diff options
| author | 2026-10-14 15:52:01 +0330 | |
|---|---|---|
| committer | 2026-10-14 15:52:01 +0330 | |
| commit | 7c54e48bca711a6596be4bb17fcaa4330b9928e3 (patch) | |
| tree | fd426755976847e368fe298db1c7c757f58799b9 /src/runtime/rt.zc | |
| parent | todo: 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.zc | 119 |
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; |
