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 }