code wiki / (root) / nx_ccbuild_lib.nx

nx_ccbuild_lib.nx source

↩ module page · 156 lines · 7215 B

1// nx_ccbuild_lib.nx -- LIB: COMPILE A SOURCE WITH A NAMED COMPILER, THEN RUN IT. 2// 3// WHY THIS EXISTS. Every compiler gate needs the same four moves: fork/exec a 4// chosen cc, assemble+link its output sovereignly, make the product executable, 5// and run it capturing status. nx_cc_equiv_gate had the only correct copy, and 6// the next gate that needed it (nx_linecont_gate, LN15) would have COPIED it -- 7// which is the duplicate-ruler defect: two spellings of one invariant that drift 8// silently. nx_spendgate/nx_capsearch over 6,925 organs (corpus_complete=1) 9// returned NO incumbent exposing this primitive, so it is extracted here once. 10// 11// The bodies below are VERBATIM from nx_cc_equiv_gate's eq_* helpers so the 12// migration of that gate onto this lib is provably neutral: its oracle is its own 13// published verdict, rows=10 passed=10 selfhost=1 -- anything else means the 14// extraction changed behaviour and must be reverted, not argued with. 15// 16// EVERY CORRECTION THAT LIVED IN THOSE HELPERS IS CARRIED, NOT RE-LEARNED: 17// * nxasm writes 0644, so the product is NOT EXECUTABLE and every exec dies 127. 18// Both sides dying identically made two failures agree and READ AS AN 19// EQUIVALENCE. The fchmodat is load-bearing; do not drop it. 20// * a failed rename used to be DISCARDED, so elf_out went missing while the 21// build still returned 0 and the downstream 127 looked like a compiler fault. 22// * the compiler's stderr must go to a REAL log, never /dev/null: a gate that 23// reports a failure without its diagnostic makes the failure unactionable. 24// * every source path is relative to the buildroot, so a caller with a different 25// cwd (nx_job_run fixes cwd at ~/nishihost) fails to FIND its sources and every 26// row reports build-failed -- which reads as a broken compiler when nothing is 27// wrong. cb_anchor_root exists to make that impossible; call it FIRST. 28// 29// ONE DELIBERATE CHANGE FROM THE ORIGINAL: eq_build carried an `o_tmp` parameter 30// its body never read. It is dropped here rather than carried forward, and the 31// neutrality of dropping it is exactly what the equiv gate's re-run proves. 32 33import "nx_syscalls.nx" 34 35// Fork/exec `path` with argv/envp, optionally redirecting child stdout/stderr. 36// Returns the RAW wait status (0 == clean exit 0). 37func cb_run(path: *u8, argv: *i64, envp: *i64, redir_out: i64, redir_err: i64) -> i64 { 38 let pid: i64 = sys_fork() 39 if pid == 0 { 40 if redir_out >= 0 { sys_dup3(redir_out, 1, 0) } 41 if redir_err >= 0 { sys_dup3(redir_err, 2, 0) } 42 sys_execve(path, argv, envp) 43 sys_exit(127) 44 } 45 let st: *i64 = sys_mmap(16) as *i64 46 sys_wait4(pid, st, 0) 47 return st[0] 48} 49 50// -1 missing, else byte size. Names WHICH stage produced nothing. 51func cb_fsize(path: *u8) -> i64 { 52 let fd: i64 = sys_openat_rd(path) 53 if fd < 0 { return 0 - 1 } 54 let sz: i64 = sys_lseek(fd, 0, 2) 55 sys_close(fd) 56 return sz 57} 58 59// Compile src_path with cc_path -> elf_out via sovereign nxasm (NO gcc/binutils). 60// Returns 0 ok, 1 compile-fail, 2 asm-fail, 3 rename-fail. 61// `asm_log` receives the assembler's output; `devnull` is the COMPILER's stderr and 62// callers should pass a real fd when they intend to diagnose a failure. 63func cb_build(cc_path: *u8, src_path: *u8, s_tmp: *u8, 64 elf_out: *u8, envp: *i64, devnull: i64, 65 asm_tmp: *u8, asm_log: *u8) -> i64 { 66 return cb_build_flag(cc_path, src_path, s_tmp, elf_out, envp, devnull, 67 asm_tmp, asm_log, 0 as *u8) 68} 69 70// Same build, with ONE compiler flag appended after the source path. Added for the LN7 71// elision gate, which must compile the SAME source with and without `--bckelide` and compare 72// the assembly. cb_build now DELEGATES here rather than keeping its own copy of the sequence: 73// a second spelling of "fork the cc, assemble, rename, chmod" is precisely the duplicate-ruler 74// defect this file was extracted to end, and every correction in its header (the 0644 chmod, 75// the discarded rename, the real stderr log) is inherited here rather than re-learned. 76// A null `flag` reproduces cb_build's original argv exactly -- two slots, path then NULL. 77// NOTE the flag goes AFTER the path deliberately: nx_compile_x86's argv loop used to drop every 78// flag that followed the path (it clobbered its own cursor), and that defect was fixed on 79// 2026-08-25. Passing the flag in the position that USED to be broken means this lane fails 80// loudly if that regression ever returns, instead of quietly compiling without the flag. 81func cb_build_flag(cc_path: *u8, src_path: *u8, s_tmp: *u8, 82 elf_out: *u8, envp: *i64, devnull: i64, 83 asm_tmp: *u8, asm_log: *u8, flag: *u8) -> i64 { 84 let a1: *i64 = sys_mmap(8 * 4) as *i64 85 a1[0] = cc_path as i64; a1[1] = src_path as i64; a1[2] = 0 86 if flag != (0 as *u8) { a1[2] = flag as i64; a1[3] = 0 } 87 let sfd: i64 = sys_openat_wr(s_tmp, 0x1a4) 88 let st1: i64 = cb_run(cc_path, a1, envp, sfd, devnull) 89 sys_close(sfd) 90 if st1 != 0 { return 1 } 91 92 let nxasm: *u8 = "_offc/nxasm_x86_main.elf\x00" 93 let tmpe: *u8 = asm_tmp 94 let a2: *i64 = sys_mmap(8 * 6) as *i64 95 a2[0] = nxasm as i64 96 a2[1] = s_tmp as i64 97 a2[2] = tmpe as i64 98 a2[3] = 0 99 let alog: i64 = sys_openat_wr(asm_log, 0x1a4) 100 let arc: i64 = cb_run(nxasm, a2, envp, alog, alog) 101 sys_close(alog) 102 if arc != 0 { return 2 } 103 if sys_renameat(tmpe, elf_out) != 0 { return 3 } 104 sys_fchmodat(elf_out, 0x1ed) 105 return 0 106} 107 108// Run elf with stdout captured to out_path; returns RAW wait status. 109func cb_run_capture(elf: *u8, out_path: *u8, envp: *i64, devnull: i64) -> i64 { 110 let ofd: i64 = sys_openat_wr(out_path, 0x1a4) 111 let a: *i64 = sys_mmap(8 * 2) as *i64 112 a[0] = elf as i64; a[1] = 0 113 let st: i64 = cb_run(elf, a, envp, ofd, devnull) 114 sys_close(ofd) 115 return st 116} 117 118// Byte-compare two files. 1 equal, 0 different / unreadable. 119func cb_files_equal(pa: *u8, pb: *u8) -> i64 { 120 let fa: i64 = sys_openat_rd(pa) 121 let fb: i64 = sys_openat_rd(pb) 122 if fa < 0 { if fb < 0 { return 1 } } 123 if fa < 0 { sys_close(fb); return 0 } 124 if fb < 0 { sys_close(fa); return 0 } 125 let ba: *u8 = sys_mmap(65536) 126 let bb: *u8 = sys_mmap(65536) 127 var equal: i64 = 1 128 var more: i64 = 1 129 while more == 1 { 130 let na: i64 = sys_read(fa, ba, 65536) 131 let nb: i64 = sys_read(fb, bb, 65536) 132 if na != nb { equal = 0; more = 0 } 133 if more == 1 { 134 if na <= 0 { more = 0 } 135 var i: i64 = 0 136 while i < na { 137 if ba[i] != bb[i] { equal = 0; i = na; more = 0 } 138 i = i + 1 139 } 140 } 141 } 142 sys_close(fa) 143 sys_close(fb) 144 return equal 145} 146 147// A GATE WHOSE VERDICT DEPENDS ON THE CALLER'S WORKING DIRECTORY IS NOT A 148// MEASUREMENT. Anchor to the tree, THEN measure. Returns 0 when it could not 149// find the tree, so a caller can refuse rather than measure the wrong thing. 150func cb_anchor_root() -> i64 { 151 let fd: i64 = sys_openat_rd("buildroot/runtime/nx_compile_x86.nx\x00" as *u8) 152 if fd < 0 { return 0 } 153 sys_close(fd) 154 sys_chdir("buildroot\x00" as *u8) 155 return 1 156}