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 */
23package org.graalvm.compiler.printer;
24
25import static org.graalvm.compiler.debug.GraalDebugConfig.Options.CanonicalGraphStringsCheckConstants;
26import static org.graalvm.compiler.debug.GraalDebugConfig.Options.CanonicalGraphStringsExcludeVirtuals;
27import static org.graalvm.compiler.debug.GraalDebugConfig.Options.CanonicalGraphStringsRemoveIdentities;
28import static org.graalvm.compiler.debug.GraalDebugConfig.Options.PrintCanonicalGraphStringFlavor;
29
30import java.io.BufferedWriter;
31import java.io.FileWriter;
32import java.io.IOException;
33import java.io.PrintWriter;
34import java.io.StringWriter;
35import java.nio.file.Path;
36import java.util.ArrayList;
37import java.util.Collections;
38import java.util.Iterator;
39import java.util.List;
40import java.util.Map;
41import java.util.regex.Pattern;
42
43import org.graalvm.compiler.api.replacements.SnippetReflectionProvider;
44import org.graalvm.compiler.core.common.Fields;
45import org.graalvm.compiler.debug.TTY;
46import org.graalvm.compiler.graph.Graph;
47import org.graalvm.compiler.graph.Node;
48import org.graalvm.compiler.graph.NodeMap;
49import org.graalvm.compiler.graph.Position;
50import org.graalvm.compiler.nodeinfo.Verbosity;
51import org.graalvm.compiler.nodes.ConstantNode;
52import org.graalvm.compiler.nodes.FixedNode;
53import org.graalvm.compiler.nodes.FixedWithNextNode;
54import org.graalvm.compiler.nodes.FrameState;
55import org.graalvm.compiler.nodes.FullInfopointNode;
56import org.graalvm.compiler.nodes.PhiNode;
57import org.graalvm.compiler.nodes.ProxyNode;
58import org.graalvm.compiler.nodes.StructuredGraph;
59import org.graalvm.compiler.nodes.ValueNode;
60import org.graalvm.compiler.nodes.cfg.Block;
61import org.graalvm.compiler.nodes.cfg.ControlFlowGraph;
62import org.graalvm.compiler.nodes.virtual.VirtualObjectNode;
63import org.graalvm.compiler.phases.schedule.SchedulePhase;
64
65import jdk.vm.ci.meta.ResolvedJavaMethod;
66
67public 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}
289