The graph build algorithm considers dependencies between elements of
a given set of instructions or basic blocks. Any dependencies coming
into or going out of instructions that do not belong to that range
are ignored. The steps in the build algorithm for the DDG are very
similar to the steps in the build algorithm for the PDG. As such,
one of the design goals is to reuse the build algorithm code to
allow creation of both DDG and PDG representations while allowing
the two implementations to define their own distinct and independent
node and edge types. This is achieved by using the well-known builder
design pattern to isolate the construction of the dependence graph
from its concrete representation.
The following UML diagram depicts the overall structure of the design
pattern as it applies to the dependence graph implementation.
..image:: uml_builder_pattern.png
Notice that the common code for building the two types of graphs are
provided in the ``DependenceGraphBuilder`` class, while the ``DDGBuilder``
and ``PDGBuilder`` control some aspects of how the graph is constructed
by the way of overriding virtual methods defined in ``DependenceGraphBuilder``.
Note also that the steps and the names used in this diagram are for
illustrative purposes and may be different from those in the actual
implementation.
Design Trade-offs
-----------------
Advantages:
^^^^^^^^^^^
- Builder allows graph construction code to be reused for DDG and PDG.
- Builder allows us to create DDG and PDG as separate graphs.
- DDG nodes and edges are completely disjoint from PDG nodes and edges allowing them to change easily and independently.
Disadvantages:
^^^^^^^^^^^^^^
- Builder may be perceived as over-engineering at first.
- There are some similarities between DDG nodes and edges compared to PDG nodes and edges, but there is little reuse of the class definitions.
- This is tolerable given that the node and edge types are fairly simple and there is little code reuse opportunity anyway.
.._implementation-details:
Implementation Details
======================
The current implementation of DDG differs slightly from the dependence
graph described in [1]_ in the following ways:
1. The graph nodes in the paper represent three main program components, namely *assignment statements*, *for loop headers* and *while loop headers*. In this implementation, DDG nodes naturally represent LLVM IR instructions. An assignment statement in this implementation typically involves a node representing the ``store`` instruction along with a number of individual nodes computing the right-hand-side of the assignment that connect to the ``store`` node via a def-use edge. The loop header instructions are not represented as special nodes in this implementation because they have limited uses and can be easily identified, for example, through ``LoopAnalysis``.
2. The paper describes five types of dependency edges between nodes namely *loop dependency*, *flow-*, *anti-*, *output-*, and *input-* dependencies. In this implementation *memory* edges represent the *flow-*, *anti-*, *output-*, and *input-* dependencies. However, *loop dependencies* are not made explicit, because they mainly represent association between a loop structure and the program elements inside the loop and this association is fairly obvious in LLVM IR itself.
3. The paper describes two types of pi-blocks; *recurrences* whose bodies are SCCs and *IN* nodes whose bodies are not part of any SCC. In this implementation, pi-blocks are only created for *recurrences*. *IN* nodes remain as simple DDG nodes in the graph.
..[1] "D. J. Kuck, R. H. Kuhn, D. A. Padua, B. Leasure, and M. Wolfe (1981). DEPENDENCE GRAPHS AND COMPILER OPTIMIZATIONS."
..[2] "J. FERRANTE (IBM), K. J. OTTENSTEIN (Michigan Technological University) and JOE D. WARREN (Rice University), 1987. The Program Dependence Graph and Its Use in Optimization."