Lesson 5: Global Analysis

Gist

Dominators

Lots of definitions!

An algorithm for finding dominators:

dom = {every block -> all blocks}
dom[entry] = {entry}
while dom is still changing:
    for vertex in CFG except entry:
        dom[vertex] = {vertex} ∪ ⋂(dom[p] for p in vertex.preds}

The dom relation will, in the end, map each block to its set of dominators. We initialize it as the “complete” relation, i.e., mapping every block to the set of all blocks. The exception is the entry block, which we ensure always only has itself as a dominator. (This keeps the algorithm from being confused by blocks that jump back to the entry node.) The loop pares down the sets by iterating to convergence.

The running time is O(n²) in the worst case. But there’s a trick: if you iterate over the CFG in reverse post-order, and the CFG is well behaved (reducible), it runs in linear time—the outer loop runs a constant number of times.

Natural Loops

Some things about loops:

Loop-Invariant Code Motion (LICM)

And finally, loop-invariant code motion (LICM) is an optimization that works on natural loops. It moves code from inside a loop to before the loop, if the computation always does the same thing on every iteration of the loop.

A loop’s preheader is its header’s unique predecessor. LICM moves code to the preheader. But while natural loops need to have a unique header, the header does not necessarily have a unique predecessor. So it’s often convenient to invent an empty preheader block that jumps directly to the header, and then move all the in-edges to the header to point there instead.

LICM needs two ingredients: identifying loop-invariant instructions in the loop body, and deciding when it’s safe to move one from the body to the preheader.

To identify loop-invariant instructions:

iterate to convergence:
    for every instruction in the loop:
        mark it as LI iff, for all arguments x, either:
            all reaching defintions of x are outside of the loop, or
            there is exactly one definition, and it is already marked as
                loop invariant

(This determination requires that you already calculated reaching definitions! Presumably using data flow.)

It’s safe to move a loop-invariant instruction to the preheader iff:

The last criterion is somewhat tricky: it ensures that the computation would have been computed eventually anyway, so it’s safe to just do it earlier. But it’s not true of loops that may execute zero times, which, when you think about it, rules out most for loops! It’s possible to relax this condition if:

Tasks