native_target.h (52459B)
1 #ifndef KIT_ARCH_NATIVE_TARGET_H 2 #define KIT_ARCH_NATIVE_TARGET_H 3 4 #include <string.h> 5 6 #include "arch/mc.h" 7 #include "cg/cgir.h" 8 #include "cg/type.h" 9 #include "core/core.h" 10 #include "core/slice.h" /* Slice, for resolve_name */ 11 12 /* NativeTarget is the physical native-emission contract. It is driven after 13 * semantic CG has been either direct-lowered by NativeDirectTarget or recorded, 14 * optimized, machinized, and allocated. It must not speak in semantic CGLocal 15 * ids except where a descriptor is carried for diagnostics or ABI queries. */ 16 17 typedef u32 NativeFrameSlot; 18 #define NATIVE_FRAME_SLOT_NONE 0u 19 20 typedef enum NativeFrameSlotKind { 21 NATIVE_FRAME_SLOT_LOCAL, 22 NATIVE_FRAME_SLOT_PARAM, 23 NATIVE_FRAME_SLOT_SPILL, 24 NATIVE_FRAME_SLOT_ALLOCA, 25 NATIVE_FRAME_SLOT_OUTGOING, 26 NATIVE_FRAME_SLOT_SAVE, 27 } NativeFrameSlotKind; 28 29 typedef enum NativeFrameSlotFlag { 30 NATIVE_FRAME_SLOT_NONE_FLAG = 0, 31 NATIVE_FRAME_SLOT_ADDR_TAKEN = 1u << 0, 32 NATIVE_FRAME_SLOT_MEMORY_REQUIRED = 1u << 1, 33 NATIVE_FRAME_SLOT_FIXED_OFFSET = 1u << 2, 34 /* A recyclable single-pass temp slot. native_frame_slot_alloc serves such a 35 * request from the per-(size,align) free list first, before bumping cum_off; 36 * native_frame_release_slot returns the slot there when its temp dies at a 37 * statement boundary. Bounds the -O0 frame to max-simultaneous-live temps. */ 38 NATIVE_FRAME_SLOT_TRANSIENT = 1u << 3, 39 } NativeFrameSlotFlag; 40 41 typedef struct NativeFrameSlotDesc { 42 KitCgTypeId type; 43 Sym name; 44 SrcLoc loc; 45 u32 size; 46 u32 align; 47 i32 fixed_offset; 48 /* Spill-traffic priority (O1.md W1.0), copied from IRFrameSlot.priority. The 49 * known-frame emitter (opt_emit_native) presents slot descs to the backend in 50 * descending-priority order, so the bump allocator (native_frame_slot_alloc) 51 * gives the hottest spills the smallest final displacement. The backend itself 52 * ignores this field — it is purely an ordering signal for the emitter. */ 53 u32 priority; 54 u8 kind; /* NativeFrameSlotKind */ 55 u8 pad; 56 u16 flags; /* NativeFrameSlotFlag */ 57 } NativeFrameSlotDesc; 58 59 typedef struct NativeKnownFrameDesc { 60 const NativeFrameSlotDesc* slots; 61 u32 nslots; 62 u32 max_outgoing; 63 u32 align; 64 /* Callee-saved hard registers the allocator assigned, one bitmask per 65 * NativeAllocClass (indexed by class id). The backend reserves a save slot 66 * and emits the prologue save / epilogue restore for each — equivalent to a 67 * reserve_callee_saves() call, but folded into the known-frame setup so the 68 * full frame is fixed before the prologue is emitted. NULL / 0 means none. */ 69 const u32* callee_saved_used; 70 u32 ncallee_classes; 71 /* Union of the clobber register names of every inline-asm block in the body. 72 * Inline-asm clobbers are invisible to the operand scan that builds 73 * callee_saved_used, so the optimizer forwards the raw names here and the 74 * backend resolves them with its own clobber parser, folding the callee-saved 75 * ones into its save set (applying its ABI predicate, which excludes the 76 * frame pointer and keeps any reserved-but-callee-saved scratch such as x64 77 * rbx). The prologue/epilogue then preserve them, so the asm hook needs no 78 * per-block spill — which on the known-frame path would request a frame slot 79 * after the frame is already final. NULL / 0 when the body contains no inline 80 * asm. */ 81 const Sym* asm_clobbers; 82 u32 nasm_clobbers; 83 /* Union of KitCgAsmClobberAbiSet bits over the body's inline-asm blocks: an 84 * arch-neutral "clobbers the whole caller/callee-saved set" the backend 85 * expands against its own register file, alongside the named asm_clobbers. */ 86 u32 asm_clobber_abi_sets; 87 /* Whether the function body contains a dynamic alloca. The backend needs this 88 * up front (before the body) to decide prologue/epilogue form, since with a 89 * known frame the slim-epilogue eligibility is settled at func_begin. */ 90 u8 has_alloca; 91 /* Whether the body has an operation that needs a backend-internal scratch 92 * spill slot — on aa64, an atomic read-modify-write, whose retry loop spills 93 * one scratch register. The backend reserves the slot up front so the body 94 * never grows the frame after the prologue. */ 95 u8 needs_scratch_spill; 96 /* Whether the function is a leaf — its body contains no call of any kind 97 * (regular or sibling/tail). A leaf does not clobber the return-address 98 * register or the stack below sp through a call, so backends can omit the 99 * saved-frame record entirely (rv64 leaf tier) or skip the stack reservation 100 * and keep locals in the red zone (x64 SysV red-zone tier) — but ONLY when 101 * `has_asm` is also clear (see below). Conservatively false whenever any 102 * IR_CALL is present. */ 103 u8 is_leaf; 104 /* Whether the body contains an inline-asm block. Inline asm can clobber the 105 * return-address register (rv64 ra) or write into the red zone / make a call 106 * (x64) without the optimizer modelling it, so the frame-eliding 107 * leaf/red-zone tiers must NOT fire when this is set — even for an 108 * otherwise-leaf function. The single-pass and fat known-frame shapes always 109 * save the return address and reserve their stack, so they are unaffected. */ 110 u8 has_asm; 111 /* Whether the body reads its own frame-pointer chain via 112 * __builtin_frame_address / __builtin_return_address (INTRIN_FRAME_ADDRESS / 113 * INTRIN_RETURN_ADDRESS). Such a function must keep a valid frame record and 114 * frame pointer, so the frameless-leaf tier (rv64 slim_prologue, which emits 115 * no prologue and never anchors s0) must NOT fire. aa64/x64 keep the frame 116 * record in every prologue shape, so they ignore this flag. */ 117 u8 reads_frame; 118 } NativeKnownFrameDesc; 119 120 typedef enum NativeAllocClass { 121 NATIVE_REG_INT, 122 NATIVE_REG_FP, 123 NATIVE_REG_VEC, 124 NATIVE_REG_CLASS_COUNT, 125 } NativeAllocClass; 126 127 #define NATIVE_MAX_HARD_REGS 32u 128 129 typedef u32 NativeRegMaskSet[NATIVE_REG_CLASS_COUNT]; 130 131 typedef enum NativeRegFlag { 132 NATIVE_REG_NONE = 0, 133 NATIVE_REG_ALLOCABLE = 1u << 0, 134 NATIVE_REG_CALLER_SAVED = 1u << 1, 135 NATIVE_REG_CALLEE_SAVED = 1u << 2, 136 NATIVE_REG_ARG = 1u << 3, 137 NATIVE_REG_RET = 1u << 4, 138 NATIVE_REG_RESERVED = 1u << 5, 139 NATIVE_REG_TEMP_PREFERRED = 1u << 6, 140 } NativeRegFlag; 141 142 typedef struct NativePhysRegInfo { 143 Reg reg; 144 u8 cls; /* NativeAllocClass */ 145 u8 abi_index; /* 0xff when not an ordered ABI arg/ret register */ 146 u16 flags; /* NativeRegFlag */ 147 u16 spill_cost; 148 u16 copy_cost; 149 } NativePhysRegInfo; 150 151 typedef struct NativeAllocClassInfo { 152 u8 cls; /* NativeAllocClass */ 153 u8 pad[3]; 154 155 /* NativeDirectTarget (-O0) value-cache register policy. Optimized 156 * allocation is described independently by NATIVE_REG_ALLOCABLE rows in 157 * `phys`; neither path may infer its value bank from the other. */ 158 const Reg* ndt_allocable; 159 u32 ndt_allocable_count; 160 161 /* NativeDirectTarget (-O0) value-cache temporary policy only. Optimized 162 * emission must not infer operand registers from this list; it owns the 163 * independently declared, instruction-scoped `emit_temps` bank below. */ 164 const Reg* scratch; 165 u32 nscratch; 166 167 /* Registers owned exclusively by an -O1 native-emission instruction scope. 168 * They are operand-facing temporaries, not MIR values and not backend-private 169 * implementation registers. The set is explicit rather than inherited from 170 * the -O0 cache's `scratch` policy: those two clients have different 171 * lifetimes and may require disjoint banks (notably ARM32). */ 172 const Reg* emit_temps; 173 u32 nemit_temps; 174 175 /* Backend-internal registers that may be loaned specifically while binding 176 * a register-only inline-asm block. They are otherwise unavailable to the 177 * optimizer emitter. The asm staging planner uses them only when the block 178 * has no memory constraint (whose address binder may need the same bank). */ 179 const Reg* asm_temps; 180 u32 nasm_temps; 181 182 /* Ordered register policy for operands self-allocated by the O0 direct 183 * inline-asm binder. This is distinct from ndt_allocable and scratch: values 184 * live for the complete asm block, and the order is an architecture policy. 185 * Explicit hard-register pins are validated separately and need not appear 186 * here. Selection itself is shared in cg/native_asm.c. */ 187 const Reg* direct_asm_allocable; 188 u32 ndirect_asm_allocable; 189 190 /* Subset of reserved optimizer emitter temps whose contents survive ordinary 191 * backend hooks when they are not passed as operands or named by a clobber 192 * effect. The native emitter may retain clean FS_SPILL values in these and 193 * in dead O1 allocation registers whose ABI preservation is established 194 * (caller-saved, or a callee-saved register present in the known frame) 195 * across adjacent instructions; O1 allocation-register preservation is 196 * governed by machine_op_clobbers below. */ 197 u32 emit_cache_mask; 198 199 const NativePhysRegInfo* phys; 200 u32 nphys; 201 } NativeAllocClassInfo; 202 203 typedef struct NativeRegInfo NativeRegInfo; 204 struct NativeRegInfo { 205 const NativeAllocClassInfo* classes; 206 u32 nclasses; 207 208 /* True when this register model's scratch + caller-saved NDT cache pool is 209 * rich enough that the single-pass (-O0) NativeDirectTarget never needs to 210 * fall back to a callee-saved register under pressure. When set, NDT is 211 * enforced caller-saved-only (nd_scratch_acquire skips callee-saved regs; 212 * nd_func_end asserts none were used), which lets the backend reserve only a 213 * tiny tcc-style deferred-`sub` prologue region instead of a worst-case 214 * callee-save area. When clear (the default), NDT keeps its historical 215 * behavior: it may use callee-saved registers as extra scratch and the 216 * prologue preserves them. Independent of the optimizer/-O1 path, which always 217 * has full callee-save freedom. */ 218 u8 ndt_caller_saved_only; 219 220 /* True when the ABI scalar-result register is clobbered ONLY at call 221 * boundaries (where the -O0 cache already flushes), so a scalar call result 222 * can be left cached in it across following ops instead of being moved into a 223 * general cache register (saving the post-call `mov cachereg, retreg`). Set on 224 * aarch64 (x0) and riscv64 (a0), whose div/mul/etc. take explicit operands. 225 * Clear on x86-64, where RAX is an implicit div/mul operand and a result left 226 * in it would be silently clobbered before its consumer. */ 227 u8 ndt_result_reg_stable; 228 229 /* True when this backend honors MF_SEXT_LOAD by emitting a sign-extending 230 * load that fills the whole register (aa64 ldrsb/ldrsh -> X), so the shared 231 * Native codegen may record load_sext on the loaded local and elide the following 232 * CV_SEXT. Clear (the default) on backends whose narrow load zero-extends 233 * regardless of the flag (x86-64, riscv64 today): there the convert must run, 234 * so the flag is a no-op and the extend is emitted normally. */ 235 u8 load_sext; 236 237 /* Map a register name to its (Reg, class). `name` is the raw spelling 238 * ("rax", "x8", "a7"); the caller resolves any Sym to its bytes first so this 239 * stays pool-free. Returns 0 on success, non-zero for a non-register name. */ 240 int (*resolve_name)(const NativeRegInfo*, Slice name, Reg* out, 241 NativeAllocClass* cls_out); 242 /* True when (cls, reg) is a valid hard-register home for an inline-asm value 243 * operand. This is intentionally separate from allocator availability: 244 * syscall idioms need ABI registers such as x8/a7, while stack/frame, zero, 245 * link, platform, and backend scratch registers must stay unavailable even if 246 * the assembler can name them. */ 247 int (*asm_operand_reg_ok)(const NativeRegInfo*, NativeAllocClass cls, 248 Reg reg); 249 /* Optional target-specific register-constraint parser for inline asm. The 250 * input is the constraint body after generic modifiers ('=', '+', '&') have 251 * been stripped. Return non-zero only for constraints that name a register 252 * class; set fixed_out to REG_NONE for a free class or to a physical register 253 * when the constraint hard-wires the operand (x86 "a" -> rax). Set 254 * allowed_mask_out to 0 for the whole class, or a physical-register bitmask 255 * when the constraint names a restricted class subset. */ 256 int (*asm_constraint_reg)(const NativeRegInfo*, const char* body, 257 NativeAllocClass* cls_out, Reg* fixed_out, 258 u32* allowed_mask_out); 259 const char* (*debug_name)(const NativeRegInfo*, NativeAllocClass, Reg); 260 u32 (*dwarf_reg)(const NativeRegInfo*, NativeAllocClass, Reg); 261 }; 262 263 /* Validate the target-independent NativeRegInfo contract. Both the direct and 264 * optimized adapters call this at their boundary, so malformed physical rows, 265 * role flags, or temporary banks fail identically at O0 and O1. */ 266 void native_reg_info_validate(Compiler* c, const NativeRegInfo* ri); 267 268 /* Classes are identified by NativeAllocClass, not by their position in the 269 * target's table. Keep the lookup policy shared by every register consumer. */ 270 const NativeAllocClassInfo* native_reg_info_class_info( 271 const NativeRegInfo* ri, NativeAllocClass cls); 272 273 /* Static register roles have one authority: NativePhysRegInfo.flags. Targets 274 * with an OS-dependent preservation ABI override caller/callee masks through 275 * NativeTarget below; all other role consumers derive their masks here. */ 276 u32 native_reg_info_flag_mask(const NativeRegInfo* ri, NativeAllocClass cls, 277 u16 flag); 278 279 typedef enum NativeLocKind { 280 NATIVE_LOC_NONE, 281 NATIVE_LOC_REG, 282 NATIVE_LOC_FRAME, 283 NATIVE_LOC_STACK, 284 NATIVE_LOC_IMM, 285 NATIVE_LOC_GLOBAL, 286 /* Address value of a concrete frame slot. Distinct from NATIVE_LOC_FRAME, 287 * which is the scalar value stored in the slot, and NATIVE_LOC_ADDR, which 288 * is the scalar value addressed by an effective-address expression. */ 289 NATIVE_LOC_FRAME_ADDR, 290 NATIVE_LOC_ADDR, 291 } NativeLocKind; 292 293 typedef enum NativeAddrBaseKind { 294 NATIVE_ADDR_BASE_NONE, 295 /* Pointer value already held in a physical register. */ 296 NATIVE_ADDR_BASE_REG, 297 /* Address of the frame slot itself: FP + slot offset. */ 298 NATIVE_ADDR_BASE_FRAME, 299 /* Scalar pointer value stored in a frame slot. base_type is mandatory and 300 * is the exact value type, independent of the colored slot descriptor. */ 301 NATIVE_ADDR_BASE_FRAME_VALUE, 302 NATIVE_ADDR_BASE_GLOBAL, 303 } NativeAddrBaseKind; 304 305 typedef enum NativeAddrIndexKind { 306 NATIVE_ADDR_INDEX_NONE, 307 NATIVE_ADDR_INDEX_REG, 308 /* Scalar index value stored in a frame slot. index_type is mandatory and is 309 * the exact value type, independent of the colored slot descriptor. */ 310 NATIVE_ADDR_INDEX_FRAME_VALUE, 311 } NativeAddrIndexKind; 312 313 typedef enum NativeImmUse { 314 NATIVE_IMM_MOVE, 315 NATIVE_IMM_BINOP, 316 NATIVE_IMM_CMP, 317 NATIVE_IMM_ADDR_OFFSET, 318 } NativeImmUse; 319 320 /* Addressing-mode index extend (O1-PATTERNS L8). A backend that advertises 321 * can_fold_extend_into_addr may receive a NATIVE_ADDR_INDEX_REG whose register 322 * names a 32-bit value the addressing mode widens to 64 bits with this extend 323 * (aa64 `[Xbase, Wm, sxtw/uxtw #log2_scale]`). NONE = full-width index, the 324 * default; every other backend only ever sees NONE. */ 325 typedef enum NativeAddrIndexExt { 326 NATIVE_ADDR_IDX_EXT_NONE = 0, 327 NATIVE_ADDR_IDX_EXT_SXTW = 1, 328 NATIVE_ADDR_IDX_EXT_UXTW = 2, 329 } NativeAddrIndexExt; 330 331 typedef struct NativeAddr { 332 u8 base_kind; /* NativeAddrBaseKind */ 333 u8 cls; /* NativeAllocClass for base value */ 334 u8 index_kind; /* NativeAddrIndexKind */ 335 u8 index_cls; /* NativeAllocClass for index value */ 336 u8 log2_scale; 337 u8 index_ext; /* NativeAddrIndexExt */ 338 u8 pad[2]; 339 KitCgTypeId base_type; 340 KitCgTypeId index_type; 341 union { 342 Reg reg; 343 NativeFrameSlot frame; 344 struct { 345 ObjSymId sym; 346 i64 addend; 347 } global; 348 } base; 349 union { 350 Reg reg; 351 NativeFrameSlot frame; 352 } index; 353 i32 offset; 354 } NativeAddr; 355 356 typedef struct NativeLoc { 357 u8 kind; /* NativeLocKind */ 358 u8 cls; /* NativeAllocClass for register-like locations */ 359 /* Cached scalar-width descriptor, stamped once by the NDT at the 360 * register-NativeLoc choke point (nd_loc_reg) so the per-arch binop/move/ 361 * convert/cmp hot path reads a byte instead of re-crossing the type bridge 362 * via cg_type_size on `type`. Layout: 363 * bit0 SZINFO_VALID — set => the size field is authoritative 364 * bits 1..3 SZINFO_SIZE_LOG2 — log2(byte_size) clamped: 0=1,1=2,2=4,3=8,4=16 365 * Each arch derives its own `is64` predicate from this cached size plus its 366 * pointer/xlen rule (so the rv32 4-byte-pointer and i128 distinctions stay 367 * correct per-arch) — the descriptor caches only the size, never a baked 368 * width class. Left 0 (invalid) for cold locs (immediates, memory, unstamped 369 * producers); the arch then falls back to the live cg_type_size query, so 370 * partial adoption stays byte-identical. */ 371 u8 szinfo; 372 /* L7 shift rider (O1-PATTERNS): when nonzero on a register NativeLoc passed 373 * as the second source of a binop, the value is pre-shifted left by `shift` 374 * (1..4) and the backend emits the shifted-register ALU form 375 * (`add xD,xA,xS,lsl #shift`). 0 = use the register as-is. Only stamped when 376 * the target advertised can_fold_shift_into_alu, so a backend without the 377 * capability never sees it. */ 378 u8 shift; 379 KitCgTypeId type; 380 union { 381 Reg reg; 382 NativeFrameSlot frame; 383 struct { 384 NativeFrameSlot slot; 385 i32 offset; 386 } stack; 387 i64 imm; 388 struct { 389 ObjSymId sym; 390 i64 addend; 391 } global; 392 NativeAddr addr; 393 } v; 394 } NativeLoc; 395 396 /* Narrow register-only operand descriptor for the -O0 NativeDirectTarget hot 397 * path. NDT resolves every post-materialization binop/move/cmp/convert operand 398 * to a hard register (or, for an arithmetic/compare RHS, a target-legal 399 * immediate), then crosses into the per-arch NativeTarget. The fat 48 B 400 * NativeLoc carries a union wide enough for memory/global/addr forms that these 401 * ops never receive; passing it by value copies 48 B per operand (144 B/op) 402 * only for the arch to unwrap a register number + the cached width. NativeRegLoc 403 * carries exactly the fields the arch reads — register/immediate, class, the 404 * §E.2 cached width descriptor, and the type for the cold width-fallback — in 405 * 16 B. The narrow *_rr hooks below take it; they are byte-identical to the fat 406 * hooks because native_loc_from_reg reconstructs the same NativeLoc the fat path 407 * would have received. */ 408 typedef struct NativeRegLoc { 409 u8 cls; /* NativeAllocClass for the register/value */ 410 u8 szinfo; /* cached scalar-width descriptor, same encoding as NativeLoc */ 411 u8 is_imm; /* 1 => v.imm holds a target-legal immediate (RHS only); else reg */ 412 u8 pad; 413 KitCgTypeId type; 414 union { 415 Reg reg; 416 i64 imm; 417 } v; 418 } NativeRegLoc; 419 420 typedef struct NativeInst NativeInst; 421 422 typedef enum NativePatchKind { 423 NATIVE_PATCH_FRAME_SIZE, 424 NATIVE_PATCH_MAX_OUTGOING, 425 NATIVE_PATCH_ARCH = 0x1000, 426 } NativePatchKind; 427 428 typedef struct NativePatch { 429 u32 kind; /* NativePatchKind or arch-private */ 430 u32 section_id; 431 u32 offset; 432 u32 width; 433 i64 addend; 434 u64 value; 435 } NativePatch; 436 437 typedef struct NativeFramePatchState { 438 u32 max_outgoing; 439 u32 max_align; 440 } NativeFramePatchState; 441 442 /* A semantic machine operation, enough for the target to report the physical 443 * registers its encoding clobbers as a side effect (e.g. x86 idiv writes 444 * rax/rdx, variable shifts use cl, and a bitfield read-modify-write borrows 445 * fixed temporaries). Built by the optimizer from an instruction; the 446 * descriptor keeps the backend from depending on the optimizer IR. */ 447 typedef enum NativeMachineOpKind { 448 NATIVE_MOP_BINOP, 449 NATIVE_MOP_BITFIELD_LOAD, 450 NATIVE_MOP_BITFIELD_STORE, 451 NATIVE_MOP_VA_START, 452 NATIVE_MOP_VA_ARG, 453 NATIVE_MOP_ATOMIC_CAS, 454 NATIVE_MOP_ATOMIC_RMW, 455 NATIVE_MOP_INTRINSIC, 456 /* A thread-local address materialization (IR_TLS_ADDR_OF). On targets whose 457 * TLS access model uses fixed scratch/result registers or a resolver-thunk 458 * call (e.g. Mach-O TLV descriptors → x0/x16/x17/lr), the encoding clobbers 459 * those regs even though the IR op only declares its destination. Targets 460 * whose TLS sequence touches only the destination register (ELF Local-Exec) 461 * report no clobbers. */ 462 NATIVE_MOP_TLS_ADDR, 463 NATIVE_MOP_COUNT, 464 } NativeMachineOpKind; 465 466 typedef struct NativeMachineOp { 467 u8 kind; /* NativeMachineOpKind */ 468 u8 binop; /* BinOp, when kind == NATIVE_MOP_BINOP */ 469 u8 intrin; /* IntrinKind, when kind == NATIVE_MOP_INTRINSIC */ 470 u8 second_is_reg; /* binop's second operand is a register (not an immediate) 471 */ 472 u8 result_is_fp; /* result lands in an FP register (e.g. va_arg of a double) 473 */ 474 } NativeMachineOp; 475 476 typedef struct NativeCallDesc { 477 KitCgTypeId fn_type; 478 NativeLoc callee; 479 /* Each argument is a complete semantic value. REG/IMM carry scalar bits; 480 * FRAME/STACK/ADDR name dereferenceable storage; GLOBAL/FRAME_ADDR are 481 * scalar address values and must classify as one pointer-sized ABI part. 482 * Backends use native_loc_addr_role plus the checked converters below rather 483 * than inferring value-vs-storage semantics from union fields. */ 484 const NativeLoc* args; 485 const NativeLoc* results; 486 u32 nargs; 487 u32 nresults; 488 u16 flags; /* CGCallFlag */ 489 u8 tail_policy; /* KitCgTailPolicy */ 490 u8 pad; 491 KitCgInlinePolicy inline_policy; 492 } NativeCallDesc; 493 494 typedef enum NativeCallPhaseMoveKind { 495 NATIVE_CALL_MOVE_NONE, 496 NATIVE_CALL_MOVE_VALUE, 497 NATIVE_CALL_MOVE_ADDR, 498 } NativeCallPhaseMoveKind; 499 500 typedef struct NativeCallPhaseMove { 501 NativeLoc src; 502 NativeLoc dst; 503 MemAccess mem; 504 u8 src_kind; /* NativeCallPhaseMoveKind */ 505 u8 dst_kind; /* NativeLocKind */ 506 u8 pad[2]; 507 } NativeCallPhaseMove; 508 509 typedef struct NativeCallPhaseRet { 510 NativeLoc src; 511 NativeLoc dst; 512 MemAccess mem; 513 } NativeCallPhaseRet; 514 515 typedef struct NativeCallPhase { 516 NativeLoc callee; 517 NativeCallPhaseMove* args; 518 NativeCallPhaseRet* rets; 519 u32 nargs; 520 u32 nrets; 521 u32 stack_arg_size; 522 u16 flags; /* CGCallFlag */ 523 u8 has_sret; 524 u8 is_variadic; 525 } NativeCallPhase; 526 527 typedef struct NativeTarget NativeTarget; 528 struct NativeTarget { 529 Compiler* c; 530 ObjBuilder* obj; 531 MCEmitter* mc; 532 const NativeRegInfo* regs; 533 u64 disabled_backend_features; 534 535 NativeAllocClass (*class_for_type)(NativeTarget*, KitCgTypeId); 536 int (*imm_legal)(NativeTarget*, NativeImmUse, u32 op, KitCgTypeId, i64); 537 int (*addr_legal)(NativeTarget*, const NativeAddr*, MemAccess); 538 /* Optional O1-PATTERNS rider capabilities. The target-agnostic recognition 539 * passes (pass_combine / pass_addr_fold) only stamp an operand rider after 540 * the consuming backend advertises the matching capability here, so a 541 * backend that cannot emit the folded form never receives a rider it would 542 * have to silently drop. NULL == not supported (the default for every 543 * backend that has not opted in). 544 * 545 * can_fold_shift_into_alu (L7): the backend emits a single-use left shift 546 * folded into a consuming IADD/ISUB/AND/ORR/EOR as the shifted-register form 547 * (aa64 `add xD,xA,xS,lsl #k`), honoring OptOperand.shift on a binop's 548 * second source operand. 549 * 550 * can_fold_extend_into_addr (L8): the backend emits a single-use sxtw/uxtw 551 * of a load/store index folded into the addressing mode (aa64 `[Xb, Wm, 552 * sxtw/uxtw #scale]`), honoring NativeAddr.index_ext. */ 553 int (*can_fold_shift_into_alu)(NativeTarget*); 554 int (*can_fold_extend_into_addr)(NativeTarget*); 555 /* Optional, but exhaustive when present. Report every optimizer-visible 556 * physical register the target's encoding of `op` clobbers as a side effect 557 * (not its declared operands/results), one bitmask per NativeAllocClass. 558 * This includes registers that can be MIR hard homes and cache-enabled emit 559 * temps whose retained spill value must be invalidated. Pure backend-private 560 * temporaries are reserved, never cached, and do not appear here. This single 561 * contract lets regalloc keep live values out of fixed clobbers and lets the 562 * emitter retain clean spill values safely. Examples include x86 idiv 563 * (rax/rdx), variable shift (rcx), bitfield RMW and atomics (rax/rcx/rdx), 564 * and format-dependent TLS sequences. Return non-zero if any register is 565 * clobbered, 0 otherwise. NULL promises no optimizer-visible fixed 566 * clobbers. */ 567 int (*machine_op_clobbers)(NativeTarget*, const NativeMachineOp* op, 568 NativeRegMaskSet clobber_mask); 569 570 void (*func_begin)(NativeTarget*, const CGFuncDesc*); 571 void (*func_begin_known_frame)(NativeTarget*, const CGFuncDesc*, 572 const NativeKnownFrameDesc*, 573 NativeFrameSlot* out_slots); 574 void (*note_frame_state)(NativeTarget*, const NativeFramePatchState*); 575 /* Optional. Called once after func_begin and before frame-slot mapping, with 576 * the set of callee-saved hard registers the allocator assigned (one bitmask 577 * per NativeAllocClass, indexed by class id). The target reserves save slots 578 * and emits the prologue save / epilogue restore for each. Register 579 * allocation is complete before emission, so the caller knows the full set 580 * up front. */ 581 void (*reserve_callee_saves)(NativeTarget*, const u32* used_by_class, 582 u32 nclasses); 583 /* Optional live-ABI caller/callee-saved register masks for a class. Static 584 * NativeAllocClassInfo masks describe the target register file, but some 585 * targets vary preservation rules by OS ABI (x64 SysV vs Win64 XMM regs). 586 * The optimizer and direct emission use these to keep allocation, call 587 * clobbers, and prologue save sets aligned with the selected ABI. NULL falls 588 * back to NativeAllocClassInfo.{caller,callee}_saved_mask. */ 589 u32 (*caller_saved_mask)(NativeTarget*, NativeAllocClass); 590 u32 (*callee_saved_mask)(NativeTarget*, NativeAllocClass); 591 /* Bytes of stack-passed arguments the fixed parameters of this function 592 * signature use (the part beyond the register arg pools). Sets *variadic to 593 * whether the signature is variadic and *nparams to the fixed parameter 594 * count. Used to decide tail-call (sibling) realizability: the callee's 595 * outgoing stack args must fit the area the caller itself received. Either 596 * out-pointer may be NULL. May itself be NULL. */ 597 u32 (*signature_stack_bytes)(NativeTarget*, KitCgTypeId fn_type, 598 int* variadic, u32* nparams); 599 /* Pure query: the outgoing stack-argument bytes a call with this descriptor 600 * uses, rounded to the ABI's outgoing-area alignment. Reads only fn_type, 601 * flags, nargs, and each args[i].type — never argument *locations* — so the 602 * optimizer can call it in a frame-planning pre-pass, before any argument 603 * marshalling is emitted, to size the outgoing area. Must equal the 604 * stack_arg_size marshal_call computes for the same descriptor. May be NULL. */ 605 u32 (*call_stack_bytes)(NativeTarget*, const NativeCallDesc*); 606 /* Integer hardware zero register, if the ISA has one (aa64 wzr/xzr, rv64 607 * x0). When `has_store_zero_reg` is set, the emit path stores a constant 0 608 * straight from `store_zero_reg` instead of materializing 0 into a scratch 609 * with a mov/movz first. */ 610 u8 has_store_zero_reg; 611 Reg store_zero_reg; 612 void (*func_end)(NativeTarget*); 613 614 NativeFrameSlot (*frame_slot)(NativeTarget*, const NativeFrameSlotDesc*); 615 /* Optional. Return a recyclable single-pass temp slot (one previously handed 616 * out for a NATIVE_FRAME_SLOT_TRANSIENT desc) to the frame's free list so a 617 * later transient allocation of the same (size,align) reuses it instead of 618 * growing the frame. Called by NativeDirectTarget's reclaim_temps at statement 619 * boundaries. NULL means the backend does not recycle (frame still correct, 620 * just larger). */ 621 void (*release_frame_slot)(NativeTarget*, NativeFrameSlot); 622 /* Optional post-finalization query for a native frame slot's debug location. 623 * Each arch owns the frame layout math and returns the coordinate system its 624 * debugger/unwinder path can materialize. */ 625 int (*frame_slot_debug_loc)(NativeTarget*, NativeFrameSlot, CGDebugLoc*); 626 /* Place the incoming parameter into `dst`. The caller (which has run register 627 * allocation) chooses the destination: a hard register (NATIVE_LOC_REG) for a 628 * register-allocated scalar param, a frame slot (NATIVE_LOC_FRAME) for an 629 * address-taken / spilled / aggregate param. NATIVE_LOC_NONE means the param 630 * is unused and only the ABI register/stack cursor must advance. Incoming arg 631 * registers are never allocable, so reg destinations never alias an incoming 632 * arg register and ordering across params is unconstrained. */ 633 void (*bind_param)(NativeTarget*, const CGParamDesc*, NativeLoc dst); 634 /* Optional. Called once by the optimizer emit path after the last bind_param, 635 * before the body. Lets a backend that defers register-destination param 636 * binds (to resolve them as a parallel copy, since the allocator may rotate 637 * params across the incoming arg registers — a permutation the naive 638 * per-param move order cannot realize) flush them now. Backends that bind 639 * eagerly leave this NULL. */ 640 void (*bind_params_end)(NativeTarget*); 641 642 MCLabel (*label_new)(NativeTarget*); 643 void (*label_place)(NativeTarget*, MCLabel); 644 void (*jump)(NativeTarget*, MCLabel); 645 void (*cmp_branch)(NativeTarget*, CmpOp, NativeLoc a, NativeLoc b, 646 MCLabel target); 647 void (*indirect_branch)(NativeTarget*, NativeLoc addr, 648 const MCLabel* valid_targets, u32 ntargets); 649 void (*load_label_addr)(NativeTarget*, NativeLoc dst, MCLabel target); 650 651 void (*emit)(NativeTarget*, const NativeInst*); 652 /* All instruction-emission hooks require caller-selected legal physical 653 * value operands. In particular, dst values are NATIVE_LOC_REG and 654 * arithmetic sources are NATIVE_LOC_REG or target-legal immediates. 655 * NativeAddr may retain typed FRAME_VALUE base/index components; the backend 656 * materializes those exact-width values only through its declared internal 657 * registers. It must not allocate or expose a new IR-visible register. */ 658 void (*move)(NativeTarget*, NativeLoc dst_reg, NativeLoc src_reg); 659 void (*load_imm)(NativeTarget*, NativeLoc dst_reg, i64 imm); 660 void (*load_const)(NativeTarget*, NativeLoc dst_reg, ConstBytes); 661 /* Address materialization must accept dst_reg aliasing a BASE_REG component 662 * while still consuming any distinct index before the destination write. 663 * The emitter never requests dst_reg aliasing the index itself. */ 664 void (*load_addr)(NativeTarget*, NativeLoc dst_reg, NativeAddr addr); 665 /* A one-register scalar load from a plain BASE_REG address must accept 666 * dst_reg aliasing that base: the effective address is consumed before the 667 * destination is written. This lets the target-neutral emitter reuse an 668 * instruction-leased address register under maximal temporary pressure. */ 669 void (*load)(NativeTarget*, NativeLoc dst_reg, NativeAddr addr, MemAccess); 670 void (*store)(NativeTarget*, NativeAddr addr, NativeLoc src_reg, MemAccess); 671 void (*tls_addr_of)(NativeTarget*, NativeLoc dst_reg, ObjSymId sym, 672 i64 addend); 673 void (*copy_bytes)(NativeTarget*, NativeAddr dst, NativeAddr src, 674 AggregateAccess); 675 void (*set_bytes)(NativeTarget*, NativeAddr dst, NativeLoc byte_value, 676 AggregateAccess); 677 /* Like load(), a one-register bitfield load must consume a plain BASE_REG 678 * address before writing dst_reg and accept those registers aliasing. */ 679 void (*bitfield_load)(NativeTarget*, NativeLoc dst_reg, 680 NativeAddr record_addr, BitFieldAccess); 681 void (*bitfield_store)(NativeTarget*, NativeAddr record_addr, 682 NativeLoc src_reg, BitFieldAccess); 683 void (*binop)(NativeTarget*, BinOp, NativeLoc dst_reg, NativeLoc a_reg, 684 NativeLoc b_reg_or_imm); 685 void (*unop)(NativeTarget*, UnOp, NativeLoc dst_reg, NativeLoc src_reg); 686 void (*cmp)(NativeTarget*, CmpOp, NativeLoc dst_reg, NativeLoc a_reg, 687 NativeLoc b_reg_or_imm); 688 void (*convert)(NativeTarget*, ConvKind, NativeLoc dst_reg, 689 NativeLoc src_reg); 690 /* Narrow register-only entry points for the -O0 NativeDirectTarget hot path 691 * (§E.3). Same semantics and byte output as the fat binop/move/cmp/convert, 692 * but the operands cross as 16 B NativeRegLoc instead of 48 B NativeLoc. NDT 693 * calls these when installed (always, for the native arch backends); the 694 * fat hooks above remain for the opt/-O1 replay path, which is unchanged. 695 * Optional: a backend that leaves them NULL keeps the fat path. */ 696 void (*binop_rr)(NativeTarget*, BinOp, NativeRegLoc dst, NativeRegLoc a, 697 NativeRegLoc b_reg_or_imm); 698 void (*move_rr)(NativeTarget*, NativeRegLoc dst, NativeRegLoc src); 699 void (*cmp_rr)(NativeTarget*, CmpOp, NativeRegLoc dst, NativeRegLoc a, 700 NativeRegLoc b_reg_or_imm); 701 void (*convert_rr)(NativeTarget*, ConvKind, NativeRegLoc dst, 702 NativeRegLoc src); 703 void (*alloca_)(NativeTarget*, NativeLoc dst_reg, NativeLoc size_reg, 704 u32 align); 705 706 void (*spill)(NativeTarget*, NativeLoc src_reg, NativeFrameSlot, MemAccess); 707 void (*reload)(NativeTarget*, NativeLoc dst_reg, NativeFrameSlot, MemAccess); 708 709 /* Mutating ABI-marshalling phase. It may stage the callee, emit stack 710 * arguments and register shuffles, and fill any remaining generic 711 * moves/results in NativeCallPhase. The phase owns every populated ABI 712 * argument destination and any staged callee until emit_call consumes it. */ 713 void (*marshal_call)(NativeTarget*, const NativeCallDesc*, NativeCallPhase*); 714 void (*emit_call)(NativeTarget*, const NativeCallPhase*); 715 /* The corresponding mutating return-marshalling phase. `value` is the 716 * single returned local's location, or NULL for void; the hook may emit an 717 * indirect-result copy and returns the ordered register moves in 718 * out_rets/out_nrets. Completed destinations remain live through the phase. */ 719 void (*marshal_ret)(NativeTarget*, const CGFuncDesc*, const NativeLoc* value, 720 NativeCallPhaseRet** out_rets, u32* out_nrets); 721 void (*ret)(NativeTarget*); 722 723 /* Like load(), a one-register atomic load must consume a plain BASE_REG 724 * address before writing dst and accept those registers aliasing. Backends 725 * already materialize any stronger atomic addressing constraint into their 726 * private scratch before the value load. */ 727 void (*atomic_load)(NativeTarget*, NativeLoc dst, NativeAddr addr, MemAccess, 728 KitCgMemOrder); 729 void (*atomic_store)(NativeTarget*, NativeAddr addr, NativeLoc src, MemAccess, 730 KitCgMemOrder); 731 void (*atomic_rmw)(NativeTarget*, KitCgAtomicOp, NativeLoc dst, 732 NativeAddr addr, NativeLoc val, MemAccess, KitCgMemOrder); 733 void (*atomic_cas)(NativeTarget*, NativeLoc prior, NativeLoc ok, 734 NativeAddr addr, NativeLoc expected, NativeLoc desired, 735 MemAccess, KitCgMemOrder success, KitCgMemOrder failure); 736 void (*fence)(NativeTarget*, KitCgMemOrder); 737 /* Variadic support. The optimizer passes the va_list pointer opaquely as a 738 * NativeLoc (a register or memory location holding the address of the 739 * va_list object); va_arg additionally receives the argument type and a 740 * destination location for the fetched value. All va_list layout knowledge 741 * (pointer ABI vs register-save-area ABI, field offsets, sizes) lives behind 742 * these hooks, which query the target ABI -- the optimizer makes no layout 743 * assumptions. */ 744 void (*va_start_)(NativeTarget*, NativeLoc ap_ptr); 745 void (*va_arg_)(NativeTarget*, NativeLoc dst, NativeLoc ap_ptr, 746 KitCgTypeId type); 747 void (*va_end_)(NativeTarget*, NativeLoc ap_ptr); 748 void (*va_copy_)(NativeTarget*, NativeLoc dst_ap_ptr, NativeLoc src_ap_ptr); 749 /* Value arguments reach the backend as NATIVE_LOC_REG unless the backend 750 * explicitly accepts an immediate for that intrinsic operand. The shared 751 * target-sequence/control operands recognized by 752 * native_intrinsic_arg_accepts_imm below also remain NATIVE_LOC_IMM when the 753 * IR supplied a constant. Keeping this capability explicit prevents a 754 * backend from accidentally decoding an immediate's union payload as a 755 * register while allowing targets with private materialization registers to 756 * avoid unnecessary pressure on the optimizer's operand-temp bank. */ 757 int (*intrinsic_arg_accepts_imm)(NativeTarget*, IntrinKind, u32 arg_index); 758 void (*intrinsic)(NativeTarget*, IntrinKind, const NativeLoc* dsts, u32 ndst, 759 const NativeLoc* args, u32 narg); 760 void (*asm_block)(NativeTarget*, const char* tmpl, const AsmConstraint* outs, 761 u32 nout, NativeLoc* out_locs, const AsmConstraint* ins, 762 u32 nin, const NativeLoc* in_locs, const Sym* clobbers, 763 u32 nclob); 764 void (*file_scope_asm)(NativeTarget*, const char* src, size_t len); 765 void (*patch_add)(NativeTarget*, const NativePatch*); 766 void (*patch_apply)(NativeTarget*); 767 void (*trap)(NativeTarget*); 768 void (*set_loc)(NativeTarget*, SrcLoc); 769 void (*finalize)(NativeTarget*); 770 void (*destroy)(NativeTarget*); 771 }; 772 773 static inline const NativeAllocClassInfo* native_target_class_info( 774 const NativeTarget* t, NativeAllocClass cls) { 775 return t ? native_reg_info_class_info(t->regs, cls) : NULL; 776 } 777 778 static inline u32 native_target_caller_saved_mask(NativeTarget* t, 779 NativeAllocClass cls) { 780 if (t && t->caller_saved_mask) return t->caller_saved_mask(t, cls); 781 return t ? native_reg_info_flag_mask(t->regs, cls, 782 NATIVE_REG_CALLER_SAVED) 783 : 0u; 784 } 785 786 static inline u32 native_target_callee_saved_mask(NativeTarget* t, 787 NativeAllocClass cls) { 788 if (t && t->callee_saved_mask) return t->callee_saved_mask(t, cls); 789 return t ? native_reg_info_flag_mask(t->regs, cls, 790 NATIVE_REG_CALLEE_SAVED) 791 : 0u; 792 } 793 794 static inline u32 native_target_arg_mask(const NativeTarget* t, 795 NativeAllocClass cls) { 796 return t ? native_reg_info_flag_mask(t->regs, cls, NATIVE_REG_ARG) : 0u; 797 } 798 799 static inline u32 native_target_ret_mask(const NativeTarget* t, 800 NativeAllocClass cls) { 801 return t ? native_reg_info_flag_mask(t->regs, cls, NATIVE_REG_RET) : 0u; 802 } 803 804 static inline u32 native_target_reserved_mask(const NativeTarget* t, 805 NativeAllocClass cls) { 806 return t ? native_reg_info_flag_mask(t->regs, cls, NATIVE_REG_RESERVED) : 0u; 807 } 808 809 /* Intrinsic operands whose immediate form is shared across every NativeTarget. 810 * Other immediates are scalar values: the optimized emitter materializes them 811 * unless the target explicitly advertises support through its capability 812 * hook. */ 813 static inline int native_intrinsic_arg_accepts_imm(NativeTarget* t, 814 IntrinKind kind, 815 u32 arg_index) { 816 switch (kind) { 817 case INTRIN_MEMMOVE: 818 return arg_index == 2u; /* constant byte count */ 819 case INTRIN_PREFETCH: 820 return arg_index >= 1u; /* rw, locality */ 821 case INTRIN_ASSUME_ALIGNED: 822 return arg_index >= 1u; /* alignment, offset */ 823 case INTRIN_EXPECT: 824 return arg_index == 1u; /* expected value hint */ 825 case INTRIN_SYSCALL: 826 /* Syscall hooks already use their target's parallel ABI-argument mover, 827 * which accepts both registers and immediates and avoids requiring up to 828 * seven simultaneous emitter-temp leases. */ 829 return 1; 830 case INTRIN_DMB: 831 case INTRIN_DSB: 832 return arg_index == 0u; /* KitCgBarrierScope */ 833 case INTRIN_FRAME_ADDRESS: 834 case INTRIN_RETURN_ADDRESS: 835 return arg_index == 0u; /* constant frame-chain level */ 836 default: 837 return t && t->intrinsic_arg_accepts_imm 838 ? t->intrinsic_arg_accepts_imm(t, kind, arg_index) 839 : 0; 840 } 841 } 842 843 /* Location constructors. A designated compound literal initializes the named 844 * fields and zero-fills the rest (so it is value-identical to the former 845 * memset + field stores) but lets the compiler emit only the needed stores 846 * instead of an out-of-line memset of the ~64-byte struct — these run on the 847 * hottest per-operand codegen path. */ 848 static inline NativeLoc native_loc_none(void) { 849 return (NativeLoc){.kind = NATIVE_LOC_NONE}; 850 } 851 852 /* Target-neutral location constructors and scalar queries. These are 853 * byte-identical across the native backends, so they live here as the single 854 * source of truth. (loc_reg's register mask differs per arch and stays 855 * per-backend.) */ 856 static inline NativeLoc native_loc_reg(KitCgTypeId type, NativeAllocClass cls, 857 Reg reg) { 858 return (NativeLoc){ 859 .kind = NATIVE_LOC_REG, .cls = (u8)cls, .type = type, .v.reg = reg}; 860 } 861 862 static inline NativeLoc native_loc_stack(KitCgTypeId type, NativeFrameSlot slot, 863 i32 offset) { 864 return (NativeLoc){.kind = NATIVE_LOC_STACK, 865 .cls = NATIVE_REG_INT, 866 .type = type, 867 .v.stack = {.slot = slot, .offset = offset}}; 868 } 869 870 /* A NativeLoc that structurally contains an address has one of two disjoint 871 * semantic roles. Storage locations are dereferenced to obtain a value; 872 * address recipes are themselves scalar pointer values. Keep the role in the 873 * shared contract so ABI marshallers cannot accidentally turn `&slot` into a 874 * load from slot (or turn a stored scalar into its address). */ 875 typedef enum NativeLocAddrRole { 876 NATIVE_LOC_ADDR_ROLE_NONE, 877 NATIVE_LOC_ADDR_ROLE_STORAGE, 878 NATIVE_LOC_ADDR_ROLE_VALUE, 879 } NativeLocAddrRole; 880 881 static inline NativeLocAddrRole native_loc_addr_role(NativeLoc loc) { 882 switch ((NativeLocKind)loc.kind) { 883 case NATIVE_LOC_FRAME: 884 case NATIVE_LOC_STACK: 885 case NATIVE_LOC_ADDR: 886 return NATIVE_LOC_ADDR_ROLE_STORAGE; 887 case NATIVE_LOC_GLOBAL: 888 case NATIVE_LOC_FRAME_ADDR: 889 return NATIVE_LOC_ADDR_ROLE_VALUE; 890 default: 891 return NATIVE_LOC_ADDR_ROLE_NONE; 892 } 893 } 894 895 /* Resolve storage that may be dereferenced. `offset` selects bytes within the 896 * stored object (including an existing STACK/ADDR displacement). */ 897 static inline int native_loc_storage_addr(NativeLoc loc, i32 offset, 898 NativeAddr* out) { 899 NativeAddr addr = {0}; 900 if (!out || native_loc_addr_role(loc) != NATIVE_LOC_ADDR_ROLE_STORAGE) 901 return 0; 902 switch ((NativeLocKind)loc.kind) { 903 case NATIVE_LOC_FRAME: 904 addr.base_kind = NATIVE_ADDR_BASE_FRAME; 905 addr.base.frame = loc.v.frame; 906 addr.base_type = loc.type; 907 break; 908 case NATIVE_LOC_STACK: 909 addr.base_kind = NATIVE_ADDR_BASE_FRAME; 910 addr.base.frame = loc.v.stack.slot; 911 addr.base_type = loc.type; 912 addr.offset = loc.v.stack.offset; 913 break; 914 case NATIVE_LOC_ADDR: 915 addr = loc.v.addr; 916 break; 917 default: 918 return 0; 919 } 920 addr.offset += offset; 921 *out = addr; 922 return 1; 923 } 924 925 /* Resolve a scalar address recipe. There is intentionally no part offset: 926 * GLOBAL/FRAME_ADDR are one pointer value, not byte-addressable aggregate 927 * storage. A call marshaller that tries to split one must reject the shape. */ 928 static inline int native_loc_address_value(NativeLoc loc, NativeAddr* out) { 929 NativeAddr addr = {0}; 930 if (!out || native_loc_addr_role(loc) != NATIVE_LOC_ADDR_ROLE_VALUE) 931 return 0; 932 switch ((NativeLocKind)loc.kind) { 933 case NATIVE_LOC_FRAME_ADDR: 934 addr.base_kind = NATIVE_ADDR_BASE_FRAME; 935 addr.base.frame = loc.v.frame; 936 addr.base_type = loc.type; 937 break; 938 case NATIVE_LOC_GLOBAL: 939 addr.base_kind = NATIVE_ADDR_BASE_GLOBAL; 940 addr.base.global.sym = loc.v.global.sym; 941 addr.base.global.addend = loc.v.global.addend; 942 addr.base_type = loc.type; 943 break; 944 default: 945 return 0; 946 } 947 *out = addr; 948 return 1; 949 } 950 951 /* Iterate the exact bytes carried by one ABI part as native load/store widths. 952 * ABIArgPart.size is a semantic byte count, not the rounded size of its 953 * register or stack carrier: a six-byte aggregate part must transfer 4+2 954 * bytes, never one 8-byte access that can cross the source/destination object. 955 * 956 * Chunks are returned from low to high byte offsets, greedily choosing the 957 * largest power-of-two width no greater than max_chunk. All native targets are 958 * little-endian today, so the byte offset is also the register shift in bytes 959 * when packing/unpacking a part. Physical ABI stack-slot sizing remains the 960 * backend's separate responsibility. */ 961 typedef struct NativePartChunkIter { 962 u32 total; 963 u32 offset; 964 u32 max_chunk; 965 } NativePartChunkIter; 966 967 static inline NativePartChunkIter native_part_chunks(u32 total, 968 u32 max_chunk) { 969 NativePartChunkIter it; 970 it.total = total; 971 it.offset = 0; 972 it.max_chunk = max_chunk; 973 return it; 974 } 975 976 static inline int native_part_chunk_next(NativePartChunkIter* it, 977 u32* offset_out, u32* size_out) { 978 u32 chunk = 1u; 979 u32 remaining; 980 if (!it || !offset_out || !size_out || it->offset >= it->total || 981 it->max_chunk == 0u) 982 return 0; 983 remaining = it->total - it->offset; 984 while (chunk <= remaining / 2u && chunk <= it->max_chunk / 2u) chunk <<= 1; 985 *offset_out = it->offset; 986 *size_out = chunk; 987 it->offset += chunk; 988 return 1; 989 } 990 991 /* Select the raw byte lane used for one ABI part of an immediate scalar. The 992 * complete value is only i64-wide, so bounds are explicit. Offset zero keeps 993 * the producer's signed numeric value (preserving established narrow-scalar 994 * extension behavior); later parts are bit slices and are zero-extended. */ 995 static inline int native_loc_imm_part(NativeLoc loc, u32 offset, u32 size, 996 i64* out) { 997 u64 bits, mask; 998 if (!out || loc.kind != NATIVE_LOC_IMM || size == 0u || size > 8u || 999 offset >= 8u || size > 8u - offset) 1000 return 0; 1001 if (offset == 0u) { 1002 *out = loc.v.imm; 1003 return 1; 1004 } 1005 bits = (u64)loc.v.imm >> (offset * 8u); 1006 mask = size == 8u ? ~(u64)0 : (((u64)1u << (size * 8u)) - 1u); 1007 *out = (i64)(bits & mask); 1008 return 1; 1009 } 1010 1011 static inline int native_loc_is_fp(NativeLoc loc) { 1012 return (NativeAllocClass)loc.cls == NATIVE_REG_FP; 1013 } 1014 1015 /* Narrow a register-or-immediate NativeLoc to the 16 B NativeRegLoc the §E.3 1016 * *_rr hooks take. Only NATIVE_LOC_REG / NATIVE_LOC_IMM forms reach these hooks 1017 * (the post-materialization contract), so this carries the register/immediate, 1018 * class, the cached width descriptor, and the type for the cold fallback. */ 1019 static inline NativeRegLoc native_reg_loc_of(NativeLoc loc) { 1020 NativeRegLoc r; 1021 r.cls = loc.cls; 1022 r.szinfo = loc.szinfo; 1023 r.is_imm = (u8)(loc.kind == NATIVE_LOC_IMM); 1024 r.pad = 0; 1025 r.type = loc.type; 1026 if (loc.kind == NATIVE_LOC_IMM) 1027 r.v.imm = loc.v.imm; 1028 else 1029 r.v.reg = loc.v.reg; 1030 return r; 1031 } 1032 1033 /* Reconstruct the full NativeLoc a fat binop/move/cmp/convert hook would have 1034 * received from a NativeRegLoc. Byte-identical by construction: the arch reads 1035 * only kind / cls / szinfo / type / v.{reg,imm}, all preserved here, and the 1036 * rest of the 48 B union is never consulted on this path. */ 1037 static inline NativeLoc native_loc_from_reg(NativeRegLoc r) { 1038 NativeLoc loc; 1039 loc.kind = (u8)(r.is_imm ? NATIVE_LOC_IMM : NATIVE_LOC_REG); 1040 loc.cls = r.cls; 1041 loc.szinfo = r.szinfo; 1042 loc.shift = 0; /* O1-only L7 rider; the -O0 NDT fast path never sets it */ 1043 loc.type = r.type; 1044 if (r.is_imm) 1045 loc.v.imm = r.v.imm; 1046 else 1047 loc.v.reg = r.v.reg; 1048 return loc; 1049 } 1050 1051 /* NativeLoc.szinfo layout (see the field comment on NativeLoc). */ 1052 #define NATIVE_SZINFO_VALID 0x01u 1053 #define NATIVE_SZINFO_SIZE_LOG2_SHIFT 1u 1054 #define NATIVE_SZINFO_SIZE_LOG2_MASK 0x07u 1055 1056 /* size (bytes, 1..16) -> log2 bucket (0=1,1=2,2=4,3=8,4=16). */ 1057 static inline u8 native_szinfo_log2(u32 size) { 1058 if (size <= 1u) return 0u; 1059 if (size <= 2u) return 1u; 1060 if (size <= 4u) return 2u; 1061 if (size <= 8u) return 3u; 1062 return 4u; 1063 } 1064 1065 /* Stamp a register NativeLoc's cached width descriptor from its type, computing 1066 * cg_type_size exactly once. Called by the NDT at its register-loc choke point; 1067 * the arch backends then read loc.szinfo instead of re-querying. Only meaningful 1068 * for scalar (<=16B) reg locs; left invalid for a 0 type or an over-wide size so 1069 * the arch's own (panicking) query path runs. The mapped size is byte-identical 1070 * to native_type_size / aarch64's type_size32 for in-range scalars: a 0 type 1071 * maps to 8, a 0 size to 8 (the register-sized default both helpers apply). */ 1072 static inline void native_loc_stamp_size(NativeTarget* t, NativeLoc* loc) { 1073 u64 n = loc->type ? cg_type_size(t->c, loc->type) : 8u; 1074 if (n == 0) n = 8u; 1075 /* Cache only exact-power-of-two scalar widths (1/2/4/8/16): the szinfo bucket 1076 * recovers the size exactly, so the cached value is bit-for-bit what 1077 * cg_type_size / native_type_size would return. Anything else (over-wide, or a 1078 * non-power-of-two aggregate that should never reach a register loc) is left 1079 * invalid and falls back to the live query. */ 1080 if (n != 1u && n != 2u && n != 4u && n != 8u && n != 16u) return; 1081 loc->szinfo = (u8)(NATIVE_SZINFO_VALID | ((u32)native_szinfo_log2((u32)n) 1082 << NATIVE_SZINFO_SIZE_LOG2_SHIFT)); 1083 } 1084 1085 /* Byte size (1/2/4/8/16) recovered from a stamped szinfo descriptor. Only valid 1086 * when (szinfo & NATIVE_SZINFO_VALID). */ 1087 static inline u32 native_szinfo_size(u8 szinfo) { 1088 u32 lg = 1089 ((u32)szinfo >> NATIVE_SZINFO_SIZE_LOG2_SHIFT) & NATIVE_SZINFO_SIZE_LOG2_MASK; 1090 return 1u << lg; 1091 } 1092 1093 /* Scalar size/align, clamped to a usable register-sized default. Shared by the 1094 * backends whose scalars are at most pointer-width (x64, rv64); aa64 keeps its 1095 * own size query because it asserts on over-wide scalars. */ 1096 /* Round v up to the next multiple of align (a power of two, or 0 → no-op). 1097 * Shared by every native backend's frame-layout math. */ 1098 static inline u32 align_up_u32(u32 v, u32 align) { 1099 u32 mask = align ? align - 1u : 0u; 1100 return (v + mask) & ~mask; 1101 } 1102 1103 static inline u32 native_type_size(NativeTarget* t, KitCgTypeId type) { 1104 u64 n = type ? cg_type_size(t->c, type) : 8u; 1105 if (n == 0) n = 8u; 1106 return (u32)n; 1107 } 1108 1109 static inline u32 native_type_align(NativeTarget* t, KitCgTypeId type) { 1110 u64 n = type ? cg_type_align(t->c, type) : 8u; 1111 if (n == 0) n = 1u; 1112 if (n > 16u) n = 16u; 1113 return (u32)n; 1114 } 1115 1116 static inline MemAccess native_mem_for_type(NativeTarget* t, KitCgTypeId type, 1117 u32 size) { 1118 MemAccess m; 1119 memset(&m, 0, sizeof m); 1120 m.type = type; 1121 m.size = size ? size : native_type_size(t, type); 1122 m.align = native_type_align(t, type); 1123 return m; 1124 } 1125 1126 /* FP register class for a scalar type: a float value lives in an FP register 1127 * only when the hardware float ABI has a register that wide. flen comes from 1128 * the target float ABI (SINGLE->4, DOUBLE->8, SOFT->0); the DEFAULT/unset 1129 * sentinel maps to the pointer width, which preserves the historical "FP iff 1130 * float and <= 8 bytes" behavior for lp64d / x86-64 and yields the correct 1131 * rv32 soft-double result (double is 8 bytes > flen=4 on ilp32f, > 0 on ilp32, 1132 * so it is INT-class and never bit-cast through an FP register via fmv.d.x). 1133 * aa64 keeps its own (same predicate, distinct mem helper). */ 1134 static inline NativeAllocClass native_class_for_type_fp_le8(NativeTarget* t, 1135 KitCgTypeId type) { 1136 u32 flen; 1137 switch (t->c->target.float_abi) { 1138 case KIT_FLOAT_ABI_SINGLE: 1139 flen = 4u; 1140 break; 1141 case KIT_FLOAT_ABI_DOUBLE: 1142 flen = 8u; 1143 break; 1144 case KIT_FLOAT_ABI_SOFT: 1145 flen = 0u; 1146 break; 1147 default: 1148 flen = t->c->target.ptr_size; 1149 break; /* DEFAULT: historical */ 1150 } 1151 if (type && flen && cg_type_is_float(t->c, type) && 1152 cg_type_size(t->c, type) <= flen) 1153 return NATIVE_REG_FP; 1154 return NATIVE_REG_INT; 1155 } 1156 1157 #endif