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}