Fil-C on Nix

Filnix wiki

mbrock/filnix/nanofil

check_reuse.pl

Check reuse: a heap access check's proof covers later scalar accesses through the same pointer or a related one, in its block and along CFG edges (docs/m7.md).

Pizlonation states one check per access, right before it. This pass deletes a check when an earlier proof already establishes what it would: the same pointer operand, or one related to it (both constant offsets, by single-definition flights with the same lower donor, from one root; round 3), at least its range, shifted by their distance, and rights, and nothing in between that could free the object or redefine either temporary. The covered access then reads the earlier proof. Nothing is moved, widened or added, so every remaining check fails exactly where it failed before.

Facts

FactMeans
availP, Q, Lo, Hi, Rights, AligntmpQ is a proof of P that P+[Lo, Hi) allows Rights at alignment Align

Blocks are walked once, in order. A block's entry facts are those every incoming edge carries: a plain edge from a block already walked carries its exit facts, a poll edge or an edge from a later block none. Only facts whose proof has one definition and whose pointer has at most one cross an edge, and a phi kills the facts naming its destination. A kept heap check adds its fact once its first access has run. An instruction that is transparent/1 keeps the facts that do not mention the temporary it defines; any other instruction (a call, allocation, getter, pointer access, setjmp, va_arg, ...) removes them all.

