/* * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.graalvm.compiler.truffle.phases; import jdk.vm.ci.code.CodeUtil; import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.JavaKind; import jdk.vm.ci.meta.MetaUtil; import org.graalvm.compiler.api.replacements.SnippetReflectionProvider; import org.graalvm.compiler.core.common.type.StampFactory; import org.graalvm.compiler.core.common.type.TypeReference; import org.graalvm.compiler.debug.MethodFilter; import org.graalvm.compiler.debug.TTY; import org.graalvm.compiler.graph.Node; import org.graalvm.compiler.graph.NodeSourcePosition; import org.graalvm.compiler.nodes.ConstantNode; import org.graalvm.compiler.nodes.FixedWithNextNode; import org.graalvm.compiler.nodes.StructuredGraph; import org.graalvm.compiler.nodes.ValueNode; import org.graalvm.compiler.nodes.calc.AddNode; import org.graalvm.compiler.nodes.java.LoadIndexedNode; import org.graalvm.compiler.nodes.java.StoreIndexedNode; import org.graalvm.compiler.options.OptionValues; import org.graalvm.compiler.phases.BasePhase; import org.graalvm.compiler.phases.tiers.HighTierContext; import org.graalvm.compiler.truffle.OptimizedCallTarget; import org.graalvm.compiler.truffle.OptimizedDirectCallNode; import org.graalvm.compiler.truffle.TruffleCompilerOptions; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; public abstract class InstrumentPhase extends BasePhase<HighTierContext> { private static final String[] OMITTED_STACK_PATTERNS = new String[]{ OptimizedCallTarget.class.getName() + ".callProxy", OptimizedCallTarget.class.getName() + ".callRoot", OptimizedCallTarget.class.getName() + ".callInlined", OptimizedDirectCallNode.class.getName() + ".callProxy", OptimizedDirectCallNode.class.getName() + ".call" }; private final Instrumentation instrumentation; protected final MethodFilter[] methodFilter; protected final SnippetReflectionProvider snippetReflection; public InstrumentPhase(OptionValues options, SnippetReflectionProvider snippetReflection, Instrumentation instrumentation) { String filterValue = instrumentationFilter(options); if (filterValue != null) { methodFilter = MethodFilter.parse(filterValue); } else { methodFilter = new MethodFilter[0]; } this.snippetReflection = snippetReflection; this.instrumentation = instrumentation; } public Instrumentation getInstrumentation() { return instrumentation; } protected String instrumentationFilter(OptionValues options) { return TruffleCompilerOptions.TruffleInstrumentFilter.getValue(options); } protected static void insertCounter(StructuredGraph graph, HighTierContext context, JavaConstant tableConstant, FixedWithNextNode targetNode, int slotIndex) { assert (tableConstant != null); TypeReference typeRef = TypeReference.createExactTrusted(context.getMetaAccess().lookupJavaType(tableConstant)); ConstantNode table = graph.unique(new ConstantNode(tableConstant, StampFactory.object(typeRef, true))); ConstantNode rawIndex = graph.unique(ConstantNode.forInt(slotIndex)); LoadIndexedNode load = graph.add(new LoadIndexedNode(null, table, rawIndex, JavaKind.Long)); ConstantNode one = graph.unique(ConstantNode.forLong(1L)); ValueNode add = graph.unique(new AddNode(load, one)); StoreIndexedNode store = graph.add(new StoreIndexedNode(table, rawIndex, JavaKind.Long, add)); graph.addAfterFixed(targetNode, load); graph.addAfterFixed(load, store); } @Override public float codeSizeIncrease() { return 2.5f; } @Override protected void run(StructuredGraph graph, HighTierContext context) { JavaConstant tableConstant = snippetReflection.forObject(instrumentation.getAccessTable()); try { instrumentGraph(graph, context, tableConstant); } catch (Exception e) { throw new RuntimeException(e); } } protected abstract void instrumentGraph(StructuredGraph graph, HighTierContext context, JavaConstant tableConstant); protected abstract int instrumentationPointSlotCount(); protected abstract boolean instrumentPerInlineSite(OptionValues options); protected abstract Point createPoint(int id, int startIndex, Node n); public Point getOrCreatePoint(Node n) { Point point = instrumentation.getOrCreatePoint(methodFilter, n, this); assert point == null || point.slotCount() == instrumentationPointSlotCount() : "Slot count mismatch between instrumentation point and expected value."; return point; } public static class Instrumentation { private Comparator<Point> pointsComparator = new Comparator<Point>() { @Override public int compare(Point x, Point y) { long diff = y.getHotness() - x.getHotness(); if (diff < 0) { return -1; } else if (diff == 0) { return 0; } else { return 1; } } }; private Comparator<Map.Entry<String, Point>> entriesComparator = new Comparator<Map.Entry<String, Point>>() { @Override public int compare(Map.Entry<String, Point> x, Map.Entry<String, Point> y) { long diff = y.getValue().getHotness() - x.getValue().getHotness(); if (diff < 0) { return -1; } else if (diff == 0) { return 0; } else { return 1; } } }; private final long[] accessTable; public Map<String, Point> pointMap = new LinkedHashMap<>(); public int tableIdCount; public int tableStartIndex; public Instrumentation(long[] accessTable) { this.accessTable = accessTable; } /* * Node source location is determined by its inlining chain. A flag value controls whether * we discriminate nodes by their inlining site, or only by the method in which they were * defined. */ private static String filterAndEncode(MethodFilter[] methodFilter, Node node, InstrumentPhase phase) { NodeSourcePosition pos = node.getNodeSourcePosition(); if (pos != null) { if (!MethodFilter.matches(methodFilter, pos.getMethod())) { return null; } if (phase.instrumentPerInlineSite(node.getOptions())) { StringBuilder sb = new StringBuilder(); while (pos != null) { MetaUtil.appendLocation(sb.append("at "), pos.getMethod(), pos.getBCI()); pos = pos.getCaller(); if (pos != null) { sb.append(CodeUtil.NEW_LINE); } } return sb.toString(); } else { return MetaUtil.appendLocation(new StringBuilder(), pos.getMethod(), pos.getBCI()).toString(); } } else { // IfNode has no position information, and is probably synthetic, so we do not // instrument it. return null; } } private static String prettify(String key, Point p, OptionValues options) { if (p.isPrettified(options)) { StringBuilder sb = new StringBuilder(); NodeSourcePosition pos = p.getPosition(); NodeSourcePosition lastPos = null; int repetitions = 1; callerChainLoop: while (pos != null) { // Skip stack frame if it is a known pattern. for (String pattern : OMITTED_STACK_PATTERNS) { if (pos.getMethod().format("%H.%n(%p)").contains(pattern)) { pos = pos.getCaller(); continue callerChainLoop; } } if (lastPos == null) { // Always output first method. lastPos = pos; MetaUtil.appendLocation(sb, pos.getMethod(), pos.getBCI()); } else if (!lastPos.getMethod().equals(pos.getMethod())) { // Output count for identical BCI outputs, and output next method. if (repetitions > 1) { sb.append(" x" + repetitions); repetitions = 1; } sb.append(CodeUtil.NEW_LINE); lastPos = pos; MetaUtil.appendLocation(sb, pos.getMethod(), pos.getBCI()); } else if (lastPos.getBCI() != pos.getBCI()) { // Conflate identical BCI outputs. if (repetitions > 1) { sb.append(" x" + repetitions); repetitions = 1; } lastPos = pos; sb.append(" [bci: " + pos.getBCI() + "]"); } else { // Identical BCI to the one seen previously. repetitions++; } pos = pos.getCaller(); } if (repetitions > 1) { sb.append(" x" + repetitions); repetitions = 1; } return sb.toString(); } else { return key; } } public synchronized ArrayList<String> accessTableToList(OptionValues options) { /* * Using sortedEntries.addAll(pointMap.entrySet(), instead of the iteration bellow, is * not safe and is detected by FindBugs. From FindBugs: * * "The entrySet() method is allowed to return a view of the underlying Map in which a * single Entry object is reused and returned during the iteration. As of Java 1.6, both * IdentityHashMap and EnumMap did so. When iterating through such a Map, the Entry * value is only valid until you advance to the next iteration. If, for example, you try * to pass such an entrySet to an addAll method, things will go badly wrong." */ List<Map.Entry<String, Point>> sortedEntries = new ArrayList<>(); for (Map.Entry<String, Point> entry : pointMap.entrySet()) { Map.Entry<String, Point> immutableEntry = new AbstractMap.SimpleImmutableEntry<>(entry.getKey(), entry.getValue()); sortedEntries.add(immutableEntry); } Collections.sort(sortedEntries, entriesComparator); ArrayList<String> list = new ArrayList<>(); for (Map.Entry<String, Point> entry : sortedEntries) { list.add(prettify(entry.getKey(), entry.getValue(), options) + CodeUtil.NEW_LINE + entry.getValue()); } return list; } public synchronized ArrayList<String> accessTableToHistogram() { long totalExecutions = 0; for (Point point : pointMap.values()) { totalExecutions += point.getHotness(); } List<Point> sortedPoints = new ArrayList<>(); sortedPoints.addAll(pointMap.values()); Collections.sort(sortedPoints, pointsComparator); ArrayList<String> histogram = new ArrayList<>(); for (Point point : sortedPoints) { int length = (int) ((1.0 * point.getHotness() / totalExecutions) * 80); String bar = String.join("", Collections.nCopies(length, "*")); histogram.add(String.format("%3d: %s", point.getId(), bar)); } return histogram; } public synchronized void dumpAccessTable(OptionValues options) { // Dump accumulated profiling information. TTY.println("Execution profile (sorted by hotness)"); TTY.println("====================================="); for (String line : accessTableToHistogram()) { TTY.println(line); } TTY.println(); for (String line : accessTableToList(options)) { TTY.println(line); TTY.println(); } } public synchronized Point getOrCreatePoint(MethodFilter[] methodFilter, Node n, InstrumentPhase phase) { String key = filterAndEncode(methodFilter, n, phase); if (key == null) { return null; } Point existing = pointMap.get(key); int slotCount = phase.instrumentationPointSlotCount(); if (existing != null) { return existing; } else if (tableStartIndex + slotCount < phase.getInstrumentation().getAccessTable().length) { int id = tableIdCount++; int startIndex = tableStartIndex; tableStartIndex += slotCount; Point p = phase.createPoint(id, startIndex, n); pointMap.put(key, p); return p; } else { if (tableStartIndex < phase.getInstrumentation().getAccessTable().length) { TTY.println("Maximum number of instrumentation counters exceeded."); tableStartIndex += slotCount; } return null; } } public long[] getAccessTable() { return accessTable; } } public abstract static class Point { protected int id; protected int rawIndex; protected NodeSourcePosition position; public Point(int id, int rawIndex, NodeSourcePosition position) { this.id = id; this.rawIndex = rawIndex; this.position = position; } public int slotIndex(int offset) { assert offset < slotCount() : "Offset exceeds instrumentation point's slot count: " + offset; return rawIndex + offset; } public int getId() { return id; } public NodeSourcePosition getPosition() { return position; } public abstract int slotCount(); public abstract long getHotness(); public abstract boolean isPrettified(OptionValues options); } }