1 /*
   2  * Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 package org.graalvm.compiler.printer;
  24 
  25 import java.io.BufferedOutputStream;
  26 import java.io.File;
  27 import java.io.FileNotFoundException;
  28 import java.io.FileOutputStream;
  29 import java.io.OutputStream;
  30 import java.nio.file.Path;
  31 import java.util.ArrayList;
  32 import java.util.Collections;
  33 import java.util.List;
  34 
  35 import org.graalvm.compiler.bytecode.BytecodeDisassembler;
  36 import org.graalvm.compiler.code.CompilationResult;
  37 import org.graalvm.compiler.code.DisassemblerProvider;
  38 import org.graalvm.compiler.core.common.alloc.Trace;
  39 import org.graalvm.compiler.core.common.alloc.TraceBuilderResult;
  40 import org.graalvm.compiler.core.common.cfg.AbstractBlockBase;
  41 import org.graalvm.compiler.core.gen.NodeLIRBuilder;
  42 import org.graalvm.compiler.debug.Debug;
  43 import org.graalvm.compiler.debug.DebugDumpHandler;
  44 import org.graalvm.compiler.debug.DebugDumpScope;
  45 import org.graalvm.compiler.debug.GraalDebugConfig.Options;
  46 import org.graalvm.compiler.debug.GraalError;
  47 import org.graalvm.compiler.debug.TTY;
  48 import org.graalvm.compiler.debug.internal.DebugScope;
  49 import org.graalvm.compiler.graph.Graph;
  50 import org.graalvm.compiler.java.BciBlockMapping;
  51 import org.graalvm.compiler.lir.LIR;
  52 import org.graalvm.compiler.lir.alloc.trace.GlobalLivenessInfo;
  53 import org.graalvm.compiler.lir.debug.IntervalDumper;
  54 import org.graalvm.compiler.lir.gen.LIRGenerationResult;
  55 import org.graalvm.compiler.nodes.StructuredGraph;
  56 import org.graalvm.compiler.nodes.StructuredGraph.ScheduleResult;
  57 import org.graalvm.compiler.nodes.cfg.ControlFlowGraph;
  58 import org.graalvm.compiler.options.UniquePathUtilities;
  59 import org.graalvm.compiler.serviceprovider.GraalServices;
  60 
  61 import jdk.vm.ci.code.CodeCacheProvider;
  62 import jdk.vm.ci.code.InstalledCode;
  63 import jdk.vm.ci.meta.JavaMethod;
  64 import jdk.vm.ci.meta.ResolvedJavaMethod;
  65 
  66 /**
  67  * Observes compilation events and uses {@link CFGPrinter} to produce a control flow graph for the
  68  * <a href="http://java.net/projects/c1visualizer/">C1 Visualizer</a>.
  69  */
  70 public class CFGPrinterObserver implements DebugDumpHandler {
  71 
  72     private CFGPrinter cfgPrinter;
  73     private File cfgFile;
  74     private JavaMethod curMethod;
  75     private List<String> curDecorators = Collections.emptyList();
  76     private final boolean dumpFrontend;
  77 
  78     public CFGPrinterObserver(boolean dumpFrontend) {
  79         this.dumpFrontend = dumpFrontend;
  80     }
  81 
  82     @Override
  83     public void dump(Object object, String format, Object... arguments) {
  84         String message = String.format(format, arguments);
  85         try {
  86             dumpSandboxed(object, message);
  87         } catch (Throwable ex) {
  88             TTY.println("CFGPrinter: Exception during output of " + message + ": " + ex);
  89             ex.printStackTrace();
  90         }
  91     }
  92 
  93     /**
  94      * Looks for the outer most method and its {@link DebugDumpScope#decorator}s in the current
  95      * debug scope and opens a new compilation scope if this pair does not match the current method
  96      * and decorator pair.
  97      */
  98     private boolean checkMethodScope() {
  99         JavaMethod method = null;
 100         ArrayList<String> decorators = new ArrayList<>();
 101         for (Object o : Debug.context()) {
 102             if (o instanceof JavaMethod) {
 103                 method = (JavaMethod) o;
 104                 decorators.clear();
 105             } else if (o instanceof StructuredGraph) {
 106                 StructuredGraph graph = (StructuredGraph) o;
 107                 if (graph.method() != null) {
 108                     method = graph.method();
 109                     decorators.clear();
 110                 }
 111             } else if (o instanceof DebugDumpScope) {
 112                 DebugDumpScope debugDumpScope = (DebugDumpScope) o;
 113                 if (debugDumpScope.decorator) {
 114                     decorators.add(debugDumpScope.name);
 115                 }
 116             }
 117         }
 118 
 119         if (method == null) {
 120             return false;
 121         }
 122 
 123         if (!method.equals(curMethod) || !curDecorators.equals(decorators)) {
 124             cfgPrinter.printCompilation(method);
 125             TTY.println("CFGPrinter: Dumping method %s to %s", method, cfgFile.getAbsolutePath());
 126         }
 127         curMethod = method;
 128         curDecorators = decorators;
 129         return true;
 130     }
 131 
 132     private static boolean isFrontendObject(Object object) {
 133         return object instanceof Graph || object instanceof BciBlockMapping;
 134     }
 135 
 136     private LIR lastLIR = null;
 137     private IntervalDumper delayedIntervals = null;
 138 
 139     public void dumpSandboxed(Object object, String message) {
 140         if (!dumpFrontend && isFrontendObject(object)) {
 141             return;
 142         }
 143 
 144         if (cfgPrinter == null) {
 145             cfgFile = getCFGPath().toFile();
 146             try {
 147                 /*
 148                  * Initializing a debug environment multiple times by calling
 149                  * DebugEnvironment#initialize will create new CFGPrinterObserver objects that refer
 150                  * to the same file path. This means the CFG file may be overridden by another
 151                  * instance. Appending to an existing CFG file is not an option as the writing
 152                  * happens buffered.
 153                  */
 154                 OutputStream out = new BufferedOutputStream(new FileOutputStream(cfgFile));
 155                 cfgPrinter = new CFGPrinter(out);
 156             } catch (FileNotFoundException e) {
 157                 throw new GraalError("Could not open " + cfgFile.getAbsolutePath());
 158             }
 159             TTY.println("CFGPrinter: Output to file %s", cfgFile.getAbsolutePath());
 160         }
 161 
 162         if (!checkMethodScope()) {
 163             return;
 164         }
 165         try {
 166             if (curMethod instanceof ResolvedJavaMethod) {
 167                 cfgPrinter.method = (ResolvedJavaMethod) curMethod;
 168             }
 169 
 170             if (object instanceof LIR) {
 171                 cfgPrinter.lir = (LIR) object;
 172             } else {
 173                 cfgPrinter.lir = Debug.contextLookup(LIR.class);
 174             }
 175             cfgPrinter.nodeLirGenerator = Debug.contextLookup(NodeLIRBuilder.class);
 176             cfgPrinter.livenessInfo = Debug.contextLookup(GlobalLivenessInfo.class);
 177             cfgPrinter.res = Debug.contextLookup(LIRGenerationResult.class);
 178             if (cfgPrinter.nodeLirGenerator != null) {
 179                 cfgPrinter.target = cfgPrinter.nodeLirGenerator.getLIRGeneratorTool().target();
 180             }
 181             if (cfgPrinter.lir != null && cfgPrinter.lir.getControlFlowGraph() instanceof ControlFlowGraph) {
 182                 cfgPrinter.cfg = (ControlFlowGraph) cfgPrinter.lir.getControlFlowGraph();
 183             }
 184 
 185             CodeCacheProvider codeCache = Debug.contextLookup(CodeCacheProvider.class);
 186             if (codeCache != null) {
 187                 cfgPrinter.target = codeCache.getTarget();
 188             }
 189 
 190             if (object instanceof BciBlockMapping) {
 191                 BciBlockMapping blockMap = (BciBlockMapping) object;
 192                 cfgPrinter.printCFG(message, blockMap);
 193                 if (blockMap.code.getCode() != null) {
 194                     cfgPrinter.printBytecodes(new BytecodeDisassembler(false).disassemble(blockMap.code));
 195                 }
 196 
 197             } else if (object instanceof LIR) {
 198                 // Currently no node printing for lir
 199                 cfgPrinter.printCFG(message, cfgPrinter.lir.codeEmittingOrder(), false);
 200                 lastLIR = (LIR) object;
 201                 if (delayedIntervals != null) {
 202                     cfgPrinter.printIntervals(message, delayedIntervals);
 203                     delayedIntervals = null;
 204                 }
 205             } else if (object instanceof ScheduleResult) {
 206                 cfgPrinter.printSchedule(message, (ScheduleResult) object);
 207             } else if (object instanceof StructuredGraph) {
 208                 if (cfgPrinter.cfg == null) {
 209                     StructuredGraph graph = (StructuredGraph) object;
 210                     cfgPrinter.cfg = ControlFlowGraph.compute(graph, true, true, true, false);
 211                     cfgPrinter.printCFG(message, cfgPrinter.cfg.getBlocks(), true);
 212                 } else {
 213                     cfgPrinter.printCFG(message, cfgPrinter.cfg.getBlocks(), true);
 214                 }
 215 
 216             } else if (object instanceof CompilationResult) {
 217                 final CompilationResult compResult = (CompilationResult) object;
 218                 cfgPrinter.printMachineCode(disassemble(codeCache, compResult, null), message);
 219             } else if (object instanceof InstalledCode) {
 220                 CompilationResult compResult = Debug.contextLookup(CompilationResult.class);
 221                 if (compResult != null) {
 222                     cfgPrinter.printMachineCode(disassemble(codeCache, compResult, (InstalledCode) object), message);
 223                 }
 224             } else if (object instanceof IntervalDumper) {
 225                 if (lastLIR == cfgPrinter.lir) {
 226                     cfgPrinter.printIntervals(message, (IntervalDumper) object);
 227                 } else {
 228                     if (delayedIntervals != null) {
 229                         Debug.log("Some delayed intervals were dropped (%s)", delayedIntervals);
 230                     }
 231                     delayedIntervals = (IntervalDumper) object;
 232                 }
 233             } else if (object instanceof AbstractBlockBase<?>[]) {
 234                 cfgPrinter.printCFG(message, (AbstractBlockBase<?>[]) object, false);
 235             } else if (object instanceof Trace) {
 236                 cfgPrinter.printCFG(message, ((Trace) object).getBlocks(), false);
 237             } else if (object instanceof TraceBuilderResult) {
 238                 cfgPrinter.printTraces(message, (TraceBuilderResult) object);
 239             }
 240         } finally {
 241             cfgPrinter.target = null;
 242             cfgPrinter.lir = null;
 243             cfgPrinter.res = null;
 244             cfgPrinter.nodeLirGenerator = null;
 245             cfgPrinter.livenessInfo = null;
 246             cfgPrinter.cfg = null;
 247             cfgPrinter.flush();
 248         }
 249     }
 250 
 251     private static Path getCFGPath() {
 252         return UniquePathUtilities.getPath(DebugScope.getConfig().getOptions(), Options.PrintCFGFileName, Options.DumpPath, "cfg");
 253     }
 254 
 255     /** Lazy initialization to delay service lookup until disassembler is actually needed. */
 256     static class DisassemblerHolder {
 257         private static final DisassemblerProvider disassembler;
 258 
 259         static {
 260             DisassemblerProvider selected = null;
 261             for (DisassemblerProvider d : GraalServices.load(DisassemblerProvider.class)) {
 262                 String name = d.getName().toLowerCase();
 263                 if (name.contains("hcf") || name.contains("hexcodefile")) {
 264                     selected = d;
 265                     break;
 266                 }
 267             }
 268             if (selected == null) {
 269                 selected = new DisassemblerProvider() {
 270                     @Override
 271                     public String getName() {
 272                         return "nop";
 273                     }
 274                 };
 275             }
 276             disassembler = selected;
 277         }
 278     }
 279 
 280     private static String disassemble(CodeCacheProvider codeCache, CompilationResult compResult, InstalledCode installedCode) {
 281         DisassemblerProvider dis = DisassemblerHolder.disassembler;
 282         if (installedCode != null) {
 283             return dis.disassembleInstalledCode(codeCache, compResult, installedCode);
 284         }
 285         return dis.disassembleCompiledCode(codeCache, compResult);
 286     }
 287 
 288     @Override
 289     public void close() {
 290         if (cfgPrinter != null) {
 291             cfgPrinter.close();
 292             cfgPrinter = null;
 293             curDecorators = Collections.emptyList();
 294             curMethod = null;
 295         }
 296     }
 297 
 298     public String getDumpPath() {
 299         if (cfgFile != null) {
 300             return cfgFile.getAbsolutePath();
 301         }
 302         return null;
 303     }
 304 }