/* * This file is part of the Jikes RVM project (http://jikesrvm.org). * * This file is licensed to You under the Eclipse Public License (EPL); * You may not use this file except in compliance with the License. You * may obtain a copy of the License at * * http://www.opensource.org/licenses/eclipse-1.0.php * * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. */ package org.jikesrvm.compilers.opt.ir; import static org.jikesrvm.compilers.opt.ir.IRDumpTools.determineFileName; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.Enumeration; import org.jikesrvm.classloader.RVMMethod; import org.jikesrvm.compilers.opt.inlining.InlineSequence; import org.jikesrvm.compilers.opt.ir.operand.MethodOperand; import org.jikesrvm.util.Pair; /** * Prints a CFG visualization to a file using the dot output format. Further processing * can be done using tools such as <a href="http://graphviz.org">Graphviz</a> * or graphical frontends such as <a href="http://zvtm.sourceforge.net/zgrviewer.html">ZGRviewer</a>. */ public class CFGVisualization { private static final String CENTERED = "\\n"; private static final String LEFT_JUSTIFIED = "\\l"; @SuppressWarnings("unused") // just here as documentation to make it clear that 0 is taken as a value private static final byte UNVISITED = 0; private static final byte HIGHLIGHTED = 1; private static final byte GRAY = 2; private static final byte BLACK = 3; private final BufferedWriter out; private final IR ir; private final byte[] nodeToColour; public CFGVisualization(IR ir, String tag) throws SecurityException, IOException { String fileName = determineFileName(ir, tag, ".graph"); String dir = ir.options.VISUALIZE_IR_DIRECTORY; if (dir.trim().isEmpty()) { dir = System.getProperty("user.dir"); } File directory = new File(dir); File file = new File(directory, fileName); this.out = new BufferedWriter(new FileWriter(file)); this.ir = ir; nodeToColour = new byte[ir.cfg.numberOfNodes()]; } public void visualizeCFG() throws IOException { out.write("digraph G {\n node [shape=box];\n"); out.write("ENTRY" + "[ label=\"" + "ENTRY" + CENTERED + "\n"); BasicBlock entry = ir.cfg.entry(); out.write(enumerateAndFormatInstructions(entry)); out.write("\"" + formatHighlighting(entry) + "];\n"); dfsCFG(entry, ir); out.write("}"); out.close(); } protected void dfsCFG(BasicBlock bb, IR ir) throws IOException { Enumeration<BasicBlock> successors = bb.getOutNodes(); nodeToColour[bb.getIndex()] = GRAY; while (successors.hasMoreElements()) { BasicBlock succBB = successors.nextElement(); StringWrapper returnObj = setDirectionalEdges(succBB, bb); out.write(returnObj.getStr()); String to = returnObj.getTo(); boolean toNotEmpty = !to.isEmpty(); if (toNotEmpty) { out.write(to + "[ label=\"" + to + CENTERED); } out.write(enumerateAndFormatInstructions(succBB)); if (toNotEmpty) { out.write("\"" + formatHighlighting(succBB) + "];\n"); } byte colour = nodeToColour[succBB.getIndex()]; if (colour != GRAY && colour != BLACK) { dfsCFG(succBB,ir); } } nodeToColour[bb.getIndex()] = BLACK; } /** * Generates control-flow edge descriptions for basic blocks. * @param succBB successor basic block * @param bb current basic block * @return a pair of strings */ protected StringWrapper setDirectionalEdges(BasicBlock succBB, BasicBlock bb) { String to = null; String str = null; int bbNumber = bb.getNumber(); BasicBlock entry = ir.cfg.entry(); boolean succBBIsExit = succBB.isExit(); boolean bbIsEntry = bb == entry; if (bbIsEntry || succBBIsExit) { if (bbIsEntry && !succBBIsExit) { to = "BB" + succBB.getNumber(); str = "ENTRY" + "->" + "{" + to + "}" + ";\n"; } else if (!bbIsEntry && succBBIsExit) { to = "EXIT"; str = "BB" + bbNumber + "->" + "{" + to + "}" + ";\n"; } else { // bbIsEntry && succBBisExit to = "EXIT"; str = "ENTRY" + "->" + "{" + to + "}" + ";\n"; } } else { to = "BB" + succBB.getNumber(); if (succBB == entry) { to = "ENTRY"; } str = "BB" + bbNumber + "->" + "{" + to + "}" + ";\n"; } return new StringWrapper(to, str); } protected String enumerateAndFormatInstructions(BasicBlock succBB) { StringBuilder next = new StringBuilder(); for (Enumeration<Instruction> e = succBB.forwardInstrEnumerator(); e.hasMoreElements();) { Instruction inst = e.nextElement(); int lineNumber = 0; String s = formatInstruction(inst); s = s.replaceAll("\n", " "); s = s.replaceAll("\"", "\\\\\""); InlineSequence position = inst.position(); int bytecodeIndex = inst.getBytecodeIndex(); if (position != null) { lineNumber = position.getMethod().getLineNumberForBCIndex(bytecodeIndex); } next.append(s); next.append(" "); next.append(bytecodeIndex); next.append(","); next.append(lineNumber); next.append(LEFT_JUSTIFIED); next.append("\n"); } return next.toString(); } /** * Formats instructions. Currently only calls are specially formatted, * everything else is just printed. * * @param inst an instruction * @return the String for the instruction */ protected String formatInstruction(Instruction inst) { if (Call.conforms(inst)) { return "CALL " + formatCall(inst); } else { return inst.toString(); } } protected String formatCall(Instruction inst) { StringBuilder buf = new StringBuilder(); MethodOperand mop = Call.getMethod(inst); if (mop != null && mop.hasTarget()) { RVMMethod method = mop.getTarget(); buf.append(method.getDeclaringClass()); buf.append(":"); buf.append(method.getName()); buf.append(":"); buf.append(method.getDescriptor()); buf.append(":"); int params = Call.getNumberOfParams(inst); for (int i = 1; i <= params; i++) { buf.append(Call.getParam(inst, i - 1)); if (i < params) { buf.append(", "); } else { buf.append("; "); } } } return buf.toString(); } public void markBlockAsHighlighted(BasicBlock bb) { if (bb != null) { nodeToColour[bb.getIndex()] = HIGHLIGHTED; } } protected String formatHighlighting(BasicBlock bb) { if (nodeToColour[bb.getIndex()] == HIGHLIGHTED) { return " style=\"filled\", color=\"lightblue\""; } else { return ""; } } protected static final class StringWrapper extends Pair<String, String> { public StringWrapper(String to, String str) { super(to, str); } public String getTo() { return first; } public String getStr() { return second; } } }