native.c (250345B)
1 /* aa64 NativeTarget production-readiness checklist: 2 * - ABI completeness: finish AAPCS64/Linux va_list and register-save-area 3 * lowering, verify Apple/AAPCS64/Windows arm64 differences, handle all 4 * homogeneous aggregates, indirect/byval/sret corner cases, small aggregate 5 * splitting, multi-register returns, stack alignment, and ABI diagnostics. 6 * - Calls and returns: replace call-plus-return tail handling with true direct 7 * and indirect sibling calls, preserve musttail ABI guarantees, support stack 8 * argument reshuffling without clobbering live inputs, and cover all sret, 9 * variadic, FP, aggregate, and many-argument combinations. 10 * - Frame lowering: implement known-frame/prologue integration for optimized 11 * emission, spill/reload hooks, callee-save tracking for integer and FP/SIMD 12 * registers, large-frame probing/materialization as needed by each platform, 13 * dynamic alloca restoration, and unwind/debug frame metadata. 14 * - Operations and intrinsics: fill remaining scalar, FP, conversion, rounding, 15 * overflow, bit, vector/SIMD, trap, prefetch, and target-specific intrinsics; 16 * validate NaN/ordered/unordered FP compare semantics and integer narrowing 17 * behavior for every supported width. 18 * - Aggregates and memory: support large constants, overlap-safe memmove, 19 * optimized bulk copy/set selection, bitfield load/store, packed/unaligned 20 * accesses, volatile access constraints, and record/slice edge cases across 21 * direct and optimized lowering. 22 * - Atomics: replace ordinary load/store RMW/CAS sequences with correct LL/SC 23 * or LSE loops, implement acquire/release/seq_cst mappings precisely, handle 24 * failure ordering, byte/halfword/word/dword widths, and retry/clobber rules. 25 * - Inline and file-scope asm: complete register/memory/immediate constraints, 26 * named operands, tied operands, early-clobber and clobber validation, hard 27 * register conflicts, memory barriers, outputs for aggregates/FP values, and 28 * file-scope asm integration. */ 29 30 #include <string.h> 31 32 #include "abi/abi.h" 33 #include "arch/aa64/aa64.h" 34 #include "arch/aa64/asm.h" 35 #include "arch/aa64/isa.h" 36 #include "arch/aa64/regs.h" 37 #include "asm/asm.h" 38 #include "asm/asm_lex.h" 39 #include "cg/native_argmove.h" 40 #include "cg/native_asm.h" 41 #include "cg/native_direct_target.h" 42 #include "cg/native_frame.h" 43 #include "cg/type.h" 44 #include "core/arena.h" 45 #include "core/bytes.h" 46 #include "core/core.h" 47 #include "core/pool.h" 48 #include "core/slice.h" 49 #include "obj/obj.h" 50 51 #if defined(__GNUC__) || defined(__clang__) 52 #define AA_UNUSED_FN __attribute__((unused)) 53 #else 54 #define AA_UNUSED_FN 55 #endif 56 57 enum { 58 AA_X8 = 8u, /* indirect-result (sret) register; usable as a copy base that 59 aa_copy_bytes (which scratches only x16/x17) never clobbers */ 60 /* Tail epilogues restore x30 and may use x16/x17 while rebuilding the frame, 61 * so an indirect tail target is moved here only after argument marshalling. 62 * x9 is an instruction-scoped emitter temp and no later operand phase runs 63 * between marshal_call and the tail branch. */ 64 AA_TAIL_TARGET = 9u, 65 AA_TMP0 = 16u, 66 AA_TMP1 = 17u, 67 /* Callee-saved frame-base anchor. The single-pass far-slot fast path 68 * (aa_emit_mem / AA_PATCH_SLOT) addresses fixed slots at positive scaled 69 * offsets from a stable bottom anchor: sp itself when the function never 70 * moves sp, else this register, set to the frame base in the prologue and 71 * saved / restored like a callee-save (only when the function calls alloca). 72 */ 73 AA_FRAME_BASE = 28u, 74 AA_FP = 29u, 75 AA_LR = 30u, 76 AA_SP = 31u, 77 AA_FRAME_SAVE_SIZE = 16u, 78 /* Worst-case reserved prologue region (NDT single-pass path patches it in 79 * place; the optimizer path reserves exactly what it emits). Sized to hold 80 * the fat prologue plus the Windows large-frame stack probe (≤7 words, see 81 * aa_words_stack_probe). */ 82 /* Worst-case prologue/epilogue word counts. The W1.1 bottom-record general 83 * path can stack: a Windows page probe (≤7), the robust `sub sp` (≤5), the 84 * far saved-pair address build + stp (≤3), the x29 anchor (≤3), a callee-save 85 * base build for huge frames (≤4), and up to 18 callee-save store/restores 86 * (~5 int stp pairs + an int single + 8 fp singles ≈ 14 insns) — round up. */ 87 AA_PROLOGUE_WORDS = 48u, 88 AA_TAIL_WORDS = 48u, 89 /* Single-pass (-O0) tcc-style prologue. The frame-independent entry 90 * `stp x29,x30,[sp,#-(16+top_home)]! ; mov x29,sp` (AA_NDT_FIXED_ENTRY_WORDS) 91 * is emitted live in aa_func_begin; only the `sub sp` that grows the rest of 92 * the frame below the saved pair is deferred (patched once frame_size is 93 * final), plus — on stack-probing targets (Windows) — the page probe. The 94 * single-pass path uses no callee-saved registers (enforced in 95 * nd_scratch_acquire), so nothing else is deferred except the optional 96 * alloca frame-base anchor setup: worst-case sub = load_imm(2)+3 = 5; +probe 97 * load_imm(2)+5 = 7; +frame-base save/mov = 2. */ 98 AA_NDT_FIXED_ENTRY_WORDS = 2u, 99 AA_NDT_SUB_WORDS = 14u, /* worst case (probe + sub + frame-base) */ 100 AA_NDT_SUB_WORDS_NOPROBE = 7u, /* reserved region when no stack probe */ 101 }; 102 103 /* Windows/AArch64 TLS Local-Exec. The TEB pointer lives in the reserved 104 * platform register x18 (never allocated; see AA_PHYS_INT_RESERVED(18)), and 105 * the thread's TLS-array pointer (TEB.ThreadLocalStoragePointer) sits at 106 * TEB+0x58 — same offset as on Win64/x86-64. */ 107 enum { 108 AA_WIN_TEB_REG = 18u, 109 AA_WIN_TEB_TLS_PTR_OFF = 0x58u, 110 }; 111 112 /* ============================================================================ 113 * AAPCS64 frame layout 114 * 115 * Two layouts. Every fp- or sp-relative offset in this file is computed via one 116 * of the aa_fp_off_ / aa_sp_off_ helpers below — no site does bare arithmetic 117 * on AA_FP / AA_SP, and no site outside those helpers branches on the layout. 118 * 119 * TOP-RECORD (default — single-pass -O0, fat frames, and out_stack>0 small 120 * frames). fp anchors at the caller's saved-pair address near the top; sp at 121 * the bottom of the outgoing-arg area. Offsets are frame-size-independent. 122 * 123 * high addr caller's stack frame 124 * +------------------------------+ 125 * | incoming stack args | aa_fp_off_in_arg(a,i) = 16+i 126 * +------------------------------+ 127 * fp --> | saved x29 (prev fp) | aa_fp_off_saved_fp() = 0 128 * | saved x30 (prev lr) | aa_fp_off_saved_lr() = 8 129 * +------------------------------+ 130 * | frame slots | aa_fp_off_slot(a,off) = -off 131 * | (callee-saves + locals | 132 * | + spills + sret/variadic) | 133 * +------------------------------+ 134 * | outgoing args | aa_sp_off_out_arg(i) 135 * sp --> +------------------------------+ 136 * low addr CFA = fp + 16 137 * 138 * BOTTOM-RECORD (fp_at_bottom — the W1.1 uniform x29-at-bottom layout: every 139 * known frame on the -O1 path EXCEPT slim Tier A and the Windows-variadic 140 * top_home case). The frame record + slots stack ABOVE x29, anchored just above 141 * the outgoing-arg area: x29 = sp + out_stack. Every slot is then a positive 142 * `ldr/str [x29,#k]` — one instruction within the 32 KB scaled reach (add-build 143 * `add x16,x29,#hi ; ldr [x16,#lo]` only past it), replacing the top-record 144 * `sub x17,x29,#k ; ldur` fallback. x29 is the frame pointer (reserved, 145 * alloca-stable, set once) so alloca falls out for free and outgoing args stay 146 * sp-relative (calls after an alloca still address [sp,#k]). Offsets depend on 147 * frame_size (hence known-frame only, where the frame is final before the body). 148 * Let N = frame_size, os = out_stack: 149 * 150 * high addr caller's stack frame 151 * +------------------------------+ 152 * | incoming stack args | aa_fp_off_in_arg(a,i)=(N-os)+i 153 * +------------------------------+ <- caller's sp = CFA = x29+(N-os) 154 * | frame slots (+ align pad) | aa_fp_off_slot(a,off)=(N-os)-off 155 * | (callee-saves + locals …) | (in [16, N-os), above record) 156 * +------------------------------+ 157 * x29 = sp+os->| saved x29 (prev fp) | aa_fp_off_saved_fp() = 0 158 * | saved x30 (prev lr) | aa_fp_off_saved_lr() = 8 159 * +------------------------------+ 160 * | outgoing args (os) | aa_sp_off_out_arg(i) ([sp,#i]) 161 * sp --> +------------------------------+ 162 * low addr 163 * 164 * frame_size (N) = align16(AA_FRAME_SAVE_SIZE + slot_bytes + out_stack). 165 * The saved pair sits AT x29 so [x29]=caller fp, [x29+8]=caller lr — the 166 * frame-pointer chain kit's unwinder / __kit_backtrace walks (uniform 167 * fp[0]/fp[1]); non-negotiable. When os==0 this reduces to fp = sp and the 168 * earlier folded `stp [sp,#-N]! ; mov x29,sp` encoding (the original 169 * fp_at_bottom fast path). Tail calls write outgoing args into the caller's 170 * incoming-args window — physically the same address, expressed via 171 * aa_fp_off_tail_out_arg. 172 * ========================================================================== */ 173 174 typedef struct AAFrameLayout { 175 u32 slot_bytes; /* sum of aa_frame_slot reservations (callee-saves + locals 176 * + spills + sret/variadic) */ 177 u32 out_stack; /* max outgoing-arg bytes across all calls in this function */ 178 u32 top_home; /* Windows-variadic GP register home area, reserved between 179 * the saved pair and the incoming stack args so the 180 * plain-pointer va_list walks register then stack varargs as 181 * one contiguous block (0 on every other ABI). */ 182 u32 frame_size; /* align16(AA_FRAME_SAVE_SIZE + top_home + slot_bytes + 183 * out_stack) */ 184 } AAFrameLayout; 185 186 static inline AAFrameLayout aa_build_layout(u32 slot_bytes, u32 out_stack, 187 u32 top_home) { 188 AAFrameLayout L; 189 L.slot_bytes = slot_bytes; 190 L.out_stack = out_stack; 191 L.top_home = top_home; 192 L.frame_size = 193 align_up_u32(AA_FRAME_SAVE_SIZE + top_home + slot_bytes + out_stack, 16u); 194 return L; 195 } 196 197 /* FP-relative byte offsets. The saved-pair is at [fp]/[fp+8] in both the 198 * top-record and bottom-record (fp_at_bottom) layouts, so these two are 199 * layout-independent. The frame-size-dependent helpers — aa_fp_off_in_arg, 200 * aa_fp_off_slot, aa_fp_off_tail_out_arg — branch on a->fp_at_bottom and are 201 * defined after AANativeTarget (see aa_fp_off_* below aa_of). */ 202 static inline i32 aa_fp_off_saved_fp(void) { return 0; } 203 static inline i32 aa_fp_off_saved_lr(void) { return 8; } 204 205 /* SP-relative byte offsets. */ 206 static inline i32 aa_sp_off_out_arg(u32 byte_off) { return (i32)byte_off; } 207 static inline u32 aa_sp_off_saved_pair(const AAFrameLayout* L) { 208 return L->frame_size - AA_FRAME_SAVE_SIZE - L->top_home; 209 } 210 211 /* Frame slots and callee-save records are owned by the shared NativeFrame 212 * bookkeeping (cg/native_frame.h); these aliases keep the aa64-local spellings. 213 */ 214 typedef NativeFrameSlotEntry AANativeSlot; 215 216 /* Deferred in-function patches, all resolved in aa_func_end once the frame 217 * layout (max_outgoing, callee-saves) is final. One growable list carries both 218 * kinds; each entry patches a disjoint, fixed code position, so insertion order 219 * is irrelevant. The prologue region is patched separately (exactly one per 220 * function, fixed position) and is not a list entry. */ 221 typedef enum AAPatchKind { 222 AA_PATCH_ALLOCA, /* single instr: add dst, sp, #max_outgoing */ 223 AA_PATCH_TAIL, /* AA_TAIL_WORDS region: callee restores + frame + br/b */ 224 AA_PATCH_SLOT, /* single instr: far fixed-slot ldr/str at a positive scaled 225 * offset from the frame base (sp, or AA_FRAME_BASE under 226 * alloca), resolved once frame_size is final. */ 227 } AAPatchKind; 228 229 /* A deferred far fixed-slot access. The body emits a one-word placeholder 230 * `ldr/str [sp,#0]`; aa_apply_patches rewrites the base register and scaled 231 * offset once the frame is final. slot_off + extra is the slot's byte position 232 * (as fed to aa_fp_off_slot); the positive frame-base offset is 233 * aa_sp_off_saved_pair(L) + aa_fp_off_slot(slot)+extra (top-record). */ 234 typedef struct AASlotPatch { 235 u32 slot_off; /* aa_slot(...)->off */ 236 i32 extra; /* addr.offset added to the slot base */ 237 u8 sz; /* size_idx: 0 (byte) / 1 (half) / 2 (word) / 3 (dword). sz<2 238 * reserves a 2nd placeholder word (overflow fallback). */ 239 u8 vbit; /* SIMD/FP register (always 0 for sz<2) */ 240 u8 load; /* 1 = ldr, 0 = str */ 241 u8 rt; /* transfer register */ 242 } AASlotPatch; 243 244 typedef struct AAPatch { 245 AAPatchKind kind; 246 u32 pos; 247 union { 248 u32 dst_reg; /* AA_PATCH_ALLOCA */ 249 NativeLoc callee; /* AA_PATCH_TAIL */ 250 AASlotPatch slot; /* AA_PATCH_SLOT */ 251 } u; 252 } AAPatch; 253 254 typedef NativeFrameCalleeSave AACalleeSave; 255 256 typedef struct AANativeTarget { 257 NativeTarget base; 258 SrcLoc loc; 259 const CGFuncDesc* func; 260 261 /* Shared frame bookkeeping: slot table, cumulative offset, max-outgoing, 262 * callee-save set, and the known_frame / has_alloca / frame_final flags. */ 263 NativeFrame frame; 264 /* Final frame size, set once in aa_func_begin_known_frame when fp_at_bottom 265 * is decided. Read by the fp-relative offset helpers in the bottom-record 266 * layout (where slot/incoming-arg offsets depend on frame_size); meaningless 267 * and unread on the single-pass path, which never sets fp_at_bottom. */ 268 u32 frame_size_final; 269 /* Final outgoing-arg area (= max_outgoing), set with frame_size_final in 270 * aa_func_begin_known_frame. In the bottom-record layout x29 is anchored 271 * `out_stack` bytes above sp, so every fp-relative offset is measured from 272 * `frame_size - out_stack` (the bytes above the anchor). Zero on the 273 * single-pass path (top-record) and on os==0 known frames (where the formula 274 * reduces to the original fp_at_bottom one). */ 275 u32 out_stack_final; 276 u32 incoming_stack_size; 277 /* Windows-variadic GP register home area size (gp_reg_count * gp_slot_size, 278 * 64 today; 0 on every other ABI). When nonzero the function takes the fat 279 * top-record layout and homes x0..x7 into [fp + AA_FRAME_SAVE_SIZE ..] so the 280 * plain-pointer va_list can walk register then stack varargs contiguously. */ 281 u32 top_home_bytes; 282 u32 next_param_int; 283 u32 next_param_fp; 284 u32 next_param_stack; 285 NativeFrameSlot sret_ptr_slot; 286 NativeFrameSlot saved_tmp_slot; 287 NativeFrameSlot va_gr_slot; 288 NativeFrameSlot va_vr_slot; 289 290 AAPatch* patches; 291 u32 npatches; 292 u32 patches_cap; 293 u32 nalloca; /* count of AA_PATCH_ALLOCA entries; gates slim prologue/frame */ 294 295 u32 func_start; 296 u32 prologue_pos; 297 u32 prologue_region_words; /* single-pass: reserved deferred-`sub` region */ 298 u32 minimal_prologue_words; /* opt path: exact prologue length, else 0 */ 299 MCLabel epilogue_label; 300 301 /* Set at func_end when this function qualifies for the slim prologue/epilogue 302 * (Tier A: no body locals/spills, no callee-saves, no alloca, no outgoing 303 * stack args, no sret/variadic). When set, the prologue patch and epilogue 304 * emit a 2-insn `stp x29,x30,[sp,#-16]! ; mov x29,sp` and matching `ldp 305 * x29,x30,[sp],#16 ; ret` instead of the fat 4+3-insn FP-frame form. */ 306 u8 slim_prologue; 307 /* Set by aa_func_begin_known_frame for the W1.1 uniform x29-at-bottom layout: 308 * every known frame except slim Tier A and the Windows-variadic top_home 309 * case. x29 is anchored out_stack bytes above sp with the saved pair AT x29; 310 * every slot is a positive `ldr/str [x29,#k]`, slots/callee-saves stack ABOVE 311 * the record, incoming args at x29 + (frame_size - out_stack) = CFA. Covers 312 * out_stack>0, alloca (x29 is stable across the floated sp), and arbitrarily 313 * large frames. Two prologue/epilogue encodings: a folded `stp [sp,#-N]!` 314 * fast path when out_stack==0 && frame_size<=504 && !alloca, and the general 315 * `sub sp ; stp [sp,#os] ; add x29,sp,#os` / `mov x16,x29 ; ldp [x16] ; 316 * add sp,x16,#(N-os)` form otherwise. The frame-size-dependent offsets are 317 * the reason this is only available on the known-frame path (frame final 318 * before the body). Mutually exclusive with slim_prologue (Tier A). */ 319 u8 fp_at_bottom; 320 /* L10: a stricter case of slim Tier A — a true frameless leaf. Set by 321 * aa_func_begin_known_frame when, on top of the slim conditions (no 322 * callee-saves, no alloca, no body slots, no outgoing stack args, no 323 * sret/variadic), the function is also a LEAF (no call of any kind, so x30/LR 324 * is never clobbered), contains no inline asm (which could clobber LR or make 325 * a call opaquely), and never reads its own frame chain 326 * (__builtin_frame_address / __builtin_return_address). Such a function needs 327 * no frame record at all: the prologue/epilogue emit nothing but `ret`, and 328 * the CFA stays at sp with the return address live in LR (the aa64 CIE 329 * default). Implies slim_prologue=0 and fp_at_bottom=0. */ 330 u8 frameless; 331 332 /* Single-pass far-slot fast path (Lever 1 / Fix B). When set, fixed-slot 333 * loads/stores whose top-record fp offset falls outside stur's ±256 range are 334 * emitted as a one-word positive scaled `ldr/str [base,#scaled]` placeholder 335 * plus an AA_PATCH_SLOT, instead of the multi-insn `sub xN,x29,#off ; ldur` 336 * address build. The base is a stable bottom anchor: sp (the common case) or 337 * AA_FRAME_BASE when the function moves sp via alloca. Decided at func_begin 338 * (off when a Windows GP home area shifts the layout); the frame-base 339 * fallback is armed lazily on the first alloca. Only 4/8-byte int/fp slots 340 * take this path — byte/half stay on the existing path (their positive scaled 341 * reach, 4 KB/8 KB, is too small to guarantee a one-word patch). */ 342 u8 slot_sp_base; 343 u8 uses_frame_base; /* an alloca fired: patch AA_PATCH_SLOT against 344 * AA_FRAME_BASE and set it up in the prologue. */ 345 NativeFrameSlot frame_base_slot; /* home for the saved caller AA_FRAME_BASE */ 346 } AANativeTarget; 347 348 static AANativeTarget* aa_of(NativeTarget* t) { return (AANativeTarget*)t; } 349 350 /* Layout-aware FP-relative offsets. Every frame use site goes through these; 351 * the fp_at_bottom test lives here and nowhere else. 352 * 353 * top-record (default): record near the top, fp anchored at the saved pair. 354 * incoming args at fp+16+b, slots below fp at -off. CFA = fp+16. 355 * bottom-record (fp_at_bottom): x29 = sp + out_stack, the saved pair AT x29, 356 * slots above the record at (frame_size - out_stack) - off, incoming args 357 * at x29 + (frame_size - out_stack). CFA = x29 + (frame_size - out_stack). 358 * The "bytes above the anchor" is bsz = frame_size - out_stack = align16(16 359 * + slot_bytes) padding aside, so slots land in [16, bsz) and never overlap 360 * the 16-byte record. When out_stack==0, bsz = frame_size (fp = sp). */ 361 static inline u32 aa_fp_bottom_above(const AANativeTarget* a) { 362 return a->frame_size_final - a->out_stack_final; 363 } 364 static inline i32 aa_fp_off_in_arg(const AANativeTarget* a, u32 byte_off) { 365 /* top-record incoming args sit above the saved pair and the (usually empty) 366 * Windows-variadic GP home area; bottom-record never carries a home area. */ 367 u32 base = a->fp_at_bottom ? aa_fp_bottom_above(a) 368 : AA_FRAME_SAVE_SIZE + a->top_home_bytes; 369 return (i32)(base + byte_off); 370 } 371 static inline i32 aa_fp_off_slot(const AANativeTarget* a, u32 slot_off) { 372 return a->fp_at_bottom ? (i32)aa_fp_bottom_above(a) - (i32)slot_off 373 : -(i32)slot_off; 374 } 375 /* Outgoing stack args on a tail call land in the caller's incoming-arg window — 376 * the same physical address the tail-callee will read via aa_fp_off_in_arg. 377 * Same helper, distinct name for site-side intent. */ 378 static inline i32 aa_fp_off_tail_out_arg(const AANativeTarget* a, 379 u32 byte_off) { 380 return aa_fp_off_in_arg(a, byte_off); 381 } 382 /* CFA = caller's sp, expressed as an fp-relative offset (fp+16 top-record, 383 * x29 + (frame_size - out_stack) bottom-record). Named so the CFI emit site 384 * stays layout-blind. */ 385 static inline i32 aa_cfa_off(const AANativeTarget* a) { 386 return a->fp_at_bottom ? (i32)aa_fp_bottom_above(a) 387 : (i32)(AA_FRAME_SAVE_SIZE + a->top_home_bytes); 388 } 389 390 /* fp-relative offset of GP home slot `i` (Windows variadic only). The home area 391 * sits just above the saved pair and just below the incoming stack args, so 392 * slot gp_reg_count coincides with incoming-arg byte 0 (top-record only — a 393 * function with a home area never takes a slim/bottom layout). */ 394 static inline i32 aa_fp_off_home_slot(u32 i) { 395 return (i32)(AA_FRAME_SAVE_SIZE + i * 8u); 396 } 397 398 static _Noreturn void aa_panic(AANativeTarget* a, const char* msg) { 399 compiler_panic(a->base.c, a->loc, "aarch64 native target: %s", msg); 400 } 401 402 /* Declared locally rather than pulling in debug/debug.h, keeping the 403 * backend's dependency on the Debug producer to this one entry point — 404 * same pattern as the x64/rv64 emit TUs (see arch/mc.h). */ 405 extern void debug_emit_row(Debug*, ObjSecId text_section, u32 offset, SrcLoc); 406 extern void debug_func_pc_range(Debug*, ObjSecId text_section, u32 begin_ofs, 407 u32 end_ofs); 408 409 static void aa_emit32(MCEmitter* mc, u32 word) { 410 /* obj_pos is a section lookup + buf_pos; only the -g line table needs it, so 411 * skip it on the common no-debug compile (one fewer lookup per instruction). 412 * The pre-write offset must be read BEFORE mc_emit32 advances the cursor. */ 413 if (mc->debug) { 414 u32 ofs = obj_pos(mc->obj, mc->section_id); 415 mc_emit32(mc, word); 416 debug_emit_row(mc->debug, mc->section_id, ofs, mc->loc); 417 } else { 418 mc_emit32(mc, word); 419 } 420 } 421 422 static void aa_patch32(ObjBuilder* obj, ObjSecId sec, u32 off, u32 word) { 423 u8 b[4]; 424 wr_u32_le(b, word); 425 obj_patch(obj, sec, off, b, sizeof b); 426 } 427 428 static u32 type_size32(NativeTarget* t, KitCgTypeId type) { 429 u64 n = type ? cg_type_size(t->c, type) : 8u; 430 if (n == 0) n = 8u; 431 if (n > 16u) 432 compiler_panic(t->c, (SrcLoc){0, 0, 0}, 433 "aarch64 native target: scalar too large"); 434 return (u32)n; 435 } 436 437 static u32 type_align32(NativeTarget* t, KitCgTypeId type) { 438 u64 n = type ? cg_type_align(t->c, type) : 8u; 439 if (n == 0) n = 1u; 440 if (n > 16u) n = 16u; 441 return (u32)n; 442 } 443 444 static u32 size_idx(u32 n) { 445 if (n <= 1u) return 0u; 446 if (n <= 2u) return 1u; 447 if (n <= 4u) return 2u; 448 return 3u; 449 } 450 451 static u32 loc_reg(NativeLoc loc) { return loc.v.reg & 0x1fu; } 452 453 /* Scalar byte width of a register operand. Reads the NDT-stamped szinfo 454 * descriptor (one byte) when present, else falls back to the live type query — 455 * so partial adoption stays byte-identical. */ 456 static u32 loc_size32(NativeTarget* t, NativeLoc loc) { 457 if (loc.szinfo & NATIVE_SZINFO_VALID) return native_szinfo_size(loc.szinfo); 458 return type_size32(t, loc.type); 459 } 460 461 /* The original predicate exactly, but sourcing the width from the cached 462 * descriptor when stamped: `size == 8 || is_ptr`. is_ptr is short-circuited 463 * away whenever the cached size is already 8 (the pointer case on this arch), 464 * so the common path becomes a single byte test. Byte-identical to the old 465 * type_size32+cg_type_is_ptr form. */ 466 static int loc_is_64(NativeTarget* t, NativeLoc loc) { 467 return loc_size32(t, loc) == 8u || cg_type_is_ptr(t->c, loc.type); 468 } 469 470 /* native_loc_is_fp is shared in native_target.h. */ 471 472 static int aa_use_got_for_sym(NativeTarget* t, ObjSymId sym) { 473 return obj_symbol_extern_via_got(t->c, t->obj, sym); 474 } 475 476 static RelocKind aa_ldst_reloc_for_size(u32 size) { 477 switch (size) { 478 case 0: 479 return R_AARCH64_LDST8_ABS_LO12_NC; 480 case 1: 481 return R_AARCH64_LDST16_ABS_LO12_NC; 482 case 2: 483 return R_AARCH64_LDST32_ABS_LO12_NC; 484 case 3: 485 return R_AARCH64_LDST64_ABS_LO12_NC; 486 default: 487 return R_AARCH64_LDST64_ABS_LO12_NC; 488 } 489 } 490 491 static u32 aa_load_imm_words(u32* out, u32 cap, u32 sf, u32 rd, i64 imm) { 492 u64 v = (u64)imm; 493 u32 words = sf ? 4u : 2u; 494 u32 n = 0; 495 for (u32 i = 0; i < words; ++i) { 496 u32 part = (u32)((v >> (i * 16u)) & 0xffffu); 497 if (!part && n) continue; 498 if (n >= cap) return 0; 499 out[n] = n ? aa64_movk(sf, rd, part, i) : aa64_movz(sf, rd, part, i); 500 ++n; 501 } 502 if (!n) { 503 if (!cap) return 0; 504 out[n++] = aa64_movz(sf, rd, 0, 0); 505 } 506 return n; 507 } 508 509 static void aa_emit_load_imm(MCEmitter* mc, u32 sf, u32 rd, i64 imm) { 510 u32 words[4]; 511 u32 n = aa_load_imm_words(words, 4u, sf, rd, imm); 512 for (u32 i = 0; i < n; ++i) aa_emit32(mc, words[i]); 513 } 514 515 static void aa_emit_add_imm(AANativeTarget* a, u32 rd, u32 rn, i32 off) { 516 u32 imm12, sh; 517 MCEmitter* mc = a->base.mc; 518 if (off >= 0 && aa64_addsub_imm_fits(off, &imm12, &sh)) { 519 aa_emit32(mc, aa64_add_imm(1, rd, rn, imm12, sh)); 520 return; 521 } 522 if (off < 0 && aa64_addsub_imm_fits(-(i64)off, &imm12, &sh)) { 523 aa_emit32(mc, aa64_sub_imm(1, rd, rn, imm12, sh)); 524 return; 525 } 526 u32 tmp = rd == rn ? (rd == AA_TMP0 ? AA_TMP1 : AA_TMP0) : rd; 527 aa_emit_load_imm(mc, 1, tmp, off); 528 aa_emit32(mc, aa64_add(1, rd, rn, tmp)); 529 } 530 531 static void aa_emit_add_i64(AANativeTarget* a, u32 rd, u32 rn, i64 off) { 532 u32 imm12, sh; 533 MCEmitter* mc = a->base.mc; 534 if (off >= 0 && aa64_addsub_imm_fits(off, &imm12, &sh)) { 535 aa_emit32(mc, aa64_add_imm(1, rd, rn, imm12, sh)); 536 return; 537 } 538 if (off < 0 && aa64_addsub_imm_fits(-off, &imm12, &sh)) { 539 aa_emit32(mc, aa64_sub_imm(1, rd, rn, imm12, sh)); 540 return; 541 } 542 u32 tmp = rd == rn ? (rd == AA_TMP0 ? AA_TMP1 : AA_TMP0) : rd; 543 aa_emit_load_imm(mc, 1, tmp, off); 544 aa_emit32(mc, aa64_add(1, rd, rn, tmp)); 545 } 546 547 /* Unscaled load with an explicit load opcode (AA64_LDST_OPC_LDR for a plain 548 * zero-extending load, AA64_LDST_OPC_LDRS_X for a sign-extending ldursb/ldursh 549 * into the X register). */ 550 static u32 aa_ldur_op_v(u32 size, u32 v, u32 ld_opc, u32 rt, u32 rn, 551 i32 simm9) { 552 return aa64_ldst_simm9_pack((AA64LdStSimm9){.size = size, 553 .V = v, 554 .opc = ld_opc, 555 .imm9 = (u32)simm9 & 0x1ffu, 556 .Rn = rn, 557 .Rt = rt}); 558 } 559 560 static u32 aa_ldur_v(u32 size, u32 v, u32 rt, u32 rn, i32 simm9) { 561 return aa_ldur_op_v(size, v, AA64_LDST_OPC_LDR, rt, rn, simm9); 562 } 563 564 static u32 aa_stur_v(u32 size, u32 v, u32 rt, u32 rn, i32 simm9) { 565 return aa64_ldst_simm9_pack((AA64LdStSimm9){.size = size, 566 .V = v, 567 .opc = AA64_LDST_OPC_STR, 568 .imm9 = (u32)simm9 & 0x1ffu, 569 .Rn = rn, 570 .Rt = rt}); 571 } 572 573 /* Scaled load with an explicit load opcode (see aa_ldur_op_v). The scale shift 574 * is the access size for plain LDR; a sign-extending narrow load (opc=LDRS_X) 575 * uses the same byte-size scale (the encoded imm12 is byte_off >> size). */ 576 static u32 aa_ldr_uimm_op_v(u32 size, u32 v, u32 ld_opc, u32 rt, u32 rn, 577 u32 byte_off) { 578 u32 sc = byte_off >> size; 579 return aa64_ldst_uimm_pack((AA64LdStUimm){ 580 .size = size, .V = v, .opc = ld_opc, .imm12 = sc, .Rn = rn, .Rt = rt}); 581 } 582 583 static u32 aa_ldr_uimm_v(u32 size, u32 v, u32 rt, u32 rn, u32 byte_off) { 584 return aa_ldr_uimm_op_v(size, v, AA64_LDST_OPC_LDR, rt, rn, byte_off); 585 } 586 587 static u32 aa_str_uimm_v(u32 size, u32 v, u32 rt, u32 rn, u32 byte_off) { 588 u32 sc = byte_off >> size; 589 return aa64_ldst_uimm_pack((AA64LdStUimm){.size = size, 590 .V = v, 591 .opc = AA64_LDST_OPC_STR, 592 .imm12 = sc, 593 .Rn = rn, 594 .Rt = rt}); 595 } 596 597 static u32 aa_ldr_uimm(u32 size, u32 rt, u32 rn, u32 byte_off) { 598 return aa_ldr_uimm_v(size, 0, rt, rn, byte_off); 599 } 600 601 static u32 aa_str_uimm(u32 size, u32 rt, u32 rn, u32 byte_off) { 602 return aa_str_uimm_v(size, 0, rt, rn, byte_off); 603 } 604 605 /* PRFM (immediate), zero byte offset. prfop is the architectural Rt field: 606 * type[4:3] | level[2:1] | policy[0]. */ 607 static u32 aa_prfm(u32 prfop, u32 rn) { 608 return 0xF9800000u | ((rn & 31u) << 5) | (prfop & 31u); 609 } 610 611 /* Register-offset load/store with an explicit load opcode (ld_opc is consulted 612 * only when load != 0; AA64_LDST_OPC_LDRS_X gives a sign-extending ldrsb/ldrsh 613 * into the X register) and an explicit index-extend `option` 614 * (AA64_LDST_OPTION_*): LSL/UXTX (full X index, the default), or SXTW/UXTW for 615 * a 32-bit W index widened by the addressing mode (O1-PATTERNS L8). */ 616 static u32 aa_ldst_regoff_opt_v(u32 size, u32 v, u32 load, u32 ld_opc, u32 rt, 617 u32 rn, u32 rm, u32 option, u32 scaled) { 618 return aa64_ldst_regoff_pack((AA64LdStRegOff){ 619 .size = size & 3u, 620 .V = v & 1u, 621 .opc = (load ? ld_opc : AA64_LDST_OPC_STR), 622 .Rm = rm & 0x1fu, 623 .option = option & 7u, 624 .S = scaled & 1u, 625 .Rn = rn & 0x1fu, 626 .Rt = rt & 0x1fu}); 627 } 628 629 static u32 aa_ldst_regoff_op_v(u32 size, u32 v, u32 load, u32 ld_opc, u32 rt, 630 u32 rn, u32 rm, u32 scaled) { 631 return aa_ldst_regoff_opt_v(size, v, load, ld_opc, rt, rn, rm, 632 AA64_LDST_OPTION_LSL, scaled); 633 } 634 635 static u32 aa_ldst_regoff_v(u32 size, u32 v, u32 load, u32 rt, u32 rn, u32 rm, 636 u32 scaled) { 637 return aa_ldst_regoff_op_v(size, v, load, AA64_LDST_OPC_LDR, rt, rn, rm, 638 scaled); 639 } 640 641 static u32 aa_mrs_tpidr_el0(u32 rt) { return 0xd53bd040u | (rt & 0x1fu); } 642 643 /* The scalar-FP / bit-packing encoders below delegate to the single-source 644 * isa.h encoders so encode/decode stay in lockstep. ftype 0=single, 1=double; 645 * the historical aa_* signatures (and call sites) are preserved as thin 646 * wrappers. */ 647 static u32 aa_fp_bin(u32 op, u32 is_double, u32 rd, u32 rn, u32 rm) { 648 return aa64_fp_dp2(is_double, op, rd, rn, rm); 649 } 650 651 static u32 aa_fcmp(u32 is_double, u32 rn, u32 rm) { 652 return aa64_fcmp_reg(is_double, rn, rm); 653 } 654 655 static u32 aa_fneg(u32 is_double, u32 rd, u32 rn) { 656 return aa64_fp_dp1(is_double, AA64_FP_DP1_FNEG, rd, rn); 657 } 658 659 static u32 aa_fmov_fp(u32 is_double, u32 rd, u32 rn) { 660 return aa64_fp_dp1(is_double, AA64_FP_DP1_FMOV, rd, rn); 661 } 662 663 /* MOV Vd.16B, Vn.16B (alias of ORR Vd.16B, Vn.16B, Vn.16B): a full 128-bit 664 * SIMD register copy. Used to move binary128 / long double values, which fmov 665 * (scalar, max 64-bit) would truncate. */ 666 static u32 aa_mov_vec16(u32 rd, u32 rn) { 667 return 0x4ea01c00u | ((rn & 0x1fu) << 16) | ((rn & 0x1fu) << 5) | 668 (rd & 0x1fu); 669 } 670 671 /* The FP<->int conversion family: sf selects the GPR width (1=64-bit), ftype 672 * the FP width (1=double). Their roles flip between the convert-from-int 673 * (s/ucvtf: sf=src GPR, ftype=dst FP) and convert-to-int (fcvtz*: sf=dst GPR, 674 * ftype=src FP) directions, but the encoding is the same shape. */ 675 static u32 aa_scvtf(u32 is_double_dst, u32 is64_src, u32 fd, u32 rn) { 676 return aa64_fp_int_cvt(is64_src, is_double_dst, AA64_FP_ICVT_SCVTF, fd, rn); 677 } 678 679 static u32 aa_ucvtf(u32 is_double_dst, u32 is64_src, u32 fd, u32 rn) { 680 return aa64_fp_int_cvt(is64_src, is_double_dst, AA64_FP_ICVT_UCVTF, fd, rn); 681 } 682 683 static u32 aa_fcvtzs(u32 is64_dst, u32 is_double_src, u32 rd, u32 fn) { 684 return aa64_fp_int_cvt(is64_dst, is_double_src, AA64_FP_ICVT_FCVTZS, rd, fn); 685 } 686 687 static u32 aa_fcvtzu(u32 is64_dst, u32 is_double_src, u32 rd, u32 fn) { 688 return aa64_fp_int_cvt(is64_dst, is_double_src, AA64_FP_ICVT_FCVTZU, rd, fn); 689 } 690 691 static u32 aa_fcvt_d_s(u32 rd, u32 rn) { 692 return aa64_fcvt_prec(/*src=single*/ 0, /*dst=double*/ 1, rd, rn); 693 } 694 695 static u32 aa_fcvt_s_d(u32 rd, u32 rn) { 696 return aa64_fcvt_prec(/*src=double*/ 1, /*dst=single*/ 0, rd, rn); 697 } 698 699 /* fmov between GPR and FP reg: the FP ftype tracks the operand width (64-bit 700 * GPR <-> double, 32-bit GPR <-> single), so sf and ftype move together. */ 701 static u32 aa_fmov_gpr_to_fp(u32 is64, u32 fd, u32 rn) { 702 return aa64_fp_int_cvt(is64, is64, AA64_FP_ICVT_FMOV_TO_FP, fd, rn); 703 } 704 705 static u32 aa_fmov_fp_to_gpr(u32 is64, u32 rd, u32 fn) { 706 return aa64_fp_int_cvt(is64, is64, AA64_FP_ICVT_FMOV_TO_GPR, rd, fn); 707 } 708 709 static u32 aa_clz(u32 sf, u32 rd, u32 rn) { 710 return aa64_dp1(sf, AA64_DP1_CLZ, rd, rn); 711 } 712 713 static u32 aa_rbit(u32 sf, u32 rd, u32 rn) { 714 return aa64_dp1(sf, AA64_DP1_RBIT, rd, rn); 715 } 716 717 /* REV reverses all bytes of the operand, so the 32-bit form is REV(32) and the 718 * 64-bit form is REV(64) — the opcode2 follows sf rather than being constant. 719 */ 720 static u32 aa_rev(u32 sf, u32 rd, u32 rn) { 721 return aa64_dp1(sf, sf ? AA64_DP1_REV64 : AA64_DP1_REV32, rd, rn); 722 } 723 724 static u32 aa_sbfm(u32 sf, u32 rd, u32 rn, u32 immr, u32 imms) { 725 return aa64_bitfield(sf, /*SBFM*/ 0u, immr, imms, rd, rn); 726 } 727 728 static u32 aa_ubfm(u32 sf, u32 rd, u32 rn, u32 immr, u32 imms) { 729 return aa64_bitfield(sf, /*UBFM*/ 2u, immr, imms, rd, rn); 730 } 731 732 static u32 aa_ldaxr(u32 size, u32 rt, u32 rn) { 733 return (size << 30) | 0x085ffc00u | ((rn & 0x1fu) << 5) | (rt & 0x1fu); 734 } 735 736 static u32 aa_ldxr(u32 size, u32 rt, u32 rn) { 737 return (size << 30) | 0x085f7c00u | ((rn & 0x1fu) << 5) | (rt & 0x1fu); 738 } 739 740 static u32 aa_stlxr(u32 size, u32 rs, u32 rt, u32 rn) { 741 return (size << 30) | 0x0800fc00u | ((rs & 0x1fu) << 16) | 742 ((rn & 0x1fu) << 5) | (rt & 0x1fu); 743 } 744 745 static u32 aa_stxr(u32 size, u32 rs, u32 rt, u32 rn) { 746 return (size << 30) | 0x08007c00u | ((rs & 0x1fu) << 16) | 747 ((rn & 0x1fu) << 5) | (rt & 0x1fu); 748 } 749 750 static u32 aa_ldar(u32 size, u32 rt, u32 rn) { 751 return (size << 30) | 0x08dffc00u | ((rn & 0x1fu) << 5) | (rt & 0x1fu); 752 } 753 754 static u32 aa_stlr(u32 size, u32 rt, u32 rn) { 755 return (size << 30) | 0x089ffc00u | ((rn & 0x1fu) << 5) | (rt & 0x1fu); 756 } 757 758 static u32 aa_umaddl(u32 rd, u32 rn, u32 rm, u32 ra) { 759 return 0x9ba00000u | ((rm & 0x1fu) << 16) | ((ra & 0x1fu) << 10) | 760 ((rn & 0x1fu) << 5) | (rd & 0x1fu); 761 } 762 763 static u32 aa_smaddl(u32 rd, u32 rn, u32 rm, u32 ra) { 764 return 0x9b200000u | ((rm & 0x1fu) << 16) | ((ra & 0x1fu) << 10) | 765 ((rn & 0x1fu) << 5) | (rd & 0x1fu); 766 } 767 768 static u32 aa_smulh(u32 rd, u32 rn, u32 rm) { 769 return 0x9b407c00u | ((rm & 0x1fu) << 16) | ((rn & 0x1fu) << 5) | 770 (rd & 0x1fu); 771 } 772 773 static u32 aa_umulh(u32 rd, u32 rn, u32 rm) { 774 return 0x9bc07c00u | ((rm & 0x1fu) << 16) | ((rn & 0x1fu) << 5) | 775 (rd & 0x1fu); 776 } 777 778 static u32 aa_subs_reg(u32 sf, u32 rd, u32 rn, u32 rm) { 779 return aa64_addsubsr_pack( 780 (AA64AddSubSR){.sf = sf, .op = 1, .S = 1, .Rm = rm, .Rn = rn, .Rd = rd}); 781 } 782 783 static void aa_emit_cmp_to_flags(NativeTarget* t, NativeLoc lhs, NativeLoc rhs); 784 785 static u32 aa_add_lsl(u32 rd, u32 rn, u32 rm, u32 shift) { 786 return aa64_addsubsr_pack((AA64AddSubSR){.sf = 1, 787 .op = 0, 788 .S = 0, 789 .shift = 0, 790 .Rm = rm, 791 .imm6 = shift, 792 .Rn = rn, 793 .Rd = rd}); 794 } 795 796 /* L7 shifted-register ALU forms: rd = rn <op> (rm << shift). shift type is 797 * always LSL (the .shift field, 0); the shift amount is imm6. add/sub use the 798 * add/sub shifted-register family, and/orr/eor the logical shifted-register 799 * family. `sf` selects the 32- vs 64-bit operand width. */ 800 static u32 aa_addsub_lsl(u32 sf, u32 op, u32 rd, u32 rn, u32 rm, u32 shift) { 801 return aa64_addsubsr_pack((AA64AddSubSR){.sf = sf, 802 .op = op, 803 .S = 0, 804 .shift = 0, 805 .Rm = rm, 806 .imm6 = shift, 807 .Rn = rn, 808 .Rd = rd}); 809 } 810 811 static u32 aa_logsr_lsl(u32 sf, u32 opc, u32 rd, u32 rn, u32 rm, u32 shift) { 812 return aa64_logsr_pack((AA64LogSR){.sf = sf, 813 .opc = opc, 814 .shift = 0, 815 .N = 0, 816 .Rm = rm, 817 .imm6 = shift, 818 .Rn = rn, 819 .Rd = rd}); 820 } 821 822 static u32 aa_cset(u32 sf, u32 rd, u32 cond) { 823 return aa64_csinc_enc(sf, rd, AA64_ZR, AA64_ZR, cond ^ 1u); 824 } 825 826 static u32 cmp_cond(CmpOp op) { 827 switch (op) { 828 case CMP_EQ: 829 return 0x0u; 830 case CMP_NE: 831 return 0x1u; 832 case CMP_LT_U: 833 return 0x3u; 834 case CMP_LE_U: 835 return 0x9u; 836 case CMP_GT_U: 837 return 0x8u; 838 case CMP_GE_U: 839 return 0x2u; 840 case CMP_LT_S: 841 return 0xbu; 842 case CMP_LE_S: 843 return 0xdu; 844 case CMP_GT_S: 845 return 0xcu; 846 case CMP_GE_S: 847 return 0xau; 848 /* FP predicates after FCMP set NZCV as: a<b -> N; a==b -> Z,C; a>b -> C; 849 * unordered -> C,V. Each maps to a single condition except CMP_ONE_F / 850 * CMP_UEQ_F (synthesized with two instructions in aa_cmp/aa_cmp_branch, 851 * which intercept them before calling cmp_cond). */ 852 case CMP_OEQ_F: 853 return 0x0u; /* EQ */ 854 case CMP_OLT_F: 855 return 0x4u; /* MI */ 856 case CMP_OLE_F: 857 return 0x9u; /* LS */ 858 case CMP_OGT_F: 859 return 0xcu; /* GT */ 860 case CMP_OGE_F: 861 return 0xau; /* GE */ 862 case CMP_UNE_F: 863 return 0x1u; /* NE (unordered or not-equal) */ 864 case CMP_ULT_F: 865 return 0xbu; /* LT (unordered or less-than) */ 866 case CMP_ULE_F: 867 return 0xdu; /* LE (unordered or less-or-equal) */ 868 case CMP_UGT_F: 869 return 0x8u; /* HI (unordered or greater-than) */ 870 case CMP_UGE_F: 871 return 0x2u; /* CS (unordered or greater-or-equal) */ 872 default: 873 return 0x0u; 874 } 875 } 876 877 static AANativeSlot* aa_slot(AANativeTarget* a, NativeFrameSlot slot) { 878 return native_frame_slot_at(&a->frame, slot); 879 } 880 881 /* x16/x17 are the backend-private integer temporaries available to memory 882 * primitives. Address formation must not silently overwrite a store payload 883 * or a register index that the final memory instruction still consumes. Keep 884 * those conflicts explicit at the address-materialization boundary. */ 885 typedef u8 AATmpAvoid; 886 enum { 887 AA_TMP_AVOID_0 = 1u << 0, 888 AA_TMP_AVOID_1 = 1u << 1, 889 }; 890 891 static AATmpAvoid aa_tmp_avoid_reg(Reg reg) { 892 if (reg == AA_TMP0) return AA_TMP_AVOID_0; 893 if (reg == AA_TMP1) return AA_TMP_AVOID_1; 894 return 0u; 895 } 896 897 static Reg aa_addr_tmp(AANativeTarget* a, AATmpAvoid avoid) { 898 if (!(avoid & AA_TMP_AVOID_0)) return AA_TMP0; 899 if (!(avoid & AA_TMP_AVOID_1)) return AA_TMP1; 900 aa_panic(a, "address formation has no scratch register"); 901 return AA_TMP0; 902 } 903 904 static Reg aa_addr_adjust_tmp(AANativeTarget* a, Reg base, 905 AATmpAvoid avoid) { 906 AATmpAvoid base_bit = aa_tmp_avoid_reg(base); 907 if (base_bit && !(avoid & base_bit)) return base; 908 return aa_addr_tmp(a, avoid); 909 } 910 911 static AATmpAvoid aa_mem_addr_avoid(int load, NativeLoc reg, 912 NativeAddr addr) { 913 AATmpAvoid avoid = 0u; 914 /* v16/v17 and x16/x17 are separate register files. Only an integer store 915 * keeps its Rt live across integer address materialization. */ 916 if (!load && !native_loc_is_fp(reg)) 917 avoid |= aa_tmp_avoid_reg(loc_reg(reg)); 918 if (addr.index_kind == NATIVE_ADDR_INDEX_REG) 919 avoid |= aa_tmp_avoid_reg(addr.index.reg); 920 return avoid; 921 } 922 923 static void aa_addr_base(AANativeTarget* a, NativeAddr addr, 924 AATmpAvoid avoid, u32* base_out, i32* off_out) { 925 *base_out = AA_TMP0; 926 *off_out = addr.offset; 927 switch ((NativeAddrBaseKind)addr.base_kind) { 928 case NATIVE_ADDR_BASE_REG: 929 *base_out = addr.base.reg; 930 return; 931 case NATIVE_ADDR_BASE_FRAME: { 932 AANativeSlot* s = aa_slot(a, addr.base.frame); 933 *base_out = AA_FP; 934 *off_out = aa_fp_off_slot(a, s->off) + addr.offset; 935 return; 936 } 937 case NATIVE_ADDR_BASE_FRAME_VALUE: { 938 NativeLoc tmp; 939 NativeAddr base_addr = addr; 940 Reg addr_tmp = aa_addr_tmp(a, avoid); 941 if (!addr.base_type) 942 aa_panic(a, "frame-value base has no exact type"); 943 base_addr.index_kind = NATIVE_ADDR_INDEX_NONE; 944 base_addr.index_ext = NATIVE_ADDR_IDX_EXT_NONE; 945 base_addr.log2_scale = 0; 946 memset(&tmp, 0, sizeof tmp); 947 tmp.kind = NATIVE_LOC_REG; 948 tmp.cls = NATIVE_REG_INT; 949 tmp.type = addr.base_type; 950 tmp.v.reg = addr_tmp; 951 a->base.load_addr(&a->base, tmp, base_addr); 952 *base_out = addr_tmp; 953 *off_out = 0; 954 return; 955 } 956 case NATIVE_ADDR_BASE_GLOBAL: { 957 NativeLoc tmp; 958 NativeAddr base_addr = addr; 959 Reg addr_tmp = aa_addr_tmp(a, avoid); 960 base_addr.index_kind = NATIVE_ADDR_INDEX_NONE; 961 base_addr.index_ext = NATIVE_ADDR_IDX_EXT_NONE; 962 base_addr.log2_scale = 0; 963 memset(&tmp, 0, sizeof tmp); 964 tmp.kind = NATIVE_LOC_REG; 965 tmp.cls = NATIVE_REG_INT; 966 tmp.type = builtin_id(KIT_CG_BUILTIN_I64); 967 tmp.v.reg = addr_tmp; 968 a->base.load_addr(&a->base, tmp, base_addr); 969 *base_out = addr_tmp; 970 *off_out = 0; 971 return; 972 } 973 default: 974 aa_panic(a, "unsupported address base"); 975 } 976 } 977 978 static u32 aa_ldst_q_uimm(int load, u32 rt, u32 rn, u32 byte_off); 979 static u32 aa_ldst_q_simm9(int load, u32 rt, u32 rn, i32 byte_off); 980 static AAPatch* aa_patch_alloc(AANativeTarget* a); /* far-slot deferral */ 981 982 static void aa_emit_mem_q(AANativeTarget* a, int load, NativeLoc reg, 983 NativeAddr addr) { 984 u32 base, rt; 985 i32 off; 986 MCEmitter* mc = a->base.mc; 987 if (addr.index_kind != NATIVE_ADDR_INDEX_NONE) 988 aa_panic(a, "unsupported q-register indexed memory access"); 989 aa_addr_base(a, addr, 0u, &base, &off); 990 rt = loc_reg(reg); 991 if (off >= 0 && (((u32)off & 15u) == 0) && ((u32)off >> 4) <= 0xfffu) { 992 aa_emit32(mc, aa_ldst_q_uimm(load, rt, base, (u32)off)); 993 return; 994 } 995 if (off >= -256 && off <= 255) { 996 aa_emit32(mc, aa_ldst_q_simm9(load, rt, base, off)); 997 return; 998 } 999 aa_emit_add_imm(a, AA_TMP1, base, off); 1000 aa_emit32(mc, aa_ldst_q_uimm(load, rt, AA_TMP1, 0)); 1001 } 1002 1003 static void aa_emit_mem_native(AANativeTarget* a, int load, NativeLoc reg, 1004 NativeAddr addr, MemAccess mem) { 1005 u32 base, rt, sz, bytes, ld_opc; 1006 AATmpAvoid addr_avoid; 1007 i32 off; 1008 MCEmitter* mc = a->base.mc; 1009 rt = loc_reg(reg); 1010 addr_avoid = aa_mem_addr_avoid(load, reg, addr); 1011 bytes = mem.size 1012 ? mem.size 1013 : type_size32(&a->base, reg.type ? reg.type : mem.type); 1014 if (native_loc_is_fp(reg)) { 1015 if (bytes != 4u && bytes != 8u && bytes != 16u) 1016 aa_panic(a, "unsupported native fp memory width"); 1017 } else if (bytes != 1u && bytes != 2u && bytes != 4u && bytes != 8u) { 1018 aa_panic(a, "unsupported native integer memory width"); 1019 } 1020 sz = size_idx(bytes); 1021 if (native_loc_is_fp(reg) && bytes == 16u) { 1022 aa_emit_mem_q(a, load, reg, addr); 1023 return; 1024 } 1025 if (native_loc_is_fp(reg) && sz < 2u) sz = 2u; 1026 /* Lever 4: a signed narrow integer load (MF_SEXT_LOAD, byte/half, integer 1027 * register) becomes a sign-extending load into the X register (ldrsb/ldrsh, 1028 * opc=10) so it fills the whole register in one instruction — the cg layer 1029 * then drops the redundant CV_SEXT. Every other load (incl. fp, word, dword) 1030 * keeps the plain zero-extending LDR. */ 1031 ld_opc = 1032 (load && (mem.flags & MF_SEXT_LOAD) && !native_loc_is_fp(reg) && sz <= 1u) 1033 ? AA64_LDST_OPC_LDRS_X 1034 : AA64_LDST_OPC_LDR; 1035 /* Far fixed-slot fast path (Lever 1/3 / Fix B). A plain frame slot whose 1036 * top-record fp offset is past stur's ±256 range would otherwise cost `sub 1037 * xN,x29,#off (+movk) ; ldur` (2-4 insns). Instead emit a positive scaled 1038 * `ldr/str [sp,#0]` placeholder and defer the offset: once the frame is 1039 * final, aa_apply_patches rewrites it to `[base, #frame_size-...]`, base = sp 1040 * (stable) or AA_FRAME_BASE (alloca). 1041 * 1042 * The base-relative offset is frame_size - 16 - slot_off, unknown here 1043 * (single-pass: the frame is still growing) and able to exceed the scaled 1044 * ldr/str reach (4 KB/8 KB/16 KB/32 KB by size) for ANY access size — a 1045 * top-of-frame 8-byte slot sits ~frame_size off sp, and kit's own 1046 * src/api/package.c reaches a ~69 KB frame. So every size reserves TWO words: 1047 * when the resolved offset fits it is `ldr/str [base,#scaled] ; nop`, and when 1048 * it overflows the resolver emits the address-build fallback `add x17,base,#hi 1049 * ; ldr/str [x17,#lo]` (correct for any frame < 16 MB). 1050 * 1051 * Lever 3/4 interaction: a far SIGNED narrow load (ld_opc==LDRS_X) is 1052 * excluded from this scaled-slot path — the slot patch records only a plain 1053 * LDR opcode, so it would zero-extend while the cg layer dropped the CV_SEXT. 1054 * Such loads fall through to the general path below, which honors ld_opc 1055 * (ldrsb/ldrsh with an address build). Rare (a far signed char/short slot); 1056 * correctness over the one-insn win. */ 1057 int sext_far = load && ld_opc == AA64_LDST_OPC_LDRS_X; 1058 if (a->slot_sp_base && addr.base_kind == NATIVE_ADDR_BASE_FRAME && 1059 addr.index_kind == NATIVE_ADDR_INDEX_NONE && sz <= 3u && !sext_far) { 1060 AANativeSlot* s = aa_slot(a, addr.base.frame); 1061 i32 fp_off = aa_fp_off_slot(a, s->off) + addr.offset; 1062 /* The placeholder resolves to a scaled `ldr/str [base,#imm<<sz]`, whose 1063 * immediate must be a multiple of the access size. The frame base 1064 * (sp / AA_FRAME_BASE) is 16-aligned at this point (slot_sp_base is off 1065 * whenever a Windows GP home area shifts the layout), so the resolved 1066 * offset is a multiple of the access size exactly when fp_off is. An 1067 * aggregate copy chunks an under-aligned record (e.g. a 4-aligned 12-byte 1068 * struct copied as 8+4 bytes) into an access whose frame offset is not a 1069 * multiple of the chunk size; such an access can't use the scaled form, so 1070 * it falls through to the fp-relative address-build path below, which 1071 * encodes any offset. */ 1072 u32 acc = 1u << sz; 1073 if (fp_off < -256 && ((u32)fp_off & (acc - 1u)) == 0u) { 1074 u32 vbit = native_loc_is_fp(reg) ? 1u : 0u; 1075 AAPatch* p = aa_patch_alloc(a); 1076 p->kind = AA_PATCH_SLOT; 1077 p->pos = mc_pos(mc); 1078 p->u.slot.slot_off = s->off; 1079 p->u.slot.extra = addr.offset; 1080 p->u.slot.sz = (u8)sz; 1081 p->u.slot.vbit = (u8)vbit; 1082 p->u.slot.load = (u8)(load ? 1u : 0u); 1083 p->u.slot.rt = (u8)rt; 1084 aa_emit32(mc, load ? aa_ldr_uimm_v(sz, vbit, rt, AA_SP, 0) 1085 : aa_str_uimm_v(sz, vbit, rt, AA_SP, 0)); 1086 /* Reserve a second word for the address-build fallback. The final 1087 * base-relative offset is frame_size - 16 - slot_off, which is unknown 1088 * here (single-pass: the frame keeps growing) and can exceed the scaled 1089 * ldr/str reach for ANY access size in a large frame (a top-of-frame 1090 * 8-byte slot sits ~frame_size off sp; kit's own src/api/package.c has a 1091 * ~69 KB frame). So every size reserves the fallback word; aa_apply_patches 1092 * leaves it a nop when the one-word scaled form fits. */ 1093 aa_emit32(mc, aa64_nop()); 1094 return; 1095 } 1096 } 1097 if (addr.base_kind == NATIVE_ADDR_BASE_GLOBAL && 1098 addr.index_kind == NATIVE_ADDR_INDEX_NONE) { 1099 i64 addend = addr.base.global.addend + (i64)addr.offset; 1100 u32 scratch = aa_addr_tmp(a, addr_avoid); 1101 u32 pos = mc_pos(mc); 1102 if (aa_use_got_for_sym(&a->base, addr.base.global.sym)) { 1103 aa_emit32(mc, aa64_adrp(scratch, 0, 0)); 1104 mc_emit_reloc_at(mc, mc->section_id, pos, R_AARCH64_ADR_GOT_PAGE, 1105 addr.base.global.sym, 0, 0, 0); 1106 pos = mc_pos(mc); 1107 aa_emit32(mc, aa_ldr_uimm(3, scratch, scratch, 0)); 1108 mc_emit_reloc_at(mc, mc->section_id, pos, R_AARCH64_LD64_GOT_LO12_NC, 1109 addr.base.global.sym, 0, 0, 0); 1110 if (addend) aa_emit_add_i64(a, scratch, scratch, addend); 1111 aa_emit32( 1112 mc, 1113 load ? aa_ldur_op_v(sz, native_loc_is_fp(reg), ld_opc, rt, scratch, 0) 1114 : aa_stur_v(sz, native_loc_is_fp(reg), rt, scratch, 0)); 1115 return; 1116 } 1117 aa_emit32(mc, aa64_adrp(scratch, 0, 0)); 1118 mc_emit_reloc_at(mc, mc->section_id, pos, R_AARCH64_ADR_PREL_PG_HI21, 1119 addr.base.global.sym, addend, 0, 0); 1120 pos = mc_pos(mc); 1121 aa_emit32(mc, 1122 load ? aa_ldr_uimm_op_v(sz, native_loc_is_fp(reg), ld_opc, rt, 1123 scratch, 0) 1124 : aa_str_uimm_v(sz, native_loc_is_fp(reg), rt, scratch, 0)); 1125 mc_emit_reloc_at(mc, mc->section_id, pos, aa_ldst_reloc_for_size(sz), 1126 addr.base.global.sym, addend, 0, 0); 1127 return; 1128 } 1129 aa_addr_base(a, addr, addr_avoid, &base, &off); 1130 if (addr.index_kind != NATIVE_ADDR_INDEX_NONE) { 1131 u32 use_base = base; 1132 u32 scaled = 0; 1133 /* L8 index extend: a 32-bit W index widened by the addressing mode. The 1134 * Rm field still names the same register number; only the option bits 1135 * change (010=UXTW, 110=SXTW). NONE keeps the full-width LSL/UXTX form. */ 1136 u32 option = AA64_LDST_OPTION_LSL; 1137 if (addr.index_kind != NATIVE_ADDR_INDEX_REG) 1138 aa_panic(a, "unsupported address index"); 1139 if (addr.index_ext == NATIVE_ADDR_IDX_EXT_SXTW) 1140 option = AA64_LDST_OPTION_SXTW; 1141 else if (addr.index_ext == NATIVE_ADDR_IDX_EXT_UXTW) 1142 option = AA64_LDST_OPTION_UXTW; 1143 if (off) { 1144 use_base = aa_addr_adjust_tmp(a, base, addr_avoid); 1145 aa_emit_add_imm(a, use_base, base, off); 1146 } 1147 if (addr.log2_scale == 0) { 1148 scaled = 0; 1149 } else if (addr.log2_scale == sz) { 1150 scaled = 1; 1151 } else { 1152 aa_panic(a, "unsupported memory address scale"); 1153 } 1154 aa_emit32(mc, aa_ldst_regoff_opt_v(sz, native_loc_is_fp(reg), load, ld_opc, 1155 rt, use_base, addr.index.reg, option, 1156 scaled)); 1157 return; 1158 } 1159 if (off >= 0 && (((u32)off & ((1u << sz) - 1u)) == 0) && 1160 ((u32)off >> sz) <= 0xfffu) { 1161 aa_emit32(mc, load ? aa_ldr_uimm_op_v(sz, native_loc_is_fp(reg), ld_opc, rt, 1162 base, (u32)off) 1163 : aa_str_uimm_v(sz, native_loc_is_fp(reg), rt, base, 1164 (u32)off)); 1165 return; 1166 } 1167 if (off >= -256 && off <= 255) { 1168 aa_emit32(mc, load ? aa_ldur_op_v(sz, native_loc_is_fp(reg), ld_opc, rt, 1169 base, off) 1170 : aa_stur_v(sz, native_loc_is_fp(reg), rt, base, off)); 1171 return; 1172 } 1173 { 1174 u32 scratch = aa_addr_adjust_tmp(a, base, addr_avoid); 1175 aa_emit_add_imm(a, scratch, base, off); 1176 aa_emit32(mc, load ? aa_ldur_op_v(sz, native_loc_is_fp(reg), ld_opc, rt, 1177 scratch, 0) 1178 : aa_stur_v(sz, native_loc_is_fp(reg), rt, scratch, 0)); 1179 } 1180 } 1181 1182 static u32 aa_part_transfer_tmp(AANativeTarget* a, NativeLoc reg, 1183 NativeAddr addr) { 1184 u32 used = 1u << loc_reg(reg); 1185 if (addr.base_kind == NATIVE_ADDR_BASE_REG) used |= 1u << addr.base.reg; 1186 if (addr.index_kind == NATIVE_ADDR_INDEX_REG) used |= 1u << addr.index.reg; 1187 if (!(used & (1u << AA_TMP0))) return AA_TMP0; 1188 if (!(used & (1u << AA_TMP1))) return AA_TMP1; 1189 aa_panic(a, "exact ABI part transfer has no scratch register"); 1190 return AA_TMP0; 1191 } 1192 1193 /* A register carrier is 8 bytes, but the final part of a small aggregate may 1194 * carry any byte count from 1 through 8. Keep the one-instruction native-width 1195 * path; decompose 3/5/6/7-byte memory transfers into exact low-to-high chunks. 1196 * This is deliberately below call marshalling so incoming binds, outgoing 1197 * args, and generic NativeCallPhaseRet write phases all share the same rule. */ 1198 static void aa_emit_mem(AANativeTarget* a, int load, NativeLoc reg, 1199 NativeAddr addr, MemAccess mem) { 1200 u32 bytes = mem.size 1201 ? mem.size 1202 : type_size32(&a->base, reg.type ? reg.type : mem.type); 1203 NativePartChunkIter it; 1204 NativeLoc lane; 1205 u32 chunk_off, chunk_size, regno, tmp; 1206 if (bytes == 1u || bytes == 2u || bytes == 4u || bytes == 8u || 1207 (native_loc_is_fp(reg) && bytes == 16u)) { 1208 aa_emit_mem_native(a, load, reg, addr, mem); 1209 return; 1210 } 1211 if (native_loc_is_fp(reg) || bytes == 0u || bytes > 8u) 1212 aa_panic(a, "unsupported exact ABI part memory width"); 1213 1214 regno = loc_reg(reg); 1215 /* A native-width load may overwrite its address register because it is one 1216 * instruction (`ldr x0, [x0]`). An exact-width load is a sequence, so retain 1217 * the effective address in a backend-private register when dst aliases its 1218 * base or index. The other private register remains available for packing 1219 * later chunks into dst. */ 1220 if (load && 1221 ((addr.base_kind == NATIVE_ADDR_BASE_REG && addr.base.reg == regno) || 1222 (addr.index_kind == NATIVE_ADDR_INDEX_REG && 1223 addr.index.reg == regno))) { 1224 NativeLoc addr_loc; 1225 NativeAddr retained; 1226 u32 addr_reg = regno == AA_TMP0 ? AA_TMP1 : AA_TMP0; 1227 if (regno == AA_TMP0 || regno == AA_TMP1) 1228 aa_panic(a, "exact load destination aliases private address register"); 1229 addr_loc = native_loc_reg(builtin_id(KIT_CG_BUILTIN_I64), 1230 NATIVE_REG_INT, addr_reg); 1231 a->base.load_addr(&a->base, addr_loc, addr); 1232 memset(&retained, 0, sizeof retained); 1233 retained.base_kind = NATIVE_ADDR_BASE_REG; 1234 retained.base.reg = addr_reg; 1235 retained.base_type = addr_loc.type; 1236 addr = retained; 1237 } 1238 tmp = aa_part_transfer_tmp(a, reg, addr); 1239 it = native_part_chunks(bytes, 8u); 1240 while (native_part_chunk_next(&it, &chunk_off, &chunk_size)) { 1241 NativeAddr chunk_addr = addr; 1242 MemAccess chunk_mem = mem; 1243 chunk_addr.offset += (i32)chunk_off; 1244 chunk_mem.size = chunk_size; 1245 if (chunk_mem.align > chunk_size) chunk_mem.align = chunk_size; 1246 lane = reg; 1247 if (chunk_off == 0u) { 1248 lane.v.reg = regno; 1249 } else { 1250 lane.v.reg = tmp; 1251 if (!load) 1252 aa_emit32(a->base.mc, 1253 aa_ubfm(1u, tmp, regno, chunk_off * 8u, 63u)); 1254 } 1255 aa_emit_mem_native(a, load, lane, chunk_addr, chunk_mem); 1256 if (load && chunk_off != 0u) 1257 aa_emit32(a->base.mc, 1258 aa_logsr_lsl(1u, 1u, regno, regno, tmp, chunk_off * 8u)); 1259 } 1260 } 1261 1262 static NativeAllocClass aa_class_for_type(NativeTarget* t, KitCgTypeId type) { 1263 if (type && cg_type_is_float(t->c, type) && cg_type_size(t->c, type) <= 8u) 1264 return NATIVE_REG_FP; 1265 return NATIVE_REG_INT; 1266 } 1267 1268 static int aa_addr_legal(NativeTarget* t, const NativeAddr* addr, 1269 MemAccess mem) { 1270 u32 sz; 1271 (void)t; 1272 if (!addr) return 0; 1273 if (addr->index_kind == NATIVE_ADDR_INDEX_NONE) return 1; 1274 if (addr->index_kind != NATIVE_ADDR_INDEX_REG) return 0; 1275 /* The SXTW/UXTW index-extend (L8) is encoded in the same regoff form as the 1276 * plain LSL/UXTX index, so the scale legality is identical: scale 0 (no 1277 * shift) or scale == access size (S bit). */ 1278 if (addr->log2_scale == 0) return 1; 1279 sz = size_idx(mem.size ? mem.size : 8u); 1280 return addr->log2_scale == sz; 1281 } 1282 1283 /* O1-PATTERNS rider capabilities (see native_target.h). aa64 emits both folded 1284 * forms: the shifted-register ALU op (L7, aa_binop) and the SXTW/UXTW 1285 * index-extend addressing mode (L8, aa_emit_mem). */ 1286 static int aa_can_fold_shift_into_alu(NativeTarget* t) { 1287 (void)t; 1288 return 1; 1289 } 1290 1291 static int aa_can_fold_extend_into_addr(NativeTarget* t) { 1292 (void)t; 1293 return 1; 1294 } 1295 1296 /* True if `mul Rd, Rn, #c` can be replaced by a single non-mul aarch64 1297 * instruction using only Rn as a source (no extra scratch reg). Constants 1298 * that match: 0, 1, -1, +/-2^k, 2^k+1, 1-2^k for k in [1..width-1]. The 1299 * shift exponent must fit imm6 for the operand width (width = 32 if !sf 1300 * else 64). The emit side is aa_emit_mul_const_imm. */ 1301 static int aa64_imul_strength_reducible(u32 sf, i64 imm) { 1302 u32 max_sh = sf ? 63u : 31u; 1303 u64 a; 1304 if (imm == 0 || imm == 1 || imm == -1) return 1; 1305 /* +2^k */ 1306 a = (u64)imm; 1307 if (imm > 0 && (a & (a - 1u)) == 0u) { 1308 u32 k = (u32)__builtin_ctzll(a); 1309 return k <= max_sh; 1310 } 1311 /* -2^k */ 1312 if (imm < 0) { 1313 a = (u64)(-imm); 1314 if (a && (a & (a - 1u)) == 0u) { 1315 u32 k = (u32)__builtin_ctzll(a); 1316 return k >= 1u && k <= max_sh; 1317 } 1318 } 1319 /* 2^k + 1 (k >= 1, so c >= 3) */ 1320 if (imm >= 3) { 1321 u64 m = (u64)(imm - 1); 1322 if ((m & (m - 1u)) == 0u) { 1323 u32 k = (u32)__builtin_ctzll(m); 1324 return k >= 1u && k <= max_sh; 1325 } 1326 } 1327 /* 1 - 2^k (k >= 1, so c <= -1) */ 1328 if (imm <= -1) { 1329 u64 m = (u64)(1 - imm); 1330 if (m && (m & (m - 1u)) == 0u) { 1331 u32 k = (u32)__builtin_ctzll(m); 1332 return k >= 1u && k <= max_sh; 1333 } 1334 } 1335 return 0; 1336 } 1337 1338 /* Which constant operands the backend can fold directly into an instruction 1339 * (so the optimizer can leave them as immediates instead of materializing a 1340 * register). Currently: add/sub/cmp 12-bit immediates (optionally <<12), 1341 * any value for a plain register move (movz/movk synthesizes it), and 1342 * strength-reducible mul constants (handled in aa_binop via shift / shifted 1343 * add or sub). */ 1344 static int aa_imm_legal(NativeTarget* t, NativeImmUse use, u32 op, 1345 KitCgTypeId type, i64 imm) { 1346 u32 imm12, sh; 1347 switch (use) { 1348 case NATIVE_IMM_BINOP: 1349 if ((BinOp)op == BO_IADD || (BinOp)op == BO_ISUB) 1350 return aa64_addsub_imm_fits(imm < 0 ? -imm : imm, &imm12, &sh); 1351 if ((BinOp)op == BO_IMUL) { 1352 u32 sf = type_size32(t, type) == 8u ? 1u : 0u; 1353 return aa64_imul_strength_reducible(sf, imm); 1354 } 1355 /* LSL/LSR/ASR #imm via the UBFM/SBFM aliases: shift count in range. */ 1356 if ((BinOp)op == BO_SHL || (BinOp)op == BO_SHR_S || 1357 (BinOp)op == BO_SHR_U) { 1358 u32 bits = type_size32(t, type) == 8u ? 64u : 32u; 1359 return imm >= 0 && (u64)imm < (u64)bits; 1360 } 1361 /* AND/ORR/EOR #bitmask: encodable as an AArch64 logical immediate. */ 1362 if ((BinOp)op == BO_AND || (BinOp)op == BO_OR || (BinOp)op == BO_XOR) { 1363 u32 sf = type_size32(t, type) == 8u ? 1u : 0u; 1364 u32 N, immr, imms; 1365 return aa64_logimm_encode((u64)imm, sf, &N, &immr, &imms); 1366 } 1367 return 0; 1368 case NATIVE_IMM_CMP: 1369 /* cmp lowers to subs #imm12; cmn (negative) is not wired, so require a 1370 * non-negative immediate. */ 1371 return imm >= 0 && aa64_addsub_imm_fits(imm, &imm12, &sh); 1372 case NATIVE_IMM_ADDR_OFFSET: 1373 return aa64_addsub_imm_fits(imm < 0 ? -imm : imm, &imm12, &sh); 1374 case NATIVE_IMM_MOVE: 1375 return 1; 1376 } 1377 return 0; 1378 } 1379 1380 static int aa_addr_index_is_reg(const NativeAddr* addr, u32 reg) { 1381 return addr->index_kind == NATIVE_ADDR_INDEX_REG && addr->index.reg == reg; 1382 } 1383 1384 static u32 aa_tmp_avoiding(u32 reg) { 1385 return reg == AA_TMP0 ? AA_TMP1 : AA_TMP0; 1386 } 1387 1388 static void aa_load_addr_from_base(AANativeTarget* a, u32 rd, u32 base, i64 off, 1389 const NativeAddr* addr) { 1390 if (addr->index_kind == NATIVE_ADDR_INDEX_NONE) { 1391 if (rd != base || off) aa_emit_add_i64(a, rd, base, off); 1392 return; 1393 } 1394 if (addr->index_kind != NATIVE_ADDR_INDEX_REG) 1395 aa_panic(a, "unsupported address index"); 1396 if (addr->log2_scale > 4u) aa_panic(a, "unsupported address scale"); 1397 1398 /* Read the index before writing rd. O1 can legally select the same physical 1399 * register for an address result and its dead-after-use index; materializing 1400 * the base into rd first would turn `base + index` into `base + base`. */ 1401 u32 idx_reg = addr->index.reg; 1402 if (addr->index_ext != NATIVE_ADDR_IDX_EXT_NONE) { 1403 /* L8 extend rider on an address-materialization (LEA-shaped) use: the 1404 * shifted-register add takes a full-width X index, so first widen the 1405 * 32-bit W index into a scratch (sxtw/uxtw) before the scaled add. The 1406 * regoff memory form (aa_emit_mem) folds the extend directly; this path is 1407 * the fallback when the indexed indirect is materialized as an address. */ 1408 u32 tmp = aa_tmp_avoiding(rd == base ? base : rd); 1409 if (tmp == base) tmp = aa_tmp_avoiding(base); 1410 if (addr->index_ext == NATIVE_ADDR_IDX_EXT_SXTW) 1411 aa_emit32(a->base.mc, aa_sbfm(1, tmp, idx_reg, 0, 31)); 1412 else 1413 aa_emit32(a->base.mc, aa_ubfm(1, tmp, idx_reg, 0, 31)); 1414 idx_reg = tmp; 1415 } 1416 aa_emit32(a->base.mc, aa_add_lsl(rd, base, idx_reg, addr->log2_scale)); 1417 if (off) aa_emit_add_i64(a, rd, rd, off); 1418 } 1419 1420 static void aa_materialize_frame_index(AANativeTarget* a, NativeAddr* addr, 1421 u32 avoid_reg) { 1422 NativeAddr load; 1423 NativeLoc idx; 1424 MemAccess mem; 1425 u32 reg; 1426 if (addr->index_kind != NATIVE_ADDR_INDEX_FRAME_VALUE) return; 1427 if (!addr->index_type) 1428 aa_panic(a, "frame-value index has no exact type"); 1429 reg = avoid_reg == AA_TMP1 ? AA_TMP0 : AA_TMP1; 1430 memset(&load, 0, sizeof load); 1431 load.base_kind = NATIVE_ADDR_BASE_FRAME; 1432 load.base.frame = addr->index.frame; 1433 load.base_type = addr->index_type; 1434 memset(&idx, 0, sizeof idx); 1435 idx.kind = NATIVE_LOC_REG; 1436 idx.cls = NATIVE_REG_INT; 1437 idx.type = load.base_type; 1438 idx.v.reg = reg; 1439 memset(&mem, 0, sizeof mem); 1440 mem.type = load.base_type; 1441 mem.size = type_size32(&a->base, load.base_type); 1442 mem.align = type_align32(&a->base, load.base_type); 1443 aa_emit_mem(a, 1, idx, load, mem); 1444 addr->index_kind = NATIVE_ADDR_INDEX_REG; 1445 addr->index.reg = reg; 1446 } 1447 1448 static NativeLoc native_loc_reg(KitCgTypeId type, NativeAllocClass cls, 1449 Reg reg); 1450 1451 static u32 aa_ldst_q_uimm(int load, u32 rt, u32 rn, u32 byte_off) { 1452 return aa64_ldst_uimm_pack((AA64LdStUimm){.size = 0, 1453 .V = 1, 1454 .opc = load ? 3u : 2u, 1455 .imm12 = byte_off >> 4, 1456 .Rn = rn, 1457 .Rt = rt}); 1458 } 1459 1460 static u32 aa_ldst_q_simm9(int load, u32 rt, u32 rn, i32 byte_off) { 1461 return aa64_ldst_simm9_pack((AA64LdStSimm9){.size = 0, 1462 .V = 1, 1463 .opc = load ? 3u : 2u, 1464 .imm9 = (u32)byte_off & 0x1ffu, 1465 .Rn = rn, 1466 .Rt = rt}); 1467 } 1468 1469 static void aa_emit_q_frame(AANativeTarget* a, int load, u32 qreg, 1470 NativeFrameSlot slot, u32 offset) { 1471 AANativeSlot* s = aa_slot(a, slot); 1472 i32 off = aa_fp_off_slot(a, s->off) + (i32)offset; 1473 MCEmitter* mc = a->base.mc; 1474 if (off >= 0 && ((u32)off & 15u) == 0 && ((u32)off >> 4) <= 0xfffu) { 1475 aa_emit32(mc, aa_ldst_q_uimm(load, qreg, AA_FP, (u32)off)); 1476 return; 1477 } 1478 if (off >= -256 && off <= 255) { 1479 aa_emit32(mc, aa_ldst_q_simm9(load, qreg, AA_FP, off)); 1480 return; 1481 } 1482 aa_emit_add_imm(a, AA_TMP1, AA_FP, off); 1483 aa_emit32(mc, aa_ldst_q_uimm(load, qreg, AA_TMP1, 0)); 1484 } 1485 1486 /* Reserve the variadic register-save-area frame slots (gp then fp). Split from 1487 * the store emission so the known-frame path can fix the full frame — including 1488 * these slots — before the prologue, then emit the stores after it. */ 1489 static void aa_reserve_variadic_reg_saves(AANativeTarget* a) { 1490 NativeFrameSlotDesc sd; 1491 KitCgTypeId i64 = builtin_id(KIT_CG_BUILTIN_I64); 1492 ABIVaListInfo vai = abi_va_list_layout(a->base.c->abi); 1493 if (vai.kind != ABI_VA_LIST_AAPCS64) return; 1494 memset(&sd, 0, sizeof sd); 1495 sd.type = i64; 1496 sd.size = vai.gp_reg_count * vai.gp_slot_size; 1497 sd.align = 8; 1498 sd.kind = NATIVE_FRAME_SLOT_SAVE; 1499 a->va_gr_slot = a->base.frame_slot(&a->base, &sd); 1500 sd.size = vai.fp_reg_count * vai.fp_slot_size; 1501 sd.align = 16; 1502 a->va_vr_slot = a->base.frame_slot(&a->base, &sd); 1503 } 1504 1505 /* Emit the stores into the variadic register-save area. For AAPCS64 these land 1506 * in the reserved gr/vr frame slots (aa_reserve_variadic_reg_saves); for the 1507 * Windows GP home area they land in [fp + AA_FRAME_SAVE_SIZE ..], the 1508 * top-of-frame block contiguous with the incoming stack args. */ 1509 static void aa_emit_variadic_reg_save_stores(AANativeTarget* a) { 1510 NativeAddr addr; 1511 MemAccess mem; 1512 KitCgTypeId i64 = builtin_id(KIT_CG_BUILTIN_I64); 1513 ABIVaListInfo vai = abi_va_list_layout(a->base.c->abi); 1514 if (vai.kind == ABI_VA_LIST_POINTER && a->top_home_bytes) { 1515 /* Windows: home x0..x{gp_reg_count-1} so the plain-pointer va_list walks 1516 * register then stack varargs as one block. The named leading registers are 1517 * homed too (harmless): va_start skips past them. */ 1518 memset(&mem, 0, sizeof mem); 1519 mem.type = i64; 1520 mem.size = 8; 1521 mem.align = 8; 1522 memset(&addr, 0, sizeof addr); 1523 addr.base_kind = NATIVE_ADDR_BASE_REG; 1524 addr.base.reg = AA_FP; 1525 addr.base_type = i64; 1526 for (u32 r = 0; r < vai.gp_reg_count && r < 8u; ++r) { 1527 NativeLoc src = native_loc_reg(i64, NATIVE_REG_INT, r); 1528 addr.offset = aa_fp_off_home_slot(r); 1529 aa_emit_mem(a, 0, src, addr, mem); 1530 } 1531 return; 1532 } 1533 if (vai.kind != ABI_VA_LIST_AAPCS64) return; 1534 memset(&mem, 0, sizeof mem); 1535 mem.type = i64; 1536 mem.size = 8; 1537 mem.align = 8; 1538 memset(&addr, 0, sizeof addr); 1539 addr.base_kind = NATIVE_ADDR_BASE_FRAME; 1540 addr.base.frame = a->va_gr_slot; 1541 addr.base_type = i64; 1542 for (u32 r = 0; r < vai.gp_reg_count && r < 8u; ++r) { 1543 NativeLoc src = native_loc_reg(i64, NATIVE_REG_INT, r); 1544 addr.offset = (i32)(r * vai.gp_slot_size); 1545 aa_emit_mem(a, 0, src, addr, mem); 1546 } 1547 for (u32 r = 0; r < vai.fp_reg_count && r < 8u; ++r) 1548 aa_emit_q_frame(a, 0, r, a->va_vr_slot, r * vai.fp_slot_size); 1549 } 1550 1551 static void aa_emit_entry_saves(AANativeTarget* a); 1552 1553 /* Per-function state reset + function-symbol / cfi / prologue-anchor setup 1554 * shared by both entry points (aa_func_begin for the single-pass path, 1555 * aa_func_begin_known_frame for the optimizer path). Emits no prologue. */ 1556 static void aa_func_begin_common(NativeTarget* t, const CGFuncDesc* fd) { 1557 AANativeTarget* a = aa_of(t); 1558 MCEmitter* mc = t->mc; 1559 a->func = fd; 1560 /* Shared frame bookkeeping: clears the slot table, cum_off, max_outgoing, 1561 * callee-save set, and known_frame/has_alloca/frame_final. cum_off counts 1562 * frame-slot bytes below fp; the saved fp/lr pair (16 bytes at [fp, fp+8]) is 1563 * *not* part of it — aa_build_layout adds it in aa_func_end. */ 1564 native_frame_reset(&a->frame); 1565 a->incoming_stack_size = 0; 1566 a->next_param_int = 0; 1567 a->next_param_fp = 0; 1568 /* 0-based byte cursor for incoming stack args (also reported as the 1569 * caller's incoming_stack_size for tail-call realizability). bind_param 1570 * forms its fp-relative address via aa_fp_off_in_arg(next_param_stack), 1571 * which adds the saved-pair offset. */ 1572 a->next_param_stack = 0; 1573 a->sret_ptr_slot = NATIVE_FRAME_SLOT_NONE; 1574 a->saved_tmp_slot = NATIVE_FRAME_SLOT_NONE; 1575 a->va_gr_slot = NATIVE_FRAME_SLOT_NONE; 1576 a->va_vr_slot = NATIVE_FRAME_SLOT_NONE; 1577 a->npatches = 0; 1578 a->nalloca = 0; 1579 a->slim_prologue = 0; 1580 a->fp_at_bottom = 0; 1581 a->frameless = 0; 1582 a->frame_size_final = 0; 1583 a->out_stack_final = 0; 1584 a->slot_sp_base = 0; 1585 a->uses_frame_base = 0; 1586 a->frame_base_slot = NATIVE_FRAME_SLOT_NONE; 1587 /* Windows variadic functions reserve a GP register home area at the top of 1588 * the frame (just below the incoming stack args). The plain-pointer va_list 1589 * then walks register-passed then stack-passed varargs as one block. Other 1590 * ABIs leave gp_reg_count 0 here: Apple ARM64 routes all varargs to the 1591 * stack, AAPCS64 uses a struct va_list with separate reg-save pointers. */ 1592 { 1593 const ABIFuncInfo* fi = abi_cg_func_info(t->c->abi, fd->fn_type); 1594 ABIVaListInfo vai = abi_va_list_layout(t->c->abi); 1595 a->top_home_bytes = (fi && fi->variadic && vai.kind == ABI_VA_LIST_POINTER) 1596 ? vai.gp_reg_count * vai.gp_slot_size 1597 : 0u; 1598 } 1599 mc_set_section(mc, fd->text_section_id); 1600 mc_emit_align(mc, 4, 0); 1601 a->func_start = mc_pos(mc); 1602 mc_begin_function(mc, fd->sym, fd->text_section_id, a->func_start); 1603 mc_cfi_startproc(mc); 1604 a->prologue_pos = mc_pos(mc); 1605 a->minimal_prologue_words = 0; 1606 a->epilogue_label = mc_label_new(mc); 1607 } 1608 1609 /* Single-pass (NativeDirectTarget) entry point: the frame is not known up 1610 * front, so reserve a worst-case prologue region (patched in aa_func_end once 1611 * max_outgoing / callee-saves are final) and emit the entry saves now. */ 1612 static void aa_func_begin(NativeTarget* t, const CGFuncDesc* fd) { 1613 AANativeTarget* a = aa_of(t); 1614 MCEmitter* mc = t->mc; 1615 u32 region; 1616 aa_func_begin_common(t, fd); 1617 /* tcc-style prologue. Emit the frame-independent entry live: save the fp/lr 1618 * pair via a (16 + top_home)-byte pre-decrement and anchor fp at the pair. 1619 * top_home (the Windows-variadic GP home area, 0 elsewhere) is known here, so 1620 * the pair and fp land at exactly the addresses every fp-relative offset 1621 * already assumes — the frame is byte-identical to the old single big-`sub` 1622 * prologue, only the instruction sequence changes. */ 1623 aa_emit32(mc, aa64_stp64_pre( 1624 AA_FP, AA_LR, AA_SP, 1625 -(i32)((AA_FRAME_SAVE_SIZE + a->top_home_bytes) / 8u))); 1626 aa_emit32(mc, aa64_add_imm(1, AA_FP, AA_SP, 0, 0)); /* mov x29, sp */ 1627 /* Arm the far-slot positive-scaled fast path for the common top-record 1628 * layout. A Windows GP home area (top_home_bytes) places incoming args/home 1629 * above the saved pair and is rare; leave those on the fp-relative path. */ 1630 a->slot_sp_base = (a->top_home_bytes == 0u); 1631 /* Reserve only the deferred `sub sp` (+ Windows probe), patched in 1632 * aa_func_end. The region starts here; record it for the patch and CFI. */ 1633 region = abi_stack_probe_interval(a->base.c->abi) ? AA_NDT_SUB_WORDS 1634 : AA_NDT_SUB_WORDS_NOPROBE; 1635 a->prologue_pos = mc_pos(mc); 1636 a->prologue_region_words = region; 1637 { 1638 u8 nops[AA_NDT_SUB_WORDS * 4u]; 1639 for (u32 i = 0; i < region; ++i) wr_u32_le(nops + i * 4u, aa64_nop()); 1640 if (mc->debug) { 1641 u32 ofs = obj_pos(mc->obj, mc->section_id); 1642 mc_emit_bytes(mc, nops, region * 4u); 1643 for (u32 i = 0; i < region; ++i) 1644 debug_emit_row(mc->debug, mc->section_id, ofs + i * 4u, mc->loc); 1645 } else { 1646 mc_emit_bytes(mc, nops, region * 4u); 1647 } 1648 } 1649 aa_emit_entry_saves(a); 1650 } 1651 1652 /* Reserve the entry-save frame slots: the sret-pointer home (x8) and, for 1653 * variadic functions, the argument register-save area. Reserving is split from 1654 * emitting so the known-frame path can fix the full frame before the prologue; 1655 * the single-pass path runs both back to back via aa_emit_entry_saves. */ 1656 static void aa_reserve_entry_saves(AANativeTarget* a) { 1657 NativeTarget* t = &a->base; 1658 const ABIFuncInfo* abi = abi_cg_func_info(t->c->abi, a->func->fn_type); 1659 if (abi && abi->has_sret) { 1660 NativeFrameSlotDesc sd; 1661 memset(&sd, 0, sizeof sd); 1662 sd.type = builtin_id(KIT_CG_BUILTIN_I64); 1663 sd.size = 8; 1664 sd.align = 8; 1665 sd.kind = NATIVE_FRAME_SLOT_SAVE; 1666 a->sret_ptr_slot = t->frame_slot(t, &sd); 1667 } 1668 if (abi && abi->variadic) aa_reserve_variadic_reg_saves(a); 1669 } 1670 1671 /* Emit the entry-save stores (x8 → sret slot, then the variadic reg-save area). 1672 * Slots must already be reserved (aa_reserve_entry_saves). */ 1673 static void aa_emit_entry_save_stores(AANativeTarget* a) { 1674 NativeTarget* t = &a->base; 1675 const ABIFuncInfo* abi = abi_cg_func_info(t->c->abi, a->func->fn_type); 1676 if (abi && abi->has_sret) { 1677 NativeAddr addr; 1678 NativeLoc src; 1679 MemAccess mem; 1680 KitCgTypeId i64 = builtin_id(KIT_CG_BUILTIN_I64); 1681 memset(&addr, 0, sizeof addr); 1682 addr.base_kind = NATIVE_ADDR_BASE_FRAME; 1683 addr.base.frame = a->sret_ptr_slot; 1684 addr.base_type = i64; 1685 memset(&src, 0, sizeof src); 1686 src.kind = NATIVE_LOC_REG; 1687 src.cls = NATIVE_REG_INT; 1688 src.type = i64; 1689 src.v.reg = 8u; 1690 memset(&mem, 0, sizeof mem); 1691 mem.type = i64; 1692 mem.size = 8; 1693 mem.align = 8; 1694 aa_emit_mem(a, 0, src, addr, mem); 1695 } 1696 if (abi && abi->variadic) aa_emit_variadic_reg_save_stores(a); 1697 } 1698 1699 /* Reserve + emit the entry saves back to back. Single-pass (NativeDirectTarget) 1700 * path, where the prologue region is a reserved worst-case block and slot 1701 * offsets need not be final before it. */ 1702 static void aa_emit_entry_saves(AANativeTarget* a) { 1703 aa_reserve_entry_saves(a); 1704 aa_emit_entry_save_stores(a); 1705 } 1706 1707 static void aa_note_frame_state(NativeTarget* t, 1708 const NativeFramePatchState* state) { 1709 AANativeTarget* a = aa_of(t); 1710 if (state && state->max_outgoing > a->frame.max_outgoing) 1711 a->frame.max_outgoing = state->max_outgoing; 1712 } 1713 1714 /* Reserve a save slot for each callee-saved register the allocator used. Runs 1715 * before frame-slot mapping so these slots get the lowest offsets, keeping the 1716 * prologue stores within stur's signed-9-bit range. The prologue/epilogue 1717 * save/restore is emitted from this list in aa_patch_prologue / aa_func_end. */ 1718 static void aa_reserve_callee_saves(NativeTarget* t, const u32* used, 1719 u32 nclasses) { 1720 AANativeTarget* a = aa_of(t); 1721 /* aa64 homes each callee-save in its own 8-byte frame slot (reserved before 1722 * the body slots so they sit nearest fp, in stur range), so alloc_slots=1. 1723 * Adjacent integer slots are later paired into stp/ldp. */ 1724 NativeFrameSaveSpec spec[NATIVE_REG_VEC + 1]; 1725 memset(spec, 0, sizeof spec); 1726 spec[NATIVE_REG_INT].size = 8; 1727 spec[NATIVE_REG_INT].align = 8; 1728 spec[NATIVE_REG_INT].type = builtin_id(KIT_CG_BUILTIN_I64); 1729 spec[NATIVE_REG_FP].size = 8; 1730 spec[NATIVE_REG_FP].align = 8; 1731 spec[NATIVE_REG_FP].type = builtin_id(KIT_CG_BUILTIN_F64); 1732 native_frame_set_callee_saves(&a->frame, used, nclasses, spec, 1733 NATIVE_REG_VEC + 1, 1); 1734 } 1735 1736 static MemAccess aa_mem_for_type(NativeTarget* t, KitCgTypeId type, u32 size); 1737 static void aa_words_callee_saves(AANativeTarget* a, int save, u32* words, 1738 u32 cap, u32* n); 1739 1740 static void aa_emit_callee_restores(AANativeTarget* a) { 1741 u32 words[AA_PROLOGUE_WORDS]; 1742 u32 n = 0; 1743 aa_words_callee_saves(a, 0, words, AA_PROLOGUE_WORDS, &n); 1744 for (u32 i = 0; i < n; ++i) aa_emit32(a->base.mc, words[i]); 1745 } 1746 1747 static void aa_words_load_imm(AANativeTarget* a, u32* words, u32 cap, u32* n, 1748 u32 rd, i64 imm) { 1749 u32 tmp[4]; 1750 u32 m = aa_load_imm_words(tmp, 4u, 1, rd, imm); 1751 if (!m || *n + m > cap) aa_panic(a, "instruction patch too small"); 1752 for (u32 i = 0; i < m; ++i) words[(*n)++] = tmp[i]; 1753 } 1754 1755 /* Windows large-frame stack probe. kit's prologue reserves the whole frame in 1756 * one `sub sp, sp, #N`, but Windows grows a thread stack one guard page at a 1757 * time: a sub that jumps SP more than a page past the guard page leaves the 1758 * skipped pages uncommitted, and the first store into them faults (and, since 1759 * SP itself is then in uncommitted memory, the fault can't even be delivered). 1760 * Touch every page the frame spans, top-down, so each guard page commits in 1761 * turn before the sub. Inlined (no external __chkstk symbol / no reloc in the 1762 * patched prologue region); mirrors the linker's aa64_coff_chkstk body. Only 1763 * x16/x17 are clobbered — the following sub-sp / saved-pair material re-derives 1764 * both. Emitted only when frame_size > interval (one page). */ 1765 static void aa_words_stack_probe(AANativeTarget* a, u32* words, u32 cap, u32* n, 1766 u32 frame_size, u32 interval) { 1767 u32 imm12, sh; 1768 if (!aa64_addsub_imm_fits(interval, &imm12, &sh)) 1769 aa_panic(a, "stack-probe interval not an addsub immediate"); 1770 /* x16 = frame_size ; x17 = sp */ 1771 aa_words_load_imm(a, words, cap, n, AA_TMP0, frame_size); 1772 if (*n + 5u > cap) aa_panic(a, "instruction patch too small"); 1773 words[(*n)++] = aa64_add_imm(1, AA_TMP1, AA_SP, 0, 0); /* mov x17, sp */ 1774 /* loop: x17 -= page ; x16 -= page (sets flags) ; touch [x17] ; b.gt loop */ 1775 words[(*n)++] = aa64_sub_imm(1, AA_TMP1, AA_TMP1, imm12, sh); 1776 words[(*n)++] = aa64_subs_imm12(1, AA_TMP0, AA_TMP0, imm12, sh); 1777 words[(*n)++] = aa64_ldr64_uimm12(31, AA_TMP1, 0); /* ldr xzr, [x17] */ 1778 /* branch back to the `sub x17` three words above while x16 stays positive */ 1779 words[(*n)++] = 1780 aa64_brcond_pack((AA64BrCond){.imm19 = (u32)(-3), .cond = 0xcu /* GT */}); 1781 } 1782 1783 static void aa_words_sub_sp_frame(AANativeTarget* a, u32* words, u32 cap, 1784 u32* n, u32 frame_size) { 1785 u32 imm12, sh; 1786 if (aa64_addsub_imm_fits(frame_size, &imm12, &sh)) { 1787 if (*n >= cap) aa_panic(a, "instruction patch too small"); 1788 words[(*n)++] = aa64_sub_imm(1, AA_SP, AA_SP, imm12, sh); 1789 return; 1790 } 1791 aa_words_load_imm(a, words, cap, n, AA_TMP0, frame_size); 1792 if (*n + 3u > cap) aa_panic(a, "instruction patch too small"); 1793 words[(*n)++] = aa64_add_imm(1, AA_TMP1, AA_SP, 0, 0); 1794 words[(*n)++] = aa64_sub(1, AA_TMP1, AA_TMP1, AA_TMP0); 1795 words[(*n)++] = aa64_add_imm(1, AA_SP, AA_TMP1, 0, 0); 1796 } 1797 1798 /* Anchor fp at the AAPCS64 saved-pair address (= sp + saved-pair offset). 1799 * The slim_prologue path achieves the same anchor in a single insn via 1800 * `add x29, sp, #0` after the pre-decrement stp moves sp to the saved-pair. */ 1801 static void aa_words_frame_ptr_from_sp(AANativeTarget* a, u32* words, u32 cap, 1802 u32* n, const AAFrameLayout* L) { 1803 u32 imm12, sh; 1804 u32 anchor = aa_sp_off_saved_pair(L); 1805 if (aa64_addsub_imm_fits(anchor, &imm12, &sh)) { 1806 if (*n >= cap) aa_panic(a, "instruction patch too small"); 1807 words[(*n)++] = aa64_add_imm(1, AA_FP, AA_SP, imm12, sh); 1808 return; 1809 } 1810 aa_words_load_imm(a, words, cap, n, AA_TMP0, anchor); 1811 if (*n + 2u > cap) aa_panic(a, "instruction patch too small"); 1812 words[(*n)++] = aa64_add_imm(1, AA_TMP1, AA_SP, 0, 0); 1813 words[(*n)++] = aa64_add(1, AA_FP, AA_TMP1, AA_TMP0); 1814 } 1815 1816 /* x17 = address of the saved-pair slot (= sp + saved-pair offset). Used by 1817 * the fat prologue to materialize the stp destination when the offset 1818 * doesn't fit stp's signed-7-bit-scaled immediate. */ 1819 static void aa_words_saved_pair_addr(AANativeTarget* a, u32* words, u32 cap, 1820 u32* n, const AAFrameLayout* L) { 1821 u32 save_off = aa_sp_off_saved_pair(L); 1822 u32 imm12, sh; 1823 if (aa64_addsub_imm_fits(save_off, &imm12, &sh)) { 1824 if (*n >= cap) aa_panic(a, "instruction patch too small"); 1825 words[(*n)++] = aa64_add_imm(1, AA_TMP1, AA_SP, imm12, sh); 1826 return; 1827 } 1828 aa_words_load_imm(a, words, cap, n, AA_TMP0, save_off); 1829 if (*n + 2u > cap) aa_panic(a, "instruction patch too small"); 1830 words[(*n)++] = aa64_add_imm(1, AA_TMP1, AA_SP, 0, 0); 1831 words[(*n)++] = aa64_add(1, AA_TMP1, AA_TMP1, AA_TMP0); 1832 } 1833 1834 /* Positive frame-base offset of the slot homing the caller's AA_FRAME_BASE 1835 * (alloca functions only). Same positive-scaled form the far-slot patches use, 1836 * so it is one word and fits any frame within dword scaled reach (32 KB). */ 1837 static u32 aa_frame_base_save_off(AANativeTarget* a, const AAFrameLayout* L) { 1838 i32 off = (i32)aa_sp_off_saved_pair(L) + 1839 aa_fp_off_slot(a, aa_slot(a, a->frame_base_slot)->off); 1840 if (off < 0 || ((u32)off >> 3) > 0xfffu) 1841 aa_panic(a, "frame-base save slot out of positive scaled range"); 1842 return (u32)off; 1843 } 1844 1845 static void aa_words_restore_frame(AANativeTarget* a, u32* words, u32 cap, 1846 u32* n, const AAFrameLayout* L) { 1847 /* L10: frameless leaf — nothing to tear down (no record, sp untouched). The 1848 * caller emits the bare `ret`. */ 1849 if (a->frameless) return; 1850 if (!L->frame_size) return; 1851 /* Restore the caller's AA_FRAME_BASE before tearing the frame down. It still 1852 * holds the frame base here (untouched by the body), so the slot is reachable 1853 * as a positive scaled offset off itself; the load then overwrites it with 1854 * the saved caller value. Alloca single-pass functions only. */ 1855 if (a->uses_frame_base) { 1856 u32 off = aa_frame_base_save_off(a, L); 1857 if (*n >= cap) aa_panic(a, "epilogue too small for frame-base restore"); 1858 words[(*n)++] = aa_ldr_uimm_v(3, 0, AA_FRAME_BASE, AA_FRAME_BASE, off); 1859 } 1860 if (a->slim_prologue) { 1861 if (*n + 1u > cap) aa_panic(a, "instruction patch too small"); 1862 /* `ldp x29, x30, [sp], #16` — pop saved pair, restore sp. */ 1863 words[(*n)++] = aa64_ldp64_post(AA_FP, AA_LR, AA_SP, 2); 1864 return; 1865 } 1866 if (a->fp_at_bottom) { 1867 u32 os = L->out_stack; 1868 if (os == 0u && !a->frame.has_alloca && L->frame_size <= 504u) { 1869 /* Folded fast path (matches the os==0 prologue fold): the saved pair sits 1870 * at the very bottom (= sp, since no alloca floated it), so 1871 * `ldp x29,x30,[sp],#N` reloads the pair AND releases the whole frame in 1872 * one insn. Callee-saves were already restored by aa_emit_callee_restores. 1873 * N <= 504 holds the post-index imm. */ 1874 if (*n + 1u > cap) aa_panic(a, "instruction patch too small"); 1875 words[(*n)++] = 1876 aa64_ldp64_post(AA_FP, AA_LR, AA_SP, (i32)(L->frame_size / 8u)); 1877 return; 1878 } 1879 /* General bottom-record teardown, correct with OR without alloca (the 1880 * post-indexed `ldp [sp],#N` can't be used once alloca floated sp): recover 1881 * sp from the stable anchor x29, not from sp. `mov x16,x29 ; ldp x29,x30, 1882 * [x16] ; add sp,x16,#(fs-os)` — x16 holds the anchor across the pair reload 1883 * so the final add restores sp = x29 + (fs-os) = caller's sp = CFA. */ 1884 { 1885 u32 bsz = L->frame_size - os; /* = aa_cfa_off in this layout */ 1886 u32 imm12, sh; 1887 if (*n + 2u > cap) aa_panic(a, "instruction patch too small"); 1888 words[(*n)++] = aa64_add_imm(1, AA_TMP0, AA_FP, 0, 0); /* mov x16, x29 */ 1889 words[(*n)++] = aa64_ldp64_soff(AA_FP, AA_LR, AA_TMP0, 0); 1890 if (aa64_addsub_imm_fits(bsz, &imm12, &sh)) { 1891 if (*n >= cap) aa_panic(a, "instruction patch too small"); 1892 words[(*n)++] = aa64_add_imm(1, AA_SP, AA_TMP0, imm12, sh); 1893 } else { 1894 /* bsz exceeds the addsub-imm window. The 3-register add cannot target 1895 * SP (Rd=31 means XZR there), so sum into x16 (a normal register) then 1896 * copy to sp via the immediate `add sp, x16, #0` form (Rd=31 == SP). */ 1897 aa_words_load_imm(a, words, cap, n, AA_TMP1, bsz); 1898 if (*n + 2u > cap) aa_panic(a, "instruction patch too small"); 1899 words[(*n)++] = aa64_add(1, AA_TMP0, AA_TMP0, AA_TMP1); 1900 words[(*n)++] = aa64_add_imm(1, AA_SP, AA_TMP0, 0, 0); 1901 } 1902 return; 1903 } 1904 } 1905 if (aa_cfa_off(a) == AA_FRAME_SAVE_SIZE) { 1906 /* Common top-record case (CFA == 16: no Windows-variadic GP home area). The 1907 * saved pair sits at [fp] and the caller's sp is fp+16, so `mov sp,x29` 1908 * followed by a post-indexed `ldp x29,x30,[sp],#16` restores the pair and 1909 * sp in two insns with no scratch — vs the fat path's three (`mov x16,fp; 1910 * ldp [x16]; add sp,x16,#16`). Correct under alloca: `mov sp,x29` resets sp 1911 * from the fp anchor before the post-index pops the pair. */ 1912 if (*n + 2u > cap) aa_panic(a, "instruction patch too small"); 1913 words[(*n)++] = aa64_add_imm(1, AA_SP, AA_FP, 0, 0); 1914 words[(*n)++] = aa64_ldp64_post(AA_FP, AA_LR, AA_SP, 2); 1915 return; 1916 } 1917 if (*n + 3u > cap) aa_panic(a, "instruction patch too small"); 1918 /* AAPCS64: fp is the saved-pair address. Reload pair from [fp], then restore 1919 * sp to fp + CFA-offset (= caller's original sp = CFA). The CFA offset is 1920 * AA_FRAME_SAVE_SIZE normally, plus the Windows-variadic GP home area when 1921 * present (the only case that still reaches here). */ 1922 words[(*n)++] = aa64_add_imm(1, AA_TMP0, AA_FP, 0, 0); 1923 words[(*n)++] = aa64_ldp64_soff(AA_FP, AA_LR, AA_TMP0, 0); 1924 words[(*n)++] = aa64_add_imm(1, AA_SP, AA_TMP0, (u32)aa_cfa_off(a), 0); 1925 } 1926 1927 /* Emit callee-save store (save=1) or restore (save=0) words into `words`, 1928 * pairing adjacent integer registers into a single stp/ldp. 1929 * reserve_callee_saves allocates consecutive 8-byte slots in order, so 1930 * callee_saves[i] sits 8 bytes above callee_saves[i+1]; for an int pair the 1931 * lower-addressed reg[i+1] is the stp's Rt and reg[i] is Rt2. FP registers (and 1932 * an unpaired trailing int) use the single-register stur/ldur form. 1933 * 1934 * Layout reach. Top-record callee-saves sit nearest fp at small NEGATIVE offsets 1935 * (reserved first), so they always fit stur/stp directly. Bottom-record (W1.1) 1936 * anchors x29 at the bottom, so callee-saves stack near the TOP at large 1937 * POSITIVE offsets — past stp's +504 reach in any frame above ~512 bytes (and 1938 * past the scaled str reach of 32 KB in a huge frame). When the highest 1939 * callee-save offset exceeds stp's reach we materialize a base register 1940 * `x16 = x29 + adj` once (adj 16-aligned so it stays addsub-encodable) and 1941 * address the whole callee-save block relative to it at small offsets — x16 is 1942 * dead here on both the prologue (after the x29 anchor) and epilogue (callee 1943 * restores precede the teardown that reuses x16) paths. */ 1944 static void aa_words_callee_saves(AANativeTarget* a, int save, u32* words, 1945 u32 cap, u32* n) { 1946 u32 base = AA_FP; 1947 i32 adj = 0; 1948 if (a->fp_at_bottom && a->frame.ncallee_saves) { 1949 /* The first reserved callee-save (smallest slot.off) gets the largest fp 1950 * offset; that bound decides whether a base register is needed. */ 1951 i32 hi = aa_fp_off_slot(a, aa_slot(a, a->frame.callee_saves[0].slot)->off); 1952 if (hi > 504) { 1953 u32 imm12, sh; 1954 adj = (i32)((u32)(hi - 504) & ~15u); /* 16-aligned, leaves hi-adj <= 504 */ 1955 if (!aa64_addsub_imm_fits((u32)adj, &imm12, &sh)) { 1956 /* adj is a multiple of 4096 worth of frame; build it via x16 load_imm. */ 1957 aa_words_load_imm(a, words, cap, n, AA_TMP0, adj); 1958 if (*n + 1u > cap) aa_panic(a, "prologue too large"); 1959 words[(*n)++] = aa64_add(1, AA_TMP0, AA_FP, AA_TMP0); 1960 } else { 1961 if (*n >= cap) aa_panic(a, "prologue too large"); 1962 words[(*n)++] = aa64_add_imm(1, AA_TMP0, AA_FP, imm12, sh); 1963 } 1964 base = AA_TMP0; 1965 } 1966 } 1967 for (u32 i = 0; i < a->frame.ncallee_saves;) { 1968 const AACalleeSave* cs = &a->frame.callee_saves[i]; 1969 i32 off = aa_fp_off_slot(a, aa_slot(a, cs->slot)->off) - adj; 1970 if (i + 1u < a->frame.ncallee_saves && cs->cls == (u8)NATIVE_REG_INT && 1971 a->frame.callee_saves[i + 1u].cls == (u8)NATIVE_REG_INT) { 1972 const AACalleeSave* cs2 = &a->frame.callee_saves[i + 1u]; 1973 i32 off2 = aa_fp_off_slot(a, aa_slot(a, cs2->slot)->off) - adj; 1974 /* cs2 is reserved after cs (larger slot.off), so it is the lower address 1975 * in both layouts (off2 = off - 8): stp's Rt = cs2, Rt2 = cs, base off2. 1976 * stp/ldp's signed-7-bit scaled immediate reaches ±504. */ 1977 if (off2 < -512 || off2 > 504) 1978 aa_panic(a, "callee-save pair offset out of prologue range"); 1979 if (*n >= cap) aa_panic(a, "prologue too large"); 1980 words[(*n)++] = save 1981 ? aa64_stp64_soff(cs2->reg, cs->reg, base, off2 / 8) 1982 : aa64_ldp64_soff(cs2->reg, cs->reg, base, off2 / 8); 1983 i += 2u; 1984 } else { 1985 u32 v = cs->cls == (u8)NATIVE_REG_FP ? 1u : 0u; 1986 if (*n >= cap) aa_panic(a, "prologue too large"); 1987 if (a->fp_at_bottom) { 1988 /* Positive, 8-aligned offset (after the optional base adjustment): the 1989 * unscaled stur (±256) can't reach it, so use the scaled unsigned-imm 1990 * str/ldr (reach 32 KB; adj keeps offsets in range for huge frames). */ 1991 if (off < 0 || (u32)off > 0x7ff8u) 1992 aa_panic(a, "callee-save offset out of prologue range"); 1993 words[(*n)++] = save ? aa_str_uimm_v(3, v, cs->reg, base, (u32)off) 1994 : aa_ldr_uimm_v(3, v, cs->reg, base, (u32)off); 1995 } else { 1996 if (off < -256 || off > 255) 1997 aa_panic(a, "callee-save offset out of prologue range"); 1998 words[(*n)++] = save ? aa_stur_v(3, v, cs->reg, base, off) 1999 : aa_ldur_v(3, v, cs->reg, base, off); 2000 } 2001 i += 1u; 2002 } 2003 } 2004 } 2005 2006 /* Build the prologue instruction words for `L` into `words` (capacity `cap`), 2007 * returning the count. Shared by the NativeDirectTarget patch path (reserves 2008 * a fixed worst-case region, then patches it here) and the optimizer path 2009 * (aa_func_begin_known_frame emits exactly these words up front). 2010 * 2011 * All variants establish a post-prologue state defined by L: saved x29/x30 at 2012 * [fp]/[fp+8], callee-saves at aa_fp_off_slot of each. The top-record variants 2013 * leave fp = sp + aa_sp_off_saved_pair(L) (saved-pair near the top); the 2014 * bottom-record variant leaves fp = sp (saved-pair at the bottom). */ 2015 static u32 aa_build_prologue_words(AANativeTarget* a, const AAFrameLayout* L, 2016 u32* words, u32 cap) { 2017 u32 n = 0; 2018 /* L10: frameless leaf — no record, no sp adjustment. The body uses only 2019 * allocated registers + incoming args; sp is untouched and LR holds the live 2020 * return address (no call clobbered it). */ 2021 if (a->frameless) return 0; 2022 if (!L->frame_size) return 0; 2023 if (a->slim_prologue) { 2024 if (cap < 2u) aa_panic(a, "prologue too large"); 2025 /* `stp x29, x30, [sp, #-16]!; add x29, sp, #0` — the pre-decrement stp 2026 * moves sp down to the saved-pair address, so a no-op add anchors fp 2027 * there directly. AAPCS64 frame record. */ 2028 words[n++] = aa64_stp64_pre(AA_FP, AA_LR, AA_SP, -2); 2029 words[n++] = aa64_add_imm(1, AA_FP, AA_SP, 0, 0); 2030 return n; 2031 } 2032 if (a->fp_at_bottom) { 2033 /* W1.1 uniform x29-at-bottom: anchor x29 = sp + out_stack, saved pair AT 2034 * x29, slots above it. Two encodings: */ 2035 u32 os = L->out_stack; 2036 u32 imm12, sh; 2037 if (os == 0u && L->frame_size <= 504u) { 2038 /* Folded fast path (the original fp_at_bottom): the saved pair is at the 2039 * very bottom, so `stp x29,x30,[sp,#-N]!` decrements sp by the whole frame 2040 * AND saves the pair in one insn; `mov x29,sp` anchors fp there. The 2041 * post-index ldp on exit needs N <= 504. */ 2042 if (n + 2u > cap) aa_panic(a, "prologue too large"); 2043 words[n++] = 2044 aa64_stp64_pre(AA_FP, AA_LR, AA_SP, -(i32)(L->frame_size / 8u)); 2045 words[n++] = aa64_add_imm(1, AA_FP, AA_SP, 0, 0); 2046 aa_words_callee_saves(a, 1, words, cap, &n); 2047 return n; 2048 } 2049 /* General bottom-record. `sub sp,sp,#fs` (robust / Windows-probed); save the 2050 * pair at [sp,#os] and anchor x29 = sp + os; callee-saves at positive x29 2051 * offsets. Outgoing args occupy [sp, sp+os); the body addresses them 2052 * sp-relative, so anchoring x29 above them keeps every slot positive while 2053 * leaving the arg area reachable from the (possibly alloca-floated) sp. */ 2054 { 2055 u32 interval = abi_stack_probe_interval(a->base.c->abi); 2056 if (interval && L->frame_size > interval) 2057 aa_words_stack_probe(a, words, cap, &n, L->frame_size, interval); 2058 } 2059 aa_words_sub_sp_frame(a, words, cap, &n, L->frame_size); 2060 if (os <= 504u) { 2061 /* `stp x29,x30,[sp,#os]` — os fits stp's signed-7-bit scaled imm. */ 2062 if (n >= cap) aa_panic(a, "prologue too large"); 2063 words[n++] = aa64_stp64_soff(AA_FP, AA_LR, AA_SP, (i32)(os / 8u)); 2064 } else { 2065 /* Far outgoing area: build the saved-pair address (sp + os) into x17. The 2066 * 3-register add cannot read SP (Rn=31 means XZR), so copy sp into x17 via 2067 * the immediate `add x17, sp, #0` form first, then add os. */ 2068 if (aa64_addsub_imm_fits(os, &imm12, &sh)) { 2069 if (n >= cap) aa_panic(a, "prologue too large"); 2070 words[n++] = aa64_add_imm(1, AA_TMP1, AA_SP, imm12, sh); 2071 } else { 2072 aa_words_load_imm(a, words, cap, &n, AA_TMP0, os); 2073 if (n + 2u > cap) aa_panic(a, "prologue too large"); 2074 words[n++] = aa64_add_imm(1, AA_TMP1, AA_SP, 0, 0); 2075 words[n++] = aa64_add(1, AA_TMP1, AA_TMP1, AA_TMP0); 2076 } 2077 if (n >= cap) aa_panic(a, "prologue too large"); 2078 words[n++] = aa64_stp64_soff(AA_FP, AA_LR, AA_TMP1, 0); 2079 } 2080 /* x29 = sp + os (the anchor). Same SP-as-Rn restriction: build via immediate 2081 * copy + register add for a far os. */ 2082 if (aa64_addsub_imm_fits(os, &imm12, &sh)) { 2083 if (n >= cap) aa_panic(a, "prologue too large"); 2084 words[n++] = aa64_add_imm(1, AA_FP, AA_SP, imm12, sh); 2085 } else { 2086 aa_words_load_imm(a, words, cap, &n, AA_TMP0, os); 2087 if (n + 2u > cap) aa_panic(a, "prologue too large"); 2088 words[n++] = aa64_add_imm(1, AA_FP, AA_SP, 0, 0); 2089 words[n++] = aa64_add(1, AA_FP, AA_FP, AA_TMP0); 2090 } 2091 aa_words_callee_saves(a, 1, words, cap, &n); 2092 return n; 2093 } 2094 /* On targets that don't auto-grow the stack (Windows), probe each page the 2095 * frame spans before the single large `sub sp` jumps past the guard page. 2096 * slim_prologue/fp_at_bottom returned above — their frames are bounded to 2097 * one page (≤16 / ≤504 bytes), so only this path can exceed `interval`. */ 2098 { 2099 u32 interval = abi_stack_probe_interval(a->base.c->abi); 2100 if (interval && L->frame_size > interval) 2101 aa_words_stack_probe(a, words, cap, &n, L->frame_size, interval); 2102 } 2103 aa_words_sub_sp_frame(a, words, cap, &n, L->frame_size); 2104 /* Fat top-record prologue: build the saved-pair address (sp + saved_pair_off) 2105 * into x17, store the pair there, then anchor fp. Reached only by the 2106 * single-pass (-O0) path and the Windows-variadic top_home case — the known- 2107 * frame -O1 path takes slim_prologue or the fp_at_bottom layout above. */ 2108 aa_words_saved_pair_addr(a, words, cap, &n, L); 2109 if (n >= cap) aa_panic(a, "prologue too large"); 2110 words[n++] = aa64_stp64_soff(AA_FP, AA_LR, AA_TMP1, 0); /* fp,lr @ [x17] */ 2111 aa_words_frame_ptr_from_sp(a, words, cap, &n, L); 2112 /* Save callee-saved registers the allocator used (fp-relative; their slots 2113 * were reserved first by aa_reserve_callee_saves so offsets fit stur). */ 2114 aa_words_callee_saves(a, 1, words, cap, &n); 2115 return n; 2116 } 2117 2118 /* Build the deferred portion of the single-pass (-O0) prologue: the optional 2119 * Windows page probe and the `sub sp` that grows the frame below the fp/lr pair 2120 * the live entry (aa_func_begin) already saved and anchored fp at. Only 2121 * `frame_size - 16 - top_home` (= aa_sp_off_saved_pair) remains to subtract. */ 2122 static u32 aa_build_ndt_sub_words(AANativeTarget* a, const AAFrameLayout* L, 2123 u32* words, u32 cap) { 2124 u32 n = 0; 2125 u32 sub_bytes = aa_sp_off_saved_pair(L); 2126 if (!sub_bytes) return 0; 2127 { 2128 u32 interval = abi_stack_probe_interval(a->base.c->abi); 2129 if (interval && sub_bytes > interval) 2130 aa_words_stack_probe(a, words, cap, &n, sub_bytes, interval); 2131 } 2132 aa_words_sub_sp_frame(a, words, cap, &n, sub_bytes); 2133 /* Set up the frame-base anchor for alloca functions: sp now points at the 2134 * frame base, so save the caller's AA_FRAME_BASE into its slot (positive 2135 * scaled, valid before the move) and anchor AA_FRAME_BASE = sp. The body 2136 * never touches AA_FRAME_BASE (callee-saved; the single-pass path allocates 2137 * caller-saved only), so it stays the frame base across any alloca. */ 2138 if (a->uses_frame_base) { 2139 u32 off = aa_frame_base_save_off(a, L); 2140 if (n + 2u > cap) aa_panic(a, "prologue too small for frame-base setup"); 2141 words[n++] = aa_str_uimm_v(3, 0, AA_FRAME_BASE, AA_SP, off); 2142 words[n++] = aa64_add_imm(1, AA_FRAME_BASE, AA_SP, 0, 0); 2143 } 2144 return n; 2145 } 2146 2147 /* Patch the reserved deferred-`sub` region (prologue_region_words at 2148 * prologue_pos) once the frame is final. Single-pass (NativeDirectTarget) path 2149 * only — the optimizer path emits its prologue final and never patches. The 2150 * fixed entry (pair save + fp anchor) was already emitted live, so this writes 2151 * just the probe + `sub sp` and branches over any unused tail. */ 2152 static void aa_patch_prologue(AANativeTarget* a, const AAFrameLayout* L) { 2153 u32 words[AA_NDT_SUB_WORDS]; 2154 u32 region = a->prologue_region_words; 2155 u32 n; 2156 ObjSecId sec = a->func->text_section_id; 2157 if (region > AA_NDT_SUB_WORDS) aa_panic(a, "prologue region too large"); 2158 memset(words, 0, sizeof words); 2159 n = aa_build_ndt_sub_words(a, L, words, region); 2160 /* If the deferred prologue is shorter than the reserved region, branch 2161 * straight to the entry saves rather than executing the trailing NOPs. */ 2162 if (n < region) { 2163 words[n] = aa64_b(region - n); 2164 for (u32 i = n + 1u; i < region; ++i) words[i] = 0xd503201fu; 2165 } 2166 { 2167 u8 pbytes[AA_NDT_SUB_WORDS * 4u]; 2168 for (u32 i = 0; i < region; ++i) wr_u32_le(pbytes + i * 4u, words[i]); 2169 obj_patch(a->base.obj, sec, a->prologue_pos, pbytes, (size_t)region * 4u); 2170 } 2171 } 2172 2173 static void aa_emit_restore_frame(AANativeTarget* a, const AAFrameLayout* L) { 2174 MCEmitter* mc = a->base.mc; 2175 u32 words[AA_PROLOGUE_WORDS]; 2176 u32 n = 0; 2177 if (!L->frame_size) return; 2178 aa_words_restore_frame(a, words, AA_PROLOGUE_WORDS, &n, L); 2179 for (u32 i = 0; i < n; ++i) aa_emit32(mc, words[i]); 2180 } 2181 2182 /* Reserve one entry in the deferred-patch list, growing (arena-doubling) as 2183 * needed. The returned pointer is stable until the next aa_patch_alloc. */ 2184 static AAPatch* aa_patch_alloc(AANativeTarget* a) { 2185 if (a->npatches == a->patches_cap) { 2186 u32 cap = a->patches_cap ? a->patches_cap * 2u : 8u; 2187 AAPatch* nb = arena_zarray(a->base.c->tu, AAPatch, cap); 2188 if (a->patches) memcpy(nb, a->patches, sizeof(*nb) * a->npatches); 2189 a->patches = nb; 2190 a->patches_cap = cap; 2191 } 2192 return &a->patches[a->npatches++]; 2193 } 2194 2195 /* Append FP-relative loads that restore the saved callee registers (stp/ldp 2196 * paired, same as the prologue saves). Shared by the tail-call patch; the 2197 * function epilogue uses aa_emit_callee_restores. */ 2198 static void aa_words_callee_restores(AANativeTarget* a, u32* words, u32 cap, 2199 u32* n) { 2200 aa_words_callee_saves(a, 0, words, cap, n); 2201 } 2202 2203 /* Drain the deferred-patch list. Each entry targets a disjoint, fixed code 2204 * position, so insertion order does not affect output. */ 2205 static void aa_apply_patches(AANativeTarget* a, const AAFrameLayout* L) { 2206 ObjSecId sec = a->func->text_section_id; 2207 for (u32 i = 0; i < a->npatches; ++i) { 2208 AAPatch* p = &a->patches[i]; 2209 if (p->kind == AA_PATCH_ALLOCA) { 2210 u32 imm12, sh; 2211 if (!aa64_addsub_imm_fits(a->frame.max_outgoing, &imm12, &sh)) 2212 aa_panic(a, "outgoing area too large for alloca result"); 2213 aa_patch32(a->base.obj, sec, p->pos, 2214 aa64_add_imm(1, p->u.dst_reg, AA_SP, imm12, sh)); 2215 } else if (p->kind == AA_PATCH_SLOT) { 2216 /* Resolve a far fixed-slot access to a positive scaled load/store off the 2217 * stable bottom anchor. The slot sits at fp_off below fp (top-record); 2218 * adding the bytes the prologue subtracted to reach the frame base 2219 * (aa_sp_off_saved_pair) gives its offset from sp / AA_FRAME_BASE. */ 2220 const AASlotPatch* sl = &p->u.slot; 2221 i32 fp_off = aa_fp_off_slot(a, sl->slot_off) + sl->extra; 2222 i32 base_off = (i32)aa_sp_off_saved_pair(L) + fp_off; 2223 u32 base = a->uses_frame_base ? AA_FRAME_BASE : AA_SP; 2224 u32 ubo = (u32)base_off; 2225 /* Natural alignment is guaranteed by the slot allocator (and the byte 2226 * case is unconditionally aligned); a misalignment would mean a layout 2227 * bug. */ 2228 if (base_off < 0 || (ubo & ((1u << sl->sz) - 1u)) != 0u) 2229 aa_panic(a, "far slot offset negative or misaligned"); 2230 if ((ubo >> sl->sz) <= 0xfffu) { 2231 /* Scaled immediate fits: one-word ldr/str; the reserved second 2232 * placeholder word stays the nop emitted at body time. */ 2233 aa_patch32(a->base.obj, sec, p->pos, 2234 sl->load 2235 ? aa_ldr_uimm_v(sl->sz, sl->vbit, sl->rt, base, ubo) 2236 : aa_str_uimm_v(sl->sz, sl->vbit, sl->rt, base, ubo)); 2237 } else { 2238 /* The scaled reach (4 KB/8 KB/16 KB/32 KB by size) is below the largest 2239 * frames, so build the address into the reserved second word instead: 2240 * `add x17,base,#(off & ~0xfff) ; ldr/str rt,[x17,#(off & 0xfff)]`. The 2241 * high part is a multiple of 4096 (fits the shift-12 addsub imm for any 2242 * frame < 16 MB); the low part is <4096 and, being naturally aligned to 2243 * the access size (base_off is, and hi is 4096-aligned), scale-divides 2244 * cleanly. */ 2245 u32 hi = ubo & ~0xfffu; 2246 u32 lo = ubo & 0xfffu; 2247 if ((hi >> 12) > 0xfffu) 2248 aa_panic(a, "far slot offset out of address-build range"); 2249 aa_patch32(a->base.obj, sec, p->pos, 2250 aa64_add_imm(1, AA_TMP1, base, hi >> 12, 1)); 2251 aa_patch32(a->base.obj, sec, p->pos + 4u, 2252 sl->load 2253 ? aa_ldr_uimm_v(sl->sz, sl->vbit, sl->rt, AA_TMP1, lo) 2254 : aa_str_uimm_v(sl->sz, sl->vbit, sl->rt, AA_TMP1, lo)); 2255 } 2256 } else { /* AA_PATCH_TAIL */ 2257 NativeLoc callee = p->u.callee; 2258 u32 words[AA_TAIL_WORDS]; 2259 u32 n = 0; 2260 memset(words, 0, sizeof words); 2261 aa_words_callee_restores(a, words, AA_TAIL_WORDS, &n); 2262 aa_words_restore_frame(a, words, AA_TAIL_WORDS, &n, L); 2263 if (n >= AA_TAIL_WORDS) aa_panic(a, "tail patch too small"); 2264 if (callee.kind == NATIVE_LOC_REG) { 2265 words[n++] = aa64_br(loc_reg(callee)); 2266 } else if (callee.kind == NATIVE_LOC_GLOBAL) { 2267 while (n + 1u < AA_TAIL_WORDS) words[n++] = 0xd503201fu; 2268 words[n++] = aa64_b(0); 2269 } else { 2270 aa_panic(a, "unsupported tail target"); 2271 } 2272 while (n < AA_TAIL_WORDS) words[n++] = 0xd503201fu; 2273 for (u32 w = 0; w < AA_TAIL_WORDS; ++w) 2274 aa_patch32(a->base.obj, sec, p->pos + w * 4u, words[w]); 2275 } 2276 } 2277 } 2278 2279 static void aa_func_end(NativeTarget* t) { 2280 AANativeTarget* a = aa_of(t); 2281 MCEmitter* mc = t->mc; 2282 AAFrameLayout L = aa_build_layout(a->frame.cum_off, a->frame.max_outgoing, 2283 a->top_home_bytes); 2284 /* known_frame (optimizer): prologue, allocas, and tail epilogues were emitted 2285 * final and slim eligibility was settled in aa_func_begin_known_frame — there 2286 * is nothing to patch. Single-pass (NDT): the frame-independent entry was 2287 * emitted live and only the deferred `sub` region + patches remain; resolve 2288 * them now that the frame is final. The advance past the prologue (for CFI) 2289 * is the live fixed entry plus the reserved deferred region. */ 2290 u32 prologue_advance_words = 2291 a->frame.known_frame 2292 ? a->minimal_prologue_words 2293 : (AA_NDT_FIXED_ENTRY_WORDS + a->prologue_region_words); 2294 mc_label_place(mc, a->epilogue_label); 2295 aa_emit_callee_restores(a); 2296 aa_emit_restore_frame(a, &L); 2297 aa_emit32(mc, aa64_ret(AA_LR)); 2298 if (a->frame.known_frame) { 2299 /* The frame-planning pre-pass plus final prologue/alloca/tail emission must 2300 * leave nothing deferred; a stray patch would mean a body-time frame change 2301 * the final prologue never saw. */ 2302 if (a->npatches != 0) aa_panic(a, "known-frame path left deferred patches"); 2303 } else { 2304 aa_patch_prologue(a, &L); 2305 aa_apply_patches(a, &L); 2306 } 2307 if (a->frameless) { 2308 /* L10: frameless leaf — CFA = sp (unchanged from entry) and the return 2309 * address stays live in LR (the aa64 CIE default), so no saved-register 2310 * rules. The state holds from the first instruction (offset 0). */ 2311 mc_cfi_set_next_pc_offset(mc, 0); 2312 mc_cfi_def_cfa(mc, AA_SP, 0); 2313 } else { 2314 i32 cfa = aa_cfa_off(a); 2315 mc_cfi_set_next_pc_offset(mc, prologue_advance_words * 4u); 2316 /* CFA = caller's sp, an fp-relative offset that depends on the layout: 2317 * fp+16 (top-record) or fp+frame_size (bottom-record). saved fp/lr live at 2318 * [fp]/[fp+8] in both, hence at CFA-cfa / CFA-cfa+8. */ 2319 mc_cfi_def_cfa(mc, AA_FP, cfa); 2320 mc_cfi_offset(mc, AA_FP, aa_fp_off_saved_fp() - cfa); 2321 mc_cfi_offset(mc, AA_LR, aa_fp_off_saved_lr() - cfa); 2322 } 2323 obj_symbol_define(t->obj, a->func->sym, a->func->text_section_id, 2324 a->func_start, mc_pos(mc) - a->func_start); 2325 if (a->func->atomize) { 2326 obj_atom_define(t->obj, a->func->text_section_id, a->func_start, 2327 mc_pos(mc) - a->func_start, a->func->sym, 0); 2328 } 2329 /* Hand the function's PC range to the Debug producer so its line program 2330 * (and DW_AT_low_pc/high_pc) cover this function — emit_section_line skips 2331 * functions without a recorded range. */ 2332 if (mc->debug) 2333 debug_func_pc_range(mc->debug, a->func->text_section_id, a->func_start, 2334 mc_pos(mc)); 2335 mc_cfi_endproc(mc); 2336 mc_end_function(mc); 2337 a->func = NULL; 2338 } 2339 2340 static NativeFrameSlot aa_frame_slot(NativeTarget* t, 2341 const NativeFrameSlotDesc* d) { 2342 return native_frame_slot_alloc(&aa_of(t)->frame, d); 2343 } 2344 2345 static void aa_release_frame_slot(NativeTarget* t, NativeFrameSlot slot) { 2346 native_frame_release_slot(&aa_of(t)->frame, slot); 2347 } 2348 2349 static int aa_frame_slot_debug_loc(NativeTarget* t, NativeFrameSlot slot, 2350 CGDebugLoc* out) { 2351 AANativeTarget* a = aa_of(t); 2352 AANativeSlot* s; 2353 i32 fp_off; 2354 if (!out) return 0; 2355 memset(out, 0, sizeof *out); 2356 if (slot == NATIVE_FRAME_SLOT_NONE || slot > a->frame.nslots) return 0; 2357 s = aa_slot(a, slot); 2358 fp_off = aa_fp_off_slot(a, s->off); 2359 out->kind = CG_DEBUG_LOC_FRAME; 2360 /* The hosted dbg stop snapshot currently carries x29/fp as the frame base 2361 * for variable materialization, so report the same FP-relative slot offset 2362 * used by native memory operands. */ 2363 out->v.frame_ofs = fp_off; 2364 return 1; 2365 } 2366 2367 static void aa_asm_clobber_masks(Compiler* c, SrcLoc loc, const Sym* clobbers, 2368 u32 nclob, u32* int_mask, u32* fp_mask); 2369 /* abi_clobber_masks is shared as native_asm_abi_clobber_masks 2370 * (cg/native_asm.h); it reads the masks from t->regs->classes. */ 2371 2372 /* Build the callee-saved set the prologue must preserve: the allocator-assigned 2373 * callee-saved registers (frame->callee_saved_used) plus any an inline-asm 2374 * block clobbers. The latter are opaque to the optimizer's operand scan, so it 2375 * forwards the raw clobber names (frame->asm_clobbers) and the arch-neutral 2376 * clobber-ABI sets (frame->asm_clobber_abi_sets); we resolve both into masks 2377 * and keep only the callee-saved ones, per AAPCS64: x19..x28 and the low 64 2378 * bits of v8..v15 (x29/x30 are the frame pointer and link register, handled by 2379 * the prologue head, not as ordinary callee-saves). This is the same register 2380 * selection the per-block spill used, hoisted into the prologue. */ 2381 static int aa_asm_reg_is_callee_saved(NativeTarget* t, NativeAllocClass cls, 2382 Reg r) { 2383 (void)t; 2384 return (cls == NATIVE_REG_INT && r >= 19u && r <= 28u) || 2385 (cls == NATIVE_REG_FP && r >= 8u && r <= 15u); 2386 } 2387 2388 static u32 aa_known_callee_saves(NativeTarget* t, 2389 const NativeKnownFrameDesc* frame, u32* out, 2390 u32 cap) { 2391 AANativeTarget* a = aa_of(t); 2392 SrcLoc loc = a->func ? a->func->loc : (SrcLoc){0, 0, 0}; 2393 return native_asm_known_callee_saves( 2394 t, loc, frame, out, cap, aa_asm_clobber_masks, 2395 aa_asm_reg_is_callee_saved); 2396 } 2397 2398 static u32 aa_signature_stack_bytes(NativeTarget* t, KitCgTypeId fn_type, 2399 int* variadic, u32* nparams); 2400 2401 /* Optimizer entry point: the full frame is supplied up front, so the prologue, 2402 * entry saves, slim-form eligibility, allocas, and tail epilogues are all final 2403 * the moment they are emitted — no back-patching (aa_func_end skips the patch 2404 * passes when a->frame.known_frame). Slot creation order matches the 2405 * single-pass path (callee-saves first for stur range, then the static slots, 2406 * then sret/variadic entry saves) so offsets are identical to what the patch 2407 * path would produce. */ 2408 static void aa_func_begin_known_frame(NativeTarget* t, const CGFuncDesc* fd, 2409 const NativeKnownFrameDesc* frame, 2410 NativeFrameSlot* out_slots) { 2411 AANativeTarget* a = aa_of(t); 2412 AAFrameLayout L; 2413 u32 words[AA_PROLOGUE_WORDS]; 2414 u32 n; 2415 aa_func_begin_common(t, fd); 2416 a->frame.known_frame = 1; 2417 if (frame) { 2418 u32 cs[NATIVE_REG_CLASS_COUNT]; 2419 u32 ncs = aa_known_callee_saves(t, frame, cs, NATIVE_REG_CLASS_COUNT); 2420 a->frame.has_alloca = frame->has_alloca; 2421 if (ncs) aa_reserve_callee_saves(t, cs, ncs); 2422 for (u32 i = 0; i < frame->nslots; ++i) { 2423 NativeFrameSlot slot = aa_frame_slot(t, &frame->slots[i]); 2424 if (out_slots) out_slots[i] = slot; 2425 } 2426 aa_reserve_entry_saves(a); 2427 /* Reserve the atomic-RMW scratch spill last (matching its lazy position in 2428 * the single-pass path), so aa_saved_tmp_spill reuses it instead of growing 2429 * the frame mid-body. */ 2430 if (frame->needs_scratch_spill) { 2431 NativeFrameSlotDesc sd; 2432 memset(&sd, 0, sizeof sd); 2433 sd.type = builtin_id(KIT_CG_BUILTIN_I64); 2434 sd.size = 8; 2435 sd.align = 8; 2436 sd.kind = NATIVE_FRAME_SLOT_SPILL; 2437 a->saved_tmp_slot = a->base.frame_slot(&a->base, &sd); 2438 } 2439 if (frame->max_outgoing > a->frame.max_outgoing) 2440 a->frame.max_outgoing = frame->max_outgoing; 2441 } 2442 /* Frame is final: slot_bytes (cum_off) and out_stack (max_outgoing) are both 2443 * known, so the prologue immediates and slim-form choice are settled here. 2444 * frame_size_final + out_stack_final must be set before aa_build_prologue_words 2445 * / entry saves, since the bottom-record offset helpers read both. */ 2446 L = aa_build_layout(a->frame.cum_off, a->frame.max_outgoing, 2447 a->top_home_bytes); 2448 a->frame_size_final = L.frame_size; 2449 a->out_stack_final = L.out_stack; 2450 /* Layout choice (W1.1). Slim Tier A: no callee-saves, no alloca, no body 2451 * slots, no outgoing stack args — the whole frame is the 16-byte record, with 2452 * its own folded `stp [sp,#-16]!` encoding. Otherwise the uniform x29-at-bottom 2453 * (fp_at_bottom) layout: x29 anchored out_stack bytes above sp, every slot a 2454 * positive [x29,#k], for ALL remaining known frames (out_stack>0, alloca, and 2455 * arbitrarily large frames included). The only exception is a Windows-variadic 2456 * GP home area, which must sit ABOVE the saved pair (the plain-pointer va_list 2457 * walks register then stack varargs as one contiguous block) — bottom-record 2458 * has no room for it, so that case keeps the fat top-record layout. 2459 * (See aa_func_end for the single-pass path, which never takes any slim/bottom 2460 * form.) */ 2461 a->slim_prologue = a->frame.ncallee_saves == 0 && !a->frame.has_alloca && 2462 L.slot_bytes == 0 && L.out_stack == 0 && 2463 !a->top_home_bytes; 2464 /* L10: a slim Tier A function that is additionally a leaf (no call clobbers 2465 * x30/LR), has no inline asm (which could clobber LR / call opaquely), and 2466 * never reads its own frame chain (__builtin_frame_address / 2467 * __builtin_return_address) needs no frame record at all. Emit no prologue / 2468 * epilogue / saved-pair CFI; the return address rides LR and the CFA stays at 2469 * sp. A frameless function has frame_size 0 in this layout (slim's whole frame 2470 * is the 16-byte record we are eliding), so it takes neither slim nor 2471 * fp_at_bottom encoding. Additionally exclude any function with incoming 2472 * stack arguments: those are addressed fp-relative (aa_fp_off_in_arg, off 2473 * x29), so a function that reads them needs the x29 anchor a frameless 2474 * prologue never sets up. (Stack args do not show up in L.slot_bytes / 2475 * L.out_stack — they sit above the saved pair — so this guard is separate.) */ 2476 a->frameless = a->slim_prologue && frame && frame->is_leaf && 2477 !frame->has_asm && !frame->reads_frame && 2478 aa_signature_stack_bytes(t, fd->fn_type, NULL, NULL) == 0u; 2479 if (a->frameless) a->slim_prologue = 0; 2480 a->fp_at_bottom = !a->slim_prologue && !a->frameless && !a->top_home_bytes; 2481 n = aa_build_prologue_words(a, &L, words, AA_PROLOGUE_WORDS); 2482 for (u32 i = 0; i < n; ++i) aa_emit32(t->mc, words[i]); 2483 a->minimal_prologue_words = n; 2484 a->frame.frame_final = 1; 2485 aa_emit_entry_save_stores(a); 2486 } 2487 2488 static void aa_spill(NativeTarget* t, NativeLoc src, NativeFrameSlot slot, 2489 MemAccess mem) { 2490 NativeAddr addr; 2491 memset(&addr, 0, sizeof addr); 2492 addr.base_kind = NATIVE_ADDR_BASE_FRAME; 2493 addr.base.frame = slot; 2494 addr.base_type = src.type; 2495 aa_emit_mem(aa_of(t), 0, src, addr, mem); 2496 } 2497 2498 static void aa_reload(NativeTarget* t, NativeLoc dst, NativeFrameSlot slot, 2499 MemAccess mem) { 2500 NativeAddr addr; 2501 memset(&addr, 0, sizeof addr); 2502 addr.base_kind = NATIVE_ADDR_BASE_FRAME; 2503 addr.base.frame = slot; 2504 addr.base_type = dst.type; 2505 aa_emit_mem(aa_of(t), 1, dst, addr, mem); 2506 } 2507 2508 static MCLabel aa_label_new(NativeTarget* t) { return mc_label_new(t->mc); } 2509 2510 static void aa_label_place(NativeTarget* t, MCLabel label) { 2511 mc_label_place(t->mc, label); 2512 } 2513 2514 static void aa_jump(NativeTarget* t, MCLabel label) { 2515 aa_emit32(t->mc, aa64_b(0)); 2516 mc_emit_label_ref(t->mc, label, R_AARCH64_JUMP26, 4, 0); 2517 } 2518 2519 static void aa_cmp_branch(NativeTarget* t, CmpOp op, NativeLoc lhs, 2520 NativeLoc rhs, MCLabel label) { 2521 /* Fuse `<reg> == 0` / `!= 0` tests into a single cbz/cbnz, dropping the 2522 * separate `cmp <reg>,#0`. Only the GPR integer-equality case qualifies: the 2523 * rhs must be an immediate zero (the cg layer passes IMM 0 for null/zero 2524 * tests) and lhs a register (always true on the -O0 path). FP compares route 2525 * through FCMP and keep cmp+b.cond. */ 2526 if ((op == CMP_EQ || op == CMP_NE) && !native_loc_is_fp(lhs) && 2527 lhs.kind == NATIVE_LOC_REG && rhs.kind == NATIVE_LOC_IMM && 2528 rhs.v.imm == 0) { 2529 u32 sf = loc_is_64(t, lhs) ? 1u : 0u; 2530 aa_emit32(t->mc, op == CMP_EQ ? aa64_cbz(sf, loc_reg(lhs), 0) 2531 : aa64_cbnz_imm(sf, loc_reg(lhs), 0)); 2532 mc_emit_label_ref(t->mc, label, R_AARCH64_CONDBR19, 4, 0); 2533 return; 2534 } 2535 aa_emit_cmp_to_flags(t, lhs, rhs); 2536 /* CMP_ONE_F / CMP_UEQ_F have no single FP condition: take the branch from a 2537 * pair of conditional branches to the same label (no scratch register). */ 2538 if (op == CMP_ONE_F) { 2539 /* ordered & !=: branch if a<b (MI) or a>b (GT). */ 2540 aa_emit32(t->mc, aa64_brcond_pack((AA64BrCond){.cond = 0x4u})); /* MI */ 2541 mc_emit_label_ref(t->mc, label, R_AARCH64_CONDBR19, 4, 0); 2542 aa_emit32(t->mc, aa64_brcond_pack((AA64BrCond){.cond = 0xcu})); /* GT */ 2543 mc_emit_label_ref(t->mc, label, R_AARCH64_CONDBR19, 4, 0); 2544 return; 2545 } 2546 if (op == CMP_UEQ_F) { 2547 /* unordered | ==: branch if a==b (EQ) or unordered (VS). */ 2548 aa_emit32(t->mc, aa64_brcond_pack((AA64BrCond){.cond = 0x0u})); /* EQ */ 2549 mc_emit_label_ref(t->mc, label, R_AARCH64_CONDBR19, 4, 0); 2550 aa_emit32(t->mc, aa64_brcond_pack((AA64BrCond){.cond = 0x6u})); /* VS */ 2551 mc_emit_label_ref(t->mc, label, R_AARCH64_CONDBR19, 4, 0); 2552 return; 2553 } 2554 aa_emit32(t->mc, aa64_brcond_pack((AA64BrCond){.cond = cmp_cond(op)})); 2555 mc_emit_label_ref(t->mc, label, R_AARCH64_CONDBR19, 4, 0); 2556 } 2557 2558 static void aa_indirect_branch(NativeTarget* t, NativeLoc addr, 2559 const MCLabel* valid_targets, u32 ntargets) { 2560 (void)valid_targets; 2561 (void)ntargets; 2562 aa_emit32(t->mc, aa64_br(loc_reg(addr))); 2563 } 2564 2565 static void aa_load_label_addr(NativeTarget* t, NativeLoc dst, MCLabel target) { 2566 /* `&&label` address-take: adrp/add with the ADR_PREL_PG_HI21 + 2567 * ADD_ABS_LO12_NC relocation pair against the label's per-block local symbol 2568 * — the same form used to address a global — so the reference is genuinely 2569 * relocatable (reaches ±4 GiB) and any assembler resolves it from the symbol. 2570 * Replaces the old 16-byte INTRA-label sequence with a baked offset. */ 2571 MCEmitter* mc = t->mc; 2572 u32 rd = loc_reg(dst); 2573 ObjSymId sym = mc_label_symbol(mc, target); 2574 u32 pos = mc_pos(mc); 2575 aa_emit32(mc, aa64_adrp(rd, 0, 0)); 2576 mc_emit_reloc_at(mc, mc->section_id, pos, R_AARCH64_ADR_PREL_PG_HI21, sym, 0, 2577 0, 0); 2578 pos = mc_pos(mc); 2579 aa_emit32(mc, aa64_add_imm(1, rd, rd, 0, 0)); 2580 mc_emit_reloc_at(mc, mc->section_id, pos, R_AARCH64_ADD_ABS_LO12_NC, sym, 0, 2581 0, 0); 2582 } 2583 2584 static void aa_move(NativeTarget* t, NativeLoc dst, NativeLoc src) { 2585 /* Identity move elision: same-class same-reg is a no-op on aarch64 2586 * regardless of width (mov xN,xN and mov wN,wN both leave the low bits 2587 * untouched). Catches no-op IR_CONVERT (BITCAST, ZEXT/SEXT with 2588 * src_bits>=dst_bits, FEXT/FTRUNC across-class) when the allocator put 2589 * dst and src in the same hard reg — common post #2.5 return-reg 2590 * coalescing, e.g. `convert opnds=[v0,v0]` after a pointer-returning call 2591 * was emitting `mov x0,x0`. Cross-class (fp<->gpr) bitcasts are not 2592 * elided here even when the reg numbers match — the register files are 2593 * disjoint. */ 2594 if (dst.kind == NATIVE_LOC_REG && src.kind == NATIVE_LOC_REG && 2595 native_loc_is_fp(dst) == native_loc_is_fp(src) && dst.v.reg == src.v.reg) 2596 return; 2597 if (native_loc_is_fp(dst) && native_loc_is_fp(src)) { 2598 if (loc_size32(t, dst) == 16u) 2599 aa_emit32(t->mc, aa_mov_vec16(loc_reg(dst), loc_reg(src))); 2600 else 2601 aa_emit32(t->mc, aa_fmov_fp(loc_size32(t, dst) == 8u, loc_reg(dst), 2602 loc_reg(src))); 2603 } else if (native_loc_is_fp(dst)) { 2604 aa_emit32(t->mc, 2605 aa_fmov_gpr_to_fp(loc_is_64(t, src), loc_reg(dst), loc_reg(src))); 2606 } else if (native_loc_is_fp(src)) { 2607 aa_emit32(t->mc, 2608 aa_fmov_fp_to_gpr(loc_is_64(t, dst), loc_reg(dst), loc_reg(src))); 2609 } else { 2610 aa_emit32(t->mc, 2611 aa64_mov_reg(loc_is_64(t, dst), loc_reg(dst), loc_reg(src))); 2612 } 2613 } 2614 2615 static NativeLoc aa_tmp_loc(KitCgTypeId type, Reg reg); 2616 2617 static void aa_load_imm_native(NativeTarget* t, NativeLoc dst, i64 imm) { 2618 aa_emit_load_imm(t->mc, loc_is_64(t, dst), loc_reg(dst), imm); 2619 } 2620 2621 static void aa_load_const(NativeTarget* t, NativeLoc dst, ConstBytes cbytes) { 2622 u64 v = 0; 2623 if (cbytes.size > 8u) 2624 compiler_panic(t->c, ((AANativeTarget*)t)->loc, 2625 "aarch64 native target: byte constant too large"); 2626 for (u32 i = 0; i < cbytes.size; ++i) v |= (u64)cbytes.bytes[i] << (i * 8u); 2627 if (native_loc_is_fp(dst)) { 2628 NativeLoc tmp = aa_tmp_loc(cbytes.type, AA_TMP0); 2629 aa_emit_load_imm(t->mc, cbytes.size == 8u, AA_TMP0, (i64)v); 2630 aa_move(t, dst, tmp); 2631 } else { 2632 aa_emit_load_imm(t->mc, loc_is_64(t, dst), loc_reg(dst), (i64)v); 2633 } 2634 } 2635 2636 static void aa_load_addr(NativeTarget* t, NativeLoc dst, NativeAddr addr) { 2637 AANativeTarget* a = aa_of(t); 2638 u32 rd = loc_reg(dst); 2639 aa_materialize_frame_index(a, &addr, rd); 2640 switch ((NativeAddrBaseKind)addr.base_kind) { 2641 case NATIVE_ADDR_BASE_FRAME: { 2642 AANativeSlot* s = aa_slot(a, addr.base.frame); 2643 aa_load_addr_from_base(a, rd, AA_FP, 2644 aa_fp_off_slot(a, s->off) + addr.offset, &addr); 2645 return; 2646 } 2647 case NATIVE_ADDR_BASE_FRAME_VALUE: { 2648 NativeAddr load; 2649 MemAccess mem; 2650 u32 base_reg = aa_addr_index_is_reg(&addr, rd) ? aa_tmp_avoiding(rd) : rd; 2651 memset(&load, 0, sizeof load); 2652 load.base_kind = NATIVE_ADDR_BASE_FRAME; 2653 load.base.frame = addr.base.frame; 2654 load.base_type = 2655 addr.base_type ? addr.base_type : builtin_id(KIT_CG_BUILTIN_I64); 2656 memset(&mem, 0, sizeof mem); 2657 mem.type = load.base_type; 2658 mem.size = type_size32(t, load.base_type); 2659 mem.align = type_align32(t, load.base_type); 2660 aa_emit_mem(a, 1, native_loc_reg(dst.type, NATIVE_REG_INT, base_reg), 2661 load, mem); 2662 aa_load_addr_from_base(a, rd, base_reg, addr.offset, &addr); 2663 return; 2664 } 2665 case NATIVE_ADDR_BASE_REG: 2666 aa_load_addr_from_base(a, rd, addr.base.reg, addr.offset, &addr); 2667 return; 2668 case NATIVE_ADDR_BASE_GLOBAL: { 2669 i64 addend = addr.base.global.addend + (i64)addr.offset; 2670 u32 base_reg = aa_addr_index_is_reg(&addr, rd) ? aa_tmp_avoiding(rd) : rd; 2671 u32 pos = mc_pos(t->mc); 2672 if (aa_use_got_for_sym(t, addr.base.global.sym)) { 2673 aa_emit32(t->mc, aa64_adrp(base_reg, 0, 0)); 2674 mc_emit_reloc_at(t->mc, t->mc->section_id, pos, R_AARCH64_ADR_GOT_PAGE, 2675 addr.base.global.sym, 0, 0, 0); 2676 pos = mc_pos(t->mc); 2677 aa_emit32(t->mc, aa_ldr_uimm(3, base_reg, base_reg, 0)); 2678 mc_emit_reloc_at(t->mc, t->mc->section_id, pos, 2679 R_AARCH64_LD64_GOT_LO12_NC, addr.base.global.sym, 0, 0, 2680 0); 2681 if (addend) aa_emit_add_i64(a, base_reg, base_reg, addend); 2682 aa_load_addr_from_base(a, rd, base_reg, 0, &addr); 2683 return; 2684 } 2685 aa_emit32(t->mc, aa64_adrp(base_reg, 0, 0)); 2686 mc_emit_reloc_at(t->mc, t->mc->section_id, pos, 2687 R_AARCH64_ADR_PREL_PG_HI21, addr.base.global.sym, addend, 2688 0, 0); 2689 pos = mc_pos(t->mc); 2690 aa_emit32(t->mc, aa64_add_imm(1, base_reg, base_reg, 0, 0)); 2691 mc_emit_reloc_at(t->mc, t->mc->section_id, pos, R_AARCH64_ADD_ABS_LO12_NC, 2692 addr.base.global.sym, addend, 0, 0); 2693 aa_load_addr_from_base(a, rd, base_reg, 0, &addr); 2694 return; 2695 } 2696 default: 2697 aa_panic(a, "unsupported load_addr"); 2698 } 2699 } 2700 2701 static void aa_load_native(NativeTarget* t, NativeLoc dst, NativeAddr addr, 2702 MemAccess mem) { 2703 aa_emit_mem(aa_of(t), 1, dst, addr, mem); 2704 } 2705 2706 static void aa_store_native(NativeTarget* t, NativeAddr addr, NativeLoc src, 2707 MemAccess mem) { 2708 aa_emit_mem(aa_of(t), 0, src, addr, mem); 2709 } 2710 2711 /* Windows/AArch64 TLS Local-Exec (PE-COFF). Mirrors x64_tls_addr_of_win64: 2712 * ldr rd, [x18, #0x58] ; TEB.ThreadLocalStoragePointer 2713 * adrp x16, _tls_index ; PAGEBASE_REL21 2714 * add x16, x16, :lo12:_tls_index ; PAGEOFFSET_12A 2715 * ldr w16, [x16] ; module's TLS index 2716 * ldr rd, [rd, x16, lsl #3] ; this module's TLS block base 2717 * add rd, rd, #:secrel_hi12:sym ; SECREL_HIGH12A (sh=1) 2718 * add rd, rd, #:secrel_lo12:sym ; SECREL_LOW12A (sh=0) 2719 * We materialize &_tls_index via ADRP+ADD (not LDR :lo12:) on purpose: the 2720 * COFF reader collapses LDST32→LDST64 width, so an LDR :lo12: form would be 2721 * mis-scaled at link time; ADD_ABS_LO12_NC carries no width and round-trips 2722 * cleanly. AA_TMP0 (x16) is the reserved scratch; rd is an allocated reg 2723 * distinct from x16/x17/x18. */ 2724 static void aa_tls_addr_of_win(NativeTarget* t, NativeLoc dst, ObjSymId sym, 2725 i64 addend) { 2726 MCEmitter* mc = t->mc; 2727 u32 sec = mc->section_id; 2728 u32 rd = loc_reg(dst); 2729 u32 pos; 2730 Sym idx_name = pool_intern_slice(t->c->global, SLICE_LIT("_tls_index")); 2731 ObjSymId idx_sym = obj_symbol_find(t->obj, idx_name); 2732 if (idx_sym == 0) 2733 idx_sym = 2734 obj_symbol(t->obj, idx_name, SB_GLOBAL, SK_UNDEF, OBJ_SEC_NONE, 0, 0); 2735 /* (1) rd = TEB.ThreadLocalStoragePointer. */ 2736 aa_emit32(mc, aa_ldr_uimm(3, rd, AA_WIN_TEB_REG, AA_WIN_TEB_TLS_PTR_OFF)); 2737 /* (2)+(3) x16 = &_tls_index via ADRP + ADD. */ 2738 pos = mc_pos(mc); 2739 aa_emit32(mc, aa64_adrp(AA_TMP0, 0, 0)); 2740 mc_emit_reloc_at(mc, sec, pos, R_AARCH64_ADR_PREL_PG_HI21, idx_sym, 0, 0, 0); 2741 pos = mc_pos(mc); 2742 aa_emit32(mc, aa64_add_imm(1, AA_TMP0, AA_TMP0, 0, 0)); 2743 mc_emit_reloc_at(mc, sec, pos, R_AARCH64_ADD_ABS_LO12_NC, idx_sym, 0, 0, 0); 2744 /* (4) w16 = _tls_index (the loaded value). */ 2745 aa_emit32(mc, aa_ldr_uimm(2, AA_TMP0, AA_TMP0, 0)); 2746 /* (5) rd = TLS array slot for this module: ldr rd, [rd, x16, lsl #3]. */ 2747 aa_emit32(mc, aa_ldst_regoff_v(3, 0, 1, rd, rd, AA_TMP0, 1)); 2748 /* (6) rd += :secrel_hi12:sym (ADD with sh=1; linker patches imm12). */ 2749 pos = mc_pos(mc); 2750 aa_emit32(mc, aa64_add_imm(1, rd, rd, 0, 1)); 2751 mc_emit_reloc_at(mc, sec, pos, R_COFF_AARCH64_SECREL_HIGH12A, sym, addend, 1, 2752 0); 2753 /* (7) rd += :secrel_lo12:sym (ADD with sh=0). */ 2754 pos = mc_pos(mc); 2755 aa_emit32(mc, aa64_add_imm(1, rd, rd, 0, 0)); 2756 mc_emit_reloc_at(mc, sec, pos, R_COFF_AARCH64_SECREL_LOW12A, sym, addend, 1, 2757 0); 2758 } 2759 2760 static void aa_tls_addr_of(NativeTarget* t, NativeLoc dst, ObjSymId sym, 2761 i64 addend) { 2762 AANativeTarget* a = aa_of(t); 2763 MCEmitter* mc = t->mc; 2764 u32 rd = loc_reg(dst); 2765 u32 pos; 2766 if (obj_format_tls_via_descriptor(t->c)) { 2767 aa_emit32(mc, aa64_adrp(0, 0, 0)); 2768 pos = mc_pos(mc) - 4u; 2769 mc_emit_reloc_at(mc, mc->section_id, pos, R_AARCH64_TLVP_LOAD_PAGE21, sym, 2770 0, 0, 0); 2771 aa_emit32(mc, aa_ldr_uimm(3, 0, 0, 0)); 2772 pos = mc_pos(mc) - 4u; 2773 mc_emit_reloc_at(mc, mc->section_id, pos, R_AARCH64_TLVP_LOAD_PAGEOFF12, 2774 sym, 0, 0, 0); 2775 aa_emit32(mc, aa_ldr_uimm(3, AA_TMP0, 0, 0)); 2776 aa_emit32(mc, aa64_blr(AA_TMP0)); 2777 if (addend) aa_emit_add_i64(a, 0, 0, addend); 2778 if (rd != 0) aa_emit32(mc, aa64_mov_reg(1, rd, 0)); 2779 return; 2780 } 2781 if (obj_format_tls_model(t->c) == OBJ_TLS_WINDOWS_TEB) { 2782 aa_tls_addr_of_win(t, dst, sym, addend); 2783 return; 2784 } 2785 if (t->c->target.obj != KIT_OBJ_ELF) { 2786 aa_panic(a, "unsupported TLS object format"); 2787 } 2788 aa_emit32(mc, aa_mrs_tpidr_el0(rd)); 2789 pos = mc_pos(mc); 2790 aa_emit32(mc, aa64_add_imm(1, rd, rd, 0, 1)); 2791 mc_emit_reloc_at(mc, mc->section_id, pos, R_AARCH64_TLSLE_ADD_TPREL_HI12, sym, 2792 addend, 0, 0); 2793 pos = mc_pos(mc); 2794 aa_emit32(mc, aa64_add_imm(1, rd, rd, 0, 0)); 2795 mc_emit_reloc_at(mc, mc->section_id, pos, R_AARCH64_TLSLE_ADD_TPREL_LO12_NC, 2796 sym, addend, 0, 0); 2797 } 2798 2799 static NativeLoc aa_tmp_loc(KitCgTypeId type, Reg reg) { 2800 NativeLoc loc; 2801 memset(&loc, 0, sizeof loc); 2802 loc.kind = NATIVE_LOC_REG; 2803 loc.cls = NATIVE_REG_INT; 2804 loc.type = type; 2805 loc.v.reg = reg; 2806 return loc; 2807 } 2808 2809 static NativeAddr aa_addr_plus(NativeAddr addr, u32 off) { 2810 addr.offset += (i32)off; 2811 return addr; 2812 } 2813 2814 static void aa_copy_bytes_dir(NativeTarget* t, NativeAddr dst, NativeAddr src, 2815 AggregateAccess access, int backward) { 2816 KitCgTypeId i64 = builtin_id(KIT_CG_BUILTIN_I64); 2817 KitCgTypeId i32 = builtin_id(KIT_CG_BUILTIN_I32); 2818 KitCgTypeId i16 = builtin_id(KIT_CG_BUILTIN_I16); 2819 KitCgTypeId i8 = builtin_id(KIT_CG_BUILTIN_I8); 2820 NativeLoc tmp = aa_tmp_loc(i64, AA_TMP0); 2821 u32 off = 0; 2822 while (off < access.size) { 2823 u32 rem = access.size - off; 2824 u32 pos; 2825 MemAccess mem = access.mem; 2826 if (rem >= 8u) { 2827 mem.type = i64; 2828 mem.size = 8u; 2829 } else if (rem >= 4u) { 2830 mem.type = i32; 2831 mem.size = 4u; 2832 tmp.type = i32; 2833 } else if (rem >= 2u) { 2834 mem.type = i16; 2835 mem.size = 2u; 2836 tmp.type = i16; 2837 } else { 2838 mem.type = i8; 2839 mem.size = 1u; 2840 tmp.type = i8; 2841 } 2842 mem.align = mem.size; 2843 pos = backward ? access.size - off - mem.size : off; 2844 aa_load_native(t, tmp, aa_addr_plus(src, pos), mem); 2845 aa_store_native(t, aa_addr_plus(dst, pos), tmp, mem); 2846 off += mem.size; 2847 tmp.type = i64; 2848 } 2849 } 2850 2851 static void aa_copy_bytes(NativeTarget* t, NativeAddr dst, NativeAddr src, 2852 AggregateAccess access) { 2853 aa_copy_bytes_dir(t, dst, src, access, 0); 2854 } 2855 2856 /* L3: widen the AGG_SET (whole-struct/array zero-init, memset) expander. The 2857 * old form emitted one `strb` per byte even for an aligned whole-struct zero 2858 * (cjson had a 64-long strb run). Instead, splat the fill byte across a 64-bit 2859 * register once and store the widest aligned chunk covering the remaining run 2860 * (`str x` -> `str w` -> `strh` -> `strb` tail), exactly mirroring the 2861 * aa_copy_bytes_dir width ladder. This is a pure per-call expansion (it only 2862 * removes emitted instructions); correctness holds for any fill byte (the splat 2863 * replicates it to all 8 bytes), for unaligned bases/sizes (the ladder narrows 2864 * the chunk to the remaining run, which the store offset never over-runs), and 2865 * for the exact byte count. 2866 * 2867 * The splat costs a few up-front instructions, so for tiny runs the per-byte 2868 * loop is still cheaper; gate the widening on a size threshold. */ 2869 #define AA_SET_BYTES_WIDEN_MIN 8u 2870 2871 static void aa_set_bytes(NativeTarget* t, NativeAddr dst, NativeLoc byte_value, 2872 AggregateAccess access) { 2873 KitCgTypeId i8 = builtin_id(KIT_CG_BUILTIN_I8); 2874 NativeLoc byte = byte_value; 2875 MemAccess mem = access.mem; 2876 mem.type = i8; 2877 mem.size = 1u; 2878 mem.align = 1u; 2879 byte.type = i8; 2880 if (access.size < AA_SET_BYTES_WIDEN_MIN || native_loc_is_fp(byte_value)) { 2881 /* Small run (or a non-integer fill value): the original per-byte loop. */ 2882 for (u32 off = 0; off < access.size; ++off) 2883 aa_store_native(t, aa_addr_plus(dst, off), byte, mem); 2884 return; 2885 } 2886 /* Splat the fill byte to every byte of a 64-bit scratch (AA_TMP0), distinct 2887 * from the address base/index (IR_AGG_SET materializes the value clear of 2888 * them). `and w,b,#0xff` clears any high bits, then ORR-shift doublings build 2889 * bb -> bbbb -> bbbbbbbb. For a zero fill these collapse to zeros, but stay 2890 * correct; the win is replacing N byte stores with N/8 dword stores. */ 2891 { 2892 MCEmitter* mc = t->mc; 2893 KitCgTypeId i64 = builtin_id(KIT_CG_BUILTIN_I64); 2894 KitCgTypeId i32 = builtin_id(KIT_CG_BUILTIN_I32); 2895 KitCgTypeId i16 = builtin_id(KIT_CG_BUILTIN_I16); 2896 u32 b = loc_reg(byte_value); 2897 NativeLoc val = aa_tmp_loc(i64, AA_TMP0); 2898 u32 immr = 0, imms = 0, N = 0; 2899 u32 off = 0; 2900 (void)aa64_logimm_encode(0xffu, 0u, &N, &immr, &imms); 2901 aa_emit32(mc, aa64_and_imm(0u, AA_TMP0, b, N, immr, imms)); /* w16 = b&0xff */ 2902 aa_emit32(mc, aa64_logsr_pack((AA64LogSR){.sf = 0u, 2903 .opc = AA64_LOG_ORR_OPC, 2904 .Rm = AA_TMP0, 2905 .imm6 = 8u, 2906 .Rn = AA_TMP0, 2907 .Rd = AA_TMP0})); /* orr w,w,w<<8 */ 2908 aa_emit32(mc, aa64_logsr_pack((AA64LogSR){ 2909 .sf = 0u, 2910 .opc = AA64_LOG_ORR_OPC, 2911 .Rm = AA_TMP0, 2912 .imm6 = 16u, 2913 .Rn = AA_TMP0, 2914 .Rd = AA_TMP0})); /* orr w,w,w<<16 -> low 32 = bbbb */ 2915 aa_emit32(mc, aa64_logsr_pack((AA64LogSR){ 2916 .sf = 1u, 2917 .opc = AA64_LOG_ORR_OPC, 2918 .Rm = AA_TMP0, 2919 .imm6 = 32u, 2920 .Rn = AA_TMP0, 2921 .Rd = AA_TMP0})); /* orr x,x,x<<32 -> all 8 bytes = b */ 2922 while (off < access.size) { 2923 u32 rem = access.size - off; 2924 if (rem >= 8u) { 2925 mem.type = i64; 2926 mem.size = 8u; 2927 val.type = i64; 2928 } else if (rem >= 4u) { 2929 mem.type = i32; 2930 mem.size = 4u; 2931 val.type = i32; 2932 } else if (rem >= 2u) { 2933 mem.type = i16; 2934 mem.size = 2u; 2935 val.type = i16; 2936 } else { 2937 mem.type = i8; 2938 mem.size = 1u; 2939 val.type = i8; 2940 } 2941 mem.align = mem.size; 2942 aa_store_native(t, aa_addr_plus(dst, off), val, mem); 2943 off += mem.size; 2944 } 2945 } 2946 } 2947 2948 static void aa_lsl_imm(NativeTarget* t, u32 sf, u32 rd, u32 rn, u32 sh); 2949 static void aa_lsr_imm(NativeTarget* t, u32 sf, u32 rd, u32 rn, u32 sh); 2950 static void aa_asr_imm(NativeTarget* t, u32 sf, u32 rd, u32 rn, u32 sh); 2951 2952 /* Strength-reduce `mul rd, rn, #imm` for the constants accepted by 2953 * aa64_imul_strength_reducible into a single non-mul instruction. Callers 2954 * must gate on aa64_imul_strength_reducible(sf, imm) — this routine panics 2955 * on unhandled constants. */ 2956 static void aa_emit_mul_const_imm(NativeTarget* t, u32 sf, u32 rd, u32 rn, 2957 i64 imm) { 2958 u64 a; 2959 if (imm == 0) { 2960 aa_emit32(t->mc, aa64_mov_reg(sf, rd, AA64_ZR)); 2961 return; 2962 } 2963 if (imm == 1) { 2964 if (rd != rn) aa_emit32(t->mc, aa64_mov_reg(sf, rd, rn)); 2965 return; 2966 } 2967 if (imm == -1) { 2968 aa_emit32(t->mc, aa64_neg(sf, rd, rn)); 2969 return; 2970 } 2971 /* +2^k: lsl rd, rn, #k */ 2972 a = (u64)imm; 2973 if (imm > 0 && (a & (a - 1u)) == 0u) { 2974 u32 k = (u32)__builtin_ctzll(a); 2975 aa_lsl_imm(t, sf, rd, rn, k); 2976 return; 2977 } 2978 /* -2^k: sub rd, xzr, rn, lsl #k */ 2979 if (imm < 0) { 2980 a = (u64)(-imm); 2981 if (a && (a & (a - 1u)) == 0u) { 2982 u32 k = (u32)__builtin_ctzll(a); 2983 aa_emit32(t->mc, aa64_addsubsr_pack((AA64AddSubSR){.sf = sf, 2984 .op = 1u, 2985 .S = 0u, 2986 .shift = 0u, 2987 .Rm = rn, 2988 .imm6 = k, 2989 .Rn = AA64_ZR, 2990 .Rd = rd})); 2991 return; 2992 } 2993 } 2994 /* 2^k + 1: add rd, rn, rn, lsl #k */ 2995 if (imm >= 3) { 2996 u64 m = (u64)(imm - 1); 2997 if ((m & (m - 1u)) == 0u) { 2998 u32 k = (u32)__builtin_ctzll(m); 2999 aa_emit32(t->mc, aa64_addsubsr_pack((AA64AddSubSR){.sf = sf, 3000 .op = 0u, 3001 .S = 0u, 3002 .shift = 0u, 3003 .Rm = rn, 3004 .imm6 = k, 3005 .Rn = rn, 3006 .Rd = rd})); 3007 return; 3008 } 3009 } 3010 /* 1 - 2^k: sub rd, rn, rn, lsl #k */ 3011 if (imm <= -1) { 3012 u64 m = (u64)(1 - imm); 3013 if (m && (m & (m - 1u)) == 0u) { 3014 u32 k = (u32)__builtin_ctzll(m); 3015 aa_emit32(t->mc, aa64_addsubsr_pack((AA64AddSubSR){.sf = sf, 3016 .op = 1u, 3017 .S = 0u, 3018 .shift = 0u, 3019 .Rm = rn, 3020 .imm6 = k, 3021 .Rn = rn, 3022 .Rd = rd})); 3023 return; 3024 } 3025 } 3026 aa_panic(aa_of(t), "aa_emit_mul_const_imm: unhandled constant"); 3027 } 3028 3029 static void aa_binop(NativeTarget* t, BinOp op, NativeLoc dst, NativeLoc lhs, 3030 NativeLoc rhs) { 3031 u32 sf = loc_is_64(t, dst) ? 1u : 0u; 3032 u32 rd = loc_reg(dst), rn = loc_reg(lhs), rm = loc_reg(rhs); 3033 if (native_loc_is_fp(dst)) { 3034 u32 d = loc_size32(t, dst) == 8u; 3035 switch (op) { 3036 case BO_FADD: 3037 aa_emit32(t->mc, aa_fp_bin(0x002800u, d, rd, rn, rm)); 3038 return; 3039 case BO_FSUB: 3040 aa_emit32(t->mc, aa_fp_bin(0x003800u, d, rd, rn, rm)); 3041 return; 3042 case BO_FMUL: 3043 aa_emit32(t->mc, aa_fp_bin(0x000800u, d, rd, rn, rm)); 3044 return; 3045 case BO_FDIV: 3046 aa_emit32(t->mc, aa_fp_bin(0x001800u, d, rd, rn, rm)); 3047 return; 3048 default: 3049 aa_panic(aa_of(t), "unsupported floating binary op"); 3050 } 3051 } 3052 if (rhs.kind == NATIVE_LOC_IMM && (op == BO_IADD || op == BO_ISUB)) { 3053 i64 imm = rhs.v.imm; 3054 int is_add = (op == BO_IADD); 3055 u32 imm12, sh; 3056 if (imm < 0) { 3057 is_add = !is_add; 3058 imm = -imm; 3059 } 3060 if (!aa64_addsub_imm_fits(imm, &imm12, &sh)) 3061 aa_panic(aa_of(t), "binop immediate not encodable"); 3062 aa_emit32(t->mc, is_add ? aa64_add_imm(sf, rd, rn, imm12, sh) 3063 : aa64_sub_imm(sf, rd, rn, imm12, sh)); 3064 return; 3065 } 3066 if (rhs.kind == NATIVE_LOC_IMM && op == BO_IMUL) { 3067 aa_emit_mul_const_imm(t, sf, rd, rn, rhs.v.imm); 3068 return; 3069 } 3070 if (rhs.kind == NATIVE_LOC_IMM && 3071 (op == BO_SHL || op == BO_SHR_U || op == BO_SHR_S)) { 3072 u32 shamt = (u32)rhs.v.imm; /* imm_legal guarantees 0 <= imm < datasize */ 3073 if (op == BO_SHL) 3074 aa_lsl_imm(t, sf, rd, rn, shamt); 3075 else if (op == BO_SHR_U) 3076 aa_lsr_imm(t, sf, rd, rn, shamt); 3077 else 3078 aa_asr_imm(t, sf, rd, rn, shamt); 3079 return; 3080 } 3081 if (rhs.kind == NATIVE_LOC_IMM && 3082 (op == BO_AND || op == BO_OR || op == BO_XOR)) { 3083 u32 N, immr, imms; 3084 if (!aa64_logimm_encode((u64)rhs.v.imm, sf, &N, &immr, &imms)) 3085 aa_panic(aa_of(t), "logical immediate not encodable"); 3086 if (op == BO_AND) 3087 aa_emit32(t->mc, aa64_and_imm(sf, rd, rn, N, immr, imms)); 3088 else if (op == BO_OR) 3089 aa_emit32(t->mc, aa64_orr_imm(sf, rd, rn, N, immr, imms)); 3090 else 3091 aa_emit32(t->mc, aa64_eor_imm(sf, rd, rn, N, immr, imms)); 3092 return; 3093 } 3094 /* L7 shifted-register ALU: a single-use `lsl rm,#k` (k in 1..4) folded into 3095 * this op's second source. Emit `<op> rd,rn,rm,lsl #k` in one instruction 3096 * instead of `lsl rT,rm,#k; <op> rd,rn,rT`. Only an integer register rhs can 3097 * carry a shift rider (immediates never do, and the recognition pass restricts 3098 * it to add/sub/and/orr/eor). */ 3099 if (rhs.kind == NATIVE_LOC_REG && rhs.shift) { 3100 u32 k = rhs.shift; 3101 switch (op) { 3102 case BO_IADD: 3103 aa_emit32(t->mc, aa_addsub_lsl(sf, /*op=add*/ 0u, rd, rn, rm, k)); 3104 return; 3105 case BO_ISUB: 3106 aa_emit32(t->mc, aa_addsub_lsl(sf, /*op=sub*/ 1u, rd, rn, rm, k)); 3107 return; 3108 case BO_AND: 3109 aa_emit32(t->mc, aa_logsr_lsl(sf, AA64_LOG_AND_OPC, rd, rn, rm, k)); 3110 return; 3111 case BO_OR: 3112 aa_emit32(t->mc, aa_logsr_lsl(sf, AA64_LOG_ORR_OPC, rd, rn, rm, k)); 3113 return; 3114 case BO_XOR: 3115 aa_emit32(t->mc, aa_logsr_lsl(sf, AA64_LOG_EOR_OPC, rd, rn, rm, k)); 3116 return; 3117 default: 3118 aa_panic(aa_of(t), "shift rider on unsupported binop"); 3119 } 3120 } 3121 switch (op) { 3122 case BO_IADD: 3123 aa_emit32(t->mc, aa64_add(sf, rd, rn, rm)); 3124 return; 3125 case BO_ISUB: 3126 aa_emit32(t->mc, aa64_sub(sf, rd, rn, rm)); 3127 return; 3128 case BO_IMUL: 3129 aa_emit32(t->mc, aa64_mul(sf, rd, rn, rm)); 3130 return; 3131 case BO_SDIV: 3132 aa_emit32(t->mc, aa64_sdiv(sf, rd, rn, rm)); 3133 return; 3134 case BO_UDIV: 3135 aa_emit32(t->mc, aa64_udiv(sf, rd, rn, rm)); 3136 return; 3137 case BO_SREM: 3138 aa_emit32(t->mc, aa64_sdiv(sf, AA_TMP0, rn, rm)); 3139 aa_emit32(t->mc, aa64_mul(sf, AA_TMP0, AA_TMP0, rm)); 3140 aa_emit32(t->mc, aa64_sub(sf, rd, rn, AA_TMP0)); 3141 return; 3142 case BO_UREM: 3143 aa_emit32(t->mc, aa64_udiv(sf, AA_TMP0, rn, rm)); 3144 aa_emit32(t->mc, aa64_mul(sf, AA_TMP0, AA_TMP0, rm)); 3145 aa_emit32(t->mc, aa64_sub(sf, rd, rn, AA_TMP0)); 3146 return; 3147 case BO_AND: 3148 aa_emit32(t->mc, aa64_and(sf, rd, rn, rm)); 3149 return; 3150 case BO_OR: 3151 aa_emit32(t->mc, aa64_orr(sf, rd, rn, rm)); 3152 return; 3153 case BO_XOR: 3154 aa_emit32(t->mc, aa64_eor(sf, rd, rn, rm)); 3155 return; 3156 case BO_SHL: 3157 aa_emit32(t->mc, aa64_lslv(sf, rd, rn, rm)); 3158 return; 3159 case BO_SHR_U: 3160 aa_emit32(t->mc, aa64_lsrv(sf, rd, rn, rm)); 3161 return; 3162 case BO_SHR_S: 3163 aa_emit32(t->mc, aa64_asrv(sf, rd, rn, rm)); 3164 return; 3165 default: 3166 aa_panic(aa_of(t), "unsupported binary op"); 3167 } 3168 } 3169 3170 static void aa_unop(NativeTarget* t, UnOp op, NativeLoc dst, NativeLoc src) { 3171 u32 sf = loc_is_64(t, dst) ? 1u : 0u; 3172 if (native_loc_is_fp(dst)) { 3173 switch (op) { 3174 case UO_FNEG: 3175 case UO_NEG: 3176 aa_emit32(t->mc, aa_fneg(loc_size32(t, dst) == 8u, loc_reg(dst), 3177 loc_reg(src))); 3178 return; 3179 default: 3180 aa_panic(aa_of(t), "unsupported floating unary op"); 3181 } 3182 } 3183 switch (op) { 3184 case UO_NEG: 3185 aa_emit32(t->mc, aa64_neg(sf, loc_reg(dst), loc_reg(src))); 3186 return; 3187 case UO_BNOT: 3188 aa_emit32(t->mc, aa64_mvn(sf, loc_reg(dst), loc_reg(src))); 3189 return; 3190 case UO_NOT: 3191 aa_emit32(t->mc, aa64_subs_imm12(sf, AA64_ZR, loc_reg(src), 0, 0)); 3192 aa_emit32(t->mc, aa_cset(sf, loc_reg(dst), 0x0u)); 3193 return; 3194 default: 3195 aa_panic(aa_of(t), "unsupported unary op"); 3196 } 3197 } 3198 3199 static void aa_emit_cmp_to_flags(NativeTarget* t, NativeLoc lhs, 3200 NativeLoc rhs) { 3201 if (native_loc_is_fp(lhs)) { 3202 aa_emit32(t->mc, 3203 aa_fcmp(loc_size32(t, lhs) == 8u, loc_reg(lhs), loc_reg(rhs))); 3204 return; 3205 } 3206 { 3207 u32 sf = loc_is_64(t, lhs) ? 1u : 0u; 3208 if (rhs.kind == NATIVE_LOC_IMM) { 3209 u32 imm12 = 0, sh = 0; 3210 if (rhs.v.imm < 0 || !aa64_addsub_imm_fits(rhs.v.imm, &imm12, &sh)) 3211 aa_panic(aa_of(t), "cmp immediate not encodable"); 3212 aa_emit32(t->mc, aa64_subs_imm12(sf, AA64_ZR, loc_reg(lhs), imm12, sh)); 3213 return; 3214 } 3215 aa_emit32(t->mc, aa_subs_reg(sf, AA64_ZR, loc_reg(lhs), loc_reg(rhs))); 3216 } 3217 } 3218 3219 static void aa_cmp(NativeTarget* t, CmpOp op, NativeLoc dst, NativeLoc lhs, 3220 NativeLoc rhs) { 3221 u32 sf = loc_is_64(t, dst); 3222 u32 rd = loc_reg(dst); 3223 aa_emit_cmp_to_flags(t, lhs, rhs); 3224 /* CMP_ONE_F (ordered & !=) and CMP_UEQ_F (unordered | ==) have no single 3225 * AArch64 FP condition. After FCMP, unordered sets V (and Z=0), so VC 3226 * (V==0) selects "ordered". */ 3227 if (op == CMP_ONE_F) { 3228 /* ordered & not-equal: NE masked to the ordered case. */ 3229 aa_emit32(t->mc, aa_cset(sf, rd, 0x1u)); /* cset rd, NE */ 3230 aa_emit32(t->mc, 3231 aa64_csel_enc(sf, rd, rd, AA64_ZR, 0x7u)); /* csel rd,rd,zr,VC */ 3232 return; 3233 } 3234 if (op == CMP_UEQ_F) { 3235 /* equal, or forced to 1 when unordered. */ 3236 aa_emit32(t->mc, aa_cset(sf, rd, 0x0u)); /* cset rd, EQ */ 3237 aa_emit32(t->mc, aa64_csinc_enc(sf, rd, rd, AA64_ZR, 3238 0x7u)); /* csinc rd,rd,zr,VC */ 3239 return; 3240 } 3241 aa_emit32(t->mc, aa_cset(sf, rd, cmp_cond(op))); 3242 } 3243 3244 static void aa_convert(NativeTarget* t, ConvKind op, NativeLoc dst, 3245 NativeLoc src) { 3246 int dst_fp = native_loc_is_fp(dst); 3247 int src_fp = native_loc_is_fp(src); 3248 switch (op) { 3249 case CV_TRUNC: 3250 case CV_BITCAST: 3251 aa_move(t, dst, src); 3252 return; 3253 case CV_ZEXT: { 3254 u32 src_bits = loc_size32(t, src) * 8u; 3255 u32 dst_bits = loc_size32(t, dst) * 8u; 3256 u32 sf = dst_bits > 32u; 3257 if (src_bits >= dst_bits) { 3258 aa_move(t, dst, src); 3259 } else if (src_bits >= 32u) { 3260 /* w-reg mov zero-extends into the x-reg. When dst and src are the same 3261 * hard register (the -O0 convert-coalescing rename targets the source's 3262 * own register) it is a no-op: the value already arrived there from a 3263 * 32-bit-writing op, which itself cleared the upper 32 bits. */ 3264 if (!(dst.kind == NATIVE_LOC_REG && src.kind == NATIVE_LOC_REG && 3265 dst.v.reg == src.v.reg)) 3266 aa_emit32(t->mc, aa64_mov_reg(0, loc_reg(dst), loc_reg(src))); 3267 } else { 3268 aa_emit32(t->mc, 3269 aa_ubfm(sf, loc_reg(dst), loc_reg(src), 0, src_bits - 1u)); 3270 } 3271 return; 3272 } 3273 case CV_SEXT: { 3274 u32 src_bits = loc_size32(t, src) * 8u; 3275 u32 dst_bits = loc_size32(t, dst) * 8u; 3276 u32 sf = dst_bits > 32u; 3277 if (src_bits >= dst_bits) { 3278 aa_move(t, dst, src); 3279 } else { 3280 aa_emit32(t->mc, 3281 aa_sbfm(sf, loc_reg(dst), loc_reg(src), 0, src_bits - 1u)); 3282 } 3283 return; 3284 } 3285 case CV_ITOF_S: 3286 aa_emit32(t->mc, aa_scvtf(loc_size32(t, dst) == 8u, loc_is_64(t, src), 3287 loc_reg(dst), loc_reg(src))); 3288 return; 3289 case CV_ITOF_U: 3290 aa_emit32(t->mc, aa_ucvtf(loc_size32(t, dst) == 8u, loc_is_64(t, src), 3291 loc_reg(dst), loc_reg(src))); 3292 return; 3293 case CV_FTOI_S: 3294 aa_emit32(t->mc, aa_fcvtzs(loc_is_64(t, dst), loc_size32(t, src) == 8u, 3295 loc_reg(dst), loc_reg(src))); 3296 return; 3297 case CV_FTOI_U: 3298 aa_emit32(t->mc, aa_fcvtzu(loc_is_64(t, dst), loc_size32(t, src) == 8u, 3299 loc_reg(dst), loc_reg(src))); 3300 return; 3301 case CV_FEXT: 3302 if (dst_fp && src_fp) 3303 aa_emit32(t->mc, aa_fcvt_d_s(loc_reg(dst), loc_reg(src))); 3304 else 3305 aa_move(t, dst, src); 3306 return; 3307 case CV_FTRUNC: 3308 if (dst_fp && src_fp) 3309 aa_emit32(t->mc, aa_fcvt_s_d(loc_reg(dst), loc_reg(src))); 3310 else 3311 aa_move(t, dst, src); 3312 return; 3313 default: 3314 aa_panic(aa_of(t), "unsupported conversion"); 3315 } 3316 } 3317 3318 /* §E.3 narrow register-only entry points. The NDT crosses 16 B NativeRegLoc 3319 * here; these reconstruct the NativeLoc the fat hook expects and delegate, so 3320 * the emitted bytes are identical. Used only on the -O0 NDT path; the opt 3321 * replay path calls aa_binop/aa_move/aa_cmp/aa_convert directly. */ 3322 static void aa_binop_rr(NativeTarget* t, BinOp op, NativeRegLoc dst, 3323 NativeRegLoc a, NativeRegLoc b) { 3324 aa_binop(t, op, native_loc_from_reg(dst), native_loc_from_reg(a), 3325 native_loc_from_reg(b)); 3326 } 3327 3328 static void aa_move_rr(NativeTarget* t, NativeRegLoc dst, NativeRegLoc src) { 3329 aa_move(t, native_loc_from_reg(dst), native_loc_from_reg(src)); 3330 } 3331 3332 static void aa_cmp_rr(NativeTarget* t, CmpOp op, NativeRegLoc dst, 3333 NativeRegLoc a, NativeRegLoc b) { 3334 aa_cmp(t, op, native_loc_from_reg(dst), native_loc_from_reg(a), 3335 native_loc_from_reg(b)); 3336 } 3337 3338 static void aa_convert_rr(NativeTarget* t, ConvKind op, NativeRegLoc dst, 3339 NativeRegLoc src) { 3340 aa_convert(t, op, native_loc_from_reg(dst), native_loc_from_reg(src)); 3341 } 3342 3343 static void aa_alloca(NativeTarget* t, NativeLoc dst, NativeLoc size, 3344 u32 align) { 3345 AANativeTarget* a = aa_of(t); 3346 u32 use_align = align < 16u ? 16u : align; 3347 if (use_align & (use_align - 1u)) aa_panic(a, "alloca alignment not pow2"); 3348 aa_emit_add_imm(a, AA_TMP0, loc_reg(size), (i32)(use_align - 1u)); 3349 aa_emit_load_imm(t->mc, 1, AA_TMP1, -(i64)use_align); 3350 aa_emit32(t->mc, aa64_and(1, AA_TMP0, AA_TMP0, AA_TMP1)); 3351 aa_emit32(t->mc, aa64_add_imm(1, AA_TMP1, AA_SP, 0, 0)); 3352 aa_emit32(t->mc, aa64_sub(1, AA_TMP1, AA_TMP1, AA_TMP0)); 3353 aa_emit32(t->mc, aa64_add_imm(1, AA_SP, AA_TMP1, 0, 0)); 3354 /* The alloca result is sp + outgoing-area bytes. On the known-frame path 3355 * max_outgoing is already final, so emit the final `add dst, sp, #N` here; on 3356 * the single-pass path it is not known yet, so record a patch. */ 3357 if (a->frame.known_frame) { 3358 u32 imm12, sh; 3359 if (!aa64_addsub_imm_fits(a->frame.max_outgoing, &imm12, &sh)) 3360 aa_panic(a, "outgoing area too large for alloca result"); 3361 aa_emit32(t->mc, aa64_add_imm(1, loc_reg(dst), AA_SP, imm12, sh)); 3362 } else { 3363 AAPatch* p; 3364 /* First alloca arms the frame-base anchor. The far-slot fast path can no 3365 * longer key off sp (this `sub sp` floats it), so deferred AA_PATCH_SLOTs — 3366 * those already emitted and any still to come — resolve against 3367 * AA_FRAME_BASE instead. Reserve a static-frame home for the caller's 3368 * value; the prologue saves it and sets AA_FRAME_BASE = frame base (before 3369 * the body runs, so every slot access sees a valid anchor), the epilogue 3370 * restores it. Only needed when slot_sp_base armed the fast path in the 3371 * first place. */ 3372 if (a->slot_sp_base && !a->uses_frame_base) { 3373 NativeFrameSlotDesc sd; 3374 memset(&sd, 0, sizeof sd); 3375 sd.type = builtin_id(KIT_CG_BUILTIN_I64); 3376 sd.size = 8; 3377 sd.align = 8; 3378 sd.kind = NATIVE_FRAME_SLOT_SAVE; 3379 a->frame_base_slot = t->frame_slot(t, &sd); 3380 a->uses_frame_base = 1; 3381 } 3382 p = aa_patch_alloc(a); 3383 p->kind = AA_PATCH_ALLOCA; 3384 p->pos = mc_pos(t->mc); 3385 p->u.dst_reg = loc_reg(dst); 3386 a->nalloca++; 3387 aa_emit32(t->mc, aa64_add_imm(1, loc_reg(dst), AA_SP, 0, 0)); 3388 } 3389 } 3390 3391 static MemAccess aa_mem_for_type(NativeTarget* t, KitCgTypeId type, u32 size) { 3392 MemAccess mem; 3393 memset(&mem, 0, sizeof mem); 3394 mem.type = type; 3395 mem.size = size ? size : type_size32(t, type); 3396 mem.align = type_align32(t, type); 3397 if (mem.align > mem.size && mem.size) mem.align = mem.size; 3398 return mem; 3399 } 3400 3401 /* native_loc_reg / native_loc_stack are shared in native_target.h. */ 3402 3403 static NativeAddr aa_storage_addr(AANativeTarget* a, NativeLoc loc, 3404 u32 offset) { 3405 NativeAddr addr; 3406 if (!native_loc_storage_addr(loc, (i32)offset, &addr)) 3407 aa_panic(a, "location is not storage"); 3408 return addr; 3409 } 3410 3411 static void aa_addr_of_loc(NativeTarget* t, NativeLoc dst, NativeLoc src) { 3412 AANativeTarget* a = aa_of(t); 3413 NativeAddr addr = aa_storage_addr(a, src, 0); 3414 aa_load_addr(t, dst, addr); 3415 } 3416 3417 static void aa_load_part(NativeTarget* t, NativeLoc dst, NativeLoc src, 3418 u32 offset, u32 size) { 3419 AANativeTarget* a = aa_of(t); 3420 MemAccess mem = aa_mem_for_type(t, dst.type, size); 3421 if (src.kind == NATIVE_LOC_REG) { 3422 aa_move(t, dst, src); 3423 return; 3424 } 3425 if (native_loc_addr_role(src) == NATIVE_LOC_ADDR_ROLE_VALUE) { 3426 NativeAddr addr; 3427 if (offset != 0u || size != t->c->target.ptr_size || 3428 !native_loc_address_value(src, &addr)) 3429 aa_panic(a, "split or invalid address-value argument"); 3430 aa_load_addr(t, dst, addr); 3431 return; 3432 } 3433 if (native_loc_addr_role(src) == NATIVE_LOC_ADDR_ROLE_STORAGE) { 3434 NativeAddr addr = aa_storage_addr(a, src, offset); 3435 addr.base_type = dst.type; 3436 aa_emit_mem(a, 1, dst, addr, mem); 3437 return; 3438 } 3439 if (src.kind == NATIVE_LOC_IMM) { 3440 i64 part; 3441 if (!native_loc_imm_part(src, offset, size, &part)) 3442 aa_panic(a, "invalid immediate argument part"); 3443 aa_emit_load_imm(t->mc, loc_is_64(t, dst), loc_reg(dst), part); 3444 return; 3445 } 3446 aa_panic(a, "unsupported call argument source"); 3447 } 3448 3449 static void aa_store_part(NativeTarget* t, NativeLoc dst, NativeLoc src, 3450 u32 offset, u32 size) { 3451 AANativeTarget* a = aa_of(t); 3452 MemAccess mem = aa_mem_for_type(t, src.type, size); 3453 if (native_loc_addr_role(dst) == NATIVE_LOC_ADDR_ROLE_STORAGE) { 3454 NativeAddr addr = aa_storage_addr(a, dst, offset); 3455 addr.base_type = src.type; 3456 aa_emit_mem(a, 0, src, addr, mem); 3457 return; 3458 } 3459 if (dst.kind == NATIVE_LOC_REG) { 3460 aa_move(t, dst, src); 3461 return; 3462 } 3463 aa_panic(a, "unsupported call return destination"); 3464 } 3465 3466 static void aa_store_outgoing_part(NativeTarget* t, int tail_call, 3467 u32 stack_off, NativeLoc src, u32 size) { 3468 NativeAddr addr; 3469 MemAccess mem = aa_mem_for_type(t, src.type, size); 3470 memset(&addr, 0, sizeof addr); 3471 addr.base_kind = NATIVE_ADDR_BASE_REG; 3472 addr.base.reg = tail_call ? AA_FP : AA_SP; 3473 addr.base_type = src.type; 3474 /* Tail calls write outgoing args into the caller's incoming-args window 3475 * (= [fp + 16 + off], same address the tail-callee will read via 3476 * aa_fp_off_in_arg). Non-tail calls write to the sp-anchored outgoing 3477 * area at the bottom of the caller's frame. */ 3478 addr.offset = tail_call ? aa_fp_off_tail_out_arg(aa_of(t), stack_off) 3479 : aa_sp_off_out_arg(stack_off); 3480 aa_emit_mem(aa_of(t), 0, src, addr, mem); 3481 } 3482 3483 /* Copy only the bytes that belong to a stack-routed value. The caller owns the 3484 * separately rounded ABI slot and advances its physical stack cursor itself; 3485 * this helper never treats that carrier/padding size as readable source data. */ 3486 static void aa_store_outgoing_value_bytes(NativeTarget* t, int tail_call, 3487 u32 stack_off, NativeLoc src, 3488 u32 value_size) { 3489 NativeLoc tmp = 3490 native_loc_reg(src.type, NATIVE_REG_INT, AA_TMP0); 3491 u32 off = 0; 3492 while (off < value_size) { 3493 u32 chunk = value_size - off; 3494 if (chunk > 8u) chunk = 8u; 3495 aa_load_part(t, tmp, src, off, chunk); 3496 aa_store_outgoing_part(t, tail_call, stack_off + off, tmp, chunk); 3497 off += chunk; 3498 } 3499 } 3500 3501 static const ABIArgInfo* aa_param_abi(NativeTarget* t, const ABIFuncInfo* abi, 3502 const NativeCallDesc* desc, u32 i, 3503 ABIArgInfo* scratch) { 3504 if (abi && i < abi->nparams) return &abi->params[i]; 3505 memset(scratch, 0, sizeof *scratch); 3506 scratch->kind = ABI_ARG_DIRECT; 3507 scratch->flags = ABI_AF_NONE; 3508 scratch->nparts = 1; 3509 scratch->parts = arena_zarray(t->c->tu, ABIArgPart, 1); 3510 /* Windows ARM64 routes variadic floating-point arguments through the integer 3511 * registers/stack (the classifier's remap_fp_parts_to_int does the same for 3512 * the *named* params of a variadic function); the value's bit pattern moves 3513 * via fmov x,d. Every other ABI keeps the `...` FP args in v registers. */ 3514 ((ABIArgPart*)scratch->parts)[0].cls = 3515 (cg_type_is_float(t->c, desc->args[i].type) && 3516 !(abi && abi->vararg_fp_via_int)) 3517 ? ABI_CLASS_FP 3518 : ABI_CLASS_INT; 3519 ((ABIArgPart*)scratch->parts)[0].loc = ABI_LOC_REG; 3520 ((ABIArgPart*)scratch->parts)[0].size = type_size32(t, desc->args[i].type); 3521 ((ABIArgPart*)scratch->parts)[0].align = type_align32(t, desc->args[i].type); 3522 ((ABIArgPart*)scratch->parts)[0].src_offset = 0; 3523 return scratch; 3524 } 3525 3526 /* Stack footprint of a single argument part. AAPCS64 uses 8-byte slots. Apple 3527 * ARM64 uses compact 4-byte slots for fixed stack-passed int32-sized values, 3528 * but its forced stack variadics still use 8-byte slots. */ 3529 static u32 aa_stack_arg_min_align(const ABIFuncInfo* abi) { 3530 return (abi && abi->stack_arg_min_align) ? abi->stack_arg_min_align : 8u; 3531 } 3532 3533 static u32 aa_vararg_stack_arg_min_align(const ABIFuncInfo* abi) { 3534 if (abi && abi->vararg_stack_arg_min_align) 3535 return abi->vararg_stack_arg_min_align; 3536 return aa_stack_arg_min_align(abi); 3537 } 3538 3539 static u32 aa_vararg_stack_start(const ABIFuncInfo* abi, u32 cursor) { 3540 return align_up_u32(cursor, aa_vararg_stack_arg_min_align(abi)); 3541 } 3542 3543 /* Natural stack alignment of a part, capped at 16 (binary128). */ 3544 static u32 aa_part_stack_align_min(u32 min_align, const ABIArgPart* part) { 3545 u32 al = part->align ? part->align : 8u; 3546 if (al < min_align) al = min_align; 3547 if (al > 16u) al = 16u; 3548 return al; 3549 } 3550 3551 static u32 aa_part_stack_align(const ABIFuncInfo* abi, const ABIArgPart* part) { 3552 return aa_part_stack_align_min(aa_stack_arg_min_align(abi), part); 3553 } 3554 3555 static u32 aa_part_vararg_stack_align(const ABIFuncInfo* abi, 3556 const ABIArgPart* part) { 3557 return aa_part_stack_align_min(aa_vararg_stack_arg_min_align(abi), part); 3558 } 3559 3560 static u32 aa_part_stack_size(const ABIFuncInfo* abi, const ABIArgPart* part) { 3561 return align_up_u32(part->size ? part->size : 8u, 3562 aa_part_stack_align(abi, part)); 3563 } 3564 3565 static u32 aa_part_vararg_stack_size(const ABIFuncInfo* abi, 3566 const ABIArgPart* part) { 3567 return align_up_u32(part->size ? part->size : 8u, 3568 aa_part_vararg_stack_align(abi, part)); 3569 } 3570 3571 /* The scalar type used to move one ABI part through a register. Aggregate 3572 * args/results are split into parts; each part must move at its own width, not 3573 * the (possibly >8-byte) aggregate width. */ 3574 static KitCgTypeId aa_part_scalar_type(const ABIArgPart* part) { 3575 if (part->cls == ABI_CLASS_FP) { 3576 if (part->size <= 4u) return builtin_id(KIT_CG_BUILTIN_F32); 3577 if (part->size <= 8u) return builtin_id(KIT_CG_BUILTIN_F64); 3578 return builtin_id(KIT_CG_BUILTIN_F128); 3579 } 3580 switch (part->size) { 3581 case 1u: 3582 return builtin_id(KIT_CG_BUILTIN_I8); 3583 case 2u: 3584 return builtin_id(KIT_CG_BUILTIN_I16); 3585 case 4u: 3586 return builtin_id(KIT_CG_BUILTIN_I32); 3587 default: 3588 return builtin_id(KIT_CG_BUILTIN_I64); 3589 } 3590 } 3591 3592 static u32 aa_class_vararg_stack_size(const ABIFuncInfo* abi, 3593 const ABIArgInfo* ai) { 3594 u32 total = 0; 3595 u32 min_align = aa_vararg_stack_arg_min_align(abi); 3596 if (!ai || ai->kind == ABI_ARG_IGNORE) return 0; 3597 if (ai->kind == ABI_ARG_INDIRECT) return 8u; 3598 for (u32 p = 0; p < ai->nparts; ++p) { 3599 total = align_up_u32(total, aa_part_vararg_stack_align(abi, &ai->parts[p])); 3600 total += aa_part_vararg_stack_size(abi, &ai->parts[p]); 3601 } 3602 return align_up_u32(total ? total : min_align, min_align); 3603 } 3604 3605 static u32 aa_call_stack_size(NativeTarget* t, const NativeCallDesc* desc) { 3606 const ABIFuncInfo* abi = abi_cg_func_info(t->c->abi, desc->fn_type); 3607 u32 next_int = 0, next_fp = 0, stack = 0; 3608 for (u32 i = 0; i < desc->nargs; ++i) { 3609 ABIArgInfo tmp; 3610 const ABIArgInfo* ai = aa_param_abi(t, abi, desc, i, &tmp); 3611 int force_stack = 3612 abi && abi->variadic && abi->vararg_on_stack && i >= abi->nparams; 3613 if (ai->kind == ABI_ARG_IGNORE) continue; 3614 if (force_stack) { 3615 stack = aa_vararg_stack_start(abi, stack); 3616 stack += aa_class_vararg_stack_size(abi, ai); 3617 continue; 3618 } 3619 if (ai->kind == ABI_ARG_INDIRECT) { 3620 if (next_int < 8u) 3621 next_int++; 3622 else 3623 stack += 8u; 3624 continue; 3625 } 3626 for (u32 p = 0; p < ai->nparts; ++p) { 3627 const ABIArgPart* part = &ai->parts[p]; 3628 if (part->cls == ABI_CLASS_FP) { 3629 if (next_fp < 8u) 3630 next_fp++; 3631 else { 3632 stack = align_up_u32(stack, aa_part_stack_align(abi, part)); 3633 stack += aa_part_stack_size(abi, part); 3634 } 3635 } else { 3636 if (next_int < 8u) 3637 next_int++; 3638 else { 3639 stack = align_up_u32(stack, aa_part_stack_align(abi, part)); 3640 stack += aa_part_stack_size(abi, part); 3641 } 3642 } 3643 } 3644 } 3645 return align_up_u32(stack, 16u); 3646 } 3647 3648 /* Stack-argument bytes a call with `fn_type`'s fixed parameters uses. Reuses 3649 * aa_call_stack_size by routing the declared params through it (their ABI 3650 * classification is independent of the actual operand locations, which 3651 * aa_call_stack_size ignores for register/stack placement). */ 3652 static u32 aa_signature_stack_bytes(NativeTarget* t, KitCgTypeId fn_type, 3653 int* variadic, u32* nparams) { 3654 const ABIFuncInfo* abi = abi_cg_func_info(t->c->abi, fn_type); 3655 NativeCallDesc d; 3656 if (variadic) *variadic = abi ? (int)abi->variadic : 0; 3657 if (nparams) *nparams = abi ? abi->nparams : 0u; 3658 memset(&d, 0, sizeof d); 3659 d.fn_type = fn_type; 3660 d.nargs = abi ? abi->nparams : 0u; 3661 if (d.nargs) d.args = arena_zarray(t->c->tu, NativeLoc, d.nargs); 3662 return aa_call_stack_size(t, &d); 3663 } 3664 3665 /* Pure NativeTarget.call_stack_bytes: outgoing stack bytes for a full call 3666 * descriptor (handles variadic stack args, unlike signature_stack_bytes which 3667 * sees only the fixed params). aa_call_stack_size reads only fn_type and each 3668 * args[i].type, so the frame-planning pre-pass can call this before emitting. 3669 */ 3670 static u32 aa_call_stack_bytes(NativeTarget* t, const NativeCallDesc* desc) { 3671 return aa_call_stack_size(t, desc); 3672 } 3673 3674 /* One register-passed call argument: write `src` (or its address) into the 3675 * argument register `dst`. Collected during planning and emitted as a batch so 3676 * the backend can order them as a parallel copy (see aa_emit_reg_arg_moves). */ 3677 typedef NativeArgMove AAArgMove; 3678 3679 /* AAPCS64/Apple permit at most 8 GP + 8 FP register-passed argument slots. */ 3680 #define AA_MAX_REG_ARG_MOVES 16u 3681 3682 static void aa_emit_one_arg_move(NativeTarget* t, const NativeArgMove* m) { 3683 if (m->is_addr) 3684 aa_addr_of_loc(t, m->dst, m->src); 3685 else 3686 aa_load_part(t, m->dst, m->src, m->src_offset, m->size); 3687 } 3688 3689 /* Emit register-argument moves as a parallel copy via the shared scheduler: 3690 * every register is read by all moves that source it before any move overwrites 3691 * it; a true cycle is broken through a scratch. The allocator usually arranges 3692 * a conflict-free order, but not always (notably variadic args, where it can 3693 * leave a prior call's result in x0 even though x0 is this call's first arg 3694 * register), so the backend must not assume a safe order. Cycle scratch is 3695 * AA_TMP1 (x17) for int and v16 for fp — distinct from x16 (AA_TMP0), which may 3696 * hold a stashed indirect callee. */ 3697 static void aa_emit_reg_arg_moves(NativeTarget* t, NativeArgMove* moves, 3698 u32 n) { 3699 NativeArgShuffle s; 3700 if (n > AA_MAX_REG_ARG_MOVES) 3701 aa_panic(aa_of(t), "too many register arguments"); 3702 memset(&s, 0, sizeof s); 3703 s.t = t; 3704 s.emit_one = aa_emit_one_arg_move; 3705 s.reg_move = aa_move; 3706 s.scratch[NATIVE_REG_INT] = AA_TMP1; 3707 s.scratch[NATIVE_REG_FP] = 16u; 3708 s.scratch_class_mask = (1u << NATIVE_REG_INT) | (1u << NATIVE_REG_FP); 3709 native_arg_shuffle(&s, moves, n); 3710 } 3711 3712 static void aa_marshal_call(NativeTarget* t, const NativeCallDesc* desc, 3713 NativeCallPhase* plan) { 3714 NativeCallPhaseRet* rets; 3715 const ABIFuncInfo* abi = abi_cg_func_info(t->c->abi, desc->fn_type); 3716 NativeLoc saved_indirect_callee; 3717 int preserve_indirect_callee = 0; 3718 /* Right-size the result scratch to the exact number of entries the ret loops 3719 * below write: nparts on a DIRECT register return, 1 on the !abi fallback, 3720 * 0 (NULL) otherwise (IGNORE / sret / no results). */ 3721 u32 nrets_cap = (abi && abi->ret.kind == ABI_ARG_DIRECT && desc->nresults) 3722 ? abi->ret.nparts 3723 : ((!abi && desc->nresults) ? 1u : 0u); 3724 memset(plan, 0, sizeof *plan); 3725 rets = 3726 nrets_cap ? arena_zarray(t->c->tu, NativeCallPhaseRet, nrets_cap) : NULL; 3727 plan->callee = desc->callee; 3728 plan->rets = rets; 3729 plan->flags = desc->flags; 3730 plan->has_sret = abi && abi->has_sret; 3731 plan->is_variadic = abi && abi->variadic; 3732 plan->stack_arg_size = aa_call_stack_size(t, desc); 3733 native_frame_note_outgoing(&aa_of(t)->frame, plan->stack_arg_size); 3734 /* An indirect target in an argument/result or backend-private register can 3735 * be overwritten while this hook fills x0..x7, x8 (sret), or uses x16/x17 3736 * for exact-width stack arguments. Preserve it in LR, whose incoming value 3737 * is already frame-saved for a non-tail call and is dead on a tail path. 3738 * After all argument work, move it to the final call/branch register. */ 3739 if (plan->callee.kind == NATIVE_LOC_REG && 3740 (NativeAllocClass)plan->callee.cls == NATIVE_REG_INT && 3741 (plan->callee.v.reg <= AA_X8 || plan->callee.v.reg == AA_TMP0 || 3742 plan->callee.v.reg == AA_TMP1)) { 3743 saved_indirect_callee = 3744 native_loc_reg(plan->callee.type, NATIVE_REG_INT, AA_LR); 3745 aa_move(t, saved_indirect_callee, plan->callee); 3746 preserve_indirect_callee = 1; 3747 } 3748 { 3749 u32 next_int = 0, next_fp = 0, stack = 0, nmoves = 0; 3750 int tail_call = (desc->flags & CG_CALL_TAIL) != 0; 3751 AAArgMove moves[AA_MAX_REG_ARG_MOVES]; 3752 /* Stack-passed arguments are stored inline as we walk, *before* any 3753 * argument register is written, so a stack-arg source that the allocator 3754 * left in an arg register (e.g. a prior call's result still in x0, consumed 3755 * as a variadic stack arg) is read while it is still live. Stack stores 3756 * only touch memory and the AA_TMP0/v16 scratch, never an arg-register 3757 * source, so emitting them first cannot clobber a register-arg source. 3758 * Register-passed arguments are collected and emitted afterward as a 3759 * parallel copy (aa_emit_reg_arg_moves) so they likewise never overwrite a 3760 * register another argument still needs to read. */ 3761 for (u32 i = 0; i < desc->nargs; ++i) { 3762 ABIArgInfo tmp; 3763 const ABIArgInfo* ai = aa_param_abi(t, abi, desc, i, &tmp); 3764 int force_stack = 3765 abi && abi->variadic && abi->vararg_on_stack && i >= abi->nparams; 3766 if (ai->kind == ABI_ARG_IGNORE) continue; 3767 if (force_stack) { 3768 u32 n = aa_class_vararg_stack_size(abi, ai); 3769 stack = aa_vararg_stack_start(abi, stack); 3770 if (ai->kind == ABI_ARG_DIRECT && ai->nparts == 1 && n <= 8u) { 3771 /* Scalar variadic argument in one <=8-byte slot. Load the value at 3772 * its OWN width — a sub-slot integer load zero-extends into the 3773 * 64-bit temp — then store the whole slot. Loading the slot width 3774 * from the value's home would pull garbage high bits, and storing 3775 * only the value width would leave them undefined; either way a 3776 * callee that va_arg's a wider type than was passed (the common 3777 * `(int)0` read back as a null pointer) would see garbage. Zero- 3778 * extend-then-store-the-slot is what clang emits and makes it 3779 * well-defined. */ 3780 const ABIArgPart* part = &ai->parts[0]; 3781 NativeAllocClass cls = 3782 part->cls == ABI_CLASS_FP ? NATIVE_REG_FP : NATIVE_REG_INT; 3783 NativeLoc tmpreg = native_loc_reg( 3784 desc->args[i].type, cls, cls == NATIVE_REG_FP ? 16u : AA_TMP0); 3785 aa_load_part(t, tmpreg, desc->args[i], part->src_offset, part->size); 3786 aa_store_outgoing_part(t, tail_call, stack, tmpreg, n); 3787 stack += n; 3788 continue; 3789 } 3790 { 3791 /* Aggregate / multi-word variadic argument: `n` is the physical ABI 3792 * carrier (Apple rounds a 12-byte unnamed aggregate to 16), not a 3793 * source access width. Copy the value's exact object bytes, leaving 3794 * carrier padding untouched, then advance by the full slot. */ 3795 u32 value_size = type_size32(t, desc->args[i].type); 3796 aa_store_outgoing_value_bytes(t, tail_call, stack, desc->args[i], 3797 value_size); 3798 stack += n; 3799 } 3800 continue; 3801 } 3802 if (ai->kind == ABI_ARG_INDIRECT) { 3803 if (next_int < 8u) { 3804 AAArgMove* m = &moves[nmoves++]; 3805 m->dst = native_loc_reg(builtin_id(KIT_CG_BUILTIN_I64), 3806 NATIVE_REG_INT, next_int++); 3807 m->src = desc->args[i]; 3808 m->src_offset = 0; 3809 m->size = 8; 3810 m->is_addr = 1; 3811 } else { 3812 NativeLoc ptr = native_loc_reg(builtin_id(KIT_CG_BUILTIN_I64), 3813 NATIVE_REG_INT, AA_TMP0); 3814 aa_addr_of_loc(t, ptr, desc->args[i]); 3815 aa_store_outgoing_part(t, tail_call, stack, ptr, 8); 3816 stack += 8u; 3817 } 3818 continue; 3819 } 3820 for (u32 p = 0; p < ai->nparts; ++p) { 3821 const ABIArgPart* part = &ai->parts[p]; 3822 NativeAllocClass cls = 3823 part->cls == ABI_CLASS_FP ? NATIVE_REG_FP : NATIVE_REG_INT; 3824 if ((cls == NATIVE_REG_FP && next_fp < 8u) || 3825 (cls == NATIVE_REG_INT && next_int < 8u)) { 3826 AAArgMove* m = &moves[nmoves++]; 3827 m->dst = 3828 native_loc_reg(desc->args[i].type, cls, 3829 cls == NATIVE_REG_FP ? next_fp++ : next_int++); 3830 m->src = desc->args[i]; 3831 m->src_offset = part->src_offset; 3832 m->size = part->size; 3833 m->is_addr = 0; 3834 } else { 3835 NativeLoc tmpreg = native_loc_reg( 3836 desc->args[i].type, cls, cls == NATIVE_REG_FP ? 16u : AA_TMP0); 3837 aa_load_part(t, tmpreg, desc->args[i], part->src_offset, part->size); 3838 stack = align_up_u32(stack, aa_part_stack_align(abi, part)); 3839 aa_store_outgoing_part(t, tail_call, stack, tmpreg, part->size); 3840 stack += aa_part_stack_size(abi, part); 3841 } 3842 } 3843 } 3844 aa_emit_reg_arg_moves(t, moves, nmoves); 3845 /* Set the indirect-result register (x8) *after* the argument loads: an 3846 * argument source may have been allocated to x8, and the sret pointer load 3847 * would otherwise clobber it before it is moved into its argument 3848 * register. */ 3849 if (abi && abi->has_sret) { 3850 NativeLoc x8 = 3851 native_loc_reg(builtin_id(KIT_CG_BUILTIN_I64), NATIVE_REG_INT, 8u); 3852 if (desc->flags & CG_CALL_TAIL) { 3853 AANativeTarget* a = aa_of(t); 3854 NativeLoc saved = native_loc_stack(x8.type, a->sret_ptr_slot, 0); 3855 aa_load_part(t, x8, saved, 0, 8); 3856 } else if (desc->nresults) { 3857 aa_addr_of_loc(t, x8, desc->results[0]); 3858 } 3859 } 3860 if (preserve_indirect_callee) { 3861 NativeLoc final_callee = native_loc_reg( 3862 saved_indirect_callee.type, NATIVE_REG_INT, 3863 tail_call ? AA_TAIL_TARGET : AA_TMP0); 3864 aa_move(t, final_callee, saved_indirect_callee); 3865 plan->callee = final_callee; 3866 } 3867 } 3868 if (abi && abi->ret.kind == ABI_ARG_DIRECT && desc->nresults) { 3869 u32 nr = 0, ni = 0, nf = 0; 3870 for (u32 p = 0; p < abi->ret.nparts; ++p) { 3871 const ABIArgPart* part = &abi->ret.parts[p]; 3872 NativeAllocClass cls = 3873 part->cls == ABI_CLASS_FP ? NATIVE_REG_FP : NATIVE_REG_INT; 3874 KitCgTypeId pty = aa_part_scalar_type(part); 3875 rets[nr].src = 3876 native_loc_reg(pty, cls, cls == NATIVE_REG_FP ? nf++ : ni++); 3877 rets[nr].dst = desc->results[0]; 3878 if (rets[nr].dst.kind == NATIVE_LOC_FRAME) 3879 rets[nr].dst = native_loc_stack(pty, desc->results[0].v.frame, 3880 (i32)part->src_offset); 3881 else if (rets[nr].dst.kind == NATIVE_LOC_STACK) { 3882 rets[nr].dst.v.stack.offset += (i32)part->src_offset; 3883 rets[nr].dst.type = pty; 3884 } else if (rets[nr].dst.kind == NATIVE_LOC_ADDR) { 3885 rets[nr].dst.v.addr.offset += (i32)part->src_offset; 3886 rets[nr].dst.type = pty; 3887 } 3888 rets[nr].mem = aa_mem_for_type(t, pty, part->size); 3889 nr++; 3890 } 3891 plan->nrets = nr; 3892 } else if (abi && abi->ret.kind == ABI_ARG_IGNORE) { 3893 plan->nrets = 0; 3894 } else if (!abi && desc->nresults) { 3895 rets[0].src = native_loc_reg(desc->results[0].type, NATIVE_REG_INT, 0); 3896 rets[0].dst = desc->results[0]; 3897 rets[0].mem = aa_mem_for_type(t, desc->results[0].type, 0); 3898 plan->nrets = 1; 3899 } 3900 } 3901 3902 static void aa_ret(NativeTarget* t); 3903 3904 static void aa_emit_tail_site(NativeTarget* t, NativeLoc callee) { 3905 AANativeTarget* a = aa_of(t); 3906 if (a->frame.known_frame) { 3907 /* Frame is final: emit the tail epilogue (callee restores + frame restore + 3908 * branch) directly, exactly the words aa_apply_patches would patch in but 3909 * without the reserved NOP padding. */ 3910 AAFrameLayout L = aa_build_layout(a->frame.cum_off, a->frame.max_outgoing, 3911 a->top_home_bytes); 3912 u32 words[AA_TAIL_WORDS]; 3913 u32 n = 0; 3914 aa_words_callee_restores(a, words, AA_TAIL_WORDS, &n); 3915 aa_words_restore_frame(a, words, AA_TAIL_WORDS, &n, &L); 3916 if (n >= AA_TAIL_WORDS) aa_panic(a, "tail epilogue too large"); 3917 for (u32 i = 0; i < n; ++i) aa_emit32(t->mc, words[i]); 3918 if (callee.kind == NATIVE_LOC_REG) { 3919 aa_emit32(t->mc, aa64_br(loc_reg(callee))); 3920 } else if (callee.kind == NATIVE_LOC_GLOBAL) { 3921 u32 pos = mc_pos(t->mc); 3922 aa_emit32(t->mc, aa64_b(0)); 3923 mc_emit_reloc_at(t->mc, t->mc->section_id, pos, R_AARCH64_JUMP26, 3924 callee.v.global.sym, callee.v.global.addend, 0, 0); 3925 } else { 3926 aa_panic(a, "unsupported tail target"); 3927 } 3928 return; 3929 } 3930 /* Single-pass: reserve a worst-case region and record a patch; the callee 3931 * restores and frame restore depend on the not-yet-final frame layout. */ 3932 AAPatch* p = aa_patch_alloc(a); 3933 p->kind = AA_PATCH_TAIL; 3934 p->pos = mc_pos(t->mc); 3935 p->u.callee = callee; 3936 for (u32 i = 0; i < AA_TAIL_WORDS; ++i) aa_emit32(t->mc, 0xd503201fu); 3937 if (callee.kind == NATIVE_LOC_GLOBAL) { 3938 mc_emit_reloc_at(t->mc, t->mc->section_id, 3939 p->pos + (AA_TAIL_WORDS - 1u) * 4u, R_AARCH64_JUMP26, 3940 callee.v.global.sym, callee.v.global.addend, 0, 0); 3941 } 3942 } 3943 3944 static void aa_emit_call(NativeTarget* t, const NativeCallPhase* plan) { 3945 int is_tail = (plan->flags & CG_CALL_TAIL) != 0; 3946 if (is_tail) { 3947 if (plan->callee.kind != NATIVE_LOC_GLOBAL && 3948 plan->callee.kind != NATIVE_LOC_REG) 3949 aa_panic(aa_of(t), "unsupported tail target"); 3950 aa_emit_tail_site(t, plan->callee); 3951 return; 3952 } 3953 if (plan->callee.kind == NATIVE_LOC_GLOBAL) { 3954 aa_emit32(t->mc, aa64_bl(0)); 3955 mc_emit_reloc_at(t->mc, t->mc->section_id, mc_pos(t->mc) - 4u, 3956 R_AARCH64_CALL26, plan->callee.v.global.sym, 3957 plan->callee.v.global.addend, 0, 0); 3958 return; 3959 } 3960 if (plan->callee.kind == NATIVE_LOC_REG) { 3961 aa_emit32(t->mc, aa64_blr(loc_reg(plan->callee))); 3962 return; 3963 } 3964 aa_panic(aa_of(t), "unsupported call target"); 3965 } 3966 3967 static void aa_marshal_ret(NativeTarget* t, const CGFuncDesc* fd, 3968 const NativeLoc* value, 3969 NativeCallPhaseRet** out_rets, u32* out_nrets) { 3970 const ABIFuncInfo* abi = abi_cg_func_info(t->c->abi, fd->fn_type); 3971 NativeCallPhaseRet* rets = NULL; 3972 u32 nr = 0; 3973 if (value && abi && abi->ret.kind == ABI_ARG_INDIRECT) { 3974 AANativeTarget* a = aa_of(t); 3975 /* Hold the sret destination pointer in x8, not AA_TMP1: aa_copy_bytes 3976 * materializes out-of-range source/dest frame offsets into AA_TMP1, which 3977 * would clobber the destination base mid-copy (only triggered once a frame 3978 * is large enough that the source offset escapes stur's signed-9 range). */ 3979 NativeLoc dstp = 3980 native_loc_reg(builtin_id(KIT_CG_BUILTIN_I64), NATIVE_REG_INT, AA_X8); 3981 NativeLoc saved = native_loc_stack(dstp.type, a->sret_ptr_slot, 0); 3982 NativeAddr dst_addr, src_addr; 3983 AggregateAccess access; 3984 aa_load_part(t, dstp, saved, 0, 8); 3985 memset(&dst_addr, 0, sizeof dst_addr); 3986 dst_addr.base_kind = NATIVE_ADDR_BASE_REG; 3987 dst_addr.base.reg = AA_X8; 3988 dst_addr.base_type = value->type; 3989 src_addr = aa_storage_addr(a, *value, 0); 3990 src_addr.base_type = value->type; 3991 memset(&access, 0, sizeof access); 3992 access.type = value->type; 3993 access.size = (u32)cg_type_size(t->c, value->type); 3994 access.align = type_align32(t, value->type); 3995 aa_copy_bytes(t, dst_addr, src_addr, access); 3996 *out_rets = NULL; 3997 *out_nrets = 0; 3998 return; 3999 } 4000 if (value && abi && abi->ret.kind == ABI_ARG_DIRECT) { 4001 u32 ni = 0, nf = 0; 4002 int exact_parts = 0; 4003 for (u32 p = 0; p < abi->ret.nparts; ++p) { 4004 const ABIArgPart* part = &abi->ret.parts[p]; 4005 if (part->cls == ABI_CLASS_INT && part->size != 1u && 4006 part->size != 2u && part->size != 4u && part->size != 8u) { 4007 exact_parts = 1; 4008 break; 4009 } 4010 } 4011 4012 /* The generic return write phase materializes a memory source using the 4013 * scalar carrier type, so an I64 carrier for a 3/5/6/7-byte aggregate part 4014 * becomes an eight-byte load before NativeCallPhaseRet.mem can narrow the 4015 * final move. Once one exact-width part is present, marshal every return 4016 * part here in ABI order. aa_load_part retains the exact byte count and 4017 * uses only backend-private temporaries, so already-filled x0/x1 or v0..v3 4018 * return registers remain live while later parts are loaded. */ 4019 if (exact_parts) { 4020 for (u32 p = 0; p < abi->ret.nparts; ++p) { 4021 const ABIArgPart* part = &abi->ret.parts[p]; 4022 NativeAllocClass cls = 4023 part->cls == ABI_CLASS_FP ? NATIVE_REG_FP : NATIVE_REG_INT; 4024 KitCgTypeId pty = aa_part_scalar_type(part); 4025 Reg rreg = cls == NATIVE_REG_FP ? nf++ : ni++; 4026 NativeLoc dst = native_loc_reg(pty, cls, rreg); 4027 aa_load_part(t, dst, *value, part->src_offset, part->size); 4028 } 4029 *out_rets = NULL; 4030 *out_nrets = 0; 4031 return; 4032 } 4033 4034 rets = arena_zarray(t->c->tu, NativeCallPhaseRet, abi->ret.nparts); 4035 for (u32 p = 0; p < abi->ret.nparts; ++p) { 4036 const ABIArgPart* part = &abi->ret.parts[p]; 4037 NativeAllocClass cls = 4038 part->cls == ABI_CLASS_FP ? NATIVE_REG_FP : NATIVE_REG_INT; 4039 KitCgTypeId pty = aa_part_scalar_type(part); 4040 rets[nr].src = *value; 4041 if (rets[nr].src.kind == NATIVE_LOC_FRAME) 4042 rets[nr].src = 4043 native_loc_stack(pty, value->v.frame, (i32)part->src_offset); 4044 else if (rets[nr].src.kind == NATIVE_LOC_STACK) { 4045 rets[nr].src.v.stack.offset += (i32)part->src_offset; 4046 rets[nr].src.type = pty; 4047 } else if (rets[nr].src.kind == NATIVE_LOC_ADDR) { 4048 rets[nr].src.v.addr.offset += (i32)part->src_offset; 4049 rets[nr].src.type = pty; 4050 } 4051 rets[nr].dst = 4052 native_loc_reg(pty, cls, cls == NATIVE_REG_FP ? nf++ : ni++); 4053 rets[nr].mem = aa_mem_for_type(t, pty, part->size); 4054 nr++; 4055 } 4056 } else if (value) { 4057 rets = arena_zarray(t->c->tu, NativeCallPhaseRet, 1); 4058 rets[0].src = *value; 4059 rets[0].dst = native_loc_reg(value->type, NATIVE_REG_INT, 0); 4060 rets[0].mem = aa_mem_for_type(t, value->type, 0); 4061 nr = 1; 4062 } 4063 *out_rets = rets; 4064 *out_nrets = nr; 4065 } 4066 4067 static void aa_ret(NativeTarget* t) { 4068 AANativeTarget* a = aa_of(t); 4069 aa_jump(t, a->epilogue_label); 4070 } 4071 4072 static u32 aa_bit_storage_reg_bits(u32 storage_bytes) { 4073 return storage_bytes == 8u ? 64u : 32u; 4074 } 4075 4076 static void aa_lsl_imm(NativeTarget* t, u32 sf, u32 rd, u32 rn, u32 sh) { 4077 u32 bits = sf ? 64u : 32u; 4078 if (!sh) { 4079 if (rd != rn) aa_emit32(t->mc, aa64_mov_reg(sf, rd, rn)); 4080 return; 4081 } 4082 aa_emit32(t->mc, aa_ubfm(sf, rd, rn, bits - sh, bits - 1u - sh)); 4083 } 4084 4085 static void aa_lsr_imm(NativeTarget* t, u32 sf, u32 rd, u32 rn, u32 sh) { 4086 if (!sh) { 4087 if (rd != rn) aa_emit32(t->mc, aa64_mov_reg(sf, rd, rn)); 4088 return; 4089 } 4090 aa_emit32(t->mc, aa_ubfm(sf, rd, rn, sh, sf ? 63u : 31u)); 4091 } 4092 4093 static void aa_asr_imm(NativeTarget* t, u32 sf, u32 rd, u32 rn, u32 sh) { 4094 if (!sh) { 4095 if (rd != rn) aa_emit32(t->mc, aa64_mov_reg(sf, rd, rn)); 4096 return; 4097 } 4098 aa_emit32(t->mc, aa_sbfm(sf, rd, rn, sh, sf ? 63u : 31u)); 4099 } 4100 4101 static void aa_bitfield_load(NativeTarget* t, NativeLoc dst, NativeAddr addr, 4102 BitFieldAccess bf) { 4103 u32 storage = bf.storage.size ? bf.storage.size : 4u; 4104 u32 bits = aa_bit_storage_reg_bits(storage); 4105 u32 width = bf.bit_width ? bf.bit_width : 1u; 4106 u32 sf = bits == 64u; 4107 NativeAddr saddr = aa_addr_plus(addr, bf.storage_offset); 4108 NativeLoc tmp = dst; 4109 tmp.type = bf.storage.type ? bf.storage.type : dst.type; 4110 aa_load_native(t, tmp, saddr, bf.storage); 4111 aa_lsl_imm(t, sf, loc_reg(dst), loc_reg(dst), 4112 bits - (u32)bf.bit_offset - width); 4113 if (bf.signed_) 4114 aa_asr_imm(t, sf, loc_reg(dst), loc_reg(dst), bits - width); 4115 else 4116 aa_lsr_imm(t, sf, loc_reg(dst), loc_reg(dst), bits - width); 4117 } 4118 4119 static void aa_bitfield_store(NativeTarget* t, NativeAddr addr, NativeLoc src, 4120 BitFieldAccess bf) { 4121 u32 storage = bf.storage.size ? bf.storage.size : 4u; 4122 u32 bits = aa_bit_storage_reg_bits(storage); 4123 u32 width = bf.bit_width ? bf.bit_width : 1u; 4124 u32 sf = bits == 64u; 4125 u64 ones = width >= 64u ? ~(u64)0 : ((1ull << width) - 1ull); 4126 u64 field_mask = ones << bf.bit_offset; 4127 NativeAddr saddr = aa_addr_plus(addr, bf.storage_offset); 4128 NativeLoc word = 4129 aa_tmp_loc(bf.storage.type ? bf.storage.type : src.type, AA_TMP0); 4130 aa_load_native(t, word, saddr, bf.storage); 4131 aa_emit_load_imm(t->mc, sf, AA_TMP1, (i64)~field_mask); 4132 aa_emit32(t->mc, aa64_and(sf, AA_TMP0, AA_TMP0, AA_TMP1)); 4133 aa_emit32(t->mc, aa_ubfm(sf, AA_TMP1, loc_reg(src), 0, width - 1u)); 4134 aa_lsl_imm(t, sf, AA_TMP1, AA_TMP1, bf.bit_offset); 4135 aa_emit32(t->mc, aa64_orr(sf, AA_TMP0, AA_TMP0, AA_TMP1)); 4136 aa_store_native(t, saddr, word, bf.storage); 4137 } 4138 4139 static void aa_trap(NativeTarget* t); 4140 4141 static int aa_order_acquire(KitCgMemOrder order) { 4142 return order == KIT_CG_MO_CONSUME || order == KIT_CG_MO_ACQUIRE || 4143 order == KIT_CG_MO_ACQ_REL || order == KIT_CG_MO_SEQ_CST; 4144 } 4145 4146 static int aa_order_release(KitCgMemOrder order) { 4147 return order == KIT_CG_MO_RELEASE || order == KIT_CG_MO_ACQ_REL || 4148 order == KIT_CG_MO_SEQ_CST; 4149 } 4150 4151 static NativeLoc aa_i64_reg_loc(u32 reg) { 4152 return native_loc_reg(builtin_id(KIT_CG_BUILTIN_I64), NATIVE_REG_INT, reg); 4153 } 4154 4155 static void aa_atomic_addr_reg(NativeTarget* t, NativeAddr addr, u32 reg) { 4156 NativeLoc dst = aa_i64_reg_loc(reg); 4157 t->load_addr(t, dst, addr); 4158 } 4159 4160 static u32 aa_saved_tmp_pick(u32 a, u32 b, u32 c) { 4161 static const u32 regs[] = {11u, 12u, 13u, 14u, 15u}; 4162 for (u32 i = 0; i < sizeof regs / sizeof regs[0]; ++i) { 4163 if (regs[i] != a && regs[i] != b && regs[i] != c) return regs[i]; 4164 } 4165 return 15u; 4166 } 4167 4168 static void aa_saved_tmp_spill(AANativeTarget* a, u32 reg) { 4169 NativeFrameSlotDesc sd; 4170 NativeAddr addr; 4171 MemAccess mem; 4172 memset(&sd, 0, sizeof sd); 4173 if (a->saved_tmp_slot == NATIVE_FRAME_SLOT_NONE) { 4174 sd.type = builtin_id(KIT_CG_BUILTIN_I64); 4175 sd.size = 8; 4176 sd.align = 8; 4177 sd.kind = NATIVE_FRAME_SLOT_SPILL; 4178 a->saved_tmp_slot = a->base.frame_slot(&a->base, &sd); 4179 } 4180 memset(&addr, 0, sizeof addr); 4181 addr.base_kind = NATIVE_ADDR_BASE_FRAME; 4182 addr.base.frame = a->saved_tmp_slot; 4183 addr.base_type = builtin_id(KIT_CG_BUILTIN_I64); 4184 mem = aa_mem_for_type(&a->base, addr.base_type, 8); 4185 aa_store_native(&a->base, addr, aa_i64_reg_loc(reg), mem); 4186 } 4187 4188 static void aa_saved_tmp_restore(AANativeTarget* a, u32 reg) { 4189 NativeAddr addr; 4190 MemAccess mem; 4191 memset(&addr, 0, sizeof addr); 4192 addr.base_kind = NATIVE_ADDR_BASE_FRAME; 4193 addr.base.frame = a->saved_tmp_slot; 4194 addr.base_type = builtin_id(KIT_CG_BUILTIN_I64); 4195 mem = aa_mem_for_type(&a->base, addr.base_type, 8); 4196 aa_load_native(&a->base, aa_i64_reg_loc(reg), addr, mem); 4197 } 4198 4199 static void aa_atomic_load(NativeTarget* t, NativeLoc dst, NativeAddr addr, 4200 MemAccess mem, KitCgMemOrder order) { 4201 u32 base = AA_TMP0; 4202 u32 sz = size_idx(mem.size ? mem.size : loc_size32(t, dst)); 4203 aa_atomic_addr_reg(t, addr, base); 4204 aa_emit32(t->mc, aa_order_acquire(order) 4205 ? aa_ldar(sz, loc_reg(dst), base) 4206 : aa_ldr_uimm(sz, loc_reg(dst), base, 0)); 4207 if (order == KIT_CG_MO_SEQ_CST) 4208 aa_emit32(t->mc, aa64_dmb(AA64_BARRIER_OPT_ISH)); 4209 } 4210 4211 static void aa_atomic_store(NativeTarget* t, NativeAddr addr, NativeLoc src, 4212 MemAccess mem, KitCgMemOrder order) { 4213 u32 base = AA_TMP0; 4214 u32 sz = size_idx(mem.size ? mem.size : loc_size32(t, src)); 4215 if (order == KIT_CG_MO_SEQ_CST) 4216 aa_emit32(t->mc, aa64_dmb(AA64_BARRIER_OPT_ISH)); 4217 aa_atomic_addr_reg(t, addr, base); 4218 aa_emit32(t->mc, aa_order_release(order) 4219 ? aa_stlr(sz, loc_reg(src), base) 4220 : aa_str_uimm(sz, loc_reg(src), base, 0)); 4221 if (order == KIT_CG_MO_SEQ_CST) 4222 aa_emit32(t->mc, aa64_dmb(AA64_BARRIER_OPT_ISH)); 4223 } 4224 4225 static void aa_atomic_rmw(NativeTarget* t, KitCgAtomicOp op, NativeLoc dst, 4226 NativeAddr addr, NativeLoc val, MemAccess mem, 4227 KitCgMemOrder order) { 4228 AANativeTarget* a = aa_of(t); 4229 u32 base = AA_TMP0; 4230 u32 next_reg = AA_TMP1; 4231 u32 status = aa_saved_tmp_pick(loc_reg(dst), loc_reg(val), base); 4232 NativeLoc next = aa_tmp_loc(dst.type, next_reg); 4233 MCLabel retry = mc_label_new(t->mc); 4234 u32 sz = size_idx(mem.size ? mem.size : loc_size32(t, dst)); 4235 if (order == KIT_CG_MO_SEQ_CST) 4236 aa_emit32(t->mc, aa64_dmb(AA64_BARRIER_OPT_ISH)); 4237 aa_saved_tmp_spill(a, status); 4238 aa_atomic_addr_reg(t, addr, base); 4239 mc_label_place(t->mc, retry); 4240 aa_emit32(t->mc, aa_order_acquire(order) ? aa_ldaxr(sz, loc_reg(dst), base) 4241 : aa_ldxr(sz, loc_reg(dst), base)); 4242 switch (op) { 4243 case KIT_CG_ATOMIC_XCHG: 4244 aa_move(t, next, val); 4245 break; 4246 case KIT_CG_ATOMIC_ADD: 4247 aa_binop(t, BO_IADD, next, dst, val); 4248 break; 4249 case KIT_CG_ATOMIC_SUB: 4250 aa_binop(t, BO_ISUB, next, dst, val); 4251 break; 4252 case KIT_CG_ATOMIC_AND: 4253 aa_binop(t, BO_AND, next, dst, val); 4254 break; 4255 case KIT_CG_ATOMIC_OR: 4256 aa_binop(t, BO_OR, next, dst, val); 4257 break; 4258 case KIT_CG_ATOMIC_XOR: 4259 aa_binop(t, BO_XOR, next, dst, val); 4260 break; 4261 case KIT_CG_ATOMIC_NAND: 4262 aa_binop(t, BO_AND, next, dst, val); 4263 aa_unop(t, UO_BNOT, next, next); 4264 break; 4265 default: 4266 aa_panic(a, "unsupported atomic rmw op"); 4267 } 4268 aa_emit32(t->mc, aa_order_release(order) 4269 ? aa_stlxr(sz, status, next_reg, base) 4270 : aa_stxr(sz, status, next_reg, base)); 4271 aa_emit32(t->mc, aa64_cbnz_imm(0, status, 0)); 4272 mc_emit_label_ref(t->mc, retry, R_AARCH64_CONDBR19, 4, 0); 4273 aa_saved_tmp_restore(a, status); 4274 if (order == KIT_CG_MO_SEQ_CST) 4275 aa_emit32(t->mc, aa64_dmb(AA64_BARRIER_OPT_ISH)); 4276 } 4277 4278 static void aa_atomic_cas(NativeTarget* t, NativeLoc prior, NativeLoc ok, 4279 NativeAddr addr, NativeLoc expected, 4280 NativeLoc desired, MemAccess mem, 4281 KitCgMemOrder success, KitCgMemOrder failure) { 4282 u32 base = AA_TMP0; 4283 u32 status = AA_TMP1; 4284 u32 sz = size_idx(mem.size ? mem.size : loc_size32(t, prior)); 4285 u32 sf = sz == 3u; 4286 int acquire = aa_order_acquire(success) || aa_order_acquire(failure); 4287 int release = aa_order_release(success); 4288 MCLabel retry = mc_label_new(t->mc); 4289 MCLabel fail = mc_label_new(t->mc); 4290 MCLabel done = mc_label_new(t->mc); 4291 if (success == KIT_CG_MO_SEQ_CST || failure == KIT_CG_MO_SEQ_CST) 4292 aa_emit32(t->mc, aa64_dmb(AA64_BARRIER_OPT_ISH)); 4293 aa_atomic_addr_reg(t, addr, base); 4294 mc_label_place(t->mc, retry); 4295 aa_emit32(t->mc, acquire ? aa_ldaxr(sz, loc_reg(prior), base) 4296 : aa_ldxr(sz, loc_reg(prior), base)); 4297 aa_emit32(t->mc, aa_subs_reg(sf, AA64_ZR, loc_reg(prior), loc_reg(expected))); 4298 aa_emit32(t->mc, aa64_brcond_pack((AA64BrCond){.cond = cmp_cond(CMP_NE)})); 4299 mc_emit_label_ref(t->mc, fail, R_AARCH64_CONDBR19, 4, 0); 4300 aa_emit32(t->mc, release ? aa_stlxr(sz, status, loc_reg(desired), base) 4301 : aa_stxr(sz, status, loc_reg(desired), base)); 4302 aa_emit32(t->mc, aa64_cbnz_imm(0, status, 0)); 4303 mc_emit_label_ref(t->mc, retry, R_AARCH64_CONDBR19, 4, 0); 4304 aa_emit_load_imm(t->mc, loc_is_64(t, ok), loc_reg(ok), 1); 4305 aa_jump(t, done); 4306 mc_label_place(t->mc, fail); 4307 aa_emit32(t->mc, aa64_clrex(AA64_BARRIER_OPT_SY)); 4308 aa_emit_load_imm(t->mc, loc_is_64(t, ok), loc_reg(ok), 0); 4309 mc_label_place(t->mc, done); 4310 if (success == KIT_CG_MO_SEQ_CST || failure == KIT_CG_MO_SEQ_CST) 4311 aa_emit32(t->mc, aa64_dmb(AA64_BARRIER_OPT_ISH)); 4312 } 4313 4314 static void aa_fence(NativeTarget* t, KitCgMemOrder order) { 4315 if (order != KIT_CG_MO_RELAXED) 4316 aa_emit32(t->mc, aa64_dmb(AA64_BARRIER_OPT_ISH)); 4317 } 4318 4319 /* Map a KitCgBarrierScope (passed as an immediate arg to DMB/DSB) onto an 4320 * AArch64 barrier domain option. Defaults to full-system (SY) when the scope 4321 * is absent or unrecognized. */ 4322 static u32 aa_barrier_opt(const NativeLoc* args, u32 narg) { 4323 if (narg < 1u || args[0].kind != NATIVE_LOC_IMM) return AA64_BARRIER_OPT_SY; 4324 switch ((KitCgBarrierScope)args[0].v.imm) { 4325 case KIT_CG_BARRIER_FULL: 4326 return AA64_BARRIER_OPT_SY; 4327 case KIT_CG_BARRIER_INNER: 4328 return AA64_BARRIER_OPT_ISH; 4329 case KIT_CG_BARRIER_INNER_STORE: 4330 return AA64_BARRIER_OPT_ISHST; 4331 case KIT_CG_BARRIER_OUTER: 4332 return AA64_BARRIER_OPT_OSH; 4333 case KIT_CG_BARRIER_OUTER_STORE: 4334 return AA64_BARRIER_OPT_OSHST; 4335 case KIT_CG_BARRIER_NON_SHARE: 4336 return AA64_BARRIER_OPT_NSH; 4337 } 4338 return AA64_BARRIER_OPT_SY; 4339 } 4340 4341 static void aa_intrinsic(NativeTarget* t, IntrinKind kind, 4342 const NativeLoc* dsts, u32 ndst, const NativeLoc* args, 4343 u32 narg) { 4344 AggregateAccess access; 4345 NativeAddr dst_addr; 4346 NativeAddr src_addr; 4347 memset(&access, 0, sizeof access); 4348 memset(&dst_addr, 0, sizeof dst_addr); 4349 memset(&src_addr, 0, sizeof src_addr); 4350 switch (kind) { 4351 case INTRIN_NONE: 4352 if (ndst == 1u && narg == 3u && native_loc_is_fp(dsts[0])) { 4353 u32 d = type_size32(t, dsts[0].type) == 8u; 4354 aa_emit32(t->mc, aa_fp_bin(0x000800u, d, loc_reg(dsts[0]), 4355 loc_reg(args[0]), loc_reg(args[1]))); 4356 aa_emit32(t->mc, aa_fp_bin(0x002800u, d, loc_reg(dsts[0]), 4357 loc_reg(dsts[0]), loc_reg(args[2]))); 4358 return; 4359 } 4360 break; 4361 case INTRIN_CLZ: 4362 if (ndst == 1u && narg == 1u) { 4363 aa_emit32(t->mc, aa_clz(loc_is_64(t, args[0]), loc_reg(dsts[0]), 4364 loc_reg(args[0]))); 4365 return; 4366 } 4367 break; 4368 case INTRIN_CTZ: 4369 if (ndst == 1u && narg == 1u) { 4370 u32 sf = loc_is_64(t, args[0]); 4371 aa_emit32(t->mc, aa_rbit(sf, loc_reg(dsts[0]), loc_reg(args[0]))); 4372 aa_emit32(t->mc, aa_clz(sf, loc_reg(dsts[0]), loc_reg(dsts[0]))); 4373 return; 4374 } 4375 break; 4376 case INTRIN_POPCOUNT: 4377 if (ndst == 1u && narg == 1u) { 4378 u32 sf = loc_is_64(t, args[0]); 4379 u32 rd = loc_reg(dsts[0]); 4380 u32 rn = loc_reg(args[0]); 4381 MCLabel loop = mc_label_new(t->mc); 4382 MCLabel done = mc_label_new(t->mc); 4383 aa_emit_load_imm(t->mc, sf, rd, 0); 4384 aa_emit32(t->mc, aa64_mov_reg(sf, AA_TMP0, rn)); 4385 mc_label_place(t->mc, loop); 4386 aa_emit32(t->mc, aa64_cbz(sf, AA_TMP0, 0)); 4387 mc_emit_label_ref(t->mc, done, R_AARCH64_CONDBR19, 4, 0); 4388 aa_emit_load_imm(t->mc, sf, AA_TMP1, 1); 4389 aa_emit32(t->mc, aa64_and(sf, AA_TMP1, AA_TMP0, AA_TMP1)); 4390 aa_emit32(t->mc, aa64_add(sf, rd, rd, AA_TMP1)); 4391 aa_emit_load_imm(t->mc, sf, AA_TMP1, 1); 4392 aa_emit32(t->mc, aa64_lsrv(sf, AA_TMP0, AA_TMP0, AA_TMP1)); 4393 aa_jump(t, loop); 4394 mc_label_place(t->mc, done); 4395 return; 4396 } 4397 break; 4398 case INTRIN_BSWAP: 4399 if (ndst == 1u && narg == 1u) { 4400 u32 width = abi_cg_sizeof(t->c->abi, dsts[0].type); 4401 switch (width) { 4402 case 2: { 4403 u32 sf = 0; 4404 aa_emit32(t->mc, aa_rev(sf, loc_reg(dsts[0]), loc_reg(args[0]))); 4405 aa_emit_load_imm(t->mc, 0, AA_TMP0, 16); 4406 aa_emit32(t->mc, aa64_lsrv(0, loc_reg(dsts[0]), loc_reg(dsts[0]), 4407 AA_TMP0)); 4408 return; 4409 } 4410 case 4: { 4411 u32 sf = 0; 4412 aa_emit32(t->mc, aa_rev(sf, loc_reg(dsts[0]), loc_reg(args[0]))); 4413 return; 4414 } 4415 case 8: { 4416 u32 sf = 1; 4417 aa_emit32(t->mc, aa_rev(sf, loc_reg(dsts[0]), loc_reg(args[0]))); 4418 return; 4419 } 4420 default: 4421 break; 4422 } 4423 } 4424 break; 4425 case INTRIN_SADD_OVERFLOW: 4426 case INTRIN_UADD_OVERFLOW: 4427 case INTRIN_SSUB_OVERFLOW: 4428 case INTRIN_USUB_OVERFLOW: 4429 if (ndst == 2u && narg == 2u) { 4430 u32 sf = loc_is_64(t, dsts[0]); 4431 u32 rd = loc_reg(dsts[0]); 4432 if (kind == INTRIN_SADD_OVERFLOW || kind == INTRIN_UADD_OVERFLOW) 4433 aa_emit32(t->mc, 4434 aa64_addsubsr_pack((AA64AddSubSR){.sf = sf, 4435 .op = 0, 4436 .S = 1, 4437 .Rm = loc_reg(args[1]), 4438 .Rn = loc_reg(args[0]), 4439 .Rd = rd})); 4440 else 4441 aa_emit32(t->mc, 4442 aa64_addsubsr_pack((AA64AddSubSR){.sf = sf, 4443 .op = 1, 4444 .S = 1, 4445 .Rm = loc_reg(args[1]), 4446 .Rn = loc_reg(args[0]), 4447 .Rd = rd})); 4448 aa_emit32(t->mc, 4449 aa_cset(loc_is_64(t, dsts[1]), loc_reg(dsts[1]), 4450 (kind == INTRIN_SADD_OVERFLOW || 4451 kind == INTRIN_SSUB_OVERFLOW) 4452 ? 0x6u 4453 : (kind == INTRIN_UADD_OVERFLOW ? 0x2u : 0x3u))); 4454 return; 4455 } 4456 break; 4457 case INTRIN_SMUL_OVERFLOW: 4458 case INTRIN_UMUL_OVERFLOW: 4459 if (ndst == 2u && narg == 2u) { 4460 u32 sf = loc_is_64(t, dsts[0]); 4461 if (sf) { 4462 if (kind == INTRIN_SMUL_OVERFLOW) { 4463 aa_emit32(t->mc, 4464 aa_smulh(AA_TMP0, loc_reg(args[0]), loc_reg(args[1]))); 4465 aa_emit32(t->mc, aa64_mul(1, loc_reg(dsts[0]), loc_reg(args[0]), 4466 loc_reg(args[1]))); 4467 aa_emit32(t->mc, aa_sbfm(1, AA_TMP1, loc_reg(dsts[0]), 63, 63)); 4468 aa_emit32(t->mc, aa_subs_reg(1, AA64_ZR, AA_TMP0, AA_TMP1)); 4469 aa_emit32(t->mc, aa_cset(0, loc_reg(dsts[1]), cmp_cond(CMP_NE))); 4470 } else { 4471 aa_emit32(t->mc, 4472 aa_umulh(AA_TMP0, loc_reg(args[0]), loc_reg(args[1]))); 4473 aa_emit32(t->mc, aa64_mul(1, loc_reg(dsts[0]), loc_reg(args[0]), 4474 loc_reg(args[1]))); 4475 aa_emit32(t->mc, aa_subs_reg(1, AA64_ZR, AA_TMP0, AA64_ZR)); 4476 aa_emit32(t->mc, aa_cset(0, loc_reg(dsts[1]), cmp_cond(CMP_NE))); 4477 } 4478 } else if (kind == INTRIN_SMUL_OVERFLOW) { 4479 aa_emit32(t->mc, aa_smaddl(AA_TMP0, loc_reg(args[0]), 4480 loc_reg(args[1]), AA64_ZR)); 4481 aa_emit32(t->mc, aa64_mov_reg(0, loc_reg(dsts[0]), AA_TMP0)); 4482 aa_emit32(t->mc, aa_sbfm(1, AA_TMP1, loc_reg(dsts[0]), 0, 31)); 4483 aa_emit32(t->mc, aa_subs_reg(1, AA64_ZR, AA_TMP0, AA_TMP1)); 4484 aa_emit32(t->mc, aa_cset(0, loc_reg(dsts[1]), cmp_cond(CMP_NE))); 4485 } else { 4486 aa_emit32(t->mc, aa_umaddl(AA_TMP0, loc_reg(args[0]), 4487 loc_reg(args[1]), AA64_ZR)); 4488 aa_emit32(t->mc, aa64_mov_reg(0, loc_reg(dsts[0]), AA_TMP0)); 4489 aa_emit_load_imm(t->mc, 1, AA_TMP1, 32); 4490 aa_emit32(t->mc, aa64_lsrv(1, AA_TMP1, AA_TMP0, AA_TMP1)); 4491 aa_emit32(t->mc, aa_subs_reg(1, AA64_ZR, AA_TMP1, AA64_ZR)); 4492 aa_emit32(t->mc, aa_cset(0, loc_reg(dsts[1]), cmp_cond(CMP_NE))); 4493 } 4494 return; 4495 } 4496 break; 4497 case INTRIN_SMUL_HIGH: 4498 case INTRIN_UMUL_HIGH: 4499 if (ndst == 1u && narg == 2u) { 4500 u32 sf = loc_is_64(t, dsts[0]); 4501 u32 rd = loc_reg(dsts[0]); 4502 u32 rn = loc_reg(args[0]); 4503 u32 rm = loc_reg(args[1]); 4504 if (sf) { 4505 aa_emit32(t->mc, kind == INTRIN_SMUL_HIGH 4506 ? aa_smulh(rd, rn, rm) 4507 : aa_umulh(rd, rn, rm)); 4508 } else { 4509 aa_emit32(t->mc, kind == INTRIN_SMUL_HIGH 4510 ? aa_smaddl(AA_TMP0, rn, rm, AA64_ZR) 4511 : aa_umaddl(AA_TMP0, rn, rm, AA64_ZR)); 4512 if (kind == INTRIN_SMUL_HIGH) 4513 aa_asr_imm(t, 1, rd, AA_TMP0, 32); 4514 else 4515 aa_lsr_imm(t, 1, rd, AA_TMP0, 32); 4516 } 4517 return; 4518 } 4519 break; 4520 case INTRIN_MEMMOVE: { 4521 MCLabel forward = mc_label_new(t->mc); 4522 MCLabel done = mc_label_new(t->mc); 4523 if (narg != 3u || args[0].kind != NATIVE_LOC_REG || 4524 args[1].kind != NATIVE_LOC_REG || args[2].kind != NATIVE_LOC_IMM) 4525 aa_panic(aa_of(t), "unsupported memory intrinsic operands"); 4526 if (args[2].v.imm < 0 || args[2].v.imm > 0xffffffffll) 4527 aa_panic(aa_of(t), "unsupported memory intrinsic size"); 4528 access.size = (u32)args[2].v.imm; 4529 access.align = 1u; 4530 dst_addr.base_kind = NATIVE_ADDR_BASE_REG; 4531 dst_addr.base.reg = args[0].v.reg; 4532 src_addr.base_kind = NATIVE_ADDR_BASE_REG; 4533 src_addr.base.reg = args[1].v.reg; 4534 aa_emit32(t->mc, aa_subs_reg(1, AA64_ZR, args[0].v.reg, args[1].v.reg)); 4535 aa_emit32(t->mc, 4536 aa64_brcond_pack((AA64BrCond){.cond = cmp_cond(CMP_LT_U)})); 4537 mc_emit_label_ref(t->mc, forward, R_AARCH64_CONDBR19, 4, 0); 4538 aa_copy_bytes_dir(t, dst_addr, src_addr, access, 1); 4539 aa_jump(t, done); 4540 mc_label_place(t->mc, forward); 4541 aa_copy_bytes_dir(t, dst_addr, src_addr, access, 0); 4542 mc_label_place(t->mc, done); 4543 return; 4544 } 4545 case INTRIN_EXPECT: 4546 case INTRIN_ASSUME_ALIGNED: 4547 if (ndst == 1u && narg >= 1u) { 4548 if (args[0].kind == NATIVE_LOC_IMM) 4549 aa_load_imm_native(t, dsts[0], args[0].v.imm); 4550 else 4551 aa_move(t, dsts[0], args[0]); 4552 } 4553 return; 4554 case INTRIN_PREFETCH: 4555 if (narg >= 1u && args[0].kind == NATIVE_LOC_REG) { 4556 /* GCC locality 0..3 maps to streaming L1, then L3/L2/L1 keep. PST 4557 * differs from PLD by bit 3 of prfop; both are baseline A64 hints. */ 4558 static const u8 op[4] = {1u, 4u, 2u, 0u}; 4559 u32 rw = 0u; 4560 u32 locality = 3u; 4561 if (narg >= 2u && args[1].kind == NATIVE_LOC_IMM) 4562 rw = (u32)args[1].v.imm; 4563 if (narg >= 3u && args[2].kind == NATIVE_LOC_IMM) 4564 locality = (u32)args[2].v.imm; 4565 if (locality > 3u) locality = 3u; 4566 aa_emit32(t->mc, aa_prfm(op[locality] | (rw ? 8u : 0u), 4567 loc_reg(args[0]))); 4568 } 4569 return; 4570 case INTRIN_TRAP: 4571 aa_trap(t); 4572 return; 4573 case INTRIN_SYSCALL: 4574 if (ndst == 1u && narg >= 1u && narg <= 7u) { 4575 static const u32 syscall_regs[7] = {AA_X8, 0u, 1u, 2u, 3u, 4u, 5u}; 4576 AAArgMove moves[7]; 4577 for (u32 i = 0; i < narg; ++i) { 4578 AAArgMove* m = &moves[i]; 4579 memset(m, 0, sizeof *m); 4580 m->dst = 4581 native_loc_reg(dsts[0].type, NATIVE_REG_INT, syscall_regs[i]); 4582 m->src = args[i]; 4583 m->size = t->c->target.ptr_size; 4584 } 4585 aa_emit_reg_arg_moves(t, moves, narg); 4586 aa_emit32(t->mc, aa64_svc(0)); 4587 aa_move(t, dsts[0], native_loc_reg(dsts[0].type, NATIVE_REG_INT, 0)); 4588 } 4589 return; 4590 case INTRIN_READCYCLECOUNTER: 4591 /* MRS Xd, CNTVCT_EL0 (op0=3 op1=3 CRn=14 CRm=0 op2=2): the EL0-readable 4592 * virtual counter. Prefer it over PMCCNTR_EL0, which traps at EL0 unless 4593 * the PMU is explicitly enabled for userspace. */ 4594 if (ndst == 1u) 4595 aa_emit32(t->mc, aa64_sysreg_move(1, 3, 3, 14, 0, 2, loc_reg(dsts[0]))); 4596 return; 4597 case INTRIN_CPU_NOP: 4598 aa_emit32(t->mc, aa64_hint(AA64_HINT_OP_NOP)); 4599 return; 4600 case INTRIN_CPU_YIELD: 4601 aa_emit32(t->mc, aa64_hint(AA64_HINT_OP_YIELD)); 4602 return; 4603 case INTRIN_WFI: 4604 aa_emit32(t->mc, aa64_hint(AA64_HINT_OP_WFI)); 4605 return; 4606 case INTRIN_WFE: 4607 aa_emit32(t->mc, aa64_hint(AA64_HINT_OP_WFE)); 4608 return; 4609 case INTRIN_SEV: 4610 aa_emit32(t->mc, aa64_hint(AA64_HINT_OP_SEV)); 4611 return; 4612 case INTRIN_ISB: 4613 aa_emit32(t->mc, aa64_isb(AA64_BARRIER_OPT_SY)); 4614 return; 4615 case INTRIN_DMB: 4616 aa_emit32(t->mc, aa64_dmb(aa_barrier_opt(args, narg))); 4617 return; 4618 case INTRIN_DSB: 4619 aa_emit32(t->mc, aa64_dsb(aa_barrier_opt(args, narg))); 4620 return; 4621 case INTRIN_IRQ_SAVE: 4622 /* Read the interrupt-mask state, then mask D,A,I,F. */ 4623 if (ndst == 1u) { 4624 aa_emit32(t->mc, aa64_mrs_daif(loc_reg(dsts[0]))); 4625 aa_emit32(t->mc, aa64_msr_daifset(AA64_DAIF_ALL)); 4626 } 4627 return; 4628 case INTRIN_IRQ_RESTORE: 4629 if (narg == 1u) aa_emit32(t->mc, aa64_msr_daif(loc_reg(args[0]))); 4630 return; 4631 case INTRIN_IRQ_DISABLE: 4632 aa_emit32(t->mc, aa64_msr_daifset(AA64_DAIF_ALL)); 4633 return; 4634 case INTRIN_IRQ_ENABLE: 4635 aa_emit32(t->mc, aa64_msr_daifclr(AA64_DAIF_ALL)); 4636 return; 4637 case INTRIN_FRAME_ADDRESS: 4638 case INTRIN_RETURN_ADDRESS: 4639 /* Walk the AAPCS64 frame-record chain. Every kit prologue stores 4640 * {x29, x30} and anchors x29 at the record: [x29] = caller's x29, 4641 * [x29 + 8] = saved x30 (this frame's return address). The level is a 4642 * compile-time constant, so the walk unrolls to `level` dependent loads. 4643 */ 4644 if (ndst == 1u) { 4645 u32 level = (narg >= 1u && args[0].kind == NATIVE_LOC_IMM) 4646 ? (u32)args[0].v.imm 4647 : 0u; 4648 u32 rd = loc_reg(dsts[0]); 4649 aa_emit32(t->mc, aa64_mov_reg(1, rd, AA_FP)); 4650 for (u32 i = 0; i < level; ++i) 4651 aa_emit32(t->mc, aa64_ldr64_uimm12(rd, rd, 0)); /* rd = *(rd) */ 4652 if (kind == INTRIN_RETURN_ADDRESS) 4653 aa_emit32(t->mc, aa64_ldr64_uimm12(rd, rd, 1)); /* rd = *(rd + 8) */ 4654 } 4655 return; 4656 default: 4657 aa_panic(aa_of(t), "unsupported compiler intrinsic"); 4658 } 4659 } 4660 4661 static void aa_trap(NativeTarget* t) { aa_emit32(t->mc, aa64_brk(0)); } 4662 4663 /* file_scope_asm + finalize are shared (cg/native_asm.h). */ 4664 4665 static int aa_machine_op_clobbers(NativeTarget* t, const NativeMachineOp* op, 4666 u32 mask[NATIVE_REG_CLASS_COUNT]) { 4667 mask[0] = mask[1] = mask[2] = 0; 4668 switch ((NativeMachineOpKind)op->kind) { 4669 case NATIVE_MOP_TLS_ADDR: 4670 /* ELF Local-Exec materializes the address using only the destination 4671 * register. Mach-O TLV calls its resolver through private x16/x17/LR; 4672 * only x0 can hold an optimizer-owned value at this boundary. */ 4673 if (!obj_format_tls_via_descriptor(t->c)) return 0; 4674 mask[NATIVE_REG_INT] = 1u << 0; 4675 return 1; 4676 case NATIVE_MOP_INTRINSIC: 4677 if ((IntrinKind)op->intrin != INTRIN_SYSCALL) return 0; 4678 mask[NATIVE_REG_INT] = (1u << 0) | (1u << 1) | (1u << 2) | (1u << 3) | 4679 (1u << 4) | (1u << 5) | (1u << AA_X8); 4680 return 1; 4681 case NATIVE_MOP_BINOP: 4682 case NATIVE_MOP_BITFIELD_LOAD: 4683 case NATIVE_MOP_BITFIELD_STORE: 4684 case NATIVE_MOP_VA_START: 4685 case NATIVE_MOP_VA_ARG: 4686 case NATIVE_MOP_ATOMIC_CAS: 4687 case NATIVE_MOP_ATOMIC_RMW: 4688 return 0; 4689 case NATIVE_MOP_COUNT: 4690 break; 4691 } 4692 aa_panic(aa_of(t), "invalid machine-effect operation"); 4693 return 0; 4694 } 4695 4696 static void aa_set_loc(NativeTarget* t, SrcLoc loc) { 4697 AANativeTarget* a = aa_of(t); 4698 a->loc = loc; 4699 if (t->mc) mc_set_loc(t->mc, loc); 4700 } 4701 4702 static void aa_bind_native_param(NativeTarget* t, const CGParamDesc* p, 4703 NativeLoc dst); 4704 4705 /* ABI argument/return registers (x0..x7) come FIRST so the -O0 value cache 4706 * prefers them: an argument expression then computes straight into its ABI arg 4707 * register and the per-call parallel-copy (native_arg_shuffle) collapses to 4708 * no-ops — the tcc get_reg(0..N) trick (mirrors RV's NDT a0..a7 bank and 4709 * x64's leading rsi/rdi). They are caller-saved, so the deferred prologue stays 4710 * callee-save-free. Incoming params are spilled to frame homes at entry before 4711 * any body op (aa_bind_native_param), so caching in x0..x7 cannot clobber a 4712 * live parameter. Next come the remaining caller-saved temps x8/x12..x15; 4713 * callee-saved x19..x28 are appended and only chosen under register pressure, 4714 * after which the prologue saves/restores them. x9/x10/x11 are the explicit O1 4715 * operand-temp bank (also the O0 scratch policy) and stay out of this pool. */ 4716 static const Reg aa_ndt_int_allocable[] = { 4717 0u, 1u, 2u, 3u, 4u, 5u, 6u, 7u, 8u, 12u, 13u, 14u, 4718 15u, 19u, 20u, 21u, 22u, 23u, 24u, 25u, 26u, 27u, 28u}; 4719 /* Three O1 operand temporaries, not two: a 3-operand op (for example 4720 * `binop dst, a, b` or `store [base+index], value`) can require three distinct 4721 * materializations in one instruction scope. Location MIR leaves spilled 4722 * operands in their frame homes until this point; the scope leases x9/x10/x11 4723 * without inventing persistent MIR register values. All three are caller-saved 4724 * and reserved out of the allocable set above. The O0 direct target happens to 4725 * use the same bank through its independent `scratch` policy. */ 4726 static const Reg aa_int_scratch[] = {9u, 10u, 11u}; 4727 static const Reg aa_int_asm_temps[] = {AA_TMP0, AA_TMP1}; 4728 static const Reg aa_direct_asm_int[] = {0u, 1u, 2u, 3u, 4u, 5u, 6u, 4729 7u, 8u, 11u, 12u, 13u, 14u, 15u}; 4730 /* ABI fp argument/return registers (v0..v7) come FIRST (same rationale as the 4731 * int pool: fp args land in place, the shuffle is a no-op). Then the caller- 4732 * saved fp temps v18/v19, v22..v31 — the rest of the -O0 value cache under 4733 * ndt_caller_saved_only. v16 is a backend-private/arg-shuffle cycle temporary; 4734 * v20/v21 are the O1 operand-temp bank and the O0 per-op scratch policy, so all 4735 * three stay out of the pool. Callee-saved v8..v15 (AAPCS64) are appended 4736 * like the int set; the caller-saved-only NDT never selects them. */ 4737 static const Reg aa_ndt_fp_allocable[] = { 4738 0u, 1u, 2u, 3u, 4u, 5u, 6u, 7u, 18u, 19u, 22u, 23u, 24u, 25u, 4739 26u, 27u, 28u, 29u, 30u, 31u, 8u, 9u, 10u, 11u, 12u, 13u, 14u, 15u}; 4740 static const Reg aa_fp_scratch[] = {20u, 21u}; 4741 static const Reg aa_direct_asm_fp[] = { 4742 0u, 1u, 2u, 3u, 4u, 5u, 6u, 7u, 16u, 17u, 18u, 4743 19u, 22u, 23u, 24u, 25u, 26u, 27u, 28u, 29u, 30u, 31u}; 4744 4745 #define AA_PHYS_INT_ALLOC(r) \ 4746 {.reg = (r), \ 4747 .cls = NATIVE_REG_INT, \ 4748 .abi_index = 0xffu, \ 4749 .flags = NATIVE_REG_ALLOCABLE | NATIVE_REG_CALLER_SAVED, \ 4750 .spill_cost = 1u, \ 4751 .copy_cost = 1u} 4752 #define AA_PHYS_INT_CALLER(r) \ 4753 {.reg = (r), \ 4754 .cls = NATIVE_REG_INT, \ 4755 .abi_index = 0xffu, \ 4756 .flags = NATIVE_REG_CALLER_SAVED, \ 4757 .spill_cost = 1u, \ 4758 .copy_cost = 1u} 4759 #define AA_PHYS_INT_ARG(r) \ 4760 {.reg = (r), \ 4761 .cls = NATIVE_REG_INT, \ 4762 .abi_index = (r), \ 4763 .flags = NATIVE_REG_CALLER_SAVED | NATIVE_REG_ARG | \ 4764 ((r) < 2u ? NATIVE_REG_RET : 0), \ 4765 .spill_cost = 1u, \ 4766 .copy_cost = 1u} 4767 #define AA_PHYS_INT_CALLEE(r) \ 4768 {.reg = (r), \ 4769 .cls = NATIVE_REG_INT, \ 4770 .abi_index = 0xffu, \ 4771 .flags = NATIVE_REG_ALLOCABLE | NATIVE_REG_CALLEE_SAVED, \ 4772 .spill_cost = 4u, \ 4773 .copy_cost = 1u} 4774 #define AA_PHYS_INT_RESERVED_ROLE(r, role) \ 4775 {.reg = (r), \ 4776 .cls = NATIVE_REG_INT, \ 4777 .abi_index = 0xffu, \ 4778 .flags = NATIVE_REG_RESERVED | (role), \ 4779 .spill_cost = 0u, \ 4780 .copy_cost = 0u} 4781 #define AA_PHYS_INT_RESERVED(r) \ 4782 AA_PHYS_INT_RESERVED_ROLE((r), NATIVE_REG_NONE) 4783 #define AA_PHYS_INT_RESERVED_CALLER(r) \ 4784 AA_PHYS_INT_RESERVED_ROLE((r), NATIVE_REG_CALLER_SAVED) 4785 4786 static const NativePhysRegInfo aa_int_phys[] = { 4787 AA_PHYS_INT_ARG(0u), AA_PHYS_INT_ARG(1u), 4788 AA_PHYS_INT_ARG(2u), AA_PHYS_INT_ARG(3u), 4789 AA_PHYS_INT_ARG(4u), AA_PHYS_INT_ARG(5u), 4790 AA_PHYS_INT_ARG(6u), AA_PHYS_INT_ARG(7u), 4791 AA_PHYS_INT_ALLOC(8u), AA_PHYS_INT_RESERVED_CALLER(9u), 4792 AA_PHYS_INT_RESERVED_CALLER(10u), AA_PHYS_INT_RESERVED_CALLER(11u), 4793 AA_PHYS_INT_ALLOC(12u), AA_PHYS_INT_ALLOC(13u), 4794 AA_PHYS_INT_ALLOC(14u), AA_PHYS_INT_ALLOC(15u), 4795 AA_PHYS_INT_RESERVED_CALLER(16u), AA_PHYS_INT_RESERVED_CALLER(17u), 4796 AA_PHYS_INT_RESERVED_CALLER(18u), AA_PHYS_INT_CALLEE(19u), 4797 AA_PHYS_INT_CALLEE(20u), AA_PHYS_INT_CALLEE(21u), 4798 AA_PHYS_INT_CALLEE(22u), AA_PHYS_INT_CALLEE(23u), 4799 AA_PHYS_INT_CALLEE(24u), AA_PHYS_INT_CALLEE(25u), 4800 AA_PHYS_INT_CALLEE(26u), AA_PHYS_INT_CALLEE(27u), 4801 AA_PHYS_INT_CALLEE(28u), AA_PHYS_INT_RESERVED(29u), 4802 AA_PHYS_INT_RESERVED(30u), AA_PHYS_INT_RESERVED(31u), 4803 }; 4804 4805 #define AA_PHYS_FP_ALLOC(r) \ 4806 {.reg = (r), \ 4807 .cls = NATIVE_REG_FP, \ 4808 .abi_index = 0xffu, \ 4809 .flags = NATIVE_REG_ALLOCABLE | NATIVE_REG_CALLER_SAVED, \ 4810 .spill_cost = 1u, \ 4811 .copy_cost = 1u} 4812 #define AA_PHYS_FP_CALLER(r) \ 4813 {.reg = (r), \ 4814 .cls = NATIVE_REG_FP, \ 4815 .abi_index = 0xffu, \ 4816 .flags = NATIVE_REG_CALLER_SAVED, \ 4817 .spill_cost = 1u, \ 4818 .copy_cost = 1u} 4819 #define AA_PHYS_FP_ARG(r) \ 4820 {.reg = (r), \ 4821 .cls = NATIVE_REG_FP, \ 4822 .abi_index = (r), \ 4823 .flags = NATIVE_REG_CALLER_SAVED | NATIVE_REG_ARG | \ 4824 ((r) < 4u ? NATIVE_REG_RET : 0), \ 4825 .spill_cost = 1u, \ 4826 .copy_cost = 1u} 4827 #define AA_PHYS_FP_CALLEE(r) \ 4828 {.reg = (r), \ 4829 .cls = NATIVE_REG_FP, \ 4830 .abi_index = 0xffu, \ 4831 .flags = NATIVE_REG_ALLOCABLE | NATIVE_REG_CALLEE_SAVED, \ 4832 .spill_cost = 4u, \ 4833 .copy_cost = 1u} 4834 #define AA_PHYS_FP_RESERVED(r) \ 4835 {.reg = (r), \ 4836 .cls = NATIVE_REG_FP, \ 4837 .abi_index = 0xffu, \ 4838 .flags = NATIVE_REG_RESERVED | \ 4839 NATIVE_REG_CALLER_SAVED, \ 4840 .spill_cost = 0u, \ 4841 .copy_cost = 0u} 4842 4843 static const NativePhysRegInfo aa_fp_phys[] = { 4844 AA_PHYS_FP_ARG(0u), AA_PHYS_FP_ARG(1u), AA_PHYS_FP_ARG(2u), 4845 AA_PHYS_FP_ARG(3u), AA_PHYS_FP_ARG(4u), AA_PHYS_FP_ARG(5u), 4846 AA_PHYS_FP_ARG(6u), AA_PHYS_FP_ARG(7u), AA_PHYS_FP_CALLEE(8u), 4847 AA_PHYS_FP_CALLEE(9u), AA_PHYS_FP_CALLEE(10u), AA_PHYS_FP_CALLEE(11u), 4848 AA_PHYS_FP_CALLEE(12u), AA_PHYS_FP_CALLEE(13u), AA_PHYS_FP_CALLEE(14u), 4849 AA_PHYS_FP_CALLEE(15u), AA_PHYS_FP_CALLER(16u), AA_PHYS_FP_CALLER(17u), 4850 AA_PHYS_FP_ALLOC(18u), AA_PHYS_FP_ALLOC(19u), AA_PHYS_FP_RESERVED(20u), 4851 AA_PHYS_FP_RESERVED(21u), AA_PHYS_FP_CALLER(22u), AA_PHYS_FP_CALLER(23u), 4852 AA_PHYS_FP_CALLER(24u), AA_PHYS_FP_CALLER(25u), AA_PHYS_FP_CALLER(26u), 4853 AA_PHYS_FP_CALLER(27u), AA_PHYS_FP_CALLER(28u), AA_PHYS_FP_CALLER(29u), 4854 AA_PHYS_FP_CALLER(30u), AA_PHYS_FP_CALLER(31u), 4855 }; 4856 4857 static const NativeAllocClassInfo aa_classes[] = { 4858 {.cls = NATIVE_REG_INT, 4859 .ndt_allocable = aa_ndt_int_allocable, 4860 .ndt_allocable_count = 4861 sizeof aa_ndt_int_allocable / sizeof aa_ndt_int_allocable[0], 4862 .scratch = aa_int_scratch, 4863 .nscratch = sizeof aa_int_scratch / sizeof aa_int_scratch[0], 4864 .emit_temps = aa_int_scratch, 4865 .nemit_temps = sizeof aa_int_scratch / sizeof aa_int_scratch[0], 4866 .asm_temps = aa_int_asm_temps, 4867 .nasm_temps = sizeof aa_int_asm_temps / sizeof aa_int_asm_temps[0], 4868 .direct_asm_allocable = aa_direct_asm_int, 4869 .ndirect_asm_allocable = 4870 sizeof aa_direct_asm_int / sizeof aa_direct_asm_int[0], 4871 .emit_cache_mask = (1u << 9u) | (1u << 10u) | (1u << 11u), 4872 .phys = aa_int_phys, 4873 .nphys = sizeof aa_int_phys / sizeof aa_int_phys[0]}, 4874 {.cls = NATIVE_REG_FP, 4875 .ndt_allocable = aa_ndt_fp_allocable, 4876 .ndt_allocable_count = 4877 sizeof aa_ndt_fp_allocable / sizeof aa_ndt_fp_allocable[0], 4878 .scratch = aa_fp_scratch, 4879 .nscratch = sizeof aa_fp_scratch / sizeof aa_fp_scratch[0], 4880 .emit_temps = aa_fp_scratch, 4881 .nemit_temps = sizeof aa_fp_scratch / sizeof aa_fp_scratch[0], 4882 .direct_asm_allocable = aa_direct_asm_fp, 4883 .ndirect_asm_allocable = 4884 sizeof aa_direct_asm_fp / sizeof aa_direct_asm_fp[0], 4885 .emit_cache_mask = (1u << 20u) | (1u << 21u), 4886 .phys = aa_fp_phys, 4887 .nphys = sizeof aa_fp_phys / sizeof aa_fp_phys[0]}, 4888 }; 4889 4890 /* Resolve a register name ("x8", "v3", ...) to its (class, Reg). Powers the 4891 * optimizer's inline-asm clobber masks and explicit hard-register operands 4892 * ("{x8}" from a GNU local register variable). x0..x30 are DWARF 0..30; the 4893 * SIMD/FP bank v0..v31 is DWARF 64..95. Returns non-zero for a non-register 4894 * name (cc/memory/unknown), which the caller skips. */ 4895 static int aa_resolve_name(const NativeRegInfo* ri, Slice name, Reg* out, 4896 NativeAllocClass* cls_out) { 4897 char buf[16]; 4898 uint32_t dwarf; 4899 (void)ri; 4900 if (!name.s || !name.len || name.len >= sizeof buf) return 1; 4901 memcpy(buf, name.s, name.len); 4902 buf[name.len] = '\0'; 4903 if (aa64_register_index(buf, &dwarf) != 0) return 1; 4904 if (dwarf <= 30u) { 4905 *cls_out = NATIVE_REG_INT; 4906 *out = (Reg)dwarf; 4907 return 0; 4908 } 4909 if (dwarf >= 64u && dwarf <= 95u) { 4910 *cls_out = NATIVE_REG_FP; 4911 *out = (Reg)(dwarf - 64u); 4912 return 0; 4913 } 4914 return 1; 4915 } 4916 4917 static int aa_asm_operand_reg_ok(const NativeRegInfo* ri, NativeAllocClass cls, 4918 Reg reg) { 4919 (void)ri; 4920 if (cls == NATIVE_REG_INT) { 4921 if (reg <= 8u) return 1; 4922 if (reg >= 12u && reg <= 15u) return 1; 4923 if (reg >= 19u && reg <= 28u) return 1; 4924 return 0; 4925 } 4926 if (cls == NATIVE_REG_FP) { 4927 if (reg <= 19u) return 1; 4928 if (reg >= 22u && reg <= 31u) return 1; 4929 } 4930 return 0; 4931 } 4932 4933 static int aa_asm_constraint_reg(const NativeRegInfo* ri, const char* body, 4934 NativeAllocClass* cls_out, Reg* fixed_out, 4935 u32* allowed_mask_out) { 4936 (void)ri; 4937 if (!body || !body[0] || body[1]) return 0; 4938 if (fixed_out) *fixed_out = REG_NONE; 4939 if (allowed_mask_out) *allowed_mask_out = 0; 4940 switch (body[0]) { 4941 case 'r': 4942 if (cls_out) *cls_out = NATIVE_REG_INT; 4943 return 1; 4944 case 'w': 4945 if (cls_out) *cls_out = NATIVE_REG_FP; 4946 return 1; 4947 case 'x': 4948 if (cls_out) *cls_out = NATIVE_REG_FP; 4949 if (allowed_mask_out) *allowed_mask_out = 0x0000ffffu; /* v0..v15 */ 4950 return 1; 4951 case 'y': 4952 if (cls_out) *cls_out = NATIVE_REG_FP; 4953 if (allowed_mask_out) *allowed_mask_out = 0x000000ffu; /* v0..v7 */ 4954 return 1; 4955 default: 4956 return 0; 4957 } 4958 } 4959 4960 static const NativeRegInfo aa_reg_info = { 4961 .classes = aa_classes, 4962 .nclasses = sizeof aa_classes / sizeof aa_classes[0], 4963 /* x8..x15 give NDT a rich caller-saved scratch pool, so the -O0 path never 4964 * needs a callee-saved register — enabling the tcc-style deferred-`sub` 4965 * prologue (see aa_func_begin / AA_NDT_SUB_WORDS). */ 4966 .ndt_caller_saved_only = 1u, 4967 /* x0 is clobbered only by calls (div/mul take explicit operands), so a 4968 * scalar call result can stay cached in it — see L4 Phase 1 in nd_call. */ 4969 .ndt_result_reg_stable = 1u, 4970 /* aa_emit_mem emits ldrsb/ldrsh (opc=10, X form) for an MF_SEXT_LOAD narrow 4971 * integer load, filling the whole register — so the -O0 NDT may drop the 4972 * following CV_SEXT (Lever 4). */ 4973 .load_sext = 1u, 4974 .resolve_name = aa_resolve_name, 4975 .asm_operand_reg_ok = aa_asm_operand_reg_ok, 4976 .asm_constraint_reg = aa_asm_constraint_reg, 4977 }; 4978 4979 static void aa_va_start_native(NativeTarget* t, NativeLoc ap_ptr); 4980 static void aa_va_arg_native(NativeTarget* t, NativeLoc dst, NativeLoc ap_ptr, 4981 KitCgTypeId type); 4982 static void aa_va_end_native(NativeTarget* t, NativeLoc ap_ptr); 4983 static void aa_va_copy_native(NativeTarget* t, NativeLoc dst_ap_ptr, 4984 NativeLoc src_ap_ptr); 4985 static void aa_asm_block_native(NativeTarget* t, const char* tmpl, 4986 const AsmConstraint* outs, u32 nout, 4987 NativeLoc* out_locs, const AsmConstraint* ins, 4988 u32 nin, const NativeLoc* in_locs, 4989 const Sym* clobbers, u32 nclob); 4990 4991 NativeTarget* aa64_native_target_new(Compiler* c, ObjBuilder* obj, 4992 MCEmitter* mc) { 4993 AANativeTarget* a = arena_znew(c->tu, AANativeTarget); 4994 NativeTarget* t; 4995 if (!a) return NULL; 4996 t = &a->base; 4997 t->c = c; 4998 t->obj = obj; 4999 t->mc = mc; 5000 native_frame_init(&a->frame, c); 5001 t->regs = &aa_reg_info; 5002 t->class_for_type = aa_class_for_type; 5003 t->imm_legal = aa_imm_legal; 5004 t->addr_legal = aa_addr_legal; 5005 t->can_fold_shift_into_alu = aa_can_fold_shift_into_alu; 5006 t->can_fold_extend_into_addr = aa_can_fold_extend_into_addr; 5007 t->machine_op_clobbers = aa_machine_op_clobbers; 5008 t->func_begin = aa_func_begin; 5009 t->func_begin_known_frame = aa_func_begin_known_frame; 5010 t->note_frame_state = aa_note_frame_state; 5011 t->reserve_callee_saves = aa_reserve_callee_saves; 5012 t->signature_stack_bytes = aa_signature_stack_bytes; 5013 t->call_stack_bytes = aa_call_stack_bytes; 5014 t->has_store_zero_reg = 1; 5015 t->store_zero_reg = 31u; /* wzr/xzr in the Rt position of a store */ 5016 t->func_end = aa_func_end; 5017 t->frame_slot = aa_frame_slot; 5018 t->release_frame_slot = aa_release_frame_slot; 5019 t->frame_slot_debug_loc = aa_frame_slot_debug_loc; 5020 t->bind_param = aa_bind_native_param; 5021 t->label_new = aa_label_new; 5022 t->label_place = aa_label_place; 5023 t->jump = aa_jump; 5024 t->cmp_branch = aa_cmp_branch; 5025 t->indirect_branch = aa_indirect_branch; 5026 t->load_label_addr = aa_load_label_addr; 5027 t->move = aa_move; 5028 t->load_imm = aa_load_imm_native; 5029 t->load_const = aa_load_const; 5030 t->load_addr = aa_load_addr; 5031 t->load = aa_load_native; 5032 t->store = aa_store_native; 5033 t->tls_addr_of = aa_tls_addr_of; 5034 t->copy_bytes = aa_copy_bytes; 5035 t->set_bytes = aa_set_bytes; 5036 t->bitfield_load = aa_bitfield_load; 5037 t->bitfield_store = aa_bitfield_store; 5038 t->binop = aa_binop; 5039 t->unop = aa_unop; 5040 t->cmp = aa_cmp; 5041 t->convert = aa_convert; 5042 t->binop_rr = aa_binop_rr; 5043 t->move_rr = aa_move_rr; 5044 t->cmp_rr = aa_cmp_rr; 5045 t->convert_rr = aa_convert_rr; 5046 t->alloca_ = aa_alloca; 5047 t->spill = aa_spill; 5048 t->reload = aa_reload; 5049 t->marshal_call = aa_marshal_call; 5050 t->emit_call = aa_emit_call; 5051 t->marshal_ret = aa_marshal_ret; 5052 t->ret = aa_ret; 5053 t->atomic_load = aa_atomic_load; 5054 t->atomic_store = aa_atomic_store; 5055 t->atomic_rmw = aa_atomic_rmw; 5056 t->atomic_cas = aa_atomic_cas; 5057 t->fence = aa_fence; 5058 t->va_start_ = aa_va_start_native; 5059 t->va_arg_ = aa_va_arg_native; 5060 t->va_end_ = aa_va_end_native; 5061 t->va_copy_ = aa_va_copy_native; 5062 t->intrinsic = aa_intrinsic; 5063 t->asm_block = aa_asm_block_native; 5064 t->file_scope_asm = native_file_scope_asm; 5065 t->trap = aa_trap; 5066 t->set_loc = aa_set_loc; 5067 t->finalize = native_finalize; 5068 return t; 5069 } 5070 5071 /* Place the incoming parameter into `dst`: a hard register (the common 5072 * register-allocated scalar case -> a single arg-reg move, or a stack load 5073 * straight into the register), a frame slot (address-taken / aggregate / 5074 * spilled), or nowhere (unused). Incoming arg registers are never allocable, 5075 * so a register dst never aliases an incoming arg register. */ 5076 static void aa_bind_native_param(NativeTarget* t, const CGParamDesc* p, 5077 NativeLoc dst) { 5078 AANativeTarget* a = aa_of(t); 5079 const ABIFuncInfo* abi = abi_cg_func_info(t->c->abi, a->func->fn_type); 5080 const ABIArgInfo* ai = 5081 p->index < abi->nparams ? &abi->params[p->index] : NULL; 5082 int to_reg = dst.kind == NATIVE_LOC_REG; 5083 if (!ai || ai->kind == ABI_ARG_IGNORE) return; 5084 if (ai->kind == ABI_ARG_INDIRECT) { 5085 NativeAddr d_addr, from; 5086 AggregateAccess access; 5087 NativeLoc src = 5088 native_loc_reg(p->type, NATIVE_REG_INT, 5089 a->next_param_int < 8u ? a->next_param_int++ : AA_TMP0); 5090 if (src.v.reg == AA_TMP0) { 5091 NativeAddr saddr; 5092 memset(&saddr, 0, sizeof saddr); 5093 saddr.base_kind = NATIVE_ADDR_BASE_REG; 5094 saddr.base.reg = AA_FP; 5095 saddr.offset = aa_fp_off_in_arg(a, a->next_param_stack); 5096 aa_emit_mem(a, 1, src, saddr, aa_mem_for_type(t, p->type, 8)); 5097 a->next_param_stack += 8u; 5098 } 5099 if (dst.kind != NATIVE_LOC_FRAME) 5100 aa_panic(a, "indirect parameter requires a frame destination"); 5101 memset(&d_addr, 0, sizeof d_addr); 5102 d_addr.base_kind = NATIVE_ADDR_BASE_FRAME; 5103 d_addr.base.frame = dst.v.frame; 5104 d_addr.base_type = p->type; 5105 memset(&from, 0, sizeof from); 5106 from.base_kind = NATIVE_ADDR_BASE_REG; 5107 from.base.reg = src.v.reg; 5108 from.base_type = p->type; 5109 memset(&access, 0, sizeof access); 5110 access.type = p->type; 5111 access.size = p->size ? p->size : (u32)cg_type_size(t->c, p->type); 5112 access.align = p->align ? p->align : type_align32(t, p->type); 5113 aa_copy_bytes(t, d_addr, from, access); 5114 return; 5115 } 5116 for (u32 i = 0; i < ai->nparts; ++i) { 5117 const ABIArgPart* part = &ai->parts[i]; 5118 NativeAllocClass cls = 5119 part->cls == ABI_CLASS_FP ? NATIVE_REG_FP : NATIVE_REG_INT; 5120 int reg_dst = to_reg && (NativeAllocClass)dst.cls == cls; 5121 NativeLoc src; 5122 if (cls == NATIVE_REG_FP && a->next_param_fp < 8u) { 5123 src = native_loc_reg(p->type, cls, a->next_param_fp++); 5124 } else if (cls == NATIVE_REG_INT && a->next_param_int < 8u) { 5125 src = native_loc_reg(p->type, cls, a->next_param_int++); 5126 } else { 5127 /* Stack-passed part: load straight into the dst register when possible, 5128 * otherwise use a backend-private transfer register for the frame store. */ 5129 Reg tmp = 5130 reg_dst ? (Reg)dst.v.reg : (cls == NATIVE_REG_FP ? 16u : AA_TMP0); 5131 NativeAddr saddr; 5132 src = native_loc_reg(p->type, cls, tmp); 5133 a->next_param_stack = 5134 align_up_u32(a->next_param_stack, aa_part_stack_align(abi, part)); 5135 memset(&saddr, 0, sizeof saddr); 5136 saddr.base_kind = NATIVE_ADDR_BASE_REG; 5137 saddr.base.reg = AA_FP; 5138 saddr.base_type = p->type; 5139 saddr.offset = aa_fp_off_in_arg(a, a->next_param_stack); 5140 aa_emit_mem(a, 1, src, saddr, aa_mem_for_type(t, p->type, part->size)); 5141 a->next_param_stack += aa_part_stack_size(abi, part); 5142 } 5143 if (dst.kind == NATIVE_LOC_NONE) { 5144 /* Unused parameter: only the ABI cursor advances. */ 5145 } else if (to_reg) { 5146 NativeLoc d = native_loc_reg(dst.type ? dst.type : p->type, 5147 (NativeAllocClass)dst.cls, (Reg)dst.v.reg); 5148 if (!(src.kind == NATIVE_LOC_REG && src.v.reg == d.v.reg && 5149 (NativeAllocClass)src.cls == (NativeAllocClass)d.cls)) 5150 aa_move(t, d, src); 5151 } else { 5152 aa_store_part( 5153 t, native_loc_stack(p->type, dst.v.frame, (i32)part->src_offset), src, 5154 0, part->size); 5155 } 5156 } 5157 a->incoming_stack_size = align_up_u32(a->next_param_stack, 16u); 5158 } 5159 5160 static void aa_bind_param(NativeDirectTarget* d, const CGParamDesc* p, 5161 CGLocal local, NativeDirectLocal* l) { 5162 NativeLoc dst; 5163 (void)local; 5164 memset(&dst, 0, sizeof dst); 5165 dst.kind = NATIVE_LOC_FRAME; 5166 dst.type = p->type; 5167 dst.v.frame = l->home; 5168 aa_bind_native_param(d->native, p, dst); 5169 } 5170 5171 static const char* aa_no_tail(NativeDirectTarget* d, const CGCallDesc* call) { 5172 NativeCallDesc nd; 5173 u32 stack; 5174 native_direct_project_tail_call_desc(d, call, &nd); 5175 stack = aa_call_stack_size(d->native, &nd); 5176 if (stack > aa_of(d->native)->incoming_stack_size) 5177 return "aarch64 tail call: stack argument area too small"; 5178 return NULL; 5179 } 5180 5181 static NativeAddr aa_direct_addr(NativeDirectTarget* d, Operand op) { 5182 NativeAddr addr; 5183 memset(&addr, 0, sizeof addr); 5184 switch ((OpKind)op.kind) { 5185 case OPK_LOCAL: 5186 addr.base_kind = NATIVE_ADDR_BASE_FRAME; 5187 addr.base.frame = d->locals[op.v.local - 1u].home; 5188 addr.base_type = op.type; 5189 return addr; 5190 case OPK_INDIRECT: 5191 addr.base_kind = NATIVE_ADDR_BASE_FRAME_VALUE; 5192 addr.base.frame = d->locals[op.v.ind.base - 1u].home; 5193 addr.cls = d->locals[op.v.ind.base - 1u].cls; 5194 addr.base_type = d->locals[op.v.ind.base - 1u].type; 5195 addr.offset = op.v.ind.ofs; 5196 return addr; 5197 default: 5198 compiler_panic(d->base.c, d->loc, 5199 "aarch64 native target: operand is not addressable"); 5200 } 5201 } 5202 5203 static NativeAddr aa_direct_materialize_addr(NativeDirectTarget* d, 5204 Operand op) { 5205 NativeAddr addr = aa_direct_addr(d, op); 5206 if (addr.base_kind == NATIVE_ADDR_BASE_FRAME_VALUE) { 5207 NativeLoc base = native_loc_reg(addr.base_type, NATIVE_REG_INT, AA_TMP1); 5208 NativeAddr load; 5209 memset(&load, 0, sizeof load); 5210 load.base_kind = NATIVE_ADDR_BASE_FRAME; 5211 load.base.frame = addr.base.frame; 5212 load.base_type = addr.base_type; 5213 aa_emit_mem(aa_of(d->native), 1, base, load, 5214 aa_mem_for_type(d->native, addr.base_type, 8)); 5215 addr.base_kind = NATIVE_ADDR_BASE_REG; 5216 addr.base.reg = AA_TMP1; 5217 } 5218 return addr; 5219 } 5220 5221 static NativeAddr aa_direct_pointer_addr(NativeDirectTarget* d, Operand op) { 5222 NativeAddr addr; 5223 memset(&addr, 0, sizeof addr); 5224 if (op.kind == OPK_LOCAL) { 5225 NativeLoc base = native_loc_reg(op.type, NATIVE_REG_INT, AA_TMP1); 5226 NativeAddr load; 5227 memset(&load, 0, sizeof load); 5228 load.base_kind = NATIVE_ADDR_BASE_FRAME; 5229 load.base.frame = d->locals[op.v.local - 1u].home; 5230 load.base_type = op.type; 5231 aa_emit_mem(aa_of(d->native), 1, base, load, 5232 aa_mem_for_type(d->native, op.type, 8)); 5233 addr.base_kind = NATIVE_ADDR_BASE_REG; 5234 addr.base.reg = AA_TMP1; 5235 addr.base_type = op.type; 5236 return addr; 5237 } 5238 return aa_direct_materialize_addr(d, op); 5239 } 5240 5241 static NativeAddr aa_reg_addr(KitCgTypeId type, u32 reg, i32 offset) { 5242 NativeAddr addr; 5243 memset(&addr, 0, sizeof addr); 5244 addr.base_kind = NATIVE_ADDR_BASE_REG; 5245 addr.base.reg = reg; 5246 addr.base_type = type; 5247 addr.offset = offset; 5248 return addr; 5249 } 5250 5251 static void aa_load_ap_addr(NativeDirectTarget* d, Operand ap_addr, 5252 u32 dst_reg) { 5253 NativeLoc dst = 5254 native_loc_reg(builtin_id(KIT_CG_BUILTIN_I64), NATIVE_REG_INT, dst_reg); 5255 NativeAddr ap = aa_direct_pointer_addr(d, ap_addr); 5256 d->native->load_addr(d->native, dst, ap); 5257 } 5258 5259 /* The va cores use caller-provided operand registers plus backend-private 5260 * x16/x17 and v16. Direct wrappers choose x9/x10 from the O0 scratch policy; 5261 * the optimized emitter supplies an instruction-scoped operand location. None 5262 * can alias a persistently allocated live value. */ 5263 static u32 aa_va_base_reg(AANativeTarget* a, NativeAddr ap) { 5264 if (ap.base_kind != NATIVE_ADDR_BASE_REG) 5265 compiler_panic(a->base.c, a->func ? a->func->loc : (SrcLoc){0, 0, 0}, 5266 "aarch64 native target: va_list pointer not in register"); 5267 return ap.base.reg; 5268 } 5269 5270 /* va_list layout is queried from the ABI; the optimizer/direct callers pass the 5271 * va_list pointer opaquely. `ap` addresses the va_list object itself. */ 5272 static void aa_va_start_core(AANativeTarget* a, NativeAddr ap) { 5273 NativeTarget* t = &a->base; 5274 const ABIFuncInfo* abi = 5275 a->func ? abi_cg_func_info(t->c->abi, a->func->fn_type) : NULL; 5276 ABIVaListInfo vai = abi_va_list_layout(t->c->abi); 5277 NativeLoc ptr = 5278 native_loc_reg(builtin_id(KIT_CG_BUILTIN_I64), NATIVE_REG_INT, AA_TMP0); 5279 if (vai.kind == ABI_VA_LIST_POINTER) { 5280 if (a->top_home_bytes) { 5281 /* Windows: `va_list = &<first vararg>` inside the contiguous 5282 * [GP home area | incoming stack args] block. Named args consume the 5283 * leading slots; next_param_int (FP params remapped to GP included) plus 5284 * next_param_stack locate the first unnamed slot. Home slot 5285 * gp_reg_count coincides with incoming-arg byte 0, so a single formula 5286 * spans both regions. */ 5287 i32 off = 5288 aa_fp_off_home_slot(a->next_param_int) + (i32)a->next_param_stack; 5289 aa_emit_add_imm(a, AA_TMP0, AA_FP, off); 5290 aa_emit_mem(a, 0, ptr, ap, aa_mem_for_type(t, ptr.type, 8)); 5291 return; 5292 } 5293 /* `va_list = &<first vararg>`. Variadic stack args follow the fixed 5294 * incoming params in the same caller window. Apple ARM64 compact fixed 5295 * stack args may leave this cursor at +4, while the first variadic slot 5296 * starts at the next 8-byte boundary. */ 5297 u32 stack = aa_vararg_stack_start(abi, a->next_param_stack); 5298 aa_emit_add_imm(a, AA_TMP0, AA_FP, aa_fp_off_in_arg(a, stack)); 5299 aa_emit_mem(a, 0, ptr, ap, aa_mem_for_type(t, ptr.type, 8)); 5300 return; 5301 } 5302 if (vai.kind == ABI_VA_LIST_AAPCS64) { 5303 KitCgTypeId i32_ty = builtin_id(KIT_CG_BUILTIN_I32); 5304 NativeLoc i32tmp = native_loc_reg(i32_ty, NATIVE_REG_INT, AA_TMP1); 5305 MemAccess ptr_mem = aa_mem_for_type(t, ptr.type, 8); 5306 MemAccess i32_mem = aa_mem_for_type(t, i32_ty, 4); 5307 AANativeSlot* gr = aa_slot(a, a->va_gr_slot); 5308 AANativeSlot* vr = aa_slot(a, a->va_vr_slot); 5309 u32 base = aa_va_base_reg(a, ap); 5310 u32 used_gr = a->next_param_int < vai.gp_reg_count ? a->next_param_int 5311 : vai.gp_reg_count; 5312 u32 used_vr = a->next_param_fp < vai.fp_reg_count ? a->next_param_fp 5313 : vai.fp_reg_count; 5314 /* __stack points at the incoming stack args, which sit above the saved 5315 * fp/lr pair — the same address bind_param uses (aa_fp_off_in_arg), not the 5316 * raw next_param_stack cursor. */ 5317 aa_emit_add_imm(a, AA_TMP0, AA_FP, 5318 aa_fp_off_in_arg(a, a->next_param_stack)); 5319 aa_emit_mem(a, 0, ptr, aa_reg_addr(ptr.type, base, (i32)vai.stack_offset), 5320 ptr_mem); 5321 aa_emit_add_imm(a, AA_TMP0, AA_FP, 5322 aa_fp_off_slot(a, gr->off) + 5323 (i32)(vai.gp_reg_count * vai.gp_slot_size)); 5324 aa_emit_mem(a, 0, ptr, aa_reg_addr(ptr.type, base, (i32)vai.gr_top_offset), 5325 ptr_mem); 5326 aa_emit_add_imm(a, AA_TMP0, AA_FP, 5327 aa_fp_off_slot(a, vr->off) + 5328 (i32)(vai.fp_reg_count * vai.fp_slot_size)); 5329 aa_emit_mem(a, 0, ptr, aa_reg_addr(ptr.type, base, (i32)vai.vr_top_offset), 5330 ptr_mem); 5331 aa_emit_load_imm(t->mc, 0, AA_TMP1, 5332 -(i32)((vai.gp_reg_count - used_gr) * vai.gp_slot_size)); 5333 aa_emit_mem(a, 0, i32tmp, 5334 aa_reg_addr(i32_ty, base, (i32)vai.gr_offs_offset), i32_mem); 5335 aa_emit_load_imm(t->mc, 0, AA_TMP1, 5336 -(i32)((vai.fp_reg_count - used_vr) * vai.fp_slot_size)); 5337 aa_emit_mem(a, 0, i32tmp, 5338 aa_reg_addr(i32_ty, base, (i32)vai.vr_offs_offset), i32_mem); 5339 return; 5340 } 5341 compiler_panic(t->c, a->func ? a->func->loc : (SrcLoc){0, 0, 0}, 5342 "aarch64 native target: unsupported va_list layout"); 5343 } 5344 5345 static void aa_va_arg_core(AANativeTarget* a, NativeLoc dst, NativeAddr ap, 5346 KitCgTypeId type) { 5347 NativeTarget* t = &a->base; 5348 ABIVaListInfo vai = abi_va_list_layout(t->c->abi); 5349 NativeLoc cur = 5350 native_loc_reg(builtin_id(KIT_CG_BUILTIN_I64), NATIVE_REG_INT, AA_TMP0); 5351 /* The fetched value is written directly into caller-provided `dst`, which is 5352 * distinct from the va_list base. Only TMP0/TMP1 are backend-private. */ 5353 NativeLoc val = dst; 5354 NativeAddr src; 5355 MemAccess ptr_mem = aa_mem_for_type(t, cur.type, 8); 5356 MemAccess val_mem = aa_mem_for_type(t, type, type_size32(t, type)); 5357 if (dst.kind != NATIVE_LOC_REG) 5358 compiler_panic(t->c, a->func ? a->func->loc : (SrcLoc){0, 0, 0}, 5359 "aarch64 native target: va_arg destination must be a " 5360 "register"); 5361 if (vai.kind == ABI_VA_LIST_POINTER) { 5362 aa_emit_mem(a, 1, cur, ap, ptr_mem); 5363 src = aa_reg_addr(type, AA_TMP0, 0); 5364 { 5365 const ABIFuncInfo* abi = 5366 a->func ? abi_cg_func_info(t->c->abi, a->func->fn_type) : NULL; 5367 ABIArgPart part; 5368 memset(&part, 0, sizeof part); 5369 part.cls = cg_type_is_float(t->c, type) ? ABI_CLASS_FP : ABI_CLASS_INT; 5370 part.size = type_size32(t, type); 5371 part.align = type_align32(t, type); 5372 aa_emit_add_imm(a, AA_TMP1, AA_TMP0, 5373 (i32)aa_part_vararg_stack_size(abi, &part)); 5374 } 5375 aa_emit_mem(a, 0, native_loc_reg(cur.type, NATIVE_REG_INT, AA_TMP1), ap, 5376 ptr_mem); 5377 aa_emit_mem(a, 1, val, src, val_mem); 5378 return; 5379 } 5380 if (vai.kind == ABI_VA_LIST_AAPCS64) { 5381 KitCgTypeId i32_ty = builtin_id(KIT_CG_BUILTIN_I32); 5382 NativeLoc off = native_loc_reg(i32_ty, NATIVE_REG_INT, AA_TMP1); 5383 MemAccess i32_mem = aa_mem_for_type(t, i32_ty, 4); 5384 int is_fp = cg_type_is_float(t->c, type); 5385 u32 base = aa_va_base_reg(a, ap); 5386 u32 offs_field = is_fp ? vai.vr_offs_offset : vai.gr_offs_offset; 5387 u32 top_field = is_fp ? vai.vr_top_offset : vai.gr_top_offset; 5388 u32 slot_size = is_fp ? vai.fp_slot_size : vai.gp_slot_size; 5389 MCLabel stack_label = mc_label_new(t->mc); 5390 MCLabel done_label = mc_label_new(t->mc); 5391 aa_emit_mem(a, 1, off, aa_reg_addr(i32_ty, base, (i32)offs_field), i32_mem); 5392 aa_emit32(t->mc, aa64_subs_imm12(0, AA64_ZR, AA_TMP1, 0, 0)); 5393 aa_emit32(t->mc, 5394 aa64_brcond_pack((AA64BrCond){.cond = cmp_cond(CMP_GE_S)})); 5395 mc_emit_label_ref(t->mc, stack_label, R_AARCH64_CONDBR19, 4, 0); 5396 aa_emit_mem(a, 1, cur, aa_reg_addr(cur.type, base, (i32)top_field), 5397 ptr_mem); 5398 aa_emit32(t->mc, aa_sbfm(1, AA_TMP1, AA_TMP1, 0, 31)); 5399 aa_emit32(t->mc, aa64_add(1, AA_TMP0, AA_TMP0, AA_TMP1)); 5400 aa_emit_mem(a, 1, val, aa_reg_addr(type, AA_TMP0, 0), val_mem); 5401 aa_emit_add_imm(a, AA_TMP1, AA_TMP1, (i32)slot_size); 5402 aa_emit_mem(a, 0, off, aa_reg_addr(i32_ty, base, (i32)offs_field), i32_mem); 5403 aa_emit32(t->mc, aa64_b(0)); 5404 mc_emit_label_ref(t->mc, done_label, R_AARCH64_JUMP26, 4, 0); 5405 mc_label_place(t->mc, stack_label); 5406 aa_emit_mem(a, 1, cur, aa_reg_addr(cur.type, base, (i32)vai.stack_offset), 5407 ptr_mem); 5408 aa_emit_mem(a, 1, val, aa_reg_addr(type, AA_TMP0, 0), val_mem); 5409 aa_emit_add_imm(a, AA_TMP0, AA_TMP0, 8); 5410 aa_emit_mem(a, 0, cur, aa_reg_addr(cur.type, base, (i32)vai.stack_offset), 5411 ptr_mem); 5412 mc_label_place(t->mc, done_label); 5413 return; 5414 } 5415 compiler_panic(t->c, a->func ? a->func->loc : (SrcLoc){0, 0, 0}, 5416 "aarch64 native target: unsupported va_list layout"); 5417 } 5418 5419 static void aa_va_copy_core(AANativeTarget* a, NativeAddr dst_ap, 5420 NativeAddr src_ap) { 5421 NativeTarget* t = &a->base; 5422 ABIVaListInfo vai = abi_va_list_layout(t->c->abi); 5423 NativeLoc tmp = 5424 native_loc_reg(builtin_id(KIT_CG_BUILTIN_I64), NATIVE_REG_INT, AA_TMP0); 5425 MemAccess mem = aa_mem_for_type(t, tmp.type, 8); 5426 if (vai.kind == ABI_VA_LIST_POINTER) { 5427 aa_emit_mem(a, 1, tmp, src_ap, mem); 5428 aa_emit_mem(a, 0, tmp, dst_ap, mem); 5429 return; 5430 } 5431 if (vai.kind == ABI_VA_LIST_AAPCS64) { 5432 u32 sb = aa_va_base_reg(a, src_ap); 5433 u32 db = aa_va_base_reg(a, dst_ap); 5434 for (u32 off = 0; off < vai.type.size; off += 8u) { 5435 aa_emit_mem(a, 1, tmp, aa_reg_addr(tmp.type, sb, (i32)off), mem); 5436 aa_emit_mem(a, 0, tmp, aa_reg_addr(tmp.type, db, (i32)off), mem); 5437 } 5438 return; 5439 } 5440 compiler_panic(t->c, a->func ? a->func->loc : (SrcLoc){0, 0, 0}, 5441 "aarch64 native target: unsupported va_list layout"); 5442 } 5443 5444 /* ---- Direct-path (NativeDirectTarget) wrappers: convert semantic operands to 5445 * NativeAddr/NativeLoc, then call the shared cores above. ---- */ 5446 5447 /* The cores reserve x16/x17 (TMP0/TMP1) as backend-private temporaries and 5448 * require the va_list base register(s) to be distinct. aa_direct_pointer_addr 5449 * returns the pointer in TMP1, so the direct wrappers first relocate it into 5450 * x9/x10 before calling the cores. */ 5451 static NativeAddr aa_direct_va_base(NativeDirectTarget* d, Operand ap_addr, 5452 u32 reg) { 5453 aa_load_ap_addr(d, ap_addr, reg); 5454 return aa_reg_addr(builtin_id(KIT_CG_BUILTIN_I64), reg, 0); 5455 } 5456 5457 static void aa_va_start_(NativeDirectTarget* d, Operand ap_addr) { 5458 aa_va_start_core(aa_of(d->native), aa_direct_va_base(d, ap_addr, 10u)); 5459 } 5460 5461 static void aa_va_arg_(NativeDirectTarget* d, Operand dst_op, Operand ap_addr, 5462 KitCgTypeId type) { 5463 AANativeTarget* a = aa_of(d->native); 5464 int is_fp = cg_type_is_float(d->base.c, type); 5465 NativeLoc res = native_loc_reg(type, is_fp ? NATIVE_REG_FP : NATIVE_REG_INT, 5466 is_fp ? 16u : 9u); 5467 MemAccess val_mem = 5468 aa_mem_for_type(d->native, type, type_size32(d->native, type)); 5469 NativeAddr dst; 5470 aa_va_arg_core(a, res, aa_direct_va_base(d, ap_addr, 10u), type); 5471 dst = aa_direct_materialize_addr(d, dst_op); 5472 aa_emit_mem(a, 0, res, dst, val_mem); 5473 } 5474 5475 static void aa_va_end_(NativeDirectTarget* d, Operand ap_addr) { 5476 (void)d; 5477 (void)ap_addr; 5478 } 5479 5480 static void aa_va_copy_(NativeDirectTarget* d, Operand dst_ap_addr, 5481 Operand src_ap_addr) { 5482 AANativeTarget* a = aa_of(d->native); 5483 NativeAddr src = aa_direct_va_base(d, src_ap_addr, 9u); 5484 NativeAddr dst = aa_direct_va_base(d, dst_ap_addr, 10u); 5485 aa_va_copy_core(a, dst, src); 5486 } 5487 5488 /* ---- NativeTarget (optimizer) hooks: the optimizer passes the va_list 5489 * pointer as a materialized register; layout is resolved inside the cores. ---- 5490 */ 5491 5492 static NativeAddr aa_va_addr_from_ptr(NativeLoc ap_ptr) { 5493 NativeAddr addr; 5494 memset(&addr, 0, sizeof addr); 5495 addr.base_kind = NATIVE_ADDR_BASE_REG; 5496 addr.cls = NATIVE_REG_INT; 5497 addr.base.reg = ap_ptr.v.reg; 5498 addr.base_type = ap_ptr.type; 5499 return addr; 5500 } 5501 5502 static void aa_va_start_native(NativeTarget* t, NativeLoc ap_ptr) { 5503 aa_va_start_core(aa_of(t), aa_va_addr_from_ptr(ap_ptr)); 5504 } 5505 5506 static void aa_va_arg_native(NativeTarget* t, NativeLoc dst, NativeLoc ap_ptr, 5507 KitCgTypeId type) { 5508 aa_va_arg_core(aa_of(t), dst, aa_va_addr_from_ptr(ap_ptr), type); 5509 } 5510 5511 static void aa_va_end_native(NativeTarget* t, NativeLoc ap_ptr) { 5512 (void)t; 5513 (void)ap_ptr; 5514 } 5515 5516 static void aa_va_copy_native(NativeTarget* t, NativeLoc dst_ap_ptr, 5517 NativeLoc src_ap_ptr) { 5518 aa_va_copy_core(aa_of(t), aa_va_addr_from_ptr(dst_ap_ptr), 5519 aa_va_addr_from_ptr(src_ap_ptr)); 5520 } 5521 5522 /* constraint_body / constraint_early / match_index are shared 5523 * (cg/native_asm.h). */ 5524 5525 _Noreturn static void aa_asm_panic_at(Compiler* c, SrcLoc loc, 5526 const char* msg) { 5527 compiler_panic(c, loc, "aarch64 inline asm: %s", msg); 5528 } 5529 5530 _Noreturn static void aa_asm_panic(NativeDirectTarget* d, const char* msg) { 5531 aa_asm_panic_at(d->base.c, d->loc, msg); 5532 } 5533 5534 AA_UNUSED_FN static void aa_asm_bound_reg(Operand* out, KitCgTypeId type, 5535 NativeAllocClass cls, Reg reg) { 5536 memset(out, 0, sizeof *out); 5537 out->kind = AA64_INLINE_OPK_REG; 5538 out->pad[0] = 5539 (cls == NATIVE_REG_FP) ? AA64_INLINE_OPCLS_FP : AA64_INLINE_OPCLS_INT; 5540 out->type = type; 5541 out->v.local = (CGLocal)reg; 5542 } 5543 5544 AA_UNUSED_FN static void aa_asm_bound_mem(Operand* out, KitCgTypeId type, 5545 Reg base) { 5546 memset(out, 0, sizeof *out); 5547 out->kind = OPK_INDIRECT; 5548 out->type = type; 5549 out->v.ind.base = (CGLocal)base; 5550 out->v.ind.index = CG_LOCAL_NONE; 5551 } 5552 5553 static int aa_asm_parse_reg_clobber(Compiler* c, SrcLoc loc, Sym name, 5554 NativeAllocClass* cls_out, Reg* reg_out) { 5555 Slice s = pool_slice(c->global, name); 5556 char buf[16]; 5557 uint32_t dwarf; 5558 if (!s.s || !s.len) return 0; 5559 if (s.len == 2 && s.s[0] == 'c' && s.s[1] == 'c') return 0; 5560 if (s.len == 6 && memcmp(s.s, "memory", 6) == 0) return 0; 5561 if (s.len >= sizeof buf) aa_asm_panic_at(c, loc, "clobber name is too long"); 5562 memcpy(buf, s.s, s.len); 5563 buf[s.len] = '\0'; 5564 if (aa64_register_index(buf, &dwarf) != 0) 5565 aa_asm_panic_at(c, loc, "unknown clobber register"); 5566 if (dwarf <= 30u) { 5567 *cls_out = NATIVE_REG_INT; 5568 *reg_out = (Reg)dwarf; 5569 return 1; 5570 } 5571 if (dwarf >= 64u && dwarf <= 95u) { 5572 *cls_out = NATIVE_REG_FP; 5573 *reg_out = (Reg)(dwarf - 64u); 5574 return 1; 5575 } 5576 aa_asm_panic_at(c, loc, "unsupported clobber register"); 5577 return 0; 5578 } 5579 5580 static void aa_asm_clobber_masks(Compiler* c, SrcLoc loc, const Sym* clobbers, 5581 u32 nclob, u32* int_mask, u32* fp_mask) { 5582 *int_mask = 0; 5583 *fp_mask = 0; 5584 for (u32 i = 0; i < nclob; ++i) { 5585 NativeAllocClass cls; 5586 Reg reg; 5587 if (!aa_asm_parse_reg_clobber(c, loc, clobbers[i], &cls, ®)) continue; 5588 if (cls == NATIVE_REG_INT) 5589 *int_mask |= 1u << reg; 5590 else if (cls == NATIVE_REG_FP) 5591 *fp_mask |= 1u << reg; 5592 } 5593 } 5594 5595 /* Pin resolution + panic is the shared native_asm_bind_direct_operands path. */ 5596 5597 AA_UNUSED_FN static void aa_direct_load_operand_to_reg(NativeDirectTarget* d, 5598 Operand op, 5599 NativeLoc dst) { 5600 NativeAddr addr; 5601 memset(&addr, 0, sizeof addr); 5602 switch ((OpKind)op.kind) { 5603 case OPK_IMM: 5604 if ((NativeAllocClass)dst.cls != NATIVE_REG_INT) 5605 aa_asm_panic(d, "floating-point immediate asm input is unsupported"); 5606 d->native->load_imm(d->native, dst, op.v.imm); 5607 return; 5608 case OPK_LOCAL: 5609 addr.base_kind = NATIVE_ADDR_BASE_FRAME; 5610 addr.base.frame = d->locals[op.v.local - 1u].home; 5611 addr.base_type = op.type; 5612 aa_emit_mem(aa_of(d->native), 1, dst, addr, 5613 aa_mem_for_type(d->native, op.type, 0)); 5614 return; 5615 case OPK_GLOBAL: 5616 addr.base_kind = NATIVE_ADDR_BASE_GLOBAL; 5617 addr.base.global.sym = op.v.global.sym; 5618 addr.base.global.addend = op.v.global.addend; 5619 addr.base_type = op.type; 5620 d->native->load_addr(d->native, dst, addr); 5621 return; 5622 case OPK_INDIRECT: 5623 addr = aa_direct_materialize_addr(d, op); 5624 aa_emit_mem(aa_of(d->native), 1, dst, addr, 5625 aa_mem_for_type(d->native, op.type, 0)); 5626 return; 5627 } 5628 aa_asm_panic(d, "unsupported asm input operand"); 5629 } 5630 5631 AA_UNUSED_FN static void aa_direct_load_address_to_reg(NativeDirectTarget* d, 5632 Operand op, 5633 NativeLoc dst) { 5634 NativeAddr addr = aa_direct_addr(d, op); 5635 d->native->load_addr(d->native, dst, addr); 5636 } 5637 5638 AA_UNUSED_FN static void aa_direct_store_reg_to_operand(NativeDirectTarget* d, 5639 Operand op, 5640 NativeLoc src) { 5641 NativeAddr addr; 5642 memset(&addr, 0, sizeof addr); 5643 if (op.kind == OPK_LOCAL) { 5644 addr.base_kind = NATIVE_ADDR_BASE_FRAME; 5645 addr.base.frame = d->locals[op.v.local - 1u].home; 5646 addr.base_type = op.type; 5647 } else { 5648 addr = aa_direct_materialize_addr(d, op); 5649 } 5650 aa_emit_mem(aa_of(d->native), 0, src, addr, 5651 aa_mem_for_type(d->native, op.type, 0)); 5652 } 5653 5654 typedef struct AAAsmSavedClobber { 5655 NativeFrameSlot slot; 5656 NativeAllocClass cls; 5657 Reg reg; 5658 KitCgTypeId type; 5659 } AAAsmSavedClobber; 5660 5661 static void aa_asm_save_one(AANativeTarget* a, AAAsmSavedClobber* s) { 5662 NativeFrameSlotDesc desc; 5663 NativeAddr addr; 5664 NativeLoc reg; 5665 memset(&desc, 0, sizeof desc); 5666 desc.type = s->type; 5667 desc.size = 8; 5668 desc.align = 8; 5669 desc.kind = NATIVE_FRAME_SLOT_SAVE; 5670 s->slot = a->base.frame_slot(&a->base, &desc); 5671 memset(&addr, 0, sizeof addr); 5672 addr.base_kind = NATIVE_ADDR_BASE_FRAME; 5673 addr.base.frame = s->slot; 5674 addr.base_type = s->type; 5675 reg = native_loc_reg(s->type, s->cls, s->reg); 5676 aa_emit_mem(a, 0, reg, addr, aa_mem_for_type(&a->base, s->type, 8)); 5677 } 5678 5679 AA_UNUSED_FN static void aa_asm_restore_one(AANativeTarget* a, 5680 const AAAsmSavedClobber* s) { 5681 NativeAddr addr; 5682 NativeLoc reg = native_loc_reg(s->type, s->cls, s->reg); 5683 memset(&addr, 0, sizeof addr); 5684 addr.base_kind = NATIVE_ADDR_BASE_FRAME; 5685 addr.base.frame = s->slot; 5686 addr.base_type = s->type; 5687 aa_emit_mem(a, 1, reg, addr, aa_mem_for_type(&a->base, s->type, 8)); 5688 } 5689 5690 AA_UNUSED_FN static AAAsmSavedClobber* aa_asm_save_callee_clobbers( 5691 AANativeTarget* a, u32 int_mask, u32 fp_mask, u32* nsaved_out) { 5692 AAAsmSavedClobber* saved = 5693 arena_zarray(a->base.c->tu, AAAsmSavedClobber, 20u); 5694 u32 n = 0; 5695 KitCgTypeId i64 = builtin_id(KIT_CG_BUILTIN_I64); 5696 KitCgTypeId f64 = builtin_id(KIT_CG_BUILTIN_F64); 5697 for (Reg r = 19u; r <= 28u; ++r) { 5698 if ((int_mask & (1u << r)) == 0) continue; 5699 saved[n].cls = NATIVE_REG_INT; 5700 saved[n].reg = r; 5701 saved[n].type = i64; 5702 aa_asm_save_one(a, &saved[n++]); 5703 } 5704 for (Reg r = 8u; r <= 15u; ++r) { 5705 if ((fp_mask & (1u << r)) == 0) continue; 5706 saved[n].cls = NATIVE_REG_FP; 5707 saved[n].reg = r; 5708 saved[n].type = f64; 5709 aa_asm_save_one(a, &saved[n++]); 5710 } 5711 *nsaved_out = n; 5712 return saved; 5713 } 5714 5715 /* Hook adapters that bridge the arch-typed save/restore + assembler entry to 5716 * the shared NativeAsmDirectHooks signatures (which speak NativeDirectTarget + 5717 * an opaque save record). */ 5718 static void* aa_asm_hook_save_callee_clobbers(NativeDirectTarget* d, 5719 u32 int_mask, u32 fp_mask, 5720 u32* nsaved_out) { 5721 return aa_asm_save_callee_clobbers(aa_of(d->native), int_mask, fp_mask, 5722 nsaved_out); 5723 } 5724 static void aa_asm_hook_restore_one(NativeDirectTarget* d, void* saved, 5725 u32 idx) { 5726 aa_asm_restore_one(aa_of(d->native), &((AAAsmSavedClobber*)saved)[idx]); 5727 } 5728 static void aa_asm_hook_run_template(NativeDirectTarget* d, const char* tmpl, 5729 const AsmConstraint* outs, u32 nout, 5730 Operand* bound_outs, 5731 const AsmConstraint* ins, u32 nin, 5732 Operand* bound_ins, const Sym* clobbers, 5733 u32 nclob) { 5734 AA64Asm* a = aa64_asm_open(d->base.c); 5735 aa64_inline_bind(a, outs, nout, bound_outs, ins, nin, bound_ins, clobbers, 5736 nclob); 5737 aa64_asm_run_template(a, d->native->mc, tmpl); 5738 aa64_asm_close(a); 5739 } 5740 5741 static void aa_direct_asm_block(NativeDirectTarget* d, const char* tmpl, 5742 const AsmConstraint* outs, u32 nout, 5743 Operand* out_ops, const AsmConstraint* ins, 5744 u32 nin, const Operand* in_ops, 5745 const Sym* clobbers, u32 nclob, 5746 u32 clobber_abi_sets) { 5747 static const NativeAsmDirectHooks hooks = { 5748 .opk_reg = AA64_INLINE_OPK_REG, 5749 .opcls_fp = AA64_INLINE_OPCLS_FP, 5750 .panic = aa_asm_panic, 5751 .bound_reg = aa_asm_bound_reg, 5752 .bound_mem = aa_asm_bound_mem, 5753 .clobber_masks = aa_asm_clobber_masks, 5754 .save_callee_clobbers = aa_asm_hook_save_callee_clobbers, 5755 .restore_one = aa_asm_hook_restore_one, 5756 .load_operand_to_reg = aa_direct_load_operand_to_reg, 5757 .load_address_to_reg = aa_direct_load_address_to_reg, 5758 .store_reg_to_operand = aa_direct_store_reg_to_operand, 5759 .run_template = aa_asm_hook_run_template, 5760 }; 5761 native_asm_bind_direct_operands(d, tmpl, outs, nout, out_ops, ins, nin, 5762 in_ops, clobbers, nclob, clobber_abi_sets, 5763 &hooks); 5764 } 5765 5766 /* ---- NativeTarget (optimizer) asm hook ---- 5767 * 5768 * The optimized emitter owns register placement, staging, and output 5769 * writeback. This hook binds its concrete locations and only materializes 5770 * memory-constraint bases in backend-private registers. */ 5771 5772 static NativeAddr aa_asm_loc_to_addr(AANativeTarget* a, SrcLoc loc, 5773 NativeLoc src) { 5774 NativeAddr addr; 5775 memset(&addr, 0, sizeof addr); 5776 addr.base_type = src.type; 5777 switch ((NativeLocKind)src.kind) { 5778 case NATIVE_LOC_FRAME: 5779 addr.base_kind = NATIVE_ADDR_BASE_FRAME; 5780 addr.base.frame = src.v.frame; 5781 return addr; 5782 case NATIVE_LOC_ADDR: 5783 return src.v.addr; 5784 case NATIVE_LOC_GLOBAL: 5785 addr.base_kind = NATIVE_ADDR_BASE_GLOBAL; 5786 addr.base.global.sym = src.v.global.sym; 5787 addr.base.global.addend = src.v.global.addend; 5788 return addr; 5789 case NATIVE_LOC_REG: 5790 addr.base_kind = NATIVE_ADDR_BASE_REG; 5791 addr.cls = NATIVE_REG_INT; 5792 addr.base.reg = src.v.reg; 5793 return addr; 5794 default: 5795 aa_asm_panic_at(a->base.c, loc, "unsupported memory asm operand"); 5796 } 5797 } 5798 5799 /* Resolve a memory-constraint operand to a single base register with zero 5800 * offset, folding any frame/global/offset into a backend-private register. At 5801 * most x16/x17 are used across one asm block. */ 5802 static Reg aa_asm_native_mem_base(AANativeTarget* a, SrcLoc loc, NativeLoc src, 5803 u32* ntmp) { 5804 NativeAddr addr = aa_asm_loc_to_addr(a, loc, src); 5805 u32 base; 5806 i32 off; 5807 Reg dst; 5808 if (addr.index_kind != NATIVE_ADDR_INDEX_NONE) 5809 aa_asm_panic_at(a->base.c, loc, "indexed memory asm operand unsupported"); 5810 aa_addr_base(a, addr, 0u, &base, &off); 5811 if (off == 0) return (Reg)base; 5812 if (*ntmp >= 2u) 5813 aa_asm_panic_at(a->base.c, loc, "too many memory asm operands"); 5814 dst = (*ntmp == 0u) ? AA_TMP0 : AA_TMP1; 5815 (*ntmp)++; 5816 aa_emit_add_imm(a, dst, base, off); 5817 return dst; 5818 } 5819 5820 static void aa_asm_native_panic(NativeTarget* t, SrcLoc loc, const char* msg) { 5821 aa_asm_panic_at(t->c, loc, msg); 5822 } 5823 5824 static Reg aa_asm_native_mem_base_hook(NativeTarget* t, SrcLoc loc, 5825 NativeLoc src, u32* ntmp) { 5826 return aa_asm_native_mem_base(aa_of(t), loc, src, ntmp); 5827 } 5828 5829 static void aa_asm_native_run_template_hook( 5830 NativeTarget* t, const char* tmpl, const AsmConstraint* outs, u32 nout, 5831 Operand* bound_outs, const AsmConstraint* ins, u32 nin, Operand* bound_ins, 5832 const Sym* clobbers, u32 nclob) { 5833 AA64Asm* asmh = aa64_asm_open(t->c); 5834 aa64_inline_bind(asmh, outs, nout, bound_outs, ins, nin, bound_ins, clobbers, 5835 nclob); 5836 aa64_asm_run_template(asmh, t->mc, tmpl); 5837 aa64_asm_close(asmh); 5838 } 5839 5840 static void aa_asm_block_native(NativeTarget* t, const char* tmpl, 5841 const AsmConstraint* outs, u32 nout, 5842 NativeLoc* out_locs, const AsmConstraint* ins, 5843 u32 nin, const NativeLoc* in_locs, 5844 const Sym* clobbers, u32 nclob) { 5845 AANativeTarget* a = aa_of(t); 5846 SrcLoc loc = a->func ? a->func->loc : (SrcLoc){0, 0, 0}; 5847 static const NativeAsmNativeHooks hooks = { 5848 .panic = aa_asm_native_panic, 5849 .bound_reg = aa_asm_bound_reg, 5850 .bound_mem = aa_asm_bound_mem, 5851 .mem_base = aa_asm_native_mem_base_hook, 5852 .run_template = aa_asm_native_run_template_hook, 5853 }; 5854 native_asm_bind_native_operands(t, loc, tmpl, outs, nout, out_locs, ins, nin, 5855 in_locs, clobbers, nclob, &hooks); 5856 } 5857 5858 static const NativeOps aa_direct_ops = { 5859 .bind_param = aa_bind_param, 5860 .tail_call_unrealizable_reason = aa_no_tail, 5861 .va_start_ = aa_va_start_, 5862 .va_arg_ = aa_va_arg_, 5863 .va_end_ = aa_va_end_, 5864 .va_copy_ = aa_va_copy_, 5865 .asm_block = aa_direct_asm_block, 5866 }; 5867 5868 const NativeOps* aa64_native_direct_ops(void) { return &aa_direct_ops; }