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}