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}