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, ®)) 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; }