From d55b5fdf92a674a6495878f97a09e95bafdcba3f Mon Sep 17 00:00:00 2001 From: zinc Date: Wed, 14 Oct 2026 15:58:58 +0330 Subject: bounds checking: port agent/bounds-checking onto main 0879d0a; unbounded main in o3call, o3 tests expect no check, boundsrules, bounds_fixed, bounds_heap and zc0unbounded tests --- src/runtime/rt.zc | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) (limited to 'src/runtime/rt.zc') diff --git a/src/runtime/rt.zc b/src/runtime/rt.zc index 4e52cb5..8c26161 100644 --- a/src/runtime/rt.zc +++ b/src/runtime/rt.zc @@ -1,6 +1,9 @@ namespace rt; include run; +// the runtime pokes at headers and raw memory on purpose and runs at full speed: no index bounds checks +unbounded; + // ============================================================================ // rt.zc: the runtime half of the genome. memory map, i/o, arithmetic helpers, // the heap (size-class free lists) and the mark-sweep collector. @@ -84,6 +87,13 @@ enhance ishl(a, b) micro "a = shl a b"; enhance lfa(k) micro "r1 = ld 589824; k = add r1 k"; // 1 when i is outside 0..n-1 (the unsigned compare also catches negative i), else 0. enhance outside(i, n) micro "i = ltu i n; i = xor i 1"; +// the index check of a pointer, as one instruction: 1 when p is the start of a heap +// block (its check word p[-3] is p ^ MAGIC) and the byte or word offset i * m is not +// inside the block's payload (q units per word: 1 for word elements, 8 for packed +// ones), else 0. a load outside memory reads 0, so any p is safe to ask about. the +// payload is paylen (p[-5] - HDR words, at most the size an o3 raw block was asked for, +// which is the complement of its type word p[-1] when that is negative). see bchk. +enhance bnd(p, i, m, q) micro "r6 = sub p 3; r6 = ld r6; r7 = xor p 0x2B7E1516; r6 = eq r6 r7; r7 = sub p 5; r7 = ld r7; r7 = sub r7 5; r8 = sub p 1; r8 = ld r8; r8 = not r8; r7 = minu r7 r8; r7 = mul r7 q; r8 = mul i m; r8 = ltu r8 r7; r8 = xor r8 1; r0 = and r6 r8"; // ---- bit operations and float conversions ---- // these use single machine instructions where the cpu has them. @@ -1100,6 +1110,27 @@ word dat(word h, int i) { if (i < 0 || i >= h[0]) oob(i, h[0]); return h[2] + i * h[3]; } +// the bounds check of p[i] on a pointer (or an untyped word), emitted by the compiler +// in front of the access. a pointer has no length, but a pointer to the start of +// a heap block does have one: the words the block holds (paylen). for any +// other pointer (the middle of a block, a frame, a global, memory managed by hand) +// nothing is known and this returns at once. sz is the element size in words, or +// minus the element size in bytes for packed elements. the length is the block's +// payload, which a buddy block rounds up to a power of two; an o3 raw block is exact. +int bchk(word p, int i, int sz) { + int n; + if (p < HEAP_LO + HDR || p >= hbump || p[-3] != (p ^ MAGIC)) return 0; + n = paylen(p); + if (sz > 0) { + if (i >= 0 && i < n && i * sz < n) return 0; + oob(i, (n + sz - 1) / sz); + return 0; + } + sz = 0 - sz; n = n * 8; + if (i >= 0 && i < n && i * sz < n) return 0; + oob(i, (n + sz - 1) / sz); + return 0; +} // add x to the end of a growable array h, growing its items when they are full. the // array owns the items, so x is handed to it (tagged d). unchecked int dpush(word h, .d x) { -- cgit v1.2.3