Fil-C on Nix

Filnix wiki

mbrock/filnix/nanofil

lowers_check.pl

The GC-root contract of a frame, checked: at every point of every reachable block, each live pointer temporary's lowers[] entry holds its current lower (docs/lowers.md). The collector marks the object of every lower in the frame record at each safepoint, so a live pointer whose entry held anything else could lose its object.

check_lowers/4 validates the frame filc_abi.pl built, whatever chose it: it does not ask which flights lower_roots.pl derived or how frame_slots.pl pooled the slots, only what each instruction writes.

Facts

FactHolds at a point when
holdsE, Nson every path here, entry E was last written with a lower that is the current lower of each name in Ns

The state maps each entry to its Ns; an absent entry holds nothing known. Every name is only ever added to its own entry's Ns.

Transfer

EventEffect
entryeach p parameter N, in order: holds(E(N), [N])
a phi or an instruction defining p temporary Nholds(E(N), [N])
flighttmpN, _, _, lowertmpP, P ≠ N, E(P) = E(N)emits no store (filc_amd64.pl); requires P in E's Ns, then adds N
on the edge from B, a p phi N whose inputs are all temporaries with entry E(N)copies no lower; requires the input V from B in E's Ns at B's end, then adds N

Nothing else writes a temporary's entry: the runtime writes lowers[] only in zlongjmp, and a function with setjmp must give every pointer an entry of its own (today's discipline). Calls, polls and helpers only read it. Shadows, stack-aux, snapshot and jmpbuf roots are other entries.

Merge and check

