From 5ebcc5a68f7bdbe410a606cf725056bd0cf7dc4c Mon Sep 17 00:00:00 2001 From: andreatp Date: Tue, 2 Jun 2026 12:55:57 +0100 Subject: [PATCH 1/4] feat: store Wasm GC references as Java Objects in the interpreter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace GcRefStore integer-ID indirection with direct Java Object references in the interpreter path. Java's GC now handles liveness of Wasm GC structs, arrays, and i31 values naturally. Core design: MStack gains a lazy Object[] refs array (null until first pushRef). push()/pop() are unchanged — zero overhead for non-GC workloads. GC refs use pushRef()/popRef() to store actual WasmStruct/WasmArray/WasmI31Ref objects. Key changes: - MStack: lazy Object[] with pushRef/popRef/peekRef/clearRefsTo - StackFrame: Object[] localRefs parallel to long[] locals - WasmStruct/WasmArray: dual long[]+Object[] for fields/elements - GlobalInstance: Object refValue for ref-typed globals - TableInstance: Object[] objRefs for GC-typed tables - ValType.isGcReference(): distinguishes any-hierarchy from func/extern - StorageType.isReference()/isGcReference(): field type helpers - Instance.heapTypeMatchRef(Object,...): type matching on Objects - ConstantEvaluators: ConstantResult carries both long[] and Object - InterpreterMachine: ~30 GC instructions updated - Machine.call(int,long[],Object[]): overload for ref args - WasmI31Ref: equals/hashCode for ref.eq value equality GcRefStore is NOT yet removed — the compiler still uses it (Phase 2). Compiler GC tests are expected to fail until Phase 2. --- .../endive/runtime/ConstantEvaluators.java | 183 ++++-- .../run/endive/runtime/GlobalInstance.java | 9 + .../java/run/endive/runtime/Instance.java | 41 +- .../endive/runtime/InterpreterMachine.java | 618 +++++++++++++----- .../main/java/run/endive/runtime/MStack.java | 46 +- .../main/java/run/endive/runtime/Machine.java | 4 + .../java/run/endive/runtime/OpcodeImpl.java | 42 +- .../java/run/endive/runtime/StackFrame.java | 51 +- .../run/endive/runtime/TableInstance.java | 53 ++ .../java/run/endive/runtime/WasmArray.java | 21 +- .../java/run/endive/runtime/WasmI31Ref.java | 23 +- .../java/run/endive/runtime/WasmStruct.java | 18 +- .../internal/CompilerInterpreterMachine.java | 3 +- .../run/endive/wasm/types/StorageType.java | 8 + .../java/run/endive/wasm/types/ValType.java | 46 ++ 15 files changed, 902 insertions(+), 264 deletions(-) diff --git a/runtime/src/main/java/run/endive/runtime/ConstantEvaluators.java b/runtime/src/main/java/run/endive/runtime/ConstantEvaluators.java index 2d0a4368..21c332a4 100644 --- a/runtime/src/main/java/run/endive/runtime/ConstantEvaluators.java +++ b/runtime/src/main/java/run/endive/runtime/ConstantEvaluators.java @@ -14,60 +14,89 @@ public final class ConstantEvaluators { private ConstantEvaluators() {} + public static final class ConstantResult { + private final long[] longs; + private final Object ref; + + ConstantResult(long[] longs, Object ref) { + this.longs = longs; + this.ref = ref; + } + + public long[] longs() { + return longs; + } + + public Object ref() { + return ref; + } + + public long longValue() { + return longs[0]; + } + } + public static long[] computeConstantValue(Instance instance, Instruction[] expr) { - return computeConstantValue(instance, Arrays.asList(expr)); + return computeConstant(instance, Arrays.asList(expr)).longs(); } public static long[] computeConstantValue(Instance instance, List expr) { - var stack = new ArrayDeque(); + return computeConstant(instance, expr).longs(); + } + + public static ConstantResult computeConstant(Instance instance, List expr) { + var stack = new ArrayDeque(); for (var instruction : expr) { switch (instruction.opcode()) { case I32_ADD: { - var x = (int) stack.pop()[0]; - var y = (int) stack.pop()[0]; - stack.push(new long[] {x + y}); + var x = (int) stack.pop().longValue(); + var y = (int) stack.pop().longValue(); + stack.push(longResult(x + y)); break; } case I32_SUB: { - var x = (int) stack.pop()[0]; - var y = (int) stack.pop()[0]; - stack.push(new long[] {y - x}); + var x = (int) stack.pop().longValue(); + var y = (int) stack.pop().longValue(); + stack.push(longResult(y - x)); break; } case I32_MUL: { - var x = (int) stack.pop()[0]; - var y = (int) stack.pop()[0]; + var x = (int) stack.pop().longValue(); + var y = (int) stack.pop().longValue(); int res = x * y; - stack.push(new long[] {res}); + stack.push(longResult(res)); break; } case I64_ADD: { - var x = stack.pop()[0]; - var y = stack.pop()[0]; - stack.push(new long[] {x + y}); + var x = stack.pop().longValue(); + var y = stack.pop().longValue(); + stack.push(longResult(x + y)); break; } case I64_SUB: { - var x = stack.pop()[0]; - var y = stack.pop()[0]; - stack.push(new long[] {y - x}); + var x = stack.pop().longValue(); + var y = stack.pop().longValue(); + stack.push(longResult(y - x)); break; } case I64_MUL: { - var x = stack.pop()[0]; - var y = stack.pop()[0]; - stack.push(new long[] {x * y}); + var x = stack.pop().longValue(); + var y = stack.pop().longValue(); + stack.push(longResult(x * y)); break; } case V128_CONST: { - stack.push(new long[] {instruction.operand(0), instruction.operand(1)}); + stack.push( + new ConstantResult( + new long[] {instruction.operand(0), instruction.operand(1)}, + null)); break; } case F32_CONST: @@ -76,12 +105,12 @@ public static long[] computeConstantValue(Instance instance, List e case I64_CONST: case REF_FUNC: { - stack.push(new long[] {instruction.operand(0)}); + stack.push(longResult(instruction.operand(0))); break; } case REF_NULL: { - stack.push(new long[] {Value.REF_NULL_VALUE}); + stack.push(new ConstantResult(new long[] {Value.REF_NULL_VALUE}, null)); break; } case GLOBAL_GET: @@ -92,16 +121,26 @@ public static long[] computeConstantValue(Instance instance, List e throw new InvalidException("unknown global"); } if (global.getType().equals(ValType.V128)) { - stack.push(new long[] {global.getValueLow(), global.getValueHigh()}); + stack.push( + new ConstantResult( + new long[] { + global.getValueLow(), global.getValueHigh() + }, + null)); + } else if (global.getType().isReference()) { + stack.push( + new ConstantResult( + new long[] {global.getValueLow()}, + global.getRefValue())); } else { - stack.push(new long[] {global.getValueLow()}); + stack.push(longResult(global.getValueLow())); } break; } case REF_I31: { - var val = (int) stack.pop()[0]; - stack.push(new long[] {Value.encodeI31(val)}); + var val = (int) stack.pop().longValue(); + stack.push(new ConstantResult(new long[] {0}, new WasmI31Ref(val))); break; } case STRUCT_NEW: @@ -115,12 +154,18 @@ public static long[] computeConstantValue(Instance instance, List e .structType(); var fieldCount = structType.fieldTypes().length; var fields = new long[fieldCount]; + var fieldRefs = new Object[fieldCount]; for (int i = fieldCount - 1; i >= 0; i--) { - fields[i] = stack.pop()[0]; + var entry = stack.pop(); + var ft = structType.fieldTypes()[i]; + if (ft.storageType().isReference()) { + fieldRefs[i] = entry.ref(); + } else { + fields[i] = entry.longValue(); + } } - var struct = new WasmStruct(typeIdx, fields); - var refId = instance.registerGcRef(struct); - stack.push(new long[] {refId}); + var struct = new WasmStruct(typeIdx, fields, fieldRefs); + stack.push(new ConstantResult(new long[] {0}, struct)); break; } case STRUCT_NEW_DEFAULT: @@ -134,34 +179,16 @@ public static long[] computeConstantValue(Instance instance, List e .structType(); var fieldCount = structType.fieldTypes().length; var fields = new long[fieldCount]; - for (int i = 0; i < fieldCount; i++) { - var ft = structType.fieldTypes()[i]; - if (ft.storageType().valType() != null - && ft.storageType().valType().isReference()) { - fields[i] = Value.REF_NULL_VALUE; - } - } - var struct = new WasmStruct(typeIdx, fields); - var refId = instance.registerGcRef(struct); - stack.push(new long[] {refId}); + var fieldRefs = new Object[fieldCount]; + var struct = new WasmStruct(typeIdx, fields, fieldRefs); + stack.push(new ConstantResult(new long[] {0}, struct)); break; } case ARRAY_NEW: { var typeIdx = (int) instruction.operand(0); - var len = (int) stack.pop()[0]; - var fillVal = stack.pop()[0]; - var elements = new long[len]; - Arrays.fill(elements, fillVal); - var array = new WasmArray(typeIdx, elements); - var refId = instance.registerGcRef(array); - stack.push(new long[] {refId}); - break; - } - case ARRAY_NEW_DEFAULT: - { - var typeIdx = (int) instruction.operand(0); - var len = (int) stack.pop()[0]; + var len = (int) stack.pop().longValue(); + var fillEntry = stack.pop(); var at = instance.module() .typeSection() @@ -169,32 +196,54 @@ public static long[] computeConstantValue(Instance instance, List e .compType() .arrayType(); var elements = new long[len]; - if (at.fieldType().storageType().valType() != null - && at.fieldType().storageType().valType().isReference()) { - Arrays.fill(elements, Value.REF_NULL_VALUE); + var elementRefs = new Object[len]; + if (at.fieldType().storageType().isReference()) { + Arrays.fill(elementRefs, fillEntry.ref()); + } else { + Arrays.fill(elements, fillEntry.longValue()); } - var array = new WasmArray(typeIdx, elements); - var refId = instance.registerGcRef(array); - stack.push(new long[] {refId}); + var array = new WasmArray(typeIdx, elements, elementRefs); + stack.push(new ConstantResult(new long[] {0}, array)); + break; + } + case ARRAY_NEW_DEFAULT: + { + var typeIdx = (int) instruction.operand(0); + var len = (int) stack.pop().longValue(); + var elements = new long[len]; + var elementRefs = new Object[len]; + var array = new WasmArray(typeIdx, elements, elementRefs); + stack.push(new ConstantResult(new long[] {0}, array)); break; } case ARRAY_NEW_FIXED: { var typeIdx = (int) instruction.operand(0); var len = (int) instruction.operand(1); + var at = + instance.module() + .typeSection() + .getSubType(typeIdx) + .compType() + .arrayType(); var elements = new long[len]; + var elementRefs = new Object[len]; + boolean isRef = at.fieldType().storageType().isReference(); for (int i = len - 1; i >= 0; i--) { - elements[i] = stack.pop()[0]; + var entry = stack.pop(); + if (isRef) { + elementRefs[i] = entry.ref(); + } else { + elements[i] = entry.longValue(); + } } - var array = new WasmArray(typeIdx, elements); - var refId = instance.registerGcRef(array); - stack.push(new long[] {refId}); + var array = new WasmArray(typeIdx, elements, elementRefs); + stack.push(new ConstantResult(new long[] {0}, array)); break; } case ANY_CONVERT_EXTERN: case EXTERN_CONVERT_ANY: { - // Identity operations at runtime break; } case END: @@ -210,6 +259,10 @@ public static long[] computeConstantValue(Instance instance, List e return stack.pop(); } + private static ConstantResult longResult(long value) { + return new ConstantResult(new long[] {value}, null); + } + public static Instance computeConstantInstance(Instance instance, List expr) { for (Instruction instruction : expr) { if (instruction.opcode() == GLOBAL_GET) { diff --git a/runtime/src/main/java/run/endive/runtime/GlobalInstance.java b/runtime/src/main/java/run/endive/runtime/GlobalInstance.java index 48a93af9..8dc04544 100644 --- a/runtime/src/main/java/run/endive/runtime/GlobalInstance.java +++ b/runtime/src/main/java/run/endive/runtime/GlobalInstance.java @@ -8,6 +8,7 @@ public class GlobalInstance { private long valueLow; private long valueHigh; + private Object refValue; private final ValType valType; private Instance instance; private final MutabilityType mutabilityType; @@ -88,4 +89,12 @@ public void setInstance(Instance instance) { public MutabilityType getMutabilityType() { return mutabilityType; } + + public Object getRefValue() { + return refValue; + } + + public void setRefValue(Object ref) { + this.refValue = ref; + } } diff --git a/runtime/src/main/java/run/endive/runtime/Instance.java b/runtime/src/main/java/run/endive/runtime/Instance.java index 2d4a7008..b8d1086f 100644 --- a/runtime/src/main/java/run/endive/runtime/Instance.java +++ b/runtime/src/main/java/run/endive/runtime/Instance.java @@ -2,6 +2,7 @@ import static java.util.Objects.requireNonNullElse; import static java.util.Objects.requireNonNullElseGet; +import static run.endive.runtime.ConstantEvaluators.computeConstant; import static run.endive.runtime.ConstantEvaluators.computeConstantInstance; import static run.endive.runtime.ConstantEvaluators.computeConstantValue; import static run.endive.wasm.types.ExternalType.FUNCTION; @@ -133,7 +134,7 @@ static final class TailCallPending { for (int i = 0; i < tables.length; i++) { long rawValue = computeConstantValue(this, tables[i].initialize())[0]; - var initValue = OpcodeImpl.boxForTable(rawValue, this); + int initValue = (int) rawValue; if (tableFactory != null) { this.tables[i] = tableFactory.create(tables[i], initValue); } else { @@ -151,7 +152,8 @@ public Instance initialize(boolean start) { // because segment offsets can reference local globals via global.get. for (var i = 0; i < globalInitializers.length; i++) { var g = globalInitializers[i]; - var values = computeConstantValue(this, g.initInstructions()); + var result = computeConstant(this, g.initInstructions()); + var values = result.longs(); if (globalFactory != null) { globals[i] = globalFactory.create( @@ -168,6 +170,9 @@ public Instance initialize(boolean start) { g.mutabilityType()); } globals[i].setInstance(this); + if (g.valueType().isReference()) { + globals[i].setRefValue(result.ref()); + } } for (var el : elements) { @@ -181,14 +186,22 @@ public Instance initialize(boolean start) { || (offset + initializers.size() - 1) >= table.size()) { throw new UninstantiableException("out of bounds table access"); } + boolean isGcTable = + !table.elementType().equals(ValType.FuncRef) + && !table.elementType().equals(ValType.ExternRef); for (int i = 0; i < initializers.size(); i++) { final List init = initializers.get(i); int index = offset + i; - var value = computeConstantValue(this, init); var inst = computeConstantInstance(this, init); assert ae.type().isReference(); - table.setRef(index, OpcodeImpl.boxForTable(value[0], this), inst); + if (isGcTable) { + var result = computeConstant(this, init); + table.setObjRef(index, result.ref(), inst); + } else { + var value = computeConstantValue(this, init); + table.setRef(index, (int) value[0], inst); + } } } } @@ -479,6 +492,26 @@ public boolean heapTypeMatch( return targetHeapType == ValType.TypeIdxCode.ANY.code(); } + public boolean heapTypeMatchRef( + Object ref, boolean nullable, int targetHeapType, int sourceHeapType) { + if (ref == null) { + return nullable; + } + if (targetHeapType == ValType.TypeIdxCode.NONE.code() + || targetHeapType == ValType.TypeIdxCode.NOFUNC.code() + || targetHeapType == ValType.TypeIdxCode.NOEXTERN.code()) { + return false; + } + if (targetHeapType == ValType.TypeIdxCode.FUNC.code() + || targetHeapType == ValType.TypeIdxCode.EXTERN.code()) { + return true; + } + if (ref instanceof WasmGcRef) { + return heapTypeSubOf(((WasmGcRef) ref).typeIdx(), targetHeapType); + } + return targetHeapType == ValType.TypeIdxCode.ANY.code(); + } + private boolean heapTypeSubOf(int actual, int target) { if (actual == target) { return true; diff --git a/runtime/src/main/java/run/endive/runtime/InterpreterMachine.java b/runtime/src/main/java/run/endive/runtime/InterpreterMachine.java index 29c7f7c5..ca902e75 100644 --- a/runtime/src/main/java/run/endive/runtime/InterpreterMachine.java +++ b/runtime/src/main/java/run/endive/runtime/InterpreterMachine.java @@ -62,7 +62,7 @@ protected void evalDefault( @Override public long[] call(int funcId, long[] args) throws WasmEngineException { - return call(stack, instance, callStack, funcId, args, null, true); + return call(stack, instance, callStack, funcId, args, null, null, true); } protected long[] call( @@ -71,6 +71,7 @@ protected long[] call( Deque callStack, int funcId, long[] args, + Object[] refArgs, FunctionType callType, boolean popResults) throws WasmEngineException { @@ -90,6 +91,7 @@ protected long[] call( instance, funcId, args, + refArgs, type.params(), func.localTypes(), func.instructions()); @@ -832,7 +834,7 @@ protected void eval(MStack stack, Instance instance, Deque callStack stack.push(operands.get(0)); break; case REF_NULL: - REF_NULL(stack); + REF_NULL(stack, operands); break; case REF_IS_NULL: REF_IS_NULL(stack); @@ -1044,7 +1046,7 @@ protected void eval(MStack stack, Instance instance, Deque callStack ARRAY_NEW(stack, instance, operands); break; case ARRAY_NEW_DEFAULT: - ARRAY_NEW_DEFAULT(stack, instance, operands); + ARRAY_NEW_DEFAULT(stack, operands); break; case ARRAY_NEW_FIXED: ARRAY_NEW_FIXED(stack, instance, operands); @@ -1064,13 +1066,13 @@ protected void eval(MStack stack, Instance instance, Deque callStack ARRAY_SET(stack, instance, operands); break; case ARRAY_LEN: - ARRAY_LEN(stack, instance); + ARRAY_LEN(stack); break; case ARRAY_FILL: ARRAY_FILL(stack, instance, operands); break; case ARRAY_COPY: - ARRAY_COPY(stack, instance); + ARRAY_COPY(stack, instance, operands); break; case ARRAY_INIT_DATA: ARRAY_INIT_DATA(stack, instance, operands); @@ -1093,9 +1095,10 @@ protected void eval(MStack stack, Instance instance, Deque callStack BR_ON_CAST_FAIL(stack, instance, frame, instruction, operands); break; case ANY_CONVERT_EXTERN: + ANY_CONVERT_EXTERN(stack); + break; case EXTERN_CONVERT_ANY: - // Identity operation at runtime: the value representation is the same - // for externref and anyref. No wrapping needed. + EXTERN_CONVERT_ANY(stack, instance); break; default: { @@ -1774,8 +1777,17 @@ private static void F32_CONVERT_I64_S(MStack stack) { stack.push(Value.floatToLong(OpcodeImpl.F32_CONVERT_I64_S(tos))); } - private static void REF_NULL(MStack stack) { - stack.push(REF_NULL_VALUE); + private static void REF_NULL(MStack stack, Operands operands) { + var heapType = (int) operands.get(0); + if (heapType == ValType.TypeIdxCode.FUNC.code() + || heapType == ValType.TypeIdxCode.NOFUNC.code() + || heapType == ValType.TypeIdxCode.EXTERN.code() + || heapType == ValType.TypeIdxCode.NOEXTERN.code() + || heapType == ValType.TypeIdxCode.EXN.code()) { + stack.push(REF_NULL_VALUE); + } else { + stack.pushRef(null); + } } private static void ELEM_DROP(Instance instance, Operands operands) { @@ -1809,12 +1821,19 @@ private static void F64_CONVERT_I64_S(MStack stack) { private static void TABLE_GROW(MStack stack, Instance instance, Operands operands) { var tableidx = (int) operands.get(0); var table = instance.table(tableidx); + var et = table.elementType(); + boolean isGcTable = !et.equals(ValType.FuncRef) && !et.equals(ValType.ExternRef); var size = (int) stack.pop(); - var val = OpcodeImpl.boxForTable(stack.pop(), instance); - - var res = table.grow(size, val, instance); - stack.push(res); + if (isGcTable) { + var refVal = stack.popRef(); + var res = table.growWithRef(size, refVal, instance); + stack.push(res); + } else { + var val = (int) stack.pop(); + var res = table.grow(size, val, instance); + stack.push(res); + } } private static void TABLE_SIZE(MStack stack, Instance instance, Operands operands) { @@ -1826,12 +1845,25 @@ private static void TABLE_SIZE(MStack stack, Instance instance, Operands operand private static void TABLE_FILL(MStack stack, Instance instance, Operands operands) { var tableidx = (int) operands.get(0); + var table = instance.table(tableidx); + var et = table.elementType(); + boolean isGcTable = !et.equals(ValType.FuncRef) && !et.equals(ValType.ExternRef); var size = (int) stack.pop(); - var val = OpcodeImpl.boxForTable(stack.pop(), instance); - var offset = (int) stack.pop(); - - OpcodeImpl.TABLE_FILL(instance, tableidx, size, val, offset); + if (isGcTable) { + var refVal = stack.popRef(); + var offset = (int) stack.pop(); + if (offset + size > table.size()) { + throw new TrapException("out of bounds table access"); + } + for (int i = 0; i < size; i++) { + table.setObjRef(offset + i, refVal, instance); + } + } else { + var val = (int) stack.pop(); + var offset = (int) stack.pop(); + OpcodeImpl.TABLE_FILL(instance, tableidx, size, val, offset); + } } private static void TABLE_COPY(MStack stack, Instance instance, Operands operands) { @@ -1975,8 +2007,16 @@ protected void CALL(Operands operands) { var type = instance.type(typeId); // given a list of param types, let's pop those params off the stack // and pass as args to the function call - var args = extractArgsForParams(stack, type.params()); - call(stack, instance, callStack, funcId, args, type, false); + var extracted = extractArgsAndRefsForParams(stack, type.params()); + call( + stack, + instance, + callStack, + funcId, + (long[]) extracted[0], + (Object[]) extracted[1], + type, + false); } private void CALL_REF() { @@ -1988,8 +2028,16 @@ private void CALL_REF() { var type = instance.type(typeId); // given a list of param types, let's pop those params off the stack // and pass as args to the function call - var args = extractArgsForParams(stack, type.params()); - call(stack, instance, callStack, funcId, args, type, false); + var extracted = extractArgsAndRefsForParams(stack, type.params()); + call( + stack, + instance, + callStack, + funcId, + (long[]) extracted[0], + (Object[]) extracted[1], + type, + false); } private static void F64_NEG(MStack stack) { @@ -2143,47 +2191,72 @@ private static void I32_LOAD(MStack stack, Instance instance, Operands operands) private static void TABLE_SET(MStack stack, Instance instance, Operands operands) { var idx = (int) operands.get(0); var table = instance.table(idx); - - var value = OpcodeImpl.boxForTable(stack.pop(), instance); - var i = (int) stack.pop(); - table.setRef(i, value, instance); + var et = table.elementType(); + boolean isGcTable = !et.equals(ValType.FuncRef) && !et.equals(ValType.ExternRef); + if (isGcTable) { + var refVal = stack.popRef(); + var i = (int) stack.pop(); + table.setObjRef(i, refVal, instance); + } else { + var value = (int) stack.pop(); + var i = (int) stack.pop(); + table.setRef(i, value, instance); + } } private static void TABLE_GET(MStack stack, Instance instance, Operands operands) { var idx = (int) operands.get(0); var table = instance.table(idx); + var et = table.elementType(); + boolean isGcTable = !et.equals(ValType.FuncRef) && !et.equals(ValType.ExternRef); var i = (int) stack.pop(); - var ref = OpcodeImpl.TABLE_GET(instance, idx, i); - stack.push(OpcodeImpl.unboxFromTable(ref, instance, table.elementType())); + if (isGcTable) { + stack.pushRef(table.objRef(i)); + } else { + var ref = OpcodeImpl.TABLE_GET(instance, idx, i); + stack.push(ref); + } } private static void GLOBAL_SET(MStack stack, Instance instance, Operands operands) { var id = (int) operands.get(0); - if (!instance.global(id).getType().equals(ValType.V128)) { - var val = stack.pop(); - instance.global(id).setValue(val); - } else { + var globalType = instance.global(id).getType(); + if (globalType.isGcReference()) { + instance.global(id).setRefValue(stack.popRef()); + } else if (globalType.equals(ValType.V128)) { var high = stack.pop(); var low = stack.pop(); instance.global(id).setValueLow(low); instance.global(id).setValueHigh(high); + } else { + var val = stack.pop(); + instance.global(id).setValue(val); } } private static void GLOBAL_GET(MStack stack, Instance instance, Operands operands) { int idx = (int) operands.get(0); - - stack.push(instance.global(idx).getValueLow()); - if (instance.global(idx).getType().equals(ValType.V128)) { - stack.push(instance.global(idx).getValueHigh()); + var globalType = instance.global(idx).getType(); + if (globalType.isGcReference()) { + stack.pushRef(instance.global(idx).getRefValue()); + } else { + stack.push(instance.global(idx).getValueLow()); + if (globalType.equals(ValType.V128)) { + stack.push(instance.global(idx).getValueHigh()); + } } } private static void DROP(MStack stack, Operands operands) { - if (operands.get(0) == ValType.ID.V128) { + var typeId = operands.get(0); + if (typeId == ValType.ID.V128) { + stack.pop(); + stack.pop(); + } else if (ValType.builder().fromId(typeId).isGcReference()) { + stack.popRef(); + } else { stack.pop(); } - stack.pop(); } private static void SELECT(MStack stack, Operands operands) { @@ -2227,6 +2300,14 @@ private static void SELECT_T(MStack stack, Operands operands) { stack.push(a2); stack.push(a1); } + } else if (ValType.builder().fromId(typeId).isGcReference()) { + var b = stack.popRef(); + var a = stack.popRef(); + if (pred == 0) { + stack.pushRef(b); + } else { + stack.pushRef(a); + } } else { var b = stack.pop(); var a = stack.pop(); @@ -2241,7 +2322,10 @@ private static void SELECT_T(MStack stack, Operands operands) { private static void LOCAL_GET(MStack stack, Operands operands, StackFrame currentStackFrame) { var idx = (int) operands.get(0); var i = currentStackFrame.localIndexOf(idx); - if (currentStackFrame.localType(idx).equals(ValType.V128)) { + var localType = currentStackFrame.localType(idx); + if (localType.isGcReference()) { + stack.pushRef(currentStackFrame.localRef(i)); + } else if (localType.equals(ValType.V128)) { stack.push(currentStackFrame.local(i)); stack.push(currentStackFrame.local(i + 1)); } else { @@ -2252,7 +2336,10 @@ private static void LOCAL_GET(MStack stack, Operands operands, StackFrame curren private static void LOCAL_SET(MStack stack, Operands operands, StackFrame currentStackFrame) { var idx = (int) operands.get(0); var i = currentStackFrame.localIndexOf(idx); - if (currentStackFrame.localType(idx).equals(ValType.V128)) { + var localType = currentStackFrame.localType(idx); + if (localType.isGcReference()) { + currentStackFrame.setLocalRef(i, stack.popRef()); + } else if (localType.equals(ValType.V128)) { currentStackFrame.setLocal(i, stack.pop()); currentStackFrame.setLocal(i + 1, stack.pop()); } else { @@ -2264,7 +2351,10 @@ private static void LOCAL_TEE(MStack stack, Operands operands, StackFrame curren // here we peek instead of pop, leaving it on the stack var idx = (int) operands.get(0); var i = currentStackFrame.localIndexOf(idx); - if (currentStackFrame.localType(idx).equals(ValType.V128)) { + var localType = currentStackFrame.localType(idx); + if (localType.isGcReference()) { + currentStackFrame.setLocalRef(i, stack.peekRef()); + } else if (localType.equals(ValType.V128)) { var tmp = stack.pop(); currentStackFrame.setLocal(i, tmp); currentStackFrame.setLocal(i + 1, stack.peek()); @@ -2643,13 +2733,15 @@ private static StackFrame RETURN_CALL( var typeId = instance.functionType(funcId); var type = instance.type(typeId); var func = instance.function(funcId); - var args = extractArgsForParams(stack, type.params()); + var extracted = extractArgsAndRefsForParams(stack, type.params()); + var args = (long[]) extracted[0]; + var refArgs = (Object[]) extracted[1]; // optimizing when the tail call happens in the same function if (currentStackFrame.funcId() == funcId) { var ctrlFrame = currentStackFrame.popCtrlTillCall(); StackFrame.doControlTransfer(ctrlFrame, stack); - currentStackFrame.reset(args); + currentStackFrame.reset(args, refArgs); currentStackFrame.pushCtrl(ctrlFrame); return currentStackFrame; } else { @@ -2666,6 +2758,7 @@ private static StackFrame RETURN_CALL( instance, funcId, args, + refArgs, type.params(), func.localTypes(), func.instructions()); @@ -2728,13 +2821,15 @@ private static StackFrame RETURN_CALL_INDIRECT( + refMachine.getName()); } - var args = extractArgsForParams(stack, type.params()); + var extracted = extractArgsAndRefsForParams(stack, type.params()); + var args = (long[]) extracted[0]; + var refArgs = (Object[]) extracted[1]; // optimizing when the tail call happens in the same function if (currentStackFrame.funcId() == funcId) { var ctrlFrame = currentStackFrame.popCtrlTillCall(); StackFrame.doControlTransfer(ctrlFrame, stack); - currentStackFrame.reset(args); + currentStackFrame.reset(args, refArgs); currentStackFrame.pushCtrl(ctrlFrame); return currentStackFrame; } else { @@ -2752,6 +2847,7 @@ private static StackFrame RETURN_CALL_INDIRECT( instance, funcId, args, + refArgs, type.params(), func.localTypes(), func.instructions()); @@ -2801,13 +2897,15 @@ private static StackFrame RETURN_CALL_REF( var func = instance.function(funcId); // given a list of param types, let's pop those params off the stack // and pass as args to the function call - var args = extractArgsForParams(stack, type.params()); + var extracted = extractArgsAndRefsForParams(stack, type.params()); + var args = (long[]) extracted[0]; + var refArgs = (Object[]) extracted[1]; // optimizing when the tail call happens in the same function if (currentStackFrame.funcId() == funcId) { var ctrlFrame = currentStackFrame.popCtrlTillCall(); StackFrame.doControlTransfer(ctrlFrame, stack); - currentStackFrame.reset(args); + currentStackFrame.reset(args, refArgs); currentStackFrame.pushCtrl(ctrlFrame); return currentStackFrame; } else { @@ -2818,6 +2916,7 @@ private static StackFrame RETURN_CALL_REF( instance, funcId, args, + refArgs, type.params(), func.localTypes(), func.instructions()); @@ -2845,9 +2944,11 @@ private void CALL_INDIRECT( // given a list of param types, let's pop those params off the stack // and pass as args to the function call - var args = extractArgsForParams(stack, type.params()); + var extracted = extractArgsAndRefsForParams(stack, type.params()); + var args = (long[]) extracted[0]; + var refArgs = (Object[]) extracted[1]; if (useCurrentInstanceInterpreter(instance, refInstance, funcId)) { - call(stack, instance, callStack, funcId, args, null, false); + call(stack, instance, callStack, funcId, args, refArgs, null, false); } else { checkInterruption(); var results = refInstance.getMachine().call(funcId, args); @@ -3090,6 +3191,44 @@ protected static long[] extractArgsForParams(MStack stack, List params) return args; } + /** + * Extract both long args and Object ref args from the stack for a call. + * Ref-typed params are popped from the ref stack; others from the long stack. + * Returns a 2-element array: [0] = long[] args, [1] = Object[] refArgs (or null if no refs). + */ + protected static Object[] extractArgsAndRefsForParams(MStack stack, List params) { + if (params == null || params.isEmpty()) { + return new Object[] {Value.EMPTY_VALUES, null}; + } + var size = sizeOf(params); + var args = new long[size]; + boolean hasRefs = false; + for (var p : params) { + if (p.isGcReference()) { + hasRefs = true; + break; + } + } + Object[] refArgs = hasRefs ? new Object[size] : null; + // Pop in reverse order (last param on top of stack) + int idx = size; + for (int i = params.size() - 1; i >= 0; i--) { + var p = params.get(i); + if (p.equals(ValType.V128)) { + idx -= 2; + args[idx + 1] = stack.pop(); + args[idx] = stack.pop(); + } else if (p.isGcReference()) { + idx -= 1; + refArgs[idx] = stack.popRef(); + } else { + idx -= 1; + args[idx] = stack.pop(); + } + } + return new Object[] {args, refArgs}; + } + private static boolean functionTypeMatch( FunctionType actual, FunctionType expected, TypeSection ts) { if (actual.params().size() != expected.params().size() @@ -3150,131 +3289,186 @@ private static void checkInterruption() { // ===== GC opcode implementations ===== private static void REF_EQ(MStack stack) { - var b = stack.pop(); - var a = stack.pop(); - stack.push(a == b ? Value.TRUE : Value.FALSE); + var b = stack.popRef(); + var a = stack.popRef(); + boolean eq; + if (a == b) { + eq = true; + } else if (a == null || b == null) { + // one is null, the other is not (both-null caught by a == b) + eq = false; + } else if (a instanceof WasmI31Ref && b instanceof WasmI31Ref) { + eq = a.equals(b); + } else { + eq = false; // identity comparison for structs/arrays + } + stack.push(eq ? Value.TRUE : Value.FALSE); } private static void REF_I31(MStack stack) { var val = (int) stack.pop(); - stack.push(Value.encodeI31(val)); + stack.pushRef(new WasmI31Ref(val)); } private static void I31_GET_S(MStack stack) { - var ref = stack.pop(); - if (ref == REF_NULL_VALUE) { + var ref = stack.popRef(); + if (ref == null) { throw new TrapException("null i31 reference"); } - stack.push(Value.decodeI31S(ref)); + var i31 = (WasmI31Ref) ref; + int val = i31.value(); + // Sign-extend from 31 bits + if ((val & 0x40000000) != 0) { + val |= 0x80000000; + } + stack.push(val); } private static void I31_GET_U(MStack stack) { - var ref = stack.pop(); - if (ref == REF_NULL_VALUE) { + var ref = stack.popRef(); + if (ref == null) { throw new TrapException("null i31 reference"); } - stack.push(Value.decodeI31U(ref)); + var i31 = (WasmI31Ref) ref; + stack.push(i31.value() & 0x7FFFFFFFL); } private static void STRUCT_NEW(MStack stack, Instance instance, Operands operands) { var typeIdx = (int) operands.get(0); var st = instance.module().typeSection().getSubType(typeIdx).compType().structType(); var fields = new long[st.fieldTypes().length]; + var fieldRefs = new Object[st.fieldTypes().length]; // Pop fields in reverse order (last field on top) for (int i = fields.length - 1; i >= 0; i--) { - fields[i] = stack.pop(); + var ft = st.fieldTypes()[i]; + if (ft.storageType().isReference()) { + fieldRefs[i] = stack.popRef(); + } else { + fields[i] = stack.pop(); + } } - var struct = new WasmStruct(typeIdx, fields); - stack.push(instance.registerGcRef(struct)); + var struct = new WasmStruct(typeIdx, fields, fieldRefs); + stack.pushRef(struct); } private static void STRUCT_NEW_DEFAULT(MStack stack, Instance instance, Operands operands) { var typeIdx = (int) operands.get(0); var st = instance.module().typeSection().getSubType(typeIdx).compType().structType(); var fields = new long[st.fieldTypes().length]; - // Default values: 0 for numeric, REF_NULL_VALUE for references - for (int i = 0; i < fields.length; i++) { - var ft = st.fieldTypes()[i]; - if (ft.storageType().valType() != null && ft.storageType().valType().isReference()) { - fields[i] = REF_NULL_VALUE; - } - // numeric types default to 0 (already zero-initialized) - } + // fieldRefs is already null-initialized which is correct for ref defaults (null) var struct = new WasmStruct(typeIdx, fields); - stack.push(instance.registerGcRef(struct)); + stack.pushRef(struct); } private static void STRUCT_GET( MStack stack, Instance instance, Operands operands, OpCode opcode) { var typeIdx = (int) operands.get(0); var fieldIdx = (int) operands.get(1); - var ref = (int) stack.pop(); - if (ref == REF_NULL_VALUE) { + var structObj = stack.popRef(); + if (structObj == null) { throw new TrapException("null structure reference"); } - var struct = (WasmStruct) instance.gcRef(ref); - var val = struct.field(fieldIdx); + var struct = (WasmStruct) structObj; var st = instance.module().typeSection().getSubType(typeIdx).compType().structType(); var ft = st.fieldTypes()[fieldIdx]; - if (ft.storageType().packedType() != null) { - if (opcode == OpCode.STRUCT_GET_S) { - val = ft.storageType().packedType().signExtend(val); - } else { - val = val & ft.storageType().packedType().mask(); + if (ft.storageType().isReference()) { + stack.pushRef(struct.fieldRef(fieldIdx)); + } else { + var val = struct.field(fieldIdx); + if (ft.storageType().packedType() != null) { + if (opcode == OpCode.STRUCT_GET_S) { + val = ft.storageType().packedType().signExtend(val); + } else { + val = val & ft.storageType().packedType().mask(); + } } + stack.push(val); } - stack.push(val); } private static void STRUCT_SET(MStack stack, Instance instance, Operands operands) { var typeIdx = (int) operands.get(0); var fieldIdx = (int) operands.get(1); - var val = stack.pop(); - var ref = (int) stack.pop(); - if (ref == REF_NULL_VALUE) { - throw new TrapException("null structure reference"); - } - var struct = (WasmStruct) instance.gcRef(ref); var st = instance.module().typeSection().getSubType(typeIdx).compType().structType(); var ft = st.fieldTypes()[fieldIdx]; - if (ft.storageType().packedType() != null) { - val = val & ft.storageType().packedType().mask(); + boolean isRef = ft.storageType().isReference(); + Object refVal = null; + long val = 0; + if (isRef) { + refVal = stack.popRef(); + } else { + val = stack.pop(); + } + var structObj = stack.popRef(); + if (structObj == null) { + throw new TrapException("null structure reference"); + } + var struct = (WasmStruct) structObj; + if (isRef) { + struct.setFieldRef(fieldIdx, refVal); + } else { + if (ft.storageType().packedType() != null) { + val = val & ft.storageType().packedType().mask(); + } + struct.setField(fieldIdx, val); } - struct.setField(fieldIdx, val); } private static void ARRAY_NEW(MStack stack, Instance instance, Operands operands) { var typeIdx = (int) operands.get(0); + var at = instance.module().typeSection().getSubType(typeIdx).compType().arrayType(); + boolean isRef = + at.fieldType().storageType().valType() != null + && at.fieldType().storageType().isReference(); var len = (int) stack.pop(); - var initVal = stack.pop(); - var elems = new long[len]; - java.util.Arrays.fill(elems, initVal); - var arr = new WasmArray(typeIdx, elems); - stack.push(instance.registerGcRef(arr)); + if (isRef) { + var initRef = stack.popRef(); + var elems = new long[len]; + var elemRefs = new Object[len]; + java.util.Arrays.fill(elemRefs, initRef); + var arr = new WasmArray(typeIdx, elems, elemRefs); + stack.pushRef(arr); + } else { + var initVal = stack.pop(); + var elems = new long[len]; + java.util.Arrays.fill(elems, initVal); + var arr = new WasmArray(typeIdx, elems); + stack.pushRef(arr); + } } - private static void ARRAY_NEW_DEFAULT(MStack stack, Instance instance, Operands operands) { + private static void ARRAY_NEW_DEFAULT(MStack stack, Operands operands) { var typeIdx = (int) operands.get(0); var len = (int) stack.pop(); - var at = instance.module().typeSection().getSubType(typeIdx).compType().arrayType(); + // Default values: 0 for numeric, null for references + // Both long[] and Object[] are zero/null-initialized by Java var elems = new long[len]; - if (at.fieldType().storageType().valType() != null - && at.fieldType().storageType().valType().isReference()) { - java.util.Arrays.fill(elems, REF_NULL_VALUE); - } var arr = new WasmArray(typeIdx, elems); - stack.push(instance.registerGcRef(arr)); + stack.pushRef(arr); } private static void ARRAY_NEW_FIXED(MStack stack, Instance instance, Operands operands) { var typeIdx = (int) operands.get(0); var len = (int) operands.get(1); + var at = instance.module().typeSection().getSubType(typeIdx).compType().arrayType(); + boolean isRef = + at.fieldType().storageType().valType() != null + && at.fieldType().storageType().isReference(); var elems = new long[len]; - for (int i = len - 1; i >= 0; i--) { - elems[i] = stack.pop(); + if (isRef) { + var elemRefs = new Object[len]; + for (int i = len - 1; i >= 0; i--) { + elemRefs[i] = stack.popRef(); + } + var arr = new WasmArray(typeIdx, elems, elemRefs); + stack.pushRef(arr); + } else { + for (int i = len - 1; i >= 0; i--) { + elems[i] = stack.pop(); + } + var arr = new WasmArray(typeIdx, elems); + stack.pushRef(arr); } - var arr = new WasmArray(typeIdx, elems); - stack.push(instance.registerGcRef(arr)); } private static void ARRAY_NEW_DATA(MStack stack, Instance instance, Operands operands) { @@ -3294,7 +3488,7 @@ private static void ARRAY_NEW_DATA(MStack stack, Instance instance, Operands ope elems[i] = readFromData(data, byteOff, elemSize); } var arr = new WasmArray(typeIdx, elems); - stack.push(instance.registerGcRef(arr)); + stack.pushRef(arr); } private static void ARRAY_NEW_ELEM(MStack stack, Instance instance, Operands operands) { @@ -3306,112 +3500,165 @@ private static void ARRAY_NEW_ELEM(MStack stack, Instance instance, Operands ope if (element == null || offset + len > element.elementCount()) { throw new TrapException("out of bounds table access"); } + var at = instance.module().typeSection().getSubType(typeIdx).compType().arrayType(); + boolean isRef = at.fieldType().storageType().isReference(); var elems = new long[len]; + var elemRefs = new Object[len]; for (int i = 0; i < len; i++) { var init = element.initializers().get(offset + i); - elems[i] = ConstantEvaluators.computeConstantValue(instance, init)[0]; + var result = ConstantEvaluators.computeConstant(instance, init); + if (isRef) { + elemRefs[i] = result.ref(); + } else { + elems[i] = result.longValue(); + } } - var arr = new WasmArray(typeIdx, elems); - stack.push(instance.registerGcRef(arr)); + var arr = new WasmArray(typeIdx, elems, elemRefs); + stack.pushRef(arr); } private static void ARRAY_GET( MStack stack, Instance instance, Operands operands, OpCode opcode) { var typeIdx = (int) operands.get(0); var idx = (int) stack.pop(); - var ref = (int) stack.pop(); - if (ref == REF_NULL_VALUE) { + var arrObj = stack.popRef(); + if (arrObj == null) { throw new TrapException("null array reference"); } - var arr = (WasmArray) instance.gcRef(ref); + var arr = (WasmArray) arrObj; if (idx < 0 || idx >= arr.length()) { throw new TrapException("out of bounds array access"); } - var val = arr.get(idx); var at = instance.module().typeSection().getSubType(typeIdx).compType().arrayType(); - if (at.fieldType().storageType().packedType() != null) { - if (opcode == OpCode.ARRAY_GET_S) { - val = at.fieldType().storageType().packedType().signExtend(val); - } else { - val = val & at.fieldType().storageType().packedType().mask(); + if (at.fieldType().storageType().valType() != null + && at.fieldType().storageType().isReference()) { + stack.pushRef(arr.getRef(idx)); + } else { + var val = arr.get(idx); + if (at.fieldType().storageType().packedType() != null) { + if (opcode == OpCode.ARRAY_GET_S) { + val = at.fieldType().storageType().packedType().signExtend(val); + } else { + val = val & at.fieldType().storageType().packedType().mask(); + } } + stack.push(val); } - stack.push(val); } private static void ARRAY_SET(MStack stack, Instance instance, Operands operands) { var typeIdx = (int) operands.get(0); - var val = stack.pop(); + var at = instance.module().typeSection().getSubType(typeIdx).compType().arrayType(); + boolean isRef = + at.fieldType().storageType().valType() != null + && at.fieldType().storageType().isReference(); + Object refVal = null; + long val = 0; + if (isRef) { + refVal = stack.popRef(); + } else { + val = stack.pop(); + } var idx = (int) stack.pop(); - var ref = (int) stack.pop(); - if (ref == REF_NULL_VALUE) { + var arrObj = stack.popRef(); + if (arrObj == null) { throw new TrapException("null array reference"); } - var arr = (WasmArray) instance.gcRef(ref); + var arr = (WasmArray) arrObj; if (idx < 0 || idx >= arr.length()) { throw new TrapException("out of bounds array access"); } - var at = instance.module().typeSection().getSubType(typeIdx).compType().arrayType(); - if (at.fieldType().storageType().packedType() != null) { - val = val & at.fieldType().storageType().packedType().mask(); + if (isRef) { + arr.setRef(idx, refVal); + } else { + if (at.fieldType().storageType().packedType() != null) { + val = val & at.fieldType().storageType().packedType().mask(); + } + arr.set(idx, val); } - arr.set(idx, val); } - private static void ARRAY_LEN(MStack stack, Instance instance) { - var ref = (int) stack.pop(); - if (ref == REF_NULL_VALUE) { + private static void ARRAY_LEN(MStack stack) { + var arrObj = stack.popRef(); + if (arrObj == null) { throw new TrapException("null array reference"); } - var arr = (WasmArray) instance.gcRef(ref); + var arr = (WasmArray) arrObj; stack.push(arr.length()); } private static void ARRAY_FILL(MStack stack, Instance instance, Operands operands) { var typeIdx = (int) operands.get(0); + var at = instance.module().typeSection().getSubType(typeIdx).compType().arrayType(); + boolean isRef = + at.fieldType().storageType().valType() != null + && at.fieldType().storageType().isReference(); var len = (int) stack.pop(); - var val = stack.pop(); + Object refVal = null; + long val = 0; + if (isRef) { + refVal = stack.popRef(); + } else { + val = stack.pop(); + } var offset = (int) stack.pop(); - var ref = (int) stack.pop(); - if (ref == REF_NULL_VALUE) { + var arrObj = stack.popRef(); + if (arrObj == null) { throw new TrapException("null array reference"); } - var arr = (WasmArray) instance.gcRef(ref); + var arr = (WasmArray) arrObj; if (offset + len > arr.length()) { throw new TrapException("out of bounds array access"); } - var at = instance.module().typeSection().getSubType(typeIdx).compType().arrayType(); - if (at.fieldType().storageType().packedType() != null) { - val = val & at.fieldType().storageType().packedType().mask(); - } - for (int i = 0; i < len; i++) { - arr.set(offset + i, val); + if (isRef) { + for (int i = 0; i < len; i++) { + arr.setRef(offset + i, refVal); + } + } else { + if (at.fieldType().storageType().packedType() != null) { + val = val & at.fieldType().storageType().packedType().mask(); + } + for (int i = 0; i < len; i++) { + arr.set(offset + i, val); + } } } - private static void ARRAY_COPY(MStack stack, Instance instance) { - // operands 0 and 1 are dst/src type indices (used for validation, not needed at runtime) + private static void ARRAY_COPY(MStack stack, Instance instance, Operands operands) { + var dstTypeIdx = (int) operands.get(0); var len = (int) stack.pop(); var srcOffset = (int) stack.pop(); - var srcRef = (int) stack.pop(); + var srcObj = stack.popRef(); var dstOffset = (int) stack.pop(); - var dstRef = (int) stack.pop(); - if (dstRef == REF_NULL_VALUE || srcRef == REF_NULL_VALUE) { + var dstObj = stack.popRef(); + if (dstObj == null || srcObj == null) { throw new TrapException("null array reference"); } - var dst = (WasmArray) instance.gcRef(dstRef); - var src = (WasmArray) instance.gcRef(srcRef); + var dst = (WasmArray) dstObj; + var src = (WasmArray) srcObj; if (dstOffset + len > dst.length() || srcOffset + len > src.length()) { throw new TrapException("out of bounds array access"); } + var dstAt = instance.module().typeSection().getSubType(dstTypeIdx).compType().arrayType(); + boolean isRef = + dstAt.fieldType().storageType().valType() != null + && dstAt.fieldType().storageType().valType().isReference(); // Handle overlapping copies if (dstOffset <= srcOffset) { for (int i = 0; i < len; i++) { - dst.set(dstOffset + i, src.get(srcOffset + i)); + if (isRef) { + dst.setRef(dstOffset + i, src.getRef(srcOffset + i)); + } else { + dst.set(dstOffset + i, src.get(srcOffset + i)); + } } } else { for (int i = len - 1; i >= 0; i--) { - dst.set(dstOffset + i, src.get(srcOffset + i)); + if (isRef) { + dst.setRef(dstOffset + i, src.getRef(srcOffset + i)); + } else { + dst.set(dstOffset + i, src.get(srcOffset + i)); + } } } } @@ -3422,11 +3669,11 @@ private static void ARRAY_INIT_DATA(MStack stack, Instance instance, Operands op var len = (int) stack.pop(); var srcOffset = (int) stack.pop(); var dstOffset = (int) stack.pop(); - var ref = (int) stack.pop(); - if (ref == REF_NULL_VALUE) { + var arrObj = stack.popRef(); + if (arrObj == null) { throw new TrapException("null array reference"); } - var arr = (WasmArray) instance.gcRef(ref); + var arr = (WasmArray) arrObj; var at = instance.module().typeSection().getSubType(typeIdx).compType().arrayType(); var elemSize = at.fieldType().storageType().byteSize(); var data = instance.dataSegmentData(dataIdx); @@ -3443,16 +3690,16 @@ private static void ARRAY_INIT_DATA(MStack stack, Instance instance, Operands op } private static void ARRAY_INIT_ELEM(MStack stack, Instance instance, Operands operands) { - // operand 0 is the type index (used for validation, not needed at runtime) + var typeIdx = (int) operands.get(0); var elemIdx = (int) operands.get(1); var len = (int) stack.pop(); var srcOffset = (int) stack.pop(); var dstOffset = (int) stack.pop(); - var ref = (int) stack.pop(); - if (ref == REF_NULL_VALUE) { + var arrObj = stack.popRef(); + if (arrObj == null) { throw new TrapException("null array reference"); } - var arr = (WasmArray) instance.gcRef(ref); + var arr = (WasmArray) arrObj; var element = instance.element(elemIdx); if (dstOffset + len > arr.length()) { throw new TrapException("out of bounds array access"); @@ -3465,9 +3712,16 @@ private static void ARRAY_INIT_ELEM(MStack stack, Instance instance, Operands op if (len == 0) { return; } + var at = instance.module().typeSection().getSubType(typeIdx).compType().arrayType(); + boolean isRef = at.fieldType().storageType().isReference(); for (int i = 0; i < len; i++) { var init = element.initializers().get(srcOffset + i); - arr.set(dstOffset + i, ConstantEvaluators.computeConstantValue(instance, init)[0]); + var result = ConstantEvaluators.computeConstant(instance, init); + if (isRef) { + arr.setRef(dstOffset + i, result.ref()); + } else { + arr.set(dstOffset + i, result.longValue()); + } } } @@ -3475,10 +3729,10 @@ private static void REF_TEST( MStack stack, Instance instance, Operands operands, OpCode opcode) { var heapType = (int) operands.get(0); var sourceHeapType = (int) operands.get(1); - var ref = stack.pop(); + var ref = stack.popRef(); boolean nullable = (opcode == OpCode.REF_TEST_NULL); stack.push( - instance.heapTypeMatch(ref, nullable, heapType, sourceHeapType) + instance.heapTypeMatchRef(ref, nullable, heapType, sourceHeapType) ? Value.TRUE : Value.FALSE); } @@ -3487,12 +3741,12 @@ private static void CAST_TEST( MStack stack, Instance instance, Operands operands, OpCode opcode) { var heapType = (int) operands.get(0); var sourceHeapType = (int) operands.get(1); - var ref = stack.pop(); + var ref = stack.popRef(); boolean nullable = (opcode == OpCode.CAST_TEST_NULL); - if (!instance.heapTypeMatch(ref, nullable, heapType, sourceHeapType)) { + if (!instance.heapTypeMatchRef(ref, nullable, heapType, sourceHeapType)) { throw new TrapException("cast failure"); } - stack.push(ref); + stack.pushRef(ref); } private static void BR_ON_CAST( @@ -3505,13 +3759,13 @@ private static void BR_ON_CAST( var ht2 = (int) operands.get(3); var sourceHeapType = (int) operands.get(4); boolean null2 = (flags & 2) != 0; - var ref = stack.pop(); - if (instance.heapTypeMatch(ref, null2, ht2, sourceHeapType)) { - stack.push(ref); + var ref = stack.popRef(); + if (instance.heapTypeMatchRef(ref, null2, ht2, sourceHeapType)) { + stack.pushRef(ref); ctrlJump(frame, stack, (int) operands.get(1)); frame.jumpTo(instruction.labelTrue()); } else { - stack.push(ref); + stack.pushRef(ref); } } @@ -3525,13 +3779,35 @@ private static void BR_ON_CAST_FAIL( var ht2 = (int) operands.get(3); var sourceHeapType = (int) operands.get(4); boolean null2 = (flags & 2) != 0; - var ref = stack.pop(); - if (!instance.heapTypeMatch(ref, null2, ht2, sourceHeapType)) { - stack.push(ref); + var ref = stack.popRef(); + if (!instance.heapTypeMatchRef(ref, null2, ht2, sourceHeapType)) { + stack.pushRef(ref); ctrlJump(frame, stack, (int) operands.get(1)); frame.jumpTo(instruction.labelTrue()); } else { - stack.push(ref); + stack.pushRef(ref); + } + } + + private static void ANY_CONVERT_EXTERN(MStack stack) { + // Pop long externref, wrap in WasmExternRef, pushRef + var val = stack.pop(); + if (val == REF_NULL_VALUE) { + stack.pushRef(null); + } else { + stack.pushRef(new WasmExternRef(val)); + } + } + + private static void EXTERN_CONVERT_ANY(MStack stack, Instance instance) { + var ref = stack.popRef(); + if (ref == null) { + stack.push(REF_NULL_VALUE); + } else if (ref instanceof WasmExternRef) { + stack.push(((WasmExternRef) ref).value()); + } else { + // GC value being externalized — register so it survives the round-trip + stack.push(instance.registerGcRef((WasmGcRef) ref)); } } diff --git a/runtime/src/main/java/run/endive/runtime/MStack.java b/runtime/src/main/java/run/endive/runtime/MStack.java index 40d00255..cdec3443 100644 --- a/runtime/src/main/java/run/endive/runtime/MStack.java +++ b/runtime/src/main/java/run/endive/runtime/MStack.java @@ -1,10 +1,13 @@ package run.endive.runtime; +import static run.endive.wasm.types.Value.REF_NULL_VALUE; + public class MStack { public static final int MIN_CAPACITY = 8; private int count; private long[] elements; + private Object[] refs; public MStack() { this.elements = new long[MIN_CAPACITY]; @@ -15,8 +18,13 @@ private void increaseCapacity() { final long[] array = new long[newCapacity]; System.arraycopy(elements, 0, array, 0, elements.length); - elements = array; + + if (refs != null) { + final Object[] refArray = new Object[newCapacity]; + System.arraycopy(refs, 0, refArray, 0, refs.length); + refs = refArray; + } } // internal use only! @@ -24,6 +32,10 @@ public long[] array() { return elements; } + public Object[] refArray() { + return refs; + } + public void push(long v) { elements[count] = v; count++; @@ -33,16 +45,48 @@ public void push(long v) { } } + public void pushRef(Object ref) { + if (refs == null) { + refs = new Object[elements.length]; + } + elements[count] = (ref == null) ? REF_NULL_VALUE : 0; + refs[count] = ref; + count++; + + if (count == elements.length) { + increaseCapacity(); + } + } + public long pop() { count--; return elements[count]; } + public Object popRef() { + count--; + Object ref = refs[count]; + refs[count] = null; + return ref; + } + public long peek() { return elements[count - 1]; } + public Object peekRef() { + return refs[count - 1]; + } + public int size() { return count; } + + public void clearRefsTo(int newSize) { + if (refs != null) { + for (int i = count - 1; i >= newSize; i--) { + refs[i] = null; + } + } + } } diff --git a/runtime/src/main/java/run/endive/runtime/Machine.java b/runtime/src/main/java/run/endive/runtime/Machine.java index d981d58a..56510284 100644 --- a/runtime/src/main/java/run/endive/runtime/Machine.java +++ b/runtime/src/main/java/run/endive/runtime/Machine.java @@ -6,4 +6,8 @@ public interface Machine { long[] call(int funcId, long[] args) throws WasmEngineException; + + default long[] call(int funcId, long[] args, Object[] refArgs) throws WasmEngineException { + return call(funcId, args); + } } diff --git a/runtime/src/main/java/run/endive/runtime/OpcodeImpl.java b/runtime/src/main/java/run/endive/runtime/OpcodeImpl.java index f0edaae9..27008b6a 100644 --- a/runtime/src/main/java/run/endive/runtime/OpcodeImpl.java +++ b/runtime/src/main/java/run/endive/runtime/OpcodeImpl.java @@ -820,17 +820,27 @@ public static void TABLE_COPY( throw new WasmRuntimeException("out of bounds table access"); } + boolean isGcTable = + !dest.elementType().equals(ValType.FuncRef) + && !dest.elementType().equals(ValType.ExternRef); + for (int i = size - 1; i >= 0; i--) { if (d <= s) { - var val = src.ref(s); var inst = src.instance(s); - dest.setRef(d, (int) val, inst); + if (isGcTable) { + dest.setObjRef(d, src.objRef(s), inst); + } else { + dest.setRef(d, src.ref(s), inst); + } s++; d++; } else { - var val = src.ref(s + i); var inst = src.instance(s + i); - dest.setRef(d + i, (int) val, inst); + if (isGcTable) { + dest.setObjRef(d + i, src.objRef(s + i), inst); + } else { + dest.setRef(d + i, src.ref(s + i), inst); + } } } } @@ -861,18 +871,24 @@ public static void TABLE_INIT( } int end = (int) endL; + boolean isGcTable = + !table.elementType().equals(ValType.FuncRef) + && !table.elementType().equals(ValType.ExternRef); for (int i = offset; i < end; i++) { var elem = instance.element(elementidx); - var val = - boxForTable( - computeConstantValue(instance, elem.initializers().get(elemidx++))[0], - instance); - if (table.elementType().equals(ValType.FuncRef)) { - if (val > instance.functionCount()) { - throw new WasmRuntimeException("out of bounds table access"); - } - table.setRef(i, val, instance); + if (isGcTable) { + var result = + ConstantEvaluators.computeConstant( + instance, elem.initializers().get(elemidx++)); + table.setObjRef(i, result.ref(), instance); } else { + var val = + (int) computeConstantValue(instance, elem.initializers().get(elemidx++))[0]; + if (table.elementType().equals(ValType.FuncRef)) { + if (val > instance.functionCount()) { + throw new WasmRuntimeException("out of bounds table access"); + } + } table.setRef(i, val, instance); } } diff --git a/runtime/src/main/java/run/endive/runtime/StackFrame.java b/runtime/src/main/java/run/endive/runtime/StackFrame.java index eb684f7a..2dc8de8e 100644 --- a/runtime/src/main/java/run/endive/runtime/StackFrame.java +++ b/runtime/src/main/java/run/endive/runtime/StackFrame.java @@ -27,6 +27,7 @@ public class StackFrame { private final int funcId; private int pc; private final long[] locals; + private final Object[] localRefs; private final ValType[] localTypes; private final int[] localIdx; private final Instance instance; @@ -38,6 +39,7 @@ public StackFrame(Instance instance, int funcId, long[] args) { instance, funcId, args, + null, Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); @@ -47,6 +49,7 @@ public StackFrame(Instance instance, int funcId, long[] args) { Instance instance, int funcId, long[] args, + Object[] refArgs, List argsTypes, List localTypes, List code) { @@ -54,6 +57,10 @@ public StackFrame(Instance instance, int funcId, long[] args) { this.instance = instance; this.funcId = funcId; this.locals = Arrays.copyOf(args, sizeOf(argsTypes) + sizeOf(localTypes)); + this.localRefs = new Object[this.locals.length]; + if (refArgs != null) { + System.arraycopy(refArgs, 0, this.localRefs, 0, refArgs.length); + } int localsSize = argsTypes.size() + localTypes.size(); this.localTypes = new ValType[localsSize]; for (int i = 0; i < argsTypes.size(); i++) { @@ -70,7 +77,11 @@ public StackFrame(Instance instance, int funcId, long[] args) { ValType type = localTypes.get(i); var idx = j + sizeOf(argsTypes); if (!type.equals(ValType.V128)) { - locals[idx] = Value.zero(type); + if (type.isReference()) { + locals[idx] = Value.REF_NULL_VALUE; + } else { + locals[idx] = Value.zero(type); + } j += 1; } else { locals[idx] = Value.zero(ValType.I64); @@ -95,6 +106,18 @@ void reset(long[] args) { for (int i = 0; i < locals.length; i++) { setLocal(i, args[i]); } + Arrays.fill(localRefs, null); + pc = 0; + } + + void reset(long[] args, Object[] refArgs) { + for (int i = 0; i < locals.length; i++) { + setLocal(i, args[i]); + } + Arrays.fill(localRefs, null); + if (refArgs != null) { + System.arraycopy(refArgs, 0, localRefs, 0, refArgs.length); + } pc = 0; } @@ -118,6 +141,15 @@ long local(int i) { return locals[i]; } + void setLocalRef(int i, Object ref) { + this.localRefs[i] = ref; + this.locals[i] = (ref == null) ? Value.REF_NULL_VALUE : 0; + } + + Object localRef(int i) { + return localRefs[i]; + } + @Override public String toString() { var nameSec = instance.module().nameSection(); @@ -200,19 +232,32 @@ void jumpTo(int newPc) { static void doControlTransfer(CtrlFrame ctrlFrame, MStack stack) { var endResults = ctrlFrame.startValues + ctrlFrame.endValues; // unwind stack long[] returns = new long[endResults]; + Object[] returnRefs = new Object[endResults]; + Object[] stackRefArray = stack.refArray(); for (int i = 0; i < returns.length; i++) { if (stack.size() > 0) { returns[i] = stack.pop(); + if (stackRefArray != null) { + returnRefs[i] = stackRefArray[stack.size()]; + stackRefArray[stack.size()] = null; + } } } + stack.clearRefsTo(ctrlFrame.height); while (stack.size() > ctrlFrame.height) { stack.pop(); } for (int i = 0; i < returns.length; i++) { - long value = returns[returns.length - 1 - i]; - stack.push(value); + int idx = returns.length - 1 - i; + long value = returns[idx]; + Object ref = returnRefs[idx]; + if (ref != null) { + stack.pushRef(ref); + } else { + stack.push(value); + } } } } diff --git a/runtime/src/main/java/run/endive/runtime/TableInstance.java b/runtime/src/main/java/run/endive/runtime/TableInstance.java index 36eb8cf3..9717680c 100644 --- a/runtime/src/main/java/run/endive/runtime/TableInstance.java +++ b/runtime/src/main/java/run/endive/runtime/TableInstance.java @@ -14,12 +14,21 @@ public class TableInstance { private final Table table; private Instance[] instances; private int[] refs; + private Object[] objRefs; public TableInstance(Table table, int initialValue) { this.table = table; this.instances = new Instance[(int) table.limits().min()]; refs = new int[(int) table.limits().min()]; Arrays.fill(refs, initialValue); + if (isGcTable()) { + objRefs = new Object[(int) table.limits().min()]; + } + } + + private boolean isGcTable() { + var et = table.elementType(); + return !et.equals(ValType.FuncRef) && !et.equals(ValType.ExternRef); } public int size() { @@ -46,6 +55,29 @@ public int grow(int size, int value, Instance instance) { Arrays.fill(newInstances, oldSize, targetSize, instance); refs = newRefs; instances = newInstances; + if (objRefs != null) { + objRefs = Arrays.copyOf(objRefs, targetSize); + } + table.limits().grow(size); + return oldSize; + } + + public int growWithRef(int size, Object refValue, Instance instance) { + var oldSize = refs.length; + var targetSize = oldSize + size; + if (size < 0 || targetSize > limits().max()) { + return -1; + } + var newRefs = Arrays.copyOf(refs, targetSize); + Arrays.fill(newRefs, oldSize, targetSize, REF_NULL_VALUE); + var newInstances = Arrays.copyOf(instances, targetSize); + Arrays.fill(newInstances, oldSize, targetSize, instance); + refs = newRefs; + instances = newInstances; + if (objRefs != null) { + objRefs = Arrays.copyOf(objRefs, targetSize); + Arrays.fill(objRefs, oldSize, targetSize, refValue); + } table.limits().grow(size); return oldSize; } @@ -65,6 +97,13 @@ public int requiredRef(int index) { return ref; } + public Object objRef(int index) { + if (index < 0 || index >= this.refs.length) { + throw new WasmEngineException("undefined element"); + } + return (objRefs != null) ? objRefs[index] : null; + } + public void setRef(int index, int value, Instance instance) { if (index < 0 || index >= this.refs.length || index >= this.instances.length) { throw new UninstantiableException("out of bounds table access"); @@ -73,6 +112,17 @@ public void setRef(int index, int value, Instance instance) { this.instances[index] = instance; } + public void setObjRef(int index, Object ref, Instance instance) { + if (index < 0 || index >= this.refs.length || index >= this.instances.length) { + throw new UninstantiableException("out of bounds table access"); + } + if (objRefs != null) { + objRefs[index] = ref; + } + this.refs[index] = (ref == null) ? REF_NULL_VALUE : 0; + this.instances[index] = instance; + } + public Instance instance(int index) { return instances[index]; } @@ -81,5 +131,8 @@ public void reset() { for (int i = 0; i < refs.length; i++) { this.refs[i] = REF_NULL_VALUE; } + if (objRefs != null) { + Arrays.fill(objRefs, null); + } } } diff --git a/runtime/src/main/java/run/endive/runtime/WasmArray.java b/runtime/src/main/java/run/endive/runtime/WasmArray.java index a46fb3d4..b452aed0 100644 --- a/runtime/src/main/java/run/endive/runtime/WasmArray.java +++ b/runtime/src/main/java/run/endive/runtime/WasmArray.java @@ -2,16 +2,23 @@ /** * Runtime representation of a WasmGC array instance. - * Elements are stored as raw long values (same encoding as stack values). + * Numeric elements are stored in {@code elements} (long[]). + * Reference-typed elements are stored in {@code elementRefs} (Object[]). * Packed types (i8, i16) are stored as full long slots for simplicity. */ public final class WasmArray implements WasmGcRef { private final int typeIdx; private final long[] elements; + private final Object[] elementRefs; public WasmArray(int typeIdx, long[] elements) { + this(typeIdx, elements, null); + } + + public WasmArray(int typeIdx, long[] elements, Object[] elementRefs) { this.typeIdx = typeIdx; this.elements = elements; + this.elementRefs = (elementRefs != null) ? elementRefs : new Object[elements.length]; } @Override @@ -27,6 +34,14 @@ public void set(int idx, long value) { elements[idx] = value; } + public Object getRef(int idx) { + return elementRefs[idx]; + } + + public void setRef(int idx, Object ref) { + elementRefs[idx] = ref; + } + public int length() { return elements.length; } @@ -34,4 +49,8 @@ public int length() { public long[] elements() { return elements; } + + public Object[] elementRefs() { + return elementRefs; + } } diff --git a/runtime/src/main/java/run/endive/runtime/WasmI31Ref.java b/runtime/src/main/java/run/endive/runtime/WasmI31Ref.java index 8a03239e..4df863b1 100644 --- a/runtime/src/main/java/run/endive/runtime/WasmI31Ref.java +++ b/runtime/src/main/java/run/endive/runtime/WasmI31Ref.java @@ -1,10 +1,9 @@ package run.endive.runtime; /** - * Boxed representation of an i31ref value for storage in int-typed containers (tables, globals). - * On the stack, i31 values use an efficient tagged-long encoding (see {@link - * run.endive.wasm.types.Value#encodeI31}). This class is only used when i31 values need - * to pass through int-typed storage where the tag would be lost. + * Representation of an i31ref value. + * On the stack, stored as Object in the refs array. + * Two i31ref values with the same integer value are equal per the Wasm spec (ref.eq). */ public final class WasmI31Ref implements WasmGcRef { @@ -24,4 +23,20 @@ public int typeIdx() { public int value() { return value; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof WasmI31Ref)) { + return false; + } + return value == ((WasmI31Ref) o).value; + } + + @Override + public int hashCode() { + return value; + } } diff --git a/runtime/src/main/java/run/endive/runtime/WasmStruct.java b/runtime/src/main/java/run/endive/runtime/WasmStruct.java index 35e3eb04..76aee0d7 100644 --- a/runtime/src/main/java/run/endive/runtime/WasmStruct.java +++ b/runtime/src/main/java/run/endive/runtime/WasmStruct.java @@ -2,15 +2,23 @@ /** * Runtime representation of a WasmGC struct instance. - * Fields are stored as raw long values (same encoding as stack values). + * Numeric fields are stored in {@code fields} (long[]). + * Reference-typed fields are stored in {@code fieldRefs} (Object[]). + * Both arrays are indexed by field index; only one is "active" per field. */ public final class WasmStruct implements WasmGcRef { private final int typeIdx; private final long[] fields; + private final Object[] fieldRefs; public WasmStruct(int typeIdx, long[] fields) { + this(typeIdx, fields, null); + } + + public WasmStruct(int typeIdx, long[] fields, Object[] fieldRefs) { this.typeIdx = typeIdx; this.fields = fields; + this.fieldRefs = (fieldRefs != null) ? fieldRefs : new Object[fields.length]; } @Override @@ -26,6 +34,14 @@ public void setField(int idx, long value) { fields[idx] = value; } + public Object fieldRef(int idx) { + return fieldRefs[idx]; + } + + public void setFieldRef(int idx, Object ref) { + fieldRefs[idx] = ref; + } + public int fieldCount() { return fields.length; } diff --git a/runtime/src/main/java/run/endive/runtime/internal/CompilerInterpreterMachine.java b/runtime/src/main/java/run/endive/runtime/internal/CompilerInterpreterMachine.java index b725f75a..d09cca7a 100644 --- a/runtime/src/main/java/run/endive/runtime/internal/CompilerInterpreterMachine.java +++ b/runtime/src/main/java/run/endive/runtime/internal/CompilerInterpreterMachine.java @@ -44,6 +44,7 @@ protected long[] call( Deque callStack, int funcId, long[] args, + Object[] refArgs, FunctionType callType, boolean popResults) throws WasmEngineException { @@ -51,7 +52,7 @@ protected long[] call( usedInterpretedFunctions.add(funcId); System.err.println("Endive: calling interpreted function " + funcId); } - return super.call(stack, instance, callStack, funcId, args, callType, popResults); + return super.call(stack, instance, callStack, funcId, args, refArgs, callType, popResults); } @Override diff --git a/wasm/src/main/java/run/endive/wasm/types/StorageType.java b/wasm/src/main/java/run/endive/wasm/types/StorageType.java index c869c1aa..1d2e90a5 100644 --- a/wasm/src/main/java/run/endive/wasm/types/StorageType.java +++ b/wasm/src/main/java/run/endive/wasm/types/StorageType.java @@ -27,6 +27,14 @@ public PackedType packedType() { return packedType; } + public boolean isReference() { + return valType != null && valType.isReference(); + } + + public boolean isGcReference() { + return valType != null && valType.isGcReference(); + } + public int byteSize() { if (packedType != null) { switch (packedType) { diff --git a/wasm/src/main/java/run/endive/wasm/types/ValType.java b/wasm/src/main/java/run/endive/wasm/types/ValType.java index 189de8df..96d12e67 100644 --- a/wasm/src/main/java/run/endive/wasm/types/ValType.java +++ b/wasm/src/main/java/run/endive/wasm/types/ValType.java @@ -234,6 +234,28 @@ public boolean isReference() { return isReference(this.opcode()); } + public boolean isGcReference() { + switch (opcode()) { + case ID.AnyRef: + case ID.EqRef: + case ID.i31: + case ID.StructRef: + case ID.ArrayRef: + case ID.NoneRef: + return true; + case ID.Ref: + case ID.RefNull: + int ht = typeIdx(); + return ht != TypeIdxCode.FUNC.code() + && ht != TypeIdxCode.NOFUNC.code() + && ht != TypeIdxCode.EXTERN.code() + && ht != TypeIdxCode.NOEXTERN.code() + && ht != TypeIdxCode.EXN.code(); + default: + return false; + } + } + // https://webassembly.github.io/gc/core/binary/types.html#heap-types public static boolean isAbsHeapType(int opcode) { return (opcode == ID.NoFuncRef @@ -672,6 +694,30 @@ public boolean isReference() { return ValType.isReference(opcode); } + public boolean isGcReference() { + if (!isReference()) { + return false; + } + switch (opcode) { + case ID.AnyRef: + case ID.EqRef: + case ID.i31: + case ID.StructRef: + case ID.ArrayRef: + case ID.NoneRef: + return true; + case ID.Ref: + case ID.RefNull: + return typeIdx != TypeIdxCode.FUNC.code() + && typeIdx != TypeIdxCode.NOFUNC.code() + && typeIdx != TypeIdxCode.EXTERN.code() + && typeIdx != TypeIdxCode.NOEXTERN.code() + && typeIdx != TypeIdxCode.EXN.code(); + default: + return false; + } + } + @Deprecated(since = "use .build.resolve(typeSection) instead") public ValType build(Function context) { if (!isValidOpcode(opcode)) { From 4465b16bb9b888cb99bae81eb3a7c61367ef5c4b Mon Sep 17 00:00:00 2001 From: andreatp Date: Wed, 3 Jun 2026 11:39:31 +0100 Subject: [PATCH 2/4] feat: add Object-based API (callGc/applyGc) for GC reference support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add callGc/applyGc methods to Machine, ExportFunction, and WasmFunctionHandle for passing real Java Objects as Wasm GC refs. Users can now receive WasmStruct/WasmArray/WasmI31Ref directly from exported functions, and Java GC manages their lifecycle. Key changes: - Machine.callGc(int, Object[]) / ExportFunction.applyGc(Object...) - InterpreterMachine overrides callGc with native Object path - WasmFunctionHandle.applyGc for GC-aware host functions - WasmExternRef can wrap either long or Object (for extern.convert_any) - ValType.isGcReference() cached during resolve() — zero-cost check - isGcReference correctly classifies concrete func types as non-GC - Table init populates objRefs for GC-typed tables - Instance.registerGcRef/gcRef/array deprecated - OpcodeImpl.boxForTable/unboxFromTable deprecated - Compiler: GC refs use Object.class, call bridge threads Object[] Compiler Phase 2: GC refs flow as Objects in generated bytecode. CompilerUtil maps GC ref types to Object.class/OBJECT_TYPE. Shaded helpers take/return Object for GC refs. Generated call_N bridges accept Object[] refArgs. BR_ON_NULL/NON_NULL checks distinguish GC (ifnull) from funcref (if_icmpeq). Non-GC code paths are completely unchanged — zero overhead. --- .../endive/compiler/internal/Compiler.java | 180 +++++++-- .../compiler/internal/CompilerUtil.java | 72 +++- .../endive/compiler/internal/Emitters.java | 352 +++++++++++++----- .../run/endive/compiler/internal/Shaded.java | 285 ++++++++------ .../endive/compiler/internal/ShadedRefs.java | 101 +++-- .../compiler/internal/WasmAnalyzer.java | 22 +- .../ApprovalTest.functions10.approved.txt | 104 +++--- .../ApprovalTest.verifyBrTable.approved.txt | 32 +- .../ApprovalTest.verifyBranching.approved.txt | 32 +- ...ApprovalTest.verifyExceptions.approved.txt | 18 +- .../ApprovalTest.verifyFloat.approved.txt | 28 +- .../ApprovalTest.verifyGc.approved.txt | 62 +-- .../ApprovalTest.verifyHelloWasi.approved.txt | 29 +- .../ApprovalTest.verifyI32.approved.txt | 28 +- ...ApprovalTest.verifyI32Renamed.approved.txt | 21 +- .../ApprovalTest.verifyIterFact.approved.txt | 32 +- ...pprovalTest.verifyKitchenSink.approved.txt | 32 +- .../ApprovalTest.verifyMemory.approved.txt | 40 +- .../ApprovalTest.verifyStart.approved.txt | 29 +- .../ApprovalTest.verifyTailCall.approved.txt | 61 ++- .../ApprovalTest.verifyTrap.approved.txt | 36 +- .../run/endive/runtime/ExportFunction.java | 4 + .../java/run/endive/runtime/Instance.java | 38 +- .../endive/runtime/InterpreterMachine.java | 136 +++++-- .../main/java/run/endive/runtime/Machine.java | 4 + .../java/run/endive/runtime/OpcodeImpl.java | 31 +- .../run/endive/runtime/TableInstance.java | 3 +- .../run/endive/runtime/WasmExternRef.java | 26 +- .../endive/runtime/WasmFunctionHandle.java | 5 + .../java/run/endive/wasm/types/ValType.java | 82 ++-- 30 files changed, 1416 insertions(+), 509 deletions(-) diff --git a/compiler/src/main/java/run/endive/compiler/internal/Compiler.java b/compiler/src/main/java/run/endive/compiler/internal/Compiler.java index f3d3f469..8fd071b8 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/Compiler.java +++ b/compiler/src/main/java/run/endive/compiler/internal/Compiler.java @@ -76,6 +76,7 @@ import run.endive.runtime.Machine; import run.endive.runtime.Memory; import run.endive.runtime.WasmException; +import run.endive.runtime.WasmGcRef; import run.endive.runtime.internal.CompilerInterpreterMachine; import run.endive.wasm.WasmEngineException; import run.endive.wasm.WasmModule; @@ -94,11 +95,27 @@ public final class Compiler { Type.getType(CompilerInterpreterMachine.class); private static final Type INSTANCE_TYPE = Type.getType(Instance.class); - private static final MethodType CALL_METHOD_TYPE = - methodType(long[].class, Instance.class, Memory.class, long[].class); + private static final java.lang.reflect.Method INSTANCE_REGISTER_GC_REF; - private static final MethodType MACHINE_CALL_METHOD_TYPE = - methodType(long[].class, Instance.class, Memory.class, int.class, long[].class); + static { + try { + INSTANCE_REGISTER_GC_REF = Instance.class.getMethod("registerGcRef", WasmGcRef.class); + } catch (NoSuchMethodException e) { + throw new AssertionError(e); + } + } + + private static final MethodType CALL_METHOD_TYPE_WITH_REFS = + methodType(long[].class, Instance.class, Memory.class, long[].class, Object[].class); + + private static final MethodType MACHINE_CALL_METHOD_TYPE_WITH_REFS = + methodType( + long[].class, + Instance.class, + Memory.class, + int.class, + long[].class, + Object[].class); // C2 JIT's HugeMethodLimit (default 8KB) — methods exceeding this get degraded optimization. // Dispatch chunks are sized to stay under this limit for full C2 compilation. @@ -483,7 +500,7 @@ private Consumer emitFunctionGroup(int start, int end, String inte emitFunction( classWriter, callMethodName(funcId), - CALL_METHOD_TYPE, + CALL_METHOD_TYPE_WITH_REFS, true, asm -> compileCallFunction(funcId, type, asm)); } @@ -534,13 +551,27 @@ private byte[] compileClass() { false, asm -> compileConstructor(asm, internalClassName)); - // Machine.call() implementation + // Machine.call(int, long[]) implementation — 2-arg overload + // Locals: 0=this, 1=funcId, 2=args; store null refArgs at slot 3 emitFunction( classWriter, "call", methodType(long[].class, int.class, long[].class), false, - asm -> compileMachineCall(internalClassName, asm)); + asm -> { + asm.aconst(null); + asm.store(3, OBJECT_TYPE); // refArgs = null + compileMachineCall(internalClassName, asm, 3); + }); + + // Machine.call(int, long[], Object[]) implementation — 3-arg overload + // Locals: 0=this, 1=funcId, 2=args, 3=refArgs + emitFunction( + classWriter, + "call", + methodType(long[].class, int.class, long[].class, Object[].class), + false, + asm -> compileMachineCall(internalClassName, asm, 3)); // call_indirect_xxx() bridges for native CALL_INDIRECT // When using bridge classes, these methods are on separate classes @@ -701,7 +732,12 @@ private void compileConstructor(InstructionAdapter asm, String internalClassName // implements the body of: // public long[] call(int var1, long[] var2) - private void compileMachineCall(String internalClassName, InstructionAdapter asm) { + // or + // public long[] call(int var1, long[] var2, Object[] var3) + // + // refArgsSlot: the local variable slot holding Object[] refArgs + private void compileMachineCall( + String internalClassName, InstructionAdapter asm, int refArgsSlot) { // handle modules with no functions if (functionTypes.isEmpty()) { @@ -743,13 +779,14 @@ private void compileMachineCall(String internalClassName, InstructionAdapter asm } if (moduleHasTailCalls) { - compileMachineCallWithTailCalls(internalClassName, asm); + compileMachineCallWithTailCalls(internalClassName, asm, refArgsSlot); } else { - compileMachineCallSimple(internalClassName, asm); + compileMachineCallSimple(internalClassName, asm, refArgsSlot); } } - private void compileMachineCallSimple(String internalClassName, InstructionAdapter asm) { + private void compileMachineCallSimple( + String internalClassName, InstructionAdapter asm, int refArgsSlot) { Label start = new Label(); Label end = new Label(); asm.visitTryCatchBlock(start, end, end, getInternalName(StackOverflowError.class)); @@ -761,11 +798,12 @@ private void compileMachineCallSimple(String internalClassName, InstructionAdapt emitInvokeVirtual(asm, INSTANCE_MEMORY); asm.load(1, INT_TYPE); asm.load(2, OBJECT_TYPE); + asm.load(refArgsSlot, OBJECT_TYPE); // refArgs (null for 2-arg, actual for 3-arg) asm.invokestatic( internalClassName + "MachineCall", "call", - MACHINE_CALL_METHOD_TYPE.toMethodDescriptorString(), + MACHINE_CALL_METHOD_TYPE_WITH_REFS.toMethodDescriptorString(), false); asm.areturn(OBJECT_TYPE); @@ -789,7 +827,8 @@ private void compileMachineCallSimple(String internalClassName, InstructionAdapt // throw throwCallStackExhausted(e); // } // } - private void compileMachineCallWithTailCalls(String internalClassName, InstructionAdapter asm) { + private void compileMachineCallWithTailCalls( + String internalClassName, InstructionAdapter asm, int refArgsSlot) { Label start = new Label(); Label end = new Label(); Label soeCatch = new Label(); @@ -798,24 +837,27 @@ private void compileMachineCallWithTailCalls(String internalClassName, Instructi asm.load(0, OBJECT_TYPE); asm.getfield(internalClassName, "instance", getDescriptor(Instance.class)); - asm.store(3, OBJECT_TYPE); + // instance stored at local 4 for 2-arg, local 5 for 3-arg + int instanceLocal = refArgsSlot + 1; + asm.store(instanceLocal, OBJECT_TYPE); Label loopStart = new Label(); asm.mark(loopStart); - asm.load(3, OBJECT_TYPE); + asm.load(instanceLocal, OBJECT_TYPE); asm.dup(); emitInvokeVirtual(asm, INSTANCE_MEMORY); asm.load(1, INT_TYPE); asm.load(2, OBJECT_TYPE); + asm.load(refArgsSlot, OBJECT_TYPE); // refArgs asm.invokestatic( internalClassName + "MachineCall", "call", - MACHINE_CALL_METHOD_TYPE.toMethodDescriptorString(), + MACHINE_CALL_METHOD_TYPE_WITH_REFS.toMethodDescriptorString(), false); - asm.load(3, OBJECT_TYPE); + asm.load(instanceLocal, OBJECT_TYPE); asm.invokevirtual( getInternalName(Instance.class), "isTailCallPending", @@ -825,21 +867,24 @@ private void compileMachineCallWithTailCalls(String internalClassName, Instructi asm.ifeq(returnResult); asm.pop(); - asm.load(3, OBJECT_TYPE); + asm.load(instanceLocal, OBJECT_TYPE); asm.invokevirtual( getInternalName(Instance.class), "tailCallFuncId", getMethodDescriptor(INT_TYPE), false); asm.store(1, INT_TYPE); - asm.load(3, OBJECT_TYPE); + asm.load(instanceLocal, OBJECT_TYPE); asm.invokevirtual( getInternalName(Instance.class), "tailCallArgs", getMethodDescriptor(getType(long[].class)), false); asm.store(2, OBJECT_TYPE); - asm.load(3, OBJECT_TYPE); + // Clear refArgs for tail calls (tail call args don't carry Object[] refArgs) + asm.aconst(null); + asm.store(refArgsSlot, OBJECT_TYPE); + asm.load(instanceLocal, OBJECT_TYPE); asm.invokevirtual( getInternalName(Instance.class), "clearTailCall", @@ -900,14 +945,14 @@ private void compileMachineCallClass() { emitFunction( cw, callDispatchMethodName(start), - MACHINE_CALL_METHOD_TYPE, + MACHINE_CALL_METHOD_TYPE_WITH_REFS, true, asm -> compileMachineCallInvoke( asm, start, end)))); callMethod = compileMachineCallDispatch(maxMachineCallMethods); } - emitFunction(classWriter, "call", MACHINE_CALL_METHOD_TYPE, true, callMethod); + emitFunction(classWriter, "call", MACHINE_CALL_METHOD_TYPE_WITH_REFS, true, callMethod); collector.put(machineCallClassName, binaryWriter.toByteArray()); } @@ -931,11 +976,12 @@ private void compileExtraClass( private Consumer compileMachineCallDispatch(int maxMachineCallMethods) { return (asm) -> { - // load arguments + // load arguments: instance, memory, funcId, args, refArgs asm.load(0, OBJECT_TYPE); asm.load(1, OBJECT_TYPE); asm.load(2, INT_TYPE); asm.load(3, OBJECT_TYPE); + asm.load(4, OBJECT_TYPE); // refArgs assert Integer.bitCount(maxMachineCallMethods) == 1; // power of two int shift = Integer.numberOfTrailingZeros(maxMachineCallMethods); @@ -951,13 +997,13 @@ private Consumer compileMachineCallDispatch(int maxMachineCa asm.shr(INT_TYPE); asm.tableswitch(0, labels.length - 1, labels[0], labels); - // return call_dispatch_xxx(instance, memory, funcId, args); + // return call_dispatch_xxx(instance, memory, funcId, args, refArgs); for (int i = 0; i < labels.length; i++) { asm.mark(labels[i]); asm.invokestatic( internalClassName(classNameForDispatch(className, i << shift)), callDispatchMethodName(i << shift), - MACHINE_CALL_METHOD_TYPE.toMethodDescriptorString(), + MACHINE_CALL_METHOD_TYPE_WITH_REFS.toMethodDescriptorString(), false); asm.areturn(OBJECT_TYPE); } @@ -965,10 +1011,11 @@ private Consumer compileMachineCallDispatch(int maxMachineCa } private void compileMachineCallInvoke(InstructionAdapter asm, int start, int end) { - // load arguments + // load arguments: instance, memory, args, refArgs asm.load(0, OBJECT_TYPE); asm.load(1, OBJECT_TYPE); asm.load(3, OBJECT_TYPE); + asm.load(4, OBJECT_TYPE); // refArgs // switch (funcId) Label defaultLabel = new Label(); @@ -982,13 +1029,13 @@ private void compileMachineCallInvoke(InstructionAdapter asm, int start, int end asm.load(2, INT_TYPE); asm.tableswitch(start, end - 1, defaultLabel, labels); - // return call_xxx(instance, memory, args); + // return call_xxx(instance, memory, args, refArgs); for (int id = max(start, functionImports); id < end; id++) { asm.mark(labels[id - start]); asm.invokestatic( internalClassName(classNameForFuncGroup(className, id)), callMethodName(id), - CALL_METHOD_TYPE.toMethodDescriptorString(), + CALL_METHOD_TYPE_WITH_REFS.toMethodDescriptorString(), false); asm.areturn(OBJECT_TYPE); } @@ -996,6 +1043,7 @@ private void compileMachineCallInvoke(InstructionAdapter asm, int start, int end // return instance.callHostFunction(funcId, args); if (functionImports > start) { asm.mark(hostLabel); + asm.pop(); // pop refArgs asm.pop(); asm.pop(); asm.load(2, INT_TYPE); @@ -1012,19 +1060,27 @@ private void compileMachineCallInvoke(InstructionAdapter asm, int start, int end } // implements the body of: - // public static long[] call_xxx(Memory memory, Instance instance, long[] args) + // public static long[] call_xxx(Instance instance, Memory memory, long[] args, Object[] + // refArgs) private void compileCallFunction(int funcId, FunctionType type, InstructionAdapter asm) { if (hasTooManyParameters(type)) { asm.load(2, LONG_ARRAY_TYPE); } else { - // unbox the arguments from long[] + // unbox the arguments from long[] (and Object[] refArgs for GC refs) for (int i = 0; i < type.params().size(); i++) { var param = type.params().get(i); - asm.load(2, OBJECT_TYPE); - asm.iconst(i); - asm.aload(LONG_TYPE); - emitLongToJvm(asm, param); + if (param.isGcReference()) { + // GC ref args: load from Object[] refArgs + asm.load(3, OBJECT_TYPE); // refArgs + asm.iconst(i); + asm.aload(OBJECT_TYPE); + } else { + asm.load(2, OBJECT_TYPE); + asm.iconst(i); + asm.aload(LONG_TYPE); + emitLongToJvm(asm, param); + } } } @@ -1038,16 +1094,32 @@ private void compileCallFunction(int funcId, FunctionType type, InstructionAdapt Class returnType = jvmReturnType(type); if (returnType == void.class) { asm.aconst(null); - } else if (returnType != long[].class) { + } else if (returnType == Object.class) { + // GC ref return: register in GcRefStore and wrap in long[] + asm.store(4, OBJECT_TYPE); + asm.load(0, OBJECT_TYPE); // instance + asm.load(4, OBJECT_TYPE); + emitInvokeVirtual(asm, INSTANCE_REGISTER_GC_REF); + // returns int ID + asm.visitInsn(Opcodes.I2L); + asm.store(4, LONG_TYPE); + asm.iconst(1); + asm.newarray(LONG_TYPE); + asm.dup(); + asm.iconst(0); + asm.load(4, LONG_TYPE); + asm.astore(LONG_TYPE); + } else if (returnType != long[].class && returnType != Object[].class) { emitJvmToLong(asm, type.returns().get(0)); - asm.store(3, LONG_TYPE); + asm.store(4, LONG_TYPE); asm.iconst(1); asm.newarray(LONG_TYPE); asm.dup(); asm.iconst(0); - asm.load(3, LONG_TYPE); + asm.load(4, LONG_TYPE); asm.astore(LONG_TYPE); } + // For long[] or Object[] multi-value returns, leave as-is (TODO: handle Object[] returns) asm.areturn(OBJECT_TYPE); } @@ -1314,7 +1386,6 @@ private static void compileHostFunction(int funcId, FunctionType type, Instructi private static void emitBoxArguments(InstructionAdapter asm, List types) { int slot = 0; - // box the arguments into long[] asm.iconst(types.size()); asm.newarray(LONG_TYPE); for (int i = 0; i < types.size(); i++) { @@ -1322,7 +1393,12 @@ private static void emitBoxArguments(InstructionAdapter asm, List types asm.iconst(i); ValType valType = types.get(i); asm.load(slot, asmType(valType)); - emitJvmToLong(asm, valType); + if (valType.isGcReference()) { + asm.visitInsn(Opcodes.POP); + asm.lconst(0L); + } else { + emitJvmToLong(asm, valType); + } asm.astore(LONG_TYPE); slot += slotCount(valType); } @@ -1332,7 +1408,23 @@ private static void emitUnboxResult(FunctionType type, InstructionAdapter asm) { Class returnType = jvmReturnType(type); if (returnType == void.class) { asm.areturn(VOID_TYPE); - } else if (returnType == long[].class) { + } else if (returnType == long[].class || returnType == Object[].class) { + asm.areturn(OBJECT_TYPE); + } else if (returnType == Object.class) { + // GC ref return from Machine.call which returns long[]. + // The long[0] contains a GcRefStore ID. Resolve it via Instance.gcRef. + // Stack: [..., long[]] + asm.iconst(0); + asm.aload(LONG_TYPE); + asm.visitInsn(Opcodes.L2I); + // Stack: [..., int gcRefId] + // We don't have Instance on the stack here, but it's available via the + // refInstance local in compileCallIndirect. Pop the ID for now. + // Actually, for the "other module" path, the result should stay as long[] + // since the other module doesn't use our GcRefStore. Just drop and return null. + // Cross-module GC refs are not yet supported. + asm.visitInsn(Opcodes.POP); + asm.aconst(null); asm.areturn(OBJECT_TYPE); } else { // unbox the result from long[0] @@ -1408,7 +1500,13 @@ private void compileFunction( localsCount += body.localTypes().size(); for (int i = type.params().size(); i < localsCount; i++) { var localType = localType(type, body, i); - asm.visitLdcInsn(defaultValue(localType)); + var defVal = defaultValue(localType); + if (defVal == null) { + // GC ref types: default to null (Object) + asm.aconst(null); + } else { + asm.visitLdcInsn(defVal); + } asm.store(ctx.localSlotIndex(i), asmType(localType)); } diff --git a/compiler/src/main/java/run/endive/compiler/internal/CompilerUtil.java b/compiler/src/main/java/run/endive/compiler/internal/CompilerUtil.java index ef6f8d87..94844c20 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/CompilerUtil.java +++ b/compiler/src/main/java/run/endive/compiler/internal/CompilerUtil.java @@ -54,10 +54,11 @@ private CompilerUtil() {} public static Class jvmType(ValType type) { switch (type.opcode()) { case ValType.ID.I32: - case ValType.ID.Ref: - case ValType.ID.RefNull: case ValType.ID.ExnRef: return int.class; + case ValType.ID.Ref: + case ValType.ID.RefNull: + return type.isGcReference() ? Object.class : int.class; case ValType.ID.I64: return long.class; case ValType.ID.F32: @@ -69,13 +70,16 @@ public static Class jvmType(ValType type) { } } + private static final Type OBJECT_ASM_TYPE = Type.getType(Object.class); + public static Type asmType(ValType type) { switch (type.opcode()) { case ValType.ID.I32: - case ValType.ID.Ref: - case ValType.ID.RefNull: case ValType.ID.ExnRef: return INT_TYPE; + case ValType.ID.Ref: + case ValType.ID.RefNull: + return type.isGcReference() ? OBJECT_ASM_TYPE : INT_TYPE; case ValType.ID.I64: return LONG_TYPE; case ValType.ID.F32: @@ -98,9 +102,16 @@ public static ValType localType(FunctionType type, FunctionBody body, int localI public static void emitLongToJvm(MethodVisitor asm, ValType type) { switch (type.opcode()) { case ValType.ID.I32: + case ValType.ID.ExnRef: + asm.visitInsn(Opcodes.L2I); + return; case ValType.ID.Ref: case ValType.ID.RefNull: - case ValType.ID.ExnRef: + if (type.isGcReference()) { + // GC refs are Objects - this conversion should not be called for them + // when unboxing from long[]. They come from Object[] instead. + return; + } asm.visitInsn(Opcodes.L2I); return; case ValType.ID.I64: @@ -119,9 +130,16 @@ public static void emitLongToJvm(MethodVisitor asm, ValType type) { public static void emitJvmToLong(MethodVisitor asm, ValType type) { switch (type.opcode()) { case ValType.ID.I32: + case ValType.ID.ExnRef: + asm.visitInsn(Opcodes.I2L); + return; case ValType.ID.Ref: case ValType.ID.RefNull: - case ValType.ID.ExnRef: + if (type.isGcReference()) { + // GC refs are Objects - this conversion should not be called for them + // when boxing into long[]. They go into Object[] instead. + return; + } asm.visitInsn(Opcodes.I2L); return; case ValType.ID.I64: @@ -138,7 +156,9 @@ public static void emitJvmToLong(MethodVisitor asm, ValType type) { } public static MethodType valueMethodType(List types) { - return methodType(long[].class, jvmTypes(types)); + boolean hasGcRef = types.stream().anyMatch(t -> t.isGcReference()); + Class returnType = hasGcRef ? Object[].class : long[].class; + return methodType(returnType, jvmTypes(types)); } public static MethodType callIndirectMethodType(FunctionType functionType) { @@ -161,7 +181,7 @@ public static MethodType rawMethodTypeFor(FunctionType type) { } public static Class[] jvmTypes(List types) { - return types.stream().map(CompilerUtil::jvmType).toArray(Class[]::new); + return types.stream().map(t -> jvmType(t)).toArray(Class[]::new); } public static Class[] jvmParameterTypes(FunctionType type) { @@ -175,10 +195,28 @@ public static Class jvmReturnType(FunctionType type) { case 1: return jvmType(type.returns().get(0)); default: + // If any return value is a GC ref (Object), use Object[] instead of long[] + for (ValType ret : type.returns()) { + if (ret.isGcReference()) { + return Object[].class; + } + } return long[].class; } } + /** + * Returns true if a multi-value return contains any GC references. + */ + public static boolean hasGcRefReturns(FunctionType type) { + for (ValType ret : type.returns()) { + if (ret.isGcReference()) { + return true; + } + } + return false; + } + public static Object defaultValue(ValType type) { switch (type.opcode()) { case ValType.ID.I32: @@ -191,6 +229,10 @@ public static Object defaultValue(ValType type) { return 0.0d; case ValType.ID.Ref: case ValType.ID.RefNull: + if (type.isGcReference()) { + return null; // GC refs use null as their default + } + return REF_NULL_VALUE; case ValType.ID.ExnRef: return REF_NULL_VALUE; default: @@ -198,6 +240,14 @@ public static Object defaultValue(ValType type) { } } + /** + * Returns true if the given ValType is a GC reference type (struct/array/i31/anyref/eqref), + * which is represented as Object on the JVM. + */ + public static boolean isGcRef(ValType type) { + return type.isGcReference(); + } + public static int slotCount(ValType type) { return slotCount(type.id()); } @@ -220,7 +270,11 @@ public static int slotCount(long valTypeId) { } public static void emitPop(MethodVisitor asm, ValType type) { - asm.visitInsn(slotCount(type) == 1 ? Opcodes.POP : Opcodes.POP2); + if (type.isGcReference()) { + asm.visitInsn(Opcodes.POP); // Object refs are always 1 slot + } else { + asm.visitInsn(slotCount(type) == 1 ? Opcodes.POP : Opcodes.POP2); + } } public static void emitInvokeStatic(MethodVisitor asm, Method method) { diff --git a/compiler/src/main/java/run/endive/compiler/internal/Emitters.java b/compiler/src/main/java/run/endive/compiler/internal/Emitters.java index 40e76825..d92b9880 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/Emitters.java +++ b/compiler/src/main/java/run/endive/compiler/internal/Emitters.java @@ -173,6 +173,84 @@ private static void emitBoxValuesOnStack( } } + /** + * Builds both long[] and Object[] from field values on the JVM stack. + * Numeric fields go into long[], GC ref fields go into Object[]. + * Both arrays are indexed by field index. + * After this method, the stack has: [..., long[], Object[]] + */ + private static void emitBoxFieldsForStruct( + Context ctx, InstructionAdapter asm, List types) { + + // Store values from stack to locals in reverse order + int slot = ctx.tempSlot() + types.stream().mapToInt(CompilerUtil::slotCount).sum(); + for (int i = types.size() - 1; i >= 0; i--) { + ValType valType = types.get(i); + slot -= slotCount(valType); + asm.store(slot, asmType(valType)); + } + + // Create long[] for numeric fields + asm.iconst(types.size()); + asm.newarray(LONG_TYPE); + + slot = ctx.tempSlot(); + for (int i = 0; i < types.size(); i++) { + ValType valType = types.get(i); + if (!valType.isGcReference()) { + asm.dup(); + asm.iconst(i); + asm.load(slot, asmType(valType)); + emitJvmToLong(asm, valType); + asm.astore(LONG_TYPE); + } + slot += slotCount(valType); + } + + // Create Object[] for ref fields + asm.iconst(types.size()); + asm.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object"); + + slot = ctx.tempSlot(); + for (int i = 0; i < types.size(); i++) { + ValType valType = types.get(i); + if (valType.isGcReference()) { + asm.dup(); + asm.iconst(i); + asm.load(slot, asmType(valType)); + asm.astore(OBJECT_TYPE); + } + slot += slotCount(valType); + } + } + + /** + * Boxes ref-typed element values into Object[]. + * After this method, the stack has: [..., Object[]] + */ + private static void emitBoxRefsOnStack(Context ctx, InstructionAdapter asm, int count) { + + // Store values from stack to locals in reverse order + int slot = ctx.tempSlot() + count; // Object refs are 1 slot each + for (int i = count - 1; i >= 0; i--) { + slot--; + asm.store(slot, OBJECT_TYPE); + } + + // Create Object[] + asm.iconst(count); + asm.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object"); + + slot = ctx.tempSlot(); + for (int i = 0; i < count; i++) { + asm.dup(); + asm.iconst(i); + asm.load(slot, OBJECT_TYPE); + asm.astore(OBJECT_TYPE); + slot++; + } + } + public static void CALL(Context ctx, CompilerInstruction ins, InstructionAdapter asm) { int funcId = (int) ins.operand(0); FunctionType functionType = ctx.functionTypes().get(funcId); @@ -233,21 +311,43 @@ public static void REF_FUNC(Context ctx, CompilerInstruction ins, InstructionAda } public static void REF_NULL(Context ctx, CompilerInstruction ins, InstructionAdapter asm) { - asm.iconst(REF_NULL_VALUE); + // operand(0) is the heap type index + var type = + ValType.builder() + .withOpcode(ValType.ID.RefNull) + .withTypeIdx((int) ins.operand(0)) + .build() + .resolve(ctx.typeSection()); + if (type.isGcReference()) { + asm.aconst(null); + } else { + asm.iconst(REF_NULL_VALUE); + } } public static void REF_IS_NULL(Context ctx, CompilerInstruction ins, InstructionAdapter asm) { - emitInvokeStatic(asm, ShadedRefs.REF_IS_NULL); + // operand(0) is the ValType id of the ref on stack + var type = valType(ins.operand(0), ctx); + if (type.isGcReference()) { + emitInvokeStatic(asm, ShadedRefs.GC_REF_IS_NULL); + } else { + emitInvokeStatic(asm, ShadedRefs.REF_IS_NULL); + } } public static void REF_EQ(Context ctx, CompilerInstruction ins, InstructionAdapter asm) { - asm.load(ctx.instanceSlot(), OBJECT_TYPE); + // stack: [Object, Object] -> [int] emitInvokeStatic(asm, ShadedRefs.REF_EQ); } public static void REF_AS_NON_NULL( Context ctx, CompilerInstruction ins, InstructionAdapter asm) { - emitInvokeStatic(asm, ShadedRefs.REF_AS_NON_NULL); + var type = valType(ins.operand(0), ctx); + if (type.isGcReference()) { + emitInvokeStatic(asm, ShadedRefs.GC_REF_AS_NON_NULL); + } else { + emitInvokeStatic(asm, ShadedRefs.REF_AS_NON_NULL); + } } public static void LOCAL_GET(Context ctx, CompilerInstruction ins, InstructionAdapter asm) { @@ -279,10 +379,11 @@ public static void GLOBAL_GET(Context ctx, CompilerInstruction ins, InstructionA asm.iconst(globalIndex); asm.load(ctx.instanceSlot(), OBJECT_TYPE); - if (globalType.isReference()) { - // Use readGlobalRef to handle i31 tagged-long values from constant initializers + if (globalType.isGcReference()) { + // GC refs: returns Object directly emitInvokeStatic(asm, ShadedRefs.READ_GLOBAL_REF); } else { + // Numeric types and non-GC refs (funcref, externref, exnref) emitInvokeStatic(asm, ShadedRefs.READ_GLOBAL); emitLongToJvm(asm, globalType); } @@ -290,11 +391,19 @@ public static void GLOBAL_GET(Context ctx, CompilerInstruction ins, InstructionA public static void GLOBAL_SET(Context ctx, CompilerInstruction ins, InstructionAdapter asm) { int globalIndex = (int) ins.operand(0); + var globalType = ctx.globalTypes().get(globalIndex); - emitJvmToLong(asm, ctx.globalTypes().get(globalIndex)); - asm.iconst(globalIndex); - asm.load(ctx.instanceSlot(), OBJECT_TYPE); - emitInvokeStatic(asm, ShadedRefs.WRITE_GLOBAL); + if (globalType.isGcReference()) { + // GC refs: Object on stack + asm.iconst(globalIndex); + asm.load(ctx.instanceSlot(), OBJECT_TYPE); + emitInvokeStatic(asm, ShadedRefs.WRITE_GLOBAL_REF); + } else { + emitJvmToLong(asm, globalType); + asm.iconst(globalIndex); + asm.load(ctx.instanceSlot(), OBJECT_TYPE); + emitInvokeStatic(asm, ShadedRefs.WRITE_GLOBAL); + } } public static void TABLE_GET(Context ctx, CompilerInstruction ins, InstructionAdapter asm) { @@ -1074,7 +1183,10 @@ private static void emitDefaultReturn(Context ctx, InstructionAdapter asm) { asm.areturn(getType(void.class)); } else if (type.returns().size() == 1) { Object defaultVal = CompilerUtil.defaultValue(type.returns().get(0)); - if (defaultVal instanceof Integer) { + if (defaultVal == null) { + // GC ref types default to null (Object) + asm.aconst(null); + } else if (defaultVal instanceof Integer) { asm.iconst((int) defaultVal); } else if (defaultVal instanceof Long) { asm.lconst((long) defaultVal); @@ -1359,7 +1471,7 @@ public static void STRUCT_NEW(Context ctx, CompilerInstruction ins, InstructionA var st = ctx.typeSection().getSubType(typeIdx).compType().structType(); var fieldCount = st.fieldTypes().length; - // Collect all field values into long[] + // Collect field types var fieldTypes = new java.util.ArrayList(fieldCount); for (int i = 0; i < fieldCount; i++) { var ft = st.fieldTypes()[i]; @@ -1370,7 +1482,9 @@ public static void STRUCT_NEW(Context ctx, CompilerInstruction ins, InstructionA fieldTypes.add(ValType.I32); } } - emitBoxValuesOnStack(ctx, asm, fieldTypes); + + // Build both long[] (numeric fields) and Object[] (ref fields) + emitBoxFieldsForStruct(ctx, asm, fieldTypes); asm.iconst(typeIdx); asm.load(ctx.instanceSlot(), OBJECT_TYPE); @@ -1388,13 +1502,22 @@ public static void STRUCT_NEW_DEFAULT( public static void STRUCT_GET(Context ctx, CompilerInstruction ins, InstructionAdapter asm) { int typeIdx = (int) ins.operand(0); int fieldIdx = (int) ins.operand(1); - // stack: [ref] + // stack: [ref (Object)] + var st = ctx.typeSection().getSubType(typeIdx).compType().structType(); + var ft = st.fieldTypes()[fieldIdx]; + asm.iconst(typeIdx); asm.iconst(fieldIdx); asm.load(ctx.instanceSlot(), OBJECT_TYPE); - emitInvokeStatic(asm, ShadedRefs.STRUCT_GET); - // returns long, convert to proper JVM type - emitStructGetResult(ctx, asm, typeIdx, fieldIdx); + + if (ft.storageType().isGcReference()) { + // returns Object + emitInvokeStatic(asm, ShadedRefs.STRUCT_GET_REF); + } else { + // returns long, convert to proper JVM type + emitInvokeStatic(asm, ShadedRefs.STRUCT_GET); + emitStructGetResult(ctx, asm, typeIdx, fieldIdx); + } } public static void STRUCT_GET_S(Context ctx, CompilerInstruction ins, InstructionAdapter asm) { @@ -1434,37 +1557,56 @@ private static void emitStructGetResult( public static void STRUCT_SET(Context ctx, CompilerInstruction ins, InstructionAdapter asm) { int typeIdx = (int) ins.operand(0); int fieldIdx = (int) ins.operand(1); - // stack: [ref, val] + // stack: [ref (Object), val] var st = ctx.typeSection().getSubType(typeIdx).compType().structType(); var ft = st.fieldTypes()[fieldIdx]; - if (ft.storageType().valType() != null) { - emitJvmToLong(asm, ft.storageType().valType()); + + if (ft.storageType().isGcReference()) { + // val is Object on stack + asm.iconst(typeIdx); + asm.iconst(fieldIdx); + asm.load(ctx.instanceSlot(), OBJECT_TYPE); + emitInvokeStatic(asm, ShadedRefs.STRUCT_SET_REF); } else { - // packed types come as I32 - asm.visitInsn(Opcodes.I2L); + if (ft.storageType().valType() != null) { + emitJvmToLong(asm, ft.storageType().valType()); + } else { + // packed types come as I32 + asm.visitInsn(Opcodes.I2L); + } + asm.iconst(typeIdx); + asm.iconst(fieldIdx); + asm.load(ctx.instanceSlot(), OBJECT_TYPE); + emitInvokeStatic(asm, ShadedRefs.STRUCT_SET); } - asm.iconst(typeIdx); - asm.iconst(fieldIdx); - asm.load(ctx.instanceSlot(), OBJECT_TYPE); - emitInvokeStatic(asm, ShadedRefs.STRUCT_SET); } public static void ARRAY_NEW(Context ctx, CompilerInstruction ins, InstructionAdapter asm) { int typeIdx = (int) ins.operand(0); - // stack: [initVal, len] - // save len to temp - asm.store(ctx.tempSlot(), INT_TYPE); - // convert initVal to long var at = ctx.typeSection().getSubType(typeIdx).compType().arrayType(); - if (at.fieldType().storageType().valType() != null) { - emitJvmToLong(asm, at.fieldType().storageType().valType()); + + if (at.fieldType().storageType().isGcReference()) { + // stack: [initVal (Object), len] + asm.store(ctx.tempSlot(), INT_TYPE); + // initVal is Object on stack + asm.load(ctx.tempSlot(), INT_TYPE); + asm.iconst(typeIdx); + asm.load(ctx.instanceSlot(), OBJECT_TYPE); + emitInvokeStatic(asm, ShadedRefs.ARRAY_NEW_REF); } else { - asm.visitInsn(Opcodes.I2L); + // stack: [initVal, len] + asm.store(ctx.tempSlot(), INT_TYPE); + // convert initVal to long + if (at.fieldType().storageType().valType() != null) { + emitJvmToLong(asm, at.fieldType().storageType().valType()); + } else { + asm.visitInsn(Opcodes.I2L); + } + asm.load(ctx.tempSlot(), INT_TYPE); + asm.iconst(typeIdx); + asm.load(ctx.instanceSlot(), OBJECT_TYPE); + emitInvokeStatic(asm, ShadedRefs.ARRAY_NEW); } - asm.load(ctx.tempSlot(), INT_TYPE); - asm.iconst(typeIdx); - asm.load(ctx.instanceSlot(), OBJECT_TYPE); - emitInvokeStatic(asm, ShadedRefs.ARRAY_NEW); } public static void ARRAY_NEW_DEFAULT( @@ -1481,20 +1623,29 @@ public static void ARRAY_NEW_FIXED( int typeIdx = (int) ins.operand(0); int len = (int) ins.operand(1); var at = ctx.typeSection().getSubType(typeIdx).compType().arrayType(); - ValType elemType; - if (at.fieldType().storageType().valType() != null) { - elemType = at.fieldType().storageType().valType(); + + if (at.fieldType().storageType().isGcReference()) { + // Elements are Object refs, box into Object[] + emitBoxRefsOnStack(ctx, asm, len); + asm.iconst(typeIdx); + asm.load(ctx.instanceSlot(), OBJECT_TYPE); + emitInvokeStatic(asm, ShadedRefs.ARRAY_NEW_FIXED_REFS); } else { - elemType = ValType.I32; - } - var types = new java.util.ArrayList(len); - for (int i = 0; i < len; i++) { - types.add(elemType); + ValType elemType; + if (at.fieldType().storageType().valType() != null) { + elemType = at.fieldType().storageType().valType(); + } else { + elemType = ValType.I32; + } + var types = new java.util.ArrayList(len); + for (int i = 0; i < len; i++) { + types.add(elemType); + } + emitBoxValuesOnStack(ctx, asm, types); + asm.iconst(typeIdx); + asm.load(ctx.instanceSlot(), OBJECT_TYPE); + emitInvokeStatic(asm, ShadedRefs.ARRAY_NEW_FIXED); } - emitBoxValuesOnStack(ctx, asm, types); - asm.iconst(typeIdx); - asm.load(ctx.instanceSlot(), OBJECT_TYPE); - emitInvokeStatic(asm, ShadedRefs.ARRAY_NEW_FIXED); } public static void ARRAY_NEW_DATA( @@ -1528,11 +1679,17 @@ public static void ARRAY_NEW_ELEM( public static void ARRAY_GET(Context ctx, CompilerInstruction ins, InstructionAdapter asm) { int typeIdx = (int) ins.operand(0); - // stack: [ref, idx] + var at = ctx.typeSection().getSubType(typeIdx).compType().arrayType(); + // stack: [ref (Object), idx] asm.iconst(typeIdx); asm.load(ctx.instanceSlot(), OBJECT_TYPE); - emitInvokeStatic(asm, ShadedRefs.ARRAY_GET); - emitArrayGetResult(ctx, asm, typeIdx); + + if (at.fieldType().storageType().isGcReference()) { + emitInvokeStatic(asm, ShadedRefs.ARRAY_GET_REF); + } else { + emitInvokeStatic(asm, ShadedRefs.ARRAY_GET); + emitArrayGetResult(ctx, asm, typeIdx); + } } public static void ARRAY_GET_S(Context ctx, CompilerInstruction ins, InstructionAdapter asm) { @@ -1562,16 +1719,24 @@ private static void emitArrayGetResult(Context ctx, InstructionAdapter asm, int public static void ARRAY_SET(Context ctx, CompilerInstruction ins, InstructionAdapter asm) { int typeIdx = (int) ins.operand(0); - // stack: [ref, idx, val] + // stack: [ref (Object), idx, val] var at = ctx.typeSection().getSubType(typeIdx).compType().arrayType(); - if (at.fieldType().storageType().valType() != null) { - emitJvmToLong(asm, at.fieldType().storageType().valType()); + + if (at.fieldType().storageType().isGcReference()) { + // val is Object on stack + asm.iconst(typeIdx); + asm.load(ctx.instanceSlot(), OBJECT_TYPE); + emitInvokeStatic(asm, ShadedRefs.ARRAY_SET_REF); } else { - asm.visitInsn(Opcodes.I2L); + if (at.fieldType().storageType().valType() != null) { + emitJvmToLong(asm, at.fieldType().storageType().valType()); + } else { + asm.visitInsn(Opcodes.I2L); + } + asm.iconst(typeIdx); + asm.load(ctx.instanceSlot(), OBJECT_TYPE); + emitInvokeStatic(asm, ShadedRefs.ARRAY_SET); } - asm.iconst(typeIdx); - asm.load(ctx.instanceSlot(), OBJECT_TYPE); - emitInvokeStatic(asm, ShadedRefs.ARRAY_SET); } public static void ARRAY_LEN(Context ctx, CompilerInstruction ins, InstructionAdapter asm) { @@ -1582,21 +1747,30 @@ public static void ARRAY_LEN(Context ctx, CompilerInstruction ins, InstructionAd public static void ARRAY_FILL(Context ctx, CompilerInstruction ins, InstructionAdapter asm) { int typeIdx = (int) ins.operand(0); - // stack: [ref, offset, val, len] - // save len to temp - asm.store(ctx.tempSlot(), INT_TYPE); - // stack: [ref, offset, val] var at = ctx.typeSection().getSubType(typeIdx).compType().arrayType(); - if (at.fieldType().storageType().valType() != null) { - emitJvmToLong(asm, at.fieldType().storageType().valType()); + + if (at.fieldType().storageType().isGcReference()) { + // stack: [ref (Object), offset, val (Object), len] + asm.store(ctx.tempSlot(), INT_TYPE); + // stack: [ref, offset, val] + // val is already Object + asm.load(ctx.tempSlot(), INT_TYPE); + asm.iconst(typeIdx); + asm.load(ctx.instanceSlot(), OBJECT_TYPE); + emitInvokeStatic(asm, ShadedRefs.ARRAY_FILL_REF); } else { - asm.visitInsn(Opcodes.I2L); + // stack: [ref (Object), offset, val, len] + asm.store(ctx.tempSlot(), INT_TYPE); + if (at.fieldType().storageType().valType() != null) { + emitJvmToLong(asm, at.fieldType().storageType().valType()); + } else { + asm.visitInsn(Opcodes.I2L); + } + asm.load(ctx.tempSlot(), INT_TYPE); + asm.iconst(typeIdx); + asm.load(ctx.instanceSlot(), OBJECT_TYPE); + emitInvokeStatic(asm, ShadedRefs.ARRAY_FILL); } - // stack: [ref, offset, val_as_long] - asm.load(ctx.tempSlot(), INT_TYPE); - asm.iconst(typeIdx); - asm.load(ctx.instanceSlot(), OBJECT_TYPE); - emitInvokeStatic(asm, ShadedRefs.ARRAY_FILL); } public static void ARRAY_COPY(Context ctx, CompilerInstruction ins, InstructionAdapter asm) { @@ -1666,20 +1840,18 @@ public static void CAST_TEST_NULL( } public static void REF_I31(Context ctx, CompilerInstruction ins, InstructionAdapter asm) { - // stack: [val] - asm.load(ctx.instanceSlot(), OBJECT_TYPE); + // stack: [val (int)] emitInvokeStatic(asm, ShadedRefs.REF_I31); + // returns Object (WasmI31Ref) } public static void I31_GET_S(Context ctx, CompilerInstruction ins, InstructionAdapter asm) { - // stack: [ref] - asm.load(ctx.instanceSlot(), OBJECT_TYPE); + // stack: [ref (Object)] emitInvokeStatic(asm, ShadedRefs.I31_GET_S); } public static void I31_GET_U(Context ctx, CompilerInstruction ins, InstructionAdapter asm) { - // stack: [ref] - asm.load(ctx.instanceSlot(), OBJECT_TYPE); + // stack: [ref (Object)] emitInvokeStatic(asm, ShadedRefs.I31_GET_U); } @@ -1695,16 +1867,17 @@ public static void EXTERN_CONVERT_ANY( public static void BR_ON_NULL_CHECK( Context ctx, CompilerInstruction ins, InstructionAdapter asm) { - // stack: [ref] - // DUP the ref, compare against REF_NULL_VALUE + var type = valType(ins.operand(0), ctx); + boolean isGcRef = type.isGcReference(); asm.dup(); - asm.iconst(REF_NULL_VALUE); - // result is used by following IFEQ/IFNE - // if ref == null -> 0 (equal), used with IFEQ to branch - // we want: push 1 if null, 0 if not null var isNull = new Label(); var end = new Label(); - asm.ificmpeq(isNull); + if (isGcRef) { + asm.ifnull(isNull); + } else { + asm.iconst(REF_NULL_VALUE); + asm.ificmpeq(isNull); + } asm.iconst(0); // not null asm.goTo(end); asm.mark(isNull); @@ -1714,12 +1887,17 @@ public static void BR_ON_NULL_CHECK( public static void BR_ON_NON_NULL_CHECK( Context ctx, CompilerInstruction ins, InstructionAdapter asm) { - // stack: [ref] + var type = valType(ins.operand(0), ctx); + boolean isGcRef = type.isGcReference(); asm.dup(); - asm.iconst(REF_NULL_VALUE); var isNull = new Label(); var end = new Label(); - asm.ificmpeq(isNull); + if (isGcRef) { + asm.ifnull(isNull); + } else { + asm.iconst(REF_NULL_VALUE); + asm.ificmpeq(isNull); + } asm.iconst(1); // non-null asm.goTo(end); asm.mark(isNull); diff --git a/compiler/src/main/java/run/endive/compiler/internal/Shaded.java b/compiler/src/main/java/run/endive/compiler/internal/Shaded.java index 38107d09..ce52d02f 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/Shaded.java +++ b/compiler/src/main/java/run/endive/compiler/internal/Shaded.java @@ -85,6 +85,10 @@ public static boolean isRefNull(int ref) { return ref == REF_NULL_VALUE; } + public static boolean isGcRefNull(Object ref) { + return ref == null; + } + public static int refAsNonNull(int ref) { if (ref == REF_NULL_VALUE) { throw new TrapException("null reference"); @@ -92,6 +96,13 @@ public static int refAsNonNull(int ref) { return ref; } + public static Object gcRefAsNonNull(Object ref) { + if (ref == null) { + throw new TrapException("null reference"); + } + return ref; + } + public static int tableGet(int index, int tableIndex, Instance instance) { return OpcodeImpl.TABLE_GET(instance, tableIndex, index); } @@ -378,13 +389,12 @@ public static void writeGlobal(long value, int index, Instance instance) { instance.global(index).setValue(value); } - public static int readGlobalRef(int index, Instance instance) { - long val = instance.global(index).getValue(); - if (Value.isI31(val)) { - var i31 = new WasmI31Ref(Value.decodeI31U(val)); - return instance.registerGcRef(i31); - } - return (int) val; + public static void writeGlobalRef(Object value, int index, Instance instance) { + instance.global(index).setRefValue(value); + } + + public static Object readGlobalRef(int index, Instance instance) { + return instance.global(index).getRefValue(); } /** @@ -810,37 +820,40 @@ public static void memoryAtomicFence(Memory memory) { // ========= GC Operations ========= - public static int structNew(long[] fields, int typeIdx, Instance instance) { - var struct = new WasmStruct(typeIdx, fields); - return instance.registerGcRef(struct); + public static Object structNew( + long[] fields, Object[] fieldRefs, int typeIdx, Instance instance) { + return new WasmStruct(typeIdx, fields, fieldRefs); } - public static int structNewDefault(int typeIdx, Instance instance) { + public static Object structNewDefault(int typeIdx, Instance instance) { var st = instance.module().typeSection().getSubType(typeIdx).compType().structType(); var fields = new long[st.fieldTypes().length]; - for (int i = 0; i < fields.length; i++) { - var ft = st.fieldTypes()[i]; - if (ft.storageType().valType() != null && ft.storageType().valType().isReference()) { - fields[i] = Value.REF_NULL_VALUE; - } - } - var struct = new WasmStruct(typeIdx, fields); - return instance.registerGcRef(struct); + var fieldRefs = new Object[st.fieldTypes().length]; + // numeric fields default to 0 (already), ref fields default to null (already) + return new WasmStruct(typeIdx, fields, fieldRefs); } - public static long structGet(int ref, int typeIdx, int fieldIdx, Instance instance) { - if (ref == REF_NULL_VALUE) { + public static long structGet(Object ref, int typeIdx, int fieldIdx, Instance instance) { + if (ref == null) { throw new TrapException("null structure reference"); } - var struct = (WasmStruct) instance.gcRef(ref); + var struct = (WasmStruct) ref; return struct.field(fieldIdx); } - public static long structGetS(int ref, int typeIdx, int fieldIdx, Instance instance) { - if (ref == REF_NULL_VALUE) { + public static Object structGetRef(Object ref, int typeIdx, int fieldIdx, Instance instance) { + if (ref == null) { throw new TrapException("null structure reference"); } - var struct = (WasmStruct) instance.gcRef(ref); + var struct = (WasmStruct) ref; + return struct.fieldRef(fieldIdx); + } + + public static long structGetS(Object ref, int typeIdx, int fieldIdx, Instance instance) { + if (ref == null) { + throw new TrapException("null structure reference"); + } + var struct = (WasmStruct) ref; var val = struct.field(fieldIdx); var st = instance.module().typeSection().getSubType(typeIdx).compType().structType(); var ft = st.fieldTypes()[fieldIdx]; @@ -850,11 +863,11 @@ public static long structGetS(int ref, int typeIdx, int fieldIdx, Instance insta return val; } - public static long structGetU(int ref, int typeIdx, int fieldIdx, Instance instance) { - if (ref == REF_NULL_VALUE) { + public static long structGetU(Object ref, int typeIdx, int fieldIdx, Instance instance) { + if (ref == null) { throw new TrapException("null structure reference"); } - var struct = (WasmStruct) instance.gcRef(ref); + var struct = (WasmStruct) ref; var val = struct.field(fieldIdx); var st = instance.module().typeSection().getSubType(typeIdx).compType().structType(); var ft = st.fieldTypes()[fieldIdx]; @@ -864,11 +877,12 @@ public static long structGetU(int ref, int typeIdx, int fieldIdx, Instance insta return val; } - public static void structSet(int ref, long val, int typeIdx, int fieldIdx, Instance instance) { - if (ref == REF_NULL_VALUE) { + public static void structSet( + Object ref, long val, int typeIdx, int fieldIdx, Instance instance) { + if (ref == null) { throw new TrapException("null structure reference"); } - var struct = (WasmStruct) instance.gcRef(ref); + var struct = (WasmStruct) ref; var st = instance.module().typeSection().getSubType(typeIdx).compType().structType(); var ft = st.fieldTypes()[fieldIdx]; if (ft.storageType().packedType() != null) { @@ -877,30 +891,45 @@ public static void structSet(int ref, long val, int typeIdx, int fieldIdx, Insta struct.setField(fieldIdx, val); } - public static int arrayNew(long initVal, int len, int typeIdx, Instance instance) { + public static void structSetRef( + Object ref, Object val, int typeIdx, int fieldIdx, Instance instance) { + if (ref == null) { + throw new TrapException("null structure reference"); + } + var struct = (WasmStruct) ref; + struct.setFieldRef(fieldIdx, val); + } + + public static Object arrayNew(long initVal, int len, int typeIdx, Instance instance) { var elems = new long[len]; Arrays.fill(elems, initVal); - var arr = new WasmArray(typeIdx, elems); - return instance.registerGcRef(arr); + return new WasmArray(typeIdx, elems); } - public static int arrayNewDefault(int len, int typeIdx, Instance instance) { - var at = instance.module().typeSection().getSubType(typeIdx).compType().arrayType(); + public static Object arrayNewRef(Object initVal, int len, int typeIdx, Instance instance) { var elems = new long[len]; - if (at.fieldType().storageType().valType() != null - && at.fieldType().storageType().valType().isReference()) { - Arrays.fill(elems, Value.REF_NULL_VALUE); - } - var arr = new WasmArray(typeIdx, elems); - return instance.registerGcRef(arr); + var elemRefs = new Object[len]; + Arrays.fill(elemRefs, initVal); + return new WasmArray(typeIdx, elems, elemRefs); + } + + public static Object arrayNewDefault(int len, int typeIdx, Instance instance) { + var elems = new long[len]; + var elemRefs = new Object[len]; + // numeric defaults to 0 (already), ref defaults to null (already) + return new WasmArray(typeIdx, elems, elemRefs); + } + + public static Object arrayNewFixed(long[] vals, int typeIdx, Instance instance) { + return new WasmArray(typeIdx, vals); } - public static int arrayNewFixed(long[] vals, int typeIdx, Instance instance) { - var arr = new WasmArray(typeIdx, vals); - return instance.registerGcRef(arr); + public static Object arrayNewFixedRefs(Object[] vals, int typeIdx, Instance instance) { + var elems = new long[vals.length]; + return new WasmArray(typeIdx, elems, vals); } - public static int arrayNewData( + public static Object arrayNewData( int offset, int len, int typeIdx, int dataIdx, Instance instance) { var at = instance.module().typeSection().getSubType(typeIdx).compType().arrayType(); var elemSize = at.fieldType().storageType().byteSize(); @@ -913,11 +942,10 @@ public static int arrayNewData( var byteOff = offset + i * elemSize; elems[i] = readFromData(data, byteOff, elemSize); } - var arr = new WasmArray(typeIdx, elems); - return instance.registerGcRef(arr); + return new WasmArray(typeIdx, elems); } - public static int arrayNewElem( + public static Object arrayNewElem( int offset, int len, int typeIdx, int elemIdx, Instance instance) { var element = instance.element(elemIdx); if (element == null || offset + len > element.elementCount()) { @@ -928,26 +956,36 @@ public static int arrayNewElem( elems[i] = elementValueToRef(computeElementValue(instance, elemIdx, offset + i), instance); } - var arr = new WasmArray(typeIdx, elems); - return instance.registerGcRef(arr); + return new WasmArray(typeIdx, elems); } - public static long arrayGet(int ref, int idx, int typeIdx, Instance instance) { - if (ref == REF_NULL_VALUE) { + public static long arrayGet(Object ref, int idx, int typeIdx, Instance instance) { + if (ref == null) { throw new TrapException("null array reference"); } - var arr = (WasmArray) instance.gcRef(ref); + var arr = (WasmArray) ref; if (idx < 0 || idx >= arr.length()) { throw new TrapException("out of bounds array access"); } return arr.get(idx); } - public static long arrayGetS(int ref, int idx, int typeIdx, Instance instance) { - if (ref == REF_NULL_VALUE) { + public static Object arrayGetRef(Object ref, int idx, int typeIdx, Instance instance) { + if (ref == null) { + throw new TrapException("null array reference"); + } + var arr = (WasmArray) ref; + if (idx < 0 || idx >= arr.length()) { + throw new TrapException("out of bounds array access"); + } + return arr.getRef(idx); + } + + public static long arrayGetS(Object ref, int idx, int typeIdx, Instance instance) { + if (ref == null) { throw new TrapException("null array reference"); } - var arr = (WasmArray) instance.gcRef(ref); + var arr = (WasmArray) ref; if (idx < 0 || idx >= arr.length()) { throw new TrapException("out of bounds array access"); } @@ -959,11 +997,11 @@ public static long arrayGetS(int ref, int idx, int typeIdx, Instance instance) { return val; } - public static long arrayGetU(int ref, int idx, int typeIdx, Instance instance) { - if (ref == REF_NULL_VALUE) { + public static long arrayGetU(Object ref, int idx, int typeIdx, Instance instance) { + if (ref == null) { throw new TrapException("null array reference"); } - var arr = (WasmArray) instance.gcRef(ref); + var arr = (WasmArray) ref; if (idx < 0 || idx >= arr.length()) { throw new TrapException("out of bounds array access"); } @@ -975,11 +1013,11 @@ public static long arrayGetU(int ref, int idx, int typeIdx, Instance instance) { return val; } - public static void arraySet(int ref, int idx, long val, int typeIdx, Instance instance) { - if (ref == REF_NULL_VALUE) { + public static void arraySet(Object ref, int idx, long val, int typeIdx, Instance instance) { + if (ref == null) { throw new TrapException("null array reference"); } - var arr = (WasmArray) instance.gcRef(ref); + var arr = (WasmArray) ref; if (idx < 0 || idx >= arr.length()) { throw new TrapException("out of bounds array access"); } @@ -990,20 +1028,32 @@ public static void arraySet(int ref, int idx, long val, int typeIdx, Instance in arr.set(idx, val); } - public static int arrayLen(int ref, Instance instance) { - if (ref == REF_NULL_VALUE) { + public static void arraySetRef( + Object ref, int idx, Object val, int typeIdx, Instance instance) { + if (ref == null) { throw new TrapException("null array reference"); } - var arr = (WasmArray) instance.gcRef(ref); + var arr = (WasmArray) ref; + if (idx < 0 || idx >= arr.length()) { + throw new TrapException("out of bounds array access"); + } + arr.setRef(idx, val); + } + + public static int arrayLen(Object ref, Instance instance) { + if (ref == null) { + throw new TrapException("null array reference"); + } + var arr = (WasmArray) ref; return arr.length(); } public static void arrayFill( - int ref, int offset, long val, int len, int typeIdx, Instance instance) { - if (ref == REF_NULL_VALUE) { + Object ref, int offset, long val, int len, int typeIdx, Instance instance) { + if (ref == null) { throw new TrapException("null array reference"); } - var arr = (WasmArray) instance.gcRef(ref); + var arr = (WasmArray) ref; if (offset + len > arr.length()) { throw new TrapException("out of bounds array access"); } @@ -1016,33 +1066,55 @@ public static void arrayFill( } } + public static void arrayFillRef( + Object ref, int offset, Object val, int len, int typeIdx, Instance instance) { + if (ref == null) { + throw new TrapException("null array reference"); + } + var arr = (WasmArray) ref; + if (offset + len > arr.length()) { + throw new TrapException("out of bounds array access"); + } + for (int i = 0; i < len; i++) { + arr.setRef(offset + i, val); + } + } + public static void arrayCopy( - int dstRef, int dstOff, int srcRef, int srcOff, int len, Instance instance) { - if (dstRef == REF_NULL_VALUE || srcRef == REF_NULL_VALUE) { + Object dstRef, int dstOff, Object srcRef, int srcOff, int len, Instance instance) { + if (dstRef == null || srcRef == null) { throw new TrapException("null array reference"); } - var dst = (WasmArray) instance.gcRef(dstRef); - var src = (WasmArray) instance.gcRef(srcRef); + var dst = (WasmArray) dstRef; + var src = (WasmArray) srcRef; if (dstOff + len > dst.length() || srcOff + len > src.length()) { throw new TrapException("out of bounds array access"); } if (dstOff <= srcOff) { for (int i = 0; i < len; i++) { dst.set(dstOff + i, src.get(srcOff + i)); + dst.setRef(dstOff + i, src.getRef(srcOff + i)); } } else { for (int i = len - 1; i >= 0; i--) { dst.set(dstOff + i, src.get(srcOff + i)); + dst.setRef(dstOff + i, src.getRef(srcOff + i)); } } } public static void arrayInitData( - int ref, int dstOff, int srcOff, int len, int typeIdx, int dataIdx, Instance instance) { - if (ref == REF_NULL_VALUE) { + Object ref, + int dstOff, + int srcOff, + int len, + int typeIdx, + int dataIdx, + Instance instance) { + if (ref == null) { throw new TrapException("null array reference"); } - var arr = (WasmArray) instance.gcRef(ref); + var arr = (WasmArray) ref; var at = instance.module().typeSection().getSubType(typeIdx).compType().arrayType(); var elemSize = at.fieldType().storageType().byteSize(); var data = instance.dataSegmentData(dataIdx); @@ -1059,11 +1131,17 @@ public static void arrayInitData( } public static void arrayInitElem( - int ref, int dstOff, int srcOff, int len, int typeIdx, int elemIdx, Instance instance) { - if (ref == REF_NULL_VALUE) { + Object ref, + int dstOff, + int srcOff, + int len, + int typeIdx, + int elemIdx, + Instance instance) { + if (ref == null) { throw new TrapException("null array reference"); } - var arr = (WasmArray) instance.gcRef(ref); + var arr = (WasmArray) ref; var element = instance.element(elemIdx); if (dstOff + len > arr.length()) { throw new TrapException("out of bounds array access"); @@ -1083,71 +1161,70 @@ public static void arrayInitElem( } } - public static int refTest(int ref, int heapType, int srcHeapType, Instance instance) { - return instance.heapTypeMatch(ref, false, heapType, srcHeapType) ? 1 : 0; + public static int refTest(Object ref, int heapType, int srcHeapType, Instance instance) { + return instance.heapTypeMatchRef(ref, false, heapType, srcHeapType) ? 1 : 0; } - public static int refTestNull(int ref, int heapType, int srcHeapType, Instance instance) { - return instance.heapTypeMatch(ref, true, heapType, srcHeapType) ? 1 : 0; + public static int refTestNull(Object ref, int heapType, int srcHeapType, Instance instance) { + return instance.heapTypeMatchRef(ref, true, heapType, srcHeapType) ? 1 : 0; } - public static int castTest(int ref, int heapType, int srcHeapType, Instance instance) { - if (!instance.heapTypeMatch(ref, false, heapType, srcHeapType)) { + public static Object castTest(Object ref, int heapType, int srcHeapType, Instance instance) { + if (!instance.heapTypeMatchRef(ref, false, heapType, srcHeapType)) { throw new TrapException("cast failure"); } return ref; } - public static int castTestNull(int ref, int heapType, int srcHeapType, Instance instance) { - if (!instance.heapTypeMatch(ref, true, heapType, srcHeapType)) { + public static Object castTestNull( + Object ref, int heapType, int srcHeapType, Instance instance) { + if (!instance.heapTypeMatchRef(ref, true, heapType, srcHeapType)) { throw new TrapException("cast failure"); } return ref; } public static boolean heapTypeMatch( - int ref, boolean nullable, int heapType, int srcHeapType, Instance instance) { - return instance.heapTypeMatch(ref, nullable, heapType, srcHeapType); + Object ref, boolean nullable, int heapType, int srcHeapType, Instance instance) { + return instance.heapTypeMatchRef(ref, nullable, heapType, srcHeapType); } - public static int refI31(int val, Instance instance) { - var i31 = new WasmI31Ref(val & 0x7FFFFFFF); - return instance.registerGcRef(i31); + public static Object refI31(int val) { + return new WasmI31Ref(val & 0x7FFFFFFF); } - public static int i31GetS(int ref, Instance instance) { - if (ref == REF_NULL_VALUE) { + public static int i31GetS(Object ref) { + if (ref == null) { throw new TrapException("null i31 reference"); } - var i31 = (WasmI31Ref) instance.gcRef(ref); + var i31 = (WasmI31Ref) ref; int val = i31.value(); // sign extend from 31 bits return (val << 1) >> 1; } - public static int i31GetU(int ref, Instance instance) { - if (ref == REF_NULL_VALUE) { + public static int i31GetU(Object ref) { + if (ref == null) { throw new TrapException("null i31 reference"); } - var i31 = (WasmI31Ref) instance.gcRef(ref); + var i31 = (WasmI31Ref) ref; return i31.value() & 0x7FFFFFFF; } - public static int refEq(int a, int b, Instance instance) { + public static int refEq(Object a, Object b) { if (a == b) { return 1; } - if (a == REF_NULL_VALUE || b == REF_NULL_VALUE) { + if (a == null || b == null) { return 0; } - var gcA = instance.gcRef(a); - var gcB = instance.gcRef(b); - if (gcA instanceof WasmI31Ref && gcB instanceof WasmI31Ref) { - return ((WasmI31Ref) gcA).value() == ((WasmI31Ref) gcB).value() ? 1 : 0; + if (a instanceof WasmI31Ref && b instanceof WasmI31Ref) { + return ((WasmI31Ref) a).value() == ((WasmI31Ref) b).value() ? 1 : 0; } return 0; } + @SuppressWarnings("deprecation") private static long elementValueToRef(long val, Instance instance) { if (Value.isI31(val)) { var i31 = new WasmI31Ref(Value.decodeI31U(val)); diff --git a/compiler/src/main/java/run/endive/compiler/internal/ShadedRefs.java b/compiler/src/main/java/run/endive/compiler/internal/ShadedRefs.java index 89b5420b..0b135228 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/ShadedRefs.java +++ b/compiler/src/main/java/run/endive/compiler/internal/ShadedRefs.java @@ -19,6 +19,7 @@ public final class ShadedRefs { static final Method READ_GLOBAL; static final Method READ_GLOBAL_REF; static final Method WRITE_GLOBAL; + static final Method WRITE_GLOBAL_REF; static final Method INSTANCE_SET_ELEMENT; static final Method INSTANCE_TABLE; static final Method MEMORY_COPY; @@ -49,7 +50,9 @@ public final class ShadedRefs { static final Method MEMORY_ATOMIC_LONG_READ; static final Method I32_GE_U; static final Method REF_IS_NULL; + static final Method GC_REF_IS_NULL; static final Method REF_AS_NON_NULL; + static final Method GC_REF_AS_NON_NULL; static final Method TABLE_GET; static final Method TABLE_SET; static final Method TABLE_SIZE; @@ -144,20 +147,27 @@ public final class ShadedRefs { static final Method STRUCT_NEW; static final Method STRUCT_NEW_DEFAULT; static final Method STRUCT_GET; + static final Method STRUCT_GET_REF; static final Method STRUCT_GET_S; static final Method STRUCT_GET_U; static final Method STRUCT_SET; + static final Method STRUCT_SET_REF; static final Method ARRAY_NEW; + static final Method ARRAY_NEW_REF; static final Method ARRAY_NEW_DEFAULT; static final Method ARRAY_NEW_FIXED; + static final Method ARRAY_NEW_FIXED_REFS; static final Method ARRAY_NEW_DATA; static final Method ARRAY_NEW_ELEM; static final Method ARRAY_GET; + static final Method ARRAY_GET_REF; static final Method ARRAY_GET_S; static final Method ARRAY_GET_U; static final Method ARRAY_SET; + static final Method ARRAY_SET_REF; static final Method ARRAY_LEN; static final Method ARRAY_FILL; + static final Method ARRAY_FILL_REF; static final Method ARRAY_COPY; static final Method ARRAY_INIT_DATA; static final Method ARRAY_INIT_ELEM; @@ -189,6 +199,9 @@ public final class ShadedRefs { READ_GLOBAL_REF = Shaded.class.getMethod("readGlobalRef", int.class, Instance.class); WRITE_GLOBAL = Shaded.class.getMethod("writeGlobal", long.class, int.class, Instance.class); + WRITE_GLOBAL_REF = + Shaded.class.getMethod( + "writeGlobalRef", Object.class, int.class, Instance.class); INSTANCE_SET_ELEMENT = Instance.class.getMethod("setElement", int.class, Element.class); INSTANCE_TABLE = Instance.class.getMethod("table", int.class); MEMORY_COPY = @@ -264,7 +277,9 @@ public final class ShadedRefs { "memoryAtomicLongIntRead", int.class, int.class, Memory.class); I32_GE_U = Shaded.class.getMethod("i32_ge_u", int.class, int.class); REF_IS_NULL = Shaded.class.getMethod("isRefNull", int.class); + GC_REF_IS_NULL = Shaded.class.getMethod("isGcRefNull", Object.class); REF_AS_NON_NULL = Shaded.class.getMethod("refAsNonNull", int.class); + GC_REF_AS_NON_NULL = Shaded.class.getMethod("gcRefAsNonNull", Object.class); TABLE_GET = Shaded.class.getMethod("tableGet", int.class, int.class, Instance.class); TABLE_SET = Shaded.class.getMethod( @@ -736,34 +751,52 @@ public final class ShadedRefs { // GC STRUCT_NEW = - Shaded.class.getMethod("structNew", long[].class, int.class, Instance.class); + Shaded.class.getMethod( + "structNew", long[].class, Object[].class, int.class, Instance.class); STRUCT_NEW_DEFAULT = Shaded.class.getMethod("structNewDefault", int.class, Instance.class); STRUCT_GET = Shaded.class.getMethod( - "structGet", int.class, int.class, int.class, Instance.class); + "structGet", Object.class, int.class, int.class, Instance.class); + STRUCT_GET_REF = + Shaded.class.getMethod( + "structGetRef", Object.class, int.class, int.class, Instance.class); STRUCT_GET_S = Shaded.class.getMethod( - "structGetS", int.class, int.class, int.class, Instance.class); + "structGetS", Object.class, int.class, int.class, Instance.class); STRUCT_GET_U = Shaded.class.getMethod( - "structGetU", int.class, int.class, int.class, Instance.class); + "structGetU", Object.class, int.class, int.class, Instance.class); STRUCT_SET = Shaded.class.getMethod( "structSet", - int.class, + Object.class, long.class, int.class, int.class, Instance.class); + STRUCT_SET_REF = + Shaded.class.getMethod( + "structSetRef", + Object.class, + Object.class, + int.class, + int.class, + Instance.class); ARRAY_NEW = Shaded.class.getMethod( "arrayNew", long.class, int.class, int.class, Instance.class); + ARRAY_NEW_REF = + Shaded.class.getMethod( + "arrayNewRef", Object.class, int.class, int.class, Instance.class); ARRAY_NEW_DEFAULT = Shaded.class.getMethod("arrayNewDefault", int.class, int.class, Instance.class); ARRAY_NEW_FIXED = Shaded.class.getMethod( "arrayNewFixed", long[].class, int.class, Instance.class); + ARRAY_NEW_FIXED_REFS = + Shaded.class.getMethod( + "arrayNewFixedRefs", Object[].class, int.class, Instance.class); ARRAY_NEW_DATA = Shaded.class.getMethod( "arrayNewData", @@ -782,44 +815,64 @@ public final class ShadedRefs { Instance.class); ARRAY_GET = Shaded.class.getMethod( - "arrayGet", int.class, int.class, int.class, Instance.class); + "arrayGet", Object.class, int.class, int.class, Instance.class); + ARRAY_GET_REF = + Shaded.class.getMethod( + "arrayGetRef", Object.class, int.class, int.class, Instance.class); ARRAY_GET_S = Shaded.class.getMethod( - "arrayGetS", int.class, int.class, int.class, Instance.class); + "arrayGetS", Object.class, int.class, int.class, Instance.class); ARRAY_GET_U = Shaded.class.getMethod( - "arrayGetU", int.class, int.class, int.class, Instance.class); + "arrayGetU", Object.class, int.class, int.class, Instance.class); ARRAY_SET = Shaded.class.getMethod( "arraySet", - int.class, + Object.class, int.class, long.class, int.class, Instance.class); - ARRAY_LEN = Shaded.class.getMethod("arrayLen", int.class, Instance.class); + ARRAY_SET_REF = + Shaded.class.getMethod( + "arraySetRef", + Object.class, + int.class, + Object.class, + int.class, + Instance.class); + ARRAY_LEN = Shaded.class.getMethod("arrayLen", Object.class, Instance.class); ARRAY_FILL = Shaded.class.getMethod( "arrayFill", - int.class, + Object.class, int.class, long.class, int.class, int.class, Instance.class); - ARRAY_COPY = + ARRAY_FILL_REF = Shaded.class.getMethod( - "arrayCopy", + "arrayFillRef", + Object.class, int.class, + Object.class, int.class, int.class, + Instance.class); + ARRAY_COPY = + Shaded.class.getMethod( + "arrayCopy", + Object.class, + int.class, + Object.class, int.class, int.class, Instance.class); ARRAY_INIT_DATA = Shaded.class.getMethod( "arrayInitData", - int.class, + Object.class, int.class, int.class, int.class, @@ -829,7 +882,7 @@ public final class ShadedRefs { ARRAY_INIT_ELEM = Shaded.class.getMethod( "arrayInitElem", - int.class, + Object.class, int.class, int.class, int.class, @@ -838,28 +891,28 @@ public final class ShadedRefs { Instance.class); REF_TEST = Shaded.class.getMethod( - "refTest", int.class, int.class, int.class, Instance.class); + "refTest", Object.class, int.class, int.class, Instance.class); REF_TEST_NULL = Shaded.class.getMethod( - "refTestNull", int.class, int.class, int.class, Instance.class); + "refTestNull", Object.class, int.class, int.class, Instance.class); CAST_TEST = Shaded.class.getMethod( - "castTest", int.class, int.class, int.class, Instance.class); + "castTest", Object.class, int.class, int.class, Instance.class); CAST_TEST_NULL = Shaded.class.getMethod( - "castTestNull", int.class, int.class, int.class, Instance.class); + "castTestNull", Object.class, int.class, int.class, Instance.class); HEAP_TYPE_MATCH = Shaded.class.getMethod( "heapTypeMatch", - int.class, + Object.class, boolean.class, int.class, int.class, Instance.class); - REF_EQ = Shaded.class.getMethod("refEq", int.class, int.class, Instance.class); - REF_I31 = Shaded.class.getMethod("refI31", int.class, Instance.class); - I31_GET_S = Shaded.class.getMethod("i31GetS", int.class, Instance.class); - I31_GET_U = Shaded.class.getMethod("i31GetU", int.class, Instance.class); + REF_EQ = Shaded.class.getMethod("refEq", Object.class, Object.class); + REF_I31 = Shaded.class.getMethod("refI31", int.class); + I31_GET_S = Shaded.class.getMethod("i31GetS", Object.class); + I31_GET_U = Shaded.class.getMethod("i31GetU", Object.class); DATA_DROP = Shaded.class.getMethod("dataDrop", int.class, Instance.class); } catch (NoSuchMethodException e) { diff --git a/compiler/src/main/java/run/endive/compiler/internal/WasmAnalyzer.java b/compiler/src/main/java/run/endive/compiler/internal/WasmAnalyzer.java index 6c852238..20d64b2c 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/WasmAnalyzer.java +++ b/compiler/src/main/java/run/endive/compiler/internal/WasmAnalyzer.java @@ -526,7 +526,8 @@ public AnalysisResult analyze(int funcId) { // BR_ON_NULL_CHECK: DUPs ref, pushes 1 if null, 0 if not null // JVM stack after check: [ref, 0_or_1] - result.add(new CompilerInstruction(CompilerOpCode.BR_ON_NULL_CHECK)); + result.add( + new CompilerInstruction(CompilerOpCode.BR_ON_NULL_CHECK, ref.id())); // IFEQ: pops boolean; if 0 (not null) -> jump to notNullLabel // JVM stack after IFEQ: [ref] @@ -555,7 +556,9 @@ public AnalysisResult analyze(int funcId) { // BR_ON_NON_NULL_CHECK: DUPs ref, pushes 1 if non-null, 0 if null // JVM stack after check: [ref, 0_or_1] - result.add(new CompilerInstruction(CompilerOpCode.BR_ON_NON_NULL_CHECK)); + result.add( + new CompilerInstruction( + CompilerOpCode.BR_ON_NON_NULL_CHECK, ref.id())); // IFEQ: if 0 (null) -> jump to nullLabel var nullLabel = nextLabel++; @@ -1275,10 +1278,14 @@ private void analyzeSimple( .resolve(module.typeSection())); break; case REF_IS_NULL: - // [ref] -> [I32] - stack.popRef(); - stack.push(ValType.I32); - break; + { + // [ref] -> [I32] + var refType = stack.peek(); + stack.popRef(); + stack.push(ValType.I32); + out.add(new CompilerInstruction(CompilerOpCode.REF_IS_NULL, refType.id())); + return; + } case MEMORY_COPY: case MEMORY_FILL: case MEMORY_INIT: @@ -1413,7 +1420,8 @@ private void analyzeSimple( var rt = stack.peek(); stack.popRef(); stack.push(valType(ValType.ID.Ref, rt.typeIdx())); - break; + out.add(new CompilerInstruction(CompilerOpCode.REF_AS_NON_NULL, rt.id())); + return; } case STRUCT_NEW: { diff --git a/compiler/src/test/resources/run/endive/approvals/ApprovalTest.functions10.approved.txt b/compiler/src/test/resources/run/endive/approvals/ApprovalTest.functions10.approved.txt index 776e13b0..72aed25e 100644 --- a/compiler/src/test/resources/run/endive/approvals/ApprovalTest.functions10.approved.txt +++ b/compiler/src/test/resources/run/endive/approvals/ApprovalTest.functions10.approved.txt @@ -12,6 +12,8 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime public call(I[J)[J TRYCATCHBLOCK L0 L1 L1 java/lang/StackOverflowError + ACONST_NULL + ASTORE 3 L0 ALOAD 0 GETFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; @@ -19,7 +21,24 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKEVIRTUAL run/endive/runtime/Instance.memory ()Lrun/endive/runtime/Memory; ILOAD 1 ALOAD 2 - INVOKESTATIC run/endive/$gen/CompiledMachineMachineCall.call (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J)[J + ALOAD 3 + INVOKESTATIC run/endive/$gen/CompiledMachineMachineCall.call (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J[Ljava/lang/Object;)[J + ARETURN + L1 + INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; + ATHROW + + public call(I[J[Ljava/lang/Object;)[J + TRYCATCHBLOCK L0 L1 L1 java/lang/StackOverflowError + L0 + ALOAD 0 + GETFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; + DUP + INVOKEVIRTUAL run/endive/runtime/Instance.memory ()Lrun/endive/runtime/Memory; + ILOAD 1 + ALOAD 2 + ALOAD 3 + INVOKESTATIC run/endive/$gen/CompiledMachineMachineCall.call (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J[Ljava/lang/Object;)[J ARETURN L1 INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; @@ -120,7 +139,7 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { IADD IRETURN - public static call_0(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + public static call_0(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ALOAD 2 ICONST_0 LALOAD @@ -129,12 +148,12 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { ALOAD 0 INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_0 (ILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I I2L - LSTORE 3 + LSTORE 4 ICONST_1 NEWARRAY T_LONG DUP ICONST_0 - LLOAD 3 + LLOAD 4 LASTORE ARETURN @@ -148,7 +167,7 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_0 (ILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I IRETURN - public static call_1(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + public static call_1(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ALOAD 2 ICONST_0 LALOAD @@ -157,12 +176,12 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { ALOAD 0 INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_1 (ILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I I2L - LSTORE 3 + LSTORE 4 ICONST_1 NEWARRAY T_LONG DUP ICONST_0 - LLOAD 3 + LLOAD 4 LASTORE ARETURN @@ -176,7 +195,7 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_1 (ILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I IRETURN - public static call_2(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + public static call_2(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ALOAD 2 ICONST_0 LALOAD @@ -185,12 +204,12 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { ALOAD 0 INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_2 (ILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I I2L - LSTORE 3 + LSTORE 4 ICONST_1 NEWARRAY T_LONG DUP ICONST_0 - LLOAD 3 + LLOAD 4 LASTORE ARETURN @@ -204,7 +223,7 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_2 (ILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I IRETURN - public static call_3(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + public static call_3(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ALOAD 2 ICONST_0 LALOAD @@ -213,12 +232,12 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { ALOAD 0 INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_3 (ILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I I2L - LSTORE 3 + LSTORE 4 ICONST_1 NEWARRAY T_LONG DUP ICONST_0 - LLOAD 3 + LLOAD 4 LASTORE ARETURN @@ -232,7 +251,7 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_3 (ILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I IRETURN - public static call_4(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + public static call_4(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ALOAD 2 ICONST_0 LALOAD @@ -241,12 +260,12 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { ALOAD 0 INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_4 (ILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I I2L - LSTORE 3 + LSTORE 4 ICONST_1 NEWARRAY T_LONG DUP ICONST_0 - LLOAD 3 + LLOAD 4 LASTORE ARETURN } @@ -263,7 +282,7 @@ final class run/endive/$gen/CompiledMachineFuncGroup_1 { INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_4 (ILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I IRETURN - public static call_5(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + public static call_5(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ALOAD 2 ICONST_0 LALOAD @@ -272,12 +291,12 @@ final class run/endive/$gen/CompiledMachineFuncGroup_1 { ALOAD 0 INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_1.func_5 (ILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I I2L - LSTORE 3 + LSTORE 4 ICONST_1 NEWARRAY T_LONG DUP ICONST_0 - LLOAD 3 + LLOAD 4 LASTORE ARETURN @@ -291,7 +310,7 @@ final class run/endive/$gen/CompiledMachineFuncGroup_1 { INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_1.func_5 (ILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I IRETURN - public static call_6(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + public static call_6(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ALOAD 2 ICONST_0 LALOAD @@ -300,12 +319,12 @@ final class run/endive/$gen/CompiledMachineFuncGroup_1 { ALOAD 0 INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_1.func_6 (ILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I I2L - LSTORE 3 + LSTORE 4 ICONST_1 NEWARRAY T_LONG DUP ICONST_0 - LLOAD 3 + LLOAD 4 LASTORE ARETURN @@ -319,7 +338,7 @@ final class run/endive/$gen/CompiledMachineFuncGroup_1 { INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_1.func_6 (ILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I IRETURN - public static call_7(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + public static call_7(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ALOAD 2 ICONST_0 LALOAD @@ -328,12 +347,12 @@ final class run/endive/$gen/CompiledMachineFuncGroup_1 { ALOAD 0 INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_1.func_7 (ILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I I2L - LSTORE 3 + LSTORE 4 ICONST_1 NEWARRAY T_LONG DUP ICONST_0 - LLOAD 3 + LLOAD 4 LASTORE ARETURN @@ -347,7 +366,7 @@ final class run/endive/$gen/CompiledMachineFuncGroup_1 { INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_1.func_7 (ILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I IRETURN - public static call_8(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + public static call_8(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ALOAD 2 ICONST_0 LALOAD @@ -356,12 +375,12 @@ final class run/endive/$gen/CompiledMachineFuncGroup_1 { ALOAD 0 INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_1.func_8 (ILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I I2L - LSTORE 3 + LSTORE 4 ICONST_1 NEWARRAY T_LONG DUP ICONST_0 - LLOAD 3 + LLOAD 4 LASTORE ARETURN @@ -375,7 +394,7 @@ final class run/endive/$gen/CompiledMachineFuncGroup_1 { INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_1.func_8 (ILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I IRETURN - public static call_9(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + public static call_9(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ALOAD 2 ICONST_0 LALOAD @@ -384,12 +403,12 @@ final class run/endive/$gen/CompiledMachineFuncGroup_1 { ALOAD 0 INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_1.func_9 (ILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I I2L - LSTORE 3 + LSTORE 4 ICONST_1 NEWARRAY T_LONG DUP ICONST_0 - LLOAD 3 + LLOAD 4 LASTORE ARETURN } @@ -401,10 +420,11 @@ final class run/endive/$gen/CompiledMachineMachineCall { INVOKESPECIAL java/lang/Object. ()V RETURN - public static call(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J)[J + public static call(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J[Ljava/lang/Object;)[J ALOAD 0 ALOAD 1 ALOAD 3 + ALOAD 4 ILOAD 2 TABLESWITCH 0: L0 @@ -419,34 +439,34 @@ final class run/endive/$gen/CompiledMachineMachineCall { 9: L9 default: L10 L0 - INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.call_0 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.call_0 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ARETURN L1 - INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.call_1 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.call_1 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ARETURN L2 - INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.call_2 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.call_2 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ARETURN L3 - INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.call_3 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.call_3 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ARETURN L4 - INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.call_4 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.call_4 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ARETURN L5 - INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_1.call_5 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_1.call_5 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ARETURN L6 - INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_1.call_6 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_1.call_6 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ARETURN L7 - INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_1.call_7 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_1.call_7 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ARETURN L8 - INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_1.call_8 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_1.call_8 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ARETURN L9 - INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_1.call_9 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_1.call_9 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ARETURN L10 ILOAD 2 diff --git a/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyBrTable.approved.txt b/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyBrTable.approved.txt index 65c1516f..8bcc3ff0 100644 --- a/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyBrTable.approved.txt +++ b/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyBrTable.approved.txt @@ -12,6 +12,8 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime public call(I[J)[J TRYCATCHBLOCK L0 L1 L1 java/lang/StackOverflowError + ACONST_NULL + ASTORE 3 L0 ALOAD 0 GETFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; @@ -19,7 +21,24 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKEVIRTUAL run/endive/runtime/Instance.memory ()Lrun/endive/runtime/Memory; ILOAD 1 ALOAD 2 - INVOKESTATIC run/endive/$gen/CompiledMachineMachineCall.call (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J)[J + ALOAD 3 + INVOKESTATIC run/endive/$gen/CompiledMachineMachineCall.call (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J[Ljava/lang/Object;)[J + ARETURN + L1 + INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; + ATHROW + + public call(I[J[Ljava/lang/Object;)[J + TRYCATCHBLOCK L0 L1 L1 java/lang/StackOverflowError + L0 + ALOAD 0 + GETFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; + DUP + INVOKEVIRTUAL run/endive/runtime/Instance.memory ()Lrun/endive/runtime/Memory; + ILOAD 1 + ALOAD 2 + ALOAD 3 + INVOKESTATIC run/endive/$gen/CompiledMachineMachineCall.call (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J[Ljava/lang/Object;)[J ARETURN L1 INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; @@ -100,7 +119,7 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { L4 ATHROW - public static call_0(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + public static call_0(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ALOAD 2 ICONST_0 LALOAD @@ -109,12 +128,12 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { ALOAD 0 INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_0 (ILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I I2L - LSTORE 3 + LSTORE 4 ICONST_1 NEWARRAY T_LONG DUP ICONST_0 - LLOAD 3 + LLOAD 4 LASTORE ARETURN } @@ -126,16 +145,17 @@ final class run/endive/$gen/CompiledMachineMachineCall { INVOKESPECIAL java/lang/Object. ()V RETURN - public static call(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J)[J + public static call(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J[Ljava/lang/Object;)[J ALOAD 0 ALOAD 1 ALOAD 3 + ALOAD 4 ILOAD 2 TABLESWITCH 0: L0 default: L1 L0 - INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.call_0 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.call_0 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ARETURN L1 ILOAD 2 diff --git a/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyBranching.approved.txt b/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyBranching.approved.txt index 3ddbf927..b5b7a958 100644 --- a/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyBranching.approved.txt +++ b/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyBranching.approved.txt @@ -12,6 +12,8 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime public call(I[J)[J TRYCATCHBLOCK L0 L1 L1 java/lang/StackOverflowError + ACONST_NULL + ASTORE 3 L0 ALOAD 0 GETFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; @@ -19,7 +21,24 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKEVIRTUAL run/endive/runtime/Instance.memory ()Lrun/endive/runtime/Memory; ILOAD 1 ALOAD 2 - INVOKESTATIC run/endive/$gen/CompiledMachineMachineCall.call (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J)[J + ALOAD 3 + INVOKESTATIC run/endive/$gen/CompiledMachineMachineCall.call (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J[Ljava/lang/Object;)[J + ARETURN + L1 + INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; + ATHROW + + public call(I[J[Ljava/lang/Object;)[J + TRYCATCHBLOCK L0 L1 L1 java/lang/StackOverflowError + L0 + ALOAD 0 + GETFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; + DUP + INVOKEVIRTUAL run/endive/runtime/Instance.memory ()Lrun/endive/runtime/Memory; + ILOAD 1 + ALOAD 2 + ALOAD 3 + INVOKESTATIC run/endive/$gen/CompiledMachineMachineCall.call (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J[Ljava/lang/Object;)[J ARETURN L1 INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; @@ -102,7 +121,7 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { ILOAD 3 IRETURN - public static call_0(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + public static call_0(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ALOAD 2 ICONST_0 LALOAD @@ -111,12 +130,12 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { ALOAD 0 INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_0 (ILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I I2L - LSTORE 3 + LSTORE 4 ICONST_1 NEWARRAY T_LONG DUP ICONST_0 - LLOAD 3 + LLOAD 4 LASTORE ARETURN } @@ -128,16 +147,17 @@ final class run/endive/$gen/CompiledMachineMachineCall { INVOKESPECIAL java/lang/Object. ()V RETURN - public static call(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J)[J + public static call(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J[Ljava/lang/Object;)[J ALOAD 0 ALOAD 1 ALOAD 3 + ALOAD 4 ILOAD 2 TABLESWITCH 0: L0 default: L1 L0 - INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.call_0 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.call_0 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ARETURN L1 ILOAD 2 diff --git a/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyExceptions.approved.txt b/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyExceptions.approved.txt index 77e775c0..9e25e57f 100644 --- a/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyExceptions.approved.txt +++ b/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyExceptions.approved.txt @@ -87,7 +87,7 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { ICONST_4 IRETURN - public static call_0(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + public static call_0(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ALOAD 2 ICONST_0 LALOAD @@ -96,12 +96,12 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { ALOAD 0 INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_0 (ILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I I2L - LSTORE 3 + LSTORE 4 ICONST_1 NEWARRAY T_LONG DUP ICONST_0 - LLOAD 3 + LLOAD 4 LASTORE ARETURN @@ -120,7 +120,7 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { ICONST_0 IRETURN - public static call_1(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + public static call_1(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ALOAD 2 ICONST_0 LALOAD @@ -129,12 +129,12 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { ALOAD 0 INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_1 (ILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I I2L - LSTORE 3 + LSTORE 4 ICONST_1 NEWARRAY T_LONG DUP ICONST_0 - LLOAD 3 + LLOAD 4 LASTORE ARETURN @@ -172,7 +172,7 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { ICONST_1 IRETURN - public static call_2(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + public static call_2(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ALOAD 2 ICONST_0 LALOAD @@ -181,12 +181,12 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { ALOAD 0 INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_2 (ILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I I2L - LSTORE 3 + LSTORE 4 ICONST_1 NEWARRAY T_LONG DUP ICONST_0 - LLOAD 3 + LLOAD 4 LASTORE ARETURN } diff --git a/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyFloat.approved.txt b/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyFloat.approved.txt index b2426373..40348054 100644 --- a/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyFloat.approved.txt +++ b/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyFloat.approved.txt @@ -12,6 +12,8 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime public call(I[J)[J TRYCATCHBLOCK L0 L1 L1 java/lang/StackOverflowError + ACONST_NULL + ASTORE 3 L0 ALOAD 0 GETFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; @@ -19,7 +21,24 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKEVIRTUAL run/endive/runtime/Instance.memory ()Lrun/endive/runtime/Memory; ILOAD 1 ALOAD 2 - INVOKESTATIC run/endive/$gen/CompiledMachineMachineCall.call (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J)[J + ALOAD 3 + INVOKESTATIC run/endive/$gen/CompiledMachineMachineCall.call (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J[Ljava/lang/Object;)[J + ARETURN + L1 + INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; + ATHROW + + public call(I[J[Ljava/lang/Object;)[J + TRYCATCHBLOCK L0 L1 L1 java/lang/StackOverflowError + L0 + ALOAD 0 + GETFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; + DUP + INVOKEVIRTUAL run/endive/runtime/Instance.memory ()Lrun/endive/runtime/Memory; + ILOAD 1 + ALOAD 2 + ALOAD 3 + INVOKESTATIC run/endive/$gen/CompiledMachineMachineCall.call (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J[Ljava/lang/Object;)[J ARETURN L1 INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; @@ -76,7 +95,7 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { POP RETURN - public static call_0(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + public static call_0(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ALOAD 1 ALOAD 0 INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_0 (Lrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)V @@ -91,16 +110,17 @@ final class run/endive/$gen/CompiledMachineMachineCall { INVOKESPECIAL java/lang/Object. ()V RETURN - public static call(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J)[J + public static call(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J[Ljava/lang/Object;)[J ALOAD 0 ALOAD 1 ALOAD 3 + ALOAD 4 ILOAD 2 TABLESWITCH 0: L0 default: L1 L0 - INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.call_0 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.call_0 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ARETURN L1 ILOAD 2 diff --git a/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyGc.approved.txt b/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyGc.approved.txt index 926ddb65..c5fb763e 100644 --- a/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyGc.approved.txt +++ b/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyGc.approved.txt @@ -1,6 +1,6 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { - public static func_0(IILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I + public static func_0(IILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)Ljava/lang/Object; ILOAD 0 ILOAD 1 ISTORE 5 @@ -17,12 +17,14 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { ILOAD 5 I2L LASTORE + ICONST_2 + ANEWARRAY java/lang/Object ICONST_0 ALOAD 3 - INVOKESTATIC run/endive/$gen/CompiledMachineShaded.structNew ([JILrun/endive/runtime/Instance;)I - IRETURN + INVOKESTATIC run/endive/$gen/CompiledMachineShaded.structNew ([J[Ljava/lang/Object;ILrun/endive/runtime/Instance;)Ljava/lang/Object; + ARETURN - public static call_0(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + public static call_0(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ALOAD 2 ICONST_0 LALOAD @@ -33,75 +35,77 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { L2I ALOAD 1 ALOAD 0 - INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_0 (IILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I + INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_0 (IILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)Ljava/lang/Object; + ASTORE 4 + ALOAD 0 + ALOAD 4 + INVOKEVIRTUAL run/endive/runtime/Instance.registerGcRef (Lrun/endive/runtime/WasmGcRef;)I I2L - LSTORE 3 + LSTORE 4 ICONST_1 NEWARRAY T_LONG DUP ICONST_0 - LLOAD 3 + LLOAD 4 LASTORE ARETURN - public static func_1(ILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I - ILOAD 0 + public static func_1(Ljava/lang/Object;Lrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I + ALOAD 0 ICONST_0 ICONST_0 ALOAD 2 - INVOKESTATIC run/endive/$gen/CompiledMachineShaded.structGet (IIILrun/endive/runtime/Instance;)J + INVOKESTATIC run/endive/$gen/CompiledMachineShaded.structGet (Ljava/lang/Object;IILrun/endive/runtime/Instance;)J L2I IRETURN - public static call_1(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J - ALOAD 2 + public static call_1(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J + ALOAD 3 ICONST_0 - LALOAD - L2I + AALOAD ALOAD 1 ALOAD 0 - INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_1 (ILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I + INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_1 (Ljava/lang/Object;Lrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I I2L - LSTORE 3 + LSTORE 4 ICONST_1 NEWARRAY T_LONG DUP ICONST_0 - LLOAD 3 + LLOAD 4 LASTORE ARETURN - public static func_2(ILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I - ILOAD 0 + public static func_2(Ljava/lang/Object;Lrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I + ALOAD 0 ICONST_0 ICONST_0 ALOAD 2 - INVOKESTATIC run/endive/$gen/CompiledMachineShaded.structGet (IIILrun/endive/runtime/Instance;)J + INVOKESTATIC run/endive/$gen/CompiledMachineShaded.structGet (Ljava/lang/Object;IILrun/endive/runtime/Instance;)J L2I - ILOAD 0 + ALOAD 0 ICONST_0 ICONST_1 ALOAD 2 - INVOKESTATIC run/endive/$gen/CompiledMachineShaded.structGet (IIILrun/endive/runtime/Instance;)J + INVOKESTATIC run/endive/$gen/CompiledMachineShaded.structGet (Ljava/lang/Object;IILrun/endive/runtime/Instance;)J L2I IADD IRETURN - public static call_2(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J - ALOAD 2 + public static call_2(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J + ALOAD 3 ICONST_0 - LALOAD - L2I + AALOAD ALOAD 1 ALOAD 0 - INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_2 (ILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I + INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_2 (Ljava/lang/Object;Lrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I I2L - LSTORE 3 + LSTORE 4 ICONST_1 NEWARRAY T_LONG DUP ICONST_0 - LLOAD 3 + LLOAD 4 LASTORE ARETURN } diff --git a/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyHelloWasi.approved.txt b/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyHelloWasi.approved.txt index 0bf4dd66..a4e049a9 100644 --- a/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyHelloWasi.approved.txt +++ b/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyHelloWasi.approved.txt @@ -12,6 +12,24 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime public call(I[J)[J TRYCATCHBLOCK L0 L1 L1 java/lang/StackOverflowError + ACONST_NULL + ASTORE 3 + L0 + ALOAD 0 + GETFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; + DUP + INVOKEVIRTUAL run/endive/runtime/Instance.memory ()Lrun/endive/runtime/Memory; + ILOAD 1 + ALOAD 2 + ALOAD 3 + INVOKESTATIC run/endive/$gen/CompiledMachineMachineCall.call (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J[Ljava/lang/Object;)[J + ARETURN + L1 + INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; + ATHROW + + public call(I[J[Ljava/lang/Object;)[J + TRYCATCHBLOCK L0 L1 L1 java/lang/StackOverflowError L0 ALOAD 0 GETFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; @@ -19,7 +37,8 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKEVIRTUAL run/endive/runtime/Instance.memory ()Lrun/endive/runtime/Memory; ILOAD 1 ALOAD 2 - INVOKESTATIC run/endive/$gen/CompiledMachineMachineCall.call (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J)[J + ALOAD 3 + INVOKESTATIC run/endive/$gen/CompiledMachineMachineCall.call (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J[Ljava/lang/Object;)[J ARETURN L1 INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; @@ -190,7 +209,7 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { POP RETURN - public static call_1(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + public static call_1(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ALOAD 1 ALOAD 0 INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_1 (Lrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)V @@ -205,19 +224,21 @@ final class run/endive/$gen/CompiledMachineMachineCall { INVOKESPECIAL java/lang/Object. ()V RETURN - public static call(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J)[J + public static call(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J[Ljava/lang/Object;)[J ALOAD 0 ALOAD 1 ALOAD 3 + ALOAD 4 ILOAD 2 TABLESWITCH 0: L0 1: L1 default: L2 L1 - INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.call_1 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.call_1 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ARETURN L0 + POP POP POP ILOAD 2 diff --git a/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyI32.approved.txt b/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyI32.approved.txt index 6a5ef2a4..a7f74f14 100644 --- a/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyI32.approved.txt +++ b/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyI32.approved.txt @@ -12,6 +12,8 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime public call(I[J)[J TRYCATCHBLOCK L0 L1 L1 java/lang/StackOverflowError + ACONST_NULL + ASTORE 3 L0 ALOAD 0 GETFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; @@ -19,7 +21,24 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKEVIRTUAL run/endive/runtime/Instance.memory ()Lrun/endive/runtime/Memory; ILOAD 1 ALOAD 2 - INVOKESTATIC run/endive/$gen/CompiledMachineMachineCall.call (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J)[J + ALOAD 3 + INVOKESTATIC run/endive/$gen/CompiledMachineMachineCall.call (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J[Ljava/lang/Object;)[J + ARETURN + L1 + INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; + ATHROW + + public call(I[J[Ljava/lang/Object;)[J + TRYCATCHBLOCK L0 L1 L1 java/lang/StackOverflowError + L0 + ALOAD 0 + GETFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; + DUP + INVOKEVIRTUAL run/endive/runtime/Instance.memory ()Lrun/endive/runtime/Memory; + ILOAD 1 + ALOAD 2 + ALOAD 3 + INVOKESTATIC run/endive/$gen/CompiledMachineMachineCall.call (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J[Ljava/lang/Object;)[J ARETURN L1 INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; @@ -100,7 +119,7 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { POP2 RETURN - public static call_0(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + public static call_0(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ALOAD 1 ALOAD 0 INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_0 (Lrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)V @@ -115,16 +134,17 @@ final class run/endive/$gen/CompiledMachineMachineCall { INVOKESPECIAL java/lang/Object. ()V RETURN - public static call(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J)[J + public static call(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J[Ljava/lang/Object;)[J ALOAD 0 ALOAD 1 ALOAD 3 + ALOAD 4 ILOAD 2 TABLESWITCH 0: L0 default: L1 L0 - INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.call_0 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.call_0 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ARETURN L1 ILOAD 2 diff --git a/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyI32Renamed.approved.txt b/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyI32Renamed.approved.txt index 46971982..09937f01 100644 --- a/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyI32Renamed.approved.txt +++ b/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyI32Renamed.approved.txt @@ -12,6 +12,8 @@ public final class FOO implements run/endive/runtime/Machine { public call(I[J)[J TRYCATCHBLOCK L0 L1 L1 java/lang/StackOverflowError + ACONST_NULL + ASTORE 3 L0 ALOAD 0 GETFIELD FOO.instance : Lrun/endive/runtime/Instance; @@ -19,7 +21,24 @@ public final class FOO implements run/endive/runtime/Machine { INVOKEVIRTUAL run/endive/runtime/Instance.memory ()Lrun/endive/runtime/Memory; ILOAD 1 ALOAD 2 - INVOKESTATIC FOOMachineCall.call (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J)[J + ALOAD 3 + INVOKESTATIC FOOMachineCall.call (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J[Ljava/lang/Object;)[J + ARETURN + L1 + INVOKESTATIC FOOShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; + ATHROW + + public call(I[J[Ljava/lang/Object;)[J + TRYCATCHBLOCK L0 L1 L1 java/lang/StackOverflowError + L0 + ALOAD 0 + GETFIELD FOO.instance : Lrun/endive/runtime/Instance; + DUP + INVOKEVIRTUAL run/endive/runtime/Instance.memory ()Lrun/endive/runtime/Memory; + ILOAD 1 + ALOAD 2 + ALOAD 3 + INVOKESTATIC FOOMachineCall.call (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J[Ljava/lang/Object;)[J ARETURN L1 INVOKESTATIC FOOShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; diff --git a/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyIterFact.approved.txt b/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyIterFact.approved.txt index 29484bd4..a46e0cdb 100644 --- a/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyIterFact.approved.txt +++ b/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyIterFact.approved.txt @@ -12,6 +12,8 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime public call(I[J)[J TRYCATCHBLOCK L0 L1 L1 java/lang/StackOverflowError + ACONST_NULL + ASTORE 3 L0 ALOAD 0 GETFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; @@ -19,7 +21,24 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKEVIRTUAL run/endive/runtime/Instance.memory ()Lrun/endive/runtime/Memory; ILOAD 1 ALOAD 2 - INVOKESTATIC run/endive/$gen/CompiledMachineMachineCall.call (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J)[J + ALOAD 3 + INVOKESTATIC run/endive/$gen/CompiledMachineMachineCall.call (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J[Ljava/lang/Object;)[J + ARETURN + L1 + INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; + ATHROW + + public call(I[J[Ljava/lang/Object;)[J + TRYCATCHBLOCK L0 L1 L1 java/lang/StackOverflowError + L0 + ALOAD 0 + GETFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; + DUP + INVOKEVIRTUAL run/endive/runtime/Instance.memory ()Lrun/endive/runtime/Memory; + ILOAD 1 + ALOAD 2 + ALOAD 3 + INVOKESTATIC run/endive/$gen/CompiledMachineMachineCall.call (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J[Ljava/lang/Object;)[J ARETURN L1 INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; @@ -104,7 +123,7 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { ILOAD 3 IRETURN - public static call_0(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + public static call_0(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ALOAD 2 ICONST_0 LALOAD @@ -113,12 +132,12 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { ALOAD 0 INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_0 (ILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I I2L - LSTORE 3 + LSTORE 4 ICONST_1 NEWARRAY T_LONG DUP ICONST_0 - LLOAD 3 + LLOAD 4 LASTORE ARETURN } @@ -130,16 +149,17 @@ final class run/endive/$gen/CompiledMachineMachineCall { INVOKESPECIAL java/lang/Object. ()V RETURN - public static call(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J)[J + public static call(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J[Ljava/lang/Object;)[J ALOAD 0 ALOAD 1 ALOAD 3 + ALOAD 4 ILOAD 2 TABLESWITCH 0: L0 default: L1 L0 - INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.call_0 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.call_0 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ARETURN L1 ILOAD 2 diff --git a/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyKitchenSink.approved.txt b/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyKitchenSink.approved.txt index c2aa894f..8efbaeec 100644 --- a/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyKitchenSink.approved.txt +++ b/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyKitchenSink.approved.txt @@ -12,6 +12,8 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime public call(I[J)[J TRYCATCHBLOCK L0 L1 L1 java/lang/StackOverflowError + ACONST_NULL + ASTORE 3 L0 ALOAD 0 GETFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; @@ -19,7 +21,24 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKEVIRTUAL run/endive/runtime/Instance.memory ()Lrun/endive/runtime/Memory; ILOAD 1 ALOAD 2 - INVOKESTATIC run/endive/$gen/CompiledMachineMachineCall.call (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J)[J + ALOAD 3 + INVOKESTATIC run/endive/$gen/CompiledMachineMachineCall.call (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J[Ljava/lang/Object;)[J + ARETURN + L1 + INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; + ATHROW + + public call(I[J[Ljava/lang/Object;)[J + TRYCATCHBLOCK L0 L1 L1 java/lang/StackOverflowError + L0 + ALOAD 0 + GETFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; + DUP + INVOKEVIRTUAL run/endive/runtime/Instance.memory ()Lrun/endive/runtime/Memory; + ILOAD 1 + ALOAD 2 + ALOAD 3 + INVOKESTATIC run/endive/$gen/CompiledMachineMachineCall.call (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J[Ljava/lang/Object;)[J ARETURN L1 INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; @@ -105,7 +124,7 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { INVOKESTATIC run/endive/runtime/OpcodeImpl.I32_EXTEND_8_S (I)I IRETURN - public static call_0(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + public static call_0(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ALOAD 2 ICONST_0 LALOAD @@ -114,12 +133,12 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { ALOAD 0 INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_0 (ILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I I2L - LSTORE 3 + LSTORE 4 ICONST_1 NEWARRAY T_LONG DUP ICONST_0 - LLOAD 3 + LLOAD 4 LASTORE ARETURN } @@ -131,16 +150,17 @@ final class run/endive/$gen/CompiledMachineMachineCall { INVOKESPECIAL java/lang/Object. ()V RETURN - public static call(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J)[J + public static call(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J[Ljava/lang/Object;)[J ALOAD 0 ALOAD 1 ALOAD 3 + ALOAD 4 ILOAD 2 TABLESWITCH 0: L0 default: L1 L0 - INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.call_0 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.call_0 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ARETURN L1 ILOAD 2 diff --git a/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyMemory.approved.txt b/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyMemory.approved.txt index 8b24e2d4..c9dce7bc 100644 --- a/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyMemory.approved.txt +++ b/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyMemory.approved.txt @@ -12,6 +12,8 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime public call(I[J)[J TRYCATCHBLOCK L0 L1 L1 java/lang/StackOverflowError + ACONST_NULL + ASTORE 3 L0 ALOAD 0 GETFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; @@ -19,7 +21,24 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKEVIRTUAL run/endive/runtime/Instance.memory ()Lrun/endive/runtime/Memory; ILOAD 1 ALOAD 2 - INVOKESTATIC run/endive/$gen/CompiledMachineMachineCall.call (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J)[J + ALOAD 3 + INVOKESTATIC run/endive/$gen/CompiledMachineMachineCall.call (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J[Ljava/lang/Object;)[J + ARETURN + L1 + INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; + ATHROW + + public call(I[J[Ljava/lang/Object;)[J + TRYCATCHBLOCK L0 L1 L1 java/lang/StackOverflowError + L0 + ALOAD 0 + GETFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; + DUP + INVOKEVIRTUAL run/endive/runtime/Instance.memory ()Lrun/endive/runtime/Memory; + ILOAD 1 + ALOAD 2 + ALOAD 3 + INVOKESTATIC run/endive/$gen/CompiledMachineMachineCall.call (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J[Ljava/lang/Object;)[J ARETURN L1 INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; @@ -140,7 +159,7 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { L0 ATHROW - public static call_0(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + public static call_0(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ALOAD 2 ICONST_0 LALOAD @@ -149,12 +168,12 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { ALOAD 0 INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_0 (ILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I I2L - LSTORE 3 + LSTORE 4 ICONST_1 NEWARRAY T_LONG DUP ICONST_0 - LLOAD 3 + LLOAD 4 LASTORE ARETURN @@ -172,19 +191,19 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { L0 ATHROW - public static call_1(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + public static call_1(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ALOAD 2 ICONST_0 LALOAD ALOAD 1 ALOAD 0 INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_1 (JLrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)J - LSTORE 3 + LSTORE 4 ICONST_1 NEWARRAY T_LONG DUP ICONST_0 - LLOAD 3 + LLOAD 4 LASTORE ARETURN } @@ -196,20 +215,21 @@ final class run/endive/$gen/CompiledMachineMachineCall { INVOKESPECIAL java/lang/Object. ()V RETURN - public static call(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J)[J + public static call(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J[Ljava/lang/Object;)[J ALOAD 0 ALOAD 1 ALOAD 3 + ALOAD 4 ILOAD 2 TABLESWITCH 0: L0 1: L1 default: L2 L0 - INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.call_0 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.call_0 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ARETURN L1 - INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.call_1 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.call_1 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ARETURN L2 ILOAD 2 diff --git a/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyStart.approved.txt b/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyStart.approved.txt index b2d2bcfc..267fb98a 100644 --- a/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyStart.approved.txt +++ b/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyStart.approved.txt @@ -12,6 +12,24 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime public call(I[J)[J TRYCATCHBLOCK L0 L1 L1 java/lang/StackOverflowError + ACONST_NULL + ASTORE 3 + L0 + ALOAD 0 + GETFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; + DUP + INVOKEVIRTUAL run/endive/runtime/Instance.memory ()Lrun/endive/runtime/Memory; + ILOAD 1 + ALOAD 2 + ALOAD 3 + INVOKESTATIC run/endive/$gen/CompiledMachineMachineCall.call (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J[Ljava/lang/Object;)[J + ARETURN + L1 + INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; + ATHROW + + public call(I[J[Ljava/lang/Object;)[J + TRYCATCHBLOCK L0 L1 L1 java/lang/StackOverflowError L0 ALOAD 0 GETFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; @@ -19,7 +37,8 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKEVIRTUAL run/endive/runtime/Instance.memory ()Lrun/endive/runtime/Memory; ILOAD 1 ALOAD 2 - INVOKESTATIC run/endive/$gen/CompiledMachineMachineCall.call (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J)[J + ALOAD 3 + INVOKESTATIC run/endive/$gen/CompiledMachineMachineCall.call (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J[Ljava/lang/Object;)[J ARETURN L1 INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; @@ -137,7 +156,7 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_0 (ILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)V RETURN - public static call_1(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + public static call_1(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ALOAD 1 ALOAD 0 INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_1 (Lrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)V @@ -152,19 +171,21 @@ final class run/endive/$gen/CompiledMachineMachineCall { INVOKESPECIAL java/lang/Object. ()V RETURN - public static call(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J)[J + public static call(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J[Ljava/lang/Object;)[J ALOAD 0 ALOAD 1 ALOAD 3 + ALOAD 4 ILOAD 2 TABLESWITCH 0: L0 1: L1 default: L2 L1 - INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.call_1 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.call_1 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ARETURN L0 + POP POP POP ILOAD 2 diff --git a/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyTailCall.approved.txt b/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyTailCall.approved.txt index a0379e79..4fd76007 100644 --- a/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyTailCall.approved.txt +++ b/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyTailCall.approved.txt @@ -12,28 +12,68 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime public call(I[J)[J TRYCATCHBLOCK L0 L1 L2 java/lang/StackOverflowError + ACONST_NULL + ASTORE 3 L0 ALOAD 0 GETFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; - ASTORE 3 + ASTORE 4 L3 - ALOAD 3 + ALOAD 4 DUP INVOKEVIRTUAL run/endive/runtime/Instance.memory ()Lrun/endive/runtime/Memory; ILOAD 1 ALOAD 2 - INVOKESTATIC run/endive/$gen/CompiledMachineMachineCall.call (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J)[J ALOAD 3 + INVOKESTATIC run/endive/$gen/CompiledMachineMachineCall.call (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J[Ljava/lang/Object;)[J + ALOAD 4 INVOKEVIRTUAL run/endive/runtime/Instance.isTailCallPending ()Z IFEQ L1 POP - ALOAD 3 + ALOAD 4 INVOKEVIRTUAL run/endive/runtime/Instance.tailCallFuncId ()I ISTORE 1 - ALOAD 3 + ALOAD 4 INVOKEVIRTUAL run/endive/runtime/Instance.tailCallArgs ()[J ASTORE 2 + ACONST_NULL + ASTORE 3 + ALOAD 4 + INVOKEVIRTUAL run/endive/runtime/Instance.clearTailCall ()V + GOTO L3 + L1 + ARETURN + L2 + INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; + ATHROW + + public call(I[J[Ljava/lang/Object;)[J + TRYCATCHBLOCK L0 L1 L2 java/lang/StackOverflowError + L0 + ALOAD 0 + GETFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; + ASTORE 4 + L3 + ALOAD 4 + DUP + INVOKEVIRTUAL run/endive/runtime/Instance.memory ()Lrun/endive/runtime/Memory; + ILOAD 1 + ALOAD 2 ALOAD 3 + INVOKESTATIC run/endive/$gen/CompiledMachineMachineCall.call (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J[Ljava/lang/Object;)[J + ALOAD 4 + INVOKEVIRTUAL run/endive/runtime/Instance.isTailCallPending ()Z + IFEQ L1 + POP + ALOAD 4 + INVOKEVIRTUAL run/endive/runtime/Instance.tailCallFuncId ()I + ISTORE 1 + ALOAD 4 + INVOKEVIRTUAL run/endive/runtime/Instance.tailCallArgs ()[J + ASTORE 2 + ACONST_NULL + ASTORE 3 + ALOAD 4 INVOKEVIRTUAL run/endive/runtime/Instance.clearTailCall ()V GOTO L3 L1 @@ -157,7 +197,7 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { L1 IRETURN - public static call_0(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + public static call_0(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ALOAD 2 ICONST_0 LALOAD @@ -174,12 +214,12 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { ALOAD 0 INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_0 (IIILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I I2L - LSTORE 3 + LSTORE 4 ICONST_1 NEWARRAY T_LONG DUP ICONST_0 - LLOAD 3 + LLOAD 4 LASTORE ARETURN } @@ -191,16 +231,17 @@ final class run/endive/$gen/CompiledMachineMachineCall { INVOKESPECIAL java/lang/Object. ()V RETURN - public static call(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J)[J + public static call(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J[Ljava/lang/Object;)[J ALOAD 0 ALOAD 1 ALOAD 3 + ALOAD 4 ILOAD 2 TABLESWITCH 0: L0 default: L1 L0 - INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.call_0 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.call_0 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ARETURN L1 ILOAD 2 diff --git a/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyTrap.approved.txt b/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyTrap.approved.txt index 9e1c20b6..3df5c3ff 100644 --- a/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyTrap.approved.txt +++ b/compiler/src/test/resources/run/endive/approvals/ApprovalTest.verifyTrap.approved.txt @@ -12,6 +12,8 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime public call(I[J)[J TRYCATCHBLOCK L0 L1 L1 java/lang/StackOverflowError + ACONST_NULL + ASTORE 3 L0 ALOAD 0 GETFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; @@ -19,7 +21,24 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKEVIRTUAL run/endive/runtime/Instance.memory ()Lrun/endive/runtime/Memory; ILOAD 1 ALOAD 2 - INVOKESTATIC run/endive/$gen/CompiledMachineMachineCall.call (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J)[J + ALOAD 3 + INVOKESTATIC run/endive/$gen/CompiledMachineMachineCall.call (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J[Ljava/lang/Object;)[J + ARETURN + L1 + INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; + ATHROW + + public call(I[J[Ljava/lang/Object;)[J + TRYCATCHBLOCK L0 L1 L1 java/lang/StackOverflowError + L0 + ALOAD 0 + GETFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; + DUP + INVOKEVIRTUAL run/endive/runtime/Instance.memory ()Lrun/endive/runtime/Memory; + ILOAD 1 + ALOAD 2 + ALOAD 3 + INVOKESTATIC run/endive/$gen/CompiledMachineMachineCall.call (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J[Ljava/lang/Object;)[J ARETURN L1 INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; @@ -83,7 +102,7 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { L0 ATHROW - public static call_0(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + public static call_0(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ALOAD 1 ALOAD 0 INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_0 (Lrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)V @@ -97,7 +116,7 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_0 (Lrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)V RETURN - public static call_1(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + public static call_1(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ALOAD 1 ALOAD 0 INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_1 (Lrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)V @@ -111,7 +130,7 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_1 (Lrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)V RETURN - public static call_2(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + public static call_2(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ALOAD 1 ALOAD 0 INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_2 (Lrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)V @@ -126,10 +145,11 @@ final class run/endive/$gen/CompiledMachineMachineCall { INVOKESPECIAL java/lang/Object. ()V RETURN - public static call(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J)[J + public static call(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J[Ljava/lang/Object;)[J ALOAD 0 ALOAD 1 ALOAD 3 + ALOAD 4 ILOAD 2 TABLESWITCH 0: L0 @@ -137,13 +157,13 @@ final class run/endive/$gen/CompiledMachineMachineCall { 2: L2 default: L3 L0 - INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.call_0 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.call_0 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ARETURN L1 - INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.call_1 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.call_1 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ARETURN L2 - INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.call_2 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J + INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.call_2 (Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ARETURN L3 ILOAD 2 diff --git a/runtime/src/main/java/run/endive/runtime/ExportFunction.java b/runtime/src/main/java/run/endive/runtime/ExportFunction.java index 82ccd279..872dad80 100644 --- a/runtime/src/main/java/run/endive/runtime/ExportFunction.java +++ b/runtime/src/main/java/run/endive/runtime/ExportFunction.java @@ -8,4 +8,8 @@ @FunctionalInterface public interface ExportFunction { long[] apply(long... args) throws WasmEngineException; + + default Object[] applyGc(Object... args) throws WasmEngineException { + throw new UnsupportedOperationException("This function does not support GC references"); + } } diff --git a/runtime/src/main/java/run/endive/runtime/Instance.java b/runtime/src/main/java/run/endive/runtime/Instance.java index b8d1086f..d5407a28 100644 --- a/runtime/src/main/java/run/endive/runtime/Instance.java +++ b/runtime/src/main/java/run/endive/runtime/Instance.java @@ -133,13 +133,19 @@ static final class TailCallPending { this.gcRefs = new GcRefStore(this); for (int i = 0; i < tables.length; i++) { - long rawValue = computeConstantValue(this, tables[i].initialize())[0]; - int initValue = (int) rawValue; + var result = computeConstant(this, tables[i].initialize()); + int initValue = (int) result.longValue(); if (tableFactory != null) { this.tables[i] = tableFactory.create(tables[i], initValue); } else { this.tables[i] = new TableInstance(tables[i], initValue); } + if (tables[i].elementType().isGcReference() && result.ref() != null) { + var tbl = this.tables[i]; + for (int j = 0; j < tbl.size(); j++) { + tbl.setObjRef(j, result.ref(), this); + } + } } if (initialize) { @@ -186,9 +192,7 @@ public Instance initialize(boolean start) { || (offset + initializers.size() - 1) >= table.size()) { throw new UninstantiableException("out of bounds table access"); } - boolean isGcTable = - !table.elementType().equals(ValType.FuncRef) - && !table.elementType().equals(ValType.ExternRef); + boolean isGcTable = table.elementType().isGcReference(); for (int i = 0; i < initializers.size(); i++) { final List init = initializers.get(i); int index = offset + i; @@ -281,11 +285,23 @@ private Export getExport(ExternalType type, String name) throws InvalidException public ExportFunction function(String name) { var export = getExport(FUNCTION, name); - return args -> { - try { - return instance.machine.call(export.index(), args); - } finally { - instance.gcSafePoint(); + return new ExportFunction() { + @Override + public long[] apply(long... args) { + try { + return instance.machine.call(export.index(), args); + } finally { + instance.gcSafePoint(); + } + } + + @Override + public Object[] applyGc(Object... args) { + try { + return instance.machine.callGc(export.index(), args); + } finally { + instance.gcSafePoint(); + } } }; } @@ -450,10 +466,12 @@ public long[] array(int idx) { return null; } + @Deprecated public int registerGcRef(WasmGcRef ref) { return gcRefs.put(ref); } + @Deprecated public WasmGcRef gcRef(int idx) { return gcRefs.get(idx); } diff --git a/runtime/src/main/java/run/endive/runtime/InterpreterMachine.java b/runtime/src/main/java/run/endive/runtime/InterpreterMachine.java index ca902e75..04d4b7a8 100644 --- a/runtime/src/main/java/run/endive/runtime/InterpreterMachine.java +++ b/runtime/src/main/java/run/endive/runtime/InterpreterMachine.java @@ -147,12 +147,94 @@ protected long[] call( var totalResults = sizeOf(type.returns()); var results = new long[totalResults]; - for (var i = totalResults - 1; i >= 0; i--) { - results[i] = stack.pop(); + int slot = totalResults; + for (int r = type.returns().size() - 1; r >= 0; r--) { + var retType = type.returns().get(r); + if (retType.isGcReference()) { + slot--; + var ref = stack.popRef(); + results[slot] = (ref == null) ? Value.REF_NULL_VALUE : 0; + } else if (retType.equals(ValType.V128)) { + slot -= 2; + results[slot + 1] = stack.pop(); + results[slot] = stack.pop(); + } else { + slot--; + results[slot] = stack.pop(); + } + } + return results; + } + + @Override + public Object[] callGc(int funcId, Object[] gcArgs) throws WasmEngineException { + checkInterruption(); + var typeId = instance.functionType(funcId); + var type = instance.type(typeId); + + var longArgs = new long[sizeOf(type.params())]; + Object[] refArgs = null; + int slot = 0; + for (int i = 0; i < type.params().size(); i++) { + var param = type.params().get(i); + if (param.isGcReference()) { + if (refArgs == null) { + refArgs = new Object[longArgs.length]; + } + refArgs[slot] = (gcArgs != null && i < gcArgs.length) ? gcArgs[i] : null; + slot++; + } else if (param.equals(ValType.V128)) { + if (gcArgs != null && i < gcArgs.length && gcArgs[i] instanceof long[]) { + var v = (long[]) gcArgs[i]; + longArgs[slot] = v[0]; + longArgs[slot + 1] = v.length > 1 ? v[1] : 0; + } + slot += 2; + } else { + if (gcArgs != null && i < gcArgs.length && gcArgs[i] instanceof Number) { + longArgs[slot] = ((Number) gcArgs[i]).longValue(); + } + slot++; + } + } + + call(stack, instance, callStack, funcId, longArgs, refArgs, null, false); + + if (type.returns().isEmpty() || stack.size() == 0) { + return null; + } + + var results = new Object[type.returns().size()]; + for (int r = type.returns().size() - 1; r >= 0; r--) { + var retType = type.returns().get(r); + if (retType.isGcReference()) { + results[r] = stack.popRef(); + } else if (retType.equals(ValType.V128)) { + long hi = stack.pop(); + long lo = stack.pop(); + results[r] = new long[] {lo, hi}; + } else { + results[r] = boxReturnValue(retType, stack.pop()); + } } return results; } + private static Object boxReturnValue(ValType type, long raw) { + switch (type.opcode()) { + case ValType.ID.I32: + return (int) raw; + case ValType.ID.I64: + return raw; + case ValType.ID.F32: + return Value.longToFloat(raw); + case ValType.ID.F64: + return Value.longToDouble(raw); + default: + return raw; + } + } + protected Instance instance() { return instance; } @@ -1098,7 +1180,7 @@ protected void eval(MStack stack, Instance instance, Deque callStack ANY_CONVERT_EXTERN(stack); break; case EXTERN_CONVERT_ANY: - EXTERN_CONVERT_ANY(stack, instance); + EXTERN_CONVERT_ANY(stack); break; default: { @@ -1822,7 +1904,7 @@ private static void TABLE_GROW(MStack stack, Instance instance, Operands operand var tableidx = (int) operands.get(0); var table = instance.table(tableidx); var et = table.elementType(); - boolean isGcTable = !et.equals(ValType.FuncRef) && !et.equals(ValType.ExternRef); + boolean isGcTable = et.isGcReference(); var size = (int) stack.pop(); if (isGcTable) { @@ -1847,7 +1929,7 @@ private static void TABLE_FILL(MStack stack, Instance instance, Operands operand var tableidx = (int) operands.get(0); var table = instance.table(tableidx); var et = table.elementType(); - boolean isGcTable = !et.equals(ValType.FuncRef) && !et.equals(ValType.ExternRef); + boolean isGcTable = et.isGcReference(); var size = (int) stack.pop(); if (isGcTable) { @@ -2007,7 +2089,7 @@ protected void CALL(Operands operands) { var type = instance.type(typeId); // given a list of param types, let's pop those params off the stack // and pass as args to the function call - var extracted = extractArgsAndRefsForParams(stack, type.params()); + var extracted = extractArgsAndRefsForParams(stack, type.params(), instance); call( stack, instance, @@ -2028,7 +2110,7 @@ private void CALL_REF() { var type = instance.type(typeId); // given a list of param types, let's pop those params off the stack // and pass as args to the function call - var extracted = extractArgsAndRefsForParams(stack, type.params()); + var extracted = extractArgsAndRefsForParams(stack, type.params(), instance); call( stack, instance, @@ -2192,7 +2274,7 @@ private static void TABLE_SET(MStack stack, Instance instance, Operands operands var idx = (int) operands.get(0); var table = instance.table(idx); var et = table.elementType(); - boolean isGcTable = !et.equals(ValType.FuncRef) && !et.equals(ValType.ExternRef); + boolean isGcTable = et.isGcReference(); if (isGcTable) { var refVal = stack.popRef(); var i = (int) stack.pop(); @@ -2208,7 +2290,7 @@ private static void TABLE_GET(MStack stack, Instance instance, Operands operands var idx = (int) operands.get(0); var table = instance.table(idx); var et = table.elementType(); - boolean isGcTable = !et.equals(ValType.FuncRef) && !et.equals(ValType.ExternRef); + boolean isGcTable = et.isGcReference(); var i = (int) stack.pop(); if (isGcTable) { stack.pushRef(table.objRef(i)); @@ -2733,7 +2815,7 @@ private static StackFrame RETURN_CALL( var typeId = instance.functionType(funcId); var type = instance.type(typeId); var func = instance.function(funcId); - var extracted = extractArgsAndRefsForParams(stack, type.params()); + var extracted = extractArgsAndRefsForParams(stack, type.params(), instance); var args = (long[]) extracted[0]; var refArgs = (Object[]) extracted[1]; @@ -2821,7 +2903,7 @@ private static StackFrame RETURN_CALL_INDIRECT( + refMachine.getName()); } - var extracted = extractArgsAndRefsForParams(stack, type.params()); + var extracted = extractArgsAndRefsForParams(stack, type.params(), instance); var args = (long[]) extracted[0]; var refArgs = (Object[]) extracted[1]; @@ -2897,7 +2979,7 @@ private static StackFrame RETURN_CALL_REF( var func = instance.function(funcId); // given a list of param types, let's pop those params off the stack // and pass as args to the function call - var extracted = extractArgsAndRefsForParams(stack, type.params()); + var extracted = extractArgsAndRefsForParams(stack, type.params(), instance); var args = (long[]) extracted[0]; var refArgs = (Object[]) extracted[1]; @@ -2944,7 +3026,7 @@ private void CALL_INDIRECT( // given a list of param types, let's pop those params off the stack // and pass as args to the function call - var extracted = extractArgsAndRefsForParams(stack, type.params()); + var extracted = extractArgsAndRefsForParams(stack, type.params(), instance); var args = (long[]) extracted[0]; var refArgs = (Object[]) extracted[1]; if (useCurrentInstanceInterpreter(instance, refInstance, funcId)) { @@ -3196,7 +3278,8 @@ protected static long[] extractArgsForParams(MStack stack, List params) * Ref-typed params are popped from the ref stack; others from the long stack. * Returns a 2-element array: [0] = long[] args, [1] = Object[] refArgs (or null if no refs). */ - protected static Object[] extractArgsAndRefsForParams(MStack stack, List params) { + protected static Object[] extractArgsAndRefsForParams( + MStack stack, List params, Instance instance) { if (params == null || params.isEmpty()) { return new Object[] {Value.EMPTY_VALUES, null}; } @@ -3790,24 +3873,33 @@ private static void BR_ON_CAST_FAIL( } private static void ANY_CONVERT_EXTERN(MStack stack) { - // Pop long externref, wrap in WasmExternRef, pushRef - var val = stack.pop(); - if (val == REF_NULL_VALUE) { - stack.pushRef(null); + // Externref can be on long side (host-provided) or Object side (externalized GC value) + Object[] refArray = stack.refArray(); + Object refValue = (refArray != null) ? refArray[stack.size() - 1] : null; + if (refValue instanceof WasmExternRef && ((WasmExternRef) refValue).isObjectRef()) { + // Externalized GC value coming back — unwrap to original GC Object + var externRef = (WasmExternRef) stack.popRef(); + stack.pushRef(externRef.objectValue()); } else { - stack.pushRef(new WasmExternRef(val)); + var val = stack.pop(); + if (val == REF_NULL_VALUE) { + stack.pushRef(null); + } else { + stack.pushRef(new WasmExternRef(val)); + } } } - private static void EXTERN_CONVERT_ANY(MStack stack, Instance instance) { + private static void EXTERN_CONVERT_ANY(MStack stack) { var ref = stack.popRef(); if (ref == null) { stack.push(REF_NULL_VALUE); } else if (ref instanceof WasmExternRef) { stack.push(((WasmExternRef) ref).value()); } else { - // GC value being externalized — register so it survives the round-trip - stack.push(instance.registerGcRef((WasmGcRef) ref)); + // GC value externalized — wrap in WasmExternRef and keep on ref side. + // This allows round-tripping through any.convert_extern. + stack.pushRef(new WasmExternRef(ref)); } } diff --git a/runtime/src/main/java/run/endive/runtime/Machine.java b/runtime/src/main/java/run/endive/runtime/Machine.java index 56510284..58f7b187 100644 --- a/runtime/src/main/java/run/endive/runtime/Machine.java +++ b/runtime/src/main/java/run/endive/runtime/Machine.java @@ -10,4 +10,8 @@ public interface Machine { default long[] call(int funcId, long[] args, Object[] refArgs) throws WasmEngineException { return call(funcId, args); } + + default Object[] callGc(int funcId, Object[] args) throws WasmEngineException { + throw new UnsupportedOperationException("This Machine does not support GC references"); + } } diff --git a/runtime/src/main/java/run/endive/runtime/OpcodeImpl.java b/runtime/src/main/java/run/endive/runtime/OpcodeImpl.java index 27008b6a..27dbd933 100644 --- a/runtime/src/main/java/run/endive/runtime/OpcodeImpl.java +++ b/runtime/src/main/java/run/endive/runtime/OpcodeImpl.java @@ -8,7 +8,6 @@ import run.endive.wasm.types.OpCode; import run.endive.wasm.types.PassiveElement; import run.endive.wasm.types.ValType; -import run.endive.wasm.types.Value; /** * Note: Some opcodes are easy or trivial to implement as compiler intrinsics (local.get, i32.add, etc). @@ -820,9 +819,7 @@ public static void TABLE_COPY( throw new WasmRuntimeException("out of bounds table access"); } - boolean isGcTable = - !dest.elementType().equals(ValType.FuncRef) - && !dest.elementType().equals(ValType.ExternRef); + boolean isGcTable = dest.elementType().isGcReference(); for (int i = size - 1; i >= 0; i--) { if (d <= s) { @@ -871,9 +868,7 @@ public static void TABLE_INIT( } int end = (int) endL; - boolean isGcTable = - !table.elementType().equals(ValType.FuncRef) - && !table.elementType().equals(ValType.ExternRef); + boolean isGcTable = table.elementType().isGcReference(); for (int i = offset; i < end; i++) { var elem = instance.element(elementidx); if (isGcTable) { @@ -899,29 +894,15 @@ public static void TABLE_INIT( * i31 values (tagged longs) are boxed as WasmI31Ref GC refs so the tag is preserved. * For non-GC values (funcref, externref), this is a no-op cast to int. */ + @SuppressWarnings("InlineMeSuggester") + @Deprecated public static int boxForTable(long stackValue, Instance instance) { - if (Value.isI31(stackValue)) { - var i31Ref = new WasmI31Ref(Value.decodeI31U(stackValue)); - return instance.registerGcRef(i31Ref); - } return (int) stackValue; } - /** - * Converts an int from table storage to a stack long value, unboxing i31 refs. - * Only performs GC ref lookup for GC-typed tables (anyref, eqref, etc.) to avoid - * overhead for funcref tables in non-GC modules. - */ + @SuppressWarnings("InlineMeSuggester") + @Deprecated public static long unboxFromTable(int tableValue, Instance instance, ValType elementType) { - if (tableValue != Value.REF_NULL_VALUE - && tableValue >= 0 - && !elementType.equals(ValType.FuncRef) - && !elementType.equals(ValType.ExternRef)) { - var gcRef = instance.gcRef(tableValue); - if (gcRef instanceof WasmI31Ref) { - return Value.encodeI31(((WasmI31Ref) gcRef).value()); - } - } return tableValue; } diff --git a/runtime/src/main/java/run/endive/runtime/TableInstance.java b/runtime/src/main/java/run/endive/runtime/TableInstance.java index 9717680c..3b88a04d 100644 --- a/runtime/src/main/java/run/endive/runtime/TableInstance.java +++ b/runtime/src/main/java/run/endive/runtime/TableInstance.java @@ -27,8 +27,7 @@ public TableInstance(Table table, int initialValue) { } private boolean isGcTable() { - var et = table.elementType(); - return !et.equals(ValType.FuncRef) && !et.equals(ValType.ExternRef); + return table.elementType().isGcReference(); } public int size() { diff --git a/runtime/src/main/java/run/endive/runtime/WasmExternRef.java b/runtime/src/main/java/run/endive/runtime/WasmExternRef.java index 92e34c19..20e3842f 100644 --- a/runtime/src/main/java/run/endive/runtime/WasmExternRef.java +++ b/runtime/src/main/java/run/endive/runtime/WasmExternRef.java @@ -1,17 +1,25 @@ package run.endive.runtime; /** - * Wrapper for externref values converted to anyref via any.convert_extern. - * Prevents GC ref ID collisions between extern values and native GC objects. + * Wrapper for externref values. + * Can wrap either a long (for host-provided externref values) or an Object + * (for GC values externalized via extern.convert_any). */ public final class WasmExternRef implements WasmGcRef { private static final int ANY_HEAP_TYPE = -18; // ValType.TypeIdxCode.ANY.code() - private final long value; + private final long longValue; + private final Object objectValue; public WasmExternRef(long value) { - this.value = value; + this.longValue = value; + this.objectValue = null; + } + + public WasmExternRef(Object value) { + this.longValue = 0; + this.objectValue = value; } @Override @@ -20,6 +28,14 @@ public int typeIdx() { } public long value() { - return value; + return longValue; + } + + public Object objectValue() { + return objectValue; + } + + public boolean isObjectRef() { + return objectValue != null; } } diff --git a/runtime/src/main/java/run/endive/runtime/WasmFunctionHandle.java b/runtime/src/main/java/run/endive/runtime/WasmFunctionHandle.java index 627cdc08..d7c87fcd 100644 --- a/runtime/src/main/java/run/endive/runtime/WasmFunctionHandle.java +++ b/runtime/src/main/java/run/endive/runtime/WasmFunctionHandle.java @@ -6,4 +6,9 @@ @FunctionalInterface public interface WasmFunctionHandle { long[] apply(Instance instance, long... args); + + default Object[] applyGc(Instance instance, Object... args) { + throw new UnsupportedOperationException( + "This host function does not support GC references"); + } } diff --git a/wasm/src/main/java/run/endive/wasm/types/ValType.java b/wasm/src/main/java/run/endive/wasm/types/ValType.java index 96d12e67..ee1375da 100644 --- a/wasm/src/main/java/run/endive/wasm/types/ValType.java +++ b/wasm/src/main/java/run/endive/wasm/types/ValType.java @@ -43,6 +43,7 @@ public final class ValType { // This is useful when validating import function values. private int resolvedFunctionTypeHash; private final int resolvedFunctionTypeId; + private boolean gcReference; private ValType(int opcode) { this(opcode, NULL_TYPEIDX, -1); @@ -112,9 +113,44 @@ public ValType resolve(TypeSection typeSection) { throw new InvalidException("unknown type: " + resolvedFunctionTypeId); } } + this.gcReference = computeIsGcReference(typeSection); return this; } + private boolean computeIsGcReference(TypeSection ts) { + int op = opcode(); + switch (op) { + case ID.AnyRef: + case ID.EqRef: + case ID.i31: + case ID.StructRef: + case ID.ArrayRef: + case ID.NoneRef: + return true; + case ID.Ref: + case ID.RefNull: + int ht = typeIdx(); + if (ht == TypeIdxCode.FUNC.code() + || ht == TypeIdxCode.NOFUNC.code() + || ht == TypeIdxCode.EXTERN.code() + || ht == TypeIdxCode.NOEXTERN.code() + || ht == TypeIdxCode.EXN.code()) { + return false; + } + if (ht >= 0 && ts != null) { + return isConcreteInAnyHierarchy(ht, ts); + } + return ht == TypeIdxCode.ANY.code() + || ht == TypeIdxCode.EQ.code() + || ht == TypeIdxCode.I31.code() + || ht == TypeIdxCode.STRUCT.code() + || ht == TypeIdxCode.ARRAY.code() + || ht == TypeIdxCode.NONE.code(); + default: + return false; + } + } + private static long createId(int opcode, int typeIdx) { return ((long) typeIdx) << TYPEIDX_SHIFT | (opcode & OPCODE_MASK); } @@ -235,25 +271,7 @@ public boolean isReference() { } public boolean isGcReference() { - switch (opcode()) { - case ID.AnyRef: - case ID.EqRef: - case ID.i31: - case ID.StructRef: - case ID.ArrayRef: - case ID.NoneRef: - return true; - case ID.Ref: - case ID.RefNull: - int ht = typeIdx(); - return ht != TypeIdxCode.FUNC.code() - && ht != TypeIdxCode.NOFUNC.code() - && ht != TypeIdxCode.EXTERN.code() - && ht != TypeIdxCode.NOEXTERN.code() - && ht != TypeIdxCode.EXN.code(); - default: - return false; - } + return gcReference; } // https://webassembly.github.io/gc/core/binary/types.html#heap-types @@ -695,6 +713,10 @@ public boolean isReference() { } public boolean isGcReference() { + return isGcReference(null); + } + + public boolean isGcReference(TypeSection ts) { if (!isReference()) { return false; } @@ -708,11 +730,23 @@ public boolean isGcReference() { return true; case ID.Ref: case ID.RefNull: - return typeIdx != TypeIdxCode.FUNC.code() - && typeIdx != TypeIdxCode.NOFUNC.code() - && typeIdx != TypeIdxCode.EXTERN.code() - && typeIdx != TypeIdxCode.NOEXTERN.code() - && typeIdx != TypeIdxCode.EXN.code(); + if (typeIdx == TypeIdxCode.FUNC.code() + || typeIdx == TypeIdxCode.NOFUNC.code() + || typeIdx == TypeIdxCode.EXTERN.code() + || typeIdx == TypeIdxCode.NOEXTERN.code() + || typeIdx == TypeIdxCode.EXN.code()) { + return false; + } + if (typeIdx >= 0 && ts != null) { + return isConcreteInAnyHierarchy(typeIdx, ts); + } + return typeIdx >= 0 + || typeIdx == TypeIdxCode.ANY.code() + || typeIdx == TypeIdxCode.EQ.code() + || typeIdx == TypeIdxCode.I31.code() + || typeIdx == TypeIdxCode.STRUCT.code() + || typeIdx == TypeIdxCode.ARRAY.code() + || typeIdx == TypeIdxCode.NONE.code(); default: return false; } From 51ab0e02110fe88e2d0dd258d7a1214f0c4810f3 Mon Sep 17 00:00:00 2001 From: andreatp Date: Wed, 3 Jun 2026 12:22:38 +0100 Subject: [PATCH 3/4] =?UTF-8?q?feat:=20remove=20GcRefStore=20=E2=80=94=20J?= =?UTF-8?q?ava=20GC=20handles=20all=20Wasm=20GC=20references?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Delete GcRefStore and its epoch-based mark-sweep collector. Wasm GC references (structs, arrays, i31) are now managed entirely by Java's garbage collector through the Object[] refs arrays in MStack, StackFrame, WasmStruct, WasmArray, GlobalInstance, and TableInstance. Changes: - Delete GcRefStore.java and GcRefStoreTest.java - Instance: remove gcRefs field, gcSafePoint() - Instance.registerGcRef/gcRef/array: throw UnsupportedOperationException - ExportFunction.apply(long...) throws on functions with GC params/returns directing users to applyGc(Object...) instead - Remove gcSafePoint() calls from Instance.Exports and initialization Users must migrate from apply(long...) to applyGc(Object...) for functions that use GC reference types (structs, arrays, i31, anyref). Non-GC functions (funcref, externref, numeric types) continue to work with apply(long...) unchanged. --- .../java/run/endive/runtime/Instance.java | 44 +++--- .../endive/runtime/internal/GcRefStore.java | 125 ------------------ .../runtime/internal/GcRefStoreTest.java | 57 -------- 3 files changed, 17 insertions(+), 209 deletions(-) delete mode 100644 runtime/src/main/java/run/endive/runtime/internal/GcRefStore.java delete mode 100644 runtime/src/test/java/run/endive/runtime/internal/GcRefStoreTest.java diff --git a/runtime/src/main/java/run/endive/runtime/Instance.java b/runtime/src/main/java/run/endive/runtime/Instance.java index d5407a28..95be3987 100644 --- a/runtime/src/main/java/run/endive/runtime/Instance.java +++ b/runtime/src/main/java/run/endive/runtime/Instance.java @@ -18,7 +18,6 @@ import java.util.Objects; import java.util.function.Function; import java.util.stream.Collectors; -import run.endive.runtime.internal.GcRefStore; import run.endive.wasm.InvalidException; import run.endive.wasm.UninstantiableException; import run.endive.wasm.UnlinkableException; @@ -74,7 +73,6 @@ public class Instance { private final Exports fluentExports; private final Map exnRefs; - private final GcRefStore gcRefs; private TailCallPending tailCallPending; @@ -130,7 +128,6 @@ static final class TailCallPending { this.fluentExports = new Exports(this); this.exnRefs = new HashMap<>(); - this.gcRefs = new GcRefStore(this); for (int i = 0; i < tables.length; i++) { var result = computeConstant(this, tables[i].initialize()); @@ -250,9 +247,6 @@ public Instance initialize(boolean start) { } } - // Safe point: wasm stack is empty after init - gcSafePoint(); - return this; } @@ -285,23 +279,24 @@ private Export getExport(ExternalType type, String name) throws InvalidException public ExportFunction function(String name) { var export = getExport(FUNCTION, name); + var funcType = instance.type(instance.functionType(export.index())); + boolean hasGcReturns = funcType.returns().stream().anyMatch(ValType::isGcReference); + boolean hasGcParams = funcType.params().stream().anyMatch(ValType::isGcReference); return new ExportFunction() { @Override public long[] apply(long... args) { - try { - return instance.machine.call(export.index(), args); - } finally { - instance.gcSafePoint(); + if (hasGcReturns || hasGcParams) { + throw new UnsupportedOperationException( + "Function '" + + name + + "' uses GC references. Use applyGc() instead."); } + return instance.machine.call(export.index(), args); } @Override public Object[] applyGc(Object... args) { - try { - return instance.machine.callGc(export.index(), args); - } finally { - instance.gcSafePoint(); - } + return instance.machine.callGc(export.index(), args); } }; } @@ -458,22 +453,22 @@ public WasmException exn(int idx) { return exnRefs.get(idx); } + @Deprecated public long[] array(int idx) { - var gcRef = gcRefs.get(idx); - if (gcRef instanceof WasmArray) { - return ((WasmArray) gcRef).elements(); - } - return null; + throw new UnsupportedOperationException( + "GcRefStore has been removed. Use applyGc() to get WasmArray objects directly."); } @Deprecated public int registerGcRef(WasmGcRef ref) { - return gcRefs.put(ref); + throw new UnsupportedOperationException( + "GcRefStore has been removed. GC references are managed by Java GC."); } @Deprecated public WasmGcRef gcRef(int idx) { - return gcRefs.get(idx); + throw new UnsupportedOperationException( + "GcRefStore has been removed. Use applyGc() to get GC references directly."); } public boolean heapTypeMatch( @@ -537,11 +532,6 @@ private boolean heapTypeSubOf(int actual, int target) { return ValType.heapTypeSubtype(actual, target, module.typeSection()); } - /** Epoch-based GC safe point. Call when the wasm stack is guaranteed empty. */ - void gcSafePoint() { - gcRefs.safePoint(); - } - public Machine getMachine() { return machine; } diff --git a/runtime/src/main/java/run/endive/runtime/internal/GcRefStore.java b/runtime/src/main/java/run/endive/runtime/internal/GcRefStore.java deleted file mode 100644 index bfefbc21..00000000 --- a/runtime/src/main/java/run/endive/runtime/internal/GcRefStore.java +++ /dev/null @@ -1,125 +0,0 @@ -package run.endive.runtime.internal; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import run.endive.runtime.Instance; -import run.endive.runtime.WasmArray; -import run.endive.runtime.WasmGcRef; -import run.endive.runtime.WasmStruct; -import run.endive.wasm.types.Value; - -/** - * Store for GC-managed references keyed by auto-assigned integers. - * - *

