code wiki / _hdl_build / nx_build_run.nx
nx_build_run.nx source
↩ module page · 152 lines · 9129 B
1// nx_build_run.nx -- SOVEREIGN, REUSABLE build runner in pure NishiLang (operator: "stop using sh again").
2// Generalizes nx_build_orchestrator from one hardcoded target to ANY module passed as argv[1]: it compiles
3// (compiler stdout -> /tmp/<name>.s) with RECOMPILE-RETRY (the known-good compiler is nondeterministic and
4// sometimes emits an empty .s), assembles+links (gcc -> /tmp/<name>.elf), runs the ELF, and exits with the
5// ELF's own exit code. The ORCHESTRATION is sovereign NishiLang via sys_fork/dup3/execve/wait4 -- NO shell,
6// NO .sh runner. (gcc is still exec'd as the assembler/linker = the next dep to retire with nxasm; bash is
7// retired here.) Usage: nx_build_run.elf <module-basename-in-runtime/_hdl_build>. license_tier: ORIGINAL
8import "nx_syscalls.nx"
9import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc)
10
11// build-runner verdicts (structured, not magic ints buried in logic)
12const BR_OK: i64 = 0
13const BR_USAGE: i64 = 2
14const BR_COMPILE_FAIL: i64 = 3
15const BR_LINK_FAIL: i64 = 4
16
17const BR_MIN_ASM_BYTES: i64 = 1000 // a real .s is far larger; <= this = the empty-output nondeterminism
18const BR_MAX_RETRIES: i64 = 12 // recompile-retry ceiling for the nondeterministic compiler
19
20func brn_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
21// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer
22// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the
23// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls).
24// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign.
25func brn_putn(v: i64) -> i64 { nxi_out(v); return 0 }
26func brn_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
27func brn_fputs(fd: i64, s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(fd, s, n); return 0 }
28// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer
29// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the
30// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls).
31// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign.
32func brn_fputn(fd: i64, v: i64) -> i64 { nxi_fd(fd, v); return 0 }
33
34// append NUL-terminated `s` into `dst` at `off`; return the new offset (does not NUL-terminate -- caller does).
35func brn_cat(dst: *u8, off: i64, s: *u8) -> i64 {
36 var i: i64 = 0
37 while s[i] != (0 as u8) { dst[off + i] = s[i]; i = i + 1 }
38 return off + i
39}
40
41// fork + optional stdout/stderr redirects + execve; parent waits; returns child's WEXITSTATUS.
42// redir_out -> child stdout (e.g. the .s file); redir_err -> child stderr (e.g. /dev/null to mute compiler noise).
43func brn_run(path: *u8, argv: *i64, envp: *i64, redir_out: i64, redir_err: i64) -> i64 {
44 let pid: i64 = sys_fork()
45 if pid == 0 {
46 if redir_out >= 0 { sys_dup3(redir_out, 1, 0) }
47 if redir_err >= 0 { sys_dup3(redir_err, 2, 0) }
48 sys_execve(path, argv, envp)
49 sys_exit(127)
50 }
51 let st: *i64 = sys_mmap(16) as *i64
52 sys_wait4(pid, st, 0)
53 return (st[0] >> 8) & 0xff
54}
55
56// like brn_run but returns the RAW wait status (so the caller can see signal-kill, not just WEXITSTATUS).
57func brn_run_status(path: *u8, argv: *i64, envp: *i64, redir_out: i64, redir_err: i64) -> i64 {
58 let pid: i64 = sys_fork()
59 if pid == 0 {
60 if redir_out >= 0 { sys_dup3(redir_out, 1, 0) }
61 if redir_err >= 0 { sys_dup3(redir_err, 2, 0) }
62 sys_execve(path, argv, envp)
63 sys_exit(127)
64 }
65 let st: *i64 = sys_mmap(16) as *i64
66 sys_wait4(pid, st, 0)
67 return st[0]
68}
69
70// byte-equality over n bytes -- the determinism check for the compile-twice guard.
71func br_bytes_equal(a: *u8, b: *u8, n: i64) -> i64 {
72 var i: i64 = 0
73 while i < n { if a[i] != b[i] { return 0 } i = i + 1 }
74 return 1
75}
76
77func main(argc: i64, argv: *i64) -> i64 {
78 if argc < 2 { brn_puts("usage: nx_build_run <module-basename>\n" as *u8); sys_exit(BR_USAGE); return BR_USAGE }
79 let name: *u8 = argv[1] as *u8
80
81 // compose paths: runtime/_hdl_build/<name>.nx , /tmp/<name>.s , /tmp/<name>.elf
82 let src: *u8 = sys_mmap(512); var o: i64 = 0
83 o = brn_cat(src, o, "runtime/_hdl_build/" as *u8); o = brn_cat(src, o, name); o = brn_cat(src, o, ".nx" as *u8); src[o] = 0 as u8
84 let spath: *u8 = sys_mmap(256); o = 0
85 o = brn_cat(spath, o, "/tmp/" as *u8); o = brn_cat(spath, o, name); o = brn_cat(spath, o, ".s" as *u8); spath[o] = 0 as u8
86 let elfpath: *u8 = sys_mmap(256); o = 0
87 o = brn_cat(elfpath, o, "/tmp/" as *u8); o = brn_cat(elfpath, o, name); o = brn_cat(elfpath, o, ".elf" as *u8); elfpath[o] = 0 as u8
88 let spath2: *u8 = sys_mmap(256); o = 0
89 o = brn_cat(spath2, o, "/tmp/" as *u8); o = brn_cat(spath2, o, name); o = brn_cat(spath2, o, ".s2" as *u8); spath2[o] = 0 as u8
90
91 // SOVEREIGN compiler (v2): complete syscall table (kill/mkdirat/fchmodat/renameat2), fast on non-crypto
92 // (0.11s on the control plane). C bootstrap retired from the build path. (Crypto stack is slow here
93 // until W2 regalloc lands -- tracked.) This is the team's right tool: sovereign + <10s one-step fixes.
94 let compiler: *u8 = "_offc/nx_cc_sovereign.elf" as *u8
95 let gcc: *u8 = "/usr/bin/gcc" as *u8
96 let envp: *i64 = sys_mmap(8*4) as *i64; envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64; envp[1] = 0
97 // /dev/null to mute the compiler's symbol-list stderr noise (the .s goes to stdout, not stderr).
98 let devnull: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4)
99
100 // STEP 1 -- DETERMINISM GUARD (issue auto-filed via nx_issue_intake, tutored fix): compile TWICE
101 // and require byte-IDENTICAL .s. Good compiles are byte-deterministic (verified: 3/3 identical), so
102 // two matching outputs => trustworthy; a mismatch means the compiler hit its nondeterministic flake
103 // -- EMPTY *or* large-but-broken (which the old size-only check silently accepted) -> recompile.
104 let a1: *i64 = sys_mmap(8*4) as *i64; a1[0] = compiler as i64; a1[1] = src as i64; a1[2] = 0
105 var asm_bytes: i64 = 0; var tries: i64 = 0; var stable: i64 = 0
106 while tries < BR_MAX_RETRIES {
107 let sfd: i64 = sys_openat_wr(spath, 0x1a4)
108 brn_run(compiler, a1, envp, sfd, devnull)
109 sys_close(sfd)
110 let sfd2: i64 = sys_openat_wr(spath2, 0x1a4)
111 brn_run(compiler, a1, envp, sfd2, devnull)
112 sys_close(sfd2)
113 let l1: *i64 = sys_mmap(16) as *i64
114 let d1: *u8 = sys_read_file(spath, l1)
115 let l2: *i64 = sys_mmap(16) as *i64
116 let d2: *u8 = sys_read_file(spath2, l2)
117 asm_bytes = l1[0]
118 if asm_bytes > BR_MIN_ASM_BYTES { if l1[0] == l2[0] { if br_bytes_equal(d1, d2, l1[0]) == 1 { stable = 1; break } } }
119 tries = tries + 1
120 }
121 if stable == 0 {
122 brn_puts("COMPILE-FAIL (unstable/nondeterministic .s after retries) for " as *u8); brn_puts(name); brn_puts("\n" as *u8)
123 sys_exit(BR_COMPILE_FAIL); return BR_COMPILE_FAIL
124 }
125
126 // STEP 2 -- assemble+link.
127 let a2: *i64 = sys_mmap(8*12) as *i64
128 a2[0]=gcc as i64; a2[1]="-nostdlib" as *u8 as i64; a2[2]="-no-pie" as *u8 as i64; a2[3]="-static" as *u8 as i64
129 a2[4]=spath as i64; a2[5]="-o" as *u8 as i64; a2[6]=elfpath as i64; a2[7]=0
130 let rc2: i64 = brn_run(gcc, a2, envp, 0 - 1, 0 - 1)
131 if rc2 != 0 { brn_puts("LINK-FAIL for " as *u8); brn_puts(name); brn_puts("\n" as *u8); sys_exit(BR_LINK_FAIL); return BR_LINK_FAIL }
132
133 // STEP 3 -- run the built ELF; surface its exit code. The build runner is the STANDING signal-capturer
134 // (operator: "we dont want dodging -- all of this should hit the backlog with a nishi team member capturing
135 // these signals"): a runtime CRASH (killed by a signal despite a byte-stable compile) is a MISCOMPILE -> it
136 // is auto-filed to /tmp/nishi_issues.log with THIS module as the repro, never silently dodged.
137 let a3: *i64 = sys_mmap(8*4) as *i64; a3[0] = elfpath as i64; a3[1] = 0
138 let raw3: i64 = brn_run_status(elfpath, a3, envp, 0 - 1, 0 - 1)
139 let rc3: i64 = (raw3 >> 8) & 0xff
140 let sig3: i64 = raw3 & 0x7f
141 if sig3 != 0 {
142 let cfd: i64 = sys_openat_append("/tmp/nishi_issues.log" as *u8, 0x1a4)
143 brn_fputs(cfd, "SIGNAL kind=RUNTIME-CRASH module=" as *u8); brn_fputs(cfd, name)
144 brn_fputs(cfd, " signal=" as *u8); brn_fputn(cfd, sig3)
145 brn_fputs(cfd, " :: auto-captured by build runner (stable compile + runtime crash = miscompile) -> Engineer/W2 regalloc, repro=this module\n" as *u8)
146 sys_close(cfd)
147 brn_puts("[nx_build_run] CRASH-CAPTURED signal=" as *u8); brn_putn(sig3); brn_puts(" -> filed to /tmp/nishi_issues.log\n" as *u8)
148 }
149 brn_puts("[nx_build_run] " as *u8); brn_puts(name); brn_puts(": asm=" as *u8); brn_putn(asm_bytes)
150 brn_puts("B retries=" as *u8); brn_putn(tries); brn_puts(" run-exit=" as *u8); brn_putn(rc3); brn_puts("\n" as *u8)
151 sys_exit(rc3); return rc3
152}