Guards and the shadow
The runtime keeps one shadow byte per 16-byte granule (RFC 0035 §2.2 and
amendment 3, RFC 0036 §5; the values are in
weavec_rt.h):
| Value | Meaning |
|---|---|
0x00 |
all 16 bytes addressable |
0x01–0x0F |
the first k bytes addressable, the rest not |
0xF1 |
a frame’s left redzone |
0xF2 |
a redzone between a frame’s objects |
0xF3 |
a frame’s right redzone |
0xF4 |
a dynamic stack object’s redzone |
0xF7 |
poisoned by the program through ASan’s interface |
0xF8 |
a tracked local out of scope |
0xF9 |
a tracked global’s redzone |
0xFA |
a heap slot after its object; also committed arena slots not yet handed out, and the granule before each class region |
0xFD |
a released heap object |
0xFE |
the lowest 64 KiB (the null page) |
The shadow of an address a is the byte at base + ((a >> 4) & mask);
GuardExpand reads base and mask from the descriptor __weavec_rt_shadow
once per function. The runtime reserves the shadow for a window of the
address space at start-up, without backing: the whole user address space on
Linux (the width the stack’s address gives), and 2^42 bytes on Darwin,
where the kernel’s cost of a reservation grows with its size. An address
above the window aliases a lower one; the fast path may then fail
spuriously, and the slow path, which knows the window, lets it pass. When
the reservation fails, mask is 0 and base points to zero bytes: every
guard passes, and the runtime says so once on standard error.
Fast paths (RFC 0035 amendment 4). A guard whose bytes lie in one
granule is three instructions: the shift and mask, the shadow load, and a
branch on a nonzero byte, weighted cold. The partial-granule test (whether
the access ends within the first k bytes) is on the slow side of the
branch, before the call. An access of up to 16 bytes that may cross a
granule loads the two shadow bytes it may touch at once; a range of
constant length up to 113 bytes loads eight with a constant mask. A nonzero
byte only sends the access to the exact check. Any other range calls
__weavec_rt_range(a, n, site), which scans the shadow.
The slow path. __weavec_rt_guard(a, w, site) decides exactly (the
window, partial granules, the poison kind) and names a failure by the
shadow value that caused it (see Reports). site is a constant
__weavec_rt_site record {file, line, column, flags} built from the
instruction’s location (the flags say write, report mode, a monitor, and
whether a null string is accepted), or null. The slow paths are
preserve_all on AArch64 and x86-64, so an inline guard costs the code
around it no spills.
Null bases (RFC 0035 §2.4). An access whose address is p + x, with
x not a constant below 4,096 and p not known to be non-null, also tests
p: a null base takes the slow path, which reports a null dereference. A
constant offset below 4,096 from a null base faults on the null page, and a
guard on an address in the lowest 64 KiB fails as a null dereference
through its shadow, even when the optimiser moved the access to the other
arm of a select.