nx_x86_64_loadstore_test.nx source
↩ module page · 100 lines · 3765 B
1// nx_x86_64_loadstore_test.nx -- session 2 smoke for load/store/GEP.
2//
3// Hand-builds a program that:
4// 1. allocates a 32-byte stack buffer via subq $32, %rsp
5// 2. stores a sequence of bytes into the buffer via different
6// store widths (movb / movw / movl / movq)
7// 3. loads them back via the corresponding load widths with the
8// expected sign-extension behaviour
9// 4. computes an expected output via a couple of adds, then writes
10// the result via sys_write
11//
12// Test value: write the byte 'X' (0x58) to stdout, then exit 0.
13// - movb $0x58, -1(%rbp) ; store byte
14// - movzbq -1(%rbp), %rsi ; load unsigned byte into address-form
15// We adapt: build a small in-stack buffer "X\n" and write 2 bytes.
16
17import "syscalls.nx"
18import "nx_outbuf.nx"
19import "nx_x86_64.nx"
20
21func main() -> i64 {
22 let o: *OutBuf = out_new(8192)
23
24 let main_name: *u8 = "main" as *u8
25 let _start_name: *u8 = "_start" as *u8
26
27 out_str(o, "# Emitted by nx_x86_64.nx session 2 (load/store/GEP)\n")
28 out_str(o, " .att_syntax prefix\n")
29
30 // _start trampoline
31 x86_emit_function_start(o, _start_name)
32 out_str(o, " call main\n")
33 x86_emit_movabsq(o, "rax" as *u8, NX_X64_SYS_EXIT)
34 x86_emit_movabsq(o, "rdi" as *u8, 0)
35 x86_emit_syscall(o)
36 x86_emit_function_end(o, _start_name)
37
38 // main:
39 // prologue (32-byte frame -- stack buf at -8(%rbp)..-32(%rbp))
40 // build "X\n" in the buffer:
41 // movb $0x58, -8(%rbp)
42 // movb $0x0A, -7(%rbp)
43 // sys_write(1, %rbp - 8, 2):
44 // movabsq $1, %rdi
45 // leaq -8(%rbp), %rsi
46 // movabsq $2, %rdx
47 // movabsq $1, %rax
48 // syscall
49 // verify load-back via movzbq / movsbq / movzwq:
50 // movzbq -8(%rbp), %rcx -- should be 0x58 (88)
51 // movzwq -8(%rbp), %rdx -- should be 0x0A58 (2648)
52 // epilogue / ret 0
53
54 x86_emit_function_start(o, main_name)
55 x86_emit_prologue(o, 32)
56
57 // Buffer construction via byte stores.
58 // Manually use the store primitive: movb $imm, -N(%rbp) isn't a
59 // single insn; emit as movq imm into rax + store_byte.
60 x86_emit_movabsq(o, "rax" as *u8, 0x58) // 'X'
61 x86_emit_store_byte(o, "rax" as *u8, "rbp" as *u8, 0 - 8)
62 x86_emit_movabsq(o, "rax" as *u8, 0x0A) // '\n'
63 x86_emit_store_byte(o, "rax" as *u8, "rbp" as *u8, 0 - 7)
64
65 // Verify load-back (asserts via "compute checksum, write extra
66 // byte if wrong" -- but simplest is just: trust the store +
67 // write happens correctly when run).
68 x86_emit_load_byte_unsigned(o, "rbp" as *u8, 0 - 8, "rcx" as *u8)
69 // rcx must equal 0x58. We don't have setcc yet (session 3) so
70 // we just rely on the subsequent write to validate end-to-end.
71
72 // sys_write(1, &buf, 2): load rsi via lea.
73 x86_emit_movabsq(o, "rdi" as *u8, 1)
74 x86_emit_lea_disp(o, "rbp" as *u8, 0 - 8, "rsi" as *u8)
75 x86_emit_movabsq(o, "rdx" as *u8, 2)
76 x86_emit_movabsq(o, "rax" as *u8, NX_X64_SYS_WRITE)
77 x86_emit_syscall(o)
78
79 // Test GEP: leaq 1(%rbp - 8) should point at the '\n' byte.
80 // We re-write it as a one-byte write just to exercise the
81 // GEP primitive's lea form.
82 x86_emit_lea_disp(o, "rbp" as *u8, 0 - 8, "rsi" as *u8)
83 // Now do rsi = rsi + 1 to point at the second byte (GEP form).
84 x86_emit_movabsq(o, "rax" as *u8, 1)
85 x86_emit_gep_add(o, "rsi" as *u8, "rax" as *u8)
86 x86_emit_movabsq(o, "rdi" as *u8, 1)
87 x86_emit_movabsq(o, "rdx" as *u8, 1)
88 x86_emit_movabsq(o, "rax" as *u8, NX_X64_SYS_WRITE)
89 x86_emit_syscall(o)
90
91 // ret 0.
92 x86_emit_movabsq(o, "rax" as *u8, 0)
93 x86_emit_epilogue(o)
94 x86_emit_function_end(o, main_name)
95
96 x86_emit_gnu_stack_note(o)
97
98 sys_write(1, o.buf, o.pos)
99 return 0
100}