code wiki / _hdl_build / nx_race_vs_c_test.nx

nx_race_vs_c_test.nx source

↩ module page · 131 lines · 5843 B

1// nx_race_vs_c_test.nx -- the ONGOING racing gate: Nishi vs C, 1:1, sovereign. 2// 3// "win over C in an ONGOING fashion" (operator) -- so this is a PERMANENT gate, not 4// a one-shot. It forks+execs both pre-built contestants (the SAME kernel compiled 5// by our nx_cc and by gcc -O2), captures each one's "<checksum> <cycles>" line, and 6// judges. The HARD invariant is CAPABILITY: byte-identical checksum = our codegen 7// did NOT miscompile. Speed/size are REPORTED + tracked, never red-failed -- we 8// already KNOW we trail gcc on raw compute (the stack-machine/no-regalloc tax), and 9// a gate that fails on the known grind would be a false-failing gate (racing 10// doctrine: bake a NON-false-failing gate). It fails ONLY on a correctness break -- 11// the one thing that must never regress while we close the speed gap. 12// 13// Build lane must produce /tmp/race_nishi.elf and /tmp/race_c.elf first (the two 14// contestants). Known answer: byte-identical checksum -> exit 0; prints the board. 15// 16// CITATIONS (real): fair-benchmark methodology -- Curtsinger & Berger, "Stabilizer: 17// Statistically Sound Performance Evaluation," ASPLOS 2013. The energy/time/memory 18// tradeoff this race feeds into -- Pereira et al., "Energy Efficiency across 19// Programming Languages," ACM SLE 2017. The named speed lever -- Poletto & Sarkar, 20// linear-scan register allocation, ACM TOPLAS 1999. 21 22import "nx_syscalls.nx" 23 24func rk_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 25func rk_emit(name: *u8, v: i64) -> i64 { 26 rk_puts(name) 27 let b: *u8 = sys_mmap(32); var m: i64 = v; if m < 0 { m = 0 - m } 28 let t: *u8 = sys_mmap(32); var k: i64 = 0 29 if m == 0 { t[0] = 48; k = 1 } 30 while m > 0 { t[k] = 48 + (m % 10); m = m / 10; k = k + 1 } 31 var i: i64 = 0; while i < k { b[i] = t[k - 1 - i]; i = i + 1 } 32 b[k] = 10; sys_write(1, b, k + 1); return 0 33} 34 35func rk_is_digit(c: i64) -> i64 { if c < 48 { return 0 } if c > 57 { return 0 } return 1 } 36 37// Run a binary with its stdout redirected to a file (open+dup3 before exec in the 38// child -- no pipe fd-packing ambiguity). Returns the child's exit code (0 = ok). 39func rk_run_to_file(binpath: *u8, outpath: *u8) -> i64 { 40 let argv: *i64 = sys_mmap(32) as *i64 41 argv[0] = binpath as i64 42 argv[1] = 0 43 let envp: *i64 = sys_mmap(8) as *i64 44 envp[0] = 0 45 let pid: i64 = sys_fork() 46 if pid < 0 { return pid } 47 if pid == 0 { 48 let fd: i64 = sys_openat_wr(outpath, 0x1a4) // 0644 49 if fd < 0 { sys_exit(126) } 50 sys_dup3(fd, 1, 0) 51 sys_close(fd) 52 sys_execve(binpath, argv, envp) 53 sys_exit(127) // exec failed 54 } 55 let status: *i64 = sys_mmap(16) as *i64 56 status[0] = 0 57 sys_wait4(pid, status, 0) 58 return wait_exit_code(status[0]) 59} 60 61func rk_read_file(path: *u8, buf: *u8, cap: i64) -> i64 { 62 let fd: i64 = sys_openat_rd(path) 63 if fd < 0 { return 0 - 1 } 64 var total: i64 = 0 65 var go: i64 = 1 66 while go == 1 { 67 let got: i64 = sys_read(fd, (buf as i64 + total) as *u8, cap - total) 68 if got <= 0 { go = 0 } else { total = total + got; if total >= cap { go = 0 } } 69 } 70 sys_close(fd) 71 return total 72} 73 74// parse two leading decimals ("<checksum> <cycles>") into out[0],out[1]. 75func rk_parse_two(buf: *u8, len: i64, out: *i64) -> i64 { 76 var i: i64 = 0 77 var which: i64 = 0 78 while which < 2 { 79 var adv: i64 = 1 80 while adv == 1 { 81 if i >= len { adv = 0 } 82 else { if rk_is_digit(buf[i] as i64) == 1 { adv = 0 } else { i = i + 1 } } 83 } 84 if i >= len { return 0 - 1 } 85 var v: i64 = 0 86 var rd: i64 = 1 87 while rd == 1 { 88 if i >= len { rd = 0 } 89 else { 90 let c: i64 = buf[i] as i64 91 if rk_is_digit(c) == 1 { v = v * 10 + (c - 48); i = i + 1 } else { rd = 0 } 92 } 93 } 94 out[which] = v 95 which = which + 1 96 } 97 return 0 98} 99 100func main() -> i64 { 101 let nbuf: *u8 = sys_mmap(256) 102 let cbuf: *u8 = sys_mmap(256) 103 let no: *i64 = sys_mmap(16) as *i64 104 let co: *i64 = sys_mmap(16) as *i64 105 106 let rn: i64 = rk_run_to_file("/tmp/race_nishi.elf" as *u8, "/tmp/race_n.out" as *u8) 107 let rc: i64 = rk_run_to_file("/tmp/race_c.elf" as *u8, "/tmp/race_c.out" as *u8) 108 if rn != 0 { rk_puts("nishi contestant did not run (build /tmp/race_nishi.elf first)\n" as *u8); sys_exit(2); return 2 } 109 if rc != 0 { rk_puts("c contestant did not run (build /tmp/race_c.elf first)\n" as *u8); sys_exit(3); return 3 } 110 111 let nl: i64 = rk_read_file("/tmp/race_n.out" as *u8, nbuf, 250) 112 let cl: i64 = rk_read_file("/tmp/race_c.out" as *u8, cbuf, 250) 113 if rk_parse_two(nbuf, nl, no) != 0 { rk_puts("parse nishi out failed\n" as *u8); sys_exit(4); return 4 } 114 if rk_parse_two(cbuf, cl, co) != 0 { rk_puts("parse c out failed\n" as *u8); sys_exit(5); return 5 } 115 116 rk_puts("=== ONGOING RACE: Nishi vs C (1:1, same kernel, same box) ===\n" as *u8) 117 rk_emit(" nishi checksum : " as *u8, no[0]) 118 rk_emit(" c checksum : " as *u8, co[0]) 119 rk_emit(" nishi cycles : " as *u8, no[1]) 120 rk_emit(" c cycles : " as *u8, co[1]) 121 if co[1] > 0 { rk_emit(" nishi/c cycles x100 (100=tie, >100=C ahead): " as *u8, (no[1] * 100) / co[1]) } 122 123 if no[0] == co[0] { rk_puts(" CAPABILITY: byte-identical checksum -> fair race, NO miscompile\n" as *u8) } 124 if no[1] <= co[1] { rk_puts(" SPEED: Nishi wins/ties\n" as *u8) } 125 else { rk_puts(" SPEED: C ahead -- tracked; lever = G1 regalloc + denser codegen (the grind)\n" as *u8) } 126 127 // HARD GATE: correctness only. Perf is tracked, never false-failed. 128 if no[0] != co[0] { rk_puts(" *** MISCOMPILE: checksums differ ***\n" as *u8); sys_exit(1); return 1 } 129 sys_exit(0) 130 return 0 131}