diff options
| author | 2026-10-14 20:03:11 +0330 | |
|---|---|---|
| committer | 2026-10-14 20:04:08 +0330 | |
| commit | 6a7fe0898a2bde8abd3c28697e28c9fc0cb1e7b2 (patch) | |
| tree | 0d4d876bf38f4157f87aee3a091c8d05257620ae /src/lib | |
| parent | jit_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')
| -rw-r--r-- | src/lib/explain.zc | 62 |
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; +} |
