pass_native_emit.c (121889B)
1 #include <string.h> 2 3 #include "cg/native_asm.h" 4 #include "cg/type.h" 5 #include "core/metrics.h" 6 #include "core/pool.h" 7 #include "opt/opt_internal.h" 8 9 #undef Operand 10 #undef CGParamDesc 11 #undef CGCallDesc 12 #undef CGFuncDesc 13 #undef CGLocalStorage 14 #undef CGABIValue 15 #undef CGABIPart 16 #undef CGCallPlan 17 #undef CGCallPlanMove 18 #undef CGCallPlanRet 19 #undef CGScopeDesc 20 21 typedef struct NativeEmitTempScope NativeEmitTempScope; 22 23 typedef struct NativeEmitCtx { 24 Compiler* c; 25 Func* f; 26 NativeTarget* target; 27 NativeFrameSlot* slot_map; 28 MCLabel* labels; 29 u8* label_placed; 30 ObjSecId local_static_sec; 31 ObjSymId local_static_sym; 32 u32 local_static_base; 33 u32 local_static_size; 34 u8 local_static_active; 35 /* Set by emit_block for the IR_RET that is the last inst of the last block 36 * in emit_order. emit_ret consults it to skip the trailing 37 * branch-to-epilogue: func_end places the epilogue label at the very next 38 * position, so the branch would just jump to the next 4 bytes. */ 39 u8 emitting_terminal_ret; 40 /* Instruction-scoped materialization state. NULL outside emit_inst (notably 41 * during frame planning/parameter binding). */ 42 NativeEmitTempScope* temps; 43 /* Physical registers live after each MIR instruction, indexed by InstId. 44 * Used only to scavenge dead caller-saved registers after the dedicated temp 45 * bank is exhausted. */ 46 OptHardRegSet* live_after_by_inst; 47 u32 live_after_cap; 48 u8 live_after_ready; 49 /* Clean frame-value forwarding cache, indexed by physical register. A 50 * nonzero slot means the register holds the exact value already stored in 51 * that private FS_SPILL slot. Memory is always authoritative. */ 52 FrameSlot frame_cache_slot[OPT_REG_CLASSES][OPT_MAX_HARD_REGS]; 53 KitCgTypeId frame_cache_type[OPT_REG_CLASSES][OPT_MAX_HARD_REGS]; 54 u32 frame_cache_held[OPT_REG_CLASSES]; 55 /* Callee-saved registers selected by the pre-frame asm staging plan. The 56 * actual instruction-local allocator is deterministic and chooses the same 57 * registers during emission; these masks make their ABI preservation part of 58 * the known frame instead of allocating save slots mid-body. */ 59 u32 asm_stage_callee_used[OPT_REG_CLASSES]; 60 /* Callee-saved registers already represented in the known frame, from MIR 61 * allocation or asm staging. A dead register in this set may safely retain a 62 * clean spill value without creating a new ABI preservation obligation. */ 63 u32 callee_saved_used[OPT_REG_CLASSES]; 64 } NativeEmitCtx; 65 66 static void frame_cache_clear(NativeEmitCtx* e); 67 static void frame_cache_drop_reg(NativeEmitCtx* e, u32 cls, Reg reg); 68 static void frame_cache_invalidate_masks(NativeEmitCtx* e, 69 const u32* masks); 70 static NativeLoc frame_cache_take_slot(NativeEmitCtx* e, FrameSlot slot, 71 NativeLoc source, 72 NativeAllocClass cls, 73 KitCgTypeId type, Reg avoid_a, 74 Reg avoid_b); 75 static void compute_emit_live_after(NativeEmitCtx* e); 76 77 static _Noreturn void emit_panic(NativeEmitCtx* e, SrcLoc loc, 78 const char* msg) { 79 compiler_panic(e->c, loc, "opt native emit: %s", msg); 80 } 81 82 static void emit_local_static_begin(NativeEmitCtx* e, 83 const CGLocalStaticDataDesc* desc, 84 SrcLoc loc) { 85 Sym name; 86 SecKind kind; 87 u16 flags; 88 u32 align; 89 if (!desc) emit_panic(e, loc, "missing local static data descriptor"); 90 if (e->local_static_active) emit_panic(e, loc, "nested local static data"); 91 if (desc->attrs.section) { 92 name = (Sym)desc->attrs.section; 93 kind = 94 (desc->attrs.flags & KIT_CG_DATADEF_READONLY) ? SEC_RODATA : SEC_DATA; 95 flags = (desc->attrs.flags & KIT_CG_DATADEF_READONLY) 96 ? SF_ALLOC 97 : (SF_ALLOC | SF_WRITE); 98 } else if (desc->attrs.flags & KIT_CG_DATADEF_READONLY) { 99 name = pool_intern_slice(e->c->global, SLICE_LIT(".rodata")); 100 kind = SEC_RODATA; 101 flags = SF_ALLOC; 102 } else { 103 name = pool_intern_slice(e->c->global, SLICE_LIT(".data")); 104 kind = SEC_DATA; 105 flags = SF_ALLOC | SF_WRITE; 106 } 107 align = desc->align ? desc->align : 1u; 108 e->local_static_sec = obj_section(e->target->obj, name, kind, flags, align); 109 e->local_static_base = 110 obj_align_to(e->target->obj, e->local_static_sec, align); 111 e->local_static_size = 0; 112 e->local_static_sym = desc->sym; 113 e->local_static_active = 1; 114 } 115 116 static void emit_local_static_write(NativeEmitCtx* e, const u8* data, u64 len, 117 SrcLoc loc) { 118 u8 zero[64]; 119 u64 orig_len = len; 120 if (!e->local_static_active) emit_panic(e, loc, "local static data inactive"); 121 if (!len) return; 122 if (data) { 123 obj_write(e->target->obj, e->local_static_sec, data, (size_t)len); 124 } else { 125 memset(zero, 0, sizeof zero); 126 while (len >= sizeof zero) { 127 obj_write(e->target->obj, e->local_static_sec, zero, sizeof zero); 128 len -= sizeof zero; 129 } 130 if (len) obj_write(e->target->obj, e->local_static_sec, zero, (size_t)len); 131 } 132 e->local_static_size += (u32)orig_len; 133 } 134 135 static void emit_local_static_label_addr(NativeEmitCtx* e, MCLabel target, 136 i64 addend, u32 width, SrcLoc loc) { 137 u8 zero[8]; 138 u32 off; 139 RelocKind kind; 140 if (!e->local_static_active) emit_panic(e, loc, "local static data inactive"); 141 /* A jump-table / label-address slot is one target pointer wide: 8 bytes 142 * (R_ABS64) on a 64-bit target, 4 bytes (R_ABS32) on rv32/ELFCLASS32. */ 143 if (width == 8u) 144 kind = R_ABS64; 145 else if (width == 4u) 146 kind = R_ABS32; 147 else { 148 emit_panic(e, loc, "unsupported local static label width"); 149 return; 150 } 151 memset(zero, 0, sizeof zero); 152 off = e->local_static_base + e->local_static_size; 153 obj_write(e->target->obj, e->local_static_sec, zero, width); 154 mc_emit_label_data_reloc(e->target->mc, e->local_static_sec, off, 155 target, kind, width, addend); 156 e->local_static_size += width; 157 } 158 159 static void emit_local_static_end(NativeEmitCtx* e, SrcLoc loc) { 160 if (!e->local_static_active) emit_panic(e, loc, "local static data inactive"); 161 obj_symbol_define_live(e->target->obj, e->local_static_sym, 162 e->local_static_sec, e->local_static_base, 163 e->local_static_size); 164 e->local_static_active = 0; 165 e->local_static_sec = OBJ_SEC_NONE; 166 e->local_static_sym = OBJ_SYM_NONE; 167 e->local_static_base = 0; 168 e->local_static_size = 0; 169 } 170 171 static u32 type_size_or(Compiler* c, KitCgTypeId type, u32 fallback) { 172 u64 n = type ? cg_type_size(c, type) : 0u; 173 if (!n || n > 0xffffffffull) return fallback; 174 return (u32)n; 175 } 176 177 static u32 type_align_or(Compiler* c, KitCgTypeId type, u32 fallback) { 178 u64 n = type ? cg_type_align(c, type) : 0u; 179 if (!n || n > 0xffffffffull) return fallback; 180 return (u32)n; 181 } 182 183 static MemAccess mem_for_type(Compiler* c, KitCgTypeId type) { 184 MemAccess mem; 185 memset(&mem, 0, sizeof mem); 186 mem.type = type; 187 mem.size = type_size_or(c, type, 8u); 188 mem.align = type_align_or(c, type, mem.size >= 8u ? 8u : mem.size); 189 return mem; 190 } 191 192 static NativeAllocClass class_for_type(NativeEmitCtx* e, KitCgTypeId type) { 193 if (e->target->class_for_type) 194 return e->target->class_for_type(e->target, type); 195 return cg_type_is_float(e->c, type) ? NATIVE_REG_FP : NATIVE_REG_INT; 196 } 197 198 static NativeLoc loc_none(void) { 199 NativeLoc loc; 200 memset(&loc, 0, sizeof loc); 201 return loc; 202 } 203 204 static NativeLoc loc_reg(KitCgTypeId type, NativeAllocClass cls, Reg reg) { 205 NativeLoc loc; 206 memset(&loc, 0, sizeof loc); 207 loc.kind = NATIVE_LOC_REG; 208 loc.cls = (u8)cls; 209 loc.type = type; 210 loc.v.reg = reg; 211 return loc; 212 } 213 214 static NativeLoc loc_frame(KitCgTypeId type, NativeAllocClass cls, 215 NativeFrameSlot slot) { 216 NativeLoc loc; 217 memset(&loc, 0, sizeof loc); 218 loc.kind = NATIVE_LOC_FRAME; 219 loc.cls = (u8)cls; 220 loc.type = type; 221 loc.v.frame = slot; 222 return loc; 223 } 224 225 static NativeLoc loc_frame_addr(KitCgTypeId type, NativeFrameSlot slot) { 226 NativeLoc loc; 227 memset(&loc, 0, sizeof loc); 228 loc.kind = NATIVE_LOC_FRAME_ADDR; 229 loc.cls = NATIVE_REG_INT; 230 loc.type = type; 231 loc.v.frame = slot; 232 return loc; 233 } 234 235 static NativeLoc loc_imm(KitCgTypeId type, i64 imm) { 236 NativeLoc loc; 237 memset(&loc, 0, sizeof loc); 238 loc.kind = NATIVE_LOC_IMM; 239 loc.cls = NATIVE_REG_INT; 240 loc.type = type; 241 loc.v.imm = imm; 242 return loc; 243 } 244 245 static NativeLoc loc_global(KitCgTypeId type, ObjSymId sym, i64 addend) { 246 NativeLoc loc; 247 memset(&loc, 0, sizeof loc); 248 loc.kind = NATIVE_LOC_GLOBAL; 249 loc.cls = NATIVE_REG_INT; 250 loc.type = type; 251 loc.v.global.sym = sym; 252 loc.v.global.addend = addend; 253 return loc; 254 } 255 256 static int loc_same_frame(NativeLoc a, NativeLoc b) { 257 return a.kind == NATIVE_LOC_FRAME && b.kind == NATIVE_LOC_FRAME && 258 a.v.frame == b.v.frame; 259 } 260 261 #define NATIVE_EMIT_TEMP_CACHE 16u 262 263 typedef struct NativeEmitTempCacheEntry { 264 NativeLoc source; 265 NativeLoc result; 266 } NativeEmitTempCacheEntry; 267 268 struct NativeEmitTempScope { 269 NativeEmitCtx* emit; 270 const Inst* inst; 271 u8 allow_asm_temps; 272 u8 effects_ready; 273 u8 live_after_applied; 274 u8 pad; 275 u32 leased[OPT_REG_CLASSES]; 276 u32 unavailable[OPT_REG_CLASSES]; 277 /* Definition registers that currently hold a cached stack source consumed 278 * by this same instruction. They may be used in place as source+destination 279 * even though the normal avoidance set contains the destination register. */ 280 u32 cache_def_reuse[OPT_REG_CLASSES]; 281 NativeEmitTempCacheEntry cache[NATIVE_EMIT_TEMP_CACHE]; 282 u32 ncache; 283 }; 284 285 typedef struct NativeEmitTempMark { 286 u32 leased[OPT_REG_CLASSES]; 287 u32 ncache; 288 } NativeEmitTempMark; 289 290 typedef struct FrameCacheNeedCtx { 291 NativeEmitCtx* emit; 292 u32 regs[OPT_REG_CLASSES]; 293 } FrameCacheNeedCtx; 294 295 static void frame_cache_collect_needed(const Inst* in, 296 FrameCacheNeedCtx* need) { 297 NativeEmitCtx* e = need->emit; 298 for (u32 i = 0; in && i < in->nopnds; ++i) { 299 const OptOperand* op = &in->opnds[i]; 300 if (opt_inst_operand_is_def(in, i) || op->kind != OPK_STACK) continue; 301 for (u32 c = 0; c < OPT_REG_CLASSES; ++c) 302 for (Reg r = 0; r < OPT_MAX_HARD_REGS; ++r) 303 if ((e->frame_cache_held[c] & (1u << r)) && 304 e->frame_cache_slot[c][r] == op->v.frame_slot && 305 e->frame_cache_type[c][r] == op->type) 306 need->regs[c] |= 1u << r; 307 } 308 } 309 310 static int temp_loc_equal(NativeLoc a, NativeLoc b) { 311 if (a.kind != b.kind || a.cls != b.cls || a.type != b.type) return 0; 312 switch ((NativeLocKind)a.kind) { 313 case NATIVE_LOC_NONE: 314 return 1; 315 case NATIVE_LOC_REG: 316 return a.v.reg == b.v.reg; 317 case NATIVE_LOC_FRAME: 318 case NATIVE_LOC_FRAME_ADDR: 319 return a.v.frame == b.v.frame; 320 case NATIVE_LOC_STACK: 321 return a.v.stack.slot == b.v.stack.slot && 322 a.v.stack.offset == b.v.stack.offset; 323 case NATIVE_LOC_IMM: 324 return a.v.imm == b.v.imm; 325 case NATIVE_LOC_GLOBAL: 326 return a.v.global.sym == b.v.global.sym && 327 a.v.global.addend == b.v.global.addend; 328 case NATIVE_LOC_ADDR: 329 return memcmp(&a.v.addr, &b.v.addr, sizeof a.v.addr) == 0; 330 } 331 return 0; 332 } 333 334 static void temp_scope_apply_effects(NativeEmitCtx* e, 335 NativeEmitTempScope* scope) { 336 OptRegEffectMasks effects; 337 FrameCacheNeedCtx needed; 338 u32 invalidate[OPT_REG_CLASSES]; 339 if (!e || !scope || scope->effects_ready) return; 340 memset(&needed, 0, sizeof needed); 341 opt_inst_reg_effect_masks(e->f, scope->inst, &effects); 342 needed.emit = e; 343 frame_cache_collect_needed(scope->inst, &needed); 344 for (u32 c = 0; c < OPT_REG_CLASSES; ++c) { 345 u32 clobbers = effects.clobbers.cls[c]; 346 /* Call clobbers happen after argument/callee staging. A call-local temp is 347 * consumed by the parallel-copy plan before emit_call and may therefore 348 * live in a caller-saved register; forbidding the entire ABI clobber set 349 * makes high-pressure calls impossible precisely when every callee-saved 350 * allocation register is live across them. Calls clear the block cache 351 * below, so this exception cannot preserve a stale value past the call. */ 352 if (scope->inst && (IROp)scope->inst->op == IR_CALL) clobbers = 0u; 353 scope->unavailable[c] |= 354 effects.uses.cls[c] | effects.defs.cls[c] | clobbers; 355 scope->cache_def_reuse[c] = 356 needed.regs[c] & effects.defs.cls[c] & 357 ~(effects.uses.cls[c] | effects.clobbers.cls[c]); 358 invalidate[c] = scope->unavailable[c] & ~scope->cache_def_reuse[c]; 359 } 360 if (scope->inst && ((IROp)scope->inst->op == IR_CALL || 361 (IROp)scope->inst->op == IR_ASM_BLOCK)) 362 frame_cache_clear(e); 363 else 364 frame_cache_invalidate_masks(e, invalidate); 365 scope->effects_ready = 1u; 366 } 367 368 static void temp_scope_begin(NativeEmitCtx* e, NativeEmitTempScope* scope, 369 const Inst* in) { 370 const u32* machine_clobbers = NULL; 371 for (u32 c = 0; c < OPT_REG_CLASSES; ++c) { 372 scope->leased[c] = 0u; 373 scope->unavailable[c] = 0u; 374 scope->cache_def_reuse[c] = 0u; 375 } 376 scope->emit = e; 377 scope->inst = in; 378 scope->allow_asm_temps = 0u; 379 scope->effects_ready = 0u; 380 scope->live_after_applied = 0u; 381 /* ncache is the sole validity boundary for the materialization entries. 382 * Clearing the 16-entry payload on every MIR instruction only burns memory 383 * bandwidth; each entry is fully assigned before ncache exposes it. */ 384 scope->ncache = 0u; 385 e->temps = scope; 386 387 if (e->live_after_ready) { 388 if (in && in->id != INST_ID_NONE && in->id < e->live_after_cap) 389 for (u32 c = 0; c < OPT_REG_CLASSES; ++c) 390 scope->unavailable[c] |= e->live_after_by_inst[in->id].cls[c]; 391 scope->live_after_applied = 1u; 392 temp_scope_apply_effects(e, scope); 393 return; 394 } 395 396 /* Before the allocation-register scavenger is enabled, MIR's ownership 397 * invariant keeps every explicit use/def out of the reserved emission-temp 398 * bank. Only an exceptional backend-declared machine clobber can collide 399 * with those temps, so handle that table directly and defer the full 400 * use/def analysis alongside hard liveness. */ 401 machine_clobbers = opt_inst_machine_clobber_masks(e->f, in); 402 if (in && ((IROp)in->op == IR_CALL || (IROp)in->op == IR_ASM_BLOCK)) { 403 frame_cache_clear(e); 404 } else if (machine_clobbers) { 405 for (u32 c = 0; c < OPT_REG_CLASSES; ++c) 406 scope->unavailable[c] |= machine_clobbers[c]; 407 frame_cache_invalidate_masks(e, machine_clobbers); 408 } 409 } 410 411 /* Physical live-after is needed only for the allocation-register scavenger. 412 * Most instructions (and most functions) fit entirely in the target's 413 * dedicated emission-temp bank, so defer the full hard-liveness dataflow and 414 * per-instruction table until a scope actually exhausts that bank. */ 415 static void temp_scope_require_live_after(NativeEmitCtx* e) { 416 u32 live[OPT_REG_CLASSES]; 417 const Inst* in; 418 if (!e) return; 419 if (!e->live_after_ready) compute_emit_live_after(e); 420 if (!e->temps) return; 421 if (e->temps->live_after_applied) return; 422 in = e->temps->inst; 423 memset(live, 0, sizeof live); 424 if (in && in->id != INST_ID_NONE && in->id < e->live_after_cap) 425 for (u32 c = 0; c < OPT_REG_CLASSES; ++c) { 426 live[c] = e->live_after_by_inst[in->id].cls[c]; 427 e->temps->unavailable[c] |= live[c]; 428 } 429 e->temps->live_after_applied = 1u; 430 temp_scope_apply_effects(e, e->temps); 431 for (u32 c = 0; c < OPT_REG_CLASSES; ++c) { 432 live[c] &= ~e->temps->cache_def_reuse[c]; 433 } 434 frame_cache_invalidate_masks(e, live); 435 } 436 437 static NativeEmitTempMark temp_scope_mark(NativeEmitCtx* e) { 438 NativeEmitTempMark mark; 439 memset(&mark, 0, sizeof mark); 440 if (!e->temps) return mark; 441 memcpy(mark.leased, e->temps->leased, sizeof mark.leased); 442 mark.ncache = e->temps->ncache; 443 return mark; 444 } 445 446 static void temp_scope_rewind(NativeEmitCtx* e, NativeEmitTempMark mark) { 447 if (!e->temps) return; 448 memcpy(e->temps->leased, mark.leased, sizeof mark.leased); 449 e->temps->ncache = mark.ncache; 450 } 451 452 /* Forget every instruction-local materialization fact whose value lived in 453 * `reg`. This does not release the register: a caller that overwrote a cached 454 * value in place still owns the new result through the existing lease. */ 455 static int temp_scope_drop_cached_reg(NativeEmitCtx* e, 456 NativeAllocClass cls, Reg reg) { 457 u32 c = (u32)cls; 458 u32 out = 0; 459 int owned = 0; 460 if (!e->temps || c >= OPT_REG_CLASSES || reg >= OPT_MAX_HARD_REGS) return 0; 461 for (u32 i = 0; i < e->temps->ncache; ++i) { 462 NativeEmitTempCacheEntry entry = e->temps->cache[i]; 463 if (entry.result.kind == NATIVE_LOC_REG && entry.result.cls == (u8)cls && 464 entry.result.v.reg == reg) { 465 owned = 1; 466 continue; 467 } 468 if (out != i) e->temps->cache[out] = entry; 469 ++out; 470 } 471 if (!owned) return 0; 472 e->temps->ncache = out; 473 e->temps->cache_def_reuse[c] &= ~(1u << reg); 474 return 1; 475 } 476 477 /* Relinquish a register only when this instruction acquired it as a cached 478 * materialization. Earlier ABI-phase destinations are leased without a cache 479 * entry and therefore remain protected. */ 480 static void temp_scope_release_cached_reg(NativeEmitCtx* e, 481 NativeAllocClass cls, Reg reg) { 482 u32 c = (u32)cls; 483 if (!temp_scope_drop_cached_reg(e, cls, reg)) return; 484 e->temps->leased[c] &= ~(1u << reg); 485 } 486 487 /* End one ordering phase inside a compound native operation. A call consumes 488 * all argument/callee staging before it clobbers caller-saved registers; none 489 * of those leases or materialization facts may be observed by return 490 * writeback. The instruction scope (and its hard-reg avoidance set) remains 491 * active for the post-call phase. */ 492 static void temp_scope_phase_barrier(NativeEmitCtx* e) { 493 if (!e->temps) return; 494 memset(e->temps->leased, 0, sizeof e->temps->leased); 495 e->temps->ncache = 0; 496 } 497 498 /* Keep a completed register destination owned until the current compound 499 * operation reaches its next ordering barrier. write_loc deliberately 500 * rewinds the temporary leases used to perform one move; without this separate 501 * phase lease, a later move could reuse (and overwrite) an earlier ABI 502 * argument or return register while materializing its own source. */ 503 static void temp_scope_hold_loc(NativeEmitCtx* e, NativeLoc loc) { 504 u32 c; 505 Reg r; 506 if (!e->temps || loc.kind != NATIVE_LOC_REG) return; 507 c = (u32)loc.cls; 508 r = loc.v.reg; 509 if (c >= OPT_REG_CLASSES || r >= OPT_MAX_HARD_REGS) 510 emit_panic(e, e->temps->inst ? e->temps->inst->loc : (SrcLoc){0, 0, 0}, 511 "invalid phase-owned register"); 512 frame_cache_drop_reg(e, c, r); 513 e->temps->leased[c] |= 1u << r; 514 } 515 516 static void temp_scope_end(NativeEmitCtx* e, NativeEmitTempScope* scope) { 517 if (e->temps != scope) 518 emit_panic(e, scope->inst ? scope->inst->loc : (SrcLoc){0, 0, 0}, 519 "temporary scope mismatch"); 520 #ifndef NDEBUG 521 for (u32 c = 0; c < OPT_REG_CLASSES; ++c) 522 if (scope->leased[c] & e->frame_cache_held[c]) 523 emit_panic(e, scope->inst ? scope->inst->loc : (SrcLoc){0, 0, 0}, 524 "temporary lease escaped into frame cache"); 525 #endif 526 memset(scope->leased, 0, sizeof scope->leased); 527 scope->ncache = 0; 528 e->temps = NULL; 529 } 530 531 static int temp_reg_available(NativeEmitCtx* e, NativeAllocClass cls, Reg r, 532 Reg a, Reg b) { 533 u32 c = (u32)cls; 534 u32 bit; 535 if (r == a || r == b || r >= 32u || c >= OPT_REG_CLASSES) return 0; 536 bit = 1u << r; 537 if (!e->temps) return 1; 538 return !(e->temps->leased[c] & bit) && 539 !(e->temps->unavailable[c] & bit) && 540 !(e->frame_cache_held[c] & bit); 541 } 542 543 static Reg temp_acquire(NativeEmitCtx* e, NativeAllocClass cls, Reg a, Reg b, 544 SrcLoc loc) { 545 u32 c = (u32)cls; 546 if (c < OPT_REG_CLASSES) { 547 /* Prefer the target-declared O1 operand-temp bank. These registers are 548 * excluded from allocation, but still respect explicit/implicit instruction 549 * clobbers and other leases in this scope. */ 550 for (u32 i = 0; i < e->f->emit_temp_reg_count[c]; ++i) { 551 Reg r = e->f->emit_temp_regs[c][i]; 552 if (!temp_reg_available(e, cls, r, a, b)) continue; 553 if (e->temps) e->temps->leased[c] |= 1u << r; 554 return r; 555 } 556 /* A dead caller-saved allocation register is an equally local temporary: 557 * it needs no prologue save and liveness proves its value is dead before 558 * we overwrite it. This is the pressure escape hatch for two-temp targets 559 * and for ARM32, whose backend-internal LR must not be leased. */ 560 if (e->temps) { 561 temp_scope_require_live_after(e); 562 for (u32 i = 0; i < e->f->opt_hard_reg_count[c]; ++i) { 563 Reg r = e->f->opt_hard_regs[c][i]; 564 if (r >= 32u || !(e->f->opt_caller_saved[c] & (1u << r))) continue; 565 if (!temp_reg_available(e, cls, r, a, b)) continue; 566 e->temps->leased[c] |= 1u << r; 567 metrics_count(e->c, "opt.native_emit.scavenges", 1); 568 return r; 569 } 570 } 571 /* A clean cache entry is always evictable: its frame store is already 572 * authoritative. Prefer losing the forwarding opportunity over failing a 573 * real instruction's temporary demand. */ 574 if (e->temps) { 575 for (u32 i = 0; i < e->f->emit_temp_reg_count[c]; ++i) { 576 Reg r = e->f->emit_temp_regs[c][i]; 577 u32 bit = r < 32u ? 1u << r : 0u; 578 if (!bit || !(e->frame_cache_held[c] & bit) || r == a || r == b || 579 (e->temps->leased[c] & bit) || 580 (e->temps->unavailable[c] & bit)) 581 continue; 582 frame_cache_drop_reg(e, c, r); 583 e->temps->leased[c] |= bit; 584 return r; 585 } 586 } 587 } 588 compiler_panic( 589 e->c, loc, 590 "opt native emit: no instruction-local native temporary " 591 "(class=%u op=%u inst=%u leased=0x%08x unavailable=0x%08x " 592 "cached=0x%08x avoid=%u/%u)", 593 (unsigned)c, 594 e->temps && e->temps->inst ? (unsigned)e->temps->inst->op : 0u, 595 e->temps && e->temps->inst ? (unsigned)e->temps->inst->id : 0u, 596 e->temps && c < OPT_REG_CLASSES ? (unsigned)e->temps->leased[c] : 0u, 597 e->temps && c < OPT_REG_CLASSES ? (unsigned)e->temps->unavailable[c] 598 : 0u, 599 c < OPT_REG_CLASSES ? (unsigned)e->frame_cache_held[c] : 0u, 600 (unsigned)a, (unsigned)b); 601 } 602 603 static int asm_temp_reg_ok(NativeEmitCtx* e, NativeAllocClass cls, Reg r, 604 u32 allowed_mask) { 605 if (r >= 32u || (allowed_mask && !(allowed_mask & (1u << r)))) return 0; 606 return !e->target->regs || !e->target->regs->asm_operand_reg_ok || 607 e->target->regs->asm_operand_reg_ok(e->target->regs, cls, r); 608 } 609 610 static const NativeAllocClassInfo* emit_class_info(NativeEmitCtx* e, 611 NativeAllocClass cls) { 612 if (!e->target->regs) return NULL; 613 for (u32 i = 0; i < e->target->regs->nclasses; ++i) 614 if (e->target->regs->classes[i].cls == (u8)cls) 615 return &e->target->regs->classes[i]; 616 return NULL; 617 } 618 619 static void frame_cache_drop_reg(NativeEmitCtx* e, u32 cls, Reg reg) { 620 if (cls >= OPT_REG_CLASSES || reg >= OPT_MAX_HARD_REGS) return; 621 e->frame_cache_slot[cls][reg] = FRAME_SLOT_NONE; 622 e->frame_cache_type[cls][reg] = 0; 623 e->frame_cache_held[cls] &= ~(1u << reg); 624 } 625 626 static void frame_cache_clear(NativeEmitCtx* e) { 627 memset(e->frame_cache_slot, 0, sizeof e->frame_cache_slot); 628 memset(e->frame_cache_type, 0, sizeof e->frame_cache_type); 629 memset(e->frame_cache_held, 0, sizeof e->frame_cache_held); 630 } 631 632 static void frame_cache_invalidate_masks(NativeEmitCtx* e, 633 const u32* masks) { 634 if (!masks) return; 635 for (u32 c = 0; c < OPT_REG_CLASSES; ++c) { 636 u32 killed = e->frame_cache_held[c] & masks[c]; 637 for (Reg r = 0; killed && r < 32u; ++r) 638 if (killed & (1u << r)) { 639 frame_cache_drop_reg(e, c, r); 640 killed &= ~(1u << r); 641 } 642 } 643 } 644 645 static u32 frame_cache_retainable_mask(NativeEmitCtx* e, 646 NativeAllocClass cls) { 647 const NativeAllocClassInfo* ci = emit_class_info(e, cls); 648 u32 c = (u32)cls; 649 u32 mask; 650 u32 preserved_allocable; 651 if (!ci || c >= OPT_REG_CLASSES) return 0u; 652 mask = ci->emit_cache_mask; 653 /* Without hard live-after data, only target-reserved cache registers can be 654 * retained across instructions. An ordinary allocation register reaching a 655 * spill store may still carry a live MIR value; treating it as a cache entry 656 * would let a later reload lease and overwrite that value. */ 657 if (!e->live_after_ready) return mask; 658 preserved_allocable = native_target_caller_saved_mask(e->target, cls); 659 preserved_allocable |= e->callee_saved_used[c]; 660 /* An allocable register is a legal persistent cache only when acquisition 661 * proved it dead and its ABI preservation is already established: 662 * caller-saved until the next call boundary, or a callee-save already present 663 * in the known frame because MIR/asm uses it. The NativeTarget contract 664 * requires ordinary hooks to preserve such registers unless the current 665 * instruction's exhaustive machine_op_clobbers effect says otherwise; 666 * calls/asm clear the cache at their phase boundary. */ 667 for (u32 i = 0; i < e->f->opt_hard_reg_count[c]; ++i) { 668 Reg r = e->f->opt_hard_regs[c][i]; 669 if (r < 32u && (preserved_allocable & (1u << r))) mask |= 1u << r; 670 } 671 return mask; 672 } 673 674 /* Acquire a lease satisfying an inline-asm register constraint. Unlike the 675 * general temporary picker this may name a fixed ABI register (for example 676 * x86 "a"). Regalloc has already kept values live across the asm out of that 677 * register; the availability check enforces that contract at the boundary. */ 678 static Reg temp_acquire_asm(NativeEmitCtx* e, NativeAllocClass cls, Reg fixed, 679 u32 allowed_mask, SrcLoc loc) { 680 u32 c = (u32)cls; 681 if (!e->temps || c >= OPT_REG_CLASSES) 682 emit_panic(e, loc, "asm register staging outside temporary scope"); 683 temp_scope_require_live_after(e); 684 if (fixed != (Reg)REG_NONE) { 685 if (!asm_temp_reg_ok(e, cls, fixed, allowed_mask) || 686 !temp_reg_available(e, cls, fixed, REG_NONE, REG_NONE)) 687 emit_panic(e, loc, "fixed asm staging register is unavailable"); 688 e->temps->leased[c] |= 1u << fixed; 689 return fixed; 690 } 691 for (u32 i = 0; i < e->f->emit_temp_reg_count[c]; ++i) { 692 Reg r = e->f->emit_temp_regs[c][i]; 693 /* The O1 operand-temp bank is deliberately hidden from user 694 * named-register requirements and allocator homes, but it is valid as an 695 * internally substituted unrestricted asm operand. Restricted masks still 696 * apply. */ 697 if (r >= 32u || (allowed_mask && !(allowed_mask & (1u << r))) || 698 !temp_reg_available(e, cls, r, REG_NONE, REG_NONE)) 699 continue; 700 e->temps->leased[c] |= 1u << r; 701 return r; 702 } 703 if (e->temps->allow_asm_temps) { 704 const NativeAllocClassInfo* ci = emit_class_info(e, cls); 705 for (u32 i = 0; ci && i < ci->nasm_temps; ++i) { 706 Reg r = ci->asm_temps[i]; 707 if (r >= 32u || (allowed_mask && !(allowed_mask & (1u << r))) || 708 !temp_reg_available(e, cls, r, REG_NONE, REG_NONE)) 709 continue; 710 e->temps->leased[c] |= 1u << r; 711 return r; 712 } 713 } 714 /* An asm boundary may use every target-declared asm register, not just the 715 * allocator's general-purpose subset. This is what makes high-arity blocks 716 * possible without assigning a fixed register to an entire PReg live range. 717 * A setup prepass records any selected callee-saved registers in the known 718 * frame, so choosing one here remains ABI-safe. */ 719 for (u32 i = 0; i < e->f->opt_phys_reg_count[c]; ++i) { 720 Reg r = e->f->opt_phys_regs[c][i].reg; 721 if (!asm_temp_reg_ok(e, cls, r, allowed_mask) || 722 !temp_reg_available(e, cls, r, REG_NONE, REG_NONE)) 723 continue; 724 e->temps->leased[c] |= 1u << r; 725 return r; 726 } 727 compiler_panic(e->c, loc, 728 "opt native emit: no register available for asm operand " 729 "staging (class=%u leased=0x%08x unavailable=0x%08x " 730 "allowed=0x%08x)", 731 (unsigned)c, (unsigned)e->temps->leased[c], 732 (unsigned)e->temps->unavailable[c], 733 (unsigned)allowed_mask); 734 } 735 736 /* A leased register is owned by the current emission scope and may therefore 737 * be overwritten in place. An ordinary OPK_REG may still be live after the 738 * instruction even when the result itself is frame-resident, so reusing it as 739 * the result would silently clobber an allocator-owned value. */ 740 static int temp_reg_is_leased(NativeEmitCtx* e, NativeAllocClass cls, Reg r) { 741 u32 c = (u32)cls; 742 return e->temps && c < OPT_REG_CLASSES && r < 32u && 743 (e->temps->leased[c] & (1u << r)) != 0; 744 } 745 746 static int temp_available(NativeEmitCtx* e, NativeAllocClass cls, Reg a, 747 Reg b) { 748 u32 c = (u32)cls; 749 if (c < OPT_REG_CLASSES) { 750 for (u32 i = 0; i < e->f->emit_temp_reg_count[c]; ++i) { 751 Reg r = e->f->emit_temp_regs[c][i]; 752 if (temp_reg_available(e, cls, r, a, b)) return 1; 753 } 754 if (e->temps) { 755 temp_scope_require_live_after(e); 756 for (u32 i = 0; i < e->f->opt_hard_reg_count[c]; ++i) { 757 Reg r = e->f->opt_hard_regs[c][i]; 758 if (r < 32u && (e->f->opt_caller_saved[c] & (1u << r)) && 759 temp_reg_available(e, cls, r, a, b)) 760 return 1; 761 } 762 } 763 } 764 return 0; 765 } 766 767 static NativeLoc temp_loc_acquire(NativeEmitCtx* e, KitCgTypeId type, 768 NativeAllocClass cls, Reg a, Reg b, 769 SrcLoc loc) { 770 return loc_reg(type, cls, temp_acquire(e, cls, a, b, loc)); 771 } 772 773 static NativeFrameSlot map_slot(NativeEmitCtx* e, NativeFrameSlot slot, 774 SrcLoc loc) { 775 if (slot == NATIVE_FRAME_SLOT_NONE) return NATIVE_FRAME_SLOT_NONE; 776 if (slot > e->f->nframe_slots) emit_panic(e, loc, "bad frame slot"); 777 if (!e->slot_map[slot]) emit_panic(e, loc, "unmapped frame slot"); 778 return e->slot_map[slot]; 779 } 780 781 static MCLabel ensure_label(NativeEmitCtx* e, u32 block, SrcLoc loc) { 782 if (block >= e->f->nblocks) emit_panic(e, loc, "bad block label"); 783 if (e->labels[block] == MC_LABEL_NONE) 784 e->labels[block] = e->target->label_new(e->target); 785 return e->labels[block]; 786 } 787 788 static NativeAddr addr_from_loc(NativeEmitCtx* e, NativeLoc loc, 789 SrcLoc src_loc) { 790 NativeAddr addr; 791 memset(&addr, 0, sizeof addr); 792 addr.base_type = loc.type; 793 switch ((NativeLocKind)loc.kind) { 794 case NATIVE_LOC_FRAME: 795 addr.base_kind = NATIVE_ADDR_BASE_FRAME; 796 addr.base.frame = loc.v.frame; 797 return addr; 798 case NATIVE_LOC_FRAME_ADDR: 799 addr.base_kind = NATIVE_ADDR_BASE_FRAME; 800 addr.base.frame = loc.v.frame; 801 return addr; 802 case NATIVE_LOC_STACK: 803 addr.base_kind = NATIVE_ADDR_BASE_FRAME; 804 addr.base.frame = loc.v.stack.slot; 805 addr.offset = loc.v.stack.offset; 806 return addr; 807 case NATIVE_LOC_GLOBAL: 808 addr.base_kind = NATIVE_ADDR_BASE_GLOBAL; 809 addr.base.global.sym = loc.v.global.sym; 810 addr.base.global.addend = loc.v.global.addend; 811 return addr; 812 case NATIVE_LOC_REG: 813 addr.base_kind = NATIVE_ADDR_BASE_REG; 814 addr.cls = loc.cls; 815 addr.base.reg = loc.v.reg; 816 return addr; 817 case NATIVE_LOC_ADDR: 818 return loc.v.addr; 819 default: 820 emit_panic(e, src_loc, "location is not addressable"); 821 } 822 } 823 824 static NativeAddr addr_from_operand(NativeEmitCtx* e, const OptOperand* op, 825 SrcLoc loc) { 826 NativeAddr addr; 827 memset(&addr, 0, sizeof addr); 828 if (!op) emit_panic(e, loc, "missing address operand"); 829 addr.base_type = op->type; 830 switch ((OptOperandKind)op->kind) { 831 case OPT_OPK_LOCAL: 832 addr.base_kind = NATIVE_ADDR_BASE_FRAME; 833 addr.base.frame = map_slot(e, op->v.frame_slot, loc); 834 return addr; 835 case OPT_OPK_FRAME_ADDR: 836 addr.base_kind = NATIVE_ADDR_BASE_FRAME; 837 addr.base.frame = map_slot(e, op->v.frame_slot, loc); 838 return addr; 839 case OPT_OPK_STACK: 840 addr.base_type = op->type; 841 { 842 NativeFrameSlot mapped = map_slot(e, op->v.frame_slot, loc); 843 NativeAllocClass cls = class_for_type(e, addr.base_type); 844 NativeLoc held = frame_cache_take_slot( 845 e, op->v.frame_slot, loc_frame(addr.base_type, cls, mapped), cls, 846 addr.base_type, REG_NONE, REG_NONE); 847 if (held.kind == NATIVE_LOC_REG) { 848 addr.base_kind = NATIVE_ADDR_BASE_REG; 849 addr.cls = held.cls; 850 addr.base.reg = held.v.reg; 851 } else { 852 addr.base_kind = NATIVE_ADDR_BASE_FRAME_VALUE; 853 addr.base.frame = mapped; 854 } 855 } 856 return addr; 857 case OPT_OPK_GLOBAL: 858 addr.base_kind = NATIVE_ADDR_BASE_GLOBAL; 859 addr.base.global.sym = op->v.global.sym; 860 addr.base.global.addend = op->v.global.addend; 861 return addr; 862 case OPT_OPK_INDIRECT: 863 addr.cls = NATIVE_REG_INT; 864 addr.base_type = op->v.ind.base_type; 865 if (!addr.base_type) 866 emit_panic(e, loc, "indirect base has no location type"); 867 if (op->v.ind.base_kind == OPT_INDIRECT_FRAME) { 868 { 869 NativeFrameSlot mapped = map_slot(e, op->v.ind.base, loc); 870 NativeLoc held = frame_cache_take_slot( 871 e, op->v.ind.base, 872 loc_frame(addr.base_type, NATIVE_REG_INT, mapped), 873 NATIVE_REG_INT, addr.base_type, REG_NONE, REG_NONE); 874 if (held.kind == NATIVE_LOC_REG) { 875 addr.base_kind = NATIVE_ADDR_BASE_REG; 876 addr.base.reg = held.v.reg; 877 } else { 878 addr.base_kind = NATIVE_ADDR_BASE_FRAME_VALUE; 879 addr.base.frame = mapped; 880 } 881 } 882 } else if (op->v.ind.base_kind == OPT_INDIRECT_FRAME_ADDR) { 883 addr.base_kind = NATIVE_ADDR_BASE_FRAME; 884 addr.base.frame = map_slot(e, op->v.ind.base, loc); 885 } else { 886 addr.base_kind = NATIVE_ADDR_BASE_REG; 887 addr.base.reg = op->v.ind.base; 888 } 889 addr.index_cls = NATIVE_REG_INT; 890 if (op->v.ind.index_kind == OPT_INDIRECT_FRAME) { 891 addr.index_type = op->v.ind.index_type; 892 if (!addr.index_type) 893 emit_panic(e, loc, "indirect index has no location type"); 894 { 895 NativeFrameSlot mapped = map_slot(e, op->v.ind.index, loc); 896 NativeLoc held = frame_cache_take_slot( 897 e, op->v.ind.index, 898 loc_frame(addr.index_type, NATIVE_REG_INT, mapped), 899 NATIVE_REG_INT, addr.index_type, REG_NONE, REG_NONE); 900 if (held.kind == NATIVE_LOC_REG) { 901 addr.index_kind = NATIVE_ADDR_INDEX_REG; 902 addr.index.reg = held.v.reg; 903 } else { 904 addr.index_kind = NATIVE_ADDR_INDEX_FRAME_VALUE; 905 addr.index.frame = mapped; 906 } 907 } 908 } else if (op->v.ind.index_kind == OPT_INDIRECT_FRAME_ADDR) { 909 emit_panic(e, loc, "frame address cannot be an indirect index"); 910 } else if (op->v.ind.index == (Reg)REG_NONE) { 911 addr.index_kind = NATIVE_ADDR_INDEX_NONE; 912 } else { 913 addr.index_type = op->v.ind.index_type; 914 if (!addr.index_type) 915 emit_panic(e, loc, "indirect index has no location type"); 916 addr.index_kind = NATIVE_ADDR_INDEX_REG; 917 addr.index.reg = op->v.ind.index; 918 } 919 addr.log2_scale = op->v.ind.log2_scale; 920 /* L8 extend rider: OPT_IDX_EXT_* and NATIVE_ADDR_IDX_EXT_* share the 921 * 0=NONE/1=SXTW/2=UXTW encoding. Only set by the addr-fold recognition 922 * when the target advertised can_fold_extend_into_addr, so a backend 923 * without the capability always sees NONE. */ 924 addr.index_ext = op->v.ind.index_ext; 925 addr.offset = op->v.ind.ofs; 926 return addr; 927 case OPT_OPK_REG: 928 addr.base_kind = NATIVE_ADDR_BASE_REG; 929 addr.cls = op->cls; 930 addr.base.reg = op->v.reg; 931 return addr; 932 default: 933 emit_panic(e, loc, "operand is not addressable"); 934 } 935 } 936 937 static NativeAddr pointer_addr_from_operand(NativeEmitCtx* e, 938 const OptOperand* op, SrcLoc loc, 939 Reg avoid_a, Reg avoid_b) { 940 NativeAddr addr; 941 memset(&addr, 0, sizeof addr); 942 if (!op) emit_panic(e, loc, "missing pointer operand"); 943 addr.base_type = op->type; 944 switch ((OptOperandKind)op->kind) { 945 case OPT_OPK_LOCAL: { 946 NativeAddr frame; 947 NativeLoc dst; 948 NativeAllocClass cls; 949 Reg r; 950 /* An OPK_LOCAL in a pointer-address position is ambiguous. When the 951 * operand's type is a pointer, the local *holds* the pointer value and 952 * must be loaded to get the address. Otherwise the local *is* the 953 * aggregate storage and its frame home is the address directly — loading 954 * it would dereference the aggregate's first 8 bytes as a pointer (e.g. 955 * an `__int128` call result copied by `agg_copy`). Mirrors the 956 * single-pass path's nd_addr_pointer. */ 957 if (!cg_type_is_ptr(e->c, op->type)) { 958 addr.base_kind = NATIVE_ADDR_BASE_FRAME; 959 addr.base.frame = map_slot(e, op->v.frame_slot, loc); 960 return addr; 961 } 962 cls = class_for_type(e, op->type); 963 r = temp_acquire(e, cls, avoid_a, avoid_b, loc); 964 memset(&frame, 0, sizeof frame); 965 frame.base_kind = NATIVE_ADDR_BASE_FRAME; 966 frame.base.frame = map_slot(e, op->v.frame_slot, loc); 967 frame.base_type = op->type; 968 dst = loc_reg(op->type, cls, r); 969 e->target->load(e->target, dst, frame, mem_for_type(e->c, op->type)); 970 addr.base_kind = NATIVE_ADDR_BASE_REG; 971 addr.cls = (u8)cls; 972 addr.base.reg = r; 973 return addr; 974 } 975 case OPT_OPK_FRAME_ADDR: 976 addr.base_kind = NATIVE_ADDR_BASE_FRAME; 977 addr.base.frame = map_slot(e, op->v.frame_slot, loc); 978 return addr; 979 case OPT_OPK_STACK: 980 addr.cls = class_for_type(e, op->type); 981 addr.base_type = op->type; 982 { 983 NativeFrameSlot mapped = map_slot(e, op->v.frame_slot, loc); 984 NativeAllocClass cls = (NativeAllocClass)addr.cls; 985 NativeLoc held = frame_cache_take_slot( 986 e, op->v.frame_slot, loc_frame(addr.base_type, cls, mapped), cls, 987 addr.base_type, avoid_a, avoid_b); 988 if (held.kind == NATIVE_LOC_REG) { 989 addr.base_kind = NATIVE_ADDR_BASE_REG; 990 addr.base.reg = held.v.reg; 991 } else { 992 addr.base_kind = NATIVE_ADDR_BASE_FRAME_VALUE; 993 addr.base.frame = mapped; 994 } 995 } 996 return addr; 997 case OPT_OPK_GLOBAL: 998 addr.base_kind = NATIVE_ADDR_BASE_GLOBAL; 999 addr.base.global.sym = op->v.global.sym; 1000 addr.base.global.addend = op->v.global.addend; 1001 return addr; 1002 case OPT_OPK_INDIRECT: 1003 return addr_from_operand(e, op, loc); 1004 case OPT_OPK_REG: 1005 addr.base_kind = NATIVE_ADDR_BASE_REG; 1006 addr.cls = op->cls; 1007 addr.base.reg = op->v.reg; 1008 return addr; 1009 default: 1010 emit_panic(e, loc, "operand is not a pointer address"); 1011 } 1012 } 1013 1014 static Reg addr_base_reg(const NativeAddr* addr) { 1015 return addr && addr->base_kind == NATIVE_ADDR_BASE_REG ? addr->base.reg 1016 : REG_NONE; 1017 } 1018 1019 static Reg addr_index_reg(const NativeAddr* addr) { 1020 return addr && addr->index_kind == NATIVE_ADDR_INDEX_REG ? addr->index.reg 1021 : REG_NONE; 1022 } 1023 1024 static void collapse_addr_to_reg(NativeEmitCtx* e, NativeAddr* addr, 1025 SrcLoc loc) { 1026 /* Materialize the full address into an instruction-local temporary. We 1027 * normally must not reuse the base register as the destination: physical MIR 1028 * may keep that value live past this memory op (e.g. a pointer stored into 1029 * several of its own fields and then returned), so an in-place `add base, 1030 * base, #off` would corrupt it. Avoid both base and index so load_addr can 1031 * still read them. 1032 * 1033 * If no third temporary is available and the base is itself a scoped 1034 * temporary, that base is transient and may be updated in place. load_addr 1035 * reads the base before adding the scaled index, so dst==base is sound; 1036 * dst==index would clobber the index before use and is never selected. If the 1037 * base is caller-owned, temp_acquire reports exhaustion instead of silently 1038 * changing its value. */ 1039 Reg base = addr_base_reg(addr); 1040 Reg index = addr_index_reg(addr); 1041 Reg r; 1042 if (temp_available(e, NATIVE_REG_INT, base, index)) 1043 r = temp_acquire(e, NATIVE_REG_INT, base, index, loc); 1044 else if (base != index && temp_reg_is_leased(e, NATIVE_REG_INT, base)) 1045 r = base; 1046 else 1047 r = temp_acquire(e, NATIVE_REG_INT, base, index, loc); /* diagnoses cleanly */ 1048 NativeLoc dst = loc_reg(addr->base_type, NATIVE_REG_INT, r); 1049 e->target->load_addr(e->target, dst, *addr); 1050 /* load_addr replaced any prior value fact attached to its destination. The 1051 * result remains leased; only the old cache identity is invalid now. */ 1052 temp_scope_drop_cached_reg(e, NATIVE_REG_INT, r); 1053 if (base != r) 1054 temp_scope_release_cached_reg(e, NATIVE_REG_INT, base); 1055 if (index != r) 1056 temp_scope_release_cached_reg(e, NATIVE_REG_INT, index); 1057 memset(addr, 0, sizeof *addr); 1058 addr->base_kind = NATIVE_ADDR_BASE_REG; 1059 addr->cls = NATIVE_REG_INT; 1060 addr->base.reg = r; 1061 addr->base_type = dst.type; 1062 } 1063 1064 /* Collapse an address the target cannot encode for this access (e.g. an 1065 * index scale aarch64 cannot fold into a load/store) into a single base 1066 * register via load_addr. Mirrors NativeDirectTarget's nd_addr_materialize so 1067 * the O1 emit path legalizes the same address shapes as direct -O0 emission. */ 1068 static void legalize_addr(NativeEmitCtx* e, NativeAddr* addr, MemAccess mem, 1069 SrcLoc loc) { 1070 if (e->target->addr_legal && !e->target->addr_legal(e->target, addr, mem)) 1071 collapse_addr_to_reg(e, addr, loc); 1072 } 1073 1074 static NativeLoc loc_from_operand(NativeEmitCtx* e, const OptOperand* op, 1075 SrcLoc loc) { 1076 if (!op) return loc_none(); 1077 switch ((OptOperandKind)op->kind) { 1078 case OPT_OPK_REG: 1079 return loc_reg(op->type, (NativeAllocClass)op->cls, op->v.reg); 1080 case OPT_OPK_IMM: 1081 return loc_imm(op->type, op->v.imm); 1082 case OPT_OPK_GLOBAL: 1083 return loc_global(op->type, op->v.global.sym, op->v.global.addend); 1084 case OPT_OPK_LOCAL: 1085 return loc_frame(op->type, class_for_type(e, op->type), 1086 map_slot(e, op->v.frame_slot, loc)); 1087 case OPT_OPK_STACK: 1088 return loc_frame(op->type, class_for_type(e, op->type), 1089 map_slot(e, op->v.frame_slot, loc)); 1090 case OPT_OPK_FRAME_ADDR: 1091 return loc_frame_addr(op->type, map_slot(e, op->v.frame_slot, loc)); 1092 case OPT_OPK_INDIRECT: { 1093 NativeLoc out = loc_none(); 1094 out.kind = NATIVE_LOC_ADDR; 1095 out.cls = op->cls; 1096 out.type = op->type; 1097 out.v.addr = addr_from_operand(e, op, loc); 1098 return out; 1099 } 1100 } 1101 emit_panic(e, loc, "bad operand kind"); 1102 } 1103 1104 static NativeLoc materialize(NativeEmitCtx* e, NativeLoc src, 1105 NativeAllocClass cls, KitCgTypeId type, 1106 Reg avoid_a, Reg avoid_b, SrcLoc loc) { 1107 NativeLoc dst; 1108 NativeAddr addr; 1109 MemAccess mem; 1110 if (src.kind == NATIVE_LOC_REG) return src; 1111 if (e->temps) { 1112 KitCgTypeId want_type = type ? type : src.type; 1113 for (u32 i = 0; i < e->temps->ncache; ++i) { 1114 NativeEmitTempCacheEntry* entry = &e->temps->cache[i]; 1115 Reg cached_reg = entry->result.v.reg; 1116 u32 cached_bit = cached_reg < 32u ? 1u << cached_reg : 0u; 1117 int avoided = cached_reg == avoid_a || cached_reg == avoid_b; 1118 if (entry->result.cls != (u8)cls || entry->result.type != want_type || 1119 (avoided && 1120 !(e->temps->cache_def_reuse[(u32)cls] & cached_bit))) 1121 continue; 1122 if (temp_loc_equal(entry->source, src)) return entry->result; 1123 } 1124 } 1125 dst = temp_loc_acquire(e, type ? type : src.type, cls, avoid_a, avoid_b, 1126 loc); 1127 switch ((NativeLocKind)src.kind) { 1128 case NATIVE_LOC_IMM: 1129 e->target->load_imm(e->target, dst, src.v.imm); 1130 break; 1131 case NATIVE_LOC_GLOBAL: 1132 addr = addr_from_loc(e, src, loc); 1133 e->target->load_addr(e->target, dst, addr); 1134 break; 1135 case NATIVE_LOC_FRAME_ADDR: 1136 addr = addr_from_loc(e, src, loc); 1137 e->target->load_addr(e->target, dst, addr); 1138 break; 1139 case NATIVE_LOC_FRAME: 1140 case NATIVE_LOC_STACK: 1141 case NATIVE_LOC_ADDR: 1142 addr = addr_from_loc(e, src, loc); 1143 mem = mem_for_type(e->c, dst.type); 1144 e->target->load(e->target, dst, addr, mem); 1145 break; 1146 default: 1147 emit_panic(e, loc, "cannot materialize location"); 1148 } 1149 if (e->temps && e->temps->ncache < NATIVE_EMIT_TEMP_CACHE) { 1150 NativeEmitTempCacheEntry* entry = &e->temps->cache[e->temps->ncache++]; 1151 entry->source = src; 1152 entry->result = dst; 1153 } 1154 return dst; 1155 } 1156 1157 static int frame_cache_slot_is_exact_spill(NativeEmitCtx* e, 1158 const OptOperand* op, 1159 MemAccess mem) { 1160 const IRFrameSlot* slot; 1161 if (!op || op->kind != OPK_STACK || op->v.frame_slot == FRAME_SLOT_NONE || 1162 op->v.frame_slot > e->f->nframe_slots) 1163 return 0; 1164 slot = &e->f->frame_slots[op->v.frame_slot - 1u]; 1165 /* Spill-slot coloring may reuse one slot for different, non-overlapping type 1166 * IDs. Exactness is the stored bit width; the cache entry separately records 1167 * the current value's type/class and only answers an identical use. */ 1168 return slot->kind == FS_SPILL && slot->size >= mem.size && 1169 mem.size == type_size_or(e->c, op->type, 0u); 1170 } 1171 1172 static void frame_cache_invalidate_slot(NativeEmitCtx* e, FrameSlot slot) { 1173 if (slot == FRAME_SLOT_NONE) return; 1174 for (u32 c = 0; c < OPT_REG_CLASSES; ++c) 1175 for (Reg r = 0; r < OPT_MAX_HARD_REGS; ++r) 1176 if (e->frame_cache_slot[c][r] == slot) 1177 frame_cache_drop_reg(e, c, r); 1178 } 1179 1180 static int frame_cache_types_compatible(NativeEmitCtx* e, 1181 KitCgTypeId cached, 1182 KitCgTypeId requested) { 1183 (void)e; 1184 return cached == requested; 1185 } 1186 1187 static NativeLoc frame_cache_take_slot(NativeEmitCtx* e, FrameSlot slot, 1188 NativeLoc source, 1189 NativeAllocClass cls, 1190 KitCgTypeId type, Reg avoid_a, 1191 Reg avoid_b) { 1192 u32 c = (u32)cls; 1193 if (!e->temps || slot == FRAME_SLOT_NONE || c >= OPT_REG_CLASSES) 1194 return loc_none(); 1195 for (Reg r = 0; r < OPT_MAX_HARD_REGS; ++r) { 1196 u32 bit = 1u << r; 1197 NativeLoc result; 1198 int avoided = r == avoid_a || r == avoid_b; 1199 if (!(e->frame_cache_held[c] & bit) || 1200 (avoided && !(e->temps->cache_def_reuse[c] & bit)) || 1201 e->frame_cache_slot[c][r] != slot || 1202 !frame_cache_types_compatible(e, e->frame_cache_type[c][r], type) || 1203 ((e->temps->unavailable[c] & bit) && 1204 !(e->temps->cache_def_reuse[c] & bit))) 1205 continue; 1206 frame_cache_drop_reg(e, c, r); 1207 e->temps->leased[c] |= bit; 1208 result = loc_reg(type, cls, r); 1209 if (e->temps->ncache < NATIVE_EMIT_TEMP_CACHE) { 1210 NativeEmitTempCacheEntry* entry = 1211 &e->temps->cache[e->temps->ncache++]; 1212 entry->source = source; 1213 entry->result = result; 1214 } 1215 return result; 1216 } 1217 return loc_none(); 1218 } 1219 1220 static NativeLoc frame_cache_take(NativeEmitCtx* e, const OptOperand* op, 1221 NativeLoc source, NativeAllocClass cls, 1222 KitCgTypeId type, Reg avoid_a, Reg avoid_b) { 1223 if (!op || op->kind != OPK_STACK) return loc_none(); 1224 return frame_cache_take_slot(e, op->v.frame_slot, source, cls, type, 1225 avoid_a, avoid_b); 1226 } 1227 1228 static void frame_cache_note_store(NativeEmitCtx* e, const OptOperand* op, 1229 NativeLoc src, MemAccess mem) { 1230 NativeAllocClass cls; 1231 u32 c, bit; 1232 Reg r; 1233 int retainable; 1234 if (!op || op->kind != OPK_STACK) return; 1235 frame_cache_invalidate_slot(e, op->v.frame_slot); 1236 if (!e->temps || src.kind != NATIVE_LOC_REG || 1237 !frame_cache_slot_is_exact_spill(e, op, mem)) 1238 return; 1239 cls = (NativeAllocClass)src.cls; 1240 c = (u32)cls; 1241 r = src.v.reg; 1242 if (c >= OPT_REG_CLASSES || r >= OPT_MAX_HARD_REGS) return; 1243 bit = 1u << r; 1244 /* Cross-instruction retention follows the explicit NativeTarget preservation 1245 * contract: declared cache-enabled emission temps plus liveness-proven dead 1246 * O1 allocation registers 1247 * whose caller/callee-save preservation is established, with every 1248 * exceptional fixed clobber represented by the current instruction's 1249 * exhaustive machine effect. */ 1250 retainable = (frame_cache_retainable_mask(e, cls) & bit) != 0; 1251 if (!retainable) return; 1252 frame_cache_drop_reg(e, c, r); 1253 e->temps->leased[c] &= ~bit; /* transfer scope ownership to block cache */ 1254 e->frame_cache_slot[c][r] = op->v.frame_slot; 1255 e->frame_cache_type[c][r] = op->type; 1256 e->frame_cache_held[c] |= bit; 1257 } 1258 1259 static NativeLoc materialize_operand(NativeEmitCtx* e, const OptOperand* op, 1260 NativeAllocClass cls, KitCgTypeId type, 1261 Reg avoid_a, Reg avoid_b, SrcLoc loc) { 1262 NativeLoc source = loc_from_operand(e, op, loc); 1263 NativeLoc cached = frame_cache_take(e, op, source, cls, 1264 type ? type : source.type, avoid_a, 1265 avoid_b); 1266 if (cached.kind != NATIVE_LOC_NONE) return cached; 1267 return materialize(e, source, cls, type, avoid_a, avoid_b, loc); 1268 } 1269 1270 static void write_loc(NativeEmitCtx* e, NativeLoc dst, NativeLoc src, 1271 MemAccess mem, SrcLoc loc) { 1272 NativeAddr addr; 1273 NativeEmitTempMark mark = temp_scope_mark(e); 1274 if (dst.kind == NATIVE_LOC_NONE) goto done; 1275 if (loc_same_frame(dst, src)) goto done; 1276 if (dst.kind == NATIVE_LOC_REG) { 1277 if (src.kind == NATIVE_LOC_REG) { 1278 if (dst.v.reg != src.v.reg || dst.cls != src.cls) 1279 e->target->move(e->target, dst, src); 1280 goto done; 1281 } 1282 /* An immediate goes straight into the destination register; routing it 1283 * through an instruction-local temp and then moving would cost an extra 1284 * instruction. */ 1285 if (src.kind == NATIVE_LOC_IMM) { 1286 e->target->load_imm(e->target, dst, src.v.imm); 1287 goto done; 1288 } 1289 /* A register destination is itself the best materialization register. 1290 * Loading through an emitter temp first adds a move to every spill reload 1291 * and ABI/frame copy. Native loads consume their address before defining 1292 * the destination, so even an address based on dst is safe here. */ 1293 if (src.kind == NATIVE_LOC_GLOBAL || 1294 src.kind == NATIVE_LOC_FRAME_ADDR) { 1295 addr = addr_from_loc(e, src, loc); 1296 e->target->load_addr(e->target, dst, addr); 1297 goto done; 1298 } 1299 if (src.kind == NATIVE_LOC_FRAME || src.kind == NATIVE_LOC_STACK || 1300 src.kind == NATIVE_LOC_ADDR) { 1301 addr = addr_from_loc(e, src, loc); 1302 e->target->load(e->target, dst, addr, mem); 1303 goto done; 1304 } 1305 emit_panic(e, loc, "cannot write location to register"); 1306 } 1307 addr = addr_from_loc(e, dst, loc); 1308 if (src.kind != NATIVE_LOC_REG) 1309 src = materialize(e, src, (NativeAllocClass)dst.cls, dst.type, REG_NONE, 1310 REG_NONE, loc); 1311 e->target->store(e->target, addr, src, mem); 1312 done: 1313 temp_scope_rewind(e, mark); 1314 } 1315 1316 /* Execute one move in a multi-move ABI phase and retain its register 1317 * destination. The backend call/return marshallers promise that moves are 1318 * source-safe in the order returned; this helper supplies the complementary 1319 * destination-lifetime guarantee for emitter-side materialization. */ 1320 static void write_phase_loc(NativeEmitCtx* e, NativeLoc dst, NativeLoc src, 1321 MemAccess mem, SrcLoc loc) { 1322 write_loc(e, dst, src, mem, loc); 1323 temp_scope_hold_loc(e, dst); 1324 } 1325 1326 static void write_operand(NativeEmitCtx* e, const OptOperand* dst, 1327 NativeLoc src, MemAccess mem, SrcLoc loc) { 1328 NativeLoc real_dst = loc_from_operand(e, dst, loc); 1329 /* Keep the register that actually reaches a spill store. Besides avoiding a 1330 * second materialization in write_loc, this lets ownership transfer from the 1331 * instruction lease to the clean block cache. Constants and copies into a 1332 * spill home are common producers and otherwise lose their forwarding fact 1333 * at this abstraction boundary. */ 1334 if (dst && dst->kind == OPK_STACK && src.kind != NATIVE_LOC_REG && 1335 !loc_same_frame(real_dst, src)) { 1336 /* Keep integer zero in the target's architectural zero register when it 1337 * can be stored directly. Materializing it into an emitter temp costs an 1338 * otherwise redundant movz/li on every zero-initialized spill. */ 1339 if (src.kind == NATIVE_LOC_IMM && src.v.imm == 0 && 1340 e->target->has_store_zero_reg && 1341 class_for_type(e, real_dst.type) == NATIVE_REG_INT) 1342 src = loc_reg(real_dst.type, NATIVE_REG_INT, 1343 e->target->store_zero_reg); 1344 else 1345 src = materialize(e, src, (NativeAllocClass)real_dst.cls, real_dst.type, 1346 REG_NONE, REG_NONE, loc); 1347 } 1348 write_loc(e, real_dst, src, mem, loc); 1349 frame_cache_note_store(e, dst, src, mem); 1350 } 1351 1352 /* Place a value directly in an already-leased register. This is the boundary 1353 * operation used for fixed/constrained asm operands: going through materialize 1354 * would acquire an unrelated temporary and then copy, which both wastes a 1355 * register and can make a satisfiable high-arity asm look impossible. */ 1356 static void load_loc_into_reg(NativeEmitCtx* e, NativeLoc dst, NativeLoc src, 1357 SrcLoc loc) { 1358 NativeAddr addr; 1359 if (dst.kind != NATIVE_LOC_REG) 1360 emit_panic(e, loc, "staging destination is not a register"); 1361 switch ((NativeLocKind)src.kind) { 1362 case NATIVE_LOC_REG: 1363 if (src.v.reg != dst.v.reg || src.cls != dst.cls) 1364 e->target->move(e->target, dst, src); 1365 return; 1366 case NATIVE_LOC_IMM: 1367 e->target->load_imm(e->target, dst, src.v.imm); 1368 return; 1369 case NATIVE_LOC_GLOBAL: 1370 case NATIVE_LOC_FRAME_ADDR: 1371 addr = addr_from_loc(e, src, loc); 1372 e->target->load_addr(e->target, dst, addr); 1373 return; 1374 case NATIVE_LOC_FRAME: 1375 case NATIVE_LOC_STACK: 1376 case NATIVE_LOC_ADDR: 1377 addr = addr_from_loc(e, src, loc); 1378 e->target->load(e->target, dst, addr, mem_for_type(e->c, dst.type)); 1379 return; 1380 default: 1381 emit_panic(e, loc, "cannot stage asm operand in a register"); 1382 } 1383 } 1384 1385 static int asm_reg_loc_satisfies(NativeEmitCtx* e, NativeLoc loc, 1386 NativeAllocClass cls, Reg fixed, 1387 u32 allowed_mask) { 1388 if (loc.kind != NATIVE_LOC_REG || loc.cls != (u8)cls) return 0; 1389 if (fixed != (Reg)REG_NONE && loc.v.reg != fixed) return 0; 1390 return asm_temp_reg_ok(e, cls, loc.v.reg, allowed_mask); 1391 } 1392 1393 static int asm_reg_requirement(NativeEmitCtx* e, 1394 const IRAsmRegRequirement* req, 1395 NativeAllocClass* cls, Reg* fixed, 1396 u32* allowed, SrcLoc loc) { 1397 if (!req || !req->present) return 0; 1398 if (req->cls >= OPT_REG_CLASSES || req->fixed_reg < -1 || 1399 req->fixed_reg >= (i32)OPT_MAX_HARD_REGS) 1400 emit_panic(e, loc, "invalid pre-resolved asm register requirement"); 1401 *cls = (NativeAllocClass)req->cls; 1402 *fixed = req->fixed_reg < 0 ? REG_NONE : (Reg)req->fixed_reg; 1403 *allowed = req->allowed_mask; 1404 return 1; 1405 } 1406 1407 /* Localize an asm register constraint at the instruction boundary. An 1408 * unrestricted allocated register may already satisfy it; fixed/subset 1409 * requirements are normally fulfilled by scoped staging. No emitter-owned 1410 * register becomes a MIR home. */ 1411 static NativeLoc asm_stage_reg_operand(NativeEmitCtx* e, 1412 const AsmConstraint* constraint, 1413 const IRAsmRegRequirement* req, 1414 NativeLoc real, int load_value, 1415 int* staged, SrcLoc loc) { 1416 NativeAllocClass cls; 1417 KitCgTypeId type; 1418 Reg fixed; 1419 u32 allowed; 1420 Reg r; 1421 NativeLoc out; 1422 if (staged) *staged = 0; 1423 if (!asm_reg_requirement(e, req, &cls, &fixed, &allowed, loc)) 1424 return real; 1425 type = constraint->type ? constraint->type : real.type; 1426 if (asm_reg_loc_satisfies(e, real, cls, fixed, allowed)) return real; 1427 r = temp_acquire_asm(e, cls, fixed, allowed, loc); 1428 out = loc_reg(type, cls, r); 1429 if (load_value) load_loc_into_reg(e, out, real, loc); 1430 if (staged) *staged = 1; 1431 return out; 1432 } 1433 1434 /* For an arithmetic / compare source operand: keep it as an immediate when it 1435 * is a constant the target can encode for `use` (so no register is wasted 1436 * materializing it); otherwise materialize into a register. */ 1437 static NativeLoc operand_imm_or_reg(NativeEmitCtx* e, const OptOperand* op, 1438 NativeImmUse use, u32 sub, Reg avoid_a, 1439 Reg avoid_b, SrcLoc loc) { 1440 if (op->kind == OPK_IMM && e->target->imm_legal && 1441 e->target->imm_legal(e->target, use, sub, op->type, op->v.imm)) 1442 return loc_imm(op->type, op->v.imm); 1443 return materialize_operand(e, op, class_for_type(e, op->type), op->type, 1444 avoid_a, avoid_b, loc); 1445 } 1446 1447 static Reg loc_avoid_reg(NativeLoc l) { 1448 return l.kind == NATIVE_LOC_REG ? l.v.reg : REG_NONE; 1449 } 1450 1451 static int type_is_aggregate_or_large(NativeEmitCtx* e, KitCgTypeId type) { 1452 /* "Large" = wider than one machine word (ptr_size): such a value cannot move 1453 * through a single register, so IR_COPY/IR_LOAD/IR_STORE of it must go 1454 * through copy_bytes. 8 on rv64/x64/aa64, 4 on rv32 (so an 8-byte i64/double 1455 * is large there and is copied as two words rather than truncated into one 1456 * register). */ 1457 return type && (cg_type_is_aggregate(e->c, type) || 1458 type_size_or(e->c, type, 8u) > e->c->target.ptr_size); 1459 } 1460 1461 /* Acquire the register that receives a scalar memory load whose MIR result has 1462 * a frame home. If the address already consumes every available value temp, 1463 * first collapse it to one plain register. A leased integer base is then dead 1464 * after address evaluation and may receive the loaded scalar in place: every 1465 * NativeTarget load hook consumes a simple effective address before writing 1466 * its destination. Never apply this to an allocator-owned base, an FP result, 1467 * or a value wider than one machine register. */ 1468 static NativeLoc acquire_frame_load_result(NativeEmitCtx* e, 1469 KitCgTypeId type, 1470 NativeAddr* addr, SrcLoc loc) { 1471 NativeAllocClass cls = class_for_type(e, type); 1472 Reg base = addr_base_reg(addr); 1473 Reg index = addr_index_reg(addr); 1474 if (temp_available(e, cls, base, index)) 1475 return temp_loc_acquire(e, type, cls, base, index, loc); 1476 1477 collapse_addr_to_reg(e, addr, loc); 1478 base = addr_base_reg(addr); 1479 if (cls == NATIVE_REG_INT && 1480 type_size_or(e->c, type, e->c->target.ptr_size) <= 1481 e->c->target.ptr_size && 1482 addr->base_kind == NATIVE_ADDR_BASE_REG && 1483 addr->index_kind == NATIVE_ADDR_INDEX_NONE && addr->offset == 0 && 1484 temp_reg_is_leased(e, NATIVE_REG_INT, base)) 1485 return loc_reg(type, cls, base); 1486 1487 return temp_loc_acquire(e, type, cls, base, addr_index_reg(addr), loc); 1488 } 1489 1490 /* Materialize an address whose MIR destination has a frame home. A normal 1491 * free temp receives the address directly. Under full pressure, collapse the 1492 * expression into its leased base and return that already-materialized value; 1493 * collapse_addr_to_reg refuses to overwrite an allocator-owned base. */ 1494 static NativeLoc emit_frame_address_result(NativeEmitCtx* e, 1495 KitCgTypeId type, 1496 NativeAddr* addr, SrcLoc loc) { 1497 NativeAllocClass cls = class_for_type(e, type); 1498 Reg base = addr_base_reg(addr); 1499 Reg index = addr_index_reg(addr); 1500 NativeLoc dst; 1501 if (temp_available(e, cls, base, index)) { 1502 dst = temp_loc_acquire(e, type, cls, base, index, loc); 1503 e->target->load_addr(e->target, dst, *addr); 1504 return dst; 1505 } 1506 collapse_addr_to_reg(e, addr, loc); 1507 base = addr_base_reg(addr); 1508 if (cls != NATIVE_REG_INT || 1509 !temp_reg_is_leased(e, NATIVE_REG_INT, base)) 1510 emit_panic(e, loc, "materialized address has no scoped integer owner"); 1511 return loc_reg(type, cls, base); 1512 } 1513 1514 /* Materialize a scalar value while an effective address remains live. If 1515 * the address consumes the whole value-temp bank, reduce it to one scoped 1516 * base first; collapse releases cache-owned components that are dead after 1517 * address evaluation. The value then explicitly avoids the surviving base 1518 * and index, keeping the backend hook's address and data roles disjoint. */ 1519 static NativeLoc materialize_operand_away_from_addr( 1520 NativeEmitCtx* e, const OptOperand* op, NativeAddr* addr, SrcLoc loc) { 1521 NativeAllocClass cls = class_for_type(e, op->type); 1522 if (!temp_available(e, cls, addr_base_reg(addr), addr_index_reg(addr))) 1523 collapse_addr_to_reg(e, addr, loc); 1524 return materialize_operand(e, op, cls, op->type, addr_base_reg(addr), 1525 addr_index_reg(addr), loc); 1526 } 1527 1528 /* Copy an aggregate / oversized value between two memory locations. dst and 1529 * src must be addressable (frame/global/indirect/reg-as-pointer); used for 1530 * IR_COPY/IR_LOAD/IR_STORE whose value type cannot move through one register. 1531 */ 1532 static void emit_agg_move(NativeEmitCtx* e, NativeAddr da, NativeAddr sa, 1533 KitCgTypeId type, SrcLoc loc) { 1534 AggregateAccess acc; 1535 memset(&acc, 0, sizeof acc); 1536 acc.type = type; 1537 acc.size = type_size_or(e->c, type, 8u); 1538 acc.align = type_align_or(e->c, type, 8u); 1539 acc.mem = mem_for_type(e->c, type); 1540 /* Legalize both addresses exactly as the scalar load/store path does: a 1541 * copy_bytes granule ladder addresses base+offset only, so any addressing mode 1542 * the target cannot encode — notably a scaled index, which an 8-byte i64/double 1543 * load on a 32-bit target arrives with (it is a "large" move here, the array 1544 * index folded into the address) — must collapse into a plain base register 1545 * first. Without this the index is silently dropped and every element aliases 1546 * element 0. */ 1547 legalize_addr(e, &da, acc.mem, loc); 1548 legalize_addr(e, &sa, acc.mem, loc); 1549 e->target->copy_bytes(e->target, da, sa, acc); 1550 } 1551 1552 static CGFuncDesc semantic_func_desc(NativeEmitCtx* e) { 1553 OptCGFuncDesc* in = &e->f->desc; 1554 CGFuncDesc out; 1555 memset(&out, 0, sizeof out); 1556 out.sym = in->sym; 1557 out.text_section_id = in->text_section_id; 1558 out.group_id = in->group_id; 1559 out.fn_type = in->fn_type; 1560 out.result_type = in->result_type; 1561 out.nparams = in->nparams; 1562 out.loc = in->loc; 1563 out.flags = in->flags; 1564 out.inline_policy = in->inline_policy; 1565 out.atomize = in->atomize; 1566 if (in->nparams && in->params) { 1567 CGParamDesc* params = arena_zarray(e->f->arena, CGParamDesc, in->nparams); 1568 for (u32 i = 0; i < in->nparams; ++i) { 1569 params[i].index = in->params[i].index; 1570 params[i].name = in->params[i].name; 1571 params[i].type = in->params[i].type; 1572 params[i].size = in->params[i].size; 1573 params[i].align = in->params[i].align; 1574 params[i].flags = in->params[i].flags; 1575 params[i].loc = in->params[i].loc; 1576 } 1577 out.params = params; 1578 } 1579 return out; 1580 } 1581 1582 static CGParamDesc semantic_param_desc(const IRParam* p) { 1583 CGParamDesc out; 1584 memset(&out, 0, sizeof out); 1585 out.index = p->index; 1586 out.name = p->name; 1587 out.type = p->type; 1588 out.size = p->size; 1589 out.align = p->align; 1590 out.flags = p->flags; 1591 out.loc = p->loc; 1592 return out; 1593 } 1594 1595 static NativeLoc loc_for_preg(NativeEmitCtx* e, PReg preg, KitCgTypeId type, 1596 SrcLoc loc) { 1597 u8 kind = opt_preg_alloc_kind(e->f, preg); 1598 if (kind == OPT_ALLOC_HARD) 1599 return loc_reg(type, (NativeAllocClass)opt_preg_loc_cls(e->f, preg), 1600 opt_preg_hard_reg(e->f, preg)); 1601 if (kind == OPT_ALLOC_SPILL) 1602 return loc_frame(type, class_for_type(e, type), 1603 map_slot(e, opt_preg_spill_slot(e->f, preg), loc)); 1604 return loc_none(); 1605 } 1606 1607 static void bind_params(NativeEmitCtx* e) { 1608 for (u32 i = 0; i < e->f->nparams; ++i) { 1609 IRParam* p = &e->f->params[i]; 1610 CGParamDesc sd = semantic_param_desc(p); 1611 NativeLoc dst; 1612 if (p->storage.kind == CG_LOCAL_STORAGE_REG) 1613 dst = loc_for_preg(e, (PReg)p->storage.v.reg, p->type, p->loc); 1614 else 1615 dst = loc_frame(p->type, class_for_type(e, p->type), 1616 map_slot(e, p->storage.v.frame_slot, p->loc)); 1617 if (e->target->bind_param) e->target->bind_param(e->target, &sd, dst); 1618 } 1619 /* Let a backend that defers register-destination binds resolve them now (as a 1620 * parallel copy), once every param's incoming location has been read. */ 1621 if (e->target->bind_params_end) e->target->bind_params_end(e->target); 1622 } 1623 1624 /* The parameter value is placed into its allocated location by bind_param at 1625 * function entry; the IR_PARAM_DECL marker emits nothing. */ 1626 static void emit_param_decl(NativeEmitCtx* e, Inst* in) { 1627 (void)e; 1628 (void)in; 1629 } 1630 1631 static NativeFrameSlot temp_slot(NativeEmitCtx* e, KitCgTypeId type, SrcLoc loc, 1632 NativeFrameSlotKind kind) { 1633 NativeFrameSlotDesc d; 1634 memset(&d, 0, sizeof d); 1635 d.type = type; 1636 d.loc = loc; 1637 d.size = type_size_or(e->c, type, 8u); 1638 d.align = type_align_or(e->c, type, d.size >= 8u ? 8u : d.size); 1639 d.kind = kind; 1640 return e->target->frame_slot(e->target, &d); 1641 } 1642 1643 static NativeLoc abi_storage_loc(NativeEmitCtx* e, const OptCGABIValue* v, 1644 SrcLoc loc) { 1645 if (!v) return loc_none(); 1646 return loc_from_operand(e, &v->storage, loc); 1647 } 1648 1649 static void emit_call(NativeEmitCtx* e, Inst* in) { 1650 IRCallAux* aux = (IRCallAux*)in->extra.aux; 1651 NativeCallDesc d; 1652 NativeCallPhase plan; 1653 NativeLoc* args = NULL; 1654 NativeLoc* results = NULL; 1655 NativeLoc final_result = loc_none(); 1656 NativeFrameSlot result_slot = NATIVE_FRAME_SLOT_NONE; 1657 MemAccess result_mem; 1658 if (!aux) return; 1659 memset(&d, 0, sizeof d); 1660 memset(&plan, 0, sizeof plan); 1661 if (aux->desc.nargs) 1662 args = arena_zarray(e->f->arena, NativeLoc, aux->desc.nargs); 1663 for (u32 i = 0; i < aux->desc.nargs; ++i) 1664 args[i] = abi_storage_loc(e, &aux->desc.args[i], in->loc); 1665 if (aux->desc.ret.storage.kind) { 1666 KitCgTypeId rty = aux->desc.ret.type; 1667 results = arena_zarray(e->f->arena, NativeLoc, 1); 1668 final_result = abi_storage_loc(e, &aux->desc.ret, in->loc); 1669 /* Hand marshal_call the value's real destination directly whenever it is a 1670 * register or a frame slot: a scalar result is a single move out of the ABI 1671 * result register, and an aggregate / oversized result — which marshal_call or 1672 * the callee writes in parts and so must land in memory — lands straight in 1673 * its frame home. Routing either through a fresh temp slot (store then 1674 * reload / copy_bytes) was a pure round trip on every call. The temp slot 1675 * is a fallback for the rare result whose storage is neither a register nor 1676 * a frame slot (e.g. written into a global); lowering hoists aggregates to 1677 * a frame home (opt_lower_to_mir), so this branch is scalar-only in 1678 * practice. */ 1679 if (final_result.kind == NATIVE_LOC_REG || 1680 final_result.kind == NATIVE_LOC_FRAME) { 1681 results[0] = final_result; 1682 } else { 1683 result_slot = temp_slot(e, rty, in->loc, NATIVE_FRAME_SLOT_SPILL); 1684 results[0] = loc_frame(rty, class_for_type(e, rty), result_slot); 1685 } 1686 } 1687 d.fn_type = aux->desc.fn_type; 1688 d.callee = loc_from_operand(e, &aux->desc.callee, in->loc); 1689 d.args = args; 1690 d.results = results; 1691 d.nargs = aux->desc.nargs; 1692 d.nresults = results ? 1u : 0u; 1693 d.flags = aux->desc.flags; 1694 d.tail_policy = aux->desc.tail_policy; 1695 d.inline_policy = aux->desc.inline_policy; 1696 /* Materialize a frame-resident indirect callee before marshal_call starts its 1697 * argument phase. Real backends marshal register arguments inside 1698 * marshal_call; doing this afterward allowed the emitter to reuse an already 1699 * populated ABI argument register for the callee. The acquired lease also 1700 * keeps generic phase argument moves from selecting the callee register. */ 1701 if (d.callee.kind != NATIVE_LOC_REG && 1702 d.callee.kind != NATIVE_LOC_GLOBAL) 1703 d.callee = materialize(e, d.callee, NATIVE_REG_INT, d.callee.type, 1704 REG_NONE, REG_NONE, in->loc); 1705 e->target->marshal_call(e->target, &d, &plan); 1706 for (u32 i = 0; i < plan.nargs; ++i) 1707 write_phase_loc(e, plan.args[i].dst, plan.args[i].src, plan.args[i].mem, 1708 in->loc); 1709 e->target->emit_call(e->target, &plan); 1710 temp_scope_phase_barrier(e); 1711 for (u32 i = 0; i < plan.nrets; ++i) 1712 write_phase_loc(e, plan.rets[i].dst, plan.rets[i].src, plan.rets[i].mem, 1713 in->loc); 1714 if (result_slot && final_result.kind != NATIVE_LOC_NONE) { 1715 KitCgTypeId rty = aux->desc.ret.type; 1716 NativeLoc tmp = loc_frame(rty, class_for_type(e, rty), result_slot); 1717 result_mem = mem_for_type(e->c, rty); 1718 if (final_result.kind != NATIVE_LOC_REG && 1719 (cg_type_is_aggregate(e->c, rty) || 1720 type_size_or(e->c, rty, 8u) > e->c->target.ptr_size)) { 1721 /* Aggregate / oversized result: move bytes rather than a scalar copy 1722 * (which would exceed the single-register width). The result was either 1723 * written in parts by marshal_call's rets, or by the callee via the sret 1724 * pointer; either way it now lives in the temp slot. */ 1725 AggregateAccess acc; 1726 NativeAddr da = addr_from_loc(e, final_result, in->loc); 1727 NativeAddr sa = addr_from_loc(e, tmp, in->loc); 1728 memset(&acc, 0, sizeof acc); 1729 acc.type = rty; 1730 acc.size = type_size_or(e->c, rty, 8u); 1731 acc.align = type_align_or(e->c, rty, 8u); 1732 acc.mem = result_mem; 1733 e->target->copy_bytes(e->target, da, sa, acc); 1734 } else { 1735 write_loc(e, final_result, tmp, result_mem, in->loc); 1736 } 1737 } 1738 } 1739 1740 static void emit_ret(NativeEmitCtx* e, Inst* in, const CGFuncDesc* fd) { 1741 IRRetAux* aux = (IRRetAux*)in->extra.aux; 1742 NativeLoc value = loc_none(); 1743 const NativeLoc* values = NULL; 1744 NativeCallPhaseRet* rets = NULL; 1745 u32 nrets = 0; 1746 if (aux && aux->present) { 1747 /* Hand marshal_ret the value's location directly. For an aggregate / oversized 1748 * result it is a memory location (marshal_ret copies to the sret pointer or 1749 * reads parts into the return registers); for a scalar it is the value's 1750 * register or slot, which marshal_ret moves into the return register. The old 1751 * code spilled scalars to a fresh slot and reloaded them, a pure round 1752 * trip on every return. */ 1753 value = abi_storage_loc(e, &aux->val, in->loc); 1754 values = &value; 1755 } 1756 e->target->marshal_ret(e->target, fd, values, &rets, &nrets); 1757 for (u32 i = 0; i < nrets; ++i) 1758 write_phase_loc(e, rets[i].dst, rets[i].src, rets[i].mem, in->loc); 1759 /* Skip the trailing branch-to-epilogue when this IR_RET is the very last 1760 * inst emitted: func_end will place the epilogue label at mc_pos right 1761 * after this, so the branch would jump to the next 4 bytes. The actual 1762 * `ret` instruction lives in func_end's restore-frame sequence and is 1763 * unaffected. */ 1764 if (!e->emitting_terminal_ret) e->target->ret(e->target); 1765 } 1766 1767 static void emit_inst_body(NativeEmitCtx* e, u32 block, u32 order_index, 1768 Inst* in, const CGFuncDesc* fd) { 1769 NativeLoc dst, a, b, src, tmp; 1770 NativeAddr addr, addr2; 1771 Reg dst_reg; 1772 (void)block; 1773 if (e->target->set_loc) e->target->set_loc(e->target, in->loc); 1774 switch ((IROp)in->op) { 1775 case IR_NOP: 1776 case IR_CONST_I: 1777 case IR_CONST_BYTES: 1778 case IR_PHI: 1779 case IR_SCOPE_BEGIN: 1780 case IR_SCOPE_END: 1781 return; 1782 case IR_PARAM_DECL: 1783 emit_param_decl(e, in); 1784 return; 1785 case IR_LOAD_IMM: 1786 dst = loc_from_operand(e, &in->opnds[0], in->loc); 1787 write_operand(e, &in->opnds[0], 1788 loc_imm(in->opnds[0].type, in->extra.imm), 1789 mem_for_type(e->c, in->opnds[0].type), in->loc); 1790 return; 1791 case IR_LOAD_CONST: { 1792 NativeLoc real = loc_from_operand(e, &in->opnds[0], in->loc); 1793 dst = real; 1794 if (dst.kind != NATIVE_LOC_REG) 1795 dst = temp_loc_acquire(e, in->opnds[0].type, 1796 class_for_type(e, in->opnds[0].type), REG_NONE, 1797 REG_NONE, in->loc); 1798 e->target->load_const(e->target, dst, in->extra.cbytes); 1799 if (real.kind != NATIVE_LOC_REG) 1800 write_operand(e, &in->opnds[0], dst, 1801 mem_for_type(e->c, in->opnds[0].type), in->loc); 1802 return; 1803 } 1804 case IR_COPY: 1805 if (type_is_aggregate_or_large(e, in->opnds[0].type)) { 1806 emit_agg_move(e, addr_from_operand(e, &in->opnds[0], in->loc), 1807 addr_from_operand(e, &in->opnds[1], in->loc), 1808 in->opnds[0].type, in->loc); 1809 return; 1810 } 1811 dst = loc_from_operand(e, &in->opnds[0], in->loc); 1812 src = loc_from_operand(e, &in->opnds[1], in->loc); 1813 if (in->opnds[1].kind == OPK_STACK) { 1814 NativeLoc cached = frame_cache_take( 1815 e, &in->opnds[1], src, 1816 class_for_type(e, in->opnds[1].type), in->opnds[1].type, 1817 REG_NONE, REG_NONE); 1818 if (cached.kind != NATIVE_LOC_NONE) src = cached; 1819 } 1820 write_operand(e, &in->opnds[0], src, 1821 mem_for_type(e->c, in->opnds[0].type), in->loc); 1822 return; 1823 case IR_LOAD: 1824 if (type_is_aggregate_or_large(e, in->opnds[0].type)) { 1825 addr = addr_from_operand(e, &in->opnds[1], in->loc); 1826 emit_agg_move(e, addr_from_operand(e, &in->opnds[0], in->loc), addr, 1827 in->opnds[0].type, in->loc); 1828 return; 1829 } 1830 dst = loc_from_operand(e, &in->opnds[0], in->loc); 1831 addr = addr_from_operand(e, &in->opnds[1], in->loc); 1832 legalize_addr(e, &addr, in->extra.mem, in->loc); 1833 if (dst.kind == NATIVE_LOC_REG) { 1834 e->target->load(e->target, dst, addr, in->extra.mem); 1835 } else { 1836 tmp = acquire_frame_load_result(e, in->opnds[0].type, &addr, in->loc); 1837 e->target->load(e->target, tmp, addr, in->extra.mem); 1838 write_operand(e, &in->opnds[0], tmp, in->extra.mem, in->loc); 1839 } 1840 return; 1841 case IR_STORE: 1842 if (type_is_aggregate_or_large(e, in->opnds[1].type)) { 1843 emit_agg_move(e, addr_from_operand(e, &in->opnds[0], in->loc), 1844 addr_from_operand(e, &in->opnds[1], in->loc), 1845 in->opnds[1].type, in->loc); 1846 return; 1847 } 1848 addr = addr_from_operand(e, &in->opnds[0], in->loc); 1849 legalize_addr(e, &addr, in->extra.mem, in->loc); 1850 src = loc_from_operand(e, &in->opnds[1], in->loc); 1851 /* Storing a constant 0 from the hardware zero register avoids 1852 * materializing 0 into an operand temp first (e.g. `strb wzr, [..]` 1853 * rather than `movz w9,0; strb w9, [..]`). */ 1854 if (src.kind == NATIVE_LOC_IMM && src.v.imm == 0 && 1855 e->target->has_store_zero_reg && 1856 class_for_type(e, in->opnds[1].type) == NATIVE_REG_INT) 1857 src = loc_reg(in->opnds[1].type, NATIVE_REG_INT, 1858 e->target->store_zero_reg); 1859 /* Source register aliases the address base/index (e.g. `*p = (T)p`). 1860 * Collapse the address into an instruction-local temp: 1861 * collapse_addr_to_reg selects one distinct from both base and index — 1862 * hence distinct from `src` — so the store reads `src` and writes through 1863 * the fresh temp with no alias. This stays entirely in registers; the 1864 * frame is fully planned before emission, so emit never allocates a slot 1865 * here. */ 1866 if (src.kind == NATIVE_LOC_REG && (src.v.reg == addr_base_reg(&addr) || 1867 src.v.reg == addr_index_reg(&addr))) 1868 collapse_addr_to_reg(e, &addr, in->loc); 1869 if (src.kind != NATIVE_LOC_REG) { 1870 src = materialize_operand_away_from_addr(e, &in->opnds[1], &addr, 1871 in->loc); 1872 } 1873 e->target->store(e->target, addr, src, in->extra.mem); 1874 return; 1875 case IR_ADDR_OF: { 1876 NativeLoc real = loc_from_operand(e, &in->opnds[0], in->loc); 1877 addr = addr_from_operand(e, &in->opnds[1], in->loc); 1878 dst = real; 1879 if (dst.kind != NATIVE_LOC_REG || 1880 (addr.index_kind == NATIVE_ADDR_INDEX_REG && 1881 dst.v.reg == addr.index.reg)) { 1882 dst = emit_frame_address_result(e, in->opnds[0].type, &addr, in->loc); 1883 } else { 1884 e->target->load_addr(e->target, dst, addr); 1885 } 1886 if (!temp_loc_equal(real, dst)) 1887 write_operand(e, &in->opnds[0], dst, 1888 mem_for_type(e->c, in->opnds[0].type), in->loc); 1889 return; 1890 } 1891 case IR_TLS_ADDR_OF: { 1892 IRTlsAux* aux = (IRTlsAux*)in->extra.aux; 1893 NativeLoc real = loc_from_operand(e, &in->opnds[0], in->loc); 1894 dst = real; 1895 if (dst.kind != NATIVE_LOC_REG) 1896 dst = temp_loc_acquire(e, in->opnds[0].type, NATIVE_REG_INT, REG_NONE, 1897 REG_NONE, in->loc); 1898 e->target->tls_addr_of(e->target, dst, aux->sym, aux->addend); 1899 if (real.kind != NATIVE_LOC_REG) 1900 write_operand(e, &in->opnds[0], dst, 1901 mem_for_type(e->c, in->opnds[0].type), in->loc); 1902 return; 1903 } 1904 case IR_AGG_COPY: { 1905 IRAggAux* aux = (IRAggAux*)in->extra.aux; 1906 addr = pointer_addr_from_operand(e, &in->opnds[0], in->loc, REG_NONE, 1907 REG_NONE); 1908 addr2 = pointer_addr_from_operand( 1909 e, &in->opnds[1], in->loc, 1910 addr.base_kind == NATIVE_ADDR_BASE_REG ? addr.base.reg : REG_NONE, 1911 REG_NONE); 1912 legalize_addr(e, &addr, aux->access.mem, in->loc); 1913 legalize_addr(e, &addr2, aux->access.mem, in->loc); 1914 e->target->copy_bytes(e->target, addr, addr2, aux->access); 1915 return; 1916 } 1917 case IR_AGG_SET: { 1918 IRAggAux* aux = (IRAggAux*)in->extra.aux; 1919 addr = pointer_addr_from_operand(e, &in->opnds[0], in->loc, REG_NONE, 1920 REG_NONE); 1921 legalize_addr(e, &addr, aux->access.mem, in->loc); 1922 src = loc_from_operand(e, &in->opnds[1], in->loc); 1923 if (src.kind != NATIVE_LOC_REG) { 1924 src = materialize_operand_away_from_addr(e, &in->opnds[1], &addr, 1925 in->loc); 1926 } 1927 e->target->set_bytes(e->target, addr, src, aux->access); 1928 return; 1929 } 1930 case IR_BITFIELD_LOAD: { 1931 IRBitFieldAux* aux = (IRBitFieldAux*)in->extra.aux; 1932 NativeLoc real = loc_from_operand(e, &in->opnds[0], in->loc); 1933 dst = real; 1934 addr = addr_from_operand(e, &in->opnds[1], in->loc); 1935 legalize_addr(e, &addr, aux->access.storage, in->loc); 1936 if (dst.kind != NATIVE_LOC_REG) 1937 dst = acquire_frame_load_result(e, in->opnds[0].type, &addr, 1938 in->loc); 1939 e->target->bitfield_load(e->target, dst, addr, aux->access); 1940 if (real.kind != NATIVE_LOC_REG) 1941 write_operand(e, &in->opnds[0], dst, 1942 mem_for_type(e->c, in->opnds[0].type), in->loc); 1943 return; 1944 } 1945 case IR_BITFIELD_STORE: { 1946 IRBitFieldAux* aux = (IRBitFieldAux*)in->extra.aux; 1947 addr = addr_from_operand(e, &in->opnds[0], in->loc); 1948 legalize_addr(e, &addr, aux->access.storage, in->loc); 1949 src = loc_from_operand(e, &in->opnds[1], in->loc); 1950 /* The field store is a read-modify-write that keeps the address live 1951 * across the value placement, so the value must not be materialized into 1952 * the address's base/index register. Mirror IR_STORE/IR_AGG_SET: when no 1953 * other operand temp is free, collapse the address to one temp first, 1954 * then materialize the value avoiding the address regs. Without this, a 1955 * spilled pointer reloaded into the first temp and a value reusing that 1956 * temp collide — the value `mov`s over the address, nulling it (SIGSEGV 1957 * in optimized SQLite). */ 1958 if (src.kind != NATIVE_LOC_REG) { 1959 src = materialize_operand_away_from_addr(e, &in->opnds[1], &addr, 1960 in->loc); 1961 } 1962 e->target->bitfield_store(e->target, addr, src, aux->access); 1963 return; 1964 } 1965 case IR_BINOP: 1966 dst = loc_from_operand(e, &in->opnds[0], in->loc); 1967 dst_reg = dst.kind == NATIVE_LOC_REG ? dst.v.reg : REG_NONE; 1968 b = loc_from_operand(e, &in->opnds[2], in->loc); 1969 a = materialize_operand(e, &in->opnds[1], 1970 class_for_type(e, in->opnds[1].type), 1971 in->opnds[1].type, dst_reg, loc_avoid_reg(b), 1972 in->loc); 1973 b = operand_imm_or_reg(e, &in->opnds[2], NATIVE_IMM_BINOP, 1974 (u32)in->extra.imm, a.v.reg, dst_reg, in->loc); 1975 /* L7 shift rider: a single-use SHL folded into this binop's second 1976 * source. The rider only survives onto a register `b` (a shifted-reg 1977 * ALU operand); an immediate `b` never carries one. The recognition 1978 * pass only stamps it for targets advertising can_fold_shift_into_alu, 1979 * so a backend ignoring NativeLoc.shift never receives a nonzero rider. */ 1980 if (b.kind == NATIVE_LOC_REG) b.shift = in->opnds[2].shift; 1981 if (dst.kind != NATIVE_LOC_REG) { 1982 NativeAllocClass dst_cls = class_for_type(e, in->opnds[0].type); 1983 if (a.kind == NATIVE_LOC_REG && a.cls == (u8)dst_cls && 1984 temp_reg_is_leased(e, dst_cls, a.v.reg)) 1985 dst = loc_reg(in->opnds[0].type, dst_cls, a.v.reg); 1986 else 1987 dst = temp_loc_acquire(e, in->opnds[0].type, dst_cls, a.v.reg, 1988 loc_avoid_reg(b), in->loc); 1989 } 1990 e->target->binop(e->target, (BinOp)in->extra.imm, dst, a, b); 1991 if (in->opnds[0].kind != OPK_REG) 1992 write_operand(e, &in->opnds[0], dst, 1993 mem_for_type(e->c, in->opnds[0].type), in->loc); 1994 return; 1995 case IR_UNOP: 1996 dst = loc_from_operand(e, &in->opnds[0], in->loc); 1997 dst_reg = dst.kind == NATIVE_LOC_REG ? dst.v.reg : REG_NONE; 1998 a = materialize_operand(e, &in->opnds[1], 1999 class_for_type(e, in->opnds[1].type), 2000 in->opnds[1].type, dst_reg, REG_NONE, in->loc); 2001 if (dst.kind != NATIVE_LOC_REG) { 2002 NativeAllocClass dst_cls = class_for_type(e, in->opnds[0].type); 2003 if (a.kind == NATIVE_LOC_REG && a.cls == (u8)dst_cls && 2004 temp_reg_is_leased(e, dst_cls, a.v.reg)) 2005 dst = loc_reg(in->opnds[0].type, dst_cls, a.v.reg); 2006 else 2007 dst = temp_loc_acquire(e, in->opnds[0].type, dst_cls, a.v.reg, 2008 REG_NONE, 2009 in->loc); 2010 } 2011 e->target->unop(e->target, (UnOp)in->extra.imm, dst, a); 2012 if (in->opnds[0].kind != OPK_REG) 2013 write_operand(e, &in->opnds[0], dst, 2014 mem_for_type(e->c, in->opnds[0].type), in->loc); 2015 return; 2016 case IR_CMP: 2017 dst = loc_from_operand(e, &in->opnds[0], in->loc); 2018 dst_reg = dst.kind == NATIVE_LOC_REG ? dst.v.reg : REG_NONE; 2019 b = loc_from_operand(e, &in->opnds[2], in->loc); 2020 a = materialize_operand(e, &in->opnds[1], 2021 class_for_type(e, in->opnds[1].type), 2022 in->opnds[1].type, dst_reg, loc_avoid_reg(b), 2023 in->loc); 2024 b = operand_imm_or_reg(e, &in->opnds[2], NATIVE_IMM_CMP, 2025 (u32)in->extra.imm, a.v.reg, dst_reg, in->loc); 2026 if (dst.kind != NATIVE_LOC_REG) { 2027 NativeAllocClass dst_cls = class_for_type(e, in->opnds[0].type); 2028 if (a.kind == NATIVE_LOC_REG && a.cls == (u8)dst_cls && 2029 temp_reg_is_leased(e, dst_cls, a.v.reg)) 2030 dst = loc_reg(in->opnds[0].type, dst_cls, a.v.reg); 2031 else 2032 dst = temp_loc_acquire(e, in->opnds[0].type, dst_cls, a.v.reg, 2033 loc_avoid_reg(b), in->loc); 2034 } 2035 e->target->cmp(e->target, (CmpOp)in->extra.imm, dst, a, b); 2036 if (in->opnds[0].kind != OPK_REG) 2037 write_operand(e, &in->opnds[0], dst, 2038 mem_for_type(e->c, in->opnds[0].type), in->loc); 2039 return; 2040 case IR_CONVERT: 2041 dst = loc_from_operand(e, &in->opnds[0], in->loc); 2042 dst_reg = dst.kind == NATIVE_LOC_REG ? dst.v.reg : REG_NONE; 2043 src = materialize_operand(e, &in->opnds[1], 2044 class_for_type(e, in->opnds[1].type), 2045 in->opnds[1].type, dst_reg, REG_NONE, in->loc); 2046 if (dst.kind != NATIVE_LOC_REG) { 2047 NativeAllocClass dst_cls = class_for_type(e, in->opnds[0].type); 2048 if (src.kind == NATIVE_LOC_REG && src.cls == (u8)dst_cls && 2049 temp_reg_is_leased(e, dst_cls, src.v.reg)) 2050 dst = loc_reg(in->opnds[0].type, dst_cls, src.v.reg); 2051 else 2052 dst = temp_loc_acquire(e, in->opnds[0].type, dst_cls, src.v.reg, 2053 REG_NONE, 2054 in->loc); 2055 } 2056 e->target->convert(e->target, (ConvKind)in->extra.imm, dst, src); 2057 if (in->opnds[0].kind != OPK_REG) 2058 write_operand(e, &in->opnds[0], dst, 2059 mem_for_type(e->c, in->opnds[0].type), in->loc); 2060 return; 2061 case IR_CALL: 2062 emit_call(e, in); 2063 return; 2064 case IR_BR: 2065 e->target->jump(e->target, 2066 ensure_label(e, e->f->blocks[block].succ[0], in->loc)); 2067 return; 2068 case IR_CMP_BRANCH: { 2069 u32 next = order_index + 1u < e->f->emit_order_n 2070 ? e->f->emit_order[order_index + 1u] 2071 : UINT32_MAX; 2072 b = loc_from_operand(e, &in->opnds[1], in->loc); 2073 a = materialize_operand(e, &in->opnds[0], 2074 class_for_type(e, in->opnds[0].type), 2075 in->opnds[0].type, REG_NONE, loc_avoid_reg(b), 2076 in->loc); 2077 b = operand_imm_or_reg(e, &in->opnds[1], NATIVE_IMM_CMP, 2078 (u32)in->extra.imm, a.v.reg, REG_NONE, in->loc); 2079 e->target->cmp_branch( 2080 e->target, (CmpOp)in->extra.imm, a, b, 2081 ensure_label(e, e->f->blocks[block].succ[0], in->loc)); 2082 if (e->f->blocks[block].nsucc > 1u && e->f->blocks[block].succ[1] != next) 2083 e->target->jump(e->target, 2084 ensure_label(e, e->f->blocks[block].succ[1], in->loc)); 2085 return; 2086 } 2087 case IR_SWITCH: { 2088 IRSwitchAux* aux = (IRSwitchAux*)in->extra.aux; 2089 /* Lease the selector in one register for the whole chain. */ 2090 NativeLoc sel = materialize_operand( 2091 e, &in->opnds[0], class_for_type(e, in->opnds[0].type), 2092 in->opnds[0].type, REG_NONE, REG_NONE, in->loc); 2093 /* Each case value is interpreted at the selector type (matches the CG 2094 * fallback chain in cg/native_direct_target.c). Route each through the 2095 * same NATIVE_IMM_CMP immediate-or-register logic IR_CMP_BRANCH uses, so 2096 * a legal compare immediate becomes `cmp sel, #k` with no per-case 2097 * materialization; an illegal immediate (e.g. too large for the target's 2098 * compare encoding) falls back to load_imm into a temp that avoids the 2099 * leased selector register. */ 2100 KitCgTypeId case_type = 2101 aux && aux->selector_type ? aux->selector_type : in->opnds[0].type; 2102 NativeEmitTempMark case_mark = temp_scope_mark(e); 2103 for (u32 i = 0; aux && i < aux->ncases; ++i) { 2104 OptOperand case_op; 2105 NativeLoc imm; 2106 memset(&case_op, 0, sizeof case_op); 2107 case_op.kind = OPK_IMM; 2108 case_op.type = case_type; 2109 case_op.v.imm = (i64)aux->cases[i].value; 2110 imm = operand_imm_or_reg(e, &case_op, NATIVE_IMM_CMP, (u32)CMP_EQ, 2111 sel.v.reg, REG_NONE, in->loc); 2112 e->target->cmp_branch(e->target, CMP_EQ, sel, imm, 2113 ensure_label(e, aux->cases[i].block, in->loc)); 2114 temp_scope_rewind(e, case_mark); 2115 } 2116 if (aux) 2117 e->target->jump(e->target, 2118 ensure_label(e, aux->default_block, in->loc)); 2119 return; 2120 } 2121 case IR_INDIRECT_BRANCH: { 2122 IRIndirectAux* aux = (IRIndirectAux*)in->extra.aux; 2123 MCLabel* labels = aux && aux->ntargets 2124 ? arena_array(e->f->arena, MCLabel, aux->ntargets) 2125 : NULL; 2126 for (u32 i = 0; aux && i < aux->ntargets; ++i) 2127 labels[i] = ensure_label(e, aux->targets[i], in->loc); 2128 src = materialize_operand(e, &in->opnds[0], NATIVE_REG_INT, 2129 in->opnds[0].type, REG_NONE, REG_NONE, 2130 in->loc); 2131 e->target->indirect_branch(e->target, src, labels, 2132 aux ? aux->ntargets : 0u); 2133 return; 2134 } 2135 case IR_LOAD_LABEL_ADDR: { 2136 NativeLoc real = loc_from_operand(e, &in->opnds[0], in->loc); 2137 dst = real; 2138 if (dst.kind != NATIVE_LOC_REG) 2139 dst = temp_loc_acquire(e, in->opnds[0].type, NATIVE_REG_INT, REG_NONE, 2140 REG_NONE, in->loc); 2141 e->target->load_label_addr(e->target, dst, 2142 ensure_label(e, (u32)in->extra.imm, in->loc)); 2143 if (real.kind != NATIVE_LOC_REG) 2144 write_operand(e, &in->opnds[0], dst, 2145 mem_for_type(e->c, in->opnds[0].type), in->loc); 2146 return; 2147 } 2148 case IR_LOCAL_STATIC_DATA_BEGIN: { 2149 CgIrLocalStaticBeginAux* aux = (CgIrLocalStaticBeginAux*)in->extra.aux; 2150 emit_local_static_begin(e, aux ? &aux->desc : NULL, in->loc); 2151 return; 2152 } 2153 case IR_LOCAL_STATIC_DATA_WRITE: { 2154 CgIrLocalStaticWriteAux* aux = (CgIrLocalStaticWriteAux*)in->extra.aux; 2155 if (!aux) emit_panic(e, in->loc, "missing local static data write"); 2156 emit_local_static_write(e, aux->has_data ? aux->data : NULL, aux->len, 2157 in->loc); 2158 return; 2159 } 2160 case IR_LOCAL_STATIC_DATA_LABEL_ADDR: { 2161 CgIrLocalStaticLabelAux* aux = (CgIrLocalStaticLabelAux*)in->extra.aux; 2162 if (!aux) emit_panic(e, in->loc, "missing local static label data"); 2163 (void)aux->address_space; 2164 emit_local_static_label_addr(e, 2165 ensure_label(e, (u32)aux->target, in->loc), 2166 aux->addend, aux->width, in->loc); 2167 return; 2168 } 2169 case IR_LOCAL_STATIC_DATA_END: 2170 emit_local_static_end(e, in->loc); 2171 return; 2172 case IR_RET: 2173 emit_ret(e, in, fd); 2174 return; 2175 case IR_ALLOCA: { 2176 NativeLoc real = loc_from_operand(e, &in->opnds[0], in->loc); 2177 dst = real; 2178 src = materialize_operand(e, &in->opnds[1], NATIVE_REG_INT, 2179 in->opnds[1].type, REG_NONE, REG_NONE, 2180 in->loc); 2181 if (dst.kind != NATIVE_LOC_REG) 2182 dst = temp_loc_acquire(e, in->opnds[0].type, NATIVE_REG_INT, src.v.reg, 2183 REG_NONE, in->loc); 2184 e->target->alloca_(e->target, dst, src, (u32)in->extra.imm); 2185 if (real.kind != NATIVE_LOC_REG) 2186 write_operand(e, &in->opnds[0], dst, 2187 mem_for_type(e->c, in->opnds[0].type), in->loc); 2188 return; 2189 } 2190 case IR_ATOMIC_LOAD: { 2191 IRAtomicAux* aux = (IRAtomicAux*)in->extra.aux; 2192 dst = loc_from_operand(e, &in->opnds[0], in->loc); 2193 addr = pointer_addr_from_operand(e, &in->opnds[1], in->loc, REG_NONE, 2194 REG_NONE); 2195 legalize_addr(e, &addr, aux->mem, in->loc); 2196 if (dst.kind != NATIVE_LOC_REG) 2197 dst = acquire_frame_load_result(e, in->opnds[0].type, &addr, 2198 in->loc); 2199 e->target->atomic_load(e->target, dst, addr, aux->mem, aux->mo); 2200 if (in->opnds[0].kind != OPK_REG) 2201 write_operand(e, &in->opnds[0], dst, aux->mem, in->loc); 2202 return; 2203 } 2204 case IR_ATOMIC_STORE: { 2205 IRAtomicAux* aux = (IRAtomicAux*)in->extra.aux; 2206 addr = pointer_addr_from_operand(e, &in->opnds[0], in->loc, REG_NONE, 2207 REG_NONE); 2208 legalize_addr(e, &addr, aux->mem, in->loc); 2209 src = loc_from_operand(e, &in->opnds[1], in->loc); 2210 if (src.kind != NATIVE_LOC_REG) 2211 src = materialize_operand_away_from_addr(e, &in->opnds[1], &addr, 2212 in->loc); 2213 e->target->atomic_store(e->target, addr, src, aux->mem, aux->mo); 2214 return; 2215 } 2216 case IR_ATOMIC_RMW: { 2217 IRAtomicAux* aux = (IRAtomicAux*)in->extra.aux; 2218 dst = loc_from_operand(e, &in->opnds[0], in->loc); 2219 addr = pointer_addr_from_operand(e, &in->opnds[1], in->loc, REG_NONE, 2220 REG_NONE); 2221 legalize_addr(e, &addr, aux->mem, in->loc); 2222 src = materialize_operand(e, &in->opnds[2], 2223 class_for_type(e, in->opnds[2].type), 2224 in->opnds[2].type, REG_NONE, REG_NONE, in->loc); 2225 if (dst.kind != NATIVE_LOC_REG) 2226 dst = temp_loc_acquire(e, in->opnds[0].type, 2227 class_for_type(e, in->opnds[0].type), src.v.reg, 2228 REG_NONE, in->loc); 2229 e->target->atomic_rmw(e->target, (KitCgAtomicOp)aux->op, dst, addr, src, 2230 aux->mem, aux->mo); 2231 if (in->opnds[0].kind != OPK_REG) 2232 write_operand(e, &in->opnds[0], dst, aux->mem, in->loc); 2233 return; 2234 } 2235 case IR_ATOMIC_CAS: { 2236 IRCasAux* aux = (IRCasAux*)in->extra.aux; 2237 NativeLoc ok; 2238 NativeLoc expected; 2239 NativeLoc desired; 2240 dst = loc_from_operand(e, &in->opnds[0], in->loc); 2241 ok = loc_from_operand(e, &in->opnds[1], in->loc); 2242 addr = pointer_addr_from_operand(e, &in->opnds[2], in->loc, REG_NONE, 2243 REG_NONE); 2244 legalize_addr(e, &addr, aux->mem, in->loc); 2245 expected = materialize_operand( 2246 e, &in->opnds[3], class_for_type(e, in->opnds[3].type), 2247 in->opnds[3].type, REG_NONE, REG_NONE, in->loc); 2248 desired = materialize_operand( 2249 e, &in->opnds[4], class_for_type(e, in->opnds[4].type), 2250 in->opnds[4].type, expected.v.reg, REG_NONE, in->loc); 2251 if (dst.kind != NATIVE_LOC_REG) 2252 dst = temp_loc_acquire(e, in->opnds[0].type, 2253 class_for_type(e, in->opnds[0].type), expected.v.reg, 2254 desired.v.reg, in->loc); 2255 if (ok.kind != NATIVE_LOC_REG) 2256 ok = temp_loc_acquire(e, in->opnds[1].type, 2257 class_for_type(e, in->opnds[1].type), dst.v.reg, 2258 expected.v.reg, in->loc); 2259 e->target->atomic_cas(e->target, dst, ok, addr, expected, desired, 2260 aux->mem, aux->success, aux->failure); 2261 if (in->opnds[0].kind != OPK_REG) 2262 write_operand(e, &in->opnds[0], dst, aux->mem, in->loc); 2263 if (in->opnds[1].kind != OPK_REG) 2264 write_operand(e, &in->opnds[1], ok, 2265 mem_for_type(e->c, in->opnds[1].type), in->loc); 2266 return; 2267 } 2268 case IR_VA_START: { 2269 NativeLoc ap = materialize_operand(e, &in->opnds[0], NATIVE_REG_INT, 2270 in->opnds[0].type, REG_NONE, REG_NONE, 2271 in->loc); 2272 e->target->va_start_(e->target, ap); 2273 return; 2274 } 2275 case IR_VA_END: { 2276 NativeLoc ap = materialize_operand(e, &in->opnds[0], NATIVE_REG_INT, 2277 in->opnds[0].type, REG_NONE, REG_NONE, 2278 in->loc); 2279 e->target->va_end_(e->target, ap); 2280 return; 2281 } 2282 case IR_VA_COPY: { 2283 NativeLoc d = materialize_operand(e, &in->opnds[0], NATIVE_REG_INT, 2284 in->opnds[0].type, REG_NONE, REG_NONE, 2285 in->loc); 2286 NativeLoc s = materialize_operand(e, &in->opnds[1], NATIVE_REG_INT, 2287 in->opnds[1].type, d.v.reg, REG_NONE, 2288 in->loc); 2289 e->target->va_copy_(e->target, d, s); 2290 return; 2291 } 2292 case IR_VA_ARG: { 2293 KitCgTypeId ty = in->opnds[0].type; 2294 NativeLoc ap = materialize_operand(e, &in->opnds[1], NATIVE_REG_INT, 2295 in->opnds[1].type, REG_NONE, REG_NONE, 2296 in->loc); 2297 NativeLoc res; 2298 if (type_is_aggregate_or_large(e, ty)) { 2299 /* A value too wide for one register (an 8-byte i64/double on a 32-bit 2300 * target, or an aggregate) cannot pass through one instruction-local 2301 * register; hand the target its memory destination so it can copy the 2302 * value directly. */ 2303 e->target->va_arg_(e->target, 2304 loc_from_operand(e, &in->opnds[0], in->loc), ap, ty); 2305 return; 2306 } 2307 /* The result must land in a register distinct from the va_list pointer; 2308 * fetch into an instruction-local temp, then write to the real 2309 * destination. */ 2310 res = temp_loc_acquire(e, ty, class_for_type(e, ty), ap.v.reg, REG_NONE, 2311 in->loc); 2312 e->target->va_arg_(e->target, res, ap, ty); 2313 write_operand(e, &in->opnds[0], res, mem_for_type(e->c, ty), in->loc); 2314 return; 2315 } 2316 case IR_ASM_BLOCK: { 2317 IRAsmAux* aux = (IRAsmAux*)in->extra.aux; 2318 NativeLoc* out_locs = aux && aux->nout 2319 ? arena_array(e->f->arena, NativeLoc, aux->nout) 2320 : NULL; 2321 NativeLoc* real_out_locs = 2322 aux && aux->nout 2323 ? arena_array(e->f->arena, NativeLoc, aux->nout) 2324 : NULL; 2325 NativeLoc* in_locs = aux && aux->nin 2326 ? arena_array(e->f->arena, NativeLoc, aux->nin) 2327 : NULL; 2328 int* staged_outs = 2329 aux && aux->nout ? arena_zarray(e->f->arena, int, aux->nout) : NULL; 2330 if (e->temps) 2331 e->temps->allow_asm_temps = 2332 (u8)!(aux && aux->has_memory_constraint); 2333 /* Register constraints are an instruction-local placement requirement, 2334 * not a property of the value's whole live range. Keep regalloc's chosen 2335 * hard register when it satisfies the constraint; otherwise stage the 2336 * value through a scoped lease. The backend asm binder validates and 2337 * binds concrete register operands; only backend-private materialization 2338 * for memory constraints remains on its side of the boundary. */ 2339 for (u32 i = 0; aux && i < aux->nout; ++i) { 2340 real_out_locs[i] = 2341 loc_from_operand(e, &aux->out_ops[i], in->loc); 2342 out_locs[i] = asm_stage_reg_operand( 2343 e, &aux->outs[i], 2344 aux->out_reg_reqs ? &aux->out_reg_reqs[i] : NULL, 2345 real_out_locs[i], 2346 aux->outs[i].dir == KIT_CG_ASM_INOUT, &staged_outs[i], in->loc); 2347 } 2348 for (u32 i = 0; aux && i < aux->nin; ++i) { 2349 NativeLoc real = loc_from_operand(e, &aux->in_ops[i], in->loc); 2350 int matched = native_asm_match_index(aux->ins[i].str); 2351 if (matched >= 0) { 2352 if ((u32)matched >= aux->nout || 2353 out_locs[matched].kind != NATIVE_LOC_REG) 2354 emit_panic(e, in->loc, "invalid matching asm operand"); 2355 if (native_asm_constraint_early(aux->outs[matched].str)) 2356 emit_panic(e, in->loc, 2357 "matching input names early-clobber output"); 2358 load_loc_into_reg(e, out_locs[matched], real, in->loc); 2359 in_locs[i] = out_locs[matched]; 2360 } else { 2361 in_locs[i] = asm_stage_reg_operand( 2362 e, &aux->ins[i], 2363 aux->in_reg_reqs ? &aux->in_reg_reqs[i] : NULL, real, 1, NULL, 2364 in->loc); 2365 } 2366 } 2367 e->target->asm_block(e->target, aux ? aux->tmpl : "", 2368 aux ? aux->outs : NULL, aux ? aux->nout : 0, 2369 out_locs, aux ? aux->ins : NULL, aux ? aux->nin : 0, 2370 in_locs, aux ? aux->clobbers : NULL, 2371 aux ? aux->nclob : 0); 2372 for (u32 i = 0; aux && i < aux->nout; ++i) 2373 if (staged_outs[i]) 2374 write_operand(e, &aux->out_ops[i], out_locs[i], 2375 mem_for_type(e->c, out_locs[i].type), in->loc); 2376 return; 2377 } 2378 case IR_BREAK_TO: 2379 case IR_CONTINUE_TO: 2380 emit_panic(e, in->loc, "operation is not wired to NativeTarget yet"); 2381 case IR_FENCE: 2382 e->target->fence(e->target, (KitCgMemOrder)in->extra.imm); 2383 return; 2384 case IR_UNREACHABLE: 2385 e->target->trap(e->target); 2386 return; 2387 case IR_INTRINSIC: { 2388 IRIntrinAux* aux = (IRIntrinAux*)in->extra.aux; 2389 NativeLoc* dsts = aux && aux->ndst 2390 ? arena_array(e->f->arena, NativeLoc, aux->ndst) 2391 : NULL; 2392 NativeLoc* real_dsts = 2393 aux && aux->ndst 2394 ? arena_array(e->f->arena, NativeLoc, aux->ndst) 2395 : NULL; 2396 NativeLoc* args = aux && aux->narg 2397 ? arena_array(e->f->arena, NativeLoc, aux->narg) 2398 : NULL; 2399 for (u32 i = 0; aux && i < aux->narg; ++i) { 2400 if (aux->args[i].kind == OPK_IMM && 2401 native_intrinsic_arg_accepts_imm(e->target, aux->kind, i)) { 2402 args[i] = loc_from_operand(e, &aux->args[i], in->loc); 2403 } else { 2404 args[i] = materialize_operand( 2405 e, &aux->args[i], class_for_type(e, aux->args[i].type), 2406 aux->args[i].type, REG_NONE, REG_NONE, in->loc); 2407 } 2408 } 2409 for (u32 i = 0; aux && i < aux->ndst; ++i) { 2410 NativeAllocClass cls = class_for_type(e, aux->dsts[i].type); 2411 real_dsts[i] = loc_from_operand(e, &aux->dsts[i], in->loc); 2412 dsts[i] = real_dsts[i]; 2413 if (dsts[i].kind != NATIVE_LOC_NONE && 2414 dsts[i].kind != NATIVE_LOC_REG) 2415 dsts[i] = temp_loc_acquire(e, aux->dsts[i].type, cls, REG_NONE, 2416 REG_NONE, 2417 in->loc); 2418 } 2419 e->target->intrinsic(e->target, aux->kind, dsts, aux->ndst, args, 2420 aux->narg); 2421 for (u32 i = 0; aux && i < aux->ndst; ++i) { 2422 if (real_dsts[i].kind == NATIVE_LOC_NONE || 2423 real_dsts[i].kind == NATIVE_LOC_REG) 2424 continue; 2425 write_operand(e, &aux->dsts[i], dsts[i], 2426 mem_for_type(e->c, aux->dsts[i].type), in->loc); 2427 } 2428 return; 2429 } 2430 default: 2431 emit_panic(e, in->loc, "unknown IR op"); 2432 } 2433 } 2434 2435 static void emit_inst(NativeEmitCtx* e, u32 block, u32 order_index, Inst* in, 2436 const CGFuncDesc* fd) { 2437 NativeEmitTempScope scope; 2438 temp_scope_begin(e, &scope, in); 2439 emit_inst_body(e, block, order_index, in, fd); 2440 temp_scope_end(e, &scope); 2441 } 2442 2443 static int native_emit_terminates(const Inst* in) { 2444 if (!in) return 0; 2445 switch ((IROp)in->op) { 2446 case IR_BR: 2447 case IR_CONDBR: 2448 case IR_CMP_BRANCH: 2449 case IR_SWITCH: 2450 case IR_INDIRECT_BRANCH: 2451 case IR_RET: 2452 case IR_UNREACHABLE: 2453 case IR_BREAK_TO: 2454 case IR_CONTINUE_TO: 2455 return 1; 2456 case IR_INTRINSIC: { 2457 IRIntrinAux* aux = (IRIntrinAux*)in->extra.aux; 2458 return aux && (aux->kind == INTRIN_LONGJMP || aux->kind == INTRIN_TRAP); 2459 } 2460 default: 2461 return 0; 2462 } 2463 } 2464 2465 static void emit_block(NativeEmitCtx* e, u32 block, u32 order_index, 2466 const CGFuncDesc* fd) { 2467 if (block >= e->f->nblocks) return; 2468 /* No cache fact crosses a control-flow edge. Even a single-predecessor block 2469 * may be entered through a layout-independent branch, so block entry is the 2470 * simple, explicit ownership boundary. */ 2471 frame_cache_clear(e); 2472 if (!e->label_placed[block]) { 2473 e->label_placed[block] = 1u; 2474 e->target->label_place(e->target, 2475 ensure_label(e, block, (SrcLoc){0, 0, 0})); 2476 } 2477 Block* bl = &e->f->blocks[block]; 2478 int is_last_block = order_index + 1u == e->f->emit_order_n; 2479 for (u32 i = 0; i < bl->ninsts; ++i) { 2480 e->emitting_terminal_ret = is_last_block && i + 1u == bl->ninsts && 2481 (IROp)bl->insts[i].op == IR_RET; 2482 emit_inst(e, block, order_index, &bl->insts[i], fd); 2483 } 2484 e->emitting_terminal_ret = 0; 2485 if (bl->nsucc == 1u && 2486 (bl->ninsts == 0 || 2487 !native_emit_terminates(&bl->insts[bl->ninsts - 1u]))) { 2488 u32 next = order_index + 1u < e->f->emit_order_n 2489 ? e->f->emit_order[order_index + 1u] 2490 : UINT32_MAX; 2491 if (bl->succ[0] != next) 2492 e->target->jump(e->target, 2493 ensure_label(e, bl->succ[0], (SrcLoc){0, 0, 0})); 2494 } 2495 } 2496 2497 #define EMIT_MAX_REG_CLASSES 4u 2498 2499 static void collect_used_reg(Func* f, Inst* in, OptOperand* op, int is_def, 2500 void* ctx) { 2501 u32* used = (u32*)ctx; 2502 (void)f; 2503 (void)in; 2504 (void)is_def; 2505 if (op && op->kind == OPT_OPK_REG && op->cls < EMIT_MAX_REG_CLASSES && 2506 op->v.reg < 32u) 2507 used[op->cls] |= 1u << op->v.reg; 2508 } 2509 2510 static void compute_emit_live_after(NativeEmitCtx* e) { 2511 OptHardBlockLive* blocks = opt_maybe_build_hard_live(e->f); 2512 e->live_after_ready = 1u; 2513 e->live_after_cap = e->f->next_inst_id; 2514 if (!e->live_after_cap) return; 2515 e->live_after_by_inst = 2516 arena_zarray(e->f->arena, OptHardRegSet, e->live_after_cap); 2517 for (u32 b = 0; b < e->f->nblocks; ++b) { 2518 Block* bl = &e->f->blocks[b]; 2519 OptHardRegSet live = 2520 opt_hard_live_out_for_block(blocks ? &blocks[b] : NULL); 2521 for (u32 ri = bl->ninsts; ri > 0; --ri) { 2522 Inst* in = &bl->insts[ri - 1u]; 2523 OptRegEffectMasks effects; 2524 OptHardRegSet kills; 2525 if (in->id != INST_ID_NONE && in->id < e->live_after_cap) 2526 e->live_after_by_inst[in->id] = live; 2527 opt_inst_reg_effect_masks(e->f, in, &effects); 2528 opt_reg_effect_mask_kills(&effects, &kills); 2529 opt_hard_live_step(&live, &effects.uses, &kills); 2530 } 2531 } 2532 } 2533 2534 static NativeLoc asm_planning_loc(const OptOperand* op) { 2535 NativeLoc loc = loc_none(); 2536 if (!op) return loc; 2537 loc.type = op->type; 2538 if (op->kind == OPK_REG) 2539 loc = loc_reg(op->type, (NativeAllocClass)op->cls, op->v.reg); 2540 return loc; 2541 } 2542 2543 /* Dry-run one instruction's asm allocator before frame finalization. The same 2544 * candidate order and unavailable masks are used by final emission, so retain 2545 * only the ABI-relevant result: callee-saved staging registers overwritten. */ 2546 static void plan_one_asm_staging(NativeEmitCtx* e, Inst* in, IRAsmAux* aux) { 2547 NativeEmitTempScope scope; 2548 if (!aux) return; 2549 temp_scope_begin(e, &scope, in); 2550 scope.allow_asm_temps = (u8)!aux->has_memory_constraint; 2551 for (u32 k = 0; k < aux->nout; ++k) { 2552 (void)asm_stage_reg_operand( 2553 e, &aux->outs[k], 2554 aux->out_reg_reqs ? &aux->out_reg_reqs[k] : NULL, 2555 asm_planning_loc(&aux->out_ops[k]), 0, NULL, in->loc); 2556 } 2557 for (u32 k = 0; k < aux->nin; ++k) { 2558 if (native_asm_match_index(aux->ins[k].str) >= 0) continue; 2559 (void)asm_stage_reg_operand( 2560 e, &aux->ins[k], 2561 aux->in_reg_reqs ? &aux->in_reg_reqs[k] : NULL, 2562 asm_planning_loc(&aux->in_ops[k]), 0, NULL, in->loc); 2563 } 2564 for (u32 c = 0; c < OPT_REG_CLASSES; ++c) 2565 e->asm_stage_callee_used[c] |= 2566 scope.leased[c] & e->f->opt_callee_saved[c]; 2567 temp_scope_end(e, &scope); 2568 } 2569 2570 static NativeLoc* marshal_call_args_reserve(NativeEmitCtx* e, NativeLoc* args, 2571 u32* cap, u32 want) { 2572 u32 grown; 2573 if (want <= *cap) return args; 2574 grown = *cap ? *cap : want; 2575 while (grown < want && grown <= 0x7fffffffu) grown *= 2u; 2576 if (grown < want) grown = want; 2577 *cap = grown; 2578 return arena_zarray(e->f->arena, NativeLoc, grown); 2579 } 2580 2581 static void plan_asm_clobber_push(NativeEmitCtx* e, Sym** clobbers, u32* count, 2582 u32* cap, Sym clobber) { 2583 if (*count == *cap) { 2584 u32 grown = *cap ? *cap * 2u : 8u; 2585 Sym* next; 2586 if (grown < *cap) grown = *count + 1u; 2587 next = arena_array(e->f->arena, Sym, grown); 2588 if (*count) memcpy(next, *clobbers, sizeof *next * *count); 2589 *clobbers = next; 2590 *cap = grown; 2591 } 2592 (*clobbers)[(*count)++] = clobber; 2593 } 2594 2595 /* Plan the complete call frame before any code is emitted, then hand it to the 2596 * backend via func_begin_known_frame so the prologue is emitted final. The 2597 * optimizer knows everything the frame needs after register allocation and MIR 2598 * lowering: the callee-saved set (scanned from the MIR), every static frame 2599 * slot (f->frame_slots), and the outgoing-arg area (the max over all calls of 2600 * the pure call_stack_bytes query). The body therefore allocates no slots, so 2601 * the frame is final up front and nothing is back-patched. Populates 2602 * e->slot_map from the backend-assigned slot handles for the body to use. */ 2603 static void plan_frame(NativeEmitCtx* e, const CGFuncDesc* fd) { 2604 NativeTarget* t = e->target; 2605 NativeKnownFrameDesc frame; 2606 NativeFrameSlotDesc* slots = NULL; 2607 NativeFrameSlot* out_slots = NULL; 2608 u32 used[EMIT_MAX_REG_CLASSES]; 2609 u32 nclasses; 2610 NativeLoc* call_args = NULL; 2611 u32 call_args_cap = 0; 2612 u32 max_outgoing = 0; 2613 u8 has_alloca = 0; 2614 u8 needs_scratch_spill = 0; 2615 u8 has_call = 0; 2616 u8 has_asm = 0; 2617 u8 reads_frame = 0; 2618 u32 nasm_clob = 0; 2619 u32 asm_clob_cap = 0; 2620 u32 asm_clobber_abi_sets = 0; 2621 Sym* asm_clobbers = NULL; 2622 memset(&frame, 0, sizeof frame); 2623 memset(used, 0, sizeof used); 2624 memset(e->asm_stage_callee_used, 0, sizeof e->asm_stage_callee_used); 2625 memset(e->callee_saved_used, 0, sizeof e->callee_saved_used); 2626 /* One primary MIR scan collects allocated hard registers, asm staging, and 2627 * all frame-shape facts. This used to be three independent full walks. */ 2628 for (u32 b = 0; b < e->f->nblocks; ++b) { 2629 Block* bl = &e->f->blocks[b]; 2630 for (u32 i = 0; i < bl->ninsts; ++i) { 2631 Inst* in = &bl->insts[i]; 2632 if (t->reserve_callee_saves) 2633 opt_walk_inst_operands(e->f, in, collect_used_reg, used); 2634 if ((IROp)in->op == IR_ALLOCA) { 2635 has_alloca = 1; 2636 } else if ((IROp)in->op == IR_ATOMIC_RMW) { 2637 needs_scratch_spill = 1; 2638 } else if ((IROp)in->op == IR_CALL) { 2639 IRCallAux* aux = (IRCallAux*)in->extra.aux; 2640 /* Any call (regular or sibling/tail) means the function is not a leaf: 2641 * it clobbers the return-address register and the stack below sp. */ 2642 has_call = 1; 2643 if (aux && t->call_stack_bytes) { 2644 NativeCallDesc d; 2645 u32 sb; 2646 call_args = marshal_call_args_reserve( 2647 e, call_args, &call_args_cap, aux->desc.nargs); 2648 memset(&d, 0, sizeof d); 2649 d.fn_type = aux->desc.fn_type; 2650 d.flags = aux->desc.flags; 2651 d.nargs = aux->desc.nargs; 2652 for (u32 k = 0; k < aux->desc.nargs; ++k) { 2653 memset(&call_args[k], 0, sizeof call_args[k]); 2654 call_args[k].type = aux->desc.args[k].type; 2655 } 2656 d.args = call_args; 2657 sb = t->call_stack_bytes(t, &d); 2658 if (sb > max_outgoing) max_outgoing = sb; 2659 } 2660 } else if ((IROp)in->op == IR_ASM_BLOCK) { 2661 /* Inline asm may clobber the return-address register or the red zone 2662 * opaquely; disqualifies the frame-eliding tiers (see has_asm). Its 2663 * callee-saved register clobbers and named-register operand 2664 * requirements are equally opaque to the operand scan below; collect 2665 * them now so the backend can fold them into the saved set. */ 2666 IRAsmAux* aux = (IRAsmAux*)in->extra.aux; 2667 has_asm = 1; 2668 if (aux) { 2669 plan_one_asm_staging(e, in, aux); 2670 for (u32 k = 0; k < aux->nclob; ++k) 2671 plan_asm_clobber_push(e, &asm_clobbers, &nasm_clob, 2672 &asm_clob_cap, aux->clobbers[k]); 2673 for (u32 k = 0; k < aux->nout; ++k) 2674 if (aux->outs[k].reg) 2675 plan_asm_clobber_push(e, &asm_clobbers, &nasm_clob, 2676 &asm_clob_cap, aux->outs[k].reg); 2677 for (u32 k = 0; k < aux->nin; ++k) 2678 if (aux->ins[k].reg) 2679 plan_asm_clobber_push(e, &asm_clobbers, &nasm_clob, 2680 &asm_clob_cap, aux->ins[k].reg); 2681 asm_clobber_abi_sets |= aux->clobber_abi_sets; 2682 } 2683 } else if ((IROp)in->op == IR_INTRINSIC) { 2684 /* __builtin_frame_address / __builtin_return_address read the frame 2685 * record, so the function must keep one (disables the rv64 frameless 2686 * leaf tier; see NativeKnownFrameDesc.reads_frame). */ 2687 IRIntrinAux* aux = (IRIntrinAux*)in->extra.aux; 2688 if (aux && (aux->kind == INTRIN_FRAME_ADDRESS || 2689 aux->kind == INTRIN_RETURN_ADDRESS)) 2690 reads_frame = 1; 2691 } else if ((IROp)in->op == IR_TLS_ADDR_OF) { 2692 /* The Mach-O TLV descriptor access calls the resolver thunk — a hidden 2693 * call the leaf analysis must see, or the frame-eliding leaf tiers (the 2694 * x64 SysV red zone, which keeps spills below sp; any return-address 2695 * elision) miscompile: the thunk's `call`/`blr` clobbers the return 2696 * address and the bytes below sp. ELF Local-Exec and Windows TEB 2697 * accesses make no call, so only the descriptor model disqualifies. */ 2698 if (obj_format_tls_via_descriptor(t->c)) has_call = 1; 2699 } 2700 } 2701 } 2702 nclasses = t->reserve_callee_saves && t->regs 2703 ? (t->regs->nclasses < EMIT_MAX_REG_CLASSES 2704 ? t->regs->nclasses 2705 : EMIT_MAX_REG_CLASSES) 2706 : 0u; 2707 for (u32 i = 0; t->regs && i < t->regs->nclasses; ++i) { 2708 const NativeAllocClassInfo* ci = &t->regs->classes[i]; 2709 if (ci->cls >= EMIT_MAX_REG_CLASSES) continue; 2710 used[ci->cls] &= 2711 native_target_callee_saved_mask(t, (NativeAllocClass)ci->cls); 2712 used[ci->cls] |= e->asm_stage_callee_used[ci->cls]; 2713 } 2714 for (u32 c = 0; c < nclasses && c < OPT_REG_CLASSES; ++c) 2715 e->callee_saved_used[c] = used[c]; 2716 e->slot_map = 2717 arena_zarray(e->f->arena, NativeFrameSlot, e->f->nframe_slots + 1u); 2718 if (e->f->nframe_slots) { 2719 slots = arena_zarray(e->f->arena, NativeFrameSlotDesc, e->f->nframe_slots); 2720 out_slots = arena_zarray(e->f->arena, NativeFrameSlot, e->f->nframe_slots); 2721 for (u32 i = 0; i < e->f->nframe_slots; ++i) { 2722 IRFrameSlot* s = &e->f->frame_slots[i]; 2723 NativeFrameSlotDesc* d = &slots[i]; 2724 memset(d, 0, sizeof *d); 2725 d->type = s->type; 2726 d->name = s->name; 2727 d->loc = s->loc; 2728 d->size = s->size; 2729 d->align = s->align; 2730 d->priority = s->priority; 2731 d->kind = s->kind; 2732 d->flags = s->flags; 2733 } 2734 } 2735 /* W1.0 hot-slot-low ordering. The backend's known-frame path bump-allocates 2736 * each body slot in the order it is presented (cum_off is monotonic, see 2737 * native_frame_slot_alloc), so slots presented first get the smallest final 2738 * frame displacement. Present the body slots in DESCENDING priority order so 2739 * the hottest spills land low: disp8 on x64 (-4 bytes/access), inside the 2740 * scaled reach on aa64, inside the +/-2KB imm12 window on rv64. 2741 * 2742 * `order[k]` is the original slot index now presented at position k. Fixed- 2743 * offset slots keep their original positions (their displacement is not 2744 * order-derived); the remaining slots are stable-sorted by descending priority 2745 * with an original-index tie-break (so the sort is deterministic and, among 2746 * equal priorities, byte-stable). The native slot the backend returns for 2747 * position k therefore belongs to original IR slot `order[k]`, and the slot_map 2748 * is written back through `order` so e->slot_map[ir_id] stays exact -- a 2749 * transposed map silently miscompiles every spill, so it is asserted below. */ 2750 u32 nfs = e->f->nframe_slots; 2751 u32* slot_order = NULL; /* slot_order[k] = original IR slot index at position k */ 2752 if (nfs > 1u) { 2753 u32* order = arena_array(e->f->arena, u32, nfs); 2754 u32* movable = arena_array(e->f->arena, u32, nfs); 2755 u32 nmovable = 0; 2756 for (u32 i = 0; i < nfs; ++i) { 2757 order[i] = i; /* fixed slots stay pinned at their original index */ 2758 if (!(slots[i].flags & NATIVE_FRAME_SLOT_FIXED_OFFSET)) 2759 movable[nmovable++] = i; 2760 } 2761 /* Stable descending-priority sort of the movable indices (insertion sort: 2762 * nslots is small per function and this is off the per-instruction path; if 2763 * a profile shows it material, swap in a radix over the integer priority -- 2764 * see O1.md W1.0 "Linear?"). Insertion sort is naturally stable, preserving 2765 * original order among equal priorities. */ 2766 for (u32 i = 1; i < nmovable; ++i) { 2767 u32 cur = movable[i]; 2768 u32 cur_pri = slots[cur].priority; 2769 u32 j = i; 2770 while (j > 0 && slots[movable[j - 1]].priority < cur_pri) { 2771 movable[j] = movable[j - 1]; 2772 --j; 2773 } 2774 movable[j] = cur; 2775 } 2776 /* Drop the sorted movable indices back into the non-fixed positions. */ 2777 u32 mi = 0; 2778 for (u32 k = 0; k < nfs; ++k) 2779 if (!(slots[k].flags & NATIVE_FRAME_SLOT_FIXED_OFFSET)) 2780 order[k] = movable[mi++]; 2781 /* Reorder the desc array in place via a scratch copy so frame.slots is the 2782 * contiguous, permuted view the backend iterates. */ 2783 NativeFrameSlotDesc* ordered = 2784 arena_array(e->f->arena, NativeFrameSlotDesc, nfs); 2785 for (u32 k = 0; k < nfs; ++k) ordered[k] = slots[order[k]]; 2786 slot_order = order; 2787 slots = ordered; 2788 } 2789 frame.slots = slots; 2790 frame.nslots = e->f->nframe_slots; 2791 frame.max_outgoing = max_outgoing; 2792 frame.callee_saved_used = nclasses ? used : NULL; 2793 frame.ncallee_classes = nclasses; 2794 frame.has_alloca = has_alloca; 2795 frame.needs_scratch_spill = needs_scratch_spill; 2796 frame.is_leaf = !has_call; 2797 frame.has_asm = has_asm; 2798 frame.reads_frame = reads_frame; 2799 frame.asm_clobbers = asm_clobbers; 2800 frame.nasm_clobbers = nasm_clob; 2801 frame.asm_clobber_abi_sets = asm_clobber_abi_sets; 2802 t->func_begin_known_frame(t, fd, &frame, out_slots); 2803 /* Map each backend native slot (returned for presentation position k) back to 2804 * its original IR frame slot id. With ordering, position k holds IR slot 2805 * order[k]; without it (nfs<=1 or no reorder), position k == IR slot k. */ 2806 for (u32 k = 0; k < e->f->nframe_slots; ++k) { 2807 u32 ir_idx = slot_order ? slot_order[k] : k; 2808 e->slot_map[e->f->frame_slots[ir_idx].id] = out_slots[k]; 2809 } 2810 #ifndef NDEBUG 2811 /* The slot_map must stay an exact 1:1 of IR frame slots to the native slots 2812 * the backend returned -- a transposed/dropped map silently miscompiles every 2813 * spill. The actual native slot ids are NOT bounded by nframe_slots (the 2814 * backend reserves callee-saves / entry-saves / scratch around the body slots, 2815 * so body native ids are an arbitrary distinct block), so verify the real 2816 * correctness condition instead: `order` is a permutation of [0, nfs) -- i.e. 2817 * every IR frame slot is presented exactly once -- and each presented position 2818 * received a non-NONE native slot. Both are O(nslots) (seen array sized by the 2819 * IR index domain), off the per-instruction path. With identity ordering this 2820 * is vacuously the original code's invariant. */ 2821 if (nfs) { 2822 u8* seen = arena_zarray(e->f->arena, u8, nfs); 2823 for (u32 k = 0; k < nfs; ++k) { 2824 u32 ir_idx = slot_order ? slot_order[k] : k; 2825 if (ir_idx >= nfs || seen[ir_idx]) 2826 compiler_panic(e->c, (SrcLoc){0, 0, 0}, 2827 "opt W1.0: slot order is not a permutation at pos %u", 2828 (unsigned)k); 2829 seen[ir_idx] = 1u; 2830 if (out_slots[k] == NATIVE_FRAME_SLOT_NONE) 2831 compiler_panic(e->c, (SrcLoc){0, 0, 0}, 2832 "opt W1.0: backend returned no native slot for pos %u", 2833 (unsigned)k); 2834 } 2835 } 2836 #endif 2837 } 2838 2839 void opt_emit_native(Compiler* c, Func* f, NativeTarget* target) { 2840 NativeEmitCtx e; 2841 Func view; 2842 CGFuncDesc fd; 2843 if (!f || !target) return; 2844 memset(&e, 0, sizeof e); 2845 e.f = opt_mir_view(f, &view) ? &view : f; 2846 e.c = c; 2847 e.target = target; 2848 metrics_scope_begin(c, "opt.native_emit.setup"); 2849 e.labels = arena_array(e.f->arena, MCLabel, e.f->nblocks ? e.f->nblocks : 1u); 2850 e.label_placed = 2851 arena_zarray(e.f->arena, u8, e.f->nblocks ? e.f->nblocks : 1u); 2852 for (u32 i = 0; i < e.f->nblocks; ++i) e.labels[i] = MC_LABEL_NONE; 2853 fd = semantic_func_desc(&e); 2854 metrics_scope_end(c, "opt.native_emit.setup"); 2855 2856 metrics_scope_begin(c, "opt.native_emit.func_begin"); 2857 /* The optimizer has the whole frame after regalloc + MIR lowering, so it 2858 * plans it up front (plan_frame) and drives func_begin_known_frame: the 2859 * backend emits a final prologue with no reserved NOP region and no 2860 * back-patching. The body allocates no frame slots, so the frame stays final; 2861 * allocas and tail epilogues are emitted final too. (Contrast the 2862 * single-pass NativeDirectTarget path, which reserves and patches.) */ 2863 plan_frame(&e, &fd); 2864 bind_params(&e); 2865 metrics_scope_end(c, "opt.native_emit.func_begin"); 2866 2867 metrics_scope_begin(c, "opt.native_emit.body"); 2868 for (u32 i = 0; i < e.f->emit_order_n; ++i) 2869 emit_block(&e, e.f->emit_order[i], i, &fd); 2870 metrics_scope_end(c, "opt.native_emit.body"); 2871 2872 metrics_scope_begin(c, "opt.native_emit.func_end"); 2873 target->func_end(target); 2874 metrics_scope_end(c, "opt.native_emit.func_end"); 2875 }