rocq/theories/Cfg.v
The control-flow graph (CFG) of a function and its dominators, transcribed from cfg.pl: func_cfg/2, cfg_idoms/2 (Cooper, Harvey and Kennedy, "A Simple, Fast Dominance Algorithm"), dom_children/3, dom_intervals/3 and dominates_by_interval/3. The validator asks "does A dominate B?" for every use of every temporary, and the dominator tree's preorder intervals answer it in constant time, as they do for ir.pl.
Parts
Orders
cfg.pl keeps a block's successors and predecessors as ordsets, sorted in the standard order of terms. The model keeps a jump's own order and does not sort. That changes the reverse postorder, the order of the sweeps and the preorder numbers, but not the immediate dominators, which are unique, nor the relation dominates_by_interval states, which is all ir.pl reads from them.
Fuel
The Prolog recursions end because each visits a block at most once or walks up a finite tree; the Gallina ones carry fuel that bounds the same walk, and give no answer if it runs out, which a correct bound never lets happen. The bounds are noted at each.
Option binding: let* x ≔ e in b is b with x the value of e, or none if e has none.
The graph
As jump_succs(Jump, Succs): the labels the jump j can go to, in its own order, a poll edge's included.
As cfg(Entry, Rpo, Succs, Preds): a function's control-flow graph, over its block labels.
| cfg_entry | : name | the entry block's label |
| cfg_rpo | : listname | the labels reachable from the entry, in reverse postorder |
| cfg_succs | each block's successors | |
| cfg_preds | each block's predecessors, unreachable ones included |
As reverse_postorder(Ns, Succs, Seen0, Seen, Post0, Post): depth first from each of Ns in turn, a label's successors before the labels after it; Post has the reverse postorder in front of Post0. Along any chain of nested calls, each call but the last takes one element of some list the walk has met (the entry or a block's successors), and the last is on an empty list, so the fuel is two more than the number of edges.
As func_cfg(Blocks, Cfg): the graph of the blocks bs, whose entry is the first block; each block's predecessors include unreachable ones. No graph when an edge targets a label that is no block's, where label_preds/3 fails.
Immediate dominators
As labels_numbered(Labels, 0, Pairs): each label of ls with its position in the list, from 0.
As common_dominator(Num, Idom, A, B, Common): the nearest common dominator of the labels a and b in the tree of immediate dominators idom found so far, num being reverse postorder numbers. Walk up from the later of a and b until they meet. Each step moves one of them to its immediate dominator (idom), earlier in reverse postorder, so twice the number of labels is enough fuel.
As label_idoms(Preds, Num, B, Idom0-Changed0, Idom-Changed): one step of a sweep, at the label b. Its immediate dominator becomes the common_dominator of its predecessors that have one so far, and the flag records whether it changed.
As idom_fixpoint(Labels, Preds, Num, Idom0, Idom): sweeps of label_idoms over the labels ls in reverse postorder until one changes nothing. A sweep changes an idom only to a label earlier in reverse postorder, and the sweeps settle within the graph's loop nesting depth plus two (Cooper, Harvey and Kennedy), so one more sweep than labels is enough fuel.
As cfg_idoms(Cfg, Idom): each reachable label of the graph g but the entry, with its immediate dominator, the nearest block every path from the entry to it passes through.
del_assoc(Entry, ...): the entry is its own idom only during the sweeps.
The dominator tree's intervals
As dom_children(Cfg, Idom, Kids): each reachable label's children in the dominator tree, the labels it is the immediate dominator of, in reverse postorder.
As subtree_intervals(L, Kids, N0, N, Intervals0, Intervals): number the subtree at the label l in preorder from n0, giving each label its number and the last number of its subtree, and return the next free number. The tree's depth is at most the number of labels, the fuel.
As dom_intervals(Cfg, Idom, Intervals): each reachable label's preorder number in the dominator tree and the last one of its subtree, its dominator interval.
As dominates_by_interval(Intervals, A, B): the label a dominates b, both reachable: every path from the entry to b passes through a, which holds when b's preorder number lies in a's interval. A label dominates itself.
The dominator intervals of a function's blocks bs: func_cfg, cfg_idoms and dom_intervals in turn, as definitions_dominate/3 and stage_sources/4 compute them.