// ------------------------- // Implementation rationale // ------------------------- // // Two key observations are: // // - Topological orderings of nodes for directed acyclic graphs (DAGs) are orderings that "respect" dominance // without actually needing to compute the entire dominator tree. Nodes sorted by dominance are easier // to read, since they resemble structured flow more. If node A dominates B, it would mean that in the // resulting ordering, node A will appear before node B, as it would appear in "normal" programs. // For cyclic graphs we can simply ignore back-edges to turn the input graph into a DAG. // // - Paths constructed by fallthrough edges in the control flow graph cannot be broken up into smaller // paths in the resulting node sequence without changing the code of the basic block (e.g. inserting a goto). // This puts constraints on what kind of topological orderings we can construct. // // In this implementation, we create a "view" on the input control flow graph, that contracts nodes in a single // path induced by fallthrough edges into a single node. It is safe to put the nodes of this new graph in any // ordering without invalidating the requirements for fallthrough edges, since the new nodes never have outgoing // fallthrough edges by construction. The exception to this rule is the entry point node, which always has to // start at the beginning of the sequence. Therefore, doing a topological sorting starting at this entry point // node results in a valid sequence of basic blocks that is reasonably readable. // // There is still some form of "non-determinism" in the algorithm, as neighbours of each node are still somewhat // arbitrarily ordered. As a result, an exit point block (e.g. a block with a return) might still end up // somewhere in the middle of the sequence, which can be somewhat counter-intuitive. /// <summary> /// Determines an ordering of nodes in the control flow graph in such a way that the basic blocks can be /// concatenated together in sequence, and still result in a valid execution of the original program. /// </summary> /// <param name="cfg">The control flow graph to pull the nodes from.</param> /// <typeparam name="TInstruction">The type of instructions stored in the graph.</typeparam> /// <returns>The ordering.</returns> public static IEnumerable <ControlFlowNode <TInstruction> > SortNodes <TInstruction>( this ControlFlowGraph <TInstruction> cfg) { var pathsView = DetermineUnbreakablePaths(cfg); var sorter = new TopologicalSorter <ControlFlowNode <TInstruction> >(pathsView.GetImpliedNeighbours, true); return(sorter .GetTopologicalSorting(cfg.Entrypoint) .Reverse() .SelectMany(n => pathsView.GetUnbreakablePath(n))); }
/// <summary> /// Collects all dependency nodes recursively, and sorts them in a topological order such that the final collection /// of nodes can be executed sequentially. /// </summary> /// <param name="node">The node to find all dependencies for.</param> /// <param name="flags">Flags that influence the behaviour of the algorithm.</param> /// <typeparam name="T">The type of contents that each node contains.</typeparam> /// <returns>The topological ordering of all dependencies of the node.</returns> /// <exception cref="CyclicDependencyException">Occurs when there is a cyclic dependency in the graph.</exception> public static IEnumerable <DataFlowNode <T> > GetOrderedDependencies <T>(this DataFlowNode <T> node, DependencyCollectionFlags flags) { try { var topologicalSorting = new TopologicalSorter <DataFlowNode <T> >(GetSortedOutgoingEdges); return(topologicalSorting.GetTopologicalSorting(node)); } catch (CycleDetectedException ex) { throw new CyclicDependencyException("Cyclic dependency was detected.", ex); } IReadOnlyList <DataFlowNode <T> > GetSortedOutgoingEdges(DataFlowNode <T> n) { var result = new List <DataFlowNode <T> >(); // Prioritize stack dependencies over variable dependencies. if ((flags & DependencyCollectionFlags.IncludeStackDependencies) != 0) { foreach (var dependency in n.StackDependencies) { if (dependency.HasKnownDataSources) { result.Add(dependency.First().Node); } } } if ((flags & DependencyCollectionFlags.IncludeVariableDependencies) != 0) { foreach (var entry in n.VariableDependencies) { if (entry.Value.HasKnownDataSources) { result.Add(entry.Value.First().Node); } } } return(result); } }
/// <summary> /// Constructs the tree of scopes and basic blocks based on the provided control flow graph. /// </summary> /// <param name="cfg">The control flow graph.</param> /// <returns>The root scope.</returns> public ScopeBlock <TInstruction> ConstructBlocks(ControlFlowGraph <TInstruction> cfg) { // Sort the nodes in topological order, where we ignore cyclic dependencies. var sorter = new TopologicalSorter <ControlFlowNode <TInstruction> >(ChildrenLister) { IgnoreCycles = true }; var sorting = sorter .GetTopologicalSorting(cfg.Entrypoint) .Reverse() #if DEBUG .ToArray() #endif ; // We maintain a stack of scope information. Every time we enter a new region, we enter a new scope, // and similarly, we leave a scope when we leave a region. var rootScope = new ScopeBlock <TInstruction>(); var scopeStack = new IndexableStack <ScopeInfo>(); scopeStack.Push(new ScopeInfo(cfg, rootScope)); // Add the nodes in the order of the sorting. foreach (var node in sorting) { var currentScope = scopeStack.Peek(); if (node.ParentRegion != currentScope.Region) { UpdateScopeStack(node, scopeStack); currentScope = scopeStack.Peek(); } currentScope.AddBlock(node.Contents); } return(rootScope); }