check_share.pl
Shared check parts: a heap check of an object an earlier check already tested skips the null test, and right after a check of the same object reuses the upper it loaded (docs/perf.md item 17).
A check test lower; jz; cmp lower, ptr; jb; mov -16(lower), up; sub $Size, up; cmp up, ptr; ja (split bounds, local selection) has two parts that depend only on the object: the null test of its lower and the load of its upper. A capability's lower never changes, so once a check of one lower has passed, the null test of any later check of that lower is dead. Its upper does change, when the object is freed (free sets upper = lower), so a loaded upper is reused only while nothing could have freed the object, and only while the register still holds it. Every check stays where it was, with the same tests otherwise, failure label, pointer and lower.
Facts
| Fact | Holds here when |
|---|---|
testedR | a full check of a pointer whose lower is R's passed at a point that dominates here |
upperR | the last instruction that touched the object state is a full check of R's lower, and everything since is transparent |
defReg, Size (machine) | Reg = upper − Size as that check computed it, and no line since wrote Reg or could have freed |
R is a lower root: a single-definition temporary (parameter, phi or instruction) reached from the checked pointer through single-definition flights' lower donors (ir.pl's lower_chain/3). Every name on the way has one definition, so at any point they all dominate, the pointer's lower is R's current one (docs/m7.md, round 4's dominance argument). A pointer with no such root has no fact.
Transfer, merge and invalidators
- A full check of class R skips its null test under
testedR, then addstestedR, and setsupperRif its access is transparent, otherwise noupper. - A transparent instruction keeps both facts; any other instruction clears
upper.testedhas no invalidator: it is a fact about a value. Functions with setjmp are left alone. - A block starts with the
testedfacts at the end of its immediate dominator (--share=nullandupper;blockstarts empty), and noupper. - Transparent (
share_transparent/1): arithmetic, comparisons, conversions, flights, scalar raw accesses and raw pointer loads (fused, reused or renewed). Everything else clearsupper: calls, allocations, getters, pointer stores (ensure-aux may exit), cas, atomic pointer accesses, fences, va_arg, ABI actions and fixed asm. - At the machine level (
shared_uppers/2)defReg, Sizeis cleared by any line that writes Reg, a label, a call, an unconditional jump, a fence, a locked instruction, and any line amd64_contract does not audit. A conditional jump keeps it on its fall-through.
An upper is reused only when both levels agree. Under upperR the check emits the placeholder upper_reuser9, Size, and every check with Size =< 16 marks upper_defReg, Size after computing it; Reg is %r9 for a check whose upper a later one reuses (flight arithmetic between two checks writes %rdx; only calls, pointer stores and cas write %r9) and %rdx, the reference's, otherwise. The machine pass turns each placeholder into the adjustment from the earlier size, or back into the load where def does not hold, and deletes the markers.
--share=none|block|null|upper (NANOFIL_SHARE) selects this; the default is none. Only the split bounds with local selection share.
L, in reverse postorder, walked from the tested facts at the end of its immediate dominator, which comes earlier.
An unreachable block is walked from no facts.
producer(+I, +Is0-Need0, -Is-Need), over a block backwards: a check whose upper a later check reuses (Need) computes it into %r9; every other loaded upper stays in %rdx, as the reference has it, so the register pass still sees %rdx die at the check.
R is P's lower root, following single-definition flights to the first name that is not one. The depth bound stops a cycle of flights in unreachable code, as ir:lower_chain/4's does.
The upper load and borrow test as filc_amd64's split shape emits them under local selection (item 6a: no borrow test for Size =< 16).
From R = upper − Size0 to upper − Size, both at most 16. Neither borrows: a nonnull lower has its 16-byte header below it and upper >= lower (docs/perf.md item 6a).
Line falls through or branches conditionally, writes no part of R, is not a fence or locked, and is audited.