diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugInfoParser.java b/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugInfoParser.java index 22f1e8f01..98b454e3b 100644 --- a/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugInfoParser.java +++ b/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugInfoParser.java @@ -72,8 +72,8 @@ public class DebugInfoParser extends ModuleParser { private void parseCode(byte[] data) { var builder = new ControlFlowParser(); - var codeParser = new CodeSectionParser(builder, builder); - codeParser.parse(data); + var codeParser = new CodeSectionParser(builder); + codeParser.parse(builder, data); controlFlow = builder.build(); } 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 index fbb31855a..5b7fc421b 100644 --- a/core/src/main/java/org/teavm/backend/wasm/disasm/BaseDisassemblyListener.java +++ b/core/src/main/java/org/teavm/backend/wasm/disasm/BaseDisassemblyListener.java @@ -15,30 +15,18 @@ */ 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 { +public abstract class BaseDisassemblyListener { protected final DisassemblyWriter writer; protected final NameProvider nameProvider; - protected int address; - private int addressOffset; public BaseDisassemblyListener(DisassemblyWriter writer, NameProvider nameProvider) { this.writer = writer; this.nameProvider = nameProvider; } - 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(" "); 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 index 798e61ed4..99164b868 100644 --- a/core/src/main/java/org/teavm/backend/wasm/disasm/Disassembler.java +++ b/core/src/main/java/org/teavm/backend/wasm/disasm/Disassembler.java @@ -24,6 +24,7 @@ import java.nio.file.Files; import java.util.function.Consumer; import org.teavm.backend.wasm.parser.AddressListener; import org.teavm.backend.wasm.parser.CodeSectionParser; +import org.teavm.backend.wasm.parser.GlobalSectionParser; import org.teavm.backend.wasm.parser.ImportSectionListener; import org.teavm.backend.wasm.parser.ImportSectionParser; import org.teavm.backend.wasm.parser.ModuleParser; @@ -96,23 +97,36 @@ public final class Disassembler { }; if (code == 1) { return bytes -> { + writer.write("(; type section size: " + bytes.length + " ;)"); var typeWriter = new DisassemblyTypeSectionListener(writer, nameProvider); - typeWriter.setAddressOffset(pos); - var sectionParser = new TypeSectionParser(typeWriter, typeWriter); - sectionParser.parse(bytes); + writer.setAddressOffset(pos); + var sectionParser = new TypeSectionParser(typeWriter); + sectionParser.parse(writer.addressListener, bytes); out.flush(); }; } else if (code == 2) { return bytes -> { - var parser = new ImportSectionParser(AddressListener.EMPTY, importListener); - parser.parse(bytes); + var parser = new ImportSectionParser(importListener); + parser.parse(AddressListener.EMPTY, bytes); + }; + } else if (code == 6) { + return bytes -> { + writer.write("(; global section size: " + bytes.length + " ;)"); + var globalWriter = new DisassemblyGlobalSectionListener(writer, nameProvider); + writer.setAddressOffset(pos); + var sectionParser = new GlobalSectionParser(globalWriter); + sectionParser.setFunctionIndexOffset(importListener.count); + sectionParser.parse(writer.addressListener, bytes); + out.flush(); }; } else if (code == 10) { return bytes -> { var disassembler = new DisassemblyCodeSectionListener(writer, nameProvider); - disassembler.setAddressOffset(pos); - var sectionParser = new CodeSectionParser(disassembler, disassembler); - sectionParser.parse(bytes); + writer.setAddressOffset(pos); + writer.write("(; code section size: " + bytes.length + " ;)"); + var sectionParser = new CodeSectionParser(disassembler); + sectionParser.setFunctionIndexOffset(importListener.count); + sectionParser.parse(writer.addressListener, bytes); out.flush(); }; } else { @@ -124,7 +138,7 @@ public final class Disassembler { if (code == 0 && name.equals("name")) { return bytes -> { var parser = new NameSectionParser(listener); - parser.parse(bytes); + parser.parse(AddressListener.EMPTY, bytes); }; } else { return null; diff --git a/core/src/main/java/org/teavm/backend/wasm/disasm/DisassemblyCodeListener.java b/core/src/main/java/org/teavm/backend/wasm/disasm/DisassemblyCodeListener.java new file mode 100644 index 000000000..8ddacd637 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/disasm/DisassemblyCodeListener.java @@ -0,0 +1,849 @@ +/* + * 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.model.WasmNumType; +import org.teavm.backend.wasm.model.expression.WasmFloatBinaryOperation; +import org.teavm.backend.wasm.model.expression.WasmFloatType; +import org.teavm.backend.wasm.model.expression.WasmFloatUnaryOperation; +import org.teavm.backend.wasm.model.expression.WasmInt32Subtype; +import org.teavm.backend.wasm.model.expression.WasmInt64Subtype; +import org.teavm.backend.wasm.model.expression.WasmIntBinaryOperation; +import org.teavm.backend.wasm.model.expression.WasmIntType; +import org.teavm.backend.wasm.model.expression.WasmIntUnaryOperation; +import org.teavm.backend.wasm.model.expression.WasmSignedType; +import org.teavm.backend.wasm.parser.BranchOpcode; +import org.teavm.backend.wasm.parser.CodeListener; +import org.teavm.backend.wasm.parser.LocalOpcode; +import org.teavm.backend.wasm.parser.Opcode; +import org.teavm.backend.wasm.parser.WasmHollowType; + +public class DisassemblyCodeListener extends BaseDisassemblyListener implements CodeListener { + private int blockIdGen; + private int currentFunctionId; + + public DisassemblyCodeListener(DisassemblyWriter writer, NameProvider nameProvider) { + super(writer, nameProvider); + } + + public void setCurrentFunctionId(int currentFunctionId) { + this.currentFunctionId = currentFunctionId; + } + + @Override + public void error(int depth) { + writer.address(); + writer.write("error").eol(); + for (int i = 0; i < depth; ++i) { + writer.outdent(); + } + } + + @Override + public int startBlock(boolean loop, WasmHollowType type) { + writer.address(); + var label = blockIdGen++; + writer.write(loop ? "loop" : "block").write(" $label_" + label); + writeBlockType(type); + writer.indent().eol(); + return label; + } + + @Override + public int startConditionalBlock(WasmHollowType type) { + writer.address(); + var label = blockIdGen++; + writer.write("if ").write(" $label_" + label); + writeBlockType(type); + writer.indent().eol(); + return label; + } + + @Override + public void startElseSection(int token) { + writer.address(); + writer.outdent().write("else (; $label_" + token + " ;)").indent().eol(); + } + + @Override + public int startTry(WasmHollowType type) { + writer.address(); + var label = blockIdGen++; + writer.write("try ").write(" $label_" + label); + writeBlockType(type); + writer.indent().eol(); + return label; + } + + @Override + public void startCatch(int tagIndex) { + writer.outdent().address(); + writer.write("catch ").write(String.valueOf(tagIndex)).indent().eol(); + } + + @Override + public void endBlock(int token, boolean loop) { + writer.address().outdent().write("end (; $label_" + token + " ;)").eol(); + } + + @Override + public void branch(BranchOpcode opcode, int depth, int target) { + writer.address(); + switch (opcode) { + case BR: + writer.write("br"); + break; + case BR_IF: + writer.write("br_if"); + break; + } + writer.write(" $label_" + target).eol(); + } + + @Override + public void tableBranch(int[] depths, int[] targets, int defaultDepth, int defaultTarget) { + writer.address(); + writer.write("br_table"); + for (var target : targets) { + writer.write(" $label_" + target); + } + writer.write(" $label_" + defaultTarget).eol(); + } + + @Override + public void throwInstruction(int tagIndex) { + writer.address(); + writer.write("throw ").write(String.valueOf(tagIndex)).eol(); + } + + @Override + public void opcode(Opcode opcode) { + writer.address(); + switch (opcode) { + case UNREACHABLE: + writer.write("unreachable"); + break; + case NOP: + writer.write("nop"); + break; + case RETURN: + writer.write("return"); + break; + case DROP: + writer.write("drop"); + break; + case REF_EQ: + writer.write("ref.eq"); + break; + case ARRAY_LENGTH: + writer.write("array.length"); + break; + } + writer.eol(); + } + + @Override + public void local(LocalOpcode opcode, int index) { + writer.address(); + switch (opcode) { + case GET: + writer.write("local.get"); + break; + case SET: + writer.write("local.set"); + break; + } + writer.write(" "); + writeLocalRef(currentFunctionId, index); + writer.eol(); + } + + @Override + public void getGlobal(int globalIndex) { + writer.address().write("global.get "); + writeGlobalRef(globalIndex); + writer.eol(); + } + + @Override + public void setGlobal(int globalIndex) { + writer.address().write("global.set "); + writeGlobalRef(globalIndex); + writer.eol(); + } + + @Override + public void call(int functionIndex) { + writer.address(); + writer.write("call "); + writeFunctionRef(functionIndex); + writer.eol(); + } + + @Override + public void indirectCall(int typeIndex, int tableIndex) { + writer.address(); + writer.write("call_indirect " + tableIndex + " " + typeIndex).eol(); + } + + @Override + public void callReference(int typeIndex) { + writer.address(); + writer.write("call_ref " + typeIndex).eol(); + } + + @Override + public void loadInt32(WasmInt32Subtype convertFrom, int align, int offset) { + writer.address(); + var defaultAlign = 0; + switch (convertFrom) { + case INT8: + writer.write("i32.load8_s"); + defaultAlign = 1; + break; + case UINT8: + writer.write("i32.load8_u"); + defaultAlign = 1; + break; + case INT16: + writer.write("i32.load16_s"); + defaultAlign = 2; + break; + case UINT16: + writer.write("i32.load16_u"); + defaultAlign = 2; + break; + case INT32: + writer.write("i32.load"); + defaultAlign = 4; + break; + } + writeMemArg(align, defaultAlign, offset); + writer.eol(); + } + + @Override + public void storeInt32(WasmInt32Subtype convertTo, int align, int offset) { + writer.address(); + var defaultAlign = 0; + switch (convertTo) { + case INT8: + case UINT8: + writer.write("i32.store8"); + defaultAlign = 1; + break; + case INT16: + case UINT16: + writer.write("i32.store16"); + defaultAlign = 2; + break; + case INT32: + writer.write("i32.store"); + defaultAlign = 4; + break; + } + writeMemArg(align, defaultAlign, offset); + writer.eol(); + } + + @Override + public void loadInt64(WasmInt64Subtype convertFrom, int align, int offset) { + writer.address(); + var defaultAlign = 0; + switch (convertFrom) { + case INT8: + writer.write("i64.load8_s"); + defaultAlign = 1; + break; + case UINT8: + writer.write("i64.load8_u"); + defaultAlign = 1; + break; + case INT16: + writer.write("i64.load16_s"); + defaultAlign = 2; + break; + case UINT16: + writer.write("i64.load16_u"); + defaultAlign = 2; + break; + case INT32: + writer.write("i64.load32_s"); + defaultAlign = 4; + break; + case UINT32: + writer.write("i64.load32_u"); + defaultAlign = 4; + break; + case INT64: + writer.write("i64.load"); + defaultAlign = 8; + break; + } + writeMemArg(align, defaultAlign, offset); + writer.eol(); + } + + @Override + public void storeInt64(WasmInt64Subtype convertTo, int align, int offset) { + writer.address(); + var defaultAlign = 0; + switch (convertTo) { + case INT8: + case UINT8: + writer.write("i64.store8"); + defaultAlign = 1; + break; + case INT16: + case UINT16: + writer.write("i64.store16"); + defaultAlign = 2; + break; + case INT32: + case UINT32: + writer.write("i64.store32"); + defaultAlign = 4; + break; + case INT64: + writer.write("i64.store"); + defaultAlign = 8; + break; + } + writeMemArg(align, defaultAlign, offset); + writer.eol(); + } + + @Override + public void loadFloat32(int align, int offset) { + writer.address().write("f32.load"); + writeMemArg(align, 4, offset); + writer.eol(); + } + + @Override + public void storeFloat32(int align, int offset) { + writer.address().write("f32.store"); + writeMemArg(align, 4, offset); + writer.eol(); + } + + + @Override + public void loadFloat64(int align, int offset) { + writer.address().write("f64.load"); + writeMemArg(align, 8, offset); + writer.eol(); + } + + @Override + public void storeFloat64(int align, int offset) { + writer.address().write("f64.store"); + writeMemArg(align, 8, offset); + writer.eol(); + } + + @Override + public void memoryGrow() { + writer.address().write("memory.grow").eol(); + } + + @Override + public void memoryFill() { + writer.address().write("memory.fill").eol(); + } + + @Override + public void memoryCopy() { + writer.address().write("memory.copy").eol(); + } + + private void writeMemArg(int align, int defaultAlign, int offset) { + var needsComma = false; + if (align != defaultAlign) { + writer.write(" align=" + align); + needsComma = true; + } + if (offset != 0) { + if (needsComma) { + writer.write(","); + } + writer.write(" offset=" + offset); + } + } + + @Override + public void unary(WasmIntUnaryOperation opcode, WasmIntType type) { + writer.address(); + switch (type) { + case INT32: + writer.write("i32."); + break; + case INT64: + writer.write("i64."); + break; + } + switch (opcode) { + case CLZ: + writer.write("clz"); + break; + case CTZ: + writer.write("ctz"); + break; + case EQZ: + writer.write("eqz"); + break; + case POPCNT: + writer.write("popcnt"); + break; + } + writer.eol(); + } + + @Override + public void unary(WasmFloatUnaryOperation opcode, WasmFloatType type) { + writer.address(); + switch (type) { + case FLOAT32: + writer.write("f32."); + break; + case FLOAT64: + writer.write("f64."); + break; + } + switch (opcode) { + case ABS: + writer.write("abs"); + break; + case NEG: + writer.write("neg"); + break; + case FLOOR: + writer.write("floor"); + break; + case CEIL: + writer.write("ceil"); + break; + case TRUNC: + writer.write("trunc"); + break; + case NEAREST: + writer.write("nearest"); + break; + case SQRT: + writer.write("sqrt"); + break; + case COPYSIGN: + writer.write("copysign"); + break; + } + writer.eol(); + } + + @Override + public void binary(WasmIntBinaryOperation opcode, WasmIntType type) { + writer.address(); + switch (type) { + case INT32: + writer.write("i32."); + break; + case INT64: + writer.write("i64."); + break; + } + switch (opcode) { + case ADD: + writer.write("add"); + break; + case SUB: + writer.write("sub"); + break; + case MUL: + writer.write("mul"); + break; + case DIV_SIGNED: + writer.write("div_s"); + break; + case DIV_UNSIGNED: + writer.write("div_u"); + break; + case REM_SIGNED: + writer.write("rem_s"); + break; + case REM_UNSIGNED: + writer.write("rem_u"); + break; + case AND: + writer.write("and"); + break; + case OR: + writer.write("or"); + break; + case XOR: + writer.write("xor"); + break; + case SHL: + writer.write("shl"); + break; + case SHR_SIGNED: + writer.write("shr_s"); + break; + case SHR_UNSIGNED: + writer.write("shr_u"); + break; + case ROTL: + writer.write("rotl"); + break; + case ROTR: + writer.write("rotr"); + break; + case EQ: + writer.write("eq"); + break; + case NE: + writer.write("ne"); + break; + case LT_SIGNED: + writer.write("lt_s"); + break; + case LT_UNSIGNED: + writer.write("lt_u"); + break; + case GT_SIGNED: + writer.write("gt_s"); + break; + case GT_UNSIGNED: + writer.write("gt_u"); + break; + case LE_SIGNED: + writer.write("le_s"); + break; + case LE_UNSIGNED: + writer.write("le_u"); + break; + case GE_SIGNED: + writer.write("ge_s"); + break; + case GE_UNSIGNED: + writer.write("ge_u"); + break; + } + writer.eol(); + } + + @Override + public void binary(WasmFloatBinaryOperation opcode, WasmFloatType type) { + writer.address(); + switch (type) { + case FLOAT32: + writer.write("f32."); + break; + case FLOAT64: + writer.write("f64."); + break; + } + switch (opcode) { + case ADD: + writer.write("add"); + break; + case SUB: + writer.write("sub"); + break; + case MUL: + writer.write("mul"); + break; + case DIV: + writer.write("div"); + break; + case MIN: + writer.write("min"); + break; + case MAX: + writer.write("max"); + break; + case EQ: + writer.write("eq"); + break; + case NE: + writer.write("ne"); + break; + case LT: + writer.write("lt"); + break; + case GT: + writer.write("gt"); + break; + case LE: + writer.write("le"); + break; + case GE: + writer.write("ge"); + break; + } + writer.eol(); + } + + @Override + public void convert(WasmNumType sourceType, WasmNumType targetType, boolean signed, boolean reinterpret) { + switch (targetType) { + case INT32: + writer.write("i32."); + switch (sourceType) { + case FLOAT32: + if (reinterpret) { + writer.write("reinterpret_f32"); + } else if (signed) { + writer.write("trunc_f32_s"); + } else { + writer.write("trunc_f32_u"); + } + break; + case FLOAT64: + if (signed) { + writer.write("trunc_f64_s"); + } else { + writer.write("trunc_f64_u"); + } + break; + case INT64: + writer.write("wrap_i64"); + break; + default: + writer.write("error"); + break; + } + break; + case INT64: + writer.write("i64."); + switch (sourceType) { + case FLOAT32: + if (signed) { + writer.write("trunc_f32_s"); + } else { + writer.write("trunc_f32_u"); + } + break; + case FLOAT64: + if (reinterpret) { + writer.write("reinterpret_f64"); + } else if (signed) { + writer.write("trunc_f64_s"); + } else { + writer.write("trunc_f64_u"); + } + break; + case INT32: + if (signed) { + writer.write("extend_i32_s"); + } else { + writer.write("extend_i32_u"); + } + break; + default: + writer.write("error"); + break; + } + break; + case FLOAT32: + writer.write("f32."); + switch (sourceType) { + case INT32: + if (reinterpret) { + writer.write("reinterpret_i32"); + } else if (signed) { + writer.write("convert_i32_s"); + } else { + writer.write("convert_i32_u"); + } + break; + case INT64: + if (signed) { + writer.write("convert_i64_s"); + } else { + writer.write("convert_i64_u"); + } + break; + case FLOAT64: + writer.write("demote_f64"); + break; + default: + writer.write("error"); + break; + } + break; + case FLOAT64: + writer.write("f64."); + switch (sourceType) { + case INT32: + if (signed) { + writer.write("convert_i32_s"); + } else { + writer.write("convert_i32_u"); + } + break; + case INT64: + if (reinterpret) { + writer.write("reinterpret_i64"); + } else if (signed) { + writer.write("convert_i64_s"); + } else { + writer.write("convert_i64_u"); + } + break; + case FLOAT32: + writer.write("promote_f32"); + break; + default: + writer.write("error"); + break; + } + break; + } + writer.eol(); + } + + @Override + public void int32Constant(int value) { + writer.address().write("i32.const " + value).eol(); + } + + @Override + public void int64Constant(long value) { + writer.address().write("i64.const " + value).eol(); + } + + @Override + public void float32Constant(float value) { + writer.address().write("f32.const " + Float.toHexString(value)).eol(); + } + + @Override + public void float64Constant(double value) { + writer.address().write("f64.const " + Double.toHexString(value)).eol(); + } + + @Override + public void nullConstant(WasmHollowType.Reference type) { + writer.address().write("ref.null "); + writeType(type); + writer.eol(); + } + + @Override + public void cast(WasmHollowType.Reference type, boolean nullable) { + writer.address().write("ref.cast (ref "); + if (!nullable) { + writer.write("null "); + } + writeType(type); + writer.write(")").eol(); + } + + @Override + public void test(WasmHollowType.Reference type, boolean nullable) { + writer.address().write("ref.test (ref "); + if (!nullable) { + writer.write("null "); + } + writeType(type); + writer.write(")").eol(); + } + + @Override + public void structNew(int typeIndex) { + writer.address().write("struct.new "); + writeTypeRef(typeIndex); + writer.eol(); + } + + @Override + public void structNewDefault(int typeIndex) { + writer.address().write("struct.new_default "); + writeTypeRef(typeIndex); + writer.eol(); + } + + @Override + public void structGet(WasmSignedType signedType, int typeIndex, int fieldIndex) { + writer.address(); + if (signedType == null) { + writer.write("struct.get"); + } else if (signedType == WasmSignedType.SIGNED) { + writer.write("struct.get_s"); + } else { + writer.write("struct.get_u"); + } + writer.write(" "); + writeTypeRef(typeIndex); + writer.write(" "); + writeFieldRef(typeIndex, fieldIndex); + writer.eol(); + } + + @Override + public void structSet(int typeIndex, int fieldIndex) { + writer.address().write("struct.set "); + writeTypeRef(typeIndex); + writer.write(" "); + writeFieldRef(typeIndex, fieldIndex); + writer.eol(); + } + + @Override + public void arrayNewDefault(int typeIndex) { + writer.address().write("array.new_default "); + writeTypeRef(typeIndex); + writer.eol(); + } + + @Override + public void arrayGet(WasmSignedType signedType, int typeIndex) { + writer.address(); + if (signedType == null) { + writer.write("array.get"); + } else if (signedType == WasmSignedType.SIGNED) { + writer.write("array.get_s"); + } else { + writer.write("array.get_u"); + } + writer.write(" "); + writeTypeRef(typeIndex); + writer.eol(); + } + + @Override + public void arraySet(int typeIndex) { + writer.address().write("array.set "); + writeTypeRef(typeIndex); + writer.eol(); + } + + @Override + public void functionReference(int functionIndex) { + writer.address().write("ref.func "); + writeFunctionRef(functionIndex); + writer.eol(); + } + + @Override + public void int31Reference() { + writer.address().write("ref.i31").eol(); + } + + @Override + public void int31Get(WasmSignedType signedType) { + writer.address().write("ref.i31_").write(signedType == WasmSignedType.SIGNED ? "s" : "u").eol(); + } +} 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 ba426baa9..24e3ed716 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,37 +15,23 @@ */ package org.teavm.backend.wasm.disasm; -import org.teavm.backend.wasm.model.WasmNumType; -import org.teavm.backend.wasm.model.expression.WasmFloatBinaryOperation; -import org.teavm.backend.wasm.model.expression.WasmFloatType; -import org.teavm.backend.wasm.model.expression.WasmFloatUnaryOperation; -import org.teavm.backend.wasm.model.expression.WasmInt32Subtype; -import org.teavm.backend.wasm.model.expression.WasmInt64Subtype; -import org.teavm.backend.wasm.model.expression.WasmIntBinaryOperation; -import org.teavm.backend.wasm.model.expression.WasmIntType; -import org.teavm.backend.wasm.model.expression.WasmIntUnaryOperation; -import org.teavm.backend.wasm.model.expression.WasmSignedType; -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.LocalOpcode; -import org.teavm.backend.wasm.parser.Opcode; import org.teavm.backend.wasm.parser.WasmHollowType; -public class DisassemblyCodeSectionListener extends BaseDisassemblyListener implements AddressListener, - CodeSectionListener, CodeListener { - private int blockIdGen; +public class DisassemblyCodeSectionListener extends BaseDisassemblyListener implements CodeSectionListener { private int currentFunctionId; + private DisassemblyCodeListener codeListener; public DisassemblyCodeSectionListener(DisassemblyWriter writer, NameProvider nameProvider) { super(writer, nameProvider); + codeListener = new DisassemblyCodeListener(writer, nameProvider); } @Override public boolean functionStart(int index, int size) { currentFunctionId = index; - writer.address(address).write("(func ").write("(; " + index + " ;)"); + writer.address().write("(func ").write("(; " + index + " ;)"); var name = nameProvider.function(index); if (name != null) { writer.write(" $").write(name); @@ -56,12 +42,12 @@ public class DisassemblyCodeSectionListener extends BaseDisassemblyListener impl @Override public void localsStart(int count) { - writer.address(address).write("(; locals " + count + " ;)").eol(); + writer.address().write("(; locals " + count + " ;)").eol(); } @Override public void local(int start, int count, WasmHollowType type) { - writer.address(address); + writer.address(); for (int i = 0; i < count; ++i) { writer.write("(local (; " + (i + start) + " ;) "); var name = nameProvider.local(currentFunctionId, i + start); @@ -75,8 +61,8 @@ public class DisassemblyCodeSectionListener extends BaseDisassemblyListener impl @Override public CodeListener code() { - blockIdGen = 0; - return this; + codeListener.setCurrentFunctionId(currentFunctionId); + return codeListener; } @Override @@ -88,809 +74,4 @@ public class DisassemblyCodeSectionListener extends BaseDisassemblyListener impl public void sectionEnd() { writer.outdent().write(")").eol(); } - - @Override - public void error(int depth) { - writer.address(address); - writer.write("error").eol(); - for (int i = 0; i < depth; ++i) { - writer.outdent(); - } - } - - @Override - public int startBlock(boolean loop, WasmHollowType type) { - writer.address(address); - var label = blockIdGen++; - writer.write(loop ? "loop" : "block").write(" $label_" + label); - writeBlockType(type); - writer.indent().eol(); - return label; - } - - @Override - public int startConditionalBlock(WasmHollowType type) { - writer.address(address); - var label = blockIdGen++; - writer.write("if ").write(" $label_" + label); - writeBlockType(type); - writer.indent().eol(); - return label; - } - - @Override - public void startElseSection(int token) { - writer.address(address); - writer.outdent().write("else (; $label_" + token + " ;)").indent().eol(); - } - - @Override - public int startTry(WasmHollowType type) { - writer.address(address); - var label = blockIdGen++; - writer.write("try ").write(" $label_" + label); - writeBlockType(type); - writer.indent().eol(); - return label; - } - - @Override - public void startCatch(int tagIndex) { - writer.outdent().address(address); - writer.write("catch ").write(String.valueOf(tagIndex)).indent().eol(); - } - - @Override - public void endBlock(int token, boolean loop) { - writer.address(address).outdent().write("end (; $label_" + token + " ;)").eol(); - } - - @Override - public void branch(BranchOpcode opcode, int depth, int target) { - writer.address(address); - switch (opcode) { - case BR: - writer.write("br"); - break; - case BR_IF: - writer.write("br_if"); - break; - } - writer.write(" $label_" + target).eol(); - } - - @Override - public void tableBranch(int[] depths, int[] targets, int defaultDepth, int defaultTarget) { - writer.address(address); - writer.write("br_table"); - for (var target : targets) { - writer.write(" $label_" + target); - } - writer.write(" $label_" + defaultTarget).eol(); - } - - @Override - public void throwInstruction(int tagIndex) { - writer.address(address); - writer.write("throw ").write(String.valueOf(tagIndex)).eol(); - } - - @Override - public void opcode(Opcode opcode) { - writer.address(address); - switch (opcode) { - case UNREACHABLE: - writer.write("unreachable"); - break; - case NOP: - writer.write("nop"); - break; - case RETURN: - writer.write("return"); - break; - case DROP: - writer.write("drop"); - break; - case REF_EQ: - writer.write("ref.eq"); - break; - case ARRAY_LENGTH: - writer.write("array.length"); - break; - } - writer.eol(); - } - - @Override - public void local(LocalOpcode opcode, int index) { - writer.address(address); - switch (opcode) { - case GET: - writer.write("local.get"); - break; - case SET: - writer.write("local.set"); - break; - } - writer.write(" "); - writeLocalRef(currentFunctionId, index); - writer.eol(); - } - - @Override - public void getGlobal(int globalIndex) { - writer.address(address).write("global.get "); - writeGlobalRef(globalIndex); - writer.eol(); - } - - @Override - public void setGlobal(int globalIndex) { - writer.address(address).write("global.set "); - writeGlobalRef(globalIndex); - writer.eol(); - } - - @Override - public void call(int functionIndex) { - writer.address(address); - writer.write("call "); - writeFunctionRef(functionIndex); - writer.eol(); - } - - @Override - public void indirectCall(int typeIndex, int tableIndex) { - writer.address(address); - writer.write("call_indirect " + tableIndex + " " + typeIndex).eol(); - } - - @Override - public void callReference(int typeIndex) { - writer.address(address); - writer.write("call_ref " + typeIndex).eol(); - } - - @Override - public void loadInt32(WasmInt32Subtype convertFrom, int align, int offset) { - writer.address(address); - var defaultAlign = 0; - switch (convertFrom) { - case INT8: - writer.write("i32.load8_s"); - defaultAlign = 1; - break; - case UINT8: - writer.write("i32.load8_u"); - defaultAlign = 1; - break; - case INT16: - writer.write("i32.load16_s"); - defaultAlign = 2; - break; - case UINT16: - writer.write("i32.load16_u"); - defaultAlign = 2; - break; - case INT32: - writer.write("i32.load"); - defaultAlign = 4; - break; - } - writeMemArg(align, defaultAlign, offset); - writer.eol(); - } - - @Override - public void storeInt32(WasmInt32Subtype convertTo, int align, int offset) { - writer.address(address); - var defaultAlign = 0; - switch (convertTo) { - case INT8: - case UINT8: - writer.write("i32.store8"); - defaultAlign = 1; - break; - case INT16: - case UINT16: - writer.write("i32.store16"); - defaultAlign = 2; - break; - case INT32: - writer.write("i32.store"); - defaultAlign = 4; - break; - } - writeMemArg(align, defaultAlign, offset); - writer.eol(); - } - - @Override - public void loadInt64(WasmInt64Subtype convertFrom, int align, int offset) { - writer.address(address); - var defaultAlign = 0; - switch (convertFrom) { - case INT8: - writer.write("i64.load8_s"); - defaultAlign = 1; - break; - case UINT8: - writer.write("i64.load8_u"); - defaultAlign = 1; - break; - case INT16: - writer.write("i64.load16_s"); - defaultAlign = 2; - break; - case UINT16: - writer.write("i64.load16_u"); - defaultAlign = 2; - break; - case INT32: - writer.write("i64.load32_s"); - defaultAlign = 4; - break; - case UINT32: - writer.write("i64.load32_u"); - defaultAlign = 4; - break; - case INT64: - writer.write("i64.load"); - defaultAlign = 8; - break; - } - writeMemArg(align, defaultAlign, offset); - writer.eol(); - } - - @Override - public void storeInt64(WasmInt64Subtype convertTo, int align, int offset) { - writer.address(address); - var defaultAlign = 0; - switch (convertTo) { - case INT8: - case UINT8: - writer.write("i64.store8"); - defaultAlign = 1; - break; - case INT16: - case UINT16: - writer.write("i64.store16"); - defaultAlign = 2; - break; - case INT32: - case UINT32: - writer.write("i64.store32"); - defaultAlign = 4; - break; - case INT64: - writer.write("i64.store"); - defaultAlign = 8; - break; - } - writeMemArg(align, defaultAlign, offset); - writer.eol(); - } - - @Override - public void loadFloat32(int align, int offset) { - writer.address(address).write("f32.load"); - writeMemArg(align, 4, offset); - writer.eol(); - } - - @Override - public void storeFloat32(int align, int offset) { - writer.address(address).write("f32.store"); - writeMemArg(align, 4, offset); - writer.eol(); - } - - - @Override - public void loadFloat64(int align, int offset) { - writer.address(address).write("f64.load"); - writeMemArg(align, 8, offset); - writer.eol(); - } - - @Override - public void storeFloat64(int align, int offset) { - writer.address(address).write("f64.store"); - writeMemArg(align, 8, offset); - writer.eol(); - } - - @Override - public void memoryGrow() { - writer.address(address).write("memory.grow").eol(); - } - - @Override - public void memoryFill() { - writer.address(address).write("memory.fill").eol(); - } - - @Override - public void memoryCopy() { - writer.address(address).write("memory.copy").eol(); - } - - private void writeMemArg(int align, int defaultAlign, int offset) { - var needsComma = false; - if (align != defaultAlign) { - writer.write(" align=" + align); - needsComma = true; - } - if (offset != 0) { - if (needsComma) { - writer.write(","); - } - writer.write(" offset=" + offset); - } - } - - @Override - public void unary(WasmIntUnaryOperation opcode, WasmIntType type) { - writer.address(address); - switch (type) { - case INT32: - writer.write("i32."); - break; - case INT64: - writer.write("i64."); - break; - } - switch (opcode) { - case CLZ: - writer.write("clz"); - break; - case CTZ: - writer.write("ctz"); - break; - case EQZ: - writer.write("eqz"); - break; - case POPCNT: - writer.write("popcnt"); - break; - } - writer.eol(); - } - - @Override - public void unary(WasmFloatUnaryOperation opcode, WasmFloatType type) { - writer.address(address); - switch (type) { - case FLOAT32: - writer.write("f32."); - break; - case FLOAT64: - writer.write("f64."); - break; - } - switch (opcode) { - case ABS: - writer.write("abs"); - break; - case NEG: - writer.write("neg"); - break; - case FLOOR: - writer.write("floor"); - break; - case CEIL: - writer.write("ceil"); - break; - case TRUNC: - writer.write("trunc"); - break; - case NEAREST: - writer.write("nearest"); - break; - case SQRT: - writer.write("sqrt"); - break; - case COPYSIGN: - writer.write("copysign"); - break; - } - writer.eol(); - } - - @Override - public void binary(WasmIntBinaryOperation opcode, WasmIntType type) { - writer.address(address); - switch (type) { - case INT32: - writer.write("i32."); - break; - case INT64: - writer.write("i64."); - break; - } - switch (opcode) { - case ADD: - writer.write("add"); - break; - case SUB: - writer.write("sub"); - break; - case MUL: - writer.write("mul"); - break; - case DIV_SIGNED: - writer.write("div_s"); - break; - case DIV_UNSIGNED: - writer.write("div_u"); - break; - case REM_SIGNED: - writer.write("rem_s"); - break; - case REM_UNSIGNED: - writer.write("rem_u"); - break; - case AND: - writer.write("and"); - break; - case OR: - writer.write("or"); - break; - case XOR: - writer.write("xor"); - break; - case SHL: - writer.write("shl"); - break; - case SHR_SIGNED: - writer.write("shr_s"); - break; - case SHR_UNSIGNED: - writer.write("shr_u"); - break; - case ROTL: - writer.write("rotl"); - break; - case ROTR: - writer.write("rotr"); - break; - case EQ: - writer.write("eq"); - break; - case NE: - writer.write("ne"); - break; - case LT_SIGNED: - writer.write("lt_s"); - break; - case LT_UNSIGNED: - writer.write("lt_u"); - break; - case GT_SIGNED: - writer.write("gt_s"); - break; - case GT_UNSIGNED: - writer.write("gt_u"); - break; - case LE_SIGNED: - writer.write("le_s"); - break; - case LE_UNSIGNED: - writer.write("le_u"); - break; - case GE_SIGNED: - writer.write("ge_s"); - break; - case GE_UNSIGNED: - writer.write("ge_u"); - break; - } - writer.eol(); - } - - @Override - public void binary(WasmFloatBinaryOperation opcode, WasmFloatType type) { - writer.address(address); - switch (type) { - case FLOAT32: - writer.write("f32."); - break; - case FLOAT64: - writer.write("f64."); - break; - } - switch (opcode) { - case ADD: - writer.write("add"); - break; - case SUB: - writer.write("sub"); - break; - case MUL: - writer.write("mul"); - break; - case DIV: - writer.write("div"); - break; - case MIN: - writer.write("min"); - break; - case MAX: - writer.write("max"); - break; - case EQ: - writer.write("eq"); - break; - case NE: - writer.write("ne"); - break; - case LT: - writer.write("lt"); - break; - case GT: - writer.write("gt"); - break; - case LE: - writer.write("le"); - break; - case GE: - writer.write("ge"); - break; - } - writer.eol(); - } - - @Override - public void convert(WasmNumType sourceType, WasmNumType targetType, boolean signed, boolean reinterpret) { - switch (targetType) { - case INT32: - writer.write("i32."); - switch (sourceType) { - case FLOAT32: - if (reinterpret) { - writer.write("reinterpret_f32"); - } else if (signed) { - writer.write("trunc_f32_s"); - } else { - writer.write("trunc_f32_u"); - } - break; - case FLOAT64: - if (signed) { - writer.write("trunc_f64_s"); - } else { - writer.write("trunc_f64_u"); - } - break; - case INT64: - writer.write("wrap_i64"); - break; - default: - writer.write("error"); - break; - } - break; - case INT64: - writer.write("i64."); - switch (sourceType) { - case FLOAT32: - if (signed) { - writer.write("trunc_f32_s"); - } else { - writer.write("trunc_f32_u"); - } - break; - case FLOAT64: - if (reinterpret) { - writer.write("reinterpret_f64"); - } else if (signed) { - writer.write("trunc_f64_s"); - } else { - writer.write("trunc_f64_u"); - } - break; - case INT32: - if (signed) { - writer.write("extend_i32_s"); - } else { - writer.write("extend_i32_u"); - } - break; - default: - writer.write("error"); - break; - } - break; - case FLOAT32: - writer.write("f32."); - switch (sourceType) { - case INT32: - if (reinterpret) { - writer.write("reinterpret_i32"); - } else if (signed) { - writer.write("convert_i32_s"); - } else { - writer.write("convert_i32_u"); - } - break; - case INT64: - if (signed) { - writer.write("convert_i64_s"); - } else { - writer.write("convert_i64_u"); - } - break; - case FLOAT64: - writer.write("demote_f64"); - break; - default: - writer.write("error"); - break; - } - break; - case FLOAT64: - writer.write("f64."); - switch (sourceType) { - case INT32: - if (signed) { - writer.write("convert_i32_s"); - } else { - writer.write("convert_i32_u"); - } - break; - case INT64: - if (reinterpret) { - writer.write("reinterpret_i64"); - } else if (signed) { - writer.write("convert_i64_s"); - } else { - writer.write("convert_i64_u"); - } - break; - case FLOAT32: - writer.write("promote_f32"); - break; - default: - writer.write("error"); - break; - } - break; - } - writer.eol(); - } - - @Override - public void int32Constant(int value) { - writer.address(address).write("i32.const " + value).eol(); - } - - @Override - public void int64Constant(long value) { - writer.address(address).write("i64.const " + value).eol(); - } - - @Override - public void float32Constant(float value) { - writer.address(address).write("f32.const " + Float.toHexString(value)).eol(); - } - - @Override - public void float64Constant(double value) { - writer.address(address).write("f64.const " + Double.toHexString(value)).eol(); - } - - @Override - public void nullConstant(WasmHollowType.Reference type) { - writer.address(address).write("ref.null "); - writeType(type); - writer.eol(); - } - - @Override - public void cast(WasmHollowType.Reference type, boolean nullable) { - writer.address(address).write("ref.cast (ref "); - if (!nullable) { - writer.write("null "); - } - writeType(type); - writer.write(")").eol(); - } - - @Override - public void test(WasmHollowType.Reference type, boolean nullable) { - writer.address(address).write("ref.test (ref "); - if (!nullable) { - writer.write("null "); - } - writeType(type); - writer.write(")").eol(); - } - - @Override - public void structNew(int typeIndex) { - writer.address(address).write("struct.new "); - writeTypeRef(typeIndex); - writer.eol(); - } - - @Override - public void structNewDefault(int typeIndex) { - writer.address(address).write("struct.new_default "); - writeTypeRef(typeIndex); - writer.eol(); - } - - @Override - public void structGet(WasmSignedType signedType, int typeIndex, int fieldIndex) { - writer.address(address); - if (signedType == null) { - writer.write("struct.get"); - } else if (signedType == WasmSignedType.SIGNED) { - writer.write("struct.get_s"); - } else { - writer.write("struct.get_u"); - } - writer.write(" "); - writeTypeRef(typeIndex); - writer.write(" "); - writeFieldRef(typeIndex, fieldIndex); - writer.eol(); - } - - @Override - public void structSet(int typeIndex, int fieldIndex) { - writer.address(address).write("struct.set "); - writeTypeRef(typeIndex); - writer.write(" "); - writeFieldRef(typeIndex, fieldIndex); - writer.eol(); - } - - @Override - public void arrayNewDefault(int typeIndex) { - writer.address(address).write("array.new_default "); - writeTypeRef(typeIndex); - writer.eol(); - } - - @Override - public void arrayGet(WasmSignedType signedType, int typeIndex) { - writer.address(address); - if (signedType == null) { - writer.write("array.get"); - } else if (signedType == WasmSignedType.SIGNED) { - writer.write("array.get_s"); - } else { - writer.write("array.get_u"); - } - writer.write(" "); - writeTypeRef(typeIndex); - writer.eol(); - } - - @Override - public void arraySet(int typeIndex) { - writer.address(address).write("array.set "); - writeTypeRef(typeIndex); - writer.eol(); - } - - @Override - public void functionReference(int functionIndex) { - writer.address(address).write("ref.func "); - writeFunctionRef(functionIndex); - writer.eol(); - } - - @Override - public void int31Reference() { - writer.address(address).write("ref.i31").eol(); - } - - @Override - public void int31Get(WasmSignedType signedType) { - writer.address(address).write("ref.i31_").write(signedType == WasmSignedType.SIGNED ? "s" : "u").eol(); - } - } diff --git a/core/src/main/java/org/teavm/backend/wasm/disasm/DisassemblyGlobalSectionListener.java b/core/src/main/java/org/teavm/backend/wasm/disasm/DisassemblyGlobalSectionListener.java new file mode 100644 index 000000000..d257825ce --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/disasm/DisassemblyGlobalSectionListener.java @@ -0,0 +1,52 @@ +/* + * 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.CodeListener; +import org.teavm.backend.wasm.parser.GlobalSectionListener; +import org.teavm.backend.wasm.parser.WasmHollowType; + +public class DisassemblyGlobalSectionListener extends BaseDisassemblyListener implements GlobalSectionListener { + private DisassemblyCodeListener codeListener; + + public DisassemblyGlobalSectionListener(DisassemblyWriter writer, NameProvider nameProvider) { + super(writer, nameProvider); + codeListener = new DisassemblyCodeListener(writer, nameProvider); + } + + @Override + public CodeListener startGlobal(int index, WasmHollowType type, boolean mutable) { + writer.address().write("(global (; ").write(String.valueOf(index)).write(" ;) "); + var name = nameProvider.global(index); + if (name != null) { + writer.write("$").write(name).write(" "); + } + if (mutable) { + writer.write("(mut "); + writeType(type); + writer.write(")"); + } else { + writeType(type); + } + writer.indent().eol(); + return codeListener; + } + + @Override + public void endGlobal() { + writer.outdent().write(")").eol(); + } +} 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 index b30751ac4..d8a57b939 100644 --- a/core/src/main/java/org/teavm/backend/wasm/disasm/DisassemblyTypeSectionListener.java +++ b/core/src/main/java/org/teavm/backend/wasm/disasm/DisassemblyTypeSectionListener.java @@ -15,13 +15,11 @@ */ 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 { +public class DisassemblyTypeSectionListener extends BaseDisassemblyListener implements TypeSectionListener { private boolean currentTypeNeedsClosing; private boolean emittingReturn; private int currentTypeIndex; @@ -34,7 +32,7 @@ public class DisassemblyTypeSectionListener extends BaseDisassemblyListener impl @Override public void startRecType(int count) { - writer.address(address).write("(rec ").indent().eol(); + writer.address().write("(rec ").indent().eol(); } @Override @@ -45,7 +43,7 @@ public class DisassemblyTypeSectionListener extends BaseDisassemblyListener impl @Override public void startType(int index, boolean open, int[] supertypes) { currentTypeIndex = index; - writer.address(address).write("(type (; ").write(String.valueOf(index)).write(" ;) "); + writer.address().write("(type (; ").write(String.valueOf(index)).write(" ;) "); var name = nameProvider.type(index); if (name != null) { writer.write("$").write(name).write(" "); @@ -65,7 +63,7 @@ public class DisassemblyTypeSectionListener extends BaseDisassemblyListener impl @Override public void startArrayType() { - writer.address(address).write("(array ").indent().eol(); + writer.address().write("(array ").indent().eol(); } @Override @@ -76,12 +74,12 @@ public class DisassemblyTypeSectionListener extends BaseDisassemblyListener impl @Override public void startStructType(int fieldCount) { needsFieldIndex = true; - writer.address(address).write("(struct ").indent().eol(); + writer.address().write("(struct ").indent().eol(); } @Override public void field(WasmHollowStorageType hollowType, boolean mutable) { - writer.address(address).write("(field "); + writer.address().write("(field "); if (needsFieldIndex) { writer.write("(; " + fieldIndex++ + " ;) "); var name = nameProvider.field(currentTypeIndex, fieldIndex); @@ -107,7 +105,7 @@ public class DisassemblyTypeSectionListener extends BaseDisassemblyListener impl @Override public void funcType(int paramCount) { - writer.address(address).write("(func ").indent().eol(); + writer.address().write("(func ").indent().eol(); } @Override @@ -117,7 +115,7 @@ public class DisassemblyTypeSectionListener extends BaseDisassemblyListener impl @Override public void resultType(WasmHollowType type) { - writer.address(address).write("(").write(emittingReturn ? "result" : "param").write(" "); + writer.address().write("(").write(emittingReturn ? "result" : "param").write(" "); writeType(type); writer.write(")").eol(); } diff --git a/core/src/main/java/org/teavm/backend/wasm/disasm/DisassemblyWriter.java b/core/src/main/java/org/teavm/backend/wasm/disasm/DisassemblyWriter.java index b6b34d148..79b5bd3a5 100644 --- a/core/src/main/java/org/teavm/backend/wasm/disasm/DisassemblyWriter.java +++ b/core/src/main/java/org/teavm/backend/wasm/disasm/DisassemblyWriter.java @@ -16,6 +16,7 @@ package org.teavm.backend.wasm.disasm; import java.io.PrintWriter; +import org.teavm.backend.wasm.parser.AddressListener; public class DisassemblyWriter { private PrintWriter out; @@ -24,14 +25,18 @@ public class DisassemblyWriter { private int address; private boolean hasAddress; private boolean lineStarted; + private int addressOffset; public DisassemblyWriter(PrintWriter out, boolean withAddress) { this.out = out; this.withAddress = withAddress; } - public DisassemblyWriter address(int address) { - this.address = address; + public void setAddressOffset(int addressOffset) { + this.addressOffset = addressOffset; + } + + public DisassemblyWriter address() { hasAddress = true; return this; } @@ -83,4 +88,11 @@ public class DisassemblyWriter { out.print(s); return this; } + + public final AddressListener addressListener = new AddressListener() { + @Override + public void address(int address) { + DisassemblyWriter.this.address = address + addressOffset; + } + }; } 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 index 10b71b6e8..4fa041647 100644 --- a/core/src/main/java/org/teavm/backend/wasm/parser/BaseSectionParser.java +++ b/core/src/main/java/org/teavm/backend/wasm/parser/BaseSectionParser.java @@ -15,186 +15,53 @@ */ package org.teavm.backend.wasm.parser; -import java.nio.charset.StandardCharsets; - public abstract class BaseSectionParser { - protected AddressListener addressListener; - protected byte[] data; - protected int ptr; - private int lastReportedPtr = -1; + protected WasmBinaryReader reader; - public BaseSectionParser(AddressListener addressListener) { - this.addressListener = addressListener; + public void parse(AddressListener addressListener, byte[] data) { + parse(new WasmBinaryReader(addressListener, data)); } - public void parse(byte[] data) { - this.data = data; - ptr = 0; + public void parse(WasmBinaryReader reader) { + this.reader = reader; try { parseContent(); } finally { - this.data = null; + this.reader = 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); - } - } + reader.reportAddress(); } 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; + return reader.readSignedLEB(); } 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; + return reader.readLEB(); } 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; + return reader.readSignedLongLEB(); } 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; + return reader.readLongLEB(); } protected int readFixedInt() { - return ((data[ptr++] & 0xFF) << 24) - | ((data[ptr++] & 0xFF) << 16) - | ((data[ptr++] & 0xFF) << 8) - | (data[ptr++] & 0xFF); + return reader.readFixedInt(); } 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); + return reader.readFixedLong(); } protected String readString() { - var size = readLEB(); - var result = new String(data, ptr, size, StandardCharsets.UTF_8); - ptr += size; - return result; + return reader.readString(); } } diff --git a/core/src/main/java/org/teavm/backend/wasm/parser/CodeListener.java b/core/src/main/java/org/teavm/backend/wasm/parser/CodeListener.java index efd168211..4e052da85 100644 --- a/core/src/main/java/org/teavm/backend/wasm/parser/CodeListener.java +++ b/core/src/main/java/org/teavm/backend/wasm/parser/CodeListener.java @@ -182,4 +182,7 @@ public interface CodeListener { default void int31Get(WasmSignedType signedType) { } + + CodeListener EMPTY = new CodeListener() { + }; } diff --git a/core/src/main/java/org/teavm/backend/wasm/parser/CodeParser.java b/core/src/main/java/org/teavm/backend/wasm/parser/CodeParser.java new file mode 100644 index 000000000..0675ee9bc --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/parser/CodeParser.java @@ -0,0 +1,824 @@ +/* + * 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 java.util.ArrayList; +import java.util.List; +import org.teavm.backend.wasm.model.WasmNumType; +import org.teavm.backend.wasm.model.expression.WasmFloatBinaryOperation; +import org.teavm.backend.wasm.model.expression.WasmFloatType; +import org.teavm.backend.wasm.model.expression.WasmFloatUnaryOperation; +import org.teavm.backend.wasm.model.expression.WasmInt32Subtype; +import org.teavm.backend.wasm.model.expression.WasmInt64Subtype; +import org.teavm.backend.wasm.model.expression.WasmIntBinaryOperation; +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 CodeParser extends BaseSectionParser { + private CodeListener codeListener; + private final List blockStack = new ArrayList<>(); + private int functionIndexOffset; + + public void setCodeListener(CodeListener codeListener) { + this.codeListener = codeListener; + } + + public void setFunctionIndexOffset(int functionIndexOffset) { + this.functionIndexOffset = functionIndexOffset; + } + + @Override + protected void parseContent() { + parseCode(); + } + + public boolean parseSingleExpression(WasmBinaryReader reader) { + this.reader = reader; + try { + return parseExpressions(); + } finally { + this.reader = reader; + } + } + + private void parseCode() { + if (!parseExpressions()) { + codeListener.error(blockStack.size()); + blockStack.clear(); + } + } + + private boolean parseExpressions() { + while (reader.data[reader.ptr] != 0x0B) { + if (!parseExpr()) { + return false; + } + } + return true; + } + + private boolean parseExpr() { + reportAddress(); + switch (reader.data[reader.ptr++] & 0xFF) { + case 0x00: + codeListener.opcode(Opcode.UNREACHABLE); + break; + case 0x01: + codeListener.opcode(Opcode.NOP); + break; + case 0x02: + return parseBlock(false); + case 0x03: + return parseBlock(true); + case 0x04: + return parseConditional(); + case 0x06: + return parseTryCatch(); + case 0x8: + codeListener.throwInstruction(readLEB()); + break; + case 0x0C: + parseBranch(BranchOpcode.BR); + break; + case 0x0D: + parseBranch(BranchOpcode.BR_IF); + break; + case 0x0E: + parseTableBranch(); + break; + case 0x0F: + codeListener.opcode(Opcode.RETURN); + break; + case 0x10: + codeListener.call(readLEB() + functionIndexOffset); + break; + case 0x11: + codeListener.indirectCall(readLEB(), readLEB()); + break; + case 0x14: + codeListener.callReference(readLEB()); + break; + + case 0x1A: + codeListener.opcode(Opcode.DROP); + break; + + case 0x20: + codeListener.local(LocalOpcode.GET, readLEB()); + break; + case 0x21: + codeListener.local(LocalOpcode.SET, readLEB()); + break; + + case 0x23: + codeListener.getGlobal(readLEB()); + break; + case 0x24: + codeListener.setGlobal(readLEB()); + break; + + case 0x28: + codeListener.loadInt32(WasmInt32Subtype.INT32, 1 << readLEB(), readLEB()); + break; + case 0x29: + codeListener.loadInt64(WasmInt64Subtype.INT64, 1 << readLEB(), readLEB()); + break; + case 0x2A: + codeListener.loadFloat32(1 << readLEB(), readLEB()); + break; + case 0x2B: + codeListener.loadFloat64(1 << readLEB(), readLEB()); + break; + case 0x2C: + codeListener.loadInt32(WasmInt32Subtype.INT8, 1 << readLEB(), readLEB()); + break; + case 0x2D: + codeListener.loadInt32(WasmInt32Subtype.UINT8, 1 << readLEB(), readLEB()); + break; + case 0x2E: + codeListener.loadInt32(WasmInt32Subtype.INT16, 1 << readLEB(), readLEB()); + break; + case 0x2F: + codeListener.loadInt32(WasmInt32Subtype.UINT16, 1 << readLEB(), readLEB()); + break; + case 0x30: + codeListener.loadInt64(WasmInt64Subtype.INT8, 1 << readLEB(), readLEB()); + break; + case 0x31: + codeListener.loadInt64(WasmInt64Subtype.UINT8, 1 << readLEB(), readLEB()); + break; + case 0x32: + codeListener.loadInt64(WasmInt64Subtype.INT16, 1 << readLEB(), readLEB()); + break; + case 0x33: + codeListener.loadInt64(WasmInt64Subtype.UINT16, 1 << readLEB(), readLEB()); + break; + case 0x34: + codeListener.loadInt64(WasmInt64Subtype.INT32, 1 << readLEB(), readLEB()); + break; + case 0x35: + codeListener.loadInt64(WasmInt64Subtype.UINT32, 1 << readLEB(), readLEB()); + break; + case 0x36: + codeListener.storeInt32(WasmInt32Subtype.INT32, 1 << readLEB(), readLEB()); + break; + case 0x37: + codeListener.storeInt64(WasmInt64Subtype.INT64, 1 << readLEB(), readLEB()); + break; + case 0x38: + codeListener.storeFloat32(1 << readLEB(), readLEB()); + break; + case 0x39: + codeListener.storeFloat64(1 << readLEB(), readLEB()); + break; + case 0x3A: + codeListener.storeInt32(WasmInt32Subtype.INT8, 1 << readLEB(), readLEB()); + break; + case 0x3B: + codeListener.storeInt32(WasmInt32Subtype.INT16, 1 << readLEB(), readLEB()); + break; + case 0x3C: + codeListener.storeInt64(WasmInt64Subtype.INT8, 1 << readLEB(), readLEB()); + break; + case 0x3D: + codeListener.storeInt64(WasmInt64Subtype.INT16, 1 << readLEB(), readLEB()); + break; + case 0x3E: + codeListener.storeInt64(WasmInt64Subtype.INT32, 1 << readLEB(), readLEB()); + break; + case 0x40: + readLEB(); + codeListener.memoryGrow(); + break; + + case 0x41: + codeListener.int32Constant(readSignedLEB()); + break; + case 0x42: + codeListener.int64Constant(readSignedLongLEB()); + break; + case 0x43: + codeListener.float32Constant(Float.intBitsToFloat(readFixedInt())); + break; + case 0x44: + codeListener.float64Constant(Double.longBitsToDouble(readFixedLong())); + break; + + case 0x45: + codeListener.unary(WasmIntUnaryOperation.EQZ, WasmIntType.INT32); + break; + case 0x46: + codeListener.binary(WasmIntBinaryOperation.EQ, WasmIntType.INT32); + break; + case 0x47: + codeListener.binary(WasmIntBinaryOperation.NE, WasmIntType.INT32); + break; + case 0x48: + codeListener.binary(WasmIntBinaryOperation.LT_SIGNED, WasmIntType.INT32); + break; + case 0x49: + codeListener.binary(WasmIntBinaryOperation.LT_UNSIGNED, WasmIntType.INT32); + break; + case 0x4A: + codeListener.binary(WasmIntBinaryOperation.GT_SIGNED, WasmIntType.INT32); + break; + case 0x4B: + codeListener.binary(WasmIntBinaryOperation.GT_UNSIGNED, WasmIntType.INT32); + break; + case 0x4C: + codeListener.binary(WasmIntBinaryOperation.LE_SIGNED, WasmIntType.INT32); + break; + case 0x4D: + codeListener.binary(WasmIntBinaryOperation.LE_UNSIGNED, WasmIntType.INT32); + break; + case 0x4E: + codeListener.binary(WasmIntBinaryOperation.GE_SIGNED, WasmIntType.INT32); + break; + case 0x4F: + codeListener.binary(WasmIntBinaryOperation.GE_UNSIGNED, WasmIntType.INT32); + break; + + case 0x50: + codeListener.unary(WasmIntUnaryOperation.EQZ, WasmIntType.INT64); + break; + case 0x51: + codeListener.binary(WasmIntBinaryOperation.EQ, WasmIntType.INT64); + break; + case 0x52: + codeListener.binary(WasmIntBinaryOperation.NE, WasmIntType.INT64); + break; + case 0x53: + codeListener.binary(WasmIntBinaryOperation.LT_SIGNED, WasmIntType.INT64); + break; + case 0x54: + codeListener.binary(WasmIntBinaryOperation.LT_UNSIGNED, WasmIntType.INT64); + break; + case 0x55: + codeListener.binary(WasmIntBinaryOperation.GT_SIGNED, WasmIntType.INT64); + break; + case 0x56: + codeListener.binary(WasmIntBinaryOperation.GT_UNSIGNED, WasmIntType.INT64); + break; + case 0x57: + codeListener.binary(WasmIntBinaryOperation.LE_SIGNED, WasmIntType.INT64); + break; + case 0x58: + codeListener.binary(WasmIntBinaryOperation.LE_UNSIGNED, WasmIntType.INT64); + break; + case 0x59: + codeListener.binary(WasmIntBinaryOperation.GE_SIGNED, WasmIntType.INT64); + break; + case 0x5A: + codeListener.binary(WasmIntBinaryOperation.GE_UNSIGNED, WasmIntType.INT64); + break; + + case 0x5B: + codeListener.binary(WasmFloatBinaryOperation.EQ, WasmFloatType.FLOAT32); + break; + case 0x5C: + codeListener.binary(WasmFloatBinaryOperation.NE, WasmFloatType.FLOAT32); + break; + case 0x5D: + codeListener.binary(WasmFloatBinaryOperation.LT, WasmFloatType.FLOAT32); + break; + case 0x5E: + codeListener.binary(WasmFloatBinaryOperation.GT, WasmFloatType.FLOAT32); + break; + case 0x5F: + codeListener.binary(WasmFloatBinaryOperation.LE, WasmFloatType.FLOAT32); + break; + case 0x60: + codeListener.binary(WasmFloatBinaryOperation.GE, WasmFloatType.FLOAT32); + break; + + case 0x61: + codeListener.binary(WasmFloatBinaryOperation.EQ, WasmFloatType.FLOAT64); + break; + case 0x62: + codeListener.binary(WasmFloatBinaryOperation.NE, WasmFloatType.FLOAT64); + break; + case 0x63: + codeListener.binary(WasmFloatBinaryOperation.LT, WasmFloatType.FLOAT64); + break; + case 0x64: + codeListener.binary(WasmFloatBinaryOperation.GT, WasmFloatType.FLOAT64); + break; + case 0x65: + codeListener.binary(WasmFloatBinaryOperation.LE, WasmFloatType.FLOAT64); + break; + case 0x66: + codeListener.binary(WasmFloatBinaryOperation.GE, WasmFloatType.FLOAT64); + break; + + case 0x67: + codeListener.unary(WasmIntUnaryOperation.CLZ, WasmIntType.INT32); + break; + case 0x68: + codeListener.unary(WasmIntUnaryOperation.CTZ, WasmIntType.INT32); + break; + case 0x69: + codeListener.unary(WasmIntUnaryOperation.POPCNT, WasmIntType.INT32); + break; + case 0x6A: + codeListener.binary(WasmIntBinaryOperation.ADD, WasmIntType.INT32); + break; + case 0x6B: + codeListener.binary(WasmIntBinaryOperation.SUB, WasmIntType.INT32); + break; + case 0x6C: + codeListener.binary(WasmIntBinaryOperation.MUL, WasmIntType.INT32); + break; + case 0x6D: + codeListener.binary(WasmIntBinaryOperation.DIV_SIGNED, WasmIntType.INT32); + break; + case 0x6E: + codeListener.binary(WasmIntBinaryOperation.DIV_UNSIGNED, WasmIntType.INT32); + break; + case 0x6F: + codeListener.binary(WasmIntBinaryOperation.REM_SIGNED, WasmIntType.INT32); + break; + case 0x70: + codeListener.binary(WasmIntBinaryOperation.REM_UNSIGNED, WasmIntType.INT32); + break; + case 0x71: + codeListener.binary(WasmIntBinaryOperation.AND, WasmIntType.INT32); + break; + case 0x72: + codeListener.binary(WasmIntBinaryOperation.OR, WasmIntType.INT32); + break; + case 0x73: + codeListener.binary(WasmIntBinaryOperation.XOR, WasmIntType.INT32); + break; + case 0x74: + codeListener.binary(WasmIntBinaryOperation.SHL, WasmIntType.INT32); + break; + case 0x75: + codeListener.binary(WasmIntBinaryOperation.SHR_SIGNED, WasmIntType.INT32); + break; + case 0x76: + codeListener.binary(WasmIntBinaryOperation.SHR_UNSIGNED, WasmIntType.INT32); + break; + case 0x77: + codeListener.binary(WasmIntBinaryOperation.ROTL, WasmIntType.INT32); + break; + case 0x78: + codeListener.binary(WasmIntBinaryOperation.ROTR, WasmIntType.INT32); + break; + + case 0x79: + codeListener.unary(WasmIntUnaryOperation.CLZ, WasmIntType.INT64); + break; + case 0x7A: + codeListener.unary(WasmIntUnaryOperation.CTZ, WasmIntType.INT64); + break; + case 0x7B: + codeListener.unary(WasmIntUnaryOperation.POPCNT, WasmIntType.INT64); + break; + case 0x7C: + codeListener.binary(WasmIntBinaryOperation.ADD, WasmIntType.INT64); + break; + case 0x7D: + codeListener.binary(WasmIntBinaryOperation.SUB, WasmIntType.INT64); + break; + case 0x7E: + codeListener.binary(WasmIntBinaryOperation.MUL, WasmIntType.INT64); + break; + case 0x7F: + codeListener.binary(WasmIntBinaryOperation.DIV_SIGNED, WasmIntType.INT64); + break; + case 0x80: + codeListener.binary(WasmIntBinaryOperation.DIV_UNSIGNED, WasmIntType.INT64); + break; + case 0x81: + codeListener.binary(WasmIntBinaryOperation.REM_SIGNED, WasmIntType.INT64); + break; + case 0x82: + codeListener.binary(WasmIntBinaryOperation.REM_UNSIGNED, WasmIntType.INT64); + break; + case 0x83: + codeListener.binary(WasmIntBinaryOperation.AND, WasmIntType.INT64); + break; + case 0x84: + codeListener.binary(WasmIntBinaryOperation.OR, WasmIntType.INT64); + break; + case 0x85: + codeListener.binary(WasmIntBinaryOperation.XOR, WasmIntType.INT64); + break; + case 0x86: + codeListener.binary(WasmIntBinaryOperation.SHL, WasmIntType.INT64); + break; + case 0x87: + codeListener.binary(WasmIntBinaryOperation.SHR_UNSIGNED, WasmIntType.INT64); + break; + case 0x88: + codeListener.binary(WasmIntBinaryOperation.SHR_UNSIGNED, WasmIntType.INT64); + break; + case 0x89: + codeListener.binary(WasmIntBinaryOperation.ROTL, WasmIntType.INT64); + break; + case 0x8A: + codeListener.binary(WasmIntBinaryOperation.ROTR, WasmIntType.INT64); + break; + + case 0x8B: + codeListener.unary(WasmFloatUnaryOperation.ABS, WasmFloatType.FLOAT32); + break; + case 0x8C: + codeListener.unary(WasmFloatUnaryOperation.NEG, WasmFloatType.FLOAT32); + break; + case 0x8D: + codeListener.unary(WasmFloatUnaryOperation.CEIL, WasmFloatType.FLOAT32); + break; + case 0x8E: + codeListener.unary(WasmFloatUnaryOperation.FLOOR, WasmFloatType.FLOAT32); + break; + case 0x8F: + codeListener.unary(WasmFloatUnaryOperation.TRUNC, WasmFloatType.FLOAT32); + break; + case 0x90: + codeListener.unary(WasmFloatUnaryOperation.NEAREST, WasmFloatType.FLOAT32); + break; + case 0x91: + codeListener.unary(WasmFloatUnaryOperation.SQRT, WasmFloatType.FLOAT32); + break; + case 0x92: + codeListener.binary(WasmFloatBinaryOperation.ADD, WasmFloatType.FLOAT32); + break; + case 0x93: + codeListener.binary(WasmFloatBinaryOperation.SUB, WasmFloatType.FLOAT32); + break; + case 0x94: + codeListener.binary(WasmFloatBinaryOperation.MUL, WasmFloatType.FLOAT32); + break; + case 0x95: + codeListener.binary(WasmFloatBinaryOperation.DIV, WasmFloatType.FLOAT32); + break; + case 0x96: + codeListener.binary(WasmFloatBinaryOperation.MIN, WasmFloatType.FLOAT32); + break; + case 0x97: + codeListener.binary(WasmFloatBinaryOperation.MAX, WasmFloatType.FLOAT32); + break; + case 0x98: + codeListener.unary(WasmFloatUnaryOperation.COPYSIGN, WasmFloatType.FLOAT32); + break; + + case 0x99: + codeListener.unary(WasmFloatUnaryOperation.ABS, WasmFloatType.FLOAT64); + break; + case 0x9A: + codeListener.unary(WasmFloatUnaryOperation.NEG, WasmFloatType.FLOAT64); + break; + case 0x9B: + codeListener.unary(WasmFloatUnaryOperation.CEIL, WasmFloatType.FLOAT64); + break; + case 0x9C: + codeListener.unary(WasmFloatUnaryOperation.FLOOR, WasmFloatType.FLOAT64); + break; + case 0x9D: + codeListener.unary(WasmFloatUnaryOperation.TRUNC, WasmFloatType.FLOAT64); + break; + case 0x9E: + codeListener.unary(WasmFloatUnaryOperation.NEAREST, WasmFloatType.FLOAT64); + break; + case 0x9F: + codeListener.unary(WasmFloatUnaryOperation.SQRT, WasmFloatType.FLOAT64); + break; + case 0xA0: + codeListener.binary(WasmFloatBinaryOperation.ADD, WasmFloatType.FLOAT64); + break; + case 0xA1: + codeListener.binary(WasmFloatBinaryOperation.SUB, WasmFloatType.FLOAT64); + break; + case 0xA2: + codeListener.binary(WasmFloatBinaryOperation.MUL, WasmFloatType.FLOAT64); + break; + case 0xA3: + codeListener.binary(WasmFloatBinaryOperation.DIV, WasmFloatType.FLOAT64); + break; + case 0xA4: + codeListener.binary(WasmFloatBinaryOperation.MIN, WasmFloatType.FLOAT64); + break; + case 0xA5: + codeListener.binary(WasmFloatBinaryOperation.MAX, WasmFloatType.FLOAT64); + break; + case 0xA6: + codeListener.unary(WasmFloatUnaryOperation.COPYSIGN, WasmFloatType.FLOAT64); + break; + + case 0xA7: + codeListener.convert(WasmNumType.INT64, WasmNumType.INT32, false, false); + break; + case 0xA8: + codeListener.convert(WasmNumType.FLOAT32, WasmNumType.INT32, false, false); + break; + case 0xA9: + codeListener.convert(WasmNumType.FLOAT32, WasmNumType.INT32, true, false); + break; + case 0xAA: + codeListener.convert(WasmNumType.FLOAT64, WasmNumType.INT32, false, false); + break; + case 0xAB: + codeListener.convert(WasmNumType.FLOAT64, WasmNumType.INT32, true, false); + break; + case 0xAC: + codeListener.convert(WasmNumType.INT32, WasmNumType.INT64, false, false); + break; + case 0xAD: + codeListener.convert(WasmNumType.INT32, WasmNumType.INT64, true, false); + break; + case 0xAE: + codeListener.convert(WasmNumType.FLOAT32, WasmNumType.INT64, false, false); + break; + case 0xAF: + codeListener.convert(WasmNumType.FLOAT32, WasmNumType.INT64, true, false); + break; + case 0xB0: + codeListener.convert(WasmNumType.FLOAT64, WasmNumType.INT64, false, false); + break; + case 0xB1: + codeListener.convert(WasmNumType.FLOAT64, WasmNumType.INT64, true, false); + break; + case 0xB2: + codeListener.convert(WasmNumType.INT32, WasmNumType.FLOAT32, false, false); + break; + case 0xB3: + codeListener.convert(WasmNumType.INT32, WasmNumType.FLOAT32, true, false); + break; + case 0xB4: + codeListener.convert(WasmNumType.INT64, WasmNumType.FLOAT32, false, false); + break; + case 0xB5: + codeListener.convert(WasmNumType.INT64, WasmNumType.FLOAT32, true, false); + break; + case 0xB6: + codeListener.convert(WasmNumType.FLOAT64, WasmNumType.FLOAT32, true, false); + break; + case 0xB7: + codeListener.convert(WasmNumType.INT32, WasmNumType.FLOAT64, false, false); + break; + case 0xB8: + codeListener.convert(WasmNumType.INT32, WasmNumType.FLOAT64, true, false); + break; + case 0xB9: + codeListener.convert(WasmNumType.INT64, WasmNumType.FLOAT64, false, false); + break; + case 0xBA: + codeListener.convert(WasmNumType.INT64, WasmNumType.FLOAT64, true, false); + break; + case 0xBC: + codeListener.convert(WasmNumType.FLOAT32, WasmNumType.INT32, false, true); + break; + case 0xBD: + codeListener.convert(WasmNumType.FLOAT64, WasmNumType.INT64, false, true); + break; + case 0xBE: + codeListener.convert(WasmNumType.INT32, WasmNumType.FLOAT32, false, true); + break; + case 0xBF: + codeListener.convert(WasmNumType.INT64, WasmNumType.FLOAT64, false, true); + break; + + case 0xD0: + codeListener.nullConstant(reader.readHeapType()); + break; + + case 0xD2: + codeListener.functionReference(readLEB() + functionIndexOffset); + break; + + case 0xD3: + codeListener.opcode(Opcode.REF_EQ); + break; + + case 0xFB: + return parseExtExpr2(); + case 0xFC: + return parseExtExpr(); + + default: + return false; + } + return true; + } + + private boolean parseExtExpr() { + switch (readLEB()) { + case 10: { + if (reader.data[reader.ptr++] != 0 || reader.data[reader.ptr++] != 0) { + return false; + } + codeListener.memoryCopy(); + return true; + } + case 11: { + if (reader.data[reader.ptr++] != 0) { + return false; + } + codeListener.memoryFill(); + return true; + } + + default: + return false; + } + } + + private boolean parseExtExpr2() { + switch (readLEB()) { + case 0: + codeListener.structNew(readLEB()); + return true; + + case 1: + codeListener.structNewDefault(readLEB()); + return true; + + case 2: + codeListener.structGet(null, readLEB(), readLEB()); + return true; + case 3: + codeListener.structGet(WasmSignedType.SIGNED, readLEB(), readLEB()); + return true; + case 4: + codeListener.structGet(WasmSignedType.UNSIGNED, readLEB(), readLEB()); + return true; + + case 5: + codeListener.structSet(readLEB(), readLEB()); + return true; + + case 7: + codeListener.arrayNewDefault(readLEB()); + return true; + + case 11: + codeListener.arrayGet(null, readLEB()); + return true; + case 12: + codeListener.arrayGet(WasmSignedType.SIGNED, readLEB()); + return true; + case 13: + codeListener.arrayGet(WasmSignedType.UNSIGNED, readLEB()); + return true; + + case 14: + codeListener.arraySet(readLEB()); + return true; + + case 15: + codeListener.opcode(Opcode.ARRAY_LENGTH); + return true; + + case 17: + codeListener.arrayCopy(readLEB(), readLEB()); + return true; + + case 20: + codeListener.test(reader.readHeapType(), false); + return true; + + case 21: + codeListener.test(reader.readHeapType(), true); + return true; + + case 22: + codeListener.cast(reader.readHeapType(), false); + return true; + + case 23: + codeListener.cast(reader.readHeapType(), true); + return true; + + case 28: + codeListener.int31Reference(); + return true; + + case 29: + codeListener.int31Get(WasmSignedType.SIGNED); + return true; + case 30: + codeListener.int31Get(WasmSignedType.UNSIGNED); + return true; + + default: + return false; + } + } + + private boolean parseBlock(boolean isLoop) { + var type = reader.readType(); + var token = codeListener.startBlock(isLoop, type); + blockStack.add(new Block(token)); + if (!parseExpressions()) { + return false; + } + blockStack.remove(blockStack.size() - 1); + reportAddress(); + codeListener.endBlock(token, isLoop); + ++reader.ptr; + return true; + } + + private boolean parseConditional() { + var type = reader.readType(); + var token = codeListener.startConditionalBlock(type); + blockStack.add(new Block(token)); + var hasElse = false; + loop: while (true) { + switch (reader.data[reader.ptr]) { + case 0x0B: + break loop; + case 0x05: + if (hasElse) { + return false; + } + reportAddress(); + codeListener.startElseSection(blockStack.get(blockStack.size() - 1).token); + ++reader.ptr; + break; + default: + if (!parseExpr()) { + return false; + } + break; + } + } + blockStack.remove(blockStack.size() - 1); + reportAddress(); + codeListener.endBlock(token, false); + ++reader.ptr; + return true; + } + + private boolean parseTryCatch() { + var type = reader.readType(); + var token = codeListener.startTry(type); + blockStack.add(new Block(token)); + loop: while (true) { + switch (reader.data[reader.ptr]) { + case 0x0B: + break loop; + case 0x07: { + reportAddress(); + var tagIndex = readLEB(); + ++reader.ptr; + codeListener.startCatch(tagIndex); + break; + } + default: + if (!parseExpr()) { + return false; + } + break; + } + } + blockStack.remove(blockStack.size() - 1); + reportAddress(); + codeListener.endBlock(token, false); + ++reader.ptr; + return true; + } + + private void parseBranch(BranchOpcode opcode) { + var depth = readLEB(); + var target = blockStack.get(blockStack.size() - depth - 1); + codeListener.branch(opcode, depth, target.token); + } + + private void parseTableBranch() { + var count = readLEB(); + var depths = new int[count]; + var targets = new int[count]; + for (var i = 0; i < count; ++i) { + var depth = readLEB(); + depths[i] = depth; + targets[i] = blockStack.get(blockStack.size() - depth - 1).token; + } + var defaultDepth = readLEB(); + var defaultTarget = blockStack.get(blockStack.size() - defaultDepth - 1).token; + codeListener.tableBranch(depths, targets, defaultDepth, defaultTarget); + } + + private static class Block { + int token; + + Block(int token) { + this.token = token; + } + } +} 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 f7e743e0c..b1a5cfe93 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 @@ -15,27 +15,12 @@ */ package org.teavm.backend.wasm.parser; -import java.util.ArrayList; -import java.util.List; -import org.teavm.backend.wasm.model.WasmNumType; -import org.teavm.backend.wasm.model.expression.WasmFloatBinaryOperation; -import org.teavm.backend.wasm.model.expression.WasmFloatType; -import org.teavm.backend.wasm.model.expression.WasmFloatUnaryOperation; -import org.teavm.backend.wasm.model.expression.WasmInt32Subtype; -import org.teavm.backend.wasm.model.expression.WasmInt64Subtype; -import org.teavm.backend.wasm.model.expression.WasmIntBinaryOperation; -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 extends BaseSectionParser { private CodeSectionListener listener; - private CodeListener codeListener; - private List blockStack = new ArrayList<>(); private int functionIndexOffset; + private CodeParser codeParser = new CodeParser(); - public CodeSectionParser(AddressListener addressListener, CodeSectionListener listener) { - super(addressListener); + public CodeSectionParser(CodeSectionListener listener) { this.listener = listener; } @@ -58,15 +43,17 @@ public class CodeSectionParser extends BaseSectionParser { private void parseFunction(int index) { reportAddress(); var functionSize = readLEB(); - var end = ptr + functionSize; + var end = reader.ptr + functionSize; if (listener.functionStart(index + functionIndexOffset, functionSize)) { parseLocals(); - codeListener = listener.code(); + var codeListener = listener.code(); if (codeListener != null) { - parseCode(); + codeParser.setCodeListener(codeListener); + codeParser.setFunctionIndexOffset(functionIndexOffset); + codeParser.parse(reader); } } - ptr = end; + reader.ptr = end; reportAddress(); listener.functionEnd(); } @@ -79,777 +66,10 @@ public class CodeSectionParser extends BaseSectionParser { for (int i = 0; i < localEntries; ++i) { reportAddress(); var countInGroup = readLEB(); - var type = readType(); + var type = reader.readType(); listener.local(localIndex, countInGroup, type); localIndex += countInGroup; } reportAddress(); } - - private void parseCode() { - if (!parseExpressions()) { - codeListener.error(blockStack.size()); - blockStack.clear(); - } - } - - private boolean parseExpressions() { - while (data[ptr] != 0x0B) { - if (!parseExpr()) { - return false; - } - } - return true; - } - - private boolean parseExpr() { - reportAddress(); - switch (data[ptr++] & 0xFF) { - case 0x00: - codeListener.opcode(Opcode.UNREACHABLE); - break; - case 0x01: - codeListener.opcode(Opcode.NOP); - break; - case 0x02: - return parseBlock(false); - case 0x03: - return parseBlock(true); - case 0x04: - return parseConditional(); - case 0x06: - return parseTryCatch(); - case 0x8: - codeListener.throwInstruction(readLEB()); - break; - case 0x0C: - parseBranch(BranchOpcode.BR); - break; - case 0x0D: - parseBranch(BranchOpcode.BR_IF); - break; - case 0x0E: - parseTableBranch(); - break; - case 0x0F: - codeListener.opcode(Opcode.RETURN); - break; - case 0x10: - codeListener.call(readLEB() + functionIndexOffset); - break; - case 0x11: - codeListener.indirectCall(readLEB(), readLEB()); - break; - case 0x14: - codeListener.callReference(readLEB()); - break; - - case 0x1A: - codeListener.opcode(Opcode.DROP); - break; - - case 0x20: - codeListener.local(LocalOpcode.GET, readLEB()); - break; - case 0x21: - codeListener.local(LocalOpcode.SET, readLEB()); - break; - - case 0x23: - codeListener.getGlobal(readLEB()); - break; - case 0x24: - codeListener.setGlobal(readLEB()); - break; - - case 0x28: - codeListener.loadInt32(WasmInt32Subtype.INT32, 1 << readLEB(), readLEB()); - break; - case 0x29: - codeListener.loadInt64(WasmInt64Subtype.INT64, 1 << readLEB(), readLEB()); - break; - case 0x2A: - codeListener.loadFloat32(1 << readLEB(), readLEB()); - break; - case 0x2B: - codeListener.loadFloat64(1 << readLEB(), readLEB()); - break; - case 0x2C: - codeListener.loadInt32(WasmInt32Subtype.INT8, 1 << readLEB(), readLEB()); - break; - case 0x2D: - codeListener.loadInt32(WasmInt32Subtype.UINT8, 1 << readLEB(), readLEB()); - break; - case 0x2E: - codeListener.loadInt32(WasmInt32Subtype.INT16, 1 << readLEB(), readLEB()); - break; - case 0x2F: - codeListener.loadInt32(WasmInt32Subtype.UINT16, 1 << readLEB(), readLEB()); - break; - case 0x30: - codeListener.loadInt64(WasmInt64Subtype.INT8, 1 << readLEB(), readLEB()); - break; - case 0x31: - codeListener.loadInt64(WasmInt64Subtype.UINT8, 1 << readLEB(), readLEB()); - break; - case 0x32: - codeListener.loadInt64(WasmInt64Subtype.INT16, 1 << readLEB(), readLEB()); - break; - case 0x33: - codeListener.loadInt64(WasmInt64Subtype.UINT16, 1 << readLEB(), readLEB()); - break; - case 0x34: - codeListener.loadInt64(WasmInt64Subtype.INT32, 1 << readLEB(), readLEB()); - break; - case 0x35: - codeListener.loadInt64(WasmInt64Subtype.UINT32, 1 << readLEB(), readLEB()); - break; - case 0x36: - codeListener.storeInt32(WasmInt32Subtype.INT32, 1 << readLEB(), readLEB()); - break; - case 0x37: - codeListener.storeInt64(WasmInt64Subtype.INT64, 1 << readLEB(), readLEB()); - break; - case 0x38: - codeListener.storeFloat32(1 << readLEB(), readLEB()); - break; - case 0x39: - codeListener.storeFloat64(1 << readLEB(), readLEB()); - break; - case 0x3A: - codeListener.storeInt32(WasmInt32Subtype.INT8, 1 << readLEB(), readLEB()); - break; - case 0x3B: - codeListener.storeInt32(WasmInt32Subtype.INT16, 1 << readLEB(), readLEB()); - break; - case 0x3C: - codeListener.storeInt64(WasmInt64Subtype.INT8, 1 << readLEB(), readLEB()); - break; - case 0x3D: - codeListener.storeInt64(WasmInt64Subtype.INT16, 1 << readLEB(), readLEB()); - break; - case 0x3E: - codeListener.storeInt64(WasmInt64Subtype.INT32, 1 << readLEB(), readLEB()); - break; - case 0x40: - readLEB(); - codeListener.memoryGrow(); - break; - - case 0x41: - codeListener.int32Constant(readSignedLEB()); - break; - case 0x42: - codeListener.int64Constant(readSignedLongLEB()); - break; - case 0x43: - codeListener.float32Constant(Float.intBitsToFloat(readFixedInt())); - break; - case 0x44: - codeListener.float64Constant(Double.longBitsToDouble(readFixedLong())); - break; - - case 0x45: - codeListener.unary(WasmIntUnaryOperation.EQZ, WasmIntType.INT32); - break; - case 0x46: - codeListener.binary(WasmIntBinaryOperation.EQ, WasmIntType.INT32); - break; - case 0x47: - codeListener.binary(WasmIntBinaryOperation.NE, WasmIntType.INT32); - break; - case 0x48: - codeListener.binary(WasmIntBinaryOperation.LT_SIGNED, WasmIntType.INT32); - break; - case 0x49: - codeListener.binary(WasmIntBinaryOperation.LT_UNSIGNED, WasmIntType.INT32); - break; - case 0x4A: - codeListener.binary(WasmIntBinaryOperation.GT_SIGNED, WasmIntType.INT32); - break; - case 0x4B: - codeListener.binary(WasmIntBinaryOperation.GT_UNSIGNED, WasmIntType.INT32); - break; - case 0x4C: - codeListener.binary(WasmIntBinaryOperation.LE_SIGNED, WasmIntType.INT32); - break; - case 0x4D: - codeListener.binary(WasmIntBinaryOperation.LE_UNSIGNED, WasmIntType.INT32); - break; - case 0x4E: - codeListener.binary(WasmIntBinaryOperation.GE_SIGNED, WasmIntType.INT32); - break; - case 0x4F: - codeListener.binary(WasmIntBinaryOperation.GE_UNSIGNED, WasmIntType.INT32); - break; - - case 0x50: - codeListener.unary(WasmIntUnaryOperation.EQZ, WasmIntType.INT64); - break; - case 0x51: - codeListener.binary(WasmIntBinaryOperation.EQ, WasmIntType.INT64); - break; - case 0x52: - codeListener.binary(WasmIntBinaryOperation.NE, WasmIntType.INT64); - break; - case 0x53: - codeListener.binary(WasmIntBinaryOperation.LT_SIGNED, WasmIntType.INT64); - break; - case 0x54: - codeListener.binary(WasmIntBinaryOperation.LT_UNSIGNED, WasmIntType.INT64); - break; - case 0x55: - codeListener.binary(WasmIntBinaryOperation.GT_SIGNED, WasmIntType.INT64); - break; - case 0x56: - codeListener.binary(WasmIntBinaryOperation.GT_UNSIGNED, WasmIntType.INT64); - break; - case 0x57: - codeListener.binary(WasmIntBinaryOperation.LE_SIGNED, WasmIntType.INT64); - break; - case 0x58: - codeListener.binary(WasmIntBinaryOperation.LE_UNSIGNED, WasmIntType.INT64); - break; - case 0x59: - codeListener.binary(WasmIntBinaryOperation.GE_SIGNED, WasmIntType.INT64); - break; - case 0x5A: - codeListener.binary(WasmIntBinaryOperation.GE_UNSIGNED, WasmIntType.INT64); - break; - - case 0x5B: - codeListener.binary(WasmFloatBinaryOperation.EQ, WasmFloatType.FLOAT32); - break; - case 0x5C: - codeListener.binary(WasmFloatBinaryOperation.NE, WasmFloatType.FLOAT32); - break; - case 0x5D: - codeListener.binary(WasmFloatBinaryOperation.LT, WasmFloatType.FLOAT32); - break; - case 0x5E: - codeListener.binary(WasmFloatBinaryOperation.GT, WasmFloatType.FLOAT32); - break; - case 0x5F: - codeListener.binary(WasmFloatBinaryOperation.LE, WasmFloatType.FLOAT32); - break; - case 0x60: - codeListener.binary(WasmFloatBinaryOperation.GE, WasmFloatType.FLOAT32); - break; - - case 0x61: - codeListener.binary(WasmFloatBinaryOperation.EQ, WasmFloatType.FLOAT64); - break; - case 0x62: - codeListener.binary(WasmFloatBinaryOperation.NE, WasmFloatType.FLOAT64); - break; - case 0x63: - codeListener.binary(WasmFloatBinaryOperation.LT, WasmFloatType.FLOAT64); - break; - case 0x64: - codeListener.binary(WasmFloatBinaryOperation.GT, WasmFloatType.FLOAT64); - break; - case 0x65: - codeListener.binary(WasmFloatBinaryOperation.LE, WasmFloatType.FLOAT64); - break; - case 0x66: - codeListener.binary(WasmFloatBinaryOperation.GE, WasmFloatType.FLOAT64); - break; - - case 0x67: - codeListener.unary(WasmIntUnaryOperation.CLZ, WasmIntType.INT32); - break; - case 0x68: - codeListener.unary(WasmIntUnaryOperation.CTZ, WasmIntType.INT32); - break; - case 0x69: - codeListener.unary(WasmIntUnaryOperation.POPCNT, WasmIntType.INT32); - break; - case 0x6A: - codeListener.binary(WasmIntBinaryOperation.ADD, WasmIntType.INT32); - break; - case 0x6B: - codeListener.binary(WasmIntBinaryOperation.SUB, WasmIntType.INT32); - break; - case 0x6C: - codeListener.binary(WasmIntBinaryOperation.MUL, WasmIntType.INT32); - break; - case 0x6D: - codeListener.binary(WasmIntBinaryOperation.DIV_SIGNED, WasmIntType.INT32); - break; - case 0x6E: - codeListener.binary(WasmIntBinaryOperation.DIV_UNSIGNED, WasmIntType.INT32); - break; - case 0x6F: - codeListener.binary(WasmIntBinaryOperation.REM_SIGNED, WasmIntType.INT32); - break; - case 0x70: - codeListener.binary(WasmIntBinaryOperation.REM_UNSIGNED, WasmIntType.INT32); - break; - case 0x71: - codeListener.binary(WasmIntBinaryOperation.AND, WasmIntType.INT32); - break; - case 0x72: - codeListener.binary(WasmIntBinaryOperation.OR, WasmIntType.INT32); - break; - case 0x73: - codeListener.binary(WasmIntBinaryOperation.XOR, WasmIntType.INT32); - break; - case 0x74: - codeListener.binary(WasmIntBinaryOperation.SHL, WasmIntType.INT32); - break; - case 0x75: - codeListener.binary(WasmIntBinaryOperation.SHR_SIGNED, WasmIntType.INT32); - break; - case 0x76: - codeListener.binary(WasmIntBinaryOperation.SHR_UNSIGNED, WasmIntType.INT32); - break; - case 0x77: - codeListener.binary(WasmIntBinaryOperation.ROTL, WasmIntType.INT32); - break; - case 0x78: - codeListener.binary(WasmIntBinaryOperation.ROTR, WasmIntType.INT32); - break; - - case 0x79: - codeListener.unary(WasmIntUnaryOperation.CLZ, WasmIntType.INT64); - break; - case 0x7A: - codeListener.unary(WasmIntUnaryOperation.CTZ, WasmIntType.INT64); - break; - case 0x7B: - codeListener.unary(WasmIntUnaryOperation.POPCNT, WasmIntType.INT64); - break; - case 0x7C: - codeListener.binary(WasmIntBinaryOperation.ADD, WasmIntType.INT64); - break; - case 0x7D: - codeListener.binary(WasmIntBinaryOperation.SUB, WasmIntType.INT64); - break; - case 0x7E: - codeListener.binary(WasmIntBinaryOperation.MUL, WasmIntType.INT64); - break; - case 0x7F: - codeListener.binary(WasmIntBinaryOperation.DIV_SIGNED, WasmIntType.INT64); - break; - case 0x80: - codeListener.binary(WasmIntBinaryOperation.DIV_UNSIGNED, WasmIntType.INT64); - break; - case 0x81: - codeListener.binary(WasmIntBinaryOperation.REM_SIGNED, WasmIntType.INT64); - break; - case 0x82: - codeListener.binary(WasmIntBinaryOperation.REM_UNSIGNED, WasmIntType.INT64); - break; - case 0x83: - codeListener.binary(WasmIntBinaryOperation.AND, WasmIntType.INT64); - break; - case 0x84: - codeListener.binary(WasmIntBinaryOperation.OR, WasmIntType.INT64); - break; - case 0x85: - codeListener.binary(WasmIntBinaryOperation.XOR, WasmIntType.INT64); - break; - case 0x86: - codeListener.binary(WasmIntBinaryOperation.SHL, WasmIntType.INT64); - break; - case 0x87: - codeListener.binary(WasmIntBinaryOperation.SHR_UNSIGNED, WasmIntType.INT64); - break; - case 0x88: - codeListener.binary(WasmIntBinaryOperation.SHR_UNSIGNED, WasmIntType.INT64); - break; - case 0x89: - codeListener.binary(WasmIntBinaryOperation.ROTL, WasmIntType.INT64); - break; - case 0x8A: - codeListener.binary(WasmIntBinaryOperation.ROTR, WasmIntType.INT64); - break; - - case 0x8B: - codeListener.unary(WasmFloatUnaryOperation.ABS, WasmFloatType.FLOAT32); - break; - case 0x8C: - codeListener.unary(WasmFloatUnaryOperation.NEG, WasmFloatType.FLOAT32); - break; - case 0x8D: - codeListener.unary(WasmFloatUnaryOperation.CEIL, WasmFloatType.FLOAT32); - break; - case 0x8E: - codeListener.unary(WasmFloatUnaryOperation.FLOOR, WasmFloatType.FLOAT32); - break; - case 0x8F: - codeListener.unary(WasmFloatUnaryOperation.TRUNC, WasmFloatType.FLOAT32); - break; - case 0x90: - codeListener.unary(WasmFloatUnaryOperation.NEAREST, WasmFloatType.FLOAT32); - break; - case 0x91: - codeListener.unary(WasmFloatUnaryOperation.SQRT, WasmFloatType.FLOAT32); - break; - case 0x92: - codeListener.binary(WasmFloatBinaryOperation.ADD, WasmFloatType.FLOAT32); - break; - case 0x93: - codeListener.binary(WasmFloatBinaryOperation.SUB, WasmFloatType.FLOAT32); - break; - case 0x94: - codeListener.binary(WasmFloatBinaryOperation.MUL, WasmFloatType.FLOAT32); - break; - case 0x95: - codeListener.binary(WasmFloatBinaryOperation.DIV, WasmFloatType.FLOAT32); - break; - case 0x96: - codeListener.binary(WasmFloatBinaryOperation.MIN, WasmFloatType.FLOAT32); - break; - case 0x97: - codeListener.binary(WasmFloatBinaryOperation.MAX, WasmFloatType.FLOAT32); - break; - case 0x98: - codeListener.unary(WasmFloatUnaryOperation.COPYSIGN, WasmFloatType.FLOAT32); - break; - - case 0x99: - codeListener.unary(WasmFloatUnaryOperation.ABS, WasmFloatType.FLOAT64); - break; - case 0x9A: - codeListener.unary(WasmFloatUnaryOperation.NEG, WasmFloatType.FLOAT64); - break; - case 0x9B: - codeListener.unary(WasmFloatUnaryOperation.CEIL, WasmFloatType.FLOAT64); - break; - case 0x9C: - codeListener.unary(WasmFloatUnaryOperation.FLOOR, WasmFloatType.FLOAT64); - break; - case 0x9D: - codeListener.unary(WasmFloatUnaryOperation.TRUNC, WasmFloatType.FLOAT64); - break; - case 0x9E: - codeListener.unary(WasmFloatUnaryOperation.NEAREST, WasmFloatType.FLOAT64); - break; - case 0x9F: - codeListener.unary(WasmFloatUnaryOperation.SQRT, WasmFloatType.FLOAT64); - break; - case 0xA0: - codeListener.binary(WasmFloatBinaryOperation.ADD, WasmFloatType.FLOAT64); - break; - case 0xA1: - codeListener.binary(WasmFloatBinaryOperation.SUB, WasmFloatType.FLOAT64); - break; - case 0xA2: - codeListener.binary(WasmFloatBinaryOperation.MUL, WasmFloatType.FLOAT64); - break; - case 0xA3: - codeListener.binary(WasmFloatBinaryOperation.DIV, WasmFloatType.FLOAT64); - break; - case 0xA4: - codeListener.binary(WasmFloatBinaryOperation.MIN, WasmFloatType.FLOAT64); - break; - case 0xA5: - codeListener.binary(WasmFloatBinaryOperation.MAX, WasmFloatType.FLOAT64); - break; - case 0xA6: - codeListener.unary(WasmFloatUnaryOperation.COPYSIGN, WasmFloatType.FLOAT64); - break; - - case 0xA7: - codeListener.convert(WasmNumType.INT64, WasmNumType.INT32, false, false); - break; - case 0xA8: - codeListener.convert(WasmNumType.FLOAT32, WasmNumType.INT32, false, false); - break; - case 0xA9: - codeListener.convert(WasmNumType.FLOAT32, WasmNumType.INT32, true, false); - break; - case 0xAA: - codeListener.convert(WasmNumType.FLOAT64, WasmNumType.INT32, false, false); - break; - case 0xAB: - codeListener.convert(WasmNumType.FLOAT64, WasmNumType.INT32, true, false); - break; - case 0xAC: - codeListener.convert(WasmNumType.INT32, WasmNumType.INT64, false, false); - break; - case 0xAD: - codeListener.convert(WasmNumType.INT32, WasmNumType.INT64, true, false); - break; - case 0xAE: - codeListener.convert(WasmNumType.FLOAT32, WasmNumType.INT64, false, false); - break; - case 0xAF: - codeListener.convert(WasmNumType.FLOAT32, WasmNumType.INT64, true, false); - break; - case 0xB0: - codeListener.convert(WasmNumType.FLOAT64, WasmNumType.INT64, false, false); - break; - case 0xB1: - codeListener.convert(WasmNumType.FLOAT64, WasmNumType.INT64, true, false); - break; - case 0xB2: - codeListener.convert(WasmNumType.INT32, WasmNumType.FLOAT32, false, false); - break; - case 0xB3: - codeListener.convert(WasmNumType.INT32, WasmNumType.FLOAT32, true, false); - break; - case 0xB4: - codeListener.convert(WasmNumType.INT64, WasmNumType.FLOAT32, false, false); - break; - case 0xB5: - codeListener.convert(WasmNumType.INT64, WasmNumType.FLOAT32, true, false); - break; - case 0xB6: - codeListener.convert(WasmNumType.FLOAT64, WasmNumType.FLOAT32, true, false); - break; - case 0xB7: - codeListener.convert(WasmNumType.INT32, WasmNumType.FLOAT64, false, false); - break; - case 0xB8: - codeListener.convert(WasmNumType.INT32, WasmNumType.FLOAT64, true, false); - break; - case 0xB9: - codeListener.convert(WasmNumType.INT64, WasmNumType.FLOAT64, false, false); - break; - case 0xBA: - codeListener.convert(WasmNumType.INT64, WasmNumType.FLOAT64, true, false); - break; - case 0xBC: - codeListener.convert(WasmNumType.FLOAT32, WasmNumType.INT32, false, true); - break; - case 0xBD: - codeListener.convert(WasmNumType.FLOAT64, WasmNumType.INT64, false, true); - break; - case 0xBE: - codeListener.convert(WasmNumType.INT32, WasmNumType.FLOAT32, false, true); - break; - case 0xBF: - codeListener.convert(WasmNumType.INT64, WasmNumType.FLOAT64, false, true); - break; - - case 0xD0: - codeListener.nullConstant(readHeapType()); - break; - - case 0xD2: - codeListener.functionReference(readLEB() + functionIndexOffset); - break; - - case 0xD3: - codeListener.opcode(Opcode.REF_EQ); - break; - - case 0xFB: - return parseExtExpr2(); - case 0xFC: - return parseExtExpr(); - - default: - return false; - } - return true; - } - - private boolean parseExtExpr() { - switch (readLEB()) { - case 10: { - if (data[ptr++] != 0 || data[ptr++] != 0) { - return false; - } - codeListener.memoryCopy(); - return true; - } - case 11: { - if (data[ptr++] != 0) { - return false; - } - codeListener.memoryFill(); - return true; - } - - default: - return false; - } - } - - private boolean parseExtExpr2() { - switch (readLEB()) { - case 0: - codeListener.structNew(readLEB()); - return true; - - case 1: - codeListener.structNewDefault(readLEB()); - return true; - - case 2: - codeListener.structGet(null, readLEB(), readLEB()); - return true; - case 3: - codeListener.structGet(WasmSignedType.SIGNED, readLEB(), readLEB()); - return true; - case 4: - codeListener.structGet(WasmSignedType.UNSIGNED, readLEB(), readLEB()); - return true; - - case 5: - codeListener.structSet(readLEB(), readLEB()); - return true; - - case 7: - codeListener.arrayNewDefault(readLEB()); - return true; - - case 11: - codeListener.arrayGet(null, readLEB()); - return true; - case 12: - codeListener.arrayGet(WasmSignedType.SIGNED, readLEB()); - return true; - case 13: - codeListener.arrayGet(WasmSignedType.UNSIGNED, readLEB()); - return true; - - case 14: - codeListener.arraySet(readLEB()); - return true; - - case 15: - codeListener.opcode(Opcode.ARRAY_LENGTH); - return true; - - case 17: - codeListener.arrayCopy(readLEB(), readLEB()); - return true; - - case 20: - codeListener.test(readHeapType(), false); - return true; - - case 21: - codeListener.test(readHeapType(), true); - return true; - - case 22: - codeListener.cast(readHeapType(), false); - return true; - - case 23: - codeListener.cast(readHeapType(), true); - return true; - - case 28: - codeListener.int31Reference(); - return true; - - case 29: - codeListener.int31Get(WasmSignedType.SIGNED); - return true; - case 30: - codeListener.int31Get(WasmSignedType.UNSIGNED); - return true; - - default: - return false; - } - } - - private boolean parseBlock(boolean isLoop) { - var type = readType(); - var token = codeListener.startBlock(isLoop, type); - blockStack.add(new Block(token)); - if (!parseExpressions()) { - return false; - } - blockStack.remove(blockStack.size() - 1); - reportAddress(); - codeListener.endBlock(token, isLoop); - ++ptr; - return true; - } - - private boolean parseConditional() { - var type = readType(); - var token = codeListener.startConditionalBlock(type); - blockStack.add(new Block(token)); - var hasElse = false; - loop: while (true) { - switch (data[ptr]) { - case 0x0B: - break loop; - case 0x05: - if (hasElse) { - return false; - } - reportAddress(); - codeListener.startElseSection(blockStack.get(blockStack.size() - 1).token); - ++ptr; - break; - default: - if (!parseExpr()) { - return false; - } - break; - } - } - blockStack.remove(blockStack.size() - 1); - reportAddress(); - codeListener.endBlock(token, false); - ++ptr; - return true; - } - - private boolean parseTryCatch() { - var type = readType(); - var token = codeListener.startTry(type); - blockStack.add(new Block(token)); - loop: while (true) { - switch (data[ptr]) { - case 0x0B: - break loop; - case 0x07: { - reportAddress(); - var tagIndex = readLEB(); - ++ptr; - codeListener.startCatch(tagIndex); - break; - } - default: - if (!parseExpr()) { - return false; - } - break; - } - } - blockStack.remove(blockStack.size() - 1); - reportAddress(); - codeListener.endBlock(token, false); - ++ptr; - return true; - } - - private void parseBranch(BranchOpcode opcode) { - var depth = readLEB(); - var target = blockStack.get(blockStack.size() - depth - 1); - codeListener.branch(opcode, depth, target.token); - } - - private void parseTableBranch() { - var count = readLEB(); - var depths = new int[count]; - var targets = new int[count]; - for (var i = 0; i < count; ++i) { - var depth = readLEB(); - depths[i] = depth; - targets[i] = blockStack.get(blockStack.size() - depth - 1).token; - } - var defaultDepth = readLEB(); - var defaultTarget = blockStack.get(blockStack.size() - defaultDepth - 1).token; - codeListener.tableBranch(depths, targets, defaultDepth, defaultTarget); - } - - private static class Block { - int token; - - Block(int token) { - this.token = token; - } - } } diff --git a/core/src/main/java/org/teavm/backend/wasm/parser/GlobalSectionListener.java b/core/src/main/java/org/teavm/backend/wasm/parser/GlobalSectionListener.java new file mode 100644 index 000000000..7d27e3a89 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/parser/GlobalSectionListener.java @@ -0,0 +1,22 @@ +/* + * 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 GlobalSectionListener { + CodeListener startGlobal(int index, WasmHollowType type, boolean mutable); + + void endGlobal(); +} diff --git a/core/src/main/java/org/teavm/backend/wasm/parser/GlobalSectionParser.java b/core/src/main/java/org/teavm/backend/wasm/parser/GlobalSectionParser.java new file mode 100644 index 000000000..daf91ed21 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/parser/GlobalSectionParser.java @@ -0,0 +1,52 @@ +/* + * 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 GlobalSectionParser extends BaseSectionParser { + private final GlobalSectionListener listener; + private CodeParser codeParser; + private int functionIndexOffset; + + public GlobalSectionParser(GlobalSectionListener listener) { + this.listener = listener; + codeParser = new CodeParser(); + } + + public void setFunctionIndexOffset(int functionIndexOffset) { + this.functionIndexOffset = functionIndexOffset; + } + + @Override + protected void parseContent() { + var count = readLEB(); + for (var i = 0; i < count; ++i) { + reportAddress(); + var type = reader.readType(); + var mutable = reader.data[reader.ptr++] != 0; + var codeListener = listener.startGlobal(i, type, mutable); + if (codeListener == null) { + codeListener = CodeListener.EMPTY; + } + codeParser.setCodeListener(codeListener); + codeParser.setFunctionIndexOffset(functionIndexOffset); + if (!codeParser.parseSingleExpression(reader)) { + throw new ParseException("Error parsing global initializer", reader.ptr); + } + reader.ptr++; + listener.endGlobal(); + } + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/parser/ImportSectionParser.java b/core/src/main/java/org/teavm/backend/wasm/parser/ImportSectionParser.java index 2caffdcbb..256da35a7 100644 --- a/core/src/main/java/org/teavm/backend/wasm/parser/ImportSectionParser.java +++ b/core/src/main/java/org/teavm/backend/wasm/parser/ImportSectionParser.java @@ -18,8 +18,7 @@ package org.teavm.backend.wasm.parser; public class ImportSectionParser extends BaseSectionParser { private final ImportSectionListener listener; - public ImportSectionParser(AddressListener addressListener, ImportSectionListener listener) { - super(addressListener); + public ImportSectionParser(ImportSectionListener listener) { this.listener = listener; } @@ -37,12 +36,12 @@ public class ImportSectionParser extends BaseSectionParser { var name = readString(); listener.startEntry(module, name); reportAddress(); - var type = data[ptr++]; + var type = reader.data[reader.ptr++]; if (type == 0) { var typeIndex = readLEB(); listener.function(typeIndex); } else { - throw new ParseException("Unsupported import type", ptr); + throw new ParseException("Unsupported import type", reader.ptr); } listener.endEntry(); } diff --git a/core/src/main/java/org/teavm/backend/wasm/parser/NameSectionParser.java b/core/src/main/java/org/teavm/backend/wasm/parser/NameSectionParser.java index 5ae24a13d..f394ba9d4 100644 --- a/core/src/main/java/org/teavm/backend/wasm/parser/NameSectionParser.java +++ b/core/src/main/java/org/teavm/backend/wasm/parser/NameSectionParser.java @@ -19,16 +19,15 @@ public class NameSectionParser extends BaseSectionParser { private final NameSectionListener listener; public NameSectionParser(NameSectionListener listener) { - super(AddressListener.EMPTY); this.listener = listener; } @Override protected void parseContent() { - while (ptr < data.length) { - var sectionType = data[ptr++]; + while (reader.ptr < reader.data.length) { + var sectionType = reader.data[reader.ptr++]; var sectionLength = readLEB(); - var next = ptr + sectionLength; + var next = reader.ptr + sectionLength; switch (sectionType) { case 1: parseNameMapSubsection(listener.functions()); @@ -46,7 +45,7 @@ public class NameSectionParser extends BaseSectionParser { parseIndirectNameMap(listener.fields()); break; } - ptr = next; + reader.ptr = next; } } 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 index 2c5c7ea86..aef48d4d9 100644 --- a/core/src/main/java/org/teavm/backend/wasm/parser/TypeSectionParser.java +++ b/core/src/main/java/org/teavm/backend/wasm/parser/TypeSectionParser.java @@ -19,8 +19,7 @@ public class TypeSectionParser extends BaseSectionParser { private TypeSectionListener listener; private int typeIndex; - public TypeSectionParser(AddressListener addressListener, TypeSectionListener listener) { - super(addressListener); + public TypeSectionParser(TypeSectionListener listener) { this.listener = listener; } @@ -38,7 +37,7 @@ public class TypeSectionParser extends BaseSectionParser { } private void parseType() { - if (data[ptr] == 0x4E) { + if (reader.data[reader.ptr] == 0x4E) { parseRecType(); } else { parseSubtype(); @@ -47,7 +46,7 @@ public class TypeSectionParser extends BaseSectionParser { private void parseRecType() { reportAddress(); - ++ptr; + ++reader.ptr; var count = readLEB(); listener.startRecType(count); for (var i = 0; i < count; ++i) { @@ -57,15 +56,15 @@ public class TypeSectionParser extends BaseSectionParser { } private void parseSubtype() { - switch (data[ptr]) { + switch (reader.data[reader.ptr]) { case 0x50: reportAddress(); - ++ptr; + ++reader.ptr; parseCompositeType(true, readSupertypes()); break; case 0x4F: reportAddress(); - ++ptr; + ++reader.ptr; parseCompositeType(false, readSupertypes()); break; default: @@ -77,17 +76,17 @@ public class TypeSectionParser extends BaseSectionParser { private void parseCompositeType(boolean open, int[] supertypes) { reportAddress(); listener.startType(typeIndex++, open, supertypes); - switch (data[ptr]) { + switch (reader.data[reader.ptr]) { case 0x5E: reportAddress(); - ++ptr; + ++reader.ptr; listener.startArrayType(); parseField(); listener.endArrayType(); break; case 0x5F: { reportAddress(); - ++ptr; + ++reader.ptr; var fieldCount = readLEB(); listener.startStructType(fieldCount); for (var i = 0; i < fieldCount; ++i) { @@ -98,32 +97,32 @@ public class TypeSectionParser extends BaseSectionParser { } case 0x60: { reportAddress(); - ++ptr; + ++reader.ptr; var paramCount = readLEB(); listener.funcType(paramCount); for (var i = 0; i < paramCount; ++i) { reportAddress(); - listener.resultType(readType()); + listener.resultType(reader.readType()); } var resultCount = readLEB(); listener.funcTypeResults(resultCount); for (var i = 0; i < resultCount; ++i) { reportAddress(); - listener.resultType(readType()); + listener.resultType(reader.readType()); } listener.endFuncType(); break; } default: - throw new ParseException("Unknown type declaration", ptr); + throw new ParseException("Unknown type declaration", reader.ptr); } listener.endType(); } private void parseField() { reportAddress(); - var type = readStorageType(); - var mutable = data[ptr++] != 0; + var type = reader.readStorageType(); + var mutable = reader.data[reader.ptr++] != 0; listener.field(type, mutable); } diff --git a/core/src/main/java/org/teavm/backend/wasm/parser/WasmBinaryReader.java b/core/src/main/java/org/teavm/backend/wasm/parser/WasmBinaryReader.java new file mode 100644 index 000000000..4a1e2cdde --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/parser/WasmBinaryReader.java @@ -0,0 +1,189 @@ +/* + * 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 java.nio.charset.StandardCharsets; + +public class WasmBinaryReader { + private final AddressListener addressListener; + public final byte[] data; + public int ptr; + private int lastReportedPtr = -1; + + public WasmBinaryReader(AddressListener addressListener, byte[] data) { + this.addressListener = addressListener; + this.data = data; + } + + public void reportAddress() { + if (ptr != lastReportedPtr) { + lastReportedPtr = ptr; + if (addressListener != null) { + addressListener.address(ptr); + } + } + } + + public 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()); + } + } + + public 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); + } + } + + public WasmHollowType.Reference readHeapType() { + var typeId = data[ptr]; + if ((typeId & 0xC0) == 0x40) { + var result = readAbsHeapType(typeId); + ++ptr; + return result; + } + return new WasmHollowType.CompositeReference(readLEB()); + } + + public 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); + } + } + + public 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; + } + + public 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; + } + + public 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; + } + + public 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; + } + + public int readFixedInt() { + return ((data[ptr++] & 0xFF) << 24) + | ((data[ptr++] & 0xFF) << 16) + | ((data[ptr++] & 0xFF) << 8) + | (data[ptr++] & 0xFF); + } + + public 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); + } + + public String readString() { + var size = readLEB(); + var result = new String(data, ptr, size, StandardCharsets.UTF_8); + ptr += size; + return result; + } +}