package org.dynjs.ir.representations; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Stack; import org.dynjs.ir.Instruction; import org.dynjs.ir.Scope; import org.dynjs.ir.instructions.Branch; import org.dynjs.ir.instructions.ExceptionRegionEndMarker; import org.dynjs.ir.instructions.ExceptionRegionStartMarker; import org.dynjs.ir.instructions.Jump; import org.dynjs.ir.instructions.LabelInstr; import org.dynjs.ir.instructions.Return; import org.dynjs.ir.instructions.ThrowException; import org.dynjs.ir.operands.Label; import org.jruby.dirgra.DirectedGraph; import org.jruby.dirgra.Edge; /** */ public class CFG { public enum EdgeType { REGULAR, // Any non-special edge. Not really used. EXCEPTION, // Edge to exception handling basic blocks FALL_THROUGH, // Edge which is the natural fall through choice on a branch EXIT // Edge to dummy exit BB } private Scope scope; private Map<Label, BasicBlock> bbMap; // Map of bb -> first bb of the rescue block that initiates exception handling for all exceptions thrown within this bb private Map<BasicBlock, BasicBlock> rescuerMap; private BasicBlock entryBB; private BasicBlock exitBB; /** * BB that traps all exception-edges out of the CFG. frame popping & clean up loc. */ private BasicBlock globalEnsureBB; private DirectedGraph<BasicBlock> graph; private int nextBBId; // Next available basic block id LinkedList<BasicBlock> postOrderList; // Post order traversal list of the cfg public CFG(Scope scope) { this.scope = scope; this.graph = new DirectedGraph<BasicBlock>(); this.bbMap = new HashMap<Label, BasicBlock>(); this.rescuerMap = new HashMap<BasicBlock, BasicBlock>(); this.nextBBId = 0; this.entryBB = this.exitBB = null; this.globalEnsureBB = null; this.postOrderList = null; } public int getNextBBID() { nextBBId++; return nextBBId; } public BasicBlock getEntryBB() { return entryBB; } public BasicBlock getExitBB() { return exitBB; } /** * How many BasicBlocks are there in this CFG? */ public int size() { return graph.size(); } /** * Build the Control Flow Graph */ public DirectedGraph<BasicBlock> build(List<Instruction> instructions) { // Map of label & basic blocks which are waiting for a bb with that label Map<Label, List<BasicBlock>> forwardRefs = new HashMap<Label, List<BasicBlock>>(); // List of bbs that have a 'return' instruction List<BasicBlock> returnBBs = new ArrayList<BasicBlock>(); // List of bbs that have a 'throw' instruction List<BasicBlock> exceptionBBs = new ArrayList<BasicBlock>(); // Stack of nested rescue regions Stack<ExceptionRegion> nestedExceptionRegions = new Stack<ExceptionRegion>(); // List of all rescued regions List<ExceptionRegion> allExceptionRegions = new ArrayList<ExceptionRegion>(); // Dummy entry basic block (see note at end to see why) entryBB = createBB(nestedExceptionRegions); // First real bb BasicBlock firstBB = createBB(nestedExceptionRegions); // Build the rest! BasicBlock currBB = firstBB; BasicBlock newBB; boolean bbEnded = false; boolean nextBBIsFallThrough = true; for (Instruction i : instructions) { if (i instanceof LabelInstr) { Label l = ((LabelInstr) i).getLabel(); newBB = createBB(l, nestedExceptionRegions); // Jump instruction bbs dont add an edge to the succeeding bb by default if (nextBBIsFallThrough) graph.addEdge(currBB, newBB, EdgeType.FALL_THROUGH); currBB = newBB; bbEnded = false; nextBBIsFallThrough = true; // Add forward reference edges List<BasicBlock> frefs = forwardRefs.get(l); if (frefs != null) { for (BasicBlock b : frefs) { graph.addEdge(b, newBB, EdgeType.REGULAR); } } } else if (bbEnded && !(i instanceof ExceptionRegionEndMarker)) { newBB = createBB(nestedExceptionRegions); // Jump instruction bbs dont add an edge to the succeeding bb by default if (nextBBIsFallThrough) graph.addEdge(currBB, newBB, EdgeType.FALL_THROUGH); // currBB cannot be null! currBB = newBB; bbEnded = false; nextBBIsFallThrough = true; } if (i instanceof ExceptionRegionStartMarker) { // We dont need the instruction anymore -- so it is not added to the CFG. ExceptionRegionStartMarker ersmi = (ExceptionRegionStartMarker) i; ExceptionRegion rr = new ExceptionRegion(ersmi.getLabel(), currBB); rr.addBB(currBB); allExceptionRegions.add(rr); if (!nestedExceptionRegions.empty()) { nestedExceptionRegions.peek().addNestedRegion(rr); } nestedExceptionRegions.push(rr); } else if (i instanceof ExceptionRegionEndMarker) { // We dont need the instruction anymore -- so it is not added to the CFG. nestedExceptionRegions.pop().setEndBB(currBB); } else if (i.transfersControl()) { bbEnded = true; currBB.addInstr(i); Label target; nextBBIsFallThrough = false; if (i instanceof Branch) { target = ((Branch) i).getTarget(); nextBBIsFallThrough = true; } else if (i instanceof Jump) { target = ((Jump) i).getTarget(); } else if (i instanceof Return) { target = null; returnBBs.add(currBB); } else if (i instanceof ThrowException) { target = null; exceptionBBs.add(currBB); } else { throw new RuntimeException("Unhandled case in CFG builder for basic block ending instr: " + i); } if (target != null) addEdge(currBB, target, forwardRefs); } else if (!(i instanceof LabelInstr)) { currBB.addInstr(i); } } // Process all rescued regions for (ExceptionRegion rr : allExceptionRegions) { // When this exception region represents an unrescued region // from a copied ensure block, we have a dummy label Label rescueLabel = rr.getFirstRescueBlockLabel(); if (!Label.UNRESCUED_REGION_LABEL.equals(rescueLabel)) { BasicBlock firstRescueBB = bbMap.get(rescueLabel); // Mark the BB as a rescue entry BB firstRescueBB.markRescueEntryBB(); // Record a mapping from the region's exclusive basic blocks to the first bb that will start exception handling for all their exceptions. // Add an exception edge from every exclusive bb of the region to firstRescueBB for (BasicBlock b : rr.getExclusiveBBs()) { setRescuerBB(b, firstRescueBB); graph.addEdge(b, firstRescueBB, EdgeType.EXCEPTION); } } } buildExitBasicBlock(nestedExceptionRegions, firstBB, returnBBs, exceptionBBs, nextBBIsFallThrough, currBB, entryBB); optimize(); return graph; } private BasicBlock createBB(Stack<ExceptionRegion> nestedExceptionRegions) { return createBB(scope.getNewLabel(), nestedExceptionRegions); } private BasicBlock createBB(Label label, Stack<ExceptionRegion> nestedExceptionRegions) { BasicBlock basicBlock = new BasicBlock(this, label); addBasicBlock(basicBlock); if (!nestedExceptionRegions.empty()) nestedExceptionRegions.peek().addBB(basicBlock); return basicBlock; } public void addBasicBlock(BasicBlock bb) { graph.findOrCreateVertexFor(bb); // adds vertex to graph bbMap.put(bb.getLabel(), bb); // Reset so later dataflow analyses get all basic blocks postOrderList = null; } public Scope getScope() { return scope; } public void addEdge(BasicBlock source, BasicBlock destination, Object type) { graph.findOrCreateVertexFor(source).addEdgeTo(destination, type); } private void addEdge(BasicBlock src, Label targetLabel, Map<Label, List<BasicBlock>> forwardRefs) { BasicBlock target = bbMap.get(targetLabel); if (target != null) { graph.addEdge(src, target, EdgeType.REGULAR); return; } // Add a forward reference from target -> source List<BasicBlock> forwardReferences = forwardRefs.get(targetLabel); if (forwardReferences == null) { forwardReferences = new ArrayList<BasicBlock>(); forwardRefs.put(targetLabel, forwardReferences); } forwardReferences.add(src); } /** * Create special empty exit BasicBlock that all BasicBlocks will eventually * flow into. All Edges to this 'dummy' BasicBlock will get marked with * an edge type of EXIT. * * Special BasicBlocks worth noting: * 1. Exceptions, Returns, Entry(why?) -> ExitBB * 2. Returns -> ExitBB */ private BasicBlock buildExitBasicBlock(Stack<ExceptionRegion> nestedExceptionRegions, BasicBlock firstBB, List<BasicBlock> returnBBs, List<BasicBlock> exceptionBBs, boolean nextIsFallThrough, BasicBlock currBB, BasicBlock entryBB) { exitBB = createBB(nestedExceptionRegions); graph.addEdge(entryBB, exitBB, EdgeType.EXIT); graph.addEdge(entryBB, firstBB, EdgeType.FALL_THROUGH); for (BasicBlock rb : returnBBs) { graph.addEdge(rb, exitBB, EdgeType.EXIT); } for (BasicBlock rb : exceptionBBs) { graph.addEdge(rb, exitBB, EdgeType.EXIT); } if (nextIsFallThrough) graph.addEdge(currBB, exitBB, EdgeType.EXIT); return exitBB; } public void collapseStraightLineBBs() { // Collect cfgs in a list first since the cfg/graph API returns an iterator // over live data. But, basic block merging modifies that live data. // // SSS FIXME: So, we need a cfg/graph API that returns an iterator over // frozen data rather than live data. List<BasicBlock> cfgBBs = new ArrayList<BasicBlock>(); for (BasicBlock b: getBasicBlocks()) cfgBBs.add(b); Set<BasicBlock> mergedBBs = new HashSet<BasicBlock>(); for (BasicBlock b: cfgBBs) { if (!mergedBBs.contains(b) && (outDegree(b) == 1)) { for (Edge<BasicBlock> e : getOutgoingEdges(b)) { BasicBlock outB = e.getDestination().getData(); if ((e.getType() != EdgeType.EXCEPTION) && (inDegree(outB) == 1) && (mergeBBs(b, outB) == true)) { mergedBBs.add(outB); } } } } } private void deleteOrphanedBlocks(DirectedGraph<BasicBlock> graph) { // System.out.println("\nGraph:\n" + toStringGraph()); // System.out.println("\nInstructions:\n" + toStringInstrs()); // FIXME: Quick and dirty implementation while (true) { BasicBlock bbToRemove = null; for (BasicBlock b : graph.allData()) { if (b == entryBB) continue; // Skip entry bb! // Every other bb should have at least one incoming edge if (graph.findVertexFor(b).getIncomingEdges().isEmpty()) { bbToRemove = b; break; } } if (bbToRemove == null) break; removeBB(bbToRemove); } } public Collection<BasicBlock> getBasicBlocks() { return graph.allData(); } public BasicBlock getBBForLabel(Label label) { return bbMap.get(label); } public Set<Edge<BasicBlock>> getOutgoingEdges(BasicBlock block) { return graph.findVertexFor(block).getOutgoingEdges(); } public BasicBlock getIncomingSourceOfType(BasicBlock block, Object type) { return graph.findVertexFor(block).getIncomingSourceDataOfType(type); } public Iterable<BasicBlock> getOutgoingDestinations(BasicBlock block) { return graph.findVertexFor(block).getOutgoingDestinationsData(); } public BasicBlock getOutgoingDestinationOfType(BasicBlock block, Object type) { return graph.findVertexFor(block).getOutgoingDestinationDataOfType(type); } public Iterable<BasicBlock> getOutgoingDestinationsOfType(BasicBlock block, Object type) { return graph.findVertexFor(block).getOutgoingDestinationsDataOfType(type); } public Iterable<BasicBlock> getOutgoingDestinationsNotOfType(BasicBlock block, Object type) { return graph.findVertexFor(block).getOutgoingDestinationsDataNotOfType(type); } public BasicBlock getRescuerBBFor(BasicBlock block) { return rescuerMap.get(block); } public int inDegree(BasicBlock b) { return graph.findVertexFor(b).inDegree(); } private boolean mergeBBs(BasicBlock a, BasicBlock b) { BasicBlock aR = getRescuerBBFor(a); BasicBlock bR = getRescuerBBFor(b); // We can merge 'a' and 'b' if one of the following is true: // 1. 'a' and 'b' are both not empty // They are protected by the same rescue block. // NOTE: We need not check the ensure block map because all ensure blocks are already // captured in the bb rescue block map. So, if aR == bR, it is guaranteed that the // ensure blocks for the two are identical. // 2. One of 'a' or 'b' is empty. We dont need to check for rescue block match because // an empty basic block cannot raise an exception, can it? if ((aR == bR) || a.isEmpty() || b.isEmpty()) { // First, remove straight-line jump, if present Instruction lastInstr = a.getLastInstr(); if (lastInstr instanceof Jump) a.removeInstr(lastInstr); // Swallow b's instrs. a.swallowBB(b); // Fixup edges graph.removeEdge(a, b); for (Edge<BasicBlock> e : getOutgoingEdges(b)) { addEdge(a, e.getDestination().getData(), e.getType()); } // Delete bb removeBB(b); // Update rescue map if (aR == null && bR != null) { setRescuerBB(a, bR); } return true; } else { return false; } } private void optimize() { List<Edge> toRemove = new ArrayList<Edge>(); for (BasicBlock b : graph.allData()) { boolean noExceptions = true; for (Instruction i : b.getInstructions()) { if (i.canRaiseException()) { noExceptions = false; break; } } if (noExceptions) { for (Edge<BasicBlock> e : graph.findVertexFor(b).getOutgoingEdgesOfType(EdgeType.EXCEPTION)) { BasicBlock source = e.getSource().getData(); BasicBlock destination = e.getDestination().getData(); toRemove.add(e); if (rescuerMap.get(source) == destination) rescuerMap.remove(source); } } } if (!toRemove.isEmpty()) { for (Edge edge: toRemove) { graph.removeEdge(edge); } } deleteOrphanedBlocks(graph); collapseStraightLineBBs(); } public int outDegree(BasicBlock b) { return graph.findVertexFor(b).outDegree(); } public void removeBB(BasicBlock b) { graph.removeVertexFor(b); bbMap.remove(b.getLabel()); rescuerMap.remove(b); } public void setRescuerBB(BasicBlock block, BasicBlock rescuerBlock) { rescuerMap.put(block, rescuerBlock); } public String toStringInstrs() { StringBuilder buf = new StringBuilder(); for (BasicBlock b : graph.getInorderData()) { buf.append(b.toStringInstrs()); } buf.append("\n\n------ Rescue block map ------\n"); List<BasicBlock> e = new ArrayList<BasicBlock>(rescuerMap.keySet()); Collections.sort(e); for (BasicBlock bb : e) { buf.append("BB ").append(bb.getID()).append(" --> BB ").append(rescuerMap.get(bb).getID()).append("\n"); } return buf.toString(); } }