Fil-C on Nix

Filnix wiki

mbrock/filnix/nanofil

cfg.pl

Control-flow analyses over one function's blocks, as pure functions from IR terms to assocs keyed by block label.

func_cfg/2 builds cfgEntry, Rpo, Succs, Preds, which the other analyses take. Each export documents its relation below.

AnalysisPredicates
dominatorscfg_idoms/2, dom_children/3, cfg_frontiers/3, dominates/3, dom_intervals/3, dominates_by_interval/3
natural loopsnatural_loops/3
backward livenessliveness/5, and temp_liveness/3 over temps

PLAN asks for these as tabled relations. cfg_tabled.pl has that version; docs/m5.md has the measurement that made this one the one passes use.

Variable conventions

VariableMeans
L, Ba block's label
Sa successor's label
Pa predecessor's label
Ls, Ss, Pslists of labels
T, Dtemp names
useslists
usesassoc
usesordsets
usespairs

CFG

jump_succs+Jump, -Succsdet

Succs is the ordset of labels Jump can branch to.

45jump_succsgotoT, L ←
47jump_succsbranch_, T1, T2, Ls ←
sortL1, L2, Ls
51jump_succsret_, []
52jump_succsret, []
53jump_succsrets_, []
54jump_succsunreachable, []
edge_label+Target, -Ldet

A jump's Target is the label L, or poll(L): a pollcheck on the edge, then L (ir.pl). A label is any other term, so the cut commits to poll/1.

62edge_labelpollL, L ←
!
63edge_labelL, L
func_cfg+Blocks, -Cfgdet

Cfg = cfg(Entry, Rpo, Succs, Preds) is the control-flow graph of Blocks, whose first block is the entry:

- Rpo: the reachable labels in reverse postorder, Entry first;
- Succs: assoc Label -> ordset of successor labels (every block);
- Preds: assoc Label -> ordset of predecessor labels (every block,
  including unreachable predecessors, as phis must list them).
75func_cfgBlocks, cfgEntry, Rpo, Succs, Preds ←
Blocks = blockEntry, _, _, _|_
findallLabel−Ss,
memberblockLabel, _, _, Jump, Blocks
jump_succsJump, Ss
,
SuccPairs
list_to_assocSuccPairs, Succs
findallLabel−[], memberLabel−_, SuccPairs, NoPreds
list_to_assocNoPreds, Preds0
foldlsucc_pair_preds, SuccPairs, Preds0, Preds1
assoc_to_listPreds1, PredPairs0
findallLabel−Ps,
memberLabel−Ps0, PredPairs0
sortPs0, Ps
,
PredPairs
list_to_assocPredPairs, Preds
empty_assocSeen0
reverse_postorderEntry, Succs, Seen0, _, [], Rpo
succ_pair_preds+Pred−Ss, +Preds0, -Preds

Preds is Preds0 with Pred in the (unsorted) predecessor list of each label in Ss.

98succ_pair_predsPred−Ss, Preds0, Preds ←
foldlpred_succ_predsPred, Ss, Preds0, Preds
101pred_succ_predsPred, Succ, Preds0, Preds ←
get_assocSucc, Preds0, Ps
put_assocSucc, Preds0, Pred|Ps, Preds
reverse_postorder+Ls, +Succs, +Seen0, -Seen, +Post0, -Post

Post is a reverse postorder, in front of Post0, of the labels reachable from Ls (depth first, each of Ls in turn) without passing through Seen0; Seen is Seen0 with those labels.

109reverse_postorder[], _, Seen, Seen, Post, Post
110reverse_postorderL|Ls, Succs, Seen0, Seen, Post0, Post ←
get_assocL, Seen0, _
→
reverse_postorderLs, Succs, Seen0, Seen, Post0, Post
else
put_assocL, Seen0, t, Seen1
get_assocL, Succs, Ss
reverse_postorderSs, Succs, Seen1, Seen2, Post0, Post1
reverse_postorderLs, Succs, Seen2, Seen, L|Post1, Post

Dominators

cfg_idoms+Cfg, -Idomdet

Idom is the assoc Label -> immediate dominator, for the reachable labels other than the entry (Cooper, Harvey and Kennedy, "A Simple, Fast Dominance Algorithm").

