code wiki / (root) / nx_x86_64_loadstore_test.nx

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}