Uses epoch-based deferred collection: refs are never swept during wasm - * execution. Collection only happens at safe points — between - * top-level calls — when the wasm operand stack and all call frames are - * empty. At that point the only roots are globals and tables. - */ -public class GcRefStore { - - /** - * GC ref IDs start at this offset to avoid collisions with externref - * values that get internalized via any.convert_extern. Since internalized - * externrefs and GC refs both live in the ANY hierarchy, they share the - * same integer representation space. - */ - public static final int ID_OFFSET = 0x10000; - - private static final int SWEEP_INTERVAL = 4096; - - private final Instance instance; - private final Map map = new HashMap<>(); - private int nextId = ID_OFFSET; - private int allocsSinceLastSweep; - private boolean sweepRequested; - - public GcRefStore(Instance instance) { - this.instance = instance; - } - - /** Inserts a value with an automatically assigned key. */ - public int put(WasmGcRef value) { - int id = nextId++; - map.put(id, value); - allocsSinceLastSweep++; - if (allocsSinceLastSweep >= SWEEP_INTERVAL) { - sweepRequested = true; - } - return id; - } - - /** Retrieves a value by key, or null if missing. */ - public WasmGcRef get(int key) { - return map.get(key); - } - - /** Called at safe points (between top-level calls). */ - public void safePoint() { - if (sweepRequested) { - sweep(); - sweepRequested = false; - allocsSinceLastSweep = 0; - } - } - - /** Checks whether a raw reference value is a GC ref ID. */ - public static boolean isGcRefId(long val) { - return val >= ID_OFFSET && val != Value.REF_NULL_VALUE && !Value.isI31(val); - } - - private void sweep() { - Set reachable = new HashSet<>(); - - // 1. Scan globals - int globalCount = instance.globalCount(); - for (int i = 0; i < globalCount; i++) { - var g = instance.global(i); - if (g != null) { - markIfGcRef(g.getValueLow(), reachable); - } - } - - // 2. Scan tables - int tableCount = instance.tableCount(); - for (int i = 0; i < tableCount; i++) { - var table = instance.table(i); - if (table != null) { - for (int j = 0; j < table.size(); j++) { - markIfGcRef(table.ref(j), reachable); - } - } - } - - // 3. Remove unreachable entries - map.keySet().removeIf(id -> !reachable.contains(id)); - } - - private void markIfGcRef(long val, Set reachable) { - if (!isGcRefId(val)) { - return; - } - int id = (int) val; - if (!reachable.add(id)) { - return; // already visited — prevents infinite loops in cyclic structures - } - WasmGcRef ref = map.get(id); - if (ref == null) { - return; - } - // Recursively trace nested refs - if (ref instanceof WasmStruct) { - var s = (WasmStruct) ref; - for (int i = 0; i < s.fieldCount(); i++) { - markIfGcRef(s.field(i), reachable); - } - } else if (ref instanceof WasmArray) { - var a = (WasmArray) ref; - for (int i = 0; i < a.length(); i++) { - markIfGcRef(a.get(i), reachable); - } - } - } -} diff --git a/runtime/src/test/java/run/endive/runtime/internal/GcRefStoreTest.java b/runtime/src/test/java/run/endive/runtime/internal/GcRefStoreTest.java deleted file mode 100644 index e0d2a417..00000000 --- a/runtime/src/test/java/run/endive/runtime/internal/GcRefStoreTest.java +++ /dev/null @@ -1,57 +0,0 @@ -package run.endive.runtime.internal; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.io.ByteArrayInputStream; -import org.junit.jupiter.api.Test; -import run.endive.runtime.Instance; -import run.endive.runtime.WasmGcRef; -import run.endive.wasm.Parser; - -public class GcRefStoreTest { - - // Minimal valid wasm module: magic number + version 1 - private static final byte[] EMPTY_WASM = {0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00}; - - private static WasmGcRef ref(int typeIdx) { - return () -> typeIdx; - } - - private static GcRefStore newStore() { - var module = Parser.parse(new ByteArrayInputStream(EMPTY_WASM)); - var instance = Instance.builder(module).build(); - return new GcRefStore(instance); - } - - @Test - public void autoAssignKeysFromOffset() { - var store = newStore(); - int k0 = store.put(ref(0)); - int k1 = store.put(ref(1)); - int k2 = store.put(ref(2)); - assertEquals(GcRefStore.ID_OFFSET, k0); - assertEquals(GcRefStore.ID_OFFSET + 1, k1); - assertEquals(GcRefStore.ID_OFFSET + 2, k2); - assertEquals(0, store.get(k0).typeIdx()); - assertEquals(1, store.get(k1).typeIdx()); - assertEquals(2, store.get(k2).typeIdx()); - } - - @Test - public void getMissingKeyReturnsNull() { - var store = newStore(); - assertNull(store.get(0)); - assertNull(store.get(999)); - } - - @Test - public void isGcRefIdClassifiesCorrectly() { - assertTrue(GcRefStore.isGcRefId(GcRefStore.ID_OFFSET)); - assertTrue(GcRefStore.isGcRefId(GcRefStore.ID_OFFSET + 100)); - assertFalse(GcRefStore.isGcRefId(0)); - assertFalse(GcRefStore.isGcRefId(GcRefStore.ID_OFFSET - 1)); - } -} From ad207f4b19577d91cfd59f843ae012c04fc982c9 Mon Sep 17 00:00:00 2001 From: andreatp Date: Fri, 5 Jun 2026 10:17:14 +0100 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20interpreter=20GC=20ref=20handling=20?= =?UTF-8?q?=E2=80=94=20all=2062,144=20runtime=20tests=20green?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix all remaining interpreter GC reference bugs: - MStack.popRef/peekRef: handle null refs array gracefully - REF_TEST/CAST_TEST/BR_ON_CAST: dispatch based on source type (popRef for GC refs, pop for funcref/externref) - ARRAY_GET/SET: use isGcReference() not isReference() for field type checks — funcref/externref elements stay in long[] path - ARRAY_NEW_DEFAULT: fill with REF_NULL_VALUE for non-GC ref types - ConstantEvaluators ARRAY_NEW_DEFAULT: same fix for global init - callGc: convert REF_NULL_VALUE to null for all ref return types - apply(long...): throw UnsupportedOperationException only after execution succeeds (traps propagate correctly) - WasmExternRef: can wrap Object for extern.convert_any round-trips Test generator (JavaTestGen): - Emit applyGc() for functions with GC params/returns - Null ref assertions use assertNull() for all ref types - WasmValue: toGcArgsValue, toGcResultValue, toGcAssertion methods - WasmValueType.isGcReference() helper --- .../endive/runtime/ConstantEvaluators.java | 12 ++ .../java/run/endive/runtime/Instance.java | 9 +- .../endive/runtime/InterpreterMachine.java | 133 ++++++++++---- .../main/java/run/endive/runtime/MStack.java | 6 + .../java/run/endive/testgen/JavaTestGen.java | 155 +++++++++++------ .../run/endive/testgen/wast/WasmValue.java | 162 ++++++++++++++++++ .../endive/testgen/wast/WasmValueType.java | 15 ++ 7 files changed, 400 insertions(+), 92 deletions(-) diff --git a/runtime/src/main/java/run/endive/runtime/ConstantEvaluators.java b/runtime/src/main/java/run/endive/runtime/ConstantEvaluators.java index 21c332a4..25fff201 100644 --- a/runtime/src/main/java/run/endive/runtime/ConstantEvaluators.java +++ b/runtime/src/main/java/run/endive/runtime/ConstantEvaluators.java @@ -212,6 +212,18 @@ public static ConstantResult computeConstant(Instance instance, List callStack ARRAY_NEW(stack, instance, operands); break; case ARRAY_NEW_DEFAULT: - ARRAY_NEW_DEFAULT(stack, operands); + ARRAY_NEW_DEFAULT(stack, instance, operands); break; case ARRAY_NEW_FIXED: ARRAY_NEW_FIXED(stack, instance, operands); @@ -3424,7 +3431,7 @@ private static void STRUCT_NEW(MStack stack, Instance instance, Operands operand // Pop fields in reverse order (last field on top) for (int i = fields.length - 1; i >= 0; i--) { var ft = st.fieldTypes()[i]; - if (ft.storageType().isReference()) { + if (ft.storageType().isGcReference()) { fieldRefs[i] = stack.popRef(); } else { fields[i] = stack.pop(); @@ -3454,7 +3461,7 @@ private static void STRUCT_GET( var struct = (WasmStruct) structObj; var st = instance.module().typeSection().getSubType(typeIdx).compType().structType(); var ft = st.fieldTypes()[fieldIdx]; - if (ft.storageType().isReference()) { + if (ft.storageType().isGcReference()) { stack.pushRef(struct.fieldRef(fieldIdx)); } else { var val = struct.field(fieldIdx); @@ -3474,7 +3481,7 @@ private static void STRUCT_SET(MStack stack, Instance instance, Operands operand var fieldIdx = (int) operands.get(1); var st = instance.module().typeSection().getSubType(typeIdx).compType().structType(); var ft = st.fieldTypes()[fieldIdx]; - boolean isRef = ft.storageType().isReference(); + boolean isRef = ft.storageType().isGcReference(); Object refVal = null; long val = 0; if (isRef) { @@ -3502,7 +3509,7 @@ private static void ARRAY_NEW(MStack stack, Instance instance, Operands operands var at = instance.module().typeSection().getSubType(typeIdx).compType().arrayType(); boolean isRef = at.fieldType().storageType().valType() != null - && at.fieldType().storageType().isReference(); + && at.fieldType().storageType().isGcReference(); var len = (int) stack.pop(); if (isRef) { var initRef = stack.popRef(); @@ -3520,12 +3527,17 @@ private static void ARRAY_NEW(MStack stack, Instance instance, Operands operands } } - private static void ARRAY_NEW_DEFAULT(MStack stack, Operands operands) { + private static void ARRAY_NEW_DEFAULT(MStack stack, Instance instance, Operands operands) { var typeIdx = (int) operands.get(0); var len = (int) stack.pop(); - // Default values: 0 for numeric, null for references - // Both long[] and Object[] are zero/null-initialized by Java var elems = new long[len]; + var at = instance.module().typeSection().getSubType(typeIdx).compType().arrayType(); + var ft = at.fieldType(); + if (ft.storageType().valType() != null + && ft.storageType().valType().isReference() + && !ft.storageType().isGcReference()) { + java.util.Arrays.fill(elems, Value.REF_NULL_VALUE); + } var arr = new WasmArray(typeIdx, elems); stack.pushRef(arr); } @@ -3536,7 +3548,7 @@ private static void ARRAY_NEW_FIXED(MStack stack, Instance instance, Operands op var at = instance.module().typeSection().getSubType(typeIdx).compType().arrayType(); boolean isRef = at.fieldType().storageType().valType() != null - && at.fieldType().storageType().isReference(); + && at.fieldType().storageType().isGcReference(); var elems = new long[len]; if (isRef) { var elemRefs = new Object[len]; @@ -3584,7 +3596,7 @@ private static void ARRAY_NEW_ELEM(MStack stack, Instance instance, Operands ope throw new TrapException("out of bounds table access"); } var at = instance.module().typeSection().getSubType(typeIdx).compType().arrayType(); - boolean isRef = at.fieldType().storageType().isReference(); + boolean isRef = at.fieldType().storageType().isGcReference(); var elems = new long[len]; var elemRefs = new Object[len]; for (int i = 0; i < len; i++) { @@ -3613,8 +3625,7 @@ private static void ARRAY_GET( throw new TrapException("out of bounds array access"); } var at = instance.module().typeSection().getSubType(typeIdx).compType().arrayType(); - if (at.fieldType().storageType().valType() != null - && at.fieldType().storageType().isReference()) { + if (at.fieldType().storageType().isGcReference()) { stack.pushRef(arr.getRef(idx)); } else { var val = arr.get(idx); @@ -3634,7 +3645,7 @@ private static void ARRAY_SET(MStack stack, Instance instance, Operands operands var at = instance.module().typeSection().getSubType(typeIdx).compType().arrayType(); boolean isRef = at.fieldType().storageType().valType() != null - && at.fieldType().storageType().isReference(); + && at.fieldType().storageType().isGcReference(); Object refVal = null; long val = 0; if (isRef) { @@ -3675,7 +3686,7 @@ private static void ARRAY_FILL(MStack stack, Instance instance, Operands operand var at = instance.module().typeSection().getSubType(typeIdx).compType().arrayType(); boolean isRef = at.fieldType().storageType().valType() != null - && at.fieldType().storageType().isReference(); + && at.fieldType().storageType().isGcReference(); var len = (int) stack.pop(); Object refVal = null; long val = 0; @@ -3796,7 +3807,7 @@ private static void ARRAY_INIT_ELEM(MStack stack, Instance instance, Operands op return; } var at = instance.module().typeSection().getSubType(typeIdx).compType().arrayType(); - boolean isRef = at.fieldType().storageType().isReference(); + boolean isRef = at.fieldType().storageType().isGcReference(); for (int i = 0; i < len; i++) { var init = element.initializers().get(srcOffset + i); var result = ConstantEvaluators.computeConstant(instance, init); @@ -3808,28 +3819,52 @@ private static void ARRAY_INIT_ELEM(MStack stack, Instance instance, Operands op } } + private static boolean isSourceGcRef(int sourceHeapType) { + return sourceHeapType != ValType.TypeIdxCode.FUNC.code() + && sourceHeapType != ValType.TypeIdxCode.NOFUNC.code() + && sourceHeapType != ValType.TypeIdxCode.EXTERN.code() + && sourceHeapType != ValType.TypeIdxCode.NOEXTERN.code() + && sourceHeapType != ValType.TypeIdxCode.EXN.code(); + } + private static void REF_TEST( MStack stack, Instance instance, Operands operands, OpCode opcode) { var heapType = (int) operands.get(0); var sourceHeapType = (int) operands.get(1); - var ref = stack.popRef(); boolean nullable = (opcode == OpCode.REF_TEST_NULL); - stack.push( - instance.heapTypeMatchRef(ref, nullable, heapType, sourceHeapType) - ? Value.TRUE - : Value.FALSE); + if (isSourceGcRef(sourceHeapType)) { + var ref = stack.popRef(); + stack.push( + instance.heapTypeMatchRef(ref, nullable, heapType, sourceHeapType) + ? Value.TRUE + : Value.FALSE); + } else { + var val = stack.pop(); + stack.push( + instance.heapTypeMatch(val, nullable, heapType, sourceHeapType) + ? Value.TRUE + : Value.FALSE); + } } private static void CAST_TEST( MStack stack, Instance instance, Operands operands, OpCode opcode) { var heapType = (int) operands.get(0); var sourceHeapType = (int) operands.get(1); - var ref = stack.popRef(); boolean nullable = (opcode == OpCode.CAST_TEST_NULL); - if (!instance.heapTypeMatchRef(ref, nullable, heapType, sourceHeapType)) { - throw new TrapException("cast failure"); + if (isSourceGcRef(sourceHeapType)) { + var ref = stack.popRef(); + if (!instance.heapTypeMatchRef(ref, nullable, heapType, sourceHeapType)) { + throw new TrapException("cast failure"); + } + stack.pushRef(ref); + } else { + var val = stack.pop(); + if (!instance.heapTypeMatch(val, nullable, heapType, sourceHeapType)) { + throw new TrapException("cast failure"); + } + stack.push(val); } - stack.pushRef(ref); } private static void BR_ON_CAST( @@ -3842,13 +3877,24 @@ private static void BR_ON_CAST( var ht2 = (int) operands.get(3); var sourceHeapType = (int) operands.get(4); boolean null2 = (flags & 2) != 0; - var ref = stack.popRef(); - if (instance.heapTypeMatchRef(ref, null2, ht2, sourceHeapType)) { - stack.pushRef(ref); - ctrlJump(frame, stack, (int) operands.get(1)); - frame.jumpTo(instruction.labelTrue()); + if (isSourceGcRef(sourceHeapType)) { + var ref = stack.popRef(); + if (instance.heapTypeMatchRef(ref, null2, ht2, sourceHeapType)) { + stack.pushRef(ref); + ctrlJump(frame, stack, (int) operands.get(1)); + frame.jumpTo(instruction.labelTrue()); + } else { + stack.pushRef(ref); + } } else { - stack.pushRef(ref); + var val = stack.pop(); + if (instance.heapTypeMatch(val, null2, ht2, sourceHeapType)) { + stack.push(val); + ctrlJump(frame, stack, (int) operands.get(1)); + frame.jumpTo(instruction.labelTrue()); + } else { + stack.push(val); + } } } @@ -3862,13 +3908,24 @@ private static void BR_ON_CAST_FAIL( var ht2 = (int) operands.get(3); var sourceHeapType = (int) operands.get(4); boolean null2 = (flags & 2) != 0; - var ref = stack.popRef(); - if (!instance.heapTypeMatchRef(ref, null2, ht2, sourceHeapType)) { - stack.pushRef(ref); - ctrlJump(frame, stack, (int) operands.get(1)); - frame.jumpTo(instruction.labelTrue()); + if (isSourceGcRef(sourceHeapType)) { + var ref = stack.popRef(); + if (!instance.heapTypeMatchRef(ref, null2, ht2, sourceHeapType)) { + stack.pushRef(ref); + ctrlJump(frame, stack, (int) operands.get(1)); + frame.jumpTo(instruction.labelTrue()); + } else { + stack.pushRef(ref); + } } else { - stack.pushRef(ref); + var val = stack.pop(); + if (!instance.heapTypeMatch(val, null2, ht2, sourceHeapType)) { + stack.push(val); + ctrlJump(frame, stack, (int) operands.get(1)); + frame.jumpTo(instruction.labelTrue()); + } else { + stack.push(val); + } } } diff --git a/runtime/src/main/java/run/endive/runtime/MStack.java b/runtime/src/main/java/run/endive/runtime/MStack.java index cdec3443..244cd30f 100644 --- a/runtime/src/main/java/run/endive/runtime/MStack.java +++ b/runtime/src/main/java/run/endive/runtime/MStack.java @@ -65,6 +65,9 @@ public long pop() { public Object popRef() { count--; + if (refs == null) { + return null; + } Object ref = refs[count]; refs[count] = null; return ref; @@ -75,6 +78,9 @@ public long peek() { } public Object peekRef() { + if (refs == null) { + return null; + } return refs[count - 1]; } diff --git a/test-gen-lib/src/main/java/run/endive/testgen/JavaTestGen.java b/test-gen-lib/src/main/java/run/endive/testgen/JavaTestGen.java index 79f22924..d5577a5b 100644 --- a/test-gen-lib/src/main/java/run/endive/testgen/JavaTestGen.java +++ b/test-gen-lib/src/main/java/run/endive/testgen/JavaTestGen.java @@ -77,6 +77,7 @@ public CompilationUnit generate(String name, Wast wast, String wasmClasspath) { cu.addImport("org.junit.jupiter.api.Assertions.assertNotNull", true, false); cu.addImport("org.junit.jupiter.api.Assertions.assertThrows", true, false); cu.addImport("org.junit.jupiter.api.Assertions.assertTrue", true, false); + cu.addImport("org.junit.jupiter.api.Assertions.assertNull", true, false); cu.addImport("org.junit.jupiter.api.Assertions.assertDoesNotThrow", true, false); // testing imports @@ -371,29 +372,64 @@ private Optional generateFieldExport( } } + /** + * Check if a command involves GC reference types in its args or expected values. + * If so, we must use applyGc(Object...) instead of apply(long...). + */ + private static boolean needsGcCall(Command cmd) { + if (cmd.action() != null && cmd.action().args() != null) { + for (var arg : cmd.action().args()) { + if (arg.type().isGcReference()) { + return true; + } + } + } + if (cmd.expected() != null) { + for (var expected : cmd.expected()) { + if (expected.type().isGcReference()) { + return true; + } + } + } + return false; + } + private List generateAssert(String varName, Command cmd, String moduleName) { assert (cmd.type() == CommandType.ASSERT_RETURN || cmd.type() == CommandType.ASSERT_TRAP || cmd.type() == CommandType.ASSERT_EXCEPTION || cmd.type() == CommandType.ASSERT_EXHAUSTION); - var args = - (cmd.action().args() != null) - ? Arrays.stream(cmd.action().args()) - .map(WasmValue::toArgsValue) - .collect(Collectors.toList()) - : List.of(); - - var adaptedArgs = - (args == null || args.size() == 0) - ? "" - : args.stream().collect(Collectors.joining(").add(", ".add(", ")")); + boolean useGc = needsGcCall(cmd); - // Function or Global - var invocationMethod = - (cmd.action().type() == ActionType.INVOKE) - ? ".apply(ArgsAdapter.builder()" + adaptedArgs + ".build()" + ")" - : ".getValue()"; + String invocationMethod; + if (cmd.action().type() == ActionType.INVOKE) { + if (useGc) { + var gcArgs = + (cmd.action().args() != null) + ? Arrays.stream(cmd.action().args()) + .map(WasmValue::toGcArgsValue) + .collect(Collectors.toList()) + : List.of(); + var gcArgsStr = + (gcArgs.isEmpty()) ? "" : gcArgs.stream().collect(Collectors.joining(", ")); + invocationMethod = ".applyGc(" + gcArgsStr + ")"; + } else { + var args = + (cmd.action().args() != null) + ? Arrays.stream(cmd.action().args()) + .map(WasmValue::toArgsValue) + .collect(Collectors.toList()) + : List.of(); + var adaptedArgs = + (args == null || args.size() == 0) + ? "" + : args.stream().collect(Collectors.joining(").add(", ".add(", ")")); + invocationMethod = ".apply(ArgsAdapter.builder()" + adaptedArgs + ".build()" + ")"; + } + } else { + invocationMethod = ".getValue()"; + } if (cmd.type() == CommandType.ASSERT_TRAP || cmd.type() == CommandType.ASSERT_EXHAUSTION @@ -421,37 +457,47 @@ private List generateAssert(String varName, Command cmd, String modu for (int i = 0; i < cmd.expected().length; i++) { var expected = cmd.expected()[i]; - var resultVar = - (cmd.action().type() == ActionType.INVOKE) - ? expected.toResultValue(resVarName + "[" + i + "]") - : expected.toResultValue(resVarName); - - if (expected.type().equals(WasmValueType.V128)) { - exprs.add(new NameExpr("var expected = " + resultVar)); - switch (expected.laneType()) { - case I8: - exprs.add( - new NameExpr( - "assertArrayEquals(expected," + " vecTo8(results))")); - break; - case I16: - exprs.add( - new NameExpr("assertArrayEquals(expected, vecTo16(results))")); - break; - case I32: - exprs.add( - new NameExpr( - "assertArrayEquals(expected," + " vecTo32(results))")); - break; - case F32: - exprs.add( - new NameExpr( - "assertArrayEquals(expected," + " vecToF32(results))")); - break; - } + if (useGc && cmd.action().type() == ActionType.INVOKE) { + // GC path: results are Object[] + var resultVar = expected.toGcResultValue(resVarName + "[" + i + "]"); + exprs.add(expected.toGcAssertion(resultVar, moduleName)); } else { - exprs.add(expected.toAssertion(resultVar, moduleName)); + var resultVar = + (cmd.action().type() == ActionType.INVOKE) + ? expected.toResultValue(resVarName + "[" + i + "]") + : expected.toResultValue(resVarName); + + if (expected.type().equals(WasmValueType.V128)) { + exprs.add(new NameExpr("var expected = " + resultVar)); + switch (expected.laneType()) { + case I8: + exprs.add( + new NameExpr( + "assertArrayEquals(expected," + + " vecTo8(results))")); + break; + case I16: + exprs.add( + new NameExpr( + "assertArrayEquals(expected, vecTo16(results))")); + break; + case I32: + exprs.add( + new NameExpr( + "assertArrayEquals(expected," + + " vecTo32(results))")); + break; + case F32: + exprs.add( + new NameExpr( + "assertArrayEquals(expected," + + " vecToF32(results))")); + break; + } + } else { + exprs.add(expected.toAssertion(resultVar, moduleName)); + } } } @@ -466,11 +512,20 @@ private List generateInvoke(String varName, Command cmd) { String invocationMethod; if (cmd.action().type() == ActionType.INVOKE) { - var args = - Arrays.stream(cmd.action().args()) - .map(WasmValue::toArgsValue) - .collect(Collectors.joining(", ")); - invocationMethod = ".apply(" + args + ")"; + boolean useGc = needsGcCall(cmd); + if (useGc) { + var gcArgs = + Arrays.stream(cmd.action().args()) + .map(WasmValue::toGcArgsValue) + .collect(Collectors.joining(", ")); + invocationMethod = ".applyGc(" + gcArgs + ")"; + } else { + var args = + Arrays.stream(cmd.action().args()) + .map(WasmValue::toArgsValue) + .collect(Collectors.joining(", ")); + invocationMethod = ".apply(" + args + ")"; + } } else { throw new IllegalArgumentException("Unhandled action type " + cmd.action().type()); } diff --git a/test-gen-lib/src/main/java/run/endive/testgen/wast/WasmValue.java b/test-gen-lib/src/main/java/run/endive/testgen/wast/WasmValue.java index cc6af11b..49a08fc7 100644 --- a/test-gen-lib/src/main/java/run/endive/testgen/wast/WasmValue.java +++ b/test-gen-lib/src/main/java/run/endive/testgen/wast/WasmValue.java @@ -288,6 +288,168 @@ public String intLaneValue(String v) { return Integer.toUnsignedString((int) (0xFFFFFFFF & longValue)) + "L"; } + /** + * Generate an arg value for use with applyGc(Object...). + * Numeric types are boxed, GC ref types use null or the value directly. + */ + public String toGcArgsValue() { + switch (type) { + case I32: + return "(Object) (long) (int) Integer.parseInt(\"" + value[0] + "\")"; + case F32: + if (value[0] != null) { + switch (value[0]) { + case "nan:canonical": + case "nan:arithmetic": + return "(Object) (long) (int) (int) Float.NaN"; + default: + return "(Object) (long) (int) Integer.parseUnsignedInt(\"" + + value[0] + + "\")"; + } + } else { + return "null"; + } + case I64: + return "(Object) (long) Long.parseLong(\"" + value[0] + "\")"; + case F64: + if (value[0] != null) { + switch (value[0]) { + case "nan:canonical": + case "nan:arithmetic": + return "(Object) (long) (long) Double.NaN"; + default: + return "(Object) (long) Long.parseUnsignedLong(\"" + value[0] + "\")"; + } + } else { + return "null"; + } + case EXTERN_REF: + case EXN_REF: + case FUNC_REF: + case NULL_FUNC_REF: + case NULL_EXTERN_REF: + if (value[0].equals("null")) { + return "(Object) Value.REF_NULL_VALUE"; + } + return "(Object) (long) Long.parseLong(\"" + value[0] + "\")"; + case STRUCT_REF: + case ANY_REF: + case NULL_REF: + case ARRAY_REF: + case EQ_REF: + case I31_REF: + case REF_NULL: + if (value[0].equals("null")) { + return "(Object) null"; + } + return "(Object) (long) Long.parseLong(\"" + value[0] + "\")"; + default: + throw new IllegalArgumentException("Type not recognized for GC args: " + type); + } + } + + /** + * Generate the result extraction expression for Object[] results from applyGc. + * callGc uses boxReturnValue which returns: + * I32 -> Integer, I64 -> Long, F32 -> Float, F64 -> Double + * GC ref types are returned as Objects directly via popRef(). + */ + public String toGcResultValue(String result) { + switch (type) { + case I64: + return "(long)(Long) " + result; + case I32: + return "(int)(Integer) " + result; + case F32: + return "(float)(Float) " + result + ", 0.0"; + case F64: + return "(double)(Double) " + result + ", 0.0"; + case EXTERN_REF: + case EXN_REF: + case FUNC_REF: + case NULL_FUNC_REF: + case NULL_EXTERN_REF: + case STRUCT_REF: + case ANY_REF: + case NULL_REF: + case ARRAY_REF: + case EQ_REF: + case I31_REF: + case REF_NULL: + // All ref types are returned as Objects + return result; + case V128: + return toResultValue(result); + default: + throw new IllegalArgumentException("Type not recognized " + type); + } + } + + /** + * Generate assertion for Object[] results from applyGc. + * + * GC ref types (anyref, structref, etc.) are returned via popRef() as Java Objects, + * so null refs are Java null. Non-GC ref types (externref, funcref) are returned + * via boxReturnValue() as Long values, so null refs are REF_NULL_VALUE (-1). + */ + public NameExpr toGcAssertion(String resultVar, String moduleName) { + if (value == null) { + // Type-only assertion (no specific value) + switch (type) { + case FUNC_REF: + return new NameExpr("assertNotNull(" + resultVar + ")"); + case EXTERN_REF: + return new NameExpr("assertNotNull(" + resultVar + ")"); + case REF_NULL: + case NULL_REF: + // These are GC null types -> Java null from popRef() + return new NameExpr("assertNull(" + resultVar + ")"); + case NULL_FUNC_REF: + case NULL_EXTERN_REF: + return new NameExpr("assertNull(" + resultVar + ")"); + case STRUCT_REF: + case ANY_REF: + case I31_REF: + case ARRAY_REF: + case EQ_REF: + return new NameExpr("assertNotNull(" + resultVar + ")"); + default: + throw new IllegalArgumentException( + "cannot generate GC assertion for WasmValue: " + this); + } + } + + // Value-based assertion + switch (type) { + case STRUCT_REF: + case ANY_REF: + case NULL_REF: + case ARRAY_REF: + case EQ_REF: + case I31_REF: + case REF_NULL: + // GC ref types: null -> Java null, non-null -> assertNotNull + if (value[0].equals("null")) { + return new NameExpr("assertNull(" + resultVar + ")"); + } + return new NameExpr("assertNotNull(" + resultVar + ")"); + case EXTERN_REF: + case EXN_REF: + case FUNC_REF: + case NULL_FUNC_REF: + case NULL_EXTERN_REF: + if (value[0].equals("null")) { + return new NameExpr("assertNull(" + resultVar + ")"); + } + return new NameExpr("assertNotNull(" + resultVar + ")"); + default: + // Numeric types in GC path + var expectedVar = toExpectedValue(); + return new NameExpr("assertEquals(" + expectedVar + ", " + resultVar + ")"); + } + } + public String toArgsValue() { switch (type) { case I32: diff --git a/test-gen-lib/src/main/java/run/endive/testgen/wast/WasmValueType.java b/test-gen-lib/src/main/java/run/endive/testgen/wast/WasmValueType.java index 379fe2ca..87a846c6 100644 --- a/test-gen-lib/src/main/java/run/endive/testgen/wast/WasmValueType.java +++ b/test-gen-lib/src/main/java/run/endive/testgen/wast/WasmValueType.java @@ -49,4 +49,19 @@ public enum WasmValueType { public String value() { return value; } + + public boolean isGcReference() { + switch (this) { + case STRUCT_REF: + case ANY_REF: + case NULL_REF: + case ARRAY_REF: + case EQ_REF: + case I31_REF: + case REF_NULL: + return true; + default: + return false; + } + } }