nx_langcensus.nx source
↩ module page · 554 lines · 26963 B
1// nx_langcensus.nx -- THE WHOLE-POPULATION COMPILER CENSUS: compile EVERY top-level program in the tree with
2// a GIVEN compiler, classify each outcome by REASON, and (against a baseline census) name every program whose
3// class changed. This is the instrument a language or codegen change is measured by.
4//
5// WHY THIS EXISTS, MEASURED 2026-09-03. The estate's incumbent compiler ruler, nx_cc_equiv_gate, runs
6// EQ_ROWS = 10 corpus rows. Ten rows is a SAMPLE, and a sample can see that a mechanism works; it can never
7// see that a CLASS of program stopped compiling. On the day LN36/LN39/LN40 landed, a first cut of the integer
8// literal bound refused every decimal above i64 max. Every hand-written fixture was GREEN and the 10-row
9// equivalence gate was GREEN, while 286 real estate programs had stopped compiling -- the estate writes u64
10// BIT PATTERNS in decimal (FNV offset bases, all-ones masks, hash multipliers), so the correct bound is
11// 2^64-1. Only a census over all 13,790 programs could see that class, and it existed only as a shell loop.
12// ★★★★★★A RULER THAT SAMPLES CAN PROVE A MECHANISM AND CAN NEVER PROVE A POPULATION -- AND A COMPILER CHANGE
13// IS A CLAIM ABOUT THE POPULATION.
14//
15// THREE OUTCOMES, NEVER TWO. A compiler that LOOPS on an input is not a compiler that refused it: before the
16// LN40 packer fix a 20-digit fraction literal spun forever, and a census without a per-program deadline hangs
17// at that file and reports nothing about the 9,000 programs after it. HANG is its own class here, and it is
18// counted, named, and treated as a regression. A harness failure (fork/pipe/exec) is a FOURTH class that
19// asserts nothing about the subject -- ★AN AXIS THAT CANNOT SEE MUST ABSTAIN, NEVER ACQUIT.
20//
21// THE REASON TRAVELS WITH THE COUNT. Every refusal row carries the compiler's own first diagnostic line with
22// the file:line prefix stripped, so rows group into causes and a 773-refusal census reads as a dozen classes
23// rather than a number. The delta against a baseline names each program, because ★A COUNT WITHOUT A WORKLIST
24// IS NOT ACTIONABLE.
25//
26// NO SILENT CAP. If the file table or any buffer fills, coverage_complete goes to 0 and the organ REFUSES
27// (exit LC_EXIT_UNPROVEN) rather than publishing a prefix as a population.
28//
29// Usage: nx_langcensus <cc.elf> <out-path> [timeout_ms] [baseline-census-path]
30// Run with CWD = nxc2/ (or any root holding runtime/). Writes one row per program to <out-path>:
31// OK <path> | ERR <path> :: <reason> | HANG <path> | HARN <path> rc=<n>
32// Exit: 0 NO-REGRESSION - 1 REGRESSION (new refusals or hangs, each named) - 2 usage - 3 UNPROVEN.
33// With no baseline the exit is 0 whenever the census completed: a first census has nothing to regress from,
34// and saying otherwise would make the instrument's first run a permanent RED.
35//
36// RESOURCE ENVELOPE. One compiler fork per program, strictly sequential, each bounded by timeout_ms. Buffers
37// (capture, dirent, path table, reason table) are allocated ONCE before the walk and reused -- ★NEVER
38// ALLOCATE IN A HOT LOOP: a per-row capture buffer over 13,790 rows is gigabytes of address space for
39// nothing. The compiler's stdout is discarded to the capture buffer and never to disk.
40//
41// license_tier: ORIGINAL No hw writes (Rule 26).
42
43import "nx_syscalls.nx"
44import "nx_tool_run.nx"
45
46// ---- bounds, every one derived and named (Rule 11) -------------------------
47// Measured 2026-09-03: the tree holds 13,790 top-level programs across runtime/ and runtime/_hdl_build/.
48// The table is sized well above that so growth does not silently truncate; if it ever fills, the organ
49// refuses rather than reporting a prefix.
50const LC_MAX_PROGRAMS: i64 = 32768
51// Longest path observed is well under 256; PATH_MAX is the ceiling the kernel itself enforces.
52const LC_PATH_MAX: i64 = 4096
53// A getdents64 buffer of 64 KiB reads a large directory in a handful of calls; the loop runs to exhaustion
54// regardless, so this is a syscall-count choice and never a coverage one.
55const LC_DIRBUF: i64 = 65536
56// THE CAPTURE HOLDS THE COMPILER'S ASSEMBLY, NOT JUST ITS DIAGNOSTIC -- measured the hard way 2026-09-03.
57// tr_run_capture_to merges stdout and stderr, and nx_compile_x86 writes the whole .s to STDOUT: its own
58// assembly is 4,178,018 B. With a 256 KiB capture every program whose asm exceeded it came back non-zero
59// with an EMPTY reason, and the census read 3,523 refusals where an independent instrument read 773 -- the
60// 2,866 difference was exactly the empty-reason class. ★★★★★★TWO INSTRUMENTS THAT DISAGREE LOCATE A DEFECT
61// NEITHER COULD FIND ALONE, AND THE ONE THAT LOOKED CONFIDENT WAS THE BROKEN ONE.
62// 16 MiB is ~4x the largest assembly the tree produces, mmap'd ONCE for the whole run (pages fault in on
63// demand, so the headroom costs address space and not resident memory).
64const LC_CAPBUF: i64 = 16777216
65// A capture that reached its ceiling has an UNKNOWN tail, so the run cannot be judged: it is neither a
66// refusal nor a pass. ★AN AXIS THAT CANNOT SEE MUST ABSTAIN, NEVER ACQUIT -- and never convict either.
67const LC_CAP_NEARFULL: i64 = 16773120
68// A grouped reason is a sentence, not a transcript.
69const LC_REASON_MAX: i64 = 120
70// Distinct reasons observed in a full census: a dozen. 512 is far above that and the overflow is announced.
71const LC_MAX_REASONS: i64 = 512
72// Default per-program deadline. The slowest single program in the tree compiles in about 12 s on a quiet
73// box; 60 s leaves a wide margin while still turning an infinite loop into one row instead of a dead census.
74const LC_DEFAULT_TIMEOUT_MS: i64 = 60000
75// Output row buffer: a path plus a reason plus the row tag. Rows are written AS THEY ARE DECIDED, never
76// buffered to the end -- ★A LONG JOB THAT BUFFERS ITS OUTPUT HAS NO PARTIAL RESULTS: an interruption costs
77// everything rather than the remainder, and a 13,790-program census is exactly the run somebody kills.
78const LC_ROWBUF: i64 = 8192
79const LC_MODE_0644: i64 = 0x1a4
80// Progress heartbeat: a line every N programs so a long run is visibly alive and its rate is readable.
81// ★A RUN THAT PRINTS NOTHING FOR AN HOUR IS INDISTINGUISHABLE FROM A HUNG ONE.
82const LC_HEARTBEAT_EVERY: i64 = 250
83
84const LC_EXIT_OK: i64 = 0
85const LC_EXIT_REGRESSION: i64 = 1
86const LC_EXIT_USAGE: i64 = 2
87const LC_EXIT_UNPROVEN: i64 = 3
88
89// classes
90const LC_OK: i64 = 0
91const LC_ERR: i64 = 1
92const LC_HANG: i64 = 2
93const LC_HARN: i64 = 3
94
95// ---- small string helpers (no allocation) ----------------------------------
96func lc_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
97func lc_eq_n(a: *u8, b: *u8, n: i64) -> i64 {
98 var i: i64 = 0
99 while i < n { if a[i] != b[i] { return 0 } i = i + 1 }
100 return 1
101}
102func lc_ends_with(s: *u8, sfx: *u8) -> i64 {
103 let n: i64 = lc_len(s)
104 let m: i64 = lc_len(sfx)
105 if m > n { return 0 }
106 return lc_eq_n(((s as i64) + n - m) as *u8, sfx, m)
107}
108func lc_cat(dst: *u8, at: i64, s: *u8, cap: i64) -> i64 {
109 var i: i64 = 0
110 var o: i64 = at
111 while s[i] != (0 as u8) {
112 if o >= cap - 1 { return o }
113 dst[o] = s[i]
114 o = o + 1
115 i = i + 1
116 }
117 dst[o] = 0 as u8
118 return o
119}
120func lc_catn(dst: *u8, at: i64, v: i64, cap: i64) -> i64 {
121 if v == 0 {
122 if at < cap - 1 { dst[at] = 48 as u8; dst[at + 1] = 0 as u8; return at + 1 }
123 return at
124 }
125 var d: i64 = v
126 var tmp: *u8 = sys_mmap(32)
127 var k: i64 = 0
128 while d > 0 { tmp[k] = ((d % 10) + 48) as u8; d = d / 10; k = k + 1 }
129 var o: i64 = at
130 while k > 0 {
131 k = k - 1
132 if o < cap - 1 { dst[o] = tmp[k]; o = o + 1 }
133 }
134 dst[o] = 0 as u8
135 return o
136}
137func lc_puts(s: *u8) -> i64 { sys_write(1, s, lc_len(s)) return 0 }
138func lc_putn(v: i64) -> i64 {
139 let b: *u8 = sys_mmap(32)
140 let n: i64 = lc_catn(b, 0, v, 32)
141 sys_write(1, b, n)
142 return 0
143}
144
145// ---- the program predicate -------------------------------------------------
146// A TOP-LEVEL PROGRAM is a .nx file with a line that starts `func main`. Anything else is a library and has
147// no binary by design -- ★BEFORE REPORTING SOMETHING MISSING, CHECK WHETHER IT WAS EVER SUPPOSED TO EXIST.
148func lc_has_main(path: *u8) -> i64 {
149 let lp: *i64 = sys_mmap(8) as *i64
150 lp[0] = 0
151 let buf: *u8 = sys_read_file(path, lp)
152 if (buf as i64) == 0 { return 0 }
153 let n: i64 = lp[0]
154 let pat: *u8 = "func main" as *u8
155 let m: i64 = lc_len(pat)
156 var i: i64 = 0
157 var atbol: i64 = 1
158 while i + m <= n {
159 if atbol == 1 {
160 if lc_eq_n(((buf as i64) + i) as *u8, pat, m) == 1 { return 1 }
161 }
162 if buf[i] == (10 as u8) { atbol = 1 } else { atbol = 0 }
163 i = i + 1
164 }
165 return 0
166}
167
168// ---- the reason extractor --------------------------------------------------
169// Take the compiler's FIRST diagnostic line and strip the `error at <file>:<line>: ` prefix so that two
170// programs failing the same way group into ONE cause. A line with no recognised marker yields an empty
171// reason, which is itself a class the histogram shows rather than hiding.
172func lc_marker_at(buf: *u8, i: i64, n: i64) -> i64 {
173 let a: *u8 = "error at " as *u8
174 let b: *u8 = "this integer literal" as *u8
175 let c: *u8 = "the exponent of this number" as *u8
176 let d: *u8 = "a control byte" as *u8
177 let e: *u8 = "the string literal that opens" as *u8
178 // The OLDER diagnostic path: parse_die and the driver's own log lines carry a `<organ>: ` prefix and no
179 // file:line, so a marker list keyed only on the teaching voice reads them as "no diagnostic at all".
180 // Measured 2026-09-03: 90 refusals grouped into one blank class purely because of this omission.
181 // ★★★★★A MARKER LIST KEYED ON ONE VOCABULARY CANNOT SEE INSTANCES WRITTEN IN ANOTHER, AND THE MISS READS
182 // AS "THIS DOES NOT EXIST" RATHER THAN "MY DEFINITION IS TOO NARROW".
183 let f2: *u8 = "nx_parse: " as *u8
184 let g2: *u8 = "nx_compile_x86: " as *u8
185 if i + lc_len(f2) <= n { if lc_eq_n(((buf as i64) + i) as *u8, f2, lc_len(f2)) == 1 { return 1 } }
186 if i + lc_len(g2) <= n { if lc_eq_n(((buf as i64) + i) as *u8, g2, lc_len(g2)) == 1 { return 1 } }
187 if i + lc_len(a) <= n { if lc_eq_n(((buf as i64) + i) as *u8, a, lc_len(a)) == 1 { return 1 } }
188 if i + lc_len(b) <= n { if lc_eq_n(((buf as i64) + i) as *u8, b, lc_len(b)) == 1 { return 1 } }
189 if i + lc_len(c) <= n { if lc_eq_n(((buf as i64) + i) as *u8, c, lc_len(c)) == 1 { return 1 } }
190 if i + lc_len(d) <= n { if lc_eq_n(((buf as i64) + i) as *u8, d, lc_len(d)) == 1 { return 1 } }
191 if i + lc_len(e) <= n { if lc_eq_n(((buf as i64) + i) as *u8, e, lc_len(e)) == 1 { return 1 } }
192 return 0
193}
194// Writes a NUL-terminated reason into out (cap LC_REASON_MAX). Returns 1 when a marker was found.
195func lc_reason(buf: *u8, n: i64, out: *u8) -> i64 {
196 out[0] = 0 as u8
197 var i: i64 = 0
198 var found: i64 = 0 - 1
199 while i < n {
200 if lc_marker_at(buf, i, n) == 1 { found = i; i = n } else { i = i + 1 }
201 }
202 if found < 0 { return 0 }
203 // skip `error at <path>:<line>: ` when that is the marker, so the reason groups across files
204 var s: i64 = found
205 let a: *u8 = "error at " as *u8
206 let al: i64 = lc_len(a)
207 if lc_eq_n(((buf as i64) + found) as *u8, a, al) == 1 {
208 var j: i64 = found + al
209 var colons: i64 = 0
210 while j < n {
211 if buf[j] == (10 as u8) { j = n } else {
212 if buf[j] == (58 as u8) {
213 colons = colons + 1
214 if colons == 2 {
215 s = j + 1
216 if s < n { if buf[s] == (32 as u8) { s = s + 1 } }
217 j = n
218 } else { j = j + 1 }
219 } else { j = j + 1 }
220 }
221 }
222 }
223 var o: i64 = 0
224 var k: i64 = s
225 while k < n {
226 if buf[k] == (10 as u8) { k = n } else {
227 if o < LC_REASON_MAX - 1 { out[o] = buf[k]; o = o + 1 }
228 k = k + 1
229 }
230 }
231 out[o] = 0 as u8
232 return 1
233}
234
235// ---- the walk ---------------------------------------------------------------
236// ⚠ONE getdents64 CALL IS NOT A DIRECTORY LISTING -- loop until it returns <= 0.
237// Appends every `<dir>/<name>.nx` into tab. Sets full[0]=1 if the table filled (never silent).
238func lc_walk(dir: *u8, tab: *i64, cap: i64, n_in: i64, full: *i64) -> i64 {
239 var n: i64 = n_in
240 let fd: i64 = sys_openat_rd(dir)
241 if fd < 0 { return n }
242 let dbuf: *u8 = sys_mmap(LC_DIRBUF)
243 var go: i64 = 1
244 while go == 1 {
245 let nr: i64 = sys_getdents64(fd, dbuf, LC_DIRBUF)
246 if nr <= 0 { go = 0 } else {
247 var off: i64 = 0
248 while off < nr {
249 let rec: *u8 = ((dbuf as i64) + off) as *u8
250 let nm: *u8 = dirent_name(rec)
251 if lc_ends_with(nm, ".nx" as *u8) == 1 {
252 if n >= cap { full[0] = 1 } else {
253 let p: *u8 = sys_mmap(LC_PATH_MAX)
254 var o: i64 = lc_cat(p, 0, dir, LC_PATH_MAX)
255 o = lc_cat(p, o, "/" as *u8, LC_PATH_MAX)
256 o = lc_cat(p, o, nm, LC_PATH_MAX)
257 tab[n] = p as i64
258 n = n + 1
259 }
260 }
261 let rl: i64 = dirent_reclen(rec)
262 if rl <= 0 { off = nr } else { off = off + rl }
263 }
264 }
265 }
266 sys_close(fd)
267 return n
268}
269
270// ---- baseline join ----------------------------------------------------------
271// 1 iff the baseline census records `<tag> <path>` (tag OK / ERR / HANG / HARN) for this path. The baseline
272// is the writer's OWN artifact from a previous run, which is why the join is a substring match on a row
273// shape this organ controls end to end.
274func lc_base_class(base: *u8, blen: i64, path: *u8) -> i64 {
275 if blen <= 0 { return 0 - 1 }
276 let pl: i64 = lc_len(path)
277 var i: i64 = 0
278 var atbol: i64 = 1
279 while i < blen {
280 if atbol == 1 {
281 var tag: i64 = 0 - 1
282 var off: i64 = 0
283 if i + 3 <= blen { if lc_eq_n(((base as i64) + i) as *u8, "OK " as *u8, 3) == 1 { tag = LC_OK; off = 3 } }
284 if i + 4 <= blen { if lc_eq_n(((base as i64) + i) as *u8, "ERR " as *u8, 4) == 1 { tag = LC_ERR; off = 4 } }
285 if i + 5 <= blen { if lc_eq_n(((base as i64) + i) as *u8, "HANG " as *u8, 5) == 1 { tag = LC_HANG; off = 5 } }
286 if i + 5 <= blen { if lc_eq_n(((base as i64) + i) as *u8, "HARN " as *u8, 5) == 1 { tag = LC_HARN; off = 5 } }
287 if tag >= 0 {
288 if i + off + pl <= blen {
289 if lc_eq_n(((base as i64) + i + off) as *u8, path, pl) == 1 {
290 let after: i64 = i + off + pl
291 var ok: i64 = 0
292 if after >= blen { ok = 1 }
293 if ok == 0 { if base[after] == (10 as u8) { ok = 1 } }
294 if ok == 0 { if base[after] == (32 as u8) { ok = 1 } }
295 if ok == 1 { return tag }
296 }
297 }
298 }
299 }
300 if base[i] == (10 as u8) { atbol = 1 } else { atbol = 0 }
301 i = i + 1
302 }
303 return 0 - 1
304}
305
306func lc_class_name(c: i64) -> *u8 {
307 if c == LC_OK { return "OK" as *u8 }
308 if c == LC_ERR { return "ERR" as *u8 }
309 if c == LC_HANG { return "HANG" as *u8 }
310 if c == LC_HARN { return "HARN" as *u8 }
311 return "ABSENT" as *u8
312}
313
314func main(argc: i64, argv: *i64) -> i64 {
315 if argc < 3 {
316 lc_puts("usage: nx_langcensus <cc.elf> <out-path> [timeout_ms] [baseline-census-path]\n" as *u8)
317 lc_puts(" Compiles EVERY top-level program under runtime/ with <cc.elf> and classifies each\n" as *u8)
318 lc_puts(" outcome: OK, ERR (with the grouped reason), HANG (deadline), HARN (harness, asserts\n" as *u8)
319 lc_puts(" nothing about the subject). With a baseline it names every program whose class moved.\n" as *u8)
320 lc_puts(" This organ REFUSES to guess a compiler: a default path finds a stale binary and returns\n" as *u8)
321 lc_puts(" a fully formed verdict about the wrong artifact.\n" as *u8)
322 sys_exit(LC_EXIT_USAGE)
323 }
324 let cc: *u8 = argv[1] as *u8
325 let outp: *u8 = argv[2] as *u8
326 var tmo: i64 = LC_DEFAULT_TIMEOUT_MS
327 if argc >= 4 {
328 var v: i64 = 0
329 let s: *u8 = argv[3] as *u8
330 var i: i64 = 0
331 var okd: i64 = 1
332 while s[i] != (0 as u8) {
333 if s[i] < (48 as u8) { okd = 0 }
334 if s[i] > (57 as u8) { okd = 0 }
335 if okd == 1 { v = v * 10 + ((s[i] as i64) - 48) }
336 i = i + 1
337 }
338 if okd == 1 { if v > 0 { tmo = v } }
339 }
340 // VERIFY THE SUBJECT EXISTS BEFORE MEASURING: a census against an absent compiler would report every
341 // program as a harness failure and read like a catastrophic regression.
342 let ccfd: i64 = sys_openat_rd(cc)
343 if ccfd < 0 {
344 lc_puts("LANGCENSUS verdict=UNPROVEN -- cannot open the compiler: " as *u8)
345 lc_puts(cc)
346 lc_puts("\n" as *u8)
347 sys_exit(LC_EXIT_UNPROVEN)
348 }
349 sys_close(ccfd)
350
351 var base: *u8 = 0 as *u8
352 var blen: i64 = 0
353 if argc >= 5 {
354 let blp: *i64 = sys_mmap(8) as *i64
355 blp[0] = 0
356 base = sys_read_file(argv[4] as *u8, blp)
357 if (base as i64) != 0 { blen = blp[0] }
358 if blen <= 0 {
359 lc_puts("LANGCENSUS verdict=UNPROVEN -- a baseline was named but could not be read: " as *u8)
360 lc_puts(argv[4] as *u8)
361 lc_puts("\n A census that silently ignores its baseline reports NO REGRESSION for every change.\n" as *u8)
362 sys_exit(LC_EXIT_UNPROVEN)
363 }
364 }
365
366 let full: *i64 = sys_mmap(8) as *i64
367 full[0] = 0
368 let tab: *i64 = sys_mmap(LC_MAX_PROGRAMS * 8) as *i64
369 var nf: i64 = 0
370 nf = lc_walk("runtime" as *u8, tab, LC_MAX_PROGRAMS, nf, full)
371 nf = lc_walk("runtime/_hdl_build" as *u8, tab, LC_MAX_PROGRAMS, nf, full)
372 if full[0] == 1 {
373 lc_puts("LANGCENSUS verdict=UNPROVEN -- the program table filled at " as *u8)
374 lc_putn(LC_MAX_PROGRAMS)
375 lc_puts(" entries; a prefix of a population is not a population.\n" as *u8)
376 sys_exit(LC_EXIT_UNPROVEN)
377 }
378
379 // buffers allocated ONCE, reused for every row
380 let cap: *u8 = sys_mmap(LC_CAPBUF)
381 let clen: *i64 = sys_mmap(8) as *i64
382 let reason: *u8 = sys_mmap(LC_REASON_MAX)
383 let row: *u8 = sys_mmap(LC_ROWBUF)
384 // The rows file is opened ONCE, truncating, and every row is written the moment it is decided.
385 let ofd: i64 = sys_openat_wr(outp, LC_MODE_0644)
386 if ofd < 0 {
387 lc_puts("LANGCENSUS verdict=UNPROVEN -- cannot open the rows file for writing: " as *u8)
388 lc_puts(outp)
389 lc_puts("\n" as *u8)
390 sys_exit(LC_EXIT_UNPROVEN)
391 }
392 var olen: i64 = 0
393 var wfail: i64 = 0
394 let rtab: *i64 = sys_mmap(LC_MAX_REASONS * 8) as *i64
395 let rcnt: *i64 = sys_mmap(LC_MAX_REASONS * 8) as *i64
396 var nr: i64 = 0
397 var rfull: i64 = 0
398 let av: *i64 = sys_mmap(32) as *i64
399
400 var n_ok: i64 = 0
401 var n_err: i64 = 0
402 var n_hang: i64 = 0
403 var n_harn: i64 = 0
404 var n_prog: i64 = 0
405 var n_capfull: i64 = 0
406 var n_noreason: i64 = 0
407 var new_err: i64 = 0
408 var new_hang: i64 = 0
409 var fixed: i64 = 0
410
411 lc_puts("LANGCENSUS cc=" as *u8); lc_puts(cc)
412 lc_puts(" files=" as *u8); lc_putn(nf)
413 lc_puts(" timeout_ms=" as *u8); lc_putn(tmo)
414 lc_puts("\n" as *u8)
415
416 var i: i64 = 0
417 while i < nf {
418 let p: *u8 = tab[i] as *u8
419 if lc_has_main(p) == 1 {
420 n_prog = n_prog + 1
421 av[0] = cc as i64
422 av[1] = p as i64
423 av[2] = 0
424 clen[0] = 0
425 let rc: i64 = tr_run_capture_to(cc, av, cap, LC_CAPBUF, clen, tmo)
426 var cls: i64 = LC_HARN
427 if rc == 0 { cls = LC_OK }
428 if rc > 0 { cls = LC_ERR }
429 if rc == TR_ERR_TIMEOUT { cls = LC_HANG }
430 // A filled capture means the tail is unknown, so a non-zero rc cannot be attributed to the
431 // subject: abstain rather than publish it as a refusal.
432 if clen[0] >= LC_CAP_NEARFULL { if cls == LC_ERR { cls = LC_HARN; n_capfull = n_capfull + 1 } }
433 if cls == LC_OK { n_ok = n_ok + 1 }
434 if cls == LC_HANG { n_hang = n_hang + 1 }
435 if cls == LC_HARN { n_harn = n_harn + 1 }
436 var ro: i64 = 0
437 if cls == LC_ERR {
438 n_err = n_err + 1
439 if lc_reason(cap, clen[0], reason) == 0 { n_noreason = n_noreason + 1; lc_cat(reason, 0, "(no recognised diagnostic marker in the capture)" as *u8, LC_REASON_MAX) }
440 // histogram: group identical reasons
441 var k: i64 = 0
442 var hit: i64 = 0 - 1
443 while k < nr {
444 let ex: *u8 = rtab[k] as *u8
445 if lc_len(ex) == lc_len(reason) {
446 if lc_eq_n(ex, reason, lc_len(reason)) == 1 { hit = k; k = nr }
447 }
448 if hit < 0 { k = k + 1 }
449 }
450 if hit >= 0 { rcnt[hit] = rcnt[hit] + 1 } else {
451 if nr >= LC_MAX_REASONS { rfull = 1 } else {
452 let cpy: *u8 = sys_mmap(LC_REASON_MAX)
453 lc_cat(cpy, 0, reason, LC_REASON_MAX)
454 rtab[nr] = cpy as i64
455 rcnt[nr] = 1
456 nr = nr + 1
457 }
458 }
459 ro = lc_cat(row, 0, "ERR " as *u8, LC_ROWBUF)
460 ro = lc_cat(row, ro, p, LC_ROWBUF)
461 ro = lc_cat(row, ro, " :: " as *u8, LC_ROWBUF)
462 ro = lc_cat(row, ro, reason, LC_ROWBUF)
463 }
464 if cls == LC_OK { ro = lc_cat(row, 0, "OK " as *u8, LC_ROWBUF); ro = lc_cat(row, ro, p, LC_ROWBUF) }
465 if cls == LC_HANG { ro = lc_cat(row, 0, "HANG " as *u8, LC_ROWBUF); ro = lc_cat(row, ro, p, LC_ROWBUF) }
466 if cls == LC_HARN {
467 ro = lc_cat(row, 0, "HARN " as *u8, LC_ROWBUF)
468 ro = lc_cat(row, ro, p, LC_ROWBUF)
469 ro = lc_cat(row, ro, " rc=" as *u8, LC_ROWBUF)
470 ro = lc_catn(row, ro, 0 - rc, LC_ROWBUF)
471 }
472 ro = lc_cat(row, ro, "\n" as *u8, LC_ROWBUF)
473 let wn: i64 = sys_write(ofd, row, ro)
474 if wn != ro { wfail = wfail + 1 } else { olen = olen + ro }
475 if n_prog % LC_HEARTBEAT_EVERY == 0 {
476 lc_puts("PROGRESS programs=" as *u8); lc_putn(n_prog)
477 lc_puts(" ok=" as *u8); lc_putn(n_ok)
478 lc_puts(" err=" as *u8); lc_putn(n_err)
479 lc_puts(" hang=" as *u8); lc_putn(n_hang)
480 lc_puts("\n" as *u8)
481 }
482 // delta against the baseline: name every program whose class MOVED
483 if blen > 0 {
484 let was: i64 = lc_base_class(base, blen, p)
485 if was >= 0 {
486 if was != cls {
487 if cls == LC_ERR { new_err = new_err + 1 }
488 if cls == LC_HANG { new_hang = new_hang + 1 }
489 if was == LC_ERR { if cls == LC_OK { fixed = fixed + 1 } }
490 if was == LC_HANG { if cls == LC_OK { fixed = fixed + 1 } }
491 lc_puts("DELTA " as *u8); lc_puts(lc_class_name(was))
492 lc_puts(" -> " as *u8); lc_puts(lc_class_name(cls))
493 lc_puts(" " as *u8); lc_puts(p)
494 if cls == LC_ERR { lc_puts(" :: " as *u8); lc_puts(reason) }
495 lc_puts("\n" as *u8)
496 }
497 }
498 }
499 }
500 i = i + 1
501 }
502
503 sys_close(ofd)
504 var wrote: i64 = olen
505 if wfail > 0 { wrote = 0 - 1 }
506
507 // THE PARTITION IS A CLAIM: PRINT IT AND CHECK IT SUMS.
508 let sum: i64 = n_ok + n_err + n_hang + n_harn
509 lc_puts("LANGCENSUS programs=" as *u8); lc_putn(n_prog)
510 lc_puts(" ok=" as *u8); lc_putn(n_ok)
511 lc_puts(" err=" as *u8); lc_putn(n_err)
512 lc_puts(" hang=" as *u8); lc_putn(n_hang)
513 lc_puts(" harness=" as *u8); lc_putn(n_harn)
514 lc_puts(" sum=" as *u8); lc_putn(sum)
515 if sum == n_prog { lc_puts(" RECONCILES" as *u8) } else { lc_puts(" LEAK -- the partition does not sum" as *u8) }
516 lc_puts(" rows_written=" as *u8); lc_putn(wrote)
517 lc_puts("\n" as *u8)
518
519 // the reason histogram: causes, not a number
520 var h: i64 = 0
521 while h < nr {
522 lc_puts("REASON n=" as *u8); lc_putn(rcnt[h])
523 lc_puts(" " as *u8); lc_puts(rtab[h] as *u8)
524 lc_puts("\n" as *u8)
525 h = h + 1
526 }
527 if rfull == 1 { lc_puts("REASON-TABLE FILLED -- the histogram above is a PREFIX of its own count\n" as *u8) }
528 if n_capfull > 0 { lc_puts("CAPTURE-CEILING-REACHED n=" as *u8); lc_putn(n_capfull); lc_puts(" -- those runs are HARNESS (unjudgeable), never refusals
529" as *u8) }
530 if n_noreason > 0 { lc_puts("REFUSALS WITH NO RECOGNISED MARKER n=" as *u8); lc_putn(n_noreason); lc_puts(" -- widen lc_marker_at or read them by hand; they are NOT one cause
531" as *u8) }
532 if wfail > 0 { lc_puts("ROW WRITES FAILED n=" as *u8); lc_putn(wfail); lc_puts(" -- the written census is PARTIAL, so it cannot be a baseline
533" as *u8) }
534
535 var verdict: i64 = LC_EXIT_OK
536 if sum != n_prog { verdict = LC_EXIT_UNPROVEN }
537 if wfail > 0 { verdict = LC_EXIT_UNPROVEN }
538 if wrote < 0 { verdict = LC_EXIT_UNPROVEN }
539 if blen > 0 {
540 lc_puts("LANGCENSUS delta new_err=" as *u8); lc_putn(new_err)
541 lc_puts(" new_hang=" as *u8); lc_putn(new_hang)
542 lc_puts(" fixed=" as *u8); lc_putn(fixed)
543 lc_puts("\n" as *u8)
544 if verdict == LC_EXIT_OK {
545 if new_err + new_hang > 0 { verdict = LC_EXIT_REGRESSION }
546 }
547 }
548 if blen <= 0 { lc_puts("LANGCENSUS no baseline given -- this run is a BASELINE, not a judgement\n" as *u8) }
549 if verdict == LC_EXIT_OK { lc_puts("LANGCENSUS verdict=NO-REGRESSION\n" as *u8) }
550 if verdict == LC_EXIT_REGRESSION { lc_puts("LANGCENSUS verdict=REGRESSION -- every offender is named on a DELTA line above\n" as *u8) }
551 if verdict == LC_EXIT_UNPROVEN { lc_puts("LANGCENSUS verdict=UNPROVEN -- the census could not be published whole\n" as *u8) }
552 sys_exit(verdict)
553 return verdict
554}