code wiki / _hdl_build / nx_doc_mainscan.nx
nx_doc_mainscan.nx source
↩ module page · 74 lines · 4029 B
1// nx_doc_mainscan.nx -- DOCTOR diagnosis for a NEW failure class discovered live this session: nxasm exits rc=102
2// "UNDEFINED label: main" when a LIBRARY module (functions only, no `func main`) is built as a standalone
3// executable (the linker has no entry point). Seen real: `nx_sov_build_run nx_tls13` -> rc=102, because nx_tls13.nx
4// is the TLS-1.3 record-layer LIBRARY (consumed by nx_pub_tls_serve / the gates, which DO build green). This is a
5// DIFFERENT class from compiler-level CC-UNDEFFN ("call to undefined function: <name>"): it is an ASSEMBLER-level
6// missing entry-point. READ-ONLY BY CONSTRUCTION: it does NOT fabricate a `main` (that would be a fake program and
7// could mask a genuinely missing entry point) -- it DIAGNOSES "this source has no main; if it is a library, build it
8// through its consumer/gate, not standalone." Reuses kw_eq + dhl_isident/dhl_name_at from nx_doc_kwscan. ORIGINAL
9import "nx_syscalls.nx"
10import "nx_doc_kwscan.nx"
11
12// local substring search (kept self-contained; avoids pulling the seg-store in just for ki_substr).
13func ms_substr(hay: *u8, hlen: i64, needle: *u8, nlen: i64) -> i64 {
14 if nlen <= 0 { return 0-1 }
15 var i: i64 = 0
16 while i + nlen <= hlen {
17 var m: i64 = 1; var j: i64 = 0
18 while j < nlen { if hay[i+j] != needle[j] { m = 0; j = nlen } else { j = j + 1 } }
19 if m == 1 { return i }
20 i = i + 1
21 }
22 return 0-1
23}
24// 1 iff the source defines `func main` (whole-word `func` then the identifier `main`).
25// BUFFER-PASSING VARIANT, added 2026-08-07. The original allocated two 256 B scratch buffers PER CALL and
26// never freed them -- the `return 1` fast path leaks BY CONSTRUCTION. Harmless for the doctor one-shot
27// diagnosis, but nx_gatebuilt_gate calls this once per gate source across 3,184 files: 6,368 mmap syscalls
28// and ~1.6 MB of leaked address space for ONE census.
29// NEVER ALLOCATE IN A HOT LOOP -- PASS THE BUFFER IN. ms_has_main below keeps the original signature,
30// delegates to this, and now FREES what it allocated, so existing callers are behaviourally identical and
31// no longer leak either.
32func ms_has_main_buf(src: *u8, n: i64, nb: *u8, nb2: *u8) -> i64 {
33 var i: i64 = 0
34 while i < n {
35 var isstart: i64 = 0
36 if dhl_isident(src[i] as i64) == 1 {
37 if i == 0 { isstart = 1 } else { if dhl_isident(src[i-1] as i64) == 0 { isstart = 1 } }
38 }
39 if isstart == 1 {
40 let nl: i64 = dhl_name_at(src, n, i, nb)
41 if kw_eq(nb, nl, "func" as *u8) == 1 {
42 var k: i64 = i + nl
43 var sk: i64 = 0
44 while sk == 0 { if k < n { if src[k]==(32 as u8){k=k+1} else { if src[k]==(9 as u8){k=k+1} else {sk=1} } } else {sk=1} }
45 if k < n { if dhl_isident(src[k] as i64) == 1 {
46 let nl2: i64 = dhl_name_at(src, n, k, nb2)
47 if kw_eq(nb2, nl2, "main" as *u8) == 1 { return 1 }
48 } }
49 }
50 i = i + nl
51 } else {
52 i = i + 1
53 }
54 }
55 return 0
56}
57// Original signature PRESERVED so every existing caller (ms_is_library_build_error, the doctor path) is
58// unaffected: allocate, delegate, FREE. The old body leaked BOTH buffers on its `return 1` fast path.
59func ms_has_main(src: *u8, n: i64) -> i64 {
60 let nb: *u8 = sys_mmap(256)
61 let nb2: *u8 = sys_mmap(256)
62 let r: i64 = ms_has_main_buf(src, n, nb, nb2)
63 sys_munmap(nb, 256)
64 sys_munmap(nb2, 256)
65 return r
66}
67// 1 iff the diagnostic is the missing-entry-point assembler error AND the source has no main (= a library built
68// standalone). If the source DOES define main, the same diagnostic means a DIFFERENT problem (a genuinely
69// unresolved entry symbol) -> return 0 so we never misroute it to the library remedy.
70func ms_is_library_build_error(diag: *u8, dlen: i64, src: *u8, n: i64) -> i64 {
71 if ms_substr(diag, dlen, "UNDEFINED label: main" as *u8, 21) < 0 { return 0 }
72 if ms_has_main(src, n) == 1 { return 0 }
73 return 1
74}