127cfg_idomscfgEntry, Rpo, _, Preds, Idom ←
labels_numberedRpo, 0, Numbered
list_to_assocNumbered, Num
empty_assocIdom0
put_assocEntry, Idom0, Entry, Idom1
Rpo = _|Rest
idom_fixpointRest, Preds, Num, Idom1, Idom2
del_assocEntry, Idom2, _, Idom
labels_numbered+Labels, +N0, -Pairs

Pairs is Label-N for each of Labels, counting from N0.

138labels_numbered[], _, []
139labels_numberedL|Ls, N, L−N|Ps ←
N1 is N + 1
labels_numberedLs, N1, Ps
idom_fixpoint+Labels, +Preds, +Num, +Idom0, -Idom

Idom is the fixed point, from Idom0, of sweeps over Labels in reverse postorder.

145idom_fixpointLabels, Preds, Num, Idom0, Idom ←
foldllabel_idomsPreds, Num, Labels, Idom0−false, Idom1−Changed
changed_idom_fixpointChanged, Labels, Preds, Num, Idom1, Idom
changed_idom_fixpoint+Changed, +Labels, +Preds, +Num, +Idom0, -Idom

as idom_fixpoint/5, after a sweep that changed an idom (Changed is true) or none (false).

153changed_idom_fixpointtrue, Labels, Preds, Num, Idom0, Idom ←
idom_fixpointLabels, Preds, Num, Idom0, Idom
155changed_idom_fixpointfalse, _, _, _, Idom, Idom
label_idoms+Preds, +Num, +B, +Idom0−Changed0, −Idom−Changed

in Idom, the idom of B is the nearest common dominator of those of its predecessors that have an idom in Idom0. Changed is true if that differs from B's idom in Idom0, and Changed0 if not.

161label_idomsPreds, Num, B, Idom0−Changed0, Idom−Changed ←
get_assocB, Preds, Ps
includehas_idomIdom0, Ps, Processed
Processed = First|Others
foldlcommon_dominatorNum, Idom0, Others, First, New
get_assocB, Idom0, Old
Old ≡ New
→
Idom = Idom0
Changed = Changed0
else
put_assocB, Idom0, New, Idom
Changed = true
174has_idomIdom, L ←
get_assocL, Idom, _
common_dominator+Num, +Idom, +A, +B, -Common

Common is the nearest common dominator of A and B: A if they are equal, and otherwise the nearest common dominator of the earlier one in reverse postorder and the idom of the later one.

181common_dominatorNum, Idom, A, B, Common ←
A ≡ B
→
Common = A
else
get_assocA, Num, NA
get_assocB, Num, NB
NA > NB
→
get_assocA, Idom, A1
common_dominatorNum, Idom, A1, B, Common
else
get_assocB, Idom, B1
common_dominatorNum, Idom, A, B1, Common
dom_children+Cfg, +Idom, -Kidsdet

Kids is the assoc Label -> the children of Label in the dominator tree, in reverse postorder, for every reachable label.

199dom_childrencfg_, Rpo, _, _, Idom, Kids ←
findallL−[], memberL, Rpo, NoKids
list_to_assocNoKids, Kids0
reverseRpo, Reversed
foldllabel_kidsIdom, Reversed, Kids0, Kids
label_kids+Idom, +L, +Kids0, -Kids

Kids is Kids0 with L in front of the children of its idom, if it has one.

207label_kidsIdom, L, Kids0, Kids ←
get_assocL, Idom, Parent
→
get_assocParent, Kids0, Cs
put_assocParent, Kids0, L|Cs, Kids
else
Kids = Kids0
dominates+Idom, +A, +Bsemidet

A dominates B (both reachable): A is B, or A dominates the idom of B.

218dominatesIdom, A, B ←
A ≡ B
→
true
else
get_assocB, Idom, D
dominatesIdom, A, D
dom_intervals+Cfg, +Idom, -Intervalsdet

Intervals is the assoc Label -> Pre-Last for every reachable label: Pre is the preorder number of Label in the dominator tree, and Last the last one in its subtree.

231dom_intervalsCfg, Idom, Intervals ←
Cfg = cfgEntry, _, _, _
dom_childrenCfg, Idom, Kids
empty_assocIntervals0
subtree_intervalsEntry, Kids, 0, _, Intervals0, Intervals
subtree_intervals+L, +Kids, +N0, -N, +Intervals0, -Intervals

