aboutsummaryrefslogtreecommitdiff
path: root/src/runtime/rt.zc
diff options
context:
space:
mode:
authorGravatar zinc <zinc@downsta.ir> 2026-10-14 15:52:01 +0330
committerGravatar zinc <zinc@downsta.ir> 2026-10-14 15:52:01 +0330
commit7c54e48bca711a6596be4bb17fcaa4330b9928e3 (patch)
treefd426755976847e368fe298db1c7c757f58799b9 /src/runtime/rt.zc
parenttodo: record each agent's state at the end of the session and the merge order (diff)
line tables and backtraces: port agent/line-tables onto main, op 56 on the x86-64 jit, docs, measurements, tests; default genome reservation 0x68000
Diffstat (limited to 'src/runtime/rt.zc')
-rw-r--r--src/runtime/rt.zc119
1 files changed, 109 insertions, 10 deletions
diff --git a/src/runtime/rt.zc b/src/runtime/rt.zc
index ef5a5c6..4e52cb5 100644
--- a/src/runtime/rt.zc
+++ b/src/runtime/rt.zc
@@ -34,20 +34,20 @@ include run;
// across targets and image sizes.
// @map-rt
-const MW = 0x1D5000;
+const MW = 0x1DD000;
const CODE_END = 0x80000;
-const HEAP_LO = 0x1D5000;
-const HEAP_HI = 0x235000;
+const HEAP_LO = 0x1DD000;
+const HEAP_HI = 0x23D000;
const HEAP_GROW = 0x1;
const ZFP = 0x90000;
const GORG = 0x114100;
-const FBOT = 0x174100;
-const FTOP = 0x1B4100;
-const RS0 = 0x1B4100;
-const MARK0 = 0x1C4100;
+const FBOT = 0x17C100;
+const FTOP = 0x1BC100;
+const RS0 = 0x1BC100;
+const MARK0 = 0x1CC100;
const MARKN = 0x8000;
-const DSLO = 0x1CC100;
-const DSHI = 0x1D4FF0;
+const DSLO = 0x1D4100;
+const DSHI = 0x1DCFF0;
const MAGIC = 0x2B7E1516;
// @end-map
@@ -670,6 +670,7 @@ int scan_ptr(int p, int n) {
// pointer (so this can run at all) and stops
int frame_overflow() {
put_s("zy: frame stack overflow\n");
+ trace_at(1);
halt();
return 0;
}
@@ -714,6 +715,7 @@ word frame_leave() {
int oom() {
outmode = 0; outgrow = 0;
put_s("zc: out of heap memory\n");
+ trace_at(1);
halt();
return 0;
}
@@ -825,6 +827,7 @@ unchecked int free(.d p) {
if (isobj(p) && (p[-4] & F_SHARE)) return 0;
if ((dbg & 1) && quar(p)) {
put_s("zy: double free from line "); put_n(site_line_of(p)); put_c(10);
+ trace_at(1);
return 0;
}
if (isobj(p)) {
@@ -840,6 +843,7 @@ unchecked int recycle(.d p) {
word b; int n, i = 0; word w;
if ((dbg & 1) && quar(p)) {
put_s("zy: double free from line "); put_n(site_line_of(p)); put_c(10);
+ trace_at(1);
return 0;
}
if (!isobj(p) || (p[-4] & F_SHARE)) return 0;
@@ -986,6 +990,99 @@ unchecked int dgrow(word h, int need) {
free(old);
return 0;
}
+// ---- line tables and backtraces ----
+
+// with lines on (the default for a program) the compiler records, for every
+// function it compiles, a line block among the code: [next, entry, end, name,
+// file, count, then count words of offset << 32 | line]. name and file are
+// packed byte strings (length word, then the bytes). each word after the
+// count says that the code from entry + offset up to the next word's offset
+// came from that source line. blocks are chained through line_blocks, newest
+// first. a function nested in another (a closure) has its own block and the
+// innermost block holding an address is the one that names it. the compiler
+// fills line_blocks when it compiles in the same memory as the program; the
+// compiler of an image writes the head into the image's own line_blocks.
+// frame_namer, when the compiler is resident, is a function that returns the
+// name (a zero-terminated character string) of the function holding an
+// address, for frames in code without lines.
+word line_blocks, frame_namer;
+// op 56 of the syscall gate: the return stack pointer, the word after the top return address.
+const int SYS_RSP = 56;
+// a trace shows this many of the innermost frames, then skips to the last few
+const int TRACE_HEAD = 24, TRACE_TAIL = 4;
+// the line block of the innermost function whose code holds address a, or 0
+word line_block_of(int a) {
+ word b = line_blocks, best = 0;
+ while (b) {
+ if (b[1] <= a && a < b[2] && (!best || b[1] > best[1])) best = b;
+ b = b[0];
+ }
+ return best;
+}
+// the source line of address a within its line block b: the last recorded line at or before it
+int line_of(word b, int a) {
+ int i = 0, off = a - b[1], n = b[5], line = 0; word w;
+ while (i < n) {
+ w = b[6 + i];
+ if ((w >> 32) > off) break;
+ line = w & 0xFFFFFFFF;
+ i++;
+ }
+ return line;
+}
+// write the hexadecimal form of a number
+int put_hex(int n) {
+ char[20] b; int i = 0, d;
+ put_s("0x");
+ if (!n) { put_c('0'); return 0; }
+ while (n && i < 16) { d = n & 15; if (d < 10) b[i++] = '0' + d; else b[i++] = 'a' + d - 10; n = n >> 4; }
+ while (i) put_c(b[--i]);
+ return 0;
+}
+// write the line for one frame, given the return address a that is on the
+// return stack: its function, file and line when the compiler recorded them,
+// else the address and the nearest name. code of the runtime and the compiler
+// that has no lines is left out, as are words that are not return addresses.
+// returns 1 when a line was written.
+int trace_frame(int a) {
+ word b; char* name = 0;
+ if (a <= 0) return 0;
+ b = line_block_of(a - 1);
+ if (b) {
+ put_s(" at "); put_bs(b[4]); put_c(':'); put_n(line_of(b, a - 1)); put_s(" in "); put_bs(b[3]); put_c(10);
+ return 1;
+ }
+ if (a >= GORG && a < GORG[6]) return 0;
+ if (!callable_code(a)) return 0;
+ if (frame_namer) name = frame_namer(a);
+ put_s(" at "); put_hex(a);
+ if (name) { put_s(" in "); put_s(name); }
+ put_c(10);
+ return 1;
+}
+// print the call stack, innermost call first, one line per frame, from the
+// return stack. skip is how many words at the top of the stack belong to the
+// runtime's own calls and are left out: a function that calls trace_at passes 1
+// to leave out the return into itself. this reads the pointer with the gate
+// store inline, since a call to syscall would add a frame of its own.
+int trace_at(int skip) {
+ word g = ZFP + 2; int r, lo, shown = 0, left; word rs;
+ g[1] = 0; g[2] = 0; g[3] = 0; g[0] = SYS_RSP;
+ r = g[0];
+ lo = return_low;
+ if (r <= lo || r - lo > 0x7FFFFFF) return 0;
+ r = r - skip;
+ while (r > lo && shown < TRACE_HEAD) { r--; rs = r; if (trace_frame(rs[0])) shown++; }
+ left = r - lo;
+ if (left > TRACE_TAIL + 8) {
+ put_s(" ... "); put_n(left - TRACE_TAIL); put_s(" more frames\n");
+ r = lo + TRACE_TAIL;
+ }
+ while (r > lo) { r--; rs = r; trace_frame(rs[0]); }
+ return 0;
+}
+// print the current call stack without stopping
+int trace() { trace_at(1); return 0; }
// the out-of-range report for T[?] indexing (dat, dpop)
int oob(int i, int n) {
put_s("zy: index ");
@@ -993,6 +1090,7 @@ int oob(int i, int n) {
put_s(" out of range (length ");
put_n(n);
put_s(")\n");
+ trace_at(1);
halt();
return 0;
}
@@ -1276,9 +1374,10 @@ int checkptr(int w) {
if (!(dbg & 1) || !w) return 0;
if (w < HEAP_LO || w >= hstop) return 0;
p = obj_base(w);
- if (!p) { put_s("zy: bad heap pointer\n"); halt(); return 0; }
+ if (!p) { put_s("zy: bad heap pointer\n"); trace_at(1); halt(); return 0; }
if (!(p[-4] & 1)) {
put_s("zy: use after free from line "); put_n(site_line_of(p)); put_c(10);
+ trace_at(1);
halt();
}
return 0;