This package includes classes for building control-flow graphs. While the basics were designed with Java in mind, the classes here are not specific to Java. Java-specific additions to the classes are defined in {@link fluid.java.control} and in specific operator files in {@link fluid.java.operator}. Java specific analyses using these nodes are defined in {@link fluid.java.analysis}
The control-flow graph is built with explicit nodes and edges and can be traversed in either direction. (The alert reader may notice that this implies the structure is a symmetric edge directed graph. We are in the process of fitting the graph to the {@link fluid.tree.SymmetricEdgeDigraphInterface} interface.) The edges carry analysis information and inherit from {@link fluid.control.ControlEdge}. The nodes potentially affect analysis information flowing through them; they inherit from {@link fluid.control.ControlNode}. Apart from ports (see below), all control nodes fall into one of five abstract classes depending on how many input edges and output edges they have, as pictorially represented in the following diagram:
+-@--{@link fluid.control.Flow}--@-+ / \ {@link fluid.control.Source}---@---{@link fluid.control.Split} {@link fluid.control.Join}---@---{@link fluid.control.Sink} \ / +-----@------+(If one needs, say, a three way split, one must use two split nodes.) The actual class of node determines the semantic action. For example a {@link fluid.control.Merge} node (inheriting from {@link fluid.control.Join}) leads to the ``meeting'' of two lattice values in forward data-flow analysis. For a complete list of nodes other than ports, see below.
This package includes the capability to place two or more independent flows on the same edge. For example a {@link fluid.control.TrackedMerge} node can be used to join two control flows into a single edge. Later in the graph, a {@link fluid.control.TrackedDemerge} node can be used to separate them. A good example of using this for Java is for finally clauses of throw statements: no matter whether the try clause terminates normally or abruptly, the finally clause is always executed. Rather than copy the finally clause for the two flows, or worse, to merge them, the control-flow graph gives each an independent flow of control through the finally clause. When the clause terminates, the flows are separated again. The ability to have independent flows on a single edge is also used to separate exception throws from control-flow caused by break or continue. Internally each flow is named by a sequence (see {@link fluid.control.LabelList}) of control-flow labels (see {@link fluid.control.ControlLabel}).
The control-flow graph is highly structured, following the abstract syntax of the program. Each abstract syntax node has an associate control flow component (see {@link fluid.control.Component}). All control flow enters or leaves a component only through a fixed number of ports (see {@link fluid.control.Port}). That means even control flow caused by thrown exceptions or method returns must be directed through some port. Each component has three ports, each of a different type:
The control-flow graph is constructed by associating a fixed control-flow component with every syntax node participating in control-flow. The association is carried out by an instance of {@link fluid.control.ComponentFactory}. If a node has children which participate in control flow, then its component includes subcomponents (see {@link fluid.control.Subcomponent}) which are in essence holes in the control-flow graph. The subcomponent has ports that correspond to the ports of the component for the corresponding child. The ports of the subcomponent are never directly connected to the ports of the component of the child. Instead, the connection is computed whenever analysis comes up to a port. For instance, if analysis follows control-flow out through a port of a subcomponent, the appropriate child node of the current node is found and its component determined. Then analysis continues with the appropriate port of this component. This all happens invisibly to individual analyses. This indirection enables the control-flow graph to be up-to-date at all times.
Handling nodes with a varying number of children is more difficult. All the children are linked together with a certain number of edges (specified by the component). The component (which must be an instance of the class {@link fluid.control.VariableComponent}) also determines how control-flow enters and leaves the sequence and for every child how the sequence edges connect up to its ports (or not).
Here is a listing of all node types except for ports:
This package also includes control-flow analysis template classes. The basic worklisting algorithm is specified in the abstract class {@link fluid.control.FlowAnalysis}. This class is refined in {@link fluid.control.ForwardAnalysis} and {@link fluid.control.BackwardAnalysis}. These two classes must be parameterized by classes implementing {@link fluid.control.ForwardTransfer} and {@link fluid.control.BackwardTransfer} respectively. The transfer classes implement the language-specific actions. All other details are handled by these generic classes: ports, components, and labels. We will discuss only labels here.
An edge can have a different control-flow analysis value for any sequence of labels. These sequences are affected by only three control-flow nodes: {@link fluid.control.AddLabel}, {@link fluid.control.LabelTest}, and {@link fluid.control.PendingLabelStrip}. The sequence is treated as a stack: we only make changes to the front, adding or removing the first label. During analysis, the actual effect on the sequence is determined by the direction of analysis (forward or backward) and by language specific actions (as we shall see):