native.c (187438B)
1 /* src/arch/x64/native.c — x86-64 (SysV / Win64) NativeTarget implementation. 2 * 3 * Mirrors the rv64 reference (src/arch/rv64/native.c): a physical-emission 4 * NativeTarget driven at -O0 by the shared NativeDirectTarget and at -O1+ by 5 * the optimizer emit path. ABI decisions route through abi/ and the per-OS 6 * X64ABIRegs (x64_abi_for_os); this file owns ISA emission and the x64 frame 7 * layout. 8 * 9 * Frame model (single, rbp-anchored): the prologue does `push rbp; mov rbp,rsp; 10 * sub rsp,frame_size`. Local/spill slots live below rbp at positive byte 11 * offsets `off` (address = rbp - off). Incoming stack args sit above the saved 12 * return address at [rbp + 16 + shadow_space + ...]. Callee-saved GPRs (and, on 13 * Win64, XMMs) are saved below the locals; outgoing args sit at [rsp + 0..]. 14 * The single-pass (-O0) prologue reserves a NOP placeholder patched in func_end 15 * once max_outgoing and callee-saves are known. 16 * 17 * Register model. R10/R11 and XMM14/XMM15 are backend-private hook/asm 18 * temporaries and are never optimizer operand locations. RSP/RBP are reserved 19 * stack/frame pointers. RAX is reserved too (return value and div/mul implicit 20 * operand), but is not in either temporary bank, so inline asm may pin an 21 * operand to it (the Linux syscall idiom) — see x64_asm_operand_reg_ok. The O1 22 * instruction-scoped operand bank is R8/R9 (int) and XMM4/XMM5 (fp), 23 * caller-saved on both SysV and Win64; the O0 direct target happens to use the 24 * same registers through its independent `scratch` policy. Both banks are 25 * reserved from persistent allocation. 26 * Callee-saved set is resolved per-OS via x64_abi_for_os at runtime (the 27 * legality masks below are SysV's, the conservative superset that both ABIs' 28 * allocators respect — Win64's extra callee-saves RDI/RSI/xmm6-15 only shrink 29 * the allocable pool, never grow it). */ 30 31 #include <string.h> 32 33 #include "abi/abi.h" 34 #include "arch/x64/asm.h" 35 #include "arch/x64/emit.h" 36 #include "arch/x64/isa.h" 37 #include "arch/x64/regs.h" 38 #include "arch/x64/x64.h" 39 #include "asm/asm.h" 40 #include "asm/asm_lex.h" 41 #include "cg/native_argmove.h" 42 #include "cg/native_asm.h" 43 #include "cg/native_direct_target.h" 44 #include "cg/native_frame.h" 45 #include "cg/type.h" 46 #include "core/arena.h" 47 #include "core/bytes.h" 48 #include "core/pool.h" 49 #include "core/slice.h" 50 #include "obj/obj.h" 51 52 enum { 53 X64_TMP_INT = X64_R10, /* backend-private int temp (reserved) */ 54 X64_TMP_INT2 = X64_R11, /* backend-private int temp (reserved) */ 55 X64_TMP_FP = X64_XMM0 + 14, /* backend-private fp temp (reserved) */ 56 X64_TMP_FP2 = X64_XMM15, /* backend-private fp temp (reserved) */ 57 X64_MAX_REG_ARG_MOVES = 16u, 58 /* Deferred entry register-binds (-O1): bounded by simultaneously-live 59 * register-homed param parts, i.e. the allocable register count. */ 60 X64_MAX_BIND_MOVES = 32u, 61 X64_MAX_CS_FP_REGS = 10u, /* Win64 xmm6..xmm15 */ 62 }; 63 64 /* ============================ target state ============================ */ 65 66 /* Frame slots and callee-save records live in the shared NativeFrame 67 * bookkeeping (cg/native_frame.h); these aliases keep the x64-local spellings. 68 * x64 reads only .reg/.cls of a callee-save (it computes save offsets below the 69 * locals rather than homing them in frame slots, so .slot/.type stay unused). 70 */ 71 typedef NativeFrameSlotEntry X64NativeSlot; 72 typedef NativeFrameCalleeSave X64CalleeSave; 73 74 typedef enum X64PatchKind { X64_PATCH_ALLOCA } X64PatchKind; 75 76 typedef struct X64Patch { 77 u8 kind; /* X64PatchKind */ 78 u32 pos; /* byte offset of the disp32 to patch */ 79 } X64Patch; 80 81 typedef struct X64NativeTarget { 82 NativeTarget base; 83 SrcLoc loc; 84 const CGFuncDesc* func; 85 86 /* Shared frame bookkeeping: slot table, cum_off, max_outgoing, callee-save 87 * set, and the known_frame / has_alloca / frame_final flags. */ 88 NativeFrame frame; 89 u32 frame_size_final; 90 91 u32 incoming_stack_size; /* fixed-param stack bytes (tail-call check) */ 92 u32 next_param_int; 93 u32 next_param_fp; 94 u32 next_param_stack; 95 u8 has_sret; 96 u8 is_variadic; 97 NativeFrameSlot sret_ptr_slot; 98 NativeFrameSlot reg_save_slot; /* SysV variadic 176B __va_list_tag area */ 99 100 X64Patch* patches; 101 u32 npatches; 102 u32 patches_cap; 103 u32 nalloca; 104 105 u32 func_start; 106 u32 prologue_pos; 107 u32 prologue_nbytes; 108 MCLabel epilogue_label; 109 110 /* Known-frame (-O1) prologue cost-model tiers, settled in 111 * x64_func_begin_known_frame; both 0 on the single-pass path (which can't 112 * know the frame up front). Either one suppresses the `sub rsp` reservation; 113 * the rbp frame record (push rbp; mov rbp,rsp) and every rbp-relative offset 114 * stay unchanged, so the epilogue (`leave`), CFI (CFA = rbp+16), and debug 115 * locs are identical to the fat shape. slim_frame - empty frame (no 116 * callee-saves/locals/outgoing/alloca): the `sub rsp` reserved nothing, so it 117 * is simply dropped. Safe for non-leaves (push rbp keeps rsp 16-aligned for 118 * calls, and nothing lives below rsp). SysV + Win64. redzone_leaf - SysV leaf 119 * with a small frame (<= 128B, no alloca, no outgoing args): 120 * locals/callee-saves stay at their rbp-relative offsets, which now land in 121 * the 128-byte red zone instead of a reserved region. Leaf-only — a call 122 * would clobber the red zone. */ 123 u8 slim_frame; 124 u8 redzone_leaf; 125 126 /* Optimizer (-O1) entry binds: register-destination param binds are deferred 127 * here and resolved as a parallel copy in x64_bind_params_end, since the 128 * allocator may rotate params across the incoming arg registers — a 129 * permutation the naive per-param move order would clobber. */ 130 NativeArgMove bind_moves[X64_MAX_BIND_MOVES]; 131 u32 nbind_moves; 132 133 const X64ABIRegs* abi; 134 } X64NativeTarget; 135 136 static X64NativeTarget* x64_of(NativeTarget* t) { return (X64NativeTarget*)t; } 137 138 static _Noreturn void x64_panic(X64NativeTarget* a, const char* msg) { 139 compiler_panic(a->base.c, a->loc, "x64 native target: %s", msg); 140 } 141 142 static X64NativeSlot* x64_slot_get(X64NativeTarget* a, NativeFrameSlot fs) { 143 return native_frame_slot_at(&a->frame, fs); 144 } 145 146 /* ============================ type helpers ============================ */ 147 148 /* Scalar size/align/mem/class/loc constructors are shared in native_target.h 149 * (native_type_size, native_type_align, native_mem_for_type, 150 * native_class_for_type_fp_le8, native_loc_reg, native_loc_stack, 151 * native_loc_is_fp). loc_reg's mask is arch-specific and stays here. */ 152 153 /* A scalar value occupies a 64-bit register when it is pointer-sized or wider 154 * (drives REX.W selection). */ 155 static int x64_is_64(NativeTarget* t, KitCgTypeId type) { 156 return native_type_size(t, type) >= 8u || cg_type_is_ptr(t->c, type); 157 } 158 159 /* Scalar byte width of a register operand. Reads the NDT-stamped szinfo 160 * descriptor (one byte) when present, else falls back to the live type query — 161 * byte-identical to native_type_size for in-range scalars. */ 162 static u32 loc_size32(NativeTarget* t, NativeLoc loc) { 163 if (loc.szinfo & NATIVE_SZINFO_VALID) return native_szinfo_size(loc.szinfo); 164 return native_type_size(t, loc.type); 165 } 166 167 /* Select the physical integer carrier width. Native scalar sizes are 168 * 1/2/4/8, while a small direct aggregate may legitimately remain packed in a 169 * register under the -O0 cache; its 5/6/7-byte representation needs a 64-bit 170 * move even though its semantic size is below eight bytes. */ 171 static int loc_is_64(NativeTarget* t, NativeLoc loc) { 172 return loc_size32(t, loc) > 4u || cg_type_is_ptr(t->c, loc.type); 173 } 174 175 static u32 loc_reg(NativeLoc loc) { return loc.v.reg & 0xfu; } 176 177 /* SSE scalar prefix: F2 (double / 8-byte) vs F3 (single / 4-byte). */ 178 static u8 sse_scalar_prefix(u32 size) { return size == 8u ? 0xF2u : 0xF3u; } 179 180 /* Forward decls for the rel32 branch emitters (used by convert before the 181 * control-flow section defines them). */ 182 static void emit_jmp_rel32(MCEmitter* mc, MCLabel l); 183 static void emit_jcc_rel32(MCEmitter* mc, u32 cc, MCLabel l); 184 185 /* ============================ register tables ============================ */ 186 187 #define X64_PHYS_INT_ARG(r) \ 188 {.reg = (r), \ 189 .cls = NATIVE_REG_INT, \ 190 .abi_index = 0xffu, \ 191 .flags = NATIVE_REG_ALLOCABLE | NATIVE_REG_CALLER_SAVED | NATIVE_REG_ARG, \ 192 .spill_cost = 1u, \ 193 .copy_cost = 1u} 194 #define X64_PHYS_INT_ARG_RESERVED(r) \ 195 {.reg = (r), \ 196 .cls = NATIVE_REG_INT, \ 197 .abi_index = 0xffu, \ 198 .flags = NATIVE_REG_CALLER_SAVED | NATIVE_REG_ARG | NATIVE_REG_RESERVED, \ 199 .spill_cost = 0u, \ 200 .copy_cost = 0u} 201 #define X64_PHYS_INT_RET_ARG(r) \ 202 {.reg = (r), \ 203 .cls = NATIVE_REG_INT, \ 204 .abi_index = 0xffu, \ 205 .flags = NATIVE_REG_ALLOCABLE | NATIVE_REG_CALLER_SAVED | NATIVE_REG_ARG | \ 206 NATIVE_REG_RET, \ 207 .spill_cost = 1u, \ 208 .copy_cost = 1u} 209 #define X64_PHYS_INT_CALLER(r) \ 210 {.reg = (r), \ 211 .cls = NATIVE_REG_INT, \ 212 .abi_index = 0xffu, \ 213 .flags = NATIVE_REG_ALLOCABLE | NATIVE_REG_CALLER_SAVED, \ 214 .spill_cost = 1u, \ 215 .copy_cost = 1u} 216 #define X64_PHYS_INT_CALLEE(r) \ 217 {.reg = (r), \ 218 .cls = NATIVE_REG_INT, \ 219 .abi_index = 0xffu, \ 220 .flags = NATIVE_REG_ALLOCABLE | NATIVE_REG_CALLEE_SAVED, \ 221 .spill_cost = 4u, \ 222 .copy_cost = 1u} 223 #define X64_PHYS_INT_RESERVED_ROLE(r, role) \ 224 {.reg = (r), \ 225 .cls = NATIVE_REG_INT, \ 226 .abi_index = 0xffu, \ 227 .flags = NATIVE_REG_RESERVED | (role), \ 228 .spill_cost = 0u, \ 229 .copy_cost = 0u} 230 #define X64_PHYS_INT_RESERVED(r) \ 231 X64_PHYS_INT_RESERVED_ROLE((r), NATIVE_REG_NONE) 232 #define X64_PHYS_INT_RESERVED_CALLER(r) \ 233 X64_PHYS_INT_RESERVED_ROLE((r), NATIVE_REG_CALLER_SAVED) 234 #define X64_PHYS_INT_RESERVED_CALLEE(r) \ 235 X64_PHYS_INT_RESERVED_ROLE((r), NATIVE_REG_CALLEE_SAVED) 236 237 /* The NDT (-O0) value-cache / scratch pool (read only by NativeDirectTarget; 238 * the optimizer allocates over the phys[] ALLOCABLE flags instead, which also 239 * include rcx/rdx/rsi/rdi). Caller-saved RSI/RDI lead so the cache prefers them 240 * (no prologue save) and only spills into callee-saved R13-R15 under pressure 241 * (the fixed-size save region covers them). RSI/RDI are safe to cache: they have 242 * no implicit x86 use — unlike RCX (shift count) and RAX/RDX (div/mul), which 243 * are therefore excluded — and incoming args are spilled to frame homes at entry 244 * before any body op runs. R8/R9 are also the O1 operand-temp bank; R10/R11 are 245 * backend-private (reserved); RAX is reserved (return/div-mul, asm-pinnable). */ 246 static const Reg x64_ndt_int_allocable[] = {X64_RSI, X64_RDI, X64_R13, 247 X64_R14, X64_R15}; 248 static const Reg x64_int_scratch[] = {X64_R8, X64_R9}; 249 static const Reg x64_int_asm_temps[] = {X64_TMP_INT, X64_TMP_INT2}; 250 static const Reg x64_direct_asm_int[] = {X64_RDI, X64_RSI, X64_RDX, X64_RCX}; 251 252 static const NativePhysRegInfo x64_int_phys[] = { 253 X64_PHYS_INT_RESERVED_ROLE( 254 X64_RAX, NATIVE_REG_CALLER_SAVED | NATIVE_REG_RET), 255 X64_PHYS_INT_ARG(X64_RCX), 256 X64_PHYS_INT_RET_ARG(X64_RDX), 257 X64_PHYS_INT_RESERVED_CALLEE(X64_RBX), 258 X64_PHYS_INT_RESERVED(X64_RSP), /* stack pointer */ 259 X64_PHYS_INT_RESERVED(X64_RBP), /* frame pointer */ 260 X64_PHYS_INT_ARG(X64_RSI), 261 X64_PHYS_INT_ARG(X64_RDI), 262 X64_PHYS_INT_ARG_RESERVED(X64_R8), /* O1 operand temp / O0 scratch */ 263 X64_PHYS_INT_ARG_RESERVED(X64_R9), /* O1 operand temp / O0 scratch */ 264 X64_PHYS_INT_RESERVED_CALLER(X64_R10), /* backend-private temp */ 265 X64_PHYS_INT_RESERVED_CALLER(X64_R11), /* backend-private temp */ 266 X64_PHYS_INT_RESERVED_CALLEE(X64_R12), 267 X64_PHYS_INT_CALLEE(X64_R13), 268 X64_PHYS_INT_CALLEE(X64_R14), 269 X64_PHYS_INT_CALLEE(X64_R15), 270 }; 271 272 #define X64_PHYS_FP_ARG_RET(r) \ 273 {.reg = (r), \ 274 .cls = NATIVE_REG_FP, \ 275 .abi_index = 0xffu, \ 276 .flags = NATIVE_REG_ALLOCABLE | NATIVE_REG_CALLER_SAVED | NATIVE_REG_ARG | \ 277 NATIVE_REG_RET, \ 278 .spill_cost = 1u, \ 279 .copy_cost = 1u} 280 #define X64_PHYS_FP_ARG(r) \ 281 {.reg = (r), \ 282 .cls = NATIVE_REG_FP, \ 283 .abi_index = 0xffu, \ 284 .flags = NATIVE_REG_ALLOCABLE | NATIVE_REG_CALLER_SAVED | NATIVE_REG_ARG, \ 285 .spill_cost = 1u, \ 286 .copy_cost = 1u} 287 #define X64_PHYS_FP_ARG_RESERVED(r) \ 288 {.reg = (r), \ 289 .cls = NATIVE_REG_FP, \ 290 .abi_index = 0xffu, \ 291 .flags = NATIVE_REG_CALLER_SAVED | NATIVE_REG_ARG | NATIVE_REG_RESERVED, \ 292 .spill_cost = 0u, \ 293 .copy_cost = 0u} 294 #define X64_PHYS_FP_CALLER(r) \ 295 {.reg = (r), \ 296 .cls = NATIVE_REG_FP, \ 297 .abi_index = 0xffu, \ 298 .flags = NATIVE_REG_ALLOCABLE | NATIVE_REG_CALLER_SAVED, \ 299 .spill_cost = 1u, \ 300 .copy_cost = 1u} 301 #define X64_PHYS_FP_RESERVED(r) \ 302 {.reg = (r), \ 303 .cls = NATIVE_REG_FP, \ 304 .abi_index = 0xffu, \ 305 .flags = NATIVE_REG_RESERVED | \ 306 NATIVE_REG_CALLER_SAVED, \ 307 .spill_cost = 0u, \ 308 .copy_cost = 0u} 309 310 /* Allocable FP pool: arg/ret xmm0..xmm3 lead so -O0 fp-arg producers 311 * materialize directly into the ABI arg registers (Lever 1), then xmm6..xmm11. 312 * xmm0/xmm1 are also the fp ret regs but ndt_result_reg_stable is OFF, so the 313 * fp result still moves out of xmm0 via the post-call path (often a no-op). 314 * xmm4/xmm5 form the O1 operand-temp bank (and O0 scratch policy); 315 * xmm14/xmm15 are backend-private. */ 316 static const Reg x64_ndt_fp_allocable[] = { 317 X64_XMM0, X64_XMM1, X64_XMM2, X64_XMM3, 318 X64_XMM6, X64_XMM7, X64_XMM8, X64_XMM0 + 9, 319 X64_XMM0 + 10, X64_XMM0 + 11}; 320 static const Reg x64_fp_scratch[] = {X64_XMM4, X64_XMM5}; 321 static const Reg x64_fp_asm_temps[] = {X64_TMP_FP, X64_TMP_FP2}; 322 static const Reg x64_direct_asm_fp[] = { 323 X64_XMM0, X64_XMM1, X64_XMM2, X64_XMM3, 324 X64_XMM6, X64_XMM7, X64_XMM8, X64_XMM0 + 9, 325 X64_XMM0 + 10, X64_XMM0 + 11}; 326 327 static const NativePhysRegInfo x64_fp_phys[] = { 328 X64_PHYS_FP_ARG_RET(X64_XMM0), X64_PHYS_FP_ARG_RET(X64_XMM1), 329 X64_PHYS_FP_ARG(X64_XMM2), X64_PHYS_FP_ARG(X64_XMM3), 330 X64_PHYS_FP_ARG_RESERVED(X64_XMM4), X64_PHYS_FP_ARG_RESERVED(X64_XMM5), 331 X64_PHYS_FP_ARG(X64_XMM6), X64_PHYS_FP_ARG(X64_XMM7), 332 X64_PHYS_FP_CALLER(X64_XMM8), X64_PHYS_FP_CALLER(X64_XMM0 + 9), 333 X64_PHYS_FP_CALLER(X64_XMM0 + 10), X64_PHYS_FP_CALLER(X64_XMM0 + 11), 334 X64_PHYS_FP_RESERVED(X64_XMM0 + 12), X64_PHYS_FP_RESERVED(X64_XMM0 + 13), 335 X64_PHYS_FP_RESERVED(X64_XMM0 + 14), /* backend-private temp */ 336 X64_PHYS_FP_RESERVED(X64_XMM15), /* backend-private temp */ 337 }; 338 339 static const NativeAllocClassInfo x64_classes[] = { 340 {.cls = NATIVE_REG_INT, 341 .ndt_allocable = x64_ndt_int_allocable, 342 .ndt_allocable_count = 343 sizeof x64_ndt_int_allocable / sizeof x64_ndt_int_allocable[0], 344 .scratch = x64_int_scratch, 345 .nscratch = sizeof x64_int_scratch / sizeof x64_int_scratch[0], 346 .emit_temps = x64_int_scratch, 347 .nemit_temps = sizeof x64_int_scratch / sizeof x64_int_scratch[0], 348 .asm_temps = x64_int_asm_temps, 349 .nasm_temps = sizeof x64_int_asm_temps / sizeof x64_int_asm_temps[0], 350 .direct_asm_allocable = x64_direct_asm_int, 351 .ndirect_asm_allocable = 352 sizeof x64_direct_asm_int / sizeof x64_direct_asm_int[0], 353 .emit_cache_mask = (1u << X64_R8) | (1u << X64_R9), 354 .phys = x64_int_phys, 355 .nphys = sizeof x64_int_phys / sizeof x64_int_phys[0]}, 356 {.cls = NATIVE_REG_FP, 357 .ndt_allocable = x64_ndt_fp_allocable, 358 .ndt_allocable_count = 359 sizeof x64_ndt_fp_allocable / sizeof x64_ndt_fp_allocable[0], 360 .scratch = x64_fp_scratch, 361 .nscratch = sizeof x64_fp_scratch / sizeof x64_fp_scratch[0], 362 .emit_temps = x64_fp_scratch, 363 .nemit_temps = sizeof x64_fp_scratch / sizeof x64_fp_scratch[0], 364 .asm_temps = x64_fp_asm_temps, 365 .nasm_temps = sizeof x64_fp_asm_temps / sizeof x64_fp_asm_temps[0], 366 .direct_asm_allocable = x64_direct_asm_fp, 367 .ndirect_asm_allocable = 368 sizeof x64_direct_asm_fp / sizeof x64_direct_asm_fp[0], 369 .emit_cache_mask = (1u << X64_XMM4) | (1u << X64_XMM5), 370 .phys = x64_fp_phys, 371 .nphys = sizeof x64_fp_phys / sizeof x64_fp_phys[0]}, 372 }; 373 374 /* Resolve a register name ("r10", "xmm3", ...) to its (class, Reg). Powers the 375 * optimizer's inline-asm clobber masks and explicit hard-register operands 376 * ("{r10}" from a GNU local register variable). GPR names map through the HW 377 * encoding; xmm names through the DWARF index table. Returns non-zero for a 378 * non-register name (cc/memory/unknown), which the caller skips. */ 379 static int x64_resolve_name(const NativeRegInfo* ri, Slice name, Reg* out, 380 NativeAllocClass* cls_out) { 381 char buf[16]; 382 uint32_t idx; 383 (void)ri; 384 if (!name.s || !name.len || name.len >= sizeof buf) return 1; 385 memcpy(buf, name.s, name.len); 386 buf[name.len] = '\0'; 387 if (x64_register_hw_index(buf, &idx) == 0 && idx <= 15u) { 388 *cls_out = NATIVE_REG_INT; 389 *out = (Reg)idx; 390 return 0; 391 } 392 if (x64_register_index(buf, &idx) == 0 && idx >= 17u && idx <= 32u) { 393 *cls_out = NATIVE_REG_FP; 394 *out = (Reg)(idx - 17u); 395 return 0; 396 } 397 return 1; 398 } 399 400 static int x64_asm_operand_reg_ok(const NativeRegInfo* ri, NativeAllocClass cls, 401 Reg reg) { 402 (void)ri; 403 if (cls == NATIVE_REG_INT) { 404 switch (reg) { 405 /* RAX is reserved but not an emitter temp, so it is a legal asm pin (the 406 * Linux syscall number/return register). R8/R9 are optimizer operand 407 * temps and R10/R11 backend/asm temps, so those stay excluded. */ 408 case X64_RAX: 409 case X64_RBX: 410 case X64_RCX: 411 case X64_RDX: 412 case X64_RSI: 413 case X64_RDI: 414 case X64_R12: 415 case X64_R13: 416 case X64_R14: 417 case X64_R15: 418 return 1; 419 default: 420 return 0; 421 } 422 } 423 if (cls == NATIVE_REG_FP) 424 return reg <= X64_XMM0 + 13u && reg != X64_XMM4 && reg != X64_XMM5; 425 return 0; 426 } 427 428 static int x64_asm_constraint_reg(const NativeRegInfo* ri, const char* body, 429 NativeAllocClass* cls_out, Reg* fixed_out, 430 u32* allowed_mask_out) { 431 (void)ri; 432 if (!body || !body[0] || body[1]) return 0; 433 if (fixed_out) *fixed_out = REG_NONE; 434 if (allowed_mask_out) *allowed_mask_out = 0; 435 switch (body[0]) { 436 case 'r': 437 case 'q': 438 if (cls_out) *cls_out = NATIVE_REG_INT; 439 return 1; 440 case 'a': 441 if (cls_out) *cls_out = NATIVE_REG_INT; 442 if (fixed_out) *fixed_out = X64_RAX; 443 return 1; 444 case 'b': 445 if (cls_out) *cls_out = NATIVE_REG_INT; 446 if (fixed_out) *fixed_out = X64_RBX; 447 return 1; 448 case 'c': 449 if (cls_out) *cls_out = NATIVE_REG_INT; 450 if (fixed_out) *fixed_out = X64_RCX; 451 return 1; 452 case 'd': 453 if (cls_out) *cls_out = NATIVE_REG_INT; 454 if (fixed_out) *fixed_out = X64_RDX; 455 return 1; 456 case 'S': 457 if (cls_out) *cls_out = NATIVE_REG_INT; 458 if (fixed_out) *fixed_out = X64_RSI; 459 return 1; 460 case 'D': 461 if (cls_out) *cls_out = NATIVE_REG_INT; 462 if (fixed_out) *fixed_out = X64_RDI; 463 return 1; 464 case 'x': 465 case 'v': 466 if (cls_out) *cls_out = NATIVE_REG_FP; 467 return 1; 468 default: 469 return 0; 470 } 471 } 472 473 static const NativeRegInfo x64_reg_info = { 474 .classes = x64_classes, 475 .nclasses = sizeof x64_classes / sizeof x64_classes[0], 476 .resolve_name = x64_resolve_name, 477 .asm_operand_reg_ok = x64_asm_operand_reg_ok, 478 .asm_constraint_reg = x64_asm_constraint_reg, 479 }; 480 481 /* ============================ legality ============================ */ 482 483 static int x64_imm_legal(NativeTarget* t, NativeImmUse use, u32 op, 484 KitCgTypeId type, i64 imm) { 485 (void)t; 486 (void)type; 487 switch (use) { 488 case NATIVE_IMM_MOVE: 489 return 1; 490 case NATIVE_IMM_BINOP: 491 switch ((BinOp)op) { 492 case BO_IADD: 493 case BO_ISUB: 494 case BO_AND: 495 case BO_OR: 496 case BO_XOR: 497 case BO_IMUL: 498 return imm_fits_i32(imm); 499 case BO_SHL: 500 case BO_SHR_S: 501 case BO_SHR_U: 502 return imm >= 0 && imm <= 63; 503 default: 504 return 0; 505 } 506 case NATIVE_IMM_CMP: 507 return imm_fits_i32(imm); 508 case NATIVE_IMM_ADDR_OFFSET: 509 return imm_fits_i32(imm); 510 } 511 return 0; 512 } 513 514 static int x64_addr_legal(NativeTarget* t, const NativeAddr* addr, 515 MemAccess mem) { 516 (void)t; 517 (void)mem; 518 if (!addr) return 0; 519 if (addr->base_kind != NATIVE_ADDR_BASE_REG && 520 addr->base_kind != NATIVE_ADDR_BASE_FRAME) 521 return 0; 522 /* x64 supports [base + index*scale + disp32]; index must be a register. */ 523 if (addr->index_kind != NATIVE_ADDR_INDEX_NONE && 524 addr->index_kind != NATIVE_ADDR_INDEX_REG) 525 return 0; 526 return imm_fits_i32(addr->offset); 527 } 528 529 /* ============================ globals / addresses ============================ 530 */ 531 532 static int x64_use_got_for_sym(NativeTarget* t, ObjSymId sym) { 533 return obj_symbol_extern_via_got(t->c, t->obj, sym); 534 } 535 536 /* PC-relative reloc kind for a non-GOT &sym reference. Functions use PLT32 so 537 * the linker can route through a PLT; data uses plain PC32. */ 538 static u32 x64_pcrel_reloc_for_sym(NativeTarget* t, ObjSymId sym) { 539 const ObjSym* s = obj_symbol_get(t->obj, sym); 540 if (s && (s->kind == SK_FUNC || s->kind == SK_IFUNC)) return R_X64_PLT32; 541 return R_PC32; 542 } 543 544 /* Materialize &sym + addend into dst_reg. Local/static-link symbols use 545 * `lea rd, [rip + disp32]`; GOT-routed externs use `mov rd, [rip + GOT]` then 546 * add any nonzero addend. */ 547 static void x64_emit_global_lea(NativeTarget* t, u32 dst_reg, ObjSymId sym, 548 i64 addend) { 549 MCEmitter* mc = t->mc; 550 u32 sec = mc->section_id; 551 if (x64_use_got_for_sym(t, sym)) { 552 u8 op; 553 u32 disp_pos; 554 emit_rex(mc, 1, dst_reg, 0, 0); 555 op = X64_OPC_MOV_R_RM; 556 mc_emit_bytes(mc, &op, 1); 557 { 558 u8 mr = modrm(0u, dst_reg & 7u, 5u); /* [rip + disp32] */ 559 mc_emit_bytes(mc, &mr, 1); 560 } 561 disp_pos = mc_pos(mc); 562 emit_u32le(mc, 0); 563 mc_emit_reloc_at(mc, sec, disp_pos, R_X64_REX_GOTPCRELX, sym, -4, 1, 0); 564 if (addend) { 565 i32 a = (i32)addend; 566 emit_rex(mc, 1, 0, 0, dst_reg); 567 if (imm_fits_i8(a)) { 568 u8 buf[3] = {X64_OPC_ALU_IMM8, modrm(3u, X64_ALU_SUB_ADD, dst_reg & 7u), 569 (u8)a}; 570 mc_emit_bytes(mc, buf, 3); 571 } else { 572 u8 buf[2] = {X64_OPC_ALU_IMM32, 573 modrm(3u, X64_ALU_SUB_ADD, dst_reg & 7u)}; 574 mc_emit_bytes(mc, buf, 2); 575 emit_u32le(mc, (u32)a); 576 } 577 } 578 return; 579 } 580 { 581 u8 op = X64_OPC_LEA; 582 u32 disp_pos; 583 emit_rex(mc, 1, dst_reg, 0, 0); 584 mc_emit_bytes(mc, &op, 1); 585 { 586 u8 mr = modrm(0u, dst_reg & 7u, 5u); /* [rip + disp32] */ 587 mc_emit_bytes(mc, &mr, 1); 588 } 589 disp_pos = mc_pos(mc); 590 emit_u32le(mc, 0); 591 mc_emit_reloc_at(mc, sec, disp_pos, x64_pcrel_reloc_for_sym(t, sym), sym, 592 addend - 4, 1, 0); 593 } 594 } 595 596 /* Resolve a NativeAddr to (base, index, log2_scale, off). Plain FRAME bases 597 * remain rbp-relative. GLOBAL and FRAME_VALUE components use backend-private 598 * fixed temps; each FRAME_VALUE load uses its exact base_type/index_type. */ 599 static u32 x64_resolve_addr(X64NativeTarget* a, const NativeAddr* addr, 600 u32 scratch, u32* idx_out, u32* scale_out, 601 i32* off_out) { 602 NativeTarget* t = &a->base; 603 u32 base; 604 i32 off; 605 switch (addr->base_kind) { 606 case NATIVE_ADDR_BASE_REG: 607 base = addr->base.reg & 0xfu; 608 off = addr->offset; 609 break; 610 case NATIVE_ADDR_BASE_FRAME: { 611 X64NativeSlot* s = x64_slot_get(a, addr->base.frame); 612 base = X64_RBP; 613 off = -(i32)s->off + addr->offset; 614 break; 615 } 616 case NATIVE_ADDR_BASE_FRAME_VALUE: { 617 X64NativeSlot* s = x64_slot_get(a, addr->base.frame); 618 u32 size; 619 if (!addr->base_type) x64_panic(a, "frame-value base has no exact type"); 620 size = native_type_size(t, addr->base_type); 621 emit_mov_load(t->mc, size, 0, scratch, X64_RBP, -(i32)s->off); 622 base = scratch; 623 off = addr->offset; 624 break; 625 } 626 case NATIVE_ADDR_BASE_GLOBAL: 627 x64_emit_global_lea(t, scratch, addr->base.global.sym, 628 addr->base.global.addend); 629 base = scratch; 630 off = addr->offset; 631 break; 632 default: 633 x64_panic(a, "unsupported address base"); 634 } 635 if (addr->index_kind == NATIVE_ADDR_INDEX_REG) { 636 *idx_out = addr->index.reg & 0xfu; 637 *scale_out = addr->log2_scale; 638 } else if (addr->index_kind == NATIVE_ADDR_INDEX_FRAME_VALUE) { 639 X64NativeSlot* s = x64_slot_get(a, addr->index.frame); 640 u32 size; 641 u32 index_tmp = base == X64_TMP_INT2 ? X64_TMP_INT : X64_TMP_INT2; 642 if (!addr->index_type) 643 x64_panic(a, "frame-value index has no exact type"); 644 size = native_type_size(t, addr->index_type); 645 emit_mov_load(t->mc, size, 0, index_tmp, X64_RBP, -(i32)s->off); 646 *idx_out = index_tmp; 647 *scale_out = addr->log2_scale; 648 } else { 649 *idx_out = REG_NONE; 650 *scale_out = 0; 651 } 652 *off_out = off; 653 return base; 654 } 655 656 /* ============================ memory ============================ */ 657 658 static int x64_native_part_width(u32 size); 659 static u32 x64_addr_to_base_reg(X64NativeTarget* a, NativeAddr addr, 660 u32 scratch); 661 662 /* Central load/store primitive. is_load: 1 load into reg, 0 store reg to mem. 663 * Materializes the address through X64_TMP_INT2 (r11) for non-reg bases. */ 664 static void x64_emit_mem(X64NativeTarget* a, int is_load, NativeLoc reg, 665 NativeAddr addr, MemAccess mem) { 666 NativeTarget* t = &a->base; 667 MCEmitter* mc = t->mc; 668 u32 r = loc_reg(reg); 669 int fp = native_loc_is_fp(reg); 670 u32 sz = mem.size ? mem.size : loc_size32(t, reg); 671 u32 base, idx, scale; 672 i32 off; 673 674 if (!fp && !x64_native_part_width(sz)) 675 x64_panic(a, "raw integer memory width is not native"); 676 677 /* Global base: fold into a single rip-relative access when local. */ 678 if (addr.base_kind == NATIVE_ADDR_BASE_GLOBAL && 679 addr.index_kind == NATIVE_ADDR_INDEX_NONE && 680 !x64_use_got_for_sym(t, addr.base.global.sym)) { 681 ObjSymId sym = addr.base.global.sym; 682 i64 ad = addr.base.global.addend + addr.offset; 683 u32 sec = mc->section_id; 684 u32 disp_pos; 685 if (fp) { 686 u8 prefix = sse_scalar_prefix(sz); 687 mc_emit_bytes(mc, &prefix, 1); 688 emit_rex(mc, 0, r, 0, 0); 689 { 690 u8 op2[2] = {X64_OPC_TWOBYTE, (u8)(is_load ? 0x10u : 0x11u)}; 691 mc_emit_bytes(mc, op2, 2); 692 } 693 } else if (sz == 8 || sz == 4) { 694 emit_rex(mc, sz == 8, r, 0, 0); 695 { 696 u8 op = is_load ? X64_OPC_MOV_R_RM : X64_OPC_MOV_RM_R; 697 mc_emit_bytes(mc, &op, 1); 698 } 699 } else if (sz == 2) { 700 if (is_load) { 701 emit_rex(mc, 0, r, 0, 0); 702 { 703 u8 op2[2] = {X64_OPC_TWOBYTE, X64_OPC_MOVZX_W}; 704 mc_emit_bytes(mc, op2, 2); 705 } 706 } else { 707 u8 p = X64_OPSIZE_PFX; 708 mc_emit_bytes(mc, &p, 1); 709 emit_rex(mc, 0, r, 0, 0); 710 { 711 u8 op = X64_OPC_MOV_RM_R; 712 mc_emit_bytes(mc, &op, 1); 713 } 714 } 715 } else { /* size 1 */ 716 if (is_load) { 717 emit_rex(mc, 0, r, 0, 0); 718 { 719 u8 op2[2] = {X64_OPC_TWOBYTE, X64_OPC_MOVZX_B}; 720 mc_emit_bytes(mc, op2, 2); 721 } 722 } else { 723 emit_rex_force(mc, 0, r, 0, 0); 724 { 725 u8 op = X64_OPC_MOV_RM_R8; 726 mc_emit_bytes(mc, &op, 1); 727 } 728 } 729 } 730 { 731 u8 mr = modrm(0u, r & 7u, 5u); 732 mc_emit_bytes(mc, &mr, 1); 733 } 734 disp_pos = mc_pos(mc); 735 emit_u32le(mc, 0); 736 mc_emit_reloc_at(mc, sec, disp_pos, x64_pcrel_reloc_for_sym(t, sym), sym, 737 ad - 4, 1, 0); 738 return; 739 } 740 741 base = x64_resolve_addr(a, &addr, X64_TMP_INT2, &idx, &scale, &off); 742 if (fp) { 743 u8 prefix = sse_scalar_prefix(sz); 744 if (is_load) 745 emit_sse_load_idx(mc, prefix, 0x10, r, base, idx, scale, off); 746 else 747 emit_sse_store_idx(mc, prefix, 0x11, r, base, idx, scale, off); 748 } else if (is_load) { 749 /* Loads narrower than 4 bytes zero-extend (sign-extension is applied by a 750 * later CV_SEXT). */ 751 emit_mov_load_idx(mc, sz, 0, r, base, idx, scale, off); 752 } else { 753 emit_mov_store_idx(mc, sz, r, base, idx, scale, off); 754 } 755 } 756 757 static int x64_native_part_width(u32 size) { 758 return size == 1u || size == 2u || size == 4u || size == 8u; 759 } 760 761 /* The two integer registers in the backend-private bank. Exact-width helpers 762 * assign them explicit address/data roles for their complete local phase. */ 763 static int x64_private_int_reg(Reg reg) { 764 return reg == X64_TMP_INT || reg == X64_TMP_INT2; 765 } 766 767 static Reg x64_other_private_int(Reg reg) { 768 return reg == X64_TMP_INT ? X64_TMP_INT2 : X64_TMP_INT; 769 } 770 771 /* Resolve the complete effective address into one explicitly selected private 772 * register. Exact-width multi-chunk operations keep this address stable while 773 * their other private register carries chunk data; callers no longer depend on 774 * x64_emit_mem's internal address-scratch choice. */ 775 static NativeAddr x64_stabilize_part_addr(X64NativeTarget* a, NativeAddr addr, 776 Reg addr_reg) { 777 u32 base; 778 KitCgTypeId base_type; 779 base_type = addr.base_type; 780 base = x64_addr_to_base_reg(a, addr, addr_reg); 781 if (base != addr_reg) emit_mov_rr(a->base.mc, 1, addr_reg, base); 782 memset(&addr, 0, sizeof addr); 783 addr.base_kind = NATIVE_ADDR_BASE_REG; 784 addr.cls = NATIVE_REG_INT; 785 addr.base.reg = addr_reg; 786 addr.base_type = base_type; 787 return addr; 788 } 789 790 /* Load an exact-width little-endian integer ABI part. x86 has native memory 791 * operations only for 1/2/4/8 bytes, so a 3/5/6/7-byte tail is assembled from 792 * bounded chunks in the low bits of dst. No chunk crosses the semantic part. */ 793 static void x64_load_int_part(X64NativeTarget* a, NativeLoc dst, 794 NativeAddr addr, MemAccess mem, u32 size) { 795 NativePartChunkIter chunks; 796 NativeTarget* t = &a->base; 797 Reg rd = (Reg)loc_reg(dst); 798 Reg addr_reg; 799 Reg data_reg; 800 NativeLoc tmp; 801 u32 chunk_off, chunk_size; 802 int private_dst; 803 int first = 1; 804 805 if (x64_native_part_width(size)) { 806 mem.size = size; 807 x64_emit_mem(a, 1, dst, addr, mem); 808 return; 809 } 810 if (size == 0u || size > 8u || native_loc_is_fp(dst)) 811 x64_panic(a, "invalid exact-width integer part load"); 812 813 private_dst = x64_private_int_reg(rd); 814 addr_reg = private_dst ? x64_other_private_int(rd) : X64_TMP_INT2; 815 data_reg = private_dst ? addr_reg : X64_TMP_INT; 816 addr = x64_stabilize_part_addr(a, addr, addr_reg); 817 /* A private destination consumes both integer temps (one accumulator, one 818 * chunk destination). Keep the stable address in the private FP hold register 819 * and restore it immediately before each alias-safe memory load. */ 820 if (private_dst) 821 emit_sse_rr_w(t->mc, 0x66, 0x6E, 1, X64_TMP_FP, addr_reg); 822 tmp = native_loc_reg(builtin_id(KIT_CG_BUILTIN_I64), NATIVE_REG_INT, 823 data_reg); 824 chunks = native_part_chunks(size, 8u); 825 while (native_part_chunk_next(&chunks, &chunk_off, &chunk_size)) { 826 NativeAddr chunk_addr = addr; 827 NativeLoc chunk_dst = first ? dst : tmp; 828 chunk_addr.offset += (i32)chunk_off; 829 mem.size = chunk_size; 830 if (private_dst) 831 emit_sse_rr_w(t->mc, 0x66, 0x7E, 1, X64_TMP_FP, addr_reg); 832 x64_emit_mem(a, 1, chunk_dst, chunk_addr, mem); 833 if (!first) { 834 emit_shift_imm(t->mc, 1, X64_SHIFT_SUB_SHL, data_reg, 835 (u8)(chunk_off * 8u)); 836 emit_alu_rr(t->mc, 1, X64_OPC_ALU_OR, rd, data_reg); 837 } 838 first = 0; 839 } 840 } 841 842 /* Store exactly the semantic bytes carried in an integer ABI register. The 843 * first chunk uses the source directly; later chunks use a shifted private 844 * copy, preserving both the source register and adjacent destination bytes. */ 845 static void x64_store_int_part(X64NativeTarget* a, NativeAddr addr, 846 NativeLoc src, MemAccess mem, u32 size) { 847 NativePartChunkIter chunks; 848 NativeTarget* t = &a->base; 849 Reg rs = (Reg)loc_reg(src); 850 Reg addr_reg; 851 Reg data_reg; 852 NativeLoc tmp; 853 u32 chunk_off, chunk_size, shifted = 0; 854 int private_src; 855 int copied = 0; 856 857 if (x64_native_part_width(size)) { 858 mem.size = size; 859 x64_emit_mem(a, 0, src, addr, mem); 860 return; 861 } 862 if (size == 0u || size > 8u || native_loc_is_fp(src)) 863 x64_panic(a, "invalid exact-width integer part store"); 864 865 private_src = x64_private_int_reg(rs); 866 addr_reg = private_src ? x64_other_private_int(rs) : X64_TMP_INT2; 867 data_reg = private_src ? rs : X64_TMP_INT; 868 /* Preserve a backend-private source while complete address stabilization is 869 * free to use both integer temps. Restore it after the stores as well: these 870 * helpers promise not to consume their source even when another backend 871 * marshalling phase supplied it from the private bank. */ 872 if (private_src) 873 emit_sse_rr_w(t->mc, 0x66, 0x6E, 1, X64_TMP_FP, rs); 874 addr = x64_stabilize_part_addr(a, addr, addr_reg); 875 if (private_src) 876 emit_sse_rr_w(t->mc, 0x66, 0x7E, 1, X64_TMP_FP, data_reg); 877 tmp = native_loc_reg(builtin_id(KIT_CG_BUILTIN_I64), NATIVE_REG_INT, 878 data_reg); 879 chunks = native_part_chunks(size, 8u); 880 while (native_part_chunk_next(&chunks, &chunk_off, &chunk_size)) { 881 NativeAddr chunk_addr = addr; 882 NativeLoc chunk_src = src; 883 chunk_addr.offset += (i32)chunk_off; 884 if (chunk_off != 0u) { 885 if (!copied) { 886 emit_mov_rr(t->mc, 1, data_reg, rs); 887 copied = 1; 888 } 889 emit_shift_imm(t->mc, 1, X64_SHIFT_SUB_SHR, data_reg, 890 (u8)((chunk_off - shifted) * 8u)); 891 shifted = chunk_off; 892 chunk_src = tmp; 893 } 894 mem.size = chunk_size; 895 x64_emit_mem(a, 0, chunk_src, chunk_addr, mem); 896 } 897 if (private_src) 898 emit_sse_rr_w(t->mc, 0x66, 0x7E, 1, X64_TMP_FP, rs); 899 } 900 901 /* ============================ moves / data ============================ */ 902 903 static void x64_move(NativeTarget* t, NativeLoc dst, NativeLoc src) { 904 MCEmitter* mc = t->mc; 905 int dfp = native_loc_is_fp(dst), sfp = native_loc_is_fp(src); 906 u32 rd = loc_reg(dst), rs = loc_reg(src); 907 if (dfp && sfp) { 908 if (rd == rs) return; 909 emit_sse_rr(mc, sse_scalar_prefix(loc_size32(t, dst)), 0x10, rd, 910 rs); 911 return; 912 } 913 if (dfp && !sfp) { /* movd/movq gpr -> xmm: 66 0F 6E /r */ 914 int w = loc_size32(t, dst) == 8u; 915 emit_sse_rr_w(mc, 0x66, 0x6E, w, rd, rs); 916 return; 917 } 918 if (!dfp && sfp) { /* movd/movq xmm -> gpr: 66 0F 7E /r (xmm is reg field) */ 919 int w = loc_size32(t, src) == 8u; 920 emit_sse_rr_w(mc, 0x66, 0x7E, w, rs, rd); 921 return; 922 } 923 if (rd == rs) return; 924 emit_mov_rr(mc, loc_is_64(t, dst) ? 1 : 0, rd, rs); 925 } 926 927 /* Preserve an indirect call target below rsp while its arguments are 928 * marshalled. CFA is rbp-based, so this transient balanced pair needs no CFI 929 * adjustment. High registers use the PUSH/POP opcode's REX.B extension. */ 930 static void x64_push_reg(MCEmitter* mc, Reg reg) { 931 if (reg & 8u) emit1(mc, X64_REX_BASE | X64_REX_B); 932 emit1(mc, X64_OPC_PUSH_R | (reg & 7u)); 933 } 934 935 static void x64_pop_reg(MCEmitter* mc, Reg reg) { 936 if (reg & 8u) emit1(mc, X64_REX_BASE | X64_REX_B); 937 emit1(mc, X64_OPC_POP_R | (reg & 7u)); 938 } 939 940 static void x64_load_imm(NativeTarget* t, NativeLoc dst, i64 imm) { 941 x64_emit_load_imm(t->mc, loc_is_64(t, dst) ? 1 : 0, loc_reg(dst), imm); 942 } 943 944 /* FP constant: materialize the bit pattern in a GPR scratch, then movd/movq 945 * into the FPR. Integer constant: plain load_imm. */ 946 static void x64_load_const(NativeTarget* t, NativeLoc dst, ConstBytes cb) { 947 u64 v = 0; 948 u32 i; 949 for (i = 0; i < cb.size && i < 8u; ++i) v |= (u64)cb.bytes[i] << (i * 8u); 950 if (!native_loc_is_fp(dst)) { 951 x64_load_imm(t, dst, (i64)v); 952 return; 953 } 954 x64_emit_load_imm(t->mc, cb.size == 8u, X64_TMP_INT, (i64)v); 955 emit_sse_rr_w(t->mc, 0x66, 0x6E, cb.size == 8u, loc_reg(dst), X64_TMP_INT); 956 } 957 958 static void x64_load_addr(NativeTarget* t, NativeLoc dst, NativeAddr addr) { 959 X64NativeTarget* a = x64_of(t); 960 MCEmitter* mc = t->mc; 961 u32 rd = loc_reg(dst); 962 u32 base, idx, scale; 963 i32 off; 964 if (addr.base_kind == NATIVE_ADDR_BASE_GLOBAL && 965 addr.index_kind == NATIVE_ADDR_INDEX_NONE) { 966 x64_emit_global_lea(t, rd, addr.base.global.sym, 967 addr.base.global.addend + addr.offset); 968 return; 969 } 970 base = x64_resolve_addr(a, &addr, rd, &idx, &scale, &off); 971 if (idx == REG_NONE) { 972 if (base == rd && off == 0) return; /* already &slot in rd */ 973 emit_lea(mc, rd, base, off); 974 return; 975 } 976 /* lea rd, [base + idx*scale + off] */ 977 { 978 u8 buf[16]; 979 u32 n = 0; 980 n += x64_pack_rex(buf + n, 1, rd, idx, base); 981 buf[n++] = X64_OPC_LEA; 982 n += x64_pack_mem_sib(buf + n, rd, base, idx, scale, off); 983 mc_emit_bytes(mc, buf, n); 984 } 985 } 986 987 static void x64_load(NativeTarget* t, NativeLoc dst, NativeAddr addr, 988 MemAccess mem) { 989 u32 size = mem.size ? mem.size : loc_size32(t, dst); 990 if (!x64_native_part_width(size) && size <= 8u) 991 x64_load_int_part(x64_of(t), dst, addr, mem, size); 992 else 993 x64_emit_mem(x64_of(t), 1, dst, addr, mem); 994 } 995 static void x64_store(NativeTarget* t, NativeAddr addr, NativeLoc src, 996 MemAccess mem) { 997 u32 size = mem.size ? mem.size : loc_size32(t, src); 998 if (!x64_native_part_width(size) && size <= 8u) 999 x64_store_int_part(x64_of(t), addr, src, mem, size); 1000 else 1001 x64_emit_mem(x64_of(t), 0, src, addr, mem); 1002 } 1003 1004 /* Resolve an addressable NativeAddr to a bare base register (no index, off 0) 1005 * by emitting an lea into `scratch` when needed. */ 1006 static u32 x64_addr_to_base_reg(X64NativeTarget* a, NativeAddr addr, 1007 u32 scratch) { 1008 MCEmitter* mc = a->base.mc; 1009 u32 base, idx, scale; 1010 i32 off; 1011 if (addr.base_kind == NATIVE_ADDR_BASE_GLOBAL && 1012 addr.index_kind == NATIVE_ADDR_INDEX_NONE) { 1013 x64_emit_global_lea(&a->base, scratch, addr.base.global.sym, 1014 addr.base.global.addend + addr.offset); 1015 return scratch; 1016 } 1017 base = x64_resolve_addr(a, &addr, scratch, &idx, &scale, &off); 1018 if (idx == REG_NONE && off == 0) return base; 1019 if (idx == REG_NONE) { 1020 emit_lea(mc, scratch, base, off); 1021 return scratch; 1022 } 1023 { 1024 u8 buf[16]; 1025 u32 n = 0; 1026 n += x64_pack_rex(buf + n, 1, scratch, idx, base); 1027 buf[n++] = X64_OPC_LEA; 1028 n += x64_pack_mem_sib(buf + n, scratch, base, idx, scale, off); 1029 mc_emit_bytes(mc, buf, n); 1030 } 1031 return scratch; 1032 } 1033 1034 /* Normalize one address at a time, load through R10, preserve the granule in 1035 * XMM14 while the destination is normalized, then restore it to R10 for the 1036 * store. This keeps both effective addresses and the transfer value entirely 1037 * in the backend-private R10/R11/XMM14 bank; no allocator/cache-visible fixed 1038 * register is touched. */ 1039 static void x64_copy_bytes(NativeTarget* t, NativeAddr dst, NativeAddr src, 1040 AggregateAccess access) { 1041 X64NativeTarget* a = x64_of(t); 1042 MCEmitter* mc = t->mc; 1043 u32 n = access.size, i = 0; 1044 while (i < n) { 1045 u32 rem = n - i, s; 1046 NativeAddr sa = src, da = dst; 1047 u32 sb, db; 1048 if (rem >= 8u) { 1049 s = 8u; 1050 } else if (rem >= 4u) { 1051 s = 4u; 1052 } else if (rem >= 2u) { 1053 s = 2u; 1054 } else { 1055 s = 1u; 1056 } 1057 sa.offset += (i32)i; 1058 da.offset += (i32)i; 1059 sb = x64_addr_to_base_reg(a, sa, X64_TMP_INT2); 1060 emit_mov_load(mc, s, 0, X64_TMP_INT, sb, 0); 1061 emit_sse_rr_w(mc, 0x66, 0x6E, s == 8u, X64_TMP_FP, X64_TMP_INT); 1062 db = x64_addr_to_base_reg(a, da, X64_TMP_INT2); 1063 if (db == X64_TMP_INT) { 1064 emit_mov_rr(mc, 1, X64_TMP_INT2, db); 1065 db = X64_TMP_INT2; 1066 } 1067 emit_sse_rr_w(mc, 0x66, 0x7E, s == 8u, X64_TMP_FP, X64_TMP_INT); 1068 emit_mov_store(mc, s, X64_TMP_INT, db, 0); 1069 i += s; 1070 } 1071 } 1072 1073 static void x64_set_bytes(NativeTarget* t, NativeAddr dst, NativeLoc byte_value, 1074 AggregateAccess access) { 1075 X64NativeTarget* a = x64_of(t); 1076 MCEmitter* mc = t->mc; 1077 u32 dr = x64_addr_to_base_reg(a, dst, X64_TMP_INT2); 1078 u32 n = access.size, i = 0; 1079 /* R10 holds the broadcast value and R11 the resolved destination. Keep both 1080 * entirely backend-private: the variable case broadcasts the low byte in 1081 * XMM14 (punpcklbw/punpcklwd/punpckldq), then moves the qword to R10. */ 1082 if (dr == X64_TMP_INT) { 1083 emit_mov_rr(mc, 1, X64_TMP_INT2, dr); 1084 dr = X64_TMP_INT2; 1085 } 1086 if (byte_value.kind == NATIVE_LOC_IMM) { 1087 u8 b = (u8)(byte_value.v.imm & 0xffu); 1088 u64 b64 = b; 1089 b64 |= b64 << 8; 1090 b64 |= b64 << 16; 1091 b64 |= b64 << 32; 1092 x64_emit_load_imm(mc, 1, X64_TMP_INT, (i64)b64); 1093 } else { 1094 emit_mov_rr(mc, 0, X64_TMP_INT, loc_reg(byte_value)); 1095 emit_alu_imm32(mc, 0, X64_ALU_SUB_AND, X64_TMP_INT, 0xff); 1096 emit_sse_rr_w(mc, 0x66, 0x6E, 0, X64_TMP_FP, X64_TMP_INT); 1097 emit_sse_rr(mc, 0x66, 0x60, X64_TMP_FP, X64_TMP_FP); 1098 emit_sse_rr(mc, 0x66, 0x61, X64_TMP_FP, X64_TMP_FP); 1099 emit_sse_rr(mc, 0x66, 0x62, X64_TMP_FP, X64_TMP_FP); 1100 emit_sse_rr_w(mc, 0x66, 0x7E, 1, X64_TMP_FP, X64_TMP_INT); 1101 } 1102 while (i + 8u <= n) { 1103 emit_mov_store(mc, 8, X64_TMP_INT, dr, (i32)i); 1104 i += 8u; 1105 } 1106 while (i + 4u <= n) { 1107 emit_mov_store(mc, 4, X64_TMP_INT, dr, (i32)i); 1108 i += 4u; 1109 } 1110 while (i + 2u <= n) { 1111 emit_mov_store(mc, 2, X64_TMP_INT, dr, (i32)i); 1112 i += 2u; 1113 } 1114 while (i < n) { 1115 emit_mov_store(mc, 1, X64_TMP_INT, dr, (i32)i); 1116 i += 1u; 1117 } 1118 } 1119 1120 /* ============================ bitfields ============================ */ 1121 1122 static void x64_bitfield_load(NativeTarget* t, NativeLoc dst, NativeAddr ra, 1123 BitFieldAccess bf) { 1124 X64NativeTarget* a = x64_of(t); 1125 MCEmitter* mc = t->mc; 1126 u32 storage_bytes = bf.storage.size ? bf.storage.size : 4u; 1127 int w = storage_bytes == 8u ? 1 : 0; 1128 u32 reg_size = w ? 64u : 32u; 1129 u32 lsb = bf.bit_offset; 1130 u32 width = bf.bit_width ? bf.bit_width : 1u; 1131 u32 rd = loc_reg(dst); 1132 u32 base; 1133 ra.offset += (i32)bf.storage_offset; 1134 base = x64_addr_to_base_reg(a, ra, X64_TMP_INT2); 1135 emit_mov_load(mc, storage_bytes, 0, rd, base, 0); 1136 { 1137 u8 left = (u8)(reg_size - lsb - width); 1138 u8 right = (u8)(reg_size - width); 1139 if (left) emit_shift_imm(mc, w, X64_SHIFT_SUB_SHL, rd, left); 1140 if (right) 1141 emit_shift_imm(mc, w, bf.signed_ ? X64_SHIFT_SUB_SAR : X64_SHIFT_SUB_SHR, 1142 rd, right); 1143 } 1144 } 1145 1146 static void x64_bitfield_store(NativeTarget* t, NativeAddr ra, NativeLoc src, 1147 BitFieldAccess bf) { 1148 X64NativeTarget* a = x64_of(t); 1149 MCEmitter* mc = t->mc; 1150 u32 storage_bytes = bf.storage.size ? bf.storage.size : 4u; 1151 int w = storage_bytes == 8u ? 1 : 0; 1152 u32 lsb = bf.bit_offset; 1153 u32 width = bf.bit_width ? bf.bit_width : 1u; 1154 u64 ones = width >= 64u ? ~(u64)0 : (((u64)1 << width) - 1u); 1155 u64 mask = ones << lsb; 1156 u32 src_reg = loc_reg(src); 1157 u32 base; 1158 ra.offset += (i32)bf.storage_offset; 1159 /* Resolve the address before borrowing the fixed value temporaries. A bare 1160 * register address may be returned unchanged, so explicitly stabilize it in 1161 * r11 when the read-modify-write is about to clobber that register. */ 1162 base = x64_addr_to_base_reg(a, ra, X64_TMP_INT2); 1163 if (base == X64_RAX || base == X64_RCX || base == X64_RDX) { 1164 emit_mov_rr(mc, 1, X64_TMP_INT2, base); 1165 base = X64_TMP_INT2; 1166 } 1167 /* A dying source may legally occupy one of the fixed clobber registers. 1168 * Preserve it after address resolution (which may transiently use r10 for a 1169 * spilled index) and before loading the storage/mask into rax/rcx. RDX stays 1170 * live until it has been copied to RCX below, so it needs no staging. */ 1171 if (src_reg == X64_RAX || src_reg == X64_RCX) { 1172 emit_mov_rr(mc, w, X64_TMP_INT, src_reg); 1173 src_reg = X64_TMP_INT; 1174 } 1175 /* rax = storage; rax &= ~mask. */ 1176 emit_mov_load(mc, storage_bytes, 0, X64_RAX, base, 0); 1177 x64_emit_load_imm(mc, w, X64_RCX, (i64)~mask); 1178 emit_alu_rr(mc, w, X64_OPC_ALU_AND, X64_RAX, X64_RCX); 1179 /* rcx = (src & ones) << lsb. */ 1180 emit_mov_rr(mc, w, X64_RCX, src_reg); 1181 x64_emit_load_imm(mc, w, X64_RDX, (i64)ones); 1182 emit_alu_rr(mc, w, X64_OPC_ALU_AND, X64_RCX, X64_RDX); 1183 if (lsb) emit_shift_imm(mc, w, X64_SHIFT_SUB_SHL, X64_RCX, (u8)lsb); 1184 emit_alu_rr(mc, w, X64_OPC_ALU_OR, X64_RAX, X64_RCX); 1185 emit_mov_store(mc, storage_bytes, X64_RAX, base, 0); 1186 } 1187 1188 /* ============================ arithmetic ============================ */ 1189 1190 static void x64_binop(NativeTarget* t, BinOp op, NativeLoc dst, NativeLoc aop, 1191 NativeLoc bop) { 1192 X64NativeTarget* a = x64_of(t); 1193 MCEmitter* mc = t->mc; 1194 u32 rd = loc_reg(dst); 1195 1196 /* FP binops: two-address. dst = aop op bop. */ 1197 if (op == BO_FADD || op == BO_FSUB || op == BO_FMUL || op == BO_FDIV) { 1198 u32 ra = loc_reg(aop), rb = loc_reg(bop); 1199 u8 prefix = sse_scalar_prefix(loc_size32(t, dst)); 1200 u8 opcode; 1201 switch (op) { 1202 case BO_FADD: 1203 opcode = 0x58; 1204 break; 1205 case BO_FSUB: 1206 opcode = 0x5C; 1207 break; 1208 case BO_FMUL: 1209 opcode = 0x59; 1210 break; 1211 default: 1212 opcode = 0x5E; 1213 break; /* BO_FDIV */ 1214 } 1215 if (rd == rb && rd != ra) { 1216 if (op == BO_FADD || op == BO_FMUL) { /* commutative */ 1217 emit_sse_rr(mc, prefix, opcode, rd, ra); 1218 return; 1219 } 1220 /* non-commutative dst==rb: stage rb in fp scratch. */ 1221 emit_sse_rr(mc, prefix, 0x10, X64_TMP_FP2, rb); 1222 emit_sse_rr(mc, prefix, 0x10, rd, ra); 1223 emit_sse_rr(mc, prefix, opcode, rd, X64_TMP_FP2); 1224 return; 1225 } 1226 if (rd != ra) emit_sse_rr(mc, prefix, 0x10, rd, ra); 1227 emit_sse_rr(mc, prefix, opcode, rd, rb); 1228 return; 1229 } 1230 1231 { 1232 int w = loc_is_64(t, dst) ? 1 : 0; 1233 int b_imm = bop.kind == NATIVE_LOC_IMM; 1234 i64 imm = b_imm ? bop.v.imm : 0; 1235 u32 ra = loc_reg(aop); 1236 1237 /* Division: rax/rdx implicit; divisor must avoid rax/rdx. */ 1238 if (op == BO_SDIV || op == BO_UDIV || op == BO_SREM || op == BO_UREM) { 1239 u32 rb; 1240 if (b_imm) { 1241 x64_emit_load_imm(mc, w, X64_R11, imm); 1242 rb = X64_R11; 1243 } else { 1244 rb = loc_reg(bop); 1245 /* Preserve the divisor before placing the dividend in RAX. This is a 1246 * real parallel-copy edge: a dying divisor may be in RAX while the 1247 * dividend is elsewhere, or in RDX before CQO/XOR clears it. */ 1248 if (rb == X64_RAX || rb == X64_RDX) { 1249 emit_mov_rr(mc, w, X64_R11, rb); 1250 rb = X64_R11; 1251 } 1252 } 1253 if (ra != X64_RAX) emit_mov_rr(mc, w, X64_RAX, ra); 1254 if (op == BO_SDIV || op == BO_SREM) { 1255 emit_cqo_or_cdq(mc, w); 1256 emit_f7_rm(mc, w, X64_F7_SUB_IDIV, rb); 1257 } else { 1258 emit_xor_self(mc, w, X64_RDX); 1259 emit_f7_rm(mc, w, X64_F7_SUB_DIV, rb); 1260 } 1261 { 1262 u32 result = (op == BO_SREM || op == BO_UREM) ? X64_RDX : X64_RAX; 1263 if (rd != result) emit_mov_rr(mc, w, rd, result); 1264 } 1265 return; 1266 } 1267 1268 /* Shifts: count in CL or imm8. */ 1269 if (op == BO_SHL || op == BO_SHR_U || op == BO_SHR_S) { 1270 u32 sub = (op == BO_SHL) ? X64_SHIFT_SUB_SHL 1271 : (op == BO_SHR_U) ? X64_SHIFT_SUB_SHR 1272 : X64_SHIFT_SUB_SAR; 1273 if (b_imm) { 1274 u32 wbits = w ? 64u : 32u; 1275 if (rd != ra) emit_mov_rr(mc, w, rd, ra); 1276 emit_shift_imm(mc, w, sub, rd, (u8)((u64)imm & (wbits - 1u))); 1277 return; 1278 } 1279 { 1280 u32 rb = loc_reg(bop); 1281 /* RCX cannot simultaneously hold a distinct result and the shift 1282 * count. Compute through backend-private R10, then publish the result 1283 * after the instruction has consumed CL. */ 1284 if (rd == X64_RCX && (ra != X64_RCX || rb != X64_RCX)) { 1285 emit_mov_rr(mc, w, X64_TMP_INT, ra); 1286 if (rb != X64_RCX) emit_mov_rr(mc, 0, X64_RCX, rb); 1287 emit_shift_cl(mc, w, sub, X64_TMP_INT); 1288 emit_mov_rr(mc, w, X64_RCX, X64_TMP_INT); 1289 return; 1290 } 1291 /* Place the count in cl and the value in dst. Stage the count through 1292 * r11 first so neither move clobbers the other when the value already 1293 * sits in rcx or the count sits in dst. (The optimizer additionally 1294 * keeps values live across the shift out of rcx — see 1295 * x64_machine_op_clobbers.) */ 1296 if (rb != X64_RCX) { 1297 emit_mov_rr(mc, 0, X64_TMP_INT2, rb); 1298 if (rd != ra) emit_mov_rr(mc, w, rd, ra); 1299 emit_mov_rr(mc, 0, X64_RCX, X64_TMP_INT2); 1300 } else if (rd != ra) { 1301 emit_mov_rr(mc, w, rd, ra); 1302 } 1303 } 1304 emit_shift_cl(mc, w, sub, rd); 1305 return; 1306 } 1307 1308 /* IMM-form fast paths (b_imm guaranteed legal by imm_legal: imm32). */ 1309 if (b_imm && (op == BO_IADD || op == BO_ISUB || op == BO_AND || 1310 op == BO_OR || op == BO_XOR || op == BO_IMUL)) { 1311 if (op == BO_IMUL) { 1312 if (imm_fits_i8(imm)) { 1313 emit_imul_imm8(mc, w, rd, ra, (i8)imm); 1314 return; 1315 } 1316 emit_imul_imm32(mc, w, rd, ra, (i32)imm); 1317 return; 1318 } 1319 { 1320 u32 sub; 1321 switch (op) { 1322 case BO_IADD: 1323 sub = X64_ALU_SUB_ADD; 1324 break; 1325 case BO_OR: 1326 sub = X64_ALU_SUB_OR; 1327 break; 1328 case BO_AND: 1329 sub = X64_ALU_SUB_AND; 1330 break; 1331 case BO_ISUB: 1332 sub = X64_ALU_SUB_SUB; 1333 break; 1334 default: 1335 sub = X64_ALU_SUB_XOR; 1336 break; /* BO_XOR */ 1337 } 1338 if (rd != ra) emit_mov_rr(mc, w, rd, ra); 1339 if (imm_fits_i8(imm)) 1340 emit_alu_imm8(mc, w, sub, rd, (i8)imm); 1341 else 1342 emit_alu_imm32(mc, w, sub, rd, (i32)imm); 1343 return; 1344 } 1345 } 1346 1347 /* Generic 2-operand ALU: dst = ra op rb. Preserve rb if dst == rb. */ 1348 { 1349 u32 rb = loc_reg(bop); 1350 if (rd == rb && rd != ra) { 1351 switch (op) { 1352 case BO_IADD: 1353 emit_alu_rr(mc, w, X64_OPC_ALU_ADD, rd, ra); 1354 return; 1355 case BO_AND: 1356 emit_alu_rr(mc, w, X64_OPC_ALU_AND, rd, ra); 1357 return; 1358 case BO_OR: 1359 emit_alu_rr(mc, w, X64_OPC_ALU_OR, rd, ra); 1360 return; 1361 case BO_XOR: 1362 emit_alu_rr(mc, w, X64_OPC_ALU_XOR, rd, ra); 1363 return; 1364 case BO_IMUL: 1365 emit_imul_rr(mc, w, rd, ra); 1366 return; 1367 default: 1368 break; /* ISUB falls through: stage rb */ 1369 } 1370 emit_mov_rr(mc, w, X64_R11, rb); 1371 rb = X64_R11; 1372 } 1373 if (rd != ra) emit_mov_rr(mc, w, rd, ra); 1374 switch (op) { 1375 case BO_IADD: 1376 emit_alu_rr(mc, w, X64_OPC_ALU_ADD, rd, rb); 1377 break; 1378 case BO_ISUB: 1379 emit_alu_rr(mc, w, X64_OPC_ALU_SUB, rd, rb); 1380 break; 1381 case BO_AND: 1382 emit_alu_rr(mc, w, X64_OPC_ALU_AND, rd, rb); 1383 break; 1384 case BO_OR: 1385 emit_alu_rr(mc, w, X64_OPC_ALU_OR, rd, rb); 1386 break; 1387 case BO_XOR: 1388 emit_alu_rr(mc, w, X64_OPC_ALU_XOR, rd, rb); 1389 break; 1390 case BO_IMUL: 1391 emit_imul_rr(mc, w, rd, rb); 1392 break; 1393 default: 1394 x64_panic(a, "unsupported binop"); 1395 } 1396 } 1397 } 1398 } 1399 1400 /* FP sign-mask constant materialized in fp scratch for FNEG. */ 1401 static void x64_unop(NativeTarget* t, UnOp op, NativeLoc dst, NativeLoc src) { 1402 X64NativeTarget* a = x64_of(t); 1403 MCEmitter* mc = t->mc; 1404 u32 rd = loc_reg(dst), rs = loc_reg(src); 1405 if (op == UO_FNEG) { 1406 int dbl = loc_size32(t, dst) == 8u; 1407 if (rd != rs) 1408 emit_sse_rr(mc, sse_scalar_prefix(dbl ? 8u : 4u), 0x10, rd, rs); 1409 /* sign mask into fp scratch via gpr, then XORPS/XORPD. */ 1410 x64_emit_load_imm(mc, dbl, X64_TMP_INT, 1411 dbl ? (i64)0x8000000000000000ull : (i64)0x80000000ull); 1412 emit_sse_rr_w(mc, 0x66, 0x6E, dbl, X64_TMP_FP2, X64_TMP_INT); 1413 emit_sse_rr(mc, dbl ? 0x66 : 0, 0x57, rd, X64_TMP_FP2); 1414 return; 1415 } 1416 { 1417 int w = loc_is_64(t, dst) ? 1 : 0; 1418 switch (op) { 1419 case UO_NEG: 1420 if (rd != rs) emit_mov_rr(mc, w, rd, rs); 1421 emit_f7_rm(mc, w, X64_F7_SUB_NEG, rd); 1422 return; 1423 case UO_BNOT: 1424 if (rd != rs) emit_mov_rr(mc, w, rd, rs); 1425 emit_f7_rm(mc, w, X64_F7_SUB_NOT, rd); 1426 return; 1427 case UO_NOT: 1428 /* !x -> (x == 0) as 0/1. */ 1429 emit_test_self(mc, w, rs); 1430 emit_setcc(mc, X64_CC_E, rd); 1431 emit_movzx_r32_r8(mc, rd, rd); 1432 return; 1433 default: 1434 x64_panic(a, "unsupported unop"); 1435 } 1436 } 1437 } 1438 1439 /* ============================ compares ============================ */ 1440 1441 static u32 cmp_to_cc(CmpOp op) { 1442 switch (op) { 1443 case CMP_EQ: 1444 return X64_CC_E; 1445 case CMP_NE: 1446 return X64_CC_NE; 1447 case CMP_LT_U: 1448 return X64_CC_B; 1449 case CMP_LE_U: 1450 return X64_CC_BE; 1451 case CMP_GT_U: 1452 return X64_CC_A; 1453 case CMP_GE_U: 1454 return X64_CC_AE; 1455 case CMP_LT_S: 1456 return X64_CC_L; 1457 case CMP_LE_S: 1458 return X64_CC_LE; 1459 case CMP_GT_S: 1460 return X64_CC_G; 1461 case CMP_GE_S: 1462 return X64_CC_GE; 1463 default: 1464 return X64_CC_E; 1465 } 1466 } 1467 1468 static int cmp_is_fp(CmpOp op, NativeLoc aop) { 1469 /* FP-ness is self-describing from the opcode; FP eq/ne are distinct opcodes 1470 * (CMP_OEQ_F/CMP_UNE_F), so no operand-class sniffing is needed. */ 1471 (void)aop; 1472 return op >= CMP_OEQ_F; 1473 } 1474 1475 /* Emit `cmp ra, rb` (or ucomis[sd] for FP), setting flags from ra - rb. */ 1476 static void x64_emit_cmp_flags(NativeTarget* t, NativeLoc aop, NativeLoc bop, 1477 int fp) { 1478 X64NativeTarget* a = x64_of(t); 1479 MCEmitter* mc = t->mc; 1480 if (fp) { 1481 u8 prefix = loc_size32(t, aop) == 8u ? 0x66u : 0u; 1482 emit_sse_rr(mc, prefix, 0x2E, loc_reg(aop), loc_reg(bop)); /* ucomis */ 1483 return; 1484 } 1485 { 1486 int w = loc_is_64(t, aop) ? 1 : 0; 1487 u32 ra = loc_reg(aop); 1488 if (bop.kind == NATIVE_LOC_IMM) { 1489 i64 imm = bop.v.imm; 1490 if (imm_fits_i8(imm)) 1491 emit_alu_imm8(mc, w, X64_ALU_SUB_CMP, ra, (i8)imm); 1492 else 1493 emit_alu_imm32(mc, w, X64_ALU_SUB_CMP, ra, (i32)imm); 1494 return; 1495 } 1496 emit_alu_rr(mc, w, X64_OPC_ALU_CMP, ra, loc_reg(bop)); 1497 (void)a; 1498 } 1499 } 1500 1501 /* FP ordered setcc: result = (primary cc) && !unordered (NP). */ 1502 static void x64_fp_setcc_ordered(NativeTarget* t, u32 primary, u32 dst) { 1503 MCEmitter* mc = t->mc; 1504 emit_setcc(mc, primary, dst); 1505 emit_movzx_r32_r8(mc, dst, dst); 1506 emit_setcc(mc, X64_CC_NP, X64_R11); 1507 emit_movzx_r32_r8(mc, X64_R11, X64_R11); 1508 emit_alu_rr(mc, 0, X64_OPC_ALU_AND, dst, X64_R11); 1509 } 1510 1511 /* FP unordered predicate: result = (primary cc) || unordered (P). */ 1512 static void x64_fp_setcc_unord(NativeTarget* t, u32 primary, u32 dst) { 1513 MCEmitter* mc = t->mc; 1514 emit_setcc(mc, primary, dst); 1515 emit_movzx_r32_r8(mc, dst, dst); 1516 emit_setcc(mc, X64_CC_P, X64_R11); 1517 emit_movzx_r32_r8(mc, X64_R11, X64_R11); 1518 emit_alu_rr(mc, 0, X64_OPC_ALU_OR, dst, X64_R11); 1519 } 1520 1521 static void x64_cmp(NativeTarget* t, CmpOp op, NativeLoc dst, NativeLoc aop, 1522 NativeLoc bop) { 1523 MCEmitter* mc = t->mc; 1524 u32 d = loc_reg(dst); 1525 int fp = cmp_is_fp(op, aop); 1526 x64_emit_cmp_flags(t, aop, bop, fp); 1527 if (fp) { 1528 /* ucomis sets ZF/CF and, when unordered (NaN), also PF. Each predicate's 1529 * flag formula is built explicitly (NOT blindly as !(opposite)): 1530 * ordered: E/B/BE alias {==,<,<=} only when also NP (not-parity); 1531 * NE/A/AE already exclude unordered, so they stand alone. 1532 * unordered: E/B/BE already include the unordered case (ZF/CF set on 1533 * NaN), so they stand alone; NE/A/AE need an OR with P. */ 1534 switch (op) { 1535 /* ordered: require not-unordered (NP) on the equality-flag cases */ 1536 case CMP_OEQ_F: 1537 x64_fp_setcc_ordered(t, X64_CC_E, d); 1538 return; 1539 case CMP_OLT_F: 1540 x64_fp_setcc_ordered(t, X64_CC_B, d); 1541 return; 1542 case CMP_OLE_F: 1543 x64_fp_setcc_ordered(t, X64_CC_BE, d); 1544 return; 1545 case CMP_ONE_F: 1546 emit_setcc(mc, X64_CC_NE, d); 1547 break; 1548 case CMP_OGT_F: 1549 emit_setcc(mc, X64_CC_A, d); 1550 break; 1551 case CMP_OGE_F: 1552 emit_setcc(mc, X64_CC_AE, d); 1553 break; 1554 /* unordered: OR-with-P on the cases that exclude unordered */ 1555 case CMP_UEQ_F: 1556 emit_setcc(mc, X64_CC_E, d); 1557 break; 1558 case CMP_ULT_F: 1559 emit_setcc(mc, X64_CC_B, d); 1560 break; 1561 case CMP_ULE_F: 1562 emit_setcc(mc, X64_CC_BE, d); 1563 break; 1564 case CMP_UNE_F: 1565 x64_fp_setcc_unord(t, X64_CC_NE, d); 1566 return; 1567 case CMP_UGT_F: 1568 x64_fp_setcc_unord(t, X64_CC_A, d); 1569 return; 1570 case CMP_UGE_F: 1571 x64_fp_setcc_unord(t, X64_CC_AE, d); 1572 return; 1573 default: 1574 emit_setcc(mc, cmp_to_cc(op), d); 1575 break; 1576 } 1577 emit_movzx_r32_r8(mc, d, d); 1578 return; 1579 } 1580 emit_setcc(mc, cmp_to_cc(op), d); 1581 emit_movzx_r32_r8(mc, d, d); 1582 } 1583 1584 /* ============================ converts ============================ */ 1585 1586 static void x64_convert(NativeTarget* t, ConvKind k, NativeLoc dst, 1587 NativeLoc src) { 1588 X64NativeTarget* a = x64_of(t); 1589 MCEmitter* mc = t->mc; 1590 u32 rd = loc_reg(dst), rs = loc_reg(src); 1591 switch (k) { 1592 case CV_SEXT: { 1593 u32 src_sz = loc_size32(t, src); 1594 int w = loc_is_64(t, dst) ? 1 : 0; 1595 emit_extend_rr(mc, w, 1, src_sz, rd, rs); 1596 return; 1597 } 1598 case CV_ZEXT: { 1599 u32 src_sz = loc_size32(t, src); 1600 int w = loc_is_64(t, dst) ? 1 : 0; 1601 emit_extend_rr(mc, w, 0, src_sz, rd, rs); 1602 return; 1603 } 1604 case CV_TRUNC: 1605 emit_mov_rr(mc, 0, rd, rs); /* low 32 bits; clears high */ 1606 return; 1607 case CV_ITOF_S: 1608 case CV_ITOF_U: { 1609 int w_src = loc_is_64(t, src) ? 1 : 0; 1610 u8 prefix = sse_scalar_prefix(loc_size32(t, dst)); 1611 if (k == CV_ITOF_U && w_src == 1) { 1612 MCLabel L_high = mc_label_new(mc); 1613 MCLabel L_done = mc_label_new(mc); 1614 emit_test_self(mc, 1, rs); 1615 emit_jcc_rel32(mc, X64_CC_S, L_high); 1616 emit_sse_rr_w(mc, prefix, 0x2A, 1, rd, rs); 1617 emit_jmp_rel32(mc, L_done); 1618 mc_label_place(mc, L_high); 1619 emit_mov_rr(mc, 1, X64_R11, rs); 1620 emit_mov_rr(mc, 1, X64_TMP_INT, rs); 1621 emit_alu_imm8(mc, 1, X64_ALU_SUB_AND, X64_TMP_INT, 1); 1622 emit_shift_imm(mc, 1, X64_SHIFT_SUB_SHR, X64_R11, 1); 1623 emit_alu_rr(mc, 1, X64_OPC_ALU_OR, X64_R11, X64_TMP_INT); 1624 emit_sse_rr_w(mc, prefix, 0x2A, 1, rd, X64_R11); 1625 emit_sse_rr(mc, prefix, 0x58, rd, rd); 1626 mc_label_place(mc, L_done); 1627 return; 1628 } 1629 if (k == CV_ITOF_U) { 1630 emit_extend_rr(mc, 0, 0, 4, X64_R11, rs); /* zext u32 -> 64 */ 1631 rs = X64_R11; 1632 w_src = 1; 1633 } 1634 emit_sse_rr_w(mc, prefix, 0x2A, w_src, rd, rs); 1635 return; 1636 } 1637 case CV_FTOI_S: 1638 case CV_FTOI_U: { 1639 int w_dst = loc_is_64(t, dst) ? 1 : 0; 1640 u8 prefix = sse_scalar_prefix(loc_size32(t, src)); 1641 /* Unsigned 64-bit FTOI needs the 2^63 bias dance; otherwise cvtt 1642 * (with the destination widened to 64 for u32) is exact. */ 1643 if (k == CV_FTOI_U && w_dst == 1) { 1644 int dbl = loc_size32(t, src) == 8u; 1645 MCLabel L_small = mc_label_new(mc); 1646 MCLabel L_done = mc_label_new(mc); 1647 /* limit = 2^63 in fp scratch. */ 1648 x64_emit_load_imm( 1649 mc, 1, X64_R11, 1650 dbl ? (i64)0x43E0000000000000ull : (i64)0x5F000000ull); 1651 emit_sse_rr_w(mc, 0x66, 0x6E, dbl, X64_TMP_FP2, X64_R11); 1652 emit_sse_rr(mc, dbl ? 0x66 : 0, 0x2E, rs, X64_TMP_FP2); /* ucomis */ 1653 emit_jcc_rel32(mc, X64_CC_B, L_small); 1654 emit_sse_rr(mc, prefix, 0x10, X64_TMP_FP, rs); 1655 emit_sse_rr(mc, prefix, 0x5C, X64_TMP_FP, X64_TMP_FP2); /* sub bias */ 1656 emit_sse_rr_w(mc, prefix, 0x2C, 1, rd, X64_TMP_FP); 1657 x64_emit_load_imm(mc, 1, X64_R11, (i64)0x8000000000000000ull); 1658 emit_alu_rr(mc, 1, X64_OPC_ALU_XOR, rd, X64_R11); 1659 emit_jmp_rel32(mc, L_done); 1660 mc_label_place(mc, L_small); 1661 emit_sse_rr_w(mc, prefix, 0x2C, 1, rd, rs); 1662 mc_label_place(mc, L_done); 1663 return; 1664 } 1665 if (k == CV_FTOI_U) w_dst = 1; /* widen u32 result */ 1666 emit_sse_rr_w(mc, prefix, 0x2C, w_dst, rd, rs); 1667 return; 1668 } 1669 case CV_FEXT: 1670 emit_sse_rr(mc, 0xF3, 0x5A, rd, rs); /* cvtss2sd */ 1671 return; 1672 case CV_FTRUNC: 1673 emit_sse_rr(mc, 0xF2, 0x5A, rd, rs); /* cvtsd2ss */ 1674 return; 1675 case CV_BITCAST: 1676 if (!native_loc_is_fp(src) && native_loc_is_fp(dst)) { 1677 emit_sse_rr_w(mc, 0x66, 0x6E, loc_is_64(t, dst), rd, rs); 1678 } else if (native_loc_is_fp(src) && !native_loc_is_fp(dst)) { 1679 emit_sse_rr_w(mc, 0x66, 0x7E, loc_is_64(t, src), rs, rd); 1680 } else { 1681 x64_move(t, dst, src); 1682 } 1683 return; 1684 default: 1685 x64_panic(a, "unsupported convert"); 1686 } 1687 } 1688 1689 /* §E.3 narrow register-only entry points. The NDT crosses 16 B NativeRegLoc 1690 * here; these reconstruct the NativeLoc the fat hook expects and delegate, so 1691 * the emitted bytes are identical. Used only on the -O0 NDT path; the opt 1692 * replay path calls x64_binop/x64_move/x64_cmp/x64_convert directly. */ 1693 static void x64_binop_rr(NativeTarget* t, BinOp op, NativeRegLoc dst, 1694 NativeRegLoc a, NativeRegLoc b) { 1695 x64_binop(t, op, native_loc_from_reg(dst), native_loc_from_reg(a), 1696 native_loc_from_reg(b)); 1697 } 1698 1699 static void x64_move_rr(NativeTarget* t, NativeRegLoc dst, NativeRegLoc src) { 1700 x64_move(t, native_loc_from_reg(dst), native_loc_from_reg(src)); 1701 } 1702 1703 static void x64_cmp_rr(NativeTarget* t, CmpOp op, NativeRegLoc dst, 1704 NativeRegLoc a, NativeRegLoc b) { 1705 x64_cmp(t, op, native_loc_from_reg(dst), native_loc_from_reg(a), 1706 native_loc_from_reg(b)); 1707 } 1708 1709 static void x64_convert_rr(NativeTarget* t, ConvKind op, NativeRegLoc dst, 1710 NativeRegLoc src) { 1711 x64_convert(t, op, native_loc_from_reg(dst), native_loc_from_reg(src)); 1712 } 1713 1714 /* ============================ spill / reload ============================ */ 1715 1716 static void x64_spill(NativeTarget* t, NativeLoc src, NativeFrameSlot slot, 1717 MemAccess mem) { 1718 NativeAddr addr; 1719 memset(&addr, 0, sizeof addr); 1720 addr.base_kind = NATIVE_ADDR_BASE_FRAME; 1721 addr.base.frame = slot; 1722 addr.base_type = src.type; 1723 x64_emit_mem(x64_of(t), 0, src, addr, mem); 1724 } 1725 static void x64_reload(NativeTarget* t, NativeLoc dst, NativeFrameSlot slot, 1726 MemAccess mem) { 1727 NativeAddr addr; 1728 memset(&addr, 0, sizeof addr); 1729 addr.base_kind = NATIVE_ADDR_BASE_FRAME; 1730 addr.base.frame = slot; 1731 addr.base_type = dst.type; 1732 x64_emit_mem(x64_of(t), 1, dst, addr, mem); 1733 } 1734 1735 /* ============================ control flow ============================ */ 1736 1737 static void emit_jmp_rel32(MCEmitter* mc, MCLabel l) { 1738 u8 op = X64_OPC_JMP_REL32; 1739 mc_emit_bytes(mc, &op, 1); 1740 emit_u32le(mc, 0); 1741 mc_emit_label_ref(mc, l, R_PC32, 4, -4); 1742 } 1743 static void emit_jcc_rel32(MCEmitter* mc, u32 cc, MCLabel l) { 1744 u8 op[2] = {X64_OPC_TWOBYTE, (u8)(X64_OPC_JCC_BASE | (cc & 0xfu))}; 1745 mc_emit_bytes(mc, op, 2); 1746 emit_u32le(mc, 0); 1747 mc_emit_label_ref(mc, l, R_PC32, 4, -4); 1748 } 1749 1750 static MCLabel x64_label_new(NativeTarget* t) { 1751 return mc_label_new(t->mc); 1752 } 1753 static void x64_label_place(NativeTarget* t, MCLabel l) { 1754 mc_label_place(t->mc, l); 1755 } 1756 static void x64_jump(NativeTarget* t, MCLabel l) { emit_jmp_rel32(t->mc, l); } 1757 1758 static void x64_cmp_branch(NativeTarget* t, CmpOp op, NativeLoc aop, 1759 NativeLoc bop, MCLabel l) { 1760 MCEmitter* mc = t->mc; 1761 int fp = cmp_is_fp(op, aop); 1762 if (fp) { 1763 /* Materialize the 0/1 result, then branch on nonzero. */ 1764 NativeLoc tmp = 1765 native_loc_reg(builtin_id(KIT_CG_BUILTIN_I32), NATIVE_REG_INT, 1766 X64_TMP_INT); 1767 x64_cmp(t, op, tmp, aop, bop); 1768 emit_test_self(mc, 0, X64_TMP_INT); 1769 emit_jcc_rel32(mc, X64_CC_NE, l); 1770 return; 1771 } 1772 x64_emit_cmp_flags(t, aop, bop, 0); 1773 emit_jcc_rel32(mc, cmp_to_cc(op), l); 1774 } 1775 1776 /* Emit an indirect `call`/`jmp r/m64` (opcode FF) against register `r`. The 1777 * ModRM reg field is the opcode digit: /2 for `call`, /4 for `jmp`. Byte-for- 1778 * byte the open-coded encoding used by x64_indirect_branch, x64_emit_call's 1779 * indirect (r11-staged) site, and x64_emit_tail_site's indirect (r11) site. */ 1780 static void x64_emit_indirect_rm(MCEmitter* mc, u32 r, u32 digit) { 1781 if (r & 8u) { 1782 u8 rex = X64_REX_BASE | X64_REX_B; 1783 mc_emit_bytes(mc, &rex, 1); 1784 } 1785 { 1786 u8 buf[2] = {X64_OP_JMP_RM64, modrm(3u, digit, r & 7u)}; 1787 mc_emit_bytes(mc, buf, 2); 1788 } 1789 } 1790 1791 static void x64_indirect_branch(NativeTarget* t, NativeLoc addr, 1792 const MCLabel* valid_targets, u32 ntargets) { 1793 MCEmitter* mc = t->mc; 1794 u32 r = loc_reg(addr); 1795 (void)valid_targets; 1796 (void)ntargets; 1797 x64_emit_indirect_rm(mc, r, 4u); /* jmp r/m, /4 */ 1798 } 1799 1800 static void x64_load_label_addr(NativeTarget* t, NativeLoc dst, MCLabel l) { 1801 /* `&&label` address-take: `leaq sym(%rip), rd` with an R_PC32 relocation 1802 * against the label's per-block local symbol — same form as a global 1803 * address-take, so a re-encoding assembler recomputes the displacement. 1804 * (A baked disp32 with no reloc would break once clang re-lays-out the 1805 * function.) */ 1806 MCEmitter* mc = t->mc; 1807 u32 rd = loc_reg(dst); 1808 ObjSymId sym = mc_label_symbol(mc, l); 1809 u32 disp_pos; 1810 emit_rex(mc, 1, rd, 0, 0); 1811 { 1812 u8 op = X64_OPC_LEA; 1813 mc_emit_bytes(mc, &op, 1); 1814 } 1815 { 1816 u8 mr = modrm(0u, rd & 7u, 5u); /* [rip + disp32] */ 1817 mc_emit_bytes(mc, &mr, 1); 1818 } 1819 disp_pos = mc_pos(mc); 1820 emit_u32le(mc, 0); 1821 mc_emit_reloc_at(mc, mc->section_id, disp_pos, R_PC32, sym, -4, 1, 0); 1822 } 1823 1824 /* ============================ frame / lifecycle ============================ 1825 */ 1826 1827 static NativeFrameSlot x64_frame_slot(NativeTarget* t, 1828 const NativeFrameSlotDesc* d) { 1829 return native_frame_slot_alloc(&x64_of(t)->frame, d); 1830 } 1831 1832 static void x64_release_frame_slot(NativeTarget* t, NativeFrameSlot slot) { 1833 native_frame_release_slot(&x64_of(t)->frame, slot); 1834 } 1835 1836 static int x64_frame_slot_debug_loc(NativeTarget* t, NativeFrameSlot slot, 1837 CGDebugLoc* out) { 1838 X64NativeTarget* a = x64_of(t); 1839 X64NativeSlot* s; 1840 if (!out) return 0; 1841 memset(out, 0, sizeof *out); 1842 if (slot == NATIVE_FRAME_SLOT_NONE || slot > a->frame.nslots) return 0; 1843 s = x64_slot_get(a, slot); 1844 out->kind = CG_DEBUG_LOC_FRAME; 1845 /* x64 slots live at RBP - off (exactly how the memory-operand path addresses 1846 * them). The hosted dbg snapshot seeds the frame base with RBP, so report 1847 * the RBP-relative offset — mirroring aa64's FP-relative convention. */ 1848 out->v.frame_ofs = -(i32)s->off; 1849 return 1; 1850 } 1851 1852 /* xmm save area base (rbp-relative). XMM saves are 16-aligned. */ 1853 static u32 x64_xmm_base(const X64NativeTarget* a, u32 cs_fp) { 1854 if (cs_fp == 0) return a->frame.cum_off; 1855 return align_up_u32(a->frame.cum_off, 16u); 1856 } 1857 1858 static u32 x64_compute_frame_size(const X64NativeTarget* a, u32 cs_int, 1859 u32 cs_fp) { 1860 u32 xmm_base = x64_xmm_base(a, cs_fp); 1861 u32 raw = a->frame.max_outgoing + cs_int * 8u + cs_fp * 16u + xmm_base; 1862 u32 fs = align_up_u32(raw, 16u); 1863 return fs ? fs : 16u; 1864 } 1865 1866 /* Collect the callee-saves the body actually used. */ 1867 static u32 x64_collect_int_saves(X64NativeTarget* a, Reg* regs) { 1868 u32 n = 0, i; 1869 for (i = 0; i < a->frame.ncallee_saves; ++i) 1870 if (a->frame.callee_saves[i].cls == NATIVE_REG_INT) 1871 regs[n++] = a->frame.callee_saves[i].reg; 1872 return n; 1873 } 1874 static u32 x64_collect_fp_saves(X64NativeTarget* a, Reg* regs) { 1875 u32 n = 0, i; 1876 for (i = 0; i < a->frame.ncallee_saves; ++i) 1877 if (a->frame.callee_saves[i].cls == NATIVE_REG_FP) 1878 regs[n++] = a->frame.callee_saves[i].reg; 1879 return n; 1880 } 1881 1882 /* rbp-relative offset of callee-save slot `idx`. The int GPRs sit below the 1883 * 16-byte-aligned XMM save area (`xmm_base`) and the `n_fp` 16-byte XMM saves; 1884 * the XMMs sit just below `xmm_base`. Single source for the prologue spill, the 1885 * epilogue/tail-site restore, and the CFI offsets — all previously open-coded 1886 * with this exact arithmetic. */ 1887 static inline i32 x64_cs_int_off(u32 xmm_base, u32 n_fp, u32 idx) { 1888 return -(i32)xmm_base - (i32)n_fp * 16 - (i32)(idx + 1u) * 8; 1889 } 1890 static inline i32 x64_cs_fp_off(u32 xmm_base, u32 idx) { 1891 return -(i32)xmm_base - (i32)(idx + 1u) * 16; 1892 } 1893 1894 /* Emit the callee-save restores (reverse spill order: XMMs then GPRs) at the 1895 * current emit cursor. Shared by the function epilogue (x64_func_end) and the 1896 * tail-call site (x64_emit_tail_site); mirrors aa64's aa_emit_callee_restores. 1897 * Frame-size-independent, so it needs only the collected save sets. */ 1898 static void x64_emit_callee_restores(X64NativeTarget* a) { 1899 MCEmitter* mc = a->base.mc; 1900 Reg cs_int[X64_MAX_CS_INT_REGS], cs_fp[X64_MAX_CS_FP_REGS]; 1901 u32 n_int = x64_collect_int_saves(a, cs_int); 1902 u32 n_fp = x64_collect_fp_saves(a, cs_fp); 1903 u32 xmm_base = x64_xmm_base(a, n_fp); 1904 i32 i; 1905 for (i = (i32)n_fp - 1; i >= 0; --i) 1906 emit_sse_load(mc, 0, 0x28, cs_fp[i], X64_RBP, 1907 x64_cs_fp_off(xmm_base, (u32)i)); /* movaps */ 1908 for (i = (i32)n_int - 1; i >= 0; --i) 1909 emit_mov_load(mc, 8, 0, cs_int[i], X64_RBP, 1910 x64_cs_int_off(xmm_base, n_fp, (u32)i)); 1911 } 1912 1913 static ObjSymId x64_chkstk_sym(NativeTarget* t) { 1914 Sym name = pool_intern_slice(t->c->global, SLICE_LIT("__chkstk")); 1915 ObjSymId s = obj_symbol_find(t->obj, name); 1916 if (s != 0) return s; 1917 return obj_symbol(t->obj, name, SB_GLOBAL, SK_UNDEF, OBJ_SEC_NONE, 0, 0); 1918 } 1919 1920 /* Build the prologue byte sequence into buf. Returns bytes written and, when 1921 * the chkstk path fires, the disp32 offset of the call site. When `skip_sub` is 1922 * set (the known-frame slim / red-zone tiers), the `sub rsp` reservation is 1923 * omitted entirely: the frame record is established but no stack is reserved, 1924 * either because the frame is empty (slim) or because the locals/saves live in 1925 * the SysV red zone (redzone_leaf). Callers must only set it when the frame 1926 * needs no reserved region (no alloca, no outgoing args, and — for the red 1927 * zone — a leaf frame <= 128 bytes). */ 1928 static u32 x64_build_prologue(X64NativeTarget* a, u8* buf, u32 cap, 1929 u32 frame_size, const Reg* cs_int, u32 n_int, 1930 const Reg* cs_fp, u32 n_fp, int skip_sub, 1931 u32* chkstk_disp_pos_out) { 1932 u32 wi = 0; 1933 u32 xmm_base = x64_xmm_base(a, n_fp); 1934 u32 i; 1935 /* Page granularity for Windows large-frame probing (0 = no probe needed). 1936 * Win64 reserves >1-page frames through __chkstk; the same ABI capability 1937 * the aarch64 backend reads for its inline probe. */ 1938 u32 probe = abi_stack_probe_interval(a->base.c->abi); 1939 *chkstk_disp_pos_out = (u32)-1; 1940 if (cap < X64_PROLOGUE_BASE_BYTES) 1941 x64_panic(a, "prologue placeholder overflow"); 1942 /* push rbp; mov rbp, rsp. */ 1943 buf[wi++] = (u8)(X64_OPC_PUSH_R | (X64_RBP & 7u)); 1944 buf[wi++] = X64_REX_BASE | X64_REX_W; 1945 buf[wi++] = X64_OPC_MOV_RM_R; 1946 buf[wi++] = modrm(3u, X64_RSP, X64_RBP); 1947 /* sub rsp, frame_size (or chkstk on Win64 large frame); skipped by the slim / 1948 * red-zone tiers, which reserve no stack. */ 1949 if (skip_sub) { 1950 /* no reservation */ 1951 } else if (probe && frame_size > probe) { 1952 if (wi + 13u > cap) x64_panic(a, "prologue placeholder overflow"); 1953 buf[wi++] = (u8)(X64_OPC_MOV_RI | (X64_RAX & 7u)); /* mov eax, imm32 */ 1954 wr_u32_le(buf + wi, frame_size); 1955 wi += 4; 1956 buf[wi++] = X64_OPC_CALL_REL32; 1957 *chkstk_disp_pos_out = wi; 1958 wr_u32_le(buf + wi, 0); 1959 wi += 4; 1960 buf[wi++] = X64_REX_BASE | X64_REX_W; /* sub rsp, rax */ 1961 buf[wi++] = X64_OPC_ALU_SUB; 1962 buf[wi++] = modrm(3u, X64_RAX, X64_RSP); 1963 } else { 1964 if (wi + 7u > cap) x64_panic(a, "prologue placeholder overflow"); 1965 buf[wi++] = X64_REX_BASE | X64_REX_W; 1966 buf[wi++] = X64_OPC_ALU_IMM32; 1967 buf[wi++] = modrm(3u, X64_ALU_SUB_SUB, X64_RSP); 1968 wr_u32_le(buf + wi, frame_size); 1969 wi += 4; 1970 } 1971 /* sret: spill the first int arg reg (destination pointer) into its slot. 1972 * Use the minimal disp encoding (x64_pack_mem) so it matches the body's 1973 * frame stores and the matching epilogue restore — the `cc -S | as` 1974 * round-trip can then reproduce these bytes exactly. The -O0 placeholder is 1975 * NOP-padded to a fixed width, so a shorter prologue is harmless. */ 1976 if (a->has_sret && a->sret_ptr_slot != NATIVE_FRAME_SLOT_NONE) { 1977 X64NativeSlot* s = x64_slot_get(a, a->sret_ptr_slot); 1978 u32 sret_reg = a->abi->int_args[0]; 1979 i32 off = -(i32)s->off; 1980 if (wi + 8u > cap) x64_panic(a, "prologue placeholder overflow"); 1981 buf[wi++] = 1982 (u8)(X64_REX_BASE | X64_REX_W | ((sret_reg & 8u) ? X64_REX_R : 0u)); 1983 buf[wi++] = X64_OPC_MOV_RM_R; 1984 wi += x64_pack_mem(buf + wi, sret_reg & 7u, X64_RBP, off); 1985 } 1986 /* Spill callee-saved GPRs. */ 1987 for (i = 0; i < n_int; ++i) { 1988 u32 reg = cs_int[i]; 1989 i32 off = x64_cs_int_off(xmm_base, n_fp, i); 1990 if (wi + 8u > cap) x64_panic(a, "prologue placeholder overflow"); 1991 buf[wi++] = (u8)(X64_REX_BASE | X64_REX_W | ((reg & 8u) ? X64_REX_R : 0u)); 1992 buf[wi++] = X64_OPC_MOV_RM_R; 1993 wi += x64_pack_mem(buf + wi, reg & 7u, X64_RBP, off); 1994 } 1995 /* Spill callee-saved XMMs (Win64). movaps [rbp+disp], xmm. */ 1996 for (i = 0; i < n_fp; ++i) { 1997 u32 xmm = cs_fp[i]; 1998 i32 off = x64_cs_fp_off(xmm_base, i); 1999 u8 rex = (u8)((xmm & 8u) ? (X64_REX_BASE | X64_REX_R) : 0u); 2000 u32 need = rex ? 9u : 8u; 2001 if (wi + need > cap) x64_panic(a, "prologue placeholder overflow"); 2002 if (rex) buf[wi++] = rex; 2003 buf[wi++] = X64_OPC_TWOBYTE; 2004 buf[wi++] = 0x29; /* MOVAPS r/m128, xmm */ 2005 wi += x64_pack_mem(buf + wi, xmm & 7u, X64_RBP, off); 2006 } 2007 return wi; 2008 } 2009 2010 static u32 x64_signature_stack_bytes(NativeTarget* t, KitCgTypeId fn_type, 2011 int* variadic, u32* nparams); 2012 2013 static void x64_func_begin_common(NativeTarget* t, const CGFuncDesc* fd) { 2014 X64NativeTarget* a = x64_of(t); 2015 MCEmitter* mc = t->mc; 2016 const ABIFuncInfo* abi = abi_cg_func_info(t->c->abi, fd->fn_type); 2017 a->func = fd; 2018 a->loc = fd->loc; 2019 a->abi = x64_abi_for_os(t->c->target.os); 2020 /* Shared frame bookkeeping: clears the slot table, cum_off, max_outgoing, 2021 * callee-save set, and known_frame/has_alloca/frame_final. */ 2022 native_frame_reset(&a->frame); 2023 /* Tail-call legality is queried while the single-pass frontend is emitting 2024 * the body, so derive the complete incoming stack window from the signature 2025 * up front rather than waiting for bind_param to walk every declaration. 2026 * x64_signature_stack_bytes includes Win64 shadow space; keep only the 2027 * reusable argument suffix here because x64_no_tail adds the common prefix 2028 * on both sides of its comparison. */ 2029 a->incoming_stack_size = 2030 x64_signature_stack_bytes(t, fd->fn_type, NULL, NULL) - 2031 a->abi->shadow_space; 2032 a->next_param_int = 0; 2033 a->next_param_fp = 0; 2034 a->next_param_stack = 0; 2035 a->has_sret = (abi && abi->has_sret) ? 1u : 0u; 2036 a->is_variadic = (abi && abi->variadic) ? 1u : 0u; 2037 a->sret_ptr_slot = NATIVE_FRAME_SLOT_NONE; 2038 a->reg_save_slot = NATIVE_FRAME_SLOT_NONE; 2039 a->npatches = 0; 2040 a->nalloca = 0; 2041 a->nbind_moves = 0; 2042 a->slim_frame = 0; 2043 a->redzone_leaf = 0; 2044 /* Single-pass (-O0) reservation: the NDT caches only in its allocable pool's 2045 * bounded callee-saved subset (3 int + 6 fp on Win64), so a far smaller 2046 * placeholder than the optimizer-worst-case X64_PROLOGUE_BYTES suffices. The 2047 * known-frame path overrides prologue_nbytes with its exact length. */ 2048 a->prologue_nbytes = a->abi->shadow_space ? X64_NDT_PROLOGUE_BYTES_WIN64 2049 : X64_NDT_PROLOGUE_BYTES; 2050 2051 mc_set_section(mc, fd->text_section_id); 2052 mc_emit_align(mc, 16, X64_NOP1); 2053 a->func_start = mc_pos(mc); 2054 mc_begin_function(mc, fd->sym, fd->text_section_id, a->func_start); 2055 mc_cfi_startproc(mc); 2056 a->epilogue_label = mc_label_new(mc); 2057 } 2058 2059 /* Reserve the sret-pointer slot and (SysV) the 176-byte variadic reg-save 2060 * area. Advances next_param_int past the sret pointer (a0). */ 2061 static void x64_reserve_entry_saves(X64NativeTarget* a) { 2062 NativeTarget* t = &a->base; 2063 if (a->has_sret) { 2064 NativeFrameSlotDesc sd; 2065 memset(&sd, 0, sizeof sd); 2066 sd.type = builtin_id(KIT_CG_BUILTIN_I64); 2067 sd.size = 8; 2068 sd.align = 8; 2069 sd.kind = NATIVE_FRAME_SLOT_SAVE; 2070 a->sret_ptr_slot = t->frame_slot(t, &sd); 2071 a->next_param_int = 1; 2072 } 2073 if (a->is_variadic && a->abi->emit_sysv_vararg_save) { 2074 NativeFrameSlotDesc rd; 2075 memset(&rd, 0, sizeof rd); 2076 rd.type = builtin_id(KIT_CG_BUILTIN_I64); 2077 rd.size = 176; 2078 rd.align = 8; 2079 rd.kind = NATIVE_FRAME_SLOT_SAVE; 2080 a->reg_save_slot = t->frame_slot(t, &rd); 2081 } 2082 } 2083 2084 static void x64_emit_variadic_reg_saves(X64NativeTarget* a) { 2085 NativeTarget* t = &a->base; 2086 MCEmitter* mc = t->mc; 2087 if (!a->is_variadic) return; 2088 if (a->abi->emit_sysv_vararg_save) { 2089 X64NativeSlot* rs = x64_slot_get(a, a->reg_save_slot); 2090 static const u32 gprs[6] = {X64_RDI, X64_RSI, X64_RDX, 2091 X64_RCX, X64_R8, X64_R9}; 2092 u32 i; 2093 for (i = 0; i < 6u; ++i) 2094 emit_mov_store(mc, 8, gprs[i], X64_RBP, -(i32)rs->off + (i32)(i * 8u)); 2095 for (i = 0; i < 8u; ++i) 2096 emit_sse_store(mc, 0xF2, 0x11, (u32)(X64_XMM0 + i), X64_RBP, 2097 -(i32)rs->off + (i32)(48u + i * 16u)); 2098 return; 2099 } 2100 /* Win64 variadic: spill the 4 GPR arg slots to the home space. */ 2101 emit_mov_store(mc, 8, X64_RCX, X64_RBP, 16); 2102 emit_mov_store(mc, 8, X64_RDX, X64_RBP, 24); 2103 emit_mov_store(mc, 8, X64_R8, X64_RBP, 32); 2104 emit_mov_store(mc, 8, X64_R9, X64_RBP, 40); 2105 } 2106 2107 static void x64_func_begin(NativeTarget* t, const CGFuncDesc* fd) { 2108 X64NativeTarget* a = x64_of(t); 2109 MCEmitter* mc = t->mc; 2110 u32 i; 2111 x64_func_begin_common(t, fd); 2112 a->prologue_pos = mc_pos(mc); 2113 for (i = 0; i < a->prologue_nbytes; ++i) emit1(mc, X64_NOP1); 2114 x64_reserve_entry_saves(a); 2115 x64_emit_variadic_reg_saves(a); 2116 } 2117 2118 /* x64 homes callee-saves below the locals (offsets computed in 2119 * x64_compute_frame_size / x64_build_prologue), not in frame slots, so 2120 * alloc_slots=0: native_frame just records the {reg,cls} set from the masks. */ 2121 static void x64_reserve_callee_saves(NativeTarget* t, const u32* used, 2122 u32 nclasses) { 2123 native_frame_set_callee_saves(&x64_of(t)->frame, used, nclasses, NULL, 0, 0); 2124 } 2125 2126 static int x64_reg_is_callee_int(const X64ABIRegs* abi, Reg r); 2127 static int x64_reg_is_callee_fp(const X64ABIRegs* abi, Reg r); 2128 2129 static u32 x64_live_callee_saved_mask(NativeTarget* t, NativeAllocClass cls) { 2130 X64NativeTarget* a = x64_of(t); 2131 const X64ABIRegs* abi = a->abi ? a->abi : x64_abi_for_os(t->c->target.os); 2132 u32 mask = 0; 2133 for (Reg r = 0; r < 16u; ++r) { 2134 if (cls == NATIVE_REG_INT && x64_reg_is_callee_int(abi, r)) mask |= 1u << r; 2135 if (cls == NATIVE_REG_FP && x64_reg_is_callee_fp(abi, r)) mask |= 1u << r; 2136 } 2137 return mask; 2138 } 2139 2140 static u32 x64_live_caller_saved_mask(NativeTarget* t, NativeAllocClass cls) { 2141 u32 caller_saved = 2142 native_reg_info_flag_mask(t->regs, cls, NATIVE_REG_CALLER_SAVED); 2143 return caller_saved & ~x64_live_callee_saved_mask(t, cls); 2144 } 2145 2146 static void x64_asm_clobber_masks(Compiler* c, SrcLoc loc, const Sym* clobbers, 2147 u32 nclob, u32* int_mask, u32* fp_mask); 2148 2149 /* abi_clobber_masks is shared as native_asm_abi_clobber_masks 2150 * (cg/native_asm.h); it reads the target's live ABI masks. */ 2151 2152 /* Build the callee-saved set the prologue must preserve: the allocator-assigned 2153 * callee-saved registers (frame->callee_saved_used) plus any an inline-asm 2154 * block clobbers. The latter are opaque to the optimizer's operand scan, so it 2155 * forwards the raw clobber names (frame->asm_clobbers) and the arch-neutral 2156 * clobber-ABI sets (frame->asm_clobber_abi_sets); we resolve both into masks 2157 * and keep only the callee-saved ones. x64_reg_is_callee_* follow the live ABI: 2158 * they exclude rbp (handled by the prologue head) and keep the 2159 * reserved-but-callee- saved scratch rbx/r12 (which the caller still expects 2160 * preserved). This is the same register selection the per-block spill used, 2161 * hoisted into the prologue. */ 2162 static int x64_asm_reg_is_callee_saved(NativeTarget* t, NativeAllocClass cls, 2163 Reg r) { 2164 X64NativeTarget* a = x64_of(t); 2165 const X64ABIRegs* abi = a->abi ? a->abi : x64_abi_for_os(t->c->target.os); 2166 return (cls == NATIVE_REG_INT && x64_reg_is_callee_int(abi, r)) || 2167 (cls == NATIVE_REG_FP && x64_reg_is_callee_fp(abi, r)); 2168 } 2169 2170 static u32 x64_known_callee_saves(NativeTarget* t, 2171 const NativeKnownFrameDesc* frame, u32* out, 2172 u32 cap) { 2173 X64NativeTarget* a = x64_of(t); 2174 SrcLoc loc = a->func ? a->func->loc : (SrcLoc){0, 0, 0}; 2175 return native_asm_known_callee_saves( 2176 t, loc, frame, out, cap, x64_asm_clobber_masks, 2177 x64_asm_reg_is_callee_saved); 2178 } 2179 2180 /* Optimizer entry point: the full frame is supplied up front, so the prologue 2181 * is emitted final the moment it is built — no NOP region, no func_end patch 2182 * (x64_func_end skips patching when known_frame). x64_build_prologue emits the 2183 * push rbp / sub rsp / sret spill / callee-save spills; the variadic 2184 * register-save stores are emitted separately, as on the single-pass path. */ 2185 static void x64_func_begin_known_frame(NativeTarget* t, const CGFuncDesc* fd, 2186 const NativeKnownFrameDesc* frame, 2187 NativeFrameSlot* out_slots) { 2188 X64NativeTarget* a = x64_of(t); 2189 MCEmitter* mc = t->mc; 2190 Reg cs_int[X64_MAX_CS_INT_REGS], cs_fp[X64_MAX_CS_FP_REGS]; 2191 u32 n_int, n_fp, frame_size, nbytes, chkstk_disp_pos, i; 2192 u8 buf[X64_PROLOGUE_BYTES_WIN64]; 2193 x64_func_begin_common(t, fd); 2194 a->frame.known_frame = 1; 2195 if (frame) { 2196 u32 cs[NATIVE_REG_CLASS_COUNT]; 2197 u32 ncs = 2198 x64_known_callee_saves(t, frame, cs, NATIVE_REG_CLASS_COUNT); 2199 a->frame.has_alloca = frame->has_alloca; 2200 if (ncs) x64_reserve_callee_saves(t, cs, ncs); 2201 for (i = 0; i < frame->nslots; ++i) { 2202 NativeFrameSlot slot = x64_frame_slot(t, &frame->slots[i]); 2203 if (out_slots) out_slots[i] = slot; 2204 } 2205 x64_reserve_entry_saves(a); 2206 native_frame_note_outgoing(&a->frame, frame->max_outgoing); 2207 } 2208 /* Frame is final: size and offsets are settled, so emit the exact prologue. 2209 */ 2210 n_int = x64_collect_int_saves(a, cs_int); 2211 n_fp = x64_collect_fp_saves(a, cs_fp); 2212 frame_size = x64_compute_frame_size(a, n_int, n_fp); 2213 a->frame_size_final = frame_size; 2214 /* Cost-model tier selection (mirrors aa64's aa_func_begin_known_frame): with 2215 * the frame final before the body, choose the cheapest valid prologue shape. 2216 * Both tiers keep the rbp record and only drop the `sub rsp`, so the 2217 * epilogue/CFI/offset helpers are untouched. x64 needs no 2218 * `fp_at_bottom`-style fold: `push rbp` already folds the sp-move into the 2219 * store. */ 2220 a->slim_frame = a->frame.ncallee_saves == 0 && !a->frame.has_alloca && 2221 a->frame.cum_off == 0 && a->frame.max_outgoing == 0; 2222 /* redzone keeps locals below rsp in the red zone; exclude inline asm, which 2223 * may issue a `call` (clobbering the red zone) the optimizer can't see. slim 2224 * needs no such guard: it has no locals there and the return address lives on 2225 * the stack at [rbp+8], not in a clobberable register. */ 2226 a->redzone_leaf = 2227 !a->slim_frame && 2228 (t->disabled_backend_features & KIT_CG_BACKEND_RED_ZONE) == 0 && 2229 a->abi->shadow_space == 0 && frame && frame->is_leaf && 2230 !frame->has_asm && !a->frame.has_alloca && a->frame.max_outgoing == 0 && 2231 frame_size <= 128u; 2232 a->prologue_pos = mc_pos(mc); 2233 nbytes = x64_build_prologue(a, buf, sizeof buf, frame_size, cs_int, n_int, 2234 cs_fp, n_fp, a->slim_frame || a->redzone_leaf, 2235 &chkstk_disp_pos); 2236 mc_emit_bytes(mc, buf, nbytes); 2237 if (chkstk_disp_pos != (u32)-1) { 2238 ObjSymId chk = x64_chkstk_sym(t); 2239 mc_emit_reloc_at(mc, mc->section_id, a->prologue_pos + chkstk_disp_pos, 2240 R_X64_PLT32, chk, -4, 1, 0); 2241 } 2242 a->prologue_nbytes = nbytes; /* exact length: used for the CFI post offset */ 2243 x64_emit_variadic_reg_saves(a); 2244 native_frame_set_final(&a->frame); 2245 } 2246 2247 static void x64_func_end(NativeTarget* t) { 2248 X64NativeTarget* a = x64_of(t); 2249 MCEmitter* mc = t->mc; 2250 ObjBuilder* obj = t->obj; 2251 ObjSecId sec = a->func->text_section_id; 2252 Reg cs_int[X64_MAX_CS_INT_REGS], cs_fp[X64_MAX_CS_FP_REGS]; 2253 u32 n_int = x64_collect_int_saves(a, cs_int); 2254 u32 n_fp = x64_collect_fp_saves(a, cs_fp); 2255 u32 frame_size = x64_compute_frame_size(a, n_int, n_fp); 2256 u32 xmm_base = x64_xmm_base(a, n_fp); 2257 u32 end; 2258 a->frame_size_final = frame_size; 2259 2260 /* Epilogue. */ 2261 mc_label_place(mc, a->epilogue_label); 2262 x64_emit_callee_restores(a); 2263 emit_leave(mc); 2264 emit_ret(mc); 2265 2266 /* Patch the single-pass prologue placeholder. */ 2267 if (!a->frame.known_frame) { 2268 u8 buf[X64_PROLOGUE_BYTES_WIN64]; 2269 u32 chkstk_disp_pos; 2270 u32 nbytes; 2271 u32 k; 2272 for (k = 0; k < a->prologue_nbytes; ++k) buf[k] = X64_NOP1; 2273 /* Single-pass path never selects a slim/red-zone tier (it cannot know the 2274 * frame up front), so it always emits the full reservation. */ 2275 nbytes = x64_build_prologue(a, buf, a->prologue_nbytes, frame_size, cs_int, 2276 n_int, cs_fp, n_fp, 0, &chkstk_disp_pos); 2277 (void)nbytes; 2278 obj_patch(obj, sec, a->prologue_pos, buf, a->prologue_nbytes); 2279 if (chkstk_disp_pos != (u32)-1) { 2280 ObjSymId chk = x64_chkstk_sym(t); 2281 mc_emit_reloc_at(mc, sec, a->prologue_pos + chkstk_disp_pos, R_X64_PLT32, 2282 chk, -4, 1, 0); 2283 } 2284 } 2285 2286 /* Patch alloca disp32s: lea dst, [rsp + max_outgoing]. */ 2287 { 2288 u32 mo = align_up_u32(a->frame.max_outgoing, 16u); 2289 u32 k; 2290 for (k = 0; k < a->npatches; ++k) { 2291 u8 dbuf[4]; 2292 wr_u32_le(dbuf, mo); 2293 obj_patch(obj, sec, a->patches[k].pos, dbuf, 4); 2294 } 2295 } 2296 2297 /* CFI: after the prologue, CFA = rbp + 16; rbp at cfa-16, ra at cfa-8. */ 2298 { 2299 /* Body starts past the prologue. prologue_nbytes is the reserved NOP-region 2300 * size on the single-pass path and the exact prologue length on the 2301 * known-frame path (set in x64_func_begin_known_frame). */ 2302 u32 post = a->prologue_pos + a->prologue_nbytes; 2303 u32 k; 2304 mc_cfi_set_next_pc_offset(mc, post - a->func_start); 2305 /* CFI register operands are DWARF numbers, which differ from the x86-64 2306 * hardware encoding for rbp/rsp/rsi/rdi/rcx/rdx (e.g. rbp is HW 5 but 2307 * DWARF 6). Map every hardware GPR through x64_dwarf_from_hw_gpr; rip's 2308 * DWARF number (16) is already correct. */ 2309 mc_cfi_def_cfa(mc, x64_dwarf_from_hw_gpr(X64_RBP), 16); 2310 mc_cfi_offset(mc, x64_dwarf_from_hw_gpr(X64_RBP), -16); 2311 mc_cfi_offset(mc, 16u /* rip */, -8); 2312 for (k = 0; k < n_int; ++k) { 2313 i32 off = x64_cs_int_off(xmm_base, n_fp, k); 2314 mc_cfi_offset(mc, x64_dwarf_from_hw_gpr(cs_int[k]), off); 2315 } 2316 } 2317 2318 end = mc_pos(mc); 2319 obj_symbol_define(obj, a->func->sym, sec, (u64)a->func_start, 2320 (u64)(end - a->func_start)); 2321 if (a->func->atomize) 2322 obj_atom_define(obj, sec, a->func_start, end - a->func_start, a->func->sym, 2323 0); 2324 if (mc->debug) debug_func_pc_range(mc->debug, sec, a->func_start, end); 2325 mc_cfi_endproc(mc); 2326 mc_end_function(mc); 2327 a->func = NULL; 2328 } 2329 2330 /* ============================ params / ABI helpers 2331 * ============================ 2332 */ 2333 2334 /* Win64 shares one arg-slot index across int and FP. Keep cursors in lockstep. 2335 */ 2336 static void x64_sync_slot(const X64ABIRegs* abi, u32* next_int, u32* next_fp) { 2337 u32 m; 2338 if (!abi->slot_shared_int_fp) return; 2339 m = *next_int > *next_fp ? *next_int : *next_fp; 2340 *next_int = m; 2341 *next_fp = m; 2342 } 2343 2344 static const ABIArgInfo* x64_param_abi(NativeTarget* t, const ABIFuncInfo* abi, 2345 const NativeCallDesc* desc, u32 i, 2346 ABIArgInfo* scratch) { 2347 int variadic = abi && i >= abi->nparams; 2348 if (abi && i < abi->nparams) return &abi->params[i]; 2349 (void)variadic; 2350 memset(scratch, 0, sizeof *scratch); 2351 scratch->kind = ABI_ARG_DIRECT; 2352 scratch->nparts = 1; 2353 scratch->parts = arena_zarray(t->c->tu, ABIArgPart, 1); 2354 ((ABIArgPart*)scratch->parts)[0].cls = 2355 cg_type_is_float(t->c, desc->args[i].type) ? ABI_CLASS_FP : ABI_CLASS_INT; 2356 ((ABIArgPart*)scratch->parts)[0].loc = ABI_LOC_REG; 2357 ((ABIArgPart*)scratch->parts)[0].size = 2358 native_type_size(t, desc->args[i].type); 2359 ((ABIArgPart*)scratch->parts)[0].align = 2360 native_type_align(t, desc->args[i].type); 2361 return scratch; 2362 } 2363 2364 static KitCgTypeId x64_part_scalar_type(const ABIArgPart* part) { 2365 if (part->cls == ABI_CLASS_FP) 2366 return part->size <= 4u ? builtin_id(KIT_CG_BUILTIN_F32) 2367 : builtin_id(KIT_CG_BUILTIN_F64); 2368 switch (part->size) { 2369 case 1u: 2370 return builtin_id(KIT_CG_BUILTIN_I8); 2371 case 2u: 2372 return builtin_id(KIT_CG_BUILTIN_I16); 2373 case 4u: 2374 return builtin_id(KIT_CG_BUILTIN_I32); 2375 default: 2376 return builtin_id(KIT_CG_BUILTIN_I64); 2377 } 2378 } 2379 2380 /* Is the whole DIRECT arg forced to the stack (not enough reg slots)? */ 2381 static int x64_direct_to_stack(const X64ABIRegs* abi, const ABIArgInfo* ai, 2382 u32 next_int, u32 next_fp) { 2383 u32 need_int, need_fp; 2384 x64_abi_direct_reg_need(ai, &need_int, &need_fp); 2385 return next_int + need_int > abi->n_int_args || 2386 next_fp + need_fp > abi->n_fp_args; 2387 } 2388 2389 /* Raw outgoing argument bytes a call uses, including Win64 shadow space but 2390 * excluding the final call-site alignment padding. */ 2391 static u32 x64_call_stack_raw_size(NativeTarget* t, 2392 const NativeCallDesc* desc) { 2393 const ABIFuncInfo* abi = abi_cg_func_info(t->c->abi, desc->fn_type); 2394 const X64ABIRegs* aregs = x64_abi_for_os(t->c->target.os); 2395 u32 next_int = (abi && abi->has_sret) ? 1u : 0u; 2396 u32 next_fp = 0; 2397 u32 stack = aregs->shadow_space; 2398 u32 i; 2399 x64_sync_slot(aregs, &next_int, &next_fp); 2400 for (i = 0; i < desc->nargs; ++i) { 2401 ABIArgInfo tmp; 2402 const ABIArgInfo* ai = x64_param_abi(t, abi, desc, i, &tmp); 2403 u32 p; 2404 if (ai->kind == ABI_ARG_IGNORE) continue; 2405 if (ai->kind == ABI_ARG_INDIRECT) { 2406 if (next_int < aregs->n_int_args) 2407 ++next_int; 2408 else 2409 stack += 8u; 2410 x64_sync_slot(aregs, &next_int, &next_fp); 2411 continue; 2412 } 2413 if (ai->kind == ABI_ARG_DIRECT && 2414 x64_direct_to_stack(aregs, ai, next_int, next_fp)) { 2415 stack += (u32)ai->nparts * 8u; 2416 continue; 2417 } 2418 for (p = 0; p < ai->nparts; ++p) { 2419 const ABIArgPart* part = &ai->parts[p]; 2420 if (part->cls == ABI_CLASS_FP) { 2421 if (next_fp < aregs->n_fp_args) 2422 ++next_fp; 2423 else 2424 stack += 8u; 2425 } else { 2426 if (next_int < aregs->n_int_args) 2427 ++next_int; 2428 else 2429 stack += 8u; 2430 } 2431 x64_sync_slot(aregs, &next_int, &next_fp); 2432 } 2433 } 2434 return stack; 2435 } 2436 2437 /* Outgoing stack bytes a call uses (16-aligned), per the ABI. */ 2438 static u32 x64_call_stack_size(NativeTarget* t, const NativeCallDesc* desc) { 2439 return align_up_u32(x64_call_stack_raw_size(t, desc), 16u); 2440 } 2441 2442 /* A normal indirect/byval argument can point into the caller's local frame, 2443 * because that frame remains live through a call. A sibling call tears the 2444 * frame down first, so every such payload needs stable storage in the reusable 2445 * incoming argument window. Pack those payloads after the raw outgoing ABI 2446 * slots (using their natural alignment); final call alignment may provide all 2447 * the space, as in a packed three-byte Win64 argument after one stack slot. */ 2448 static u32 x64_tail_call_stack_size(NativeTarget* t, 2449 const NativeCallDesc* desc) { 2450 const ABIFuncInfo* abi = abi_cg_func_info(t->c->abi, desc->fn_type); 2451 u32 payload = x64_call_stack_raw_size(t, desc); 2452 u32 i; 2453 for (i = 0; i < desc->nargs; ++i) { 2454 ABIArgInfo tmp; 2455 const ABIArgInfo* ai = x64_param_abi(t, abi, desc, i, &tmp); 2456 u32 align, size; 2457 if (ai->kind != ABI_ARG_INDIRECT) continue; 2458 align = ai->indirect_align; 2459 if (!align) align = native_type_align(t, desc->args[i].type); 2460 size = native_type_size(t, desc->args[i].type); 2461 payload = align_up_u32(payload, align ? align : 1u); 2462 payload += size; 2463 } 2464 return align_up_u32(payload, 16u); 2465 } 2466 2467 static u32 x64_call_stack_bytes(NativeTarget* t, const NativeCallDesc* desc) { 2468 return (desc->flags & CG_CALL_TAIL) ? x64_tail_call_stack_size(t, desc) 2469 : x64_call_stack_size(t, desc); 2470 } 2471 2472 static u32 x64_signature_stack_bytes(NativeTarget* t, KitCgTypeId fn_type, 2473 int* variadic, u32* nparams) { 2474 const ABIFuncInfo* abi = abi_cg_func_info(t->c->abi, fn_type); 2475 NativeCallDesc d; 2476 if (variadic) *variadic = abi ? (int)abi->variadic : 0; 2477 if (nparams) *nparams = abi ? abi->nparams : 0u; 2478 memset(&d, 0, sizeof d); 2479 d.fn_type = fn_type; 2480 d.nargs = abi ? abi->nparams : 0u; 2481 if (d.nargs) d.args = arena_zarray(t->c->tu, NativeLoc, d.nargs); 2482 return x64_call_stack_size(t, &d); 2483 } 2484 2485 /* Resolve a dereferenceable NativeLoc to its storage address. */ 2486 static NativeAddr x64_storage_addr(X64NativeTarget* a, NativeLoc loc, 2487 u32 offset) { 2488 NativeAddr addr; 2489 if (!native_loc_storage_addr(loc, (i32)offset, &addr)) 2490 x64_panic(a, "location is not storage"); 2491 return addr; 2492 } 2493 2494 static void x64_move_part_reg(NativeTarget* t, NativeLoc dst, NativeLoc src, 2495 u32 size) { 2496 MCEmitter* mc = t->mc; 2497 int dfp = native_loc_is_fp(dst), sfp = native_loc_is_fp(src); 2498 u32 rd = loc_reg(dst), rs = loc_reg(src); 2499 if (dfp && sfp) { 2500 if (size != 4u && size != 8u) 2501 x64_panic(x64_of(t), "invalid FP register part width"); 2502 if (rd != rs) emit_sse_rr(mc, sse_scalar_prefix(size), 0x10, rd, rs); 2503 return; 2504 } 2505 if (dfp && !sfp) { 2506 if (size != 4u && size != 8u) 2507 x64_panic(x64_of(t), "invalid GPR-to-FP part width"); 2508 emit_sse_rr_w(mc, 0x66, 0x6E, size == 8u, rd, rs); 2509 return; 2510 } 2511 if (!dfp && sfp) { 2512 if (size != 4u && size != 8u) 2513 x64_panic(x64_of(t), "invalid FP-to-GPR part width"); 2514 emit_sse_rr_w(mc, 0x66, 0x7E, size == 8u, rs, rd); 2515 return; 2516 } 2517 if (rd != rs) emit_mov_rr(mc, size > 4u, rd, rs); 2518 } 2519 2520 static void x64_load_part(NativeTarget* t, NativeLoc dst, NativeLoc src, 2521 u32 offset, u32 size) { 2522 X64NativeTarget* a = x64_of(t); 2523 if (src.kind == NATIVE_LOC_REG) { 2524 x64_move_part_reg(t, dst, src, size); 2525 return; 2526 } 2527 if (native_loc_addr_role(src) == NATIVE_LOC_ADDR_ROLE_VALUE) { 2528 NativeAddr addr; 2529 if (offset != 0u || size != t->c->target.ptr_size || 2530 !native_loc_address_value(src, &addr)) 2531 x64_panic(a, "split or invalid address-value argument"); 2532 x64_load_addr(t, dst, addr); 2533 return; 2534 } 2535 if (native_loc_addr_role(src) == NATIVE_LOC_ADDR_ROLE_STORAGE) { 2536 NativeAddr addr = x64_storage_addr(a, src, offset); 2537 MemAccess mem = native_mem_for_type(t, dst.type, size); 2538 addr.base_type = dst.type; 2539 if (!x64_native_part_width(size) && size <= 8u) 2540 x64_load_int_part(a, dst, addr, mem, size); 2541 else 2542 x64_emit_mem(a, 1, dst, addr, mem); 2543 return; 2544 } 2545 if (src.kind == NATIVE_LOC_IMM) { 2546 i64 part; 2547 if (!native_loc_imm_part(src, offset, size, &part)) 2548 x64_panic(a, "invalid immediate argument part"); 2549 x64_emit_load_imm(t->mc, size > 4u, loc_reg(dst), part); 2550 return; 2551 } 2552 x64_panic(a, "unsupported part source"); 2553 } 2554 2555 static void x64_store_part(NativeTarget* t, NativeLoc dst, NativeLoc src, 2556 u32 offset, u32 size) { 2557 X64NativeTarget* a = x64_of(t); 2558 if (native_loc_addr_role(dst) == NATIVE_LOC_ADDR_ROLE_STORAGE) { 2559 NativeAddr addr = x64_storage_addr(a, dst, offset); 2560 MemAccess mem = native_mem_for_type(t, src.type, size); 2561 addr.base_type = src.type; 2562 if (!x64_native_part_width(size) && size <= 8u) 2563 x64_store_int_part(a, addr, src, mem, size); 2564 else 2565 x64_emit_mem(a, 0, src, addr, mem); 2566 return; 2567 } 2568 if (dst.kind == NATIVE_LOC_REG) { 2569 x64_move_part_reg(t, dst, src, size); 2570 return; 2571 } 2572 x64_panic(a, "unsupported part destination"); 2573 } 2574 2575 static void x64_addr_of_loc(NativeTarget* t, NativeLoc dst, NativeLoc src) { 2576 NativeAddr addr = x64_storage_addr(x64_of(t), src, 0); 2577 x64_load_addr(t, dst, addr); 2578 } 2579 2580 typedef struct X64CallMarshalCtx { 2581 u32 rsp_bias; 2582 Reg cycle_scratch; 2583 u8 tail; 2584 u8 callee_parked; 2585 } X64CallMarshalCtx; 2586 2587 static void x64_store_outgoing_part(NativeTarget* t, 2588 const X64CallMarshalCtx* call, 2589 u32 stack_off, NativeLoc src, u32 size) { 2590 X64NativeTarget* a = x64_of(t); 2591 NativeAddr addr; 2592 memset(&addr, 0, sizeof addr); 2593 addr.base_kind = NATIVE_ADDR_BASE_REG; 2594 addr.base_type = src.type; 2595 if (call->tail) { 2596 /* A sibling call reuses the caller's frame: its outgoing stack args land in 2597 * the caller's incoming-arg window. `stack_off` already includes the 2598 * shadow-space prefix (the outgoing cursor starts at shadow_space), so the 2599 * window address is [rbp + 16 + stack_off] — the same bytes the tail-callee 2600 * reads once `leave` has restored rsp to the return address. */ 2601 addr.base.reg = X64_RBP; 2602 addr.offset = (i32)(16u + stack_off); 2603 } else { 2604 addr.base.reg = X64_RSP; 2605 /* Keep semantic outgoing offsets anchored at the pre-marshalling rsp. The 2606 * per-call context owns any transient callee-save bias explicitly. */ 2607 addr.offset = (i32)(stack_off + call->rsp_bias); 2608 } 2609 { 2610 MemAccess mem = native_mem_for_type(t, src.type, size); 2611 if (!x64_native_part_width(size) && size <= 8u) 2612 x64_store_int_part(a, addr, src, mem, size); 2613 else 2614 x64_emit_mem(a, 0, src, addr, mem); 2615 } 2616 } 2617 2618 /* A stack carrier contains the same byte representation as aggregate storage, 2619 * so copy a non-power-of-two memory part chunk-by-chunk instead of needlessly 2620 * packing it into a GPR and unpacking it again. Besides being smaller, this 2621 * needs only one private register while an indirect callee may occupy r11. */ 2622 static void x64_copy_outgoing_part(NativeTarget* t, 2623 const X64CallMarshalCtx* call, u32 stack_off, 2624 NativeLoc src, 2625 const ABIArgPart* part) { 2626 NativeAllocClass cls = 2627 part->cls == ABI_CLASS_FP ? NATIVE_REG_FP : NATIVE_REG_INT; 2628 KitCgTypeId pty = x64_part_scalar_type(part); 2629 Reg tmp = cls == NATIVE_REG_FP ? X64_TMP_FP : X64_TMP_INT; 2630 NativeLoc tmpreg = native_loc_reg(pty, cls, tmp); 2631 2632 if (!x64_native_part_width(part->size) && part->size <= 8u && 2633 native_loc_addr_role(src) == NATIVE_LOC_ADDR_ROLE_STORAGE) { 2634 NativePartChunkIter chunks = native_part_chunks(part->size, 8u); 2635 u32 chunk_off, chunk_size; 2636 while (native_part_chunk_next(&chunks, &chunk_off, &chunk_size)) { 2637 x64_load_part(t, tmpreg, src, part->src_offset + chunk_off, chunk_size); 2638 x64_store_outgoing_part(t, call, stack_off + chunk_off, tmpreg, 2639 chunk_size); 2640 } 2641 return; 2642 } 2643 2644 x64_load_part(t, tmpreg, src, part->src_offset, part->size); 2645 x64_store_outgoing_part(t, call, stack_off, tmpreg, part->size); 2646 } 2647 2648 /* NativeTarget bind_param: route incoming param (ABI loc) into dst. */ 2649 static void x64_emit_reg_arg_moves(NativeTarget* t, NativeArgMove* moves, u32 n, 2650 Reg int_scratch); 2651 2652 /* Defer a register-destination param bind for the parallel-copy flush in 2653 * x64_bind_params_end. `src` is the incoming location (an arg register, or a 2654 * NATIVE_LOC_ADDR for an incoming stack slot). */ 2655 static void x64_defer_reg_bind(X64NativeTarget* a, NativeLoc dst, NativeLoc src, 2656 u32 size) { 2657 NativeArgMove* m; 2658 if (a->nbind_moves >= X64_MAX_BIND_MOVES) 2659 x64_panic(a, "too many register parameter binds"); 2660 m = &a->bind_moves[a->nbind_moves++]; 2661 memset(m, 0, sizeof *m); 2662 m->dst = dst; 2663 m->src = src; 2664 m->size = size; 2665 } 2666 2667 /* Incoming stack-arg source as a NATIVE_LOC_ADDR ([rbp + bias + stack_off]). */ 2668 static NativeLoc x64_incoming_stack_loc(KitCgTypeId type, NativeAllocClass cls, 2669 i32 off) { 2670 NativeLoc l; 2671 memset(&l, 0, sizeof l); 2672 l.kind = NATIVE_LOC_ADDR; 2673 l.cls = (u8)cls; 2674 l.type = type; 2675 l.v.addr.base_kind = NATIVE_ADDR_BASE_REG; 2676 l.v.addr.base.reg = X64_RBP; 2677 l.v.addr.base_type = type; 2678 l.v.addr.offset = off; 2679 return l; 2680 } 2681 2682 static void x64_bind_native_param(NativeTarget* t, const CGParamDesc* p, 2683 NativeLoc dst) { 2684 X64NativeTarget* a = x64_of(t); 2685 const ABIFuncInfo* abi = abi_cg_func_info(t->c->abi, a->func->fn_type); 2686 const ABIArgInfo* ai = 2687 p->index < abi->nparams ? &abi->params[p->index] : NULL; 2688 int to_reg = dst.kind == NATIVE_LOC_REG; 2689 /* Incoming stack args sit above the saved rbp + return addr (+16); Win64 2690 * additionally reserves 32B of home space. */ 2691 i32 incoming_bias = (i32)(16u + a->abi->shadow_space); 2692 u32 i; 2693 if (!ai || ai->kind == ABI_ARG_IGNORE) return; 2694 2695 if (ai->kind == ABI_ARG_INDIRECT) { 2696 /* Incoming pointer to a byval copy: load pointer, memcpy into dst frame. */ 2697 u32 ptr_reg; 2698 NativeAddr d_addr, from; 2699 AggregateAccess access; 2700 if (a->next_param_int < a->abi->n_int_args) { 2701 ptr_reg = a->abi->int_args[a->next_param_int++]; 2702 } else { 2703 /* Keep a stack-homed byval pointer out of R10/R11 while copying from 2704 * it. x64_copy_bytes owns that private bank for transfer/address 2705 * resolution; in particular, resolving the frame destination through 2706 * R11 must not overwrite the source pointer between packed chunks. RAX 2707 * is reserved from allocation and has no live entry value here. */ 2708 ptr_reg = X64_RAX; 2709 emit_mov_load(t->mc, 8, 0, ptr_reg, X64_RBP, 2710 incoming_bias + (i32)a->next_param_stack); 2711 a->next_param_stack += 8u; 2712 } 2713 x64_sync_slot(a->abi, &a->next_param_int, &a->next_param_fp); 2714 if (dst.kind != NATIVE_LOC_FRAME) 2715 x64_panic(a, "indirect parameter requires a frame destination"); 2716 memset(&d_addr, 0, sizeof d_addr); 2717 d_addr.base_kind = NATIVE_ADDR_BASE_FRAME; 2718 d_addr.base.frame = dst.v.frame; 2719 d_addr.base_type = p->type; 2720 memset(&from, 0, sizeof from); 2721 from.base_kind = NATIVE_ADDR_BASE_REG; 2722 from.base.reg = ptr_reg; 2723 from.base_type = p->type; 2724 memset(&access, 0, sizeof access); 2725 access.type = p->type; 2726 access.size = p->size ? p->size : (u32)cg_type_size(t->c, p->type); 2727 access.align = p->align ? p->align : native_type_align(t, p->type); 2728 x64_copy_bytes(t, d_addr, from, access); 2729 return; 2730 } 2731 2732 if (ai->kind == ABI_ARG_DIRECT && 2733 x64_direct_to_stack(a->abi, ai, a->next_param_int, a->next_param_fp)) { 2734 /* Whole arg on the stack. */ 2735 for (i = 0; i < ai->nparts; ++i) { 2736 const ABIArgPart* part = &ai->parts[i]; 2737 NativeAllocClass cls = 2738 part->cls == ABI_CLASS_FP ? NATIVE_REG_FP : NATIVE_REG_INT; 2739 NativeLoc isrc = x64_incoming_stack_loc( 2740 p->type, cls, incoming_bias + (i32)a->next_param_stack); 2741 a->next_param_stack += 8u; 2742 if (dst.kind == NATIVE_LOC_NONE) { 2743 /* unused */ 2744 } else if (to_reg) { 2745 /* Defer: a register dst may be another param's incoming reg. */ 2746 x64_defer_reg_bind( 2747 a, 2748 native_loc_reg(dst.type ? dst.type : p->type, 2749 (NativeAllocClass)dst.cls, (Reg)dst.v.reg), 2750 isrc, part->size); 2751 } else { 2752 /* Frame dst: load to scratch then store (memory dst is never a cycle 2753 * source, so emit eagerly — it only reads the incoming slot). */ 2754 Reg tmp = cls == NATIVE_REG_FP ? X64_TMP_FP : X64_TMP_INT; 2755 NativeLoc tloc = native_loc_reg(p->type, cls, tmp); 2756 x64_load_part(t, tloc, isrc, 0, part->size); 2757 x64_store_part( 2758 t, native_loc_stack(p->type, dst.v.frame, (i32)part->src_offset), 2759 tloc, 0, part->size); 2760 } 2761 } 2762 return; 2763 } 2764 2765 for (i = 0; i < ai->nparts; ++i) { 2766 const ABIArgPart* part = &ai->parts[i]; 2767 NativeAllocClass cls = 2768 part->cls == ABI_CLASS_FP ? NATIVE_REG_FP : NATIVE_REG_INT; 2769 NativeLoc 2770 src; /* incoming: arg register, or NATIVE_LOC_ADDR for a stack arg */ 2771 if (cls == NATIVE_REG_FP && a->next_param_fp < a->abi->n_fp_args) { 2772 src = native_loc_reg(p->type, cls, (Reg)(X64_XMM0 + a->next_param_fp++)); 2773 } else if (cls == NATIVE_REG_INT && 2774 a->next_param_int < a->abi->n_int_args) { 2775 src = native_loc_reg(p->type, cls, a->abi->int_args[a->next_param_int++]); 2776 } else { 2777 src = x64_incoming_stack_loc(p->type, cls, 2778 incoming_bias + (i32)a->next_param_stack); 2779 a->next_param_stack += 8u; 2780 } 2781 x64_sync_slot(a->abi, &a->next_param_int, &a->next_param_fp); 2782 if (dst.kind == NATIVE_LOC_NONE) { 2783 /* unused parameter; cursors advanced */ 2784 } else if (to_reg) { 2785 /* Defer the register bind: the allocator may rotate params across the 2786 * incoming arg registers, so a per-param move could clobber a register 2787 * another bind still needs. x64_bind_params_end resolves them together as 2788 * a parallel copy. */ 2789 x64_defer_reg_bind( 2790 a, 2791 native_loc_reg(dst.type ? dst.type : p->type, 2792 (NativeAllocClass)dst.cls, (Reg)dst.v.reg), 2793 src, part->size); 2794 } else if (src.kind == NATIVE_LOC_REG) { 2795 x64_store_part( 2796 t, native_loc_stack(p->type, dst.v.frame, (i32)part->src_offset), src, 2797 0, part->size); 2798 } else { 2799 /* Stack source -> frame dst: load to scratch, then store. */ 2800 Reg tmp = cls == NATIVE_REG_FP ? X64_TMP_FP : X64_TMP_INT; 2801 NativeLoc tloc = native_loc_reg(p->type, cls, tmp); 2802 x64_load_part(t, tloc, src, 0, part->size); 2803 x64_store_part( 2804 t, native_loc_stack(p->type, dst.v.frame, (i32)part->src_offset), 2805 tloc, 0, part->size); 2806 } 2807 } 2808 } 2809 2810 /* Flush the deferred register-destination param binds as a parallel copy (the 2811 * shared scheduler breaks any cycle the allocator's rotation created through 2812 * backend-private int/fp cycle temporaries). Frame-dst and indirect binds were emitted eagerly 2813 * in bind_param — they only read incoming registers, so they precede this. */ 2814 static void x64_bind_params_end(NativeTarget* t) { 2815 X64NativeTarget* a = x64_of(t); 2816 /* No callee is staged during entry binds, so r11 is free as the cycle 2817 * scratch. */ 2818 if (a->nbind_moves) 2819 x64_emit_reg_arg_moves(t, a->bind_moves, a->nbind_moves, X64_TMP_INT2); 2820 a->nbind_moves = 0; 2821 } 2822 2823 /* ============================ calls / returns ============================ */ 2824 2825 typedef NativeArgMove X64ArgMove; 2826 2827 static void x64_emit_one_arg_move(NativeTarget* t, const NativeArgMove* m) { 2828 if (m->is_addr) { 2829 x64_addr_of_loc(t, m->dst, m->src); 2830 } else { 2831 x64_load_part(t, m->dst, m->src, m->src_offset, m->size); 2832 } 2833 if (m->dup_to_gpr) { 2834 /* movq gpr, xmm: 66 REX.W 0F 7E /r (xmm in reg field). */ 2835 emit_sse_rr_w(t->mc, 0x66, 0x7E, 1, loc_reg(m->dst), m->dup_gpr); 2836 } 2837 } 2838 2839 /* Parallel-copy register arg moves via the shared scheduler. `int_scratch` is 2840 * the register used to break an integer cycle: normally r11, but rax when an 2841 * indirect callee is staged in r11 (rax is never a SysV int arg register and 2842 * the variadic AL count is written only after the moves). */ 2843 static void x64_emit_reg_arg_moves(NativeTarget* t, NativeArgMove* moves, u32 n, 2844 Reg int_scratch) { 2845 NativeArgShuffle s; 2846 if (n > X64_MAX_REG_ARG_MOVES) x64_panic(x64_of(t), "too many register args"); 2847 memset(&s, 0, sizeof s); 2848 s.t = t; 2849 s.emit_one = x64_emit_one_arg_move; 2850 s.reg_move = x64_move; 2851 s.scratch[NATIVE_REG_INT] = int_scratch; 2852 s.scratch[NATIVE_REG_FP] = X64_TMP_FP; 2853 s.scratch_class_mask = (1u << NATIVE_REG_INT) | (1u << NATIVE_REG_FP); 2854 native_arg_shuffle(&s, moves, n); 2855 } 2856 2857 static void x64_marshal_call(NativeTarget* t, const NativeCallDesc* desc, 2858 NativeCallPhase* plan) { 2859 X64NativeTarget* a = x64_of(t); 2860 const ABIFuncInfo* abi = abi_cg_func_info(t->c->abi, desc->fn_type); 2861 const X64ABIRegs* aregs = a->abi ? a->abi : x64_abi_for_os(t->c->target.os); 2862 NativeCallPhaseRet* rets; 2863 X64CallMarshalCtx call; 2864 KitCgTypeId i64t = builtin_id(KIT_CG_BUILTIN_I64); 2865 int tail = (desc->flags & CG_CALL_TAIL) != 0; 2866 /* Right-size the result scratch to the exact number of entries the ret loops 2867 * below write: nparts on a DIRECT register return, 1 on the !abi fallback, 2868 * 0 (NULL) otherwise (IGNORE / sret / no results). */ 2869 u32 nrets_cap = (abi && abi->ret.kind == ABI_ARG_DIRECT && desc->nresults) 2870 ? abi->ret.nparts 2871 : ((!abi && desc->nresults) ? 1u : 0u); 2872 memset(plan, 0, sizeof *plan); 2873 memset(&call, 0, sizeof call); 2874 call.tail = (u8)tail; 2875 call.cycle_scratch = X64_TMP_INT2; 2876 rets = nrets_cap ? arena_zarray(t->c->tu, NativeCallPhaseRet, nrets_cap) : NULL; 2877 plan->callee = desc->callee; 2878 plan->rets = rets; 2879 plan->flags = desc->flags; 2880 plan->has_sret = abi && abi->has_sret; 2881 plan->is_variadic = abi && abi->variadic; 2882 plan->stack_arg_size = x64_call_stack_bytes(t, desc); 2883 if (plan->stack_arg_size > a->frame.max_outgoing) 2884 a->frame.max_outgoing = plan->stack_arg_size; 2885 /* Argument transport owns r10 (part data), r11 (address stabilization), and 2886 * rax (parallel-copy cycle breaking). A register-indirect callee therefore 2887 * lives in a balanced stack home for the complete marshal phase, independent 2888 * of its producer register and every allowed NativeLoc argument shape. */ 2889 if (plan->callee.kind == NATIVE_LOC_REG) { 2890 if ((NativeAllocClass)plan->callee.cls != NATIVE_REG_INT) 2891 x64_panic(a, "indirect callee is not in an integer register"); 2892 x64_push_reg(t->mc, (Reg)loc_reg(plan->callee)); 2893 call.rsp_bias = 8u; 2894 call.callee_parked = 1u; 2895 call.cycle_scratch = X64_RAX; 2896 plan->callee = native_loc_reg(plan->callee.type, NATIVE_REG_INT, X64_R11); 2897 } 2898 { 2899 u32 next_int = (abi && abi->has_sret) ? 1u : 0u; 2900 u32 next_fp = 0, stack = aregs->shadow_space, nmoves = 0, i; 2901 u32 tail_payload = tail ? x64_call_stack_raw_size(t, desc) : 0u; 2902 u32 p; 2903 X64ArgMove moves[X64_MAX_REG_ARG_MOVES]; 2904 x64_sync_slot(aregs, &next_int, &next_fp); 2905 for (i = 0; i < desc->nargs; ++i) { 2906 ABIArgInfo tmp; 2907 const ABIArgInfo* ai = x64_param_abi(t, abi, desc, i, &tmp); 2908 int variadic_arg = abi && i >= abi->nparams; 2909 if (ai->kind == ABI_ARG_IGNORE) continue; 2910 if (ai->kind == ABI_ARG_INDIRECT) { 2911 NativeLoc src = desc->args[i]; 2912 if (tail) { 2913 AggregateAccess access; 2914 NativeAddr from, to; 2915 u32 align = ai->indirect_align; 2916 u32 size = native_type_size(t, src.type); 2917 if (!align) align = native_type_align(t, src.type); 2918 tail_payload = align_up_u32(tail_payload, align ? align : 1u); 2919 src = x64_incoming_stack_loc( 2920 src.type, NATIVE_REG_INT, (i32)(16u + tail_payload)); 2921 from = x64_storage_addr(a, desc->args[i], 0); 2922 to = x64_storage_addr(a, src, 0); 2923 memset(&access, 0, sizeof access); 2924 access.type = src.type; 2925 access.size = size; 2926 access.align = align ? align : 1u; 2927 x64_copy_bytes(t, to, from, access); 2928 tail_payload += size; 2929 } 2930 if (next_int < aregs->n_int_args) { 2931 X64ArgMove* m = &moves[nmoves++]; 2932 memset(m, 0, sizeof *m); 2933 m->dst = 2934 native_loc_reg(i64t, NATIVE_REG_INT, aregs->int_args[next_int++]); 2935 m->src = src; 2936 m->size = 8; 2937 m->is_addr = 1; 2938 } else { 2939 NativeLoc ptr = native_loc_reg(i64t, NATIVE_REG_INT, X64_RAX); 2940 x64_addr_of_loc(t, ptr, src); 2941 x64_store_outgoing_part(t, &call, stack, ptr, 8); 2942 stack += 8u; 2943 } 2944 x64_sync_slot(aregs, &next_int, &next_fp); 2945 continue; 2946 } 2947 if (ai->kind == ABI_ARG_DIRECT && 2948 x64_direct_to_stack(aregs, ai, next_int, next_fp)) { 2949 for (p = 0; p < ai->nparts; ++p) { 2950 const ABIArgPart* part = &ai->parts[p]; 2951 x64_copy_outgoing_part(t, &call, stack, desc->args[i], part); 2952 stack += 8u; 2953 } 2954 continue; 2955 } 2956 for (p = 0; p < ai->nparts; ++p) { 2957 const ABIArgPart* part = &ai->parts[p]; 2958 NativeAllocClass cls = 2959 part->cls == ABI_CLASS_FP ? NATIVE_REG_FP : NATIVE_REG_INT; 2960 if (cls == NATIVE_REG_FP && next_fp < aregs->n_fp_args) { 2961 X64ArgMove* m = &moves[nmoves++]; 2962 u32 slot = next_fp; 2963 memset(m, 0, sizeof *m); 2964 m->dst = native_loc_reg(desc->args[i].type, cls, 2965 (Reg)(X64_XMM0 + next_fp++)); 2966 m->src = desc->args[i]; 2967 m->src_offset = part->src_offset; 2968 m->size = part->size; 2969 if (aregs->vararg_fp_dup_to_gpr && variadic_arg && 2970 slot < aregs->n_int_args) { 2971 m->dup_to_gpr = 1; 2972 m->dup_gpr = aregs->int_args[slot]; 2973 } 2974 x64_sync_slot(aregs, &next_int, &next_fp); 2975 } else if (cls == NATIVE_REG_INT && next_int < aregs->n_int_args) { 2976 X64ArgMove* m = &moves[nmoves++]; 2977 memset(m, 0, sizeof *m); 2978 m->dst = native_loc_reg(desc->args[i].type, cls, 2979 aregs->int_args[next_int++]); 2980 m->src = desc->args[i]; 2981 m->src_offset = part->src_offset; 2982 m->size = part->size; 2983 x64_sync_slot(aregs, &next_int, &next_fp); 2984 } else { 2985 x64_copy_outgoing_part(t, &call, stack, desc->args[i], part); 2986 stack += 8u; 2987 x64_sync_slot(aregs, &next_int, &next_fp); 2988 } 2989 } 2990 } 2991 /* If an indirect callee was staged in r11 above, the cycle scratch must 2992 * avoid it; rax is free here (not an int arg reg; AL count comes later). */ 2993 x64_emit_reg_arg_moves(t, moves, nmoves, call.cycle_scratch); 2994 if (abi && abi->has_sret) { 2995 /* sret pointer in the first int-arg reg. A tail call forwards the 2996 * caller's own incoming sret pointer (spilled at entry); otherwise pass 2997 * the address of this call's result slot. */ 2998 NativeLoc sret = native_loc_reg(i64t, NATIVE_REG_INT, aregs->int_args[0]); 2999 if (tail) 3000 x64_load_part(t, sret, native_loc_stack(i64t, a->sret_ptr_slot, 0), 0, 3001 8); 3002 else if (desc->nresults) 3003 x64_addr_of_loc(t, sret, desc->results[0]); 3004 } 3005 /* Variadic call: AL = number of vector regs used. */ 3006 if (abi && abi->variadic) 3007 x64_emit_load_imm(t->mc, 0, X64_RAX, (i64)next_fp); 3008 if (call.callee_parked) { 3009 x64_pop_reg(t->mc, X64_R11); 3010 call.rsp_bias = 0u; 3011 call.callee_parked = 0u; 3012 } 3013 } 3014 /* Return value receipt. */ 3015 if (abi && abi->ret.kind == ABI_ARG_DIRECT && desc->nresults) { 3016 u32 nr = 0, ni = 0, nf = 0; 3017 static const u32 ret_int_regs[2] = {X64_RAX, X64_RDX}; 3018 u32 p; 3019 for (p = 0; p < abi->ret.nparts; ++p) { 3020 const ABIArgPart* part = &abi->ret.parts[p]; 3021 NativeAllocClass cls = 3022 part->cls == ABI_CLASS_FP ? NATIVE_REG_FP : NATIVE_REG_INT; 3023 KitCgTypeId pty = x64_part_scalar_type(part); 3024 Reg rreg = cls == NATIVE_REG_FP ? (Reg)(X64_XMM0 + nf++) 3025 : (Reg)ret_int_regs[ni++]; 3026 rets[nr].src = native_loc_reg(pty, cls, rreg); 3027 rets[nr].dst = desc->results[0]; 3028 if (rets[nr].dst.kind == NATIVE_LOC_FRAME) 3029 rets[nr].dst = native_loc_stack(pty, desc->results[0].v.frame, 3030 (i32)part->src_offset); 3031 else if (rets[nr].dst.kind == NATIVE_LOC_STACK) { 3032 rets[nr].dst.v.stack.offset += (i32)part->src_offset; 3033 rets[nr].dst.type = pty; 3034 } 3035 rets[nr].mem = native_mem_for_type(t, pty, part->size); 3036 nr++; 3037 } 3038 plan->nrets = nr; 3039 } else if (abi && abi->ret.kind == ABI_ARG_IGNORE) { 3040 plan->nrets = 0; 3041 } else if (!abi && desc->nresults) { 3042 rets[0].src = 3043 native_loc_reg(desc->results[0].type, NATIVE_REG_INT, X64_RAX); 3044 rets[0].dst = desc->results[0]; 3045 rets[0].mem = native_mem_for_type(t, desc->results[0].type, 0); 3046 plan->nrets = 1; 3047 } 3048 } 3049 3050 /* Emit a sibling (tail) call: tear the frame down and jump (no call) to the 3051 * callee. Outgoing args are already in arg regs / the caller's incoming-arg 3052 * window. `leave` (mov rsp,rbp; pop rbp) restores the caller's rbp and leaves 3053 * rsp at the return address — frame_size-independent, so no func_end patch. */ 3054 static void x64_emit_tail_site(NativeTarget* t, NativeLoc callee) { 3055 X64NativeTarget* a = x64_of(t); 3056 MCEmitter* mc = t->mc; 3057 ObjSecId sec = mc->section_id; 3058 /* Restore callee-saves before the frame teardown (O1 path; none at -O0). 3059 * Their rbp-relative offsets are frame-size-independent, and the indirect 3060 * callee was staged in r11 by marshal_call — a caller-saved scratch — so these 3061 * restores never clobber it. Mirrors the x64_func_end epilogue. */ 3062 x64_emit_callee_restores(a); 3063 emit_leave(mc); 3064 if (callee.kind == NATIVE_LOC_GLOBAL) { 3065 u8 op = X64_OPC_JMP_REL32; 3066 u32 disp_pos; 3067 mc_emit_bytes(mc, &op, 1); 3068 disp_pos = mc_pos(mc); 3069 emit_u32le(mc, 0); 3070 mc_emit_reloc_at(mc, sec, disp_pos, R_X64_PLT32, callee.v.global.sym, 3071 callee.v.global.addend - 4, 1, 0); 3072 } else if (callee.kind == NATIVE_LOC_REG) { 3073 /* indirect callee was staged in r11 by marshal_call */ 3074 u32 r = loc_reg(callee); 3075 x64_emit_indirect_rm(mc, r, 4u); /* jmp r/m, /4 */ 3076 } else { 3077 x64_panic(a, "unsupported tail call target"); 3078 } 3079 } 3080 3081 static void x64_emit_call(NativeTarget* t, const NativeCallPhase* plan) { 3082 MCEmitter* mc = t->mc; 3083 ObjSecId sec = mc->section_id; 3084 if (plan->flags & CG_CALL_TAIL) { 3085 x64_emit_tail_site(t, plan->callee); 3086 return; 3087 } 3088 if (plan->callee.kind == NATIVE_LOC_GLOBAL) { 3089 u8 op = X64_OPC_CALL_REL32; 3090 u32 disp_pos; 3091 mc_emit_bytes(mc, &op, 1); 3092 disp_pos = mc_pos(mc); 3093 emit_u32le(mc, 0); 3094 mc_emit_reloc_at(mc, sec, disp_pos, R_X64_PLT32, plan->callee.v.global.sym, 3095 plan->callee.v.global.addend - 4, 1, 0); 3096 return; 3097 } 3098 if (plan->callee.kind == NATIVE_LOC_REG) { 3099 u32 r = loc_reg(plan->callee); 3100 x64_emit_indirect_rm(mc, r, 2u); /* call r/m, /2 */ 3101 return; 3102 } 3103 x64_panic(x64_of(t), "unsupported call target"); 3104 } 3105 3106 static void x64_marshal_ret(NativeTarget* t, const CGFuncDesc* fd, 3107 const NativeLoc* value, 3108 NativeCallPhaseRet** out_rets, u32* out_nrets) { 3109 X64NativeTarget* a = x64_of(t); 3110 const ABIFuncInfo* abi = abi_cg_func_info(t->c->abi, fd->fn_type); 3111 NativeCallPhaseRet* rets = NULL; 3112 u32 nr = 0; 3113 if (value) rets = arena_zarray(t->c->tu, NativeCallPhaseRet, 4); 3114 if (value && abi && abi->ret.kind == ABI_ARG_INDIRECT) { 3115 /* sret: reload the destination pointer (spilled at entry) into rax and 3116 * copy the source aggregate into [rax]. Keep it out of R10/R11: those are 3117 * x64_copy_bytes' private transfer/address bank, and resolving a stack 3118 * source through R11 would otherwise overwrite the destination. RAX is 3119 * also the ABI's sret return register, so it can stay live through the 3120 * copy and needs no reload afterwards. */ 3121 KitCgTypeId i64t = builtin_id(KIT_CG_BUILTIN_I64); 3122 NativeLoc dstp = native_loc_reg(i64t, NATIVE_REG_INT, X64_RAX); 3123 NativeLoc saved = native_loc_stack(i64t, a->sret_ptr_slot, 0); 3124 NativeAddr dst_addr, src_addr; 3125 AggregateAccess access; 3126 x64_load_part(t, dstp, saved, 0, 8); 3127 memset(&dst_addr, 0, sizeof dst_addr); 3128 dst_addr.base_kind = NATIVE_ADDR_BASE_REG; 3129 dst_addr.base.reg = X64_RAX; 3130 dst_addr.base_type = value->type; 3131 src_addr = x64_storage_addr(a, *value, 0); 3132 src_addr.base_type = value->type; 3133 memset(&access, 0, sizeof access); 3134 access.type = value->type; 3135 access.size = (u32)cg_type_size(t->c, value->type); 3136 access.align = native_type_align(t, value->type); 3137 x64_copy_bytes(t, dst_addr, src_addr, access); 3138 *out_rets = NULL; 3139 *out_nrets = 0; 3140 return; 3141 } 3142 if (value && abi && abi->ret.kind == ABI_ARG_DIRECT) { 3143 u32 ni = 0, nf = 0; 3144 static const u32 ret_int_regs[2] = {X64_RAX, X64_RDX}; 3145 u32 p; 3146 int exact_parts = 0; 3147 for (p = 0; p < abi->ret.nparts; ++p) 3148 if (abi->ret.parts[p].cls == ABI_CLASS_INT && 3149 !x64_native_part_width(abi->ret.parts[p].size)) 3150 exact_parts = 1; 3151 3152 /* Generic write_phase materializes a register destination from its scalar 3153 * type, so an I64 carrier for a 3/5/6/7-byte aggregate part would widen the 3154 * source load to eight bytes. Once one exact part is present, marshal every 3155 * return part here in ABI order: this preserves already-filled return 3156 * registers and keeps each memory read bounded by ABIArgPart.size. */ 3157 if (exact_parts) { 3158 for (p = 0; p < abi->ret.nparts; ++p) { 3159 const ABIArgPart* part = &abi->ret.parts[p]; 3160 NativeAllocClass cls = 3161 part->cls == ABI_CLASS_FP ? NATIVE_REG_FP : NATIVE_REG_INT; 3162 KitCgTypeId pty = x64_part_scalar_type(part); 3163 Reg rreg = cls == NATIVE_REG_FP ? (Reg)(X64_XMM0 + nf++) 3164 : (Reg)ret_int_regs[ni++]; 3165 NativeLoc dst = native_loc_reg(pty, cls, rreg); 3166 x64_load_part(t, dst, *value, part->src_offset, part->size); 3167 } 3168 *out_rets = NULL; 3169 *out_nrets = 0; 3170 return; 3171 } 3172 3173 for (p = 0; p < abi->ret.nparts; ++p) { 3174 const ABIArgPart* part = &abi->ret.parts[p]; 3175 NativeAllocClass cls = 3176 part->cls == ABI_CLASS_FP ? NATIVE_REG_FP : NATIVE_REG_INT; 3177 KitCgTypeId pty = x64_part_scalar_type(part); 3178 Reg rreg = cls == NATIVE_REG_FP ? (Reg)(X64_XMM0 + nf++) 3179 : (Reg)ret_int_regs[ni++]; 3180 rets[nr].src = *value; 3181 if (rets[nr].src.kind == NATIVE_LOC_FRAME) 3182 rets[nr].src = 3183 native_loc_stack(pty, value->v.frame, (i32)part->src_offset); 3184 else if (rets[nr].src.kind == NATIVE_LOC_STACK) { 3185 rets[nr].src.v.stack.offset += (i32)part->src_offset; 3186 rets[nr].src.type = pty; 3187 } 3188 rets[nr].dst = native_loc_reg(pty, cls, rreg); 3189 rets[nr].mem = native_mem_for_type(t, pty, part->size); 3190 nr++; 3191 } 3192 } else if (value) { 3193 rets[0].src = *value; 3194 rets[0].dst = native_loc_reg(value->type, NATIVE_REG_INT, X64_RAX); 3195 rets[0].mem = native_mem_for_type(t, value->type, 0); 3196 nr = 1; 3197 } 3198 *out_rets = rets; 3199 *out_nrets = nr; 3200 } 3201 3202 static void x64_ret(NativeTarget* t) { 3203 X64NativeTarget* a = x64_of(t); 3204 x64_jump(t, a->epilogue_label); 3205 } 3206 3207 /* ============================ alloca ============================ */ 3208 3209 static void x64_alloca(NativeTarget* t, NativeLoc dst, NativeLoc size, 3210 u32 align) { 3211 X64NativeTarget* a = x64_of(t); 3212 MCEmitter* mc = t->mc; 3213 u32 rsz = loc_reg(size); 3214 u32 rd = loc_reg(dst); 3215 u32 al = align ? align : 16u; 3216 if (al < 16u) al = 16u; 3217 if (al > 16u) x64_panic(a, "alloca align > 16 not supported"); 3218 if (size.kind == NATIVE_LOC_IMM) { 3219 u64 aligned = ((u64)size.v.imm + 15u) & ~(u64)15u; 3220 if (aligned == 0) aligned = 16; 3221 /* sub rsp, imm32. */ 3222 emit_rex(mc, 1, 0, 0, X64_RSP); 3223 { 3224 u8 buf[2] = {X64_OPC_ALU_IMM32, modrm(3u, X64_ALU_SUB_SUB, X64_RSP)}; 3225 mc_emit_bytes(mc, buf, 2); 3226 } 3227 emit_u32le(mc, (u32)aligned); 3228 } else { 3229 /* r10 = (size + 15) & ~15; sub rsp, r10. */ 3230 emit_lea(mc, X64_TMP_INT, rsz, 15); 3231 emit_rex(mc, 1, 0, 0, X64_TMP_INT); 3232 { 3233 u8 buf[3] = {X64_OPC_ALU_IMM8, 3234 modrm(3u, X64_ALU_SUB_AND, X64_TMP_INT), 0xF0}; 3235 mc_emit_bytes(mc, buf, 3); 3236 } 3237 emit_alu_rr(mc, 1, X64_OPC_ALU_SUB, X64_RSP, X64_TMP_INT); 3238 } 3239 a->frame.has_alloca = 1; 3240 /* lea dst, [rsp + max_outgoing] — disp32 patched in func_end. */ 3241 if (a->npatches == a->patches_cap) { 3242 u32 cap = a->patches_cap ? a->patches_cap * 2u : 8u; 3243 X64Patch* nb = arena_zarray(t->c->tu, X64Patch, cap); 3244 if (a->patches) memcpy(nb, a->patches, sizeof(*nb) * a->npatches); 3245 a->patches = nb; 3246 a->patches_cap = cap; 3247 } 3248 emit_rex(mc, 1, rd, 0, X64_RSP); 3249 { 3250 u8 op = X64_OPC_LEA; 3251 mc_emit_bytes(mc, &op, 1); 3252 } 3253 { 3254 u8 mr = modrm(2u, rd & 7u, 4u); 3255 mc_emit_bytes(mc, &mr, 1); 3256 } 3257 { 3258 u8 s = sib(0u, 4u, X64_RSP); 3259 mc_emit_bytes(mc, &s, 1); 3260 } 3261 a->patches[a->npatches].kind = X64_PATCH_ALLOCA; 3262 a->patches[a->npatches].pos = mc_pos(mc); 3263 a->npatches++; 3264 a->nalloca++; 3265 emit_u32le(mc, 0); /* placeholder disp32 */ 3266 } 3267 3268 /* ============================ TLS ============================ */ 3269 3270 /* Win64 TLS Local-Exec (PE-COFF): TEB pointer -> _tls_index -> TLS block -> 3271 * lea &sym@SECREL. R11 is scratch. */ 3272 static void x64_tls_addr_of_win64(NativeTarget* t, NativeLoc dst, ObjSymId sym, 3273 i64 addend) { 3274 MCEmitter* mc = t->mc; 3275 u32 sec = mc->section_id; 3276 u32 rd = loc_reg(dst); 3277 /* (1) mov rd, gs:[0x58]. */ 3278 { 3279 u8 gs = 0x65; 3280 mc_emit_bytes(mc, &gs, 1); 3281 emit_rex(mc, 1, rd, 0, 0); 3282 { 3283 u8 op = X64_OPC_MOV_R_RM; 3284 mc_emit_bytes(mc, &op, 1); 3285 } 3286 { 3287 u8 mr = modrm(0u, rd & 7u, 4u); 3288 mc_emit_bytes(mc, &mr, 1); 3289 } 3290 { 3291 u8 s = sib(0u, 4u, 5u); 3292 mc_emit_bytes(mc, &s, 1); 3293 } 3294 emit_u32le(mc, 0x58u); 3295 } 3296 /* (2) mov r11d, [rip + _tls_index]. */ 3297 { 3298 Sym idx_name = pool_intern_slice(t->c->global, SLICE_LIT("_tls_index")); 3299 ObjSymId idx_sym = obj_symbol_find(t->obj, idx_name); 3300 u8 rex_r, op, mr; 3301 u32 disp_pos; 3302 if (idx_sym == 0) 3303 idx_sym = 3304 obj_symbol(t->obj, idx_name, SB_GLOBAL, SK_UNDEF, OBJ_SEC_NONE, 0, 0); 3305 rex_r = X64_REX_BASE | X64_REX_R; 3306 mc_emit_bytes(mc, &rex_r, 1); 3307 op = X64_OPC_MOV_R_RM; 3308 mc_emit_bytes(mc, &op, 1); 3309 mr = modrm(0u, 3u, 5u); /* r11&7, rip-rel */ 3310 mc_emit_bytes(mc, &mr, 1); 3311 disp_pos = mc_pos(mc); 3312 emit_u32le(mc, 0); 3313 mc_emit_reloc_at(mc, sec, disp_pos, R_PC32, idx_sym, -4, 1, 0); 3314 } 3315 /* (3) mov rd, [rd + r11*8]. */ 3316 { 3317 u8 rex = X64_REX_BASE | X64_REX_W | X64_REX_X; 3318 u8 op; 3319 if (rd & 8u) rex |= X64_REX_R | X64_REX_B; 3320 mc_emit_bytes(mc, &rex, 1); 3321 op = X64_OPC_MOV_R_RM; 3322 mc_emit_bytes(mc, &op, 1); 3323 if ((rd & 7u) == 5u) { 3324 u8 mr = modrm(1u, rd & 7u, 4u); 3325 u8 s = sib(3u, 3u, rd & 7u); 3326 u8 zero = 0; 3327 mc_emit_bytes(mc, &mr, 1); 3328 mc_emit_bytes(mc, &s, 1); 3329 mc_emit_bytes(mc, &zero, 1); 3330 } else { 3331 u8 mr = modrm(0u, rd & 7u, 4u); 3332 u8 s = sib(3u, 3u, rd & 7u); 3333 mc_emit_bytes(mc, &mr, 1); 3334 mc_emit_bytes(mc, &s, 1); 3335 } 3336 } 3337 /* (4) lea rd, [rd + sym@SECREL]. */ 3338 { 3339 u8 rex = X64_REX_BASE | X64_REX_W; 3340 u8 op; 3341 u32 disp_pos; 3342 if (rd & 8u) rex |= X64_REX_R | X64_REX_B; 3343 mc_emit_bytes(mc, &rex, 1); 3344 op = X64_OPC_LEA; 3345 mc_emit_bytes(mc, &op, 1); 3346 if ((rd & 7u) == 4u) { 3347 u8 mr = modrm(2u, rd & 7u, 4u); 3348 u8 s = sib(0u, 4u, rd & 7u); 3349 mc_emit_bytes(mc, &mr, 1); 3350 mc_emit_bytes(mc, &s, 1); 3351 } else { 3352 u8 mr = modrm(2u, rd & 7u, rd & 7u); 3353 mc_emit_bytes(mc, &mr, 1); 3354 } 3355 disp_pos = mc_pos(mc); 3356 emit_u32le(mc, 0); 3357 mc_emit_reloc_at(mc, sec, disp_pos, R_COFF_SECREL, sym, addend, 1, 0); 3358 } 3359 } 3360 3361 /* x86-64 Mach-O TLV (descriptor model): load the __thread_ptrs slot holding 3362 * the TLV descriptor address into %rdi, then call the descriptor's resolver 3363 * thunk (descriptor[0]); it returns the variable's storage address in %rax. 3364 * Peer of arm64's aa_tls_addr_of descriptor path. The slot is addressed by an 3365 * R_X64_TLV reloc (RIP-relative disp32) that the Mach-O linker routes through 3366 * __thread_ptrs. The Apple TLV thunk preserves every register except %rax, so 3367 * the only clobbers are %rdi (we load it) and %rax (the result) — modeled in 3368 * x64_machine_op_clobbers so no value lives across the access in them. Any 3369 * addend is a byte offset into the variable, applied to %rax after the call. */ 3370 static void x64_tls_addr_of_macho(NativeTarget* t, NativeLoc dst, ObjSymId sym, 3371 i64 addend) { 3372 MCEmitter* mc = t->mc; 3373 u32 sec = mc->section_id; 3374 u32 rd = loc_reg(dst); 3375 u32 disp_pos; 3376 /* movq sym@TLVP(%rip), %rdi (48 8B 3D disp32). */ 3377 emit_rex(mc, 1, X64_RDI, 0, 0); 3378 { 3379 u8 op = X64_OPC_MOV_R_RM; 3380 mc_emit_bytes(mc, &op, 1); 3381 } 3382 { 3383 u8 mr = modrm(0u, X64_RDI & 7u, 5u); /* [rip + disp32] */ 3384 mc_emit_bytes(mc, &mr, 1); 3385 } 3386 disp_pos = mc_pos(mc); 3387 emit_u32le(mc, 0); 3388 mc_emit_reloc_at(mc, sec, disp_pos, R_X64_TLV, sym, -4, 1, 0); 3389 /* callq *(%rdi) (FF /2, mem [rdi]) -> %rax = &storage. */ 3390 { 3391 u8 call[2] = {0xFFu, modrm(0u, 2u, X64_RDI & 7u)}; 3392 mc_emit_bytes(mc, call, 2); 3393 } 3394 /* %rax += addend (byte offset into the variable). */ 3395 if (addend) { 3396 i32 a = (i32)addend; 3397 if (a == addend) { 3398 emit_rex(mc, 1, 0, 0, X64_RAX); 3399 if (imm_fits_i8(a)) { 3400 u8 buf[3] = {X64_OPC_ALU_IMM8, modrm(3u, X64_ALU_SUB_ADD, X64_RAX & 7u), 3401 (u8)a}; 3402 mc_emit_bytes(mc, buf, 3); 3403 } else { 3404 u8 buf[2] = {X64_OPC_ALU_IMM32, 3405 modrm(3u, X64_ALU_SUB_ADD, X64_RAX & 7u)}; 3406 mc_emit_bytes(mc, buf, 2); 3407 emit_u32le(mc, (u32)a); 3408 } 3409 } else { 3410 /* Wide addend: materialize in r11 then add. */ 3411 x64_emit_load_imm(mc, 1, X64_R11, addend); 3412 emit_alu_rr(mc, 1, X64_OPC_ALU_ADD, X64_RAX, X64_R11); 3413 } 3414 } 3415 if (rd != X64_RAX) emit_mov_rr(mc, 1, rd, X64_RAX); 3416 } 3417 3418 /* x86-64 TLS Local-Exec: mov rd, fs:0; lea rd, [rd + sym@tpoff]. */ 3419 static void x64_tls_addr_of(NativeTarget* t, NativeLoc dst, ObjSymId sym, 3420 i64 addend) { 3421 MCEmitter* mc = t->mc; 3422 u32 sec = mc->section_id; 3423 u32 rd = loc_reg(dst); 3424 u32 disp_pos; 3425 if (obj_format_tls_via_descriptor(t->c)) { 3426 x64_tls_addr_of_macho(t, dst, sym, addend); 3427 return; 3428 } 3429 if (obj_format_tls_model(t->c) == OBJ_TLS_WINDOWS_TEB) { 3430 x64_tls_addr_of_win64(t, dst, sym, addend); 3431 return; 3432 } 3433 /* mov rd, fs:[0]. */ 3434 { 3435 u8 fs = 0x64; 3436 mc_emit_bytes(mc, &fs, 1); 3437 emit_rex(mc, 1, rd, 0, 0); 3438 { 3439 u8 op = X64_OPC_MOV_R_RM; 3440 mc_emit_bytes(mc, &op, 1); 3441 } 3442 { 3443 u8 mr = modrm(0u, rd & 7u, 4u); 3444 mc_emit_bytes(mc, &mr, 1); 3445 } 3446 { 3447 u8 s = sib(0u, 4u, 5u); 3448 mc_emit_bytes(mc, &s, 1); 3449 } 3450 emit_u32le(mc, 0); 3451 } 3452 /* lea rd, [rd + disp32@tpoff]. */ 3453 emit_rex(mc, 1, rd, 0, rd); 3454 { 3455 u8 op = X64_OPC_LEA; 3456 mc_emit_bytes(mc, &op, 1); 3457 } 3458 if ((rd & 7u) == 4u) { 3459 u8 mr = modrm(2u, rd & 7u, 4u); 3460 u8 s = sib(0u, 4u, rd & 7u); 3461 mc_emit_bytes(mc, &mr, 1); 3462 mc_emit_bytes(mc, &s, 1); 3463 } else { 3464 u8 mr = modrm(2u, rd & 7u, rd & 7u); 3465 mc_emit_bytes(mc, &mr, 1); 3466 } 3467 disp_pos = mc_pos(mc); 3468 emit_u32le(mc, 0); 3469 mc_emit_reloc_at(mc, sec, disp_pos, R_X64_TPOFF32, sym, addend, 0, 0); 3470 } 3471 3472 /* ============================ atomics ============================ */ 3473 3474 static void emit_lock_prefix(MCEmitter* mc) { 3475 u8 b = 0xF0; 3476 mc_emit_bytes(mc, &b, 1); 3477 } 3478 static void emit_mfence(MCEmitter* mc) { 3479 u8 b[3] = {0x0F, 0xAE, 0xF0}; 3480 mc_emit_bytes(mc, b, 3); 3481 } 3482 3483 /* Resolve an atomic addr to a bare base register (r11) + disp 0. */ 3484 static u32 x64_atomic_base(X64NativeTarget* a, NativeAddr addr) { 3485 return x64_addr_to_base_reg(a, addr, X64_TMP_INT2); 3486 } 3487 3488 static void x64_atomic_load(NativeTarget* t, NativeLoc dst, NativeAddr addr, 3489 MemAccess mem, KitCgMemOrder mo) { 3490 X64NativeTarget* a = x64_of(t); 3491 u32 sz = mem.size ? mem.size : loc_size32(t, dst); 3492 u32 base; 3493 (void)mo; /* x86 plain MOV is an acquire load. */ 3494 base = x64_atomic_base(a, addr); 3495 emit_mov_load(t->mc, sz, 0, loc_reg(dst), base, 0); 3496 } 3497 3498 static void x64_atomic_store(NativeTarget* t, NativeAddr addr, NativeLoc src, 3499 MemAccess mem, KitCgMemOrder mo) { 3500 X64NativeTarget* a = x64_of(t); 3501 MCEmitter* mc = t->mc; 3502 u32 sz = mem.size ? mem.size : loc_size32(t, src); 3503 int w = sz == 8u ? 1 : 0; 3504 u32 base = x64_atomic_base(a, addr); 3505 u32 sr = loc_reg(src); 3506 if (mo == KIT_CG_MO_SEQ_CST) { 3507 /* XCHG writes the prior memory value into its register operand. Stage the 3508 * semantic source in backend-private R10 so that mutation is invisible to 3509 * the allocator; R11 holds a materialized address. */ 3510 if (base == X64_TMP_INT) { 3511 emit_mov_rr(mc, 1, X64_TMP_INT2, base); 3512 base = X64_TMP_INT2; 3513 } 3514 if (sr != X64_TMP_INT) emit_mov_rr(mc, w, X64_TMP_INT, sr); 3515 emit_lock_prefix(mc); 3516 emit_rex(mc, w, X64_TMP_INT, 0, base); 3517 { 3518 u8 op = 0x87; /* xchg r/m, r */ 3519 mc_emit_bytes(mc, &op, 1); 3520 } 3521 emit_mem_operand(mc, X64_TMP_INT, base, 0); 3522 return; 3523 } 3524 emit_mov_store(mc, sz, sr, base, 0); 3525 } 3526 3527 static void x64_atomic_rmw(NativeTarget* t, KitCgAtomicOp op, NativeLoc dst, 3528 NativeAddr addr, NativeLoc val, MemAccess mem, 3529 KitCgMemOrder mo) { 3530 X64NativeTarget* a = x64_of(t); 3531 MCEmitter* mc = t->mc; 3532 u32 sz = mem.size ? mem.size : loc_size32(t, dst); 3533 int w = sz == 8u ? 1 : 0; 3534 u32 base = x64_atomic_base(a, addr); 3535 u32 dr = loc_reg(dst); 3536 u32 vr = loc_reg(val); 3537 (void)mo; /* LOCK ops are full barriers. */ 3538 /* The rmw uses fixed rax (prior), rcx (new), rdx (val); the optimizer may 3539 * have materialized the address into one of them, so keep it out (R11 is 3540 * backend-private, never an allocated operand). Stage before RDX is loaded. 3541 */ 3542 if (base == X64_RAX || base == X64_RCX || base == X64_RDX) { 3543 emit_mov_rr(mc, 1, X64_TMP_INT2, base); 3544 base = X64_TMP_INT2; 3545 } 3546 /* val staged in rdx (rax/rcx used by the cmpxchg loop). */ 3547 emit_mov_rr(mc, w, X64_RDX, vr); 3548 if (op == KIT_CG_ATOMIC_ADD || op == KIT_CG_ATOMIC_SUB) { 3549 if (op == KIT_CG_ATOMIC_SUB) emit_f7_rm(mc, w, X64_F7_SUB_NEG, X64_RDX); 3550 emit_lock_prefix(mc); 3551 emit_rex(mc, w, X64_RDX, 0, base); 3552 { 3553 u8 op2[2] = {X64_OPC_TWOBYTE, 0xC1}; /* xadd */ 3554 mc_emit_bytes(mc, op2, 2); 3555 } 3556 emit_mem_operand(mc, X64_RDX, base, 0); 3557 if (dr != X64_RDX) emit_mov_rr(mc, w, dr, X64_RDX); 3558 return; 3559 } 3560 if (op == KIT_CG_ATOMIC_XCHG) { 3561 emit_lock_prefix(mc); 3562 emit_rex(mc, w, X64_RDX, 0, base); 3563 { 3564 u8 op2 = 0x87; /* xchg */ 3565 mc_emit_bytes(mc, &op2, 1); 3566 } 3567 emit_mem_operand(mc, X64_RDX, base, 0); 3568 if (dr != X64_RDX) emit_mov_rr(mc, w, dr, X64_RDX); 3569 return; 3570 } 3571 /* AND/OR/XOR/NAND: cmpxchg retry loop. rax=prior, rcx=new, rdx=val. */ 3572 { 3573 MCLabel retry = mc_label_new(mc); 3574 emit_mov_load(mc, sz, 0, X64_RAX, base, 0); 3575 mc_label_place(mc, retry); 3576 emit_mov_rr(mc, w, X64_RCX, X64_RAX); 3577 switch (op) { 3578 case KIT_CG_ATOMIC_AND: 3579 emit_alu_rr(mc, w, X64_OPC_ALU_AND, X64_RCX, X64_RDX); 3580 break; 3581 case KIT_CG_ATOMIC_OR: 3582 emit_alu_rr(mc, w, X64_OPC_ALU_OR, X64_RCX, X64_RDX); 3583 break; 3584 case KIT_CG_ATOMIC_XOR: 3585 emit_alu_rr(mc, w, X64_OPC_ALU_XOR, X64_RCX, X64_RDX); 3586 break; 3587 case KIT_CG_ATOMIC_NAND: 3588 emit_alu_rr(mc, w, X64_OPC_ALU_AND, X64_RCX, X64_RDX); 3589 emit_f7_rm(mc, w, X64_F7_SUB_NOT, X64_RCX); 3590 break; 3591 default: 3592 x64_panic(a, "unsupported atomic rmw op"); 3593 } 3594 emit_lock_prefix(mc); 3595 emit_rex(mc, w, X64_RCX, 0, base); 3596 { 3597 u8 op2[2] = {X64_OPC_TWOBYTE, 0xB1}; /* cmpxchg */ 3598 mc_emit_bytes(mc, op2, 2); 3599 } 3600 emit_mem_operand(mc, X64_RCX, base, 0); 3601 emit_jcc_rel32(mc, X64_CC_NE, retry); 3602 if (dr != X64_RAX) emit_mov_rr(mc, w, dr, X64_RAX); 3603 } 3604 } 3605 3606 static void x64_atomic_cas(NativeTarget* t, NativeLoc prior, NativeLoc ok, 3607 NativeAddr addr, NativeLoc expected, 3608 NativeLoc desired, MemAccess mem, 3609 KitCgMemOrder success, KitCgMemOrder failure) { 3610 X64NativeTarget* a = x64_of(t); 3611 MCEmitter* mc = t->mc; 3612 u32 sz = mem.size ? mem.size : loc_size32(t, prior); 3613 int w = sz == 8u ? 1 : 0; 3614 u32 base = x64_atomic_base(a, addr); 3615 u32 rprior = loc_reg(prior); 3616 u32 rok = loc_reg(ok); 3617 u32 rexp = loc_reg(expected); 3618 u32 rdes = loc_reg(desired); 3619 (void)success; 3620 (void)failure; 3621 /* cmpxchg uses fixed rax (expected) and rcx (desired). The optimizer may have 3622 * materialized the address into either; keep it out of both (R11 is 3623 * backend-private, never an allocated operand). */ 3624 if (base == X64_RAX || base == X64_RCX) { 3625 emit_mov_rr(mc, 1, X64_TMP_INT2, base); 3626 base = X64_TMP_INT2; 3627 } 3628 /* Place expected -> rax and desired -> rcx as a parallel copy: the allocator 3629 * may have them in each other's target register (full swap) or desired in rax 3630 * (expected's target), either of which a naive two-move order would clobber. 3631 */ 3632 if (rexp == X64_RCX && rdes == X64_RAX) { 3633 /* Swap rax <-> rcx (xchg needs no temp; base is not rax/rcx here). */ 3634 emit_rex(mc, w, X64_RCX, 0, X64_RAX); 3635 { 3636 u8 xchg[2] = {0x87, modrm(3u, X64_RCX, X64_RAX)}; 3637 mc_emit_bytes(mc, xchg, 2); 3638 } 3639 } else if (rdes == X64_RAX) { 3640 /* desired sits in rax; move it to rcx before rax is overwritten. */ 3641 if (rdes != X64_RCX) emit_mov_rr(mc, w, X64_RCX, rdes); 3642 if (rexp != X64_RAX) emit_mov_rr(mc, w, X64_RAX, rexp); 3643 } else { 3644 if (rexp != X64_RAX) emit_mov_rr(mc, w, X64_RAX, rexp); 3645 if (rdes != X64_RCX) emit_mov_rr(mc, w, X64_RCX, rdes); 3646 } 3647 emit_lock_prefix(mc); 3648 emit_rex(mc, w, X64_RCX, 0, base); 3649 { 3650 u8 op2[2] = {X64_OPC_TWOBYTE, 0xB1}; /* cmpxchg [base], rcx */ 3651 mc_emit_bytes(mc, op2, 2); 3652 } 3653 emit_mem_operand(mc, X64_RCX, base, 0); 3654 emit_setcc(mc, X64_CC_E, rok); 3655 emit_movzx_r32_r8(mc, rok, rok); 3656 if (rprior != X64_RAX) emit_mov_rr(mc, w, rprior, X64_RAX); 3657 } 3658 3659 static void x64_fence(NativeTarget* t, KitCgMemOrder mo) { 3660 if (mo == KIT_CG_MO_SEQ_CST) emit_mfence(t->mc); 3661 } 3662 3663 /* ============================ variadics ============================ 3664 * SysV: __va_list_tag (gp_offset@0, fp_offset@4, overflow@8, reg_save@16). The 3665 * prologue filled the 176B reg-save area. Win64: va_list is a single pointer 3666 * to the next 8-byte slot in the home/overflow area; FP varargs are duplicated 3667 * into the matching GPR slot at the call site. `ap` addresses the va_list 3668 * object. */ 3669 3670 /* Resolve a va_list address into `scratch`, materializing it there if it is not 3671 * already, so the backend-private field-value register never aliases it. */ 3672 static u32 x64_va_base(X64NativeTarget* a, NativeAddr ap, u32 scratch) { 3673 u32 base = x64_addr_to_base_reg(a, ap, scratch); 3674 if (base != scratch) { 3675 emit_mov_rr(a->base.mc, 1, scratch, base); 3676 base = scratch; 3677 } 3678 return base; 3679 } 3680 3681 /* add r/m, imm8 (group-1 /0) directly to a memory field — advances a va_list 3682 * offset/pointer in place without consuming a register. w selects 64- vs 3683 * 32-bit. */ 3684 static void x64_add_mem_imm(MCEmitter* mc, int w, u32 base, i32 disp, i8 imm) { 3685 u8 op = X64_OPC_ALU_IMM8; 3686 u8 b; 3687 emit_rex(mc, w, 0, 0, base); 3688 mc_emit_bytes(mc, &op, 1); 3689 emit_mem_operand(mc, X64_ALU_SUB_ADD, base, disp); /* modrm.reg = /0 (ADD) */ 3690 b = (u8)imm; 3691 mc_emit_bytes(mc, &b, 1); 3692 } 3693 3694 /* add r64, [base+disp] (0x03 /r). */ 3695 static void x64_add_reg_mem(MCEmitter* mc, u32 dst, u32 base, i32 disp) { 3696 u8 op = 0x03; 3697 emit_rex(mc, 1, dst, 0, base); 3698 mc_emit_bytes(mc, &op, 1); 3699 emit_mem_operand(mc, dst, base, disp); 3700 } 3701 3702 static void x64_va_start_core(X64NativeTarget* a, NativeAddr ap) { 3703 NativeTarget* t = &a->base; 3704 MCEmitter* mc = t->mc; 3705 u32 ap_base; 3706 if (!a->is_variadic) x64_panic(a, "va_start: function not variadic"); 3707 ap_base = x64_va_base(a, ap, X64_TMP_INT2); 3708 if (a->abi->shadow_space) { 3709 /* Win64: *ap = rbp + 16 + named_int*8 + named_stack. */ 3710 u32 first = 16u + a->next_param_int * 8u + a->next_param_stack; 3711 emit_lea(mc, X64_TMP_INT, X64_RBP, (i32)first); 3712 emit_mov_store(mc, 8, X64_TMP_INT, ap_base, 0); 3713 return; 3714 } 3715 { 3716 X64NativeSlot* rs = x64_slot_get(a, a->reg_save_slot); 3717 /* gp_offset = next_param_int * 8 */ 3718 x64_emit_load_imm(mc, 0, X64_TMP_INT, (i64)(a->next_param_int * 8u)); 3719 emit_mov_store(mc, 4, X64_TMP_INT, ap_base, 0); 3720 /* fp_offset = 48 + next_param_fp * 16 */ 3721 x64_emit_load_imm(mc, 0, X64_TMP_INT, 3722 (i64)(48u + a->next_param_fp * 16u)); 3723 emit_mov_store(mc, 4, X64_TMP_INT, ap_base, 4); 3724 /* overflow_arg_area = rbp + 16 + next_param_stack */ 3725 emit_lea(mc, X64_TMP_INT, X64_RBP, 3726 (i32)(16u + a->next_param_stack)); 3727 emit_mov_store(mc, 8, X64_TMP_INT, ap_base, 8); 3728 /* reg_save_area = rbp - reg_save_slot.off */ 3729 emit_lea(mc, X64_TMP_INT, X64_RBP, -(i32)rs->off); 3730 emit_mov_store(mc, 8, X64_TMP_INT, ap_base, 16); 3731 } 3732 } 3733 3734 static void x64_va_arg_core(X64NativeTarget* a, NativeLoc dst, NativeAddr ap, 3735 KitCgTypeId type) { 3736 NativeTarget* t = &a->base; 3737 MCEmitter* mc = t->mc; 3738 u32 sz = native_type_size(t, type); 3739 int is_fp = native_loc_is_fp(dst); 3740 u32 dr = loc_reg(dst); 3741 u32 ap_base = x64_va_base(a, ap, X64_TMP_INT2); /* r11 */ 3742 /* GPR temporary for the offset/address arithmetic. For integer results the 3743 * destination is itself a throwaway operand temp — pass_native_emit fetches 3744 * va_arg into a scoped register and writes it to the real destination — so 3745 * we reuse `dr` and touch no allocable register at all. FP results keep their 3746 * value in an XMM register, so they borrow backend-private R10. 3747 * Either way only r11 (ap_base) and `gp` are used: the va_list fields are 3748 * advanced in memory (x64_add_mem_imm) and the reg-save base is folded in 3749 * with x64_add_reg_mem, so no third register is needed. */ 3750 u32 gp = is_fp ? X64_TMP_INT : dr; 3751 if (a->abi->shadow_space) { 3752 /* Win64: gp = *ap; load dr from [gp]; *ap += 8. */ 3753 emit_mov_load(mc, 8, 0, gp, ap_base, 0); 3754 if (is_fp) 3755 emit_sse_load(mc, sse_scalar_prefix(sz), 0x10, dr, gp, 0); 3756 else 3757 emit_mov_load(mc, sz, 0, dr, gp, 0); 3758 x64_add_mem_imm(mc, 1, ap_base, 0, 8); 3759 return; 3760 } 3761 { 3762 u32 offs_field = is_fp ? 4u : 0u; 3763 u32 max_offs = is_fp ? 176u : 48u; 3764 i8 stride = is_fp ? 16 : 8; 3765 MCLabel L_stack = mc_label_new(mc); 3766 MCLabel L_done = mc_label_new(mc); 3767 /* gp32 = ap[offs]; cmp gp32, max; jae L_stack. Use the imm8 form when the 3768 * threshold fits (gp_offset max 48) so the encoding is canonical and the 3769 * `cc -S | as` round-trip reproduces it; fp_offset max 176 needs imm32. */ 3770 emit_mov_load(mc, 4, 0, gp, ap_base, (i32)offs_field); 3771 if (imm_fits_i8((i64)max_offs)) 3772 emit_alu_imm8(mc, 0, X64_ALU_SUB_CMP, gp, (i8)max_offs); 3773 else 3774 emit_alu_imm32(mc, 0, X64_ALU_SUB_CMP, gp, (i32)max_offs); 3775 emit_jcc_rel32(mc, X64_CC_AE, L_stack); 3776 /* reg path: ap[offs] += stride; gp = reg_save_area(ap[16]) + offset; load. 3777 * (The memory increment leaves gp holding the old offset.) */ 3778 x64_add_mem_imm(mc, 0, ap_base, (i32)offs_field, stride); 3779 x64_add_reg_mem(mc, gp, ap_base, 16); 3780 if (is_fp) 3781 emit_sse_load(mc, sse_scalar_prefix(sz), 0x10, dr, gp, 0); 3782 else 3783 emit_mov_load(mc, sz, 0, dr, gp, 0); 3784 emit_jmp_rel32(mc, L_done); 3785 /* stack path: gp = ap[8] (overflow area); load; ap[8] += 8. */ 3786 mc_label_place(mc, L_stack); 3787 emit_mov_load(mc, 8, 0, gp, ap_base, 8); 3788 if (is_fp) 3789 emit_sse_load(mc, sse_scalar_prefix(sz), 0x10, dr, gp, 0); 3790 else 3791 emit_mov_load(mc, sz, 0, dr, gp, 0); 3792 x64_add_mem_imm(mc, 1, ap_base, 8, 8); 3793 mc_label_place(mc, L_done); 3794 } 3795 } 3796 3797 static void x64_va_copy_core(X64NativeTarget* a, NativeAddr dst_ap, 3798 NativeAddr src_ap) { 3799 NativeTarget* t = &a->base; 3800 MCEmitter* mc = t->mc; 3801 /* Resolve dst into R11, src into R10, and copy each qword through 3802 * backend-private XMM14. Uses only reserved backend registers 3803 * (R11/R10/XMM14), so the 3804 * optimizer's register choice for a va_list pointer can never be clobbered 3805 * and no allocable GPR (previously rdx) is consumed. */ 3806 u32 dst_base = x64_va_base(a, dst_ap, X64_TMP_INT2); 3807 u32 src_base = x64_va_base(a, src_ap, X64_TMP_INT); 3808 u32 n = a->abi->shadow_space ? 8u : 24u, i; 3809 for (i = 0; i < n; i += 8u) { 3810 emit_sse_load(mc, 0xF2, 0x10, X64_TMP_FP, src_base, (i32)i); /* movsd */ 3811 emit_sse_store(mc, 0xF2, 0x11, X64_TMP_FP, dst_base, (i32)i); /* movsd */ 3812 } 3813 } 3814 3815 static NativeAddr x64_va_addr_from_ptr(NativeLoc ap_ptr) { 3816 NativeAddr addr; 3817 memset(&addr, 0, sizeof addr); 3818 addr.base_kind = NATIVE_ADDR_BASE_REG; 3819 addr.cls = NATIVE_REG_INT; 3820 addr.base.reg = ap_ptr.v.reg; 3821 addr.base_type = ap_ptr.type; 3822 return addr; 3823 } 3824 3825 static void x64_va_start_native(NativeTarget* t, NativeLoc ap_ptr) { 3826 x64_va_start_core(x64_of(t), x64_va_addr_from_ptr(ap_ptr)); 3827 } 3828 static void x64_va_arg_native(NativeTarget* t, NativeLoc dst, NativeLoc ap_ptr, 3829 KitCgTypeId type) { 3830 x64_va_arg_core(x64_of(t), dst, x64_va_addr_from_ptr(ap_ptr), type); 3831 } 3832 static void x64_va_end_native(NativeTarget* t, NativeLoc ap_ptr) { 3833 (void)t; 3834 (void)ap_ptr; 3835 } 3836 static void x64_va_copy_native(NativeTarget* t, NativeLoc dst, NativeLoc src) { 3837 x64_va_copy_core(x64_of(t), x64_va_addr_from_ptr(dst), 3838 x64_va_addr_from_ptr(src)); 3839 } 3840 3841 /* ============================ intrinsics ============================ */ 3842 3843 static void emit_popcnt(MCEmitter* mc, int w, u32 dst, u32 src) { 3844 u8 p = 0xF3; 3845 mc_emit_bytes(mc, &p, 1); 3846 emit_rex(mc, w, dst, 0, src); 3847 { 3848 u8 op[2] = {X64_OPC_TWOBYTE, 0xB8}; 3849 mc_emit_bytes(mc, op, 2); 3850 } 3851 emit_rm_reg(mc, dst, src); 3852 } 3853 static void emit_popcount_software(MCEmitter* mc, int w, u32 dst, u32 src) { 3854 MCLabel loop = mc_label_new(mc); 3855 MCLabel done = mc_label_new(mc); 3856 /* Kernighan's bit-count loop. Keep the source in reserved scratch so dst 3857 * may legally overlap it, and so the baseline x86-64 profile never executes 3858 * POPCNT unless the resolved target says it is available. */ 3859 emit_mov_rr(mc, w, X64_TMP_INT, src); 3860 x64_emit_load_imm(mc, w, dst, 0); 3861 mc_label_place(mc, loop); 3862 emit_test_self(mc, w, X64_TMP_INT); 3863 emit_jcc_rel32(mc, X64_CC_E, done); 3864 emit_alu_imm8(mc, w, X64_ALU_SUB_ADD, dst, 1); 3865 emit_mov_rr(mc, w, X64_TMP_INT2, X64_TMP_INT); 3866 emit_alu_imm8(mc, w, X64_ALU_SUB_SUB, X64_TMP_INT2, 1); 3867 emit_alu_rr(mc, w, X64_OPC_ALU_AND, X64_TMP_INT, X64_TMP_INT2); 3868 emit_jmp_rel32(mc, loop); 3869 mc_label_place(mc, done); 3870 } 3871 static void emit_bs(MCEmitter* mc, int w, u8 opcode2, u32 dst, u32 src) { 3872 emit_rex(mc, w, dst, 0, src); 3873 { 3874 u8 op[2] = {X64_OPC_TWOBYTE, opcode2}; 3875 mc_emit_bytes(mc, op, 2); 3876 } 3877 emit_rm_reg(mc, dst, src); 3878 } 3879 static void emit_bswap(MCEmitter* mc, int w, u32 reg) { 3880 emit_rex(mc, w, 0, 0, reg); 3881 { 3882 u8 op[2] = {X64_OPC_TWOBYTE, (u8)(0xC8 + (reg & 7u))}; 3883 mc_emit_bytes(mc, op, 2); 3884 } 3885 } 3886 static void emit_rol16_imm8(MCEmitter* mc, u32 reg, u8 imm) { 3887 u8 p = X64_OPSIZE_PFX; 3888 mc_emit_bytes(mc, &p, 1); 3889 emit_rex(mc, 0, 0, 0, reg); 3890 { 3891 u8 buf[3] = {X64_OPC_SHIFT_IMM, modrm(3u, 0u, reg & 7u), imm}; 3892 mc_emit_bytes(mc, buf, 3); 3893 } 3894 } 3895 static void emit_ud2(MCEmitter* mc) { 3896 u8 b[2] = {0x0F, 0x0B}; 3897 mc_emit_bytes(mc, b, 2); 3898 } 3899 3900 static void emit_syscall(MCEmitter* mc) { 3901 u8 b[2] = {0x0F, 0x05}; 3902 mc_emit_bytes(mc, b, 2); 3903 } 3904 3905 static void emit_rdtsc(MCEmitter* mc) { 3906 u8 b[2] = {0x0F, 0x31}; 3907 mc_emit_bytes(mc, b, 2); 3908 } 3909 3910 /* Linux/Android x86-64 stack-protector guard: mov rd, qword ptr fs:[0x28]. */ 3911 static void emit_stack_guard(MCEmitter* mc, u32 rd) { 3912 u8 fs = 0x64; 3913 u8 op = X64_OPC_MOV_R_RM; 3914 u8 mr = modrm(0u, rd & 7u, 4u); 3915 u8 s = sib(0u, 4u, 5u); 3916 mc_emit_bytes(mc, &fs, 1); 3917 emit_rex(mc, 1, rd, 0, 0); 3918 mc_emit_bytes(mc, &op, 1); 3919 mc_emit_bytes(mc, &mr, 1); 3920 mc_emit_bytes(mc, &s, 1); 3921 emit_u32le(mc, 0x28u); 3922 } 3923 3924 /* Optimizer folding may put an immediate directly in a native intrinsic even 3925 * when the instruction form itself requires a register. Materialize those 3926 * arithmetic operands in the backend-private bank; ordinary MIR/NDT operand 3927 * locations never occupy R10/R11. Callers pass a distinct scratch for each 3928 * simultaneously-live argument. */ 3929 static u32 x64_intrinsic_arg_reg(X64NativeTarget* a, NativeLoc arg, 3930 u32 scratch, int w) { 3931 if (arg.kind == NATIVE_LOC_REG) return loc_reg(arg); 3932 if (arg.kind == NATIVE_LOC_IMM) { 3933 x64_emit_load_imm(a->base.mc, w, scratch, arg.v.imm); 3934 return scratch; 3935 } 3936 x64_panic(a, "arithmetic intrinsic operand is not register/immediate"); 3937 } 3938 3939 static int x64_intrinsic_arg_accepts_imm(NativeTarget* t, IntrinKind kind, 3940 u32 arg_index) { 3941 (void)t; 3942 if (arg_index > 1u) return 0; 3943 switch (kind) { 3944 case INTRIN_SADD_OVERFLOW: 3945 case INTRIN_UADD_OVERFLOW: 3946 case INTRIN_SSUB_OVERFLOW: 3947 case INTRIN_USUB_OVERFLOW: 3948 case INTRIN_SMUL_OVERFLOW: 3949 case INTRIN_UMUL_OVERFLOW: 3950 case INTRIN_SMUL_HIGH: 3951 case INTRIN_UMUL_HIGH: 3952 return 1; 3953 default: 3954 return 0; 3955 } 3956 } 3957 3958 static void x64_intrinsic(NativeTarget* t, IntrinKind kind, 3959 const NativeLoc* dsts, u32 ndst, 3960 const NativeLoc* args, u32 narg) { 3961 X64NativeTarget* a = x64_of(t); 3962 MCEmitter* mc = t->mc; 3963 (void)ndst; 3964 switch (kind) { 3965 case INTRIN_NONE: 3966 break; 3967 case INTRIN_EXPECT: 3968 case INTRIN_ASSUME_ALIGNED: 3969 if (args[0].kind == NATIVE_LOC_IMM) 3970 x64_emit_load_imm(mc, x64_is_64(t, dsts[0].type) ? 1 : 0, 3971 loc_reg(dsts[0]), args[0].v.imm); 3972 else 3973 x64_move(t, dsts[0], args[0]); 3974 return; 3975 case INTRIN_PREFETCH: 3976 if (narg >= 1u && args[0].kind == NATIVE_LOC_REG) { 3977 /* GCC locality 0..3 maps from no-temporal through increasing temporal 3978 * locality: PREFETCHNTA, PREFETCHT2, PREFETCHT1, PREFETCHT0. The rw 3979 * operand is intentionally ignored: PREFETCHW is not baseline x64. */ 3980 static const u8 hint[4] = {0u, 3u, 2u, 1u}; 3981 u32 locality = 3u; 3982 u32 base = loc_reg(args[0]); 3983 u8 op[2] = {0x0F, 0x18}; 3984 if (narg >= 3u && args[2].kind == NATIVE_LOC_IMM) 3985 locality = (u32)args[2].v.imm; 3986 if (locality > 3u) locality = 3u; 3987 emit_rex(mc, 0, hint[locality], 0, base); 3988 mc_emit_bytes(mc, op, 2); 3989 emit_mem_operand(mc, hint[locality], base, 0); 3990 } 3991 return; 3992 case INTRIN_TRAP: 3993 emit_ud2(mc); 3994 return; 3995 case INTRIN_READCYCLECOUNTER: { 3996 /* RDTSC returns the 64-bit timestamp counter split across edx:eax 3997 * (writing eax/edx zero-extends the upper halves of rax/rdx). Recombine 3998 * into a single 64-bit value: dst = (rdx << 32) | rax. The rax/rdx 3999 * clobber is modeled in x64_machine_op_clobbers. */ 4000 u32 rd = loc_reg(dsts[0]); 4001 emit_rdtsc(mc); 4002 emit_shift_imm(mc, 1, X64_SHIFT_SUB_SHL, X64_RDX, 32); 4003 emit_alu_rr(mc, 1, X64_OPC_ALU_OR, X64_RAX, X64_RDX); 4004 if (rd != X64_RAX) emit_mov_rr(mc, 1, rd, X64_RAX); 4005 return; 4006 } 4007 case INTRIN_STACK_GUARD: 4008 if (ndst != 1u || narg != 0u) 4009 x64_panic(a, "stack guard intrinsic has invalid operands"); 4010 emit_stack_guard(mc, loc_reg(dsts[0])); 4011 return; 4012 case INTRIN_SYSCALL: 4013 if (ndst == 1u && narg >= 1u && narg <= 7u) { 4014 static const u32 syscall_regs[7] = {X64_RAX, X64_RDI, X64_RSI, X64_RDX, 4015 X64_R10, X64_R8, X64_R9}; 4016 X64ArgMove moves[7]; 4017 for (u32 i = 0; i < narg; ++i) { 4018 X64ArgMove* m = &moves[i]; 4019 memset(m, 0, sizeof *m); 4020 m->dst = 4021 native_loc_reg(dsts[0].type, NATIVE_REG_INT, syscall_regs[i]); 4022 m->src = args[i]; 4023 m->size = t->c->target.ptr_size; 4024 } 4025 x64_emit_reg_arg_moves(t, moves, narg, X64_TMP_INT2); 4026 emit_syscall(mc); 4027 x64_move(t, dsts[0], 4028 native_loc_reg(dsts[0].type, NATIVE_REG_INT, X64_RAX)); 4029 } 4030 return; 4031 case INTRIN_POPCOUNT: { 4032 int w = x64_is_64(t, args[0].type) ? 1 : 0; 4033 if (kit_target_has_feature(t->c->target_ref, KIT_SLICE_LIT("popcnt"))) 4034 emit_popcnt(mc, w, loc_reg(dsts[0]), loc_reg(args[0])); 4035 else 4036 emit_popcount_software(mc, w, loc_reg(dsts[0]), loc_reg(args[0])); 4037 return; 4038 } 4039 case INTRIN_CTZ: 4040 emit_bs(mc, x64_is_64(t, args[0].type) ? 1 : 0, 0xBC /* bsf */, 4041 loc_reg(dsts[0]), loc_reg(args[0])); 4042 return; 4043 case INTRIN_CLZ: { 4044 int w = x64_is_64(t, args[0].type) ? 1 : 0; 4045 u32 dr = loc_reg(dsts[0]); 4046 emit_bs(mc, w, 0xBD /* bsr */, dr, loc_reg(args[0])); 4047 /* clz = (bits-1) - bsr, computed via xor with bits-1. The mask (31/63) 4048 * fits in imm8, so use the compact 0x83 form to match the canonical 4049 * encoding (and the assembler's `cc -S | as` round-trip). */ 4050 emit_alu_imm8(mc, w, X64_ALU_SUB_XOR, dr, w ? 63 : 31); 4051 return; 4052 } 4053 case INTRIN_BSWAP: { 4054 u32 width = abi_cg_sizeof(t->c->abi, dsts[0].type); 4055 switch (width) { 4056 case 2: { 4057 u32 dr = loc_reg(dsts[0]), sr = loc_reg(args[0]); 4058 if (dr != sr) emit_mov_rr(mc, 0, dr, sr); 4059 emit_rol16_imm8(mc, dr, 8); 4060 return; 4061 } 4062 case 4: { 4063 u32 dr = loc_reg(dsts[0]), sr = loc_reg(args[0]); 4064 if (dr != sr) emit_mov_rr(mc, 0, dr, sr); 4065 emit_bswap(mc, 0, dr); 4066 return; 4067 } 4068 case 8: { 4069 u32 dr = loc_reg(dsts[0]), sr = loc_reg(args[0]); 4070 if (dr != sr) emit_mov_rr(mc, 1, dr, sr); 4071 emit_bswap(mc, 1, dr); 4072 return; 4073 } 4074 default: 4075 break; 4076 } 4077 return; 4078 } 4079 case INTRIN_SADD_OVERFLOW: 4080 case INTRIN_UADD_OVERFLOW: 4081 case INTRIN_SSUB_OVERFLOW: 4082 case INTRIN_USUB_OVERFLOW: { 4083 int w = x64_is_64(t, dsts[0].type) ? 1 : 0; 4084 u32 rd = loc_reg(dsts[0]), rovf = loc_reg(dsts[1]); 4085 u32 ra = x64_intrinsic_arg_reg(a, args[0], X64_TMP_INT, w); 4086 u32 rb = x64_intrinsic_arg_reg(a, args[1], X64_TMP_INT2, w); 4087 u8 op = (kind == INTRIN_SADD_OVERFLOW || kind == INTRIN_UADD_OVERFLOW) 4088 ? X64_OPC_ALU_ADD 4089 : X64_OPC_ALU_SUB; 4090 u32 cc = (kind == INTRIN_UADD_OVERFLOW || kind == INTRIN_USUB_OVERFLOW) 4091 ? X64_CC_B 4092 : X64_CC_O; 4093 if (rd != ra) emit_mov_rr(mc, w, rd, ra); 4094 emit_alu_rr(mc, w, op, rd, rb); 4095 emit_setcc(mc, cc, rovf); 4096 emit_movzx_r32_r8(mc, rovf, rovf); 4097 return; 4098 } 4099 case INTRIN_SMUL_OVERFLOW: { 4100 int w = x64_is_64(t, dsts[0].type) ? 1 : 0; 4101 u32 rd = loc_reg(dsts[0]), rovf = loc_reg(dsts[1]); 4102 u32 ra = x64_intrinsic_arg_reg(a, args[0], X64_TMP_INT, w); 4103 u32 rb = x64_intrinsic_arg_reg(a, args[1], X64_TMP_INT2, w); 4104 if (rd != ra) emit_mov_rr(mc, w, rd, ra); 4105 emit_imul_rr(mc, w, rd, rb); 4106 emit_setcc(mc, X64_CC_O, rovf); 4107 emit_movzx_r32_r8(mc, rovf, rovf); 4108 return; 4109 } 4110 case INTRIN_UMUL_OVERFLOW: { 4111 int w = x64_is_64(t, dsts[0].type) ? 1 : 0; 4112 u32 rd = loc_reg(dsts[0]), rovf = loc_reg(dsts[1]); 4113 u32 ra = x64_intrinsic_arg_reg(a, args[0], X64_TMP_INT, w); 4114 u32 rb = x64_intrinsic_arg_reg(a, args[1], X64_TMP_INT2, w); 4115 if (rb == X64_RAX || rb == X64_RDX) { 4116 emit_mov_rr(mc, w, X64_R11, rb); 4117 rb = X64_R11; 4118 } 4119 if (ra != X64_RAX) emit_mov_rr(mc, w, X64_RAX, ra); 4120 emit_f7_rm(mc, w, X64_F7_SUB_MUL, rb); /* MUL: rdx:rax = rax * rb */ 4121 if (rd != X64_RAX) emit_mov_rr(mc, w, rd, X64_RAX); 4122 emit_setcc(mc, X64_CC_O, rovf); 4123 emit_movzx_r32_r8(mc, rovf, rovf); 4124 return; 4125 } 4126 case INTRIN_SMUL_HIGH: 4127 case INTRIN_UMUL_HIGH: { 4128 int w = x64_is_64(t, dsts[0].type) ? 1 : 0; 4129 u32 rd = loc_reg(dsts[0]); 4130 u32 ra = x64_intrinsic_arg_reg(a, args[0], X64_TMP_INT, w); 4131 u32 rb = x64_intrinsic_arg_reg(a, args[1], X64_TMP_INT2, w); 4132 if (rb == X64_RAX || rb == X64_RDX) { 4133 emit_mov_rr(mc, w, X64_R11, rb); 4134 rb = X64_R11; 4135 } 4136 if (ra != X64_RAX) emit_mov_rr(mc, w, X64_RAX, ra); 4137 emit_f7_rm(mc, w, 4138 kind == INTRIN_SMUL_HIGH ? X64_F7_SUB_IMUL 4139 : X64_F7_SUB_MUL, 4140 rb); 4141 if (rd != X64_RDX) emit_mov_rr(mc, w, rd, X64_RDX); 4142 return; 4143 } 4144 case INTRIN_MEMMOVE: { 4145 u32 dr, sr, n, i; 4146 if (narg != 3u || args[0].kind != NATIVE_LOC_REG || 4147 args[1].kind != NATIVE_LOC_REG || args[2].kind != NATIVE_LOC_IMM) 4148 x64_panic(a, "unsupported memory intrinsic operands"); 4149 if (args[2].v.imm < 0 || args[2].v.imm > 0xffffffffll) 4150 x64_panic(a, "unsupported memory intrinsic size"); 4151 dr = loc_reg(args[0]); 4152 sr = loc_reg(args[1]); 4153 n = (u32)args[2].v.imm; 4154 i = n; /* copy high-to-low so an overlapping dst > src is safe */ 4155 while (i >= 8u) { 4156 i -= 8u; 4157 emit_mov_load(mc, 8, 0, X64_TMP_INT, sr, (i32)i); 4158 emit_mov_store(mc, 8, X64_TMP_INT, dr, (i32)i); 4159 } 4160 while (i >= 4u) { 4161 i -= 4u; 4162 emit_mov_load(mc, 4, 0, X64_TMP_INT, sr, (i32)i); 4163 emit_mov_store(mc, 4, X64_TMP_INT, dr, (i32)i); 4164 } 4165 while (i >= 2u) { 4166 i -= 2u; 4167 emit_mov_load(mc, 2, 0, X64_TMP_INT, sr, (i32)i); 4168 emit_mov_store(mc, 2, X64_TMP_INT, dr, (i32)i); 4169 } 4170 while (i >= 1u) { 4171 i -= 1u; 4172 emit_mov_load(mc, 1, 0, X64_TMP_INT, sr, (i32)i); 4173 emit_mov_store(mc, 1, X64_TMP_INT, dr, (i32)i); 4174 } 4175 return; 4176 } 4177 case INTRIN_CPU_NOP: { 4178 u8 b = 0x90; /* NOP */ 4179 mc_emit_bytes(mc, &b, 1); 4180 return; 4181 } 4182 case INTRIN_CPU_YIELD: { 4183 u8 b[2] = {0xF3, 0x90}; /* PAUSE */ 4184 mc_emit_bytes(mc, b, 2); 4185 return; 4186 } 4187 case INTRIN_DMB: 4188 case INTRIN_DSB: { 4189 u8 b[3] = {0x0F, 0xAE, 0xF0}; /* MFENCE: full-system memory barrier */ 4190 mc_emit_bytes(mc, b, 3); 4191 return; 4192 } 4193 case INTRIN_IRQ_DISABLE: { 4194 u8 b = 0xFA; /* CLI (privileged) */ 4195 mc_emit_bytes(mc, &b, 1); 4196 return; 4197 } 4198 case INTRIN_IRQ_ENABLE: { 4199 u8 b = 0xFB; /* STI (privileged) */ 4200 mc_emit_bytes(mc, &b, 1); 4201 return; 4202 } 4203 case INTRIN_FRAME_ADDRESS: 4204 case INTRIN_RETURN_ADDRESS: 4205 /* Walk the rbp frame-record chain. Every kit prologue keeps the rbp 4206 * record: [rbp] = caller's rbp, [rbp + 8] = return address pushed by the 4207 * `call`. The level is a compile-time constant, so the walk unrolls to 4208 * `level` dependent loads. */ 4209 if (ndst == 1u) { 4210 u32 level = (narg >= 1u && args[0].kind == NATIVE_LOC_IMM) 4211 ? (u32)args[0].v.imm 4212 : 0u; 4213 u32 rd = loc_reg(dsts[0]); 4214 emit_mov_rr(mc, 1, rd, X64_RBP); 4215 for (u32 i = 0; i < level; ++i) 4216 emit_mov_load(mc, 8, 0, rd, rd, 0); /* rd = *(rd) */ 4217 if (kind == INTRIN_RETURN_ADDRESS) 4218 emit_mov_load(mc, 8, 0, rd, rd, 8); /* rd = *(rd + 8) */ 4219 } 4220 return; 4221 default: 4222 break; 4223 } 4224 x64_panic(a, "unsupported compiler intrinsic"); 4225 } 4226 4227 /* ============================ inline asm ============================ */ 4228 4229 _Noreturn static void x64_asm_panic_at(Compiler* c, SrcLoc loc, 4230 const char* msg) { 4231 compiler_panic(c, loc, "x64 inline asm: %s", msg); 4232 } 4233 _Noreturn static void x64_asm_panic(NativeDirectTarget* d, const char* msg) { 4234 x64_asm_panic_at(d->base.c, d->loc, msg); 4235 } 4236 4237 /* constraint_body / constraint_early / match_index are shared 4238 * (cg/native_asm.h). */ 4239 4240 static void x64_asm_bound_reg(Operand* out, KitCgTypeId type, 4241 NativeAllocClass cls, Reg reg) { 4242 memset(out, 0, sizeof *out); 4243 out->kind = X64_INLINE_OPK_REG; 4244 out->pad[0] = 4245 (cls == NATIVE_REG_FP) ? X64_INLINE_OPCLS_FP : X64_INLINE_OPCLS_INT; 4246 out->type = type; 4247 out->v.local = (CGLocal)reg; 4248 } 4249 static void x64_asm_bound_mem(Operand* out, KitCgTypeId type, Reg base) { 4250 memset(out, 0, sizeof *out); 4251 out->kind = OPK_INDIRECT; 4252 out->type = type; 4253 out->v.ind.base = (CGLocal)base; 4254 out->v.ind.index = CG_LOCAL_NONE; 4255 out->v.ind.ofs = 0; 4256 } 4257 4258 /* Parse a clobber register name into (class, reg). Returns 0 for cc/memory. 4259 * GPR names map to HW encoding via x64_register_hw_index; xmm names map via the 4260 * DWARF table (xmm0..15 = dwarf 17..32). */ 4261 static int x64_asm_parse_reg_clobber(Compiler* c, SrcLoc loc, Sym name, 4262 NativeAllocClass* cls_out, Reg* reg_out) { 4263 Slice s = pool_slice(c->global, name); 4264 char buf[16]; 4265 uint32_t idx; 4266 if (!s.s || !s.len) return 0; 4267 if (s.len == 2 && s.s[0] == 'c' && s.s[1] == 'c') return 0; 4268 if (s.len == 6 && memcmp(s.s, "memory", 6) == 0) return 0; 4269 if (s.len >= sizeof buf) x64_asm_panic_at(c, loc, "clobber name is too long"); 4270 memcpy(buf, s.s, s.len); 4271 buf[s.len] = '\0'; 4272 if (x64_register_hw_index(buf, &idx) == 0 && idx <= 15u) { 4273 *cls_out = NATIVE_REG_INT; 4274 *reg_out = (Reg)idx; 4275 return 1; 4276 } 4277 if (x64_register_index(buf, &idx) == 0 && idx >= 17u && idx <= 32u) { 4278 *cls_out = NATIVE_REG_FP; 4279 *reg_out = (Reg)(idx - 17u); 4280 return 1; 4281 } 4282 x64_asm_panic_at(c, loc, "unknown clobber register"); 4283 return 0; 4284 } 4285 4286 static void x64_asm_clobber_masks(Compiler* c, SrcLoc loc, const Sym* clobbers, 4287 u32 nclob, u32* int_mask, u32* fp_mask) { 4288 u32 i; 4289 *int_mask = 0; 4290 *fp_mask = 0; 4291 for (i = 0; i < nclob; ++i) { 4292 NativeAllocClass cls; 4293 Reg reg; 4294 if (!x64_asm_parse_reg_clobber(c, loc, clobbers[i], &cls, ®)) continue; 4295 if (cls == NATIVE_REG_INT) 4296 *int_mask |= 1u << reg; 4297 else 4298 *fp_mask |= 1u << reg; 4299 } 4300 } 4301 4302 /* Pin resolution + panic is the shared native_asm_bind_direct_operands path. */ 4303 4304 /* Direct (-O0) path: resolve a semantic Operand to a NativeAddr. */ 4305 static NativeAddr x64_direct_addr(NativeDirectTarget* d, Operand op) { 4306 NativeAddr addr; 4307 memset(&addr, 0, sizeof addr); 4308 switch ((OpKind)op.kind) { 4309 case OPK_LOCAL: 4310 addr.base_kind = NATIVE_ADDR_BASE_FRAME; 4311 addr.base.frame = d->locals[op.v.local - 1u].home; 4312 addr.base_type = op.type; 4313 return addr; 4314 case OPK_INDIRECT: 4315 addr.base_kind = NATIVE_ADDR_BASE_FRAME_VALUE; 4316 addr.base.frame = d->locals[op.v.ind.base - 1u].home; 4317 addr.cls = d->locals[op.v.ind.base - 1u].cls; 4318 addr.base_type = d->locals[op.v.ind.base - 1u].type; 4319 addr.offset = op.v.ind.ofs; 4320 return addr; 4321 default: 4322 x64_asm_panic(d, "operand is not addressable"); 4323 } 4324 } 4325 4326 static NativeAddr x64_direct_materialize_addr(NativeDirectTarget* d, 4327 Operand op) { 4328 X64NativeTarget* a = x64_of(d->native); 4329 NativeAddr addr = x64_direct_addr(d, op); 4330 if (addr.base_kind == NATIVE_ADDR_BASE_FRAME_VALUE) { 4331 NativeAddr load; 4332 memset(&load, 0, sizeof load); 4333 load.base_kind = NATIVE_ADDR_BASE_FRAME; 4334 load.base.frame = addr.base.frame; 4335 load.base_type = addr.base_type; 4336 emit_mov_load(a->base.mc, 8, 0, X64_TMP_INT2, X64_RBP, 4337 -(i32)x64_slot_get(a, addr.base.frame)->off); 4338 addr.base_kind = NATIVE_ADDR_BASE_REG; 4339 addr.base.reg = X64_TMP_INT2; 4340 } 4341 return addr; 4342 } 4343 4344 static void x64_direct_load_operand_to_reg(NativeDirectTarget* d, Operand op, 4345 NativeLoc dst) { 4346 X64NativeTarget* a = x64_of(d->native); 4347 NativeAddr addr; 4348 memset(&addr, 0, sizeof addr); 4349 switch ((OpKind)op.kind) { 4350 case OPK_IMM: 4351 if ((NativeAllocClass)dst.cls != NATIVE_REG_INT) 4352 x64_asm_panic(d, "floating-point immediate asm input is unsupported"); 4353 d->native->load_imm(d->native, dst, op.v.imm); 4354 return; 4355 case OPK_LOCAL: 4356 addr.base_kind = NATIVE_ADDR_BASE_FRAME; 4357 addr.base.frame = d->locals[op.v.local - 1u].home; 4358 addr.base_type = op.type; 4359 x64_emit_mem(a, 1, dst, addr, native_mem_for_type(d->native, op.type, 0)); 4360 return; 4361 case OPK_GLOBAL: 4362 addr.base_kind = NATIVE_ADDR_BASE_GLOBAL; 4363 addr.base.global.sym = op.v.global.sym; 4364 addr.base.global.addend = op.v.global.addend; 4365 addr.base_type = op.type; 4366 d->native->load_addr(d->native, dst, addr); 4367 return; 4368 case OPK_INDIRECT: 4369 addr = x64_direct_materialize_addr(d, op); 4370 x64_emit_mem(a, 1, dst, addr, native_mem_for_type(d->native, op.type, 0)); 4371 return; 4372 } 4373 x64_asm_panic(d, "unsupported asm input operand"); 4374 } 4375 4376 static void x64_direct_load_address_to_reg(NativeDirectTarget* d, Operand op, 4377 NativeLoc dst) { 4378 d->native->load_addr(d->native, dst, x64_direct_addr(d, op)); 4379 } 4380 4381 static void x64_direct_store_reg_to_operand(NativeDirectTarget* d, Operand op, 4382 NativeLoc src) { 4383 X64NativeTarget* a = x64_of(d->native); 4384 NativeAddr addr; 4385 memset(&addr, 0, sizeof addr); 4386 if (op.kind == OPK_LOCAL) { 4387 addr.base_kind = NATIVE_ADDR_BASE_FRAME; 4388 addr.base.frame = d->locals[op.v.local - 1u].home; 4389 addr.base_type = op.type; 4390 } else { 4391 addr = x64_direct_materialize_addr(d, op); 4392 } 4393 x64_emit_mem(a, 0, src, addr, native_mem_for_type(d->native, op.type, 0)); 4394 } 4395 4396 /* Callee-saved registers an asm block clobbers must be saved around the block. 4397 */ 4398 typedef struct X64AsmSavedClobber { 4399 NativeFrameSlot slot; 4400 NativeAllocClass cls; 4401 Reg reg; 4402 KitCgTypeId type; 4403 } X64AsmSavedClobber; 4404 4405 static void x64_asm_save_one(X64NativeTarget* a, X64AsmSavedClobber* s) { 4406 NativeFrameSlotDesc desc; 4407 NativeAddr addr; 4408 memset(&desc, 0, sizeof desc); 4409 desc.type = s->type; 4410 desc.size = s->cls == NATIVE_REG_FP ? 16u : 8u; 4411 desc.align = desc.size; 4412 desc.kind = NATIVE_FRAME_SLOT_SAVE; 4413 s->slot = a->base.frame_slot(&a->base, &desc); 4414 memset(&addr, 0, sizeof addr); 4415 addr.base_kind = NATIVE_ADDR_BASE_FRAME; 4416 addr.base.frame = s->slot; 4417 addr.base_type = s->type; 4418 x64_emit_mem(a, 0, native_loc_reg(s->type, s->cls, s->reg), addr, 4419 native_mem_for_type(&a->base, s->type, desc.size)); 4420 } 4421 static void x64_asm_restore_one(X64NativeTarget* a, 4422 const X64AsmSavedClobber* s) { 4423 NativeAddr addr; 4424 memset(&addr, 0, sizeof addr); 4425 addr.base_kind = NATIVE_ADDR_BASE_FRAME; 4426 addr.base.frame = s->slot; 4427 addr.base_type = s->type; 4428 x64_emit_mem(a, 1, native_loc_reg(s->type, s->cls, s->reg), addr, 4429 native_mem_for_type(&a->base, s->type, 4430 s->cls == NATIVE_REG_FP ? 16u : 8u)); 4431 } 4432 4433 /* SysV callee-saved: int rbx,r12-r15; no fp. Win64 adds rdi,rsi + xmm6-15. */ 4434 static int x64_reg_is_callee_int(const X64ABIRegs* abi, Reg r) { 4435 if (r == X64_RBP) return 0; /* prologue head handles rbp */ 4436 return (abi->cs_int_mask & (1ull << r)) != 0; 4437 } 4438 static int x64_reg_is_callee_fp(const X64ABIRegs* abi, Reg r) { 4439 return (abi->cs_fp_mask & (1ull << r)) != 0; 4440 } 4441 4442 static X64AsmSavedClobber* x64_asm_save_callee_clobbers(X64NativeTarget* a, 4443 u32 int_mask, 4444 u32 fp_mask, 4445 u32* nsaved_out) { 4446 X64AsmSavedClobber* saved = 4447 arena_zarray(a->base.c->tu, X64AsmSavedClobber, 32u); 4448 KitCgTypeId i64 = builtin_id(KIT_CG_BUILTIN_I64); 4449 KitCgTypeId f64 = builtin_id(KIT_CG_BUILTIN_F64); 4450 u32 n = 0; 4451 Reg r; 4452 for (r = 0; r <= 15u; ++r) { 4453 if ((int_mask & (1u << r)) == 0 || !x64_reg_is_callee_int(a->abi, r)) 4454 continue; 4455 saved[n].cls = NATIVE_REG_INT; 4456 saved[n].reg = r; 4457 saved[n].type = i64; 4458 x64_asm_save_one(a, &saved[n++]); 4459 } 4460 for (r = 0; r <= 15u; ++r) { 4461 if ((fp_mask & (1u << r)) == 0 || !x64_reg_is_callee_fp(a->abi, r)) 4462 continue; 4463 saved[n].cls = NATIVE_REG_FP; 4464 saved[n].reg = r; 4465 saved[n].type = f64; 4466 x64_asm_save_one(a, &saved[n++]); 4467 } 4468 *nsaved_out = n; 4469 return saved; 4470 } 4471 4472 /* ---- NativeTarget (optimizer) asm hook ---- 4473 * 4474 * Register placement, staging, and output writeback belong to the optimized 4475 * emitter. This hook binds those concrete locations and only materializes 4476 * memory-constraint bases in backend-private registers. */ 4477 4478 static NativeAddr x64_asm_loc_to_addr(X64NativeTarget* a, SrcLoc loc, 4479 NativeLoc src) { 4480 NativeAddr addr; 4481 memset(&addr, 0, sizeof addr); 4482 addr.base_type = src.type; 4483 switch ((NativeLocKind)src.kind) { 4484 case NATIVE_LOC_FRAME: 4485 addr.base_kind = NATIVE_ADDR_BASE_FRAME; 4486 addr.base.frame = src.v.frame; 4487 return addr; 4488 case NATIVE_LOC_ADDR: 4489 return src.v.addr; 4490 case NATIVE_LOC_GLOBAL: 4491 addr.base_kind = NATIVE_ADDR_BASE_GLOBAL; 4492 addr.base.global.sym = src.v.global.sym; 4493 addr.base.global.addend = src.v.global.addend; 4494 return addr; 4495 case NATIVE_LOC_REG: 4496 addr.base_kind = NATIVE_ADDR_BASE_REG; 4497 addr.cls = NATIVE_REG_INT; 4498 addr.base.reg = src.v.reg; 4499 return addr; 4500 default: 4501 x64_asm_panic_at(a->base.c, loc, "unsupported memory asm operand"); 4502 } 4503 } 4504 4505 static Reg x64_asm_native_mem_base(X64NativeTarget* a, SrcLoc loc, 4506 NativeLoc src, u32* ntmp) { 4507 NativeAddr addr = x64_asm_loc_to_addr(a, loc, src); 4508 Reg dst; 4509 if (addr.base_kind == NATIVE_ADDR_BASE_REG && addr.offset == 0 && 4510 addr.index_kind == NATIVE_ADDR_INDEX_NONE) { 4511 if ((addr.base.reg & 0xfu) != X64_TMP_INT && 4512 (addr.base.reg & 0xfu) != X64_TMP_INT2) 4513 return (Reg)(addr.base.reg & 0xfu); 4514 } 4515 if (*ntmp >= 2u) 4516 x64_asm_panic_at(a->base.c, loc, "too many memory asm operands"); 4517 dst = (*ntmp == 0u) ? (Reg)X64_TMP_INT : (Reg)X64_TMP_INT2; 4518 (*ntmp)++; 4519 x64_addr_to_base_reg(a, addr, dst); 4520 return dst; 4521 } 4522 4523 static void x64_asm_native_panic(NativeTarget* t, SrcLoc loc, 4524 const char* msg) { 4525 x64_asm_panic_at(t->c, loc, msg); 4526 } 4527 4528 static Reg x64_asm_native_mem_base_hook(NativeTarget* t, SrcLoc loc, 4529 NativeLoc src, u32* ntmp) { 4530 return x64_asm_native_mem_base(x64_of(t), loc, src, ntmp); 4531 } 4532 4533 static void x64_asm_native_run_template_hook( 4534 NativeTarget* t, const char* tmpl, const AsmConstraint* outs, u32 nout, 4535 Operand* bound_outs, const AsmConstraint* ins, u32 nin, Operand* bound_ins, 4536 const Sym* clobbers, u32 nclob) { 4537 X64Asm* asmh = x64_asm_open(t->c); 4538 x64_inline_bind(asmh, outs, nout, bound_outs, ins, nin, bound_ins, clobbers, 4539 nclob); 4540 x64_asm_run_template(asmh, t->mc, tmpl); 4541 x64_asm_close(asmh); 4542 } 4543 4544 static void x64_asm_block_native(NativeTarget* t, const char* tmpl, 4545 const AsmConstraint* outs, u32 nout, 4546 NativeLoc* out_locs, const AsmConstraint* ins, 4547 u32 nin, const NativeLoc* in_locs, 4548 const Sym* clobbers, u32 nclob) { 4549 X64NativeTarget* a = x64_of(t); 4550 SrcLoc loc = a->func ? a->func->loc : (SrcLoc){0, 0, 0}; 4551 static const NativeAsmNativeHooks hooks = { 4552 .panic = x64_asm_native_panic, 4553 .bound_reg = x64_asm_bound_reg, 4554 .bound_mem = x64_asm_bound_mem, 4555 .mem_base = x64_asm_native_mem_base_hook, 4556 .run_template = x64_asm_native_run_template_hook, 4557 }; 4558 native_asm_bind_native_operands(t, loc, tmpl, outs, nout, out_locs, ins, nin, 4559 in_locs, clobbers, nclob, &hooks); 4560 } 4561 4562 /* file_scope_asm + finalize are shared (cg/native_asm.h). */ 4563 4564 static void x64_trap(NativeTarget* t) { emit_ud2(t->mc); } 4565 static void x64_set_loc(NativeTarget* t, SrcLoc loc) { 4566 x64_of(t)->loc = loc; 4567 mc_set_loc(t->mc, loc); 4568 } 4569 4570 static u32 x64_binop_visible_clobbers(const NativeMachineOp* op) { 4571 switch ((BinOp)op->binop) { 4572 case BO_SDIV: 4573 case BO_UDIV: 4574 case BO_SREM: 4575 case BO_UREM: 4576 return (1u << X64_RAX) | (1u << X64_RDX); 4577 case BO_SHL: 4578 case BO_SHR_S: 4579 case BO_SHR_U: 4580 return op->second_is_reg ? (1u << X64_RCX) : 0u; 4581 default: 4582 return 0u; 4583 } 4584 } 4585 4586 static u32 x64_intrinsic_visible_clobbers(IntrinKind kind) { 4587 if (kind == INTRIN_UMUL_OVERFLOW || kind == INTRIN_SMUL_HIGH || 4588 kind == INTRIN_UMUL_HIGH || kind == INTRIN_READCYCLECOUNTER) 4589 return (1u << X64_RAX) | (1u << X64_RDX); 4590 if (kind == INTRIN_SYSCALL) 4591 return (1u << X64_RAX) | (1u << X64_RCX) | (1u << X64_RDX) | 4592 (1u << X64_RSI) | (1u << X64_RDI) | (1u << X64_R8) | 4593 (1u << X64_R9); 4594 return 0u; 4595 } 4596 4597 /* Physical registers each x86-64 instruction's encoding clobbers as a side 4598 * effect, so the optimizer keeps values live across them out of those registers 4599 * (the backend is then free to use them). idiv/div write rax (quotient) and rdx 4600 * (remainder/sign); a variable shift uses cl; cmpxchg/xadd loops use 4601 * rax/rcx/rdx; bitfield store uses the same trio for its read-modify-write. 4602 * All other ordinary helpers use declared results or backend-private R10/R11 4603 * and XMM14/XMM15 temporaries. */ 4604 static int x64_machine_op_clobbers(NativeTarget* t, const NativeMachineOp* op, 4605 u32 mask[NATIVE_REG_CLASS_COUNT]) { 4606 mask[0] = mask[1] = mask[2] = 0; 4607 switch ((NativeMachineOpKind)op->kind) { 4608 case NATIVE_MOP_TLS_ADDR: 4609 /* ELF Local-Exec / Windows TEB materialize the address using only the 4610 * destination register (no fixed clobbers). The Mach-O TLV descriptor 4611 * sequence loads the descriptor into %rdi and calls its resolver thunk, 4612 * which returns the storage in %rax and preserves every other register; 4613 * model the %rdi/%rax clobbers so no value lives across the access in 4614 * them. */ 4615 if (!obj_format_tls_via_descriptor(t->c)) return 0; 4616 mask[NATIVE_REG_INT] = (1u << X64_RAX) | (1u << X64_RDI); 4617 return 1; 4618 case NATIVE_MOP_BINOP: 4619 mask[NATIVE_REG_INT] = x64_binop_visible_clobbers(op); 4620 return mask[NATIVE_REG_INT] != 0u; 4621 case NATIVE_MOP_BITFIELD_LOAD: 4622 /* The only non-destination temporary is backend-private R11. */ 4623 return 0; 4624 case NATIVE_MOP_BITFIELD_STORE: 4625 mask[NATIVE_REG_INT] = 4626 (1u << X64_RAX) | (1u << X64_RCX) | (1u << X64_RDX); 4627 return 1; 4628 case NATIVE_MOP_ATOMIC_CAS: 4629 case NATIVE_MOP_ATOMIC_RMW: 4630 mask[NATIVE_REG_INT] = 4631 (1u << X64_RAX) | (1u << X64_RCX) | (1u << X64_RDX); 4632 return 1; 4633 case NATIVE_MOP_VA_START: 4634 case NATIVE_MOP_VA_ARG: 4635 return 0; 4636 case NATIVE_MOP_INTRINSIC: 4637 /* The unsigned multiply-overflow intrinsic emits a one-operand MUL, whose 4638 * rdx:rax product clobbers both registers. The signed variant uses a 4639 * two-operand IMUL (no fixed-register clobber). Linux syscall writes rax 4640 * and the CPU instruction itself clobbers rcx/r11; the kernel ABI treats 4641 * the integer caller-saved syscall registers as volatile. */ 4642 /* R8/R9 are cache-capable emitter temps, so the syscall effect includes 4643 * them. R10/R11 are pure backend-private temps and remain excluded. */ 4644 mask[NATIVE_REG_INT] = 4645 x64_intrinsic_visible_clobbers((IntrinKind)op->intrin); 4646 return mask[NATIVE_REG_INT] != 0u; 4647 case NATIVE_MOP_COUNT: 4648 break; 4649 } 4650 x64_panic(x64_of(t), "invalid machine-effect operation"); 4651 return 0; 4652 } 4653 4654 /* ============================ construction ============================ */ 4655 4656 NativeTarget* x64_native_target_new(Compiler* c, ObjBuilder* obj, 4657 MCEmitter* mc) { 4658 X64NativeTarget* a = arena_znew(c->tu, X64NativeTarget); 4659 NativeTarget* t; 4660 if (!a) return NULL; 4661 t = &a->base; 4662 t->c = c; 4663 t->obj = obj; 4664 t->mc = mc; 4665 native_frame_init(&a->frame, c); 4666 t->regs = &x64_reg_info; 4667 t->class_for_type = native_class_for_type_fp_le8; 4668 t->imm_legal = x64_imm_legal; 4669 t->addr_legal = x64_addr_legal; 4670 t->machine_op_clobbers = x64_machine_op_clobbers; 4671 t->func_begin = x64_func_begin; 4672 t->func_begin_known_frame = x64_func_begin_known_frame; 4673 t->bind_params_end = x64_bind_params_end; 4674 t->note_frame_state = NULL; 4675 /* Non-NULL so the optimizer emit path (plan_frame) computes the callee-saved 4676 * set; x64_func_begin_known_frame derives the records from the masks. */ 4677 t->reserve_callee_saves = x64_reserve_callee_saves; 4678 t->caller_saved_mask = x64_live_caller_saved_mask; 4679 t->callee_saved_mask = x64_live_callee_saved_mask; 4680 t->signature_stack_bytes = x64_signature_stack_bytes; 4681 t->call_stack_bytes = x64_call_stack_bytes; 4682 t->has_store_zero_reg = 0; 4683 t->func_end = x64_func_end; 4684 t->frame_slot = x64_frame_slot; 4685 t->release_frame_slot = x64_release_frame_slot; 4686 t->frame_slot_debug_loc = x64_frame_slot_debug_loc; 4687 t->bind_param = x64_bind_native_param; 4688 t->label_new = x64_label_new; 4689 t->label_place = x64_label_place; 4690 t->jump = x64_jump; 4691 t->cmp_branch = x64_cmp_branch; 4692 t->indirect_branch = x64_indirect_branch; 4693 t->load_label_addr = x64_load_label_addr; 4694 t->move = x64_move; 4695 t->load_imm = x64_load_imm; 4696 t->load_const = x64_load_const; 4697 t->load_addr = x64_load_addr; 4698 t->load = x64_load; 4699 t->store = x64_store; 4700 t->tls_addr_of = x64_tls_addr_of; 4701 t->copy_bytes = x64_copy_bytes; 4702 t->set_bytes = x64_set_bytes; 4703 t->bitfield_load = x64_bitfield_load; 4704 t->bitfield_store = x64_bitfield_store; 4705 t->binop = x64_binop; 4706 t->unop = x64_unop; 4707 t->cmp = x64_cmp; 4708 t->convert = x64_convert; 4709 t->binop_rr = x64_binop_rr; 4710 t->move_rr = x64_move_rr; 4711 t->cmp_rr = x64_cmp_rr; 4712 t->convert_rr = x64_convert_rr; 4713 t->alloca_ = x64_alloca; 4714 t->spill = x64_spill; 4715 t->reload = x64_reload; 4716 t->marshal_call = x64_marshal_call; 4717 t->emit_call = x64_emit_call; 4718 t->marshal_ret = x64_marshal_ret; 4719 t->ret = x64_ret; 4720 t->atomic_load = x64_atomic_load; 4721 t->atomic_store = x64_atomic_store; 4722 t->atomic_rmw = x64_atomic_rmw; 4723 t->atomic_cas = x64_atomic_cas; 4724 t->fence = x64_fence; 4725 t->va_start_ = x64_va_start_native; 4726 t->va_arg_ = x64_va_arg_native; 4727 t->va_end_ = x64_va_end_native; 4728 t->va_copy_ = x64_va_copy_native; 4729 t->intrinsic_arg_accepts_imm = x64_intrinsic_arg_accepts_imm; 4730 t->intrinsic = x64_intrinsic; 4731 t->asm_block = x64_asm_block_native; 4732 t->file_scope_asm = native_file_scope_asm; 4733 t->trap = x64_trap; 4734 t->set_loc = x64_set_loc; 4735 t->finalize = native_finalize; 4736 return t; 4737 } 4738 4739 /* ============================ NativeOps (-O0) ============================ */ 4740 4741 static void x64_bind_param(NativeDirectTarget* d, const CGParamDesc* p, 4742 CGLocal local, NativeDirectLocal* l) { 4743 NativeLoc dst; 4744 (void)local; 4745 memset(&dst, 0, sizeof dst); 4746 dst.kind = NATIVE_LOC_FRAME; 4747 dst.type = p->type; 4748 dst.v.frame = l->home; 4749 x64_bind_native_param(d->native, p, dst); 4750 } 4751 4752 /* A sibling call is realizable when its outgoing stack-argument area fits the 4753 * window the caller itself received. Register-only calls always qualify. */ 4754 static const char* x64_no_tail(NativeDirectTarget* d, const CGCallDesc* call) { 4755 X64NativeTarget* a = x64_of(d->native); 4756 NativeCallDesc nd; 4757 u32 stack; 4758 if (a->frame.ncallee_saves) 4759 return "x64 tail call: callee-saved registers in use"; 4760 native_direct_project_tail_call_desc(d, call, &nd); 4761 stack = x64_tail_call_stack_size(d->native, &nd); 4762 /* The tail footprint includes the shadow-space prefix; the caller's incoming 4763 * window has the same prefix, so compare against incoming_stack_size + it. */ 4764 if (stack > a->incoming_stack_size + a->abi->shadow_space) 4765 return "x64 tail call: stack argument area too small"; 4766 return NULL; 4767 } 4768 4769 /* Resolve a pointer-typed Operand (the address of a va_list object) into `reg`, 4770 * returning a register-based NativeAddr. */ 4771 static NativeAddr x64_direct_pointer_addr(NativeDirectTarget* d, Operand op) { 4772 X64NativeTarget* a = x64_of(d->native); 4773 NativeAddr addr; 4774 memset(&addr, 0, sizeof addr); 4775 if (op.kind == OPK_LOCAL) { 4776 emit_mov_load(a->base.mc, 8, 0, X64_R11, X64_RBP, 4777 -(i32)x64_slot_get(a, d->locals[op.v.local - 1u].home)->off); 4778 addr.base_kind = NATIVE_ADDR_BASE_REG; 4779 addr.base.reg = X64_R11; 4780 addr.base_type = op.type; 4781 return addr; 4782 } 4783 return x64_direct_materialize_addr(d, op); 4784 } 4785 4786 static NativeAddr x64_direct_va_base(NativeDirectTarget* d, Operand ap_addr, 4787 Reg reg) { 4788 NativeLoc dst = 4789 native_loc_reg(builtin_id(KIT_CG_BUILTIN_I64), NATIVE_REG_INT, reg); 4790 NativeAddr addr; 4791 d->native->load_addr(d->native, dst, x64_direct_pointer_addr(d, ap_addr)); 4792 memset(&addr, 0, sizeof addr); 4793 addr.base_kind = NATIVE_ADDR_BASE_REG; 4794 addr.cls = NATIVE_REG_INT; 4795 addr.base.reg = reg; 4796 addr.base_type = builtin_id(KIT_CG_BUILTIN_I64); 4797 return addr; 4798 } 4799 4800 static void x64_va_start_(NativeDirectTarget* d, Operand ap_addr) { 4801 /* Hold the va_list base in R11, not RAX: x64_va_start_core materializes the 4802 * gp/fp_offset and overflow/reg-save-area field values through RAX, which 4803 * would otherwise clobber the base before the field stores. */ 4804 x64_va_start_core(x64_of(d->native), x64_direct_va_base(d, ap_addr, X64_R11)); 4805 } 4806 static void x64_va_arg_(NativeDirectTarget* d, Operand dst, Operand ap_addr, 4807 KitCgTypeId type) { 4808 X64NativeTarget* a = x64_of(d->native); 4809 int is_fp = cg_type_is_float(d->base.c, type); 4810 NativeLoc res = native_loc_reg(type, is_fp ? NATIVE_REG_FP : NATIVE_REG_INT, 4811 is_fp ? X64_TMP_FP : (Reg)X64_RDX); 4812 NativeAddr dst_addr; 4813 /* Base in R11: the core advances/loads through R11 plus one GPR scratch (the 4814 * integer result reg itself, or RAX for FP results), so R11 must not be RAX. 4815 */ 4816 x64_va_arg_core(a, res, x64_direct_va_base(d, ap_addr, X64_R11), type); 4817 dst_addr = x64_direct_addr(d, dst); 4818 if (dst_addr.base_kind == NATIVE_ADDR_BASE_FRAME_VALUE) { 4819 emit_mov_load(a->base.mc, 8, 0, X64_R11, X64_RBP, 4820 -(i32)x64_slot_get(a, dst_addr.base.frame)->off); 4821 dst_addr.base_kind = NATIVE_ADDR_BASE_REG; 4822 dst_addr.base.reg = X64_R11; 4823 } 4824 x64_emit_mem( 4825 a, 0, res, dst_addr, 4826 native_mem_for_type(d->native, type, native_type_size(d->native, type))); 4827 } 4828 static void x64_va_end_(NativeDirectTarget* d, Operand ap_addr) { 4829 (void)d; 4830 (void)ap_addr; 4831 } 4832 static void x64_va_copy_(NativeDirectTarget* d, Operand dst, Operand src) { 4833 X64NativeTarget* a = x64_of(d->native); 4834 NativeAddr src_ap = x64_direct_va_base(d, src, X64_RAX); 4835 NativeAddr dst_ap = x64_direct_va_base(d, dst, X64_R11); 4836 x64_va_copy_core(a, dst_ap, src_ap); 4837 } 4838 4839 /* Hook adapters bridging the arch-typed save/restore + assembler entry to the 4840 * shared NativeAsmDirectHooks signatures. */ 4841 static void* x64_asm_hook_save_callee_clobbers(NativeDirectTarget* d, 4842 u32 int_mask, u32 fp_mask, 4843 u32* nsaved_out) { 4844 return x64_asm_save_callee_clobbers(x64_of(d->native), int_mask, fp_mask, 4845 nsaved_out); 4846 } 4847 static void x64_asm_hook_restore_one(NativeDirectTarget* d, void* saved, 4848 u32 idx) { 4849 x64_asm_restore_one(x64_of(d->native), &((X64AsmSavedClobber*)saved)[idx]); 4850 } 4851 static void x64_asm_hook_run_template(NativeDirectTarget* d, const char* tmpl, 4852 const AsmConstraint* outs, u32 nout, 4853 Operand* bound_outs, 4854 const AsmConstraint* ins, u32 nin, 4855 Operand* bound_ins, const Sym* clobbers, 4856 u32 nclob) { 4857 X64Asm* asmh = x64_asm_open(d->base.c); 4858 x64_inline_bind(asmh, outs, nout, bound_outs, ins, nin, bound_ins, clobbers, 4859 nclob); 4860 x64_asm_run_template(asmh, d->native->mc, tmpl); 4861 x64_asm_close(asmh); 4862 } 4863 4864 static void x64_direct_asm_block(NativeDirectTarget* d, const char* tmpl, 4865 const AsmConstraint* outs, u32 nout, 4866 Operand* out_ops, const AsmConstraint* ins, 4867 u32 nin, const Operand* in_ops, 4868 const Sym* clobbers, u32 nclob, 4869 u32 clobber_abi_sets) { 4870 static const NativeAsmDirectHooks hooks = { 4871 .opk_reg = X64_INLINE_OPK_REG, 4872 .opcls_fp = X64_INLINE_OPCLS_FP, 4873 .panic = x64_asm_panic, 4874 .bound_reg = x64_asm_bound_reg, 4875 .bound_mem = x64_asm_bound_mem, 4876 .clobber_masks = x64_asm_clobber_masks, 4877 .save_callee_clobbers = x64_asm_hook_save_callee_clobbers, 4878 .restore_one = x64_asm_hook_restore_one, 4879 .load_operand_to_reg = x64_direct_load_operand_to_reg, 4880 .load_address_to_reg = x64_direct_load_address_to_reg, 4881 .store_reg_to_operand = x64_direct_store_reg_to_operand, 4882 .run_template = x64_asm_hook_run_template, 4883 }; 4884 native_asm_bind_direct_operands(d, tmpl, outs, nout, out_ops, ins, nin, 4885 in_ops, clobbers, nclob, clobber_abi_sets, 4886 &hooks); 4887 } 4888 4889 static const NativeOps x64_direct_ops = { 4890 .bind_param = x64_bind_param, 4891 .tail_call_unrealizable_reason = x64_no_tail, 4892 .va_start_ = x64_va_start_, 4893 .va_arg_ = x64_va_arg_, 4894 .va_end_ = x64_va_end_, 4895 .va_copy_ = x64_va_copy_, 4896 .asm_block = x64_direct_asm_block, 4897 }; 4898 4899 const NativeOps* x64_native_direct_ops(void) { return &x64_direct_ops; }