1 /*
   2  * Copyright (c) 2011, 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 static org.graalvm.compiler.debug.GraalDebugConfig.asJavaMethod;
  26 
  27 import java.io.IOException;
  28 import java.lang.management.ManagementFactory;
  29 import java.util.ArrayList;
  30 import java.util.Arrays;
  31 import java.util.Collections;
  32 import java.util.Date;
  33 import java.util.HashMap;
  34 import java.util.List;
  35 import java.util.Map;
  36 import java.util.WeakHashMap;
  37 
  38 import org.graalvm.compiler.debug.Debug;
  39 import org.graalvm.compiler.debug.Debug.Scope;
  40 import org.graalvm.compiler.debug.DebugDumpHandler;
  41 import org.graalvm.compiler.debug.DebugDumpScope;
  42 import org.graalvm.compiler.debug.GraalDebugConfig.Options;
  43 import org.graalvm.compiler.debug.TTY;
  44 import org.graalvm.compiler.graph.Graph;
  45 import org.graalvm.compiler.nodes.StructuredGraph;
  46 
  47 import jdk.vm.ci.meta.JavaMethod;
  48 import jdk.vm.ci.meta.ResolvedJavaMethod;
  49 
  50 //JaCoCo Exclude
  51 
  52 /**
  53  * Observes compilation events and uses {@link IdealGraphPrinter} to generate a graph representation
  54  * that can be inspected with the <a href="http://kenai.com/projects/igv">Ideal Graph Visualizer</a>
  55  * .
  56  */
  57 public class GraphPrinterDumpHandler implements DebugDumpHandler {
  58 
  59     private final GraphPrinterSupplier printerSupplier;
  60     protected GraphPrinter printer;
  61     private List<String> previousInlineContext;
  62     private int[] dumpIds = {};
  63     private int failuresCount;
  64     private Map<Graph, List<String>> inlineContextMap;
  65     private final String jvmArguments;
  66     private final String sunJavaCommand;
  67 
  68     @FunctionalInterface
  69     public interface GraphPrinterSupplier {
  70         GraphPrinter get() throws IOException;
  71     }
  72 
  73     /**
  74      * Creates a new {@link GraphPrinterDumpHandler}.
  75      *
  76      * @param printerSupplier Supplier used to create the GraphPrinter. Should supply an optional or
  77      *            null in case of failure.
  78      */
  79     public GraphPrinterDumpHandler(GraphPrinterSupplier printerSupplier) {
  80         this.printerSupplier = printerSupplier;
  81         /* Add the JVM and Java arguments to the graph properties to help identify it. */
  82         this.jvmArguments = String.join(" ", ManagementFactory.getRuntimeMXBean().getInputArguments());
  83         this.sunJavaCommand = System.getProperty("sun.java.command");
  84     }
  85 
  86     private void ensureInitialized() {
  87         if (printer == null) {
  88             if (failuresCount > 8) {
  89                 return;
  90             }
  91             previousInlineContext = new ArrayList<>();
  92             inlineContextMap = new WeakHashMap<>();
  93             try {
  94                 printer = printerSupplier.get();
  95             } catch (IOException e) {
  96                 TTY.println(e.getMessage());
  97                 failuresCount++;
  98             }
  99         }
 100     }
 101 
 102     private int nextDumpId() {
 103         int depth = previousInlineContext.size();
 104         if (dumpIds.length < depth) {
 105             dumpIds = Arrays.copyOf(dumpIds, depth);
 106         }
 107         return dumpIds[depth - 1]++;
 108     }
 109 
 110     @Override
 111     @SuppressWarnings("try")
 112     public void dump(Object object, final String message) {
 113         if (object instanceof Graph && Options.PrintIdealGraph.getValue()) {
 114             ensureInitialized();
 115             if (printer == null) {
 116                 return;
 117             }
 118             final Graph graph = (Graph) object;
 119 
 120             // Get all current JavaMethod instances in the context.
 121             List<String> inlineContext = getInlineContext(graph);
 122 
 123             if (inlineContext != previousInlineContext) {
 124                 Map<Object, Object> properties = new HashMap<>();
 125                 properties.put("graph", graph.toString());
 126                 addCompilationId(properties, graph);
 127                 addCFGFileName(properties);
 128                 if (inlineContext.equals(previousInlineContext)) {
 129                     /*
 130                      * two different graphs have the same inline context, so make sure they appear
 131                      * in different folders by closing and reopening the top scope.
 132                      */
 133                     int inlineDepth = previousInlineContext.size() - 1;
 134                     closeScope(inlineDepth);
 135                     openScope(inlineContext.get(inlineDepth), inlineDepth, properties);
 136                 } else {
 137                     // Check for method scopes that must be closed since the previous dump.
 138                     for (int i = 0; i < previousInlineContext.size(); ++i) {
 139                         if (i >= inlineContext.size() || !inlineContext.get(i).equals(previousInlineContext.get(i))) {
 140                             for (int inlineDepth = previousInlineContext.size() - 1; inlineDepth >= i; --inlineDepth) {
 141                                 closeScope(inlineDepth);
 142                             }
 143                             break;
 144                         }
 145                     }
 146                     // Check for method scopes that must be opened since the previous dump.
 147                     for (int i = 0; i < inlineContext.size(); ++i) {
 148                         if (i >= previousInlineContext.size() || !inlineContext.get(i).equals(previousInlineContext.get(i))) {
 149                             for (int inlineDepth = i; inlineDepth < inlineContext.size(); ++inlineDepth) {
 150                                 openScope(inlineContext.get(inlineDepth), inlineDepth, inlineDepth == inlineContext.size() - 1 ? properties : null);
 151                             }
 152                             break;
 153                         }
 154                     }
 155                 }
 156             }
 157 
 158             // Save inline context for next dump.
 159             previousInlineContext = inlineContext;
 160 
 161             try (Scope s = Debug.sandbox("PrintingGraph", null)) {
 162                 // Finally, output the graph.
 163                 Map<Object, Object> properties = new HashMap<>();
 164                 properties.put("graph", graph.toString());
 165                 properties.put("scope", Debug.currentScope());
 166                 addCFGFileName(properties);
 167                 printer.print(graph, nextDumpId() + ":" + message, properties);
 168             } catch (IOException e) {
 169                 failuresCount++;
 170                 printer = null;
 171             } catch (Throwable e) {
 172                 throw Debug.handle(e);
 173             }
 174         }
 175     }
 176 
 177     private static void addCompilationId(Map<Object, Object> properties, final Graph graph) {
 178         if (graph instanceof StructuredGraph) {
 179             properties.put("compilationId", ((StructuredGraph) graph).compilationId());
 180         }
 181     }
 182 
 183     private static void addCFGFileName(Map<Object, Object> properties) {
 184         if (Options.PrintCFG.getValue() || Options.PrintBackendCFG.getValue()) {
 185             properties.put("PrintCFGFileName", CFGPrinterObserver.getCFGPath().toAbsolutePath().toString());
 186         }
 187     }
 188 
 189     private List<String> getInlineContext(Graph graph) {
 190         List<String> result = inlineContextMap.get(graph);
 191         if (result == null) {
 192             result = new ArrayList<>();
 193             Object lastMethodOrGraph = null;
 194             boolean graphSeen = false;
 195             for (Object o : Debug.context()) {
 196                 if (o == graph) {
 197                     graphSeen = true;
 198                 }
 199 
 200                 if (o instanceof DebugDumpScope) {
 201                     DebugDumpScope debugDumpScope = (DebugDumpScope) o;
 202                     if (debugDumpScope.decorator && !result.isEmpty()) {
 203                         result.set(result.size() - 1, debugDumpScope.name + ":" + result.get(result.size() - 1));
 204                     } else {
 205                         result.add(debugDumpScope.name);
 206                     }
 207                 } else {
 208                     addMethodContext(result, o, lastMethodOrGraph);
 209                 }
 210                 if (o instanceof JavaMethod || o instanceof Graph) {
 211                     lastMethodOrGraph = o;
 212                 }
 213             }
 214 
 215             if (result.isEmpty()) {
 216                 result.add(graph.toString());
 217                 graphSeen = true;
 218             }
 219             // Reverse list such that inner method comes after outer method.
 220             Collections.reverse(result);
 221             if (!graphSeen) {
 222                 /*
 223                  * The graph isn't in any context but is being processed within another graph so add
 224                  * it to the end of the scopes.
 225                  */
 226                 if (asJavaMethod(graph) != null) {
 227                     addMethodContext(result, graph, lastMethodOrGraph);
 228                 } else {
 229                     result.add(graph.toString());
 230                 }
 231             }
 232             inlineContextMap.put(graph, result);
 233         }
 234         return result;
 235     }
 236 
 237     private static void addMethodContext(List<String> result, Object o, Object lastMethodOrGraph) {
 238         JavaMethod method = asJavaMethod(o);
 239         if (method != null) {
 240             /*
 241              * Include the current method in the context if there was no previous JavaMethod or
 242              * JavaMethodContext or if the method is different or if the method is the same but it
 243              * comes from two different graphs. This ensures that recursive call patterns are
 244              * handled properly.
 245              */
 246             if (lastMethodOrGraph == null || asJavaMethod(lastMethodOrGraph) == null || !asJavaMethod(lastMethodOrGraph).equals(method) ||
 247                             (lastMethodOrGraph != o && lastMethodOrGraph instanceof Graph && o instanceof Graph)) {
 248                 result.add(method.format("%H::%n(%p)"));
 249             } else {
 250                 /*
 251                  * This prevents multiple adjacent method context objects for the same method from
 252                  * resulting in multiple IGV tree levels. This works on the assumption that real
 253                  * inlining debug scopes will have a graph context object between the inliner and
 254                  * inlinee context objects.
 255                  */
 256             }
 257         }
 258     }
 259 
 260     private void openScope(String name, int inlineDepth, Map<Object, Object> properties) {
 261         String prefix = inlineDepth == 0 ? Thread.currentThread().getName() + ":" : "";
 262         try {
 263             Map<Object, Object> props = properties;
 264             if (inlineDepth == 0) {
 265                 /* Include some VM specific properties at the root. */
 266                 if (props == null) {
 267                     props = new HashMap<>();
 268                 }
 269                 props.put("jvmArguments", jvmArguments);
 270                 if (sunJavaCommand != null) {
 271                     props.put("sun.java.command", sunJavaCommand);
 272                 }
 273                 props.put("date", new Date().toString());
 274             }
 275             printer.beginGroup(prefix + name, name, Debug.contextLookup(ResolvedJavaMethod.class), -1, props);
 276         } catch (IOException e) {
 277             failuresCount++;
 278             printer = null;
 279         }
 280     }
 281 
 282     private void closeScope(int inlineDepth) {
 283         dumpIds[inlineDepth] = 0;
 284         try {
 285             printer.endGroup();
 286         } catch (IOException e) {
 287             failuresCount++;
 288             printer = null;
 289         }
 290     }
 291 
 292     @Override
 293     public void close() {
 294         if (previousInlineContext != null) {
 295             for (int inlineDepth = 0; inlineDepth < previousInlineContext.size(); inlineDepth++) {
 296                 closeScope(inlineDepth);
 297             }
 298         }
 299         if (printer != null) {
 300             printer.close();
 301             printer = null;
 302         }
 303     }
 304 }