kit

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

emit.c (186276B)


      1 /* Wasm CGTarget emission.
      2  *
      3  * Records CGTarget operations into a per-function WIR list, then linearizes
      4  * to a WasmFunc body at func_end. Each SSA Reg becomes a Wasm local; control
      5  * flow that fits the kit_cg_if_begin/else/end pattern lowers to
      6  * if/else/end, while CG scopes (SCOPE_LOOP) lower to (block (loop ...)).
      7  *
      8  * TODO: complete IR-to-WASM coverage for:
      9  *   - bitfield load/store
     10  *   - multiple call/return results
     11  *   - address-taken parameters
     12  *   - ABI multipart params
     13  *   - dynamic memcpy/memset via memory.* calls
     14  *   - file-scope asm
     15  *   - atomics
     16  *   - intrinsics
     17  */
     18 
     19 #include <stdarg.h>
     20 #include <string.h>
     21 
     22 #include "abi/abi.h"
     23 #include "arch/wasm/internal.h"
     24 #include "cg/type.h"
     25 #include "core/arena.h"
     26 #include "core/buf.h"
     27 #include "core/heap.h"
     28 #include "core/pool.h"
     29 #include "obj/obj.h"
     30 #include "obj/wasm_imports.h"
     31 
     32 /* Shared Wasm core: in-memory WasmModule, helpers (wasm_add_func,
     33  * wasm_intern_func_type, wasm_func_add_insn, ...), and wasm_encode for
     34  * the final flush from emit_wasm. */
     35 #include "wasm/wasm.h"
     36 
     37 /* -----------------------------------------------------------------
     38  * Helpers
     39  * ----------------------------------------------------------------- */
     40 
     41 static SrcLoc cur_loc(WTarget* t) {
     42   /* Prefer the most recent statement loc the frontend reported via
     43    * wasm_set_loc — gives diagnostics the actual failing line, not the
     44    * function definition's line. Fall back to the function loc when no
     45    * statement loc has been set (line == 0). */
     46   if (t->cur_stmt_loc.line) return t->cur_stmt_loc;
     47   if (t->cur_fn_desc) return t->cur_fn_desc->loc;
     48   SrcLoc l = {0, 0, 0};
     49   return l;
     50 }
     51 
     52 static _Noreturn void wfail(WTarget* t, const char* fmt, ...) {
     53   va_list ap;
     54   va_start(ap, fmt);
     55   compiler_panicv(t->c, cur_loc(t), fmt, ap);
     56 }
     57 
     58 static _Noreturn void wfail_at(WTarget* t, SrcLoc loc, const char* fmt, ...) {
     59   va_list ap;
     60   va_start(ap, fmt);
     61   compiler_panicv(t->c, loc, fmt, ap);
     62 }
     63 
     64 static struct WasmModule* ensure_module(WTarget* t);
     65 
     66 static const char* pool_sym_cstr(Pool* p, Sym sym, size_t* len_out) {
     67   Slice sl = pool_slice(p, sym);
     68   if (len_out) *len_out = sl.len;
     69   return sl.s;
     70 }
     71 
     72 static WasmValType valtype_for_size_kind(WTarget* t, u32 size, u8 scalar_kind) {
     73   if (scalar_kind == ABI_SC_FLOAT) {
     74     if (size == 4) return WASM_VAL_F32;
     75     if (size == 8) return WASM_VAL_F64;
     76     /* The only C float wider than f64 is binary128 long double, which has
     77      * no wasm value type. Report it specifically rather than as a generic
     78      * size error. */
     79     if (size == 16) wfail(t, "wasm: long double not supported");
     80     wfail(t, "wasm: unsupported float size %u", size);
     81   }
     82   if (size <= 4) return WASM_VAL_I32;
     83   if (size == 8) return WASM_VAL_I64;
     84   wfail(t, "wasm: unsupported integer size %u", size);
     85 }
     86 
     87 static WasmValType valtype_for_type(WTarget* t, KitCgTypeId ty) {
     88   ABITypeInfo ti = abi_cg_type_info(t->c->abi, ty);
     89   if (ti.scalar_kind == ABI_SC_VOID) {
     90     wfail(t, "wasm: void value type requested");
     91   }
     92   if (ti.scalar_kind == ABI_SC_PTR) return WASM_VAL_I32; /* wasm32 ILP32 */
     93   return valtype_for_size_kind(t, ti.size, ti.scalar_kind);
     94 }
     95 
     96 static u32 align_to_u32(u32 v, u32 a) {
     97   if (!a) return v;
     98   return (v + a - 1u) & ~(a - 1u);
     99 }
    100 
    101 /* Low-memory guard: leaves the first NULL_GUARD bytes of linear memory
    102  * unassigned so addr==0 never resolves to a real symbol. */
    103 #define WASM_DATA_NULL_GUARD 16u
    104 
    105 static void type_size_align(WTarget* t, KitCgTypeId ty, u32 fallback_size,
    106                             u32 fallback_align, u32* size_out, u32* align_out) {
    107   ABITypeInfo ti;
    108   if (ty) {
    109     ti = abi_cg_type_info(t->c->abi, ty);
    110     *size_out = ti.size ? ti.size : fallback_size;
    111     *align_out = ti.align ? ti.align : fallback_align;
    112   } else {
    113     *size_out = fallback_size;
    114     *align_out = fallback_align;
    115   }
    116   if (!*size_out) *size_out = 1;
    117   if (!*align_out) *align_out = 1;
    118 }
    119 
    120 static u32 add_wasm_local(WTarget* t, WasmValType vt) {
    121   if (!t->cur_func) wfail(t, "wasm: local allocation outside a function");
    122   return wasm_func_push_local(t->c, t->module, t->cur_func, vt);
    123 }
    124 
    125 static void ensure_linear_memory(WTarget* t) {
    126   ensure_module(t);
    127   if (!t->has_memory) {
    128     WasmMemory* mem = wasm_add_memory(t->c, t->module);
    129     mem->min_pages = 1;
    130     t->has_memory = 1;
    131   }
    132 }
    133 
    134 /* Atomic memory ops require the linear memory to be declared shared and to
    135  * carry a maximum size. We promote the (single) module memory to shared on
    136  * first atomic emission. max_pages is provisionally set to the wasm32 limit
    137  * (65536, i.e. 4 GiB); wasm_materialize_data tightens it to match min_pages
    138  * after the final layout is known so embedders can pre-reserve a snug arena. */
    139 static void ensure_shared_memory(WTarget* t) {
    140   ensure_linear_memory(t);
    141   WasmMemory* mem = &t->module->memories[0];
    142   if (!mem->shared) {
    143     mem->shared = 1;
    144   }
    145   if (!mem->has_max) {
    146     mem->has_max = 1;
    147     mem->max_pages = 65536u;
    148   }
    149 }
    150 
    151 static void ensure_stack_pointer(WTarget* t) {
    152   ensure_linear_memory(t);
    153   if (!t->has_stack_pointer) {
    154     WasmGlobal* g = wasm_add_global(t->c, t->module);
    155     g->name = wasm_strdup(t->module->heap, "__stack_pointer",
    156                           sizeof("__stack_pointer") - 1u);
    157     g->type = WASM_VAL_I32;
    158     g->mutable_ = 1;
    159     g->init.kind = WASM_INSN_I32_CONST;
    160     g->init.imm = 65536;
    161     t->stack_pointer_global = t->module->nglobals - 1u;
    162     t->stack_size = 65536;
    163     t->has_stack_pointer = 1;
    164   }
    165 }
    166 
    167 /* Map an SSA Reg to its WasmFunc local index, allocating on first use. */
    168 static u32 reg_local(WTarget* t, Reg r, KitCgTypeId ty, RegClass cls) {
    169   Heap* h = t->c->ctx->heap;
    170   if (r == REG_NONE) wfail(t, "wasm: REG_NONE used as operand");
    171   if (r >= t->reg_cap) {
    172     u32 nc = t->reg_cap ? t->reg_cap : 16u;
    173     while (nc <= r) nc *= 2u;
    174     u32* nl = (u32*)h->realloc(h, t->reg_to_local, sizeof(u32) * t->reg_cap,
    175                                sizeof(u32) * nc, _Alignof(u32));
    176     KitCgTypeId* nt = (KitCgTypeId*)h->realloc(
    177         h, t->reg_type, sizeof(KitCgTypeId) * t->reg_cap,
    178         sizeof(KitCgTypeId) * nc, _Alignof(KitCgTypeId));
    179     u8* nc_arr = (u8*)h->realloc(h, t->reg_cls, t->reg_cap, nc, 1);
    180     if (!nl || !nt || !nc_arr) wfail(t, "wasm: out of memory");
    181     for (u32 i = t->reg_cap; i < nc; ++i) {
    182       nl[i] = 0xffffffffu;
    183       nt[i] = KIT_CG_TYPE_NONE;
    184       nc_arr[i] = 0;
    185     }
    186     t->reg_to_local = nl;
    187     t->reg_type = nt;
    188     t->reg_cls = nc_arr;
    189     t->reg_cap = nc;
    190   }
    191   /* CG may reuse the same Reg id with different value types: api_ensure_reg
    192    * for an SV_CMP reuses one of the cmp operands' Regs (originally e.g. i64)
    193    * to hold the i32 cmp result. Detect a type change and rebind to a fresh
    194    * wasm local; the previous binding is dead from CG's point of view. */
    195   if (t->reg_to_local[r] != 0xffffffffu && t->reg_type[r]) {
    196     WasmValType cached_vt = valtype_for_type(t, t->reg_type[r]);
    197     WasmValType want_vt = valtype_for_type(t, ty);
    198     if (cached_vt == want_vt) return t->reg_to_local[r];
    199     /* fall through to allocate fresh */
    200   }
    201   {
    202     WasmValType vt = valtype_for_type(t, ty);
    203     t->reg_to_local[r] = add_wasm_local(t, vt);
    204     t->reg_type[r] = ty;
    205     t->reg_cls[r] = (u8)cls;
    206     if (r + 1u > t->reg_hwm) t->reg_hwm = r + 1u;
    207   }
    208   return t->reg_to_local[r];
    209 }
    210 
    211 /* -----------------------------------------------------------------
    212  * WIR appending
    213  * ----------------------------------------------------------------- */
    214 
    215 static WIR* wir_push(WTarget* t) {
    216   Heap* h = t->c->ctx->heap;
    217   if (t->nwir == t->wir_cap) {
    218     u32 nc = t->wir_cap ? t->wir_cap * 2u : 64u;
    219     void* p = h->realloc(h, t->wir, sizeof(WIR) * t->wir_cap, sizeof(WIR) * nc,
    220                          _Alignof(WIR));
    221     if (!p) wfail(t, "wasm: out of memory");
    222     t->wir = (WIR*)p;
    223     t->wir_cap = nc;
    224   }
    225   WIR* w = &t->wir[t->nwir++];
    226   memset(w, 0, sizeof *w);
    227   return w;
    228 }
    229 
    230 /* Operand-kind encoding stored in WIR's imm_kind / imm_kind_b. */
    231 enum {
    232   WOP_REG = 0,
    233   WOP_IMM = 1,
    234   WOP_LOCAL = 2,
    235   WOP_WASM_LOCAL = 3,
    236   WOP_ADDR = 4
    237 };
    238 
    239 static void wir_capture_operand(WIR* w, int which, Operand op) {
    240   u32 kind;
    241   i64 ival;
    242   Reg r = REG_NONE;
    243   switch (op.kind) {
    244     case OPK_REG:
    245       kind = WOP_REG;
    246       r = op.v.reg;
    247       ival = 0;
    248       break;
    249     case OPK_IMM:
    250       kind = WOP_IMM;
    251       ival = op.v.imm;
    252       break;
    253     case OPK_LOCAL:
    254       kind = WOP_LOCAL;
    255       ival = (i64)op.v.frame_slot;
    256       break;
    257     default:
    258       kind = 99u;
    259       ival = 0;
    260       break;
    261   }
    262   if (which == 0) {
    263     w->imm_kind = kind;
    264     w->imm_a = ival;
    265     w->a = r;
    266   } else {
    267     w->imm_kind_b = kind;
    268     w->imm_b = ival;
    269     w->b = r;
    270   }
    271 }
    272 
    273 /* -----------------------------------------------------------------
    274  * Labels
    275  * ----------------------------------------------------------------- */
    276 
    277 Label wasm_label_new(CGTarget* tg) {
    278   WTarget* t = (WTarget*)tg;
    279   Heap* h = t->c->ctx->heap;
    280   if (t->nlabels == t->labels_cap) {
    281     u32 nc = t->labels_cap ? t->labels_cap * 2u : 16u;
    282     void* p = h->realloc(h, t->labels, sizeof(WLabel) * t->labels_cap,
    283                          sizeof(WLabel) * nc, _Alignof(WLabel));
    284     if (!p) wfail(t, "wasm: out of memory");
    285     t->labels = (WLabel*)p;
    286     t->labels_cap = nc;
    287   }
    288   u32 id = t->nlabels++;
    289   memset(&t->labels[id], 0, sizeof t->labels[id]);
    290   return (Label)(id + 1u);
    291 }
    292 
    293 static WLabel* lookup_label(WTarget* t, Label l) {
    294   if (l == LABEL_NONE || l - 1u >= t->nlabels) return NULL;
    295   return &t->labels[l - 1u];
    296 }
    297 
    298 void wasm_label_place(CGTarget* tg, Label l) {
    299   WTarget* t = (WTarget*)tg;
    300   WLabel* lbl = lookup_label(t, l);
    301   if (!lbl) wfail(t, "wasm: label_place on unknown label");
    302   /* If this label is registered to a scope, the scope ops drive the wasm
    303    * structure — placement here is a no-op. */
    304   if (lbl->kind == WLBL_SCOPE_BREAK || lbl->kind == WLBL_SCOPE_CONT) {
    305     lbl->placed = 1;
    306     t->dead = 0;
    307     return;
    308   }
    309   /* Free-standing label placement: record but emit nothing. The CG layer
    310    * sometimes places scope continue/break labels just before/after a
    311    * scope_begin/end pair; for wasm the structured scope ops drive the
    312    * `br N` targets, so these placements are no-ops. A subsequent jump or
    313    * cmp_branch that lands on an unbound label will diagnose. */
    314   lbl->placed = 1;
    315   lbl->wir_index = t->nwir;
    316   if (lbl->kind == WLBL_UNBOUND) lbl->kind = WLBL_FORWARD;
    317   WIR* w = wir_push(t);
    318   w->op = WIR_LABEL;
    319   w->labels[0] = l;
    320   t->dead = 0;
    321 }
    322 
    323 void wasm_jump(CGTarget* tg, Label l) {
    324   WTarget* t = (WTarget*)tg;
    325   if (t->dead) return;
    326   WLabel* lbl = lookup_label(t, l);
    327   if (!lbl) wfail(t, "wasm: jump to unknown label");
    328   WIR* w = wir_push(t);
    329   w->op = WIR_JUMP;
    330   w->labels[0] = l;
    331   t->dead = 1;
    332 }
    333 
    334 void wasm_cmp_branch(CGTarget* tg, CmpOp op, Operand a, Operand b, Label l) {
    335   WTarget* t = (WTarget*)tg;
    336   if (t->dead) return;
    337   if (!lookup_label(t, l)) wfail(t, "wasm: cmp_branch to unknown label");
    338   WIR* w = wir_push(t);
    339   w->op = WIR_CMP_BRANCH;
    340   w->cgop = (u8)op;
    341   wir_capture_operand(w, 0, a);
    342   wir_capture_operand(w, 1, b);
    343   w->type = a.type ? a.type : b.type;
    344   w->labels[0] = l;
    345 }
    346 
    347 void wasm_switch(CGTarget* tg, const CGSwitchDesc* d) {
    348   WTarget* t = (WTarget*)tg;
    349   WIR* w;
    350   if (t->dead) return;
    351   if (!d) wfail(t, "wasm: switch without descriptor");
    352   if (d->default_label == LABEL_NONE)
    353     wfail(t, "wasm: switch without default label");
    354   if (d->ncases && !d->cases) wfail(t, "wasm: switch case count without cases");
    355   if (d->selector.kind != OPK_REG && d->selector.kind != OPK_IMM &&
    356       d->selector.kind != OPK_LOCAL)
    357     wfail(t, "wasm: switch selector has unsupported operand kind");
    358   if (!lookup_label(t, d->default_label))
    359     wfail(t, "wasm: switch default label is unknown");
    360   for (u32 i = 0; i < d->ncases; ++i) {
    361     if (!lookup_label(t, d->cases[i].label))
    362       wfail(t, "wasm: switch case label is unknown");
    363   }
    364 
    365   w = wir_push(t);
    366   w->op = WIR_SWITCH;
    367   wir_capture_operand(w, 0, d->selector);
    368   w->type = d->selector_type;
    369   w->labels[0] = d->default_label;
    370   w->switch_ncases = d->ncases;
    371   if (d->ncases) {
    372     Heap* h = t->c->ctx->heap;
    373     w->switch_cases = (CGSwitchCase*)h->alloc(
    374         h, sizeof(CGSwitchCase) * d->ncases, _Alignof(CGSwitchCase));
    375     if (!w->switch_cases) wfail(t, "wasm: out of memory");
    376     memcpy(w->switch_cases, d->cases, sizeof(CGSwitchCase) * d->ncases);
    377   }
    378   t->dead = 1;
    379 }
    380 
    381 /* -----------------------------------------------------------------
    382  * Scopes
    383  * ----------------------------------------------------------------- */
    384 
    385 CGScope wasm_scope_begin(CGTarget* tg, const CGScopeDesc* d) {
    386   WTarget* t = (WTarget*)tg;
    387   if (t->nscopes >= 32u) wfail(t, "wasm: too many nested scopes (max 32)");
    388   WScope* s = &t->scopes[t->nscopes];
    389   memset(s, 0, sizeof *s);
    390   s->id = ++t->next_scope_id;
    391   s->cg_kind = d->kind;
    392   s->break_lbl = d->break_label;
    393   s->cont_lbl = d->continue_label;
    394   s->result_type = d->result_type;
    395 
    396   /* Wire scope's break/continue labels to this scope so jump()/cmp_branch()
    397    * to them can lower to wasm `br`. */
    398   if (d->break_label != LABEL_NONE) {
    399     WLabel* lbl = lookup_label(t, d->break_label);
    400     if (lbl) {
    401       lbl->kind = WLBL_SCOPE_BREAK;
    402       lbl->scope_id = s->id;
    403     }
    404   }
    405   if (d->continue_label != LABEL_NONE) {
    406     WLabel* lbl = lookup_label(t, d->continue_label);
    407     if (lbl) {
    408       lbl->kind = WLBL_SCOPE_CONT;
    409       lbl->scope_id = s->id;
    410     }
    411   }
    412 
    413   WIR* open = wir_push(t);
    414   open->op = WIR_SCOPE_OPEN;
    415   open->scope_id = s->id;
    416   open->cgop = d->kind;
    417   open->dst = REG_NONE;
    418 
    419   s->placed_in_wir = 1;
    420   t->nscopes++;
    421   return (CGScope)s->id;
    422 }
    423 
    424 static WScope* scope_by_id(WTarget* t, u32 id) {
    425   for (u32 i = 0; i < t->nscopes; ++i) {
    426     if (t->scopes[i].id == id) return &t->scopes[i];
    427   }
    428   return NULL;
    429 }
    430 
    431 void wasm_scope_end(CGTarget* tg, CGScope sc) {
    432   WTarget* t = (WTarget*)tg;
    433   WScope* s = scope_by_id(t, (u32)sc);
    434   if (!s) wfail(t, "wasm: scope_end on unknown scope");
    435   WIR* w = wir_push(t);
    436   w->op = WIR_SCOPE_CLOSE;
    437   w->scope_id = s->id;
    438   /* Pop the scope from the stack. CG always closes in LIFO order. */
    439   if (t->nscopes == 0 || t->scopes[t->nscopes - 1u].id != s->id)
    440     wfail(t, "wasm: scope_end out of LIFO order");
    441   t->nscopes--;
    442   t->dead = 0;
    443 }
    444 
    445 void wasm_break_to(CGTarget* tg, CGScope sc) {
    446   WTarget* t = (WTarget*)tg;
    447   if (t->dead) return;
    448   WScope* s = scope_by_id(t, (u32)sc);
    449   if (!s) wfail(t, "wasm: break_to unknown scope");
    450   WIR* w = wir_push(t);
    451   w->op = WIR_JUMP;
    452   w->labels[0] = s->break_lbl;
    453   t->dead = 1;
    454 }
    455 
    456 void wasm_continue_to(CGTarget* tg, CGScope sc) {
    457   WTarget* t = (WTarget*)tg;
    458   if (t->dead) return;
    459   WScope* s = scope_by_id(t, (u32)sc);
    460   if (!s) wfail(t, "wasm: continue_to unknown scope");
    461   WIR* w = wir_push(t);
    462   w->op = WIR_JUMP;
    463   w->labels[0] = s->cont_lbl;
    464   t->dead = 1;
    465 }
    466 
    467 /* -----------------------------------------------------------------
    468  * Function lifecycle
    469  * ----------------------------------------------------------------- */
    470 
    471 /* Forward decl — promotes an undefined function symbol's WasmFunc to an
    472  * import using the supplied ABI to build the wasm signature. */
    473 static void promote_import_func(WTarget* t, ObjSymId sym, WasmFunc* f,
    474                                 const ABIFuncInfo* abi);
    475 
    476 /* Lookup or allocate a Wasm function index for an ObjSymId. Returns
    477  * (wasm_func_idx, *out_func) on success. */
    478 static u32 sym_to_wasm_func(WTarget* t, ObjSymId sym, WasmFunc** out_func) {
    479   Heap* h = t->c->ctx->heap;
    480   if (sym >= t->sym_to_func_cap) {
    481     u32 nc = t->sym_to_func_cap ? t->sym_to_func_cap : 16u;
    482     while (nc <= sym) nc *= 2u;
    483     u32* p =
    484         (u32*)h->realloc(h, t->sym_to_func, sizeof(u32) * t->sym_to_func_cap,
    485                          sizeof(u32) * nc, _Alignof(u32));
    486     if (!p) wfail(t, "wasm: out of memory");
    487     for (u32 i = t->sym_to_func_cap; i < nc; ++i) p[i] = 0;
    488     t->sym_to_func = p;
    489     t->sym_to_func_cap = nc;
    490   }
    491   if (t->sym_to_func[sym]) {
    492     u32 idx = t->sym_to_func[sym] - 1u;
    493     if (out_func) *out_func = &t->module->funcs[idx];
    494     return idx;
    495   }
    496   /* Create a fresh WasmFunc and link. */
    497   WasmFunc* f = wasm_add_func(t->c, t->module);
    498   u32 idx = t->module->nfuncs - 1u;
    499   t->sym_to_func[sym] = idx + 1u;
    500   if (out_func) *out_func = f;
    501   return idx;
    502 }
    503 
    504 static WSlot* slot_push(WTarget* t) {
    505   Heap* h = t->c->ctx->heap;
    506   if (t->nslots == t->slots_cap) {
    507     u32 nc = t->slots_cap ? t->slots_cap * 2u : 16u;
    508     WSlot* ns = (WSlot*)h->realloc(h, t->slots, sizeof(WSlot) * t->slots_cap,
    509                                    sizeof(WSlot) * nc, _Alignof(WSlot));
    510     if (!ns) wfail(t, "wasm: out of memory");
    511     t->slots = ns;
    512     t->slots_cap = nc;
    513   }
    514   WSlot* s = &t->slots[t->nslots++];
    515   memset(s, 0, sizeof *s);
    516   return s;
    517 }
    518 
    519 /* True iff `ty` maps to a single wasm value type (i32/i64/f32/f64) and so can
    520  * live directly in a wasm local. Aggregates (records/arrays) and anything
    521  * wider than 8 bytes must be homed in linear memory. */
    522 static int type_is_wasm_scalar(WTarget* t, KitCgTypeId ty) {
    523   ABITypeInfo ti;
    524   if (!ty) return 0;
    525   ti = abi_cg_type_info(t->c->abi, ty);
    526   if (ti.scalar_kind == ABI_SC_VOID) return 0;
    527   if (ti.scalar_kind == ABI_SC_PTR) return 1;
    528   return ti.size <= 8u;
    529 }
    530 
    531 static FrameSlot alloc_frame_slot_kind(WTarget* t, KitCgTypeId ty, u32 size,
    532                                        u32 align, int stack_backed) {
    533   WSlot* s;
    534   u32 slot_id;
    535   type_size_align(t, ty, size, align, &size, &align);
    536   /* A non-scalar type has no wasm value type; force it into linear memory. */
    537   if (!stack_backed && !type_is_wasm_scalar(t, ty)) stack_backed = 1;
    538   s = slot_push(t);
    539   s->type = ty;
    540   s->size = size;
    541   s->align = align;
    542   if (stack_backed) {
    543     ensure_stack_pointer(t);
    544     t->frame_size = align_to_u32(t->frame_size, align);
    545     s->kind = W_SLOT_STACK;
    546     s->frame_offset = t->frame_size;
    547     t->frame_size += size;
    548     if (align > t->frame_align) t->frame_align = align;
    549     t->has_stack_frame = 1;
    550   } else {
    551     s->kind = W_SLOT_LOCAL;
    552     s->wasm_local = add_wasm_local(t, valtype_for_type(t, ty));
    553   }
    554   slot_id = t->nslots;
    555   return (FrameSlot)slot_id;
    556 }
    557 
    558 static WSlot* slot_for(WTarget* t, FrameSlot fs) {
    559   if (fs == FRAME_SLOT_NONE) wfail(t, "wasm: FRAME_SLOT_NONE used");
    560   u32 idx = fs - 1u;
    561   if (idx >= t->nslots) wfail(t, "wasm: bad frame slot id");
    562   return &t->slots[idx];
    563 }
    564 
    565 static void promote_slot_to_stack(WTarget* t, WSlot* s) {
    566   if (s->kind == W_SLOT_STACK) return;
    567   ensure_stack_pointer(t);
    568   t->frame_size = align_to_u32(t->frame_size, s->align ? s->align : 1u);
    569   s->frame_offset = t->frame_size;
    570   t->frame_size += s->size ? s->size : 1u;
    571   if (s->align > t->frame_align) t->frame_align = s->align;
    572   s->kind = W_SLOT_STACK;
    573   t->has_stack_frame = 1;
    574 }
    575 
    576 void wasm_func_begin(CGTarget* tg, const CGFuncDesc* d) {
    577   WTarget* t = (WTarget*)tg;
    578   WasmFunc* f;
    579   u32 idx;
    580   const CgType* fnty;
    581   const ABIFuncInfo* abi;
    582   Heap* h = t->c->ctx->heap;
    583 
    584   ensure_module(t);
    585   t->cur_fn_desc = d;
    586   memset(&t->cur_stmt_loc, 0, sizeof t->cur_stmt_loc);
    587   t->nwir = 0;
    588   t->nlabels = 0;
    589   t->nslots = 0;
    590   t->nscopes = 0;
    591   t->next_scope_id = 0;
    592   t->frame_size = 0;
    593   t->frame_align = 1;
    594   t->frame_base_local = 0xffffffffu;
    595   t->frame_saved_sp_local = 0xffffffffu;
    596   t->has_stack_frame = 0;
    597   t->dead = 0;
    598   t->sret_param_local = 0xffffffffu;
    599   t->va_ptr_param_local = 0xffffffffu;
    600   t->cur_has_sret = 0;
    601   t->cur_is_variadic = 0;
    602   t->varcall_saved_sp_local = 0xffffffffu;
    603   t->varcall_buf_local = 0xffffffffu;
    604   t->va_arg_tmp_addr_local = 0xffffffffu;
    605   t->nparams_cg = 0;
    606   t->nbyval_copies = 0;
    607   /* Wipe reg map. Only [0, reg_hwm) can hold a non-sentinel binding from a
    608    * prior function — reg_cap grows monotonically but slots beyond the
    609    * high-water mark are sentinel-initialized at grow time and never bound, so
    610    * wiping the full reg_cap each function is wasted work on large TUs. */
    611   for (u32 i = 0; i < t->reg_hwm; ++i) t->reg_to_local[i] = 0xffffffffu;
    612 
    613   idx = sym_to_wasm_func(t, d->sym, &f);
    614   t->cur_func_idx = idx;
    615   t->cur_func = f;
    616 
    617   fnty = cg_type_get(t->c, d->fn_type);
    618   if (!fnty || fnty->kind != KIT_CG_TYPE_FUNC)
    619     wfail(t, "wasm: func_begin without function type");
    620   abi = d->abi;
    621   if (!abi) wfail(t, "wasm: func_begin with no ABI info");
    622 
    623   /* Build the wasm function's param layout:
    624    *   [sret_ptr]? [param_0] [param_1] ...
    625    * with IGNORE'd CG params dropped. Record per-CG-param the wasm-local
    626    * index so wasm_param can place each frame slot on the right local.
    627    * params/locals/local_names grow on demand — no fixed cap. */
    628   f->nparams = 0;
    629   if (abi->has_sret) {
    630     t->sret_param_local =
    631         wasm_func_push_param(t->c, t->module, f, WASM_VAL_I32);
    632     t->cur_has_sret = 1;
    633     ensure_linear_memory(t);
    634   }
    635   if (fnty->func.nparams > t->param_local_idx_cap) {
    636     u32 nc = t->param_local_idx_cap ? t->param_local_idx_cap : 4u;
    637     while (nc < fnty->func.nparams) nc *= 2u;
    638     u32* p = (u32*)h->realloc(h, t->param_local_idx,
    639                               sizeof(u32) * t->param_local_idx_cap,
    640                               sizeof(u32) * nc, _Alignof(u32));
    641     if (!p) wfail(t, "wasm: out of memory");
    642     t->param_local_idx = p;
    643     t->param_local_idx_cap = nc;
    644   }
    645   t->nparams_cg = fnty->func.nparams;
    646   for (u32 i = 0; i < fnty->func.nparams; ++i) {
    647     const ABIArgInfo* ai = &abi->params[i];
    648     if (ai->kind == ABI_ARG_IGNORE) {
    649       t->param_local_idx[i] = 0xffffffffu;
    650       continue;
    651     }
    652     if (ai->kind == ABI_ARG_INDIRECT) {
    653       t->param_local_idx[i] =
    654           wasm_func_push_param(t->c, t->module, f, WASM_VAL_I32);
    655       ensure_linear_memory(t);
    656     } else {
    657       if (ai->nparts != 1)
    658         wfail(t, "wasm: multi-part DIRECT param %u not yet implemented", i);
    659       const ABIArgPart* p = &ai->parts[0];
    660       WasmValType vt = valtype_for_size_kind(
    661           t, p->size, p->cls == ABI_CLASS_FP ? ABI_SC_FLOAT : ABI_SC_INT);
    662       t->param_local_idx[i] = wasm_func_push_param(t->c, t->module, f, vt);
    663     }
    664   }
    665   /* Variadic: append hidden i32 va_ptr trailing param. Must match
    666    * abi_to_wasm_func_type so indirect calls' signature interning agrees. */
    667   if (abi->variadic) {
    668     t->va_ptr_param_local =
    669         wasm_func_push_param(t->c, t->module, f, WASM_VAL_I32);
    670     t->cur_is_variadic = 1;
    671     ensure_linear_memory(t);
    672   }
    673   f->nresults = 0;
    674   if (!abi->has_sret && abi->ret.kind == ABI_ARG_DIRECT &&
    675       abi->ret.nparts == 1) {
    676     const ABIArgPart* p = &abi->ret.parts[0];
    677     wasm_func_push_result(
    678         t->c, t->module, f,
    679         valtype_for_size_kind(
    680             t, p->size, p->cls == ABI_CLASS_FP ? ABI_SC_FLOAT : ABI_SC_INT));
    681   }
    682   f->typeidx = wasm_intern_func_type(t->c, t->module, f);
    683 
    684   /* Export under the symbol's name when the symbol is globally bound. */
    685   const ObjSym* sym = obj_symbol_get(t->obj, d->sym);
    686   if (sym && sym->bind != SB_LOCAL) {
    687     const char* name = pool_sym_cstr(t->c->global, sym->name, NULL);
    688     if (name && *name) {
    689       Heap* h = t->c->ctx->heap;
    690       size_t nlen = strlen(name);
    691       char* dup = (char*)h->alloc(h, nlen + 1u, 1);
    692       memcpy(dup, name, nlen + 1u);
    693       f->export_name = dup;
    694       WasmExport* e = wasm_add_export(t->c, t->module);
    695       char* exp_name = (char*)h->alloc(h, nlen + 1u, 1);
    696       memcpy(exp_name, name, nlen + 1u);
    697       e->name = exp_name;
    698       e->kind = 0; /* function export */
    699       e->index = idx;
    700     }
    701   }
    702 }
    703 
    704 CGLocalStorage wasm_param(CGTarget* tg, const CGParamDesc* d) {
    705   WTarget* t = (WTarget*)tg;
    706   CGLocalStorage ls;
    707   Heap* h = t->c->ctx->heap;
    708   u32 wli;
    709   WSlot* s;
    710   memset(&ls, 0, sizeof ls);
    711   if (d->index >= t->nparams_cg)
    712     wfail(t, "wasm: param index %u out of range (nparams=%u)", d->index,
    713           t->nparams_cg);
    714   wli = t->param_local_idx[d->index];
    715   if (wli == 0xffffffffu) {
    716     /* ABI_ARG_IGNORE — no wasm storage. Push a placeholder slot so the
    717      * returned FrameSlot is meaningful to CG; it never gets emitted. */
    718     s = slot_push(t);
    719     s->type = d->type;
    720     s->size = d->size;
    721     s->align = d->align ? d->align : 1u;
    722     s->kind = W_SLOT_LOCAL;
    723     s->wasm_local = 0;
    724     ls.kind = CG_LOCAL_STORAGE_FRAME;
    725     ls.v.frame_slot = (FrameSlot)t->nslots;
    726     return ls;
    727   }
    728   if (d->abi && d->abi->kind == ABI_ARG_INDIRECT) {
    729     /* byval: callee receives an i32 pointer; copy the aggregate into a
    730      * caller-isolated stack-backed slot at function entry. */
    731     u32 size = d->size;
    732     u32 align = d->align ? d->align : 1u;
    733     type_size_align(t, d->type, size, align, &size, &align);
    734     ensure_stack_pointer(t);
    735     s = slot_push(t);
    736     s->type = d->type;
    737     s->size = size;
    738     s->align = align;
    739     s->kind = W_SLOT_STACK;
    740     t->frame_size = align_to_u32(t->frame_size, align);
    741     s->frame_offset = t->frame_size;
    742     t->frame_size += size;
    743     if (align > t->frame_align) t->frame_align = align;
    744     t->has_stack_frame = 1;
    745     /* Queue prologue copy-in from the pointer's wasm-local into &slot. */
    746     if (t->nbyval_copies == t->byval_copies_cap) {
    747       u32 nc = t->byval_copies_cap ? t->byval_copies_cap * 2u : 4u;
    748       WByvalCopy* nb = (WByvalCopy*)h->realloc(
    749           h, t->byval_copies, sizeof(WByvalCopy) * t->byval_copies_cap,
    750           sizeof(WByvalCopy) * nc, _Alignof(WByvalCopy));
    751       if (!nb) wfail(t, "wasm: out of memory");
    752       t->byval_copies = nb;
    753       t->byval_copies_cap = nc;
    754     }
    755     WByvalCopy* bc = &t->byval_copies[t->nbyval_copies++];
    756     bc->ptr_wasm_local = wli;
    757     bc->dst_slot_id = t->nslots - 1u;
    758     ls.kind = CG_LOCAL_STORAGE_FRAME;
    759     ls.v.frame_slot = (FrameSlot)t->nslots;
    760     return ls;
    761   }
    762   if (d->flags & CG_LOCAL_ADDR_TAKEN) {
    763     wfail(t, "wasm: address-taken parameter not yet implemented");
    764   }
    765   s = slot_push(t);
    766   s->type = d->type;
    767   s->size = d->size;
    768   s->align = d->align ? d->align : 1u;
    769   s->kind = W_SLOT_LOCAL;
    770   s->wasm_local = wli;
    771   ls.kind = CG_LOCAL_STORAGE_FRAME;
    772   ls.v.frame_slot = (FrameSlot)t->nslots;
    773   return ls;
    774 }
    775 
    776 /* Allocate a frame slot backed by a fresh wasm local. */
    777 static FrameSlot alloc_frame_slot(WTarget* t, KitCgTypeId ty) {
    778   return alloc_frame_slot_kind(t, ty, 0, 0, 0);
    779 }
    780 
    781 FrameSlot wasm_frame_slot(CGTarget* tg, const FrameSlotDesc* d) {
    782   WTarget* t = (WTarget*)tg;
    783   if (!d->type && !d->size) wfail(t, "wasm: frame slot without type/size");
    784   return alloc_frame_slot_kind(
    785       t, d->type, d->size, d->align,
    786       (d->flags & FSF_ADDR_TAKEN) != 0 || d->kind == FS_ALLOCA);
    787 }
    788 
    789 CGLocalStorage wasm_local(CGTarget* tg, const CGLocalDesc* d) {
    790   WTarget* t = (WTarget*)tg;
    791   CGLocalStorage ls;
    792   memset(&ls, 0, sizeof ls);
    793   if (d->flags & (CG_LOCAL_ADDR_TAKEN | CG_LOCAL_MEMORY_REQUIRED)) {
    794     ls.kind = CG_LOCAL_STORAGE_FRAME;
    795     ls.v.frame_slot = alloc_frame_slot_kind(t, d->type, d->size, d->align, 1);
    796     return ls;
    797   }
    798   ls.kind = CG_LOCAL_STORAGE_FRAME;
    799   ls.v.frame_slot = alloc_frame_slot(t, d->type);
    800   return ls;
    801 }
    802 
    803 /* -----------------------------------------------------------------
    804  * Data-movement records
    805  * ----------------------------------------------------------------- */
    806 
    807 void wasm_load_imm(CGTarget* tg, Operand dst, i64 imm) {
    808   WTarget* t = (WTarget*)tg;
    809   if (t->dead) return;
    810   if (dst.kind != OPK_REG) wfail(t, "wasm: load_imm dst must be REG");
    811   WIR* w = wir_push(t);
    812   w->op = WIR_LOAD_IMM;
    813   w->dst = dst.v.reg;
    814   w->imm = imm;
    815   w->type = dst.type;
    816   w->cls = dst.cls;
    817 }
    818 
    819 void wasm_load_const(CGTarget* tg, Operand dst, ConstBytes cb) {
    820   WTarget* t = (WTarget*)tg;
    821   if (t->dead) return;
    822   if (dst.kind != OPK_REG) wfail(t, "wasm: load_const dst must be REG");
    823   WasmValType vt = valtype_for_type(t, cb.type);
    824   WIR* w = wir_push(t);
    825   w->dst = dst.v.reg;
    826   w->type = cb.type;
    827   w->cls = dst.cls;
    828   if (vt == WASM_VAL_F32) {
    829     if (cb.size != 4) wfail(t, "wasm: f32 const must be 4 bytes");
    830     float f;
    831     memcpy(&f, cb.bytes, 4);
    832     w->op = WIR_LOAD_CONST_F;
    833     w->fp_imm = (double)f;
    834   } else if (vt == WASM_VAL_F64) {
    835     if (cb.size != 8) wfail(t, "wasm: f64 const must be 8 bytes");
    836     double v;
    837     memcpy(&v, cb.bytes, 8);
    838     w->op = WIR_LOAD_CONST_F;
    839     w->fp_imm = v;
    840   } else {
    841     i64 v = 0;
    842     memcpy(&v, cb.bytes, cb.size < 8 ? cb.size : 8u);
    843     /* Sign-extend for small signed types so the immediate has the expected
    844      * bit pattern. */
    845     if (cb.size == 1)
    846       v = (i64)(i8)v;
    847     else if (cb.size == 2)
    848       v = (i64)(i16)v;
    849     else if (cb.size == 4)
    850       v = (i64)(i32)v;
    851     w->op = WIR_LOAD_IMM;
    852     w->imm = v;
    853   }
    854 }
    855 
    856 void wasm_copy(CGTarget* tg, Operand dst, Operand src) {
    857   WTarget* t = (WTarget*)tg;
    858   if (t->dead) return;
    859   if (dst.kind != OPK_REG || src.kind != OPK_REG)
    860     wfail(t, "wasm: copy operands must both be REG");
    861   WIR* w = wir_push(t);
    862   w->op = WIR_COPY;
    863   w->dst = dst.v.reg;
    864   w->a = src.v.reg;
    865   w->type = dst.type;
    866 }
    867 
    868 void wasm_binop(CGTarget* tg, BinOp op, Operand dst, Operand a, Operand b) {
    869   WTarget* t = (WTarget*)tg;
    870   if (t->dead) return;
    871   if (dst.kind != OPK_REG) wfail(t, "wasm: binop dst must be REG");
    872   WIR* w = wir_push(t);
    873   w->op = WIR_BINOP;
    874   w->cgop = (u8)op;
    875   w->dst = dst.v.reg;
    876   wir_capture_operand(w, 0, a);
    877   wir_capture_operand(w, 1, b);
    878   w->type = dst.type;
    879   w->cls = dst.cls;
    880 }
    881 
    882 void wasm_unop(CGTarget* tg, UnOp op, Operand dst, Operand a) {
    883   WTarget* t = (WTarget*)tg;
    884   if (t->dead) return;
    885   if (dst.kind != OPK_REG) wfail(t, "wasm: unop dst must be REG");
    886   WIR* w = wir_push(t);
    887   w->op = WIR_UNOP;
    888   w->cgop = (u8)op;
    889   w->dst = dst.v.reg;
    890   wir_capture_operand(w, 0, a);
    891   w->type = dst.type;
    892   w->cls = dst.cls;
    893 }
    894 
    895 void wasm_cmp(CGTarget* tg, CmpOp op, Operand dst, Operand a, Operand b) {
    896   WTarget* t = (WTarget*)tg;
    897   if (t->dead) return;
    898   if (dst.kind != OPK_REG) wfail(t, "wasm: cmp dst must be REG");
    899   WIR* w = wir_push(t);
    900   w->op = WIR_CMP;
    901   w->cgop = (u8)op;
    902   w->dst = dst.v.reg;
    903   wir_capture_operand(w, 0, a);
    904   wir_capture_operand(w, 1, b);
    905   w->type = dst.type;
    906   w->type2 = a.type ? a.type : b.type;
    907   w->cls = dst.cls;
    908 }
    909 
    910 void wasm_convert(CGTarget* tg, ConvKind ck, Operand dst, Operand src) {
    911   WTarget* t = (WTarget*)tg;
    912   if (t->dead) return;
    913   if (dst.kind != OPK_REG) wfail(t, "wasm: convert dst must be REG");
    914   WIR* w = wir_push(t);
    915   w->op = WIR_CONVERT;
    916   w->cgop = (u8)ck;
    917   w->dst = dst.v.reg;
    918   wir_capture_operand(w, 0, src);
    919   w->type = dst.type;
    920   w->type2 = src.type;
    921   w->cls = dst.cls;
    922 }
    923 
    924 /* Build (or reuse) the wasm typeidx for a function-typed indirect call. The
    925  * signature shape must exactly match a direct call to the same C type:
    926  * - hidden i32 sret pointer prepended when ABI has_sret
    927  * - per-param: i32 pointer for ABI_ARG_INDIRECT, else the DIRECT scalar
    928  *   produced by the wasm32 BasicCABI classifier (IGNORE params dropped)
    929  * - result: empty when has_sret, else the DIRECT scalar
    930  *
    931  * call_indirect's runtime type check compares this typeidx against the
    932  * funcref's recorded type, so any mismatch with the direct-call path would
    933  * trap. The temporary WasmFunc is stack-allocated; wasm_intern_func_type
    934  * copies the param array on insertion. */
    935 /* Translate an ABI function signature into the wasm-level param/result list.
    936  * Used both for indirect-call signature interning and for import-function
    937  * type synthesis. `what` names the call site in diagnostics. Returns the
    938  * interned type index. The caller-provided buffer `params` (length `cap`) is
    939  * filled from index 0; *nparams_out is the count written. */
    940 static u32 abi_to_wasm_func_type(WTarget* t, const ABIFuncInfo* abi,
    941                                  WasmValType* params, u32 cap, u32* nparams_out,
    942                                  WasmValType* result_out, u32* nresults_out,
    943                                  const char* what) {
    944   WasmFunc tmp;
    945   /* results is a pointer now (multi-value); back it with a small stack buffer
    946    * just like the caller-supplied params buffer, so tmp.results[0] = ... below
    947    * is not a NULL deref. A direct return lowers to at most one result. */
    948   WasmValType results[4];
    949   memset(&tmp, 0, sizeof tmp);
    950   tmp.params = params;
    951   tmp.cap_params = cap;
    952   tmp.results = results;
    953   tmp.cap_results = 4u;
    954   if (abi->has_sret) {
    955     if (tmp.nparams >= cap) wfail(t, "wasm: %s has too many params", what);
    956     params[tmp.nparams++] = WASM_VAL_I32;
    957   }
    958   for (u32 i = 0; i < abi->nparams; ++i) {
    959     const ABIArgInfo* ai = &abi->params[i];
    960     if (ai->kind == ABI_ARG_IGNORE) continue;
    961     if (tmp.nparams >= cap) wfail(t, "wasm: %s has too many params", what);
    962     if (ai->kind == ABI_ARG_INDIRECT) {
    963       params[tmp.nparams++] = WASM_VAL_I32;
    964     } else {
    965       if (ai->nparts != 1)
    966         wfail(t, "wasm: %s has multi-part DIRECT param (unsupported)", what);
    967       const ABIArgPart* p = &ai->parts[0];
    968       params[tmp.nparams++] = valtype_for_size_kind(
    969           t, p->size, p->cls == ABI_CLASS_FP ? ABI_SC_FLOAT : ABI_SC_INT);
    970     }
    971   }
    972   /* Variadic functions take one hidden trailing i32 va_ptr — the address of
    973    * the caller-packed varargs buffer in linear memory. See wasm_call's
    974    * variadic packing and wasm_va_start in this file. */
    975   if (abi->variadic) {
    976     if (tmp.nparams >= cap) wfail(t, "wasm: %s has too many params", what);
    977     params[tmp.nparams++] = WASM_VAL_I32;
    978   }
    979   tmp.nresults = 0;
    980   if (!abi->has_sret && abi->ret.kind == ABI_ARG_DIRECT &&
    981       abi->ret.nparts == 1) {
    982     const ABIArgPart* p = &abi->ret.parts[0];
    983     tmp.results[0] = valtype_for_size_kind(
    984         t, p->size, p->cls == ABI_CLASS_FP ? ABI_SC_FLOAT : ABI_SC_INT);
    985     tmp.nresults = 1;
    986   }
    987   if (nparams_out) *nparams_out = tmp.nparams;
    988   if (result_out && tmp.nresults) *result_out = tmp.results[0];
    989   if (nresults_out) *nresults_out = tmp.nresults;
    990   return wasm_intern_func_type(t->c, t->module, &tmp);
    991 }
    992 
    993 static u32 intern_indirect_signature(WTarget* t, const ABIFuncInfo* abi) {
    994   WasmValType params[64];
    995   return abi_to_wasm_func_type(t, abi, params, 64u, NULL, NULL, NULL,
    996                                "indirect call");
    997 }
    998 
    999 /* Promote `f` (already allocated for `sym` via sym_to_wasm_func) into a wasm
   1000  * `(import "<module>" "<field>" (func ...))` entry. The signature is
   1001  * synthesized from the supplied ABIFuncInfo, mirroring the layout the
   1002  * caller-side WIR_CALL pushes onto the stack: hidden i32 sret-pointer when
   1003  * has_sret, followed by lowered params, with a single i32/i64/f32/f64
   1004  * result for direct, non-sret returns. Module/field default to "env" / the
   1005  * symbol's name; either may be overridden by
   1006  * `__attribute__((import_module/import_name))`. */
   1007 static void promote_import_func(WTarget* t, ObjSymId sym, WasmFunc* f,
   1008                                 const ABIFuncInfo* abi) {
   1009   Heap* h = t->c->ctx->heap;
   1010   const ObjSym* os;
   1011   const char* sym_name;
   1012   size_t sym_name_len = 0;
   1013   Sym attr_module = 0;
   1014   Sym attr_name = 0;
   1015   const char* mod_str = "env";
   1016   size_t mod_len = sizeof("env") - 1u;
   1017   if (!t->module) return;
   1018   if (f->is_import) return;
   1019   os = obj_symbol_get(t->obj, sym);
   1020   if (!os) return;
   1021   if (os->section_id != OBJ_SEC_NONE) return; /* already defined locally */
   1022   if (os->kind != SK_UNDEF && os->kind != SK_FUNC) return;
   1023   if (!abi)
   1024     wfail(t,
   1025           "wasm: cannot synthesize import signature for '%s' "
   1026           "(missing ABI info)",
   1027           pool_sym_cstr(t->c->global, os->name, NULL));
   1028   if (f->ninsns != 0)
   1029     wfail(t, "wasm: cannot promote function with emitted body to import");
   1030   /* Synthesize the wasm type from the ABI. Diagnoses unsupported shapes
   1031    * (varargs => multi-part DIRECT or extra parts; by-value aggregates that
   1032    * the ABI didn't already lower to ABI_ARG_INDIRECT) by naming the import
   1033    * symbol so the error points at the C declaration. */
   1034   WasmValType params[64];
   1035   u32 nparams = 0;
   1036   WasmValType result_vt = 0;
   1037   u32 nresults = 0;
   1038   char what[160];
   1039   sym_name = pool_sym_cstr(t->c->global, os->name, &sym_name_len);
   1040   if (!sym_name) sym_name = "(anonymous)";
   1041   /* Snprintf-free: build a short context string by hand to avoid pulling in
   1042    * stdio. The buffer is large enough for any plausible C identifier. */
   1043   {
   1044     const char* prefix = "import '";
   1045     const char* suffix = "'";
   1046     size_t plen = 8u; /* strlen(prefix) */
   1047     size_t slen = 1u; /* strlen(suffix) */
   1048     size_t nlen = sym_name_len ? sym_name_len : strlen(sym_name);
   1049     if (nlen > sizeof(what) - plen - slen - 1u)
   1050       nlen = sizeof(what) - plen - slen - 1u;
   1051     memcpy(what, prefix, plen);
   1052     memcpy(what + plen, sym_name, nlen);
   1053     memcpy(what + plen + nlen, suffix, slen);
   1054     what[plen + nlen + slen] = 0;
   1055   }
   1056   f->typeidx = abi_to_wasm_func_type(t, abi, params, 64u, &nparams, &result_vt,
   1057                                      &nresults, what);
   1058   f->has_typeidx = 1;
   1059   /* Mirror the synthesized params/results onto the WasmFunc so the import
   1060    * encoder writes the matching signature. */
   1061   wasm_func_set_params(t->c, t->module, f, params, nparams);
   1062   wasm_func_set_results(t->c, t->module, f, &result_vt, nresults);
   1063   /* Resolve module/name overrides set via __attribute__((import_module/
   1064    * import_name)) on the C declaration. */
   1065   (void)wasm_imports_get(t->obj, os->name, &attr_module, &attr_name);
   1066   if (attr_module) mod_str = pool_sym_cstr(t->c->global, attr_module, &mod_len);
   1067   const char* name_str =
   1068       attr_name ? pool_sym_cstr(t->c->global, attr_name, &sym_name_len)
   1069                 : sym_name;
   1070   size_t name_len = attr_name
   1071                         ? sym_name_len
   1072                         : (sym_name_len ? sym_name_len : strlen(name_str));
   1073   f->is_import = 1;
   1074   {
   1075     char* m = (char*)h->alloc(h, mod_len + 1u, 1);
   1076     if (!m) wfail(t, "wasm: out of memory");
   1077     memcpy(m, mod_str, mod_len);
   1078     m[mod_len] = 0;
   1079     f->import_module = m;
   1080   }
   1081   {
   1082     char* n = (char*)h->alloc(h, name_len + 1u, 1);
   1083     if (!n) wfail(t, "wasm: out of memory");
   1084     memcpy(n, name_str, name_len);
   1085     n[name_len] = 0;
   1086     f->import_name = n;
   1087   }
   1088 }
   1089 
   1090 void wasm_call(CGTarget* tg, const CGCallDesc* d) {
   1091   WTarget* t = (WTarget*)tg;
   1092   if (t->dead) return;
   1093   int is_indirect = (d->callee.kind != OPK_GLOBAL);
   1094   if (is_indirect && d->callee.kind != OPK_REG)
   1095     wfail(t, "wasm: indirect call callee must be a register (got opkind %u)",
   1096           (unsigned)d->callee.kind);
   1097   Heap* h = t->c->ctx->heap;
   1098   int callee_has_sret = (d->abi && d->abi->has_sret) ? 1 : 0;
   1099   int callee_variadic = (d->abi && d->abi->variadic) ? 1 : 0;
   1100   int is_tail = (d->flags & CG_CALL_TAIL) ? 1 : 0;
   1101   if (is_tail) {
   1102     /* Realizability is decided by CG via wasm_ir_tail_call_unrealizable_reason
   1103      * (target.c) before CG_CALL_TAIL is set: variadic tails are rejected there,
   1104      * and sret tails forward the incoming sret pointer (handled in WIR emit).
   1105      */
   1106     ensure_module(t);
   1107     t->module->features |= WASM_FEATURE_TAIL_CALLS;
   1108   }
   1109   u32 nfixed = (u32)d->nargs;
   1110   u32 nvar = 0u;
   1111   if (callee_variadic) {
   1112     if (d->nargs < d->abi->nparams)
   1113       wfail(t, "wasm: variadic call has fewer args (%u) than fixed params (%u)",
   1114             (unsigned)d->nargs, (unsigned)d->abi->nparams);
   1115     nfixed = d->abi->nparams;
   1116     nvar = (u32)d->nargs - nfixed;
   1117   }
   1118   WIR* w = wir_push(t);
   1119   if (is_indirect) {
   1120     if (!d->abi) wfail(t, "wasm: indirect call without ABIFuncInfo");
   1121     ensure_module(t);
   1122     w->op = WIR_CALL_INDIRECT;
   1123     w->a = d->callee.v.reg;
   1124     w->imm = (i64)intern_indirect_signature(t, d->abi);
   1125   } else {
   1126     w->op = WIR_CALL;
   1127     w->call_sym = d->callee.v.global.sym;
   1128     /* Direct calls into externally-defined functions become wasm imports.
   1129      * Synthesize the import signature now while the ABI is available — the
   1130      * WIR emit loop only has the symbol index. The C frontend mints SK_FUNC
   1131      * for `extern foo(...)` declarations; the "undefined" signal is
   1132      * `section_id == OBJ_SEC_NONE`. SK_UNDEF can appear when a symbol's
   1133      * kind hasn't been pinned down yet. */
   1134     {
   1135       const ObjSym* os = obj_symbol_get(t->obj, d->callee.v.global.sym);
   1136       if (os && os->section_id == OBJ_SEC_NONE &&
   1137           (os->kind == SK_UNDEF || os->kind == SK_FUNC)) {
   1138         WasmFunc* f;
   1139         ensure_module(t);
   1140         (void)sym_to_wasm_func(t, d->callee.v.global.sym, &f);
   1141         if (!f->is_import)
   1142           promote_import_func(t, d->callee.v.global.sym, f, d->abi);
   1143       }
   1144     }
   1145   }
   1146   w->call_narg = nfixed;
   1147   w->type = d->ret.type;
   1148   w->call_has_sret = (u8)callee_has_sret;
   1149   w->call_variadic = (u8)callee_variadic;
   1150   w->call_tail = (u8)is_tail;
   1151   w->call_nvar = nvar;
   1152   if (callee_variadic) ensure_linear_memory(t);
   1153   if (callee_has_sret) {
   1154     /* Caller allocated a frame slot for the aggregate result via
   1155      * api_alloc_call_ret_storage; pass its address as the prepended i32. */
   1156     w->call_sret_addr = d->ret.storage;
   1157     ensure_linear_memory(t);
   1158   }
   1159   if (nfixed) {
   1160     w->call_args = (Reg*)h->alloc(h, sizeof(Reg) * nfixed, _Alignof(Reg));
   1161     w->call_arg_imms = (i64*)h->alloc(h, sizeof(i64) * nfixed, _Alignof(i64));
   1162     w->call_arg_kinds = (u8*)h->alloc(h, nfixed, 1);
   1163     w->call_arg_types = (KitCgTypeId*)h->alloc(h, sizeof(KitCgTypeId) * nfixed,
   1164                                                _Alignof(KitCgTypeId));
   1165     w->call_arg_addrs =
   1166         (Operand*)h->alloc(h, sizeof(Operand) * nfixed, _Alignof(Operand));
   1167     memset(w->call_arg_addrs, 0, sizeof(Operand) * nfixed);
   1168     for (u32 i = 0; i < nfixed; ++i) {
   1169       const CGABIValue* av = &d->args[i];
   1170       w->call_arg_types[i] = av->type;
   1171       int is_indirect = (av->abi && av->abi->kind == ABI_ARG_INDIRECT);
   1172       if (is_indirect) {
   1173         if (av->storage.kind != OPK_LOCAL && av->storage.kind != OPK_INDIRECT &&
   1174             av->storage.kind != OPK_GLOBAL) {
   1175           wfail(t, "wasm: byval call arg %u storage kind %u must be an address",
   1176                 i, (unsigned)av->storage.kind);
   1177         }
   1178         w->call_arg_kinds[i] = WOP_ADDR;
   1179         w->call_args[i] = REG_NONE;
   1180         w->call_arg_imms[i] = 0;
   1181         w->call_arg_addrs[i] = av->storage;
   1182         ensure_linear_memory(t);
   1183       } else if (av->storage.kind == OPK_REG) {
   1184         w->call_arg_kinds[i] = 0;
   1185         w->call_args[i] = av->storage.v.reg;
   1186         w->call_arg_imms[i] = 0;
   1187       } else if (av->storage.kind == OPK_IMM) {
   1188         w->call_arg_kinds[i] = 1;
   1189         w->call_args[i] = REG_NONE;
   1190         w->call_arg_imms[i] = av->storage.v.imm;
   1191       } else {
   1192         wfail(t,
   1193               "wasm: call arg %u has unsupported operand kind %u (only "
   1194               "REG/IMM scalar args are supported in v1)",
   1195               i, (unsigned)av->storage.kind);
   1196       }
   1197     }
   1198   }
   1199   if (nvar) {
   1200     w->call_var_regs = (Reg*)h->alloc(h, sizeof(Reg) * nvar, _Alignof(Reg));
   1201     w->call_var_imms = (i64*)h->alloc(h, sizeof(i64) * nvar, _Alignof(i64));
   1202     w->call_var_kinds = (u8*)h->alloc(h, nvar, 1);
   1203     w->call_var_types = (KitCgTypeId*)h->alloc(h, sizeof(KitCgTypeId) * nvar,
   1204                                                _Alignof(KitCgTypeId));
   1205     for (u32 i = 0; i < nvar; ++i) {
   1206       const CGABIValue* av = &d->args[nfixed + i];
   1207       const CgType* aty = av->type ? cg_type_get(t->c, av->type) : NULL;
   1208       w->call_var_types[i] = av->type;
   1209       if (aty &&
   1210           (aty->kind == KIT_CG_TYPE_RECORD || aty->kind == KIT_CG_TYPE_ARRAY)) {
   1211         wfail(t, "wasm target: aggregate variadic arg %u not yet supported", i);
   1212       }
   1213       if (av->storage.kind == OPK_REG) {
   1214         w->call_var_kinds[i] = WOP_REG;
   1215         w->call_var_regs[i] = av->storage.v.reg;
   1216         w->call_var_imms[i] = 0;
   1217       } else if (av->storage.kind == OPK_IMM) {
   1218         w->call_var_kinds[i] = WOP_IMM;
   1219         w->call_var_regs[i] = REG_NONE;
   1220         w->call_var_imms[i] = av->storage.v.imm;
   1221       } else {
   1222         wfail(t,
   1223               "wasm target: variadic arg %u has unsupported operand kind %u "
   1224               "(only REG/IMM scalar args supported in v1)",
   1225               i, (unsigned)av->storage.kind);
   1226       }
   1227     }
   1228   }
   1229   if (callee_has_sret) {
   1230     /* The call has no wasm result; the buffer pointed to by the sret arg
   1231      * holds the aggregate. */
   1232     w->dst = REG_NONE;
   1233   } else if (d->ret.storage.kind == OPK_REG &&
   1234              d->ret.storage.v.reg != REG_NONE) {
   1235     w->dst = d->ret.storage.v.reg;
   1236   } else {
   1237     w->dst = REG_NONE;
   1238   }
   1239 }
   1240 
   1241 void wasm_ret(CGTarget* tg, const CGABIValue* v) {
   1242   WTarget* t = (WTarget*)tg;
   1243   if (t->dead) return;
   1244   WIR* w = wir_push(t);
   1245   w->op = WIR_RET;
   1246   if (t->cur_has_sret && v && v->abi && v->abi->kind == ABI_ARG_INDIRECT) {
   1247     /* Aggregate sret return: emit a memcpy from av.storage to the buffer
   1248      * pointed to by the hidden sret parameter, then a void return. */
   1249     w->addr = v->storage;
   1250     w->type = v->type;
   1251     w->agg.size = (u32)abi_cg_sizeof(t->c->abi, v->type);
   1252     w->agg.align = 1u;
   1253     w->cgop = 1; /* tag: sret copy */
   1254     w->dst = REG_NONE;
   1255   } else if (v && v->storage.kind == OPK_REG && v->storage.v.reg != REG_NONE) {
   1256     w->dst = v->storage.v.reg;
   1257     w->type = v->type;
   1258   } else if (v && v->storage.kind == OPK_IMM) {
   1259     w->imm_kind = 1;
   1260     w->imm_a = v->storage.v.imm;
   1261     w->type = v->type;
   1262     w->dst = REG_NONE;
   1263   } else {
   1264     w->dst = REG_NONE;
   1265   }
   1266   t->dead = 1;
   1267 }
   1268 
   1269 void wasm_load(CGTarget* tg, Operand dst, Operand addr, MemAccess mem) {
   1270   WTarget* t = (WTarget*)tg;
   1271   if (t->dead) return;
   1272   if (dst.kind != OPK_REG) wfail(t, "wasm: load dst must be REG");
   1273   if (addr.kind != OPK_LOCAL ||
   1274       slot_for(t, addr.v.frame_slot)->kind == W_SLOT_STACK)
   1275     ensure_linear_memory(t);
   1276   WIR* w = wir_push(t);
   1277   w->op = (addr.kind == OPK_LOCAL &&
   1278            slot_for(t, addr.v.frame_slot)->kind == W_SLOT_LOCAL)
   1279               ? WIR_LOAD_LOCAL
   1280               : WIR_LOAD_MEM;
   1281   w->dst = dst.v.reg;
   1282   w->addr = addr;
   1283   if (addr.kind == OPK_LOCAL) {
   1284     WSlot* s = slot_for(t, addr.v.frame_slot);
   1285     w->imm =
   1286         (w->op == WIR_LOAD_LOCAL) ? (i64)s->wasm_local : (i64)addr.v.frame_slot;
   1287   }
   1288   w->mem = mem;
   1289   w->type = dst.type;
   1290   w->cls = dst.cls;
   1291 }
   1292 
   1293 void wasm_store(CGTarget* tg, Operand addr, Operand src, MemAccess mem) {
   1294   WTarget* t = (WTarget*)tg;
   1295   if (t->dead) return;
   1296   if (addr.kind != OPK_LOCAL ||
   1297       slot_for(t, addr.v.frame_slot)->kind == W_SLOT_STACK)
   1298     ensure_linear_memory(t);
   1299   WIR* w = wir_push(t);
   1300   w->op = (addr.kind == OPK_LOCAL &&
   1301            slot_for(t, addr.v.frame_slot)->kind == W_SLOT_LOCAL)
   1302               ? WIR_STORE_LOCAL
   1303               : WIR_STORE_MEM;
   1304   w->addr = addr;
   1305   if (addr.kind == OPK_LOCAL) {
   1306     WSlot* s = slot_for(t, addr.v.frame_slot);
   1307     w->imm = (w->op == WIR_STORE_LOCAL) ? (i64)s->wasm_local
   1308                                         : (i64)addr.v.frame_slot;
   1309   }
   1310   wir_capture_operand(w, 0, src);
   1311   w->mem = mem;
   1312   /* The store's value type is the accessed type. When storing through a
   1313    * pointer register, addr.type is the (possibly void) pointer rvalue type,
   1314    * not the pointee — so prefer the MemAccess type, which always describes
   1315    * the element being written, before falling back to the operands. */
   1316   w->type = mem.type ? mem.type : (addr.type ? addr.type : src.type);
   1317 }
   1318 
   1319 /* Variadic CG hooks. va_list on wasm32 is a single i32 pointer into a
   1320  * caller-packed buffer of 8-byte slots (see wasm_call's variadic packing).
   1321  * va_start writes the hidden va_ptr param into *ap; va_arg loads T from
   1322  * *ap and advances *ap by 8; va_end is a no-op; va_copy copies the i32. */
   1323 void wasm_va_start(CGTarget* tg, Operand ap_addr) {
   1324   WTarget* t = (WTarget*)tg;
   1325   if (t->dead) return;
   1326   if (!t->cur_is_variadic || t->va_ptr_param_local == 0xffffffffu)
   1327     wfail(t, "wasm: va_start in non-variadic function");
   1328   ensure_linear_memory(t);
   1329   WIR* w = wir_push(t);
   1330   w->op = WIR_VA_START;
   1331   w->addr = ap_addr;
   1332 }
   1333 
   1334 void wasm_va_arg(CGTarget* tg, Operand dst, Operand ap_addr, KitCgTypeId type) {
   1335   WTarget* t = (WTarget*)tg;
   1336   if (t->dead) return;
   1337   if (dst.kind != OPK_REG) wfail(t, "wasm: va_arg dst must be REG");
   1338   const CgType* aty = type ? cg_type_get(t->c, type) : NULL;
   1339   if (aty &&
   1340       (aty->kind == KIT_CG_TYPE_RECORD || aty->kind == KIT_CG_TYPE_ARRAY)) {
   1341     wfail(t, "wasm target: va_arg of aggregate type not yet supported");
   1342   }
   1343   ensure_linear_memory(t);
   1344   WIR* w = wir_push(t);
   1345   w->op = WIR_VA_ARG;
   1346   w->dst = dst.v.reg;
   1347   w->addr = ap_addr;
   1348   w->type = type;
   1349   w->cls = dst.cls;
   1350 }
   1351 
   1352 void wasm_va_end(CGTarget* tg, Operand ap_addr) {
   1353   WTarget* t = (WTarget*)tg;
   1354   (void)ap_addr;
   1355   if (t->dead) return;
   1356   /* No-op: nothing to release. */
   1357 }
   1358 
   1359 void wasm_va_copy(CGTarget* tg, Operand dst_ap_addr, Operand src_ap_addr) {
   1360   WTarget* t = (WTarget*)tg;
   1361   if (t->dead) return;
   1362   ensure_linear_memory(t);
   1363   WIR* w = wir_push(t);
   1364   w->op = WIR_VA_COPY;
   1365   w->addr = dst_ap_addr;
   1366   w->call_sret_addr = src_ap_addr; /* reused slot — see WIR comment */
   1367 }
   1368 
   1369 void wasm_addr_of(CGTarget* tg, Operand dst, Operand lv) {
   1370   WTarget* t = (WTarget*)tg;
   1371   if (t->dead) return;
   1372   if (dst.kind != OPK_REG) wfail(t, "wasm: addr_of dst must be REG");
   1373   if (lv.kind == OPK_LOCAL) {
   1374     WSlot* s = slot_for(t, lv.v.frame_slot);
   1375     if (s->kind == W_SLOT_LOCAL) {
   1376       u32 old_local = s->wasm_local;
   1377       promote_slot_to_stack(t, s);
   1378       WIR* st = wir_push(t);
   1379       st->op = WIR_STORE_MEM;
   1380       st->addr = lv;
   1381       st->type = s->type;
   1382       st->mem.type = s->type;
   1383       st->mem.size = s->size;
   1384       st->mem.align = s->align;
   1385       st->imm_kind = WOP_WASM_LOCAL;
   1386       st->imm_a = old_local;
   1387     }
   1388   } else {
   1389     ensure_linear_memory(t);
   1390   }
   1391   WIR* w = wir_push(t);
   1392   w->op = WIR_ADDR_OF;
   1393   w->dst = dst.v.reg;
   1394   w->addr = lv;
   1395   w->type = dst.type;
   1396   w->cls = dst.cls;
   1397 }
   1398 
   1399 void wasm_alloca(CGTarget* tg, Operand dst, Operand size, u32 align) {
   1400   WTarget* t = (WTarget*)tg;
   1401   if (t->dead) return;
   1402   ensure_linear_memory(t);
   1403   if (dst.kind != OPK_REG) wfail(t, "wasm: alloca dst must be REG");
   1404   ensure_stack_pointer(t);
   1405   t->has_stack_frame = 1;
   1406   WIR* w = wir_push(t);
   1407   w->op = WIR_ALLOCA;
   1408   w->dst = dst.v.reg;
   1409   wir_capture_operand(w, 0, size);
   1410   w->type = dst.type;
   1411   w->type2 = size.type;
   1412   w->cls = dst.cls;
   1413   w->imm = align ? align : 16u;
   1414 }
   1415 
   1416 void wasm_copy_bytes(CGTarget* tg, Operand dst, Operand src,
   1417                      AggregateAccess a) {
   1418   WTarget* t = (WTarget*)tg;
   1419   if (t->dead) return;
   1420   ensure_linear_memory(t);
   1421   WIR* w = wir_push(t);
   1422   w->op = WIR_COPY_BYTES;
   1423   w->addr = dst;
   1424   wir_capture_operand(w, 0, src);
   1425   w->agg = a;
   1426 }
   1427 
   1428 void wasm_set_bytes(CGTarget* tg, Operand dst, Operand byte,
   1429                     AggregateAccess a) {
   1430   WTarget* t = (WTarget*)tg;
   1431   if (t->dead) return;
   1432   WIR* w = wir_push(t);
   1433   w->op = WIR_SET_BYTES;
   1434   w->addr = dst;
   1435   wir_capture_operand(w, 0, byte);
   1436   w->agg = a;
   1437 }
   1438 
   1439 /* Atomic ops. CG forces `addr` to a REG and accepts reg-or-imm for value
   1440  * operands. Wasm only models seq_cst; the KitCgMemOrder argument is captured
   1441  * but not encoded — every emitted atomic op is sequentially consistent. The
   1442  * caller-provided MemAccess carries the type and natural alignment we need
   1443  * for the memarg width. */
   1444 static void atomic_require_addr_reg(WTarget* t, Operand addr,
   1445                                     const char* what) {
   1446   if (addr.kind != OPK_REG)
   1447     wfail(t, "wasm: %s address must be in a register (got opkind %u)", what,
   1448           (unsigned)addr.kind);
   1449 }
   1450 
   1451 void wasm_atomic_load(CGTarget* tg, Operand dst, Operand addr, MemAccess mem,
   1452                       KitCgMemOrder mo) {
   1453   WTarget* t = (WTarget*)tg;
   1454   (void)mo;
   1455   if (t->dead) return;
   1456   if (dst.kind != OPK_REG) wfail(t, "wasm: atomic_load dst must be REG");
   1457   atomic_require_addr_reg(t, addr, "atomic_load");
   1458   ensure_shared_memory(t);
   1459   WIR* w = wir_push(t);
   1460   w->op = WIR_ATOMIC_LOAD;
   1461   w->dst = dst.v.reg;
   1462   w->a = addr.v.reg;
   1463   w->mem = mem;
   1464   w->type = dst.type ? dst.type : mem.type;
   1465   w->cls = dst.cls;
   1466 }
   1467 
   1468 void wasm_atomic_store(CGTarget* tg, Operand addr, Operand src, MemAccess mem,
   1469                        KitCgMemOrder mo) {
   1470   WTarget* t = (WTarget*)tg;
   1471   (void)mo;
   1472   if (t->dead) return;
   1473   atomic_require_addr_reg(t, addr, "atomic_store");
   1474   if (src.kind != OPK_REG && src.kind != OPK_IMM)
   1475     wfail(t, "wasm: atomic_store value must be REG or IMM");
   1476   ensure_shared_memory(t);
   1477   WIR* w = wir_push(t);
   1478   w->op = WIR_ATOMIC_STORE;
   1479   w->a = addr.v.reg;
   1480   wir_capture_operand(w, 1, src);
   1481   w->mem = mem;
   1482   w->type = mem.type ? mem.type : src.type;
   1483 }
   1484 
   1485 void wasm_atomic_rmw(CGTarget* tg, KitCgAtomicOp op, Operand dst, Operand addr,
   1486                      Operand val, MemAccess mem, KitCgMemOrder mo) {
   1487   WTarget* t = (WTarget*)tg;
   1488   (void)mo;
   1489   if (t->dead) return;
   1490   if (dst.kind != OPK_REG) wfail(t, "wasm: atomic_rmw dst must be REG");
   1491   atomic_require_addr_reg(t, addr, "atomic_rmw");
   1492   if (val.kind != OPK_REG && val.kind != OPK_IMM)
   1493     wfail(t, "wasm: atomic_rmw value must be REG or IMM");
   1494   /* KIT_CG_ATOMIC_NAND has no native wasm-threads opcode; the linearizer
   1495    * expands it into an atomic cmpxchg retry loop (see WIR_ATOMIC_RMW). */
   1496   ensure_shared_memory(t);
   1497   WIR* w = wir_push(t);
   1498   w->op = WIR_ATOMIC_RMW;
   1499   w->cgop = (u8)op;
   1500   w->dst = dst.v.reg;
   1501   w->a = addr.v.reg;
   1502   wir_capture_operand(w, 1, val);
   1503   w->mem = mem;
   1504   w->type = dst.type ? dst.type : mem.type;
   1505   w->cls = dst.cls;
   1506 }
   1507 
   1508 void wasm_atomic_cas(CGTarget* tg, Operand prior, Operand ok, Operand addr,
   1509                      Operand expected, Operand desired, MemAccess mem,
   1510                      KitCgMemOrder success, KitCgMemOrder failure) {
   1511   WTarget* t = (WTarget*)tg;
   1512   (void)success;
   1513   (void)failure;
   1514   if (t->dead) return;
   1515   if (prior.kind != OPK_REG) wfail(t, "wasm: atomic_cas prior must be REG");
   1516   if (ok.kind != OPK_REG) wfail(t, "wasm: atomic_cas ok must be REG");
   1517   atomic_require_addr_reg(t, addr, "atomic_cas");
   1518   if (expected.kind != OPK_REG && expected.kind != OPK_IMM)
   1519     wfail(t, "wasm: atomic_cas expected must be REG or IMM");
   1520   if (desired.kind != OPK_REG && desired.kind != OPK_IMM)
   1521     wfail(t, "wasm: atomic_cas desired must be REG or IMM");
   1522   ensure_shared_memory(t);
   1523   WIR* w = wir_push(t);
   1524   w->op = WIR_ATOMIC_CAS;
   1525   w->dst = prior.v.reg;
   1526   w->dst2 = ok.v.reg;
   1527   w->a = addr.v.reg;
   1528   wir_capture_operand(w, 1, expected);
   1529   /* Capture desired into op_c/imm_kind_c/imm_c (third operand slot). */
   1530   if (desired.kind == OPK_REG) {
   1531     w->imm_kind_c = WOP_REG;
   1532     w->op_c = desired.v.reg;
   1533   } else {
   1534     w->imm_kind_c = WOP_IMM;
   1535     w->imm_c = desired.v.imm;
   1536   }
   1537   w->mem = mem;
   1538   w->type = prior.type ? prior.type : mem.type;
   1539   w->cls = prior.cls;
   1540   w->type2 = ok.type;
   1541 }
   1542 
   1543 void wasm_fence(CGTarget* tg, KitCgMemOrder mo) {
   1544   WTarget* t = (WTarget*)tg;
   1545   (void)mo;
   1546   if (t->dead) return;
   1547   /* Wasm atomic.fence does not require a memory to exist, but in practice it
   1548    * is meaningful only inside a module that has shared memory. We don't
   1549    * force-create memory here to avoid producing a bogus memory for fence-only
   1550    * modules. */
   1551   WIR* w = wir_push(t);
   1552   w->op = WIR_FENCE;
   1553 }
   1554 
   1555 /* Forward decls: defined further down. */
   1556 static WasmValType type_valtype(WTarget* t, KitCgTypeId ty);
   1557 void wasm_emit_unreachable(WTarget* t);
   1558 
   1559 /* Per-intrinsic-name diagnostic text. Used both by the recorder for
   1560  * SETJMP/LONGJMP (which we still reject) and for the fallback panic so
   1561  * users see "wasm target: __builtin_clz ..." instead of a numeric kind. */
   1562 static const char* intrin_name(IntrinKind k) {
   1563   switch (k) {
   1564     case INTRIN_NONE:
   1565       return "<none>";
   1566     case INTRIN_POPCOUNT:
   1567       return "__builtin_popcount";
   1568     case INTRIN_CTZ:
   1569       return "__builtin_ctz";
   1570     case INTRIN_CLZ:
   1571       return "__builtin_clz";
   1572     case INTRIN_BSWAP:
   1573       return "__builtin_bswap";
   1574     case INTRIN_SMUL_HIGH:
   1575       return "smul_high";
   1576     case INTRIN_UMUL_HIGH:
   1577       return "umul_high";
   1578     case INTRIN_MEMMOVE:
   1579       return "memmove";
   1580     case INTRIN_PREFETCH:
   1581       return "__builtin_prefetch";
   1582     case INTRIN_ASSUME_ALIGNED:
   1583       return "__builtin_assume_aligned";
   1584     case INTRIN_EXPECT:
   1585       return "__builtin_expect";
   1586     case INTRIN_TRAP:
   1587       return "__builtin_trap";
   1588     case INTRIN_SYSCALL:
   1589       return "__kit_syscall";
   1590     case INTRIN_SETJMP:
   1591       return "setjmp";
   1592     case INTRIN_LONGJMP:
   1593       return "longjmp";
   1594     case INTRIN_SADD_OVERFLOW:
   1595       return "__builtin_sadd_overflow";
   1596     case INTRIN_UADD_OVERFLOW:
   1597       return "__builtin_uadd_overflow";
   1598     case INTRIN_SSUB_OVERFLOW:
   1599       return "__builtin_ssub_overflow";
   1600     case INTRIN_USUB_OVERFLOW:
   1601       return "__builtin_usub_overflow";
   1602     case INTRIN_SMUL_OVERFLOW:
   1603       return "__builtin_smul_overflow";
   1604     case INTRIN_UMUL_OVERFLOW:
   1605       return "__builtin_umul_overflow";
   1606     case INTRIN_CPU_NOP:
   1607       return "cpu_nop";
   1608     case INTRIN_CPU_YIELD:
   1609       return "cpu_yield";
   1610     case INTRIN_WFI:
   1611       return "wfi";
   1612     case INTRIN_WFE:
   1613       return "wfe";
   1614     case INTRIN_SEV:
   1615       return "sev";
   1616     case INTRIN_ISB:
   1617       return "isb";
   1618     case INTRIN_DMB:
   1619       return "dmb";
   1620     case INTRIN_DSB:
   1621       return "dsb";
   1622     case INTRIN_IRQ_SAVE:
   1623       return "irq_save";
   1624     case INTRIN_IRQ_RESTORE:
   1625       return "irq_restore";
   1626     case INTRIN_IRQ_ENABLE:
   1627       return "irq_enable";
   1628     case INTRIN_IRQ_DISABLE:
   1629       return "irq_disable";
   1630     case INTRIN_FRAME_ADDRESS:
   1631       return "frame_address";
   1632     case INTRIN_RETURN_ADDRESS:
   1633       return "return_address";
   1634     case INTRIN_READCYCLECOUNTER:
   1635       return "readcyclecounter";
   1636     case INTRIN_STACK_GUARD:
   1637       return "stack_guard";
   1638   }
   1639   return "<unknown>";
   1640 }
   1641 
   1642 void wasm_intrinsic(CGTarget* tg, IntrinKind k, Operand* dst, u32 ndst,
   1643                     const Operand* args, u32 nargs) {
   1644   WTarget* t = (WTarget*)tg;
   1645   if (t->dead) return;
   1646 
   1647   switch (k) {
   1648     case INTRIN_TRAP:
   1649       wasm_emit_unreachable(t);
   1650       return;
   1651 
   1652     case INTRIN_PREFETCH:
   1653       /* No-op hint. */
   1654       return;
   1655 
   1656     case INTRIN_CPU_YIELD:
   1657       /* Portable spin-loop hint; wasm has no corresponding core instruction. */
   1658       return;
   1659 
   1660     case INTRIN_EXPECT:
   1661     case INTRIN_ASSUME_ALIGNED:
   1662       /* Pass-through hint: result = first argument. CG always allocates a
   1663        * dst reg for these; copy arg[0] there so downstream uses see the
   1664        * expected value. CG keeps the first arg as OPK_IMM when it was a
   1665        * literal so the constant flows through unchanged. */
   1666       if (ndst == 1 && nargs >= 1) {
   1667         if (args[0].kind == OPK_IMM) {
   1668           wasm_load_imm(tg, dst[0], args[0].v.imm);
   1669         } else {
   1670           wasm_copy(tg, dst[0], args[0]);
   1671         }
   1672       }
   1673       return;
   1674 
   1675     case INTRIN_MEMMOVE: {
   1676       /* memmove lowers to memory.copy, which is spec-defined to handle overlap
   1677        * correctly. CG forces (dst, src) to REG and passes size as OPK_IMM
   1678        * (kit_cg_memmove). */
   1679       if (nargs != 3 || args[0].kind != OPK_REG || args[1].kind != OPK_REG) {
   1680         compiler_panic(t->c, cur_loc(t),
   1681                        "wasm target: %s requires register pointers",
   1682                        intrin_name(k));
   1683         return;
   1684       }
   1685       if (args[2].kind != OPK_IMM) {
   1686         compiler_panic(t->c, cur_loc(t),
   1687                        "wasm target: %s with non-constant size is not yet "
   1688                        "supported",
   1689                        intrin_name(k));
   1690         return;
   1691       }
   1692       ensure_linear_memory(t);
   1693       AggregateAccess a;
   1694       memset(&a, 0, sizeof a);
   1695       a.size = (u32)args[2].v.imm;
   1696       a.align = 1;
   1697       WIR* w = wir_push(t);
   1698       w->op = WIR_COPY_BYTES;
   1699       w->addr = args[0];
   1700       wir_capture_operand(w, 0, args[1]);
   1701       w->agg = a;
   1702       return;
   1703     }
   1704 
   1705     case INTRIN_CLZ:
   1706     case INTRIN_CTZ:
   1707     case INTRIN_POPCOUNT:
   1708     case INTRIN_BSWAP: {
   1709       if (ndst != 1 || nargs != 1 || dst[0].kind != OPK_REG ||
   1710           args[0].kind != OPK_REG) {
   1711         compiler_panic(t->c, cur_loc(t),
   1712                        "wasm target: %s requires single REG operand",
   1713                        intrin_name(k));
   1714         return;
   1715       }
   1716       WIR* w = wir_push(t);
   1717       w->op = WIR_INTRINSIC;
   1718       w->cgop = (u8)k;
   1719       w->dst = dst[0].v.reg;
   1720       w->a = args[0].v.reg;
   1721       w->type = dst[0].type;
   1722       /* clz/ctz/popcount return int (i32) but operate at the operand's width
   1723        * (e.g. __builtin_ctzl over an i64). The wasm op width must follow the
   1724        * operand, with a wrap to the i32 dst afterward. type2 carries it. */
   1725       w->type2 = args[0].type;
   1726       w->cls = dst[0].cls;
   1727       return;
   1728     }
   1729 
   1730     case INTRIN_SADD_OVERFLOW:
   1731     case INTRIN_UADD_OVERFLOW:
   1732     case INTRIN_SSUB_OVERFLOW:
   1733     case INTRIN_USUB_OVERFLOW:
   1734     case INTRIN_SMUL_OVERFLOW:
   1735     case INTRIN_UMUL_OVERFLOW: {
   1736       if (ndst != 2 || nargs != 2 || dst[0].kind != OPK_REG ||
   1737           dst[1].kind != OPK_REG) {
   1738         compiler_panic(t->c, cur_loc(t),
   1739                        "wasm target: %s requires 2 args + 2 result regs",
   1740                        intrin_name(k));
   1741         return;
   1742       }
   1743       /* Reject i64 mul-overflow for now: wasm core has no widening 64x64
   1744        * multiply, so the standard expansion would need partial-product
   1745        * synthesis. 32-bit (the common shape on wasm32) is supported. */
   1746       WasmValType vt = type_valtype(t, dst[0].type);
   1747       if (vt == WASM_VAL_I64 &&
   1748           (k == INTRIN_SMUL_OVERFLOW || k == INTRIN_UMUL_OVERFLOW)) {
   1749         compiler_panic(t->c, cur_loc(t),
   1750                        "wasm target: 64-bit checked-overflow multiply is "
   1751                        "not yet supported");
   1752         return;
   1753       }
   1754       WIR* w = wir_push(t);
   1755       w->op = WIR_INTRINSIC;
   1756       w->cgop = (u8)k;
   1757       w->dst = dst[0].v.reg;
   1758       w->dst2 = dst[1].v.reg;
   1759       w->type = dst[0].type;
   1760       w->cls = dst[0].cls;
   1761       wir_capture_operand(w, 0, args[0]);
   1762       wir_capture_operand(w, 1, args[1]);
   1763       return;
   1764     }
   1765 
   1766     case INTRIN_SMUL_HIGH:
   1767     case INTRIN_UMUL_HIGH: {
   1768       if (ndst != 1 || nargs != 2 || dst[0].kind != OPK_REG) {
   1769         compiler_panic(t->c, cur_loc(t),
   1770                        "wasm target: %s requires 2 args + 1 result reg",
   1771                        intrin_name(k));
   1772         return;
   1773       }
   1774       WIR* w = wir_push(t);
   1775       w->op = WIR_INTRINSIC;
   1776       w->cgop = (u8)k;
   1777       w->dst = dst[0].v.reg;
   1778       w->type = dst[0].type;
   1779       w->cls = dst[0].cls;
   1780       wir_capture_operand(w, 0, args[0]);
   1781       wir_capture_operand(w, 1, args[1]);
   1782       return;
   1783     }
   1784 
   1785     case INTRIN_SETJMP:
   1786     case INTRIN_LONGJMP:
   1787       compiler_panic(t->c, cur_loc(t),
   1788                      "wasm target: %s is not yet supported (no exception/"
   1789                      "stack-unwind runtime)",
   1790                      intrin_name(k));
   1791       return;
   1792 
   1793     /* Baremetal/CPU-control intrinsics have no wasm lowering;
   1794      * kit_cg_target_supports_intrinsic reports them false so frontends
   1795      * diagnose before reaching here. Fall through to the generic panic. */
   1796     case INTRIN_CPU_NOP:
   1797     case INTRIN_WFI:
   1798     case INTRIN_WFE:
   1799     case INTRIN_SEV:
   1800     case INTRIN_ISB:
   1801     case INTRIN_DMB:
   1802     case INTRIN_DSB:
   1803     case INTRIN_IRQ_SAVE:
   1804     case INTRIN_IRQ_RESTORE:
   1805     case INTRIN_IRQ_ENABLE:
   1806     case INTRIN_IRQ_DISABLE:
   1807     case INTRIN_SYSCALL:
   1808     case INTRIN_READCYCLECOUNTER:
   1809     case INTRIN_STACK_GUARD:
   1810     /* No frame-pointer chain in wasm; reported unsupported up front. */
   1811     case INTRIN_FRAME_ADDRESS:
   1812     case INTRIN_RETURN_ADDRESS:
   1813     case INTRIN_NONE:
   1814       break;
   1815   }
   1816   compiler_panic(t->c, cur_loc(t),
   1817                  "wasm target: intrinsic %s not yet implemented",
   1818                  intrin_name(k));
   1819 }
   1820 
   1821 /* Inline asm v1 — see doc/WASM.md "Inline asm" for the surface contract.
   1822  *
   1823  * Template syntax: WAT instruction sequence. Inputs pre-pushed to the value
   1824  * stack (via local.get on synthetic input locals); outputs popped from the
   1825  * stack into synthetic output locals after the body. The snippet's
   1826  * local.get/set/tee with index < nin refers to the i-th input.
   1827  *
   1828  * Constraints: "r" → wasm local; "i" → const-folded; "m" → i32 address.
   1829  * Numeric tie-back constraints ("0","1",...) reuse the referenced output's
   1830  * slot (cg/asm.c expands them into duplicate input entries at the end).
   1831  *
   1832  * Disallowed in v1: escaping br/br_if/br_table, return/return_call*,
   1833  * call_indirect, snippet-internal locals, output count > 1, register
   1834  * clobbers (only `memory` is accepted). */
   1835 static WasmValType wasm_asm_operand_vt(WTarget* t, KitCgTypeId ty,
   1836                                        const char* what, SrcLoc loc) {
   1837   WasmValType vt;
   1838   if (!ty) wfail_at(t, loc, "wasm target: asm %s with no CG type", what);
   1839   vt = valtype_for_type(t, ty);
   1840   if (vt != WASM_VAL_I32 && vt != WASM_VAL_I64 && vt != WASM_VAL_F32 &&
   1841       vt != WASM_VAL_F64)
   1842     wfail_at(t, loc, "wasm target: asm %s of non-scalar type not supported",
   1843              what);
   1844   return vt;
   1845 }
   1846 
   1847 void wasm_asm_block(CGTarget* tg, const char* tmpl, const AsmConstraint* outs,
   1848                     u32 nout, Operand* out_ops, const AsmConstraint* ins,
   1849                     u32 nin, const Operand* in_ops, const Sym* clob,
   1850                     u32 nclob) {
   1851   WTarget* t = (WTarget*)tg;
   1852   Heap* h = t->c->ctx->heap;
   1853   Sym sym_memory;
   1854   WasmFunc scratch;
   1855   SrcLoc loc = cur_loc(t);
   1856   u32 depth;
   1857   u32 i;
   1858 
   1859   if (t->dead) return;
   1860 
   1861   /* Clobber policy: only `memory` is meaningful on wasm (effective no-op
   1862    * because cg/asm.c already spilled live SSA values). Reject named-register
   1863    * clobbers explicitly. */
   1864   sym_memory = pool_intern_slice(t->c->global, SLICE_LIT("memory"));
   1865   for (i = 0; i < nclob; ++i) {
   1866     if (clob[i] != sym_memory)
   1867       wfail_at(t, loc, "wasm target: asm register clobbers not yet supported");
   1868   }
   1869   for (i = 0; i < nout; ++i) {
   1870     if (outs[i].reg)
   1871       wfail_at(t, loc, "wasm target: asm hard-register operands not supported");
   1872   }
   1873   for (i = 0; i < nin; ++i) {
   1874     if (ins[i].reg)
   1875       wfail_at(t, loc, "wasm target: asm hard-register operands not supported");
   1876   }
   1877 
   1878   /* Build a scratch WasmFunc with the synthetic signature. Layout is:
   1879    *   params  = input types (indices 0 .. nin-1)
   1880    *   locals  = output types (indices nin .. nin+nout-1)
   1881    *   results = empty
   1882    * Snippets use local.get/set/tee N to read/write either side. The author
   1883    * is responsible for writing each output via local.set N (>= nin); empty
   1884    * snippets paired with `+r` / numeric tieback constraints get identity
   1885    * behavior because the input and output share a wasm local at lowering. */
   1886   memset(&scratch, 0, sizeof scratch);
   1887   for (i = 0; i < nin; ++i) {
   1888     WasmValType vt = wasm_asm_operand_vt(t, ins[i].type, "input operand", loc);
   1889     /* Constraint-specific checks: "i" requires an immediate operand; "m"
   1890      * requires an indirect (i32 address). */
   1891     if (ins[i].str && ins[i].str[0] == 'i' && in_ops[i].kind != OPK_IMM)
   1892       wfail_at(t, loc, "wasm target: asm 'i' input must be an immediate");
   1893     if (ins[i].str && ins[i].str[0] == 'm') {
   1894       if (in_ops[i].kind != OPK_INDIRECT)
   1895         wfail_at(t, loc, "wasm target: asm 'm' input must be indirect");
   1896       vt = WASM_VAL_I32;
   1897     }
   1898     wasm_func_push_param(t->c, t->module, &scratch, vt);
   1899   }
   1900   for (i = 0; i < nout; ++i) {
   1901     WasmValType vt =
   1902         wasm_asm_operand_vt(t, outs[i].type, "output operand", loc);
   1903     wasm_func_push_local(t->c, t->module, &scratch, vt);
   1904   }
   1905   /* No declared result — outputs are read from locals at the end of the
   1906    * lowering, not popped from the value stack. */
   1907 
   1908   /* Parse the template into scratch.insns. */
   1909   wasm_parse_wat_body(t->c, t->module, &scratch, tmpl, strlen(tmpl), loc);
   1910 
   1911   /* Walk the parsed body to reject constructs that escape or aren't
   1912    * supported in v1. Track control depth so br/br_if/br_table with imm >=
   1913    * depth (i.e. would branch out of the snippet) are rejected. */
   1914   depth = 0;
   1915   for (i = 0; i < scratch.ninsns; ++i) {
   1916     WasmInsn* in = &scratch.insns[i];
   1917     switch (in->kind) {
   1918       case WASM_INSN_BLOCK:
   1919       case WASM_INSN_LOOP:
   1920       case WASM_INSN_IF:
   1921         depth++;
   1922         break;
   1923       case WASM_INSN_END:
   1924         if (depth) depth--;
   1925         break;
   1926       case WASM_INSN_BR:
   1927       case WASM_INSN_BR_IF:
   1928         if (in->imm < 0 || (u64)in->imm >= depth)
   1929           wfail_at(t, in->loc,
   1930                    "wasm target: asm template branch escapes snippet");
   1931         break;
   1932       case WASM_INSN_BR_TABLE: {
   1933         u32 k;
   1934         for (k = 0; k < in->ntargets; ++k)
   1935           if (in->targets[k] >= depth)
   1936             wfail_at(t, in->loc,
   1937                      "wasm target: asm template br_table escapes snippet");
   1938         break;
   1939       }
   1940       case WASM_INSN_RETURN:
   1941       case WASM_INSN_RETURN_CALL:
   1942       case WASM_INSN_RETURN_CALL_INDIRECT:
   1943       case WASM_INSN_RETURN_CALL_REF:
   1944         wfail_at(t, in->loc,
   1945                  "wasm target: return/tail-call in asm template not "
   1946                  "supported");
   1947         break;
   1948       case WASM_INSN_LOCAL_GET:
   1949       case WASM_INSN_LOCAL_SET:
   1950       case WASM_INSN_LOCAL_TEE:
   1951         if (in->imm < 0 || (u64)in->imm >= (u64)(nin + nout))
   1952           wfail_at(t, in->loc,
   1953                    "wasm target: asm template references local beyond "
   1954                    "declared operands (snippet-internal locals not "
   1955                    "supported)");
   1956         break;
   1957       default:
   1958         break;
   1959     }
   1960   }
   1961 
   1962   /* Validate the body against the synthetic signature. */
   1963   wasm_validate_func(t->c, t->module, &scratch);
   1964 
   1965   {
   1966     /* Build the WIR_ASM_BLOCK payload. raw_insns is owned by the WIR; the
   1967      * other arrays are too. Sizes are zero-when-empty per the WIR teardown
   1968      * conventions. */
   1969     WIR* w = wir_push(t);
   1970     w->op = WIR_ASM_BLOCK;
   1971     w->raw_ninsns = scratch.ninsns;
   1972     if (scratch.ninsns) {
   1973       w->raw_insns = (WasmInsn*)h->alloc(h, sizeof(WasmInsn) * scratch.ninsns,
   1974                                          _Alignof(WasmInsn));
   1975       if (!w->raw_insns) wfail(t, "wasm: out of memory");
   1976       memcpy(w->raw_insns, scratch.insns, sizeof(WasmInsn) * scratch.ninsns);
   1977     }
   1978     w->asm_nin = nin;
   1979     w->asm_nout = nout;
   1980     if (nin) {
   1981       w->asm_in_kinds = (u8*)h->alloc(h, nin, 1);
   1982       w->asm_in_imms = (i64*)h->alloc(h, sizeof(i64) * nin, _Alignof(i64));
   1983       w->asm_in_regs = (Reg*)h->alloc(h, sizeof(Reg) * nin, _Alignof(Reg));
   1984       w->asm_in_types = (KitCgTypeId*)h->alloc(h, sizeof(KitCgTypeId) * nin,
   1985                                                _Alignof(KitCgTypeId));
   1986       w->asm_in_share_out = (i32*)h->alloc(h, sizeof(i32) * nin, _Alignof(i32));
   1987       if (!w->asm_in_kinds || !w->asm_in_imms || !w->asm_in_regs ||
   1988           !w->asm_in_types || !w->asm_in_share_out)
   1989         wfail(t, "wasm: out of memory");
   1990       for (i = 0; i < nin; ++i) w->asm_in_share_out[i] = -1;
   1991       for (i = 0; i < nin; ++i) {
   1992         Operand op = in_ops[i];
   1993         /* Numeric tieback constraints ("0".."9") share the matching
   1994          * output's wasm local. cg/asm.c also rewrites +r inout outputs
   1995          * into duplicate inputs at the tail of ins[], using the same
   1996          * numeric encoding. */
   1997         const char* s = ins[i].str;
   1998         if (s && s[0] >= '0' && s[0] <= '9' && s[1] == '\0') {
   1999           int idx = s[0] - '0';
   2000           if ((u32)idx < nout) w->asm_in_share_out[i] = idx;
   2001         }
   2002         switch (op.kind) {
   2003           case OPK_REG:
   2004             w->asm_in_kinds[i] = WOP_REG;
   2005             w->asm_in_regs[i] = op.v.reg;
   2006             w->asm_in_imms[i] = 0;
   2007             w->asm_in_types[i] = op.type ? op.type : ins[i].type;
   2008             break;
   2009           case OPK_IMM:
   2010             w->asm_in_kinds[i] = WOP_IMM;
   2011             w->asm_in_regs[i] = REG_NONE;
   2012             w->asm_in_imms[i] = op.v.imm;
   2013             w->asm_in_types[i] = op.type ? op.type : ins[i].type;
   2014             break;
   2015           case OPK_INDIRECT:
   2016             /* For "m" constraint: the input local holds the i32 address
   2017              * `base + ofs` of the lvalue. We re-use asm_in_imms (unused
   2018              * for WOP_REG operands) to carry the displacement so the
   2019              * linearizer can splice in `i32.const ofs; i32.add` after
   2020              * pushing the base local. */
   2021             w->asm_in_kinds[i] = WOP_REG;
   2022             w->asm_in_regs[i] = op.v.ind.base;
   2023             w->asm_in_imms[i] = (i64)op.v.ind.ofs;
   2024             w->asm_in_types[i] = builtin_id(KIT_CG_BUILTIN_I32);
   2025             break;
   2026           default:
   2027             wfail_at(t, loc, "wasm target: unsupported asm input operand kind");
   2028         }
   2029       }
   2030     }
   2031     if (nout) {
   2032       w->asm_out_regs = (Reg*)h->alloc(h, sizeof(Reg) * nout, _Alignof(Reg));
   2033       w->asm_out_types = (KitCgTypeId*)h->alloc(h, sizeof(KitCgTypeId) * nout,
   2034                                                 _Alignof(KitCgTypeId));
   2035       if (!w->asm_out_regs || !w->asm_out_types)
   2036         wfail(t, "wasm: out of memory");
   2037       for (i = 0; i < nout; ++i) {
   2038         if (out_ops[i].kind != OPK_REG)
   2039           wfail_at(t, loc, "wasm target: asm output must be a register");
   2040         w->asm_out_regs[i] = out_ops[i].v.reg;
   2041         w->asm_out_types[i] = out_ops[i].type ? out_ops[i].type : outs[i].type;
   2042       }
   2043     }
   2044   }
   2045 
   2046   /* Free scratch func storage. The parsed insns have been copied into the
   2047    * WIR payload. */
   2048   if (scratch.params)
   2049     h->free(h, scratch.params, sizeof(WasmValType) * scratch.cap_params);
   2050   if (scratch.locals)
   2051     h->free(h, scratch.locals, sizeof(WasmValType) * scratch.cap_locals);
   2052   if (scratch.insns)
   2053     h->free(h, scratch.insns, sizeof(WasmInsn) * scratch.cap_insns);
   2054 }
   2055 
   2056 void wasm_file_scope_asm(CGTarget* tg, const char* src, size_t len) {
   2057   WTarget* t = (WTarget*)tg;
   2058   (void)src;
   2059   (void)len;
   2060   compiler_panic(t->c, cur_loc(t),
   2061                  "wasm target: file-scope asm not yet supported");
   2062 }
   2063 
   2064 void wasm_set_loc(CGTarget* tg, SrcLoc loc) {
   2065   WTarget* t = (WTarget*)tg;
   2066   /* No debug info in v1, but we stash the most recent loc so cur_loc /
   2067    * diagnostics attribute to the actual statement rather than the
   2068    * function-definition location. */
   2069   t->cur_stmt_loc = loc;
   2070 }
   2071 
   2072 void wasm_emit_unreachable(WTarget* t) {
   2073   if (t->dead) return;
   2074   WIR* w = wir_push(t);
   2075   w->op = WIR_UNREACHABLE;
   2076   t->dead = 1;
   2077 }
   2078 
   2079 /* Control terminator (the C __builtin_unreachable point): emit the Wasm
   2080  * `unreachable` opcode, which traps if reached. Ends the current block. */
   2081 void wasm_unreachable(CGTarget* tg) { wasm_emit_unreachable((WTarget*)tg); }
   2082 
   2083 /* -----------------------------------------------------------------
   2084  * WIR -> WasmFunc lowering
   2085  * ----------------------------------------------------------------- */
   2086 
   2087 static void emit_insn(WTarget* t, WasmInsnKind k, i64 imm) {
   2088   wasm_func_add_insn(t->c, t->module, t->cur_func, k, imm);
   2089 }
   2090 static void emit_fp(WTarget* t, WasmInsnKind k, double v) {
   2091   wasm_func_add_fp_insn(t->c, t->module, t->cur_func, k, v);
   2092 }
   2093 
   2094 /* Push an operand onto the wasm value stack. */
   2095 static void emit_push_operand_reg(WTarget* t, Reg r) {
   2096   if (r == REG_NONE) wfail(t, "wasm: push of REG_NONE");
   2097   /* The reg must already have a local. */
   2098   if (r >= t->reg_cap || t->reg_to_local[r] == 0xffffffffu) {
   2099     wfail(t, "wasm: reg %u used before being defined", (unsigned)r);
   2100   }
   2101   emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->reg_to_local[r]);
   2102 }
   2103 
   2104 static WasmValType type_valtype(WTarget* t, KitCgTypeId ty) {
   2105   return valtype_for_type(t, ty);
   2106 }
   2107 
   2108 static void emit_push_imm(WTarget* t, WasmValType vt, i64 imm) {
   2109   WasmInsnKind k =
   2110       (vt == WASM_VAL_I64) ? WASM_INSN_I64_CONST : WASM_INSN_I32_CONST;
   2111   emit_insn(t, k, imm);
   2112 }
   2113 
   2114 static u32 memarg_align_log2(u32 align, u32 width);
   2115 static WasmInsnKind load_kind_for(WTarget* t, KitCgTypeId ty, MemAccess ma);
   2116 
   2117 static void emit_push_operand(WTarget* t, u32 kind, i64 imm, Reg r,
   2118                               KitCgTypeId ty) {
   2119   if (kind == WOP_IMM) {
   2120     WasmValType vt = type_valtype(t, ty);
   2121     if (vt == WASM_VAL_F32 || vt == WASM_VAL_F64) {
   2122       wfail(t, "wasm: float immediate operand not supported");
   2123     }
   2124     emit_push_imm(t, vt, imm);
   2125   } else if (kind == WOP_LOCAL) {
   2126     FrameSlot fs = (FrameSlot)imm;
   2127     WSlot* s = slot_for(t, fs);
   2128     if (s->kind == W_SLOT_LOCAL) {
   2129       emit_insn(t, WASM_INSN_LOCAL_GET, (i64)s->wasm_local);
   2130     } else {
   2131       MemAccess ma;
   2132       memset(&ma, 0, sizeof ma);
   2133       ma.type = ty;
   2134       ma.size = (u32)abi_cg_sizeof(t->c->abi, ty);
   2135       ma.align = s->align;
   2136       emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->frame_base_local);
   2137       WasmInsnKind k = load_kind_for(t, ty, ma);
   2138       wasm_func_add_mem_insn(
   2139           t->c, t->module, t->cur_func, k,
   2140           memarg_align_log2(ma.align, wasm_mem_width((uint8_t)k)),
   2141           s->frame_offset, 0);
   2142     }
   2143   } else {
   2144     emit_push_operand_reg(t, r);
   2145   }
   2146 }
   2147 
   2148 static void emit_local_set(WTarget* t, Reg dst, KitCgTypeId ty, RegClass cls) {
   2149   u32 idx = reg_local(t, dst, ty, cls);
   2150   emit_insn(t, WASM_INSN_LOCAL_SET, (i64)idx);
   2151 }
   2152 
   2153 static u32 memarg_align_log2(u32 align, u32 width) {
   2154   u32 a = align ? align : width;
   2155   u32 lg = 0;
   2156   if (a > width) a = width;
   2157   while (a > 1u) {
   2158     a >>= 1u;
   2159     lg++;
   2160   }
   2161   return lg;
   2162 }
   2163 
   2164 static WasmInsnKind load_kind_for(WTarget* t, KitCgTypeId ty, MemAccess ma) {
   2165   WasmValType vt = type_valtype(t, ty);
   2166   u32 size = ma.size ? ma.size : (u32)abi_cg_sizeof(t->c->abi, ty);
   2167   if (vt == WASM_VAL_F32) return WASM_INSN_F32_LOAD;
   2168   if (vt == WASM_VAL_F64) return WASM_INSN_F64_LOAD;
   2169   if (vt == WASM_VAL_I64) {
   2170     if (size == 1) return WASM_INSN_I64_LOAD8_U;
   2171     if (size == 2) return WASM_INSN_I64_LOAD16_U;
   2172     if (size == 4) return WASM_INSN_I64_LOAD32_U;
   2173     return WASM_INSN_I64_LOAD;
   2174   }
   2175   if (size == 1) return WASM_INSN_I32_LOAD8_U;
   2176   if (size == 2) return WASM_INSN_I32_LOAD16_U;
   2177   return WASM_INSN_I32_LOAD;
   2178 }
   2179 
   2180 static WasmInsnKind store_kind_for(WTarget* t, KitCgTypeId ty, MemAccess ma) {
   2181   WasmValType vt = type_valtype(t, ty);
   2182   u32 size = ma.size ? ma.size : (u32)abi_cg_sizeof(t->c->abi, ty);
   2183   if (vt == WASM_VAL_F32) return WASM_INSN_F32_STORE;
   2184   if (vt == WASM_VAL_F64) return WASM_INSN_F64_STORE;
   2185   if (vt == WASM_VAL_I64) {
   2186     if (size == 1) return WASM_INSN_I64_STORE8;
   2187     if (size == 2) return WASM_INSN_I64_STORE16;
   2188     if (size == 4) return WASM_INSN_I64_STORE32;
   2189     return WASM_INSN_I64_STORE;
   2190   }
   2191   if (size == 1) return WASM_INSN_I32_STORE8;
   2192   if (size == 2) return WASM_INSN_I32_STORE16;
   2193   return WASM_INSN_I32_STORE;
   2194 }
   2195 
   2196 /* Atomic op selection. Wasm threads gives natural-width atomic load/store for
   2197  * i32/i64 (with 8/16/32 subword variants) but only full-width (i32/i64) RMW
   2198  * and cmpxchg in kit's wasm core. Sub-word RMW/cmpxchg therefore diagnose
   2199  * rather than silently widening. */
   2200 static WasmInsnKind atomic_load_kind_for(WTarget* t, KitCgTypeId ty,
   2201                                          MemAccess ma) {
   2202   WasmValType vt = type_valtype(t, ty);
   2203   u32 size = ma.size ? ma.size : (u32)abi_cg_sizeof(t->c->abi, ty);
   2204   if (vt == WASM_VAL_F32 || vt == WASM_VAL_F64)
   2205     wfail(t,
   2206           "wasm target: atomic load of floating-point value is not "
   2207           "representable in wasm threads");
   2208   if (vt == WASM_VAL_I64) {
   2209     if (size == 1) return WASM_INSN_I64_ATOMIC_LOAD8_U;
   2210     if (size == 2) return WASM_INSN_I64_ATOMIC_LOAD16_U;
   2211     if (size == 4) return WASM_INSN_I64_ATOMIC_LOAD32_U;
   2212     if (size == 8) return WASM_INSN_I64_ATOMIC_LOAD;
   2213     wfail(t, "wasm: atomic load i64 size %u not supported", size);
   2214   }
   2215   if (size == 1) return WASM_INSN_I32_ATOMIC_LOAD8_U;
   2216   if (size == 2) return WASM_INSN_I32_ATOMIC_LOAD16_U;
   2217   if (size == 4) return WASM_INSN_I32_ATOMIC_LOAD;
   2218   wfail(t, "wasm: atomic load i32 size %u not supported", size);
   2219 }
   2220 
   2221 static WasmInsnKind atomic_store_kind_for(WTarget* t, KitCgTypeId ty,
   2222                                           MemAccess ma) {
   2223   WasmValType vt = type_valtype(t, ty);
   2224   u32 size = ma.size ? ma.size : (u32)abi_cg_sizeof(t->c->abi, ty);
   2225   if (vt == WASM_VAL_F32 || vt == WASM_VAL_F64)
   2226     wfail(t,
   2227           "wasm target: atomic store of floating-point value is not "
   2228           "representable in wasm threads");
   2229   if (vt == WASM_VAL_I64) {
   2230     if (size == 1) return WASM_INSN_I64_ATOMIC_STORE8;
   2231     if (size == 2) return WASM_INSN_I64_ATOMIC_STORE16;
   2232     if (size == 4) return WASM_INSN_I64_ATOMIC_STORE32;
   2233     if (size == 8) return WASM_INSN_I64_ATOMIC_STORE;
   2234     wfail(t, "wasm: atomic store i64 size %u not supported", size);
   2235   }
   2236   if (size == 1) return WASM_INSN_I32_ATOMIC_STORE8;
   2237   if (size == 2) return WASM_INSN_I32_ATOMIC_STORE16;
   2238   if (size == 4) return WASM_INSN_I32_ATOMIC_STORE;
   2239   wfail(t, "wasm: atomic store i32 size %u not supported", size);
   2240 }
   2241 
   2242 static WasmInsnKind atomic_rmw_kind_for(WTarget* t, KitCgAtomicOp op,
   2243                                         KitCgTypeId ty, MemAccess ma) {
   2244   WasmValType vt = type_valtype(t, ty);
   2245   u32 size = ma.size ? ma.size : (u32)abi_cg_sizeof(t->c->abi, ty);
   2246   int is64 = (vt == WASM_VAL_I64);
   2247   if (vt == WASM_VAL_F32 || vt == WASM_VAL_F64)
   2248     wfail(t,
   2249           "wasm target: atomic RMW on floating-point value is not "
   2250           "representable in wasm threads");
   2251   if (!(size == 4 || size == 8) || (is64 && size != 8) ||
   2252       (!is64 && size != 4)) {
   2253     wfail(t,
   2254           "wasm target: atomic RMW size %u not yet supported (only "
   2255           "full-width i32 and i64 atomic RMW are wired)",
   2256           size);
   2257   }
   2258   switch (op) {
   2259     case KIT_CG_ATOMIC_ADD:
   2260       return is64 ? WASM_INSN_I64_ATOMIC_RMW_ADD : WASM_INSN_I32_ATOMIC_RMW_ADD;
   2261     case KIT_CG_ATOMIC_SUB:
   2262       return is64 ? WASM_INSN_I64_ATOMIC_RMW_SUB : WASM_INSN_I32_ATOMIC_RMW_SUB;
   2263     case KIT_CG_ATOMIC_AND:
   2264       return is64 ? WASM_INSN_I64_ATOMIC_RMW_AND : WASM_INSN_I32_ATOMIC_RMW_AND;
   2265     case KIT_CG_ATOMIC_OR:
   2266       return is64 ? WASM_INSN_I64_ATOMIC_RMW_OR : WASM_INSN_I32_ATOMIC_RMW_OR;
   2267     case KIT_CG_ATOMIC_XOR:
   2268       return is64 ? WASM_INSN_I64_ATOMIC_RMW_XOR : WASM_INSN_I32_ATOMIC_RMW_XOR;
   2269     case KIT_CG_ATOMIC_XCHG:
   2270       return is64 ? WASM_INSN_I64_ATOMIC_RMW_XCHG
   2271                   : WASM_INSN_I32_ATOMIC_RMW_XCHG;
   2272     case KIT_CG_ATOMIC_NAND:
   2273       wfail(t, "wasm target: atomic NAND has no native wasm-threads opcode");
   2274   }
   2275   wfail(t, "wasm: unsupported atomic RMW op %d", (int)op);
   2276 }
   2277 
   2278 static WasmInsnKind atomic_cmpxchg_kind_for(WTarget* t, KitCgTypeId ty,
   2279                                             MemAccess ma) {
   2280   WasmValType vt = type_valtype(t, ty);
   2281   u32 size = ma.size ? ma.size : (u32)abi_cg_sizeof(t->c->abi, ty);
   2282   if (vt == WASM_VAL_F32 || vt == WASM_VAL_F64)
   2283     wfail(t,
   2284           "wasm target: atomic cmpxchg on floating-point value is not "
   2285           "representable in wasm threads");
   2286   if (vt == WASM_VAL_I64) {
   2287     if (size != 8)
   2288       wfail(t, "wasm target: atomic cmpxchg i64 size %u not yet supported",
   2289             size);
   2290     return WASM_INSN_I64_ATOMIC_RMW_CMPXCHG;
   2291   }
   2292   if (size != 4)
   2293     wfail(t, "wasm target: atomic cmpxchg i32 size %u not yet supported", size);
   2294   return WASM_INSN_I32_ATOMIC_RMW_CMPXCHG;
   2295 }
   2296 
   2297 /* Look up (or assign) `sym`'s slot in the funcref table. Returned index is
   2298  * the wasm table index (>= 1, with 0 reserved as the null/trap slot). */
   2299 static u32 func_table_index_for(WTarget* t, ObjSymId sym) {
   2300   Heap* h = t->c->ctx->heap;
   2301   for (u32 i = 0; i < t->func_table_count; ++i) {
   2302     if (t->func_table[i] == sym) return i + 1u;
   2303   }
   2304   if (t->func_table_count == t->func_table_cap) {
   2305     u32 nc = t->func_table_cap ? t->func_table_cap * 2u : 8u;
   2306     void* p = h->realloc(h, t->func_table, sizeof(ObjSymId) * t->func_table_cap,
   2307                          sizeof(ObjSymId) * nc, _Alignof(ObjSymId));
   2308     if (!p) wfail(t, "wasm: out of memory");
   2309     t->func_table = (ObjSymId*)p;
   2310     t->func_table_cap = nc;
   2311   }
   2312   t->func_table[t->func_table_count] = sym;
   2313   t->has_func_table = 1;
   2314   return ++t->func_table_count; /* slot 0 reserved; first sym -> index 1 */
   2315 }
   2316 
   2317 /* Defer function-pointer materialization to wasm_materialize_functable.
   2318  * Emits `i32.const 0` and queues a WFuncTableFixup keyed by the placeholder's
   2319  * (cur_func_idx, ninsns-1). */
   2320 static void queue_func_table_fixup(WTarget* t, ObjSymId sym) {
   2321   Heap* h = t->c->ctx->heap;
   2322   if (!t->cur_func) wfail(t, "wasm: function address outside a function");
   2323   /* Ensure the function gets a slot and force the WasmFunc shell to exist so
   2324    * the table's element segment can resolve its wasm-func index later. */
   2325   (void)func_table_index_for(t, sym);
   2326   (void)sym_to_wasm_func(t, sym, NULL);
   2327   emit_insn(t, WASM_INSN_I32_CONST, 0);
   2328   if (t->func_table_fixups_count == t->func_table_fixups_cap) {
   2329     u32 nc = t->func_table_fixups_cap ? t->func_table_fixups_cap * 2u : 16u;
   2330     void* p =
   2331         h->realloc(h, t->func_table_fixups,
   2332                    sizeof(WFuncTableFixup) * t->func_table_fixups_cap,
   2333                    sizeof(WFuncTableFixup) * nc, _Alignof(WFuncTableFixup));
   2334     if (!p) wfail(t, "wasm: out of memory");
   2335     t->func_table_fixups = (WFuncTableFixup*)p;
   2336     t->func_table_fixups_cap = nc;
   2337   }
   2338   WFuncTableFixup* fx = &t->func_table_fixups[t->func_table_fixups_count++];
   2339   fx->wasm_func_idx = t->cur_func_idx;
   2340   fx->insn_idx = t->cur_func->ninsns - 1u;
   2341   fx->sym = sym;
   2342 }
   2343 
   2344 /* Defer symbol-address resolution to wasm_materialize_data. Emits an
   2345  * i32.const placeholder into the current function and queues a WSymFixup
   2346  * keyed by (cur_func_idx, ninsns-1). The compact section layout is only
   2347  * known once every section's final size is settled, which doesn't happen
   2348  * until finalize. */
   2349 static void queue_symbol_addr_fixup(WTarget* t, ObjSymId sym, i64 addend) {
   2350   Heap* h = t->c->ctx->heap;
   2351   const ObjSym* os = obj_symbol_get(t->obj, sym);
   2352   /* Function-symbol addresses route through the funcref table, not linear
   2353    * memory. CG occasionally takes the address of an extern function before
   2354    * the function body is seen (forward declarations, indirect-call
   2355    * setup); the SK_FUNC kind is set by the frontend at sym creation. */
   2356   if (os && os->kind == SK_FUNC) {
   2357     if (addend != 0)
   2358       wfail(t, "wasm: nonzero addend on function-pointer reference");
   2359     queue_func_table_fixup(t, sym);
   2360     return;
   2361   }
   2362   if (!os)
   2363     wfail(t, "wasm target: address of unresolved symbol not yet implemented");
   2364   if (os->section_id == OBJ_SEC_NONE && os->kind != SK_COMMON)
   2365     wfail(t, "wasm target: address of undefined symbol not yet implemented");
   2366   /* SK_COMMON falls through: apply_sym_fixups allocates a BSS-style base for
   2367    * it in wasm_materialize_data and patches the i32.const at finalize. */
   2368   if (addend < INT32_MIN || addend > INT32_MAX)
   2369     wfail(t, "wasm: symbol addend out of range");
   2370   if (!t->cur_func) wfail(t, "wasm: symbol address outside a function");
   2371   emit_insn(t, WASM_INSN_I32_CONST, 0);
   2372   if (t->sym_fixups_count == t->sym_fixups_cap) {
   2373     u32 nc = t->sym_fixups_cap ? t->sym_fixups_cap * 2u : 16u;
   2374     void* p =
   2375         h->realloc(h, t->sym_fixups, sizeof(WSymFixup) * t->sym_fixups_cap,
   2376                    sizeof(WSymFixup) * nc, _Alignof(WSymFixup));
   2377     if (!p) wfail(t, "wasm: out of memory");
   2378     t->sym_fixups = (WSymFixup*)p;
   2379     t->sym_fixups_cap = nc;
   2380   }
   2381   WSymFixup* fx = &t->sym_fixups[t->sym_fixups_count++];
   2382   fx->wasm_func_idx = t->cur_func_idx;
   2383   fx->insn_idx = t->cur_func->ninsns - 1u;
   2384   fx->sym = sym;
   2385   fx->addend = addend;
   2386 }
   2387 
   2388 /* Push the value of an OPK_INDIRECT base/index component. The CG defers loading
   2389  * the pointer value of an address-taken (frame-resident) pointer local to the
   2390  * backend: the deref of such a local arrives as an OPK_INDIRECT whose base
   2391  * names the local itself, not a materialized register (see
   2392  * fold_ea_into_operand, and native_direct_target's nd_cache_reg_for, which
   2393  * loads it from the home). In the wasm backend each id is either a register
   2394  * (reg_to_local set) or a frame slot, never both — so dispatch on that: a
   2395  * register is fetched directly; a frame-resident local is read from its home
   2396  * like any other WOP_LOCAL operand. */
   2397 static void emit_push_addr_component(WTarget* t, Reg id) {
   2398   if (id < t->reg_cap && t->reg_to_local[id] != 0xffffffffu) {
   2399     emit_push_operand_reg(t, id);
   2400   } else {
   2401     WSlot* s = slot_for(t, id);
   2402     emit_push_operand(t, WOP_LOCAL, (i64)id, REG_NONE, s->type);
   2403   }
   2404 }
   2405 
   2406 /* Value type of an indirect component, whether it lives in a register or a
   2407  * frame slot (used to decide i64->i32 address narrowing). */
   2408 static WasmValType addr_component_valtype(WTarget* t, Reg id) {
   2409   if (id < t->reg_cap && t->reg_to_local[id] != 0xffffffffu && t->reg_type[id])
   2410     return type_valtype(t, t->reg_type[id]);
   2411   return type_valtype(t, slot_for(t, id)->type);
   2412 }
   2413 
   2414 static void emit_addr_operand(WTarget* t, Operand addr, uint64_t* offset_out) {
   2415   *offset_out = 0;
   2416   if (addr.kind == OPK_LOCAL) {
   2417     WSlot* s = slot_for(t, addr.v.frame_slot);
   2418     if (s->kind != W_SLOT_STACK)
   2419       wfail(t, "wasm: address of non-addressable local");
   2420     emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->frame_base_local);
   2421     *offset_out = s->frame_offset;
   2422     return;
   2423   }
   2424   if (addr.kind == OPK_INDIRECT) {
   2425     emit_push_addr_component(t, addr.v.ind.base);
   2426     if (addr.v.ind.index != REG_NONE) {
   2427       emit_push_addr_component(t, addr.v.ind.index);
   2428       if (addr_component_valtype(t, addr.v.ind.index) == WASM_VAL_I64) {
   2429         emit_insn(t, WASM_INSN_I32_WRAP_I64, 0);
   2430       }
   2431       if (addr.v.ind.log2_scale != 0) {
   2432         emit_insn(t, WASM_INSN_I32_CONST, (i64)addr.v.ind.log2_scale);
   2433         emit_insn(t, WASM_INSN_I32_SHL, 0);
   2434       }
   2435       emit_insn(t, WASM_INSN_I32_ADD, 0);
   2436     }
   2437     if (addr.v.ind.ofs >= 0) {
   2438       *offset_out = (uint32_t)addr.v.ind.ofs;
   2439     } else {
   2440       emit_insn(t, WASM_INSN_I32_CONST, (i64)addr.v.ind.ofs);
   2441       emit_insn(t, WASM_INSN_I32_ADD, 0);
   2442     }
   2443     return;
   2444   }
   2445   if (addr.kind == OPK_GLOBAL) {
   2446     queue_symbol_addr_fixup(t, addr.v.global.sym, addr.v.global.addend);
   2447     return;
   2448   }
   2449   if (addr.kind == OPK_REG) {
   2450     /* An i32 address already materialized in a register: just push it. */
   2451     emit_push_operand_reg(t, addr.v.reg);
   2452     return;
   2453   }
   2454   wfail(t, "wasm: unsupported address operand kind %u", (unsigned)addr.kind);
   2455 }
   2456 
   2457 /* Push a complete i32 address value (folding any positive offset into the base
   2458  * via i32.add). Used by bulk-memory ops (memory.copy / memory.fill) which take
   2459  * the address as a stack operand and carry no memarg offset. */
   2460 static void emit_push_addr_value(WTarget* t, Operand addr) {
   2461   uint64_t off;
   2462   emit_addr_operand(t, addr, &off);
   2463   if (off != 0) {
   2464     emit_insn(t, WASM_INSN_I32_CONST, (i64)(uint32_t)off);
   2465     emit_insn(t, WASM_INSN_I32_ADD, 0);
   2466   }
   2467 }
   2468 
   2469 static void emit_load_addr(WTarget* t, Operand addr, KitCgTypeId ty,
   2470                            MemAccess ma) {
   2471   uint64_t offset;
   2472   WasmInsnKind k = load_kind_for(t, ty, ma);
   2473   u32 width = wasm_mem_width((uint8_t)k);
   2474   emit_addr_operand(t, addr, &offset);
   2475   wasm_func_add_mem_insn(t->c, t->module, t->cur_func, k,
   2476                          memarg_align_log2(ma.align, width), offset, 0);
   2477 }
   2478 
   2479 static void emit_store_addr(WTarget* t, Operand addr, KitCgTypeId ty,
   2480                             Operand src, MemAccess ma, u32 src_kind,
   2481                             i64 src_imm, Reg src_reg) {
   2482   uint64_t offset;
   2483   WasmInsnKind k = store_kind_for(t, ty, ma);
   2484   u32 width = wasm_mem_width((uint8_t)k);
   2485   emit_addr_operand(t, addr, &offset);
   2486   if (src_kind == WOP_IMM) {
   2487     emit_push_imm(t, type_valtype(t, ty), src_imm);
   2488   } else if (src_kind == WOP_WASM_LOCAL) {
   2489     emit_insn(t, WASM_INSN_LOCAL_GET, src_imm);
   2490   } else if (src.kind == OPK_IMM) {
   2491     emit_push_imm(t, type_valtype(t, ty), src.v.imm);
   2492   } else if (src_kind == WOP_LOCAL) {
   2493     emit_push_operand(t, WOP_LOCAL, src_imm, REG_NONE, ty);
   2494   } else {
   2495     emit_push_operand_reg(t, src_reg);
   2496   }
   2497   wasm_func_add_mem_insn(t->c, t->module, t->cur_func, k,
   2498                          memarg_align_log2(ma.align, width), offset, 0);
   2499 }
   2500 
   2501 /* Map (BinOp, valtype) to wasm opcode. */
   2502 static WasmInsnKind binop_kind(WTarget* t, BinOp op, WasmValType vt) {
   2503   switch (op) {
   2504     case BO_IADD:
   2505       return vt == WASM_VAL_I64 ? WASM_INSN_I64_ADD : WASM_INSN_I32_ADD;
   2506     case BO_ISUB:
   2507       return vt == WASM_VAL_I64 ? WASM_INSN_I64_SUB : WASM_INSN_I32_SUB;
   2508     case BO_IMUL:
   2509       return vt == WASM_VAL_I64 ? WASM_INSN_I64_MUL : WASM_INSN_I32_MUL;
   2510     case BO_SDIV:
   2511       return vt == WASM_VAL_I64 ? WASM_INSN_I64_DIV_S : WASM_INSN_I32_DIV_S;
   2512     case BO_UDIV:
   2513       return vt == WASM_VAL_I64 ? WASM_INSN_I64_DIV_U : WASM_INSN_I32_DIV_U;
   2514     case BO_SREM:
   2515       return vt == WASM_VAL_I64 ? WASM_INSN_I64_REM_S : WASM_INSN_I32_REM_S;
   2516     case BO_UREM:
   2517       return vt == WASM_VAL_I64 ? WASM_INSN_I64_REM_U : WASM_INSN_I32_REM_U;
   2518     case BO_AND:
   2519       return vt == WASM_VAL_I64 ? WASM_INSN_I64_AND : WASM_INSN_I32_AND;
   2520     case BO_OR:
   2521       return vt == WASM_VAL_I64 ? WASM_INSN_I64_OR : WASM_INSN_I32_OR;
   2522     case BO_XOR:
   2523       return vt == WASM_VAL_I64 ? WASM_INSN_I64_XOR : WASM_INSN_I32_XOR;
   2524     case BO_SHL:
   2525       return vt == WASM_VAL_I64 ? WASM_INSN_I64_SHL : WASM_INSN_I32_SHL;
   2526     case BO_SHR_S:
   2527       return vt == WASM_VAL_I64 ? WASM_INSN_I64_SHR_S : WASM_INSN_I32_SHR_S;
   2528     case BO_SHR_U:
   2529       return vt == WASM_VAL_I64 ? WASM_INSN_I64_SHR_U : WASM_INSN_I32_SHR_U;
   2530     case BO_FADD:
   2531       return vt == WASM_VAL_F64 ? WASM_INSN_F64_ADD : WASM_INSN_F32_ADD;
   2532     case BO_FSUB:
   2533       return vt == WASM_VAL_F64 ? WASM_INSN_F64_SUB : WASM_INSN_F32_SUB;
   2534     case BO_FMUL:
   2535       return vt == WASM_VAL_F64 ? WASM_INSN_F64_MUL : WASM_INSN_F32_MUL;
   2536     case BO_FDIV:
   2537       return vt == WASM_VAL_F64 ? WASM_INSN_F64_DIV : WASM_INSN_F32_DIV;
   2538   }
   2539   wfail(t, "wasm: unsupported binop %d", (int)op);
   2540 }
   2541 
   2542 static WasmInsnKind cmp_kind(WTarget* t, CmpOp op, WasmValType vt) {
   2543   int is64 = (vt == WASM_VAL_I64);
   2544   switch (op) {
   2545     case CMP_EQ:
   2546       return is64 ? WASM_INSN_I64_EQ : WASM_INSN_I32_EQ;
   2547     case CMP_NE:
   2548       return is64 ? WASM_INSN_I64_NE : WASM_INSN_I32_NE;
   2549     case CMP_LT_S:
   2550       return is64 ? WASM_INSN_I64_LT_S : WASM_INSN_I32_LT_S;
   2551     case CMP_LE_S:
   2552       return is64 ? WASM_INSN_I64_LE_S : WASM_INSN_I32_LE_S;
   2553     case CMP_GT_S:
   2554       return is64 ? WASM_INSN_I64_GT_S : WASM_INSN_I32_GT_S;
   2555     case CMP_GE_S:
   2556       return is64 ? WASM_INSN_I64_GE_S : WASM_INSN_I32_GE_S;
   2557     case CMP_LT_U:
   2558       return is64 ? WASM_INSN_I64_LT_U : WASM_INSN_I32_LT_U;
   2559     case CMP_LE_U:
   2560       return is64 ? WASM_INSN_I64_LE_U : WASM_INSN_I32_LE_U;
   2561     case CMP_GT_U:
   2562       return is64 ? WASM_INSN_I64_GT_U : WASM_INSN_I32_GT_U;
   2563     case CMP_GE_U:
   2564       return is64 ? WASM_INSN_I64_GE_U : WASM_INSN_I32_GE_U;
   2565     /* FP compares are lowered by emit_fp_cmp (they may need multiple wasm
   2566      * instructions) and never reach cmp_kind. Listed so -Wswitch stays useful.
   2567      */
   2568     case CMP_OEQ_F:
   2569     case CMP_ONE_F:
   2570     case CMP_OLT_F:
   2571     case CMP_OLE_F:
   2572     case CMP_OGT_F:
   2573     case CMP_OGE_F:
   2574     case CMP_UEQ_F:
   2575     case CMP_UNE_F:
   2576     case CMP_ULT_F:
   2577     case CMP_ULE_F:
   2578     case CMP_UGT_F:
   2579     case CMP_UGE_F:
   2580       break;
   2581   }
   2582   wfail(t, "wasm: unsupported cmp %d", (int)op);
   2583 }
   2584 
   2585 /* Push both compare operands (a then b) onto the wasm stack. */
   2586 static void push_cmp_operands(WTarget* t, WIR* w, KitCgTypeId opty) {
   2587   emit_push_operand(t, w->imm_kind, w->imm_a, w->a, opty);
   2588   emit_push_operand(t, w->imm_kind_b, w->imm_b, w->b, opty);
   2589 }
   2590 
   2591 /* Lower an FP compare to the wasm stack, leaving an i32 0/1 result. wasm's
   2592  * f.eq/f.lt/f.le/f.gt/f.ge are ordered (false on NaN) and f.ne is unordered
   2593  * (true on NaN), so the 12 IEEE predicates compose from those plus i32.eqz /
   2594  * i32.or, using unordered-R == !(ordered-not-R). ONE/UEQ need both operands
   2595  * twice, so push_cmp_operands runs again for the second relation. */
   2596 static void emit_fp_cmp(WTarget* t, CmpOp op, WIR* w, KitCgTypeId opty) {
   2597   int d = (type_valtype(t, opty) == WASM_VAL_F64);
   2598   WasmInsnKind EQ = d ? WASM_INSN_F64_EQ : WASM_INSN_F32_EQ;
   2599   WasmInsnKind NE = d ? WASM_INSN_F64_NE : WASM_INSN_F32_NE;
   2600   WasmInsnKind LT = d ? WASM_INSN_F64_LT : WASM_INSN_F32_LT;
   2601   WasmInsnKind LE = d ? WASM_INSN_F64_LE : WASM_INSN_F32_LE;
   2602   WasmInsnKind GT = d ? WASM_INSN_F64_GT : WASM_INSN_F32_GT;
   2603   WasmInsnKind GE = d ? WASM_INSN_F64_GE : WASM_INSN_F32_GE;
   2604   switch (op) {
   2605     case CMP_OEQ_F:
   2606       push_cmp_operands(t, w, opty);
   2607       emit_insn(t, EQ, 0);
   2608       return;
   2609     case CMP_UNE_F:
   2610       push_cmp_operands(t, w, opty);
   2611       emit_insn(t, NE, 0);
   2612       return;
   2613     case CMP_OLT_F:
   2614       push_cmp_operands(t, w, opty);
   2615       emit_insn(t, LT, 0);
   2616       return;
   2617     case CMP_OLE_F:
   2618       push_cmp_operands(t, w, opty);
   2619       emit_insn(t, LE, 0);
   2620       return;
   2621     case CMP_OGT_F:
   2622       push_cmp_operands(t, w, opty);
   2623       emit_insn(t, GT, 0);
   2624       return;
   2625     case CMP_OGE_F:
   2626       push_cmp_operands(t, w, opty);
   2627       emit_insn(t, GE, 0);
   2628       return;
   2629     case CMP_UGE_F: /* !(OLT) */
   2630       push_cmp_operands(t, w, opty);
   2631       emit_insn(t, LT, 0);
   2632       emit_insn(t, WASM_INSN_I32_EQZ, 0);
   2633       return;
   2634     case CMP_UGT_F: /* !(OLE) */
   2635       push_cmp_operands(t, w, opty);
   2636       emit_insn(t, LE, 0);
   2637       emit_insn(t, WASM_INSN_I32_EQZ, 0);
   2638       return;
   2639     case CMP_ULE_F: /* !(OGT) */
   2640       push_cmp_operands(t, w, opty);
   2641       emit_insn(t, GT, 0);
   2642       emit_insn(t, WASM_INSN_I32_EQZ, 0);
   2643       return;
   2644     case CMP_ULT_F: /* !(OGE) */
   2645       push_cmp_operands(t, w, opty);
   2646       emit_insn(t, GE, 0);
   2647       emit_insn(t, WASM_INSN_I32_EQZ, 0);
   2648       return;
   2649     case CMP_ONE_F: /* ordered & !=: (a<b) | (a>b) */
   2650       push_cmp_operands(t, w, opty);
   2651       emit_insn(t, LT, 0);
   2652       push_cmp_operands(t, w, opty);
   2653       emit_insn(t, GT, 0);
   2654       emit_insn(t, WASM_INSN_I32_OR, 0);
   2655       return;
   2656     case CMP_UEQ_F: /* unordered | ==: !((a<b) | (a>b)) */
   2657       push_cmp_operands(t, w, opty);
   2658       emit_insn(t, LT, 0);
   2659       push_cmp_operands(t, w, opty);
   2660       emit_insn(t, GT, 0);
   2661       emit_insn(t, WASM_INSN_I32_OR, 0);
   2662       emit_insn(t, WASM_INSN_I32_EQZ, 0);
   2663       return;
   2664     default:
   2665       wfail(t, "wasm: unsupported fp cmp %d", (int)op);
   2666   }
   2667 }
   2668 
   2669 static void emit_convert(WTarget* t, ConvKind ck, WasmValType src,
   2670                          WasmValType dst, u32 sw, u32 dw) {
   2671   (void)dw;
   2672   /* Integer sign/zero extension. Sub-i32 logical widths (i8/i16) share the i32
   2673    * valtype, so a "same valtype" SEXT/ZEXT is NOT a no-op — the high bits must
   2674    * be filled per the source's logical width (sw). The CG IR keeps narrow
   2675    * immediates as truncated bit patterns, so without this an i8 value like
   2676    * (signed char)-128 reads back as 128. */
   2677   if (ck == CV_SEXT && src != WASM_VAL_F32 && src != WASM_VAL_F64) {
   2678     if (src == WASM_VAL_I32) {
   2679       if (sw == 8u)
   2680         emit_insn(t, WASM_INSN_I32_EXTEND8_S, 0);
   2681       else if (sw == 16u)
   2682         emit_insn(t, WASM_INSN_I32_EXTEND16_S, 0);
   2683     } else {
   2684       if (sw == 8u)
   2685         emit_insn(t, WASM_INSN_I64_EXTEND8_S, 0);
   2686       else if (sw == 16u)
   2687         emit_insn(t, WASM_INSN_I64_EXTEND16_S, 0);
   2688       else if (sw == 32u)
   2689         emit_insn(t, WASM_INSN_I64_EXTEND32_S, 0);
   2690     }
   2691     if (src == WASM_VAL_I32 && dst == WASM_VAL_I64)
   2692       emit_insn(t, WASM_INSN_I64_EXTEND_I32_S, 0);
   2693     else if (src == WASM_VAL_I64 && dst == WASM_VAL_I32)
   2694       emit_insn(t, WASM_INSN_I32_WRAP_I64, 0);
   2695     return;
   2696   }
   2697   if (ck == CV_ZEXT && src != WASM_VAL_F32 && src != WASM_VAL_F64) {
   2698     if (src == WASM_VAL_I32) {
   2699       if (sw > 0u && sw < 32u) {
   2700         emit_insn(t, WASM_INSN_I32_CONST, (i64)(((u32)1 << sw) - 1u));
   2701         emit_insn(t, WASM_INSN_I32_AND, 0);
   2702       }
   2703       if (dst == WASM_VAL_I64) emit_insn(t, WASM_INSN_I64_EXTEND_I32_U, 0);
   2704     } else {
   2705       if (sw > 0u && sw < 64u) {
   2706         emit_push_imm(t, WASM_VAL_I64, (i64)(((u64)1 << sw) - 1u));
   2707         emit_insn(t, WASM_INSN_I64_AND, 0);
   2708       }
   2709       if (dst == WASM_VAL_I32) emit_insn(t, WASM_INSN_I32_WRAP_I64, 0);
   2710     }
   2711     return;
   2712   }
   2713   if (src == dst && (ck == CV_BITCAST || ck == CV_TRUNC)) {
   2714     /* No-op conversion. */
   2715     return;
   2716   }
   2717   if (ck == CV_BITCAST) {
   2718     if (src == WASM_VAL_I32 && dst == WASM_VAL_F32) {
   2719       emit_insn(t, WASM_INSN_F32_REINTERPRET_I32, 0);
   2720       return;
   2721     }
   2722     if (src == WASM_VAL_F32 && dst == WASM_VAL_I32) {
   2723       emit_insn(t, WASM_INSN_I32_REINTERPRET_F32, 0);
   2724       return;
   2725     }
   2726     if (src == WASM_VAL_I64 && dst == WASM_VAL_F64) {
   2727       emit_insn(t, WASM_INSN_F64_REINTERPRET_I64, 0);
   2728       return;
   2729     }
   2730     if (src == WASM_VAL_F64 && dst == WASM_VAL_I64) {
   2731       emit_insn(t, WASM_INSN_I64_REINTERPRET_F64, 0);
   2732       return;
   2733     }
   2734     /* Width-changing ptr/int bitcasts: kit_cg_ptr_to_int and
   2735      * kit_cg_int_to_ptr route through CV_BITCAST, and on wasm32 a pointer
   2736      * is i32 while the frontend integer side may be i64. Lower as
   2737      * wrap/extend (zero-extend; pointers are non-negative addresses). */
   2738     if (src == WASM_VAL_I64 && dst == WASM_VAL_I32) {
   2739       emit_insn(t, WASM_INSN_I32_WRAP_I64, 0);
   2740       return;
   2741     }
   2742     if (src == WASM_VAL_I32 && dst == WASM_VAL_I64) {
   2743       emit_insn(t, WASM_INSN_I64_EXTEND_I32_U, 0);
   2744       return;
   2745     }
   2746     wfail(t, "wasm: unsupported bitcast");
   2747   }
   2748   if (ck == CV_TRUNC) {
   2749     if (src == WASM_VAL_I64 && dst == WASM_VAL_I32) {
   2750       emit_insn(t, WASM_INSN_I32_WRAP_I64, 0);
   2751       return;
   2752     }
   2753   }
   2754   if (ck == CV_FEXT && src == WASM_VAL_F32 && dst == WASM_VAL_F64) {
   2755     emit_insn(t, WASM_INSN_F64_PROMOTE_F32, 0);
   2756     return;
   2757   }
   2758   if (ck == CV_FTRUNC && src == WASM_VAL_F64 && dst == WASM_VAL_F32) {
   2759     emit_insn(t, WASM_INSN_F32_DEMOTE_F64, 0);
   2760     return;
   2761   }
   2762   if (ck == CV_ITOF_S) {
   2763     if (src == WASM_VAL_I32 && dst == WASM_VAL_F32) {
   2764       emit_insn(t, WASM_INSN_F32_CONVERT_I32_S, 0);
   2765       return;
   2766     }
   2767     if (src == WASM_VAL_I32 && dst == WASM_VAL_F64) {
   2768       emit_insn(t, WASM_INSN_F64_CONVERT_I32_S, 0);
   2769       return;
   2770     }
   2771     if (src == WASM_VAL_I64 && dst == WASM_VAL_F32) {
   2772       emit_insn(t, WASM_INSN_F32_CONVERT_I64_S, 0);
   2773       return;
   2774     }
   2775     if (src == WASM_VAL_I64 && dst == WASM_VAL_F64) {
   2776       emit_insn(t, WASM_INSN_F64_CONVERT_I64_S, 0);
   2777       return;
   2778     }
   2779   }
   2780   if (ck == CV_ITOF_U) {
   2781     if (src == WASM_VAL_I32 && dst == WASM_VAL_F32) {
   2782       emit_insn(t, WASM_INSN_F32_CONVERT_I32_U, 0);
   2783       return;
   2784     }
   2785     if (src == WASM_VAL_I32 && dst == WASM_VAL_F64) {
   2786       emit_insn(t, WASM_INSN_F64_CONVERT_I32_U, 0);
   2787       return;
   2788     }
   2789     if (src == WASM_VAL_I64 && dst == WASM_VAL_F32) {
   2790       emit_insn(t, WASM_INSN_F32_CONVERT_I64_U, 0);
   2791       return;
   2792     }
   2793     if (src == WASM_VAL_I64 && dst == WASM_VAL_F64) {
   2794       emit_insn(t, WASM_INSN_F64_CONVERT_I64_U, 0);
   2795       return;
   2796     }
   2797   }
   2798   if (ck == CV_FTOI_S) {
   2799     if (src == WASM_VAL_F32 && dst == WASM_VAL_I32) {
   2800       emit_insn(t, WASM_INSN_I32_TRUNC_F32_S, 0);
   2801       return;
   2802     }
   2803     if (src == WASM_VAL_F64 && dst == WASM_VAL_I32) {
   2804       emit_insn(t, WASM_INSN_I32_TRUNC_F64_S, 0);
   2805       return;
   2806     }
   2807     if (src == WASM_VAL_F32 && dst == WASM_VAL_I64) {
   2808       emit_insn(t, WASM_INSN_I64_TRUNC_F32_S, 0);
   2809       return;
   2810     }
   2811     if (src == WASM_VAL_F64 && dst == WASM_VAL_I64) {
   2812       emit_insn(t, WASM_INSN_I64_TRUNC_F64_S, 0);
   2813       return;
   2814     }
   2815   }
   2816   if (ck == CV_FTOI_U) {
   2817     if (src == WASM_VAL_F32 && dst == WASM_VAL_I32) {
   2818       emit_insn(t, WASM_INSN_I32_TRUNC_F32_U, 0);
   2819       return;
   2820     }
   2821     if (src == WASM_VAL_F64 && dst == WASM_VAL_I32) {
   2822       emit_insn(t, WASM_INSN_I32_TRUNC_F64_U, 0);
   2823       return;
   2824     }
   2825     if (src == WASM_VAL_F32 && dst == WASM_VAL_I64) {
   2826       emit_insn(t, WASM_INSN_I64_TRUNC_F32_U, 0);
   2827       return;
   2828     }
   2829     if (src == WASM_VAL_F64 && dst == WASM_VAL_I64) {
   2830       emit_insn(t, WASM_INSN_I64_TRUNC_F64_U, 0);
   2831       return;
   2832     }
   2833   }
   2834   wfail(t, "wasm: unsupported convert kind %d (%d -> %d)", (int)ck, (int)src,
   2835         (int)dst);
   2836 }
   2837 
   2838 /* During lowering we keep a running active-scope stack so we can compute
   2839  * br depths. */
   2840 typedef struct LoweringScope {
   2841   u32 id;
   2842   u8 kind;
   2843   /* Depth at which break/continue targets are reached. */
   2844   u32 break_depth;
   2845   u32 cont_depth;
   2846 } LoweringScope;
   2847 
   2848 typedef struct LoweringState {
   2849   WTarget* t;
   2850   /* Bounded by the deepest synthetic + CG scope nesting we'll emit.
   2851    * Switch islands wrap one block per case, so the limit is roughly
   2852    * (max cases + max user nesting). 1024 leaves room for very wide
   2853    * switches without forcing future per-case-count caps. */
   2854   LoweringScope stack[1024];
   2855   u32 nstack;
   2856   u32 cur_depth;
   2857 } LoweringState;
   2858 
   2859 static u32 br_to_label(LoweringState* L, Label l) {
   2860   WLabel* lbl = lookup_label(L->t, l);
   2861   if (!lbl) wfail(L->t, "wasm: br to unknown label");
   2862   if (lbl->kind == WLBL_SCOPE_BREAK) {
   2863     for (u32 i = L->nstack; i > 0; --i) {
   2864       if (L->stack[i - 1u].id == lbl->scope_id) {
   2865         return L->cur_depth - L->stack[i - 1u].break_depth;
   2866       }
   2867     }
   2868     wfail(L->t, "wasm: br to break label of inactive scope");
   2869   }
   2870   if (lbl->kind == WLBL_SCOPE_CONT) {
   2871     for (u32 i = L->nstack; i > 0; --i) {
   2872       if (L->stack[i - 1u].id == lbl->scope_id) {
   2873         return L->cur_depth - L->stack[i - 1u].cont_depth;
   2874       }
   2875     }
   2876     wfail(L->t, "wasm: br to continue label of inactive scope");
   2877   }
   2878   /* wasm_structurize wraps every reachable forward label in a synthetic
   2879    * SCOPE_BLOCK (forward goto) or SCOPE_LOOP (backward goto), and
   2880    * unroll_switch_islands reorders the WIR so switch case labels are
   2881    * forward refs from WIR_SWITCH. Arriving here means the structurer
   2882    * missed a shape — a bug, not a feature gap. */
   2883   wfail(L->t,
   2884         "wasm: br to free label whose synthetic scope was not "
   2885         "emitted; structurer bug");
   2886 }
   2887 
   2888 static i64 wasm_switch_sign_extend(u64 v, u32 width) {
   2889   if (width == 0u || width >= 64u) return (i64)v;
   2890   {
   2891     u64 bit = 1ull << (width - 1u);
   2892     u64 mask = (1ull << width) - 1u;
   2893     v &= mask;
   2894     return (i64)((v ^ bit) - bit);
   2895   }
   2896 }
   2897 
   2898 static int wasm_switch_extents(WTarget* t, const WIR* w, i64* out_vmin,
   2899                                u64* out_span) {
   2900   u32 width;
   2901   i64 vmin = INT64_MAX;
   2902   i64 vmax = INT64_MIN;
   2903   if (w->switch_ncases == 0) return 0;
   2904   width = kit_cg_type_int_width((KitCompiler*)t->c, w->type);
   2905   if (!width || width > 64u) return 0;
   2906   for (u32 i = 0; i < w->switch_ncases; ++i) {
   2907     i64 vi = wasm_switch_sign_extend(w->switch_cases[i].value, width);
   2908     if (vi < vmin) vmin = vi;
   2909     if (vi > vmax) vmax = vi;
   2910   }
   2911   if (vmax < vmin) return 0;
   2912   {
   2913     u64 delta = (u64)vmax - (u64)vmin;
   2914     if (delta == UINT64_MAX) return 0;
   2915     *out_span = delta + 1u;
   2916   }
   2917   *out_vmin = vmin;
   2918   return 1;
   2919 }
   2920 
   2921 static void emit_br_table(WTarget* t, const u32* targets, u32 ntargets) {
   2922   WasmInsn* in;
   2923   if (ntargets == 0)
   2924     wfail(t, "wasm: br_table needs at least the default target");
   2925   wasm_func_add_insn(t->c, t->module, t->cur_func, WASM_INSN_BR_TABLE, 0);
   2926   in = &t->cur_func->insns[t->cur_func->ninsns - 1u];
   2927   wasm_insn_set_targets(t->c, t->module, in, targets, ntargets);
   2928 }
   2929 
   2930 /* A switch lowers to a dense br_table when its case values span a range that
   2931  * isn't pathologically sparse relative to the number of cases; otherwise an
   2932  * `eq`/`br_if` comparison chain. Small ranges always take the table (cheap
   2933  * either way); larger ranges only when at least ~half the table slots carry a
   2934  * real case, so a sparse switch (e.g. `case 0`, `case 1000000`) doesn't
   2935  * materialize a giant mostly-default table. There is no range-splitting yet,
   2936  * so a switch that fails this test is a linear scan. */
   2937 static int switch_use_br_table(const WIR* w, u64 span) {
   2938   if (span <= 64u) return 1;
   2939   return span <= (u64)w->switch_ncases * 2u;
   2940 }
   2941 
   2942 static void emit_switch_br_table(WTarget* t, LoweringState* L, const WIR* w) {
   2943   i64 vmin;
   2944   u64 span;
   2945   u32* targets;
   2946   Label* labels;
   2947   u32 ntargets;
   2948   WasmValType vt;
   2949   Heap* h = t->c->ctx->heap;
   2950 
   2951   if (w->switch_ncases == 0) {
   2952     emit_insn(t, WASM_INSN_BR, (i64)br_to_label(L, w->labels[0]));
   2953     return;
   2954   }
   2955   if (!wasm_switch_extents(t, w, &vmin, &span))
   2956     wfail(t, "wasm: unsupported switch selector type");
   2957   vt = type_valtype(t, w->type);
   2958   if (vt != WASM_VAL_I32 && vt != WASM_VAL_I64)
   2959     wfail(t, "wasm: switch selector must be integer");
   2960   if (!switch_use_br_table(w, span)) {
   2961     for (u32 i = 0; i < w->switch_ncases; ++i) {
   2962       u32 width = kit_cg_type_int_width((KitCompiler*)t->c, w->type);
   2963       i64 vi = wasm_switch_sign_extend(w->switch_cases[i].value, width);
   2964       emit_push_operand(t, w->imm_kind, w->imm_a, w->a, w->type);
   2965       emit_push_imm(t, vt, vi);
   2966       emit_insn(t, vt == WASM_VAL_I64 ? WASM_INSN_I64_EQ : WASM_INSN_I32_EQ, 0);
   2967       emit_insn(t, WASM_INSN_BR_IF,
   2968                 (i64)br_to_label(L, w->switch_cases[i].label));
   2969     }
   2970     emit_insn(t, WASM_INSN_BR, (i64)br_to_label(L, w->labels[0]));
   2971     return;
   2972   }
   2973 
   2974   /* Dense table: one slot per value in [vmin, vmin+span), default-filled, with
   2975    * the default appended as the trailing out-of-range target. */
   2976   ntargets = (u32)span + 1u;
   2977   labels = (Label*)h->alloc(h, sizeof(Label) * span, _Alignof(Label));
   2978   targets = (u32*)h->alloc(h, sizeof(u32) * ntargets, _Alignof(u32));
   2979   if (!labels || !targets) wfail(t, "wasm: out of memory for switch table");
   2980   for (u64 i = 0; i < span; ++i) labels[i] = w->labels[0];
   2981   for (u32 i = 0; i < w->switch_ncases; ++i) {
   2982     u32 width = kit_cg_type_int_width((KitCompiler*)t->c, w->type);
   2983     i64 vi = wasm_switch_sign_extend(w->switch_cases[i].value, width);
   2984     u64 slot = (u64)(vi - vmin);
   2985     if (slot >= span) wfail(t, "wasm: switch case outside span");
   2986     labels[slot] = w->switch_cases[i].label;
   2987   }
   2988 
   2989   emit_push_operand(t, w->imm_kind, w->imm_a, w->a, w->type);
   2990   if (vmin != 0) {
   2991     emit_push_imm(t, vt, vmin);
   2992     emit_insn(t, vt == WASM_VAL_I64 ? WASM_INSN_I64_SUB : WASM_INSN_I32_SUB, 0);
   2993   }
   2994   if (vt == WASM_VAL_I64) emit_insn(t, WASM_INSN_I32_WRAP_I64, 0);
   2995 
   2996   for (u32 i = 0; i < (u32)span; ++i) targets[i] = br_to_label(L, labels[i]);
   2997   targets[ntargets - 1u] = br_to_label(L, w->labels[0]);
   2998   emit_br_table(t, targets, ntargets);
   2999   h->free(h, targets, sizeof(u32) * ntargets);
   3000   h->free(h, labels, sizeof(Label) * span);
   3001 }
   3002 
   3003 /* -----------------------------------------------------------------
   3004  * Intrinsics (bit ops / bswap / overflow arith)
   3005  *
   3006  * MEMCPY/MEMMOVE/MEMSET don't appear here: the recorder funnels them
   3007  * into WIR_COPY_BYTES / WIR_SET_BYTES which already lower to
   3008  * memory.copy / memory.fill. Hints (PREFETCH/EXPECT/ASSUME_ALIGNED)
   3009  * also don't reach the linearizer — the recorder either drops them or
   3010  * emits a plain copy. ----------------------------------------------- */
   3011 
   3012 static void emit_intrinsic_bit_op(WTarget* t, const WIR* w) {
   3013   /* clz/ctz/popcount instruction width follows the operand (type2), not the
   3014    * i32 result. i64 forms produce an i64 count that we wrap to the i32 dst. */
   3015   WasmValType vt = type_valtype(t, w->type2 ? w->type2 : w->type);
   3016   WasmValType dvt = type_valtype(t, w->type);
   3017   WasmInsnKind op;
   3018   switch ((IntrinKind)w->cgop) {
   3019     case INTRIN_CLZ:
   3020       op = (vt == WASM_VAL_I64) ? WASM_INSN_I64_CLZ : WASM_INSN_I32_CLZ;
   3021       break;
   3022     case INTRIN_CTZ:
   3023       op = (vt == WASM_VAL_I64) ? WASM_INSN_I64_CTZ : WASM_INSN_I32_CTZ;
   3024       break;
   3025     case INTRIN_POPCOUNT:
   3026       op = (vt == WASM_VAL_I64) ? WASM_INSN_I64_POPCNT : WASM_INSN_I32_POPCNT;
   3027       break;
   3028     default:
   3029       wfail(t, "wasm: unexpected bit-op intrinsic %d", (int)w->cgop);
   3030       return;
   3031   }
   3032   emit_push_operand_reg(t, w->a);
   3033   emit_insn(t, op, 0);
   3034   if (vt == WASM_VAL_I64 && dvt == WASM_VAL_I32)
   3035     emit_insn(t, WASM_INSN_I32_WRAP_I64, 0);
   3036   emit_local_set(t, w->dst, w->type, (RegClass)w->cls);
   3037 }
   3038 
   3039 static void emit_intrinsic_bswap(WTarget* t, const WIR* w) {
   3040   /* Width-by-type: the recorded result type fixes the byte width. */
   3041   u32 width = (u32)abi_cg_sizeof(t->c->abi, w->type);
   3042   if (width <= 4) {
   3043     /* Both 16- and 32-bit forms operate over i32. The 16-bit form only
   3044      * touches the low 16 bits; any extra high bits in the input are
   3045      * discarded by the AND mask. */
   3046     u32 tmp = add_wasm_local(t, WASM_VAL_I32);
   3047     emit_push_operand_reg(t, w->a);
   3048     emit_insn(t, WASM_INSN_LOCAL_SET, (i64)tmp);
   3049     if (width <= 2) {
   3050       /* (x & 0xff) << 8 */
   3051       emit_insn(t, WASM_INSN_LOCAL_GET, (i64)tmp);
   3052       emit_insn(t, WASM_INSN_I32_CONST, 0xff);
   3053       emit_insn(t, WASM_INSN_I32_AND, 0);
   3054       emit_insn(t, WASM_INSN_I32_CONST, 8);
   3055       emit_insn(t, WASM_INSN_I32_SHL, 0);
   3056       /* (x >> 8) & 0xff */
   3057       emit_insn(t, WASM_INSN_LOCAL_GET, (i64)tmp);
   3058       emit_insn(t, WASM_INSN_I32_CONST, 8);
   3059       emit_insn(t, WASM_INSN_I32_SHR_U, 0);
   3060       emit_insn(t, WASM_INSN_I32_CONST, 0xff);
   3061       emit_insn(t, WASM_INSN_I32_AND, 0);
   3062       emit_insn(t, WASM_INSN_I32_OR, 0);
   3063     } else {
   3064       /* Four-byte shuffle. */
   3065       emit_insn(t, WASM_INSN_LOCAL_GET, (i64)tmp);
   3066       emit_insn(t, WASM_INSN_I32_CONST, 24);
   3067       emit_insn(t, WASM_INSN_I32_SHR_U, 0);
   3068       emit_insn(t, WASM_INSN_I32_CONST, 0xff);
   3069       emit_insn(t, WASM_INSN_I32_AND, 0);
   3070 
   3071       emit_insn(t, WASM_INSN_LOCAL_GET, (i64)tmp);
   3072       emit_insn(t, WASM_INSN_I32_CONST, 8);
   3073       emit_insn(t, WASM_INSN_I32_SHR_U, 0);
   3074       emit_insn(t, WASM_INSN_I32_CONST, 0xff00);
   3075       emit_insn(t, WASM_INSN_I32_AND, 0);
   3076       emit_insn(t, WASM_INSN_I32_OR, 0);
   3077 
   3078       emit_insn(t, WASM_INSN_LOCAL_GET, (i64)tmp);
   3079       emit_insn(t, WASM_INSN_I32_CONST, 8);
   3080       emit_insn(t, WASM_INSN_I32_SHL, 0);
   3081       emit_insn(t, WASM_INSN_I32_CONST, 0xff0000);
   3082       emit_insn(t, WASM_INSN_I32_AND, 0);
   3083       emit_insn(t, WASM_INSN_I32_OR, 0);
   3084 
   3085       emit_insn(t, WASM_INSN_LOCAL_GET, (i64)tmp);
   3086       emit_insn(t, WASM_INSN_I32_CONST, 24);
   3087       emit_insn(t, WASM_INSN_I32_SHL, 0);
   3088       emit_insn(t, WASM_INSN_I32_OR, 0);
   3089     }
   3090     emit_local_set(t, w->dst, w->type, (RegClass)w->cls);
   3091     return;
   3092   }
   3093   /* 8-byte form: byte reverse over i64. */
   3094   u32 tmp = add_wasm_local(t, WASM_VAL_I64);
   3095   emit_push_operand_reg(t, w->a);
   3096   emit_insn(t, WASM_INSN_LOCAL_SET, (i64)tmp);
   3097   for (int i = 0; i < 8; ++i) {
   3098     emit_insn(t, WASM_INSN_LOCAL_GET, (i64)tmp);
   3099     if (i > 0) {
   3100       emit_insn(t, WASM_INSN_I64_CONST, (i64)(i * 8));
   3101       emit_insn(t, WASM_INSN_I64_SHR_U, 0);
   3102     }
   3103     emit_insn(t, WASM_INSN_I64_CONST, 0xff);
   3104     emit_insn(t, WASM_INSN_I64_AND, 0);
   3105     int shift = (7 - i) * 8;
   3106     if (shift > 0) {
   3107       emit_insn(t, WASM_INSN_I64_CONST, (i64)shift);
   3108       emit_insn(t, WASM_INSN_I64_SHL, 0);
   3109     }
   3110     if (i > 0) emit_insn(t, WASM_INSN_I64_OR, 0);
   3111   }
   3112   emit_local_set(t, w->dst, w->type, (RegClass)w->cls);
   3113 }
   3114 
   3115 static void emit_intrinsic_mul_high(WTarget* t, const WIR* w) {
   3116   IntrinKind k = (IntrinKind)w->cgop;
   3117   WasmValType vt = type_valtype(t, w->type);
   3118   u32 a_loc = add_wasm_local(t, vt);
   3119   u32 b_loc = add_wasm_local(t, vt);
   3120   emit_push_operand(t, w->imm_kind, w->imm_a, w->a, w->type);
   3121   emit_insn(t, WASM_INSN_LOCAL_SET, (i64)a_loc);
   3122   emit_push_operand(t, w->imm_kind_b, w->imm_b, w->b, w->type);
   3123   emit_insn(t, WASM_INSN_LOCAL_SET, (i64)b_loc);
   3124 
   3125   if (vt == WASM_VAL_I32) {
   3126     emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc);
   3127     emit_insn(t, k == INTRIN_SMUL_HIGH ? WASM_INSN_I64_EXTEND_I32_S
   3128                                        : WASM_INSN_I64_EXTEND_I32_U,
   3129               0);
   3130     emit_insn(t, WASM_INSN_LOCAL_GET, (i64)b_loc);
   3131     emit_insn(t, k == INTRIN_SMUL_HIGH ? WASM_INSN_I64_EXTEND_I32_S
   3132                                        : WASM_INSN_I64_EXTEND_I32_U,
   3133               0);
   3134     emit_insn(t, WASM_INSN_I64_MUL, 0);
   3135     emit_insn(t, WASM_INSN_I64_CONST, 32);
   3136     emit_insn(t, k == INTRIN_SMUL_HIGH ? WASM_INSN_I64_SHR_S
   3137                                        : WASM_INSN_I64_SHR_U,
   3138               0);
   3139     emit_insn(t, WASM_INSN_I32_WRAP_I64, 0);
   3140     emit_local_set(t, w->dst, w->type, (RegClass)w->cls);
   3141     return;
   3142   }
   3143 
   3144   /* Core wasm has no widening i64 multiply. Split into 32-bit limbs:
   3145    * high = p11 + (p01>>32) + (p10>>32) +
   3146    *        (((p00>>32) + lo32(p01) + lo32(p10)) >> 32). */
   3147   {
   3148     u32 p00 = add_wasm_local(t, WASM_VAL_I64);
   3149     u32 p01 = add_wasm_local(t, WASM_VAL_I64);
   3150     u32 p10 = add_wasm_local(t, WASM_VAL_I64);
   3151     u32 p11 = add_wasm_local(t, WASM_VAL_I64);
   3152     u32 middle = add_wasm_local(t, WASM_VAL_I64);
   3153     u32 high = add_wasm_local(t, WASM_VAL_I64);
   3154 
   3155     emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc);
   3156     emit_insn(t, WASM_INSN_I64_CONST, 0xffffffffull);
   3157     emit_insn(t, WASM_INSN_I64_AND, 0);
   3158     emit_insn(t, WASM_INSN_LOCAL_GET, (i64)b_loc);
   3159     emit_insn(t, WASM_INSN_I64_CONST, 0xffffffffull);
   3160     emit_insn(t, WASM_INSN_I64_AND, 0);
   3161     emit_insn(t, WASM_INSN_I64_MUL, 0);
   3162     emit_insn(t, WASM_INSN_LOCAL_SET, (i64)p00);
   3163 
   3164     emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc);
   3165     emit_insn(t, WASM_INSN_I64_CONST, 0xffffffffull);
   3166     emit_insn(t, WASM_INSN_I64_AND, 0);
   3167     emit_insn(t, WASM_INSN_LOCAL_GET, (i64)b_loc);
   3168     emit_insn(t, WASM_INSN_I64_CONST, 32);
   3169     emit_insn(t, WASM_INSN_I64_SHR_U, 0);
   3170     emit_insn(t, WASM_INSN_I64_MUL, 0);
   3171     emit_insn(t, WASM_INSN_LOCAL_SET, (i64)p01);
   3172 
   3173     emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc);
   3174     emit_insn(t, WASM_INSN_I64_CONST, 32);
   3175     emit_insn(t, WASM_INSN_I64_SHR_U, 0);
   3176     emit_insn(t, WASM_INSN_LOCAL_GET, (i64)b_loc);
   3177     emit_insn(t, WASM_INSN_I64_CONST, 0xffffffffull);
   3178     emit_insn(t, WASM_INSN_I64_AND, 0);
   3179     emit_insn(t, WASM_INSN_I64_MUL, 0);
   3180     emit_insn(t, WASM_INSN_LOCAL_SET, (i64)p10);
   3181 
   3182     emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc);
   3183     emit_insn(t, WASM_INSN_I64_CONST, 32);
   3184     emit_insn(t, WASM_INSN_I64_SHR_U, 0);
   3185     emit_insn(t, WASM_INSN_LOCAL_GET, (i64)b_loc);
   3186     emit_insn(t, WASM_INSN_I64_CONST, 32);
   3187     emit_insn(t, WASM_INSN_I64_SHR_U, 0);
   3188     emit_insn(t, WASM_INSN_I64_MUL, 0);
   3189     emit_insn(t, WASM_INSN_LOCAL_SET, (i64)p11);
   3190 
   3191     emit_insn(t, WASM_INSN_LOCAL_GET, (i64)p00);
   3192     emit_insn(t, WASM_INSN_I64_CONST, 32);
   3193     emit_insn(t, WASM_INSN_I64_SHR_U, 0);
   3194     emit_insn(t, WASM_INSN_LOCAL_GET, (i64)p01);
   3195     emit_insn(t, WASM_INSN_I64_CONST, 0xffffffffull);
   3196     emit_insn(t, WASM_INSN_I64_AND, 0);
   3197     emit_insn(t, WASM_INSN_I64_ADD, 0);
   3198     emit_insn(t, WASM_INSN_LOCAL_GET, (i64)p10);
   3199     emit_insn(t, WASM_INSN_I64_CONST, 0xffffffffull);
   3200     emit_insn(t, WASM_INSN_I64_AND, 0);
   3201     emit_insn(t, WASM_INSN_I64_ADD, 0);
   3202     emit_insn(t, WASM_INSN_LOCAL_SET, (i64)middle);
   3203 
   3204     emit_insn(t, WASM_INSN_LOCAL_GET, (i64)p11);
   3205     emit_insn(t, WASM_INSN_LOCAL_GET, (i64)p01);
   3206     emit_insn(t, WASM_INSN_I64_CONST, 32);
   3207     emit_insn(t, WASM_INSN_I64_SHR_U, 0);
   3208     emit_insn(t, WASM_INSN_I64_ADD, 0);
   3209     emit_insn(t, WASM_INSN_LOCAL_GET, (i64)p10);
   3210     emit_insn(t, WASM_INSN_I64_CONST, 32);
   3211     emit_insn(t, WASM_INSN_I64_SHR_U, 0);
   3212     emit_insn(t, WASM_INSN_I64_ADD, 0);
   3213     emit_insn(t, WASM_INSN_LOCAL_GET, (i64)middle);
   3214     emit_insn(t, WASM_INSN_I64_CONST, 32);
   3215     emit_insn(t, WASM_INSN_I64_SHR_U, 0);
   3216     emit_insn(t, WASM_INSN_I64_ADD, 0);
   3217     emit_insn(t, WASM_INSN_LOCAL_SET, (i64)high);
   3218 
   3219     emit_insn(t, WASM_INSN_LOCAL_GET, (i64)high);
   3220     if (k == INTRIN_SMUL_HIGH) {
   3221       /* signed_high(a,b) = unsigned_high(a,b) - (a<0 ? b : 0)
   3222        *                                      - (b<0 ? a : 0). */
   3223       emit_insn(t, WASM_INSN_LOCAL_GET, (i64)b_loc);
   3224       emit_insn(t, WASM_INSN_I64_CONST, 0);
   3225       emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc);
   3226       emit_insn(t, WASM_INSN_I64_CONST, 0);
   3227       emit_insn(t, WASM_INSN_I64_LT_S, 0);
   3228       emit_insn(t, WASM_INSN_SELECT, 0);
   3229       emit_insn(t, WASM_INSN_I64_SUB, 0);
   3230       emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc);
   3231       emit_insn(t, WASM_INSN_I64_CONST, 0);
   3232       emit_insn(t, WASM_INSN_LOCAL_GET, (i64)b_loc);
   3233       emit_insn(t, WASM_INSN_I64_CONST, 0);
   3234       emit_insn(t, WASM_INSN_I64_LT_S, 0);
   3235       emit_insn(t, WASM_INSN_SELECT, 0);
   3236       emit_insn(t, WASM_INSN_I64_SUB, 0);
   3237     }
   3238     emit_local_set(t, w->dst, w->type, (RegClass)w->cls);
   3239   }
   3240 }
   3241 
   3242 static void emit_intrinsic_overflow(WTarget* t, const WIR* w) {
   3243   IntrinKind k = (IntrinKind)w->cgop;
   3244   WasmValType vt = type_valtype(t, w->type);
   3245   WasmInsnKind k_add =
   3246       (vt == WASM_VAL_I64) ? WASM_INSN_I64_ADD : WASM_INSN_I32_ADD;
   3247   WasmInsnKind k_sub =
   3248       (vt == WASM_VAL_I64) ? WASM_INSN_I64_SUB : WASM_INSN_I32_SUB;
   3249   WasmInsnKind k_and =
   3250       (vt == WASM_VAL_I64) ? WASM_INSN_I64_AND : WASM_INSN_I32_AND;
   3251   WasmInsnKind k_xor =
   3252       (vt == WASM_VAL_I64) ? WASM_INSN_I64_XOR : WASM_INSN_I32_XOR;
   3253   WasmInsnKind k_shr_u =
   3254       (vt == WASM_VAL_I64) ? WASM_INSN_I64_SHR_U : WASM_INSN_I32_SHR_U;
   3255   WasmInsnKind k_lt_u =
   3256       (vt == WASM_VAL_I64) ? WASM_INSN_I64_LT_U : WASM_INSN_I32_LT_U;
   3257   WasmInsnKind k_const =
   3258       (vt == WASM_VAL_I64) ? WASM_INSN_I64_CONST : WASM_INSN_I32_CONST;
   3259   KitCgTypeId bool_ty = builtin_id(KIT_CG_BUILTIN_BOOL);
   3260 
   3261   /* Stash both operands in scratch locals so each side of the expansion can
   3262    * re-load them without re-evaluating immediates or relying on the wasm
   3263    * value stack shape. */
   3264   u32 a_loc = add_wasm_local(t, vt);
   3265   u32 b_loc = add_wasm_local(t, vt);
   3266   emit_push_operand(t, w->imm_kind, w->imm_a, w->a, w->type);
   3267   emit_insn(t, WASM_INSN_LOCAL_SET, (i64)a_loc);
   3268   emit_push_operand(t, w->imm_kind_b, w->imm_b, w->b, w->type);
   3269   emit_insn(t, WASM_INSN_LOCAL_SET, (i64)b_loc);
   3270 
   3271   switch (k) {
   3272     case INTRIN_UADD_OVERFLOW:
   3273       /* r = a + b; ovf = (r <u a) */
   3274       emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc);
   3275       emit_insn(t, WASM_INSN_LOCAL_GET, (i64)b_loc);
   3276       emit_insn(t, k_add, 0);
   3277       emit_local_set(t, w->dst, w->type, (RegClass)w->cls);
   3278       emit_push_operand_reg(t, w->dst);
   3279       emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc);
   3280       emit_insn(t, k_lt_u, 0);
   3281       emit_local_set(t, w->dst2, bool_ty, RC_INT);
   3282       break;
   3283     case INTRIN_USUB_OVERFLOW:
   3284       /* r = a - b; ovf = (a <u b) */
   3285       emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc);
   3286       emit_insn(t, WASM_INSN_LOCAL_GET, (i64)b_loc);
   3287       emit_insn(t, k_sub, 0);
   3288       emit_local_set(t, w->dst, w->type, (RegClass)w->cls);
   3289       emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc);
   3290       emit_insn(t, WASM_INSN_LOCAL_GET, (i64)b_loc);
   3291       emit_insn(t, k_lt_u, 0);
   3292       emit_local_set(t, w->dst2, bool_ty, RC_INT);
   3293       break;
   3294     case INTRIN_SADD_OVERFLOW:
   3295       /* r = a + b; ovf = ((r ^ a) & (r ^ b)) >>u (W-1) */
   3296       emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc);
   3297       emit_insn(t, WASM_INSN_LOCAL_GET, (i64)b_loc);
   3298       emit_insn(t, k_add, 0);
   3299       emit_local_set(t, w->dst, w->type, (RegClass)w->cls);
   3300       emit_push_operand_reg(t, w->dst);
   3301       emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc);
   3302       emit_insn(t, k_xor, 0);
   3303       emit_push_operand_reg(t, w->dst);
   3304       emit_insn(t, WASM_INSN_LOCAL_GET, (i64)b_loc);
   3305       emit_insn(t, k_xor, 0);
   3306       emit_insn(t, k_and, 0);
   3307       emit_insn(t, k_const, (i64)(vt == WASM_VAL_I64 ? 63 : 31));
   3308       emit_insn(t, k_shr_u, 0);
   3309       if (vt == WASM_VAL_I64) emit_insn(t, WASM_INSN_I32_WRAP_I64, 0);
   3310       emit_local_set(t, w->dst2, bool_ty, RC_INT);
   3311       break;
   3312     case INTRIN_SSUB_OVERFLOW:
   3313       /* r = a - b; ovf = ((a ^ b) & (a ^ r)) >>u (W-1) */
   3314       emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc);
   3315       emit_insn(t, WASM_INSN_LOCAL_GET, (i64)b_loc);
   3316       emit_insn(t, k_sub, 0);
   3317       emit_local_set(t, w->dst, w->type, (RegClass)w->cls);
   3318       emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc);
   3319       emit_insn(t, WASM_INSN_LOCAL_GET, (i64)b_loc);
   3320       emit_insn(t, k_xor, 0);
   3321       emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc);
   3322       emit_push_operand_reg(t, w->dst);
   3323       emit_insn(t, k_xor, 0);
   3324       emit_insn(t, k_and, 0);
   3325       emit_insn(t, k_const, (i64)(vt == WASM_VAL_I64 ? 63 : 31));
   3326       emit_insn(t, k_shr_u, 0);
   3327       if (vt == WASM_VAL_I64) emit_insn(t, WASM_INSN_I32_WRAP_I64, 0);
   3328       emit_local_set(t, w->dst2, bool_ty, RC_INT);
   3329       break;
   3330     case INTRIN_UMUL_OVERFLOW: {
   3331       /* i32 only (i64 rejected in recorder). Widen to i64, multiply,
   3332        * low 32 = result, ovf = (wide >> 32) != 0. */
   3333       u32 wide = add_wasm_local(t, WASM_VAL_I64);
   3334       emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc);
   3335       emit_insn(t, WASM_INSN_I64_EXTEND_I32_U, 0);
   3336       emit_insn(t, WASM_INSN_LOCAL_GET, (i64)b_loc);
   3337       emit_insn(t, WASM_INSN_I64_EXTEND_I32_U, 0);
   3338       emit_insn(t, WASM_INSN_I64_MUL, 0);
   3339       emit_insn(t, WASM_INSN_LOCAL_SET, (i64)wide);
   3340 
   3341       emit_insn(t, WASM_INSN_LOCAL_GET, (i64)wide);
   3342       emit_insn(t, WASM_INSN_I32_WRAP_I64, 0);
   3343       emit_local_set(t, w->dst, w->type, (RegClass)w->cls);
   3344 
   3345       emit_insn(t, WASM_INSN_LOCAL_GET, (i64)wide);
   3346       emit_insn(t, WASM_INSN_I64_CONST, 32);
   3347       emit_insn(t, WASM_INSN_I64_SHR_U, 0);
   3348       emit_insn(t, WASM_INSN_I64_CONST, 0);
   3349       emit_insn(t, WASM_INSN_I64_NE, 0);
   3350       emit_local_set(t, w->dst2, bool_ty, RC_INT);
   3351       break;
   3352     }
   3353     case INTRIN_SMUL_OVERFLOW: {
   3354       /* i32 only. Sign-extend, multiply, low 32 = result, ovf if
   3355        * sext(result) != wide product. */
   3356       u32 wide = add_wasm_local(t, WASM_VAL_I64);
   3357       emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc);
   3358       emit_insn(t, WASM_INSN_I64_EXTEND_I32_S, 0);
   3359       emit_insn(t, WASM_INSN_LOCAL_GET, (i64)b_loc);
   3360       emit_insn(t, WASM_INSN_I64_EXTEND_I32_S, 0);
   3361       emit_insn(t, WASM_INSN_I64_MUL, 0);
   3362       emit_insn(t, WASM_INSN_LOCAL_SET, (i64)wide);
   3363 
   3364       emit_insn(t, WASM_INSN_LOCAL_GET, (i64)wide);
   3365       emit_insn(t, WASM_INSN_I32_WRAP_I64, 0);
   3366       emit_local_set(t, w->dst, w->type, (RegClass)w->cls);
   3367 
   3368       emit_insn(t, WASM_INSN_LOCAL_GET, (i64)wide);
   3369       emit_push_operand_reg(t, w->dst);
   3370       emit_insn(t, WASM_INSN_I64_EXTEND_I32_S, 0);
   3371       emit_insn(t, WASM_INSN_I64_NE, 0);
   3372       emit_local_set(t, w->dst2, bool_ty, RC_INT);
   3373       break;
   3374     }
   3375     default:
   3376       wfail(t, "wasm: overflow intrinsic dispatch reached default (%d)",
   3377             (int)k);
   3378   }
   3379 }
   3380 
   3381 static void emit_intrinsic(WTarget* t, const WIR* w) {
   3382   IntrinKind k = (IntrinKind)w->cgop;
   3383   switch (k) {
   3384     case INTRIN_CLZ:
   3385     case INTRIN_CTZ:
   3386     case INTRIN_POPCOUNT:
   3387       emit_intrinsic_bit_op(t, w);
   3388       return;
   3389     case INTRIN_BSWAP:
   3390       emit_intrinsic_bswap(t, w);
   3391       return;
   3392     case INTRIN_SMUL_HIGH:
   3393     case INTRIN_UMUL_HIGH:
   3394       emit_intrinsic_mul_high(t, w);
   3395       return;
   3396     case INTRIN_SADD_OVERFLOW:
   3397     case INTRIN_UADD_OVERFLOW:
   3398     case INTRIN_SSUB_OVERFLOW:
   3399     case INTRIN_USUB_OVERFLOW:
   3400     case INTRIN_SMUL_OVERFLOW:
   3401     case INTRIN_UMUL_OVERFLOW:
   3402       emit_intrinsic_overflow(t, w);
   3403       return;
   3404     default:
   3405       wfail(t, "wasm: unexpected intrinsic kind %d in linearizer", (int)k);
   3406   }
   3407 }
   3408 
   3409 static void linearize_range(WTarget* t, LoweringState* L, u32 start, u32 end) {
   3410   for (u32 i = start; i < end; ++i) {
   3411     WIR* w = &t->wir[i];
   3412     switch (w->op) {
   3413       case WIR_LOAD_IMM: {
   3414         WasmValType vt = type_valtype(t, w->type);
   3415         emit_push_imm(t, vt, w->imm);
   3416         emit_local_set(t, w->dst, w->type, (RegClass)w->cls);
   3417         break;
   3418       }
   3419       case WIR_LOAD_CONST_F: {
   3420         WasmValType vt = type_valtype(t, w->type);
   3421         emit_fp(t,
   3422                 vt == WASM_VAL_F64 ? WASM_INSN_F64_CONST : WASM_INSN_F32_CONST,
   3423                 w->fp_imm);
   3424         emit_local_set(t, w->dst, w->type, (RegClass)w->cls);
   3425         break;
   3426       }
   3427       case WIR_COPY: {
   3428         emit_push_operand_reg(t, w->a);
   3429         emit_local_set(t, w->dst, w->type, (RegClass)w->cls);
   3430         break;
   3431       }
   3432       case WIR_BINOP: {
   3433         emit_push_operand(t, w->imm_kind, w->imm_a, w->a, w->type);
   3434         emit_push_operand(t, w->imm_kind_b, w->imm_b, w->b, w->type);
   3435         WasmValType vt = type_valtype(t, w->type);
   3436         emit_insn(t, binop_kind(t, (BinOp)w->cgop, vt), 0);
   3437         emit_local_set(t, w->dst, w->type, (RegClass)w->cls);
   3438         break;
   3439       }
   3440       case WIR_UNOP: {
   3441         WasmValType vt = type_valtype(t, w->type);
   3442         switch ((UnOp)w->cgop) {
   3443           case UO_NEG: {
   3444             /* 0 - a */
   3445             emit_push_imm(t, vt, 0);
   3446             emit_push_operand(t, w->imm_kind, w->imm_a, w->a, w->type);
   3447             emit_insn(
   3448                 t, vt == WASM_VAL_I64 ? WASM_INSN_I64_SUB : WASM_INSN_I32_SUB,
   3449                 0);
   3450             break;
   3451           }
   3452           case UO_FNEG: {
   3453             emit_push_operand(t, w->imm_kind, w->imm_a, w->a, w->type);
   3454             emit_insn(
   3455                 t, vt == WASM_VAL_F64 ? WASM_INSN_F64_NEG : WASM_INSN_F32_NEG,
   3456                 0);
   3457             break;
   3458           }
   3459           case UO_BNOT: {
   3460             /* a XOR -1 */
   3461             emit_push_operand(t, w->imm_kind, w->imm_a, w->a, w->type);
   3462             emit_push_imm(t, vt, -1);
   3463             emit_insn(
   3464                 t, vt == WASM_VAL_I64 ? WASM_INSN_I64_XOR : WASM_INSN_I32_XOR,
   3465                 0);
   3466             break;
   3467           }
   3468           case UO_NOT: {
   3469             /* a == 0 — i{32,64}.eqz always produces an i32 0/1. When the CG
   3470              * destination is i64 (e.g. !x where x was zext'd to i64 before the
   3471              * negation), widen the i32 boolean back to i64 so the following
   3472              * local.set is well-typed. */
   3473             emit_push_operand(t, w->imm_kind, w->imm_a, w->a, w->type);
   3474             emit_insn(
   3475                 t, vt == WASM_VAL_I64 ? WASM_INSN_I64_EQZ : WASM_INSN_I32_EQZ,
   3476                 0);
   3477             if (vt == WASM_VAL_I64) emit_insn(t, WASM_INSN_I64_EXTEND_I32_U, 0);
   3478             break;
   3479           }
   3480         }
   3481         emit_local_set(t, w->dst, w->type, (RegClass)w->cls);
   3482         break;
   3483       }
   3484       case WIR_CMP: {
   3485         CmpOp cop = (CmpOp)w->cgop;
   3486         if (cop >= CMP_OEQ_F) {
   3487           emit_fp_cmp(t, cop, w, w->type2);
   3488         } else {
   3489           push_cmp_operands(t, w, w->type2);
   3490           emit_insn(t, cmp_kind(t, cop, type_valtype(t, w->type2)), 0);
   3491         }
   3492         /* cmp result is i32 (0/1). dst type may be wider — but cg generally
   3493          * stores cmp results into i32. */
   3494         emit_local_set(t, w->dst, w->type, (RegClass)w->cls);
   3495         break;
   3496       }
   3497       case WIR_CONVERT: {
   3498         WasmValType src = type_valtype(t, w->type2);
   3499         WasmValType dst = type_valtype(t, w->type);
   3500         u32 sw = kit_cg_type_int_width((KitCompiler*)t->c, w->type2);
   3501         u32 dw = kit_cg_type_int_width((KitCompiler*)t->c, w->type);
   3502         emit_push_operand(t, w->imm_kind, w->imm_a, w->a, w->type2);
   3503         emit_convert(t, (ConvKind)w->cgop, src, dst, sw, dw);
   3504         emit_local_set(t, w->dst, w->type, (RegClass)w->cls);
   3505         break;
   3506       }
   3507       case WIR_CALL:
   3508       case WIR_CALL_INDIRECT: {
   3509         /* Tail calls tear down the caller's wasm frame (return_call /
   3510          * return_call_indirect have polymorphic-unreachable type after the
   3511          * call). Mirror the WIR_RET linear-stack epilogue before pushing
   3512          * args so the linear-memory stack frame is released. Operands
   3513          * below come from wasm locals (incoming params or reg-locals),
   3514          * not from the linear stack we just freed. Variadic tail calls are
   3515          * rejected upstream; sret tail calls forward the incoming pointer. */
   3516         if (w->call_tail && t->has_stack_frame) {
   3517           emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->frame_saved_sp_local);
   3518           emit_insn(t, WASM_INSN_GLOBAL_SET, (i64)t->stack_pointer_global);
   3519         }
   3520         if (w->call_has_sret) {
   3521           if (w->call_tail) {
   3522             /* Forward this function's own incoming sret pointer: the callee
   3523              * writes the same buffer (in our caller's frame, which outlives
   3524              * the sibling call) and return_calls back. The pointer is a wasm
   3525              * local, unaffected by the linear-frame teardown above. */
   3526             if (t->sret_param_local == 0xffffffffu)
   3527               wfail(t, "wasm: sret tail call without an incoming sret pointer");
   3528             emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->sret_param_local);
   3529           } else {
   3530             /* Push sret pointer (address of caller-allocated buffer). */
   3531             uint64_t off;
   3532             emit_addr_operand(t, w->call_sret_addr, &off);
   3533             if (off) {
   3534               emit_insn(t, WASM_INSN_I32_CONST, (i64)off);
   3535               emit_insn(t, WASM_INSN_I32_ADD, 0);
   3536             }
   3537           }
   3538         }
   3539         for (u32 a = 0; a < w->call_narg; ++a) {
   3540           if (w->call_arg_kinds[a] == WOP_REG) {
   3541             emit_push_operand_reg(t, w->call_args[a]);
   3542           } else if (w->call_arg_kinds[a] == WOP_IMM) {
   3543             WasmValType vt = type_valtype(t, w->call_arg_types[a]);
   3544             emit_push_imm(t, vt, w->call_arg_imms[a]);
   3545           } else if (w->call_arg_kinds[a] == WOP_ADDR) {
   3546             uint64_t off;
   3547             emit_addr_operand(t, w->call_arg_addrs[a], &off);
   3548             if (off) {
   3549               emit_insn(t, WASM_INSN_I32_CONST, (i64)off);
   3550               emit_insn(t, WASM_INSN_I32_ADD, 0);
   3551             }
   3552           } else {
   3553             wfail(t, "wasm: bad call-arg kind %u", w->call_arg_kinds[a]);
   3554           }
   3555         }
   3556         /* Variadic packing. Each variadic arg occupies an 8-byte slot in a
   3557          * caller-allocated linear-memory buffer; the buffer's address is
   3558          * passed as the hidden trailing i32. We save __stack_pointer to a
   3559          * scratch local before allocating the buffer and restore it after
   3560          * the call returns, so a variadic call in a loop doesn't grow the
   3561          * linear stack. See wasm_va_start / wasm_va_arg for the callee side.
   3562          */
   3563         if (w->call_variadic) {
   3564           if (w->call_nvar == 0u) {
   3565             /* No varargs: still pass a hidden i32. NULL is fine — the callee
   3566              * must not deref va_list without a matching @va_arg, which a
   3567              * well-typed program won't do. */
   3568             emit_insn(t, WASM_INSN_I32_CONST, 0);
   3569           } else {
   3570             ensure_stack_pointer(t);
   3571             if (t->varcall_saved_sp_local == 0xffffffffu)
   3572               t->varcall_saved_sp_local = add_wasm_local(t, WASM_VAL_I32);
   3573             if (t->varcall_buf_local == 0xffffffffu)
   3574               t->varcall_buf_local = add_wasm_local(t, WASM_VAL_I32);
   3575             u32 buf_size = w->call_nvar * 8u;
   3576             /* Save SP, allocate aligned buffer, set SP = buf. */
   3577             emit_insn(t, WASM_INSN_GLOBAL_GET, (i64)t->stack_pointer_global);
   3578             emit_insn(t, WASM_INSN_LOCAL_TEE, (i64)t->varcall_saved_sp_local);
   3579             emit_insn(t, WASM_INSN_I32_CONST, (i64)buf_size);
   3580             emit_insn(t, WASM_INSN_I32_SUB, 0);
   3581             emit_insn(t, WASM_INSN_I32_CONST, -(i64)8);
   3582             emit_insn(t, WASM_INSN_I32_AND, 0);
   3583             emit_insn(t, WASM_INSN_LOCAL_TEE, (i64)t->varcall_buf_local);
   3584             emit_insn(t, WASM_INSN_GLOBAL_SET, (i64)t->stack_pointer_global);
   3585             /* Pack each variadic arg at offset i*8. Store width is the
   3586              * value's natural width (i32/i64/f32/f64); the unused high
   3587              * bytes of i32/f32 slots are left as whatever __stack_pointer
   3588              * pointed at, which @va_arg won't read for those slots. */
   3589             for (u32 v = 0; v < w->call_nvar; ++v) {
   3590               KitCgTypeId vty = w->call_var_types[v];
   3591               WasmValType vvt = type_valtype(t, vty);
   3592               WasmInsnKind store_op;
   3593               u32 width;
   3594               if (vvt == WASM_VAL_I64) {
   3595                 store_op = WASM_INSN_I64_STORE;
   3596                 width = 8u;
   3597               } else if (vvt == WASM_VAL_F32) {
   3598                 store_op = WASM_INSN_F32_STORE;
   3599                 width = 4u;
   3600               } else if (vvt == WASM_VAL_F64) {
   3601                 store_op = WASM_INSN_F64_STORE;
   3602                 width = 8u;
   3603               } else {
   3604                 store_op = WASM_INSN_I32_STORE;
   3605                 width = 4u;
   3606               }
   3607               emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->varcall_buf_local);
   3608               if (w->call_var_kinds[v] == WOP_REG) {
   3609                 emit_push_operand_reg(t, w->call_var_regs[v]);
   3610               } else if (w->call_var_kinds[v] == WOP_IMM) {
   3611                 if (vvt == WASM_VAL_F32 || vvt == WASM_VAL_F64)
   3612                   wfail(t, "wasm: float immediate variadic arg unsupported");
   3613                 emit_push_imm(t, vvt, w->call_var_imms[v]);
   3614               } else {
   3615                 wfail(t, "wasm: bad variadic-arg kind %u",
   3616                       w->call_var_kinds[v]);
   3617               }
   3618               wasm_func_add_mem_insn(t->c, t->module, t->cur_func, store_op,
   3619                                      memarg_align_log2(width, width),
   3620                                      (u64)(v * 8u), 0u);
   3621             }
   3622             /* Push buf addr as hidden last arg. */
   3623             emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->varcall_buf_local);
   3624           }
   3625         }
   3626         if (w->op == WIR_CALL_INDIRECT) {
   3627           /* Callee: push the i32 table index. */
   3628           emit_push_operand_reg(t, w->a);
   3629           /* call_indirect / return_call_indirect both encode
   3630            * (typeidx, tableidx). The encoder reads `imm` as typeidx and
   3631            * `align` as tableidx. */
   3632           wasm_func_add_insn(t->c, t->module, t->cur_func,
   3633                              w->call_tail ? WASM_INSN_RETURN_CALL_INDIRECT
   3634                                           : WASM_INSN_CALL_INDIRECT,
   3635                              w->imm);
   3636           t->cur_func->insns[t->cur_func->ninsns - 1u].align = 0u;
   3637         } else {
   3638           u32 idx = sym_to_wasm_func(t, w->call_sym, NULL);
   3639           emit_insn(t, w->call_tail ? WASM_INSN_RETURN_CALL : WASM_INSN_CALL,
   3640                     (i64)idx);
   3641         }
   3642         /* Tail calls never return to this function: the operand stack is
   3643          * polymorphic-unreachable after return_call*, so writing dst or
   3644          * restoring the variadic stack pointer would be dead and would
   3645          * also corrupt stack typing. (Variadic tail calls are rejected
   3646          * upstream, so the variadic SP-restore guard is defensive.) */
   3647         if (!w->call_tail) {
   3648           if (w->dst != REG_NONE) {
   3649             emit_local_set(t, w->dst, w->type, (RegClass)w->cls);
   3650           }
   3651           /* Restore SP after variadic call so loop-resident variadic calls
   3652            * don't accumulate stack usage. Done after stashing the return
   3653            * value into its local. */
   3654           if (w->call_variadic && w->call_nvar) {
   3655             emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->varcall_saved_sp_local);
   3656             emit_insn(t, WASM_INSN_GLOBAL_SET, (i64)t->stack_pointer_global);
   3657           }
   3658         }
   3659         break;
   3660       }
   3661       case WIR_RET: {
   3662         if (w->cgop == 1) {
   3663           /* Aggregate sret return: memcpy w->addr -> *sret_param, then
   3664            * void return. The sret pointer was the hidden first wasm param.
   3665            * NOTE: this still uses a byte loop rather than memory.copy so
   3666            * the produced module remains loadable by the kit runtime
   3667            * before the wasm-core default-feature change lands. The path
   3668            * will collapse to memory.copy once the core's default feature
   3669            * set includes WASM_FEATURE_BULK_MEMORY (subagent A). */
   3670           if (t->sret_param_local == 0xffffffffu)
   3671             wfail(t, "wasm: sret return without hidden sret param");
   3672           for (u32 n = 0; n < w->agg.size; ++n) {
   3673             /* Push destination address (sret_ptr) onto stack. The memarg
   3674              * offset on the i32.store8 carries the per-byte offset. */
   3675             emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->sret_param_local);
   3676             /* Load src byte at (w->addr + n). */
   3677             Operand src = w->addr;
   3678             if (src.kind == OPK_INDIRECT)
   3679               src.v.ind.ofs += (i32)n;
   3680             else if (src.kind == OPK_GLOBAL)
   3681               src.v.global.addend += n;
   3682             uint64_t src_off;
   3683             emit_addr_operand(t, src, &src_off);
   3684             if (src.kind == OPK_LOCAL) src_off += n;
   3685             wasm_func_add_mem_insn(t->c, t->module, t->cur_func,
   3686                                    WASM_INSN_I32_LOAD8_U, 0, src_off, 0);
   3687             wasm_func_add_mem_insn(t->c, t->module, t->cur_func,
   3688                                    WASM_INSN_I32_STORE8, 0, n, 0);
   3689           }
   3690         } else if (w->dst != REG_NONE)
   3691           emit_push_operand_reg(t, w->dst);
   3692         else if (w->imm_kind == 1) {
   3693           WasmValType vt = type_valtype(t, w->type);
   3694           emit_push_imm(t, vt, w->imm_a);
   3695         }
   3696         if (t->has_stack_frame) {
   3697           emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->frame_saved_sp_local);
   3698           emit_insn(t, WASM_INSN_GLOBAL_SET, (i64)t->stack_pointer_global);
   3699         }
   3700         emit_insn(t, WASM_INSN_RETURN, 0);
   3701         break;
   3702       }
   3703       case WIR_UNREACHABLE: {
   3704         emit_insn(t, WASM_INSN_UNREACHABLE, 0);
   3705         break;
   3706       }
   3707       case WIR_LOAD_LOCAL: {
   3708         u32 wli = (u32)w->imm;
   3709         emit_insn(t, WASM_INSN_LOCAL_GET, (i64)wli);
   3710         emit_local_set(t, w->dst, w->type, (RegClass)w->cls);
   3711         break;
   3712       }
   3713       case WIR_STORE_LOCAL: {
   3714         u32 wli = (u32)w->imm;
   3715         if (w->imm_kind == 1) {
   3716           WasmValType vt = type_valtype(t, w->type);
   3717           emit_push_imm(t, vt, w->imm_a);
   3718         } else {
   3719           emit_push_operand_reg(t, w->a);
   3720         }
   3721         emit_insn(t, WASM_INSN_LOCAL_SET, (i64)wli);
   3722         break;
   3723       }
   3724       case WIR_LOAD_MEM: {
   3725         emit_load_addr(t, w->addr, w->type, w->mem);
   3726         emit_local_set(t, w->dst, w->type, (RegClass)w->cls);
   3727         break;
   3728       }
   3729       case WIR_STORE_MEM: {
   3730         Operand src;
   3731         memset(&src, 0, sizeof src);
   3732         src.kind = w->imm_kind == WOP_IMM ? OPK_IMM : OPK_REG;
   3733         src.type = w->type;
   3734         if (src.kind == OPK_IMM)
   3735           src.v.imm = w->imm_a;
   3736         else
   3737           src.v.reg = w->a;
   3738         emit_store_addr(t, w->addr, w->type, src, w->mem, w->imm_kind, w->imm_a,
   3739                         w->a);
   3740         break;
   3741       }
   3742       case WIR_ADDR_OF: {
   3743         uint64_t offset;
   3744         emit_addr_operand(t, w->addr, &offset);
   3745         if (offset) {
   3746           emit_insn(t, WASM_INSN_I32_CONST, (i64)offset);
   3747           emit_insn(t, WASM_INSN_I32_ADD, 0);
   3748         }
   3749         emit_local_set(t, w->dst, w->type, (RegClass)w->cls);
   3750         break;
   3751       }
   3752       case WIR_ALLOCA: {
   3753         u32 align = (u32)w->imm;
   3754         emit_insn(t, WASM_INSN_GLOBAL_GET, (i64)t->stack_pointer_global);
   3755         emit_push_operand(t, w->imm_kind, w->imm_a, w->a,
   3756                           w->type2 ? w->type2 : builtin_id(KIT_CG_BUILTIN_I32));
   3757         if (w->type2 && type_valtype(t, w->type2) == WASM_VAL_I64)
   3758           emit_insn(t, WASM_INSN_I32_WRAP_I64, 0);
   3759         emit_insn(t, WASM_INSN_I32_SUB, 0);
   3760         if (align > 1u) {
   3761           emit_insn(t, WASM_INSN_I32_CONST, -(i64)align);
   3762           emit_insn(t, WASM_INSN_I32_AND, 0);
   3763         }
   3764         emit_insn(t, WASM_INSN_LOCAL_TEE,
   3765                   (i64)reg_local(t, w->dst, w->type, (RegClass)w->cls));
   3766         emit_insn(t, WASM_INSN_GLOBAL_SET, (i64)t->stack_pointer_global);
   3767         break;
   3768       }
   3769       case WIR_COPY_BYTES: {
   3770         /* memory.copy: stack = dst_addr, src_addr, n; both memidx fields = 0.
   3771          */
   3772         Operand src_addr;
   3773         if (w->imm_kind != WOP_REG)
   3774           wfail(t, "wasm: copy_bytes source must be a register pointer");
   3775         memset(&src_addr, 0, sizeof src_addr);
   3776         src_addr.kind = OPK_INDIRECT;
   3777         src_addr.type = w->addr.type;
   3778         src_addr.v.ind.base = w->a;
   3779         src_addr.v.ind.index = REG_NONE;
   3780         src_addr.v.ind.log2_scale = 0;
   3781         src_addr.v.ind.ofs = 0;
   3782         if (w->agg.size == 0) break;
   3783         emit_push_addr_value(t, w->addr);
   3784         emit_push_addr_value(t, src_addr);
   3785         emit_insn(t, WASM_INSN_I32_CONST, (i64)(uint32_t)w->agg.size);
   3786         wasm_func_add_insn(t->c, t->module, t->cur_func, WASM_INSN_MEMORY_COPY,
   3787                            0);
   3788         /* dst memidx = 0, src memidx = 0 (kit-cc single-memory module). */
   3789         t->cur_func->insns[t->cur_func->ninsns - 1u].memidx = 0;
   3790         t->cur_func->insns[t->cur_func->ninsns - 1u].aux_idx = 0;
   3791         break;
   3792       }
   3793       case WIR_SET_BYTES: {
   3794         /* memory.fill: stack = dst_addr, value_i32, n; memidx = 0. */
   3795         if (w->imm_kind != WOP_IMM)
   3796           wfail(t, "wasm: set_bytes value must be immediate in v1");
   3797         if (w->agg.size == 0) break;
   3798         emit_push_addr_value(t, w->addr);
   3799         emit_insn(t, WASM_INSN_I32_CONST, (i64)(w->imm_a & 0xff));
   3800         emit_insn(t, WASM_INSN_I32_CONST, (i64)(uint32_t)w->agg.size);
   3801         wasm_func_add_insn(t->c, t->module, t->cur_func, WASM_INSN_MEMORY_FILL,
   3802                            0);
   3803         t->cur_func->insns[t->cur_func->ninsns - 1u].memidx = 0;
   3804         break;
   3805       }
   3806       case WIR_ATOMIC_LOAD: {
   3807         WasmInsnKind k = atomic_load_kind_for(t, w->type, w->mem);
   3808         u32 width = wasm_mem_width((uint8_t)k);
   3809         emit_push_operand_reg(t, w->a);
   3810         wasm_func_add_mem_insn(t->c, t->module, t->cur_func, k,
   3811                                memarg_align_log2(w->mem.align, width), 0, 0);
   3812         emit_local_set(t, w->dst, w->type, (RegClass)w->cls);
   3813         break;
   3814       }
   3815       case WIR_ATOMIC_STORE: {
   3816         WasmInsnKind k = atomic_store_kind_for(t, w->type, w->mem);
   3817         u32 width = wasm_mem_width((uint8_t)k);
   3818         emit_push_operand_reg(t, w->a);
   3819         emit_push_operand(t, w->imm_kind_b, w->imm_b, w->b, w->type);
   3820         wasm_func_add_mem_insn(t->c, t->module, t->cur_func, k,
   3821                                memarg_align_log2(w->mem.align, width), 0, 0);
   3822         break;
   3823       }
   3824       case WIR_ATOMIC_RMW: {
   3825         if ((KitCgAtomicOp)w->cgop == KIT_CG_ATOMIC_NAND) {
   3826           /* wasm-threads has no atomic.rmw.nand. Expand to a cmpxchg retry
   3827            * loop computing desired = ~(old & val):
   3828            *   loop
   3829            *     old = atomic.load(addr)            ; tee into old_local
   3830            *     desired = (old & val) ^ -1
   3831            *     got = atomic.rmw.cmpxchg(addr, old, desired)
   3832            *     br_if loop  (got != old)           ; lost the race, retry
   3833            *   end
   3834            *   dst = old_local                      ; fetch returns prior value
   3835            */
   3836           WasmValType vt = type_valtype(t, w->type);
   3837           WasmInsnKind load_k = atomic_load_kind_for(t, w->type, w->mem);
   3838           WasmInsnKind cas_k = atomic_cmpxchg_kind_for(t, w->type, w->mem);
   3839           u32 load_w = wasm_mem_width((uint8_t)load_k);
   3840           u32 cas_w = wasm_mem_width((uint8_t)cas_k);
   3841           int is64 = (vt == WASM_VAL_I64);
   3842           u32 old_local = add_wasm_local(t, vt);
   3843           emit_insn(t, WASM_INSN_LOOP, 0);
   3844           /* addr (cmpxchg arg0) */
   3845           emit_push_operand_reg(t, w->a);
   3846           /* expected = atomic.load(addr), tee into old_local */
   3847           emit_push_operand_reg(t, w->a);
   3848           wasm_func_add_mem_insn(t->c, t->module, t->cur_func, load_k,
   3849                                  memarg_align_log2(w->mem.align, load_w), 0, 0);
   3850           emit_insn(t, WASM_INSN_LOCAL_TEE, (i64)old_local);
   3851           /* desired = (old & val) ^ -1 */
   3852           emit_insn(t, WASM_INSN_LOCAL_GET, (i64)old_local);
   3853           emit_push_operand(t, w->imm_kind_b, w->imm_b, w->b, w->type);
   3854           emit_insn(t, is64 ? WASM_INSN_I64_AND : WASM_INSN_I32_AND, 0);
   3855           emit_push_imm(t, vt, -1);
   3856           emit_insn(t, is64 ? WASM_INSN_I64_XOR : WASM_INSN_I32_XOR, 0);
   3857           /* cmpxchg -> value previously in memory */
   3858           wasm_func_add_mem_insn(t->c, t->module, t->cur_func, cas_k,
   3859                                  memarg_align_log2(w->mem.align, cas_w), 0, 0);
   3860           /* retry if memory had changed (got != expected) */
   3861           emit_insn(t, WASM_INSN_LOCAL_GET, (i64)old_local);
   3862           emit_insn(t, is64 ? WASM_INSN_I64_NE : WASM_INSN_I32_NE, 0);
   3863           emit_insn(t, WASM_INSN_BR_IF, 0); /* 0 = innermost loop */
   3864           emit_insn(t, WASM_INSN_END, 0);
   3865           emit_insn(t, WASM_INSN_LOCAL_GET, (i64)old_local);
   3866           emit_local_set(t, w->dst, w->type, (RegClass)w->cls);
   3867           break;
   3868         }
   3869         WasmInsnKind k =
   3870             atomic_rmw_kind_for(t, (KitCgAtomicOp)w->cgop, w->type, w->mem);
   3871         u32 width = wasm_mem_width((uint8_t)k);
   3872         emit_push_operand_reg(t, w->a);
   3873         emit_push_operand(t, w->imm_kind_b, w->imm_b, w->b, w->type);
   3874         wasm_func_add_mem_insn(t->c, t->module, t->cur_func, k,
   3875                                memarg_align_log2(w->mem.align, width), 0, 0);
   3876         emit_local_set(t, w->dst, w->type, (RegClass)w->cls);
   3877         break;
   3878       }
   3879       case WIR_ATOMIC_CAS: {
   3880         WasmInsnKind k = atomic_cmpxchg_kind_for(t, w->type, w->mem);
   3881         WasmValType vt = type_valtype(t, w->type);
   3882         u32 width = wasm_mem_width((uint8_t)k);
   3883         /* Save expected into a fresh wasm local before consuming inputs. CG
   3884          * may reuse one of (addr, expected, desired) regs for prior or ok;
   3885          * reg_local() for w->dst/w->dst2 would then rebind that reg's local
   3886          * mid-stream, and re-pushing expected via the (now-stale) mapping
   3887          * would read an uninitialized local. The temp sidesteps that. */
   3888         u32 saved_expected = add_wasm_local(t, vt);
   3889         /* push addr; expected (tee into saved-expected, leaves on stack);
   3890          * desired; cmpxchg -> prior on stack. */
   3891         emit_push_operand_reg(t, w->a);
   3892         emit_push_operand(t, w->imm_kind_b, w->imm_b, w->b, w->type);
   3893         emit_insn(t, WASM_INSN_LOCAL_TEE, (i64)saved_expected);
   3894         emit_push_operand(t, w->imm_kind_c, w->imm_c, w->op_c, w->type);
   3895         wasm_func_add_mem_insn(t->c, t->module, t->cur_func, k,
   3896                                memarg_align_log2(w->mem.align, width), 0, 0);
   3897         /* All input regs have been consumed; safe to rebind. */
   3898         u32 prior_local = reg_local(t, w->dst, w->type, (RegClass)w->cls);
   3899         emit_insn(t, WASM_INSN_LOCAL_TEE, (i64)prior_local);
   3900         emit_insn(t, WASM_INSN_LOCAL_GET, (i64)saved_expected);
   3901         emit_insn(t, vt == WASM_VAL_I64 ? WASM_INSN_I64_EQ : WASM_INSN_I32_EQ,
   3902                   0);
   3903         emit_local_set(t, w->dst2,
   3904                        w->type2 ? w->type2 : builtin_id(KIT_CG_BUILTIN_BOOL),
   3905                        RC_INT);
   3906         break;
   3907       }
   3908       case WIR_FENCE: {
   3909         emit_insn(t, WASM_INSN_ATOMIC_FENCE, 0);
   3910         break;
   3911       }
   3912       case WIR_JUMP: {
   3913         u32 d = br_to_label(L, w->labels[0]);
   3914         emit_insn(t, WASM_INSN_BR, (i64)d);
   3915         break;
   3916       }
   3917       case WIR_CMP_BRANCH: {
   3918         CmpOp cop = (CmpOp)w->cgop;
   3919         if (cop >= CMP_OEQ_F) {
   3920           emit_fp_cmp(t, cop, w, w->type);
   3921         } else {
   3922           push_cmp_operands(t, w, w->type);
   3923           emit_insn(t, cmp_kind(t, cop, type_valtype(t, w->type)), 0);
   3924         }
   3925         u32 d = br_to_label(L, w->labels[0]);
   3926         emit_insn(t, WASM_INSN_BR_IF, (i64)d);
   3927         break;
   3928       }
   3929       case WIR_SWITCH: {
   3930         emit_switch_br_table(t, L, w);
   3931         break;
   3932       }
   3933       case WIR_SCOPE_OPEN: {
   3934         if (L->nstack >= 1024u)
   3935           wfail(t, "wasm: scope nesting too deep (max 1024)");
   3936         LoweringScope* s = &L->stack[L->nstack++];
   3937         s->id = w->scope_id;
   3938         s->kind = w->cgop;
   3939         if (w->cgop == SCOPE_LOOP) {
   3940           /* (block (loop ...)); inside the body:
   3941            *   br to loop top (cur_depth+1) = continue
   3942            *   br to past block (cur_depth) = break (one more level out) */
   3943           emit_insn(t, WASM_INSN_BLOCK, 0);
   3944           L->cur_depth++;
   3945           s->break_depth = L->cur_depth; /* `br N` lands AFTER block */
   3946           emit_insn(t, WASM_INSN_LOOP, 0);
   3947           L->cur_depth++;
   3948           s->cont_depth = L->cur_depth; /* `br N` lands at LOOP top */
   3949         } else if (w->cgop == SCOPE_BLOCK) {
   3950           emit_insn(t, WASM_INSN_BLOCK, 0);
   3951           L->cur_depth++;
   3952           s->break_depth = L->cur_depth;
   3953           s->cont_depth = L->cur_depth; /* unused */
   3954         } else {
   3955           wfail(t, "wasm: unknown scope kind %d", (int)w->cgop);
   3956         }
   3957         break;
   3958       }
   3959       case WIR_SCOPE_CLOSE: {
   3960         if (L->nstack == 0) wfail(t, "wasm: scope_close without open scope");
   3961         LoweringScope* s = &L->stack[L->nstack - 1u];
   3962         if (s->kind == SCOPE_LOOP) {
   3963           emit_insn(t, WASM_INSN_END, 0); /* close loop */
   3964           L->cur_depth--;
   3965           emit_insn(t, WASM_INSN_END, 0); /* close outer block */
   3966           L->cur_depth--;
   3967         } else {
   3968           emit_insn(t, WASM_INSN_END, 0);
   3969           L->cur_depth--;
   3970         }
   3971         L->nstack--;
   3972         break;
   3973       }
   3974       case WIR_VA_START: {
   3975         /* *ap_addr = va_ptr_param_local */
   3976         emit_push_addr_value(t, w->addr);
   3977         emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->va_ptr_param_local);
   3978         wasm_func_add_mem_insn(t->c, t->module, t->cur_func,
   3979                                WASM_INSN_I32_STORE, 2u, 0u, 0u);
   3980         break;
   3981       }
   3982       case WIR_VA_ARG: {
   3983         if (t->va_arg_tmp_addr_local == 0xffffffffu)
   3984           t->va_arg_tmp_addr_local = add_wasm_local(t, WASM_VAL_I32);
   3985         WasmValType vt = type_valtype(t, w->type);
   3986         WasmInsnKind load_op;
   3987         u32 width;
   3988         if (vt == WASM_VAL_I64) {
   3989           load_op = WASM_INSN_I64_LOAD;
   3990           width = 8u;
   3991         } else if (vt == WASM_VAL_F32) {
   3992           load_op = WASM_INSN_F32_LOAD;
   3993           width = 4u;
   3994         } else if (vt == WASM_VAL_F64) {
   3995           load_op = WASM_INSN_F64_LOAD;
   3996           width = 8u;
   3997         } else {
   3998           load_op = WASM_INSN_I32_LOAD;
   3999           width = 4u;
   4000         }
   4001         /* Load T from current *ap and stash into dst. */
   4002         emit_push_addr_value(t, w->addr);
   4003         emit_insn(t, WASM_INSN_LOCAL_TEE, (i64)t->va_arg_tmp_addr_local);
   4004         wasm_func_add_mem_insn(t->c, t->module, t->cur_func, WASM_INSN_I32_LOAD,
   4005                                2u, 0u, 0u);
   4006         wasm_func_add_mem_insn(t->c, t->module, t->cur_func, load_op,
   4007                                memarg_align_log2(width, width), 0u, 0u);
   4008         emit_local_set(t, w->dst, w->type, (RegClass)w->cls);
   4009         /* Advance: *ap = *ap + 8. */
   4010         emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->va_arg_tmp_addr_local);
   4011         emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->va_arg_tmp_addr_local);
   4012         wasm_func_add_mem_insn(t->c, t->module, t->cur_func, WASM_INSN_I32_LOAD,
   4013                                2u, 0u, 0u);
   4014         emit_insn(t, WASM_INSN_I32_CONST, (i64)8);
   4015         emit_insn(t, WASM_INSN_I32_ADD, 0);
   4016         wasm_func_add_mem_insn(t->c, t->module, t->cur_func,
   4017                                WASM_INSN_I32_STORE, 2u, 0u, 0u);
   4018         break;
   4019       }
   4020       case WIR_VA_COPY: {
   4021         /* *dst_ap = *src_ap (single i32). */
   4022         emit_push_addr_value(t, w->addr);
   4023         emit_push_addr_value(t, w->call_sret_addr);
   4024         wasm_func_add_mem_insn(t->c, t->module, t->cur_func, WASM_INSN_I32_LOAD,
   4025                                2u, 0u, 0u);
   4026         wasm_func_add_mem_insn(t->c, t->module, t->cur_func,
   4027                                WASM_INSN_I32_STORE, 2u, 0u, 0u);
   4028         break;
   4029       }
   4030       case WIR_INTRINSIC: {
   4031         emit_intrinsic(t, w);
   4032         break;
   4033       }
   4034       case WIR_ASM_BLOCK: {
   4035         Heap* h_blk = t->c->ctx->heap;
   4036         u32 nin = w->asm_nin;
   4037         u32 nout = w->asm_nout;
   4038         u32* in_locals = NULL;
   4039         u32* out_locals = NULL;
   4040         if (nin) {
   4041           in_locals =
   4042               (u32*)h_blk->alloc(h_blk, sizeof(u32) * nin, _Alignof(u32));
   4043           if (!in_locals) wfail(t, "wasm: out of memory");
   4044           /* defer per-input allocation until after output locals are known
   4045            * so numeric tieback ("+r", "0".."9") can share. */
   4046         }
   4047         if (nout) {
   4048           out_locals =
   4049               (u32*)h_blk->alloc(h_blk, sizeof(u32) * nout, _Alignof(u32));
   4050           if (!out_locals) wfail(t, "wasm: out of memory");
   4051           for (u32 i = 0; i < nout; ++i)
   4052             out_locals[i] =
   4053                 add_wasm_local(t, valtype_for_type(t, w->asm_out_types[i]));
   4054         }
   4055         if (nin) {
   4056           for (u32 i = 0; i < nin; ++i) {
   4057             i32 share = w->asm_in_share_out[i];
   4058             if (share >= 0 && (u32)share < nout) {
   4059               in_locals[i] = out_locals[share];
   4060             } else {
   4061               in_locals[i] =
   4062                   add_wasm_local(t, valtype_for_type(t, w->asm_in_types[i]));
   4063             }
   4064           }
   4065         }
   4066         /* Input materialization: push source operand, then for OPK_INDIRECT
   4067          * inputs ("m" constraint with displacement) splice in
   4068          * `i32.const ofs; i32.add` so the input local holds base+ofs.
   4069          * Finally local.set into the input's local (which may be a shared
   4070          * output local). */
   4071         for (u32 i = 0; i < nin; ++i) {
   4072           emit_push_operand(t, w->asm_in_kinds[i], w->asm_in_imms[i],
   4073                             w->asm_in_regs[i], w->asm_in_types[i]);
   4074           if (w->asm_in_kinds[i] == WOP_REG && w->asm_in_imms[i] != 0) {
   4075             emit_push_imm(t, WASM_VAL_I32, w->asm_in_imms[i]);
   4076             emit_insn(t, WASM_INSN_I32_ADD, 0);
   4077           }
   4078           emit_insn(t, WASM_INSN_LOCAL_SET, (i64)in_locals[i]);
   4079         }
   4080         /* Splice body, remapping local indices < nin+nout to the actual
   4081          * wasm local table. */
   4082         for (u32 i = 0; i < w->raw_ninsns; ++i) {
   4083           WasmInsn in = w->raw_insns[i];
   4084           if (in.kind == WASM_INSN_LOCAL_GET ||
   4085               in.kind == WASM_INSN_LOCAL_SET ||
   4086               in.kind == WASM_INSN_LOCAL_TEE) {
   4087             if (in.imm >= 0 && (u64)in.imm < (u64)nin)
   4088               in.imm = (i64)in_locals[in.imm];
   4089             else if (in.imm >= (i64)nin && (u64)in.imm < (u64)(nin + nout))
   4090               in.imm = (i64)out_locals[in.imm - (i64)nin];
   4091           }
   4092           t->module->current_loc = in.loc;
   4093           wasm_func_add_insn(t->c, t->module, t->cur_func,
   4094                              (WasmInsnKind)in.kind, in.imm);
   4095           t->cur_func->insns[t->cur_func->ninsns - 1u] = in;
   4096         }
   4097         /* Output extraction: copy each output local into the destination
   4098          * Reg's wasm local. */
   4099         for (u32 i = 0; i < nout; ++i) {
   4100           WasmValType ovt = valtype_for_type(t, w->asm_out_types[i]);
   4101           RegClass cls =
   4102               (ovt == WASM_VAL_F32 || ovt == WASM_VAL_F64) ? RC_FP : RC_INT;
   4103           emit_insn(t, WASM_INSN_LOCAL_GET, (i64)out_locals[i]);
   4104           emit_local_set(t, w->asm_out_regs[i], w->asm_out_types[i], cls);
   4105         }
   4106         if (in_locals) h_blk->free(h_blk, in_locals, sizeof(u32) * nin);
   4107         if (out_locals) h_blk->free(h_blk, out_locals, sizeof(u32) * nout);
   4108         break;
   4109       }
   4110       case WIR_LABEL: {
   4111         break;
   4112       }
   4113     }
   4114   }
   4115 }
   4116 
   4117 static void linearize(WTarget* t) {
   4118   LoweringState L;
   4119   /* Rewrite WIR so every free label is bound to a synthetic SCOPE_BLOCK
   4120    * (forward goto) or SCOPE_LOOP (backward goto). Switch islands are
   4121    * pre-reordered into selector + WIR_SWITCH + case bodies, turning their
   4122    * case labels into forward refs the same structuring covers. After this,
   4123    * br_to_label resolves every jump through scope-bound machinery. */
   4124   wasm_structurize(t);
   4125   memset(&L, 0, sizeof L);
   4126   L.t = t;
   4127 
   4128   if (t->has_stack_frame) {
   4129     t->frame_saved_sp_local = add_wasm_local(t, WASM_VAL_I32);
   4130     t->frame_base_local = add_wasm_local(t, WASM_VAL_I32);
   4131     emit_insn(t, WASM_INSN_GLOBAL_GET, (i64)t->stack_pointer_global);
   4132     emit_insn(t, WASM_INSN_LOCAL_TEE, (i64)t->frame_saved_sp_local);
   4133     if (t->frame_size) {
   4134       emit_insn(t, WASM_INSN_I32_CONST,
   4135                 (i64)align_to_u32(t->frame_size, t->frame_align));
   4136       emit_insn(t, WASM_INSN_I32_SUB, 0);
   4137     }
   4138     emit_insn(t, WASM_INSN_LOCAL_TEE, (i64)t->frame_base_local);
   4139     emit_insn(t, WASM_INSN_GLOBAL_SET, (i64)t->stack_pointer_global);
   4140   }
   4141 
   4142   /* Byval copy-in: for each ABI_ARG_INDIRECT param, copy the aggregate from
   4143    * the caller's pointer into the callee's stack-frame buffer so callee
   4144    * mutations are isolated (wasm32 BasicCABI). Byte-by-byte for v1; can be
   4145    * promoted to wider chunks later. */
   4146   for (u32 i = 0; i < t->nbyval_copies; ++i) {
   4147     const WByvalCopy* bc = &t->byval_copies[i];
   4148     const WSlot* s = &t->slots[bc->dst_slot_id];
   4149     for (u32 n = 0; n < s->size; ++n) {
   4150       /* dst: frame_base */
   4151       emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->frame_base_local);
   4152       /* src byte: i32.load8_u (ptr_local) offset=n */
   4153       emit_insn(t, WASM_INSN_LOCAL_GET, (i64)bc->ptr_wasm_local);
   4154       wasm_func_add_mem_insn(t->c, t->module, t->cur_func,
   4155                              WASM_INSN_I32_LOAD8_U, 0, n, 0);
   4156       wasm_func_add_mem_insn(t->c, t->module, t->cur_func, WASM_INSN_I32_STORE8,
   4157                              0, s->frame_offset + n, 0);
   4158     }
   4159   }
   4160 
   4161   linearize_range(t, &L, 0, t->nwir);
   4162   if (L.nstack != 0)
   4163     wfail(t, "wasm: function ended with %u open scopes", L.nstack);
   4164   /* If the body's last real WIR is a terminator (return / br / switch /
   4165    * unreachable / tail call) buried inside nested blocks, kit's wasm
   4166    * validator does not propagate the unreachable flag across enclosing
   4167    * ENDs and would complain about a missing result at the implicit
   4168    * function exit. Emit an explicit trailing `unreachable` so control[0]
   4169    * is marked unreachable independent of the validator's propagation
   4170    * rules. Also: when the terminator is a tail call we've already
   4171    * emitted the linear-stack SP restore inline (see WIR_CALL handler),
   4172    * and the function never reaches the post-body epilogue at runtime —
   4173    * skip it to avoid emitting dead GLOBAL_GET/GLOBAL_SET pairs after the
   4174    * return_call. */
   4175   int last_is_tail_call = 0;
   4176   {
   4177     int needs_unreachable = 0;
   4178     for (u32 i = t->nwir; i > 0; --i) {
   4179       WIR* w = &t->wir[i - 1u];
   4180       if (w->op == WIR_LABEL || w->op == WIR_SCOPE_OPEN ||
   4181           w->op == WIR_SCOPE_CLOSE)
   4182         continue;
   4183       if (w->op == WIR_RET || w->op == WIR_JUMP || w->op == WIR_SWITCH ||
   4184           w->op == WIR_UNREACHABLE) {
   4185         needs_unreachable = 1;
   4186       } else if ((w->op == WIR_CALL || w->op == WIR_CALL_INDIRECT) &&
   4187                  w->call_tail) {
   4188         needs_unreachable = 1;
   4189         last_is_tail_call = 1;
   4190       }
   4191       break;
   4192     }
   4193     if (needs_unreachable) emit_insn(t, WASM_INSN_UNREACHABLE, 0);
   4194   }
   4195   if (t->has_stack_frame && !t->dead && !last_is_tail_call) {
   4196     emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->frame_saved_sp_local);
   4197     emit_insn(t, WASM_INSN_GLOBAL_SET, (i64)t->stack_pointer_global);
   4198   }
   4199 }
   4200 
   4201 void wasm_func_end(CGTarget* tg) {
   4202   WTarget* t = (WTarget*)tg;
   4203   if (!t->cur_func) return;
   4204   /* Linearize WIR into the WasmFunc body. */
   4205   linearize(t);
   4206   t->cur_fn_desc = NULL;
   4207   t->cur_func = NULL;
   4208   /* Free per-function WIR arg arrays. */
   4209   Heap* h = t->c->ctx->heap;
   4210   for (u32 i = 0; i < t->nwir; ++i) {
   4211     WIR* w = &t->wir[i];
   4212     if (w->call_args) {
   4213       h->free(h, w->call_args, sizeof(Reg) * w->call_narg);
   4214       h->free(h, w->call_arg_imms, sizeof(i64) * w->call_narg);
   4215       h->free(h, w->call_arg_kinds, w->call_narg);
   4216       h->free(h, w->call_arg_types, sizeof(KitCgTypeId) * w->call_narg);
   4217       if (w->call_arg_addrs)
   4218         h->free(h, w->call_arg_addrs, sizeof(Operand) * w->call_narg);
   4219       w->call_args = NULL;
   4220       w->call_arg_imms = NULL;
   4221       w->call_arg_kinds = NULL;
   4222       w->call_arg_types = NULL;
   4223       w->call_arg_addrs = NULL;
   4224     }
   4225     if (w->switch_cases) {
   4226       h->free(h, w->switch_cases, sizeof(CGSwitchCase) * w->switch_ncases);
   4227       w->switch_cases = NULL;
   4228       w->switch_ncases = 0;
   4229     }
   4230     if (w->raw_insns) {
   4231       h->free(h, w->raw_insns, sizeof(WasmInsn) * w->raw_ninsns);
   4232       w->raw_insns = NULL;
   4233       w->raw_ninsns = 0;
   4234     }
   4235     if (w->asm_in_kinds) {
   4236       h->free(h, w->asm_in_kinds, w->asm_nin);
   4237       h->free(h, w->asm_in_imms, sizeof(i64) * w->asm_nin);
   4238       h->free(h, w->asm_in_regs, sizeof(Reg) * w->asm_nin);
   4239       h->free(h, w->asm_in_types, sizeof(KitCgTypeId) * w->asm_nin);
   4240       h->free(h, w->asm_in_share_out, sizeof(i32) * w->asm_nin);
   4241       w->asm_in_kinds = NULL;
   4242       w->asm_in_imms = NULL;
   4243       w->asm_in_regs = NULL;
   4244       w->asm_in_types = NULL;
   4245       w->asm_in_share_out = NULL;
   4246       w->asm_nin = 0;
   4247     }
   4248     if (w->asm_out_regs) {
   4249       h->free(h, w->asm_out_regs, sizeof(Reg) * w->asm_nout);
   4250       h->free(h, w->asm_out_types, sizeof(KitCgTypeId) * w->asm_nout);
   4251       w->asm_out_regs = NULL;
   4252       w->asm_out_types = NULL;
   4253       w->asm_nout = 0;
   4254     }
   4255   }
   4256   t->nwir = 0;
   4257 }
   4258 
   4259 /* CGTarget alias hook. cg/session.c has already shared (section_id, value)
   4260  * between alias_sym and target_sym at the ObjBuilder layer, which covers
   4261  * data aliases (apply_sym_fixups reads section_id/value directly off the
   4262  * ObjSym). Function aliases need extra wiring: the wasm function payload
   4263  * lives in a target-side side-table (sym_to_func), not in obj sections,
   4264  * and the alias's external linker name needs its own WasmExport entry. */
   4265 void wasm_alias(CGTarget* tg, ObjSymId alias_sym, ObjSymId target_sym,
   4266                 KitCgTypeId type) {
   4267   WTarget* t = (WTarget*)tg;
   4268   const ObjSym* tsym;
   4269   const ObjSym* asym;
   4270   (void)type;
   4271   if (t->dead) return;
   4272   /* Aliases are processed before any function body is emitted, so the module
   4273    * may not exist yet; sym_to_wasm_func / wasm_add_export both need it. */
   4274   ensure_module(t);
   4275   tsym = obj_symbol_get(t->obj, target_sym);
   4276   if (!tsym) wfail(t, "wasm: alias against unknown target symbol");
   4277   if (tsym->kind == SK_FUNC) {
   4278     /* Mirror sym_to_func so any later WIR_CALL against the alias resolves
   4279      * to the target's wasm function index. */
   4280     u32 idx = sym_to_wasm_func(t, target_sym, NULL);
   4281     if (alias_sym >= t->sym_to_func_cap) {
   4282       Heap* h = t->c->ctx->heap;
   4283       u32 nc = t->sym_to_func_cap ? t->sym_to_func_cap : 16u;
   4284       while (nc <= alias_sym) nc *= 2u;
   4285       u32* p =
   4286           (u32*)h->realloc(h, t->sym_to_func, sizeof(u32) * t->sym_to_func_cap,
   4287                            sizeof(u32) * nc, _Alignof(u32));
   4288       if (!p) wfail(t, "wasm: out of memory");
   4289       for (u32 i = t->sym_to_func_cap; i < nc; ++i) p[i] = 0;
   4290       t->sym_to_func = p;
   4291       t->sym_to_func_cap = nc;
   4292     }
   4293     t->sym_to_func[alias_sym] = idx + 1u;
   4294     /* Export under the alias's linker name when non-local. Mirrors the
   4295      * export logic at the end of wasm_func_begin. */
   4296     asym = obj_symbol_get(t->obj, alias_sym);
   4297     if (asym && asym->bind != SB_LOCAL) {
   4298       const char* name = pool_sym_cstr(t->c->global, asym->name, NULL);
   4299       if (name && *name) {
   4300         Heap* h = t->c->ctx->heap;
   4301         size_t nlen = strlen(name);
   4302         char* exp_name = (char*)h->alloc(h, nlen + 1u, 1);
   4303         WasmExport* e;
   4304         memcpy(exp_name, name, nlen + 1u);
   4305         e = wasm_add_export(t->c, t->module);
   4306         e->name = exp_name;
   4307         e->kind = 0; /* function export */
   4308         e->index = idx;
   4309       }
   4310     }
   4311     return;
   4312   }
   4313   if (tsym->kind == SK_OBJ) {
   4314     /* Data aliases: obj_symbol_define has already shared (section_id,
   4315      * value), and apply_sym_fixups reads those directly. Nothing more
   4316      * to do here — but diagnose if the target hasn't been defined yet
   4317      * (it would produce a bogus address at finalize). */
   4318     if (tsym->section_id == OBJ_SEC_NONE) {
   4319       wfail(t, "wasm: data alias against undefined target symbol");
   4320     }
   4321     return;
   4322   }
   4323   wfail(t, "wasm target: alias of symbol kind %u not yet supported",
   4324         (unsigned)tsym->kind);
   4325 }
   4326 
   4327 /* Assign each SF_ALLOC (non-EXEC) ObjBuilder section a compact base in
   4328  * linear memory. Walks sections in id order so the layout is deterministic.
   4329  * Each base is aligned to the section's required alignment and lives in
   4330  * t->section_base[sid]. Returns the next unused offset (end of data image). */
   4331 static u32 assign_section_bases(WTarget* t) {
   4332   Heap* h = t->c->ctx->heap;
   4333   u32 nsec = obj_section_count(t->obj);
   4334   if (nsec > t->section_base_cap) {
   4335     u32 nc = t->section_base_cap ? t->section_base_cap : 4u;
   4336     while (nc < nsec) nc *= 2u;
   4337     void* p = h->realloc(h, t->section_base, sizeof(u32) * t->section_base_cap,
   4338                          sizeof(u32) * nc, _Alignof(u32));
   4339     if (!p) wfail(t, "wasm: out of memory");
   4340     t->section_base = (u32*)p;
   4341     for (u32 i = t->section_base_cap; i < nc; ++i)
   4342       t->section_base[i] = 0xFFFFFFFFu;
   4343     t->section_base_cap = nc;
   4344   }
   4345   u32 next = WASM_DATA_NULL_GUARD;
   4346   for (ObjSecId sid = 0; sid < nsec; ++sid) t->section_base[sid] = 0xFFFFFFFFu;
   4347   for (ObjSecId sid = 1; sid < nsec; ++sid) {
   4348     const Section* s = obj_section_get(t->obj, sid);
   4349     if (!s || s->removed || !(s->flags & SF_ALLOC) || s->flags & SF_EXEC)
   4350       continue;
   4351     u32 align = s->align ? s->align : 1u;
   4352     if (align < 1u) align = 1u;
   4353     next = align_to_u32(next, align);
   4354     t->section_base[sid] = next;
   4355     u32 sz = (s->kind == SEC_BSS || s->sem == SSEM_NOBITS)
   4356                  ? s->bss_size
   4357                  : (u32)s->bytes.total;
   4358     if (sz > UINT32_MAX - next) wfail(t, "wasm: linear memory image too large");
   4359     next += sz;
   4360   }
   4361   return next;
   4362 }
   4363 
   4364 /* Patch a single i32/i64 value into the linear-memory image buffer at
   4365  * `offset`. Wasm is little-endian. */
   4366 static void mem_write_le(u8* buf, u32 offset, u64 value, u32 width) {
   4367   for (u32 i = 0; i < width; ++i) buf[offset + i] = (u8)(value >> (i * 8u));
   4368 }
   4369 
   4370 /* Allocate aligned BSS-style space in linear memory for every SK_COMMON
   4371  * symbol the ObjBuilder knows about. Called after assign_section_bases so
   4372  * common storage sits past the last SF_ALLOC section. Records the assigned
   4373  * base in t->common_base[id]; returns the next free cursor. */
   4374 static u32 assign_common_bases(WTarget* t, u32 next) {
   4375   Heap* h = t->c->ctx->heap;
   4376   ObjSymIter* it = obj_symiter_new(t->obj);
   4377   ObjSymEntry e;
   4378   while (obj_symiter_next(it, &e)) {
   4379     const ObjSym* os = e.sym;
   4380     if (!os || os->removed) continue;
   4381     if (os->kind != SK_COMMON) continue;
   4382     u32 align = os->common_align ? (u32)os->common_align : 1u;
   4383     if (align < 1u) align = 1u;
   4384     if (e.id >= t->common_base_cap) {
   4385       u32 nc = t->common_base_cap ? t->common_base_cap : 8u;
   4386       while (nc <= e.id) nc *= 2u;
   4387       void* p = h->realloc(h, t->common_base, sizeof(u32) * t->common_base_cap,
   4388                            sizeof(u32) * nc, _Alignof(u32));
   4389       if (!p) wfail(t, "wasm: out of memory");
   4390       t->common_base = (u32*)p;
   4391       for (u32 i = t->common_base_cap; i < nc; ++i)
   4392         t->common_base[i] = 0xFFFFFFFFu;
   4393       t->common_base_cap = nc;
   4394     }
   4395     next = align_to_u32(next, align);
   4396     t->common_base[e.id] = next;
   4397     u32 sz = (u32)os->size;
   4398     if (sz > UINT32_MAX - next)
   4399       wfail(t, "wasm: linear memory image too large (common symbols)");
   4400     next += sz;
   4401   }
   4402   obj_symiter_free(it);
   4403   return next;
   4404 }
   4405 
   4406 /* Resolve `sym + addend` to a linear-memory address. Handles both
   4407  * section-defined symbols (via t->section_base[sym->section_id]) and
   4408  * common symbols (via t->common_base[sym]). Returns 0 and sets *ok=0 if
   4409  * the symbol can't be resolved here; callers diagnose. */
   4410 static u32 wasm_sym_linear_addr(WTarget* t, ObjSymId sym, i64 addend, int* ok) {
   4411   const ObjSym* os = obj_symbol_get(t->obj, sym);
   4412   *ok = 0;
   4413   if (!os) return 0;
   4414   if (os->kind == SK_COMMON) {
   4415     if (sym >= t->common_base_cap || t->common_base[sym] == 0xFFFFFFFFu)
   4416       return 0;
   4417     *ok = 1;
   4418     return t->common_base[sym] + (u32)addend;
   4419   }
   4420   if (os->section_id == OBJ_SEC_NONE) return 0;
   4421   if (os->section_id >= t->section_base_cap ||
   4422       t->section_base[os->section_id] == 0xFFFFFFFFu)
   4423     return 0;
   4424   *ok = 1;
   4425   return t->section_base[os->section_id] + (u32)os->value + (u32)addend;
   4426 }
   4427 
   4428 /* Apply each ObjBuilder relocation to the linear-memory image. Only
   4429  * absolute (R_ABS32/R_ABS64) relocations are supported for now; PC-relative
   4430  * and other kinds diagnose. */
   4431 static void apply_data_relocs(WTarget* t, u8* mem) {
   4432   u32 ntotal = obj_reloc_total(t->obj);
   4433   for (u32 i = 0; i < ntotal; ++i) {
   4434     const Reloc* r = obj_reloc_at(t->obj, i);
   4435     if (!r || r->removed) continue;
   4436     if (r->section_id == OBJ_SEC_NONE) continue;
   4437     if (r->section_id >= t->section_base_cap ||
   4438         t->section_base[r->section_id] == 0xFFFFFFFFu)
   4439       continue;
   4440     const Section* rs = obj_section_get(t->obj, r->section_id);
   4441     if (!rs || rs->flags & SF_EXEC) continue;
   4442     const ObjSym* tos = obj_symbol_get(t->obj, r->sym);
   4443     if (!tos)
   4444       wfail(t, "wasm: data relocation against unresolved symbol not supported");
   4445     /* Function-symbol references in data sections (e.g. a static vtable
   4446      * `static fn_t v = &foo;`) resolve to wasm function-table indices, not
   4447      * linear-memory addresses. The funcref table is built before
   4448      * apply_data_relocs runs, so the index is already known. */
   4449     u32 width;
   4450     u64 value;
   4451     if (tos->kind == SK_FUNC) {
   4452       if (r->kind != R_ABS32)
   4453         wfail(t,
   4454               "wasm: function-pointer data relocation kind %u not supported "
   4455               "(only R_ABS32 on wasm32 target)",
   4456               (unsigned)r->kind);
   4457       if (r->addend != 0)
   4458         wfail(t, "wasm: nonzero addend on function-pointer data relocation");
   4459       u32 tbl_idx = func_table_index_for(t, r->sym);
   4460       width = 4;
   4461       value = (u64)tbl_idx;
   4462       u32 dst_off = t->section_base[r->section_id] + r->offset;
   4463       mem_write_le(mem, dst_off, value, width);
   4464       continue;
   4465     }
   4466     if (tos->section_id == OBJ_SEC_NONE && tos->kind != SK_COMMON)
   4467       wfail(t, "wasm: data relocation against unresolved symbol not supported");
   4468     {
   4469       int ok = 0;
   4470       /* The addend is already added by `value = sym_addr + r->addend` below;
   4471        * pass 0 here so we don't double-count. */
   4472       u32 sym_addr = wasm_sym_linear_addr(t, r->sym, 0, &ok);
   4473       if (!ok)
   4474         wfail(t,
   4475               "wasm: data relocation target symbol has no linear-memory "
   4476               "address");
   4477       switch (r->kind) {
   4478         case R_ABS32:
   4479           width = 4;
   4480           value = (u64)(u32)((i64)sym_addr + r->addend);
   4481           break;
   4482         case R_ABS64:
   4483           wfail(t,
   4484                 "wasm: R_ABS64 data relocation not supported on wasm32 target");
   4485         default:
   4486           wfail(t, "wasm: unsupported data relocation kind %u",
   4487                 (unsigned)r->kind);
   4488       }
   4489     }
   4490     u32 dst_off = t->section_base[r->section_id] + r->offset;
   4491     mem_write_le(mem, dst_off, value, width);
   4492   }
   4493 }
   4494 
   4495 /* Walk the deferred WSymFixup queue and patch the placeholder i32.const
   4496  * imm in each WasmFunc.insns[] with the resolved absolute address. */
   4497 static void apply_sym_fixups(WTarget* t) {
   4498   for (u32 i = 0; i < t->sym_fixups_count; ++i) {
   4499     WSymFixup fx = t->sym_fixups[i];
   4500     int ok = 0;
   4501     u32 addr = wasm_sym_linear_addr(t, fx.sym, fx.addend, &ok);
   4502     if (!ok) wfail(t, "wasm: deferred symbol fixup against unresolved symbol");
   4503     WasmFunc* f = &t->module->funcs[fx.wasm_func_idx];
   4504     if (fx.insn_idx >= f->ninsns)
   4505       wfail(t, "wasm: deferred symbol fixup insn_idx out of range");
   4506     f->insns[fx.insn_idx].imm = (i64)addr;
   4507   }
   4508 }
   4509 
   4510 static void wasm_materialize_data(WTarget* t) {
   4511   if (!t->has_memory) {
   4512     /* No linear memory was needed by any function body or addr_of, so
   4513      * symbol fixups should be empty by construction. */
   4514     return;
   4515   }
   4516   u32 image_end = assign_section_bases(t);
   4517   image_end = assign_common_bases(t, image_end);
   4518   u32 stack_size = t->has_stack_pointer ? t->stack_size : 0u;
   4519   u32 image = image_end ? align_to_u32(image_end, 16u) : WASM_DATA_NULL_GUARD;
   4520   if (image > UINT32_MAX - stack_size)
   4521     wfail(t, "wasm: linear memory image too large");
   4522   /* Build a single active data segment covering 0..image. Passive segments
   4523    * + memory.init would be needed for multi-TU linking; single-TU output
   4524    * stays with the simpler shape. */
   4525   WasmDataSegment* seg = NULL;
   4526   if (image) {
   4527     seg = wasm_add_data(t->c, t->module);
   4528     seg->mode = WASM_SEG_ACTIVE;
   4529     seg->memidx = 0;
   4530     seg->offset = 0;
   4531     wasm_data_set_bytes(t->c, t->module, seg, NULL, (u64)image);
   4532   }
   4533   u32 nsec = obj_section_count(t->obj);
   4534   for (ObjSecId sid = 1; sid < nsec; ++sid) {
   4535     const Section* s = obj_section_get(t->obj, sid);
   4536     if (!s || s->removed || !(s->flags & SF_ALLOC) || s->flags & SF_EXEC)
   4537       continue;
   4538     if (s->kind == SEC_BSS || s->sem == SSEM_NOBITS || !s->bytes.total)
   4539       continue;
   4540     buf_flatten(&s->bytes, seg->bytes + t->section_base[sid]);
   4541   }
   4542   if (seg) apply_data_relocs(t, seg->bytes);
   4543   apply_sym_fixups(t);
   4544   t->data_end = image;
   4545   u32 stack_top = (u32)align_to_u32(image + stack_size, 16u);
   4546   t->module->memories[0].min_pages = (stack_top + 65535u) / 65536u;
   4547   /* Shared memory requires has_max and max >= min. ensure_shared_memory set a
   4548    * provisional wasm32-ceiling cap (65536 pages = 4 GiB); now that the final
   4549    * layout is known, tighten max down to min so the module declares a snug,
   4550    * fixed shared memory. The backend never emits memory.grow, so the memory is
   4551    * non-growable regardless, and a 4 GiB declared max would otherwise force an
   4552    * embedder (e.g. `kit run`) to reserve the full ceiling up front. */
   4553   if (t->module->memories[0].shared) {
   4554     t->module->memories[0].has_max = 1;
   4555     t->module->memories[0].max_pages = t->module->memories[0].min_pages;
   4556   }
   4557   if (t->has_stack_pointer && t->stack_pointer_global < t->module->nglobals) {
   4558     t->module->globals[t->stack_pointer_global].init.imm = stack_top;
   4559   }
   4560 }
   4561 
   4562 /* Static-data initializers (e.g. `static fn_t v[] = {&foo, &bar};`) go
   4563  * through ObjBuilder relocations rather than wasm_addr_of, so they never
   4564  * touch queue_func_table_fixup. Scan the reloc table once before building
   4565  * the funcref table so every function whose address is referenced from data
   4566  * also gets a table slot. apply_data_relocs then patches the linear-memory
   4567  * image with the assigned index. */
   4568 static void wasm_collect_func_data_refs(WTarget* t) {
   4569   u32 ntotal = obj_reloc_total(t->obj);
   4570   for (u32 i = 0; i < ntotal; ++i) {
   4571     const Reloc* r = obj_reloc_at(t->obj, i);
   4572     const ObjSym* tos;
   4573     if (!r || r->removed) continue;
   4574     if (r->section_id == OBJ_SEC_NONE) continue;
   4575     {
   4576       const Section* rs = obj_section_get(t->obj, r->section_id);
   4577       if (!rs || rs->flags & SF_EXEC) continue; /* code-section relocs */
   4578     }
   4579     tos = obj_symbol_get(t->obj, r->sym);
   4580     if (!tos || tos->kind != SK_FUNC) continue;
   4581     (void)func_table_index_for(t, r->sym);
   4582     (void)sym_to_wasm_func(t, r->sym, NULL);
   4583   }
   4584 }
   4585 
   4586 /* Build the single funcref table and its active element segment, then patch
   4587  * every queued WFuncTableFixup's placeholder `i32.const 0` with the assigned
   4588  * table index. Slot 0 stays reserved (call_indirect through index 0 traps on
   4589  * the type check), so the first recorded function lands at index 1. Each
   4590  * WasmElemSegment caps its funcs array at 64 entries; we chunk across
   4591  * multiple segments when the address-taken set is larger. */
   4592 static void wasm_materialize_functable(WTarget* t) {
   4593   wasm_collect_func_data_refs(t);
   4594   if (!t->has_func_table || t->func_table_count == 0) return;
   4595   ensure_module(t);
   4596   /* Table: non-growable, sized to hold the reserved null slot plus every
   4597    * assigned entry. */
   4598   WasmTable* tbl = wasm_add_table(t->c, t->module);
   4599   tbl->elem_type = WASM_VAL_FUNCREF;
   4600   tbl->min = 1u + t->func_table_count;
   4601   tbl->max = tbl->min;
   4602   tbl->has_max = 1;
   4603   /* Active element segment populates table 0 starting at offset 1 (slot 0
   4604    * stays null). Element segments are now heap-grown — no chunking needed. */
   4605   {
   4606     WasmElemSegment* seg = wasm_add_elem(t->c, t->module);
   4607     seg->mode = WASM_SEG_ACTIVE;
   4608     seg->elem_type = WASM_VAL_FUNCREF;
   4609     seg->tableidx = 0;
   4610     seg->offset = 1;
   4611     for (u32 i = 0; i < t->func_table_count; ++i) {
   4612       ObjSymId sym = t->func_table[i];
   4613       wasm_elem_push_func(t->c, t->module, seg, sym_to_wasm_func(t, sym, NULL));
   4614     }
   4615   }
   4616   /* Patch placeholders. */
   4617   for (u32 i = 0; i < t->func_table_fixups_count; ++i) {
   4618     WFuncTableFixup fx = t->func_table_fixups[i];
   4619     u32 tbl_idx = func_table_index_for(t, fx.sym);
   4620     WasmFunc* f = &t->module->funcs[fx.wasm_func_idx];
   4621     if (fx.insn_idx >= f->ninsns)
   4622       wfail(t, "wasm: function-pointer fixup insn_idx out of range");
   4623     f->insns[fx.insn_idx].imm = (i64)tbl_idx;
   4624   }
   4625 }
   4626 
   4627 /* Wasm requires every import to occupy a lower function index than any
   4628  * defined function. The backend, however, allocates a WasmFunc for any
   4629  * direct-call target in walk order — so a defined function may end up at a
   4630  * lower array index than an import created later by promote_import_func.
   4631  * Reorder m->funcs so all imports precede all definitions, then walk every
   4632  * function-index reference in the module and apply the old->new mapping. */
   4633 static void wasm_reorder_funcs_imports_first(WTarget* t) {
   4634   WasmModule* m = t->module;
   4635   if (!m || m->nfuncs == 0) return;
   4636   Heap* h = m->heap;
   4637   u32 n = m->nfuncs;
   4638   /* Quick check: bail out if imports are already before all definitions. */
   4639   int seen_def = 0;
   4640   int needs_reorder = 0;
   4641   for (u32 i = 0; i < n; ++i) {
   4642     if (m->funcs[i].is_import) {
   4643       if (seen_def) {
   4644         needs_reorder = 1;
   4645         break;
   4646       }
   4647     } else {
   4648       seen_def = 1;
   4649     }
   4650   }
   4651   if (!needs_reorder) return;
   4652   u32* old_to_new = (u32*)h->alloc(h, sizeof(u32) * n, _Alignof(u32));
   4653   WasmFunc* new_funcs =
   4654       (WasmFunc*)h->alloc(h, sizeof(WasmFunc) * n, _Alignof(WasmFunc));
   4655   if (!old_to_new || !new_funcs) wfail(t, "wasm: out of memory");
   4656   u32 w_idx = 0;
   4657   for (u32 i = 0; i < n; ++i) {
   4658     if (m->funcs[i].is_import) {
   4659       new_funcs[w_idx] = m->funcs[i];
   4660       old_to_new[i] = w_idx++;
   4661     }
   4662   }
   4663   for (u32 i = 0; i < n; ++i) {
   4664     if (!m->funcs[i].is_import) {
   4665       new_funcs[w_idx] = m->funcs[i];
   4666       old_to_new[i] = w_idx++;
   4667     }
   4668   }
   4669   /* Swap arrays. Old buffer is freed via the module's heap-tracked
   4670    * realloc bookkeeping when wasm_module_free runs; we just overwrite the
   4671    * pointer + length here. */
   4672   h->free(h, m->funcs, sizeof(WasmFunc) * m->cap_funcs);
   4673   m->funcs = new_funcs;
   4674   m->cap_funcs = n;
   4675   /* Remap every funcidx-bearing slot in the module. */
   4676   for (u32 fi = 0; fi < n; ++fi) {
   4677     WasmFunc* f = &m->funcs[fi];
   4678     for (u32 j = 0; j < f->ninsns; ++j) {
   4679       WasmInsn* in = &f->insns[j];
   4680       if (in->kind == WASM_INSN_CALL || in->kind == WASM_INSN_RETURN_CALL ||
   4681           in->kind == WASM_INSN_REF_FUNC) {
   4682         u32 old = (u32)in->imm;
   4683         if (old < n) in->imm = (int64_t)old_to_new[old];
   4684       }
   4685     }
   4686   }
   4687   for (u32 i = 0; i < m->nexports; ++i) {
   4688     if (m->exports[i].kind == 0u && m->exports[i].index < n)
   4689       m->exports[i].index = old_to_new[m->exports[i].index];
   4690   }
   4691   for (u32 i = 0; i < m->nelems; ++i) {
   4692     WasmElemSegment* seg = &m->elems[i];
   4693     for (u32 j = 0; j < seg->nfuncs; ++j) {
   4694       if (seg->funcs[j] < n) seg->funcs[j] = old_to_new[seg->funcs[j]];
   4695     }
   4696   }
   4697   if (m->has_start && m->start_func < n)
   4698     m->start_func = old_to_new[m->start_func];
   4699   /* Update the backend's sym_to_func reverse map so any post-finalize lookups
   4700    * (e.g. data-reloc fixups) resolve to the new indices. The map stores
   4701    * idx+1 so 0 = "unassigned"; preserve that convention. */
   4702   for (ObjSymId sym = 0; sym < t->sym_to_func_cap; ++sym) {
   4703     if (t->sym_to_func[sym]) {
   4704       u32 old = t->sym_to_func[sym] - 1u;
   4705       if (old < n) t->sym_to_func[sym] = old_to_new[old] + 1u;
   4706     }
   4707   }
   4708   h->free(h, old_to_new, sizeof(u32) * n);
   4709 }
   4710 
   4711 /* Export the module's linear memory under the conventional name "memory" so
   4712  * standard runtimes (browser/wasmtime/wasmer/Node) can find it. Only emits
   4713  * when the module has at least one defined (non-import) memory and no
   4714  * memory export already exists. */
   4715 static void wasm_export_memory(WTarget* t) {
   4716   WasmModule* m = t->module;
   4717   if (!m) return;
   4718   ensure_linear_memory(t);
   4719   m = t->module;
   4720   /* Find the first defined (non-import) memory. */
   4721   u32 mem_idx = 0;
   4722   int found = 0;
   4723   for (u32 i = 0; i < m->nmemories; ++i) {
   4724     if (!m->memories[i].is_import) {
   4725       mem_idx = i;
   4726       found = 1;
   4727       break;
   4728     }
   4729   }
   4730   if (!found) return;
   4731   /* Skip if the user already added a memory export (e.g. via the WAT path
   4732    * or future explicit-export hook). */
   4733   for (u32 i = 0; i < m->nexports; ++i) {
   4734     if (m->exports[i].kind == 2u && m->exports[i].index == mem_idx) return;
   4735   }
   4736   Heap* h = t->c->ctx->heap;
   4737   WasmExport* e = wasm_add_export(t->c, m);
   4738   static const char kName[] = "memory";
   4739   char* dup = (char*)h->alloc(h, sizeof(kName), 1);
   4740   if (!dup) wfail(t, "wasm: out of memory");
   4741   memcpy(dup, kName, sizeof(kName));
   4742   e->name = dup;
   4743   e->kind = 2u; /* memory export */
   4744   e->index = mem_idx;
   4745 }
   4746 
   4747 /* Diagnose any WasmFunc that has neither a body nor import status — that's a
   4748  * declaration with no definition, e.g. a function-pointer reference to an
   4749  * extern whose call site never appeared (so we never saw an ABI to synthesize
   4750  * an import signature from). Emitting such a function would produce a
   4751  * malformed module. Diagnose by sym name so users can fix the source. */
   4752 static void wasm_diagnose_unresolved_funcs(WTarget* t) {
   4753   if (!t->module) return;
   4754   for (ObjSymId sym = 1; sym < t->sym_to_func_cap; ++sym) {
   4755     if (!t->sym_to_func[sym]) continue;
   4756     u32 idx = t->sym_to_func[sym] - 1u;
   4757     if (idx >= t->module->nfuncs) continue;
   4758     WasmFunc* f = &t->module->funcs[idx];
   4759     if (f->is_import) continue;
   4760     if (f->ninsns != 0) continue;
   4761     const ObjSym* os = obj_symbol_get(t->obj, sym);
   4762     if (!os || os->section_id != OBJ_SEC_NONE) continue;
   4763     const char* name = pool_sym_cstr(t->c->global, os->name, NULL);
   4764     wfail(t,
   4765           "wasm: undefined function '%s' has its address taken but no direct "
   4766           "call was seen — cannot synthesize import signature; add a direct "
   4767           "call or annotate the declaration",
   4768           name ? name : "(anonymous)");
   4769   }
   4770 }
   4771 
   4772 void wasm_finalize(CGTarget* tg) {
   4773   WTarget* t = (WTarget*)tg;
   4774   wasm_materialize_functable(t);
   4775   wasm_materialize_data(t);
   4776   if (t->module) {
   4777     wasm_diagnose_unresolved_funcs(t);
   4778     wasm_export_memory(t);
   4779     wasm_reorder_funcs_imports_first(t);
   4780   }
   4781   /* WasmModule remains attached to ObjBuilder via OBJ_EXT_WASM; emit_wasm
   4782    * flushes it. */
   4783 }
   4784 
   4785 static void wasm_module_freefn(Compiler* c, void* p) {
   4786   (void)c;
   4787   WasmModule* m = (WasmModule*)p;
   4788   Heap* h = m->heap;
   4789   wasm_module_free(m);
   4790   h->free(h, m, sizeof *m);
   4791 }
   4792 
   4793 WTarget* wasm_emit_target_new(Compiler* c, ObjBuilder* o, MCEmitter* mc) {
   4794   Heap* h;
   4795   WTarget* t;
   4796   if (!c) return NULL;
   4797   h = (Heap*)c->ctx->heap;
   4798   t = (WTarget*)h->alloc(h, sizeof *t, _Alignof(WTarget));
   4799   if (!t) return NULL;
   4800   memset(t, 0, sizeof *t);
   4801   t->base.c = c;
   4802   t->base.obj = o;
   4803   t->c = c;
   4804   t->obj = o;
   4805   (void)mc;
   4806   return t;
   4807 }
   4808 
   4809 void wasm_destroy(CGTarget* tg) {
   4810   WTarget* t = (WTarget*)tg;
   4811   Heap* h = t->c->ctx->heap;
   4812   if (t->reg_to_local) h->free(h, t->reg_to_local, sizeof(u32) * t->reg_cap);
   4813   if (t->reg_type) h->free(h, t->reg_type, sizeof(KitCgTypeId) * t->reg_cap);
   4814   if (t->reg_cls) h->free(h, t->reg_cls, t->reg_cap);
   4815   if (t->wir) h->free(h, t->wir, sizeof(WIR) * t->wir_cap);
   4816   if (t->labels) h->free(h, t->labels, sizeof(WLabel) * t->labels_cap);
   4817   if (t->slots) h->free(h, t->slots, sizeof(WSlot) * t->slots_cap);
   4818   if (t->param_local_idx)
   4819     h->free(h, t->param_local_idx, sizeof(u32) * t->param_local_idx_cap);
   4820   if (t->byval_copies)
   4821     h->free(h, t->byval_copies, sizeof(WByvalCopy) * t->byval_copies_cap);
   4822   if (t->sym_to_func)
   4823     h->free(h, t->sym_to_func, sizeof(u32) * t->sym_to_func_cap);
   4824   if (t->funcs) h->free(h, t->funcs, sizeof(WFunc) * t->funcs_cap);
   4825   if (t->section_base)
   4826     h->free(h, t->section_base, sizeof(u32) * t->section_base_cap);
   4827   if (t->common_base)
   4828     h->free(h, t->common_base, sizeof(u32) * t->common_base_cap);
   4829   if (t->sym_fixups)
   4830     h->free(h, t->sym_fixups, sizeof(WSymFixup) * t->sym_fixups_cap);
   4831   if (t->func_table)
   4832     h->free(h, t->func_table, sizeof(ObjSymId) * t->func_table_cap);
   4833   if (t->func_table_fixups)
   4834     h->free(h, t->func_table_fixups,
   4835             sizeof(WFuncTableFixup) * t->func_table_fixups_cap);
   4836   h->free(h, t, sizeof *t);
   4837 }
   4838 
   4839 /* -----------------------------------------------------------------
   4840  * Module bootstrap: attach a WasmModule to the ObjBuilder so emit_wasm
   4841  * can find it. Lazily on first func_begin.
   4842  * ----------------------------------------------------------------- */
   4843 
   4844 static struct WasmModule* ensure_module(WTarget* t) {
   4845   if (t->module) return t->module;
   4846   Heap* h = t->c->ctx->heap;
   4847   WasmModule* m = (WasmModule*)h->alloc(h, sizeof *m, _Alignof(WasmModule));
   4848   if (!m) wfail(t, "wasm: out of memory");
   4849   wasm_module_init(m, h);
   4850   /* kit-produced modules always declare bulk-memory support: WIR_COPY_BYTES
   4851    * / WIR_SET_BYTES lower to memory.copy / memory.fill unconditionally, and
   4852    * the sret-return path emits memory.copy too. */
   4853   m->features |= WASM_FEATURE_BULK_MEMORY;
   4854   t->module = m;
   4855   obj_ext_set(t->obj, OBJ_EXT_WASM, m, wasm_module_freefn);
   4856   return m;
   4857 }