From 5a513fd6fdb4ad67be7d6d7c6d832af5fe44f017 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Tue, 27 Aug 2024 21:11:34 +0200 Subject: [PATCH] wasm gc: improve disassembler to print type section --- .../wasm/disasm/BaseDisassemblyListener.java | 117 +++++++++++ .../backend/wasm/disasm/Disassembler.java | 97 +++++++++ .../DisassemblyCodeSectionListener.java | 127 ++---------- .../DisassemblyTypeSectionListener.java | 130 ++++++++++++ .../wasm/parser/BaseSectionParser.java | 191 ++++++++++++++++++ .../wasm/parser/CodeSectionListener.java | 8 +- .../wasm/parser/CodeSectionParser.java | 159 +-------------- .../backend/wasm/parser/SectionListener.java | 24 +++ .../wasm/parser/TypeSectionListener.java | 57 ++++++ .../wasm/parser/TypeSectionParser.java | 138 +++++++++++++ .../wasm/parser/WasmHollowStorageType.java | 65 ++++++ 11 files changed, 846 insertions(+), 267 deletions(-) create mode 100644 core/src/main/java/org/teavm/backend/wasm/disasm/BaseDisassemblyListener.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/disasm/Disassembler.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/disasm/DisassemblyTypeSectionListener.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/parser/BaseSectionParser.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/parser/SectionListener.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/parser/TypeSectionListener.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/parser/TypeSectionParser.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/parser/WasmHollowStorageType.java diff --git a/core/src/main/java/org/teavm/backend/wasm/disasm/BaseDisassemblyListener.java b/core/src/main/java/org/teavm/backend/wasm/disasm/BaseDisassemblyListener.java new file mode 100644 index 000000000..8ef97351f --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/disasm/BaseDisassemblyListener.java @@ -0,0 +1,117 @@ +/* + * Copyright 2024 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.backend.wasm.disasm; + +import org.teavm.backend.wasm.parser.AddressListener; +import org.teavm.backend.wasm.parser.WasmHollowStorageType; +import org.teavm.backend.wasm.parser.WasmHollowType; + +public abstract class BaseDisassemblyListener implements AddressListener { + protected final DisassemblyWriter writer; + protected int address; + private int addressOffset; + + public BaseDisassemblyListener(DisassemblyWriter writer) { + this.writer = writer; + } + + public void setAddressOffset(int addressOffset) { + this.addressOffset = addressOffset; + } + + @Override + public void address(int address) { + this.address = address + addressOffset; + } + + protected void writeBlockType(WasmHollowType type) { + if (type != null) { + writer.write(" "); + writeType(type); + } + } + + protected void writeType(WasmHollowStorageType type) { + if (type instanceof WasmHollowStorageType.Packed) { + switch (((WasmHollowStorageType.Packed) type).type) { + case INT8: + writer.write("i8"); + return; + case INT16: + writer.write("i16"); + return; + } + } else { + writeType(type.asUnpackedType()); + } + } + + protected void writeType(WasmHollowType type) { + if (type != null) { + if (type instanceof WasmHollowType.Number) { + switch (((WasmHollowType.Number) type).number) { + case INT32: + writer.write("i32"); + return; + case INT64: + writer.write("i64"); + return; + case FLOAT32: + writer.write("f32"); + return; + case FLOAT64: + writer.write("f64"); + return; + default: + break; + } + } else if (type instanceof WasmHollowType.SpecialReference) { + switch (((WasmHollowType.SpecialReference) type).kind) { + case ANY: + writer.write("anyref"); + return; + case FUNC: + writer.write("funcref"); + return; + case ARRAY: + writer.write("arrayref"); + return; + case EXTERN: + writer.write("externref"); + return; + case STRUCT: + writer.write("structref"); + return; + case I31: + writer.write("i31ref"); + return; + default: + throw new IllegalArgumentException(); + } + } else if (type instanceof WasmHollowType.CompositeReference) { + writer.write("(ref null "); + writeTypeId(((WasmHollowType.CompositeReference) type).index); + writer.write(")"); + return; + } + } + writer.write("unknown"); + } + + protected void writeTypeId(int index) { + writer.write(String.valueOf(index)); + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/disasm/Disassembler.java b/core/src/main/java/org/teavm/backend/wasm/disasm/Disassembler.java new file mode 100644 index 000000000..95f033932 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/disasm/Disassembler.java @@ -0,0 +1,97 @@ +/* + * Copyright 2024 konsoletyper. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.backend.wasm.disasm; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.Writer; +import java.nio.file.Files; +import java.util.function.Consumer; +import org.teavm.backend.wasm.parser.CodeSectionParser; +import org.teavm.backend.wasm.parser.ModuleParser; +import org.teavm.backend.wasm.parser.TypeSectionParser; +import org.teavm.common.AsyncInputStream; +import org.teavm.common.ByteArrayAsyncInputStream; + +public final class Disassembler { + private PrintWriter out; + private DisassemblyWriter writer; + + public Disassembler(Writer writer) { + out = new PrintWriter(writer); + this.writer = new DisassemblyWriter(out, true); + } + + public void startModule() { + writer.write("(module").indent().eol(); + } + + public void endModule() { + writer.write(")").eol(); + } + + public void disassemble(byte[] bytes) { + startModule(); + read(bytes); + endModule(); + } + + public void read(byte[] bytes) { + var input = new ByteArrayAsyncInputStream(bytes); + var parser = createParser(input); + input.readFully(parser::parse); + } + + public ModuleParser createParser(AsyncInputStream input) { + return new ModuleParser(input) { + @Override + protected Consumer getSectionConsumer(int code, int pos, String name) { + return Disassembler.this.getSectionConsumer(code, pos, name); + } + }; + } + + public Consumer getSectionConsumer(int code, int pos, String name) { + if (code == 1) { + return bytes -> { + var disassembler = new DisassemblyTypeSectionListener(writer); + disassembler.setAddressOffset(pos); + var sectionParser = new TypeSectionParser(disassembler, disassembler); + sectionParser.parse(bytes); + out.flush(); + }; + } else if (code == 10) { + return bytes -> { + var disassembler = new DisassemblyCodeSectionListener(writer); + disassembler.setAddressOffset(pos); + var sectionParser = new CodeSectionParser(disassembler, disassembler); + sectionParser.parse(bytes); + out.flush(); + }; + } else { + return null; + } + } + + public static void main(String[] args) throws IOException { + var file = new File(args[0]); + var bytes = Files.readAllBytes(file.toPath()); + var disassembler = new Disassembler(new OutputStreamWriter(System.out)); + disassembler.disassemble(bytes); + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/disasm/DisassemblyCodeSectionListener.java b/core/src/main/java/org/teavm/backend/wasm/disasm/DisassemblyCodeSectionListener.java index a02fb81ee..234088daa 100644 --- a/core/src/main/java/org/teavm/backend/wasm/disasm/DisassemblyCodeSectionListener.java +++ b/core/src/main/java/org/teavm/backend/wasm/disasm/DisassemblyCodeSectionListener.java @@ -15,11 +15,6 @@ */ package org.teavm.backend.wasm.disasm; -import java.io.File; -import java.io.IOException; -import java.io.PrintWriter; -import java.nio.file.Files; -import java.util.function.Consumer; import org.teavm.backend.wasm.model.WasmNumType; import org.teavm.backend.wasm.model.expression.WasmFloatBinaryOperation; import org.teavm.backend.wasm.model.expression.WasmFloatType; @@ -34,35 +29,16 @@ import org.teavm.backend.wasm.parser.AddressListener; import org.teavm.backend.wasm.parser.BranchOpcode; import org.teavm.backend.wasm.parser.CodeListener; import org.teavm.backend.wasm.parser.CodeSectionListener; -import org.teavm.backend.wasm.parser.CodeSectionParser; import org.teavm.backend.wasm.parser.LocalOpcode; -import org.teavm.backend.wasm.parser.ModuleParser; import org.teavm.backend.wasm.parser.Opcode; import org.teavm.backend.wasm.parser.WasmHollowType; -import org.teavm.common.ByteArrayAsyncInputStream; -public class DisassemblyCodeSectionListener implements AddressListener, CodeSectionListener, CodeListener { - private DisassemblyWriter writer; - private int address; - private int addressOffset; +public class DisassemblyCodeSectionListener extends BaseDisassemblyListener implements AddressListener, + CodeSectionListener, CodeListener { private int blockIdGen; public DisassemblyCodeSectionListener(DisassemblyWriter writer) { - this.writer = writer; - } - - public void setAddressOffset(int addressOffset) { - this.addressOffset = addressOffset; - } - - @Override - public void address(int address) { - this.address = address + addressOffset; - } - - @Override - public void sectionStart(int functionCount) { - writer.address(address).write("(; code section ;)").eol(); + super(writer); } @Override @@ -80,7 +56,9 @@ public class DisassemblyCodeSectionListener implements AddressListener, CodeSect public void local(int start, int count, WasmHollowType type) { writer.address(address); for (int i = 0; i < count; ++i) { - writer.write("(local (; " + (i + start) + " ;) " + typeToString(type) + ")").eol(); + writer.write("(local (; " + (i + start) + " ;) "); + writeType(type); + writer.write(")").eol(); } } @@ -100,52 +78,6 @@ public class DisassemblyCodeSectionListener implements AddressListener, CodeSect writer.outdent().write(")").eol(); } - private String blockTypeToString(WasmHollowType type) { - if (type == null) { - return ""; - } else { - return " " + typeToString(type); - } - } - - private String typeToString(WasmHollowType type) { - if (type != null) { - if (type instanceof WasmHollowType.Number) { - switch (((WasmHollowType.Number) type).number) { - case INT32: - return "i32"; - case INT64: - return "i64"; - case FLOAT32: - return "f32"; - case FLOAT64: - return "f64"; - default: - break; - } - } else if (type instanceof WasmHollowType.SpecialReference) { - switch (((WasmHollowType.SpecialReference) type).kind) { - case ANY: - return "any"; - case FUNC: - return "func"; - case ARRAY: - return "array"; - case EXTERN: - return "extern"; - case STRUCT: - return "struct"; - case I31: - return "i31"; - default: - throw new IllegalArgumentException(); - } - } else if (type instanceof WasmHollowType.CompositeReference) { - return String.valueOf(((WasmHollowType.CompositeReference) type).index); - } - } - return "unknown"; - } @Override public void error(int depth) { @@ -160,8 +92,9 @@ public class DisassemblyCodeSectionListener implements AddressListener, CodeSect public int startBlock(boolean loop, WasmHollowType type) { writer.address(address); var label = blockIdGen++; - writer.write(loop ? "loop" : "block").write(" $label_" + label).write(blockTypeToString(type)) - .indent().eol(); + writer.write(loop ? "loop" : "block").write(" $label_" + label); + writeBlockType(type); + writer.indent().eol(); return label; } @@ -169,7 +102,9 @@ public class DisassemblyCodeSectionListener implements AddressListener, CodeSect public int startConditionalBlock(WasmHollowType type) { writer.address(address); var label = blockIdGen++; - writer.write("if ").write(" $label_" + label).write(blockTypeToString(type)).indent().eol(); + writer.write("if ").write(" $label_" + label); + writeBlockType(type); + writer.indent().eol(); return label; } @@ -183,7 +118,9 @@ public class DisassemblyCodeSectionListener implements AddressListener, CodeSect public int startTry(WasmHollowType type) { writer.address(address); var label = blockIdGen++; - writer.write("try ").write(" $label_" + label).write(blockTypeToString(type)).indent().eol(); + writer.write("try ").write(" $label_" + label); + writeBlockType(type); + writer.indent().eol(); return label; } @@ -827,7 +764,9 @@ public class DisassemblyCodeSectionListener implements AddressListener, CodeSect @Override public void nullConstant(WasmHollowType.Reference type) { - writer.address(address).write("ref.null ").write(typeToString(type)).eol(); + writer.address(address).write("ref.null "); + writeType(type); + writer.eol(); } @Override @@ -836,7 +775,8 @@ public class DisassemblyCodeSectionListener implements AddressListener, CodeSect if (!nullable) { writer.write("null "); } - writer.write(typeToString(type)).write(")").eol(); + writeType(type); + writer.write(")").eol(); } @Override @@ -845,7 +785,8 @@ public class DisassemblyCodeSectionListener implements AddressListener, CodeSect if (!nullable) { writer.write("null "); } - writer.write(typeToString(type)).write(")").eol(); + writeType(type); + writer.write(")").eol(); } @Override @@ -914,28 +855,4 @@ public class DisassemblyCodeSectionListener implements AddressListener, CodeSect public void int31Get(WasmSignedType signedType) { writer.address(address).write("ref.i31_").write(signedType == WasmSignedType.SIGNED ? "s" : "u").eol(); } - - public static void main(String[] args) throws IOException { - var file = new File(args[0]); - var bytes = Files.readAllBytes(file.toPath()); - var input = new ByteArrayAsyncInputStream(bytes); - var parser = new ModuleParser(input) { - @Override - protected Consumer getSectionConsumer(int code, int pos, String name) { - if (code == 10) { - return bytes -> { - var out = new PrintWriter(System.out); - var writer = new DisassemblyWriter(out, true); - var disassembler = new DisassemblyCodeSectionListener(writer); - disassembler.setAddressOffset(pos); - var sectionParser = new CodeSectionParser(disassembler, disassembler); - sectionParser.parse(bytes); - out.flush(); - }; - } - return null; - } - }; - input.readFully(parser::parse); - } } diff --git a/core/src/main/java/org/teavm/backend/wasm/disasm/DisassemblyTypeSectionListener.java b/core/src/main/java/org/teavm/backend/wasm/disasm/DisassemblyTypeSectionListener.java new file mode 100644 index 000000000..d7979780d --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/disasm/DisassemblyTypeSectionListener.java @@ -0,0 +1,130 @@ +/* + * Copyright 2024 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.backend.wasm.disasm; + +import org.teavm.backend.wasm.parser.AddressListener; +import org.teavm.backend.wasm.parser.TypeSectionListener; +import org.teavm.backend.wasm.parser.WasmHollowStorageType; +import org.teavm.backend.wasm.parser.WasmHollowType; + +public class DisassemblyTypeSectionListener extends BaseDisassemblyListener implements AddressListener, + TypeSectionListener { + private boolean currentTypeNeedsClosing; + private boolean emittingReturn; + private int fieldIndex; + private boolean needsFieldIndex; + + public DisassemblyTypeSectionListener(DisassemblyWriter writer) { + super(writer); + } + + @Override + public void startRecType(int count) { + writer.address(address).write("(rec ").indent().eol(); + } + + @Override + public void endRecType() { + writer.outdent().write(")").eol(); + } + + @Override + public void startType(int index, boolean open, int[] supertypes) { + writer.address(address).write("(type (; ").write(String.valueOf(index)).write(" ;) "); + if (!open || supertypes.length > 0) { + currentTypeNeedsClosing = true; + writer.write("(sub "); + if (!open) { + writer.write("final"); + } + for (var supertype : supertypes) { + writer.write(supertype + " "); + } + } + writer.indent().eol(); + } + + @Override + public void startArrayType() { + writer.address(address).write("(array ").indent().eol(); + } + + @Override + public void endArrayType() { + writer.outdent().write(")").eol(); + } + + @Override + public void startStructType(int fieldCount) { + needsFieldIndex = true; + writer.address(address).write("(struct ").indent().eol(); + } + + @Override + public void field(WasmHollowStorageType hollowType, boolean mutable) { + writer.address(address).write("(field "); + if (needsFieldIndex) { + writer.write("(; " + fieldIndex++ + " ;) "); + } + if (mutable) { + writer.write("(mut "); + } + writeType(hollowType); + if (mutable) { + writer.write(")"); + } + writer.write(")").eol(); + } + + @Override + public void endStructType() { + writer.outdent().write(")").eol(); + fieldIndex = 0; + } + + @Override + public void funcType(int paramCount) { + writer.address(address).write("(struct ").indent().eol(); + } + + @Override + public void funcTypeResults(int returnCount) { + emittingReturn = true; + } + + @Override + public void resultType(WasmHollowType type) { + writer.address(address).write("(").write(emittingReturn ? "result" : "param").write(" "); + writeType(type); + writer.write(")").eol(); + } + + @Override + public void endFuncType() { + emittingReturn = false; + writer.outdent().write(")").eol(); + } + + @Override + public void endType() { + writer.outdent(); + if (currentTypeNeedsClosing) { + writer.write(")"); + currentTypeNeedsClosing = false; + } + writer.write(")").eol(); + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/parser/BaseSectionParser.java b/core/src/main/java/org/teavm/backend/wasm/parser/BaseSectionParser.java new file mode 100644 index 000000000..f8e25b70b --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/parser/BaseSectionParser.java @@ -0,0 +1,191 @@ +/* + * Copyright 2024 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.backend.wasm.parser; + +public abstract class BaseSectionParser { + protected AddressListener addressListener; + protected byte[] data; + protected int ptr; + private int lastReportedPtr = -1; + + public BaseSectionParser(AddressListener addressListener) { + this.addressListener = addressListener; + } + + public void parse(byte[] data) { + this.data = data; + ptr = 0; + try { + parseContent(); + } finally { + this.data = null; + } + } + + protected abstract void parseContent(); + + protected WasmHollowStorageType readStorageType() { + var typeId = data[ptr]; + switch (typeId) { + case 0x78: + ++ptr; + return WasmHollowStorageType.INT8; + case 0x77: + ++ptr; + return WasmHollowStorageType.INT16; + default: + return new WasmHollowStorageType.Regular(readType()); + } + } + + protected WasmHollowType readType() { + var typeId = data[ptr++]; + switch (typeId) { + case 0x7F: + return WasmHollowType.INT32; + case 0x7E: + return WasmHollowType.INT64; + case 0x7D: + return WasmHollowType.FLOAT32; + case 0x7C: + return WasmHollowType.FLOAT64; + case 0x63: + return readHeapType(); + case 0x40: + return null; + default: + return readAbsHeapType(typeId); + } + } + + protected WasmHollowType.Reference readHeapType() { + var typeId = data[ptr]; + if ((typeId & 0xC0) == 0x40) { + var result = readAbsHeapType(typeId); + ++ptr; + return result; + } + return new WasmHollowType.CompositeReference(readLEB()); + } + + protected WasmHollowType.SpecialReference readAbsHeapType(int typeId) { + switch (typeId) { + case 0x70: + return WasmHollowType.Reference.FUNC; + case 0x6F: + return WasmHollowType.Reference.EXTERN; + case 0x6E: + return WasmHollowType.Reference.ANY; + case 0x6C: + return WasmHollowType.Reference.I31; + case 0x6B: + return WasmHollowType.Reference.STRUCT; + case 0x6A: + return WasmHollowType.Reference.ARRAY; + default: + throw new ParseException("Unknown type", ptr); + } + } + + protected void reportAddress() { + if (ptr != lastReportedPtr) { + lastReportedPtr = ptr; + if (addressListener != null) { + addressListener.address(ptr); + } + } + } + + protected int readSignedLEB() { + var result = 0; + var shift = 0; + while (true) { + var digit = data[ptr++]; + result |= (digit & 0x7F) << shift; + if ((digit & 0x80) == 0) { + if ((digit & 0x40) != 0) { + result |= -1 << (shift + 7); + } + break; + } + shift += 7; + } + return result; + } + + protected int readLEB() { + var result = 0; + var shift = 0; + while (true) { + var digit = data[ptr++]; + result |= (digit & 0x7F) << shift; + if ((digit & 0x80) == 0) { + break; + } + shift += 7; + } + return result; + } + + protected long readSignedLongLEB() { + var result = 0L; + var shift = 0; + while (true) { + var digit = data[ptr++]; + result |= (digit & 0x7FL) << shift; + if ((digit & 0x80) == 0) { + if ((digit & 0x40) != 0) { + result |= -1L << (shift + 7); + } + break; + } + shift += 7; + } + return result; + } + + protected long readLongLEB() { + var result = 0L; + var shift = 0; + while (true) { + var digit = data[ptr++]; + result |= (digit & 0x7FL) << shift; + if ((digit & 0x80) == 0) { + break; + } + shift += 7; + } + return result; + } + + protected int readFixedInt() { + return ((data[ptr++] & 0xFF) << 24) + | ((data[ptr++] & 0xFF) << 16) + | ((data[ptr++] & 0xFF) << 8) + | (data[ptr++] & 0xFF); + } + + protected long readFixedLong() { + return ((data[ptr++] & 0xFFL) << 56) + | ((data[ptr++] & 0xFFL) << 48) + | ((data[ptr++] & 0xFFL) << 40) + | ((data[ptr++] & 0xFFL) << 32) + | ((data[ptr++] & 0xFFL) << 24) + | ((data[ptr++] & 0xFF) << 16) + | ((data[ptr++] & 0xFF) << 8) + | (data[ptr++] & 0xFF); + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/parser/CodeSectionListener.java b/core/src/main/java/org/teavm/backend/wasm/parser/CodeSectionListener.java index 9fa37a535..913bace0c 100644 --- a/core/src/main/java/org/teavm/backend/wasm/parser/CodeSectionListener.java +++ b/core/src/main/java/org/teavm/backend/wasm/parser/CodeSectionListener.java @@ -15,10 +15,7 @@ */ package org.teavm.backend.wasm.parser; -public interface CodeSectionListener { - default void sectionStart(int functionCount) { - } - +public interface CodeSectionListener extends SectionListener { default boolean functionStart(int index, int size) { return false; } @@ -35,7 +32,4 @@ public interface CodeSectionListener { default void functionEnd() { } - - default void sectionEnd() { - } } diff --git a/core/src/main/java/org/teavm/backend/wasm/parser/CodeSectionParser.java b/core/src/main/java/org/teavm/backend/wasm/parser/CodeSectionParser.java index 3a7ffce29..c64401a26 100644 --- a/core/src/main/java/org/teavm/backend/wasm/parser/CodeSectionParser.java +++ b/core/src/main/java/org/teavm/backend/wasm/parser/CodeSectionParser.java @@ -28,31 +28,18 @@ import org.teavm.backend.wasm.model.expression.WasmIntType; import org.teavm.backend.wasm.model.expression.WasmIntUnaryOperation; import org.teavm.backend.wasm.model.expression.WasmSignedType; -public class CodeSectionParser { - private AddressListener addressListener; +public class CodeSectionParser extends BaseSectionParser { private CodeSectionListener listener; - private byte[] data; - private int ptr; private CodeListener codeListener; - private int lastReportedPtr = -1; private List blockStack = new ArrayList<>(); public CodeSectionParser(AddressListener addressListener, CodeSectionListener listener) { - this.addressListener = addressListener; + super(addressListener); this.listener = listener; } - public void parse(byte[] data) { - this.data = data; - ptr = 0; - try { - parseFunctions(); - } finally { - this.data = null; - } - } - - private void parseFunctions() { + @Override + protected void parseContent() { reportAddress(); int count = readLEB(); listener.sectionStart(count); @@ -853,144 +840,6 @@ public class CodeSectionParser { codeListener.tableBranch(depths, targets, defaultDepth, defaultTarget); } - private WasmHollowType readType() { - var typeId = data[ptr++]; - switch (typeId) { - case 0x7F: - return WasmHollowType.INT32; - case 0x7E: - return WasmHollowType.INT64; - case 0x7D: - return WasmHollowType.FLOAT32; - case 0x7C: - return WasmHollowType.FLOAT64; - case 0x63: - return readHeapType(); - case 0x40: - return null; - default: - return readAbsHeapType(typeId); - } - } - - private WasmHollowType.Reference readHeapType() { - var typeId = data[ptr]; - if ((typeId & 0xC0) == 0x40) { - var result = readAbsHeapType(typeId); - ++ptr; - return result; - } - return new WasmHollowType.CompositeReference(readLEB()); - } - - private WasmHollowType.SpecialReference readAbsHeapType(int typeId) { - switch (typeId) { - case 0x70: - return WasmHollowType.Reference.FUNC; - case 0x6F: - return WasmHollowType.Reference.EXTERN; - case 0x6E: - return WasmHollowType.Reference.ANY; - case 0x6C: - return WasmHollowType.Reference.I31; - case 0x6B: - return WasmHollowType.Reference.STRUCT; - case 0x6A: - return WasmHollowType.Reference.ARRAY; - default: - throw new ParseException("Unknown type", ptr); - } - } - - private void reportAddress() { - if (ptr != lastReportedPtr) { - lastReportedPtr = ptr; - if (addressListener != null) { - addressListener.address(ptr); - } - } - } - - private int readSignedLEB() { - var result = 0; - var shift = 0; - while (true) { - var digit = data[ptr++]; - result |= (digit & 0x7F) << shift; - if ((digit & 0x80) == 0) { - if ((digit & 0x40) != 0) { - result |= -1 << (shift + 7); - } - break; - } - shift += 7; - } - return result; - } - - private int readLEB() { - var result = 0; - var shift = 0; - while (true) { - var digit = data[ptr++]; - result |= (digit & 0x7F) << shift; - if ((digit & 0x80) == 0) { - break; - } - shift += 7; - } - return result; - } - - private long readSignedLongLEB() { - var result = 0L; - var shift = 0; - while (true) { - var digit = data[ptr++]; - result |= (digit & 0x7FL) << shift; - if ((digit & 0x80) == 0) { - if ((digit & 0x40) != 0) { - result |= -1L << (shift + 7); - } - break; - } - shift += 7; - } - return result; - } - - private long readLongLEB() { - var result = 0L; - var shift = 0; - while (true) { - var digit = data[ptr++]; - result |= (digit & 0x7FL) << shift; - if ((digit & 0x80) == 0) { - break; - } - shift += 7; - } - return result; - } - - private int readFixedInt() { - return ((data[ptr++] & 0xFF) << 24) - | ((data[ptr++] & 0xFF) << 16) - | ((data[ptr++] & 0xFF) << 8) - | (data[ptr++] & 0xFF); - } - - private long readFixedLong() { - return ((data[ptr++] & 0xFFL) << 56) - | ((data[ptr++] & 0xFFL) << 48) - | ((data[ptr++] & 0xFFL) << 40) - | ((data[ptr++] & 0xFFL) << 32) - | ((data[ptr++] & 0xFFL) << 24) - | ((data[ptr++] & 0xFF) << 16) - | ((data[ptr++] & 0xFF) << 8) - | (data[ptr++] & 0xFF); - } - private static class Block { int token; diff --git a/core/src/main/java/org/teavm/backend/wasm/parser/SectionListener.java b/core/src/main/java/org/teavm/backend/wasm/parser/SectionListener.java new file mode 100644 index 000000000..ff5577167 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/parser/SectionListener.java @@ -0,0 +1,24 @@ +/* + * Copyright 2024 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.backend.wasm.parser; + +public interface SectionListener { + default void sectionStart(int functionCount) { + } + + default void sectionEnd() { + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/parser/TypeSectionListener.java b/core/src/main/java/org/teavm/backend/wasm/parser/TypeSectionListener.java new file mode 100644 index 000000000..9873aa322 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/parser/TypeSectionListener.java @@ -0,0 +1,57 @@ +/* + * Copyright 2024 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.backend.wasm.parser; + +public interface TypeSectionListener extends SectionListener { + default void startRecType(int count) { + } + + default void endRecType() { + } + + default void startType(int index, boolean open, int[] supertypes) { + } + + default void startArrayType() { + } + + default void endArrayType() { + } + + default void startStructType(int fieldCount) { + } + + default void field(WasmHollowStorageType hollowType, boolean mutable) { + } + + default void endStructType() { + } + + default void funcType(int paramCount) { + } + + default void funcTypeResults(int returnCount) { + } + + default void resultType(WasmHollowType type) { + } + + default void endFuncType() { + } + + default void endType() { + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/parser/TypeSectionParser.java b/core/src/main/java/org/teavm/backend/wasm/parser/TypeSectionParser.java new file mode 100644 index 000000000..2c5c7ea86 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/parser/TypeSectionParser.java @@ -0,0 +1,138 @@ +/* + * Copyright 2024 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.backend.wasm.parser; + +public class TypeSectionParser extends BaseSectionParser { + private TypeSectionListener listener; + private int typeIndex; + + public TypeSectionParser(AddressListener addressListener, TypeSectionListener listener) { + super(addressListener); + this.listener = listener; + } + + @Override + protected void parseContent() { + reportAddress(); + int count = readLEB(); + listener.sectionStart(count); + for (var i = 0; i < count; ++i) { + parseType(); + } + reportAddress(); + listener.sectionEnd(); + typeIndex = 0; + } + + private void parseType() { + if (data[ptr] == 0x4E) { + parseRecType(); + } else { + parseSubtype(); + } + } + + private void parseRecType() { + reportAddress(); + ++ptr; + var count = readLEB(); + listener.startRecType(count); + for (var i = 0; i < count; ++i) { + parseSubtype(); + } + listener.endRecType(); + } + + private void parseSubtype() { + switch (data[ptr]) { + case 0x50: + reportAddress(); + ++ptr; + parseCompositeType(true, readSupertypes()); + break; + case 0x4F: + reportAddress(); + ++ptr; + parseCompositeType(false, readSupertypes()); + break; + default: + parseCompositeType(true, new int[0]); + break; + } + } + + private void parseCompositeType(boolean open, int[] supertypes) { + reportAddress(); + listener.startType(typeIndex++, open, supertypes); + switch (data[ptr]) { + case 0x5E: + reportAddress(); + ++ptr; + listener.startArrayType(); + parseField(); + listener.endArrayType(); + break; + case 0x5F: { + reportAddress(); + ++ptr; + var fieldCount = readLEB(); + listener.startStructType(fieldCount); + for (var i = 0; i < fieldCount; ++i) { + parseField(); + } + listener.endStructType(); + break; + } + case 0x60: { + reportAddress(); + ++ptr; + var paramCount = readLEB(); + listener.funcType(paramCount); + for (var i = 0; i < paramCount; ++i) { + reportAddress(); + listener.resultType(readType()); + } + var resultCount = readLEB(); + listener.funcTypeResults(resultCount); + for (var i = 0; i < resultCount; ++i) { + reportAddress(); + listener.resultType(readType()); + } + listener.endFuncType(); + break; + } + default: + throw new ParseException("Unknown type declaration", ptr); + } + listener.endType(); + } + + private void parseField() { + reportAddress(); + var type = readStorageType(); + var mutable = data[ptr++] != 0; + listener.field(type, mutable); + } + + private int[] readSupertypes() { + var count = readLEB(); + var result = new int[count]; + for (var i = 0; i < count; ++i) { + result[i] = readLEB(); + } + return result; + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/parser/WasmHollowStorageType.java b/core/src/main/java/org/teavm/backend/wasm/parser/WasmHollowStorageType.java new file mode 100644 index 000000000..703ee3629 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/parser/WasmHollowStorageType.java @@ -0,0 +1,65 @@ +/* + * Copyright 2024 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.backend.wasm.parser; + +import org.teavm.backend.wasm.model.WasmPackedType; + +public abstract class WasmHollowStorageType { + public static final Packed INT16 = new Packed(WasmPackedType.INT16); + public static final Packed INT8 = new Packed(WasmPackedType.INT8); + + private WasmHollowStorageType() { + } + + public abstract WasmHollowType asUnpackedType(); + + public static Packed packed(WasmPackedType type) { + switch (type) { + case INT8: + return INT8; + case INT16: + return INT16; + default: + throw new IllegalArgumentException(); + } + } + + public static final class Regular extends WasmHollowStorageType { + public final WasmHollowType type; + + Regular(WasmHollowType type) { + this.type = type; + } + + @Override + public WasmHollowType asUnpackedType() { + return type; + } + } + + public static final class Packed extends WasmHollowStorageType { + public final WasmPackedType type; + + private Packed(WasmPackedType type) { + this.type = type; + } + + @Override + public WasmHollowType asUnpackedType() { + return WasmHollowType.INT32; + } + } +}