Renewal (round 4), range guards (round 5) and hoisted bounds (docs/bounds.md: a limit of a loop's invariant root, read by check_limit in the loop) build on these facts below.

The pass is selected with --checks=reuse; the reference path keeps every check. ir.pl's validator accepts the result by its own rule, and reuse_refines/2 checks that the only change is deleted checks whose access now reads a proof of the same or a related operand, at an offset changed by exactly their distance.

useslists
usesassoc
usesordsets
usespairs
usesirop_kind/2, pointer_memory_effects/2, access_size_align/3, temp_defs/3, single_definitions/3, guard_chain/3, same_pointer/3, pointer_distance/4, lower_chain/3, repeated_getters/4, module_symbols/2
usescfginst_def/2, inst_uses/2, func_cfg/2, cfg_idoms/2, dominates/3, natural_loops/3, dom_children/3, dom_intervals/3, dominates_by_interval/3
usesprovenancecode_items/2, context_records/3, context_index/2, with_context/3, remap_sites/3
reuse_checks+M0, -Mdet

M is the checked module M0 with each heap check that an available proof covers deleted, and its access reading that proof. Context sites follow the instructions; aliasQ, tmpQ1, none, Os records that the access of the deleted proof Q reads Q1.

66reuse_checksM0, M ←
reuse_checksplain, M0, M
reuse_checks+Mode, +M0, -Mdet

Mode plain is reuse_checks/2. Mode forward (docs/forward.md) also lets a pointer load with the heap load effects read an earlier proof, and its own check make a fact, and deletes a check that the hull of the available facts of its object covers, reading the first of them whose alignment suffices. Mode getter is forward where a repeated getter copy (repeated_getters/4) keeps the facts.

76reuse_checksMode, moduleItems0, moduleItems ←
code_itemsItems0, Code
context_indexItems0, Contexts
module_symbolsmoduleItems0, Symbols
maplistreused_itemMode−Symbols, Contexts, Code, Groups
appendGroups, Items
82reused_itemMode0−Symbols, All, funcN, L, R, Ps, Bs0, Items ←
!
names_infoPs, Bs0, Info
incomingBs0, Incoming
empty_assocExits0
The validator has no hull in a function with a setjmp (ir.pl's
stage_sources/3), nor does the pass there; pointer loads still.
empty_assocNoRep
memberchkMode0, forward, getter
memberblock_, _, Is, _, Bs0
memberlet_, _, setjmp_, _, Is
→
Mode = pointers
Rep = NoRep
Mode0 ≡ getter
→
The validator's own relation, so the pass keeps no fact it refuses.
Mode = forward
single_definitionsPs, Bs0, Once
repeated_gettersSymbols, Bs0, Once, Rep
else
Mode = Mode0
Rep = NoRep
reused_blocksBs0, Mode−Rep, Info, Incoming, Exits0, Bs, Map, Reused
context_recordsAll, N, Rs0
Rs0 ≡ []
→
Rs = []
else
remap_sitesRs0, Map, Rs1
A proof read at another distance is not Q's equal: no alias.
findallaliasQ, tmpQ1, none, Os,
memberQ−Q1−0, Reused
memberchkvalueQ, _, Os, Rs0
→
true
else
Os = []
,
Aliases
appendRs1, Aliases, Rs
with_contextfuncN, L, R, Ps, Bs, Rs, Items
108reused_item_, _, Item, Item

Blocks in order. Entry facts are those every incoming edge carries (exit facts on a plain edge from a block already walked; none on a poll edge or from a later block), kept only when the proof and its pointer are defined once in the function, less those a phi redefines.

114reused_blocks[], _, _, _, _, [], [], []
115reused_blocksblockL, Phis, Insts0, J|Bs0, Mode−Rep, Info, Incoming, Exits0, blockL, Phis, Insts, J|Bs, Map, Reused ←
get_assocL, Incoming, Edges
→
true
else
Edges = []
maplistcarriedExits0, Edges, Sets
commonSets, Facts0
includesingle_namesInfo, Facts0, Facts1
foldlphi_kills, Phis, Facts1, Facts
facts_stateInfo, Facts, St
reused_instsInsts0, Mode, Rep, Info, St, L−0−0, Insts, Map0, Reused0, stExit, _, _
put_assocL, Exits0, Exit, Exits
reused_blocksBs0, Mode−Rep, Info, Incoming, Exits, Bs, Map1, Reused1
appendMap0, Map1, Map
appendReused0, Reused1, Reused
128incomingBlocks, Incoming ←
findallL−From−Polled,
memberblockFrom, _, _, Jump, Blocks
targetJump, T
T = pollL
→
Polled = yes
else
L = T
Polled = no
,
Pairs
empty_assocI0
foldladd_incoming, Pairs, I0, Incoming
target
137gotoTT
138branch_, T, _T
139branch_, _, TT
141add_incomingL−E, I0, I ←
get_assocL, I0, Es
→
true
else
Es = []
put_assocL, I0, E|Es, I
145carried_, _−yes, [] ←
!
146carriedExits, From−no, Facts ←
get_assocFrom, Exits, Facts
→
true
else
Facts = []
149common[], []
150commonS|Ss, Facts ←
maplistsort, Ss, Os
includeeverywhereOs, S, Facts
151everywhereOs, F ←
forallmemberO, Os, ord_memberchkF, O

Within a block the facts are st(Facts, Names, ByRoot): Facts newest first, as before; Names every name a fact mentions (perhaps more); ByRoot the facts of each root (base_of/4), newest first, each with its pointer's offset from the root, K-avail(...). The candidates for a check are exactly ByRoot's list for its pointer's root, in Facts' order, and a definition scans Facts only when Names has its name: a long block costs no more per check than its facts of one root.

160facts_stateInfo, Facts, stFacts, Names, ByRoot ←
empty_assocE
foldlavail_names, Facts, E, Names
findallR−K−F,
memberF, Facts
F = availP, _, _, _, _, _
base_ofInfo, P, R, K
,
Pairs0
keysortPairs0, Pairs
group_pairs_by_keyPairs, Groups
list_to_assocGroups, ByRoot
169avail_namesavailP, Q, _, _, _, _, N0, N ←
put_assocQ, N0, true, N1
P = tmpX
→
put_assocX, N1, true, N
else
N = N1
173state_addInfo, F, stFs, Ns0, B0, stF|Fs, Ns, B ←
avail_namesF, Ns0, Ns
F = availP, _, _, _, _, _
base_ofInfo, P, R, K
get_assocR, B0, L
→
true
else
L = []
put_assocR, B0, K−F|L, B
180state_killInfo, D, St0, St ←
St0 = stFs0, Ns, _
get_assocD, Ns, _
→
excludementionsD, Fs0, Fs
facts_stateInfo, Fs, St
else
St = St0
187state_noneInfo, St ←
facts_stateInfo, [], St
189phi_killsphitmpD, _, _, Facts0, Facts ←
excludementionsD, Facts0, Facts
names_info+Params, +Blocks, -Infodet

Info is info(Defs, Uses, Bases): each temporary's number of definitions (parameters, phis and every instruction form) and of instructions reading it, and Bases, the assoc from each single-definition flight X to B-K when X's address is B's plus the constant K with B's capability, B defined once too (base_of/4).

199names_infoParams, Blocks, infoDefs, Uses, Bases ←
temp_defsParams, Blocks, DefList
findallN, memberN−_, DefList, Ns
countsNs, Defs
findallQ,
memberblock_, _, Insts, _, Blocks
memberI, Insts
memberQ, Us
,
Qs
countsQs, Uses
findallX−B−K,
memberblock_, _, Insts, _, Blocks
memberI, Insts
flight_offsetI, X, B, K
countDefs, X, 1
B = tmpBN
countDefs, BN, 1
,
BasePairs
list_to_assocBasePairs, Bases
212flight_offsetflighttmpX, add, addressB, constK, lowerB1, X, B, K ←
B ≡ B1
213flight_offsetflighttmpX, sub, addressB, constK0, lowerB1, X, B, K ←
B ≡ B1
K is −K0
214flight_offsetflighttmpX, copy, addressB, lowerB1, X, B, 0 ←
B ≡ B1
base_of+Info, +P, -R, -Kdet

P's address is R's plus K, with R's capability, by single-definition flights; R = P and K = 0 if P is no such flight or its chain has a cycle (possible in unreachable blocks).

221base_ofInfo, P, R, K ←
base_walkInfo, P, [], R0, K0
→
R = R0
K = K0
else
R = P
K = 0
224base_walkinfo_, _, Bases, tmpN, Seen, R, K ←
get_assocN, Bases, B−K0
!
¬memberchkN, Seen
base_walkinfo_, _, Bases, B, N|Seen, R, K1
K is K0 + K1
227base_walk_, P, _, P, 0
related+Info, +P, +P1, -Dsemidet

P's address is P1's plus D, same capability.

232related_, P, P1, 0 ←
P ≡ P1
!
233relatedInfo, P, P1, D ←
Roots are singly defined: names_info/3 records only flights from
singly defined bases.
base_ofInfo, P, R, K
base_ofInfo, P1, R1, K1
R ≡ R1
D is K−K1
239countsXs, Assoc ←
msortXs, Sorted
clumped_pairsSorted, Pairs
list_to_assocPairs, Assoc
242clumped_pairs[], []
243clumped_pairsX|Xs, X−N|Ps ←
same_runX, Xs, 1, N, Rest
244same_runX, Y|Ys, N0, N, Rest ←
X ≡ Y
!
N1 is N0 + 1
same_runX, Ys, N1, N, Rest
245same_run_, Rest, N, N, Rest
247countAssoc, N, C ←
get_assocN, Assoc, C0
→
C = C0
else
C = 0

A fact crosses an edge only when its proof has one definition and its pointer at most one (the emitter reloads the pointer there).

251single_namesinfoDefs, _, _, availP, Q, _, _, _, _ ←
countDefs, Q, 1
P = tmpN
→
countDefs, N, C
C ≤ 1
else
true
single_proof+Info, +Qsemidet

The proof tmp(Q) has one definition and one reader, the access that follows its check.

259single_proofinfoDefs, Uses, _, Q ←
countDefs, Q, 1
countUses, Q, 1
reused_insts+Insts0, +Mode, +Rep, +Facts, +L−Old−New, -Insts, -Map, -Reuseddet

Rep holds the repeated getter copies (repeated_getters/4), which keep the facts in getter mode. Old and New index the next instruction of block L before and after; Map pairs their sites, and Reused pairs each deleted proof with its replacement.

267reused_insts[], _, _, _, Facts, _, [], [], [], Facts
268reused_instslettmpQ, a, checkLo, Hi, Rights, Align, P, Access0|Insts0, Mode, Rep, Info, Facts, L−Old−New, Insts, Map, Reused, Exit ←
reuse_accessMode, Access0, Q
!
Old1 is Old + 1
Old2 is Old + 2
base_ofInfo, P, R, K
Facts = st_, _, ByRoot
get_assocR, ByRoot, Cands
coveringMode, Cands, K, Lo, Hi, Rights, Align, Q1, D
→
renamed_accessAccess0, Q1, D, Access
Insts = Access|Insts1
Map = instL, Old1−instL, New|Map1
New2 is New + 1
Reused = Q−Q1−D|Reused1
Facts1 = Facts
else
Insts = lettmpQ, a, checkLo, Hi, Rights, Align, P, Access0|Insts1
New1 is New + 1
New2 is New + 2
Map = instL, Old−instL, New, instL, Old1−instL, New1|Map1
state_killInfo, Q, Facts, Facts0
Reused = Reused1
state_addInfo, availP, Q, Lo, Hi, Rights, Align, Facts0, Facts1
after_defInfo, Access0, Facts1, Facts2
reused_instsInsts0, Mode, Rep, Info, Facts2, L−Old2−New2, Insts1, Map1, Reused1, Exit

A vector's full-width check and its touch (docs/vector-access.md) stay, and make a fact that the chunk accesses after them may reuse. V1 does not reuse the full-width check itself.

291reused_instslettmpQ, a, checkLo, Hi, Rights, Align, P, touchT|Insts0, Mode, Rep, Info, Facts0, L−Old−New, lettmpQ, a, checkLo, Hi, Rights, Align, P, touchT|Insts, instL, Old−instL, New, instL, Old1−instL, New1|Map, Reused, Exit ←
T ≡ tmpQ
!
Old1 is Old + 1
Old2 is Old + 2
New1 is New + 1
New2 is New + 2
state_killInfo, Q, Facts0, Facts1
state_addInfo, availP, Q, Lo, Hi, Rights, Align, Facts1, Facts
reused_instsInsts0, Mode, Rep, Info, Facts, L−Old2−New2, Insts, Map, Reused, Exit
299reused_instsInst|Insts0, Mode, Rep, Info, Facts0, L−Old−New, Inst|Insts, instL, Old−instL, New|Map, Reused, Exit ←
or
Inst = lettmpD, p, copy, globalG
get_assocD, Rep, G1
G1 ≡ G
→
after_defInfo, Inst, Facts0, Facts
else
state_noneInfo, Facts
Old1 is Old + 1
New1 is New + 1
reused_instsInsts0, Mode, Rep, Info, Facts, L−Old1−New1, Insts, Map, Reused, Exit
reuse_refines+M0, +Msemidet

M is M0 except for deleted heap checks, each of whose single access now reads an earlier proof of the same operand. This is identity, not safety: that the earlier proof still covers the access is ir.pl's rule, which valid_module/1 checks separately.

317reuse_refinesM0, M ←
reuse_refinesplain, M0, M
reuse_refines+Mode, +M0, +Msemidet

reuse_refines/2 for reuse_checks/3's Mode: with forward a deleted check's access may be a heap pointer load, and the proof it reads need not cover its range by itself (the hull, which is the validator's question too).

325reuse_refinesgetter, M0, M ←
!
reuse_refinesforward, M0, M
326reuse_refinesMode, moduleItems0, moduleItems ←
code_itemsItems0, Code0
code_itemsItems, Code
maplistitem_refinesMode, Code0, Code
330item_refinesMode, funcN, L, R, Ps, Bs0, funcN, L, R, Ps, Bs ←
!
names_infoPs, Bs0, Info
findallQ−checkedP, checkLo, Hi, Rs, Al,
memberblock_, _, Is, _, Bs0
memberlettmpQ, a, checkLo, Hi, Rs, Al, P, Is
single_namesInfo, availP, Q, Lo, Hi, Rs, Al
,
Global
maplistblock_refinesMode, Info, Global, Bs0, Bs
338item_refines_, Item, Item
340block_refinesMode, Info, Global, blockL, Phis, Insts0, J, blockL, Phis, Insts, J ←
insts_refineInsts0, Insts, rMode, Info, Global, []
insts_refine+Insts0, +Insts, +rInfo, Global, Proofssemidet

Proofs are Q-checked(P, Check) for the heap checks seen so far in the block, newest first; Global the same for every heap check in the function whose proof has one definition and its pointer at most one. A deleted check's proof had one definition and one reader, and the proof that replaces it checks the same operand and subsumes the deleted check. Whether it is available there is the validator's question.

352insts_refine[], [], _
353insts_refineInst|Insts0, Inst|Insts, rMode, Info, Global, Proofs ←
!
Inst = lettmpQ, a, checkLo, Hi, Rights, Align, P
→
Proofs1 = Q−checkedP, checkLo, Hi, Rights, Align|Proofs
else
Proofs1 = Proofs
insts_refineInsts0, Insts, rMode, Info, Global, Proofs1
359insts_refinelettmpQ, a, checkLo, Hi, Rights, Align, P, Access0|Insts0, Access|Insts, rMode, Info, Global, Proofs ←
reuse_accessMode, Access0, Q
renamed_accessAccess0, Q1, D, Access
memberchkQ1−Checked, Proofs
→
true
else
memberchkQ1−Checked, Global
Checked = checkedP1, checkLo1, Hi1, Rights1, Align1
relatedInfo, P, P1, D
LoD is Lo + D
HiD is Hi + D
subsumesLo1, Hi1, Rights1, Align1, LoD, HiD, Rights, Align
→
true
else
Mode ≡ forward
own_requirementAccess0, Lo, Hi, Rights, Align
insts_refineInsts0, Insts, rMode, Info, Global, Proofs
scalar_access+Access, +Qsemidet

Access is a scalar raw access at tmp(Q).

375scalar_accesslet_, _, raw_loadT, _, tmpQ, Q ←
T ≢ p
376scalar_accessraw_storeW, _, _, tmpQ, Q ←
W ≢ p
378renamed_accessletR, K, Op0, _, Q, D, letR, K, Op, tmpQ ←
renamed_loadOp0, D, Op
380renamed_accessraw_storeW, Off0, V, _, Q, D, raw_storeW, Off, V, tmpQ ←
varD
→
D is Off−Off0
else
Off is Off0 + D
383renamed_loadraw_loadT, Off0, D, raw_loadT, Off ←
varD
→
D is Off−Off0
else
Off is Off0 + D
385renamed_loadraw_load_pointerOff0, Es, D, raw_load_pointerOff, Es ←
varD
→
D is Off−Off0
else
Off is Off0 + D
own_requirement+Access, +Lo, +Hi, +Rights, +Align

the check of [Lo, Hi) with Rights at Align is exactly what Access needs (as pizlonation makes it), so the validator's accepting the rewired access shows that the deleted check would have passed: the hull (docs/forward.md) keeps trap timing only for such checks.

393own_requirementAccess, Lo, Hi, Rights, Align ←
access_needAccess, X, Off, Need
access_size_alignX, Size, AccessAlign
Lo =:= Off
Hi =:= Off + Size
Rights ≡ Need
Align =:= AccessAlign
access_need
398let_, _, raw_loadT, Off, _TOffread
399raw_storeW, Off, _, _WOffwrite
400let_, p, raw_load_pointerOff, _, _pOffread
reuse_access+Mode, +Access, +Q

Access, at tmp(Q), may read another proof: a scalar access, or in forward mode a heap pointer load too.

404reuse_access_, Access, Q ←
scalar_accessAccess, Q
!
405reuse_accessMode, let_, p, raw_load_pointer_, Es, tmpQ, Q ←
Mode ≢ plain

covering(+Mode, +Cands, +K, +Lo, +Hi, +Rights, +Align, -Q1, -D): of the available facts Cands of a root, K1-avail(...) with their pointer's offset K1 from it, Q1's covers the check of [Lo, Hi) with Rights and Align of the pointer at offset K, at distance D = K - K1: the first that subsumes it, or in forward mode (docs/forward.md) the first whose alignment suffices when the facts together span the check's range (an object is one interval) and one of them has write rights if it needs them. Offsets count only within +-2^62, where the integers are the true distances.

417covering_, Cands, K, Lo, Hi, Rights, Align, Q1, D ←
memberK1−avail_, Q1, Lo1, Hi1, Rights1, Align1, Cands
D is K−K1
LoD is Lo + D
HiD is Hi + D
subsumesLo1, Hi1, Rights1, Align1, LoD, HiD, Rights, Align
!
422coveringforward, Cands, K, Lo, Hi, Rights, Align, Q1, D ←
findallL2−H2−R2,
memberK2−avail_, _, Lo2, Hi2, R2, _, Cands
L2 is Lo2 + K2−K
H2 is Hi2 + K2−K
,
Spans
once
memberL2−_−_, Spans
L2 ≤ Lo
once
member_−H2−_, Spans
H2 ≥ Hi
Rights ≡ read
→
true
else
oncemember_−_−write, Spans
The nearest reader whose alignment suffices, within the 32-bit
offset a reused access emits with.
findallAD−Q1−D,
memberK1−avail_, Q1, Lo1, _, _, Align1, Cands
D is K−K1
AD is absD
AD ≤ 2147483647−absLo
LoD is Lo + D
Align1 mod Align =:= 0
LoD−Lo1 mod Align =:= 0
,
Readers
keysortReaders, _−Q1−D|_
441nearK ←
K ≥ −1 << 62
K ≤ 1 << 62
subsumes+Lo1, +Hi1, +Rights1, +Align1, +Lo, +Hi, +Rights, +Alignsemidet

A check of [Lo1, Hi1) with Rights1 at Align1 that succeeded implies that the check of [Lo, Hi) with Rights at Align, of the same pointer and object state, would succeed. Comparing with the deleted check, not just its access, keeps a wider or stronger check's trap.

449subsumesLo1, Hi1, Rights1, Align1, Lo, Hi, Rights, Align ←
Lo1 ≤ Lo
Hi ≤ Hi1
rights_includeRights1, Rights
Align1 mod Align =:= 0
Lo−Lo1 mod Align =:= 0
454rights_includewrite, _
455rights_includeread, read
after_def+Info, +Inst, +Facts0, -Factsdet

Facts are those of Facts0 that do not mention the temporary Inst defines.

461after_defInfo, Inst, Facts0, Facts ←
inst_defInst, D
→
state_killInfo, D, Facts0, Facts
else
Facts = Facts0
467mentionsD, availP, Q, _, _, _, _ ←
Q ≡ D
or
P ≡ tmpD
transparent+Instsemidet

Inst cannot free, reallocate or relabel an object, and reaches no safepoint: arithmetic, comparisons, conversions, pure pointer construction, checks, scalar raw accesses and raw pointer loads (which read aux and box words, calling no helper).

475transparentloc_, _
476transparenttouch_
477transparentflight_, _, _, _
478transparentraw_storeW, _, _, _ ←
W ≢ p
479transparentlet_, p, raw_load_pointer_, Es, _ ←
481transparentlet_, _, Op, Args ←
op_kindOp, Kind
485transparent_kindadditive, _
486transparent_kindalu, _
487transparent_kindshift, _
488transparent_kindneg, _
489transparent_kindcmp, _
490transparent_kindext, _
491transparent_kindfloat_convert, _
492transparent_kindfloat_to_int, _
493transparent_kindint_to_float, _
494transparent_kindcast, _
495transparent_kindptoi, _
496transparent_kindcheck, _
497transparent_kindld, _
498transparent_kindcheck_local, _
499transparent_kindcopy, Args ←
¬memberchkglobal_, Args
renew_checks+M0, -Mdet

M is the checked module M0 with each heap check replaced by a renew (docs/m7.md, round 4) when an earlier heap check of a pointer related to it at distance 0 dominates it and covers its bounds and alignment, rights aside, and both that check's proof and pointer have one definition. The renew reads that proof, the topmost such check on the dominator chain: no check that covers it dominates it, so it stays a check. Functions with a setjmp are unchanged. Every instruction keeps its index, so context sites are unchanged.

512renew_checksM0, M ←
renew_checkszero, M0, M
renew_checks+Distance, +M0, -Mdet

As renew_checks/2 when Distance is zero. When it is any (scheduled checks, docs/schedule.md), the earlier check may be of a pointer at any constant distance D from the same root, if it covers the replaced check's range shifted by D, and the earlier and replaced checks may be check_aheads, of their whole range.

521renew_checksDistance, moduleItems0, moduleItems ←
maplistrenewed_itemDistance, Items0, Items
524renewed_itemDistance, funcN, L, R, Ps, Bs0, funcN, L, R, Ps, Bs ←
!
memberblock_, _, Is, _, Bs0
memberlet_, _, setjmp_, _, Is
→
Bs = Bs0
else
names_infoPs, Bs0, Info
func_cfgBs0, Cfg
cfg_idomsCfg, Idom
dom_depthsCfg, Idom, Depths
dom_intervalsCfg, Idom, Dom
renew_sourcesInfo, Bs0, Depths, Sources
single_definitionsPs, Bs0, Once
renew_distance_passDistance, Pass
maplistrenewed_blockPass, Info−Once, Depths, Dom, Sources, Bs0, Bs2
Distance ≡ hull
→
hull_blocksBs2, Info−Once, Depths, Dom, Bs1
else
Bs1 = Bs2
renew_selectionN, Bs0, Bs1, Bs
540renewed_item_, Item, Item

renew_selection(+Fn, +Blocks0, +Blocks1, -Blocks): for measurement, NANOFIL_RENEW_SITES=Fn:From-To keeps only Fn's renews numbered From to To - 1 in block order (Fn alone keeps all of Fn's), and every other function's checks; unset or empty, Blocks is Blocks1. Any subset of renews is valid IR: a check left in place is the reference.

547renew_selectionFn, Bs0, Bs1, Bs ←
getenv'NANOFIL_RENEW_SITES', Spec
Spec ≢ ''
→
atomic_list_concatF, Range, :, Spec
→
atomic_list_concatA, B, -, Range
atom_numberA, From
atom_numberB, To
else
F = Spec
From = 0
To = inf
F ≡ Fn
→
foldlselected_blockFrom, To, Bs0, Bs1, Bs, 0, _
else
Bs = Bs0
else
Bs = Bs1
560selected_blockFrom, To, blockL, Ph, Is0, J, blockL, Ph, Is1, J, blockL, Ph, Is, J, K0, K ←
foldlselected_instFrom, To, Is0, Is1, Is, K0, K
563selected_instFrom, To, I0, I1, I, K0, K ←
I1 = let_, a, renew_, _, _, _, _
or
I1 = let_, a, renew_hull_, _, _, _, _
→
K is K0 + 1
K0 ≥ From
To ≡ inf
or
K0 < To
→
I = I1
else
I = I0
else
K = K0
I = I1
renew_sources+Info, +Blocks, +Depths, -Sourcesdet

Sources maps Root to s(Depth, L, I, Q1, check, P1, K) for each reachable heap check (or check_ahead) of a pointer at K from Root whose proof and pointer have one definition, sorted by dominator depth and index.

575renew_sourcesInfo, Blocks, Depths, Sources ←
findallR−sDepth, L, I, Q1, checkLo1, Hi1, Al1, P1, K,
memberblockL, _, Is, _, Blocks
get_assocL, Depths, Depth
nth0I, Is, lettmpQ1, a, Op, P1
renewal_checkOp, Lo1, Hi1, _, Al1
single_sourceInfo, Q1, P1
base_ofInfo, P1, R, K
,
Pairs0
keysortPairs0, Pairs1
group_pairs_by_keyPairs1, Groups0
findallKey−Ss,
memberKey−Ss0, Groups0
msortSs0, Ss
,
Groups
list_to_assocGroups, Sources
589single_sourceinfoDefs, _, _, Q1, tmpN1 ←
countDefs, Q1, 1
countDefs, N1, 1
dom_depths+Cfg, +Idom, -Depths

Depths maps each reachable label to its number of dominators below the entry, in one walk of the dominator tree (a walk up the tree per block is quadratic in a long chain).

595dom_depthsCfg, Idom, Depths ←
Cfg = cfgEntry, _, _, _
dom_childrenCfg, Idom, Kids
empty_assocD0
subtree_depthsEntry, Kids, 0, D0, Depths
601subtree_depthsL, Kids, N, D0, D ←
put_assocL, D0, N, D1
get_assocL, Kids, Ks
N1 is N + 1
foldlkid_depthsKids, N1, Ks, D1, D
607kid_depthsKids, N, K, D0, D ←
subtree_depthsK, Kids, N, D0, D
renewal_check+Op, -Lo, -Hi, -Rights, -Align

Op checks [Lo, Hi).

610renewal_checkcheckLo, Hi, Rights, Align, Lo, Hi, Rights, Align
611renewal_checkcheck_aheadLo, Hi, Rights, Align, _, Lo, Hi, Rights, Align
613renewed_blockDistance, Info, Depths, Dom, Sources, blockL, Phis, Is0, J, blockL, Phis, Is, J ←
get_assocL, Depths, _
→
renewed_instsIs0, 0, L, Distance, Info, Dom, Sources, Is
else
Is = Is0
619renewed_insts[], _, _, _, _, _, _, []
620renewed_instsInst0|Is0, I, L, Distance, Info−Once, Dom, Sources, Inst|Is ←
Inst0 = lettmpQ, a, Op, P
renewal_checkOp, Lo, Hi, Rights, Align
Is0 = Access|_
base_ofInfo, P, R, K
get_assocR, Sources, Ss
members_, L1, I1, Q1, checkLo1, Hi1, Al1, P1, K1, Ss
renew_distanceDistance, K, K1, D
L1 ≡ L
→
I1 < I
else
LoD is Lo + D
HiD is Hi + D
subsumesLo1, Hi1, write, Al1, LoD, HiD, Rights, Align
The validator's identity rule (ir.pl), which bounds its walk:
the pass renews only what it accepts.
renew_pointerDistance, Once, P, P1, D
→
Inst = lettmpQ, a, renewLo, Hi, Rights, Align, tmpQ1, P
else
Inst = Inst0
I1n is I + 1
renewed_instsIs0, I1n, L, Distance, Info−Once, Dom, Sources, Is
renew_distance_pass+Distance, -Pass

the single-source pass that runs first; hull's second pass is hull_blocks/5.

642renew_distance_passhull, any ←
!
643renew_distance_passD, D

hull_blocks(+Blocks0, +Info-Once, +Depths, +Dom, -Blocks): after the single-source pass, each check that no renew reads and that two dominating checks of its object cover together (hull_pair/13) becomes a renew_hull reading them. Checks are taken in dominator-tree preorder, so a check's sources are decided before it, and a check made a renew_hull is no longer a source.

651hull_blocksBlocks0, Info−Once, Depths, Dom, Blocks ←
renew_sourcesInfo, Blocks0, Depths, Sources
findallQU−true,
memberblock_, _, Is, _, Blocks0
memberlet_, a, Op, Args, Is
renewing_readerOp, Args, QU
,
Used0
sortUsed0, Used1
list_to_assocUsed1, Used
findallPre−B,
memberB, Blocks0
B = blockL, _, _, _
get_assocL, Dom, Pre−_
,
Keyed0
keysortKeyed0, Keyed
pairs_valuesKeyed, Ordered
empty_assocE
foldlhull_blockInfo−Once, Dom, Sources, Used, Ordered, E−E, _−Changes
maplistapply_hullChanges, Blocks0, Blocks
renewing_reader
662renew_, _, _, _tmpQ1, _Q1
663renew_hull_, _, _, _tmpQ1, _, _Q1
664renew_hull_, _, _, __, tmpQ1, _Q1
666hull_blockIO, Dom, Sources, Used, blockL, _, Is, _, Removed0−Ch0, Removed−Ch ←
hull_instsIs, 0, L, IO, Dom, Sources, Used, Removed0−Ch0, Removed−Ch
669hull_insts[], _, _, _, _, _, _, S, S
670hull_instsInst0|Is0, I, L, Info−Once, Dom, Sources, Used, Removed0−Ch0, S ←
Inst0 = lettmpQ, a, Op, P
renewal_checkOp, Lo, Hi, Rights, Align
¬get_assocQ, Used, _
Is0 = Access|_
base_ofInfo, P, R, K
get_assocR, Sources, Ss
hull_pairSs, Removed0, I, L, Dom, Once, P, K, Lo, Hi, Align, QL, QH
→
put_assocQ, Removed0, true, Removed1
put_assocL−I, Ch0, lettmpQ, a, renew_hullLo, Hi, Rights, Align, tmpQL, tmpQH, P, Ch1
else
Removed1 = Removed0
Ch1 = Ch0
I1 is I + 1
hull_instsIs0, I1, L, Info−Once, Dom, Sources, Used, Removed1−Ch1, S
685apply_hullChanges, blockL, Ph, Is0, J, blockL, Ph, Is, J ←
foldlapply_hull_instL, Changes, Is0, Is, 0, _
688apply_hull_instL, Changes, Inst0, Inst, I, I1 ←
get_assocL−I, Changes, Inst1
→
Inst = Inst1
else
Inst = Inst0
I1 is I + 1
692renew_distancezero, K, K1, 0 ←
K ≡ K1
693renew_distanceany, K, K1, D ←
D is K−K1
694renew_distancehull, K, K1, D ←
D is K−K1
696renew_pointerzero, Once, P, P1, _ ←
same_pointerOnce, P, P1
697renew_pointerany, Once, P, P1, D ←
pointer_distanceOnce, P, P1, D1
D1 =:= D
698renew_pointerhull, Once, P, P1, D ←
renew_pointerany, Once, P, P1, D

hull_pair(+Sources, +Removed, +I, +L, +Dom, +Once, +P, +K, +Lo, +Hi, +Align, -QL, -QH): two dominating checks of P's root whose ranges, moved to P, start at or below Lo and end at or above Hi, each within 2^62 bytes, the one or the other with alignment enough for Align (ir.pl's hull_renewable/8 is the validator's own statement). With no single covering source (renewed_insts/8 tried that first), the object is one interval, so the two together cover [Lo, Hi).

707hull_pairSs, Removed, I, L, Dom, Once, P, K, Lo, Hi, Align, QL, QH ←
Limit is 1 << 62
findallE−sQL0, D0, Lo1, Al1,
members_, L1, I1, QL0, checkLo1, _, Al1, P1, K1, Ss
¬get_assocQL0, Removed, _
L1 ≡ L
→
I1 < I
else
D0 is K−K1
E is Lo + D0−Lo1
E ≥ 0
E ≤ Limit
pointer_distanceOnce, P, P1, DP
DP =:= D0
,
Lows
Lows ≢ []
findallF−sQH0, D0, Lo1, Al1,
members_, L1, I1, QH0, checkLo1, Hi1, Al1, P1, K1, Ss
¬get_assocQH0, Removed, _
L1 ≡ L
→
I1 < I
else
D0 is K−K1
F is Hi1−Hi−D0
F ≥ 0
F ≤ Limit
pointer_distanceOnce, P, P1, DP
DP =:= D0
,
Highs
Highs ≢ []
Hi−Lo ≤ Limit
member_−sQL, DL, LoL, AlL, Lows
member_−sQH, DH, LoH, AlH, Highs
AlL mod Align =:= 0
Lo + DL−LoL mod Align =:= 0
→
true
else
AlH mod Align =:= 0
Lo + DH−LoH mod Align =:= 0
!

A renew's access: scalar or an ordinary pointer access; not a cas.

733renewable_accessAccess, Q ←
scalar_accessAccess, Q
734renewable_accesslet_, p, raw_load_pointer_, Es, tmpQ, Q ←
736renewable_accessraw_store_pointer_, _, tmpQ, Es, Q ←
renew_refines+M0, +Msemidet

M is M0 except for heap checks replaced by renews of the same range, rights and alignment, each reading a single-definition heap check's proof of a single-definition pointer related to the replaced check's at distance 0. This is identity, not safety: dominance and coverage are the validator's (ir.pl), which valid_module/1 checks separately.

747renew_refinesM0, M ←
renew_refineszero, M0, M
renew_refines+Distance, +M0, +Msemidet

renew_refines/2 for renew_checks/3's Distance: with any a renew may read a check of a pointer related to its own at any distance (the coverage is the validator's question).