Intervals is Intervals0 with the intervals of the dominator subtree at L, numbered in preorder from N0; N is the next free number.

240subtree_intervalsL, Kids, N0, N, Intervals0, Intervals ←
get_assocL, Kids, Ks
N1 is N0 + 1
foldlkid_intervalsKids, Ks, N1−Intervals0, N−Intervals1
Last is N−1
put_assocL, Intervals1, N0−Last, Intervals
247kid_intervalsKids, K, N0−Intervals0, N−Intervals ←
subtree_intervalsK, Kids, N0, N, Intervals0, Intervals
dominates_by_interval+Intervals, +A, +Bsemidet

A dominates B, in constant time, given Intervals from dom_intervals/3.

255dominates_by_intervalIntervals, A, B ←
get_assocA, Intervals, PreA−LastA
get_assocB, Intervals, PreB−_
PreA ≤ PreB
PreB ≤ LastA
cfg_frontiers+Cfg, +Idom, -DFdet

DF is the assoc Label -> ordset of the labels in its dominance frontier, for every reachable label. As in CHK, a join point is in the frontier of each label on the idom path from each of its predecessors up to (not including) its own idom.

268cfg_frontierscfg_, Rpo, _, Preds, Idom, DF ←
findallL−[], memberL, Rpo, Empty
list_to_assocEmpty, DF0
foldljoin_frontiersPreds, Idom, Rpo, DF0, DF1
assoc_to_listDF1, Pairs0
findallL−S,
memberL−Xs, Pairs0
sortXs, S
,
Pairs
list_to_assocPairs, DF
join_frontiers+Preds, +Idom, +B, +DF0, -DF

DF is DF0 with B in the frontiers it joins, if it has two or more reachable predecessors. DF0 has a key for every reachable label, so it also tells which predecessors are reachable.

284join_frontiersPreds, Idom, B, DF0, DF ←
get_assocB, Preds, Ps0
includehas_frontierDF0, Ps0, Ps
Ps = _, _|_
get_assocB, Idom, IdomB
→
foldlrunner_frontiersIdom, IdomB, B, Ps, DF0, DF
else
DF = DF0
293has_frontierDF, L ←
get_assocL, DF, _
runner_frontiers+Idom, +Stop, +B, +Runner, +DF0, -DF

DF is DF0 with B in the frontier of every label on the idom path from Runner up to (not including) Stop.

299runner_frontiersIdom, Stop, B, Runner, DF0, DF ←
Runner ≡ Stop
→
DF = DF0
else
get_assocRunner, DF0, Xs
put_assocRunner, DF0, B|Xs, DF1
get_assocRunner, Idom, Up
runner_frontiersIdom, Stop, B, Up, DF1, DF

Natural loops

natural_loops+Cfg, +Idom, -Loopsdet

Loops is the assoc Header -> loop(Latches, Body), both ordsets of labels. A latch has an edge to a header that dominates it. Body is the union of their natural loops. Only reachable blocks participate. Nested loops have separate entries; an irreducible cycle without a dominating header is NOT a natural loop. Empty does not mean acyclic, and this analysis alone cannot justify removing polls.

319natural_loopsCfg, Idom, Loops ←
Cfg = cfg_, Rpo, Succs, Preds
dom_intervalsCfg, Idom, Intervals
findallHeader−Latch,
memberLatch, Rpo
get_assocLatch, Succs, Ss
memberHeader, Ss
dominates_by_intervalIntervals, Header, Latch
,
Edges0
sortEdges0, Edges
group_pairs_by_keyEdges, Headers
maplistnatural_loopPreds, Intervals, Headers, Pairs
list_to_assocPairs, Loops
334natural_loopPreds, Intervals, H−Latches, H−loopLatches, Body ←
empty_assocEmpty
Mark the header before walking: its predecessors are outside the
natural loop (also essential for a one-block self loop).
put_assocH, Empty, true, Seen0
loop_bodyLatches, Preds, Intervals, Seen0, Seen
assoc_to_keysSeen, Body
loop_body+Pending, +Preds, +Intervals, +Seen0, -Seen

Seen is Seen0 with the reachable labels from which a label in Pending is reached without passing through Seen0; a label without an interval is unreachable. Invariant: Seen0 holds the header and the body blocks found so far, and every reachable predecessor of a body block in Seen0 is in Seen0 or Pending.

