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}