754renew_refinesDistance, moduleItems0, moduleItems ←
maplistitem_renewsDistance, Items0, Items
757item_renewsDistance, funcN, L, R, Ps, Bs0, funcN, L, R, Ps, Bs ←
!
names_infoPs, Bs0, Info
findallQ1−P1,
memberblock_, _, Is, _, Bs0
memberlettmpQ1, a, check_, _, _, _, P1, Is
single_sourceInfo, Q1, P1
,
Pairs
list_to_assocPairs, Checks
maplistblock_renewsDistance, Info, Checks, Bs0, Bs
764item_renews_, Item, Item
766block_renewsDistance, Info, Checks, blockL, Ph, Is0, J, blockL, Ph, Is, J ←
maplistinst_renewsDistance, Info, Checks, Is0, Is
769inst_renews_, _, _, Inst, Inst ←
!
770inst_renewsDistance, Info, Checks, lettmpQ, a, checkLo, Hi, Rs, Al, P, lettmpQ, a, renewLo, Hi, Rs, Al, tmpQ1, P ←
get_assocQ1, Checks, P1
relatedInfo, P, P1, D
Distance ≡ zero
→
D =:= 0
else
true
774inst_renewshull, Info, Checks, lettmpQ, a, checkLo, Hi, Rs, Al, P, lettmpQ, a, renew_hullLo, Hi, Rs, Al, tmpQL, tmpQH, P ←
get_assocQL, Checks, PL
relatedInfo, P, PL, _
get_assocQH, Checks, PH
relatedInfo, P, PH, _
guard_checks+M0, -Mdet

