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