code wiki / (root) / nx_langcensus.nx

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}