code wiki / (root) / nx_compile_x86.nx

nx_compile_x86.nx source

↩ module page · 258 lines · 10178 B

1// nx_compile_x86.nx -- source-file driver for the NishiLang x86_64 backend. 2// 3// Reads .nx source on stdin (pre-expanded; imports already inlined), 4// runs: 5// lex_source -> parse_module -> opt_run (per function) -> 6// x86ctx_emit_module 7// and writes the AT&T x86_64 asm to stdout. 8// 9// The bash wrapper (bench/nx_compile_x86.sh) drives the source -> 10// native ELF pipeline: 11// 12// nxc2.exe --target asm runtime/nx_compile_x86.nx (build driver) 13// riscv64-as + ld -> nx_compile_x86.elf 14// cat source.nx | qemu-riscv64-static nx_compile_x86.elf 15// -> x86_64 asm on stdout 16// gcc -nostdlib -static asm -o native.elf 17// run natively 18// 19// Per cardinal feedback-no-nxc2-c-extension-only-nishilang-forward: 20// this driver is the NishiLang counterpart of nxc2.exe's --target 21// x86_64 path. No C-side modifications. 22// 23// nx_safety_envelope: 24// intended_use: "Compile a .nx source (pre-import-expanded) 25// to native x86_64 SysV asm via the IR-driven 26// x86_64 backend." 27// sil_target: SIL3 28// asil_target: QM 29// dal_target: DAL B 30// iec_62304_class: NONE 31// evidence: [no_floating_point, 32// bounded_stdin_read_per_jpl_rule_2, 33// sealed_pipeline_verdict_codes, 34// opt_optional_via_env] 35// hazard_register: [bug-tape-source-exceeds-cap-silently-truncated, 36// bug-tape-opcode-not-yet-wired-passes-through] 37// residual_risk: "Hard 2 MiB source cap; pasting larger files 38// truncates silently. Streaming follow-up 39// when needed." 40// verdict: NOT_YET_EVALUATED 41 42import "nx_syscalls.nx" 43import "nx_types.nx" 44import "nx_lex_kinds.nx" 45import "nx_outbuf.nx" 46import "nx_ir.nx" 47import "nx_tokenizer.nx" 48import "nx_parse.nx" 49import "nx_opt.nx" 50import "nx_x86_64.nx" 51import "nx_ir_dump.nx" 52import "nx_ir_validate.nx" 53import "nx_x86_regalloc.nx" 54import "nx_x86_64_ctx.nx" 55import "nx_import.nx" 56const NX_MAGIC_4096: i64 = 4096 57const NX_MAGIC_262144: i64 = 262144 58 59const NX_COMPILE_X86_SRC_CAP: i64 = 2097152 // 2 MiB 60const NX_COMPILE_X86_OUT_CAP: i64 = 16777216 // 16 MiB 61const NX_COMPILE_X86_EXPAND_CAP: i64 = 4194304 // 4 MiB import-expanded 62 63const NX_COMPILE_X86_OK: i64 = 0 64const NX_COMPILE_X86_EMPTY: i64 = 1 65const NX_COMPILE_X86_LEX_FAIL: i64 = 2 66const NX_COMPILE_X86_PARSE_FAIL: i64 = 3 67const NX_COMPILE_X86_NO_FN: i64 = 4 68 69func nxcx_read_stdin(buf: *u8, cap: i64) -> i64 { 70 var total: i64 = 0 71 let BUDGET: i64 = (cap / NX_MAGIC_4096) + 16 72 var iter: i64 = 0 73 var done: i64 = 0 74 while done == 0 { 75 if iter >= BUDGET { done = 1 } 76 if done == 0 { 77 if total >= cap { done = 1 } 78 if done == 0 { 79 let dst: *u8 = ((buf as i64) + total) as *u8 80 let want: i64 = cap - total 81 let got: i64 = sys_read(0, dst, want) 82 if got < 0 { done = 1 } 83 if got == 0 { done = 1 } 84 if got > 0 { total = total + got } 85 } 86 } 87 iter = iter + 1 88 } 89 return total 90} 91 92func nxcx_log(msg: *u8, n: i64) -> i64 { 93 return sys_write(2, msg, n) 94} 95 96func main(argc: i64, argv: *i64) -> i64 { 97 // ---- 1. Acquire source ---- 98 // 99 // If argv[1] is provided, treat it as a file path and run import 100 // expansion (mirrors nx_nxc.nx). Else read raw source from stdin 101 // (no import expansion -- caller pre-expands). 102 var in_buf: *u8 = 0 as *u8 103 var in_n: i64 = 0 104 // Walk argv skipping flags so callers can mix `--target x86_64` 105 // (ignored by this binary -- it only emits x86_64) with a path 106 // argument, matching nxc2.exe's CLI shape. First arg not 107 // starting with '-' is the source path; absent it, read stdin. 108 var path_addr: i64 = 0 109 var i: i64 = 1 110 while i < argc { 111 let arg: *u8 = argv[i] as *u8 112 if arg[0] == 0x2D { // '-' flag 113 // --target NAME consumes the next argv slot too. 114 if arg[1] == 0x2D { // '--' long flag 115 if arg[2] == 0x74 { // 't' (target) 116 i = i + 2 117 continue 118 } 119 } 120 i = i + 1 121 continue 122 } 123 path_addr = arg as i64 124 i = argc // exit 125 } 126 if path_addr != 0 { 127 let path: *u8 = path_addr as *u8 128 let expand_buf: *u8 = sys_mmap(NX_COMPILE_X86_EXPAND_CAP) 129 let ctx: *ExpandCtx = expand_ctx_new(expand_buf, 130 NX_COMPILE_X86_EXPAND_CAP) 131 let rc: i64 = expand_imports(ctx, path) 132 if rc < 0 { 133 nxcx_log("nx_compile_x86: expand_imports failed\n" as *u8, 40) 134 return 10 - rc 135 } 136 let op: *i64 = ctx.out_pos 137 let end: i64 = *op 138 expand_buf[end] = 0 as u8 139 in_buf = expand_buf 140 in_n = end 141 } 142 if path_addr == 0 { 143 in_buf = sys_mmap(NX_COMPILE_X86_SRC_CAP) 144 in_n = nxcx_read_stdin(in_buf, NX_COMPILE_X86_SRC_CAP) 145 } 146 if in_n <= 0 { 147 nxcx_log("nx_compile_x86: empty input\n" as *u8, 28) 148 return NX_COMPILE_X86_EMPTY 149 } 150 151 // ---- 2. Lex ---- 152 // DIAGNOSTICS: hand the parser the source so errors can show the offending line with a caret 153 // (additive -- an unset source means no snippet, never a wrong one). 154 nx_diag_set_source(in_buf, in_n) 155 let toks: *Tok = lex_source(in_buf, NX_MAGIC_262144) 156 if toks == (0 as *Tok) { 157 nxcx_log("nx_compile_x86: lex_source failed\n" as *u8, 35) 158 return NX_COMPILE_X86_LEX_FAIL 159 } 160 161 // ---- 3. Parse ---- 162 let m: *Module = parse_module(toks, 0 as *Module) 163 if m == (0 as *Module) { 164 nxcx_log("nx_compile_x86: parse_module failed\n" as *u8, 37) 165 return NX_COMPILE_X86_PARSE_FAIL 166 } 167 if m.n_functions <= 0 { 168 nxcx_log("nx_compile_x86: no functions parsed\n" as *u8, 37) 169 return NX_COMPILE_X86_NO_FN 170 } 171 172 // ---- 3b. Post-parse IR validation (log-only, mirrors nx_nxc.nx) ---- 173 // 174 // V4 (every block ends in a terminator) is the prevention gate for 175 // the unterminated-final-block class: parse_function used to leave a 176 // body's last open block with no OP_RETURN, and the emitter's 177 // creation-order layout made it FALL THROUGH into a sibling block 178 // (nx_ttt_evaluate draw-detect clobber, found 2026-06-09). Log-only 179 // until the validator earns abort authority, same policy as nx_nxc. 180 let v_post_parse: i64 = ir_validate_module(m, "post-parse" as *u8) 181 if v_post_parse > 0 { 182 nxcx_log("nx_compile_x86: post-parse validator found violations (see above)\n" as *u8, 66) 183 } 184 185 // ---- 4. Run opt on each function ---- 186 // 187 // Enabled: the curated opt_run fixpoint (mem2reg + sccp + const-fold + 188 // copyprop + cse + gvn + licm + alloca_const + dce + thread_jumps + 189 // tail_call + strength_reduce + dse + block_merge + reassoc + sweep). 190 // mem2reg promotes alloca'd loop/state vars to SSA -> the dominant 191 // crypto-loop reload cost. Gated by the self-host gauntlet (byte-stable 192 // 2nd-gen fixpoint + differential KATs + register-survival KAT). 193 var fi: i64 = 0 194 while fi < m.n_functions { 195 let fn_base: i64 = m.functions as i64 196 let f: *Function = (fn_base + fi * 176) as *Function 197 opt_run(f) 198 fi = fi + 1 199 } 200 201 // ---- 4a. Single-block leaf inlining (module pass) ---- 202 // 203 // Reuses the shared opt_inline_module organ (nx_opt.nx), run HERE -- after 204 // per-function opt so callee bodies are clean SSA; the older nxc.nx front- 205 // ends run it PRE-opt where it almost never fires. When it fires we re-run 206 // opt_run so copyprop/const-fold/DCE clean the spliced bodies in the 207 // caller's context. Closes the second half of the racing-bench spectral 208 // gap (the ~400K uninlined eval_A calls gcc inlines). 209 // 210 // OPT-IN (/tmp/nx_inline_on): opt_inline_module returns 0 by default while 211 // an nx_cc miscompile it exposes (an instr-walk in inline_call/ 212 // opt_inline_module processing only its first element; root cause not yet 213 // identified) is outstanding, so this is a no-op on the normal path. See 214 // opt_inline_module's comment + the memory note. 215 let n_inlined: i64 = opt_inline_module(m) 216 if n_inlined > 0 { 217 var fj: i64 = 0 218 while fj < m.n_functions { 219 let fn_base2: i64 = m.functions as i64 220 let f2: *Function = (fn_base2 + fj * 176) as *Function 221 opt_run(f2) 222 fj = fj + 1 223 } 224 } 225 226 // ---- 4b. (Optional) IR dump for bootstrap-divergence diagnosis ---- 227 // 228 // If /tmp/nx_ir_dump is present, dump every function's IR to 229 // stderr in a byte-comparable format. Run the same source 230 // through qemu-RV64 + native lanes, diff the traces, pinpoint 231 // the diverging instruction. 3-step recipe: 232 // 1. touch /tmp/nx_ir_dump 233 // 2. cat repro.nx | qemu-riscv64-static _offc/nx_compile_x86.elf > /dev/null 2> /tmp/rv64.trace 234 // 3. cat repro.nx | _offc/nx_compile_x86_native.elf > /dev/null 2> /tmp/native.trace 235 // diff -u /tmp/rv64.trace /tmp/native.trace | head -30 236 let ird_marker: *u8 = "/tmp/nx_ir_dump" 237 let ird_len_p: *i64 = sys_mmap(8) as *i64 238 let ird_buf: *u8 = sys_read_file(ird_marker, ird_len_p) 239 if (ird_buf as i64) != 0 { 240 var fi: i64 = 0 241 while fi < m.n_functions { 242 let fn_base: i64 = m.functions as i64 243 let f: *Function = (fn_base + fi * 176) as *Function 244 nx_ir_dump_function(f, "post-parse" as *u8) 245 fi = fi + 1 246 } 247 } 248 249 // ---- 5. Emit x86_64 asm via the IR-driven backend ---- 250 let o: *OutBuf = out_new(NX_COMPILE_X86_OUT_CAP) 251 out_str(o, "# Generated by nx_compile_x86.nx via nx_x86_64_ctx\n") 252 out_str(o, " .att_syntax prefix\n") 253 x86ctx_emit_module(m, o) 254 255 // ---- 6. Write asm to stdout ---- 256 sys_write(1, o.buf, o.pos) 257 return NX_COMPILE_X86_OK 258}