A block's state is the meet of its reachable predecessors': per entry, the names in all of them. Then each live pointer (cfg.pl's liveness, refined per instruction) must be in its entry's Ns: before each instruction, after the last one (live out and the jump's uses, where an edge's poll would see them if it polled first) and before the first after the phis (where an edge's poll does see them). Unreachable blocks are not checked; they never run.

modulelowers_check
useslists
usesassoc
usesordsets
usespairs
usesirtemp_defs/3
usescfgfunc_cfg/2, temp_liveness/3, inst_uses/2, inst_def/2, jump_uses/2
check_lowers+Fn, +Params, +LiveBlocks, +Framedet

Frame = frame(Plain, Roots, Aliases), filc_abi.pl's, satisfies the contract above for function Fn with body parameters Params and the liveness view LiveBlocks. Throws nanofil_error otherwise.

59check_lowersFn, Params, Blocks, frame_, Roots, Aliases ←
maplistbare_block, Blocks, Bare
temp_defsParams, Bare, Defs
findallN, memberN−p, Defs, Ps0
sortPs0, Ps
list_to_assocAliases, A
maplistentryA, Ps, EntryPairs
list_to_assocEntryPairs, E
findallN−x, memberN−_, Defs, Known0
sortKnown0, Known1
list_to_assocKnown1, Known
msortRoots, R0
sortRoots, R1
R0 ≡ R1
→
true
else
fail_checkFn, 'two roots share a lowers[] entry', []
forallmemberN−K, EntryPairs,
memberchkK, Roots
→
true
else
fail_checkFn, 'the lower of ~w is not in a temporary\'s lowers[] entry', N
memberblock_, _, Is, _, Bare
memberchklet_, w, setjmp_, _, Is
→
pairs_valuesEntryPairs, Ks0
msortKs0, Ks1
sortKs0, Ks2
Ks1 ≡ Ks2
→
true
else
fail_checkFn, 'a function with setjmp shares a lowers[] entry', []
else
true
empty_assocS0
foldlparam_writeE, Params, S0, Start
func_cfgBare, cfgEntry, Rpo, _, Preds
findallL−B,
memberB, Bare
B = blockL, _, _, _
,
LBs
list_to_assocLBs, ByLabel
temp_livenessBlocks, _, Out
findallL−B,
memberB, Blocks
B = blockL, _, _, _
,
LiveLBs
list_to_assocLiveLBs, LiveByLabel
Ctx = ctxFn, E−Known, ByLabel, Preds, Entry−Start
empty_assocOuts0
fixpointRpo, Ctx, Outs0, Outs
forallmemberL, Rpo,
block_inCtx, Outs, L, In
get_assocL, ByLabel, Block
get_assocL, LiveByLabel, LiveBlock
get_assocL, Out, LiveOut
check_blockCtx, Block, LiveBlock, LiveOut, In
99fail_checkFn, Format, Args ←
formatatomWhy, Format, Args
formatatomMsg, 'lowers check, in ~w: ~w', Fn, Why
thrownanofil_errorMsg

A temporary's entry: the root its lower key names, through Aliases.

105entryA, N, N−K ←
get_assoclowertmpN, A, K0
→
K = K0
else
K = lowertmpN

A temporary's entry is its own lower key or a pointer-pool entry, never a shadow, stack-aux, snapshot or jmpbuf root (lower(tmp(hidden(_)))).

110temp_rootN, lowertmpM ←
M ≡ N
111temp_root_, lowerstoragepointer, _
113param_writeE, paramp, tmpN, S0, S ←
!
write_entryE, N, S0, S
114param_write_, _, S, S
116write_entryE, N, S0, S ←
get_assocN, E, K
put_assocK, S0, N, S
119holdsE, S, N ←
get_assocN, E, K
get_assocK, S, Ns
ord_memberchkN, Ns
fixpoint+Rpo, +Ctx, +Outs0, -Outs

Outs maps each reachable block to its state after its last instruction, the greatest fixpoint from top (an unvisited predecessor is ignored by the meet).

125fixpointRpo, Ctx, Outs0, Outs ←
foldlblock_passCtx, Rpo, Outs0−false, Outs1−Changed
Changed ≡ true
→
fixpointRpo, Ctx, Outs1, Outs
else
Outs = Outs1
129block_passCtx, L, Outs0−C0, Outs−C ←
block_inCtx, Outs0, L, In
Ctx = ctx_, _, ByLabel, _, _
get_assocL, ByLabel, block_, _, Is, _
foldlinst_transferCtx, Is, In, Out
get_assocL, Outs0, Old
Old ≡ Out
→
Outs = Outs0
C = C0
else
put_assocL, Outs0, Out, Outs
C = true
block_in+Ctx, +Outs, +L, -In

the meet of the states its visited predecessors' edges leave, after L's phis, and the parameters' at the entry block.

142block_inCtx, Outs, L, In ←
Ctx = ctx_, _, ByLabel, Preds, Entry−Start
get_assocL, Preds, Ps
get_assocL, ByLabel, block_, Phis, _, _
findallS,
memberP, Ps
get_assocP, Outs, S0
foldlphi_transferCtx, P, Phis, S0, S
,
Ss0
L ≡ Entry
→
Ss = Start|Ss0
else
Ss = Ss0
Ss = S1|Rest
→
foldlmeet, Rest, S1, In
else
empty_assocIn
151meetS1, S2, S ←
assoc_to_listS1, L1
assoc_to_listS2, L2
meet_listsL1, L2, L
list_to_assocL, S
155meet_lists[], _, [] ←
!
156meet_lists_, [], [] ←
!
157meet_listsK1−V1|T1, K2−V2|T2, L ←
compareO, K1, K2
meet_stepO, K1−V1, T1, K2−V2, T2, L
161meet_step<, _, T1, P2, T2, L ←
meet_listsT1, P2|T2, L
162meet_step>, P1, T1, _, T2, L ←
meet_listsP1|T1, T2, L
163meet_step=, K−V1, T1, _−V2, T2, L ←
ord_intersectionV1, V2, V
V ≡ []
→
L = L1
else
L = K−V|L1
meet_listsT1, T2, L1
phi_transfer+Ctx, +From, +Phi, +S0, -S

Phi's write on the edge from From. A phi whose inputs all have its entry copies no lower (filc_amd64.pl), so its input from From must hold there.

171phi_transferctxFn, E−_, _, _, _, From, phitmpN, p, Pairs, S0, S ←
!
shared_phiE, N, Pairs
→
memberchkFrom−tmpV, Pairs
→
true
else
fail_checkFn, 'the phi ~w has no input from ~w', N, From
holdsE, S0, V
→
get_assocN, E, K
get_assocK, S0, Ns0
ord_add_elementNs0, N, Ns
put_assocK, S0, Ns, S
else
fail_checkFn, 'the phi ~w shares the entry of ~w, which does not hold its lower at the end of ~w', N, V, From
else
write_entryE, N, S0, S
183phi_transfer_, _, _, S, S
185shared_phiE, N, Pairs ←
get_assocN, E, K
forallmember_−V, Pairs,
V = tmpM
get_assocM, E, K
inst_transfer+Ctx, +Inst, +S0, -S

Inst's writes of entries. Only a flight whose donor has its entry is no write (filc_amd64.pl emits none), and only when the donor's lower is there.

192inst_transferctxFn, E−_, _, _, _, I, S0, S ←
shared_flightE, I, N, P
→
holdsE, S0, P
→
get_assocN, E, K
get_assocK, S0, Ns0
ord_add_elementNs0, N, Ns
put_assocK, S0, Ns, S
else
fail_checkFn, 'the flight ~w shares the entry of ~w, which does not hold its lower', N, P
else
findallN,
get_assocN, E, _
,
Ns
foldlwrite_entryE, Ns, S0, S
203shared_flightE, flighttmpN, _, _, lowertmpP, N, P ←
P ≢ N
get_assocN, E, K
get_assocP, E, K
check_block+Ctx, +Block, +LiveBlock, +LiveOut, +In

every live pointer holds at every point of Block.

208check_blockCtx, blockL, _, Is, J, block_, _, LiveIs, _, LiveOut, In ←
Ctx = ctxFn, EK, _, _, _
jump_usesJ, JUs
ord_unionLiveOut, JUs, Last
reverseLiveIs, Rev
live_beforesRev, Last, [], Befores
foldlcheck_instCtx, L, Is, Befores, In, S2
check_liveFn, EK, L, end, S2, Last
live_befores+RevInsts, +After, +Acc, -Befores

the live set before each instruction, walking them backwards from the set After the last.

217live_befores[], _, Bs, Bs
218live_beforesI|Is, After, Acc, Bs ←
findallN, inst_defI, N, Ds0
sortDs0, Ds
ord_subtractAfter, Ds, Through
ord_unionThrough, Us, Before
live_beforesIs, Before, Before|Acc, Bs
224check_instCtx, L, I, Before, S0, S ←
Ctx = ctxFn, EK, _, _, _
check_liveFn, EK, L, I, S0, Before
inst_transferCtx, I, S0, S

Every live name is defined somewhere, and a live pointer holds.

230check_liveFn, E−K, L, At, S, Live ←
memberN, Live
¬get_assocN, K, _
→
fail_checkFn, '~w is live in block ~w at ~q but never defined', N, L, At
memberN, Live
get_assocN, E, _
¬holdsE, S, N
→
fail_checkFn, 'pointer ~w is live in block ~w at ~q, but its lowers[] entry does not hold its lower', N, L, At
else
true
238bare_blockblockL, Phis, Is0, J, blockL, Phis, Is, J ←
maplistbare, Is0, Is
240bareusesI0, _, I ←
!
bareI0, I
241bareI, I