1 /*
   2  * Copyright (c) 2015, 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.DebugOptions.PrintBinaryGraphPort;
  26 import static org.graalvm.compiler.debug.DebugOptions.PrintBinaryGraphs;
  27 import static org.graalvm.compiler.debug.DebugOptions.PrintGraphHost;
  28 import static org.graalvm.compiler.debug.DebugOptions.PrintXmlGraphPort;
  29 import static org.graalvm.compiler.debug.DebugOptions.ShowDumpFiles;
  30 
  31 import java.io.File;
  32 import java.io.IOException;
  33 import java.io.InterruptedIOException;
  34 import java.net.InetSocketAddress;
  35 import java.net.Socket;
  36 import java.nio.channels.ClosedByInterruptException;
  37 import java.nio.channels.FileChannel;
  38 import java.nio.channels.SocketChannel;
  39 import java.nio.file.FileAlreadyExistsException;
  40 import java.nio.file.Files;
  41 import java.nio.file.InvalidPathException;
  42 import java.nio.file.Path;
  43 import java.nio.file.Paths;
  44 import java.nio.file.StandardOpenOption;
  45 import java.util.ArrayList;
  46 import java.util.List;
  47 import java.util.concurrent.atomic.AtomicInteger;
  48 
  49 import org.graalvm.compiler.api.replacements.SnippetReflectionProvider;
  50 import org.graalvm.compiler.core.common.CompilationIdentifier;
  51 import org.graalvm.compiler.debug.Assertions;
  52 import org.graalvm.compiler.debug.DebugContext;
  53 import org.graalvm.compiler.debug.DebugDumpHandler;
  54 import org.graalvm.compiler.debug.DebugHandler;
  55 import org.graalvm.compiler.debug.DebugHandlersFactory;
  56 import org.graalvm.compiler.debug.DebugOptions;
  57 import org.graalvm.compiler.debug.TTY;
  58 import org.graalvm.compiler.debug.PathUtilities;
  59 import org.graalvm.compiler.graph.Graph;
  60 import org.graalvm.compiler.graph.Node;
  61 import org.graalvm.compiler.nodeinfo.Verbosity;
  62 import org.graalvm.compiler.nodes.StructuredGraph;
  63 import org.graalvm.compiler.nodes.util.GraphUtil;
  64 import org.graalvm.compiler.options.OptionValues;
  65 import org.graalvm.compiler.serviceprovider.ServiceProvider;
  66 
  67 @ServiceProvider(DebugHandlersFactory.class)
  68 public class GraalDebugHandlersFactory implements DebugHandlersFactory {
  69 
  70     private final SnippetReflectionProvider snippetReflection;
  71 
  72     public GraalDebugHandlersFactory() {
  73         this.snippetReflection = null;
  74     }
  75 
  76     public GraalDebugHandlersFactory(SnippetReflectionProvider snippetReflection) {
  77         this.snippetReflection = snippetReflection;
  78     }
  79 
  80     @Override
  81     public List<DebugHandler> createHandlers(OptionValues options) {
  82         List<DebugHandler> handlers = new ArrayList<>();
  83         if (DebugOptions.PrintGraphFile.getValue(options)) {
  84             handlers.add(new GraphPrinterDumpHandler((graph) -> createFilePrinter(graph, options, snippetReflection)));
  85         } else {
  86             handlers.add(new GraphPrinterDumpHandler((graph) -> createNetworkPrinter(graph, options, snippetReflection)));
  87         }
  88         if (DebugOptions.PrintCanonicalGraphStrings.getValue(options)) {
  89             handlers.add(new GraphPrinterDumpHandler((graph) -> createStringPrinter(snippetReflection)));
  90         }
  91         handlers.add(new NodeDumper());
  92         if (DebugOptions.PrintCFG.getValue(options) || DebugOptions.PrintBackendCFG.getValue(options)) {
  93             if (DebugOptions.PrintBinaryGraphs.getValue(options) && DebugOptions.PrintCFG.getValue(options)) {
  94                 TTY.out.println("Complete C1Visualizer dumping slows down PrintBinaryGraphs: use -Dgraal.PrintCFG=false to disable it");
  95             }
  96             handlers.add(new CFGPrinterObserver());
  97         }
  98         handlers.add(new NoDeadCodeVerifyHandler());
  99         return handlers;
 100     }
 101 
 102     private static class NodeDumper implements DebugDumpHandler {
 103         @Override
 104         public void dump(DebugContext debug, Object object, String format, Object... arguments) {
 105             if (object instanceof Node) {
 106                 Node node = (Node) object;
 107                 String location = GraphUtil.approxSourceLocation(node);
 108                 String nodeName = node.toString(Verbosity.Debugger);
 109                 if (location != null) {
 110                     debug.log("Context obj %s (approx. location: %s)", nodeName, location);
 111                 } else {
 112                     debug.log("Context obj %s", nodeName);
 113                 }
 114             }
 115         }
 116     }
 117 
 118     private static CanonicalStringGraphPrinter createStringPrinter(SnippetReflectionProvider snippetReflection) {
 119         return new CanonicalStringGraphPrinter(snippetReflection);
 120     }
 121 
 122     public static String sanitizedFileName(String n) {
 123         /*
 124          * First ensure that the name does not contain the directory separator (which would be
 125          * considered a valid path).
 126          */
 127         String name = n.replace(File.separatorChar, '_');
 128 
 129         try {
 130             Paths.get(name);
 131             return name;
 132         } catch (InvalidPathException e) {
 133             // fall through
 134         }
 135         StringBuilder buf = new StringBuilder(name.length());
 136         for (int i = 0; i < name.length(); i++) {
 137             char c = name.charAt(i);
 138             try {
 139                 Paths.get(String.valueOf(c));
 140             } catch (InvalidPathException e) {
 141                 buf.append('_');
 142             }
 143             buf.append(c);
 144         }
 145         return buf.toString();
 146     }
 147 
 148     private static GraphPrinter createNetworkPrinter(Graph graph, OptionValues options, SnippetReflectionProvider snippetReflection) throws IOException {
 149         String host = PrintGraphHost.getValue(options);
 150         int port = PrintBinaryGraphs.getValue(options) ? PrintBinaryGraphPort.getValue(options) : PrintXmlGraphPort.getValue(options);
 151         try {
 152             GraphPrinter printer;
 153             if (DebugOptions.PrintBinaryGraphs.getValue(options)) {
 154                 printer = new BinaryGraphPrinter(SocketChannel.open(new InetSocketAddress(host, port)), snippetReflection);
 155             } else {
 156                 printer = new IdealGraphPrinter(new Socket(host, port).getOutputStream(), true, snippetReflection);
 157             }
 158             TTY.println("Connected to the IGV on %s:%d", host, port);
 159             return printer;
 160         } catch (ClosedByInterruptException | InterruptedIOException e) {
 161             /*
 162              * Interrupts should not count as errors because they may be caused by a cancelled Graal
 163              * compilation. ClosedByInterruptException occurs if the SocketChannel could not be
 164              * opened. InterruptedIOException occurs if new Socket(..) was interrupted.
 165              */
 166             return null;
 167         } catch (IOException e) {
 168             if (!DebugOptions.PrintGraphFile.hasBeenSet(options)) {
 169                 return createFilePrinter(graph, options, snippetReflection);
 170             } else {
 171                 throw new IOException(String.format("Could not connect to the IGV on %s:%d", host, port), e);
 172             }
 173         }
 174     }
 175 
 176     private static final AtomicInteger unknownCompilationId = new AtomicInteger();
 177 
 178     /**
 179      * Creates a new file or directory for dumping based on a given graph and a file extension.
 180      *
 181      * @param graph a base path name is derived from {@code graph}
 182      * @param extension a suffix which if non-null and non-empty added to the end of the returned
 183      *            path separated by a {@code "."}
 184      * @param createDirectory specifies if this is a request to create a directory instead of a file
 185      * @return the created directory or file
 186      * @throws IOException if there was an error creating the directory or file
 187      */
 188     static Path createDumpPath(OptionValues options, Graph graph, String extension, boolean createDirectory) throws IOException {
 189         CompilationIdentifier compilationId = CompilationIdentifier.INVALID_COMPILATION_ID;
 190         String id = null;
 191         String label = null;
 192         if (graph instanceof StructuredGraph) {
 193             StructuredGraph sgraph = (StructuredGraph) graph;
 194             label = getGraphName(sgraph);
 195             compilationId = sgraph.compilationId();
 196             if (compilationId == CompilationIdentifier.INVALID_COMPILATION_ID) {
 197                 id = graph.getClass().getSimpleName() + "-" + sgraph.graphId();
 198             } else {
 199                 id = compilationId.toString(CompilationIdentifier.Verbosity.ID);
 200             }
 201         } else {
 202             label = graph == null ? null : graph.name != null ? graph.name : graph.toString();
 203             id = "UnknownCompilation-" + unknownCompilationId.incrementAndGet();
 204         }
 205         String ext = PathUtilities.formatExtension(extension);
 206         Path result = createUnique(DebugOptions.getDumpDirectory(options), id, label, ext, createDirectory);
 207         if (ShowDumpFiles.getValue(options) || Assertions.assertionsEnabled()) {
 208             TTY.println("Dumping debug output to %s", result.toAbsolutePath().toString());
 209         }
 210         return result;
 211     }
 212 
 213     /**
 214      * A maximum file name length supported by most file systems. There is no platform independent
 215      * way to get this in Java.
 216      */
 217     private static final int MAX_FILE_NAME_LENGTH = 255;
 218 
 219     private static final String ELLIPSIS = "...";
 220 
 221     private static Path createUnique(Path dumpDir, String id, String label, String ext, boolean createDirectory) throws IOException {
 222         String timestamp = "";
 223         for (;;) {
 224             int fileNameLengthWithoutLabel = timestamp.length() + ext.length() + id.length() + "[]".length();
 225             int labelLengthLimit = MAX_FILE_NAME_LENGTH - fileNameLengthWithoutLabel;
 226             String fileName;
 227             if (labelLengthLimit < ELLIPSIS.length()) {
 228                 // This means `id` is very long
 229                 String suffix = timestamp + ext;
 230                 int idLengthLimit = Math.min(MAX_FILE_NAME_LENGTH - suffix.length(), id.length());
 231                 fileName = sanitizedFileName(id.substring(0, idLengthLimit) + suffix);
 232             } else {
 233                 if (label == null) {
 234                     fileName = sanitizedFileName(id + timestamp + ext);
 235                 } else {
 236                     String adjustedLabel = label;
 237                     if (label.length() > labelLengthLimit) {
 238                         adjustedLabel = label.substring(0, labelLengthLimit - ELLIPSIS.length()) + ELLIPSIS;
 239                     }
 240                     fileName = sanitizedFileName(id + '[' + adjustedLabel + ']' + timestamp + ext);
 241                 }
 242             }
 243             Path result = dumpDir.resolve(fileName);
 244             try {
 245                 if (createDirectory) {
 246                     return Files.createDirectory(result);
 247                 } else {
 248                     return Files.createFile(result);
 249                 }
 250             } catch (FileAlreadyExistsException e) {
 251                 timestamp = "_" + Long.toString(System.currentTimeMillis());
 252             }
 253         }
 254     }
 255 
 256     private static String getGraphName(StructuredGraph graph) {
 257         if (graph.name != null) {
 258             return graph.name;
 259         } else if (graph.method() != null) {
 260             return graph.method().format("%h.%n(%p)").replace(" ", "");
 261         } else {
 262             return graph.toString();
 263         }
 264     }
 265 
 266     private static GraphPrinter createFilePrinter(Graph graph, OptionValues options, SnippetReflectionProvider snippetReflection) throws IOException {
 267         Path path = createDumpPath(options, graph, PrintBinaryGraphs.getValue(options) ? "bgv" : "gv.xml", false);
 268         try {
 269             GraphPrinter printer;
 270             if (DebugOptions.PrintBinaryGraphs.getValue(options)) {
 271                 printer = new BinaryGraphPrinter(FileChannel.open(path, StandardOpenOption.WRITE), snippetReflection);
 272             } else {
 273                 printer = new IdealGraphPrinter(Files.newOutputStream(path), true, snippetReflection);
 274             }
 275             return printer;
 276         } catch (IOException e) {
 277             throw new IOException(String.format("Failed to open %s to dump IGV graphs", path), e);
 278         }
 279     }
 280 }