kit

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

control.c (56488B)


      1 #include "cg/internal.h"
      2 #include "core/metrics.h"
      3 
      4 KitCgLabel kit_cg_label_new(KitCg* g) {
      5   if (!g) return KIT_CG_LABEL_NONE;
      6   if (api_unevaluated(g) && g->fn_ret_type == KIT_CG_TYPE_NONE)
      7     return KIT_CG_LABEL_NONE;
      8   return (KitCgLabel)g->target->label_new(g->target);
      9 }
     10 
     11 void kit_cg_label_place(KitCg* g, KitCgLabel label) {
     12   if (!g) return;
     13   if (api_unevaluated(g)) return;
     14   api_local_const_control_boundary(g);
     15   g->target->label_place(g->target, (Label)label);
     16 }
     17 
     18 void kit_cg_jump(KitCg* g, KitCgLabel label) {
     19   if (!g) return;
     20   if (api_unevaluated(g)) return;
     21   api_local_const_control_boundary(g);
     22   g->target->jump(g->target, (Label)label);
     23 }
     24 
     25 void api_branch_if(KitCg* g, ApiSValue* v, int branch_when_true, Label label) {
     26   CgTarget* T;
     27   KitCgTypeId ty;
     28   if (!g) return;
     29   if (api_unevaluated(g)) {
     30     api_release(g, v);
     31     return;
     32   }
     33   api_local_const_control_boundary(g);
     34   T = g->target;
     35   ty = v->type ? v->type : builtin_id(KIT_CG_BUILTIN_I32);
     36   if (v->op.kind == OPK_IMM && api_sv_kind(v) == SV_OPERAND) {
     37     if ((v->op.v.imm != 0) == !!branch_when_true) T->jump(T, label);
     38     api_release(g, v);
     39     return;
     40   }
     41   if (api_sv_kind(v) == SV_CMP) {
     42     CmpOp op = branch_when_true ? v->delayed->cmp.op
     43                                 : api_invert_cmp(v->delayed->cmp.op);
     44     /* Flag dead-transient operands so the -O0 backend drops them at the branch
     45      * instead of spilling them (eager dead-operand drop). The compare has no
     46      * local destination, so pass an immediate as the dst sentinel. */
     47     Operand none = api_op_imm(0, builtin_id(KIT_CG_BUILTIN_I32));
     48     Operand ca = api_op_kill_if_dead(g, v->delayed->cmp.a, none);
     49     Operand cb = api_op_kill_if_dead(g, v->delayed->cmp.b, none);
     50     T->cmp_branch(T, op, ca, cb, label);
     51     api_release(g, v);
     52     return;
     53   }
     54   if (api_is_i128_type(g->c, ty)) {
     55     KitCgTypeId i128 = builtin_id(KIT_CG_BUILTIN_I128);
     56     KitCgTypeId i32 = builtin_id(KIT_CG_BUILTIN_I32);
     57     KitCgTypeId ps[2] = {i128, i128};
     58     ApiSValue args[2];
     59     ApiSValue cmp;
     60     args[0] = *v;
     61     args[1] = api_make_sv(api_op_imm(0, i128), i128);
     62     api_runtime_call_values(g, "__kit_ucmpti2", i32, ps, 2, args);
     63     cmp = api_pop(g);
     64     api_branch_if(g, &cmp, branch_when_true, label);
     65     return;
     66   }
     67   /* Split-lane 8-byte truthiness: branch on (lo | hi) != 0. The value is
     68    * memory-resident, so a single-slot CMP_NE-vs-zero would only see the low
     69    * word; OR the two lanes into an i32 first. */
     70   if (api_is_wide8_scalar_type(g->c, ty)) {
     71     KitCgTypeId i32 = builtin_id(KIT_CG_BUILTIN_I32);
     72     Operand orl = api_wide8_or_lanes(g, v, ty);
     73     Operand zero = api_op_imm(0, i32);
     74     T->cmp_branch(T, branch_when_true ? CMP_NE : CMP_EQ, orl, zero, label);
     75     api_release(g, v);
     76     return;
     77   }
     78   {
     79     Operand a = api_force_local(g, v, ty);
     80     Operand zero = api_op_imm(0, ty);
     81     /* The truthiness test is `v`'s last use; drop it at the branch if it is a
     82      * dead transient (zero is the non-local dst sentinel). */
     83     a = api_op_kill_if_dead(g, a, zero);
     84     T->cmp_branch(T, branch_when_true ? CMP_NE : CMP_EQ, a, zero, label);
     85     api_release(g, v);
     86   }
     87 }
     88 
     89 void kit_cg_branch_true(KitCg* g, KitCgLabel label) {
     90   ApiSValue v;
     91   if (!g) return;
     92   v = api_pop(g);
     93   api_branch_if(g, &v, 1, (Label)label);
     94 }
     95 
     96 void kit_cg_branch_false(KitCg* g, KitCgLabel label) {
     97   ApiSValue v;
     98   if (!g) return;
     99   v = api_pop(g);
    100   api_branch_if(g, &v, 0, (Label)label);
    101 }
    102 
    103 void cg_lower_switch_default(CgTarget* t, const CGSwitchDesc* d) {
    104   /* Cmp-and-branch chain: one cmp_branch per case, then jump to
    105    * default (or fall through if LABEL_NONE). The fallback shape; the
    106    * frontend-facing kit_cg_switch picks chain vs. jump-table up
    107    * front (see cg_plan_switch) and routes here only for the chain
    108    * case. Backend overrides (the C target's switch_) and opt's IR
    109    * replay both reach this from outside the cg API, so the lowering
    110    * stays target-only and uses just cmp_branch + jump. */
    111   for (u32 i = 0; i < d->ncases; ++i) {
    112     Operand imm = api_op_imm((i64)d->cases[i].value, d->selector_type);
    113     t->cmp_branch(t, CMP_EQ, d->selector, imm, d->cases[i].label);
    114   }
    115   if (d->default_label != LABEL_NONE) {
    116     t->jump(t, d->default_label);
    117   }
    118 }
    119 
    120 /* Density / sizing thresholds for the O1 jump-table heuristic. At O0
    121  * we only honor an explicit KIT_CG_SWITCH_JUMP_TABLE hint — the policy
    122  * is single-pass and tied to frontend intent rather than analysis. O1
    123  * runs a linear scan over the case list and picks a single global table
    124  * when the cases are dense enough; clustering / multi-table layouts are
    125  * out of scope here. */
    126 #define CG_SWITCH_TABLE_MIN_CASES_O1 4u
    127 #define CG_SWITCH_TABLE_MAX_SPAN_O1 4096u
    128 #define CG_SWITCH_TABLE_DENSITY_RECIP_O1 4u /* ncases * recip >= span */
    129 
    130 typedef enum CGSwitchPlanKind {
    131   CG_SWITCH_PLAN_CHAIN,
    132   CG_SWITCH_PLAN_TABLE,
    133 } CGSwitchPlanKind;
    134 
    135 typedef struct CGSwitchPlan {
    136   CGSwitchPlanKind kind;
    137   i64 vmin;
    138   u64 span;
    139 } CGSwitchPlan;
    140 
    141 /* Single pass over the cases array, deriving (vmin, span) for the tightest
    142  * dispatch window. Dispatch is `idx = (sel - vmin) mod 2^width` with an
    143  * unsigned bounds check, so the cases live on a circle of size 2^width and
    144  * the minimal enclosing window is the complement of the largest gap between
    145  * them. We don't sort to find that gap; instead we evaluate the two windows
    146  * whose seam sits at a fixed point and take the smaller:
    147  *
    148  *   - the *signed* window [smin, smax], seam at the signed midpoint — tight
    149  *     for values near 0 or near 2^width (e.g. a (T)-1 case folded next to
    150  *     small positives);
    151  *   - the *unsigned* window [umin, umax], seam at 0 / 2^width — tight for
    152  *     values straddling the signed midpoint.
    153  *
    154  * A table-eligible cluster (span <= a few thousand) is far smaller than
    155  * 2^(width-1), so it can cross at most one of those two seams; the smaller
    156  * window is therefore the true minimum. Selector signedness isn't even
    157  * visible here (CG integer builtins carry only a width), and both windows
    158  * dispatch correctly under the modular index, so taking the min is a pure
    159  * code-size win. Returns 0 if the selector type is unusable (>64 bits) or
    160  * both windows degenerate to the full range. */
    161 static int cg_switch_extents(Compiler* c, const CGSwitchDesc* d, i64* out_vmin,
    162                              u64* out_span) {
    163   u32 width;
    164   u64 mask;
    165   i64 smin;
    166   i64 smax;
    167   u64 umin;
    168   u64 umax;
    169   u32 i;
    170   u64 sspan;
    171   u64 uspan;
    172   width = kit_cg_type_int_width((KitCompiler*)c, d->selector_type);
    173   if (!width || width > 64u) return 0;
    174   if (d->ncases == 0) return 0;
    175   mask = (width >= 64u) ? UINT64_MAX : (((u64)1u << width) - 1u);
    176   smin = INT64_MAX;
    177   smax = INT64_MIN;
    178   umin = UINT64_MAX;
    179   umax = 0;
    180   for (i = 0; i < d->ncases; ++i) {
    181     u64 raw = d->cases[i].value & mask;
    182     i64 sv = (width == 64u) ? (i64)d->cases[i].value
    183                             : api_sign_extend_width(d->cases[i].value, width);
    184     if (sv < smin) smin = sv;
    185     if (sv > smax) smax = sv;
    186     if (raw < umin) umin = raw;
    187     if (raw > umax) umax = raw;
    188   }
    189   /* span = delta + 1; a delta of UINT64_MAX is the degenerate full-range
    190    * case the table can't represent. */
    191   {
    192     u64 sdelta = (u64)smax - (u64)smin;
    193     sspan = sdelta == UINT64_MAX ? 0u : sdelta + 1u;
    194   }
    195   {
    196     u64 udelta = umax - umin;
    197     uspan = udelta == UINT64_MAX ? 0u : udelta + 1u;
    198   }
    199   if (sspan == 0u && uspan == 0u) return 0;
    200   if (uspan != 0u && (sspan == 0u || uspan < sspan)) {
    201     *out_span = uspan;
    202     *out_vmin = (i64)umin; /* a bit pattern; emission indexes modularly */
    203   } else {
    204     *out_span = sspan;
    205     *out_vmin = smin;
    206   }
    207   return 1;
    208 }
    209 
    210 static CGSwitchPlan cg_plan_switch(KitCg* g, const CGSwitchDesc* d) {
    211   CGSwitchPlan plan;
    212   plan.kind = CG_SWITCH_PLAN_CHAIN;
    213   plan.vmin = 0;
    214   plan.span = 0;
    215   if (d->ncases == 0) return plan;
    216   if (d->default_label == LABEL_NONE) return plan;
    217   if (d->hint == KIT_CG_SWITCH_BRANCH_CHAIN) return plan;
    218   if (!cg_switch_extents(g->c, d, &plan.vmin, &plan.span)) return plan;
    219   if (d->hint == KIT_CG_SWITCH_JUMP_TABLE) {
    220     /* Frontend explicitly opted in. Honor unless the span is wildly
    221      * out of bounds — a forced hint shouldn't blow up code size on a
    222      * misshapen switch. */
    223     if (plan.span > CG_SWITCH_TABLE_MAX_SPAN_O1) return plan;
    224     plan.kind = CG_SWITCH_PLAN_TABLE;
    225     return plan;
    226   }
    227   /* TARGET_DEFAULT: O0 keeps the chain; O1+ runs the density check. */
    228   if (d->opt_level == 0) return plan;
    229   if (d->ncases < CG_SWITCH_TABLE_MIN_CASES_O1) return plan;
    230   if (plan.span > CG_SWITCH_TABLE_MAX_SPAN_O1) return plan;
    231   if (plan.span > (u64)d->ncases * CG_SWITCH_TABLE_DENSITY_RECIP_O1)
    232     return plan;
    233   plan.kind = CG_SWITCH_PLAN_TABLE;
    234   return plan;
    235 }
    236 
    237 /* Emit a dense jump-table dispatch using cg-API ops. The selector value
    238  * is still on the value stack on entry. Routing through the cg API
    239  * means the same primitives work for direct CG (lowered to machine
    240  * ops) and for the opt wrapper (recorded as IR_BINOP / IR_CMP_BRANCH /
    241  * IR_LOAD / IR_INDIRECT_BRANCH, which pass_emit already lowers
    242  * natively without ever materializing IR_SWITCH). */
    243 static void cg_emit_switch_table(KitCg* g, const CGSwitchDesc* d,
    244                                  const CGSwitchPlan* plan) {
    245   Compiler* c;
    246   Heap* h;
    247   KitCgTypeId sel_ty;
    248   u32 sel_w;
    249   KitCgTypeId i64_ty;
    250   KitCgTypeId void_ptr_ty;
    251   KitCgTypeId arr_ty;
    252   Label* labels;
    253   KitCgLabel* targets;
    254   ObjSymId table_sym;
    255   KitCgDecl decl;
    256   KitCgMemAccess acc;
    257   u64 i;
    258   u32 width;
    259   c = g->c;
    260   h = (Heap*)c->ctx->heap;
    261   sel_ty = d->selector_type;
    262   sel_w = kit_cg_type_int_width((KitCompiler*)c, sel_ty);
    263   i64_ty = builtin_id(KIT_CG_BUILTIN_I64);
    264   void_ptr_ty = cg_type_ptr_to(c, builtin_id(KIT_CG_BUILTIN_VOID));
    265 
    266   /* 1. Keep the original selector, then compute idx = sel - vmin
    267    *    (selector_type wraparound is what we want; the unsigned bounds
    268    *    check below catches both negative-underflow and positive-overflow
    269    *    cases). The selector copy lets us recompute idx after the bounds
    270    *    branch instead of carrying a duplicated arithmetic value across
    271    *    control flow. */
    272   kit_cg_dup(g); /* [sel, sel] */
    273   kit_cg_push_int(g, (uint64_t)plan->vmin, sel_ty);
    274   kit_cg_int_binop(g, KIT_CG_INT_SUB, 0); /* [sel, idx] */
    275 
    276   /* 2. Bounds check: branch to default when idx u> span-1. */
    277   kit_cg_dup(g); /* [sel, idx, idx] */
    278   kit_cg_push_int(g, plan->span - 1u, sel_ty);
    279   kit_cg_int_cmp(g, KIT_CG_INT_GT_U);                  /* [sel, idx, cond] */
    280   kit_cg_branch_true(g, (KitCgLabel)d->default_label); /* [sel, idx] */
    281 
    282   /* 3. Recompute idx from the preserved selector for table addressing. */
    283   kit_cg_drop(g); /* [sel] */
    284   kit_cg_push_int(g, (uint64_t)plan->vmin, sel_ty);
    285   kit_cg_int_binop(g, KIT_CG_INT_SUB, 0); /* [idx] */
    286 
    287   /* 4. Widen idx to i64 so the subsequent index multiply runs at
    288    *    pointer width regardless of selector signedness. The bounds
    289    *    check above already established 0 <= idx < span, so zext is
    290    *    value-preserving. */
    291   if (sel_w < 64u) {
    292     kit_cg_zext(g, i64_ty);
    293   }
    294 
    295   /* 5. Build the dense label[] table and emit the rodata table. */
    296   labels =
    297       (Label*)h->alloc(h, (size_t)plan->span * sizeof *labels, _Alignof(Label));
    298   if (!labels) compiler_panic(c, g->cur_loc, "kit_cg_switch: oom");
    299   for (i = 0; i < plan->span; ++i) labels[i] = d->default_label;
    300   width = sel_w;
    301   {
    302     /* Index modularly: vmin may be the unsigned-window origin (a value with
    303      * the sign bit set), so a signed `vi - vmin` could overflow. This mirrors
    304      * the runtime `idx = sel - vmin` computed in sel_ty, and reproduces the
    305      * old non-wrapping subtraction exactly for the signed-window case. */
    306     u64 mask = (width >= 64u) ? UINT64_MAX : (((u64)1u << width) - 1u);
    307     for (i = 0; i < d->ncases; ++i) {
    308       u64 table_index = ((u64)d->cases[i].value - (u64)plan->vmin) & mask;
    309       labels[table_index] = d->cases[i].label;
    310     }
    311   }
    312   table_sym = api_emit_label_table(g, labels, (u32)plan->span);
    313   h->free(h, labels, (size_t)plan->span * sizeof *labels);
    314   if (table_sym == OBJ_SYM_NONE) {
    315     /* api_emit_label_table panics on real failure; this only fires if
    316      * a future caller asks for a 0-entry table (which cg_plan_switch
    317      * already rules out). */
    318     compiler_panic(c, g->cur_loc, "kit_cg_switch: table emission failed");
    319     return;
    320   }
    321   arr_ty = kit_cg_type_array((KitCompiler*)c, void_ptr_ty, plan->span);
    322   memset(&decl, 0, sizeof decl);
    323   decl.kind = KIT_CG_DECL_OBJECT;
    324   decl.sym.bind = KIT_SB_LOCAL;
    325   decl.sym.visibility = KIT_CG_VIS_DEFAULT;
    326   decl.as.object.flags = KIT_CG_OBJ_READONLY;
    327   api_remember_sym(g, table_sym, arr_ty, decl);
    328 
    329   /* 6. Load table[idx]: bitcast the table pointer to a pointer-to-element so
    330    *    the element place carries the pointer-size scale, put the index on top,
    331    *    project the element place, then load it. */
    332   kit_cg_push_symbol_addr(g, (KitCgSym)table_sym, 0); /* [idx, &table] */
    333   kit_cg_bitcast(g, cg_type_ptr_to(c, void_ptr_ty));  /* &table : void** */
    334   kit_cg_swap(g);                                     /* [&table, idx] */
    335   kit_cg_elem(g, 0);                                  /* [&table[idx]] */
    336   memset(&acc, 0, sizeof acc);
    337   acc.type = void_ptr_ty;
    338   acc.align = (uint32_t)c->target.ptr_align;
    339   kit_cg_load(g, acc); /* [label_addr] */
    340 
    341   /* 7. Indirect branch with the full closed target set (every case +
    342    *    default), so backends doing branch-target hardening (BTI/IBT/CFG)
    343    *    can stamp landing pads on every reachable label. */
    344   targets = (KitCgLabel*)h->alloc(h, (d->ncases + 1u) * sizeof *targets,
    345                                   _Alignof(KitCgLabel));
    346   if (!targets) compiler_panic(c, g->cur_loc, "kit_cg_switch: oom");
    347   for (i = 0; i < d->ncases; ++i) {
    348     targets[i] = (KitCgLabel)d->cases[i].label;
    349   }
    350   targets[d->ncases] = (KitCgLabel)d->default_label;
    351   kit_cg_computed_goto(g, targets, d->ncases + 1u);
    352   h->free(h, targets, (d->ncases + 1u) * sizeof *targets);
    353 }
    354 
    355 void kit_cg_switch(KitCg* g, KitCgSwitch sw) {
    356   ApiSValue selector;
    357   CGSwitchDesc desc;
    358   Heap* h;
    359   CGSwitchCase* cases = NULL;
    360   CGSwitchPlan plan;
    361   int native_switch_override;
    362   if (!g) return;
    363   if (g->sp == 0) return;
    364   if (api_unevaluated(g)) {
    365     selector = api_pop(g);
    366     api_release(g, &selector);
    367     return;
    368   }
    369   api_local_const_control_boundary(g);
    370   memset(&desc, 0, sizeof desc);
    371   desc.selector_type = resolve_type(g->c, sw.selector_type);
    372   if (!desc.selector_type) {
    373     ApiSValue tmp = g->stack[g->sp - 1u];
    374     desc.selector_type = api_sv_type(&tmp);
    375   }
    376   desc.default_label = (Label)sw.default_label;
    377   desc.ncases = sw.ncases;
    378   desc.hint = (u8)sw.hint;
    379   desc.opt_level = (u8)g->opt_level;
    380   if (sw.ncases) {
    381     h = g->c->ctx->heap;
    382     cases = (CGSwitchCase*)h->alloc(h, sw.ncases * sizeof(CGSwitchCase),
    383                                     _Alignof(CGSwitchCase));
    384     if (!cases)
    385       compiler_panic(g->c, g->cur_loc, "kit_cg_switch: out of memory");
    386     for (u32 i = 0; i < sw.ncases; ++i) {
    387       cases[i].value = sw.cases[i].value;
    388       cases[i].label = (Label)sw.cases[i].label;
    389     }
    390     desc.cases = cases;
    391   }
    392 
    393   /* Direct O0 targets may override switch_ for a single-pass branch-chain
    394    * lowering. Still honor an explicit jump-table hint so tests and frontends
    395    * can exercise the semantic label-table path without enabling O1. */
    396   native_switch_override = (g->target->switch_ && g->opt_level == 0 &&
    397                             desc.hint != KIT_CG_SWITCH_JUMP_TABLE);
    398   plan = native_switch_override ? (CGSwitchPlan){CG_SWITCH_PLAN_CHAIN, 0, 0}
    399                                 : cg_plan_switch(g, &desc);
    400 
    401   /* The label-table lowering materializes a rodata table of code-label
    402    * addresses and an indirect branch. Targets that can't express that (Wasm)
    403    * realize dense dispatch through their switch_ hook (br_table) instead, so
    404    * hand the plan—hint and all—to switch_ rather than the table path. */
    405   if (plan.kind == CG_SWITCH_PLAN_TABLE && g->target->switch_ &&
    406       g->target->supports_label_table &&
    407       !g->target->supports_label_table(g->target)) {
    408     plan.kind = CG_SWITCH_PLAN_CHAIN;
    409   }
    410 
    411   if (plan.kind == CG_SWITCH_PLAN_TABLE) {
    412     /* Selector stays on the value stack; cg_emit_switch_table consumes
    413      * it via cg-API ops so the path also records cleanly under opt. */
    414     metrics_count(g->c, "cg.switch.table", 1);
    415     cg_emit_switch_table(g, &desc, &plan);
    416   } else {
    417     metrics_count(g->c, "cg.switch.chain", 1);
    418     selector = api_pop(g);
    419     desc.selector =
    420         api_force_local_unless_imm(g, &selector, desc.selector_type);
    421     if (g->target->switch_) {
    422       g->target->switch_(g->target, &desc);
    423     } else {
    424       cg_lower_switch_default(g->target, &desc);
    425     }
    426     api_release(g, &selector);
    427   }
    428   if (cases) {
    429     h = g->c->ctx->heap;
    430     h->free(h, cases, sw.ncases * sizeof(CGSwitchCase));
    431   }
    432 }
    433 
    434 void kit_cg_push_label_addr(KitCg* g, KitCgLabel label, KitCgTypeId ptr_type) {
    435   KitCgTypeId ty;
    436   CGLocal r;
    437   Operand dst;
    438   if (!g) return;
    439   ty = resolve_type(g->c, ptr_type);
    440   if (!ty) ty = cg_type_ptr_to(g->c, builtin_id(KIT_CG_BUILTIN_VOID));
    441   if (api_unevaluated(g)) {
    442     api_push(g, api_uneval_value(g, ty));
    443     api_const_set_top(g, api_const_unknown(ty));
    444     return;
    445   }
    446   r = api_alloc_temp_local(g, ty);
    447   dst = api_op_local(r, ty);
    448   g->target->load_label_addr(g->target, dst, (Label)label);
    449   api_push(g, api_make_sv(dst, ty));
    450 }
    451 
    452 void kit_cg_computed_goto(KitCg* g, const KitCgLabel* valid_targets,
    453                           uint32_t ntargets) {
    454   ApiSValue target;
    455   KitCgTypeId target_ty;
    456   Operand target_op;
    457   if (!g) return;
    458   if (api_unevaluated(g)) {
    459     target = api_pop(g);
    460     api_release(g, &target);
    461     return;
    462   }
    463   CG_REQUIRE(g, valid_targets && ntargets != 0,
    464              "kit_cg_computed_goto: valid_targets must be non-empty");
    465   api_local_const_control_boundary(g);
    466   target = api_pop(g);
    467   target_ty = api_sv_type(&target);
    468   target_op = api_force_local(g, &target, target_ty);
    469   g->target->indirect_branch(g->target, target_op, (const Label*)valid_targets,
    470                              ntargets);
    471   api_release(g, &target);
    472 }
    473 
    474 void kit_cg_unreachable(KitCg* g) {
    475   if (!g) return;
    476   if (api_unevaluated(g)) return;
    477   api_local_const_control_boundary(g);
    478   g->target->unreachable(g->target);
    479 }
    480 
    481 /* ============================================================
    482  * Scopes / structured control flow
    483  * ============================================================ */
    484 
    485 /* Grow g->scopes to hold at least `want` entries (mirrors api_grow_locals).
    486  * New slots are zeroed so their `active`/`generation` start clean. */
    487 int api_grow_scopes(KitCg* g, u32 want) {
    488   Heap* h = g->c->ctx->heap;
    489   ApiCgScope* nb;
    490   u32 cap;
    491   if (g->scopes_cap >= want) return 1;
    492   cap = g->scopes_cap ? g->scopes_cap : 16u;
    493   while (cap < want) cap *= 2u;
    494   nb = (ApiCgScope*)h->alloc(h, sizeof(*nb) * cap, _Alignof(ApiCgScope));
    495   if (!nb) return 0;
    496   memset(nb, 0, sizeof(*nb) * cap);
    497   if (g->scopes) {
    498     memcpy(nb, g->scopes, sizeof(*nb) * g->nscopes);
    499     h->free(h, g->scopes, sizeof(*g->scopes) * g->scopes_cap);
    500   }
    501   g->scopes = nb;
    502   g->scopes_cap = cap;
    503   return 1;
    504 }
    505 
    506 /* The handle packs the 1-based scope index into the low 16 bits and the
    507  * generation into the upper 16 — see api_scope_from_handle. */
    508 KitCgScope api_scope_handle(u32 idx, u32 generation) {
    509   return (KitCgScope)((generation << 16) | ((idx + 1u) & 0xffffu));
    510 }
    511 
    512 ApiCgScope* api_scope_from_handle(KitCg* g, KitCgScope scope, int require_top,
    513                                   const char* who) {
    514   u32 scope_index;
    515   u32 generation;
    516   ApiCgScope* s;
    517   if (!g || scope == 0) return NULL;
    518   scope_index = ((u32)scope & 0xffffu);
    519   generation = ((u32)scope >> 16);
    520   CG_REQUIRE(g, scope_index != 0 && scope_index <= API_CG_MAX_SCOPES,
    521              "%.*s: invalid scope handle", SLICE_ARG(slice_from_cstr(who)));
    522   scope_index--;
    523   CG_REQUIRE(g, scope_index < g->nscopes, "%.*s: stale scope handle",
    524              SLICE_ARG(slice_from_cstr(who)));
    525   CG_REQUIRE(g, !require_top || scope_index + 1u == g->nscopes,
    526              "%.*s: non-LIFO scope end", SLICE_ARG(slice_from_cstr(who)));
    527   s = &g->scopes[scope_index];
    528   CG_REQUIRE(g, s->active && s->generation == generation,
    529              "%.*s: stale scope handle", SLICE_ARG(slice_from_cstr(who)));
    530   return s;
    531 }
    532 
    533 static int api_require_loop_scope(KitCg* g, const ApiCgScope* s,
    534                                   const char* op);
    535 
    536 int api_scope_has_result(const ApiCgScope* s) { return s->nresults != 0; }
    537 
    538 /* Store one already-popped value into a scope carry local. Shared by the result
    539  * and loop-param paths; `local`/`type` select which carry slot. */
    540 static void api_scope_store_one(KitCg* g, CGLocal local, KitCgTypeId type,
    541                                 ApiSValue* v) {
    542   Operand dst = api_op_local(local, type);
    543   Operand src =
    544       api_sv_op_is_local_or_imm(v) ? v->op : api_force_local(g, v, type);
    545   g->target->store(g->target, dst, src, api_mem_for_lvalue(g, &dst, type));
    546   api_release(g, v);
    547 }
    548 
    549 /* Reload one carry local onto the value stack (fresh temp, load, push). */
    550 static void api_scope_reload_one(KitCg* g, CGLocal local, KitCgTypeId type,
    551                                  const void* lang_type, u16 lang_flags) {
    552   CGLocal r = api_alloc_temp_local(g, type);
    553   Operand dst = api_op_local(r, type);
    554   Operand src = api_op_local(local, type);
    555   ApiSValue sv;
    556   g->target->load(g->target, dst, src, api_mem_for_lvalue(g, &src, type));
    557   sv = api_make_sv(dst, type);
    558   sv.lang_type = lang_type;
    559   sv.lang_flags = lang_flags;
    560   api_push(g, sv);
    561 }
    562 
    563 /* Pop the scope's N results into a caller array (out[i] = result i, value-stack
    564  * bottom->top). The top of stack — the last result — is popped first. */
    565 static void api_scope_pop_results(KitCg* g, ApiCgScope* s, ApiSValue* out) {
    566   u32 k;
    567   for (k = 0; k < s->nresults; ++k) out[s->nresults - 1u - k] = api_pop(g);
    568 }
    569 
    570 /* Pop N results off the value stack and store each into its carry local. */
    571 void api_scope_store_results(KitCg* g, ApiCgScope* s) {
    572   u32 k;
    573   for (k = 0; k < s->nresults; ++k) {
    574     u32 idx = s->nresults - 1u - k; /* TOS popped first -> highest index */
    575     ApiSValue v = api_pop(g);
    576     s->result_lang_types[idx] = v.lang_type;
    577     s->result_lang_flags[idx] = v.lang_flags;
    578     api_scope_store_one(g, s->result_locals[idx], s->result_types[idx], &v);
    579   }
    580 }
    581 
    582 /* Reload all N result carry locals onto the value stack (inverse of store). */
    583 void api_scope_push_results(KitCg* g, ApiCgScope* s) {
    584   u32 k;
    585   for (k = 0; k < s->nresults; ++k)
    586     api_scope_reload_one(g, s->result_locals[k], s->result_types[k],
    587                          s->result_lang_types[k], s->result_lang_flags[k]);
    588 }
    589 
    590 /* Pop N params off the value stack into the loop-param carry locals. */
    591 static void api_scope_store_params(KitCg* g, ApiCgScope* s) {
    592   u32 k;
    593   for (k = 0; k < s->nparams; ++k) {
    594     u32 idx = s->nparams - 1u - k;
    595     ApiSValue v = api_pop(g);
    596     s->param_lang_types[idx] = v.lang_type;
    597     s->param_lang_flags[idx] = v.lang_flags;
    598     api_scope_store_one(g, s->param_locals[idx], s->param_types[idx], &v);
    599   }
    600 }
    601 
    602 /* Reload all N loop-param carry locals onto the value stack. */
    603 static void api_scope_reload_params(KitCg* g, ApiCgScope* s) {
    604   u32 k;
    605   for (k = 0; k < s->nparams; ++k)
    606     api_scope_reload_one(g, s->param_locals[k], s->param_types[k],
    607                          s->param_lang_types[k], s->param_lang_flags[k]);
    608 }
    609 
    610 /* Allocate a memory-resident carry local of the given type. `resolved` is the
    611  * canonicalized type id (for the local's type); `raw` is the as-supplied id
    612  * used for the ABI size/align query, matching the historical single-result
    613  * path. */
    614 static CGLocal api_scope_alloc_carry_local(KitCg* g, KitCgTypeId resolved,
    615                                            KitCgTypeId raw) {
    616   CGLocalDesc ld;
    617   memset(&ld, 0, sizeof ld);
    618   ld.type = resolved;
    619   ld.size = abi_cg_sizeof(g->c->abi, raw);
    620   ld.align = abi_cg_alignof(g->c->abi, raw);
    621   ld.flags = CG_LOCAL_MEMORY_REQUIRED;
    622   return g->target->local(g->target, &ld);
    623 }
    624 
    625 /* Point a scope's carry-local vectors at storage: the inline buffers for the
    626  * common small arity (no allocation), or one heap block when either count
    627  * exceeds API_CG_SCOPE_SIG_INLINE. Returns 0 only on heap OOM. */
    628 static int api_scope_setup_sig(KitCg* g, ApiCgScope* s,
    629                                const KitCgScopeSig* sig) {
    630   u32 nr = sig ? sig->nresults : 0u;
    631   u32 np = sig ? sig->nparams : 0u;
    632   s->nresults = nr;
    633   s->nparams = np;
    634   s->heap_block = NULL;
    635   if (nr <= API_CG_SCOPE_SIG_INLINE && np <= API_CG_SCOPE_SIG_INLINE) {
    636     s->result_locals = s->result_locals_inl;
    637     s->result_types = s->result_types_inl;
    638     s->result_lang_types = s->result_lang_types_inl;
    639     s->result_lang_flags = s->result_lang_flags_inl;
    640     s->param_locals = s->param_locals_inl;
    641     s->param_types = s->param_types_inl;
    642     s->param_lang_types = s->param_lang_types_inl;
    643     s->param_lang_flags = s->param_lang_flags_inl;
    644     return 1;
    645   }
    646   {
    647     /* CGLocal and KitCgTypeId are both u32; pack the four vectors into one
    648      * block as [result_locals][result_types][param_locals][param_types]. */
    649     Heap* h = g->c->ctx->heap;
    650     u32 total = 2u * nr + 2u * np;
    651     u32* blk = (u32*)h->alloc(h, sizeof(u32) * total, _Alignof(u32));
    652     if (!blk) return 0;
    653     s->heap_block = blk;
    654     s->result_locals = blk;
    655     s->result_types = blk + nr;
    656     s->param_locals = blk + 2u * nr;
    657     s->param_types = blk + 2u * nr + np;
    658     s->heap_lang_types_block =
    659         (const void**)h->alloc(h, sizeof(void*) * (nr + np), _Alignof(void*));
    660     s->heap_lang_flags_block =
    661         (u16*)h->alloc(h, sizeof(u16) * (nr + np), _Alignof(u16));
    662     if (!s->heap_lang_types_block || !s->heap_lang_flags_block) {
    663       if (s->heap_lang_types_block)
    664         h->free(h, s->heap_lang_types_block, sizeof(void*) * (nr + np));
    665       if (s->heap_lang_flags_block)
    666         h->free(h, s->heap_lang_flags_block, sizeof(u16) * (nr + np));
    667       h->free(h, blk, sizeof(u32) * total);
    668       s->heap_block = NULL;
    669       s->heap_lang_types_block = NULL;
    670       s->heap_lang_flags_block = NULL;
    671       return 0;
    672     }
    673     s->result_lang_types = s->heap_lang_types_block;
    674     s->param_lang_types = s->heap_lang_types_block + nr;
    675     s->result_lang_flags = s->heap_lang_flags_block;
    676     s->param_lang_flags = s->heap_lang_flags_block + nr;
    677     return 1;
    678   }
    679 }
    680 
    681 static void api_scope_free_sig(KitCg* g, ApiCgScope* s) {
    682   Heap* h = g->c->ctx->heap;
    683   if (s->heap_block) {
    684     u32 total = 2u * s->nresults + 2u * s->nparams;
    685     h->free(h, s->heap_block, sizeof(u32) * total);
    686     s->heap_block = NULL;
    687   }
    688   if (s->heap_lang_types_block) {
    689     h->free(h, s->heap_lang_types_block,
    690             sizeof(void*) * (s->nresults + s->nparams));
    691     s->heap_lang_types_block = NULL;
    692   }
    693   if (s->heap_lang_flags_block) {
    694     h->free(h, s->heap_lang_flags_block,
    695             sizeof(u16) * (s->nresults + s->nparams));
    696     s->heap_lang_flags_block = NULL;
    697   }
    698 }
    699 
    700 static KitCgScope api_scope_begin_sig_kind(KitCg* g, u8 kind,
    701                                            const KitCgScopeSig* sig) {
    702   Label break_lbl, cont_lbl;
    703   CGScopeDesc d;
    704   ApiCgScope* s;
    705   CGScope target_scope;
    706   u32 idx, k;
    707   if (!g) return 0;
    708   break_lbl = g->target->label_new(g->target);
    709   cont_lbl =
    710       (kind == SCOPE_LOOP) ? g->target->label_new(g->target) : LABEL_NONE;
    711 
    712   /* Depth past what the 16-bit handle index can address (see api_scope_handle)
    713    * — unreachable for any realistic source. */
    714   CG_REQUIRE(g, g->nscopes < API_CG_MAX_SCOPES,
    715              "KitCg: too many nested scopes");
    716   if (!api_grow_scopes(g, g->nscopes + 1u)) {
    717     compiler_panic(g->c, g->cur_loc, "KitCg: out of memory growing scopes");
    718     return 0;
    719   }
    720   idx = g->nscopes;
    721   s = &g->scopes[idx];
    722   s->break_lbl = break_lbl;
    723   s->continue_lbl = cont_lbl;
    724   s->generation = ++g->scope_generation;
    725   if (s->generation == 0) s->generation = ++g->scope_generation;
    726   s->active = 1;
    727   if (!api_scope_setup_sig(g, s, sig)) {
    728     compiler_panic(g->c, g->cur_loc,
    729                    "KitCg: out of memory for scope signature");
    730     return 0;
    731   }
    732   for (k = 0; k < s->nresults; ++k)
    733     s->result_types[k] = resolve_type(g->c, sig->results[k]);
    734   for (k = 0; k < s->nparams; ++k)
    735     s->param_types[k] = resolve_type(g->c, sig->params[k]);
    736 
    737   /* Loop preheader: snapshot the params off the value stack into their carry
    738    * locals BEFORE the loop header, so the pop runs once in the preheader and
    739    * every back edge (store_params + jump cont) re-stores into the same locals.
    740    * Allocating a local emits nothing; the param stores belong before cont_lbl.
    741    */
    742   if (kind == SCOPE_LOOP && s->nparams) {
    743     for (k = 0; k < s->nparams; ++k)
    744       s->param_locals[k] =
    745           api_scope_alloc_carry_local(g, s->param_types[k], sig->params[k]);
    746     api_scope_store_params(g, s);
    747   }
    748 
    749   api_local_const_control_boundary(g);
    750   if (cont_lbl != LABEL_NONE) g->target->label_place(g->target, cont_lbl);
    751   g->nscopes++;
    752 
    753   memset(&d, 0, sizeof d);
    754   d.kind = kind;
    755   d.break_label = break_lbl;
    756   d.continue_label = cont_lbl;
    757   /* CGScopeDesc.result_type stays a single informational field (backends ignore
    758    * it): the single-result type, or NONE for void / multi-value. Multi-value is
    759    * realized purely through the CG carry locals above. */
    760   d.result_type = (s->nresults == 1u) ? s->result_types[0] : KIT_CG_TYPE_NONE;
    761   target_scope = g->target->scope_begin(g->target, &d);
    762   s->target_scope = target_scope;
    763 
    764   /* Allocate the result carry locals after scope_begin, matching the historical
    765    * single-result ordering. */
    766   for (k = 0; k < s->nresults; ++k)
    767     s->result_locals[k] =
    768         api_scope_alloc_carry_local(g, s->result_types[k], sig->results[k]);
    769 
    770   /* The loop body observes the params on the value stack: reload them after the
    771    * header so a back edge re-establishes exactly this shape. */
    772   if (kind == SCOPE_LOOP && s->nparams) api_scope_reload_params(g, s);
    773 
    774   return api_scope_handle(idx, s->generation);
    775 }
    776 
    777 KitCgScope kit_cg_scope_begin(KitCg* g) {
    778   KitCgScopeSig sig;
    779   memset(&sig, 0, sizeof sig);
    780   return api_scope_begin_sig_kind(g, (u8)SCOPE_LOOP, &sig);
    781 }
    782 
    783 KitCgScope kit_cg_scope_begin_value(KitCg* g, KitCgTypeId result_type) {
    784   KitCgScopeSig sig;
    785   KitCgTypeId resolved;
    786   memset(&sig, 0, sizeof sig);
    787   if (!g) return 0;
    788   resolved = resolve_type(g->c, result_type);
    789   CG_REQUIRE(g,
    790              resolved != KIT_CG_TYPE_NONE && !cg_type_is_void(g->c, resolved),
    791              "KitCg: value scope requires non-void result type");
    792   sig.results = &result_type;
    793   sig.nresults = 1u;
    794   return api_scope_begin_sig_kind(g, (u8)SCOPE_LOOP, &sig);
    795 }
    796 
    797 KitCgScope kit_cg_block_begin(KitCg* g) {
    798   KitCgScopeSig sig;
    799   memset(&sig, 0, sizeof sig);
    800   return api_scope_begin_sig_kind(g, (u8)SCOPE_BLOCK, &sig);
    801 }
    802 
    803 KitCgScope kit_cg_block_begin_value(KitCg* g, KitCgTypeId result_type) {
    804   KitCgScopeSig sig;
    805   KitCgTypeId resolved;
    806   memset(&sig, 0, sizeof sig);
    807   if (!g) return 0;
    808   resolved = resolve_type(g->c, result_type);
    809   CG_REQUIRE(g,
    810              resolved != KIT_CG_TYPE_NONE && !cg_type_is_void(g->c, resolved),
    811              "KitCg: value block requires non-void result type");
    812   sig.results = &result_type;
    813   sig.nresults = 1u;
    814   return api_scope_begin_sig_kind(g, (u8)SCOPE_BLOCK, &sig);
    815 }
    816 
    817 KitCgScope kit_cg_scope_begin_sig(KitCg* g, const KitCgScopeSig* sig) {
    818   return api_scope_begin_sig_kind(g, (u8)SCOPE_LOOP, sig);
    819 }
    820 
    821 KitCgScope kit_cg_block_begin_sig(KitCg* g, const KitCgScopeSig* sig) {
    822   return api_scope_begin_sig_kind(g, (u8)SCOPE_BLOCK, sig);
    823 }
    824 
    825 void kit_cg_scope_store_params(KitCg* g, KitCgScope scope) {
    826   ApiCgScope* s =
    827       api_scope_from_handle(g, scope, 0, "KitCg: scope_store_params");
    828   if (!s) return;
    829   if (!api_require_loop_scope(g, s, "scope_store_params")) return;
    830   api_scope_store_params(g, s);
    831 }
    832 
    833 KitCgLabel kit_cg_scope_break_label(KitCg* g, KitCgScope scope) {
    834   ApiCgScope* s =
    835       api_scope_from_handle(g, scope, 0, "KitCg: scope_break_label");
    836   return s ? (KitCgLabel)s->break_lbl : (KitCgLabel)0;
    837 }
    838 
    839 KitCgLabel kit_cg_scope_continue_label(KitCg* g, KitCgScope scope) {
    840   ApiCgScope* s =
    841       api_scope_from_handle(g, scope, 0, "KitCg: scope_continue_label");
    842   return s ? (KitCgLabel)s->continue_lbl : (KitCgLabel)0;
    843 }
    844 
    845 void kit_cg_scope_end(KitCg* g, KitCgScope scope) {
    846   ApiCgScope* s = api_scope_from_handle(g, scope, 1, "KitCg: scope_end");
    847   if (!s) return;
    848   /* The fall-through results are on TOS; store them so they join any
    849    * branch-supplied copies in the same carry locals, then reload after the
    850    * break label as the scope's outputs. */
    851   api_scope_store_results(g, s);
    852   api_local_const_control_boundary(g);
    853   g->target->label_place(g->target, s->break_lbl);
    854   g->target->scope_end(g->target, s->target_scope);
    855   api_scope_push_results(g, s);
    856   api_scope_free_sig(g, s);
    857   s->active = 0;
    858   g->nscopes--;
    859 }
    860 
    861 void kit_cg_break(KitCg* g, KitCgScope scope) {
    862   ApiCgScope* s = api_scope_from_handle(g, scope, 0, "KitCg: break");
    863   if (!s) return;
    864   api_scope_store_results(g, s);
    865   api_local_const_control_boundary(g);
    866   g->target->jump(g->target, s->break_lbl);
    867 }
    868 
    869 void kit_cg_scope_end_unreachable(KitCg* g, KitCgScope scope) {
    870   /* Close a scope whose fall-through is unreachable (its body terminated via a
    871    * branch/return). There are no fall-through results to pop; the carry locals
    872    * already hold whatever the branch that exited stored. Mirrors scope_end but
    873    * skips the leading store_results. The caller must have the value stack at
    874    * the scope's base depth. */
    875   ApiCgScope* s =
    876       api_scope_from_handle(g, scope, 1, "KitCg: scope_end_unreachable");
    877   if (!s) return;
    878   api_local_const_control_boundary(g);
    879   g->target->label_place(g->target, s->break_lbl);
    880   g->target->scope_end(g->target, s->target_scope);
    881   api_scope_push_results(g, s);
    882   api_scope_free_sig(g, s);
    883   s->active = 0;
    884   g->nscopes--;
    885 }
    886 
    887 /* Store the pre-popped result array into the scope's carry locals (forward). */
    888 static void api_scope_store_results_from(KitCg* g, ApiCgScope* s,
    889                                          ApiSValue* rs) {
    890   u32 k;
    891   for (k = 0; k < s->nresults; ++k) {
    892     s->result_lang_types[k] = rs[k].lang_type;
    893     s->result_lang_flags[k] = rs[k].lang_flags;
    894     api_scope_store_one(g, s->result_locals[k], s->result_types[k], &rs[k]);
    895   }
    896 }
    897 
    898 /* Shared body of break_true / break_false: pop the condition, then — when the
    899  * scope carries results — pop them all (so they are consumed on both the taken
    900  * and not-taken paths, per the API contract), and store-then-jump only when the
    901  * branch is taken. `break_when` selects the sense. The single-result path is
    902  * byte-identical to the historical code. */
    903 static void api_break_cond(KitCg* g, KitCgScope scope, int break_when,
    904                            const char* who) {
    905   ApiCgScope* s;
    906   ApiSValue cond;
    907   if (!g || scope == 0) return;
    908   s = api_scope_from_handle(g, scope, 0, who);
    909   if (!s) return;
    910   cond = api_pop(g);
    911 
    912   if (!api_scope_has_result(s)) {
    913     api_branch_if(g, &cond, break_when, s->break_lbl);
    914     return;
    915   }
    916   {
    917     Heap* h = g->c->ctx->heap;
    918     ApiSValue* rs = (ApiSValue*)h->alloc(h, sizeof(ApiSValue) * s->nresults,
    919                                          _Alignof(ApiSValue));
    920     if (!rs) compiler_panic(g->c, g->cur_loc, "KitCg: out of memory");
    921     api_scope_pop_results(g, s, rs);
    922     if (api_sv_kind(&cond) == SV_OPERAND && cond.op.kind == OPK_IMM) {
    923       if ((cond.op.v.imm != 0) == !!break_when) {
    924         api_scope_store_results_from(g, s, rs);
    925         api_local_const_control_boundary(g);
    926         g->target->jump(g->target, s->break_lbl);
    927       } else {
    928         u32 k;
    929         for (k = 0; k < s->nresults; ++k) api_release(g, &rs[k]);
    930       }
    931       api_release(g, &cond);
    932     } else {
    933       Label skip = g->target->label_new(g->target);
    934       api_branch_if(g, &cond, !break_when, skip);
    935       api_scope_store_results_from(g, s, rs);
    936       api_local_const_control_boundary(g);
    937       g->target->jump(g->target, s->break_lbl);
    938       api_local_const_control_boundary(g);
    939       g->target->label_place(g->target, skip);
    940     }
    941     h->free(h, rs, sizeof(ApiSValue) * s->nresults);
    942   }
    943 }
    944 
    945 void kit_cg_break_true(KitCg* g, KitCgScope scope) {
    946   api_break_cond(g, scope, 1, "KitCg: break_true");
    947 }
    948 
    949 void kit_cg_break_false(KitCg* g, KitCgScope scope) {
    950   api_break_cond(g, scope, 0, "KitCg: break_false");
    951 }
    952 
    953 /* continue jumps to the loop header, which a forward-only block scope does not
    954  * have (its continue_lbl is LABEL_NONE). Reject it with a clean diagnostic
    955  * rather than emitting a jump to a nonexistent label. */
    956 static int api_require_loop_scope(KitCg* g, const ApiCgScope* s,
    957                                   const char* op) {
    958   CG_REQUIRE(g, s->continue_lbl != LABEL_NONE,
    959              "KitCg: %s is not valid on a forward-only block scope", op);
    960   return 1;
    961 }
    962 
    963 void kit_cg_continue(KitCg* g, KitCgScope scope) {
    964   ApiCgScope* s = api_scope_from_handle(g, scope, 0, "KitCg: continue");
    965   if (!s) return;
    966   if (!api_require_loop_scope(g, s, "continue")) return;
    967   api_local_const_control_boundary(g);
    968   g->target->jump(g->target, s->continue_lbl);
    969 }
    970 
    971 void kit_cg_continue_true(KitCg* g, KitCgScope scope) {
    972   ApiCgScope* s;
    973   ApiSValue v;
    974   if (!g || scope == 0) return;
    975   s = api_scope_from_handle(g, scope, 0, "KitCg: continue_true");
    976   if (!s) return;
    977   if (!api_require_loop_scope(g, s, "continue_true")) return;
    978   v = api_pop(g);
    979   api_branch_if(g, &v, 1, s->continue_lbl);
    980 }
    981 
    982 void kit_cg_continue_false(KitCg* g, KitCgScope scope) {
    983   ApiCgScope* s;
    984   ApiSValue v;
    985   if (!g || scope == 0) return;
    986   s = api_scope_from_handle(g, scope, 0, "KitCg: continue_false");
    987   if (!s) return;
    988   if (!api_require_loop_scope(g, s, "continue_false")) return;
    989   v = api_pop(g);
    990   api_branch_if(g, &v, 0, s->continue_lbl);
    991 }
    992 
    993 /* ============================================================
    994  * Dynamic stack allocation / variadics (stubs)
    995  * ============================================================ */
    996 
    997 void kit_cg_alloca(KitCg* g, uint32_t align, KitCgTypeId result_ptr_type) {
    998   ApiSValue sz;
    999   CgTarget* T;
   1000   KitCgTypeId pty;
   1001   Operand sz_op;
   1002   CGLocal rr;
   1003   Operand dst;
   1004   if (!g) return;
   1005   T = g->target;
   1006   sz = api_pop(g);
   1007   pty = resolve_type(g->c, result_ptr_type);
   1008   if (!pty) pty = cg_type_ptr_to(g->c, builtin_id(KIT_CG_BUILTIN_VOID));
   1009   if (api_unevaluated(g)) {
   1010     api_release(g, &sz);
   1011     api_push(g, api_uneval_value(g, pty));
   1012     api_const_set_top(g, api_const_unknown(pty));
   1013     return;
   1014   }
   1015   sz_op = api_sv_op_is(&sz, OPK_IMM)
   1016               ? sz.op
   1017               : api_force_local(g, &sz, api_sv_type(&sz));
   1018   rr = api_alloc_temp_local(g, pty);
   1019   dst = api_op_local(rr, pty);
   1020   T->alloca_(T, dst, sz_op, align ? align : 16);
   1021   api_release(g, &sz);
   1022   api_push(g, api_make_sv(dst, pty));
   1023 }
   1024 
   1025 void kit_cg_vararg_start(KitCg* g) {
   1026   ApiSValue ap;
   1027   CgTarget* T;
   1028   Operand ap_op;
   1029   if (!g) return;
   1030   T = g->target;
   1031   ap = api_pop(g);
   1032   if (api_unevaluated(g)) {
   1033     api_release(g, &ap);
   1034     return;
   1035   }
   1036   ap_op = api_force_local(g, &ap, api_sv_type(&ap));
   1037   T->va_start_(T, ap_op);
   1038   api_release(g, &ap);
   1039 }
   1040 
   1041 void kit_cg_vararg_next(KitCg* g, KitCgTypeId type) {
   1042   ApiSValue ap;
   1043   CgTarget* T;
   1044   KitCgTypeId ty;
   1045   Operand ap_op;
   1046   CGLocal rr;
   1047   Operand dst;
   1048   if (!g) return;
   1049   T = g->target;
   1050   ty = resolve_type(g->c, type);
   1051   if (!ty) return;
   1052   ap = api_pop(g);
   1053   if (api_unevaluated(g)) {
   1054     api_release(g, &ap);
   1055     api_push(g, api_uneval_value(g, ty));
   1056     api_const_set_top(g, api_const_unknown(ty));
   1057     return;
   1058   }
   1059   ap_op = api_force_local(g, &ap, api_sv_type(&ap));
   1060   rr = api_alloc_temp_local(g, ty);
   1061   dst = api_op_local(rr, ty);
   1062   T->va_arg_(T, dst, ap_op, ty);
   1063   api_release(g, &ap);
   1064   api_push(g, api_make_sv(dst, ty));
   1065 }
   1066 
   1067 void kit_cg_vararg_end(KitCg* g) {
   1068   ApiSValue ap;
   1069   CgTarget* T;
   1070   Operand ap_op;
   1071   if (!g) return;
   1072   T = g->target;
   1073   ap = api_pop(g);
   1074   if (api_unevaluated(g)) {
   1075     api_release(g, &ap);
   1076     return;
   1077   }
   1078   ap_op = api_force_local(g, &ap, api_sv_type(&ap));
   1079   T->va_end_(T, ap_op);
   1080   api_release(g, &ap);
   1081 }
   1082 
   1083 void kit_cg_vararg_copy(KitCg* g) {
   1084   ApiSValue src, dst;
   1085   CgTarget* T;
   1086   Operand src_op, dst_op;
   1087   if (!g) return;
   1088   T = g->target;
   1089   src = api_pop(g);
   1090   dst = api_pop(g);
   1091   if (api_unevaluated(g)) {
   1092     api_release(g, &src);
   1093     api_release(g, &dst);
   1094     return;
   1095   }
   1096   src_op = api_force_local(g, &src, api_sv_type(&src));
   1097   dst_op = api_force_local(g, &dst, api_sv_type(&dst));
   1098   T->va_copy_(T, dst_op, src_op);
   1099   api_release(g, &src);
   1100   api_release(g, &dst);
   1101 }
   1102 
   1103 /* ============================================================
   1104  * Memory operations (stubs)
   1105  * ============================================================ */
   1106 
   1107 void kit_cg_memcpy(KitCg* g, uint64_t size, KitCgMemAccess dst_access,
   1108                    KitCgMemAccess src_access) {
   1109   ApiSValue src, dst;
   1110   CgTarget* T;
   1111   AggregateAccess agg;
   1112   Operand dst_op, src_op;
   1113   if (!g) return;
   1114   api_local_const_memory_boundary(g);
   1115   (void)src_access;
   1116   if (size > UINT32_MAX) {
   1117     compiler_panic(g->c, g->cur_loc, "KitCg: memcpy size exceeds CgTarget");
   1118     return;
   1119   }
   1120   T = g->target;
   1121   src = api_pop(g);
   1122   dst = api_pop(g);
   1123   if (api_unevaluated(g)) {
   1124     api_release(g, &src);
   1125     api_release(g, &dst);
   1126     return;
   1127   }
   1128   api_require_pointer_value(g, "memcpy destination", api_sv_type(&dst));
   1129   api_require_pointer_value(g, "memcpy source", api_sv_type(&src));
   1130   dst_op = api_force_local(g, &dst, api_sv_type(&dst));
   1131   src_op = api_force_local(g, &src, api_sv_type(&src));
   1132   memset(&agg, 0, sizeof agg);
   1133   agg.size = (u32)size;
   1134   agg.align = dst_access.align ? dst_access.align : (u32)size;
   1135   T->copy_bytes(T, dst_op, src_op, agg);
   1136   api_release(g, &dst);
   1137   api_release(g, &src);
   1138 }
   1139 
   1140 void kit_cg_memmove(KitCg* g, uint64_t size, KitCgMemAccess dst_access,
   1141                     KitCgMemAccess src_access) {
   1142   ApiSValue src, dst;
   1143   Operand args[3];
   1144   if (!g) return;
   1145   api_local_const_memory_boundary(g);
   1146   (void)dst_access;
   1147   (void)src_access;
   1148   if (size > INT64_MAX) {
   1149     compiler_panic(g->c, g->cur_loc, "KitCg: memmove size exceeds CgTarget");
   1150     return;
   1151   }
   1152   src = api_pop(g);
   1153   dst = api_pop(g);
   1154   if (api_unevaluated(g)) {
   1155     api_release(g, &src);
   1156     api_release(g, &dst);
   1157     return;
   1158   }
   1159   api_require_pointer_value(g, "memmove destination", api_sv_type(&dst));
   1160   api_require_pointer_value(g, "memmove source", api_sv_type(&src));
   1161   args[0] = api_force_local(g, &dst, api_sv_type(&dst));
   1162   args[1] = api_force_local(g, &src, api_sv_type(&src));
   1163   args[2] = api_op_imm((i64)size, builtin_id(KIT_CG_BUILTIN_I64));
   1164   g->target->intrinsic(g->target, INTRIN_MEMMOVE, NULL, 0, args, 3);
   1165   api_release(g, &dst);
   1166   api_release(g, &src);
   1167 }
   1168 
   1169 void kit_cg_memset(KitCg* g, uint8_t val, uint64_t size,
   1170                    KitCgMemAccess dst_access) {
   1171   ApiSValue dst;
   1172   CgTarget* T;
   1173   AggregateAccess agg;
   1174   Operand dst_op, byte_val;
   1175   if (!g) return;
   1176   api_local_const_memory_boundary(g);
   1177   if (size > UINT32_MAX) {
   1178     compiler_panic(g->c, g->cur_loc, "KitCg: memset size exceeds CgTarget");
   1179     return;
   1180   }
   1181   T = g->target;
   1182   dst = api_pop(g);
   1183   if (api_unevaluated(g)) {
   1184     api_release(g, &dst);
   1185     return;
   1186   }
   1187   api_require_pointer_value(g, "memset destination", api_sv_type(&dst));
   1188   dst_op = api_force_local(g, &dst, api_sv_type(&dst));
   1189   byte_val = api_op_imm((i64)val, KIT_CG_TYPE_NONE);
   1190   memset(&agg, 0, sizeof agg);
   1191   agg.size = (u32)size;
   1192   agg.align = dst_access.align ? dst_access.align : (u32)size;
   1193   T->set_bytes(T, dst_op, byte_val, agg);
   1194   api_release(g, &dst);
   1195 }
   1196 
   1197 /* log2 of a {1,2,4,8} scale, else -1. */
   1198 static int cg_scale_to_log2(u32 scale) {
   1199   switch (scale) {
   1200     case 1:
   1201       return 0;
   1202     case 2:
   1203       return 1;
   1204     case 4:
   1205       return 2;
   1206     case 8:
   1207       return 3;
   1208     default:
   1209       return -1;
   1210   }
   1211 }
   1212 
   1213 static int cg_checked_scaled_offset(i64 index, u32 scale, i64 offset,
   1214                                     i64* out) {
   1215   i64 product;
   1216   if (__builtin_mul_overflow(index, (i64)scale, &product)) return 0;
   1217   if (__builtin_add_overflow(product, offset, out)) return 0;
   1218   return 1;
   1219 }
   1220 
   1221 /* Shared core for kit_cg_elem / kit_cg_elem_scaled. `elem_size` is the index
   1222  * stride in bytes; 0 means "derive from the base pointee size" (the kit_cg_elem
   1223  * behavior). The place's access type is always the base pointee. */
   1224 static void api_cg_elem(KitCg* g, u32 elem_size, int64_t offset) {
   1225   ApiSValue idx, base;
   1226   CgTarget* T;
   1227   KitCgTypeId base_ty, base_ptr_ty, elem_ty, idx_ty;
   1228   const CgType* base_info;
   1229   u32 elemsz;
   1230   Operand base_op, idx_op;
   1231   CGLocal base_local;
   1232   if (!g) return;
   1233   T = g->target;
   1234   idx = api_pop(g);
   1235   base = api_pop(g);
   1236   base_ty = api_sv_type(&base);
   1237   base_info = cg_type_get(g->c, base_ty);
   1238   CG_REQUIRE(g,
   1239              !api_is_lvalue_sv(&base) && base_info &&
   1240                  base_info->kind == KIT_CG_TYPE_PTR,
   1241              "KitCg: elem requires a pointer value base (decay an "
   1242              "array to a pointer first)");
   1243   elem_ty = base_info->ptr.pointee;
   1244   base_ptr_ty = base_ty;
   1245   elemsz = elem_size ? elem_size : (u32)abi_cg_sizeof(g->c->abi, elem_ty);
   1246   idx_ty = idx.type ? idx.type : idx.op.type;
   1247   if (!idx_ty) idx_ty = builtin_id(KIT_CG_BUILTIN_I64);
   1248   if (api_unevaluated(g)) {
   1249     api_release(g, &base);
   1250     api_release(g, &idx);
   1251     api_push(g, api_uneval_place(g, elem_ty));
   1252     api_const_set_top(g, api_const_unknown(elem_ty));
   1253     return;
   1254   }
   1255   idx_op = api_force_local_unless_imm(g, &idx, idx_ty);
   1256 
   1257   /* Constant index folds entirely into the displacement — no instructions, just
   1258    * a larger offset on the place. A symbol-address base can remain a GLOBAL
   1259    * place, folding the symbol addend with index*scale+offset instead of first
   1260    * materializing &sym into a temporary. */
   1261   if (idx_op.kind == OPK_IMM) {
   1262     i64 ofs;
   1263     Operand place;
   1264     if (cg_checked_scaled_offset(idx_op.v.imm, elemsz, offset, &ofs) &&
   1265         api_sv_kind(&base) == SV_OPERAND && base.op.kind == OPK_GLOBAL &&
   1266         !__builtin_add_overflow(base.op.v.global.addend, ofs, &ofs)) {
   1267       place = api_op_global(base.op.v.global.sym, ofs, elem_ty);
   1268     } else {
   1269       base_op = api_force_local(g, &base, base_ptr_ty);
   1270       base_local = base_op.v.local;
   1271       if (cg_checked_scaled_offset(idx_op.v.imm, elemsz, offset, &ofs) &&
   1272           ofs >= INT32_MIN && ofs <= INT32_MAX) {
   1273         place = api_op_indirect(base_local, (i32)ofs, elem_ty);
   1274       } else if (cg_checked_scaled_offset(idx_op.v.imm, elemsz, offset, &ofs)) {
   1275         CGLocal r = api_alloc_temp_local(g, base_ptr_ty);
   1276         Operand ro = api_op_local(r, base_ptr_ty);
   1277         T->binop(T, BO_IADD, ro, api_op_local(base_local, base_ptr_ty),
   1278                  api_op_imm(ofs, base_ptr_ty));
   1279         place = api_op_indirect(r, 0, elem_ty);
   1280       } else {
   1281         CGLocal ir = api_alloc_temp_local(g, idx_ty);
   1282         Operand idx_local = api_op_local(ir, idx_ty);
   1283         int lg2 = cg_scale_to_log2(elemsz);
   1284         u8 log2_scale = 0;
   1285         i32 place_offset;
   1286         T->load_imm(T, idx_local, idx_op.v.imm);
   1287         if (lg2 >= 0) {
   1288           log2_scale = (u8)lg2;
   1289         } else {
   1290           CGLocal sr = api_alloc_temp_local(g, idx_ty);
   1291           T->binop(T, BO_IMUL, api_op_local(sr, idx_ty), idx_local,
   1292                    api_op_imm((i64)elemsz, idx_ty));
   1293           ir = sr;
   1294         }
   1295         if (offset >= INT32_MIN && offset <= INT32_MAX) {
   1296           place_offset = (i32)offset;
   1297         } else {
   1298           CGLocal r = api_alloc_temp_local(g, base_ptr_ty);
   1299           Operand ro = api_op_local(r, base_ptr_ty);
   1300           T->binop(T, BO_IADD, ro, api_op_local(base_local, base_ptr_ty),
   1301                    api_op_imm(offset, base_ptr_ty));
   1302           base_local = r;
   1303           place_offset = 0;
   1304         }
   1305         place = api_op_indirect_indexed(base_local, ir, log2_scale,
   1306                                         place_offset, elem_ty);
   1307       }
   1308     }
   1309     api_release(g, &base);
   1310     api_release(g, &idx);
   1311     api_push(g, api_make_lv(place, elem_ty));
   1312     return;
   1313   }
   1314 
   1315   /* Dynamic index: build a scaled-index place so the backend emits one
   1316    * [base + index*scale] addressing mode. A power-of-two element size rides in
   1317    * log2_scale; any other size pre-multiplies the index once (scale 1). The
   1318    * index is copied into a fresh local for unambiguous ownership. */
   1319   {
   1320     int lg2 = cg_scale_to_log2(elemsz);
   1321     CGLocal ir;
   1322     u8 log2_scale;
   1323     i32 place_offset;
   1324     base_op = api_force_local(g, &base, base_ptr_ty);
   1325     base_local = base_op.v.local;
   1326     if (offset >= INT32_MIN && offset <= INT32_MAX) {
   1327       place_offset = (i32)offset;
   1328     } else {
   1329       CGLocal r = api_alloc_temp_local(g, base_ptr_ty);
   1330       Operand ro = api_op_local(r, base_ptr_ty);
   1331       T->binop(T, BO_IADD, ro, api_op_local(base_local, base_ptr_ty),
   1332                api_op_imm(offset, base_ptr_ty));
   1333       base_local = r;
   1334       place_offset = 0;
   1335     }
   1336     /* Force the index to a local first so we can tell whether it is a dead
   1337      * transient (provably not referenced elsewhere) that we can take over as
   1338      * the index slot directly, skipping the ownership copy. */
   1339     idx_op = api_force_local_unless_imm(g, &idx, idx_ty);
   1340     if (idx.op.kind == OPK_LOCAL) idx_op = idx.op;
   1341     if (lg2 >= 0 && idx_op.kind == OPK_LOCAL && idx_op.v.local != base_local &&
   1342         api_coalesce_on(g) && api_temp_dead(g, idx_op.v.local)) {
   1343       ir = idx_op.v.local; /* reuse the dead transient; no copy */
   1344       log2_scale = (u8)lg2;
   1345     } else {
   1346       ir = api_alloc_temp_local(g, idx_ty);
   1347       if (lg2 >= 0) {
   1348         T->copy(T, api_op_local(ir, idx_ty), idx_op);
   1349         log2_scale = (u8)lg2;
   1350       } else {
   1351         T->binop(T, BO_IMUL, api_op_local(ir, idx_ty), idx_op,
   1352                  api_op_imm((i64)elemsz, idx_ty));
   1353         log2_scale = 0;
   1354       }
   1355     }
   1356     api_release(g, &base);
   1357     api_release(g, &idx);
   1358     api_push(g, api_make_lv(api_op_indirect_indexed(base_local, ir, log2_scale,
   1359                                                     place_offset, elem_ty),
   1360                             elem_ty));
   1361   }
   1362 }
   1363 
   1364 void kit_cg_elem(KitCg* g, int64_t offset) { api_cg_elem(g, 0u, offset); }
   1365 
   1366 void kit_cg_elem_scaled(KitCg* g, uint32_t elem_size, int64_t offset) {
   1367   api_cg_elem(g, elem_size, offset);
   1368 }
   1369 
   1370 static void api_cg_field_at_place(KitCg* g, ApiSValue base,
   1371                                   int64_t field_offset, KitCgTypeId field_ty) {
   1372   CgTarget* T;
   1373   KitCgTypeId base_ty;
   1374   KitCgTypeId base_ptr_ty;
   1375   Operand result;
   1376   if (!g) return;
   1377   T = g->target;
   1378   api_ensure_local(g, &base);
   1379   CG_REQUIRE(g, api_is_lvalue_sv(&base),
   1380              "KitCg: field_at requires a place; deref a pointer first");
   1381   field_ty = resolve_type(g->c, field_ty);
   1382   CG_REQUIRE(g, field_ty, "KitCg: field_at has invalid field type");
   1383   if (api_unevaluated(g)) {
   1384     api_release(g, &base);
   1385     api_push(g, api_uneval_place(g, field_ty));
   1386     api_const_set_top(g, api_const_unknown(field_ty));
   1387     return;
   1388   }
   1389   base_ty = api_sv_type(&base);
   1390   if (!base_ty) base_ty = builtin_id(KIT_CG_BUILTIN_VOID);
   1391   base_ptr_ty = cg_type_ptr_to(g->c, base_ty);
   1392   if (base.op.kind == OPK_GLOBAL) {
   1393     i64 addend;
   1394     if (!__builtin_add_overflow(base.op.v.global.addend, field_offset,
   1395                                 &addend)) {
   1396       result = api_op_global(base.op.v.global.sym, addend, field_ty);
   1397       api_release(g, &base);
   1398       api_push(g, api_make_lv(result, field_ty));
   1399       return;
   1400     }
   1401   } else if (base.op.kind == OPK_INDIRECT && field_offset >= INT32_MIN &&
   1402              field_offset <= INT32_MAX) {
   1403     i32 ofs;
   1404     if (!__builtin_add_overflow(base.op.v.ind.ofs, (i32)field_offset, &ofs)) {
   1405       /* Fold the field offset into the displacement, preserving any index/scale
   1406        * a preceding `elem` left so `p[i].f` stays one
   1407        * [base+index*scale+off]. */
   1408       result = api_op_indirect_indexed(base.op.v.ind.base, base.op.v.ind.index,
   1409                                        base.op.v.ind.log2_scale, ofs, field_ty);
   1410       api_release(g, &base);
   1411       api_push(g, api_make_lv(result, field_ty));
   1412       return;
   1413     }
   1414   }
   1415   {
   1416     Operand base_addr = api_lvalue_addr(g, &base, base_ptr_ty);
   1417     if (field_offset == 0) {
   1418       result = base_addr;
   1419     } else {
   1420       CGLocal fr = api_alloc_temp_local(g, base_ptr_ty);
   1421       result = api_op_local(fr, base_ptr_ty);
   1422       T->binop(T, BO_IADD, result, base_addr,
   1423                api_op_imm(field_offset, base_ptr_ty));
   1424     }
   1425     api_release(g, &base);
   1426     api_push(
   1427         g, api_make_lv(api_op_indirect(result.v.local, 0, field_ty), field_ty));
   1428   }
   1429 }
   1430 
   1431 void kit_cg_field_at(KitCg* g, int64_t byte_offset, KitCgTypeId field_type) {
   1432   ApiSValue base;
   1433   if (!g) return;
   1434   base = api_pop(g);
   1435   api_cg_field_at_place(g, base, byte_offset, field_type);
   1436 }
   1437 
   1438 void kit_cg_field(KitCg* g, uint32_t field_index) {
   1439   ApiSValue base;
   1440   KitCgTypeId rec_ty;
   1441   KitCgTypeId base_ty;
   1442   KitCgTypeId field_ty;
   1443   KitCgTypeId rec_ptr_ty;
   1444   const CgType* rec_info;
   1445   const ABIRecordLayout* layout;
   1446   u32 field_offset;
   1447   if (!g) return;
   1448   base = api_pop(g);
   1449   api_ensure_local(g, &base);
   1450   base_ty = api_sv_type(&base);
   1451   CG_REQUIRE(g, api_is_lvalue_sv(&base),
   1452              "KitCg: field requires a record place; deref a pointer first");
   1453   rec_ty = base_ty;
   1454   rec_ptr_ty = cg_type_ptr_to(g->c, rec_ty);
   1455   layout = abi_cg_record_layout(g->c->abi, rec_ty);
   1456   CG_REQUIRE(g, layout && field_index < layout->nfields,
   1457              "KitCg: invalid field index");
   1458   rec_info = cg_type_get(g->c, rec_ty);
   1459   CG_REQUIRE(g,
   1460              rec_info && rec_info->kind == KIT_CG_TYPE_RECORD &&
   1461                  field_index < rec_info->record.nfields,
   1462              "KitCg: invalid record base");
   1463   field_ty = rec_info->record.fields[field_index].type;
   1464   field_offset = layout->fields[field_index].offset;
   1465   if (api_unevaluated(g)) {
   1466     ApiSValue sv = api_uneval_place(g, field_ty);
   1467     if (layout->fields[field_index].bit_width != 0 ||
   1468         (rec_info->record.fields[field_index].flags & KIT_CG_FIELD_BITFIELD) !=
   1469             0) {
   1470       sv.bitfield.bit_offset = layout->fields[field_index].bit_offset;
   1471       sv.bitfield.bit_width = layout->fields[field_index].bit_width;
   1472       sv.bitfield.bit_storage_size = layout->fields[field_index].storage_size;
   1473       sv.bitfield.bit_signed =
   1474           rec_info->record.fields[field_index].bit_signed ? 1u : 0u;
   1475     }
   1476     api_release(g, &base);
   1477     api_push(g, sv);
   1478     api_const_set_top(g, api_const_unknown(field_ty));
   1479     return;
   1480   }
   1481   if (layout->fields[field_index].bit_width != 0 ||
   1482       (rec_info->record.fields[field_index].flags & KIT_CG_FIELD_BITFIELD) !=
   1483           0) {
   1484     Operand base_addr;
   1485     ApiSValue sv;
   1486     CG_REQUIRE(g, layout->fields[field_index].bit_width != 0,
   1487                "KitCg: zero-width bit-field access");
   1488     /* Project to a bit-field PLACE: the place addresses the enclosing storage
   1489      * unit and carries the bit-field geometry from the record layout. A plain
   1490      * load/store on this place performs the extract/insert; there is no
   1491      * separate bit-field memop and no bit-field rider on KitCgMemAccess. */
   1492     base_addr = api_lvalue_addr(g, &base, rec_ptr_ty);
   1493     sv = api_make_lv(base_addr, field_ty);
   1494     sv.bitfield.bit_offset = layout->fields[field_index].bit_offset;
   1495     sv.bitfield.bit_width = layout->fields[field_index].bit_width;
   1496     sv.bitfield.bit_storage_size = layout->fields[field_index].storage_size;
   1497     sv.bitfield.bit_signed =
   1498         rec_info->record.fields[field_index].bit_signed ? 1u : 0u;
   1499     api_release(g, &base);
   1500     api_push(g, sv);
   1501     return;
   1502   }
   1503   api_cg_field_at_place(g, base, (int64_t)field_offset, field_ty);
   1504 }
   1505 
   1506 void kit_cg_field_bits(KitCg* g, uint16_t bit_offset, uint16_t bit_width,
   1507                        uint32_t bit_storage_size, int bit_signed) {
   1508   ApiSValue* top;
   1509   if (!g || g->sp == 0) return;
   1510   top = &g->stack[g->sp - 1u];
   1511   CG_REQUIRE(g, api_is_lvalue_sv(top),
   1512              "KitCg: field_bits requires a place destination");
   1513   top->bitfield.bit_offset = bit_offset;
   1514   top->bitfield.bit_width = bit_width;
   1515   top->bitfield.bit_storage_size = bit_storage_size;
   1516   top->bitfield.bit_signed = bit_signed ? 1u : 0u;
   1517 }
   1518 
   1519 /* ============================================================
   1520  * Calls / return
   1521  * ============================================================ */
   1522 
   1523 /* Shared scaffolding for kit_cg_call / kit_cg_call_symbol. The two
   1524  * public entry points differ only in how the callee is obtained and in
   1525  * their pre-call stack-depth check; everything else (arg packaging, return
   1526  * storage allocation, post-call release, result push) is identical. These
   1527  * helpers carry the common shape and are the natural targets for any future
   1528  * change that wants to vary call-shape policy (e.g. an ABI-driven storage
   1529  * decision). */