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}