code wiki / _hdl_build / nx_undefscan.nx
nx_undefscan.nx source
↩ module page · 1774 lines · 74070 B
1// nx_undefscan.nx -- THE MISSING-SYMBOL CENSUS: report EVERY function a target calls that nobody in its
2// import closure defines, WITHOUT compiling it.
3//
4// WHY THIS EXISTS. The compiler reports undefined symbols ONE AT A TIME, and only AFTER expand_imports
5// succeeds -- so a single mis-layered import hides an unknown number of missing functions behind it, and
6// the outer explanation (bad import) is always true enough to stop the investigation. Measured
7// 2026-07-31: un-darkening 14 libs exposed SEVEN undefined functions no build could ever have reported --
8// nx_pctl_kill_by_cmdline (2 callers), the whole nx_fft decomposed API (4 consumers), nx_bm25_score
9// (5 callers). Each fix revealed the next, so the count was never knowable in advance. This organ makes
10// it knowable in ONE pass: walk the closure, collect every definition, collect every call site, print
11// the DIFFERENCE AS A LIST rather than as the first error.
12//
13// WHAT IT IS NOT. A textual resolver, not the compiler. Two known ways it can be wrong, stated here so a
14// caller never has to guess:
15// - a call THROUGH A FUNCTION-POINTER VARIABLE looks like an undefined call (false positive)
16// - a symbol the compiler itself provides, if any exist, has no .nx definition (false positive)
17// Both are bounded by the POSITIVE CONTROL: a target that BUILDS must report ZERO. Any residue on a
18// building target IS that false-positive set, and must be fixed here rather than explained away.
19//
20// PARTIAL IS NOT A SMALLER CLEAN (the 07-31 layerscan law, built in as a tooth). If any import in the
21// closure resolves to no file, or the queue overflows, the definition set is INCOMPLETE -- so every
22// undefined it prints may merely be defined in the file it could not read. In that state the verdict is
23// PARTIAL and a ZERO count exits NONZERO, because a clean report from a truncated scan is the exact lie
24// this class of instrument keeps telling.
25//
26// nx_undefscan scan <target> -- target = basename WITHOUT .nx, resolved runtime/ then _hdl_build/
27// EXIT: 0 CLEAN+COMPLETE - 1 UNDEFINED FOUND - 2 usage - 3 target not found - 5 PARTIAL (cannot certify)
28// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0
29import "nx_syscalls.nx"
30
31const US_MAXF: i64 = 512
32const US_NAMELEN: i64 = 64
33const US_SLOTS: i64 = 32768
34const US_PATHBUF: i64 = 1024
35const US_RTDIR: *u8 = "buildroot/runtime" as *u8
36const US_HDDIR: *u8 = "buildroot/runtime/_hdl_build" as *u8
37const US_HUBDIR: *u8 = "buildroot/runtime/hub" as *u8
38const US_WIKIDIR: *u8 = "buildroot/runtime/wiki" as *u8
39
40// RESOLVE AN IMPORT THE WAY nx_cc DOES, not the way I assumed. Measured 2026-07-31: I searched only
41// runtime/ and _hdl_build/ and reported MISSING-IMPORT nx_search_query_parser for a file that lives in
42// runtime/hub/. The compiler resolved it fine -- proven because nx_search_snippet_extract failed with
43// `UNDEFINED label: main` (a no-main library built standalone) rather than an expand_imports error, and
44// expand_imports runs FIRST. An under-searching resolver manufactures both false MISSING-IMPORTs and
45// false NOWHEREs, and a false NOWHERE is the one that gets someone to WRITE CODE THAT ALREADY EXISTS.
46// _retired/ is deliberately excluded: code there is not meant to be importable.
47func us_resolve(path: *u8, nm: *u8) -> i64 {
48 us_path(path, US_RTDIR, nm)
49 if us_exists(path) == 1 { return 1 }
50 us_path(path, US_HDDIR, nm)
51 if us_exists(path) == 1 { return 1 }
52 us_path(path, US_HUBDIR, nm)
53 if us_exists(path) == 1 { return 1 }
54 us_path(path, US_WIKIDIR, nm)
55 if us_exists(path) == 1 { return 1 }
56 return 0
57}
58
59// PER-IMPORTER RESOLUTION -- THE RULE THE COMPILER ACTUALLY USES.
60// us_resolve above takes only a NAME and probes ONE fixed global order, so for a name that exists in more
61// than one probe directory it always returns the same copy no matter who imported it. nx_cc does not work
62// that way: AN IMPORT BINDS THE IMPORTER'S OWN DIRECTORY FIRST.
63// Measured 2026-07-31: nx_m2d_engine lives in _hdl_build/ and is written against
64// _hdl_build/nx_input_abstract.nx (ia_init/2, ia_bind/4). The fixed order handed it
65// runtime/nx_input_abstract.nx (ia_init/1, ia_bind/3) -- a DIFFERENT API wearing the same names -- and the
66// arity pass reported EIGHTEEN mismatches that were not defects. The closure was also one file too big:
67// it had pulled in a file the compiler never binds.
68// THE DAMAGE IS NOT LIMITED TO ARITY. A wrong binding is a wrong DEFINITION SET, so undefined-symbol
69// verdicts go wrong in BOTH directions -- a false CLEAN when the wrong file happens to declare the name,
70// and a false UNDEFINED when it does not. That is why this is fixed at the resolver rather than per-pass.
71// Fallback order is BUILD-TARGET probe rank (_hdl_build first): that is the copy /api/build compiles, so
72// a closure read in that order is the closure the artifact was actually produced from.
73func us_dirp(id: i64) -> *u8 {
74 if id == 0 { return US_HDDIR }
75 if id == 1 { return US_RTDIR }
76 if id == 2 { return US_HUBDIR }
77 if id == 3 { return US_WIKIDIR }
78 return "" as *u8
79}
80
81// Returns the dir id that won, or 0-1 if no probe directory has the file. prefid<0 means "no importer",
82// which is the ROOT target -- it takes probe rank alone.
83func us_resolve_dir(path: *u8, nm: *u8, prefid: i64) -> i64 {
84 if prefid >= 0 {
85 us_path(path, us_dirp(prefid), nm)
86 if us_exists(path) == 1 { return prefid }
87 }
88 var i: i64 = 0
89 while i < 4 {
90 us_path(path, us_dirp(i), nm)
91 if us_exists(path) == 1 { return i }
92 i = i + 1
93 }
94 return 0 - 1
95}
96const US_FNV_OFF: i64 = 1469598103934665603
97const US_FNV_PRIME: i64 = 1099511628211
98const US_EXIT_UNDEF: i64 = 1
99const US_EXIT_USAGE: i64 = 2
100const US_EXIT_NOTFOUND: i64 = 3
101const US_EXIT_PARTIAL: i64 = 5
102const US_EXIT_SELFTEST: i64 = 4
103const US_BICONF: *u8 = "knowledge/undefscan_builtins.conf" as *u8
104const US_UNDEFCAP: i64 = 256
105const US_DIRBUF: i64 = 1048576
106
107// UNDEFINED IN THIS CLOSURE IS NOT THE SAME CLAIM AS DOES NOT EXIST, and the two have OPPOSITE remedies:
108// a symbol defined elsewhere in the corpus is a MISSING IMPORT (one line); a symbol defined nowhere is
109// UNWRITTEN CODE (a module). Measured 2026-07-31: nx_search_neutrality_gate reported 7 undefined names,
110// and I read that as one absent inverted-index module and filed it sev-8. SIX of the seven were already
111// in runtime/nx_search_inverted.nx with ~25 other consumers -- pure wiring -- and only nx_bm25_tf was
112// genuinely absent. A careful reader got it wrong from the bare list, so the LIST IS NOT THE ANSWER.
113// `corpus` mode resolves each undefined name against both source trees and names the defining file.
114
115func us_puts(s: *u8) {
116 var n: i64 = 0
117 while s[n] != (0 as u8) { n = n + 1 }
118 sys_write(1, s, n)
119}
120
121func us_putn(p: *u8, n: i64) { sys_write(1, p, n) }
122
123func us_puti(x: i64) {
124 let buf: *u8 = sys_mmap(64)
125 var v: i64 = x
126 var neg: i64 = 0
127 if v < 0 { neg = 1; v = 0 - v }
128 var i: i64 = 40
129 if v == 0 { i = i - 1; buf[i] = 48 as u8 }
130 while v > 0 {
131 let d: i64 = v - (v / 10) * 10
132 i = i - 1
133 buf[i] = (d + 48) as u8
134 v = v / 10
135 }
136 if neg == 1 { i = i - 1; buf[i] = 45 as u8 }
137 sys_write(1, ((buf as i64) + i) as *u8, 40 - i)
138}
139
140func us_isidst(c: i64) -> i64 {
141 if c == 95 { return 1 }
142 if c >= 97 { if c <= 122 { return 1 } }
143 if c >= 65 { if c <= 90 { return 1 } }
144 return 0
145}
146
147func us_isidc(c: i64) -> i64 {
148 if us_isidst(c) == 1 { return 1 }
149 if c >= 48 { if c <= 57 { return 1 } }
150 return 0
151}
152
153func us_eqlit(p: *u8, L: i64, lit: *u8, litlen: i64) -> i64 {
154 if L != litlen { return 0 }
155 var i: i64 = 0
156 while i < L {
157 if p[i] != lit[i] { return 0 }
158 i = i + 1
159 }
160 return 1
161}
162
163// Idents that can legally sit immediately before ( without being a call. Kept explicit rather than
164// inferred: a keyword silently treated as a call would be reported as a missing function forever.
165func us_iskw(p: *u8, L: i64) -> i64 {
166 if us_eqlit(p, L, "if" as *u8, 2) == 1 { return 1 }
167 if us_eqlit(p, L, "while" as *u8, 5) == 1 { return 1 }
168 if us_eqlit(p, L, "return" as *u8, 6) == 1 { return 1 }
169 if us_eqlit(p, L, "else" as *u8, 4) == 1 { return 1 }
170 if us_eqlit(p, L, "let" as *u8, 3) == 1 { return 1 }
171 if us_eqlit(p, L, "var" as *u8, 3) == 1 { return 1 }
172 if us_eqlit(p, L, "const" as *u8, 5) == 1 { return 1 }
173 if us_eqlit(p, L, "func" as *u8, 4) == 1 { return 1 }
174 if us_eqlit(p, L, "import" as *u8, 6) == 1 { return 1 }
175 if us_eqlit(p, L, "struct" as *u8, 6) == 1 { return 1 }
176 if us_eqlit(p, L, "for" as *u8, 3) == 1 { return 1 }
177 if us_eqlit(p, L, "break" as *u8, 5) == 1 { return 1 }
178 if us_eqlit(p, L, "continue" as *u8, 8) == 1 { return 1 }
179 if us_eqlit(p, L, "as" as *u8, 2) == 1 { return 1 }
180 if us_eqlit(p, L, "static" as *u8, 6) == 1 { return 1 }
181 return 0
182}
183
184// A `__`-prefixed identifier that nothing declares is a COMPILER INTRINSIC by naming convention.
185//
186// WHY A RULE AND NOT A LIST: I appended to undefscan_builtins.conf three separate times -- __syscall,
187// then six bit intrinsics, then seven x86 crypto ones -- and the sweep immediately turned up seven more
188// (atomics, __thread_clone, __i16x16_madd) plus __f32_mul/__f32_add. Each batch was verified the same
189// way and each time the list was still incomplete. The corpus convention is absolute: NO .nx file in
190// 19,981 sources declares a `__` name, so the prefix IS the classifier. A rule nothing has to remember
191// beats a list somebody must maintain.
192//
193// THIS DOES NOT HIDE ANYTHING. Presumed intrinsics are counted and printed as their own number, never
194// folded into `undefined` and never silently dropped. And a `__` name that IS declared somewhere in the
195// closure never reaches here -- it resolves as a normal definition.
196func us_is_dunder(p: *u8) -> i64 {
197 if p[0] != (95 as u8) { return 0 }
198 if p[1] != (95 as u8) { return 0 }
199 return 1
200}
201
202func us_hash(s: *u8, n: i64) -> i64 {
203 var h: i64 = US_FNV_OFF
204 var i: i64 = 0
205 while i < n {
206 h = h ^ (s[i] as i64)
207 h = h * US_FNV_PRIME
208 i = i + 1
209 }
210 if h < 0 { h = 0 - h }
211 if h < 0 { h = 0 }
212 return h
213}
214
215func us_slot(tab: *u8, i: i64) -> *u8 { return ((tab as i64) + i * US_NAMELEN) as *u8 }
216
217// Open-addressed insert. Returns the slot index, or 0-1 if the name does not fit / the table is full.
218func us_put(tab: *u8, s: *u8, n: i64) -> i64 {
219 if n <= 0 { return 0 - 1 }
220 if n >= US_NAMELEN { return 0 - 1 }
221 let h: i64 = us_hash(s, n)
222 var idx: i64 = h - (h / US_SLOTS) * US_SLOTS
223 var probes: i64 = 0
224 while probes < US_SLOTS {
225 let p: *u8 = us_slot(tab, idx)
226 if p[0] == (0 as u8) {
227 var i: i64 = 0
228 while i < n { p[i] = s[i]; i = i + 1 }
229 p[n] = 0 as u8
230 return idx
231 }
232 var eq: i64 = 1
233 if p[n] != (0 as u8) { eq = 0 }
234 var k: i64 = 0
235 while k < n {
236 if p[k] != s[k] { eq = 0; k = n } else { k = k + 1 }
237 }
238 if eq == 1 { return idx }
239 idx = idx + 1
240 if idx >= US_SLOTS { idx = 0 }
241 probes = probes + 1
242 }
243 return 0 - 1
244}
245
246func us_has(tab: *u8, s: *u8, n: i64) -> i64 {
247 if n <= 0 { return 0 }
248 if n >= US_NAMELEN { return 0 }
249 let h: i64 = us_hash(s, n)
250 var idx: i64 = h - (h / US_SLOTS) * US_SLOTS
251 var probes: i64 = 0
252 while probes < US_SLOTS {
253 let p: *u8 = us_slot(tab, idx)
254 if p[0] == (0 as u8) { return 0 }
255 var eq: i64 = 1
256 if p[n] != (0 as u8) { eq = 0 }
257 var k: i64 = 0
258 while k < n {
259 if p[k] != s[k] { eq = 0; k = n } else { k = k + 1 }
260 }
261 if eq == 1 { return 1 }
262 idx = idx + 1
263 if idx >= US_SLOTS { idx = 0 }
264 probes = probes + 1
265 }
266 return 0
267}
268
269// Queue of closure member basenames. Dedupes against 0..cnt-1 using slot cnt as scratch.
270func us_qpush(q: *u8, cnt: i64, src: *u8, n: i64) -> i64 {
271 if n <= 0 { return cnt }
272 if n >= US_NAMELEN { return cnt }
273 if cnt >= US_MAXF { return cnt }
274 let d: *u8 = us_slot(q, cnt)
275 var i: i64 = 0
276 while i < n { d[i] = src[i]; i = i + 1 }
277 d[n] = 0 as u8
278 var k: i64 = 0
279 while k < cnt {
280 if us_eqlit(us_slot(q, k), n, d, n) == 1 {
281 let e: *u8 = us_slot(q, k)
282 if e[n] == (0 as u8) { return cnt }
283 }
284 k = k + 1
285 }
286 return cnt + 1
287}
288
289// out = <dir>/<name>.nx
290func us_path(out: *u8, dir: *u8, name: *u8) {
291 var w: i64 = 0
292 var i: i64 = 0
293 while dir[i] != (0 as u8) { out[w] = dir[i]; w = w + 1; i = i + 1 }
294 out[w] = 47 as u8
295 w = w + 1
296 i = 0
297 while name[i] != (0 as u8) { out[w] = name[i]; w = w + 1; i = i + 1 }
298 out[w] = 46 as u8
299 out[w + 1] = 110 as u8
300 out[w + 2] = 120 as u8
301 out[w + 3] = 0 as u8
302}
303
304func us_exists(path: *u8) -> i64 {
305 let fd: i64 = sys_openat_rd(path)
306 if fd < 0 { return 0 }
307 sys_close(fd)
308 return 1
309}
310
311// Load compiler-provided symbols from the conf. An unreadable/absent conf yields ZERO exclusions, which
312// makes every intrinsic show up as UNDEFINED -- loud and obviously wrong. That direction is deliberate:
313// an empty exclusion set can only add false positives, never hide a real missing function.
314func us_load_builtins(tab: *u8) -> i64 {
315 let lenbox: *i64 = sys_mmap(16) as *i64
316 lenbox[0] = 0
317 let buf: *u8 = sys_read_file(US_BICONF, lenbox)
318 let n: i64 = lenbox[0]
319 if n <= 0 { return 0 }
320 var cnt: i64 = 0
321 var i: i64 = 0
322 while i < n {
323 var e: i64 = i
324 var st: i64 = 0
325 while st == 0 {
326 if e >= n { st = 1 } else {
327 if buf[e] == (10 as u8) { st = 1 } else { e = e + 1 }
328 }
329 }
330 var L: i64 = e - i
331 var trimming: i64 = 1
332 while trimming == 1 {
333 if L <= 0 { trimming = 0 } else {
334 let ch: i64 = buf[i + L - 1] as i64
335 if ch == 13 { L = L - 1 } else {
336 if ch == 32 { L = L - 1 } else {
337 if ch == 9 { L = L - 1 } else { trimming = 0 } } }
338 }
339 }
340 if L > 0 {
341 if buf[i] != (35 as u8) {
342 us_put(tab, ((buf as i64) + i) as *u8, L)
343 cnt = cnt + 1
344 }
345 }
346 i = e + 1
347 }
348 return cnt
349}
350
351// After a `func`, `struct`, or `type` keyword the next identifier is a DECLARATION. All three spell it
352// the same way, so one helper serves all three (rule 15) -- and MISSING any of them is expensive: not
353// handling `type` alone accounted for ~32 phantom undefined identifiers, because nx_tier.nx:62 declares
354// `type nx_int = i64` and thirty-odd sibling aliases that the whole corpus then uses as types.
355// Returns the index just past the declared name, or 0-1 if no identifier follows.
356func us_declare_next(buf: *u8, n: i64, from: i64, defs: *u8) -> i64 {
357 var k: i64 = from
358 var st: i64 = 0
359 while st == 0 {
360 if k >= n { st = 1 } else {
361 if us_isidst(buf[k] as i64) == 1 { st = 1 } else { k = k + 1 }
362 }
363 }
364 if k >= n { return 0 - 1 }
365 var e: i64 = k
366 var s2: i64 = 0
367 while s2 == 0 {
368 if e >= n { s2 = 1 } else {
369 if us_isidc(buf[e] as i64) == 1 { e = e + 1 } else { s2 = 1 }
370 }
371 }
372 us_put(defs, ((buf as i64) + k) as *u8, e - k)
373 return e
374}
375
376func us_isdig(c: i64) -> i64 {
377 if c >= 48 { if c <= 57 { return 1 } }
378 return 0
379}
380
381// THE SCANNER. One left-to-right pass; comments and string literals are skipped as units so a function
382// name mentioned in prose or in a message string never becomes a phantom call.
383func us_scan(buf: *u8, n: i64, defs: *u8, calls: *u8, refs: *u8, q: *u8, qcnt: i64) -> i64 {
384 var qc: i64 = qcnt
385 var i: i64 = 0
386 while i < n {
387 let c: i64 = buf[i] as i64
388 var adv: i64 = 1
389
390 var iscmt: i64 = 0
391 if c == 47 { if i + 1 < n { if buf[i + 1] == (47 as u8) { iscmt = 1 } } }
392 if iscmt == 1 {
393 var j: i64 = i + 2
394 var st: i64 = 0
395 while st == 0 {
396 if j >= n { st = 1 } else {
397 if buf[j] == (10 as u8) { st = 1 } else { j = j + 1 }
398 }
399 }
400 i = j
401 adv = 0
402 }
403
404 if adv == 1 {
405 if c == 34 {
406 var j: i64 = i + 1
407 var st: i64 = 0
408 while st == 0 {
409 if j >= n { st = 1 } else {
410 if buf[j] == (92 as u8) { j = j + 2 } else {
411 if buf[j] == (34 as u8) { j = j + 1; st = 1 } else { j = j + 1 }
412 }
413 }
414 }
415 i = j
416 adv = 0
417 } }
418
419 // A NUMERIC LITERAL is consumed WHOLE. Without this, 0x00004000 scans as the digit 0 followed by
420 // an identifier x00004000 -- ten phantom undefined names in the first real run, every one a hex
421 // constant. A tokenizer that splits a literal invents symbols that were never written.
422 if adv == 1 {
423 if us_isdig(c) == 1 {
424 var j: i64 = i
425 var st: i64 = 0
426 while st == 0 {
427 if j >= n { st = 1 } else {
428 if us_isidc(buf[j] as i64) == 1 { j = j + 1 } else { st = 1 }
429 }
430 }
431 i = j
432 adv = 0
433 } }
434
435 // A PREPROCESSOR DIRECTIVE (@ifdef / @ifndef / @endif). '@' is not an identifier character, so
436 // without this the directive NAME leaks through as a bare reference. The condition after it does
437 // NOT leak -- TARGET_X86_64 is a genuine compiler-provided flag and is declared in the conf.
438 if adv == 1 {
439 if c == 64 {
440 var j: i64 = i + 1
441 var st: i64 = 0
442 while st == 0 {
443 if j >= n { st = 1 } else {
444 if us_isidc(buf[j] as i64) == 1 { j = j + 1 } else { st = 1 }
445 }
446 }
447 i = j
448 adv = 0
449 } }
450
451 if adv == 1 {
452 if us_isidst(c) == 1 {
453 var j: i64 = i
454 var st: i64 = 0
455 while st == 0 {
456 if j >= n { st = 1 } else {
457 if us_isidc(buf[j] as i64) == 1 { j = j + 1 } else { st = 1 }
458 }
459 }
460 let L: i64 = j - i
461 let p: *u8 = ((buf as i64) + i) as *u8
462
463 var done: i64 = 0
464
465 // `let` and `var` introduce a binding whose TYPE ANNOTATION IS OPTIONAL. Classifying a
466 // declaration by a following ':' therefore misses `let _ = expr` entirely, and the name lands
467 // in the reference bucket as a phantom undefined symbol. Measured: nx_search_inverted_test
468 // uses `let _ = ...` and `let _2 = ...` as discard bindings and was reported with two
469 // undefined identifiers, one of which the corpus resolver then "found" in an unrelated file.
470 // A false positive that RESOLVES somewhere is worse than one that does not -- it invites a
471 // wrong import. Declare on the KEYWORD, not on the punctuation that may not be there.
472 var isdecl: i64 = 0
473 if us_eqlit(p, L, "func" as *u8, 4) == 1 { isdecl = 1 }
474 if us_eqlit(p, L, "struct" as *u8, 6) == 1 { isdecl = 1 }
475 if us_eqlit(p, L, "type" as *u8, 4) == 1 { isdecl = 1 }
476 if us_eqlit(p, L, "let" as *u8, 3) == 1 { isdecl = 1 }
477 if us_eqlit(p, L, "var" as *u8, 3) == 1 { isdecl = 1 }
478 if isdecl == 1 {
479 let de: i64 = us_declare_next(buf, n, j, defs)
480 if de >= 0 { i = de; done = 1 }
481 }
482
483 if done == 0 {
484 if us_eqlit(p, L, "import" as *u8, 6) == 1 {
485 var k: i64 = j
486 var qpos: i64 = 0 - 1
487 var s4: i64 = 0
488 while s4 == 0 {
489 if k >= n { s4 = 1 } else {
490 if buf[k] == (34 as u8) { qpos = k; s4 = 1 } else {
491 if buf[k] == (10 as u8) { s4 = 1 } else { k = k + 1 }
492 }
493 }
494 }
495 if qpos >= 0 {
496 var e: i64 = qpos + 1
497 var s5: i64 = 0
498 while s5 == 0 {
499 if e >= n { s5 = 1 } else {
500 if buf[e] == (34 as u8) { s5 = 1 } else { e = e + 1 }
501 }
502 }
503 if e < n {
504 let ip: *u8 = ((buf as i64) + qpos + 1) as *u8
505 var IL: i64 = e - (qpos + 1)
506 if IL > 3 {
507 if ip[IL - 3] == (46 as u8) {
508 if ip[IL - 2] == (110 as u8) {
509 if ip[IL - 1] == (120 as u8) { IL = IL - 3 } } }
510 }
511 qc = us_qpush(q, qc, ip, IL)
512 i = e + 1
513 done = 1
514 }
515 }
516 } }
517
518 if done == 0 {
519 // CLASSIFY BY THE NEXT SIGNIFICANT CHARACTER. An identifier followed by ':' is a
520 // DECLARATION -- parameter, let, var, const, or struct field -- and this language spells
521 // all five that one way, so the rule is exact rather than heuristic. Collecting them all
522 // OVER-approximates the declared set, which is the safe direction: it cannot invent a
523 // false positive from scoping, and a name declared NOWHERE in the closure still stands out.
524 var k: i64 = j
525 var t4: i64 = 0
526 while t4 == 0 {
527 if k >= n { t4 = 1 } else {
528 if buf[k] == (32 as u8) { k = k + 1 } else {
529 if buf[k] == (9 as u8) { k = k + 1 } else { t4 = 1 }
530 }
531 }
532 }
533 var cls: i64 = 0
534 if j < n { if buf[j] == (40 as u8) { cls = 1 } }
535 if cls == 0 { if k < n { if buf[k] == (58 as u8) { cls = 2 } } }
536 if cls == 1 { if us_iskw(p, L) == 0 { us_put(calls, p, L) } }
537 if cls == 2 { us_put(defs, p, L) }
538 if cls == 0 { if us_iskw(p, L) == 0 { us_put(refs, p, L) } }
539 i = j
540 }
541 adv = 0
542 } }
543
544 if adv == 1 { i = i + 1 }
545 }
546 return qc
547}
548
549// Scan ONE file for top-level declarations and, for any name in the undefined set, record the file that
550// declares it. Only the (small) undefined set is searched for, so memory is O(undefined), not O(corpus).
551func us_scan_decls(buf: *u8, n: i64, und: *u8, undcnt: i64, src: *u8, fname: *u8) -> i64 {
552 var hits: i64 = 0
553 var i: i64 = 0
554 while i < n {
555 var adv: i64 = 1
556 if us_isidst(buf[i] as i64) == 1 {
557 var j: i64 = i
558 var st: i64 = 0
559 while st == 0 {
560 if j >= n { st = 1 } else {
561 if us_isidc(buf[j] as i64) == 1 { j = j + 1 } else { st = 1 }
562 }
563 }
564 let L: i64 = j - i
565 let p: *u8 = ((buf as i64) + i) as *u8
566 var isd: i64 = 0
567 if us_eqlit(p, L, "func" as *u8, 4) == 1 { isd = 1 }
568 if us_eqlit(p, L, "struct" as *u8, 6) == 1 { isd = 1 }
569 if us_eqlit(p, L, "type" as *u8, 4) == 1 { isd = 1 }
570 if us_eqlit(p, L, "const" as *u8, 5) == 1 { isd = 1 }
571 if isd == 1 {
572 var k: i64 = j
573 var s2: i64 = 0
574 while s2 == 0 {
575 if k >= n { s2 = 1 } else {
576 if us_isidst(buf[k] as i64) == 1 { s2 = 1 } else { k = k + 1 }
577 }
578 }
579 if k < n {
580 var e: i64 = k
581 var s3: i64 = 0
582 while s3 == 0 {
583 if e >= n { s3 = 1 } else {
584 if us_isidc(buf[e] as i64) == 1 { e = e + 1 } else { s3 = 1 }
585 }
586 }
587 let dn: *u8 = ((buf as i64) + k) as *u8
588 let dl: i64 = e - k
589 var u: i64 = 0
590 while u < undcnt {
591 let up: *u8 = us_slot(und, u)
592 if us_eqlit(up, dl, dn, dl) == 1 {
593 if up[dl] == (0 as u8) {
594 let sp: *u8 = us_slot(src, u)
595 if sp[0] == (0 as u8) {
596 var z: i64 = 0
597 while fname[z] != (0 as u8) {
598 if z < US_NAMELEN - 1 { sp[z] = fname[z] }
599 z = z + 1
600 }
601 if z > US_NAMELEN - 1 { z = US_NAMELEN - 1 }
602 sp[z] = 0 as u8
603 hits = hits + 1
604 }
605 } }
606 u = u + 1
607 }
608 i = e
609 adv = 0
610 }
611 }
612 if adv == 1 { i = j; adv = 0 }
613 }
614 if adv == 1 { i = i + 1 }
615 }
616 return hits
617}
618
619// Walk ONE source tree. Deliberately the SAME two directories the import resolver searches (runtime/ then
620// _hdl_build/) and no others: a definition sitting in wiki/ or _retired/ is NOT importable, so reporting
621// it as ELSEWHERE would hand the caller a fix that cannot work.
622func us_corpus_dir(dir: *u8, und: *u8, undcnt: i64, src: *u8) -> i64 {
623 let dfd: i64 = sys_openat_rd(dir)
624 if dfd < 0 { return 0 }
625 let dbuf: *u8 = sys_mmap(US_DIRBUF)
626 let path: *u8 = sys_mmap(US_PATHBUF)
627 let lenbox: *i64 = sys_mmap(16) as *i64
628 var files: i64 = 0
629 var go: i64 = 1
630 while go == 1 {
631 let nb: i64 = sys_getdents64(dfd, dbuf, US_DIRBUF)
632 if nb <= 0 { go = 0 } else {
633 var off: i64 = 0
634 while off < nb {
635 let rec: *u8 = (dbuf as i64 + off) as *u8
636 let rl: i64 = dirent_reclen(rec)
637 if rl <= 0 { off = nb } else {
638 let nm: *u8 = dirent_name(rec)
639 var ln: i64 = 0
640 while nm[ln] != (0 as u8) { ln = ln + 1 }
641 var isnx: i64 = 0
642 if ln > 3 {
643 if nm[ln - 3] == (46 as u8) {
644 if nm[ln - 2] == (110 as u8) {
645 if nm[ln - 1] == (120 as u8) { isnx = 1 } } }
646 }
647 if isnx == 1 {
648 var po: i64 = 0
649 var di: i64 = 0
650 while dir[di] != (0 as u8) { path[po] = dir[di]; po = po + 1; di = di + 1 }
651 path[po] = 47 as u8
652 po = po + 1
653 var ni: i64 = 0
654 while nm[ni] != (0 as u8) { path[po] = nm[ni]; po = po + 1; ni = ni + 1 }
655 path[po] = 0 as u8
656 lenbox[0] = 0
657 let fb: *u8 = sys_read_file(path, lenbox)
658 if lenbox[0] > 0 {
659 us_scan_decls(fb, lenbox[0], und, undcnt, src, nm)
660 files = files + 1
661 sys_munmap(fb, lenbox[0])
662 }
663 }
664 off = off + rl
665 }
666 }
667 }
668 }
669 sys_close(dfd)
670 return files
671}
672
673// Collect EVERY top-level declaration name (func/struct/type/const) from a file into tab.
674func us_collect_decls(buf: *u8, n: i64, tab: *u8) -> i64 {
675 var cnt: i64 = 0
676 var i: i64 = 0
677 while i < n {
678 var adv: i64 = 1
679 var iscmt: i64 = 0
680 if buf[i] == (47 as u8) { if i + 1 < n { if buf[i + 1] == (47 as u8) { iscmt = 1 } } }
681 if iscmt == 1 {
682 var j: i64 = i + 2
683 var st: i64 = 0
684 while st == 0 {
685 if j >= n { st = 1 } else {
686 if buf[j] == (10 as u8) { st = 1 } else { j = j + 1 }
687 }
688 }
689 i = j
690 adv = 0
691 }
692 if adv == 1 { if buf[i] == (34 as u8) {
693 var j: i64 = i + 1
694 var st: i64 = 0
695 while st == 0 {
696 if j >= n { st = 1 } else {
697 if buf[j] == (92 as u8) { j = j + 2 } else {
698 if buf[j] == (34 as u8) { j = j + 1; st = 1 } else { j = j + 1 }
699 }
700 }
701 }
702 i = j
703 adv = 0
704 } }
705 if adv == 1 { if us_isidst(buf[i] as i64) == 1 {
706 var j: i64 = i
707 var st: i64 = 0
708 while st == 0 {
709 if j >= n { st = 1 } else {
710 if us_isidc(buf[j] as i64) == 1 { j = j + 1 } else { st = 1 }
711 }
712 }
713 let L: i64 = j - i
714 let p: *u8 = ((buf as i64) + i) as *u8
715 var isd: i64 = 0
716 if us_eqlit(p, L, "func" as *u8, 4) == 1 { isd = 1 }
717 if us_eqlit(p, L, "struct" as *u8, 6) == 1 { isd = 1 }
718 if us_eqlit(p, L, "type" as *u8, 4) == 1 { isd = 1 }
719 if us_eqlit(p, L, "const" as *u8, 5) == 1 { isd = 1 }
720 if isd == 1 {
721 let de: i64 = us_declare_next(buf, n, j, tab)
722 if de >= 0 { i = de; cnt = cnt + 1; adv = 0 }
723 }
724 if adv == 1 { i = j; adv = 0 }
725 } }
726 if adv == 1 { i = i + 1 }
727 }
728 return cnt
729}
730
731// Scan ONE target's closure end to end. Returns undefined count (funcs + idents, builtins excluded), or
732// 0-1 if the target resolves to no file. flag[0] = closure_complete, flag[1] = has_main.
733// Allocates and RELEASES its own tables so a sweep over thousands of targets stays bounded -- the memfloor
734// lesson: an allocator that grows per item is a freeze waiting for a big enough input.
735func us_scan_one(name: *u8, bi: *u8, flag: *i64) -> i64 {
736 let q: *u8 = sys_mmap(US_MAXF * US_NAMELEN)
737 let defs: *u8 = sys_mmap(US_SLOTS * US_NAMELEN)
738 let calls: *u8 = sys_mmap(US_SLOTS * US_NAMELEN)
739 let refs: *u8 = sys_mmap(US_SLOTS * US_NAMELEN)
740 let path: *u8 = sys_mmap(US_PATHBUF)
741 let lb: *i64 = sys_mmap(16) as *i64
742 flag[0] = 1
743 flag[1] = 0
744 var nl: i64 = 0
745 while name[nl] != (0 as u8) { nl = nl + 1 }
746 var qcnt: i64 = us_qpush(q, 0, name, nl)
747 var head: i64 = 0
748 var rc: i64 = 0
749 let qdir: *i64 = sys_mmap(US_MAXF * 8) as *i64
750 var qd: i64 = 0
751 while qd < US_MAXF { qdir[qd] = 0 - 1; qd = qd + 1 }
752 while head < qcnt {
753 let nm: *u8 = us_slot(q, head)
754 let did: i64 = us_resolve_dir(path, nm, qdir[head])
755 if did < 0 {
756 if head == 0 { rc = 0 - 1 }
757 flag[0] = 0
758 } else {
759 lb[0] = 0
760 let buf: *u8 = sys_read_file(path, lb)
761 if lb[0] <= 0 { flag[0] = 0 } else {
762 let before: i64 = qcnt
763 qcnt = us_scan(buf, lb[0], defs, calls, refs, q, qcnt)
764 // Every entry pushed while scanning THIS file was imported BY this file, so its binding
765 // preference is the directory this file actually resolved to -- not the root's.
766 var kid: i64 = before
767 while kid < qcnt { qdir[kid] = did; kid = kid + 1 }
768 if head == 0 { flag[1] = us_has(defs, "main" as *u8, 4) }
769 sys_munmap(buf, lb[0])
770 }
771 }
772 head = head + 1
773 if qcnt >= US_MAXF { flag[0] = 0 }
774 }
775 sys_munmap(qdir, US_MAXF * 8)
776 var undef: i64 = 0
777 if rc == 0 {
778 var s: i64 = 0
779 while s < US_SLOTS {
780 let p: *u8 = us_slot(calls, s)
781 if p[0] != (0 as u8) {
782 var L: i64 = 0
783 while p[L] != (0 as u8) { L = L + 1 }
784 if us_has(defs, p, L) == 0 { if us_has(bi, p, L) == 0 { if us_is_dunder(p) == 0 { undef = undef + 1 } } }
785 }
786 let r: *u8 = us_slot(refs, s)
787 if r[0] != (0 as u8) {
788 var L2: i64 = 0
789 while r[L2] != (0 as u8) { L2 = L2 + 1 }
790 if us_has(defs, r, L2) == 0 { if us_has(bi, r, L2) == 0 { if us_is_dunder(r) == 0 { undef = undef + 1 } } }
791 }
792 s = s + 1
793 }
794 }
795 sys_munmap(q, US_MAXF * US_NAMELEN)
796 sys_munmap(defs, US_SLOTS * US_NAMELEN)
797 sys_munmap(calls, US_SLOTS * US_NAMELEN)
798 sys_munmap(refs, US_SLOTS * US_NAMELEN)
799 if rc < 0 { return 0 - 1 }
800 return undef
801}
802
803// PAGED sweep over a directory. off/lim make this a bounded operation instead of one long run that a
804// 12s deadline would truncate silently -- and a truncated sweep reporting zero is exactly the PARTIAL lie
805// this organ exists to prevent. Prints ONLY targets with findings, plus a tail line naming what was
806// covered so the caller can page the rest and can never mistake a page for the whole corpus.
807func us_sweep(dir: *u8, off0: i64, lim: i64, bi: *u8) -> i64 {
808 let dfd: i64 = sys_openat_rd(dir)
809 if dfd < 0 { return 0 }
810 let dbuf: *u8 = sys_mmap(US_DIRBUF)
811 let base: *u8 = sys_mmap(US_PATHBUF)
812 let flag: *i64 = sys_mmap(32) as *i64
813 var idx: i64 = 0
814 var done: i64 = 0
815 var hits: i64 = 0
816 var go: i64 = 1
817 while go == 1 {
818 let nb: i64 = sys_getdents64(dfd, dbuf, US_DIRBUF)
819 if nb <= 0 { go = 0 } else {
820 var off: i64 = 0
821 while off < nb {
822 let rec: *u8 = (dbuf as i64 + off) as *u8
823 let rl: i64 = dirent_reclen(rec)
824 if rl <= 0 { off = nb } else {
825 let nm: *u8 = dirent_name(rec)
826 var ln: i64 = 0
827 while nm[ln] != (0 as u8) { ln = ln + 1 }
828 var isnx: i64 = 0
829 if ln > 3 {
830 if nm[ln - 3] == (46 as u8) {
831 if nm[ln - 2] == (110 as u8) {
832 if nm[ln - 1] == (120 as u8) { isnx = 1 } } }
833 }
834 if isnx == 1 {
835 if idx >= off0 { if done < lim {
836 var b: i64 = 0
837 while b < ln - 3 { base[b] = nm[b]; b = b + 1 }
838 base[ln - 3] = 0 as u8
839 let u: i64 = us_scan_one(base, bi, flag)
840 if u > 0 {
841 us_puts("TARGET " as *u8)
842 us_puts(base)
843 us_puts(" undefined=" as *u8)
844 us_puti(u)
845 us_puts(" has_main=" as *u8)
846 us_puti(flag[1])
847 if flag[0] == 0 { us_puts(" PARTIAL" as *u8) }
848 us_puts("\n" as *u8)
849 hits = hits + 1
850 }
851 done = done + 1
852 } }
853 idx = idx + 1
854 }
855 off = off + rl
856 }
857 }
858 }
859 }
860 sys_close(dfd)
861 us_puts("sweep_dir_total_nx=" as *u8)
862 us_puti(idx)
863 us_puts(" scanned=" as *u8)
864 us_puti(done)
865 us_puts(" offset=" as *u8)
866 us_puti(off0)
867 us_puts(" with_findings=" as *u8)
868 us_puti(hits)
869 us_puts("\n" as *u8)
870 return hits
871}
872
873func us_join(out: *u8, dir: *u8, name: *u8) {
874 var w: i64 = 0
875 var i: i64 = 0
876 while dir[i] != (0 as u8) { out[w] = dir[i]; w = w + 1; i = i + 1 }
877 out[w] = 47 as u8
878 w = w + 1
879 i = 0
880 while name[i] != (0 as u8) { out[w] = name[i]; w = w + 1; i = i + 1 }
881 out[w] = 0 as u8
882}
883
884// THE DEDUPE DISCRIMINATOR. For an artifact X.nx.<suffix> the survivor is X.nx. The artifact is a COPY if
885// the survivor declares every symbol it does; it is a COLLISION -- a DELETED CAPABILITY -- if it declares
886// symbols the survivor lacks. Sampling this population found one real deletion by luck and produced two
887// false positives, which is exactly why it has to be measured rather than eyeballed.
888// A nonzero `lost` is a CANDIDATE, not a verdict: confirm by checking whether anything still imports the
889// survivor's name. Nothing here deletes, restores, or rewrites anything -- it only reports.
890func us_dupescan(dir: *u8) -> i64 {
891 let dfd: i64 = sys_openat_rd(dir)
892 if dfd < 0 { return 0 }
893 let dbuf: *u8 = sys_mmap(US_DIRBUF)
894 let apath: *u8 = sys_mmap(US_PATHBUF)
895 let spath: *u8 = sys_mmap(US_PATHBUF)
896 let base: *u8 = sys_mmap(US_PATHBUF)
897 let lb: *i64 = sys_mmap(16) as *i64
898 var seen: i64 = 0
899 var go: i64 = 1
900 while go == 1 {
901 let nb: i64 = sys_getdents64(dfd, dbuf, US_DIRBUF)
902 if nb <= 0 { go = 0 } else {
903 var off: i64 = 0
904 while off < nb {
905 let rec: *u8 = (dbuf as i64 + off) as *u8
906 let rl: i64 = dirent_reclen(rec)
907 if rl <= 0 { off = nb } else {
908 let nm: *u8 = dirent_name(rec)
909 var ln: i64 = 0
910 while nm[ln] != (0 as u8) { ln = ln + 1 }
911 var cut: i64 = 0 - 1
912 var k: i64 = 0
913 while k + 3 < ln {
914 if nm[k] == (46 as u8) {
915 if nm[k + 1] == (110 as u8) {
916 if nm[k + 2] == (120 as u8) {
917 if nm[k + 3] == (46 as u8) { cut = k + 3 } } } }
918 k = k + 1
919 }
920 if cut > 0 {
921 seen = seen + 1
922 var b: i64 = 0
923 while b < cut { base[b] = nm[b]; b = b + 1 }
924 base[cut] = 0 as u8
925 us_join(apath, dir, nm)
926 us_join(spath, US_RTDIR, base)
927 var sfound: i64 = us_exists(spath)
928 if sfound == 0 {
929 us_join(spath, US_HDDIR, base)
930 sfound = us_exists(spath)
931 }
932 us_puts("ARTIFACT " as *u8)
933 us_puts(nm)
934 if sfound == 0 {
935 us_puts(" survivor=NONE -- nothing reclaimed the name\n" as *u8)
936 } else {
937 let ta: *u8 = sys_mmap(US_SLOTS * US_NAMELEN)
938 let tb: *u8 = sys_mmap(US_SLOTS * US_NAMELEN)
939 lb[0] = 0
940 let ab: *u8 = sys_read_file(apath, lb)
941 let an: i64 = lb[0]
942 var adecl: i64 = 0
943 if an > 0 { adecl = us_collect_decls(ab, an, ta) }
944 lb[0] = 0
945 let sb: *u8 = sys_read_file(spath, lb)
946 let sn: i64 = lb[0]
947 if sn > 0 { us_collect_decls(sb, sn, tb) }
948 var lost: i64 = 0
949 var s: i64 = 0
950 while s < US_SLOTS {
951 let p: *u8 = us_slot(ta, s)
952 if p[0] != (0 as u8) {
953 var L: i64 = 0
954 while p[L] != (0 as u8) { L = L + 1 }
955 if us_has(tb, p, L) == 0 { lost = lost + 1 }
956 }
957 s = s + 1
958 }
959 us_puts(" decls=" as *u8)
960 us_puti(adecl)
961 us_puts(" lost=" as *u8)
962 us_puti(lost)
963 if lost > 0 {
964 us_puts(" CANDIDATE-COLLISION -- survivor lacks symbols this file declared\n" as *u8)
965 } else {
966 us_puts(" COPY -- survivor declares everything\n" as *u8)
967 }
968 if an > 0 { sys_munmap(ab, an) }
969 if sn > 0 { sys_munmap(sb, sn) }
970 sys_munmap(ta, US_SLOTS * US_NAMELEN)
971 sys_munmap(tb, US_SLOTS * US_NAMELEN)
972 }
973 }
974 off = off + rl
975 }
976 }
977 }
978 }
979 sys_close(dfd)
980 return seen
981}
982
983// ===================================================================================================
984// CALL-SITE ARITY, ON THE IMPORT CLOSURE.
985//
986// WHY IT LIVES HERE AND NOT IN A SCANNER OF ITS OWN. A directory-scoped arity checker
987// (nx_aritycheck_gate) was built first and produced FOUR successive classes of false positive on the
988// real corpus, each one discovered only by hand-verifying a finding before reporting it:
989// 1. COMMENTS -- definitions were column-0 anchored but call sites were not, so a call written
990// inside a `//` line counted. aa_chain is defined once with 4 params and called
991// with 4; the "2-arg call" was a comment.
992// 2. SCOPE -- definitions were pooled per DIRECTORY, so every organ's own `main` collided.
993// 3. STRING LITERALS -- commas inside a string counted as argument separators, so
994// as_append(out, o, "system-ui,-apple-system,Segoe UI,sans-serif") read as 6 args
995// against a 3-param definition.
996// 4. CROSS-PROGRAM NAME COLLISION -- and this one is NOT patchable. nx_authorgen_gate.nx calls
997// ag_build() with ZERO arguments and is CORRECT: it imports runtime/nx_authorgen.nx
998// which declares `func ag_build() -> i64`. The directory scanner compared it against
999// an unrelated `func ag_build(parent, value, kind)` in nx_analyst_gate.nx -- a file
1000// nx_authorgen_gate.nx does not import and never links against.
1001//
1002// THE LAW THAT MOVED THE CODE: THE ONLY SOUND SCOPE FOR A CALL-SITE CHECK IS THE IMPORT CLOSURE.
1003// A directory is not a scope. Counting definitions to disambiguate does not rescue it -- ag_build is
1004// declared exactly ONCE inside _hdl_build/, so a uniqueness rule still judged it, and still got it wrong,
1005// because the definition that actually binds lives OUTSIDE the directory. Every further heuristic would
1006// have bought one more class and lost the next. This organ already walks the closure, already skips
1007// comments, strings, numeric literals and directives AS UNITS, and already refuses to certify a PARTIAL
1008// closure. Arity belongs where those three properties already hold, and nowhere else.
1009//
1010// WHY THE COMPILER DOES NOT CATCH IT: nx_cc FAILS OPEN on argument count -- a wrong-arity call compiles
1011// green and silent. Same fail-open family as the duplicate definition it now rejects (1785447657) and the
1012// undefined identifier this organ already censuses (seq1012).
1013//
1014// KNOWN FALSE-POSITIVE CLASS, STATED SO NO CALLER HAS TO GUESS: a call THROUGH A FUNCTION-POINTER
1015// VARIABLE that happens to share a name with a declared function is compared against that declaration.
1016// This is the same bounded class the undefined-symbol pass already documents, and it is bounded the same
1017// way -- by the positive control below.
1018// ===================================================================================================
1019
1020const US_ARUNK: i64 = 0 - 2
1021const US_ARAMB: i64 = 0 - 3
1022
1023func us_alen(s: *u8) -> i64 {
1024 var n: i64 = 0
1025 while s[n] != (0 as u8) { n = n + 1 }
1026 return n
1027}
1028
1029// Slot index of an EXISTING name, or 0-1. us_has answers yes/no; arity needs the identity of the slot so
1030// the parameter count can never be read for a different name than the one that was matched.
1031func us_index(tab: *u8, s: *u8, n: i64) -> i64 {
1032 if n <= 0 { return 0 - 1 }
1033 if n >= US_NAMELEN { return 0 - 1 }
1034 let h: i64 = us_hash(s, n)
1035 var idx: i64 = h - (h / US_SLOTS) * US_SLOTS
1036 var probes: i64 = 0
1037 while probes < US_SLOTS {
1038 let p: *u8 = us_slot(tab, idx)
1039 if p[0] == (0 as u8) { return 0 - 1 }
1040 var eq: i64 = 1
1041 if p[n] != (0 as u8) { eq = 0 }
1042 var k: i64 = 0
1043 while k < n {
1044 if p[k] != s[k] { eq = 0; k = n } else { k = k + 1 }
1045 }
1046 if eq == 1 { return idx }
1047 idx = idx + 1
1048 if idx >= US_SLOTS { idx = 0 }
1049 probes = probes + 1
1050 }
1051 return 0 - 1
1052}
1053
1054// Count top-level comma-separated items between the parens beginning at `op` (which must index '(').
1055// ONE helper serves BOTH a declaration's parameter list and a call's argument list -- they are the same
1056// shape, and this file already uses one helper for func/struct/type on the same reasoning (rule 15).
1057// Nested parens, string literals and // comments are skipped as units, so a comma inside any of them is
1058// never counted; a fn-pointer parameter type like `f: func(i64, i64) -> i64` therefore reads as ONE item.
1059// Returns 0 for "()", the item count otherwise, or 0-1 if the list never closes (caller must not judge).
1060func us_commas(buf: *u8, n: i64, op: i64) -> i64 {
1061 var i: i64 = op + 1
1062 var depth: i64 = 1
1063 var commas: i64 = 0
1064 var any: i64 = 0
1065 while i < n {
1066 let c: i64 = buf[i] as i64
1067 var adv: i64 = 1
1068
1069 if c == 47 {
1070 if i + 1 < n {
1071 if buf[i + 1] == (47 as u8) {
1072 var j: i64 = i + 2
1073 var st: i64 = 0
1074 while st == 0 {
1075 if j >= n { st = 1 } else {
1076 if buf[j] == (10 as u8) { st = 1 } else { j = j + 1 }
1077 }
1078 }
1079 i = j
1080 adv = 0
1081 }
1082 }
1083 }
1084
1085 if adv == 1 {
1086 if c == 34 {
1087 any = 1
1088 var j: i64 = i + 1
1089 var st: i64 = 0
1090 while st == 0 {
1091 if j >= n { st = 1 } else {
1092 if buf[j] == (92 as u8) { j = j + 2 } else {
1093 if buf[j] == (34 as u8) { j = j + 1; st = 1 } else { j = j + 1 }
1094 }
1095 }
1096 }
1097 i = j
1098 adv = 0
1099 } }
1100
1101 if adv == 1 {
1102 if c == 40 { depth = depth + 1; any = 1 }
1103 if c == 41 {
1104 depth = depth - 1
1105 if depth == 0 {
1106 if any == 0 { return 0 }
1107 return commas + 1
1108 }
1109 any = 1
1110 }
1111 if depth == 1 { if c == 44 { commas = commas + 1 } }
1112 if c != 32 { if c != 10 { if c != 9 { if c != 13 { any = 1 } } } }
1113 i = i + 1
1114 }
1115 }
1116 return 0 - 1
1117}
1118
1119// Record the parameter count of every `func NAME(...)` in this file, keyed by the SAME slot index the
1120// definition table uses -- so a later lookup can never read the arity of a different name than the one
1121// it matched.
1122// A name that appears TWICE in one closure with DIFFERENT arities is marked AMBIGUOUS and never judged
1123// again. Guessing which declaration a call meant would manufacture exactly the false positive this
1124// rewrite exists to eliminate; and two definitions of one name is a separate defect that nx_cc now
1125// rejects on its own.
1126func us_collect_arity(buf: *u8, n: i64, defs: *u8, defar: *i64) -> i64 {
1127 var cnt: i64 = 0
1128 var i: i64 = 0
1129 while i < n {
1130 var adv: i64 = 1
1131
1132 if buf[i] == (47 as u8) {
1133 if i + 1 < n {
1134 if buf[i + 1] == (47 as u8) {
1135 var j: i64 = i + 2
1136 var st: i64 = 0
1137 while st == 0 {
1138 if j >= n { st = 1 } else {
1139 if buf[j] == (10 as u8) { st = 1 } else { j = j + 1 }
1140 }
1141 }
1142 i = j
1143 adv = 0
1144 }
1145 }
1146 }
1147
1148 if adv == 1 {
1149 if buf[i] == (34 as u8) {
1150 var j: i64 = i + 1
1151 var st: i64 = 0
1152 while st == 0 {
1153 if j >= n { st = 1 } else {
1154 if buf[j] == (92 as u8) { j = j + 2 } else {
1155 if buf[j] == (34 as u8) { j = j + 1; st = 1 } else { j = j + 1 }
1156 }
1157 }
1158 }
1159 i = j
1160 adv = 0
1161 } }
1162
1163 if adv == 1 {
1164 if us_isidst(buf[i] as i64) == 1 {
1165 var j: i64 = i
1166 var st: i64 = 0
1167 while st == 0 {
1168 if j >= n { st = 1 } else {
1169 if us_isidc(buf[j] as i64) == 1 { j = j + 1 } else { st = 1 }
1170 }
1171 }
1172 let L: i64 = j - i
1173 let p: *u8 = ((buf as i64) + i) as *u8
1174 var handled: i64 = 0
1175 if us_eqlit(p, L, "func" as *u8, 4) == 1 {
1176 var k: i64 = j
1177 var s2: i64 = 0
1178 while s2 == 0 {
1179 if k >= n { s2 = 1 } else {
1180 if us_isidst(buf[k] as i64) == 1 { s2 = 1 } else { k = k + 1 }
1181 }
1182 }
1183 if k < n {
1184 var e: i64 = k
1185 var s3: i64 = 0
1186 while s3 == 0 {
1187 if e >= n { s3 = 1 } else {
1188 if us_isidc(buf[e] as i64) == 1 { e = e + 1 } else { s3 = 1 }
1189 }
1190 }
1191 let np: *u8 = ((buf as i64) + k) as *u8
1192 let NL: i64 = e - k
1193 if e < n {
1194 if buf[e] == (40 as u8) {
1195 let a: i64 = us_commas(buf, n, e)
1196 if a >= 0 {
1197 let slot: i64 = us_put(defs, np, NL)
1198 if slot >= 0 {
1199 if defar[slot] == US_ARUNK {
1200 defar[slot] = a
1201 cnt = cnt + 1
1202 } else {
1203 if defar[slot] != a { defar[slot] = US_ARAMB }
1204 }
1205 }
1206 }
1207 }
1208 }
1209 i = e
1210 handled = 1
1211 }
1212 }
1213 if handled == 0 { i = j }
1214 adv = 0
1215 } }
1216
1217 if adv == 1 { i = i + 1 }
1218 }
1219 return cnt
1220}
1221
1222// Compare every call site in this file against the arity recorded from the closure. A name with no
1223// recorded arity (US_ARUNK) is NOT a finding here -- that is the undefined-symbol pass's job, and
1224// double-reporting it would inflate one defect into two. AMBIGUOUS is likewise never judged.
1225func us_check_arity(buf: *u8, n: i64, defs: *u8, defar: *i64, fname: *u8) -> i64 {
1226 var bad: i64 = 0
1227 var i: i64 = 0
1228 while i < n {
1229 var adv: i64 = 1
1230
1231 if buf[i] == (47 as u8) {
1232 if i + 1 < n {
1233 if buf[i + 1] == (47 as u8) {
1234 var j: i64 = i + 2
1235 var st: i64 = 0
1236 while st == 0 {
1237 if j >= n { st = 1 } else {
1238 if buf[j] == (10 as u8) { st = 1 } else { j = j + 1 }
1239 }
1240 }
1241 i = j
1242 adv = 0
1243 }
1244 }
1245 }
1246
1247 if adv == 1 {
1248 if buf[i] == (34 as u8) {
1249 var j: i64 = i + 1
1250 var st: i64 = 0
1251 while st == 0 {
1252 if j >= n { st = 1 } else {
1253 if buf[j] == (92 as u8) { j = j + 2 } else {
1254 if buf[j] == (34 as u8) { j = j + 1; st = 1 } else { j = j + 1 }
1255 }
1256 }
1257 }
1258 i = j
1259 adv = 0
1260 } }
1261
1262 if adv == 1 {
1263 if us_isidst(buf[i] as i64) == 1 {
1264 var j: i64 = i
1265 var st: i64 = 0
1266 while st == 0 {
1267 if j >= n { st = 1 } else {
1268 if us_isidc(buf[j] as i64) == 1 { j = j + 1 } else { st = 1 }
1269 }
1270 }
1271 let L: i64 = j - i
1272 let p: *u8 = ((buf as i64) + i) as *u8
1273 if us_iskw(p, L) == 0 {
1274 if j < n {
1275 if buf[j] == (40 as u8) {
1276 let slot: i64 = us_index(defs, p, L)
1277 if slot >= 0 {
1278 let want: i64 = defar[slot]
1279 if want >= 0 {
1280 let got: i64 = us_commas(buf, n, j)
1281 if got >= 0 {
1282 if got != want {
1283 us_puts(" ARITY-MISMATCH " as *u8)
1284 us_puts(fname)
1285 us_puts(": " as *u8)
1286 us_putn(p, L)
1287 us_puts(" called with " as *u8)
1288 us_puti(got)
1289 us_puts(" arg(s), declared with " as *u8)
1290 us_puti(want)
1291 us_puts("\n" as *u8)
1292 bad = bad + 1
1293 }
1294 }
1295 }
1296 }
1297 }
1298 }
1299 }
1300 i = j
1301 adv = 0
1302 } }
1303
1304 if adv == 1 { i = i + 1 }
1305 }
1306 return bad
1307}
1308
1309// PROVE THE COUNTER CAN FAIL BEFORE TRUSTING IT ON THE CORPUS. Every assertion here is a case that a
1310// previous implementation of this axis got WRONG on real source -- nesting, strings, comments, empty
1311// lists -- plus the unterminated case, which must refuse rather than answer. A tooth that only tests the
1312// cases the author already handled has tested nothing.
1313func us_arity_selftest() -> i64 {
1314 var fail: i64 = 0
1315 let a: *u8 = "(a, b, c)" as *u8
1316 if us_commas(a, us_alen(a), 0) != 3 { us_puts(" T1 FAIL plain\n" as *u8); fail = fail + 1 }
1317 let b: *u8 = "()" as *u8
1318 if us_commas(b, us_alen(b), 0) != 0 { us_puts(" T2 FAIL empty\n" as *u8); fail = fail + 1 }
1319 let c: *u8 = "(x)" as *u8
1320 if us_commas(c, us_alen(c), 0) != 1 { us_puts(" T3 FAIL single\n" as *u8); fail = fail + 1 }
1321 let d: *u8 = "(f(x, y), z)" as *u8
1322 if us_commas(d, us_alen(d), 0) != 2 { us_puts(" T4 FAIL nested\n" as *u8); fail = fail + 1 }
1323 // T5 STRING LITERAL -- built BYTE-WISE, not written as an escaped literal. An escaped literal would
1324 // also be testing whatever transport carried this source onto the NAS; bytes test only the counter.
1325 // Spells: ("a,b",z) -- two arguments, two commas, one of them inside the string.
1326 let e: *u8 = sys_mmap(32)
1327 e[0] = 40 as u8
1328 e[1] = 34 as u8
1329 e[2] = 97 as u8
1330 e[3] = 44 as u8
1331 e[4] = 98 as u8
1332 e[5] = 34 as u8
1333 e[6] = 44 as u8
1334 e[7] = 122 as u8
1335 e[8] = 41 as u8
1336 e[9] = 0 as u8
1337 if us_commas(e, 9, 0) != 2 { us_puts(" T5 FAIL string-literal comma counted\n" as *u8); fail = fail + 1 }
1338 let f: *u8 = "( )" as *u8
1339 if us_commas(f, us_alen(f), 0) != 0 { us_puts(" T6 FAIL whitespace-only\n" as *u8); fail = fail + 1 }
1340 let g: *u8 = "(a, b" as *u8
1341 if us_commas(g, us_alen(g), 0) != 0 - 1 { us_puts(" T7 FAIL unterminated must refuse\n" as *u8); fail = fail + 1 }
1342 let h: *u8 = "(g(1, 2), h(3, 4), 5)" as *u8
1343 if us_commas(h, us_alen(h), 0) != 3 { us_puts(" T8 FAIL two nested\n" as *u8); fail = fail + 1 }
1344 // T9/T10 NON-VACUITY. The counter passing its own arithmetic teeth does NOT prove the REPORTING PATH
1345 // ever fires -- and 14 corpus targets in a row returned zero, which a broken checker and a clean
1346 // corpus produce identically. So drive collect AND check over a synthetic closure whose answer is
1347 // known, and REQUIRE the mismatch to be seen. Without this, "0 mismatches" is an unfalsifiable claim.
1348 let td: *u8 = sys_mmap(US_SLOTS * US_NAMELEN)
1349 let ta: *i64 = sys_mmap(US_SLOTS * 8) as *i64
1350 var zz: i64 = 0
1351 while zz < US_SLOTS { ta[zz] = US_ARUNK; zz = zz + 1 }
1352 let bad9: *u8 = "func zq_a(p: i64, q: i64) -> i64 { return p }\nfunc zq_b() -> i64 { return zq_a(1) }\n" as *u8
1353 let n9: i64 = us_alen(bad9)
1354 us_collect_arity(bad9, n9, td, ta)
1355 us_puts(" (T9 expects exactly ONE synthetic mismatch on the next line -- it IS the tooth)\n" as *u8)
1356 let m9: i64 = us_check_arity(bad9, n9, td, ta, "T9-SYNTHETIC" as *u8)
1357 if m9 != 1 { us_puts(" T9 FAIL -- the reporting path never fired; every corpus zero is VACUOUS\n" as *u8); fail = fail + 1 }
1358 let ok10: *u8 = "func zr_a(p: i64, q: i64) -> i64 { return p }\nfunc zr_b() -> i64 { return zr_a(1, 2) }\n" as *u8
1359 let n10: i64 = us_alen(ok10)
1360 us_collect_arity(ok10, n10, td, ta)
1361 let m10: i64 = us_check_arity(ok10, n10, td, ta, "T10-SYNTHETIC" as *u8)
1362 if m10 != 0 { us_puts(" T10 FAIL -- a CORRECT call was reported as a mismatch\n" as *u8); fail = fail + 1 }
1363 sys_munmap(td, US_SLOTS * US_NAMELEN)
1364 sys_munmap(ta, US_SLOTS * 8)
1365
1366 // NEGATIVE CONTROL: a counter that always returned 3 would pass T1 and T8 and nothing else. T2/T3/T7
1367 // are the ones that can only pass if it really counts, and T7 only if it really refuses.
1368 if fail == 0 { us_puts(" arity self-test OK (nesting + strings + empty + unterminated-refuses)\n" as *u8) }
1369 return fail
1370}
1371
1372// Walk ONE target's closure and check every call site in it. Returns the mismatch count, or 0-1 if the
1373// target resolves to no file. flag[0] = closure_complete.
1374// THREE passes, deliberately: the definition set must be COMPLETE before any call site is judged, or a
1375// call to a function defined in a not-yet-read import would be compared against nothing (silently clean)
1376// or against a stale entry. Reading each closure file twice more is cheap; judging early is not.
1377// RESOLVE THE WAY THE COMPILER BINDS, NOT IN A FIXED GLOBAL ORDER. Measured 2026-07-31: us_resolve takes
1378// only a NAME, so for a name that exists in BOTH runtime/ and _hdl_build/ it always returns the runtime/
1379// copy. nx_m2d_engine lives in _hdl_build/ and is written against _hdl_build/nx_input_abstract.nx
1380// (ia_init with 2 params, ia_bind with 4); the fixed order handed it runtime/nx_input_abstract.nx
1381// (ia_init with 1, ia_bind with 3) and manufactured EIGHTEEN arity mismatches that are not defects.
1382// nx_cc binds the IMPORTER'S OWN DIRECTORY FIRST, so the answer it gives is the only one that matters.
1383// This preference is applied to the ARITY path only; the existing scan path is deliberately left alone
1384// (rule 19) and carries the same latent flaw -- filed separately, with this reproducer.
1385func us_resolve_pref(path: *u8, nm: *u8, pref: *u8) -> i64 {
1386 if pref[0] != (0 as u8) {
1387 us_path(path, pref, nm)
1388 if us_exists(path) == 1 { return 1 }
1389 }
1390 return us_resolve(path, nm)
1391}
1392
1393// Which directory does this target itself live in? Probed in BUILD-TARGET rank order (_hdl_build first),
1394// because that is the copy /api/build compiles, and the closure must be read against the same tree the
1395// artifact was produced from.
1396func us_rootdir(nm: *u8, path: *u8) -> *u8 {
1397 us_path(path, US_HDDIR, nm)
1398 if us_exists(path) == 1 { return US_HDDIR }
1399 us_path(path, US_RTDIR, nm)
1400 if us_exists(path) == 1 { return US_RTDIR }
1401 us_path(path, US_HUBDIR, nm)
1402 if us_exists(path) == 1 { return US_HUBDIR }
1403 us_path(path, US_WIKIDIR, nm)
1404 if us_exists(path) == 1 { return US_WIKIDIR }
1405 return "" as *u8
1406}
1407
1408func us_arity_one(name: *u8, flag: *i64) -> i64 {
1409 let q: *u8 = sys_mmap(US_MAXF * US_NAMELEN)
1410 let defs: *u8 = sys_mmap(US_SLOTS * US_NAMELEN)
1411 let calls: *u8 = sys_mmap(US_SLOTS * US_NAMELEN)
1412 let refs: *u8 = sys_mmap(US_SLOTS * US_NAMELEN)
1413 let defar: *i64 = sys_mmap(US_SLOTS * 8) as *i64
1414 let path: *u8 = sys_mmap(US_PATHBUF)
1415 let lb: *i64 = sys_mmap(16) as *i64
1416
1417 // mmap gives ZEROED memory and zero is a LEGITIMATE arity, so the unknown state must be written
1418 // explicitly. Leaving it zeroed would silently claim every never-declared name takes no arguments.
1419 var z: i64 = 0
1420 while z < US_SLOTS { defar[z] = US_ARUNK; z = z + 1 }
1421
1422 flag[0] = 1
1423 // ONE resolution rule for the whole organ: per-importer, exactly as us_resolve_dir documents. The
1424 // root-directory approximation this path used first was closer than the fixed global order but still
1425 // wrong for a mixed closure (a runtime/ file importing runtime/hub/ importing a shadowed name).
1426 let qdir: *i64 = sys_mmap(US_MAXF * 8) as *i64
1427 var qd: i64 = 0
1428 while qd < US_MAXF { qdir[qd] = 0 - 1; qd = qd + 1 }
1429 var nl: i64 = 0
1430 while name[nl] != (0 as u8) { nl = nl + 1 }
1431 var qcnt: i64 = us_qpush(q, 0, name, nl)
1432 var head: i64 = 0
1433 var rc: i64 = 0
1434
1435 while head < qcnt {
1436 let nm: *u8 = us_slot(q, head)
1437 let did: i64 = us_resolve_dir(path, nm, qdir[head])
1438 if did < 0 {
1439 if head == 0 { rc = 0 - 1 }
1440 flag[0] = 0
1441 } else {
1442 lb[0] = 0
1443 let buf: *u8 = sys_read_file(path, lb)
1444 if lb[0] <= 0 { flag[0] = 0 } else {
1445 let before: i64 = qcnt
1446 qcnt = us_scan(buf, lb[0], defs, calls, refs, q, qcnt)
1447 var kid: i64 = before
1448 while kid < qcnt { qdir[kid] = did; kid = kid + 1 }
1449 sys_munmap(buf, lb[0])
1450 }
1451 }
1452 head = head + 1
1453 if qcnt >= US_MAXF { flag[0] = 0 }
1454 }
1455
1456 var decls: i64 = 0
1457 var bad: i64 = 0
1458 if rc == 0 {
1459 var h2: i64 = 0
1460 while h2 < qcnt {
1461 let nm2: *u8 = us_slot(q, h2)
1462 if us_resolve_dir(path, nm2, qdir[h2]) >= 0 {
1463 lb[0] = 0
1464 let b2: *u8 = sys_read_file(path, lb)
1465 if lb[0] > 0 {
1466 decls = decls + us_collect_arity(b2, lb[0], defs, defar)
1467 sys_munmap(b2, lb[0])
1468 }
1469 }
1470 h2 = h2 + 1
1471 }
1472 var h3: i64 = 0
1473 while h3 < qcnt {
1474 let nm3: *u8 = us_slot(q, h3)
1475 if us_resolve_dir(path, nm3, qdir[h3]) >= 0 {
1476 lb[0] = 0
1477 let b3: *u8 = sys_read_file(path, lb)
1478 if lb[0] > 0 {
1479 bad = bad + us_check_arity(b3, lb[0], defs, defar, nm3)
1480 sys_munmap(b3, lb[0])
1481 }
1482 }
1483 h3 = h3 + 1
1484 }
1485 }
1486
1487 flag[1] = decls
1488 flag[2] = qcnt
1489 sys_munmap(q, US_MAXF * US_NAMELEN)
1490 sys_munmap(defs, US_SLOTS * US_NAMELEN)
1491 sys_munmap(calls, US_SLOTS * US_NAMELEN)
1492 sys_munmap(refs, US_SLOTS * US_NAMELEN)
1493 sys_munmap(defar, US_SLOTS * 8)
1494 sys_munmap(qdir, US_MAXF * 8)
1495 if rc < 0 { return 0 - 1 }
1496 return bad
1497}
1498
1499
1500func main(argc: i64, argv: *i64) -> i64 {
1501 if argc < 3 {
1502 us_puts("usage: nx_undefscan scan <target> (basename WITHOUT .nx)\n" as *u8)
1503 us_puts(" Walks the import closure and prints every called-but-undefined function.\n" as *u8)
1504 us_puts(" exit 0 CLEAN+COMPLETE | 1 UNDEFINED | 3 not found | 5 PARTIAL (cannot certify)\n" as *u8)
1505 return US_EXIT_USAGE
1506 }
1507 let verb: *u8 = argv[1] as *u8
1508 var vn: i64 = 0
1509 while verb[vn] != (0 as u8) { vn = vn + 1 }
1510 if us_eqlit(verb, vn, "sweep" as *u8, 5) == 1 {
1511 if argc < 5 {
1512 us_puts("usage: nx_undefscan sweep <dir> <offset> <limit>\n" as *u8)
1513 return US_EXIT_USAGE
1514 }
1515 let sbi: *u8 = sys_mmap(US_SLOTS * US_NAMELEN)
1516 us_load_builtins(sbi)
1517 var o0: i64 = 0
1518 let os: *u8 = argv[3] as *u8
1519 var oi: i64 = 0
1520 while os[oi] != (0 as u8) { o0 = o0 * 10 + ((os[oi] as i64) - 48); oi = oi + 1 }
1521 var lm: i64 = 0
1522 let ls: *u8 = argv[4] as *u8
1523 var li: i64 = 0
1524 while ls[li] != (0 as u8) { lm = lm * 10 + ((ls[li] as i64) - 48); li = li + 1 }
1525 us_sweep(argv[2] as *u8, o0, lm, sbi)
1526 return 0
1527 }
1528
1529 if us_eqlit(verb, vn, "arity" as *u8, 5) == 1 {
1530 us_puts("nx_undefscan arity -- CALL-SITE ARITY, SCOPED TO THE IMPORT CLOSURE\n" as *u8)
1531 let sf: i64 = us_arity_selftest()
1532 if sf > 0 {
1533 us_puts("REFUSED: the counter failed its own self-test -- a corpus number from it would be noise\n" as *u8)
1534 return US_EXIT_SELFTEST
1535 }
1536 let aflag: *i64 = sys_mmap(32) as *i64
1537 let bad: i64 = us_arity_one(argv[2] as *u8, aflag)
1538 if bad < 0 {
1539 us_puts("NOT-FOUND: no such target in runtime/ or _hdl_build/: " as *u8)
1540 us_puts(argv[2] as *u8)
1541 us_puts("\n" as *u8)
1542 return US_EXIT_NOTFOUND
1543 }
1544 us_puts("target=" as *u8)
1545 us_puts(argv[2] as *u8)
1546 us_puts(" closure_files=" as *u8)
1547 us_puti(aflag[2])
1548 us_puts(" funcs_with_arity=" as *u8)
1549 us_puti(aflag[1])
1550 us_puts(" arity_mismatches=" as *u8)
1551 us_puti(bad)
1552 us_puts("\n" as *u8)
1553 if aflag[0] == 0 {
1554 us_puts("PARTIAL: an import in the closure could not be read, so the declaration set is INCOMPLETE.\n" as *u8)
1555 us_puts(" A zero here would be a claim this scan cannot support. Fix the closure, then re-run.\n" as *u8)
1556 return US_EXIT_PARTIAL
1557 }
1558 if bad > 0 { return US_EXIT_UNDEF }
1559 return 0
1560 }
1561
1562 if us_eqlit(verb, vn, "dupescan" as *u8, 8) == 1 {
1563 let n: i64 = us_dupescan(argv[2] as *u8)
1564 us_puts("dupescan_artifacts=" as *u8)
1565 us_puti(n)
1566 us_puts("\n" as *u8)
1567 return 0
1568 }
1569
1570 let target: *u8 = argv[2] as *u8
1571 var corpus: i64 = 0
1572 if argc > 3 {
1573 let a3: *u8 = argv[3] as *u8
1574 var a3n: i64 = 0
1575 while a3[a3n] != (0 as u8) { a3n = a3n + 1 }
1576 if us_eqlit(a3, a3n, "corpus" as *u8, 6) == 1 { corpus = 1 }
1577 }
1578
1579 let q: *u8 = sys_mmap(US_MAXF * US_NAMELEN)
1580 let defs: *u8 = sys_mmap(US_SLOTS * US_NAMELEN)
1581 let calls: *u8 = sys_mmap(US_SLOTS * US_NAMELEN)
1582 let refs: *u8 = sys_mmap(US_SLOTS * US_NAMELEN)
1583 let und: *u8 = sys_mmap(US_MAXF * US_NAMELEN)
1584 let usrc: *u8 = sys_mmap(US_MAXF * US_NAMELEN)
1585 let path: *u8 = sys_mmap(US_PATHBUF)
1586 let lenbox: *i64 = sys_mmap(16) as *i64
1587 let bi: *u8 = sys_mmap(US_SLOTS * US_NAMELEN)
1588 let nbi: i64 = us_load_builtins(bi)
1589
1590 var tn: i64 = 0
1591 while target[tn] != (0 as u8) { tn = tn + 1 }
1592 var qcnt: i64 = us_qpush(q, 0, target, tn)
1593 if qcnt == 0 {
1594 us_puts("REFUSED: target name empty or longer than the name buffer\n" as *u8)
1595 return US_EXIT_USAGE
1596 }
1597
1598 var missing: i64 = 0
1599 var overflow: i64 = 0
1600 var hasmain: i64 = 0
1601 var head: i64 = 0
1602 var files: i64 = 0
1603 let qdir: *i64 = sys_mmap(US_MAXF * 8) as *i64
1604 var qd: i64 = 0
1605 while qd < US_MAXF { qdir[qd] = 0 - 1; qd = qd + 1 }
1606
1607 while head < qcnt {
1608 let nm: *u8 = us_slot(q, head)
1609 let did: i64 = us_resolve_dir(path, nm, qdir[head])
1610 if did < 0 {
1611 if head == 0 {
1612 us_puts("NOT-FOUND: no such target in runtime/ or _hdl_build/: " as *u8)
1613 us_puts(nm)
1614 us_puts("\n" as *u8)
1615 return US_EXIT_NOTFOUND
1616 }
1617 us_puts("MISSING-IMPORT " as *u8)
1618 us_puts(nm)
1619 us_puts("\n" as *u8)
1620 missing = missing + 1
1621 } else {
1622 lenbox[0] = 0
1623 let buf: *u8 = sys_read_file(path, lenbox)
1624 if lenbox[0] <= 0 {
1625 us_puts("UNREADABLE " as *u8)
1626 us_puts(nm)
1627 us_puts("\n" as *u8)
1628 missing = missing + 1
1629 } else {
1630 let before: i64 = qcnt
1631 qcnt = us_scan(buf, lenbox[0], defs, calls, refs, q, qcnt)
1632 // Children inherit THIS file's directory as their binding preference (see us_resolve_dir).
1633 var kid: i64 = before
1634 while kid < qcnt { qdir[kid] = did; kid = kid + 1 }
1635 files = files + 1
1636 // Checked right after the ROOT file and before any import is scanned, so defs holds only
1637 // this file's declarations and the answer is exact. CLEAN means every symbol resolves --
1638 // NOT that the target builds. A no-main LIBRARY is legitimately CLEAN and still fails to
1639 // compile standalone with 'UNDEFINED label: main'. Measured on nx_denoise_spectral, where
1640 // reading CLEAN as should-build would have sent the next reader hunting a phantom defect.
1641 if head == 0 { hasmain = us_has(defs, "main" as *u8, 4) }
1642 }
1643 }
1644 head = head + 1
1645 if qcnt >= US_MAXF { overflow = 1 }
1646 }
1647
1648 var undef: i64 = 0
1649 var bicount: i64 = 0
1650 var undcnt: i64 = 0
1651 var intrin: i64 = 0
1652 var s: i64 = 0
1653 while s < US_SLOTS {
1654 let p: *u8 = us_slot(calls, s)
1655 if p[0] != (0 as u8) {
1656 var L: i64 = 0
1657 while p[L] != (0 as u8) { L = L + 1 }
1658 if us_has(defs, p, L) == 0 {
1659 if us_has(bi, p, L) == 1 { bicount = bicount + 1 } else {
1660 if us_is_dunder(p) == 1 { intrin = intrin + 1 } else {
1661 undcnt = us_qpush(und, undcnt, p, L)
1662 undef = undef + 1
1663 } }
1664 }
1665 }
1666 s = s + 1
1667 }
1668
1669 // SECOND CLASS: an identifier that is never called, never declared, and provided by nothing. This is
1670 // the class that made an earlier build of this organ report CLEAN on nx_fnet_mix while nx_cc failed
1671 // with UNRESOLVED identifier NX_FFT_Q -- a false clean, the exact lie this instrument exists to stop.
1672 var undefid: i64 = 0
1673 var r: i64 = 0
1674 while r < US_SLOTS {
1675 let p: *u8 = us_slot(refs, r)
1676 if p[0] != (0 as u8) {
1677 var L: i64 = 0
1678 while p[L] != (0 as u8) { L = L + 1 }
1679 if us_has(defs, p, L) == 0 {
1680 if us_has(bi, p, L) == 1 { bicount = bicount + 1 } else {
1681 if us_is_dunder(p) == 1 { intrin = intrin + 1 } else {
1682 undcnt = us_qpush(und, undcnt, p, L)
1683 undefid = undefid + 1
1684 } }
1685 }
1686 }
1687 r = r + 1
1688 }
1689
1690 // CLASSIFY, then print. A bare list of names invites the exact misreading that produced a wrong sev-8
1691 // filing today: 6 of 7 names were already importable and only 1 was unwritten.
1692 var cfiles: i64 = 0
1693 if corpus == 1 { if undcnt > 0 {
1694 cfiles = us_corpus_dir(US_RTDIR, und, undcnt, usrc)
1695 cfiles = cfiles + us_corpus_dir(US_HDDIR, und, undcnt, usrc)
1696 cfiles = cfiles + us_corpus_dir(US_HUBDIR, und, undcnt, usrc)
1697 cfiles = cfiles + us_corpus_dir(US_WIKIDIR, und, undcnt, usrc)
1698 } }
1699 var wiring: i64 = 0
1700 var unwritten: i64 = 0
1701 var u: i64 = 0
1702 while u < undcnt {
1703 let p: *u8 = us_slot(und, u)
1704 var L: i64 = 0
1705 while p[L] != (0 as u8) { L = L + 1 }
1706 if us_has(calls, p, L) == 1 { us_puts("UNDEFINED " as *u8) } else { us_puts("UNDEFINED-IDENT " as *u8) }
1707 us_putn(p, L)
1708 if corpus == 1 {
1709 let sp: *u8 = us_slot(usrc, u)
1710 if sp[0] == (0 as u8) {
1711 us_puts(" NOWHERE -- unwritten code\n" as *u8)
1712 unwritten = unwritten + 1
1713 } else {
1714 us_puts(" ELSEWHERE-IN " as *u8)
1715 us_puts(sp)
1716 us_puts(" -- MISSING IMPORT, not missing code\n" as *u8)
1717 wiring = wiring + 1
1718 }
1719 } else { us_puts("\n" as *u8) }
1720 u = u + 1
1721 }
1722 if corpus == 1 {
1723 us_puts("corpus_files=" as *u8)
1724 us_puti(cfiles)
1725 us_puts(" wiring_fixable=" as *u8)
1726 us_puti(wiring)
1727 us_puts(" unwritten=" as *u8)
1728 us_puti(unwritten)
1729 us_puts("\n" as *u8)
1730 }
1731 if undef + undefid > undcnt {
1732 us_puts("WARN undefined-list overflowed US_MAXF; listing is TRUNCATED\n" as *u8)
1733 }
1734
1735 us_puts("target=" as *u8)
1736 us_puts(target)
1737 us_puts(" closure_files=" as *u8)
1738 us_puti(files)
1739 us_puts(" missing_imports=" as *u8)
1740 us_puti(missing)
1741 us_puts(" undefined_funcs=" as *u8)
1742 us_puti(undef)
1743 us_puts(" undefined_idents=" as *u8)
1744 us_puti(undefid)
1745 us_puts(" intrinsic_presumed=" as *u8)
1746 us_puti(intrin)
1747 us_puts(" has_main=" as *u8)
1748 us_puti(hasmain)
1749 us_puts(" builtin_excluded=" as *u8)
1750 us_puti(bicount)
1751 us_puts(" builtins_loaded=" as *u8)
1752 us_puti(nbi)
1753 us_puts("\n" as *u8)
1754
1755 // THE TOOTH: a zero from an incomplete closure is not a clean bill. Report PARTIAL and exit nonzero
1756 // even when undefined==0, because the definitions that would have resolved these calls may live in
1757 // exactly the file that could not be read.
1758 if missing > 0 {
1759 us_puts("VERDICT=PARTIAL closure_complete=0 -- count is a FLOOR, not an answer\n" as *u8)
1760 if undef + undefid > 0 { return US_EXIT_UNDEF }
1761 return US_EXIT_PARTIAL
1762 }
1763 if overflow == 1 {
1764 us_puts("VERDICT=PARTIAL closure_complete=0 -- queue hit US_MAXF, closure truncated\n" as *u8)
1765 if undef + undefid > 0 { return US_EXIT_UNDEF }
1766 return US_EXIT_PARTIAL
1767 }
1768 if undef + undefid > 0 {
1769 us_puts("VERDICT=UNDEFINED-SYMBOLS closure_complete=1\n" as *u8)
1770 return US_EXIT_UNDEF
1771 }
1772 us_puts("VERDICT=CLEAN closure_complete=1\n" as *u8)
1773 return 0
1774}