code wiki / (root) / nx_x86_64.nx

nx_x86_64.nx source

↩ module page · 1065 lines · 33162 B

1// nx_x86_64.nx -- x86_64 Linux SysV asm emitter (foundation; session 1). 2// 3// Pure NishiLang port of nxc2/x86_64.c. Lives outside the C side per 4// cardinal feedback-no-nxc2-c-extension-only-nishilang-forward. This 5// file is the FOUNDATION layer; sessions 2-N fill in the per-opcode 6// IR-to-asm dispatch and wire into nx_nxc.nx as --target x86_64. 7// 8// Session 1 scope (this file): 9// * x86_64 register name table (SysV ABI) 10// * function prologue / epilogue (pushq rbp / movq rsp,rbp / subq) 11// * .text / .rodata section directives 12// * label emission 13// * literal constant load (movabsq $imm, %reg) 14// * register-to-register move (movq %src, %dst) 15// * RIP-relative address load (leaq label(%rip), %reg) 16// * add / sub register-to-register (addq / subq) 17// * syscall (the most useful primitive -- emits the 'syscall' insn) 18// * .asciz string literal emission with byte-escape 19// 20// Sessions 2-N (deferred): 21// * IR-instruction dispatch (binop / cmp / branch / call / load / 22// store / GEP / phi / return / tail_call / inline_asm) 23// * stack-machine slot layout per nx_x86_64.c 24// * regalloc integration (linear-scan with SysV-preserved regs) 25// * AVX2 SIMD (vmovdqu / vpaddd / vpsubd / vpmullq for i32x8 etc.) 26// * full nx_nxc.nx wiring as --target x86_64 dispatch 27// 28// Output format: AT&T syntax (movabsq / addq / .quad), Linux SysV ABI, 29// PIC-aware (leaq label(%rip)). Byte-compatible with `gcc` / `as`. 30// 31// genealogy_id: nxc2_x86_64_c_2026 + sysv_x86_64_abi_v0_99_6 32// lineage_id: nx_x86_64_foundation_v1 33// 34// nx_safety_envelope: 35// intended_use: "Foundation emit library for x86_64 Linux SysV 36// assembly text. Per-primitive functions that 37// write to a shared OutBuf. Session 1 of N for 38// the full IR-driven backend." 39// sil_target: SIL3 (codegen correctness; silent 40// miscompilation is worse than crash) 41// asil_target: QM 42// dal_target: DAL B 43// iec_62304_class: NONE 44// evidence: [no_floating_point_in_logic, 45// att_syntax_only_no_intel_mode, 46// sysv_abi_documented_per_function, 47// rip_relative_pic_aware, 48// bounded_loops_in_string_emit] 49// hazard_register: [bug-tape-syscall-num-not-set-rax, 50// bug-tape-frame-not-16-byte-aligned, 51// bug-tape-ret-without-restore] 52// residual_risk: "Skeleton subset (foundation); does NOT yet 53// cover every IR opcode -- caller must hand- 54// sequence the primitives. Full coverage is 55// the remaining sessions 2-N work." 56// verdict: NOT_YET_EVALUATED 57 58import "nx_syscalls.nx" 59import "nx_outbuf.nx" 60 61// ===== SysV ABI register names ==================================== 62// 63// First 6 integer arguments: rdi, rsi, rdx, rcx, r8, r9. Return in 64// rax. Frame anchor: rbp. Stack pointer: rsp. Callee-saved: 65// rbx, rbp, r12, r13, r14, r15. 66 67const NX_X64_N_ARG_REGS: i64 = 6 68 69func x86_arg_reg_name(idx: i64) -> *u8 { 70 if idx == 0 { return "rdi" as *u8 } 71 if idx == 1 { return "rsi" as *u8 } 72 if idx == 2 { return "rdx" as *u8 } 73 if idx == 3 { return "rcx" as *u8 } 74 if idx == 4 { return "r8" as *u8 } 75 if idx == 5 { return "r9" as *u8 } 76 return 0 as *u8 77} 78 79// Linux x86_64 syscall convention reuses arg regs EXCEPT rcx (which 80// gets clobbered by syscall). Position 3 becomes r10. 81 82func x86_syscall_arg_reg_name(idx: i64) -> *u8 { 83 if idx == 0 { return "rdi" as *u8 } 84 if idx == 1 { return "rsi" as *u8 } 85 if idx == 2 { return "rdx" as *u8 } 86 if idx == 3 { return "r10" as *u8 } 87 if idx == 4 { return "r8" as *u8 } 88 if idx == 5 { return "r9" as *u8 } 89 return 0 as *u8 90} 91 92// ===== indent + small helpers ===================================== 93 94func x86_indent(o: *OutBuf) -> i64 { 95 out_char(o, 0x20) 96 out_char(o, 0x20) 97 out_char(o, 0x20) 98 out_char(o, 0x20) 99 return 0 100} 101 102func x86_pct_reg(o: *OutBuf, reg: *u8) -> i64 { 103 out_char(o, 0x25) // '%' 104 out_str(o, reg) 105 return 0 106} 107 108// ===== section directives ========================================= 109 110func x86_emit_section_text(o: *OutBuf) -> i64 { 111 out_str(o, " .text\n") 112 return 0 113} 114 115func x86_emit_section_rodata(o: *OutBuf) -> i64 { 116 out_str(o, " .section .rodata\n") 117 return 0 118} 119 120// ===== labels + function symbols ================================== 121 122func x86_emit_label(o: *OutBuf, name: *u8) -> i64 { 123 out_str(o, name) 124 out_char(o, 0x3A) // ':' 125 out_char(o, 0x0A) 126 return 0 127} 128 129func x86_emit_function_start(o: *OutBuf, name: *u8) -> i64 { 130 x86_emit_section_text(o) 131 out_str(o, " .globl ") 132 out_str(o, name) 133 out_char(o, 0x0A) 134 out_str(o, " .type ") 135 out_str(o, name) 136 out_str(o, ", @function\n") 137 x86_emit_label(o, name) 138 return 0 139} 140 141func x86_emit_function_end(o: *OutBuf, name: *u8) -> i64 { 142 out_str(o, " .size ") 143 out_str(o, name) 144 out_str(o, ", .-") 145 out_str(o, name) 146 out_char(o, 0x0A) 147 return 0 148} 149 150// ===== string literal emission ==================================== 151// 152// Writes `.asciz "..."` with byte-escape for double quotes, 153// backslashes, newlines, tabs, and non-printable bytes (rendered 154// as octal escapes \NNN). Caller passes the raw byte length. 155 156func x86_emit_asciz(o: *OutBuf, s: *u8, n: i64) -> i64 { 157 out_str(o, " .asciz \"") 158 var i: i64 = 0 159 let BUDGET: i64 = n + 2 160 var iter: i64 = 0 161 while i < n { 162 if iter >= BUDGET { i = n } 163 if i < n { 164 let c: i64 = s[i] 165 if c == 0x22 { // '"' 166 out_char(o, 0x5C); out_char(o, 0x22) 167 } 168 if c == 0x5C { // '\' 169 out_char(o, 0x5C); out_char(o, 0x5C) 170 } 171 if c == 0x0A { 172 out_char(o, 0x5C); out_char(o, 0x6E) 173 } 174 if c == 0x09 { 175 out_char(o, 0x5C); out_char(o, 0x74) 176 } 177 if c == 0x0D { 178 out_char(o, 0x5C); out_char(o, 0x72) 179 } 180 if c == 0x22 { i = i + 0 } // sink to keep flow 181 if c != 0x22 { 182 if c != 0x5C { 183 if c != 0x0A { 184 if c != 0x09 { 185 if c != 0x0D { 186 if c >= 0x20 { 187 if c < 0x7F { 188 out_char(o, c) 189 } 190 } 191 if c < 0x20 { 192 out_char(o, 0x5C) 193 out_char(o, 0x30 + ((c >> 6) & 7)) 194 out_char(o, 0x30 + ((c >> 3) & 7)) 195 out_char(o, 0x30 + (c & 7)) 196 } 197 if c >= 0x7F { 198 out_char(o, 0x5C) 199 out_char(o, 0x30 + ((c >> 6) & 7)) 200 out_char(o, 0x30 + ((c >> 3) & 7)) 201 out_char(o, 0x30 + (c & 7)) 202 } 203 } 204 } 205 } 206 } 207 } 208 i = i + 1 209 } 210 iter = iter + 1 211 } 212 out_str(o, "\"\n") 213 return 0 214} 215 216// ===== function prologue / epilogue =============================== 217// 218// SysV requires 16-byte aligned rsp at the CALL instruction. After 219// the `call` pushes ra (8 bytes) and we `pushq rbp` (another 8), 220// rsp is again 16-aligned. We then `subq $frame, rsp` -- frame 221// must be a multiple of 16 to keep alignment for any nested calls. 222 223func x86_round_up_16(n: i64) -> i64 { 224 return (n + 15) & (0 - 16) 225} 226 227func x86_emit_prologue(o: *OutBuf, frame_size: i64) -> i64 { 228 let f: i64 = x86_round_up_16(frame_size) 229 out_str(o, " pushq %rbp\n") 230 out_str(o, " movq %rsp, %rbp\n") 231 if f > 0 { 232 out_str(o, " subq $") 233 out_i64(o, f) 234 out_str(o, ", %rsp\n") 235 } 236 return 0 237} 238 239func x86_emit_epilogue(o: *OutBuf) -> i64 { 240 out_str(o, " movq %rbp, %rsp\n") 241 out_str(o, " popq %rbp\n") 242 out_str(o, " ret\n") 243 return 0 244} 245 246// ===== constant load ============================================== 247// 248// movabsq accepts a full 64-bit immediate, so we don't need to split 249// into mov32 + movabs. imm is the value as i64. 250 251func x86_emit_movabsq(o: *OutBuf, reg: *u8, imm: i64) -> i64 { 252 out_str(o, " movabsq $") 253 out_i64(o, imm) 254 out_str(o, ", %") 255 out_str(o, reg) 256 out_char(o, 0x0A) 257 return 0 258} 259 260// ===== register-to-register move ================================== 261 262func x86_emit_movq_reg_reg(o: *OutBuf, src: *u8, dst: *u8) -> i64 { 263 out_str(o, " movq %") 264 out_str(o, src) 265 out_str(o, ", %") 266 out_str(o, dst) 267 out_char(o, 0x0A) 268 return 0 269} 270 271// ===== RIP-relative address load ================================== 272// 273// PIC-aware: leaq label(%rip), %reg. Used to materialize the 274// address of a global string / function / .rodata entry. 275 276func x86_emit_leaq_rip(o: *OutBuf, label: *u8, reg: *u8) -> i64 { 277 out_str(o, " leaq ") 278 out_str(o, label) 279 out_str(o, "(%rip), %") 280 out_str(o, reg) 281 out_char(o, 0x0A) 282 return 0 283} 284 285// ===== add / sub register-to-register ============================= 286// 287// addq %src, %dst computes dst = dst + src (AT&T destination on 288// the right). Same for subq: dst = dst - src. 289 290func x86_emit_addq_rr(o: *OutBuf, src: *u8, dst: *u8) -> i64 { 291 out_str(o, " addq %") 292 out_str(o, src) 293 out_str(o, ", %") 294 out_str(o, dst) 295 out_char(o, 0x0A) 296 return 0 297} 298 299func x86_emit_subq_rr(o: *OutBuf, src: *u8, dst: *u8) -> i64 { 300 out_str(o, " subq %") 301 out_str(o, src) 302 out_str(o, ", %") 303 out_str(o, dst) 304 out_char(o, 0x0A) 305 return 0 306} 307 308// ===== syscall ==================================================== 309// 310// On x86_64 Linux the syscall instruction uses: 311// rax = syscall number 312// rdi rsi rdx r10 r8 r9 = args 0..5 313// clobbers: rax (return), rcx (saved RIP), r11 (saved RFLAGS) 314// 315// Caller must load rax (syscall number) and the arg regs before 316// calling this helper -- it just emits the bare instruction. 317 318func x86_emit_syscall(o: *OutBuf) -> i64 { 319 out_str(o, " syscall\n") 320 return 0 321} 322 323// ===== whole-syscall convenience ================================== 324// 325// Loads the syscall number + up to 6 immediate args, then emits the 326// syscall. This is a CONVENIENCE for hand-written test programs; 327// the IR-driven backend will set args from value slots instead. 328 329func x86_emit_syscall_imm(o: *OutBuf, num: i64, n_args: i64, 330 a0: i64, a1: i64, a2: i64, 331 a3: i64, a4: i64, a5: i64) -> i64 { 332 if n_args > 0 { x86_emit_movabsq(o, "rdi" as *u8, a0) } 333 if n_args > 1 { x86_emit_movabsq(o, "rsi" as *u8, a1) } 334 if n_args > 2 { x86_emit_movabsq(o, "rdx" as *u8, a2) } 335 if n_args > 3 { x86_emit_movabsq(o, "r10" as *u8, a3) } 336 if n_args > 4 { x86_emit_movabsq(o, "r8" as *u8, a4) } 337 if n_args > 5 { x86_emit_movabsq(o, "r9" as *u8, a5) } 338 x86_emit_movabsq(o, "rax" as *u8, num) 339 x86_emit_syscall(o) 340 return 0 341} 342 343// ===== GNU-stack note (required by modern ld) ==================== 344 345func x86_emit_gnu_stack_note(o: *OutBuf) -> i64 { 346 out_str(o, "\n .section .note.GNU-stack,\"\",@progbits\n") 347 return 0 348} 349 350// ===== sealed Linux x86_64 syscall numbers (the foundational few) = 351 352const NX_X64_SYS_READ: i64 = 0 353const NX_X64_SYS_WRITE: i64 = 1 354const NX_X64_SYS_OPEN: i64 = 2 355const NX_X64_SYS_CLOSE: i64 = 3 356const NX_X64_SYS_MMAP: i64 = 9 357const NX_X64_SYS_EXIT: i64 = 60 358const NX_X64_SYS_EXIT_GROUP: i64 = 231 // terminates ALL tasks; _start uses this (threaded runtime, 2026-07-07) 359 360// ===== narrow-register name helpers (session 2) =================== 361// 362// x86_64 GPRs have 32-bit / 16-bit / 8-bit aliases. Stores of 363// sub-qword widths take the appropriate alias as the source operand 364// (movb %al / movw %ax / movl %eax / movq %rax). We expose helpers 365// for the regs the IR codegen actually uses: rax, rcx, rdx, rbx, 366// rdi, rsi, r8, r9, r10, r11. 367// 368// Returns 0 as *u8 for unknown input (caller treats as bug). 369 370func x86_reg_low32(reg: *u8) -> *u8 { 371 if reg[0] == 0x72 { // 'r' 372 if reg[1] == 0x61 { return "eax" as *u8 } // rax 373 if reg[1] == 0x62 { return "ebx" as *u8 } // rbx 374 if reg[1] == 0x63 { return "ecx" as *u8 } // rcx 375 if reg[1] == 0x64 { 376 if reg[2] == 0x78 { return "edx" as *u8 } // rdx 377 if reg[2] == 0x69 { return "edi" as *u8 } // rdi 378 } 379 if reg[1] == 0x73 { return "esi" as *u8 } // rsi (rs prefix; check 'i' below) 380 if reg[1] == 0x38 { return "r8d" as *u8 } 381 if reg[1] == 0x39 { return "r9d" as *u8 } 382 if reg[1] == 0x31 { 383 if reg[2] == 0x30 { return "r10d" as *u8 } 384 if reg[2] == 0x31 { return "r11d" as *u8 } 385 } 386 } 387 return 0 as *u8 388} 389 390func x86_reg_low16(reg: *u8) -> *u8 { 391 if reg[0] == 0x72 { 392 if reg[1] == 0x61 { return "ax" as *u8 } 393 if reg[1] == 0x62 { return "bx" as *u8 } 394 if reg[1] == 0x63 { return "cx" as *u8 } 395 if reg[1] == 0x64 { 396 if reg[2] == 0x78 { return "dx" as *u8 } 397 if reg[2] == 0x69 { return "di" as *u8 } 398 } 399 if reg[1] == 0x73 { return "si" as *u8 } 400 if reg[1] == 0x38 { return "r8w" as *u8 } 401 if reg[1] == 0x39 { return "r9w" as *u8 } 402 if reg[1] == 0x31 { 403 if reg[2] == 0x30 { return "r10w" as *u8 } 404 if reg[2] == 0x31 { return "r11w" as *u8 } 405 } 406 } 407 return 0 as *u8 408} 409 410func x86_reg_low8(reg: *u8) -> *u8 { 411 if reg[0] == 0x72 { 412 if reg[1] == 0x61 { return "al" as *u8 } 413 if reg[1] == 0x62 { return "bl" as *u8 } 414 if reg[1] == 0x63 { return "cl" as *u8 } 415 if reg[1] == 0x64 { 416 if reg[2] == 0x78 { return "dl" as *u8 } 417 if reg[2] == 0x69 { return "dil" as *u8 } 418 } 419 if reg[1] == 0x73 { return "sil" as *u8 } 420 if reg[1] == 0x38 { return "r8b" as *u8 } 421 if reg[1] == 0x39 { return "r9b" as *u8 } 422 if reg[1] == 0x31 { 423 if reg[2] == 0x30 { return "r10b" as *u8 } 424 if reg[2] == 0x31 { return "r11b" as *u8 } 425 } 426 } 427 return 0 as *u8 428} 429 430// ===== memory operand helper ====================================== 431// 432// Emits `disp(%base)` form. When disp is 0 emits just `(%base)`. 433 434func x86_emit_mem_disp(o: *OutBuf, disp: i64, base: *u8) -> i64 { 435 if disp != 0 { 436 out_i64(o, disp) 437 } 438 out_char(o, 0x28) // '(' 439 out_char(o, 0x25) // '%' 440 out_str(o, base) 441 out_char(o, 0x29) // ')' 442 return 0 443} 444 445// ===== load primitives (session 2) ================================ 446// 447// Five widths times two signedness = nine total entrypoints (signed 448// + unsigned at 1/2/4 bytes; 8 bytes has only one form). Destination 449// is always a full 64-bit register; sign extension is implicit in 450// the chosen mnemonic. 451 452func x86_emit_load_qword(o: *OutBuf, base: *u8, disp: i64, dst: *u8) -> i64 { 453 out_str(o, " movq ") 454 x86_emit_mem_disp(o, disp, base) 455 out_str(o, ", %") 456 out_str(o, dst) 457 out_char(o, 0x0A) 458 return 0 459} 460 461func x86_emit_load_dword_signed(o: *OutBuf, base: *u8, disp: i64, 462 dst: *u8) -> i64 { 463 out_str(o, " movslq ") 464 x86_emit_mem_disp(o, disp, base) 465 out_str(o, ", %") 466 out_str(o, dst) 467 out_char(o, 0x0A) 468 return 0 469} 470 471func x86_emit_load_dword_unsigned(o: *OutBuf, base: *u8, disp: i64, 472 dst: *u8) -> i64 { 473 let dst32: *u8 = x86_reg_low32(dst) 474 out_str(o, " movl ") 475 x86_emit_mem_disp(o, disp, base) 476 out_str(o, ", %") 477 out_str(o, dst32) 478 out_char(o, 0x0A) 479 return 0 480} 481 482func x86_emit_load_word_signed(o: *OutBuf, base: *u8, disp: i64, 483 dst: *u8) -> i64 { 484 out_str(o, " movswq ") 485 x86_emit_mem_disp(o, disp, base) 486 out_str(o, ", %") 487 out_str(o, dst) 488 out_char(o, 0x0A) 489 return 0 490} 491 492func x86_emit_load_word_unsigned(o: *OutBuf, base: *u8, disp: i64, 493 dst: *u8) -> i64 { 494 out_str(o, " movzwq ") 495 x86_emit_mem_disp(o, disp, base) 496 out_str(o, ", %") 497 out_str(o, dst) 498 out_char(o, 0x0A) 499 return 0 500} 501 502func x86_emit_load_byte_signed(o: *OutBuf, base: *u8, disp: i64, 503 dst: *u8) -> i64 { 504 out_str(o, " movsbq ") 505 x86_emit_mem_disp(o, disp, base) 506 out_str(o, ", %") 507 out_str(o, dst) 508 out_char(o, 0x0A) 509 return 0 510} 511 512func x86_emit_load_byte_unsigned(o: *OutBuf, base: *u8, disp: i64, 513 dst: *u8) -> i64 { 514 out_str(o, " movzbq ") 515 x86_emit_mem_disp(o, disp, base) 516 out_str(o, ", %") 517 out_str(o, dst) 518 out_char(o, 0x0A) 519 return 0 520} 521 522// ===== store primitives (session 2) =============================== 523// 524// Four widths. The source operand is the appropriate sub-register 525// alias (al / ax / eax / rax). 526 527func x86_emit_store_qword(o: *OutBuf, src: *u8, base: *u8, disp: i64) -> i64 { 528 out_str(o, " movq %") 529 out_str(o, src) 530 out_str(o, ", ") 531 x86_emit_mem_disp(o, disp, base) 532 out_char(o, 0x0A) 533 return 0 534} 535 536func x86_emit_store_dword(o: *OutBuf, src: *u8, base: *u8, disp: i64) -> i64 { 537 let src32: *u8 = x86_reg_low32(src) 538 out_str(o, " movl %") 539 out_str(o, src32) 540 out_str(o, ", ") 541 x86_emit_mem_disp(o, disp, base) 542 out_char(o, 0x0A) 543 return 0 544} 545 546func x86_emit_store_word(o: *OutBuf, src: *u8, base: *u8, disp: i64) -> i64 { 547 let src16: *u8 = x86_reg_low16(src) 548 out_str(o, " movw %") 549 out_str(o, src16) 550 out_str(o, ", ") 551 x86_emit_mem_disp(o, disp, base) 552 out_char(o, 0x0A) 553 return 0 554} 555 556func x86_emit_store_byte(o: *OutBuf, src: *u8, base: *u8, disp: i64) -> i64 { 557 let src8: *u8 = x86_reg_low8(src) 558 out_str(o, " movb %") 559 out_str(o, src8) 560 out_str(o, ", ") 561 x86_emit_mem_disp(o, disp, base) 562 out_char(o, 0x0A) 563 return 0 564} 565 566// ===== GEP (getelementptr) ======================================= 567// 568// nxc2's IR encodes GEP as base + i64-byte-offset (no scaling). 569// x86_64.c emits it as `addq %off, %base` -- a one-instruction 570// pointer add. When the offset is a compile-time constant we 571// could collapse to leaq, but for parity with the C reference we 572// emit the add form too. 573 574func x86_emit_gep_add(o: *OutBuf, base_reg: *u8, off_reg: *u8) -> i64 { 575 out_str(o, " addq %") 576 out_str(o, off_reg) 577 out_str(o, ", %") 578 out_str(o, base_reg) 579 out_char(o, 0x0A) 580 return 0 581} 582 583// Convenience: emit `leaq disp(%base), %dst` when GEP offset is 584// known at codegen time. Useful for struct-field access patterns. 585 586func x86_emit_lea_disp(o: *OutBuf, base: *u8, disp: i64, dst: *u8) -> i64 { 587 out_str(o, " leaq ") 588 x86_emit_mem_disp(o, disp, base) 589 out_str(o, ", %") 590 out_str(o, dst) 591 out_char(o, 0x0A) 592 return 0 593} 594 595// ===== sealed condition codes (session 3) ========================= 596// 597// x86_64 jcc / setcc condition suffix table. Match nxc2 IR opcode 598// names per x86_64.c:cc_for. 599 600const NX_X64_CC_EQ: i64 = 0 601const NX_X64_CC_NE: i64 = 1 602const NX_X64_CC_LT_S: i64 = 2 603const NX_X64_CC_LE_S: i64 = 3 604const NX_X64_CC_GT_S: i64 = 4 605const NX_X64_CC_GE_S: i64 = 5 606const NX_X64_CC_LT_U: i64 = 6 607const NX_X64_CC_LE_U: i64 = 7 608const NX_X64_CC_GT_U: i64 = 8 609const NX_X64_CC_GE_U: i64 = 9 610 611func x86_cc_suffix(cc: i64) -> *u8 { 612 if cc == NX_X64_CC_EQ { return "e" as *u8 } 613 if cc == NX_X64_CC_NE { return "ne" as *u8 } 614 if cc == NX_X64_CC_LT_S { return "l" as *u8 } 615 if cc == NX_X64_CC_LE_S { return "le" as *u8 } 616 if cc == NX_X64_CC_GT_S { return "g" as *u8 } 617 if cc == NX_X64_CC_GE_S { return "ge" as *u8 } 618 if cc == NX_X64_CC_LT_U { return "b" as *u8 } 619 if cc == NX_X64_CC_LE_U { return "be" as *u8 } 620 if cc == NX_X64_CC_GT_U { return "a" as *u8 } 621 if cc == NX_X64_CC_GE_U { return "ae" as *u8 } 622 return 0 as *u8 623} 624 625// ===== compare + test ============================================ 626// 627// AT&T semantics: `cmpq %src1, %src2` computes `src2 - src1` and 628// sets flags. So a subsequent `jl label` jumps when src2 < src1. 629// This is the OPPOSITE of Intel-syntax order; callers must remember. 630// 631// `testq %a, %b` is logical AND of a and b without storing the 632// result; sets ZF. Used for "is reg zero" with `testq %r, %r`. 633 634func x86_emit_cmpq_rr(o: *OutBuf, src1: *u8, src2: *u8) -> i64 { 635 out_str(o, " cmpq %") 636 out_str(o, src1) 637 out_str(o, ", %") 638 out_str(o, src2) 639 out_char(o, 0x0A) 640 return 0 641} 642 643func x86_emit_cmpq_imm(o: *OutBuf, imm: i64, src: *u8) -> i64 { 644 out_str(o, " cmpq $") 645 out_i64(o, imm) 646 out_str(o, ", %") 647 out_str(o, src) 648 out_char(o, 0x0A) 649 return 0 650} 651 652func x86_emit_testq_rr(o: *OutBuf, src1: *u8, src2: *u8) -> i64 { 653 out_str(o, " testq %") 654 out_str(o, src1) 655 out_str(o, ", %") 656 out_str(o, src2) 657 out_char(o, 0x0A) 658 return 0 659} 660 661// ===== setcc + zero-extend ======================================== 662// 663// `set<cc> %al` sets the low byte of rax based on flags; we then 664// `movzbq %al, %rax` to materialise the i64 boolean result. 665 666func x86_emit_setcc(o: *OutBuf, cc: i64, dst8: *u8) -> i64 { 667 out_str(o, " set") 668 out_str(o, x86_cc_suffix(cc)) 669 out_str(o, " %") 670 out_str(o, dst8) 671 out_char(o, 0x0A) 672 return 0 673} 674 675func x86_emit_movzbq_rr(o: *OutBuf, src8: *u8, dst: *u8) -> i64 { 676 out_str(o, " movzbq %") 677 out_str(o, src8) 678 out_str(o, ", %") 679 out_str(o, dst) 680 out_char(o, 0x0A) 681 return 0 682} 683 684// ===== unconditional + conditional branches ======================= 685 686func x86_emit_jmp_label(o: *OutBuf, label: *u8) -> i64 { 687 out_str(o, " jmp ") 688 out_str(o, label) 689 out_char(o, 0x0A) 690 return 0 691} 692 693func x86_emit_jcc_label(o: *OutBuf, cc: i64, label: *u8) -> i64 { 694 out_str(o, " j") 695 out_str(o, x86_cc_suffix(cc)) 696 out_char(o, 0x20) 697 out_str(o, label) 698 out_char(o, 0x0A) 699 return 0 700} 701 702// ===== arithmetic (session 4) ===================================== 703// 704// Signed multiply, signed/unsigned divide, sign-extend, bitwise ops, 705// and shifts. 706// 707// x86_64 quirks the caller must respect: 708// * imulq %src, %dst computes dst = dst * src (in-place). 709// * idivq %src computes rax = rdx:rax / src, rdx = remainder. 710// Caller MUST sign-extend rax into rdx first via 711// cqo (or zero-extend via `xorq %rdx, %rdx` for 712// the unsigned form `divq`). 713// * shift counts go in %cl (the low byte of rcx). Shift-by-imm is 714// emitted as a separate primitive. 715// 716// negq / notq are one-operand instructions on a register. 717 718func x86_emit_imulq_rr(o: *OutBuf, src: *u8, dst: *u8) -> i64 { 719 out_str(o, " imulq %") 720 out_str(o, src) 721 out_str(o, ", %") 722 out_str(o, dst) 723 out_char(o, 0x0A) 724 return 0 725} 726 727// Unsigned 64x64 -> 128 multiply (G2): `mulq %src` computes rdx:rax = rax * src. 728// The low 64 bits land in rax, the high 64 in rdx. One operand only (rax is 729// the implicit multiplicand). Used for OP_UMULHI (the rdx half). 730func x86_emit_mulq_r(o: *OutBuf, src: *u8) -> i64 { 731 out_str(o, " mulq %") 732 out_str(o, src) 733 out_char(o, 0x0A) 734 return 0 735} 736 737func x86_emit_cqo(o: *OutBuf) -> i64 { 738 out_str(o, " cqo\n") 739 return 0 740} 741 742// Hardware CRC-32C accumulate (SSE4.2): `crc32q %src,%dst` computes 743// dst = CRC32C(dst, src). In-place on dst (the running accumulator), 744// src is the 64-bit data word folded in. Emitted for OP_CRC32. 745func x86_emit_crc32q_rr(o: *OutBuf, src: *u8, dst: *u8) -> i64 { 746 out_str(o, " crc32q %") 747 out_str(o, src) 748 out_str(o, ", %") 749 out_str(o, dst) 750 out_char(o, 0x0A) 751 return 0 752} 753 754// BMI2 parallel bit DEPOSIT: `pdep %src2,%src1,%dst` deposits the low bits of 755// src1 into the set-bit positions of the mask src2, result in dst. AT&T 756// 3-operand src2,src1,dst. Emitted for OP_PDEP (__pdep64(value, mask)): 757// src1 = the value, src2 = the mask. 758func x86_emit_pdep_rrr(o: *OutBuf, src2: *u8, src1: *u8, dst: *u8) -> i64 { 759 out_str(o, " pdep %") 760 out_str(o, src2) 761 out_str(o, ", %") 762 out_str(o, src1) 763 out_str(o, ", %") 764 out_str(o, dst) 765 out_char(o, 0x0A) 766 return 0 767} 768 769// BMI2 parallel bit EXTRACT: `pext %src2,%src1,%dst` gathers the src1 bits at 770// the set-bit positions of the mask src2 down to the low bits of dst (the 771// inverse of pdep). Emitted for OP_PEXT (__pext64(value, mask)). 772func x86_emit_pext_rrr(o: *OutBuf, src2: *u8, src1: *u8, dst: *u8) -> i64 { 773 out_str(o, " pext %") 774 out_str(o, src2) 775 out_str(o, ", %") 776 out_str(o, src1) 777 out_str(o, ", %") 778 out_str(o, dst) 779 out_char(o, 0x0A) 780 return 0 781} 782 783func x86_emit_idivq_r(o: *OutBuf, src: *u8) -> i64 { 784 out_str(o, " idivq %") 785 out_str(o, src) 786 out_char(o, 0x0A) 787 return 0 788} 789 790func x86_emit_divq_r(o: *OutBuf, src: *u8) -> i64 { 791 out_str(o, " divq %") 792 out_str(o, src) 793 out_char(o, 0x0A) 794 return 0 795} 796 797func x86_emit_xorq_rr(o: *OutBuf, src: *u8, dst: *u8) -> i64 { 798 out_str(o, " xorq %") 799 out_str(o, src) 800 out_str(o, ", %") 801 out_str(o, dst) 802 out_char(o, 0x0A) 803 return 0 804} 805 806func x86_emit_andq_rr(o: *OutBuf, src: *u8, dst: *u8) -> i64 { 807 out_str(o, " andq %") 808 out_str(o, src) 809 out_str(o, ", %") 810 out_str(o, dst) 811 out_char(o, 0x0A) 812 return 0 813} 814 815func x86_emit_orq_rr(o: *OutBuf, src: *u8, dst: *u8) -> i64 { 816 out_str(o, " orq %") 817 out_str(o, src) 818 out_str(o, ", %") 819 out_str(o, dst) 820 out_char(o, 0x0A) 821 return 0 822} 823 824func x86_emit_negq_r(o: *OutBuf, dst: *u8) -> i64 { 825 out_str(o, " negq %") 826 out_str(o, dst) 827 out_char(o, 0x0A) 828 return 0 829} 830 831func x86_emit_notq_r(o: *OutBuf, dst: *u8) -> i64 { 832 out_str(o, " notq %") 833 out_str(o, dst) 834 out_char(o, 0x0A) 835 return 0 836} 837 838// Shifts: count from %cl (low byte of rcx). Caller must load count 839// into rcx first. 840 841func x86_emit_shlq_cl(o: *OutBuf, dst: *u8) -> i64 { 842 out_str(o, " shlq %cl, %") 843 out_str(o, dst) 844 out_char(o, 0x0A) 845 return 0 846} 847 848func x86_emit_sarq_cl(o: *OutBuf, dst: *u8) -> i64 { 849 out_str(o, " sarq %cl, %") 850 out_str(o, dst) 851 out_char(o, 0x0A) 852 return 0 853} 854 855func x86_emit_shrq_cl(o: *OutBuf, dst: *u8) -> i64 { 856 out_str(o, " shrq %cl, %") 857 out_str(o, dst) 858 out_char(o, 0x0A) 859 return 0 860} 861 862// Rotates: count from %cl. Single-instruction ROLQ/RORQ (Intel SDM 863// vol 2 ROL/ROR). Matches __rotl64 / __rotr64 builtins -- C 864// bootstrap parity with x86_64.c OP_ROTL64/OP_ROTR64. 865func x86_emit_rolq_cl(o: *OutBuf, dst: *u8) -> i64 { 866 out_str(o, " rolq %cl, %") 867 out_str(o, dst) 868 out_char(o, 0x0A) 869 return 0 870} 871 872func x86_emit_rorq_cl(o: *OutBuf, dst: *u8) -> i64 { 873 out_str(o, " rorq %cl, %") 874 out_str(o, dst) 875 out_char(o, 0x0A) 876 return 0 877} 878 879// Scalar bit unops on %rax -- C bootstrap parity (x86_64.c). 880// bswapq: full 64-bit byte reverse (i486 1989+). 881func x86_emit_bswapq_rax(o: *OutBuf) -> i64 { 882 out_str(o, " bswapq %rax\n") 883 return 0 884} 885 886// popcntq: population count (SSE4.2 2008+); rax <- popcount(rax). 887func x86_emit_popcntq_rax(o: *OutBuf) -> i64 { 888 out_str(o, " popcntq %rax, %rax\n") 889 return 0 890} 891 892// lzcntl: count leading zeros of low 32 bits, 32 if input 0 (BMI1 893// 2013+). Writes 32-bit eax (zero-extends to rax). Matches 894// __builtin_clz / nx_clz32 semantics. 895func x86_emit_lzcntl_eax(o: *OutBuf) -> i64 { 896 out_str(o, " lzcntl %eax, %eax\n") 897 return 0 898} 899 900// tzcntl: count trailing zeros of low 32 bits, 32 if input 0 (BMI1). 901func x86_emit_tzcntl_eax(o: *OutBuf) -> i64 { 902 out_str(o, " tzcntl %eax, %eax\n") 903 return 0 904} 905 906// Atomic family -- C bootstrap parity (x86_64.c). Address always in 907// %r11 (caller-saved, not a SysV arg reg). Conservative-strong 908// ordering for every memory order (always correct on x86 TSO). 909func x86_emit_xchgq_rax_mem_r11(o: *OutBuf) -> i64 { 910 out_str(o, " xchgq %rax, (%r11)\n") // atomic store (seq-cst) 911 return 0 912} 913func x86_emit_lock_cmpxchgq_rcx_mem_r11(o: *OutBuf) -> i64 { 914 out_str(o, " lock cmpxchgq %rcx, (%r11)\n") 915 return 0 916} 917func x86_emit_sete_al(o: *OutBuf) -> i64 { 918 out_str(o, " sete %al\n") 919 return 0 920} 921func x86_emit_lock_xaddq_rax_mem_r11(o: *OutBuf) -> i64 { 922 out_str(o, " lock xaddq %rax, (%r11)\n") 923 return 0 924} 925func x86_emit_mfence(o: *OutBuf) -> i64 { 926 out_str(o, " mfence\n") 927 return 0 928} 929 930// Shift by immediate (preferred when count is a compile-time const). 931 932func x86_emit_shlq_imm(o: *OutBuf, n: i64, dst: *u8) -> i64 { 933 out_str(o, " shlq $") 934 out_i64(o, n) 935 out_str(o, ", %") 936 out_str(o, dst) 937 out_char(o, 0x0A) 938 return 0 939} 940 941func x86_emit_sarq_imm(o: *OutBuf, n: i64, dst: *u8) -> i64 { 942 out_str(o, " sarq $") 943 out_i64(o, n) 944 out_str(o, ", %") 945 out_str(o, dst) 946 out_char(o, 0x0A) 947 return 0 948} 949 950func x86_emit_shrq_imm(o: *OutBuf, n: i64, dst: *u8) -> i64 { 951 out_str(o, " shrq $") 952 out_i64(o, n) 953 out_str(o, ", %") 954 out_str(o, dst) 955 out_char(o, 0x0A) 956 return 0 957} 958 959// ===== call / tail_call / return (session 5) ====================== 960// 961// SysV calling convention (x86_64 Linux): 962// - First 6 integer args go in rdi, rsi, rdx, rcx, r8, r9. 963// - 7th+ args are pushed RIGHT-TO-LEFT on the stack before call. 964// - rsp must be 16-byte aligned at the CALL instruction. 965// - Return value in rax. 966// - Callee-saved: rbx, rbp, r12, r13, r14, r15. 967// - Caller-saved: rax, rcx, rdx, rsi, rdi, r8-r11. 968// 969// The CALL instruction itself pushes the 8-byte return address, so 970// inside the callee rsp is 8 mod 16 right after entry. The callee's 971// `pushq %rbp` brings it to 0 mod 16; `subq $N, %rsp` keeps it that 972// way iff N is a multiple of 16 (which our prologue ensures). 973 974// Direct call to a named function symbol. Caller must have already 975// loaded arg-regs and (if > 6 args) pushed extras + padded. 976func x86_emit_call_label(o: *OutBuf, name: *u8) -> i64 { 977 out_str(o, " call ") 978 out_str(o, name) 979 out_char(o, 0x0A) 980 return 0 981} 982 983// Indirect call through a register. x86_64.c uses %r11 by 984// convention for the function pointer because r11 is not in the 985// arg-reg set and is caller-saved (no need to preserve). 986func x86_emit_call_indirect(o: *OutBuf, reg: *u8) -> i64 { 987 out_str(o, " call *%") 988 out_str(o, reg) 989 out_char(o, 0x0A) 990 return 0 991} 992 993// Tail call: equivalent of `jmp <label>` after the current frame 994// is torn down. Caller MUST have restored rbp + rsp (via the 995// epilogue, minus the final `ret`) before this emit. 996func x86_emit_tail_call_label(o: *OutBuf, name: *u8) -> i64 { 997 out_str(o, " jmp ") 998 out_str(o, name) 999 out_char(o, 0x0A) 1000 return 0 1001} 1002 1003func x86_emit_tail_call_indirect(o: *OutBuf, reg: *u8) -> i64 { 1004 out_str(o, " jmp *%") 1005 out_str(o, reg) 1006 out_char(o, 0x0A) 1007 return 0 1008} 1009 1010// Stack-arg helpers for >6-arg calls. 1011// x86_emit_push_arg_imm(o, n) -- pushq $n (8 bytes) 1012// x86_emit_push_arg_reg(o, r) -- pushq %r (8 bytes) 1013// x86_emit_pad_for_call(o, n_stack_args) 1014// -- subq $8, %rsp when count is odd 1015// -- (else no-op; rsp already aligned) 1016// x86_emit_unpad_after_call(o, n_stack_args, padded) 1017// -- addq $(n_stack_args*8 + pad), %rsp 1018 1019func x86_emit_push_arg_imm(o: *OutBuf, imm: i64) -> i64 { 1020 out_str(o, " pushq $") 1021 out_i64(o, imm) 1022 out_char(o, 0x0A) 1023 return 0 1024} 1025 1026func x86_emit_push_arg_reg(o: *OutBuf, reg: *u8) -> i64 { 1027 out_str(o, " pushq %") 1028 out_str(o, reg) 1029 out_char(o, 0x0A) 1030 return 0 1031} 1032 1033// Returns 1 if pad was emitted (caller adds 8 to unpad amount). 1034func x86_emit_pad_for_call(o: *OutBuf, n_stack_args: i64) -> i64 { 1035 if (n_stack_args & 1) == 1 { 1036 out_str(o, " subq $8, %rsp\n") 1037 return 1 1038 } 1039 return 0 1040} 1041 1042func x86_emit_unpad_after_call(o: *OutBuf, n_stack_args: i64, 1043 padded: i64) -> i64 { 1044 let total: i64 = (n_stack_args * 8) + (padded * 8) 1045 if total > 0 { 1046 out_str(o, " addq $") 1047 out_i64(o, total) 1048 out_str(o, ", %rsp\n") 1049 } 1050 return 0 1051} 1052 1053// Return-value plumbing: the C reference always materialises the 1054// return into rax via load_value and the surrounding store_result. 1055// Foundation library: just expose the convention via a helper that 1056// emits `movq <reg>, %rax`. For the most common case (already 1057// have value in rax) the caller skips this. 1058 1059func x86_emit_return_value_to_rax(o: *OutBuf, src: *u8) -> i64 { 1060 if src[0] == 0x72 { 1061 if src[1] == 0x61 { return 0 } // already rax; skip 1062 } 1063 x86_emit_movq_reg_reg(o, src, "rax" as *u8) 1064 return 0 1065}