kit

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

arith.c (84814B)


      1 #include "cg/internal.h"
      2 
      3 static int api_try_fold_int_convert(KitCg* g, ConvKind ck, KitCgTypeId sty,
      4                                     KitCgTypeId dty, i64 in, i64* out) {
      5   u32 sw;
      6   u32 dw;
      7   u64 r;
      8   if (!g || !out || !api_foldable_int_like_type(g->c, sty, &sw) ||
      9       !api_foldable_int_like_type(g->c, dty, &dw)) {
     10     return 0;
     11   }
     12   switch (ck) {
     13     case CV_SEXT:
     14       r = (u64)api_sign_extend_width((u64)in, sw);
     15       break;
     16     case CV_ZEXT:
     17       r = api_mask_width((u64)in, sw);
     18       break;
     19     case CV_TRUNC:
     20       r = api_mask_width((u64)in, dw);
     21       break;
     22     default:
     23       return 0;
     24   }
     25   *out = api_fold_result(g->c, dty, r, dw);
     26   return 1;
     27 }
     28 
     29 void api_cg_binop(KitCg* g, BinOp iop, u32 flags) {
     30   ApiSValue b, a;
     31   ApiConstValue cb, ca, result_const;
     32   CgTarget* T;
     33   KitCgTypeId ty;
     34   Operand ra, rb;
     35   CGLocal rr;
     36   Operand dst;
     37   ApiSValue folded_sv;
     38   i64 folded;
     39   int can_delay;
     40   if (!g) return;
     41   cb = api_const_at(g, 0);
     42   ca = api_const_at(g, 1);
     43   T = g->target;
     44   b = api_pop(g);
     45   a = api_pop(g);
     46   ty = a.type ? a.type : b.type;
     47   api_const_fold_binop(g, iop, ty, ca, cb, flags, &result_const);
     48   if (api_unevaluated(g)) {
     49     api_release(g, &a);
     50     api_release(g, &b);
     51     api_push(g, api_uneval_value(g, ty));
     52     api_const_set_top(g, result_const);
     53     return;
     54   }
     55   /* Delayability is a pure function of (ty, flags), neither of which changes
     56    * below (strength-reduce rewrites the op and operands, not the type), so
     57    * classify the foldable int once instead of re-deriving it at each of the
     58    * three delay gates. */
     59   can_delay = api_can_delay_int_arith(g, ty, flags);
     60 
     61   if (!flags && api_sv_op_is(&a, OPK_IMM) && api_sv_op_is(&b, OPK_IMM) &&
     62       api_try_fold_int_binop(g, iop, ty, a.op.v.imm, b.op.v.imm, &folded)) {
     63     api_release(g, &a);
     64     api_release(g, &b);
     65     api_push(g, api_make_sv(api_op_imm(folded, ty), ty));
     66     api_const_set_top(g, result_const);
     67     return;
     68   }
     69 
     70   /* Strength-reduce mul/udiv/urem by a power of two into shift/and. Rewrites
     71    * iop and the operands in place; the result flows through the same delay /
     72    * identity / fallback machinery as any other shift or and. */
     73   if (!flags) api_try_strength_reduce(g, &iop, ty, &a, &b);
     74 
     75   if (can_delay && api_try_fold_arith_chain(g, iop, ty, &a, &b, &folded_sv)) {
     76     api_release(g, &a);
     77     api_release(g, &b);
     78     api_push(g, folded_sv);
     79     api_const_set_top(g, result_const);
     80     return;
     81   }
     82 
     83   if (api_type_is_float(g->c, ty)) {
     84     ra = api_force_local(g, &a, ty);
     85     rb = api_force_local(g, &b, ty);
     86   } else {
     87     ra = api_force_local_unless_imm(g, &a, ty);
     88     rb = api_force_local_unless_imm(g, &b, ty);
     89   }
     90 
     91   if (can_delay &&
     92       api_try_collapse_binop_identity(g, iop, ty, &a, &b, &folded_sv)) {
     93     api_release(g, &a);
     94     api_release(g, &b);
     95     api_push(g, folded_sv);
     96     api_const_set_top(g, result_const);
     97     return;
     98   }
     99 
    100   if (can_delay && (ra.kind == OPK_LOCAL || rb.kind == OPK_LOCAL) &&
    101       (ra.kind == OPK_LOCAL || ra.kind == OPK_IMM) &&
    102       (rb.kind == OPK_LOCAL || rb.kind == OPK_IMM)) {
    103     int a_owned = api_sv_owns_operand_local(&a, &ra);
    104     int b_owned = api_sv_owns_operand_local(&b, &rb);
    105     api_push(g, api_make_arith_binop(g, iop, ra, rb, ty, a_owned, b_owned));
    106     if (a_owned) api_sv_set_res(&a, RES_INHERENT);
    107     if (b_owned) api_sv_set_res(&b, RES_INHERENT);
    108     api_release(g, &a);
    109     api_release(g, &b);
    110     api_const_set_top(g, result_const);
    111     return;
    112   }
    113 
    114   rr = api_alloc_temp_local(g, ty);
    115   dst = api_op_local(rr, ty);
    116   /* Flag dead-transient operands so the -O0 backend drops them after the op
    117    * instead of spilling them at the next barrier (eager dead-operand drop). */
    118   ra = api_op_kill_if_dead(g, ra, dst);
    119   rb = api_op_kill_if_dead(g, rb, dst);
    120   T->binop(T, iop, dst, ra, rb);
    121   api_release(g, &a);
    122   api_release(g, &b);
    123   api_push(g, api_make_sv(dst, ty));
    124   api_const_set_top(g, result_const);
    125 }
    126 
    127 void api_cg_unop(KitCg* g, UnOp iop, u32 flags) {
    128   ApiSValue a;
    129   ApiConstValue ca, result_const;
    130   CgTarget* T;
    131   KitCgTypeId ty;
    132   Operand ra;
    133   CGLocal rr;
    134   Operand dst;
    135   ApiSValue folded_sv;
    136   i64 folded;
    137   int can_delay;
    138   if (!g) return;
    139   ca = api_const_at(g, 0);
    140   T = g->target;
    141   a = api_pop(g);
    142   ty = a.type ? a.type : a.op.type;
    143   api_const_fold_unop(g, iop, ty, ca, flags, &result_const);
    144   if (api_unevaluated(g)) {
    145     api_release(g, &a);
    146     api_push(g, api_uneval_value(g, ty));
    147     api_const_set_top(g, result_const);
    148     return;
    149   }
    150   /* Pure function of (ty, flags); classify the foldable int once for both delay
    151    * gates below. */
    152   can_delay = api_can_delay_int_arith(g, ty, flags);
    153 
    154   if (iop == UO_FNEG) {
    155     CG_REQUIRE(g, api_type_is_float(g->c, ty),
    156                "KitCg: FP negation requires floating operand");
    157     ra = api_force_local(g, &a, ty);
    158     rr = api_alloc_temp_local(g, ty);
    159     dst = api_op_local(rr, ty);
    160     ra = api_op_kill_if_dead(g, ra, dst);
    161     T->unop(T, iop, dst, ra);
    162     api_release(g, &a);
    163     api_push(g, api_make_sv(dst, ty));
    164     api_const_set_top(g, result_const);
    165     return;
    166   }
    167 
    168   /* Logical NOT of a delayed compare stays delayed: invert the predicate in
    169    * place. For FP this flips ordered<->unordered as well as the relation (via
    170    * api_invert_cmp), so `!(a<b)` becomes UGE (NaN -> true), matching IEEE
    171    * negation. The inverted compare keeps the same i32 result type. */
    172   if (iop == UO_NOT && api_sv_kind(&a) == SV_CMP) {
    173     a.delayed->cmp.op = api_invert_cmp(a.delayed->cmp.op);
    174     api_push(g, a);
    175     api_const_set_top(g, result_const);
    176     return;
    177   }
    178 
    179   if (!flags && api_sv_op_is(&a, OPK_IMM) &&
    180       api_try_fold_int_unop(g, iop, ty, a.op.v.imm, &folded)) {
    181     api_release(g, &a);
    182     api_push(g, api_make_sv(api_op_imm(folded, ty), ty));
    183     api_const_set_top(g, result_const);
    184     return;
    185   }
    186 
    187   if (can_delay && api_try_fold_unary_chain(&a, iop, ty, &folded_sv)) {
    188     api_release(g, &a);
    189     api_push(g, folded_sv);
    190     api_const_set_top(g, result_const);
    191     return;
    192   }
    193 
    194   ra = api_force_local_unless_imm(g, &a, ty);
    195   if (can_delay && ra.kind == OPK_LOCAL) {
    196     int a_owned = api_sv_owns_operand_local(&a, &ra);
    197     api_push(g, api_make_arith_unop(g, iop, ra, ty, a_owned));
    198     if (a_owned) api_sv_set_res(&a, RES_INHERENT);
    199     api_release(g, &a);
    200     api_const_set_top(g, result_const);
    201     return;
    202   }
    203   rr = api_alloc_temp_local(g, ty);
    204   dst = api_op_local(rr, ty);
    205   ra = api_op_kill_if_dead(g, ra, dst);
    206   T->unop(T, iop, dst, ra);
    207   api_release(g, &a);
    208   api_push(g, api_make_sv(dst, ty));
    209   api_const_set_top(g, result_const);
    210 }
    211 
    212 void api_cg_cmp(KitCg* g, CmpOp cop) {
    213   ApiSValue b, a;
    214   ApiConstValue cb, ca, result_const;
    215   KitCgTypeId opty;
    216   KitCgTypeId i32;
    217   Operand ra, rb;
    218   i64 folded;
    219   if (!g) return;
    220   cb = api_const_at(g, 0);
    221   ca = api_const_at(g, 1);
    222   b = api_pop(g);
    223   a = api_pop(g);
    224   opty = a.type ? a.type : b.type;
    225   i32 = builtin_id(KIT_CG_BUILTIN_I32);
    226   api_const_fold_cmp(g, cop, ca, cb, &result_const);
    227   if (api_unevaluated(g)) {
    228     api_release(g, &a);
    229     api_release(g, &b);
    230     api_push(g, api_uneval_value(g, i32));
    231     api_const_set_top(g, result_const);
    232     return;
    233   }
    234 
    235   if (api_sv_op_is(&a, OPK_IMM) && api_sv_op_is(&b, OPK_IMM) &&
    236       api_try_fold_int_cmp(g, cop, opty, a.op.v.imm, b.op.v.imm, &folded)) {
    237     api_release(g, &a);
    238     api_release(g, &b);
    239     api_push(g, api_make_sv(api_op_imm(folded, i32), i32));
    240     api_const_set_top(g, result_const);
    241     return;
    242   }
    243 
    244   ra = api_force_local_unless_imm(g, &a, opty);
    245   rb = api_force_local_unless_imm(g, &b, opty);
    246   /* Both integer and FP compares are produced as delayed SV_CMP values.
    247    * Delaying is what lets api_branch_if (and api_cg_unop's UO_NOT) invert
    248    * the compare via api_invert_cmp, reaching the unordered FP duals
    249    * (UGE/UGT/ULE/ULT/UEQ/UNE) from `!(a<b)` etc. with NaN-correct semantics.
    250    * If the compare instead escapes into value context it is materialized
    251    * unchanged via api_materialize_cmp_to, which calls T->cmp with the same
    252    * opcode the eager path used to. */
    253   api_push(g,
    254            api_make_cmp(g, cop, ra, rb, i32, api_sv_owns_operand_local(&a, &ra),
    255                         api_sv_owns_operand_local(&b, &rb)));
    256   api_const_set_top(g, result_const);
    257 }
    258 
    259 int api_try_i128_convert(KitCg* g, ConvKind ck, KitCgTypeId sty,
    260                          KitCgTypeId dty, ApiSValue* v);
    261 int api_try_wide8_convert(KitCg* g, ConvKind ck, KitCgTypeId sty,
    262                           KitCgTypeId dty, ApiSValue* v);
    263 
    264 void api_cg_convert_kind(KitCg* g, KitCgTypeId dst_type, ConvKind ck) {
    265   ApiSValue v;
    266   ApiConstValue in_const, result_const;
    267   CgTarget* T;
    268   KitCgTypeId sty;
    269   KitCgTypeId dty;
    270   Operand src;
    271   CGLocal rr;
    272   Operand dst;
    273   if (!g) return;
    274   T = g->target;
    275   /* Keep the return-before-pop order on an invalid dst_type. */
    276   dty = resolve_type(g->c, dst_type);
    277   if (!dty) return;
    278   in_const = api_const_at(g, 0);
    279   v = api_pop(g);
    280   sty = resolve_type(g->c, v.type ? v.type : v.op.type);
    281   if (!sty) {
    282     api_release(g, &v);
    283     return;
    284   }
    285   api_const_fold_convert(g, ck, sty, dty, in_const, &result_const);
    286   if (api_unevaluated(g)) {
    287     api_release(g, &v);
    288     api_push(g, api_uneval_value(g, dty));
    289     api_const_set_top(g, result_const);
    290     return;
    291   }
    292   if (sty == dty) {
    293     v.type = dty;
    294     v.op.type = dty;
    295     api_push(g, v);
    296     api_const_set_top(g, result_const);
    297     return;
    298   }
    299   if (api_sv_op_is(&v, OPK_IMM)) {
    300     i64 folded;
    301     if (api_try_fold_int_convert(g, ck, sty, dty, v.op.v.imm, &folded)) {
    302       api_release(g, &v);
    303       /* A folded split-lane 8-byte result must be memory-resident, not a bare
    304        * i64 immediate the backend would truncate. */
    305       if (api_is_wide8_scalar_type(g->c, dty))
    306         api_push(g, api_make_wide8_int_const(g, folded, dty));
    307       else
    308         api_push(g, api_make_sv(api_op_imm(folded, dty), dty));
    309       api_const_set_top(g, result_const);
    310       return;
    311     }
    312   }
    313   if (api_try_i128_convert(g, ck, sty, dty, &v)) {
    314     api_const_set_top(g, result_const);
    315     return;
    316   }
    317   if (api_try_wide8_convert(g, ck, sty, dty, &v)) {
    318     api_const_set_top(g, result_const);
    319     return;
    320   }
    321   /* A bitcast between two narrow types of equal width and register class —
    322    * pointer<->pointer-width integer (the frontend's ptr_to_int/int_to_ptr that
    323    * brackets every field-offset add) or pointer<->pointer — reinterprets the
    324    * bits without moving them, so retype the value in place instead of forcing
    325    * it to a fresh temp and emitting a register copy. int<->float of equal width
    326    * is excluded: it crosses the GPR/FPR banks and needs a real fmov. The wide
    327    * (i128 / split-lane) cases are already handled above. */
    328   /* On a machine-word target, a same-width / same-register-class bitcast moves
    329    * no bits, so make the result value *be* the source, retyped — no temp, no
    330    * register copy. This is a value-identity rewrite (the source is dead: it is
    331    * released just below), which only the cg layer can do; the backend sees
    332    * convert(dst, src) as two independent locals and cannot know src is
    333    * substitutable. untyped_values reports the one backend-specific fact it
    334    * needs — typed backends (the C emitter, the optimizer recorder) leave it 0
    335    * and keep the explicit convert. OPK_IMM is excluded: a bitcast immediate
    336    * that becomes an address base must reach a register (an INDIRECT base cannot
    337    * be an immediate), an invariant the aggregate-arg lowering relies on. */
    338   if (ck == CV_BITCAST && T->untyped_values && v.op.kind != OPK_IMM) {
    339     /* sty/dty are already unaliased; decode each id's predicate bitset once and
    340      * read the aggregate/float bits locally instead of three fresh decodes. */
    341     u8 sty_bits = api_type_pred_bits(g->c, sty);
    342     u8 dty_bits = api_type_pred_bits(g->c, dty);
    343     if (!(sty_bits & API_PRED_AGGREGATE) && !(dty_bits & API_PRED_AGGREGATE) &&
    344         abi_cg_sizeof(g->c->abi, sty) == abi_cg_sizeof(g->c->abi, dty) &&
    345         !(sty_bits & API_PRED_FLOAT) == !(dty_bits & API_PRED_FLOAT)) {
    346       v.type = dty;
    347       v.op.type = dty;
    348       api_push(g, v);
    349       api_const_set_top(g, result_const);
    350       return;
    351     }
    352   }
    353   if (ck == CV_BITCAST && abi_cg_sizeof(g->c->abi, sty) == 16 &&
    354       abi_cg_sizeof(g->c->abi, dty) == 16 &&
    355       (api_is_f128_type(g->c, sty) || api_is_f128_type(g->c, dty))) {
    356     CGLocal local = api_f128_temp_local(g, dty);
    357     Operand dst_lv = api_op_local(local, dty);
    358     if (api_is_lvalue_sv(&v) || v.op.kind == OPK_LOCAL ||
    359         v.op.kind == OPK_INDIRECT || v.op.kind == OPK_GLOBAL) {
    360       KitCgTypeId ptr_ty = cg_type_ptr_to(g->c, dty);
    361       ApiSValue src_lv = v;
    362       Operand dst_addr;
    363       Operand src_addr;
    364       AggregateAccess agg;
    365       ApiSValue dst_place = api_make_lv(dst_lv, dty);
    366       api_sv_set_lvalue(&src_lv, 1);
    367       dst_addr = api_lvalue_addr(g, &dst_place, ptr_ty);
    368       src_addr = api_lvalue_addr(g, &src_lv, cg_type_ptr_to(g->c, sty));
    369       memset(&agg, 0, sizeof agg);
    370       agg.size = 16;
    371       agg.align = 16;
    372       g->target->copy_bytes(g->target, dst_addr, src_addr, agg);
    373     } else if (v.op.kind == OPK_LOCAL) {
    374       g->target->store(g->target, dst_lv, v.op,
    375                        api_mem_for_lvalue(g, &dst_lv, sty));
    376     } else if (v.op.kind == OPK_IMM) {
    377       u8 bytes[16];
    378       u64 lo = (u64)v.op.v.imm;
    379       memset(bytes, 0, sizeof bytes);
    380       for (u32 i = 0; i < 8; ++i) {
    381         u32 idx = g->c->target.big_endian ? 15u - i : i;
    382         bytes[idx] = (u8)(lo >> (i * 8u));
    383       }
    384       api_store_f128_bytes(g, local, dty, bytes);
    385     } else {
    386       CG_BUG(g, "KitCg: unsupported 16-byte bitcast source");
    387     }
    388     api_release(g, &v);
    389     api_push(g, api_make_lv(dst_lv, dty));
    390     api_const_set_top(g, result_const);
    391     return;
    392   }
    393 
    394   src = api_force_local(g, &v, sty);
    395   rr = api_alloc_temp_local(g, dty);
    396   dst = api_op_local(rr, dty);
    397   /* If the source operand is a transient with no other live value-stack
    398    * reference (v, its last reference, was already popped), flag it dead so the
    399    * -O0 backend can convert in place into the source's register instead of
    400    * routing through a fresh one — and drop a redundant extend entirely. */
    401   if (api_coalesce_on(g) && src.kind == OPK_LOCAL &&
    402       api_temp_dead(g, src.v.local))
    403     src.flags |= OPK_FLAG_KILL;
    404   T->convert(T, ck, dst, src);
    405   api_release(g, &v);
    406   api_push(g, api_make_sv(dst, dty));
    407   api_const_set_top(g, result_const);
    408 }
    409 
    410 /* ============================================================
    411  * 128-bit integer lowering
    412  *
    413  * i128/u128 are 16-byte memory-resident scalars (see api_is_wide16
    414  * and src/cg/wide.c). The native backends only model <=64-bit
    415  * register ops, so every i128 arithmetic/compare/convert is lowered
    416  * here to a compiler-rt-style runtime call (rt/lib/int64). This
    417  * mirrors the f128 dispatch in kit_cg_fp_*.
    418  * ============================================================ */
    419 
    420 /* The wide / soft-float dispatch class cached on the value-stack node at
    421  * `depth` below TOS (api_push stamps it from api_type_class). The five
    422  * stack-top predicates below are now a bounds check plus a tag compare; the
    423  * type-get + alias-chase gauntlet that used to run per operand happens once at
    424  * push instead. WK_NARROW for an out-of-range depth keeps every predicate false
    425  * exactly as the old bounds guards did. */
    426 static u8 api_stack_wide_kind(KitCg* g, u32 depth) {
    427   if (!g || g->sp <= depth) return WK_NARROW;
    428   return g->stack[g->sp - 1u - depth].bitfield.wide_kind;
    429 }
    430 
    431 int api_i128_stack_top(KitCg* g, u32 depth) {
    432   return api_stack_wide_kind(g, depth) == WK_I128;
    433 }
    434 
    435 /* 64-bit integer split into two 32-bit lanes by the selected ABI. The native
    436  * backend handles add/sub/and/or/xor on such values as register pairs, but
    437  * mul/div/shift must be lowered to a __*di3 runtime call (see
    438  * api_wideint64_binop). i128 routes through its own ti3 path (api_i128_*), so
    439  * the WK_WIDE8 class excludes it. */
    440 static int api_wide64_stack_top(KitCg* g, u32 depth) {
    441   return api_stack_wide_kind(g, depth) == WK_WIDE8;
    442 }
    443 
    444 static int api_binop_is_shift(BinOp iop) {
    445   return iop == BO_SHL || iop == BO_SHR_U || iop == BO_SHR_S;
    446 }
    447 
    448 static int api_is_bool_type(Compiler* c, KitCgTypeId ty) {
    449   const CgType* cg = cg_type_get(c, ty);
    450   return cg && cg->kind == KIT_CG_TYPE_BOOL;
    451 }
    452 
    453 /* Materialize an i128 value as an lvalue and return a pointer local to it. */
    454 static Operand api_i128_addr(KitCg* g, ApiSValue* v) {
    455   KitCgTypeId i128 = builtin_id(KIT_CG_BUILTIN_I128);
    456   ApiSValue lv = api_wide16_materialize_lvalue(g, v, i128);
    457   return api_lvalue_addr(g, &lv, cg_type_ptr_to(g->c, i128));
    458 }
    459 
    460 /* Load a 64-bit lane of an i128 (addressed by `addr`) into a fresh i64. */
    461 static Operand api_i128_load_lane(KitCg* g, Operand addr, i32 off) {
    462   KitCgTypeId i64 = builtin_id(KIT_CG_BUILTIN_I64);
    463   CGLocal rr = api_alloc_temp_local(g, i64);
    464   Operand dst = api_op_local(rr, i64);
    465   MemAccess ma;
    466   memset(&ma, 0, sizeof ma);
    467   ma.type = i64;
    468   ma.size = 8;
    469   ma.align = 8;
    470   g->target->load(g->target, dst, api_op_indirect(addr.v.local, off, i64), ma);
    471   return dst;
    472 }
    473 
    474 static void api_i128_binop(KitCg* g, BinOp iop) {
    475   KitCgTypeId i128 = builtin_id(KIT_CG_BUILTIN_I128);
    476   KitCgTypeId i32 = builtin_id(KIT_CG_BUILTIN_I32);
    477   const char* name = api_i128_binop_helper(iop);
    478   KitCgTypeId ps[2];
    479   ApiSValue args[2];
    480   if (!name) {
    481     compiler_panic(g->c, g->cur_loc, "KitCg: unsupported i128 binop");
    482     return;
    483   }
    484   args[1] = api_pop(g);
    485   args[0] = api_pop(g);
    486   ps[0] = i128;
    487   ps[1] = api_binop_is_shift(iop) ? i32 : i128;
    488   api_runtime_call_values(g, name, i128, ps, 2, args);
    489 }
    490 
    491 /* Runtime helper name for a 64-bit-integer mul/div/rem/shift on a 32-bit
    492  * target. Mirrors api_i128_binop_helper but with the compiler-rt *di3 names.
    493  * Returns NULL for ops the inline backend handles (add/sub/and/or/xor). */
    494 static const char* api_wideint64_binop_helper(BinOp op) {
    495   switch (op) {
    496     case BO_IMUL:
    497       return "__muldi3";
    498     case BO_SDIV:
    499       return "__divdi3";
    500     case BO_UDIV:
    501       return "__udivdi3";
    502     case BO_SREM:
    503       return "__moddi3";
    504     case BO_UREM:
    505       return "__umoddi3";
    506     case BO_SHL:
    507       return "__ashldi3";
    508     case BO_SHR_U:
    509       return "__lshrdi3";
    510     case BO_SHR_S:
    511       return "__ashrdi3";
    512     default:
    513       return NULL;
    514   }
    515 }
    516 
    517 /* Lower a 64-bit mul/div/rem/shift to a runtime call. Mirrors api_i128_binop
    518  * but ret/params are builtin i64; the shift-count param is i32 (the __ashldi3
    519  * family takes (i64 value, i32 count) per compiler-rt). */
    520 static void api_wideint64_binop(KitCg* g, BinOp iop) {
    521   KitCgTypeId i64 = builtin_id(KIT_CG_BUILTIN_I64);
    522   KitCgTypeId i32 = builtin_id(KIT_CG_BUILTIN_I32);
    523   const char* name = api_wideint64_binop_helper(iop);
    524   KitCgTypeId ps[2];
    525   ApiSValue args[2];
    526   if (!name) {
    527     compiler_panic(g->c, g->cur_loc, "KitCg: unsupported wide i64 binop");
    528     return;
    529   }
    530   args[1] = api_pop(g);
    531   args[0] = api_pop(g);
    532   ps[0] = i64;
    533   ps[1] = api_binop_is_shift(iop) ? i32 : i64;
    534   api_runtime_call_values(g, name, i64, ps, 2, args);
    535 }
    536 
    537 /* ============================================================
    538  * wide8 inline 2-word lane arithmetic
    539  *
    540  * Some 32-bit ABIs represent a 64-bit integer as a memory-resident 8-byte
    541  * scalar split into two 32-bit lanes. add/sub/and/or/xor/neg/not and compares
    542  * have no compiler-rt helper (they would recurse), so they are emitted INLINE
    543  * here as lane ops. mul/div/rem/shift route to __*di3 (api_wideint64_*).
    544  * ============================================================ */
    545 
    546 static i32 wide8_lo_off(KitCg* g) { return g->c->target.big_endian ? 4 : 0; }
    547 static i32 wide8_hi_off(KitCg* g) { return g->c->target.big_endian ? 0 : 4; }
    548 
    549 /* Emit one i32 binop into a fresh temp and return it. */
    550 static Operand wide8_i32_binop(KitCg* g, BinOp op, Operand a, Operand b) {
    551   KitCgTypeId i32 = builtin_id(KIT_CG_BUILTIN_I32);
    552   CGLocal r = api_alloc_temp_local(g, i32);
    553   Operand d = api_op_local(r, i32);
    554   g->target->binop(g->target, op, d, a, b);
    555   return d;
    556 }
    557 
    558 /* Emit one i32 compare (0/1 result) into a fresh temp and return it. */
    559 static Operand wide8_i32_cmp(KitCg* g, CmpOp op, Operand a, Operand b) {
    560   KitCgTypeId i32 = builtin_id(KIT_CG_BUILTIN_I32);
    561   CGLocal r = api_alloc_temp_local(g, i32);
    562   Operand d = api_op_local(r, i32);
    563   g->target->cmp(g->target, op, d, a, b);
    564   return d;
    565 }
    566 
    567 /* (lo | hi) of the 8-byte value `v` as an i32, for a truthiness test. Consumes
    568  * nothing on the value stack (caller owns *v). */
    569 Operand api_wide8_or_lanes(KitCg* g, ApiSValue* v, KitCgTypeId ty) {
    570   Operand addr = api_wide8_addr(g, v, ty);
    571   Operand lo = api_wide8_load_lane(g, addr, wide8_lo_off(g));
    572   Operand hi = api_wide8_load_lane(g, addr, wide8_hi_off(g));
    573   return wide8_i32_binop(g, BO_OR, lo, hi);
    574 }
    575 
    576 /* add/sub/and/or/xor on two 8-byte ints, result pushed as a fresh 8-byte value.
    577  * add/sub carry/borrow through the high lane via an sltu (CMP_LT_U). */
    578 static void api_wide64_binop_inline(KitCg* g, BinOp iop) {
    579   ApiSValue b = api_pop(g);
    580   ApiSValue a = api_pop(g);
    581   KitCgTypeId ty = a.type ? a.type : b.type;
    582   int lo = wide8_lo_off(g), hi = wide8_hi_off(g);
    583   Operand aa = api_wide8_addr(g, &a, ty);
    584   Operand ab = api_wide8_addr(g, &b, ty);
    585   Operand alo = api_wide8_load_lane(g, aa, lo);
    586   Operand ahi = api_wide8_load_lane(g, aa, hi);
    587   Operand blo = api_wide8_load_lane(g, ab, lo);
    588   Operand bhi = api_wide8_load_lane(g, ab, hi);
    589   CGLocal res = api_wide8_temp_local(g, ty);
    590   ApiSValue res_lv = api_make_lv(api_op_local(res, ty), ty);
    591   Operand ar = api_lvalue_addr(g, &res_lv, cg_type_ptr_to(g->c, ty));
    592   Operand rlo;
    593   Operand rhi;
    594   switch (iop) {
    595     case BO_AND:
    596     case BO_OR:
    597     case BO_XOR:
    598       rlo = wide8_i32_binop(g, iop, alo, blo);
    599       rhi = wide8_i32_binop(g, iop, ahi, bhi);
    600       break;
    601     case BO_IADD: {
    602       Operand carry;
    603       rlo = wide8_i32_binop(g, BO_IADD, alo, blo);
    604       carry = wide8_i32_cmp(g, CMP_LT_U, rlo, alo); /* unsigned wrap -> carry */
    605       rhi = wide8_i32_binop(g, BO_IADD, ahi, bhi);
    606       rhi = wide8_i32_binop(g, BO_IADD, rhi, carry);
    607       break;
    608     }
    609     case BO_ISUB: {
    610       Operand borrow = wide8_i32_cmp(g, CMP_LT_U, alo, blo);
    611       rlo = wide8_i32_binop(g, BO_ISUB, alo, blo);
    612       rhi = wide8_i32_binop(g, BO_ISUB, ahi, bhi);
    613       rhi = wide8_i32_binop(g, BO_ISUB, rhi, borrow);
    614       break;
    615     }
    616     default:
    617       compiler_panic(g->c, g->cur_loc,
    618                      "KitCg: unsupported wide i64 inline binop");
    619       return;
    620   }
    621   api_wide8_store_lane(g, ar, lo, rlo);
    622   api_wide8_store_lane(g, ar, hi, rhi);
    623   api_release(g, &a);
    624   api_release(g, &b);
    625   api_push(g, api_make_sv(api_op_local(res, ty), ty));
    626 }
    627 
    628 /* neg / bnot on an 8-byte int. NEG is two's complement: lo = 0-lo with borrow
    629  * into hi = 0-hi-borrow. BNOT is lane-wise xor -1. */
    630 static void api_wide64_unop_inline(KitCg* g, UnOp iop) {
    631   KitCgTypeId i32 = builtin_id(KIT_CG_BUILTIN_I32);
    632   ApiSValue a = api_pop(g);
    633   KitCgTypeId ty = a.type ? a.type : a.op.type;
    634   int lo = wide8_lo_off(g), hi = wide8_hi_off(g);
    635   Operand aa = api_wide8_addr(g, &a, ty);
    636   Operand alo = api_wide8_load_lane(g, aa, lo);
    637   Operand ahi = api_wide8_load_lane(g, aa, hi);
    638   CGLocal res = api_wide8_temp_local(g, ty);
    639   ApiSValue res_lv = api_make_lv(api_op_local(res, ty), ty);
    640   Operand ar = api_lvalue_addr(g, &res_lv, cg_type_ptr_to(g->c, ty));
    641   Operand rlo;
    642   Operand rhi;
    643   if (iop == UO_BNOT) {
    644     rlo = wide8_i32_binop(g, BO_XOR, alo, api_op_imm(-1, i32));
    645     rhi = wide8_i32_binop(g, BO_XOR, ahi, api_op_imm(-1, i32));
    646   } else { /* UO_NEG: 0 - value */
    647     Operand zero = api_op_imm(0, i32);
    648     Operand borrow = wide8_i32_cmp(g, CMP_LT_U, zero, alo); /* 0<lo -> borrow */
    649     rlo = wide8_i32_binop(g, BO_ISUB, zero, alo);
    650     rhi = wide8_i32_binop(g, BO_ISUB, zero, ahi);
    651     rhi = wide8_i32_binop(g, BO_ISUB, rhi, borrow);
    652   }
    653   api_wide8_store_lane(g, ar, lo, rlo);
    654   api_wide8_store_lane(g, ar, hi, rhi);
    655   api_release(g, &a);
    656   api_push(g, api_make_sv(api_op_local(res, ty), ty));
    657 }
    658 
    659 /* a < b over 8-byte lanes: (a_hi <{s,u} b_hi) | (a_hi==b_hi & a_lo <u b_lo).
    660  * The high lane uses the signed/unsigned relation; the low lane is always
    661  * unsigned. Returns an i32 0/1. */
    662 static Operand wide8_lt(KitCg* g, int is_signed, Operand alo, Operand ahi,
    663                         Operand blo, Operand bhi) {
    664   Operand hi_lt = wide8_i32_cmp(g, is_signed ? CMP_LT_S : CMP_LT_U, ahi, bhi);
    665   Operand hi_eq = wide8_i32_cmp(g, CMP_EQ, ahi, bhi);
    666   Operand lo_lt = wide8_i32_cmp(g, CMP_LT_U, alo, blo);
    667   Operand t = wide8_i32_binop(g, BO_AND, hi_eq, lo_lt);
    668   return wide8_i32_binop(g, BO_OR, hi_lt, t);
    669 }
    670 
    671 static Operand wide8_eq(KitCg* g, Operand alo, Operand ahi, Operand blo,
    672                         Operand bhi) {
    673   KitCgTypeId i32 = builtin_id(KIT_CG_BUILTIN_I32);
    674   Operand dlo = wide8_i32_binop(g, BO_XOR, alo, blo);
    675   Operand dhi = wide8_i32_binop(g, BO_XOR, ahi, bhi);
    676   Operand diff = wide8_i32_binop(g, BO_OR, dlo, dhi);
    677   return wide8_i32_cmp(g, CMP_EQ, diff, api_op_imm(0, i32));
    678 }
    679 
    680 static int cmp_is_signed_rel(CmpOp op) {
    681   return op == CMP_LT_S || op == CMP_LE_S || op == CMP_GT_S || op == CMP_GE_S;
    682 }
    683 
    684 /* 8-byte int compare -> eager i32 0/1 value (not a delayed SV_CMP). */
    685 static void api_wide64_cmp_inline(KitCg* g, CmpOp cop) {
    686   KitCgTypeId i32 = builtin_id(KIT_CG_BUILTIN_I32);
    687   int sg = cmp_is_signed_rel(cop);
    688   ApiSValue b = api_pop(g);
    689   ApiSValue a = api_pop(g);
    690   KitCgTypeId ty = a.type ? a.type : b.type;
    691   int lo = wide8_lo_off(g), hi = wide8_hi_off(g);
    692   Operand aa = api_wide8_addr(g, &a, ty);
    693   Operand ab = api_wide8_addr(g, &b, ty);
    694   Operand alo = api_wide8_load_lane(g, aa, lo);
    695   Operand ahi = api_wide8_load_lane(g, aa, hi);
    696   Operand blo = api_wide8_load_lane(g, ab, lo);
    697   Operand bhi = api_wide8_load_lane(g, ab, hi);
    698   Operand one = api_op_imm(1, i32);
    699   Operand res;
    700   switch (cop) {
    701     case CMP_EQ:
    702       res = wide8_eq(g, alo, ahi, blo, bhi);
    703       break;
    704     case CMP_NE:
    705       res = wide8_i32_binop(g, BO_XOR, wide8_eq(g, alo, ahi, blo, bhi), one);
    706       break;
    707     case CMP_LT_S:
    708     case CMP_LT_U:
    709       res = wide8_lt(g, sg, alo, ahi, blo, bhi);
    710       break;
    711     case CMP_GT_S:
    712     case CMP_GT_U: /* a>b  ==  b<a */
    713       res = wide8_lt(g, sg, blo, bhi, alo, ahi);
    714       break;
    715     case CMP_LE_S:
    716     case CMP_LE_U: /* a<=b ==  !(b<a) */
    717       res =
    718           wide8_i32_binop(g, BO_XOR, wide8_lt(g, sg, blo, bhi, alo, ahi), one);
    719       break;
    720     case CMP_GE_S:
    721     case CMP_GE_U: /* a>=b ==  !(a<b) */
    722       res =
    723           wide8_i32_binop(g, BO_XOR, wide8_lt(g, sg, alo, ahi, blo, bhi), one);
    724       break;
    725     default:
    726       compiler_panic(g->c, g->cur_loc, "KitCg: unsupported wide i64 compare");
    727       return;
    728   }
    729   api_release(g, &a);
    730   api_release(g, &b);
    731   api_push(g, api_make_sv(res, i32));
    732 }
    733 
    734 /* ============================================================
    735  * wide64 __builtin_*_overflow on split-lane 64-bit values
    736  *
    737  * The native backends only model single-register overflow, so a 64-bit
    738  * operand traps there. Here we legalize the 6 overflow intrinsics for a
    739  * 64-bit operand pair into 32-bit lane ops, computing both the
    740  * 64-bit wrapped value (stored to a fresh 8-byte temp) and the boolean
    741  * overflow flag, then pushing [value, ok] exactly as the native path does.
    742  * add/sub reuse the carry/borrow lane logic; mul builds the full 128-bit
    743  * product from 32x32->64 partials (no MULHU opcode exists, so each partial
    744  * is itself synthesized from 16-bit halves).
    745  * ============================================================ */
    746 
    747 /* Unsigned 32x32 -> 64 product of i32 lanes a,b, returned as (*plo,*phi) i32
    748  * via the 16-bit-halves schoolbook method (the target has no high-multiply
    749  * opcode, and a plain BO_IMUL only yields the low 32 bits).
    750  *
    751  *   a = ah*2^16 + al,  b = bh*2^16 + bl
    752  *   a*b = ah*bh*2^32 + (ah*bl + al*bh)*2^16 + al*bl
    753  */
    754 static void wide8_umul32(KitCg* g, Operand a, Operand b, Operand* plo,
    755                          Operand* phi) {
    756   KitCgTypeId i32 = builtin_id(KIT_CG_BUILTIN_I32);
    757   Operand mask = api_op_imm(0xffff, i32);
    758   Operand sh16 = api_op_imm(16, i32);
    759   Operand al = wide8_i32_binop(g, BO_AND, a, mask);
    760   Operand ah = wide8_i32_binop(g, BO_SHR_U, a, sh16);
    761   Operand bl = wide8_i32_binop(g, BO_AND, b, mask);
    762   Operand bh = wide8_i32_binop(g, BO_SHR_U, b, sh16);
    763   Operand ll =
    764       wide8_i32_binop(g, BO_IMUL, al, bl); /* bits 0..31  (<=32 bits) */
    765   Operand lh = wide8_i32_binop(g, BO_IMUL, al, bh); /* bits 16..47 */
    766   Operand hl = wide8_i32_binop(g, BO_IMUL, ah, bl); /* bits 16..47 */
    767   Operand hh = wide8_i32_binop(g, BO_IMUL, ah, bh); /* bits 32..63 */
    768   /* mid = lh + hl + (ll >> 16); a 33-bit sum -> track carry into bit 32. */
    769   Operand ll_hi = wide8_i32_binop(g, BO_SHR_U, ll, sh16);
    770   Operand mid = wide8_i32_binop(g, BO_IADD, lh, hl);
    771   /* carry out of (lh+hl) into bit 48 (i.e. +2^32 in the high word). */
    772   Operand c0 = wide8_i32_cmp(g, CMP_LT_U, mid, lh);
    773   Operand mid2 = wide8_i32_binop(g, BO_IADD, mid, ll_hi);
    774   Operand c1 = wide8_i32_cmp(g, CMP_LT_U, mid2, mid);
    775   Operand carry32 = wide8_i32_binop(g, BO_IADD, c0, c1); /* into high word */
    776   /* lo = (mid2 << 16) | (ll & 0xffff) */
    777   Operand mid2_lo = wide8_i32_binop(g, BO_AND, mid2, mask);
    778   Operand mid2_loshift = wide8_i32_binop(g, BO_SHL, mid2_lo, sh16);
    779   Operand ll_lo = wide8_i32_binop(g, BO_AND, ll, mask);
    780   *plo = wide8_i32_binop(g, BO_OR, mid2_loshift, ll_lo);
    781   /* hi = hh + (mid2 >> 16) + carry32*2^16 */
    782   Operand mid2_hi = wide8_i32_binop(g, BO_SHR_U, mid2, sh16);
    783   Operand carry_word = wide8_i32_binop(g, BO_SHL, carry32, sh16);
    784   Operand hi = wide8_i32_binop(g, BO_IADD, hh, mid2_hi);
    785   *phi = wide8_i32_binop(g, BO_IADD, hi, carry_word);
    786 }
    787 
    788 /* Add three i32 columns acc += addend, threading carry: returns the new sum and
    789  * adds the unsigned-wrap carry (0/1) into *carry. */
    790 static Operand wide8_addc(KitCg* g, Operand acc, Operand addend,
    791                           Operand* carry) {
    792   Operand sum = wide8_i32_binop(g, BO_IADD, acc, addend);
    793   Operand c = wide8_i32_cmp(g, CMP_LT_U, sum, acc);
    794   *carry = wide8_i32_binop(g, BO_IADD, *carry, c);
    795   return sum;
    796 }
    797 
    798 /* The 6 __builtin_*_overflow intrinsics for a split-lane wide64 operand pair.
    799  * Pops the two 8-byte args, computes the wrapped 64-bit value into a fresh
    800  * 8-byte temp and the bool overflow flag into an i32, then pushes [value, ok]
    801  * matching the contract of the native overflow path. */
    802 static void api_wide64_overflow_inline(KitCg* g, KitCgIntrinsic intrin) {
    803   KitCgTypeId i32 = builtin_id(KIT_CG_BUILTIN_I32);
    804   KitCgTypeId bool_ty = builtin_id(KIT_CG_BUILTIN_BOOL);
    805   Operand sh31 = api_op_imm(31, i32);
    806   ApiSValue b = api_pop(g);
    807   ApiSValue a = api_pop(g);
    808   KitCgTypeId ty = a.type ? a.type : b.type;
    809   int lo = wide8_lo_off(g), hi = wide8_hi_off(g);
    810   Operand aa = api_wide8_addr(g, &a, ty);
    811   Operand ab = api_wide8_addr(g, &b, ty);
    812   Operand alo = api_wide8_load_lane(g, aa, lo);
    813   Operand ahi = api_wide8_load_lane(g, aa, hi);
    814   Operand blo = api_wide8_load_lane(g, ab, lo);
    815   Operand bhi = api_wide8_load_lane(g, ab, hi);
    816   CGLocal res = api_wide8_temp_local(g, ty);
    817   ApiSValue res_lv = api_make_lv(api_op_local(res, ty), ty);
    818   Operand ar = api_lvalue_addr(g, &res_lv, cg_type_ptr_to(g->c, ty));
    819   Operand rlo;
    820   Operand rhi;
    821   Operand ok;
    822   switch (intrin) {
    823     case KIT_CG_INTRIN_UADD_OVERFLOW:
    824     case KIT_CG_INTRIN_SADD_OVERFLOW: {
    825       Operand carry;
    826       rlo = wide8_i32_binop(g, BO_IADD, alo, blo);
    827       carry = wide8_i32_cmp(g, CMP_LT_U, rlo, alo);
    828       rhi = wide8_i32_binop(g, BO_IADD, ahi, bhi);
    829       /* carry-out of the high lane = (rhi<ahi) before +carry, OR wrap on
    830        * +carry. Compute rhi step by step so we can detect the final carry-out.
    831        */
    832       Operand c_hi0 = wide8_i32_cmp(g, CMP_LT_U, rhi, ahi);
    833       rhi = wide8_i32_binop(g, BO_IADD, rhi, carry);
    834       Operand c_hi1 = wide8_i32_cmp(g, CMP_LT_U, rhi, carry);
    835       if (intrin == KIT_CG_INTRIN_UADD_OVERFLOW) {
    836         /* unsigned: ok = carry-out of the high lane */
    837         ok = wide8_i32_binop(g, BO_OR, c_hi0, c_hi1);
    838       } else {
    839         /* signed: ok = ((a_hi ^ r_hi) & (b_hi ^ r_hi)) sign bit (bit 31) */
    840         Operand ar_x = wide8_i32_binop(g, BO_XOR, ahi, rhi);
    841         Operand br_x = wide8_i32_binop(g, BO_XOR, bhi, rhi);
    842         Operand both = wide8_i32_binop(g, BO_AND, ar_x, br_x);
    843         ok = wide8_i32_binop(g, BO_SHR_U, both, sh31);
    844       }
    845       break;
    846     }
    847     case KIT_CG_INTRIN_USUB_OVERFLOW:
    848     case KIT_CG_INTRIN_SSUB_OVERFLOW: {
    849       Operand borrow = wide8_i32_cmp(g, CMP_LT_U, alo, blo);
    850       rlo = wide8_i32_binop(g, BO_ISUB, alo, blo);
    851       Operand t = wide8_i32_binop(g, BO_ISUB, ahi, bhi);
    852       /* high-lane borrow-out: (ahi < bhi) OR (t < borrow after subtracting). */
    853       Operand b_hi0 = wide8_i32_cmp(g, CMP_LT_U, ahi, bhi);
    854       Operand b_hi1 = wide8_i32_cmp(g, CMP_LT_U, t, borrow);
    855       rhi = wide8_i32_binop(g, BO_ISUB, t, borrow);
    856       if (intrin == KIT_CG_INTRIN_USUB_OVERFLOW) {
    857         ok = wide8_i32_binop(g, BO_OR, b_hi0, b_hi1);
    858       } else {
    859         /* signed: ok = ((a_hi ^ b_hi) & (a_hi ^ r_hi)) sign bit (bit 31) */
    860         Operand ab_x = wide8_i32_binop(g, BO_XOR, ahi, bhi);
    861         Operand ar_x = wide8_i32_binop(g, BO_XOR, ahi, rhi);
    862         Operand both = wide8_i32_binop(g, BO_AND, ab_x, ar_x);
    863         ok = wide8_i32_binop(g, BO_SHR_U, both, sh31);
    864       }
    865       break;
    866     }
    867     case KIT_CG_INTRIN_UMUL_OVERFLOW:
    868     case KIT_CG_INTRIN_SMUL_OVERFLOW: {
    869       int is_signed = (intrin == KIT_CG_INTRIN_SMUL_OVERFLOW);
    870       /* For signed, compute |a|,|b| as unsigned 64-bit, do the unsigned 128-bit
    871        * product, then apply the result sign. Overflow tests below use the
    872        * unsigned magnitude product plus the expected sign. */
    873       Operand ua_lo = alo, ua_hi = ahi, ub_lo = blo, ub_hi = bhi;
    874       Operand sgn = (Operand){0};
    875       if (is_signed) {
    876         /* a_sign = ahi >> 31 (0 or 1 in i32, but as a mask we want -1/0). */
    877         Operand am = wide8_i32_binop(g, BO_SHR_S, ahi, sh31); /* 0 or -1 */
    878         Operand bm = wide8_i32_binop(g, BO_SHR_S, bhi, sh31);
    879         /* |a| = (a ^ am) - am  (two's-complement abs), lane-wise w/ borrow. */
    880         Operand axl = wide8_i32_binop(g, BO_XOR, alo, am);
    881         Operand axh = wide8_i32_binop(g, BO_XOR, ahi, am);
    882         Operand brwa = wide8_i32_cmp(g, CMP_LT_U, axl, am);
    883         ua_lo = wide8_i32_binop(g, BO_ISUB, axl, am);
    884         Operand tah = wide8_i32_binop(g, BO_ISUB, axh, am);
    885         ua_hi = wide8_i32_binop(g, BO_ISUB, tah, brwa);
    886         Operand bxl = wide8_i32_binop(g, BO_XOR, blo, bm);
    887         Operand bxh = wide8_i32_binop(g, BO_XOR, bhi, bm);
    888         Operand brwb = wide8_i32_cmp(g, CMP_LT_U, bxl, bm);
    889         ub_lo = wide8_i32_binop(g, BO_ISUB, bxl, bm);
    890         Operand tbh = wide8_i32_binop(g, BO_ISUB, bxh, bm);
    891         ub_hi = wide8_i32_binop(g, BO_ISUB, tbh, brwb);
    892         sgn = wide8_i32_binop(g, BO_XOR, am, bm); /* result sign mask -1/0 */
    893       }
    894       /* Unsigned 128-bit product of (ua_hi:ua_lo) * (ub_hi:ub_lo).
    895        *   P00 = ua_lo*ub_lo  -> columns 0,1
    896        *   P01 = ua_lo*ub_hi  -> columns 1,2
    897        *   P10 = ua_hi*ub_lo  -> columns 1,2
    898        *   P11 = ua_hi*ub_hi  -> columns 2,3 */
    899       Operand p00l, p00h, p01l, p01h, p10l, p10h, p11l, p11h;
    900       wide8_umul32(g, ua_lo, ub_lo, &p00l, &p00h);
    901       wide8_umul32(g, ua_lo, ub_hi, &p01l, &p01h);
    902       wide8_umul32(g, ua_hi, ub_lo, &p10l, &p10h);
    903       wide8_umul32(g, ua_hi, ub_hi, &p11l, &p11h);
    904       Operand zero = api_op_imm(0, i32);
    905       /* column 0 */
    906       Operand r0 = p00l;
    907       /* column 1 = p00h + p01l + p10l */
    908       Operand c1 = zero;
    909       Operand r1 = p00h;
    910       r1 = wide8_addc(g, r1, p01l, &c1);
    911       r1 = wide8_addc(g, r1, p10l, &c1);
    912       /* column 2 = p01h + p10h + p11l + c1 */
    913       Operand c2 = zero;
    914       Operand r2 = p01h;
    915       r2 = wide8_addc(g, r2, p10h, &c2);
    916       r2 = wide8_addc(g, r2, p11l, &c2);
    917       r2 = wide8_addc(g, r2, c1, &c2);
    918       /* column 3 = p11h + c2 */
    919       Operand r3 = wide8_i32_binop(g, BO_IADD, p11h, c2);
    920       /* low 64 bits = (r1:r0); high 64 bits = (r3:r2). */
    921       Operand mlo = r0, mhi = r1;
    922       Operand hi_lo = r2, hi_hi = r3;
    923       if (is_signed) {
    924         /* Apply result sign: negate the 128-bit magnitude if sgn==-1.
    925          * negated = (x ^ sgn) - sgn across all 4 words with borrow. */
    926         Operand w0 = wide8_i32_binop(g, BO_XOR, mlo, sgn);
    927         Operand w1 = wide8_i32_binop(g, BO_XOR, mhi, sgn);
    928         Operand w2 = wide8_i32_binop(g, BO_XOR, hi_lo, sgn);
    929         Operand w3 = wide8_i32_binop(g, BO_XOR, hi_hi, sgn);
    930         Operand bor0 = wide8_i32_cmp(g, CMP_LT_U, w0, sgn);
    931         mlo = wide8_i32_binop(g, BO_ISUB, w0, sgn);
    932         Operand t1 = wide8_i32_binop(g, BO_ISUB, w1, sgn);
    933         Operand bor1a = wide8_i32_cmp(g, CMP_LT_U, w1, sgn);
    934         Operand bor1b = wide8_i32_cmp(g, CMP_LT_U, t1, bor0);
    935         mhi = wide8_i32_binop(g, BO_ISUB, t1, bor0);
    936         Operand bor1 = wide8_i32_binop(g, BO_OR, bor1a, bor1b);
    937         Operand t2 = wide8_i32_binop(g, BO_ISUB, w2, sgn);
    938         Operand bor2a = wide8_i32_cmp(g, CMP_LT_U, w2, sgn);
    939         Operand bor2b = wide8_i32_cmp(g, CMP_LT_U, t2, bor1);
    940         hi_lo = wide8_i32_binop(g, BO_ISUB, t2, bor1);
    941         Operand bor2 = wide8_i32_binop(g, BO_OR, bor2a, bor2b);
    942         Operand t3 = wide8_i32_binop(g, BO_ISUB, w3, sgn);
    943         hi_hi = wide8_i32_binop(g, BO_ISUB, t3, bor2);
    944       }
    945       rlo = mlo;
    946       rhi = mhi;
    947       if (!is_signed) {
    948         /* unsigned overflow: high 64 bits nonzero. */
    949         Operand t = wide8_i32_binop(g, BO_OR, hi_lo, hi_hi);
    950         ok = wide8_i32_cmp(g, CMP_NE, t, zero);
    951       } else {
    952         /* signed overflow: the 128-bit result is not the sign-extension of its
    953          * low 64 bits. sext = (rhi >> 31) replicated; overflow if
    954          * (hi_lo != sext) | (hi_hi != sext) where sext = arithmetic sign of
    955          * the signed low-64 result (bit 63 = rhi sign). */
    956         Operand sext = wide8_i32_binop(g, BO_SHR_S, rhi, sh31); /* 0 or -1 */
    957         Operand d2 = wide8_i32_binop(g, BO_XOR, hi_lo, sext);
    958         Operand d3 = wide8_i32_binop(g, BO_XOR, hi_hi, sext);
    959         Operand d = wide8_i32_binop(g, BO_OR, d2, d3);
    960         ok = wide8_i32_cmp(g, CMP_NE, d, zero);
    961       }
    962       break;
    963     }
    964     default:
    965       compiler_panic(g->c, g->cur_loc,
    966                      "KitCg: unsupported wide i64 overflow intrinsic");
    967       api_release(g, &a);
    968       api_release(g, &b);
    969       return;
    970   }
    971   api_wide8_store_lane(g, ar, lo, rlo);
    972   api_wide8_store_lane(g, ar, hi, rhi);
    973   api_release(g, &a);
    974   api_release(g, &b);
    975   /* Materialize ok as a fresh bool temp so it has a stable home. */
    976   {
    977     CGLocal okl = api_alloc_temp_local(g, bool_ty);
    978     Operand okd = api_op_local(okl, bool_ty);
    979     g->target->binop(g->target, BO_AND, okd, ok, api_op_imm(1, i32));
    980     api_push(g, api_make_sv(api_op_local(res, ty), ty));
    981     api_push(g, api_make_sv(okd, bool_ty));
    982   }
    983 }
    984 
    985 /* int<->split-i64 conversions (sext/zext/trunc/bitcast across the 4<->8
    986  * boundary, and i64->bool). Returns 1 if it handled (and consumed) *v. The
    987  * i64<->float conversions are routed to libcalls in kit_cg_*_to_float /
    988  * kit_cg_float_to_* and never reach here. */
    989 int api_try_wide8_convert(KitCg* g, ConvKind ck, KitCgTypeId sty,
    990                           KitCgTypeId dty, ApiSValue* v) {
    991   KitCgTypeId i32 = builtin_id(KIT_CG_BUILTIN_I32);
    992   int s_wide = api_is_wide8_scalar_type(g->c, sty);
    993   int d_wide = api_is_wide8_scalar_type(g->c, dty);
    994   int lo = wide8_lo_off(g), hi = wide8_hi_off(g);
    995   if (!s_wide && !d_wide) return 0;
    996   if (s_wide && d_wide) {
    997     /* i64<->soft-double reinterpret (same 8-byte layout) or i64<->u64. */
    998     v->type = dty;
    999     v->op.type = dty;
   1000     api_push(g, *v);
   1001     return 1;
   1002   }
   1003   if (d_wide) {
   1004     /* narrower int -> i64: low lane is the (converted-to-i32) source; high lane
   1005      * is the sign-extension (CV_SEXT) or zero (CV_ZEXT/CV_BITCAST of a ptr). */
   1006     int sext = (ck == CV_SEXT);
   1007     Operand src32;
   1008     CGLocal res;
   1009     ApiSValue res_lv;
   1010     Operand ar;
   1011     Operand hival;
   1012     if (sty != i32) {
   1013       api_push(g, *v);
   1014       api_cg_convert_kind(g, i32, ck == CV_SEXT ? CV_SEXT : CV_ZEXT);
   1015       *v = api_pop(g);
   1016     }
   1017     src32 = api_force_local(g, v, i32);
   1018     res = api_wide8_temp_local(g, dty);
   1019     res_lv = api_make_lv(api_op_local(res, dty), dty);
   1020     ar = api_lvalue_addr(g, &res_lv, cg_type_ptr_to(g->c, dty));
   1021     api_wide8_store_lane(g, ar, lo, src32);
   1022     if (sext)
   1023       hival = wide8_i32_binop(g, BO_SHR_S, src32, api_op_imm(31, i32));
   1024     else
   1025       hival = api_op_imm(0, i32);
   1026     api_wide8_store_lane(g, ar, hi, hival);
   1027     api_release(g, v);
   1028     api_push(g, api_make_sv(api_op_local(res, dty), dty));
   1029     return 1;
   1030   }
   1031   /* s_wide: i64 -> narrower. _Bool is "any bit set"; else take the low lane and
   1032    * truncate/convert further. */
   1033   if (api_is_bool_type(g->c, dty)) {
   1034     Operand orl = api_wide8_or_lanes(g, v, sty);
   1035     api_release(g, v);
   1036     api_push(g, api_make_sv(orl, i32));
   1037     kit_cg_push_int(g, 0, i32);
   1038     api_cg_cmp(g, CMP_NE);
   1039     api_cg_convert_kind(g, dty, CV_TRUNC);
   1040     return 1;
   1041   }
   1042   {
   1043     Operand addr = api_wide8_addr(g, v, sty);
   1044     Operand lolane = api_wide8_load_lane(g, addr, lo);
   1045     api_release(g, v);
   1046     api_push(g, api_make_sv(lolane, i32));
   1047     if (dty != i32) api_cg_convert_kind(g, dty, CV_TRUNC);
   1048     return 1;
   1049   }
   1050 }
   1051 
   1052 static void api_i128_unop(KitCg* g, UnOp iop) {
   1053   KitCgTypeId i128 = builtin_id(KIT_CG_BUILTIN_I128);
   1054   const char* name = NULL;
   1055   ApiSValue args[1];
   1056   KitCgTypeId ps[1];
   1057   if (iop == UO_NEG)
   1058     name = "__negti2";
   1059   else if (iop == UO_BNOT)
   1060     name = "__kit_notti3";
   1061   else {
   1062     compiler_panic(g->c, g->cur_loc, "KitCg: unsupported i128 unop");
   1063     return;
   1064   }
   1065   args[0] = api_pop(g);
   1066   ps[0] = i128;
   1067   api_runtime_call_values(g, name, i128, ps, 1, args);
   1068 }
   1069 
   1070 /* Map a relational op to the form used to compare a __kit_*cmpti2
   1071  * result (-1/0/1, a signed i32) against zero. */
   1072 static CmpOp api_i128_cmp_vs_zero(CmpOp cop) {
   1073   switch (cop) {
   1074     case CMP_EQ:
   1075       return CMP_EQ;
   1076     case CMP_NE:
   1077       return CMP_NE;
   1078     case CMP_LT_S:
   1079     case CMP_LT_U:
   1080       return CMP_LT_S;
   1081     case CMP_LE_S:
   1082     case CMP_LE_U:
   1083       return CMP_LE_S;
   1084     case CMP_GT_S:
   1085     case CMP_GT_U:
   1086       return CMP_GT_S;
   1087     case CMP_GE_S:
   1088     case CMP_GE_U:
   1089       return CMP_GE_S;
   1090     default:
   1091       return CMP_NE;
   1092   }
   1093 }
   1094 
   1095 static void api_i128_cmp(KitCg* g, CmpOp cop) {
   1096   KitCgTypeId i128 = builtin_id(KIT_CG_BUILTIN_I128);
   1097   KitCgTypeId i32 = builtin_id(KIT_CG_BUILTIN_I32);
   1098   const char* name =
   1099       api_i128_cmp_is_unsigned(cop) ? "__kit_ucmpti2" : "__kit_cmpti2";
   1100   KitCgTypeId ps[2] = {i128, i128};
   1101   ApiSValue args[2];
   1102   args[1] = api_pop(g);
   1103   args[0] = api_pop(g);
   1104   api_runtime_call_values(g, name, i32, ps, 2, args);
   1105   kit_cg_push_int(g, 0, i32);
   1106   api_cg_cmp(g, api_i128_cmp_vs_zero(cop));
   1107 }
   1108 
   1109 /* int<->i128 conversions. Returns 1 if it handled the conversion and
   1110  * consumed *v, 0 to fall through to the generic path. */
   1111 int api_try_i128_convert(KitCg* g, ConvKind ck, KitCgTypeId sty,
   1112                          KitCgTypeId dty, ApiSValue* v) {
   1113   KitCgTypeId i128 = builtin_id(KIT_CG_BUILTIN_I128);
   1114   KitCgTypeId i64 = builtin_id(KIT_CG_BUILTIN_I64);
   1115   int s_is_128 = api_is_i128_type(g->c, sty);
   1116   int d_is_128 = api_is_i128_type(g->c, dty);
   1117   if (!s_is_128 && !d_is_128) return 0;
   1118   if (s_is_128 && d_is_128) {
   1119     /* signed<->unsigned i128 reinterpret: identical layout. */
   1120     v->type = dty;
   1121     v->op.type = dty;
   1122     api_push(g, *v);
   1123     return 1;
   1124   }
   1125   if (d_is_128) {
   1126     u32 sw = kit_cg_type_int_width((KitCompiler*)g->c, sty);
   1127     const char* name = (ck == CV_SEXT) ? "__kit_sext64ti" : "__kit_zext64ti";
   1128     ApiSValue arg;
   1129     KitCgTypeId ps[1];
   1130     if (sw == 0) return 0; /* float->i128 unsupported here */
   1131     if (sw >= 64) {
   1132       arg = *v;
   1133       arg.type = i64;
   1134       arg.op.type = i64;
   1135     } else {
   1136       api_push(g, *v);
   1137       api_cg_convert_kind(g, i64, ck);
   1138       arg = api_pop(g);
   1139     }
   1140     ps[0] = i64;
   1141     api_runtime_call_values(g, name, i128, ps, 1, &arg);
   1142     return 1;
   1143   }
   1144   /* s_is_128, dty is _Bool: "value != 0" over the full 128 bits, not a
   1145    * low-lane truncation (a value whose only set bits are above bit 63 must
   1146    * still become 1). Reuse the runtime i128 compare. */
   1147   if (api_is_bool_type(g->c, dty)) {
   1148     api_push(g, *v);
   1149     kit_cg_push_int(g, 0, i128);
   1150     api_i128_cmp(g, CMP_NE); /* leaves i32 0/1 */
   1151     api_cg_convert_kind(g, dty, CV_TRUNC);
   1152     return 1;
   1153   }
   1154   /* s_is_128, dty is a narrower integer: take the low 64 bits, then
   1155    * truncate further if needed. */
   1156   {
   1157     u32 dw = kit_cg_type_int_width((KitCompiler*)g->c, dty);
   1158     i32 lo_off = g->c->target.big_endian ? 8 : 0;
   1159     Operand addr;
   1160     Operand lo;
   1161     if (dw == 0) return 0; /* i128->float unsupported here */
   1162     addr = api_i128_addr(g, v);
   1163     lo = api_i128_load_lane(g, addr, lo_off);
   1164     api_release(g, v);
   1165     if (dw >= 64) {
   1166       api_push(g, api_make_sv(lo, dty));
   1167     } else {
   1168       api_push(g, api_make_sv(lo, i64));
   1169       api_cg_convert_kind(g, dty, CV_TRUNC);
   1170     }
   1171     return 1;
   1172   }
   1173 }
   1174 
   1175 void kit_cg_int_binop(KitCg* g, KitCgIntBinOp op, uint32_t flags) {
   1176   BinOp iop = api_map_int_binop(op);
   1177   ApiConstValue result_const;
   1178   KitCgTypeId ty = KIT_CG_TYPE_NONE;
   1179   if (g && g->sp >= 2u) {
   1180     ApiConstValue cb = api_const_at(g, 0);
   1181     ApiConstValue ca = api_const_at(g, 1);
   1182     ty = api_sv_type(&g->stack[g->sp - 2u]);
   1183     api_const_fold_binop(g, iop, ty, ca, cb, flags, &result_const);
   1184   } else {
   1185     result_const = api_const_unknown(KIT_CG_TYPE_NONE);
   1186   }
   1187   if (api_unevaluated(g)) {
   1188     api_cg_binop(g, iop, flags);
   1189     return;
   1190   }
   1191   if (g && (api_i128_stack_top(g, 0) || api_i128_stack_top(g, 1))) {
   1192     api_i128_binop(g, iop);
   1193     api_const_set_top(g, result_const);
   1194     return;
   1195   }
   1196   /* 64-bit int split into 32-bit lanes: mul/div/rem/shift become __*di3
   1197    * runtime calls; add/sub/and/or/xor are emitted inline as 2-word lane ops
   1198    * (no compiler-rt helper exists for them). Both keep the value
   1199    * memory-resident so the allocator never tries to put 8 bytes in one 4-byte
   1200    * value slot. */
   1201   if (g && (api_wide64_stack_top(g, 0) || api_wide64_stack_top(g, 1))) {
   1202     if (api_wideint64_binop_helper(iop))
   1203       api_wideint64_binop(g, iop);
   1204     else
   1205       api_wide64_binop_inline(g, iop);
   1206     api_const_set_top(g, result_const);
   1207     return;
   1208   }
   1209   api_cg_binop(g, iop, flags);
   1210 }
   1211 
   1212 void kit_cg_int_unop(KitCg* g, KitCgIntUnOp op, uint32_t flags) {
   1213   UnOp iop = api_map_int_unop(op);
   1214   ApiConstValue result_const;
   1215   KitCgTypeId ty = KIT_CG_TYPE_NONE;
   1216   if (g && g->sp >= 1u) {
   1217     ApiConstValue ca = api_const_at(g, 0);
   1218     ty = api_sv_type(&g->stack[g->sp - 1u]);
   1219     api_const_fold_unop(g, iop, ty, ca, flags, &result_const);
   1220   } else {
   1221     result_const = api_const_unknown(KIT_CG_TYPE_NONE);
   1222   }
   1223   if (api_unevaluated(g)) {
   1224     api_cg_unop(g, iop, flags);
   1225     return;
   1226   }
   1227   if (g && api_i128_stack_top(g, 0) && (iop == UO_NEG || iop == UO_BNOT)) {
   1228     api_i128_unop(g, iop);
   1229     api_const_set_top(g, result_const);
   1230     return;
   1231   }
   1232   /* Split 64-bit int: neg/bnot are inline 2-word lane ops; logical-not (!x) is
   1233    * the full-value truthiness test (lo|hi)==0. */
   1234   if (g && api_wide64_stack_top(g, 0)) {
   1235     if (iop == UO_NEG || iop == UO_BNOT) {
   1236       api_wide64_unop_inline(g, iop);
   1237       api_const_set_top(g, result_const);
   1238       return;
   1239     }
   1240     if (iop == UO_NOT) {
   1241       KitCgTypeId i32 = builtin_id(KIT_CG_BUILTIN_I32);
   1242       ApiSValue v = api_pop(g);
   1243       KitCgTypeId ty = v.type ? v.type : v.op.type;
   1244       Operand orl = api_wide8_or_lanes(g, &v, ty);
   1245       api_release(g, &v);
   1246       api_push(g, api_make_sv(orl, i32));
   1247       kit_cg_push_int(g, 0, i32);
   1248       api_cg_cmp(g, CMP_EQ);
   1249       api_cg_convert_kind(g, ty, CV_ZEXT);
   1250       api_const_set_top(g, result_const);
   1251       return;
   1252     }
   1253   }
   1254   api_cg_unop(g, iop, flags);
   1255 }
   1256 
   1257 void kit_cg_int_cmp(KitCg* g, KitCgIntCmpOp op) {
   1258   CmpOp cop = api_map_int_cmp(op);
   1259   ApiConstValue result_const;
   1260   if (g && g->sp >= 2u) {
   1261     ApiConstValue cb = api_const_at(g, 0);
   1262     ApiConstValue ca = api_const_at(g, 1);
   1263     api_const_fold_cmp(g, cop, ca, cb, &result_const);
   1264   } else {
   1265     result_const = api_const_unknown(builtin_id(KIT_CG_BUILTIN_I32));
   1266   }
   1267   if (api_unevaluated(g)) {
   1268     api_cg_cmp(g, cop);
   1269     return;
   1270   }
   1271   if (g && (api_i128_stack_top(g, 0) || api_i128_stack_top(g, 1))) {
   1272     api_i128_cmp(g, cop);
   1273     api_const_set_top(g, result_const);
   1274     return;
   1275   }
   1276   if (g && (api_wide64_stack_top(g, 0) || api_wide64_stack_top(g, 1))) {
   1277     api_wide64_cmp_inline(g, cop);
   1278     api_const_set_top(g, result_const);
   1279     return;
   1280   }
   1281   api_cg_cmp(g, cop);
   1282 }
   1283 
   1284 const char* api_i128_binop_helper(BinOp op) {
   1285   switch (op) {
   1286     case BO_IADD:
   1287       return "__kit_addti3";
   1288     case BO_ISUB:
   1289       return "__kit_subti3";
   1290     case BO_IMUL:
   1291       return "__multi3";
   1292     case BO_SDIV:
   1293       return "__divti3";
   1294     case BO_UDIV:
   1295       return "__udivti3";
   1296     case BO_SREM:
   1297       return "__modti3";
   1298     case BO_UREM:
   1299       return "__umodti3";
   1300     case BO_AND:
   1301       return "__kit_andti3";
   1302     case BO_OR:
   1303       return "__kit_orti3";
   1304     case BO_XOR:
   1305       return "__kit_xorti3";
   1306     case BO_SHL:
   1307       return "__ashlti3";
   1308     case BO_SHR_U:
   1309       return "__lshrti3";
   1310     case BO_SHR_S:
   1311       return "__ashrti3";
   1312     case BO_FADD:
   1313     case BO_FSUB:
   1314     case BO_FMUL:
   1315     case BO_FDIV:
   1316     default:
   1317       return NULL;
   1318   }
   1319 }
   1320 
   1321 int api_i128_cmp_is_unsigned(CmpOp op) {
   1322   return op == CMP_LT_U || op == CMP_LE_U || op == CMP_GT_U || op == CMP_GE_U;
   1323 }
   1324 
   1325 const char* api_f128_binop_helper(KitCgFpBinOp op) {
   1326   switch (op) {
   1327     case KIT_CG_FP_ADD:
   1328       return "__addtf3";
   1329     case KIT_CG_FP_SUB:
   1330       return "__subtf3";
   1331     case KIT_CG_FP_MUL:
   1332       return "__multf3";
   1333     case KIT_CG_FP_DIV:
   1334       return "__divtf3";
   1335   }
   1336   return NULL;
   1337 }
   1338 
   1339 /* Runtime helper name for double (f64) arithmetic on a target that lacks a
   1340  * hardware double unit. Mirrors api_f128_binop_helper with the __*df3 names. */
   1341 static const char* api_softdf_binop_helper(KitCgFpBinOp op) {
   1342   switch (op) {
   1343     case KIT_CG_FP_ADD:
   1344       return "__adddf3";
   1345     case KIT_CG_FP_SUB:
   1346       return "__subdf3";
   1347     case KIT_CG_FP_MUL:
   1348       return "__muldf3";
   1349     case KIT_CG_FP_DIV:
   1350       return "__divdf3";
   1351   }
   1352   return NULL;
   1353 }
   1354 
   1355 int api_f128_stack_top(KitCg* g, u32 depth) {
   1356   return api_stack_wide_kind(g, depth) == WK_F128;
   1357 }
   1358 
   1359 /* True when the target has no hardware double: float_abi is SOFT (ilp32/lp64,
   1360  * no FP regs) or SINGLE (ilp32f/lp64f, only float in FP regs — double is always
   1361  * soft). DOUBLE (rv64 lp64d) and DEFAULT (x64/aa64 hardware-double targets that
   1362  * never set float_abi) keep the inline hardware path, so existing rv64/x64/aa64
   1363  * codegen is unchanged. */
   1364 static int api_target_double_is_soft(KitCg* g) {
   1365   if (!g) return 0;
   1366   return g->c->target.float_abi == KIT_FLOAT_ABI_SOFT ||
   1367          g->c->target.float_abi == KIT_FLOAT_ABI_SINGLE;
   1368 }
   1369 
   1370 /* True when ty is a 64-bit float (double) AND the target lacks hardware double.
   1371  * f128 is handled by the separate api_f128_* path, so width must be exactly 64.
   1372  */
   1373 static int api_type_is_soft_double(KitCg* g, KitCgTypeId ty) {
   1374   if (!api_target_double_is_soft(g)) return 0;
   1375   return kit_cg_type_float_width((KitCompiler*)g->c, ty) == 64;
   1376 }
   1377 
   1378 static int api_soft_double_stack_top(KitCg* g, u32 depth) {
   1379   return api_stack_wide_kind(g, depth) == WK_SOFT_DOUBLE;
   1380 }
   1381 
   1382 /* f32 under pure-soft ilp32/lp64 (float_abi SOFT, no FP unit): single-precision
   1383  * arithmetic/compare/convert is also a libcall. Under SINGLE (ilp32f) float is
   1384  * hardware (fadd.s etc.) so this is false; DOUBLE/DEFAULT keep hardware too. */
   1385 static int api_type_is_soft_single(KitCg* g, KitCgTypeId ty) {
   1386   if (!g || g->c->target.float_abi != KIT_FLOAT_ABI_SOFT) return 0;
   1387   return kit_cg_type_float_width((KitCompiler*)g->c, ty) == 32;
   1388 }
   1389 
   1390 static int api_soft_single_stack_top(KitCg* g, u32 depth) {
   1391   return api_stack_wide_kind(g, depth) == WK_SOFT_SINGLE;
   1392 }
   1393 
   1394 /* Runtime helper for f32 arithmetic on a soft-float target (mirrors
   1395  * api_softdf_binop_helper with the __*sf3 names). */
   1396 static const char* api_softsf_binop_helper(KitCgFpBinOp op) {
   1397   switch (op) {
   1398     case KIT_CG_FP_ADD:
   1399       return "__addsf3";
   1400     case KIT_CG_FP_SUB:
   1401       return "__subsf3";
   1402     case KIT_CG_FP_MUL:
   1403       return "__mulsf3";
   1404     case KIT_CG_FP_DIV:
   1405       return "__divsf3";
   1406   }
   1407   return NULL;
   1408 }
   1409 
   1410 /* Soft-float binary arithmetic via a single libcall `name(a,b)`, both operands
   1411  * (and the result) of type `opty`. Consumes the two operands on the stack and
   1412  * pushes the result. Shared by the f128 (tf), soft-double (df) and soft-single
   1413  * (sf) paths — only the helper name and operand type differ; the runtime ABI
   1414  * (two same-type args, same-type result) is width-neutral. `what` names the
   1415  * width in the panic raised when `op` has no helper. Mirrors api_softfp_cmp. */
   1416 static void api_softfp_binop(KitCg* g, const char* name, KitCgTypeId opty,
   1417                              const char* what) {
   1418   KitCgTypeId ps[2];
   1419   ApiSValue args[2];
   1420   if (!name)
   1421     compiler_panic(g->c, g->cur_loc, "KitCg: unsupported %s binop", what);
   1422   args[1] = api_pop(g);
   1423   args[0] = api_pop(g);
   1424   ps[0] = opty;
   1425   ps[1] = opty;
   1426   api_runtime_call_values(g, name, opty, ps, 2, args);
   1427 }
   1428 
   1429 void api_f128_call_unary(KitCg* g, const char* name, KitCgTypeId ret,
   1430                          KitCgTypeId param) {
   1431   ApiSValue args[1];
   1432   KitCgTypeId ps[1];
   1433   args[0] = api_pop(g);
   1434   ps[0] = param;
   1435   api_runtime_call_values(g, name, ret, ps, 1, args);
   1436 }
   1437 
   1438 void kit_cg_fp_binop(KitCg* g, KitCgFpBinOp op, uint32_t flags) {
   1439   (void)flags;
   1440   if (api_unevaluated(g)) {
   1441     api_cg_binop(g, api_map_fp_binop(op), 0);
   1442     return;
   1443   }
   1444   if (api_f128_stack_top(g, 0) || api_f128_stack_top(g, 1)) {
   1445     api_softfp_binop(g, api_f128_binop_helper(op),
   1446                      builtin_id(KIT_CG_BUILTIN_F128), "f128");
   1447     return;
   1448   }
   1449   if (api_soft_double_stack_top(g, 0) || api_soft_double_stack_top(g, 1)) {
   1450     api_softfp_binop(g, api_softdf_binop_helper(op),
   1451                      builtin_id(KIT_CG_BUILTIN_F64), "soft double");
   1452     return;
   1453   }
   1454   if (api_soft_single_stack_top(g, 0) || api_soft_single_stack_top(g, 1)) {
   1455     api_softfp_binop(g, api_softsf_binop_helper(op),
   1456                      builtin_id(KIT_CG_BUILTIN_F32), "soft single");
   1457     return;
   1458   }
   1459   api_cg_binop(g, api_map_fp_binop(op), 0);
   1460 }
   1461 
   1462 void kit_cg_fp_unop(KitCg* g, KitCgFpUnOp op, uint32_t flags) {
   1463   (void)flags;
   1464   if (!g) return;
   1465   CG_REQUIRE(g, op == KIT_CG_FP_NEG, "KitCg: FP unary op unsupported");
   1466   if (api_unevaluated(g)) {
   1467     api_cg_unop(g, UO_FNEG, 0);
   1468     return;
   1469   }
   1470   if (api_f128_stack_top(g, 0)) {
   1471     KitCgTypeId f128 = builtin_id(KIT_CG_BUILTIN_F128);
   1472     api_f128_call_unary(g, "__negtf2", f128, f128);
   1473     return;
   1474   }
   1475   /* Soft float has no FP unit, so negation is a libcall too (the inline FNEG
   1476    * path emits fsgnj on an FP register, which does not exist here). */
   1477   if (api_soft_double_stack_top(g, 0)) {
   1478     KitCgTypeId f64 = builtin_id(KIT_CG_BUILTIN_F64);
   1479     api_f128_call_unary(g, "__negdf2", f64, f64);
   1480     return;
   1481   }
   1482   if (api_soft_single_stack_top(g, 0)) {
   1483     KitCgTypeId f32 = builtin_id(KIT_CG_BUILTIN_F32);
   1484     api_f128_call_unary(g, "__negsf2", f32, f32);
   1485     return;
   1486   }
   1487   api_cg_unop(g, UO_FNEG, 0);
   1488 }
   1489 
   1490 /* Soft-float single-libcall comparison: call `name(a,b)` (both operands of type
   1491  * `opty`) and test its i32 three-way result against 0 with `icmp`. Consumes the
   1492  * two operands on the stack and pushes the i32 boolean. Shared by the f128 (tf)
   1493  * and soft-double (df) paths — only the helper name and operand type differ;
   1494  * the compiler-rt NaN-sign convention is identical for both. */
   1495 static void api_softfp_cmp_call(KitCg* g, const char* name, KitCgTypeId opty,
   1496                                 CmpOp icmp) {
   1497   KitCgTypeId i32 = builtin_id(KIT_CG_BUILTIN_I32);
   1498   KitCgTypeId ps[2];
   1499   ApiSValue args[2];
   1500   ps[0] = opty;
   1501   ps[1] = opty;
   1502   args[1] = api_pop(g);
   1503   args[0] = api_pop(g);
   1504   api_runtime_call_values(g, name, i32, ps, 2, args);
   1505   kit_cg_push_int(g, 0, i32);
   1506   api_cg_cmp(g, icmp);
   1507 }
   1508 
   1509 /* UEQ and ONE are the only soft-float predicates that cannot be a single
   1510  * libcall: "equal" and "unordered" both yield a nonzero magnitude from
   1511  * __eq*2/__ne*2, so they need a separate __unord*2 to split them.
   1512  *   UEQ = (__eq*2(a,b) == 0) || (__unord*2(a,b) != 0)
   1513  *   ONE = (__ne*2(a,b) != 0) && (__unord*2(a,b) == 0)
   1514  * `suffix` is "tf" (f128) or "df" (double); `opty` the matching operand type.
   1515  * The operands are dup'd (kit_cg_dup copies into a fresh owned local) so each
   1516  * libcall consumes its own copy. */
   1517 static void api_softfp_cmp_with_unord(KitCg* g, KitCgFpCmpOp op,
   1518                                       const char* suffix, KitCgTypeId opty) {
   1519   char relname[16];
   1520   char unordname[16];
   1521   CmpOp relcmp = (op == KIT_CG_FP_UEQ) ? CMP_EQ : CMP_NE;
   1522   const char* rel = (op == KIT_CG_FP_UEQ) ? "eq" : "ne";
   1523   snprintf(relname, sizeof relname, "__%s%s2", rel, suffix);
   1524   snprintf(unordname, sizeof unordname, "__unord%s2", suffix);
   1525   /* [a, b] -> [a, b, a, b] */
   1526   kit_cg_dup2(g);
   1527   /* relation on the top (dup'd) copy: [a, b, R] */
   1528   api_softfp_cmp_call(g, relname, opty, relcmp);
   1529   /* bring the original a, b back to TOS with R underneath: [R, a, b] */
   1530   kit_cg_rot3(g);
   1531   kit_cg_rot3(g);
   1532   if (op == KIT_CG_FP_UEQ) {
   1533     api_softfp_cmp_call(g, unordname, opty, CMP_NE); /* [R, unordered?] */
   1534     api_cg_binop(g, BO_OR, 0);                       /* R || unordered */
   1535   } else {
   1536     api_softfp_cmp_call(g, unordname, opty, CMP_EQ); /* [R, ordered?] */
   1537     api_cg_binop(g, BO_AND, 0);                      /* R && ordered */
   1538   }
   1539 }
   1540 
   1541 /* Emit a soft-float comparison for either f128 (suffix "tf", opty f128) or
   1542  * soft double (suffix "df", opty f64). The predicate->helper mapping and the
   1543  * compiler-rt NaN-sign convention are XLEN/width-neutral, so a single body
   1544  * serves both — only the suffix and operand type vary. */
   1545 static void api_softfp_cmp(KitCg* g, KitCgFpCmpOp op, const char* suffix,
   1546                            KitCgTypeId opty) {
   1547   char name[16];
   1548   switch (op) {
   1549     case KIT_CG_FP_OEQ:
   1550       snprintf(name, sizeof name, "__eq%s2", suffix);
   1551       api_softfp_cmp_call(g, name, opty, CMP_EQ);
   1552       return;
   1553     case KIT_CG_FP_UNE:
   1554       snprintf(name, sizeof name, "__ne%s2", suffix);
   1555       api_softfp_cmp_call(g, name, opty, CMP_NE);
   1556       return;
   1557     case KIT_CG_FP_OLT:
   1558       snprintf(name, sizeof name, "__lt%s2", suffix);
   1559       api_softfp_cmp_call(g, name, opty, CMP_LT_S);
   1560       return;
   1561     case KIT_CG_FP_OLE:
   1562       snprintf(name, sizeof name, "__le%s2", suffix);
   1563       api_softfp_cmp_call(g, name, opty, CMP_LE_S);
   1564       return;
   1565     case KIT_CG_FP_OGT:
   1566       snprintf(name, sizeof name, "__gt%s2", suffix);
   1567       api_softfp_cmp_call(g, name, opty, CMP_GT_S);
   1568       return;
   1569     case KIT_CG_FP_OGE:
   1570       snprintf(name, sizeof name, "__ge%s2", suffix);
   1571       api_softfp_cmp_call(g, name, opty, CMP_GE_S);
   1572       return;
   1573     /* unordered duals via the opposite-sign helper (NaN flips the test): */
   1574     case KIT_CG_FP_UGE:
   1575       snprintf(name, sizeof name, "__lt%s2", suffix);
   1576       api_softfp_cmp_call(g, name, opty, CMP_GE_S);
   1577       return;
   1578     case KIT_CG_FP_UGT:
   1579       snprintf(name, sizeof name, "__le%s2", suffix);
   1580       api_softfp_cmp_call(g, name, opty, CMP_GT_S);
   1581       return;
   1582     case KIT_CG_FP_ULT:
   1583       snprintf(name, sizeof name, "__ge%s2", suffix);
   1584       api_softfp_cmp_call(g, name, opty, CMP_LT_S);
   1585       return;
   1586     case KIT_CG_FP_ULE:
   1587       snprintf(name, sizeof name, "__gt%s2", suffix);
   1588       api_softfp_cmp_call(g, name, opty, CMP_LE_S);
   1589       return;
   1590     case KIT_CG_FP_UEQ:
   1591     case KIT_CG_FP_ONE:
   1592       api_softfp_cmp_with_unord(g, op, suffix, opty);
   1593       return;
   1594   }
   1595 }
   1596 
   1597 void kit_cg_fp_cmp(KitCg* g, KitCgFpCmpOp op) {
   1598   /* f128/long double and soft double are both soft-float: the comparison is a
   1599    * libcall returning a three-way i32 we test against 0. kit's runtime uses the
   1600    * standard compiler-rt sign convention (rt/lib/impl/fp_compare_impl.inc):
   1601    *   __le-family (__eq*2/__ne*2/__lt*2/__le*2): NaN -> +1
   1602    *   __ge-family (__ge*2/__gt*2):               NaN -> -1
   1603    * so each ordered predicate AND its unordered dual maps to one libcall,
   1604    * choosing the helper whose NaN sign makes the integer test fall the right
   1605    * way (ordered: NaN must fail; unordered: NaN must pass). Only UEQ/ONE, which
   1606    * must split "equal" from "unordered", need a second (__unord*2) call. The
   1607    * convention is width-neutral, so the same logic drives the tf and df
   1608    * suffixes via api_softfp_cmp. */
   1609   if (api_unevaluated(g)) {
   1610     api_cg_cmp(g, api_map_fp_cmp(op));
   1611     return;
   1612   }
   1613   if (api_f128_stack_top(g, 0) || api_f128_stack_top(g, 1)) {
   1614     api_softfp_cmp(g, op, "tf", builtin_id(KIT_CG_BUILTIN_F128));
   1615     return;
   1616   }
   1617   if (api_soft_double_stack_top(g, 0) || api_soft_double_stack_top(g, 1)) {
   1618     api_softfp_cmp(g, op, "df", builtin_id(KIT_CG_BUILTIN_F64));
   1619     return;
   1620   }
   1621   if (api_soft_single_stack_top(g, 0) || api_soft_single_stack_top(g, 1)) {
   1622     api_softfp_cmp(g, op, "sf", builtin_id(KIT_CG_BUILTIN_F32));
   1623     return;
   1624   }
   1625   api_cg_cmp(g, api_map_fp_cmp(op));
   1626 }
   1627 
   1628 void kit_cg_sext(KitCg* g, KitCgTypeId dst) {
   1629   api_cg_convert_kind(g, dst, CV_SEXT);
   1630 }
   1631 
   1632 void kit_cg_zext(KitCg* g, KitCgTypeId dst) {
   1633   api_cg_convert_kind(g, dst, CV_ZEXT);
   1634 }
   1635 
   1636 void kit_cg_trunc(KitCg* g, KitCgTypeId dst) {
   1637   api_cg_convert_kind(g, dst, CV_TRUNC);
   1638 }
   1639 
   1640 void kit_cg_ptr_to_int(KitCg* g, KitCgTypeId dst) {
   1641   api_cg_convert_kind(g, dst, CV_BITCAST);
   1642 }
   1643 
   1644 void kit_cg_int_to_ptr(KitCg* g, KitCgTypeId dst) {
   1645   api_cg_convert_kind(g, dst, CV_BITCAST);
   1646 }
   1647 
   1648 void kit_cg_bitcast(KitCg* g, KitCgTypeId dst) {
   1649   api_cg_convert_kind(g, dst, CV_BITCAST);
   1650 }
   1651 
   1652 void kit_cg_fpext(KitCg* g, KitCgTypeId dst) {
   1653   /* The f128 / soft-double predicates below validate `dst` internally (and the
   1654    * fall-through reuses `dst` directly), so the standalone resolve_type is
   1655    * redundant: for a valid id resolve_type(dst)==dst, for an invalid id every
   1656    * predicate is false and the fall-through validates. */
   1657   KitCgTypeId dty = dst;
   1658   if (!g) return;
   1659   if (api_unevaluated(g)) {
   1660     api_cg_convert_kind(g, dst, CV_FEXT);
   1661     return;
   1662   }
   1663   if (api_is_f128_type(g->c, dty)) {
   1664     ApiSValue v = api_pop(g);
   1665     KitCgTypeId sty = api_sv_type(&v);
   1666     const char* name = sty == builtin_id(KIT_CG_BUILTIN_F32) ? "__extendsftf2"
   1667                                                              : "__extenddftf2";
   1668     api_push(g, v);
   1669     api_f128_call_unary(g, name, dty, sty);
   1670     return;
   1671   }
   1672   /* float -> soft double: runtime widen via __extendsfdf2. */
   1673   if (api_type_is_soft_double(g, dty)) {
   1674     ApiSValue v = api_pop(g);
   1675     KitCgTypeId sty = api_sv_type(&v);
   1676     api_push(g, v);
   1677     api_f128_call_unary(g, "__extendsfdf2", dty, sty);
   1678     return;
   1679   }
   1680   api_cg_convert_kind(g, dst, CV_FEXT);
   1681 }
   1682 
   1683 void kit_cg_fptrunc(KitCg* g, KitCgTypeId dst) {
   1684   /* resolve_type(dst)==dst for any valid id; the predicates / fall-through
   1685    * validate, so drop the redundant standalone validation (see kit_cg_fpext).
   1686    */
   1687   KitCgTypeId dty = dst;
   1688   if (!g) return;
   1689   if (api_unevaluated(g)) {
   1690     api_cg_convert_kind(g, dst, CV_FTRUNC);
   1691     return;
   1692   }
   1693   if (api_f128_stack_top(g, 0)) {
   1694     ApiSValue v = api_pop(g);
   1695     KitCgTypeId f128 = builtin_id(KIT_CG_BUILTIN_F128);
   1696     const char* name =
   1697         dty == builtin_id(KIT_CG_BUILTIN_F32) ? "__trunctfsf2" : "__trunctfdf2";
   1698     api_push(g, v);
   1699     api_f128_call_unary(g, name, dty, f128);
   1700     return;
   1701   }
   1702   /* soft double -> float: runtime narrow via __truncdfsf2. */
   1703   if (api_soft_double_stack_top(g, 0)) {
   1704     ApiSValue v = api_pop(g);
   1705     KitCgTypeId sty = api_sv_type(&v);
   1706     api_push(g, v);
   1707     api_f128_call_unary(g, "__truncdfsf2", dty, sty);
   1708     return;
   1709   }
   1710   api_cg_convert_kind(g, dst, CV_FTRUNC);
   1711 }
   1712 
   1713 /* The integer builtin a soft-float int<->float conversion uses for an operand
   1714  * whose ABI size is `sz` bytes: i128 (>8), i64 (>4), else i32. Shared by all
   1715  * eight conversion blocks (the same width ladder appeared in each). */
   1716 static KitCgTypeId api_int_builtin_for_size(u32 sz) {
   1717   return sz > 8 ? builtin_id(KIT_CG_BUILTIN_I128)
   1718                 : (sz > 4 ? builtin_id(KIT_CG_BUILTIN_I64)
   1719                           : builtin_id(KIT_CG_BUILTIN_I32));
   1720 }
   1721 
   1722 /* The compiler-rt integer-width suffix matching api_int_builtin_for_size:
   1723  * "ti" (i128), "di" (i64), "si" (i32). */
   1724 static const char* api_int_suffix_for_size(u32 sz) {
   1725   return sz > 8 ? "ti" : (sz > 4 ? "di" : "si");
   1726 }
   1727 
   1728 /* The four width-laddered soft-float int<->float conversion families. The
   1729  * compiler-rt name is a fixed prefix, the width-derived integer suffix
   1730  * (api_int_suffix_for_size) and a fixed float suffix ("tf" for f128, "df" for
   1731  * soft double); int->float places the integer suffix before the float suffix,
   1732  * float->int after it. */
   1733 typedef enum ApiFpConvOp {
   1734   API_FPCONV_SINT_TO_FLOAT, /* __float<int><flt>   */
   1735   API_FPCONV_UINT_TO_FLOAT, /* __floatun<int><flt> */
   1736   API_FPCONV_FLOAT_TO_SINT, /* __fix<flt><int>     */
   1737   API_FPCONV_FLOAT_TO_UINT, /* __fixuns<flt><int>  */
   1738 } ApiFpConvOp;
   1739 
   1740 typedef struct FpConvDesc {
   1741   const char* prefix;
   1742   int int_first; /* 1: prefix+int+flt (int->float); 0: prefix+flt+int */
   1743 } FpConvDesc;
   1744 
   1745 static const FpConvDesc kFpConvTable[] = {
   1746     [API_FPCONV_SINT_TO_FLOAT] = {"__float", 1},
   1747     [API_FPCONV_UINT_TO_FLOAT] = {"__floatun", 1},
   1748     [API_FPCONV_FLOAT_TO_SINT] = {"__fix", 0},
   1749     [API_FPCONV_FLOAT_TO_UINT] = {"__fixuns", 0},
   1750 };
   1751 
   1752 /* Build the compiler-rt conversion libcall name for `op` with the given integer
   1753  * width (`sz` bytes) and float suffix `flt` ("tf"/"df") into `buf`. The result
   1754  * is byte-identical to the names the eight blocks previously open-coded. */
   1755 static void api_fp_conv_name(char* buf, size_t cap, ApiFpConvOp op,
   1756                              const char* flt, u32 sz) {
   1757   const FpConvDesc* d = &kFpConvTable[op];
   1758   const char* is = api_int_suffix_for_size(sz);
   1759   if (d->int_first)
   1760     snprintf(buf, cap, "%s%s%s", d->prefix, is, flt);
   1761   else
   1762     snprintf(buf, cap, "%s%s%s", d->prefix, flt, is);
   1763 }
   1764 
   1765 void kit_cg_sint_to_float(KitCg* g, KitCgTypeId dst, KitCgRounding rounding) {
   1766   (void)rounding;
   1767   if (api_unevaluated(g)) {
   1768     api_cg_convert_kind(g, dst, CV_ITOF_S);
   1769     return;
   1770   }
   1771   if (api_is_f128_type(g->c, dst)) {
   1772     ApiSValue v = api_pop(g);
   1773     KitCgTypeId sty = api_sv_type(&v);
   1774     u32 sz = (u32)abi_cg_sizeof(g->c->abi, sty);
   1775     char name[16];
   1776     api_fp_conv_name(name, sizeof name, API_FPCONV_SINT_TO_FLOAT, "tf", sz);
   1777     api_push(g, v);
   1778     api_f128_call_unary(g, name, dst, api_int_builtin_for_size(sz));
   1779     return;
   1780   }
   1781   /* signed int -> soft double: __floatsidf (i32) / __floatdidf (i64). */
   1782   if (api_type_is_soft_double(g, dst)) {
   1783     ApiSValue v = api_pop(g);
   1784     KitCgTypeId sty = api_sv_type(&v);
   1785     u32 sz = (u32)abi_cg_sizeof(g->c->abi, sty);
   1786     char name[16];
   1787     api_fp_conv_name(name, sizeof name, API_FPCONV_SINT_TO_FLOAT, "df", sz);
   1788     api_push(g, v);
   1789     api_f128_call_unary(g, name, dst, api_int_builtin_for_size(sz));
   1790     return;
   1791   }
   1792   /* signed split-i64 -> hardware single float: use __floatdisf. */
   1793   if (api_wide64_stack_top(g, 0)) {
   1794     api_f128_call_unary(g, "__floatdisf", dst, builtin_id(KIT_CG_BUILTIN_I64));
   1795     return;
   1796   }
   1797   /* i32 -> soft single float (ilp32, no FPU): __floatsisf. */
   1798   if (api_type_is_soft_single(g, dst)) {
   1799     api_f128_call_unary(g, "__floatsisf", dst, builtin_id(KIT_CG_BUILTIN_I32));
   1800     return;
   1801   }
   1802   api_cg_convert_kind(g, dst, CV_ITOF_S);
   1803 }
   1804 
   1805 void kit_cg_uint_to_float(KitCg* g, KitCgTypeId dst, KitCgRounding rounding) {
   1806   (void)rounding;
   1807   if (api_unevaluated(g)) {
   1808     api_cg_convert_kind(g, dst, CV_ITOF_U);
   1809     return;
   1810   }
   1811   if (api_is_f128_type(g->c, dst)) {
   1812     ApiSValue v = api_pop(g);
   1813     KitCgTypeId sty = api_sv_type(&v);
   1814     u32 sz = (u32)abi_cg_sizeof(g->c->abi, sty);
   1815     char name[16];
   1816     api_fp_conv_name(name, sizeof name, API_FPCONV_UINT_TO_FLOAT, "tf", sz);
   1817     api_push(g, v);
   1818     api_f128_call_unary(g, name, dst, api_int_builtin_for_size(sz));
   1819     return;
   1820   }
   1821   /* unsigned int -> soft double: __floatunsidf (i32) / __floatundidf (i64). */
   1822   if (api_type_is_soft_double(g, dst)) {
   1823     ApiSValue v = api_pop(g);
   1824     KitCgTypeId sty = api_sv_type(&v);
   1825     u32 sz = (u32)abi_cg_sizeof(g->c->abi, sty);
   1826     char name[16];
   1827     api_fp_conv_name(name, sizeof name, API_FPCONV_UINT_TO_FLOAT, "df", sz);
   1828     api_push(g, v);
   1829     api_f128_call_unary(g, name, dst, api_int_builtin_for_size(sz));
   1830     return;
   1831   }
   1832   /* unsigned i64 -> hardware single float: __floatundisf. */
   1833   if (api_wide64_stack_top(g, 0)) {
   1834     api_f128_call_unary(g, "__floatundisf", dst,
   1835                         builtin_id(KIT_CG_BUILTIN_I64));
   1836     return;
   1837   }
   1838   /* u32 -> soft single float (ilp32, no FPU): __floatunsisf. */
   1839   if (api_type_is_soft_single(g, dst)) {
   1840     api_f128_call_unary(g, "__floatunsisf", dst,
   1841                         builtin_id(KIT_CG_BUILTIN_I32));
   1842     return;
   1843   }
   1844   api_cg_convert_kind(g, dst, CV_ITOF_U);
   1845 }
   1846 
   1847 void kit_cg_float_to_sint(KitCg* g, KitCgTypeId dst, KitCgRounding rounding) {
   1848   (void)rounding;
   1849   if (api_unevaluated(g)) {
   1850     api_cg_convert_kind(g, dst, CV_FTOI_S);
   1851     return;
   1852   }
   1853   if (api_f128_stack_top(g, 0)) {
   1854     KitCgTypeId dty = dst;
   1855     u32 sz = (u32)abi_cg_sizeof(g->c->abi, dty);
   1856     KitCgTypeId rty = api_int_builtin_for_size(sz);
   1857     char name[16];
   1858     api_fp_conv_name(name, sizeof name, API_FPCONV_FLOAT_TO_SINT, "tf", sz);
   1859     api_f128_call_unary(g, name, rty, builtin_id(KIT_CG_BUILTIN_F128));
   1860     if (rty != dty) api_cg_convert_kind(g, dty, CV_TRUNC);
   1861     return;
   1862   }
   1863   /* soft double -> signed int: __fixdfsi (i32) / __fixdfdi (i64). */
   1864   if (api_soft_double_stack_top(g, 0)) {
   1865     KitCgTypeId dty = dst;
   1866     KitCgTypeId f64 = builtin_id(KIT_CG_BUILTIN_F64);
   1867     u32 sz = (u32)abi_cg_sizeof(g->c->abi, dty);
   1868     KitCgTypeId rty = api_int_builtin_for_size(sz);
   1869     char name[16];
   1870     api_fp_conv_name(name, sizeof name, API_FPCONV_FLOAT_TO_SINT, "df", sz);
   1871     api_f128_call_unary(g, name, rty, f64);
   1872     if (rty != dty) api_cg_convert_kind(g, dty, CV_TRUNC);
   1873     return;
   1874   }
   1875   /* hardware single float -> split-i64: use __fixsfdi. */
   1876   if (api_is_wide8_scalar_type(g->c, dst)) {
   1877     api_f128_call_unary(g, "__fixsfdi", dst, builtin_id(KIT_CG_BUILTIN_F32));
   1878     return;
   1879   }
   1880   /* soft single float -> signed int <=32 (ilp32, no FPU): __fixsfsi. */
   1881   if (api_soft_single_stack_top(g, 0)) {
   1882     KitCgTypeId dty = dst;
   1883     KitCgTypeId i32 = builtin_id(KIT_CG_BUILTIN_I32);
   1884     api_f128_call_unary(g, "__fixsfsi", i32, builtin_id(KIT_CG_BUILTIN_F32));
   1885     if (i32 != dty) api_cg_convert_kind(g, dty, CV_TRUNC);
   1886     return;
   1887   }
   1888   api_cg_convert_kind(g, dst, CV_FTOI_S);
   1889 }
   1890 
   1891 void kit_cg_float_to_uint(KitCg* g, KitCgTypeId dst, KitCgRounding rounding) {
   1892   (void)rounding;
   1893   if (api_unevaluated(g)) {
   1894     api_cg_convert_kind(g, dst, CV_FTOI_U);
   1895     return;
   1896   }
   1897   if (api_f128_stack_top(g, 0)) {
   1898     KitCgTypeId dty = dst;
   1899     u32 sz = (u32)abi_cg_sizeof(g->c->abi, dty);
   1900     KitCgTypeId rty = api_int_builtin_for_size(sz);
   1901     char name[16];
   1902     api_fp_conv_name(name, sizeof name, API_FPCONV_FLOAT_TO_UINT, "tf", sz);
   1903     api_f128_call_unary(g, name, rty, builtin_id(KIT_CG_BUILTIN_F128));
   1904     if (rty != dty) api_cg_convert_kind(g, dty, CV_TRUNC);
   1905     return;
   1906   }
   1907   /* soft double -> unsigned int: __fixunsdfsi (i32) / __fixunsdfdi (i64). */
   1908   if (api_soft_double_stack_top(g, 0)) {
   1909     KitCgTypeId dty = dst;
   1910     KitCgTypeId f64 = builtin_id(KIT_CG_BUILTIN_F64);
   1911     u32 sz = (u32)abi_cg_sizeof(g->c->abi, dty);
   1912     KitCgTypeId rty = api_int_builtin_for_size(sz);
   1913     char name[16];
   1914     api_fp_conv_name(name, sizeof name, API_FPCONV_FLOAT_TO_UINT, "df", sz);
   1915     api_f128_call_unary(g, name, rty, f64);
   1916     if (rty != dty) api_cg_convert_kind(g, dty, CV_TRUNC);
   1917     return;
   1918   }
   1919   /* hardware single float -> split-u64: use __fixunssfdi. */
   1920   if (api_is_wide8_scalar_type(g->c, dst)) {
   1921     api_f128_call_unary(g, "__fixunssfdi", dst, builtin_id(KIT_CG_BUILTIN_F32));
   1922     return;
   1923   }
   1924   /* soft single float -> unsigned int <=32 (ilp32, no FPU): __fixunssfsi. */
   1925   if (api_soft_single_stack_top(g, 0)) {
   1926     KitCgTypeId dty = dst;
   1927     KitCgTypeId i32 = builtin_id(KIT_CG_BUILTIN_I32);
   1928     api_f128_call_unary(g, "__fixunssfsi", i32, builtin_id(KIT_CG_BUILTIN_F32));
   1929     if (i32 != dty) api_cg_convert_kind(g, dty, CV_TRUNC);
   1930     return;
   1931   }
   1932   api_cg_convert_kind(g, dst, CV_FTOI_U);
   1933 }
   1934 
   1935 /* ============================================================
   1936  * Intrinsics (stub)
   1937  * ============================================================ */
   1938 
   1939 /* One descriptor per KitCgIntrinsic, indexed by the enum value. The four
   1940  * accessors below are field reads off this single source of truth; unmapped
   1941  * intrinsics (FMA/cache/coro) use an INTRIN_NONE row. The table is laid
   1942  * out in enum order; the _Static_assert guards its length so a new enumerator
   1943  * is a compile error rather than a silently truncated index. */
   1944 typedef struct IntrinDesc {
   1945   IntrinKind kind;
   1946   const char* name;
   1947   bool is_void;
   1948   bool is_overflow;
   1949 } IntrinDesc;
   1950 
   1951 static const IntrinDesc kIntrinTable[] = {
   1952     [KIT_CG_INTRIN_TRAP] = {INTRIN_TRAP, "trap", true, false},
   1953     [KIT_CG_INTRIN_CLZ] = {INTRIN_CLZ, "clz", false, false},
   1954     [KIT_CG_INTRIN_CTZ] = {INTRIN_CTZ, "ctz", false, false},
   1955     [KIT_CG_INTRIN_POPCOUNT] = {INTRIN_POPCOUNT, "popcount", false, false},
   1956     [KIT_CG_INTRIN_BSWAP] = {INTRIN_BSWAP, "bswap", false, false},
   1957     [KIT_CG_INTRIN_SETJMP] = {INTRIN_SETJMP, "setjmp", false, false},
   1958     [KIT_CG_INTRIN_LONGJMP] = {INTRIN_LONGJMP, "longjmp", true, false},
   1959     [KIT_CG_INTRIN_SADD_OVERFLOW] = {INTRIN_SADD_OVERFLOW, "sadd_overflow",
   1960                                      false, true},
   1961     [KIT_CG_INTRIN_UADD_OVERFLOW] = {INTRIN_UADD_OVERFLOW, "uadd_overflow",
   1962                                      false, true},
   1963     [KIT_CG_INTRIN_SSUB_OVERFLOW] = {INTRIN_SSUB_OVERFLOW, "ssub_overflow",
   1964                                      false, true},
   1965     [KIT_CG_INTRIN_USUB_OVERFLOW] = {INTRIN_USUB_OVERFLOW, "usub_overflow",
   1966                                      false, true},
   1967     [KIT_CG_INTRIN_SMUL_OVERFLOW] = {INTRIN_SMUL_OVERFLOW, "smul_overflow",
   1968                                      false, true},
   1969     [KIT_CG_INTRIN_UMUL_OVERFLOW] = {INTRIN_UMUL_OVERFLOW, "umul_overflow",
   1970                                      false, true},
   1971     [KIT_CG_INTRIN_FMA] = {INTRIN_NONE, "fma", false, false},
   1972     [KIT_CG_INTRIN_PREFETCH] = {INTRIN_PREFETCH, "prefetch", true, false},
   1973     [KIT_CG_INTRIN_EXPECT] = {INTRIN_EXPECT, "expect", false, false},
   1974     [KIT_CG_INTRIN_ASSUME_ALIGNED] = {INTRIN_ASSUME_ALIGNED, "assume_aligned",
   1975                                       false, false},
   1976     [KIT_CG_INTRIN_SYSCALL] = {INTRIN_SYSCALL, "syscall", false, false},
   1977     [KIT_CG_INTRIN_IRQ_SAVE] = {INTRIN_IRQ_SAVE, "irq_save", false, false},
   1978     [KIT_CG_INTRIN_IRQ_RESTORE] = {INTRIN_IRQ_RESTORE, "irq_restore", true,
   1979                                    false},
   1980     [KIT_CG_INTRIN_IRQ_DISABLE] = {INTRIN_IRQ_DISABLE, "irq_disable", true,
   1981                                    false},
   1982     [KIT_CG_INTRIN_IRQ_ENABLE] = {INTRIN_IRQ_ENABLE, "irq_enable", true, false},
   1983     [KIT_CG_INTRIN_DMB] = {INTRIN_DMB, "dmb", true, false},
   1984     [KIT_CG_INTRIN_DSB] = {INTRIN_DSB, "dsb", true, false},
   1985     [KIT_CG_INTRIN_ISB] = {INTRIN_ISB, "isb", true, false},
   1986     [KIT_CG_INTRIN_DCACHE_CLEAN] = {INTRIN_NONE, "dcache_clean", false, false},
   1987     [KIT_CG_INTRIN_DCACHE_INVALIDATE] = {INTRIN_NONE, "dcache_invalidate",
   1988                                          false, false},
   1989     [KIT_CG_INTRIN_DCACHE_CLEAN_INVALIDATE] = {INTRIN_NONE,
   1990                                                "dcache_clean_invalidate", false,
   1991                                                false},
   1992     [KIT_CG_INTRIN_ICACHE_INVALIDATE] = {INTRIN_NONE, "icache_invalidate",
   1993                                          false, false},
   1994     [KIT_CG_INTRIN_CPU_NOP] = {INTRIN_CPU_NOP, "cpu_nop", true, false},
   1995     [KIT_CG_INTRIN_CPU_YIELD] = {INTRIN_CPU_YIELD, "cpu_yield", true, false},
   1996     [KIT_CG_INTRIN_WFI] = {INTRIN_WFI, "wfi", true, false},
   1997     [KIT_CG_INTRIN_WFE] = {INTRIN_WFE, "wfe", true, false},
   1998     [KIT_CG_INTRIN_SEV] = {INTRIN_SEV, "sev", true, false},
   1999     [KIT_CG_INTRIN_CORO_SWITCH] = {INTRIN_NONE, "coro_switch", false, false},
   2000     [KIT_CG_INTRIN_FRAME_ADDRESS] = {INTRIN_FRAME_ADDRESS, "frame_address",
   2001                                      false, false},
   2002     [KIT_CG_INTRIN_RETURN_ADDRESS] = {INTRIN_RETURN_ADDRESS, "return_address",
   2003                                       false, false},
   2004     [KIT_CG_INTRIN_READCYCLECOUNTER] = {INTRIN_READCYCLECOUNTER,
   2005                                         "readcyclecounter", false, false},
   2006     [KIT_CG_INTRIN_SMUL_HIGH] = {INTRIN_SMUL_HIGH, "smul_high", false, false},
   2007     [KIT_CG_INTRIN_UMUL_HIGH] = {INTRIN_UMUL_HIGH, "umul_high", false, false},
   2008 };
   2009 
   2010 _Static_assert(sizeof(kIntrinTable) / sizeof(kIntrinTable[0]) ==
   2011                    KIT_CG_INTRIN_UMUL_HIGH + 1,
   2012                "kIntrinTable must have exactly one row per KitCgIntrinsic");
   2013 
   2014 /* Bounds-guarded row lookup: an out-of-range intrinsic falls back to the NONE
   2015  * row, preserving the defensive `default:` behavior the four switches carried
   2016  * before they collapsed into kIntrinTable. */
   2017 static const IntrinDesc* intrin_desc(KitCgIntrinsic intrin) {
   2018   static const IntrinDesc none = {INTRIN_NONE, NULL, false, false};
   2019   unsigned i = (unsigned)intrin;
   2020   return i < sizeof(kIntrinTable) / sizeof(kIntrinTable[0]) ? &kIntrinTable[i]
   2021                                                             : &none;
   2022 }
   2023 
   2024 IntrinKind api_map_intrinsic(KitCg* g, KitCgIntrinsic intrin,
   2025                              KitCgTypeId result_type) {
   2026   /* Width-by-type: backends derive operand width from the result type, so the
   2027    * mapping no longer needs the size here. */
   2028   (void)g;
   2029   (void)result_type;
   2030   return intrin_desc(intrin)->kind;
   2031 }
   2032 
   2033 int api_intrinsic_is_void(KitCgIntrinsic intrin) {
   2034   return intrin_desc(intrin)->is_void;
   2035 }
   2036 
   2037 int api_intrinsic_is_overflow(KitCgIntrinsic intrin) {
   2038   return intrin_desc(intrin)->is_overflow;
   2039 }
   2040 
   2041 const char* api_intrinsic_name(KitCgIntrinsic intrin) {
   2042   const char* name = intrin_desc(intrin)->name;
   2043   return name ? name : "intrinsic";
   2044 }
   2045 
   2046 void kit_cg_intrinsic(KitCg* g, KitCgIntrinsic intrin, uint32_t nargs,
   2047                       KitCgTypeId result_type) {
   2048   CgTarget* T;
   2049   KitCgTypeId rty;
   2050   KitCgTypeId int_ty;
   2051   IntrinKind kind;
   2052   ApiSValue* svs;
   2053   Operand* args;
   2054   Operand dsts[2];
   2055   u32 ndst = 0;
   2056   Heap* h;
   2057   if (!g) return;
   2058   if (api_unevaluated(g)) {
   2059     KitCgTypeId rty_u = resolve_type(g->c, result_type);
   2060     for (u32 i = 0; i < nargs; ++i) {
   2061       ApiSValue sv = api_pop(g);
   2062       api_release(g, &sv);
   2063     }
   2064     if (api_intrinsic_is_overflow(intrin)) {
   2065       KitCgTypeId bool_ty = builtin_id(KIT_CG_BUILTIN_BOOL);
   2066       if (!rty_u) rty_u = builtin_id(KIT_CG_BUILTIN_I32);
   2067       api_push(g, api_uneval_value(g, rty_u));
   2068       api_const_set_top(g, api_const_unknown(rty_u));
   2069       api_push(g, api_uneval_value(g, bool_ty));
   2070       api_const_set_top(g, api_const_unknown(bool_ty));
   2071     } else if (!api_intrinsic_is_void(intrin) &&
   2072                (rty_u && !cg_type_is_void(g->c, rty_u))) {
   2073       api_push(g, api_uneval_value(g, rty_u));
   2074       api_const_set_top(g, api_const_unknown(rty_u));
   2075     }
   2076     return;
   2077   }
   2078   /* readcyclecounter returns a 64-bit value. On rv32 that is a register pair
   2079    * the single-register native intrinsic path can't carry, and the read spans
   2080    * the cycle/cycleh CSRs with a re-read loop, so route to the libkit_rt helper
   2081    * (mirrors the wide-64 clz/ctz routing below). rv64 reads it inline. */
   2082   if (intrin == KIT_CG_INTRIN_READCYCLECOUNTER &&
   2083       g->c->target.arch == KIT_ARCH_RV32) {
   2084     api_runtime_call_values(g, "__kit_readcyclecounter",
   2085                             builtin_id(KIT_CG_BUILTIN_I64), NULL, 0, NULL);
   2086     return;
   2087   }
   2088   /* A 64-bit value is a two-register pair on the 32-bit native targets. The
   2089    * ordinary intrinsic ABI carries one register per operand/result, so use the
   2090    * rollover-free compiler runtime's limb implementation for high multiply. */
   2091   if (nargs == 2 &&
   2092       (intrin == KIT_CG_INTRIN_SMUL_HIGH ||
   2093        intrin == KIT_CG_INTRIN_UMUL_HIGH) &&
   2094       api_wide64_stack_top(g, 0) && api_wide64_stack_top(g, 1)) {
   2095     ApiSValue args_h[2];
   2096     KitCgTypeId i64 = builtin_id(KIT_CG_BUILTIN_I64);
   2097     KitCgTypeId ps[2] = {i64, i64};
   2098     args_h[1] = api_pop(g);
   2099     args_h[0] = api_pop(g);
   2100     api_runtime_call_values(g,
   2101                             intrin == KIT_CG_INTRIN_SMUL_HIGH
   2102                                 ? "__kit_mulhdi3"
   2103                                 : "__kit_umulhdi3",
   2104                             i64, ps, 2, args_h);
   2105     return;
   2106   }
   2107   /* clz/ctz/popcount/bswap on a split 64-bit value cannot use the backend's
   2108    * single-register software sequence. Route them to the compiler-rt __*di2
   2109    * helpers, which decompose into 32-bit operations. (32-bit forms still lower
   2110    * inline.) */
   2111   if (nargs == 1 && api_wide64_stack_top(g, 0)) {
   2112     const char* name = NULL;
   2113     KitCgTypeId i32 = builtin_id(KIT_CG_BUILTIN_I32);
   2114     KitCgTypeId i64 = builtin_id(KIT_CG_BUILTIN_I64);
   2115     KitCgTypeId ret = i32;
   2116     switch (intrin) {
   2117       case KIT_CG_INTRIN_CLZ:
   2118         name = "__clzdi2";
   2119         break;
   2120       case KIT_CG_INTRIN_CTZ:
   2121         name = "__ctzdi2";
   2122         break;
   2123       case KIT_CG_INTRIN_POPCOUNT:
   2124         name = "__popcountdi2";
   2125         break;
   2126       case KIT_CG_INTRIN_BSWAP:
   2127         name = "__bswapdi2";
   2128         ret = i64;
   2129         break;
   2130       default:
   2131         break;
   2132     }
   2133     if (name) {
   2134       ApiSValue arg = api_pop(g);
   2135       KitCgTypeId ps[1] = {i64};
   2136       api_runtime_call_values(g, name, ret, ps, 1, &arg);
   2137       if (ret == i32 && result_type != i32)
   2138         api_cg_convert_kind(g, result_type, CV_ZEXT);
   2139       return;
   2140     }
   2141   }
   2142   /* __builtin_*_overflow on a split 64-bit operand pair traps in the native
   2143    * backend (it only models single-register overflow). Legalize all 6 forms
   2144    * inline as 2-lane / 4-lane ops, pushing [value, ok] like the native path.
   2145    * Gated on both operands being wide64 so other targets are unchanged. */
   2146   if (nargs == 2 && api_intrinsic_is_overflow(intrin) &&
   2147       api_wide64_stack_top(g, 0) && api_wide64_stack_top(g, 1)) {
   2148     api_wide64_overflow_inline(g, intrin);
   2149     return;
   2150   }
   2151   if (nargs == 2 && intrin == KIT_CG_INTRIN_EXPECT &&
   2152       api_wide64_stack_top(g, 1)) {
   2153     ApiSValue expected = api_pop(g);
   2154     ApiSValue val = api_pop(g);
   2155     api_release(g, &expected);
   2156     api_push(g, val);
   2157     return;
   2158   }
   2159   T = g->target;
   2160   h = g->c->ctx->heap;
   2161   rty = resolve_type(g->c, result_type);
   2162   int_ty = builtin_id(KIT_CG_BUILTIN_I32);
   2163   kind = api_map_intrinsic(g, intrin, result_type);
   2164   if (!kit_cg_target_supports_intrinsic(g->c, intrin) || kind == INTRIN_NONE) {
   2165     compiler_panic(
   2166         g->c, g->cur_loc, "KitCg: target '%s' does not support intrinsic '%s'",
   2167         arch_kind_name(g->c->target.arch), api_intrinsic_name(intrin));
   2168     return;
   2169   }
   2170 
   2171   svs = NULL;
   2172   args = NULL;
   2173   if (nargs) {
   2174     svs = (ApiSValue*)h->alloc(h, sizeof(*svs) * nargs, _Alignof(ApiSValue));
   2175     args = (Operand*)h->alloc(h, sizeof(*args) * nargs, _Alignof(Operand));
   2176     memset(args, 0, sizeof(*args) * nargs);
   2177     for (u32 i = 0; i < nargs; ++i) {
   2178       u32 idx = nargs - 1u - i;
   2179       KitCgTypeId aty;
   2180       svs[idx] = api_pop(g);
   2181       aty = api_sv_type(&svs[idx]);
   2182       if (api_sv_op_is(&svs[idx], OPK_IMM) &&
   2183           (intrin == KIT_CG_INTRIN_EXPECT ||
   2184            intrin == KIT_CG_INTRIN_ASSUME_ALIGNED ||
   2185            intrin == KIT_CG_INTRIN_PREFETCH || intrin == KIT_CG_INTRIN_DMB ||
   2186            intrin == KIT_CG_INTRIN_DSB ||
   2187            intrin == KIT_CG_INTRIN_FRAME_ADDRESS ||
   2188            intrin == KIT_CG_INTRIN_RETURN_ADDRESS)) {
   2189         args[idx] = svs[idx].op;
   2190       } else {
   2191         args[idx] = api_force_local(g, &svs[idx], aty);
   2192       }
   2193     }
   2194   }
   2195 
   2196   if (api_intrinsic_is_overflow(intrin)) {
   2197     KitCgTypeId vty = rty ? rty : (nargs ? api_sv_type(&svs[0]) : int_ty);
   2198     KitCgTypeId bool_ty = builtin_id(KIT_CG_BUILTIN_BOOL);
   2199     CGLocal rr = api_alloc_temp_local(g, vty);
   2200     CGLocal ok = api_alloc_temp_local(g, bool_ty);
   2201     dsts[0] = api_op_local(rr, vty);
   2202     dsts[1] = api_op_local(ok, bool_ty);
   2203     ndst = 2;
   2204   } else if (!api_intrinsic_is_void(intrin) && !cg_type_is_void(g->c, rty)) {
   2205     CGLocal rr = api_alloc_temp_local(g, rty);
   2206     dsts[0] = api_op_local(rr, rty);
   2207     ndst = 1;
   2208   }
   2209 
   2210   T->intrinsic(T, kind, ndst ? dsts : NULL, ndst, args, nargs);
   2211 
   2212   for (u32 i = 0; i < nargs; ++i) api_release(g, &svs[i]);
   2213   if (svs) h->free(h, svs, sizeof(*svs) * nargs);
   2214   if (args) h->free(h, args, sizeof(*args) * nargs);
   2215 
   2216   if (api_intrinsic_is_overflow(intrin)) {
   2217     api_push(g, api_make_sv(dsts[0], dsts[0].type));
   2218     api_push(g, api_make_sv(dsts[1], dsts[1].type));
   2219   } else if (ndst == 1) {
   2220     api_push(g, api_make_sv(dsts[0], rty));
   2221   }
   2222 }
   2223 
   2224 /* ============================================================
   2225  * Atomics (stub)
   2226  * ============================================================ */
   2227 
   2228 KitCgTypeId api_atomic_pointee(KitCg* g, KitCgTypeId pty, const char* who) {
   2229   KitCgTypeId pointee = cg_type_pointee(g->c, pty);
   2230   CG_REQUIRE(g, pointee, "%.*s: operand is not a pointer",
   2231              SLICE_ARG(slice_from_cstr(who)));
   2232   return pointee;
   2233 }