nx_cc_splice_bisect.nx source
↩ module page · 305 lines · 10794 B
1// nx_cc_splice_bisect.nx -- PER-FUNCTION COMPILER-CODEGEN BISECTOR.
2//
3// Team organ (Doctor's localizer, sibling of nx_cc_equiv_gate): when
4// a challenger compiler's output misbehaves, this finds WHICH
5// function's codegen carries the defect -- no awk, no shell loops,
6// pure NishiLang file IO + fork/execve/wait4 (sovereign judging law).
7//
8// Method (asm splice): for every function whose body DIFFERS between
9// the two asm files, build a FRANKEN image = base.s with only that
10// one function's body replaced by the donor's, assemble+link it, and
11// run the franken compiler on a probe source. A function is a
12// BREAKER iff its splice alone flips the probe verdict. Functions
13// are self-contained ABI units, so single-function splices are valid
14// experiment isolation.
15//
16// Born 2026-06-10 bisecting the awakened-LICM self-host breakage
17// (ledger T#opt-dom-licm-dormant-paths): corpus rows all GREEN,
18// self-host RED -- the defect lives in specific big-function hoists,
19// and this organ names them.
20//
21// Usage: nx_cc_splice_bisect <base.s> <donor.s> [probe.nx]
22// base.s asm of the BLESSED compiler compiling some source
23// donor.s asm of the CHALLENGER compiling the SAME source
24// probe.nx source the franken compiler must still compile
25// (default runtime/_derefcast_minrepro.nx; compile rc 0
26// + non-empty asm = behaves)
27// Exit 0 iff NO breaker found (prints/logs the per-function table
28// to knowledge/status/cc_splice_bisect.log either way).
29//
30// license_tier: ORIGINAL
31
32import "nx_syscalls.nx"
33
34const SB_MAX_FNS: i64 = 4096
35
36func sb_puts2(logfd: i64, s: *u8) -> i64 {
37 var n: i64 = 0
38 while s[n] != (0 as u8) { n = n + 1 }
39 sys_write(1, s, n)
40 if logfd > 0 { sys_write(logfd, s, n) }
41 return 0
42}
43
44func sb_putn2(logfd: i64, v: i64) -> i64 {
45 let t: *u8 = sys_mmap(28)
46 let o: *u8 = sys_mmap(28)
47 var m: i64 = v
48 var neg: i64 = 0
49 if m < 0 { neg = 1; m = 0 - m }
50 var k: i64 = 0
51 if m == 0 { t[0] = 48 as u8; k = 1 }
52 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
53 var w: i64 = 0
54 if neg == 1 { o[0] = 45 as u8; w = 1 }
55 var i: i64 = 0
56 while i < k { o[w + i] = t[k - 1 - i]; i = i + 1 }
57 sys_write(1, o, w + k)
58 if logfd > 0 { sys_write(logfd, o, w + k) }
59 return 0
60}
61
62func sb_putsn2(logfd: i64, s: *u8, n: i64) -> i64 {
63 sys_write(1, s, n)
64 if logfd > 0 { sys_write(logfd, s, n) }
65 return 0
66}
67
68// Is buf[off..] the start of a top-level function label line?
69// Shape: ^[A-Za-z_][A-Za-z0-9_]*:\n (directives are indented or
70// dot-prefixed; .L local labels are dot-prefixed -- both excluded.)
71// Returns the name length, or 0.
72func sb_label_len(buf: *u8, off: i64, end: i64) -> i64 {
73 var c: i64 = buf[off] & 0xff
74 var alpha: i64 = 0
75 if c >= 65 { if c <= 90 { alpha = 1 } }
76 if c >= 97 { if c <= 122 { alpha = 1 } }
77 if c == 95 { alpha = 1 }
78 if alpha == 0 { return 0 }
79 var p: i64 = off
80 while p < end {
81 c = buf[p] & 0xff
82 if c == 58 { // ':'
83 if p + 1 < end {
84 if (buf[p + 1] & 0xff) != 10 { return 0 }
85 }
86 return p - off
87 }
88 var ok: i64 = 0
89 if c >= 65 { if c <= 90 { ok = 1 } }
90 if c >= 97 { if c <= 122 { ok = 1 } }
91 if c >= 48 { if c <= 57 { ok = 1 } }
92 if c == 95 { ok = 1 }
93 if ok == 0 { return 0 }
94 p = p + 1
95 }
96 return 0
97}
98
99// Scan an asm buffer into parallel fn tables.
100// tab layout per entry: name_off, name_len, body_off, body_end.
101// body = from the label line through the byte before the next
102// top-level label (carries .size/.globl of the next fn's preamble --
103// harmless: names are identical on both sides of every splice).
104// Returns the number of functions found.
105func sb_scan(buf: *u8, n: i64, tab: *i64) -> i64 {
106 var cnt: i64 = 0
107 var off: i64 = 0
108 var at_line_start: i64 = 1
109 while off < n {
110 if at_line_start == 1 {
111 let ll: i64 = sb_label_len(buf, off, n)
112 if ll > 0 {
113 if cnt > 0 {
114 tab[(cnt - 1) * 4 + 3] = off // close previous body
115 }
116 if cnt < SB_MAX_FNS {
117 tab[cnt * 4] = off
118 tab[cnt * 4 + 1] = ll
119 tab[cnt * 4 + 2] = off
120 tab[cnt * 4 + 3] = n
121 cnt = cnt + 1
122 }
123 }
124 }
125 if (buf[off] & 0xff) == 10 { at_line_start = 1 }
126 if (buf[off] & 0xff) != 10 { at_line_start = 0 }
127 off = off + 1
128 }
129 return cnt
130}
131
132func sb_names_eq(a: *u8, ao: i64, al: i64, b: *u8, bo: i64, bl: i64) -> i64 {
133 if al != bl { return 0 }
134 var i: i64 = 0
135 while i < al {
136 if a[ao + i] != b[bo + i] { return 0 }
137 i = i + 1
138 }
139 return 1
140}
141
142func sb_bodies_eq(a: *u8, as_: i64, ae: i64, b: *u8, bs: i64, be: i64) -> i64 {
143 if ae - as_ != be - bs { return 0 }
144 var i: i64 = 0
145 let len: i64 = ae - as_
146 while i < len {
147 if a[as_ + i] != b[bs + i] { return 0 }
148 i = i + 1
149 }
150 return 1
151}
152
153// fork + redirects + execve; parent waits; returns RAW wait status.
154func sb_run(path: *u8, argv: *i64, envp: *i64, redir_out: i64, redir_err: i64) -> i64 {
155 let pid: i64 = sys_fork()
156 if pid == 0 {
157 if redir_out >= 0 { sys_dup3(redir_out, 1, 0) }
158 if redir_err >= 0 { sys_dup3(redir_err, 2, 0) }
159 sys_execve(path, argv, envp)
160 sys_exit(127)
161 }
162 let st: *i64 = sys_mmap(16) as *i64
163 sys_wait4(pid, st, 0)
164 return st[0]
165}
166
167// Write base with one fn body swapped for the donor's -> /tmp/sb_frank.s
168func sb_write_franken(base: *u8, base_n: i64, bs: i64, be: i64,
169 donor: *u8, ds: i64, de: i64) -> i64 {
170 let fd: i64 = sys_openat_wr("/tmp/sb_frank.s\x00" as *u8, 0x1a4)
171 if fd < 0 { return 0 - 1 }
172 sys_write(fd, base, bs)
173 sys_write(fd, donor + ds, de - ds)
174 sys_write(fd, base + be, base_n - be)
175 sys_close(fd)
176 return 0
177}
178
179// as + ld + run franken compiler on probe. Returns:
180// 0 behaves (probe compiles, rc 0, non-empty asm)
181// 1 as-fail, 2 ld-fail, else the RAW probe wait status.
182func sb_try_franken(probe: *u8, envp: *i64, devnull: i64) -> i64 {
183 let asbin: *u8 = "/usr/bin/as\x00"
184 let a2: *i64 = sys_mmap(8 * 6) as *i64
185 a2[0] = asbin as i64
186 a2[1] = "/tmp/sb_frank.s\x00" as *u8 as i64
187 a2[2] = "-o\x00" as *u8 as i64
188 a2[3] = "/tmp/sb_frank.o\x00" as *u8 as i64
189 a2[4] = 0
190 if sb_run(asbin, a2, envp, 0 - 1, devnull) != 0 { return 1 }
191
192 let ldbin: *u8 = "/usr/bin/ld\x00"
193 let a3: *i64 = sys_mmap(8 * 6) as *i64
194 a3[0] = ldbin as i64
195 a3[1] = "/tmp/sb_frank.o\x00" as *u8 as i64
196 a3[2] = "-o\x00" as *u8 as i64
197 a3[3] = "/tmp/sb_frank.elf\x00" as *u8 as i64
198 a3[4] = 0
199 if sb_run(ldbin, a3, envp, 0 - 1, devnull) != 0 { return 2 }
200
201 let frank: *u8 = "/tmp/sb_frank.elf\x00"
202 let a4: *i64 = sys_mmap(8 * 4) as *i64
203 a4[0] = frank as i64
204 a4[1] = probe as i64
205 a4[2] = 0
206 let outfd: i64 = sys_openat_wr("/tmp/sb_probe.s\x00" as *u8, 0x1a4)
207 let st: i64 = sb_run(frank, a4, envp, outfd, devnull)
208 sys_close(outfd)
209 if st != 0 { return st }
210 // Probe asm must be non-empty (a 0-byte success is a lie).
211 let pf: i64 = sys_openat_rd("/tmp/sb_probe.s\x00" as *u8)
212 if pf < 0 { return 3 }
213 let sz: i64 = sys_lseek(pf, 0, 2)
214 sys_close(pf)
215 if sz < 64 { return 3 }
216 return 0
217}
218
219func main(argc: i64, argv: *i64) -> i64 {
220 if argc < 3 {
221 sb_puts2(0 - 1, "usage: nx_cc_splice_bisect <base.s> <donor.s> [probe.nx]\n\x00" as *u8)
222 return 2
223 }
224 let base_path: *u8 = argv[1] as *u8
225 let donor_path: *u8 = argv[2] as *u8
226 var probe: *u8 = "runtime/_derefcast_minrepro.nx\x00"
227 if argc >= 4 { probe = argv[3] as *u8 }
228
229 let bl_p: *i64 = sys_mmap(16) as *i64
230 let base: *u8 = sys_read_file(base_path, bl_p)
231 let base_n: i64 = bl_p[0]
232 let dl_p: *i64 = sys_mmap(16) as *i64
233 let donor: *u8 = sys_read_file(donor_path, dl_p)
234 let donor_n: i64 = dl_p[0]
235 if base_n < 1 { return 3 }
236 if donor_n < 1 { return 3 }
237
238 let btab: *i64 = sys_mmap(SB_MAX_FNS * 32 + 64) as *i64
239 let dtab: *i64 = sys_mmap(SB_MAX_FNS * 32 + 64) as *i64
240 let bn: i64 = sb_scan(base, base_n, btab)
241 let dn: i64 = sb_scan(donor, donor_n, dtab)
242
243 let envp: *i64 = sys_mmap(8 * 2) as *i64
244 envp[0] = "PATH=/usr/bin:/bin\x00" as *u8 as i64
245 envp[1] = 0
246 let devnull: i64 = sys_openat_wr("/dev/null\x00" as *u8, 0x1a4)
247 let logfd: i64 = sys_openat_append("knowledge/status/cc_splice_bisect.log\x00" as *u8, 0x1a4)
248
249 sb_puts2(logfd, "SPLICE-BISECT epoch=\x00" as *u8)
250 sb_putn2(logfd, sys_now_realtime_sec())
251 sb_puts2(logfd, " base_fns=\x00" as *u8)
252 sb_putn2(logfd, bn)
253 sb_puts2(logfd, " donor_fns=\x00" as *u8)
254 sb_putn2(logfd, dn)
255 sb_puts2(logfd, "\n\x00" as *u8)
256
257 var differing: i64 = 0
258 var breakers: i64 = 0
259 var bi: i64 = 0
260 while bi < bn {
261 let b_no: i64 = btab[bi * 4]
262 let b_nl: i64 = btab[bi * 4 + 1]
263 let b_bs: i64 = btab[bi * 4 + 2]
264 let b_be: i64 = btab[bi * 4 + 3]
265 // find donor fn with same name
266 var di: i64 = 0
267 var found: i64 = 0 - 1
268 while di < dn {
269 if sb_names_eq(base, b_no, b_nl, donor, dtab[di * 4], dtab[di * 4 + 1]) == 1 {
270 found = di
271 di = dn
272 }
273 di = di + 1
274 }
275 if found >= 0 {
276 let d_bs: i64 = dtab[found * 4 + 2]
277 let d_be: i64 = dtab[found * 4 + 3]
278 if sb_bodies_eq(base, b_bs, b_be, donor, d_bs, d_be) == 0 {
279 differing = differing + 1
280 sb_write_franken(base, base_n, b_bs, b_be, donor, d_bs, d_be)
281 let rc: i64 = sb_try_franken(probe, envp, devnull)
282 sb_puts2(logfd, "SPLICE-BISECT fn=\x00" as *u8)
283 sb_putsn2(logfd, base + b_no, b_nl)
284 sb_puts2(logfd, " probe_st=\x00" as *u8)
285 sb_putn2(logfd, rc)
286 if rc == 0 { sb_puts2(logfd, " verdict=BEHAVES\n\x00" as *u8) }
287 if rc != 0 {
288 sb_puts2(logfd, " verdict=BREAKER\n\x00" as *u8)
289 breakers = breakers + 1
290 }
291 }
292 }
293 bi = bi + 1
294 }
295
296 sb_puts2(logfd, "SPLICE-BISECT differing=\x00" as *u8)
297 sb_putn2(logfd, differing)
298 sb_puts2(logfd, " breakers=\x00" as *u8)
299 sb_putn2(logfd, breakers)
300 if breakers == 0 { sb_puts2(logfd, " verdict=CLEAN\n\x00" as *u8) }
301 if breakers != 0 { sb_puts2(logfd, " verdict=LOCALIZED\n\x00" as *u8) }
302 if logfd > 0 { sys_close(logfd) }
303 if breakers == 0 { return 0 }
304 return 1
305}