code wiki / (root) / nx_incclosure_lib.nx

nx_incclosure_lib.nx source

↩ module page · 129 lines · 6412 B

1// nx_incclosure_lib.nx -- ONE OWNER for import-closure NAME->PATH resolution (2026-08-25, rung LN10). 2// 3// WHY THIS IS A LIB AND NOT A SECOND COPY. 4// nx_inc_compile (the per-module incremental build cache) must answer, for each module named in a 5// recorded closure, "is the file the compiler would read still the same bytes?" -- and it can only ask 6// that of a PATH. nx_closurehash already owns that resolution and the order is load-bearing: 7// runtime/_hdl_build/<name> FIRST (buildrun compiles that twin when both exist) 8// runtime/<name> SECOND 9// runtime/<subdir>/<name> a getdents64 sweep, looped until the kernel says stop 10// A SECOND RESOLVER THAT DISAGREES BY ONE PATH HASHES THE WRONG FILE, REPORTS A CACHE HIT, AND SERVES A 11// STALE ARTIFACT. That is a silent miscompile -- the worst class this estate tracks, and the one a cache 12// exists to risk. So the resolver is EXTRACTED here and BOTH organs call it: disagreement is impossible 13// by construction rather than by discipline, which is the estate's own standing answer whenever two 14// organs must agree. nx_closurehash keeps its function names and DELEGATES, so its callers are untouched 15// and its neutrality is provable by nx_behaveprobe (live vs staged) rather than asserted. 16// 17// license_tier: ORIGINAL No hw writes (Rule 26). 18import "nx_syscalls.nx" 19 20// Widths and bounds published HERE so a consumer inherits the ruler's calibration instead of inventing 21// its own. ICL_MAXF is the closure-entry bound nx_closurehash REFUSES past rather than publishing a 22// partial hash; a consumer that stores closure rows inherits the same bound for the same reason. 23const ICL_MAXF: i64 = 1024 24const ICL_NAMEW: i64 = 192 25const ICL_SHA_HEX: i64 = 64 26const ICL_PATHW: i64 = 1024 27const ICL_DIRBUF: i64 = 262144 // getdents64 window; the sweep LOOPS, so this is a window not a cap 28const ICL_SWEEPW: i64 = 512 // "<root>/runtime" scratch 29const ICL_DOT: i64 = 46 30const ICL_DT_DIR: i64 = 4 31const ICL_DENT_RECLEN_OFF: i64 = 16 32const ICL_DENT_NAME_OFF: i64 = 19 33const ICL_DENT_TYPE_OFF: i64 = 18 34 35func icl_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 36func icl_cat(d: *u8, o: i64, s: *u8) -> i64 { var x: i64 = o; var i: i64 = 0; while s[i] != (0 as u8) { d[x] = s[i]; x = x + 1; i = i + 1 } return x } 37func icl_seq(a: *u8, b: *u8) -> i64 { 38 var i: i64 = 0 39 var go: i64 = 1 40 while go == 1 { 41 let x: i64 = a[i] as i64 42 let y: i64 = b[i] as i64 43 if x != y { return 0 } 44 if x == 0 { go = 0 } 45 i = i + 1 46 } 47 return 1 48} 49func icl_exists(p: *u8) -> i64 { let fd: i64 = sys_openat_rd(p); if fd < 0 { return 0 } sys_close(fd); return 1 } 50 51// 32-byte digest -> 64 lowercase hex chars at out, NUL-terminated. ONE encoder for both consumers. 52func icl_hex(dig: *u8, out: *u8) -> i64 { 53 var i: i64 = 0 54 while i < 32 { 55 let v: i64 = dig[i] as i64 56 let hi: i64 = (v >> 4) & 15 57 let lo: i64 = v & 15 58 if hi < 10 { out[i*2] = (48+hi) as u8 } else { out[i*2] = (87+hi) as u8 } 59 if lo < 10 { out[i*2+1] = (48+lo) as u8 } else { out[i*2+1] = (87+lo) as u8 } 60 i = i + 1 61 } 62 out[ICL_SHA_HEX] = 0 as u8 63 return ICL_SHA_HEX 64} 65 66// Resolve a bare source name (e.g. "nx_syscalls.nx") to a path under root. Returns 1 and fills `out`, 67// or 0 when it resolves nowhere. EXTRACTED VERBATIM from nx_closurehash.chresolve 2026-08-25 -- the 68// comments below are its own, kept because they are the evidence for the order: 69// 70// MEASURED 2026-08-07: a two-path resolver (_hdl_build, runtime) reported unresolved=2 on nx_mgmt_api -- 71// nx_h2c_p256.nx and nx_voprf.nx -- while that exact target COMPILES CLEAN. They live in `runtime/hub/`, 72// a THIRD search path I did not know about. *AN INCOMPLETE RESOLVER REPORTS A HEALTHY TREE AS BROKEN*. 73// Hardcoding the directory list would drift from nx_cc the first time a lane adds a folder, so after the 74// two ORDERED paths this ENUMERATES the subdirectories of runtime/ and tries each. The order still 75// matters and is preserved: _hdl_build first, then runtime, then any subdir -- so a name present in two 76// places still resolves to the one the compiler would read. 77func icl_resolve(root: *u8, name: *u8, out: *u8) -> i64 { 78 var o: i64 = icl_cat(out, 0, root) 79 o = icl_cat(out, o, "/runtime/_hdl_build/" as *u8) 80 o = icl_cat(out, o, name) 81 out[o] = 0 as u8 82 if icl_exists(out) == 1 { return 1 } 83 var o2: i64 = icl_cat(out, 0, root) 84 o2 = icl_cat(out, o2, "/runtime/" as *u8) 85 o2 = icl_cat(out, o2, name) 86 out[o2] = 0 as u8 87 if icl_exists(out) == 1 { return 1 } 88 // subdirectory sweep -- getdents64 looped until the kernel says stop, because one call returns only 89 // what fits the buffer and a partial listing here would silently under-resolve again. 90 let rp: *u8 = sys_mmap(ICL_SWEEPW) 91 var ro: i64 = icl_cat(rp, 0, root) 92 ro = icl_cat(rp, ro, "/runtime" as *u8) 93 rp[ro] = 0 as u8 94 let dfd: i64 = sys_openat_rd(rp) 95 if dfd < 0 { return 0 } 96 let db: *u8 = sys_mmap(ICL_DIRBUF) 97 var found: i64 = 0 98 var go: i64 = 1 99 while go == 1 { 100 let n: i64 = sys_getdents64(dfd, db, ICL_DIRBUF) 101 if n <= 0 { go = 0 } else { 102 var p: i64 = 0 103 while p < n { 104 let reclen: i64 = (db[p+ICL_DENT_RECLEN_OFF] as i64) | ((db[p+ICL_DENT_RECLEN_OFF+1] as i64) << 8) 105 if reclen <= 0 { p = n } else { 106 let dtype: i64 = db[p+ICL_DENT_TYPE_OFF] as i64 107 let nm: *u8 = ((db as i64) + p + ICL_DENT_NAME_OFF) as *u8 108 var ok: i64 = 0 109 if dtype == ICL_DT_DIR { ok = 1 } 110 if (nm[0] as i64) == ICL_DOT { ok = 0 } 111 if found == 1 { ok = 0 } 112 if ok == 1 { 113 var o3: i64 = icl_cat(out, 0, root) 114 o3 = icl_cat(out, o3, "/runtime/" as *u8) 115 o3 = icl_cat(out, o3, nm) 116 o3 = icl_cat(out, o3, "/" as *u8) 117 o3 = icl_cat(out, o3, name) 118 out[o3] = 0 as u8 119 if icl_exists(out) == 1 { found = 1 } 120 } 121 p = p + reclen 122 } 123 } 124 } 125 } 126 sys_close(dfd) 127 if found == 1 { return 1 } 128 return 0 129}