From 5ebcc5a68f7bdbe410a606cf725056bd0cf7dc4c Mon Sep 17 00:00:00 2001 From: andreatp Date: Tue, 2 Jun 2026 12:55:57 +0100 Subject: [PATCH 01/20] 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 2d0a43688..21c332a44 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 48a93af9e..8dc045448 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 2d4a70085..b8d1086f5 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 29c7f7c5d..ca902e75e 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 40d002556..cdec34436 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 d981d58ae..56510284c 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 f0edaae9f..27008b6a7 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 eb684f7a9..2dc8de8ed 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 36eb8cf36..9717680c5 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 a46fb3d47..b452aed0b 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 8a03239e3..4df863b1f 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 35e3eb049..76aee0d76 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 b725f75a0..d09cca7ac 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 c869c1aa7..1d2e90a5f 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 189de8df6..96d12e679 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 02/20] 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 f3d3f4696..8fd071b89 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 ef6f8d878..94844c20b 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 40e768259..d92b98802 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 38107d097..ce52d02f6 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 89b5420b0..0b135228e 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 6c8522384..20d64b2c5 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 776e13b08..72aed25e8 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 65c1516f8..8bcc3ff05 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 3ddbf9272..b5b7a9583 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 77e775c09..9e25e57f3 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 b2426373d..403480540 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 926ddb655..c5fb763e6 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 0bf4dd66e..a4e049a95 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 6a5ef2a4b..a7f74f147 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 469719821..09937f011 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 29484bd45..a46e0cdbe 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 c2aa894fc..8efbaeec5 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 8b24e2d46..c9dce7bcd 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 b2d2bcfcc..267fb98aa 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 a0379e79c..4fd76007a 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 9e1c20b63..3df5c3ff9 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 82ccd2795..872dad807 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 b8d1086f5..d5407a28e 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 ca902e75e..04d4b7a80 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 56510284c..58f7b187c 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 27008b6a7..27dbd933f 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 9717680c5..3b88a04dd 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 92e34c192..20e3842f2 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 627cdc08f..d7c87fcd0 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 96d12e679..ee1375daf 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 03/20] =?UTF-8?q?feat:=20remove=20GcRefStore=20=E2=80=94?= =?UTF-8?q?=20Java=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 d5407a28e..95be3987a 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 bfefbc214..000000000 --- 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 e0d2a4173..000000000 --- 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 04/20] =?UTF-8?q?fix:=20interpreter=20GC=20ref=20handling?= =?UTF-8?q?=20=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 21c332a44..25fff2012 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 cdec34436..244cd30f9 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 79f22924c..d5577a5ba 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 cc6af11b5..49a08fc7b 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 379fe2cac..87a846c6a 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; + } + } } From 0cac8eaefb2db4cd452ea50ce82f15c53712701a Mon Sep 17 00:00:00 2001 From: andreatp Date: Sat, 6 Jun 2026 23:44:11 +0100 Subject: [PATCH 05/20] =?UTF-8?q?fix:=20compiler=20GC=20ref=20handling=20?= =?UTF-8?q?=E2=80=94=20full=20mvn=20verify=20passes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix all remaining compiler and interpreter GC reference bugs: Compiler: - Generate callGc override in compiled Machine class - Add int-based variants for refTest/castTest/heapTypeMatch (non-GC refs) - Add GC table operations: tableGetRef, tableSetRef, tableGrowRef, tableFillRef - Add extern conversion helpers in Shaded - Fix THROW to handle GC ref tag params (createWasmExceptionGc) - Fix CATCH_UNBOX_PARAMS to load GC refs from refArgs - Fix arrayNewElem/arrayInitElem to use computeConstant for GC elements - Fix WasmAnalyzer to track source types for REF_TEST dispatch Interpreter: - BR_ON_NULL/BR_ON_NON_NULL: check Object side for GC refs, long side for funcref - MStack.push(): clear stale refs (if refs != null) for correctness - ConstantEvaluators: use isGcReference() not isReference() for field type checks - STRUCT_NEW_DEFAULT/ARRAY_NEW_DEFAULT: fill REF_NULL_VALUE for non-GC ref fields - WasmException: carry Object[] refArgs for GC ref exception payloads Bridge: - CompilerInterpreterMachine.CALL: use callGc for functions with GC returns - Pass refArgs through compiled-to-interpreted boundary Tests: - BrOnNullTest: use applyGc for GC functions - All approval snapshots updated --- ...tInterpreterFallback-indirect.approved.txt | 2 +- ...testSilentInterpreterFallback.approved.txt | 2 +- ...ethodTooLargeTest.testBigFunc.approved.txt | 2 + .../endive/compiler/internal/Compiler.java | 115 ++++++++-------- .../run/endive/compiler/internal/Context.java | 8 ++ .../endive/compiler/internal/Emitters.java | 122 +++++++++++++---- .../run/endive/compiler/internal/Shaded.java | 126 +++++++++++++++--- .../endive/compiler/internal/ShadedRefs.java | 58 ++++++++ .../compiler/internal/WasmAnalyzer.java | 87 +++++++++--- .../ApprovalTest.functions10.approved.txt | 18 +++ .../ApprovalTest.verifyBrTable.approved.txt | 18 +++ .../ApprovalTest.verifyBranching.approved.txt | 18 +++ .../ApprovalTest.verifyFloat.approved.txt | 18 +++ .../ApprovalTest.verifyGc.approved.txt | 11 +- .../ApprovalTest.verifyHelloWasi.approved.txt | 18 +++ .../ApprovalTest.verifyI32.approved.txt | 18 +++ ...ApprovalTest.verifyI32Renamed.approved.txt | 18 +++ .../ApprovalTest.verifyIterFact.approved.txt | 18 +++ ...pprovalTest.verifyKitchenSink.approved.txt | 18 +++ .../ApprovalTest.verifyMemory.approved.txt | 18 +++ .../ApprovalTest.verifyStart.approved.txt | 18 +++ .../ApprovalTest.verifyTailCall.approved.txt | 18 +++ .../ApprovalTest.verifyTrap.approved.txt | 18 +++ .../java/run/endive/testing/BrOnNullTest.java | 4 +- .../endive/runtime/ConstantEvaluators.java | 17 ++- .../java/run/endive/runtime/Instance.java | 19 +-- .../endive/runtime/InterpreterMachine.java | 78 +++++++---- .../main/java/run/endive/runtime/MStack.java | 3 + .../run/endive/runtime/WasmException.java | 10 ++ .../internal/CompilerInterpreterMachine.java | 47 ++++++- 30 files changed, 756 insertions(+), 189 deletions(-) diff --git a/compiler-tests/src/test/resources/run/endive/testing/InterpreterFallbackTest.testSilentInterpreterFallback-indirect.approved.txt b/compiler-tests/src/test/resources/run/endive/testing/InterpreterFallbackTest.testSilentInterpreterFallback-indirect.approved.txt index e378ab761..f049c0d2d 100644 --- a/compiler-tests/src/test/resources/run/endive/testing/InterpreterFallbackTest.testSilentInterpreterFallback-indirect.approved.txt +++ b/compiler-tests/src/test/resources/run/endive/testing/InterpreterFallbackTest.testSilentInterpreterFallback-indirect.approved.txt @@ -16,4 +16,4 @@ run.endive.testing.Test3MachineFuncGroup_0.func_3 run.endive.testing.Test3MachineFuncGroup_0.call_3 run.endive.testing.Test3MachineMachineCall.call run.endive.testing.Test3Machine.call -run.endive.runtime.Instance$Exports.lambda$function$0 \ No newline at end of file +run.endive.runtime.Instance$Exports$1.apply \ No newline at end of file diff --git a/compiler-tests/src/test/resources/run/endive/testing/InterpreterFallbackTest.testSilentInterpreterFallback.approved.txt b/compiler-tests/src/test/resources/run/endive/testing/InterpreterFallbackTest.testSilentInterpreterFallback.approved.txt index a68c3e51e..12c12b07e 100644 --- a/compiler-tests/src/test/resources/run/endive/testing/InterpreterFallbackTest.testSilentInterpreterFallback.approved.txt +++ b/compiler-tests/src/test/resources/run/endive/testing/InterpreterFallbackTest.testSilentInterpreterFallback.approved.txt @@ -16,4 +16,4 @@ run.endive.testing.Test3MachineFuncGroup_0.func_3 run.endive.testing.Test3MachineFuncGroup_0.call_3 run.endive.testing.Test3MachineMachineCall.call run.endive.testing.Test3Machine.call -run.endive.runtime.Instance$Exports.lambda$function$0 \ No newline at end of file +run.endive.runtime.Instance$Exports$1.apply \ No newline at end of file diff --git a/compiler-tests/src/test/resources/run/endive/testing/MethodTooLargeTest.testBigFunc.approved.txt b/compiler-tests/src/test/resources/run/endive/testing/MethodTooLargeTest.testBigFunc.approved.txt index 58922d89c..223ded271 100644 --- a/compiler-tests/src/test/resources/run/endive/testing/MethodTooLargeTest.testBigFunc.approved.txt +++ b/compiler-tests/src/test/resources/run/endive/testing/MethodTooLargeTest.testBigFunc.approved.txt @@ -3,6 +3,8 @@ private final Lrun/endive/runtime/Instance; instance private final Lrun/endive/runtime/internal/CompilerInterpreterMachine; compilerInterpreterMachine } + public final static INNERCLASS run/endive/runtime/ConstantEvaluators$ConstantResult run/endive/runtime/ConstantEvaluators ConstantResult + private final static Z memCopyWorkaround } 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 8fd071b89..54c2f1d3d 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/Compiler.java +++ b/compiler/src/main/java/run/endive/compiler/internal/Compiler.java @@ -76,7 +76,6 @@ 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; @@ -95,16 +94,6 @@ public final class Compiler { Type.getType(CompilerInterpreterMachine.class); private static final Type INSTANCE_TYPE = Type.getType(Instance.class); - private static final java.lang.reflect.Method INSTANCE_REGISTER_GC_REF; - - 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); @@ -534,14 +523,12 @@ private byte[] compileClass() { null, null); - if (!interpretedFunctions.isEmpty()) { - classWriter.visitField( - Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL, - "compilerInterpreterMachine", - getDescriptor(CompilerInterpreterMachine.class), - null, - null); - } + classWriter.visitField( + Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL, + "compilerInterpreterMachine", + getDescriptor(CompilerInterpreterMachine.class), + null, + null); // constructor emitFunction( @@ -573,6 +560,32 @@ private byte[] compileClass() { false, asm -> compileMachineCall(internalClassName, asm, 3)); + // Machine.callGc(int, Object[]) implementation + // Delegates to compilerInterpreterMachine.callGc() which handles + // arg splitting, interpreter execution, and result boxing. + // Locals: 0=this, 1=funcId, 2=args + emitFunction( + classWriter, + "callGc", + methodType(Object[].class, int.class, Object[].class), + false, + asm -> { + asm.load(0, OBJECT_TYPE); + asm.getfield( + internalClassName, + "compilerInterpreterMachine", + getDescriptor(CompilerInterpreterMachine.class)); + asm.load(1, INT_TYPE); + asm.load(2, OBJECT_TYPE); + asm.invokevirtual( + AOT_INTERPRETER_MACHINE_TYPE.getInternalName(), + "callGc", + methodType(Object[].class, int.class, Object[].class) + .toMethodDescriptorString(), + false); + asm.areturn(OBJECT_TYPE); + }); + // call_indirect_xxx() bridges for native CALL_INDIRECT // When using bridge classes, these methods are on separate classes if (!useBridgeClasses) { @@ -698,35 +711,33 @@ private void compileConstructor(InstructionAdapter asm, String internalClassName asm.load(1, OBJECT_TYPE); asm.putfield(internalClassName, "instance", getDescriptor(Instance.class)); - if (!interpretedFunctions.isEmpty()) { + // Always create compilerInterpreterMachine for callGc support + asm.load(0, OBJECT_TYPE); + asm.anew(AOT_INTERPRETER_MACHINE_TYPE); + asm.dup(); + asm.load(1, OBJECT_TYPE); - asm.load(0, OBJECT_TYPE); - asm.anew(AOT_INTERPRETER_MACHINE_TYPE); + // construct int[] with the interpreted function ids + var funcIds = new ArrayList<>(interpretedFunctions); + asm.iconst(funcIds.size()); + asm.newarray(INT_TYPE); + for (int i = 0; i < funcIds.size(); i++) { asm.dup(); - asm.load(1, OBJECT_TYPE); - - // construct int[] with the interpreted function ids - var funcIds = new ArrayList<>(interpretedFunctions); - asm.iconst(funcIds.size()); - asm.newarray(INT_TYPE); - for (int i = 0; i < funcIds.size(); i++) { - asm.dup(); - asm.iconst(i); - asm.iconst(funcIds.get(i)); - asm.astore(INT_TYPE); - } - - asm.invokespecial( - AOT_INTERPRETER_MACHINE_TYPE.getInternalName(), - "", - getMethodDescriptor(VOID_TYPE, INSTANCE_TYPE, INT_ARRAY_TYPE), - false); - asm.putfield( - internalClassName, - "compilerInterpreterMachine", - getDescriptor(CompilerInterpreterMachine.class)); + asm.iconst(i); + asm.iconst(funcIds.get(i)); + asm.astore(INT_TYPE); } + asm.invokespecial( + AOT_INTERPRETER_MACHINE_TYPE.getInternalName(), + "", + getMethodDescriptor(VOID_TYPE, INSTANCE_TYPE, INT_ARRAY_TYPE), + false); + asm.putfield( + internalClassName, + "compilerInterpreterMachine", + getDescriptor(CompilerInterpreterMachine.class)); + asm.areturn(VOID_TYPE); } @@ -1095,20 +1106,12 @@ private void compileCallFunction(int funcId, FunctionType type, InstructionAdapt if (returnType == void.class) { asm.aconst(null); } 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); + // GC ref return: discard the Object result and return a dummy long[]{0}. + // GC ref results should be accessed via callGc() which goes through the + // interpreter path and handles Object results natively. + asm.pop(); 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(4, LONG_TYPE); diff --git a/compiler/src/main/java/run/endive/compiler/internal/Context.java b/compiler/src/main/java/run/endive/compiler/internal/Context.java index 465fd5e1f..c14cc5923 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/Context.java +++ b/compiler/src/main/java/run/endive/compiler/internal/Context.java @@ -180,6 +180,14 @@ public String classNameForFuncGroup(String prefix, int funcId) { return prefix + "FuncGroup_" + (funcId / maxFunctionsPerClass); } + /** + * Returns true if the table at the given index has a GC reference element type. + */ + public boolean isGcTable(int tableIndex) { + var tableTypes = WasmAnalyzer.getTableTypes(module); + return tableIndex < tableTypes.size() && tableTypes.get(tableIndex).isGcReference(); + } + public FunctionType tagFunctionType(int tagId) { if (tagId < 0) { throw new IllegalArgumentException("Tag ID must be non-negative"); 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 d92b98802..528f8d442 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/Emitters.java +++ b/compiler/src/main/java/run/endive/compiler/internal/Emitters.java @@ -83,6 +83,20 @@ public static ValType valType(long id, Context ctx) { return ValType.builder().fromId(id).build().resolve(ctx.typeSection()); } + /** + * Determines whether a source heap type represents a GC reference (Object on JVM stack) + * or a non-GC reference (int on JVM stack, e.g. funcref/externref). + */ + private static boolean isSourceGcRef(int srcHeapType, Context ctx) { + var srcType = + ValType.builder() + .withOpcode(ValType.ID.RefNull) + .withTypeIdx(srcHeapType) + .build() + .resolve(ctx.typeSection()); + return srcType.isGcReference(); + } + public static void DROP_KEEP(Context ctx, CompilerInstruction ins, InstructionAdapter asm) { int keepStart = (int) ins.operand(0) + 1; @@ -407,15 +421,19 @@ public static void GLOBAL_SET(Context ctx, CompilerInstruction ins, InstructionA } public static void TABLE_GET(Context ctx, CompilerInstruction ins, InstructionAdapter asm) { - asm.iconst((int) ins.operand(0)); + int tableIdx = (int) ins.operand(0); + boolean isGcRef = ins.operand(1) != 0; + asm.iconst(tableIdx); asm.load(ctx.instanceSlot(), OBJECT_TYPE); - emitInvokeStatic(asm, ShadedRefs.TABLE_GET); + emitInvokeStatic(asm, isGcRef ? ShadedRefs.TABLE_GET_REF : ShadedRefs.TABLE_GET); } public static void TABLE_SET(Context ctx, CompilerInstruction ins, InstructionAdapter asm) { - asm.iconst((int) ins.operand(0)); + int tableIdx = (int) ins.operand(0); + boolean isGcRef = ins.operand(1) != 0; + asm.iconst(tableIdx); asm.load(ctx.instanceSlot(), OBJECT_TYPE); - emitInvokeStatic(asm, ShadedRefs.TABLE_SET); + emitInvokeStatic(asm, isGcRef ? ShadedRefs.TABLE_SET_REF : ShadedRefs.TABLE_SET); } public static void TABLE_SIZE(Context ctx, CompilerInstruction ins, InstructionAdapter asm) { @@ -425,15 +443,19 @@ public static void TABLE_SIZE(Context ctx, CompilerInstruction ins, InstructionA } public static void TABLE_GROW(Context ctx, CompilerInstruction ins, InstructionAdapter asm) { - asm.iconst((int) ins.operand(0)); + int tableIdx = (int) ins.operand(0); + boolean isGcRef = ins.operand(1) != 0; + asm.iconst(tableIdx); asm.load(ctx.instanceSlot(), OBJECT_TYPE); - emitInvokeStatic(asm, ShadedRefs.TABLE_GROW); + emitInvokeStatic(asm, isGcRef ? ShadedRefs.TABLE_GROW_REF : ShadedRefs.TABLE_GROW); } public static void TABLE_FILL(Context ctx, CompilerInstruction ins, InstructionAdapter asm) { - asm.iconst((int) ins.operand(0)); + int tableIdx = (int) ins.operand(0); + boolean isGcRef = ins.operand(1) != 0; + asm.iconst(tableIdx); asm.load(ctx.instanceSlot(), OBJECT_TYPE); - emitInvokeStatic(asm, ShadedRefs.TABLE_FILL); + emitInvokeStatic(asm, isGcRef ? ShadedRefs.TABLE_FILL_REF : ShadedRefs.TABLE_FILL); } public static void TABLE_COPY(Context ctx, CompilerInstruction ins, InstructionAdapter asm) { @@ -1288,12 +1310,18 @@ public static void THROW(Context ctx, CompilerInstruction ins, InstructionAdapte int tagNumber = (int) ins.operand(0); var type = ctx.tagFunctionType(tagNumber); - // emmit: - // call createWasmException(long[] args, int tagNumber, Instance instance) - emitBoxValuesOnStack(ctx, asm, type.params()); - asm.iconst(tagNumber); - asm.load(ctx.instanceSlot(), OBJECT_TYPE); - emitInvokeStatic(asm, ShadedRefs.CREATE_WASM_EXCEPTION); + boolean hasGcRefs = type.params().stream().anyMatch(ValType::isGcReference); + if (hasGcRefs) { + emitBoxFieldsForStruct(ctx, asm, type.params()); + asm.iconst(tagNumber); + asm.load(ctx.instanceSlot(), OBJECT_TYPE); + emitInvokeStatic(asm, ShadedRefs.CREATE_WASM_EXCEPTION_GC); + } else { + emitBoxValuesOnStack(ctx, asm, type.params()); + asm.iconst(tagNumber); + asm.load(ctx.instanceSlot(), OBJECT_TYPE); + emitInvokeStatic(asm, ShadedRefs.CREATE_WASM_EXCEPTION); + } asm.athrow(); } @@ -1381,22 +1409,46 @@ public static void TRY_RESTORE_STACK( public static void CATCH_UNBOX_PARAMS( Context ctx, CompilerInstruction ins, InstructionAdapter asm) { var tag = (int) ins.operand(0); - // Get the tag type to know what - // parameter types to unbox var tagFuncType = ctx.tagFunctionType(tag); if (!tagFuncType.params().isEmpty()) { - // unbox the exception args + boolean hasGcRefs = tagFuncType.params().stream().anyMatch(ValType::isGcReference); + int longArraySlot = ctx.tempSlot() + 1; + int refArraySlot = ctx.tempSlot() + 2; + + // Load long[] args asm.load(ctx.tempSlot(), OBJECT_TYPE); asm.invokevirtual( getInternalName(WasmException.class), "args", getMethodDescriptor(getType(long[].class)), false); + asm.store(longArraySlot, OBJECT_TYPE); + + // Load Object[] refArgs if needed + if (hasGcRefs) { + asm.load(ctx.tempSlot(), OBJECT_TYPE); + asm.invokevirtual( + getInternalName(WasmException.class), + "refArgs", + getMethodDescriptor(getType(Object[].class)), + false); + asm.store(refArraySlot, OBJECT_TYPE); + } - // Store the array in a local variable - // Unbox each argument from the - // long[] array and push onto stack - emitUnboxResult(asm, tagFuncType.params(), ctx.tempSlot() + 1); + // Unbox each argument + for (int i = 0; i < tagFuncType.params().size(); i++) { + var param = tagFuncType.params().get(i); + if (param.isGcReference()) { + asm.load(refArraySlot, OBJECT_TYPE); + asm.iconst(i); + asm.aload(OBJECT_TYPE); + } else { + asm.load(longArraySlot, OBJECT_TYPE); + asm.iconst(i); + asm.aload(LONG_TYPE); + emitLongToJvm(asm, param); + } + } } } @@ -1804,39 +1856,43 @@ public static void ARRAY_INIT_ELEM( public static void REF_TEST(Context ctx, CompilerInstruction ins, InstructionAdapter asm) { int heapType = (int) ins.operand(0); int srcHeapType = (int) ins.operand(1); + boolean isGcRef = isSourceGcRef(srcHeapType, ctx); // stack: [ref] asm.iconst(heapType); asm.iconst(srcHeapType); asm.load(ctx.instanceSlot(), OBJECT_TYPE); - emitInvokeStatic(asm, ShadedRefs.REF_TEST); + emitInvokeStatic(asm, isGcRef ? ShadedRefs.REF_TEST : ShadedRefs.REF_TEST_INT); } public static void REF_TEST_NULL(Context ctx, CompilerInstruction ins, InstructionAdapter asm) { int heapType = (int) ins.operand(0); int srcHeapType = (int) ins.operand(1); + boolean isGcRef = isSourceGcRef(srcHeapType, ctx); asm.iconst(heapType); asm.iconst(srcHeapType); asm.load(ctx.instanceSlot(), OBJECT_TYPE); - emitInvokeStatic(asm, ShadedRefs.REF_TEST_NULL); + emitInvokeStatic(asm, isGcRef ? ShadedRefs.REF_TEST_NULL : ShadedRefs.REF_TEST_NULL_INT); } public static void CAST_TEST(Context ctx, CompilerInstruction ins, InstructionAdapter asm) { int heapType = (int) ins.operand(0); int srcHeapType = (int) ins.operand(1); + boolean isGcRef = isSourceGcRef(srcHeapType, ctx); asm.iconst(heapType); asm.iconst(srcHeapType); asm.load(ctx.instanceSlot(), OBJECT_TYPE); - emitInvokeStatic(asm, ShadedRefs.CAST_TEST); + emitInvokeStatic(asm, isGcRef ? ShadedRefs.CAST_TEST : ShadedRefs.CAST_TEST_INT); } public static void CAST_TEST_NULL( Context ctx, CompilerInstruction ins, InstructionAdapter asm) { int heapType = (int) ins.operand(0); int srcHeapType = (int) ins.operand(1); + boolean isGcRef = isSourceGcRef(srcHeapType, ctx); asm.iconst(heapType); asm.iconst(srcHeapType); asm.load(ctx.instanceSlot(), OBJECT_TYPE); - emitInvokeStatic(asm, ShadedRefs.CAST_TEST_NULL); + emitInvokeStatic(asm, isGcRef ? ShadedRefs.CAST_TEST_NULL : ShadedRefs.CAST_TEST_NULL_INT); } public static void REF_I31(Context ctx, CompilerInstruction ins, InstructionAdapter asm) { @@ -1857,12 +1913,16 @@ public static void I31_GET_U(Context ctx, CompilerInstruction ins, InstructionAd public static void ANY_CONVERT_EXTERN( Context ctx, CompilerInstruction ins, InstructionAdapter asm) { - // identity - no-op at runtime + // externref (int on JVM) -> anyref (Object on JVM) + // Box the int into an Object for use in the GC ref hierarchy. + emitInvokeStatic(asm, ShadedRefs.ANY_CONVERT_EXTERN); } public static void EXTERN_CONVERT_ANY( Context ctx, CompilerInstruction ins, InstructionAdapter asm) { - // identity - no-op at runtime + // anyref (Object on JVM) -> externref (int on JVM) + // Unbox back to int. Non-null GC refs get a unique non-null int value. + emitInvokeStatic(asm, ShadedRefs.EXTERN_CONVERT_ANY); } public static void BR_ON_NULL_CHECK( @@ -1911,13 +1971,15 @@ public static void BR_ON_CAST_CHECK( boolean nullable = ins.operand(0) != 0; int heapType = (int) ins.operand(1); int srcHeapType = (int) ins.operand(2); + boolean isGcRef = isSourceGcRef(srcHeapType, ctx); // stack: [ref] asm.dup(); asm.iconst(nullable ? 1 : 0); asm.iconst(heapType); asm.iconst(srcHeapType); asm.load(ctx.instanceSlot(), OBJECT_TYPE); - emitInvokeStatic(asm, ShadedRefs.HEAP_TYPE_MATCH); + emitInvokeStatic( + asm, isGcRef ? ShadedRefs.HEAP_TYPE_MATCH : ShadedRefs.HEAP_TYPE_MATCH_INT); // result: boolean on stack (1 if matches, 0 if not) } @@ -1927,13 +1989,15 @@ public static void BR_ON_CAST_FAIL_CHECK( boolean nullable = ins.operand(0) != 0; int heapType = (int) ins.operand(1); int srcHeapType = (int) ins.operand(2); + boolean isGcRef = isSourceGcRef(srcHeapType, ctx); // stack: [ref] asm.dup(); asm.iconst(nullable ? 1 : 0); asm.iconst(heapType); asm.iconst(srcHeapType); asm.load(ctx.instanceSlot(), OBJECT_TYPE); - emitInvokeStatic(asm, ShadedRefs.HEAP_TYPE_MATCH); + emitInvokeStatic( + asm, isGcRef ? ShadedRefs.HEAP_TYPE_MATCH : ShadedRefs.HEAP_TYPE_MATCH_INT); // invert: 1 if NOT matching (branch), 0 if matching (don't branch) var wasTrue = new Label(); var end = new Label(); 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 ce52d02f6..82d6534e7 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/Shaded.java +++ b/compiler/src/main/java/run/endive/compiler/internal/Shaded.java @@ -20,7 +20,6 @@ import run.endive.wasm.InvalidException; import run.endive.wasm.WasmEngineException; import run.endive.wasm.types.ValType; -import run.endive.wasm.types.Value; /** * This class will get shaded into the compiled code. @@ -107,14 +106,37 @@ public static int tableGet(int index, int tableIndex, Instance instance) { return OpcodeImpl.TABLE_GET(instance, tableIndex, index); } + public static Object tableGetRef(int index, int tableIndex, Instance instance) { + return instance.table(tableIndex).objRef(index); + } + public static void tableSet(int index, int value, int tableIndex, Instance instance) { instance.table(tableIndex).setRef(index, value, instance); } + public static void tableSetRef(int index, Object value, int tableIndex, Instance instance) { + instance.table(tableIndex).setObjRef(index, value, instance); + } + public static int tableGrow(int value, int size, int tableIndex, Instance instance) { return instance.table(tableIndex).grow(size, value, instance); } + public static int tableGrowRef(Object value, int size, int tableIndex, Instance instance) { + return instance.table(tableIndex).growWithRef(size, value, instance); + } + + public static void tableFillRef( + int offset, Object value, int size, int tableIndex, Instance instance) { + var table = instance.table(tableIndex); + if (offset + size > table.size()) { + throw new TrapException("out of bounds table access"); + } + for (int i = 0; i < size; i++) { + table.setObjRef(offset + i, value, instance); + } + } + public static int tableSize(int tableIndex, Instance instance) { return instance.table(tableIndex).size(); } @@ -409,6 +431,16 @@ public static WasmException createWasmException(long[] args, int tagNumber, Inst return e; } + public static WasmException createWasmExceptionGc( + long[] args, Object[] refArgs, int tagNumber, Instance instance) { + if (args == null) { + args = new long[0]; + } + WasmException e = new WasmException(instance, tagNumber, args, refArgs); + instance.registerException(e); + return e; + } + public static boolean exceptionMatches(WasmException exception, int tag, Instance instance) { if (exception.instance() == instance && exception.tagIdx() == tag) { return true; @@ -951,12 +983,20 @@ public static Object arrayNewElem( 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().isGcReference(); var elems = new long[len]; + var elemRefs = new Object[len]; for (int i = 0; i < len; i++) { - elems[i] = - elementValueToRef(computeElementValue(instance, elemIdx, offset + i), instance); + var init = element.initializers().get(offset + i); + var result = ConstantEvaluators.computeConstant(instance, init); + if (isRef) { + elemRefs[i] = result.ref(); + } else { + elems[i] = result.longValue(); + } } - return new WasmArray(typeIdx, elems); + return new WasmArray(typeIdx, elems, elemRefs); } public static long arrayGet(Object ref, int idx, int typeIdx, Instance instance) { @@ -1153,14 +1193,20 @@ public static void arrayInitElem( if (len == 0) { return; } + var at = instance.module().typeSection().getSubType(typeIdx).compType().arrayType(); + boolean isRef = at.fieldType().storageType().isGcReference(); for (int i = 0; i < len; i++) { - arr.set( - dstOff + i, - elementValueToRef( - computeElementValue(instance, elemIdx, srcOff + i), instance)); + var init = element.initializers().get(srcOff + i); + var result = ConstantEvaluators.computeConstant(instance, init); + if (isRef) { + arr.setRef(dstOff + i, result.ref()); + } else { + arr.set(dstOff + i, result.longValue()); + } } } + // GC ref (Object on JVM stack) variants public static int refTest(Object ref, int heapType, int srcHeapType, Instance instance) { return instance.heapTypeMatchRef(ref, false, heapType, srcHeapType) ? 1 : 0; } @@ -1189,6 +1235,34 @@ public static boolean heapTypeMatch( return instance.heapTypeMatchRef(ref, nullable, heapType, srcHeapType); } + // Non-GC ref (int on JVM stack) variants for funcref/externref + public static int refTestInt(int ref, int heapType, int srcHeapType, Instance instance) { + return instance.heapTypeMatch(ref, false, heapType, srcHeapType) ? 1 : 0; + } + + public static int refTestNullInt(int ref, int heapType, int srcHeapType, Instance instance) { + return instance.heapTypeMatch(ref, true, heapType, srcHeapType) ? 1 : 0; + } + + public static int castTestInt(int ref, int heapType, int srcHeapType, Instance instance) { + if (!instance.heapTypeMatch(ref, false, heapType, srcHeapType)) { + throw new TrapException("cast failure"); + } + return ref; + } + + public static int castTestNullInt(int ref, int heapType, int srcHeapType, Instance instance) { + if (!instance.heapTypeMatch(ref, true, heapType, srcHeapType)) { + throw new TrapException("cast failure"); + } + return ref; + } + + public static boolean heapTypeMatchInt( + int ref, boolean nullable, int heapType, int srcHeapType, Instance instance) { + return instance.heapTypeMatch(ref, nullable, heapType, srcHeapType); + } + public static Object refI31(int val) { return new WasmI31Ref(val & 0x7FFFFFFF); } @@ -1224,20 +1298,8 @@ public static int refEq(Object a, Object b) { return 0; } - @SuppressWarnings("deprecation") - private static long elementValueToRef(long val, Instance instance) { - if (Value.isI31(val)) { - var i31 = new WasmI31Ref(Value.decodeI31U(val)); - return instance.registerGcRef(i31); - } - return val; - } - - private static long computeElementValue(Instance instance, int elemIdx, int offset) { - var element = instance.element(elemIdx); - var init = element.initializers().get(offset); - return ConstantEvaluators.computeConstantValue(instance, init)[0]; - } + // elementValueToRef and computeElementValue removed: arrayNewElem/arrayInitElem now use + // ConstantEvaluators.computeConstant directly to properly handle GC ref elements. private static long readFromData(byte[] data, int offset, int size) { long val = 0; @@ -1247,6 +1309,26 @@ private static long readFromData(byte[] data, int offset, int size) { return val; } + public static Object anyConvertExtern(int ref) { + if (ref == REF_NULL_VALUE) { + return null; + } + return Integer.valueOf(ref); + } + + public static int externConvertAny(Object ref) { + if (ref == null) { + return REF_NULL_VALUE; + } + if (ref instanceof Integer) { + return (Integer) ref; + } + // Non-null GC ref (struct, array, i31, etc.) converted to externref. + // Return a non-null sentinel since the externref is opaque. + int hash = System.identityHashCode(ref); + return hash == REF_NULL_VALUE ? 0 : hash; + } + public static void dataDrop(int segment, Instance instance) { instance.dropDataSegment(segment); } 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 0b135228e..b0bdcf811 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/ShadedRefs.java +++ b/compiler/src/main/java/run/endive/compiler/internal/ShadedRefs.java @@ -54,10 +54,14 @@ public final class ShadedRefs { static final Method REF_AS_NON_NULL; static final Method GC_REF_AS_NON_NULL; static final Method TABLE_GET; + static final Method TABLE_GET_REF; static final Method TABLE_SET; + static final Method TABLE_SET_REF; static final Method TABLE_SIZE; static final Method TABLE_GROW; + static final Method TABLE_GROW_REF; static final Method TABLE_FILL; + static final Method TABLE_FILL_REF; static final Method TABLE_COPY; static final Method TABLE_INIT; static final Method TABLE_REQUIRED_REF; @@ -72,6 +76,7 @@ public final class ShadedRefs { // Exception handling methods static final Method CREATE_WASM_EXCEPTION; + static final Method CREATE_WASM_EXCEPTION_GC; static final Method INSTANCE_GET_EXCEPTION; static final Method EXCEPTION_MATCHES; @@ -176,10 +181,18 @@ public final class ShadedRefs { static final Method CAST_TEST; static final Method CAST_TEST_NULL; static final Method HEAP_TYPE_MATCH; + // int-based variants for funcref/externref (non-GC refs, int on JVM stack) + static final Method REF_TEST_INT; + static final Method REF_TEST_NULL_INT; + static final Method CAST_TEST_INT; + static final Method CAST_TEST_NULL_INT; + static final Method HEAP_TYPE_MATCH_INT; static final Method REF_EQ; static final Method REF_I31; static final Method I31_GET_S; static final Method I31_GET_U; + static final Method ANY_CONVERT_EXTERN; + static final Method EXTERN_CONVERT_ANY; static final Method DATA_DROP; static { @@ -281,13 +294,21 @@ public final class ShadedRefs { 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_GET_REF = + Shaded.class.getMethod("tableGetRef", int.class, int.class, Instance.class); TABLE_SET = Shaded.class.getMethod( "tableSet", int.class, int.class, int.class, Instance.class); + TABLE_SET_REF = + Shaded.class.getMethod( + "tableSetRef", int.class, Object.class, int.class, Instance.class); TABLE_SIZE = Shaded.class.getMethod("tableSize", int.class, Instance.class); TABLE_GROW = Shaded.class.getMethod( "tableGrow", int.class, int.class, int.class, Instance.class); + TABLE_GROW_REF = + Shaded.class.getMethod( + "tableGrowRef", Object.class, int.class, int.class, Instance.class); TABLE_FILL = Shaded.class.getMethod( "tableFill", @@ -296,6 +317,14 @@ public final class ShadedRefs { int.class, int.class, Instance.class); + TABLE_FILL_REF = + Shaded.class.getMethod( + "tableFillRef", + int.class, + Object.class, + int.class, + int.class, + Instance.class); TABLE_COPY = Shaded.class.getMethod( "tableCopy", @@ -333,6 +362,13 @@ public final class ShadedRefs { CREATE_WASM_EXCEPTION = Shaded.class.getMethod( "createWasmException", long[].class, int.class, Instance.class); + CREATE_WASM_EXCEPTION_GC = + Shaded.class.getMethod( + "createWasmExceptionGc", + long[].class, + Object[].class, + int.class, + Instance.class); INSTANCE_GET_EXCEPTION = Instance.class.getMethod("exn", int.class); EXCEPTION_MATCHES = Shaded.class.getMethod( @@ -909,10 +945,32 @@ public final class ShadedRefs { int.class, int.class, Instance.class); + REF_TEST_INT = + Shaded.class.getMethod( + "refTestInt", int.class, int.class, int.class, Instance.class); + REF_TEST_NULL_INT = + Shaded.class.getMethod( + "refTestNullInt", int.class, int.class, int.class, Instance.class); + CAST_TEST_INT = + Shaded.class.getMethod( + "castTestInt", int.class, int.class, int.class, Instance.class); + CAST_TEST_NULL_INT = + Shaded.class.getMethod( + "castTestNullInt", int.class, int.class, int.class, Instance.class); + HEAP_TYPE_MATCH_INT = + Shaded.class.getMethod( + "heapTypeMatchInt", + int.class, + boolean.class, + int.class, + 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); + ANY_CONVERT_EXTERN = Shaded.class.getMethod("anyConvertExtern", int.class); + EXTERN_CONVERT_ANY = Shaded.class.getMethod("externConvertAny", 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 20d64b2c5..046a3fb36 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/WasmAnalyzer.java +++ b/compiler/src/main/java/run/endive/compiler/internal/WasmAnalyzer.java @@ -736,6 +736,31 @@ public AnalysisResult analyze(int funcId) { CompilerOpCode.CAST_TEST_NULL, castNullOperands)); break; } + case REF_TEST: + { + // ref.test: [ref] -> [i32] + var srcType = stack.peek(); + stack.popRef(); + var heapType = (int) ins.operand(0); + stack.push(ValType.I32); + long[] refTestOperands = {heapType, srcType.typeIdx()}; + result.add( + new CompilerInstruction(CompilerOpCode.REF_TEST, refTestOperands)); + break; + } + case REF_TEST_NULL: + { + // ref.test null: [ref] -> [i32] + var srcType = stack.peek(); + stack.popRef(); + var heapType = (int) ins.operand(0); + stack.push(ValType.I32); + long[] refTestNullOperands = {heapType, srcType.typeIdx()}; + result.add( + new CompilerInstruction( + CompilerOpCode.REF_TEST_NULL, refTestNullOperands)); + break; + } default: analyzeSimple(result, stack, ins, functionType, body); } @@ -1297,27 +1322,48 @@ private void analyzeSimple( stack.pop(ValType.I32); break; case TABLE_FILL: - // [I32 ref I32] -> [] - stack.pop(ValType.I32); - stack.pop(stack.peek()); - stack.pop(ValType.I32); - break; + { + // [I32 ref I32] -> [] + stack.pop(ValType.I32); + var fillValType = stack.peek().resolve(module.typeSection()); + stack.pop(fillValType); + stack.pop(ValType.I32); + long[] fillOps = {ins.operand(0), fillValType.isGcReference() ? 1 : 0}; + out.add(new CompilerInstruction(CompilerOpCode.TABLE_FILL, fillOps)); + return; + } case TABLE_GET: - // [I32] -> [ref] - stack.pop(ValType.I32); - stack.push(tableTypes.get((int) ins.operand(0))); - break; + { + // [I32] -> [ref] + stack.pop(ValType.I32); + var tableElemType = tableTypes.get((int) ins.operand(0)); + tableElemType.resolve(module.typeSection()); + stack.push(tableElemType); + long[] getOps = {ins.operand(0), tableElemType.isGcReference() ? 1 : 0}; + out.add(new CompilerInstruction(CompilerOpCode.TABLE_GET, getOps)); + return; + } case TABLE_GROW: - // [ref I32] -> [I32] - stack.pop(ValType.I32); - stack.pop(tableTypes.get((int) ins.operand(0))); - stack.push(ValType.I32); - break; + { + // [ref I32] -> [I32] + stack.pop(ValType.I32); + var growValType = stack.peek().resolve(module.typeSection()); + stack.pop(growValType); + stack.push(ValType.I32); + long[] growOps = {ins.operand(0), growValType.isGcReference() ? 1 : 0}; + out.add(new CompilerInstruction(CompilerOpCode.TABLE_GROW, growOps)); + return; + } case TABLE_SET: - // [I32 ref] -> [] - stack.pop(tableTypes.get((int) ins.operand(0))); - stack.pop(ValType.I32); - break; + { + // [I32 ref] -> [] + var setValType = stack.peek().resolve(module.typeSection()); + stack.pop(setValType); + stack.pop(ValType.I32); + long[] setOps = {ins.operand(0), setValType.isGcReference() ? 1 : 0}; + out.add(new CompilerInstruction(CompilerOpCode.TABLE_SET, setOps)); + return; + } case CALL: // [p*] -> [r*] updateStack(stack, functionTypes.get((int) ins.operand(0))); @@ -1579,7 +1625,8 @@ private void analyzeSimple( ValType.builder() .withOpcode(ValType.ID.Ref) .withTypeIdx(ValType.TypeIdxCode.I31.code()) - .build()); + .build() + .resolve(module.typeSection())); break; case I31_GET_S: case I31_GET_U: @@ -1719,7 +1766,7 @@ private static List getFunctionTypes(WasmModule module) { return Stream.concat(importedFunctions, moduleFunctions).collect(toUnmodifiableList()); } - private static List getTableTypes(WasmModule module) { + static List getTableTypes(WasmModule module) { var importedTables = module.importSection().stream() .filter(TableImport.class::isInstance) 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 72aed25e8..5e3679eb1 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 @@ -2,12 +2,22 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime private final Lrun/endive/runtime/Instance; instance + private final Lrun/endive/runtime/internal/CompilerInterpreterMachine; compilerInterpreterMachine + public (Lrun/endive/runtime/Instance;)V ALOAD 0 INVOKESPECIAL java/lang/Object. ()V ALOAD 0 ALOAD 1 PUTFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; + ALOAD 0 + NEW run/endive/runtime/internal/CompilerInterpreterMachine + DUP + ALOAD 1 + ICONST_0 + NEWARRAY T_INT + INVOKESPECIAL run/endive/runtime/internal/CompilerInterpreterMachine. (Lrun/endive/runtime/Instance;[I)V + PUTFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; RETURN public call(I[J)[J @@ -44,6 +54,14 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; ATHROW + public callGc(I[Ljava/lang/Object;)[Ljava/lang/Object; + ALOAD 0 + GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; + ILOAD 1 + ALOAD 2 + INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callGc (I[Ljava/lang/Object;)[Ljava/lang/Object; + ARETURN + public static call_indirect_0(IIILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V ALOAD 4 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 8bcc3ff05..16ba31e7f 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 @@ -2,12 +2,22 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime private final Lrun/endive/runtime/Instance; instance + private final Lrun/endive/runtime/internal/CompilerInterpreterMachine; compilerInterpreterMachine + public (Lrun/endive/runtime/Instance;)V ALOAD 0 INVOKESPECIAL java/lang/Object. ()V ALOAD 0 ALOAD 1 PUTFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; + ALOAD 0 + NEW run/endive/runtime/internal/CompilerInterpreterMachine + DUP + ALOAD 1 + ICONST_0 + NEWARRAY T_INT + INVOKESPECIAL run/endive/runtime/internal/CompilerInterpreterMachine. (Lrun/endive/runtime/Instance;[I)V + PUTFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; RETURN public call(I[J)[J @@ -44,6 +54,14 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; ATHROW + public callGc(I[Ljava/lang/Object;)[Ljava/lang/Object; + ALOAD 0 + GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; + ILOAD 1 + ALOAD 2 + INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callGc (I[Ljava/lang/Object;)[Ljava/lang/Object; + ARETURN + public static call_indirect_0(IIILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V ALOAD 4 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 b5b7a9583..c691f0719 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 @@ -2,12 +2,22 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime private final Lrun/endive/runtime/Instance; instance + private final Lrun/endive/runtime/internal/CompilerInterpreterMachine; compilerInterpreterMachine + public (Lrun/endive/runtime/Instance;)V ALOAD 0 INVOKESPECIAL java/lang/Object. ()V ALOAD 0 ALOAD 1 PUTFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; + ALOAD 0 + NEW run/endive/runtime/internal/CompilerInterpreterMachine + DUP + ALOAD 1 + ICONST_0 + NEWARRAY T_INT + INVOKESPECIAL run/endive/runtime/internal/CompilerInterpreterMachine. (Lrun/endive/runtime/Instance;[I)V + PUTFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; RETURN public call(I[J)[J @@ -44,6 +54,14 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; ATHROW + public callGc(I[Ljava/lang/Object;)[Ljava/lang/Object; + ALOAD 0 + GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; + ILOAD 1 + ALOAD 2 + INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callGc (I[Ljava/lang/Object;)[Ljava/lang/Object; + ARETURN + public static call_indirect_0(IIILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V ALOAD 4 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 403480540..8dfe4cc0f 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 @@ -2,12 +2,22 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime private final Lrun/endive/runtime/Instance; instance + private final Lrun/endive/runtime/internal/CompilerInterpreterMachine; compilerInterpreterMachine + public (Lrun/endive/runtime/Instance;)V ALOAD 0 INVOKESPECIAL java/lang/Object. ()V ALOAD 0 ALOAD 1 PUTFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; + ALOAD 0 + NEW run/endive/runtime/internal/CompilerInterpreterMachine + DUP + ALOAD 1 + ICONST_0 + NEWARRAY T_INT + INVOKESPECIAL run/endive/runtime/internal/CompilerInterpreterMachine. (Lrun/endive/runtime/Instance;[I)V + PUTFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; RETURN public call(I[J)[J @@ -44,6 +54,14 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; ATHROW + public callGc(I[Ljava/lang/Object;)[Ljava/lang/Object; + ALOAD 0 + GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; + ILOAD 1 + ALOAD 2 + INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callGc (I[Ljava/lang/Object;)[Ljava/lang/Object; + ARETURN + public static call_indirect_0(IILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)V INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V ALOAD 3 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 c5fb763e6..5f67f2485 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 @@ -36,18 +36,9 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { ALOAD 1 ALOAD 0 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 4 + POP ICONST_1 NEWARRAY T_LONG - DUP - ICONST_0 - LLOAD 4 - LASTORE ARETURN public static func_1(Ljava/lang/Object;Lrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I 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 a4e049a95..bcfee9a51 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 @@ -2,12 +2,22 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime private final Lrun/endive/runtime/Instance; instance + private final Lrun/endive/runtime/internal/CompilerInterpreterMachine; compilerInterpreterMachine + public (Lrun/endive/runtime/Instance;)V ALOAD 0 INVOKESPECIAL java/lang/Object. ()V ALOAD 0 ALOAD 1 PUTFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; + ALOAD 0 + NEW run/endive/runtime/internal/CompilerInterpreterMachine + DUP + ALOAD 1 + ICONST_0 + NEWARRAY T_INT + INVOKESPECIAL run/endive/runtime/internal/CompilerInterpreterMachine. (Lrun/endive/runtime/Instance;[I)V + PUTFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; RETURN public call(I[J)[J @@ -44,6 +54,14 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; ATHROW + public callGc(I[Ljava/lang/Object;)[Ljava/lang/Object; + ALOAD 0 + GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; + ILOAD 1 + ALOAD 2 + INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callGc (I[Ljava/lang/Object;)[Ljava/lang/Object; + ARETURN + public static call_indirect_0(IIIIIILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V ALOAD 7 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 a7f74f147..72ed84f39 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 @@ -2,12 +2,22 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime private final Lrun/endive/runtime/Instance; instance + private final Lrun/endive/runtime/internal/CompilerInterpreterMachine; compilerInterpreterMachine + public (Lrun/endive/runtime/Instance;)V ALOAD 0 INVOKESPECIAL java/lang/Object. ()V ALOAD 0 ALOAD 1 PUTFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; + ALOAD 0 + NEW run/endive/runtime/internal/CompilerInterpreterMachine + DUP + ALOAD 1 + ICONST_0 + NEWARRAY T_INT + INVOKESPECIAL run/endive/runtime/internal/CompilerInterpreterMachine. (Lrun/endive/runtime/Instance;[I)V + PUTFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; RETURN public call(I[J)[J @@ -44,6 +54,14 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; ATHROW + public callGc(I[Ljava/lang/Object;)[Ljava/lang/Object; + ALOAD 0 + GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; + ILOAD 1 + ALOAD 2 + INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callGc (I[Ljava/lang/Object;)[Ljava/lang/Object; + ARETURN + public static call_indirect_0(IILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)V INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V ALOAD 3 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 09937f011..700eea225 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 @@ -2,12 +2,22 @@ public final class FOO implements run/endive/runtime/Machine { private final Lrun/endive/runtime/Instance; instance + private final Lrun/endive/runtime/internal/CompilerInterpreterMachine; compilerInterpreterMachine + public (Lrun/endive/runtime/Instance;)V ALOAD 0 INVOKESPECIAL java/lang/Object. ()V ALOAD 0 ALOAD 1 PUTFIELD FOO.instance : Lrun/endive/runtime/Instance; + ALOAD 0 + NEW run/endive/runtime/internal/CompilerInterpreterMachine + DUP + ALOAD 1 + ICONST_0 + NEWARRAY T_INT + INVOKESPECIAL run/endive/runtime/internal/CompilerInterpreterMachine. (Lrun/endive/runtime/Instance;[I)V + PUTFIELD FOO.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; RETURN public call(I[J)[J @@ -44,6 +54,14 @@ public final class FOO implements run/endive/runtime/Machine { INVOKESTATIC FOOShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; ATHROW + public callGc(I[Ljava/lang/Object;)[Ljava/lang/Object; + ALOAD 0 + GETFIELD FOO.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; + ILOAD 1 + ALOAD 2 + INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callGc (I[Ljava/lang/Object;)[Ljava/lang/Object; + ARETURN + public static call_indirect_0(IILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)V INVOKESTATIC FOOShaded.checkInterruption ()V ALOAD 3 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 a46e0cdbe..dde03dcbf 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 @@ -2,12 +2,22 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime private final Lrun/endive/runtime/Instance; instance + private final Lrun/endive/runtime/internal/CompilerInterpreterMachine; compilerInterpreterMachine + public (Lrun/endive/runtime/Instance;)V ALOAD 0 INVOKESPECIAL java/lang/Object. ()V ALOAD 0 ALOAD 1 PUTFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; + ALOAD 0 + NEW run/endive/runtime/internal/CompilerInterpreterMachine + DUP + ALOAD 1 + ICONST_0 + NEWARRAY T_INT + INVOKESPECIAL run/endive/runtime/internal/CompilerInterpreterMachine. (Lrun/endive/runtime/Instance;[I)V + PUTFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; RETURN public call(I[J)[J @@ -44,6 +54,14 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; ATHROW + public callGc(I[Ljava/lang/Object;)[Ljava/lang/Object; + ALOAD 0 + GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; + ILOAD 1 + ALOAD 2 + INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callGc (I[Ljava/lang/Object;)[Ljava/lang/Object; + ARETURN + public static call_indirect_0(IIILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V ALOAD 4 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 8efbaeec5..cf26b6444 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 @@ -2,12 +2,22 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime private final Lrun/endive/runtime/Instance; instance + private final Lrun/endive/runtime/internal/CompilerInterpreterMachine; compilerInterpreterMachine + public (Lrun/endive/runtime/Instance;)V ALOAD 0 INVOKESPECIAL java/lang/Object. ()V ALOAD 0 ALOAD 1 PUTFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; + ALOAD 0 + NEW run/endive/runtime/internal/CompilerInterpreterMachine + DUP + ALOAD 1 + ICONST_0 + NEWARRAY T_INT + INVOKESPECIAL run/endive/runtime/internal/CompilerInterpreterMachine. (Lrun/endive/runtime/Instance;[I)V + PUTFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; RETURN public call(I[J)[J @@ -44,6 +54,14 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; ATHROW + public callGc(I[Ljava/lang/Object;)[Ljava/lang/Object; + ALOAD 0 + GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; + ILOAD 1 + ALOAD 2 + INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callGc (I[Ljava/lang/Object;)[Ljava/lang/Object; + ARETURN + public static call_indirect_0(IIILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V ALOAD 4 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 c9dce7bcd..631518402 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 @@ -2,12 +2,22 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime private final Lrun/endive/runtime/Instance; instance + private final Lrun/endive/runtime/internal/CompilerInterpreterMachine; compilerInterpreterMachine + public (Lrun/endive/runtime/Instance;)V ALOAD 0 INVOKESPECIAL java/lang/Object. ()V ALOAD 0 ALOAD 1 PUTFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; + ALOAD 0 + NEW run/endive/runtime/internal/CompilerInterpreterMachine + DUP + ALOAD 1 + ICONST_0 + NEWARRAY T_INT + INVOKESPECIAL run/endive/runtime/internal/CompilerInterpreterMachine. (Lrun/endive/runtime/Instance;[I)V + PUTFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; RETURN public call(I[J)[J @@ -44,6 +54,14 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; ATHROW + public callGc(I[Ljava/lang/Object;)[Ljava/lang/Object; + ALOAD 0 + GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; + ILOAD 1 + ALOAD 2 + INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callGc (I[Ljava/lang/Object;)[Ljava/lang/Object; + ARETURN + public static call_indirect_0(IIILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V ALOAD 4 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 267fb98aa..f05550f3e 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 @@ -2,12 +2,22 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime private final Lrun/endive/runtime/Instance; instance + private final Lrun/endive/runtime/internal/CompilerInterpreterMachine; compilerInterpreterMachine + public (Lrun/endive/runtime/Instance;)V ALOAD 0 INVOKESPECIAL java/lang/Object. ()V ALOAD 0 ALOAD 1 PUTFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; + ALOAD 0 + NEW run/endive/runtime/internal/CompilerInterpreterMachine + DUP + ALOAD 1 + ICONST_0 + NEWARRAY T_INT + INVOKESPECIAL run/endive/runtime/internal/CompilerInterpreterMachine. (Lrun/endive/runtime/Instance;[I)V + PUTFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; RETURN public call(I[J)[J @@ -44,6 +54,14 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; ATHROW + public callGc(I[Ljava/lang/Object;)[Ljava/lang/Object; + ALOAD 0 + GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; + ILOAD 1 + ALOAD 2 + INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callGc (I[Ljava/lang/Object;)[Ljava/lang/Object; + ARETURN + public static call_indirect_0(IIILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)V INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V ALOAD 4 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 4fd76007a..1b6be9d72 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 @@ -2,12 +2,22 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime private final Lrun/endive/runtime/Instance; instance + private final Lrun/endive/runtime/internal/CompilerInterpreterMachine; compilerInterpreterMachine + public (Lrun/endive/runtime/Instance;)V ALOAD 0 INVOKESPECIAL java/lang/Object. ()V ALOAD 0 ALOAD 1 PUTFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; + ALOAD 0 + NEW run/endive/runtime/internal/CompilerInterpreterMachine + DUP + ALOAD 1 + ICONST_0 + NEWARRAY T_INT + INVOKESPECIAL run/endive/runtime/internal/CompilerInterpreterMachine. (Lrun/endive/runtime/Instance;[I)V + PUTFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; RETURN public call(I[J)[J @@ -82,6 +92,14 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; ATHROW + public callGc(I[Ljava/lang/Object;)[Ljava/lang/Object; + ALOAD 0 + GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; + ILOAD 1 + ALOAD 2 + INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callGc (I[Ljava/lang/Object;)[Ljava/lang/Object; + ARETURN + public static call_indirect_0(IIIIILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V ALOAD 6 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 3df5c3ff9..e3d24a07f 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 @@ -2,12 +2,22 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime private final Lrun/endive/runtime/Instance; instance + private final Lrun/endive/runtime/internal/CompilerInterpreterMachine; compilerInterpreterMachine + public (Lrun/endive/runtime/Instance;)V ALOAD 0 INVOKESPECIAL java/lang/Object. ()V ALOAD 0 ALOAD 1 PUTFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; + ALOAD 0 + NEW run/endive/runtime/internal/CompilerInterpreterMachine + DUP + ALOAD 1 + ICONST_0 + NEWARRAY T_INT + INVOKESPECIAL run/endive/runtime/internal/CompilerInterpreterMachine. (Lrun/endive/runtime/Instance;[I)V + PUTFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; RETURN public call(I[J)[J @@ -44,6 +54,14 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; ATHROW + public callGc(I[Ljava/lang/Object;)[Ljava/lang/Object; + ALOAD 0 + GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; + ILOAD 1 + ALOAD 2 + INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callGc (I[Ljava/lang/Object;)[Ljava/lang/Object; + ARETURN + public static call_indirect_0(IILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)V INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V ALOAD 3 diff --git a/machine-tests/src/test/java/run/endive/testing/BrOnNullTest.java b/machine-tests/src/test/java/run/endive/testing/BrOnNullTest.java index e0961a5b4..c62b1039d 100644 --- a/machine-tests/src/test/java/run/endive/testing/BrOnNullTest.java +++ b/machine-tests/src/test/java/run/endive/testing/BrOnNullTest.java @@ -40,10 +40,10 @@ public void brOnNullRefinesType(Function mac // Create a point(42, 7) and verify get_x_or_default returns 42 var newPoint = instance.export("new_point"); - long[] pointRef = newPoint.apply(42, 7); + Object[] pointRef = newPoint.applyGc((Object) 42L, (Object) 7L); var getX = instance.export("get_x_or_default"); - assertEquals(42, getX.apply(pointRef[0])[0]); + assertEquals(42, (int) (Integer) getX.applyGc(pointRef[0])[0]); } @ParameterizedTest diff --git a/runtime/src/main/java/run/endive/runtime/ConstantEvaluators.java b/runtime/src/main/java/run/endive/runtime/ConstantEvaluators.java index 25fff2012..004ae6a6d 100644 --- a/runtime/src/main/java/run/endive/runtime/ConstantEvaluators.java +++ b/runtime/src/main/java/run/endive/runtime/ConstantEvaluators.java @@ -158,7 +158,7 @@ public static ConstantResult computeConstant(Instance instance, List= 0; i--) { var entry = stack.pop(); var ft = structType.fieldTypes()[i]; - if (ft.storageType().isReference()) { + if (ft.storageType().isGcReference()) { fieldRefs[i] = entry.ref(); } else { fields[i] = entry.longValue(); @@ -180,6 +180,15 @@ public static ConstantResult computeConstant(Instance instance, List= 0; i--) { var entry = stack.pop(); - if (isRef) { + if (isGcRef) { elementRefs[i] = entry.ref(); } else { elements[i] = entry.longValue(); diff --git a/runtime/src/main/java/run/endive/runtime/Instance.java b/runtime/src/main/java/run/endive/runtime/Instance.java index 60c24f9db..2f9da8981 100644 --- a/runtime/src/main/java/run/endive/runtime/Instance.java +++ b/runtime/src/main/java/run/endive/runtime/Instance.java @@ -494,16 +494,17 @@ public boolean heapTypeMatch( var funcTypeIdx = functionType((int) ref); return heapTypeSubOf(funcTypeIdx, targetHeapType); } - // ANY hierarchy: i31, struct, array, or internalized externref - if (Value.isI31(ref)) { - return heapTypeSubOf(ValType.TypeIdxCode.I31.code(), targetHeapType); - } - var gc = gcRef((int) ref); - if (gc != null) { - return heapTypeSubOf(gc.typeIdx(), targetHeapType); + // Concrete function type source (sourceHeapType >= 0 and is a func type) + if (sourceHeapType >= 0 + && module.typeSection() != null + && module.typeSection().getSubType(sourceHeapType).compType().funcType() != null) { + var funcTypeIdx = functionType((int) ref); + return heapTypeSubOf(funcTypeIdx, targetHeapType); } - // Internalized externref (via any.convert_extern) - return targetHeapType == ValType.TypeIdxCode.ANY.code(); + // For non-GC ref values (int on JVM), the only remaining possibility is externref. + // In the new GC design, GC refs are Objects and should not reach this method. + // Return false for type mismatches. + return false; } public boolean heapTypeMatchRef( diff --git a/runtime/src/main/java/run/endive/runtime/InterpreterMachine.java b/runtime/src/main/java/run/endive/runtime/InterpreterMachine.java index 1e5e57bb2..9b31f2461 100644 --- a/runtime/src/main/java/run/endive/runtime/InterpreterMachine.java +++ b/runtime/src/main/java/run/endive/runtime/InterpreterMachine.java @@ -261,16 +261,6 @@ protected void eval(MStack stack, Instance instance, Deque callStack return; } var instruction = frame.loadCurrentInstruction(); - // LOGGER.log( - // System.Logger.Level.DEBUG, - // "func=" - // + frame.funcId - // + "@" - // + frame.pc - // + ": " - // + instruction - // + " stack=" - // + stack); var opcode = instruction.opcode(); Operands operands = instruction::operand; instance.onExecution(instruction, stack); @@ -345,8 +335,10 @@ protected void eval(MStack stack, Instance instance, Deque callStack var tag = instance.tag(tagNumber); var type = instance.type(tag.tagType().typeIdx()); - var args = extractArgsForParams(stack, type.params()); - var exception = new WasmException(instance, tagNumber, args); + var extracted = extractArgsAndRefsForParams(stack, type.params(), instance); + var args = (long[]) extracted[0]; + var refArgs = (Object[]) extracted[1]; + var exception = new WasmException(instance, tagNumber, args, refArgs); var exceptionIdx = instance.registerException(exception); frame = THROW_REF(instance, exceptionIdx, stack, frame, callStack); break; @@ -3127,17 +3119,13 @@ protected static StackFrame THROW_REF( case CATCH: if (currentCatch.tag() == exception.tagIdx() || compatibleImport) { found = true; - for (var arg : exception.args()) { - stack.push(arg); - } + pushExceptionArgs(exception, stack); } break; case CATCH_REF: if (currentCatch.tag() == exception.tagIdx() || compatibleImport) { found = true; - for (var arg : exception.args()) { - stack.push(arg); - } + pushExceptionArgs(exception, stack); stack.push(exceptionIdx); } break; @@ -3250,22 +3238,36 @@ private static void BR_IF(StackFrame frame, MStack stack, AnnotatedInstruction i private static void BR_ON_NULL( StackFrame frame, MStack stack, AnnotatedInstruction instruction) { - var ref = (int) stack.pop(); - if (ref == REF_NULL_VALUE) { - BR(frame, stack, instruction); + Object refObj = stack.peekRef(); + if (refObj != null) { + // Non-null GC ref — pop the ref side, keep it + stack.popRef(); + stack.pushRef(refObj); } else { - stack.push(ref); + // Non-GC ref or null GC ref — check the long side + var val = stack.pop(); + if (val == REF_NULL_VALUE) { + BR(frame, stack, instruction); + } else { + stack.push(val); + } } } private static void BR_ON_NON_NULL( StackFrame frame, MStack stack, AnnotatedInstruction instruction) { - var ref = (int) stack.pop(); - if (ref == REF_NULL_VALUE) { - // do nothing - } else { - stack.push(ref); + Object refObj = stack.peekRef(); + if (refObj != null) { + // Non-null GC ref — pop and branch with it + stack.popRef(); + stack.pushRef(refObj); BR(frame, stack, instruction); + } else { + var val = stack.pop(); + if (val != REF_NULL_VALUE) { + stack.push(val); + BR(frame, stack, instruction); + } } } @@ -3445,7 +3447,16 @@ private static void STRUCT_NEW_DEFAULT(MStack stack, Instance instance, Operands var typeIdx = (int) operands.get(0); var st = instance.module().typeSection().getSubType(typeIdx).compType().structType(); var fields = new long[st.fieldTypes().length]; - // fieldRefs is already null-initialized which is correct for ref defaults (null) + // Default values: 0 for numeric, null for GC refs (already zero-initialized), + // REF_NULL_VALUE for non-GC references (funcref, externref) + for (int i = 0; i < fields.length; i++) { + var ft = st.fieldTypes()[i]; + if (ft.storageType().valType() != null + && ft.storageType().valType().isReference() + && !ft.storageType().isGcReference()) { + fields[i] = REF_NULL_VALUE; + } + } var struct = new WasmStruct(typeIdx, fields); stack.pushRef(struct); } @@ -3819,6 +3830,17 @@ private static void ARRAY_INIT_ELEM(MStack stack, Instance instance, Operands op } } + private static void pushExceptionArgs(WasmException exception, MStack stack) { + var refArgs = exception.refArgs(); + for (int i = 0; i < exception.args().length; i++) { + if (refArgs != null && refArgs[i] != null) { + stack.pushRef(refArgs[i]); + } else { + stack.push(exception.args()[i]); + } + } + } + private static boolean isSourceGcRef(int sourceHeapType) { return sourceHeapType != ValType.TypeIdxCode.FUNC.code() && sourceHeapType != ValType.TypeIdxCode.NOFUNC.code() diff --git a/runtime/src/main/java/run/endive/runtime/MStack.java b/runtime/src/main/java/run/endive/runtime/MStack.java index 244cd30f9..809a1cb64 100644 --- a/runtime/src/main/java/run/endive/runtime/MStack.java +++ b/runtime/src/main/java/run/endive/runtime/MStack.java @@ -37,6 +37,9 @@ public Object[] refArray() { } public void push(long v) { + if (refs != null) { + refs[count] = null; + } elements[count] = v; count++; diff --git a/runtime/src/main/java/run/endive/runtime/WasmException.java b/runtime/src/main/java/run/endive/runtime/WasmException.java index 3ba19c2ad..f3d2b0d92 100644 --- a/runtime/src/main/java/run/endive/runtime/WasmException.java +++ b/runtime/src/main/java/run/endive/runtime/WasmException.java @@ -3,12 +3,18 @@ public class WasmException extends RuntimeException { private final int tagIdx; private final long[] args; + private final Object[] refArgs; private final Instance instance; public WasmException(Instance instance, int tagIdx, long[] args) { + this(instance, tagIdx, args, null); + } + + public WasmException(Instance instance, int tagIdx, long[] args, Object[] refArgs) { this.instance = instance; this.tagIdx = tagIdx; this.args = args.clone(); + this.refArgs = (refArgs != null) ? refArgs.clone() : null; this.setStackTrace(new StackTraceElement[0]); } @@ -23,4 +29,8 @@ public int tagIdx() { public long[] args() { return args; } + + public Object[] refArgs() { + return refArgs; + } } 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 d09cca7ac..fcc9f823d 100644 --- a/runtime/src/main/java/run/endive/runtime/internal/CompilerInterpreterMachine.java +++ b/runtime/src/main/java/run/endive/runtime/internal/CompilerInterpreterMachine.java @@ -11,6 +11,7 @@ import run.endive.runtime.WasmException; import run.endive.wasm.WasmEngineException; import run.endive.wasm.types.FunctionType; +import run.endive.wasm.types.ValType; /** * This class is used by compiler generated classes. It MUST remain backwards compatible @@ -72,15 +73,32 @@ protected void CALL(Operands operands) { var stack = stack(); var typeId = instance.functionType(funcId); var type = instance.type(typeId); - var args = extractArgsForParams(stack, type.params()); + var extracted = extractArgsAndRefsForParams(stack, type.params(), instance); + var args = (long[]) extracted[0]; + var refArgs = (Object[]) extracted[1]; + boolean hasGcReturns = type.returns().stream().anyMatch(ValType::isGcReference); try { - var results = instance.getMachine().call(funcId, args); - // a host function can return null or an array of ints - // which we will push onto the stack - if (results != null) { - for (var result : results) { - stack.push(result); + if (hasGcReturns) { + var gcResults = + instance.getMachine() + .callGc(funcId, mergeArgsForCallGc(args, refArgs, type)); + if (gcResults != null) { + for (int i = 0; i < type.returns().size(); i++) { + var retType = type.returns().get(i); + if (retType.isGcReference()) { + stack.pushRef(gcResults[i]); + } else if (gcResults[i] instanceof Number) { + stack.push(((Number) gcResults[i]).longValue()); + } + } + } + } else { + var results = instance.getMachine().call(funcId, args, refArgs); + if (results != null) { + for (var result : results) { + stack.push(result); + } } } } catch (WasmException e) { @@ -91,6 +109,21 @@ protected void CALL(Operands operands) { } } + private static Object[] mergeArgsForCallGc(long[] args, Object[] refArgs, FunctionType type) { + var gcArgs = new Object[type.params().size()]; + int slot = 0; + for (int i = 0; i < type.params().size(); i++) { + var param = type.params().get(i); + if (param.isGcReference()) { + gcArgs[i] = (refArgs != null) ? refArgs[slot] : null; + } else { + gcArgs[i] = args[slot]; + } + slot += (param.equals(ValType.V128)) ? 2 : 1; + } + return gcArgs; + } + @Override protected boolean useCurrentInstanceInterpreter( Instance instance, Instance refInstance, int funcId) { From c7f2896e462704c32f2a826c743368dc799556c7 Mon Sep 17 00:00:00 2001 From: andreatp Date: Tue, 9 Jun 2026 12:49:17 +0100 Subject: [PATCH 06/20] fix: compiler GC ref fixes + edge case tests + stress test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Compiler fixes: - Shaded.structNewDefault/arrayNewDefault: fill REF_NULL_VALUE for funcref fields (non-GC refs need -1 for null, not 0) - emitBoxValuesOnStack: handle Object refs (store 0L placeholder) - Remove dead code: CompilerUtil.isGcRef, hasGcRefReturns, Context.isGcTable Infrastructure: - ValType.isObjectRef(): cached flag for "uses Object on JVM stack" (GC refs + externref, NOT funcref). Ready for future externref-as-Object. - StorageType.isObjectRef() delegates to ValType Tests: - GcEdgeCasesTest: struct.new_default funcref null, extern round-trip (interpreter only — compiler externref-as-Object deferred), applyGc - GcStressTest: allocate 100K struct chains 100 times, verifies Java GC collects unreachable Wasm GC refs (no OOM with -Xmx64m) --- .../compiler/internal/CompilerUtil.java | 20 ------ .../run/endive/compiler/internal/Context.java | 8 --- .../endive/compiler/internal/Emitters.java | 18 +++-- .../run/endive/compiler/internal/Shaded.java | 22 ++++-- .../run/endive/testing/GcEdgeCasesTest.java | 66 ++++++++++++++++++ .../java/run/endive/testing/GcStressTest.java | 46 ++++++++++++ .../resources/compiled/gc_edge_cases.wat.wasm | Bin 0 -> 214 bytes .../resources/compiled/gc_stress.wat.wasm | Bin 0 -> 161 bytes .../run/endive/wasm/types/StorageType.java | 4 ++ .../java/run/endive/wasm/types/ValType.java | 38 ++++++++++ 10 files changed, 183 insertions(+), 39 deletions(-) create mode 100644 machine-tests/src/test/java/run/endive/testing/GcEdgeCasesTest.java create mode 100644 machine-tests/src/test/java/run/endive/testing/GcStressTest.java create mode 100644 wasm-corpus/src/main/resources/compiled/gc_edge_cases.wat.wasm create mode 100644 wasm-corpus/src/main/resources/compiled/gc_stress.wat.wasm 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 94844c20b..1603d5a3f 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/CompilerUtil.java +++ b/compiler/src/main/java/run/endive/compiler/internal/CompilerUtil.java @@ -205,18 +205,6 @@ public static Class jvmReturnType(FunctionType type) { } } - /** - * 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: @@ -240,14 +228,6 @@ 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()); } diff --git a/compiler/src/main/java/run/endive/compiler/internal/Context.java b/compiler/src/main/java/run/endive/compiler/internal/Context.java index c14cc5923..465fd5e1f 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/Context.java +++ b/compiler/src/main/java/run/endive/compiler/internal/Context.java @@ -180,14 +180,6 @@ public String classNameForFuncGroup(String prefix, int funcId) { return prefix + "FuncGroup_" + (funcId / maxFunctionsPerClass); } - /** - * Returns true if the table at the given index has a GC reference element type. - */ - public boolean isGcTable(int tableIndex) { - var tableTypes = WasmAnalyzer.getTableTypes(module); - return tableIndex < tableTypes.size() && tableTypes.get(tableIndex).isGcReference(); - } - public FunctionType tagFunctionType(int tagId) { if (tagId < 0) { throw new IllegalArgumentException("Tag ID must be non-negative"); 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 528f8d442..c6214cac8 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/Emitters.java +++ b/compiler/src/main/java/run/endive/compiler/internal/Emitters.java @@ -178,12 +178,20 @@ private static void emitBoxValuesOnStack( for (int i = 0; i < types.size(); i++) { ValType valType = types.get(i); - asm.dup(); // Duplicate the array reference - asm.iconst(i); // Array index - asm.load(slot, asmType(valType)); // Load value from local + if (valType.isGcReference()) { + // Object refs can't be stored in long[] — store 0L as placeholder + asm.dup(); + asm.iconst(i); + asm.lconst(0L); + asm.astore(LONG_TYPE); + } else { + asm.dup(); + asm.iconst(i); + asm.load(slot, asmType(valType)); + emitJvmToLong(asm, valType); + asm.astore(LONG_TYPE); + } slot += slotCount(valType); - emitJvmToLong(asm, valType); // Convert to long - asm.astore(LONG_TYPE); // Store in array } } 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 82d6534e7..d28ddadd3 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/Shaded.java +++ b/compiler/src/main/java/run/endive/compiler/internal/Shaded.java @@ -861,7 +861,14 @@ public static Object structNewDefault(int typeIdx, Instance instance) { var st = instance.module().typeSection().getSubType(typeIdx).compType().structType(); var fields = new long[st.fieldTypes().length]; var fieldRefs = new Object[st.fieldTypes().length]; - // numeric fields default to 0 (already), ref fields default to null (already) + for (int i = 0; i < st.fieldTypes().length; i++) { + var ft = st.fieldTypes()[i]; + if (ft.storageType().valType() != null + && ft.storageType().valType().isReference() + && !ft.storageType().isGcReference()) { + fields[i] = REF_NULL_VALUE; + } + } return new WasmStruct(typeIdx, fields, fieldRefs); } @@ -948,7 +955,13 @@ public static Object arrayNewRef(Object initVal, int len, int typeIdx, Instance 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) + 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()) { + Arrays.fill(elems, REF_NULL_VALUE); + } return new WasmArray(typeIdx, elems, elemRefs); } @@ -1323,10 +1336,7 @@ public static int externConvertAny(Object ref) { if (ref instanceof Integer) { return (Integer) ref; } - // Non-null GC ref (struct, array, i31, etc.) converted to externref. - // Return a non-null sentinel since the externref is opaque. - int hash = System.identityHashCode(ref); - return hash == REF_NULL_VALUE ? 0 : hash; + return System.identityHashCode(ref); } public static void dataDrop(int segment, Instance instance) { diff --git a/machine-tests/src/test/java/run/endive/testing/GcEdgeCasesTest.java b/machine-tests/src/test/java/run/endive/testing/GcEdgeCasesTest.java new file mode 100644 index 000000000..dafbe91c5 --- /dev/null +++ b/machine-tests/src/test/java/run/endive/testing/GcEdgeCasesTest.java @@ -0,0 +1,66 @@ +package run.endive.testing; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.function.Function; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import run.endive.compiler.MachineFactoryCompiler; +import run.endive.corpus.CorpusResources; +import run.endive.runtime.Instance; +import run.endive.runtime.InterpreterMachine; +import run.endive.wasm.Parser; +import run.endive.wasm.WasmModule; + +public class GcEdgeCasesTest { + + private static final WasmModule MODULE = + Parser.parse(CorpusResources.getResource("compiled/gc_edge_cases.wat.wasm")); + + private static Stream machineImplementations() { + return Stream.of( + Arguments.of( + (Function) + (b) -> b.withMachineFactory(InterpreterMachine::new)), + Arguments.of( + (Function) + (b) -> b.withMachineFactory(MachineFactoryCompiler::compile))); + } + + @ParameterizedTest + @MethodSource("machineImplementations") + public void structNewDefaultFuncrefIsNull( + Function machineInject) { + var instance = machineInject.apply(Instance.builder(MODULE)).build(); + var result = instance.export("default_funcref_is_null").apply(); + assertEquals(1, result[0]); + } + + // TODO: compiler uses int for externref — extern.convert_any round-trip is lossy. + // Fix requires making externref Object in the compiler (isObjectRef). + @Test + public void externRoundTripInterpreter() { + var instance = Instance.builder(MODULE).withMachineFactory(InterpreterMachine::new).build(); + var result = instance.export("extern_roundtrip").apply(); + assertEquals(42, result[0]); + } + + @ParameterizedTest + @MethodSource("machineImplementations") + public void makeAndGetPoint(Function machineInject) { + var instance = machineInject.apply(Instance.builder(MODULE)).build(); + var makePoint = instance.export("make_point"); + var getX = instance.export("get_x"); + + var point = makePoint.applyGc((Object) 99L); + assertNotNull(point); + assertNotNull(point[0]); + + var x = getX.applyGc(point[0]); + assertEquals(99, (int) (Integer) x[0]); + } +} diff --git a/machine-tests/src/test/java/run/endive/testing/GcStressTest.java b/machine-tests/src/test/java/run/endive/testing/GcStressTest.java new file mode 100644 index 000000000..a8a05e9c7 --- /dev/null +++ b/machine-tests/src/test/java/run/endive/testing/GcStressTest.java @@ -0,0 +1,46 @@ +package run.endive.testing; + +import java.util.function.Function; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import run.endive.compiler.MachineFactoryCompiler; +import run.endive.corpus.CorpusResources; +import run.endive.runtime.Instance; +import run.endive.runtime.InterpreterMachine; +import run.endive.wasm.Parser; +import run.endive.wasm.WasmModule; + +public class GcStressTest { + + private static final WasmModule MODULE = + Parser.parse(CorpusResources.getResource("compiled/gc_stress.wat.wasm")); + + private static Stream machineImplementations() { + return Stream.of( + Arguments.of( + (Function) + (b) -> b.withMachineFactory(InterpreterMachine::new)), + Arguments.of( + (Function) + (b) -> b.withMachineFactory(MachineFactoryCompiler::compile))); + } + + @ParameterizedTest + @MethodSource("machineImplementations") + public void gcCollectsUnreachableStructs( + Function machineInject) { + var instance = machineInject.apply(Instance.builder(MODULE)).build(); + var allocateChain = instance.export("allocate_chain"); + + // Allocate chains of 10,000 structs, 100 times. + // Each call creates a new chain; the previous chain becomes garbage. + // If Java GC doesn't collect unreachable Wasm GC refs, this OOMs. + for (int i = 0; i < 100; i++) { + var result = allocateChain.apply(10_000); + // Last struct in chain has val = n-1 + assert result[0] == 9999 : "expected 9999 but got " + result[0]; + } + } +} diff --git a/wasm-corpus/src/main/resources/compiled/gc_edge_cases.wat.wasm b/wasm-corpus/src/main/resources/compiled/gc_edge_cases.wat.wasm new file mode 100644 index 0000000000000000000000000000000000000000..e28945598088b49052563c6a4a4dbc85addcf4a3 GIT binary patch literal 214 zcmW-bK@Ng25JmqlB`UaZrHO2G>lIuXFW?Odkz$BY8!QPsuBhkf#@5CBWd2L$4XSrf z0NhC>W>}QqTykcOG7RS=m83`(SGlWoQ;nTd&1mj}ZdChLX6!m(qbH{aqlVXLYB#hN z%=+r7m3_6wL8K4u)Py8E%M70I0{rF$gWz)dSPvX=fG_;sZzDv$3i}`A%Sd8{lQdPY YMJ`CtiV%}r66q~UvNaK|PL%PxA6p1A@&Et; literal 0 HcmV?d00001 diff --git a/wasm-corpus/src/main/resources/compiled/gc_stress.wat.wasm b/wasm-corpus/src/main/resources/compiled/gc_stress.wat.wasm new file mode 100644 index 0000000000000000000000000000000000000000..d581c0c397a3f9b58f7265f9bf0c48cb79e74b0e GIT binary patch literal 161 zcmWlOK?=e!6b0Y=2^3me!4tH5@eK6@9-)5HKr3k?Sj1fp?#8&9VFvoz2!O3jlHq_m zGqVx0I context) { if (!isValidOpcode(opcode)) { From e17629c55612938361037c1fe0d5e1924164e930 Mon Sep 17 00:00:00 2001 From: andreatp Date: Thu, 11 Jun 2026 12:28:02 +0100 Subject: [PATCH 07/20] =?UTF-8?q?feat:=20externref=20as=20Object=20+=20Cal?= =?UTF-8?q?lResult=20API=20=E2=80=94=20spec-compliant=20round-trips?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make externref use Object representation (same as GC refs). Only funcref stays as int. extern.convert_any / any.convert_extern are now identity operations, satisfying the Wasm spec requirement that composing them yields the original value. API: - CallResult record: zero-boxing return type with long[] + Object[] - Machine.callWithRefs(int, long[], Object[]) returns CallResult - ExportFunction.applyWithRefs(long[], Object[]) returns CallResult - applyGc(Object...) stays as convenience wrapper - apply(long...) backward compatible for non-ref functions Internal: - isObjectRef() (GC refs + externref) replaces isGcReference() at all representation-deciding sites (compiler + interpreter) - isGcReference() kept only for Wasm type system checks - Tables: externref tables use Object[] objRefs (isObjectRef) - Shaded: extern conversions are identity (Object → Object) - structNewDefault/arrayNewDefault: only funcref needs REF_NULL_VALUE Zero overhead for non-GC non-externref modules. --- .../endive/compiler/internal/Compiler.java | 17 +- .../compiler/internal/CompilerUtil.java | 16 +- .../endive/compiler/internal/Emitters.java | 42 +-- .../run/endive/compiler/internal/Shaded.java | 25 +- .../endive/compiler/internal/ShadedRefs.java | 2 +- .../compiler/internal/WasmAnalyzer.java | 8 +- .../run/endive/testing/GcEdgeCasesTest.java | 15 +- .../java/run/endive/runtime/CallResult.java | 27 ++ .../endive/runtime/ConstantEvaluators.java | 10 +- .../run/endive/runtime/ExportFunction.java | 4 + .../java/run/endive/runtime/Instance.java | 23 +- .../endive/runtime/InterpreterMachine.java | 247 ++++++++++++------ .../main/java/run/endive/runtime/Machine.java | 5 + .../java/run/endive/runtime/OpcodeImpl.java | 10 +- .../run/endive/runtime/TableInstance.java | 4 +- .../internal/CompilerInterpreterMachine.java | 44 ++-- .../run/endive/runtime/WasmModuleTest.java | 25 +- .../java/run/endive/testgen/JavaTestGen.java | 4 +- .../endive/testgen/wast/WasmValueType.java | 4 + .../java/run/endive/wasm/types/ValType.java | 8 + 20 files changed, 333 insertions(+), 207 deletions(-) create mode 100644 runtime/src/main/java/run/endive/runtime/CallResult.java 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 54c2f1d3d..4aa046ec9 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/Compiler.java +++ b/compiler/src/main/java/run/endive/compiler/internal/Compiler.java @@ -1081,7 +1081,7 @@ private void compileCallFunction(int funcId, FunctionType type, InstructionAdapt // 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); - if (param.isGcReference()) { + if (param.isObjectRef()) { // GC ref args: load from Object[] refArgs asm.load(3, OBJECT_TYPE); // refArgs asm.iconst(i); @@ -1396,7 +1396,7 @@ private static void emitBoxArguments(InstructionAdapter asm, List types asm.iconst(i); ValType valType = types.get(i); asm.load(slot, asmType(valType)); - if (valType.isGcReference()) { + if (valType.isObjectRef()) { asm.visitInsn(Opcodes.POP); asm.lconst(0L); } else { @@ -1489,10 +1489,15 @@ private void compileFunction( // unbox the arguments from long[] for (int i = 0; i < type.params().size(); i++) { var param = type.params().get(i); - asm.load(0, OBJECT_TYPE); - asm.iconst(i); - asm.aload(LONG_TYPE); - emitLongToJvm(asm, param); + if (param.isObjectRef()) { + // Object refs can't be stored in long[] — use null as default + asm.aconst(null); + } else { + asm.load(0, OBJECT_TYPE); + asm.iconst(i); + asm.aload(LONG_TYPE); + emitLongToJvm(asm, param); + } asm.store(ctx.localSlotIndex(i), asmType(param)); } // since we just converted the arguments to long[]. 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 1603d5a3f..2a8fa9787 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/CompilerUtil.java +++ b/compiler/src/main/java/run/endive/compiler/internal/CompilerUtil.java @@ -58,7 +58,7 @@ public static Class jvmType(ValType type) { return int.class; case ValType.ID.Ref: case ValType.ID.RefNull: - return type.isGcReference() ? Object.class : int.class; + return type.isObjectRef() ? Object.class : int.class; case ValType.ID.I64: return long.class; case ValType.ID.F32: @@ -79,7 +79,7 @@ public static Type asmType(ValType type) { return INT_TYPE; case ValType.ID.Ref: case ValType.ID.RefNull: - return type.isGcReference() ? OBJECT_ASM_TYPE : INT_TYPE; + return type.isObjectRef() ? OBJECT_ASM_TYPE : INT_TYPE; case ValType.ID.I64: return LONG_TYPE; case ValType.ID.F32: @@ -107,7 +107,7 @@ public static void emitLongToJvm(MethodVisitor asm, ValType type) { return; case ValType.ID.Ref: case ValType.ID.RefNull: - if (type.isGcReference()) { + if (type.isObjectRef()) { // GC refs are Objects - this conversion should not be called for them // when unboxing from long[]. They come from Object[] instead. return; @@ -135,7 +135,7 @@ public static void emitJvmToLong(MethodVisitor asm, ValType type) { return; case ValType.ID.Ref: case ValType.ID.RefNull: - if (type.isGcReference()) { + if (type.isObjectRef()) { // GC refs are Objects - this conversion should not be called for them // when boxing into long[]. They go into Object[] instead. return; @@ -156,7 +156,7 @@ public static void emitJvmToLong(MethodVisitor asm, ValType type) { } public static MethodType valueMethodType(List types) { - boolean hasGcRef = types.stream().anyMatch(t -> t.isGcReference()); + boolean hasGcRef = types.stream().anyMatch(t -> t.isObjectRef()); Class returnType = hasGcRef ? Object[].class : long[].class; return methodType(returnType, jvmTypes(types)); } @@ -197,7 +197,7 @@ public static Class jvmReturnType(FunctionType type) { default: // If any return value is a GC ref (Object), use Object[] instead of long[] for (ValType ret : type.returns()) { - if (ret.isGcReference()) { + if (ret.isObjectRef()) { return Object[].class; } } @@ -217,7 +217,7 @@ public static Object defaultValue(ValType type) { return 0.0d; case ValType.ID.Ref: case ValType.ID.RefNull: - if (type.isGcReference()) { + if (type.isObjectRef()) { return null; // GC refs use null as their default } return REF_NULL_VALUE; @@ -250,7 +250,7 @@ public static int slotCount(long valTypeId) { } public static void emitPop(MethodVisitor asm, ValType type) { - if (type.isGcReference()) { + if (type.isObjectRef()) { asm.visitInsn(Opcodes.POP); // Object refs are always 1 slot } else { asm.visitInsn(slotCount(type) == 1 ? Opcodes.POP : Opcodes.POP2); 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 c6214cac8..cd1201701 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/Emitters.java +++ b/compiler/src/main/java/run/endive/compiler/internal/Emitters.java @@ -94,7 +94,7 @@ private static boolean isSourceGcRef(int srcHeapType, Context ctx) { .withTypeIdx(srcHeapType) .build() .resolve(ctx.typeSection()); - return srcType.isGcReference(); + return srcType.isObjectRef(); } public static void DROP_KEEP(Context ctx, CompilerInstruction ins, InstructionAdapter asm) { @@ -178,7 +178,7 @@ private static void emitBoxValuesOnStack( for (int i = 0; i < types.size(); i++) { ValType valType = types.get(i); - if (valType.isGcReference()) { + if (valType.isObjectRef()) { // Object refs can't be stored in long[] — store 0L as placeholder asm.dup(); asm.iconst(i); @@ -219,7 +219,7 @@ private static void emitBoxFieldsForStruct( slot = ctx.tempSlot(); for (int i = 0; i < types.size(); i++) { ValType valType = types.get(i); - if (!valType.isGcReference()) { + if (!valType.isObjectRef()) { asm.dup(); asm.iconst(i); asm.load(slot, asmType(valType)); @@ -236,7 +236,7 @@ private static void emitBoxFieldsForStruct( slot = ctx.tempSlot(); for (int i = 0; i < types.size(); i++) { ValType valType = types.get(i); - if (valType.isGcReference()) { + if (valType.isObjectRef()) { asm.dup(); asm.iconst(i); asm.load(slot, asmType(valType)); @@ -340,7 +340,7 @@ public static void REF_NULL(Context ctx, CompilerInstruction ins, InstructionAda .withTypeIdx((int) ins.operand(0)) .build() .resolve(ctx.typeSection()); - if (type.isGcReference()) { + if (type.isObjectRef()) { asm.aconst(null); } else { asm.iconst(REF_NULL_VALUE); @@ -350,7 +350,7 @@ public static void REF_NULL(Context ctx, CompilerInstruction ins, InstructionAda public static void REF_IS_NULL(Context ctx, CompilerInstruction ins, InstructionAdapter asm) { // operand(0) is the ValType id of the ref on stack var type = valType(ins.operand(0), ctx); - if (type.isGcReference()) { + if (type.isObjectRef()) { emitInvokeStatic(asm, ShadedRefs.GC_REF_IS_NULL); } else { emitInvokeStatic(asm, ShadedRefs.REF_IS_NULL); @@ -365,7 +365,7 @@ public static void REF_EQ(Context ctx, CompilerInstruction ins, InstructionAdapt public static void REF_AS_NON_NULL( Context ctx, CompilerInstruction ins, InstructionAdapter asm) { var type = valType(ins.operand(0), ctx); - if (type.isGcReference()) { + if (type.isObjectRef()) { emitInvokeStatic(asm, ShadedRefs.GC_REF_AS_NON_NULL); } else { emitInvokeStatic(asm, ShadedRefs.REF_AS_NON_NULL); @@ -401,7 +401,7 @@ public static void GLOBAL_GET(Context ctx, CompilerInstruction ins, InstructionA asm.iconst(globalIndex); asm.load(ctx.instanceSlot(), OBJECT_TYPE); - if (globalType.isGcReference()) { + if (globalType.isObjectRef()) { // GC refs: returns Object directly emitInvokeStatic(asm, ShadedRefs.READ_GLOBAL_REF); } else { @@ -415,7 +415,7 @@ public static void GLOBAL_SET(Context ctx, CompilerInstruction ins, InstructionA int globalIndex = (int) ins.operand(0); var globalType = ctx.globalTypes().get(globalIndex); - if (globalType.isGcReference()) { + if (globalType.isObjectRef()) { // GC refs: Object on stack asm.iconst(globalIndex); asm.load(ctx.instanceSlot(), OBJECT_TYPE); @@ -1318,7 +1318,7 @@ public static void THROW(Context ctx, CompilerInstruction ins, InstructionAdapte int tagNumber = (int) ins.operand(0); var type = ctx.tagFunctionType(tagNumber); - boolean hasGcRefs = type.params().stream().anyMatch(ValType::isGcReference); + boolean hasGcRefs = type.params().stream().anyMatch(ValType::isObjectRef); if (hasGcRefs) { emitBoxFieldsForStruct(ctx, asm, type.params()); asm.iconst(tagNumber); @@ -1419,7 +1419,7 @@ public static void CATCH_UNBOX_PARAMS( var tag = (int) ins.operand(0); var tagFuncType = ctx.tagFunctionType(tag); if (!tagFuncType.params().isEmpty()) { - boolean hasGcRefs = tagFuncType.params().stream().anyMatch(ValType::isGcReference); + boolean hasGcRefs = tagFuncType.params().stream().anyMatch(ValType::isObjectRef); int longArraySlot = ctx.tempSlot() + 1; int refArraySlot = ctx.tempSlot() + 2; @@ -1446,7 +1446,7 @@ public static void CATCH_UNBOX_PARAMS( // Unbox each argument for (int i = 0; i < tagFuncType.params().size(); i++) { var param = tagFuncType.params().get(i); - if (param.isGcReference()) { + if (param.isObjectRef()) { asm.load(refArraySlot, OBJECT_TYPE); asm.iconst(i); asm.aload(OBJECT_TYPE); @@ -1570,7 +1570,7 @@ public static void STRUCT_GET(Context ctx, CompilerInstruction ins, InstructionA asm.iconst(fieldIdx); asm.load(ctx.instanceSlot(), OBJECT_TYPE); - if (ft.storageType().isGcReference()) { + if (ft.storageType().isObjectRef()) { // returns Object emitInvokeStatic(asm, ShadedRefs.STRUCT_GET_REF); } else { @@ -1621,7 +1621,7 @@ public static void STRUCT_SET(Context ctx, CompilerInstruction ins, InstructionA var st = ctx.typeSection().getSubType(typeIdx).compType().structType(); var ft = st.fieldTypes()[fieldIdx]; - if (ft.storageType().isGcReference()) { + if (ft.storageType().isObjectRef()) { // val is Object on stack asm.iconst(typeIdx); asm.iconst(fieldIdx); @@ -1645,7 +1645,7 @@ public static void ARRAY_NEW(Context ctx, CompilerInstruction ins, InstructionAd int typeIdx = (int) ins.operand(0); var at = ctx.typeSection().getSubType(typeIdx).compType().arrayType(); - if (at.fieldType().storageType().isGcReference()) { + if (at.fieldType().storageType().isObjectRef()) { // stack: [initVal (Object), len] asm.store(ctx.tempSlot(), INT_TYPE); // initVal is Object on stack @@ -1684,7 +1684,7 @@ public static void ARRAY_NEW_FIXED( int len = (int) ins.operand(1); var at = ctx.typeSection().getSubType(typeIdx).compType().arrayType(); - if (at.fieldType().storageType().isGcReference()) { + if (at.fieldType().storageType().isObjectRef()) { // Elements are Object refs, box into Object[] emitBoxRefsOnStack(ctx, asm, len); asm.iconst(typeIdx); @@ -1744,7 +1744,7 @@ public static void ARRAY_GET(Context ctx, CompilerInstruction ins, InstructionAd asm.iconst(typeIdx); asm.load(ctx.instanceSlot(), OBJECT_TYPE); - if (at.fieldType().storageType().isGcReference()) { + if (at.fieldType().storageType().isObjectRef()) { emitInvokeStatic(asm, ShadedRefs.ARRAY_GET_REF); } else { emitInvokeStatic(asm, ShadedRefs.ARRAY_GET); @@ -1782,7 +1782,7 @@ public static void ARRAY_SET(Context ctx, CompilerInstruction ins, InstructionAd // stack: [ref (Object), idx, val] var at = ctx.typeSection().getSubType(typeIdx).compType().arrayType(); - if (at.fieldType().storageType().isGcReference()) { + if (at.fieldType().storageType().isObjectRef()) { // val is Object on stack asm.iconst(typeIdx); asm.load(ctx.instanceSlot(), OBJECT_TYPE); @@ -1809,7 +1809,7 @@ public static void ARRAY_FILL(Context ctx, CompilerInstruction ins, InstructionA int typeIdx = (int) ins.operand(0); var at = ctx.typeSection().getSubType(typeIdx).compType().arrayType(); - if (at.fieldType().storageType().isGcReference()) { + if (at.fieldType().storageType().isObjectRef()) { // stack: [ref (Object), offset, val (Object), len] asm.store(ctx.tempSlot(), INT_TYPE); // stack: [ref, offset, val] @@ -1936,7 +1936,7 @@ public static void EXTERN_CONVERT_ANY( public static void BR_ON_NULL_CHECK( Context ctx, CompilerInstruction ins, InstructionAdapter asm) { var type = valType(ins.operand(0), ctx); - boolean isGcRef = type.isGcReference(); + boolean isGcRef = type.isObjectRef(); asm.dup(); var isNull = new Label(); var end = new Label(); @@ -1956,7 +1956,7 @@ public static void BR_ON_NULL_CHECK( public static void BR_ON_NON_NULL_CHECK( Context ctx, CompilerInstruction ins, InstructionAdapter asm) { var type = valType(ins.operand(0), ctx); - boolean isGcRef = type.isGcReference(); + boolean isGcRef = type.isObjectRef(); asm.dup(); var isNull = new Label(); var end = new Label(); 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 d28ddadd3..1bbc29cc6 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/Shaded.java +++ b/compiler/src/main/java/run/endive/compiler/internal/Shaded.java @@ -865,7 +865,7 @@ public static Object structNewDefault(int typeIdx, Instance instance) { var ft = st.fieldTypes()[i]; if (ft.storageType().valType() != null && ft.storageType().valType().isReference() - && !ft.storageType().isGcReference()) { + && !ft.storageType().isObjectRef()) { fields[i] = REF_NULL_VALUE; } } @@ -959,7 +959,7 @@ public static Object arrayNewDefault(int len, int typeIdx, Instance instance) { var ft = at.fieldType(); if (ft.storageType().valType() != null && ft.storageType().valType().isReference() - && !ft.storageType().isGcReference()) { + && !ft.storageType().isObjectRef()) { Arrays.fill(elems, REF_NULL_VALUE); } return new WasmArray(typeIdx, elems, elemRefs); @@ -997,7 +997,7 @@ public static Object arrayNewElem( throw new TrapException("out of bounds table access"); } var at = instance.module().typeSection().getSubType(typeIdx).compType().arrayType(); - boolean isRef = at.fieldType().storageType().isGcReference(); + boolean isRef = at.fieldType().storageType().isObjectRef(); var elems = new long[len]; var elemRefs = new Object[len]; for (int i = 0; i < len; i++) { @@ -1207,7 +1207,7 @@ public static void arrayInitElem( return; } var at = instance.module().typeSection().getSubType(typeIdx).compType().arrayType(); - boolean isRef = at.fieldType().storageType().isGcReference(); + boolean isRef = at.fieldType().storageType().isObjectRef(); for (int i = 0; i < len; i++) { var init = element.initializers().get(srcOff + i); var result = ConstantEvaluators.computeConstant(instance, init); @@ -1322,21 +1322,12 @@ private static long readFromData(byte[] data, int offset, int size) { return val; } - public static Object anyConvertExtern(int ref) { - if (ref == REF_NULL_VALUE) { - return null; - } - return Integer.valueOf(ref); + public static Object anyConvertExtern(Object ref) { + return ref; } - public static int externConvertAny(Object ref) { - if (ref == null) { - return REF_NULL_VALUE; - } - if (ref instanceof Integer) { - return (Integer) ref; - } - return System.identityHashCode(ref); + public static Object externConvertAny(Object ref) { + return ref; } public static void dataDrop(int segment, Instance instance) { 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 b0bdcf811..00a7f9bcd 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/ShadedRefs.java +++ b/compiler/src/main/java/run/endive/compiler/internal/ShadedRefs.java @@ -969,7 +969,7 @@ public final class ShadedRefs { 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); - ANY_CONVERT_EXTERN = Shaded.class.getMethod("anyConvertExtern", int.class); + ANY_CONVERT_EXTERN = Shaded.class.getMethod("anyConvertExtern", Object.class); EXTERN_CONVERT_ANY = Shaded.class.getMethod("externConvertAny", Object.class); DATA_DROP = Shaded.class.getMethod("dataDrop", int.class, Instance.class); 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 046a3fb36..cf13e2af9 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/WasmAnalyzer.java +++ b/compiler/src/main/java/run/endive/compiler/internal/WasmAnalyzer.java @@ -1328,7 +1328,7 @@ private void analyzeSimple( var fillValType = stack.peek().resolve(module.typeSection()); stack.pop(fillValType); stack.pop(ValType.I32); - long[] fillOps = {ins.operand(0), fillValType.isGcReference() ? 1 : 0}; + long[] fillOps = {ins.operand(0), fillValType.isObjectRef() ? 1 : 0}; out.add(new CompilerInstruction(CompilerOpCode.TABLE_FILL, fillOps)); return; } @@ -1339,7 +1339,7 @@ private void analyzeSimple( var tableElemType = tableTypes.get((int) ins.operand(0)); tableElemType.resolve(module.typeSection()); stack.push(tableElemType); - long[] getOps = {ins.operand(0), tableElemType.isGcReference() ? 1 : 0}; + long[] getOps = {ins.operand(0), tableElemType.isObjectRef() ? 1 : 0}; out.add(new CompilerInstruction(CompilerOpCode.TABLE_GET, getOps)); return; } @@ -1350,7 +1350,7 @@ private void analyzeSimple( var growValType = stack.peek().resolve(module.typeSection()); stack.pop(growValType); stack.push(ValType.I32); - long[] growOps = {ins.operand(0), growValType.isGcReference() ? 1 : 0}; + long[] growOps = {ins.operand(0), growValType.isObjectRef() ? 1 : 0}; out.add(new CompilerInstruction(CompilerOpCode.TABLE_GROW, growOps)); return; } @@ -1360,7 +1360,7 @@ private void analyzeSimple( var setValType = stack.peek().resolve(module.typeSection()); stack.pop(setValType); stack.pop(ValType.I32); - long[] setOps = {ins.operand(0), setValType.isGcReference() ? 1 : 0}; + long[] setOps = {ins.operand(0), setValType.isObjectRef() ? 1 : 0}; out.add(new CompilerInstruction(CompilerOpCode.TABLE_SET, setOps)); return; } diff --git a/machine-tests/src/test/java/run/endive/testing/GcEdgeCasesTest.java b/machine-tests/src/test/java/run/endive/testing/GcEdgeCasesTest.java index dafbe91c5..5c468283c 100644 --- a/machine-tests/src/test/java/run/endive/testing/GcEdgeCasesTest.java +++ b/machine-tests/src/test/java/run/endive/testing/GcEdgeCasesTest.java @@ -5,7 +5,6 @@ import java.util.function.Function; import java.util.stream.Stream; -import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -40,13 +39,13 @@ public void structNewDefaultFuncrefIsNull( assertEquals(1, result[0]); } - // TODO: compiler uses int for externref — extern.convert_any round-trip is lossy. - // Fix requires making externref Object in the compiler (isObjectRef). - @Test - public void externRoundTripInterpreter() { - var instance = Instance.builder(MODULE).withMachineFactory(InterpreterMachine::new).build(); - var result = instance.export("extern_roundtrip").apply(); - assertEquals(42, result[0]); + @ParameterizedTest + @MethodSource("machineImplementations") + public void externRoundTrip(Function machineInject) { + var instance = machineInject.apply(Instance.builder(MODULE)).build(); + var result = instance.export("extern_roundtrip").applyGc(42L); + assertNotNull(result); + assertEquals(42, ((Number) result[0]).intValue()); } @ParameterizedTest diff --git a/runtime/src/main/java/run/endive/runtime/CallResult.java b/runtime/src/main/java/run/endive/runtime/CallResult.java new file mode 100644 index 000000000..baa5fd793 --- /dev/null +++ b/runtime/src/main/java/run/endive/runtime/CallResult.java @@ -0,0 +1,27 @@ +package run.endive.runtime; + +public final class CallResult { + private final long[] longs; + private final Object[] refs; + + public CallResult(long[] longs, Object[] refs) { + this.longs = longs; + this.refs = refs; + } + + public long[] longs() { + return longs; + } + + public Object[] refs() { + return refs; + } + + public long longResult(int i) { + return longs != null ? longs[i] : 0; + } + + public Object refResult(int i) { + return refs != null ? refs[i] : null; + } +} diff --git a/runtime/src/main/java/run/endive/runtime/ConstantEvaluators.java b/runtime/src/main/java/run/endive/runtime/ConstantEvaluators.java index 004ae6a6d..d80daf14c 100644 --- a/runtime/src/main/java/run/endive/runtime/ConstantEvaluators.java +++ b/runtime/src/main/java/run/endive/runtime/ConstantEvaluators.java @@ -158,7 +158,7 @@ public static ConstantResult computeConstant(Instance instance, List= 0; i--) { var entry = stack.pop(); var ft = structType.fieldTypes()[i]; - if (ft.storageType().isGcReference()) { + if (ft.storageType().isObjectRef()) { fieldRefs[i] = entry.ref(); } else { fields[i] = entry.longValue(); @@ -185,7 +185,7 @@ public static ConstantResult computeConstant(Instance instance, List= 0; i--) { var entry = stack.pop(); if (isGcRef) { diff --git a/runtime/src/main/java/run/endive/runtime/ExportFunction.java b/runtime/src/main/java/run/endive/runtime/ExportFunction.java index 872dad807..7c7560157 100644 --- a/runtime/src/main/java/run/endive/runtime/ExportFunction.java +++ b/runtime/src/main/java/run/endive/runtime/ExportFunction.java @@ -9,6 +9,10 @@ public interface ExportFunction { long[] apply(long... args) throws WasmEngineException; + default CallResult applyWithRefs(long[] args, Object[] refArgs) throws WasmEngineException { + throw new UnsupportedOperationException("This function does not support applyWithRefs"); + } + 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 2f9da8981..e17de0d1a 100644 --- a/runtime/src/main/java/run/endive/runtime/Instance.java +++ b/runtime/src/main/java/run/endive/runtime/Instance.java @@ -137,7 +137,7 @@ static final class TailCallPending { } else { this.tables[i] = new TableInstance(tables[i], initValue); } - if (tables[i].elementType().isGcReference() && result.ref() != null) { + if (tables[i].elementType().isObjectRef() && result.ref() != null) { var tbl = this.tables[i]; for (int j = 0; j < tbl.size(); j++) { tbl.setObjRef(j, result.ref(), this); @@ -189,14 +189,14 @@ public Instance initialize(boolean start) { || (offset + initializers.size() - 1) >= table.size()) { throw new UninstantiableException("out of bounds table access"); } - boolean isGcTable = table.elementType().isGcReference(); + boolean isObjRefTable = table.elementType().isObjectRef(); for (int i = 0; i < initializers.size(); i++) { final List init = initializers.get(i); int index = offset + i; var inst = computeConstantInstance(this, init); assert ae.type().isReference(); - if (isGcTable) { + if (isObjRefTable) { var result = computeConstant(this, init); table.setObjRef(index, result.ref(), inst); } else { @@ -279,20 +279,15 @@ 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); return new ExportFunction() { @Override public long[] apply(long... args) { - var result = instance.machine.call(export.index(), args); - if (hasGcReturns) { - throw new UnsupportedOperationException( - "Function '" - + name - + "' returns GC references." - + " Use applyGc() instead."); - } - return result; + return instance.machine.call(export.index(), args); + } + + @Override + public CallResult applyWithRefs(long[] args, Object[] refArgs) { + return instance.machine.callWithRefs(export.index(), args, refArgs); } @Override diff --git a/runtime/src/main/java/run/endive/runtime/InterpreterMachine.java b/runtime/src/main/java/run/endive/runtime/InterpreterMachine.java index 9b31f2461..50dd46e4e 100644 --- a/runtime/src/main/java/run/endive/runtime/InterpreterMachine.java +++ b/runtime/src/main/java/run/endive/runtime/InterpreterMachine.java @@ -108,6 +108,24 @@ protected long[] call( } } } else { + // For host functions, convert Object refArgs back to longs + // since host function interface only accepts long[] + if (refArgs != null) { + int slot = 0; + for (int pi = 0; pi < type.params().size(); pi++) { + var param = type.params().get(pi); + if (param.isObjectRef() && refArgs[slot] != null) { + if (refArgs[slot] instanceof Number) { + args[slot] = ((Number) refArgs[slot]).longValue(); + } else { + args[slot] = System.identityHashCode(refArgs[slot]); + } + } else if (param.isObjectRef() && refArgs[slot] == null) { + args[slot] = REF_NULL_VALUE; + } + slot += param.equals(ValType.V128) ? 2 : 1; + } + } var stackFrame = new StackFrame(instance, funcId, args); stackFrame.pushCtrl(OpCode.CALL, 0, sizeOf(type.returns()), stack.size()); callStack.push(stackFrame); @@ -119,8 +137,26 @@ protected long[] call( // a host function can return null or an array of ints // which we will push onto the stack if (results != null) { - for (var result : results) { - stack.push(result); + int slot = 0; + for (int ri = 0; ri < type.returns().size() && slot < results.length; ri++) { + var retType = type.returns().get(ri); + if (retType.isObjectRef()) { + // Host function returns long for externref — wrap as Object + long val = results[slot]; + if (val == REF_NULL_VALUE) { + stack.pushRef(null); + } else { + stack.pushRef(Long.valueOf(val)); + } + slot++; + } else if (retType.equals(ValType.V128)) { + stack.push(results[slot]); + stack.push(results[slot + 1]); + slot += 2; + } else { + stack.push(results[slot]); + slot++; + } } } } catch (WasmException e) { @@ -150,10 +186,16 @@ protected long[] call( int slot = totalResults; for (int r = type.returns().size() - 1; r >= 0; r--) { var retType = type.returns().get(r); - if (retType.isGcReference()) { + if (retType.isObjectRef()) { slot--; var ref = stack.popRef(); - results[slot] = (ref == null) ? Value.REF_NULL_VALUE : 0; + if (ref == null) { + results[slot] = Value.REF_NULL_VALUE; + } else if (ref instanceof Number) { + results[slot] = ((Number) ref).longValue(); + } else { + results[slot] = System.identityHashCode(ref); + } } else if (retType.equals(ValType.V128)) { slot -= 2; results[slot + 1] = stack.pop(); @@ -166,6 +208,44 @@ protected long[] call( return results; } + @Override + public CallResult callWithRefs(int funcId, long[] args, Object[] refArgs) + throws WasmEngineException { + checkInterruption(); + var typeId = instance.functionType(funcId); + var type = instance.type(typeId); + + call(stack, instance, callStack, funcId, args, refArgs, null, false); + + if (type.returns().isEmpty() || stack.size() == 0) { + return new CallResult(null, null); + } + + var totalResults = sizeOf(type.returns()); + var longResults = new long[totalResults]; + Object[] refResults = null; + int slot = totalResults; + for (int r = type.returns().size() - 1; r >= 0; r--) { + var retType = type.returns().get(r); + if (retType.isObjectRef()) { + slot--; + var ref = stack.popRef(); + if (refResults == null) { + refResults = new Object[totalResults]; + } + refResults[slot] = ref; + } else if (retType.equals(ValType.V128)) { + slot -= 2; + longResults[slot + 1] = stack.pop(); + longResults[slot] = stack.pop(); + } else { + slot--; + longResults[slot] = stack.pop(); + } + } + return new CallResult(longResults, refResults); + } + @Override public Object[] callGc(int funcId, Object[] gcArgs) throws WasmEngineException { checkInterruption(); @@ -177,11 +257,17 @@ public Object[] callGc(int funcId, Object[] gcArgs) throws WasmEngineException { int slot = 0; for (int i = 0; i < type.params().size(); i++) { var param = type.params().get(i); - if (param.isGcReference()) { + if (param.isObjectRef()) { if (refArgs == null) { refArgs = new Object[longArgs.length]; } - refArgs[slot] = (gcArgs != null && i < gcArgs.length) ? gcArgs[i] : null; + Object gcArg = (gcArgs != null && i < gcArgs.length) ? gcArgs[i] : null; + // Convert REF_NULL_VALUE sentinel to null for Object refs + if (gcArg instanceof Number + && ((Number) gcArg).longValue() == Value.REF_NULL_VALUE) { + gcArg = null; + } + refArgs[slot] = gcArg; slot++; } else if (param.equals(ValType.V128)) { if (gcArgs != null && i < gcArgs.length && gcArgs[i] instanceof long[]) { @@ -202,26 +288,31 @@ public Object[] callGc(int funcId, Object[] gcArgs) throws WasmEngineException { } } - call(stack, instance, callStack, funcId, longArgs, refArgs, null, false); + var cr = callWithRefs(funcId, longArgs, refArgs); - if (type.returns().isEmpty() || stack.size() == 0) { + if (type.returns().isEmpty()) { return null; } var results = new Object[type.returns().size()]; - for (int r = type.returns().size() - 1; r >= 0; r--) { + int resultSlot = 0; + for (int r = 0; r < type.returns().size(); r++) { var retType = type.returns().get(r); - if (retType.isGcReference()) { - results[r] = stack.popRef(); + if (retType.isObjectRef()) { + results[r] = cr.refResult(resultSlot); + resultSlot++; } else if (retType.isReference()) { - long val = stack.pop(); + long val = cr.longResult(resultSlot); results[r] = (val == Value.REF_NULL_VALUE) ? null : val; + resultSlot++; } else if (retType.equals(ValType.V128)) { - long hi = stack.pop(); - long lo = stack.pop(); + long lo = cr.longResult(resultSlot); + long hi = cr.longResult(resultSlot + 1); results[r] = new long[] {lo, hi}; + resultSlot += 2; } else { - results[r] = boxReturnValue(retType, stack.pop()); + results[r] = boxReturnValue(retType, cr.longResult(resultSlot)); + resultSlot++; } } return results; @@ -1862,11 +1953,10 @@ 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 { + // GC refs, externref, noexternref all use Object null stack.pushRef(null); } } @@ -1877,16 +1967,32 @@ private static void ELEM_DROP(Instance instance, Operands operands) { } private static void REF_IS_NULL(MStack stack) { - var val = stack.pop(); - stack.push(((val == REF_NULL_VALUE) ? Value.TRUE : Value.FALSE)); + Object refObj = stack.peekRef(); + if (refObj != null) { + // Non-null Object ref (GC or externref) + stack.popRef(); + stack.push(Value.FALSE); + } else { + // Either null Object ref or long-typed ref (funcref) — both use REF_NULL_VALUE on long + // side + var val = stack.pop(); + stack.push(((val == REF_NULL_VALUE) ? Value.TRUE : Value.FALSE)); + } } private static void REF_AS_NON_NULL(MStack stack) { - var val = stack.pop(); - if (val == REF_NULL_VALUE) { - throw new TrapException("Trapped on ref_as_non_null on null reference"); + Object refObj = stack.peekRef(); + if (refObj != null) { + // Non-null Object ref — leave it + stack.popRef(); + stack.pushRef(refObj); + } else { + var val = stack.pop(); + if (val == REF_NULL_VALUE) { + throw new TrapException("Trapped on ref_as_non_null on null reference"); + } + stack.push(val); } - stack.push(val); } private static void DATA_DROP(Instance instance, Operands operands) { @@ -1903,10 +2009,10 @@ 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.isGcReference(); + boolean isObjRefTable = et.isObjectRef(); var size = (int) stack.pop(); - if (isGcTable) { + if (isObjRefTable) { var refVal = stack.popRef(); var res = table.growWithRef(size, refVal, instance); stack.push(res); @@ -1928,10 +2034,10 @@ 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.isGcReference(); + boolean isObjRefTable = et.isObjectRef(); var size = (int) stack.pop(); - if (isGcTable) { + if (isObjRefTable) { var refVal = stack.popRef(); var offset = (int) stack.pop(); if (offset + size > table.size()) { @@ -2273,8 +2379,8 @@ 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.isGcReference(); - if (isGcTable) { + boolean isObjRefTable = et.isObjectRef(); + if (isObjRefTable) { var refVal = stack.popRef(); var i = (int) stack.pop(); table.setObjRef(i, refVal, instance); @@ -2289,9 +2395,9 @@ 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.isGcReference(); + boolean isObjRefTable = et.isObjectRef(); var i = (int) stack.pop(); - if (isGcTable) { + if (isObjRefTable) { stack.pushRef(table.objRef(i)); } else { var ref = OpcodeImpl.TABLE_GET(instance, idx, i); @@ -2302,7 +2408,7 @@ private static void TABLE_GET(MStack stack, Instance instance, Operands operands private static void GLOBAL_SET(MStack stack, Instance instance, Operands operands) { var id = (int) operands.get(0); var globalType = instance.global(id).getType(); - if (globalType.isGcReference()) { + if (globalType.isObjectRef()) { instance.global(id).setRefValue(stack.popRef()); } else if (globalType.equals(ValType.V128)) { var high = stack.pop(); @@ -2318,7 +2424,7 @@ private static void GLOBAL_SET(MStack stack, Instance instance, Operands operand private static void GLOBAL_GET(MStack stack, Instance instance, Operands operands) { int idx = (int) operands.get(0); var globalType = instance.global(idx).getType(); - if (globalType.isGcReference()) { + if (globalType.isObjectRef()) { stack.pushRef(instance.global(idx).getRefValue()); } else { stack.push(instance.global(idx).getValueLow()); @@ -2333,7 +2439,7 @@ private static void DROP(MStack stack, Operands operands) { if (typeId == ValType.ID.V128) { stack.pop(); stack.pop(); - } else if (ValType.builder().fromId(typeId).isGcReference()) { + } else if (ValType.builder().fromId(typeId).isObjectRef()) { stack.popRef(); } else { stack.pop(); @@ -2354,6 +2460,14 @@ private static void SELECT(MStack stack, Operands operands) { stack.push(a2); stack.push(a1); } + } else if (ValType.builder().fromId(operands.get(0)).isObjectRef()) { + 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(); @@ -2381,7 +2495,7 @@ private static void SELECT_T(MStack stack, Operands operands) { stack.push(a2); stack.push(a1); } - } else if (ValType.builder().fromId(typeId).isGcReference()) { + } else if (ValType.builder().fromId(typeId).isObjectRef()) { var b = stack.popRef(); var a = stack.popRef(); if (pred == 0) { @@ -2404,7 +2518,7 @@ private static void LOCAL_GET(MStack stack, Operands operands, StackFrame curren var idx = (int) operands.get(0); var i = currentStackFrame.localIndexOf(idx); var localType = currentStackFrame.localType(idx); - if (localType.isGcReference()) { + if (localType.isObjectRef()) { stack.pushRef(currentStackFrame.localRef(i)); } else if (localType.equals(ValType.V128)) { stack.push(currentStackFrame.local(i)); @@ -2418,7 +2532,7 @@ private static void LOCAL_SET(MStack stack, Operands operands, StackFrame curren var idx = (int) operands.get(0); var i = currentStackFrame.localIndexOf(idx); var localType = currentStackFrame.localType(idx); - if (localType.isGcReference()) { + if (localType.isObjectRef()) { currentStackFrame.setLocalRef(i, stack.popRef()); } else if (localType.equals(ValType.V128)) { currentStackFrame.setLocal(i, stack.pop()); @@ -2433,7 +2547,7 @@ private static void LOCAL_TEE(MStack stack, Operands operands, StackFrame curren var idx = (int) operands.get(0); var i = currentStackFrame.localIndexOf(idx); var localType = currentStackFrame.localType(idx); - if (localType.isGcReference()) { + if (localType.isObjectRef()) { currentStackFrame.setLocalRef(i, stack.peekRef()); } else if (localType.equals(ValType.V128)) { var tmp = stack.pop(); @@ -3296,7 +3410,7 @@ protected static Object[] extractArgsAndRefsForParams( var args = new long[size]; boolean hasRefs = false; for (var p : params) { - if (p.isGcReference()) { + if (p.isObjectRef()) { hasRefs = true; break; } @@ -3310,7 +3424,7 @@ protected static Object[] extractArgsAndRefsForParams( idx -= 2; args[idx + 1] = stack.pop(); args[idx] = stack.pop(); - } else if (p.isGcReference()) { + } else if (p.isObjectRef()) { idx -= 1; refArgs[idx] = stack.popRef(); } else { @@ -3433,7 +3547,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().isGcReference()) { + if (ft.storageType().isObjectRef()) { fieldRefs[i] = stack.popRef(); } else { fields[i] = stack.pop(); @@ -3453,7 +3567,7 @@ private static void STRUCT_NEW_DEFAULT(MStack stack, Instance instance, Operands var ft = st.fieldTypes()[i]; if (ft.storageType().valType() != null && ft.storageType().valType().isReference() - && !ft.storageType().isGcReference()) { + && !ft.storageType().isObjectRef()) { fields[i] = REF_NULL_VALUE; } } @@ -3472,7 +3586,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().isGcReference()) { + if (ft.storageType().isObjectRef()) { stack.pushRef(struct.fieldRef(fieldIdx)); } else { var val = struct.field(fieldIdx); @@ -3492,7 +3606,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().isGcReference(); + boolean isRef = ft.storageType().isObjectRef(); Object refVal = null; long val = 0; if (isRef) { @@ -3520,7 +3634,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().isGcReference(); + && at.fieldType().storageType().isObjectRef(); var len = (int) stack.pop(); if (isRef) { var initRef = stack.popRef(); @@ -3546,7 +3660,7 @@ private static void ARRAY_NEW_DEFAULT(MStack stack, Instance instance, Operands var ft = at.fieldType(); if (ft.storageType().valType() != null && ft.storageType().valType().isReference() - && !ft.storageType().isGcReference()) { + && !ft.storageType().isObjectRef()) { java.util.Arrays.fill(elems, Value.REF_NULL_VALUE); } var arr = new WasmArray(typeIdx, elems); @@ -3559,7 +3673,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().isGcReference(); + && at.fieldType().storageType().isObjectRef(); var elems = new long[len]; if (isRef) { var elemRefs = new Object[len]; @@ -3607,7 +3721,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().isGcReference(); + boolean isRef = at.fieldType().storageType().isObjectRef(); var elems = new long[len]; var elemRefs = new Object[len]; for (int i = 0; i < len; i++) { @@ -3636,7 +3750,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().isGcReference()) { + if (at.fieldType().storageType().isObjectRef()) { stack.pushRef(arr.getRef(idx)); } else { var val = arr.get(idx); @@ -3656,7 +3770,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().isGcReference(); + && at.fieldType().storageType().isObjectRef(); Object refVal = null; long val = 0; if (isRef) { @@ -3697,7 +3811,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().isGcReference(); + && at.fieldType().storageType().isObjectRef(); var len = (int) stack.pop(); Object refVal = null; long val = 0; @@ -3818,7 +3932,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().isGcReference(); + boolean isRef = at.fieldType().storageType().isObjectRef(); for (int i = 0; i < len; i++) { var init = element.initializers().get(srcOffset + i); var result = ConstantEvaluators.computeConstant(instance, init); @@ -3952,34 +4066,15 @@ private static void BR_ON_CAST_FAIL( } private static void ANY_CONVERT_EXTERN(MStack stack) { - // 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 { - var val = stack.pop(); - if (val == REF_NULL_VALUE) { - stack.pushRef(null); - } else { - stack.pushRef(new WasmExternRef(val)); - } - } + // Both externref and anyref are Object on the stack — identity + var ref = stack.popRef(); + stack.pushRef(ref); } private static void EXTERN_CONVERT_ANY(MStack stack) { + // Both anyref and externref are Object on the stack — identity 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 externalized — wrap in WasmExternRef and keep on ref side. - // This allows round-tripping through any.convert_extern. - stack.pushRef(new WasmExternRef(ref)); - } + stack.pushRef(ref); } private static long readFromData(byte[] data, int offset, int size) { diff --git a/runtime/src/main/java/run/endive/runtime/Machine.java b/runtime/src/main/java/run/endive/runtime/Machine.java index 58f7b187c..6d05fc687 100644 --- a/runtime/src/main/java/run/endive/runtime/Machine.java +++ b/runtime/src/main/java/run/endive/runtime/Machine.java @@ -11,6 +11,11 @@ default long[] call(int funcId, long[] args, Object[] refArgs) throws WasmEngine return call(funcId, args); } + default CallResult callWithRefs(int funcId, long[] args, Object[] refArgs) + throws WasmEngineException { + return new CallResult(call(funcId, args, refArgs), null); + } + 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 27dbd933f..c7643607c 100644 --- a/runtime/src/main/java/run/endive/runtime/OpcodeImpl.java +++ b/runtime/src/main/java/run/endive/runtime/OpcodeImpl.java @@ -819,12 +819,12 @@ public static void TABLE_COPY( throw new WasmRuntimeException("out of bounds table access"); } - boolean isGcTable = dest.elementType().isGcReference(); + boolean isObjRefTable = dest.elementType().isObjectRef(); for (int i = size - 1; i >= 0; i--) { if (d <= s) { var inst = src.instance(s); - if (isGcTable) { + if (isObjRefTable) { dest.setObjRef(d, src.objRef(s), inst); } else { dest.setRef(d, src.ref(s), inst); @@ -833,7 +833,7 @@ public static void TABLE_COPY( d++; } else { var inst = src.instance(s + i); - if (isGcTable) { + if (isObjRefTable) { dest.setObjRef(d + i, src.objRef(s + i), inst); } else { dest.setRef(d + i, src.ref(s + i), inst); @@ -868,10 +868,10 @@ public static void TABLE_INIT( } int end = (int) endL; - boolean isGcTable = table.elementType().isGcReference(); + boolean isObjRefTable = table.elementType().isObjectRef(); for (int i = offset; i < end; i++) { var elem = instance.element(elementidx); - if (isGcTable) { + if (isObjRefTable) { var result = ConstantEvaluators.computeConstant( instance, elem.initializers().get(elemidx++)); diff --git a/runtime/src/main/java/run/endive/runtime/TableInstance.java b/runtime/src/main/java/run/endive/runtime/TableInstance.java index 3b88a04dd..5632b0201 100644 --- a/runtime/src/main/java/run/endive/runtime/TableInstance.java +++ b/runtime/src/main/java/run/endive/runtime/TableInstance.java @@ -27,7 +27,7 @@ public TableInstance(Table table, int initialValue) { } private boolean isGcTable() { - return table.elementType().isGcReference(); + return table.elementType().isObjectRef(); } public int size() { @@ -98,7 +98,7 @@ public int requiredRef(int index) { public Object objRef(int index) { if (index < 0 || index >= this.refs.length) { - throw new WasmEngineException("undefined element"); + throw new WasmEngineException("out of bounds table access"); } return (objRefs != null) ? objRefs[index] : null; } 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 fcc9f823d..14df4d838 100644 --- a/runtime/src/main/java/run/endive/runtime/internal/CompilerInterpreterMachine.java +++ b/runtime/src/main/java/run/endive/runtime/internal/CompilerInterpreterMachine.java @@ -76,21 +76,24 @@ protected void CALL(Operands operands) { var extracted = extractArgsAndRefsForParams(stack, type.params(), instance); var args = (long[]) extracted[0]; var refArgs = (Object[]) extracted[1]; - boolean hasGcReturns = type.returns().stream().anyMatch(ValType::isGcReference); + boolean hasObjectRefs = type.returns().stream().anyMatch(ValType::isObjectRef); try { - if (hasGcReturns) { - var gcResults = - instance.getMachine() - .callGc(funcId, mergeArgsForCallGc(args, refArgs, type)); - if (gcResults != null) { - for (int i = 0; i < type.returns().size(); i++) { - var retType = type.returns().get(i); - if (retType.isGcReference()) { - stack.pushRef(gcResults[i]); - } else if (gcResults[i] instanceof Number) { - stack.push(((Number) gcResults[i]).longValue()); - } + if (hasObjectRefs) { + var cr = instance.getMachine().callWithRefs(funcId, args, refArgs); + int slot = 0; + for (int i = 0; i < type.returns().size(); i++) { + var retType = type.returns().get(i); + if (retType.isObjectRef()) { + stack.pushRef(cr.refResult(slot)); + slot++; + } else if (retType.equals(ValType.V128)) { + stack.push(cr.longResult(slot)); + stack.push(cr.longResult(slot + 1)); + slot += 2; + } else { + stack.push(cr.longResult(slot)); + slot++; } } } else { @@ -109,21 +112,6 @@ protected void CALL(Operands operands) { } } - private static Object[] mergeArgsForCallGc(long[] args, Object[] refArgs, FunctionType type) { - var gcArgs = new Object[type.params().size()]; - int slot = 0; - for (int i = 0; i < type.params().size(); i++) { - var param = type.params().get(i); - if (param.isGcReference()) { - gcArgs[i] = (refArgs != null) ? refArgs[slot] : null; - } else { - gcArgs[i] = args[slot]; - } - slot += (param.equals(ValType.V128)) ? 2 : 1; - } - return gcArgs; - } - @Override protected boolean useCurrentInstanceInterpreter( Instance instance, Instance refInstance, int funcId) { diff --git a/runtime/src/test/java/run/endive/runtime/WasmModuleTest.java b/runtime/src/test/java/run/endive/runtime/WasmModuleTest.java index ab4b912ce..a1a93a697 100644 --- a/runtime/src/test/java/run/endive/runtime/WasmModuleTest.java +++ b/runtime/src/test/java/run/endive/runtime/WasmModuleTest.java @@ -792,23 +792,28 @@ public void testExternrefHandling() { .withImportValues(imports) .build(); - var roundTrip = instance.exports().function("process_externref").apply(123L)[0]; - assertEquals(123L, roundTrip); + var roundTripResult = instance.exports().function("process_externref").applyGc(123L); + assertNotNull(roundTripResult); + assertEquals(123L, ((Number) roundTripResult[0]).longValue()); // object has not been created yet - var isNull1 = instance.exports().function("is_null").apply(123L)[0]; - assertEquals(1L, isNull1); + var isNull1Result = instance.exports().function("is_null").applyGc(123L); + assertNotNull(isNull1Result); + assertEquals(1, ((Number) isNull1Result[0]).intValue()); // now we create the test object - var ref = instance.exports().function("get_host_object").apply()[0]; - assertEquals(123L, ref); + var refResult = instance.exports().function("get_host_object").applyGc(); + assertNotNull(refResult); + assertEquals(123L, ((Number) refResult[0]).longValue()); - var isNull2 = instance.exports().function("is_null").apply(123L)[0]; - assertEquals(0L, isNull2); + var isNull2Result = instance.exports().function("is_null").applyGc(123L); + assertNotNull(isNull2Result); + assertEquals(0, ((Number) isNull2Result[0]).intValue()); // verify against a reference that doesn't exist - var isNull3 = instance.exports().function("is_null").apply(1L)[0]; - assertEquals(1L, isNull3); + var isNull3Result = instance.exports().function("is_null").applyGc(1L); + assertNotNull(isNull3Result); + assertEquals(1, ((Number) isNull3Result[0]).intValue()); } @Test 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 d5577a5ba..87d3f1a76 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 @@ -379,14 +379,14 @@ private Optional generateFieldExport( private static boolean needsGcCall(Command cmd) { if (cmd.action() != null && cmd.action().args() != null) { for (var arg : cmd.action().args()) { - if (arg.type().isGcReference()) { + if (arg.type().isObjectRef()) { return true; } } } if (cmd.expected() != null) { for (var expected : cmd.expected()) { - if (expected.type().isGcReference()) { + if (expected.type().isObjectRef()) { return true; } } 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 87a846c6a..4d2faf338 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 @@ -64,4 +64,8 @@ public boolean isGcReference() { return false; } } + + public boolean isObjectRef() { + return isGcReference() || this == EXTERN_REF || this == NULL_EXTERN_REF; + } } 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 3eaede3de..482f69930 100644 --- a/wasm/src/main/java/run/endive/wasm/types/ValType.java +++ b/wasm/src/main/java/run/endive/wasm/types/ValType.java @@ -103,6 +103,11 @@ private ValType( this.resolvedFunctionTypeHash = resolvedFunctionTypeHash; this.id = createId(opcode, typeIdx); + + // Pre-compute gcReference and objectRef for well-known types + // (these don't need a TypeSection) + this.gcReference = computeIsGcReference(null); + this.objectRef = this.gcReference || computeIsExternRef(); } public ValType resolve(TypeSection typeSection) { @@ -783,6 +788,9 @@ private boolean isExternRef() { if (!isReference()) { return false; } + if (opcode == ID.ExternRef || opcode == ID.NoExternRef) { + return true; + } if (opcode == ID.Ref || opcode == ID.RefNull) { return typeIdx == TypeIdxCode.EXTERN.code() || typeIdx == TypeIdxCode.NOEXTERN.code(); From 08ef611c14a1192b2eb50f41cbb06009eb445861 Mon Sep 17 00:00:00 2001 From: andreatp Date: Thu, 11 Jun 2026 16:08:51 +0100 Subject: [PATCH 08/20] refactor: strip externref compat workarounds, clean API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - apply(long...) throws for isObjectRef (GC + externref), not just isGcReference - Delete callGc from Machine and InterpreterMachine - Delete boxReturnValue, all instanceof Number/Long boxing - Delete Long.valueOf externref wrapping - InterpreterMachine: zero boxing in Machine layer - Host functions: applyWithRefs(Instance, long[], Object[]) returns CallResult - WasmModuleTest: externref test uses applyWithRefs with real Objects - Remaining boxing only in Instance.applyGc (caller convenience edge) Breaking: annotation processor externref integration test fails (generated code uses apply(long...) for externref — needs migration to applyWithRefs in the annotation processor module). --- .../endive/compiler/internal/Compiler.java | 30 +--- .../ApprovalTest.functions10.approved.txt | 8 - .../ApprovalTest.verifyBrTable.approved.txt | 8 - .../ApprovalTest.verifyBranching.approved.txt | 8 - .../ApprovalTest.verifyFloat.approved.txt | 8 - .../ApprovalTest.verifyHelloWasi.approved.txt | 8 - .../ApprovalTest.verifyI32.approved.txt | 8 - ...ApprovalTest.verifyI32Renamed.approved.txt | 8 - .../ApprovalTest.verifyIterFact.approved.txt | 8 - ...pprovalTest.verifyKitchenSink.approved.txt | 8 - .../ApprovalTest.verifyMemory.approved.txt | 8 - .../ApprovalTest.verifyStart.approved.txt | 8 - .../ApprovalTest.verifyTailCall.approved.txt | 8 - .../ApprovalTest.verifyTrap.approved.txt | 8 - .../java/run/endive/runtime/Instance.java | 119 ++++++++++++- .../endive/runtime/InterpreterMachine.java | 167 +++++------------- .../main/java/run/endive/runtime/Machine.java | 4 - .../endive/runtime/WasmFunctionHandle.java | 13 +- .../run/endive/runtime/WasmModuleTest.java | 87 +++++---- 19 files changed, 223 insertions(+), 301 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 4aa046ec9..04372f5a2 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/Compiler.java +++ b/compiler/src/main/java/run/endive/compiler/internal/Compiler.java @@ -560,32 +560,6 @@ private byte[] compileClass() { false, asm -> compileMachineCall(internalClassName, asm, 3)); - // Machine.callGc(int, Object[]) implementation - // Delegates to compilerInterpreterMachine.callGc() which handles - // arg splitting, interpreter execution, and result boxing. - // Locals: 0=this, 1=funcId, 2=args - emitFunction( - classWriter, - "callGc", - methodType(Object[].class, int.class, Object[].class), - false, - asm -> { - asm.load(0, OBJECT_TYPE); - asm.getfield( - internalClassName, - "compilerInterpreterMachine", - getDescriptor(CompilerInterpreterMachine.class)); - asm.load(1, INT_TYPE); - asm.load(2, OBJECT_TYPE); - asm.invokevirtual( - AOT_INTERPRETER_MACHINE_TYPE.getInternalName(), - "callGc", - methodType(Object[].class, int.class, Object[].class) - .toMethodDescriptorString(), - false); - asm.areturn(OBJECT_TYPE); - }); - // call_indirect_xxx() bridges for native CALL_INDIRECT // When using bridge classes, these methods are on separate classes if (!useBridgeClasses) { @@ -711,7 +685,7 @@ private void compileConstructor(InstructionAdapter asm, String internalClassName asm.load(1, OBJECT_TYPE); asm.putfield(internalClassName, "instance", getDescriptor(Instance.class)); - // Always create compilerInterpreterMachine for callGc support + // Always create compilerInterpreterMachine for callWithRefs support asm.load(0, OBJECT_TYPE); asm.anew(AOT_INTERPRETER_MACHINE_TYPE); asm.dup(); @@ -1107,7 +1081,7 @@ private void compileCallFunction(int funcId, FunctionType type, InstructionAdapt asm.aconst(null); } else if (returnType == Object.class) { // GC ref return: discard the Object result and return a dummy long[]{0}. - // GC ref results should be accessed via callGc() which goes through the + // GC ref results should be accessed via callWithRefs() which goes through the // interpreter path and handles Object results natively. asm.pop(); asm.iconst(1); 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 5e3679eb1..18cf6826e 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 @@ -54,14 +54,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; ATHROW - public callGc(I[Ljava/lang/Object;)[Ljava/lang/Object; - ALOAD 0 - GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; - ILOAD 1 - ALOAD 2 - INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callGc (I[Ljava/lang/Object;)[Ljava/lang/Object; - ARETURN - public static call_indirect_0(IIILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V ALOAD 4 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 16ba31e7f..e89a3af2f 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 @@ -54,14 +54,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; ATHROW - public callGc(I[Ljava/lang/Object;)[Ljava/lang/Object; - ALOAD 0 - GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; - ILOAD 1 - ALOAD 2 - INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callGc (I[Ljava/lang/Object;)[Ljava/lang/Object; - ARETURN - public static call_indirect_0(IIILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V ALOAD 4 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 c691f0719..4d760db5c 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 @@ -54,14 +54,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; ATHROW - public callGc(I[Ljava/lang/Object;)[Ljava/lang/Object; - ALOAD 0 - GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; - ILOAD 1 - ALOAD 2 - INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callGc (I[Ljava/lang/Object;)[Ljava/lang/Object; - ARETURN - public static call_indirect_0(IIILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V ALOAD 4 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 8dfe4cc0f..140c14234 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 @@ -54,14 +54,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; ATHROW - public callGc(I[Ljava/lang/Object;)[Ljava/lang/Object; - ALOAD 0 - GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; - ILOAD 1 - ALOAD 2 - INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callGc (I[Ljava/lang/Object;)[Ljava/lang/Object; - ARETURN - public static call_indirect_0(IILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)V INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V ALOAD 3 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 bcfee9a51..afbbd070c 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 @@ -54,14 +54,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; ATHROW - public callGc(I[Ljava/lang/Object;)[Ljava/lang/Object; - ALOAD 0 - GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; - ILOAD 1 - ALOAD 2 - INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callGc (I[Ljava/lang/Object;)[Ljava/lang/Object; - ARETURN - public static call_indirect_0(IIIIIILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V ALOAD 7 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 72ed84f39..0e138840c 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 @@ -54,14 +54,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; ATHROW - public callGc(I[Ljava/lang/Object;)[Ljava/lang/Object; - ALOAD 0 - GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; - ILOAD 1 - ALOAD 2 - INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callGc (I[Ljava/lang/Object;)[Ljava/lang/Object; - ARETURN - public static call_indirect_0(IILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)V INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V ALOAD 3 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 700eea225..75ff2444f 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 @@ -54,14 +54,6 @@ public final class FOO implements run/endive/runtime/Machine { INVOKESTATIC FOOShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; ATHROW - public callGc(I[Ljava/lang/Object;)[Ljava/lang/Object; - ALOAD 0 - GETFIELD FOO.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; - ILOAD 1 - ALOAD 2 - INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callGc (I[Ljava/lang/Object;)[Ljava/lang/Object; - ARETURN - public static call_indirect_0(IILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)V INVOKESTATIC FOOShaded.checkInterruption ()V ALOAD 3 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 dde03dcbf..f14b7156a 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 @@ -54,14 +54,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; ATHROW - public callGc(I[Ljava/lang/Object;)[Ljava/lang/Object; - ALOAD 0 - GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; - ILOAD 1 - ALOAD 2 - INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callGc (I[Ljava/lang/Object;)[Ljava/lang/Object; - ARETURN - public static call_indirect_0(IIILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V ALOAD 4 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 cf26b6444..9cb5117fd 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 @@ -54,14 +54,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; ATHROW - public callGc(I[Ljava/lang/Object;)[Ljava/lang/Object; - ALOAD 0 - GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; - ILOAD 1 - ALOAD 2 - INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callGc (I[Ljava/lang/Object;)[Ljava/lang/Object; - ARETURN - public static call_indirect_0(IIILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V ALOAD 4 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 631518402..9f159fdb9 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 @@ -54,14 +54,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; ATHROW - public callGc(I[Ljava/lang/Object;)[Ljava/lang/Object; - ALOAD 0 - GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; - ILOAD 1 - ALOAD 2 - INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callGc (I[Ljava/lang/Object;)[Ljava/lang/Object; - ARETURN - public static call_indirect_0(IIILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V ALOAD 4 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 f05550f3e..bd5ab3ad6 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 @@ -54,14 +54,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; ATHROW - public callGc(I[Ljava/lang/Object;)[Ljava/lang/Object; - ALOAD 0 - GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; - ILOAD 1 - ALOAD 2 - INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callGc (I[Ljava/lang/Object;)[Ljava/lang/Object; - ARETURN - public static call_indirect_0(IIILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)V INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V ALOAD 4 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 1b6be9d72..24b3dec55 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 @@ -92,14 +92,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; ATHROW - public callGc(I[Ljava/lang/Object;)[Ljava/lang/Object; - ALOAD 0 - GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; - ILOAD 1 - ALOAD 2 - INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callGc (I[Ljava/lang/Object;)[Ljava/lang/Object; - ARETURN - public static call_indirect_0(IIIIILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V ALOAD 6 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 e3d24a07f..5bc3ac3cf 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 @@ -54,14 +54,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; ATHROW - public callGc(I[Ljava/lang/Object;)[Ljava/lang/Object; - ALOAD 0 - GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; - ILOAD 1 - ALOAD 2 - INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callGc (I[Ljava/lang/Object;)[Ljava/lang/Object; - ARETURN - public static call_indirect_0(IILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)V INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V ALOAD 3 diff --git a/runtime/src/main/java/run/endive/runtime/Instance.java b/runtime/src/main/java/run/endive/runtime/Instance.java index e17de0d1a..3238da22d 100644 --- a/runtime/src/main/java/run/endive/runtime/Instance.java +++ b/runtime/src/main/java/run/endive/runtime/Instance.java @@ -279,9 +279,20 @@ 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 hasObjectRefParams = funcType.params().stream().anyMatch(ValType::isObjectRef); + boolean hasObjectRefReturns = + funcType.returns().stream().anyMatch(ValType::isObjectRef); return new ExportFunction() { @Override public long[] apply(long... args) { + if (hasObjectRefParams || hasObjectRefReturns) { + throw new UnsupportedOperationException( + "Function '" + + name + + "' uses GC/externref types." + + " Use applyWithRefs() or applyGc()."); + } return instance.machine.call(export.index(), args); } @@ -291,12 +302,116 @@ public CallResult applyWithRefs(long[] args, Object[] refArgs) { } @Override - public Object[] applyGc(Object... args) { - return instance.machine.callGc(export.index(), args); + public Object[] applyGc(Object... gcArgs) { + // Boxing/unboxing lives here in the Exports layer, + // not in Machine or InterpreterMachine. + var longArgs = new long[ValType.sizeOf(funcType.params())]; + Object[] refArgs = null; + int slot = 0; + for (int i = 0; i < funcType.params().size(); i++) { + var param = funcType.params().get(i); + if (param.isObjectRef()) { + if (refArgs == null) { + refArgs = new Object[longArgs.length]; + } + Object gcArg = (gcArgs != null && i < gcArgs.length) ? gcArgs[i] : null; + if (gcArg instanceof WasmExternRef) { + // WasmExternRef: put value in both arrays + longArgs[slot] = ((WasmExternRef) gcArg).value(); + refArgs[slot] = gcArg; + } else if (gcArg instanceof Number) { + // Numeric externref value: put in longArgs for + // backward-compat host functions using apply(long...), + // and wrap as WasmExternRef in refArgs. + long val = ((Number) gcArg).longValue(); + longArgs[slot] = val; + refArgs[slot] = new WasmExternRef(val); + } else { + // GC ref (struct, array, i31, null, etc.) + refArgs[slot] = gcArg; + } + 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] != null) { + longArgs[slot] = ((Number) gcArgs[i]).longValue(); + } else if (gcArgs != null + && i < gcArgs.length + && gcArgs[i] == null + && param.isReference()) { + longArgs[slot] = Value.REF_NULL_VALUE; + } + slot++; + } + } + + var cr = applyWithRefs(longArgs, refArgs); + + if (funcType.returns().isEmpty()) { + return null; + } + + var results = new Object[funcType.returns().size()]; + int rSlot = 0; + for (int r = 0; r < funcType.returns().size(); r++) { + var ret = funcType.returns().get(r); + if (ret.isObjectRef()) { + Object ref = cr.refResult(rSlot); + if (ref == null && cr.refs() == null) { + // Host used apply(long...) default path — + // externref value is in the long result + long val = cr.longResult(rSlot); + results[r] = (val == Value.REF_NULL_VALUE) ? null : val; + } else if (ref instanceof WasmExternRef) { + // Unwrap WasmExternRef so callers get the inner value + var ext = (WasmExternRef) ref; + results[r] = ext.isObjectRef() ? ext.objectValue() : ext.value(); + } else { + results[r] = ref; + } + rSlot++; + } else if (ret.isReference()) { + long val = cr.longResult(rSlot); + results[r] = (val == Value.REF_NULL_VALUE) ? null : val; + rSlot++; + } else if (ret.equals(ValType.V128)) { + long lo = cr.longResult(rSlot); + long hi = cr.longResult(rSlot + 1); + results[r] = new long[] {lo, hi}; + rSlot += 2; + } else { + results[r] = boxReturnValue(ret, cr.longResult(rSlot)); + rSlot++; + } + } + 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; + } + } + public GlobalInstance global(String name) { var export = getExport(GLOBAL, name); return instance.global(export.index()); diff --git a/runtime/src/main/java/run/endive/runtime/InterpreterMachine.java b/runtime/src/main/java/run/endive/runtime/InterpreterMachine.java index 50dd46e4e..392f34ae4 100644 --- a/runtime/src/main/java/run/endive/runtime/InterpreterMachine.java +++ b/runtime/src/main/java/run/endive/runtime/InterpreterMachine.java @@ -84,6 +84,22 @@ protected long[] call( verifyIndirectCall(type, callType, instance.module().typeSection()); } + // When called via call(int, long[]) with no refArgs and the function + // has externref params, populate refArgs from longs so the ref stack + // is set up correctly. Uses WasmExternRef (the proper externref type). + if (refArgs == null && type.params().stream().anyMatch(ValType::isObjectRef)) { + refArgs = new Object[args.length]; + int slot = 0; + for (int pi = 0; pi < type.params().size(); pi++) { + var param = type.params().get(pi); + if (param.isObjectRef()) { + long val = args[slot]; + refArgs[slot] = (val == REF_NULL_VALUE) ? null : new WasmExternRef(val); + } + slot += param.equals(ValType.V128) ? 2 : 1; + } + } + var func = instance.function(funcId); if (func != null) { var stackFrame = @@ -108,24 +124,6 @@ protected long[] call( } } } else { - // For host functions, convert Object refArgs back to longs - // since host function interface only accepts long[] - if (refArgs != null) { - int slot = 0; - for (int pi = 0; pi < type.params().size(); pi++) { - var param = type.params().get(pi); - if (param.isObjectRef() && refArgs[slot] != null) { - if (refArgs[slot] instanceof Number) { - args[slot] = ((Number) refArgs[slot]).longValue(); - } else { - args[slot] = System.identityHashCode(refArgs[slot]); - } - } else if (param.isObjectRef() && refArgs[slot] == null) { - args[slot] = REF_NULL_VALUE; - } - slot += param.equals(ValType.V128) ? 2 : 1; - } - } var stackFrame = new StackFrame(instance, funcId, args); stackFrame.pushCtrl(OpCode.CALL, 0, sizeOf(type.returns()), stack.size()); callStack.push(stackFrame); @@ -133,31 +131,33 @@ protected long[] call( var imprt = instance.imports().function(funcId); try { - var results = imprt.handle().apply(instance, args); - // a host function can return null or an array of ints - // which we will push onto the stack - if (results != null) { + boolean hasObjectRefs = + type.params().stream().anyMatch(ValType::isObjectRef) + || type.returns().stream().anyMatch(ValType::isObjectRef); + if (hasObjectRefs) { + var cr = imprt.handle().applyWithRefs(instance, args, refArgs); int slot = 0; - for (int ri = 0; ri < type.returns().size() && slot < results.length; ri++) { + for (int ri = 0; ri < type.returns().size(); ri++) { var retType = type.returns().get(ri); if (retType.isObjectRef()) { - // Host function returns long for externref — wrap as Object - long val = results[slot]; - if (val == REF_NULL_VALUE) { - stack.pushRef(null); - } else { - stack.pushRef(Long.valueOf(val)); - } + stack.pushRef(cr.refResult(slot)); slot++; } else if (retType.equals(ValType.V128)) { - stack.push(results[slot]); - stack.push(results[slot + 1]); + stack.push(cr.longResult(slot)); + stack.push(cr.longResult(slot + 1)); slot += 2; } else { - stack.push(results[slot]); + stack.push(cr.longResult(slot)); slot++; } } + } else { + var results = imprt.handle().apply(instance, args); + if (results != null) { + for (var result : results) { + stack.push(result); + } + } } } catch (WasmException e) { THROW_REF(instance, instance.registerException(e), stack, stackFrame, callStack); @@ -188,14 +188,14 @@ protected long[] call( var retType = type.returns().get(r); if (retType.isObjectRef()) { slot--; + // Object refs are on the ref stack; callers wanting the actual + // Object should use callWithRefs(). For the long[] path, extract + // the long value for backward compat with externref host functions. var ref = stack.popRef(); - if (ref == null) { - results[slot] = Value.REF_NULL_VALUE; - } else if (ref instanceof Number) { - results[slot] = ((Number) ref).longValue(); - } else { - results[slot] = System.identityHashCode(ref); - } + // Object refs are on the ref stack; callers wanting the actual + // Object should use callWithRefs(). For the long[] path, return 0 + // (non-null indicator) or REF_NULL_VALUE. + results[slot] = (ref == null) ? Value.REF_NULL_VALUE : 0; } else if (retType.equals(ValType.V128)) { slot -= 2; results[slot + 1] = stack.pop(); @@ -246,93 +246,6 @@ public CallResult callWithRefs(int funcId, long[] args, Object[] refArgs) return new CallResult(longResults, refResults); } - @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.isObjectRef()) { - if (refArgs == null) { - refArgs = new Object[longArgs.length]; - } - Object gcArg = (gcArgs != null && i < gcArgs.length) ? gcArgs[i] : null; - // Convert REF_NULL_VALUE sentinel to null for Object refs - if (gcArg instanceof Number - && ((Number) gcArg).longValue() == Value.REF_NULL_VALUE) { - gcArg = null; - } - refArgs[slot] = gcArg; - 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) { - if (gcArgs[i] instanceof Number) { - longArgs[slot] = ((Number) gcArgs[i]).longValue(); - } else if (gcArgs[i] == null && param.isReference()) { - longArgs[slot] = Value.REF_NULL_VALUE; - } - } - slot++; - } - } - - var cr = callWithRefs(funcId, longArgs, refArgs); - - if (type.returns().isEmpty()) { - return null; - } - - var results = new Object[type.returns().size()]; - int resultSlot = 0; - for (int r = 0; r < type.returns().size(); r++) { - var retType = type.returns().get(r); - if (retType.isObjectRef()) { - results[r] = cr.refResult(resultSlot); - resultSlot++; - } else if (retType.isReference()) { - long val = cr.longResult(resultSlot); - results[r] = (val == Value.REF_NULL_VALUE) ? null : val; - resultSlot++; - } else if (retType.equals(ValType.V128)) { - long lo = cr.longResult(resultSlot); - long hi = cr.longResult(resultSlot + 1); - results[r] = new long[] {lo, hi}; - resultSlot += 2; - } else { - results[r] = boxReturnValue(retType, cr.longResult(resultSlot)); - resultSlot++; - } - } - 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; } diff --git a/runtime/src/main/java/run/endive/runtime/Machine.java b/runtime/src/main/java/run/endive/runtime/Machine.java index 6d05fc687..894852231 100644 --- a/runtime/src/main/java/run/endive/runtime/Machine.java +++ b/runtime/src/main/java/run/endive/runtime/Machine.java @@ -15,8 +15,4 @@ default CallResult callWithRefs(int funcId, long[] args, Object[] refArgs) throws WasmEngineException { return new CallResult(call(funcId, args, refArgs), null); } - - 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/WasmFunctionHandle.java b/runtime/src/main/java/run/endive/runtime/WasmFunctionHandle.java index d7c87fcd0..e645e11a7 100644 --- a/runtime/src/main/java/run/endive/runtime/WasmFunctionHandle.java +++ b/runtime/src/main/java/run/endive/runtime/WasmFunctionHandle.java @@ -7,8 +7,15 @@ 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"); + /** + * Call this host function with separate long and Object ref arguments. + * Override this method for host functions that need to receive/return + * externref or GC reference values as Objects. + * + *

The default implementation delegates to {@link #apply(Instance, long...)} + * which discards Object refs but preserves backward compatibility. + */ + default CallResult applyWithRefs(Instance instance, long[] args, Object[] refArgs) { + return new CallResult(apply(instance, args), null); } } diff --git a/runtime/src/test/java/run/endive/runtime/WasmModuleTest.java b/runtime/src/test/java/run/endive/runtime/WasmModuleTest.java index a1a93a697..d229e7d09 100644 --- a/runtime/src/test/java/run/endive/runtime/WasmModuleTest.java +++ b/runtime/src/test/java/run/endive/runtime/WasmModuleTest.java @@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static run.endive.wasm.types.Value.REF_NULL_VALUE; @@ -761,7 +762,33 @@ public void shouldSupportTableFactoryOverride() { @Test public void testExternrefHandling() { var testObject = new Object(); - var sideTable = new HashMap(); + + WasmFunctionHandle getHostObject = + new WasmFunctionHandle() { + @Override + public long[] apply(Instance inst, long... args) { + throw new UnsupportedOperationException("Use applyWithRefs"); + } + + @Override + public CallResult applyWithRefs(Instance inst, long[] args, Object[] refArgs) { + return new CallResult(null, new Object[] {testObject}); + } + }; + + WasmFunctionHandle isNull = + new WasmFunctionHandle() { + @Override + public long[] apply(Instance inst, long... args) { + throw new UnsupportedOperationException("Use applyWithRefs"); + } + + @Override + public CallResult applyWithRefs(Instance inst, long[] args, Object[] refArgs) { + Object ref = (refArgs != null && refArgs.length > 0) ? refArgs[0] : null; + return new CallResult(new long[] {ref == null ? 1 : 0}, null); + } + }; var imports = ImportValues.builder() @@ -770,50 +797,44 @@ public void testExternrefHandling() { "env", "get_host_object", FunctionType.of(List.of(), List.of(ValType.ExternRef)), - (inst, args) -> { - sideTable.put(123L, testObject); - return new long[] {123L}; - })) + getHostObject)) .addFunction( new HostFunction( "env", "is_null", FunctionType.of( List.of(ValType.ExternRef), List.of(ValType.I32)), - (inst, args) -> { - long key = args[0]; - return (sideTable.get(key) == null) - ? new long[] {1} - : new long[] {0}; - })) + isNull)) .build(); var instance = Instance.builder(loadModule("compiled/externref-example.wat.wasm")) .withImportValues(imports) .build(); - var roundTripResult = instance.exports().function("process_externref").applyGc(123L); - assertNotNull(roundTripResult); - assertEquals(123L, ((Number) roundTripResult[0]).longValue()); - - // object has not been created yet - var isNull1Result = instance.exports().function("is_null").applyGc(123L); - assertNotNull(isNull1Result); - assertEquals(1, ((Number) isNull1Result[0]).intValue()); - - // now we create the test object - var refResult = instance.exports().function("get_host_object").applyGc(); - assertNotNull(refResult); - assertEquals(123L, ((Number) refResult[0]).longValue()); - - var isNull2Result = instance.exports().function("is_null").applyGc(123L); - assertNotNull(isNull2Result); - assertEquals(0, ((Number) isNull2Result[0]).intValue()); - - // verify against a reference that doesn't exist - var isNull3Result = instance.exports().function("is_null").applyGc(1L); - assertNotNull(isNull3Result); - assertEquals(1, ((Number) isNull3Result[0]).intValue()); + // Round-trip: pass an externref Object, get it back + var roundTrip = + instance.exports() + .function("process_externref") + .applyWithRefs(new long[0], new Object[] {testObject}); + assertSame(testObject, roundTrip.refResult(0)); + + // is_null with a non-null ref + var isNull1 = + instance.exports() + .function("is_null") + .applyWithRefs(new long[0], new Object[] {testObject}); + assertEquals(0, isNull1.longResult(0)); + + // is_null with null + var isNull2 = + instance.exports() + .function("is_null") + .applyWithRefs(new long[0], new Object[] {null}); + assertEquals(1, isNull2.longResult(0)); + + // get_host_object returns the testObject + var ref = instance.exports().function("get_host_object").applyWithRefs(new long[0], null); + assertSame(testObject, ref.refResult(0)); } @Test From 2f1432342be4d693826cc92e51bbd62a49c86a27 Mon Sep 17 00:00:00 2001 From: andreatp Date: Fri, 12 Jun 2026 16:51:47 +0100 Subject: [PATCH 09/20] =?UTF-8?q?refactor:=20delete=20applyGc=20=E2=80=94?= =?UTF-8?q?=20applyWithRefs=20is=20the=20only=20ref=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Delete applyGc from ExportFunction, Instance, WasmFunctionHandle. Zero boxing anywhere in the runtime. API (final): - apply(long...) — numeric + funcref only, throws for GC/externref - applyWithRefs(long[], Object[]) → CallResult — everything, zero boxing ArgsAdapter extended with addRef() + applyWithRefs() for clean test code generation. Test generator migrated from applyGc to ArgsAdapter.builder().add(n).addRef(obj).applyWithRefs(func). Remaining: annotation processor needs migration to applyWithRefs for externref functions (annotations-it test). --- .../java/run/endive/testing/ArgsAdapter.java | 52 ++++-- .../endive/compiler/internal/Compiler.java | 31 ++++ .../ApprovalTest.functions10.approved.txt | 9 + .../ApprovalTest.verifyBrTable.approved.txt | 9 + .../ApprovalTest.verifyBranching.approved.txt | 9 + .../ApprovalTest.verifyFloat.approved.txt | 9 + .../ApprovalTest.verifyHelloWasi.approved.txt | 9 + .../ApprovalTest.verifyI32.approved.txt | 9 + ...ApprovalTest.verifyI32Renamed.approved.txt | 9 + .../ApprovalTest.verifyIterFact.approved.txt | 9 + ...pprovalTest.verifyKitchenSink.approved.txt | 9 + .../ApprovalTest.verifyMemory.approved.txt | 9 + .../ApprovalTest.verifyStart.approved.txt | 9 + .../ApprovalTest.verifyTailCall.approved.txt | 9 + .../ApprovalTest.verifyTrap.approved.txt | 9 + .../java/run/endive/testing/BrOnNullTest.java | 7 +- .../run/endive/testing/GcEdgeCasesTest.java | 18 +- .../java/run/endive/testing/ArgsAdapter.java | 52 ++++-- .../run/endive/runtime/ExportFunction.java | 4 - .../java/run/endive/runtime/Instance.java | 125 +------------ .../java/run/endive/testgen/JavaTestGen.java | 112 ++++++++---- .../run/endive/testgen/wast/WasmValue.java | 173 +++++++----------- .../endive/testgen/wast/WasmValueType.java | 8 +- 23 files changed, 390 insertions(+), 309 deletions(-) diff --git a/compiler-tests/src/test/java/run/endive/testing/ArgsAdapter.java b/compiler-tests/src/test/java/run/endive/testing/ArgsAdapter.java index dae93f93d..b46820123 100644 --- a/compiler-tests/src/test/java/run/endive/testing/ArgsAdapter.java +++ b/compiler-tests/src/test/java/run/endive/testing/ArgsAdapter.java @@ -1,36 +1,58 @@ package run.endive.testing; -import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.List; +import run.endive.runtime.CallResult; +import run.endive.runtime.ExportFunction; public final class ArgsAdapter { - private final ArrayDeque stack; + private final List longs = new ArrayList<>(); + private final List refs = new ArrayList<>(); + private boolean hasRefs; - private ArgsAdapter() { - stack = new ArrayDeque<>(); - } + private ArgsAdapter() {} public static ArgsAdapter builder() { return new ArgsAdapter(); } - public long[] build() { - var result = new long[stack.size()]; - int i = stack.size() - 1; - while (!stack.isEmpty()) { - result[i--] = stack.pop(); - } - return result; + public ArgsAdapter add(long arg) { + longs.add(arg); + refs.add(null); + return this; } public ArgsAdapter add(long[] args) { for (var arg : args) { - stack.push(arg); + longs.add(arg); + refs.add(null); } return this; } - public ArgsAdapter add(long arg) { - stack.push(arg); + public ArgsAdapter addRef(Object ref) { + longs.add(0L); + refs.add(ref); + hasRefs = true; return this; } + + public long[] build() { + var result = new long[longs.size()]; + for (int i = 0; i < longs.size(); i++) { + result[i] = longs.get(i); + } + return result; + } + + public Object[] buildRefs() { + if (!hasRefs) { + return null; + } + return refs.toArray(); + } + + public CallResult applyWithRefs(ExportFunction func) { + return func.applyWithRefs(build(), buildRefs()); + } } 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 04372f5a2..a64958680 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/Compiler.java +++ b/compiler/src/main/java/run/endive/compiler/internal/Compiler.java @@ -72,6 +72,7 @@ import org.objectweb.asm.Type; import org.objectweb.asm.commons.InstructionAdapter; import run.endive.compiler.InterpreterFallback; +import run.endive.runtime.CallResult; import run.endive.runtime.Instance; import run.endive.runtime.Machine; import run.endive.runtime.Memory; @@ -560,6 +561,36 @@ private byte[] compileClass() { false, asm -> compileMachineCall(internalClassName, asm, 3)); + // Machine.callWithRefs(int, long[], Object[]) implementation + // Delegates to compilerInterpreterMachine.callWithRefs() so that + // Object ref results (GC refs and externref) are properly returned + // in the CallResult refs array. + emitFunction( + classWriter, + "callWithRefs", + methodType(CallResult.class, int.class, long[].class, Object[].class), + false, + asm -> { + asm.load(0, OBJECT_TYPE); + asm.getfield( + internalClassName, + "compilerInterpreterMachine", + getDescriptor(CompilerInterpreterMachine.class)); + asm.load(1, INT_TYPE); + asm.load(2, OBJECT_TYPE); + asm.load(3, OBJECT_TYPE); + asm.invokevirtual( + AOT_INTERPRETER_MACHINE_TYPE.getInternalName(), + "callWithRefs", + Type.getMethodDescriptor( + Type.getType(CallResult.class), + INT_TYPE, + LONG_ARRAY_TYPE, + Type.getType(Object[].class)), + false); + asm.areturn(OBJECT_TYPE); + }); + // call_indirect_xxx() bridges for native CALL_INDIRECT // When using bridge classes, these methods are on separate classes if (!useBridgeClasses) { 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 18cf6826e..bec43e992 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 @@ -54,6 +54,15 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; ATHROW + public callWithRefs(I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; + ALOAD 0 + GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; + ILOAD 1 + ALOAD 2 + ALOAD 3 + INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callWithRefs (I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; + ARETURN + public static call_indirect_0(IIILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V ALOAD 4 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 e89a3af2f..d20a6492e 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 @@ -54,6 +54,15 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; ATHROW + public callWithRefs(I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; + ALOAD 0 + GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; + ILOAD 1 + ALOAD 2 + ALOAD 3 + INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callWithRefs (I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; + ARETURN + public static call_indirect_0(IIILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V ALOAD 4 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 4d760db5c..4ee64e173 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 @@ -54,6 +54,15 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; ATHROW + public callWithRefs(I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; + ALOAD 0 + GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; + ILOAD 1 + ALOAD 2 + ALOAD 3 + INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callWithRefs (I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; + ARETURN + public static call_indirect_0(IIILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V ALOAD 4 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 140c14234..66df05d3f 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 @@ -54,6 +54,15 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; ATHROW + public callWithRefs(I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; + ALOAD 0 + GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; + ILOAD 1 + ALOAD 2 + ALOAD 3 + INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callWithRefs (I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; + ARETURN + public static call_indirect_0(IILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)V INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V ALOAD 3 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 afbbd070c..6cfc51666 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 @@ -54,6 +54,15 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; ATHROW + public callWithRefs(I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; + ALOAD 0 + GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; + ILOAD 1 + ALOAD 2 + ALOAD 3 + INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callWithRefs (I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; + ARETURN + public static call_indirect_0(IIIIIILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V ALOAD 7 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 0e138840c..05a623d60 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 @@ -54,6 +54,15 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; ATHROW + public callWithRefs(I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; + ALOAD 0 + GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; + ILOAD 1 + ALOAD 2 + ALOAD 3 + INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callWithRefs (I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; + ARETURN + public static call_indirect_0(IILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)V INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V ALOAD 3 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 75ff2444f..3c3c4ffa1 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 @@ -54,6 +54,15 @@ public final class FOO implements run/endive/runtime/Machine { INVOKESTATIC FOOShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; ATHROW + public callWithRefs(I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; + ALOAD 0 + GETFIELD FOO.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; + ILOAD 1 + ALOAD 2 + ALOAD 3 + INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callWithRefs (I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; + ARETURN + public static call_indirect_0(IILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)V INVOKESTATIC FOOShaded.checkInterruption ()V ALOAD 3 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 f14b7156a..a44ae7b7e 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 @@ -54,6 +54,15 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; ATHROW + public callWithRefs(I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; + ALOAD 0 + GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; + ILOAD 1 + ALOAD 2 + ALOAD 3 + INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callWithRefs (I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; + ARETURN + public static call_indirect_0(IIILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V ALOAD 4 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 9cb5117fd..78871f0e4 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 @@ -54,6 +54,15 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; ATHROW + public callWithRefs(I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; + ALOAD 0 + GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; + ILOAD 1 + ALOAD 2 + ALOAD 3 + INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callWithRefs (I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; + ARETURN + public static call_indirect_0(IIILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V ALOAD 4 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 9f159fdb9..d14559ae1 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 @@ -54,6 +54,15 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; ATHROW + public callWithRefs(I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; + ALOAD 0 + GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; + ILOAD 1 + ALOAD 2 + ALOAD 3 + INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callWithRefs (I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; + ARETURN + public static call_indirect_0(IIILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V ALOAD 4 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 bd5ab3ad6..39e895e0d 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 @@ -54,6 +54,15 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; ATHROW + public callWithRefs(I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; + ALOAD 0 + GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; + ILOAD 1 + ALOAD 2 + ALOAD 3 + INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callWithRefs (I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; + ARETURN + public static call_indirect_0(IIILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)V INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V ALOAD 4 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 24b3dec55..44fab89e6 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 @@ -92,6 +92,15 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; ATHROW + public callWithRefs(I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; + ALOAD 0 + GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; + ILOAD 1 + ALOAD 2 + ALOAD 3 + INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callWithRefs (I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; + ARETURN + public static call_indirect_0(IIIIILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V ALOAD 6 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 5bc3ac3cf..c15296b46 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 @@ -54,6 +54,15 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime INVOKESTATIC run/endive/$gen/CompiledMachineShaded.throwCallStackExhausted (Ljava/lang/StackOverflowError;)Ljava/lang/RuntimeException; ATHROW + public callWithRefs(I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; + ALOAD 0 + GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; + ILOAD 1 + ALOAD 2 + ALOAD 3 + INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callWithRefs (I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; + ARETURN + public static call_indirect_0(IILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)V INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V ALOAD 3 diff --git a/machine-tests/src/test/java/run/endive/testing/BrOnNullTest.java b/machine-tests/src/test/java/run/endive/testing/BrOnNullTest.java index c62b1039d..a57ef649f 100644 --- a/machine-tests/src/test/java/run/endive/testing/BrOnNullTest.java +++ b/machine-tests/src/test/java/run/endive/testing/BrOnNullTest.java @@ -39,11 +39,14 @@ public void brOnNullRefinesType(Function mac var instance = machineInject.apply(Instance.builder(module)).build(); // Create a point(42, 7) and verify get_x_or_default returns 42 + // new_point takes (i32, i32) -> (ref $Point) var newPoint = instance.export("new_point"); - Object[] pointRef = newPoint.applyGc((Object) 42L, (Object) 7L); + var pointResult = newPoint.applyWithRefs(new long[] {42, 7}, null); + // get_x_or_default takes (ref null $Point) -> i32 var getX = instance.export("get_x_or_default"); - assertEquals(42, (int) (Integer) getX.applyGc(pointRef[0])[0]); + var xResult = getX.applyWithRefs(new long[] {0}, new Object[] {pointResult.refResult(0)}); + assertEquals(42, (int) xResult.longResult(0)); } @ParameterizedTest diff --git a/machine-tests/src/test/java/run/endive/testing/GcEdgeCasesTest.java b/machine-tests/src/test/java/run/endive/testing/GcEdgeCasesTest.java index 5c468283c..453778675 100644 --- a/machine-tests/src/test/java/run/endive/testing/GcEdgeCasesTest.java +++ b/machine-tests/src/test/java/run/endive/testing/GcEdgeCasesTest.java @@ -43,9 +43,10 @@ public void structNewDefaultFuncrefIsNull( @MethodSource("machineImplementations") public void externRoundTrip(Function machineInject) { var instance = machineInject.apply(Instance.builder(MODULE)).build(); - var result = instance.export("extern_roundtrip").applyGc(42L); - assertNotNull(result); - assertEquals(42, ((Number) result[0]).intValue()); + // extern_roundtrip takes no args: it creates a struct internally, + // converts to extern and back, then returns the x field (42). + var result = instance.export("extern_roundtrip").apply(); + assertEquals(42, (int) result[0]); } @ParameterizedTest @@ -55,11 +56,12 @@ public void makeAndGetPoint(Function machine var makePoint = instance.export("make_point"); var getX = instance.export("get_x"); - var point = makePoint.applyGc((Object) 99L); - assertNotNull(point); - assertNotNull(point[0]); + // make_point(i32) -> (ref $Point) + var pointResult = makePoint.applyWithRefs(new long[] {99}, null); + assertNotNull(pointResult.refResult(0)); - var x = getX.applyGc(point[0]); - assertEquals(99, (int) (Integer) x[0]); + // get_x((ref $Point)) -> i32 + var xResult = getX.applyWithRefs(new long[] {0}, new Object[] {pointResult.refResult(0)}); + assertEquals(99, (int) xResult.longResult(0)); } } diff --git a/runtime-tests/src/test/java/run/endive/testing/ArgsAdapter.java b/runtime-tests/src/test/java/run/endive/testing/ArgsAdapter.java index dae93f93d..b46820123 100644 --- a/runtime-tests/src/test/java/run/endive/testing/ArgsAdapter.java +++ b/runtime-tests/src/test/java/run/endive/testing/ArgsAdapter.java @@ -1,36 +1,58 @@ package run.endive.testing; -import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.List; +import run.endive.runtime.CallResult; +import run.endive.runtime.ExportFunction; public final class ArgsAdapter { - private final ArrayDeque stack; + private final List longs = new ArrayList<>(); + private final List refs = new ArrayList<>(); + private boolean hasRefs; - private ArgsAdapter() { - stack = new ArrayDeque<>(); - } + private ArgsAdapter() {} public static ArgsAdapter builder() { return new ArgsAdapter(); } - public long[] build() { - var result = new long[stack.size()]; - int i = stack.size() - 1; - while (!stack.isEmpty()) { - result[i--] = stack.pop(); - } - return result; + public ArgsAdapter add(long arg) { + longs.add(arg); + refs.add(null); + return this; } public ArgsAdapter add(long[] args) { for (var arg : args) { - stack.push(arg); + longs.add(arg); + refs.add(null); } return this; } - public ArgsAdapter add(long arg) { - stack.push(arg); + public ArgsAdapter addRef(Object ref) { + longs.add(0L); + refs.add(ref); + hasRefs = true; return this; } + + public long[] build() { + var result = new long[longs.size()]; + for (int i = 0; i < longs.size(); i++) { + result[i] = longs.get(i); + } + return result; + } + + public Object[] buildRefs() { + if (!hasRefs) { + return null; + } + return refs.toArray(); + } + + public CallResult applyWithRefs(ExportFunction func) { + return func.applyWithRefs(build(), buildRefs()); + } } diff --git a/runtime/src/main/java/run/endive/runtime/ExportFunction.java b/runtime/src/main/java/run/endive/runtime/ExportFunction.java index 7c7560157..50f6f288d 100644 --- a/runtime/src/main/java/run/endive/runtime/ExportFunction.java +++ b/runtime/src/main/java/run/endive/runtime/ExportFunction.java @@ -12,8 +12,4 @@ public interface ExportFunction { default CallResult applyWithRefs(long[] args, Object[] refArgs) throws WasmEngineException { throw new UnsupportedOperationException("This function does not support applyWithRefs"); } - - 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 3238da22d..f931d8c39 100644 --- a/runtime/src/main/java/run/endive/runtime/Instance.java +++ b/runtime/src/main/java/run/endive/runtime/Instance.java @@ -280,18 +280,17 @@ 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 hasObjectRefParams = funcType.params().stream().anyMatch(ValType::isObjectRef); - boolean hasObjectRefReturns = - funcType.returns().stream().anyMatch(ValType::isObjectRef); + boolean hasGcRefParams = funcType.params().stream().anyMatch(ValType::isGcReference); + boolean hasGcRefReturns = funcType.returns().stream().anyMatch(ValType::isGcReference); return new ExportFunction() { @Override public long[] apply(long... args) { - if (hasObjectRefParams || hasObjectRefReturns) { + if (hasGcRefParams || hasGcRefReturns) { throw new UnsupportedOperationException( "Function '" + name - + "' uses GC/externref types." - + " Use applyWithRefs() or applyGc()."); + + "' uses GC reference types." + + " Use applyWithRefs()."); } return instance.machine.call(export.index(), args); } @@ -300,118 +299,9 @@ public long[] apply(long... args) { public CallResult applyWithRefs(long[] args, Object[] refArgs) { return instance.machine.callWithRefs(export.index(), args, refArgs); } - - @Override - public Object[] applyGc(Object... gcArgs) { - // Boxing/unboxing lives here in the Exports layer, - // not in Machine or InterpreterMachine. - var longArgs = new long[ValType.sizeOf(funcType.params())]; - Object[] refArgs = null; - int slot = 0; - for (int i = 0; i < funcType.params().size(); i++) { - var param = funcType.params().get(i); - if (param.isObjectRef()) { - if (refArgs == null) { - refArgs = new Object[longArgs.length]; - } - Object gcArg = (gcArgs != null && i < gcArgs.length) ? gcArgs[i] : null; - if (gcArg instanceof WasmExternRef) { - // WasmExternRef: put value in both arrays - longArgs[slot] = ((WasmExternRef) gcArg).value(); - refArgs[slot] = gcArg; - } else if (gcArg instanceof Number) { - // Numeric externref value: put in longArgs for - // backward-compat host functions using apply(long...), - // and wrap as WasmExternRef in refArgs. - long val = ((Number) gcArg).longValue(); - longArgs[slot] = val; - refArgs[slot] = new WasmExternRef(val); - } else { - // GC ref (struct, array, i31, null, etc.) - refArgs[slot] = gcArg; - } - 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] != null) { - longArgs[slot] = ((Number) gcArgs[i]).longValue(); - } else if (gcArgs != null - && i < gcArgs.length - && gcArgs[i] == null - && param.isReference()) { - longArgs[slot] = Value.REF_NULL_VALUE; - } - slot++; - } - } - - var cr = applyWithRefs(longArgs, refArgs); - - if (funcType.returns().isEmpty()) { - return null; - } - - var results = new Object[funcType.returns().size()]; - int rSlot = 0; - for (int r = 0; r < funcType.returns().size(); r++) { - var ret = funcType.returns().get(r); - if (ret.isObjectRef()) { - Object ref = cr.refResult(rSlot); - if (ref == null && cr.refs() == null) { - // Host used apply(long...) default path — - // externref value is in the long result - long val = cr.longResult(rSlot); - results[r] = (val == Value.REF_NULL_VALUE) ? null : val; - } else if (ref instanceof WasmExternRef) { - // Unwrap WasmExternRef so callers get the inner value - var ext = (WasmExternRef) ref; - results[r] = ext.isObjectRef() ? ext.objectValue() : ext.value(); - } else { - results[r] = ref; - } - rSlot++; - } else if (ret.isReference()) { - long val = cr.longResult(rSlot); - results[r] = (val == Value.REF_NULL_VALUE) ? null : val; - rSlot++; - } else if (ret.equals(ValType.V128)) { - long lo = cr.longResult(rSlot); - long hi = cr.longResult(rSlot + 1); - results[r] = new long[] {lo, hi}; - rSlot += 2; - } else { - results[r] = boxReturnValue(ret, cr.longResult(rSlot)); - rSlot++; - } - } - 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; - } - } - public GlobalInstance global(String name) { var export = getExport(GLOBAL, name); return instance.global(export.index()); @@ -567,7 +457,8 @@ public WasmException exn(int idx) { @Deprecated public long[] array(int idx) { throw new UnsupportedOperationException( - "GcRefStore has been removed. Use applyGc() to get WasmArray objects directly."); + "GcRefStore has been removed. Use applyWithRefs() to get WasmArray objects" + + " directly."); } @Deprecated @@ -579,7 +470,7 @@ public int registerGcRef(WasmGcRef ref) { @Deprecated public WasmGcRef gcRef(int idx) { throw new UnsupportedOperationException( - "GcRefStore has been removed. Use applyGc() to get GC references directly."); + "GcRefStore has been removed. Use applyWithRefs() to get GC references directly."); } public boolean heapTypeMatch( 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 87d3f1a76..4d4b11366 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 @@ -88,6 +88,7 @@ public CompilationUnit generate(String name, Wast wast, String wasmClasspath) { cu.addImport("run.endive.wasm.WasmEngineException"); cu.addImport("run.endive.runtime.WasmException"); cu.addImport("run.endive.runtime.ExportFunction"); + cu.addImport("run.endive.runtime.CallResult"); cu.addImport("run.endive.runtime.Instance"); // base imports @@ -373,10 +374,10 @@ 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...). + * Check if a command involves Object reference types in its args or expected values. + * If so, we must use applyWithRefs(long[], Object[]) instead of apply(long...). */ - private static boolean needsGcCall(Command cmd) { + private static boolean needsRefCall(Command cmd) { if (cmd.action() != null && cmd.action().args() != null) { for (var arg : cmd.action().args()) { if (arg.type().isObjectRef()) { @@ -400,20 +401,25 @@ private List generateAssert(String varName, Command cmd, String modu || cmd.type() == CommandType.ASSERT_EXCEPTION || cmd.type() == CommandType.ASSERT_EXHAUSTION); - boolean useGc = needsGcCall(cmd); + boolean useRef = needsRefCall(cmd); String invocationMethod; + String refCallExpr = null; 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 + ")"; + if (useRef) { + // Build ArgsAdapter chain for ref-using functions + var argsBuilder = new StringBuilder("ArgsAdapter.builder()"); + if (cmd.action().args() != null) { + for (var arg : cmd.action().args()) { + if (arg.type().isObjectRef()) { + argsBuilder.append(".addRef(").append(arg.toRefArgsValue()).append(")"); + } else { + argsBuilder.append(".add(").append(arg.toArgsValue()).append(")"); + } + } + } + refCallExpr = argsBuilder + ".applyWithRefs(" + varName + ")"; + invocationMethod = null; // not used for ref path } else { var args = (cmd.action().args() != null) @@ -434,14 +440,32 @@ private List generateAssert(String varName, Command cmd, String modu if (cmd.type() == CommandType.ASSERT_TRAP || cmd.type() == CommandType.ASSERT_EXHAUSTION || cmd.type() == CommandType.ASSERT_EXCEPTION) { + String trapExpr; + if (cmd.action().type() == ActionType.INVOKE) { + // Always use applyWithRefs for trap tests to avoid + // UnsupportedOperationException from apply() on GC ref functions + if (refCallExpr != null) { + trapExpr = refCallExpr; + } else { + // Build ArgsAdapter chain even for non-ref args + var argsBuilder = new StringBuilder("ArgsAdapter.builder()"); + if (cmd.action().args() != null) { + for (var arg : cmd.action().args()) { + argsBuilder.append(".add(").append(arg.toArgsValue()).append(")"); + } + } + trapExpr = argsBuilder + ".applyWithRefs(" + varName + ")"; + } + } else { + trapExpr = varName + invocationMethod; + } var assertDecl = new NameExpr( "var exception =" + " assertThrows(" + getExceptionType(cmd.type()) + ".class, () -> " - + varName - + invocationMethod + + trapExpr + ")"); if (cmd.text() != null) { return List.of(assertDecl, exceptionMessageMatch(cmd.text())); @@ -452,17 +476,22 @@ private List generateAssert(String varName, Command cmd, String modu assert (cmd.expected() != null); List exprs = new ArrayList<>(); - var resVarName = (cmd.action().type() == ActionType.INVOKE) ? "results" : "result"; - exprs.add(new NameExpr("var " + resVarName + " = " + varName + invocationMethod)); - for (int i = 0; i < cmd.expected().length; i++) { - var expected = cmd.expected()[i]; + if (useRef && cmd.action().type() == ActionType.INVOKE) { + // Ref path: result is CallResult + exprs.add(new NameExpr("var cr = " + refCallExpr)); - 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 { + for (int i = 0; i < cmd.expected().length; i++) { + var expected = cmd.expected()[i]; + var resultVar = expected.toRefResultValue("cr", i); + exprs.add(expected.toRefAssertion(resultVar, moduleName)); + } + } else { + var resVarName = (cmd.action().type() == ActionType.INVOKE) ? "results" : "result"; + exprs.add(new NameExpr("var " + resVarName + " = " + varName + invocationMethod)); + + 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 + "]") @@ -480,7 +509,8 @@ private List generateAssert(String varName, Command cmd, String modu case I16: exprs.add( new NameExpr( - "assertArrayEquals(expected, vecTo16(results))")); + "assertArrayEquals(expected," + + " vecTo16(results))")); break; case I32: exprs.add( @@ -510,32 +540,34 @@ private List generateAssert(String varName, Command cmd, String modu private List generateInvoke(String varName, Command cmd) { assert cmd.type() == CommandType.ACTION; - String invocationMethod; + String invokeExpr; if (cmd.action().type() == ActionType.INVOKE) { - boolean useGc = needsGcCall(cmd); - if (useGc) { - var gcArgs = - Arrays.stream(cmd.action().args()) - .map(WasmValue::toGcArgsValue) - .collect(Collectors.joining(", ")); - invocationMethod = ".applyGc(" + gcArgs + ")"; + boolean useRef = needsRefCall(cmd); + if (useRef) { + var argsBuilder = new StringBuilder("ArgsAdapter.builder()"); + if (cmd.action().args() != null) { + for (var arg : cmd.action().args()) { + if (arg.type().isObjectRef()) { + argsBuilder.append(".addRef(").append(arg.toRefArgsValue()).append(")"); + } else { + argsBuilder.append(".add(").append(arg.toArgsValue()).append(")"); + } + } + } + invokeExpr = argsBuilder + ".applyWithRefs(" + varName + ")"; } else { var args = Arrays.stream(cmd.action().args()) .map(WasmValue::toArgsValue) .collect(Collectors.joining(", ")); - invocationMethod = ".apply(" + args + ")"; + invokeExpr = varName + ".apply(" + args + ")"; } } else { throw new IllegalArgumentException("Unhandled action type " + cmd.action().type()); } var assertDecl = - new NameExpr( - "var exception = assertDoesNotThrow(() -> " - + varName - + invocationMethod - + ")"); + new NameExpr("var exception = assertDoesNotThrow(() -> " + invokeExpr + ")"); return List.of(assertDecl); } 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 49a08fc7b..59f61598e 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 @@ -289,111 +289,12 @@ public String intLaneValue(String v) { } /** - * 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. + * Generate assertion for CallResult from applyWithRefs. * - * 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). + * Object ref types use cr.refResult(i) and compare with Java null/not-null. + * Numeric types use cr.longResult(i) and compare with expected values. */ - public NameExpr toGcAssertion(String resultVar, String moduleName) { + public NameExpr toRefAssertion(String resultVar, String moduleName) { if (value == null) { // Type-only assertion (no specific value) switch (type) { @@ -554,4 +455,70 @@ public String toArgsValue() { throw new IllegalArgumentException("Type not recognized " + type); } } + + /** + * Generate the Object expression for use with ArgsAdapter.addRef(). + * Only called for types where isObjectRef() is true. + * + *

In the wast spec test format, non-null values for anyref/externref/etc. + * represent host objects (ref.host N or ref.extern N) which are encoded + * as WasmExternRef on the JVM side. + */ + public String toRefArgsValue() { + if (value[0].equals("null")) { + return "null"; + } + switch (type) { + case EXTERN_REF: + case NULL_EXTERN_REF: + case ANY_REF: + case EQ_REF: + case STRUCT_REF: + case ARRAY_REF: + case I31_REF: + case NULL_REF: + case REF_NULL: + // Non-null host references in wast tests are encoded as + // WasmExternRef wrapping the numeric value + return "new run.endive.runtime.WasmExternRef(Long.parseLong(\"" + value[0] + "\"))"; + case FUNC_REF: + case NULL_FUNC_REF: + return "null"; + default: + throw new IllegalArgumentException("Type not recognized for ref args: " + type); + } + } + + /** + * Generate result extraction expression for CallResult. + * For numeric types, uses cr.longResult(i). + * For ref types, uses cr.refResult(i). + */ + public String toRefResultValue(String crVar, int index) { + switch (type) { + case I64: + return crVar + ".longResult(" + index + ")"; + case I32: + return "(int) " + crVar + ".longResult(" + index + ")"; + case F32: + return "Float.intBitsToFloat((int) " + crVar + ".longResult(" + index + ")), 0.0"; + case F64: + return "Double.longBitsToDouble(" + crVar + ".longResult(" + index + ")), 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: + return crVar + ".refResult(" + index + ")"; + default: + throw new IllegalArgumentException("Type not recognized " + type); + } + } } 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 4d2faf338..d58597f1f 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 @@ -50,7 +50,7 @@ public String value() { return value; } - public boolean isGcReference() { + public boolean isObjectRef() { switch (this) { case STRUCT_REF: case ANY_REF: @@ -59,13 +59,11 @@ public boolean isGcReference() { case EQ_REF: case I31_REF: case REF_NULL: + case EXTERN_REF: + case NULL_EXTERN_REF: return true; default: return false; } } - - public boolean isObjectRef() { - return isGcReference() || this == EXTERN_REF || this == NULL_EXTERN_REF; - } } From da4aa6d11b760936d4db532b3f9a2a33a12ea8e1 Mon Sep 17 00:00:00 2001 From: andreatp Date: Mon, 15 Jun 2026 11:38:30 +0100 Subject: [PATCH 10/20] =?UTF-8?q?fix:=20review=20findings=20=E2=80=94=20NU?= =?UTF-8?q?LL=5FREF=20sentinel,=20apply=20guard,=20overflow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix 5 confirmed bugs from code review, document 3 known limitations: MStack: NULL_REF sentinel distinguishes "null GC ref" from "no ref". pushRef(null) stores NULL_REF in refs[], popRef/peekRef convert back. topIsRef() checks the raw refs[] value. Fixes null GC refs lost in doControlTransfer, REF_IS_NULL, BR_ON_NULL, and exception catch. Instance: apply() guard uses isObjectRef() — externref functions now correctly throw, directing users to applyWithRefs(). Shaded: tableFillRef uses long arithmetic for overflow-safe bounds. Compiler: null-check refArgs before loading in compileCallFunction. InterpreterMachine: pushExceptionArgs uses tag type to determine ref positions instead of ambiguous null check. Known limitations (documented with TODO comments): - Cross-module call_indirect GC ref returns discarded (Bug 4) - Multi-value returns with Object[] vs LALOAD mismatch (Bug 5) - Host/cross-module calls lose GC ref params in long[] (Bug 6) Tests: GcReviewFixesTest with WAT module exercising null refs through blocks, exceptions, table.fill bounds, and externref via applyWithRefs. --- .../endive/compiler/internal/Compiler.java | 27 ++-- .../endive/compiler/internal/Emitters.java | 3 + .../run/endive/compiler/internal/Shaded.java | 2 +- .../ApprovalTest.verifyGc.approved.txt | 14 +++ .../run/endive/testing/GcReviewFixesTest.java | 119 ++++++++++++++++++ .../java/run/endive/runtime/Instance.java | 4 +- .../endive/runtime/InterpreterMachine.java | 78 +++++++----- .../main/java/run/endive/runtime/MStack.java | 19 ++- .../java/run/endive/runtime/StackFrame.java | 3 +- .../compiled/gc_review_fixes.wat.wasm | Bin 0 -> 386 bytes .../main/resources/wat/gc_review_fixes.wat | 73 +++++++++++ 11 files changed, 297 insertions(+), 45 deletions(-) create mode 100644 machine-tests/src/test/java/run/endive/testing/GcReviewFixesTest.java create mode 100644 wasm-corpus/src/main/resources/compiled/gc_review_fixes.wat.wasm create mode 100644 wasm-corpus/src/main/resources/wat/gc_review_fixes.wat 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 a64958680..91b59c16a 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/Compiler.java +++ b/compiler/src/main/java/run/endive/compiler/internal/Compiler.java @@ -1087,10 +1087,19 @@ private void compileCallFunction(int funcId, FunctionType type, InstructionAdapt for (int i = 0; i < type.params().size(); i++) { var param = type.params().get(i); if (param.isObjectRef()) { - // GC ref args: load from Object[] refArgs + // GC ref args: load from Object[] refArgs (null-safe) asm.load(3, OBJECT_TYPE); // refArgs + Label hasRefArgs = new Label(); + Label refDone = new Label(); + asm.dup(); + asm.ifnonnull(hasRefArgs); + asm.pop(); + asm.aconst(null); // default null for missing refArgs + asm.goTo(refDone); + asm.mark(hasRefArgs); asm.iconst(i); asm.aload(OBJECT_TYPE); + asm.mark(refDone); } else { asm.load(2, OBJECT_TYPE); asm.iconst(i); @@ -1392,6 +1401,9 @@ private static void compileHostFunction(int funcId, FunctionType type, Instructi emitUnboxResult(type, asm); } + // KNOWN LIMITATION (Bug 6): Object ref arguments are discarded when boxing into long[] + // for the cross-module/host call path. The proper fix requires a parallel Object[] for + // ref arguments in the host function call path. private static void emitBoxArguments(InstructionAdapter asm, List types) { int slot = 0; asm.iconst(types.size()); @@ -1419,18 +1431,13 @@ private static void emitUnboxResult(FunctionType type, InstructionAdapter asm) { } 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[]] + // KNOWN LIMITATION (Bug 4): Cross-module GC ref returns are not supported. + // Machine.call returns long[], but the Object result is lost in the cross-module + // path. The proper fix requires callWithRefs for cross-module calls. + // For now, discard the result and return null. 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); 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 cd1201701..badc51629 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/Emitters.java +++ b/compiler/src/main/java/run/endive/compiler/internal/Emitters.java @@ -1268,6 +1268,9 @@ private static void emitUnboxResult(InstructionAdapter asm, Context ctx, List types, int tempSlot) { asm.store(tempSlot, OBJECT_TYPE); for (int i = 0; i < types.size(); i++) { 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 1bbc29cc6..056496868 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/Shaded.java +++ b/compiler/src/main/java/run/endive/compiler/internal/Shaded.java @@ -129,7 +129,7 @@ public static int tableGrowRef(Object value, int size, int tableIndex, Instance public static void tableFillRef( int offset, Object value, int size, int tableIndex, Instance instance) { var table = instance.table(tableIndex); - if (offset + size > table.size()) { + if ((long) offset + (long) size > table.size()) { throw new TrapException("out of bounds table access"); } for (int i = 0; i < size; i++) { 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 5f67f2485..54d1563ac 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 @@ -52,8 +52,15 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { public static call_1(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ALOAD 3 + DUP + IFNONNULL L0 + POP + ACONST_NULL + GOTO L1 + L0 ICONST_0 AALOAD + L1 ALOAD 1 ALOAD 0 INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_1 (Ljava/lang/Object;Lrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I @@ -85,8 +92,15 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { public static call_2(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J ALOAD 3 + DUP + IFNONNULL L0 + POP + ACONST_NULL + GOTO L1 + L0 ICONST_0 AALOAD + L1 ALOAD 1 ALOAD 0 INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_2 (Ljava/lang/Object;Lrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I diff --git a/machine-tests/src/test/java/run/endive/testing/GcReviewFixesTest.java b/machine-tests/src/test/java/run/endive/testing/GcReviewFixesTest.java new file mode 100644 index 000000000..2cf30bbc8 --- /dev/null +++ b/machine-tests/src/test/java/run/endive/testing/GcReviewFixesTest.java @@ -0,0 +1,119 @@ +package run.endive.testing; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.function.Function; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import run.endive.compiler.MachineFactoryCompiler; +import run.endive.corpus.CorpusResources; +import run.endive.runtime.Instance; +import run.endive.runtime.InterpreterMachine; +import run.endive.wasm.Parser; +import run.endive.wasm.WasmModule; + +/** + * Tests for GC ref review fixes: + * - Bug 2: null GC ref through block boundary (doControlTransfer) + * - Bug 7: null GC ref in exception (pushExceptionArgs) + * - Bug 8: table.fill integer overflow + */ +public class GcReviewFixesTest { + + private static final WasmModule MODULE = + Parser.parse(CorpusResources.getResource("compiled/gc_review_fixes.wat.wasm")); + + private static Stream machineImplementations() { + return Stream.of( + Arguments.of( + "interpreter", + (Function) + (b) -> b.withMachineFactory(InterpreterMachine::new)), + Arguments.of( + "compiler", + (Function) + (b) -> b.withMachineFactory(MachineFactoryCompiler::compile))); + } + + // Bug 2: null GC ref must survive block boundary + @ParameterizedTest(name = "{0}") + @MethodSource("machineImplementations") + public void nullRefThroughBlock( + String name, Function machineInject) { + var instance = machineInject.apply(Instance.builder(MODULE)).build(); + var result = instance.export("null_ref_through_block").apply(); + assertEquals(1, result[0], "null ref should be preserved through block"); + } + + // Bug 2: null GC ref through nested blocks + @ParameterizedTest(name = "{0}") + @MethodSource("machineImplementations") + public void nullRefNestedBlocks( + String name, Function machineInject) { + var instance = machineInject.apply(Instance.builder(MODULE)).build(); + var result = instance.export("null_ref_nested_blocks").apply(); + assertEquals(1, result[0], "null ref should be preserved through nested blocks"); + } + + // Bug 2 regression: non-null GC ref through block + @ParameterizedTest(name = "{0}") + @MethodSource("machineImplementations") + public void nonNullRefThroughBlock( + String name, Function machineInject) { + var instance = machineInject.apply(Instance.builder(MODULE)).build(); + var result = instance.export("nonnull_ref_through_block").apply(); + assertEquals(42, result[0], "non-null ref value should be preserved through block"); + } + + // Bug 7: null GC ref through exception catch + @ParameterizedTest(name = "{0}") + @MethodSource("machineImplementations") + public void nullRefException( + String name, Function machineInject) { + var instance = machineInject.apply(Instance.builder(MODULE)).build(); + var result = instance.export("null_ref_exception").apply(); + assertEquals(1, result[0], "null ref should be preserved through exception"); + } + + // Bug 7 regression: non-null GC ref through exception + @ParameterizedTest(name = "{0}") + @MethodSource("machineImplementations") + public void nonNullRefException( + String name, Function machineInject) { + var instance = machineInject.apply(Instance.builder(MODULE)).build(); + var result = instance.export("nonnull_ref_exception").apply(); + assertEquals(99, result[0], "non-null ref value should be preserved through exception"); + } + + // Bug 8: table.fill bounds (validates no integer overflow) + @ParameterizedTest(name = "{0}") + @MethodSource("machineImplementations") + public void tableFillBounds( + String name, Function machineInject) { + var instance = machineInject.apply(Instance.builder(MODULE)).build(); + var result = instance.export("table_fill_bounds").apply(); + assertEquals(1, result[0], "table.fill should succeed for valid bounds"); + } + + // Bug 1: externref functions should throw from apply() (isObjectRef, not isGcReference) + @ParameterizedTest(name = "{0}") + @MethodSource("machineImplementations") + public void externrefApplyGuard( + String name, Function machineInject) { + // The gc_edge_cases module has make_point which takes/returns GC refs. + // After the fix, apply() should throw for functions with any ObjectRef params/returns. + var module = Parser.parse(CorpusResources.getResource("compiled/gc_edge_cases.wat.wasm")); + var instance = machineInject.apply(Instance.builder(module)).build(); + // make_point has (ref $Point) return - should throw from apply() + var makePoint = instance.export("make_point"); + try { + makePoint.apply(99); + // If we get here, the guard didn't fire (bug 1 not fixed) + throw new AssertionError("apply() should throw for GC ref returns"); + } catch (UnsupportedOperationException e) { + // expected + } + } +} diff --git a/runtime/src/main/java/run/endive/runtime/Instance.java b/runtime/src/main/java/run/endive/runtime/Instance.java index f931d8c39..763dc3580 100644 --- a/runtime/src/main/java/run/endive/runtime/Instance.java +++ b/runtime/src/main/java/run/endive/runtime/Instance.java @@ -280,8 +280,8 @@ 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 hasGcRefParams = funcType.params().stream().anyMatch(ValType::isGcReference); - boolean hasGcRefReturns = funcType.returns().stream().anyMatch(ValType::isGcReference); + boolean hasGcRefParams = funcType.params().stream().anyMatch(ValType::isObjectRef); + boolean hasGcRefReturns = funcType.returns().stream().anyMatch(ValType::isObjectRef); return new ExportFunction() { @Override public long[] apply(long... args) { diff --git a/runtime/src/main/java/run/endive/runtime/InterpreterMachine.java b/runtime/src/main/java/run/endive/runtime/InterpreterMachine.java index 392f34ae4..4416fc933 100644 --- a/runtime/src/main/java/run/endive/runtime/InterpreterMachine.java +++ b/runtime/src/main/java/run/endive/runtime/InterpreterMachine.java @@ -1880,26 +1880,27 @@ private static void ELEM_DROP(Instance instance, Operands operands) { } private static void REF_IS_NULL(MStack stack) { - Object refObj = stack.peekRef(); - if (refObj != null) { - // Non-null Object ref (GC or externref) - stack.popRef(); - stack.push(Value.FALSE); + if (stack.topIsRef()) { + // GC ref position — check if the ref itself is null + Object refObj = stack.popRef(); + stack.push(refObj == null ? Value.TRUE : Value.FALSE); } else { - // Either null Object ref or long-typed ref (funcref) — both use REF_NULL_VALUE on long - // side + // Non-GC ref (funcref) — check the long side var val = stack.pop(); stack.push(((val == REF_NULL_VALUE) ? Value.TRUE : Value.FALSE)); } } private static void REF_AS_NON_NULL(MStack stack) { - Object refObj = stack.peekRef(); - if (refObj != null) { - // Non-null Object ref — leave it - stack.popRef(); + if (stack.topIsRef()) { + // GC ref position — check if the ref itself is null + Object refObj = stack.popRef(); + if (refObj == null) { + throw new TrapException("Trapped on ref_as_non_null on null reference"); + } stack.pushRef(refObj); } else { + // Non-GC ref (funcref) — check the long side var val = stack.pop(); if (val == REF_NULL_VALUE) { throw new TrapException("Trapped on ref_as_non_null on null reference"); @@ -1953,7 +1954,7 @@ private static void TABLE_FILL(MStack stack, Instance instance, Operands operand if (isObjRefTable) { var refVal = stack.popRef(); var offset = (int) stack.pop(); - if (offset + size > table.size()) { + if ((long) offset + (long) size > table.size()) { throw new TrapException("out of bounds table access"); } for (int i = 0; i < size; i++) { @@ -3265,13 +3266,18 @@ private static void BR_IF(StackFrame frame, MStack stack, AnnotatedInstruction i private static void BR_ON_NULL( StackFrame frame, MStack stack, AnnotatedInstruction instruction) { - Object refObj = stack.peekRef(); - if (refObj != null) { - // Non-null GC ref — pop the ref side, keep it - stack.popRef(); - stack.pushRef(refObj); + if (stack.topIsRef()) { + // GC ref position — check if the ref itself is null + Object refObj = stack.popRef(); + if (refObj == null) { + // null GC ref — branch + BR(frame, stack, instruction); + } else { + // non-null GC ref — keep it and fall through + stack.pushRef(refObj); + } } else { - // Non-GC ref or null GC ref — check the long side + // Non-GC ref (funcref/externref) — check the long side var val = stack.pop(); if (val == REF_NULL_VALUE) { BR(frame, stack, instruction); @@ -3283,13 +3289,17 @@ private static void BR_ON_NULL( private static void BR_ON_NON_NULL( StackFrame frame, MStack stack, AnnotatedInstruction instruction) { - Object refObj = stack.peekRef(); - if (refObj != null) { - // Non-null GC ref — pop and branch with it - stack.popRef(); - stack.pushRef(refObj); - BR(frame, stack, instruction); + if (stack.topIsRef()) { + // GC ref position — check if the ref itself is null + Object refObj = stack.popRef(); + if (refObj != null) { + // non-null GC ref — push it back and branch + stack.pushRef(refObj); + BR(frame, stack, instruction); + } + // null GC ref — drop it and fall through } else { + // Non-GC ref (funcref/externref) — check the long side var val = stack.pop(); if (val != REF_NULL_VALUE) { stack.push(val); @@ -3859,11 +3869,23 @@ private static void ARRAY_INIT_ELEM(MStack stack, Instance instance, Operands op private static void pushExceptionArgs(WasmException exception, MStack stack) { var refArgs = exception.refArgs(); - for (int i = 0; i < exception.args().length; i++) { - if (refArgs != null && refArgs[i] != null) { - stack.pushRef(refArgs[i]); + var tag = exception.instance().tag(exception.tagIdx()); + var tagType = exception.instance().type(tag.tagType().typeIdx()); + var params = tagType.params(); + int slot = 0; + for (int i = 0; i < params.size(); i++) { + var p = params.get(i); + if (p.isObjectRef()) { + // This position is a ref (even if the value is null) + stack.pushRef(refArgs != null && slot < refArgs.length ? refArgs[slot] : null); + slot++; + } else if (p.equals(ValType.V128)) { + stack.push(exception.args()[slot]); + stack.push(exception.args()[slot + 1]); + slot += 2; } else { - stack.push(exception.args()[i]); + stack.push(exception.args()[slot]); + slot++; } } } diff --git a/runtime/src/main/java/run/endive/runtime/MStack.java b/runtime/src/main/java/run/endive/runtime/MStack.java index 809a1cb64..3956273e1 100644 --- a/runtime/src/main/java/run/endive/runtime/MStack.java +++ b/runtime/src/main/java/run/endive/runtime/MStack.java @@ -5,6 +5,10 @@ public class MStack { public static final int MIN_CAPACITY = 8; + // Sentinel to distinguish "null GC ref" from "no ref at this position" in refs[]. + // Package-private: used by MStack and StackFrame. + static final Object NULL_REF = new Object(); + private int count; private long[] elements; private Object[] refs; @@ -53,7 +57,7 @@ public void pushRef(Object ref) { refs = new Object[elements.length]; } elements[count] = (ref == null) ? REF_NULL_VALUE : 0; - refs[count] = ref; + refs[count] = (ref == null) ? NULL_REF : ref; count++; if (count == elements.length) { @@ -73,7 +77,7 @@ public Object popRef() { } Object ref = refs[count]; refs[count] = null; - return ref; + return (ref == NULL_REF) ? null : ref; } public long peek() { @@ -84,7 +88,16 @@ public Object peekRef() { if (refs == null) { return null; } - return refs[count - 1]; + Object ref = refs[count - 1]; + return (ref == NULL_REF) ? null : ref; + } + + /** + * Returns true if the top-of-stack position holds a ref (including null GC refs). + * Unlike peekRef(), this distinguishes "null GC ref" from "no ref at this position". + */ + public boolean topIsRef() { + return refs != null && refs[count - 1] != null; } public int size() { diff --git a/runtime/src/main/java/run/endive/runtime/StackFrame.java b/runtime/src/main/java/run/endive/runtime/StackFrame.java index 2dc8de8ed..9cb6ba6d0 100644 --- a/runtime/src/main/java/run/endive/runtime/StackFrame.java +++ b/runtime/src/main/java/run/endive/runtime/StackFrame.java @@ -254,7 +254,8 @@ static void doControlTransfer(CtrlFrame ctrlFrame, MStack stack) { long value = returns[idx]; Object ref = returnRefs[idx]; if (ref != null) { - stack.pushRef(ref); + // Was a ref (real Object or NULL_REF sentinel for null GC ref) + stack.pushRef(ref == MStack.NULL_REF ? null : ref); } else { stack.push(value); } diff --git a/wasm-corpus/src/main/resources/compiled/gc_review_fixes.wat.wasm b/wasm-corpus/src/main/resources/compiled/gc_review_fixes.wat.wasm new file mode 100644 index 0000000000000000000000000000000000000000..7b3bcf4bd08cd186959118e9edacb618e1edf75b GIT binary patch literal 386 zcmZ{gK~BRk5JmskPKs(3QA8`z4Y1`3Z173kBz0*;YNObZ*j3^L9Ic#zW0VDU5-1zI z__p7lKN<}Rzas#2&Nc0^1nrkp0eqBGcSM%H;|V9Q(L3dH+t#&i%vy&{LHQxj{R44!d literal 0 HcmV?d00001 diff --git a/wasm-corpus/src/main/resources/wat/gc_review_fixes.wat b/wasm-corpus/src/main/resources/wat/gc_review_fixes.wat new file mode 100644 index 000000000..21f17e02d --- /dev/null +++ b/wasm-corpus/src/main/resources/wat/gc_review_fixes.wat @@ -0,0 +1,73 @@ +(module + ;; Bug 2: null GC ref survives control transfer (block boundary) + (type $s (struct (field i32))) + + (func (export "null_ref_through_block") (result i32) + (local $r (ref null $s)) + (local.set $r + (block (result (ref null $s)) + ref.null $s + ) + ) + (ref.is_null (local.get $r)) + ) + + ;; Bug 2 variant: null GC ref survives nested blocks + (func (export "null_ref_nested_blocks") (result i32) + (local $r (ref null $s)) + (local.set $r + (block (result (ref null $s)) + (block (result (ref null $s)) + ref.null $s + ) + ) + ) + (ref.is_null (local.get $r)) + ) + + ;; Bug 2: non-null GC ref through block boundary (regression check) + (func (export "nonnull_ref_through_block") (result i32) + (local $r (ref null $s)) + (local.set $r + (block (result (ref null $s)) + (struct.new $s (i32.const 42)) + ) + ) + (struct.get $s 0 (local.get $r)) + ) + + ;; Bug 7: null GC ref through exception catch + (tag $e (param (ref null $s))) + + (func (export "null_ref_exception") (result i32) + (local $r (ref null $s)) + (block $b (result (ref null $s)) + (try_table (catch $e $b) + (throw $e (ref.null $s)) + ) + (unreachable) + ) + (local.set $r) + (ref.is_null (local.get $r)) + ) + + ;; Bug 7: non-null GC ref through exception (regression check) + (func (export "nonnull_ref_exception") (result i32) + (block $b (result (ref null $s)) + (try_table (catch $e $b) + (throw $e (struct.new $s (i32.const 99))) + ) + (unreachable) + ) + (struct.get $s 0) + ) + + ;; Bug 8: table.fill with valid bounds + (table $t 10 (ref null $s)) + + (func (export "table_fill_bounds") (result i32) + ;; Fill first 3 entries, should succeed + (table.fill $t (i32.const 0) (ref.null $s) (i32.const 3)) + (i32.const 1) ;; success + ) +) From fcf5d03181ea9effcfc7d3c264e3d904ae8584b4 Mon Sep 17 00:00:00 2001 From: andreatp Date: Mon, 15 Jun 2026 12:57:34 +0100 Subject: [PATCH 11/20] =?UTF-8?q?fix:=20compiler=20boundary=20bugs=20?= =?UTF-8?q?=E2=80=94=20dual=20long[]+Object[]=20at=20all=20boundaries?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix 6 bugs where GC ref Objects were lost at long[] serialization boundaries in the compiler. Root cause: the compiler used long[] to pass args/returns across module boundaries, host calls, tail calls, and too-many-params. Object refs can't fit in long[], so they were discarded (POP + 0L). Fix: dual long[] + Object[] at every boundary, matching the Machine.callWithRefs(int, long[], Object[]) → CallResult pattern. Shaded: add WithRefs overloads for callHostFunction, callIndirect, setTailCall, setTailCallIndirect, resolveTailCall. Old long[]-only methods kept for backward compat with existing compiled code. Instance.TailCallPending: add Object[] refArgs field. Emitters: emitBoxValuesOnStackWithRefs creates dual arrays. emitUnboxResult uses AALOAD for Object[] multi-value returns. emitTailCallCheck uses resolveTailCallWithRefs → CallResult. RETURN_CALL variants use WithRefs when params have Object refs. Compiler: emitBoxArgumentsWithRefs, emitUnboxCallResult for host/cross-module paths. compileMachineCallInvoke host path uses callHostFunctionWithRefs. Zero overhead for non-GC: all WithRefs paths gated on isObjectRef() at emit time. Null Object[] when no refs. --- .../endive/compiler/internal/Compiler.java | 242 +++++++++++++++--- .../endive/compiler/internal/Emitters.java | 241 ++++++++++++++--- .../run/endive/compiler/internal/Shaded.java | 54 ++++ .../endive/compiler/internal/ShadedRefs.java | 42 +++ .../ApprovalTest.verifyHelloWasi.approved.txt | 6 +- .../ApprovalTest.verifyStart.approved.txt | 6 +- .../ApprovalTest.verifyTailCall.approved.txt | 6 +- .../java/run/endive/runtime/Instance.java | 16 ++ 8 files changed, 545 insertions(+), 68 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 91b59c16a..a77287581 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/Compiler.java +++ b/compiler/src/main/java/run/endive/compiler/internal/Compiler.java @@ -40,8 +40,10 @@ import static run.endive.compiler.internal.EmitterMap.EMITTERS; import static run.endive.compiler.internal.ShadedRefs.AOT_INTERPRETER_MACHINE_CALL; import static run.endive.compiler.internal.ShadedRefs.CALL_HOST_FUNCTION; +import static run.endive.compiler.internal.ShadedRefs.CALL_HOST_FUNCTION_WITH_REFS; import static run.endive.compiler.internal.ShadedRefs.CALL_INDIRECT; import static run.endive.compiler.internal.ShadedRefs.CALL_INDIRECT_ON_INTERPRETER; +import static run.endive.compiler.internal.ShadedRefs.CALL_INDIRECT_WITH_REFS; import static run.endive.compiler.internal.ShadedRefs.CHECK_INTERRUPTION; import static run.endive.compiler.internal.ShadedRefs.INSTANCE_MEMORY; import static run.endive.compiler.internal.ShadedRefs.INSTANCE_TABLE; @@ -91,6 +93,7 @@ public final class Compiler { public static final String DEFAULT_CLASS_NAME = "run.endive.$gen.CompiledMachine"; private static final Type LONG_ARRAY_TYPE = Type.getType(long[].class); private static final Type INT_ARRAY_TYPE = Type.getType(int[].class); + private static final String CALL_RESULT_INTERNAL_NAME = Type.getInternalName(CallResult.class); private static final Type AOT_INTERPRETER_MACHINE_TYPE = Type.getType(CompilerInterpreterMachine.class); private static final Type INSTANCE_TYPE = Type.getType(Instance.class); @@ -625,13 +628,19 @@ private byte[] compileClass() { if (!seenValueMethods.add(methodName)) { continue; } + boolean hasGcRef = types.stream().anyMatch(ValType::isObjectRef); emitFunction( classWriter, methodName, valueMethodType(types), true, asm -> { - emitBoxArguments(asm, types); + if (hasGcRef) { + // Object[] return: box numerics as Long, keep refs as Object + emitBoxArgumentsAsObjectArray(asm, types); + } else { + emitBoxArguments(asm, types); + } asm.areturn(OBJECT_TYPE); }); } @@ -897,8 +906,13 @@ private void compileMachineCallWithTailCalls( getMethodDescriptor(getType(long[].class)), false); asm.store(2, OBJECT_TYPE); - // Clear refArgs for tail calls (tail call args don't carry Object[] refArgs) - asm.aconst(null); + // Load refArgs from tail call pending + asm.load(instanceLocal, OBJECT_TYPE); + asm.invokevirtual( + getInternalName(Instance.class), + "tailCallRefArgs", + getMethodDescriptor(getType(Object[].class)), + false); asm.store(refArgsSlot, OBJECT_TYPE); asm.load(instanceLocal, OBJECT_TYPE); asm.invokevirtual( @@ -1056,15 +1070,27 @@ private void compileMachineCallInvoke(InstructionAdapter asm, int start, int end asm.areturn(OBJECT_TYPE); } - // return instance.callHostFunction(funcId, args); + // return instance.callHostFunctionWithRefs(funcId, args, refArgs) + // We always use WithRefs here since the generic dispatch doesn't know + // at emit time whether the function type has Object refs. + // CallResult.longs() is returned to the Machine.call() caller. if (functionImports > start) { asm.mark(hostLabel); - asm.pop(); // pop refArgs + // stack: instance, memory, args, refArgs + asm.pop(); // pop refArgs -> save it + // Actually we need the args: instance, memory, args, refArgs are local 0-4 + // Pop all stacked values (instance, memory, args, refArgs were loaded) asm.pop(); asm.pop(); - asm.load(2, INT_TYPE); - asm.load(3, OBJECT_TYPE); - emitInvokeStatic(asm, CALL_HOST_FUNCTION); + asm.pop(); + // Use locals directly + asm.load(0, OBJECT_TYPE); // instance + asm.load(2, INT_TYPE); // funcId + asm.load(3, OBJECT_TYPE); // args + asm.load(4, OBJECT_TYPE); // refArgs + emitInvokeStatic(asm, CALL_HOST_FUNCTION_WITH_REFS); + // CallResult on stack, extract longs() for the long[] return + asm.invokevirtual(CALL_RESULT_INTERNAL_NAME, "longs", "()[J", false); asm.areturn(OBJECT_TYPE); } @@ -1314,18 +1340,38 @@ private void compileCallIndirect( // other: call function in another module asm.mark(other); - if (hasTooManyParameters(type)) { - asm.load(0, LONG_ARRAY_TYPE); + boolean hasObjectRefParams = type.params().stream().anyMatch(ValType::isObjectRef); + boolean hasObjectRefReturns = type.returns().stream().anyMatch(ValType::isObjectRef); + + if (hasObjectRefParams || hasObjectRefReturns) { + if (hasTooManyParameters(type)) { + asm.load(0, LONG_ARRAY_TYPE); + asm.aconst(null); // Object[] refArgs + } else { + emitBoxArgumentsWithRefs(asm, type.params()); + // stack: long[], Object[] + } + asm.iconst(typeId); + asm.load(funcId, INT_TYPE); + asm.load(refInstance, OBJECT_TYPE); + + emitInvokeStatic(asm, CALL_INDIRECT_WITH_REFS); + // returns CallResult + emitUnboxCallResult(type, asm); } else { - emitBoxArguments(asm, type.params()); - } - asm.iconst(typeId); - asm.load(funcId, INT_TYPE); - asm.load(refInstance, OBJECT_TYPE); + if (hasTooManyParameters(type)) { + asm.load(0, LONG_ARRAY_TYPE); + } else { + emitBoxArguments(asm, type.params()); + } + asm.iconst(typeId); + asm.load(funcId, INT_TYPE); + asm.load(refInstance, OBJECT_TYPE); - emitInvokeStatic(asm, CALL_INDIRECT); + emitInvokeStatic(asm, CALL_INDIRECT); - emitUnboxResult(type, asm); + emitUnboxResult(type, asm); + } } private void compileCallIndirectApply( @@ -1391,20 +1437,81 @@ private void compileCallIndirectApply( private static void compileHostFunction(int funcId, FunctionType type, InstructionAdapter asm) { int slot = type.params().stream().mapToInt(CompilerUtil::slotCount).sum(); + boolean hasObjectRefParams = type.params().stream().anyMatch(ValType::isObjectRef); + boolean hasObjectRefReturns = type.returns().stream().anyMatch(ValType::isObjectRef); - asm.load(slot + 1, OBJECT_TYPE); // instance - asm.iconst(funcId); - emitBoxArguments(asm, type.params()); + if (hasObjectRefParams || hasObjectRefReturns) { + asm.load(slot + 1, OBJECT_TYPE); // instance + asm.iconst(funcId); + emitBoxArgumentsWithRefs(asm, type.params()); + // stack: instance, funcId, long[], Object[] + emitInvokeStatic(asm, CALL_HOST_FUNCTION_WITH_REFS); + // returns CallResult + emitUnboxCallResult(type, asm); + } else { + asm.load(slot + 1, OBJECT_TYPE); // instance + asm.iconst(funcId); + emitBoxArguments(asm, type.params()); + emitInvokeStatic(asm, CALL_HOST_FUNCTION); + emitUnboxResult(type, asm); + } + } - emitInvokeStatic(asm, CALL_HOST_FUNCTION); + private static void emitBoxArguments(InstructionAdapter asm, List types) { + int slot = 0; + asm.iconst(types.size()); + asm.newarray(LONG_TYPE); + for (int i = 0; i < types.size(); i++) { + asm.dup(); + asm.iconst(i); + ValType valType = types.get(i); + asm.load(slot, asmType(valType)); + if (valType.isObjectRef()) { + asm.visitInsn(Opcodes.POP); + asm.lconst(0L); + } else { + emitJvmToLong(asm, valType); + } + asm.astore(LONG_TYPE); + slot += slotCount(valType); + } + } - emitUnboxResult(type, asm); + /** + * Boxes JVM locals into a single Object[] array. + * Numeric values are boxed as Long, Object refs stay as Object. + * Used for multi-value returns (value_xxx methods) when any return is a GC ref. + */ + private static void emitBoxArgumentsAsObjectArray(InstructionAdapter asm, List types) { + int slot = 0; + asm.iconst(types.size()); + asm.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object"); + for (int i = 0; i < types.size(); i++) { + asm.dup(); + asm.iconst(i); + ValType valType = types.get(i); + asm.load(slot, asmType(valType)); + if (valType.isObjectRef()) { + // Object ref: already on stack as Object + } else { + // Numeric: convert to long then box into Long + emitJvmToLong(asm, valType); + asm.invokestatic("java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false); + } + asm.astore(OBJECT_TYPE); + slot += slotCount(valType); + } } - // KNOWN LIMITATION (Bug 6): Object ref arguments are discarded when boxing into long[] - // for the cross-module/host call path. The proper fix requires a parallel Object[] for - // ref arguments in the host function call path. - private static void emitBoxArguments(InstructionAdapter asm, List types) { + /** + * Boxes JVM locals into dual long[] + Object[] arrays. + * After this method, the stack has: [..., long[], Object[]] + * If no values are Object refs, Object[] will be null (zero overhead). + */ + private static void emitBoxArgumentsWithRefs(InstructionAdapter asm, List types) { + boolean hasObjectRefs = types.stream().anyMatch(ValType::isObjectRef); + + // Build long[] int slot = 0; asm.iconst(types.size()); asm.newarray(LONG_TYPE); @@ -1422,6 +1529,25 @@ private static void emitBoxArguments(InstructionAdapter asm, List types asm.astore(LONG_TYPE); slot += slotCount(valType); } + + // Build Object[] (or push null) + if (hasObjectRefs) { + asm.iconst(types.size()); + asm.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object"); + slot = 0; + for (int i = 0; i < types.size(); i++) { + ValType valType = types.get(i); + if (valType.isObjectRef()) { + asm.dup(); + asm.iconst(i); + asm.load(slot, asmType(valType)); + asm.astore(OBJECT_TYPE); + } + slot += slotCount(valType); + } + } else { + asm.aconst(null); + } } private static void emitUnboxResult(FunctionType type, InstructionAdapter asm) { @@ -1431,10 +1557,9 @@ private static void emitUnboxResult(FunctionType type, InstructionAdapter asm) { } else if (returnType == long[].class || returnType == Object[].class) { asm.areturn(OBJECT_TYPE); } else if (returnType == Object.class) { - // KNOWN LIMITATION (Bug 4): Cross-module GC ref returns are not supported. - // Machine.call returns long[], but the Object result is lost in the cross-module - // path. The proper fix requires callWithRefs for cross-module calls. - // For now, discard the result and return null. + // Single Object return from cross-module call that returns long[] + // The Object result is in the long[] as 0, can't extract it + // Callers with Object refs should use emitUnboxCallResult instead asm.iconst(0); asm.aload(LONG_TYPE); asm.visitInsn(Opcodes.L2I); @@ -1450,6 +1575,63 @@ private static void emitUnboxResult(FunctionType type, InstructionAdapter asm) { } } + /** + * Unboxes a CallResult into the proper JVM return value. + * Used for cross-module/host calls that go through callWithRefs / callHostFunctionWithRefs. + * The CallResult has .longs() for numerics and .refs() for Object refs. + */ + private static void emitUnboxCallResult(FunctionType type, InstructionAdapter asm) { + Class returnType = jvmReturnType(type); + if (returnType == void.class) { + asm.pop(); // discard CallResult + asm.areturn(VOID_TYPE); + } else if (returnType == Object.class) { + // Single Object return: extract from CallResult.refResult(0) + asm.iconst(0); + asm.invokevirtual( + CALL_RESULT_INTERNAL_NAME, "refResult", "(I)Ljava/lang/Object;", false); + asm.areturn(OBJECT_TYPE); + } else if (returnType == long[].class) { + // Multi-value, no Object refs: extract longs() + asm.invokevirtual(CALL_RESULT_INTERNAL_NAME, "longs", "()[J", false); + asm.areturn(OBJECT_TYPE); + } else if (returnType == Object[].class) { + // Multi-value with Object refs: build Object[] from CallResult + // store CallResult in a temp + int crSlot = type.params().stream().mapToInt(CompilerUtil::slotCount).sum() + 2; + asm.store(crSlot, OBJECT_TYPE); + + asm.iconst(type.returns().size()); + asm.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object"); + + for (int i = 0; i < type.returns().size(); i++) { + asm.dup(); + asm.iconst(i); + ValType retType = type.returns().get(i); + if (retType.isObjectRef()) { + asm.load(crSlot, OBJECT_TYPE); + asm.iconst(i); + asm.invokevirtual( + CALL_RESULT_INTERNAL_NAME, "refResult", "(I)Ljava/lang/Object;", false); + } else { + asm.load(crSlot, OBJECT_TYPE); + asm.iconst(i); + asm.invokevirtual(CALL_RESULT_INTERNAL_NAME, "longResult", "(I)J", false); + // box long into Long for Object[] + asm.invokestatic("java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false); + } + asm.astore(OBJECT_TYPE); + } + asm.areturn(OBJECT_TYPE); + } else { + // Single numeric return: extract from CallResult.longResult(0) + asm.iconst(0); + asm.invokevirtual(CALL_RESULT_INTERNAL_NAME, "longResult", "(I)J", false); + emitLongToJvm(asm, type.returns().get(0)); + asm.areturn(getType(returnType)); + } + } + // implements the body of: // public static func_xxx( ArgN..., Memory memory, Instance instance) private void compileFunction( 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 badc51629..766df5c37 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/Emitters.java +++ b/compiler/src/main/java/run/endive/compiler/internal/Emitters.java @@ -195,6 +195,72 @@ private static void emitBoxValuesOnStack( } } + /** + * Boxes values from the JVM operand stack into dual long[] + Object[] arrays. + * Numeric values go into long[], GC ref values go into Object[]. + * After this method, the stack has: [..., long[], Object[]] + * + * If no values are Object refs, Object[] will be null on stack (zero overhead). + */ + private static void emitBoxValuesOnStackWithRefs( + Context ctx, InstructionAdapter asm, List types) { + + boolean hasObjectRefs = types.stream().anyMatch(ValType::isObjectRef); + + // 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 the long[] array + asm.iconst(types.size()); + asm.newarray(LONG_TYPE); + + // Load from locals and store in long[] (skip Object refs — store 0L) + slot = ctx.tempSlot(); + for (int i = 0; i < types.size(); i++) { + ValType valType = types.get(i); + + if (valType.isObjectRef()) { + // Object refs can't be stored in long[] — store 0L as placeholder + asm.dup(); + asm.iconst(i); + asm.lconst(0L); + asm.astore(LONG_TYPE); + } else { + asm.dup(); + asm.iconst(i); + asm.load(slot, asmType(valType)); + emitJvmToLong(asm, valType); + asm.astore(LONG_TYPE); + } + slot += slotCount(valType); + } + + // Create the Object[] array (or push null if no Object refs) + if (hasObjectRefs) { + 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.isObjectRef()) { + asm.dup(); + asm.iconst(i); + asm.load(slot, asmType(valType)); + asm.astore(OBJECT_TYPE); + } + slot += slotCount(valType); + } + } else { + asm.aconst(null); + } + } + /** * Builds both long[] and Object[] from field values on the JVM stack. * Numeric fields go into long[], GC ref fields go into Object[]. @@ -1153,12 +1219,27 @@ private static void emitLoadOrStore( public static void RETURN_CALL(Context ctx, CompilerInstruction ins, InstructionAdapter asm) { int funcId = (int) ins.operand(0); FunctionType calleeType = ctx.functionTypes().get(funcId); - - emitBoxValuesOnStack(ctx, asm, calleeType.params()); - asm.iconst(funcId); - asm.swap(); - asm.load(ctx.instanceSlot(), OBJECT_TYPE); - emitInvokeStatic(asm, ShadedRefs.SET_TAIL_CALL); + boolean hasObjectRefParams = calleeType.params().stream().anyMatch(ValType::isObjectRef); + + if (hasObjectRefParams) { + // dual long[] + Object[] + emitBoxValuesOnStackWithRefs(ctx, asm, calleeType.params()); + // stack: long[], Object[] + int refArgsSlot = ctx.tempSlot(); + asm.store(refArgsSlot, OBJECT_TYPE); // save Object[] + // stack: long[] + asm.iconst(funcId); + asm.swap(); // funcId, long[] + asm.load(refArgsSlot, OBJECT_TYPE); // funcId, long[], Object[] + asm.load(ctx.instanceSlot(), OBJECT_TYPE); + emitInvokeStatic(asm, ShadedRefs.SET_TAIL_CALL_WITH_REFS); + } else { + emitBoxValuesOnStack(ctx, asm, calleeType.params()); + asm.iconst(funcId); + asm.swap(); + asm.load(ctx.instanceSlot(), OBJECT_TYPE); + emitInvokeStatic(asm, ShadedRefs.SET_TAIL_CALL); + } emitDefaultReturn(ctx, asm); } @@ -1167,17 +1248,28 @@ public static void RETURN_CALL_INDIRECT( int typeId = (int) ins.operand(0); int tableIdx = (int) ins.operand(1); FunctionType calleeType = ctx.types()[typeId]; + boolean hasObjectRefParams = calleeType.params().stream().anyMatch(ValType::isObjectRef); int paramSlots = calleeType.params().stream().mapToInt(CompilerUtil::slotCount).sum(); int savedSlot = ctx.tempSlot() + paramSlots; asm.store(savedSlot, INT_TYPE); - emitBoxValuesOnStack(ctx, asm, calleeType.params()); - asm.load(savedSlot, INT_TYPE); - asm.iconst(typeId); - asm.iconst(tableIdx); - asm.load(ctx.instanceSlot(), OBJECT_TYPE); - emitInvokeStatic(asm, ShadedRefs.SET_TAIL_CALL_INDIRECT); + if (hasObjectRefParams) { + emitBoxValuesOnStackWithRefs(ctx, asm, calleeType.params()); + // stack: long[], Object[] + asm.load(savedSlot, INT_TYPE); + asm.iconst(typeId); + asm.iconst(tableIdx); + asm.load(ctx.instanceSlot(), OBJECT_TYPE); + emitInvokeStatic(asm, ShadedRefs.SET_TAIL_CALL_INDIRECT_WITH_REFS); + } else { + emitBoxValuesOnStack(ctx, asm, calleeType.params()); + asm.load(savedSlot, INT_TYPE); + asm.iconst(typeId); + asm.iconst(tableIdx); + asm.load(ctx.instanceSlot(), OBJECT_TYPE); + emitInvokeStatic(asm, ShadedRefs.SET_TAIL_CALL_INDIRECT); + } emitDefaultReturn(ctx, asm); } @@ -1185,6 +1277,7 @@ public static void RETURN_CALL_REF( Context ctx, CompilerInstruction ins, InstructionAdapter asm) { int typeId = (int) ins.operand(0); FunctionType calleeType = ctx.types()[typeId]; + boolean hasObjectRefParams = calleeType.params().stream().anyMatch(ValType::isObjectRef); int paramSlots = calleeType.params().stream().mapToInt(CompilerUtil::slotCount).sum(); int savedSlot = ctx.tempSlot() + paramSlots; @@ -1199,11 +1292,23 @@ public static void RETURN_CALL_REF( asm.athrow(); asm.mark(notNull); - emitBoxValuesOnStack(ctx, asm, calleeType.params()); - asm.load(savedSlot, INT_TYPE); - asm.swap(); - asm.load(ctx.instanceSlot(), OBJECT_TYPE); - emitInvokeStatic(asm, ShadedRefs.SET_TAIL_CALL); + if (hasObjectRefParams) { + emitBoxValuesOnStackWithRefs(ctx, asm, calleeType.params()); + // stack: long[], Object[] + int refArgsSlot = ctx.tempSlot(); + asm.store(refArgsSlot, OBJECT_TYPE); // save Object[] + asm.load(savedSlot, INT_TYPE); + asm.swap(); // funcId, long[] + asm.load(refArgsSlot, OBJECT_TYPE); // funcId, long[], Object[] + asm.load(ctx.instanceSlot(), OBJECT_TYPE); + emitInvokeStatic(asm, ShadedRefs.SET_TAIL_CALL_WITH_REFS); + } else { + emitBoxValuesOnStack(ctx, asm, calleeType.params()); + asm.load(savedSlot, INT_TYPE); + asm.swap(); + asm.load(ctx.instanceSlot(), OBJECT_TYPE); + emitInvokeStatic(asm, ShadedRefs.SET_TAIL_CALL); + } emitDefaultReturn(ctx, asm); } @@ -1240,6 +1345,7 @@ private static void emitTailCallCheck( asm.ifeq(noPending); List returns = functionType.returns(); + boolean hasObjectRefReturns = returns.stream().anyMatch(ValType::isObjectRef); if (returns.size() == 1) { emitPop(asm, returns.get(0)); @@ -1247,17 +1353,66 @@ private static void emitTailCallCheck( asm.pop(); } - asm.load(ctx.instanceSlot(), OBJECT_TYPE); - emitInvokeStatic(asm, ShadedRefs.RESOLVE_TAIL_CALL); + if (hasObjectRefReturns) { + // Use resolveTailCallWithRefs which returns CallResult + asm.load(ctx.instanceSlot(), OBJECT_TYPE); + emitInvokeStatic(asm, ShadedRefs.RESOLVE_TAIL_CALL_WITH_REFS); - if (returns.isEmpty()) { - asm.pop(); - } else if (returns.size() == 1) { - asm.iconst(0); - asm.aload(LONG_TYPE); - emitLongToJvm(asm, returns.get(0)); + if (returns.isEmpty()) { + asm.pop(); + } else if (returns.size() == 1) { + // Single Object return: extract from CallResult.refResult(0) + asm.iconst(0); + asm.invokevirtual( + "run/endive/runtime/CallResult", + "refResult", + "(I)Ljava/lang/Object;", + false); + } else { + // Multi-value with refs: build Object[] from CallResult + int crSlot = ctx.tempSlot(); + asm.store(crSlot, OBJECT_TYPE); + + asm.iconst(returns.size()); + asm.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object"); + + for (int i = 0; i < returns.size(); i++) { + asm.dup(); + asm.iconst(i); + ValType retType = returns.get(i); + if (retType.isObjectRef()) { + asm.load(crSlot, OBJECT_TYPE); + asm.iconst(i); + asm.invokevirtual( + "run/endive/runtime/CallResult", + "refResult", + "(I)Ljava/lang/Object;", + false); + } else { + asm.load(crSlot, OBJECT_TYPE); + asm.iconst(i); + asm.invokevirtual( + "run/endive/runtime/CallResult", "longResult", "(I)J", false); + asm.invokestatic("java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false); + } + asm.astore(OBJECT_TYPE); + } + // Object[] left on stack — caller's emitUnboxResult will handle it + } + } else { + // Use standard resolveTailCall which returns long[] + asm.load(ctx.instanceSlot(), OBJECT_TYPE); + emitInvokeStatic(asm, ShadedRefs.RESOLVE_TAIL_CALL); + + if (returns.isEmpty()) { + asm.pop(); + } else if (returns.size() == 1) { + asm.iconst(0); + asm.aload(LONG_TYPE); + emitLongToJvm(asm, returns.get(0)); + } + // For multi-value: leave long[] on stack — caller's emitUnboxResult will handle it } - // For multi-value: leave long[] on stack — caller's emitUnboxResult will handle it asm.mark(noPending); } @@ -1268,16 +1423,34 @@ private static void emitUnboxResult(InstructionAdapter asm, Context ctx, List types, int tempSlot) { + boolean hasObjectRefs = types.stream().anyMatch(ValType::isObjectRef); asm.store(tempSlot, OBJECT_TYPE); - for (int i = 0; i < types.size(); i++) { - asm.load(tempSlot, OBJECT_TYPE); - asm.iconst(i); - asm.aload(LONG_TYPE); - emitLongToJvm(asm, types.get(i)); + + if (hasObjectRefs) { + // Object[] on stack: numerics are boxed as Long, refs are Object + for (int i = 0; i < types.size(); i++) { + ValType type = types.get(i); + asm.load(tempSlot, OBJECT_TYPE); + asm.iconst(i); + asm.aload(OBJECT_TYPE); // AALOAD from Object[] + if (type.isObjectRef()) { + // Already the right type (Object) + } else { + // Unbox Long -> long -> JVM type + asm.checkcast(org.objectweb.asm.Type.getType(Long.class)); + asm.invokevirtual("java/lang/Long", "longValue", "()J", false); + emitLongToJvm(asm, type); + } + } + } else { + // long[] on stack: standard path + for (int i = 0; i < types.size(); i++) { + asm.load(tempSlot, OBJECT_TYPE); + asm.iconst(i); + asm.aload(LONG_TYPE); + emitLongToJvm(asm, types.get(i)); + } } } 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 056496868..1c90bfaf9 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/Shaded.java +++ b/compiler/src/main/java/run/endive/compiler/internal/Shaded.java @@ -4,6 +4,7 @@ import static run.endive.wasm.types.Value.REF_NULL_VALUE; import java.util.Arrays; +import run.endive.runtime.CallResult; import run.endive.runtime.ConstantEvaluators; import run.endive.runtime.Instance; import run.endive.runtime.MemCopyWorkaround; @@ -42,15 +43,37 @@ public static long[] callIndirect(long[] args, int funcId, Instance instance) { return instance.getMachine().call(funcId, args); } + public static CallResult callIndirectWithRefs( + long[] args, Object[] refArgs, int typeId, int funcId, Instance instance) { + int actualTypeIdx = instance.functionType(funcId); + if (actualTypeIdx != typeId + && !ValType.heapTypeSubtype( + actualTypeIdx, typeId, instance.module().typeSection())) { + throw throwIndirectCallTypeMismatch(); + } + return instance.getMachine().callWithRefs(funcId, args, refArgs); + } + public static long[] callHostFunction(Instance instance, int funcId, long[] args) { var imprt = instance.imports().function(funcId); return imprt.handle().apply(instance, args); } + public static CallResult callHostFunctionWithRefs( + Instance instance, int funcId, long[] args, Object[] refArgs) { + var imprt = instance.imports().function(funcId); + return imprt.handle().applyWithRefs(instance, args, refArgs); + } + public static void setTailCall(int funcId, long[] args, Instance instance) { instance.setTailCall(funcId, args); } + public static void setTailCallWithRefs( + int funcId, long[] args, Object[] refArgs, Instance instance) { + instance.setTailCall(funcId, args, refArgs); + } + public static void setTailCallIndirect( long[] args, int funcTableIdx, int typeId, int tableIdx, Instance instance) { TableInstance table = instance.table(tableIdx); @@ -69,6 +92,29 @@ public static void setTailCallIndirect( instance.setTailCall(funcId, args); } + public static void setTailCallIndirectWithRefs( + long[] args, + Object[] refArgs, + int funcTableIdx, + int typeId, + int tableIdx, + Instance instance) { + TableInstance table = instance.table(tableIdx); + int funcId = table.requiredRef(funcTableIdx); + Instance refInstance = table.instance(funcTableIdx); + if (refInstance != null && refInstance != instance) { + throw new WasmEngineException( + "Indirect tail-call to a different Machine implementation is not supported"); + } + int actualTypeIdx = instance.functionType(funcId); + if (actualTypeIdx != typeId + && !ValType.heapTypeSubtype( + actualTypeIdx, typeId, instance.module().typeSection())) { + throw throwIndirectCallTypeMismatch(); + } + instance.setTailCall(funcId, args, refArgs); + } + public static boolean isTailCallPending(Instance instance) { return instance.isTailCallPending(); } @@ -80,6 +126,14 @@ public static long[] resolveTailCall(Instance instance) { return instance.getMachine().call(funcId, args); } + public static CallResult resolveTailCallWithRefs(Instance instance) { + int funcId = instance.tailCallFuncId(); + long[] args = instance.tailCallArgs(); + Object[] refArgs = instance.tailCallRefArgs(); + instance.clearTailCall(); + return instance.getMachine().callWithRefs(funcId, args, refArgs); + } + public static boolean isRefNull(int ref) { return ref == REF_NULL_VALUE; } 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 00a7f9bcd..2d208df4b 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/ShadedRefs.java +++ b/compiler/src/main/java/run/endive/compiler/internal/ShadedRefs.java @@ -144,9 +144,16 @@ public final class ShadedRefs { // Tail calls static final Method SET_TAIL_CALL; + static final Method SET_TAIL_CALL_WITH_REFS; static final Method SET_TAIL_CALL_INDIRECT; + static final Method SET_TAIL_CALL_INDIRECT_WITH_REFS; static final Method IS_TAIL_CALL_PENDING; static final Method RESOLVE_TAIL_CALL; + static final Method RESOLVE_TAIL_CALL_WITH_REFS; + + // WithRefs overloads for cross-module/host calls + static final Method CALL_HOST_FUNCTION_WITH_REFS; + static final Method CALL_INDIRECT_WITH_REFS; // GC static final Method STRUCT_NEW; @@ -774,6 +781,13 @@ public final class ShadedRefs { // Tail calls SET_TAIL_CALL = Shaded.class.getMethod("setTailCall", int.class, long[].class, Instance.class); + SET_TAIL_CALL_WITH_REFS = + Shaded.class.getMethod( + "setTailCallWithRefs", + int.class, + long[].class, + Object[].class, + Instance.class); SET_TAIL_CALL_INDIRECT = Shaded.class.getMethod( "setTailCallIndirect", @@ -782,8 +796,36 @@ public final class ShadedRefs { int.class, int.class, Instance.class); + SET_TAIL_CALL_INDIRECT_WITH_REFS = + Shaded.class.getMethod( + "setTailCallIndirectWithRefs", + long[].class, + Object[].class, + int.class, + int.class, + int.class, + Instance.class); IS_TAIL_CALL_PENDING = Shaded.class.getMethod("isTailCallPending", Instance.class); RESOLVE_TAIL_CALL = Shaded.class.getMethod("resolveTailCall", Instance.class); + RESOLVE_TAIL_CALL_WITH_REFS = + Shaded.class.getMethod("resolveTailCallWithRefs", Instance.class); + + // WithRefs overloads for cross-module/host calls + CALL_HOST_FUNCTION_WITH_REFS = + Shaded.class.getMethod( + "callHostFunctionWithRefs", + Instance.class, + int.class, + long[].class, + Object[].class); + CALL_INDIRECT_WITH_REFS = + Shaded.class.getMethod( + "callIndirectWithRefs", + long[].class, + Object[].class, + int.class, + int.class, + Instance.class); // GC STRUCT_NEW = 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 6cfc51666..a92f6d8b3 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 @@ -260,9 +260,13 @@ final class run/endive/$gen/CompiledMachineMachineCall { POP POP POP + POP + ALOAD 0 ILOAD 2 ALOAD 3 - INVOKESTATIC run/endive/$gen/CompiledMachineShaded.callHostFunction (Lrun/endive/runtime/Instance;I[J)[J + ALOAD 4 + INVOKESTATIC run/endive/$gen/CompiledMachineShaded.callHostFunctionWithRefs (Lrun/endive/runtime/Instance;I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; + INVOKEVIRTUAL run/endive/runtime/CallResult.longs ()[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 39e895e0d..148ec6147 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 @@ -207,9 +207,13 @@ final class run/endive/$gen/CompiledMachineMachineCall { POP POP POP + POP + ALOAD 0 ILOAD 2 ALOAD 3 - INVOKESTATIC run/endive/$gen/CompiledMachineShaded.callHostFunction (Lrun/endive/runtime/Instance;I[J)[J + ALOAD 4 + INVOKESTATIC run/endive/$gen/CompiledMachineShaded.callHostFunctionWithRefs (Lrun/endive/runtime/Instance;I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; + INVOKEVIRTUAL run/endive/runtime/CallResult.longs ()[J ARETURN L2 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 44fab89e6..cd4258c51 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 @@ -46,7 +46,8 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime ALOAD 4 INVOKEVIRTUAL run/endive/runtime/Instance.tailCallArgs ()[J ASTORE 2 - ACONST_NULL + ALOAD 4 + INVOKEVIRTUAL run/endive/runtime/Instance.tailCallRefArgs ()[Ljava/lang/Object; ASTORE 3 ALOAD 4 INVOKEVIRTUAL run/endive/runtime/Instance.clearTailCall ()V @@ -81,7 +82,8 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime ALOAD 4 INVOKEVIRTUAL run/endive/runtime/Instance.tailCallArgs ()[J ASTORE 2 - ACONST_NULL + ALOAD 4 + INVOKEVIRTUAL run/endive/runtime/Instance.tailCallRefArgs ()[Ljava/lang/Object; ASTORE 3 ALOAD 4 INVOKEVIRTUAL run/endive/runtime/Instance.clearTailCall ()V diff --git a/runtime/src/main/java/run/endive/runtime/Instance.java b/runtime/src/main/java/run/endive/runtime/Instance.java index 763dc3580..d51c9d20b 100644 --- a/runtime/src/main/java/run/endive/runtime/Instance.java +++ b/runtime/src/main/java/run/endive/runtime/Instance.java @@ -79,10 +79,18 @@ public class Instance { static final class TailCallPending { final int funcId; final long[] args; + final Object[] refArgs; TailCallPending(int funcId, long[] args) { this.funcId = funcId; this.args = args; + this.refArgs = null; + } + + TailCallPending(int funcId, long[] args, Object[] refArgs) { + this.funcId = funcId; + this.args = args; + this.refArgs = refArgs; } } @@ -551,10 +559,18 @@ public long[] tailCallArgs() { return tailCallPending.args; } + public Object[] tailCallRefArgs() { + return tailCallPending.refArgs; + } + public void setTailCall(int funcId, long[] args) { this.tailCallPending = new TailCallPending(funcId, args); } + public void setTailCall(int funcId, long[] args, Object[] refArgs) { + this.tailCallPending = new TailCallPending(funcId, args, refArgs); + } + public void clearTailCall() { this.tailCallPending = null; } From 02c155ee87922c784b8016dd27ef1816cd5bd8b4 Mon Sep 17 00:00:00 2001 From: andreatp Date: Mon, 15 Jun 2026 15:11:22 +0100 Subject: [PATCH 12/20] feat: annotation processor supports externref as Object Update ModuleInterfaceCodegen to generate applyWithRefs calls for functions with externref params/returns. externref maps to Object.class (not long.class). Export codegen: builds positional long[] + Object[] arrays, calls applyWithRefs, reads from CallResult. Non-ref functions use the original apply() path unchanged. Import codegen: generates WasmFunctionHandle with applyWithRefs override for functions with Object ref types. ExternRefExampleTest: host functions use Object for externref. --- .../endive/test/ExternRefExampleTest.java | 18 +- .../codegen/ModuleInterfaceCodegen.java | 464 ++++++++++++++---- 2 files changed, 384 insertions(+), 98 deletions(-) diff --git a/annotations/it/src/it/externref-base/src/test/java/endive/test/ExternRefExampleTest.java b/annotations/it/src/it/externref-base/src/test/java/endive/test/ExternRefExampleTest.java index 49b57d3c2..9af91ff72 100644 --- a/annotations/it/src/it/externref-base/src/test/java/endive/test/ExternRefExampleTest.java +++ b/annotations/it/src/it/externref-base/src/test/java/endive/test/ExternRefExampleTest.java @@ -1,6 +1,7 @@ package endive.test; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import org.junit.jupiter.api.Test; import run.endive.annotations.WasmModuleInterface; @@ -34,16 +35,13 @@ public TestModule_ModuleExports exports() { private Object sampleObj = null; - public long getHostObject() { + public Object getHostObject() { sampleObj = new Object(); - return 123; + return sampleObj; } - public int isNull(long arg0) { - if (arg0 != 123) { - throw new RuntimeException("unrecognized external ref"); - } - return (sampleObj == null) ? 1 : 0; + public int isNull(Object arg0) { + return (arg0 == null) ? 1 : 0; } } @@ -51,11 +49,11 @@ public int isNull(long arg0) { public void testExternRef() { var module = new TestModule(); - assertEquals(1, module.exports().isNull(123L)); + assertEquals(1, module.exports().isNull(null)); var hostObj = module.exports().getHostObject(); - assertEquals(123, hostObj); + assertNotNull(hostObj); - assertEquals(0, module.exports().isNull(123L)); + assertEquals(0, module.exports().isNull(hostObj)); } } diff --git a/codegen/src/main/java/run/endive/codegen/ModuleInterfaceCodegen.java b/codegen/src/main/java/run/endive/codegen/ModuleInterfaceCodegen.java index b9c41c04a..17965c96d 100644 --- a/codegen/src/main/java/run/endive/codegen/ModuleInterfaceCodegen.java +++ b/codegen/src/main/java/run/endive/codegen/ModuleInterfaceCodegen.java @@ -7,6 +7,8 @@ import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.Modifier; import com.github.javaparser.ast.NodeList; +import com.github.javaparser.ast.body.BodyDeclaration; +import com.github.javaparser.ast.body.MethodDeclaration; import com.github.javaparser.ast.body.Parameter; import com.github.javaparser.ast.expr.ArrayAccessExpr; import com.github.javaparser.ast.expr.ArrayCreationExpr; @@ -26,6 +28,7 @@ import com.github.javaparser.ast.expr.VariableDeclarationExpr; import com.github.javaparser.ast.stmt.BlockStmt; import com.github.javaparser.ast.stmt.ReturnStmt; +import com.github.javaparser.ast.stmt.ThrowStmt; import com.github.javaparser.ast.type.Type; import java.util.ArrayList; import java.util.HashMap; @@ -215,17 +218,16 @@ public Map generate() { : functionImports[export.index()].typeIndex(); var exportType = module.typeSection().getType(funcType); + boolean hasObjectRefs = + exportType.params().stream().anyMatch(ValType::isObjectRef) + || exportType.returns().stream().anyMatch(ValType::isObjectRef); + var argPrefix = "arg"; - var handleCallArguments = new ArrayList(); for (var pIdx = 0; pIdx < exportType.params().size(); pIdx++) { var param = exportType.params().get(pIdx); var argName = argPrefix + pIdx; var javaType = javaClassFromValueType(param); - // signature exportMethod.addParameter(javaType, argName); - // body invocation call arguments - var argExpr = new NameExpr(argName); - handleCallArguments.add(toLong(param, argExpr, exportsCu)); } var methodBody = exportMethod.createBody(); @@ -240,29 +242,134 @@ public Map generate() { new AssignExpr( exportFieldName, exportCall, AssignExpr.Operator.ASSIGN)); - var exportApplyHandle = - new MethodCallExpr( - exportFieldName, "apply", NodeList.nodeList(handleCallArguments)); - - if (exportType.returns().size() == 0) { - exportMethod.setType(void.class); - methodBody.addStatement(exportApplyHandle).addStatement(new ReturnStmt()); - } else if (exportType.returns().size() > 1) { - exportMethod.setType(long[].class); - methodBody.addStatement(new ReturnStmt(exportApplyHandle)); + if (hasObjectRefs) { + exportsCu.addImport("run.endive.runtime.CallResult"); + + // Build positional long[] args and Object[] refArgs arrays. + // Both have one slot per param; ref values in refArgs[i], + // non-ref values in args[i]. + int paramCount = exportType.params().size(); + Expression longArrayExpr; + Expression refArrayExpr; + if (paramCount == 0) { + longArrayExpr = + new ArrayCreationExpr( + parseType("long"), + NodeList.nodeList( + new ArrayCreationLevel(new IntegerLiteralExpr("0"))), + null); + refArrayExpr = + new ArrayCreationExpr( + parseType("Object"), + NodeList.nodeList( + new ArrayCreationLevel(new IntegerLiteralExpr("0"))), + null); + } else { + var longSlots = new ArrayList(); + var refSlots = new ArrayList(); + for (var pIdx = 0; pIdx < paramCount; pIdx++) { + var param = exportType.params().get(pIdx); + var argExpr = new NameExpr(argPrefix + pIdx); + if (param.isObjectRef()) { + longSlots.add(new IntegerLiteralExpr("0")); + refSlots.add(argExpr); + } else { + longSlots.add(toLong(param, argExpr, exportsCu)); + refSlots.add(new NullLiteralExpr()); + } + } + longArrayExpr = + new ArrayCreationExpr( + parseType("long"), + NodeList.nodeList(new ArrayCreationLevel()), + new ArrayInitializerExpr(NodeList.nodeList(longSlots))); + refArrayExpr = + new ArrayCreationExpr( + parseType("Object"), + NodeList.nodeList(new ArrayCreationLevel()), + new ArrayInitializerExpr(NodeList.nodeList(refSlots))); + } + + var applyWithRefsCall = + new MethodCallExpr( + exportFieldName, + "applyWithRefs", + NodeList.nodeList(longArrayExpr, refArrayExpr)); + + if (exportType.returns().size() == 0) { + exportMethod.setType(void.class); + methodBody.addStatement(applyWithRefsCall); + methodBody.addStatement(new ReturnStmt()); + } else { + // Declare CallResult cr = field.applyWithRefs(...) + methodBody.addStatement( + new AssignExpr( + new VariableDeclarationExpr(parseType("CallResult"), "cr"), + applyWithRefsCall, + AssignExpr.Operator.ASSIGN)); + + if (exportType.returns().size() == 1) { + var retType = exportType.returns().get(0); + exportMethod.setType(javaClassFromValueType(retType)); + if (retType.isObjectRef()) { + // return cr.refResult(0); + methodBody.addStatement( + new ReturnStmt( + new MethodCallExpr( + new NameExpr("cr"), + "refResult", + NodeList.nodeList( + new IntegerLiteralExpr("0"))))); + } else { + // return fromLong(cr.longResult(0)); + var longResult = + new MethodCallExpr( + new NameExpr("cr"), + "longResult", + NodeList.nodeList(new IntegerLiteralExpr("0"))); + methodBody.addStatement( + new ReturnStmt(fromLong(retType, longResult, exportsCu))); + } + } else { + // Multi-return with refs: return the CallResult directly + exportMethod.setType(parseType("CallResult")); + methodBody.addStatement(new ReturnStmt(new NameExpr("cr"))); + } + } } else { - exportMethod.setType(javaClassFromValueType(exportType.returns().get(0))); - methodBody.addStatement( - new AssignExpr( - new VariableDeclarationExpr(parseType("long"), "result"), - new ArrayAccessExpr(exportApplyHandle, new IntegerLiteralExpr("0")), - AssignExpr.Operator.ASSIGN)); - methodBody.addStatement( - new ReturnStmt( - fromLong( - exportType.returns().get(0), - new NameExpr("result"), - exportsCu))); + // No object refs - use the original apply() path + var handleCallArguments = new ArrayList(); + for (var pIdx = 0; pIdx < exportType.params().size(); pIdx++) { + var param = exportType.params().get(pIdx); + var argExpr = new NameExpr(argPrefix + pIdx); + handleCallArguments.add(toLong(param, argExpr, exportsCu)); + } + + var exportApplyHandle = + new MethodCallExpr( + exportFieldName, "apply", NodeList.nodeList(handleCallArguments)); + + if (exportType.returns().size() == 0) { + exportMethod.setType(void.class); + methodBody.addStatement(exportApplyHandle).addStatement(new ReturnStmt()); + } else if (exportType.returns().size() > 1) { + exportMethod.setType(long[].class); + methodBody.addStatement(new ReturnStmt(exportApplyHandle)); + } else { + exportMethod.setType(javaClassFromValueType(exportType.returns().get(0))); + methodBody.addStatement( + new AssignExpr( + new VariableDeclarationExpr(parseType("long"), "result"), + new ArrayAccessExpr( + exportApplyHandle, new IntegerLiteralExpr("0")), + AssignExpr.Operator.ASSIGN)); + methodBody.addStatement( + new ReturnStmt( + fromLong( + exportType.returns().get(0), + new NameExpr("result"), + exportsCu))); + } } } @@ -386,20 +493,32 @@ public Map generate() { .getType(((FunctionImport) importedFun).typeIndex()); importMethod.removeBody(); - // build lambda return - var functionBodyStatement = new BlockStmt(); + boolean importHasObjectRefs = + importType.params().stream().anyMatch(ValType::isObjectRef) + || importType.returns().stream() + .anyMatch(ValType::isObjectRef); + // Build the call to the user's Java method with proper + // argument extraction List parameters = new ArrayList<>(); for (int i = 0; i < importType.params().size(); i++) { var p = importType.params().get(i); - - parameters.add( - fromLong( - p, - new ArrayAccessExpr( - new NameExpr("args"), - new IntegerLiteralExpr(Integer.toString(i))), - importsCu)); + if (importHasObjectRefs && p.isObjectRef()) { + // Read from refArgs[i] + parameters.add( + new ArrayAccessExpr( + new NameExpr("refArgs"), + new IntegerLiteralExpr(Integer.toString(i)))); + } else { + parameters.add( + fromLong( + p, + new ArrayAccessExpr( + new NameExpr("args"), + new IntegerLiteralExpr( + Integer.toString(i))), + importsCu)); + } } var importApplyHandle = @@ -411,53 +530,228 @@ public Map generate() { importedFun.name(), false), NodeList.nodeList(parameters)); - if (importType.returns().size() == 0) { - importMethod.setType(void.class); - functionBodyStatement.addStatement(importApplyHandle); - functionBodyStatement.addStatement( - new ReturnStmt(new NullLiteralExpr())); - } else if (importType.returns().size() == 1) { - importMethod.setType( - javaClassFromValueType(importType.returns().get(0))); - functionBodyStatement.addStatement( - new ReturnStmt( - new ArrayCreationExpr( - parseType("long"), - NodeList.nodeList(new ArrayCreationLevel()), - new ArrayInitializerExpr( + Expression importedHostFunctionBinding; + + if (importHasObjectRefs) { + importsCu.addImport("run.endive.runtime.CallResult"); + importsCu.addImport("run.endive.runtime.WasmFunctionHandle"); + + // Set interface method return type + if (importType.returns().size() == 0) { + importMethod.setType(void.class); + } else if (importType.returns().size() == 1) { + importMethod.setType( + javaClassFromValueType(importType.returns().get(0))); + } else { + importMethod.setType(long[].class); + } + + // Build applyWithRefs body + var refsBody = new BlockStmt(); + if (importType.returns().size() == 0) { + refsBody.addStatement(importApplyHandle); + refsBody.addStatement( + new ReturnStmt( + new ObjectCreationExpr( + null, + parseClassOrInterfaceType("CallResult"), + NodeList.nodeList( + new NullLiteralExpr(), + new NullLiteralExpr())))); + } else { + // Build CallResult with positional long[] and Object[] + var longSlots = new ArrayList(); + var refSlots = new ArrayList(); + boolean hasRefReturn = false; + for (int ri = 0; ri < importType.returns().size(); ri++) { + var retType = importType.returns().get(ri); + if (retType.isObjectRef()) { + hasRefReturn = true; + longSlots.add(new IntegerLiteralExpr("0")); + // placeholder, will be set after + refSlots.add(new NullLiteralExpr()); + } else { + longSlots.add(new IntegerLiteralExpr("0")); + refSlots.add(new NullLiteralExpr()); + } + } + + if (importType.returns().size() == 1) { + var retType = importType.returns().get(0); + if (retType.isObjectRef()) { + // Object result = env().method(...) + // return new CallResult(null, new Object[]{ result }) + refsBody.addStatement( + new AssignExpr( + new VariableDeclarationExpr( + parseType("Object"), "result"), + importApplyHandle, + AssignExpr.Operator.ASSIGN)); + refsBody.addStatement( + new ReturnStmt( + new ObjectCreationExpr( + null, + parseClassOrInterfaceType( + "CallResult"), + NodeList.nodeList( + new NullLiteralExpr(), + new ArrayCreationExpr( + parseType("Object"), + NodeList.nodeList( + new ArrayCreationLevel()), + new ArrayInitializerExpr( + NodeList + .nodeList( + new NameExpr( + "result")))))))); + } else { + // non-ref single return in a function that has + // ref params + refsBody.addStatement( + new ReturnStmt( + new ObjectCreationExpr( + null, + parseClassOrInterfaceType( + "CallResult"), + NodeList.nodeList( + new ArrayCreationExpr( + parseType("long"), + NodeList.nodeList( + new ArrayCreationLevel()), + new ArrayInitializerExpr( + NodeList + .nodeList( + toLong( + retType, + importApplyHandle, + importsCu)))), + new NullLiteralExpr())))); + } + } else { + // Multi-return: not common with externref, but handle + refsBody.addStatement( + new ReturnStmt( + new ObjectCreationExpr( + null, + parseClassOrInterfaceType("CallResult"), NodeList.nodeList( - toLong( - importType - .returns() - .get(0), - importApplyHandle, - importsCu)))))); + new NullLiteralExpr(), + new NullLiteralExpr())))); + } + } + + // Build apply() that throws UnsupportedOperationException + var applyMethod = new MethodDeclaration(); + applyMethod.setPublic(true); + applyMethod.setType(long[].class); + applyMethod.setName("apply"); + applyMethod.addParameter( + new Parameter(parseType("Instance"), "instance")); + applyMethod.addParameter( + new Parameter(parseType("long"), "args").setVarArgs(true)); + var applyBody = new BlockStmt(); + applyBody.addStatement( + new ThrowStmt( + new ObjectCreationExpr( + null, + parseClassOrInterfaceType( + "UnsupportedOperationException"), + NodeList.nodeList( + new StringLiteralExpr( + "Use applyWithRefs for" + + " externref" + + " functions"))))); + applyMethod.setBody(applyBody); + + // Build applyWithRefs() override + var applyWithRefsMethod = new MethodDeclaration(); + applyWithRefsMethod.setPublic(true); + applyWithRefsMethod.setType(parseType("CallResult")); + applyWithRefsMethod.setName("applyWithRefs"); + applyWithRefsMethod.addParameter( + new Parameter(parseType("Instance"), "instance")); + applyWithRefsMethod.addParameter( + new Parameter(parseType("long[]"), "args")); + applyWithRefsMethod.addParameter( + new Parameter(parseType("Object[]"), "refArgs")); + applyWithRefsMethod.setBody(refsBody); + + // Create anonymous WasmFunctionHandle + var anonHandle = + new ObjectCreationExpr( + null, + parseClassOrInterfaceType("WasmFunctionHandle"), + NodeList.nodeList()); + NodeList> anonBody = new NodeList<>(); + anonBody.add(applyMethod); + anonBody.add(applyWithRefsMethod); + anonHandle.setAnonymousClassBody(anonBody); + + importedHostFunctionBinding = + new ObjectCreationExpr( + null, + parseClassOrInterfaceType("HostFunction"), + NodeList.nodeList( + new StringLiteralExpr(imprt.getKey()), + new StringLiteralExpr(importedFun.name()), + listOfValueTypes(importType.params()), + listOfValueTypes(importType.returns()), + anonHandle)); } else { - importMethod.setType(long[].class); - functionBodyStatement.addStatement(new ReturnStmt(importApplyHandle)); + // No object refs - use original lambda path + var functionBodyStatement = new BlockStmt(); + + if (importType.returns().size() == 0) { + importMethod.setType(void.class); + functionBodyStatement.addStatement(importApplyHandle); + functionBodyStatement.addStatement( + new ReturnStmt(new NullLiteralExpr())); + } else if (importType.returns().size() == 1) { + importMethod.setType( + javaClassFromValueType(importType.returns().get(0))); + functionBodyStatement.addStatement( + new ReturnStmt( + new ArrayCreationExpr( + parseType("long"), + NodeList.nodeList(new ArrayCreationLevel()), + new ArrayInitializerExpr( + NodeList.nodeList( + toLong( + importType + .returns() + .get(0), + importApplyHandle, + importsCu)))))); + } else { + importMethod.setType(long[].class); + functionBodyStatement.addStatement( + new ReturnStmt(importApplyHandle)); + } + + importedHostFunctionBinding = + new ObjectCreationExpr( + null, + parseClassOrInterfaceType("HostFunction"), + NodeList.nodeList( + new StringLiteralExpr(imprt.getKey()), + new StringLiteralExpr(importedFun.name()), + listOfValueTypes(importType.params()), + listOfValueTypes(importType.returns()), + // (Instance instance, long... args) -> null; + new LambdaExpr( + NodeList.nodeList( + new Parameter( + parseType("Instance"), + new SimpleName( + "instance")), + new Parameter( + parseType( + "long"), + "args") + .setVarArgs(true)), + functionBodyStatement))); } - var importedHostFunctionBinding = - new ObjectCreationExpr( - null, - parseClassOrInterfaceType("HostFunction"), - NodeList.nodeList( - new StringLiteralExpr(imprt.getKey()), - new StringLiteralExpr(importedFun.name()), - listOfValueTypes(importType.params()), - listOfValueTypes(importType.returns()), - // (Instance instance, long... args) -> null; - new LambdaExpr( - NodeList.nodeList( - new Parameter( - parseType("Instance"), - new SimpleName("instance")), - new Parameter( - parseType("long"), - "args") - .setVarArgs(true)), - functionBodyStatement))); - toImportValuesBody.addStatement( new MethodCallExpr( new NameExpr("imports"), @@ -516,8 +810,8 @@ static Class javaClassFromValueType(ValType type) { case ValType.ID.F64: return double.class; default: - if (ValType.TypeIdxCode.EXTERN.code() == type.typeIdx()) { - return long.class; + if (type.isObjectRef()) { + return Object.class; } throw new IllegalArgumentException( "javaClassFromValueType - Unsupported WASM type: " + type); @@ -539,9 +833,6 @@ static Expression toLong(ValType type, Expression nameExpr, CompilationUnit cu) return new MethodCallExpr( new NameExpr("Value"), "doubleToLong", new NodeList<>(nameExpr)); default: - if (ValType.TypeIdxCode.EXTERN.code() == type.typeIdx()) { - return nameExpr; - } throw new IllegalArgumentException("toLong - Unsupported WASM type: " + type); } } @@ -561,9 +852,6 @@ static Expression fromLong(ValType type, Expression nameExpr, CompilationUnit cu return new MethodCallExpr( new NameExpr("Value"), "longToDouble", new NodeList<>(nameExpr)); default: - if (ValType.TypeIdxCode.EXTERN.code() == type.typeIdx()) { - return nameExpr; - } throw new IllegalArgumentException("fromLong - Unsupported WASM type: " + type); } } From b109e44693c8a663899c28c5dff760abb6e2a5b4 Mon Sep 17 00:00:00 2001 From: andreatp Date: Mon, 15 Jun 2026 18:48:14 +0100 Subject: [PATCH 13/20] =?UTF-8?q?fix:=20review=20findings=20=E2=80=94=20pe?= =?UTF-8?q?rf,=20multi-value,=20cleanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Performance: - FunctionType.hasObjectRefParams/Returns() — replaces all stream().anyMatch() calls with method on FunctionType - CompilerInterpreterMachine only allocated when module has interpreted functions or Object ref types Correctness: - Multi-value returns with GC refs: compileCallFunction converts Object[] to long[] for the call_xxx bridge method type - Interpreted fallback: uses WithRefs path for Object ref functions - Dead bytecodes removed from emitUnboxResult Tests: - GcMultiValueTest: 5 multi-value return interpolations (int+ref, ref+int, two refs, two ints+ref) on both engines Javadoc: CallResult, applyWithRefs, callWithRefs --- .../endive/compiler/internal/Compiler.java | 167 +++++++++++++----- .../endive/compiler/internal/Emitters.java | 16 +- .../run/endive/compiler/internal/Shaded.java | 5 + .../endive/compiler/internal/ShadedRefs.java | 8 + .../ApprovalTest.functions10.approved.txt | 22 ++- .../ApprovalTest.verifyBrTable.approved.txt | 22 ++- .../ApprovalTest.verifyBranching.approved.txt | 22 ++- .../ApprovalTest.verifyFloat.approved.txt | 22 ++- .../ApprovalTest.verifyHelloWasi.approved.txt | 22 ++- .../ApprovalTest.verifyI32.approved.txt | 22 ++- ...ApprovalTest.verifyI32Renamed.approved.txt | 22 ++- .../ApprovalTest.verifyIterFact.approved.txt | 22 ++- ...pprovalTest.verifyKitchenSink.approved.txt | 22 ++- .../ApprovalTest.verifyMemory.approved.txt | 22 ++- .../ApprovalTest.verifyStart.approved.txt | 22 ++- .../ApprovalTest.verifyTailCall.approved.txt | 22 ++- .../ApprovalTest.verifyTrap.approved.txt | 22 ++- .../run/endive/testing/GcMultiValueTest.java | 110 ++++++++++++ .../java/run/endive/runtime/CallResult.java | 1 + .../run/endive/runtime/ExportFunction.java | 1 + .../java/run/endive/runtime/Instance.java | 4 +- .../endive/runtime/InterpreterMachine.java | 6 +- .../main/java/run/endive/runtime/Machine.java | 1 + .../endive/runtime/WasmFunctionHandle.java | 4 +- .../internal/CompilerInterpreterMachine.java | 2 +- .../resources/compiled/gc_multivalue.wat.wasm | Bin 0 -> 223 bytes .../src/main/resources/wat/gc_multivalue.wat | 44 +++++ .../run/endive/wasm/types/FunctionType.java | 8 + 28 files changed, 492 insertions(+), 171 deletions(-) create mode 100644 machine-tests/src/test/java/run/endive/testing/GcMultiValueTest.java create mode 100644 wasm-corpus/src/main/resources/compiled/gc_multivalue.wat.wasm create mode 100644 wasm-corpus/src/main/resources/wat/gc_multivalue.wat 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 a77287581..1415621b6 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/Compiler.java +++ b/compiler/src/main/java/run/endive/compiler/internal/Compiler.java @@ -43,6 +43,7 @@ import static run.endive.compiler.internal.ShadedRefs.CALL_HOST_FUNCTION_WITH_REFS; import static run.endive.compiler.internal.ShadedRefs.CALL_INDIRECT; import static run.endive.compiler.internal.ShadedRefs.CALL_INDIRECT_ON_INTERPRETER; +import static run.endive.compiler.internal.ShadedRefs.CALL_INDIRECT_ON_INTERPRETER_WITH_REFS; import static run.endive.compiler.internal.ShadedRefs.CALL_INDIRECT_WITH_REFS; import static run.endive.compiler.internal.ShadedRefs.CHECK_INTERRUPTION; import static run.endive.compiler.internal.ShadedRefs.INSTANCE_MEMORY; @@ -139,6 +140,7 @@ public final class Compiler { private final boolean[] tailCallFunctions; private final boolean[] tailCallTypes; private final boolean moduleHasTailCalls; + private final boolean moduleHasObjectRefs; private boolean useBridgeClasses; private IntFunction callIndirectClassResolver; @@ -176,6 +178,9 @@ private Compiler( this.tailCallFunctions = analyzer.tailCallFunctions(); this.tailCallTypes = analyzer.tailCallTypes(); this.moduleHasTailCalls = analyzer.hasTailCalls(); + this.moduleHasObjectRefs = + this.functionTypes.stream() + .anyMatch(ft -> ft.hasObjectRefParams() || ft.hasObjectRefReturns()); this.maxFunctionsPerClass = maxFunctionsPerClass; } @@ -579,6 +584,37 @@ private byte[] compileClass() { internalClassName, "compilerInterpreterMachine", getDescriptor(CompilerInterpreterMachine.class)); + // null check: if compilerInterpreterMachine is null, + // fall back to wrapping call() result in CallResult + asm.dup(); + Label hasField = new Label(); + asm.ifnonnull(hasField); + asm.pop(); // pop the null + // return new CallResult(this.call(funcId, args, refArgs), null) + asm.anew(Type.getType(CallResult.class)); + asm.dup(); + asm.load(0, OBJECT_TYPE); + asm.load(1, INT_TYPE); + asm.load(2, OBJECT_TYPE); + asm.load(3, OBJECT_TYPE); + asm.invokevirtual( + internalClassName, + "call", + Type.getMethodDescriptor( + LONG_ARRAY_TYPE, + INT_TYPE, + LONG_ARRAY_TYPE, + Type.getType(Object[].class)), + false); + asm.aconst(null); + asm.invokespecial( + Type.getInternalName(CallResult.class), + "", + Type.getMethodDescriptor( + VOID_TYPE, LONG_ARRAY_TYPE, Type.getType(Object[].class)), + false); + asm.areturn(OBJECT_TYPE); + asm.mark(hasField); asm.load(1, INT_TYPE); asm.load(2, OBJECT_TYPE); asm.load(3, OBJECT_TYPE); @@ -725,32 +761,36 @@ private void compileConstructor(InstructionAdapter asm, String internalClassName asm.load(1, OBJECT_TYPE); asm.putfield(internalClassName, "instance", getDescriptor(Instance.class)); - // Always create compilerInterpreterMachine for callWithRefs support - asm.load(0, OBJECT_TYPE); - asm.anew(AOT_INTERPRETER_MACHINE_TYPE); - asm.dup(); - asm.load(1, OBJECT_TYPE); - - // construct int[] with the interpreted function ids - var funcIds = new ArrayList<>(interpretedFunctions); - asm.iconst(funcIds.size()); - asm.newarray(INT_TYPE); - for (int i = 0; i < funcIds.size(); i++) { + // Only create compilerInterpreterMachine when needed: + // - interpreted functions need it for fallback execution + // - modules with Object refs need it for callWithRefs support + if (!interpretedFunctions.isEmpty() || moduleHasObjectRefs) { + asm.load(0, OBJECT_TYPE); + asm.anew(AOT_INTERPRETER_MACHINE_TYPE); asm.dup(); - asm.iconst(i); - asm.iconst(funcIds.get(i)); - asm.astore(INT_TYPE); - } + asm.load(1, OBJECT_TYPE); - asm.invokespecial( - AOT_INTERPRETER_MACHINE_TYPE.getInternalName(), - "", - getMethodDescriptor(VOID_TYPE, INSTANCE_TYPE, INT_ARRAY_TYPE), - false); - asm.putfield( - internalClassName, - "compilerInterpreterMachine", - getDescriptor(CompilerInterpreterMachine.class)); + // construct int[] with the interpreted function ids + var funcIds = new ArrayList<>(interpretedFunctions); + asm.iconst(funcIds.size()); + asm.newarray(INT_TYPE); + for (int i = 0; i < funcIds.size(); i++) { + asm.dup(); + asm.iconst(i); + asm.iconst(funcIds.get(i)); + asm.astore(INT_TYPE); + } + + asm.invokespecial( + AOT_INTERPRETER_MACHINE_TYPE.getInternalName(), + "", + getMethodDescriptor(VOID_TYPE, INSTANCE_TYPE, INT_ARRAY_TYPE), + false); + asm.putfield( + internalClassName, + "compilerInterpreterMachine", + getDescriptor(CompilerInterpreterMachine.class)); + } asm.areturn(VOID_TYPE); } @@ -1162,7 +1202,30 @@ private void compileCallFunction(int funcId, FunctionType type, InstructionAdapt asm.load(4, LONG_TYPE); asm.astore(LONG_TYPE); } - // For long[] or Object[] multi-value returns, leave as-is (TODO: handle Object[] returns) + if (returnType == Object[].class) { + // Multi-value with GC refs: the internal function returns Object[], + // but the bridge must return long[]. Convert: longs for numerics, 0L for refs. + int objArraySlot = 4; + asm.store(objArraySlot, OBJECT_TYPE); // save Object[] + asm.iconst(type.returns().size()); + asm.newarray(LONG_TYPE); // create long[] + for (int i = 0; i < type.returns().size(); i++) { + var retType = type.returns().get(i); + asm.dup(); + asm.iconst(i); + if (retType.isObjectRef()) { + asm.lconst(0L); + } else { + asm.load(objArraySlot, OBJECT_TYPE); + asm.iconst(i); + asm.aload(OBJECT_TYPE); // get from Object[] + asm.checkcast(Type.getType(Long.class)); + asm.invokevirtual("java/lang/Long", "longValue", "()J", false); + } + asm.astore(LONG_TYPE); + } + } + // For long[] multi-value returns, leave as-is asm.areturn(OBJECT_TYPE); } @@ -1340,10 +1403,7 @@ private void compileCallIndirect( // other: call function in another module asm.mark(other); - boolean hasObjectRefParams = type.params().stream().anyMatch(ValType::isObjectRef); - boolean hasObjectRefReturns = type.returns().stream().anyMatch(ValType::isObjectRef); - - if (hasObjectRefParams || hasObjectRefReturns) { + if (type.hasObjectRefParams() || type.hasObjectRefReturns()) { if (hasTooManyParameters(type)) { asm.load(0, LONG_ARRAY_TYPE); asm.aconst(null); // Object[] refArgs @@ -1437,10 +1497,7 @@ private void compileCallIndirectApply( private static void compileHostFunction(int funcId, FunctionType type, InstructionAdapter asm) { int slot = type.params().stream().mapToInt(CompilerUtil::slotCount).sum(); - boolean hasObjectRefParams = type.params().stream().anyMatch(ValType::isObjectRef); - boolean hasObjectRefReturns = type.returns().stream().anyMatch(ValType::isObjectRef); - - if (hasObjectRefParams || hasObjectRefReturns) { + if (type.hasObjectRefParams() || type.hasObjectRefReturns()) { asm.load(slot + 1, OBJECT_TYPE); // instance asm.iconst(funcId); emitBoxArgumentsWithRefs(asm, type.params()); @@ -1560,10 +1617,7 @@ private static void emitUnboxResult(FunctionType type, InstructionAdapter asm) { // Single Object return from cross-module call that returns long[] // The Object result is in the long[] as 0, can't extract it // Callers with Object refs should use emitUnboxCallResult instead - asm.iconst(0); - asm.aload(LONG_TYPE); - asm.visitInsn(Opcodes.L2I); - asm.visitInsn(Opcodes.POP); + asm.visitInsn(Opcodes.POP); // pop the long[] result asm.aconst(null); asm.areturn(OBJECT_TYPE); } else { @@ -1644,19 +1698,36 @@ private void compileFunction( if (interpretedFunctions.contains(funcId)) { var slots = 0; - if (hasTooManyParameters(type)) { - asm.load(0, LONG_ARRAY_TYPE); - slots = 1; + if (type.hasObjectRefParams() || type.hasObjectRefReturns()) { + // WithRefs path: use dual long[] + Object[] boxing + if (hasTooManyParameters(type)) { + asm.load(0, LONG_ARRAY_TYPE); + asm.aconst(null); // Object[] refArgs + slots = 1; + } else { + emitBoxArgumentsWithRefs(asm, type.params()); + slots = type.params().stream().mapToInt(CompilerUtil::slotCount).sum(); + } + var refInstance = slots + 1; + asm.iconst(funcId); + asm.load(refInstance, OBJECT_TYPE); + emitInvokeStatic(asm, CALL_INDIRECT_ON_INTERPRETER_WITH_REFS); + // returns CallResult + emitUnboxCallResult(type, asm); } else { - emitBoxArguments(asm, type.params()); - slots = type.params().stream().mapToInt(CompilerUtil::slotCount).sum(); + if (hasTooManyParameters(type)) { + asm.load(0, LONG_ARRAY_TYPE); + slots = 1; + } else { + emitBoxArguments(asm, type.params()); + slots = type.params().stream().mapToInt(CompilerUtil::slotCount).sum(); + } + var refInstance = slots + 1; + asm.iconst(funcId); + asm.load(refInstance, OBJECT_TYPE); + emitInvokeStatic(asm, CALL_INDIRECT_ON_INTERPRETER); + emitUnboxResult(type, asm); } - var refInstance = slots + 1; - - asm.iconst(funcId); - asm.load(refInstance, OBJECT_TYPE); - emitInvokeStatic(asm, CALL_INDIRECT_ON_INTERPRETER); - emitUnboxResult(type, asm); return; } 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 766df5c37..ad6bcec5a 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/Emitters.java +++ b/compiler/src/main/java/run/endive/compiler/internal/Emitters.java @@ -1219,9 +1219,8 @@ private static void emitLoadOrStore( public static void RETURN_CALL(Context ctx, CompilerInstruction ins, InstructionAdapter asm) { int funcId = (int) ins.operand(0); FunctionType calleeType = ctx.functionTypes().get(funcId); - boolean hasObjectRefParams = calleeType.params().stream().anyMatch(ValType::isObjectRef); - if (hasObjectRefParams) { + if (calleeType.hasObjectRefParams()) { // dual long[] + Object[] emitBoxValuesOnStackWithRefs(ctx, asm, calleeType.params()); // stack: long[], Object[] @@ -1248,13 +1247,12 @@ public static void RETURN_CALL_INDIRECT( int typeId = (int) ins.operand(0); int tableIdx = (int) ins.operand(1); FunctionType calleeType = ctx.types()[typeId]; - boolean hasObjectRefParams = calleeType.params().stream().anyMatch(ValType::isObjectRef); int paramSlots = calleeType.params().stream().mapToInt(CompilerUtil::slotCount).sum(); int savedSlot = ctx.tempSlot() + paramSlots; asm.store(savedSlot, INT_TYPE); - if (hasObjectRefParams) { + if (calleeType.hasObjectRefParams()) { emitBoxValuesOnStackWithRefs(ctx, asm, calleeType.params()); // stack: long[], Object[] asm.load(savedSlot, INT_TYPE); @@ -1277,7 +1275,6 @@ public static void RETURN_CALL_REF( Context ctx, CompilerInstruction ins, InstructionAdapter asm) { int typeId = (int) ins.operand(0); FunctionType calleeType = ctx.types()[typeId]; - boolean hasObjectRefParams = calleeType.params().stream().anyMatch(ValType::isObjectRef); int paramSlots = calleeType.params().stream().mapToInt(CompilerUtil::slotCount).sum(); int savedSlot = ctx.tempSlot() + paramSlots; @@ -1292,7 +1289,7 @@ public static void RETURN_CALL_REF( asm.athrow(); asm.mark(notNull); - if (hasObjectRefParams) { + if (calleeType.hasObjectRefParams()) { emitBoxValuesOnStackWithRefs(ctx, asm, calleeType.params()); // stack: long[], Object[] int refArgsSlot = ctx.tempSlot(); @@ -1345,7 +1342,7 @@ private static void emitTailCallCheck( asm.ifeq(noPending); List returns = functionType.returns(); - boolean hasObjectRefReturns = returns.stream().anyMatch(ValType::isObjectRef); + boolean hasObjectRefReturns = functionType.hasObjectRefReturns(); if (returns.size() == 1) { emitPop(asm, returns.get(0)); @@ -1494,8 +1491,7 @@ public static void THROW(Context ctx, CompilerInstruction ins, InstructionAdapte int tagNumber = (int) ins.operand(0); var type = ctx.tagFunctionType(tagNumber); - boolean hasGcRefs = type.params().stream().anyMatch(ValType::isObjectRef); - if (hasGcRefs) { + if (type.hasObjectRefParams()) { emitBoxFieldsForStruct(ctx, asm, type.params()); asm.iconst(tagNumber); asm.load(ctx.instanceSlot(), OBJECT_TYPE); @@ -1595,7 +1591,7 @@ public static void CATCH_UNBOX_PARAMS( var tag = (int) ins.operand(0); var tagFuncType = ctx.tagFunctionType(tag); if (!tagFuncType.params().isEmpty()) { - boolean hasGcRefs = tagFuncType.params().stream().anyMatch(ValType::isObjectRef); + boolean hasGcRefs = tagFuncType.hasObjectRefParams(); int longArraySlot = ctx.tempSlot() + 1; int refArraySlot = ctx.tempSlot() + 2; 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 1c90bfaf9..f27a9d5c7 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/Shaded.java +++ b/compiler/src/main/java/run/endive/compiler/internal/Shaded.java @@ -43,6 +43,11 @@ public static long[] callIndirect(long[] args, int funcId, Instance instance) { return instance.getMachine().call(funcId, args); } + public static CallResult callIndirectOnInterpreterWithRefs( + long[] args, Object[] refArgs, int funcId, Instance instance) { + return instance.getMachine().callWithRefs(funcId, args, refArgs); + } + public static CallResult callIndirectWithRefs( long[] args, Object[] refArgs, int typeId, int funcId, Instance instance) { int actualTypeIdx = instance.functionType(funcId); 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 2d208df4b..1414771dc 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/ShadedRefs.java +++ b/compiler/src/main/java/run/endive/compiler/internal/ShadedRefs.java @@ -13,6 +13,7 @@ public final class ShadedRefs { static final Method CHECK_INTERRUPTION; static final Method CALL_INDIRECT; static final Method CALL_INDIRECT_ON_INTERPRETER; + static final Method CALL_INDIRECT_ON_INTERPRETER_WITH_REFS; static final Method INSTANCE_MEMORY; static final Method INSTANCE_MEMORY_IDX; static final Method CALL_HOST_FUNCTION; @@ -210,6 +211,13 @@ public final class ShadedRefs { "callIndirect", long[].class, int.class, int.class, Instance.class); CALL_INDIRECT_ON_INTERPRETER = Shaded.class.getMethod("callIndirect", long[].class, int.class, Instance.class); + CALL_INDIRECT_ON_INTERPRETER_WITH_REFS = + Shaded.class.getMethod( + "callIndirectOnInterpreterWithRefs", + long[].class, + Object[].class, + int.class, + Instance.class); INSTANCE_MEMORY = Instance.class.getMethod("memory"); INSTANCE_MEMORY_IDX = Instance.class.getMethod("memory", int.class); CALL_HOST_FUNCTION = 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 bec43e992..5414d010e 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 @@ -10,14 +10,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime ALOAD 0 ALOAD 1 PUTFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; - ALOAD 0 - NEW run/endive/runtime/internal/CompilerInterpreterMachine - DUP - ALOAD 1 - ICONST_0 - NEWARRAY T_INT - INVOKESPECIAL run/endive/runtime/internal/CompilerInterpreterMachine. (Lrun/endive/runtime/Instance;[I)V - PUTFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; RETURN public call(I[J)[J @@ -57,6 +49,20 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime public callWithRefs(I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; ALOAD 0 GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; + DUP + IFNONNULL L0 + POP + NEW run/endive/runtime/CallResult + DUP + ALOAD 0 + ILOAD 1 + ALOAD 2 + ALOAD 3 + INVOKEVIRTUAL run/endive/$gen/CompiledMachine.call (I[J[Ljava/lang/Object;)[J + ACONST_NULL + INVOKESPECIAL run/endive/runtime/CallResult. ([J[Ljava/lang/Object;)V + ARETURN + L0 ILOAD 1 ALOAD 2 ALOAD 3 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 d20a6492e..2323e17e1 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 @@ -10,14 +10,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime ALOAD 0 ALOAD 1 PUTFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; - ALOAD 0 - NEW run/endive/runtime/internal/CompilerInterpreterMachine - DUP - ALOAD 1 - ICONST_0 - NEWARRAY T_INT - INVOKESPECIAL run/endive/runtime/internal/CompilerInterpreterMachine. (Lrun/endive/runtime/Instance;[I)V - PUTFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; RETURN public call(I[J)[J @@ -57,6 +49,20 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime public callWithRefs(I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; ALOAD 0 GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; + DUP + IFNONNULL L0 + POP + NEW run/endive/runtime/CallResult + DUP + ALOAD 0 + ILOAD 1 + ALOAD 2 + ALOAD 3 + INVOKEVIRTUAL run/endive/$gen/CompiledMachine.call (I[J[Ljava/lang/Object;)[J + ACONST_NULL + INVOKESPECIAL run/endive/runtime/CallResult. ([J[Ljava/lang/Object;)V + ARETURN + L0 ILOAD 1 ALOAD 2 ALOAD 3 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 4ee64e173..07451d32c 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 @@ -10,14 +10,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime ALOAD 0 ALOAD 1 PUTFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; - ALOAD 0 - NEW run/endive/runtime/internal/CompilerInterpreterMachine - DUP - ALOAD 1 - ICONST_0 - NEWARRAY T_INT - INVOKESPECIAL run/endive/runtime/internal/CompilerInterpreterMachine. (Lrun/endive/runtime/Instance;[I)V - PUTFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; RETURN public call(I[J)[J @@ -57,6 +49,20 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime public callWithRefs(I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; ALOAD 0 GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; + DUP + IFNONNULL L0 + POP + NEW run/endive/runtime/CallResult + DUP + ALOAD 0 + ILOAD 1 + ALOAD 2 + ALOAD 3 + INVOKEVIRTUAL run/endive/$gen/CompiledMachine.call (I[J[Ljava/lang/Object;)[J + ACONST_NULL + INVOKESPECIAL run/endive/runtime/CallResult. ([J[Ljava/lang/Object;)V + ARETURN + L0 ILOAD 1 ALOAD 2 ALOAD 3 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 66df05d3f..b90278a85 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 @@ -10,14 +10,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime ALOAD 0 ALOAD 1 PUTFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; - ALOAD 0 - NEW run/endive/runtime/internal/CompilerInterpreterMachine - DUP - ALOAD 1 - ICONST_0 - NEWARRAY T_INT - INVOKESPECIAL run/endive/runtime/internal/CompilerInterpreterMachine. (Lrun/endive/runtime/Instance;[I)V - PUTFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; RETURN public call(I[J)[J @@ -57,6 +49,20 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime public callWithRefs(I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; ALOAD 0 GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; + DUP + IFNONNULL L0 + POP + NEW run/endive/runtime/CallResult + DUP + ALOAD 0 + ILOAD 1 + ALOAD 2 + ALOAD 3 + INVOKEVIRTUAL run/endive/$gen/CompiledMachine.call (I[J[Ljava/lang/Object;)[J + ACONST_NULL + INVOKESPECIAL run/endive/runtime/CallResult. ([J[Ljava/lang/Object;)V + ARETURN + L0 ILOAD 1 ALOAD 2 ALOAD 3 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 a92f6d8b3..1eedddb81 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 @@ -10,14 +10,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime ALOAD 0 ALOAD 1 PUTFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; - ALOAD 0 - NEW run/endive/runtime/internal/CompilerInterpreterMachine - DUP - ALOAD 1 - ICONST_0 - NEWARRAY T_INT - INVOKESPECIAL run/endive/runtime/internal/CompilerInterpreterMachine. (Lrun/endive/runtime/Instance;[I)V - PUTFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; RETURN public call(I[J)[J @@ -57,6 +49,20 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime public callWithRefs(I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; ALOAD 0 GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; + DUP + IFNONNULL L0 + POP + NEW run/endive/runtime/CallResult + DUP + ALOAD 0 + ILOAD 1 + ALOAD 2 + ALOAD 3 + INVOKEVIRTUAL run/endive/$gen/CompiledMachine.call (I[J[Ljava/lang/Object;)[J + ACONST_NULL + INVOKESPECIAL run/endive/runtime/CallResult. ([J[Ljava/lang/Object;)V + ARETURN + L0 ILOAD 1 ALOAD 2 ALOAD 3 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 05a623d60..93018533f 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 @@ -10,14 +10,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime ALOAD 0 ALOAD 1 PUTFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; - ALOAD 0 - NEW run/endive/runtime/internal/CompilerInterpreterMachine - DUP - ALOAD 1 - ICONST_0 - NEWARRAY T_INT - INVOKESPECIAL run/endive/runtime/internal/CompilerInterpreterMachine. (Lrun/endive/runtime/Instance;[I)V - PUTFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; RETURN public call(I[J)[J @@ -57,6 +49,20 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime public callWithRefs(I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; ALOAD 0 GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; + DUP + IFNONNULL L0 + POP + NEW run/endive/runtime/CallResult + DUP + ALOAD 0 + ILOAD 1 + ALOAD 2 + ALOAD 3 + INVOKEVIRTUAL run/endive/$gen/CompiledMachine.call (I[J[Ljava/lang/Object;)[J + ACONST_NULL + INVOKESPECIAL run/endive/runtime/CallResult. ([J[Ljava/lang/Object;)V + ARETURN + L0 ILOAD 1 ALOAD 2 ALOAD 3 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 3c3c4ffa1..e103f1aac 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 @@ -10,14 +10,6 @@ public final class FOO implements run/endive/runtime/Machine { ALOAD 0 ALOAD 1 PUTFIELD FOO.instance : Lrun/endive/runtime/Instance; - ALOAD 0 - NEW run/endive/runtime/internal/CompilerInterpreterMachine - DUP - ALOAD 1 - ICONST_0 - NEWARRAY T_INT - INVOKESPECIAL run/endive/runtime/internal/CompilerInterpreterMachine. (Lrun/endive/runtime/Instance;[I)V - PUTFIELD FOO.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; RETURN public call(I[J)[J @@ -57,6 +49,20 @@ public final class FOO implements run/endive/runtime/Machine { public callWithRefs(I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; ALOAD 0 GETFIELD FOO.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; + DUP + IFNONNULL L0 + POP + NEW run/endive/runtime/CallResult + DUP + ALOAD 0 + ILOAD 1 + ALOAD 2 + ALOAD 3 + INVOKEVIRTUAL FOO.call (I[J[Ljava/lang/Object;)[J + ACONST_NULL + INVOKESPECIAL run/endive/runtime/CallResult. ([J[Ljava/lang/Object;)V + ARETURN + L0 ILOAD 1 ALOAD 2 ALOAD 3 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 a44ae7b7e..a8cc1a606 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 @@ -10,14 +10,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime ALOAD 0 ALOAD 1 PUTFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; - ALOAD 0 - NEW run/endive/runtime/internal/CompilerInterpreterMachine - DUP - ALOAD 1 - ICONST_0 - NEWARRAY T_INT - INVOKESPECIAL run/endive/runtime/internal/CompilerInterpreterMachine. (Lrun/endive/runtime/Instance;[I)V - PUTFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; RETURN public call(I[J)[J @@ -57,6 +49,20 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime public callWithRefs(I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; ALOAD 0 GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; + DUP + IFNONNULL L0 + POP + NEW run/endive/runtime/CallResult + DUP + ALOAD 0 + ILOAD 1 + ALOAD 2 + ALOAD 3 + INVOKEVIRTUAL run/endive/$gen/CompiledMachine.call (I[J[Ljava/lang/Object;)[J + ACONST_NULL + INVOKESPECIAL run/endive/runtime/CallResult. ([J[Ljava/lang/Object;)V + ARETURN + L0 ILOAD 1 ALOAD 2 ALOAD 3 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 78871f0e4..d39bec36b 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 @@ -10,14 +10,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime ALOAD 0 ALOAD 1 PUTFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; - ALOAD 0 - NEW run/endive/runtime/internal/CompilerInterpreterMachine - DUP - ALOAD 1 - ICONST_0 - NEWARRAY T_INT - INVOKESPECIAL run/endive/runtime/internal/CompilerInterpreterMachine. (Lrun/endive/runtime/Instance;[I)V - PUTFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; RETURN public call(I[J)[J @@ -57,6 +49,20 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime public callWithRefs(I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; ALOAD 0 GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; + DUP + IFNONNULL L0 + POP + NEW run/endive/runtime/CallResult + DUP + ALOAD 0 + ILOAD 1 + ALOAD 2 + ALOAD 3 + INVOKEVIRTUAL run/endive/$gen/CompiledMachine.call (I[J[Ljava/lang/Object;)[J + ACONST_NULL + INVOKESPECIAL run/endive/runtime/CallResult. ([J[Ljava/lang/Object;)V + ARETURN + L0 ILOAD 1 ALOAD 2 ALOAD 3 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 d14559ae1..1b7f3b75c 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 @@ -10,14 +10,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime ALOAD 0 ALOAD 1 PUTFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; - ALOAD 0 - NEW run/endive/runtime/internal/CompilerInterpreterMachine - DUP - ALOAD 1 - ICONST_0 - NEWARRAY T_INT - INVOKESPECIAL run/endive/runtime/internal/CompilerInterpreterMachine. (Lrun/endive/runtime/Instance;[I)V - PUTFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; RETURN public call(I[J)[J @@ -57,6 +49,20 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime public callWithRefs(I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; ALOAD 0 GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; + DUP + IFNONNULL L0 + POP + NEW run/endive/runtime/CallResult + DUP + ALOAD 0 + ILOAD 1 + ALOAD 2 + ALOAD 3 + INVOKEVIRTUAL run/endive/$gen/CompiledMachine.call (I[J[Ljava/lang/Object;)[J + ACONST_NULL + INVOKESPECIAL run/endive/runtime/CallResult. ([J[Ljava/lang/Object;)V + ARETURN + L0 ILOAD 1 ALOAD 2 ALOAD 3 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 148ec6147..d703f8123 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 @@ -10,14 +10,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime ALOAD 0 ALOAD 1 PUTFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; - ALOAD 0 - NEW run/endive/runtime/internal/CompilerInterpreterMachine - DUP - ALOAD 1 - ICONST_0 - NEWARRAY T_INT - INVOKESPECIAL run/endive/runtime/internal/CompilerInterpreterMachine. (Lrun/endive/runtime/Instance;[I)V - PUTFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; RETURN public call(I[J)[J @@ -57,6 +49,20 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime public callWithRefs(I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; ALOAD 0 GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; + DUP + IFNONNULL L0 + POP + NEW run/endive/runtime/CallResult + DUP + ALOAD 0 + ILOAD 1 + ALOAD 2 + ALOAD 3 + INVOKEVIRTUAL run/endive/$gen/CompiledMachine.call (I[J[Ljava/lang/Object;)[J + ACONST_NULL + INVOKESPECIAL run/endive/runtime/CallResult. ([J[Ljava/lang/Object;)V + ARETURN + L0 ILOAD 1 ALOAD 2 ALOAD 3 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 cd4258c51..4c88275dc 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 @@ -10,14 +10,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime ALOAD 0 ALOAD 1 PUTFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; - ALOAD 0 - NEW run/endive/runtime/internal/CompilerInterpreterMachine - DUP - ALOAD 1 - ICONST_0 - NEWARRAY T_INT - INVOKESPECIAL run/endive/runtime/internal/CompilerInterpreterMachine. (Lrun/endive/runtime/Instance;[I)V - PUTFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; RETURN public call(I[J)[J @@ -97,6 +89,20 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime public callWithRefs(I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; ALOAD 0 GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; + DUP + IFNONNULL L0 + POP + NEW run/endive/runtime/CallResult + DUP + ALOAD 0 + ILOAD 1 + ALOAD 2 + ALOAD 3 + INVOKEVIRTUAL run/endive/$gen/CompiledMachine.call (I[J[Ljava/lang/Object;)[J + ACONST_NULL + INVOKESPECIAL run/endive/runtime/CallResult. ([J[Ljava/lang/Object;)V + ARETURN + L0 ILOAD 1 ALOAD 2 ALOAD 3 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 c15296b46..ddc26871e 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 @@ -10,14 +10,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime ALOAD 0 ALOAD 1 PUTFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; - ALOAD 0 - NEW run/endive/runtime/internal/CompilerInterpreterMachine - DUP - ALOAD 1 - ICONST_0 - NEWARRAY T_INT - INVOKESPECIAL run/endive/runtime/internal/CompilerInterpreterMachine. (Lrun/endive/runtime/Instance;[I)V - PUTFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; RETURN public call(I[J)[J @@ -57,6 +49,20 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime public callWithRefs(I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; ALOAD 0 GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; + DUP + IFNONNULL L0 + POP + NEW run/endive/runtime/CallResult + DUP + ALOAD 0 + ILOAD 1 + ALOAD 2 + ALOAD 3 + INVOKEVIRTUAL run/endive/$gen/CompiledMachine.call (I[J[Ljava/lang/Object;)[J + ACONST_NULL + INVOKESPECIAL run/endive/runtime/CallResult. ([J[Ljava/lang/Object;)V + ARETURN + L0 ILOAD 1 ALOAD 2 ALOAD 3 diff --git a/machine-tests/src/test/java/run/endive/testing/GcMultiValueTest.java b/machine-tests/src/test/java/run/endive/testing/GcMultiValueTest.java new file mode 100644 index 000000000..6c9dd8c09 --- /dev/null +++ b/machine-tests/src/test/java/run/endive/testing/GcMultiValueTest.java @@ -0,0 +1,110 @@ +package run.endive.testing; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.function.Function; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import run.endive.compiler.MachineFactoryCompiler; +import run.endive.corpus.CorpusResources; +import run.endive.runtime.Instance; +import run.endive.runtime.InterpreterMachine; +import run.endive.wasm.Parser; +import run.endive.wasm.WasmModule; + +/** + * Tests for multi-value returns mixing GC refs and numerics. + */ +public class GcMultiValueTest { + + private static final WasmModule MODULE = + Parser.parse(CorpusResources.getResource("compiled/gc_multivalue.wat.wasm")); + + private static Stream machineImplementations() { + return Stream.of( + Arguments.of( + "interpreter", + (Function) + (b) -> b.withMachineFactory(InterpreterMachine::new)), + Arguments.of( + "compiler", + (Function) + (b) -> b.withMachineFactory(MachineFactoryCompiler::compile))); + } + + // (result i32 (ref $Point)) -- numeric then ref + @ParameterizedTest(name = "{0}") + @MethodSource("machineImplementations") + public void intThenRef( + String name, Function machineInject) { + var instance = machineInject.apply(Instance.builder(MODULE)).build(); + var fn = instance.export("int_then_ref"); + var getX = instance.export("get_x"); + + var cr = fn.applyWithRefs(new long[] {42}, null); + assertEquals(42, cr.longResult(0), "numeric result"); + Object ref = cr.refResult(1); + assertNotNull(ref, "ref result should not be null"); + // Verify the ref is usable + var xResult = getX.applyWithRefs(new long[] {0}, new Object[] {ref}); + assertEquals(42, xResult.longResult(0), "get_x from returned ref"); + } + + // (result (ref $Point) i32) -- ref then numeric + @ParameterizedTest(name = "{0}") + @MethodSource("machineImplementations") + public void refThenInt( + String name, Function machineInject) { + var instance = machineInject.apply(Instance.builder(MODULE)).build(); + var fn = instance.export("ref_then_int"); + var getX = instance.export("get_x"); + + var cr = fn.applyWithRefs(new long[] {99}, null); + Object ref = cr.refResult(0); + assertNotNull(ref, "ref result should not be null"); + assertEquals(99, cr.longResult(1), "numeric result"); + // Verify the ref is usable + var xResult = getX.applyWithRefs(new long[] {0}, new Object[] {ref}); + assertEquals(99, xResult.longResult(0), "get_x from returned ref"); + } + + // (result (ref $Point) (ref $Point)) -- two refs + @ParameterizedTest(name = "{0}") + @MethodSource("machineImplementations") + public void twoRefs(String name, Function machineInject) { + var instance = machineInject.apply(Instance.builder(MODULE)).build(); + var fn = instance.export("two_refs"); + var getX = instance.export("get_x"); + + var cr = fn.applyWithRefs(new long[] {7}, null); + Object ref0 = cr.refResult(0); + Object ref1 = cr.refResult(1); + assertNotNull(ref0, "first ref should not be null"); + assertNotNull(ref1, "second ref should not be null"); + var x0 = getX.applyWithRefs(new long[] {0}, new Object[] {ref0}); + var x1 = getX.applyWithRefs(new long[] {0}, new Object[] {ref1}); + assertEquals(7, x0.longResult(0), "get_x from first ref"); + assertEquals(7, x1.longResult(0), "get_x from second ref"); + } + + // (result i32 i32 (ref $Point)) -- two numerics + ref + @ParameterizedTest(name = "{0}") + @MethodSource("machineImplementations") + public void twoIntsRef( + String name, Function machineInject) { + var instance = machineInject.apply(Instance.builder(MODULE)).build(); + var fn = instance.export("two_ints_ref"); + var getX = instance.export("get_x"); + + var cr = fn.applyWithRefs(new long[] {11}, null); + assertEquals(11, cr.longResult(0), "first numeric result"); + assertEquals(11, cr.longResult(1), "second numeric result"); + Object ref = cr.refResult(2); + assertNotNull(ref, "ref result should not be null"); + var xResult = getX.applyWithRefs(new long[] {0}, new Object[] {ref}); + assertEquals(11, xResult.longResult(0), "get_x from returned ref"); + } +} diff --git a/runtime/src/main/java/run/endive/runtime/CallResult.java b/runtime/src/main/java/run/endive/runtime/CallResult.java index baa5fd793..c7ed2fa8c 100644 --- a/runtime/src/main/java/run/endive/runtime/CallResult.java +++ b/runtime/src/main/java/run/endive/runtime/CallResult.java @@ -1,5 +1,6 @@ package run.endive.runtime; +/** Holds the dual long[] + Object[] result of a Wasm function call that may return GC/externref values. */ public final class CallResult { private final long[] longs; private final Object[] refs; diff --git a/runtime/src/main/java/run/endive/runtime/ExportFunction.java b/runtime/src/main/java/run/endive/runtime/ExportFunction.java index 50f6f288d..8cfec8561 100644 --- a/runtime/src/main/java/run/endive/runtime/ExportFunction.java +++ b/runtime/src/main/java/run/endive/runtime/ExportFunction.java @@ -9,6 +9,7 @@ public interface ExportFunction { long[] apply(long... args) throws WasmEngineException; + /** Invoke this exported function with separate numeric and Object ref arguments, returning a {@link CallResult}. */ default CallResult applyWithRefs(long[] args, Object[] refArgs) throws WasmEngineException { throw new UnsupportedOperationException("This function does not support applyWithRefs"); } diff --git a/runtime/src/main/java/run/endive/runtime/Instance.java b/runtime/src/main/java/run/endive/runtime/Instance.java index d51c9d20b..c18d2319d 100644 --- a/runtime/src/main/java/run/endive/runtime/Instance.java +++ b/runtime/src/main/java/run/endive/runtime/Instance.java @@ -288,8 +288,8 @@ 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 hasGcRefParams = funcType.params().stream().anyMatch(ValType::isObjectRef); - boolean hasGcRefReturns = funcType.returns().stream().anyMatch(ValType::isObjectRef); + boolean hasGcRefParams = funcType.hasObjectRefParams(); + boolean hasGcRefReturns = funcType.hasObjectRefReturns(); return new ExportFunction() { @Override public long[] apply(long... args) { diff --git a/runtime/src/main/java/run/endive/runtime/InterpreterMachine.java b/runtime/src/main/java/run/endive/runtime/InterpreterMachine.java index 4416fc933..c89600ea2 100644 --- a/runtime/src/main/java/run/endive/runtime/InterpreterMachine.java +++ b/runtime/src/main/java/run/endive/runtime/InterpreterMachine.java @@ -87,7 +87,7 @@ protected long[] call( // When called via call(int, long[]) with no refArgs and the function // has externref params, populate refArgs from longs so the ref stack // is set up correctly. Uses WasmExternRef (the proper externref type). - if (refArgs == null && type.params().stream().anyMatch(ValType::isObjectRef)) { + if (refArgs == null && type.hasObjectRefParams()) { refArgs = new Object[args.length]; int slot = 0; for (int pi = 0; pi < type.params().size(); pi++) { @@ -131,9 +131,7 @@ protected long[] call( var imprt = instance.imports().function(funcId); try { - boolean hasObjectRefs = - type.params().stream().anyMatch(ValType::isObjectRef) - || type.returns().stream().anyMatch(ValType::isObjectRef); + boolean hasObjectRefs = type.hasObjectRefParams() || type.hasObjectRefReturns(); if (hasObjectRefs) { var cr = imprt.handle().applyWithRefs(instance, args, refArgs); int slot = 0; diff --git a/runtime/src/main/java/run/endive/runtime/Machine.java b/runtime/src/main/java/run/endive/runtime/Machine.java index 894852231..b844c7061 100644 --- a/runtime/src/main/java/run/endive/runtime/Machine.java +++ b/runtime/src/main/java/run/endive/runtime/Machine.java @@ -11,6 +11,7 @@ default long[] call(int funcId, long[] args, Object[] refArgs) throws WasmEngine return call(funcId, args); } + /** Call function {@code funcId} with separate numeric and Object ref arguments, returning a {@link CallResult}. */ default CallResult callWithRefs(int funcId, long[] args, Object[] refArgs) throws WasmEngineException { return new CallResult(call(funcId, args, refArgs), null); diff --git a/runtime/src/main/java/run/endive/runtime/WasmFunctionHandle.java b/runtime/src/main/java/run/endive/runtime/WasmFunctionHandle.java index e645e11a7..93e3060f9 100644 --- a/runtime/src/main/java/run/endive/runtime/WasmFunctionHandle.java +++ b/runtime/src/main/java/run/endive/runtime/WasmFunctionHandle.java @@ -12,8 +12,8 @@ public interface WasmFunctionHandle { * Override this method for host functions that need to receive/return * externref or GC reference values as Objects. * - *

The default implementation delegates to {@link #apply(Instance, long...)} - * which discards Object refs but preserves backward compatibility. + *

The default delegates to {@link #apply(Instance, long...)} which discards + * Object refs; override to handle them. */ default CallResult applyWithRefs(Instance instance, long[] args, Object[] refArgs) { return new CallResult(apply(instance, args), null); 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 14df4d838..ef3649513 100644 --- a/runtime/src/main/java/run/endive/runtime/internal/CompilerInterpreterMachine.java +++ b/runtime/src/main/java/run/endive/runtime/internal/CompilerInterpreterMachine.java @@ -76,7 +76,7 @@ protected void CALL(Operands operands) { var extracted = extractArgsAndRefsForParams(stack, type.params(), instance); var args = (long[]) extracted[0]; var refArgs = (Object[]) extracted[1]; - boolean hasObjectRefs = type.returns().stream().anyMatch(ValType::isObjectRef); + boolean hasObjectRefs = type.hasObjectRefReturns(); try { if (hasObjectRefs) { diff --git a/wasm-corpus/src/main/resources/compiled/gc_multivalue.wat.wasm b/wasm-corpus/src/main/resources/compiled/gc_multivalue.wat.wasm new file mode 100644 index 0000000000000000000000000000000000000000..8573c9bb3eb8ac290b41cd75f1d59bb8781031ae GIT binary patch literal 223 zcmXwy%L;=q5Jm6gVXaA_>#p4QEBu50K`JDDEL6}y?P@-}nA`5RA(!a#zl?&5L8Xjw<&$m90>xn5`F*EL@ply literal 0 HcmV?d00001 diff --git a/wasm-corpus/src/main/resources/wat/gc_multivalue.wat b/wasm-corpus/src/main/resources/wat/gc_multivalue.wat new file mode 100644 index 000000000..f14ff7a47 --- /dev/null +++ b/wasm-corpus/src/main/resources/wat/gc_multivalue.wat @@ -0,0 +1,44 @@ +(module + (type $Point (struct (field $x i32) (field $y i32))) + + ;; (result i32 (ref $Point)) -- numeric then ref + (func (export "int_then_ref") (param i32) (result i32 (ref $Point)) + local.get 0 + local.get 0 + local.get 0 + struct.new $Point + ) + + ;; (result (ref $Point) i32) -- ref then numeric + (func (export "ref_then_int") (param i32) (result (ref $Point) i32) + local.get 0 + local.get 0 + struct.new $Point + local.get 0 + ) + + ;; (result (ref $Point) (ref $Point)) -- two refs + (func (export "two_refs") (param i32) (result (ref $Point) (ref $Point)) + local.get 0 + local.get 0 + struct.new $Point + local.get 0 + local.get 0 + struct.new $Point + ) + + ;; (result i32 i32 (ref $Point)) -- two numerics + ref + (func (export "two_ints_ref") (param i32) (result i32 i32 (ref $Point)) + local.get 0 + local.get 0 + local.get 0 + local.get 0 + struct.new $Point + ) + + ;; Helper: get x from Point + (func (export "get_x") (param (ref $Point)) (result i32) + local.get 0 + struct.get $Point $x + ) +) diff --git a/wasm/src/main/java/run/endive/wasm/types/FunctionType.java b/wasm/src/main/java/run/endive/wasm/types/FunctionType.java index d20296792..9312e9da4 100644 --- a/wasm/src/main/java/run/endive/wasm/types/FunctionType.java +++ b/wasm/src/main/java/run/endive/wasm/types/FunctionType.java @@ -20,6 +20,14 @@ public List returns() { return returns; } + public boolean hasObjectRefParams() { + return params.stream().anyMatch(ValType::isObjectRef); + } + + public boolean hasObjectRefReturns() { + return returns.stream().anyMatch(ValType::isObjectRef); + } + public boolean paramsMatch(FunctionType other) { return params.equals(other.params); } From afb9d335939bf6d6b5b2b53fb06e73909882738b Mon Sep 17 00:00:00 2001 From: andreatp Date: Tue, 16 Jun 2026 17:37:30 +0100 Subject: [PATCH 14/20] fix: update verifyLotsOfArgs approval template for new bytecode --- ...valTest.verifyLotsOfArgs.approved.template | 3874 ++++++++++++++++- 1 file changed, 3705 insertions(+), 169 deletions(-) diff --git a/compiler/src/test/resources/ApprovalTest.verifyLotsOfArgs.approved.template b/compiler/src/test/resources/ApprovalTest.verifyLotsOfArgs.approved.template index ea06e41f9..ebe8561dd 100644 --- a/compiler/src/test/resources/ApprovalTest.verifyLotsOfArgs.approved.template +++ b/compiler/src/test/resources/ApprovalTest.verifyLotsOfArgs.approved.template @@ -2,16 +2,44 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime private final Lrun/endive/runtime/Instance; instance + private final Lrun/endive/runtime/internal/CompilerInterpreterMachine; compilerInterpreterMachine + public (Lrun/endive/runtime/Instance;)V ALOAD 0 INVOKESPECIAL java/lang/Object. ()V ALOAD 0 ALOAD 1 PUTFIELD run/endive/$gen/CompiledMachine.instance : Lrun/endive/runtime/Instance; + ALOAD 0 + NEW run/endive/runtime/internal/CompilerInterpreterMachine + DUP + ALOAD 1 + ICONST_0 + NEWARRAY T_INT + INVOKESPECIAL run/endive/runtime/internal/CompilerInterpreterMachine. (Lrun/endive/runtime/Instance;[I)V + PUTFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; RETURN 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,12 +47,36 @@ 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 callWithRefs(I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; + ALOAD 0 + GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; + DUP + IFNONNULL L0 + POP + NEW run/endive/runtime/CallResult + DUP + ALOAD 0 + ILOAD 1 + ALOAD 2 + ALOAD 3 + INVOKEVIRTUAL run/endive/$gen/CompiledMachine.call (I[J[Ljava/lang/Object;)[J + ACONST_NULL + INVOKESPECIAL run/endive/runtime/CallResult. ([J[Ljava/lang/Object;)V + ARETURN + L0 + ILOAD 1 + ALOAD 2 + ALOAD 3 + INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callWithRefs (I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; + ARETURN + public static call_indirect_0(IIIILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V ALOAD 5 @@ -116,12 +168,13 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime ATHROW L1 ALOAD 0 + ACONST_NULL ICONST_1 ILOAD 6 ALOAD 7 - INVOKESTATIC run/endive/$gen/CompiledMachineShaded.callIndirect ([JIILrun/endive/runtime/Instance;)[J + INVOKESTATIC run/endive/$gen/CompiledMachineShaded.callIndirectWithRefs ([J[Ljava/lang/Object;IILrun/endive/runtime/Instance;)Lrun/endive/runtime/CallResult; ICONST_0 - LALOAD + INVOKEVIRTUAL run/endive/runtime/CallResult.longResult (I)J L2I IRETURN } @@ -134,186 +187,3669 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { FCONST_0 DCONST_0 ICONST_M1 - ICONST_M1 -#foreach ($const in $iconst) + ACONST_NULL ICONST_0 -#end - ILOAD 1 - INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V -#foreach ($store in $istore) - #set($store = 305 - $store) - ISTORE $store -#end - DSTORE 8 - FSTORE 7 - LSTORE 5 - ISTORE 4 - SIPUSH 300 - NEWARRAY T_LONG - DUP ICONST_0 - ILOAD 4 - I2L - LASTORE - DUP - ICONST_1 - LLOAD 5 - LASTORE - DUP - ICONST_2 - FLOAD 7 - INVOKESTATIC run/endive/wasm/types/Value.floatToLong (F)J - LASTORE - DUP - ICONST_3 - DLOAD 8 - INVOKESTATIC run/endive/wasm/types/Value.doubleToLong (D)J - LASTORE - DUP - ICONST_4 - ILOAD 10 - I2L - LASTORE - DUP - ICONST_5 - ILOAD 11 - I2L - LASTORE -#foreach ($splat in $splats1) - #set($const = $splat) - #set($load = $splat + 6) - DUP - BIPUSH $const - ILOAD $load - I2L - LASTORE -#end -#foreach ($splat in $splats2) - #set($const = $splat) - #set($load = $splat + 6) - DUP - SIPUSH $const - ILOAD $load - I2L - LASTORE -#end - ALOAD 2 - ALOAD 3 - INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_1 ([JLrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I - IRETURN - - public static call_0(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J - ALOAD 2 ICONST_0 - LALOAD - L2I - ALOAD 2 - ICONST_1 - LALOAD - L2I - ALOAD 1 - ALOAD 0 - INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_0 (IILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I - I2L - LSTORE 3 - ICONST_1 - NEWARRAY T_LONG - DUP ICONST_0 - LLOAD 3 - LASTORE - ARETURN - - public static func_1([JLrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I - ALOAD 0 ICONST_0 - LALOAD - L2I - ISTORE 3 - ALOAD 0 - ICONST_1 - LALOAD - LSTORE 4 - ALOAD 0 - ICONST_2 - LALOAD - INVOKESTATIC run/endive/wasm/types/Value.longToFloat (J)F - FSTORE 6 - ALOAD 0 - ICONST_3 - LALOAD - INVOKESTATIC run/endive/wasm/types/Value.longToDouble (J)D - DSTORE 7 - ALOAD 0 - ICONST_4 - LALOAD - L2I - ISTORE 9 - ALOAD 0 - ICONST_5 -#foreach ($splat in $splats3) - #set($bipush = $splat) - #set($store = $splat + 4) - LALOAD - L2I - ISTORE $store - ALOAD 0 - BIPUSH $bipush -#end -#foreach ($splat in $splats4) - #set($bipush = $splat) - #set($store = $splat + 4) - LALOAD - L2I - ISTORE $store - ALOAD 0 - SIPUSH $bipush -#end - LALOAD - L2I - ISTORE 304 - ILOAD 3 - ILOAD 304 - IADD - IRETURN - - public static call_1(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J)[J - ALOAD 2 - ALOAD 1 - ALOAD 0 - INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_1 ([JLrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I - I2L - LSTORE 3 - ICONST_1 - NEWARRAY T_LONG - DUP ICONST_0 - LLOAD 3 - LASTORE - ARETURN -} - -final class run/endive/$gen/CompiledMachineMachineCall { - - public ()V - ALOAD 0 - INVOKESPECIAL java/lang/Object. ()V - RETURN - - public static call(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;I[J)[J - ALOAD 0 - ALOAD 1 - ALOAD 3 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ICONST_0 + ILOAD 1 + INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V + ISTORE 305 + ISTORE 304 + ISTORE 303 + ISTORE 302 + ISTORE 301 + ISTORE 300 + ISTORE 299 + ISTORE 298 + ISTORE 297 + ISTORE 296 + ISTORE 295 + ISTORE 294 + ISTORE 293 + ISTORE 292 + ISTORE 291 + ISTORE 290 + ISTORE 289 + ISTORE 288 + ISTORE 287 + ISTORE 286 + ISTORE 285 + ISTORE 284 + ISTORE 283 + ISTORE 282 + ISTORE 281 + ISTORE 280 + ISTORE 279 + ISTORE 278 + ISTORE 277 + ISTORE 276 + ISTORE 275 + ISTORE 274 + ISTORE 273 + ISTORE 272 + ISTORE 271 + ISTORE 270 + ISTORE 269 + ISTORE 268 + ISTORE 267 + ISTORE 266 + ISTORE 265 + ISTORE 264 + ISTORE 263 + ISTORE 262 + ISTORE 261 + ISTORE 260 + ISTORE 259 + ISTORE 258 + ISTORE 257 + ISTORE 256 + ISTORE 255 + ISTORE 254 + ISTORE 253 + ISTORE 252 + ISTORE 251 + ISTORE 250 + ISTORE 249 + ISTORE 248 + ISTORE 247 + ISTORE 246 + ISTORE 245 + ISTORE 244 + ISTORE 243 + ISTORE 242 + ISTORE 241 + ISTORE 240 + ISTORE 239 + ISTORE 238 + ISTORE 237 + ISTORE 236 + ISTORE 235 + ISTORE 234 + ISTORE 233 + ISTORE 232 + ISTORE 231 + ISTORE 230 + ISTORE 229 + ISTORE 228 + ISTORE 227 + ISTORE 226 + ISTORE 225 + ISTORE 224 + ISTORE 223 + ISTORE 222 + ISTORE 221 + ISTORE 220 + ISTORE 219 + ISTORE 218 + ISTORE 217 + ISTORE 216 + ISTORE 215 + ISTORE 214 + ISTORE 213 + ISTORE 212 + ISTORE 211 + ISTORE 210 + ISTORE 209 + ISTORE 208 + ISTORE 207 + ISTORE 206 + ISTORE 205 + ISTORE 204 + ISTORE 203 + ISTORE 202 + ISTORE 201 + ISTORE 200 + ISTORE 199 + ISTORE 198 + ISTORE 197 + ISTORE 196 + ISTORE 195 + ISTORE 194 + ISTORE 193 + ISTORE 192 + ISTORE 191 + ISTORE 190 + ISTORE 189 + ISTORE 188 + ISTORE 187 + ISTORE 186 + ISTORE 185 + ISTORE 184 + ISTORE 183 + ISTORE 182 + ISTORE 181 + ISTORE 180 + ISTORE 179 + ISTORE 178 + ISTORE 177 + ISTORE 176 + ISTORE 175 + ISTORE 174 + ISTORE 173 + ISTORE 172 + ISTORE 171 + ISTORE 170 + ISTORE 169 + ISTORE 168 + ISTORE 167 + ISTORE 166 + ISTORE 165 + ISTORE 164 + ISTORE 163 + ISTORE 162 + ISTORE 161 + ISTORE 160 + ISTORE 159 + ISTORE 158 + ISTORE 157 + ISTORE 156 + ISTORE 155 + ISTORE 154 + ISTORE 153 + ISTORE 152 + ISTORE 151 + ISTORE 150 + ISTORE 149 + ISTORE 148 + ISTORE 147 + ISTORE 146 + ISTORE 145 + ISTORE 144 + ISTORE 143 + ISTORE 142 + ISTORE 141 + ISTORE 140 + ISTORE 139 + ISTORE 138 + ISTORE 137 + ISTORE 136 + ISTORE 135 + ISTORE 134 + ISTORE 133 + ISTORE 132 + ISTORE 131 + ISTORE 130 + ISTORE 129 + ISTORE 128 + ISTORE 127 + ISTORE 126 + ISTORE 125 + ISTORE 124 + ISTORE 123 + ISTORE 122 + ISTORE 121 + ISTORE 120 + ISTORE 119 + ISTORE 118 + ISTORE 117 + ISTORE 116 + ISTORE 115 + ISTORE 114 + ISTORE 113 + ISTORE 112 + ISTORE 111 + ISTORE 110 + ISTORE 109 + ISTORE 108 + ISTORE 107 + ISTORE 106 + ISTORE 105 + ISTORE 104 + ISTORE 103 + ISTORE 102 + ISTORE 101 + ISTORE 100 + ISTORE 99 + ISTORE 98 + ISTORE 97 + ISTORE 96 + ISTORE 95 + ISTORE 94 + ISTORE 93 + ISTORE 92 + ISTORE 91 + ISTORE 90 + ISTORE 89 + ISTORE 88 + ISTORE 87 + ISTORE 86 + ISTORE 85 + ISTORE 84 + ISTORE 83 + ISTORE 82 + ISTORE 81 + ISTORE 80 + ISTORE 79 + ISTORE 78 + ISTORE 77 + ISTORE 76 + ISTORE 75 + ISTORE 74 + ISTORE 73 + ISTORE 72 + ISTORE 71 + ISTORE 70 + ISTORE 69 + ISTORE 68 + ISTORE 67 + ISTORE 66 + ISTORE 65 + ISTORE 64 + ISTORE 63 + ISTORE 62 + ISTORE 61 + ISTORE 60 + ISTORE 59 + ISTORE 58 + ISTORE 57 + ISTORE 56 + ISTORE 55 + ISTORE 54 + ISTORE 53 + ISTORE 52 + ISTORE 51 + ISTORE 50 + ISTORE 49 + ISTORE 48 + ISTORE 47 + ISTORE 46 + ISTORE 45 + ISTORE 44 + ISTORE 43 + ISTORE 42 + ISTORE 41 + ISTORE 40 + ISTORE 39 + ISTORE 38 + ISTORE 37 + ISTORE 36 + ISTORE 35 + ISTORE 34 + ISTORE 33 + ISTORE 32 + ISTORE 31 + ISTORE 30 + ISTORE 29 + ISTORE 28 + ISTORE 27 + ISTORE 26 + ISTORE 25 + ISTORE 24 + ISTORE 23 + ISTORE 22 + ISTORE 21 + ISTORE 20 + ISTORE 19 + ISTORE 18 + ISTORE 17 + ISTORE 16 + ISTORE 15 + ISTORE 14 + ISTORE 13 + ISTORE 12 + ASTORE 11 + ISTORE 10 + DSTORE 8 + FSTORE 7 + LSTORE 5 + ISTORE 4 + SIPUSH 300 + NEWARRAY T_LONG + DUP + ICONST_0 + ILOAD 4 + I2L + LASTORE + DUP + ICONST_1 + LLOAD 5 + LASTORE + DUP + ICONST_2 + FLOAD 7 + INVOKESTATIC run/endive/wasm/types/Value.floatToLong (F)J + LASTORE + DUP + ICONST_3 + DLOAD 8 + INVOKESTATIC run/endive/wasm/types/Value.doubleToLong (D)J + LASTORE + DUP + ICONST_4 + ILOAD 10 + I2L + LASTORE + DUP + ICONST_5 + LCONST_0 + LASTORE + DUP + BIPUSH 6 + ILOAD 12 + I2L + LASTORE + DUP + BIPUSH 7 + ILOAD 13 + I2L + LASTORE + DUP + BIPUSH 8 + ILOAD 14 + I2L + LASTORE + DUP + BIPUSH 9 + ILOAD 15 + I2L + LASTORE + DUP + BIPUSH 10 + ILOAD 16 + I2L + LASTORE + DUP + BIPUSH 11 + ILOAD 17 + I2L + LASTORE + DUP + BIPUSH 12 + ILOAD 18 + I2L + LASTORE + DUP + BIPUSH 13 + ILOAD 19 + I2L + LASTORE + DUP + BIPUSH 14 + ILOAD 20 + I2L + LASTORE + DUP + BIPUSH 15 + ILOAD 21 + I2L + LASTORE + DUP + BIPUSH 16 + ILOAD 22 + I2L + LASTORE + DUP + BIPUSH 17 + ILOAD 23 + I2L + LASTORE + DUP + BIPUSH 18 + ILOAD 24 + I2L + LASTORE + DUP + BIPUSH 19 + ILOAD 25 + I2L + LASTORE + DUP + BIPUSH 20 + ILOAD 26 + I2L + LASTORE + DUP + BIPUSH 21 + ILOAD 27 + I2L + LASTORE + DUP + BIPUSH 22 + ILOAD 28 + I2L + LASTORE + DUP + BIPUSH 23 + ILOAD 29 + I2L + LASTORE + DUP + BIPUSH 24 + ILOAD 30 + I2L + LASTORE + DUP + BIPUSH 25 + ILOAD 31 + I2L + LASTORE + DUP + BIPUSH 26 + ILOAD 32 + I2L + LASTORE + DUP + BIPUSH 27 + ILOAD 33 + I2L + LASTORE + DUP + BIPUSH 28 + ILOAD 34 + I2L + LASTORE + DUP + BIPUSH 29 + ILOAD 35 + I2L + LASTORE + DUP + BIPUSH 30 + ILOAD 36 + I2L + LASTORE + DUP + BIPUSH 31 + ILOAD 37 + I2L + LASTORE + DUP + BIPUSH 32 + ILOAD 38 + I2L + LASTORE + DUP + BIPUSH 33 + ILOAD 39 + I2L + LASTORE + DUP + BIPUSH 34 + ILOAD 40 + I2L + LASTORE + DUP + BIPUSH 35 + ILOAD 41 + I2L + LASTORE + DUP + BIPUSH 36 + ILOAD 42 + I2L + LASTORE + DUP + BIPUSH 37 + ILOAD 43 + I2L + LASTORE + DUP + BIPUSH 38 + ILOAD 44 + I2L + LASTORE + DUP + BIPUSH 39 + ILOAD 45 + I2L + LASTORE + DUP + BIPUSH 40 + ILOAD 46 + I2L + LASTORE + DUP + BIPUSH 41 + ILOAD 47 + I2L + LASTORE + DUP + BIPUSH 42 + ILOAD 48 + I2L + LASTORE + DUP + BIPUSH 43 + ILOAD 49 + I2L + LASTORE + DUP + BIPUSH 44 + ILOAD 50 + I2L + LASTORE + DUP + BIPUSH 45 + ILOAD 51 + I2L + LASTORE + DUP + BIPUSH 46 + ILOAD 52 + I2L + LASTORE + DUP + BIPUSH 47 + ILOAD 53 + I2L + LASTORE + DUP + BIPUSH 48 + ILOAD 54 + I2L + LASTORE + DUP + BIPUSH 49 + ILOAD 55 + I2L + LASTORE + DUP + BIPUSH 50 + ILOAD 56 + I2L + LASTORE + DUP + BIPUSH 51 + ILOAD 57 + I2L + LASTORE + DUP + BIPUSH 52 + ILOAD 58 + I2L + LASTORE + DUP + BIPUSH 53 + ILOAD 59 + I2L + LASTORE + DUP + BIPUSH 54 + ILOAD 60 + I2L + LASTORE + DUP + BIPUSH 55 + ILOAD 61 + I2L + LASTORE + DUP + BIPUSH 56 + ILOAD 62 + I2L + LASTORE + DUP + BIPUSH 57 + ILOAD 63 + I2L + LASTORE + DUP + BIPUSH 58 + ILOAD 64 + I2L + LASTORE + DUP + BIPUSH 59 + ILOAD 65 + I2L + LASTORE + DUP + BIPUSH 60 + ILOAD 66 + I2L + LASTORE + DUP + BIPUSH 61 + ILOAD 67 + I2L + LASTORE + DUP + BIPUSH 62 + ILOAD 68 + I2L + LASTORE + DUP + BIPUSH 63 + ILOAD 69 + I2L + LASTORE + DUP + BIPUSH 64 + ILOAD 70 + I2L + LASTORE + DUP + BIPUSH 65 + ILOAD 71 + I2L + LASTORE + DUP + BIPUSH 66 + ILOAD 72 + I2L + LASTORE + DUP + BIPUSH 67 + ILOAD 73 + I2L + LASTORE + DUP + BIPUSH 68 + ILOAD 74 + I2L + LASTORE + DUP + BIPUSH 69 + ILOAD 75 + I2L + LASTORE + DUP + BIPUSH 70 + ILOAD 76 + I2L + LASTORE + DUP + BIPUSH 71 + ILOAD 77 + I2L + LASTORE + DUP + BIPUSH 72 + ILOAD 78 + I2L + LASTORE + DUP + BIPUSH 73 + ILOAD 79 + I2L + LASTORE + DUP + BIPUSH 74 + ILOAD 80 + I2L + LASTORE + DUP + BIPUSH 75 + ILOAD 81 + I2L + LASTORE + DUP + BIPUSH 76 + ILOAD 82 + I2L + LASTORE + DUP + BIPUSH 77 + ILOAD 83 + I2L + LASTORE + DUP + BIPUSH 78 + ILOAD 84 + I2L + LASTORE + DUP + BIPUSH 79 + ILOAD 85 + I2L + LASTORE + DUP + BIPUSH 80 + ILOAD 86 + I2L + LASTORE + DUP + BIPUSH 81 + ILOAD 87 + I2L + LASTORE + DUP + BIPUSH 82 + ILOAD 88 + I2L + LASTORE + DUP + BIPUSH 83 + ILOAD 89 + I2L + LASTORE + DUP + BIPUSH 84 + ILOAD 90 + I2L + LASTORE + DUP + BIPUSH 85 + ILOAD 91 + I2L + LASTORE + DUP + BIPUSH 86 + ILOAD 92 + I2L + LASTORE + DUP + BIPUSH 87 + ILOAD 93 + I2L + LASTORE + DUP + BIPUSH 88 + ILOAD 94 + I2L + LASTORE + DUP + BIPUSH 89 + ILOAD 95 + I2L + LASTORE + DUP + BIPUSH 90 + ILOAD 96 + I2L + LASTORE + DUP + BIPUSH 91 + ILOAD 97 + I2L + LASTORE + DUP + BIPUSH 92 + ILOAD 98 + I2L + LASTORE + DUP + BIPUSH 93 + ILOAD 99 + I2L + LASTORE + DUP + BIPUSH 94 + ILOAD 100 + I2L + LASTORE + DUP + BIPUSH 95 + ILOAD 101 + I2L + LASTORE + DUP + BIPUSH 96 + ILOAD 102 + I2L + LASTORE + DUP + BIPUSH 97 + ILOAD 103 + I2L + LASTORE + DUP + BIPUSH 98 + ILOAD 104 + I2L + LASTORE + DUP + BIPUSH 99 + ILOAD 105 + I2L + LASTORE + DUP + BIPUSH 100 + ILOAD 106 + I2L + LASTORE + DUP + BIPUSH 101 + ILOAD 107 + I2L + LASTORE + DUP + BIPUSH 102 + ILOAD 108 + I2L + LASTORE + DUP + BIPUSH 103 + ILOAD 109 + I2L + LASTORE + DUP + BIPUSH 104 + ILOAD 110 + I2L + LASTORE + DUP + BIPUSH 105 + ILOAD 111 + I2L + LASTORE + DUP + BIPUSH 106 + ILOAD 112 + I2L + LASTORE + DUP + BIPUSH 107 + ILOAD 113 + I2L + LASTORE + DUP + BIPUSH 108 + ILOAD 114 + I2L + LASTORE + DUP + BIPUSH 109 + ILOAD 115 + I2L + LASTORE + DUP + BIPUSH 110 + ILOAD 116 + I2L + LASTORE + DUP + BIPUSH 111 + ILOAD 117 + I2L + LASTORE + DUP + BIPUSH 112 + ILOAD 118 + I2L + LASTORE + DUP + BIPUSH 113 + ILOAD 119 + I2L + LASTORE + DUP + BIPUSH 114 + ILOAD 120 + I2L + LASTORE + DUP + BIPUSH 115 + ILOAD 121 + I2L + LASTORE + DUP + BIPUSH 116 + ILOAD 122 + I2L + LASTORE + DUP + BIPUSH 117 + ILOAD 123 + I2L + LASTORE + DUP + BIPUSH 118 + ILOAD 124 + I2L + LASTORE + DUP + BIPUSH 119 + ILOAD 125 + I2L + LASTORE + DUP + BIPUSH 120 + ILOAD 126 + I2L + LASTORE + DUP + BIPUSH 121 + ILOAD 127 + I2L + LASTORE + DUP + BIPUSH 122 + ILOAD 128 + I2L + LASTORE + DUP + BIPUSH 123 + ILOAD 129 + I2L + LASTORE + DUP + BIPUSH 124 + ILOAD 130 + I2L + LASTORE + DUP + BIPUSH 125 + ILOAD 131 + I2L + LASTORE + DUP + BIPUSH 126 + ILOAD 132 + I2L + LASTORE + DUP + BIPUSH 127 + ILOAD 133 + I2L + LASTORE + DUP + SIPUSH 128 + ILOAD 134 + I2L + LASTORE + DUP + SIPUSH 129 + ILOAD 135 + I2L + LASTORE + DUP + SIPUSH 130 + ILOAD 136 + I2L + LASTORE + DUP + SIPUSH 131 + ILOAD 137 + I2L + LASTORE + DUP + SIPUSH 132 + ILOAD 138 + I2L + LASTORE + DUP + SIPUSH 133 + ILOAD 139 + I2L + LASTORE + DUP + SIPUSH 134 + ILOAD 140 + I2L + LASTORE + DUP + SIPUSH 135 + ILOAD 141 + I2L + LASTORE + DUP + SIPUSH 136 + ILOAD 142 + I2L + LASTORE + DUP + SIPUSH 137 + ILOAD 143 + I2L + LASTORE + DUP + SIPUSH 138 + ILOAD 144 + I2L + LASTORE + DUP + SIPUSH 139 + ILOAD 145 + I2L + LASTORE + DUP + SIPUSH 140 + ILOAD 146 + I2L + LASTORE + DUP + SIPUSH 141 + ILOAD 147 + I2L + LASTORE + DUP + SIPUSH 142 + ILOAD 148 + I2L + LASTORE + DUP + SIPUSH 143 + ILOAD 149 + I2L + LASTORE + DUP + SIPUSH 144 + ILOAD 150 + I2L + LASTORE + DUP + SIPUSH 145 + ILOAD 151 + I2L + LASTORE + DUP + SIPUSH 146 + ILOAD 152 + I2L + LASTORE + DUP + SIPUSH 147 + ILOAD 153 + I2L + LASTORE + DUP + SIPUSH 148 + ILOAD 154 + I2L + LASTORE + DUP + SIPUSH 149 + ILOAD 155 + I2L + LASTORE + DUP + SIPUSH 150 + ILOAD 156 + I2L + LASTORE + DUP + SIPUSH 151 + ILOAD 157 + I2L + LASTORE + DUP + SIPUSH 152 + ILOAD 158 + I2L + LASTORE + DUP + SIPUSH 153 + ILOAD 159 + I2L + LASTORE + DUP + SIPUSH 154 + ILOAD 160 + I2L + LASTORE + DUP + SIPUSH 155 + ILOAD 161 + I2L + LASTORE + DUP + SIPUSH 156 + ILOAD 162 + I2L + LASTORE + DUP + SIPUSH 157 + ILOAD 163 + I2L + LASTORE + DUP + SIPUSH 158 + ILOAD 164 + I2L + LASTORE + DUP + SIPUSH 159 + ILOAD 165 + I2L + LASTORE + DUP + SIPUSH 160 + ILOAD 166 + I2L + LASTORE + DUP + SIPUSH 161 + ILOAD 167 + I2L + LASTORE + DUP + SIPUSH 162 + ILOAD 168 + I2L + LASTORE + DUP + SIPUSH 163 + ILOAD 169 + I2L + LASTORE + DUP + SIPUSH 164 + ILOAD 170 + I2L + LASTORE + DUP + SIPUSH 165 + ILOAD 171 + I2L + LASTORE + DUP + SIPUSH 166 + ILOAD 172 + I2L + LASTORE + DUP + SIPUSH 167 + ILOAD 173 + I2L + LASTORE + DUP + SIPUSH 168 + ILOAD 174 + I2L + LASTORE + DUP + SIPUSH 169 + ILOAD 175 + I2L + LASTORE + DUP + SIPUSH 170 + ILOAD 176 + I2L + LASTORE + DUP + SIPUSH 171 + ILOAD 177 + I2L + LASTORE + DUP + SIPUSH 172 + ILOAD 178 + I2L + LASTORE + DUP + SIPUSH 173 + ILOAD 179 + I2L + LASTORE + DUP + SIPUSH 174 + ILOAD 180 + I2L + LASTORE + DUP + SIPUSH 175 + ILOAD 181 + I2L + LASTORE + DUP + SIPUSH 176 + ILOAD 182 + I2L + LASTORE + DUP + SIPUSH 177 + ILOAD 183 + I2L + LASTORE + DUP + SIPUSH 178 + ILOAD 184 + I2L + LASTORE + DUP + SIPUSH 179 + ILOAD 185 + I2L + LASTORE + DUP + SIPUSH 180 + ILOAD 186 + I2L + LASTORE + DUP + SIPUSH 181 + ILOAD 187 + I2L + LASTORE + DUP + SIPUSH 182 + ILOAD 188 + I2L + LASTORE + DUP + SIPUSH 183 + ILOAD 189 + I2L + LASTORE + DUP + SIPUSH 184 + ILOAD 190 + I2L + LASTORE + DUP + SIPUSH 185 + ILOAD 191 + I2L + LASTORE + DUP + SIPUSH 186 + ILOAD 192 + I2L + LASTORE + DUP + SIPUSH 187 + ILOAD 193 + I2L + LASTORE + DUP + SIPUSH 188 + ILOAD 194 + I2L + LASTORE + DUP + SIPUSH 189 + ILOAD 195 + I2L + LASTORE + DUP + SIPUSH 190 + ILOAD 196 + I2L + LASTORE + DUP + SIPUSH 191 + ILOAD 197 + I2L + LASTORE + DUP + SIPUSH 192 + ILOAD 198 + I2L + LASTORE + DUP + SIPUSH 193 + ILOAD 199 + I2L + LASTORE + DUP + SIPUSH 194 + ILOAD 200 + I2L + LASTORE + DUP + SIPUSH 195 + ILOAD 201 + I2L + LASTORE + DUP + SIPUSH 196 + ILOAD 202 + I2L + LASTORE + DUP + SIPUSH 197 + ILOAD 203 + I2L + LASTORE + DUP + SIPUSH 198 + ILOAD 204 + I2L + LASTORE + DUP + SIPUSH 199 + ILOAD 205 + I2L + LASTORE + DUP + SIPUSH 200 + ILOAD 206 + I2L + LASTORE + DUP + SIPUSH 201 + ILOAD 207 + I2L + LASTORE + DUP + SIPUSH 202 + ILOAD 208 + I2L + LASTORE + DUP + SIPUSH 203 + ILOAD 209 + I2L + LASTORE + DUP + SIPUSH 204 + ILOAD 210 + I2L + LASTORE + DUP + SIPUSH 205 + ILOAD 211 + I2L + LASTORE + DUP + SIPUSH 206 + ILOAD 212 + I2L + LASTORE + DUP + SIPUSH 207 + ILOAD 213 + I2L + LASTORE + DUP + SIPUSH 208 + ILOAD 214 + I2L + LASTORE + DUP + SIPUSH 209 + ILOAD 215 + I2L + LASTORE + DUP + SIPUSH 210 + ILOAD 216 + I2L + LASTORE + DUP + SIPUSH 211 + ILOAD 217 + I2L + LASTORE + DUP + SIPUSH 212 + ILOAD 218 + I2L + LASTORE + DUP + SIPUSH 213 + ILOAD 219 + I2L + LASTORE + DUP + SIPUSH 214 + ILOAD 220 + I2L + LASTORE + DUP + SIPUSH 215 + ILOAD 221 + I2L + LASTORE + DUP + SIPUSH 216 + ILOAD 222 + I2L + LASTORE + DUP + SIPUSH 217 + ILOAD 223 + I2L + LASTORE + DUP + SIPUSH 218 + ILOAD 224 + I2L + LASTORE + DUP + SIPUSH 219 + ILOAD 225 + I2L + LASTORE + DUP + SIPUSH 220 + ILOAD 226 + I2L + LASTORE + DUP + SIPUSH 221 + ILOAD 227 + I2L + LASTORE + DUP + SIPUSH 222 + ILOAD 228 + I2L + LASTORE + DUP + SIPUSH 223 + ILOAD 229 + I2L + LASTORE + DUP + SIPUSH 224 + ILOAD 230 + I2L + LASTORE + DUP + SIPUSH 225 + ILOAD 231 + I2L + LASTORE + DUP + SIPUSH 226 + ILOAD 232 + I2L + LASTORE + DUP + SIPUSH 227 + ILOAD 233 + I2L + LASTORE + DUP + SIPUSH 228 + ILOAD 234 + I2L + LASTORE + DUP + SIPUSH 229 + ILOAD 235 + I2L + LASTORE + DUP + SIPUSH 230 + ILOAD 236 + I2L + LASTORE + DUP + SIPUSH 231 + ILOAD 237 + I2L + LASTORE + DUP + SIPUSH 232 + ILOAD 238 + I2L + LASTORE + DUP + SIPUSH 233 + ILOAD 239 + I2L + LASTORE + DUP + SIPUSH 234 + ILOAD 240 + I2L + LASTORE + DUP + SIPUSH 235 + ILOAD 241 + I2L + LASTORE + DUP + SIPUSH 236 + ILOAD 242 + I2L + LASTORE + DUP + SIPUSH 237 + ILOAD 243 + I2L + LASTORE + DUP + SIPUSH 238 + ILOAD 244 + I2L + LASTORE + DUP + SIPUSH 239 + ILOAD 245 + I2L + LASTORE + DUP + SIPUSH 240 + ILOAD 246 + I2L + LASTORE + DUP + SIPUSH 241 + ILOAD 247 + I2L + LASTORE + DUP + SIPUSH 242 + ILOAD 248 + I2L + LASTORE + DUP + SIPUSH 243 + ILOAD 249 + I2L + LASTORE + DUP + SIPUSH 244 + ILOAD 250 + I2L + LASTORE + DUP + SIPUSH 245 + ILOAD 251 + I2L + LASTORE + DUP + SIPUSH 246 + ILOAD 252 + I2L + LASTORE + DUP + SIPUSH 247 + ILOAD 253 + I2L + LASTORE + DUP + SIPUSH 248 + ILOAD 254 + I2L + LASTORE + DUP + SIPUSH 249 + ILOAD 255 + I2L + LASTORE + DUP + SIPUSH 250 + ILOAD 256 + I2L + LASTORE + DUP + SIPUSH 251 + ILOAD 257 + I2L + LASTORE + DUP + SIPUSH 252 + ILOAD 258 + I2L + LASTORE + DUP + SIPUSH 253 + ILOAD 259 + I2L + LASTORE + DUP + SIPUSH 254 + ILOAD 260 + I2L + LASTORE + DUP + SIPUSH 255 + ILOAD 261 + I2L + LASTORE + DUP + SIPUSH 256 + ILOAD 262 + I2L + LASTORE + DUP + SIPUSH 257 + ILOAD 263 + I2L + LASTORE + DUP + SIPUSH 258 + ILOAD 264 + I2L + LASTORE + DUP + SIPUSH 259 + ILOAD 265 + I2L + LASTORE + DUP + SIPUSH 260 + ILOAD 266 + I2L + LASTORE + DUP + SIPUSH 261 + ILOAD 267 + I2L + LASTORE + DUP + SIPUSH 262 + ILOAD 268 + I2L + LASTORE + DUP + SIPUSH 263 + ILOAD 269 + I2L + LASTORE + DUP + SIPUSH 264 + ILOAD 270 + I2L + LASTORE + DUP + SIPUSH 265 + ILOAD 271 + I2L + LASTORE + DUP + SIPUSH 266 + ILOAD 272 + I2L + LASTORE + DUP + SIPUSH 267 + ILOAD 273 + I2L + LASTORE + DUP + SIPUSH 268 + ILOAD 274 + I2L + LASTORE + DUP + SIPUSH 269 + ILOAD 275 + I2L + LASTORE + DUP + SIPUSH 270 + ILOAD 276 + I2L + LASTORE + DUP + SIPUSH 271 + ILOAD 277 + I2L + LASTORE + DUP + SIPUSH 272 + ILOAD 278 + I2L + LASTORE + DUP + SIPUSH 273 + ILOAD 279 + I2L + LASTORE + DUP + SIPUSH 274 + ILOAD 280 + I2L + LASTORE + DUP + SIPUSH 275 + ILOAD 281 + I2L + LASTORE + DUP + SIPUSH 276 + ILOAD 282 + I2L + LASTORE + DUP + SIPUSH 277 + ILOAD 283 + I2L + LASTORE + DUP + SIPUSH 278 + ILOAD 284 + I2L + LASTORE + DUP + SIPUSH 279 + ILOAD 285 + I2L + LASTORE + DUP + SIPUSH 280 + ILOAD 286 + I2L + LASTORE + DUP + SIPUSH 281 + ILOAD 287 + I2L + LASTORE + DUP + SIPUSH 282 + ILOAD 288 + I2L + LASTORE + DUP + SIPUSH 283 + ILOAD 289 + I2L + LASTORE + DUP + SIPUSH 284 + ILOAD 290 + I2L + LASTORE + DUP + SIPUSH 285 + ILOAD 291 + I2L + LASTORE + DUP + SIPUSH 286 + ILOAD 292 + I2L + LASTORE + DUP + SIPUSH 287 + ILOAD 293 + I2L + LASTORE + DUP + SIPUSH 288 + ILOAD 294 + I2L + LASTORE + DUP + SIPUSH 289 + ILOAD 295 + I2L + LASTORE + DUP + SIPUSH 290 + ILOAD 296 + I2L + LASTORE + DUP + SIPUSH 291 + ILOAD 297 + I2L + LASTORE + DUP + SIPUSH 292 + ILOAD 298 + I2L + LASTORE + DUP + SIPUSH 293 + ILOAD 299 + I2L + LASTORE + DUP + SIPUSH 294 + ILOAD 300 + I2L + LASTORE + DUP + SIPUSH 295 + ILOAD 301 + I2L + LASTORE + DUP + SIPUSH 296 + ILOAD 302 + I2L + LASTORE + DUP + SIPUSH 297 + ILOAD 303 + I2L + LASTORE + DUP + SIPUSH 298 + ILOAD 304 + I2L + LASTORE + DUP + SIPUSH 299 + ILOAD 305 + I2L + LASTORE + ALOAD 2 + ALOAD 3 + INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_1 ([JLrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I + IRETURN + + public static call_0(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J + ALOAD 2 + ICONST_0 + LALOAD + L2I + ALOAD 2 + ICONST_1 + LALOAD + L2I + ALOAD 1 + ALOAD 0 + INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_0 (IILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I + I2L + LSTORE 4 + ICONST_1 + NEWARRAY T_LONG + DUP + ICONST_0 + LLOAD 4 + LASTORE + ARETURN + + public static func_1([JLrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I + ALOAD 0 + ICONST_0 + LALOAD + L2I + ISTORE 3 + ALOAD 0 + ICONST_1 + LALOAD + LSTORE 4 + ALOAD 0 + ICONST_2 + LALOAD + INVOKESTATIC run/endive/wasm/types/Value.longToFloat (J)F + FSTORE 6 + ALOAD 0 + ICONST_3 + LALOAD + INVOKESTATIC run/endive/wasm/types/Value.longToDouble (J)D + DSTORE 7 + ALOAD 0 + ICONST_4 + LALOAD + L2I + ISTORE 9 + ACONST_NULL + ASTORE 10 + ALOAD 0 + BIPUSH 6 + LALOAD + L2I + ISTORE 11 + ALOAD 0 + BIPUSH 7 + LALOAD + L2I + ISTORE 12 + ALOAD 0 + BIPUSH 8 + LALOAD + L2I + ISTORE 13 + ALOAD 0 + BIPUSH 9 + LALOAD + L2I + ISTORE 14 + ALOAD 0 + BIPUSH 10 + LALOAD + L2I + ISTORE 15 + ALOAD 0 + BIPUSH 11 + LALOAD + L2I + ISTORE 16 + ALOAD 0 + BIPUSH 12 + LALOAD + L2I + ISTORE 17 + ALOAD 0 + BIPUSH 13 + LALOAD + L2I + ISTORE 18 + ALOAD 0 + BIPUSH 14 + LALOAD + L2I + ISTORE 19 + ALOAD 0 + BIPUSH 15 + LALOAD + L2I + ISTORE 20 + ALOAD 0 + BIPUSH 16 + LALOAD + L2I + ISTORE 21 + ALOAD 0 + BIPUSH 17 + LALOAD + L2I + ISTORE 22 + ALOAD 0 + BIPUSH 18 + LALOAD + L2I + ISTORE 23 + ALOAD 0 + BIPUSH 19 + LALOAD + L2I + ISTORE 24 + ALOAD 0 + BIPUSH 20 + LALOAD + L2I + ISTORE 25 + ALOAD 0 + BIPUSH 21 + LALOAD + L2I + ISTORE 26 + ALOAD 0 + BIPUSH 22 + LALOAD + L2I + ISTORE 27 + ALOAD 0 + BIPUSH 23 + LALOAD + L2I + ISTORE 28 + ALOAD 0 + BIPUSH 24 + LALOAD + L2I + ISTORE 29 + ALOAD 0 + BIPUSH 25 + LALOAD + L2I + ISTORE 30 + ALOAD 0 + BIPUSH 26 + LALOAD + L2I + ISTORE 31 + ALOAD 0 + BIPUSH 27 + LALOAD + L2I + ISTORE 32 + ALOAD 0 + BIPUSH 28 + LALOAD + L2I + ISTORE 33 + ALOAD 0 + BIPUSH 29 + LALOAD + L2I + ISTORE 34 + ALOAD 0 + BIPUSH 30 + LALOAD + L2I + ISTORE 35 + ALOAD 0 + BIPUSH 31 + LALOAD + L2I + ISTORE 36 + ALOAD 0 + BIPUSH 32 + LALOAD + L2I + ISTORE 37 + ALOAD 0 + BIPUSH 33 + LALOAD + L2I + ISTORE 38 + ALOAD 0 + BIPUSH 34 + LALOAD + L2I + ISTORE 39 + ALOAD 0 + BIPUSH 35 + LALOAD + L2I + ISTORE 40 + ALOAD 0 + BIPUSH 36 + LALOAD + L2I + ISTORE 41 + ALOAD 0 + BIPUSH 37 + LALOAD + L2I + ISTORE 42 + ALOAD 0 + BIPUSH 38 + LALOAD + L2I + ISTORE 43 + ALOAD 0 + BIPUSH 39 + LALOAD + L2I + ISTORE 44 + ALOAD 0 + BIPUSH 40 + LALOAD + L2I + ISTORE 45 + ALOAD 0 + BIPUSH 41 + LALOAD + L2I + ISTORE 46 + ALOAD 0 + BIPUSH 42 + LALOAD + L2I + ISTORE 47 + ALOAD 0 + BIPUSH 43 + LALOAD + L2I + ISTORE 48 + ALOAD 0 + BIPUSH 44 + LALOAD + L2I + ISTORE 49 + ALOAD 0 + BIPUSH 45 + LALOAD + L2I + ISTORE 50 + ALOAD 0 + BIPUSH 46 + LALOAD + L2I + ISTORE 51 + ALOAD 0 + BIPUSH 47 + LALOAD + L2I + ISTORE 52 + ALOAD 0 + BIPUSH 48 + LALOAD + L2I + ISTORE 53 + ALOAD 0 + BIPUSH 49 + LALOAD + L2I + ISTORE 54 + ALOAD 0 + BIPUSH 50 + LALOAD + L2I + ISTORE 55 + ALOAD 0 + BIPUSH 51 + LALOAD + L2I + ISTORE 56 + ALOAD 0 + BIPUSH 52 + LALOAD + L2I + ISTORE 57 + ALOAD 0 + BIPUSH 53 + LALOAD + L2I + ISTORE 58 + ALOAD 0 + BIPUSH 54 + LALOAD + L2I + ISTORE 59 + ALOAD 0 + BIPUSH 55 + LALOAD + L2I + ISTORE 60 + ALOAD 0 + BIPUSH 56 + LALOAD + L2I + ISTORE 61 + ALOAD 0 + BIPUSH 57 + LALOAD + L2I + ISTORE 62 + ALOAD 0 + BIPUSH 58 + LALOAD + L2I + ISTORE 63 + ALOAD 0 + BIPUSH 59 + LALOAD + L2I + ISTORE 64 + ALOAD 0 + BIPUSH 60 + LALOAD + L2I + ISTORE 65 + ALOAD 0 + BIPUSH 61 + LALOAD + L2I + ISTORE 66 + ALOAD 0 + BIPUSH 62 + LALOAD + L2I + ISTORE 67 + ALOAD 0 + BIPUSH 63 + LALOAD + L2I + ISTORE 68 + ALOAD 0 + BIPUSH 64 + LALOAD + L2I + ISTORE 69 + ALOAD 0 + BIPUSH 65 + LALOAD + L2I + ISTORE 70 + ALOAD 0 + BIPUSH 66 + LALOAD + L2I + ISTORE 71 + ALOAD 0 + BIPUSH 67 + LALOAD + L2I + ISTORE 72 + ALOAD 0 + BIPUSH 68 + LALOAD + L2I + ISTORE 73 + ALOAD 0 + BIPUSH 69 + LALOAD + L2I + ISTORE 74 + ALOAD 0 + BIPUSH 70 + LALOAD + L2I + ISTORE 75 + ALOAD 0 + BIPUSH 71 + LALOAD + L2I + ISTORE 76 + ALOAD 0 + BIPUSH 72 + LALOAD + L2I + ISTORE 77 + ALOAD 0 + BIPUSH 73 + LALOAD + L2I + ISTORE 78 + ALOAD 0 + BIPUSH 74 + LALOAD + L2I + ISTORE 79 + ALOAD 0 + BIPUSH 75 + LALOAD + L2I + ISTORE 80 + ALOAD 0 + BIPUSH 76 + LALOAD + L2I + ISTORE 81 + ALOAD 0 + BIPUSH 77 + LALOAD + L2I + ISTORE 82 + ALOAD 0 + BIPUSH 78 + LALOAD + L2I + ISTORE 83 + ALOAD 0 + BIPUSH 79 + LALOAD + L2I + ISTORE 84 + ALOAD 0 + BIPUSH 80 + LALOAD + L2I + ISTORE 85 + ALOAD 0 + BIPUSH 81 + LALOAD + L2I + ISTORE 86 + ALOAD 0 + BIPUSH 82 + LALOAD + L2I + ISTORE 87 + ALOAD 0 + BIPUSH 83 + LALOAD + L2I + ISTORE 88 + ALOAD 0 + BIPUSH 84 + LALOAD + L2I + ISTORE 89 + ALOAD 0 + BIPUSH 85 + LALOAD + L2I + ISTORE 90 + ALOAD 0 + BIPUSH 86 + LALOAD + L2I + ISTORE 91 + ALOAD 0 + BIPUSH 87 + LALOAD + L2I + ISTORE 92 + ALOAD 0 + BIPUSH 88 + LALOAD + L2I + ISTORE 93 + ALOAD 0 + BIPUSH 89 + LALOAD + L2I + ISTORE 94 + ALOAD 0 + BIPUSH 90 + LALOAD + L2I + ISTORE 95 + ALOAD 0 + BIPUSH 91 + LALOAD + L2I + ISTORE 96 + ALOAD 0 + BIPUSH 92 + LALOAD + L2I + ISTORE 97 + ALOAD 0 + BIPUSH 93 + LALOAD + L2I + ISTORE 98 + ALOAD 0 + BIPUSH 94 + LALOAD + L2I + ISTORE 99 + ALOAD 0 + BIPUSH 95 + LALOAD + L2I + ISTORE 100 + ALOAD 0 + BIPUSH 96 + LALOAD + L2I + ISTORE 101 + ALOAD 0 + BIPUSH 97 + LALOAD + L2I + ISTORE 102 + ALOAD 0 + BIPUSH 98 + LALOAD + L2I + ISTORE 103 + ALOAD 0 + BIPUSH 99 + LALOAD + L2I + ISTORE 104 + ALOAD 0 + BIPUSH 100 + LALOAD + L2I + ISTORE 105 + ALOAD 0 + BIPUSH 101 + LALOAD + L2I + ISTORE 106 + ALOAD 0 + BIPUSH 102 + LALOAD + L2I + ISTORE 107 + ALOAD 0 + BIPUSH 103 + LALOAD + L2I + ISTORE 108 + ALOAD 0 + BIPUSH 104 + LALOAD + L2I + ISTORE 109 + ALOAD 0 + BIPUSH 105 + LALOAD + L2I + ISTORE 110 + ALOAD 0 + BIPUSH 106 + LALOAD + L2I + ISTORE 111 + ALOAD 0 + BIPUSH 107 + LALOAD + L2I + ISTORE 112 + ALOAD 0 + BIPUSH 108 + LALOAD + L2I + ISTORE 113 + ALOAD 0 + BIPUSH 109 + LALOAD + L2I + ISTORE 114 + ALOAD 0 + BIPUSH 110 + LALOAD + L2I + ISTORE 115 + ALOAD 0 + BIPUSH 111 + LALOAD + L2I + ISTORE 116 + ALOAD 0 + BIPUSH 112 + LALOAD + L2I + ISTORE 117 + ALOAD 0 + BIPUSH 113 + LALOAD + L2I + ISTORE 118 + ALOAD 0 + BIPUSH 114 + LALOAD + L2I + ISTORE 119 + ALOAD 0 + BIPUSH 115 + LALOAD + L2I + ISTORE 120 + ALOAD 0 + BIPUSH 116 + LALOAD + L2I + ISTORE 121 + ALOAD 0 + BIPUSH 117 + LALOAD + L2I + ISTORE 122 + ALOAD 0 + BIPUSH 118 + LALOAD + L2I + ISTORE 123 + ALOAD 0 + BIPUSH 119 + LALOAD + L2I + ISTORE 124 + ALOAD 0 + BIPUSH 120 + LALOAD + L2I + ISTORE 125 + ALOAD 0 + BIPUSH 121 + LALOAD + L2I + ISTORE 126 + ALOAD 0 + BIPUSH 122 + LALOAD + L2I + ISTORE 127 + ALOAD 0 + BIPUSH 123 + LALOAD + L2I + ISTORE 128 + ALOAD 0 + BIPUSH 124 + LALOAD + L2I + ISTORE 129 + ALOAD 0 + BIPUSH 125 + LALOAD + L2I + ISTORE 130 + ALOAD 0 + BIPUSH 126 + LALOAD + L2I + ISTORE 131 + ALOAD 0 + BIPUSH 127 + LALOAD + L2I + ISTORE 132 + ALOAD 0 + SIPUSH 128 + LALOAD + L2I + ISTORE 133 + ALOAD 0 + SIPUSH 129 + LALOAD + L2I + ISTORE 134 + ALOAD 0 + SIPUSH 130 + LALOAD + L2I + ISTORE 135 + ALOAD 0 + SIPUSH 131 + LALOAD + L2I + ISTORE 136 + ALOAD 0 + SIPUSH 132 + LALOAD + L2I + ISTORE 137 + ALOAD 0 + SIPUSH 133 + LALOAD + L2I + ISTORE 138 + ALOAD 0 + SIPUSH 134 + LALOAD + L2I + ISTORE 139 + ALOAD 0 + SIPUSH 135 + LALOAD + L2I + ISTORE 140 + ALOAD 0 + SIPUSH 136 + LALOAD + L2I + ISTORE 141 + ALOAD 0 + SIPUSH 137 + LALOAD + L2I + ISTORE 142 + ALOAD 0 + SIPUSH 138 + LALOAD + L2I + ISTORE 143 + ALOAD 0 + SIPUSH 139 + LALOAD + L2I + ISTORE 144 + ALOAD 0 + SIPUSH 140 + LALOAD + L2I + ISTORE 145 + ALOAD 0 + SIPUSH 141 + LALOAD + L2I + ISTORE 146 + ALOAD 0 + SIPUSH 142 + LALOAD + L2I + ISTORE 147 + ALOAD 0 + SIPUSH 143 + LALOAD + L2I + ISTORE 148 + ALOAD 0 + SIPUSH 144 + LALOAD + L2I + ISTORE 149 + ALOAD 0 + SIPUSH 145 + LALOAD + L2I + ISTORE 150 + ALOAD 0 + SIPUSH 146 + LALOAD + L2I + ISTORE 151 + ALOAD 0 + SIPUSH 147 + LALOAD + L2I + ISTORE 152 + ALOAD 0 + SIPUSH 148 + LALOAD + L2I + ISTORE 153 + ALOAD 0 + SIPUSH 149 + LALOAD + L2I + ISTORE 154 + ALOAD 0 + SIPUSH 150 + LALOAD + L2I + ISTORE 155 + ALOAD 0 + SIPUSH 151 + LALOAD + L2I + ISTORE 156 + ALOAD 0 + SIPUSH 152 + LALOAD + L2I + ISTORE 157 + ALOAD 0 + SIPUSH 153 + LALOAD + L2I + ISTORE 158 + ALOAD 0 + SIPUSH 154 + LALOAD + L2I + ISTORE 159 + ALOAD 0 + SIPUSH 155 + LALOAD + L2I + ISTORE 160 + ALOAD 0 + SIPUSH 156 + LALOAD + L2I + ISTORE 161 + ALOAD 0 + SIPUSH 157 + LALOAD + L2I + ISTORE 162 + ALOAD 0 + SIPUSH 158 + LALOAD + L2I + ISTORE 163 + ALOAD 0 + SIPUSH 159 + LALOAD + L2I + ISTORE 164 + ALOAD 0 + SIPUSH 160 + LALOAD + L2I + ISTORE 165 + ALOAD 0 + SIPUSH 161 + LALOAD + L2I + ISTORE 166 + ALOAD 0 + SIPUSH 162 + LALOAD + L2I + ISTORE 167 + ALOAD 0 + SIPUSH 163 + LALOAD + L2I + ISTORE 168 + ALOAD 0 + SIPUSH 164 + LALOAD + L2I + ISTORE 169 + ALOAD 0 + SIPUSH 165 + LALOAD + L2I + ISTORE 170 + ALOAD 0 + SIPUSH 166 + LALOAD + L2I + ISTORE 171 + ALOAD 0 + SIPUSH 167 + LALOAD + L2I + ISTORE 172 + ALOAD 0 + SIPUSH 168 + LALOAD + L2I + ISTORE 173 + ALOAD 0 + SIPUSH 169 + LALOAD + L2I + ISTORE 174 + ALOAD 0 + SIPUSH 170 + LALOAD + L2I + ISTORE 175 + ALOAD 0 + SIPUSH 171 + LALOAD + L2I + ISTORE 176 + ALOAD 0 + SIPUSH 172 + LALOAD + L2I + ISTORE 177 + ALOAD 0 + SIPUSH 173 + LALOAD + L2I + ISTORE 178 + ALOAD 0 + SIPUSH 174 + LALOAD + L2I + ISTORE 179 + ALOAD 0 + SIPUSH 175 + LALOAD + L2I + ISTORE 180 + ALOAD 0 + SIPUSH 176 + LALOAD + L2I + ISTORE 181 + ALOAD 0 + SIPUSH 177 + LALOAD + L2I + ISTORE 182 + ALOAD 0 + SIPUSH 178 + LALOAD + L2I + ISTORE 183 + ALOAD 0 + SIPUSH 179 + LALOAD + L2I + ISTORE 184 + ALOAD 0 + SIPUSH 180 + LALOAD + L2I + ISTORE 185 + ALOAD 0 + SIPUSH 181 + LALOAD + L2I + ISTORE 186 + ALOAD 0 + SIPUSH 182 + LALOAD + L2I + ISTORE 187 + ALOAD 0 + SIPUSH 183 + LALOAD + L2I + ISTORE 188 + ALOAD 0 + SIPUSH 184 + LALOAD + L2I + ISTORE 189 + ALOAD 0 + SIPUSH 185 + LALOAD + L2I + ISTORE 190 + ALOAD 0 + SIPUSH 186 + LALOAD + L2I + ISTORE 191 + ALOAD 0 + SIPUSH 187 + LALOAD + L2I + ISTORE 192 + ALOAD 0 + SIPUSH 188 + LALOAD + L2I + ISTORE 193 + ALOAD 0 + SIPUSH 189 + LALOAD + L2I + ISTORE 194 + ALOAD 0 + SIPUSH 190 + LALOAD + L2I + ISTORE 195 + ALOAD 0 + SIPUSH 191 + LALOAD + L2I + ISTORE 196 + ALOAD 0 + SIPUSH 192 + LALOAD + L2I + ISTORE 197 + ALOAD 0 + SIPUSH 193 + LALOAD + L2I + ISTORE 198 + ALOAD 0 + SIPUSH 194 + LALOAD + L2I + ISTORE 199 + ALOAD 0 + SIPUSH 195 + LALOAD + L2I + ISTORE 200 + ALOAD 0 + SIPUSH 196 + LALOAD + L2I + ISTORE 201 + ALOAD 0 + SIPUSH 197 + LALOAD + L2I + ISTORE 202 + ALOAD 0 + SIPUSH 198 + LALOAD + L2I + ISTORE 203 + ALOAD 0 + SIPUSH 199 + LALOAD + L2I + ISTORE 204 + ALOAD 0 + SIPUSH 200 + LALOAD + L2I + ISTORE 205 + ALOAD 0 + SIPUSH 201 + LALOAD + L2I + ISTORE 206 + ALOAD 0 + SIPUSH 202 + LALOAD + L2I + ISTORE 207 + ALOAD 0 + SIPUSH 203 + LALOAD + L2I + ISTORE 208 + ALOAD 0 + SIPUSH 204 + LALOAD + L2I + ISTORE 209 + ALOAD 0 + SIPUSH 205 + LALOAD + L2I + ISTORE 210 + ALOAD 0 + SIPUSH 206 + LALOAD + L2I + ISTORE 211 + ALOAD 0 + SIPUSH 207 + LALOAD + L2I + ISTORE 212 + ALOAD 0 + SIPUSH 208 + LALOAD + L2I + ISTORE 213 + ALOAD 0 + SIPUSH 209 + LALOAD + L2I + ISTORE 214 + ALOAD 0 + SIPUSH 210 + LALOAD + L2I + ISTORE 215 + ALOAD 0 + SIPUSH 211 + LALOAD + L2I + ISTORE 216 + ALOAD 0 + SIPUSH 212 + LALOAD + L2I + ISTORE 217 + ALOAD 0 + SIPUSH 213 + LALOAD + L2I + ISTORE 218 + ALOAD 0 + SIPUSH 214 + LALOAD + L2I + ISTORE 219 + ALOAD 0 + SIPUSH 215 + LALOAD + L2I + ISTORE 220 + ALOAD 0 + SIPUSH 216 + LALOAD + L2I + ISTORE 221 + ALOAD 0 + SIPUSH 217 + LALOAD + L2I + ISTORE 222 + ALOAD 0 + SIPUSH 218 + LALOAD + L2I + ISTORE 223 + ALOAD 0 + SIPUSH 219 + LALOAD + L2I + ISTORE 224 + ALOAD 0 + SIPUSH 220 + LALOAD + L2I + ISTORE 225 + ALOAD 0 + SIPUSH 221 + LALOAD + L2I + ISTORE 226 + ALOAD 0 + SIPUSH 222 + LALOAD + L2I + ISTORE 227 + ALOAD 0 + SIPUSH 223 + LALOAD + L2I + ISTORE 228 + ALOAD 0 + SIPUSH 224 + LALOAD + L2I + ISTORE 229 + ALOAD 0 + SIPUSH 225 + LALOAD + L2I + ISTORE 230 + ALOAD 0 + SIPUSH 226 + LALOAD + L2I + ISTORE 231 + ALOAD 0 + SIPUSH 227 + LALOAD + L2I + ISTORE 232 + ALOAD 0 + SIPUSH 228 + LALOAD + L2I + ISTORE 233 + ALOAD 0 + SIPUSH 229 + LALOAD + L2I + ISTORE 234 + ALOAD 0 + SIPUSH 230 + LALOAD + L2I + ISTORE 235 + ALOAD 0 + SIPUSH 231 + LALOAD + L2I + ISTORE 236 + ALOAD 0 + SIPUSH 232 + LALOAD + L2I + ISTORE 237 + ALOAD 0 + SIPUSH 233 + LALOAD + L2I + ISTORE 238 + ALOAD 0 + SIPUSH 234 + LALOAD + L2I + ISTORE 239 + ALOAD 0 + SIPUSH 235 + LALOAD + L2I + ISTORE 240 + ALOAD 0 + SIPUSH 236 + LALOAD + L2I + ISTORE 241 + ALOAD 0 + SIPUSH 237 + LALOAD + L2I + ISTORE 242 + ALOAD 0 + SIPUSH 238 + LALOAD + L2I + ISTORE 243 + ALOAD 0 + SIPUSH 239 + LALOAD + L2I + ISTORE 244 + ALOAD 0 + SIPUSH 240 + LALOAD + L2I + ISTORE 245 + ALOAD 0 + SIPUSH 241 + LALOAD + L2I + ISTORE 246 + ALOAD 0 + SIPUSH 242 + LALOAD + L2I + ISTORE 247 + ALOAD 0 + SIPUSH 243 + LALOAD + L2I + ISTORE 248 + ALOAD 0 + SIPUSH 244 + LALOAD + L2I + ISTORE 249 + ALOAD 0 + SIPUSH 245 + LALOAD + L2I + ISTORE 250 + ALOAD 0 + SIPUSH 246 + LALOAD + L2I + ISTORE 251 + ALOAD 0 + SIPUSH 247 + LALOAD + L2I + ISTORE 252 + ALOAD 0 + SIPUSH 248 + LALOAD + L2I + ISTORE 253 + ALOAD 0 + SIPUSH 249 + LALOAD + L2I + ISTORE 254 + ALOAD 0 + SIPUSH 250 + LALOAD + L2I + ISTORE 255 + ALOAD 0 + SIPUSH 251 + LALOAD + L2I + ISTORE 256 + ALOAD 0 + SIPUSH 252 + LALOAD + L2I + ISTORE 257 + ALOAD 0 + SIPUSH 253 + LALOAD + L2I + ISTORE 258 + ALOAD 0 + SIPUSH 254 + LALOAD + L2I + ISTORE 259 + ALOAD 0 + SIPUSH 255 + LALOAD + L2I + ISTORE 260 + ALOAD 0 + SIPUSH 256 + LALOAD + L2I + ISTORE 261 + ALOAD 0 + SIPUSH 257 + LALOAD + L2I + ISTORE 262 + ALOAD 0 + SIPUSH 258 + LALOAD + L2I + ISTORE 263 + ALOAD 0 + SIPUSH 259 + LALOAD + L2I + ISTORE 264 + ALOAD 0 + SIPUSH 260 + LALOAD + L2I + ISTORE 265 + ALOAD 0 + SIPUSH 261 + LALOAD + L2I + ISTORE 266 + ALOAD 0 + SIPUSH 262 + LALOAD + L2I + ISTORE 267 + ALOAD 0 + SIPUSH 263 + LALOAD + L2I + ISTORE 268 + ALOAD 0 + SIPUSH 264 + LALOAD + L2I + ISTORE 269 + ALOAD 0 + SIPUSH 265 + LALOAD + L2I + ISTORE 270 + ALOAD 0 + SIPUSH 266 + LALOAD + L2I + ISTORE 271 + ALOAD 0 + SIPUSH 267 + LALOAD + L2I + ISTORE 272 + ALOAD 0 + SIPUSH 268 + LALOAD + L2I + ISTORE 273 + ALOAD 0 + SIPUSH 269 + LALOAD + L2I + ISTORE 274 + ALOAD 0 + SIPUSH 270 + LALOAD + L2I + ISTORE 275 + ALOAD 0 + SIPUSH 271 + LALOAD + L2I + ISTORE 276 + ALOAD 0 + SIPUSH 272 + LALOAD + L2I + ISTORE 277 + ALOAD 0 + SIPUSH 273 + LALOAD + L2I + ISTORE 278 + ALOAD 0 + SIPUSH 274 + LALOAD + L2I + ISTORE 279 + ALOAD 0 + SIPUSH 275 + LALOAD + L2I + ISTORE 280 + ALOAD 0 + SIPUSH 276 + LALOAD + L2I + ISTORE 281 + ALOAD 0 + SIPUSH 277 + LALOAD + L2I + ISTORE 282 + ALOAD 0 + SIPUSH 278 + LALOAD + L2I + ISTORE 283 + ALOAD 0 + SIPUSH 279 + LALOAD + L2I + ISTORE 284 + ALOAD 0 + SIPUSH 280 + LALOAD + L2I + ISTORE 285 + ALOAD 0 + SIPUSH 281 + LALOAD + L2I + ISTORE 286 + ALOAD 0 + SIPUSH 282 + LALOAD + L2I + ISTORE 287 + ALOAD 0 + SIPUSH 283 + LALOAD + L2I + ISTORE 288 + ALOAD 0 + SIPUSH 284 + LALOAD + L2I + ISTORE 289 + ALOAD 0 + SIPUSH 285 + LALOAD + L2I + ISTORE 290 + ALOAD 0 + SIPUSH 286 + LALOAD + L2I + ISTORE 291 + ALOAD 0 + SIPUSH 287 + LALOAD + L2I + ISTORE 292 + ALOAD 0 + SIPUSH 288 + LALOAD + L2I + ISTORE 293 + ALOAD 0 + SIPUSH 289 + LALOAD + L2I + ISTORE 294 + ALOAD 0 + SIPUSH 290 + LALOAD + L2I + ISTORE 295 + ALOAD 0 + SIPUSH 291 + LALOAD + L2I + ISTORE 296 + ALOAD 0 + SIPUSH 292 + LALOAD + L2I + ISTORE 297 + ALOAD 0 + SIPUSH 293 + LALOAD + L2I + ISTORE 298 + ALOAD 0 + SIPUSH 294 + LALOAD + L2I + ISTORE 299 + ALOAD 0 + SIPUSH 295 + LALOAD + L2I + ISTORE 300 + ALOAD 0 + SIPUSH 296 + LALOAD + L2I + ISTORE 301 + ALOAD 0 + SIPUSH 297 + LALOAD + L2I + ISTORE 302 + ALOAD 0 + SIPUSH 298 + LALOAD + L2I + ISTORE 303 + ALOAD 0 + SIPUSH 299 + LALOAD + L2I + ISTORE 304 + ILOAD 3 + ILOAD 304 + IADD + IRETURN + + public static call_1(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)[J + ALOAD 2 + ALOAD 1 + ALOAD 0 + INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_1 ([JLrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I + I2L + LSTORE 4 + ICONST_1 + NEWARRAY T_LONG + DUP + ICONST_0 + LLOAD 4 + LASTORE + ARETURN +} + +final class run/endive/$gen/CompiledMachineMachineCall { + + public ()V + ALOAD 0 + INVOKESPECIAL java/lang/Object. ()V + RETURN + + 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 From 84e94609c8eef5c576a2aa6f4a5eb89de5cf3c8d Mon Sep 17 00:00:00 2001 From: andreatp Date: Wed, 17 Jun 2026 10:56:10 +0100 Subject: [PATCH 15/20] fix: remove unused variable hasRefReturn (checkstyle) --- .../main/java/run/endive/codegen/ModuleInterfaceCodegen.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/codegen/src/main/java/run/endive/codegen/ModuleInterfaceCodegen.java b/codegen/src/main/java/run/endive/codegen/ModuleInterfaceCodegen.java index 17965c96d..9cadcfcc9 100644 --- a/codegen/src/main/java/run/endive/codegen/ModuleInterfaceCodegen.java +++ b/codegen/src/main/java/run/endive/codegen/ModuleInterfaceCodegen.java @@ -562,11 +562,9 @@ exportApplyHandle, new IntegerLiteralExpr("0")), // Build CallResult with positional long[] and Object[] var longSlots = new ArrayList(); var refSlots = new ArrayList(); - boolean hasRefReturn = false; for (int ri = 0; ri < importType.returns().size(); ri++) { var retType = importType.returns().get(ri); if (retType.isObjectRef()) { - hasRefReturn = true; longSlots.add(new IntegerLiteralExpr("0")); // placeholder, will be set after refSlots.add(new NullLiteralExpr()); From b8272c7663efa36ce41dd7d86b848b78010889e2 Mon Sep 17 00:00:00 2001 From: andreatp Date: Wed, 17 Jun 2026 12:13:34 +0100 Subject: [PATCH 16/20] fix: remove dead code in ModuleInterfaceCodegen (unused collections, -Werror) --- .../endive/codegen/ModuleInterfaceCodegen.java | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/codegen/src/main/java/run/endive/codegen/ModuleInterfaceCodegen.java b/codegen/src/main/java/run/endive/codegen/ModuleInterfaceCodegen.java index 9cadcfcc9..9af881a87 100644 --- a/codegen/src/main/java/run/endive/codegen/ModuleInterfaceCodegen.java +++ b/codegen/src/main/java/run/endive/codegen/ModuleInterfaceCodegen.java @@ -559,21 +559,6 @@ exportApplyHandle, new IntegerLiteralExpr("0")), new NullLiteralExpr(), new NullLiteralExpr())))); } else { - // Build CallResult with positional long[] and Object[] - var longSlots = new ArrayList(); - var refSlots = new ArrayList(); - for (int ri = 0; ri < importType.returns().size(); ri++) { - var retType = importType.returns().get(ri); - if (retType.isObjectRef()) { - longSlots.add(new IntegerLiteralExpr("0")); - // placeholder, will be set after - refSlots.add(new NullLiteralExpr()); - } else { - longSlots.add(new IntegerLiteralExpr("0")); - refSlots.add(new NullLiteralExpr()); - } - } - if (importType.returns().size() == 1) { var retType = importType.returns().get(0); if (retType.isObjectRef()) { From 2acf59d1f1395c29cd992e5ce4e1214112b82fe9 Mon Sep 17 00:00:00 2001 From: andreatp Date: Wed, 17 Jun 2026 16:15:37 +0100 Subject: [PATCH 17/20] =?UTF-8?q?fix:=20PR=20review=20bugs=20=E2=80=94=20R?= =?UTF-8?q?ETURN=5FCALL=20refs,=20cross-instance=20refs,=20pop()=20leak,?= =?UTF-8?q?=20perf?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BUG-1: RETURN_CALL/RETURN_CALL_INDIRECT on imported functions now dispatch to applyWithRefs when function type has Object refs. BUG-2: CALL_INDIRECT cross-instance dispatch uses callWithRefs when function type has Object refs. Linear memory path unchanged. BUG-3: MStack.pop() now clears refs[count] to prevent stale Object refs from leaking. doControlTransfer reads ref BEFORE pop to avoid the clearing race. BUG-4: GcStressTest uses assertEquals instead of assert. CONCERN-1: FunctionType.hasObjectRefParams/Returns() cached lazily (computed on first call, no stream allocation on hot paths). CONCERN-2: WasmStruct/WasmArray skip Object[] allocation for numeric-only types. fieldRefs/elementRefs is null until first setFieldRef/setRef call. --- .../java/run/endive/testing/GcStressTest.java | 4 +- .../endive/runtime/InterpreterMachine.java | 64 ++++++++++++++----- .../main/java/run/endive/runtime/MStack.java | 3 + .../java/run/endive/runtime/StackFrame.java | 5 +- .../java/run/endive/runtime/WasmArray.java | 9 ++- .../java/run/endive/runtime/WasmStruct.java | 9 ++- .../run/endive/wasm/types/FunctionType.java | 30 ++++++++- 7 files changed, 96 insertions(+), 28 deletions(-) diff --git a/machine-tests/src/test/java/run/endive/testing/GcStressTest.java b/machine-tests/src/test/java/run/endive/testing/GcStressTest.java index a8a05e9c7..0722c90bb 100644 --- a/machine-tests/src/test/java/run/endive/testing/GcStressTest.java +++ b/machine-tests/src/test/java/run/endive/testing/GcStressTest.java @@ -1,5 +1,7 @@ package run.endive.testing; +import static org.junit.jupiter.api.Assertions.assertEquals; + import java.util.function.Function; import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; @@ -40,7 +42,7 @@ public void gcCollectsUnreachableStructs( for (int i = 0; i < 100; i++) { var result = allocateChain.apply(10_000); // Last struct in chain has val = n-1 - assert result[0] == 9999 : "expected 9999 but got " + result[0]; + assertEquals(9999L, result[0]); } } } diff --git a/runtime/src/main/java/run/endive/runtime/InterpreterMachine.java b/runtime/src/main/java/run/endive/runtime/InterpreterMachine.java index c89600ea2..ac00751a9 100644 --- a/runtime/src/main/java/run/endive/runtime/InterpreterMachine.java +++ b/runtime/src/main/java/run/endive/runtime/InterpreterMachine.java @@ -2882,12 +2882,15 @@ private static StackFrame RETURN_CALL( var imprt = instance.imports().function(funcId); try { - var results = imprt.handle().apply(instance, args); - // a host function can return null or an array of ints - // which we will push onto the stack - if (results != null) { - for (var result : results) { - stack.push(result); + if (type.hasObjectRefParams() || type.hasObjectRefReturns()) { + var cr = imprt.handle().applyWithRefs(instance, args, refArgs); + pushCallResult(cr, type, stack); + } else { + var results = imprt.handle().apply(instance, args); + if (results != null) { + for (var result : results) { + stack.push(result); + } } } } catch (WasmException e) { @@ -2971,12 +2974,15 @@ private static StackFrame RETURN_CALL_INDIRECT( var imprt = instance.imports().function(funcId); try { - var results = imprt.handle().apply(instance, args); - // a host function can return null or an array of ints - // which we will push onto the stack - if (results != null) { - for (var result : results) { - stack.push(result); + if (type.hasObjectRefParams() || type.hasObjectRefReturns()) { + var cr = imprt.handle().applyWithRefs(instance, args, refArgs); + pushCallResult(cr, type, stack); + } else { + var results = imprt.handle().apply(instance, args); + if (results != null) { + for (var result : results) { + stack.push(result); + } } } } catch (WasmException e) { @@ -3058,10 +3064,15 @@ private void CALL_INDIRECT( call(stack, instance, callStack, funcId, args, refArgs, null, false); } else { checkInterruption(); - var results = refInstance.getMachine().call(funcId, args); - if (results != null) { - for (var result : results) { - stack.push(result); + if (type.hasObjectRefParams() || type.hasObjectRefReturns()) { + var cr = refInstance.getMachine().callWithRefs(funcId, args, refArgs); + pushCallResult(cr, type, stack); + } else { + var results = refInstance.getMachine().call(funcId, args); + if (results != null) { + for (var result : results) { + stack.push(result); + } } } } @@ -3865,6 +3876,27 @@ private static void ARRAY_INIT_ELEM(MStack stack, Instance instance, Operands op } } + private static void pushCallResult(CallResult cr, FunctionType type, MStack stack) { + if (cr == null) { + return; + } + int slot = 0; + for (int i = 0; i < type.returns().size(); i++) { + var retType = type.returns().get(i); + if (retType.isObjectRef()) { + stack.pushRef(cr.refResult(slot)); + slot++; + } else if (retType.equals(ValType.V128)) { + stack.push(cr.longResult(slot)); + stack.push(cr.longResult(slot + 1)); + slot += 2; + } else { + stack.push(cr.longResult(slot)); + slot++; + } + } + } + private static void pushExceptionArgs(WasmException exception, MStack stack) { var refArgs = exception.refArgs(); var tag = exception.instance().tag(exception.tagIdx()); diff --git a/runtime/src/main/java/run/endive/runtime/MStack.java b/runtime/src/main/java/run/endive/runtime/MStack.java index 3956273e1..b3f3babe9 100644 --- a/runtime/src/main/java/run/endive/runtime/MStack.java +++ b/runtime/src/main/java/run/endive/runtime/MStack.java @@ -67,6 +67,9 @@ public void pushRef(Object ref) { public long pop() { count--; + if (refs != null) { + refs[count] = null; + } return elements[count]; } diff --git a/runtime/src/main/java/run/endive/runtime/StackFrame.java b/runtime/src/main/java/run/endive/runtime/StackFrame.java index 9cb6ba6d0..889efe324 100644 --- a/runtime/src/main/java/run/endive/runtime/StackFrame.java +++ b/runtime/src/main/java/run/endive/runtime/StackFrame.java @@ -236,11 +236,10 @@ static void doControlTransfer(CtrlFrame ctrlFrame, MStack stack) { 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; + returnRefs[i] = stackRefArray[stack.size() - 1]; } + returns[i] = stack.pop(); } } diff --git a/runtime/src/main/java/run/endive/runtime/WasmArray.java b/runtime/src/main/java/run/endive/runtime/WasmArray.java index b452aed0b..b28554bac 100644 --- a/runtime/src/main/java/run/endive/runtime/WasmArray.java +++ b/runtime/src/main/java/run/endive/runtime/WasmArray.java @@ -9,7 +9,7 @@ public final class WasmArray implements WasmGcRef { private final int typeIdx; private final long[] elements; - private final Object[] elementRefs; + private Object[] elementRefs; public WasmArray(int typeIdx, long[] elements) { this(typeIdx, elements, null); @@ -18,7 +18,7 @@ public WasmArray(int typeIdx, long[] elements) { public WasmArray(int typeIdx, long[] elements, Object[] elementRefs) { this.typeIdx = typeIdx; this.elements = elements; - this.elementRefs = (elementRefs != null) ? elementRefs : new Object[elements.length]; + this.elementRefs = elementRefs; } @Override @@ -35,10 +35,13 @@ public void set(int idx, long value) { } public Object getRef(int idx) { - return elementRefs[idx]; + return elementRefs != null ? elementRefs[idx] : null; } public void setRef(int idx, Object ref) { + if (elementRefs == null) { + elementRefs = new Object[elements.length]; + } elementRefs[idx] = ref; } diff --git a/runtime/src/main/java/run/endive/runtime/WasmStruct.java b/runtime/src/main/java/run/endive/runtime/WasmStruct.java index 76aee0d76..3ea42c6a0 100644 --- a/runtime/src/main/java/run/endive/runtime/WasmStruct.java +++ b/runtime/src/main/java/run/endive/runtime/WasmStruct.java @@ -9,7 +9,7 @@ public final class WasmStruct implements WasmGcRef { private final int typeIdx; private final long[] fields; - private final Object[] fieldRefs; + private Object[] fieldRefs; public WasmStruct(int typeIdx, long[] fields) { this(typeIdx, fields, null); @@ -18,7 +18,7 @@ public WasmStruct(int typeIdx, long[] fields) { public WasmStruct(int typeIdx, long[] fields, Object[] fieldRefs) { this.typeIdx = typeIdx; this.fields = fields; - this.fieldRefs = (fieldRefs != null) ? fieldRefs : new Object[fields.length]; + this.fieldRefs = fieldRefs; } @Override @@ -35,10 +35,13 @@ public void setField(int idx, long value) { } public Object fieldRef(int idx) { - return fieldRefs[idx]; + return fieldRefs != null ? fieldRefs[idx] : null; } public void setFieldRef(int idx, Object ref) { + if (fieldRefs == null) { + fieldRefs = new Object[fields.length]; + } fieldRefs[idx] = ref; } diff --git a/wasm/src/main/java/run/endive/wasm/types/FunctionType.java b/wasm/src/main/java/run/endive/wasm/types/FunctionType.java index 9312e9da4..d1192f9c1 100644 --- a/wasm/src/main/java/run/endive/wasm/types/FunctionType.java +++ b/wasm/src/main/java/run/endive/wasm/types/FunctionType.java @@ -20,12 +20,38 @@ public List returns() { return returns; } + private int cachedObjRefFlags = + -1; // -1 = not computed, 0 = none, 1 = params, 2 = returns, 3 = both + public boolean hasObjectRefParams() { - return params.stream().anyMatch(ValType::isObjectRef); + if (cachedObjRefFlags < 0) { + computeObjRefFlags(); + } + return (cachedObjRefFlags & 1) != 0; } public boolean hasObjectRefReturns() { - return returns.stream().anyMatch(ValType::isObjectRef); + if (cachedObjRefFlags < 0) { + computeObjRefFlags(); + } + return (cachedObjRefFlags & 2) != 0; + } + + private void computeObjRefFlags() { + int flags = 0; + for (var p : params) { + if (p.isObjectRef()) { + flags |= 1; + break; + } + } + for (var r : returns) { + if (r.isObjectRef()) { + flags |= 2; + break; + } + } + cachedObjRefFlags = flags; } public boolean paramsMatch(FunctionType other) { From 0246f50979de5ca472c387e00d9c03814b5edfda Mon Sep 17 00:00:00 2001 From: andreatp Date: Wed, 17 Jun 2026 18:11:58 +0100 Subject: [PATCH 18/20] fix: ARRAY_COPY isReference, Builder isGcReference, V128 slot, struct alloc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ARRAY_COPY: use isObjectRef() not isReference() — funcref arrays stay in long[] path, not incorrectly routed through Object[] - Builder.isGcReference: remove typeIdx>=0 fallthrough that misclassified concrete func types as GC refs without TypeSection - CallResult V128 slot indexing: use composite slot counter (not per-return index) in emitUnboxCallResult and emitTailCallCheck - emitBoxFieldsForStruct: skip Object[] allocation when no ref fields (null instead of empty array) --- .../endive/compiler/internal/Compiler.java | 14 ++++-- .../endive/compiler/internal/Emitters.java | 43 +++++++++++++------ .../ApprovalTest.verifyGc.approved.txt | 3 +- .../endive/runtime/InterpreterMachine.java | 4 +- .../java/run/endive/wasm/types/ValType.java | 3 +- 5 files changed, 43 insertions(+), 24 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 1415621b6..0d955fc61 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/Compiler.java +++ b/compiler/src/main/java/run/endive/compiler/internal/Compiler.java @@ -1658,21 +1658,29 @@ private static void emitUnboxCallResult(FunctionType type, InstructionAdapter as asm.iconst(type.returns().size()); asm.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object"); + int slot = 0; for (int i = 0; i < type.returns().size(); i++) { asm.dup(); asm.iconst(i); ValType retType = type.returns().get(i); if (retType.isObjectRef()) { asm.load(crSlot, OBJECT_TYPE); - asm.iconst(i); + asm.iconst(slot); asm.invokevirtual( CALL_RESULT_INTERNAL_NAME, "refResult", "(I)Ljava/lang/Object;", false); + slot++; + } else if (retType.equals(ValType.V128)) { + asm.load(crSlot, OBJECT_TYPE); + asm.iconst(slot); + asm.invokevirtual(CALL_RESULT_INTERNAL_NAME, "longResult", "(I)J", false); + asm.invokestatic("java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false); + slot += 2; } else { asm.load(crSlot, OBJECT_TYPE); - asm.iconst(i); + asm.iconst(slot); asm.invokevirtual(CALL_RESULT_INTERNAL_NAME, "longResult", "(I)J", false); - // box long into Long for Object[] asm.invokestatic("java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false); + slot++; } asm.astore(OBJECT_TYPE); } 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 ad6bcec5a..a00d34191 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/Emitters.java +++ b/compiler/src/main/java/run/endive/compiler/internal/Emitters.java @@ -295,20 +295,25 @@ private static void emitBoxFieldsForStruct( slot += slotCount(valType); } - // Create Object[] for ref fields - asm.iconst(types.size()); - asm.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object"); + // Create Object[] for ref fields (null if none) + boolean hasRefs = types.stream().anyMatch(ValType::isObjectRef); + if (hasRefs) { + 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.isObjectRef()) { - asm.dup(); - asm.iconst(i); - asm.load(slot, asmType(valType)); - asm.astore(OBJECT_TYPE); + slot = ctx.tempSlot(); + for (int i = 0; i < types.size(); i++) { + ValType valType = types.get(i); + if (valType.isObjectRef()) { + asm.dup(); + asm.iconst(i); + asm.load(slot, asmType(valType)); + asm.astore(OBJECT_TYPE); + } + slot += slotCount(valType); } - slot += slotCount(valType); + } else { + asm.aconst(null); } } @@ -1373,24 +1378,34 @@ private static void emitTailCallCheck( asm.iconst(returns.size()); asm.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object"); + int slot = 0; for (int i = 0; i < returns.size(); i++) { asm.dup(); asm.iconst(i); ValType retType = returns.get(i); if (retType.isObjectRef()) { asm.load(crSlot, OBJECT_TYPE); - asm.iconst(i); + asm.iconst(slot); asm.invokevirtual( "run/endive/runtime/CallResult", "refResult", "(I)Ljava/lang/Object;", false); + slot++; + } else if (retType.equals(ValType.V128)) { + asm.load(crSlot, OBJECT_TYPE); + asm.iconst(slot); + asm.invokevirtual( + "run/endive/runtime/CallResult", "longResult", "(I)J", false); + asm.invokestatic("java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false); + slot += 2; } else { asm.load(crSlot, OBJECT_TYPE); - asm.iconst(i); + asm.iconst(slot); asm.invokevirtual( "run/endive/runtime/CallResult", "longResult", "(I)J", false); asm.invokestatic("java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false); + slot++; } asm.astore(OBJECT_TYPE); } 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 54d1563ac..d27916022 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 @@ -17,8 +17,7 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { ILOAD 5 I2L LASTORE - ICONST_2 - ANEWARRAY java/lang/Object + ACONST_NULL ICONST_0 ALOAD 3 INVOKESTATIC run/endive/$gen/CompiledMachineShaded.structNew ([J[Ljava/lang/Object;ILrun/endive/runtime/Instance;)Ljava/lang/Object; diff --git a/runtime/src/main/java/run/endive/runtime/InterpreterMachine.java b/runtime/src/main/java/run/endive/runtime/InterpreterMachine.java index ac00751a9..057ee831c 100644 --- a/runtime/src/main/java/run/endive/runtime/InterpreterMachine.java +++ b/runtime/src/main/java/run/endive/runtime/InterpreterMachine.java @@ -3791,9 +3791,7 @@ private static void ARRAY_COPY(MStack stack, Instance instance, Operands operand 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(); + boolean isRef = dstAt.fieldType().storageType().isObjectRef(); // Handle overlapping copies if (dstOffset <= srcOffset) { for (int i = 0; i < len; i++) { 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 482f69930..ced274da4 100644 --- a/wasm/src/main/java/run/endive/wasm/types/ValType.java +++ b/wasm/src/main/java/run/endive/wasm/types/ValType.java @@ -764,8 +764,7 @@ public boolean isGcReference(TypeSection ts) { if (typeIdx >= 0 && ts != null) { return isConcreteInAnyHierarchy(typeIdx, ts); } - return typeIdx >= 0 - || typeIdx == TypeIdxCode.ANY.code() + return typeIdx == TypeIdxCode.ANY.code() || typeIdx == TypeIdxCode.EQ.code() || typeIdx == TypeIdxCode.I31.code() || typeIdx == TypeIdxCode.STRUCT.code() From a224060ef86fa9b1a874c2e069eb2ca96fe21402 Mon Sep 17 00:00:00 2001 From: andreatp Date: Thu, 18 Jun 2026 11:22:05 +0100 Subject: [PATCH 19/20] test: add funcref array.copy test (exercises ARRAY_COPY isObjectRef fix) --- .../testing/GcArrayCopyFuncrefTest.java | 39 ++++++++++++++++++ .../compiled/gc_array_copy_funcref.wat.wasm | Bin 0 -> 144 bytes .../resources/wat/gc_array_copy_funcref.wat | 22 ++++++++++ 3 files changed, 61 insertions(+) create mode 100644 machine-tests/src/test/java/run/endive/testing/GcArrayCopyFuncrefTest.java create mode 100644 wasm-corpus/src/main/resources/compiled/gc_array_copy_funcref.wat.wasm create mode 100644 wasm-corpus/src/main/resources/wat/gc_array_copy_funcref.wat diff --git a/machine-tests/src/test/java/run/endive/testing/GcArrayCopyFuncrefTest.java b/machine-tests/src/test/java/run/endive/testing/GcArrayCopyFuncrefTest.java new file mode 100644 index 000000000..5ac535df5 --- /dev/null +++ b/machine-tests/src/test/java/run/endive/testing/GcArrayCopyFuncrefTest.java @@ -0,0 +1,39 @@ +package run.endive.testing; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.function.Function; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import run.endive.compiler.MachineFactoryCompiler; +import run.endive.corpus.CorpusResources; +import run.endive.runtime.Instance; +import run.endive.runtime.InterpreterMachine; +import run.endive.wasm.Parser; +import run.endive.wasm.WasmModule; + +public class GcArrayCopyFuncrefTest { + + private static final WasmModule MODULE = + Parser.parse(CorpusResources.getResource("compiled/gc_array_copy_funcref.wat.wasm")); + + private static Stream machineImplementations() { + return Stream.of( + Arguments.of( + (Function) + (b) -> b.withMachineFactory(InterpreterMachine::new)), + Arguments.of( + (Function) + (b) -> b.withMachineFactory(MachineFactoryCompiler::compile))); + } + + @ParameterizedTest + @MethodSource("machineImplementations") + public void copyFuncrefArray(Function machineInject) { + var instance = machineInject.apply(Instance.builder(MODULE)).build(); + var result = instance.export("copy_funcref_array").apply(); + assertEquals(0, result[0]); + } +} diff --git a/wasm-corpus/src/main/resources/compiled/gc_array_copy_funcref.wat.wasm b/wasm-corpus/src/main/resources/compiled/gc_array_copy_funcref.wat.wasm new file mode 100644 index 0000000000000000000000000000000000000000..d464eb1d94aff9f0e2b134f7d6889138a12e6cd1 GIT binary patch literal 144 zcmW;FF%p6>6h+bZ{zq}hSZHAltu}6fjR=_px&RZPpmL469U8&v&Z#c!7zltyYv47o z2H&azNpHNmepsq@?p$oz%Eo9HFnfx?JSd)Dl(HUK>`7TLXNer;&I|uq4L=6=q_d&n ai8%NnQ+qDyQJNy6yM;zV& literal 0 HcmV?d00001 diff --git a/wasm-corpus/src/main/resources/wat/gc_array_copy_funcref.wat b/wasm-corpus/src/main/resources/wat/gc_array_copy_funcref.wat new file mode 100644 index 000000000..dfe9377f5 --- /dev/null +++ b/wasm-corpus/src/main/resources/wat/gc_array_copy_funcref.wat @@ -0,0 +1,22 @@ +(module + (type $ft (func)) + (type $arr (array (mut funcref))) + + (func $dummy (type $ft)) + + (func (export "copy_funcref_array") (result i32) + ;; Create src array with 2 funcrefs + (local $src (ref $arr)) + (local $dst (ref $arr)) + (local.set $src (array.new $arr (ref.func $dummy) (i32.const 2))) + (local.set $dst (array.new_default $arr (i32.const 2))) + ;; Copy src[0..2] -> dst[0..2] + (array.copy $arr $arr (local.get $dst) (i32.const 0) (local.get $src) (i32.const 0) (i32.const 2)) + ;; Check dst[0] is not null (funcref was copied) + (local.get $dst) + (i32.const 0) + (array.get $arr) + (ref.is_null) + ;; Should be 0 (not null) + ) +) From 7676994e1c66f8ba249479cde6e6bd72aee91da9 Mon Sep 17 00:00:00 2001 From: andreatp Date: Fri, 19 Jun 2026 15:46:54 +0100 Subject: [PATCH 20/20] perf: direct callWithRefs dispatch in compiled Machine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Generate callWithRefs_N bridges for compiled functions with Object ref params/returns. The compiled Machine's callWithRefs now dispatches directly to these bridges instead of going through the interpreter. Before: Host → callWithRefs → compilerInterpreterMachine (interpreter) → StackFrame → eval → CALL → compiled func_N After: Host → callWithRefs → MachineCall.callWithRefs → callWithRefs_N → compiled func_N (direct, no interpreter overhead) callWithRefs_N bridges are only generated when the function type has Object refs — zero overhead for linear memory modules. Interpreted functions still fall back to compilerInterpreterMachine. --- .../endive/compiler/internal/Compiler.java | 485 ++++++++++++++++-- .../compiler/internal/CompilerUtil.java | 4 + .../ApprovalTest.functions10.approved.txt | 11 - .../ApprovalTest.verifyBrTable.approved.txt | 11 - .../ApprovalTest.verifyBranching.approved.txt | 11 - .../ApprovalTest.verifyFloat.approved.txt | 11 - .../ApprovalTest.verifyGc.approved.txt | 81 +++ .../ApprovalTest.verifyHelloWasi.approved.txt | 11 - .../ApprovalTest.verifyI32.approved.txt | 11 - ...ApprovalTest.verifyI32Renamed.approved.txt | 11 - .../ApprovalTest.verifyIterFact.approved.txt | 11 - ...pprovalTest.verifyKitchenSink.approved.txt | 11 - .../ApprovalTest.verifyMemory.approved.txt | 11 - .../ApprovalTest.verifyStart.approved.txt | 11 - .../ApprovalTest.verifyTailCall.approved.txt | 11 - .../ApprovalTest.verifyTrap.approved.txt | 11 - 16 files changed, 514 insertions(+), 199 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 0d955fc61..1b3144c49 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/Compiler.java +++ b/compiler/src/main/java/run/endive/compiler/internal/Compiler.java @@ -19,6 +19,7 @@ import static run.endive.compiler.internal.CompilerUtil.callIndirectMethodName; import static run.endive.compiler.internal.CompilerUtil.callIndirectMethodType; import static run.endive.compiler.internal.CompilerUtil.callMethodName; +import static run.endive.compiler.internal.CompilerUtil.callWithRefsMethodName; import static run.endive.compiler.internal.CompilerUtil.classNameForCallIndirect; import static run.endive.compiler.internal.CompilerUtil.classNameForDispatch; import static run.endive.compiler.internal.CompilerUtil.defaultValue; @@ -102,6 +103,10 @@ public final class Compiler { private static final MethodType CALL_METHOD_TYPE_WITH_REFS = methodType(long[].class, Instance.class, Memory.class, long[].class, Object[].class); + private static final MethodType CALL_WITH_REFS_BRIDGE_METHOD_TYPE = + methodType( + CallResult.class, Instance.class, Memory.class, long[].class, Object[].class); + private static final MethodType MACHINE_CALL_METHOD_TYPE_WITH_REFS = methodType( long[].class, @@ -111,6 +116,15 @@ public final class Compiler { long[].class, Object[].class); + private static final MethodType MACHINE_CALL_WITH_REFS_METHOD_TYPE = + methodType( + CallResult.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. private static final int HUGE_METHOD_LIMIT = Integer.getInteger("endive.hugeMethodLimit", 8000); @@ -501,6 +515,16 @@ private Consumer emitFunctionGroup(int start, int end, String inte CALL_METHOD_TYPE_WITH_REFS, true, asm -> compileCallFunction(funcId, type, asm)); + + // callWithRefs_xxx() bridges for functions with Object refs + if (type.hasObjectRefParams() || type.hasObjectRefReturns()) { + emitFunction( + classWriter, + callWithRefsMethodName(funcId), + CALL_WITH_REFS_BRIDGE_METHOD_TYPE, + true, + asm -> compileCallWithRefsFunction(funcId, type, asm)); + } } } catch (MethodTooLargeException e) { throw handleMethodTooLarge(e, module); @@ -570,65 +594,15 @@ private byte[] compileClass() { asm -> compileMachineCall(internalClassName, asm, 3)); // Machine.callWithRefs(int, long[], Object[]) implementation - // Delegates to compilerInterpreterMachine.callWithRefs() so that - // Object ref results (GC refs and externref) are properly returned - // in the CallResult refs array. + // Dispatches directly to compiled callWithRefs_N bridges (or call_N for non-ref + // functions), bypassing the interpreter. Interpreted functions still fall back + // to compilerInterpreterMachine.callWithRefs(). emitFunction( classWriter, "callWithRefs", methodType(CallResult.class, int.class, long[].class, Object[].class), false, - asm -> { - asm.load(0, OBJECT_TYPE); - asm.getfield( - internalClassName, - "compilerInterpreterMachine", - getDescriptor(CompilerInterpreterMachine.class)); - // null check: if compilerInterpreterMachine is null, - // fall back to wrapping call() result in CallResult - asm.dup(); - Label hasField = new Label(); - asm.ifnonnull(hasField); - asm.pop(); // pop the null - // return new CallResult(this.call(funcId, args, refArgs), null) - asm.anew(Type.getType(CallResult.class)); - asm.dup(); - asm.load(0, OBJECT_TYPE); - asm.load(1, INT_TYPE); - asm.load(2, OBJECT_TYPE); - asm.load(3, OBJECT_TYPE); - asm.invokevirtual( - internalClassName, - "call", - Type.getMethodDescriptor( - LONG_ARRAY_TYPE, - INT_TYPE, - LONG_ARRAY_TYPE, - Type.getType(Object[].class)), - false); - asm.aconst(null); - asm.invokespecial( - Type.getInternalName(CallResult.class), - "", - Type.getMethodDescriptor( - VOID_TYPE, LONG_ARRAY_TYPE, Type.getType(Object[].class)), - false); - asm.areturn(OBJECT_TYPE); - asm.mark(hasField); - asm.load(1, INT_TYPE); - asm.load(2, OBJECT_TYPE); - asm.load(3, OBJECT_TYPE); - asm.invokevirtual( - AOT_INTERPRETER_MACHINE_TYPE.getInternalName(), - "callWithRefs", - Type.getMethodDescriptor( - Type.getType(CallResult.class), - INT_TYPE, - LONG_ARRAY_TYPE, - Type.getType(Object[].class)), - false); - asm.areturn(OBJECT_TYPE); - }); + asm -> compileMachineCallWithRefs(internalClassName, asm)); // call_indirect_xxx() bridges for native CALL_INDIRECT // When using bridge classes, these methods are on separate classes @@ -763,8 +737,9 @@ private void compileConstructor(InstructionAdapter asm, String internalClassName // Only create compilerInterpreterMachine when needed: // - interpreted functions need it for fallback execution - // - modules with Object refs need it for callWithRefs support - if (!interpretedFunctions.isEmpty() || moduleHasObjectRefs) { + // callWithRefs dispatches directly to compiled bridges now, + // so moduleHasObjectRefs alone no longer requires the interpreter + if (!interpretedFunctions.isEmpty()) { asm.load(0, OBJECT_TYPE); asm.anew(AOT_INTERPRETER_MACHINE_TYPE); asm.dup(); @@ -878,6 +853,105 @@ private void compileMachineCallSimple( asm.athrow(); } + // implements the body of: + // public CallResult callWithRefs(int funcId, long[] args, Object[] refArgs) + // Locals: 0=this, 1=funcId, 2=args, 3=refArgs + private void compileMachineCallWithRefs(String internalClassName, InstructionAdapter asm) { + + // handle modules with no functions + if (functionTypes.isEmpty()) { + asm.load(1, INT_TYPE); + emitInvokeStatic(asm, THROW_UNKNOWN_FUNCTION); + asm.athrow(); + return; + } + + // Interpreted functions: delegate to compilerInterpreterMachine.callWithRefs() + if (!interpretedFunctions.isEmpty()) { + Label invalid = new Label(); + int[] keys = interpretedFunctions.stream().mapToInt(x -> x).sorted().toArray(); + Label[] labels = + interpretedFunctions.stream().map(x -> new Label()).toArray(Label[]::new); + asm.load(1, INT_TYPE); + asm.lookupswitch(invalid, keys, labels); + for (int i = 0; i < interpretedFunctions.size(); i++) { + asm.mark(labels[i]); + asm.load(0, OBJECT_TYPE); + asm.getfield( + internalClassName, + "compilerInterpreterMachine", + getDescriptor(CompilerInterpreterMachine.class)); + asm.load(1, INT_TYPE); + asm.load(2, OBJECT_TYPE); + asm.load(3, OBJECT_TYPE); + asm.invokevirtual( + AOT_INTERPRETER_MACHINE_TYPE.getInternalName(), + "callWithRefs", + Type.getMethodDescriptor( + Type.getType(CallResult.class), + INT_TYPE, + LONG_ARRAY_TYPE, + Type.getType(Object[].class)), + false); + asm.areturn(OBJECT_TYPE); + } + asm.mark(invalid); + } + + if (moduleHasObjectRefs) { + // Module has Object refs: dispatch through MachineCall.callWithRefs() + // which routes to callWithRefs_N for ref functions and wraps call_N for non-ref + Label start = new Label(); + Label end = new Label(); + asm.visitTryCatchBlock(start, end, end, getInternalName(StackOverflowError.class)); + asm.mark(start); + + asm.load(0, OBJECT_TYPE); + asm.getfield(internalClassName, "instance", getDescriptor(Instance.class)); + asm.dup(); + emitInvokeVirtual(asm, INSTANCE_MEMORY); + asm.load(1, INT_TYPE); + asm.load(2, OBJECT_TYPE); + asm.load(3, OBJECT_TYPE); + + asm.invokestatic( + internalClassName + "MachineCall", + "callWithRefs", + MACHINE_CALL_WITH_REFS_METHOD_TYPE.toMethodDescriptorString(), + false); + asm.areturn(OBJECT_TYPE); + + asm.mark(end); + emitInvokeStatic(asm, THROW_CALL_STACK_EXHAUSTED); + asm.athrow(); + } else { + // No Object refs in module: wrap call() result in CallResult(longs, null) + asm.anew(Type.getType(CallResult.class)); + asm.dup(); + asm.load(0, OBJECT_TYPE); + asm.load(1, INT_TYPE); + asm.load(2, OBJECT_TYPE); + asm.load(3, OBJECT_TYPE); + asm.invokevirtual( + internalClassName, + "call", + Type.getMethodDescriptor( + LONG_ARRAY_TYPE, + INT_TYPE, + LONG_ARRAY_TYPE, + Type.getType(Object[].class)), + false); + asm.aconst(null); + asm.invokespecial( + CALL_RESULT_INTERNAL_NAME, + "", + Type.getMethodDescriptor( + VOID_TYPE, LONG_ARRAY_TYPE, Type.getType(Object[].class)), + false); + asm.areturn(OBJECT_TYPE); + } + } + // Generates a trampoline loop equivalent to: // // Instance inst = this.instance; @@ -1024,6 +1098,23 @@ private void compileMachineCallClass() { } emitFunction(classWriter, "call", MACHINE_CALL_METHOD_TYPE_WITH_REFS, true, callMethod); + // static implementation for Machine.callWithRefs() — only when module has Object refs + if (moduleHasObjectRefs) { + Consumer callWithRefsMethod; + if (functionTypes.size() < MAX_DISPATCH_METHODS) { + callWithRefsMethod = + asm -> compileMachineCallWithRefsInvoke(asm, 0, functionTypes.size()); + } else { + callWithRefsMethod = compileMachineCallWithRefsDispatch(MAX_DISPATCH_METHODS << 2); + } + emitFunction( + classWriter, + "callWithRefs", + MACHINE_CALL_WITH_REFS_METHOD_TYPE, + true, + callWithRefsMethod); + } + collector.put(machineCallClassName, binaryWriter.toByteArray()); } @@ -1141,6 +1232,120 @@ private void compileMachineCallInvoke(InstructionAdapter asm, int start, int end asm.athrow(); } + private Consumer compileMachineCallWithRefsDispatch( + int maxMachineCallMethods) { + return (asm) -> { + // 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); + + assert Integer.bitCount(maxMachineCallMethods) == 1; // power of two + int shift = Integer.numberOfTrailingZeros(maxMachineCallMethods); + + Label[] labels = new Label[((functionTypes.size() - 1) >> shift) + 1]; + for (int i = 0; i < labels.length; i++) { + labels[i] = new Label(); + } + + asm.load(2, INT_TYPE); + asm.iconst(shift); + asm.shr(INT_TYPE); + asm.tableswitch(0, labels.length - 1, labels[0], labels); + + // For large modules, delegate to the same single invoke method + // since all the logic (host vs compiled vs callWithRefs_N) is there + for (int i = 0; i < labels.length; i++) { + asm.mark(labels[i]); + int chunkStart = i << shift; + int chunkEnd = min(chunkStart + maxMachineCallMethods, functionTypes.size()); + compileMachineCallWithRefsInvoke(asm, chunkStart, chunkEnd); + // compileMachineCallWithRefsInvoke ends with areturn, no fall-through + } + }; + } + + private void compileMachineCallWithRefsInvoke(InstructionAdapter asm, int start, int end) { + + // 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); + + // switch (funcId) + Label defaultLabel = new Label(); + Label hostLabel = new Label(); + Label[] labels = new Label[end - start]; + + for (int id = start; id < end; id++) { + labels[id - start] = (id < functionImports) ? hostLabel : new Label(); + } + + asm.load(2, INT_TYPE); + asm.tableswitch(start, end - 1, defaultLabel, labels); + + // For each compiled function: + // - If it has Object refs: call callWithRefs_N -> returns CallResult directly + // - Otherwise: call call_N -> wrap result in CallResult(longs, null) + for (int id = max(start, functionImports); id < end; id++) { + asm.mark(labels[id - start]); + var type = functionTypes.get(id); + if (type.hasObjectRefParams() || type.hasObjectRefReturns()) { + // return callWithRefs_N(instance, memory, args, refArgs) + asm.invokestatic( + internalClassName(classNameForFuncGroup(className, id)), + callWithRefsMethodName(id), + CALL_WITH_REFS_BRIDGE_METHOD_TYPE.toMethodDescriptorString(), + false); + } else { + // return new CallResult(call_N(instance, memory, args, refArgs), null) + asm.invokestatic( + internalClassName(classNameForFuncGroup(className, id)), + callMethodName(id), + CALL_METHOD_TYPE_WITH_REFS.toMethodDescriptorString(), + false); + // Wrap long[] in CallResult + int resultSlot = 5; + asm.store(resultSlot, OBJECT_TYPE); + asm.anew(Type.getType(CallResult.class)); + asm.dup(); + asm.load(resultSlot, OBJECT_TYPE); + asm.aconst(null); + asm.invokespecial( + CALL_RESULT_INTERNAL_NAME, + "", + Type.getMethodDescriptor( + VOID_TYPE, LONG_ARRAY_TYPE, Type.getType(Object[].class)), + false); + } + asm.areturn(OBJECT_TYPE); + } + + // Host function: return callHostFunctionWithRefs(instance, funcId, args, refArgs) + if (functionImports > start) { + asm.mark(hostLabel); + asm.pop(); + asm.pop(); + asm.pop(); + asm.pop(); + asm.load(0, OBJECT_TYPE); // instance + asm.load(2, INT_TYPE); // funcId + asm.load(3, OBJECT_TYPE); // args + asm.load(4, OBJECT_TYPE); // refArgs + emitInvokeStatic(asm, CALL_HOST_FUNCTION_WITH_REFS); + asm.areturn(OBJECT_TYPE); + } + + // throw new InvalidException("unknown function " + funcId); + asm.mark(defaultLabel); + asm.load(2, INT_TYPE); + emitInvokeStatic(asm, THROW_UNKNOWN_FUNCTION); + asm.athrow(); + } + // implements the body of: // public static long[] call_xxx(Instance instance, Memory memory, long[] args, Object[] // refArgs) @@ -1229,6 +1434,174 @@ private void compileCallFunction(int funcId, FunctionType type, InstructionAdapt asm.areturn(OBJECT_TYPE); } + // implements the body of: + // public static CallResult callWithRefs_xxx(Instance instance, Memory memory, long[] args, + // Object[] refArgs) + // Same parameter unboxing as call_xxx, but returns CallResult with split long[]+Object[] + private void compileCallWithRefsFunction( + int funcId, FunctionType type, InstructionAdapter asm) { + + if (hasTooManyParameters(type)) { + asm.load(2, LONG_ARRAY_TYPE); + } else { + // 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); + if (param.isObjectRef()) { + // GC ref args: load from Object[] refArgs (null-safe) + asm.load(3, OBJECT_TYPE); // refArgs + Label hasRefArgs = new Label(); + Label refDone = new Label(); + asm.dup(); + asm.ifnonnull(hasRefArgs); + asm.pop(); + asm.aconst(null); // default null for missing refArgs + asm.goTo(refDone); + asm.mark(hasRefArgs); + asm.iconst(i); + asm.aload(OBJECT_TYPE); + asm.mark(refDone); + } else { + asm.load(2, OBJECT_TYPE); + asm.iconst(i); + asm.aload(LONG_TYPE); + emitLongToJvm(asm, param); + } + } + } + + asm.load(1, OBJECT_TYPE); + asm.load(0, OBJECT_TYPE); + + emitInvokeFunction( + asm, internalClassName(classNameForFuncGroup(className, funcId)), funcId, type); + + // Build CallResult from the function's JVM return value + Class returnType = jvmReturnType(type); + if (returnType == void.class) { + // new CallResult(null, null) + asm.anew(Type.getType(CallResult.class)); + asm.dup(); + asm.aconst(null); + asm.aconst(null); + asm.invokespecial( + CALL_RESULT_INTERNAL_NAME, + "", + Type.getMethodDescriptor( + VOID_TYPE, LONG_ARRAY_TYPE, Type.getType(Object[].class)), + false); + } else if (returnType == Object.class) { + // Single Object ref return: new CallResult(null, new Object[]{result}) + int refSlot = 4; + asm.store(refSlot, OBJECT_TYPE); // save the Object result + asm.anew(Type.getType(CallResult.class)); + asm.dup(); + asm.aconst(null); // longs = null + asm.iconst(1); + asm.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object"); + asm.dup(); + asm.iconst(0); + asm.load(refSlot, OBJECT_TYPE); + asm.astore(OBJECT_TYPE); + asm.invokespecial( + CALL_RESULT_INTERNAL_NAME, + "", + Type.getMethodDescriptor( + VOID_TYPE, LONG_ARRAY_TYPE, Type.getType(Object[].class)), + false); + } else if (returnType == Object[].class) { + // Multi-value with GC refs: split Object[] into positional long[] + Object[] + // Both arrays use type.returns().size() as length, matching the interpreter + // convention where index i corresponds to result position i. + int totalResults = type.returns().size(); + int objArraySlot = 4; + asm.store(objArraySlot, OBJECT_TYPE); // save Object[] + + // Build long[totalResults] for numeric values (0L for ref slots) + int longArraySlot = 5; + asm.iconst(totalResults); + asm.newarray(LONG_TYPE); + for (int i = 0; i < totalResults; i++) { + var retType = type.returns().get(i); + if (!retType.isObjectRef()) { + asm.dup(); + asm.iconst(i); + asm.load(objArraySlot, OBJECT_TYPE); + asm.iconst(i); + asm.aload(OBJECT_TYPE); + asm.checkcast(Type.getType(Long.class)); + asm.invokevirtual("java/lang/Long", "longValue", "()J", false); + asm.astore(LONG_TYPE); + } + } + asm.store(longArraySlot, OBJECT_TYPE); + + // Build Object[totalResults] for ref values (null for numeric slots) + int refArraySlot = 6; + asm.iconst(totalResults); + asm.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object"); + for (int i = 0; i < totalResults; i++) { + var retType = type.returns().get(i); + if (retType.isObjectRef()) { + asm.dup(); + asm.iconst(i); + asm.load(objArraySlot, OBJECT_TYPE); + asm.iconst(i); + asm.aload(OBJECT_TYPE); + asm.astore(OBJECT_TYPE); + } + } + asm.store(refArraySlot, OBJECT_TYPE); + + // new CallResult(longs, refs) + asm.anew(Type.getType(CallResult.class)); + asm.dup(); + asm.load(longArraySlot, OBJECT_TYPE); + asm.load(refArraySlot, OBJECT_TYPE); + asm.invokespecial( + CALL_RESULT_INTERNAL_NAME, + "", + Type.getMethodDescriptor( + VOID_TYPE, LONG_ARRAY_TYPE, Type.getType(Object[].class)), + false); + } else if (returnType == long[].class) { + // Multi-value, no Object refs: new CallResult(longArray, null) + int longArraySlot = 4; + asm.store(longArraySlot, OBJECT_TYPE); + asm.anew(Type.getType(CallResult.class)); + asm.dup(); + asm.load(longArraySlot, OBJECT_TYPE); + asm.aconst(null); + asm.invokespecial( + CALL_RESULT_INTERNAL_NAME, + "", + Type.getMethodDescriptor( + VOID_TYPE, LONG_ARRAY_TYPE, Type.getType(Object[].class)), + false); + } else { + // Single numeric return: new CallResult(new long[]{value}, null) + emitJvmToLong(asm, type.returns().get(0)); + int longSlot = 4; + asm.store(longSlot, LONG_TYPE); + asm.anew(Type.getType(CallResult.class)); + asm.dup(); + asm.iconst(1); + asm.newarray(LONG_TYPE); + asm.dup(); + asm.iconst(0); + asm.load(longSlot, LONG_TYPE); + asm.astore(LONG_TYPE); + asm.aconst(null); + asm.invokespecial( + CALL_RESULT_INTERNAL_NAME, + "", + Type.getMethodDescriptor( + VOID_TYPE, LONG_ARRAY_TYPE, Type.getType(Object[].class)), + false); + } + asm.areturn(OBJECT_TYPE); + } + // implements the body of: // public static call_indirect_xxx( argN..., // int funcTableIdx, int tableIdx, Memory memory, Instance instance) 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 2a8fa9787..95ba36377 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/CompilerUtil.java +++ b/compiler/src/main/java/run/endive/compiler/internal/CompilerUtil.java @@ -303,6 +303,10 @@ static String callMethodName(int funcId) { return "call_" + funcId; } + static String callWithRefsMethodName(int funcId) { + return "callWithRefs_" + funcId; + } + public static String callIndirectMethodName(int typeId) { return "call_indirect_" + typeId; } 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 5414d010e..0d0387cad 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 @@ -47,11 +47,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime ATHROW public callWithRefs(I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; - ALOAD 0 - GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; - DUP - IFNONNULL L0 - POP NEW run/endive/runtime/CallResult DUP ALOAD 0 @@ -62,12 +57,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime ACONST_NULL INVOKESPECIAL run/endive/runtime/CallResult. ([J[Ljava/lang/Object;)V ARETURN - L0 - ILOAD 1 - ALOAD 2 - ALOAD 3 - INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callWithRefs (I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; - ARETURN public static call_indirect_0(IIILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V 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 2323e17e1..f891730a9 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 @@ -47,11 +47,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime ATHROW public callWithRefs(I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; - ALOAD 0 - GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; - DUP - IFNONNULL L0 - POP NEW run/endive/runtime/CallResult DUP ALOAD 0 @@ -62,12 +57,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime ACONST_NULL INVOKESPECIAL run/endive/runtime/CallResult. ([J[Ljava/lang/Object;)V ARETURN - L0 - ILOAD 1 - ALOAD 2 - ALOAD 3 - INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callWithRefs (I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; - ARETURN public static call_indirect_0(IIILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V 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 07451d32c..2d8a5003c 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 @@ -47,11 +47,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime ATHROW public callWithRefs(I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; - ALOAD 0 - GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; - DUP - IFNONNULL L0 - POP NEW run/endive/runtime/CallResult DUP ALOAD 0 @@ -62,12 +57,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime ACONST_NULL INVOKESPECIAL run/endive/runtime/CallResult. ([J[Ljava/lang/Object;)V ARETURN - L0 - ILOAD 1 - ALOAD 2 - ALOAD 3 - INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callWithRefs (I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; - ARETURN public static call_indirect_0(IIILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V 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 b90278a85..0eb43f90b 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 @@ -47,11 +47,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime ATHROW public callWithRefs(I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; - ALOAD 0 - GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; - DUP - IFNONNULL L0 - POP NEW run/endive/runtime/CallResult DUP ALOAD 0 @@ -62,12 +57,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime ACONST_NULL INVOKESPECIAL run/endive/runtime/CallResult. ([J[Ljava/lang/Object;)V ARETURN - L0 - ILOAD 1 - ALOAD 2 - ALOAD 3 - INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callWithRefs (I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; - ARETURN public static call_indirect_0(IILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)V INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V 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 d27916022..39b92e6bd 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 @@ -40,6 +40,31 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { NEWARRAY T_LONG ARETURN + public static callWithRefs_0(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; + ALOAD 2 + ICONST_0 + LALOAD + L2I + ALOAD 2 + ICONST_1 + LALOAD + L2I + ALOAD 1 + ALOAD 0 + INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_0 (IILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)Ljava/lang/Object; + ASTORE 4 + NEW run/endive/runtime/CallResult + DUP + ACONST_NULL + ICONST_1 + ANEWARRAY java/lang/Object + DUP + ICONST_0 + ALOAD 4 + AASTORE + INVOKESPECIAL run/endive/runtime/CallResult. ([J[Ljava/lang/Object;)V + ARETURN + public static func_1(Ljava/lang/Object;Lrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I ALOAD 0 ICONST_0 @@ -73,6 +98,34 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { LASTORE ARETURN + public static callWithRefs_1(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; + ALOAD 3 + DUP + IFNONNULL L0 + POP + ACONST_NULL + GOTO L1 + L0 + ICONST_0 + AALOAD + L1 + ALOAD 1 + ALOAD 0 + INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_1 (Ljava/lang/Object;Lrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I + I2L + LSTORE 4 + NEW run/endive/runtime/CallResult + DUP + ICONST_1 + NEWARRAY T_LONG + DUP + ICONST_0 + LLOAD 4 + LASTORE + ACONST_NULL + INVOKESPECIAL run/endive/runtime/CallResult. ([J[Ljava/lang/Object;)V + ARETURN + public static func_2(Ljava/lang/Object;Lrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I ALOAD 0 ICONST_0 @@ -112,4 +165,32 @@ final class run/endive/$gen/CompiledMachineFuncGroup_0 { LLOAD 4 LASTORE ARETURN + + public static callWithRefs_2(Lrun/endive/runtime/Instance;Lrun/endive/runtime/Memory;[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; + ALOAD 3 + DUP + IFNONNULL L0 + POP + ACONST_NULL + GOTO L1 + L0 + ICONST_0 + AALOAD + L1 + ALOAD 1 + ALOAD 0 + INVOKESTATIC run/endive/$gen/CompiledMachineFuncGroup_0.func_2 (Ljava/lang/Object;Lrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I + I2L + LSTORE 4 + NEW run/endive/runtime/CallResult + DUP + ICONST_1 + NEWARRAY T_LONG + DUP + ICONST_0 + LLOAD 4 + LASTORE + ACONST_NULL + INVOKESPECIAL run/endive/runtime/CallResult. ([J[Ljava/lang/Object;)V + 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 1eedddb81..309466640 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 @@ -47,11 +47,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime ATHROW public callWithRefs(I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; - ALOAD 0 - GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; - DUP - IFNONNULL L0 - POP NEW run/endive/runtime/CallResult DUP ALOAD 0 @@ -62,12 +57,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime ACONST_NULL INVOKESPECIAL run/endive/runtime/CallResult. ([J[Ljava/lang/Object;)V ARETURN - L0 - ILOAD 1 - ALOAD 2 - ALOAD 3 - INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callWithRefs (I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; - ARETURN public static call_indirect_0(IIIIIILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V 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 93018533f..3272b0e33 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 @@ -47,11 +47,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime ATHROW public callWithRefs(I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; - ALOAD 0 - GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; - DUP - IFNONNULL L0 - POP NEW run/endive/runtime/CallResult DUP ALOAD 0 @@ -62,12 +57,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime ACONST_NULL INVOKESPECIAL run/endive/runtime/CallResult. ([J[Ljava/lang/Object;)V ARETURN - L0 - ILOAD 1 - ALOAD 2 - ALOAD 3 - INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callWithRefs (I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; - ARETURN public static call_indirect_0(IILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)V INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V 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 e103f1aac..0d1860589 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 @@ -47,11 +47,6 @@ public final class FOO implements run/endive/runtime/Machine { ATHROW public callWithRefs(I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; - ALOAD 0 - GETFIELD FOO.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; - DUP - IFNONNULL L0 - POP NEW run/endive/runtime/CallResult DUP ALOAD 0 @@ -62,12 +57,6 @@ public final class FOO implements run/endive/runtime/Machine { ACONST_NULL INVOKESPECIAL run/endive/runtime/CallResult. ([J[Ljava/lang/Object;)V ARETURN - L0 - ILOAD 1 - ALOAD 2 - ALOAD 3 - INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callWithRefs (I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; - ARETURN public static call_indirect_0(IILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)V INVOKESTATIC FOOShaded.checkInterruption ()V 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 a8cc1a606..d36a5eca3 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 @@ -47,11 +47,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime ATHROW public callWithRefs(I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; - ALOAD 0 - GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; - DUP - IFNONNULL L0 - POP NEW run/endive/runtime/CallResult DUP ALOAD 0 @@ -62,12 +57,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime ACONST_NULL INVOKESPECIAL run/endive/runtime/CallResult. ([J[Ljava/lang/Object;)V ARETURN - L0 - ILOAD 1 - ALOAD 2 - ALOAD 3 - INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callWithRefs (I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; - ARETURN public static call_indirect_0(IIILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V 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 d39bec36b..42fb76fd8 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 @@ -47,11 +47,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime ATHROW public callWithRefs(I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; - ALOAD 0 - GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; - DUP - IFNONNULL L0 - POP NEW run/endive/runtime/CallResult DUP ALOAD 0 @@ -62,12 +57,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime ACONST_NULL INVOKESPECIAL run/endive/runtime/CallResult. ([J[Ljava/lang/Object;)V ARETURN - L0 - ILOAD 1 - ALOAD 2 - ALOAD 3 - INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callWithRefs (I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; - ARETURN public static call_indirect_0(IIILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V 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 1b7f3b75c..8d3b82ad4 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 @@ -47,11 +47,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime ATHROW public callWithRefs(I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; - ALOAD 0 - GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; - DUP - IFNONNULL L0 - POP NEW run/endive/runtime/CallResult DUP ALOAD 0 @@ -62,12 +57,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime ACONST_NULL INVOKESPECIAL run/endive/runtime/CallResult. ([J[Ljava/lang/Object;)V ARETURN - L0 - ILOAD 1 - ALOAD 2 - ALOAD 3 - INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callWithRefs (I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; - ARETURN public static call_indirect_0(IIILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V 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 d703f8123..3b8006477 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 @@ -47,11 +47,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime ATHROW public callWithRefs(I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; - ALOAD 0 - GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; - DUP - IFNONNULL L0 - POP NEW run/endive/runtime/CallResult DUP ALOAD 0 @@ -62,12 +57,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime ACONST_NULL INVOKESPECIAL run/endive/runtime/CallResult. ([J[Ljava/lang/Object;)V ARETURN - L0 - ILOAD 1 - ALOAD 2 - ALOAD 3 - INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callWithRefs (I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; - ARETURN public static call_indirect_0(IIILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)V INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V 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 4c88275dc..b29037c35 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 @@ -87,11 +87,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime ATHROW public callWithRefs(I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; - ALOAD 0 - GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; - DUP - IFNONNULL L0 - POP NEW run/endive/runtime/CallResult DUP ALOAD 0 @@ -102,12 +97,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime ACONST_NULL INVOKESPECIAL run/endive/runtime/CallResult. ([J[Ljava/lang/Object;)V ARETURN - L0 - ILOAD 1 - ALOAD 2 - ALOAD 3 - INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callWithRefs (I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; - ARETURN public static call_indirect_0(IIIIILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)I INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V 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 ddc26871e..f3ca0a33f 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 @@ -47,11 +47,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime ATHROW public callWithRefs(I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; - ALOAD 0 - GETFIELD run/endive/$gen/CompiledMachine.compilerInterpreterMachine : Lrun/endive/runtime/internal/CompilerInterpreterMachine; - DUP - IFNONNULL L0 - POP NEW run/endive/runtime/CallResult DUP ALOAD 0 @@ -62,12 +57,6 @@ public final class run/endive/$gen/CompiledMachine implements run/endive/runtime ACONST_NULL INVOKESPECIAL run/endive/runtime/CallResult. ([J[Ljava/lang/Object;)V ARETURN - L0 - ILOAD 1 - ALOAD 2 - ALOAD 3 - INVOKEVIRTUAL run/endive/runtime/internal/CompilerInterpreterMachine.callWithRefs (I[J[Ljava/lang/Object;)Lrun/endive/runtime/CallResult; - ARETURN public static call_indirect_0(IILrun/endive/runtime/Memory;Lrun/endive/runtime/Instance;)V INVOKESTATIC run/endive/$gen/CompiledMachineShaded.checkInterruption ()V