wasm: support instructions that were recently implemented in generator

This commit is contained in:
Alexey Andreev 2024-07-31 17:31:39 +02:00
parent 5572d4b5d7
commit 75bead66b3
7 changed files with 458 additions and 34 deletions

View File

@ -21,12 +21,12 @@ import java.util.List;
import org.teavm.backend.wasm.debug.info.ControlFlowInfo;
import org.teavm.backend.wasm.debug.info.FunctionControlFlow;
import org.teavm.backend.wasm.debug.info.FunctionControlFlowBuilder;
import org.teavm.backend.wasm.model.WasmType;
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.Opcode;
import org.teavm.backend.wasm.parser.WasmHollowType;
public class ControlFlowParser implements CodeSectionListener, CodeListener, AddressListener {
private int previousAddress;
@ -60,12 +60,12 @@ public class ControlFlowParser implements CodeSectionListener, CodeListener, Add
}
@Override
public int startBlock(boolean loop, WasmType type) {
public int startBlock(boolean loop, WasmHollowType type) {
return startBlock(loop);
}
@Override
public int startConditionalBlock(WasmType type) {
public int startConditionalBlock(WasmHollowType type) {
return startBlock(false);
}

View File

@ -21,7 +21,6 @@ import java.io.PrintWriter;
import java.nio.file.Files;
import java.util.function.Consumer;
import org.teavm.backend.wasm.model.WasmNumType;
import org.teavm.backend.wasm.model.WasmType;
import org.teavm.backend.wasm.model.expression.WasmFloatBinaryOperation;
import org.teavm.backend.wasm.model.expression.WasmFloatType;
import org.teavm.backend.wasm.model.expression.WasmFloatUnaryOperation;
@ -30,6 +29,7 @@ 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;
@ -38,20 +38,26 @@ import org.teavm.backend.wasm.parser.CodeSectionParser;
import org.teavm.backend.wasm.parser.LocalOpcode;
import org.teavm.backend.wasm.parser.ModuleParser;
import org.teavm.backend.wasm.parser.Opcode;
import org.teavm.backend.wasm.parser.WasmHollowType;
import org.teavm.common.ByteArrayAsyncInputStream;
public class DisassemblyCodeSectionListener implements AddressListener, CodeSectionListener, CodeListener {
private DisassemblyWriter writer;
private int address;
private int addressOffset;
private int blockIdGen;
public DisassemblyCodeSectionListener(DisassemblyWriter writer) {
this.writer = writer;
}
public void setAddressOffset(int addressOffset) {
this.addressOffset = addressOffset;
}
@Override
public void address(int address) {
this.address = address;
this.address = address + addressOffset;
}
@Override
@ -61,7 +67,7 @@ public class DisassemblyCodeSectionListener implements AddressListener, CodeSect
@Override
public boolean functionStart(int index, int size) {
writer.address(address).write("(func $fun_" + index).indent().eol();
writer.address(address).write("(func (; " + index + " ;)").indent().eol();
return true;
}
@ -71,10 +77,10 @@ public class DisassemblyCodeSectionListener implements AddressListener, CodeSect
}
@Override
public void local(int start, int count, WasmType type) {
public void local(int start, int count, WasmHollowType type) {
writer.address(address);
for (int i = 0; i < count; ++i) {
writer.write("(local $loc_" + (i + start) + " " + typeToString(type) + ")").eol();
writer.write("(local (; " + (i + start) + " ;) " + typeToString(type) + ")").eol();
}
}
@ -94,7 +100,7 @@ public class DisassemblyCodeSectionListener implements AddressListener, CodeSect
writer.outdent().write(")").eol();
}
private String blockTypeToString(WasmType type) {
private String blockTypeToString(WasmHollowType type) {
if (type == null) {
return "";
} else {
@ -102,10 +108,10 @@ public class DisassemblyCodeSectionListener implements AddressListener, CodeSect
}
}
private String typeToString(WasmType type) {
private String typeToString(WasmHollowType type) {
if (type != null) {
if (type instanceof WasmType.Number) {
switch (((WasmType.Number) type).number) {
if (type instanceof WasmHollowType.Number) {
switch (((WasmHollowType.Number) type).number) {
case INT32:
return "i32";
case INT64:
@ -117,8 +123,23 @@ public class DisassemblyCodeSectionListener implements AddressListener, CodeSect
default:
break;
}
} else if (type instanceof WasmType.Reference) {
return "ref";
} else if (type instanceof WasmHollowType.SpecialReference) {
switch (((WasmHollowType.SpecialReference) type).kind) {
case ANY:
return "any";
case FUNC:
return "func";
case ARRAY:
return "array";
case EXTERN:
return "extern";
case STRUCT:
return "struct";
default:
throw new IllegalArgumentException();
}
} else if (type instanceof WasmHollowType.CompositeReference) {
return String.valueOf(((WasmHollowType.CompositeReference) type).index);
}
}
return "unknown";
@ -134,7 +155,7 @@ public class DisassemblyCodeSectionListener implements AddressListener, CodeSect
}
@Override
public int startBlock(boolean loop, WasmType type) {
public int startBlock(boolean loop, WasmHollowType type) {
writer.address(address);
var label = blockIdGen++;
writer.write(loop ? "loop" : "block").write(" $label_" + label).write(blockTypeToString(type))
@ -143,7 +164,7 @@ public class DisassemblyCodeSectionListener implements AddressListener, CodeSect
}
@Override
public int startConditionalBlock(WasmType type) {
public int startConditionalBlock(WasmHollowType type) {
writer.address(address);
var label = blockIdGen++;
writer.write("if ").write(" $label_" + label).write(blockTypeToString(type)).indent().eol();
@ -156,6 +177,20 @@ public class DisassemblyCodeSectionListener implements AddressListener, CodeSect
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).write(blockTypeToString(type)).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();
@ -185,6 +220,12 @@ public class DisassemblyCodeSectionListener implements AddressListener, CodeSect
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);
@ -201,6 +242,12 @@ public class DisassemblyCodeSectionListener implements AddressListener, CodeSect
case DROP:
writer.write("drop");
break;
case REF_EQ:
writer.write("ref.eq");
break;
case ARRAY_LENGTH:
writer.write("array.length");
break;
}
writer.eol();
}
@ -216,20 +263,35 @@ public class DisassemblyCodeSectionListener implements AddressListener, CodeSect
writer.write("local.set");
break;
}
writer.write(" $loc_" + index).eol();
writer.write(" " + index).eol();
}
@Override
public void getGlobal(int globalIndex) {
writer.address(address).write("global.get ").write(Integer.toString(globalIndex)).eol();
}
@Override
public void setGlobal(int globalIndex) {
writer.address(address).write("global.set ").write(Integer.toString(globalIndex)).eol();
}
@Override
public void call(int functionIndex) {
writer.address(address);
writer.write("call $fun_" + functionIndex).eol();
writer.write("call " + functionIndex).eol();
}
@Override
public void indirectCall(int typeIndex, int tableIndex) {
writer.address(address);
//TODO: type signature must be printed
writer.write("call_indirect $table_" + tableIndex + " $type_" + typeIndex).eol();
writer.write("call_indirect " + tableIndex + " " + typeIndex).eol();
}
@Override
public void callReference(int typeIndex) {
writer.address(address);
writer.write("call_ref " + typeIndex).eol();
}
@Override
@ -429,6 +491,9 @@ public class DisassemblyCodeSectionListener implements AddressListener, CodeSect
case CTZ:
writer.write("ctz");
break;
case EQZ:
writer.write("eqz");
break;
case POPCNT:
writer.write("popcnt");
break;
@ -758,6 +823,73 @@ public class DisassemblyCodeSectionListener implements AddressListener, CodeSect
writer.address(address).write("f64.const " + Double.toHexString(value)).eol();
}
@Override
public void nullConstant(WasmHollowType.Reference type) {
writer.address(address).write("ref.null ").write(typeToString(type)).eol();
}
@Override
public void cast(WasmHollowType.Reference type) {
writer.address(address).write("ref.cast ").write(typeToString(type)).eol();
}
@Override
public void structNew(int typeIndex) {
writer.address(address).write("struct.new ").write(Integer.toString(typeIndex)).eol();
}
@Override
public void structNewDefault(int typeIndex) {
writer.address(address).write("struct.new_default ").write(Integer.toString(typeIndex)).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(" ").write(Integer.toString(typeIndex)).write(" ").write(Integer.toString(fieldIndex)).eol();
}
@Override
public void structSet(int typeIndex, int fieldIndex) {
writer.address(address).write("struct.set ").write(Integer.toString(typeIndex)).write(" ")
.write(Integer.toString(fieldIndex)).eol();
}
@Override
public void arrayNewDefault(int typeIndex) {
writer.address(address).write("array.new_default ").write(Integer.toString(typeIndex)).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(" ").write(Integer.toString(typeIndex)).eol();
}
@Override
public void arraySet(int typeIndex) {
writer.address(address).write("array.set ").write(Integer.toString(typeIndex)).eol();
}
@Override
public void functionReference(int functionIndex) {
writer.address(address).write("ref.func ").write(Integer.toString(functionIndex)).eol();
}
public static void main(String[] args) throws IOException {
var file = new File(args[0]);
var bytes = Files.readAllBytes(file.toPath());
@ -770,6 +902,7 @@ public class DisassemblyCodeSectionListener implements AddressListener, CodeSect
var out = new PrintWriter(System.out);
var writer = new DisassemblyWriter(out, true);
var disassembler = new DisassemblyCodeSectionListener(writer);
disassembler.setAddressOffset(pos);
var sectionParser = new CodeSectionParser(disassembler, disassembler);
sectionParser.parse(bytes);
out.flush();

View File

@ -16,7 +16,6 @@
package org.teavm.backend.wasm.parser;
import org.teavm.backend.wasm.model.WasmNumType;
import org.teavm.backend.wasm.model.WasmType;
import org.teavm.backend.wasm.model.expression.WasmFloatBinaryOperation;
import org.teavm.backend.wasm.model.expression.WasmFloatType;
import org.teavm.backend.wasm.model.expression.WasmFloatUnaryOperation;
@ -25,22 +24,30 @@ 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 interface CodeListener {
default void error(int depth) {
}
default int startBlock(boolean loop, WasmType type) {
default int startBlock(boolean loop, WasmHollowType type) {
return 0;
}
default int startConditionalBlock(WasmType type) {
default int startConditionalBlock(WasmHollowType type) {
return 0;
}
default void startElseSection(int token) {
}
default int startTry(WasmHollowType type) {
return 0;
}
default void startCatch(int tagIndex) {
}
default void endBlock(int token, boolean loop) {
}
@ -50,6 +57,9 @@ public interface CodeListener {
default void tableBranch(int[] depths, int[] targets, int defaultDepth, int defaultTarget) {
}
default void throwInstruction(int tagIndex) {
}
default void opcode(Opcode opcode) {
}
@ -121,4 +131,43 @@ public interface CodeListener {
default void float64Constant(double value) {
}
default void nullConstant(WasmHollowType.Reference type) {
}
default void cast(WasmHollowType.Reference type) {
}
default void structNew(int typeIndex) {
}
default void structNewDefault(int typeIndex) {
}
default void structGet(WasmSignedType signedType, int typeIndex, int fieldIndex) {
}
default void structSet(int typeIndex, int fieldIndex) {
}
default void arrayNewDefault(int typeIndex) {
}
default void arrayGet(WasmSignedType signedType, int typeIndex) {
}
default void arraySet(int typeIndex) {
}
default void getGlobal(int globalIndex) {
}
default void setGlobal(int globalIndex) {
}
default void callReference(int typeIndex) {
}
default void functionReference(int functionIndex) {
}
}

View File

@ -15,8 +15,6 @@
*/
package org.teavm.backend.wasm.parser;
import org.teavm.backend.wasm.model.WasmType;
public interface CodeSectionListener {
default void sectionStart(int functionCount) {
}
@ -28,7 +26,7 @@ public interface CodeSectionListener {
default void localsStart(int count) {
}
default void local(int start, int count, WasmType type) {
default void local(int start, int count, WasmHollowType type) {
}
default CodeListener code() {

View File

@ -18,7 +18,6 @@ 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.WasmType;
import org.teavm.backend.wasm.model.expression.WasmFloatBinaryOperation;
import org.teavm.backend.wasm.model.expression.WasmFloatType;
import org.teavm.backend.wasm.model.expression.WasmFloatUnaryOperation;
@ -27,6 +26,7 @@ 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 {
private AddressListener addressListener;
@ -125,6 +125,11 @@ public class CodeSectionParser {
return parseBlock(true);
case 0x04:
return parseConditional();
case 0x06:
return parseTryCatch();
case 0x8:
codeListener.throwInstruction(readLEB());
break;
case 0x0C:
parseBranch(BranchOpcode.BR);
break;
@ -143,6 +148,9 @@ public class CodeSectionParser {
case 0x11:
codeListener.indirectCall(readLEB(), readLEB());
break;
case 0x14:
codeListener.callReference(readLEB());
break;
case 0x1A:
codeListener.opcode(Opcode.DROP);
@ -155,6 +163,13 @@ public class CodeSectionParser {
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;
@ -617,6 +632,20 @@ public class CodeSectionParser {
codeListener.convert(WasmNumType.INT64, WasmNumType.FLOAT64, false, true);
break;
case 0xD0:
codeListener.nullConstant(readHeapType());
break;
case 0xD2:
codeListener.functionReference(readLEB());
break;
case 0xD3:
codeListener.opcode(Opcode.REF_EQ);
break;
case 0xFB:
return parseExtExpr2();
case 0xFC:
return parseExtExpr();
@ -648,6 +677,61 @@ public class CodeSectionParser {
}
}
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 23:
codeListener.cast(readHeapType());
return true;
default:
return false;
}
}
private boolean parseBlock(boolean isLoop) {
var type = readType();
var token = codeListener.startBlock(isLoop, type);
@ -693,6 +777,35 @@ public class CodeSectionParser {
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);
@ -713,19 +826,50 @@ public class CodeSectionParser {
codeListener.tableBranch(depths, targets, defaultDepth, defaultTarget);
}
private WasmType readType() {
private WasmHollowType readType() {
var typeId = data[ptr++];
switch (typeId) {
case 0x7F:
return WasmType.INT32;
return WasmHollowType.INT32;
case 0x7E:
return WasmType.INT64;
return WasmHollowType.INT64;
case 0x7D:
return WasmType.FLOAT32;
return WasmHollowType.FLOAT32;
case 0x7C:
return WasmType.FLOAT64;
default:
return WasmHollowType.FLOAT64;
case 0x63:
return readHeapType();
case 0x40:
return null;
default:
return readAbsHeapType(typeId);
}
}
private WasmHollowType.Reference readHeapType() {
var typeId = data[ptr];
if ((typeId & 0xC0) == 0x40) {
var result = readAbsHeapType(typeId);
++ptr;
return result;
}
return new WasmHollowType.CompositeReference(readLEB());
}
private WasmHollowType.SpecialReference readAbsHeapType(int typeId) {
switch (typeId) {
case 0x70:
return WasmHollowType.Reference.FUNC;
case 0x6F:
return WasmHollowType.Reference.EXTERN;
case 0x6E:
return WasmHollowType.Reference.ANY;
case 0x6B:
return WasmHollowType.Reference.STRUCT;
case 0x6A:
return WasmHollowType.Reference.ARRAY;
default:
throw new ParseException("Unknown type", ptr);
}
}

View File

@ -19,5 +19,7 @@ public enum Opcode {
UNREACHABLE,
NOP,
RETURN,
DROP
DROP,
REF_EQ,
ARRAY_LENGTH
}

View File

@ -0,0 +1,98 @@
/*
* Copyright 2024 konsoletyper.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.backend.wasm.parser;
import java.util.Objects;
import org.teavm.backend.wasm.model.WasmNumType;
import org.teavm.backend.wasm.model.WasmStorageType;
import org.teavm.backend.wasm.model.WasmType;
public class WasmHollowType {
public static final Number INT32 = new Number(WasmNumType.INT32);
public static final Number INT64 = new Number(WasmNumType.INT64);
public static final Number FLOAT32 = new Number(WasmNumType.FLOAT32);
public static final Number FLOAT64 = new Number(WasmNumType.FLOAT64);
private WasmStorageType.Regular storageType;
private WasmHollowType() {
}
public static Number num(WasmNumType number) {
switch (number) {
case INT32:
return INT32;
case INT64:
return INT64;
case FLOAT32:
return FLOAT32;
case FLOAT64:
return FLOAT64;
default:
throw new IllegalArgumentException();
}
}
public static final class Number extends WasmHollowType {
public final WasmNumType number;
private Number(WasmNumType number) {
this.number = number;
}
}
public static abstract class Reference extends WasmHollowType {
public static final SpecialReference FUNC = new SpecialReference(WasmType.SpecialReferenceKind.FUNC);
public static final SpecialReference ANY = new SpecialReference(WasmType.SpecialReferenceKind.ANY);
public static final SpecialReference EXTERN = new SpecialReference(WasmType.SpecialReferenceKind.EXTERN);
public static final SpecialReference STRUCT = new SpecialReference(WasmType.SpecialReferenceKind.STRUCT);
public static final SpecialReference ARRAY = new SpecialReference(WasmType.SpecialReferenceKind.ARRAY);
}
public static final class CompositeReference extends WasmHollowType.Reference {
public final int index;
public CompositeReference(int index) {
this.index = index;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof CompositeReference)) {
return false;
}
CompositeReference that = (CompositeReference) o;
return index == that.index;
}
@Override
public int hashCode() {
return Objects.hashCode(index);
}
}
public static final class SpecialReference extends WasmHollowType.Reference {
public final WasmType.SpecialReferenceKind kind;
SpecialReference(WasmType.SpecialReferenceKind kind) {
this.kind = kind;
}
}
}