348loop_body[], _, _, Seen, Seen
349loop_bodyB|Pending, Preds, Intervals, Seen0, Seen ←
get_assocB, Seen0, _
→
loop_bodyPending, Preds, Intervals, Seen0, Seen
¬get_assocB, Intervals, _
→
loop_bodyPending, Preds, Intervals, Seen0, Seen
else
put_assocB, Seen0, true, Seen1
get_assocB, Preds, Ps
appendPs, Pending, More
loop_bodyMore, Preds, Intervals, Seen1, Seen

Liveness

liveness+Cfg, +GenKill, +EdgeUses, -LiveIn, -LiveOutdet

LiveIn and LiveOut are the assocs Label -> ordset of the names live on entry to and exit from each block (every block, reachable or not), the least solution of the backward dataflow equations

LiveOut(B) = union over successors S of LiveIn(S) and EdgeUses(B-S)
LiveIn(B)  = Gen(B) ∪ (LiveOut(B) \ Kill(B))

GenKill is the assoc Label -> Gen-Kill (ordsets: the names read before any write in the block, and the names written). EdgeUses is the assoc (Pred-Succ) -> ordset of the names read on that edge only (phi arguments). Names are anything ordsets can hold, so the same relation serves temps (temp_liveness/3) and promote's memory slots.

377livenesscfg_, Rpo, Succs, Preds, GenKill, EdgeUses, LiveIn, LiveOut ←
assoc_to_keysSuccs, All
subtractAll, Rpo, Unreached
reverseRpo, Post
appendPost, Unreached, Order
labels_numberedOrder, 0, Numbered
list_to_assocNumbered, Num
findallN−L, memberL−N, Numbered, Pending
findallL−[], memberL, All, Empty
list_to_assocEmpty, LiveIn0
liveness_worklistPending, Num, Succs, Preds, GenKill, EdgeUses, LiveIn0, LiveIn
findallL−Out,
memberL, All
live_outL, Succs, EdgeUses, LiveIn, Out
,
OutPairs
list_to_assocOutPairs, LiveOut
liveness_worklist+Pending, +Num, +Succs, +Preds, +GenKill, +EdgeUses, +LiveIn0, -LiveIn

LiveIn is the least solution above LiveIn0, given that only the blocks in Pending may violate the equations. Pending is an ordset of N-L, N the position of L in postorder, so the pending block first in postorder is next; when a block's live-in set grows, its predecessors are pending again.

402liveness_worklist[], _, _, _, _, _, LiveIn, LiveIn
403liveness_worklist_−L|Pending0, Num, Succs, Preds, GenKill, EdgeUses, LiveIn0, LiveIn ←
live_outL, Succs, EdgeUses, LiveIn0, Out
get_assocL, GenKill, Gen−Kill
ord_subtractOut, Kill, Through
ord_unionGen, Through, New
get_assocL, LiveIn0, Old
Old ≡ New
→
liveness_worklistPending0, Num, Succs, Preds, GenKill, EdgeUses, LiveIn0, LiveIn
else
put_assocL, LiveIn0, New, LiveIn1
get_assocL, Preds, PredLabels
findallN−P,
memberP, PredLabels
get_assocP, Num, N
,
Requeued0
sortRequeued0, Requeued
ord_unionPending0, Requeued, Pending
liveness_worklistPending, Num, Succs, Preds, GenKill, EdgeUses, LiveIn1, LiveIn
live_out+L, +Succs, +EdgeUses, +LiveIn, -Out

Out is the live-out set of L, given the live-in sets LiveIn.

428live_outL, Succs, EdgeUses, LiveIn, Out ←
get_assocL, Succs, Ss
foldlsucc_liveL, EdgeUses, LiveIn, Ss, [], Out
432succ_liveL, EdgeUses, LiveIn, S, Out0, Out ←
get_assocS, LiveIn, SuccIn
ord_unionOut0, SuccIn, Out1
get_assocL−S, EdgeUses, Us
→
ord_unionOut1, Us, Out
else
Out = Out1

Temps

temp_liveness+Blocks, -LiveIn, -LiveOutdet

LiveIn and LiveOut are liveness/5 of Blocks over temp names. A phi defines its temp at block entry (in Kill, never in LiveIn) and reads its argument on the edge.

