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 }