code wiki / (root) / _offc_nxc_small.nx

_offc_nxc_small.nx source

↩ module page · 169 lines · 7039 B

1// nxc.nx -- the full NishiLang sovereign-compiler driver. 2// 3// Chains every piece of our stack in pure NishiLang: 4// 5// source (.nx) -> import-expand -> lex -> parse -> opt 6// -> regalloc -> riscv -> nxasm (assemble) 7// -> elf_writer (wrap) -> sys_write(stdout) 8// 9// Output is a self-contained RV64 Linux ELF executable. Zero third- 10// party code on the program path: no gcc, no binutils `as`, no ld, 11// no libc, no external crt0. The only external thing is the Linux 12// kernel ABI, which NishiOS replaces 1:1. 13// 14// CLI: `nxc <path>.nx` reads the file, runs the import expander to 15// inline `import \"...\"` directives into a single source blob, then 16// drives the compile pipeline and writes the ELF to stdout. Pipe 17// to a file + chmod +x to run: 18// nxc hello.nx > hello && chmod +x hello && ./hello 19// 20// With no args it compiles a baked-in demo source so `./nxc` still 21// exits successfully for smoke-testing the chain. 22// 23// Status (2026-04-22): parse.nx has full monomorphization for 24// generic struct + enum; the earlier \"syntax accepted but no 25// substitution\" gap is closed. Outstanding work to fully retire 26// gcc from the compiler bootstrap is QEMU/real-RV64 verification of 27// the produced ELF (Phase 5 roadmap). 28 29import "syscalls.nx" 30import "types.nx" 31import "lex_kinds.nx" 32import "outbuf.nx" 33import "ir.nx" 34import "lex.nx" 35import "parse.nx" 36import "opt.nx" 37import "regalloc.nx" 38import "riscv.nx" 39import "crt0.nx" 40import "import.nx" 41import "nxasm_v2.nx" 42import "elf_writer.nx" 43 44// Drive: source text -> ELF bytes written to `fd`. 45// 46// Buffer sizes calibrated against nxc.nx-compiling-itself (the 47// Thompson-defense bootstrap target). Source ~6KLOC NishiLang 48// expands to ~85K lines of asm + ~340K bytes of code + ~250K 49// tokens. Generous headroom to handle real programs without 50// overflow: 51// 52// tokens: 256K (was 4K) -- Source <= ~25K LOC sustainable 53// asm_out: 16 MB (was 128K) -- compiler-class outputs fit 54// code_buf: 4 MB (was 64K) -- 1M instructions max 55// 56// All allocated via sys_mmap which is page-aligned + lazy-faulted, 57// so unused pages cost ~zero RSS. 58func nxc_compile(src: *u8, fd: i64) -> i64 { 59 // Stage 1: lex source into tokens. 256K-token cap covers 60 // any program up to ~25K source lines. 61 sys_write(2, "nxc: lex start\n" as *u8, 15) 62 let toks: *Tok = lex_source(src, 4096) 63 if toks == (0 as *Tok) { return 1 } 64 sys_write(2, "nxc: lex done\n" as *u8, 14) 65 66 // Stage 2: parse into a Module. 67 let m: *Module = parse_module(toks, 0 as *Module) 68 if m == (0 as *Module) { return 2 } 69 sys_write(2, "nxc: parse done\n" as *u8, 16) 70 71 // Stage 2.5: module-level cross-function inlining (F4 pass). 72 // Walks every call site; tiny pure callees get inlined into 73 // their callers. Defined in opt.nx since 188; previously 74 // unwired, now active in the sovereign self-host pipeline. 75 opt_inline_module(m) 76 77 // Stage 3: opt + regalloc + riscv emit for every function. 78 // Prepend the sovereign _start stub so the ELF entry point 79 // lands on valid code that forwards argc/argv to main + exits. 80 // 81 // Progress markers on stderr: one '.' per 10 functions compiled 82 // so bootstrap_proof.sh can see liveness during a slow qemu run. 83 // Suppressed when NXC_QUIET is set (not yet wired; future flag). 84 sys_write(2, "nxc: lex+parse done\n" as *u8, 20) 85 let asm_out: *OutBuf = out_new(16777216) // 16 MB 86 emit_crt0_start(asm_out) 87 var fi: i64 = 0 88 while fi < m.n_functions { 89 let fn_base: i64 = m.functions as i64 90 let f: *Function = (fn_base + fi * 176) as *Function 91 let name_addr: i64 = f.name_start 92 let fn_name: *u8 = name_addr as *u8 93 opt_run(f) 94 let locs_raw: *u8 = sys_mmap(f.n_values * 24 + 64) 95 let locs: *ValueLoc = locs_raw as *ValueLoc 96 let cs_raw: *u8 = sys_mmap(16) 97 let cs: *i64 = cs_raw as *i64 98 *cs = 0 99 let cs_fpr_raw: *u8 = sys_mmap(16) 100 let cs_fpr: *i64 = cs_fpr_raw as *i64 101 *cs_fpr = 0 102 let sb_raw: *u8 = sys_mmap(16) 103 let sb: *i64 = sb_raw as *i64 104 *sb = 0 105 regalloc_function(f, locs, cs, cs_fpr, sb) 106 // Y2K-class fix (T#y2k-class-001): ra_slot above spill area. 107 var _ra_slot_in: i64 = (*sb + 7) & (0 - 8) 108 if _ra_slot_in < 8 { _ra_slot_in = 8 } 109 emit_function(f, locs, asm_out, fn_name, _ra_slot_in + 8, _ra_slot_in, *cs, *cs_fpr) 110 fi = fi + 1 111 if (fi - (fi / 10) * 10) == 0 { 112 sys_write(2, "." as *u8, 1) 113 } 114 } 115 sys_write(2, "\nnxc: codegen done\n" as *u8, 19) 116 117 // Stage 4: assemble the .s text into machine bytes. 118 // 4 MB code buffer covers up to ~1 M instructions. 119 let code_buf: *u8 = sys_mmap(4194304) 120 let code_len: i64 = assemble(asm_out.buf, asm_out.pos, code_buf, 4194304) 121 if code_len <= 0 { return 3 } 122 123 // Stage 5: wrap in an ELF executable and emit. 124 write_elf(code_buf, code_len, fd) 125 return 0 126} 127 128// Sovereign CLI: `nxc <source.nx>` prints the compiled ELF to stdout. 129// Pipe to a file: `nxc hello.nx > hello && chmod +x hello && ./hello`. 130// 131// Main signature matches the RV64 Linux kernel's entry contract: 132// a0 = argc, a1 = argv (pointer to an array of char* pointers). 133// Our `_start` stub marshals these from the initial stack; this 134// function unpacks the path string from argv[1] and drives the 135// compiler pipeline. 136// 137// When invoked with no args we fall back to a hardcoded demo source 138// so `./nxc` (no arg) still produces a working ELF that exits 30. 139// Top-level buffer cap for the import-expanded source text. Every 140// runtime/*.nx combined is well under 1 MB; 4 MB gives headroom for 141// large trees. One allocation, no realloc path. 142const EXPAND_OUT_CAP: i64 = 4194304 143 144func main(argc: i64, argv: *i64) -> i64 { 145 if argc < 2 { 146 // Fallback demo: source baked in for smoke-testing the chain. 147 // Single file, no imports -- exercises the compile pipeline 148 // without touching the import preprocessor. 149 let demo: *u8 = "func main() -> i64 { return __syscall(93, 30, 0, 0, 0, 0, 0) }" 150 return nxc_compile(demo, 1) 151 } 152 // argv[1] is a (char *). Interpret as a *u8 path and drive the 153 // full preprocessor -> compile chain. 154 let path: *u8 = (argv[1]) as *u8 155 156 sys_write(2, "nxc: expand start\n" as *u8, 18) 157 let expand_buf: *u8 = sys_mmap(EXPAND_OUT_CAP) 158 let ctx: *ExpandCtx = expand_ctx_new(expand_buf, EXPAND_OUT_CAP) 159 let rc: i64 = expand_imports(ctx, path) 160 if rc < 0 { return 10 - rc } // nonzero exit encodes error kind 161 sys_write(2, "nxc: expand done\n" as *u8, 17) 162 163 // Null-terminate the expanded buffer so the lexer sees EOF. 164 let op: *i64 = ctx.out_pos 165 let end: i64 = *op 166 expand_buf[end] = 0 167 168 return nxc_compile(expand_buf, 1) 169}