aboutsummaryrefslogtreecommitdiff
path: root/src/lib/explain.zc
diff options
context:
space:
mode:
authorGravatar zinc <zinc@downsta.ir> 2026-10-14 20:03:11 +0330
committerGravatar zinc <zinc@downsta.ir> 2026-10-14 20:04:08 +0330
commit6a7fe0898a2bde8abd3c28697e28c9fc0cb1e7b2 (patch)
tree0d4d876bf38f4157f87aee3a091c8d05257620ae /src/lib/explain.zc
parentjit_x64_test: link tests, src and examples into the scratch directory so embe... (diff)
explain-checks: the record writer moves to src/lib/explain.zc, embedded and compiled only in that mode; the genome keeps the call sites and a hook
Diffstat (limited to 'src/lib/explain.zc')
-rw-r--r--src/lib/explain.zc62
1 files changed, 62 insertions, 0 deletions
diff --git a/src/lib/explain.zc b/src/lib/explain.zc
new file mode 100644
index 0000000..aef529b
--- /dev/null
+++ b/src/lib/explain.zc
@@ -0,0 +1,62 @@
+namespace explain;
+include rt;
+// writes the records of zy --explain-checks (format version 1, docs/SPEC.md
+// 4.15.4). the compiler embeds this file and compiles it only in that mode;
+// its analysis calls note once for every bounds check it keeps. no top-level
+// unbounded here: it would carry over into the unit being explained.
+const int NOT_LOOP = 1, NOT_INVARIANT = 2, NOT_LEN = 3, SIZE = 4, PACKED = 5, HOISTED = 6, MAYFREE = 7;
+word src;
+// one character of the unit's source
+int byte(int k) { return (src[k >> 2] >> ((k & 3) * 8)) & 255; }
+// the column (from 1) of the source offset o
+int col(int o) {
+ int k = o;
+ while (k > 0 && byte(k - 1) != 10) k--;
+ return o - k + 1;
+}
+// a json string
+int str(char* s) {
+ int i = 0, ch;
+ rt.put_c(34);
+ while (s[i]) {
+ ch = s[i] & 255;
+ if (ch == 34 || ch == 92) rt.put_c(92);
+ if (ch < 32) rt.put_c(32); else rt.put_c(ch);
+ i++;
+ }
+ rt.put_c(34);
+ return 0;
+}
+int field(char* name) { rt.put_c(44); str(name); rt.put_c(58); return 0; }
+// the first line
+int version() { rt.put_s("{\"version\":1}"); rt.put_c(10); return 0; }
+// one kept check: kind 0 index, 1 pointer; o1 and o2 the source offsets of its [ and ]
+int note(word unit, int kind, int why, int line, int o1, int end_line, int o2, int depth, char* fn, char* file) {
+ src = unit;
+ rt.put_s("{\"file\":"); str(file);
+ field("line"); rt.put_n(line);
+ field("col"); rt.put_n(col(o1));
+ field("end_line"); rt.put_n(end_line);
+ field("end_col"); rt.put_n(col(o2));
+ field("fn"); str(fn);
+ field("kind"); if (kind) str("pointer"); else str("index");
+ field("loop_depth"); rt.put_n(depth);
+ field("reason");
+ if (why == NOT_LOOP) str("index-not-loop-variable");
+ else if (why == NOT_INVARIANT) str("bound-not-invariant");
+ else if (why == NOT_LEN) str("index-not-len-derived");
+ else if (why == SIZE) str("size-unknown");
+ else if (why == PACKED) str("packed-elements");
+ else if (why == MAYFREE) str("pointer-may-be-freed");
+ else str("hoisted-to-loop-entry");
+ field("fix");
+ if (why == NOT_LOOP) str("index with the variable of a loop for (i = 0; i < bound; i++), or mark the code unbounded if the index is known to be in range");
+ else if (why == NOT_INVARIANT) str("the loop body changes the index, the bound or the array, takes an address or makes a closure; keep them fixed inside the loop");
+ else if (why == NOT_LEN) str("bound the loop with len() of the array, or with a constant no larger than its length");
+ else if (why == SIZE) str("the compiler does not know the size of the block; allocate it here with a constant size, or index a fixed array");
+ else if (why == PACKED) str("packed elements are checked by the runtime on every access");
+ else if (why == MAYFREE) str("the loop body calls something that might free or resize the block; move the call out of the loop");
+ else str("checked once when the loop starts, and on every access only if that check fails; nothing to do");
+ rt.put_c(125); rt.put_c(10);
+ return 0;
+}