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.Options.CanonicalGraphStringsCheckConstants;
  26 import static org.graalvm.compiler.debug.GraalDebugConfig.Options.CanonicalGraphStringsExcludeVirtuals;
  27 import static org.graalvm.compiler.debug.GraalDebugConfig.Options.CanonicalGraphStringsRemoveIdentities;
  28 import static org.graalvm.compiler.debug.GraalDebugConfig.Options.PrintCanonicalGraphStringFlavor;
  29 
  30 import java.io.BufferedWriter;
  31 import java.io.FileWriter;
  32 import java.io.IOException;
  33 import java.io.PrintWriter;
  34 import java.io.StringWriter;
  35 import java.nio.file.Path;
  36 import java.util.ArrayList;
  37 import java.util.Collections;
  38 import java.util.Iterator;
  39 import java.util.List;
  40 import java.util.Map;
  41 import java.util.regex.Pattern;
  42 
  43 import org.graalvm.compiler.api.replacements.SnippetReflectionProvider;
  44 import org.graalvm.compiler.core.common.Fields;
  45 import org.graalvm.compiler.debug.TTY;
  46 import org.graalvm.compiler.graph.Graph;
  47 import org.graalvm.compiler.graph.Node;
  48 import org.graalvm.compiler.graph.NodeMap;
  49 import org.graalvm.compiler.graph.Position;
  50 import org.graalvm.compiler.nodeinfo.Verbosity;
  51 import org.graalvm.compiler.nodes.ConstantNode;
  52 import org.graalvm.compiler.nodes.FixedNode;
  53 import org.graalvm.compiler.nodes.FixedWithNextNode;
  54 import org.graalvm.compiler.nodes.FrameState;
  55 import org.graalvm.compiler.nodes.FullInfopointNode;
  56 import org.graalvm.compiler.nodes.PhiNode;
  57 import org.graalvm.compiler.nodes.ProxyNode;
  58 import org.graalvm.compiler.nodes.StructuredGraph;
  59 import org.graalvm.compiler.nodes.ValueNode;
  60 import org.graalvm.compiler.nodes.cfg.Block;
  61 import org.graalvm.compiler.nodes.cfg.ControlFlowGraph;
  62 import org.graalvm.compiler.nodes.virtual.VirtualObjectNode;
  63 import org.graalvm.compiler.options.OptionValues;
  64 import org.graalvm.compiler.phases.schedule.SchedulePhase;
  65 
  66 import jdk.vm.ci.meta.ResolvedJavaMethod;
  67 
  68 public class CanonicalStringGraphPrinter implements GraphPrinter {
  69     private static final Pattern IDENTITY_PATTERN = Pattern.compile("([A-Za-z0-9$_]+)@[0-9a-f]+");
  70     private Path currentDirectory;
  71     private Path root;
  72     private SnippetReflectionProvider snippetReflection;
  73 
  74     public CanonicalStringGraphPrinter(Path directory) {
  75         this.currentDirectory = directory;
  76         this.root = directory;
  77     }
  78 
  79     @Override
  80     public void setSnippetReflectionProvider(SnippetReflectionProvider snippetReflection) {
  81         this.snippetReflection = snippetReflection;
  82     }
  83 
  84     @Override
  85     public SnippetReflectionProvider getSnippetReflectionProvider() {
  86         return snippetReflection;
  87     }
  88 
  89     protected static String escapeFileName(String name) {
  90         byte[] bytes = name.getBytes();
  91         StringBuilder sb = new StringBuilder();
  92         for (byte b : bytes) {
  93             if ((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9') || b == '.' || b == '-' || b == '_') {
  94                 sb.append((char) b);
  95             } else {
  96                 sb.append('%');
  97                 sb.append(Integer.toHexString(b));
  98             }
  99         }
 100         return sb.toString();
 101     }
 102 
 103     private static String removeIdentities(String str) {
 104         return IDENTITY_PATTERN.matcher(str).replaceAll("$1");
 105     }
 106 
 107     protected static void writeCanonicalGraphExpressionString(ValueNode node, boolean checkConstants, boolean removeIdentities, PrintWriter writer) {
 108         writer.print(node.getClass().getSimpleName());
 109         writer.print("(");
 110         Fields properties = node.getNodeClass().getData();
 111         for (int i = 0; i < properties.getCount(); i++) {
 112             String dataStr = String.valueOf(properties.get(node, i));
 113             if (removeIdentities) {
 114                 dataStr = removeIdentities(dataStr);
 115             }
 116             writer.print(dataStr);
 117             if (i + 1 < properties.getCount() || node.inputPositions().iterator().hasNext()) {
 118                 writer.print(", ");
 119             }
 120         }
 121         Iterator<Position> iterator = node.inputPositions().iterator();
 122         while (iterator.hasNext()) {
 123             Position position = iterator.next();
 124             Node input = position.get(node);
 125             if (checkConstants && input instanceof ConstantNode) {
 126                 ConstantNode constantNode = (ConstantNode) input;
 127                 String valueString = constantNode.getValue().toValueString();
 128                 if (removeIdentities) {
 129                     valueString = removeIdentities(valueString);
 130                 }
 131                 writer.print(valueString);
 132             } else if (input instanceof ValueNode && !(input instanceof PhiNode) && !(input instanceof FixedNode)) {
 133                 writeCanonicalGraphExpressionString((ValueNode) input, checkConstants, removeIdentities, writer);
 134             } else if (input == null) {
 135                 writer.print("null");
 136             } else {
 137                 writer.print(input.getClass().getSimpleName());
 138             }
 139             if (iterator.hasNext()) {
 140                 writer.print(", ");
 141             }
 142         }
 143         writer.print(")");
 144     }
 145 
 146     protected static void writeCanonicalExpressionCFGString(StructuredGraph graph, boolean checkConstants, boolean removeIdentities, PrintWriter writer) {
 147         ControlFlowGraph controlFlowGraph = ControlFlowGraph.compute(graph, true, true, false, false);
 148         for (Block block : controlFlowGraph.getBlocks()) {
 149             writer.print("Block ");
 150             writer.print(block);
 151             writer.print(" ");
 152             if (block == controlFlowGraph.getStartBlock()) {
 153                 writer.print("* ");
 154             }
 155             writer.print("-> ");
 156             for (Block successor : block.getSuccessors()) {
 157                 writer.print(successor);
 158                 writer.print(" ");
 159             }
 160             writer.println();
 161             FixedNode node = block.getBeginNode();
 162             while (node != null) {
 163                 writeCanonicalGraphExpressionString(node, checkConstants, removeIdentities, writer);
 164                 writer.println();
 165                 if (node instanceof FixedWithNextNode) {
 166                     node = ((FixedWithNextNode) node).next();
 167                 } else {
 168                     node = null;
 169                 }
 170             }
 171         }
 172     }
 173 
 174     protected static void writeCanonicalGraphString(StructuredGraph graph, boolean excludeVirtual, boolean checkConstants, PrintWriter writer) {
 175         SchedulePhase schedule = new SchedulePhase(SchedulePhase.SchedulingStrategy.EARLIEST);
 176         schedule.apply(graph);
 177         StructuredGraph.ScheduleResult scheduleResult = graph.getLastSchedule();
 178 
 179         NodeMap<Integer> canonicalId = graph.createNodeMap();
 180         int nextId = 0;
 181 
 182         List<String> constantsLines = null;
 183         if (checkConstants) {
 184             constantsLines = new ArrayList<>();
 185         }
 186 
 187         for (Block block : scheduleResult.getCFG().getBlocks()) {
 188             writer.print("Block ");
 189             writer.print(block);
 190             writer.print(" ");
 191             if (block == scheduleResult.getCFG().getStartBlock()) {
 192                 writer.print("* ");
 193             }
 194             writer.print("-> ");
 195             for (Block successor : block.getSuccessors()) {
 196                 writer.print(successor);
 197                 writer.print(" ");
 198             }
 199             writer.println();
 200             for (Node node : scheduleResult.getBlockToNodesMap().get(block)) {
 201                 if (node instanceof ValueNode && node.isAlive()) {
 202                     if (!excludeVirtual || !(node instanceof VirtualObjectNode || node instanceof ProxyNode || node instanceof FullInfopointNode)) {
 203                         if (node instanceof ConstantNode) {
 204                             if (constantsLines != null) {
 205                                 String name = node.toString(Verbosity.Name);
 206                                 String str = name + (excludeVirtual ? "" : "    (" + filteredUsageCount(node) + ")");
 207                                 constantsLines.add(str);
 208                             }
 209                         } else {
 210                             int id;
 211                             if (canonicalId.get(node) != null) {
 212                                 id = canonicalId.get(node);
 213                             } else {
 214                                 id = nextId++;
 215                                 canonicalId.set(node, id);
 216                             }
 217                             String name = node.getClass().getSimpleName();
 218                             writer.print("  ");
 219                             writer.print(id);
 220                             writer.print("|");
 221                             writer.print(name);
 222                             if (!excludeVirtual) {
 223                                 writer.print("    (");
 224                                 writer.print(filteredUsageCount(node));
 225                                 writer.print(")");
 226                             }
 227                             writer.println();
 228                         }
 229                     }
 230                 }
 231             }
 232         }
 233         if (constantsLines != null) {
 234             writer.print(constantsLines.size());
 235             writer.println(" constants:");
 236             Collections.sort(constantsLines);
 237             for (String s : constantsLines) {
 238                 writer.println(s);
 239             }
 240         }
 241     }
 242 
 243     public static String getCanonicalGraphString(StructuredGraph graph, boolean excludeVirtual, boolean checkConstants) {
 244         StringWriter stringWriter = new StringWriter();
 245         PrintWriter writer = new PrintWriter(stringWriter);
 246         writeCanonicalGraphString(graph, excludeVirtual, checkConstants, writer);
 247         writer.flush();
 248         return stringWriter.toString();
 249     }
 250 
 251     private static int filteredUsageCount(Node node) {
 252         return node.usages().filter(n -> !(n instanceof FrameState)).count();
 253     }
 254 
 255     @Override
 256     public void beginGroup(String name, String shortName, ResolvedJavaMethod method, int bci, Map<Object, Object> properties) throws IOException {
 257         currentDirectory = currentDirectory.resolve(escapeFileName(name));
 258     }
 259 
 260     @Override
 261     public void print(Graph graph, Map<Object, Object> properties, int id, String format, Object... args) throws IOException {
 262         if (graph instanceof StructuredGraph) {
 263             OptionValues options = graph.getOptions();
 264             StructuredGraph structuredGraph = (StructuredGraph) graph;
 265             currentDirectory.toFile().mkdirs();
 266             if (this.root != null) {
 267                 TTY.println("Dumping string graphs in %s", this.root);
 268                 this.root = null;
 269             }
 270             String title = formatTitle(id, format, args);
 271             Path filePath = currentDirectory.resolve(escapeFileName(title));
 272             try (PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(filePath.toFile())))) {
 273                 switch (PrintCanonicalGraphStringFlavor.getValue(options)) {
 274                     case 1:
 275                         writeCanonicalExpressionCFGString(structuredGraph, CanonicalGraphStringsCheckConstants.getValue(options), CanonicalGraphStringsRemoveIdentities.getValue(options), writer);
 276                         break;
 277                     case 0:
 278                     default:
 279                         writeCanonicalGraphString(structuredGraph, CanonicalGraphStringsExcludeVirtuals.getValue(options), CanonicalGraphStringsCheckConstants.getValue(options), writer);
 280                         break;
 281                 }
 282             }
 283         }
 284     }
 285 
 286     @Override
 287     public void endGroup() throws IOException {
 288         currentDirectory = currentDirectory.getParent();
 289         assert currentDirectory != null;
 290     }
 291 
 292     @Override
 293     public void close() {
 294     }
 295 }