types, int[] vals) {
+ int zero = bridge.exports().emitIconst32(0);
+ int argsPtr =
+ bridge.exports()
+ .emitLoadI64(bridge.exports().useVar(ctxPtrVar), zero, CtxBuffer.ARGS_PTR);
+ for (int i = 0; i < types.size(); i++) {
+ int val = (vals != null) ? vals[i] : emitZero(types.get(i));
+ int widened = widenToI64ForType(val, types.get(i));
+ bridge.exports().emitStoreI64(argsPtr, zero, widened, CtxBuffer.argOffset(i));
+ }
+ }
+
+ FunctionType resolveCallTargetType(int funcId) {
+ if (funcId < numImports) {
+ int idx = 0;
+ for (var imp :
+ module.importSection().stream().collect(java.util.stream.Collectors.toList())) {
+ if (imp.importType() == ExternalType.FUNCTION) {
+ if (idx == funcId) {
+ int typeIdx = ((FunctionImport) imp).typeIndex();
+ return (FunctionType) module.typeSection().getType(typeIdx);
+ }
+ idx++;
+ }
+ }
+ throw new IllegalArgumentException("Import function not found: " + funcId);
+ }
+ int bodyIdx = funcId - numImports;
+ int typeIdx = module.functionSection().getFunctionType(bodyIdx);
+ return (FunctionType) module.typeSection().getType(typeIdx);
+ }
+
+ ValType resolveGlobalType(int globalIdx) {
+ int importGlobalIdx = 0;
+ for (var imp :
+ module.importSection().stream().collect(java.util.stream.Collectors.toList())) {
+ if (imp.importType() == ExternalType.GLOBAL) {
+ if (importGlobalIdx == globalIdx) {
+ return ((run.endive.wasm.types.GlobalImport) imp).type();
+ }
+ importGlobalIdx++;
+ }
+ }
+ int moduleGlobalIdx = globalIdx - importGlobalIdx;
+ return module.globalSection().getGlobal(moduleGlobalIdx).valueType();
+ }
+
+ int widenToI64(int valId, ValType type) {
+ if (type.equals(ValType.I32)) {
+ return bridge.exports().emitUextendI64(valId);
+ }
+ return valId;
+ }
+
+ int narrowFromI64(int valId, ValType type) {
+ if (type.equals(ValType.I32)) {
+ return bridge.exports().emitIreduceI32(valId);
+ }
+ return valId;
+ }
+
+ int widenToI64ForType(int valId, ValType type) {
+ if (type.equals(ValType.I32)) {
+ return bridge.exports().emitUextendI64(valId);
+ }
+ if (type.equals(ValType.F32)) {
+ int bits = bridge.exports().emitBitcastF32ToI32(valId);
+ return bridge.exports().emitUextendI64(bits);
+ }
+ if (type.equals(ValType.F64)) {
+ return bridge.exports().emitBitcastF64ToI64(valId);
+ }
+ return valId; // I64
+ }
+
+ int narrowFromI64ForType(int valId, ValType type) {
+ if (type.equals(ValType.I32)) {
+ return bridge.exports().emitIreduceI32(valId);
+ }
+ if (type.equals(ValType.F32)) {
+ int narrow = bridge.exports().emitIreduceI32(valId);
+ return bridge.exports().emitBitcastI32ToF32(narrow);
+ }
+ if (type.equals(ValType.F64)) {
+ return bridge.exports().emitBitcastI64ToF64(valId);
+ }
+ return valId; // I64
+ }
+
+ int getOrCreateSigRef(FunctionType ft) {
+ String key = ft.toString();
+ Integer cached = sigRefCache.get(key);
+ if (cached != null) {
+ return cached;
+ }
+
+ bridge.exports().beginSig();
+ bridge.exports().sigAddParam(CraneliftBridge.TYPE_I64); // memBase
+ bridge.exports().sigAddParam(CraneliftBridge.TYPE_I64); // ctxPtr
+ for (ValType param : ft.params()) {
+ bridge.exports().sigAddParam(valTypeToBridgeType(param));
+ }
+ for (ValType ret : ft.returns()) {
+ bridge.exports().sigAddReturn(valTypeToBridgeType(ret));
+ }
+ int sigRef = bridge.exports().endSig();
+ sigRefCache.put(key, sigRef);
+ return sigRef;
+ }
+
+ int getOrCreateMultiReturnSigRef(FunctionType ft) {
+ String key = "__mr__" + ft.toString();
+ Integer cached = sigRefCache.get(key);
+ if (cached != null) {
+ return cached;
+ }
+
+ bridge.exports().beginSig();
+ bridge.exports().sigAddParam(CraneliftBridge.TYPE_I64); // memBase
+ bridge.exports().sigAddParam(CraneliftBridge.TYPE_I64); // ctxPtr
+ for (ValType param : ft.params()) {
+ bridge.exports().sigAddParam(valTypeToBridgeType(param));
+ }
+ // Multi-return: single i64 dummy return (actual values in argsBuffer)
+ bridge.exports().sigAddReturn(CraneliftBridge.TYPE_I64);
+ int sigRef = bridge.exports().endSig();
+ sigRefCache.put(key, sigRef);
+ return sigRef;
+ }
+
+ int getOrCreateTrampolineSigRef() {
+ String key = "__trampoline__";
+ Integer cached = sigRefCache.get(key);
+ if (cached != null) {
+ return cached;
+ }
+
+ bridge.exports().beginSig();
+ bridge.exports().sigAddParam(CraneliftBridge.TYPE_I64);
+ bridge.exports().sigAddReturn(CraneliftBridge.TYPE_I64);
+ int sigRef = bridge.exports().endSig();
+ sigRefCache.put(key, sigRef);
+ return sigRef;
+ }
+
+ /** Signature for memmove/memset: (i64, i64, i64) -> i64. */
+ int getOrCreateMemopSigRef() {
+ String key = "__memop__";
+ Integer cached = sigRefCache.get(key);
+ if (cached != null) {
+ return cached;
+ }
+
+ bridge.exports().beginSig();
+ bridge.exports().sigAddParam(CraneliftBridge.TYPE_I64); // dst ptr
+ bridge.exports().sigAddParam(CraneliftBridge.TYPE_I64); // src ptr / value
+ bridge.exports().sigAddParam(CraneliftBridge.TYPE_I64); // size
+ bridge.exports().sigAddReturn(CraneliftBridge.TYPE_I64);
+ int sigRef = bridge.exports().endSig();
+ sigRefCache.put(key, sigRef);
+ return sigRef;
+ }
+}
diff --git a/redline/compiler/src/main/java/run/endive/redline/experimental/compiler/internal/NativeCompiler.java b/redline/compiler/src/main/java/run/endive/redline/experimental/compiler/internal/NativeCompiler.java
new file mode 100644
index 000000000..bef44c0c2
--- /dev/null
+++ b/redline/compiler/src/main/java/run/endive/redline/experimental/compiler/internal/NativeCompiler.java
@@ -0,0 +1,1878 @@
+package run.endive.redline.experimental.compiler.internal;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import run.endive.redline.experimental.api.internal.CtxBuffer;
+import run.endive.redline.experimental.api.internal.TypeMapUtils;
+import run.endive.redline.experimental.bridge.internal.CraneliftBridge;
+import run.endive.wasm.WasmEngineException;
+import run.endive.wasm.WasmModule;
+import run.endive.wasm.types.AnnotatedInstruction;
+import run.endive.wasm.types.ExternalType;
+import run.endive.wasm.types.FunctionType;
+import run.endive.wasm.types.OpCode;
+import run.endive.wasm.types.ValType;
+
+/**
+ * Thin orchestrator that compiles Wasm functions to native code via Cranelift.
+ *
+ * Walks instructions, delegates opcodes to {@link NativeEmitters},
+ * handles control flow inline. Dead code after unconditional transfers
+ * is tracked via the control stack's {@code unreachable} flag.
+ */
+public final class NativeCompiler {
+
+ private final CraneliftBridge bridge;
+ private final String triple;
+ private final WasmModule module;
+ private final int numImports;
+ private final int[] canonicalTypeMap;
+
+ private NativeCompiler(CraneliftBridge bridge, String triple, WasmModule module) {
+ this.bridge = bridge;
+ this.triple = triple;
+ this.module = module;
+ this.numImports =
+ (int)
+ module.importSection().stream()
+ .filter(i -> i.importType() == ExternalType.FUNCTION)
+ .count();
+ this.canonicalTypeMap = TypeMapUtils.buildCanonicalTypeMap(module);
+ }
+
+ // --- Control frame ---
+
+ private static final class ControlFrame {
+ enum Kind {
+ BLOCK,
+ LOOP,
+ IF,
+ FUNCTION
+ }
+
+ final Kind kind;
+ final int mergeBlock;
+ final int loopBlock;
+ int elseBlock;
+ final int[] mergeParamIds;
+ final FunctionType blockType;
+ final int stackHeight;
+ int[] elseParamIds; // IF only: block param IDs for else block
+ boolean unreachable;
+ boolean hasElse;
+
+ ControlFrame(
+ Kind kind,
+ int mergeBlock,
+ int loopBlock,
+ int elseBlock,
+ int[] mergeParamIds,
+ FunctionType blockType,
+ int stackHeight) {
+ this.kind = kind;
+ this.mergeBlock = mergeBlock;
+ this.loopBlock = loopBlock;
+ this.elseBlock = elseBlock;
+ this.mergeParamIds = mergeParamIds;
+ this.blockType = blockType;
+ this.stackHeight = stackHeight;
+ }
+
+ int branchTarget() {
+ return kind == Kind.LOOP ? loopBlock : mergeBlock;
+ }
+
+ int branchArgCount() {
+ return kind == Kind.LOOP ? blockType.params().size() : blockType.returns().size();
+ }
+ }
+
+ // --- Compilation ---
+
+ private static final int THREAD_COUNT =
+ Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
+
+ private static final ExecutorService POOL = Executors.newFixedThreadPool(THREAD_COUNT);
+
+ public static byte[][] compileAll(String triple, WasmModule module) {
+ return new NativeCompiler(null, triple, module).compileAll((boolean[]) null);
+ }
+
+ /**
+ * Compiles functions to native code. If {@code filter} is non-null, only
+ * functions where {@code filter[numImports + bodyIndex]} is {@code true}
+ * are compiled; others get {@code null} entries.
+ */
+ private byte[][] compileAll(boolean[] filter) {
+ int count = module.codeSection().functionBodyCount();
+ byte[][] results = new byte[count][];
+ int threads = Math.min(THREAD_COUNT, count);
+ if (threads < 1) {
+ threads = 1;
+ }
+
+ int chunkSize = (count + threads - 1) / threads;
+ List> futures = new ArrayList<>();
+
+ for (int t = 0; t < threads; t++) {
+ int start = t * chunkSize;
+ int end = Math.min(start + chunkSize, count);
+ if (start >= count) {
+ break;
+ }
+
+ futures.add(
+ POOL.submit(
+ () -> {
+ try (var threadBridge = new CraneliftBridge()) {
+ threadBridge.init(triple);
+ var threadCompiler =
+ new NativeCompiler(threadBridge, triple, module);
+ for (int i = start; i < end; i++) {
+ if (filter != null && !filter[numImports + i]) {
+ continue;
+ }
+ try {
+ results[i] = threadCompiler.compileFunction(i);
+ } catch (RuntimeException e) {
+ throw new WasmEngineException(
+ "Failed to compile function "
+ + i
+ + ": "
+ + e.getMessage(),
+ e);
+ }
+ }
+ }
+ return null;
+ }));
+ }
+
+ for (Future> f : futures) {
+ try {
+ f.get();
+ } catch (ExecutionException e) {
+ if (e.getCause() instanceof RuntimeException) {
+ throw (RuntimeException) e.getCause();
+ }
+ throw new WasmEngineException("Parallel compilation failed", e.getCause());
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new WasmEngineException("Compilation interrupted", e);
+ }
+ }
+ return results;
+ }
+
+ private byte[] compileFunction(int bodyIndex) {
+ var body = module.codeSection().getFunctionBody(bodyIndex);
+ int typeIdx = module.functionSection().getFunctionType(bodyIndex);
+ var funcType = (FunctionType) module.typeSection().getType(typeIdx);
+
+ // Multi-return: functions with >1 return write results to argsBuffer
+ // and return a single i64 (dummy). Single-return uses registers (fast path).
+ boolean multiReturn = funcType.returns().size() > 1;
+
+ // --- Setup function ---
+ bridge.exports().createFunction();
+
+ bridge.exports().addParamType(CraneliftBridge.TYPE_I64); // memBase
+ bridge.exports().addParamType(CraneliftBridge.TYPE_I64); // ctxPtr
+ for (ValType param : funcType.params()) {
+ bridge.exports().addParamType(EmitContext.valTypeToBridgeType(param));
+ }
+ if (multiReturn) {
+ // Multi-return: return single i64 dummy (actual values in argsBuffer)
+ bridge.exports().addReturnType(CraneliftBridge.TYPE_I64);
+ } else {
+ for (ValType ret : funcType.returns()) {
+ bridge.exports().addReturnType(EmitContext.valTypeToBridgeType(ret));
+ }
+ }
+
+ bridge.exports().buildFunction();
+
+ // Create entry block
+ int entry = bridge.exports().createBlock();
+ bridge.exports().appendBlockParamsForFuncParams(entry);
+ bridge.exports().switchToBlock(entry);
+
+ // Get params
+ int memBaseParam = bridge.exports().funcParam(entry, 0);
+ int ctxPtrParam = bridge.exports().funcParam(entry, 1);
+ int[] paramVals = new int[funcType.params().size()];
+ for (int i = 0; i < paramVals.length; i++) {
+ paramVals[i] = bridge.exports().funcParam(entry, i + 2);
+ }
+
+ // memBase and ctxPtr as variables
+ int memBaseVar = bridge.exports().declareVar(CraneliftBridge.TYPE_I64);
+ bridge.exports().defVar(memBaseVar, memBaseParam);
+ int ctxPtrVar = bridge.exports().declareVar(CraneliftBridge.TYPE_I64);
+ bridge.exports().defVar(ctxPtrVar, ctxPtrParam);
+
+ // Locals
+ int numParams = funcType.params().size();
+ int numBodyLocals = body.localTypes().size();
+ int totalLocals = numParams + numBodyLocals;
+ int[] localVars = new int[totalLocals];
+ for (int i = 0; i < numParams; i++) {
+ localVars[i] =
+ bridge.exports()
+ .declareVar(EmitContext.valTypeToBridgeType(funcType.params().get(i)));
+ bridge.exports().defVar(localVars[i], paramVals[i]);
+ }
+ for (int i = 0; i < numBodyLocals; i++) {
+ ValType localType = body.localTypes().get(i);
+ localVars[numParams + i] =
+ bridge.exports().declareVar(EmitContext.valTypeToBridgeType(localType));
+ int zero = emitZero(localType);
+ bridge.exports().defVar(localVars[numParams + i], zero);
+ }
+
+ // --- Stack depth guard (wasmtime-style) ---
+ // At function entry: read RSP via get_stack_pointer.
+ // If STACK_LIMIT == 0 (first call): store RSP - 512KB as limit.
+ // Otherwise: if RSP < STACK_LIMIT → trap "call stack exhausted".
+ // Cost: 1 load + 1 compare + 1 branch (predicted not-taken).
+ {
+ int sp = bridge.exports().emitGetStackPointer();
+ int zero = bridge.exports().emitIconst32(0);
+ int stackLimit =
+ bridge.exports()
+ .emitLoadI64(
+ bridge.exports().useVar(ctxPtrVar),
+ zero,
+ CtxBuffer.STACK_LIMIT);
+
+ // Check if limit needs initialization (== 0)
+ int zeroI64 = bridge.exports().emitIconst64(0, 0);
+ int needsInit = bridge.exports().emitIcmp(0, stackLimit, zeroI64); // EQ
+ int initBlock = bridge.exports().createBlock();
+ int checkBlock = bridge.exports().createBlock();
+ bridge.exports().emitBrif(needsInit, initBlock, checkBlock);
+
+ // Init block: store SP - 512KB as limit, then continue
+ bridge.exports().switchToBlock(initBlock);
+ int reserve = bridge.exports().emitIconst64(524288, 0); // 512KB
+ int newLimit = bridge.exports().emitIsub(sp, reserve);
+ bridge.exports()
+ .emitStoreI64(
+ bridge.exports().useVar(ctxPtrVar),
+ bridge.exports().emitIconst32(0),
+ newLimit,
+ CtxBuffer.STACK_LIMIT);
+ bridge.exports().emitJump(checkBlock);
+
+ // Check block: compare SP against limit
+ bridge.exports().switchToBlock(checkBlock);
+ // Re-load limit (may have been just written)
+ int limit2 =
+ bridge.exports()
+ .emitLoadI64(
+ bridge.exports().useVar(ctxPtrVar),
+ bridge.exports().emitIconst32(0),
+ CtxBuffer.STACK_LIMIT);
+ int exhausted = bridge.exports().emitIcmp(3, sp, limit2); // LT unsigned
+ int trapBlock = bridge.exports().createBlock();
+ int okBlock = bridge.exports().createBlock();
+ bridge.exports().emitBrif(exhausted, trapBlock, okBlock);
+
+ // Trap block: write trap code and return
+ bridge.exports().switchToBlock(trapBlock);
+ int ctxVal = bridge.exports().useVar(ctxPtrVar);
+ int zeroT = bridge.exports().emitIconst32(0);
+ int code = bridge.exports().emitIconst32(CtxBuffer.TRAP_CALL_STACK_EXHAUSTED);
+ bridge.exports().emitStoreI32(ctxVal, zeroT, code, CtxBuffer.TRAP_CODE);
+ if (multiReturn || funcType.returns().isEmpty()) {
+ if (funcType.returns().isEmpty()) {
+ bridge.exports().emitReturnVoid();
+ } else {
+ bridge.exports().emitReturn(bridge.exports().emitIconst64(0, 0));
+ }
+ } else {
+ bridge.exports().emitReturn(emitZero(funcType.returns().get(0)));
+ }
+
+ bridge.exports().switchToBlock(okBlock);
+ }
+
+ // --- Interrupt check ---
+ // If INTERRUPT_FLAG != 0 → trap immediately.
+ // Cost: 1 load + 1 compare + 1 branch (predicted not-taken).
+ {
+ int flag =
+ bridge.exports()
+ .emitLoadI64(
+ bridge.exports().useVar(ctxPtrVar),
+ bridge.exports().emitIconst32(0),
+ CtxBuffer.INTERRUPT_FLAG);
+ int zeroI64 = bridge.exports().emitIconst64(0, 0);
+ int interrupted = bridge.exports().emitIcmp(1, flag, zeroI64); // NE
+ int trapBlock = bridge.exports().createBlock();
+ int okBlock = bridge.exports().createBlock();
+ bridge.exports().emitBrif(interrupted, trapBlock, okBlock);
+
+ bridge.exports().switchToBlock(trapBlock);
+ int ctxVal = bridge.exports().useVar(ctxPtrVar);
+ int zeroT = bridge.exports().emitIconst32(0);
+ int code = bridge.exports().emitIconst32(CtxBuffer.TRAP_INTERRUPTED);
+ bridge.exports().emitStoreI32(ctxVal, zeroT, code, CtxBuffer.TRAP_CODE);
+ if (multiReturn || funcType.returns().isEmpty()) {
+ if (funcType.returns().isEmpty()) {
+ bridge.exports().emitReturnVoid();
+ } else {
+ bridge.exports().emitReturn(bridge.exports().emitIconst64(0, 0));
+ }
+ } else {
+ bridge.exports().emitReturn(emitZero(funcType.returns().get(0)));
+ }
+
+ bridge.exports().switchToBlock(okBlock);
+ }
+
+ // --- Create emit context ---
+ var valueStack = new NativeValueStack();
+ var ctx =
+ new EmitContext(
+ bridge,
+ valueStack,
+ module,
+ numImports,
+ funcType,
+ localVars,
+ memBaseVar,
+ ctxPtrVar,
+ new HashMap<>(),
+ multiReturn,
+ canonicalTypeMap);
+
+ // --- Emission loop ---
+ Deque controlStack = new ArrayDeque<>();
+
+ // Implicit function-level frame
+ controlStack.push(
+ new ControlFrame(ControlFrame.Kind.FUNCTION, -1, -1, -1, new int[0], funcType, 0));
+ List instructions = body.instructions();
+ for (int idx = 0; idx < instructions.size(); idx++) {
+ AnnotatedInstruction ins = instructions.get(idx);
+
+ if (!controlStack.isEmpty() && controlStack.peek().unreachable) {
+ switch (ins.opcode()) {
+ case END:
+ case ELSE:
+ // Process normally — these exit the unreachable state
+ break;
+ case BLOCK:
+ case LOOP:
+ case IF:
+ // Push dummy frame to keep control stack balanced
+ controlStack.push(
+ new ControlFrame(
+ ins.opcode() == OpCode.IF
+ ? ControlFrame.Kind.IF
+ : ins.opcode() == OpCode.LOOP
+ ? ControlFrame.Kind.LOOP
+ : ControlFrame.Kind.BLOCK,
+ -1,
+ -1,
+ -1,
+ new int[0],
+ FunctionType.empty(),
+ valueStack.size()));
+ controlStack.peek().unreachable = true;
+ continue;
+ default:
+ continue; // skip all other instructions
+ }
+ }
+
+ if (ins.opcode() == OpCode.END) {
+ emitEnd(ctx, controlStack);
+ continue;
+ }
+
+ emitInstruction(ctx, ins, controlStack);
+ }
+
+ bridge.exports().sealAllBlocks();
+ return bridge.compile();
+ }
+
+ // --- Block type decoding ---
+
+ private FunctionType decodeBlockType(AnnotatedInstruction ins) {
+ long typeId = ins.operands()[0];
+ if (typeId == 0x40) {
+ return FunctionType.empty();
+ }
+ if (ValType.isValid(typeId)) {
+ return FunctionType.returning(ValType.builder().fromId(typeId).build());
+ }
+ return (FunctionType) module.typeSection().getType((int) typeId);
+ }
+
+ // --- Control stack helpers ---
+
+ private static ControlFrame getControlFrame(Deque controlStack, int depth) {
+ Iterator it = controlStack.iterator();
+ for (int i = 0; i < depth; i++) {
+ it.next();
+ }
+ return it.next();
+ }
+
+ private int[] appendBlockParams(int blockId, List types) {
+ int[] paramIds = new int[types.size()];
+ for (int i = 0; i < types.size(); i++) {
+ paramIds[i] =
+ bridge.exports()
+ .appendBlockParam(
+ blockId, EmitContext.valTypeToBridgeType(types.get(i)));
+ }
+ return paramIds;
+ }
+
+ private void emitJumpToBlock(int blockId, int argCount, NativeValueStack valueStack) {
+ if (argCount == 0) {
+ bridge.exports().emitJump(blockId);
+ } else if (argCount == 1) {
+ bridge.exports().emitJumpWithArg(blockId, valueStack.pop());
+ } else {
+ int[] args = new int[argCount];
+ for (int i = argCount - 1; i >= 0; i--) {
+ args[i] = valueStack.pop();
+ }
+ for (int a : args) {
+ bridge.exports().pushCallArg(a);
+ }
+ bridge.exports().emitJumpWithArgs(blockId);
+ }
+ }
+
+ private void emitDeadPredecessor(int targetBlock, List types) {
+ int deadBlock = bridge.exports().createBlock();
+ bridge.exports().switchToBlock(deadBlock);
+ if (types.size() == 1) {
+ bridge.exports().emitJumpWithArg(targetBlock, emitZero(types.get(0)));
+ } else {
+ for (ValType t : types) {
+ bridge.exports().pushCallArg(emitZero(t));
+ }
+ bridge.exports().emitJumpWithArgs(targetBlock);
+ }
+ }
+
+ private int emitZero(ValType type) {
+ if (type.equals(ValType.I32)) {
+ return bridge.exports().emitIconst32(0);
+ }
+ if (type.equals(ValType.I64)) {
+ return bridge.exports().emitIconst64(0, 0);
+ }
+ if (type.equals(ValType.F32)) {
+ return bridge.exports().emitF32const(0);
+ }
+ if (type.equals(ValType.F64)) {
+ return bridge.exports().emitF64const(0, 0);
+ }
+ // Reference types use i64 representation
+ int op = type.opcode();
+ if (op == ValType.ID.RefNull || op == ValType.ID.Ref) {
+ return bridge.exports().emitIconst64(0, 0);
+ }
+ throw new UnsupportedOperationException("Unsupported type: " + type);
+ }
+
+ // --- Instruction emission ---
+
+ private void emitInstruction(
+ EmitContext ctx, AnnotatedInstruction ins, Deque controlStack) {
+
+ var valueStack = ctx.valueStack;
+
+ switch (ins.opcode()) {
+ // --- Constants ---
+ case I32_CONST:
+ NativeEmitters.emitI32Const(ctx, ins);
+ break;
+ case I64_CONST:
+ NativeEmitters.emitI64Const(ctx, ins);
+ break;
+ case F32_CONST:
+ NativeEmitters.emitF32Const(ctx, ins);
+ break;
+ case F64_CONST:
+ NativeEmitters.emitF64Const(ctx, ins);
+ break;
+
+ // --- i32 Arithmetic ---
+ case I32_ADD:
+ NativeEmitters.emitI32BinaryOp(ctx, 0);
+ break;
+ case I32_SUB:
+ NativeEmitters.emitI32BinaryOp(ctx, 1);
+ break;
+ case I32_MUL:
+ NativeEmitters.emitI32BinaryOp(ctx, 2);
+ break;
+ case I32_DIV_S:
+ NativeEmitters.emitSafeDiv(ctx, true, false, false);
+ break;
+ case I32_DIV_U:
+ NativeEmitters.emitSafeDiv(ctx, false, false, false);
+ break;
+ case I32_REM_S:
+ NativeEmitters.emitSafeDiv(ctx, true, true, false);
+ break;
+ case I32_REM_U:
+ NativeEmitters.emitSafeDiv(ctx, false, true, false);
+ break;
+ case I32_AND:
+ NativeEmitters.emitI32BinaryOp(ctx, 3);
+ break;
+ case I32_OR:
+ NativeEmitters.emitI32BinaryOp(ctx, 4);
+ break;
+ case I32_XOR:
+ NativeEmitters.emitI32BinaryOp(ctx, 5);
+ break;
+ case I32_SHL:
+ NativeEmitters.emitI32BinaryOp(ctx, 6);
+ break;
+ case I32_SHR_S:
+ NativeEmitters.emitI32BinaryOp(ctx, 7);
+ break;
+ case I32_SHR_U:
+ NativeEmitters.emitI32BinaryOp(ctx, 8);
+ break;
+ case I32_ROTL:
+ NativeEmitters.emitI32BinaryOp(ctx, 9);
+ break;
+ case I32_ROTR:
+ NativeEmitters.emitI32BinaryOp(ctx, 10);
+ break;
+ case I32_CLZ:
+ NativeEmitters.emitI32UnaryOp(ctx, 0);
+ break;
+ case I32_CTZ:
+ NativeEmitters.emitI32UnaryOp(ctx, 1);
+ break;
+ case I32_POPCNT:
+ NativeEmitters.emitI32UnaryOp(ctx, 2);
+ break;
+
+ // --- i32 Comparisons ---
+ case I32_EQZ:
+ NativeEmitters.emitI32UnaryOp(ctx, 3);
+ break;
+ case I32_EQ:
+ NativeEmitters.emitIcmp(ctx, 0);
+ break;
+ case I32_NE:
+ NativeEmitters.emitIcmp(ctx, 1);
+ break;
+ case I32_LT_S:
+ NativeEmitters.emitIcmp(ctx, 2);
+ break;
+ case I32_LT_U:
+ NativeEmitters.emitIcmp(ctx, 3);
+ break;
+ case I32_GT_S:
+ NativeEmitters.emitIcmp(ctx, 4);
+ break;
+ case I32_GT_U:
+ NativeEmitters.emitIcmp(ctx, 5);
+ break;
+ case I32_LE_S:
+ NativeEmitters.emitIcmp(ctx, 6);
+ break;
+ case I32_LE_U:
+ NativeEmitters.emitIcmp(ctx, 7);
+ break;
+ case I32_GE_S:
+ NativeEmitters.emitIcmp(ctx, 8);
+ break;
+ case I32_GE_U:
+ NativeEmitters.emitIcmp(ctx, 9);
+ break;
+
+ // --- i32 Extensions ---
+ case I32_EXTEND_8_S:
+ NativeEmitters.emitI32Extend8S(ctx);
+ break;
+ case I32_EXTEND_16_S:
+ NativeEmitters.emitI32Extend16S(ctx);
+ break;
+
+ // --- i64 Arithmetic ---
+ case I64_ADD:
+ NativeEmitters.emitI32BinaryOp(ctx, 0);
+ break;
+ case I64_SUB:
+ NativeEmitters.emitI32BinaryOp(ctx, 1);
+ break;
+ case I64_MUL:
+ NativeEmitters.emitI32BinaryOp(ctx, 2);
+ break;
+ case I64_DIV_S:
+ NativeEmitters.emitSafeDiv(ctx, true, false, true);
+ break;
+ case I64_DIV_U:
+ NativeEmitters.emitSafeDiv(ctx, false, false, true);
+ break;
+ case I64_REM_S:
+ NativeEmitters.emitSafeDiv(ctx, true, true, true);
+ break;
+ case I64_REM_U:
+ NativeEmitters.emitSafeDiv(ctx, false, true, true);
+ break;
+ case I64_AND:
+ NativeEmitters.emitI32BinaryOp(ctx, 3);
+ break;
+ case I64_OR:
+ NativeEmitters.emitI32BinaryOp(ctx, 4);
+ break;
+ case I64_XOR:
+ NativeEmitters.emitI32BinaryOp(ctx, 5);
+ break;
+ case I64_SHL:
+ NativeEmitters.emitI32BinaryOp(ctx, 6);
+ break;
+ case I64_SHR_S:
+ NativeEmitters.emitI32BinaryOp(ctx, 7);
+ break;
+ case I64_SHR_U:
+ NativeEmitters.emitI32BinaryOp(ctx, 8);
+ break;
+ case I64_ROTL:
+ NativeEmitters.emitI32BinaryOp(ctx, 9);
+ break;
+ case I64_ROTR:
+ NativeEmitters.emitI32BinaryOp(ctx, 10);
+ break;
+ case I64_CLZ:
+ NativeEmitters.emitI64UnaryOp(ctx, 0);
+ break;
+ case I64_CTZ:
+ NativeEmitters.emitI64UnaryOp(ctx, 1);
+ break;
+ case I64_POPCNT:
+ NativeEmitters.emitI64UnaryOp(ctx, 2);
+ break;
+
+ // --- i64 Comparisons ---
+ case I64_EQZ:
+ NativeEmitters.emitI64UnaryOp(ctx, 3);
+ break;
+ case I64_EQ:
+ NativeEmitters.emitIcmp(ctx, 0);
+ break;
+ case I64_NE:
+ NativeEmitters.emitIcmp(ctx, 1);
+ break;
+ case I64_LT_S:
+ NativeEmitters.emitIcmp(ctx, 2);
+ break;
+ case I64_LT_U:
+ NativeEmitters.emitIcmp(ctx, 3);
+ break;
+ case I64_GT_S:
+ NativeEmitters.emitIcmp(ctx, 4);
+ break;
+ case I64_GT_U:
+ NativeEmitters.emitIcmp(ctx, 5);
+ break;
+ case I64_LE_S:
+ NativeEmitters.emitIcmp(ctx, 6);
+ break;
+ case I64_LE_U:
+ NativeEmitters.emitIcmp(ctx, 7);
+ break;
+ case I64_GE_S:
+ NativeEmitters.emitIcmp(ctx, 8);
+ break;
+ case I64_GE_U:
+ NativeEmitters.emitIcmp(ctx, 9);
+ break;
+
+ // --- i64 Extensions ---
+ case I64_EXTEND_I32_S:
+ NativeEmitters.emitI64ExtendI32S(ctx);
+ break;
+ case I64_EXTEND_I32_U:
+ NativeEmitters.emitI64ExtendI32U(ctx);
+ break;
+ case I64_EXTEND_8_S:
+ NativeEmitters.emitI64Extend8S(ctx);
+ break;
+ case I64_EXTEND_16_S:
+ NativeEmitters.emitI64Extend16S(ctx);
+ break;
+ case I64_EXTEND_32_S:
+ NativeEmitters.emitI64Extend32S(ctx);
+ break;
+ case I32_WRAP_I64:
+ NativeEmitters.emitI32WrapI64(ctx);
+ break;
+
+ // --- Memory loads ---
+ case I32_LOAD:
+ NativeEmitters.emitLoad(ctx, ins, 0);
+ break;
+ case I64_LOAD:
+ NativeEmitters.emitLoad(ctx, ins, 1);
+ break;
+ case F32_LOAD:
+ NativeEmitters.emitLoad(ctx, ins, 2);
+ break;
+ case F64_LOAD:
+ NativeEmitters.emitLoad(ctx, ins, 3);
+ break;
+ case I32_LOAD8_U:
+ NativeEmitters.emitLoad(ctx, ins, 4);
+ break;
+ case I32_LOAD8_S:
+ NativeEmitters.emitLoad(ctx, ins, 5);
+ break;
+ case I32_LOAD16_U:
+ NativeEmitters.emitLoad(ctx, ins, 6);
+ break;
+ case I32_LOAD16_S:
+ NativeEmitters.emitLoad(ctx, ins, 7);
+ break;
+ case I64_LOAD8_U:
+ NativeEmitters.emitLoad(ctx, ins, 8);
+ break;
+ case I64_LOAD8_S:
+ NativeEmitters.emitLoad(ctx, ins, 9);
+ break;
+ case I64_LOAD16_U:
+ NativeEmitters.emitLoad(ctx, ins, 10);
+ break;
+ case I64_LOAD16_S:
+ NativeEmitters.emitLoad(ctx, ins, 11);
+ break;
+ case I64_LOAD32_U:
+ NativeEmitters.emitLoad(ctx, ins, 12);
+ break;
+ case I64_LOAD32_S:
+ NativeEmitters.emitLoad(ctx, ins, 13);
+ break;
+
+ // --- Memory stores ---
+ case I32_STORE:
+ NativeEmitters.emitStore(ctx, ins, 0);
+ break;
+ case I64_STORE:
+ NativeEmitters.emitStore(ctx, ins, 1);
+ break;
+ case F32_STORE:
+ NativeEmitters.emitStore(ctx, ins, 2);
+ break;
+ case F64_STORE:
+ NativeEmitters.emitStore(ctx, ins, 3);
+ break;
+ case I32_STORE8:
+ NativeEmitters.emitStore(ctx, ins, 4);
+ break;
+ case I32_STORE16:
+ NativeEmitters.emitStore(ctx, ins, 5);
+ break;
+ case I64_STORE8:
+ NativeEmitters.emitStore(ctx, ins, 6);
+ break;
+ case I64_STORE16:
+ NativeEmitters.emitStore(ctx, ins, 7);
+ break;
+ case I64_STORE32:
+ NativeEmitters.emitStore(ctx, ins, 8);
+ break;
+
+ // --- Locals ---
+ case LOCAL_GET:
+ NativeEmitters.emitLocalGet(ctx, ins);
+ break;
+ case LOCAL_SET:
+ NativeEmitters.emitLocalSet(ctx, ins);
+ break;
+ case LOCAL_TEE:
+ NativeEmitters.emitLocalTee(ctx, ins);
+ break;
+
+ // --- Select ---
+ case SELECT:
+ case SELECT_T:
+ NativeEmitters.emitSelect(ctx);
+ break;
+
+ // --- Globals ---
+ case GLOBAL_GET:
+ NativeEmitters.emitGlobalGet(ctx, ins);
+ break;
+ case GLOBAL_SET:
+ NativeEmitters.emitGlobalSet(ctx, ins);
+ break;
+
+ // --- Memory operations ---
+ case MEMORY_SIZE:
+ NativeEmitters.emitMemorySize(ctx);
+ break;
+ case MEMORY_GROW:
+ NativeEmitters.emitMemoryGrow(ctx);
+ break;
+ case MEMORY_COPY:
+ NativeEmitters.emitMemoryCopy(ctx);
+ break;
+ case MEMORY_FILL:
+ NativeEmitters.emitMemoryFill(ctx);
+ break;
+ case MEMORY_INIT:
+ NativeEmitters.emitMemoryInit(ctx, ins);
+ break;
+ case DATA_DROP:
+ NativeEmitters.emitDataDrop(ctx, ins);
+ break;
+
+ // --- Misc ---
+ case NOP:
+ break;
+ case DROP:
+ valueStack.pop();
+ break;
+ case UNREACHABLE:
+ NativeEmitters.emitUnreachable(ctx);
+ controlStack.peek().unreachable = true;
+ break;
+
+ // --- f32 Arithmetic ---
+ case F32_ADD:
+ NativeEmitters.emitFloatBinaryOp(ctx, 0);
+ break;
+ case F32_SUB:
+ NativeEmitters.emitFloatBinaryOp(ctx, 1);
+ break;
+ case F32_MUL:
+ NativeEmitters.emitFloatBinaryOp(ctx, 2);
+ break;
+ case F32_DIV:
+ NativeEmitters.emitFloatBinaryOp(ctx, 3);
+ break;
+ case F32_MIN:
+ NativeEmitters.emitFloatBinaryOp(ctx, 4);
+ break;
+ case F32_MAX:
+ NativeEmitters.emitFloatBinaryOp(ctx, 5);
+ break;
+ case F32_COPYSIGN:
+ NativeEmitters.emitFloatBinaryOp(ctx, 6);
+ break;
+ case F32_ABS:
+ NativeEmitters.emitFloatUnaryOp(ctx, 0);
+ break;
+ case F32_NEG:
+ NativeEmitters.emitFloatUnaryOp(ctx, 1);
+ break;
+ case F32_CEIL:
+ NativeEmitters.emitFloatUnaryOp(ctx, 2);
+ break;
+ case F32_FLOOR:
+ NativeEmitters.emitFloatUnaryOp(ctx, 3);
+ break;
+ case F32_TRUNC:
+ NativeEmitters.emitFloatUnaryOp(ctx, 4);
+ break;
+ case F32_NEAREST:
+ NativeEmitters.emitFloatUnaryOp(ctx, 5);
+ break;
+ case F32_SQRT:
+ NativeEmitters.emitFloatUnaryOp(ctx, 6);
+ break;
+
+ // --- f32 Comparisons ---
+ case F32_EQ:
+ NativeEmitters.emitFcmp(ctx, 0);
+ break;
+ case F32_NE:
+ NativeEmitters.emitFcmp(ctx, 1);
+ break;
+ case F32_LT:
+ NativeEmitters.emitFcmp(ctx, 2);
+ break;
+ case F32_GT:
+ NativeEmitters.emitFcmp(ctx, 3);
+ break;
+ case F32_LE:
+ NativeEmitters.emitFcmp(ctx, 4);
+ break;
+ case F32_GE:
+ NativeEmitters.emitFcmp(ctx, 5);
+ break;
+
+ // --- f64 Arithmetic ---
+ case F64_ADD:
+ NativeEmitters.emitFloatBinaryOp(ctx, 0);
+ break;
+ case F64_SUB:
+ NativeEmitters.emitFloatBinaryOp(ctx, 1);
+ break;
+ case F64_MUL:
+ NativeEmitters.emitFloatBinaryOp(ctx, 2);
+ break;
+ case F64_DIV:
+ NativeEmitters.emitFloatBinaryOp(ctx, 3);
+ break;
+ case F64_MIN:
+ NativeEmitters.emitFloatBinaryOp(ctx, 4);
+ break;
+ case F64_MAX:
+ NativeEmitters.emitFloatBinaryOp(ctx, 5);
+ break;
+ case F64_COPYSIGN:
+ NativeEmitters.emitFloatBinaryOp(ctx, 6);
+ break;
+ case F64_ABS:
+ NativeEmitters.emitFloatUnaryOp(ctx, 0);
+ break;
+ case F64_NEG:
+ NativeEmitters.emitFloatUnaryOp(ctx, 1);
+ break;
+ case F64_CEIL:
+ NativeEmitters.emitFloatUnaryOp(ctx, 2);
+ break;
+ case F64_FLOOR:
+ NativeEmitters.emitFloatUnaryOp(ctx, 3);
+ break;
+ case F64_TRUNC:
+ NativeEmitters.emitFloatUnaryOp(ctx, 4);
+ break;
+ case F64_NEAREST:
+ NativeEmitters.emitFloatUnaryOp(ctx, 5);
+ break;
+ case F64_SQRT:
+ NativeEmitters.emitFloatUnaryOp(ctx, 6);
+ break;
+
+ // --- f64 Comparisons ---
+ case F64_EQ:
+ NativeEmitters.emitFcmp(ctx, 0);
+ break;
+ case F64_NE:
+ NativeEmitters.emitFcmp(ctx, 1);
+ break;
+ case F64_LT:
+ NativeEmitters.emitFcmp(ctx, 2);
+ break;
+ case F64_GT:
+ NativeEmitters.emitFcmp(ctx, 3);
+ break;
+ case F64_LE:
+ NativeEmitters.emitFcmp(ctx, 4);
+ break;
+ case F64_GE:
+ NativeEmitters.emitFcmp(ctx, 5);
+ break;
+
+ // --- Conversions ---
+ case I32_TRUNC_F32_S:
+ NativeEmitters.emitSafeTrunc(
+ ctx, CraneliftBridge.TYPE_I32, CraneliftBridge.TYPE_F32, true);
+ break;
+ case I32_TRUNC_F32_U:
+ NativeEmitters.emitSafeTrunc(
+ ctx, CraneliftBridge.TYPE_I32, CraneliftBridge.TYPE_F32, false);
+ break;
+ case I32_TRUNC_F64_S:
+ NativeEmitters.emitSafeTrunc(
+ ctx, CraneliftBridge.TYPE_I32, CraneliftBridge.TYPE_F64, true);
+ break;
+ case I32_TRUNC_F64_U:
+ NativeEmitters.emitSafeTrunc(
+ ctx, CraneliftBridge.TYPE_I32, CraneliftBridge.TYPE_F64, false);
+ break;
+ case I64_TRUNC_F32_S:
+ NativeEmitters.emitSafeTrunc(
+ ctx, CraneliftBridge.TYPE_I64, CraneliftBridge.TYPE_F32, true);
+ break;
+ case I64_TRUNC_F32_U:
+ NativeEmitters.emitSafeTrunc(
+ ctx, CraneliftBridge.TYPE_I64, CraneliftBridge.TYPE_F32, false);
+ break;
+ case I64_TRUNC_F64_S:
+ NativeEmitters.emitSafeTrunc(
+ ctx, CraneliftBridge.TYPE_I64, CraneliftBridge.TYPE_F64, true);
+ break;
+ case I64_TRUNC_F64_U:
+ NativeEmitters.emitSafeTrunc(
+ ctx, CraneliftBridge.TYPE_I64, CraneliftBridge.TYPE_F64, false);
+ break;
+
+ case I32_TRUNC_SAT_F32_S:
+ NativeEmitters.emitTruncSat(ctx, CraneliftBridge.TYPE_I32, true);
+ break;
+ case I32_TRUNC_SAT_F32_U:
+ NativeEmitters.emitTruncSat(ctx, CraneliftBridge.TYPE_I32, false);
+ break;
+ case I32_TRUNC_SAT_F64_S:
+ NativeEmitters.emitTruncSat(ctx, CraneliftBridge.TYPE_I32, true);
+ break;
+ case I32_TRUNC_SAT_F64_U:
+ NativeEmitters.emitTruncSat(ctx, CraneliftBridge.TYPE_I32, false);
+ break;
+ case I64_TRUNC_SAT_F32_S:
+ NativeEmitters.emitTruncSat(ctx, CraneliftBridge.TYPE_I64, true);
+ break;
+ case I64_TRUNC_SAT_F32_U:
+ NativeEmitters.emitTruncSat(ctx, CraneliftBridge.TYPE_I64, false);
+ break;
+ case I64_TRUNC_SAT_F64_S:
+ NativeEmitters.emitTruncSat(ctx, CraneliftBridge.TYPE_I64, true);
+ break;
+ case I64_TRUNC_SAT_F64_U:
+ NativeEmitters.emitTruncSat(ctx, CraneliftBridge.TYPE_I64, false);
+ break;
+
+ case F32_CONVERT_I32_S:
+ NativeEmitters.emitConvertFloat(ctx, CraneliftBridge.TYPE_F32, true);
+ break;
+ case F32_CONVERT_I32_U:
+ NativeEmitters.emitConvertFloat(ctx, CraneliftBridge.TYPE_F32, false);
+ break;
+ case F32_CONVERT_I64_S:
+ NativeEmitters.emitConvertFloat(ctx, CraneliftBridge.TYPE_F32, true);
+ break;
+ case F32_CONVERT_I64_U:
+ NativeEmitters.emitConvertFloat(ctx, CraneliftBridge.TYPE_F32, false);
+ break;
+ case F64_CONVERT_I32_S:
+ NativeEmitters.emitConvertFloat(ctx, CraneliftBridge.TYPE_F64, true);
+ break;
+ case F64_CONVERT_I32_U:
+ NativeEmitters.emitConvertFloat(ctx, CraneliftBridge.TYPE_F64, false);
+ break;
+ case F64_CONVERT_I64_S:
+ NativeEmitters.emitConvertFloat(ctx, CraneliftBridge.TYPE_F64, true);
+ break;
+ case F64_CONVERT_I64_U:
+ NativeEmitters.emitConvertFloat(ctx, CraneliftBridge.TYPE_F64, false);
+ break;
+
+ case F64_PROMOTE_F32:
+ NativeEmitters.emitFpromote(ctx);
+ break;
+ case F32_DEMOTE_F64:
+ NativeEmitters.emitFdemote(ctx);
+ break;
+
+ case F32_REINTERPRET_I32:
+ NativeEmitters.emitBitcastI32ToF32(ctx);
+ break;
+ case I32_REINTERPRET_F32:
+ NativeEmitters.emitBitcastF32ToI32(ctx);
+ break;
+ case F64_REINTERPRET_I64:
+ NativeEmitters.emitBitcastI64ToF64(ctx);
+ break;
+ case I64_REINTERPRET_F64:
+ NativeEmitters.emitBitcastF64ToI64(ctx);
+ break;
+
+ // --- Calls ---
+ case CALL:
+ NativeEmitters.emitCall(ctx, ins);
+ break;
+ case CALL_INDIRECT:
+ NativeEmitters.emitCallIndirect(ctx, ins);
+ break;
+ case RETURN_CALL:
+ NativeEmitters.emitReturnCall(ctx, ins);
+ controlStack.peek().unreachable = true;
+ break;
+ case RETURN_CALL_INDIRECT:
+ NativeEmitters.emitReturnCallIndirect(ctx, ins);
+ controlStack.peek().unreachable = true;
+ break;
+
+ // --- Control flow (stays in orchestrator) ---
+ case BLOCK:
+ emitBlock(ctx, ins, controlStack);
+ break;
+ case LOOP:
+ emitLoop(ctx, ins, controlStack);
+ break;
+ case IF:
+ emitIf(ctx, ins, controlStack);
+ break;
+ case ELSE:
+ emitElse(ctx, controlStack);
+ break;
+ // END is handled in the main loop, not here
+ case BR:
+ emitBr(ctx, ins, controlStack);
+ break;
+ case BR_IF:
+ emitBrIf(ctx, ins, controlStack);
+ break;
+ case BR_TABLE:
+ emitBrTable(ctx, ins, controlStack);
+ break;
+ case RETURN:
+ emitReturn(ctx, controlStack);
+ break;
+
+ // --- Table operations ---
+ case TABLE_GET:
+ NativeEmitters.emitTableGet(ctx, ins);
+ break;
+ case TABLE_SET:
+ NativeEmitters.emitTableSet(ctx, ins);
+ break;
+ case TABLE_SIZE:
+ NativeEmitters.emitTableSize(ctx, ins);
+ break;
+ case TABLE_GROW:
+ NativeEmitters.emitTableGrow(ctx, ins);
+ break;
+ case TABLE_FILL:
+ NativeEmitters.emitTableFill(ctx, ins);
+ break;
+ case TABLE_COPY:
+ NativeEmitters.emitTableCopy(ctx, ins);
+ break;
+ case TABLE_INIT:
+ NativeEmitters.emitTableInit(ctx, ins);
+ break;
+ case ELEM_DROP:
+ NativeEmitters.emitElemDrop(ctx, ins);
+ break;
+
+ // --- Atomic loads ---
+ case I32_ATOMIC_LOAD:
+ NativeEmitters.emitAtomicLoad(ctx, ins, 0);
+ break;
+ case I64_ATOMIC_LOAD:
+ NativeEmitters.emitAtomicLoad(ctx, ins, 1);
+ break;
+ case I32_ATOMIC_LOAD8_U:
+ NativeEmitters.emitAtomicLoad(ctx, ins, 2);
+ break;
+ case I32_ATOMIC_LOAD16_U:
+ NativeEmitters.emitAtomicLoad(ctx, ins, 3);
+ break;
+ case I64_ATOMIC_LOAD8_U:
+ NativeEmitters.emitAtomicLoad(ctx, ins, 4);
+ break;
+ case I64_ATOMIC_LOAD16_U:
+ NativeEmitters.emitAtomicLoad(ctx, ins, 5);
+ break;
+ case I64_ATOMIC_LOAD32_U:
+ NativeEmitters.emitAtomicLoad(ctx, ins, 6);
+ break;
+
+ // --- Atomic stores ---
+ case I32_ATOMIC_STORE:
+ NativeEmitters.emitAtomicStore(ctx, ins, 0);
+ break;
+ case I64_ATOMIC_STORE:
+ NativeEmitters.emitAtomicStore(ctx, ins, 1);
+ break;
+ case I32_ATOMIC_STORE8:
+ NativeEmitters.emitAtomicStore(ctx, ins, 2);
+ break;
+ case I32_ATOMIC_STORE16:
+ NativeEmitters.emitAtomicStore(ctx, ins, 3);
+ break;
+ case I64_ATOMIC_STORE8:
+ NativeEmitters.emitAtomicStore(ctx, ins, 4);
+ break;
+ case I64_ATOMIC_STORE16:
+ NativeEmitters.emitAtomicStore(ctx, ins, 5);
+ break;
+ case I64_ATOMIC_STORE32:
+ NativeEmitters.emitAtomicStore(ctx, ins, 6);
+ break;
+
+ // --- Atomic RMW add ---
+ case I32_ATOMIC_RMW_ADD:
+ NativeEmitters.emitAtomicRmw(ctx, ins, 0, 0);
+ break;
+ case I64_ATOMIC_RMW_ADD:
+ NativeEmitters.emitAtomicRmw(ctx, ins, 1, 0);
+ break;
+ case I32_ATOMIC_RMW8_ADD_U:
+ NativeEmitters.emitAtomicRmw(ctx, ins, 2, 0);
+ break;
+ case I32_ATOMIC_RMW16_ADD_U:
+ NativeEmitters.emitAtomicRmw(ctx, ins, 3, 0);
+ break;
+ case I64_ATOMIC_RMW8_ADD_U:
+ NativeEmitters.emitAtomicRmw(ctx, ins, 4, 0);
+ break;
+ case I64_ATOMIC_RMW16_ADD_U:
+ NativeEmitters.emitAtomicRmw(ctx, ins, 5, 0);
+ break;
+ case I64_ATOMIC_RMW32_ADD_U:
+ NativeEmitters.emitAtomicRmw(ctx, ins, 6, 0);
+ break;
+
+ // --- Atomic RMW sub ---
+ case I32_ATOMIC_RMW_SUB:
+ NativeEmitters.emitAtomicRmw(ctx, ins, 0, 1);
+ break;
+ case I64_ATOMIC_RMW_SUB:
+ NativeEmitters.emitAtomicRmw(ctx, ins, 1, 1);
+ break;
+ case I32_ATOMIC_RMW8_SUB_U:
+ NativeEmitters.emitAtomicRmw(ctx, ins, 2, 1);
+ break;
+ case I32_ATOMIC_RMW16_SUB_U:
+ NativeEmitters.emitAtomicRmw(ctx, ins, 3, 1);
+ break;
+ case I64_ATOMIC_RMW8_SUB_U:
+ NativeEmitters.emitAtomicRmw(ctx, ins, 4, 1);
+ break;
+ case I64_ATOMIC_RMW16_SUB_U:
+ NativeEmitters.emitAtomicRmw(ctx, ins, 5, 1);
+ break;
+ case I64_ATOMIC_RMW32_SUB_U:
+ NativeEmitters.emitAtomicRmw(ctx, ins, 6, 1);
+ break;
+
+ // --- Atomic RMW and ---
+ case I32_ATOMIC_RMW_AND:
+ NativeEmitters.emitAtomicRmw(ctx, ins, 0, 2);
+ break;
+ case I64_ATOMIC_RMW_AND:
+ NativeEmitters.emitAtomicRmw(ctx, ins, 1, 2);
+ break;
+ case I32_ATOMIC_RMW8_AND_U:
+ NativeEmitters.emitAtomicRmw(ctx, ins, 2, 2);
+ break;
+ case I32_ATOMIC_RMW16_AND_U:
+ NativeEmitters.emitAtomicRmw(ctx, ins, 3, 2);
+ break;
+ case I64_ATOMIC_RMW8_AND_U:
+ NativeEmitters.emitAtomicRmw(ctx, ins, 4, 2);
+ break;
+ case I64_ATOMIC_RMW16_AND_U:
+ NativeEmitters.emitAtomicRmw(ctx, ins, 5, 2);
+ break;
+ case I64_ATOMIC_RMW32_AND_U:
+ NativeEmitters.emitAtomicRmw(ctx, ins, 6, 2);
+ break;
+
+ // --- Atomic RMW or ---
+ case I32_ATOMIC_RMW_OR:
+ NativeEmitters.emitAtomicRmw(ctx, ins, 0, 3);
+ break;
+ case I64_ATOMIC_RMW_OR:
+ NativeEmitters.emitAtomicRmw(ctx, ins, 1, 3);
+ break;
+ case I32_ATOMIC_RMW8_OR_U:
+ NativeEmitters.emitAtomicRmw(ctx, ins, 2, 3);
+ break;
+ case I32_ATOMIC_RMW16_OR_U:
+ NativeEmitters.emitAtomicRmw(ctx, ins, 3, 3);
+ break;
+ case I64_ATOMIC_RMW8_OR_U:
+ NativeEmitters.emitAtomicRmw(ctx, ins, 4, 3);
+ break;
+ case I64_ATOMIC_RMW16_OR_U:
+ NativeEmitters.emitAtomicRmw(ctx, ins, 5, 3);
+ break;
+ case I64_ATOMIC_RMW32_OR_U:
+ NativeEmitters.emitAtomicRmw(ctx, ins, 6, 3);
+ break;
+
+ // --- Atomic RMW xor ---
+ case I32_ATOMIC_RMW_XOR:
+ NativeEmitters.emitAtomicRmw(ctx, ins, 0, 4);
+ break;
+ case I64_ATOMIC_RMW_XOR:
+ NativeEmitters.emitAtomicRmw(ctx, ins, 1, 4);
+ break;
+ case I32_ATOMIC_RMW8_XOR_U:
+ NativeEmitters.emitAtomicRmw(ctx, ins, 2, 4);
+ break;
+ case I32_ATOMIC_RMW16_XOR_U:
+ NativeEmitters.emitAtomicRmw(ctx, ins, 3, 4);
+ break;
+ case I64_ATOMIC_RMW8_XOR_U:
+ NativeEmitters.emitAtomicRmw(ctx, ins, 4, 4);
+ break;
+ case I64_ATOMIC_RMW16_XOR_U:
+ NativeEmitters.emitAtomicRmw(ctx, ins, 5, 4);
+ break;
+ case I64_ATOMIC_RMW32_XOR_U:
+ NativeEmitters.emitAtomicRmw(ctx, ins, 6, 4);
+ break;
+
+ // --- Atomic RMW xchg ---
+ case I32_ATOMIC_RMW_XCHG:
+ NativeEmitters.emitAtomicRmw(ctx, ins, 0, 5);
+ break;
+ case I64_ATOMIC_RMW_XCHG:
+ NativeEmitters.emitAtomicRmw(ctx, ins, 1, 5);
+ break;
+ case I32_ATOMIC_RMW8_XCHG_U:
+ NativeEmitters.emitAtomicRmw(ctx, ins, 2, 5);
+ break;
+ case I32_ATOMIC_RMW16_XCHG_U:
+ NativeEmitters.emitAtomicRmw(ctx, ins, 3, 5);
+ break;
+ case I64_ATOMIC_RMW8_XCHG_U:
+ NativeEmitters.emitAtomicRmw(ctx, ins, 4, 5);
+ break;
+ case I64_ATOMIC_RMW16_XCHG_U:
+ NativeEmitters.emitAtomicRmw(ctx, ins, 5, 5);
+ break;
+ case I64_ATOMIC_RMW32_XCHG_U:
+ NativeEmitters.emitAtomicRmw(ctx, ins, 6, 5);
+ break;
+
+ // --- Atomic cmpxchg ---
+ case I32_ATOMIC_RMW_CMPXCHG:
+ NativeEmitters.emitAtomicCmpxchg(ctx, ins, 0);
+ break;
+ case I64_ATOMIC_RMW_CMPXCHG:
+ NativeEmitters.emitAtomicCmpxchg(ctx, ins, 1);
+ break;
+ case I32_ATOMIC_RMW8_CMPXCHG_U:
+ NativeEmitters.emitAtomicCmpxchg(ctx, ins, 2);
+ break;
+ case I32_ATOMIC_RMW16_CMPXCHG_U:
+ NativeEmitters.emitAtomicCmpxchg(ctx, ins, 3);
+ break;
+ case I64_ATOMIC_RMW8_CMPXCHG_U:
+ NativeEmitters.emitAtomicCmpxchg(ctx, ins, 4);
+ break;
+ case I64_ATOMIC_RMW16_CMPXCHG_U:
+ NativeEmitters.emitAtomicCmpxchg(ctx, ins, 5);
+ break;
+ case I64_ATOMIC_RMW32_CMPXCHG_U:
+ NativeEmitters.emitAtomicCmpxchg(ctx, ins, 6);
+ break;
+
+ // --- Atomic wait/notify/fence ---
+ case MEM_ATOMIC_WAIT32:
+ NativeEmitters.emitAtomicWait32(ctx, ins);
+ break;
+ case MEM_ATOMIC_WAIT64:
+ NativeEmitters.emitAtomicWait64(ctx, ins);
+ break;
+ case MEM_ATOMIC_NOTIFY:
+ NativeEmitters.emitAtomicNotify(ctx, ins);
+ break;
+ case ATOMIC_FENCE:
+ NativeEmitters.emitAtomicFence(ctx);
+ break;
+
+ // --- Reference types ---
+ case REF_NULL:
+ NativeEmitters.emitRefNull(ctx);
+ break;
+ case REF_IS_NULL:
+ NativeEmitters.emitRefIsNull(ctx);
+ break;
+ case REF_FUNC:
+ NativeEmitters.emitRefFunc(ctx, ins);
+ break;
+
+ default:
+ throw new UnsupportedOperationException(
+ "Opcode not yet supported: " + ins.opcode());
+ }
+ }
+
+ // --- Control flow handlers ---
+
+ private void emitBlock(
+ EmitContext ctx, AnnotatedInstruction ins, Deque controlStack) {
+ FunctionType bt = decodeBlockType(ins);
+ int mergeBlock = bridge.exports().createBlock();
+ int[] mergeParamIds = appendBlockParams(mergeBlock, bt.returns());
+ int savedHeight = ctx.valueStack.size() - bt.params().size();
+ controlStack.push(
+ new ControlFrame(
+ ControlFrame.Kind.BLOCK,
+ mergeBlock,
+ -1,
+ -1,
+ mergeParamIds,
+ bt,
+ savedHeight));
+ }
+
+ private void emitLoop(
+ EmitContext ctx, AnnotatedInstruction ins, Deque controlStack) {
+ FunctionType bt = decodeBlockType(ins);
+ int loopHeader = bridge.exports().createBlock();
+ int mergeBlock = bridge.exports().createBlock();
+ int[] mergeParamIds = appendBlockParams(mergeBlock, bt.returns());
+ int[] loopParamIds = appendBlockParams(loopHeader, bt.params());
+ int savedHeight = ctx.valueStack.size() - bt.params().size();
+ emitJumpToBlock(loopHeader, bt.params().size(), ctx.valueStack);
+ bridge.exports().switchToBlock(loopHeader);
+ for (int pid : loopParamIds) {
+ ctx.valueStack.push(pid);
+ }
+
+ // --- Interrupt check at loop header ---
+ {
+ int flag =
+ bridge.exports()
+ .emitLoadI64(
+ bridge.exports().useVar(ctx.ctxPtrVar),
+ bridge.exports().emitIconst32(0),
+ CtxBuffer.INTERRUPT_FLAG);
+ int zeroI64 = bridge.exports().emitIconst64(0, 0);
+ int interrupted = bridge.exports().emitIcmp(1, flag, zeroI64); // NE
+ int trapBlock = bridge.exports().createBlock();
+ int okBlock = bridge.exports().createBlock();
+ bridge.exports().emitBrif(interrupted, trapBlock, okBlock);
+
+ NativeEmitters.fillTrapBlock(ctx, trapBlock, CtxBuffer.TRAP_INTERRUPTED);
+
+ bridge.exports().switchToBlock(okBlock);
+ }
+
+ controlStack.push(
+ new ControlFrame(
+ ControlFrame.Kind.LOOP,
+ mergeBlock,
+ loopHeader,
+ -1,
+ mergeParamIds,
+ bt,
+ savedHeight));
+ }
+
+ private void emitIf(
+ EmitContext ctx, AnnotatedInstruction ins, Deque controlStack) {
+ FunctionType bt = decodeBlockType(ins);
+ int condition = ctx.valueStack.pop();
+ int thenBlock = bridge.exports().createBlock();
+ int elseBlock = bridge.exports().createBlock();
+ int mergeBlock = bridge.exports().createBlock();
+ int[] mergeParamIds = appendBlockParams(mergeBlock, bt.returns());
+ int savedHeight = ctx.valueStack.size() - bt.params().size();
+
+ int[] elseParamIds = null;
+ if (bt.params().isEmpty()) {
+ bridge.exports().emitBrif(condition, thenBlock, elseBlock);
+ bridge.exports().switchToBlock(thenBlock);
+ bridge.exports().sealBlock(thenBlock);
+ } else {
+ // Block has params — both branches need them.
+ // brif only passes args to the true target, so we use a
+ // fallthrough block for the false path.
+ int[] thenParamIds = appendBlockParams(thenBlock, bt.params());
+ elseParamIds = appendBlockParams(elseBlock, bt.params());
+
+ int paramCount = bt.params().size();
+ int[] paramVals = new int[paramCount];
+ for (int i = paramCount - 1; i >= 0; i--) {
+ paramVals[i] = ctx.valueStack.pop();
+ }
+
+ // brif → thenBlock (with args) / fallthroughBlock (no args)
+ int fallthroughBlock = bridge.exports().createBlock();
+ for (int pv : paramVals) {
+ bridge.exports().pushCallArg(pv);
+ }
+ bridge.exports().emitBrifWithJumpArgs(condition, thenBlock, fallthroughBlock);
+
+ // Fallthrough → elseBlock (with args)
+ bridge.exports().switchToBlock(fallthroughBlock);
+ if (paramCount == 1) {
+ bridge.exports().emitJumpWithArg(elseBlock, paramVals[0]);
+ } else {
+ for (int pv : paramVals) {
+ bridge.exports().pushCallArg(pv);
+ }
+ bridge.exports().emitJumpWithArgs(elseBlock);
+ }
+
+ // Switch to then block, push its block params onto value stack
+ bridge.exports().switchToBlock(thenBlock);
+ bridge.exports().sealBlock(thenBlock);
+ for (int pid : thenParamIds) {
+ ctx.valueStack.push(pid);
+ }
+ }
+
+ var frame =
+ new ControlFrame(
+ ControlFrame.Kind.IF,
+ mergeBlock,
+ -1,
+ elseBlock,
+ mergeParamIds,
+ bt,
+ savedHeight);
+ frame.elseParamIds = elseParamIds;
+ controlStack.push(frame);
+ }
+
+ private void emitElse(EmitContext ctx, Deque controlStack) {
+ ControlFrame frame = controlStack.peek();
+ if (frame.mergeBlock < 0) {
+ frame.hasElse = true;
+ return;
+ }
+ if (!frame.unreachable) {
+ emitJumpToBlock(frame.mergeBlock, frame.blockType.returns().size(), ctx.valueStack);
+ }
+ ctx.valueStack.trimTo(frame.stackHeight);
+ bridge.exports().switchToBlock(frame.elseBlock);
+ bridge.exports().sealBlock(frame.elseBlock);
+
+ // Push else block's param IDs for the else branch
+ if (frame.elseParamIds != null) {
+ for (int pid : frame.elseParamIds) {
+ ctx.valueStack.push(pid);
+ }
+ }
+
+ frame.hasElse = true;
+ frame.unreachable = false;
+ }
+
+ private void emitEnd(EmitContext ctx, Deque controlStack) {
+ ControlFrame frame = controlStack.pop();
+ var valueStack = ctx.valueStack;
+ boolean isDummy = frame.mergeBlock < 0 && frame.kind != ControlFrame.Kind.FUNCTION;
+ boolean dead = frame.unreachable;
+
+ switch (frame.kind) {
+ case FUNCTION:
+ if (!dead) {
+ emitFuncReturn(ctx, frame.blockType);
+ }
+ // If dead, the block is already terminated (by BR/RETURN at
+ // function level). No return needed — inner block ENDs switch
+ // to merge blocks and reset unreachable, so they go through
+ // the !dead path above.
+
+ break;
+
+ case BLOCK:
+ case LOOP:
+ if (isDummy) {
+ break;
+ }
+ if (!dead) {
+ emitJumpToBlock(frame.mergeBlock, frame.blockType.returns().size(), valueStack);
+ } else if (frame.mergeParamIds.length > 0) {
+ emitDeadPredecessor(frame.mergeBlock, frame.blockType.returns());
+ }
+ bridge.exports().switchToBlock(frame.mergeBlock);
+ bridge.exports().sealBlock(frame.mergeBlock);
+ // Always use trimTo + push: merge block params are the canonical
+ // values regardless of whether the block was reachable.
+ valueStack.trimTo(frame.stackHeight);
+ for (int pid : frame.mergeParamIds) {
+ valueStack.push(pid);
+ }
+
+ break;
+
+ case IF:
+ if (isDummy) {
+ break;
+ }
+ if (!frame.hasElse) {
+ if (!dead) {
+ emitJumpToBlock(
+ frame.mergeBlock, frame.blockType.returns().size(), valueStack);
+ }
+ bridge.exports().switchToBlock(frame.elseBlock);
+ bridge.exports().sealBlock(frame.elseBlock);
+ // Implicit else: pass block params through to merge.
+ // For IF without ELSE, params == returns per Wasm spec.
+ if (frame.elseParamIds != null && frame.elseParamIds.length > 0) {
+ // Use else block's own param values
+ if (frame.elseParamIds.length == 1) {
+ bridge.exports()
+ .emitJumpWithArg(frame.mergeBlock, frame.elseParamIds[0]);
+ } else {
+ for (int pid : frame.elseParamIds) {
+ bridge.exports().pushCallArg(pid);
+ }
+ bridge.exports().emitJumpWithArgs(frame.mergeBlock);
+ }
+ } else if (frame.blockType.returns().isEmpty()) {
+ bridge.exports().emitJump(frame.mergeBlock);
+ } else {
+ // No else params (paramless IF) — use dummy zeros
+ for (ValType t : frame.blockType.returns()) {
+ bridge.exports().pushCallArg(emitZero(t));
+ }
+ bridge.exports().emitJumpWithArgs(frame.mergeBlock);
+ }
+ } else {
+ if (!dead) {
+ emitJumpToBlock(
+ frame.mergeBlock, frame.blockType.returns().size(), valueStack);
+ } else if (frame.mergeParamIds.length > 0) {
+ emitDeadPredecessor(frame.mergeBlock, frame.blockType.returns());
+ }
+ }
+ bridge.exports().switchToBlock(frame.mergeBlock);
+ bridge.exports().sealBlock(frame.mergeBlock);
+ valueStack.trimTo(frame.stackHeight);
+ for (int pid : frame.mergeParamIds) {
+ valueStack.push(pid);
+ }
+
+ break;
+ }
+ if (!controlStack.isEmpty() && !isDummy) {
+ controlStack.peek().unreachable = false;
+ }
+ }
+
+ private void emitFuncReturn(EmitContext ctx, FunctionType funcType) {
+ int retCount = funcType.returns().size();
+ var valueStack = ctx.valueStack;
+ if (retCount == 0) {
+ bridge.exports().emitReturnVoid();
+ } else if (!ctx.multiReturn && retCount == 1 && !valueStack.isEmpty()) {
+ bridge.exports().emitReturn(valueStack.pop());
+ } else if (retCount >= 1 && valueStack.size() >= retCount) {
+ int[] retVals = new int[retCount];
+ for (int ri = retCount - 1; ri >= 0; ri--) {
+ retVals[ri] = valueStack.pop();
+ }
+ if (ctx.multiReturn) {
+ ctx.emitWriteReturnsToArgsBuffer(funcType.returns(), retVals);
+ bridge.exports().emitReturn(bridge.exports().emitIconst64(0, 0));
+ } else {
+ bridge.exports().emitReturn(retVals[0]);
+ }
+ } else {
+ ctx.emitReturnForFuncType();
+ }
+ }
+
+ private void emitReturnWithArgs(EmitContext ctx, int[] args, int argCount) {
+ if (argCount == 0) {
+ bridge.exports().emitReturnVoid();
+ } else if (!ctx.multiReturn && argCount == 1) {
+ bridge.exports().emitReturn(args[0]);
+ } else if (ctx.multiReturn) {
+ ctx.emitWriteReturnsToArgsBuffer(ctx.funcType.returns(), args);
+ bridge.exports().emitReturn(bridge.exports().emitIconst64(0, 0));
+ } else {
+ bridge.exports().emitReturn(args[0]);
+ }
+ }
+
+ private void emitBrToFunction(EmitContext ctx, ControlFrame funcFrame) {
+ int retCount = funcFrame.blockType.returns().size();
+ if (retCount == 0) {
+ bridge.exports().emitReturnVoid();
+ } else if (!ctx.multiReturn && retCount == 1) {
+ bridge.exports().emitReturn(ctx.valueStack.pop());
+ } else {
+ int[] retVals = new int[retCount];
+ for (int i = retCount - 1; i >= 0; i--) {
+ retVals[i] = ctx.valueStack.pop();
+ }
+ if (ctx.multiReturn) {
+ ctx.emitWriteReturnsToArgsBuffer(funcFrame.blockType.returns(), retVals);
+ bridge.exports().emitReturn(bridge.exports().emitIconst64(0, 0));
+ } else {
+ bridge.exports().emitReturn(retVals[0]);
+ }
+ }
+ }
+
+ private void emitBr(
+ EmitContext ctx, AnnotatedInstruction ins, Deque controlStack) {
+ int depth = (int) ins.operands()[0];
+ ControlFrame target = getControlFrame(controlStack, depth);
+ if (target.kind == ControlFrame.Kind.FUNCTION) {
+ emitBrToFunction(ctx, target);
+ } else {
+ int brTarget = target.branchTarget();
+ int argCount = target.branchArgCount();
+ emitJumpToBlock(brTarget, argCount, ctx.valueStack);
+ }
+ controlStack.peek().unreachable = true;
+ }
+
+ private void emitBrIf(
+ EmitContext ctx, AnnotatedInstruction ins, Deque controlStack) {
+ int depth = (int) ins.operands()[0];
+ int condition = ctx.valueStack.pop();
+ ControlFrame target = getControlFrame(controlStack, depth);
+
+ if (target.kind == ControlFrame.Kind.FUNCTION) {
+ // BR_IF targeting function = conditional return
+ int returnBlock = bridge.exports().createBlock();
+ int fallthroughBlock = bridge.exports().createBlock();
+ int argCount = target.branchArgCount();
+
+ if (argCount > 0) {
+ int[] args = new int[argCount];
+ for (int i = argCount - 1; i >= 0; i--) {
+ args[i] = ctx.valueStack.pop();
+ }
+ // Add block params to returnBlock so brif can pass values
+ int[] returnParams = new int[argCount];
+ var returns = target.blockType.returns();
+ for (int i = 0; i < argCount; i++) {
+ returnParams[i] =
+ bridge.exports()
+ .appendBlockParam(
+ returnBlock,
+ EmitContext.valTypeToBridgeType(returns.get(i)));
+ }
+ for (int a : args) {
+ bridge.exports().pushCallArg(a);
+ }
+ bridge.exports().emitBrifWithJumpArgs(condition, returnBlock, fallthroughBlock);
+ // Push args back for fallthrough
+ for (int a : args) {
+ ctx.valueStack.push(a);
+ }
+ // Return block: emit return with block params (not original args)
+ bridge.exports().switchToBlock(returnBlock);
+ emitReturnWithArgs(ctx, returnParams, argCount);
+ } else {
+ bridge.exports().emitBrif(condition, returnBlock, fallthroughBlock);
+ bridge.exports().switchToBlock(returnBlock);
+ bridge.exports().emitReturnVoid();
+ }
+ bridge.exports().switchToBlock(fallthroughBlock);
+ return;
+ }
+
+ int brTarget = target.branchTarget();
+ int fallthroughBlock = bridge.exports().createBlock();
+ int argCount = target.branchArgCount();
+
+ if (argCount > 0) {
+ int[] args = new int[argCount];
+ for (int i = argCount - 1; i >= 0; i--) {
+ args[i] = ctx.valueStack.pop();
+ }
+ for (int a : args) {
+ bridge.exports().pushCallArg(a);
+ }
+ bridge.exports().emitBrifWithJumpArgs(condition, brTarget, fallthroughBlock);
+ for (int a : args) {
+ ctx.valueStack.push(a);
+ }
+ } else {
+ bridge.exports().emitBrif(condition, brTarget, fallthroughBlock);
+ }
+ bridge.exports().switchToBlock(fallthroughBlock);
+ }
+
+ private void emitBrTable(
+ EmitContext ctx, AnnotatedInstruction ins, Deque controlStack) {
+ int index = ctx.valueStack.pop();
+ int defaultIdx = ins.operandCount() - 1;
+ int defaultDepth = (int) ins.operand(defaultIdx);
+
+ ControlFrame defaultTarget = getControlFrame(controlStack, defaultDepth);
+ int argCount = defaultTarget.branchArgCount();
+ int[] brArgs = new int[argCount];
+ for (int i = argCount - 1; i >= 0; i--) {
+ brArgs[i] = ctx.valueStack.pop();
+ }
+
+ // Build jump table targets with edge splitting.
+ // Cranelift's br_table doesn't support block arguments, and FUNCTION
+ // targets need a return instead of a jump — so we create intermediate
+ // blocks for any target that needs special handling.
+ int[] intermediates = new int[defaultIdx];
+ boolean[] needsIntermediate = new boolean[defaultIdx];
+ for (int i = 0; i < defaultIdx; i++) {
+ int depth = (int) ins.operand(i);
+ ControlFrame target = getControlFrame(controlStack, depth);
+ if (argCount > 0 || target.kind == ControlFrame.Kind.FUNCTION) {
+ intermediates[i] = bridge.exports().createBlock();
+ needsIntermediate[i] = true;
+ bridge.exports().pushBrTableTarget(intermediates[i]);
+ } else {
+ bridge.exports().pushBrTableTarget(target.branchTarget());
+ }
+ }
+ boolean defaultNeedsIntermediate =
+ argCount > 0 || defaultTarget.kind == ControlFrame.Kind.FUNCTION;
+ int defaultJtTarget;
+ if (defaultNeedsIntermediate) {
+ defaultJtTarget = bridge.exports().createBlock();
+ } else {
+ defaultJtTarget = defaultTarget.branchTarget();
+ }
+ bridge.exports().emitBrTable(index, defaultJtTarget);
+
+ // Emit intermediate blocks
+ for (int i = 0; i < defaultIdx; i++) {
+ if (!needsIntermediate[i]) {
+ continue;
+ }
+ bridge.exports().switchToBlock(intermediates[i]);
+ bridge.exports().sealBlock(intermediates[i]);
+ int depth = (int) ins.operand(i);
+ ControlFrame target = getControlFrame(controlStack, depth);
+ if (target.kind == ControlFrame.Kind.FUNCTION) {
+ emitReturnWithArgs(ctx, brArgs, argCount);
+ } else if (argCount == 0) {
+ bridge.exports().emitJump(target.branchTarget());
+ } else {
+ emitJumpWithArgs(target.branchTarget(), brArgs, argCount);
+ }
+ }
+
+ if (defaultNeedsIntermediate) {
+ bridge.exports().switchToBlock(defaultJtTarget);
+ bridge.exports().sealBlock(defaultJtTarget);
+ if (defaultTarget.kind == ControlFrame.Kind.FUNCTION) {
+ emitReturnWithArgs(ctx, brArgs, argCount);
+ } else if (argCount == 0) {
+ bridge.exports().emitJump(defaultTarget.branchTarget());
+ } else {
+ emitJumpWithArgs(defaultTarget.branchTarget(), brArgs, argCount);
+ }
+ }
+
+ controlStack.peek().unreachable = true;
+ }
+
+ private void emitJumpWithArgs(int blockId, int[] args, int argCount) {
+ if (argCount == 1) {
+ bridge.exports().emitJumpWithArg(blockId, args[0]);
+ } else {
+ for (int a : args) {
+ bridge.exports().pushCallArg(a);
+ }
+ bridge.exports().emitJumpWithArgs(blockId);
+ }
+ }
+
+ private void emitReturn(EmitContext ctx, Deque controlStack) {
+ ControlFrame funcFrame = null;
+ for (ControlFrame f : controlStack) {
+ funcFrame = f;
+ }
+ if (funcFrame != null) {
+ emitBrToFunction(ctx, funcFrame);
+ } else {
+ bridge.exports().emitReturnVoid();
+ }
+ controlStack.peek().unreachable = true;
+ }
+}
diff --git a/redline/compiler/src/main/java/run/endive/redline/experimental/compiler/internal/NativeEmitters.java b/redline/compiler/src/main/java/run/endive/redline/experimental/compiler/internal/NativeEmitters.java
new file mode 100644
index 000000000..a03df5ef1
--- /dev/null
+++ b/redline/compiler/src/main/java/run/endive/redline/experimental/compiler/internal/NativeEmitters.java
@@ -0,0 +1,1839 @@
+package run.endive.redline.experimental.compiler.internal;
+
+import run.endive.redline.experimental.api.internal.CtxBuffer;
+import run.endive.wasm.types.AnnotatedInstruction;
+import run.endive.wasm.types.ExternalType;
+import run.endive.wasm.types.FunctionType;
+import run.endive.wasm.types.TableImport;
+import run.endive.wasm.types.ValType;
+
+/**
+ * Static methods that emit Cranelift IR for each opcode category.
+ * No control flow logic — these are pure opcode handlers that pop
+ * operands from the value stack, call bridge exports, and push results.
+ */
+final class NativeEmitters {
+
+ private NativeEmitters() {}
+
+ // --- Constants ---
+
+ static void emitI32Const(EmitContext ctx, AnnotatedInstruction ins) {
+ ctx.valueStack.push(ctx.bridge.exports().emitIconst32((int) ins.operands()[0]));
+ }
+
+ static void emitI64Const(EmitContext ctx, AnnotatedInstruction ins) {
+ long val = ins.operands()[0];
+ ctx.valueStack.push(ctx.bridge.exports().emitIconst64((int) val, (int) (val >>> 32)));
+ }
+
+ static void emitF32Const(EmitContext ctx, AnnotatedInstruction ins) {
+ ctx.valueStack.push(ctx.bridge.exports().emitF32const((int) ins.operands()[0]));
+ }
+
+ static void emitF64Const(EmitContext ctx, AnnotatedInstruction ins) {
+ long bits = ins.operands()[0];
+ ctx.valueStack.push(ctx.bridge.exports().emitF64const((int) bits, (int) (bits >>> 32)));
+ }
+
+ // --- i32 Arithmetic ---
+
+ static void emitI32BinaryOp(EmitContext ctx, int op) {
+ int b = ctx.valueStack.pop();
+ int a = ctx.valueStack.pop();
+ int result;
+ switch (op) {
+ case 0:
+ result = ctx.bridge.exports().emitIadd(a, b);
+ break;
+ case 1:
+ result = ctx.bridge.exports().emitIsub(a, b);
+ break;
+ case 2:
+ result = ctx.bridge.exports().emitImul(a, b);
+ break;
+ case 3:
+ result = ctx.bridge.exports().emitBand(a, b);
+ break;
+ case 4:
+ result = ctx.bridge.exports().emitBor(a, b);
+ break;
+ case 5:
+ result = ctx.bridge.exports().emitBxor(a, b);
+ break;
+ case 6:
+ result = ctx.bridge.exports().emitIshl(a, b);
+ break;
+ case 7:
+ result = ctx.bridge.exports().emitSshr(a, b);
+ break;
+ case 8:
+ result = ctx.bridge.exports().emitUshr(a, b);
+ break;
+ case 9:
+ result = ctx.bridge.exports().emitRotl(a, b);
+ break;
+ case 10:
+ result = ctx.bridge.exports().emitRotr(a, b);
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown i32 binary op: " + op);
+ }
+ ctx.valueStack.push(result);
+ }
+
+ static void emitI32UnaryOp(EmitContext ctx, int op) {
+ int val = ctx.valueStack.pop();
+ int result;
+ switch (op) {
+ case 0:
+ result = ctx.bridge.exports().emitClz(val);
+ break;
+ case 1:
+ result = ctx.bridge.exports().emitCtz(val);
+ break;
+ case 2:
+ result = ctx.bridge.exports().emitPopcnt(val);
+ break;
+ case 3:
+ result = ctx.bridge.exports().emitEqz(val);
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown i32 unary op: " + op);
+ }
+ ctx.valueStack.push(result);
+ }
+
+ // --- i64 Arithmetic ---
+
+ static void emitI64UnaryOp(EmitContext ctx, int op) {
+ int val = ctx.valueStack.pop();
+ int result;
+ switch (op) {
+ case 0:
+ result = ctx.bridge.exports().emitClz(val);
+ break;
+ case 1:
+ result = ctx.bridge.exports().emitCtz(val);
+ break;
+ case 2:
+ result = ctx.bridge.exports().emitPopcnt(val);
+ break;
+ case 3:
+ result = ctx.bridge.exports().emitEqzI64(val);
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown i64 unary op: " + op);
+ }
+ ctx.valueStack.push(result);
+ }
+
+ // --- Comparisons (i32 and i64 share emitIcmp) ---
+
+ static void emitIcmp(EmitContext ctx, int cmpCode) {
+ int b = ctx.valueStack.pop();
+ int a = ctx.valueStack.pop();
+ ctx.valueStack.push(ctx.bridge.exports().emitIcmp(cmpCode, a, b));
+ }
+
+ // --- Float Arithmetic ---
+
+ static void emitFloatBinaryOp(EmitContext ctx, int op) {
+ int b = ctx.valueStack.pop();
+ int a = ctx.valueStack.pop();
+ int result;
+ switch (op) {
+ case 0:
+ result = ctx.bridge.exports().emitFadd(a, b);
+ break;
+ case 1:
+ result = ctx.bridge.exports().emitFsub(a, b);
+ break;
+ case 2:
+ result = ctx.bridge.exports().emitFmul(a, b);
+ break;
+ case 3:
+ result = ctx.bridge.exports().emitFdiv(a, b);
+ break;
+ case 4:
+ result = ctx.bridge.exports().emitFmin(a, b);
+ break;
+ case 5:
+ result = ctx.bridge.exports().emitFmax(a, b);
+ break;
+ case 6:
+ result = ctx.bridge.exports().emitFcopysign(a, b);
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown float binary op: " + op);
+ }
+ ctx.valueStack.push(result);
+ }
+
+ static void emitFloatUnaryOp(EmitContext ctx, int op) {
+ int val = ctx.valueStack.pop();
+ int result;
+ switch (op) {
+ case 0:
+ result = ctx.bridge.exports().emitFabs(val);
+ break;
+ case 1:
+ result = ctx.bridge.exports().emitFneg(val);
+ break;
+ case 2:
+ result = ctx.bridge.exports().emitCeil(val);
+ break;
+ case 3:
+ result = ctx.bridge.exports().emitFloor(val);
+ break;
+ case 4:
+ result = ctx.bridge.exports().emitTruncFloat(val);
+ break;
+ case 5:
+ result = ctx.bridge.exports().emitNearest(val);
+ break;
+ case 6:
+ result = ctx.bridge.exports().emitSqrt(val);
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown float unary op: " + op);
+ }
+ ctx.valueStack.push(result);
+ }
+
+ // --- Float Comparisons ---
+
+ static void emitFcmp(EmitContext ctx, int cmpCode) {
+ int b = ctx.valueStack.pop();
+ int a = ctx.valueStack.pop();
+ ctx.valueStack.push(ctx.bridge.exports().emitFcmp(cmpCode, a, b));
+ }
+
+ // --- Safe Division (with trap pre-checks) ---
+
+ static void emitSafeDiv(EmitContext ctx, boolean signed, boolean isRem, boolean is64) {
+ int divisor = ctx.valueStack.pop();
+ int dividend = ctx.valueStack.pop();
+
+ int trapBlockZero = ctx.bridge.exports().createBlock();
+ int safeBlock;
+
+ int zero =
+ is64
+ ? ctx.bridge.exports().emitIconst64(0, 0)
+ : ctx.bridge.exports().emitIconst32(0);
+ int isZero = ctx.bridge.exports().emitIcmp(0, divisor, zero); // EQ
+
+ if (signed && !isRem) {
+ int checkOverflow = ctx.bridge.exports().createBlock();
+ safeBlock = ctx.bridge.exports().createBlock();
+ ctx.bridge.exports().emitBrif(isZero, trapBlockZero, checkOverflow);
+
+ ctx.bridge.exports().switchToBlock(checkOverflow);
+ int intMin;
+ int negOne;
+ if (is64) {
+ intMin = ctx.bridge.exports().emitIconst64(0, 0x80000000);
+ negOne = ctx.bridge.exports().emitIconst64(-1, -1);
+ } else {
+ intMin = ctx.bridge.exports().emitIconst32(0x80000000);
+ negOne = ctx.bridge.exports().emitIconst32(-1);
+ }
+ int isMin = ctx.bridge.exports().emitIcmp(0, dividend, intMin);
+ int isNeg1 = ctx.bridge.exports().emitIcmp(0, divisor, negOne);
+ int both = ctx.bridge.exports().emitBand(isMin, isNeg1);
+
+ int trapBlockOverflow = ctx.bridge.exports().createBlock();
+ ctx.bridge.exports().emitBrif(both, trapBlockOverflow, safeBlock);
+
+ fillTrapBlock(ctx, trapBlockOverflow, CtxBuffer.TRAP_INT_OVERFLOW);
+ } else {
+ safeBlock = ctx.bridge.exports().createBlock();
+ ctx.bridge.exports().emitBrif(isZero, trapBlockZero, safeBlock);
+ }
+
+ fillTrapBlock(ctx, trapBlockZero, CtxBuffer.TRAP_DIV_BY_ZERO);
+
+ ctx.bridge.exports().switchToBlock(safeBlock);
+ int result;
+ if (signed) {
+ result =
+ isRem
+ ? ctx.bridge.exports().emitSrem(dividend, divisor)
+ : ctx.bridge.exports().emitSdiv(dividend, divisor);
+ } else {
+ result =
+ isRem
+ ? ctx.bridge.exports().emitUrem(dividend, divisor)
+ : ctx.bridge.exports().emitUdiv(dividend, divisor);
+ }
+ ctx.valueStack.push(result);
+ }
+
+ // --- Safe Float Truncation ---
+
+ static void emitSafeTrunc(EmitContext ctx, int targetType, int sourceType, boolean signed) {
+ int fval = ctx.valueStack.pop();
+
+ int satResult =
+ signed
+ ? ctx.bridge.exports().emitFcvtToSintSat(targetType, fval)
+ : ctx.bridge.exports().emitFcvtToUintSat(targetType, fval);
+
+ // NaN check: fcmp NE x,x → true if NaN
+ int isNan = ctx.bridge.exports().emitFcmp(1, fval, fval);
+ int nanTrapBlock = ctx.bridge.exports().createBlock();
+ int rangeCheckBlock = ctx.bridge.exports().createBlock();
+ ctx.bridge.exports().emitBrif(isNan, nanTrapBlock, rangeCheckBlock);
+ fillTrapBlock(ctx, nanTrapBlock, CtxBuffer.TRAP_TRUNC_NAN);
+
+ // Range check: trap if value is out of representable integer range
+ ctx.bridge.exports().switchToBlock(rangeCheckBlock);
+ int tooHigh = emitTruncUpperCheck(ctx, fval, targetType, sourceType, signed);
+ int tooLow = emitTruncLowerCheck(ctx, fval, targetType, sourceType, signed);
+ int outOfRange = ctx.bridge.exports().emitBor(tooHigh, tooLow);
+ int overflowTrapBlock = ctx.bridge.exports().createBlock();
+ int okBlock = ctx.bridge.exports().createBlock();
+ ctx.bridge.exports().emitBrif(outOfRange, overflowTrapBlock, okBlock);
+
+ fillTrapBlock(ctx, overflowTrapBlock, CtxBuffer.TRAP_TRUNC_OVERFLOW);
+
+ ctx.bridge.exports().switchToBlock(okBlock);
+ ctx.valueStack.push(satResult);
+ }
+
+ private static int emitTruncUpperCheck(
+ EmitContext ctx, int fval, int targetType, int sourceType, boolean signed) {
+ if (sourceType == 2 /* F32 */) {
+ float bound;
+ if (targetType == 0 /* I32 */) {
+ bound = signed ? 0x1.0p31f : 0x1.0p32f;
+ } else {
+ bound = signed ? 0x1.0p63f : 0x1.0p64f;
+ }
+ int c = ctx.bridge.exports().emitF32const(Float.floatToRawIntBits(bound));
+ return ctx.bridge.exports().emitFcmp(5, fval, c); // GE
+ } else {
+ double bound;
+ if (targetType == 0 /* I32 */) {
+ bound = signed ? 0x1.0p31 : 0x1.0p32;
+ } else {
+ bound = signed ? 0x1.0p63 : 0x1.0p64;
+ }
+ long bits = Double.doubleToRawLongBits(bound);
+ int c = ctx.bridge.exports().emitF64const((int) bits, (int) (bits >>> 32));
+ return ctx.bridge.exports().emitFcmp(5, fval, c); // GE
+ }
+ }
+
+ private static int emitTruncLowerCheck(
+ EmitContext ctx, int fval, int targetType, int sourceType, boolean signed) {
+ if (sourceType == 2 /* F32 */) {
+ float bound;
+ if (signed) {
+ bound = targetType == 0 /* I32 */ ? -0x1.000002p31f : -0x1.000002p63f;
+ } else {
+ bound = -1.0f;
+ }
+ int c = ctx.bridge.exports().emitF32const(Float.floatToRawIntBits(bound));
+ return ctx.bridge.exports().emitFcmp(4, fval, c); // LE
+ } else {
+ double bound;
+ if (signed) {
+ bound = targetType == 0 /* I32 */ ? -0x1.00000002p31 : -0x1.0000000000001p63;
+ } else {
+ bound = -1.0;
+ }
+ long bits = Double.doubleToRawLongBits(bound);
+ int c = ctx.bridge.exports().emitF64const((int) bits, (int) (bits >>> 32));
+ return ctx.bridge.exports().emitFcmp(4, fval, c); // LE
+ }
+ }
+
+ // --- Trap helper ---
+
+ static void fillTrapBlock(EmitContext ctx, int trapBlock, int trapCode) {
+ ctx.bridge.exports().switchToBlock(trapBlock);
+ int ctxVal = ctx.bridge.exports().useVar(ctx.ctxPtrVar);
+ int zero = ctx.bridge.exports().emitIconst32(0);
+ int code = ctx.bridge.exports().emitIconst32(trapCode);
+ ctx.bridge.exports().emitStoreI32(ctxVal, zero, code, CtxBuffer.TRAP_CODE);
+ ctx.emitReturnForFuncType();
+ }
+
+ // --- Extensions ---
+
+ static void emitI32Extend8S(EmitContext ctx) {
+ ctx.valueStack.push(ctx.bridge.exports().emitSextend832(ctx.valueStack.pop()));
+ }
+
+ static void emitI32Extend16S(EmitContext ctx) {
+ ctx.valueStack.push(ctx.bridge.exports().emitSextend1632(ctx.valueStack.pop()));
+ }
+
+ static void emitI64ExtendI32S(EmitContext ctx) {
+ ctx.valueStack.push(ctx.bridge.exports().emitSextendI64(ctx.valueStack.pop()));
+ }
+
+ static void emitI64ExtendI32U(EmitContext ctx) {
+ ctx.valueStack.push(ctx.bridge.exports().emitUextendI64(ctx.valueStack.pop()));
+ }
+
+ static void emitI64Extend8S(EmitContext ctx) {
+ ctx.valueStack.push(ctx.bridge.exports().emitSextend864(ctx.valueStack.pop()));
+ }
+
+ static void emitI64Extend16S(EmitContext ctx) {
+ ctx.valueStack.push(ctx.bridge.exports().emitSextend1664(ctx.valueStack.pop()));
+ }
+
+ static void emitI64Extend32S(EmitContext ctx) {
+ ctx.valueStack.push(ctx.bridge.exports().emitSextend3264(ctx.valueStack.pop()));
+ }
+
+ static void emitI32WrapI64(EmitContext ctx) {
+ ctx.valueStack.push(ctx.bridge.exports().emitI32WrapI64(ctx.valueStack.pop()));
+ }
+
+ // --- Reload memBase after calls (callee may have done memory.grow) ---
+
+ private static void reloadMemBase(EmitContext ctx) {
+ int zero = ctx.bridge.exports().emitIconst32(0);
+ int newMemBase =
+ ctx.bridge
+ .exports()
+ .emitLoadI64(
+ ctx.bridge.exports().useVar(ctx.ctxPtrVar),
+ zero,
+ CtxBuffer.MEM_BASE_ADDR);
+ ctx.bridge.exports().defVar(ctx.memBaseVar, newMemBase);
+ }
+
+ // --- Memory bounds check ---
+
+ private static final int[] LOAD_ACCESS_SIZE = {4, 8, 4, 8, 1, 1, 2, 2, 1, 1, 2, 2, 4, 4};
+ private static final int[] STORE_ACCESS_SIZE = {4, 8, 4, 8, 1, 2, 1, 2, 4};
+
+ /**
+ * Emit a bounds check: if addr + offset + accessSize > memPages * 65536,
+ * trap with OOB. Single compare + branch, predicted not-taken.
+ */
+ private static void emitBoundsCheck(EmitContext ctx, int addr, int offset, int accessSize) {
+ // Compute effective end address as i64 to avoid i32 overflow
+ int addr64 = ctx.bridge.exports().emitUextendI64(addr);
+ long endOffset = Integer.toUnsignedLong(offset) + accessSize;
+ int end =
+ ctx.bridge
+ .exports()
+ .emitIadd(
+ addr64,
+ ctx.bridge
+ .exports()
+ .emitIconst64(
+ (int) (endOffset & 0xFFFFFFFFL),
+ (int) (endOffset >>> 32)));
+
+ // Load memory size in bytes: memPages * 65536
+ int zero = ctx.bridge.exports().emitIconst32(0);
+ int memPages =
+ ctx.bridge
+ .exports()
+ .emitLoadI32(
+ ctx.bridge.exports().useVar(ctx.ctxPtrVar),
+ zero,
+ CtxBuffer.MEMORY_PAGES);
+ int memPages64 = ctx.bridge.exports().emitUextendI64(memPages);
+ int memSize =
+ ctx.bridge.exports().emitIshl(memPages64, ctx.bridge.exports().emitIconst64(16, 0));
+
+ // if end > memSize → trap
+ int oob = ctx.bridge.exports().emitIcmp(5, end, memSize); // GT unsigned
+ int trapBlock = ctx.bridge.exports().createBlock();
+ int okBlock = ctx.bridge.exports().createBlock();
+ ctx.bridge.exports().emitBrif(oob, trapBlock, okBlock);
+
+ fillTrapBlock(ctx, trapBlock, CtxBuffer.TRAP_OOB);
+
+ ctx.bridge.exports().switchToBlock(okBlock);
+ }
+
+ // --- Memory loads ---
+
+ static void emitLoad(EmitContext ctx, AnnotatedInstruction ins, int loadType) {
+ int addr = ctx.valueStack.pop();
+ int offset = (int) ins.operands()[1];
+ emitBoundsCheck(ctx, addr, offset, LOAD_ACCESS_SIZE[loadType]);
+ int memBase = ctx.bridge.exports().useVar(ctx.memBaseVar);
+ int result;
+ switch (loadType) {
+ case 0:
+ result = ctx.bridge.exports().emitLoadI32(memBase, addr, offset);
+ break;
+ case 1:
+ result = ctx.bridge.exports().emitLoadI64(memBase, addr, offset);
+ break;
+ case 2:
+ result = ctx.bridge.exports().emitLoadF32(memBase, addr, offset);
+ break;
+ case 3:
+ result = ctx.bridge.exports().emitLoadF64(memBase, addr, offset);
+ break;
+ case 4:
+ result = ctx.bridge.exports().emitLoad8u(memBase, addr, offset);
+ break;
+ case 5:
+ result = ctx.bridge.exports().emitLoad8s(memBase, addr, offset);
+ break;
+ case 6:
+ result = ctx.bridge.exports().emitLoad16u(memBase, addr, offset);
+ break;
+ case 7:
+ result = ctx.bridge.exports().emitLoad16s(memBase, addr, offset);
+ break;
+ case 8:
+ result = ctx.bridge.exports().emitLoad8uI64(memBase, addr, offset);
+ break;
+ case 9:
+ result = ctx.bridge.exports().emitLoad8sI64(memBase, addr, offset);
+ break;
+ case 10:
+ result = ctx.bridge.exports().emitLoad16uI64(memBase, addr, offset);
+ break;
+ case 11:
+ result = ctx.bridge.exports().emitLoad16sI64(memBase, addr, offset);
+ break;
+ case 12:
+ result = ctx.bridge.exports().emitLoad32uI64(memBase, addr, offset);
+ break;
+ case 13:
+ result = ctx.bridge.exports().emitLoad32sI64(memBase, addr, offset);
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown load type: " + loadType);
+ }
+ ctx.valueStack.push(result);
+ }
+
+ // --- Memory stores ---
+
+ static void emitStore(EmitContext ctx, AnnotatedInstruction ins, int storeType) {
+ int value = ctx.valueStack.pop();
+ int addr = ctx.valueStack.pop();
+ int offset = (int) ins.operands()[1];
+ emitBoundsCheck(ctx, addr, offset, STORE_ACCESS_SIZE[storeType]);
+ int memBase = ctx.bridge.exports().useVar(ctx.memBaseVar);
+ switch (storeType) {
+ case 0:
+ ctx.bridge.exports().emitStoreI32(memBase, addr, value, offset);
+ break;
+ case 1:
+ ctx.bridge.exports().emitStoreI64(memBase, addr, value, offset);
+ break;
+ case 2:
+ ctx.bridge.exports().emitStoreF32(memBase, addr, value, offset);
+ break;
+ case 3:
+ ctx.bridge.exports().emitStoreF64(memBase, addr, value, offset);
+ break;
+ case 4:
+ ctx.bridge.exports().emitStore8(memBase, addr, value, offset);
+ break;
+ case 5:
+ ctx.bridge.exports().emitStore16(memBase, addr, value, offset);
+ break;
+ case 6:
+ ctx.bridge.exports().emitStore8I64(memBase, addr, value, offset);
+ break;
+ case 7:
+ ctx.bridge.exports().emitStore16I64(memBase, addr, value, offset);
+ break;
+ case 8:
+ ctx.bridge.exports().emitStore32I64(memBase, addr, value, offset);
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown store type: " + storeType);
+ }
+ }
+
+ // --- Locals ---
+
+ static void emitLocalGet(EmitContext ctx, AnnotatedInstruction ins) {
+ ctx.valueStack.push(ctx.bridge.exports().useVar(ctx.localVars[(int) ins.operands()[0]]));
+ }
+
+ static void emitLocalSet(EmitContext ctx, AnnotatedInstruction ins) {
+ int val = ctx.valueStack.pop();
+ ctx.bridge.exports().defVar(ctx.localVars[(int) ins.operands()[0]], val);
+ }
+
+ static void emitLocalTee(EmitContext ctx, AnnotatedInstruction ins) {
+ int val = ctx.valueStack.peek();
+ ctx.bridge.exports().defVar(ctx.localVars[(int) ins.operands()[0]], val);
+ }
+
+ // --- Select ---
+
+ static void emitSelect(EmitContext ctx) {
+ int cond = ctx.valueStack.pop();
+ int val2 = ctx.valueStack.pop();
+ int val1 = ctx.valueStack.pop();
+ ctx.valueStack.push(ctx.bridge.exports().emitSelect(cond, val1, val2));
+ }
+
+ // --- Globals ---
+
+ static void emitGlobalGet(EmitContext ctx, AnnotatedInstruction ins) {
+ int globalIdx = (int) ins.operands()[0];
+ int zero = ctx.bridge.exports().emitIconst32(0);
+ int globalsPtr =
+ ctx.bridge
+ .exports()
+ .emitLoadI64(
+ ctx.bridge.exports().useVar(ctx.ctxPtrVar),
+ zero,
+ CtxBuffer.GLOBALS_PTR);
+ int offsetVal = ctx.bridge.exports().emitIconst32(globalIdx * 8);
+ int rawVal = ctx.bridge.exports().emitLoadI64(globalsPtr, offsetVal, 0);
+ ValType globalType = ctx.resolveGlobalType(globalIdx);
+ ctx.valueStack.push(ctx.narrowFromI64ForType(rawVal, globalType));
+ }
+
+ static void emitGlobalSet(EmitContext ctx, AnnotatedInstruction ins) {
+ int globalIdx = (int) ins.operands()[0];
+ int value = ctx.valueStack.pop();
+ ValType globalType = ctx.resolveGlobalType(globalIdx);
+ int widened = ctx.widenToI64ForType(value, globalType);
+ int zero = ctx.bridge.exports().emitIconst32(0);
+ int globalsPtr =
+ ctx.bridge
+ .exports()
+ .emitLoadI64(
+ ctx.bridge.exports().useVar(ctx.ctxPtrVar),
+ zero,
+ CtxBuffer.GLOBALS_PTR);
+ int offsetVal = ctx.bridge.exports().emitIconst32(globalIdx * 8);
+ ctx.bridge.exports().emitStoreI64(globalsPtr, offsetVal, widened, 0);
+ }
+
+ // --- Memory operations ---
+
+ static void emitMemorySize(EmitContext ctx) {
+ int zero = ctx.bridge.exports().emitIconst32(0);
+ int pages =
+ ctx.bridge
+ .exports()
+ .emitLoadI32(
+ ctx.bridge.exports().useVar(ctx.ctxPtrVar),
+ zero,
+ CtxBuffer.MEMORY_PAGES);
+ ctx.valueStack.push(pages);
+ }
+
+ static void emitMemoryGrow(EmitContext ctx) {
+ int delta = ctx.valueStack.pop();
+ int zero = ctx.bridge.exports().emitIconst32(0);
+ ctx.bridge
+ .exports()
+ .emitStoreI32(
+ ctx.bridge.exports().useVar(ctx.ctxPtrVar),
+ zero,
+ delta,
+ CtxBuffer.MEM_GROW_DELTA);
+ int memGrowPtr =
+ ctx.bridge
+ .exports()
+ .emitLoadI64(
+ ctx.bridge.exports().useVar(ctx.ctxPtrVar),
+ zero,
+ CtxBuffer.MEM_GROW_PTR);
+ int growSig = ctx.getOrCreateTrampolineSigRef();
+ ctx.bridge.exports().pushCallArg(ctx.bridge.exports().useVar(ctx.ctxPtrVar));
+ int rawResult = ctx.bridge.exports().emitCallIndirect(growSig, memGrowPtr);
+ int result = ctx.bridge.exports().emitIreduceI32(rawResult);
+ ctx.valueStack.push(result);
+ int newMemBase =
+ ctx.bridge
+ .exports()
+ .emitLoadI64(
+ ctx.bridge.exports().useVar(ctx.ctxPtrVar),
+ zero,
+ CtxBuffer.MEM_BASE_ADDR);
+ ctx.bridge.exports().defVar(ctx.memBaseVar, newMemBase);
+ }
+
+ // --- Unreachable ---
+
+ static void emitUnreachable(EmitContext ctx) {
+ int ctxVal = ctx.bridge.exports().useVar(ctx.ctxPtrVar);
+ int zero = ctx.bridge.exports().emitIconst32(0);
+ int code = ctx.bridge.exports().emitIconst32(CtxBuffer.TRAP_UNREACHABLE);
+ ctx.bridge.exports().emitStoreI32(ctxVal, zero, code, CtxBuffer.TRAP_CODE);
+ ctx.emitReturnForFuncType();
+ }
+
+ // --- Conversions ---
+
+ static void emitTruncSat(EmitContext ctx, int targetType, boolean signed) {
+ int val = ctx.valueStack.pop();
+ ctx.valueStack.push(
+ signed
+ ? ctx.bridge.exports().emitFcvtToSintSat(targetType, val)
+ : ctx.bridge.exports().emitFcvtToUintSat(targetType, val));
+ }
+
+ static void emitConvertFloat(EmitContext ctx, int targetType, boolean signed) {
+ int val = ctx.valueStack.pop();
+ ctx.valueStack.push(
+ signed
+ ? ctx.bridge.exports().emitFcvtFromSint(targetType, val)
+ : ctx.bridge.exports().emitFcvtFromUint(targetType, val));
+ }
+
+ static void emitFpromote(EmitContext ctx) {
+ ctx.valueStack.push(ctx.bridge.exports().emitFpromote(ctx.valueStack.pop()));
+ }
+
+ static void emitFdemote(EmitContext ctx) {
+ ctx.valueStack.push(ctx.bridge.exports().emitFdemote(ctx.valueStack.pop()));
+ }
+
+ static void emitBitcastI32ToF32(EmitContext ctx) {
+ ctx.valueStack.push(ctx.bridge.exports().emitBitcastI32ToF32(ctx.valueStack.pop()));
+ }
+
+ static void emitBitcastF32ToI32(EmitContext ctx) {
+ ctx.valueStack.push(ctx.bridge.exports().emitBitcastF32ToI32(ctx.valueStack.pop()));
+ }
+
+ static void emitBitcastI64ToF64(EmitContext ctx) {
+ ctx.valueStack.push(ctx.bridge.exports().emitBitcastI64ToF64(ctx.valueStack.pop()));
+ }
+
+ static void emitBitcastF64ToI64(EmitContext ctx) {
+ ctx.valueStack.push(ctx.bridge.exports().emitBitcastF64ToI64(ctx.valueStack.pop()));
+ }
+
+ // --- Calls ---
+
+ static void emitCall(EmitContext ctx, AnnotatedInstruction ins) {
+ int targetFuncId = (int) ins.operands()[0];
+ FunctionType targetType = ctx.resolveCallTargetType(targetFuncId);
+
+ boolean calleeMultiReturn = targetType.returns().size() > 1;
+ int sigRef =
+ calleeMultiReturn
+ ? ctx.getOrCreateMultiReturnSigRef(targetType)
+ : ctx.getOrCreateSigRef(targetType);
+
+ int argCount = targetType.params().size();
+ int[] argVals = new int[argCount];
+ for (int i = argCount - 1; i >= 0; i--) {
+ argVals[i] = ctx.valueStack.pop();
+ }
+
+ int zero = ctx.bridge.exports().emitIconst32(0);
+ ctx.bridge
+ .exports()
+ .emitStoreI32(
+ ctx.bridge.exports().useVar(ctx.ctxPtrVar),
+ zero,
+ ctx.bridge.exports().emitIconst32(argCount),
+ CtxBuffer.ARG_COUNT);
+ int argsPtr =
+ ctx.bridge
+ .exports()
+ .emitLoadI64(
+ ctx.bridge.exports().useVar(ctx.ctxPtrVar),
+ zero,
+ CtxBuffer.ARGS_PTR);
+ for (int i = 0; i < argCount; i++) {
+ int widened = ctx.widenToI64(argVals[i], targetType.params().get(i));
+ ctx.bridge.exports().emitStoreI64(argsPtr, zero, widened, CtxBuffer.argOffset(i));
+ }
+
+ int funcTablePtr =
+ ctx.bridge
+ .exports()
+ .emitLoadI64(
+ ctx.bridge.exports().useVar(ctx.ctxPtrVar),
+ zero,
+ CtxBuffer.FUNC_TABLE_PTR);
+ int funcIdOffset = ctx.bridge.exports().emitIconst32(targetFuncId * 8);
+ int funcPtr = ctx.bridge.exports().emitLoadI64(funcTablePtr, funcIdOffset, 0);
+
+ ctx.bridge.exports().pushCallArg(ctx.bridge.exports().useVar(ctx.memBaseVar));
+ ctx.bridge.exports().pushCallArg(ctx.bridge.exports().useVar(ctx.ctxPtrVar));
+ for (int i = 0; i < argCount; i++) {
+ ctx.bridge.exports().pushCallArg(argVals[i]);
+ }
+
+ int rawResult = ctx.bridge.exports().emitCallIndirect(sigRef, funcPtr);
+
+ if (calleeMultiReturn) {
+ // Read return values from argsBuffer
+ int zero2 = ctx.bridge.exports().emitIconst32(0);
+ int argsPtr2 =
+ ctx.bridge
+ .exports()
+ .emitLoadI64(
+ ctx.bridge.exports().useVar(ctx.ctxPtrVar),
+ zero2,
+ CtxBuffer.ARGS_PTR);
+ for (int i = 0; i < targetType.returns().size(); i++) {
+ int raw = ctx.bridge.exports().emitLoadI64(argsPtr2, zero2, CtxBuffer.argOffset(i));
+ ctx.valueStack.push(ctx.narrowFromI64ForType(raw, targetType.returns().get(i)));
+ }
+ } else if (!targetType.returns().isEmpty()) {
+ ctx.valueStack.push(rawResult);
+ }
+
+ // Reload memBase — callee may have called memory.grow
+ reloadMemBase(ctx);
+ }
+
+ static void emitCallIndirect(EmitContext ctx, AnnotatedInstruction ins) {
+ int typeId = (int) ins.operands()[0];
+ int tableIdx = (int) ins.operands()[1];
+ FunctionType targetType = (FunctionType) ctx.module.typeSection().getType(typeId);
+
+ // Pop table element index (i32)
+ int elemIdx = ctx.valueStack.pop();
+
+ // Pop call arguments
+ int argCount = targetType.params().size();
+ int[] argVals = new int[argCount];
+ for (int i = argCount - 1; i >= 0; i--) {
+ argVals[i] = ctx.valueStack.pop();
+ }
+
+ var b = ctx.bridge.exports();
+ int zero = b.emitIconst32(0);
+ int ctxPtr = b.useVar(ctx.ctxPtrVar);
+
+ // 1. Load tablePtr from TABLE_PTRS[tableIdx]
+ int tablePtrsPtr = b.emitLoadI64(ctxPtr, zero, CtxBuffer.TABLE_PTRS);
+ int tableOffset = b.emitIconst32(tableIdx * 8);
+ int tablePtr = b.emitLoadI64(tablePtrsPtr, tableOffset, 0);
+
+ // 2. Bounds check: elemIdx >= table.size → trap "undefined element"
+ int tableSize = b.emitLoadI32(tablePtr, zero, CtxBuffer.TABLE_SIZE_OFFSET);
+ int oobCheck = b.emitIcmp(9, b.emitUextendI64(elemIdx), b.emitUextendI64(tableSize));
+ int trapOobBlock = b.createBlock();
+ int afterOobBlock = b.createBlock();
+ b.emitBrif(oobCheck, trapOobBlock, afterOobBlock);
+ fillTrapBlock(ctx, trapOobBlock, CtxBuffer.TRAP_UNDEFINED_ELEMENT);
+ b.switchToBlock(afterOobBlock);
+
+ // 3. Calculate entry offset: elemIdx * 16 (TABLE_ENTRY_SIZE)
+ int entryOffset = b.emitImul(elemIdx, b.emitIconst32(CtxBuffer.TABLE_ENTRY_SIZE));
+
+ // 4. Load funcPtr from entry: tablePtr[ENTRIES_OFFSET + entryOffset + FUNC_PTR_OFFSET]
+ int funcPtr =
+ b.emitLoadI64(
+ tablePtr,
+ entryOffset,
+ CtxBuffer.TABLE_ENTRIES_OFFSET + CtxBuffer.ENTRY_FUNC_PTR_OFFSET);
+
+ // 5. Null check: funcPtr == 0 → trap
+ int zero64 = b.emitIconst64(0, 0);
+ int isNull = b.emitIcmp(0, funcPtr, zero64); // EQ
+ int trapNullBlock = b.createBlock();
+ int afterNullBlock = b.createBlock();
+ b.emitBrif(isNull, trapNullBlock, afterNullBlock);
+ fillTrapBlock(ctx, trapNullBlock, CtxBuffer.TRAP_UNINITIALIZED_ELEMENT);
+ b.switchToBlock(afterNullBlock);
+
+ // 6. Type check: entry.typeIdx == expectedCanonicalType
+ int actualType =
+ b.emitLoadI32(
+ tablePtr,
+ entryOffset,
+ CtxBuffer.TABLE_ENTRIES_OFFSET + CtxBuffer.ENTRY_TYPE_IDX_OFFSET);
+ int canonicalTypeId = ctx.canonicalTypeMap[typeId];
+ int expectedType = b.emitIconst32(canonicalTypeId);
+ int typeMismatch =
+ b.emitIcmp(1, b.emitUextendI64(actualType), b.emitUextendI64(expectedType)); // NE
+ int trapTypeBlock = b.createBlock();
+ int afterTypeBlock = b.createBlock();
+ b.emitBrif(typeMismatch, trapTypeBlock, afterTypeBlock);
+ fillTrapBlock(ctx, trapTypeBlock, CtxBuffer.TRAP_INDIRECT_CALL_TYPE_MISMATCH);
+ b.switchToBlock(afterTypeBlock);
+
+ // 7. Determine if callee might be multi-return (>1 return)
+ boolean calleeMultiReturn = targetType.returns().size() > 1;
+
+ // Write args to argsBuffer (for import stubs that read from it)
+ int argsPtr = b.emitLoadI64(b.useVar(ctx.ctxPtrVar), zero, CtxBuffer.ARGS_PTR);
+ int zero2 = b.emitIconst32(0);
+ b.emitStoreI32(
+ b.useVar(ctx.ctxPtrVar), zero2, b.emitIconst32(argCount), CtxBuffer.ARG_COUNT);
+ for (int i = 0; i < argCount; i++) {
+ int widened = ctx.widenToI64(argVals[i], targetType.params().get(i));
+ b.emitStoreI64(argsPtr, zero2, widened, CtxBuffer.argOffset(i));
+ }
+
+ // 8. Build SigRef and call (funcPtr loaded directly from table entry)
+ int sigRef;
+ if (calleeMultiReturn) {
+ sigRef = ctx.getOrCreateMultiReturnSigRef(targetType);
+ } else {
+ sigRef = ctx.getOrCreateSigRef(targetType);
+ }
+
+ b.pushCallArg(b.useVar(ctx.memBaseVar));
+ b.pushCallArg(b.useVar(ctx.ctxPtrVar));
+ for (int i = 0; i < argCount; i++) {
+ b.pushCallArg(argVals[i]);
+ }
+
+ int rawResult = b.emitCallIndirect(sigRef, funcPtr);
+
+ // 9. Handle results
+ if (calleeMultiReturn) {
+ int zero3 = b.emitIconst32(0);
+ int argsPtr2 = b.emitLoadI64(b.useVar(ctx.ctxPtrVar), zero3, CtxBuffer.ARGS_PTR);
+ for (int i = 0; i < targetType.returns().size(); i++) {
+ int raw = b.emitLoadI64(argsPtr2, zero3, CtxBuffer.argOffset(i));
+ ctx.valueStack.push(ctx.narrowFromI64ForType(raw, targetType.returns().get(i)));
+ }
+ } else if (!targetType.returns().isEmpty()) {
+ ctx.valueStack.push(rawResult);
+ }
+
+ // Reload memBase — callee may have called memory.grow
+ reloadMemBase(ctx);
+ }
+
+ // --- Tail calls (return_call / return_call_indirect) ---
+
+ static void emitReturnCall(EmitContext ctx, AnnotatedInstruction ins) {
+ int targetFuncId = (int) ins.operands()[0];
+ FunctionType targetType = ctx.resolveCallTargetType(targetFuncId);
+
+ boolean calleeMultiReturn = targetType.returns().size() > 1;
+ int sigRef =
+ calleeMultiReturn
+ ? ctx.getOrCreateMultiReturnSigRef(targetType)
+ : ctx.getOrCreateSigRef(targetType);
+
+ int argCount = targetType.params().size();
+ int[] argVals = new int[argCount];
+ for (int i = argCount - 1; i >= 0; i--) {
+ argVals[i] = ctx.valueStack.pop();
+ }
+
+ int zero = ctx.bridge.exports().emitIconst32(0);
+ ctx.bridge
+ .exports()
+ .emitStoreI32(
+ ctx.bridge.exports().useVar(ctx.ctxPtrVar),
+ zero,
+ ctx.bridge.exports().emitIconst32(argCount),
+ CtxBuffer.ARG_COUNT);
+ int argsPtr =
+ ctx.bridge
+ .exports()
+ .emitLoadI64(
+ ctx.bridge.exports().useVar(ctx.ctxPtrVar),
+ zero,
+ CtxBuffer.ARGS_PTR);
+ for (int i = 0; i < argCount; i++) {
+ int widened = ctx.widenToI64(argVals[i], targetType.params().get(i));
+ ctx.bridge.exports().emitStoreI64(argsPtr, zero, widened, CtxBuffer.argOffset(i));
+ }
+
+ int funcTablePtr =
+ ctx.bridge
+ .exports()
+ .emitLoadI64(
+ ctx.bridge.exports().useVar(ctx.ctxPtrVar),
+ zero,
+ CtxBuffer.FUNC_TABLE_PTR);
+ int funcIdOffset = ctx.bridge.exports().emitIconst32(targetFuncId * 8);
+ int funcPtr = ctx.bridge.exports().emitLoadI64(funcTablePtr, funcIdOffset, 0);
+
+ ctx.bridge.exports().pushCallArg(ctx.bridge.exports().useVar(ctx.memBaseVar));
+ ctx.bridge.exports().pushCallArg(ctx.bridge.exports().useVar(ctx.ctxPtrVar));
+ for (int i = 0; i < argCount; i++) {
+ ctx.bridge.exports().pushCallArg(argVals[i]);
+ }
+
+ ctx.bridge.exports().emitReturnCallIndirect(sigRef, funcPtr);
+ }
+
+ static void emitReturnCallIndirect(EmitContext ctx, AnnotatedInstruction ins) {
+ int typeId = (int) ins.operands()[0];
+ int tableIdx = (int) ins.operands()[1];
+ FunctionType targetType = (FunctionType) ctx.module.typeSection().getType(typeId);
+
+ int elemIdx = ctx.valueStack.pop();
+
+ int argCount = targetType.params().size();
+ int[] argVals = new int[argCount];
+ for (int i = argCount - 1; i >= 0; i--) {
+ argVals[i] = ctx.valueStack.pop();
+ }
+
+ var b = ctx.bridge.exports();
+ int zero = b.emitIconst32(0);
+ int ctxPtr = b.useVar(ctx.ctxPtrVar);
+
+ int tablePtrsPtr = b.emitLoadI64(ctxPtr, zero, CtxBuffer.TABLE_PTRS);
+ int tableOffset = b.emitIconst32(tableIdx * 8);
+ int tablePtr = b.emitLoadI64(tablePtrsPtr, tableOffset, 0);
+
+ int tableSize = b.emitLoadI32(tablePtr, zero, CtxBuffer.TABLE_SIZE_OFFSET);
+ int oobCheck = b.emitIcmp(9, b.emitUextendI64(elemIdx), b.emitUextendI64(tableSize));
+ int trapOobBlock = b.createBlock();
+ int afterOobBlock = b.createBlock();
+ b.emitBrif(oobCheck, trapOobBlock, afterOobBlock);
+ fillTrapBlock(ctx, trapOobBlock, CtxBuffer.TRAP_UNDEFINED_ELEMENT);
+ b.switchToBlock(afterOobBlock);
+
+ int entryOffset = b.emitImul(elemIdx, b.emitIconst32(CtxBuffer.TABLE_ENTRY_SIZE));
+
+ int funcPtr =
+ b.emitLoadI64(
+ tablePtr,
+ entryOffset,
+ CtxBuffer.TABLE_ENTRIES_OFFSET + CtxBuffer.ENTRY_FUNC_PTR_OFFSET);
+
+ int zero64 = b.emitIconst64(0, 0);
+ int isNull = b.emitIcmp(0, funcPtr, zero64);
+ int trapNullBlock = b.createBlock();
+ int afterNullBlock = b.createBlock();
+ b.emitBrif(isNull, trapNullBlock, afterNullBlock);
+ fillTrapBlock(ctx, trapNullBlock, CtxBuffer.TRAP_UNINITIALIZED_ELEMENT);
+ b.switchToBlock(afterNullBlock);
+
+ int actualType =
+ b.emitLoadI32(
+ tablePtr,
+ entryOffset,
+ CtxBuffer.TABLE_ENTRIES_OFFSET + CtxBuffer.ENTRY_TYPE_IDX_OFFSET);
+ int canonicalTypeId = ctx.canonicalTypeMap[typeId];
+ int expectedType = b.emitIconst32(canonicalTypeId);
+ int typeMismatch =
+ b.emitIcmp(1, b.emitUextendI64(actualType), b.emitUextendI64(expectedType));
+ int trapTypeBlock = b.createBlock();
+ int afterTypeBlock = b.createBlock();
+ b.emitBrif(typeMismatch, trapTypeBlock, afterTypeBlock);
+ fillTrapBlock(ctx, trapTypeBlock, CtxBuffer.TRAP_INDIRECT_CALL_TYPE_MISMATCH);
+ b.switchToBlock(afterTypeBlock);
+
+ boolean calleeMultiReturn = targetType.returns().size() > 1;
+
+ int argsPtr = b.emitLoadI64(b.useVar(ctx.ctxPtrVar), zero, CtxBuffer.ARGS_PTR);
+ int zero2 = b.emitIconst32(0);
+ b.emitStoreI32(
+ b.useVar(ctx.ctxPtrVar), zero2, b.emitIconst32(argCount), CtxBuffer.ARG_COUNT);
+ for (int i = 0; i < argCount; i++) {
+ int widened = ctx.widenToI64(argVals[i], targetType.params().get(i));
+ b.emitStoreI64(argsPtr, zero2, widened, CtxBuffer.argOffset(i));
+ }
+
+ int sigRef;
+ if (calleeMultiReturn) {
+ sigRef = ctx.getOrCreateMultiReturnSigRef(targetType);
+ } else {
+ sigRef = ctx.getOrCreateSigRef(targetType);
+ }
+
+ b.pushCallArg(b.useVar(ctx.memBaseVar));
+ b.pushCallArg(b.useVar(ctx.ctxPtrVar));
+ for (int i = 0; i < argCount; i++) {
+ b.pushCallArg(argVals[i]);
+ }
+
+ b.emitReturnCallIndirect(sigRef, funcPtr);
+ }
+
+ // --- Table operations (fully native, no trampoline) ---
+
+ private static int loadTablePtr(EmitContext ctx, int tableIdx) {
+ var b = ctx.bridge.exports();
+ int zero = b.emitIconst32(0);
+ int tablePtrsPtr = b.emitLoadI64(b.useVar(ctx.ctxPtrVar), zero, CtxBuffer.TABLE_PTRS);
+ int offset = b.emitIconst32(tableIdx * 8);
+ return b.emitLoadI64(tablePtrsPtr, offset, 0);
+ }
+
+ private static boolean isExternRefTable(EmitContext ctx, int tableIdx) {
+ int importTableCount = 0;
+ for (var imp :
+ ctx.module.importSection().stream().collect(java.util.stream.Collectors.toList())) {
+ if (imp.importType() == ExternalType.TABLE) {
+ if (importTableCount == tableIdx) {
+ return ((TableImport) imp).entryType().equals(ValType.ExternRef);
+ }
+ importTableCount++;
+ }
+ }
+ int definedIdx = tableIdx - importTableCount;
+ return ctx.module
+ .tableSection()
+ .getTable(definedIdx)
+ .elementType()
+ .equals(ValType.ExternRef);
+ }
+
+ static void emitTableGet(EmitContext ctx, AnnotatedInstruction ins) {
+ int tableIdx = (int) ins.operands()[0];
+ int index = ctx.valueStack.pop();
+ var b = ctx.bridge.exports();
+ int zero = b.emitIconst32(0);
+
+ int tablePtr = loadTablePtr(ctx, tableIdx);
+ int tableSize = b.emitLoadI32(tablePtr, zero, CtxBuffer.TABLE_SIZE_OFFSET);
+
+ // Bounds check: index >= size → trap
+ int oob = b.emitIcmp(9, b.emitUextendI64(index), b.emitUextendI64(tableSize));
+ int trapBlock = b.createBlock();
+ int okBlock = b.createBlock();
+ b.emitBrif(oob, trapBlock, okBlock);
+ fillTrapBlock(ctx, trapBlock, CtxBuffer.TRAP_TABLE_OOB);
+ b.switchToBlock(okBlock);
+
+ // Load funcId from 16-byte entry: entry.funcId at offset +4
+ int entryOffset = b.emitImul(index, b.emitIconst32(CtxBuffer.TABLE_ENTRY_SIZE));
+ int ref =
+ b.emitLoadI32(
+ tablePtr,
+ entryOffset,
+ CtxBuffer.TABLE_ENTRIES_OFFSET + CtxBuffer.ENTRY_FUNC_ID_OFFSET);
+ // Sign-extend so REF_NULL_VALUE (-1 as i32) stays -1 as i64.
+ ctx.valueStack.push(b.emitSextendI64(ref));
+ }
+
+ static void emitTableSet(EmitContext ctx, AnnotatedInstruction ins) {
+ int tableIdx = (int) ins.operands()[0];
+ // Value is i64 on stack (ref type), narrow to i32 for table storage
+ int funcId = ctx.bridge.exports().emitIreduceI32(ctx.valueStack.pop());
+ int index = ctx.valueStack.pop();
+ var b = ctx.bridge.exports();
+ int zero = b.emitIconst32(0);
+
+ int tablePtr = loadTablePtr(ctx, tableIdx);
+ int tableSize = b.emitLoadI32(tablePtr, zero, CtxBuffer.TABLE_SIZE_OFFSET);
+
+ // Bounds check
+ int oob = b.emitIcmp(9, b.emitUextendI64(index), b.emitUextendI64(tableSize));
+ int trapBlock = b.createBlock();
+ int okBlock = b.createBlock();
+ b.emitBrif(oob, trapBlock, okBlock);
+ fillTrapBlock(ctx, trapBlock, CtxBuffer.TRAP_TABLE_OOB);
+ b.switchToBlock(okBlock);
+
+ // Calculate entry offset for 16-byte entries
+ int entryOffset = b.emitImul(index, b.emitIconst32(CtxBuffer.TABLE_ENTRY_SIZE));
+ int entryTypeOff = CtxBuffer.TABLE_ENTRIES_OFFSET + CtxBuffer.ENTRY_TYPE_IDX_OFFSET;
+ int entryFuncIdOff = CtxBuffer.TABLE_ENTRIES_OFFSET + CtxBuffer.ENTRY_FUNC_ID_OFFSET;
+ int entryFuncPtrOff = CtxBuffer.TABLE_ENTRIES_OFFSET + CtxBuffer.ENTRY_FUNC_PTR_OFFSET;
+
+ // Check if value is REF_NULL (-1)
+ int refNull = b.emitIconst32(-1);
+ int isNull = b.emitIcmp(0, b.emitUextendI64(funcId), b.emitUextendI64(refNull)); // EQ
+
+ int nullBlock = b.createBlock();
+ int nonNullBlock = b.createBlock();
+ int mergeBlock = b.createBlock();
+ b.emitBrif(isNull, nullBlock, nonNullBlock);
+
+ // Null path: write null entry
+ b.switchToBlock(nullBlock);
+ b.emitStoreI32(tablePtr, entryOffset, b.emitIconst32(0), entryTypeOff);
+ b.emitStoreI32(tablePtr, entryOffset, refNull, entryFuncIdOff);
+ b.emitStoreI64(tablePtr, entryOffset, b.emitIconst64(0, 0), entryFuncPtrOff);
+ b.emitJump(mergeBlock);
+
+ // Non-null path
+ b.switchToBlock(nonNullBlock);
+ if (isExternRefTable(ctx, tableIdx)) {
+ // ExternRef: store raw value directly, no function lookup
+ b.emitStoreI32(tablePtr, entryOffset, b.emitIconst32(0), entryTypeOff);
+ b.emitStoreI32(tablePtr, entryOffset, funcId, entryFuncIdOff);
+ b.emitStoreI64(tablePtr, entryOffset, b.emitIconst64(0, 0), entryFuncPtrOff);
+ } else {
+ // FuncRef: resolve funcId → funcPtr+typeIdx
+ int funcTablePtr =
+ b.emitLoadI64(b.useVar(ctx.ctxPtrVar), zero, CtxBuffer.FUNC_TABLE_PTR);
+ int funcPtrOffset = b.emitImul(funcId, b.emitIconst32(8));
+ int funcPtr = b.emitLoadI64(funcTablePtr, funcPtrOffset, 0);
+ int funcTypesPtr =
+ b.emitLoadI64(b.useVar(ctx.ctxPtrVar), zero, CtxBuffer.FUNC_TYPES_PTR);
+ int typeIdxOffset = b.emitImul(funcId, b.emitIconst32(4));
+ int typeIdx = b.emitLoadI32(funcTypesPtr, typeIdxOffset, 0);
+ b.emitStoreI32(tablePtr, entryOffset, typeIdx, entryTypeOff);
+ b.emitStoreI32(tablePtr, entryOffset, funcId, entryFuncIdOff);
+ b.emitStoreI64(tablePtr, entryOffset, funcPtr, entryFuncPtrOff);
+ }
+ b.emitJump(mergeBlock);
+
+ b.switchToBlock(mergeBlock);
+ }
+
+ static void emitTableSize(EmitContext ctx, AnnotatedInstruction ins) {
+ int tableIdx = (int) ins.operands()[0];
+ var b = ctx.bridge.exports();
+ int zero = b.emitIconst32(0);
+ int tablePtr = loadTablePtr(ctx, tableIdx);
+ int size = b.emitLoadI32(tablePtr, zero, CtxBuffer.TABLE_SIZE_OFFSET);
+ ctx.valueStack.push(size);
+ }
+
+ static void emitTableGrow(EmitContext ctx, AnnotatedInstruction ins) {
+ int tableIdx = (int) ins.operands()[0];
+ int delta = ctx.valueStack.pop();
+ // Fill value is i64 (ref type on stack), narrow to i32 for table storage
+ int fillValue = ctx.bridge.exports().emitIreduceI32(ctx.valueStack.pop());
+ var b = ctx.bridge.exports();
+ int zero = b.emitIconst32(0);
+
+ int tablePtr = loadTablePtr(ctx, tableIdx);
+ int oldSize = b.emitLoadI32(tablePtr, zero, CtxBuffer.TABLE_SIZE_OFFSET);
+ int maxSize = b.emitLoadI32(tablePtr, zero, CtxBuffer.TABLE_MAX_OFFSET);
+
+ // newSize = oldSize + delta
+ int newSize = b.emitIadd(b.emitUextendI64(oldSize), b.emitUextendI64(delta));
+
+ // if newSize > maxSize → return -1
+ int overMax = b.emitIcmp(5, newSize, b.emitUextendI64(maxSize)); // UGT
+ // if delta < 0 (unsigned: very large) → also fail
+ // Combine: newSize > max OR newSize < oldSize (overflow)
+ int overflow = b.emitIcmp(3, newSize, b.emitUextendI64(oldSize)); // ULT = overflow
+ int fail = b.emitBor(overMax, overflow);
+
+ int failBlock = b.createBlock();
+ int okBlock = b.createBlock();
+ int mergeBlock = b.createBlock();
+ int mergeParam = b.appendBlockParam(mergeBlock, 0 /* TYPE_I32 */);
+
+ b.emitBrif(fail, failBlock, okBlock);
+
+ // Fail block: push -1
+ b.switchToBlock(failBlock);
+ b.emitJumpWithArg(mergeBlock, b.emitIconst32(-1));
+
+ // OK block: update size, fill new slots via trampoline, return oldSize
+ b.switchToBlock(okBlock);
+ int newSizeI32 = b.emitIreduceI32(newSize);
+ b.emitStoreI32(tablePtr, zero, newSizeI32, CtxBuffer.TABLE_SIZE_OFFSET);
+
+ // Fill new slots via trampoline (runtime loop)
+ int argsPtr = b.emitLoadI64(b.useVar(ctx.ctxPtrVar), zero, CtxBuffer.ARGS_PTR);
+ b.emitStoreI64(argsPtr, zero, b.emitUextendI64(oldSize), CtxBuffer.argOffset(0));
+ b.emitStoreI64(argsPtr, zero, b.emitUextendI64(newSizeI32), CtxBuffer.argOffset(1));
+ b.emitStoreI64(argsPtr, zero, b.emitUextendI64(fillValue), CtxBuffer.argOffset(2));
+ b.emitStoreI64(argsPtr, zero, tablePtr, CtxBuffer.argOffset(3)); // already i64
+ b.emitStoreI64(
+ argsPtr, zero, b.emitUextendI64(b.emitIconst32(tableIdx)), CtxBuffer.argOffset(4));
+
+ int tableOpsPtr = b.emitLoadI64(b.useVar(ctx.ctxPtrVar), zero, CtxBuffer.TRAMPOLINE_PTR);
+ b.emitStoreI32(
+ b.useVar(ctx.ctxPtrVar),
+ zero,
+ b.emitIconst32(-1), // sentinel for table grow fill
+ CtxBuffer.ARG_COUNT);
+ int trampolineSig = ctx.getOrCreateTrampolineSigRef();
+ b.pushCallArg(b.useVar(ctx.ctxPtrVar));
+ b.emitCallIndirect(trampolineSig, tableOpsPtr);
+
+ b.emitJumpWithArg(mergeBlock, oldSize);
+
+ b.switchToBlock(mergeBlock);
+ ctx.valueStack.push(mergeParam);
+ }
+
+ static void emitTableFill(EmitContext ctx, AnnotatedInstruction ins) {
+ int tableIdx = (int) ins.operands()[0];
+ int size = ctx.valueStack.pop();
+ // Fill value is i64 (ref type on stack), narrow to i32 for table storage
+ int fillValue = ctx.bridge.exports().emitIreduceI32(ctx.valueStack.pop());
+ int offset = ctx.valueStack.pop();
+ var b = ctx.bridge.exports();
+ int zero = b.emitIconst32(0);
+
+ int tablePtr = loadTablePtr(ctx, tableIdx);
+ int tableSize = b.emitLoadI32(tablePtr, zero, CtxBuffer.TABLE_SIZE_OFFSET);
+
+ // Bounds check: offset + size > tableSize → trap
+ int end = b.emitIadd(b.emitUextendI64(offset), b.emitUextendI64(size));
+ int oob = b.emitIcmp(5, end, b.emitUextendI64(tableSize)); // UGT
+ int trapBlock = b.createBlock();
+ int okBlock = b.createBlock();
+ b.emitBrif(oob, trapBlock, okBlock);
+ fillTrapBlock(ctx, trapBlock, CtxBuffer.TRAP_TABLE_OOB);
+ b.switchToBlock(okBlock);
+
+ // Emit fill via trampoline (runtime loop)
+ int argsPtr = b.emitLoadI64(b.useVar(ctx.ctxPtrVar), zero, CtxBuffer.ARGS_PTR);
+ b.emitStoreI64(argsPtr, zero, b.emitUextendI64(offset), CtxBuffer.argOffset(0));
+ int endI32 = b.emitIreduceI32(end);
+ b.emitStoreI64(argsPtr, zero, b.emitUextendI64(endI32), CtxBuffer.argOffset(1));
+ b.emitStoreI64(argsPtr, zero, b.emitUextendI64(fillValue), CtxBuffer.argOffset(2));
+ b.emitStoreI64(argsPtr, zero, tablePtr, CtxBuffer.argOffset(3)); // already i64
+ b.emitStoreI64(
+ argsPtr, zero, b.emitUextendI64(b.emitIconst32(tableIdx)), CtxBuffer.argOffset(4));
+
+ b.emitStoreI32(
+ b.useVar(ctx.ctxPtrVar),
+ zero,
+ b.emitIconst32(-2), // sentinel for table fill
+ CtxBuffer.ARG_COUNT);
+ int trampolinePtr = b.emitLoadI64(b.useVar(ctx.ctxPtrVar), zero, CtxBuffer.TRAMPOLINE_PTR);
+ int trampolineSig = ctx.getOrCreateTrampolineSigRef();
+ b.pushCallArg(b.useVar(ctx.ctxPtrVar));
+ b.emitCallIndirect(trampolineSig, trampolinePtr);
+ }
+
+ static void emitTableCopy(EmitContext ctx, AnnotatedInstruction ins) {
+ int dstTableIdx = (int) ins.operands()[0];
+ int srcTableIdx = (int) ins.operands()[1];
+ int size = ctx.valueStack.pop();
+ int srcOffset = ctx.valueStack.pop();
+ int dstOffset = ctx.valueStack.pop();
+ var b = ctx.bridge.exports();
+ int zero = b.emitIconst32(0);
+
+ int srcTablePtr = loadTablePtr(ctx, srcTableIdx);
+ int dstTablePtr = loadTablePtr(ctx, dstTableIdx);
+ int srcTableSize = b.emitLoadI32(srcTablePtr, zero, CtxBuffer.TABLE_SIZE_OFFSET);
+ int dstTableSize = b.emitLoadI32(dstTablePtr, zero, CtxBuffer.TABLE_SIZE_OFFSET);
+
+ // Bounds checks
+ int srcEnd = b.emitIadd(b.emitUextendI64(srcOffset), b.emitUextendI64(size));
+ int dstEnd = b.emitIadd(b.emitUextendI64(dstOffset), b.emitUextendI64(size));
+ int srcOob = b.emitIcmp(5, srcEnd, b.emitUextendI64(srcTableSize));
+ int dstOob = b.emitIcmp(5, dstEnd, b.emitUextendI64(dstTableSize));
+ int oob = b.emitBor(srcOob, dstOob);
+
+ int trapBlock = b.createBlock();
+ int okBlock = b.createBlock();
+ b.emitBrif(oob, trapBlock, okBlock);
+ fillTrapBlock(ctx, trapBlock, CtxBuffer.TRAP_TABLE_OOB);
+ b.switchToBlock(okBlock);
+
+ // Copy via trampoline (handles overlapping correctly)
+ int argsPtr = b.emitLoadI64(b.useVar(ctx.ctxPtrVar), zero, CtxBuffer.ARGS_PTR);
+ b.emitStoreI64(argsPtr, zero, srcTablePtr, CtxBuffer.argOffset(0)); // already i64
+ b.emitStoreI64(argsPtr, zero, dstTablePtr, CtxBuffer.argOffset(1)); // already i64
+ b.emitStoreI64(argsPtr, zero, b.emitUextendI64(srcOffset), CtxBuffer.argOffset(2));
+ b.emitStoreI64(argsPtr, zero, b.emitUextendI64(dstOffset), CtxBuffer.argOffset(3));
+ b.emitStoreI64(argsPtr, zero, b.emitUextendI64(size), CtxBuffer.argOffset(4));
+
+ b.emitStoreI32(
+ b.useVar(ctx.ctxPtrVar),
+ zero,
+ b.emitIconst32(-3), // sentinel for table copy
+ CtxBuffer.ARG_COUNT);
+ int trampolinePtr = b.emitLoadI64(b.useVar(ctx.ctxPtrVar), zero, CtxBuffer.TRAMPOLINE_PTR);
+ int trampolineSig = ctx.getOrCreateTrampolineSigRef();
+ b.pushCallArg(b.useVar(ctx.ctxPtrVar));
+ b.emitCallIndirect(trampolineSig, trampolinePtr);
+ }
+
+ static void emitTableInit(EmitContext ctx, AnnotatedInstruction ins) {
+ int elemIdx = (int) ins.operands()[0];
+ int tableIdx = (int) ins.operands()[1];
+ int size = ctx.valueStack.pop();
+ int srcOffset = ctx.valueStack.pop();
+ int dstOffset = ctx.valueStack.pop();
+ var b = ctx.bridge.exports();
+ int zero = b.emitIconst32(0);
+
+ // TABLE.INIT always goes through trampoline (needs elem segment data)
+ int argsPtr = b.emitLoadI64(b.useVar(ctx.ctxPtrVar), zero, CtxBuffer.ARGS_PTR);
+ b.emitStoreI64(
+ argsPtr, zero, b.emitUextendI64(b.emitIconst32(tableIdx)), CtxBuffer.argOffset(0));
+ b.emitStoreI64(
+ argsPtr, zero, b.emitUextendI64(b.emitIconst32(elemIdx)), CtxBuffer.argOffset(1));
+ b.emitStoreI64(argsPtr, zero, b.emitUextendI64(dstOffset), CtxBuffer.argOffset(2));
+ b.emitStoreI64(argsPtr, zero, b.emitUextendI64(srcOffset), CtxBuffer.argOffset(3));
+ b.emitStoreI64(argsPtr, zero, b.emitUextendI64(size), CtxBuffer.argOffset(4));
+
+ b.emitStoreI32(
+ b.useVar(ctx.ctxPtrVar),
+ zero,
+ b.emitIconst32(-4), // sentinel for table init
+ CtxBuffer.ARG_COUNT);
+ int trampolinePtr = b.emitLoadI64(b.useVar(ctx.ctxPtrVar), zero, CtxBuffer.TRAMPOLINE_PTR);
+ int trampolineSig = ctx.getOrCreateTrampolineSigRef();
+ b.pushCallArg(b.useVar(ctx.ctxPtrVar));
+ b.emitCallIndirect(trampolineSig, trampolinePtr);
+ }
+
+ static void emitElemDrop(EmitContext ctx, AnnotatedInstruction ins) {
+ int elemIdx = (int) ins.operands()[0];
+ var b = ctx.bridge.exports();
+ int zero = b.emitIconst32(0);
+
+ // ELEM.DROP always goes through trampoline
+ int argsPtr = b.emitLoadI64(b.useVar(ctx.ctxPtrVar), zero, CtxBuffer.ARGS_PTR);
+ b.emitStoreI64(
+ argsPtr, zero, b.emitUextendI64(b.emitIconst32(elemIdx)), CtxBuffer.argOffset(0));
+
+ b.emitStoreI32(
+ b.useVar(ctx.ctxPtrVar),
+ zero,
+ b.emitIconst32(-5), // sentinel for elem drop
+ CtxBuffer.ARG_COUNT);
+ int trampolinePtr = b.emitLoadI64(b.useVar(ctx.ctxPtrVar), zero, CtxBuffer.TRAMPOLINE_PTR);
+ int trampolineSig = ctx.getOrCreateTrampolineSigRef();
+ b.pushCallArg(b.useVar(ctx.ctxPtrVar));
+ b.emitCallIndirect(trampolineSig, trampolinePtr);
+ }
+
+ // --- Bulk memory operations (inline native memmove/memset) ---
+
+ /**
+ * Emit OOB check for bulk memory ops: if addr + size > memPages * 65536, trap.
+ * Both addr and size are i32 runtime values.
+ */
+ private static void emitBulkBoundsCheck(EmitContext ctx, int addr, int size) {
+ var b = ctx.bridge.exports();
+ int addr64 = b.emitUextendI64(addr);
+ int size64 = b.emitUextendI64(size);
+ int end = b.emitIadd(addr64, size64);
+
+ int zero = b.emitIconst32(0);
+ int memPages = b.emitLoadI32(b.useVar(ctx.ctxPtrVar), zero, CtxBuffer.MEMORY_PAGES);
+ int memPages64 = b.emitUextendI64(memPages);
+ int memSize = b.emitIshl(memPages64, b.emitIconst64(16, 0));
+
+ int oob = b.emitIcmp(5, end, memSize); // GT unsigned
+ int trapBlock = b.createBlock();
+ int okBlock = b.createBlock();
+ b.emitBrif(oob, trapBlock, okBlock);
+
+ fillTrapBlock(ctx, trapBlock, CtxBuffer.TRAP_OOB);
+ b.switchToBlock(okBlock);
+ }
+
+ static void emitMemoryCopy(EmitContext ctx) {
+ int size = ctx.valueStack.pop();
+ int srcOffset = ctx.valueStack.pop();
+ int dstOffset = ctx.valueStack.pop();
+ var b = ctx.bridge.exports();
+
+ // OOB checks for both src and dst ranges
+ emitBulkBoundsCheck(ctx, srcOffset, size);
+ emitBulkBoundsCheck(ctx, dstOffset, size);
+
+ // Compute native addresses: memBase + offset
+ int memBase = b.useVar(ctx.memBaseVar);
+ int effectiveDst = b.emitIadd(memBase, b.emitUextendI64(dstOffset));
+ int effectiveSrc = b.emitIadd(memBase, b.emitUextendI64(srcOffset));
+ int size64 = b.emitUextendI64(size);
+
+ // Call memmove(dst, src, size) via function pointer in ctxBuffer
+ int zero = b.emitIconst32(0);
+ int memmovePtr = b.emitLoadI64(b.useVar(ctx.ctxPtrVar), zero, CtxBuffer.MEMMOVE_PTR);
+ int sigRef = ctx.getOrCreateMemopSigRef();
+ b.pushCallArg(effectiveDst);
+ b.pushCallArg(effectiveSrc);
+ b.pushCallArg(size64);
+ b.emitCallIndirect(sigRef, memmovePtr);
+ }
+
+ static void emitMemoryFill(EmitContext ctx) {
+ int size = ctx.valueStack.pop();
+ int value = ctx.valueStack.pop();
+ int dstOffset = ctx.valueStack.pop();
+ var b = ctx.bridge.exports();
+
+ // OOB check for dst range
+ emitBulkBoundsCheck(ctx, dstOffset, size);
+
+ // Compute native address: memBase + dstOffset
+ int memBase = b.useVar(ctx.memBaseVar);
+ int effectiveDst = b.emitIadd(memBase, b.emitUextendI64(dstOffset));
+ int value64 = b.emitUextendI64(value);
+ int size64 = b.emitUextendI64(size);
+
+ // Call memset(dst, value, size) via function pointer in ctxBuffer
+ int zero = b.emitIconst32(0);
+ int memsetPtr = b.emitLoadI64(b.useVar(ctx.ctxPtrVar), zero, CtxBuffer.MEMSET_PTR);
+ int sigRef = ctx.getOrCreateMemopSigRef();
+ b.pushCallArg(effectiveDst);
+ b.pushCallArg(value64);
+ b.pushCallArg(size64);
+ b.emitCallIndirect(sigRef, memsetPtr);
+ }
+
+ static void emitMemoryInit(EmitContext ctx, AnnotatedInstruction ins) {
+ int segmentId = (int) ins.operands()[0];
+ int size = ctx.valueStack.pop();
+ int srcOffset = ctx.valueStack.pop();
+ int dstOffset = ctx.valueStack.pop();
+ var b = ctx.bridge.exports();
+ int zero = b.emitIconst32(0);
+
+ int argsPtr = b.emitLoadI64(b.useVar(ctx.ctxPtrVar), zero, CtxBuffer.ARGS_PTR);
+ b.emitStoreI64(
+ argsPtr, zero, b.emitUextendI64(b.emitIconst32(segmentId)), CtxBuffer.argOffset(0));
+ b.emitStoreI64(argsPtr, zero, b.emitUextendI64(dstOffset), CtxBuffer.argOffset(1));
+ b.emitStoreI64(argsPtr, zero, b.emitUextendI64(srcOffset), CtxBuffer.argOffset(2));
+ b.emitStoreI64(argsPtr, zero, b.emitUextendI64(size), CtxBuffer.argOffset(3));
+
+ b.emitStoreI32(
+ b.useVar(ctx.ctxPtrVar),
+ zero,
+ b.emitIconst32(-8), // sentinel for memory.init
+ CtxBuffer.ARG_COUNT);
+ int trampolinePtr = b.emitLoadI64(b.useVar(ctx.ctxPtrVar), zero, CtxBuffer.TRAMPOLINE_PTR);
+ int trampolineSig = ctx.getOrCreateTrampolineSigRef();
+ b.pushCallArg(b.useVar(ctx.ctxPtrVar));
+ b.emitCallIndirect(trampolineSig, trampolinePtr);
+ }
+
+ static void emitDataDrop(EmitContext ctx, AnnotatedInstruction ins) {
+ int segmentId = (int) ins.operands()[0];
+ var b = ctx.bridge.exports();
+ int zero = b.emitIconst32(0);
+
+ int argsPtr = b.emitLoadI64(b.useVar(ctx.ctxPtrVar), zero, CtxBuffer.ARGS_PTR);
+ b.emitStoreI64(
+ argsPtr, zero, b.emitUextendI64(b.emitIconst32(segmentId)), CtxBuffer.argOffset(0));
+
+ b.emitStoreI32(
+ b.useVar(ctx.ctxPtrVar),
+ zero,
+ b.emitIconst32(-9), // sentinel for data.drop
+ CtxBuffer.ARG_COUNT);
+ int trampolinePtr = b.emitLoadI64(b.useVar(ctx.ctxPtrVar), zero, CtxBuffer.TRAMPOLINE_PTR);
+ int trampolineSig = ctx.getOrCreateTrampolineSigRef();
+ b.pushCallArg(b.useVar(ctx.ctxPtrVar));
+ b.emitCallIndirect(trampolineSig, trampolinePtr);
+ }
+
+ // --- Atomic operations ---
+
+ private static final int[] ATOMIC_LOAD_ACCESS_SIZE = {4, 8, 1, 2, 1, 2, 4};
+ private static final int[] ATOMIC_STORE_ACCESS_SIZE = {4, 8, 1, 2, 1, 2, 4};
+ private static final int[] ATOMIC_RMW_ACCESS_SIZE = {4, 8, 1, 2, 1, 2, 4};
+
+ private static void emitAlignmentCheck(EmitContext ctx, int addr, int offset, int alignment) {
+ if (alignment <= 1) {
+ return; // no alignment needed for byte ops
+ }
+ var b = ctx.bridge.exports();
+ // effective = (addr + offset) as i64
+ int addr64 = b.emitUextendI64(addr);
+ int effective =
+ b.emitIadd(
+ addr64,
+ b.emitIconst64(
+ (int) (Integer.toUnsignedLong(offset) & 0xFFFFFFFFL),
+ (int) (Integer.toUnsignedLong(offset) >>> 32)));
+ int mask = b.emitIconst64(alignment - 1, 0);
+ int rem = b.emitBand(effective, mask);
+ int zero64 = b.emitIconst64(0, 0);
+ int misaligned = b.emitIcmp(1, rem, zero64); // NE
+ int trapBlock = b.createBlock();
+ int okBlock = b.createBlock();
+ b.emitBrif(misaligned, trapBlock, okBlock);
+ fillTrapBlock(ctx, trapBlock, CtxBuffer.TRAP_UNALIGNED_ATOMIC);
+ b.switchToBlock(okBlock);
+ }
+
+ static void emitAtomicLoad(EmitContext ctx, AnnotatedInstruction ins, int loadType) {
+ int addr = ctx.valueStack.pop();
+ int offset = (int) ins.operands()[1];
+ int accessSize = ATOMIC_LOAD_ACCESS_SIZE[loadType];
+ emitAlignmentCheck(ctx, addr, offset, accessSize);
+ emitBoundsCheck(ctx, addr, offset, accessSize);
+ int memBase = ctx.bridge.exports().useVar(ctx.memBaseVar);
+ int result;
+ switch (loadType) {
+ case 0:
+ result = ctx.bridge.exports().emitAtomicLoadI32(memBase, addr, offset);
+ break;
+ case 1:
+ result = ctx.bridge.exports().emitAtomicLoadI64(memBase, addr, offset);
+ break;
+ case 2:
+ result = ctx.bridge.exports().emitAtomicLoad8UI32(memBase, addr, offset);
+ break;
+ case 3:
+ result = ctx.bridge.exports().emitAtomicLoad16UI32(memBase, addr, offset);
+ break;
+ case 4:
+ result = ctx.bridge.exports().emitAtomicLoad8UI64(memBase, addr, offset);
+ break;
+ case 5:
+ result = ctx.bridge.exports().emitAtomicLoad16UI64(memBase, addr, offset);
+ break;
+ case 6:
+ result = ctx.bridge.exports().emitAtomicLoad32UI64(memBase, addr, offset);
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown atomic load type: " + loadType);
+ }
+ ctx.valueStack.push(result);
+ }
+
+ static void emitAtomicStore(EmitContext ctx, AnnotatedInstruction ins, int storeType) {
+ int value = ctx.valueStack.pop();
+ int addr = ctx.valueStack.pop();
+ int offset = (int) ins.operands()[1];
+ int accessSize = ATOMIC_STORE_ACCESS_SIZE[storeType];
+ emitAlignmentCheck(ctx, addr, offset, accessSize);
+ emitBoundsCheck(ctx, addr, offset, accessSize);
+ int memBase = ctx.bridge.exports().useVar(ctx.memBaseVar);
+ switch (storeType) {
+ case 0:
+ ctx.bridge.exports().emitAtomicStoreI32(memBase, addr, value, offset);
+ break;
+ case 1:
+ ctx.bridge.exports().emitAtomicStoreI64(memBase, addr, value, offset);
+ break;
+ case 2:
+ ctx.bridge.exports().emitAtomicStore8(memBase, addr, value, offset);
+ break;
+ case 3:
+ ctx.bridge.exports().emitAtomicStore16(memBase, addr, value, offset);
+ break;
+ case 4:
+ ctx.bridge.exports().emitAtomicStore8I64(memBase, addr, value, offset);
+ break;
+ case 5:
+ ctx.bridge.exports().emitAtomicStore16I64(memBase, addr, value, offset);
+ break;
+ case 6:
+ ctx.bridge.exports().emitAtomicStore32I64(memBase, addr, value, offset);
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown atomic store type: " + storeType);
+ }
+ }
+
+ // rmwOp: 0=add, 1=sub, 2=and, 3=or, 4=xor, 5=xchg
+ static void emitAtomicRmw(EmitContext ctx, AnnotatedInstruction ins, int widthType, int rmwOp) {
+ int value = ctx.valueStack.pop();
+ int addr = ctx.valueStack.pop();
+ int offset = (int) ins.operands()[1];
+ int accessSize = ATOMIC_RMW_ACCESS_SIZE[widthType];
+ emitAlignmentCheck(ctx, addr, offset, accessSize);
+ emitBoundsCheck(ctx, addr, offset, accessSize);
+ int memBase = ctx.bridge.exports().useVar(ctx.memBaseVar);
+ int result;
+ switch (widthType) {
+ case 0:
+ result = ctx.bridge.exports().emitAtomicRmwI32(rmwOp, memBase, addr, value, offset);
+ break;
+ case 1:
+ result = ctx.bridge.exports().emitAtomicRmwI64(rmwOp, memBase, addr, value, offset);
+ break;
+ case 2:
+ result =
+ ctx.bridge
+ .exports()
+ .emitAtomicRmw8UI32(rmwOp, memBase, addr, value, offset);
+ break;
+ case 3:
+ result =
+ ctx.bridge
+ .exports()
+ .emitAtomicRmw16UI32(rmwOp, memBase, addr, value, offset);
+ break;
+ case 4:
+ result =
+ ctx.bridge
+ .exports()
+ .emitAtomicRmw8UI64(rmwOp, memBase, addr, value, offset);
+ break;
+ case 5:
+ result =
+ ctx.bridge
+ .exports()
+ .emitAtomicRmw16UI64(rmwOp, memBase, addr, value, offset);
+ break;
+ case 6:
+ result =
+ ctx.bridge
+ .exports()
+ .emitAtomicRmw32UI64(rmwOp, memBase, addr, value, offset);
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown atomic RMW width: " + widthType);
+ }
+ ctx.valueStack.push(result);
+ }
+
+ static void emitAtomicCmpxchg(EmitContext ctx, AnnotatedInstruction ins, int widthType) {
+ int replacement = ctx.valueStack.pop();
+ int expected = ctx.valueStack.pop();
+ int addr = ctx.valueStack.pop();
+ int offset = (int) ins.operands()[1];
+ int accessSize = ATOMIC_RMW_ACCESS_SIZE[widthType];
+ emitAlignmentCheck(ctx, addr, offset, accessSize);
+ emitBoundsCheck(ctx, addr, offset, accessSize);
+ int memBase = ctx.bridge.exports().useVar(ctx.memBaseVar);
+ int result;
+ switch (widthType) {
+ case 0:
+ result =
+ ctx.bridge
+ .exports()
+ .emitAtomicCasI32(memBase, addr, expected, replacement, offset);
+ break;
+ case 1:
+ result =
+ ctx.bridge
+ .exports()
+ .emitAtomicCasI64(memBase, addr, expected, replacement, offset);
+ break;
+ case 2:
+ result =
+ ctx.bridge
+ .exports()
+ .emitAtomicCas8UI32(memBase, addr, expected, replacement, offset);
+ break;
+ case 3:
+ result =
+ ctx.bridge
+ .exports()
+ .emitAtomicCas16UI32(memBase, addr, expected, replacement, offset);
+ break;
+ case 4:
+ result =
+ ctx.bridge
+ .exports()
+ .emitAtomicCas8UI64(memBase, addr, expected, replacement, offset);
+ break;
+ case 5:
+ result =
+ ctx.bridge
+ .exports()
+ .emitAtomicCas16UI64(memBase, addr, expected, replacement, offset);
+ break;
+ case 6:
+ result =
+ ctx.bridge
+ .exports()
+ .emitAtomicCas32UI64(memBase, addr, expected, replacement, offset);
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown atomic CAS width: " + widthType);
+ }
+ ctx.valueStack.push(result);
+ }
+
+ static void emitAtomicWait32(EmitContext ctx, AnnotatedInstruction ins) {
+ int timeout = ctx.valueStack.pop(); // i64
+ int expected = ctx.valueStack.pop(); // i32
+ int addr = ctx.valueStack.pop(); // i32
+ int offset = (int) ins.operands()[1];
+ emitAlignmentCheck(ctx, addr, offset, 4);
+ emitBoundsCheck(ctx, addr, offset, 4);
+ var b = ctx.bridge.exports();
+ int zero = b.emitIconst32(0);
+
+ int argsPtr = b.emitLoadI64(b.useVar(ctx.ctxPtrVar), zero, CtxBuffer.ARGS_PTR);
+ b.emitStoreI64(argsPtr, zero, b.emitUextendI64(addr), CtxBuffer.argOffset(0));
+ b.emitStoreI64(argsPtr, zero, b.emitUextendI64(expected), CtxBuffer.argOffset(1));
+ b.emitStoreI64(
+ argsPtr,
+ zero,
+ b.emitIconst64(
+ (int) (Integer.toUnsignedLong(offset) & 0xFFFFFFFFL),
+ (int) (Integer.toUnsignedLong(offset) >>> 32)),
+ CtxBuffer.argOffset(2));
+ b.emitStoreI64(argsPtr, zero, timeout, CtxBuffer.argOffset(3));
+
+ b.emitStoreI32(b.useVar(ctx.ctxPtrVar), zero, b.emitIconst32(-10), CtxBuffer.ARG_COUNT);
+ int trampolinePtr = b.emitLoadI64(b.useVar(ctx.ctxPtrVar), zero, CtxBuffer.TRAMPOLINE_PTR);
+ int trampolineSig = ctx.getOrCreateTrampolineSigRef();
+ b.pushCallArg(b.useVar(ctx.ctxPtrVar));
+ int rawResult = b.emitCallIndirect(trampolineSig, trampolinePtr);
+ ctx.valueStack.push(b.emitIreduceI32(rawResult));
+ }
+
+ static void emitAtomicWait64(EmitContext ctx, AnnotatedInstruction ins) {
+ int timeout = ctx.valueStack.pop(); // i64
+ int expected = ctx.valueStack.pop(); // i64
+ int addr = ctx.valueStack.pop(); // i32
+ int offset = (int) ins.operands()[1];
+ emitAlignmentCheck(ctx, addr, offset, 8);
+ emitBoundsCheck(ctx, addr, offset, 8);
+ var b = ctx.bridge.exports();
+ int zero = b.emitIconst32(0);
+
+ int argsPtr = b.emitLoadI64(b.useVar(ctx.ctxPtrVar), zero, CtxBuffer.ARGS_PTR);
+ b.emitStoreI64(argsPtr, zero, b.emitUextendI64(addr), CtxBuffer.argOffset(0));
+ b.emitStoreI64(argsPtr, zero, expected, CtxBuffer.argOffset(1));
+ b.emitStoreI64(
+ argsPtr,
+ zero,
+ b.emitIconst64(
+ (int) (Integer.toUnsignedLong(offset) & 0xFFFFFFFFL),
+ (int) (Integer.toUnsignedLong(offset) >>> 32)),
+ CtxBuffer.argOffset(2));
+ b.emitStoreI64(argsPtr, zero, timeout, CtxBuffer.argOffset(3));
+
+ b.emitStoreI32(b.useVar(ctx.ctxPtrVar), zero, b.emitIconst32(-11), CtxBuffer.ARG_COUNT);
+ int trampolinePtr = b.emitLoadI64(b.useVar(ctx.ctxPtrVar), zero, CtxBuffer.TRAMPOLINE_PTR);
+ int trampolineSig = ctx.getOrCreateTrampolineSigRef();
+ b.pushCallArg(b.useVar(ctx.ctxPtrVar));
+ int rawResult = b.emitCallIndirect(trampolineSig, trampolinePtr);
+ ctx.valueStack.push(b.emitIreduceI32(rawResult));
+ }
+
+ static void emitAtomicNotify(EmitContext ctx, AnnotatedInstruction ins) {
+ int count = ctx.valueStack.pop(); // i32
+ int addr = ctx.valueStack.pop(); // i32
+ int offset = (int) ins.operands()[1];
+ emitAlignmentCheck(ctx, addr, offset, 4);
+ emitBoundsCheck(ctx, addr, offset, 4);
+ var b = ctx.bridge.exports();
+ int zero = b.emitIconst32(0);
+
+ int argsPtr = b.emitLoadI64(b.useVar(ctx.ctxPtrVar), zero, CtxBuffer.ARGS_PTR);
+ b.emitStoreI64(argsPtr, zero, b.emitUextendI64(addr), CtxBuffer.argOffset(0));
+ b.emitStoreI64(argsPtr, zero, b.emitUextendI64(count), CtxBuffer.argOffset(1));
+ b.emitStoreI64(
+ argsPtr,
+ zero,
+ b.emitIconst64(
+ (int) (Integer.toUnsignedLong(offset) & 0xFFFFFFFFL),
+ (int) (Integer.toUnsignedLong(offset) >>> 32)),
+ CtxBuffer.argOffset(2));
+
+ b.emitStoreI32(b.useVar(ctx.ctxPtrVar), zero, b.emitIconst32(-12), CtxBuffer.ARG_COUNT);
+ int trampolinePtr = b.emitLoadI64(b.useVar(ctx.ctxPtrVar), zero, CtxBuffer.TRAMPOLINE_PTR);
+ int trampolineSig = ctx.getOrCreateTrampolineSigRef();
+ b.pushCallArg(b.useVar(ctx.ctxPtrVar));
+ int rawResult = b.emitCallIndirect(trampolineSig, trampolinePtr);
+ ctx.valueStack.push(b.emitIreduceI32(rawResult));
+ }
+
+ static void emitAtomicFence(EmitContext ctx) {
+ ctx.bridge.exports().emitFence();
+ }
+
+ // --- Reference type operations ---
+
+ static void emitRefNull(EmitContext ctx) {
+ // Ref types are i64 on the value stack. REF_NULL_VALUE = -1.
+ // Sign-extend so -1 as i32 → -1 as i64 (matching Java's (long)(int)-1).
+ var b = ctx.bridge.exports();
+ ctx.valueStack.push(b.emitSextendI64(b.emitIconst32(-1)));
+ }
+
+ static void emitRefIsNull(EmitContext ctx) {
+ var b = ctx.bridge.exports();
+ int val = ctx.valueStack.pop();
+ // val is i64 (ref type), compare against REF_NULL_VALUE (-1 as i64)
+ int refNull = b.emitSextendI64(b.emitIconst32(-1));
+ int isNull = b.emitIcmp(0, val, refNull); // EQ, both i64
+ ctx.valueStack.push(isNull);
+ }
+
+ static void emitRefFunc(EmitContext ctx, AnnotatedInstruction ins) {
+ int funcIdx = (int) ins.operands()[0];
+ // Ref types are i64 on the value stack. FuncIds are non-negative,
+ // so sign-extend and uextend give the same result.
+ var b = ctx.bridge.exports();
+ ctx.valueStack.push(b.emitSextendI64(b.emitIconst32(funcIdx)));
+ }
+}
diff --git a/redline/compiler/src/main/java/run/endive/redline/experimental/compiler/internal/NativeValueStack.java b/redline/compiler/src/main/java/run/endive/redline/experimental/compiler/internal/NativeValueStack.java
new file mode 100644
index 000000000..429b0303f
--- /dev/null
+++ b/redline/compiler/src/main/java/run/endive/redline/experimental/compiler/internal/NativeValueStack.java
@@ -0,0 +1,35 @@
+package run.endive.redline.experimental.compiler.internal;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+
+final class NativeValueStack {
+
+ private final Deque stack = new ArrayDeque<>();
+
+ void push(int valueId) {
+ stack.push(valueId);
+ }
+
+ int pop() {
+ return stack.pop();
+ }
+
+ int peek() {
+ return stack.peek();
+ }
+
+ int size() {
+ return stack.size();
+ }
+
+ boolean isEmpty() {
+ return stack.isEmpty();
+ }
+
+ void trimTo(int height) {
+ while (stack.size() > height) {
+ stack.pop();
+ }
+ }
+}
diff --git a/redline/pom.xml b/redline/pom.xml
new file mode 100644
index 000000000..eb56b061f
--- /dev/null
+++ b/redline/pom.xml
@@ -0,0 +1,24 @@
+
+
+ 4.0.0
+
+
+ run.endive
+ endive
+ 999-SNAPSHOT
+ ../pom.xml
+
+ redline-parent-experimental
+ pom
+ Endive - Redline
+ Redline native compiler for WebAssembly
+
+
+ api
+ bridge
+ compiler
+ runner
+ runner-tests
+
+
+
diff --git a/redline/runner-tests/pom.xml b/redline/runner-tests/pom.xml
new file mode 100644
index 000000000..f191c42de
--- /dev/null
+++ b/redline/runner-tests/pom.xml
@@ -0,0 +1,284 @@
+
+
+ 4.0.0
+
+
+ run.endive
+ redline-parent-experimental
+ 999-SNAPSHOT
+ ../pom.xml
+
+ redline-runner-tests-experimental
+ jar
+ Endive - Redline Runner Tests
+ Spec tests for the Redline native runner
+
+
+ 25
+ true
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+ run.endive
+ redline-api-experimental
+ test
+
+
+ run.endive
+ redline-compiler-experimental
+ test
+
+
+ run.endive
+ redline-runner-experimental
+ test
+
+
+ run.endive
+ runtime
+ test
+
+
+ run.endive
+ wasm
+ test
+
+
+ run.endive
+ wasm-corpus
+ test
+
+
+ run.endive
+ wasm-tools
+ test
+
+
+
+
+
+
+ run.endive
+ test-gen-plugin
+ ${project.version}
+
+ ${project.basedir}/../../testsuite
+
+ address.wast
+ align.wast
+ binary-leb128.wast
+ binary.wast
+ block.wast
+ br.wast
+ br_if.wast
+ br_table.wast
+ bulk.wast
+ call.wast
+ call_indirect.wast
+ comments.wast
+ const.wast
+ conversions.wast
+ custom.wast
+ data.wast
+ elem.wast
+ endianness.wast
+ exports.wast
+ f32.wast
+ f32_bitwise.wast
+ f32_cmp.wast
+ f64.wast
+ f64_bitwise.wast
+ f64_cmp.wast
+ fac.wast
+ float_exprs.wast
+ float_literals.wast
+ float_memory.wast
+ float_misc.wast
+ forward.wast
+ func.wast
+ func_ptrs.wast
+ global.wast
+ i32.wast
+ i64.wast
+ if.wast
+ imports.wast
+ inline-module.wast
+ int_exprs.wast
+ int_literals.wast
+ labels.wast
+ left-to-right.wast
+ linking.wast
+ load.wast
+ local_get.wast
+ local_set.wast
+ local_tee.wast
+ loop.wast
+ memory.wast
+ memory_copy.wast
+ memory_fill.wast
+ memory_grow.wast
+ memory_init.wast
+ memory_redundancy.wast
+ memory_size.wast
+ memory_trap.wast
+ names.wast
+ nop.wast
+ proposals/tail-call/return_call.wast
+ proposals/tail-call/return_call_indirect.wast
+ proposals/threads/atomic.wast
+ proposals/threads/exports.wast
+ proposals/threads/imports.wast
+ proposals/threads/memory.wast
+ ref_func.wast
+ ref_is_null.wast
+ ref_null.wast
+ return.wast
+ select.wast
+ skip-stack-guard-page.wast
+ stack.wast
+ start.wast
+ store.wast
+ switch.wast
+ table-sub.wast
+ table.wast
+ table_copy.wast
+ table_fill.wast
+ table_get.wast
+ table_grow.wast
+ table_init.wast
+ table_set.wast
+ table_size.wast
+ token.wast
+ traps.wast
+ type.wast
+ unreachable.wast
+ unreached-invalid.wast
+ unreached-valid.wast
+ unwind.wast
+ utf8-custom-section-id.wast
+ utf8-import-field.wast
+ utf8-import-module.wast
+ utf8-invalid-encoding.wast
+
+ SpecV1GlobalTest.test76, SpecV1GlobalTest.test77,
+ SpecV1MemoryTest.test6, SpecV1MemoryTest.test7,
+ SpecV1ElemTest.test14, SpecV1ElemTest.test15, SpecV1ElemTest.test34, SpecV1ElemTest.test36,
+ SpecV1ElemTest.test97,
+ SpecV1BulkTest.test75,
+ SpecV1AlignTest.test157, SpecV1AlignTest.test158, SpecV1AlignTest.test159,
+ SpecV1AlignTest.test160, SpecV1AlignTest.test161,
+ SpecV1BinaryTest.test41, SpecV1BinaryTest.test42, SpecV1BinaryTest.test43,
+ SpecV1BinaryTest.test44, SpecV1BinaryTest.test45, SpecV1BinaryTest.test46,
+ SpecV1BinaryTest.test47, SpecV1BinaryTest.test48, SpecV1BinaryTest.test49,
+ SpecV1BinaryTest.test50,
+ SpecV1DataTest.test9, SpecV1DataTest.test10,
+ SpecV1ImportsTest.instantiate_testModule8Instance,
+ SpecV1ImportsTest.test118, SpecV1ImportsTest.test119, SpecV1ImportsTest.test120,
+ SpecV1ImportsTest.test123, SpecV1ImportsTest.test124, SpecV1ImportsTest.test125,
+ SpecV1ImportsTest.test127, SpecV1ImportsTest.test128, SpecV1ImportsTest.test129,
+ SpecV1LinkingTest.test129, SpecV1LinkingTest.test130, SpecV1LinkingTest.test131,
+ SpecV1StartTest.test18,
+ SpecV1FuncTest.test85,
+ SpecV1ThreadsImportsTest.test64, SpecV1ThreadsImportsTest.test65,
+ SpecV1ThreadsImportsTest.test66,
+ SpecV1ThreadsImportsTest.test98, SpecV1ThreadsImportsTest.test99,
+ SpecV1ThreadsImportsTest.test100,
+ SpecV1ThreadsMemoryTest.test9, SpecV1ThreadsMemoryTest.test10,
+ SpecV1BrTableTest.test147, SpecV1BrTableTest.test148, SpecV1BrTableTest.test149,
+ SpecV1ElemTest.test89, SpecV1ElemTest.test90, SpecV1ElemTest.test93,
+ SpecV1GlobalTest.test28,
+ SpecV1RefIsNullTest.test2,
+ SpecV1SelectTest.test30, SpecV1SelectTest.test35, SpecV1SelectTest.test36,
+ SpecV1TableFillTest.test8, SpecV1TableFillTest.test9, SpecV1TableFillTest.test10,
+ SpecV1TableFillTest.test13, SpecV1TableFillTest.test14, SpecV1TableFillTest.test15,
+ SpecV1TableFillTest.test18, SpecV1TableFillTest.test19, SpecV1TableFillTest.test20,
+ SpecV1TableFillTest.test23, SpecV1TableFillTest.test24, SpecV1TableFillTest.test26,
+ SpecV1TableFillTest.test32,
+ SpecV1TableGetTest.test3,
+ SpecV1TableGrowTest.test8, SpecV1TableGrowTest.test13, SpecV1TableGrowTest.test15,
+ SpecV1TableGrowTest.test16, SpecV1TableGrowTest.test17, SpecV1TableGrowTest.test19,
+ SpecV1TableSetTest.test3
+
+ obsolete-keywords.wast
+ simd_address.wast
+ simd_align.wast
+ simd_bit_shift.wast
+ simd_bitwise.wast
+ simd_boolean.wast
+ simd_const.wast
+ simd_conversions.wast
+ simd_f32x4.wast
+ simd_f32x4_arith.wast
+ simd_f32x4_cmp.wast
+ simd_f32x4_pmin_pmax.wast
+ simd_f32x4_rounding.wast
+ simd_f64x2.wast
+ simd_f64x2_arith.wast
+ simd_f64x2_cmp.wast
+ simd_f64x2_pmin_pmax.wast
+ simd_f64x2_rounding.wast
+ simd_i16x8_arith.wast
+ simd_i16x8_arith2.wast
+ simd_i16x8_cmp.wast
+ simd_i16x8_extadd_pairwise_i8x16.wast
+ simd_i16x8_extmul_i8x16.wast
+ simd_i16x8_q15mulr_sat_s.wast
+ simd_i16x8_sat_arith.wast
+ simd_i32x4_arith.wast
+ simd_i32x4_arith2.wast
+ simd_i32x4_cmp.wast
+ simd_i32x4_dot_i16x8.wast
+ simd_i32x4_extadd_pairwise_i16x8.wast
+ simd_i32x4_extmul_i16x8.wast
+ simd_i32x4_trunc_sat_f32x4.wast
+ simd_i32x4_trunc_sat_f64x2.wast
+ simd_i64x2_arith.wast
+ simd_i64x2_arith2.wast
+ simd_i64x2_cmp.wast
+ simd_i64x2_extmul_i32x4.wast
+ simd_i8x16_arith.wast
+ simd_i8x16_arith2.wast
+ simd_i8x16_cmp.wast
+ simd_i8x16_sat_arith.wast
+ simd_int_to_int_extend.wast
+ simd_lane.wast
+ simd_linking.wast
+ simd_load.wast
+ simd_load16_lane.wast
+ simd_load32_lane.wast
+ simd_load64_lane.wast
+ simd_load8_lane.wast
+ simd_load_extend.wast
+ simd_load_splat.wast
+ simd_load_zero.wast
+ simd_select.wast
+ simd_splat.wast
+ simd_store.wast
+ simd_store16_lane.wast
+ simd_store32_lane.wast
+ simd_store64_lane.wast
+ simd_store8_lane.wast
+
+
+
+
+
+ wasm-test-gen
+
+
+
+
+
+
+
diff --git a/redline/runner-tests/src/test/java/run/endive/redline/experimental/runner/internal/InterruptionTest.java b/redline/runner-tests/src/test/java/run/endive/redline/experimental/runner/internal/InterruptionTest.java
new file mode 100644
index 000000000..755f10d0b
--- /dev/null
+++ b/redline/runner-tests/src/test/java/run/endive/redline/experimental/runner/internal/InterruptionTest.java
@@ -0,0 +1,63 @@
+package run.endive.redline.experimental.runner.internal;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.junit.jupiter.api.Test;
+import run.endive.corpus.CorpusResources;
+import run.endive.redline.experimental.api.internal.RedlineTarget;
+import run.endive.redline.experimental.compiler.internal.NativeCompiler;
+import run.endive.redline.experimental.runner.NativeMachineFactory;
+import run.endive.runtime.Instance;
+import run.endive.wasm.Parser;
+import run.endive.wasm.WasmEngineException;
+
+public class InterruptionTest {
+
+ @Test
+ public void shouldInterruptLoopViaThread() throws InterruptedException {
+ try (var instance = buildInstance("compiled/infinite-loop.c.wasm")) {
+ var function = instance.export("run");
+ assertThreadInterruption(function::apply);
+ }
+ }
+
+ @Test
+ public void shouldInterruptCallViaThread() throws InterruptedException {
+ try (var instance = buildInstance("compiled/power.c.wasm")) {
+ var function = instance.export("run");
+ assertThreadInterruption(() -> function.apply(100));
+ }
+ }
+
+ private static void assertThreadInterruption(Runnable function) throws InterruptedException {
+ AtomicBoolean interrupted = new AtomicBoolean();
+ Thread thread =
+ new Thread(
+ () -> {
+ var e = assertThrows(WasmEngineException.class, function::run);
+ assertEquals("interrupted", e.getMessage());
+ interrupted.set(true);
+ });
+ thread.setDaemon(true);
+ thread.start();
+ Thread.sleep(100);
+
+ thread.interrupt();
+ SECONDS.timedJoin(thread, 10);
+ assertTrue(interrupted.get());
+ }
+
+ private static Instance buildInstance(String resource) {
+ var module = Parser.parse(CorpusResources.getResource(resource));
+ return NativeMachineFactory.builder(module)
+ .withCompilerFunction(
+ m ->
+ NativeCompiler.compileAll(
+ RedlineTarget.detectHost().orElseThrow().triple(), m))
+ .build();
+ }
+}
diff --git a/redline/runner-tests/src/test/java/run/endive/testing/ArgsAdapter.java b/redline/runner-tests/src/test/java/run/endive/testing/ArgsAdapter.java
new file mode 100644
index 000000000..b46820123
--- /dev/null
+++ b/redline/runner-tests/src/test/java/run/endive/testing/ArgsAdapter.java
@@ -0,0 +1,58 @@
+package run.endive.testing;
+
+import java.util.ArrayList;
+import java.util.List;
+import run.endive.runtime.CallResult;
+import run.endive.runtime.ExportFunction;
+
+public final class ArgsAdapter {
+ private final List longs = new ArrayList<>();
+ private final List