native.c (171311B)
1 /* ARM32 (ARMv7-M Thumb-2, soft-float) NativeTarget backend. 2 * 3 * Built on the shared NativeDirectTarget/MCEmitter/NativeFrame substrate. This 4 * is the Phase-1 "walking skeleton" backend: it lowers the smoke set (return, 5 * ALU, compare + if/else, and a <=4-int-arg call) correctly; the op-group 6 * expansion (i64 lanes, bitfields, atomics, varargs, inline asm, the disasm 7 * tables, code-density tiers) is Phase 2. 8 * 9 * Frame model (single-pass, FP-anchored): 10 * prologue: PUSH {r7, lr} ; save the frame pointer + return address 11 * MOV r7, sp ; r7 = frame anchor (just below the saved pair) 12 * SUB sp, sp, #N ; reserve locals + outgoing args (patched at end) 13 * slots: addressed [r7, #-off] (off fixed at allocation, anchor fixed) 14 * epilogue: MOV sp, r7 ; drop the frame (size-independent) 15 * POP {r7, pc} ; restore r7 + return (Thumb bit from stacked lr) 16 * 17 * The NDT value-cache pool is the caller-saved arg registers r0..r3 (Lever 1: 18 * args materialize directly into their ABI registers); r12 (IP) is its O0 19 * scratch and a backend-private hook temporary; r7 is the frame pointer. 20 * Callee-saved r4..r11 are reserved from the -O0 pool (the optimizer allocates 21 * over them at -O1). */ 22 #include <string.h> 23 24 #include "abi/abi.h" 25 #include "arch/arm32/arm32.h" 26 #include "arch/arm32/asm.h" 27 #include "arch/arm32/isa.h" 28 #include "arch/arm32/regs.h" 29 #include "arch/arm32/variant.h" 30 #include "arch/mc.h" 31 #include "arch/native_target.h" 32 #include "cg/cgir.h" 33 #include "cg/native_argmove.h" 34 #include "cg/native_asm.h" 35 #include "cg/native_direct_target.h" 36 #include "cg/native_frame.h" 37 #include "cg/type.h" 38 #include "core/bytes.h" 39 #include "core/core.h" 40 #include "core/pool.h" 41 #include "obj/obj.h" 42 43 extern void debug_emit_row(Debug*, ObjSecId text_section, u32 offset, SrcLoc); 44 extern void debug_func_pc_range(Debug*, ObjSecId text_section, u32 begin_ofs, 45 u32 end_ofs); 46 47 #define ARM_FP 7u /* frame pointer (Thumb convention) */ 48 #define ARM_SCRATCH 12u /* IP: NDT operand-materialization / cycle-break scratch */ 49 /* Backend emit-internal temp for multi-step sequences (div/mod). lr is RESERVED 50 * (never holds an NDT operand) and dead in the body — its live value is saved on 51 * the stack by the prologue and restored via `pop {..,pc}`, so clobbering the lr 52 * register between calls is free and cannot collide with an NDT operand in IP. */ 53 #define ARM_TMP 14u 54 #define ARM_MAX_REG_ARG_MOVES 16u 55 /* Worst-case T32 instruction slots the single-pass deferred-`SUB sp` region must 56 * hold: an IP-staged large frame is MOVW + MOVT + SUB sp,sp,ip = 3 words. A 57 * small frame patches the first slot with one SUBW and NOP-fills the rest. */ 58 #define ARM_NDT_SUB_WORDS 3u 59 60 /* Incoming-arg window geometry (r7-relative). The prologue saves {r7, lr} (8 61 * bytes), so r7 sits 8 bytes below the caller's outgoing-arg block: incoming 62 * stack args begin at [r7 + 8]. A variadic callee additionally spills the GP 63 * argument registers r0..r3 (16 bytes) into a save area pushed ABOVE the saved 64 * pair, making them contiguous-below the named stack args; that shifts the 65 * stack-arg window down by 16 and locates the variadic register-save area at 66 * [r7 + 8]. */ 67 #define ARM_SAVED_PAIR_BYTES 8u 68 #define ARM_VA_GP_SAVE_BYTES 16u /* r0..r3 spilled for variadics */ 69 70 /* ============================ state ============================ */ 71 72 typedef struct Arm32NativeTarget { 73 NativeTarget base; /* MUST be first member (arm_of is a reinterpret cast) */ 74 const Arm32Variant* variant; 75 SrcLoc loc; 76 const CGFuncDesc* func; 77 NativeFrame frame; 78 u32 next_param_int; /* incoming param cursor: r0..r3 */ 79 u32 next_param_stack; 80 u8 has_sret; 81 u8 is_variadic; 82 NativeFrameSlot sret_ptr_slot; 83 /* Lazily-allocated 4-byte home for an indirect call target when the call has 84 * outgoing stack args. At -O0 the two backend staging registers (IP for the 85 * value, LR for a large-offset address) may both be consumed, so the callee 86 * cannot remain in either register across staging: it is spilled here and 87 * reloaded just before BLX. NATIVE_FRAME_SLOT_NONE until first needed. */ 88 NativeFrameSlot callee_spill_slot; 89 u32 func_start; 90 u32 prologue_sub_pos; /* file offset of the deferred SUB sp (patched at end) */ 91 MCLabel epilogue_label; 92 /* Known-frame (-O1) state. On the known-frame path the optimizer hands us the 93 * exact frame up front, so the prologue is emitted final (no deferred SUB to 94 * patch in arm_func_end). saved_reglist is the PUSH/POP register set (callee- 95 * saves + r7 + lr); saved_block_bytes is its size in bytes, which incoming 96 * stack args sit above (so bind_param adds it instead of the bare 8). */ 97 u8 known_frame; 98 u8 slim_prologue; /* leaf, no frame: emit BX lr, no PUSH/anchor */ 99 u16 saved_reglist; /* registers PUSHed by the prologue (POP restores via pc) */ 100 u32 saved_block_bytes; 101 /* Bytes of incoming stack-argument window this function owns (its own params 102 * beyond r0..r3, 8-byte rounded). A tail/sibling call is realizable only if the 103 * callee's outgoing stack args fit here — its outgoing stack args reuse this 104 * window in place. */ 105 u32 incoming_stack_size; 106 /* alloca: each dynamic allocation moves sp down and returns sp + max_outgoing 107 * (the block sits just above the call outgoing-arg area at the bottom of the 108 * frame). max_outgoing is only final at func_end, so the `ADD dst, sp, #off` 109 * is emitted with a placeholder and patched here. */ 110 u32 alloca_patch_pos[16]; 111 u8 alloca_patch_rd[16]; 112 u8 n_alloca_patch; 113 } Arm32NativeTarget; 114 115 static Arm32NativeTarget* arm_of(NativeTarget* t) { 116 return (Arm32NativeTarget*)t; 117 } 118 119 static _Noreturn void arm_panic(Arm32NativeTarget* a, const char* msg) { 120 compiler_panic(a->base.c, a->loc, "arm32 native target: %s", msg); 121 } 122 123 static u32 loc_reg(NativeLoc loc) { return loc.v.reg & 0xfu; } 124 125 /* Forward declarations for helpers referenced before their definitions (the 126 * aggregate copy is shared by INDIRECT param binding, INDIRECT returns, and the 127 * wide va_arg path). */ 128 static void arm_copy_bytes(NativeTarget* t, NativeAddr dst, NativeAddr src, 129 AggregateAccess acc); 130 131 /* ============================ emit ============================ */ 132 133 static void arm_emit_t16(MCEmitter* mc, u16 hw) { 134 u8 b[2]; 135 u32 ofs = obj_pos(mc->obj, mc->section_id); 136 b[0] = (u8)(hw & 0xffu); 137 b[1] = (u8)((hw >> 8) & 0xffu); 138 mc_emit_bytes(mc, b, sizeof b); 139 if (mc->debug) debug_emit_row(mc->debug, mc->section_id, ofs, mc->loc); 140 } 141 142 /* A 32-bit Thumb-2 instruction (passed as (hw1<<16)|hw2) is two LE half-words, 143 * hw1 first in memory. NOT a single LE word — never route through mc_emit32. */ 144 static void arm_emit_t32(MCEmitter* mc, u32 instr) { 145 u8 b[4]; 146 u32 ofs = obj_pos(mc->obj, mc->section_id); 147 u32 hw1 = (instr >> 16) & 0xffffu, hw2 = instr & 0xffffu; 148 b[0] = (u8)(hw1 & 0xffu); 149 b[1] = (u8)((hw1 >> 8) & 0xffu); 150 b[2] = (u8)(hw2 & 0xffu); 151 b[3] = (u8)((hw2 >> 8) & 0xffu); 152 mc_emit_bytes(mc, b, sizeof b); 153 if (mc->debug) debug_emit_row(mc->debug, mc->section_id, ofs, mc->loc); 154 } 155 156 static void arm_patch_t32(Arm32NativeTarget* a, u32 pos, u32 instr) { 157 u8 b[4]; 158 u32 hw1 = (instr >> 16) & 0xffffu, hw2 = instr & 0xffffu; 159 b[0] = (u8)(hw1 & 0xffu); 160 b[1] = (u8)((hw1 >> 8) & 0xffu); 161 b[2] = (u8)(hw2 & 0xffu); 162 b[3] = (u8)((hw2 >> 8) & 0xffu); 163 obj_patch(a->base.obj, a->base.mc->section_id, pos, b, 4); 164 } 165 166 /* Emit PUSH/POP, picking the 16-bit Thumb-1 form (STMDB sp!/LDMIA sp!) when the 167 * register list fits it (no flags). The 16-bit PUSH allows {r0..r7, lr}; POP 168 * allows {r0..r7, pc}; any other high register (r8..r12, or lr in a POP / pc in 169 * a PUSH) forces the 32-bit .W form. The common frame pairs {r7,lr}/{r7,pc} and 170 * a low-reg callee-save set narrow; mixed high callee-saves stay .W. */ 171 static void arm_emit_push(MCEmitter* mc, u32 reglist) { 172 u32 low = reglist & 0xffu, hi = reglist & ~0xffu; 173 if ((hi & ~(1u << 14u)) == 0u) /* only r0..r7 and optionally lr */ 174 arm_emit_t16(mc, arm_push16(low, (hi >> 14) & 1u)); 175 else 176 arm_emit_t32(mc, arm_push_w(reglist)); 177 } 178 static void arm_emit_pop(MCEmitter* mc, u32 reglist) { 179 u32 low = reglist & 0xffu, hi = reglist & ~0xffu; 180 if ((hi & ~(1u << 15u)) == 0u) /* only r0..r7 and optionally pc */ 181 arm_emit_t16(mc, arm_pop16(low, (hi >> 15) & 1u)); 182 else 183 arm_emit_t32(mc, arm_pop_w(reglist)); 184 } 185 186 /* ============================ register tables ============================ */ 187 188 #define ARM_PHYS_ARG(r, idx) \ 189 {.reg = (r), \ 190 .cls = NATIVE_REG_INT, \ 191 .abi_index = (idx), \ 192 .flags = NATIVE_REG_CALLER_SAVED | NATIVE_REG_ARG | \ 193 ((idx) < 2u ? NATIVE_REG_RET : 0), \ 194 .spill_cost = 1u, \ 195 .copy_cost = 1u} 196 #define ARM_PHYS_CALLEE(r) \ 197 {.reg = (r), \ 198 .cls = NATIVE_REG_INT, \ 199 .abi_index = 0xffu, \ 200 .flags = NATIVE_REG_ALLOCABLE | NATIVE_REG_CALLEE_SAVED, \ 201 .spill_cost = 4u, \ 202 .copy_cost = 1u} 203 #define ARM_PHYS_RESERVED_ROLE(r, role) \ 204 {.reg = (r), \ 205 .cls = NATIVE_REG_INT, \ 206 .abi_index = 0xffu, \ 207 .flags = NATIVE_REG_RESERVED | (role), \ 208 .spill_cost = 0u, \ 209 .copy_cost = 0u} 210 #define ARM_PHYS_RESERVED(r) \ 211 ARM_PHYS_RESERVED_ROLE((r), NATIVE_REG_NONE) 212 213 /* NDT (-O0) value-cache pool: the caller-saved arg registers r0..r3, fronted so 214 * a producer materializes a call's args directly into their ABI registers 215 * (Lever 1 via api_pack_call_args_in_order). The -O0 cache flushes at every 216 * call/branch/return, so caller-saved suffices and the prologue needs no 217 * callee-save spills. */ 218 static const Reg arm_ndt_int_allocable[] = {0u, 1u, 2u, 3u}; 219 static const Reg arm_int_scratch[] = {ARM_SCRATCH}; 220 /* O1 operand temporaries are the caller-saved low registers. They are excluded 221 * from persistent allocation and every Thumb2 operand hook accepts them. IP 222 * and LR remain backend-private: many hooks use those two internally, so 223 * exposing either as an operand location would couple independent lifetimes. */ 224 static const Reg arm_int_emit_temps[] = {0u, 1u, 2u, 3u}; 225 static const Reg arm_int_asm_temps[] = {ARM_TMP}; 226 static const Reg arm_direct_asm_int[] = {0u, 1u, 2u, 3u, 4u, 5u, 227 6u, 8u, 9u, 10u, 11u}; 228 229 static const NativePhysRegInfo arm_int_phys[] = { 230 ARM_PHYS_ARG(0u, 0u), ARM_PHYS_ARG(1u, 1u), 231 ARM_PHYS_ARG(2u, 2u), ARM_PHYS_ARG(3u, 3u), 232 ARM_PHYS_CALLEE(4u), ARM_PHYS_CALLEE(5u), 233 ARM_PHYS_CALLEE(6u), 234 ARM_PHYS_RESERVED_ROLE(7u, NATIVE_REG_CALLEE_SAVED), /* fp */ 235 ARM_PHYS_CALLEE(8u), ARM_PHYS_CALLEE(9u), 236 ARM_PHYS_CALLEE(10u), ARM_PHYS_CALLEE(11u), 237 ARM_PHYS_RESERVED_ROLE(12u, NATIVE_REG_CALLER_SAVED), /* ip/temp */ 238 ARM_PHYS_RESERVED(13u), /* sp */ 239 ARM_PHYS_RESERVED(14u), /* lr */ 240 ARM_PHYS_RESERVED(15u), /* pc */ 241 }; 242 243 static int arm_resolve_name(const NativeRegInfo* ri, Slice name, Reg* out, 244 NativeAllocClass* cls_out) { 245 char buf[16]; 246 uint32_t dwarf; 247 (void)ri; 248 if (!name.s || !name.len || name.len >= sizeof buf) return 1; 249 memcpy(buf, name.s, name.len); 250 buf[name.len] = '\0'; 251 if (arm32_register_index(buf, &dwarf) != 0) return 1; 252 if (dwarf <= 15u) { 253 *cls_out = NATIVE_REG_INT; 254 *out = (Reg)dwarf; 255 return 0; 256 } 257 return 1; 258 } 259 260 static int arm_asm_operand_reg_ok(const NativeRegInfo* ri, NativeAllocClass cls, 261 Reg reg) { 262 (void)ri; 263 if (cls != NATIVE_REG_INT) return 0; 264 if (reg <= 3u) return 1; /* r0..r3 */ 265 if (reg >= 4u && reg <= 11u && reg != 7u) return 1; /* r4..r11 except fp */ 266 return 0; 267 } 268 269 static int arm_asm_constraint_reg(const NativeRegInfo* ri, const char* body, 270 NativeAllocClass* cls_out, Reg* fixed_out, 271 u32* allowed_mask_out) { 272 (void)ri; 273 if (!body || !body[0]) return 0; 274 if (fixed_out) *fixed_out = REG_NONE; 275 if (allowed_mask_out) *allowed_mask_out = 0; 276 if ((body[0] == 'r' || body[0] == 'l') && body[1] == '\0') { 277 if (cls_out) *cls_out = NATIVE_REG_INT; 278 if (body[0] == 'l' && allowed_mask_out) 279 *allowed_mask_out = 0x000000ffu; /* r0..r7, filtered by reg_ok. */ 280 return 1; 281 } 282 return 0; 283 } 284 285 static const NativeAllocClassInfo arm_classes[] = { 286 {.cls = NATIVE_REG_INT, 287 .ndt_allocable = arm_ndt_int_allocable, 288 .ndt_allocable_count = 289 sizeof arm_ndt_int_allocable / sizeof arm_ndt_int_allocable[0], 290 .scratch = arm_int_scratch, 291 .nscratch = sizeof arm_int_scratch / sizeof arm_int_scratch[0], 292 .emit_temps = arm_int_emit_temps, 293 .nemit_temps = sizeof arm_int_emit_temps / sizeof arm_int_emit_temps[0], 294 .asm_temps = arm_int_asm_temps, 295 .nasm_temps = sizeof arm_int_asm_temps / sizeof arm_int_asm_temps[0], 296 .direct_asm_allocable = arm_direct_asm_int, 297 .ndirect_asm_allocable = 298 sizeof arm_direct_asm_int / sizeof arm_direct_asm_int[0], 299 .emit_cache_mask = 0x0000000fu, /* r0..r3; hooks use private ip/lr */ 300 .phys = arm_int_phys, 301 .nphys = sizeof arm_int_phys / sizeof arm_int_phys[0]}, 302 }; 303 304 static const NativeRegInfo arm_reg_info = { 305 .classes = arm_classes, 306 .nclasses = sizeof arm_classes / sizeof arm_classes[0], 307 .ndt_caller_saved_only = 1u, 308 .ndt_result_reg_stable = 1u, 309 .resolve_name = arm_resolve_name, 310 .asm_operand_reg_ok = arm_asm_operand_reg_ok, 311 .asm_constraint_reg = arm_asm_constraint_reg, 312 }; 313 314 /* ============================ legality ============================ */ 315 316 /* Resolve `rd = ra <ADD|SUB> imm` into a concrete non-negative (is_sub, mag) 317 * immediate. Adding `imm` equals subtracting `-imm` and vice versa (modulo 318 * 2^32), so a small negative constant folds into the opposite operation: SUB #3 319 * for `+ -3`, ADD #3 for `- -3`. Returns 1 (and fills is_sub/mag) when the 320 * magnitude is a Thumb modified-immediate OR fits the 12-bit ADDW/SUBW field; 321 * 0 when the constant must be materialized into a register first. The emit path 322 * (arm_emit_alu_imm) and the legality predicate (arm_imm_legal) BOTH route 323 * through this, so a value can never be accepted as legal yet mis-encoded — 324 * the bug where `SUB #-3` legalized via the -imm trick but emitted SUBW with a 325 * 12-bit-truncated 0xFFFFFFFD. */ 326 static int arm_resolve_addsub_imm(BinOp op, i64 imm, int* is_sub, u32* mag) { 327 /* k = the value actually added to ra (subtracting imm adds -imm). */ 328 u32 k = (u32)(op == BO_ISUB ? (u64)0 - (u64)imm : (u64)imm); 329 u32 nk = (u32)((u64)0 - (u64)k); /* the magnitude of the SUB alternative */ 330 u32 enc; 331 if (thumb_expand_imm_encode(k, &enc) || k <= 0xfffu) { 332 *is_sub = 0; 333 *mag = k; 334 return 1; 335 } 336 if (thumb_expand_imm_encode(nk, &enc) || nk <= 0xfffu) { 337 *is_sub = 1; 338 *mag = nk; 339 return 1; 340 } 341 return 0; 342 } 343 344 static int arm_imm_legal(NativeTarget* t, NativeImmUse use, u32 op, 345 KitCgTypeId type, i64 imm) { 346 u32 enc; 347 (void)t; 348 (void)type; 349 switch (use) { 350 case NATIVE_IMM_MOVE: 351 return 1; /* MOVW/MOVT materializes any 32-bit value */ 352 case NATIVE_IMM_CMP: 353 return thumb_expand_imm_encode((u32)imm, &enc); 354 case NATIVE_IMM_BINOP: 355 switch ((BinOp)op) { 356 case BO_IADD: 357 case BO_ISUB: { 358 int is_sub; 359 u32 mag; 360 return arm_resolve_addsub_imm((BinOp)op, imm, &is_sub, &mag); 361 } 362 case BO_AND: 363 case BO_OR: 364 case BO_XOR: 365 return thumb_expand_imm_encode((u32)imm, &enc); 366 case BO_SHL: 367 case BO_SHR_S: 368 case BO_SHR_U: 369 return imm >= 0 && imm < 32; 370 default: 371 return 0; 372 } 373 case NATIVE_IMM_ADDR_OFFSET: 374 return imm >= -255 && imm <= 4095; 375 } 376 return 0; 377 } 378 379 static int arm_addr_legal(NativeTarget* t, const NativeAddr* addr, 380 MemAccess mem) { 381 (void)t; 382 (void)mem; 383 if (!addr) return 0; 384 /* Base + 12-bit positive or 8-bit signed offset, no scaled index in v1. */ 385 if (addr->base_kind == NATIVE_ADDR_BASE_FRAME_VALUE) return 0; 386 if (addr->index_kind != NATIVE_ADDR_INDEX_NONE) return 0; 387 return addr->offset >= -255 && addr->offset <= 4095; 388 } 389 390 /* Capability hook for the arch-neutral L7 shift-into-ALU fold 391 * (src/opt/pass_combine.c). arm32 has the 32-bit shifted-register forms wired up 392 * (arm_binop's register path emits ADD.W/SUB.W/AND.W/ORR.W/EOR.W rd,rn,rm,LSL #k 393 * via the arm_*_reg_lsl encoders), but the capability is left OFF here. 394 * 395 * WHY OFF: opting in surfaces a latent miscompile in the shared combine pass for 396 * the `base + (i << k)` address pattern when k exceeds the address index-scale 397 * range (k>=4, e.g. a 16-byte struct stride). The order is: addr-synth's index 398 * fold (pass_combine.c rule (b)) only absorbs a shift of 1..3 into the EA's 399 * log2_scale; for k=4 it declines, leaving the `i<<4` as an explicit IADD that 400 * computes the element address. L7 then stamps a shift rider on that IADD's rhs. 401 * On a later combine iteration, addr-synth rule (a) (reg+reg) folds that IADD 402 * into the indirect's index but copies only rhs.v.reg, DROPPING rhs.shift — so 403 * the `*16` silently becomes `*1` and every element aliases element 0 404 * (test/parse/cases/opt_03_addr_index_alias_store regresses 42 -> 1 under qemu). 405 * aa64 avoids this because its addr_legal accepts the scaled-index EA, so 406 * addr-synth claims the address shift before L7 ever sees it; arm32's addr_legal 407 * rejects every indexed address (no scaled-index load/store in v1), so the 408 * shift stays in the ALU lane and the rider-drop bites. Re-enable once the 409 * shared pass preserves rhs.shift across the rule-(a) index fold; the encoders + 410 * arm_binop branch + decode round-trip test are already in place. */ 411 static int arm_can_fold_shift_into_alu(NativeTarget* t) { 412 (void)t; 413 return 0; 414 } 415 416 /* ============================ 16-bit narrow helpers ============================ 417 * The 16-bit Thumb-1 data-processing forms used by the density pass always set 418 * the condition flags (there is no S=0 narrow DP form outside an IT block). kit 419 * keeps flags live only across CMP→IT (and comparisons always re-emit their 420 * CMP), so binop/move/shift/ldst results are FLAG-DEAD — narrowing them to a 421 * flag-setting 16-bit encoding is observationally identical. The MOV.W sites 422 * that sit between a CMP and an IT-predicated consumer (arm_cmp, arm_unop's 423 * UO_NOT, arm_emit_setcc) are NOT routed through here and stay 32-bit. */ 424 static inline int arm_low_reg(u32 r) { return r <= 7u; } 425 /* True when an unsigned immediate fits an n-bit field. */ 426 static inline int arm_uimm_fits(u32 v, u32 nbits) { return v < (1u << nbits); } 427 428 /* ============================ memory ============================ */ 429 430 static void arm_emit_load_u32(Arm32NativeTarget* a, u32 rd, u32 v); 431 static u32 arm_pick_scratch(Arm32NativeTarget* a, u32 rt, u32 pinned); 432 433 /* Add a signed compile-time constant to rd in place (rd += k), using a reserved 434 * scratch only for a magnitude exceeding the inline immediate forms. */ 435 static void arm_emit_add_const(Arm32NativeTarget* a, u32 rd, i64 k) { 436 MCEmitter* mc = a->base.mc; 437 u32 enc, mag; 438 if (k == 0) return; 439 mag = (u32)(k < 0 ? -k : k); 440 /* 16-bit `ADD/SUB sp,sp,#imm7*4` (no flags) for the SP-adjust forms (e.g. the 441 * variadic GP-save reclaim). Requires rd==sp, a non-zero multiple of 4, and 442 * imm7 in range. */ 443 if (rd == 13u && (mag & 3u) == 0u && (mag >> 2) <= 0x7fu) { 444 arm_emit_t16(mc, k < 0 ? arm_sub_sp_sp_imm16(mag >> 2) 445 : arm_add_sp_sp_imm16(mag >> 2)); 446 return; 447 } 448 if (mag <= 0xfffu) { 449 arm_emit_t32(mc, k < 0 ? arm_sub_imm12(rd, rd, mag) 450 : arm_add_imm12(rd, rd, mag)); 451 } else if (thumb_expand_imm_encode(mag, &enc)) { 452 arm_emit_t32(mc, arm_dp_imm(k < 0 ? 13u : 8u, 0u, rd, rd, enc)); 453 } else { 454 u32 s = arm_pick_scratch(a, rd, 0u); 455 arm_emit_load_u32(a, s, mag); 456 arm_emit_t32(mc, k < 0 ? arm_sub_reg(rd, rd, s) : arm_add_reg(rd, rd, s)); 457 } 458 } 459 460 /* Materialize the runtime address of `sym` (+ addend) into register `rd`. 461 * 462 * arm32's house strategy is MOVW/MOVT absolute (Cortex-M has no literal pools 463 * and the bare-metal lane is non-PIC): 464 * MOVW rd, #:lower16:sym ; R_ARM_THM_MOVW_ABS_NC -> S[15:0] 465 * MOVT rd, #:upper16:sym ; R_ARM_THM_MOVT_ABS -> S[31:16] 466 * A constant byte `addend` is applied as a SEPARATE add, NOT folded into the 467 * reloc: ARM uses REL relocations (the addend rides in the relocated field), and 468 * the MOVW/MOVT pair splits a 32-bit value across two 16-bit fields whose 469 * per-half (S+A) computation is carry-lossy across the 16-bit boundary. Keeping 470 * the reloc addend 0 and adding the offset here is both always-correct and the 471 * contract elf_arm_reloc_field_addend documents (and ld.lld-compatible). */ 472 static void arm_emit_global_addr(Arm32NativeTarget* a, u32 rd, ObjSymId sym, 473 i64 addend) { 474 MCEmitter* mc = a->base.mc; 475 u32 pos = mc_pos(mc); 476 arm_emit_t32(mc, arm_movw(rd, 0u)); 477 mc_emit_reloc_at(mc, mc->section_id, pos, R_ARM_THM_MOVW_ABS_NC, sym, 0, 1, 0); 478 pos = mc_pos(mc); 479 arm_emit_t32(mc, arm_movt(rd, 0u)); 480 mc_emit_reloc_at(mc, mc->section_id, pos, R_ARM_THM_MOVT_ABS, sym, 0, 1, 0); 481 arm_emit_add_const(a, rd, addend); 482 } 483 484 /* The unified memory-access address layer (ARM32.md §1) lives here. Every 485 * load/store/copy resolves a NativeAddr to a legal (base register, in-range 486 * displacement) through ONE function, arm_resolve_mem, with ONE temporary- 487 * reservation invariant asserted in one place. 488 * 489 * Backend budget: IP (r12, ARM_SCRATCH) and LR (r14, ARM_TMP) are reserved for 490 * hook-internal staging; they are deliberately not O1 operand locations. r7 491 * (FP) holds the frame anchor and is never temporary storage. A single access 492 * needs at most one address-staging register, distinct from transfer register 493 * `rt`. The picker 494 * draws it from {IP,LR} minus {rt} minus a caller `pinned` mask (held bases the 495 * access must preserve) and panics if none is free, so the invariant is explicit 496 * rather than relying on incidental liveness. */ 497 static void arm_emit_base_off(Arm32NativeTarget* a, u32 rd, u32 base, i32 off); 498 static void arm_load_addr(NativeTarget* t, NativeLoc dst, NativeAddr addr); 499 500 /* True if every byte of a `span`-byte access at signed displacement `off` fits 501 * the Thumb-2 single-granule immediate forms (T4 negative -255..0, T3 positive 502 * 0..4095): off >= -255 and off+span <= 4096 implies every granule offset in 503 * [off, off+span-1] lands in [-255, 4095]. */ 504 static int arm_span_inline(i32 off, u32 span) { 505 return off >= -255 && off + (i32)span <= 4096; 506 } 507 508 /* Pick a reserved staging register from {LR, IP} that is neither the transfer 509 * register `rt` (0xff = none) nor in `pinned` (held base registers the access 510 * must preserve). LR (ARM_TMP) is preferred because IP (ARM_SCRATCH) is the NDT's 511 * operand-materialization scratch and can hold a live value across the access 512 * (e.g. a store whose value the NDT homed in IP); LR is only ever a transient 513 * backend temp within a single emit, so staging an address there is free. The 514 * single point that enforces the scratch invariant. */ 515 static u32 arm_pick_scratch(Arm32NativeTarget* a, u32 rt, u32 pinned) { 516 u32 avoid = (rt < 16u ? (1u << rt) : 0u) | pinned; 517 if (!(avoid & (1u << ARM_TMP))) return ARM_TMP; 518 if (!(avoid & (1u << ARM_SCRATCH))) return ARM_SCRATCH; 519 arm_panic(a, "memory access: no free address-staging scratch"); 520 } 521 522 typedef struct ArmMemAddr { 523 u32 base; /* base register to address through */ 524 i32 off; /* signed displacement; access [base, #off (+lane/granule)] */ 525 } ArmMemAddr; 526 527 /* Materialize the unsigned 32-bit constant `v` into `rd` (MOVW + optional MOVT). 528 * Used to stage large frame/memory offsets and SUB-sp amounts that exceed the 529 * inline immediate forms. */ 530 static void arm_emit_load_u32(Arm32NativeTarget* a, u32 rd, u32 v) { 531 MCEmitter* mc = a->base.mc; 532 u32 enc; 533 if (thumb_expand_imm_encode(v, &enc)) { 534 arm_emit_t32(mc, arm_mov_imm(rd, enc)); 535 return; 536 } 537 if (thumb_expand_imm_encode(~v, &enc)) { 538 arm_emit_t32(mc, arm_mvn_imm(rd, enc)); 539 return; 540 } 541 arm_emit_t32(mc, arm_movw(rd, v & 0xffffu)); 542 if ((v >> 16) != 0u) arm_emit_t32(mc, arm_movt(rd, (v >> 16) & 0xffffu)); 543 } 544 545 /* Resolve `addr` for a `span`-byte access whose transfer register is `rt` 546 * (0xff = none; the wide path pins both lane registers via `pinned` instead), 547 * returning a legal (base, off). A FRAME/REG base already in range is used 548 * directly; otherwise the full effective address is materialized once into a 549 * picked scratch (off := 0). A GLOBAL base is always materialized via MOVW/MOVT, 550 * folding addr.offset into the reloc addend so off := 0 — this is the single 551 * point that guarantees a store's global address never lands in the value 552 * register (the i64-store-to-global miscompile). */ 553 static ArmMemAddr arm_resolve_mem(Arm32NativeTarget* a, const NativeAddr* addr, 554 u32 rt, u32 span, u32 pinned) { 555 ArmMemAddr r; 556 /* Scaled-index address (base + offset + index<<scale): materialize the whole 557 * effective address once into a reserved scratch and return (scratch, 0). 558 * arm32 has no LDRD/STRD indexed form, so a wide (8-byte) indexed access must 559 * share ONE computed base across both lanes; doing it here keeps every base 560 * kind (frame / reg / global) uniform and is the §1 single-base guarantee for 561 * the high lane. The scratch aliases neither the transfer register(s) — rt and 562 * `pinned` — nor the index register. */ 563 if (addr->index_kind == NATIVE_ADDR_INDEX_REG) { 564 MCEmitter* mc = a->base.mc; 565 u32 idx = addr->index.reg & 0xfu; 566 u32 s = arm_pick_scratch(a, rt, pinned | (1u << idx)); 567 switch (addr->base_kind) { 568 case NATIVE_ADDR_BASE_FRAME: { 569 NativeFrameSlotEntry* e = 570 native_frame_slot_at(&a->frame, addr->base.frame); 571 arm_emit_base_off(a, s, ARM_FP, -(i32)e->off + addr->offset); 572 break; 573 } 574 case NATIVE_ADDR_BASE_REG: 575 arm_emit_base_off(a, s, addr->base.reg & 0xfu, addr->offset); 576 break; 577 case NATIVE_ADDR_BASE_GLOBAL: 578 arm_emit_global_addr(a, s, addr->base.global.sym, 579 addr->base.global.addend + addr->offset); 580 break; 581 default: 582 arm_panic(a, "unsupported indexed addressing mode"); 583 } 584 arm_emit_t32(mc, arm_add_reg_lsl(s, s, idx, addr->log2_scale)); 585 r.base = s; 586 r.off = 0; 587 return r; 588 } 589 switch (addr->base_kind) { 590 case NATIVE_ADDR_BASE_FRAME: { 591 NativeFrameSlotEntry* s = 592 native_frame_slot_at(&a->frame, addr->base.frame); 593 i32 off = -(i32)s->off + addr->offset; 594 if (arm_span_inline(off, span)) { 595 r.base = ARM_FP; 596 r.off = off; 597 return r; 598 } 599 r.base = arm_pick_scratch(a, rt, pinned | (1u << ARM_FP)); 600 arm_emit_base_off(a, r.base, ARM_FP, off); 601 r.off = 0; 602 return r; 603 } 604 case NATIVE_ADDR_BASE_REG: { 605 u32 base = addr->base.reg & 0xfu; 606 if (arm_span_inline(addr->offset, span)) { 607 r.base = base; 608 r.off = addr->offset; 609 return r; 610 } 611 r.base = arm_pick_scratch(a, rt, pinned | (1u << base)); 612 arm_emit_base_off(a, r.base, base, addr->offset); 613 r.off = 0; 614 return r; 615 } 616 case NATIVE_ADDR_BASE_GLOBAL: 617 r.base = arm_pick_scratch(a, rt, pinned); 618 arm_emit_global_addr(a, r.base, addr->base.global.sym, 619 addr->base.global.addend + addr->offset); 620 r.off = 0; 621 return r; 622 default: 623 arm_panic(a, "unsupported addressing mode"); 624 } 625 } 626 627 /* Emit one in-range granule access: LDR/STR (+B/H) of `rt` at [base, #off], 628 * dispatching width. The caller (via arm_resolve_mem) guarantees `off` is in the 629 * T3/T4 immediate range for this width. Loads zero-extend; signed narrowing is a 630 * separate convert. */ 631 static void arm_emit_mem_one(Arm32NativeTarget* a, int is_load, u32 rt, u32 base, 632 i32 off, u32 size) { 633 MCEmitter* mc = a->base.mc; 634 u32 t3hw1, t4hw1; 635 /* 16-bit narrow forms (no flags, biggest -O0 win since loads/stores dominate): 636 * T2 [sp, #imm8*4]: word only, rt low reg, base == sp (NOT r7 — the frame is 637 * r7-anchored, so this applies only to genuine sp-based slots). 638 * T1 [rn, #imm5*scale]: rt + rn both low regs (r7/ARM_FP qualifies), positive 639 * offset that is an exact multiple of the access size and fits 5 bits. 640 * Signed loads (LDRSB/LDRSH) have no 16-bit immediate-offset form, so the 641 * width dispatch below only narrows the unsigned word/byte/half loads + stores; 642 * the resolver never asks this function for a signed-narrowing load (that is a 643 * separate convert). */ 644 if (size == 4u && base == ARM_SP && arm_low_reg(rt) && off >= 0 && 645 (off & 3) == 0 && arm_uimm_fits((u32)off >> 2, 8u)) { 646 arm_emit_t16(mc, arm_ldst_sp_16(is_load ? 0x9800u : 0x9000u, rt, 647 (u32)off >> 2)); 648 return; 649 } 650 if (arm_low_reg(rt) && arm_low_reg(base) && off >= 0) { 651 u32 uoff = (u32)off; 652 if (size == 4u && (uoff & 3) == 0 && arm_uimm_fits(uoff >> 2, 5u)) { 653 arm_emit_t16(mc, arm_ldst_i5_16(is_load ? 0x6800u : 0x6000u, rt, base, 654 uoff >> 2)); 655 return; 656 } 657 if (size == 2u && (uoff & 1) == 0 && arm_uimm_fits(uoff >> 1, 5u)) { 658 arm_emit_t16(mc, arm_ldst_i5_16(is_load ? 0x8800u : 0x8000u, rt, base, 659 uoff >> 1)); 660 return; 661 } 662 if (size == 1u && arm_uimm_fits(uoff, 5u)) { 663 arm_emit_t16(mc, arm_ldst_i5_16(is_load ? 0x7800u : 0x7000u, rt, base, 664 uoff)); 665 return; 666 } 667 } 668 if (size == 1u) { 669 t3hw1 = is_load ? 0xf890u : 0xf880u; 670 t4hw1 = is_load ? 0xf810u : 0xf800u; 671 } else if (size == 2u) { 672 t3hw1 = is_load ? 0xf8b0u : 0xf8a0u; 673 t4hw1 = is_load ? 0xf830u : 0xf820u; 674 } else { 675 t3hw1 = is_load ? 0xf8d0u : 0xf8c0u; 676 t4hw1 = is_load ? 0xf850u : 0xf840u; 677 } 678 if (off >= 0 && off <= 4095) { 679 arm_emit_t32(mc, arm_t32(t3hw1 | base, (rt << 12) | (u32)off)); 680 } else if (off < 0 && off >= -255) { 681 arm_emit_t32(mc, arm_ldst_t4(t4hw1, rt, base, (u32)(-off), 0u)); 682 } else { 683 arm_panic(a, "arm_emit_mem_one: offset out of range (resolver bug)"); 684 } 685 } 686 687 /* Emit a load (is_load=1) or store of `reg` at `addr`, dispatching width from 688 * mem.size. A wide (8-byte i64/double) access splits into two 4-byte lanes over 689 * the register pair rt:rt+1 sharing ONE resolved base — the high lane's address 690 * is thus computed correctly for frame/reg/global uniformly. */ 691 static void arm_emit_mem(Arm32NativeTarget* a, int is_load, NativeLoc reg, 692 NativeAddr addr, MemAccess mem) { 693 u32 rt = loc_reg(reg); 694 u32 size = mem.size ? mem.size : native_type_size(&a->base, reg.type); 695 if (size == 0u) arm_panic(a, "zero-width memory access"); 696 if (size > 8u) arm_panic(a, "memory access wider than 8 bytes"); 697 if (size == 8u) { 698 u32 hi = (rt + 1u) & 0xfu; 699 ArmMemAddr r = 700 arm_resolve_mem(a, &addr, 0xffu, 8u, (1u << rt) | (1u << hi)); 701 arm_emit_mem_one(a, is_load, rt, r.base, r.off, 4u); 702 arm_emit_mem_one(a, is_load, hi, r.base, r.off + 4, 4u); 703 return; 704 } 705 if (size != 1u && size != 2u && size != 4u) { 706 NativePartChunkIter it = native_part_chunks(size, 4u); 707 KitCgTypeId lane_type = builtin_id(KIT_CG_BUILTIN_I32); 708 u32 carrier_mask = 1u << rt; 709 u32 addr_inputs = 0u; 710 u32 chunk_off, chunk_size; 711 u32 tmp = REG_NONE; 712 ArmMemAddr r; 713 if (size > 4u) carrier_mask |= 1u << ((rt + 1u) & 0xfu); 714 if (addr.base_kind == NATIVE_ADDR_BASE_REG) 715 addr_inputs |= 1u << (addr.base.reg & 0xfu); 716 if (addr.index_kind == NATIVE_ADDR_INDEX_REG) 717 addr_inputs |= 1u << (addr.index.reg & 0xfu); 718 if (is_load && (carrier_mask & addr_inputs)) { 719 /* A single LDR may use the destination as its base (`ldr r0, [r0]`). 720 * An exact part is a sequence, so doing that would destroy the address 721 * before its later chunks. Retain the complete effective address in a 722 * private register first; avoid every address input because load_addr 723 * still needs them while it constructs the indexed form. */ 724 u32 addr_reg = arm_pick_scratch(a, 0xffu, carrier_mask | addr_inputs); 725 NativeLoc addr_loc = 726 native_loc_reg(lane_type, NATIVE_REG_INT, (Reg)addr_reg); 727 arm_load_addr(&a->base, addr_loc, addr); 728 r.base = addr_reg; 729 r.off = 0; 730 } else { 731 /* Resolve once for the full span. Besides avoiding repeated global/far 732 * address formation, this gives every chunk one stable base and makes 733 * scratch ownership explicit: a later pack temporary must avoid r.base. */ 734 r = arm_resolve_mem(a, &addr, 0xffu, size, carrier_mask); 735 } 736 /* ABIArgPart.size is exact even when the carrier is a 32-bit register. 737 * Split 3/5/6/7-byte parts into bounded 4/2/1-byte accesses and pack each 738 * chunk into the appropriate low/high carrier lane. This boundary is also 739 * used by generic call-result moves, so no caller can silently widen a 740 * semantic part into an adjacent frame object. */ 741 while (native_part_chunk_next(&it, &chunk_off, &chunk_size)) { 742 u32 lane_reg = (rt + chunk_off / 4u) & 0xfu; 743 u32 lane_shift = (chunk_off & 3u) * 8u; 744 u32 transfer_reg = lane_reg; 745 if (lane_shift) { 746 if (tmp == REG_NONE) 747 tmp = arm_pick_scratch(a, 0xffu, 748 carrier_mask | (1u << (r.base & 0xfu))); 749 transfer_reg = tmp; 750 if (!is_load) 751 arm_emit_t32(a->base.mc, 752 arm_shift_imm(1u, tmp, lane_reg, lane_shift)); 753 } 754 arm_emit_mem_one(a, is_load, transfer_reg, r.base, 755 r.off + (i32)chunk_off, chunk_size); 756 if (is_load && lane_shift) 757 arm_emit_t32(a->base.mc, 758 arm_orr_reg_lsl(lane_reg, lane_reg, tmp, lane_shift)); 759 } 760 return; 761 } 762 { 763 ArmMemAddr r = arm_resolve_mem(a, &addr, rt, size, 0u); 764 arm_emit_mem_one(a, is_load, rt, r.base, r.off, size); 765 } 766 } 767 768 /* ============================ moves / immediates ============================ */ 769 770 static void arm_load_imm(NativeTarget* t, NativeLoc dst, i64 imm) { 771 MCEmitter* mc = t->mc; 772 u32 rd = loc_reg(dst); 773 u32 v = (u32)imm; 774 u32 enc; 775 /* MOVS rd,#imm8 (low reg, 0..255) — flag-dead 16-bit immediate move. This is 776 * the general NDT load_imm hook, never the CMP→IT flag-preserving MOV.W (those 777 * sites call arm_mov_imm directly), so setting flags here is observationally 778 * inert. */ 779 if (arm_low_reg(rd) && v <= 0xffu) { 780 arm_emit_t16(mc, arm_movs_imm8(rd, v)); 781 return; 782 } 783 if (thumb_expand_imm_encode(v, &enc)) { 784 arm_emit_t32(mc, arm_mov_imm(rd, enc)); 785 return; 786 } 787 if (thumb_expand_imm_encode(~v, &enc)) { 788 arm_emit_t32(mc, arm_mvn_imm(rd, enc)); 789 return; 790 } 791 arm_emit_t32(mc, arm_movw(rd, v & 0xffffu)); 792 if ((v >> 16) != 0u) arm_emit_t32(mc, arm_movt(rd, (v >> 16) & 0xffffu)); 793 } 794 795 static void arm_move(NativeTarget* t, NativeLoc dst, NativeLoc src) { 796 u32 rd = loc_reg(dst), rs = loc_reg(src); 797 if (rd == rs) return; 798 arm_emit_t16(t->mc, arm_mov_hi(rd, rs)); 799 } 800 801 static void arm_load(NativeTarget* t, NativeLoc dst, NativeAddr addr, 802 MemAccess mem) { 803 arm_emit_mem(arm_of(t), 1, dst, addr, mem); 804 } 805 static void arm_store(NativeTarget* t, NativeAddr addr, NativeLoc src, 806 MemAccess mem) { 807 arm_emit_mem(arm_of(t), 0, src, addr, mem); 808 } 809 static void arm_spill(NativeTarget* t, NativeLoc src, NativeFrameSlot slot, 810 MemAccess mem) { 811 NativeAddr addr; 812 memset(&addr, 0, sizeof addr); 813 addr.base_kind = NATIVE_ADDR_BASE_FRAME; 814 addr.base.frame = slot; 815 addr.base_type = src.type; 816 arm_emit_mem(arm_of(t), 0, src, addr, mem); 817 } 818 static void arm_reload(NativeTarget* t, NativeLoc dst, NativeFrameSlot slot, 819 MemAccess mem) { 820 NativeAddr addr; 821 memset(&addr, 0, sizeof addr); 822 addr.base_kind = NATIVE_ADDR_BASE_FRAME; 823 addr.base.frame = slot; 824 addr.base_type = dst.type; 825 arm_emit_mem(arm_of(t), 1, dst, addr, mem); 826 } 827 828 /* Materialize a full address into `rd`: base + signed byte offset. */ 829 static void arm_emit_base_off(Arm32NativeTarget* a, u32 rd, u32 base, i32 off) { 830 MCEmitter* mc = a->base.mc; 831 u32 enc; 832 if (off == 0) { 833 if (rd != base) arm_emit_t16(mc, arm_mov_hi(rd, base)); 834 } else if (off > 0 && off <= 0xfff) { 835 arm_emit_t32(mc, arm_add_imm12(rd, base, (u32)off)); 836 } else if (off < 0 && thumb_expand_imm_encode((u32)(-off), &enc)) { 837 arm_emit_t32(mc, arm_dp_imm(13u, 0u, rd, base, enc)); /* SUB rd, base, #-off */ 838 } else { 839 /* Large frame/array offset: stage |off| in a temp then add/sub into rd. The 840 * temp must not alias `base` (read after write). Use rd itself when rd != 841 * base (the resolver guarantees a staged base reg differs from its source); 842 * only when rd aliases base (load_addr's address-of-an-indexed-REG case) 843 * fall back to the reserved ARM_TMP, which is dead in that context. */ 844 u32 tmp = (rd != base) ? rd : arm_pick_scratch(a, 0xffu, 1u << base); 845 arm_emit_load_u32(a, tmp, (u32)(off < 0 ? -off : off)); 846 arm_emit_t32(mc, off < 0 ? arm_sub_reg(rd, base, tmp) 847 : arm_add_reg(rd, base, tmp)); 848 } 849 } 850 851 /* Materialize the address of a NativeAddr into the destination register. 852 * 853 * The NDT routes a base+index (or any addr the addr_legal hook rejects) 854 * through here and then treats `rd` as a plain BASE_REG, so this MUST fold any 855 * scaled index: rd = base + offset + (index << log2_scale). A GLOBAL base's 856 * byte offset folds into the MOVW/MOVT reloc addend; FRAME/REG bases add the 857 * offset explicitly. `rd` is a fresh NDT scratch, distinct from the pinned 858 * index register, so no aliasing post-add is needed. */ 859 static void arm_load_addr(NativeTarget* t, NativeLoc dst, NativeAddr addr) { 860 Arm32NativeTarget* a = arm_of(t); 861 MCEmitter* mc = t->mc; 862 u32 rd = loc_reg(dst); 863 if (addr.base_kind == NATIVE_ADDR_BASE_GLOBAL) { 864 arm_emit_global_addr(a, rd, addr.base.global.sym, 865 addr.base.global.addend + addr.offset); 866 } else if (addr.base_kind == NATIVE_ADDR_BASE_FRAME) { 867 NativeFrameSlotEntry* s = native_frame_slot_at(&a->frame, addr.base.frame); 868 arm_emit_base_off(a, rd, ARM_FP, -(i32)s->off + addr.offset); 869 } else if (addr.base_kind == NATIVE_ADDR_BASE_FRAME_VALUE) { 870 NativeAddr load; 871 NativeLoc value; 872 MemAccess mem; 873 if (!addr.base_type) 874 arm_panic(a, "frame-value base has no exact type"); 875 memset(&load, 0, sizeof load); 876 load.base_kind = NATIVE_ADDR_BASE_FRAME; 877 load.base.frame = addr.base.frame; 878 load.base_type = addr.base_type; 879 memset(&value, 0, sizeof value); 880 value.kind = NATIVE_LOC_REG; 881 value.cls = NATIVE_REG_INT; 882 value.type = addr.base_type; 883 value.v.reg = rd; 884 memset(&mem, 0, sizeof mem); 885 mem.type = value.type; 886 mem = native_mem_for_type(t, value.type, 0); 887 arm_emit_mem(a, 1, value, load, mem); 888 arm_emit_base_off(a, rd, rd, addr.offset); 889 } else if (addr.base_kind == NATIVE_ADDR_BASE_REG) { 890 arm_emit_base_off(a, rd, addr.base.reg & 0xfu, addr.offset); 891 } else { 892 arm_panic(a, "unsupported addressing mode in load_addr"); 893 } 894 if (addr.index_kind == NATIVE_ADDR_INDEX_REG) { 895 u32 idx = addr.index.reg & 0xfu; 896 arm_emit_t32(mc, arm_add_reg_lsl(rd, rd, idx, addr.log2_scale)); 897 } else if (addr.index_kind == NATIVE_ADDR_INDEX_FRAME_VALUE) { 898 NativeAddr load; 899 NativeLoc value; 900 MemAccess mem; 901 u32 idx = arm_pick_scratch(a, rd, 1u << rd); 902 if (!addr.index_type) 903 arm_panic(a, "frame-value index has no exact type"); 904 memset(&load, 0, sizeof load); 905 load.base_kind = NATIVE_ADDR_BASE_FRAME; 906 load.base.frame = addr.index.frame; 907 load.base_type = addr.index_type; 908 memset(&value, 0, sizeof value); 909 value.kind = NATIVE_LOC_REG; 910 value.cls = NATIVE_REG_INT; 911 value.type = addr.index_type; 912 value.v.reg = idx; 913 memset(&mem, 0, sizeof mem); 914 mem.type = value.type; 915 mem = native_mem_for_type(t, value.type, 0); 916 arm_emit_mem(a, 1, value, load, mem); 917 arm_emit_t32(mc, arm_add_reg_lsl(rd, rd, idx, addr.log2_scale)); 918 } else if (addr.index_kind != NATIVE_ADDR_INDEX_NONE) { 919 arm_panic(a, "unsupported address index kind in load_addr"); 920 } 921 } 922 923 /* ============================ ALU ============================ */ 924 925 /* Emit `rd = ra (+|-) mag` in the densest legal Thumb-2 encoding: the 16-bit 926 * ADDS/SUBS imm3/imm8 (flag-dead) when the magnitude is small and both registers 927 * are low, else the 32-bit modified-immediate form, else the 12-bit ADDW/SUBW 928 * raw form. `mag` is guaranteed by arm_resolve_addsub_imm to fit a modified 929 * immediate or the 12-bit field, so the raw fallback never truncates. */ 930 static void arm_emit_alu_imm(MCEmitter* mc, int is_sub, u32 rd, u32 ra, 931 u32 mag) { 932 u32 enc; 933 if (!is_sub) { 934 if (arm_low_reg(rd) && arm_low_reg(ra) && arm_uimm_fits(mag, 3u)) 935 arm_emit_t16(mc, arm_adds_imm3(rd, ra, mag)); 936 else if (rd == ra && arm_low_reg(rd) && arm_uimm_fits(mag, 8u)) 937 arm_emit_t16(mc, arm_adds_imm8(rd, mag)); 938 else if (thumb_expand_imm_encode(mag, &enc)) 939 arm_emit_t32(mc, arm_dp_imm(8u, 0u, rd, ra, enc)); 940 else 941 arm_emit_t32(mc, arm_add_imm12(rd, ra, mag & 0xfffu)); 942 } else { 943 if (arm_low_reg(rd) && arm_low_reg(ra) && arm_uimm_fits(mag, 3u)) 944 arm_emit_t16(mc, arm_subs_imm3(rd, ra, mag)); 945 else if (rd == ra && arm_low_reg(rd) && arm_uimm_fits(mag, 8u)) 946 arm_emit_t16(mc, arm_subs_imm8(rd, mag)); 947 else if (thumb_expand_imm_encode(mag, &enc)) 948 arm_emit_t32(mc, arm_dp_imm(13u, 0u, rd, ra, enc)); 949 else 950 arm_emit_t32(mc, arm_sub_imm12(rd, ra, mag & 0xfffu)); 951 } 952 } 953 954 static void arm_binop(NativeTarget* t, BinOp op, NativeLoc dst, NativeLoc a_loc, 955 NativeLoc b) { 956 Arm32NativeTarget* a = arm_of(t); 957 MCEmitter* mc = t->mc; 958 u32 rd = loc_reg(dst), ra = loc_reg(a_loc); 959 int b_imm = b.kind == NATIVE_LOC_IMM; 960 u32 rb = b_imm ? 0u : loc_reg(b); 961 u32 enc; 962 /* L7 shifted-register fold (O1-only; the rider is set by pass_combine when the 963 * target advertised can_fold_shift_into_alu). `b` is a register pre-shifted 964 * left by `b.shift` (1..4): emit the one-instruction 32-bit shifted form 965 * `<op>.W rd,ra,rb,LSL #k` instead of a separate LSL + op. Only the five DP 966 * ops binop_takes_shifted_rhs admits ever carry a rider. The 16-bit forms have 967 * no shifted operand, so this forces a .W encoding — still a net -1 insn. 968 * NOTE: arm_can_fold_shift_into_alu currently returns 0 (see its comment for 969 * the shared-pass rider-drop miscompile), so b.shift is always 0 here today — 970 * this branch is the ready-to-go emit path for when the fold is re-enabled. */ 971 if (!b_imm && b.shift) { 972 u32 k = b.shift; 973 switch (op) { 974 case BO_IADD: arm_emit_t32(mc, arm_add_reg_lsl(rd, ra, rb, k)); return; 975 case BO_ISUB: arm_emit_t32(mc, arm_sub_reg_lsl(rd, ra, rb, k)); return; 976 case BO_AND: arm_emit_t32(mc, arm_and_reg_lsl(rd, ra, rb, k)); return; 977 case BO_OR: arm_emit_t32(mc, arm_orr_reg_lsl(rd, ra, rb, k)); return; 978 case BO_XOR: arm_emit_t32(mc, arm_eor_reg_lsl(rd, ra, rb, k)); return; 979 default: arm_panic(a, "shift rider on unsupported binop"); 980 } 981 } 982 switch (op) { 983 case BO_IADD: 984 if (b_imm) { 985 /* Fold the constant into ADD/SUB (a negative addend becomes SUB) via the 986 * shared resolver; arm_emit_alu_imm picks the densest legal encoding. */ 987 int is_sub; 988 u32 mag; 989 if (!arm_resolve_addsub_imm(BO_IADD, b.v.imm, &is_sub, &mag)) 990 arm_panic(a, "add immediate not encodable"); 991 arm_emit_alu_imm(mc, is_sub, rd, ra, mag); 992 } else if (arm_low_reg(rd) && arm_low_reg(ra) && arm_low_reg(rb)) { 993 arm_emit_t16(mc, arm_adds_reg16(rd, ra, rb)); /* flag-dead */ 994 } else { 995 arm_emit_t32(mc, arm_add_reg(rd, ra, rb)); 996 } 997 return; 998 case BO_ISUB: 999 if (b_imm) { 1000 int is_sub; 1001 u32 mag; 1002 if (!arm_resolve_addsub_imm(BO_ISUB, b.v.imm, &is_sub, &mag)) 1003 arm_panic(a, "sub immediate not encodable"); 1004 arm_emit_alu_imm(mc, is_sub, rd, ra, mag); 1005 } else if (arm_low_reg(rd) && arm_low_reg(ra) && arm_low_reg(rb)) { 1006 arm_emit_t16(mc, arm_subs_reg16(rd, ra, rb)); /* flag-dead */ 1007 } else { 1008 arm_emit_t32(mc, arm_sub_reg(rd, ra, rb)); 1009 } 1010 return; 1011 case BO_IMUL: 1012 arm_emit_t32(mc, arm_mul(rd, ra, rb)); 1013 return; 1014 case BO_SDIV: 1015 arm_emit_t32(mc, arm_sdiv(rd, ra, rb)); 1016 return; 1017 case BO_UDIV: 1018 arm_emit_t32(mc, arm_udiv(rd, ra, rb)); 1019 return; 1020 case BO_SREM: 1021 case BO_UREM: { 1022 /* rd = ra - (ra/rb)*rb via SDIV/UDIV into a quotient temp, then MLS. The 1023 * temp must survive ra/rb (read by MLS), so it must differ from ra and rb; 1024 * it may be rd when rd aliases neither operand (the common case — no extra 1025 * register). Otherwise pick a reserved scratch (LR, else IP): when rd does 1026 * alias an operand, rd is an allocated register (≠ IP/LR), so at most one of 1027 * {IP,LR} can be an operand and the other is always free. */ 1028 u32 q = (rd != ra && rd != rb) 1029 ? rd 1030 : ((ra != ARM_TMP && rb != ARM_TMP) ? ARM_TMP : ARM_SCRATCH); 1031 arm_emit_t32(mc, op == BO_SREM ? arm_sdiv(q, ra, rb) : arm_udiv(q, ra, rb)); 1032 arm_emit_t32(mc, arm_mls(rd, q, rb, ra)); 1033 return; 1034 } 1035 case BO_AND: 1036 if (b_imm && thumb_expand_imm_encode((u32)b.v.imm, &enc)) 1037 arm_emit_t32(mc, arm_dp_imm(0u, 0u, rd, ra, enc)); 1038 else 1039 arm_emit_t32(mc, arm_and_reg(rd, ra, rb)); 1040 return; 1041 case BO_OR: 1042 if (b_imm && thumb_expand_imm_encode((u32)b.v.imm, &enc)) 1043 arm_emit_t32(mc, arm_dp_imm(2u, 0u, rd, ra, enc)); 1044 else 1045 arm_emit_t32(mc, arm_orr_reg(rd, ra, rb)); 1046 return; 1047 case BO_XOR: 1048 if (b_imm && thumb_expand_imm_encode((u32)b.v.imm, &enc)) 1049 arm_emit_t32(mc, arm_dp_imm(4u, 0u, rd, ra, enc)); 1050 else 1051 arm_emit_t32(mc, arm_eor_reg(rd, ra, rb)); 1052 return; 1053 case BO_SHL: { 1054 /* LSLS rd,rm,#imm5 (low regs, flag-dead). LSL #0 is a plain move in both 1055 * widths, so narrowing the whole 0..31 range is safe for LSL. */ 1056 u32 sh = (u32)b.v.imm & 31u; 1057 if (b_imm && arm_low_reg(rd) && arm_low_reg(ra)) 1058 arm_emit_t16(mc, arm_lsls_imm16(rd, ra, sh)); 1059 else if (b_imm) 1060 arm_emit_t32(mc, arm_shift_imm(0u, rd, ra, sh)); 1061 else 1062 arm_emit_t32(mc, arm_shift_reg(0u, rd, ra, rb)); 1063 return; 1064 } 1065 case BO_SHR_U: { 1066 /* LSRS: the 16-bit form encodes #32 as imm5=0, so only narrow #1..#31; 1067 * #0 (no shift) keeps the .W form whose imm5=0 means #0 (a move). */ 1068 u32 sh = (u32)b.v.imm & 31u; 1069 if (b_imm && sh != 0u && arm_low_reg(rd) && arm_low_reg(ra)) 1070 arm_emit_t16(mc, arm_lsrs_imm16(rd, ra, sh)); 1071 else if (b_imm) 1072 arm_emit_t32(mc, arm_shift_imm(1u, rd, ra, sh)); 1073 else 1074 arm_emit_t32(mc, arm_shift_reg(1u, rd, ra, rb)); 1075 return; 1076 } 1077 case BO_SHR_S: { 1078 u32 sh = (u32)b.v.imm & 31u; /* ASRS: same #32-as-0 caveat as LSRS. */ 1079 if (b_imm && sh != 0u && arm_low_reg(rd) && arm_low_reg(ra)) 1080 arm_emit_t16(mc, arm_asrs_imm16(rd, ra, sh)); 1081 else if (b_imm) 1082 arm_emit_t32(mc, arm_shift_imm(2u, rd, ra, sh)); 1083 else 1084 arm_emit_t32(mc, arm_shift_reg(2u, rd, ra, rb)); 1085 return; 1086 } 1087 default: 1088 arm_panic(a, "binop not lowered (FP / wide8 is Phase 2)"); 1089 } 1090 } 1091 1092 static void arm_unop(NativeTarget* t, UnOp op, NativeLoc dst, NativeLoc src) { 1093 Arm32NativeTarget* a = arm_of(t); 1094 MCEmitter* mc = t->mc; 1095 u32 rd = loc_reg(dst), rs = loc_reg(src); 1096 u32 enc; 1097 switch (op) { 1098 case UO_NEG: 1099 thumb_expand_imm_encode(0u, &enc); 1100 arm_emit_t32(mc, arm_dp_imm(14u, 0u, rd, rs, enc)); /* RSB rd, rs, #0 */ 1101 return; 1102 case UO_BNOT: 1103 arm_emit_t32(mc, arm_mvn_reg(rd, rs)); 1104 return; 1105 case UO_NOT: { 1106 /* Logical not -> (rs == 0) materialized as 0/1: CMP rs,#0; MOV rd,#0; 1107 * IT EQ; MOV<eq> rd,#1. CMP precedes the MOV (rd may alias rs). */ 1108 u32 enc0, enc1; 1109 thumb_expand_imm_encode(0u, &enc0); 1110 thumb_expand_imm_encode(1u, &enc1); 1111 arm_emit_t32(mc, arm_cmp_imm(rs, enc0)); 1112 arm_emit_t32(mc, arm_mov_imm(rd, enc0)); 1113 arm_emit_t16(mc, (u16)(0xbf08u | (ARM_CC_EQ << 4))); /* IT EQ */ 1114 arm_emit_t32(mc, arm_mov_imm(rd, enc1)); 1115 return; 1116 } 1117 default: 1118 arm_panic(a, "unop not lowered (FNEG is a soft-float follow-on)"); 1119 } 1120 } 1121 1122 /* Map a CmpOp to its ARM condition code (taken-when-true). */ 1123 static u32 arm_cond_for(Arm32NativeTarget* a, CmpOp op) { 1124 switch (op) { 1125 case CMP_EQ: return ARM_CC_EQ; 1126 case CMP_NE: return ARM_CC_NE; 1127 case CMP_LT_S: return ARM_CC_LT; 1128 case CMP_LE_S: return ARM_CC_LE; 1129 case CMP_GT_S: return ARM_CC_GT; 1130 case CMP_GE_S: return ARM_CC_GE; 1131 case CMP_LT_U: return ARM_CC_CC; 1132 case CMP_LE_U: return ARM_CC_LS; 1133 case CMP_GT_U: return ARM_CC_HI; 1134 case CMP_GE_U: return ARM_CC_CS; 1135 default: 1136 arm_panic(a, "FP compare not lowered (Phase 2)"); 1137 } 1138 } 1139 1140 static void arm_emit_cmp_operands(Arm32NativeTarget* a, NativeLoc x, 1141 NativeLoc y) { 1142 MCEmitter* mc = a->base.mc; 1143 u32 enc; 1144 if (y.kind == NATIVE_LOC_IMM && thumb_expand_imm_encode((u32)y.v.imm, &enc)) 1145 arm_emit_t32(mc, arm_cmp_imm(loc_reg(x), enc)); 1146 else 1147 arm_emit_t32(mc, arm_cmp_reg(loc_reg(x), loc_reg(y))); 1148 } 1149 1150 /* Materialize the boolean (a OP b) into dst: CMP; MOV dst,#0; IT cc; MOV dst,#1. 1151 * The CMP must precede the `MOV dst,#0`: dst can ALIAS a compare operand (e.g. a 1152 * soft-float compare leaves its libcall result in r0 and the bool destination is 1153 * also r0), and a leading `MOV dst,#0` would clobber that operand before CMP 1154 * reads it. MOV.W #imm (T2, S=0) does not set flags, so emitting it after CMP 1155 * leaves the compare flags intact for the IT-predicated `MOV dst,#1`. */ 1156 static void arm_cmp(NativeTarget* t, CmpOp op, NativeLoc dst, NativeLoc a_loc, 1157 NativeLoc b) { 1158 Arm32NativeTarget* a = arm_of(t); 1159 MCEmitter* mc = t->mc; 1160 u32 rd = loc_reg(dst); 1161 u32 cc = arm_cond_for(a, op); 1162 u32 enc0, enc1; 1163 thumb_expand_imm_encode(0u, &enc0); 1164 thumb_expand_imm_encode(1u, &enc1); 1165 arm_emit_cmp_operands(a, a_loc, b); /* CMP a, b (sets flags) */ 1166 arm_emit_t32(mc, arm_mov_imm(rd, enc0)); /* dst = 0 (MOV.W, no flag set) */ 1167 arm_emit_t16(mc, (u16)(0xbf08u | (cc << 4))); /* IT cc (1 insn, mask 0b1000) */ 1168 arm_emit_t32(mc, arm_mov_imm(rd, enc1)); /* MOV<cc> dst, #1 */ 1169 } 1170 1171 static void arm_convert(NativeTarget* t, ConvKind op, NativeLoc dst, 1172 NativeLoc src) { 1173 Arm32NativeTarget* a = arm_of(t); 1174 MCEmitter* mc = t->mc; 1175 u32 rd = loc_reg(dst), rs = loc_reg(src); 1176 u32 dsz = native_type_size(t, dst.type); 1177 u32 ssz = native_type_size(t, src.type); 1178 switch (op) { 1179 case CV_SEXT: 1180 if (ssz == 1u) arm_emit_t32(mc, arm_sxtb(rd, rs)); 1181 else if (ssz == 2u) arm_emit_t32(mc, arm_sxth(rd, rs)); 1182 else if (rd != rs) arm_emit_t16(mc, arm_mov_hi(rd, rs)); 1183 return; 1184 case CV_ZEXT: 1185 if (ssz == 1u) arm_emit_t32(mc, arm_uxtb(rd, rs)); 1186 else if (ssz == 2u) arm_emit_t32(mc, arm_uxth(rd, rs)); 1187 else if (rd != rs) arm_emit_t16(mc, arm_mov_hi(rd, rs)); 1188 return; 1189 case CV_TRUNC: 1190 if (dsz == 1u) arm_emit_t32(mc, arm_uxtb(rd, rs)); 1191 else if (dsz == 2u) arm_emit_t32(mc, arm_uxth(rd, rs)); 1192 else if (rd != rs) arm_emit_t16(mc, arm_mov_hi(rd, rs)); 1193 return; 1194 case CV_BITCAST: 1195 if (rd != rs) arm_emit_t16(mc, arm_mov_hi(rd, rs)); 1196 return; 1197 default: 1198 arm_panic(a, "convert not lowered (FP conversions are Phase 2)"); 1199 } 1200 } 1201 1202 /* ============================ _rr narrow hooks ============================ */ 1203 1204 static void arm_binop_rr(NativeTarget* t, BinOp op, NativeRegLoc dst, 1205 NativeRegLoc a, NativeRegLoc b) { 1206 arm_binop(t, op, native_loc_from_reg(dst), native_loc_from_reg(a), 1207 native_loc_from_reg(b)); 1208 } 1209 static void arm_move_rr(NativeTarget* t, NativeRegLoc dst, NativeRegLoc src) { 1210 arm_move(t, native_loc_from_reg(dst), native_loc_from_reg(src)); 1211 } 1212 static void arm_cmp_rr(NativeTarget* t, CmpOp op, NativeRegLoc dst, 1213 NativeRegLoc a, NativeRegLoc b) { 1214 arm_cmp(t, op, native_loc_from_reg(dst), native_loc_from_reg(a), 1215 native_loc_from_reg(b)); 1216 } 1217 static void arm_convert_rr(NativeTarget* t, ConvKind op, NativeRegLoc dst, 1218 NativeRegLoc src) { 1219 arm_convert(t, op, native_loc_from_reg(dst), native_loc_from_reg(src)); 1220 } 1221 1222 /* ============================ control flow ============================ */ 1223 1224 static MCLabel arm_label_new(NativeTarget* t) { return mc_label_new(t->mc); } 1225 static void arm_label_place(NativeTarget* t, MCLabel l) { 1226 mc_label_place(t->mc, l); 1227 } 1228 static void arm_jump(NativeTarget* t, MCLabel l) { 1229 arm_emit_t32(t->mc, arm_b_w()); 1230 mc_emit_label_ref(t->mc, l, R_ARM_THM_JUMP24, 4, 0); 1231 } 1232 static void arm_cmp_branch(NativeTarget* t, CmpOp op, NativeLoc a_loc, 1233 NativeLoc b, MCLabel target) { 1234 Arm32NativeTarget* a = arm_of(t); 1235 u32 cc = arm_cond_for(a, op); 1236 arm_emit_cmp_operands(a, a_loc, b); 1237 arm_emit_t32(t->mc, arm_b_cond_w(cc)); 1238 mc_emit_label_ref(t->mc, target, R_ARM_THM_JUMP19, 4, 0); 1239 } 1240 1241 /* ============================ frame lifecycle ============================ */ 1242 1243 static u32 arm_signature_stack_bytes(NativeTarget* t, KitCgTypeId fn_type, 1244 int* variadic, u32* nparams); 1245 1246 static NativeFrameSlot arm_frame_slot(NativeTarget* t, 1247 const NativeFrameSlotDesc* d) { 1248 return native_frame_slot_alloc(&arm_of(t)->frame, d); 1249 } 1250 static void arm_release_frame_slot(NativeTarget* t, NativeFrameSlot slot) { 1251 native_frame_release_slot(&arm_of(t)->frame, slot); 1252 } 1253 static int arm_frame_slot_debug_loc(NativeTarget* t, NativeFrameSlot slot, 1254 CGDebugLoc* out) { 1255 Arm32NativeTarget* a = arm_of(t); 1256 NativeFrameSlotEntry* s; 1257 if (!out) return 0; 1258 memset(out, 0, sizeof *out); 1259 if (slot == NATIVE_FRAME_SLOT_NONE || slot > a->frame.nslots) return 0; 1260 s = native_frame_slot_at(&a->frame, slot); 1261 out->kind = CG_DEBUG_LOC_FRAME; 1262 out->v.frame_ofs = -(i32)s->off; /* r7-relative */ 1263 return 1; 1264 } 1265 1266 static u32 arm_frame_size(const Arm32NativeTarget* a) { 1267 u32 f = align_up_u32(a->frame.cum_off + a->frame.max_outgoing, 8u); 1268 /* AAPCS: sp must be 8-byte aligned at public interfaces (calls). The total 1269 * adjustment from entry sp is saved_block_bytes + (variadic ? 16 : 0) + f; the 1270 * 16-byte GP-save area is 8-aligned, so only an odd-word saved-register block 1271 * (e.g. {r4,r7,lr} = 12) breaks alignment — pad the frame by a word then. */ 1272 if (a->saved_block_bytes & 4u) f += 4u; 1273 return f; 1274 } 1275 1276 /* r7-relative offset where the incoming stack-argument window begins. The saved 1277 * {r7, lr} pair is 8 bytes; a variadic callee also spilled r0..r3 (16 bytes) in 1278 * a save area above the pair, pushing the named stack args 16 bytes higher. The 1279 * spilled r0..r3 occupy [r7 + ARM_SAVED_PAIR_BYTES, r7 + arg_window_base). */ 1280 static u32 arm_arg_window_base(const Arm32NativeTarget* a) { 1281 /* saved_block_bytes covers the saved-register block ({r7,lr} = 8 at -O0, plus 1282 * any callee-saves the -O1 known-frame prologue PUSHes); a variadic callee 1283 * also spilled r0..r3 (16 bytes) just above it. Both push the named incoming 1284 * stack args higher, so the window base accounts for them together. */ 1285 return a->saved_block_bytes + (a->is_variadic ? ARM_VA_GP_SAVE_BYTES : 0u); 1286 } 1287 1288 static void arm_reserve_callee_saves(NativeTarget* t, const u32* used_by_class, 1289 u32 nclasses) { 1290 native_frame_set_callee_saves(&arm_of(t)->frame, used_by_class, nclasses, 1291 NULL, 0, 0); 1292 } 1293 1294 /* AAPCS callee-saved integer registers this backend lets the allocator use: 1295 * r4..r11 except r7 (the frame pointer, preserved by the saved-pair head, not as 1296 * an ordinary callee-save). Matches arm_int_phys's ARM_PHYS_CALLEE set. */ 1297 static int arm_reg_is_callee_int(Reg r) { 1298 return r >= 4u && r <= 11u && r != ARM_FP; 1299 } 1300 1301 static void arm_asm_clobber_masks(Compiler* c, SrcLoc loc, 1302 const Sym* clobbers, u32 nclob, 1303 u32* int_mask, u32* fp_mask); 1304 1305 /* Build the callee-saved set the prologue must preserve, from the allocator's 1306 * per-class masks plus opaque inline-asm clobbers. Only the integer class 1307 * exists in this soft-float backend. r7 is structural frame state and is 1308 * excluded here (and rejected when named explicitly by the clobber parser). */ 1309 static int arm_asm_reg_is_callee_saved(NativeTarget* t, NativeAllocClass cls, 1310 Reg r) { 1311 (void)t; 1312 return cls == NATIVE_REG_INT && arm_reg_is_callee_int(r); 1313 } 1314 1315 static u32 arm_known_callee_saves(NativeTarget* t, 1316 const NativeKnownFrameDesc* frame) { 1317 Arm32NativeTarget* a = arm_of(t); 1318 u32 masks[NATIVE_REG_CLASS_COUNT] = {0, 0, 0}; 1319 SrcLoc loc = a->func ? a->func->loc : (SrcLoc){0, 0, 0}; 1320 native_asm_known_callee_saves(t, loc, frame, masks, 1321 NATIVE_REG_CLASS_COUNT, 1322 arm_asm_clobber_masks, 1323 arm_asm_reg_is_callee_saved); 1324 return masks[NATIVE_REG_INT]; 1325 } 1326 1327 /* True (and sets *imm7) when `n` fits the 16-bit `SUB sp,sp,#imm7*4` form: 1328 * a non-zero multiple of 4 with imm7 in 0..127 (n <= 508). The frame size is a 1329 * multiple of 8, so the multiple-of-4 requirement is always met. */ 1330 static int arm_sp_adjust_narrow(u32 n, u32* imm7) { 1331 if (n == 0 || (n & 3u) != 0u || (n >> 2) > 0x7fu) return 0; 1332 *imm7 = n >> 2; 1333 return 1; 1334 } 1335 1336 /* Build the `SUB sp, sp, #n` instruction sequence (n a multiple of 8) into 1337 * words[], returning the count (<= ARM_NDT_SUB_WORDS). A small frame fits the 1338 * 16-bit `SUB sp,sp,#imm7*4`, packed (SUB16 in hw1, NOP16 in hw2) into one T32 1339 * slot so the single-pass deferred patch still overwrites exactly one reserved 1340 * word; medium amounts use one SUBW/SUB.W modimm; large amounts (n > 4095, big 1341 * frames) stage n into IP via MOVW(+MOVT) and `SUB sp, sp, ip`. IP is the emit 1342 * scratch, dead at the prologue. Shared by the inline known-frame emit and the 1343 * single-pass patch. */ 1344 static u32 arm_build_sub_sp(u32 n, u32* words) { 1345 u32 enc, imm7, wi = 0; 1346 if (n == 0) return 0; 1347 if (arm_sp_adjust_narrow(n, &imm7)) { 1348 /* hw1 = SUB sp (16-bit), hw2 = NOP (16-bit). arm_patch_t32 writes hw1 first 1349 * in memory, so SUB executes then NOP. */ 1350 words[wi++] = arm_t32(arm_sub_sp_sp_imm16(imm7), arm_nop16()); 1351 return wi; 1352 } 1353 if (n <= 0xfffu) { 1354 words[wi++] = arm_sub_imm12(13u, 13u, n); 1355 return wi; 1356 } 1357 if (thumb_expand_imm_encode(n, &enc)) { 1358 words[wi++] = arm_dp_imm(13u, 0u, 13u, 13u, enc); /* SUB.W sp,sp,#modimm */ 1359 return wi; 1360 } 1361 words[wi++] = arm_movw(ARM_SCRATCH, n & 0xffffu); 1362 if ((n >> 16) != 0u) words[wi++] = arm_movt(ARM_SCRATCH, (n >> 16) & 0xffffu); 1363 words[wi++] = arm_sub_reg(13u, 13u, ARM_SCRATCH); 1364 return wi; 1365 } 1366 1367 /* Emit `SUB sp, sp, #n` inline (known-frame prologue, where the size is final). 1368 * The inline path is not slot-constrained, so a small frame emits a bare 16-bit 1369 * SUB (no NOP padding); larger frames reuse arm_build_sub_sp's word list. */ 1370 static void arm_emit_sub_sp(Arm32NativeTarget* a, u32 n) { 1371 u32 words[ARM_NDT_SUB_WORDS]; 1372 u32 imm7, nwords, i; 1373 if (arm_sp_adjust_narrow(n, &imm7)) { 1374 arm_emit_t16(a->base.mc, arm_sub_sp_sp_imm16(imm7)); 1375 return; 1376 } 1377 nwords = arm_build_sub_sp(n, words); 1378 for (i = 0; i < nwords; ++i) arm_emit_t32(a->base.mc, words[i]); 1379 } 1380 1381 /* sret: reserve a hidden frame slot for the incoming destination pointer (r0) 1382 * and advance the param cursor past it. Shared by the single-pass and 1383 * known-frame paths (slot creation must happen at the same point in both). */ 1384 static void arm_reserve_sret_slot(Arm32NativeTarget* a) { 1385 NativeTarget* t = &a->base; 1386 if (a->has_sret) { 1387 NativeFrameSlotDesc sd; 1388 memset(&sd, 0, sizeof sd); 1389 sd.type = builtin_id(KIT_CG_BUILTIN_I32); 1390 sd.size = 4; 1391 sd.align = 4; 1392 sd.kind = NATIVE_FRAME_SLOT_SAVE; 1393 a->sret_ptr_slot = arm_frame_slot(t, &sd); 1394 a->next_param_int = 1; /* r0 consumed by the sret pointer */ 1395 } 1396 } 1397 1398 /* Lazily reserve the per-function indirect-callee spill home (4 bytes, r7- 1399 * anchored). Allocated on first need during body codegen — at -O0 the frame is 1400 * deferred (settled in arm_func_end), so a body-time slot just grows cum_off; 1401 * one slot is reused across every indirect call in the function. */ 1402 static NativeFrameSlot arm_callee_spill_slot(Arm32NativeTarget* a) { 1403 if (a->callee_spill_slot == NATIVE_FRAME_SLOT_NONE) { 1404 NativeFrameSlotDesc sd; 1405 memset(&sd, 0, sizeof sd); 1406 sd.type = builtin_id(KIT_CG_BUILTIN_I32); 1407 sd.size = 4; 1408 sd.align = 4; 1409 sd.kind = NATIVE_FRAME_SLOT_SAVE; 1410 a->callee_spill_slot = arm_frame_slot(&a->base, &sd); 1411 } 1412 return a->callee_spill_slot; 1413 } 1414 1415 /* Spill the incoming sret pointer (r0) to its hidden home. The home is 1416 * r7-anchored, so this runs correctly after the frame is set up. */ 1417 static void arm_emit_sret_store(Arm32NativeTarget* a) { 1418 NativeTarget* t = &a->base; 1419 if (a->has_sret && a->sret_ptr_slot != NATIVE_FRAME_SLOT_NONE) { 1420 KitCgTypeId i32t = builtin_id(KIT_CG_BUILTIN_I32); 1421 NativeAddr addr; 1422 memset(&addr, 0, sizeof addr); 1423 addr.base_kind = NATIVE_ADDR_BASE_FRAME; 1424 addr.base.frame = a->sret_ptr_slot; 1425 addr.base_type = i32t; 1426 arm_emit_mem(a, 0, native_loc_reg(i32t, NATIVE_REG_INT, ARM_R0), addr, 1427 native_mem_for_type(t, i32t, 4)); 1428 } 1429 } 1430 1431 static void arm_func_begin(NativeTarget* t, const CGFuncDesc* fd) { 1432 Arm32NativeTarget* a = arm_of(t); 1433 MCEmitter* mc = t->mc; 1434 const ABIFuncInfo* abi = abi_cg_func_info(t->c->abi, fd->fn_type); 1435 a->func = fd; 1436 a->loc = fd->loc; 1437 native_frame_reset(&a->frame); 1438 a->next_param_int = 0; 1439 a->next_param_stack = 0; 1440 a->has_sret = (abi && abi->has_sret) ? 1u : 0u; 1441 a->is_variadic = (abi && abi->variadic) ? 1u : 0u; 1442 a->sret_ptr_slot = NATIVE_FRAME_SLOT_NONE; 1443 a->callee_spill_slot = NATIVE_FRAME_SLOT_NONE; 1444 a->known_frame = 0; 1445 a->slim_prologue = 0; 1446 a->saved_reglist = (u16)((1u << ARM_FP) | (1u << 14u)); /* {r7, lr} */ 1447 a->saved_block_bytes = 8u; /* two words */ 1448 a->n_alloca_patch = 0; 1449 a->incoming_stack_size = arm_signature_stack_bytes(t, fd->fn_type, NULL, NULL); 1450 1451 mc_set_section(mc, fd->text_section_id); 1452 mc_emit_align(mc, 4, 0); 1453 a->func_start = mc_pos(mc); 1454 mc_begin_function(mc, fd->sym, fd->text_section_id, a->func_start); 1455 mc_cfi_startproc(mc); 1456 a->epilogue_label = mc_label_new(mc); 1457 1458 /* Prologue: save fp+lr, set the frame anchor, reserve the (deferred) frame. 1459 * The deferred `SUB sp` is patched in arm_func_end once cum_off/max_outgoing 1460 * are final. Reserve ARM_NDT_SUB_WORDS T32 slots so a large frame (> 4095) can 1461 * be patched as an IP-staged MOVW/MOVT/SUB sequence; a small frame patches the 1462 * first slot with SUBW and fills the rest with NOPs. 1463 * A variadic callee first spills the GP arg registers r0..r3 into a save area 1464 * just above the saved pair (so they are contiguous-below the named incoming 1465 * stack args at [r7 + 8 + 16]); va_start/va_arg walk forward across both. */ 1466 if (a->is_variadic) 1467 arm_emit_push(mc, 0xfu); /* PUSH {r0-r3} (16-bit) */ 1468 arm_emit_push(mc, (1u << ARM_FP) | (1u << 14u)); /* PUSH {r7, lr} (16-bit) */ 1469 arm_emit_t16(mc, arm_mov_hi(ARM_FP, 13u)); /* MOV r7, sp */ 1470 a->prologue_sub_pos = mc_pos(mc); 1471 for (u32 i = 0; i < ARM_NDT_SUB_WORDS; ++i) 1472 arm_emit_t32(mc, arm_nop32()); /* placeholder, patched in arm_func_end */ 1473 1474 /* sret: spill the incoming destination pointer (r0) to a hidden home. */ 1475 arm_reserve_sret_slot(a); 1476 arm_emit_sret_store(a); 1477 } 1478 1479 /* Optimizer (-O1) entry point: the optimizer has run register allocation and 1480 * supplies the exact frame up front, so the prologue is emitted final the moment 1481 * it is built — no deferred `SUB sp, #0` placeholder, no arm_func_end patch 1482 * (arm_func_end skips patching when known_frame). The callee-saved registers the 1483 * allocator used are folded directly into the PUSH/POP register list rather than 1484 * given their own frame slots: they ride above the frame anchor r7 in the 1485 * STMDB-saved block, which keeps slot offsets ([r7, #-off]) byte-identical to 1486 * the single-pass layout and lets one LDMIA-via-pc restore-and-return. 1487 * 1488 * Slot creation order matches the single-pass path: static slots first, then the 1489 * sret entry-save slot. (Callee-saves take no slots here, so order vs. them is 1490 * moot.) */ 1491 static void arm_func_begin_known_frame(NativeTarget* t, const CGFuncDesc* fd, 1492 const NativeKnownFrameDesc* kf, 1493 NativeFrameSlot* out_slots) { 1494 Arm32NativeTarget* a = arm_of(t); 1495 MCEmitter* mc = t->mc; 1496 const ABIFuncInfo* abi = abi_cg_func_info(t->c->abi, fd->fn_type); 1497 u32 cs_mask; 1498 u32 reglist, nsaved, frame, i; 1499 1500 a->func = fd; 1501 a->loc = fd->loc; 1502 cs_mask = arm_known_callee_saves(t, kf); 1503 native_frame_reset(&a->frame); 1504 a->next_param_int = 0; 1505 a->next_param_stack = 0; 1506 a->has_sret = (abi && abi->has_sret) ? 1u : 0u; 1507 a->is_variadic = (abi && abi->variadic) ? 1u : 0u; 1508 a->sret_ptr_slot = NATIVE_FRAME_SLOT_NONE; 1509 a->callee_spill_slot = NATIVE_FRAME_SLOT_NONE; 1510 a->known_frame = 1; 1511 a->slim_prologue = 0; 1512 a->n_alloca_patch = 0; 1513 a->incoming_stack_size = arm_signature_stack_bytes(t, fd->fn_type, NULL, NULL); 1514 a->frame.known_frame = 1; 1515 a->frame.has_alloca = kf ? kf->has_alloca : 0u; 1516 1517 mc_set_section(mc, fd->text_section_id); 1518 mc_emit_align(mc, 4, 0); 1519 a->func_start = mc_pos(mc); 1520 mc_begin_function(mc, fd->sym, fd->text_section_id, a->func_start); 1521 mc_cfi_startproc(mc); 1522 a->epilogue_label = mc_label_new(mc); 1523 1524 /* Record the callee-save set (for parity / debug) and build the body's slots 1525 * and outgoing-area reservation, then settle the exact frame. */ 1526 if (cs_mask) { 1527 u32 used[NATIVE_REG_CLASS_COUNT] = {0, 0, 0}; 1528 used[NATIVE_REG_INT] = cs_mask; 1529 arm_reserve_callee_saves(t, used, NATIVE_REG_CLASS_COUNT); 1530 } 1531 if (kf) { 1532 for (i = 0; i < kf->nslots; ++i) { 1533 NativeFrameSlot slot = arm_frame_slot(t, &kf->slots[i]); 1534 if (out_slots) out_slots[i] = slot; 1535 } 1536 arm_reserve_sret_slot(a); 1537 native_frame_note_outgoing(&a->frame, kf->max_outgoing); 1538 } else { 1539 arm_reserve_sret_slot(a); 1540 } 1541 1542 /* The PUSH/POP register list: callee-saves (low to high) + r7 + lr. STMDB 1543 * stores in ascending register order regardless of the bit order, so r7 lands 1544 * in the block correctly and `MOV r7, sp` anchors just below the whole block. 1545 * The popped lr is restored into pc, returning. */ 1546 reglist = cs_mask | (1u << ARM_FP) | (1u << 14u); 1547 nsaved = 0; 1548 for (i = 0; i < 16u; ++i) 1549 if (reglist & (1u << i)) nsaved++; 1550 a->saved_reglist = (u16)reglist; 1551 a->saved_block_bytes = nsaved * 4u; 1552 1553 /* Leaf minimal-frame tier (the register-starved analog of the aa64/rv64 1554 * frameless leaf): a leaf with no callee-saves, no body slots, no outgoing 1555 * args, no sret/variadic and only register params needs no frame record, no r7 1556 * anchor and no SUB sp. It cannot, however, return through a bare `BX lr`: 1557 * arm32 is register-starved and uses LR (ARM_TMP) pervasively as a 1558 * backend-private temporary — global-address staging in arm_resolve_mem, 1559 * `__builtin_*_overflow` sequences, popcount, bitfield insert, and the 1560 * byte-copy granule engine. A leaf body may hit any of these (e.g. 1561 * `static int g; return g;` stages &g into LR, 1562 * after which a bare `BX lr` would branch to &g). So the tier still saves and 1563 * restores the return address with a single-register `PUSH {lr}` / `POP {pc}`, 1564 * leaving the body free to clobber lr; it just skips the r7 frame record and 1565 * the stack reservation. (aa64/rv64 keep their return-address register out of 1566 * the backend temporary bank, so their frameless leaves skip even this push; arm32 1567 * cannot.) Inline asm cannot occur here (asm_block is not installed). */ 1568 a->slim_prologue = kf && kf->is_leaf && !kf->reads_frame && cs_mask == 0 && 1569 !a->frame.has_alloca && a->frame.cum_off == 0 && 1570 a->frame.max_outgoing == 0 && !a->has_sret && 1571 !a->is_variadic && 1572 arm_signature_stack_bytes(t, fd->fn_type, NULL, NULL) == 0; 1573 if (a->slim_prologue) { 1574 a->saved_reglist = (u16)(1u << 14u); /* {lr} */ 1575 a->saved_block_bytes = 4u; 1576 arm_emit_push(mc, 1u << 14u); /* PUSH {lr} (16-bit, 0xb500) */ 1577 native_frame_set_final(&a->frame); 1578 return; 1579 } 1580 1581 /* Emit the final prologue as a fp-at-pair split push: the callee-saves ride 1582 * ABOVE a tight {r7, lr} frame-record pair so r7 anchors exactly that pair — 1583 * [r7]=caller's r7, [r7+4]=return address — which @frame_address/@return_address 1584 * and any frame-pointer backtrace walk. (A single combined PUSH would leave r7 1585 * pointing at the lowest callee-save, with the return address an arbitrary 1586 * distance above, breaking the chain whenever r8..r11 sit between r7 and lr.) 1587 * Push order, high address to low: variadic GP-save (r0..r3) first, then the 1588 * callee-saves, then the pair; the total block size is unchanged, so 1589 * saved_block_bytes and every [r7, #-off] body slot keep their offsets, and 1590 * va_start still reaches the GP-save at r7 + saved_block_bytes. 1591 * A variadic callee's r0..r3 save area sits just above the saved block (below 1592 * the named incoming stack args); va_start/va_arg walk forward across both, and 1593 * the teardown reclaims it (arm_emit_frame_restore). */ 1594 if (a->is_variadic) arm_emit_push(mc, 0xfu); /* PUSH {r0-r3} */ 1595 if (cs_mask) arm_emit_push(mc, cs_mask); /* PUSH {callee-saves} */ 1596 arm_emit_push(mc, (1u << ARM_FP) | (1u << 14u)); /* PUSH {r7, lr} */ 1597 arm_emit_t16(mc, arm_mov_hi(ARM_FP, 13u)); /* MOV r7, sp */ 1598 frame = arm_frame_size(a); 1599 arm_emit_sub_sp(a, frame); 1600 1601 arm_emit_sret_store(a); 1602 native_frame_set_final(&a->frame); 1603 } 1604 1605 /* Emit the frame teardown, mirroring the fp-at-pair split prologue: r7 anchors 1606 * the {r7, lr} pair at the bottom of the saved block, the callee-saves sit above 1607 * it, and (for a variadic callee) the r0..r3 GP-save above those. 1608 * 1609 * The common case — no callee-saves, no GP-save, returning — peels the pair with 1610 * a single `POP {r7, pc}` (the saved lr lands straight in pc). Otherwise restore 1611 * the pair with `POP {r7, lr}`, then peel the callee-saves and reclaim the 1612 * GP-save, leaving lr = the caller's return address — popped into pc with a final 1613 * `BX lr` when returning, or kept for a tail-call branch when not. */ 1614 static void arm_emit_frame_restore(Arm32NativeTarget* a, int to_pc) { 1615 MCEmitter* mc = a->base.mc; 1616 u32 va = a->is_variadic ? ARM_VA_GP_SAVE_BYTES : 0u; 1617 u32 cs_mask = a->saved_reglist & ~((1u << ARM_FP) | (1u << 14u)); 1618 arm_emit_t16(mc, arm_mov_hi(13u, ARM_FP)); /* MOV sp, r7 (sp -> {r7,lr} pair) */ 1619 if (to_pc && va == 0u && cs_mask == 0u) { 1620 arm_emit_pop(mc, (1u << ARM_FP) | (1u << 15u)); /* POP {r7, pc} (16-bit) */ 1621 return; 1622 } 1623 arm_emit_pop(mc, (1u << ARM_FP) | (1u << 14u)); /* POP {r7, lr} (16-bit) */ 1624 if (cs_mask) arm_emit_pop(mc, cs_mask); /* POP {callee-saves} */ 1625 if (va) arm_emit_add_const(a, 13u, (i64)va); /* reclaim r0..r3 save */ 1626 if (to_pc) arm_emit_t16(mc, arm_bx(14u)); /* BX lr (return) */ 1627 } 1628 1629 static void arm_func_end(NativeTarget* t) { 1630 Arm32NativeTarget* a = arm_of(t); 1631 MCEmitter* mc = t->mc; 1632 u32 frame; 1633 /* Place the epilogue and emit the teardown. */ 1634 mc_label_place(mc, a->epilogue_label); 1635 if (a->slim_prologue) 1636 /* Minimal leaf: only lr was saved (PUSH {lr}); pop it straight into pc. This 1637 * restores the return address the body may have clobbered using LR as a 1638 * backend-private temporary — see the func_begin slim_prologue note. */ 1639 arm_emit_pop(mc, 1u << 15u); /* POP {pc} (16-bit, 0xbd00) */ 1640 else 1641 arm_emit_frame_restore(a, /*to_pc=*/1); 1642 1643 /* Single-pass path: settle the frame and patch the deferred `SUB sp` region. 1644 * The known-frame path emitted its prologue final in 1645 * arm_func_begin_known_frame (frame already settled there), so it never 1646 * patches. The reserved region is ARM_NDT_SUB_WORDS NOP.W slots; patch the 1647 * leading slots with the built SUB sequence (one SUBW for a small frame, an 1648 * IP-staged MOVW/MOVT/SUB for a large one) and leave the trailing NOP.W slots, 1649 * which execute harmlessly. */ 1650 if (!a->known_frame) { 1651 u32 words[ARM_NDT_SUB_WORDS]; 1652 u32 nwords, i; 1653 native_frame_set_final(&a->frame); 1654 frame = arm_frame_size(a); 1655 nwords = arm_build_sub_sp(frame, words); 1656 for (i = 0; i < nwords; ++i) 1657 arm_patch_t32(a, a->prologue_sub_pos + i * 4u, words[i]); 1658 } 1659 1660 /* Patch each alloca's `ADD dst, sp, #max_outgoing` now that the outgoing-arg 1661 * area size is final (the alloca block sits just above it, so a later call's 1662 * sp-relative outgoing stores never clobber it). */ 1663 { 1664 u32 i, mo = a->frame.max_outgoing; 1665 if (a->n_alloca_patch && mo > 0xfffu) 1666 arm_panic(a, "alloca with a large outgoing-arg area (needs IP staging)"); 1667 for (i = 0; i < a->n_alloca_patch; ++i) 1668 arm_patch_t32(a, a->alloca_patch_pos[i], 1669 arm_add_imm12(a->alloca_patch_rd[i], 13u, mo)); 1670 } 1671 1672 /* Publish the function symbol. ARM marks Thumb STT_FUNC symbols with the 1673 * low bit set (the Thumb bit), so &fn and indirect BLX reach Thumb state; 1674 * direct-branch relocs mask it off (S & ~1). */ 1675 { 1676 u32 end = mc_pos(mc); 1677 ObjSecId sec = a->func->text_section_id; 1678 obj_symbol_define(t->obj, a->func->sym, sec, (u64)a->func_start | 1u, 1679 (u64)(end - a->func_start)); 1680 if (a->func->atomize) 1681 obj_atom_define(t->obj, sec, a->func_start, end - a->func_start, 1682 a->func->sym, 0); 1683 if (mc->debug) debug_func_pc_range(mc->debug, sec, a->func_start, end); 1684 } 1685 1686 mc_cfi_endproc(mc); 1687 mc_end_function(mc); 1688 a->func = NULL; 1689 } 1690 1691 /* ============================ param binding ============================ */ 1692 1693 #define ARM32_GPR_BYTES 4u 1694 1695 /* Lane scalar type for an ABI part: an 8-byte i64/double DIRECT argument is two 1696 * 4-byte INT lanes, and an aggregate uses word/sub-word lanes. The NDT sizes 1697 * every load/store/move it emits for return/call marshalling or a param bind 1698 * from the lane location's TYPE (nd_type_mem -> cg_type_size), so each lane must 1699 * carry a type whose natural size equals the lane's byte width — NOT the full 1700 * 8-byte scalar type (which would drive an 8-byte access the 32-bit backend has 1701 * no single-instruction form for). Mirrors riscv's rv_part_scalar_type. */ 1702 static KitCgTypeId arm_part_scalar_type(const ABIArgPart* part) { 1703 switch (part->size) { 1704 case 1u: return builtin_id(KIT_CG_BUILTIN_I8); 1705 case 2u: return builtin_id(KIT_CG_BUILTIN_I16); 1706 case 8u: return builtin_id(KIT_CG_BUILTIN_I64); 1707 default: return builtin_id(KIT_CG_BUILTIN_I32); 1708 } 1709 } 1710 1711 /* AAPCS even/8-align rule: an argument whose natural alignment is 8 bytes 1712 * rounds NCRN up to an even core register (r0:r1 or r2:r3) and 8-byte-aligns its 1713 * stack slot. This covers both an i64/double scalar pair AND an 8-byte-aligned 1714 * aggregate (e.g. struct{double;long;}). The ABI classifier surfaces that 1715 * alignment on the first part (scalar pair lanes carry align=8; an aggregate's 1716 * parts[0].align is the record's natural alignment; the synthesized variadic 1717 * form below mirrors this), so parts[0].align is the single discriminator — 1718 * uniform for named and variadic args and independent of part count. 1719 * 1720 * This predicate is ONLY the even/8-align rule. Whether an argument may split 1721 * across the reg/stack boundary is not a separate flag: round-to-even makes an 1722 * 8-byte scalar pair fit-or-fully-spill (its 2 parts never straddle), while a 1723 * larger aggregate straddles per AAPCS C.5 — the naive per-part walks below 1724 * realize both with no special case. */ 1725 static int arm_arg_needs_even(const ABIArgInfo* ai) { 1726 return ai->kind == ABI_ARG_DIRECT && ai->nparts > 0u && ai->parts && 1727 ai->parts[0].align >= 2u * ARM32_GPR_BYTES; 1728 } 1729 1730 static void arm_bind_native_param(NativeTarget* t, const CGParamDesc* p, 1731 NativeLoc dst) { 1732 Arm32NativeTarget* a = arm_of(t); 1733 const ABIFuncInfo* abi = abi_cg_func_info(t->c->abi, a->func->fn_type); 1734 const ABIArgInfo* ai = p->index < abi->nparams ? &abi->params[p->index] : NULL; 1735 int to_reg = dst.kind == NATIVE_LOC_REG; 1736 int even = ai && arm_arg_needs_even(ai); 1737 u32 i; 1738 if (!ai || ai->kind == ABI_ARG_IGNORE) return; 1739 if (ai->kind == ABI_ARG_INDIRECT) { 1740 /* An ABI-classified indirect argument is a pointer to caller-owned storage. 1741 * Copy it into the parameter's frame home so the body owns a private copy 1742 * (matching the by-value semantics). */ 1743 KitCgTypeId i32t = builtin_id(KIT_CG_BUILTIN_I32); 1744 NativeLoc src = native_loc_reg(i32t, NATIVE_REG_INT, 1745 a->next_param_int < 4u 1746 ? (Reg)a->next_param_int 1747 : ARM_SCRATCH); 1748 NativeAddr d_addr, from; 1749 AggregateAccess access; 1750 if (a->next_param_int < 4u) { 1751 a->next_param_int++; 1752 } else { 1753 NativeAddr sa; 1754 memset(&sa, 0, sizeof sa); 1755 sa.base_kind = NATIVE_ADDR_BASE_REG; 1756 sa.base.reg = ARM_FP; 1757 sa.base_type = i32t; 1758 sa.offset = (i32)(arm_arg_window_base(a) + a->next_param_stack); 1759 arm_emit_mem(a, 1, src, sa, native_mem_for_type(t, i32t, 4)); 1760 a->next_param_stack += 4u; 1761 } 1762 if (dst.kind != NATIVE_LOC_FRAME) 1763 arm_panic(a, "indirect parameter requires a frame destination"); 1764 memset(&d_addr, 0, sizeof d_addr); 1765 d_addr.base_kind = NATIVE_ADDR_BASE_FRAME; 1766 d_addr.base.frame = dst.v.frame; 1767 d_addr.base_type = p->type; 1768 memset(&from, 0, sizeof from); 1769 from.base_kind = NATIVE_ADDR_BASE_REG; 1770 from.base.reg = loc_reg(src); 1771 from.base_type = p->type; 1772 memset(&access, 0, sizeof access); 1773 access.type = p->type; 1774 access.size = p->size ? p->size : (u32)cg_type_size(t->c, p->type); 1775 access.align = p->align ? p->align : native_type_align(t, p->type); 1776 arm_copy_bytes(t, d_addr, from, access); 1777 return; 1778 } 1779 /* AAPCS: round NCRN up to even before an 8-byte-aligned argument. The parts 1780 * then fill core registers r0..r3; any that do not fit spill to the (8-byte- 1781 * aligned for part 0) incoming stack window. An 8-byte scalar pair fully fits 1782 * or fully spills (round-to-even guarantees it); a larger aggregate straddles 1783 * the reg/stack boundary (AAPCS C.5). */ 1784 if (even) a->next_param_int = align_up_u32(a->next_param_int, 2u); 1785 for (i = 0; i < ai->nparts; ++i) { 1786 const ABIArgPart* part = &ai->parts[i]; 1787 KitCgTypeId lty = arm_part_scalar_type(part); 1788 NativeLoc src; 1789 if (a->next_param_int < 4u) { 1790 src = native_loc_reg(lty, NATIVE_REG_INT, (Reg)(a->next_param_int++)); 1791 } else { 1792 /* Incoming stack arg: above the saved register block = [r7 + N + k], 1793 * where N = saved_block_bytes (8 for the bare {r7,lr} -O0 prologue; larger 1794 * when the -O1 known-frame prologue also PUSHes callee-saves). The first 1795 * stack part of an 8-byte-aligned arg lands on an 8-byte boundary. */ 1796 NativeAddr sa; 1797 if (i == 0 && even) 1798 a->next_param_stack = 1799 align_up_u32(a->next_param_stack, 2u * ARM32_GPR_BYTES); 1800 src = native_loc_reg(lty, NATIVE_REG_INT, ARM_SCRATCH); 1801 memset(&sa, 0, sizeof sa); 1802 sa.base_kind = NATIVE_ADDR_BASE_REG; 1803 sa.base.reg = ARM_FP; 1804 sa.base_type = lty; 1805 sa.offset = (i32)(arm_arg_window_base(a) + a->next_param_stack); 1806 arm_emit_mem(a, 1, src, sa, native_mem_for_type(t, lty, part->size)); 1807 a->next_param_stack += 4u; 1808 } 1809 if (dst.kind == NATIVE_LOC_NONE) { 1810 /* unused parameter; cursor already advanced */ 1811 } else if (to_reg) { 1812 NativeLoc d = native_loc_reg(dst.type ? dst.type : p->type, 1813 (NativeAllocClass)dst.cls, (Reg)dst.v.reg); 1814 if (!(src.kind == NATIVE_LOC_REG && loc_reg(src) == loc_reg(d))) 1815 arm_move(t, d, src); 1816 } else { 1817 NativeAddr home; 1818 memset(&home, 0, sizeof home); 1819 home.base_kind = NATIVE_ADDR_BASE_FRAME; 1820 home.base.frame = dst.v.frame; 1821 home.base_type = lty; 1822 home.offset = (i32)part->src_offset; 1823 arm_emit_mem(a, 0, src, home, native_mem_for_type(t, lty, part->size)); 1824 } 1825 } 1826 } 1827 1828 /* ============================ calls / returns ============================ */ 1829 1830 /* Advance the AAPCS argument-placement cursor across ONE argument `ai`, updating 1831 * the core-register count *next_int (r0..r3) and the outgoing-stack byte count 1832 * *stack. The single authority for the pair rule, shared by the signature 1833 * stack-size accounting so it cannot drift from emission: 1834 * - an 8-byte aligned pair rounds NCRN up to even and, if it does not fully 1835 * fit in r0..r3, passes ENTIRELY on the 8-byte-aligned stack (no straddle); 1836 * - every other part takes the next core register, else a 4-byte stack slot. */ 1837 static void arm_arg_advance(const ABIArgInfo* ai, u32* next_int, u32* stack) { 1838 int even = arm_arg_needs_even(ai); 1839 u32 p; 1840 if (ai->kind == ABI_ARG_IGNORE) return; 1841 if (ai->kind == ABI_ARG_INDIRECT) { 1842 if (*next_int < 4u) (*next_int)++; 1843 else *stack += 4u; 1844 return; 1845 } 1846 if (even) *next_int = align_up_u32(*next_int, 2u); 1847 for (p = 0; p < ai->nparts; ++p) { 1848 if (p == 0 && even && *next_int >= 4u) 1849 *stack = align_up_u32(*stack, 2u * ARM32_GPR_BYTES); 1850 if (*next_int < 4u) (*next_int)++; 1851 else *stack += 4u; 1852 } 1853 } 1854 1855 /* Resolve the ABI classification of call argument `i`. Named params come from 1856 * the callee's ABIFuncInfo; unnamed (variadic) args are synthesized here. A 1857 * scalar wider than one GPR (8-byte i64 / soft double) becomes two 4-byte INT 1858 * parts (AAPCS32 passes them in an even/odd register pair or 8-byte-aligned on 1859 * the stack — the even/8-byte alignment is applied by the caller below). */ 1860 static const ABIArgInfo* arm_param_abi(NativeTarget* t, const ABIFuncInfo* abi, 1861 const NativeCallDesc* desc, u32 i, 1862 ABIArgInfo* scratch) { 1863 u32 sz, align; 1864 if (abi && i < abi->nparams) return &abi->params[i]; 1865 sz = native_type_size(t, desc->args[i].type); 1866 align = native_type_align(t, desc->args[i].type); 1867 memset(scratch, 0, sizeof *scratch); 1868 scratch->kind = ABI_ARG_DIRECT; 1869 if (sz > 4u) { 1870 u32 nparts = (sz + 3u) / 4u, p; 1871 ABIArgPart* parts = arena_zarray(t->c->tu, ABIArgPart, nparts); 1872 for (p = 0; p < nparts; ++p) { 1873 u32 off = p * 4u; 1874 parts[p].cls = ABI_CLASS_INT; 1875 parts[p].loc = ABI_LOC_REG; 1876 parts[p].size = (sz - off) < 4u ? (sz - off) : 4u; 1877 parts[p].align = 4u; 1878 parts[p].src_offset = off; 1879 } 1880 /* Mirror the named classifier: parts[0].align carries the argument's natural 1881 * alignment so the AAPCS even/8-align walk treats a synthesized variadic 1882 * i64/double (or 8-aligned aggregate) the same as a named one. */ 1883 parts[0].align = align; 1884 scratch->nparts = nparts; 1885 scratch->parts = parts; 1886 return scratch; 1887 } 1888 scratch->nparts = 1; 1889 { 1890 ABIArgPart* part = arena_zarray(t->c->tu, ABIArgPart, 1); 1891 part->cls = ABI_CLASS_INT; 1892 part->loc = ABI_LOC_REG; 1893 part->size = sz; 1894 part->align = align; 1895 scratch->parts = part; 1896 } 1897 return scratch; 1898 } 1899 1900 /* Outgoing stack-argument bytes for a call: int parts beyond r0..r3. */ 1901 static u32 arm_call_stack_size(NativeTarget* t, const NativeCallDesc* desc) { 1902 const ABIFuncInfo* abi = abi_cg_func_info(t->c->abi, desc->fn_type); 1903 u32 next_int = (abi && abi->has_sret) ? 1u : 0u; 1904 u32 stack = 0, i, p; 1905 ABIArgInfo scratch; 1906 if (!abi) return 0; 1907 for (i = 0; i < desc->nargs; ++i) { 1908 const ABIArgInfo* ai = arm_param_abi(t, abi, desc, i, &scratch); 1909 int even = arm_arg_needs_even(ai); 1910 if (ai->kind == ABI_ARG_IGNORE) continue; 1911 if (ai->kind == ABI_ARG_INDIRECT) { 1912 if (next_int < 4u) next_int++; 1913 else { 1914 stack = align_up_u32(stack, 4u); 1915 stack += 4u; 1916 } 1917 continue; 1918 } 1919 if (even) next_int = align_up_u32(next_int, 2u); /* even-pair */ 1920 for (p = 0; p < ai->nparts; ++p) { 1921 if (p == 0 && even && next_int >= 4u) stack = align_up_u32(stack, 8u); 1922 if (next_int < 4u) next_int++; 1923 else stack += 4u; 1924 } 1925 } 1926 return align_up_u32(stack, 8u); 1927 } 1928 1929 static u32 arm_signature_stack_bytes(NativeTarget* t, KitCgTypeId fn_type, 1930 int* variadic, u32* nparams) { 1931 const ABIFuncInfo* abi = abi_cg_func_info(t->c->abi, fn_type); 1932 u32 next_int = (abi && abi->has_sret) ? 1u : 0u; 1933 u32 stack = 0, i; 1934 if (variadic) *variadic = abi && abi->variadic; 1935 if (nparams) *nparams = abi ? abi->nparams : 0u; 1936 if (!abi) return 0; 1937 for (i = 0; i < abi->nparams; ++i) 1938 arm_arg_advance(&abi->params[i], &next_int, &stack); 1939 return align_up_u32(stack, 8u); 1940 } 1941 1942 static u32 arm_call_stack_bytes(NativeTarget* t, const NativeCallDesc* desc) { 1943 return arm_call_stack_size(t, desc); 1944 } 1945 1946 static void arm_load_part(NativeTarget* t, NativeLoc dst, NativeLoc src, 1947 u32 src_offset, u32 size) { 1948 NativeAddr addr; 1949 /* Load `size` bytes at src_offset of `src` into register `dst`. */ 1950 if (src.kind == NATIVE_LOC_REG) { 1951 arm_move(t, dst, src); 1952 return; 1953 } 1954 if (src.kind == NATIVE_LOC_IMM) { 1955 i64 part; 1956 if (!native_loc_imm_part(src, src_offset, size, &part)) 1957 arm_panic(arm_of(t), "invalid immediate argument part"); 1958 arm_load_imm(t, dst, part); 1959 return; 1960 } 1961 if (native_loc_addr_role(src) == NATIVE_LOC_ADDR_ROLE_VALUE) { 1962 if (src_offset != 0u || size != t->c->target.ptr_size || 1963 !native_loc_address_value(src, &addr)) 1964 arm_panic(arm_of(t), "split or invalid address-value argument"); 1965 arm_load_addr(t, dst, addr); 1966 return; 1967 } 1968 { 1969 MemAccess mem; 1970 if (!native_loc_storage_addr(src, (i32)src_offset, &addr)) 1971 arm_panic(arm_of(t), "unsupported arg source location"); 1972 addr.base_type = dst.type; 1973 memset(&mem, 0, sizeof mem); 1974 mem.type = dst.type; 1975 mem.size = size; 1976 arm_emit_mem(arm_of(t), 1, dst, addr, mem); 1977 } 1978 } 1979 1980 /* Store an outgoing stack argument. A normal call writes the sp-anchored 1981 * outgoing-arg area ([sp, #stack_off]). A tail/sibling call instead writes the 1982 * caller's incoming-arg window ([r7 + arg_window_base + stack_off]) — the exact 1983 * address the tail-callee will read after the epilogue restores sp to the 1984 * caller's entry sp and branches. */ 1985 static void arm_store_outgoing(NativeTarget* t, u32 stack_off, NativeLoc src, 1986 u32 size, int tail) { 1987 Arm32NativeTarget* a = arm_of(t); 1988 NativeAddr addr; 1989 MemAccess mem; 1990 memset(&addr, 0, sizeof addr); 1991 addr.base_kind = NATIVE_ADDR_BASE_REG; 1992 addr.base.reg = tail ? ARM_FP : 13u; 1993 addr.offset = (i32)(tail ? arm_arg_window_base(a) + stack_off : stack_off); 1994 addr.base_type = src.type; 1995 memset(&mem, 0, sizeof mem); 1996 mem.type = src.type; 1997 mem.size = size; 1998 arm_emit_mem(a, 0, src, addr, mem); 1999 } 2000 2001 /* Materialize the address of a frame/stack-resident NativeLoc into `dst` (used 2002 * for arguments that the ABI explicitly classifies as indirect). */ 2003 static void arm_addr_of_loc(NativeTarget* t, NativeLoc dst, NativeLoc src) { 2004 NativeAddr addr; 2005 if (!native_loc_storage_addr(src, 0, &addr)) 2006 arm_panic(arm_of(t), "address-of non-memory location"); 2007 addr.base_type = dst.type; 2008 arm_load_addr(t, dst, addr); 2009 } 2010 2011 static void arm_emit_one_arg_move(NativeTarget* t, const NativeArgMove* m) { 2012 if (m->is_addr) 2013 arm_addr_of_loc(t, m->dst, m->src); 2014 else 2015 arm_load_part(t, m->dst, m->src, m->src_offset, m->size); 2016 } 2017 2018 static void arm_emit_reg_arg_moves(NativeTarget* t, NativeArgMove* moves, 2019 u32 n) { 2020 NativeArgShuffle s; 2021 if (n > ARM_MAX_REG_ARG_MOVES) arm_panic(arm_of(t), "too many register args"); 2022 memset(&s, 0, sizeof s); 2023 s.t = t; 2024 s.emit_one = arm_emit_one_arg_move; 2025 s.reg_move = arm_move; 2026 s.scratch[NATIVE_REG_INT] = ARM_SCRATCH; 2027 s.scratch_class_mask = 1u << NATIVE_REG_INT; 2028 native_arg_shuffle(&s, moves, n); 2029 } 2030 2031 static void arm_marshal_call(NativeTarget* t, const NativeCallDesc* desc, 2032 NativeCallPhase* plan) { 2033 Arm32NativeTarget* a = arm_of(t); 2034 const ABIFuncInfo* abi = abi_cg_func_info(t->c->abi, desc->fn_type); 2035 NativeCallPhaseRet* rets; 2036 u32 nrets_cap = (abi && abi->ret.kind == ABI_ARG_DIRECT && desc->nresults) 2037 ? abi->ret.nparts 2038 : ((!abi && desc->nresults) ? 1u : 0u); 2039 int tail = (desc->flags & CG_CALL_TAIL) != 0; 2040 int tail_callee_saved = 0; 2041 memset(plan, 0, sizeof *plan); 2042 rets = nrets_cap ? arena_zarray(t->c->tu, NativeCallPhaseRet, nrets_cap) : NULL; 2043 plan->callee = desc->callee; 2044 plan->rets = rets; 2045 plan->flags = desc->flags; 2046 plan->has_sret = abi && abi->has_sret; 2047 plan->is_variadic = abi && abi->variadic; 2048 plan->stack_arg_size = arm_call_stack_size(t, desc); 2049 /* A tail call's outgoing stack args reuse the incoming-arg window in place, so 2050 * they do not enlarge this frame's outgoing-arg area. */ 2051 if (!tail && plan->stack_arg_size > a->frame.max_outgoing) 2052 a->frame.max_outgoing = plan->stack_arg_size; 2053 2054 /* Outgoing stack args stage through IP normally; a tail call holds the callee 2055 * in IP, so it stages through LR instead (LR is restored by the epilogue 2056 * afterward; the in-window store address needs no scratch). */ 2057 u32 arg_stage = tail ? ARM_TMP : ARM_SCRATCH; 2058 2059 /* Stage an indirect callee into a register that survives the arg-register 2060 * setup, the outgoing stack-arg staging, AND (for a tail call) the epilogue's 2061 * POP of r0..r11. A tail call holds it in IP (never popped) and stages args via 2062 * LR; a normal call stages args via IP, so an IP- or arg-register-resident 2063 * callee moves to LR (BLX lr is valid). A callee already in a preserved 2064 * register (r4..r11, or the chosen staging reg) needs no move. 2065 * 2066 * The exception is the -O0 (single-pass) call with outgoing stack args: staging 2067 * those args may consume BOTH backend temporaries (IP for the value, LR for a 2068 * large-offset store address), so the callee cannot survive in LR. Spill it 2069 * to its frame home now — before any arg move clobbers its register — and 2070 * reload just before BLX (arm_emit_call). The known-frame (-O1) path keeps 2071 * the register form: its optimizer-allocated callee already survives, and 2072 * its frame is final (no body-time slot allocation). */ 2073 if (plan->callee.kind == NATIVE_LOC_REG && 2074 (NativeAllocClass)plan->callee.cls == NATIVE_REG_INT) { 2075 u32 cr = plan->callee.v.reg & 0xfu; 2076 if (!tail && !a->known_frame && plan->stack_arg_size > 0u) { 2077 NativeFrameSlot slot = arm_callee_spill_slot(a); 2078 NativeAddr home; 2079 memset(&home, 0, sizeof home); 2080 home.base_kind = NATIVE_ADDR_BASE_FRAME; 2081 home.base.frame = slot; 2082 home.base_type = plan->callee.type; 2083 arm_emit_mem(a, 0, plan->callee, home, 2084 native_mem_for_type(t, plan->callee.type, 4)); 2085 plan->callee = native_loc_stack(plan->callee.type, slot, 0); 2086 } else { 2087 u32 dst = tail ? ARM_SCRATCH : ARM_TMP; 2088 int needs = tail ? 1 : (cr <= 3u || cr == arg_stage); 2089 if (needs && cr != dst) { 2090 NativeLoc scratch = 2091 native_loc_reg(plan->callee.type, NATIVE_REG_INT, dst); 2092 arm_move(t, scratch, plan->callee); 2093 plan->callee = scratch; 2094 } 2095 } 2096 } 2097 2098 /* An indirect sibling call lives in IP across argument marshalling because 2099 * r0-r11 are restored by the tail epilogue and LR is the stack-part carrier. 2100 * A three-byte part needs both private registers: LR carries the part while 2101 * IP packs its final byte (and a far outgoing address can need IP as its 2102 * resolved base). Preserve the callee with one balanced temporary stack save 2103 * for the entire no-call marshalling interval, making both private registers 2104 * available to the exact-width boundary. Tail stack arguments are addressed 2105 * through FP into the incoming window, never through the temporarily shifted 2106 * SP; CFA is FP-anchored. There are no compiler-time exits between this PUSH 2107 * and its matching POP below. */ 2108 if (tail && plan->callee.kind == NATIVE_LOC_REG && 2109 loc_reg(plan->callee) == ARM_SCRATCH) { 2110 arm_emit_push(t->mc, 1u << ARM_SCRATCH); 2111 tail_callee_saved = 1; 2112 } 2113 { 2114 u32 next_int = (abi && abi->has_sret) ? 1u : 0u; 2115 u32 stack = 0, nmoves = 0, i, p; 2116 NativeArgMove moves[ARM_MAX_REG_ARG_MOVES]; 2117 KitCgTypeId i32t = builtin_id(KIT_CG_BUILTIN_I32); 2118 ABIArgInfo scratch; 2119 for (i = 0; i < desc->nargs; ++i) { 2120 const ABIArgInfo* ai = arm_param_abi(t, abi, desc, i, &scratch); 2121 int even = arm_arg_needs_even(ai); 2122 if (ai->kind == ABI_ARG_IGNORE) continue; 2123 if (ai->kind == ABI_ARG_INDIRECT) { 2124 /* Pass an ABI-classified indirect argument as a pointer to the caller's 2125 * copy (the callee makes its own copy in bind_param). The pointer rides 2126 * a core reg when one is free, else the outgoing stack. */ 2127 if (next_int < 4u) { 2128 NativeArgMove* m = &moves[nmoves++]; 2129 m->dst = native_loc_reg(i32t, NATIVE_REG_INT, (Reg)(next_int++)); 2130 m->src = desc->args[i]; 2131 m->src_offset = 0; 2132 m->size = 4u; 2133 m->is_addr = 1; 2134 } else { 2135 NativeLoc ptr = native_loc_reg(i32t, NATIVE_REG_INT, arg_stage); 2136 arm_addr_of_loc(t, ptr, desc->args[i]); 2137 stack = align_up_u32(stack, 4u); 2138 arm_store_outgoing(t, stack, ptr, 4u, tail); 2139 stack += 4u; 2140 } 2141 continue; 2142 } 2143 /* AAPCS32: round NCRN up to even for an 8-byte-aligned arg, then fill core 2144 * registers r0..r3; parts that do not fit spill to the (8-byte-aligned for 2145 * part 0) outgoing stack. An 8-byte scalar pair fits-or-fully-spills; 2146 * a larger aggregate straddles the reg/stack boundary (AAPCS C.5). */ 2147 if (even) next_int = align_up_u32(next_int, 2u); 2148 for (p = 0; p < ai->nparts; ++p) { 2149 const ABIArgPart* part = &ai->parts[p]; 2150 KitCgTypeId lty = arm_part_scalar_type(part); 2151 if (next_int < 4u) { 2152 NativeArgMove* m = &moves[nmoves++]; 2153 m->dst = native_loc_reg(lty, NATIVE_REG_INT, (Reg)(next_int++)); 2154 m->src = desc->args[i]; 2155 m->src_offset = part->src_offset; 2156 m->size = part->size; 2157 m->is_addr = 0; 2158 } else { 2159 if (p == 0 && even) stack = align_up_u32(stack, 8u); 2160 else stack = align_up_u32(stack, 4u); 2161 if (desc->args[i].kind == NATIVE_LOC_REG) { 2162 /* Source already in a register: store it straight to the outgoing 2163 * slot. Routing it through backend-private IP is unnecessary and 2164 * would add an independent lifetime while other call operands are 2165 * still leased. Keeping the source location intact also preserves 2166 * the instruction-scoped ownership contract. */ 2167 arm_store_outgoing(t, stack, desc->args[i], part->size, tail); 2168 } else { 2169 NativeLoc tmp = native_loc_reg(lty, NATIVE_REG_INT, arg_stage); 2170 arm_load_part(t, tmp, desc->args[i], part->src_offset, part->size); 2171 arm_store_outgoing(t, stack, tmp, part->size, tail); 2172 } 2173 stack += 4u; 2174 } 2175 } 2176 } 2177 arm_emit_reg_arg_moves(t, moves, nmoves); 2178 if (abi && abi->has_sret) { 2179 /* sret destination pointer in r0. A tail call forwards OUR own incoming 2180 * sret pointer (spilled at entry) so the tail-callee writes the result 2181 * straight into our caller's buffer; a normal call passes &result. */ 2182 NativeLoc r0 = native_loc_reg(i32t, NATIVE_REG_INT, ARM_R0); 2183 if (tail && a->has_sret) { 2184 NativeLoc saved = native_loc_stack(i32t, a->sret_ptr_slot, 0); 2185 arm_load_part(t, r0, saved, 0, 4u); 2186 } else if (desc->nresults) { 2187 arm_load_addr(t, r0, (NativeAddr){.base_kind = NATIVE_ADDR_BASE_FRAME, 2188 .base.frame = desc->results[0].v.frame, 2189 .base_type = i32t}); 2190 } 2191 } 2192 } 2193 if (tail_callee_saved) arm_emit_pop(t->mc, 1u << ARM_SCRATCH); 2194 if (abi && abi->ret.kind == ABI_ARG_DIRECT && desc->nresults) { 2195 u32 nr = 0, ni = 0, p; 2196 for (p = 0; p < abi->ret.nparts; ++p) { 2197 const ABIArgPart* part = &abi->ret.parts[p]; 2198 /* Per-lane type so each NDT-emitted result move is sized to the lane (4), 2199 * not the full 8-byte scalar (which the 32-bit backend cannot single-step 2200 * load/store). The ret pair is always r0:r1 (no even-rounding needed). */ 2201 KitCgTypeId pty = arm_part_scalar_type(part); 2202 rets[nr].src = native_loc_reg(pty, NATIVE_REG_INT, (Reg)(ni++)); 2203 rets[nr].dst = desc->results[0]; 2204 if (rets[nr].dst.kind == NATIVE_LOC_FRAME) 2205 rets[nr].dst = native_loc_stack(pty, desc->results[0].v.frame, 2206 (i32)part->src_offset); 2207 rets[nr].mem = native_mem_for_type(t, pty, part->size); 2208 nr++; 2209 } 2210 plan->nrets = nr; 2211 } else if (!abi && desc->nresults) { 2212 rets[0].src = native_loc_reg(desc->results[0].type, NATIVE_REG_INT, ARM_R0); 2213 rets[0].dst = desc->results[0]; 2214 rets[0].mem = native_mem_for_type(t, desc->results[0].type, 0); 2215 plan->nrets = 1; 2216 } 2217 } 2218 2219 static void arm_emit_call(NativeTarget* t, const NativeCallPhase* plan) { 2220 Arm32NativeTarget* a = arm_of(t); 2221 MCEmitter* mc = t->mc; 2222 if (plan->flags & CG_CALL_TAIL) { 2223 /* Sibling call: the outgoing args are already placed (register args live; 2224 * stack args written into the incoming-arg window). Tear the frame down with 2225 * lr restored to OUR caller's return address, then BRANCH to the callee — it 2226 * runs on our caller's frame and returns straight to our caller. The arm32 2227 * epilogue is frame-size-independent (MOV sp,r7), so no patch is needed even 2228 * on the single-pass path. The callee is direct (B.W) or held in IP (BX). */ 2229 if (plan->callee.kind != NATIVE_LOC_GLOBAL && 2230 plan->callee.kind != NATIVE_LOC_REG) 2231 arm_panic(a, "unsupported tail target"); 2232 arm_emit_frame_restore(a, /*to_pc=*/0); 2233 if (plan->callee.kind == NATIVE_LOC_GLOBAL) { 2234 u32 pos = mc_pos(mc); 2235 arm_emit_t32(mc, arm_b_w()); 2236 mc_emit_reloc_at(mc, mc->section_id, pos, R_ARM_THM_JUMP24, 2237 plan->callee.v.global.sym, plan->callee.v.global.addend, 0, 2238 0); 2239 } else { 2240 arm_emit_t16(mc, arm_bx(loc_reg(plan->callee))); 2241 } 2242 return; 2243 } 2244 if (plan->callee.kind == NATIVE_LOC_GLOBAL) { 2245 u32 pos = mc_pos(mc); 2246 arm_emit_t32(mc, arm_bl()); 2247 mc_emit_reloc_at(mc, mc->section_id, pos, R_ARM_THM_CALL, 2248 plan->callee.v.global.sym, plan->callee.v.global.addend, 0, 2249 0); 2250 return; 2251 } 2252 if (plan->callee.kind == NATIVE_LOC_REG) { 2253 arm_emit_t16(mc, arm_blx_reg(loc_reg(plan->callee))); 2254 return; 2255 } 2256 if (plan->callee.kind == NATIVE_LOC_STACK) { 2257 /* -O0 indirect call with stack args: the callee was spilled to a frame home 2258 * (arm_marshal_call) to free the backend staging registers. Arg staging is 2259 * done now, so IP is free; reload the target and BLX it. */ 2260 NativeLoc tmp = native_loc_reg(plan->callee.type, NATIVE_REG_INT, ARM_SCRATCH); 2261 NativeAddr home; 2262 memset(&home, 0, sizeof home); 2263 home.base_kind = NATIVE_ADDR_BASE_FRAME; 2264 home.base.frame = plan->callee.v.stack.slot; 2265 home.base_type = plan->callee.type; 2266 arm_emit_mem(a, 1, tmp, home, native_mem_for_type(t, plan->callee.type, 4)); 2267 arm_emit_t16(mc, arm_blx_reg(ARM_SCRATCH)); 2268 return; 2269 } 2270 arm_panic(a, "unsupported call target"); 2271 } 2272 2273 static void arm_marshal_ret(NativeTarget* t, const CGFuncDesc* fd, 2274 const NativeLoc* value, 2275 NativeCallPhaseRet** out_rets, u32* out_nrets) { 2276 Arm32NativeTarget* a = arm_of(t); 2277 const ABIFuncInfo* abi = abi_cg_func_info(t->c->abi, fd->fn_type); 2278 NativeCallPhaseRet* rets = NULL; 2279 u32 nr = 0; 2280 if (value) rets = arena_zarray(t->c->tu, NativeCallPhaseRet, 4); 2281 if (value && abi && abi->ret.kind == ABI_ARG_INDIRECT) { 2282 /* Large aggregate return: copy the value into the caller-provided 2283 * destination via the hidden sret pointer spilled at entry. The body's 2284 * return slot is *value; load the saved pointer into ip, then copy. No 2285 * register return parts. */ 2286 KitCgTypeId i32t = builtin_id(KIT_CG_BUILTIN_I32); 2287 NativeLoc dstp = native_loc_reg(i32t, NATIVE_REG_INT, ARM_SCRATCH); 2288 NativeLoc saved = native_loc_stack(i32t, a->sret_ptr_slot, 0); 2289 NativeAddr dst_addr, src_addr; 2290 AggregateAccess access; 2291 arm_load_part(t, dstp, saved, 0, 4u); 2292 memset(&dst_addr, 0, sizeof dst_addr); 2293 dst_addr.base_kind = NATIVE_ADDR_BASE_REG; 2294 dst_addr.base.reg = ARM_SCRATCH; 2295 dst_addr.base_type = value->type; 2296 memset(&src_addr, 0, sizeof src_addr); 2297 if (value->kind == NATIVE_LOC_FRAME) { 2298 src_addr.base_kind = NATIVE_ADDR_BASE_FRAME; 2299 src_addr.base.frame = value->v.frame; 2300 } else if (value->kind == NATIVE_LOC_STACK) { 2301 src_addr.base_kind = NATIVE_ADDR_BASE_FRAME; 2302 src_addr.base.frame = value->v.stack.slot; 2303 src_addr.offset = value->v.stack.offset; 2304 } else { 2305 arm_panic(a, "indirect return value must be in memory"); 2306 } 2307 src_addr.base_type = value->type; 2308 memset(&access, 0, sizeof access); 2309 access.type = value->type; 2310 access.size = (u32)cg_type_size(t->c, value->type); 2311 access.align = native_type_align(t, value->type); 2312 arm_copy_bytes(t, dst_addr, src_addr, access); 2313 *out_rets = NULL; 2314 *out_nrets = 0; 2315 return; 2316 } 2317 if (value && abi && abi->ret.kind == ABI_ARG_DIRECT) { 2318 u32 ni = 0, p; 2319 int exact_parts = 0; 2320 for (p = 0; p < abi->ret.nparts; ++p) { 2321 const ABIArgPart* part = &abi->ret.parts[p]; 2322 if (part->size != 1u && part->size != 2u && part->size != 4u && 2323 part->size != 8u) { 2324 exact_parts = 1; 2325 break; 2326 } 2327 } 2328 2329 /* A return-plan register destination makes the generic write phase 2330 * materialize its source using the scalar carrier type. For a three-byte 2331 * AAPCS aggregate tail that carrier is I32, so the exact mem.size=3 on the 2332 * final move arrives too late: materialization has already read four bytes. 2333 * Future 5/6/7-byte parts have the same mismatch. Once any such part is 2334 * present, fill every return register here in ABI order through the shared 2335 * exact-width argument boundary. arm_load_part uses only backend-private 2336 * temporaries, preserving return registers populated by earlier parts. */ 2337 if (exact_parts) { 2338 for (p = 0; p < abi->ret.nparts; ++p) { 2339 const ABIArgPart* part = &abi->ret.parts[p]; 2340 KitCgTypeId pty = arm_part_scalar_type(part); 2341 NativeLoc dst = 2342 native_loc_reg(pty, NATIVE_REG_INT, (Reg)(ni++)); 2343 arm_load_part(t, dst, *value, part->src_offset, part->size); 2344 } 2345 *out_rets = NULL; 2346 *out_nrets = 0; 2347 return; 2348 } 2349 2350 for (p = 0; p < abi->ret.nparts; ++p) { 2351 const ABIArgPart* part = &abi->ret.parts[p]; 2352 /* Per-lane type: an i64/double return is two i32 lanes in r0:r1; sizing 2353 * each move from the lane type keeps the 32-bit backend off the 8-byte 2354 * memory path. (See arm_part_scalar_type.) */ 2355 KitCgTypeId pty = arm_part_scalar_type(part); 2356 rets[nr].src = *value; 2357 if (rets[nr].src.kind == NATIVE_LOC_FRAME) 2358 rets[nr].src = 2359 native_loc_stack(pty, value->v.frame, (i32)part->src_offset); 2360 rets[nr].dst = native_loc_reg(pty, NATIVE_REG_INT, (Reg)(ni++)); 2361 rets[nr].mem = native_mem_for_type(t, pty, part->size); 2362 nr++; 2363 } 2364 } else if (value) { 2365 rets[0].src = *value; 2366 rets[0].dst = native_loc_reg(value->type, NATIVE_REG_INT, ARM_R0); 2367 rets[0].mem = native_mem_for_type(t, value->type, 0); 2368 nr = 1; 2369 } 2370 *out_rets = rets; 2371 *out_nrets = nr; 2372 } 2373 2374 static void arm_ret(NativeTarget* t) { 2375 Arm32NativeTarget* a = arm_of(t); 2376 arm_jump(t, a->epilogue_label); 2377 } 2378 2379 /* ============================ misc / stubs ============================ */ 2380 2381 static void arm_trap(NativeTarget* t) { arm_emit_t16(t->mc, arm_bkpt(0u)); } 2382 static void arm_set_loc(NativeTarget* t, SrcLoc loc) { 2383 arm_of(t)->loc = loc; 2384 mc_set_loc(t->mc, loc); 2385 } 2386 2387 #define ARM_UNIMPL(name) arm_panic(arm_of(t), name " not implemented in Phase 1") 2388 2389 /* Assemble up to 4 little-endian bytes of `c` starting at byte `off` into a u32. */ 2390 static u32 arm_const_lane(ConstBytes c, u32 off) { 2391 u32 v = 0, i; 2392 for (i = 0; i < 4u && off + i < c.size; ++i) 2393 v |= (u32)c.bytes[off + i] << (i * 8u); 2394 return v; 2395 } 2396 2397 static void arm_load_const(NativeTarget* t, NativeLoc dst, ConstBytes c) { 2398 Arm32NativeTarget* a = arm_of(t); 2399 /* Scalar constant <=4 bytes: reinterpret the ABI bytes (little-endian on 2400 * arm32) as a u32 and materialize via arm_load_imm (MOV.W/MVN.W/MOVW+MOVT). */ 2401 if (c.size <= 4u) { 2402 arm_load_imm(t, dst, (i64)(i32)arm_const_lane(c, 0u)); 2403 return; 2404 } 2405 /* 8-byte (i64/double) backstop. At -O0 the NDT lowers wide8 constants into two 2406 * 32-bit lanes before they reach the backend (nd_load_const guards it), so 2407 * this is only exercised by paths that hand the full 8-byte constant straight 2408 * to a destination — materialize the low lane then the high lane, little-end 2409 * first. A memory destination (frame/stack home) carries both lanes 4 bytes 2410 * apart; a single 32-bit register can only hold the low lane (the high lane 2411 * has no home in one NativeLoc), which matches a 32-bit reinterpret. */ 2412 if (c.size > 8u) arm_panic(a, "load_const wider than 8 bytes"); 2413 if (dst.kind == NATIVE_LOC_REG) { 2414 arm_load_imm(t, dst, (i64)(i32)arm_const_lane(c, 0u)); 2415 return; 2416 } 2417 { 2418 KitCgTypeId i32t = builtin_id(KIT_CG_BUILTIN_I32); 2419 NativeLoc lane = native_loc_reg(i32t, NATIVE_REG_INT, ARM_TMP); 2420 NativeAddr home; 2421 MemAccess mem = native_mem_for_type(t, i32t, 4u); 2422 u32 off; 2423 memset(&home, 0, sizeof home); 2424 home.base_type = i32t; 2425 if (dst.kind == NATIVE_LOC_FRAME) { 2426 home.base_kind = NATIVE_ADDR_BASE_FRAME; 2427 home.base.frame = dst.v.frame; 2428 } else if (dst.kind == NATIVE_LOC_STACK) { 2429 home.base_kind = NATIVE_ADDR_BASE_FRAME; 2430 home.base.frame = dst.v.stack.slot; 2431 home.offset = dst.v.stack.offset; 2432 } else { 2433 arm_panic(a, "8-byte load_const destination not lowered"); 2434 } 2435 for (off = 0; off < c.size; off += 4u) { 2436 NativeAddr d = home; 2437 d.offset += (i32)off; 2438 arm_load_imm(t, lane, (i64)(i32)arm_const_lane(c, off)); 2439 arm_emit_mem(a, 0, lane, d, mem); 2440 } 2441 } 2442 } 2443 static void arm_load_label_addr(NativeTarget* t, NativeLoc dst, MCLabel l) { 2444 /* `&&label` address-take (computed goto / jump-table base): materialize the 2445 * label's address via MOVW/MOVT ABS against its per-block local symbol — the 2446 * same form arm_emit_global_addr uses for a global, so an encoding-divergent 2447 * assembler recomputes the split immediate. (Setting the Thumb bit for a 2448 * BX/BLX through this address is the indirect-branch consumer's job.) */ 2449 Arm32NativeTarget* a = arm_of(t); 2450 ObjSymId sym = mc_label_symbol(t->mc, l); 2451 arm_emit_global_addr(a, loc_reg(dst), sym, 0); 2452 } 2453 static void arm_indirect_branch(NativeTarget* t, NativeLoc addr, 2454 const MCLabel* targets, u32 n) { 2455 /* The cg layer materialized the dense switch's target code address into 2456 * `addr` (a load of table[idx] from the .Lkit_jt rodata table). The table 2457 * entries are R_ARM_ABS32 relocations against per-block local label symbols, 2458 * which carry NO Thumb bit (only STT_FUNC symbols get it in arm_func_end), so 2459 * the loaded address is even. BX to an even address would switch to ARM 2460 * state (UNPREDICTABLE on M-profile); force the Thumb bit with ORR #1 first. 2461 * `addr` is released by the caller right after this, so clobbering it is free. 2462 */ 2463 MCEmitter* mc = t->mc; 2464 u32 ra = loc_reg(addr); 2465 u32 enc; 2466 (void)targets; 2467 (void)n; 2468 thumb_expand_imm_encode(1u, &enc); 2469 arm_emit_t32(mc, arm_dp_imm(2u, 0u, ra, ra, enc)); /* ORR ra, ra, #1 */ 2470 arm_emit_t16(mc, arm_bx(ra)); /* BX ra */ 2471 } 2472 static void arm_tls_addr_of(NativeTarget* t, NativeLoc dst, ObjSymId sym, 2473 i64 addend) { 2474 /* The M-profile (Cortex-M) has no CP15 thread-ID register and the bare-metal 2475 * arm32 lane is single-threaded, so the local-exec model degenerates: each 2476 * `__thread` object has exactly one instance, addressed absolutely like a 2477 * regular static. Materialize its address with the same MOVW/MOVT-absolute 2478 * sequence as a global; the freestanding image places the TLS image as part 2479 * of its data, so &tlsvar is a link-time constant. (A true variant-I TLS path 2480 * with a software thread pointer is a follow-on for a multi-threaded lane.) */ 2481 arm_emit_global_addr(arm_of(t), loc_reg(dst), sym, addend); 2482 } 2483 /* Aggregate / wide byte copy (struct/array, INDIRECT param/return copies, the 2484 * wide va_arg copy, and memmove's loops). Each side is resolved ONCE to a stable 2485 * (base register, displacement): a FRAME/REG side in range for the whole span is 2486 * addressed directly; a GLOBAL or out-of-range side has its effective address 2487 * materialized into a held scratch. A 4|2|1 granule ladder then transfers each 2488 * chunk through a register that aliases neither base. 2489 * 2490 * Register budget, allocated explicitly up front: two reserved scratch (IP, LR); 2491 * one callee-save borrowed (pushed/popped) only for the rare case where BOTH 2492 * sides must be materialized AND the two bases plus the transfer exceed {IP,LR}. 2493 * The borrow is taken before any frame-relative materialization, so r7-relative 2494 * offsets (and already-held REG bases) are unaffected by the SP change. */ 2495 2496 /* The natural (base register, displacement) of a FRAME/REG addr, no range check. */ 2497 static void arm_addr_natural(Arm32NativeTarget* a, const NativeAddr* addr, 2498 u32* base, i32* off) { 2499 if (addr->base_kind == NATIVE_ADDR_BASE_FRAME) { 2500 NativeFrameSlotEntry* s = native_frame_slot_at(&a->frame, addr->base.frame); 2501 *base = ARM_FP; 2502 *off = -(i32)s->off + addr->offset; 2503 } else { 2504 *base = addr->base.reg & 0xfu; 2505 *off = addr->offset; 2506 } 2507 } 2508 2509 /* Whether `addr` can be addressed directly for a `span`-byte access (a real base 2510 * register + in-range displacement for the whole span). GLOBAL is never direct. 2511 * On success sets base+off to the natural (base register, displacement). */ 2512 static int arm_addr_direct(Arm32NativeTarget* a, const NativeAddr* addr, 2513 u32 span, u32* base, i32* off) { 2514 if (addr->base_kind != NATIVE_ADDR_BASE_FRAME && 2515 addr->base_kind != NATIVE_ADDR_BASE_REG) 2516 return 0; 2517 arm_addr_natural(a, addr, base, off); 2518 return arm_span_inline(*off, span); 2519 } 2520 2521 /* Take a transfer/held-base register for copy_bytes: a free reserved scratch 2522 * (IP then LR) not already in `*used`, else a borrowed callee-save (r4..r6 not in 2523 * `*used`, pushed; recorded in `*borrowed`, which starts 0xff and is set once). */ 2524 static u32 arm_copy_take_reg(Arm32NativeTarget* a, u32* used, u32* borrowed) { 2525 u32 r; 2526 if (!(*used & (1u << ARM_SCRATCH))) r = ARM_SCRATCH; 2527 else if (!(*used & (1u << ARM_TMP))) r = ARM_TMP; 2528 else { 2529 r = (*used & (1u << 4)) ? ((*used & (1u << 5)) ? 6u : 5u) : 4u; 2530 *borrowed = r; 2531 arm_emit_push(a->base.mc, 1u << r); 2532 } 2533 *used |= 1u << r; 2534 return r; 2535 } 2536 2537 static void arm_copy_bytes(NativeTarget* t, NativeAddr dst, NativeAddr src, 2538 AggregateAccess acc) { 2539 Arm32NativeTarget* a = arm_of(t); 2540 u32 size = acc.size, off; 2541 u32 sbase, dbase, xfer, used = 0u, borrowed = 0xffu; 2542 i32 soff = 0, doff = 0; 2543 int sdir, ddir; 2544 if (!size) return; 2545 if (size > 4096u) arm_panic(a, "aggregate copy > 4096 bytes (needs memcpy)"); 2546 2547 sdir = arm_addr_direct(a, &src, size, &sbase, &soff); 2548 ddir = arm_addr_direct(a, &dst, size, &dbase, &doff); 2549 if (sdir) used |= 1u << sbase; 2550 if (ddir) used |= 1u << dbase; 2551 2552 /* Allocate held scratch for the non-direct sides, then the transfer reg. */ 2553 if (!sdir) sbase = arm_copy_take_reg(a, &used, &borrowed); 2554 if (!ddir) dbase = arm_copy_take_reg(a, &used, &borrowed); 2555 xfer = arm_copy_take_reg(a, &used, &borrowed); 2556 2557 /* Materialize the non-direct sides (after any borrow-push so a frame-relative 2558 * address still reads the unchanged r7). */ 2559 if (!sdir) { 2560 if (src.base_kind == NATIVE_ADDR_BASE_GLOBAL) 2561 arm_emit_global_addr(a, sbase, src.base.global.sym, 2562 src.base.global.addend + src.offset); 2563 else { 2564 u32 nb; 2565 i32 no; 2566 arm_addr_natural(a, &src, &nb, &no); 2567 arm_emit_base_off(a, sbase, nb, no); 2568 } 2569 soff = 0; 2570 } 2571 if (!ddir) { 2572 if (dst.base_kind == NATIVE_ADDR_BASE_GLOBAL) 2573 arm_emit_global_addr(a, dbase, dst.base.global.sym, 2574 dst.base.global.addend + dst.offset); 2575 else { 2576 u32 nb; 2577 i32 no; 2578 arm_addr_natural(a, &dst, &nb, &no); 2579 arm_emit_base_off(a, dbase, nb, no); 2580 } 2581 doff = 0; 2582 } 2583 2584 /* Granule ladder: word, then trailing halfword/byte. Each chunk stays in range 2585 * (the resolver guaranteed the span fits, and a materialized base has off 0). */ 2586 for (off = 0; off < size;) { 2587 u32 rem = size - off; 2588 u32 sz = rem >= 4u ? 4u : rem >= 2u ? 2u : 1u; 2589 arm_emit_mem_one(a, 1, xfer, sbase, soff + (i32)off, sz); 2590 arm_emit_mem_one(a, 0, xfer, dbase, doff + (i32)off, sz); 2591 off += sz; 2592 } 2593 if (borrowed != 0xffu) arm_emit_pop(a->base.mc, 1u << borrowed); 2594 } 2595 2596 /* Copy `size` bytes between two register-based addresses with the same 4|2|1 2597 * granule ladder as copy_bytes, but in a chosen direction: forward (low→high) 2598 * or backward (high→low). memmove picks the overlap-safe direction at runtime. 2599 * Transfers through `xfer`, which must alias neither base. */ 2600 static void arm_copy_granules_dir(Arm32NativeTarget* a, u32 dbase, u32 sbase, 2601 u32 size, u32 xfer, int backward) { 2602 if (backward) { 2603 u32 off = size; 2604 while (off > 0u) { 2605 u32 sz = off >= 4u ? 4u : off >= 2u ? 2u : 1u; 2606 off -= sz; 2607 arm_emit_mem_one(a, 1, xfer, sbase, (i32)off, sz); 2608 arm_emit_mem_one(a, 0, xfer, dbase, (i32)off, sz); 2609 } 2610 } else { 2611 u32 off = 0; 2612 while (off < size) { 2613 u32 rem = size - off; 2614 u32 sz = rem >= 4u ? 4u : rem >= 2u ? 2u : 1u; 2615 arm_emit_mem_one(a, 1, xfer, sbase, (i32)off, sz); 2616 arm_emit_mem_one(a, 0, xfer, dbase, (i32)off, sz); 2617 off += sz; 2618 } 2619 } 2620 } 2621 2622 /* set_bytes: store the materialized fill byte `v` across acc.size bytes. The 2623 * common case is struct/array zero-init (v == 0); any fill byte is correct. One 2624 * STRB per byte — correctness-first; word-splat widening is deferred (it would 2625 * need a second scratch for the 0x01010101 multiplier or a shifted-ORR encoder, 2626 * and the dst base may itself live in ip). */ 2627 static void arm_set_bytes(NativeTarget* t, NativeAddr dst, NativeLoc v, 2628 AggregateAccess acc) { 2629 Arm32NativeTarget* a = arm_of(t); 2630 MemAccess mem = acc.mem; 2631 mem.size = 1u; 2632 mem.align = 1u; 2633 for (u32 off = 0; off < acc.size; ++off) { 2634 NativeAddr d = dst; 2635 d.offset += (i32)off; 2636 arm_emit_mem(a, 0, v, d, mem); /* STRB v, [dst+off] */ 2637 } 2638 } 2639 /* C bit-fields. The storage unit {bf.storage.type, bf.storage.size} lives at 2640 * bf.storage_offset from `addr`; the field is `bf.bit_width` bits at bit 2641 * bf.bit_offset within it. Load = load the unit (zero-extended) then UBFX/SBFX; 2642 * store = load the unit, BFI the value in, store the unit back. Sub-word storage 2643 * units (1/2 bytes) are loaded/stored at their natural width, so the BFI/UBFX 2644 * geometry (offset+width <= storage_bits) stays in range. */ 2645 static void arm_bitfield_load(NativeTarget* t, NativeLoc dst, NativeAddr addr, 2646 BitFieldAccess bf) { 2647 Arm32NativeTarget* a = arm_of(t); 2648 MCEmitter* mc = t->mc; 2649 u32 rd = loc_reg(dst); 2650 u32 width = bf.bit_width ? bf.bit_width : 1u; 2651 NativeAddr saddr = addr; 2652 NativeLoc unit = dst; 2653 saddr.offset += (i32)bf.storage_offset; 2654 unit.type = bf.storage.type ? bf.storage.type : dst.type; 2655 arm_emit_mem(a, 1, unit, saddr, bf.storage); /* LDR(B/H) rd, [addr+soff] */ 2656 if (bf.signed_) 2657 arm_emit_t32(mc, arm_sbfx(rd, rd, bf.bit_offset, width)); 2658 else 2659 arm_emit_t32(mc, arm_ubfx(rd, rd, bf.bit_offset, width)); 2660 } 2661 static void arm_bitfield_store(NativeTarget* t, NativeAddr addr, NativeLoc v, 2662 BitFieldAccess bf) { 2663 Arm32NativeTarget* a = arm_of(t); 2664 MCEmitter* mc = t->mc; 2665 u32 rv = loc_reg(v); 2666 u32 width = bf.bit_width ? bf.bit_width : 1u; 2667 KitCgTypeId unit_ty = bf.storage.type ? bf.storage.type : v.type; 2668 NativeLoc word = native_loc_reg(unit_ty, NATIVE_REG_INT, ARM_TMP); 2669 NativeAddr saddr = addr; 2670 saddr.offset += (i32)bf.storage_offset; 2671 arm_emit_mem(a, 1, word, saddr, bf.storage); /* LDR tmp, [addr] */ 2672 arm_emit_t32(mc, arm_bfi(ARM_TMP, rv, bf.bit_offset, width)); /* insert v */ 2673 arm_emit_mem(a, 0, word, saddr, bf.storage); /* STR tmp, [addr] */ 2674 } 2675 static void arm_alloca(NativeTarget* t, NativeLoc dst, NativeLoc size, 2676 u32 align) { 2677 Arm32NativeTarget* a = arm_of(t); 2678 MCEmitter* mc = t->mc; 2679 u32 rsz = loc_reg(size), rd = loc_reg(dst); 2680 u32 enc7; 2681 (void)align; /* AAPCS keeps sp 8-byte aligned; round the request up to 8. */ 2682 thumb_expand_imm_encode(7u, &enc7); 2683 /* ip = (size + 7) & ~7; sp -= ip. */ 2684 arm_emit_t32(mc, arm_add_imm12(ARM_SCRATCH, rsz, 7u)); 2685 arm_emit_t32(mc, arm_dp_imm(1u, 0u, ARM_SCRATCH, ARM_SCRATCH, enc7)); /* BIC */ 2686 arm_emit_t32(mc, arm_sub_reg(13u, 13u, ARM_SCRATCH)); 2687 /* dst = sp + max_outgoing (the block sits above the outgoing-arg area). The 2688 * offset is finalized in arm_func_end; emit a placeholder ADDW to patch. */ 2689 if (a->n_alloca_patch >= 16u) arm_panic(a, "too many alloca sites"); 2690 a->alloca_patch_rd[a->n_alloca_patch] = (u8)rd; 2691 a->alloca_patch_pos[a->n_alloca_patch++] = mc_pos(mc); 2692 arm_emit_t32(mc, arm_add_imm12(rd, 13u, 0u)); 2693 a->frame.has_alloca = 1; 2694 } 2695 /* ============================ atomics ============================ */ 2696 /* ARMv7-M atomics over LDREX/STREX (word/halfword/byte) + DMB. The cg layer 2697 * routes 8-byte _Atomic to the spinlock libcall (atomic_lock_free_max=4), so 2698 * only <=4-byte ops reach here. 2699 * 2700 * Register budget: LR (ARM_TMP) holds the access address. On the shared O0 2701 * path, a value operand may occupy IP (ARM_SCRATCH); on O1, operands arrive in 2702 * allocated r4..r11 or instruction-scoped r0..r3 locations and IP/LR remain 2703 * backend-private. The core must therefore keep IP unavailable for its own 2704 * extra state so it is safe on both paths. STREX additionally needs its status, 2705 * value and base registers mutually distinct. 2706 * 2707 * The rmw/cas loops therefore borrow callee-saved registers for the private 2708 * temporaries (new value + STREX status). At -O1 the optimizer freely allocates 2709 * the operands (prior/expected/desired/ok, or rmw dst/val) and the address base 2710 * across the whole callee-saved set, so a fixed r4/r5 would alias an operand. The 2711 * borrow is therefore OPERAND-AWARE (arm_atomic_borrow): it picks free callee- 2712 * saved registers clear of the live operand+base set, guaranteeing base + all 2713 * operands + the STREX temporaries are mutually distinct. The picked 2714 * registers are push/pop-balanced so the 2715 * caller's callee-saved values are preserved (free at -O0, safe at -O1). */ 2716 2717 /* Pick `n` distinct callee-saved temporaries (r4,r5,r6,r8,r9,r10,r11 — fp/r7 is 2718 * reserved) clear of the `used` register set, for the atomic LDREX/STREX private 2719 * temporaries. Fills regs[0..n) and returns their push/pop mask in *mask. The 2720 * pool has 7 registers; the largest live set (rmw: dst+val+base = 3, cas: 2721 * 4 operands + base = 5) always leaves enough free for n (2 / 1 respectively). */ 2722 static void arm_atomic_borrow(Arm32NativeTarget* a, u32 used, u32 n, u32* regs, 2723 u32* mask) { 2724 static const u32 pool[] = {4u, 5u, 6u, 8u, 9u, 10u, 11u}; 2725 u32 got = 0u, m = 0u, i; 2726 for (i = 0; i < sizeof pool / sizeof pool[0] && got < n; ++i) { 2727 if (used & (1u << pool[i])) continue; 2728 regs[got++] = pool[i]; 2729 m |= 1u << pool[i]; 2730 } 2731 if (got < n) arm_panic(a, "atomic: no free scratch register"); 2732 *mask = m; 2733 } 2734 2735 static int arm_order_acquire(KitCgMemOrder o) { 2736 return o == KIT_CG_MO_CONSUME || o == KIT_CG_MO_ACQUIRE || 2737 o == KIT_CG_MO_ACQ_REL || o == KIT_CG_MO_SEQ_CST; 2738 } 2739 static int arm_order_release(KitCgMemOrder o) { 2740 return o == KIT_CG_MO_RELEASE || o == KIT_CG_MO_ACQ_REL || 2741 o == KIT_CG_MO_SEQ_CST; 2742 } 2743 2744 /* Materialize the access address into lr (ARM_TMP) and return it. lr is never an 2745 * NDT operand and is dead in the body, so it never collides with the value 2746 * operands (which the value-cache keeps in r0..r3 / ip). The incoming NativeAddr 2747 * may be a frame slot or a register; load_addr lands the pointer in lr, which 2748 * the LDREX/STREX sequence then uses as the base. */ 2749 static u32 arm_atomic_addr_reg(Arm32NativeTarget* a, NativeAddr addr) { 2750 /* Use the pointer's own register when the address already is one (the -O1 2751 * case: location MIR keeps the allocated address component explicit). 2752 * Otherwise (a FRAME/GLOBAL address, e.g. -O0) materialize into 2753 * backend-private LR, which holds no operand there. */ 2754 if (addr.base_kind == NATIVE_ADDR_BASE_REG && addr.offset == 0 && 2755 addr.index_kind == NATIVE_ADDR_INDEX_NONE) 2756 return addr.base.reg & 0xfu; 2757 arm_load_addr(&a->base, 2758 native_loc_reg(builtin_id(KIT_CG_BUILTIN_I32), NATIVE_REG_INT, 2759 ARM_TMP), 2760 addr); 2761 return ARM_TMP; 2762 } 2763 2764 /* LDREX-family load for the access width (word / halfword / byte). */ 2765 static void arm_emit_ldrex(Arm32NativeTarget* a, u32 sz, u32 rt, u32 base) { 2766 MCEmitter* mc = a->base.mc; 2767 if (sz >= 4u) arm_emit_t32(mc, arm_ldrex(rt, base, 0u)); 2768 else if (sz == 2u) arm_emit_t32(mc, arm_ldrexh(rt, base)); 2769 else arm_emit_t32(mc, arm_ldrexb(rt, base)); 2770 } 2771 /* STREX-family store; writes the 0/1 success status into `rd`. */ 2772 static void arm_emit_strex(Arm32NativeTarget* a, u32 sz, u32 rd, u32 rt, 2773 u32 base) { 2774 MCEmitter* mc = a->base.mc; 2775 if (sz >= 4u) arm_emit_t32(mc, arm_strex(rd, rt, base, 0u)); 2776 else if (sz == 2u) arm_emit_t32(mc, arm_strexh(rd, rt, base)); 2777 else arm_emit_t32(mc, arm_strexb(rd, rt, base)); 2778 } 2779 2780 static void arm_atomic_load(NativeTarget* t, NativeLoc dst, NativeAddr addr, 2781 MemAccess mem, KitCgMemOrder order) { 2782 Arm32NativeTarget* a = arm_of(t); 2783 MCEmitter* mc = t->mc; 2784 u32 sz = mem.size ? mem.size : native_type_size(t, dst.type); 2785 u32 base = arm_atomic_addr_reg(a, addr); 2786 NativeAddr m; 2787 if (sz > 4u) arm_panic(a, "8-byte atomic load not lowered (spinlock libcall)"); 2788 memset(&m, 0, sizeof m); 2789 m.base_kind = NATIVE_ADDR_BASE_REG; 2790 m.base.reg = base; 2791 m.base_type = dst.type; 2792 arm_emit_mem(a, 1, dst, m, mem); /* plain LDR/LDRH/LDRB is atomic for <=4B */ 2793 if (arm_order_acquire(order)) arm_emit_t32(mc, arm_dmb(0xfu)); 2794 } 2795 2796 static void arm_atomic_store(NativeTarget* t, NativeAddr addr, NativeLoc v, 2797 MemAccess mem, KitCgMemOrder order) { 2798 Arm32NativeTarget* a = arm_of(t); 2799 MCEmitter* mc = t->mc; 2800 u32 sz = mem.size ? mem.size : native_type_size(t, v.type); 2801 u32 base = arm_atomic_addr_reg(a, addr); 2802 NativeAddr m; 2803 if (sz > 4u) arm_panic(a, "8-byte atomic store not lowered (spinlock libcall)"); 2804 if (arm_order_release(order)) arm_emit_t32(mc, arm_dmb(0xfu)); 2805 memset(&m, 0, sizeof m); 2806 m.base_kind = NATIVE_ADDR_BASE_REG; 2807 m.base.reg = base; 2808 m.base_type = v.type; 2809 arm_emit_mem(a, 0, v, m, mem); /* plain STR/STRH/STRB is atomic for <=4B */ 2810 if (order == KIT_CG_MO_SEQ_CST) arm_emit_t32(mc, arm_dmb(0xfu)); 2811 } 2812 /* The ARMv7-M barrier option for a full-system barrier ("sy"). 64-bit clz/ctz/ 2813 * bswap on a 32-bit target are already routed to __*di2 libcalls by cg, so the 2814 * backend only ever sees the 16/32-bit forms here. Each lowered intrinsic must 2815 * stay in lockstep with arm32_supports_intrinsic (arch.c). */ 2816 #define ARM_BARRIER_SY 0xfu 2817 2818 static void arm_atomic_rmw(NativeTarget* t, KitCgAtomicOp op, NativeLoc dst, 2819 NativeAddr addr, NativeLoc val, MemAccess mem, 2820 KitCgMemOrder order) { 2821 Arm32NativeTarget* a = arm_of(t); 2822 MCEmitter* mc = t->mc; 2823 u32 sz = mem.size ? mem.size : native_type_size(t, dst.type); 2824 u32 rd = loc_reg(dst); 2825 u32 rv = loc_reg(val); 2826 u32 base, newv, status, temps[2], pushmask; 2827 MCLabel retry = mc_label_new(mc); 2828 if (sz > 4u) arm_panic(a, "8-byte atomic rmw not lowered (spinlock libcall)"); 2829 /* Borrow a new-value temp + a STREX-status temp clear of {addr base, rd, rv} 2830 * across a balanced push/pop, so the sequence touches no live operand. ip is 2831 * left alone (an operand may live there) and the operand-aware pick avoids the 2832 * base/operand registers the optimizer chose at -O1. */ 2833 base = arm_atomic_addr_reg(a, addr); /* lr (-O0) or the pointer's own reg */ 2834 arm_atomic_borrow(a, (1u << rd) | (1u << rv) | (1u << base), 2u, temps, 2835 &pushmask); 2836 newv = temps[0]; 2837 status = temps[1]; 2838 arm_emit_push(mc, pushmask); 2839 if (arm_order_release(order)) arm_emit_t32(mc, arm_dmb(0xfu)); 2840 /* Retry loop: rd = *base (LDREX); r4 = rd OP val; STREX r5,r4,[base]; retry 2841 * while r5 != 0. */ 2842 mc_label_place(mc, retry); 2843 arm_emit_ldrex(a, sz, rd, base); 2844 switch (op) { 2845 case KIT_CG_ATOMIC_XCHG: 2846 arm_emit_t16(mc, arm_mov_hi(newv, rv)); 2847 break; 2848 case KIT_CG_ATOMIC_ADD: 2849 arm_emit_t32(mc, arm_add_reg(newv, rd, rv)); 2850 break; 2851 case KIT_CG_ATOMIC_SUB: 2852 arm_emit_t32(mc, arm_sub_reg(newv, rd, rv)); 2853 break; 2854 case KIT_CG_ATOMIC_AND: 2855 arm_emit_t32(mc, arm_and_reg(newv, rd, rv)); 2856 break; 2857 case KIT_CG_ATOMIC_OR: 2858 arm_emit_t32(mc, arm_orr_reg(newv, rd, rv)); 2859 break; 2860 case KIT_CG_ATOMIC_XOR: 2861 arm_emit_t32(mc, arm_eor_reg(newv, rd, rv)); 2862 break; 2863 case KIT_CG_ATOMIC_NAND: 2864 arm_emit_t32(mc, arm_and_reg(newv, rd, rv)); 2865 arm_emit_t32(mc, arm_mvn_reg(newv, newv)); 2866 break; 2867 default: 2868 arm_panic(a, "unsupported atomic rmw op"); 2869 } 2870 arm_emit_strex(a, sz, status, newv, base); /* STREX r5, r4, [lr] */ 2871 arm_emit_t32(mc, arm_cmp_imm(status, 0u)); /* CMP r5, #0 */ 2872 arm_emit_t32(mc, arm_b_cond_w(ARM_CC_NE)); /* BNE retry */ 2873 mc_emit_label_ref(mc, retry, R_ARM_THM_JUMP19, 4, 0); 2874 if (arm_order_acquire(order)) arm_emit_t32(mc, arm_dmb(0xfu)); 2875 arm_emit_pop(mc, pushmask); 2876 } 2877 2878 static void arm_atomic_cas(NativeTarget* t, NativeLoc prior, NativeLoc ok, 2879 NativeAddr addr, NativeLoc expected, 2880 NativeLoc desired, MemAccess mem, 2881 KitCgMemOrder success, KitCgMemOrder failure) { 2882 Arm32NativeTarget* a = arm_of(t); 2883 MCEmitter* mc = t->mc; 2884 u32 sz = mem.size ? mem.size : native_type_size(t, prior.type); 2885 u32 rprior = loc_reg(prior); 2886 u32 rexp = loc_reg(expected); 2887 u32 rdes = loc_reg(desired); 2888 u32 rok = loc_reg(ok); 2889 u32 base, status, statusv[1], pushmask; 2890 u32 enc0, enc1; 2891 MCLabel retry = mc_label_new(mc); 2892 MCLabel fail = mc_label_new(mc); 2893 MCLabel done = mc_label_new(mc); 2894 (void)failure; 2895 if (sz > 4u) arm_panic(a, "8-byte atomic cas not lowered (spinlock libcall)"); 2896 thumb_expand_imm_encode(0u, &enc0); 2897 thumb_expand_imm_encode(1u, &enc1); 2898 /* STREX status temp clear of {addr base, prior, expected, desired, ok} — 2899 * never ip (an operand may live there), and operand-aware so the optimizer's 2900 * -O1 register choices for the five live values never alias it. */ 2901 base = arm_atomic_addr_reg(a, addr); /* lr (-O0) or the pointer's own reg */ 2902 arm_atomic_borrow(a, 2903 (1u << rprior) | (1u << rexp) | (1u << rdes) | (1u << rok) | 2904 (1u << base), 2905 1u, statusv, &pushmask); 2906 status = statusv[0]; 2907 arm_emit_push(mc, pushmask); 2908 if (arm_order_release(success)) arm_emit_t32(mc, arm_dmb(0xfu)); 2909 /* retry: prior = *base (LDREX); if prior != expected goto fail; STREX 2910 * r4,desired,[base]; retry on failure; ok = 1; goto done. fail: clear the 2911 * monitor (CLREX) and ok = 0. */ 2912 mc_label_place(mc, retry); 2913 arm_emit_ldrex(a, sz, rprior, base); 2914 arm_emit_t32(mc, arm_cmp_reg(rprior, rexp)); 2915 arm_emit_t32(mc, arm_b_cond_w(ARM_CC_NE)); /* BNE fail */ 2916 mc_emit_label_ref(mc, fail, R_ARM_THM_JUMP19, 4, 0); 2917 arm_emit_strex(a, sz, status, rdes, base); 2918 arm_emit_t32(mc, arm_cmp_imm(status, 0u)); 2919 arm_emit_t32(mc, arm_b_cond_w(ARM_CC_NE)); /* BNE retry */ 2920 mc_emit_label_ref(mc, retry, R_ARM_THM_JUMP19, 4, 0); 2921 arm_emit_t32(mc, arm_mov_imm(rok, enc1)); /* ok = 1 */ 2922 arm_emit_t32(mc, arm_b_w()); /* B done */ 2923 mc_emit_label_ref(mc, done, R_ARM_THM_JUMP24, 4, 0); 2924 mc_label_place(mc, fail); 2925 arm_emit_t32(mc, arm_clrex()); /* drop the exclusive reservation */ 2926 arm_emit_t32(mc, arm_mov_imm(rok, enc0)); /* ok = 0 */ 2927 mc_label_place(mc, done); 2928 if (arm_order_acquire(success)) arm_emit_t32(mc, arm_dmb(0xfu)); 2929 arm_emit_pop(mc, pushmask); 2930 } 2931 2932 static void arm_fence(NativeTarget* t, KitCgMemOrder order) { 2933 if (order == KIT_CG_MO_RELAXED) return; 2934 arm_emit_t32(t->mc, arm_dmb(0xfu)); 2935 } 2936 2937 /* ============================ varargs ============================ */ 2938 /* AAPCS32 va_list is a plain 4-byte pointer to the next argument slot. The 2939 * prologue spilled the unconsumed GP arg registers r0..r3 into a save area just 2940 * above the saved {r7, lr} pair (at [r7 + ARM_SAVED_PAIR_BYTES]); the named 2941 * incoming stack args follow contiguously at [r7 + arg_window_base], so a 2942 * uniform 4-byte stride (8-byte-aligned for i64/double) walks both regions. 2943 * `ap` is a NativeAddr addressing the va_list object itself. */ 2944 2945 static void arm_va_start_core(Arm32NativeTarget* a, NativeAddr ap) { 2946 NativeTarget* t = &a->base; 2947 MCEmitter* mc = t->mc; 2948 KitCgTypeId i32t = builtin_id(KIT_CG_BUILTIN_I32); 2949 NativeLoc ptr = native_loc_reg(i32t, NATIVE_REG_INT, ARM_TMP); 2950 ABIVaListInfo vai = abi_va_list_layout(t->c->abi); 2951 if (vai.kind != ABI_VA_LIST_POINTER) arm_panic(a, "unsupported va_list layout"); 2952 if (!a->is_variadic) arm_panic(a, "va_start: function not variadic"); 2953 /* *ap = r7 + saved_block_bytes + next_param_int*4: the GP save area (r0..r3) 2954 * sits just above the saved register block, so its base is saved_block_bytes 2955 * (8 for the bare {r7,lr} single-pass prologue; larger when the -O1 known-frame 2956 * prologue also PUSHes callee-saves), and next_param_int*4 skips the GP slots 2957 * the fixed params already consumed. lr (ARM_TMP) is the staging temp. */ 2958 arm_emit_t32(mc, arm_add_imm12(ARM_TMP, ARM_FP, 2959 a->saved_block_bytes + a->next_param_int * 4u)); 2960 arm_emit_mem(a, 0, ptr, ap, native_mem_for_type(t, i32t, 4)); 2961 } 2962 2963 /* Whether a va_arg value is too wide to move through one core register (an 2964 * 8-byte i64 / soft-double): it occupies two GP slots and is copied straight 2965 * from the save area into its destination memory. */ 2966 static int arm_va_arg_is_wide(NativeTarget* t, KitCgTypeId type) { 2967 return native_type_size(t, type) > 4u; 2968 } 2969 2970 /* Wide / aggregate va_arg: read the cursor, 8-byte-align it (i64/double slots 2971 * are 8-byte aligned on the stack per AAPCS32), advance past the whole span, 2972 * then byte-copy from the (aligned) cursor into the destination memory. ip holds 2973 * the cursor across the copy; arm_copy_bytes uses lr internally. */ 2974 static void arm_va_arg_wide(Arm32NativeTarget* a, NativeAddr dst, NativeAddr ap, 2975 u32 sz) { 2976 NativeTarget* t = &a->base; 2977 MCEmitter* mc = t->mc; 2978 KitCgTypeId i32t = builtin_id(KIT_CG_BUILTIN_I32); 2979 u32 span = align_up_u32(sz, 4u); 2980 u32 enc7; 2981 NativeLoc cur = native_loc_reg(i32t, NATIVE_REG_INT, ARM_SCRATCH); 2982 NativeLoc nxt = native_loc_reg(i32t, NATIVE_REG_INT, ARM_TMP); 2983 NativeAddr src; 2984 AggregateAccess acc; 2985 /* cur = *ap. */ 2986 arm_emit_mem(a, 1, cur, ap, native_mem_for_type(t, i32t, 4)); 2987 /* 8-byte align the cursor: cur = (cur + 7) & ~7. ~7 (0xFFFFFFF8) is not a 2988 * Thumb modified immediate, so clear the low 3 bits with BIC #7 (op4=1; #7 IS 2989 * encodable). */ 2990 arm_emit_t32(mc, arm_add_imm12(ARM_SCRATCH, ARM_SCRATCH, 7u)); 2991 thumb_expand_imm_encode(7u, &enc7); 2992 arm_emit_t32(mc, arm_dp_imm(1u, 0u, ARM_SCRATCH, ARM_SCRATCH, enc7)); /* BIC */ 2993 /* *ap = cur + span. */ 2994 arm_emit_t32(mc, arm_add_imm12(ARM_TMP, ARM_SCRATCH, span)); 2995 arm_emit_mem(a, 0, nxt, ap, native_mem_for_type(t, i32t, 4)); 2996 /* Copy sz bytes from [cur] to the destination. */ 2997 memset(&src, 0, sizeof src); 2998 src.base_kind = NATIVE_ADDR_BASE_REG; 2999 src.base.reg = ARM_SCRATCH; 3000 src.base_type = i32t; 3001 memset(&acc, 0, sizeof acc); 3002 acc.type = i32t; 3003 acc.size = sz; 3004 acc.align = 4u; 3005 arm_copy_bytes(t, dst, src, acc); 3006 } 3007 3008 static void arm_va_arg_core(Arm32NativeTarget* a, NativeLoc dst, NativeAddr ap, 3009 KitCgTypeId type) { 3010 NativeTarget* t = &a->base; 3011 MCEmitter* mc = t->mc; 3012 KitCgTypeId i32t = builtin_id(KIT_CG_BUILTIN_I32); 3013 u32 sz = native_type_size(t, type); 3014 /* The cursor must survive the value load (dst = [cursor]) and the +4 update, 3015 * so it cannot alias `dst`. O1 destinations come from the scoped r0..r3 bank; 3016 * the O0 direct path may use IP, so choose LR only for that case and IP 3017 * otherwise. */ 3018 u32 cur_reg = (dst.kind == NATIVE_LOC_REG && loc_reg(dst) == ARM_SCRATCH) 3019 ? ARM_TMP 3020 : ARM_SCRATCH; 3021 NativeLoc cur = native_loc_reg(i32t, NATIVE_REG_INT, cur_reg); 3022 NativeAddr from; 3023 ABIVaListInfo vai = abi_va_list_layout(t->c->abi); 3024 if (vai.kind != ABI_VA_LIST_POINTER) arm_panic(a, "unsupported va_list layout"); 3025 if (dst.kind != NATIVE_LOC_REG) arm_panic(a, "va_arg destination must be reg"); 3026 /* cur = *ap; load value from [cur]; *ap = cur + 4 (one GP-slot stride). */ 3027 arm_emit_mem(a, 1, cur, ap, native_mem_for_type(t, i32t, 4)); 3028 memset(&from, 0, sizeof from); 3029 from.base_kind = NATIVE_ADDR_BASE_REG; 3030 from.base.reg = cur_reg; 3031 from.base_type = type; 3032 arm_emit_mem(a, 1, dst, from, native_mem_for_type(t, type, sz)); 3033 arm_emit_t32(mc, arm_add_imm12(cur_reg, cur_reg, 4u)); 3034 arm_emit_mem(a, 0, cur, ap, native_mem_for_type(t, i32t, 4)); 3035 } 3036 3037 static void arm_va_copy_core(Arm32NativeTarget* a, NativeAddr dst_ap, 3038 NativeAddr src_ap) { 3039 NativeTarget* t = &a->base; 3040 KitCgTypeId i32t = builtin_id(KIT_CG_BUILTIN_I32); 3041 NativeLoc tmp = native_loc_reg(i32t, NATIVE_REG_INT, ARM_SCRATCH); 3042 arm_emit_mem(a, 1, tmp, src_ap, native_mem_for_type(t, i32t, 4)); 3043 arm_emit_mem(a, 0, tmp, dst_ap, native_mem_for_type(t, i32t, 4)); 3044 } 3045 3046 /* ---- NativeTarget (optimizer) hooks: ap is a materialized register ---- */ 3047 static NativeAddr arm_va_addr_from_ptr(NativeLoc ap_ptr) { 3048 NativeAddr addr; 3049 memset(&addr, 0, sizeof addr); 3050 addr.base_kind = NATIVE_ADDR_BASE_REG; 3051 addr.cls = NATIVE_REG_INT; 3052 addr.base.reg = ap_ptr.v.reg; 3053 addr.base_type = ap_ptr.type; 3054 return addr; 3055 } 3056 /* The destination memory of a value-location: a wide (i64/double) va_arg result 3057 * is memory-resident, so the optimizer hands its storage (a frame/stack slot, or 3058 * an explicit address), NOT a pointer in a register. Convert it to a NativeAddr 3059 * the byte-copy can write through. (Mirrors RISC-V's storage-address 3060 * resolver.) */ 3061 static NativeAddr arm_loc_addr(Arm32NativeTarget* a, NativeLoc loc) { 3062 NativeAddr addr; 3063 memset(&addr, 0, sizeof addr); 3064 switch ((NativeLocKind)loc.kind) { 3065 case NATIVE_LOC_FRAME: 3066 addr.base_kind = NATIVE_ADDR_BASE_FRAME; 3067 addr.base.frame = loc.v.frame; 3068 addr.base_type = loc.type; 3069 return addr; 3070 case NATIVE_LOC_STACK: 3071 addr.base_kind = NATIVE_ADDR_BASE_FRAME; 3072 addr.base.frame = loc.v.stack.slot; 3073 addr.base_type = loc.type; 3074 addr.offset = loc.v.stack.offset; 3075 return addr; 3076 case NATIVE_LOC_ADDR: 3077 return loc.v.addr; 3078 default: 3079 arm_panic(a, "va_arg destination is not addressable"); 3080 } 3081 } 3082 static void arm_va_start_native(NativeTarget* t, NativeLoc ap_ptr) { 3083 arm_va_start_core(arm_of(t), arm_va_addr_from_ptr(ap_ptr)); 3084 } 3085 static void arm_va_arg_native(NativeTarget* t, NativeLoc dst, NativeLoc ap_ptr, 3086 KitCgTypeId type) { 3087 Arm32NativeTarget* a = arm_of(t); 3088 if (arm_va_arg_is_wide(t, type)) { 3089 arm_va_arg_wide(a, arm_loc_addr(a, dst), arm_va_addr_from_ptr(ap_ptr), 3090 native_type_size(t, type)); 3091 return; 3092 } 3093 arm_va_arg_core(a, dst, arm_va_addr_from_ptr(ap_ptr), type); 3094 } 3095 static void arm_va_end_native(NativeTarget* t, NativeLoc ap_ptr) { 3096 (void)t; 3097 (void)ap_ptr; 3098 } 3099 static void arm_va_copy_native(NativeTarget* t, NativeLoc dst_ap_ptr, 3100 NativeLoc src_ap_ptr) { 3101 arm_va_copy_core(arm_of(t), arm_va_addr_from_ptr(dst_ap_ptr), 3102 arm_va_addr_from_ptr(src_ap_ptr)); 3103 } 3104 3105 /* Materialize the boolean of ARM condition `cc` into rd: MOV rd,#0; IT cc; 3106 * MOV<cc> rd,#1. The flag state must already be set by a preceding CMP; the 3107 * leading MOV.W (no S) does not disturb it. */ 3108 static void arm_emit_setcc(MCEmitter* mc, u32 rd, u32 cc) { 3109 u32 enc0, enc1; 3110 thumb_expand_imm_encode(0u, &enc0); 3111 thumb_expand_imm_encode(1u, &enc1); 3112 arm_emit_t32(mc, arm_mov_imm(rd, enc0)); 3113 arm_emit_t16(mc, (u16)(0xbf08u | (cc << 4))); /* IT cc (mask 0b1000) */ 3114 arm_emit_t32(mc, arm_mov_imm(rd, enc1)); 3115 } 3116 3117 /* The 32-bit `__builtin_*_overflow` family. dsts = [value, overflow]; args = 3118 * [a, b]. i64 overflow is lowered to lane ops by the cg layer (arith.c), so the 3119 * backend only sees the 32-bit forms. LR/IP are backend-private temporaries on 3120 * O1; on O0, IP's operand value has already been consumed before these internal 3121 * sequences use it. */ 3122 /* nd_intrinsic forwards an immediate argument as NATIVE_LOC_IMM (not in a 3123 * register). A value-consuming intrinsic needs it in a register: materialize it 3124 * into `into` (an output register, free to reuse as an input) and return the reg 3125 * loc; pass a register arg through unchanged. Mirrors riscv's intrinsic path. */ 3126 static NativeLoc arm_intrin_arg(Arm32NativeTarget* a, NativeLoc arg, u32 into) { 3127 NativeLoc r; 3128 if (arg.kind != NATIVE_LOC_IMM) return arg; 3129 r = native_loc_reg(arg.type ? arg.type : builtin_id(KIT_CG_BUILTIN_I32), 3130 NATIVE_REG_INT, into); 3131 arm_load_imm(&a->base, r, arg.v.imm); 3132 return r; 3133 } 3134 3135 static void arm_overflow(Arm32NativeTarget* a, IntrinKind kind, 3136 const NativeLoc* dsts, const NativeLoc* args) { 3137 MCEmitter* mc = a->base.mc; 3138 u32 ra = loc_reg(args[0]), rb = loc_reg(args[1]); 3139 u32 rd = loc_reg(dsts[0]), rovf = loc_reg(dsts[1]); 3140 u32 enc0; 3141 switch (kind) { 3142 case INTRIN_SADD_OVERFLOW: 3143 case INTRIN_SSUB_OVERFLOW: 3144 /* r = a +/- b; signed ovf is the sign bit of ((a^r)&(b^r)) [ADD] or 3145 * ((a^b)&(a^r)) [SUB]. */ 3146 if (kind == INTRIN_SADD_OVERFLOW) 3147 arm_emit_t32(mc, arm_add_reg(ARM_TMP, ra, rb)); 3148 else 3149 arm_emit_t32(mc, arm_sub_reg(ARM_TMP, ra, rb)); 3150 arm_emit_t32(mc, arm_eor_reg(ARM_SCRATCH, ra, ARM_TMP)); /* a ^ r */ 3151 if (kind == INTRIN_SADD_OVERFLOW) 3152 arm_emit_t32(mc, arm_eor_reg(rovf, rb, ARM_TMP)); /* b ^ r */ 3153 else 3154 arm_emit_t32(mc, arm_eor_reg(rovf, ra, rb)); /* a ^ b */ 3155 arm_emit_t32(mc, arm_and_reg(rovf, rovf, ARM_SCRATCH)); 3156 arm_emit_t32(mc, arm_shift_imm(1u, rovf, rovf, 31u)); /* LSR #31 -> 0/1 */ 3157 arm_emit_t16(mc, arm_mov_hi(rd, ARM_TMP)); 3158 return; 3159 case INTRIN_UADD_OVERFLOW: 3160 arm_emit_t32(mc, arm_add_reg(ARM_TMP, ra, rb)); /* r = a + b */ 3161 arm_emit_t32(mc, arm_cmp_reg(ARM_TMP, ra)); /* CMP r, a */ 3162 arm_emit_setcc(mc, rovf, ARM_CC_CC); /* ovf = (r < a) unsigned */ 3163 arm_emit_t16(mc, arm_mov_hi(rd, ARM_TMP)); 3164 return; 3165 case INTRIN_USUB_OVERFLOW: 3166 arm_emit_t32(mc, arm_cmp_reg(ra, rb)); /* CMP a, b */ 3167 arm_emit_t32(mc, arm_sub_reg(ARM_TMP, ra, rb)); /* r = a - b (no flags) */ 3168 arm_emit_setcc(mc, rovf, ARM_CC_CC); /* ovf = (a < b) unsigned */ 3169 arm_emit_t16(mc, arm_mov_hi(rd, ARM_TMP)); 3170 return; 3171 case INTRIN_UMUL_OVERFLOW: 3172 /* UMULL lr:ip = a*b; ovf = (high != 0); value = low. */ 3173 arm_emit_t32(mc, arm_umull(ARM_TMP, ARM_SCRATCH, ra, rb)); 3174 thumb_expand_imm_encode(0u, &enc0); 3175 arm_emit_t32(mc, arm_cmp_imm(ARM_SCRATCH, enc0)); 3176 arm_emit_setcc(mc, rovf, ARM_CC_NE); 3177 arm_emit_t16(mc, arm_mov_hi(rd, ARM_TMP)); 3178 return; 3179 case INTRIN_SMUL_OVERFLOW: 3180 /* SMULL lr:ip = a*b; ovf = (high != (low >>s 31)); value = low. */ 3181 arm_emit_t32(mc, arm_smull(ARM_TMP, ARM_SCRATCH, ra, rb)); 3182 arm_emit_t32(mc, arm_shift_imm(2u, rovf, ARM_TMP, 31u)); /* rovf = low ASR 31 */ 3183 arm_emit_t32(mc, arm_cmp_reg(ARM_SCRATCH, rovf)); 3184 arm_emit_setcc(mc, rovf, ARM_CC_NE); 3185 arm_emit_t16(mc, arm_mov_hi(rd, ARM_TMP)); 3186 return; 3187 default: 3188 arm_panic(a, "arm_overflow: not an overflow intrinsic"); 3189 } 3190 } 3191 3192 static void arm_intrinsic(NativeTarget* t, IntrinKind kind, 3193 const NativeLoc* dsts, u32 ndst, const NativeLoc* args, 3194 u32 narg) { 3195 Arm32NativeTarget* a = arm_of(t); 3196 MCEmitter* mc = t->mc; 3197 switch (kind) { 3198 case INTRIN_TRAP: 3199 arm_trap(t); 3200 return; 3201 case INTRIN_MEMMOVE: { 3202 /* Overlap-safe move: copy forward when dst < src (unsigned), else backward, 3203 * so each granule's load precedes any store that could clobber it. dst/src 3204 * are pointer registers, size a constant; the transfer reg aliases neither 3205 * base. Shares the granule ladder with copy_bytes. */ 3206 MCLabel forward = arm_label_new(t), done = arm_label_new(t); 3207 u32 dst, src, size, xfer; 3208 if (narg != 3u || args[0].kind != NATIVE_LOC_REG || 3209 args[1].kind != NATIVE_LOC_REG || args[2].kind != NATIVE_LOC_IMM) 3210 arm_panic(a, "memmove: expected (reg dst, reg src, imm size)"); 3211 if (args[2].v.imm < 0 || args[2].v.imm > 4096) 3212 arm_panic(a, "memmove: size out of range (needs a runtime loop)"); 3213 dst = loc_reg(args[0]); 3214 src = loc_reg(args[1]); 3215 size = (u32)args[2].v.imm; 3216 xfer = (dst != ARM_TMP && src != ARM_TMP) ? ARM_TMP : ARM_SCRATCH; 3217 if (xfer == dst || xfer == src) 3218 arm_panic(a, "memmove: no free transfer register"); 3219 arm_cmp_branch(t, CMP_LT_U, args[0], args[1], forward); 3220 arm_copy_granules_dir(a, dst, src, size, xfer, /*backward=*/1); 3221 arm_jump(t, done); 3222 arm_label_place(t, forward); 3223 arm_copy_granules_dir(a, dst, src, size, xfer, /*backward=*/0); 3224 arm_label_place(t, done); 3225 return; 3226 } 3227 case INTRIN_CLZ: 3228 if (ndst == 1u && narg == 1u) { 3229 NativeLoc a0 = arm_intrin_arg(a, args[0], loc_reg(dsts[0])); 3230 arm_emit_t32(mc, arm_clz(loc_reg(dsts[0]), loc_reg(a0))); 3231 return; 3232 } 3233 break; 3234 case INTRIN_CTZ: 3235 /* RBIT reverses the bit order, so the trailing-zero count becomes a 3236 * leading-zero count of the reversed word. */ 3237 if (ndst == 1u && narg == 1u) { 3238 NativeLoc a0 = arm_intrin_arg(a, args[0], loc_reg(dsts[0])); 3239 arm_emit_t32(mc, arm_rbit(loc_reg(dsts[0]), loc_reg(a0))); 3240 arm_emit_t32(mc, arm_clz(loc_reg(dsts[0]), loc_reg(dsts[0]))); 3241 return; 3242 } 3243 break; 3244 case INTRIN_BSWAP: 3245 if (ndst == 1u && narg == 1u) { 3246 u32 width = native_type_size(t, dsts[0].type); 3247 NativeLoc a0 = arm_intrin_arg(a, args[0], loc_reg(dsts[0])); 3248 /* Use the 32-bit REV.W/REV16.W forms: the dst may be a high register 3249 * (IP at -O0 or r4..r11 when allocated at -O1) that the 16-bit T1 3250 * encoding cannot represent. */ 3251 if (width == 4u) { 3252 arm_emit_t32(mc, arm_rev_w(loc_reg(dsts[0]), loc_reg(a0))); 3253 return; 3254 } 3255 if (width == 2u) { 3256 /* REV16 byte-swaps within each halfword: the low halfword of the 3257 * input becomes the swapped 16-bit result in the low halfword. */ 3258 arm_emit_t32(mc, arm_rev16_w(loc_reg(dsts[0]), loc_reg(a0))); 3259 return; 3260 } 3261 } 3262 break; 3263 case INTRIN_POPCOUNT: 3264 /* 32-bit SWAR popcount (no hardware popcount on ARMv7-M). All masks are 3265 * ThumbExpandImm-encodable, so no MOVW/MOVT is needed. lr/ip are the 3266 * scratch. 64-bit popcount stays in the cg lane path / a follow-on. */ 3267 if (ndst == 1u && narg == 1u && native_type_size(t, dsts[0].type) <= 4u) { 3268 u32 rd = loc_reg(dsts[0]); 3269 u32 rs = loc_reg(arm_intrin_arg(a, args[0], rd)); 3270 u32 e55, e33, e0f, e01; 3271 thumb_expand_imm_encode(0x55555555u, &e55); 3272 thumb_expand_imm_encode(0x33333333u, &e33); 3273 thumb_expand_imm_encode(0x0f0f0f0fu, &e0f); 3274 thumb_expand_imm_encode(0x01010101u, &e01); 3275 arm_emit_t32(mc, arm_shift_imm(1u, ARM_SCRATCH, rs, 1u)); /* ip = x>>1 */ 3276 arm_emit_t32(mc, arm_dp_imm(0u, 0u, ARM_SCRATCH, ARM_SCRATCH, e55)); /* ip &= 0x55.. */ 3277 arm_emit_t32(mc, arm_sub_reg(ARM_TMP, rs, ARM_SCRATCH)); /* lr = x - ip */ 3278 arm_emit_t32(mc, arm_dp_imm(0u, 0u, ARM_SCRATCH, ARM_TMP, e33)); /* ip = lr & 0x33.. */ 3279 arm_emit_t32(mc, arm_shift_imm(1u, ARM_TMP, ARM_TMP, 2u)); /* lr >>= 2 */ 3280 arm_emit_t32(mc, arm_dp_imm(0u, 0u, ARM_TMP, ARM_TMP, e33)); /* lr &= 0x33.. */ 3281 arm_emit_t32(mc, arm_add_reg(ARM_TMP, ARM_TMP, ARM_SCRATCH)); /* lr += ip */ 3282 arm_emit_t32(mc, arm_shift_imm(1u, ARM_SCRATCH, ARM_TMP, 4u)); /* ip = lr>>4 */ 3283 arm_emit_t32(mc, arm_add_reg(ARM_TMP, ARM_TMP, ARM_SCRATCH)); /* lr += ip */ 3284 arm_emit_t32(mc, arm_dp_imm(0u, 0u, ARM_TMP, ARM_TMP, e0f)); /* lr &= 0x0f.. */ 3285 arm_emit_t32(mc, arm_mov_imm(ARM_SCRATCH, e01)); /* ip = 0x01010101 */ 3286 arm_emit_t32(mc, arm_mul(ARM_TMP, ARM_TMP, ARM_SCRATCH)); /* lr *= ip */ 3287 arm_emit_t32(mc, arm_shift_imm(1u, rd, ARM_TMP, 24u)); /* rd = lr>>24 */ 3288 return; 3289 } 3290 break; 3291 case INTRIN_EXPECT: 3292 case INTRIN_ASSUME_ALIGNED: 3293 /* Branch/alignment hints: the result is the first argument, unchanged. An 3294 * immediate value materializes straight into dst; an 8-byte value carries 3295 * two lanes, so move each (a single arm_move would drop the high lane). */ 3296 if (ndst == 1u && narg >= 1u) { 3297 if (args[0].kind == NATIVE_LOC_IMM) { 3298 arm_load_imm(t, dsts[0], args[0].v.imm); 3299 } else if (native_type_size(t, dsts[0].type) > 4u && 3300 dsts[0].kind == NATIVE_LOC_REG && 3301 args[0].kind == NATIVE_LOC_REG) { 3302 u32 dlo = loc_reg(dsts[0]), slo = loc_reg(args[0]); 3303 if (dlo != slo) arm_emit_t16(mc, arm_mov_hi(dlo, slo)); 3304 arm_emit_t16(mc, arm_mov_hi((dlo + 1u) & 0xfu, (slo + 1u) & 0xfu)); 3305 } else { 3306 arm_move(t, dsts[0], args[0]); 3307 } 3308 return; 3309 } 3310 break; 3311 case INTRIN_PREFETCH: 3312 return; /* no-op: M-profile has no PLD */ 3313 case INTRIN_CPU_NOP: 3314 arm_emit_t16(mc, arm_nop16()); 3315 return; 3316 case INTRIN_CPU_YIELD: 3317 arm_emit_t16(mc, arm_yield16()); 3318 return; 3319 case INTRIN_WFI: 3320 arm_emit_t16(mc, arm_wfi16()); 3321 return; 3322 case INTRIN_WFE: 3323 arm_emit_t16(mc, arm_wfe16()); 3324 return; 3325 case INTRIN_SEV: 3326 arm_emit_t16(mc, arm_sev16()); 3327 return; 3328 case INTRIN_DMB: 3329 arm_emit_t32(mc, arm_dmb(ARM_BARRIER_SY)); 3330 return; 3331 case INTRIN_DSB: 3332 arm_emit_t32(mc, arm_dsb(ARM_BARRIER_SY)); 3333 return; 3334 case INTRIN_ISB: 3335 arm_emit_t32(mc, arm_isb(ARM_BARRIER_SY)); 3336 return; 3337 case INTRIN_SADD_OVERFLOW: 3338 case INTRIN_SSUB_OVERFLOW: 3339 case INTRIN_UADD_OVERFLOW: 3340 case INTRIN_USUB_OVERFLOW: 3341 case INTRIN_SMUL_OVERFLOW: 3342 case INTRIN_UMUL_OVERFLOW: 3343 if (ndst == 2u && narg == 2u) { 3344 /* Materialize immediate operands into the output registers (free to use 3345 * as inputs: arm_overflow reads both operands before writing dsts). */ 3346 NativeLoc av[2]; 3347 av[0] = arm_intrin_arg(a, args[0], loc_reg(dsts[0])); 3348 av[1] = arm_intrin_arg(a, args[1], loc_reg(dsts[1])); 3349 arm_overflow(a, kind, dsts, av); 3350 return; 3351 } 3352 break; 3353 case INTRIN_SMUL_HIGH: 3354 case INTRIN_UMUL_HIGH: 3355 if (ndst == 1u && narg == 2u) { 3356 u32 rd = loc_reg(dsts[0]); 3357 u32 ra = loc_reg(args[0]); 3358 u32 rb = loc_reg(args[1]); 3359 arm_emit_t32(mc, kind == INTRIN_SMUL_HIGH 3360 ? arm_smull(ARM_TMP, ARM_SCRATCH, ra, rb) 3361 : arm_umull(ARM_TMP, ARM_SCRATCH, ra, rb)); 3362 arm_emit_t16(mc, arm_mov_hi(rd, ARM_SCRATCH)); 3363 return; 3364 } 3365 break; 3366 case INTRIN_FRAME_ADDRESS: 3367 case INTRIN_RETURN_ADDRESS: 3368 /* kit's prologue anchors r7 at the saved pair: [r7]=caller r7, 3369 * [r7+4]=this frame's saved lr (return address). The level is constant 3370 * (unrolls to `level` dependent loads up the fp chain). A frame-reading 3371 * function is kept off the frameless-leaf tier (reads_frame), so r7 is 3372 * always valid here. */ 3373 if (ndst == 1u) { 3374 u32 level = (narg >= 1u && args[0].kind == NATIVE_LOC_IMM) 3375 ? (u32)args[0].v.imm 3376 : 0u; 3377 u32 rd = loc_reg(dsts[0]), i; 3378 arm_emit_t16(mc, arm_mov_hi(rd, ARM_FP)); /* rd = r7 */ 3379 for (i = 0; i < level; ++i) 3380 arm_emit_t32(mc, arm_ldr_imm(rd, rd, 0)); /* rd = *(rd) */ 3381 if (kind == INTRIN_RETURN_ADDRESS) 3382 arm_emit_t32(mc, arm_ldr_imm(rd, rd, 4u)); /* rd = *(rd+4) = saved lr */ 3383 return; 3384 } 3385 break; 3386 default: 3387 break; 3388 } 3389 arm_panic(a, "intrinsic not lowered (FMA/syscall/setjmp/etc. are follow-ons)"); 3390 } 3391 3392 /* ============================ inline asm ============================ */ 3393 3394 _Noreturn static void arm_asm_panic_at(Compiler* c, SrcLoc loc, 3395 const char* msg) { 3396 compiler_panic(c, loc, "arm32 inline asm: %s", msg); 3397 } 3398 _Noreturn static void arm_asm_panic(NativeDirectTarget* d, const char* msg) { 3399 arm_asm_panic_at(d->base.c, d->loc, msg); 3400 } 3401 3402 static void arm_asm_bound_reg(Operand* out, KitCgTypeId type, 3403 NativeAllocClass cls, Reg reg) { 3404 memset(out, 0, sizeof *out); 3405 out->kind = ARM32_INLINE_OPK_REG; 3406 out->pad[0] = 3407 (cls == NATIVE_REG_FP) ? ARM32_INLINE_OPCLS_FP : ARM32_INLINE_OPCLS_INT; 3408 out->type = type; 3409 out->v.local = (CGLocal)reg; 3410 } 3411 static void arm_asm_bound_mem(Operand* out, KitCgTypeId type, Reg base) { 3412 memset(out, 0, sizeof *out); 3413 out->kind = OPK_INDIRECT; 3414 out->type = type; 3415 out->v.ind.base = (CGLocal)base; 3416 out->v.ind.index = CG_LOCAL_NONE; 3417 } 3418 3419 /* Parse one clobber name into a core-register mask bit. r7 is the live frame 3420 * anchor and sp/pc are structural state, so an inline block may not declare 3421 * them clobbered; cc/memory contribute no register. */ 3422 static void arm_asm_clobber_masks(Compiler* c, SrcLoc loc, const Sym* clobbers, 3423 u32 nclob, u32* int_mask, u32* fp_mask) { 3424 *int_mask = 0; 3425 *fp_mask = 0; 3426 for (u32 i = 0; i < nclob; ++i) { 3427 Slice s = pool_slice(c->global, clobbers[i]); 3428 char buf[16]; 3429 uint32_t dwarf; 3430 if (!s.s || !s.len || s.len >= sizeof buf) continue; 3431 memcpy(buf, s.s, s.len); 3432 buf[s.len] = '\0'; 3433 if (!strcmp(buf, "cc") || !strcmp(buf, "memory")) continue; 3434 if (arm32_register_index(buf, &dwarf) != 0 || dwarf > 15u) 3435 arm_asm_panic_at(c, loc, "unsupported clobber register"); 3436 if (dwarf == ARM_FP || dwarf == 13u || dwarf == 15u) 3437 arm_asm_panic_at(c, loc, "structural register cannot be clobbered"); 3438 if (dwarf <= 15u) 3439 *int_mask |= 1u << dwarf; 3440 } 3441 } 3442 3443 /* Operand-address resolution for the direct (-O0) path (mirrors aa64). */ 3444 static NativeAddr arm_direct_addr(NativeDirectTarget* d, Operand op) { 3445 NativeAddr addr; 3446 memset(&addr, 0, sizeof addr); 3447 switch ((OpKind)op.kind) { 3448 case OPK_LOCAL: 3449 addr.base_kind = NATIVE_ADDR_BASE_FRAME; 3450 addr.base.frame = d->locals[op.v.local - 1u].home; 3451 addr.base_type = op.type; 3452 return addr; 3453 case OPK_INDIRECT: 3454 addr.base_kind = NATIVE_ADDR_BASE_FRAME_VALUE; 3455 addr.base.frame = d->locals[op.v.ind.base - 1u].home; 3456 addr.cls = d->locals[op.v.ind.base - 1u].cls; 3457 addr.base_type = d->locals[op.v.ind.base - 1u].type; 3458 addr.offset = op.v.ind.ofs; 3459 return addr; 3460 default: 3461 arm_asm_panic(d, "operand is not addressable"); 3462 } 3463 } 3464 static NativeAddr arm_direct_materialize_addr(NativeDirectTarget* d, 3465 Operand op) { 3466 NativeAddr addr = arm_direct_addr(d, op); 3467 if (addr.base_kind == NATIVE_ADDR_BASE_FRAME_VALUE) { 3468 NativeLoc base = native_loc_reg(addr.base_type, NATIVE_REG_INT, ARM_SCRATCH); 3469 NativeAddr load; 3470 memset(&load, 0, sizeof load); 3471 load.base_kind = NATIVE_ADDR_BASE_FRAME; 3472 load.base.frame = addr.base.frame; 3473 load.base_type = addr.base_type; 3474 arm_emit_mem(arm_of(d->native), 1, base, load, 3475 native_mem_for_type(d->native, addr.base_type, 4)); 3476 addr.base_kind = NATIVE_ADDR_BASE_REG; 3477 addr.base.reg = ARM_SCRATCH; 3478 } 3479 return addr; 3480 } 3481 3482 static void arm_direct_load_operand_to_reg(NativeDirectTarget* d, Operand op, 3483 NativeLoc dst) { 3484 NativeAddr addr; 3485 memset(&addr, 0, sizeof addr); 3486 switch ((OpKind)op.kind) { 3487 case OPK_IMM: 3488 d->native->load_imm(d->native, dst, op.v.imm); 3489 return; 3490 case OPK_LOCAL: 3491 addr.base_kind = NATIVE_ADDR_BASE_FRAME; 3492 addr.base.frame = d->locals[op.v.local - 1u].home; 3493 addr.base_type = op.type; 3494 arm_emit_mem(arm_of(d->native), 1, dst, addr, 3495 native_mem_for_type(d->native, op.type, 0)); 3496 return; 3497 case OPK_GLOBAL: 3498 addr.base_kind = NATIVE_ADDR_BASE_GLOBAL; 3499 addr.base.global.sym = op.v.global.sym; 3500 addr.base.global.addend = op.v.global.addend; 3501 addr.base_type = op.type; 3502 d->native->load_addr(d->native, dst, addr); 3503 return; 3504 case OPK_INDIRECT: 3505 addr = arm_direct_materialize_addr(d, op); 3506 arm_emit_mem(arm_of(d->native), 1, dst, addr, 3507 native_mem_for_type(d->native, op.type, 0)); 3508 return; 3509 } 3510 arm_asm_panic(d, "unsupported asm input operand"); 3511 } 3512 static void arm_direct_load_address_to_reg(NativeDirectTarget* d, Operand op, 3513 NativeLoc dst) { 3514 /* For an "m" memory operand: a LOCAL's address is its frame home; an INDIRECT 3515 * (*(base+ofs)) names an address held in a frame slot; load_addr materializes 3516 * that typed FRAME_VALUE through backend-owned temporaries. */ 3517 d->native->load_addr(d->native, dst, arm_direct_materialize_addr(d, op)); 3518 } 3519 static void arm_direct_store_reg_to_operand(NativeDirectTarget* d, Operand op, 3520 NativeLoc src) { 3521 NativeAddr addr; 3522 memset(&addr, 0, sizeof addr); 3523 if (op.kind == OPK_LOCAL) { 3524 addr.base_kind = NATIVE_ADDR_BASE_FRAME; 3525 addr.base.frame = d->locals[op.v.local - 1u].home; 3526 addr.base_type = op.type; 3527 } else { 3528 addr = arm_direct_materialize_addr(d, op); 3529 } 3530 arm_emit_mem(arm_of(d->native), 0, src, addr, 3531 native_mem_for_type(d->native, op.type, 0)); 3532 } 3533 3534 /* Callee-saved spill/restore for asm clobbers (r4..r11, excluding r7=fp). */ 3535 typedef struct ArmAsmSavedClobber { 3536 NativeFrameSlot slot; 3537 Reg reg; 3538 KitCgTypeId type; 3539 } ArmAsmSavedClobber; 3540 3541 static ArmAsmSavedClobber* arm_asm_save_callee_clobbers(Arm32NativeTarget* a, 3542 u32 int_mask, 3543 u32 fp_mask, 3544 u32* nsaved_out) { 3545 ArmAsmSavedClobber* saved = 3546 arena_zarray(a->base.c->tu, ArmAsmSavedClobber, 12u); 3547 KitCgTypeId i32t = builtin_id(KIT_CG_BUILTIN_I32); 3548 u32 n = 0; 3549 (void)fp_mask; 3550 for (Reg r = 4u; r <= 11u; ++r) { 3551 NativeFrameSlotDesc sd; 3552 NativeAddr addr; 3553 if (r == ARM_FP || (int_mask & (1u << r)) == 0) continue; 3554 memset(&sd, 0, sizeof sd); 3555 sd.type = i32t; 3556 sd.size = 4; 3557 sd.align = 4; 3558 sd.kind = NATIVE_FRAME_SLOT_SAVE; 3559 saved[n].slot = a->base.frame_slot(&a->base, &sd); 3560 saved[n].reg = r; 3561 saved[n].type = i32t; 3562 memset(&addr, 0, sizeof addr); 3563 addr.base_kind = NATIVE_ADDR_BASE_FRAME; 3564 addr.base.frame = saved[n].slot; 3565 addr.base_type = i32t; 3566 arm_emit_mem(a, 0, native_loc_reg(i32t, NATIVE_REG_INT, r), addr, 3567 native_mem_for_type(&a->base, i32t, 4)); 3568 n++; 3569 } 3570 *nsaved_out = n; 3571 return saved; 3572 } 3573 static void arm_asm_restore_one_rec(Arm32NativeTarget* a, 3574 const ArmAsmSavedClobber* s) { 3575 NativeAddr addr; 3576 memset(&addr, 0, sizeof addr); 3577 addr.base_kind = NATIVE_ADDR_BASE_FRAME; 3578 addr.base.frame = s->slot; 3579 addr.base_type = s->type; 3580 arm_emit_mem(a, 1, native_loc_reg(s->type, NATIVE_REG_INT, s->reg), addr, 3581 native_mem_for_type(&a->base, s->type, 4)); 3582 } 3583 static void* arm_asm_hook_save_callee_clobbers(NativeDirectTarget* d, 3584 u32 int_mask, u32 fp_mask, 3585 u32* nsaved_out) { 3586 return arm_asm_save_callee_clobbers(arm_of(d->native), int_mask, fp_mask, 3587 nsaved_out); 3588 } 3589 static void arm_asm_hook_restore_one(NativeDirectTarget* d, void* saved, 3590 u32 idx) { 3591 arm_asm_restore_one_rec(arm_of(d->native), 3592 &((ArmAsmSavedClobber*)saved)[idx]); 3593 } 3594 3595 static void arm_asm_hook_run_template(NativeDirectTarget* d, const char* tmpl, 3596 const AsmConstraint* outs, u32 nout, 3597 Operand* bound_outs, 3598 const AsmConstraint* ins, u32 nin, 3599 Operand* bound_ins, const Sym* clobbers, 3600 u32 nclob) { 3601 Arm32Asm* asmh = arm32_asm_open(d->base.c); 3602 arm32_inline_bind(asmh, outs, nout, bound_outs, ins, nin, bound_ins, clobbers, 3603 nclob); 3604 arm32_asm_run_template(asmh, d->native->mc, tmpl); 3605 arm32_asm_close(asmh); 3606 } 3607 3608 static void arm_direct_asm_block(NativeDirectTarget* d, const char* tmpl, 3609 const AsmConstraint* outs, u32 nout, 3610 Operand* out_ops, const AsmConstraint* ins, 3611 u32 nin, const Operand* in_ops, 3612 const Sym* clobbers, u32 nclob, 3613 u32 clobber_abi_sets) { 3614 static const NativeAsmDirectHooks hooks = { 3615 .opk_reg = ARM32_INLINE_OPK_REG, 3616 .opcls_fp = ARM32_INLINE_OPCLS_FP, 3617 .panic = arm_asm_panic, 3618 .bound_reg = arm_asm_bound_reg, 3619 .bound_mem = arm_asm_bound_mem, 3620 .clobber_masks = arm_asm_clobber_masks, 3621 .save_callee_clobbers = arm_asm_hook_save_callee_clobbers, 3622 .restore_one = arm_asm_hook_restore_one, 3623 .load_operand_to_reg = arm_direct_load_operand_to_reg, 3624 .load_address_to_reg = arm_direct_load_address_to_reg, 3625 .store_reg_to_operand = arm_direct_store_reg_to_operand, 3626 .run_template = arm_asm_hook_run_template, 3627 }; 3628 native_asm_bind_direct_operands(d, tmpl, outs, nout, out_ops, ins, nin, 3629 in_ops, clobbers, nclob, clobber_abi_sets, 3630 &hooks); 3631 } 3632 3633 /* Convert a concrete memory-constraint location to a NativeAddr. */ 3634 static NativeAddr arm_asm_in_loc_addr(NativeTarget* t, NativeLoc loc, 3635 KitCgTypeId type) { 3636 NativeAddr ma; 3637 memset(&ma, 0, sizeof ma); 3638 if (loc.kind == NATIVE_LOC_ADDR) { 3639 ma = loc.v.addr; 3640 } else if (loc.kind == NATIVE_LOC_FRAME || loc.kind == NATIVE_LOC_STACK) { 3641 ma.base_kind = NATIVE_ADDR_BASE_FRAME; 3642 ma.base.frame = 3643 loc.kind == NATIVE_LOC_FRAME ? loc.v.frame : loc.v.stack.slot; 3644 ma.offset = loc.kind == NATIVE_LOC_STACK ? loc.v.stack.offset : 0; 3645 ma.base_type = type; 3646 } else { 3647 arm_asm_panic_at(t->c, arm_of(t)->loc, "unsupported memory asm input"); 3648 } 3649 return ma; 3650 } 3651 3652 /* Reserve the next backend-private memory-base register: IP first, then LR. 3653 * The optimized emitter never presents IP/LR as asm operands, so both are free 3654 * here. */ 3655 static Reg arm_asm_mem_base_reg(NativeTarget* t, u32* nbase) { 3656 Reg r; 3657 if (*nbase == 0u) 3658 r = (Reg)ARM_SCRATCH; 3659 else if (*nbase == 1u) 3660 r = (Reg)ARM_TMP; 3661 else 3662 arm_asm_panic_at(t->c, arm_of(t)->loc, 3663 "too many memory inline-asm operands"); 3664 (*nbase)++; 3665 return r; 3666 } 3667 3668 static void arm_asm_native_panic(NativeTarget* t, SrcLoc loc, 3669 const char* msg) { 3670 arm_asm_panic_at(t->c, loc, msg); 3671 } 3672 3673 static Reg arm_asm_native_mem_base_hook(NativeTarget* t, SrcLoc loc, 3674 NativeLoc src, u32* ntmp) { 3675 Reg base = arm_asm_mem_base_reg(t, ntmp); 3676 NativeAddr ma = arm_asm_in_loc_addr(t, src, src.type); 3677 (void)loc; 3678 arm_load_addr( 3679 t, native_loc_reg(builtin_id(KIT_CG_BUILTIN_I32), NATIVE_REG_INT, base), 3680 ma); 3681 return base; 3682 } 3683 3684 static void arm_asm_native_run_template_hook( 3685 NativeTarget* t, const char* tmpl, const AsmConstraint* outs, u32 nout, 3686 Operand* bound_outs, const AsmConstraint* ins, u32 nin, Operand* bound_ins, 3687 const Sym* clobbers, u32 nclob) { 3688 Arm32Asm* asmh = arm32_asm_open(t->c); 3689 arm32_inline_bind(asmh, outs, nout, bound_outs, ins, nin, bound_ins, clobbers, 3690 nclob); 3691 arm32_asm_run_template(asmh, t->mc, tmpl); 3692 arm32_asm_close(asmh); 3693 } 3694 3695 static void arm_asm_block_native(NativeTarget* t, const char* tmpl, 3696 const AsmConstraint* outs, u32 nout, 3697 NativeLoc* out_locs, const AsmConstraint* ins, 3698 u32 nin, const NativeLoc* in_locs, 3699 const Sym* clobbers, u32 nclob) { 3700 SrcLoc loc = arm_of(t)->loc; 3701 static const NativeAsmNativeHooks hooks = { 3702 .panic = arm_asm_native_panic, 3703 .bound_reg = arm_asm_bound_reg, 3704 .bound_mem = arm_asm_bound_mem, 3705 .mem_base = arm_asm_native_mem_base_hook, 3706 .run_template = arm_asm_native_run_template_hook, 3707 }; 3708 native_asm_bind_native_operands(t, loc, tmpl, outs, nout, out_locs, ins, nin, 3709 in_locs, clobbers, nclob, &hooks); 3710 } 3711 3712 /* ============================ construction ============================ */ 3713 3714 NativeTarget* arm32_native_target_new(Compiler* c, ObjBuilder* obj, 3715 MCEmitter* mc) { 3716 Arm32NativeTarget* a = arena_znew(c->tu, Arm32NativeTarget); 3717 NativeTarget* t; 3718 if (!a) return NULL; 3719 t = &a->base; 3720 t->c = c; 3721 t->obj = obj; 3722 t->mc = mc; 3723 a->variant = arm32_variant_for_kind(c->target.arch); 3724 native_frame_init(&a->frame, c); 3725 t->regs = &arm_reg_info; 3726 t->class_for_type = native_class_for_type_fp_le8; 3727 t->imm_legal = arm_imm_legal; 3728 t->addr_legal = arm_addr_legal; 3729 t->can_fold_shift_into_alu = arm_can_fold_shift_into_alu; 3730 t->func_begin = arm_func_begin; 3731 t->func_begin_known_frame = arm_func_begin_known_frame; 3732 t->reserve_callee_saves = arm_reserve_callee_saves; 3733 t->signature_stack_bytes = arm_signature_stack_bytes; 3734 t->call_stack_bytes = arm_call_stack_bytes; 3735 t->has_store_zero_reg = 0; /* ARM has no zero register */ 3736 t->func_end = arm_func_end; 3737 t->frame_slot = arm_frame_slot; 3738 t->release_frame_slot = arm_release_frame_slot; 3739 t->frame_slot_debug_loc = arm_frame_slot_debug_loc; 3740 t->bind_param = arm_bind_native_param; 3741 t->label_new = arm_label_new; 3742 t->label_place = arm_label_place; 3743 t->jump = arm_jump; 3744 t->cmp_branch = arm_cmp_branch; 3745 t->indirect_branch = arm_indirect_branch; 3746 t->load_label_addr = arm_load_label_addr; 3747 t->move = arm_move; 3748 t->load_imm = arm_load_imm; 3749 t->load_const = arm_load_const; 3750 t->load_addr = arm_load_addr; 3751 t->load = arm_load; 3752 t->store = arm_store; 3753 t->tls_addr_of = arm_tls_addr_of; 3754 t->copy_bytes = arm_copy_bytes; 3755 t->set_bytes = arm_set_bytes; 3756 t->bitfield_load = arm_bitfield_load; 3757 t->bitfield_store = arm_bitfield_store; 3758 t->binop = arm_binop; 3759 t->unop = arm_unop; 3760 t->cmp = arm_cmp; 3761 t->convert = arm_convert; 3762 t->binop_rr = arm_binop_rr; 3763 t->move_rr = arm_move_rr; 3764 t->cmp_rr = arm_cmp_rr; 3765 t->convert_rr = arm_convert_rr; 3766 t->alloca_ = arm_alloca; 3767 t->spill = arm_spill; 3768 t->reload = arm_reload; 3769 t->marshal_call = arm_marshal_call; 3770 t->emit_call = arm_emit_call; 3771 t->marshal_ret = arm_marshal_ret; 3772 t->ret = arm_ret; 3773 t->atomic_load = arm_atomic_load; 3774 t->atomic_store = arm_atomic_store; 3775 t->atomic_rmw = arm_atomic_rmw; 3776 t->atomic_cas = arm_atomic_cas; 3777 t->fence = arm_fence; 3778 t->va_start_ = arm_va_start_native; 3779 t->va_arg_ = arm_va_arg_native; 3780 t->va_end_ = arm_va_end_native; 3781 t->va_copy_ = arm_va_copy_native; 3782 t->intrinsic = arm_intrinsic; 3783 t->asm_block = arm_asm_block_native; 3784 t->file_scope_asm = native_file_scope_asm; 3785 t->trap = arm_trap; 3786 t->set_loc = arm_set_loc; 3787 t->finalize = native_finalize; 3788 return t; 3789 } 3790 3791 /* ============================ NativeOps (-O0) ============================ */ 3792 3793 static void arm_bind_param(NativeDirectTarget* d, const CGParamDesc* p, 3794 CGLocal local, NativeDirectLocal* l) { 3795 NativeLoc dst; 3796 (void)local; 3797 memset(&dst, 0, sizeof dst); 3798 dst.kind = NATIVE_LOC_FRAME; 3799 dst.type = p->type; 3800 dst.v.frame = l->home; 3801 arm_bind_native_param(d->native, p, dst); 3802 } 3803 3804 /* A sibling call's outgoing stack args reuse the caller's incoming-arg window in 3805 * place; it is realizable only if they fit. (Register-only callees always fit.) */ 3806 static const char* arm_no_tail(NativeDirectTarget* d, const CGCallDesc* call) { 3807 NativeCallDesc nd; 3808 native_direct_project_tail_call_desc(d, call, &nd); 3809 if (arm_call_stack_size(d->native, &nd) > arm_of(d->native)->incoming_stack_size) 3810 return "arm32 tail call: callee stack args exceed the caller's window"; 3811 return NULL; 3812 } 3813 3814 /* ---- Direct (-O0) varargs wrappers: resolve a va_list operand's address into 3815 * a register, then call the shared cores. The va cores use ip (cursor/temp) and 3816 * lr (copy scratch); the va_list base register must be distinct, so the direct 3817 * wrappers stage it into a callee-saved scratch (r4) before calling. ---- */ 3818 static NativeAddr arm_direct_pointer_addr(NativeDirectTarget* d, Operand op, 3819 Reg reg) { 3820 NativeAddr addr; 3821 KitCgTypeId i32t = builtin_id(KIT_CG_BUILTIN_I32); 3822 memset(&addr, 0, sizeof addr); 3823 if (op.kind == OPK_LOCAL) { 3824 /* The local HOLDS the pointer (&ap): load its home value. */ 3825 NativeLoc base = native_loc_reg(i32t, NATIVE_REG_INT, reg); 3826 NativeAddr load; 3827 memset(&load, 0, sizeof load); 3828 load.base_kind = NATIVE_ADDR_BASE_FRAME; 3829 load.base.frame = d->locals[op.v.local - 1u].home; 3830 load.base_type = op.type; 3831 arm_emit_mem(arm_of(d->native), 1, base, load, 3832 native_mem_for_type(d->native, op.type, 4)); 3833 addr.base_kind = NATIVE_ADDR_BASE_REG; 3834 addr.base.reg = reg; 3835 addr.base_type = i32t; 3836 return addr; 3837 } 3838 /* OPK_INDIRECT names *(base+ofs): its address base+ofs IS the pointer. */ 3839 { 3840 NativeAddr m = arm_direct_materialize_addr(d, op); 3841 NativeLoc dst = native_loc_reg(i32t, NATIVE_REG_INT, reg); 3842 d->native->load_addr(d->native, dst, m); 3843 addr.base_kind = NATIVE_ADDR_BASE_REG; 3844 addr.base.reg = reg; 3845 addr.base_type = i32t; 3846 return addr; 3847 } 3848 } 3849 3850 static void arm_va_start_(NativeDirectTarget* d, Operand ap_addr) { 3851 arm_va_start_core(arm_of(d->native), arm_direct_pointer_addr(d, ap_addr, 4u)); 3852 } 3853 static void arm_va_arg_(NativeDirectTarget* d, Operand dst_op, Operand ap_addr, 3854 KitCgTypeId type) { 3855 Arm32NativeTarget* a = arm_of(d->native); 3856 NativeAddr ap = arm_direct_pointer_addr(d, ap_addr, 4u); 3857 if (arm_va_arg_is_wide(d->native, type)) { 3858 /* Wide (i64/double) va_arg copies straight from the save area into the 3859 * destination memory. arm_va_arg_wide uses ip (cursor) + lr (copy scratch), 3860 * so an OPK_INDIRECT destination pointer must live in a distinct reg (r5), 3861 * not ip — materialize it there. A FRAME (OPK_LOCAL) destination needs no 3862 * register. */ 3863 NativeAddr dst = arm_direct_addr(d, dst_op); 3864 if (dst.base_kind == NATIVE_ADDR_BASE_FRAME_VALUE) { 3865 NativeLoc base = native_loc_reg(dst.base_type, NATIVE_REG_INT, 5u); 3866 NativeAddr load; 3867 memset(&load, 0, sizeof load); 3868 load.base_kind = NATIVE_ADDR_BASE_FRAME; 3869 load.base.frame = dst.base.frame; 3870 load.base_type = dst.base_type; 3871 arm_emit_mem(a, 1, base, load, 3872 native_mem_for_type(d->native, dst.base_type, 4)); 3873 dst.base_kind = NATIVE_ADDR_BASE_REG; 3874 dst.base.reg = 5u; 3875 } 3876 arm_va_arg_wide(a, dst, ap, native_type_size(d->native, type)); 3877 return; 3878 } 3879 { 3880 NativeLoc res = native_loc_reg(type, NATIVE_REG_INT, 5u); 3881 NativeAddr dst; 3882 arm_va_arg_core(a, res, ap, type); 3883 dst = arm_direct_materialize_addr(d, dst_op); 3884 arm_emit_mem(a, 0, res, dst, 3885 native_mem_for_type(d->native, type, 3886 native_type_size(d->native, type))); 3887 } 3888 } 3889 static void arm_va_end_(NativeDirectTarget* d, Operand ap_addr) { 3890 (void)d; 3891 (void)ap_addr; 3892 } 3893 static void arm_va_copy_(NativeDirectTarget* d, Operand dst_op, Operand src_op) { 3894 Arm32NativeTarget* a = arm_of(d->native); 3895 NativeAddr src = arm_direct_pointer_addr(d, src_op, 4u); 3896 NativeAddr dst = arm_direct_pointer_addr(d, dst_op, 5u); 3897 arm_va_copy_core(a, dst, src); 3898 } 3899 3900 static const NativeOps arm_direct_ops = { 3901 .bind_param = arm_bind_param, 3902 .tail_call_unrealizable_reason = arm_no_tail, 3903 .va_start_ = arm_va_start_, 3904 .va_arg_ = arm_va_arg_, 3905 .va_end_ = arm_va_end_, 3906 .va_copy_ = arm_va_copy_, 3907 .asm_block = arm_direct_asm_block, 3908 }; 3909 3910 const NativeOps* arm32_native_direct_ops(void) { return &arm_direct_ops; }