code wiki / _hdl_build / nx_x86emit.nx
nx_x86emit.nx source
↩ module page · 140 lines · 10281 B
1// nx_x86emit.nx -- SOVEREIGN x86-64 MACHINE-CODE EMITTER (P5 baseline-JIT foundation; SOTA ladder).
2// The keystone (nx_js_jit_keystone) proved runtime EMISSION+EXECUTION works with hand-hex. A template
3// JIT needs to emit code PROGRAMMATICALLY, so this is the assembler: one function per instruction form
4// the Sparkplug-tier compiler will template, each producing exact x86-64 bytes into a code buffer.
5// Verified end-to-end by nx_x86emit_gate (emits functions through THIS api, executes them, checks
6// results) -- cardinal rule 2: verify the primitive before building the JIT on it.
7//
8// Conventions: REX.W (64-bit) on everything. Register numbers = hardware encoding (RAX=0..R15=15).
9// Memory base MUST NOT be RSP/R12 (low3==100 forces a SIB the callers here never need); the JIT uses
10// RBX (stack base) + R14 (env base), both SIB-free. load_idx/store_idx emit SIB for [base+index+disp8].
11// Wide immediates + rel patches are written byte-by-byte with a VARIABLE shift (nx_cc blanks the .s on
12// an IMMEDIATE shift count >31 -- reference-gotcha; i*8 is a register shift, legal for 0..63).
13// license_tier: ORIGINAL
14import "nx_syscalls.nx"
15
16// hardware register numbers
17const RAX: i64 = 0
18const RCX: i64 = 1
19const RDX: i64 = 2
20const RBX: i64 = 3
21const RSP: i64 = 4
22const RBP: i64 = 5
23const RSI: i64 = 6
24const RDI: i64 = 7
25const R8: i64 = 8
26const R9: i64 = 9
27const R10: i64 = 10
28const R11: i64 = 11
29const R12: i64 = 12
30const R13: i64 = 13
31const R14: i64 = 14
32const R15: i64 = 15
33
34// condition-code second opcode bytes (0F 8x jcc / 0F 9x setcc)
35const CC_E: i64 = 0x84
36const CC_NE: i64 = 0x85
37const CC_B: i64 = 0x82
38const CC_AE: i64 = 0x83
39const CC_L: i64 = 0x8C
40const CC_GE: i64 = 0x8D
41const CC_LE: i64 = 0x8E
42const CC_G: i64 = 0x8F
43const SET_E: i64 = 0x94
44const SET_NE: i64 = 0x95
45const SET_L: i64 = 0x9C
46const SET_GE: i64 = 0x9D
47const SET_LE: i64 = 0x9E
48const SET_G: i64 = 0x9F
49
50func xe1(cb: *u8, pb: *i64, b: i64) -> i64 { let p: i64 = pb[0]; cb[p] = (b & 0xff) as u8; pb[0] = p + 1; return 0 }
51func xe_u32(cb: *u8, pb: *i64, v: i64) -> i64 { var i: i64 = 0; while i < 4 { let sh: i64 = i * 8; xe1(cb, pb, (v >> sh) & 0xff); i = i + 1 } return 0 }
52func xe_u64(cb: *u8, pb: *i64, v: i64) -> i64 { var i: i64 = 0; while i < 8 { let sh: i64 = i * 8; xe1(cb, pb, (v >> sh) & 0xff); i = i + 1 } return 0 }
53func xhi(r: i64) -> i64 { if r >= 8 { return 1 } return 0 }
54func xlo(r: i64) -> i64 { return r & 7 }
55func xe_rex(cb: *u8, pb: *i64, w: i64, reg: i64, idx: i64, base: i64) -> i64 { let by: i64 = 0x40 | (w << 3) | (xhi(reg) << 2) | (xhi(idx) << 1) | xhi(base); xe1(cb, pb, by); return 0 }
56func xe_modrm(cb: *u8, pb: *i64, mod: i64, reg: i64, rm: i64) -> i64 { xe1(cb, pb, (mod << 6) | (xlo(reg) << 3) | xlo(rm)); return 0 }
57func xe_sib(cb: *u8, pb: *i64, scale: i64, index: i64, base: i64) -> i64 { xe1(cb, pb, (scale << 6) | (xlo(index) << 3) | xlo(base)); return 0 }
58
59// mov dst, src (89 /r: mov r/m64, r64 ; rm=dst reg=src)
60func xe_mov_rr(cb: *u8, pb: *i64, dst: i64, src: i64) -> i64 { xe_rex(cb, pb, 1, src, 0, dst); xe1(cb, pb, 0x89); xe_modrm(cb, pb, 3, src, dst); return 0 }
61// mov dst, imm32 (sign-extended to 64) (C7 /0)
62func xe_mov_ri32(cb: *u8, pb: *i64, dst: i64, imm: i64) -> i64 { xe_rex(cb, pb, 1, 0, 0, dst); xe1(cb, pb, 0xC7); xe_modrm(cb, pb, 3, 0, dst); xe_u32(cb, pb, imm); return 0 }
63// movabs dst, imm64 (REX.W B8+rd io) -- pointers / large constants
64func xe_mov_ri64(cb: *u8, pb: *i64, dst: i64, imm: i64) -> i64 { xe_rex(cb, pb, 1, 0, 0, dst); xe1(cb, pb, 0xB8 | xlo(dst)); xe_u64(cb, pb, imm); return 0 }
65// mov dst, [base+disp32] (8B /r)
66func xe_load(cb: *u8, pb: *i64, dst: i64, base: i64, disp: i64) -> i64 {
67 xe_rex(cb, pb, 1, dst, 0, base)
68 xe1(cb, pb, 0x8B)
69 if xlo(base) == 4 { xe_modrm(cb, pb, 2, dst, 4); xe_sib(cb, pb, 0, 4, base); xe_u32(cb, pb, disp); return 0 }
70 xe_modrm(cb, pb, 2, dst, base)
71 xe_u32(cb, pb, disp)
72 return 0
73}
74// mov [base+disp32], src (89 /r)
75func xe_store(cb: *u8, pb: *i64, base: i64, disp: i64, src: i64) -> i64 {
76 xe_rex(cb, pb, 1, src, 0, base)
77 xe1(cb, pb, 0x89)
78 if xlo(base) == 4 { xe_modrm(cb, pb, 2, src, 4); xe_sib(cb, pb, 0, 4, base); xe_u32(cb, pb, disp); return 0 }
79 xe_modrm(cb, pb, 2, src, base)
80 xe_u32(cb, pb, disp)
81 return 0
82}
83// mov dst, [base + index + disp8] (8B /r, SIB scale=1)
84func xe_load_idx(cb: *u8, pb: *i64, dst: i64, base: i64, index: i64, disp8: i64) -> i64 {
85 xe_rex(cb, pb, 1, dst, index, base)
86 xe1(cb, pb, 0x8B)
87 xe_modrm(cb, pb, 1, dst, 4)
88 xe_sib(cb, pb, 0, index, base)
89 xe1(cb, pb, disp8 & 0xff)
90 return 0
91}
92// mov [base + index + disp8], src (89 /r, SIB scale=1)
93func xe_store_idx(cb: *u8, pb: *i64, base: i64, index: i64, disp8: i64, src: i64) -> i64 {
94 xe_rex(cb, pb, 1, src, index, base)
95 xe1(cb, pb, 0x89)
96 xe_modrm(cb, pb, 1, src, 4)
97 xe_sib(cb, pb, 0, index, base)
98 xe1(cb, pb, disp8 & 0xff)
99 return 0
100}
101// add/sub/cmp dst, src (reg,reg)
102func xe_add_rr(cb: *u8, pb: *i64, dst: i64, src: i64) -> i64 { xe_rex(cb, pb, 1, src, 0, dst); xe1(cb, pb, 0x01); xe_modrm(cb, pb, 3, src, dst); return 0 }
103func xe_sub_rr(cb: *u8, pb: *i64, dst: i64, src: i64) -> i64 { xe_rex(cb, pb, 1, src, 0, dst); xe1(cb, pb, 0x29); xe_modrm(cb, pb, 3, src, dst); return 0 }
104func xe_cmp_rr(cb: *u8, pb: *i64, a: i64, b: i64) -> i64 { xe_rex(cb, pb, 1, b, 0, a); xe1(cb, pb, 0x39); xe_modrm(cb, pb, 3, b, a); return 0 }
105// imul dst, src (0F AF /r ; reg=dst rm=src)
106func xe_imul_rr(cb: *u8, pb: *i64, dst: i64, src: i64) -> i64 { xe_rex(cb, pb, 1, dst, 0, src); xe1(cb, pb, 0x0F); xe1(cb, pb, 0xAF); xe_modrm(cb, pb, 3, dst, src); return 0 }
107// ---- SSE scalar-double (hardware IEEE f64) -- used by the JIT float fast path (nx_f64 is correctly-rounded
108// round-to-nearest-even, so hardware == soft-float bit-for-bit; parity holds). Uses XMM0/XMM1 (hi=0, no REX).
109func xe_movq_xr(cb: *u8, pb: *i64, xmm: i64, gpr: i64) -> i64 { xe1(cb, pb, 0x66); xe_rex(cb, pb, 1, xmm, 0, gpr); xe1(cb, pb, 0x0F); xe1(cb, pb, 0x6E); xe_modrm(cb, pb, 3, xmm, gpr); return 0 } // movq xmm,r64
110func xe_movq_rx(cb: *u8, pb: *i64, gpr: i64, xmm: i64) -> i64 { xe1(cb, pb, 0x66); xe_rex(cb, pb, 1, xmm, 0, gpr); xe1(cb, pb, 0x0F); xe1(cb, pb, 0x7E); xe_modrm(cb, pb, 3, xmm, gpr); return 0 } // movq r64,xmm
111func xe_sse_arith(cb: *u8, pb: *i64, opc: i64, xd: i64, xs: i64) -> i64 { xe1(cb, pb, 0xF2); xe1(cb, pb, 0x0F); xe1(cb, pb, opc); xe_modrm(cb, pb, 3, xd, xs); return 0 } // <op>sd xd,xs (add58 sub5C mul59 div5E)
112func xe_cvtsi2sd(cb: *u8, pb: *i64, xmm: i64, gpr: i64) -> i64 { xe1(cb, pb, 0xF2); xe_rex(cb, pb, 1, xmm, 0, gpr); xe1(cb, pb, 0x0F); xe1(cb, pb, 0x2A); xe_modrm(cb, pb, 3, xmm, gpr); return 0 } // cvtsi2sd xmm,r64 (int->double)
113// signed idiv: rdx:rax / src -> rax quot, rdx rem. cqo sign-extends rax into rdx. (48 99 ; 48 F7 /7)
114func xe_cqo(cb: *u8, pb: *i64) -> i64 { xe1(cb, pb, 0x48); xe1(cb, pb, 0x99); return 0 }
115func xe_idiv(cb: *u8, pb: *i64, src: i64) -> i64 { xe_rex(cb, pb, 1, 0, 0, src); xe1(cb, pb, 0xF7); xe_modrm(cb, pb, 3, 7, src); return 0 }
116func xe_inc(cb: *u8, pb: *i64, dst: i64) -> i64 { xe_rex(cb, pb, 1, 0, 0, dst); xe1(cb, pb, 0xFF); xe_modrm(cb, pb, 3, 0, dst); return 0 }
117func xe_add_ri32(cb: *u8, pb: *i64, dst: i64, imm: i64) -> i64 { xe_rex(cb, pb, 1, 0, 0, dst); xe1(cb, pb, 0x81); xe_modrm(cb, pb, 3, 0, dst); xe_u32(cb, pb, imm); return 0 }
118func xe_sub_ri32(cb: *u8, pb: *i64, dst: i64, imm: i64) -> i64 { xe_rex(cb, pb, 1, 0, 0, dst); xe1(cb, pb, 0x81); xe_modrm(cb, pb, 3, 5, dst); xe_u32(cb, pb, imm); return 0 }
119func xe_cmp_ri32(cb: *u8, pb: *i64, dst: i64, imm: i64) -> i64 { xe_rex(cb, pb, 1, 0, 0, dst); xe1(cb, pb, 0x81); xe_modrm(cb, pb, 3, 7, dst); xe_u32(cb, pb, imm); return 0 }
120func xe_shl_imm(cb: *u8, pb: *i64, dst: i64, cnt: i64) -> i64 { xe_rex(cb, pb, 1, 0, 0, dst); xe1(cb, pb, 0xC1); xe_modrm(cb, pb, 3, 4, dst); xe1(cb, pb, cnt & 0xff); return 0 }
121func xe_shr_imm(cb: *u8, pb: *i64, dst: i64, cnt: i64) -> i64 { xe_rex(cb, pb, 1, 0, 0, dst); xe1(cb, pb, 0xC1); xe_modrm(cb, pb, 3, 5, dst); xe1(cb, pb, cnt & 0xff); return 0 }
122func xe_sar_imm(cb: *u8, pb: *i64, dst: i64, cnt: i64) -> i64 { xe_rex(cb, pb, 1, 0, 0, dst); xe1(cb, pb, 0xC1); xe_modrm(cb, pb, 3, 7, dst); xe1(cb, pb, cnt & 0xff); return 0 } // arithmetic shift right (sign-fill)
123// one-operand signed multiply: RDX:RAX = RAX * src (F7 /5 -- mirrors xe_idiv's F7 /7). RDX = signed mulhi.
124func xe_imul1(cb: *u8, pb: *i64, src: i64) -> i64 { xe_rex(cb, pb, 1, 0, 0, src); xe1(cb, pb, 0xF7); xe_modrm(cb, pb, 3, 5, src); return 0 }
125// setcc dst8 + movzx dst64,dst8 -> dst = (flag ? 1 : 0)
126func xe_setcc(cb: *u8, pb: *i64, cc: i64, dst: i64) -> i64 { xe1(cb, pb, 0x40 | xhi(dst)); xe1(cb, pb, 0x0F); xe1(cb, pb, cc); xe_modrm(cb, pb, 3, 0, dst); return 0 }
127func xe_movzx_rb(cb: *u8, pb: *i64, dst: i64, src: i64) -> i64 { xe_rex(cb, pb, 1, dst, 0, src); xe1(cb, pb, 0x0F); xe1(cb, pb, 0xB6); xe_modrm(cb, pb, 3, dst, src); return 0 }
128// jmp rel32 (E9) / jcc rel32 (0F cc). Returns the OPERAND position (patch site). Emit 0 placeholder.
129func xe_jmp(cb: *u8, pb: *i64) -> i64 { xe1(cb, pb, 0xE9); let at: i64 = pb[0]; xe_u32(cb, pb, 0); return at }
130func xe_jcc(cb: *u8, pb: *i64, cc: i64) -> i64 { xe1(cb, pb, 0x0F); xe1(cb, pb, cc); let at: i64 = pb[0]; xe_u32(cb, pb, 0); return at }
131// patch a rel32 at `at` to jump to code offset `target` (rel is from END of the 4-byte operand).
132func xe_patch(cb: *u8, at: i64, target: i64) -> i64 { let rel: i64 = target - (at + 4); var i: i64 = 0; while i < 4 { let sh: i64 = i * 8; cb[at + i] = ((rel >> sh) & 0xff) as u8; i = i + 1 } return 0 }
133// call reg (indirect) (FF /2)
134func xe_call_reg(cb: *u8, pb: *i64, reg: i64) -> i64 { if xhi(reg) == 1 { xe1(cb, pb, 0x41) } xe1(cb, pb, 0xFF); xe_modrm(cb, pb, 3, 2, reg); return 0 }
135func xe_push(cb: *u8, pb: *i64, reg: i64) -> i64 { if xhi(reg) == 1 { xe1(cb, pb, 0x41) } xe1(cb, pb, 0x50 | xlo(reg)); return 0 }
136func xe_pop(cb: *u8, pb: *i64, reg: i64) -> i64 { if xhi(reg) == 1 { xe1(cb, pb, 0x41) } xe1(cb, pb, 0x58 | xlo(reg)); return 0 }
137func xe_ret(cb: *u8, pb: *i64) -> i64 { xe1(cb, pb, 0xC3); return 0 }
138
139// PROT_READ|WRITE|EXEC = 7 ; MAP_PRIVATE|ANON = 0x22 (probe-proven)
140func xe_mmap_rwx(size: i64) -> *u8 { let r: i64 = __syscall(SYS_MMAP, 0, size, 7, 0x22, -1, 0); return r as *u8 }