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}