Skip to content
UNDER THE HOOD

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.