001/*
002 *  This file is part of the Jikes RVM project (http://jikesrvm.org).
003 *
004 *  This file is licensed to You under the Eclipse Public License (EPL);
005 *  You may not use this file except in compliance with the License. You
006 *  may obtain a copy of the License at
007 *
008 *      http://www.opensource.org/licenses/eclipse-1.0.php
009 *
010 *  See the COPYRIGHT.txt file distributed with this work for information
011 *  regarding copyright ownership.
012 */
013package org.jikesrvm.compilers.opt.driver;
014
015import java.io.BufferedWriter;
016import java.io.File;
017import java.io.FileWriter;
018import java.io.IOException;
019import java.util.Enumeration;
020
021import org.jikesrvm.classloader.RVMMethod;
022import org.jikesrvm.compilers.opt.inlining.InlineSequence;
023import org.jikesrvm.compilers.opt.ir.BasicBlock;
024import org.jikesrvm.compilers.opt.ir.Call;
025import org.jikesrvm.compilers.opt.ir.IR;
026import org.jikesrvm.compilers.opt.ir.Instruction;
027import org.jikesrvm.compilers.opt.ir.operand.MethodOperand;
028import org.jikesrvm.runtime.Time;
029import org.jikesrvm.util.Pair;
030
031/**
032 * Prints a CFG visualization to a file using the dot output format. Further processing
033 * can be done using tools such as <a href="http://graphviz.org">Graphviz</a>
034 * or graphical frontends such as <a href="http://zvtm.sourceforge.net/zgrviewer.html">ZGRviewer</a>.
035 */
036public class CFGVisualization {
037
038  private static final String CENTERED = "\\n";
039  private static final String LEFT_JUSTIFIED = "\\l";
040
041  @SuppressWarnings("unused")
042  // just here as documentation to make it clear that 0 is taken as a value
043  private static final byte UNVISITED = 0;
044
045  private static final byte HIGHLIGHTED = 1;
046  private static final byte GRAY = 2;
047  private static final byte BLACK = 3;
048
049  private final BufferedWriter out;
050  private final IR ir;
051
052  private final byte[] nodeToColour;
053
054  public CFGVisualization(IR ir, String tag) throws SecurityException, IOException {
055    RVMMethod method = ir.getMethod();
056    String fileName = determineFileName(ir, tag, method);
057    String dir = ir.options.VISUALIZE_IR_DIRECTORY;
058    if (dir.trim().isEmpty()) {
059      dir = System.getProperty("user.dir");
060    }
061    File directory = new File(dir);
062    File file = new File(directory, fileName);
063    this.out = new BufferedWriter(new FileWriter(file));
064    this.ir = ir;
065    nodeToColour = new byte[ir.cfg.numberOfNodes()];
066  }
067
068  private String determineFileName(IR ir, String tag, RVMMethod method) {
069    return tag.replace(' ', '-').replace('/', '-') + "_" +
070        method.getDeclaringClass().getDescriptor().classNameFromDescriptor() + "_" +
071        method.getName() + "_" +
072        "opt" + ir.options.getOptLevel() + "-" + Time.currentTimeMillis() + ".graph";
073  }
074
075  public void visualizeCFG() throws IOException {
076    out.write("digraph G {\n node [shape=box];\n");
077    out.write("ENTRY" + "[ label=\"" + "ENTRY" + CENTERED + "\n");
078    BasicBlock entry = ir.cfg.entry();
079    out.write(enumerateAndFormatInstructions(entry));
080    out.write("\"" + formatHighlighting(entry) + "];\n");
081    dfsCFG(entry, ir);
082    out.write("}");
083    out.close();
084  }
085
086  protected void dfsCFG(BasicBlock bb, IR ir) throws IOException {
087    Enumeration<BasicBlock> successors = bb.getOutNodes();
088    nodeToColour[bb.getIndex()] = GRAY;
089    while (successors.hasMoreElements()) {
090      BasicBlock succBB = successors.nextElement();
091      StringWrapper returnObj = setDirectionalEdges(succBB, bb);
092      out.write(returnObj.getStr());
093      String to = returnObj.getTo();
094      boolean toNotEmpty = !to.isEmpty();
095      if (toNotEmpty) {
096        out.write(to + "[ label=\"" + to + CENTERED);
097      }
098      out.write(enumerateAndFormatInstructions(succBB));
099      if (toNotEmpty) {
100        out.write("\"" + formatHighlighting(succBB) + "];\n");
101      }
102      byte colour = nodeToColour[succBB.getIndex()];
103      if (colour != GRAY && colour != BLACK) {
104        dfsCFG(succBB,ir);
105      }
106    }
107    nodeToColour[bb.getIndex()] = BLACK;
108  }
109
110  /**
111   * Generates control-flow edge descriptions for basic blocks.
112   * @param succBB successor basic block
113   * @param bb current basic block
114   * @return a pair of strings
115   */
116  protected StringWrapper setDirectionalEdges(BasicBlock succBB, BasicBlock bb) {
117    String to = null;
118    String str = null;
119    int bbNumber = bb.getNumber();
120    BasicBlock entry = ir.cfg.entry();
121    boolean succBBIsExit = succBB.isExit();
122    boolean bbIsEntry = bb == entry;
123
124    if (bbIsEntry || succBBIsExit) {
125      if (bbIsEntry && !succBBIsExit) {
126        to = "BB" + succBB.getNumber();
127        str = "ENTRY" + "->" + "{" + to + "}" + ";\n";
128      } else if (!bbIsEntry && succBBIsExit) {
129        to = "EXIT";
130        str = "BB" + bbNumber + "->" + "{" + to + "}" + ";\n";
131      } else { // bbIsEntry && succBBisExit
132        to = "EXIT";
133        str = "ENTRY" + "->" + "{" + to + "}" + ";\n";
134      }
135    } else {
136      to = "BB" + succBB.getNumber();
137      if (succBB == entry) {
138        to = "ENTRY";
139      }
140      str = "BB" + bbNumber + "->" + "{" + to + "}" + ";\n";
141    }
142    return new StringWrapper(to, str);
143  }
144
145  protected String enumerateAndFormatInstructions(BasicBlock succBB) {
146    StringBuilder next = new StringBuilder();
147    for (Enumeration<Instruction> e = succBB.forwardInstrEnumerator(); e.hasMoreElements();) {
148      Instruction inst = e.nextElement();
149      int lineNumber = 0;
150      String s = formatInstruction(inst);
151      s = s.replaceAll("\n", " ");
152      s = s.replaceAll("\"", "\\\\\"");
153      InlineSequence position = inst.position;
154      int bytecodeIndex = inst.getBytecodeIndex();
155      if (position != null) {
156        lineNumber = position.getMethod().getLineNumberForBCIndex(bytecodeIndex);
157      }
158      next.append(s);
159      next.append(" ");
160      next.append(bytecodeIndex);
161      next.append(",");
162      next.append(lineNumber);
163      next.append(LEFT_JUSTIFIED);
164      next.append("\n");
165    }
166    return next.toString();
167  }
168
169  /**
170   * Formats instructions. Currently only calls are specially formatted,
171   * everything else is just printed.
172   *
173   * @param inst an instruction
174   * @return the String for the instruction
175   */
176  protected String formatInstruction(Instruction inst) {
177    if (Call.conforms(inst)) {
178      return "CALL " + formatCall(inst);
179    } else {
180      return inst.toString();
181    }
182  }
183
184  protected String formatCall(Instruction inst) {
185    StringBuilder buf = new StringBuilder();
186    MethodOperand mop = Call.getMethod(inst);
187    if (mop != null && mop.hasTarget()) {
188      RVMMethod method = mop.getTarget();
189      buf.append(method.getDeclaringClass());
190      buf.append(":");
191      buf.append(method.getName());
192      buf.append(":");
193      buf.append(method.getDescriptor());
194      buf.append(":");
195      int params = Call.getNumberOfParams(inst);
196      for (int i = 1; i <= params; i++) {
197        buf.append(Call.getParam(inst, i - 1));
198        if (i < params) {
199          buf.append(", ");
200        } else {
201          buf.append("; ");
202        }
203      }
204    }
205    return buf.toString();
206  }
207
208  public void markBlockAsHighlighted(BasicBlock bb) {
209    if (bb != null) {
210      nodeToColour[bb.getIndex()] = HIGHLIGHTED;
211    }
212  }
213
214  protected String formatHighlighting(BasicBlock bb) {
215    if (nodeToColour[bb.getIndex()] == HIGHLIGHTED) {
216      return " style=\"filled\", color=\"lightblue\"";
217    } else {
218      return "";
219    }
220  }
221
222  protected static final class StringWrapper extends Pair<String, String> {
223
224    public StringWrapper(String to, String str) {
225      super(to, str);
226    }
227
228    public String getTo() {
229      return first;
230    }
231
232    public String getStr() {
233      return second;
234    }
235
236  }
237
238}