code wiki / _hdl_build / nx_x86emit_gate.nx
nx_x86emit_gate.nx source
↩ module page · 175 lines · 7948 B
1// nx_x86emit_gate.nx -- VERIFY the sovereign x86-64 emitter (nx_x86emit) by EMITTING functions
2// through its api and EXECUTING them (cardinal rule 2: verify the primitive before the JIT rides it).
3// Each test emits a tiny function via the SAME encoders the Sparkplug-tier JIT will use, casts the
4// RWX buffer to a fn-ptr, calls it, and checks the result. Any encoding bug shows as a wrong value
5// or a SIGSEGV/SIGILL here -- never silently in the JIT. Covers: reg-moves, imm32/imm64, arithmetic,
6// imul, signed idiv (cqo+idiv), setcc booleanization, [base+disp] load/store, [base+index] SIB
7// load/store (the VM stack access pattern), forward jcc + backward jmp (a real counted loop), and an
8// indirect CALL through a register (the native->helper path). expect_exit: 0 license_tier: ORIGINAL
9import "nx_x86emit.nx"
10
11func g_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
12func g_pn(v: i64) -> i64 {
13 if v == 0 { sys_write(1, "0" as *u8, 1); return 0 }
14 var m: i64 = v
15 if m < 0 { sys_write(1, "-" as *u8, 1); m = 0 - m }
16 let t: *u8 = sys_mmap(32)
17 var k: i64 = 0
18 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
19 let o: *u8 = sys_mmap(32)
20 var q: i64 = k - 1
21 var x: i64 = 0
22 while q >= 0 { o[x] = t[q]; x = x + 1; q = q - 1 }
23 sys_write(1, o, x)
24 return 0
25}
26func chk(label: *u8, got: i64, want: i64, pp: *i64, tp: *i64) -> i64 {
27 tp[0] = tp[0] + 1
28 g_puts(" ")
29 g_puts(label)
30 g_puts(": got=")
31 g_pn(got)
32 g_puts(" want=")
33 g_pn(want)
34 if got == want { pp[0] = pp[0] + 1; g_puts(" ok\n" as *u8) } else { g_puts(" FAIL\n" as *u8) }
35 return 0
36}
37
38// a plain helper the emitted code will CALL indirectly (SysV: arg rdi, ret rax).
39func helper_triple(x: i64) -> i64 { return x * 3 }
40
41func main() -> i64 {
42 g_puts("=== nx_x86emit_gate: emit x86-64 through the api + EXECUTE it ===\n" as *u8)
43 let pp: *i64 = sys_mmap(8) as *i64
44 let tp: *i64 = sys_mmap(8) as *i64
45 let pbx: *i64 = sys_mmap(8) as *i64
46
47 // T1 arithmetic: f(x)=(x+5)*x - x ; f(10)=140 -- mov/add imm/imul/sub
48 let c1: *u8 = xe_mmap_rwx(4096)
49 pbx[0] = 0
50 xe_mov_rr(c1, pbx, RAX, RDI) // rax = x
51 xe_add_ri32(c1, pbx, RAX, 5) // rax = x+5
52 xe_imul_rr(c1, pbx, RAX, RDI) // rax = (x+5)*x
53 xe_sub_rr(c1, pbx, RAX, RDI) // rax -= x
54 xe_ret(c1, pbx)
55 let f1: func(i64) -> i64 = (c1 as i64) as func(i64) -> i64
56 chk("T1 (x+5)*x-x @10" as *u8, f1(10), 140, pp, tp)
57
58 // T2 signed idiv: f(x)=x/7 (cqo+idiv) ; f(100)=14
59 let c2: *u8 = xe_mmap_rwx(4096)
60 pbx[0] = 0
61 xe_mov_rr(c2, pbx, RAX, RDI) // rax = x
62 xe_mov_ri32(c2, pbx, RCX, 7) // rcx = 7
63 xe_cqo(c2, pbx) // sign-extend rax -> rdx:rax
64 xe_idiv(c2, pbx, RCX) // rax = rax/rcx
65 xe_ret(c2, pbx)
66 let f2: func(i64) -> i64 = (c2 as i64) as func(i64) -> i64
67 chk("T2 x/7 @100" as *u8, f2(100), 14, pp, tp)
68
69 // T3 setcc booleanize: f(x) = (x < 50) ? 1 : 0 ; f(20)=1, f(80)=0
70 let c3: *u8 = xe_mmap_rwx(4096)
71 pbx[0] = 0
72 xe_cmp_ri32(c3, pbx, RDI, 50) // cmp x, 50
73 xe_setcc(c3, pbx, SET_L, RAX) // al = (x<50)
74 xe_movzx_rb(c3, pbx, RAX, RAX) // rax = zero-extend al
75 xe_ret(c3, pbx)
76 let f3: func(i64) -> i64 = (c3 as i64) as func(i64) -> i64
77 chk("T3 (x<50) @20" as *u8, f3(20), 1, pp, tp)
78 chk("T3 (x<50) @80" as *u8, f3(80), 0, pp, tp)
79
80 // T4 imm64 (movabs): f(_) = 0x0123456789ABCDEF
81 let c4: *u8 = xe_mmap_rwx(4096)
82 pbx[0] = 0
83 xe_mov_ri64(c4, pbx, RAX, 81985529216486895) // 0x0123456789ABCDEF
84 xe_ret(c4, pbx)
85 let f4: func(i64) -> i64 = (c4 as i64) as func(i64) -> i64
86 chk("T4 movabs imm64" as *u8, f4(0), 81985529216486895, pp, tp)
87
88 // T5 memory [base+disp]: buf[0]=11 buf[1]=22; f(bufptr)= *(buf+8) + *(buf+0) = 33
89 let c5: *u8 = xe_mmap_rwx(4096)
90 pbx[0] = 0
91 xe_load(c5, pbx, RAX, RDI, 8) // rax = [rdi+8]
92 xe_load(c5, pbx, RCX, RDI, 0) // rcx = [rdi+0]
93 xe_add_rr(c5, pbx, RAX, RCX) // rax += rcx
94 xe_ret(c5, pbx)
95 let buf5: *i64 = sys_mmap(64) as *i64
96 buf5[0] = 11
97 buf5[1] = 22
98 let f5: func(i64) -> i64 = (c5 as i64) as func(i64) -> i64
99 chk("T5 load [base+disp]" as *u8, f5(buf5 as i64), 33, pp, tp)
100
101 // T6 store [base+disp]: write x*2 to buf[3], return it
102 let c6: *u8 = xe_mmap_rwx(4096)
103 pbx[0] = 0
104 xe_mov_rr(c6, pbx, RAX, RDI) // rax = x
105 xe_add_rr(c6, pbx, RAX, RDI) // rax = 2x
106 xe_store(c6, pbx, RSI, 24, RAX) // [rsi+24] = rax (buf[3])
107 xe_ret(c6, pbx)
108 let buf6: *i64 = sys_mmap(64) as *i64
109 // 2-arg emitted fn (rdi=x, rsi=buf); call via a 2-arg cast
110 let f6: func(i64, i64) -> i64 = (c6 as i64) as func(i64, i64) -> i64
111 f6(21, buf6 as i64)
112 chk("T6 store [base+disp]" as *u8, buf6[3], 42, pp, tp)
113
114 // T7 SIB [base+index*1+disp8] load+store (the VM 2-slot stack pattern: stack[i*2], stack[i*2+1]).
115 // f(base): idx=rcx=16 (=cell 2 * 16B); tag=[base+idx+0], pay=[base+idx+8]; store pay+tag to
116 // [base+idx+0]; return that. cells are 16B; put tag=100 pay=5 at byte 16 -> expect 105.
117 let c7: *u8 = xe_mmap_rwx(4096)
118 pbx[0] = 0
119 xe_mov_ri32(c7, pbx, RCX, 16) // index = 16
120 xe_load_idx(c7, pbx, RAX, RDI, RCX, 0) // rax = [base+idx+0] (=100)
121 xe_load_idx(c7, pbx, RDX, RDI, RCX, 8) // rdx = [base+idx+8] (=5)
122 xe_add_rr(c7, pbx, RAX, RDX) // rax = 105
123 xe_store_idx(c7, pbx, RDI, RCX, 0, RAX) // [base+idx+0] = 105
124 xe_ret(c7, pbx)
125 let buf7: *i64 = sys_mmap(128) as *i64
126 buf7[2] = 100 // byte 16
127 buf7[3] = 5 // byte 24
128 let f7: func(i64) -> i64 = (c7 as i64) as func(i64) -> i64
129 let r7: i64 = f7(buf7 as i64)
130 chk("T7 SIB idx load+add" as *u8, r7, 105, pp, tp)
131 chk("T7 SIB idx store" as *u8, buf7[2], 105, pp, tp)
132
133 // T8 CONTROL FLOW -- a real counted loop with forward jcc + backward jmp + patch:
134 // sum=0; i=0; while(i<N){ sum+=i; i++; } return sum ; f(1000)=499500
135 let c8: *u8 = xe_mmap_rwx(4096)
136 pbx[0] = 0
137 xe_mov_ri32(c8, pbx, RAX, 0) // sum=0
138 xe_mov_ri32(c8, pbx, RCX, 0) // i=0
139 let ltop: i64 = pbx[0] // loop top
140 xe_cmp_rr(c8, pbx, RCX, RDI) // cmp i, N
141 let jend: i64 = xe_jcc(c8, pbx, CC_GE) // jge end (patch later)
142 xe_add_rr(c8, pbx, RAX, RCX) // sum += i
143 xe_inc(c8, pbx, RCX) // i++
144 let jback: i64 = xe_jmp(c8, pbx) // jmp top
145 xe_patch(c8, jback, ltop) // back-edge
146 let lend: i64 = pbx[0]
147 xe_patch(c8, jend, lend) // forward exit
148 xe_ret(c8, pbx)
149 let f8: func(i64) -> i64 = (c8 as i64) as func(i64) -> i64
150 chk("T8 counted loop @1000" as *u8, f8(1000), 499500, pp, tp)
151 chk("T8 counted loop @1" as *u8, f8(1), 0, pp, tp)
152
153 // T9 indirect CALL through a register (native->helper path). Emit:
154 // push rbx ; (align) ; mov r10, &helper_triple ; call r10 ; pop rbx ; ret
155 // pass x through: helper takes rdi (already x), returns 3x. f(14)=42.
156 let c9: *u8 = xe_mmap_rwx(4096)
157 pbx[0] = 0
158 xe_push(c9, pbx, RBX) // align the stack to 16 (one push after entry return-addr)
159 xe_mov_ri64(c9, pbx, R10, (&helper_triple) as i64)
160 xe_call_reg(c9, pbx, R10) // rax = helper_triple(rdi)
161 xe_pop(c9, pbx, RBX)
162 xe_ret(c9, pbx)
163 let f9: func(i64) -> i64 = (c9 as i64) as func(i64) -> i64
164 chk("T9 indirect call helper @14" as *u8, f9(14), 42, pp, tp)
165
166 g_puts(" --- x86 emitter: " as *u8)
167 g_pn(pp[0])
168 g_puts("/" as *u8)
169 g_pn(tp[0])
170 g_puts(" encodings emit + execute correctly ---\n" as *u8)
171 if pp[0] == tp[0] { g_puts("=== verdict=GREEN: sovereign x86-64 emitter VERIFIED (JIT can ride it) ===\n" as *u8); sys_exit(0); return 0 }
172 g_puts("=== RED: an encoding is wrong ===\n" as *u8)
173 sys_exit(1)
174 return 1
175}