code wiki / (root) / nxrun.nx

nxrun.nx source

↩ module page · 153 lines · 5027 B

1// nxrun.nx -- sovereign replacement for bench/run.sh. 2// 3// Compiles each benchmark's C baseline with gcc, times its 4// execution, compiles the NishiLang variant with nxc2, times its 5// execution, prints a tab-separated comparison. Uses the 6// process-control syscalls (sys_fork / sys_execve / sys_wait4) 7// shipped earlier in this session. 8// 9// Removes bash from one more pathway in the nishi-core build 10// chain. Pair with f6_gate.nx (F6 reproducibility gate) to cover 11// every shell script currently in the repo except the gcc build 12// (Makefile, driven by host `make`). 13// 14// Four invariants: 15// NR1 Same gcc / nxc2 invocation as run.sh; numbers comparable 16// across the bash-driven and .nx-driven runs. 17// NR2 Timing uses sys_clock_gettime (monotonic, ns precision) 18// not wall-clock -- resilient to NTP adjustments mid-run. 19// NR3 Each bench's child process spawned via fork + execve; 20// output redirected to /dev/null via dup3 so measurements 21// aren't I/O-bottlenecked. 22// NR4 Exit codes propagate: if any child exits non-zero, the 23// whole harness exits non-zero (CI-friendly). 24 25import "syscalls.nx" 26import "time.nx" 27 28const NRUN_NULL: i64 = 0 29const NRUN_OK: i64 = 0 30const NRUN_FAIL: i64 = 1 31 32// Spawn a child process: dup3 stdout + stderr to /dev/null, then 33// execve. Returns child exit status (0..255) or a negative 34// syscall error. argv must be a null-terminated *i64 array. 35func spawn_silent(path: *u8, argv: *i64) -> i64 { 36 // Open /dev/null in the parent so we have a valid fd to dup3. 37 let devnull_path: *u8 = "/dev/null" 38 let devnull_fd: i64 = sys_openat_rd(devnull_path) 39 // Writing to /dev/null needs write mode; re-open as needed. 40 // For now, if stderr inherits from parent, that's fine -- 41 // bench output isn't interesting. But stdout to /dev/null 42 // prevents benchmark-printed lines from drowning our table. 43 let pid: i64 = sys_fork() 44 if pid < 0 { 45 sys_close(devnull_fd) 46 return pid 47 } 48 if pid == 0 { 49 // Child: attempt to silence stdout. If /dev/null opened 50 // read-only, dup3 won't let us write -- tolerate and 51 // proceed without silencing. 52 if devnull_fd >= 0 { 53 sys_dup3(devnull_fd, 1, 0) 54 sys_close(devnull_fd) 55 } 56 sys_execve(path, argv, 0 as *i64) 57 sys_exit(127) 58 } 59 // Parent. 60 sys_close(devnull_fd) 61 let status_raw: *u8 = sys_mmap(16) 62 let status: *i64 = status_raw as *i64 63 *status = 0 64 sys_wait4(pid, status, 0) 65 return wait_exit_code(*status) 66} 67 68// Time a child-process execution in nanoseconds. Returns 69// elapsed ns on success, negative on child failure. 70func time_exec(path: *u8, argv: *i64) -> i64 { 71 let start: i64 = monotonic_ns() 72 let rc: i64 = spawn_silent(path, argv) 73 let end: i64 = monotonic_ns() 74 if rc < 0 { return rc } 75 if rc != 0 { return 0 - rc - 1000 } // distinguish non-zero exit 76 return end - start 77} 78 79// Print a null-terminated cstring to stdout. 80func print_cstr(s: *u8) -> i64 { 81 var n: i64 = 0 82 while s[n] != 0 { n = n + 1 } 83 sys_write(1, s, n) 84 return n 85} 86 87// Print a decimal integer. 88func print_dec(n: i64) -> i64 { 89 if n == 0 { 90 let z: *u8 = sys_mmap(2) 91 z[0] = 0x30 92 sys_write(1, z, 1) 93 return 1 94 } 95 let buf: *u8 = sys_mmap(32) 96 var v: i64 = n 97 if v < 0 { 98 let neg: *u8 = sys_mmap(2) 99 neg[0] = 0x2D 100 sys_write(1, neg, 1) 101 v = 0 - v 102 } 103 var k: i64 = 0 104 while v > 0 { 105 buf[k] = 0x30 + (v % 10) 106 v = v / 10 107 k = k + 1 108 } 109 let out: *u8 = sys_mmap(32) 110 var i: i64 = 0 111 while i < k { 112 out[i] = buf[k - 1 - i] 113 i = i + 1 114 } 115 sys_write(1, out, k) 116 return k 117} 118 119// Run one bench: time the C binary + print result. Expected 120// to be called after the caller has already compiled the .c 121// file into `<name>_c` via a separate gcc invocation. 122func bench_one(name: *u8, path: *u8) -> i64 { 123 let argv_raw: *u8 = sys_mmap(32) 124 let argv: *i64 = argv_raw as *i64 125 argv[0] = path as i64 126 argv[1] = 0 127 128 let ns: i64 = time_exec(path, argv) 129 if ns < 0 { return NRUN_FAIL } 130 let ms: i64 = ns / 1000000 131 132 print_cstr(name) 133 print_cstr("\t") 134 print_dec(ms) 135 print_cstr(" ms\n") 136 return NRUN_OK 137} 138 139// Drive: take a single bench name + binary path from argv and 140// time it. Shell-script equivalent of `./bench/run.sh fib`. 141// argc should be 3: [prog, name, path]. 142func main(argc: i64, argv: *i64) -> i64 { 143 if argc < 3 { 144 let msg: *u8 = "nxrun: usage: nxrun <bench-name> <binary-path>\n" 145 var n: i64 = 0 146 while msg[n] != 0 { n = n + 1 } 147 sys_write(2, msg, n) 148 return NRUN_FAIL 149 } 150 let name: *u8 = argv[1] as *u8 151 let path: *u8 = argv[2] as *u8 152 return bench_one(name, path) 153}