448temp_livenessBlocks, LiveIn, LiveOut ←
func_cfgBlocks, Cfg
blocks_gen_killBlocks, GenKill
phi_edge_usesBlocks, EdgeUses
livenessCfg, GenKill, EdgeUses, LiveIn, LiveOut
blocks_gen_kill+Blocks, -GenKilldet

GenKill is the assoc Label -> Gen-Kill for each block: Gen is the ordset of temps read before they are written in it, and Kill the ordset of temps written (phi destinations included).

460blocks_gen_killBlocks, GenKill ←
findallL−Gen−Kill,
memberB, Blocks
B = blockL, _, _, _
block_gen_killB, Gen, Kill
,
Pairs
list_to_assocPairs, GenKill
469block_gen_killblock_, Phis, Insts, Jump, Gen, Kill ←
findallD, memberphitmpD, _, _, Phis, PhiDefs
sortPhiDefs, Kill0
foldlinst_gen_kill, Insts, []−Kill0, Gen1−Kill
jump_usesJump, JumpUses
ord_subtractJumpUses, Kill, JumpGen
ord_unionGen1, JumpGen, Gen
477inst_gen_killI, Gen0−Kill0, Gen−Kill ←
ord_subtractUs, Kill0, New
ord_unionGen0, New, Gen
findallD, inst_defI, D, Ds0
sortDs0, Ds
ord_unionKill0, Ds, Kill
phi_edge_uses+Blocks, -EdgeUsesdet

EdgeUses is the assoc (Pred-Succ) -> ordset of the temps that the phis of Succ read on the edge from Pred, for the edges that have any.

490phi_edge_usesBlocks, EdgeUses ←
findallP−S−T,
memberblockS, Phis, _, _, Blocks
memberphi_, _, Args, Phis
memberP−tmpT, Args
,
Pairs0
keysortPairs0, Pairs1
group_pairs_by_keyPairs1, Groups
findallEdge−S,
memberEdge−Ts, Groups
sortTs, S
,
Pairs
list_to_assocPairs, EdgeUses
inst_uses+Inst, -Usesdet

Uses is the ordset of temps that the instruction Inst reads.

510inst_useslet_, _, _, Args, Us ←
tempsArgs, Us
512inst_usesflight_, _, Args, Lower, Us ←
findallV,
memberA, Args
,
Vs
Lower = lowerP
→
tempsP|Vs, Us
else
tempsVs, Us
515inst_usesstore_, V, A, Us ←
tempsV, A, Us
517inst_usesraw_store_, _, V, A, Us ←
tempsV, A, Us
519inst_usesraw_store_pointer_, V, A, _, Us ←
tempsV, A, Us
521inst_usescall_, F, Args, Us ←
findallV, memberarg_, V, Args, Vs
tempsF|Vs, Us
524inst_usesraw_callRes, F, Args, Us ←
inst_usescallRes, F, Args, Us
526inst_usesloc_, _, []
527inst_useslocal_alloc_, _, []
528inst_useslocal_load_, _, _, _, _, []
529inst_useslocal_store_, V, _, _, Us ←
tempsV, Us

Only flight's address projection wraps a value. Do not traverse temp names (opaque ground terms) or mistake a destination for a use.

flight_arg_value
533addressPP
534tmpNtmpN
535constNconstN
inst_def+Inst, -Tempnondet

The instruction Inst defines Temp (a call can define several).

541inst_deflettmpD, _, _, _, D
542inst_defflighttmpD, _, _, _, D
543inst_deflocal_loadtmpD, _, _, _, _, D
544inst_defcallresulttmpD, _, _, _, D
545inst_defcalltupleRs, _, _, D ←
memberresulttmpD, _, Rs
547inst_defraw_callRes, F, Args, D ←
inst_defcallRes, F, Args, D
jump_uses+Jump, -Usesdet

Uses is the ordset of temps that Jump reads.

554jump_usesgoto_, []
555jump_usesbranchV, _, _, Us ←
tempsV, Us
557jump_usesretV, Us ←
tempsV, Us
559jump_usesret, []
560jump_usesretsVs, Us ←
tempsVs, Us
562jump_usesunreachable, []
temps+Values, -Names

Names is the ordset of temp names among Values.

566tempsVs, Ts ←
findallT, membertmpT, Vs, Ts0
sortTs0, Ts