kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

engine.c (64074B)


      1 /* The interpreter engine: an explicit-stack dispatch loop over the lowered
      2  * bytecode. IR-level calls push/pop InterpFrames on the InterpStack instead of
      3  * recursing on the host C stack, so execution can be suspended and resumed.
      4  *
      5  * Dispatch is a switch on the record opcode. (Direct threading via a computed
      6  * goto is reserved for a later pass; the InterpInsn keeps a `handler` slot for
      7  * it. A switch keeps the engine portable under -Wpedantic and self-host.) */
      8 
      9 #include <kit/config.h> /* KIT_INTERP_THREADED: dispatch default */
     10 #include <string.h>
     11 
     12 #include "abi/abi.h"
     13 #include "cg/cgir.h"
     14 #include "cg/type.h"
     15 #include "core/arena.h"
     16 #include "core/core.h"
     17 #include "core/diag.h"
     18 #include "interp/interp.h"
     19 
     20 #define PERM_R KIT_INTERP_PERM_READ
     21 #define PERM_W KIT_INTERP_PERM_WRITE
     22 
     23 static SrcLoc iloc(void) {
     24   SrcLoc l;
     25   l.file_id = 0;
     26   l.line = 0;
     27   l.col = 0;
     28   return l;
     29 }
     30 
     31 /* ---- width / fp helpers ---- */
     32 
     33 static u64 mask_w(u64 v, u32 w) {
     34   if (w >= 8) return v;
     35   if (w == 0) return v;
     36   return v & ((1ull << (w * 8u)) - 1ull);
     37 }
     38 
     39 static i64 sext_w(u64 v, u32 w) {
     40   u32 bits;
     41   u64 m;
     42   if (w >= 8 || w == 0) return (i64)v;
     43   bits = w * 8u;
     44   v &= ((1ull << bits) - 1ull);
     45   m = 1ull << (bits - 1u);
     46   return (i64)((v ^ m) - m);
     47 }
     48 
     49 /* Low `width`-bit mask (width in *bits*, 0..64). */
     50 static u64 bits_mask(u32 width) {
     51   return width >= 64u ? ~0ull : ((1ull << width) - 1ull);
     52 }
     53 
     54 /* Interpreter-private va_list layout: a single cursor walks a contiguous buffer
     55  * of the anonymous arguments, each at an 8-byte (16 for >8B types) aligned
     56  * slot. The interpreter owns both the call-site buffer build and
     57  * va_start/va_arg, so the layout is self-consistent regardless of the target
     58  * ABI's real va_list. */
     59 static u32 va_align_of(u32 size) { return size > 8u ? 16u : 8u; }
     60 static u32 va_stride_of(u32 size) {
     61   return size > 8u ? ((size + 15u) & ~15u) : 8u;
     62 }
     63 
     64 static double rd_f(u64 bits, u32 w) {
     65   if (w == 4) {
     66     float f;
     67     u32 b = (u32)bits;
     68     memcpy(&f, &b, 4);
     69     return (double)f;
     70   }
     71   {
     72     double d;
     73     memcpy(&d, &bits, 8);
     74     return d;
     75   }
     76 }
     77 
     78 static u64 wr_f(double d, u32 w) {
     79   if (w == 4) {
     80     float f = (float)d;
     81     u32 b;
     82     memcpy(&b, &f, 4);
     83     return b;
     84   }
     85   {
     86     u64 b;
     87     memcpy(&b, &d, 8);
     88     return b;
     89   }
     90 }
     91 
     92 /* ---- memory access (always vtable-translated) ---- */
     93 /* A translation miss latches st->mem_fault; the run loop converts the latch to
     94  * a delivered fault at the next straight-line/branch re-check point. */
     95 
     96 static u64 mem_read(InterpStack* st, u64 addr, u32 size) {
     97   u8* host = interp_translate(st->prog, addr, size, PERM_R);
     98   u64 v = 0;
     99   if (!host) {
    100     st->mem_fault = 1;
    101     return 0;
    102   }
    103   memcpy(&v, host, size ? size : 8u);
    104   return v;
    105 }
    106 
    107 static void mem_write(InterpStack* st, u64 addr, u32 size, u64 v) {
    108   u8* host = interp_translate(st->prog, addr, size, PERM_W);
    109   if (!host) {
    110     st->mem_fault = 1;
    111     return;
    112   }
    113   memcpy(host, &v, size ? size : 8u);
    114 }
    115 
    116 static void mem_copy(InterpStack* st, u64 dst, u64 src, u32 n) {
    117   u8* d = interp_translate(st->prog, dst, n, PERM_W);
    118   u8* s = interp_translate(st->prog, src, n, PERM_R);
    119   if (!d || !s) {
    120     st->mem_fault = 1;
    121     return;
    122   }
    123   memmove(d, s, n);
    124 }
    125 
    126 /* ---- operand access ---- */
    127 
    128 static u64 frame_base(InterpStack* st, u32 mem_off) {
    129   return (u64)(uintptr_t)(st->mem_arena + mem_off);
    130 }
    131 
    132 /* addr_from_operand semantics: the abstract address an lvalue operand denotes.
    133  */
    134 static u64 op_addr(InterpStack* st, InterpFunc* fn, u64* regs, u32 mem_off,
    135                    const Operand* op) {
    136   switch ((OptOperandKind)op->kind) {
    137     case OPT_OPK_LOCAL:
    138       return frame_base(st, mem_off) + fn->slot_off[op->v.frame_slot];
    139     case OPT_OPK_GLOBAL:
    140       return (u64)(uintptr_t)interp_global_base(fn, op->v.global.sym) +
    141              (u64)op->v.global.addend;
    142     case OPT_OPK_INDIRECT: {
    143       u64 a = regs[op->v.ind.base];
    144       if (op->v.ind.index != (Reg)REG_NONE)
    145         a += regs[op->v.ind.index] << op->v.ind.log2_scale;
    146       a += (u64)(i64)op->v.ind.ofs;
    147       return a;
    148     }
    149     case OPT_OPK_REG:
    150       return regs[op->v.reg];
    151     default:
    152       return 0;
    153   }
    154 }
    155 
    156 /* loc_from_operand-as-value semantics: the scalar value of a value operand. */
    157 static u64 op_value(InterpStack* st, InterpFunc* fn, u64* regs, u32 mem_off,
    158                     const Operand* op) {
    159   switch ((OptOperandKind)op->kind) {
    160     case OPT_OPK_REG:
    161       return regs[op->v.reg];
    162     case OPT_OPK_IMM:
    163       return (u64)op->v.imm;
    164     case OPT_OPK_LOCAL:
    165     case OPT_OPK_GLOBAL:
    166     case OPT_OPK_INDIRECT: {
    167       u64 a = op_addr(st, fn, regs, mem_off, op);
    168       u32 sz = abi_cg_sizeof(fn->prog->c->abi, op->type);
    169       return mem_read(st, a, sz ? sz : 8u);
    170     }
    171     default:
    172       return 0;
    173   }
    174 }
    175 
    176 /* write_loc semantics: store a scalar result into a destination operand, which
    177  * may be a register OR a memory location (OPK_LOCAL/GLOBAL/INDIRECT). The
    178  * optimizer leaves un-promoted (e.g. address-taken) destinations as memory. */
    179 static void write_dst(InterpStack* st, InterpFunc* fn, u64* regs, u32 mem_off,
    180                       const Operand* op, u64 value) {
    181   if (op->kind == OPK_REG) {
    182     regs[op->v.reg] = value;
    183     return;
    184   }
    185   {
    186     u64 a = op_addr(st, fn, regs, mem_off, op);
    187     u32 sz = abi_cg_sizeof(fn->prog->c->abi, op->type);
    188     mem_write(st, a, sz ? sz : 8u, value);
    189   }
    190 }
    191 
    192 /* pointer_addr_from_operand semantics: the address an aggregate pointer
    193  * operand denotes. An OPK_LOCAL of pointer type *holds* the pointer (load it);
    194  * otherwise the local *is* the aggregate storage (its frame home is the
    195  * address). Used only by AGG_COPY/AGG_SET. */
    196 static u64 interp_ptr_addr(InterpStack* st, InterpFunc* fn, u64* regs,
    197                            u32 mem_off, const Operand* op) {
    198   if (op->kind == OPK_LOCAL && !cg_type_is_ptr(fn->prog->c, op->type))
    199     return frame_base(st, mem_off) + fn->slot_off[op->v.frame_slot];
    200   if (op->kind == OPK_LOCAL) {
    201     /* pointer-typed local: the slot holds the pointer value */
    202     u64 slot = frame_base(st, mem_off) + fn->slot_off[op->v.frame_slot];
    203     return mem_read(st, slot, 8u);
    204   }
    205   return op_addr(st, fn, regs, mem_off, op);
    206 }
    207 
    208 /* Common compiler intrinsics. Returns 0 (and sets status) if unsupported. */
    209 static int interp_intrinsic(InterpStack* st, InterpFunc* fn, u64* regs,
    210                             u32 mem_off, InterpInsn* in);
    211 
    212 /* The register and addressable-memory arenas are FIXED reservations that never
    213  * move: an OP_ADDR_OF materializes a local's address as an absolute host
    214  * pointer into mem_arena, and that pointer can escape into a register or out to
    215  * another local, so reallocating (moving) the arena would dangle it. Frames
    216  * follow strict stack discipline (CALL bumps the top, RET rewinds it), so a
    217  * generous fixed reservation suffices; overflow traps cleanly as a stack
    218  * overflow rather than corrupting memory. */
    219 /* TODO(perf): these 16 MiB are allocated and freed per kit_interp_call /
    220  * kit_interp_call_args (each spins up a fresh InterpStack). For call-heavy
    221  * embeddings, reuse the arenas across calls via a per-program stack pool rather
    222  * than shrinking the reservation — the size must stay generous because the
    223  * arenas are non-relocating (escaping pointers; see above). */
    224 #define INTERP_REGS_RESERVE (8u * 1024u * 1024u)
    225 #define INTERP_MEM_RESERVE (8u * 1024u * 1024u)
    226 
    227 static u32 bump(u8* arena, u32* top, u32 cap, u32 size, u32 align) {
    228   u32 off = (*top + align - 1u) & ~(align - 1u);
    229   (void)arena;
    230   if (off + size > cap || off + size < off) return 0xffffffffu; /* overflow */
    231   *top = off + size;
    232   return off;
    233 }
    234 
    235 /* Push a fresh frame for fn; returns its index, or 0xffffffff on overflow.
    236  * The arenas never move, so existing frame pointers stay valid. */
    237 static u32 frame_push(InterpStack* st, InterpFunc* fn) {
    238   InterpFrame* fr;
    239   u32 regs_off, mem_off;
    240   if (st->nframes == st->frames_cap) {
    241     Heap* h = st->prog->c->ctx->heap;
    242     u32 ncap = st->frames_cap ? st->frames_cap * 2u : 32u;
    243     InterpFrame* nf = (InterpFrame*)h->realloc(
    244         h, st->frames, sizeof(InterpFrame) * st->frames_cap,
    245         sizeof(InterpFrame) * ncap, _Alignof(InterpFrame));
    246     if (!nf) return 0xffffffffu;
    247     st->frames = nf;
    248     st->frames_cap = ncap;
    249   }
    250   regs_off = bump(st->regs_arena, &st->regs_top, st->regs_cap,
    251                   (fn->npregs ? fn->npregs : 1u) * 8u, 8u);
    252   mem_off = bump(st->mem_arena, &st->mem_top, st->mem_cap,
    253                  fn->frame_bytes ? fn->frame_bytes : 16u, fn->frame_align);
    254   if (regs_off == 0xffffffffu || mem_off == 0xffffffffu) return 0xffffffffu;
    255   fr = &st->frames[st->nframes];
    256   memset(fr, 0, sizeof *fr);
    257   fr->fn = fn;
    258   fr->regs_off = regs_off;
    259   fr->mem_off = mem_off;
    260   fr->frame_bytes = fn->frame_bytes;
    261   fr->alloca_top = fn->frame_bytes;
    262   fr->ip = &fn->code[fn->block_pc[fn->f->entry] == INTERP_PC_NONE
    263                          ? 0u
    264                          : fn->block_pc[fn->f->entry]];
    265   /* zero the register file */
    266   memset(st->regs_arena + regs_off, 0, (fn->npregs ? fn->npregs : 1u) * 8u);
    267   st->nframes++;
    268   return st->nframes - 1u;
    269 }
    270 
    271 static void unsupported(InterpStack* st, const char* what) {
    272   st->status = KIT_INTERP_ERROR;
    273   st->trap_reason = what;
    274   diag_emit(st->prog->c->ctx->diag, KIT_DIAG_ERROR, iloc(),
    275             "interp: %s not supported", what ? what : "operation");
    276 }
    277 
    278 static void fault(InterpStack* st, const char* what) {
    279   st->status = KIT_INTERP_TRAP;
    280   st->trap_reason = what;
    281   diag_emit(st->prog->c->ctx->diag, KIT_DIAG_ERROR, iloc(), "interp: trap: %s",
    282             what ? what : "fault");
    283 }
    284 
    285 /* ---- integer/fp arithmetic ---- */
    286 
    287 /* Shift-count mask for the spec's portable "reduce modulo width" rule
    288  * (doc/IR.md). The engine stores every scalar in a u64, so the meaningful
    289  * range is the storage width (<=64 bits); 16-byte scalars are lowered to
    290  * memory / 64-bit-half sequences before reaching here, never as a w==16 BINOP.
    291  * Clamping to the storage width keeps the host C shift in range regardless and
    292  * is identical to (w*8-1) for every width the engine actually carries (<=8). */
    293 static u32 shift_mask(u32 w) { return (w >= 8u ? 64u : w * 8u) - 1u; }
    294 
    295 static u64 do_binop(InterpStack* st, u32 binop, u64 a, u64 b, u32 w, u8 fp) {
    296   if (fp) {
    297     double x = rd_f(a, w), y = rd_f(b, w), r = 0;
    298     switch ((BinOp)binop) {
    299       case BO_FADD:
    300         r = x + y;
    301         break;
    302       case BO_FSUB:
    303         r = x - y;
    304         break;
    305       case BO_FMUL:
    306         r = x * y;
    307         break;
    308       case BO_FDIV:
    309         r = x / y;
    310         break;
    311       default:
    312         unsupported(st, "fp binop");
    313         return 0;
    314     }
    315     return wr_f(r, w);
    316   }
    317   switch ((BinOp)binop) {
    318     case BO_IADD:
    319       return mask_w(a + b, w);
    320     case BO_ISUB:
    321       return mask_w(a - b, w);
    322     case BO_IMUL:
    323       return mask_w(a * b, w);
    324     case BO_SDIV: {
    325       i64 x = sext_w(a, w), y = sext_w(b, w);
    326       if (y == 0) {
    327         fault(st, "integer divide by zero");
    328         return 0;
    329       }
    330       /* INT_MIN / -1 overflows (UB / SIGFPE on x86) — wraps to INT_MIN. */
    331       if (y == -1) return mask_w(0u - (u64)x, w);
    332       return mask_w((u64)(x / y), w);
    333     }
    334     case BO_UDIV: {
    335       u64 x = mask_w(a, w), y = mask_w(b, w);
    336       if (y == 0) {
    337         fault(st, "integer divide by zero");
    338         return 0;
    339       }
    340       return mask_w(x / y, w);
    341     }
    342     case BO_SREM: {
    343       i64 x = sext_w(a, w), y = sext_w(b, w);
    344       if (y == 0) {
    345         fault(st, "integer divide by zero");
    346         return 0;
    347       }
    348       if (y == -1) return 0; /* INT_MIN % -1 == 0 (avoids the overflow UB) */
    349       return mask_w((u64)(x % y), w);
    350     }
    351     case BO_UREM: {
    352       u64 x = mask_w(a, w), y = mask_w(b, w);
    353       if (y == 0) {
    354         fault(st, "integer divide by zero");
    355         return 0;
    356       }
    357       return mask_w(x % y, w);
    358     }
    359     case BO_AND:
    360       return mask_w(a & b, w);
    361     case BO_OR:
    362       return mask_w(a | b, w);
    363     case BO_XOR:
    364       return mask_w(a ^ b, w);
    365     case BO_SHL:
    366       return mask_w(a << (b & shift_mask(w)), w);
    367     case BO_SHR_S: {
    368       i64 x = sext_w(a, w);
    369       return mask_w((u64)(x >> (b & shift_mask(w))), w);
    370     }
    371     case BO_SHR_U:
    372       return mask_w(mask_w(a, w) >> (b & shift_mask(w)), w);
    373     default:
    374       unsupported(st, "int binop");
    375       return 0;
    376   }
    377 }
    378 
    379 static int do_cmp(InterpStack* st, u32 cmp, u64 a, u64 b, u32 w) {
    380   /* FP-ness is self-describing from the opcode (the FP block starts at
    381    * CMP_OEQ_F); no operand-class sniffing needed. */
    382   if (cmp >= CMP_OEQ_F) {
    383     double x = rd_f(a, w), y = rd_f(b, w);
    384     int uno = (x != x) || (y != y); /* unordered: either operand is NaN */
    385     switch ((CmpOp)cmp) {
    386       case CMP_OEQ_F:
    387         return x == y; /* ordered: false on NaN */
    388       case CMP_ONE_F:
    389         return !uno && (x != y);
    390       case CMP_OLT_F:
    391         return x < y;
    392       case CMP_OLE_F:
    393         return x <= y;
    394       case CMP_OGT_F:
    395         return x > y;
    396       case CMP_OGE_F:
    397         return x >= y;
    398       case CMP_UEQ_F:
    399         return uno || (x == y);
    400       case CMP_UNE_F:
    401         return x != y; /* unordered: true on NaN */
    402       case CMP_ULT_F:
    403         return uno || (x < y);
    404       case CMP_ULE_F:
    405         return uno || (x <= y);
    406       case CMP_UGT_F:
    407         return uno || (x > y);
    408       case CMP_UGE_F:
    409         return uno || (x >= y);
    410       default:
    411         break;
    412     }
    413   }
    414   switch ((CmpOp)cmp) {
    415     case CMP_EQ:
    416       return mask_w(a, w) == mask_w(b, w);
    417     case CMP_NE:
    418       return mask_w(a, w) != mask_w(b, w);
    419     case CMP_LT_S:
    420       return sext_w(a, w) < sext_w(b, w);
    421     case CMP_LE_S:
    422       return sext_w(a, w) <= sext_w(b, w);
    423     case CMP_GT_S:
    424       return sext_w(a, w) > sext_w(b, w);
    425     case CMP_GE_S:
    426       return sext_w(a, w) >= sext_w(b, w);
    427     case CMP_LT_U:
    428       return mask_w(a, w) < mask_w(b, w);
    429     case CMP_LE_U:
    430       return mask_w(a, w) <= mask_w(b, w);
    431     case CMP_GT_U:
    432       return mask_w(a, w) > mask_w(b, w);
    433     case CMP_GE_U:
    434       return mask_w(a, w) >= mask_w(b, w);
    435     default:
    436       unsupported(st, "cmp");
    437       return 0;
    438   }
    439 }
    440 
    441 /* Saturating float-to-integer (NaN -> 0, out-of-range -> clamped to the
    442  * destination width). Matches Wasm trunc_sat semantics and, crucially, avoids
    443  * the UB of casting a NaN/overflowing double to an integer (which traps under
    444  * UBSan). For in-range values this is identical to a plain truncating cast, so
    445  * well-defined C float->int conversions are unaffected. Avoids <math.h>
    446  * (libkit is freestanding) by building the 2^k bound with a loop. */
    447 static u64 ftoi_sat(double d, u32 wbytes, int is_signed) {
    448   u32 bits, i;
    449   double bound;
    450   if (d != d) return 0; /* NaN */
    451   if (wbytes == 0 || wbytes > 8) wbytes = 8;
    452   bits = wbytes * 8u;
    453   if (is_signed) {
    454     bound = 1.0;
    455     for (i = 0; i + 1u < bits; ++i) bound *= 2.0; /* 2^(bits-1) */
    456     if (d >= bound)
    457       return mask_w(
    458           bits >= 64 ? 0x7fffffffffffffffull : (((u64)1 << (bits - 1u)) - 1u),
    459           wbytes);
    460     if (d < -bound)
    461       return mask_w(
    462           bits >= 64 ? 0x8000000000000000ull : ((u64)1 << (bits - 1u)), wbytes);
    463     return mask_w((u64)(i64)d, wbytes);
    464   }
    465   bound = 1.0;
    466   for (i = 0; i < bits; ++i) bound *= 2.0; /* 2^bits */
    467   if (d < 0.0) return 0;
    468   if (d >= bound)
    469     return mask_w(bits >= 64 ? ~0ull : (((u64)1 << bits) - 1u), wbytes);
    470   return mask_w((u64)d, wbytes);
    471 }
    472 
    473 static u64 do_convert(InterpStack* st, InterpInsn* in, u64 v) {
    474   u32 wd = in->w0, ws = in->w1;
    475   switch ((ConvKind)in->sub) {
    476     case CV_SEXT:
    477       return mask_w((u64)sext_w(v, ws), wd);
    478     case CV_ZEXT:
    479       return mask_w(mask_w(v, ws), wd);
    480     case CV_TRUNC:
    481       return mask_w(v, wd);
    482     case CV_ITOF_S:
    483       return wr_f((double)sext_w(v, ws), wd);
    484     case CV_ITOF_U:
    485       return wr_f((double)mask_w(v, ws), wd);
    486     case CV_FTOI_S:
    487       return ftoi_sat(rd_f(v, ws), wd, 1);
    488     case CV_FTOI_U:
    489       return ftoi_sat(rd_f(v, ws), wd, 0);
    490     case CV_FEXT:
    491       return wr_f(rd_f(v, ws), wd);
    492     case CV_FTRUNC:
    493       return wr_f(rd_f(v, ws), wd);
    494     case CV_BITCAST:
    495       return mask_w(v, wd);
    496     default:
    497       unsupported(st, "convert");
    498       return 0;
    499   }
    500 }
    501 
    502 static u64 do_rmw(u32 op, u64 old, u64 val, u32 w) {
    503   switch ((KitCgAtomicOp)op) {
    504     case KIT_CG_ATOMIC_XCHG:
    505       return mask_w(val, w);
    506     case KIT_CG_ATOMIC_ADD:
    507       return mask_w(old + val, w);
    508     case KIT_CG_ATOMIC_SUB:
    509       return mask_w(old - val, w);
    510     case KIT_CG_ATOMIC_AND:
    511       return mask_w(old & val, w);
    512     case KIT_CG_ATOMIC_OR:
    513       return mask_w(old | val, w);
    514     case KIT_CG_ATOMIC_XOR:
    515       return mask_w(old ^ val, w);
    516     case KIT_CG_ATOMIC_NAND:
    517       return mask_w(~(old & val), w);
    518     default:
    519       return old;
    520   }
    521 }
    522 
    523 static u64 do_unop(InterpStack* st, u32 unop, u64 a, u32 w, u8 fp) {
    524   (void)fp;
    525   switch ((UnOp)unop) {
    526     case UO_NEG:
    527       return mask_w(0u - a, w); /* well-defined two's-complement */
    528     case UO_FNEG:
    529       return wr_f(-rd_f(a, w), w);
    530     case UO_NOT:
    531       return mask_w(a, w) == 0 ? 1u : 0u;
    532     case UO_BNOT:
    533       return mask_w(~a, w);
    534     default:
    535       unsupported(st, "unop");
    536       return 0;
    537   }
    538 }
    539 
    540 /* Bind call arguments into a freshly-pushed callee frame (value semantics). */
    541 static void bind_args(InterpStack* st, u32 caller_idx, u32 callee_idx,
    542                       const OptCGCallDesc* desc) {
    543   InterpProgram* p = st->prog;
    544   InterpFrame* caller = &st->frames[caller_idx];
    545   InterpFrame* callee = &st->frames[callee_idx];
    546   InterpFunc* cfn = caller->fn;
    547   InterpFunc* efn = callee->fn;
    548   u64* cregs = (u64*)(st->regs_arena + caller->regs_off);
    549   u64* eregs = (u64*)(st->regs_arena + callee->regs_off);
    550   u32 nbind = desc->nargs < efn->f->nparams ? desc->nargs : efn->f->nparams;
    551   u32 i;
    552   for (i = 0; i < nbind; ++i) {
    553     OptCGABIValue* arg = &desc->args[i];
    554     IRParam* pr = &efn->f->params[i];
    555     u32 size = abi_cg_sizeof(p->c->abi, arg->type);
    556     if (pr->storage.kind == CG_LOCAL_STORAGE_REG) {
    557       eregs[pr->storage.v.reg] =
    558           op_value(st, cfn, cregs, caller->mem_off, &arg->storage);
    559     } else {
    560       u64 dst = frame_base(st, callee->mem_off) +
    561                 efn->slot_off[pr->storage.v.frame_slot];
    562       if (cg_type_is_aggregate(p->c, arg->type) || size > 8u) {
    563         u64 src = op_addr(st, cfn, cregs, caller->mem_off, &arg->storage);
    564         mem_copy(st, dst, src, size);
    565       } else {
    566         mem_write(st, dst, size ? size : 8u,
    567                   op_value(st, cfn, cregs, caller->mem_off, &arg->storage));
    568       }
    569     }
    570   }
    571 }
    572 
    573 /* Lay out the anonymous (variadic) arguments of an internal call into a
    574  * contiguous buffer in the callee frame's addressable region, above its static
    575  * frame and any future alloca. Records the buffer offset on the callee frame so
    576  * IOP_VA_START can hand va_arg a cursor over it. Returns 0 on stack overflow.
    577  */
    578 static int build_varargs(InterpStack* st, u32 caller_idx, u32 callee_idx,
    579                          const OptCGCallDesc* desc) {
    580   InterpProgram* p = st->prog;
    581   InterpFrame* caller = &st->frames[caller_idx];
    582   InterpFrame* callee = &st->frames[callee_idx];
    583   InterpFunc* cfn = caller->fn;
    584   InterpFunc* efn = callee->fn;
    585   u64* cregs = (u64*)(st->regs_arena + caller->regs_off);
    586   u32 nfixed = efn->f->nparams;
    587   u32 cur = (callee->alloca_top + 15u) & ~15u; /* 16-align buffer start */
    588   u32 buf_start = cur;
    589   u32 i;
    590   if (desc->nargs <= nfixed) return 1; /* no anonymous args */
    591   for (i = nfixed; i < desc->nargs; ++i) {
    592     OptCGABIValue* arg = &desc->args[i];
    593     u32 size = abi_cg_sizeof(p->c->abi, arg->type);
    594     u32 al = va_align_of(size);
    595     u64 dst;
    596     cur = (cur + al - 1u) & ~(al - 1u);
    597     if ((u64)callee->mem_off + cur + va_stride_of(size) > st->mem_cap) return 0;
    598     dst = frame_base(st, callee->mem_off) + cur;
    599     if (cg_type_is_aggregate(p->c, arg->type) || size > 8u) {
    600       u64 src = op_addr(st, cfn, cregs, caller->mem_off, &arg->storage);
    601       mem_copy(st, dst, src, size);
    602     } else {
    603       mem_write(st, dst, 8u,
    604                 op_value(st, cfn, cregs, caller->mem_off, &arg->storage));
    605     }
    606     cur += va_stride_of(size);
    607   }
    608   callee->has_varargs = 1;
    609   callee->vararg_off = callee->mem_off + buf_start;
    610   callee->alloca_top = cur;
    611   if (callee->mem_off + cur > st->mem_top) st->mem_top = callee->mem_off + cur;
    612   return 1;
    613 }
    614 
    615 /* ---- external (host ABI) call marshalling ---- */
    616 
    617 /* Record an integer-register argument. Returns non-zero (with *why) on
    618  * overflow of the supported register-thunk family. */
    619 static int ffi_push_int(InterpFfiArgs* fa, u64 v, const char** why) {
    620   if (fa->nint >= 8u) {
    621     *why = "external call: too many int args";
    622     return 1;
    623   }
    624   fa->iargs[fa->nint++] = v;
    625   return 0;
    626 }
    627 
    628 /* Record an fp-register argument, tracking single vs double precision (the two
    629  * occupy the fp register differently). Returns non-zero (with *why) on overflow
    630  * or a float/double mix within one signature. */
    631 static int ffi_push_fp(InterpFfiArgs* fa, u64 bits, u32 size,
    632                        const char** why) {
    633   if (fa->nfp >= 8u) {
    634     *why = "external call: too many fp args";
    635     return 1;
    636   }
    637   if (size == 4u) {
    638     if (fa->nfp > 0u && !fa->args_fp_is_float) {
    639       *why = "external call: mixed float/double args";
    640       return 1;
    641     }
    642     fa->args_fp_is_float = 1u;
    643     fa->fargs_f[fa->nfp++] = (float)rd_f(bits, 4u);
    644   } else {
    645     if (fa->nfp > 0u && fa->args_fp_is_float) {
    646       *why = "external call: mixed float/double args";
    647       return 1;
    648     }
    649     fa->fargs[fa->nfp++] = rd_f(bits, size ? size : 8u);
    650   }
    651   return 0;
    652 }
    653 
    654 static u64 ext_call(InterpStack* st, InterpFrame* fr, u64* regs, void* host_fp,
    655                     const OptCGCallDesc* desc) {
    656   InterpProgram* p = st->prog;
    657   const ABIFuncInfo* fi = desc->abi;
    658   InterpFfiArgs fa;
    659   const char* reason = NULL;
    660   u32 i;
    661 
    662   if (!fi) {
    663     unsupported(st, "external call without ABI info");
    664     return 0;
    665   }
    666   if (fi->vararg_on_stack && fi->variadic) {
    667     unsupported(st, "variadic external call (stack-routed)");
    668     return 0;
    669   }
    670   memset(&fa, 0, sizeof fa);
    671   fa.fi = fi;
    672 
    673   /* hidden struct return: pass the caller's aggregate-return slot directly.
    674    * When the call is a tail call its result has no local home (ret.storage is
    675    * void) — forward this frame's own sret destination instead. */
    676   if (fi->has_sret) {
    677     u32 rsz = abi_cg_sizeof(p->c->abi, desc->ret.type);
    678     if (desc->ret.storage.kind == OPK_LOCAL ||
    679         desc->ret.storage.kind == OPK_GLOBAL ||
    680         desc->ret.storage.kind == OPK_INDIRECT) {
    681       u64 dst = op_addr(st, fr->fn, regs, fr->mem_off, &desc->ret.storage);
    682       fa.sret = interp_translate(p, dst, rsz, PERM_W);
    683     } else {
    684       fa.sret = fr->sret_ptr; /* tail call: deliver to our caller's sret slot */
    685     }
    686     if (!fa.sret) {
    687       unsupported(st, "sret destination");
    688       return 0;
    689     }
    690     fa.iargs[fa.nint++] = (u64)(uintptr_t)fa.sret;
    691     fa.ret_is_void = 1;
    692   }
    693 
    694   for (i = 0; i < desc->nargs; ++i) {
    695     OptCGABIValue* arg = &desc->args[i];
    696     const ABIArgInfo* ai = (i < fi->nparams) ? &fi->params[i] : NULL;
    697     if (ai && ai->kind == ABI_ARG_IGNORE) continue;
    698     if (ai && ai->kind == ABI_ARG_INDIRECT) {
    699       /* byval: pass a pointer to the aggregate (caller's copy). */
    700       u64 a = op_addr(st, fr->fn, regs, fr->mem_off, &arg->storage);
    701       u8* h = interp_translate(p, a, 1, PERM_R);
    702       if (fa.nint >= 8) {
    703         unsupported(st, "external call: too many int args");
    704         return 0;
    705       }
    706       fa.iargs[fa.nint++] = (u64)(uintptr_t)h;
    707       continue;
    708     }
    709     if (ai && ai->kind == ABI_ARG_DIRECT && ai->nparts > 1) {
    710       /* aggregate split across registers: read each part from memory. */
    711       u64 base = op_addr(st, fr->fn, regs, fr->mem_off, &arg->storage);
    712       u32 k;
    713       for (k = 0; k < ai->nparts; ++k) {
    714         const ABIArgPart* pt = &ai->parts[k];
    715         u64 chunk = mem_read(st, base + pt->src_offset, pt->size);
    716         int bad = (pt->cls == ABI_CLASS_FP)
    717                       ? ffi_push_fp(&fa, chunk, pt->size, &reason)
    718                       : ffi_push_int(&fa, chunk, &reason);
    719         if (bad) {
    720           unsupported(st, reason);
    721           return 0;
    722         }
    723       }
    724       continue;
    725     }
    726     /* scalar (or variadic extra arg): route by type. The named-parameter
    727      * aggregate/large cases are handled by the INDIRECT / multi-part branches
    728      * above; a variadic-tail arg has no ABI classification (ai==NULL), so an
    729      * aggregate or >8-byte scalar here can't be marshalled (and op_value's
    730      * 8-byte read would overflow) — diagnose rather than corrupt. */
    731     if (cg_type_is_aggregate(p->c, arg->type) ||
    732         abi_cg_sizeof(p->c->abi, arg->type) > 8u) {
    733       unsupported(st, "external call: aggregate/oversized variadic argument");
    734       return 0;
    735     }
    736     {
    737       ABITypeInfo ti = abi_cg_type_info(p->c->abi, arg->type);
    738       u64 v = op_value(st, fr->fn, regs, fr->mem_off, &arg->storage);
    739       int bad = (ti.scalar_kind == ABI_SC_FLOAT)
    740                     ? ffi_push_fp(&fa, v, ti.size ? ti.size : 8u, &reason)
    741                     : ffi_push_int(&fa, v, &reason);
    742       if (bad) {
    743         unsupported(st, reason);
    744         return 0;
    745       }
    746     }
    747   }
    748 
    749   /* Return classification from the ABI's own return descriptor (robust even
    750    * when desc->ret.type is void, e.g. a tail call whose result is not stored
    751    * into any caller local). A small struct can come back in up to two
    752    * registers; each part's class steers which return register the thunk reads.
    753    */
    754   if (!fi->has_sret) {
    755     if (fi->ret.kind == ABI_ARG_IGNORE || fi->ret.nparts == 0) {
    756       fa.ret_is_void = 1;
    757       fa.ret_nparts = 0;
    758     } else if (fi->ret.nparts > 2) {
    759       unsupported(st, "external call: 3+ register struct return");
    760       return 0;
    761     } else {
    762       u32 k;
    763       fa.ret_nparts = (u8)fi->ret.nparts;
    764       for (k = 0; k < fi->ret.nparts; ++k) {
    765         fa.ret_fp[k] = (fi->ret.parts[k].cls == ABI_CLASS_FP) ? 1u : 0u;
    766         fa.ret_size[k] = fi->ret.parts[k].size ? fi->ret.parts[k].size : 8u;
    767         /* A 4-byte fp return part is a single in the low half of an fp reg; the
    768          * two-register thunks read fp parts as doubles, so diagnose it. */
    769         if (fi->ret.nparts > 1u && fa.ret_fp[k] && fa.ret_size[k] == 4u) {
    770           unsupported(st, "external call: 32-bit fp struct-return field");
    771           return 0;
    772         }
    773       }
    774     }
    775   }
    776 
    777   {
    778     u64 out[2] = {0, 0};
    779     if (interp_ffi_invoke(host_fp, &fa, out, &reason) != 0) {
    780       unsupported(st, reason ? reason : "external call signature");
    781       return 0;
    782     }
    783     if (fa.ret_is_void || fa.ret_nparts == 0) return 0;
    784     /* Deliver the result. A register destination (OPK_REG) takes the low
    785      * register; a memory destination (an address-taken result local, or a small
    786      * aggregate returned in registers) receives each part's bytes scattered to
    787      * its src_offset. A value-less tail call has no home — the low register is
    788      * shuttled out as the scalar result. */
    789     if (desc->ret.storage.kind == OPK_REG) {
    790       if (fa.ret_nparts == 1) regs[desc->ret.storage.v.reg] = out[0];
    791     } else if (desc->ret.storage.kind == OPK_LOCAL ||
    792                desc->ret.storage.kind == OPK_GLOBAL ||
    793                desc->ret.storage.kind == OPK_INDIRECT) {
    794       u64 dst = op_addr(st, fr->fn, regs, fr->mem_off, &desc->ret.storage);
    795       u32 k;
    796       for (k = 0; k < fi->ret.nparts && k < 2u; ++k)
    797         mem_write(st, dst + fi->ret.parts[k].src_offset, fa.ret_size[k],
    798                   out[k]);
    799     }
    800     return out[0];
    801   }
    802 }
    803 
    804 /* ---- engine ---- */
    805 
    806 /* Dispatch mechanism. With labels-as-values (GNU computed goto) the engine is
    807  * direct-threaded: each InterpInsn caches the &&handler of its opcode and every
    808  * handler tail-dispatches straight to the next via `goto *`, giving the branch
    809  * predictor a distinct indirect branch per opcode site. This is the default
    810  * (KIT_INTERP_THREADED in <kit/config.h>). GCC, clang, and kit itself
    811  * (__kit__) all implement labels-as-values; any other compiler transparently
    812  * falls back to a portable `switch`, sharing one set of handler bodies through
    813  * OP()/NEXT()/GO(). Force the choice with -DKIT_INTERP_THREADED=0|1. */
    814 #if !defined(KIT_INTERP_THREADED)
    815 /* Belt-and-braces: config.h normally defines this. Default on so a missed
    816  * include degrades to threaded-where-supported, never a silent switch. */
    817 #define KIT_INTERP_THREADED 1
    818 #endif
    819 /* Effective dispatch: requested AND the compiler can compile labels-as-values.
    820  */
    821 #if KIT_INTERP_THREADED && \
    822     (defined(__GNUC__) || defined(__clang__) || defined(__kit__))
    823 #define INTERP_DISPATCH_THREADED 1
    824 #else
    825 #define INTERP_DISPATCH_THREADED 0
    826 #endif
    827 
    828 /* The opcode roster: one entry per InterpOp with a handler, used to publish the
    829  * threaded dispatch table from the in-function &&labels. Must stay in sync with
    830  * the OP(...) handlers below (a missing/extra entry is a compile error: an
    831  * undefined or unused label). */
    832 // clang-format off
    833 #define INTERP_OPS(X)    \
    834   X(IOP_NOP)             \
    835   X(IOP_LOAD_IMM)        \
    836   X(IOP_LOAD_CONST)      \
    837   X(IOP_COPY)            \
    838   X(IOP_COPY_AGG)        \
    839   X(IOP_LOAD)            \
    840   X(IOP_LOAD_AGG)        \
    841   X(IOP_STORE)           \
    842   X(IOP_STORE_AGG)       \
    843   X(IOP_ADDR_OF)         \
    844   X(IOP_TLS_ADDR)        \
    845   X(IOP_BINOP)           \
    846   X(IOP_UNOP)            \
    847   X(IOP_CMP)             \
    848   X(IOP_CONVERT)         \
    849   X(IOP_CALL)            \
    850   X(IOP_BR)              \
    851   X(IOP_CONDBR)          \
    852   X(IOP_CMP_BRANCH)      \
    853   X(IOP_SWITCH)          \
    854   X(IOP_INDIRECT_BR)     \
    855   X(IOP_LOAD_LABEL_ADDR) \
    856   X(IOP_RET)             \
    857   X(IOP_RET_VOID)        \
    858   X(IOP_ALLOCA)          \
    859   X(IOP_AGG_COPY)        \
    860   X(IOP_AGG_SET)         \
    861   X(IOP_BITFIELD_LOAD)   \
    862   X(IOP_BITFIELD_STORE)  \
    863   X(IOP_VA_START)        \
    864   X(IOP_VA_ARG)          \
    865   X(IOP_VA_END)          \
    866   X(IOP_VA_COPY)         \
    867   X(IOP_ATOMIC_LOAD)     \
    868   X(IOP_ATOMIC_STORE)    \
    869   X(IOP_ATOMIC_RMW)      \
    870   X(IOP_ATOMIC_CAS)      \
    871   X(IOP_FENCE)           \
    872   X(IOP_INTRINSIC)       \
    873   X(IOP_UNREACHABLE)     \
    874   X(IOP_TRAP)
    875 // clang-format on
    876 
    877 #if INTERP_DISPATCH_THREADED
    878 #define OP(name) L_##name
    879 /* linear op: re-check the memory-fault latch, advance, dispatch the next insn
    880  */
    881 #define NEXT()                         \
    882   do {                                 \
    883     if (st->mem_fault) goto fault_mem; \
    884     ++ip;                              \
    885     in = ip;                           \
    886     I = in->inst;                      \
    887     goto * in->handler;                \
    888   } while (0)
    889 /* branch op: ip already retargeted, dispatch without advancing */
    890 #define GO()            \
    891   do {                  \
    892     in = ip;            \
    893     I = in->inst;       \
    894     goto * in->handler; \
    895   } while (0)
    896 #if defined(__clang__)
    897 #pragma clang diagnostic push
    898 #pragma clang diagnostic ignored "-Wgnu-label-as-value"
    899 #pragma clang diagnostic ignored "-Wpedantic"
    900 #elif defined(__GNUC__)
    901 #pragma GCC diagnostic push
    902 #pragma GCC diagnostic ignored "-Wpedantic"
    903 #endif
    904 #else
    905 #define OP(name) case name
    906 #define NEXT() break
    907 #define GO() continue
    908 #endif
    909 
    910 KitInterpStatus interp_run_stack(InterpStack* st, int64_t* out_ret) {
    911   InterpProgram* p = st->prog;
    912   InterpFrame* fr;
    913   InterpFunc* fn;
    914   u64* regs;
    915   u32 mem_off;
    916   InterpInsn* ip;
    917   InterpInsn* in = NULL;
    918   const Inst* I = NULL;
    919 
    920   if (st->nframes == 0) {
    921     st->status = KIT_INTERP_DONE;
    922     if (out_ret) *out_ret = (int64_t)st->scalar_ret;
    923     return KIT_INTERP_DONE;
    924   }
    925 
    926 #if INTERP_DISPATCH_THREADED
    927   /* Per-function lazy threading: copy each opcode's handler into its record on
    928    * first entry to the function (RELOAD runs whenever the top frame changes).
    929    */
    930 #define RELOAD()                                               \
    931   do {                                                         \
    932     fr = &st->frames[st->nframes - 1u];                        \
    933     fn = fr->fn;                                               \
    934     regs = (u64*)(st->regs_arena + fr->regs_off);              \
    935     mem_off = fr->mem_off;                                     \
    936     ip = fr->ip;                                               \
    937     if (!fn->threaded) {                                       \
    938       u32 ti_;                                                 \
    939       for (ti_ = 0; ti_ < fn->ncode; ++ti_) {                  \
    940         u32 o_ = fn->code[ti_].op;                             \
    941         fn->code[ti_].handler =                                \
    942             st->dt[o_ < (u32)IOP__COUNT ? o_ : (u32)IOP_TRAP]; \
    943       }                                                        \
    944       fn->threaded = 1;                                        \
    945     }                                                          \
    946   } while (0)
    947 #else
    948 #define RELOAD()                                  \
    949   do {                                            \
    950     fr = &st->frames[st->nframes - 1u];           \
    951     fn = fr->fn;                                  \
    952     regs = (u64*)(st->regs_arena + fr->regs_off); \
    953     mem_off = fr->mem_off;                        \
    954     ip = fr->ip;                                  \
    955   } while (0)
    956 #endif
    957 
    958 #if INTERP_DISPATCH_THREADED
    959   if (!st->dt_ready) {
    960 #define DT_ENTRY(name) st->dt[name] = &&L_##name;
    961     INTERP_OPS(DT_ENTRY)
    962 #undef DT_ENTRY
    963     st->dt_ready = 1;
    964   }
    965 #endif
    966 
    967   RELOAD();
    968   if (!fn->ok) {
    969     unsupported(st, fn->reject_reason ? fn->reject_reason : "function");
    970     return (KitInterpStatus)st->status;
    971   }
    972   st->mem_fault = 0;
    973 
    974 #if INTERP_DISPATCH_THREADED
    975   in = ip;
    976   I = in->inst;
    977   goto * in->handler;
    978 #else
    979   for (;;) {
    980     in = ip;
    981     I = in->inst;
    982     switch ((InterpOp)in->op) {
    983 #endif
    984   OP(IOP_NOP) : NEXT();
    985   OP(IOP_LOAD_IMM)
    986       : write_dst(st, fn, regs, mem_off, &I->opnds[0], (u64)in->imm);
    987   NEXT();
    988   OP(IOP_LOAD_CONST) : {
    989     ConstBytes cb = I->extra.cbytes;
    990     u64 v = 0;
    991     u32 n = cb.size > 8u ? 8u : cb.size;
    992     if (cb.bytes && n) memcpy(&v, cb.bytes, n);
    993     write_dst(st, fn, regs, mem_off, &I->opnds[0], v);
    994     NEXT();
    995   }
    996   OP(IOP_COPY)
    997       : write_dst(st, fn, regs, mem_off, &I->opnds[0],
    998                   op_value(st, fn, regs, mem_off, &I->opnds[1]));
    999   NEXT();
   1000   OP(IOP_COPY_AGG) : {
   1001     u64 d = op_addr(st, fn, regs, mem_off, &I->opnds[0]);
   1002     u64 s = op_addr(st, fn, regs, mem_off, &I->opnds[1]);
   1003     mem_copy(st, d, s, abi_cg_sizeof(p->c->abi, I->opnds[0].type));
   1004     NEXT();
   1005   }
   1006   OP(IOP_LOAD) : {
   1007     u64 a = op_addr(st, fn, regs, mem_off, &I->opnds[1]);
   1008     write_dst(st, fn, regs, mem_off, &I->opnds[0],
   1009               mem_read(st, a, in->w0 ? in->w0 : 8u));
   1010     NEXT();
   1011   }
   1012   OP(IOP_LOAD_AGG) : {
   1013     u64 d = op_addr(st, fn, regs, mem_off, &I->opnds[0]);
   1014     u64 s = op_addr(st, fn, regs, mem_off, &I->opnds[1]);
   1015     mem_copy(st, d, s, abi_cg_sizeof(p->c->abi, I->opnds[0].type));
   1016     NEXT();
   1017   }
   1018   OP(IOP_STORE) : {
   1019     u64 a = op_addr(st, fn, regs, mem_off, &I->opnds[0]);
   1020     u64 v = op_value(st, fn, regs, mem_off, &I->opnds[1]);
   1021     mem_write(st, a, in->w0 ? in->w0 : 8u, v);
   1022     NEXT();
   1023   }
   1024   OP(IOP_STORE_AGG) : {
   1025     u64 d = op_addr(st, fn, regs, mem_off, &I->opnds[0]);
   1026     u64 s = op_addr(st, fn, regs, mem_off, &I->opnds[1]);
   1027     mem_copy(st, d, s, abi_cg_sizeof(p->c->abi, I->opnds[1].type));
   1028     NEXT();
   1029   }
   1030   OP(IOP_ADDR_OF)
   1031       : write_dst(st, fn, regs, mem_off, &I->opnds[0],
   1032                   op_addr(st, fn, regs, mem_off, &I->opnds[1]));
   1033   NEXT();
   1034   OP(IOP_BINOP) : {
   1035     u64 r = do_binop(st, in->sub, op_value(st, fn, regs, mem_off, &I->opnds[1]),
   1036                      op_value(st, fn, regs, mem_off, &I->opnds[2]), in->w0,
   1037                      in->fp0);
   1038     if (st->status) goto stop;
   1039     write_dst(st, fn, regs, mem_off, &I->opnds[0], r);
   1040     NEXT();
   1041   }
   1042   OP(IOP_UNOP) : {
   1043     u64 r = do_unop(st, in->sub, op_value(st, fn, regs, mem_off, &I->opnds[1]),
   1044                     in->w0, in->fp0);
   1045     if (st->status) goto stop;
   1046     write_dst(st, fn, regs, mem_off, &I->opnds[0], r);
   1047     NEXT();
   1048   }
   1049   OP(IOP_CMP) : {
   1050     u64 r =
   1051         (u64)do_cmp(st, in->sub, op_value(st, fn, regs, mem_off, &I->opnds[1]),
   1052                     op_value(st, fn, regs, mem_off, &I->opnds[2]), in->w0);
   1053     if (st->status) goto stop;
   1054     write_dst(st, fn, regs, mem_off, &I->opnds[0], r);
   1055     NEXT();
   1056   }
   1057   OP(IOP_CONVERT) : {
   1058     u64 r = do_convert(st, in, op_value(st, fn, regs, mem_off, &I->opnds[1]));
   1059     if (st->status) goto stop;
   1060     write_dst(st, fn, regs, mem_off, &I->opnds[0], r);
   1061     NEXT();
   1062   }
   1063   OP(IOP_ALLOCA) : {
   1064     u64 size = op_value(st, fn, regs, mem_off, &I->opnds[1]);
   1065     u32 align = in->imm ? (u32)in->imm : 16u;
   1066     u32 off = (fr->alloca_top + align - 1u) & ~(align - 1u);
   1067     if ((u64)fr->mem_off + off + size > st->mem_cap) {
   1068       fault(st, "alloca: stack overflow");
   1069       goto stop;
   1070     }
   1071     write_dst(st, fn, regs, mem_off, &I->opnds[0],
   1072               frame_base(st, fr->mem_off) + off);
   1073     fr->alloca_top = off + (u32)size;
   1074     /* Advance the global high-water so a nested call's frame is allocated
   1075      * ABOVE this live alloca region (otherwise it would alias it). */
   1076     if (fr->mem_off + fr->alloca_top > st->mem_top)
   1077       st->mem_top = fr->mem_off + fr->alloca_top;
   1078     NEXT();
   1079   }
   1080   OP(IOP_BR) : ip = &fn->code[in->t0];
   1081   GO();
   1082   OP(IOP_CONDBR) : {
   1083     u64 c = op_value(st, fn, regs, mem_off, &I->opnds[0]);
   1084     /* A faulting selector would otherwise branch on garbage: branch ops
   1085      * skip the straight-line fault re-check, so test the latch here. */
   1086     if (st->mem_fault) {
   1087       fault(st, "invalid memory access");
   1088       goto stop;
   1089     }
   1090     ip = &fn->code[c ? in->t0 : in->t1];
   1091     GO();
   1092   }
   1093   OP(IOP_CMP_BRANCH) : {
   1094     int taken = do_cmp(
   1095         st, in->sub, op_value(st, fn, regs, mem_off, &I->opnds[0]),
   1096         op_value(st, fn, regs, mem_off, &I->opnds[1]), in->w0 ? in->w0 : 8u);
   1097     if (st->status) goto stop;
   1098     if (st->mem_fault) {
   1099       fault(st, "invalid memory access");
   1100       goto stop;
   1101     }
   1102     ip = &fn->code[taken ? in->t0 : in->t1];
   1103     GO();
   1104   }
   1105   OP(IOP_SWITCH) : {
   1106     InterpSwitch* sw = &fn->switches[in->t0];
   1107     u64 sel = op_value(st, fn, regs, mem_off, &I->opnds[0]);
   1108     u32 ci;
   1109     u32 target = sw->default_pc;
   1110     u32 selw = (u32)abi_cg_sizeof(p->c->abi, sw->sel_type);
   1111     if (st->mem_fault) {
   1112       fault(st, "invalid memory access");
   1113       goto stop;
   1114     }
   1115     for (ci = 0; ci < sw->ncases; ++ci) {
   1116       if (mask_w(sel, selw) == mask_w(sw->aux->cases[ci].value, selw)) {
   1117         target = sw->case_pc[ci];
   1118         break; /* leaves the case-search loop, not the dispatch */
   1119       }
   1120     }
   1121     if (target == INTERP_PC_NONE) {
   1122       fault(st, "switch: no target");
   1123       goto stop;
   1124     }
   1125     ip = &fn->code[target];
   1126     GO();
   1127   }
   1128   OP(IOP_LOAD_LABEL_ADDR)
   1129       : /* encode target pc as the label address */
   1130         write_dst(st, fn, regs, mem_off, &I->opnds[0], (u64)in->t0);
   1131   NEXT();
   1132   OP(IOP_INDIRECT_BR) : {
   1133     u64 target = op_value(st, fn, regs, mem_off, &I->opnds[0]);
   1134     if (st->mem_fault) {
   1135       fault(st, "invalid memory access");
   1136       goto stop;
   1137     }
   1138     if (target >= fn->ncode) {
   1139       fault(st, "indirect branch out of range");
   1140       goto stop;
   1141     }
   1142     ip = &fn->code[target];
   1143     GO();
   1144   }
   1145   OP(IOP_CALL) : {
   1146     IRCallAux* aux = (IRCallAux*)I->extra.aux;
   1147     OptCGCallDesc* desc = &aux->desc;
   1148     InterpFunc* callee = NULL;
   1149     void* host_fp = NULL;
   1150     if (desc->callee.kind == OPK_GLOBAL) {
   1151       callee = interp_func_for_sym(p, desc->callee.v.global.sym);
   1152       if (callee && !callee->ok) {
   1153         /* A known internal callee we cannot interpret: propagate its
   1154          * reason rather than silently calling the native version (the
   1155          * --no-jit contract is that execution never falls back to JIT). */
   1156         unsupported(st,
   1157                     callee->reject_reason ? callee->reject_reason : "callee");
   1158         goto stop;
   1159       }
   1160       if (!callee) host_fp = interp_global_base(fn, desc->callee.v.global.sym);
   1161     } else if (desc->callee.kind == OPK_REG) {
   1162       host_fp = (void*)(uintptr_t)regs[desc->callee.v.reg];
   1163       /* If the function pointer targets a TU-internal function, interpret
   1164        * it (don't run its native code) so --no-jit truly never executes
   1165        * JITed code. External pointers fall through to the FFI path. */
   1166       callee = interp_func_for_addr(p, host_fp);
   1167       if (callee && !callee->ok) {
   1168         unsupported(st,
   1169                     callee->reject_reason ? callee->reject_reason : "callee");
   1170         goto stop;
   1171       }
   1172     }
   1173     if (callee) {
   1174       /* internal call: push a frame and bind args. */
   1175       u32 caller_idx = st->nframes - 1u;
   1176       u32 callee_idx;
   1177       if (!in->tail) fr->ip = ip + 1; /* resume after a non-tail call */
   1178       callee_idx = frame_push(st, callee);
   1179       if (callee_idx == 0xffffffffu) {
   1180         fault(st, "call: stack overflow");
   1181         goto stop;
   1182       }
   1183       bind_args(st, caller_idx, callee_idx, desc);
   1184       if (!build_varargs(st, caller_idx, callee_idx, desc)) {
   1185         fault(st, "call: stack overflow");
   1186         goto stop;
   1187       }
   1188       if (st->mem_fault) {
   1189         fault(st, "invalid memory access");
   1190         goto stop;
   1191       }
   1192       {
   1193         InterpFrame* cf = &st->frames[callee_idx];
   1194         InterpFrame* caller = &st->frames[caller_idx];
   1195         if (in->tail) {
   1196           /* True O(1) tail call: the callee's result IS this function's
   1197            * result, so inherit the tail-caller's return target and relocate
   1198            * the freshly-built callee frame down onto the (now dead) caller's
   1199            * register/memory region, rewinding the arenas. A tail loop then
   1200            * runs in constant interp+host stack space instead of growing the
   1201            * fixed reservation each iteration.
   1202            *
   1203            * Safe because the callee has not executed yet: no absolute
   1204            * pointers into its own frame exist (va_start runs later; an arg
   1205            * holding &caller_local would be UB, the caller being about to
   1206            * return). bind_args/build_varargs already copied every argument
   1207            * value out of the caller, so overwriting the caller is fine. */
   1208           u32 dst_regs = caller->regs_off;
   1209           u32 dst_mem = caller->mem_off;
   1210           u32 nregs_bytes = (callee->npregs ? callee->npregs : 1u) * 8u;
   1211           u32 mem_used = cf->alloca_top; /* static frame + vararg buffer */
   1212           cf->ret_wanted = caller->ret_wanted;
   1213           cf->ret_dst = caller->ret_dst;
   1214           cf->sret_ptr = caller->sret_ptr;
   1215           if (cf->regs_off != dst_regs)
   1216             memmove(st->regs_arena + dst_regs, st->regs_arena + cf->regs_off,
   1217                     nregs_bytes);
   1218           if (cf->mem_off != dst_mem) {
   1219             memmove(st->mem_arena + dst_mem, st->mem_arena + cf->mem_off,
   1220                     mem_used);
   1221             if (cf->has_varargs) cf->vararg_off -= (cf->mem_off - dst_mem);
   1222           }
   1223           cf->regs_off = dst_regs;
   1224           cf->mem_off = dst_mem;
   1225           *caller = *cf;
   1226           st->nframes = caller_idx + 1u;
   1227           st->regs_top = dst_regs + nregs_bytes;
   1228           st->mem_top = dst_mem + mem_used;
   1229         } else if (desc->ret.storage.kind == OPK_REG) {
   1230           cf->ret_wanted = 1;
   1231           cf->ret_dst = desc->ret.storage.v.reg;
   1232         } else if (desc->ret.storage.kind == OPK_LOCAL) {
   1233           /* aggregate return: callee writes into the caller's slot */
   1234           u64 a = frame_base(st, caller->mem_off) +
   1235                   caller->fn->slot_off[desc->ret.storage.v.frame_slot];
   1236           cf->sret_ptr = interp_translate(p, a, 1, PERM_W);
   1237         }
   1238       }
   1239       RELOAD();
   1240       GO();
   1241     }
   1242     if (!host_fp) {
   1243       unsupported(st, "unresolved call target");
   1244       goto stop;
   1245     }
   1246     {
   1247       u64 callret = ext_call(st, fr, regs, host_fp, desc);
   1248       if (st->status) goto stop;
   1249       if (in->tail) {
   1250         /* External tail call: the call's result is this function's
   1251          * result (desc.ret.storage may be empty for a tail call). */
   1252         u64 rv = callret;
   1253         u8 want = fr->ret_wanted;
   1254         u32 rdst = fr->ret_dst;
   1255         st->regs_top = fr->regs_off;
   1256         st->mem_top = fr->mem_off;
   1257         st->nframes--;
   1258         st->scalar_ret = rv;
   1259         if (st->nframes == 0) {
   1260           st->status = KIT_INTERP_DONE;
   1261           if (out_ret) *out_ret = (int64_t)rv;
   1262           return KIT_INTERP_DONE;
   1263         }
   1264         if (want) {
   1265           InterpFrame* caller = &st->frames[st->nframes - 1u];
   1266           u64* cregs = (u64*)(st->regs_arena + caller->regs_off);
   1267           cregs[rdst] = rv;
   1268         }
   1269         RELOAD();
   1270         GO();
   1271       }
   1272     }
   1273     NEXT();
   1274   }
   1275   OP(IOP_RET) : OP(IOP_RET_VOID) : {
   1276     u8 is_fp = 0;
   1277     u64 rv = 0;
   1278     u8* sret = fr->sret_ptr;
   1279     if (in->op == IOP_RET) {
   1280       IRRetAux* aux = (IRRetAux*)I->extra.aux;
   1281       OptCGABIValue* val = &aux->val;
   1282       if (cg_type_is_aggregate(p->c, val->type) ||
   1283           abi_cg_sizeof(p->c->abi, val->type) > 8u) {
   1284         if (sret) {
   1285           u64 src = op_addr(st, fn, regs, mem_off, &val->storage);
   1286           u8* s = interp_translate(p, src, abi_cg_sizeof(p->c->abi, val->type),
   1287                                    PERM_R);
   1288           if (s) memcpy(sret, s, abi_cg_sizeof(p->c->abi, val->type));
   1289         }
   1290       } else {
   1291         ABITypeInfo ti = abi_cg_type_info(p->c->abi, val->type);
   1292         u32 sz = abi_cg_sizeof(p->c->abi, val->type);
   1293         rv = op_value(st, fn, regs, mem_off, &val->storage);
   1294         is_fp = (ti.scalar_kind == ABI_SC_FLOAT) ? 1u : 0u;
   1295         /* A scalar result whose caller destination is a memory slot (an
   1296          * address-taken result local) is delivered via sret_ptr, not a
   1297          * register — write it there. */
   1298         if (sret) memcpy(sret, &rv, sz ? (sz > 8u ? 8u : sz) : 8u);
   1299       }
   1300     }
   1301     /* The popped (callee) frame records where its scalar result lands in
   1302      * the caller — capture before popping, then rewind the arenas to the
   1303      * frame's bases (strict stack discipline). */
   1304     {
   1305       u8 want = fr->ret_wanted;
   1306       u32 dst = fr->ret_dst;
   1307       st->regs_top = fr->regs_off;
   1308       st->mem_top = fr->mem_off;
   1309       st->nframes--;
   1310       st->scalar_ret = rv;
   1311       st->ret_is_fp = is_fp;
   1312       if (st->nframes == 0) {
   1313         st->status = KIT_INTERP_DONE;
   1314         if (out_ret) *out_ret = (int64_t)rv;
   1315         return KIT_INTERP_DONE;
   1316       }
   1317       if (want) {
   1318         InterpFrame* caller = &st->frames[st->nframes - 1u];
   1319         u64* cregs = (u64*)(st->regs_arena + caller->regs_off);
   1320         cregs[dst] = rv;
   1321       }
   1322     }
   1323     RELOAD();
   1324     GO();
   1325   }
   1326   OP(IOP_INTRINSIC) : {
   1327     if (!interp_intrinsic(st, fn, regs, mem_off, in)) goto stop;
   1328     NEXT();
   1329   }
   1330   OP(IOP_FENCE) : NEXT(); /* single-thread: no-op */
   1331   OP(IOP_AGG_SET) : OP(IOP_AGG_COPY) : {
   1332     /* AGG_COPY/SET use pointer-deref addressing (pointer_addr_from_operand):
   1333      * a LOCAL holding a pointer is dereferenced; otherwise it is the slot. */
   1334     u64 d = interp_ptr_addr(st, fn, regs, mem_off, &I->opnds[0]);
   1335     if (in->op == IOP_AGG_COPY) {
   1336       IRAggAux* aux = (IRAggAux*)I->extra.aux;
   1337       u64 s = interp_ptr_addr(st, fn, regs, mem_off, &I->opnds[1]);
   1338       mem_copy(st, d, s, aux ? aux->access.size : 0u);
   1339     } else {
   1340       IRAggAux* aux = (IRAggAux*)I->extra.aux;
   1341       u64 byte = op_value(st, fn, regs, mem_off, &I->opnds[1]);
   1342       u32 n = aux ? aux->access.size : 0u;
   1343       u8* h = interp_translate(p, d, n, PERM_W);
   1344       if (h)
   1345         memset(h, (int)(byte & 0xffu), n);
   1346       else
   1347         st->mem_fault = 1;
   1348     }
   1349     NEXT();
   1350   }
   1351   OP(IOP_TLS_ADDR) : {
   1352     /* A thread-local's symbol does not resolve to its storage on every
   1353      * target (a Mach-O symbol resolves to a TLV descriptor), so route
   1354      * through interp_tls_addr / the host resolve_tls hook, which returns the
   1355      * running thread's address of the variable (already +addend). */
   1356     IRTlsAux* aux = (IRTlsAux*)I->extra.aux;
   1357     void* addr = aux ? interp_tls_addr(fn, aux->sym, aux->addend) : NULL;
   1358     if (!addr) {
   1359       unsupported(st, "unresolved thread-local symbol");
   1360       goto stop;
   1361     }
   1362     write_dst(st, fn, regs, mem_off, &I->opnds[0], (u64)(uintptr_t)addr);
   1363     NEXT();
   1364   }
   1365   OP(IOP_BITFIELD_LOAD) : {
   1366     /* opnds[1] is the record address; the field bits live in the storage
   1367      * unit at record + storage_offset. Extract by shift+mask (target uses
   1368      * little-endian bit numbering), sign-extending signed fields. */
   1369     IRBitFieldAux* aux = (IRBitFieldAux*)I->extra.aux;
   1370     u64 rec, raw, v = 0;
   1371     u32 ssz, width;
   1372     if (!aux) {
   1373       unsupported(st, "bitfield access");
   1374       goto stop;
   1375     }
   1376     rec = op_addr(st, fn, regs, mem_off, &I->opnds[1]);
   1377     ssz = aux->access.storage.size ? aux->access.storage.size : 4u;
   1378     width = aux->access.bit_width;
   1379     if (width) {
   1380       raw = mem_read(st, rec + aux->access.storage_offset, ssz);
   1381       v = (raw >> aux->access.bit_offset) & bits_mask(width);
   1382       if (aux->access.signed_ && width < 64u && (v & (1ull << (width - 1u))))
   1383         v |= ~bits_mask(width);
   1384     }
   1385     write_dst(st, fn, regs, mem_off, &I->opnds[0], v);
   1386     NEXT();
   1387   }
   1388   OP(IOP_BITFIELD_STORE) : {
   1389     /* opnds[0] = record address, opnds[1] = source value. Read-modify-write
   1390      * the storage unit: clear the field bits, then OR in the masked, shifted
   1391      * source. A zero-width field is a layout barrier — no store. */
   1392     IRBitFieldAux* aux = (IRBitFieldAux*)I->extra.aux;
   1393     u64 rec, addr, ones, fmask, src, raw;
   1394     u32 ssz, width;
   1395     if (!aux) {
   1396       unsupported(st, "bitfield access");
   1397       goto stop;
   1398     }
   1399     width = aux->access.bit_width;
   1400     if (width == 0) NEXT();
   1401     rec = op_addr(st, fn, regs, mem_off, &I->opnds[0]);
   1402     ssz = aux->access.storage.size ? aux->access.storage.size : 4u;
   1403     addr = rec + aux->access.storage_offset;
   1404     ones = bits_mask(width);
   1405     fmask = ones << aux->access.bit_offset;
   1406     src = op_value(st, fn, regs, mem_off, &I->opnds[1]);
   1407     raw = mem_read(st, addr, ssz);
   1408     raw = (raw & ~fmask) | ((src & ones) << aux->access.bit_offset);
   1409     mem_write(st, addr, ssz, raw);
   1410     NEXT();
   1411   }
   1412   OP(IOP_VA_START) : {
   1413     /* opnds[0] is the va_list object's address (a pointer value). Seed it
   1414      * with a cursor over this frame's anonymous-argument buffer. */
   1415     u64 ap = op_value(st, fn, regs, mem_off, &I->opnds[0]);
   1416     u64 cursor = fr->has_varargs ? frame_base(st, fr->vararg_off) : 0u;
   1417     mem_write(st, ap, 8u, cursor);
   1418     NEXT();
   1419   }
   1420   OP(IOP_VA_END) : NEXT(); /* nothing to release in the cursor model */
   1421   OP(IOP_VA_COPY) : {
   1422     /* opnds = [dst va_list addr, src va_list addr]: duplicate the cursor. */
   1423     u64 d = op_value(st, fn, regs, mem_off, &I->opnds[0]);
   1424     u64 s = op_value(st, fn, regs, mem_off, &I->opnds[1]);
   1425     mem_write(st, d, 8u, mem_read(st, s, 8u));
   1426     NEXT();
   1427   }
   1428   OP(IOP_VA_ARG) : {
   1429     /* opnds[0] = dst (type drives the read width), opnds[1] = va_list addr.
   1430      * Align the cursor, read the slot, advance, store the cursor back. */
   1431     KitCgTypeId ty = I->opnds[0].type;
   1432     u64 ap = op_value(st, fn, regs, mem_off, &I->opnds[1]);
   1433     u64 cursor = mem_read(st, ap, 8u);
   1434     u32 size = abi_cg_sizeof(p->c->abi, ty);
   1435     u32 al = va_align_of(size);
   1436     cursor = (cursor + al - 1u) & ~((u64)al - 1u);
   1437     if (cg_type_is_aggregate(p->c, ty) || size > 8u) {
   1438       u64 dstaddr = op_addr(st, fn, regs, mem_off, &I->opnds[0]);
   1439       mem_copy(st, dstaddr, cursor, size);
   1440     } else {
   1441       write_dst(st, fn, regs, mem_off, &I->opnds[0],
   1442                 mem_read(st, cursor, size ? size : 8u));
   1443     }
   1444     mem_write(st, ap, 8u, cursor + va_stride_of(size));
   1445     NEXT();
   1446   }
   1447   /* Atomics: single-threaded interpreter, so the operation is serialized
   1448    * and the memory order is irrelevant (treated as seq-cst). */
   1449   OP(IOP_ATOMIC_LOAD) : {
   1450     u64 a = op_value(st, fn, regs, mem_off, &I->opnds[1]);
   1451     write_dst(st, fn, regs, mem_off, &I->opnds[0],
   1452               mem_read(st, a, in->w0 ? in->w0 : 8u));
   1453     NEXT();
   1454   }
   1455   OP(IOP_ATOMIC_STORE) : {
   1456     u64 a = op_value(st, fn, regs, mem_off, &I->opnds[0]);
   1457     mem_write(st, a, in->w0 ? in->w0 : 8u,
   1458               op_value(st, fn, regs, mem_off, &I->opnds[1]));
   1459     NEXT();
   1460   }
   1461   OP(IOP_ATOMIC_RMW) : {
   1462     u32 w = in->w0 ? in->w0 : 8u;
   1463     u64 a = op_value(st, fn, regs, mem_off, &I->opnds[1]);
   1464     u64 old = mem_read(st, a, w);
   1465     u64 v = op_value(st, fn, regs, mem_off, &I->opnds[2]);
   1466     mem_write(st, a, w, do_rmw(in->sub, old, v, w));
   1467     write_dst(st, fn, regs, mem_off, &I->opnds[0], old);
   1468     NEXT();
   1469   }
   1470   OP(IOP_ATOMIC_CAS) : {
   1471     u32 w = in->w0 ? in->w0 : 8u;
   1472     u64 a = op_value(st, fn, regs, mem_off, &I->opnds[2]);
   1473     u64 expected = op_value(st, fn, regs, mem_off, &I->opnds[3]);
   1474     u64 desired = op_value(st, fn, regs, mem_off, &I->opnds[4]);
   1475     u64 old = mem_read(st, a, w);
   1476     u64 ok = (mask_w(old, w) == mask_w(expected, w));
   1477     if (ok) mem_write(st, a, w, desired);
   1478     write_dst(st, fn, regs, mem_off, &I->opnds[0], old); /* prior */
   1479     write_dst(st, fn, regs, mem_off, &I->opnds[1], ok);  /* ok flag */
   1480     NEXT();
   1481   }
   1482   OP(IOP_UNREACHABLE) : fault(st, "unreachable");
   1483   goto stop;
   1484   OP(IOP_TRAP)
   1485       : unsupported(st, fn->reject_reason ? fn->reject_reason : "operation");
   1486   goto stop;
   1487 #if !INTERP_DISPATCH_THREADED
   1488   default:
   1489     unsupported(st, "opcode");
   1490     goto stop;
   1491 }
   1492 if (st->mem_fault) {
   1493   fault(st, "invalid memory access");
   1494   goto stop;
   1495 }
   1496 ip++;
   1497 }
   1498 #else
   1499     fault_mem:
   1500       fault(st, "invalid memory access");
   1501       /* fall through to stop */
   1502 #endif
   1503 
   1504 stop: fr->ip = ip;
   1505 return (KitInterpStatus)st->status;
   1506 #undef RELOAD
   1507 }
   1508 #if INTERP_DISPATCH_THREADED
   1509 #if defined(__clang__)
   1510 #pragma clang diagnostic pop
   1511 #elif defined(__GNUC__)
   1512 #pragma GCC diagnostic pop
   1513 #endif
   1514 #endif
   1515 
   1516 /* ---- intrinsics ---- */
   1517 
   1518 static u64 ipopcount(u64 v, u32 w) {
   1519   u64 m = (w >= 8) ? ~0ull : ((1ull << (w * 8u)) - 1ull);
   1520   u64 x = v & m;
   1521   u64 n = 0;
   1522   while (x) {
   1523     n += (x & 1u);
   1524     x >>= 1;
   1525   }
   1526   return n;
   1527 }
   1528 static u64 ictz(u64 v, u32 w) {
   1529   u32 bits = w * 8u;
   1530   u64 n = 0;
   1531   if ((v & ((bits >= 64) ? ~0ull : ((1ull << bits) - 1ull))) == 0) return bits;
   1532   while (!(v & 1u)) {
   1533     n++;
   1534     v >>= 1;
   1535   }
   1536   return n;
   1537 }
   1538 static u64 iclz(u64 v, u32 w) {
   1539   u32 bits = w * 8u;
   1540   u64 n = 0;
   1541   u64 top = 1ull << (bits - 1u);
   1542   v &= (bits >= 64) ? ~0ull : ((1ull << bits) - 1ull);
   1543   if (v == 0) return bits;
   1544   while (!(v & top)) {
   1545     n++;
   1546     v <<= 1;
   1547   }
   1548   return n;
   1549 }
   1550 static u64 ibswap(u64 v, u32 nbytes) {
   1551   u64 r = 0;
   1552   u32 i;
   1553   for (i = 0; i < nbytes; ++i) {
   1554     r = (r << 8) | (v & 0xffu);
   1555     v >>= 8;
   1556   }
   1557   return r;
   1558 }
   1559 
   1560 static u64 imul_high_u64(u64 a, u64 b) {
   1561   u64 a0 = (u32)a, a1 = a >> 32;
   1562   u64 b0 = (u32)b, b1 = b >> 32;
   1563   u64 p00 = a0 * b0;
   1564   u64 p01 = a0 * b1;
   1565   u64 p10 = a1 * b0;
   1566   u64 p11 = a1 * b1;
   1567   u64 middle = (p00 >> 32) + (u32)p01 + (u32)p10;
   1568   return p11 + (p01 >> 32) + (p10 >> 32) + (middle >> 32);
   1569 }
   1570 
   1571 static int interp_intrinsic(InterpStack* st, InterpFunc* fn, u64* regs,
   1572                             u32 mem_off, InterpInsn* in) {
   1573   InterpProgram* p = st->prog;
   1574   IRIntrinAux* aux = (IRIntrinAux*)in->inst->extra.aux;
   1575   Compiler* c = p->c;
   1576   if (!aux) {
   1577     unsupported(st, "intrinsic");
   1578     return 0;
   1579   }
   1580 #define ARGV(i) op_value(st, fn, regs, mem_off, &aux->args[i])
   1581 #define AWID(i) ((u32)abi_cg_sizeof(c->abi, aux->args[i].type))
   1582 #define DWID(i) ((u32)abi_cg_sizeof(c->abi, aux->dsts[i].type))
   1583 #define DST0 \
   1584   (aux->ndst > 0 && aux->dsts[0].kind == OPK_REG ? aux->dsts[0].v.reg : 0u)
   1585   switch (aux->kind) {
   1586     case INTRIN_MEMMOVE: {
   1587       u64 d = ARGV(0), s = ARGV(1), n = ARGV(2);
   1588       mem_copy(st, d, s, (u32)n);
   1589       if (aux->ndst > 0 && aux->dsts[0].kind == OPK_REG) regs[DST0] = d;
   1590       return 1;
   1591     }
   1592     case INTRIN_POPCOUNT:
   1593       regs[DST0] = ipopcount(ARGV(0), AWID(0));
   1594       return 1;
   1595     case INTRIN_CTZ:
   1596       regs[DST0] = ictz(ARGV(0), AWID(0));
   1597       return 1;
   1598     case INTRIN_CLZ:
   1599       regs[DST0] = iclz(ARGV(0), AWID(0));
   1600       return 1;
   1601     case INTRIN_BSWAP:
   1602       regs[DST0] = ibswap(ARGV(0), DWID(0));
   1603       return 1;
   1604     case INTRIN_SMUL_HIGH:
   1605     case INTRIN_UMUL_HIGH: {
   1606       u32 w = AWID(0);
   1607       u64 a = mask_w(ARGV(0), w);
   1608       u64 b = mask_w(ARGV(1), w);
   1609       u64 hi;
   1610       if (w < 8u) {
   1611         if (aux->kind == INTRIN_SMUL_HIGH)
   1612           hi = (u64)((i64)sext_w(a, w) * (i64)sext_w(b, w)) >> (w * 8u);
   1613         else
   1614           hi = (a * b) >> (w * 8u);
   1615       } else {
   1616         hi = imul_high_u64(a, b);
   1617         if (aux->kind == INTRIN_SMUL_HIGH) {
   1618           if ((i64)a < 0) hi -= b;
   1619           if ((i64)b < 0) hi -= a;
   1620         }
   1621       }
   1622       regs[DST0] = mask_w(hi, w);
   1623       return 1;
   1624     }
   1625     case INTRIN_EXPECT:
   1626       regs[DST0] = ARGV(0);
   1627       return 1;
   1628     case INTRIN_ASSUME_ALIGNED:
   1629       if (aux->ndst > 0 && aux->dsts[0].kind == OPK_REG) regs[DST0] = ARGV(0);
   1630       return 1;
   1631     case INTRIN_PREFETCH:
   1632       return 1;
   1633     /* CPU hints and memory barriers have no observable effect in the
   1634      * single-threaded interpreter model: treat them as no-ops. */
   1635     case INTRIN_CPU_NOP:
   1636     case INTRIN_CPU_YIELD:
   1637     case INTRIN_ISB:
   1638     case INTRIN_DMB:
   1639     case INTRIN_DSB:
   1640       return 1;
   1641     case INTRIN_TRAP:
   1642       fault(st, "__builtin_trap");
   1643       return 0;
   1644     case INTRIN_SADD_OVERFLOW:
   1645     case INTRIN_UADD_OVERFLOW:
   1646     case INTRIN_SSUB_OVERFLOW:
   1647     case INTRIN_USUB_OVERFLOW:
   1648     case INTRIN_SMUL_OVERFLOW:
   1649     case INTRIN_UMUL_OVERFLOW: {
   1650       u32 w = AWID(0);
   1651       u64 a = ARGV(0), b = ARGV(1);
   1652       u64 res = 0;
   1653       int ovf = 0;
   1654       switch (aux->kind) {
   1655         /* For w<8 the operands fit in i64/u64 so the exact result is available
   1656          * and a re-narrow comparison detects overflow; for w==8 there is no
   1657          * wider type, so detect via sign/carry logic (the re-narrow trick would
   1658          * always read "no overflow"). */
   1659         case INTRIN_SADD_OVERFLOW: {
   1660           i64 x = sext_w(a, w), y = sext_w(b, w);
   1661           u64 r = (u64)x + (u64)y;
   1662           res = mask_w(r, w);
   1663           ovf = (w < 8) ? (sext_w(res, w) != x + y)
   1664                         : (int)((((u64)x ^ r) & ((u64)y ^ r)) >> 63);
   1665           break;
   1666         }
   1667         case INTRIN_UADD_OVERFLOW: {
   1668           u64 x = mask_w(a, w), y = mask_w(b, w), r = x + y;
   1669           res = mask_w(r, w);
   1670           ovf = (res != r) || (mask_w(r, w) < x);
   1671           break;
   1672         }
   1673         case INTRIN_SSUB_OVERFLOW: {
   1674           i64 x = sext_w(a, w), y = sext_w(b, w);
   1675           u64 r = (u64)x - (u64)y;
   1676           res = mask_w(r, w);
   1677           ovf = (w < 8) ? (sext_w(res, w) != x - y)
   1678                         : (int)((((u64)x ^ (u64)y) & ((u64)x ^ r)) >> 63);
   1679           break;
   1680         }
   1681         case INTRIN_USUB_OVERFLOW: {
   1682           ovf = mask_w(a, w) < mask_w(b, w);
   1683           res = mask_w(mask_w(a, w) - mask_w(b, w), w);
   1684           break;
   1685         }
   1686         case INTRIN_SMUL_OVERFLOW: {
   1687           i64 x = sext_w(a, w), y = sext_w(b, w);
   1688           u64 r = (u64)x * (u64)y;
   1689           res = mask_w(r, w);
   1690           if (w < 8) {
   1691             ovf = (sext_w(res, w) != x * y);
   1692           } else if (x == 0 || y == 0) {
   1693             ovf = 0;
   1694           } else if ((x == -1 && (u64)y == 0x8000000000000000ull) ||
   1695                      (y == -1 && (u64)x == 0x8000000000000000ull)) {
   1696             ovf = 1; /* INT64_MIN * -1 */
   1697           } else {
   1698             ovf = ((i64)r / x != y);
   1699           }
   1700           break;
   1701         }
   1702         case INTRIN_UMUL_OVERFLOW: {
   1703           u64 x = mask_w(a, w), y = mask_w(b, w), r = x * y;
   1704           res = mask_w(r, w);
   1705           ovf = (w < 8) ? (r != res) : (x != 0 && r / x != y);
   1706           break;
   1707         }
   1708         default:
   1709           break;
   1710       }
   1711       if (aux->ndst > 0 && aux->dsts[0].kind == OPK_REG)
   1712         regs[aux->dsts[0].v.reg] = res;
   1713       if (aux->ndst > 1 && aux->dsts[1].kind == OPK_REG)
   1714         regs[aux->dsts[1].v.reg] = (u64)ovf;
   1715       return 1;
   1716     }
   1717     default:
   1718       unsupported(st, "intrinsic");
   1719       return 0;
   1720   }
   1721 #undef ARGV
   1722 #undef AWID
   1723 #undef DWID
   1724 #undef DST0
   1725 }
   1726 
   1727 /* ---- public stack API ---- */
   1728 
   1729 KitInterpStack* kit_interp_stack_new(KitInterpProgram* pp) {
   1730   InterpProgram* p = (InterpProgram*)pp;
   1731   Heap* h;
   1732   InterpStack* st;
   1733   if (!p) return NULL;
   1734   h = p->c->ctx->heap;
   1735   st = (InterpStack*)h->alloc(h, sizeof(*st), _Alignof(InterpStack));
   1736   if (!st) return NULL;
   1737   memset(st, 0, sizeof *st);
   1738   st->prog = p;
   1739   /* Fixed, non-relocating arenas (see bump()/INTERP_*_RESERVE). */
   1740   st->regs_arena = (u8*)h->alloc(h, INTERP_REGS_RESERVE, 16u);
   1741   st->mem_arena = (u8*)h->alloc(h, INTERP_MEM_RESERVE, 16u);
   1742   if (!st->regs_arena || !st->mem_arena) {
   1743     if (st->regs_arena) h->free(h, st->regs_arena, INTERP_REGS_RESERVE);
   1744     if (st->mem_arena) h->free(h, st->mem_arena, INTERP_MEM_RESERVE);
   1745     h->free(h, st, sizeof *st);
   1746     return NULL;
   1747   }
   1748   st->regs_cap = INTERP_REGS_RESERVE;
   1749   st->mem_cap = INTERP_MEM_RESERVE;
   1750   return (KitInterpStack*)st;
   1751 }
   1752 
   1753 void kit_interp_stack_free(KitInterpStack* s) {
   1754   InterpStack* st = (InterpStack*)s;
   1755   Heap* h;
   1756   if (!st) return;
   1757   h = st->prog->c->ctx->heap;
   1758   if (st->frames) h->free(h, st->frames, sizeof(InterpFrame) * st->frames_cap);
   1759   if (st->regs_arena) h->free(h, st->regs_arena, st->regs_cap);
   1760   if (st->mem_arena) h->free(h, st->mem_arena, st->mem_cap);
   1761   h->free(h, st, sizeof *st);
   1762 }
   1763 
   1764 static void bind_entry_param(InterpStack* st, InterpFunc* fn, u32 idx, u32 i,
   1765                              u64 value) {
   1766   InterpFrame* fr = &st->frames[idx];
   1767   IRParam* pr;
   1768   if (i >= fn->f->nparams) return;
   1769   pr = &fn->f->params[i];
   1770   if (pr->storage.kind == CG_LOCAL_STORAGE_REG) {
   1771     u64* regs = (u64*)(st->regs_arena + fr->regs_off);
   1772     regs[pr->storage.v.reg] = value;
   1773   } else {
   1774     u64 dst =
   1775         frame_base(st, fr->mem_off) + fn->slot_off[pr->storage.v.frame_slot];
   1776     mem_write(st, dst, 8u, value);
   1777   }
   1778 }
   1779 
   1780 KitStatus kit_interp_call_on(KitInterpStack* s, KitInterpFunc* ff, int argc,
   1781                              char** argv) {
   1782   InterpStack* st = (InterpStack*)s;
   1783   InterpFunc* fn = (InterpFunc*)ff;
   1784   u32 idx;
   1785   if (!st || !fn) return KIT_INVALID;
   1786   idx = frame_push(st, fn);
   1787   if (idx == 0xffffffffu) return KIT_NOMEM;
   1788   bind_entry_param(st, fn, idx, 0u, (u64)(unsigned)argc);
   1789   bind_entry_param(st, fn, idx, 1u, (u64)(uintptr_t)argv);
   1790   return KIT_OK;
   1791 }
   1792 
   1793 KitInterpStatus kit_interp_resume(KitInterpStack* s, int64_t* out_ret) {
   1794   InterpStack* st = (InterpStack*)s;
   1795   if (!st) return KIT_INTERP_ERROR;
   1796   return interp_run_stack(st, out_ret);
   1797 }
   1798 
   1799 KitInterpStatus kit_interp_call(KitInterpProgram* pp, KitInterpFunc* ff,
   1800                                 int argc, char** argv, int64_t* out_ret) {
   1801   KitInterpStack* s = kit_interp_stack_new(pp);
   1802   KitInterpStatus rc;
   1803   if (!s) return KIT_INTERP_ERROR;
   1804   if (kit_interp_call_on(s, ff, argc, argv) != KIT_OK) {
   1805     kit_interp_stack_free(s);
   1806     return KIT_INTERP_ERROR;
   1807   }
   1808   rc = kit_interp_resume(s, out_ret);
   1809   kit_interp_stack_free(s);
   1810   return rc;
   1811 }
   1812 
   1813 KitInterpStatus kit_interp_call_args(KitInterpProgram* pp, KitInterpFunc* ff,
   1814                                      const uint64_t* args, uint32_t nargs,
   1815                                      int64_t* out_ret) {
   1816   InterpStack* st = (InterpStack*)kit_interp_stack_new(pp);
   1817   InterpFunc* fn = (InterpFunc*)ff;
   1818   KitInterpStatus rc;
   1819   u32 idx, i;
   1820   if (!st) return KIT_INTERP_ERROR;
   1821   if (!fn) {
   1822     kit_interp_stack_free((KitInterpStack*)st);
   1823     return KIT_INTERP_ERROR;
   1824   }
   1825   idx = frame_push(st, fn);
   1826   if (idx == 0xffffffffu) {
   1827     kit_interp_stack_free((KitInterpStack*)st);
   1828     return KIT_INTERP_ERROR;
   1829   }
   1830   for (i = 0; i < nargs; ++i) bind_entry_param(st, fn, idx, i, args[i]);
   1831   rc = interp_run_stack(st, out_ret);
   1832   kit_interp_stack_free((KitInterpStack*)st);
   1833   return rc;
   1834 }
   1835 
   1836 KitStatus kit_interp_stack_reset(KitInterpStack* s) {
   1837   InterpStack* st = (InterpStack*)s;
   1838   if (!st) return KIT_INVALID;
   1839   /* Keep the (fixed, non-relocating) arenas; rewind their bump tops and drop
   1840    * all frames + the return shuttle + any prior status/trap. */
   1841   st->nframes = 0;
   1842   st->regs_top = 0;
   1843   st->mem_top = 0;
   1844   st->scalar_ret = 0;
   1845   st->ret_is_fp = 0;
   1846   st->status = KIT_INTERP_DONE;
   1847   st->trap_reason = NULL;
   1848   st->mem_fault = 0;
   1849   return KIT_OK;
   1850 }
   1851 
   1852 KitStatus kit_interp_call_args_on(KitInterpStack* s, KitInterpFunc* ff,
   1853                                   const uint64_t* args, uint32_t nargs) {
   1854   InterpStack* st = (InterpStack*)s;
   1855   InterpFunc* fn = (InterpFunc*)ff;
   1856   u32 idx, i;
   1857   if (!st || !fn) return KIT_INVALID;
   1858   idx = frame_push(st, fn);
   1859   if (idx == 0xffffffffu) return KIT_NOMEM;
   1860   for (i = 0; i < nargs; ++i) bind_entry_param(st, fn, idx, i, args[i]);
   1861   return KIT_OK;
   1862 }
   1863 
   1864 const char* kit_interp_stack_trap_reason(KitInterpStack* s) {
   1865   InterpStack* st = (InterpStack*)s;
   1866   return st ? st->trap_reason : NULL;
   1867 }