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