nx_x86_64.nx source
↩ module page · 1070 lines · 33418 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// LN18 (2026-09-01): signed-overflow flag pair for the checked-arithmetic fuse (jo / jno).
611const NX_X64_CC_O: i64 = 10
612const NX_X64_CC_NO: i64 = 11
613
614func x86_cc_suffix(cc: i64) -> *u8 {
615 if cc == NX_X64_CC_EQ { return "e" as *u8 }
616 if cc == NX_X64_CC_NE { return "ne" as *u8 }
617 if cc == NX_X64_CC_LT_S { return "l" as *u8 }
618 if cc == NX_X64_CC_LE_S { return "le" as *u8 }
619 if cc == NX_X64_CC_GT_S { return "g" as *u8 }
620 if cc == NX_X64_CC_GE_S { return "ge" as *u8 }
621 if cc == NX_X64_CC_LT_U { return "b" as *u8 }
622 if cc == NX_X64_CC_LE_U { return "be" as *u8 }
623 if cc == NX_X64_CC_GT_U { return "a" as *u8 }
624 if cc == NX_X64_CC_GE_U { return "ae" as *u8 }
625 if cc == NX_X64_CC_O { return "o" as *u8 }
626 if cc == NX_X64_CC_NO { return "no" as *u8 }
627 return 0 as *u8
628}
629
630// ===== compare + test ============================================
631//
632// AT&T semantics: `cmpq %src1, %src2` computes `src2 - src1` and
633// sets flags. So a subsequent `jl label` jumps when src2 < src1.
634// This is the OPPOSITE of Intel-syntax order; callers must remember.
635//
636// `testq %a, %b` is logical AND of a and b without storing the
637// result; sets ZF. Used for "is reg zero" with `testq %r, %r`.
638
639func x86_emit_cmpq_rr(o: *OutBuf, src1: *u8, src2: *u8) -> i64 {
640 out_str(o, " cmpq %")
641 out_str(o, src1)
642 out_str(o, ", %")
643 out_str(o, src2)
644 out_char(o, 0x0A)
645 return 0
646}
647
648func x86_emit_cmpq_imm(o: *OutBuf, imm: i64, src: *u8) -> i64 {
649 out_str(o, " cmpq $")
650 out_i64(o, imm)
651 out_str(o, ", %")
652 out_str(o, src)
653 out_char(o, 0x0A)
654 return 0
655}
656
657func x86_emit_testq_rr(o: *OutBuf, src1: *u8, src2: *u8) -> i64 {
658 out_str(o, " testq %")
659 out_str(o, src1)
660 out_str(o, ", %")
661 out_str(o, src2)
662 out_char(o, 0x0A)
663 return 0
664}
665
666// ===== setcc + zero-extend ========================================
667//
668// `set<cc> %al` sets the low byte of rax based on flags; we then
669// `movzbq %al, %rax` to materialise the i64 boolean result.
670
671func x86_emit_setcc(o: *OutBuf, cc: i64, dst8: *u8) -> i64 {
672 out_str(o, " set")
673 out_str(o, x86_cc_suffix(cc))
674 out_str(o, " %")
675 out_str(o, dst8)
676 out_char(o, 0x0A)
677 return 0
678}
679
680func x86_emit_movzbq_rr(o: *OutBuf, src8: *u8, dst: *u8) -> i64 {
681 out_str(o, " movzbq %")
682 out_str(o, src8)
683 out_str(o, ", %")
684 out_str(o, dst)
685 out_char(o, 0x0A)
686 return 0
687}
688
689// ===== unconditional + conditional branches =======================
690
691func x86_emit_jmp_label(o: *OutBuf, label: *u8) -> i64 {
692 out_str(o, " jmp ")
693 out_str(o, label)
694 out_char(o, 0x0A)
695 return 0
696}
697
698func x86_emit_jcc_label(o: *OutBuf, cc: i64, label: *u8) -> i64 {
699 out_str(o, " j")
700 out_str(o, x86_cc_suffix(cc))
701 out_char(o, 0x20)
702 out_str(o, label)
703 out_char(o, 0x0A)
704 return 0
705}
706
707// ===== arithmetic (session 4) =====================================
708//
709// Signed multiply, signed/unsigned divide, sign-extend, bitwise ops,
710// and shifts.
711//
712// x86_64 quirks the caller must respect:
713// * imulq %src, %dst computes dst = dst * src (in-place).
714// * idivq %src computes rax = rdx:rax / src, rdx = remainder.
715// Caller MUST sign-extend rax into rdx first via
716// cqo (or zero-extend via `xorq %rdx, %rdx` for
717// the unsigned form `divq`).
718// * shift counts go in %cl (the low byte of rcx). Shift-by-imm is
719// emitted as a separate primitive.
720//
721// negq / notq are one-operand instructions on a register.
722
723func x86_emit_imulq_rr(o: *OutBuf, src: *u8, dst: *u8) -> i64 {
724 out_str(o, " imulq %")
725 out_str(o, src)
726 out_str(o, ", %")
727 out_str(o, dst)
728 out_char(o, 0x0A)
729 return 0
730}
731
732// Unsigned 64x64 -> 128 multiply (G2): `mulq %src` computes rdx:rax = rax * src.
733// The low 64 bits land in rax, the high 64 in rdx. One operand only (rax is
734// the implicit multiplicand). Used for OP_UMULHI (the rdx half).
735func x86_emit_mulq_r(o: *OutBuf, src: *u8) -> i64 {
736 out_str(o, " mulq %")
737 out_str(o, src)
738 out_char(o, 0x0A)
739 return 0
740}
741
742func x86_emit_cqo(o: *OutBuf) -> i64 {
743 out_str(o, " cqo\n")
744 return 0
745}
746
747// Hardware CRC-32C accumulate (SSE4.2): `crc32q %src,%dst` computes
748// dst = CRC32C(dst, src). In-place on dst (the running accumulator),
749// src is the 64-bit data word folded in. Emitted for OP_CRC32.
750func x86_emit_crc32q_rr(o: *OutBuf, src: *u8, dst: *u8) -> i64 {
751 out_str(o, " crc32q %")
752 out_str(o, src)
753 out_str(o, ", %")
754 out_str(o, dst)
755 out_char(o, 0x0A)
756 return 0
757}
758
759// BMI2 parallel bit DEPOSIT: `pdep %src2,%src1,%dst` deposits the low bits of
760// src1 into the set-bit positions of the mask src2, result in dst. AT&T
761// 3-operand src2,src1,dst. Emitted for OP_PDEP (__pdep64(value, mask)):
762// src1 = the value, src2 = the mask.
763func x86_emit_pdep_rrr(o: *OutBuf, src2: *u8, src1: *u8, dst: *u8) -> i64 {
764 out_str(o, " pdep %")
765 out_str(o, src2)
766 out_str(o, ", %")
767 out_str(o, src1)
768 out_str(o, ", %")
769 out_str(o, dst)
770 out_char(o, 0x0A)
771 return 0
772}
773
774// BMI2 parallel bit EXTRACT: `pext %src2,%src1,%dst` gathers the src1 bits at
775// the set-bit positions of the mask src2 down to the low bits of dst (the
776// inverse of pdep). Emitted for OP_PEXT (__pext64(value, mask)).
777func x86_emit_pext_rrr(o: *OutBuf, src2: *u8, src1: *u8, dst: *u8) -> i64 {
778 out_str(o, " pext %")
779 out_str(o, src2)
780 out_str(o, ", %")
781 out_str(o, src1)
782 out_str(o, ", %")
783 out_str(o, dst)
784 out_char(o, 0x0A)
785 return 0
786}
787
788func x86_emit_idivq_r(o: *OutBuf, src: *u8) -> i64 {
789 out_str(o, " idivq %")
790 out_str(o, src)
791 out_char(o, 0x0A)
792 return 0
793}
794
795func x86_emit_divq_r(o: *OutBuf, src: *u8) -> i64 {
796 out_str(o, " divq %")
797 out_str(o, src)
798 out_char(o, 0x0A)
799 return 0
800}
801
802func x86_emit_xorq_rr(o: *OutBuf, src: *u8, dst: *u8) -> i64 {
803 out_str(o, " xorq %")
804 out_str(o, src)
805 out_str(o, ", %")
806 out_str(o, dst)
807 out_char(o, 0x0A)
808 return 0
809}
810
811func x86_emit_andq_rr(o: *OutBuf, src: *u8, dst: *u8) -> i64 {
812 out_str(o, " andq %")
813 out_str(o, src)
814 out_str(o, ", %")
815 out_str(o, dst)
816 out_char(o, 0x0A)
817 return 0
818}
819
820func x86_emit_orq_rr(o: *OutBuf, src: *u8, dst: *u8) -> i64 {
821 out_str(o, " orq %")
822 out_str(o, src)
823 out_str(o, ", %")
824 out_str(o, dst)
825 out_char(o, 0x0A)
826 return 0
827}
828
829func x86_emit_negq_r(o: *OutBuf, dst: *u8) -> i64 {
830 out_str(o, " negq %")
831 out_str(o, dst)
832 out_char(o, 0x0A)
833 return 0
834}
835
836func x86_emit_notq_r(o: *OutBuf, dst: *u8) -> i64 {
837 out_str(o, " notq %")
838 out_str(o, dst)
839 out_char(o, 0x0A)
840 return 0
841}
842
843// Shifts: count from %cl (low byte of rcx). Caller must load count
844// into rcx first.
845
846func x86_emit_shlq_cl(o: *OutBuf, dst: *u8) -> i64 {
847 out_str(o, " shlq %cl, %")
848 out_str(o, dst)
849 out_char(o, 0x0A)
850 return 0
851}
852
853func x86_emit_sarq_cl(o: *OutBuf, dst: *u8) -> i64 {
854 out_str(o, " sarq %cl, %")
855 out_str(o, dst)
856 out_char(o, 0x0A)
857 return 0
858}
859
860func x86_emit_shrq_cl(o: *OutBuf, dst: *u8) -> i64 {
861 out_str(o, " shrq %cl, %")
862 out_str(o, dst)
863 out_char(o, 0x0A)
864 return 0
865}
866
867// Rotates: count from %cl. Single-instruction ROLQ/RORQ (Intel SDM
868// vol 2 ROL/ROR). Matches __rotl64 / __rotr64 builtins -- C
869// bootstrap parity with x86_64.c OP_ROTL64/OP_ROTR64.
870func x86_emit_rolq_cl(o: *OutBuf, dst: *u8) -> i64 {
871 out_str(o, " rolq %cl, %")
872 out_str(o, dst)
873 out_char(o, 0x0A)
874 return 0
875}
876
877func x86_emit_rorq_cl(o: *OutBuf, dst: *u8) -> i64 {
878 out_str(o, " rorq %cl, %")
879 out_str(o, dst)
880 out_char(o, 0x0A)
881 return 0
882}
883
884// Scalar bit unops on %rax -- C bootstrap parity (x86_64.c).
885// bswapq: full 64-bit byte reverse (i486 1989+).
886func x86_emit_bswapq_rax(o: *OutBuf) -> i64 {
887 out_str(o, " bswapq %rax\n")
888 return 0
889}
890
891// popcntq: population count (SSE4.2 2008+); rax <- popcount(rax).
892func x86_emit_popcntq_rax(o: *OutBuf) -> i64 {
893 out_str(o, " popcntq %rax, %rax\n")
894 return 0
895}
896
897// lzcntl: count leading zeros of low 32 bits, 32 if input 0 (BMI1
898// 2013+). Writes 32-bit eax (zero-extends to rax). Matches
899// __builtin_clz / nx_clz32 semantics.
900func x86_emit_lzcntl_eax(o: *OutBuf) -> i64 {
901 out_str(o, " lzcntl %eax, %eax\n")
902 return 0
903}
904
905// tzcntl: count trailing zeros of low 32 bits, 32 if input 0 (BMI1).
906func x86_emit_tzcntl_eax(o: *OutBuf) -> i64 {
907 out_str(o, " tzcntl %eax, %eax\n")
908 return 0
909}
910
911// Atomic family -- C bootstrap parity (x86_64.c). Address always in
912// %r11 (caller-saved, not a SysV arg reg). Conservative-strong
913// ordering for every memory order (always correct on x86 TSO).
914func x86_emit_xchgq_rax_mem_r11(o: *OutBuf) -> i64 {
915 out_str(o, " xchgq %rax, (%r11)\n") // atomic store (seq-cst)
916 return 0
917}
918func x86_emit_lock_cmpxchgq_rcx_mem_r11(o: *OutBuf) -> i64 {
919 out_str(o, " lock cmpxchgq %rcx, (%r11)\n")
920 return 0
921}
922func x86_emit_sete_al(o: *OutBuf) -> i64 {
923 out_str(o, " sete %al\n")
924 return 0
925}
926func x86_emit_lock_xaddq_rax_mem_r11(o: *OutBuf) -> i64 {
927 out_str(o, " lock xaddq %rax, (%r11)\n")
928 return 0
929}
930func x86_emit_mfence(o: *OutBuf) -> i64 {
931 out_str(o, " mfence\n")
932 return 0
933}
934
935// Shift by immediate (preferred when count is a compile-time const).
936
937func x86_emit_shlq_imm(o: *OutBuf, n: i64, dst: *u8) -> i64 {
938 out_str(o, " shlq $")
939 out_i64(o, n)
940 out_str(o, ", %")
941 out_str(o, dst)
942 out_char(o, 0x0A)
943 return 0
944}
945
946func x86_emit_sarq_imm(o: *OutBuf, n: i64, dst: *u8) -> i64 {
947 out_str(o, " sarq $")
948 out_i64(o, n)
949 out_str(o, ", %")
950 out_str(o, dst)
951 out_char(o, 0x0A)
952 return 0
953}
954
955func x86_emit_shrq_imm(o: *OutBuf, n: i64, dst: *u8) -> i64 {
956 out_str(o, " shrq $")
957 out_i64(o, n)
958 out_str(o, ", %")
959 out_str(o, dst)
960 out_char(o, 0x0A)
961 return 0
962}
963
964// ===== call / tail_call / return (session 5) ======================
965//
966// SysV calling convention (x86_64 Linux):
967// - First 6 integer args go in rdi, rsi, rdx, rcx, r8, r9.
968// - 7th+ args are pushed RIGHT-TO-LEFT on the stack before call.
969// - rsp must be 16-byte aligned at the CALL instruction.
970// - Return value in rax.
971// - Callee-saved: rbx, rbp, r12, r13, r14, r15.
972// - Caller-saved: rax, rcx, rdx, rsi, rdi, r8-r11.
973//
974// The CALL instruction itself pushes the 8-byte return address, so
975// inside the callee rsp is 8 mod 16 right after entry. The callee's
976// `pushq %rbp` brings it to 0 mod 16; `subq $N, %rsp` keeps it that
977// way iff N is a multiple of 16 (which our prologue ensures).
978
979// Direct call to a named function symbol. Caller must have already
980// loaded arg-regs and (if > 6 args) pushed extras + padded.
981func x86_emit_call_label(o: *OutBuf, name: *u8) -> i64 {
982 out_str(o, " call ")
983 out_str(o, name)
984 out_char(o, 0x0A)
985 return 0
986}
987
988// Indirect call through a register. x86_64.c uses %r11 by
989// convention for the function pointer because r11 is not in the
990// arg-reg set and is caller-saved (no need to preserve).
991func x86_emit_call_indirect(o: *OutBuf, reg: *u8) -> i64 {
992 out_str(o, " call *%")
993 out_str(o, reg)
994 out_char(o, 0x0A)
995 return 0
996}
997
998// Tail call: equivalent of `jmp <label>` after the current frame
999// is torn down. Caller MUST have restored rbp + rsp (via the
1000// epilogue, minus the final `ret`) before this emit.
1001func x86_emit_tail_call_label(o: *OutBuf, name: *u8) -> i64 {
1002 out_str(o, " jmp ")
1003 out_str(o, name)
1004 out_char(o, 0x0A)
1005 return 0
1006}
1007
1008func x86_emit_tail_call_indirect(o: *OutBuf, reg: *u8) -> i64 {
1009 out_str(o, " jmp *%")
1010 out_str(o, reg)
1011 out_char(o, 0x0A)
1012 return 0
1013}
1014
1015// Stack-arg helpers for >6-arg calls.
1016// x86_emit_push_arg_imm(o, n) -- pushq $n (8 bytes)
1017// x86_emit_push_arg_reg(o, r) -- pushq %r (8 bytes)
1018// x86_emit_pad_for_call(o, n_stack_args)
1019// -- subq $8, %rsp when count is odd
1020// -- (else no-op; rsp already aligned)
1021// x86_emit_unpad_after_call(o, n_stack_args, padded)
1022// -- addq $(n_stack_args*8 + pad), %rsp
1023
1024func x86_emit_push_arg_imm(o: *OutBuf, imm: i64) -> i64 {
1025 out_str(o, " pushq $")
1026 out_i64(o, imm)
1027 out_char(o, 0x0A)
1028 return 0
1029}
1030
1031func x86_emit_push_arg_reg(o: *OutBuf, reg: *u8) -> i64 {
1032 out_str(o, " pushq %")
1033 out_str(o, reg)
1034 out_char(o, 0x0A)
1035 return 0
1036}
1037
1038// Returns 1 if pad was emitted (caller adds 8 to unpad amount).
1039func x86_emit_pad_for_call(o: *OutBuf, n_stack_args: i64) -> i64 {
1040 if (n_stack_args & 1) == 1 {
1041 out_str(o, " subq $8, %rsp\n")
1042 return 1
1043 }
1044 return 0
1045}
1046
1047func x86_emit_unpad_after_call(o: *OutBuf, n_stack_args: i64,
1048 padded: i64) -> i64 {
1049 let total: i64 = (n_stack_args * 8) + (padded * 8)
1050 if total > 0 {
1051 out_str(o, " addq $")
1052 out_i64(o, total)
1053 out_str(o, ", %rsp\n")
1054 }
1055 return 0
1056}
1057
1058// Return-value plumbing: the C reference always materialises the
1059// return into rax via load_value and the surrounding store_result.
1060// Foundation library: just expose the convention via a helper that
1061// emits `movq <reg>, %rax`. For the most common case (already
1062// have value in rax) the caller skips this.
1063
1064func x86_emit_return_value_to_rax(o: *OutBuf, src: *u8) -> i64 {
1065 if src[0] == 0x72 {
1066 if src[1] == 0x61 { return 0 } // already rax; skip
1067 }
1068 x86_emit_movq_reg_reg(o, src, "rax" as *u8)
1069 return 0
1070}