Range guards (docs/m7.md, round 5, slice A). M is the checked module M0 with each heap check in a natural loop, followed by a renewable access, whose pointer is a base defined outside its innermost loop plus an offset with a constant interval (ir.pl's guard_chain/3), replaced by renew_if of that pointer and a guard: one span_test per loop and base, appended to the end of the loop header's immediate dominator, spanning every byte the group's checks can reach. Nothing else moves, so existing context sites are unchanged; a guard has no site and cannot trap. Functions with a setjmp are unchanged.

791guard_checksmoduleItems0, moduleItems ←
maplistguarded_item, Items0, Items
794guarded_itemfuncN, L, R, Ps, Bs0, funcN, L, R, Ps, Bs ←
!
memberblock_, _, Is, _, Bs0
memberlet_, _, setjmp_, _, Is
→
Bs = Bs0
func_cfgBs0, Cfg
cfg_idomsCfg, Idom
natural_loopsCfg, Idom, Loops0
assoc_to_listLoops0, Loops
Loops ≢ []
single_definitionsPs, Bs0, Once
findallD−L0,
memberblockL0, Phs, Is, _, Bs0
memberphitmpD, _, _, Phs
or
memberI0, Is
,
DLs0
sort1, @<, DLs0, DLs
list_to_assocDLs, DefBlocks
Cfg = cfgEntry, _, _, _
block_loopsLoops, Idom, BlockLoops
findallL0−H0,
gen_assocL0, BlockLoops, Ls0
lastLs0, H0−_−_
,
LoopOf0
list_to_assocLoopOf0, LoopOf
Place = placeEntry, DefBlocks, LoopOf
findallC,
memberblockL1, _, Is, _, Bs0
get_assocL1, BlockLoops, Outward
guard_candidatesIs, 0, L1, Outward, Once−DefBlocks, Idom, C
,
Cs
Cs ≢ []
→
guard_placesCs, Place, Idom, Places
findallX−B−K−Lo1−Hi1,
membercIH, B, K, _, _, Lo1, Hi1, Cs
get_assocB−K−IH, Places, X
,
KVs0
msortKVs0, KVs
group_pairs_by_keyKVs, Groups0
findallKey−Spans,
memberKey−Spans0, Groups0
Key = _−_−Kind
span_clustersKind, Spans0, Spans1
memberSpans, Spans1
,
Groups
empty_assocE
foldldefined_name, DLs, E, Names0
foldlparam_name, Ps, Names0, Names
fresh_guardsGroups, Names, 0, Guards
foldlplaced_guard, Guards, Bs0, Bs1
findallL2−I2−G,
membercIH, B, K, L2, I2, Lo2, Hi2, Cs
get_assocB−K−IH, Places, X
once
membergG, X, B, K, GLo, GHi, Guards
GLo ≤ Lo2
Hi2 ≤ GHi
,
Sites0
list_to_assocSites0, Sites
maplistguarded_blockSites, Bs1, Bs
else
Bs = Bs0
835guarded_itemItem, Item
837defined_nameD−_, N0, N ←
put_assocD, N0, true, N
839param_nameparam_, tmpD, N0, N ←
!
put_assocD, N0, true, N
840param_name_, N, N
guard_candidates+Insts, +I, +L, +Outward, +Defs, +Idom, -Cnondet

C is c(IH, B, K, L, J, Lo, Hi) for a heap check at index J >= I of block L, followed by a renewable access, whose pointer is base B plus an offset whose accesses cover [Lo, Hi) of B, B defined before the loop whose header's immediate dominator is IH: the outermost of the loops around L for which B is. K is const for a constant offset, else ranged.

850guard_candidatesInst, Access|Is, I, L, Outward, Defs, Idom, C ←
Inst = lettmpQ, a, check0, Hi, _, _, P
Defs = Once−_
guard_chainOnce, P, Chain
member_−Body−IH, Outward
guard_baseChain, Defs, Body, Idom, IH, B, OLo, OHi
→
Hi1 is OHi + Hi
OLo =:= OHi
→
K = const
else
K = ranged
guard_worthK, OLo, Hi1
C = cIH, B, K, L, I, OLo, Hi1
or
I2 is I + 2
guard_candidatesIs, I2, L, Outward, Defs, Idom, C
else
I1 is I + 1
guard_candidatesAccess|Is, I1, L, Outward, Defs, Idom, C

guard_places(+Cs, +Place, +Idom, -Places): Places maps B-K-IH to X, the block at whose end the guard of base B and offset kind K for the loops whose headers' immediate dominators are IH goes. The IHs of one base and kind that lie in the same loop (or in none) share one place: their nearest common dominator, or B's definition block (the entry for a parameter) when that is deeper. So one guard serves several loops (bzip2's BZ2_decompress has dozens of bit-reading loops, each run once or twice, on one state pointer), yet runs only where every path to those loops runs: at mainGtU's entry it would run on each call, though its loop is reached by few. A place in another loop than its IHs (a definition inside an inner loop) falls back to each IH.

875guard_placesCs, placeEntry, DefBlocks, LoopOf, Idom, Places ←
findallB−K−LK−IH,
membercIH, B, K, _, _, _, _, Cs
loop_ofLoopOf, IH, LK
,
Ps0
sortPs0, Ps
group_pairs_by_keyPs, Groups
findallB−K−IH−X,
memberB−K−LK−IHs, Groups
IHs = IH0|IHs1
foldlcommon_dominatorIdom, IHs1, IH0, X0
B = tmpN
get_assocN, DefBlocks, DL
→
true
else
DL = Entry
DL ≢ X0
dominatesIdom, X0, DL
→
X1 = DL
else
X1 = X0
memberIH, IHs
loop_ofLoopOf, X1, LK
→
X = X1
else
X = IH
,
Pairs
list_to_assocPairs, Places
888loop_ofLoopOf, L, H ←
get_assocL, LoopOf, H0
→
H = H0
else
H = none
common_dominator+Idom, +A, +B, -Ddet

D is the deepest block that dominates both A and B.

893common_dominatorIdom, A, B, D ←
dominatesIdom, B, A
→
D = B
get_assocB, Idom, B1
→
common_dominatorIdom, A, B1, D
else
D = B
guard_worth+Kind, +Lo, +Hisemidet

A guard of [Lo, Hi) may pass. A span test fails as a whole, and then each access it guards pays its full check plus the test of G. A ranged span over 256 KiB is a type's bound times a stride (a byte or short index into a row of a table), which objects rarely hold: bzip2's s->len[t][v], for a byte t, spans 1 MB of a 55 KB state. A table that large (bzip2's ftab, 256 KiB) still fits. A span_test's ends are signed 64-bit (ir.pl's valid_op); a span past them is no guard at all, and merged clusters stay within each part's.

909guard_worthconst, Lo, Hi ←
span_fitsLo, Hi
910guard_worthranged, Lo, Hi ←
span_fitsLo, Hi
Hi−Lo ≤ 262144
912span_fitsLo, Hi ←
Lo ≥ −1 << 63
Hi ≤ 1 << 63−1
block_loops+Loops, +Idom, -BlockLoopsdet

BlockLoops maps each block in some natural loop whose header has an immediate dominator to its loops' H-Body-IH, outermost first (nested loops' bodies are nested). Built by walking the loops outermost first and each body once, so the cost is the bodies' total size, not blocks times loops.

921block_loopsLoops, Idom, BlockLoops ←
findallN−H−B−IH,
memberH−loop_, B, Loops
get_assocH, Idom, IH
lengthB, N0
N is −N0
,
Ls
keysortLs, Sorted
pairs_valuesSorted, Outermost
findallL−Lp,
memberLp, Outermost
Lp = _−B−_
memberL, B
,
LLs
sort1, @=<, LLs, ByBlock
group_pairs_by_keyByBlock, Groups
list_to_assocGroups, BlockLoops

span_clusters(+Kind, +Spans, -Clusters): the Lo-Hi Spans of one base in one loop, in clusters, each one span test. A span test passes or fails as a whole, so a span the object cannot hold must not take others down with it. An offset that varies is bounded by its type and masks, not by the array it indexes (a byte index into a six-row table), so each distinct ranged span gets its own test; constant offsets, fields, merge where they overlap or lie within 64 bytes.

936span_clustersranged, Spans0, Clusters ←
sortSpans0, Spans
findallS, memberS, Spans, Clusters
939span_clustersconst, Spans0, Clusters ←
msortSpans0, Spans
Spans = Lo−Hi|Rest
merged_spansRest, Lo, Hi, Clusters
944merged_spans[], Lo, Hi, Lo−Hi
945merged_spansLo1−Hi1|Rest, Lo, Hi, Clusters ←
Lo1 ≤ Hi + 64
→
Hi2 is maxHi, Hi1
merged_spansRest, Lo, Hi2, Clusters
else
Clusters = Lo−Hi|Clusters1
merged_spansRest, Lo1, Hi1, Clusters1
guard_base+Chain, +Once−DefBlocks, +Body, +Idom, +IH, -B, -OLo, -OHisemidet

B is the farthest base on the chain whose single definition is outside the loop Body and dominates its header's immediate dominator IH, so a guard at IH's end may read it.

956guard_baseChain, Once−DefBlocks, Body, Idom, IH, B, OLo, OHi ←
reverseChain, Far
memberB−iOLo, OHi, Far
B = tmpN
get_assocN, Once, Def
Def ≡ param
→
true
else
get_assocN, DefBlocks, DL
¬ord_memberchkDL, Body
dominatesIdom, DL, IH
!
fresh_guards+Groups, +Names, +K, -Guardsdet

A name no instruction or phi defines per group, g(Name, H, B, K, Lo, Hi).

968fresh_guards[], _, _, []
969fresh_guardsH−B−K−Spans|Gs, Names, K0, gName, H, B, K, Lo, Hi|Guards ←
pairs_keys_valuesSpans, Los, His
min_listLos, Lo
max_listHis, Hi
fresh_nameNames, K0, Name, K1
fresh_guardsGs, Names, K1, Guards
974fresh_nameNames, K0, Name, K ←
formatatomName0, 'guard.~w', K0
K1 is K0 + 1
get_assocName0, Names, _
→
fresh_nameNames, K1, Name, K
else
Name = Name0
K = K1
980names_mentionedBs, Name ←
memberblock_, _, Is, _, Bs
memberI, Is
inst_defI, Name
or
memberchkName, Us
!
984placed_guardgName, X, B, _, Lo, Hi, Bs0, Bs ←
appendPre, blockX, Ph, Is, J|Post, Bs0
!
appendIs, lettmpName, w, span_testLo, Hi, B, Is1
appendPre, blockX, Ph, Is1, J|Post, Bs
989guarded_blockSites, blockL, Ph, Is0, J, blockL, Ph, Is, J ←
guarded_instsIs0, 0, L, Sites, Is
992guarded_insts[], _, _, _, []
993guarded_instsInst0|Is0, I, L, Sites, Inst|Is ←
get_assocL−I, Sites, G
Inst0 = letQ, a, checkLo, Hi, Rights, Align, P
→
Inst = letQ, a, renew_ifLo, Hi, Rights, Align, P, tmpG
else
Inst = Inst0
I1 is I + 1
guarded_instsIs0, I1, L, Sites, Is
guard_refines+M0, +Msemidet

M is M0 except for span_test lets of fresh names appended at block ends, and heap checks replaced in place by renew_if of the same pointer, range, rights and alignment, reading one of those guards. This is identity, not safety: that the guard covers the access is the validator's rule (ir.pl).

1010guard_refinesmoduleItems0, moduleItems ←
maplistitem_guards, Items0, Items
1013item_guardsfuncN, L, R, Ps, Bs0, funcN, L, R, Ps, Bs ←
!
maplistblock_guards, Bs0, Bs, Gss
appendGss, Gs
msortGs, Sorted
lengthGs, NG
sortGs, Set
lengthSet, NG
¬
memberG, Sorted
¬
memberG, Sorted
memberparam_, tmpG1, Ps
G1 ≡ G
maplistguard_reads_okSet, Bs, Bs0
1019item_guardsItem, Item
1021block_guardsblockL, Ph, Is0, J, blockL, Ph, Is, J, Gs ←
lengthIs0, N
lengthPre, N
appendPre, Extra, Is
findallG, memberlettmpG, w, span_test_, _, _, Extra, Gs
lengthExtra, NE
lengthGs, NE
maplistinst_guarded, Is0, Pre
1027inst_guardedInst, Inst ←
!
1028inst_guardedletQ, a, checkLo, Hi, Rs, Al, P, letQ, a, renew_ifLo, Hi, Rs, Al, P, tmp_

Each renew_if that replaced a check reads one of the fresh guards.

1032guard_reads_okSet, block_, _, Is, _, block_, _, Is0, _ ←
guard_readsIs0, Is, Set
1035guard_reads[], _, _
1036guard_readsI0|Is0, I|Is, Set ←
I0 = let_, a, check_, _, _, _, _
I = let_, a, renew_if_, _, _, _, _, tmpG
→
ord_memberchkG, Set
else
true
guard_readsIs0, Is, Set
hoist_checks+M0, -Mdet

Hoisted bounds (docs/bounds.md). M is the checked module M0 with each heap check in a natural loop, at offset 0 and followed by a renewable access, whose pointer has the lower of a base defined outside the loop (lower_chain/3, any address), replaced by check_limit of that pointer and a limit of the base for the check's size: one limit/1 per base, size and place, appended where guard_places/4 would put a guard, so it runs once before the loops it serves. Nothing else moves, so context sites are unchanged; a limit has no site and cannot trap. Functions with a setjmp are unchanged.

1055hoist_checksmoduleItems0, moduleItems ←
maplisthoisted_item, Items0, Items
1058hoisted_itemfuncN, L, R, Ps, Bs0, funcN, L, R, Ps, Bs ←
!
memberblock_, _, Is, _, Bs0
memberlet_, _, setjmp_, _, Is
→
Bs = Bs0
func_cfgBs0, Cfg
cfg_idomsCfg, Idom
natural_loopsCfg, Idom, Loops0
assoc_to_listLoops0, Loops
Loops ≢ []
single_definitionsPs, Bs0, Once
findallD−L0,
memberblockL0, Phs, Is, _, Bs0
memberphitmpD, _, _, Phs
or
memberI0, Is
,
DLs0
sort1, @<, DLs0, DLs
list_to_assocDLs, DefBlocks
Cfg = cfgEntry, _, _, _
block_loopsLoops, Idom, BlockLoops
findallL0−H0,
gen_assocL0, BlockLoops, Ls0
lastLs0, H0−_−_
,
LoopOf0
list_to_assocLoopOf0, LoopOf
findallC,
memberblockL1, _, Is, _, Bs0
get_assocL1, BlockLoops, Outward
hoist_candidatesIs, 0, L1, Outward, Once−DefBlocks, Idom, C
,
Cs
Cs ≢ []
→
guard_placesCs, placeEntry, DefBlocks, LoopOf, Idom, Places
findallX−B−Size,
membercIH, B, Size, _, _, _, _, Cs
get_assocB−Size−IH, Places, X
,
Keys0
sortKeys0, Keys
empty_assocE
foldldefined_name, DLs, E, Names0
foldlparam_name, Ps, Names0, Names
fresh_limitsKeys, Names, 0, Limits
foldlplaced_limit, Limits, Bs0, Bs1
findallL2−I2−U,
membercIH, B, Size, L2, I2, _, _, Cs
get_assocB−Size−IH, Places, X
memberchkuU, X, B, Size, Limits
,
Sites0
list_to_assocSites0, Sites
maplistlimited_blockSites, Bs1, Bs
else
Bs = Bs0
1094hoisted_itemItem, Item
hoist_candidates+Insts, +I, +L, +Outward, +Defs, +Idom, -Cnondet

C is c(IH, B, Size, L, J, 0, 0) for a heap check of [0, Size) at index J >= I of block L, followed by a renewable access, whose pointer has the lower of base B, defined before the loop whose header's immediate dominator is IH: the outermost of the loops around L for which some base on the chain is.

1103hoist_candidatesInst, Access|Is, I, L, Outward, Defs, Idom, C ←
Inst = lettmpQ, a, check0, Size, _, _, P
Defs = Once−_
lower_namesOnce, P, Chain
member_−Body−IH, Outward
guard_baseChain, Defs, Body, Idom, IH, B, _, _
lower_chainOnce, P, B
→
C = cIH, B, Size, L, I, 0, 0
or
I2 is I + 2
hoist_candidatesIs, I2, L, Outward, Defs, Idom, C
else
I1 is I + 1
hoist_candidatesAccess|Is, I1, L, Outward, Defs, Idom, C
lower_names+Once, +P, -Chain

P-i(0, 0) and each lower donor after it through single-definition flights, as guard_base/8 reads a chain.

1118lower_namesOnce, P, P−i0, 0|Chain ←
P = tmpX
get_assocX, Once, flight_, _, _, lowerD
→
lower_namesOnce, D, Chain
else
Chain = []
fresh_limits+Keys, +Names, +K, -Limitsdet

A name no instruction or phi defines per X-B-Size key, u(Name, X, B, Size).

1128fresh_limits[], _, _, []
1129fresh_limitsX−B−Size|Ks, Names, K0, uName, X, B, Size|Limits ←
fresh_limit_nameNames, K0, Name, K1
fresh_limitsKs, Names, K1, Limits
1133fresh_limit_nameNames, K0, Name, K ←
formatatomName0, 'limit.~w', K0
K1 is K0 + 1
get_assocName0, Names, _
→
fresh_limit_nameNames, K1, Name, K
else
Name = Name0
K = K1
1139placed_limituName, X, B, Size, Bs0, Bs ←
appendPre, blockX, Ph, Is, J|Post, Bs0
!
appendIs, lettmpName, l, limitSize, B, Is1
appendPre, blockX, Ph, Is1, J|Post, Bs
1144limited_blockSites, blockL, Ph, Is0, J, blockL, Ph, Is, J ←
limited_instsIs0, 0, L, Sites, Is
1147limited_insts[], _, _, _, []
1148limited_instsInst0|Is0, I, L, Sites, Inst|Is ←
get_assocL−I, Sites, U
Inst0 = letQ, a, checkLo, Hi, Rights, Align, P
→
Inst = letQ, a, check_limitLo, Hi, Rights, Align, P, tmpU
else
Inst = Inst0
I1 is I + 1
limited_instsIs0, I1, L, Sites, Is
hoist_refines+M0, +Msemidet

M is M0 except for limit lets of fresh names appended at block ends, and heap checks replaced in place by check_limit of the same pointer, range, rights and alignment, reading one of those limits. This is identity, not safety: that the limit is of the pointer's lower and size is the validator's rule (ir.pl).

1165hoist_refinesmoduleItems0, moduleItems ←
maplistitem_limits, Items0, Items
1168item_limitsfuncN, L, R, Ps, Bs0, funcN, L, R, Ps, Bs ←
!
maplistblock_limits, Bs0, Bs, Uss
appendUss, Us
lengthUs, NU
sortUs, Set
lengthSet, NU
¬
memberU, Set
¬
memberU, Set
memberparam_, tmpU1, Ps
U1 ≡ U
maplistlimit_reads_okSet, Bs, Bs0
1174item_limitsItem, Item
1176block_limitsblockL, Ph, Is0, J, blockL, Ph, Is, J, Us ←
lengthIs0, N
lengthPre, N
appendPre, Extra, Is
findallU, memberlettmpU, l, limit_, _, Extra, Us
lengthExtra, NE
lengthUs, NE
maplistinst_limited, Is0, Pre
1182inst_limitedInst, Inst ←
!
1183inst_limitedletQ, a, checkLo, Hi, Rs, Al, P, letQ, a, check_limitLo, Hi, Rs, Al, P, tmp_
1186limit_reads_okSet, block_, _, Is, _, block_, _, Is0, _ ←
limit_reads_fromIs0, Is, Set
1189limit_reads_from[], _, _
1190limit_reads_fromI0|Is0, I|Is, Set ←
I0 = let_, a, check_, _, _, _, _
I = let_, a, check_limit_, _, _, _, _, tmpU
→
ord_memberchkU, Set
else
true
limit_reads_fromIs0, Is, Set