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}