code wiki / _hdl_build / nx_gatelib.nx

nx_gatelib.nx source

↩ module page · 314 lines · 13387 B

1// nx_gatelib.nx -- the shared substrate under the gate-coverage scanners. 2// 3// ===== WHY THIS EXISTS ============================================ 4// 5// nx_gatescan (does anything test this organ?) and nx_gatequality (how much 6// of it does that test reach?) are two questions over the same tree, and 7// they were written as two files that duplicated NINE functions between 8// them: the directory walk, file read, path join, the .nx and gate-name 9// predicates, the ends-with helper, the string length helper, and the 10// gate-to-organ attribution rule. 11// 12// ★I WROTE BOTH COPIES, IN ONE SESSION, WHILE BUILDING TOOLS TO FIND 13// EXACTLY THIS CLASS OF PROBLEM. The tree walk was already named as debt 14// twice -- filed rather than fixed, on the reasoning that extracting it 15// would touch other consumers -- and then the very next organ added a fourth 16// copy. Naming a debt is not paying it, and a filed debt keeps accruing. 17// 18// ===== THE ATTRIBUTION RULE IS THE REAL REASON TO SHARE ========= 19// 20// The mechanical helpers are cheap to duplicate; the attribution rule is 21// not. It decides whether a gate named <organ>_<aspect>_test.nx counts as 22// covering <organ>, and getting it wrong is how the first gatescan run 23// reported 1019 ungated organs when the true figure was 963. With two 24// copies, one can be corrected and the other silently left behind -- and the 25// two tools would then disagree about which organs are gated while both 26// looked healthy. ★A RULE THAT TWO TOOLS MUST AGREE ON BELONGS IN ONE 27// PLACE, and that is a stronger reason than the line count. 28// 29// ===== WHAT IS NOT SHARED ========================================= 30// 31// The gates of both consumers keep their own INDEPENDENT re-derivations of 32// these rules. That is deliberate and is not duplication in the sense that 33// matters: a gate calling the code under test can only confirm it is 34// self-consistent, whereas a second implementation agreeing is evidence. 35// The same reasoning kept nx_arrhenius and nx_stability as separate 36// Arrhenius implementations -- and that decision is what caught the dead 37// primitive, so it has already paid for itself once. 38// 39// license_tier: ORIGINAL 40import "nx_syscalls.nx" 41import "nx_eco_graph.nx" 42import "nx_import_scan.nx" 43const GL_MAGIC_3900: i64 = 3900 44const GL_MAGIC_131072: i64 = 131072 45const GL_MAGIC_1024: i64 = 1024 46 47const GL_FILECAP: i64 = 262144 48 49func gl_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 50 51func gl_join(path: *u8, base_n: i64, nm: *u8) -> i64 { 52 path[base_n] = 47 as u8 53 var o: i64 = base_n + 1 54 var i: i64 = 0 55 while nm[i] != (0 as u8) { path[o] = nm[i]; o = o + 1; i = i + 1 } 56 return o 57} 58 59func gl_read(path: *u8, buf: *u8, cap: i64) -> i64 { 60 let fd: i64 = sys_openat_rd(path) 61 if fd < 0 { return 0 } 62 var total: i64 = 0 63 var go: i64 = 1 64 while go == 1 { 65 let nr: i64 = sys_read(fd, ((buf as i64) + total) as *u8, cap - total) 66 if nr <= 0 { go = 0 } else { total = total + nr; if total >= cap { go = 0 } } 67 } 68 sys_close(fd) 69 return total 70} 71 72func gl_isdotdot(nm: *u8) -> i64 { 73 if nm[0] == (46 as u8) { if nm[1] == (0 as u8) { return 1 } if nm[1] == (46 as u8) { if nm[2] == (0 as u8) { return 1 } } } 74 return 0 75} 76 77func gl_is_nx(nm: *u8, n: i64) -> i64 { 78 if n < 4 { return 0 } 79 if nm[n - 3] != (46 as u8) { return 0 } 80 if nm[n - 2] != (110 as u8) { return 0 } 81 if nm[n - 1] != (120 as u8) { return 0 } 82 return 1 83} 84 85func gl_ends(name: *u8, n: i64, suf: *u8) -> i64 { 86 let m: i64 = gl_len(suf) 87 if m > n { return 0 } 88 var i: i64 = 0 89 while i < m { if name[n - m + i] != suf[i] { return 0 } i = i + 1 } 90 return 1 91} 92 93// Both gate-file conventions in live use in this tree. 94func gl_is_gate_name(name: *u8, n: i64) -> i64 { 95 if gl_ends(name, n, "_test.nx" as *u8) == 1 { return 1 } 96 if gl_ends(name, n, "_gate.nx" as *u8) == 1 { return 1 } 97 return 0 98} 99 100// Identifier character -- letters, digits, underscore. Token boundaries are 101// what stop si_class being "covered" by a reference to si_class_of. 102func gl_is_ident(c: i64) -> i64 { 103 if c >= 97 { if c <= 122 { return 1 } } 104 if c >= 65 { if c <= 90 { return 1 } } 105 if c >= 48 { if c <= 57 { return 1 } } 106 if c == 95 { return 1 } 107 return 0 108} 109 110// ★THE ATTRIBUTION RULE -- the one both tools must agree on. 111// 112// A gate covers organ X if its name starts with X's stem FOLLOWED BY AN 113// UNDERSCORE and ends in _test.nx or _gate.nx. The underscore is the whole 114// rule: without it nx_strategy_test.nx would be credited to nx_str, marking 115// genuinely untested code as covered -- a false negative, which is the 116// dangerous direction for a coverage tool because it produces silence. 117// 118// Walks the split points longest-first so nx_foo_bar_test.nx credits 119// nx_foo_bar where that organ exists and only falls back to nx_foo when it 120// does not. Returns the organ node id, or -1. 121func gl_attribute(g: *EcoGraph, name: *u8, n: i64, buf: *u8) -> i64 { 122 var stem: i64 = 0 - 1 123 if gl_ends(name, n, "_test.nx" as *u8) == 1 { stem = n - 8 } 124 if stem < 0 { if gl_ends(name, n, "_gate.nx" as *u8) == 1 { stem = n - 8 } } 125 if stem <= 0 { return 0 - 1 } 126 var p: i64 = stem 127 var found: i64 = 0 - 1 128 while p > 0 { 129 if found < 0 { 130 if name[p] == (95 as u8) { 131 var i: i64 = 0 132 while i < p { buf[i] = name[i]; i = i + 1 } 133 buf[p] = 46 as u8; buf[p + 1] = 110 as u8; buf[p + 2] = 120 as u8; buf[p + 3] = 0 as u8 134 let idx: i64 = eg_find(g, buf, p + 3) 135 if idx >= 0 { found = idx } 136 } 137 } 138 p = p - 1 139 } 140 return found 141} 142 143// Whole-token search. Used for "does this gate reference this function". 144func gl_has_token(hay: *u8, hn: i64, name: *u8, nl: i64) -> i64 { 145 if nl <= 0 { return 0 } 146 var i: i64 = 0 147 while i + nl <= hn { 148 var m: i64 = 0 149 var ok: i64 = 1 150 while m < nl { if hay[i + m] != name[m] { ok = 0; m = nl } else { m = m + 1 } } 151 if ok == 1 { 152 var lb: i64 = 1 153 var rb: i64 = 1 154 if i > 0 { if gl_is_ident(hay[i - 1] as i64) == 1 { lb = 0 } } 155 if i + nl < hn { if gl_is_ident(hay[i + nl] as i64) == 1 { rb = 0 } } 156 if lb == 1 { if rb == 1 { return 1 } } 157 } 158 i = i + 1 159 } 160 return 0 161} 162 163// ★THE TREE WALK, AT LAST IN ONE PLACE. 164// 165// This existed as egb_walk in nx_eco_graph_build and tr_walk in nx_dr_tree 166// before either scanner was written, and both of those live in modules that 167// own a main(), so neither could be imported by a second entry point. That 168// is why it kept being copied rather than shared -- a structural reason, not 169// an oversight, and the fix is a module with no main() rather than a note 170// promising to do it later. 171// 172// Two behaviours are folded into one walk so the callers do not need two: 173// edges != 0 -- intern each .nx and add its imports as graph edges 174// pathoff != 0 -- record where each basename was found, first path wins 175// A caller wanting only one passes 0 for the other, and pays nothing for it. 176// st[0] counts files visited. 177func gl_walk(g: *EcoGraph, path: *u8, path_n: i64, filebuf: *u8, aoff: *i64, alen: *i64, 178 st: *i64, edges: i64, patharena: *u8, pathoff: *i64, used: *i64, arenacap: i64) -> i64 { 179 if path_n > GL_MAGIC_3900 { return 0 } 180 path[path_n] = 0 as u8 181 let fd: i64 = sys_openat_rd(path) 182 if fd < 0 { return 0 } 183 let dbuf: *u8 = sys_mmap(GL_MAGIC_131072) 184 var go: i64 = 1 185 while go == 1 { 186 let nr: i64 = sys_getdents64(fd, dbuf, GL_MAGIC_131072) 187 if nr <= 0 { go = 0 } else { 188 var off: i64 = 0 189 while off < nr { 190 let rec: *u8 = ((dbuf as i64) + off) as *u8 191 let ty: i64 = dirent_type(rec) 192 let nm: *u8 = dirent_name(rec) 193 if gl_isdotdot(nm) == 0 { 194 let cs: i64 = gl_join(path, path_n, nm) 195 if ty == 4 { 196 gl_walk(g, path, cs, filebuf, aoff, alen, st, edges, patharena, pathoff, used, arenacap) 197 } else { 198 let nmn: i64 = gl_len(nm) 199 if gl_is_nx(nm, nmn) == 1 { 200 let fnode: i64 = eg_intern(g, nm, nmn) 201 if fnode >= 0 { 202 path[cs] = 0 as u8 203 if (pathoff as i64) != 0 { 204 if pathoff[fnode] == 0 { 205 if used[0] + cs + 2 < arenacap { 206 var q: i64 = 0 207 while q <= cs { patharena[used[0] + q] = path[q]; q = q + 1 } 208 pathoff[fnode] = used[0] + 1 209 used[0] = used[0] + cs + 2 210 st[0] = st[0] + 1 211 } 212 } 213 } 214 if edges != 0 { 215 let flen: i64 = gl_read(path, filebuf, GL_FILECAP) 216 if flen > 0 { 217 if (pathoff as i64) == 0 { st[0] = st[0] + 1 } 218 let ic: i64 = nis_scan(filebuf, flen, aoff, alen, GL_MAGIC_1024) 219 var k: i64 = 0 220 while k < ic { 221 let dep: *u8 = ((filebuf as i64) + aoff[k]) as *u8 222 let dnode: i64 = eg_intern(g, dep, alen[k]) 223 if dnode >= 0 { eg_add_edge(g, fnode, dnode) } 224 k = k + 1 225 } 226 } 227 } 228 } 229 } 230 } 231 } 232 off = off + dirent_reclen(rec) 233 } 234 } 235 } 236 sys_close(fd) 237 return 0 238} 239 240// ===== SOURCE INSPECTION -- shared by the coverage and duplication tools === 241// 242// Both nx_gatequality (which functions does a gate reach?) and nx_dupfunc 243// (which function bodies are written twice?) need to split a file into its 244// top-level definitions and to ignore comments while doing it. Putting them 245// here rather than in either consumer is the point: the tool that finds 246// duplication must not be built by duplicating. 247 248func gl_extract_funcs(src: *u8, n: i64, names: *u8, offs: *i64, lens: *i64, spans: *i64, cap: i64) -> i64 { 249 var cnt: i64 = 0 250 var i: i64 = 0 251 var w: i64 = 0 252 var atline: i64 = 1 253 while i < n - 6 { 254 if atline == 1 { 255 if src[i] == (102 as u8) { 256 if src[i+1] == (117 as u8) { 257 if src[i+2] == (110 as u8) { 258 if src[i+3] == (99 as u8) { 259 if src[i+4] == (32 as u8) { 260 var p: i64 = i + 5 261 var st: i64 = p 262 while p < n { if gl_is_ident(src[p] as i64) == 1 { p = p + 1 } else { p = n } } 263 // recompute end properly 264 p = st 265 while p < n { 266 if gl_is_ident(src[p] as i64) == 1 { p = p + 1 } else { 267 if cnt < cap { 268 let l: i64 = p - st 269 if l > 0 { 270 if l < 96 { 271 offs[cnt] = w 272 lens[cnt] = l 273 spans[cnt] = i 274 var q: i64 = 0 275 while q < l { names[w + q] = src[st + q]; q = q + 1 } 276 w = w + l 277 cnt = cnt + 1 278 } 279 } 280 } 281 p = n 282 } 283 } 284 } 285 } 286 } 287 } 288 } 289 } 290 if src[i] == (10 as u8) { atline = 1 } else { atline = 0 } 291 i = i + 1 292 } 293 return cnt 294} 295 296func gl_strip_comments(src: *u8, n: i64, dst: *u8) -> i64 { 297 var i: i64 = 0 298 var w: i64 = 0 299 var incomment: i64 = 0 300 while i < n { 301 let c: i64 = src[i] as i64 302 if incomment == 1 { 303 if c == 10 { incomment = 0; dst[w] = 10 as u8; w = w + 1 } 304 } else { 305 var skip: i64 = 0 306 if c == 47 { 307 if i + 1 < n { if src[i+1] == (47 as u8) { incomment = 1; skip = 1 } } 308 } 309 if skip == 0 { dst[w] = src[i]; w = w + 1 } 310 } 311 i = i + 1 312 } 313 return w 314}