kit

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

control.c (37245B)


      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   return (KitCgLabel)g->target->label_new(g->target);
      7 }
      8 
      9 void kit_cg_label_place(KitCg* g, KitCgLabel label) {
     10   if (!g) return;
     11   api_local_const_control_boundary(g);
     12   g->target->label_place(g->target, (Label)label);
     13 }
     14 
     15 void kit_cg_jump(KitCg* g, KitCgLabel label) {
     16   if (!g) return;
     17   api_local_const_control_boundary(g);
     18   g->target->jump(g->target, (Label)label);
     19 }
     20 
     21 void api_branch_if(KitCg* g, ApiSValue* v, int branch_when_true, Label label) {
     22   CgTarget* T;
     23   KitCgTypeId ty;
     24   if (!g) return;
     25   api_local_const_control_boundary(g);
     26   T = g->target;
     27   ty = v->type ? v->type : builtin_id(KIT_CG_BUILTIN_I32);
     28   if (v->op.kind == OPK_IMM && v->kind == SV_OPERAND) {
     29     if ((v->op.v.imm != 0) == !!branch_when_true) T->jump(T, label);
     30     api_release(g, v);
     31     return;
     32   }
     33   if (v->kind == SV_CMP) {
     34     CmpOp op = branch_when_true ? v->delayed.cmp.op
     35                                 : api_invert_cmp(v->delayed.cmp.op);
     36     T->cmp_branch(T, op, v->delayed.cmp.a, v->delayed.cmp.b, label);
     37     api_release(g, v);
     38     return;
     39   }
     40   if (api_is_i128_type(g->c, ty)) {
     41     KitCgTypeId i128 = builtin_id(KIT_CG_BUILTIN_I128);
     42     KitCgTypeId i32 = builtin_id(KIT_CG_BUILTIN_I32);
     43     KitCgTypeId ps[2] = {i128, i128};
     44     ApiSValue args[2];
     45     ApiSValue cmp;
     46     args[0] = *v;
     47     args[1] = api_make_sv(api_op_imm(0, i128), i128);
     48     api_runtime_call_values(g, "__kit_ucmpti2", i32, ps, 2, args);
     49     cmp = api_pop(g);
     50     api_branch_if(g, &cmp, branch_when_true, label);
     51     return;
     52   }
     53   /* Split-lane 8-byte truthiness: branch on (lo | hi) != 0. The value is
     54    * memory-resident, so a single-slot CMP_NE-vs-zero would only see the low
     55    * word; OR the two lanes into an i32 first. */
     56   if (api_is_wide8_scalar_type(g->c, ty)) {
     57     KitCgTypeId i32 = builtin_id(KIT_CG_BUILTIN_I32);
     58     Operand orl = api_wide8_or_lanes(g, v, ty);
     59     Operand zero = api_op_imm(0, i32);
     60     T->cmp_branch(T, branch_when_true ? CMP_NE : CMP_EQ, orl, zero, label);
     61     api_release(g, v);
     62     return;
     63   }
     64   {
     65     Operand a = api_force_local(g, v, ty);
     66     Operand zero = api_op_imm(0, ty);
     67     T->cmp_branch(T, branch_when_true ? CMP_NE : CMP_EQ, a, zero, label);
     68     api_release(g, v);
     69   }
     70 }
     71 
     72 void kit_cg_branch_true(KitCg* g, KitCgLabel label) {
     73   ApiSValue v;
     74   if (!g) return;
     75   v = api_pop(g);
     76   api_branch_if(g, &v, 1, (Label)label);
     77 }
     78 
     79 void kit_cg_branch_false(KitCg* g, KitCgLabel label) {
     80   ApiSValue v;
     81   if (!g) return;
     82   v = api_pop(g);
     83   api_branch_if(g, &v, 0, (Label)label);
     84 }
     85 
     86 void cg_lower_switch_default(CgTarget* t, const CGSwitchDesc* d) {
     87   /* Cmp-and-branch chain: one cmp_branch per case, then jump to
     88    * default (or fall through if LABEL_NONE). The fallback shape; the
     89    * frontend-facing kit_cg_switch picks chain vs. jump-table up
     90    * front (see cg_plan_switch) and routes here only for the chain
     91    * case. Backend overrides (the C target's switch_) and opt's IR
     92    * replay both reach this from outside the cg API, so the lowering
     93    * stays target-only and uses just cmp_branch + jump. */
     94   for (u32 i = 0; i < d->ncases; ++i) {
     95     Operand imm = api_op_imm((i64)d->cases[i].value, d->selector_type);
     96     t->cmp_branch(t, CMP_EQ, d->selector, imm, d->cases[i].label);
     97   }
     98   if (d->default_label != LABEL_NONE) {
     99     t->jump(t, d->default_label);
    100   }
    101 }
    102 
    103 /* Density / sizing thresholds for the O1 jump-table heuristic. At O0
    104  * we only honor an explicit KIT_CG_SWITCH_JUMP_TABLE hint — the policy
    105  * is single-pass and tied to frontend intent rather than analysis. O1
    106  * runs a linear scan over the case list and picks a single global table
    107  * when the cases are dense enough; clustering / multi-table layouts are
    108  * out of scope here. */
    109 #define CG_SWITCH_TABLE_MIN_CASES_O1 4u
    110 #define CG_SWITCH_TABLE_MAX_SPAN_O1 4096u
    111 #define CG_SWITCH_TABLE_DENSITY_RECIP_O1 4u /* ncases * recip >= span */
    112 
    113 typedef enum CGSwitchPlanKind {
    114   CG_SWITCH_PLAN_CHAIN,
    115   CG_SWITCH_PLAN_TABLE,
    116 } CGSwitchPlanKind;
    117 
    118 typedef struct CGSwitchPlan {
    119   CGSwitchPlanKind kind;
    120   i64 vmin;
    121   u64 span;
    122 } CGSwitchPlan;
    123 
    124 /* Single pass over the cases array: derive (vmin, vmax, span) in the
    125  * selector type's signed interpretation. Frontends store case values as
    126  * the u64 bit pattern of an i64 sign-extended from the selector's
    127  * width, so api_sign_extend_width recovers the source ordering for
    128  * both signed and unsigned selectors that fit in i64. Returns 0 if the
    129  * selector type is unusable for our policy (>64 bits) or if span
    130  * overflows u64. */
    131 static int cg_switch_extents(Compiler* c, const CGSwitchDesc* d, i64* out_vmin,
    132                              u64* out_span) {
    133   u32 width;
    134   i64 vmin;
    135   i64 vmax;
    136   u32 i;
    137   width = kit_cg_type_int_width((KitCompiler*)c, d->selector_type);
    138   if (!width || width > 64u) return 0;
    139   vmin = INT64_MAX;
    140   vmax = INT64_MIN;
    141   for (i = 0; i < d->ncases; ++i) {
    142     i64 vi = (width == 64u) ? (i64)d->cases[i].value
    143                             : api_sign_extend_width(d->cases[i].value, width);
    144     if (vi < vmin) vmin = vi;
    145     if (vi > vmax) vmax = vi;
    146   }
    147   if (vmax < vmin) return 0;
    148   {
    149     u64 delta = (u64)vmax - (u64)vmin;
    150     if (delta == UINT64_MAX) return 0;
    151     *out_span = delta + 1u;
    152   }
    153   *out_vmin = vmin;
    154   return 1;
    155 }
    156 
    157 static CGSwitchPlan cg_plan_switch(KitCg* g, const CGSwitchDesc* d) {
    158   CGSwitchPlan plan;
    159   plan.kind = CG_SWITCH_PLAN_CHAIN;
    160   plan.vmin = 0;
    161   plan.span = 0;
    162   if (d->ncases == 0) return plan;
    163   if (d->default_label == LABEL_NONE) return plan;
    164   if (d->hint == KIT_CG_SWITCH_BRANCH_CHAIN) return plan;
    165   if (!cg_switch_extents(g->c, d, &plan.vmin, &plan.span)) return plan;
    166   if (d->hint == KIT_CG_SWITCH_JUMP_TABLE) {
    167     /* Frontend explicitly opted in. Honor unless the span is wildly
    168      * out of bounds — a forced hint shouldn't blow up code size on a
    169      * misshapen switch. */
    170     if (plan.span > CG_SWITCH_TABLE_MAX_SPAN_O1) return plan;
    171     plan.kind = CG_SWITCH_PLAN_TABLE;
    172     return plan;
    173   }
    174   /* TARGET_DEFAULT: O0 keeps the chain; O1+ runs the density check. */
    175   if (d->opt_level == 0) return plan;
    176   if (d->opt_level >= 2 && plan.span != d->ncases) return plan;
    177   if (d->ncases < CG_SWITCH_TABLE_MIN_CASES_O1) return plan;
    178   if (plan.span > CG_SWITCH_TABLE_MAX_SPAN_O1) return plan;
    179   if (plan.span > (u64)d->ncases * CG_SWITCH_TABLE_DENSITY_RECIP_O1)
    180     return plan;
    181   plan.kind = CG_SWITCH_PLAN_TABLE;
    182   return plan;
    183 }
    184 
    185 /* Emit a dense jump-table dispatch using cg-API ops. The selector value
    186  * is still on the value stack on entry. Routing through the cg API
    187  * means the same primitives work for direct CG (lowered to machine
    188  * ops) and for the opt wrapper (recorded as IR_BINOP / IR_CMP_BRANCH /
    189  * IR_LOAD / IR_INDIRECT_BRANCH, which pass_emit already lowers
    190  * natively without ever materializing IR_SWITCH). */
    191 static void cg_emit_switch_table(KitCg* g, const CGSwitchDesc* d,
    192                                  const CGSwitchPlan* plan) {
    193   Compiler* c;
    194   Heap* h;
    195   KitCgTypeId sel_ty;
    196   u32 sel_w;
    197   KitCgTypeId i64_ty;
    198   KitCgTypeId void_ptr_ty;
    199   KitCgTypeId arr_ty;
    200   Label* labels;
    201   KitCgLabel* targets;
    202   ObjSymId table_sym;
    203   KitCgDecl decl;
    204   KitCgMemAccess acc;
    205   u64 i;
    206   u32 width;
    207   c = g->c;
    208   h = (Heap*)c->ctx->heap;
    209   sel_ty = d->selector_type;
    210   sel_w = kit_cg_type_int_width((KitCompiler*)c, sel_ty);
    211   i64_ty = builtin_id(KIT_CG_BUILTIN_I64);
    212   void_ptr_ty = cg_type_ptr_to(c, builtin_id(KIT_CG_BUILTIN_VOID));
    213 
    214   /* 1. Keep the original selector, then compute idx = sel - vmin
    215    *    (selector_type wraparound is what we want; the unsigned bounds
    216    *    check below catches both negative-underflow and positive-overflow
    217    *    cases). The selector copy lets us recompute idx after the bounds
    218    *    branch instead of carrying a duplicated arithmetic value across
    219    *    control flow. */
    220   kit_cg_dup(g); /* [sel, sel] */
    221   kit_cg_push_int(g, (uint64_t)plan->vmin, sel_ty);
    222   kit_cg_int_binop(g, KIT_CG_INT_SUB, 0); /* [sel, idx] */
    223 
    224   /* 2. Bounds check: branch to default when idx u> span-1. */
    225   kit_cg_dup(g); /* [sel, idx, idx] */
    226   kit_cg_push_int(g, plan->span - 1u, sel_ty);
    227   kit_cg_int_cmp(g, KIT_CG_INT_GT_U);                  /* [sel, idx, cond] */
    228   kit_cg_branch_true(g, (KitCgLabel)d->default_label); /* [sel, idx] */
    229 
    230   /* 3. Recompute idx from the preserved selector for table addressing. */
    231   kit_cg_drop(g); /* [sel] */
    232   kit_cg_push_int(g, (uint64_t)plan->vmin, sel_ty);
    233   kit_cg_int_binop(g, KIT_CG_INT_SUB, 0); /* [idx] */
    234 
    235   /* 4. Widen idx to i64 so the subsequent index multiply runs at
    236    *    pointer width regardless of selector signedness. The bounds
    237    *    check above already established 0 <= idx < span, so zext is
    238    *    value-preserving. */
    239   if (sel_w < 64u) {
    240     kit_cg_zext(g, i64_ty);
    241   }
    242 
    243   /* 5. Build the dense label[] table and emit the rodata table. */
    244   labels =
    245       (Label*)h->alloc(h, (size_t)plan->span * sizeof *labels, _Alignof(Label));
    246   if (!labels) compiler_panic(c, g->cur_loc, "kit_cg_switch: oom");
    247   for (i = 0; i < plan->span; ++i) labels[i] = d->default_label;
    248   width = sel_w;
    249   for (i = 0; i < d->ncases; ++i) {
    250     i64 vi = (width == 64u) ? (i64)d->cases[i].value
    251                             : api_sign_extend_width(d->cases[i].value, width);
    252     u64 table_index = (u64)(vi - plan->vmin);
    253     labels[table_index] = d->cases[i].label;
    254   }
    255   table_sym = api_emit_label_table(g, labels, (u32)plan->span);
    256   h->free(h, labels, (size_t)plan->span * sizeof *labels);
    257   if (table_sym == OBJ_SYM_NONE) {
    258     /* api_emit_label_table panics on real failure; this only fires if
    259      * a future caller asks for a 0-entry table (which cg_plan_switch
    260      * already rules out). */
    261     compiler_panic(c, g->cur_loc, "kit_cg_switch: table emission failed");
    262     return;
    263   }
    264   arr_ty = kit_cg_type_array((KitCompiler*)c, void_ptr_ty, plan->span);
    265   memset(&decl, 0, sizeof decl);
    266   decl.kind = KIT_CG_DECL_OBJECT;
    267   decl.sym.bind = KIT_SB_LOCAL;
    268   decl.sym.visibility = KIT_CG_VIS_DEFAULT;
    269   decl.as.object.flags = KIT_CG_OBJ_READONLY;
    270   api_remember_sym(g, table_sym, arr_ty, decl);
    271 
    272   /* 6. Load table[idx]: bitcast the table pointer to a pointer-to-element so
    273    *    the element place carries the pointer-size scale, put the index on top,
    274    *    project the element place, then load it. */
    275   kit_cg_push_symbol_addr(g, (KitCgSym)table_sym, 0); /* [idx, &table] */
    276   kit_cg_bitcast(g, cg_type_ptr_to(c, void_ptr_ty));  /* &table : void** */
    277   kit_cg_swap(g);                                     /* [&table, idx] */
    278   kit_cg_elem(g, 0);                                  /* [&table[idx]] */
    279   memset(&acc, 0, sizeof acc);
    280   acc.type = void_ptr_ty;
    281   acc.align = (uint32_t)c->target.ptr_align;
    282   kit_cg_load(g, acc); /* [label_addr] */
    283 
    284   /* 7. Indirect branch with the full closed target set (every case +
    285    *    default), so backends doing branch-target hardening (BTI/IBT/CFG)
    286    *    can stamp landing pads on every reachable label. */
    287   targets = (KitCgLabel*)h->alloc(h, (d->ncases + 1u) * sizeof *targets,
    288                                   _Alignof(KitCgLabel));
    289   if (!targets) compiler_panic(c, g->cur_loc, "kit_cg_switch: oom");
    290   for (i = 0; i < d->ncases; ++i) {
    291     targets[i] = (KitCgLabel)d->cases[i].label;
    292   }
    293   targets[d->ncases] = (KitCgLabel)d->default_label;
    294   kit_cg_computed_goto(g, targets, d->ncases + 1u);
    295   h->free(h, targets, (d->ncases + 1u) * sizeof *targets);
    296 }
    297 
    298 void kit_cg_switch(KitCg* g, KitCgSwitch sw) {
    299   ApiSValue selector;
    300   CGSwitchDesc desc;
    301   Heap* h;
    302   CGSwitchCase* cases = NULL;
    303   CGSwitchPlan plan;
    304   int native_switch_override;
    305   if (!g) return;
    306   if (g->sp == 0) return;
    307   api_local_const_control_boundary(g);
    308   memset(&desc, 0, sizeof desc);
    309   desc.selector_type = resolve_type(g->c, sw.selector_type);
    310   if (!desc.selector_type) {
    311     ApiSValue tmp = g->stack[g->sp - 1u];
    312     desc.selector_type = api_sv_type(&tmp);
    313   }
    314   desc.default_label = (Label)sw.default_label;
    315   desc.ncases = sw.ncases;
    316   desc.hint = (u8)sw.hint;
    317   desc.opt_level = (u8)g->opt_level;
    318   if (sw.ncases) {
    319     h = g->c->ctx->heap;
    320     cases = (CGSwitchCase*)h->alloc(h, sw.ncases * sizeof(CGSwitchCase),
    321                                     _Alignof(CGSwitchCase));
    322     if (!cases)
    323       compiler_panic(g->c, g->cur_loc, "kit_cg_switch: out of memory");
    324     for (u32 i = 0; i < sw.ncases; ++i) {
    325       cases[i].value = sw.cases[i].value;
    326       cases[i].label = (Label)sw.cases[i].label;
    327     }
    328     desc.cases = cases;
    329   }
    330 
    331   /* Direct O0 targets may override switch_ for a single-pass branch-chain
    332    * lowering. Still honor an explicit jump-table hint so tests and frontends
    333    * can exercise the semantic label-table path without enabling O1. */
    334   native_switch_override = (g->target->switch_ && g->opt_level == 0 &&
    335                             desc.hint != KIT_CG_SWITCH_JUMP_TABLE);
    336   plan = native_switch_override ? (CGSwitchPlan){CG_SWITCH_PLAN_CHAIN, 0, 0}
    337                                 : cg_plan_switch(g, &desc);
    338 
    339   /* The label-table lowering materializes a rodata table of code-label
    340    * addresses and an indirect branch. Targets that can't express that (Wasm)
    341    * realize dense dispatch through their switch_ hook (br_table) instead, so
    342    * hand the plan—hint and all—to switch_ rather than the table path. */
    343   if (plan.kind == CG_SWITCH_PLAN_TABLE && g->target->switch_ &&
    344       g->target->supports_label_table &&
    345       !g->target->supports_label_table(g->target)) {
    346     plan.kind = CG_SWITCH_PLAN_CHAIN;
    347   }
    348 
    349   if (plan.kind == CG_SWITCH_PLAN_TABLE) {
    350     /* Selector stays on the value stack; cg_emit_switch_table consumes
    351      * it via cg-API ops so the path also records cleanly under opt. */
    352     metrics_count(g->c, "cg.switch.table", 1);
    353     cg_emit_switch_table(g, &desc, &plan);
    354   } else {
    355     metrics_count(g->c, "cg.switch.chain", 1);
    356     selector = api_pop(g);
    357     desc.selector =
    358         api_force_local_unless_imm(g, &selector, desc.selector_type);
    359     if (g->target->switch_) {
    360       g->target->switch_(g->target, &desc);
    361     } else {
    362       cg_lower_switch_default(g->target, &desc);
    363     }
    364     api_release(g, &selector);
    365   }
    366   if (cases) {
    367     h = g->c->ctx->heap;
    368     h->free(h, cases, sw.ncases * sizeof(CGSwitchCase));
    369   }
    370 }
    371 
    372 void kit_cg_push_label_addr(KitCg* g, KitCgLabel label, KitCgTypeId ptr_type) {
    373   KitCgTypeId ty;
    374   CGLocal r;
    375   Operand dst;
    376   if (!g) return;
    377   ty = resolve_type(g->c, ptr_type);
    378   if (!ty) ty = cg_type_ptr_to(g->c, builtin_id(KIT_CG_BUILTIN_VOID));
    379   r = api_alloc_temp_local(g, ty);
    380   dst = api_op_local(r, ty);
    381   g->target->load_label_addr(g->target, dst, (Label)label);
    382   api_push(g, api_make_sv(dst, ty));
    383 }
    384 
    385 void kit_cg_computed_goto(KitCg* g, const KitCgLabel* valid_targets,
    386                           uint32_t ntargets) {
    387   ApiSValue target;
    388   KitCgTypeId target_ty;
    389   Operand target_op;
    390   if (!g) return;
    391   if (!valid_targets || ntargets == 0) {
    392     compiler_panic(g->c, g->cur_loc,
    393                    "kit_cg_computed_goto: valid_targets must be non-empty");
    394     return;
    395   }
    396   api_local_const_control_boundary(g);
    397   target = api_pop(g);
    398   target_ty = api_sv_type(&target);
    399   target_op = api_force_local(g, &target, target_ty);
    400   g->target->indirect_branch(g->target, target_op, (const Label*)valid_targets,
    401                              ntargets);
    402   api_release(g, &target);
    403 }
    404 
    405 void kit_cg_unreachable(KitCg* g) {
    406   if (!g) return;
    407   api_local_const_control_boundary(g);
    408   g->target->unreachable(g->target);
    409 }
    410 
    411 /* ============================================================
    412  * Scopes / structured control flow
    413  * ============================================================ */
    414 
    415 KitCgScope api_scope_handle(u32 idx, u32 generation) {
    416   return (KitCgScope)((generation << 8) | ((idx + 1u) & 0xffu));
    417 }
    418 
    419 ApiCgScope* api_scope_from_handle(KitCg* g, KitCgScope scope, int require_top,
    420                                   const char* who) {
    421   u32 scope_index;
    422   u32 generation;
    423   ApiCgScope* s;
    424   if (!g || scope == 0) return NULL;
    425   scope_index = ((u32)scope & 0xffu);
    426   generation = ((u32)scope >> 8);
    427   if (scope_index == 0 || scope_index > API_CG_MAX_SCOPES) {
    428     compiler_panic(g->c, g->cur_loc, "%.*s: invalid scope handle",
    429                    SLICE_ARG(slice_from_cstr(who)));
    430     return NULL;
    431   }
    432   scope_index--;
    433   if (scope_index >= g->nscopes) {
    434     compiler_panic(g->c, g->cur_loc, "%.*s: stale scope handle",
    435                    SLICE_ARG(slice_from_cstr(who)));
    436     return NULL;
    437   }
    438   if (require_top && scope_index + 1u != g->nscopes) {
    439     compiler_panic(g->c, g->cur_loc, "%.*s: non-LIFO scope end",
    440                    SLICE_ARG(slice_from_cstr(who)));
    441     return NULL;
    442   }
    443   s = &g->scopes[scope_index];
    444   if (!s->active || s->generation != generation) {
    445     compiler_panic(g->c, g->cur_loc, "%.*s: stale scope handle",
    446                    SLICE_ARG(slice_from_cstr(who)));
    447     return NULL;
    448   }
    449   return s;
    450 }
    451 
    452 int api_scope_has_result(const ApiCgScope* s) {
    453   return s->result_type != KIT_CG_TYPE_NONE;
    454 }
    455 
    456 void api_scope_store_result(KitCg* g, ApiCgScope* s, ApiSValue* result) {
    457   Operand dst;
    458   Operand src;
    459   if (!api_scope_has_result(s)) return;
    460   dst = api_op_local(s->result_local, s->result_type);
    461   src = api_sv_op_is_local_or_imm(result)
    462             ? result->op
    463             : api_force_local(g, result, s->result_type);
    464   g->target->store(g->target, dst, src,
    465                    api_mem_for_lvalue(g, &dst, s->result_type));
    466   api_release(g, result);
    467 }
    468 
    469 void api_scope_push_result(KitCg* g, ApiCgScope* s) {
    470   Operand dst;
    471   Operand src;
    472   CGLocal r;
    473   if (!api_scope_has_result(s)) return;
    474   r = api_alloc_temp_local(g, s->result_type);
    475   dst = api_op_local(r, s->result_type);
    476   src = api_op_local(s->result_local, s->result_type);
    477   g->target->load(g->target, dst, src,
    478                   api_mem_for_lvalue(g, &src, s->result_type));
    479   api_push(g, api_make_sv(dst, s->result_type));
    480 }
    481 
    482 static KitCgScope api_scope_begin_kind(KitCg* g, u8 kind,
    483                                        KitCgTypeId result_type) {
    484   Label break_lbl, cont_lbl;
    485   CGScopeDesc d;
    486   ApiCgScope* s;
    487   CGScope target_scope;
    488   u32 idx;
    489   if (!g) return 0;
    490   break_lbl = g->target->label_new(g->target);
    491   cont_lbl =
    492       (kind == SCOPE_LOOP) ? g->target->label_new(g->target) : LABEL_NONE;
    493   api_local_const_control_boundary(g);
    494   if (cont_lbl != LABEL_NONE) g->target->label_place(g->target, cont_lbl);
    495 
    496   if (g->nscopes >= API_CG_MAX_SCOPES) {
    497     compiler_panic(g->c, g->cur_loc, "KitCg: too many nested scopes");
    498     return 0;
    499   }
    500   idx = g->nscopes;
    501   s = &g->scopes[idx];
    502   s->break_lbl = break_lbl;
    503   s->continue_lbl = cont_lbl;
    504   s->result_type = resolve_type(g->c, result_type);
    505   s->generation = ++g->scope_generation;
    506   if (s->generation == 0) s->generation = ++g->scope_generation;
    507   s->active = 1;
    508   g->nscopes++;
    509 
    510   memset(&d, 0, sizeof d);
    511   d.kind = kind;
    512   d.break_label = break_lbl;
    513   d.continue_label = cont_lbl;
    514   d.result_type = s->result_type;
    515   target_scope = g->target->scope_begin(g->target, &d);
    516   s->target_scope = target_scope;
    517   s->result_local = CG_LOCAL_NONE;
    518   if (api_scope_has_result(s)) {
    519     CGLocalDesc ld;
    520     memset(&ld, 0, sizeof ld);
    521     ld.type = s->result_type;
    522     ld.size = abi_cg_sizeof(g->c->abi, result_type);
    523     ld.align = abi_cg_alignof(g->c->abi, result_type);
    524     ld.flags = CG_LOCAL_MEMORY_REQUIRED;
    525     s->result_local = g->target->local(g->target, &ld);
    526   }
    527 
    528   return api_scope_handle(idx, s->generation);
    529 }
    530 
    531 KitCgScope kit_cg_scope_begin(KitCg* g, KitCgTypeId result_type) {
    532   return api_scope_begin_kind(g, (u8)SCOPE_LOOP, result_type);
    533 }
    534 
    535 KitCgScope kit_cg_block_begin(KitCg* g, KitCgTypeId result_type) {
    536   return api_scope_begin_kind(g, (u8)SCOPE_BLOCK, result_type);
    537 }
    538 
    539 KitCgLabel kit_cg_scope_break_label(KitCg* g, KitCgScope scope) {
    540   ApiCgScope* s =
    541       api_scope_from_handle(g, scope, 0, "KitCg: scope_break_label");
    542   return s ? (KitCgLabel)s->break_lbl : (KitCgLabel)0;
    543 }
    544 
    545 KitCgLabel kit_cg_scope_continue_label(KitCg* g, KitCgScope scope) {
    546   ApiCgScope* s =
    547       api_scope_from_handle(g, scope, 0, "KitCg: scope_continue_label");
    548   return s ? (KitCgLabel)s->continue_lbl : (KitCgLabel)0;
    549 }
    550 
    551 void kit_cg_scope_end(KitCg* g, KitCgScope scope) {
    552   ApiCgScope* s = api_scope_from_handle(g, scope, 1, "KitCg: scope_end");
    553   if (!s) return;
    554   if (api_scope_has_result(s)) {
    555     ApiSValue result = api_pop(g);
    556     api_scope_store_result(g, s, &result);
    557   }
    558   api_local_const_control_boundary(g);
    559   g->target->label_place(g->target, s->break_lbl);
    560   g->target->scope_end(g->target, s->target_scope);
    561   api_scope_push_result(g, s);
    562   s->active = 0;
    563   g->nscopes--;
    564 }
    565 
    566 void kit_cg_break(KitCg* g, KitCgScope scope) {
    567   ApiCgScope* s = api_scope_from_handle(g, scope, 0, "KitCg: break");
    568   if (!s) return;
    569   if (api_scope_has_result(s)) {
    570     ApiSValue result = api_pop(g);
    571     api_scope_store_result(g, s, &result);
    572   }
    573   api_local_const_control_boundary(g);
    574   g->target->jump(g->target, s->break_lbl);
    575 }
    576 
    577 void kit_cg_break_true(KitCg* g, KitCgScope scope) {
    578   ApiCgScope* s;
    579   ApiSValue cond;
    580   if (!g || scope == 0) return;
    581   s = api_scope_from_handle(g, scope, 0, "KitCg: break_true");
    582   if (!s) return;
    583   cond = api_pop(g);
    584 
    585   if (api_scope_has_result(s)) {
    586     ApiSValue result = api_pop(g);
    587     if (cond.kind == SV_OPERAND && cond.op.kind == OPK_IMM) {
    588       if (cond.op.v.imm != 0) {
    589         api_scope_store_result(g, s, &result);
    590         api_local_const_control_boundary(g);
    591         g->target->jump(g->target, s->break_lbl);
    592       } else {
    593         api_release(g, &result);
    594       }
    595       api_release(g, &cond);
    596     } else {
    597       Label skip = g->target->label_new(g->target);
    598       api_branch_if(g, &cond, 0, skip);
    599       api_scope_store_result(g, s, &result);
    600       api_local_const_control_boundary(g);
    601       g->target->jump(g->target, s->break_lbl);
    602       api_local_const_control_boundary(g);
    603       g->target->label_place(g->target, skip);
    604     }
    605   } else {
    606     api_branch_if(g, &cond, 1, s->break_lbl);
    607   }
    608 }
    609 
    610 void kit_cg_break_false(KitCg* g, KitCgScope scope) {
    611   ApiCgScope* s;
    612   ApiSValue cond;
    613   if (!g || scope == 0) return;
    614   s = api_scope_from_handle(g, scope, 0, "KitCg: break_false");
    615   if (!s) return;
    616   cond = api_pop(g);
    617 
    618   if (api_scope_has_result(s)) {
    619     ApiSValue result = api_pop(g);
    620     if (cond.kind == SV_OPERAND && cond.op.kind == OPK_IMM) {
    621       if (cond.op.v.imm == 0) {
    622         api_scope_store_result(g, s, &result);
    623         api_local_const_control_boundary(g);
    624         g->target->jump(g->target, s->break_lbl);
    625       } else {
    626         api_release(g, &result);
    627       }
    628       api_release(g, &cond);
    629     } else {
    630       Label skip = g->target->label_new(g->target);
    631       api_branch_if(g, &cond, 1, skip);
    632       api_scope_store_result(g, s, &result);
    633       api_local_const_control_boundary(g);
    634       g->target->jump(g->target, s->break_lbl);
    635       api_local_const_control_boundary(g);
    636       g->target->label_place(g->target, skip);
    637     }
    638   } else {
    639     api_branch_if(g, &cond, 0, s->break_lbl);
    640   }
    641 }
    642 
    643 /* continue jumps to the loop header, which a forward-only block scope does not
    644  * have (its continue_lbl is LABEL_NONE). Reject it with a clean diagnostic
    645  * rather than emitting a jump to a nonexistent label. */
    646 static int api_require_loop_scope(KitCg* g, const ApiCgScope* s,
    647                                   const char* op) {
    648   if (s->continue_lbl == LABEL_NONE) {
    649     compiler_panic(g->c, g->cur_loc,
    650                    "KitCg: %s is not valid on a forward-only block scope", op);
    651     return 0;
    652   }
    653   return 1;
    654 }
    655 
    656 void kit_cg_continue(KitCg* g, KitCgScope scope) {
    657   ApiCgScope* s = api_scope_from_handle(g, scope, 0, "KitCg: continue");
    658   if (!s) return;
    659   if (!api_require_loop_scope(g, s, "continue")) return;
    660   api_local_const_control_boundary(g);
    661   g->target->jump(g->target, s->continue_lbl);
    662 }
    663 
    664 void kit_cg_continue_true(KitCg* g, KitCgScope scope) {
    665   ApiCgScope* s;
    666   ApiSValue v;
    667   if (!g || scope == 0) return;
    668   s = api_scope_from_handle(g, scope, 0, "KitCg: continue_true");
    669   if (!s) return;
    670   if (!api_require_loop_scope(g, s, "continue_true")) return;
    671   v = api_pop(g);
    672   api_branch_if(g, &v, 1, s->continue_lbl);
    673 }
    674 
    675 void kit_cg_continue_false(KitCg* g, KitCgScope scope) {
    676   ApiCgScope* s;
    677   ApiSValue v;
    678   if (!g || scope == 0) return;
    679   s = api_scope_from_handle(g, scope, 0, "KitCg: continue_false");
    680   if (!s) return;
    681   if (!api_require_loop_scope(g, s, "continue_false")) return;
    682   v = api_pop(g);
    683   api_branch_if(g, &v, 0, s->continue_lbl);
    684 }
    685 
    686 /* ============================================================
    687  * Dynamic stack allocation / variadics (stubs)
    688  * ============================================================ */
    689 
    690 void kit_cg_alloca(KitCg* g, uint32_t align, KitCgTypeId result_ptr_type) {
    691   ApiSValue sz;
    692   CgTarget* T;
    693   KitCgTypeId pty;
    694   Operand sz_op;
    695   CGLocal rr;
    696   Operand dst;
    697   if (!g) return;
    698   T = g->target;
    699   sz = api_pop(g);
    700   pty = resolve_type(g->c, result_ptr_type);
    701   if (!pty) pty = cg_type_ptr_to(g->c, builtin_id(KIT_CG_BUILTIN_VOID));
    702   sz_op = api_sv_op_is(&sz, OPK_IMM)
    703               ? sz.op
    704               : api_force_local(g, &sz, api_sv_type(&sz));
    705   rr = api_alloc_temp_local(g, pty);
    706   dst = api_op_local(rr, pty);
    707   T->alloca_(T, dst, sz_op, align ? align : 16);
    708   api_release(g, &sz);
    709   api_push(g, api_make_sv(dst, pty));
    710 }
    711 
    712 void kit_cg_vararg_start(KitCg* g) {
    713   ApiSValue ap;
    714   CgTarget* T;
    715   Operand ap_op;
    716   if (!g) return;
    717   T = g->target;
    718   ap = api_pop(g);
    719   ap_op = api_force_local(g, &ap, api_sv_type(&ap));
    720   T->va_start_(T, ap_op);
    721   api_release(g, &ap);
    722 }
    723 
    724 void kit_cg_vararg_next(KitCg* g, KitCgTypeId type) {
    725   ApiSValue ap;
    726   CgTarget* T;
    727   KitCgTypeId ty;
    728   Operand ap_op;
    729   CGLocal rr;
    730   Operand dst;
    731   if (!g) return;
    732   T = g->target;
    733   ty = resolve_type(g->c, type);
    734   if (!ty) return;
    735   ap = api_pop(g);
    736   ap_op = api_force_local(g, &ap, api_sv_type(&ap));
    737   rr = api_alloc_temp_local(g, ty);
    738   dst = api_op_local(rr, ty);
    739   T->va_arg_(T, dst, ap_op, ty);
    740   api_release(g, &ap);
    741   api_push(g, api_make_sv(dst, ty));
    742 }
    743 
    744 void kit_cg_vararg_end(KitCg* g) {
    745   ApiSValue ap;
    746   CgTarget* T;
    747   Operand ap_op;
    748   if (!g) return;
    749   T = g->target;
    750   ap = api_pop(g);
    751   ap_op = api_force_local(g, &ap, api_sv_type(&ap));
    752   T->va_end_(T, ap_op);
    753   api_release(g, &ap);
    754 }
    755 
    756 void kit_cg_vararg_copy(KitCg* g) {
    757   ApiSValue src, dst;
    758   CgTarget* T;
    759   Operand src_op, dst_op;
    760   if (!g) return;
    761   T = g->target;
    762   src = api_pop(g);
    763   dst = api_pop(g);
    764   src_op = api_force_local(g, &src, api_sv_type(&src));
    765   dst_op = api_force_local(g, &dst, api_sv_type(&dst));
    766   T->va_copy_(T, dst_op, src_op);
    767   api_release(g, &src);
    768   api_release(g, &dst);
    769 }
    770 
    771 /* ============================================================
    772  * Memory operations (stubs)
    773  * ============================================================ */
    774 
    775 void kit_cg_memcpy(KitCg* g, uint64_t size, KitCgMemAccess dst_access,
    776                    KitCgMemAccess src_access) {
    777   ApiSValue src, dst;
    778   CgTarget* T;
    779   AggregateAccess agg;
    780   Operand dst_op, src_op;
    781   if (!g) return;
    782   api_local_const_memory_boundary(g);
    783   (void)src_access;
    784   if (size > UINT32_MAX) {
    785     compiler_panic(g->c, g->cur_loc, "KitCg: memcpy size exceeds CgTarget");
    786     return;
    787   }
    788   T = g->target;
    789   src = api_pop(g);
    790   dst = api_pop(g);
    791   api_require_pointer_value(g, "memcpy destination", api_sv_type(&dst));
    792   api_require_pointer_value(g, "memcpy source", api_sv_type(&src));
    793   dst_op = api_force_local(g, &dst, api_sv_type(&dst));
    794   src_op = api_force_local(g, &src, api_sv_type(&src));
    795   memset(&agg, 0, sizeof agg);
    796   agg.size = (u32)size;
    797   agg.align = dst_access.align ? dst_access.align : (u32)size;
    798   T->copy_bytes(T, dst_op, src_op, agg);
    799   api_release(g, &dst);
    800   api_release(g, &src);
    801 }
    802 
    803 void kit_cg_memmove(KitCg* g, uint64_t size, KitCgMemAccess dst_access,
    804                     KitCgMemAccess src_access) {
    805   ApiSValue src, dst;
    806   Operand args[3];
    807   if (!g) return;
    808   api_local_const_memory_boundary(g);
    809   (void)dst_access;
    810   (void)src_access;
    811   if (size > INT64_MAX) {
    812     compiler_panic(g->c, g->cur_loc, "KitCg: memmove size exceeds CgTarget");
    813     return;
    814   }
    815   src = api_pop(g);
    816   dst = api_pop(g);
    817   api_require_pointer_value(g, "memmove destination", api_sv_type(&dst));
    818   api_require_pointer_value(g, "memmove source", api_sv_type(&src));
    819   args[0] = api_force_local(g, &dst, api_sv_type(&dst));
    820   args[1] = api_force_local(g, &src, api_sv_type(&src));
    821   args[2] = api_op_imm((i64)size, builtin_id(KIT_CG_BUILTIN_I64));
    822   g->target->intrinsic(g->target, INTRIN_MEMMOVE, NULL, 0, args, 3);
    823   api_release(g, &dst);
    824   api_release(g, &src);
    825 }
    826 
    827 void kit_cg_memset(KitCg* g, uint8_t val, uint64_t size,
    828                    KitCgMemAccess dst_access) {
    829   ApiSValue dst;
    830   CgTarget* T;
    831   AggregateAccess agg;
    832   Operand dst_op, byte_val;
    833   if (!g) return;
    834   api_local_const_memory_boundary(g);
    835   if (size > UINT32_MAX) {
    836     compiler_panic(g->c, g->cur_loc, "KitCg: memset size exceeds CgTarget");
    837     return;
    838   }
    839   T = g->target;
    840   dst = api_pop(g);
    841   api_require_pointer_value(g, "memset destination", api_sv_type(&dst));
    842   dst_op = api_force_local(g, &dst, api_sv_type(&dst));
    843   byte_val = api_op_imm((i64)val, KIT_CG_TYPE_NONE);
    844   memset(&agg, 0, sizeof agg);
    845   agg.size = (u32)size;
    846   agg.align = dst_access.align ? dst_access.align : (u32)size;
    847   T->set_bytes(T, dst_op, byte_val, agg);
    848   api_release(g, &dst);
    849 }
    850 
    851 /* log2 of a {1,2,4,8} scale, else -1. */
    852 static int cg_scale_to_log2(u32 scale) {
    853   switch (scale) {
    854     case 1:
    855       return 0;
    856     case 2:
    857       return 1;
    858     case 4:
    859       return 2;
    860     case 8:
    861       return 3;
    862     default:
    863       return -1;
    864   }
    865 }
    866 
    867 void kit_cg_elem(KitCg* g, int64_t offset) {
    868   ApiSValue idx, base;
    869   CgTarget* T;
    870   KitCgTypeId base_ty, base_ptr_ty, elem_ty, idx_ty;
    871   const CgType* base_info;
    872   u32 elemsz;
    873   Operand base_op, idx_op;
    874   CGLocal base_local;
    875   if (!g) return;
    876   T = g->target;
    877   idx = api_pop(g);
    878   base = api_pop(g);
    879   base_ty = api_sv_type(&base);
    880   base_info = cg_type_get(g->c, base_ty);
    881   if (api_is_lvalue_sv(&base) || !base_info ||
    882       base_info->kind != KIT_CG_TYPE_PTR) {
    883     compiler_panic(g->c, g->cur_loc,
    884                    "KitCg: elem requires a pointer value base (decay an "
    885                    "array to a pointer first)");
    886     return;
    887   }
    888   elem_ty = base_info->ptr.pointee;
    889   base_ptr_ty = base_ty;
    890   elemsz = (u32)abi_cg_sizeof(g->c->abi, elem_ty);
    891   idx_ty = idx.type ? idx.type : idx.op.type;
    892   if (!idx_ty) idx_ty = builtin_id(KIT_CG_BUILTIN_I64);
    893   base_op = api_force_local(g, &base, base_ptr_ty);
    894   base_local = base_op.v.local;
    895   idx_op = api_force_local_unless_imm(g, &idx, idx_ty);
    896 
    897   /* Constant index folds entirely into the displacement — no instructions, just
    898    * a larger offset on the OPK_INDIRECT. */
    899   if (idx_op.kind == OPK_IMM) {
    900     i64 ofs = idx_op.v.imm * (i64)elemsz + offset;
    901     Operand place;
    902     if (ofs >= INT32_MIN && ofs <= INT32_MAX) {
    903       place = api_op_indirect(base_local, (i32)ofs, elem_ty);
    904     } else {
    905       CGLocal r = api_alloc_temp_local(g, base_ptr_ty);
    906       Operand ro = api_op_local(r, base_ptr_ty);
    907       T->binop(T, BO_IADD, ro, api_op_local(base_local, base_ptr_ty),
    908                api_op_imm(ofs, base_ptr_ty));
    909       place = api_op_indirect(r, 0, elem_ty);
    910     }
    911     api_release(g, &base);
    912     api_release(g, &idx);
    913     api_push(g, api_make_lv(place, elem_ty));
    914     return;
    915   }
    916 
    917   /* Dynamic index: build a scaled-index place so the backend emits one
    918    * [base + index*scale] addressing mode. A power-of-two element size rides in
    919    * log2_scale; any other size pre-multiplies the index once (scale 1). The
    920    * index is copied into a fresh local for unambiguous ownership. */
    921   {
    922     int lg2 = cg_scale_to_log2(elemsz);
    923     CGLocal ir = api_alloc_temp_local(g, idx_ty);
    924     Operand iro = api_op_local(ir, idx_ty);
    925     u8 log2_scale;
    926     /* Refresh idx_op: allocating `ir` may have materialized a delayed index. */
    927     idx_op = api_force_local_unless_imm(g, &idx, idx_ty);
    928     if (idx.op.kind == OPK_LOCAL) idx_op = idx.op;
    929     if (lg2 >= 0) {
    930       T->copy(T, iro, idx_op);
    931       log2_scale = (u8)lg2;
    932     } else {
    933       T->binop(T, BO_IMUL, iro, idx_op, api_op_imm((i64)elemsz, idx_ty));
    934       log2_scale = 0;
    935     }
    936     api_release(g, &base);
    937     api_release(g, &idx);
    938     api_push(g, api_make_lv(api_op_indirect_indexed(base_local, ir, log2_scale,
    939                                                     (i32)offset, elem_ty),
    940                             elem_ty));
    941   }
    942 }
    943 
    944 void kit_cg_field(KitCg* g, uint32_t field_index) {
    945   ApiSValue base;
    946   CgTarget* T;
    947   KitCgTypeId rec_ty;
    948   KitCgTypeId base_ty;
    949   KitCgTypeId field_ty;
    950   KitCgTypeId rec_ptr_ty;
    951   const CgType* rec_info;
    952   const ABIRecordLayout* layout;
    953   u32 field_offset;
    954   Operand result;
    955   CGLocal rr;
    956   if (!g) return;
    957   T = g->target;
    958   base = api_pop(g);
    959   api_ensure_local(g, &base);
    960   base_ty = api_sv_type(&base);
    961   if (!api_is_lvalue_sv(&base)) {
    962     compiler_panic(g->c, g->cur_loc,
    963                    "KitCg: field requires a record place; deref a pointer "
    964                    "first");
    965     api_release(g, &base);
    966     return;
    967   }
    968   rec_ty = base_ty;
    969   rec_ptr_ty = cg_type_ptr_to(g->c, rec_ty);
    970   layout = abi_cg_record_layout(g->c->abi, rec_ty);
    971   if (!layout || field_index >= layout->nfields) {
    972     compiler_panic(g->c, g->cur_loc, "KitCg: invalid field index");
    973     return;
    974   }
    975   rec_info = cg_type_get(g->c, rec_ty);
    976   if (!rec_info || rec_info->kind != KIT_CG_TYPE_RECORD ||
    977       field_index >= rec_info->record.nfields) {
    978     compiler_panic(g->c, g->cur_loc, "KitCg: invalid record base");
    979     return;
    980   }
    981   field_ty = rec_info->record.fields[field_index].type;
    982   field_offset = layout->fields[field_index].offset;
    983   if (layout->fields[field_index].bit_width != 0 ||
    984       (rec_info->record.fields[field_index].flags & KIT_CG_FIELD_BITFIELD) !=
    985           0) {
    986     Operand base_addr;
    987     ApiSValue sv;
    988     if (layout->fields[field_index].bit_width == 0) {
    989       compiler_panic(g->c, g->cur_loc, "KitCg: zero-width bit-field access");
    990       api_release(g, &base);
    991       return;
    992     }
    993     /* Project to a bit-field PLACE: the place addresses the enclosing storage
    994      * unit and carries the bit-field geometry from the record layout. A plain
    995      * load/store on this place performs the extract/insert; there is no
    996      * separate bit-field memop and no bit-field rider on KitCgMemAccess. */
    997     base_addr = api_lvalue_addr(g, &base, rec_ptr_ty);
    998     sv = api_make_lv(base_addr, field_ty);
    999     sv.bitfield.bit_offset = layout->fields[field_index].bit_offset;
   1000     sv.bitfield.bit_width = layout->fields[field_index].bit_width;
   1001     sv.bitfield.bit_storage_size = layout->fields[field_index].storage_size;
   1002     sv.bitfield.bit_signed =
   1003         rec_info->record.fields[field_index].bit_signed ? 1u : 0u;
   1004     api_release(g, &base);
   1005     api_push(g, sv);
   1006     return;
   1007   }
   1008   if (base.op.kind == OPK_GLOBAL) {
   1009     result =
   1010         api_op_global(base.op.v.global.sym,
   1011                       base.op.v.global.addend + (i64)field_offset, field_ty);
   1012     api_push(g, api_make_lv(result, field_ty));
   1013   } else if (base.op.kind == OPK_INDIRECT && field_offset <= (u32)INT32_MAX &&
   1014              base.op.v.ind.ofs <= INT32_MAX - (i32)field_offset) {
   1015     /* Fold the field offset into the displacement, preserving any index/scale
   1016      * a preceding `elem` left so `p[i].f` stays one [base+index*scale+off]. */
   1017     result = api_op_indirect_indexed(
   1018         base.op.v.ind.base, base.op.v.ind.index, base.op.v.ind.log2_scale,
   1019         base.op.v.ind.ofs + (i32)field_offset, field_ty);
   1020     api_push(g, api_make_lv(result, field_ty));
   1021   } else {
   1022     Operand base_addr;
   1023     rr = api_alloc_temp_local(g, rec_ptr_ty);
   1024     base_addr = api_op_local(rr, rec_ptr_ty);
   1025     T->addr_of(T, base_addr, base.op);
   1026     api_release(g, &base);
   1027     if (field_offset == 0) {
   1028       result = base_addr;
   1029     } else {
   1030       CGLocal fr = api_alloc_temp_local(g, rec_ptr_ty);
   1031       result = api_op_local(fr, rec_ptr_ty);
   1032       T->binop(T, BO_IADD, result, base_addr,
   1033                api_op_imm((i64)field_offset, rec_ptr_ty));
   1034       api_release_temp_local(g, base_addr.v.local);
   1035     }
   1036     api_push(
   1037         g, api_make_lv(api_op_indirect(result.v.local, 0, field_ty), field_ty));
   1038   }
   1039 }
   1040 
   1041 void kit_cg_field_bits(KitCg* g, uint16_t bit_offset, uint16_t bit_width,
   1042                        uint32_t bit_storage_size, int bit_signed) {
   1043   ApiSValue* top;
   1044   if (!g || g->sp == 0) return;
   1045   top = &g->stack[g->sp - 1u];
   1046   if (!api_is_lvalue_sv(top)) {
   1047     compiler_panic(g->c, g->cur_loc,
   1048                    "KitCg: field_bits requires a place destination");
   1049     return;
   1050   }
   1051   top->bitfield.bit_offset = bit_offset;
   1052   top->bitfield.bit_width = bit_width;
   1053   top->bitfield.bit_storage_size = bit_storage_size;
   1054   top->bitfield.bit_signed = bit_signed ? 1u : 0u;
   1055 }
   1056 
   1057 /* ============================================================
   1058  * Calls / return
   1059  * ============================================================ */
   1060 
   1061 /* Shared scaffolding for kit_cg_call / kit_cg_call_symbol. The two
   1062  * public entry points differ only in how the callee is obtained and in
   1063  * their pre-call stack-depth check; everything else (arg packaging, return
   1064  * storage allocation, post-call release, result push) is identical. These
   1065  * helpers carry the common shape and are the natural targets for any future
   1066  * change that wants to vary call-shape policy (e.g. an ABI-driven storage
   1067  * decision). */