Now let's look at an example of
control flow graph manipulation. Let's write a pass that will merge two basic
blocks which are immediately adjacent where the first basic block has no other
successors and the second basic block has no other predecessors. Obviously, we
need to ignore the entry and exit basic blocks. Additionally, let's maintain
exception semantics by requiring that the exception handler set is the same for
both basic blocks.
Code goes here.
This one is a little more involved. When
visiting a basic block, we look at the predecessors of the current basic block.
By merging basic blocks with their predecessors rather than their successors,
we can avoid having to re-compute the traversal order if we traverse forward
through the graph.
We check that the basic block is not the
exit node and that there is only one predecessor. Likewise, we check that the
predecessor has only one successor and that the predecessor is not the entry
node. Finally, we check the equality of the exception handlers. If all of these
tests pass, the basic blocks are mergeable.
We check the last quad in the previous
basic block; if it is a branch we remove it. We add all of the quads in the
current basic block to the previous basic block, merge the flags of the two
basic blocks, remove the successor edge between them, and add all successors of
the second basic block to the first basic block. And the blocks are merged!
These two techniques are interchangeable,
but in some cases it is more convenient to use one rather than the other.
Visitors are good because they separate the
traversal order from the operations performed on each element. Because they are
objects, they can be passed around and be used to store results. They also
allow advanced selection of objects based on their type, a la QuadVisitor.
However, visitors require a class
definition and are therefore cumbersome for simple traversals. If a traversal
is very simple and doesn't warrant a new class definition, it may be better to
use an iterator.