kit

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

native.c (177755B)


      1 /* src/arch/rv64/native.c — RISC-V (RV64GC, LP64D) NativeTarget implementation.
      2  *
      3  * Mirrors the aa64 reference (src/arch/aa64/native.c): a physical-emission
      4  * NativeTarget driven at -O0 by the shared NativeDirectTarget and at -O1+ by
      5  * the optimizer emit path. ABI decisions go through the abi/ interface; this
      6  * file owns only ISA emission and the RV64 frame layout.
      7  *
      8  * Frame model (single, top-record): s0 (x8) is the frame pointer anchored at
      9  * the saved s0/ra pair; slots live below s0 at positive byte offsets `off`
     10  * (address = s0 - off); outgoing args sit at the bottom of the frame (sp+0..).
     11  *   frame_size  = align16(16 + cum_off + max_outgoing + va_save_sz)
     12  *   fp_pair_off = frame_size - 16 - va_save_sz   (saved pair, sp-relative)
     13  *   CFA = s0 + (frame_size - fp_pair_off)
     14  * RISC-V has no condition flags: comparisons materialize a 0/1 via SLT/SLTU or
     15  * FLT/FLE; branches compare two registers directly. x0 is a hardware zero. */
     16 
     17 #include <string.h>
     18 
     19 #include "abi/abi.h"
     20 #include "arch/riscv/asm.h"
     21 #include "arch/riscv/isa.h"
     22 #include "arch/riscv/regs.h"
     23 #include "arch/riscv/rv64.h"
     24 #include "arch/riscv/variant.h"
     25 #include "asm/asm.h"
     26 #include "asm/asm_lex.h"
     27 #include "cg/native_argmove.h"
     28 #include "cg/native_asm.h"
     29 #include "cg/native_direct_target.h"
     30 #include "cg/native_frame.h"
     31 #include "cg/type.h"
     32 #include "core/arena.h"
     33 #include "core/bytes.h"
     34 #include "core/pool.h"
     35 #include "core/slice.h"
     36 #include "obj/obj.h"
     37 
     38 enum {
     39   RV_TMP0 = 5u,  /* t0: backend-private temp (reserved, never allocable) */
     40   RV_TMP1 = 6u,  /* t1: backend-private temp */
     41   RV_TMP2 = 7u,  /* t2: backend/asm temp (reserved in phys table) */
     42   RV_TMP3 = 28u, /* t3: backend/asm temp (reserved in phys table) */
     43   RV_FTMP0 = 0u, /* ft0: backend/asm FP temp */
     44   RV_FTMP1 = 1u, /* ft1: backend/asm FP temp */
     45   RV_FA0 = 10u,  /* fa0..fa7 = f10..f17 (FP arg/return registers) */
     46   RV_FA7 = 17u,
     47   /* Single-pass (-O0) tcc-style prologue. The frame-independent entry
     48    *   `addi sp,sp,-(frame_save_size+va_save) ; sd s0,0(sp) ; sd ra,ptr(sp) ;
     49    *    addi s0,sp,0`
     50    * (RV_NDT_FIXED_ENTRY_WORDS) is emitted live in rv_func_begin: it pre-decrements
     51    * sp by just the saved-pair + variadic-GP-save area, stores the s0/ra pair at
     52    * the top, and anchors s0 there. The pre-decrement is always small
     53    * (<= 16 + 64 = 80 on rv64, 8 + 32 = 40 on rv32), so it always fits imm12 and
     54    * needs no lui+add far form. Only the second `sub sp` that grows the rest of
     55    * the frame below the pair is deferred (patched once frame_size is final); the
     56    * single-pass path uses no callee-saved registers (enforced in the NDT scratch
     57    * acquire) and RISC-V has no Windows-style stack probe, so nothing else is
     58    * deferred and the reserved region is tiny: worst-case sub = lui(1)+addi(1)+
     59    * add(1) = 3 words. The sret + variadic GP spills are s0-relative and emitted
     60    * live after the region (rv_emit_entry_save_stores). */
     61   RV_NDT_FIXED_ENTRY_WORDS = 4u,
     62   RV_NDT_SUB_WORDS = 4u, /* region buffer: worst-case far `sub sp` is 3 (+1 pad) */
     63   /* Known-frame (-O1) prologues are emitted directly, not into the fixed -O0
     64    * NOP region, and additionally save callee-saved registers (up to 11 int + 12
     65    * fp, each up to 4 words for a far s0-relative offset) on top of the header,
     66    * sret, and variadic spills. Size the build buffer for the worst case. */
     67   RV_KNOWN_PROLOGUE_WORDS = 192u,
     68 };
     69 
     70 /* s1..s11 (11) + fs0..fs11 (12); separate int/fp collect arrays use this cap.
     71  */
     72 #define RV_MAX_CALLEE_SAVES 16u
     73 #define RV_MAX_REG_ARG_MOVES 16u
     74 
     75 extern void debug_emit_row(Debug*, ObjSecId text_section, u32 offset, SrcLoc);
     76 extern void debug_func_pc_range(Debug*, ObjSecId text_section, u32 begin_ofs,
     77                                 u32 end_ofs);
     78 
     79 /* ============================ low-level emit ============================ */
     80 
     81 void rv64_emit32(MCEmitter* mc, u32 word) {
     82   /* Read the pre-write offset (only the -g line table needs it) before
     83    * mc_emit32 advances the cursor. */
     84   if (mc->debug) {
     85     u32 ofs = obj_pos(mc->obj, mc->section_id);
     86     mc_emit32(mc, word);
     87     debug_emit_row(mc->debug, mc->section_id, ofs, mc->loc);
     88   } else {
     89     mc_emit32(mc, word);
     90   }
     91 }
     92 
     93 void rv64_emit16(MCEmitter* mc, u32 halfword) {
     94   u8 b[2];
     95   u32 ofs = obj_pos(mc->obj, mc->section_id);
     96   b[0] = (u8)(halfword & 0xff);
     97   b[1] = (u8)((halfword >> 8) & 0xff);
     98   mc_emit_bytes(mc, b, sizeof b);
     99   if (mc->debug) debug_emit_row(mc->debug, mc->section_id, ofs, mc->loc);
    100 }
    101 
    102 static void rv_patch32(ObjBuilder* obj, ObjSecId sec, u32 off, u32 word) {
    103   u8 b[4];
    104   wr_u32_le(b, word);
    105   obj_patch(obj, sec, off, b, sizeof b);
    106 }
    107 
    108 static int fits_i12(i64 v) { return v >= -2048 && v <= 2047; }
    109 static int fits_i32(i64 v) {
    110   return v >= (i64)(i32)0x80000000 && v <= (i64)(i32)0x7fffffff;
    111 }
    112 
    113 static i64 floor_div_4096(i64 v) {
    114   if (v >= 0) return v / 4096;
    115   return -((-v + 4095) / 4096);
    116 }
    117 
    118 static void rv_emit_li32(const RiscvVariant* v, MCEmitter* mc, u32 rd,
    119                          i32 imm) {
    120   if (imm >= -2048 && imm <= 2047) {
    121     rv64_emit32(mc, rv_addi(rd, RV_ZERO, imm));
    122     return;
    123   }
    124   {
    125     i64 hi64 = floor_div_4096((i64)imm + 0x800);
    126     i32 hi = (i32)hi64;
    127     i32 lo = (i32)((i64)imm - hi64 * 4096);
    128     rv64_emit32(mc, rv_lui(rd, (u32)hi & 0xfffffu));
    129     /* ADDIW is RV64-only; on RV32 the value fits 32 bits so plain ADDI is
    130      * exact (and identical to ADDIW's low result on RV64). */
    131     if (lo)
    132       rv64_emit32(mc,
    133                   v->has_w_forms ? rv_addiw(rd, rd, lo) : rv_addi(rd, rd, lo));
    134   }
    135 }
    136 
    137 static i32 sext12(u32 v) {
    138   v &= 0xfffu;
    139   return (v & 0x800u) ? (i32)v - 4096 : (i32)v;
    140 }
    141 
    142 /* Builds a full XLEN-wide value. The recursion / slli-12 chain assembles bits
    143  * above 32 and is only ever reached on rv64 (a single rv32 register cannot hold
    144  * a value wider than 32 bits — the cg layer legalizes those into pairs). */
    145 static void rv_emit_li64(const RiscvVariant* v, MCEmitter* mc, u32 rd,
    146                          u64 imm) {
    147   if (fits_i32((i64)imm)) {
    148     rv_emit_li32(v, mc, rd, (i32)(i64)imm);
    149     return;
    150   }
    151   {
    152     i32 lo = sext12((u32)imm);
    153     u64 hi = (imm - (u64)(i64)lo) >> 12;
    154     rv_emit_li64(v, mc, rd, hi);
    155     rv64_emit32(mc, rv_slli(rd, rd, 12));
    156     if (lo) rv64_emit32(mc, rv_addi(rd, rd, lo));
    157   }
    158 }
    159 
    160 /* sf!=0 selects a full native-width materialization; sf==0 a 32-bit value. On
    161  * rv32 the native width is 32, so the wide branch collapses to the 32-bit
    162  * path. */
    163 static void rv_emit_load_imm(const RiscvVariant* v, MCEmitter* mc, u32 sf,
    164                              u32 rd, i64 imm) {
    165   if (!sf || v->xlen == 32u) {
    166     rv_emit_li32(v, mc, rd, (i32)imm);
    167     return;
    168   }
    169   if (fits_i32(imm))
    170     rv_emit_li32(v, mc, rd, (i32)imm);
    171   else
    172     rv_emit_li64(v, mc, rd, (u64)imm);
    173 }
    174 
    175 /* rd = base + off, materializing the offset when it exceeds imm12. Uses RV_TMP1
    176  * as scratch for the wide path, so callers must keep RV_TMP1 free. */
    177 static void rv_emit_addr_adjust(const RiscvVariant* v, MCEmitter* mc, u32 rd,
    178                                 u32 base, i32 off) {
    179   if (off == 0) {
    180     if (rd != base) rv64_emit32(mc, rv_addi(rd, base, 0));
    181     return;
    182   }
    183   if (fits_i12(off)) {
    184     rv64_emit32(mc, rv_addi(rd, base, off));
    185     return;
    186   }
    187   rv_emit_load_imm(v, mc, 1, RV_TMP1, (i64)off);
    188   rv64_emit32(mc, rv_add(rd, base, RV_TMP1));
    189 }
    190 
    191 static u32 enc_int_store(const RiscvVariant* v, u32 nbytes, u32 src, u32 base,
    192                          i32 off) {
    193   switch (nbytes) {
    194     case 1:
    195       return rv_sb(src, base, off);
    196     case 2:
    197       return rv_sh(src, base, off);
    198     case 4:
    199       return rv_sw(src, base, off);
    200     default:
    201       /* The widest GPR store is SD on rv64, SW on rv32. */
    202       return v->ptr_bytes == 8u ? rv_sd(src, base, off) : rv_sw(src, base, off);
    203   }
    204 }
    205 static u32 enc_int_load(const RiscvVariant* v, u32 nbytes, int sign_ext, u32 rd,
    206                         u32 base, i32 off) {
    207   switch (nbytes) {
    208     case 1:
    209       return sign_ext ? rv_lb(rd, base, off) : rv_lbu(rd, base, off);
    210     case 2:
    211       return sign_ext ? rv_lh(rd, base, off) : rv_lhu(rd, base, off);
    212     case 4:
    213       /* LWU (zero-extending 32-bit load) is RV64-only; on rv32 a 4-byte load
    214        * is just LW (no wider container to zero-extend into). */
    215       return sign_ext || v->xlen == 32u ? rv_lw(rd, base, off)
    216                                         : rv_lwu(rd, base, off);
    217     default:
    218       /* The widest GPR load is LD on rv64, LW on rv32. */
    219       return v->ptr_bytes == 8u ? rv_ld(rd, base, off) : rv_lw(rd, base, off);
    220   }
    221 }
    222 
    223 /* Pointer-width GPR load/store (GOT entries, frame-value bases, saved ra/s0,
    224  * sret/indirect/va_list pointers): LD/SD on rv64, LW/SW on rv32. */
    225 static u32 rv_ld_ptr(const RiscvVariant* v, u32 rd, u32 base, i32 off) {
    226   return v->ptr_bytes == 8u ? rv_ld(rd, base, off) : rv_lw(rd, base, off);
    227 }
    228 static u32 rv_sd_ptr(const RiscvVariant* v, u32 src, u32 base, i32 off) {
    229   return v->ptr_bytes == 8u ? rv_sd(src, base, off) : rv_sw(src, base, off);
    230 }
    231 
    232 /* ============================ target state ============================ */
    233 
    234 /* Frame slots and callee-save records live in the shared NativeFrame
    235  * bookkeeping (cg/native_frame.h); these aliases keep the rv64-local spellings.
    236  */
    237 typedef NativeFrameSlotEntry RvNativeSlot;
    238 typedef NativeFrameCalleeSave RvCalleeSave;
    239 
    240 typedef enum RvPatchKind { RV_PATCH_ALLOCA } RvPatchKind;
    241 
    242 typedef struct RvPatch {
    243   u8 kind; /* RvPatchKind */
    244   u32 pos;
    245   u32 dst_reg;
    246 } RvPatch;
    247 
    248 typedef struct RvNativeTarget {
    249   NativeTarget base;
    250   /* Immutable per-XLEN descriptor (rv32 / rv64), set once in the constructor
    251    * from c->target.arch. Every XLEN-dependent emit site reads it; with the
    252    * rv64 variant each site reproduces the historical literal exactly. */
    253   const RiscvVariant* variant;
    254   SrcLoc loc;
    255   const CGFuncDesc* func;
    256 
    257   /* Shared frame bookkeeping: slot table, cum_off, max_outgoing, callee-save
    258    * set, and the known_frame / has_alloca / frame_final flags. */
    259   NativeFrame frame;
    260   u32 frame_size_final;
    261   u32 fp_pair_off;
    262   u32 minimal_prologue_words; /* known-frame path: exact prologue length, else 0
    263                                */
    264 
    265   /* Known-frame (-O1) leaf no-frame tier (aa64's slim_prologue equivalent),
    266    * settled in rv_func_begin_known_frame; always 0 on the single-pass path. A
    267    * leaf with no callee-saves, no body slots, no outgoing args, no
    268    * sret/variadic and register-only params never reads s0 nor clobbers ra, so
    269    * it emits NO prologue and a bare `ret` — the whole frame setup/teardown is
    270    * elided. RISC-V has no pre/post-indexed store, so aa64's fp_at_bottom fold
    271    * would save zero instructions on a kept frame and is intentionally not
    272    * ported (see doc/plan/ARCH.md §2); this leaf tier is the rv64 win. */
    273   u8 slim_prologue;
    274 
    275   u32 incoming_stack_size; /* fixed-param stack bytes (tail-call check) */
    276   u32 next_param_int;
    277   u32 next_param_fp;
    278   u32 next_param_stack;
    279   u8 has_sret;
    280   u8 is_variadic;
    281   NativeFrameSlot sret_ptr_slot;
    282 
    283   RvPatch* patches;
    284   u32 npatches;
    285   u32 patches_cap;
    286   u32 nalloca;
    287 
    288   u32 func_start;
    289   u32 prologue_pos;
    290   u32 prologue_region_words; /* single-pass: reserved deferred-`sub` region */
    291   MCLabel epilogue_label;
    292 } RvNativeTarget;
    293 
    294 static RvNativeTarget* rv_of(NativeTarget* t) { return (RvNativeTarget*)t; }
    295 
    296 static _Noreturn void rv_panic(RvNativeTarget* a, const char* msg) {
    297   compiler_panic(a->base.c, a->loc, "rv64 native target: %s", msg);
    298 }
    299 
    300 static RvNativeSlot* rv_slot_get(RvNativeTarget* a, NativeFrameSlot fs) {
    301   return native_frame_slot_at(&a->frame, fs);
    302 }
    303 
    304 /* s0-relative byte offset of a frame slot's base (address = s0 + ret). */
    305 static i32 rv_s0_off_slot(const RvNativeSlot* s) { return -(i32)s->off; }
    306 
    307 static u32 rv_va_save_sz(const RvNativeTarget* a) {
    308   /* ABI-derived: the variadic register-save area is gp_reg_count*gp_slot_size
    309    * (a0..a7 = 64 bytes for LP64D, 32 for ILP32). Only present in variadics. */
    310   return a->is_variadic ? native_frame_va_save_bytes(a->base.c->abi) : 0u;
    311 }
    312 
    313 /* s0-relative byte offset of incoming stack arg at byte_off. Stack args sit
    314  * just above the saved pair; the variadic GP save area (when present) is
    315  * contiguous with them at [s0 + frame_save_size). */
    316 static i32 rv_s0_off_in_arg(const RvNativeTarget* a, u32 byte_off) {
    317   u32 base = a->variant->frame_save_size;
    318   if (a->is_variadic) base += rv_va_save_sz(a);
    319   return (i32)(base + byte_off);
    320 }
    321 
    322 /* Callee-saved registers are homed just below the locals at rv_save_off() —
    323  * they are NOT frame slots, so the frame size must reserve their bytes
    324  * explicitly. Integer saves are ptr_bytes wide (sd on rv64, sw on rv32); FP
    325  * saves are always 8 bytes (fsd, even on rv32d). On rv64 both are 8 so the sum
    326  * is identical to the historical ncallee_saves*8. Zero at -O0. */
    327 static u32 rv_callee_save_bytes(const RvNativeTarget* a) {
    328   u32 ptr = a->variant->ptr_bytes;
    329   u32 i, bytes = 0;
    330   for (i = 0; i < a->frame.ncallee_saves; ++i)
    331     bytes += a->frame.callee_saves[i].cls == NATIVE_REG_FP ? 8u : ptr;
    332   return bytes;
    333 }
    334 
    335 static u32 rv_frame_size(const RvNativeTarget* a) {
    336   u32 raw = a->variant->frame_save_size + a->frame.cum_off +
    337             rv_callee_save_bytes(a) + a->frame.max_outgoing + rv_va_save_sz(a);
    338   return align_up_u32(raw, 16u);
    339 }
    340 
    341 static u32 rv_fp_pair_off(const RvNativeTarget* a, u32 frame_size) {
    342   return frame_size - a->variant->frame_save_size - rv_va_save_sz(a);
    343 }
    344 
    345 /* ============================ type helpers ============================ */
    346 
    347 /* Scalar size/align/mem/class/loc constructors are shared in native_target.h
    348  * (native_type_size, native_type_align, native_mem_for_type,
    349  * native_class_for_type_fp_le8, native_loc_reg, native_loc_stack,
    350  * native_loc_is_fp). loc_reg's mask is arch-specific and stays here. */
    351 
    352 /* True when a scalar value is WIDER than XLEN's natural single-register width,
    353  * i.e. it needs the "wide" (rv64 64-bit) ops rather than the base ops. On rv64
    354  * a pointer is 8 bytes and counts as wide alongside i64/double; on rv32 a
    355  * pointer is 4 bytes and fits a single 32-bit register, so it is NOT wide and
    356  * the base (non-W) ops apply. (Kept named rv_is_64 to minimize churn; for the
    357  * rv64 variant the result is byte-identical to the old predicate.) */
    358 static int rv_is_64(NativeTarget* t, KitCgTypeId type) {
    359   const RiscvVariant* v = rv_of(t)->variant;
    360   return native_type_size(t, type) >= 8u ||
    361          (v->xlen == 64u && cg_type_is_ptr(t->c, type));
    362 }
    363 
    364 /* Scalar byte width of a register operand. Reads the NDT-stamped szinfo
    365  * descriptor (one byte) when present, else falls back to the live type query —
    366  * byte-identical to native_type_size for in-range scalars. */
    367 static u32 loc_size32(NativeTarget* t, NativeLoc loc) {
    368   if (loc.szinfo & NATIVE_SZINFO_VALID) return native_szinfo_size(loc.szinfo);
    369   return native_type_size(t, loc.type);
    370 }
    371 
    372 static u32 rv_internal_tmp(RvNativeTarget* a, u32 avoid_mask) {
    373   static const u8 temps[] = {RV_TMP1, RV_TMP0, RV_TMP2, RV_TMP3};
    374   for (u32 i = 0; i < sizeof temps / sizeof temps[0]; ++i)
    375     if (!(avoid_mask & (1u << temps[i]))) return temps[i];
    376   rv_panic(a, "no backend temporary for frame component");
    377 }
    378 
    379 /* Load a typed scalar value from a frame slot. Address components retain their
    380  * own type because spill-slot coloring may reuse a wider descriptor; loading
    381  * the descriptor width would consume neighboring bytes. Far s0 offsets are
    382  * materialized through a distinct backend-owned register. */
    383 static void rv_load_frame_component(RvNativeTarget* a, u32 dst,
    384                                     NativeFrameSlot slot, KitCgTypeId type,
    385                                     u32 avoid_mask) {
    386   RvNativeSlot* s = rv_slot_get(a, slot);
    387   i32 off = rv_s0_off_slot(s);
    388   u32 size;
    389   if (!type) rv_panic(a, "frame-value component has no exact type");
    390   size = native_type_size(&a->base, type);
    391   if (fits_i12(off)) {
    392     rv64_emit32(a->base.mc,
    393                 enc_int_load(a->variant, size, 0, dst, RV_S0, off));
    394     return;
    395   }
    396   {
    397     u32 tmp = rv_internal_tmp(a, avoid_mask | (1u << dst) | (1u << RV_S0));
    398     rv_emit_load_imm(a->variant, a->base.mc, 1, tmp, (i64)off);
    399     rv64_emit32(a->base.mc, rv_add(tmp, RV_S0, tmp));
    400     rv64_emit32(a->base.mc,
    401                 enc_int_load(a->variant, size, 0, dst, tmp, 0));
    402   }
    403 }
    404 
    405 /* The original rv_is_64 predicate exactly, but sourcing the width from the
    406  * cached descriptor: `size >= 8 || (xlen==64 && is_ptr)`. The is_ptr clause
    407  * keeps the rv32 4-byte-pointer distinction (a stamped 4-byte pointer caches
    408  * size 4, so >= 8 is false and the xlen guard decides). Byte-identical. */
    409 static int loc_is_64(NativeTarget* t, NativeLoc loc) {
    410   const RiscvVariant* v = rv_of(t)->variant;
    411   return loc_size32(t, loc) >= 8u ||
    412          (v->xlen == 64u && cg_type_is_ptr(t->c, loc.type));
    413 }
    414 
    415 static u32 loc_reg(NativeLoc loc) { return loc.v.reg & 0x1fu; }
    416 
    417 /* ============================ register tables ============================ */
    418 
    419 #define RV_PHYS_INT_ARG(r, idx)                        \
    420   {.reg = (r),                                         \
    421    .cls = NATIVE_REG_INT,                              \
    422    .abi_index = (idx),                                 \
    423    .flags = NATIVE_REG_CALLER_SAVED | NATIVE_REG_ARG | \
    424             ((idx) < 2u ? NATIVE_REG_RET : 0),         \
    425    .spill_cost = 1u,                                   \
    426    .copy_cost = 1u}
    427 #define RV_PHYS_INT_CALLER(r)                               \
    428   {.reg = (r),                                              \
    429    .cls = NATIVE_REG_INT,                                   \
    430    .abi_index = 0xffu,                                      \
    431    .flags = NATIVE_REG_ALLOCABLE | NATIVE_REG_CALLER_SAVED, \
    432    .spill_cost = 1u,                                        \
    433    .copy_cost = 1u}
    434 #define RV_PHYS_INT_CALLEE(r)                               \
    435   {.reg = (r),                                              \
    436    .cls = NATIVE_REG_INT,                                   \
    437    .abi_index = 0xffu,                                      \
    438    .flags = NATIVE_REG_ALLOCABLE | NATIVE_REG_CALLEE_SAVED, \
    439    .spill_cost = 4u,                                        \
    440    .copy_cost = 1u}
    441 #define RV_PHYS_INT_RESERVED_ROLE(r, role) \
    442   {.reg = (r),                              \
    443    .cls = NATIVE_REG_INT,                   \
    444    .abi_index = 0xffu,                      \
    445    .flags = NATIVE_REG_RESERVED | (role),   \
    446    .spill_cost = 0u,                        \
    447    .copy_cost = 0u}
    448 #define RV_PHYS_INT_RESERVED(r) \
    449   RV_PHYS_INT_RESERVED_ROLE((r), NATIVE_REG_NONE)
    450 #define RV_PHYS_INT_RESERVED_CALLER(r) \
    451   RV_PHYS_INT_RESERVED_ROLE((r), NATIVE_REG_CALLER_SAVED)
    452 
    453 /* t0..t3 (x5,x6,x7,x28) are backend-private hook/asm temporaries
    454  * (RV_TMP0..RV_TMP3), reserved and never exposed as optimizer operand
    455  * locations. t4/t5/t6 form the O1 instruction-scoped operand-temp bank,
    456  * disjoint from backend temporaries so a hook cannot overwrite a leased
    457  * operand. Three are needed, not two: a load/store target can need an address
    458  * materialization alongside a 3-operand operation. aarch64 declares three for
    459  * the same reason; x64 needs only two because it folds the index into its
    460  * addressing mode. Location MIR retains spills as frame locations until the
    461  * consuming instruction opens this lease scope.
    462  *
    463  * rv_ndt_int_allocable is the NDT (-O0) value-cache / scratch pool and is read
    464  * only
    465  * by NativeDirectTarget; the optimizer allocates over the phys[] ALLOCABLE flags
    466  * (s1..s11, callee-saved, saved by its -O1 prologue) instead. The NDT pool is
    467  * the caller-saved argument registers a0..a7: the -O0 cache flushes to frame
    468  * homes at every call/branch/return, so nothing needs to survive in a register
    469  * across a call, and caller-saved regs keep the prologue free of callee-save
    470  * spills — which also keeps the single-pass prologue placeholder (a fixed
    471  * RV_PROLOGUE_WORDS region) from ever overflowing on large/variadic frames the
    472  * way callee-save offsets would. Incoming args are spilled to homes at entry
    473  * before any body op, so caching in a0..a7 cannot clobber a live parameter. */
    474 static const Reg rv_ndt_int_allocable[] = {10u, 11u, 12u, 13u, 14u,
    475                                            15u, 16u, 17u}; /* a0..a7 */
    476 static const Reg rv_int_scratch[] = {29u, 30u, 31u};  /* t4, t5, t6 */
    477 static const Reg rv_int_asm_temps[] = {RV_TMP2, RV_TMP3};
    478 static const Reg rv_direct_asm_int[] = {10u, 11u, 12u, 13u, 14u, 15u,
    479                                         16u, 17u, 29u, 30u, 31u};
    480 
    481 static const NativePhysRegInfo rv_int_phys[] = {
    482     RV_PHYS_INT_RESERVED(0u), /* zero */
    483     RV_PHYS_INT_RESERVED(1u), /* ra */
    484     RV_PHYS_INT_RESERVED(2u), /* sp */
    485     RV_PHYS_INT_RESERVED(3u), /* gp */
    486     RV_PHYS_INT_RESERVED(4u), /* tp */
    487     RV_PHYS_INT_RESERVED_CALLER(5u), /* t0 = TMP0 */
    488     RV_PHYS_INT_RESERVED_CALLER(6u), /* t1 = TMP1 */
    489     RV_PHYS_INT_RESERVED_CALLER(7u), /* t2 = TMP2 (backend/asm) */
    490     RV_PHYS_INT_RESERVED_ROLE(8u, NATIVE_REG_CALLEE_SAVED), /* s0/fp */
    491     RV_PHYS_INT_CALLEE(9u),   /* s1 */
    492     RV_PHYS_INT_ARG(10u, 0u),  RV_PHYS_INT_ARG(11u, 1u),
    493     RV_PHYS_INT_ARG(12u, 2u),  RV_PHYS_INT_ARG(13u, 3u),
    494     RV_PHYS_INT_ARG(14u, 4u),  RV_PHYS_INT_ARG(15u, 5u),
    495     RV_PHYS_INT_ARG(16u, 6u),  RV_PHYS_INT_ARG(17u, 7u),
    496     RV_PHYS_INT_CALLEE(18u),   RV_PHYS_INT_CALLEE(19u),
    497     RV_PHYS_INT_CALLEE(20u),   RV_PHYS_INT_CALLEE(21u),
    498     RV_PHYS_INT_CALLEE(22u),   RV_PHYS_INT_CALLEE(23u),
    499     RV_PHYS_INT_CALLEE(24u),   RV_PHYS_INT_CALLEE(25u),
    500     RV_PHYS_INT_CALLEE(26u),   RV_PHYS_INT_CALLEE(27u),
    501     RV_PHYS_INT_RESERVED_CALLER(28u), /* t3 = TMP3 (backend/asm) */
    502     RV_PHYS_INT_RESERVED_CALLER(29u), /* t4 = O1 operand temp / O0 scratch */
    503     RV_PHYS_INT_RESERVED_CALLER(30u), /* t5 = O1 operand temp / O0 scratch */
    504     RV_PHYS_INT_RESERVED_CALLER(31u), /* t6 = O1 operand temp / O0 scratch */
    505 };
    506 
    507 #define RV_PHYS_FP_ARG(r, idx)                         \
    508   {.reg = (r),                                         \
    509    .cls = NATIVE_REG_FP,                               \
    510    .abi_index = (idx),                                 \
    511    .flags = NATIVE_REG_CALLER_SAVED | NATIVE_REG_ARG | \
    512             ((idx) < 2u ? NATIVE_REG_RET : 0),         \
    513    .spill_cost = 1u,                                   \
    514    .copy_cost = 1u}
    515 #define RV_PHYS_FP_CALLER(r)                                \
    516   {.reg = (r),                                              \
    517    .cls = NATIVE_REG_FP,                                    \
    518    .abi_index = 0xffu,                                      \
    519    .flags = NATIVE_REG_ALLOCABLE | NATIVE_REG_CALLER_SAVED, \
    520    .spill_cost = 1u,                                        \
    521    .copy_cost = 1u}
    522 #define RV_PHYS_FP_CALLEE(r)                                \
    523   {.reg = (r),                                              \
    524    .cls = NATIVE_REG_FP,                                    \
    525    .abi_index = 0xffu,                                      \
    526    .flags = NATIVE_REG_ALLOCABLE | NATIVE_REG_CALLEE_SAVED, \
    527    .spill_cost = 4u,                                        \
    528    .copy_cost = 1u}
    529 #define RV_PHYS_FP_RESERVED(r)                   \
    530   {.reg = (r),                                   \
    531    .cls = NATIVE_REG_FP,                         \
    532    .abi_index = 0xffu,                           \
    533    .flags = NATIVE_REG_RESERVED |                \
    534             NATIVE_REG_CALLER_SAVED,             \
    535    .spill_cost = 0u,                             \
    536    .copy_cost = 0u}
    537 
    538 /* NDT (-O0) fp value-cache / scratch pool (read only by NativeDirectTarget; the
    539  * optimizer uses the phys[] ALLOCABLE flags, which also cover the callee-saved
    540  * fs0..fs11). Caller-saved only — the fa0..fa7 arg registers, then ft4..ft7 and
    541  * ft8..ft11 — for the same reasons as the int pool: the -O0 cache flushes at
    542  * every call/branch/return, so caller-saved suffices, and keeping callee-saves
    543  * out of the pool keeps the fixed single-pass prologue placeholder from
    544  * overflowing. fa0..fa7 come FIRST (mirroring the int pool's a0..a7):
    545  * nd_cache_alloc scans the pool in order, so fronting the arg registers lets a
    546  * producer materialize a call's fp args directly into their ABI registers
    547  * (Lever 1, via api_pack_call_args_in_order). ft0/ft1 are backend/asm
    548  * temporaries; ft2/ft3 form the O1 operand-temp bank and O0 scratch policy. */
    549 static const Reg rv_ndt_fp_allocable[] = {
    550     10u, 11u, 12u, 13u, 14u, 15u, 16u, 17u, 4u,  5u,  6u, 7u,
    551     28u, 29u, 30u, 31u}; /* fa0-7,ft4-7,ft8-11 */
    552 static const Reg rv_fp_scratch[] = {2u, 3u};               /* ft2, ft3 */
    553 static const Reg rv_fp_asm_temps[] = {RV_FTMP0, RV_FTMP1};
    554 static const Reg rv_direct_asm_fp[] = {10u, 11u, 12u, 13u, 14u, 15u,
    555                                        16u, 17u, 4u,  5u,  6u,  7u,
    556                                        28u, 29u, 30u, 31u};
    557 
    558 static const NativePhysRegInfo rv_fp_phys[] = {
    559     RV_PHYS_FP_RESERVED(0u), /* ft0 = FTMP0 */
    560     RV_PHYS_FP_RESERVED(1u), /* ft1 = FTMP1 */
    561     RV_PHYS_FP_RESERVED(2u), /* ft2 = O1 operand temp / O0 scratch */
    562     RV_PHYS_FP_RESERVED(3u), /* ft3 = O1 operand temp / O0 scratch */
    563     RV_PHYS_FP_CALLER(4u),   RV_PHYS_FP_CALLER(5u),   RV_PHYS_FP_CALLER(6u),
    564     RV_PHYS_FP_CALLER(7u),   RV_PHYS_FP_CALLEE(8u),   RV_PHYS_FP_CALLEE(9u),
    565     RV_PHYS_FP_ARG(10u, 0u), RV_PHYS_FP_ARG(11u, 1u), RV_PHYS_FP_ARG(12u, 2u),
    566     RV_PHYS_FP_ARG(13u, 3u), RV_PHYS_FP_ARG(14u, 4u), RV_PHYS_FP_ARG(15u, 5u),
    567     RV_PHYS_FP_ARG(16u, 6u), RV_PHYS_FP_ARG(17u, 7u), RV_PHYS_FP_CALLEE(18u),
    568     RV_PHYS_FP_CALLEE(19u),  RV_PHYS_FP_CALLEE(20u),  RV_PHYS_FP_CALLEE(21u),
    569     RV_PHYS_FP_CALLEE(22u),  RV_PHYS_FP_CALLEE(23u),  RV_PHYS_FP_CALLEE(24u),
    570     RV_PHYS_FP_CALLEE(25u),  RV_PHYS_FP_CALLEE(26u),  RV_PHYS_FP_CALLEE(27u),
    571     RV_PHYS_FP_CALLER(28u),  RV_PHYS_FP_CALLER(29u),  RV_PHYS_FP_CALLER(30u),
    572     RV_PHYS_FP_CALLER(31u),
    573 };
    574 
    575 static const NativeAllocClassInfo rv_classes[] = {
    576     {.cls = NATIVE_REG_INT,
    577      .ndt_allocable = rv_ndt_int_allocable,
    578      .ndt_allocable_count =
    579          sizeof rv_ndt_int_allocable / sizeof rv_ndt_int_allocable[0],
    580      .scratch = rv_int_scratch,
    581      .nscratch = sizeof rv_int_scratch / sizeof rv_int_scratch[0],
    582      .emit_temps = rv_int_scratch,
    583      .nemit_temps = sizeof rv_int_scratch / sizeof rv_int_scratch[0],
    584      .asm_temps = rv_int_asm_temps,
    585      .nasm_temps = sizeof rv_int_asm_temps / sizeof rv_int_asm_temps[0],
    586      .direct_asm_allocable = rv_direct_asm_int,
    587      .ndirect_asm_allocable =
    588          sizeof rv_direct_asm_int / sizeof rv_direct_asm_int[0],
    589      .emit_cache_mask = (1u << 29u) | (1u << 30u) | (1u << 31u),
    590      .phys = rv_int_phys,
    591      .nphys = sizeof rv_int_phys / sizeof rv_int_phys[0]},
    592     {.cls = NATIVE_REG_FP,
    593      .ndt_allocable = rv_ndt_fp_allocable,
    594      .ndt_allocable_count =
    595          sizeof rv_ndt_fp_allocable / sizeof rv_ndt_fp_allocable[0],
    596      .scratch = rv_fp_scratch,
    597      .nscratch = sizeof rv_fp_scratch / sizeof rv_fp_scratch[0],
    598      .emit_temps = rv_fp_scratch,
    599      .nemit_temps = sizeof rv_fp_scratch / sizeof rv_fp_scratch[0],
    600      .asm_temps = rv_fp_asm_temps,
    601      .nasm_temps = sizeof rv_fp_asm_temps / sizeof rv_fp_asm_temps[0],
    602      .direct_asm_allocable = rv_direct_asm_fp,
    603      .ndirect_asm_allocable =
    604          sizeof rv_direct_asm_fp / sizeof rv_direct_asm_fp[0],
    605      .emit_cache_mask = (1u << 2u) | (1u << 3u),
    606      .phys = rv_fp_phys,
    607      .nphys = sizeof rv_fp_phys / sizeof rv_fp_phys[0]},
    608 };
    609 
    610 /* Resolve a register name ("a7", "fa0", ...) to its (class, Reg). Powers the
    611  * optimizer's inline-asm clobber masks and explicit hard-register operands
    612  * ("{a7}" from a GNU local register variable). x0..x31 are DWARF 0..31; the
    613  * FP bank f0..f31 is DWARF 32..63. Returns non-zero for a non-register name
    614  * (cc/memory/unknown), which the caller skips. */
    615 static int rv_resolve_name(const NativeRegInfo* ri, Slice name, Reg* out,
    616                            NativeAllocClass* cls_out) {
    617   char buf[16];
    618   uint32_t dwarf;
    619   (void)ri;
    620   if (!name.s || !name.len || name.len >= sizeof buf) return 1;
    621   memcpy(buf, name.s, name.len);
    622   buf[name.len] = '\0';
    623   if (rv64_register_index(buf, &dwarf) != 0) return 1;
    624   if (dwarf <= 31u) {
    625     *cls_out = NATIVE_REG_INT;
    626     *out = (Reg)dwarf;
    627     return 0;
    628   }
    629   if (dwarf >= 32u && dwarf <= 63u) {
    630     *cls_out = NATIVE_REG_FP;
    631     *out = (Reg)(dwarf - 32u);
    632     return 0;
    633   }
    634   return 1;
    635 }
    636 
    637 static int rv_asm_operand_reg_ok(const NativeRegInfo* ri, NativeAllocClass cls,
    638                                  Reg reg) {
    639   (void)ri;
    640   if (cls == NATIVE_REG_INT) {
    641     if (reg == 9u) return 1;                /* s1 */
    642     if (reg >= 10u && reg <= 17u) return 1; /* a0..a7 */
    643     if (reg >= 18u && reg <= 27u) return 1; /* s2..s11 */
    644     if (reg == 31u) return 1;               /* t6 */
    645     return 0;
    646   }
    647   if (cls == NATIVE_REG_FP) return reg >= 4u && reg <= 31u;
    648   return 0;
    649 }
    650 
    651 static int rv_asm_constraint_reg(const NativeRegInfo* ri, const char* body,
    652                                  NativeAllocClass* cls_out, Reg* fixed_out,
    653                                  u32* allowed_mask_out) {
    654   (void)ri;
    655   if (!body || !body[0]) return 0;
    656   if (fixed_out) *fixed_out = REG_NONE;
    657   if (allowed_mask_out) *allowed_mask_out = 0;
    658   if (body[0] == 'r' && body[1] == '\0') {
    659     if (cls_out) *cls_out = NATIVE_REG_INT;
    660     return 1;
    661   }
    662   if (body[0] == 'f' && body[1] == '\0') {
    663     if (cls_out) *cls_out = NATIVE_REG_FP;
    664     return 1;
    665   }
    666   if (body[0] == 'c' && body[1] == 'r' && body[2] == '\0') {
    667     if (cls_out) *cls_out = NATIVE_REG_INT;
    668     if (allowed_mask_out) *allowed_mask_out = 0x0000ff00u; /* x8..x15 */
    669     return 1;
    670   }
    671   if (body[0] == 'c' && body[1] == 'f' && body[2] == '\0') {
    672     if (cls_out) *cls_out = NATIVE_REG_FP;
    673     if (allowed_mask_out) *allowed_mask_out = 0x0000ff00u; /* f8..f15 */
    674     return 1;
    675   }
    676   return 0;
    677 }
    678 
    679 static const NativeRegInfo rv_reg_info = {
    680     .classes = rv_classes,
    681     .nclasses = sizeof rv_classes / sizeof rv_classes[0],
    682     /* The NDT value-cache / scratch pool is the caller-saved a-regs (int) and
    683      * ft/fa regs (fp), so the -O0 path never needs a callee-saved register —
    684      * enabling the tcc-style deferred-`sub` prologue (see rv_func_begin). */
    685     .ndt_caller_saved_only = 1u,
    686     /* a0/fa0 are clobbered only by calls: rv div/rem/mul are R-type with
    687      * explicit operands (unlike x86-64's implicit-RAX div/mul), so a scalar
    688      * call result can stay cached in a0/fa0 across the following straight-line
    689      * ops and feed the next consumer with no mov — exactly as on aa64 (cf. the
    690      * aa64 ndt_result_reg_stable comment; doc/plan/PERF.md §4.2).
    691      * The NDT places the result directly in the ABI result reg post-call. */
    692     .ndt_result_reg_stable = 1u,
    693     .resolve_name = rv_resolve_name,
    694     .asm_operand_reg_ok = rv_asm_operand_reg_ok,
    695     .asm_constraint_reg = rv_asm_constraint_reg,
    696 };
    697 
    698 /* ============================ legality ============================ */
    699 
    700 static int rv_imm_legal(NativeTarget* t, NativeImmUse use, u32 op,
    701                         KitCgTypeId type, i64 imm) {
    702   /* SLLI/SRLI/SRAI shamt is shamt_bits wide: 6 bits (max 63) on rv64, 5 bits
    703    * (max 31) on rv32. */
    704   i64 shamt_max = (i64)((1u << rv_of(t)->variant->shamt_bits) - 1u);
    705   (void)type;
    706   switch (use) {
    707     case NATIVE_IMM_MOVE:
    708       return 1;
    709     case NATIVE_IMM_BINOP:
    710       switch ((BinOp)op) {
    711         case BO_IADD:
    712           return fits_i12(imm);
    713         case BO_ISUB:
    714           return fits_i12(-imm); /* emitted as ADDI with negated imm */
    715         case BO_AND:
    716         case BO_OR:
    717         case BO_XOR:
    718           return fits_i12(imm);
    719         case BO_SHL:
    720         case BO_SHR_S:
    721         case BO_SHR_U:
    722           return imm >= 0 && imm <= shamt_max;
    723         default:
    724           return 0;
    725       }
    726     case NATIVE_IMM_CMP:
    727       /* Only the compare-against-zero case is folded; a non-zero RHS is
    728        * materialized into a register first. This is a deliberate codegen
    729        * simplification, not an architectural constraint — SLTI/SLTIU do
    730        * accept a 12-bit signed immediate, but the comparison lowering does
    731        * not special-case immediate operands beyond zero. */
    732       return imm == 0;
    733     case NATIVE_IMM_ADDR_OFFSET:
    734       return fits_i12(imm);
    735   }
    736   return 0;
    737 }
    738 
    739 static int rv_addr_legal(NativeTarget* t, const NativeAddr* addr,
    740                          MemAccess mem) {
    741   (void)t;
    742   (void)mem;
    743   if (!addr) return 0;
    744   if (addr->index_kind != NATIVE_ADDR_INDEX_NONE) return 0;
    745   if (addr->base_kind != NATIVE_ADDR_BASE_REG &&
    746       addr->base_kind != NATIVE_ADDR_BASE_FRAME)
    747     return 0;
    748   return fits_i12(addr->offset);
    749 }
    750 
    751 /* ============================ memory ============================ */
    752 
    753 /* Emit the RISC-V PC-relative HI20/LO12 reloc pair:
    754  *   auipc(dst, 0)             ; R_RV_*_HI20 against `target_sym`
    755  *   .LpcrelHi local anchor at the auipc PC
    756  *   <follow_insn>             ; R_RV_PCREL_LO12_I against the anchor
    757  * The HI20 reloc kind (`hi20`) and the encoded follow-up instruction word
    758  * (`follow_insn` — e.g. addi dst,dst,0 for a direct addr or ld dst,dst,0 for a
    759  * GOT indirection) vary per call site; the anchor symbol, LO12 reloc kind, and
    760  * all addends/flags are fixed. The LO12 reloc is always *_LO12_I (I-type insn);
    761  * no caller currently needs the *_LO12_S form. Behavior-preserving extraction
    762  * of the auipc+anchor idiom shared by rv_emit_global_addr and
    763  * rv_load_label_addr. */
    764 static void rv_emit_pcrel_anchor(NativeTarget* t, u32 dst, ObjSymId target_sym,
    765                                  RelocKind hi20, u32 follow_insn) {
    766   MCEmitter* mc = t->mc;
    767   u32 sec = mc->section_id;
    768   u32 ap = mc_pos(mc);
    769   rv64_emit32(mc, rv_auipc(dst, 0));
    770   mc_emit_reloc_at(mc, sec, ap, hi20, target_sym, 0, 0, 0);
    771   {
    772     Sym an = pool_intern_slice(t->c->global, SLICE_LIT(".LpcrelHi"));
    773     ObjSymId anchor = obj_symbol(t->obj, an, SB_LOCAL, SK_OBJ, sec, (u64)ap, 0);
    774     u32 lp = mc_pos(mc);
    775     rv64_emit32(mc, follow_insn);
    776     mc_emit_reloc_at(mc, sec, lp, R_RV_PCREL_LO12_I, anchor, 0, 0, 0);
    777   }
    778 }
    779 
    780 /* Materialize the runtime address of a global into `dst`, including addend. */
    781 static void rv_emit_global_addr(RvNativeTarget* a, u32 dst, ObjSymId sym,
    782                                 i64 addend) {
    783   NativeTarget* t = &a->base;
    784   MCEmitter* mc = t->mc;
    785   if (obj_symbol_extern_via_got(t->c, t->obj, sym))
    786     rv_emit_pcrel_anchor(t, dst, sym, R_RV_GOT_HI20,
    787                          rv_ld_ptr(a->variant, dst, dst, 0));
    788   else
    789     rv_emit_pcrel_anchor(t, dst, sym, R_RV_PCREL_HI20, rv_addi(dst, dst, 0));
    790   if (addend) rv_emit_addr_adjust(a->variant, mc, dst, dst, (i32)addend);
    791 }
    792 
    793 /* Fold (base_reg << 0) + (index << scale) into RV_TMP0 via Zba. */
    794 static u32 rv_fold_index(RvNativeTarget* a, u32 base, u32 idx, u8 log2_scale) {
    795   MCEmitter* mc = a->base.mc;
    796   switch (log2_scale) {
    797     case 0:
    798       rv64_emit32(mc, rv_add(RV_TMP0, base, idx));
    799       break;
    800     case 1:
    801       rv64_emit32(mc, rv_sh1add(RV_TMP0, idx, base));
    802       break;
    803     case 2:
    804       rv64_emit32(mc, rv_sh2add(RV_TMP0, idx, base));
    805       break;
    806     default:
    807       rv64_emit32(mc, rv_sh3add(RV_TMP0, idx, base));
    808       break;
    809   }
    810   return RV_TMP0;
    811 }
    812 
    813 /* Resolve any NativeAddr to a base register + imm12 offset. RISC-V has no
    814  * indexed load/store, so an index is folded into RV_TMP0 via Zba; far offsets
    815  * and GLOBAL or exact-typed FRAME_VALUE components are materialized in the
    816  * backend-private RV_TMP0/RV_TMP1 bank. Plain FRAME bases remain s0-relative. */
    817 static void rv_resolve_mem_addr(RvNativeTarget* a, const NativeAddr* addr,
    818                                 u32* base_out, i32* off_out) {
    819   MCEmitter* mc = a->base.mc;
    820   u32 base;
    821   i32 off;
    822   switch (addr->base_kind) {
    823     case NATIVE_ADDR_BASE_REG:
    824       base = addr->base.reg & 0x1fu;
    825       off = addr->offset;
    826       break;
    827     case NATIVE_ADDR_BASE_FRAME: {
    828       RvNativeSlot* s = rv_slot_get(a, addr->base.frame);
    829       base = RV_S0;
    830       off = rv_s0_off_slot(s) + addr->offset;
    831       break;
    832     }
    833     case NATIVE_ADDR_BASE_FRAME_VALUE: {
    834       rv_load_frame_component(a, RV_TMP0, addr->base.frame, addr->base_type,
    835                               0u);
    836       base = RV_TMP0;
    837       off = addr->offset;
    838       break;
    839     }
    840     case NATIVE_ADDR_BASE_GLOBAL:
    841       rv_emit_global_addr(a, RV_TMP0, addr->base.global.sym,
    842                           addr->base.global.addend);
    843       base = RV_TMP0;
    844       off = addr->offset;
    845       break;
    846     default:
    847       rv_panic(a, "unsupported address base");
    848   }
    849   if (addr->index_kind == NATIVE_ADDR_INDEX_REG) {
    850     base = rv_fold_index(a, base, addr->index.reg & 0x1fu, addr->log2_scale);
    851   } else if (addr->index_kind == NATIVE_ADDR_INDEX_FRAME_VALUE) {
    852     u32 idx = rv_internal_tmp(a, 1u << base);
    853     rv_load_frame_component(a, idx, addr->index.frame, addr->index_type,
    854                             1u << base);
    855     base = rv_fold_index(a, base, idx, addr->log2_scale);
    856   }
    857   if (!fits_i12(off)) {
    858     u32 tmp = rv_internal_tmp(a, 1u << base);
    859     rv_emit_load_imm(a->variant, mc, 1, tmp, (i64)off);
    860     rv64_emit32(mc, rv_add(RV_TMP0, base, tmp));
    861     base = RV_TMP0;
    862     off = 0;
    863   }
    864   *base_out = base;
    865   *off_out = off;
    866 }
    867 
    868 /* Choose a backend-private integer register for an exact-width ABI transfer.
    869  * TMP0/TMP1 are considered last because address resolution normally owns that
    870  * pair; callers that choose before resolution therefore exclude them by
    871  * passing them as explicit avoids. */
    872 static u32 rv_part_tmp(RvNativeTarget* a, u32 avoid_a, u32 avoid_b,
    873                        u32 avoid_c) {
    874   static const u8 temps[] = {RV_TMP2, RV_TMP3, RV_TMP1, RV_TMP0};
    875   u32 i;
    876   for (i = 0; i < (u32)(sizeof temps / sizeof temps[0]); ++i) {
    877     u32 r = temps[i];
    878     if (r != avoid_a && r != avoid_b && r != avoid_c) return r;
    879   }
    880   rv_panic(a, "no temporary for exact-width ABI part");
    881   return RV_TMP2;
    882 }
    883 
    884 static u32 rv_addr_reg(const NativeAddr* addr, int index) {
    885   if (!index && addr->base_kind == NATIVE_ADDR_BASE_REG)
    886     return addr->base.reg & 0x1fu;
    887   if (index && addr->index_kind == NATIVE_ADDR_INDEX_REG)
    888     return addr->index.reg & 0x1fu;
    889   return REG_NONE;
    890 }
    891 
    892 /* TMP2/TMP3 are not borrowed by rv_resolve_mem_addr, so a packed store may
    893  * retain its source in either one while TMP0/TMP1 materialize the address. */
    894 static u32 rv_part_preserved_tmp(RvNativeTarget* a, const NativeAddr* addr) {
    895   u32 base = rv_addr_reg(addr, 0);
    896   u32 index = rv_addr_reg(addr, 1);
    897   if (RV_TMP2 != base && RV_TMP2 != index) return RV_TMP2;
    898   if (RV_TMP3 != base && RV_TMP3 != index) return RV_TMP3;
    899   rv_panic(a, "exact-width ABI address consumes transfer temporaries");
    900   return RV_TMP2;
    901 }
    902 
    903 /* RISC-V has no 3/5/6/7-byte scalar load/store. ABI parts of those semantic
    904  * sizes still occupy one packed integer register, so move them with bounded
    905  * low-to-high 4/2/1 (or RV32 2/1) accesses and explicitly pack/unpack the
    906  * register bits. This keeps every memory access within the semantic object;
    907  * the separately rounded ABI carrier/stack-slot size is not an access width. */
    908 static void rv_emit_exact_int_part(RvNativeTarget* a, int is_load, u32 reg,
    909                                    NativeAddr addr, u32 size) {
    910   const RiscvVariant* v = a->variant;
    911   MCEmitter* mc = a->base.mc;
    912   NativePartChunkIter chunks = native_part_chunks(size, v->ptr_bytes);
    913   u32 base;
    914   i32 off;
    915   u32 chunk_off, chunk_size;
    916 
    917   if (!is_load) {
    918     /* Preserve the packed source before address resolution, which may borrow
    919      * TMP0/TMP1. Avoid any explicit address components as well: a plain
    920      * BASE_REG address must remain intact until it has been resolved. */
    921     u32 value = rv_part_preserved_tmp(a, &addr);
    922     u32 consumed = 0u;
    923     if (value != reg) rv64_emit32(mc, rv_addi(value, reg, 0));
    924     rv_resolve_mem_addr(a, &addr, &base, &off);
    925     if (!fits_i12((i64)off + (i64)size - 1)) {
    926       u32 stable = rv_part_tmp(a, value, base, REG_NONE);
    927       rv_emit_addr_adjust(v, mc, stable, base, off);
    928       base = stable;
    929       off = 0;
    930     }
    931     while (native_part_chunk_next(&chunks, &chunk_off, &chunk_size)) {
    932       if (chunk_off != consumed) {
    933         rv64_emit32(mc, rv_srli(value, value,
    934                                 (chunk_off - consumed) * 8u));
    935         consumed = chunk_off;
    936       }
    937       rv64_emit32(mc,
    938                   enc_int_store(v, chunk_size, value, base,
    939                                 off + (i32)chunk_off));
    940     }
    941     return;
    942   }
    943 
    944   rv_resolve_mem_addr(a, &addr, &base, &off);
    945   if (base == reg || !fits_i12((i64)off + (i64)size - 1)) {
    946     u32 stable = rv_part_tmp(a, reg, base, REG_NONE);
    947     rv_emit_addr_adjust(v, mc, stable, base, off);
    948     base = stable;
    949     off = 0;
    950   }
    951   {
    952     u32 piece = rv_part_tmp(a, reg, base, REG_NONE);
    953     int first = 1;
    954     while (native_part_chunk_next(&chunks, &chunk_off, &chunk_size)) {
    955       u32 dst = first ? reg : piece;
    956       rv64_emit32(mc, enc_int_load(v, chunk_size, 0, dst, base,
    957                                    off + (i32)chunk_off));
    958       if (!first) {
    959         rv64_emit32(mc, rv_slli(piece, piece, chunk_off * 8u));
    960         rv64_emit32(mc, rv_or(reg, reg, piece));
    961       }
    962       first = 0;
    963     }
    964   }
    965 }
    966 
    967 /* Central load/store primitive. is_load: 1 load into reg, 0 store reg to mem.
    968  */
    969 static void rv_emit_mem(RvNativeTarget* a, int is_load, NativeLoc reg,
    970                         NativeAddr addr, MemAccess mem) {
    971   NativeTarget* t = &a->base;
    972   MCEmitter* mc = t->mc;
    973   u32 r = loc_reg(reg);
    974   int fp = native_loc_is_fp(reg);
    975   u32 sz = mem.size ? mem.size : loc_size32(t, reg);
    976   u32 base;
    977   i32 off;
    978 
    979   if (!fp && sz != 0u && sz <= a->variant->ptr_bytes &&
    980       (sz & (sz - 1u)) != 0u) {
    981     rv_emit_exact_int_part(a, is_load, r, addr, sz);
    982     return;
    983   }
    984 
    985   rv_resolve_mem_addr(a, &addr, &base, &off);
    986   if (fp) {
    987     rv64_emit32(
    988         mc, is_load ? (sz == 8u ? rv_fld(r, base, off) : rv_flw(r, base, off))
    989                     : (sz == 8u ? rv_fsd(r, base, off) : rv_fsw(r, base, off)));
    990   } else {
    991     rv64_emit32(mc, is_load ? enc_int_load(a->variant, sz, 0, r, base, off)
    992                             : enc_int_store(a->variant, sz, r, base, off));
    993   }
    994 }
    995 
    996 /* ============================ moves / data ============================ */
    997 
    998 static void rv_move(NativeTarget* t, NativeLoc dst, NativeLoc src) {
    999   MCEmitter* mc = t->mc;
   1000   int dfp = native_loc_is_fp(dst), sfp = native_loc_is_fp(src);
   1001   u32 rd = loc_reg(dst), rs = loc_reg(src);
   1002   if (dfp && sfp) {
   1003     u32 fmt = loc_size32(t, dst) == 8u ? RV_FMT_D : RV_FMT_S;
   1004     if (rd == rs) return;
   1005     rv64_emit32(mc, rv_fsgnj(fmt, rd, rs, rs));
   1006     return;
   1007   }
   1008   if (!dfp && sfp) {
   1009     u32 sz = loc_size32(t, src);
   1010     rv64_emit32(mc, sz == 8u ? rv_fmv_x_d(rd, rs) : rv_fmv_x_w(rd, rs));
   1011     return;
   1012   }
   1013   if (dfp && !sfp) {
   1014     u32 sz = loc_size32(t, dst);
   1015     rv64_emit32(mc, sz == 8u ? rv_fmv_d_x(rd, rs) : rv_fmv_w_x(rd, rs));
   1016     return;
   1017   }
   1018   if (rd == rs) return;
   1019   rv64_emit32(mc, rv_addi(rd, rs, 0));
   1020 }
   1021 
   1022 static void rv_load_imm(NativeTarget* t, NativeLoc dst, i64 imm) {
   1023   rv_emit_load_imm(rv_of(t)->variant, t->mc, loc_is_64(t, dst) ? 1u : 0u,
   1024                    loc_reg(dst), imm);
   1025 }
   1026 
   1027 static void rv_load_const(NativeTarget* t, NativeLoc dst, ConstBytes cb) {
   1028   RvNativeTarget* a = rv_of(t);
   1029   u64 v = 0;
   1030   u32 i;
   1031   if (!native_loc_is_fp(dst)) {
   1032     for (i = 0; i < cb.size && i < 8u; ++i) v |= (u64)cb.bytes[i] << (i * 8u);
   1033     rv_load_imm(t, dst, (i64)v);
   1034     return;
   1035   }
   1036   /* FP constant: materialize the bit pattern in TMP0, bitcast into the FPR. */
   1037   for (i = 0; i < cb.size && i < 8u; ++i) v |= (u64)cb.bytes[i] << (i * 8u);
   1038   rv_emit_load_imm(a->variant, t->mc, 1, RV_TMP0, (i64)v);
   1039   if (cb.size == 8u)
   1040     rv64_emit32(t->mc, rv_fmv_d_x(loc_reg(dst), RV_TMP0));
   1041   else
   1042     rv64_emit32(t->mc, rv_fmv_w_x(loc_reg(dst), RV_TMP0));
   1043   (void)a;
   1044 }
   1045 
   1046 static void rv_load_addr(NativeTarget* t, NativeLoc dst, NativeAddr addr) {
   1047   RvNativeTarget* a = rv_of(t);
   1048   MCEmitter* mc = t->mc;
   1049   u32 rd = loc_reg(dst);
   1050   u32 base;
   1051   i32 off;
   1052   if (addr.base_kind == NATIVE_ADDR_BASE_GLOBAL) {
   1053     rv_emit_global_addr(a, rd, addr.base.global.sym,
   1054                         addr.base.global.addend + addr.offset);
   1055     base = rd;
   1056     off = 0;
   1057   } else if (addr.base_kind == NATIVE_ADDR_BASE_FRAME_VALUE) {
   1058     /* Load the pointer stored in the frame slot, then add the offset. */
   1059     rv_load_frame_component(a, rd, addr.base.frame, addr.base_type, 0u);
   1060     base = rd;
   1061     off = addr.offset;
   1062   } else if (addr.base_kind == NATIVE_ADDR_BASE_FRAME) {
   1063     RvNativeSlot* s = rv_slot_get(a, addr.base.frame);
   1064     base = RV_S0;
   1065     off = rv_s0_off_slot(s) + addr.offset;
   1066   } else if (addr.base_kind == NATIVE_ADDR_BASE_REG) {
   1067     base = addr.base.reg & 0x1fu;
   1068     off = addr.offset;
   1069   } else {
   1070     rv_panic(a, "unsupported address base in load_addr");
   1071   }
   1072   /* Fold any index via Zba sh{1,2,3}add (index << scale) + base. Resolve the
   1073    * byte offset before loading a frame index so the far-offset helper's
   1074    * scratch cannot overwrite that index. */
   1075   if (addr.index_kind == NATIVE_ADDR_INDEX_REG ||
   1076       addr.index_kind == NATIVE_ADDR_INDEX_FRAME_VALUE) {
   1077     u32 idx;
   1078     if (off != 0 || base != rd)
   1079       rv_emit_addr_adjust(a->variant, mc, rd, base, off);
   1080     if (addr.index_kind == NATIVE_ADDR_INDEX_REG) {
   1081       idx = addr.index.reg & 0x1fu;
   1082     } else {
   1083       idx = rv_internal_tmp(a, 1u << rd);
   1084       rv_load_frame_component(a, idx, addr.index.frame, addr.index_type,
   1085                               1u << rd);
   1086     }
   1087     switch (addr.log2_scale) {
   1088       case 0:
   1089         rv64_emit32(mc, rv_add(rd, rd, idx));
   1090         break;
   1091       case 1:
   1092         rv64_emit32(mc, rv_sh1add(rd, idx, rd));
   1093         break;
   1094       case 2:
   1095         rv64_emit32(mc, rv_sh2add(rd, idx, rd));
   1096         break;
   1097       default:
   1098         rv64_emit32(mc, rv_sh3add(rd, idx, rd));
   1099         break;
   1100     }
   1101     return;
   1102   }
   1103   rv_emit_addr_adjust(a->variant, mc, rd, base, off);
   1104 }
   1105 
   1106 static void rv_load(NativeTarget* t, NativeLoc dst, NativeAddr addr,
   1107                     MemAccess mem) {
   1108   rv_emit_mem(rv_of(t), 1, dst, addr, mem);
   1109 }
   1110 static void rv_store(NativeTarget* t, NativeAddr addr, NativeLoc src,
   1111                      MemAccess mem) {
   1112   rv_emit_mem(rv_of(t), 0, src, addr, mem);
   1113 }
   1114 
   1115 /* copy_bytes: resolve dst and src to dedicated pointer regs (RV_TMP3 / RV_TMP0)
   1116  * once, then copy granule-by-granule advancing both pointers. dst is resolved
   1117  * first because its base may itself live in RV_TMP1 (the transfer reg, e.g. the
   1118  * sret pointer from marshal_ret); capturing it into RV_TMP3 before src resolution
   1119  * (which may clobber RV_TMP1 for far offsets) keeps it live. Advancing the
   1120  * pointers keeps every load/store at offset 0, so no offset ever exceeds imm12
   1121  * and the transfer reg never aliases a base. */
   1122 static void rv_copy_bytes(NativeTarget* t, NativeAddr dst, NativeAddr src,
   1123                           AggregateAccess access) {
   1124   RvNativeTarget* a = rv_of(t);
   1125   const RiscvVariant* v = a->variant;
   1126   MCEmitter* mc = t->mc;
   1127   KitCgTypeId i64t = builtin_id(KIT_CG_BUILTIN_I64);
   1128   u32 rem = access.size;
   1129   u32 maxg = v->ptr_bytes; /* widest granule: 8 on rv64, 4 on rv32 */
   1130   rv_load_addr(t, native_loc_reg(i64t, NATIVE_REG_INT, RV_TMP3), dst);
   1131   rv_load_addr(t, native_loc_reg(i64t, NATIVE_REG_INT, RV_TMP0), src);
   1132   while (rem) {
   1133     u32 sz = rem >= 8u && maxg >= 8u ? 8u
   1134              : rem >= 4u             ? 4u
   1135              : rem >= 2u             ? 2u
   1136                                      : 1u;
   1137     rv64_emit32(mc, enc_int_load(v, sz, 0, RV_TMP1, RV_TMP0, 0));
   1138     rv64_emit32(mc, enc_int_store(v, sz, RV_TMP1, RV_TMP3, 0));
   1139     rv64_emit32(mc, rv_addi(RV_TMP0, RV_TMP0, (i32)sz));
   1140     rv64_emit32(mc, rv_addi(RV_TMP3, RV_TMP3, (i32)sz));
   1141     rem -= sz;
   1142   }
   1143 }
   1144 
   1145 static void rv_set_bytes(NativeTarget* t, NativeAddr dst, NativeLoc byte_value,
   1146                          AggregateAccess access) {
   1147   MCEmitter* mc = t->mc;
   1148   KitCgTypeId i64t = builtin_id(KIT_CG_BUILTIN_I64);
   1149   u32 bv = loc_reg(byte_value);
   1150   u32 rem = access.size;
   1151   rv_load_addr(t, native_loc_reg(i64t, NATIVE_REG_INT, RV_TMP3), dst);
   1152   while (rem) {
   1153     rv64_emit32(mc, rv_sb(bv, RV_TMP3, 0));
   1154     rv64_emit32(mc, rv_addi(RV_TMP3, RV_TMP3, 1));
   1155     rem -= 1u;
   1156   }
   1157 }
   1158 
   1159 /* ============================ arithmetic ============================ */
   1160 
   1161 static void rv_binop(NativeTarget* t, BinOp op, NativeLoc dst, NativeLoc aop,
   1162                      NativeLoc bop) {
   1163   const RiscvVariant* v = rv_of(t)->variant;
   1164   MCEmitter* mc = t->mc;
   1165   u32 rd = loc_reg(dst);
   1166   u32 ra = loc_reg(aop);
   1167   int sf = loc_is_64(t, dst);
   1168   /* The W-form ops (ADDW/SUBW/MULW/SLLW/...) are RV64-only and act on a 32-bit
   1169    * value held in a 64-bit register. They are emitted only for a narrow value
   1170    * on rv64; on rv32 the BASE ops ARE the 32-bit ops, so `w` is always 0 and we
   1171    * fall to the base ops. */
   1172   int w = !sf && v->has_w_forms;
   1173   /* Immediate shamt mask: 5-bit (&31) for a W-form / rv32 op, else shamt_bits
   1174    * (6-bit &63 on rv64) for the native-width op. */
   1175   u32 shmask = w ? 31u : ((1u << v->shamt_bits) - 1u);
   1176   int b_imm = bop.kind == NATIVE_LOC_IMM;
   1177   u32 rb = b_imm ? 0u : loc_reg(bop);
   1178   i64 imm = b_imm ? bop.v.imm : 0;
   1179 
   1180   switch (op) {
   1181     case BO_FADD:
   1182     case BO_FSUB:
   1183     case BO_FMUL:
   1184     case BO_FDIV: {
   1185       u32 fmt = loc_size32(t, dst) == 8u ? RV_FMT_D : RV_FMT_S;
   1186       switch (op) {
   1187         case BO_FADD:
   1188           rv64_emit32(mc, rv_fadd(fmt, rd, ra, rb));
   1189           break;
   1190         case BO_FSUB:
   1191           rv64_emit32(mc, rv_fsub(fmt, rd, ra, rb));
   1192           break;
   1193         case BO_FMUL:
   1194           rv64_emit32(mc, rv_fmul(fmt, rd, ra, rb));
   1195           break;
   1196         default:
   1197           rv64_emit32(mc, rv_fdiv(fmt, rd, ra, rb));
   1198           break;
   1199       }
   1200       return;
   1201     }
   1202     case BO_IADD:
   1203       if (b_imm) {
   1204         rv64_emit32(mc,
   1205                     w ? rv_addiw(rd, ra, (i32)imm) : rv_addi(rd, ra, (i32)imm));
   1206       } else {
   1207         rv64_emit32(mc, w ? rv_addw(rd, ra, rb) : rv_add(rd, ra, rb));
   1208       }
   1209       return;
   1210     case BO_ISUB:
   1211       if (b_imm) {
   1212         rv64_emit32(
   1213             mc, w ? rv_addiw(rd, ra, (i32)-imm) : rv_addi(rd, ra, (i32)-imm));
   1214       } else {
   1215         rv64_emit32(mc, w ? rv_subw(rd, ra, rb) : rv_sub(rd, ra, rb));
   1216       }
   1217       return;
   1218     case BO_IMUL:
   1219       rv64_emit32(mc, w ? rv_mulw(rd, ra, rb) : rv_mul(rd, ra, rb));
   1220       return;
   1221     case BO_SDIV:
   1222       rv64_emit32(mc, w ? rv_divw(rd, ra, rb) : rv_div(rd, ra, rb));
   1223       return;
   1224     case BO_UDIV:
   1225       rv64_emit32(mc, w ? rv_divuw(rd, ra, rb) : rv_divu(rd, ra, rb));
   1226       return;
   1227     case BO_SREM:
   1228       rv64_emit32(mc, w ? rv_remw(rd, ra, rb) : rv_rem(rd, ra, rb));
   1229       return;
   1230     case BO_UREM:
   1231       rv64_emit32(mc, w ? rv_remuw(rd, ra, rb) : rv_remu(rd, ra, rb));
   1232       return;
   1233     case BO_AND:
   1234       rv64_emit32(mc, b_imm ? rv_andi(rd, ra, (i32)imm) : rv_and(rd, ra, rb));
   1235       return;
   1236     case BO_OR:
   1237       rv64_emit32(mc, b_imm ? rv_ori(rd, ra, (i32)imm) : rv_or(rd, ra, rb));
   1238       return;
   1239     case BO_XOR:
   1240       rv64_emit32(mc, b_imm ? rv_xori(rd, ra, (i32)imm) : rv_xor(rd, ra, rb));
   1241       return;
   1242     case BO_SHL:
   1243       if (b_imm)
   1244         rv64_emit32(mc, w ? rv_slliw(rd, ra, (u32)imm & shmask)
   1245                           : rv_slli(rd, ra, (u32)imm & shmask));
   1246       else
   1247         rv64_emit32(mc, w ? rv_sllw(rd, ra, rb) : rv_sll(rd, ra, rb));
   1248       return;
   1249     case BO_SHR_U:
   1250       if (b_imm)
   1251         rv64_emit32(mc, w ? rv_srliw(rd, ra, (u32)imm & shmask)
   1252                           : rv_srli(rd, ra, (u32)imm & shmask));
   1253       else
   1254         rv64_emit32(mc, w ? rv_srlw(rd, ra, rb) : rv_srl(rd, ra, rb));
   1255       return;
   1256     case BO_SHR_S:
   1257       if (b_imm)
   1258         rv64_emit32(mc, w ? rv_sraiw(rd, ra, (u32)imm & shmask)
   1259                           : rv_srai(rd, ra, (u32)imm & shmask));
   1260       else
   1261         rv64_emit32(mc, w ? rv_sraw(rd, ra, rb) : rv_sra(rd, ra, rb));
   1262       return;
   1263     default:
   1264       rv_panic(rv_of(t), "unsupported binop");
   1265   }
   1266 }
   1267 
   1268 static void rv_unop(NativeTarget* t, UnOp op, NativeLoc dst, NativeLoc src) {
   1269   const RiscvVariant* v = rv_of(t)->variant;
   1270   MCEmitter* mc = t->mc;
   1271   u32 rd = loc_reg(dst), rs = loc_reg(src);
   1272   int sf = loc_is_64(t, dst);
   1273   int w = !sf && v->has_w_forms; /* SUBW is RV64-only; base SUB on rv32 */
   1274   switch (op) {
   1275     case UO_NEG:
   1276       rv64_emit32(mc, w ? rv_subw(rd, RV_ZERO, rs) : rv_sub(rd, RV_ZERO, rs));
   1277       return;
   1278     case UO_FNEG: {
   1279       u32 fmt = loc_size32(t, dst) == 8u ? RV_FMT_D : RV_FMT_S;
   1280       rv64_emit32(mc, rv_fsgnjn(fmt, rd, rs, rs));
   1281       return;
   1282     }
   1283     case UO_BNOT:
   1284       rv64_emit32(mc, rv_xori(rd, rs, -1));
   1285       return;
   1286     case UO_NOT:
   1287       rv64_emit32(mc, rv_sltiu(rd, rs, 1));
   1288       return;
   1289     default:
   1290       rv_panic(rv_of(t), "unsupported unop");
   1291   }
   1292 }
   1293 
   1294 /* Sign/zero-extend a 32-bit operand into a 64-bit register for comparison.
   1295  * Returns the register to compare. */
   1296 static u32 rv_cmp_ext(NativeTarget* t, int is_signed, NativeLoc op, u32 tmp) {
   1297   const RiscvVariant* v = rv_of(t)->variant;
   1298   MCEmitter* mc = t->mc;
   1299   u32 r = loc_reg(op);
   1300   /* On rv32 a 32-bit operand already fills the whole register — there is no
   1301    * wider container to canonicalize into, so the extension is a no-op. */
   1302   if (v->xlen == 32u) return r;
   1303   if (loc_is_64(t, op)) return r;
   1304   if (is_signed) {
   1305     rv64_emit32(mc, rv_addiw(tmp, r, 0)); /* sign-extend low 32 */
   1306   } else {
   1307     rv64_emit32(mc, rv_slli(tmp, r, 32));
   1308     rv64_emit32(mc, rv_srli(tmp, tmp, 32));
   1309   }
   1310   return tmp;
   1311 }
   1312 
   1313 static int cmp_is_signed(CmpOp op) {
   1314   switch (op) {
   1315     case CMP_LT_U:
   1316     case CMP_LE_U:
   1317     case CMP_GT_U:
   1318     case CMP_GE_U:
   1319       return 0;
   1320     default:
   1321       return 1;
   1322   }
   1323 }
   1324 
   1325 /* Emit a 0/1 comparison result into rd from two integer registers. */
   1326 static void rv_emit_icmp(NativeTarget* t, CmpOp op, u32 rd, u32 ra, u32 rb) {
   1327   MCEmitter* mc = t->mc;
   1328   switch (op) {
   1329     case CMP_EQ:
   1330       rv64_emit32(mc, rv_sub(rd, ra, rb));
   1331       rv64_emit32(mc, rv_sltiu(rd, rd, 1));
   1332       return;
   1333     case CMP_NE:
   1334       rv64_emit32(mc, rv_sub(rd, ra, rb));
   1335       rv64_emit32(mc, rv_sltu(rd, RV_ZERO, rd));
   1336       return;
   1337     case CMP_LT_S:
   1338       rv64_emit32(mc, rv_slt(rd, ra, rb));
   1339       return;
   1340     case CMP_LT_U:
   1341       rv64_emit32(mc, rv_sltu(rd, ra, rb));
   1342       return;
   1343     case CMP_GT_S:
   1344       rv64_emit32(mc, rv_slt(rd, rb, ra));
   1345       return;
   1346     case CMP_GT_U:
   1347       rv64_emit32(mc, rv_sltu(rd, rb, ra));
   1348       return;
   1349     case CMP_GE_S:
   1350       rv64_emit32(mc, rv_slt(rd, ra, rb));
   1351       rv64_emit32(mc, rv_xori(rd, rd, 1));
   1352       return;
   1353     case CMP_GE_U:
   1354       rv64_emit32(mc, rv_sltu(rd, ra, rb));
   1355       rv64_emit32(mc, rv_xori(rd, rd, 1));
   1356       return;
   1357     case CMP_LE_S:
   1358       rv64_emit32(mc, rv_slt(rd, rb, ra));
   1359       rv64_emit32(mc, rv_xori(rd, rd, 1));
   1360       return;
   1361     case CMP_LE_U:
   1362       rv64_emit32(mc, rv_sltu(rd, rb, ra));
   1363       rv64_emit32(mc, rv_xori(rd, rd, 1));
   1364       return;
   1365     default:
   1366       rv_panic(rv_of(t), "unsupported integer cmp");
   1367   }
   1368 }
   1369 
   1370 /* Format-dispatching wrappers over the ordered FP compares (feq/flt/fle are
   1371  * ordered: they yield 0 on NaN; flt/fle are signaling, raising NV on NaN —
   1372  * pre-existing for ordered ops, and the boolean result is still correct). */
   1373 static u32 rv_feq_fmt(u32 fmt, u32 rd, u32 ra, u32 rb) {
   1374   return fmt == RV_FMT_D ? rv_feq_d(rd, ra, rb) : rv_feq_s(rd, ra, rb);
   1375 }
   1376 static u32 rv_flt_fmt(u32 fmt, u32 rd, u32 ra, u32 rb) {
   1377   return fmt == RV_FMT_D ? rv_flt_d(rd, ra, rb) : rv_flt_s(rd, ra, rb);
   1378 }
   1379 static u32 rv_fle_fmt(u32 fmt, u32 rd, u32 ra, u32 rb) {
   1380   return fmt == RV_FMT_D ? rv_fle_d(rd, ra, rb) : rv_fle_s(rd, ra, rb);
   1381 }
   1382 
   1383 static void rv_cmp(NativeTarget* t, CmpOp op, NativeLoc dst, NativeLoc aop,
   1384                    NativeLoc bop) {
   1385   MCEmitter* mc = t->mc;
   1386   u32 rd = loc_reg(dst);
   1387   /* FP-ness is self-describing from the opcode (FP block starts at CMP_OEQ_F).
   1388    * Unordered predicates use unordered-R == NOT(ordered-not-R): the ordered
   1389    * compare into rd, then `xori rd,rd,1`. ONE/UEQ have no single ordered
   1390    * primitive and OR the two strict relations (a<b | a>b) via scratch RV_TMP2
   1391    * (x7, reserved & never allocable, so it can't alias rd). */
   1392   if (op >= CMP_OEQ_F) {
   1393     u32 fmt = loc_size32(t, aop) == 8u ? RV_FMT_D : RV_FMT_S;
   1394     u32 ra = loc_reg(aop), rb = loc_reg(bop);
   1395     switch (op) {
   1396       case CMP_OEQ_F:
   1397         rv64_emit32(mc, rv_feq_fmt(fmt, rd, ra, rb));
   1398         return;
   1399       case CMP_UNE_F: /* !(OEQ) */
   1400         rv64_emit32(mc, rv_feq_fmt(fmt, rd, ra, rb));
   1401         rv64_emit32(mc, rv_xori(rd, rd, 1));
   1402         return;
   1403       case CMP_OLT_F:
   1404         rv64_emit32(mc, rv_flt_fmt(fmt, rd, ra, rb));
   1405         return;
   1406       case CMP_OLE_F:
   1407         rv64_emit32(mc, rv_fle_fmt(fmt, rd, ra, rb));
   1408         return;
   1409       case CMP_OGT_F:
   1410         rv64_emit32(mc, rv_flt_fmt(fmt, rd, rb, ra));
   1411         return;
   1412       case CMP_OGE_F:
   1413         rv64_emit32(mc, rv_fle_fmt(fmt, rd, rb, ra));
   1414         return;
   1415       case CMP_UGE_F: /* !(OLT) */
   1416         rv64_emit32(mc, rv_flt_fmt(fmt, rd, ra, rb));
   1417         rv64_emit32(mc, rv_xori(rd, rd, 1));
   1418         return;
   1419       case CMP_UGT_F: /* !(OLE) */
   1420         rv64_emit32(mc, rv_fle_fmt(fmt, rd, ra, rb));
   1421         rv64_emit32(mc, rv_xori(rd, rd, 1));
   1422         return;
   1423       case CMP_ULE_F: /* !(OGT) */
   1424         rv64_emit32(mc, rv_flt_fmt(fmt, rd, rb, ra));
   1425         rv64_emit32(mc, rv_xori(rd, rd, 1));
   1426         return;
   1427       case CMP_ULT_F: /* !(OGE) */
   1428         rv64_emit32(mc, rv_fle_fmt(fmt, rd, rb, ra));
   1429         rv64_emit32(mc, rv_xori(rd, rd, 1));
   1430         return;
   1431       case CMP_ONE_F: /* ordered & !=: (a<b) | (a>b) */
   1432         rv64_emit32(mc, rv_flt_fmt(fmt, rd, ra, rb));
   1433         rv64_emit32(mc, rv_flt_fmt(fmt, RV_TMP2, rb, ra));
   1434         rv64_emit32(mc, rv_or(rd, rd, RV_TMP2));
   1435         return;
   1436       case CMP_UEQ_F: /* unordered | ==: !((a<b) | (a>b)) */
   1437         rv64_emit32(mc, rv_flt_fmt(fmt, rd, ra, rb));
   1438         rv64_emit32(mc, rv_flt_fmt(fmt, RV_TMP2, rb, ra));
   1439         rv64_emit32(mc, rv_or(rd, rd, RV_TMP2));
   1440         rv64_emit32(mc, rv_xori(rd, rd, 1));
   1441         return;
   1442       default:
   1443         rv_panic(rv_of(t), "unsupported fp cmp");
   1444     }
   1445   }
   1446   {
   1447     int sg = cmp_is_signed(op);
   1448     u32 ra = rv_cmp_ext(t, sg, aop, RV_TMP0);
   1449     u32 rb = rv_cmp_ext(t, sg, bop, RV_TMP1);
   1450     rv_emit_icmp(t, op, rd, ra, rb);
   1451   }
   1452 }
   1453 
   1454 static void rv_convert(NativeTarget* t, ConvKind op, NativeLoc dst,
   1455                        NativeLoc src) {
   1456   const RiscvVariant* v = rv_of(t)->variant;
   1457   MCEmitter* mc = t->mc;
   1458   u32 rd = loc_reg(dst), rs = loc_reg(src);
   1459   u32 src_sz = loc_size32(t, src);
   1460   u32 dst_sz = loc_size32(t, dst);
   1461   /* `il` (int-side wide): the 64-bit-integer fcvt L-forms are RV64-only; on
   1462    * rv32 only the w/wu forms exist and a 64-bit int<->fp is legalized to a
   1463    * libcall before reaching here. */
   1464   int il = v->has_w_forms;
   1465   switch (op) {
   1466     case CV_SEXT:
   1467       if (src_sz >= 4u) {
   1468         /* ADDIW sign-extends bits[31:0] into a 64-bit reg (RV64). On rv32 a
   1469          * 4-byte value already spans the whole register, so a plain move (or
   1470          * nothing when rd==rs) is the sign extension. */
   1471         if (v->has_w_forms)
   1472           rv64_emit32(mc, rv_addiw(rd, rs, 0));
   1473         else if (rd != rs)
   1474           rv64_emit32(mc, rv_addi(rd, rs, 0));
   1475       } else {
   1476         u32 sh = v->xlen - src_sz * 8u;
   1477         rv64_emit32(mc, rv_slli(rd, rs, sh));
   1478         rv64_emit32(mc, rv_srai(rd, rd, sh));
   1479       }
   1480       return;
   1481     case CV_ZEXT: {
   1482       u32 sh = v->xlen - src_sz * 8u;
   1483       rv64_emit32(mc, rv_slli(rd, rs, sh));
   1484       rv64_emit32(mc, rv_srli(rd, rd, sh));
   1485       return;
   1486     }
   1487     case CV_TRUNC:
   1488       if (rd != rs || dst_sz <= 4u)
   1489         rv64_emit32(mc, rv_addi(rd, rs, 0)); /* low bits; users re-narrow */
   1490       return;
   1491     case CV_ITOF_S:
   1492       if (dst_sz == 8u)
   1493         rv64_emit32(
   1494             mc, il && src_sz == 8u ? rv_fcvt_d_l(rd, rs) : rv_fcvt_d_w(rd, rs));
   1495       else
   1496         rv64_emit32(
   1497             mc, il && src_sz == 8u ? rv_fcvt_s_l(rd, rs) : rv_fcvt_s_w(rd, rs));
   1498       return;
   1499     case CV_ITOF_U:
   1500       if (dst_sz == 8u)
   1501         rv64_emit32(mc, il && src_sz == 8u ? rv_fcvt_d_lu(rd, rs)
   1502                                            : rv_fcvt_d_wu(rd, rs));
   1503       else
   1504         rv64_emit32(mc, il && src_sz == 8u ? rv_fcvt_s_lu(rd, rs)
   1505                                            : rv_fcvt_s_wu(rd, rs));
   1506       return;
   1507     case CV_FTOI_S:
   1508       if (src_sz == 8u)
   1509         rv64_emit32(
   1510             mc, il && dst_sz == 8u ? rv_fcvt_l_d(rd, rs) : rv_fcvt_w_d(rd, rs));
   1511       else
   1512         rv64_emit32(
   1513             mc, il && dst_sz == 8u ? rv_fcvt_l_s(rd, rs) : rv_fcvt_w_s(rd, rs));
   1514       return;
   1515     case CV_FTOI_U:
   1516       if (src_sz == 8u)
   1517         rv64_emit32(mc, il && dst_sz == 8u ? rv_fcvt_lu_d(rd, rs)
   1518                                            : rv_fcvt_wu_d(rd, rs));
   1519       else
   1520         rv64_emit32(mc, il && dst_sz == 8u ? rv_fcvt_lu_s(rd, rs)
   1521                                            : rv_fcvt_wu_s(rd, rs));
   1522       return;
   1523     case CV_FEXT:
   1524       rv64_emit32(mc, rv_fcvt_d_s(rd, rs));
   1525       return;
   1526     case CV_FTRUNC:
   1527       rv64_emit32(mc, rv_fcvt_s_d(rd, rs));
   1528       return;
   1529     case CV_BITCAST:
   1530       rv_move(t, dst, src);
   1531       return;
   1532     default:
   1533       rv_panic(rv_of(t), "unsupported convert");
   1534   }
   1535 }
   1536 
   1537 /* §E.3 narrow register-only entry points. The NDT crosses 16 B NativeRegLoc
   1538  * here; these reconstruct the NativeLoc the fat hook expects and delegate, so
   1539  * the emitted bytes are identical. Used only on the -O0 NDT path; the opt
   1540  * replay path calls rv_binop/rv_move/rv_cmp/rv_convert directly. */
   1541 static void rv_binop_rr(NativeTarget* t, BinOp op, NativeRegLoc dst,
   1542                         NativeRegLoc a, NativeRegLoc b) {
   1543   rv_binop(t, op, native_loc_from_reg(dst), native_loc_from_reg(a),
   1544            native_loc_from_reg(b));
   1545 }
   1546 
   1547 static void rv_move_rr(NativeTarget* t, NativeRegLoc dst, NativeRegLoc src) {
   1548   rv_move(t, native_loc_from_reg(dst), native_loc_from_reg(src));
   1549 }
   1550 
   1551 static void rv_cmp_rr(NativeTarget* t, CmpOp op, NativeRegLoc dst,
   1552                       NativeRegLoc a, NativeRegLoc b) {
   1553   rv_cmp(t, op, native_loc_from_reg(dst), native_loc_from_reg(a),
   1554          native_loc_from_reg(b));
   1555 }
   1556 
   1557 static void rv_convert_rr(NativeTarget* t, ConvKind op, NativeRegLoc dst,
   1558                           NativeRegLoc src) {
   1559   rv_convert(t, op, native_loc_from_reg(dst), native_loc_from_reg(src));
   1560 }
   1561 
   1562 /* ============================ spill / reload ============================ */
   1563 
   1564 static void rv_spill(NativeTarget* t, NativeLoc src, NativeFrameSlot slot,
   1565                      MemAccess mem) {
   1566   NativeAddr addr;
   1567   memset(&addr, 0, sizeof addr);
   1568   addr.base_kind = NATIVE_ADDR_BASE_FRAME;
   1569   addr.base.frame = slot;
   1570   addr.base_type = src.type;
   1571   rv_emit_mem(rv_of(t), 0, src, addr, mem);
   1572 }
   1573 static void rv_reload(NativeTarget* t, NativeLoc dst, NativeFrameSlot slot,
   1574                       MemAccess mem) {
   1575   NativeAddr addr;
   1576   memset(&addr, 0, sizeof addr);
   1577   addr.base_kind = NATIVE_ADDR_BASE_FRAME;
   1578   addr.base.frame = slot;
   1579   addr.base_type = dst.type;
   1580   rv_emit_mem(rv_of(t), 1, dst, addr, mem);
   1581 }
   1582 
   1583 /* ============================ control flow ============================ */
   1584 
   1585 static MCLabel rv_label_new(NativeTarget* t) { return mc_label_new(t->mc); }
   1586 static void rv_label_place(NativeTarget* t, MCLabel l) {
   1587   mc_label_place(t->mc, l);
   1588 }
   1589 static void rv_jump(NativeTarget* t, MCLabel l) {
   1590   rv64_emit32(t->mc, rv_jal(RV_ZERO, 0));
   1591   mc_emit_label_ref(t->mc, l, R_RV_JAL, 4, 0);
   1592 }
   1593 
   1594 static void rv_cmp_branch(NativeTarget* t, CmpOp op, NativeLoc aop,
   1595                           NativeLoc bop, MCLabel l) {
   1596   MCEmitter* mc = t->mc;
   1597   /* RISC-V B-type branches reach only ±4 KiB, which a single (especially
   1598    * -O0) function can exceed between a branch and its target. Rather than a
   1599    * lone conditional branch to the label, emit a short *inverted* branch
   1600    * that skips an unconditional `jal` (±1 MiB) to the target. The inverted
   1601    * branch's displacement is the constant SKIP_JAL (skip just the jal) and
   1602    * so is always in range; the jal carries the long reach. See rv_jump. */
   1603   enum { SKIP_JAL = 8 }; /* branch over the 4-byte jal that follows it */
   1604   /* FP compares have no register-register branch form: materialize the 0/1
   1605    * into TMP0 via rv_cmp (handles all 12 predicates), then branch on nonzero.
   1606    */
   1607   if (op >= CMP_OEQ_F) {
   1608     NativeLoc tmp =
   1609         native_loc_reg(builtin_id(KIT_CG_BUILTIN_I64), NATIVE_REG_INT, RV_TMP0);
   1610     rv_cmp(t, op, tmp, aop, bop);
   1611     /* Skip the jal when the result is 0 (condition false). */
   1612     rv64_emit32(mc, rv_beq(RV_TMP0, RV_ZERO, SKIP_JAL));
   1613     rv_jump(t, l);
   1614     return;
   1615   }
   1616   {
   1617     int sg = cmp_is_signed(op);
   1618     u32 ra = rv_cmp_ext(t, sg, aop, RV_TMP0);
   1619     u32 rb = rv_cmp_ext(t, sg, bop, RV_TMP1);
   1620     u32 word;
   1621     /* Encode the *inverse* of `op`, skipping the jal when NOT taken. */
   1622     switch (op) {
   1623       case CMP_EQ:
   1624         word = rv_bne(ra, rb, SKIP_JAL);
   1625         break;
   1626       case CMP_NE:
   1627         word = rv_beq(ra, rb, SKIP_JAL);
   1628         break;
   1629       case CMP_LT_S:
   1630         word = rv_bge(ra, rb, SKIP_JAL);
   1631         break;
   1632       case CMP_GE_S:
   1633         word = rv_blt(ra, rb, SKIP_JAL);
   1634         break;
   1635       case CMP_LT_U:
   1636         word = rv_bgeu(ra, rb, SKIP_JAL);
   1637         break;
   1638       case CMP_GE_U:
   1639         word = rv_bltu(ra, rb, SKIP_JAL);
   1640         break;
   1641       case CMP_GT_S:
   1642         word = rv_bge(rb, ra, SKIP_JAL);
   1643         break;
   1644       case CMP_LE_S:
   1645         word = rv_blt(rb, ra, SKIP_JAL);
   1646         break;
   1647       case CMP_GT_U:
   1648         word = rv_bgeu(rb, ra, SKIP_JAL);
   1649         break;
   1650       case CMP_LE_U:
   1651         word = rv_bltu(rb, ra, SKIP_JAL);
   1652         break;
   1653       default:
   1654         rv_panic(rv_of(t), "unsupported cmp_branch");
   1655     }
   1656     rv64_emit32(mc, word);
   1657     rv_jump(t, l);
   1658   }
   1659 }
   1660 
   1661 static void rv_indirect_branch(NativeTarget* t, NativeLoc addr,
   1662                                const MCLabel* valid_targets, u32 ntargets) {
   1663   (void)valid_targets;
   1664   (void)ntargets;
   1665   rv64_emit32(t->mc, rv_jalr(RV_ZERO, loc_reg(addr), 0));
   1666 }
   1667 
   1668 static void rv_load_label_addr(NativeTarget* t, NativeLoc dst, MCLabel l) {
   1669   /* `&&label` address-take: auipc/addi with a %pcrel_hi/%pcrel_lo relocation
   1670    * pair against the label's per-block local symbol — the same form
   1671    * rv_emit_global_addr uses for a global — so a compressing/re-encoding
   1672    * assembler recomputes the displacement (a baked offset would break under
   1673    * the C extension). */
   1674   u32 rd = loc_reg(dst);
   1675   ObjSymId sym = mc_label_symbol(t->mc, l);
   1676   rv_emit_pcrel_anchor(t, rd, sym, R_RV_PCREL_HI20, rv_addi(rd, rd, 0));
   1677 }
   1678 
   1679 /* ============================ frame / lifecycle ============================
   1680  */
   1681 
   1682 static NativeFrameSlot rv_frame_slot(NativeTarget* t,
   1683                                      const NativeFrameSlotDesc* d) {
   1684   return native_frame_slot_alloc(&rv_of(t)->frame, d);
   1685 }
   1686 
   1687 static void rv_release_frame_slot(NativeTarget* t, NativeFrameSlot slot) {
   1688   native_frame_release_slot(&rv_of(t)->frame, slot);
   1689 }
   1690 
   1691 static int rv_frame_slot_debug_loc(NativeTarget* t, NativeFrameSlot slot,
   1692                                    CGDebugLoc* out) {
   1693   RvNativeTarget* a = rv_of(t);
   1694   RvNativeSlot* s;
   1695   if (!out) return 0;
   1696   memset(out, 0, sizeof *out);
   1697   if (slot == NATIVE_FRAME_SLOT_NONE || slot > a->frame.nslots) return 0;
   1698   s = rv_slot_get(a, slot);
   1699   out->kind = CG_DEBUG_LOC_FRAME;
   1700   /* rv64 slots are addressed s0/fp-relative (rv_s0_off_slot); the hosted dbg
   1701    * snapshot seeds the frame base with s0, matching aa64's FP-relative
   1702    * convention. */
   1703   out->v.frame_ofs = rv_s0_off_slot(s);
   1704   return 1;
   1705 }
   1706 
   1707 static void rv_func_begin_common(NativeTarget* t, const CGFuncDesc* fd) {
   1708   RvNativeTarget* a = rv_of(t);
   1709   MCEmitter* mc = t->mc;
   1710   const ABIFuncInfo* abi = abi_cg_func_info(t->c->abi, fd->fn_type);
   1711   a->func = fd;
   1712   a->loc = fd->loc;
   1713   /* Shared frame bookkeeping: clears the slot table, cum_off, max_outgoing,
   1714    * callee-save set, and known_frame/has_alloca/frame_final. */
   1715   native_frame_reset(&a->frame);
   1716   a->incoming_stack_size = 0;
   1717   a->next_param_int = 0;
   1718   a->next_param_fp = 0;
   1719   a->next_param_stack = 0;
   1720   a->has_sret = (abi && abi->has_sret) ? 1u : 0u;
   1721   a->is_variadic = (abi && abi->variadic) ? 1u : 0u;
   1722   a->sret_ptr_slot = NATIVE_FRAME_SLOT_NONE;
   1723   a->npatches = 0;
   1724   a->nalloca = 0;
   1725   a->minimal_prologue_words = 0;
   1726   a->slim_prologue = 0;
   1727 
   1728   mc_set_section(mc, fd->text_section_id);
   1729   mc_emit_align(mc, 4, 0);
   1730   a->func_start = mc_pos(mc);
   1731   mc_begin_function(mc, fd->sym, fd->text_section_id, a->func_start);
   1732   mc_cfi_startproc(mc);
   1733   a->epilogue_label = mc_label_new(mc);
   1734 }
   1735 
   1736 /* sret: reserve a hidden slot for the incoming destination pointer (a0). */
   1737 static void rv_reserve_entry_saves(RvNativeTarget* a) {
   1738   NativeTarget* t = &a->base;
   1739   if (a->has_sret) {
   1740     NativeFrameSlotDesc sd;
   1741     u32 ptr = a->variant->ptr_bytes;
   1742     memset(&sd, 0, sizeof sd);
   1743     sd.type = builtin_id(KIT_CG_BUILTIN_I64);
   1744     sd.size = ptr; /* a pointer slot: 8 on rv64, 4 on rv32 */
   1745     sd.align = ptr;
   1746     sd.kind = NATIVE_FRAME_SLOT_SAVE;
   1747     a->sret_ptr_slot = t->frame_slot(t, &sd);
   1748     a->next_param_int = 1; /* a0 consumed by the sret pointer */
   1749   }
   1750 }
   1751 
   1752 /* Emit the s0-relative (frame-independent) entry stores live: the sret a0 spill
   1753  * and, for variadics, the unconsumed-GP register save area. Both are anchored at
   1754  * s0 (set by the fixed entry in rv_func_begin) and sit at non-negative offsets in
   1755  * the saved-pair + variadic region the fixed entry already pre-decremented sp
   1756  * for, so they run correctly after the deferred `sub sp`. Single-pass path only;
   1757  * the known-frame path emits these inline in rv_build_prologue. */
   1758 static void rv_emit_entry_save_stores(RvNativeTarget* a) {
   1759   NativeTarget* t = &a->base;
   1760   if (a->has_sret && a->sret_ptr_slot != NATIVE_FRAME_SLOT_NONE) {
   1761     KitCgTypeId i64t = builtin_id(KIT_CG_BUILTIN_I64);
   1762     u32 ptr = a->variant->ptr_bytes;
   1763     NativeAddr addr;
   1764     memset(&addr, 0, sizeof addr);
   1765     addr.base_kind = NATIVE_ADDR_BASE_FRAME;
   1766     addr.base.frame = a->sret_ptr_slot;
   1767     addr.base_type = i64t;
   1768     rv_emit_mem(a, 0, native_loc_reg(i64t, NATIVE_REG_INT, RV_A0), addr,
   1769                 native_mem_for_type(t, i64t, ptr));
   1770   }
   1771   /* variadic GP save area: home a0..a7 at [s0 + frame_save_size + i*gp_slot_bytes]
   1772    * (positive, always within imm12). Emitted live in rv_func_begin, before the
   1773    * params are bound, so next_param_int is not yet final — home ALL eight GP
   1774    * registers (the named leading ones are homed too; va_start skips past them via
   1775    * its next_param_int cursor, so the extra named-slot stores are dead-but-
   1776    * harmless, mirroring aa64). The save-area offsets are byte-identical to the
   1777    * historical rv_build_prologue layout. */
   1778   if (a->is_variadic) {
   1779     const RiscvVariant* v = a->variant;
   1780     u32 fsz = v->frame_save_size;
   1781     u32 gp_slot = v->gp_slot_bytes;
   1782     u32 i;
   1783     for (i = 0; i < 8u; ++i)
   1784       rv64_emit32(t->mc, rv_sd_ptr(v, RV_A0 + i, RV_S0,
   1785                                    (i32)fsz + (i32)i * (i32)gp_slot));
   1786   }
   1787 }
   1788 
   1789 /* Collect the callee-saves the body used (none at -O0). */
   1790 static u32 rv_collect_int_saves(RvNativeTarget* a, u32* regs) {
   1791   u32 n = 0, i;
   1792   for (i = 0; i < a->frame.ncallee_saves; ++i)
   1793     if (a->frame.callee_saves[i].cls == NATIVE_REG_INT)
   1794       regs[n++] = a->frame.callee_saves[i].reg;
   1795   return n;
   1796 }
   1797 static u32 rv_collect_fp_saves(RvNativeTarget* a, u32* regs) {
   1798   u32 n = 0, i;
   1799   for (i = 0; i < a->frame.ncallee_saves; ++i)
   1800     if (a->frame.callee_saves[i].cls == NATIVE_REG_FP)
   1801       regs[n++] = a->frame.callee_saves[i].reg;
   1802   return n;
   1803 }
   1804 
   1805 /* s0-relative offset of a saved register, below the locals. The flat index runs
   1806  * 0..n_int-1 over integer saves (each ptr_bytes wide) then n_int..n_int+n_fp-1
   1807  * over fp saves (each 8 bytes wide, fsd). On rv64 ptr_bytes==8 so this reduces
   1808  * to the historical uniform -cum_off-8-8*idx layout, byte-for-byte. */
   1809 static i32 rv_save_off(RvNativeTarget* a, u32 n_int, u32 idx) {
   1810   i32 base = -(i32)(a->frame.cum_off);
   1811   u32 ptr = a->variant->ptr_bytes;
   1812   if (idx < n_int) return base - (i32)ptr * (i32)(idx + 1u);
   1813   return base - (i32)(ptr * n_int) - 8 * (i32)(idx - n_int + 1u);
   1814 }
   1815 
   1816 static void rv_load_s0(const RiscvVariant* v, MCEmitter* mc, int fp, u32 reg,
   1817                        i32 off) {
   1818   if (fits_i12(off)) {
   1819     rv64_emit32(mc,
   1820                 fp ? rv_fld(reg, RV_S0, off) : rv_ld_ptr(v, reg, RV_S0, off));
   1821     return;
   1822   }
   1823   rv_emit_load_imm(v, mc, 1, RV_TMP0, (i64)off);
   1824   rv64_emit32(mc, rv_add(RV_TMP0, RV_S0, RV_TMP0));
   1825   rv64_emit32(mc, fp ? rv_fld(reg, RV_TMP0, 0) : rv_ld_ptr(v, reg, RV_TMP0, 0));
   1826 }
   1827 
   1828 /* Build the prologue instruction sequence into words[]. Returns count. */
   1829 static u32 rv_build_prologue(RvNativeTarget* a, u32* words, u32 cap,
   1830                              u32 frame_size, u32 fp_pair_off,
   1831                              const u32* int_regs, u32 n_int, const u32* fp_regs,
   1832                              u32 n_fp) {
   1833   const RiscvVariant* v = a->variant;
   1834   u32 ptr = v->ptr_bytes;         /* saved-pair / int-save stride */
   1835   u32 gp_slot = v->gp_slot_bytes; /* vararg GP-slot stride */
   1836   u32 fsz = v->frame_save_size;   /* saved ra+s0 pair base offset */
   1837   u32 wi = 0;
   1838   /* lui+ADD{I,IW} materializes a 32-bit constant in TMP0; ADDIW is RV64-only so
   1839    * use plain ADDI on rv32 (the value already fits 32 bits). */
   1840 #define ADDI_LO(rd, lo) \
   1841   (v->has_w_forms ? rv_addiw((rd), (rd), (lo)) : rv_addi((rd), (rd), (lo)))
   1842 #define PUSH(w)                                                  \
   1843   do {                                                           \
   1844     if (wi >= cap) rv_panic(a, "prologue placeholder overflow"); \
   1845     words[wi++] = (w);                                           \
   1846   } while (0)
   1847   /* sp -= frame_size */
   1848   if (fits_i12(-(i32)frame_size)) {
   1849     PUSH(rv_addi(RV_SP, RV_SP, -(i32)frame_size));
   1850   } else {
   1851     i32 neg = -(i32)frame_size;
   1852     i32 hi = (i32)(((i64)neg + 0x800) >> 12);
   1853     i32 lo = neg - (i32)((u32)hi << 12);
   1854     PUSH(rv_lui(RV_TMP0, (u32)hi & 0xfffffu));
   1855     if (lo) PUSH(ADDI_LO(RV_TMP0, lo));
   1856     PUSH(rv_add(RV_SP, RV_SP, RV_TMP0));
   1857   }
   1858   /* save s0/ra at [sp + fp_pair_off], set s0 = sp + fp_pair_off. The saved-pair
   1859    * internal stride is ptr_bytes (s0 at +0, ra at +ptr). */
   1860   if (fits_i12((i32)fp_pair_off + (i32)ptr)) {
   1861     PUSH(rv_sd_ptr(v, RV_S0, RV_SP, (i32)fp_pair_off));
   1862     PUSH(rv_sd_ptr(v, RV_RA, RV_SP, (i32)fp_pair_off + (i32)ptr));
   1863     PUSH(rv_addi(RV_S0, RV_SP, (i32)fp_pair_off));
   1864   } else {
   1865     i32 off = (i32)fp_pair_off;
   1866     i32 hi = (i32)(((i64)off + 0x800) >> 12);
   1867     i32 lo = off - (i32)((u32)hi << 12);
   1868     PUSH(rv_lui(RV_TMP0, (u32)hi & 0xfffffu));
   1869     if (lo) PUSH(ADDI_LO(RV_TMP0, lo));
   1870     PUSH(rv_add(RV_TMP0, RV_SP, RV_TMP0));
   1871     PUSH(rv_sd_ptr(v, RV_S0, RV_TMP0, 0));
   1872     PUSH(rv_sd_ptr(v, RV_RA, RV_TMP0, (i32)ptr));
   1873     PUSH(rv_addi(RV_S0, RV_TMP0, 0));
   1874   }
   1875   /* sret a0 spill */
   1876   if (a->has_sret && a->sret_ptr_slot != NATIVE_FRAME_SLOT_NONE) {
   1877     RvNativeSlot* s = rv_slot_get(a, a->sret_ptr_slot);
   1878     PUSH(rv_sd_ptr(v, RV_A0, RV_S0, rv_s0_off_slot(s)));
   1879   }
   1880   /* variadic GP save area: spill unconsumed a-regs at
   1881    * [s0 + frame_save_size + i*gp_slot_bytes] */
   1882   if (a->is_variadic) {
   1883     u32 i;
   1884     for (i = a->next_param_int; i < 8u; ++i)
   1885       PUSH(rv_sd_ptr(v, RV_A0 + i, RV_S0, (i32)fsz + (i32)i * (i32)gp_slot));
   1886   }
   1887   /* callee saves: integer with the pointer-width store (sw/sd), fp with fsd. */
   1888   {
   1889     u32 i;
   1890     for (i = 0; i < n_int; ++i) {
   1891       i32 off = rv_save_off(a, n_int, i);
   1892       if (fits_i12(off)) {
   1893         PUSH(rv_sd_ptr(v, int_regs[i], RV_S0, off));
   1894       } else {
   1895         /* rare; emitted directly is fine in the known-frame path, but the
   1896          * single-pass placeholder must hold these too. Use the wide form. */
   1897         i32 hi = (i32)(((i64)off + 0x800) >> 12);
   1898         i32 lo = off - (i32)((u32)hi << 12);
   1899         PUSH(rv_lui(RV_TMP0, (u32)hi & 0xfffffu));
   1900         if (lo) PUSH(ADDI_LO(RV_TMP0, lo));
   1901         PUSH(rv_add(RV_TMP0, RV_S0, RV_TMP0));
   1902         PUSH(rv_sd_ptr(v, int_regs[i], RV_TMP0, 0));
   1903       }
   1904     }
   1905     for (i = 0; i < n_fp; ++i) {
   1906       i32 off = rv_save_off(a, n_int, n_int + i);
   1907       if (fits_i12(off)) {
   1908         PUSH(rv_fsd(fp_regs[i], RV_S0, off));
   1909       } else {
   1910         i32 hi = (i32)(((i64)off + 0x800) >> 12);
   1911         i32 lo = off - (i32)((u32)hi << 12);
   1912         PUSH(rv_lui(RV_TMP0, (u32)hi & 0xfffffu));
   1913         if (lo) PUSH(ADDI_LO(RV_TMP0, lo));
   1914         PUSH(rv_add(RV_TMP0, RV_S0, RV_TMP0));
   1915         PUSH(rv_fsd(fp_regs[i], RV_TMP0, 0));
   1916       }
   1917     }
   1918   }
   1919 #undef PUSH
   1920 #undef ADDI_LO
   1921   return wi;
   1922 }
   1923 
   1924 /* Build the deferred portion of the single-pass (-O0) prologue: the `sub sp`
   1925  * that grows the frame below the saved s0/ra pair the live entry (rv_func_begin)
   1926  * already saved and anchored s0 at. The fixed entry pre-decremented sp by the
   1927  * saved-pair + variadic-GP-save area (frame_save_size + va_save_sz), so only
   1928  * `frame_size - (frame_save_size + va_save_sz)` remains to subtract. RISC-V has
   1929  * no stack probe, so this is the whole deferred region. Returns the word count
   1930  * (0 when nothing remains to subtract; <= 3 for the far form). */
   1931 static u32 rv_build_ndt_sub(RvNativeTarget* a, u32 frame_size, u32* words,
   1932                             u32 cap) {
   1933   const RiscvVariant* v = a->variant;
   1934   u32 pair_area = v->frame_save_size + rv_va_save_sz(a);
   1935   u32 sub_bytes = frame_size - pair_area;
   1936   u32 wi = 0;
   1937 #define ADDI_LO(rd, lo) \
   1938   (v->has_w_forms ? rv_addiw((rd), (rd), (lo)) : rv_addi((rd), (rd), (lo)))
   1939 #define PUSH(w)                                                  \
   1940   do {                                                           \
   1941     if (wi >= cap) rv_panic(a, "ndt sub placeholder overflow"); \
   1942     words[wi++] = (w);                                           \
   1943   } while (0)
   1944   if (!sub_bytes) return 0;
   1945   if (fits_i12(-(i32)sub_bytes)) {
   1946     PUSH(rv_addi(RV_SP, RV_SP, -(i32)sub_bytes));
   1947   } else {
   1948     i32 neg = -(i32)sub_bytes;
   1949     i32 hi = (i32)(((i64)neg + 0x800) >> 12);
   1950     i32 lo = neg - (i32)((u32)hi << 12);
   1951     PUSH(rv_lui(RV_TMP0, (u32)hi & 0xfffffu));
   1952     if (lo) PUSH(ADDI_LO(RV_TMP0, lo));
   1953     PUSH(rv_add(RV_SP, RV_SP, RV_TMP0));
   1954   }
   1955 #undef PUSH
   1956 #undef ADDI_LO
   1957   return wi;
   1958 }
   1959 
   1960 static void rv_func_begin(NativeTarget* t, const CGFuncDesc* fd) {
   1961   RvNativeTarget* a = rv_of(t);
   1962   MCEmitter* mc = t->mc;
   1963   const RiscvVariant* v;
   1964   u32 pair_area, region, i;
   1965   rv_func_begin_common(t, fd);
   1966   v = a->variant;
   1967   /* tcc-style prologue. Emit the frame-independent entry live: pre-decrement sp
   1968    * by just the saved-pair + variadic-GP-save area, store the s0/ra pair at the
   1969    * top of it, and anchor s0 there. The pair and s0 land at exactly the
   1970    * addresses every s0-relative offset already assumes (s0 == old_sp -
   1971    * frame_save_size - va_save_sz, identical to the old `sub frame_size` then
   1972    * `addi s0,sp,fp_pair_off`), so the frame is byte-identical — only the
   1973    * instruction sequence changes. pair_area is small (<=80 rv64 / <=40 rv32) and
   1974    * always fits imm12. */
   1975   pair_area = v->frame_save_size + rv_va_save_sz(a);
   1976   rv64_emit32(mc, rv_addi(RV_SP, RV_SP, -(i32)pair_area));
   1977   rv64_emit32(mc, rv_sd_ptr(v, RV_S0, RV_SP, 0));
   1978   rv64_emit32(mc, rv_sd_ptr(v, RV_RA, RV_SP, (i32)v->ptr_bytes));
   1979   rv64_emit32(mc, rv_addi(RV_S0, RV_SP, 0)); /* s0 = sp = saved-pair address */
   1980   /* Reserve only the deferred `sub sp` region (patched in rv_func_end); record
   1981    * it for the patch and CFI. RISC-V has no stack probe, so the region is just
   1982    * the worst-case far `sub`. */
   1983   region = RV_NDT_SUB_WORDS;
   1984   a->prologue_pos = mc_pos(mc);
   1985   /* The live fixed entry is exactly RV_NDT_FIXED_ENTRY_WORDS words; the CFI
   1986    * advance in rv_func_end relies on prologue_pos sitting just past it. */
   1987   if (a->prologue_pos - a->func_start != RV_NDT_FIXED_ENTRY_WORDS * 4u)
   1988     rv_panic(a, "fixed prologue entry size drifted");
   1989   a->prologue_region_words = region;
   1990   for (i = 0; i < region; ++i) rv64_emit32(mc, RV_NOP);
   1991   /* sret + variadic GP spills are s0-relative and run live after the region. */
   1992   rv_reserve_entry_saves(a);
   1993   rv_emit_entry_save_stores(a);
   1994 }
   1995 
   1996 static void rv_func_end(NativeTarget* t) {
   1997   RvNativeTarget* a = rv_of(t);
   1998   MCEmitter* mc = t->mc;
   1999   ObjBuilder* obj = t->obj;
   2000   ObjSecId sec = a->func->text_section_id;
   2001   u32 int_regs[RV_MAX_CALLEE_SAVES], fp_regs[RV_MAX_CALLEE_SAVES];
   2002   u32 n_int = rv_collect_int_saves(a, int_regs);
   2003   u32 n_fp = rv_collect_fp_saves(a, fp_regs);
   2004   u32 frame_size = rv_frame_size(a);
   2005   u32 fp_pair_off = rv_fp_pair_off(a, frame_size);
   2006   u32 end;
   2007   i32 i;
   2008   a->frame_size_final = frame_size;
   2009   a->fp_pair_off = fp_pair_off;
   2010 
   2011   /* epilogue */
   2012   mc_label_place(mc, a->epilogue_label);
   2013   if (a->slim_prologue) {
   2014     /* Frameless leaf: no callee-saves, no s0/ra to reload, sp untouched. */
   2015     rv64_emit32(mc, rv_jalr(RV_ZERO, RV_RA, 0));
   2016   } else {
   2017     const RiscvVariant* v = a->variant;
   2018     for (i = (i32)n_int - 1; i >= 0; --i)
   2019       rv_load_s0(v, mc, 0, int_regs[i], rv_save_off(a, n_int, (u32)i));
   2020     for (i = (i32)n_fp - 1; i >= 0; --i)
   2021       rv_load_s0(v, mc, 1, fp_regs[i], rv_save_off(a, n_int, n_int + (u32)i));
   2022     if (a->frame.has_alloca)
   2023       rv_emit_addr_adjust(v, mc, RV_SP, RV_S0, -(i32)fp_pair_off);
   2024     /* Reload ra/s0 from the saved pair (s0 at +0, ra at +ptr_bytes), pointer
   2025      * width. */
   2026     rv64_emit32(mc, rv_ld_ptr(v, RV_RA, RV_S0, (i32)v->ptr_bytes));
   2027     rv64_emit32(mc, rv_ld_ptr(v, RV_S0, RV_S0, 0));
   2028     /* sp += frame_size */
   2029     if (fits_i12((i32)frame_size)) {
   2030       rv64_emit32(mc, rv_addi(RV_SP, RV_SP, (i32)frame_size));
   2031     } else {
   2032       rv_emit_load_imm(v, mc, 1, RV_TMP0, (i64)frame_size);
   2033       rv64_emit32(mc, rv_add(RV_SP, RV_SP, RV_TMP0));
   2034     }
   2035     rv64_emit32(mc, rv_jalr(RV_ZERO, RV_RA, 0));
   2036   }
   2037 
   2038   /* patch the deferred-`sub` region. The frame-independent entry (saved pair +
   2039    * s0 anchor) was already emitted live in rv_func_begin; only the `sub sp` that
   2040    * grows the frame below the pair is deferred here. Single-pass path only — the
   2041    * known-frame path emits its prologue final and never patches. */
   2042   if (!a->frame.known_frame) {
   2043     u32 region = a->prologue_region_words;
   2044     u32 words[RV_NDT_SUB_WORDS];
   2045     u32 nwords, k;
   2046     if (region > RV_NDT_SUB_WORDS) rv_panic(a, "prologue region too large");
   2047     for (k = 0; k < region; ++k) words[k] = RV_NOP;
   2048     nwords = rv_build_ndt_sub(a, frame_size, words, region);
   2049     /* If the deferred `sub` is shorter than the reserved region, branch straight
   2050      * to the entry saves rather than executing the trailing NOPs. */
   2051     if (nwords < region) {
   2052       words[nwords] = rv_jal(RV_ZERO, (i32)((region - nwords) * 4u));
   2053       for (k = nwords + 1u; k < region; ++k) words[k] = RV_NOP;
   2054     }
   2055     for (k = 0; k < region; ++k)
   2056       rv_patch32(obj, sec, a->prologue_pos + k * 4u, words[k]);
   2057   }
   2058   /* patch alloca sites: addi dst, sp, max_outgoing */
   2059   {
   2060     u32 mo = align_up_u32(a->frame.max_outgoing, 16u);
   2061     u32 k;
   2062     if (mo > 2047u) rv_panic(a, "max_outgoing too large for alloca patch");
   2063     for (k = 0; k < a->npatches; ++k)
   2064       rv_patch32(obj, sec, a->patches[k].pos,
   2065                  rv_addi(a->patches[k].dst_reg, RV_SP, (i32)mo));
   2066   }
   2067 
   2068   /* CFI: CFA = s0 + (frame_size - fp_pair_off) */
   2069   {
   2070     if (a->slim_prologue) {
   2071       /* Frameless leaf: CFA = sp (unchanged from entry) and the return address
   2072        * stays live in ra (the CIE default), so no saved-register rules. The
   2073        * state holds from the first instruction (offset 0). */
   2074       mc_cfi_set_next_pc_offset(mc, 0);
   2075       mc_cfi_def_cfa(mc, RV_SP, 0);
   2076     } else {
   2077       i32 cfa = (i32)frame_size - (i32)fp_pair_off;
   2078       /* CFI advance to the post-prologue PC. known-frame: prologue_pos +
   2079        * minimal_prologue_words. single-pass: prologue_pos is past the live fixed
   2080        * entry, so add the reserved deferred-`sub` region (the saved pair + s0
   2081        * anchor are already established by the fixed entry; the deferred `sub` is
   2082        * what completes the frame). The sret/variadic spills after the region do
   2083        * not move sp/s0, so the CFA state holds from here. */
   2084       u32 post = a->prologue_pos + (a->frame.known_frame
   2085                                         ? a->minimal_prologue_words * 4u
   2086                                         : a->prologue_region_words * 4u);
   2087       u32 k;
   2088       mc_cfi_set_next_pc_offset(mc, post - a->func_start);
   2089       mc_cfi_def_cfa(mc, RV_S0, cfa);
   2090       mc_cfi_offset(mc, RV_S0, -cfa);
   2091       /* ra is saved at the saved-pair stride above s0 (ptr_bytes). */
   2092       mc_cfi_offset(mc, RV_RA, -cfa + (i32)a->variant->ptr_bytes);
   2093       for (k = 0; k < n_int; ++k)
   2094         mc_cfi_offset(mc, int_regs[k], rv_save_off(a, n_int, k) - cfa);
   2095       for (k = 0; k < n_fp; ++k)
   2096         mc_cfi_offset(mc, 32u + fp_regs[k],
   2097                        rv_save_off(a, n_int, n_int + k) - cfa);
   2098     }
   2099   }
   2100 
   2101   end = mc_pos(mc);
   2102   obj_symbol_define(obj, a->func->sym, sec, (u64)a->func_start,
   2103                     (u64)(end - a->func_start));
   2104   if (a->func->atomize)
   2105     obj_atom_define(obj, sec, a->func_start, end - a->func_start, a->func->sym,
   2106                     0);
   2107   if (mc->debug) debug_func_pc_range(mc->debug, sec, a->func_start, end);
   2108   mc_cfi_endproc(mc);
   2109   mc_end_function(mc);
   2110   a->func = NULL;
   2111 }
   2112 
   2113 /* rv64 homes its callee-saves below the locals at rv_save_off(idx) rather than
   2114  * in frame slots, so alloc_slots=0: native_frame just records the {reg,cls} set
   2115  * derived from the optimizer's per-class used-masks. */
   2116 static void rv_reserve_callee_saves(NativeTarget* t, const u32* used,
   2117                                     u32 nclasses) {
   2118   native_frame_set_callee_saves(&rv_of(t)->frame, used, nclasses, NULL, 0, 0);
   2119 }
   2120 
   2121 static int rv_reg_is_callee_int(Reg r);
   2122 static int rv_reg_is_callee_fp(Reg r);
   2123 static void rv_asm_clobber_masks(Compiler* c, SrcLoc loc, const Sym* clobbers,
   2124                                  u32 nclob, u32* int_mask, u32* fp_mask);
   2125 
   2126 /* Expand the arch-neutral clobber-ABI sets (KitCgAsmClobberAbiSet bits) into
   2127  * this target's per-class caller/callee-saved register masks. */
   2128 /* abi_clobber_masks is shared as native_asm_abi_clobber_masks
   2129  * (cg/native_asm.h); it reads the masks from t->regs->classes. */
   2130 
   2131 /* Build the callee-saved set the prologue must preserve: the allocator-assigned
   2132  * callee-saved registers (frame->callee_saved_used) plus any an inline-asm
   2133  * block clobbers. The latter are opaque to the optimizer's operand scan, so it
   2134  * forwards the raw clobber names (frame->asm_clobbers) and the arch-neutral
   2135  * clobber-ABI sets (frame->asm_clobber_abi_sets); we resolve both into masks
   2136  * and keep only the callee-saved ones — rv_reg_is_callee_int excludes s0 (the
   2137  * frame pointer, preserved by the prologue head, not as an ordinary
   2138  * callee-save). This is the same register selection the per-block spill used,
   2139  * hoisted into the prologue. Writes up to `cap` per-class masks into `out` and
   2140  * returns the class count to reserve. */
   2141 static int rv_asm_reg_is_callee_saved(NativeTarget* t, NativeAllocClass cls,
   2142                                       Reg r) {
   2143   (void)t;
   2144   return (cls == NATIVE_REG_INT && rv_reg_is_callee_int(r)) ||
   2145          (cls == NATIVE_REG_FP && rv_reg_is_callee_fp(r));
   2146 }
   2147 
   2148 static u32 rv_known_callee_saves(NativeTarget* t,
   2149                                  const NativeKnownFrameDesc* frame, u32* out,
   2150                                  u32 cap) {
   2151   RvNativeTarget* a = rv_of(t);
   2152   SrcLoc loc = a->func ? a->func->loc : (SrcLoc){0, 0, 0};
   2153   return native_asm_known_callee_saves(
   2154       t, loc, frame, out, cap, rv_asm_clobber_masks,
   2155       rv_asm_reg_is_callee_saved);
   2156 }
   2157 
   2158 static u32 rv_signature_stack_bytes(NativeTarget* t, KitCgTypeId fn_type,
   2159                                     int* variadic, u32* nparams);
   2160 
   2161 /* Optimizer entry point: the full frame is supplied up front, so the prologue
   2162  * is emitted final the moment it is built — no NOP region, no func_end patch
   2163  * (rv_func_end skips patching when known_frame). rv_build_prologue emits the
   2164  * sret spill and the variadic register-save stores inline, so there is no
   2165  * separate entry-save emission. Slot creation order matches the single-pass
   2166  * path: callee-saves first (only recorded for rv64), then static slots, then
   2167  * the sret entry-save slot. */
   2168 static void rv_func_begin_known_frame(NativeTarget* t, const CGFuncDesc* fd,
   2169                                       const NativeKnownFrameDesc* frame,
   2170                                       NativeFrameSlot* out_slots) {
   2171   RvNativeTarget* a = rv_of(t);
   2172   MCEmitter* mc = t->mc;
   2173   u32 int_regs[RV_MAX_CALLEE_SAVES], fp_regs[RV_MAX_CALLEE_SAVES];
   2174   u32 n_int, n_fp, frame_size, fp_pair_off, nwords, i;
   2175   u32 words[RV_KNOWN_PROLOGUE_WORDS];
   2176   rv_func_begin_common(t, fd);
   2177   a->frame.known_frame = 1;
   2178   if (frame) {
   2179     u32 cs[NATIVE_REG_CLASS_COUNT];
   2180     u32 ncs = rv_known_callee_saves(t, frame, cs, NATIVE_REG_CLASS_COUNT);
   2181     a->frame.has_alloca = frame->has_alloca;
   2182     if (ncs) rv_reserve_callee_saves(t, cs, ncs);
   2183     for (i = 0; i < frame->nslots; ++i) {
   2184       NativeFrameSlot slot = rv_frame_slot(t, &frame->slots[i]);
   2185       if (out_slots) out_slots[i] = slot;
   2186     }
   2187     rv_reserve_entry_saves(a);
   2188     native_frame_note_outgoing(&a->frame, frame->max_outgoing);
   2189   }
   2190   /* Frame is final: size and offsets are settled, so emit the exact prologue.
   2191    */
   2192   frame_size = rv_frame_size(a);
   2193   fp_pair_off = rv_fp_pair_off(a, frame_size);
   2194   a->frame_size_final = frame_size;
   2195   a->fp_pair_off = fp_pair_off;
   2196   a->prologue_pos = mc_pos(mc);
   2197   /* Leaf no-frame tier (aa64 slim_prologue equivalent): a leaf with no
   2198    * callee-saves, no body slots, no outgoing args, no sret/variadic and
   2199    * register-only params never reads s0 (no frame slots / stack args) nor
   2200    * clobbers ra (no calls). Emit no prologue at all; rv_func_end emits a bare
   2201    * `ret`. cum_off==0 already implies no sret slot and no param spills, but the
   2202    * extra guards keep the intent explicit. Inline asm is excluded: it can
   2203    * clobber ra opaquely, and without the saved record the bare `ret` would
   2204    * return through the destroyed link register. */
   2205   a->slim_prologue = frame && frame->is_leaf && !frame->has_asm &&
   2206                      !frame->reads_frame && a->frame.ncallee_saves == 0 &&
   2207                      !a->frame.has_alloca && a->frame.cum_off == 0 &&
   2208                      a->frame.max_outgoing == 0 && !a->has_sret &&
   2209                      !a->is_variadic &&
   2210                      rv_signature_stack_bytes(t, fd->fn_type, NULL, NULL) == 0;
   2211   if (a->slim_prologue) {
   2212     a->minimal_prologue_words = 0;
   2213     native_frame_set_final(&a->frame);
   2214     return;
   2215   }
   2216   n_int = rv_collect_int_saves(a, int_regs);
   2217   n_fp = rv_collect_fp_saves(a, fp_regs);
   2218   nwords = rv_build_prologue(a, words, RV_KNOWN_PROLOGUE_WORDS, frame_size,
   2219                              fp_pair_off, int_regs, n_int, fp_regs, n_fp);
   2220   for (i = 0; i < nwords; ++i) rv64_emit32(mc, words[i]);
   2221   a->minimal_prologue_words = nwords;
   2222   native_frame_set_final(&a->frame);
   2223 }
   2224 
   2225 /* ============================ params / ABI helpers
   2226  * ============================ */
   2227 
   2228 static const ABIArgInfo* rv_param_abi(NativeTarget* t, const ABIFuncInfo* abi,
   2229                                       const NativeCallDesc* desc, u32 i,
   2230                                       ABIArgInfo* scratch) {
   2231   /* Synthesized for unnamed (variadic) args, or untyped calls. RISC-V LP64D
   2232    * passes variadic FP args in INTEGER registers (as their bit pattern), not
   2233    * the FP pool — so a variadic float part is ABI_CLASS_INT. */
   2234   int variadic = abi && i >= abi->nparams;
   2235   u32 gpr = rv_of(t)->variant->ptr_bytes; /* GPR width: 4 ilp32 / 8 lp64 */
   2236   u32 sz, align;
   2237   int is_fp;
   2238   if (abi && i < abi->nparams) return &abi->params[i];
   2239   sz = native_type_size(t, desc->args[i].type);
   2240   align = native_type_align(t, desc->args[i].type);
   2241   /* A variadic FP arg rides the INTEGER pool as its bit pattern (RISC-V passes
   2242    * unnamed FP args in GPRs), so it is INT-class here. */
   2243   is_fp = !variadic && cg_type_is_float(t->c, desc->args[i].type);
   2244   memset(scratch, 0, sizeof *scratch);
   2245   scratch->kind = ABI_ARG_DIRECT;
   2246   /* A scalar wider than one GPR (an 8-byte i64 / soft-double on ilp32) rides a
   2247    * register pair, matching the named-arg classifier (abi_rv64.c). Synthesize
   2248    * one INT part per GPR-word so the per-part marshaller fills both registers
   2249    * (low word in the lower-numbered reg) instead of dropping the high half into
   2250    * a single register. FP-class args (hardware-float, size<=GPR) stay single.
   2251    */
   2252   if (!is_fp && sz > gpr) {
   2253     u32 nparts = (sz + gpr - 1u) / gpr, p;
   2254     ABIArgPart* parts = arena_zarray(t->c->tu, ABIArgPart, nparts);
   2255     for (p = 0; p < nparts; ++p) {
   2256       u32 off = p * gpr;
   2257       parts[p].cls = ABI_CLASS_INT;
   2258       parts[p].loc = ABI_LOC_REG;
   2259       parts[p].size = (sz - off) < gpr ? (sz - off) : gpr;
   2260       parts[p].align = gpr;
   2261       parts[p].src_offset = off;
   2262     }
   2263     scratch->nparts = nparts;
   2264     scratch->parts = parts;
   2265     return scratch;
   2266   }
   2267   scratch->nparts = 1;
   2268   scratch->parts = arena_zarray(t->c->tu, ABIArgPart, 1);
   2269   ((ABIArgPart*)scratch->parts)[0].cls = is_fp ? ABI_CLASS_FP : ABI_CLASS_INT;
   2270   ((ABIArgPart*)scratch->parts)[0].loc = ABI_LOC_REG;
   2271   ((ABIArgPart*)scratch->parts)[0].size = sz;
   2272   ((ABIArgPart*)scratch->parts)[0].align = align;
   2273   return scratch;
   2274 }
   2275 
   2276 /* Outgoing stack-slot size/align: the xlen-word (gp_slot_bytes: 8 lp64d /
   2277  * 4 ilp32) is the natural slot stride; stack ABI alignment caps at 16. */
   2278 static u32 rv_part_stack_size(const RiscvVariant* v, const ABIArgPart* part) {
   2279   u32 slot = v->gp_slot_bytes;
   2280   return align_up_u32(part->size ? part->size : slot, slot);
   2281 }
   2282 static u32 rv_part_stack_align(const RiscvVariant* v, const ABIArgPart* part) {
   2283   u32 slot = v->gp_slot_bytes;
   2284   u32 al = part->align ? part->align : slot;
   2285   if (al < slot) al = slot;
   2286   if (al > 16u) al = 16u;
   2287   return al;
   2288 }
   2289 
   2290 static KitCgTypeId rv_part_scalar_type(const ABIArgPart* part) {
   2291   if (part->cls == ABI_CLASS_FP) {
   2292     if (part->size <= 4u) return builtin_id(KIT_CG_BUILTIN_F32);
   2293     return builtin_id(KIT_CG_BUILTIN_F64);
   2294   }
   2295   switch (part->size) {
   2296     case 1u:
   2297       return builtin_id(KIT_CG_BUILTIN_I8);
   2298     case 2u:
   2299       return builtin_id(KIT_CG_BUILTIN_I16);
   2300     case 4u:
   2301       return builtin_id(KIT_CG_BUILTIN_I32);
   2302     default:
   2303       return builtin_id(KIT_CG_BUILTIN_I64);
   2304   }
   2305 }
   2306 
   2307 static u32 rv_class_stack_size(const RiscvVariant* v, const ABIArgInfo* ai) {
   2308   u32 slot = v->gp_slot_bytes;
   2309   u32 total = 0, p;
   2310   if (!ai || ai->kind == ABI_ARG_IGNORE) return 0;
   2311   if (ai->kind == ABI_ARG_INDIRECT) return v->ptr_bytes;
   2312   for (p = 0; p < ai->nparts; ++p) {
   2313     total = align_up_u32(total, rv_part_stack_align(v, &ai->parts[p]));
   2314     total += rv_part_stack_size(v, &ai->parts[p]);
   2315   }
   2316   return align_up_u32(total ? total : slot, slot);
   2317 }
   2318 
   2319 static u32 rv_call_stack_size(NativeTarget* t, const NativeCallDesc* desc) {
   2320   const RiscvVariant* v = rv_of(t)->variant;
   2321   const ABIFuncInfo* abi = abi_cg_func_info(t->c->abi, desc->fn_type);
   2322   /* sret consumes a0 as the implicit first integer argument. */
   2323   u32 next_int = (abi && abi->has_sret) ? 1u : 0u;
   2324   u32 next_fp = 0, stack = 0, i, p;
   2325   for (i = 0; i < desc->nargs; ++i) {
   2326     ABIArgInfo tmp;
   2327     const ABIArgInfo* ai = rv_param_abi(t, abi, desc, i, &tmp);
   2328     int force_stack =
   2329         abi && abi->variadic && abi->vararg_on_stack && i >= abi->nparams;
   2330     if (ai->kind == ABI_ARG_IGNORE) continue;
   2331     if (force_stack) {
   2332       stack += rv_class_stack_size(v, ai);
   2333       continue;
   2334     }
   2335     if (ai->kind == ABI_ARG_INDIRECT) {
   2336       if (next_int < 8u)
   2337         next_int++;
   2338       else
   2339         stack += v->ptr_bytes;
   2340       continue;
   2341     }
   2342     for (p = 0; p < ai->nparts; ++p) {
   2343       const ABIArgPart* part = &ai->parts[p];
   2344       if (part->cls == ABI_CLASS_FP) {
   2345         if (next_fp < 8u)
   2346           next_fp++;
   2347         else {
   2348           stack = align_up_u32(stack, rv_part_stack_align(v, part));
   2349           stack += rv_part_stack_size(v, part);
   2350         }
   2351       } else {
   2352         if (next_int < 8u)
   2353           next_int++;
   2354         else {
   2355           stack = align_up_u32(stack, rv_part_stack_align(v, part));
   2356           stack += rv_part_stack_size(v, part);
   2357         }
   2358       }
   2359     }
   2360   }
   2361   return align_up_u32(stack, 16u);
   2362 }
   2363 
   2364 static u32 rv_signature_stack_bytes(NativeTarget* t, KitCgTypeId fn_type,
   2365                                     int* variadic, u32* nparams) {
   2366   const ABIFuncInfo* abi = abi_cg_func_info(t->c->abi, fn_type);
   2367   NativeCallDesc d;
   2368   if (variadic) *variadic = abi ? (int)abi->variadic : 0;
   2369   if (nparams) *nparams = abi ? abi->nparams : 0u;
   2370   memset(&d, 0, sizeof d);
   2371   d.fn_type = fn_type;
   2372   d.nargs = abi ? abi->nparams : 0u;
   2373   if (d.nargs) d.args = arena_zarray(t->c->tu, NativeLoc, d.nargs);
   2374   return rv_call_stack_size(t, &d);
   2375 }
   2376 
   2377 static u32 rv_call_stack_bytes(NativeTarget* t, const NativeCallDesc* desc) {
   2378   return rv_call_stack_size(t, desc);
   2379 }
   2380 
   2381 /* Resolve a dereferenceable NativeLoc to its storage address. */
   2382 static NativeAddr rv_storage_addr(RvNativeTarget* a, NativeLoc loc,
   2383                                   u32 offset) {
   2384   NativeAddr addr;
   2385   if (!native_loc_storage_addr(loc, (i32)offset, &addr))
   2386     rv_panic(a, "location is not storage");
   2387   return addr;
   2388 }
   2389 
   2390 static void rv_load_part(NativeTarget* t, NativeLoc dst, NativeLoc src,
   2391                          u32 offset, u32 size) {
   2392   RvNativeTarget* a = rv_of(t);
   2393   if (src.kind == NATIVE_LOC_REG) {
   2394     rv_move(t, dst, src);
   2395     return;
   2396   }
   2397   if (native_loc_addr_role(src) == NATIVE_LOC_ADDR_ROLE_VALUE) {
   2398     NativeAddr addr;
   2399     if (offset != 0u || size != t->c->target.ptr_size ||
   2400         !native_loc_address_value(src, &addr))
   2401       rv_panic(a, "split or invalid address-value argument");
   2402     rv_load_addr(t, dst, addr);
   2403     return;
   2404   }
   2405   if (native_loc_addr_role(src) == NATIVE_LOC_ADDR_ROLE_STORAGE) {
   2406     NativeAddr addr = rv_storage_addr(a, src, offset);
   2407     addr.base_type = dst.type;
   2408     rv_emit_mem(a, 1, dst, addr, native_mem_for_type(t, dst.type, size));
   2409     return;
   2410   }
   2411   if (src.kind == NATIVE_LOC_IMM) {
   2412     i64 part;
   2413     if (!native_loc_imm_part(src, offset, size, &part))
   2414       rv_panic(a, "invalid immediate argument part");
   2415     rv_emit_load_imm(a->variant, t->mc, loc_is_64(t, dst) ? 1u : 0u,
   2416                      loc_reg(dst), part);
   2417     return;
   2418   }
   2419   rv_panic(a, "unsupported part source");
   2420 }
   2421 
   2422 static void rv_store_part(NativeTarget* t, NativeLoc dst, NativeLoc src,
   2423                           u32 offset, u32 size) {
   2424   RvNativeTarget* a = rv_of(t);
   2425   if (native_loc_addr_role(dst) == NATIVE_LOC_ADDR_ROLE_STORAGE) {
   2426     NativeAddr addr = rv_storage_addr(a, dst, offset);
   2427     addr.base_type = src.type;
   2428     rv_emit_mem(a, 0, src, addr, native_mem_for_type(t, src.type, size));
   2429     return;
   2430   }
   2431   if (dst.kind == NATIVE_LOC_REG) {
   2432     rv_move(t, dst, src);
   2433     return;
   2434   }
   2435   rv_panic(a, "unsupported part destination");
   2436 }
   2437 
   2438 static void rv_addr_of_loc(NativeTarget* t, NativeLoc dst, NativeLoc src) {
   2439   NativeAddr addr = rv_storage_addr(rv_of(t), src, 0);
   2440   rv_load_addr(t, dst, addr);
   2441 }
   2442 
   2443 static void rv_store_outgoing_part(NativeTarget* t, int tail_call,
   2444                                    u32 stack_off, NativeLoc src, u32 size) {
   2445   NativeAddr addr;
   2446   memset(&addr, 0, sizeof addr);
   2447   addr.base_kind = NATIVE_ADDR_BASE_REG;
   2448   addr.base_type = src.type;
   2449   if (tail_call) {
   2450     /* A sibling call reuses the caller's frame: its outgoing stack args land in
   2451      * the caller's incoming-arg window ([s0 + 16 + va_save + off]) — physically
   2452      * the same address the tail-callee will read at [sp+off] once the teardown
   2453      * has restored sp to the caller's entry sp (the CFA). */
   2454     addr.base.reg = RV_S0;
   2455     addr.offset = rv_s0_off_in_arg(rv_of(t), stack_off);
   2456   } else {
   2457     addr.base.reg = RV_SP;
   2458     addr.offset = (i32)stack_off;
   2459   }
   2460   rv_emit_mem(rv_of(t), 0, src, addr, native_mem_for_type(t, src.type, size));
   2461 }
   2462 
   2463 /* Copy exactly the semantic bytes of a stack-routed value. The caller owns
   2464  * the separately rounded physical ABI slot and advances its stack cursor by
   2465  * that carrier size; padding is never treated as readable source storage. */
   2466 static void rv_store_outgoing_value_bytes(NativeTarget* t, int tail_call,
   2467                                           u32 stack_off, NativeLoc src,
   2468                                           u32 value_size) {
   2469   RvNativeTarget* a = rv_of(t);
   2470   NativeLoc tmp = native_loc_reg(src.type, NATIVE_REG_INT, RV_TMP0);
   2471   u32 off = 0;
   2472   while (off < value_size) {
   2473     u32 chunk = value_size - off;
   2474     if (chunk > a->variant->ptr_bytes) chunk = a->variant->ptr_bytes;
   2475     rv_load_part(t, tmp, src, off, chunk);
   2476     rv_store_outgoing_part(t, tail_call, stack_off + off, tmp, chunk);
   2477     off += chunk;
   2478   }
   2479 }
   2480 
   2481 /* Give every indirect target one lifetime that is disjoint from argument
   2482  * transport. `ra` is reserved from allocation and from the t0-t3 backend
   2483  * temporary bank, while its incoming value is already saved in the function
   2484  * frame at any real call site. A normal JALR may read and write ra in the same
   2485  * instruction. A tail site copies this parked value to t1 immediately before
   2486  * teardown restores the incoming ra.
   2487  *
   2488  * Lowering normally presents REG/GLOBAL, but NativeCallDesc permits scalar
   2489  * address values and dereferenceable storage too. Route every non-global shape
   2490  * through the same checked part boundary so FRAME/STACK/ADDR/FRAME_ADDR/IMM
   2491  * remain valid without acquiring an optimizer-owned register. NONE is kept for
   2492  * planning-only queries that never emit a call. */
   2493 static NativeLoc rv_stage_indirect_callee(NativeTarget* t, NativeLoc callee) {
   2494   RvNativeTarget* a = rv_of(t);
   2495   NativeLoc parked;
   2496   if (callee.kind == NATIVE_LOC_NONE || callee.kind == NATIVE_LOC_GLOBAL)
   2497     return callee;
   2498   if (callee.kind == NATIVE_LOC_REG &&
   2499       (NativeAllocClass)callee.cls != NATIVE_REG_INT)
   2500     rv_panic(a, "indirect callee is not in an integer register");
   2501   if (!callee.type) rv_panic(a, "indirect callee has no pointer type");
   2502   parked = native_loc_reg(callee.type, NATIVE_REG_INT, RV_RA);
   2503   rv_load_part(t, parked, callee, 0, a->variant->ptr_bytes);
   2504   return parked;
   2505 }
   2506 
   2507 /* NativeTarget bind_param: route incoming param (ABI loc) into dst. */
   2508 static void rv_bind_native_param(NativeTarget* t, const CGParamDesc* p,
   2509                                  NativeLoc dst) {
   2510   RvNativeTarget* a = rv_of(t);
   2511   const ABIFuncInfo* abi = abi_cg_func_info(t->c->abi, a->func->fn_type);
   2512   const ABIArgInfo* ai =
   2513       p->index < abi->nparams ? &abi->params[p->index] : NULL;
   2514   int to_reg = dst.kind == NATIVE_LOC_REG;
   2515   u32 i;
   2516   if (!ai || ai->kind == ABI_ARG_IGNORE) return;
   2517   if (ai->kind == ABI_ARG_INDIRECT) {
   2518     NativeLoc src = native_loc_reg(
   2519         builtin_id(KIT_CG_BUILTIN_I64), NATIVE_REG_INT,
   2520         a->next_param_int < 8u ? RV_A0 + a->next_param_int : RV_TMP0);
   2521     NativeAddr d_addr, from;
   2522     AggregateAccess access;
   2523     if (a->next_param_int < 8u) {
   2524       a->next_param_int++;
   2525     } else {
   2526       NativeAddr sa;
   2527       memset(&sa, 0, sizeof sa);
   2528       sa.base_kind = NATIVE_ADDR_BASE_REG;
   2529       sa.base.reg = RV_S0;
   2530       sa.offset = rv_s0_off_in_arg(a, a->next_param_stack);
   2531       sa.base_type = src.type;
   2532       rv_emit_mem(a, 1, src, sa,
   2533                   native_mem_for_type(t, src.type, a->variant->ptr_bytes));
   2534       a->next_param_stack += a->variant->ptr_bytes;
   2535     }
   2536     if (dst.kind != NATIVE_LOC_FRAME)
   2537       rv_panic(a, "indirect parameter requires a frame destination");
   2538     memset(&d_addr, 0, sizeof d_addr);
   2539     d_addr.base_kind = NATIVE_ADDR_BASE_FRAME;
   2540     d_addr.base.frame = dst.v.frame;
   2541     d_addr.base_type = p->type;
   2542     memset(&from, 0, sizeof from);
   2543     from.base_kind = NATIVE_ADDR_BASE_REG;
   2544     from.base.reg = loc_reg(src);
   2545     from.base_type = p->type;
   2546     memset(&access, 0, sizeof access);
   2547     access.type = p->type;
   2548     access.size = p->size ? p->size : (u32)cg_type_size(t->c, p->type);
   2549     access.align = p->align ? p->align : native_type_align(t, p->type);
   2550     rv_copy_bytes(t, d_addr, from, access);
   2551     return;
   2552   }
   2553   for (i = 0; i < ai->nparts; ++i) {
   2554     const ABIArgPart* part = &ai->parts[i];
   2555     NativeAllocClass cls =
   2556         part->cls == ABI_CLASS_FP ? NATIVE_REG_FP : NATIVE_REG_INT;
   2557     NativeLoc src;
   2558     if (cls == NATIVE_REG_FP && a->next_param_fp < 8u) {
   2559       src = native_loc_reg(p->type, cls, RV_FA0 + a->next_param_fp++);
   2560     } else if (cls == NATIVE_REG_INT && a->next_param_int < 8u) {
   2561       src = native_loc_reg(p->type, cls, RV_A0 + a->next_param_int++);
   2562     } else {
   2563       Reg tmp = (cls == NATIVE_REG_FP) ? RV_FTMP0 : RV_TMP0;
   2564       NativeAddr sa;
   2565       src = native_loc_reg(p->type, cls, tmp);
   2566       a->next_param_stack = align_up_u32(a->next_param_stack,
   2567                                          rv_part_stack_align(a->variant, part));
   2568       memset(&sa, 0, sizeof sa);
   2569       sa.base_kind = NATIVE_ADDR_BASE_REG;
   2570       sa.base.reg = RV_S0;
   2571       sa.base_type = p->type;
   2572       sa.offset = rv_s0_off_in_arg(a, a->next_param_stack);
   2573       rv_emit_mem(a, 1, src, sa, native_mem_for_type(t, p->type, part->size));
   2574       a->next_param_stack += rv_part_stack_size(a->variant, part);
   2575     }
   2576     if (dst.kind == NATIVE_LOC_NONE) {
   2577       /* unused parameter; cursors already advanced */
   2578     } else if (to_reg) {
   2579       NativeLoc d = native_loc_reg(dst.type ? dst.type : p->type,
   2580                                    (NativeAllocClass)dst.cls, (Reg)dst.v.reg);
   2581       if (!(src.kind == NATIVE_LOC_REG && loc_reg(src) == loc_reg(d) &&
   2582             (NativeAllocClass)src.cls == (NativeAllocClass)d.cls))
   2583         rv_move(t, d, src);
   2584     } else {
   2585       rv_store_part(
   2586           t, native_loc_stack(p->type, dst.v.frame, (i32)part->src_offset), src,
   2587           0, part->size);
   2588     }
   2589   }
   2590   a->incoming_stack_size = align_up_u32(a->next_param_stack, 16u);
   2591 }
   2592 
   2593 /* ============================ calls / returns ============================ */
   2594 
   2595 typedef NativeArgMove RvArgMove;
   2596 
   2597 static void rv_emit_one_arg_move(NativeTarget* t, const NativeArgMove* m) {
   2598   if (m->is_addr)
   2599     rv_addr_of_loc(t, m->dst, m->src);
   2600   else
   2601     rv_load_part(t, m->dst, m->src, m->src_offset, m->size);
   2602 }
   2603 
   2604 /* Parallel-copy register arg moves via the shared scheduler; cycles break
   2605  * through backend-private t1/ft1. */
   2606 static void rv_emit_reg_arg_moves(NativeTarget* t, NativeArgMove* moves,
   2607                                   u32 n) {
   2608   NativeArgShuffle s;
   2609   if (n > RV_MAX_REG_ARG_MOVES) rv_panic(rv_of(t), "too many register args");
   2610   memset(&s, 0, sizeof s);
   2611   s.t = t;
   2612   s.emit_one = rv_emit_one_arg_move;
   2613   s.reg_move = rv_move;
   2614   s.scratch[NATIVE_REG_INT] = RV_TMP1;
   2615   s.scratch[NATIVE_REG_FP] = RV_FTMP1;
   2616   s.scratch_class_mask = (1u << NATIVE_REG_INT) | (1u << NATIVE_REG_FP);
   2617   native_arg_shuffle(&s, moves, n);
   2618 }
   2619 
   2620 static void rv_marshal_call(NativeTarget* t, const NativeCallDesc* desc,
   2621                             NativeCallPhase* plan) {
   2622   RvNativeTarget* a = rv_of(t);
   2623   const ABIFuncInfo* abi = abi_cg_func_info(t->c->abi, desc->fn_type);
   2624   NativeCallPhaseRet* rets;
   2625   KitCgTypeId i64t = builtin_id(KIT_CG_BUILTIN_I64);
   2626   /* Right-size the result scratch to the exact number of entries the ret loops
   2627    * below write: nparts on a DIRECT register return, 1 on the !abi fallback,
   2628    * 0 (NULL) otherwise (IGNORE / sret / no results). */
   2629   u32 nrets_cap = (abi && abi->ret.kind == ABI_ARG_DIRECT && desc->nresults)
   2630                       ? abi->ret.nparts
   2631                       : ((!abi && desc->nresults) ? 1u : 0u);
   2632   memset(plan, 0, sizeof *plan);
   2633   rets = nrets_cap ? arena_zarray(t->c->tu, NativeCallPhaseRet, nrets_cap) : NULL;
   2634   plan->callee = desc->callee;
   2635   plan->rets = rets;
   2636   plan->flags = desc->flags;
   2637   plan->has_sret = abi && abi->has_sret;
   2638   plan->is_variadic = abi && abi->variadic;
   2639   plan->stack_arg_size = rv_call_stack_size(t, desc);
   2640   if (plan->stack_arg_size > a->frame.max_outgoing)
   2641     a->frame.max_outgoing = plan->stack_arg_size;
   2642   plan->callee = rv_stage_indirect_callee(t, plan->callee);
   2643   {
   2644     /* sret returns pass the hidden destination pointer as the implicit first
   2645      * integer argument (a0), so the real args start at a1. */
   2646     u32 next_int = (abi && abi->has_sret) ? 1u : 0u;
   2647     u32 next_fp = 0, stack = 0, nmoves = 0, i, p;
   2648     int tail = (desc->flags & CG_CALL_TAIL) != 0;
   2649     RvArgMove moves[RV_MAX_REG_ARG_MOVES];
   2650     for (i = 0; i < desc->nargs; ++i) {
   2651       ABIArgInfo tmp;
   2652       const ABIArgInfo* ai = rv_param_abi(t, abi, desc, i, &tmp);
   2653       int force_stack =
   2654           abi && abi->variadic && abi->vararg_on_stack && i >= abi->nparams;
   2655       if (ai->kind == ABI_ARG_IGNORE) continue;
   2656       if (force_stack) {
   2657         u32 value_size = native_type_size(t, desc->args[i].type);
   2658         u32 n = rv_class_stack_size(a->variant, ai);
   2659         rv_store_outgoing_value_bytes(t, tail, stack, desc->args[i],
   2660                                       value_size);
   2661         stack += n;
   2662         continue;
   2663       }
   2664       if (ai->kind == ABI_ARG_INDIRECT) {
   2665         u32 ptr_sz = a->variant->ptr_bytes;
   2666         if (next_int < 8u) {
   2667           RvArgMove* m = &moves[nmoves++];
   2668           m->dst = native_loc_reg(i64t, NATIVE_REG_INT, RV_A0 + next_int++);
   2669           m->src = desc->args[i];
   2670           m->src_offset = 0;
   2671           m->size = ptr_sz;
   2672           m->is_addr = 1;
   2673         } else {
   2674           NativeLoc ptr = native_loc_reg(i64t, NATIVE_REG_INT, RV_TMP0);
   2675           rv_addr_of_loc(t, ptr, desc->args[i]);
   2676           rv_store_outgoing_part(t, tail, stack, ptr, ptr_sz);
   2677           stack += ptr_sz;
   2678         }
   2679         continue;
   2680       }
   2681       for (p = 0; p < ai->nparts; ++p) {
   2682         const ABIArgPart* part = &ai->parts[p];
   2683         NativeAllocClass cls =
   2684             part->cls == ABI_CLASS_FP ? NATIVE_REG_FP : NATIVE_REG_INT;
   2685         if ((cls == NATIVE_REG_FP && next_fp < 8u) ||
   2686             (cls == NATIVE_REG_INT && next_int < 8u)) {
   2687           RvArgMove* m = &moves[nmoves++];
   2688           Reg areg =
   2689               cls == NATIVE_REG_FP ? RV_FA0 + next_fp++ : RV_A0 + next_int++;
   2690           m->dst = native_loc_reg(desc->args[i].type, cls, areg);
   2691           m->src = desc->args[i];
   2692           m->src_offset = part->src_offset;
   2693           m->size = part->size;
   2694           m->is_addr = 0;
   2695         } else {
   2696           Reg tmp = cls == NATIVE_REG_FP ? RV_FTMP0 : RV_TMP0;
   2697           NativeLoc tmpreg = native_loc_reg(desc->args[i].type, cls, tmp);
   2698           rv_load_part(t, tmpreg, desc->args[i], part->src_offset, part->size);
   2699           stack = align_up_u32(stack, rv_part_stack_align(a->variant, part));
   2700           rv_store_outgoing_part(t, tail, stack, tmpreg, part->size);
   2701           stack += rv_part_stack_size(a->variant, part);
   2702         }
   2703       }
   2704     }
   2705     rv_emit_reg_arg_moves(t, moves, nmoves);
   2706     if (abi && abi->has_sret) {
   2707       /* sret pointer goes in a0; arg loads have completed. A tail call forwards
   2708        * the caller's own incoming sret pointer (spilled at entry) so the
   2709        * sibling writes the result into the caller's caller's destination;
   2710        * otherwise pass the address of this call's result slot. */
   2711       NativeLoc a0 = native_loc_reg(i64t, NATIVE_REG_INT, RV_A0);
   2712       if (tail)
   2713         rv_load_part(t, a0, native_loc_stack(i64t, a->sret_ptr_slot, 0), 0,
   2714                      a->variant->ptr_bytes);
   2715       else if (desc->nresults)
   2716         rv_addr_of_loc(t, a0, desc->results[0]);
   2717     }
   2718   }
   2719   if (abi && abi->ret.kind == ABI_ARG_DIRECT && desc->nresults) {
   2720     u32 nr = 0, ni = 0, nf = 0, p;
   2721     for (p = 0; p < abi->ret.nparts; ++p) {
   2722       const ABIArgPart* part = &abi->ret.parts[p];
   2723       NativeAllocClass cls =
   2724           part->cls == ABI_CLASS_FP ? NATIVE_REG_FP : NATIVE_REG_INT;
   2725       KitCgTypeId pty = rv_part_scalar_type(part);
   2726       Reg rreg = cls == NATIVE_REG_FP ? RV_FA0 + nf++ : RV_A0 + ni++;
   2727       rets[nr].src = native_loc_reg(pty, cls, rreg);
   2728       rets[nr].dst = desc->results[0];
   2729       if (rets[nr].dst.kind == NATIVE_LOC_FRAME)
   2730         rets[nr].dst = native_loc_stack(pty, desc->results[0].v.frame,
   2731                                         (i32)part->src_offset);
   2732       else if (rets[nr].dst.kind == NATIVE_LOC_STACK) {
   2733         rets[nr].dst.v.stack.offset += (i32)part->src_offset;
   2734         rets[nr].dst.type = pty;
   2735       }
   2736       rets[nr].mem = native_mem_for_type(t, pty, part->size);
   2737       nr++;
   2738     }
   2739     plan->nrets = nr;
   2740   } else if (abi && abi->ret.kind == ABI_ARG_IGNORE) {
   2741     plan->nrets = 0;
   2742   } else if (!abi && desc->nresults) {
   2743     rets[0].src = native_loc_reg(desc->results[0].type, NATIVE_REG_INT, RV_A0);
   2744     rets[0].dst = desc->results[0];
   2745     rets[0].mem = native_mem_for_type(t, desc->results[0].type, 0);
   2746     plan->nrets = 1;
   2747   }
   2748 }
   2749 
   2750 /* Emit a sibling (tail) call: tear the frame down to the caller's entry state
   2751  * and jump (no link) to the callee. Outgoing args are already in the arg regs /
   2752  * the caller's incoming-arg window. At -O0 there are no callee-saves, and the
   2753  * sp restore uses the CFA offset (s0 + 16 + va_save), which is independent of
   2754  * the not-yet-final frame_size — so no func_end patching is needed. */
   2755 static void rv_emit_tail_site(NativeTarget* t, NativeLoc callee) {
   2756   RvNativeTarget* a = rv_of(t);
   2757   const RiscvVariant* v = a->variant;
   2758   MCEmitter* mc = t->mc;
   2759   i32 cfa = (i32)(v->frame_save_size + rv_va_save_sz(a));
   2760   int indirect = callee.kind == NATIVE_LOC_REG;
   2761   u32 int_regs[RV_MAX_CALLEE_SAVES], fp_regs[RV_MAX_CALLEE_SAVES];
   2762   u32 n_int = rv_collect_int_saves(a, int_regs);
   2763   u32 n_fp = rv_collect_fp_saves(a, fp_regs);
   2764   i32 i;
   2765   /* Stage the target parked in ra into a reserved scratch BEFORE teardown: the
   2766    * callee-save / s0 / ra restores below would otherwise overwrite it. t1 is
   2767    * reserved (never allocable) and untouched by the restore loop (which only
   2768    * uses t0 for far offsets). */
   2769   if (indirect) rv64_emit32(mc, rv_addi(RV_TMP1, loc_reg(callee), 0));
   2770   /* Restore callee-saves before tearing the frame down (O1 path; none at -O0).
   2771    * Their save offsets are s0-relative via rv_save_off, so the restore is
   2772    * frame-size- and teardown-order-independent. */
   2773   for (i = (i32)n_int - 1; i >= 0; --i)
   2774     rv_load_s0(v, mc, 0, int_regs[i], rv_save_off(a, n_int, (u32)i));
   2775   for (i = (i32)n_fp - 1; i >= 0; --i)
   2776     rv_load_s0(v, mc, 1, fp_regs[i], rv_save_off(a, n_int, n_int + (u32)i));
   2777   rv64_emit32(mc, rv_ld_ptr(v, RV_RA, RV_S0, (i32)v->ptr_bytes));
   2778   rv64_emit32(mc, rv_addi(RV_SP, RV_S0, cfa));
   2779   rv64_emit32(mc, rv_ld_ptr(v, RV_S0, RV_S0, 0));
   2780   if (callee.kind == NATIVE_LOC_GLOBAL) {
   2781     u32 pos = mc_pos(mc);
   2782     rv64_emit32(mc, rv_auipc(RV_TMP0, 0));
   2783     rv64_emit32(mc, rv_jalr(RV_ZERO, RV_TMP0, 0));
   2784     mc_emit_reloc_at(mc, mc->section_id, pos, R_RV_CALL, callee.v.global.sym,
   2785                       callee.v.global.addend, 0, 0);
   2786   } else if (indirect) {
   2787     rv64_emit32(mc, rv_jalr(RV_ZERO, RV_TMP1, 0));
   2788   } else {
   2789     rv_panic(a, "unsupported tail call target");
   2790   }
   2791 }
   2792 
   2793 static void rv_emit_call(NativeTarget* t, const NativeCallPhase* plan) {
   2794   MCEmitter* mc = t->mc;
   2795   ObjSecId sec = mc->section_id;
   2796   if (plan->flags & CG_CALL_TAIL) {
   2797     rv_emit_tail_site(t, plan->callee);
   2798     return;
   2799   }
   2800   if (plan->callee.kind == NATIVE_LOC_GLOBAL) {
   2801     u32 pos = mc_pos(mc);
   2802     rv64_emit32(mc, rv_auipc(RV_RA, 0));
   2803     rv64_emit32(mc, rv_jalr(RV_RA, RV_RA, 0));
   2804     mc_emit_reloc_at(mc, sec, pos, R_RV_CALL, plan->callee.v.global.sym,
   2805                       plan->callee.v.global.addend, 0, 0);
   2806     return;
   2807   }
   2808   if (plan->callee.kind == NATIVE_LOC_REG) {
   2809     rv64_emit32(mc, rv_jalr(RV_RA, loc_reg(plan->callee), 0));
   2810     return;
   2811   }
   2812   rv_panic(rv_of(t), "unsupported call target");
   2813 }
   2814 
   2815 static void rv_marshal_ret(NativeTarget* t, const CGFuncDesc* fd,
   2816                            const NativeLoc* value,
   2817                            NativeCallPhaseRet** out_rets, u32* out_nrets) {
   2818   RvNativeTarget* a = rv_of(t);
   2819   const ABIFuncInfo* abi = abi_cg_func_info(t->c->abi, fd->fn_type);
   2820   NativeCallPhaseRet* rets = NULL;
   2821   u32 nr = 0;
   2822   if (value) rets = arena_zarray(t->c->tu, NativeCallPhaseRet, 4);
   2823   if (value && abi && abi->ret.kind == ABI_ARG_INDIRECT) {
   2824     KitCgTypeId i64t = builtin_id(KIT_CG_BUILTIN_I64);
   2825     NativeLoc dstp = native_loc_reg(i64t, NATIVE_REG_INT, RV_TMP1);
   2826     NativeLoc saved = native_loc_stack(i64t, a->sret_ptr_slot, 0);
   2827     NativeAddr dst_addr, src_addr;
   2828     AggregateAccess access;
   2829     rv_load_part(t, dstp, saved, 0, a->variant->ptr_bytes);
   2830     memset(&dst_addr, 0, sizeof dst_addr);
   2831     dst_addr.base_kind = NATIVE_ADDR_BASE_REG;
   2832     dst_addr.base.reg = RV_TMP1;
   2833     dst_addr.base_type = value->type;
   2834     src_addr = rv_storage_addr(a, *value, 0);
   2835     src_addr.base_type = value->type;
   2836     memset(&access, 0, sizeof access);
   2837     access.type = value->type;
   2838     access.size = (u32)cg_type_size(t->c, value->type);
   2839     access.align = native_type_align(t, value->type);
   2840     rv_copy_bytes(t, dst_addr, src_addr, access);
   2841     *out_rets = NULL;
   2842     *out_nrets = 0;
   2843     return;
   2844   }
   2845   if (value && abi && abi->ret.kind == ABI_ARG_DIRECT) {
   2846     u32 ni = 0, nf = 0, p;
   2847     for (p = 0; p < abi->ret.nparts; ++p) {
   2848       const ABIArgPart* part = &abi->ret.parts[p];
   2849       NativeAllocClass cls =
   2850           part->cls == ABI_CLASS_FP ? NATIVE_REG_FP : NATIVE_REG_INT;
   2851       KitCgTypeId pty = rv_part_scalar_type(part);
   2852       Reg rreg = cls == NATIVE_REG_FP ? RV_FA0 + nf++ : RV_A0 + ni++;
   2853       NativeLoc dst = native_loc_reg(pty, cls, rreg);
   2854       /* Returning a memory-homed aggregate through the generic move phase
   2855        * loses ABIArgPart.size while materializing its scalar carrier type (a
   2856        * six-byte part becomes an eight-byte load). Marshal here through the
   2857        * same exact-width boundary as outgoing arguments instead. */
   2858       rv_load_part(t, dst, *value, part->src_offset, part->size);
   2859     }
   2860     *out_rets = NULL;
   2861     *out_nrets = 0;
   2862     return;
   2863   } else if (value) {
   2864     rets[0].src = *value;
   2865     rets[0].dst = native_loc_reg(value->type, NATIVE_REG_INT, RV_A0);
   2866     rets[0].mem = native_mem_for_type(t, value->type, 0);
   2867     nr = 1;
   2868   }
   2869   *out_rets = rets;
   2870   *out_nrets = nr;
   2871 }
   2872 
   2873 static void rv_ret(NativeTarget* t) {
   2874   RvNativeTarget* a = rv_of(t);
   2875   rv_jump(t, a->epilogue_label);
   2876 }
   2877 
   2878 /* ============================ alloca ============================ */
   2879 
   2880 static void rv_alloca(NativeTarget* t, NativeLoc dst, NativeLoc size,
   2881                       u32 align) {
   2882   RvNativeTarget* a = rv_of(t);
   2883   MCEmitter* mc = t->mc;
   2884   u32 rsz = loc_reg(size);
   2885   u32 rd = loc_reg(dst);
   2886   u32 al = align ? align : 16u;
   2887   if (al < 16u) al = 16u;
   2888   /* round up: t0 = (size + (al-1)) & ~(al-1) */
   2889   rv64_emit32(mc, rv_addi(RV_TMP0, rsz, (i32)(al - 1u)));
   2890   rv_emit_load_imm(a->variant, mc, 1, RV_TMP1, -(i64)al);
   2891   rv64_emit32(mc, rv_and(RV_TMP0, RV_TMP0, RV_TMP1));
   2892   rv64_emit32(mc, rv_sub(RV_SP, RV_SP, RV_TMP0));
   2893   a->frame.has_alloca = 1;
   2894   /* dst = sp + max_outgoing (patched in func_end) */
   2895   if (a->npatches == a->patches_cap) {
   2896     u32 cap = a->patches_cap ? a->patches_cap * 2u : 8u;
   2897     RvPatch* nb = arena_zarray(t->c->tu, RvPatch, cap);
   2898     if (a->patches) memcpy(nb, a->patches, sizeof(*nb) * a->npatches);
   2899     a->patches = nb;
   2900     a->patches_cap = cap;
   2901   }
   2902   a->patches[a->npatches].kind = RV_PATCH_ALLOCA;
   2903   a->patches[a->npatches].pos = mc_pos(mc);
   2904   a->patches[a->npatches].dst_reg = rd;
   2905   a->npatches++;
   2906   a->nalloca++;
   2907   rv64_emit32(mc, RV_NOP); /* placeholder for addi dst, sp, max_outgoing */
   2908 }
   2909 
   2910 /* ============================ TLS / bitfield / atomics
   2911  * ============================ */
   2912 
   2913 static void rv_tls_addr_of(NativeTarget* t, NativeLoc dst, ObjSymId sym,
   2914                            i64 addend) {
   2915   MCEmitter* mc = t->mc;
   2916   u32 sec = mc->section_id;
   2917   u32 rd = loc_reg(dst);
   2918   /* Local-Exec only, matching aa64 (aa_tls_addr_of) and x64 (x64_tls_addr_of):
   2919    * kit links the whole module statically, so every _Thread_local symbol is
   2920    * resolved within the image and TPREL is always valid. An Initial-Exec GOT
   2921    * path (R_RV_TLS_GOT_HI20) used to be emitted for extern-via-GOT symbols
   2922    * under -fPIE (the hosted default), but the linker has no layout/apply for
   2923    * that reloc, so it produced a hard "unsupported reloc kind" link failure
   2924    * rather than a working binary. */
   2925   /* lui t0, %tprel_hi(sym); add t0, tp, t0; addi dst, t0, %tprel_lo(sym). */
   2926   {
   2927     u32 hp = mc_pos(mc);
   2928     rv64_emit32(mc, rv_lui(RV_TMP0, 0));
   2929     mc_emit_reloc_at(mc, sec, hp, R_RV_TPREL_HI20, sym, addend, 0, 0);
   2930     rv64_emit32(mc, rv_add(RV_TMP0, RV_TP, RV_TMP0));
   2931     {
   2932       u32 lp = mc_pos(mc);
   2933       rv64_emit32(mc, rv_addi(rd, RV_TMP0, 0));
   2934       mc_emit_reloc_at(mc, sec, lp, R_RV_TPREL_LO12_I, sym, addend, 0, 0);
   2935     }
   2936   }
   2937 }
   2938 static void rv_bitfield_load(NativeTarget* t, NativeLoc dst, NativeAddr ra,
   2939                              BitFieldAccess bf) {
   2940   RvNativeTarget* a = rv_of(t);
   2941   const RiscvVariant* v = a->variant;
   2942   MCEmitter* mc = t->mc;
   2943   u32 storage_bytes = bf.storage.size ? bf.storage.size : 4u;
   2944   u32 rd = loc_reg(dst);
   2945   u32 base;
   2946   i32 off;
   2947   u32 lsb = bf.bit_offset;
   2948   u32 width = bf.bit_width ? bf.bit_width : 1u;
   2949   /* Shift left so the field's MSB lands at the register top (XLEN-1), then
   2950    * shift right to sign/zero extend it down. Shifts are XLEN-wide. */
   2951   u32 sh_left = v->xlen - (lsb + width);
   2952   u32 sh_right = v->xlen - width;
   2953   ra.offset += (i32)bf.storage_offset;
   2954   rv_resolve_mem_addr(a, &ra, &base, &off);
   2955   rv64_emit32(mc, enc_int_load(v, storage_bytes, 0, rd, base, off));
   2956   rv64_emit32(mc, rv_slli(rd, rd, sh_left));
   2957   if (bf.signed_)
   2958     rv64_emit32(mc, rv_srai(rd, rd, sh_right));
   2959   else
   2960     rv64_emit32(mc, rv_srli(rd, rd, sh_right));
   2961 }
   2962 static void rv_bitfield_store(NativeTarget* t, NativeAddr ra, NativeLoc src,
   2963                               BitFieldAccess bf) {
   2964   RvNativeTarget* a = rv_of(t);
   2965   const RiscvVariant* v = a->variant;
   2966   MCEmitter* mc = t->mc;
   2967   u32 storage_bytes = bf.storage.size ? bf.storage.size : 4u;
   2968   u32 src_reg = loc_reg(src);
   2969   u32 base;
   2970   i32 off;
   2971   u32 lsb = bf.bit_offset;
   2972   u32 width = bf.bit_width ? bf.bit_width : 1u;
   2973   u64 ones = width >= 64u ? ~(u64)0 : (((u64)1 << width) - 1u);
   2974   u64 mask_in = ones << lsb;
   2975   ra.offset += (i32)bf.storage_offset;
   2976   /* Resolve the field address; rv_resolve_mem_addr may use RV_TMP0/RV_TMP1, so
   2977    * stabilize the base into RV_TMP1 before consuming the scratch temps. */
   2978   rv_resolve_mem_addr(a, &ra, &base, &off);
   2979   if (base != RV_S0 && base != RV_TMP1) {
   2980     rv_emit_addr_adjust(v, mc, RV_TMP1, base, off);
   2981     base = RV_TMP1;
   2982     off = 0;
   2983   } else if (base == RV_TMP1 && off != 0) {
   2984     rv_emit_addr_adjust(v, mc, RV_TMP1, RV_TMP1, off);
   2985     off = 0;
   2986   }
   2987   /* word in RV_TMP2; merged via RV_TMP0 (clear mask, then shifted src). */
   2988   rv64_emit32(mc, enc_int_load(v, storage_bytes, 0, RV_TMP2, base, off));
   2989   rv_emit_load_imm(v, mc, 1, RV_TMP0, (i64)~mask_in);
   2990   rv64_emit32(mc, rv_and(RV_TMP2, RV_TMP2, RV_TMP0));
   2991   rv_emit_load_imm(v, mc, 1, RV_TMP0, (i64)ones);
   2992   rv64_emit32(mc, rv_and(RV_TMP0, src_reg, RV_TMP0));
   2993   if (lsb) rv64_emit32(mc, rv_slli(RV_TMP0, RV_TMP0, lsb));
   2994   rv64_emit32(mc, rv_or(RV_TMP2, RV_TMP2, RV_TMP0));
   2995   rv64_emit32(mc, enc_int_store(v, storage_bytes, RV_TMP2, base, off));
   2996 }
   2997 static int rv_order_acquire(KitCgMemOrder o) {
   2998   return o == KIT_CG_MO_CONSUME || o == KIT_CG_MO_ACQUIRE ||
   2999          o == KIT_CG_MO_ACQ_REL || o == KIT_CG_MO_SEQ_CST;
   3000 }
   3001 static int rv_order_release(KitCgMemOrder o) {
   3002   return o == KIT_CG_MO_RELEASE || o == KIT_CG_MO_ACQ_REL ||
   3003          o == KIT_CG_MO_SEQ_CST;
   3004 }
   3005 
   3006 /* Materialize the atomic operand address into RV_TMP0 (a bare pointer, since
   3007  * LR/SC and AMO take a base register with no offset) and return it. */
   3008 static u32 rv_atomic_addr_reg(RvNativeTarget* a, NativeAddr addr) {
   3009   NativeLoc dst =
   3010       native_loc_reg(builtin_id(KIT_CG_BUILTIN_I64), NATIVE_REG_INT, RV_TMP0);
   3011   rv_load_addr(&a->base, dst, addr);
   3012   return RV_TMP0;
   3013 }
   3014 
   3015 static void rv_atomic_load(NativeTarget* t, NativeLoc dst, NativeAddr addr,
   3016                            MemAccess mem, KitCgMemOrder mo) {
   3017   RvNativeTarget* a = rv_of(t);
   3018   MCEmitter* mc = t->mc;
   3019   u32 sf = (mem.size ? mem.size : loc_size32(t, dst)) == 8u ? 1u : 0u;
   3020   u32 base = rv_atomic_addr_reg(a, addr);
   3021   if (mo == KIT_CG_MO_SEQ_CST) rv64_emit32(mc, rv_fence_rw_rw());
   3022   if (rv_order_acquire(mo)) {
   3023     /* lr.w/d as an ordered load (aq=1). */
   3024     rv64_emit32(mc, sf ? rv_lr_d(loc_reg(dst), base, 1, 0)
   3025                        : rv_lr_w(loc_reg(dst), base, 1, 0));
   3026   } else {
   3027     rv64_emit32(mc, enc_int_load(a->variant,
   3028                                  mem.size ? mem.size : loc_size32(t, dst), 0,
   3029                                  loc_reg(dst), base, 0));
   3030   }
   3031 }
   3032 
   3033 static void rv_atomic_store(NativeTarget* t, NativeAddr addr, NativeLoc src,
   3034                             MemAccess mem, KitCgMemOrder mo) {
   3035   RvNativeTarget* a = rv_of(t);
   3036   MCEmitter* mc = t->mc;
   3037   u32 sz = mem.size ? mem.size : loc_size32(t, src);
   3038   /* RV_TMP0 holds the address; never collides with src (an allocable reg). */
   3039   u32 base = rv_atomic_addr_reg(a, addr);
   3040   if (rv_order_release(mo)) rv64_emit32(mc, rv_fence_rw_rw());
   3041   rv64_emit32(mc, enc_int_store(a->variant, sz, loc_reg(src), base, 0));
   3042   if (mo == KIT_CG_MO_SEQ_CST) rv64_emit32(mc, rv_fence_rw_rw());
   3043 }
   3044 
   3045 static void rv_atomic_rmw(NativeTarget* t, KitCgAtomicOp op, NativeLoc dst,
   3046                           NativeAddr addr, NativeLoc val, MemAccess mem,
   3047                           KitCgMemOrder mo) {
   3048   RvNativeTarget* a = rv_of(t);
   3049   const RiscvVariant* v = a->variant;
   3050   MCEmitter* mc = t->mc;
   3051   u32 sf = (mem.size ? mem.size : loc_size32(t, dst)) == 8u ? 1u : 0u;
   3052   /* W-form add/sub apply only to a 32-bit value on rv64; on rv32 the base ops
   3053    * are the 32-bit ops. */
   3054   int w = !sf && v->has_w_forms;
   3055   u32 base = rv_atomic_addr_reg(a, addr); /* RV_TMP0 */
   3056   u32 vreg = loc_reg(val);
   3057   u32 rd = loc_reg(dst);
   3058   u32 aq = (u32)rv_order_acquire(mo);
   3059   u32 rl = (u32)rv_order_release(mo);
   3060   MCLabel retry = mc_label_new(mc);
   3061   /* LR/SC loop: dst = *base; new = dst op val; sc new; retry on failure.
   3062    * RV_TMP1 carries the SC status, RV_TMP3 the computed new value. */
   3063   mc_label_place(mc, retry);
   3064   rv64_emit32(mc, sf ? rv_lr_d(rd, base, aq, 0) : rv_lr_w(rd, base, aq, 0));
   3065   switch (op) {
   3066     case KIT_CG_ATOMIC_XCHG:
   3067       rv64_emit32(mc, rv_addi(RV_TMP3, vreg, 0));
   3068       break;
   3069     case KIT_CG_ATOMIC_ADD:
   3070       rv64_emit32(mc,
   3071                   w ? rv_addw(RV_TMP3, rd, vreg) : rv_add(RV_TMP3, rd, vreg));
   3072       break;
   3073     case KIT_CG_ATOMIC_SUB:
   3074       rv64_emit32(mc,
   3075                   w ? rv_subw(RV_TMP3, rd, vreg) : rv_sub(RV_TMP3, rd, vreg));
   3076       break;
   3077     case KIT_CG_ATOMIC_AND:
   3078       rv64_emit32(mc, rv_and(RV_TMP3, rd, vreg));
   3079       break;
   3080     case KIT_CG_ATOMIC_OR:
   3081       rv64_emit32(mc, rv_or(RV_TMP3, rd, vreg));
   3082       break;
   3083     case KIT_CG_ATOMIC_XOR:
   3084       rv64_emit32(mc, rv_xor(RV_TMP3, rd, vreg));
   3085       break;
   3086     case KIT_CG_ATOMIC_NAND:
   3087       rv64_emit32(mc, rv_and(RV_TMP3, rd, vreg));
   3088       rv64_emit32(mc, rv_xori(RV_TMP3, RV_TMP3, -1));
   3089       break;
   3090     default:
   3091       rv_panic(a, "unsupported atomic rmw op");
   3092   }
   3093   rv64_emit32(mc, sf ? rv_sc_d(RV_TMP1, base, RV_TMP3, 0, rl)
   3094                      : rv_sc_w(RV_TMP1, base, RV_TMP3, 0, rl));
   3095   rv64_emit32(mc, rv_bne(RV_TMP1, RV_ZERO, 0));
   3096   mc_emit_label_ref(mc, retry, R_RV_BRANCH, 4, 0);
   3097 }
   3098 
   3099 static void rv_atomic_cas(NativeTarget* t, NativeLoc prior, NativeLoc ok,
   3100                           NativeAddr addr, NativeLoc expected,
   3101                           NativeLoc desired, MemAccess mem,
   3102                           KitCgMemOrder success, KitCgMemOrder failure) {
   3103   RvNativeTarget* a = rv_of(t);
   3104   MCEmitter* mc = t->mc;
   3105   u32 sf = (mem.size ? mem.size : loc_size32(t, prior)) == 8u ? 1u : 0u;
   3106   u32 base = rv_atomic_addr_reg(a, addr); /* RV_TMP0 */
   3107   u32 rprior = loc_reg(prior);
   3108   u32 rexp = loc_reg(expected);
   3109   u32 rdes = loc_reg(desired);
   3110   u32 rok = loc_reg(ok);
   3111   u32 aq = (u32)rv_order_acquire(success);
   3112   u32 rl = (u32)rv_order_release(success);
   3113   MCLabel retry = mc_label_new(mc);
   3114   MCLabel fail = mc_label_new(mc);
   3115   MCLabel done = mc_label_new(mc);
   3116   (void)failure;
   3117   mc_label_place(mc, retry);
   3118   rv64_emit32(mc,
   3119               sf ? rv_lr_d(rprior, base, aq, 0) : rv_lr_w(rprior, base, aq, 0));
   3120   /* if (prior != expected) -> fail */
   3121   rv64_emit32(mc, rv_bne(rprior, rexp, 0));
   3122   mc_emit_label_ref(mc, fail, R_RV_BRANCH, 4, 0);
   3123   /* sc.w/d status, desired, (base); retry on failure. */
   3124   rv64_emit32(mc, sf ? rv_sc_d(RV_TMP1, base, rdes, 0, rl)
   3125                      : rv_sc_w(RV_TMP1, base, rdes, 0, rl));
   3126   rv64_emit32(mc, rv_bne(RV_TMP1, RV_ZERO, 0));
   3127   mc_emit_label_ref(mc, retry, R_RV_BRANCH, 4, 0);
   3128   /* ok = 1; jump done. */
   3129   rv_emit_load_imm(a->variant, mc, 0, rok, 1);
   3130   rv64_emit32(mc, rv_jal(RV_ZERO, 0));
   3131   mc_emit_label_ref(mc, done, R_RV_JAL, 4, 0);
   3132   mc_label_place(mc, fail);
   3133   rv_emit_load_imm(a->variant, mc, 0, rok, 0);
   3134   mc_label_place(mc, done);
   3135 }
   3136 
   3137 static void rv_fence(NativeTarget* t, KitCgMemOrder mo) {
   3138   if (mo == KIT_CG_MO_RELAXED) return;
   3139   rv64_emit32(t->mc, rv_fence_rw_rw());
   3140 }
   3141 /* ---- variadics (LP64D ABI_VA_LIST_POINTER) ----
   3142  * va_list is a single void* to the next argument slot. The prologue spilled
   3143  * unconsumed a-regs into the 64-byte save area at [s0+16); incoming stack args
   3144  * follow contiguously, so a uniform 8-byte stride covers both. `ap` is a
   3145  * NativeAddr that addresses the va_list object itself. */
   3146 
   3147 static void rv_va_start_core(RvNativeTarget* a, NativeAddr ap) {
   3148   NativeTarget* t = &a->base;
   3149   const RiscvVariant* v = a->variant;
   3150   MCEmitter* mc = t->mc;
   3151   ABIVaListInfo vai = abi_va_list_layout(t->c->abi);
   3152   KitCgTypeId i64t = builtin_id(KIT_CG_BUILTIN_I64);
   3153   u32 slot = vai.gp_slot_size ? vai.gp_slot_size : v->gp_slot_bytes;
   3154   if (vai.kind != ABI_VA_LIST_POINTER)
   3155     rv_panic(a, "unsupported va_list layout");
   3156   if (!a->is_variadic) rv_panic(a, "va_start: function not variadic");
   3157   /* *ap = s0 + frame_save + next_param_int*gp_slot (skip named-int slots). */
   3158   rv64_emit32(
   3159       mc, rv_addi(RV_TMP1, RV_S0,
   3160                   (i32)v->frame_save_size + (i32)(a->next_param_int * slot)));
   3161   rv_emit_mem(a, 0, native_loc_reg(i64t, NATIVE_REG_INT, RV_TMP1), ap,
   3162               native_mem_for_type(t, i64t, v->ptr_bytes));
   3163 }
   3164 
   3165 /* Wide / aggregate va_arg: a value too large for a single GPR (an 8-byte
   3166  * i64 / soft-double on ilp32) occupies consecutive GP slots in the save area
   3167  * and cannot move through one register. Read the cursor, advance it past the
   3168  * whole span, then byte-copy the value from the (saved) cursor into the
   3169  * destination memory. RV_TMP2 holds the cursor across the rv_copy_bytes call,
   3170  * which itself uses RV_TMP0/RV_TMP1/RV_TMP3. */
   3171 static void rv_va_arg_wide(RvNativeTarget* a, NativeAddr dst, NativeAddr ap,
   3172                            u32 sz) {
   3173   NativeTarget* t = &a->base;
   3174   const RiscvVariant* v = a->variant;
   3175   MCEmitter* mc = t->mc;
   3176   KitCgTypeId i64t = builtin_id(KIT_CG_BUILTIN_I64);
   3177   ABIVaListInfo vai = abi_va_list_layout(t->c->abi);
   3178   u32 slot = vai.gp_slot_size ? vai.gp_slot_size : v->gp_slot_bytes;
   3179   u32 span = align_up_u32(sz, slot);
   3180   NativeLoc cur = native_loc_reg(i64t, NATIVE_REG_INT, RV_TMP2);
   3181   NativeLoc nxt = native_loc_reg(i64t, NATIVE_REG_INT, RV_TMP1);
   3182   NativeAddr src;
   3183   AggregateAccess acc;
   3184   /* cur = *ap; *ap = cur + span. */
   3185   rv_emit_mem(a, 1, cur, ap, native_mem_for_type(t, i64t, v->ptr_bytes));
   3186   rv64_emit32(mc, rv_addi(RV_TMP1, RV_TMP2, (i32)span));
   3187   rv_emit_mem(a, 0, nxt, ap, native_mem_for_type(t, i64t, v->ptr_bytes));
   3188   /* Copy sz bytes from [cur] to the destination. */
   3189   memset(&src, 0, sizeof src);
   3190   src.base_kind = NATIVE_ADDR_BASE_REG;
   3191   src.base.reg = RV_TMP2;
   3192   src.base_type = i64t;
   3193   memset(&acc, 0, sizeof acc);
   3194   acc.type = i64t;
   3195   acc.size = sz;
   3196   acc.align = slot;
   3197   rv_copy_bytes(t, dst, src, acc);
   3198 }
   3199 
   3200 static void rv_va_arg_core(RvNativeTarget* a, NativeLoc dst, NativeAddr ap,
   3201                            KitCgTypeId type) {
   3202   NativeTarget* t = &a->base;
   3203   const RiscvVariant* v = a->variant;
   3204   MCEmitter* mc = t->mc;
   3205   ABIVaListInfo vai = abi_va_list_layout(t->c->abi);
   3206   KitCgTypeId i64t = builtin_id(KIT_CG_BUILTIN_I64);
   3207   u32 sz = native_type_size(t, type);
   3208   u32 slot = vai.gp_slot_size ? vai.gp_slot_size : v->gp_slot_bytes;
   3209   NativeLoc cur = native_loc_reg(i64t, NATIVE_REG_INT, RV_TMP1);
   3210   NativeAddr from;
   3211   if (vai.kind != ABI_VA_LIST_POINTER)
   3212     rv_panic(a, "unsupported va_list layout");
   3213   if (dst.kind != NATIVE_LOC_REG) rv_panic(a, "va_arg destination must be reg");
   3214   /* cur = *ap; load value from [cur]; *ap = cur + slot (one GP-slot stride). */
   3215   rv_emit_mem(a, 1, cur, ap, native_mem_for_type(t, i64t, v->ptr_bytes));
   3216   memset(&from, 0, sizeof from);
   3217   from.base_kind = NATIVE_ADDR_BASE_REG;
   3218   from.base.reg = RV_TMP1;
   3219   from.base_type = type;
   3220   if (native_loc_is_fp(dst)) {
   3221     /* Variadic FP args sit in the integer save area as their bit pattern;
   3222      * load into RV_TMP2 and bitcast into the FPR. The fmv_d_x (double) path is
   3223      * RV64-only — on rv32 doubles are passed soft and never reach here. */
   3224     NativeLoc itmp = native_loc_reg(type, NATIVE_REG_INT, RV_TMP2);
   3225     rv_emit_mem(a, 1, itmp, from, native_mem_for_type(t, type, sz));
   3226     rv64_emit32(mc, sz == 8u ? rv_fmv_d_x(loc_reg(dst), RV_TMP2)
   3227                              : rv_fmv_w_x(loc_reg(dst), RV_TMP2));
   3228   } else {
   3229     rv_emit_mem(a, 1, dst, from, native_mem_for_type(t, type, sz));
   3230   }
   3231   rv64_emit32(mc, rv_addi(RV_TMP1, RV_TMP1, (i32)slot));
   3232   rv_emit_mem(a, 0, cur, ap, native_mem_for_type(t, i64t, v->ptr_bytes));
   3233 }
   3234 
   3235 static void rv_va_copy_core(RvNativeTarget* a, NativeAddr dst_ap,
   3236                             NativeAddr src_ap) {
   3237   NativeTarget* t = &a->base;
   3238   u32 ptr = a->variant->ptr_bytes;
   3239   KitCgTypeId i64t = builtin_id(KIT_CG_BUILTIN_I64);
   3240   NativeLoc tmp = native_loc_reg(i64t, NATIVE_REG_INT, RV_TMP1);
   3241   /* va_list is a single pointer-width slot. */
   3242   rv_emit_mem(a, 1, tmp, src_ap, native_mem_for_type(t, i64t, ptr));
   3243   rv_emit_mem(a, 0, tmp, dst_ap, native_mem_for_type(t, i64t, ptr));
   3244 }
   3245 
   3246 static NativeAddr rv_va_addr_from_ptr(NativeLoc ap_ptr) {
   3247   NativeAddr addr;
   3248   memset(&addr, 0, sizeof addr);
   3249   addr.base_kind = NATIVE_ADDR_BASE_REG;
   3250   addr.cls = NATIVE_REG_INT;
   3251   addr.base.reg = ap_ptr.v.reg;
   3252   addr.base_type = ap_ptr.type;
   3253   return addr;
   3254 }
   3255 
   3256 static void rv_va_start_native(NativeTarget* t, NativeLoc ap_ptr) {
   3257   rv_va_start_core(rv_of(t), rv_va_addr_from_ptr(ap_ptr));
   3258 }
   3259 /* A scalar whose value cannot move through one GPR (size > GPR width, e.g. an
   3260  * 8-byte i64 / soft-double on ilp32). pass_native_emit hands such a va_arg its
   3261  * memory destination directly rather than a scratch register. */
   3262 static int rv_va_arg_is_wide(NativeTarget* t, KitCgTypeId type) {
   3263   return native_type_size(t, type) > rv_of(t)->variant->ptr_bytes;
   3264 }
   3265 
   3266 static void rv_va_arg_native(NativeTarget* t, NativeLoc dst, NativeLoc ap_ptr,
   3267                              KitCgTypeId type) {
   3268   RvNativeTarget* a = rv_of(t);
   3269   if (rv_va_arg_is_wide(t, type)) {
   3270     rv_va_arg_wide(a, rv_storage_addr(a, dst, 0), rv_va_addr_from_ptr(ap_ptr),
   3271                    native_type_size(t, type));
   3272     return;
   3273   }
   3274   rv_va_arg_core(a, dst, rv_va_addr_from_ptr(ap_ptr), type);
   3275 }
   3276 static void rv_va_end_native(NativeTarget* t, NativeLoc ap_ptr) {
   3277   (void)t;
   3278   (void)ap_ptr;
   3279 }
   3280 static void rv_va_copy_native(NativeTarget* t, NativeLoc dst, NativeLoc src) {
   3281   rv_va_copy_core(rv_of(t), rv_va_addr_from_ptr(dst), rv_va_addr_from_ptr(src));
   3282 }
   3283 /* Software popcount of RV_TMP1 (already width-normalized) into rd, using
   3284  * RV_TMP1/RV_TMP2/RV_TMP3 as scratch. Mirrors the legacy bit-twiddling. On rv32
   3285  * only the 32-bit (is64==0) path is reachable for a single register. */
   3286 static void rv_emit_popcount(const RiscvVariant* v, MCEmitter* mc, u32 rd,
   3287                              int is64) {
   3288   rv64_emit32(mc, rv_srli(RV_TMP2, RV_TMP1, 1));
   3289   rv_emit_load_imm(v, mc, 1, RV_TMP3,
   3290                    is64 ? (i64)0x5555555555555555ll : (i64)0x55555555);
   3291   rv64_emit32(mc, rv_and(RV_TMP2, RV_TMP2, RV_TMP3));
   3292   rv64_emit32(mc, rv_sub(RV_TMP1, RV_TMP1, RV_TMP2));
   3293   rv_emit_load_imm(v, mc, 1, RV_TMP3,
   3294                    is64 ? (i64)0x3333333333333333ll : (i64)0x33333333);
   3295   rv64_emit32(mc, rv_and(RV_TMP2, RV_TMP1, RV_TMP3));
   3296   rv64_emit32(mc, rv_srli(RV_TMP1, RV_TMP1, 2));
   3297   rv64_emit32(mc, rv_and(RV_TMP1, RV_TMP1, RV_TMP3));
   3298   rv64_emit32(mc, rv_add(RV_TMP1, RV_TMP1, RV_TMP2));
   3299   rv64_emit32(mc, rv_srli(RV_TMP2, RV_TMP1, 4));
   3300   rv64_emit32(mc, rv_add(RV_TMP1, RV_TMP1, RV_TMP2));
   3301   rv_emit_load_imm(v, mc, 1, RV_TMP3,
   3302                    is64 ? (i64)0x0f0f0f0f0f0f0f0fll : (i64)0x0f0f0f0f);
   3303   rv64_emit32(mc, rv_and(RV_TMP1, RV_TMP1, RV_TMP3));
   3304   rv_emit_load_imm(v, mc, 1, RV_TMP3,
   3305                    is64 ? (i64)0x0101010101010101ll : (i64)0x01010101);
   3306   rv64_emit32(mc, rv_mul(RV_TMP1, RV_TMP1, RV_TMP3));
   3307   rv64_emit32(mc, rv_srli(rd, RV_TMP1, is64 ? 56u : 24u));
   3308   /* The 32-bit SWAR sum lives in product bits [24,32); since the multiply is
   3309    * 64-bit, bits [32,64) survive the >>24 and must be masked off. (The 64-bit
   3310    * path's >>56 already isolates the top byte, so it needs no mask.) */
   3311   if (!is64) rv64_emit32(mc, rv_andi(rd, rd, 0xff));
   3312 }
   3313 
   3314 /* Inline byte-granule copy/set between bare base registers (memcpy/memmove/
   3315  * memset intrinsics). dir<0 copies high-to-low (memmove backward). The 8-byte
   3316  * granule (ld/sd) and zero-extending lwu are RV64-only; on rv32 the widest
   3317  * granule is 4 bytes via lw/sw. */
   3318 static void rv_intrin_copy(const RiscvVariant* v, MCEmitter* mc, u32 dr, u32 sr,
   3319                            u32 n, int backward) {
   3320   int wide = v->ptr_bytes == 8u;
   3321   if (!backward) {
   3322     u32 i = 0;
   3323     while (wide && i + 8u <= n) {
   3324       rv64_emit32(mc, rv_ld(RV_TMP3, sr, (i32)i));
   3325       rv64_emit32(mc, rv_sd(RV_TMP3, dr, (i32)i));
   3326       i += 8u;
   3327     }
   3328     while (i + 4u <= n) {
   3329       rv64_emit32(
   3330           mc, wide ? rv_lwu(RV_TMP3, sr, (i32)i) : rv_lw(RV_TMP3, sr, (i32)i));
   3331       rv64_emit32(mc, rv_sw(RV_TMP3, dr, (i32)i));
   3332       i += 4u;
   3333     }
   3334     while (i + 2u <= n) {
   3335       rv64_emit32(mc, rv_lhu(RV_TMP3, sr, (i32)i));
   3336       rv64_emit32(mc, rv_sh(RV_TMP3, dr, (i32)i));
   3337       i += 2u;
   3338     }
   3339     while (i < n) {
   3340       rv64_emit32(mc, rv_lbu(RV_TMP3, sr, (i32)i));
   3341       rv64_emit32(mc, rv_sb(RV_TMP3, dr, (i32)i));
   3342       i += 1u;
   3343     }
   3344   } else {
   3345     u32 i = n;
   3346     while (wide && i >= 8u) {
   3347       i -= 8u;
   3348       rv64_emit32(mc, rv_ld(RV_TMP3, sr, (i32)i));
   3349       rv64_emit32(mc, rv_sd(RV_TMP3, dr, (i32)i));
   3350     }
   3351     while (i >= 4u) {
   3352       i -= 4u;
   3353       rv64_emit32(
   3354           mc, wide ? rv_lwu(RV_TMP3, sr, (i32)i) : rv_lw(RV_TMP3, sr, (i32)i));
   3355       rv64_emit32(mc, rv_sw(RV_TMP3, dr, (i32)i));
   3356     }
   3357     while (i >= 2u) {
   3358       i -= 2u;
   3359       rv64_emit32(mc, rv_lhu(RV_TMP3, sr, (i32)i));
   3360       rv64_emit32(mc, rv_sh(RV_TMP3, dr, (i32)i));
   3361     }
   3362     while (i >= 1u) {
   3363       i -= 1u;
   3364       rv64_emit32(mc, rv_lbu(RV_TMP3, sr, (i32)i));
   3365       rv64_emit32(mc, rv_sb(RV_TMP3, dr, (i32)i));
   3366     }
   3367   }
   3368 }
   3369 
   3370 static void rv_intrinsic(NativeTarget* t, IntrinKind kind,
   3371                          const NativeLoc* dsts, u32 ndst, const NativeLoc* args,
   3372                          u32 narg) {
   3373   RvNativeTarget* a = rv_of(t);
   3374   const RiscvVariant* v = a->variant;
   3375   MCEmitter* mc = t->mc;
   3376   (void)ndst;
   3377   (void)narg;
   3378   switch (kind) {
   3379     case INTRIN_NONE:
   3380       break;
   3381     case INTRIN_EXPECT:
   3382     case INTRIN_ASSUME_ALIGNED: {
   3383       /* dst = val (hint dropped). */
   3384       if (args[0].kind == NATIVE_LOC_IMM)
   3385         rv_emit_load_imm(v, mc, rv_is_64(t, dsts[0].type) ? 1u : 0u,
   3386                          loc_reg(dsts[0]), args[0].v.imm);
   3387       else
   3388         rv_move(t, dsts[0], args[0]);
   3389       return;
   3390     }
   3391     case INTRIN_PREFETCH:
   3392       return;
   3393     case INTRIN_TRAP:
   3394       rv64_emit32(mc, rv_ebreak());
   3395       return;
   3396     case INTRIN_READCYCLECOUNTER:
   3397       /* RDCYCLE rd = csrrs rd, cycle, x0 — reads the 64-bit cycle CSR on rv64
   3398        * (rv32 is gated out in rv64_supports_intrinsic). */
   3399       rv64_emit32(mc, rv_csrrs(loc_reg(dsts[0]), RV_CSR_CYCLE, RV_ZERO));
   3400       return;
   3401     case INTRIN_SYSCALL:
   3402       if (ndst == 1u && narg >= 1u && narg <= 7u) {
   3403         static const u32 syscall_regs[7] = {RV_A7, RV_A0, RV_A1, RV_A2,
   3404                                             RV_A3, RV_A4, RV_A5};
   3405         RvArgMove moves[7];
   3406         for (u32 i = 0; i < narg; ++i) {
   3407           RvArgMove* m = &moves[i];
   3408           memset(m, 0, sizeof *m);
   3409           m->dst =
   3410               native_loc_reg(dsts[0].type, NATIVE_REG_INT, syscall_regs[i]);
   3411           m->src = args[i];
   3412           m->size = t->c->target.ptr_size;
   3413         }
   3414         rv_emit_reg_arg_moves(t, moves, narg);
   3415         rv64_emit32(mc, rv_ecall());
   3416         rv_move(t, dsts[0],
   3417                 native_loc_reg(dsts[0].type, NATIVE_REG_INT, RV_A0));
   3418       }
   3419       return;
   3420     case INTRIN_BSWAP: {
   3421       u32 width = abi_cg_sizeof(t->c->abi, dsts[0].type);
   3422       switch (width) {
   3423         case 2: {
   3424           u32 rd = loc_reg(dsts[0]), rs = loc_reg(args[0]);
   3425           /* rd = ((rs & 0xff) << 8) | ((rs >> 8) & 0xff). */
   3426           rv64_emit32(mc, rv_addi(RV_TMP2, RV_ZERO, 0xff));
   3427           rv64_emit32(mc, rv_slli(RV_TMP2, RV_TMP2, 8)); /* 0xff00 */
   3428           rv64_emit32(mc, rv_slli(RV_TMP1, rs, 8));
   3429           rv64_emit32(mc, rv_and(RV_TMP1, RV_TMP1, RV_TMP2));
   3430           rv64_emit32(mc, rv_srli(RV_TMP3, rs, 8));
   3431           rv64_emit32(mc, rv_andi(RV_TMP3, RV_TMP3, 0xff));
   3432           rv64_emit32(mc, rv_or(rd, RV_TMP1, RV_TMP3));
   3433           return;
   3434         }
   3435         case 4: {
   3436           u32 rd = loc_reg(dsts[0]), rs = loc_reg(args[0]);
   3437           /* SRLIW is RV64-only; on rv32 SRLI on a 32-bit reg is equivalent. */
   3438           int w = v->has_w_forms;
   3439           rv64_emit32(mc,
   3440                       w ? rv_srliw(RV_TMP1, rs, 24) : rv_srli(RV_TMP1, rs, 24));
   3441           rv64_emit32(mc, rv_andi(RV_TMP1, RV_TMP1, 0xff));
   3442           rv64_emit32(mc,
   3443                       w ? rv_srliw(RV_TMP2, rs, 16) : rv_srli(RV_TMP2, rs, 16));
   3444           rv64_emit32(mc, rv_andi(RV_TMP2, RV_TMP2, 0xff));
   3445           rv64_emit32(mc, rv_slli(RV_TMP2, RV_TMP2, 8));
   3446           rv64_emit32(mc, rv_or(RV_TMP1, RV_TMP1, RV_TMP2));
   3447           rv64_emit32(mc,
   3448                       w ? rv_srliw(RV_TMP2, rs, 8) : rv_srli(RV_TMP2, rs, 8));
   3449           rv64_emit32(mc, rv_andi(RV_TMP2, RV_TMP2, 0xff));
   3450           rv64_emit32(mc, rv_slli(RV_TMP2, RV_TMP2, 16));
   3451           rv64_emit32(mc, rv_or(RV_TMP1, RV_TMP1, RV_TMP2));
   3452           rv64_emit32(mc, rv_andi(RV_TMP2, rs, 0xff));
   3453           rv64_emit32(mc, rv_slli(RV_TMP2, RV_TMP2, 24));
   3454           rv64_emit32(mc, rv_or(rd, RV_TMP1, RV_TMP2));
   3455           /* Canonicalize to a 32-bit value in a 64-bit reg (RV64 only); on rv32
   3456            * the result already occupies the whole register. */
   3457           if (w) {
   3458             rv64_emit32(mc, rv_slli(rd, rd, 32));
   3459             rv64_emit32(mc, rv_srli(rd, rd, 32));
   3460           }
   3461           return;
   3462         }
   3463         case 8: {
   3464           u32 rd = loc_reg(dsts[0]), rs = loc_reg(args[0]);
   3465           int i;
   3466           rv64_emit32(mc, rv_addi(RV_TMP1, RV_ZERO, 0));
   3467           for (i = 0; i < 8; ++i) {
   3468             int sh = 56 - 8 * i;
   3469             if (i == 0) {
   3470               rv64_emit32(mc, rv_andi(RV_TMP2, rs, 0xff));
   3471             } else {
   3472               rv64_emit32(mc, rv_srli(RV_TMP2, rs, (u32)(8 * i)));
   3473               rv64_emit32(mc, rv_andi(RV_TMP2, RV_TMP2, 0xff));
   3474             }
   3475             if (sh) rv64_emit32(mc, rv_slli(RV_TMP2, RV_TMP2, (u32)sh));
   3476             rv64_emit32(mc, rv_or(RV_TMP1, RV_TMP1, RV_TMP2));
   3477           }
   3478           rv64_emit32(mc, rv_addi(rd, RV_TMP1, 0));
   3479           return;
   3480         }
   3481         default:
   3482           break;
   3483       }
   3484       return;
   3485     }
   3486     case INTRIN_POPCOUNT: {
   3487       u32 rd = loc_reg(dsts[0]), rs = loc_reg(args[0]);
   3488       int is64 = rv_is_64(t, args[0].type);
   3489       /* The narrow-in-wide normalization clears the high 32 bits of a 64-bit
   3490        * reg; on rv32 there are none, so it is skipped. */
   3491       int nrm = !is64 && v->xlen == 64u;
   3492       rv64_emit32(mc, rv_addi(RV_TMP1, rs, 0));
   3493       if (nrm) {
   3494         rv64_emit32(mc, rv_slli(RV_TMP1, RV_TMP1, 32));
   3495         rv64_emit32(mc, rv_srli(RV_TMP1, RV_TMP1, 32));
   3496       }
   3497       rv_emit_popcount(v, mc, rd, is64);
   3498       return;
   3499     }
   3500     case INTRIN_CTZ: {
   3501       /* ctz(x) = popcount((x & -x) - 1) for x != 0. */
   3502       u32 rd = loc_reg(dsts[0]), rs = loc_reg(args[0]);
   3503       int is64 = rv_is_64(t, args[0].type);
   3504       int nrm = !is64 && v->xlen == 64u;
   3505       rv64_emit32(mc, rv_sub(RV_TMP1, RV_ZERO, rs));
   3506       rv64_emit32(mc, rv_and(RV_TMP1, RV_TMP1, rs));
   3507       rv64_emit32(mc, rv_addi(RV_TMP1, RV_TMP1, -1));
   3508       if (nrm) {
   3509         rv64_emit32(mc, rv_slli(RV_TMP1, RV_TMP1, 32));
   3510         rv64_emit32(mc, rv_srli(RV_TMP1, RV_TMP1, 32));
   3511       }
   3512       rv_emit_popcount(v, mc, rd, is64);
   3513       return;
   3514     }
   3515     case INTRIN_CLZ: {
   3516       /* Fold the high bit downward, then clz = popcount(~folded). */
   3517       u32 rd = loc_reg(dsts[0]), rs = loc_reg(args[0]);
   3518       int is64 = rv_is_64(t, args[0].type);
   3519       int nrm = !is64 && v->xlen == 64u;
   3520       u32 shifts[6] = {1, 2, 4, 8, 16, 32};
   3521       u32 ns = is64 ? 6u : 5u, i;
   3522       rv64_emit32(mc, rv_addi(RV_TMP1, rs, 0));
   3523       if (nrm) {
   3524         rv64_emit32(mc, rv_slli(RV_TMP1, RV_TMP1, 32));
   3525         rv64_emit32(mc, rv_srli(RV_TMP1, RV_TMP1, 32));
   3526       }
   3527       for (i = 0; i < ns; ++i) {
   3528         rv64_emit32(mc, rv_srli(RV_TMP2, RV_TMP1, shifts[i]));
   3529         rv64_emit32(mc, rv_or(RV_TMP1, RV_TMP1, RV_TMP2));
   3530       }
   3531       rv64_emit32(mc, rv_xori(RV_TMP1, RV_TMP1, -1));
   3532       if (nrm) {
   3533         rv64_emit32(mc, rv_slli(RV_TMP1, RV_TMP1, 32));
   3534         rv64_emit32(mc, rv_srli(RV_TMP1, RV_TMP1, 32));
   3535       }
   3536       rv_emit_popcount(v, mc, rd, is64);
   3537       return;
   3538     }
   3539     case INTRIN_SADD_OVERFLOW:
   3540     case INTRIN_SSUB_OVERFLOW: {
   3541       /* dsts: [val, ovf]. ADD: ovf=((a^r)&(b^r))>>(w-1);
   3542        * SUB: ovf=((a^b)&(a^r))>>(w-1). */
   3543       int is64 = rv_is_64(t, dsts[0].type);
   3544       int w = !is64 && v->has_w_forms; /* narrow op on rv64 -> W-form */
   3545       u32 ra = loc_reg(args[0]), rb = loc_reg(args[1]);
   3546       u32 rd = loc_reg(dsts[0]), rovf = loc_reg(dsts[1]);
   3547       u32 sh = is64 ? 63u : 31u;
   3548       if (kind == INTRIN_SADD_OVERFLOW)
   3549         rv64_emit32(mc, w ? rv_addw(RV_TMP2, ra, rb) : rv_add(RV_TMP2, ra, rb));
   3550       else
   3551         rv64_emit32(mc, w ? rv_subw(RV_TMP2, ra, rb) : rv_sub(RV_TMP2, ra, rb));
   3552       rv64_emit32(mc, rv_xor(RV_TMP3, ra, RV_TMP2)); /* a ^ r */
   3553       if (kind == INTRIN_SADD_OVERFLOW) {
   3554         rv64_emit32(mc, rv_xor(rovf, rb, RV_TMP2)); /* b ^ r */
   3555         rv64_emit32(mc, rv_and(rovf, rovf, RV_TMP3));
   3556       } else {
   3557         rv64_emit32(mc, rv_xor(rovf, ra, rb)); /* a ^ b */
   3558         rv64_emit32(mc, rv_and(rovf, rovf, RV_TMP3));
   3559       }
   3560       rv64_emit32(mc, w ? rv_srliw(rovf, rovf, sh) : rv_srli(rovf, rovf, sh));
   3561       rv64_emit32(mc, rv_andi(rovf, rovf, 1));
   3562       rv64_emit32(mc, rv_addi(rd, RV_TMP2, 0));
   3563       return;
   3564     }
   3565     case INTRIN_UADD_OVERFLOW:
   3566     case INTRIN_USUB_OVERFLOW: {
   3567       int is64 = rv_is_64(t, dsts[0].type);
   3568       /* `single`: the value fills the whole native register (rv64 i64 or any
   3569        * rv32 value), so the native carry/borrow sequence applies directly; the
   3570        * `!single` branch is the rv64 32-bit-in-64-bit-register implementation
   3571        * (zero-extend + srli-32), reachable only on rv64. */
   3572       int single = is64 || v->xlen == 32u;
   3573       u32 ra = loc_reg(args[0]), rb = loc_reg(args[1]);
   3574       u32 rd = loc_reg(dsts[0]), rovf = loc_reg(dsts[1]);
   3575       if (!single) {
   3576         rv64_emit32(mc, rv_slli(RV_TMP2, ra, 32));
   3577         rv64_emit32(mc, rv_srli(RV_TMP2, RV_TMP2, 32));
   3578         rv64_emit32(mc, rv_slli(RV_TMP3, rb, 32));
   3579         rv64_emit32(mc, rv_srli(RV_TMP3, RV_TMP3, 32));
   3580         ra = RV_TMP2;
   3581         rb = RV_TMP3;
   3582       }
   3583       if (kind == INTRIN_UADD_OVERFLOW) {
   3584         if (single) {
   3585           rv64_emit32(mc, rv_add(RV_TMP2, ra, rb));
   3586           rv64_emit32(mc, rv_sltu(rovf, RV_TMP2, ra));
   3587         } else {
   3588           rv64_emit32(mc, rv_add(RV_TMP2, ra, rb));
   3589           rv64_emit32(mc, rv_srli(rovf, RV_TMP2, 32));
   3590           rv64_emit32(mc, rv_sltu(rovf, RV_ZERO, rovf));
   3591           rv64_emit32(mc, rv_addiw(RV_TMP2, RV_TMP2, 0));
   3592         }
   3593       } else {
   3594         rv64_emit32(mc, rv_sltu(rovf, ra, rb));
   3595         rv64_emit32(
   3596             mc, single ? rv_sub(RV_TMP2, ra, rb) : rv_subw(RV_TMP2, ra, rb));
   3597       }
   3598       rv64_emit32(mc, rv_addi(rd, RV_TMP2, 0));
   3599       return;
   3600     }
   3601     case INTRIN_SMUL_OVERFLOW: {
   3602       int is64 = rv_is_64(t, dsts[0].type);
   3603       /* `single`: native-width product overflow via MUL + MULH and a sign-bit
   3604        * compare (shift xlen-1). rv64 i64 and any rv32 value take this path; the
   3605        * `!single` branch is the rv64 32-bit-in-64-bit-register sequence. */
   3606       int single = is64 || v->xlen == 32u;
   3607       u32 sh = is64 ? 63u : 31u;
   3608       u32 ra = loc_reg(args[0]), rb = loc_reg(args[1]);
   3609       u32 rd = loc_reg(dsts[0]), rovf = loc_reg(dsts[1]);
   3610       if (single) {
   3611         rv64_emit32(mc, rv_mul(RV_TMP2, ra, rb));
   3612         rv64_emit32(mc, rv_mulh(RV_TMP3, ra, rb));
   3613         rv64_emit32(mc, rv_srai(rovf, RV_TMP2, sh));
   3614         rv64_emit32(mc, rv_xor(rovf, RV_TMP3, rovf));
   3615         rv64_emit32(mc, rv_sltu(rovf, RV_ZERO, rovf));
   3616         rv64_emit32(mc, rv_addi(rd, RV_TMP2, 0));
   3617       } else {
   3618         rv64_emit32(mc, rv_addiw(RV_TMP2, ra, 0));
   3619         rv64_emit32(mc, rv_addiw(RV_TMP3, rb, 0));
   3620         rv64_emit32(mc, rv_mul(RV_TMP2, RV_TMP2, RV_TMP3));
   3621         rv64_emit32(mc, rv_addiw(RV_TMP3, RV_TMP2, 0));
   3622         rv64_emit32(mc, rv_xor(rovf, RV_TMP2, RV_TMP3));
   3623         rv64_emit32(mc, rv_sltu(rovf, RV_ZERO, rovf));
   3624         rv64_emit32(mc, rv_addiw(rd, RV_TMP2, 0));
   3625       }
   3626       return;
   3627     }
   3628     case INTRIN_UMUL_OVERFLOW: {
   3629       int is64 = rv_is_64(t, dsts[0].type);
   3630       /* `single`: native-width product, overflow = (high word != 0) via MULHU.
   3631        * rv64 i64 and any rv32 value take this path; `!single` is the rv64
   3632        * 32-bit-in-64-bit-register sequence. */
   3633       int single = is64 || v->xlen == 32u;
   3634       u32 ra = loc_reg(args[0]), rb = loc_reg(args[1]);
   3635       u32 rd = loc_reg(dsts[0]), rovf = loc_reg(dsts[1]);
   3636       if (single) {
   3637         rv64_emit32(mc, rv_mulhu(rovf, ra, rb));
   3638         rv64_emit32(mc, rv_mul(rd, ra, rb));
   3639         rv64_emit32(mc, rv_sltu(rovf, RV_ZERO, rovf));
   3640       } else {
   3641         rv64_emit32(mc, rv_slli(RV_TMP2, ra, 32));
   3642         rv64_emit32(mc, rv_srli(RV_TMP2, RV_TMP2, 32));
   3643         rv64_emit32(mc, rv_slli(RV_TMP3, rb, 32));
   3644         rv64_emit32(mc, rv_srli(RV_TMP3, RV_TMP3, 32));
   3645         rv64_emit32(mc, rv_mul(RV_TMP2, RV_TMP2, RV_TMP3));
   3646         rv64_emit32(mc, rv_srli(rovf, RV_TMP2, 32));
   3647         rv64_emit32(mc, rv_sltu(rovf, RV_ZERO, rovf));
   3648         rv64_emit32(mc, rv_addiw(rd, RV_TMP2, 0));
   3649       }
   3650       return;
   3651     }
   3652     case INTRIN_SMUL_HIGH:
   3653     case INTRIN_UMUL_HIGH: {
   3654       int is64 = rv_is_64(t, dsts[0].type);
   3655       int single = is64 || v->xlen == 32u;
   3656       u32 ra = loc_reg(args[0]), rb = loc_reg(args[1]);
   3657       u32 rd = loc_reg(dsts[0]);
   3658       if (single) {
   3659         rv64_emit32(mc, kind == INTRIN_SMUL_HIGH ? rv_mulh(rd, ra, rb)
   3660                                                  : rv_mulhu(rd, ra, rb));
   3661       } else if (kind == INTRIN_SMUL_HIGH) {
   3662         rv64_emit32(mc, rv_addiw(RV_TMP2, ra, 0));
   3663         rv64_emit32(mc, rv_addiw(RV_TMP3, rb, 0));
   3664         rv64_emit32(mc, rv_mul(RV_TMP2, RV_TMP2, RV_TMP3));
   3665         rv64_emit32(mc, rv_srai(rd, RV_TMP2, 32));
   3666       } else {
   3667         rv64_emit32(mc, rv_slli(RV_TMP2, ra, 32));
   3668         rv64_emit32(mc, rv_srli(RV_TMP2, RV_TMP2, 32));
   3669         rv64_emit32(mc, rv_slli(RV_TMP3, rb, 32));
   3670         rv64_emit32(mc, rv_srli(RV_TMP3, RV_TMP3, 32));
   3671         rv64_emit32(mc, rv_mul(RV_TMP2, RV_TMP2, RV_TMP3));
   3672         rv64_emit32(mc, rv_srli(rd, RV_TMP2, 32));
   3673       }
   3674       return;
   3675     }
   3676     case INTRIN_MEMMOVE: {
   3677       u32 dr, sr, n;
   3678       if (narg != 3u || args[0].kind != NATIVE_LOC_REG ||
   3679           args[1].kind != NATIVE_LOC_REG || args[2].kind != NATIVE_LOC_IMM)
   3680         rv_panic(a, "unsupported memory intrinsic operands");
   3681       if (args[2].v.imm < 0 || args[2].v.imm > 0xffffffffll)
   3682         rv_panic(a, "unsupported memory intrinsic size");
   3683       dr = loc_reg(args[0]);
   3684       sr = loc_reg(args[1]);
   3685       n = (u32)args[2].v.imm;
   3686       rv_intrin_copy(v, mc, dr, sr, n, /*reverse (overlap-safe)=*/1);
   3687       return;
   3688     }
   3689     case INTRIN_CPU_NOP:
   3690       rv64_emit32(mc, rv_nop());
   3691       return;
   3692     case INTRIN_CPU_YIELD:
   3693       rv64_emit32(mc, rv_pause());
   3694       return;
   3695     case INTRIN_ISB:
   3696       rv64_emit32(mc, rv_fence_i());
   3697       return;
   3698     case INTRIN_DMB:
   3699     case INTRIN_DSB:
   3700       rv64_emit32(mc, rv_fence_rw_rw());
   3701       return;
   3702     case INTRIN_WFI:
   3703       rv64_emit32(mc, rv_wfi());
   3704       return;
   3705     case INTRIN_FRAME_ADDRESS:
   3706     case INTRIN_RETURN_ADDRESS:
   3707       /* Walk the s0 frame-record chain. kit's RISC-V prologue anchors s0 at the
   3708        * saved pair: [s0] = caller's s0, [s0 + ptr_bytes] = saved ra (this
   3709        * frame's return address). NOTE: this differs from the psABI's
   3710        * ra@s0-8 / fp@s0-16 layout — kit stores the pair at and above s0. A
   3711        * function that reads its frame is forced off the frameless-leaf tier
   3712        * (see NativeKnownFrameDesc.reads_frame), so s0 is always valid here. The
   3713        * level is constant, so the walk unrolls to `level` dependent loads. */
   3714       if (ndst == 1u) {
   3715         u32 level = (narg >= 1u && args[0].kind == NATIVE_LOC_IMM)
   3716                         ? (u32)args[0].v.imm
   3717                         : 0u;
   3718         u32 rd = loc_reg(dsts[0]);
   3719         rv64_emit32(mc, rv_addi(rd, RV_S0, 0)); /* rd = s0 */
   3720         for (u32 i = 0; i < level; ++i)
   3721           rv64_emit32(mc, rv_ld_ptr(v, rd, rd, 0)); /* rd = *(rd) */
   3722         if (kind == INTRIN_RETURN_ADDRESS)
   3723           rv64_emit32(mc, rv_ld_ptr(v, rd, rd, (i32)v->ptr_bytes));
   3724       }
   3725       return;
   3726     default:
   3727       break;
   3728   }
   3729   rv_panic(a, "unsupported compiler intrinsic");
   3730 }
   3731 
   3732 /* Optimized-hook fixed effects. RISC-V arithmetic, bitfields, aggregate
   3733  * memory operations, atomics, varargs and Local-Exec TLS use only declared
   3734  * operands/results plus the reserved t0..t3 / ft0..ft3 backend temporaries.
   3735  * A syscall is the one exception: argument placement writes the Linux syscall
   3736  * ABI registers even though the semantic IR operands are ordinary virtual
   3737  * values. Keep live-across allocations out of those fixed destinations. */
   3738 static int rv_machine_op_clobbers(NativeTarget* t, const NativeMachineOp* op,
   3739                                   u32 mask[NATIVE_REG_CLASS_COUNT]) {
   3740   mask[0] = mask[1] = mask[2] = 0;
   3741   switch ((NativeMachineOpKind)op->kind) {
   3742     case NATIVE_MOP_INTRINSIC:
   3743       if ((IntrinKind)op->intrin != INTRIN_SYSCALL) return 0;
   3744       /* a7 = syscall number; a0..a5 = arguments / a0 result. */
   3745       mask[NATIVE_REG_INT] = (1u << RV_A0) | (1u << RV_A1) | (1u << RV_A2) |
   3746                              (1u << RV_A3) | (1u << RV_A4) | (1u << RV_A5) |
   3747                              (1u << RV_A7);
   3748       return 1;
   3749     case NATIVE_MOP_BINOP:
   3750     case NATIVE_MOP_BITFIELD_LOAD:
   3751     case NATIVE_MOP_BITFIELD_STORE:
   3752     case NATIVE_MOP_VA_START:
   3753     case NATIVE_MOP_VA_ARG:
   3754     case NATIVE_MOP_ATOMIC_CAS:
   3755     case NATIVE_MOP_ATOMIC_RMW:
   3756     case NATIVE_MOP_TLS_ADDR:
   3757       return 0;
   3758     case NATIVE_MOP_COUNT:
   3759       break;
   3760   }
   3761   rv_panic(rv_of(t), "invalid machine-effect operation");
   3762   return 0;
   3763 }
   3764 
   3765 /* ============================ inline asm ============================ */
   3766 
   3767 _Noreturn static void rv_asm_panic_at(Compiler* c, SrcLoc loc,
   3768                                       const char* msg) {
   3769   compiler_panic(c, loc, "rv64 inline asm: %s", msg);
   3770 }
   3771 _Noreturn static void rv_asm_panic(NativeDirectTarget* d, const char* msg) {
   3772   rv_asm_panic_at(d->base.c, d->loc, msg);
   3773 }
   3774 
   3775 /* constraint_body / constraint_early / match_index are shared
   3776  * (cg/native_asm.h). */
   3777 
   3778 /* Build a bound register pseudo-operand in the rv64 inline shape. */
   3779 static void rv_asm_bound_reg(Operand* out, KitCgTypeId type,
   3780                              NativeAllocClass cls, Reg reg) {
   3781   memset(out, 0, sizeof *out);
   3782   out->kind = RV64_INLINE_OPK_REG;
   3783   out->pad[0] =
   3784       (cls == NATIVE_REG_FP) ? RV64_INLINE_OPCLS_FP : RV64_INLINE_OPCLS_INT;
   3785   out->type = type;
   3786   out->v.local = (CGLocal)reg;
   3787 }
   3788 static void rv_asm_bound_mem(Operand* out, KitCgTypeId type, Reg base) {
   3789   memset(out, 0, sizeof *out);
   3790   out->kind = OPK_INDIRECT;
   3791   out->type = type;
   3792   out->v.ind.base = (CGLocal)base;
   3793   out->v.ind.index = CG_LOCAL_NONE;
   3794   out->v.ind.ofs = 0;
   3795 }
   3796 
   3797 /* Parse a clobber register name into (class, reg). Returns 0 for the special
   3798  * "cc"/"memory" clobbers and panics on an unknown register. RV64 dwarf: int
   3799  * x0..x31 = 0..31, fp f0..f31 = 32..63. */
   3800 static int rv_asm_parse_reg_clobber(Compiler* c, SrcLoc loc, Sym name,
   3801                                     NativeAllocClass* cls_out, Reg* reg_out) {
   3802   Slice s = pool_slice(c->global, name);
   3803   char buf[16];
   3804   uint32_t dwarf;
   3805   if (!s.s || !s.len) return 0;
   3806   if (s.len == 2 && s.s[0] == 'c' && s.s[1] == 'c') return 0;
   3807   if (s.len == 6 && memcmp(s.s, "memory", 6) == 0) return 0;
   3808   if (s.len >= sizeof buf) rv_asm_panic_at(c, loc, "clobber name is too long");
   3809   memcpy(buf, s.s, s.len);
   3810   buf[s.len] = '\0';
   3811   if (rv64_register_index(buf, &dwarf) != 0)
   3812     rv_asm_panic_at(c, loc, "unknown clobber register");
   3813   if (dwarf <= 31u) {
   3814     *cls_out = NATIVE_REG_INT;
   3815     *reg_out = (Reg)dwarf;
   3816     return 1;
   3817   }
   3818   if (dwarf >= 32u && dwarf <= 63u) {
   3819     *cls_out = NATIVE_REG_FP;
   3820     *reg_out = (Reg)(dwarf - 32u);
   3821     return 1;
   3822   }
   3823   rv_asm_panic_at(c, loc, "unsupported clobber register");
   3824   return 0;
   3825 }
   3826 
   3827 static void rv_asm_clobber_masks(Compiler* c, SrcLoc loc, const Sym* clobbers,
   3828                                  u32 nclob, u32* int_mask, u32* fp_mask) {
   3829   u32 i;
   3830   *int_mask = 0;
   3831   *fp_mask = 0;
   3832   for (i = 0; i < nclob; ++i) {
   3833     NativeAllocClass cls;
   3834     Reg reg;
   3835     if (!rv_asm_parse_reg_clobber(c, loc, clobbers[i], &cls, &reg)) continue;
   3836     if (cls == NATIVE_REG_INT)
   3837       *int_mask |= 1u << reg;
   3838     else
   3839       *fp_mask |= 1u << reg;
   3840   }
   3841 }
   3842 
   3843 /* Pin resolution + panic is the shared native_asm_bind_direct_operands path. */
   3844 
   3845 /* Direct (-O0) path: resolve a semantic Operand to a NativeAddr. */
   3846 static NativeAddr rv_direct_addr(NativeDirectTarget* d, Operand op) {
   3847   NativeAddr addr;
   3848   memset(&addr, 0, sizeof addr);
   3849   switch ((OpKind)op.kind) {
   3850     case OPK_LOCAL:
   3851       addr.base_kind = NATIVE_ADDR_BASE_FRAME;
   3852       addr.base.frame = d->locals[op.v.local - 1u].home;
   3853       addr.base_type = op.type;
   3854       return addr;
   3855     case OPK_INDIRECT:
   3856       addr.base_kind = NATIVE_ADDR_BASE_FRAME_VALUE;
   3857       addr.base.frame = d->locals[op.v.ind.base - 1u].home;
   3858       addr.cls = d->locals[op.v.ind.base - 1u].cls;
   3859       addr.base_type = d->locals[op.v.ind.base - 1u].type;
   3860       addr.offset = op.v.ind.ofs;
   3861       return addr;
   3862     default:
   3863       rv_asm_panic(d, "operand is not addressable");
   3864   }
   3865 }
   3866 
   3867 /* Materialize an OPK_INDIRECT (frame-value) base into a register, returning a
   3868  * plain register-based NativeAddr. */
   3869 static NativeAddr rv_direct_materialize_addr(NativeDirectTarget* d,
   3870                                              Operand op) {
   3871   RvNativeTarget* a = rv_of(d->native);
   3872   NativeAddr addr = rv_direct_addr(d, op);
   3873   if (addr.base_kind == NATIVE_ADDR_BASE_FRAME_VALUE) {
   3874     NativeLoc base = native_loc_reg(addr.base_type, NATIVE_REG_INT, RV_TMP1);
   3875     NativeAddr load;
   3876     memset(&load, 0, sizeof load);
   3877     load.base_kind = NATIVE_ADDR_BASE_FRAME;
   3878     load.base.frame = addr.base.frame;
   3879     load.base_type = addr.base_type;
   3880     rv_emit_mem(a, 1, base, load,
   3881                 native_mem_for_type(d->native, addr.base_type, 8));
   3882     addr.base_kind = NATIVE_ADDR_BASE_REG;
   3883     addr.base.reg = RV_TMP1;
   3884   }
   3885   return addr;
   3886 }
   3887 
   3888 static void rv_direct_load_operand_to_reg(NativeDirectTarget* d, Operand op,
   3889                                           NativeLoc dst) {
   3890   RvNativeTarget* a = rv_of(d->native);
   3891   NativeAddr addr;
   3892   memset(&addr, 0, sizeof addr);
   3893   switch ((OpKind)op.kind) {
   3894     case OPK_IMM:
   3895       if ((NativeAllocClass)dst.cls != NATIVE_REG_INT)
   3896         rv_asm_panic(d, "floating-point immediate asm input is unsupported");
   3897       d->native->load_imm(d->native, dst, op.v.imm);
   3898       return;
   3899     case OPK_LOCAL:
   3900       addr.base_kind = NATIVE_ADDR_BASE_FRAME;
   3901       addr.base.frame = d->locals[op.v.local - 1u].home;
   3902       addr.base_type = op.type;
   3903       rv_emit_mem(a, 1, dst, addr, native_mem_for_type(d->native, op.type, 0));
   3904       return;
   3905     case OPK_GLOBAL:
   3906       addr.base_kind = NATIVE_ADDR_BASE_GLOBAL;
   3907       addr.base.global.sym = op.v.global.sym;
   3908       addr.base.global.addend = op.v.global.addend;
   3909       addr.base_type = op.type;
   3910       d->native->load_addr(d->native, dst, addr);
   3911       return;
   3912     case OPK_INDIRECT:
   3913       addr = rv_direct_materialize_addr(d, op);
   3914       rv_emit_mem(a, 1, dst, addr, native_mem_for_type(d->native, op.type, 0));
   3915       return;
   3916   }
   3917   rv_asm_panic(d, "unsupported asm input operand");
   3918 }
   3919 
   3920 static void rv_direct_load_address_to_reg(NativeDirectTarget* d, Operand op,
   3921                                           NativeLoc dst) {
   3922   d->native->load_addr(d->native, dst, rv_direct_addr(d, op));
   3923 }
   3924 
   3925 static void rv_direct_store_reg_to_operand(NativeDirectTarget* d, Operand op,
   3926                                            NativeLoc src) {
   3927   RvNativeTarget* a = rv_of(d->native);
   3928   NativeAddr addr;
   3929   memset(&addr, 0, sizeof addr);
   3930   if (op.kind == OPK_LOCAL) {
   3931     addr.base_kind = NATIVE_ADDR_BASE_FRAME;
   3932     addr.base.frame = d->locals[op.v.local - 1u].home;
   3933     addr.base_type = op.type;
   3934   } else {
   3935     addr = rv_direct_materialize_addr(d, op);
   3936   }
   3937   rv_emit_mem(a, 0, src, addr, native_mem_for_type(d->native, op.type, 0));
   3938 }
   3939 
   3940 /* Callee-saved registers an asm block clobbers must be spilled/restored around
   3941  * the block (the only ABI duty the allocator cannot discharge itself). */
   3942 typedef struct RvAsmSavedClobber {
   3943   NativeFrameSlot slot;
   3944   NativeAllocClass cls;
   3945   Reg reg;
   3946   KitCgTypeId type;
   3947 } RvAsmSavedClobber;
   3948 
   3949 /* A clobber save slot is register-width: ptr_bytes for an integer reg (4 on
   3950  * rv32, 8 on rv64) but always 8 for an FP reg (fsd, even on rv32d). */
   3951 static u32 rv_asm_save_bytes(const RvNativeTarget* a,
   3952                              const RvAsmSavedClobber* s) {
   3953   return s->cls == NATIVE_REG_FP ? 8u : a->variant->ptr_bytes;
   3954 }
   3955 static void rv_asm_save_one(RvNativeTarget* a, RvAsmSavedClobber* s) {
   3956   NativeFrameSlotDesc desc;
   3957   NativeAddr addr;
   3958   u32 sz = rv_asm_save_bytes(a, s);
   3959   memset(&desc, 0, sizeof desc);
   3960   desc.type = s->type;
   3961   desc.size = sz;
   3962   desc.align = sz;
   3963   desc.kind = NATIVE_FRAME_SLOT_SAVE;
   3964   s->slot = a->base.frame_slot(&a->base, &desc);
   3965   memset(&addr, 0, sizeof addr);
   3966   addr.base_kind = NATIVE_ADDR_BASE_FRAME;
   3967   addr.base.frame = s->slot;
   3968   addr.base_type = s->type;
   3969   rv_emit_mem(a, 0, native_loc_reg(s->type, s->cls, s->reg), addr,
   3970               native_mem_for_type(&a->base, s->type, sz));
   3971 }
   3972 static void rv_asm_restore_one(RvNativeTarget* a, const RvAsmSavedClobber* s) {
   3973   NativeAddr addr;
   3974   u32 sz = rv_asm_save_bytes(a, s);
   3975   memset(&addr, 0, sizeof addr);
   3976   addr.base_kind = NATIVE_ADDR_BASE_FRAME;
   3977   addr.base.frame = s->slot;
   3978   addr.base_type = s->type;
   3979   rv_emit_mem(a, 1, native_loc_reg(s->type, s->cls, s->reg), addr,
   3980               native_mem_for_type(&a->base, s->type, sz));
   3981 }
   3982 
   3983 /* psABI callee-saved: integer s0..s11 (x8,x9,x18..x27), fp fs0..fs11
   3984  * (f8,f9,f18..f27). x8 is the frame pointer and never asm-clobbered. */
   3985 static int rv_reg_is_callee_int(Reg r) {
   3986   return r == 9u || (r >= 18u && r <= 27u);
   3987 }
   3988 static int rv_reg_is_callee_fp(Reg r) {
   3989   return r == 8u || r == 9u || (r >= 18u && r <= 27u);
   3990 }
   3991 
   3992 static RvAsmSavedClobber* rv_asm_save_callee_clobbers(RvNativeTarget* a,
   3993                                                       u32 int_mask, u32 fp_mask,
   3994                                                       u32* nsaved_out) {
   3995   RvAsmSavedClobber* saved =
   3996       arena_zarray(a->base.c->tu, RvAsmSavedClobber, 24u);
   3997   KitCgTypeId i64 = builtin_id(KIT_CG_BUILTIN_I64);
   3998   KitCgTypeId f64 = builtin_id(KIT_CG_BUILTIN_F64);
   3999   u32 n = 0;
   4000   Reg r;
   4001   for (r = 0; r <= 31u; ++r) {
   4002     if ((int_mask & (1u << r)) == 0 || !rv_reg_is_callee_int(r)) continue;
   4003     saved[n].cls = NATIVE_REG_INT;
   4004     saved[n].reg = r;
   4005     saved[n].type = i64;
   4006     rv_asm_save_one(a, &saved[n++]);
   4007   }
   4008   for (r = 0; r <= 31u; ++r) {
   4009     if ((fp_mask & (1u << r)) == 0 || !rv_reg_is_callee_fp(r)) continue;
   4010     saved[n].cls = NATIVE_REG_FP;
   4011     saved[n].reg = r;
   4012     saved[n].type = f64;
   4013     rv_asm_save_one(a, &saved[n++]);
   4014   }
   4015   *nsaved_out = n;
   4016   return saved;
   4017 }
   4018 
   4019 /* ---- NativeTarget (optimizer) asm hook ----
   4020  * The optimized emitter owns register placement, staging, and output
   4021  * writeback. This hook binds its concrete locations and only materializes
   4022  * memory-constraint bases in backend-private registers. */
   4023 
   4024 static NativeAddr rv_asm_loc_to_addr(RvNativeTarget* a, SrcLoc loc,
   4025                                      NativeLoc src) {
   4026   NativeAddr addr;
   4027   memset(&addr, 0, sizeof addr);
   4028   addr.base_type = src.type;
   4029   switch ((NativeLocKind)src.kind) {
   4030     case NATIVE_LOC_FRAME:
   4031       addr.base_kind = NATIVE_ADDR_BASE_FRAME;
   4032       addr.base.frame = src.v.frame;
   4033       return addr;
   4034     case NATIVE_LOC_ADDR:
   4035       return src.v.addr;
   4036     case NATIVE_LOC_GLOBAL:
   4037       addr.base_kind = NATIVE_ADDR_BASE_GLOBAL;
   4038       addr.base.global.sym = src.v.global.sym;
   4039       addr.base.global.addend = src.v.global.addend;
   4040       return addr;
   4041     case NATIVE_LOC_REG:
   4042       addr.base_kind = NATIVE_ADDR_BASE_REG;
   4043       addr.cls = NATIVE_REG_INT;
   4044       addr.base.reg = src.v.reg;
   4045       return addr;
   4046     default:
   4047       rv_asm_panic_at(a->base.c, loc, "unsupported memory asm operand");
   4048   }
   4049 }
   4050 
   4051 /* Resolve a memory-constraint operand to a single base register with zero
   4052  * offset, folding any frame/global/offset into a reserved scratch register. */
   4053 static Reg rv_asm_native_mem_base(RvNativeTarget* a, SrcLoc loc, NativeLoc src,
   4054                                   u32* ntmp) {
   4055   NativeAddr addr = rv_asm_loc_to_addr(a, loc, src);
   4056   u32 base;
   4057   i32 off;
   4058   Reg dst;
   4059   if (addr.index_kind != NATIVE_ADDR_INDEX_NONE)
   4060     rv_asm_panic_at(a->base.c, loc, "indexed memory asm operand unsupported");
   4061   rv_resolve_mem_addr(a, &addr, &base, &off);
   4062   if (off == 0 && base != RV_TMP0 && base != RV_TMP1) return (Reg)base;
   4063   if (*ntmp >= 2u)
   4064     rv_asm_panic_at(a->base.c, loc, "too many memory asm operands");
   4065   dst = (*ntmp == 0u) ? RV_TMP0 : RV_TMP1;
   4066   (*ntmp)++;
   4067   rv_emit_addr_adjust(a->variant, a->base.mc, dst, base, off);
   4068   return dst;
   4069 }
   4070 
   4071 static void rv_asm_native_panic(NativeTarget* t, SrcLoc loc, const char* msg) {
   4072   rv_asm_panic_at(t->c, loc, msg);
   4073 }
   4074 
   4075 static Reg rv_asm_native_mem_base_hook(NativeTarget* t, SrcLoc loc,
   4076                                        NativeLoc src, u32* ntmp) {
   4077   return rv_asm_native_mem_base(rv_of(t), loc, src, ntmp);
   4078 }
   4079 
   4080 static void rv_asm_native_run_template_hook(
   4081     NativeTarget* t, const char* tmpl, const AsmConstraint* outs, u32 nout,
   4082     Operand* bound_outs, const AsmConstraint* ins, u32 nin, Operand* bound_ins,
   4083     const Sym* clobbers, u32 nclob) {
   4084   Rv64Asm* asmh = rv64_asm_open(t->c);
   4085   rv64_inline_bind(asmh, outs, nout, bound_outs, ins, nin, bound_ins, clobbers,
   4086                    nclob);
   4087   rv64_asm_run_template(asmh, t->mc, tmpl);
   4088   rv64_asm_close(asmh);
   4089 }
   4090 
   4091 static void rv_asm_block_native(NativeTarget* t, const char* tmpl,
   4092                                 const AsmConstraint* outs, u32 nout,
   4093                                 NativeLoc* out_locs, const AsmConstraint* ins,
   4094                                 u32 nin, const NativeLoc* in_locs,
   4095                                 const Sym* clobbers, u32 nclob) {
   4096   RvNativeTarget* a = rv_of(t);
   4097   SrcLoc loc = a->func ? a->func->loc : (SrcLoc){0, 0, 0};
   4098   static const NativeAsmNativeHooks hooks = {
   4099       .panic = rv_asm_native_panic,
   4100       .bound_reg = rv_asm_bound_reg,
   4101       .bound_mem = rv_asm_bound_mem,
   4102       .mem_base = rv_asm_native_mem_base_hook,
   4103       .run_template = rv_asm_native_run_template_hook,
   4104   };
   4105   native_asm_bind_native_operands(t, loc, tmpl, outs, nout, out_locs, ins, nin,
   4106                                   in_locs, clobbers, nclob, &hooks);
   4107 }
   4108 /* file_scope_asm + finalize are shared (cg/native_asm.h). */
   4109 
   4110 static void rv_trap(NativeTarget* t) { rv64_emit32(t->mc, rv_ebreak()); }
   4111 static void rv_set_loc(NativeTarget* t, SrcLoc loc) {
   4112   rv_of(t)->loc = loc;
   4113   mc_set_loc(t->mc, loc);
   4114 }
   4115 
   4116 /* ============================ construction ============================ */
   4117 
   4118 NativeTarget* rv64_native_target_new(Compiler* c, ObjBuilder* obj,
   4119                                      MCEmitter* mc) {
   4120   RvNativeTarget* a = arena_znew(c->tu, RvNativeTarget);
   4121   NativeTarget* t;
   4122   if (!a) return NULL;
   4123   t = &a->base;
   4124   t->c = c;
   4125   t->obj = obj;
   4126   t->mc = mc;
   4127   a->variant = riscv_variant_for_kind(c->target.arch);
   4128   native_frame_init(&a->frame, c);
   4129   t->regs = &rv_reg_info;
   4130   t->class_for_type = native_class_for_type_fp_le8;
   4131   t->imm_legal = rv_imm_legal;
   4132   t->addr_legal = rv_addr_legal;
   4133   t->machine_op_clobbers = rv_machine_op_clobbers;
   4134   t->func_begin = rv_func_begin;
   4135   t->func_begin_known_frame = rv_func_begin_known_frame;
   4136   t->note_frame_state = NULL;
   4137   /* Non-NULL so the optimizer emit path (plan_frame) computes the callee-saved
   4138    * set; rv_func_begin_known_frame derives the records from the masks. */
   4139   t->reserve_callee_saves = rv_reserve_callee_saves;
   4140   t->signature_stack_bytes = rv_signature_stack_bytes;
   4141   t->call_stack_bytes = rv_call_stack_bytes;
   4142   t->has_store_zero_reg = 1;
   4143   t->store_zero_reg = RV_ZERO;
   4144   t->func_end = rv_func_end;
   4145   t->frame_slot = rv_frame_slot;
   4146   t->release_frame_slot = rv_release_frame_slot;
   4147   t->frame_slot_debug_loc = rv_frame_slot_debug_loc;
   4148   t->bind_param = rv_bind_native_param;
   4149   t->label_new = rv_label_new;
   4150   t->label_place = rv_label_place;
   4151   t->jump = rv_jump;
   4152   t->cmp_branch = rv_cmp_branch;
   4153   t->indirect_branch = rv_indirect_branch;
   4154   t->load_label_addr = rv_load_label_addr;
   4155   t->move = rv_move;
   4156   t->load_imm = rv_load_imm;
   4157   t->load_const = rv_load_const;
   4158   t->load_addr = rv_load_addr;
   4159   t->load = rv_load;
   4160   t->store = rv_store;
   4161   t->tls_addr_of = rv_tls_addr_of;
   4162   t->copy_bytes = rv_copy_bytes;
   4163   t->set_bytes = rv_set_bytes;
   4164   t->bitfield_load = rv_bitfield_load;
   4165   t->bitfield_store = rv_bitfield_store;
   4166   t->binop = rv_binop;
   4167   t->unop = rv_unop;
   4168   t->cmp = rv_cmp;
   4169   t->convert = rv_convert;
   4170   t->binop_rr = rv_binop_rr;
   4171   t->move_rr = rv_move_rr;
   4172   t->cmp_rr = rv_cmp_rr;
   4173   t->convert_rr = rv_convert_rr;
   4174   t->alloca_ = rv_alloca;
   4175   t->spill = rv_spill;
   4176   t->reload = rv_reload;
   4177   t->marshal_call = rv_marshal_call;
   4178   t->emit_call = rv_emit_call;
   4179   t->marshal_ret = rv_marshal_ret;
   4180   t->ret = rv_ret;
   4181   t->atomic_load = rv_atomic_load;
   4182   t->atomic_store = rv_atomic_store;
   4183   t->atomic_rmw = rv_atomic_rmw;
   4184   t->atomic_cas = rv_atomic_cas;
   4185   t->fence = rv_fence;
   4186   t->va_start_ = rv_va_start_native;
   4187   t->va_arg_ = rv_va_arg_native;
   4188   t->va_end_ = rv_va_end_native;
   4189   t->va_copy_ = rv_va_copy_native;
   4190   t->intrinsic = rv_intrinsic;
   4191   t->asm_block = rv_asm_block_native;
   4192   t->file_scope_asm = native_file_scope_asm;
   4193   t->trap = rv_trap;
   4194   t->set_loc = rv_set_loc;
   4195   t->finalize = native_finalize;
   4196   return t;
   4197 }
   4198 
   4199 /* ============================ NativeOps (-O0) ============================ */
   4200 
   4201 static void rv_bind_param(NativeDirectTarget* d, const CGParamDesc* p,
   4202                           CGLocal local, NativeDirectLocal* l) {
   4203   NativeLoc dst;
   4204   (void)local;
   4205   memset(&dst, 0, sizeof dst);
   4206   dst.kind = NATIVE_LOC_FRAME;
   4207   dst.type = p->type;
   4208   dst.v.frame = l->home;
   4209   rv_bind_native_param(d->native, p, dst);
   4210 }
   4211 
   4212 /* A sibling call is realizable when its outgoing stack-argument area fits the
   4213  * window the caller itself received (so the args land in the caller's incoming
   4214  * slots without overflowing into the caller's caller's frame). Register-only
   4215  * calls (the common case) always qualify. Mirrors aa64's aa_no_tail. */
   4216 static const char* rv_no_tail(NativeDirectTarget* d, const CGCallDesc* call) {
   4217   RvNativeTarget* a = rv_of(d->native);
   4218   NativeCallDesc nd;
   4219   u32 stack;
   4220   if (a->frame.ncallee_saves)
   4221     return "rv64 tail call: callee-saved registers in use";
   4222   native_direct_project_tail_call_desc(d, call, &nd);
   4223   stack = rv_call_stack_size(d->native, &nd);
   4224   if (stack > a->incoming_stack_size)
   4225     return "rv64 tail call: stack argument area too small";
   4226   return NULL;
   4227 }
   4228 
   4229 /* Resolve a pointer-typed Operand (the address of a va_list object) into `reg`
   4230  * and return a register-based NativeAddr. An OPK_LOCAL holds the va_list object
   4231  * itself, so we take its frame address; an OPK_INDIRECT holds the pointer in
   4232  * memory and must be loaded. The va cores use TMP1/TMP2 internally, so `reg`
   4233  * must be distinct from those (callers pass TMP0 / TMP3). */
   4234 /* ap_addr is the pointer value &ap (the va_list object's address). For an
   4235  * OPK_LOCAL the local HOLDS that pointer, so load its home value; an
   4236  * OPK_INDIRECT names *(base+ofs), whose address base+ofs is the pointer.
   4237  * Mirrors aa64's aa_direct_pointer_addr. */
   4238 static NativeAddr rv_direct_pointer_addr(NativeDirectTarget* d, Operand op) {
   4239   RvNativeTarget* a = rv_of(d->native);
   4240   NativeAddr addr;
   4241   memset(&addr, 0, sizeof addr);
   4242   if (op.kind == OPK_LOCAL) {
   4243     NativeLoc base = native_loc_reg(op.type, NATIVE_REG_INT, RV_TMP1);
   4244     NativeAddr load;
   4245     memset(&load, 0, sizeof load);
   4246     load.base_kind = NATIVE_ADDR_BASE_FRAME;
   4247     load.base.frame = d->locals[op.v.local - 1u].home;
   4248     load.base_type = op.type;
   4249     rv_emit_mem(a, 1, base, load, native_mem_for_type(d->native, op.type, 8));
   4250     addr.base_kind = NATIVE_ADDR_BASE_REG;
   4251     addr.base.reg = RV_TMP1;
   4252     addr.base_type = op.type;
   4253     return addr;
   4254   }
   4255   return rv_direct_materialize_addr(d, op);
   4256 }
   4257 
   4258 static NativeAddr rv_direct_va_base(NativeDirectTarget* d, Operand ap_addr,
   4259                                     Reg reg) {
   4260   NativeLoc dst =
   4261       native_loc_reg(builtin_id(KIT_CG_BUILTIN_I64), NATIVE_REG_INT, reg);
   4262   NativeAddr addr;
   4263   d->native->load_addr(d->native, dst, rv_direct_pointer_addr(d, ap_addr));
   4264   memset(&addr, 0, sizeof addr);
   4265   addr.base_kind = NATIVE_ADDR_BASE_REG;
   4266   addr.cls = NATIVE_REG_INT;
   4267   addr.base.reg = reg;
   4268   addr.base_type = builtin_id(KIT_CG_BUILTIN_I64);
   4269   return addr;
   4270 }
   4271 
   4272 static void rv_va_start_(NativeDirectTarget* d, Operand ap_addr) {
   4273   rv_va_start_core(rv_of(d->native), rv_direct_va_base(d, ap_addr, RV_TMP3));
   4274 }
   4275 static void rv_va_arg_(NativeDirectTarget* d, Operand dst, Operand ap_addr,
   4276                        KitCgTypeId type) {
   4277   RvNativeTarget* a = rv_of(d->native);
   4278   NativeAllocClass cls;
   4279   /* A value too wide for one GPR (8-byte i64 / soft-double on ilp32) is copied
   4280    * straight from the save area into its destination memory. */
   4281   if (rv_va_arg_is_wide(d->native, type)) {
   4282     rv_va_arg_wide(a, rv_direct_addr(d, dst),
   4283                    rv_direct_va_base(d, ap_addr, RV_TMP3),
   4284                    native_type_size(d->native, type));
   4285     return;
   4286   }
   4287   /* Float-ABI-aware class: a soft (or wider-than-flen) float is INT-class so
   4288    * the va_arg fetch never lands a double in an FP register on rv32. */
   4289   cls = native_class_for_type_fp_le8(d->native, type);
   4290   NativeLoc res =
   4291       native_loc_reg(type, cls, cls == NATIVE_REG_FP ? RV_FTMP0 : RV_TMP0);
   4292   NativeAddr dst_addr;
   4293   rv_va_arg_core(a, res, rv_direct_va_base(d, ap_addr, RV_TMP3), type);
   4294   /* Store the fetched value back into the semantic destination. */
   4295   dst_addr = rv_direct_addr(d, dst);
   4296   if (dst_addr.base_kind == NATIVE_ADDR_BASE_FRAME_VALUE) {
   4297     NativeLoc base =
   4298         native_loc_reg(dst_addr.base_type, NATIVE_REG_INT, RV_TMP1);
   4299     NativeAddr load;
   4300     memset(&load, 0, sizeof load);
   4301     load.base_kind = NATIVE_ADDR_BASE_FRAME;
   4302     load.base.frame = dst_addr.base.frame;
   4303     load.base_type = dst_addr.base_type;
   4304     rv_emit_mem(a, 1, base, load,
   4305                 native_mem_for_type(d->native, dst_addr.base_type, 8));
   4306     dst_addr.base_kind = NATIVE_ADDR_BASE_REG;
   4307     dst_addr.base.reg = RV_TMP1;
   4308   }
   4309   rv_emit_mem(
   4310       a, 0, res, dst_addr,
   4311       native_mem_for_type(d->native, type, native_type_size(d->native, type)));
   4312 }
   4313 static void rv_va_end_(NativeDirectTarget* d, Operand ap_addr) {
   4314   (void)d;
   4315   (void)ap_addr;
   4316 }
   4317 static void rv_va_copy_(NativeDirectTarget* d, Operand dst, Operand src) {
   4318   RvNativeTarget* a = rv_of(d->native);
   4319   NativeAddr src_ap = rv_direct_va_base(d, src, RV_TMP0);
   4320   NativeAddr dst_ap = rv_direct_va_base(d, dst, RV_TMP3);
   4321   rv_va_copy_core(a, dst_ap, src_ap);
   4322 }
   4323 
   4324 /* Hook adapters bridging the arch-typed save/restore + assembler entry to the
   4325  * shared NativeAsmDirectHooks signatures. */
   4326 static void* rv_asm_hook_save_callee_clobbers(NativeDirectTarget* d,
   4327                                               u32 int_mask, u32 fp_mask,
   4328                                               u32* nsaved_out) {
   4329   return rv_asm_save_callee_clobbers(rv_of(d->native), int_mask, fp_mask,
   4330                                      nsaved_out);
   4331 }
   4332 static void rv_asm_hook_restore_one(NativeDirectTarget* d, void* saved,
   4333                                     u32 idx) {
   4334   rv_asm_restore_one(rv_of(d->native), &((RvAsmSavedClobber*)saved)[idx]);
   4335 }
   4336 static void rv_asm_hook_run_template(NativeDirectTarget* d, const char* tmpl,
   4337                                      const AsmConstraint* outs, u32 nout,
   4338                                      Operand* bound_outs,
   4339                                      const AsmConstraint* ins, u32 nin,
   4340                                      Operand* bound_ins, const Sym* clobbers,
   4341                                      u32 nclob) {
   4342   Rv64Asm* asmh = rv64_asm_open(d->base.c);
   4343   rv64_inline_bind(asmh, outs, nout, bound_outs, ins, nin, bound_ins, clobbers,
   4344                    nclob);
   4345   rv64_asm_run_template(asmh, d->native->mc, tmpl);
   4346   rv64_asm_close(asmh);
   4347 }
   4348 
   4349 static void rv_direct_asm_block(NativeDirectTarget* d, const char* tmpl,
   4350                                 const AsmConstraint* outs, u32 nout,
   4351                                 Operand* out_ops, const AsmConstraint* ins,
   4352                                 u32 nin, const Operand* in_ops,
   4353                                 const Sym* clobbers, u32 nclob,
   4354                                 u32 clobber_abi_sets) {
   4355   static const NativeAsmDirectHooks hooks = {
   4356       .opk_reg = RV64_INLINE_OPK_REG,
   4357       .opcls_fp = RV64_INLINE_OPCLS_FP,
   4358       .panic = rv_asm_panic,
   4359       .bound_reg = rv_asm_bound_reg,
   4360       .bound_mem = rv_asm_bound_mem,
   4361       .clobber_masks = rv_asm_clobber_masks,
   4362       .save_callee_clobbers = rv_asm_hook_save_callee_clobbers,
   4363       .restore_one = rv_asm_hook_restore_one,
   4364       .load_operand_to_reg = rv_direct_load_operand_to_reg,
   4365       .load_address_to_reg = rv_direct_load_address_to_reg,
   4366       .store_reg_to_operand = rv_direct_store_reg_to_operand,
   4367       .run_template = rv_asm_hook_run_template,
   4368   };
   4369   native_asm_bind_direct_operands(d, tmpl, outs, nout, out_ops, ins, nin,
   4370                                   in_ops, clobbers, nclob, clobber_abi_sets,
   4371                                   &hooks);
   4372 }
   4373 
   4374 static const NativeOps rv_direct_ops = {
   4375     .bind_param = rv_bind_param,
   4376     .tail_call_unrealizable_reason = rv_no_tail,
   4377     .va_start_ = rv_va_start_,
   4378     .va_arg_ = rv_va_arg_,
   4379     .va_end_ = rv_va_end_,
   4380     .va_copy_ = rv_va_copy_,
   4381     .asm_block = rv_direct_asm_block,
   4382 };
   4383 
   4384 const NativeOps* rv64_native_direct_ops(void) { return &rv_direct_ops; }