--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.replacements/src/org/graalvm/compiler/replacements/ReplacementsImpl.java Thu Feb 16 15:46:09 2017 -0800
@@ -0,0 +1,446 @@
+/*
+ * Copyright (c) 2011, 2016, 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.replacements;
+
+import static org.graalvm.compiler.core.common.CompilationIdentifier.INVALID_COMPILATION_ID;
+import static org.graalvm.compiler.core.common.GraalOptions.DeoptALot;
+import static org.graalvm.compiler.core.common.GraalOptions.UseSnippetGraphCache;
+import static org.graalvm.compiler.java.BytecodeParserOptions.InlineDuringParsing;
+import static org.graalvm.compiler.java.BytecodeParserOptions.InlineIntrinsicsDuringParsing;
+import static org.graalvm.compiler.nodes.StructuredGraph.NO_PROFILING_INFO;
+import static org.graalvm.compiler.nodes.graphbuilderconf.InlineInvokePlugin.InlineInfo.createIntrinsicInlineInfo;
+import static org.graalvm.compiler.nodes.graphbuilderconf.IntrinsicContext.CompilationContext.INLINE_AFTER_PARSING;
+import static org.graalvm.compiler.phases.common.DeadCodeEliminationPhase.Optionality.Required;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.graalvm.compiler.api.replacements.Fold;
+import org.graalvm.compiler.api.replacements.MethodSubstitution;
+import org.graalvm.compiler.api.replacements.Snippet;
+import org.graalvm.compiler.api.replacements.SnippetReflectionProvider;
+import org.graalvm.compiler.api.replacements.SnippetTemplateCache;
+import org.graalvm.compiler.bytecode.BytecodeProvider;
+import org.graalvm.compiler.bytecode.ResolvedJavaMethodBytecode;
+import org.graalvm.compiler.core.common.CollectionsFactory;
+import org.graalvm.compiler.core.common.GraalOptions;
+import org.graalvm.compiler.core.common.spi.ConstantFieldProvider;
+import org.graalvm.compiler.debug.Debug;
+import org.graalvm.compiler.debug.Debug.Scope;
+import org.graalvm.compiler.debug.DebugCloseable;
+import org.graalvm.compiler.debug.DebugTimer;
+import org.graalvm.compiler.debug.GraalError;
+import org.graalvm.compiler.graph.Node;
+import org.graalvm.compiler.graph.Node.NodeIntrinsic;
+import org.graalvm.compiler.java.GraphBuilderPhase;
+import org.graalvm.compiler.java.GraphBuilderPhase.Instance;
+import org.graalvm.compiler.nodes.CallTargetNode;
+import org.graalvm.compiler.nodes.Invoke;
+import org.graalvm.compiler.nodes.StateSplit;
+import org.graalvm.compiler.nodes.StructuredGraph;
+import org.graalvm.compiler.nodes.StructuredGraph.AllowAssumptions;
+import org.graalvm.compiler.nodes.ValueNode;
+import org.graalvm.compiler.nodes.graphbuilderconf.GeneratedInvocationPlugin;
+import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration;
+import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration.Plugins;
+import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderContext;
+import org.graalvm.compiler.nodes.graphbuilderconf.InlineInvokePlugin;
+import org.graalvm.compiler.nodes.graphbuilderconf.IntrinsicContext;
+import org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugin;
+import org.graalvm.compiler.nodes.graphbuilderconf.MethodSubstitutionPlugin;
+import org.graalvm.compiler.nodes.java.MethodCallTargetNode;
+import org.graalvm.compiler.nodes.spi.Replacements;
+import org.graalvm.compiler.nodes.spi.StampProvider;
+import org.graalvm.compiler.options.OptionValue;
+import org.graalvm.compiler.options.OptionValue.OverrideScope;
+import org.graalvm.compiler.phases.OptimisticOptimizations;
+import org.graalvm.compiler.phases.common.CanonicalizerPhase;
+import org.graalvm.compiler.phases.common.ConvertDeoptimizeToGuardPhase;
+import org.graalvm.compiler.phases.common.DeadCodeEliminationPhase;
+import org.graalvm.compiler.phases.tiers.PhaseContext;
+import org.graalvm.compiler.phases.util.Providers;
+import org.graalvm.compiler.word.Word;
+
+import jdk.vm.ci.code.TargetDescription;
+import jdk.vm.ci.meta.ConstantReflectionProvider;
+import jdk.vm.ci.meta.MetaAccessProvider;
+import jdk.vm.ci.meta.ResolvedJavaMethod;
+import jdk.vm.ci.meta.ResolvedJavaType;
+
+public class ReplacementsImpl implements Replacements, InlineInvokePlugin {
+
+ public final Providers providers;
+ public final SnippetReflectionProvider snippetReflection;
+ public final TargetDescription target;
+ private GraphBuilderConfiguration.Plugins graphBuilderPlugins;
+
+ /**
+ * The preprocessed replacement graphs.
+ */
+ protected final ConcurrentMap<ResolvedJavaMethod, StructuredGraph> graphs;
+
+ protected final BytecodeProvider bytecodeProvider;
+
+ public void setGraphBuilderPlugins(GraphBuilderConfiguration.Plugins plugins) {
+ assert this.graphBuilderPlugins == null;
+ this.graphBuilderPlugins = plugins;
+ }
+
+ public GraphBuilderConfiguration.Plugins getGraphBuilderPlugins() {
+ return graphBuilderPlugins;
+ }
+
+ protected boolean hasGeneratedInvocationPluginAnnotation(ResolvedJavaMethod method) {
+ return method.getAnnotation(Node.NodeIntrinsic.class) != null || method.getAnnotation(Fold.class) != null;
+ }
+
+ protected boolean hasGenericInvocationPluginAnnotation(ResolvedJavaMethod method) {
+ return method.getAnnotation(Word.Operation.class) != null;
+ }
+
+ private static final int MAX_GRAPH_INLINING_DEPTH = 100; // more than enough
+
+ /**
+ * Determines whether a given method should be inlined based on whether it has a substitution or
+ * whether the inlining context is already within a substitution.
+ *
+ * @return an object specifying how {@code method} is to be inlined or null if it should not be
+ * inlined based on substitution related criteria
+ */
+ @Override
+ public InlineInfo shouldInlineInvoke(GraphBuilderContext b, ResolvedJavaMethod method, ValueNode[] args) {
+ ResolvedJavaMethod subst = getSubstitutionMethod(method);
+ if (subst != null) {
+ if (b.parsingIntrinsic() || InlineDuringParsing.getValue() || InlineIntrinsicsDuringParsing.getValue()) {
+ // Forced inlining of intrinsics
+ return createIntrinsicInlineInfo(subst, bytecodeProvider);
+ }
+ return null;
+ }
+ if (b.parsingIntrinsic()) {
+ if (hasGeneratedInvocationPluginAnnotation(method)) {
+ throw new GraalError("%s should have been handled by a %s", method.format("%H.%n(%p)"), GeneratedInvocationPlugin.class.getSimpleName());
+ }
+ if (hasGenericInvocationPluginAnnotation(method)) {
+ throw new GraalError("%s should have been handled by %s", method.format("%H.%n(%p)"), WordOperationPlugin.class.getSimpleName());
+ }
+
+ assert b.getDepth() < MAX_GRAPH_INLINING_DEPTH : "inlining limit exceeded";
+
+ if (method.getName().startsWith("$jacoco")) {
+ throw new GraalError("Found call to JaCoCo instrumentation method " + method.format("%H.%n(%p)") + ". Placing \"//JaCoCo Exclude\" anywhere in " +
+ b.getMethod().getDeclaringClass().getSourceFileName() + " should fix this.");
+ }
+
+ // Force inlining when parsing replacements
+ return createIntrinsicInlineInfo(method, bytecodeProvider);
+ } else {
+ assert method.getAnnotation(NodeIntrinsic.class) == null : String.format("@%s method %s must only be called from within a replacement%n%s", NodeIntrinsic.class.getSimpleName(),
+ method.format("%h.%n"), b);
+ }
+ return null;
+ }
+
+ @Override
+ public void notifyNotInlined(GraphBuilderContext b, ResolvedJavaMethod method, Invoke invoke) {
+ if (b.parsingIntrinsic()) {
+ IntrinsicContext intrinsic = b.getIntrinsic();
+ if (!intrinsic.isCallToOriginal(method)) {
+ throw new GraalError("All non-recursive calls in the intrinsic %s must be inlined or intrinsified: found call to %s",
+ intrinsic.getIntrinsicMethod().format("%H.%n(%p)"), method.format("%h.%n(%p)"));
+ }
+ }
+ }
+
+ // This map is key'ed by a class name instead of a Class object so that
+ // it is stable across VM executions (in support of replay compilation).
+ private final Map<String, SnippetTemplateCache> snippetTemplateCache;
+
+ public ReplacementsImpl(Providers providers, SnippetReflectionProvider snippetReflection, BytecodeProvider bytecodeProvider, TargetDescription target) {
+ this.providers = providers.copyWith(this);
+ this.snippetReflection = snippetReflection;
+ this.target = target;
+ this.graphs = new ConcurrentHashMap<>();
+ this.snippetTemplateCache = CollectionsFactory.newMap();
+ this.bytecodeProvider = bytecodeProvider;
+ }
+
+ private static final DebugTimer SnippetPreparationTime = Debug.timer("SnippetPreparationTime");
+
+ @Override
+ public StructuredGraph getSnippet(ResolvedJavaMethod method, Object[] args) {
+ return getSnippet(method, null, args);
+ }
+
+ @Override
+ @SuppressWarnings("try")
+ public StructuredGraph getSnippet(ResolvedJavaMethod method, ResolvedJavaMethod recursiveEntry, Object[] args) {
+ assert method.getAnnotation(Snippet.class) != null : "Snippet must be annotated with @" + Snippet.class.getSimpleName();
+ assert method.hasBytecodes() : "Snippet must not be abstract or native";
+
+ StructuredGraph graph = UseSnippetGraphCache.getValue() ? graphs.get(method) : null;
+ if (graph == null) {
+ try (DebugCloseable a = SnippetPreparationTime.start()) {
+ StructuredGraph newGraph = makeGraph(method, args, recursiveEntry);
+ Debug.counter("SnippetNodeCount[%#s]", method).add(newGraph.getNodeCount());
+ if (!UseSnippetGraphCache.getValue() || args != null) {
+ return newGraph;
+ }
+ graphs.putIfAbsent(method, newGraph);
+ graph = graphs.get(method);
+ }
+ }
+ return graph;
+ }
+
+ @Override
+ public void registerSnippet(ResolvedJavaMethod method) {
+ // No initialization needed as snippet graphs are created on demand in getSnippet
+ }
+
+ @Override
+ public boolean hasSubstitution(ResolvedJavaMethod method, int invokeBci) {
+ InvocationPlugin plugin = graphBuilderPlugins.getInvocationPlugins().lookupInvocation(method);
+ return plugin != null && (!plugin.inlineOnly() || invokeBci >= 0);
+ }
+
+ @Override
+ public BytecodeProvider getReplacementBytecodeProvider() {
+ return bytecodeProvider;
+ }
+
+ @Override
+ public ResolvedJavaMethod getSubstitutionMethod(ResolvedJavaMethod method) {
+ InvocationPlugin plugin = graphBuilderPlugins.getInvocationPlugins().lookupInvocation(method);
+ if (plugin instanceof MethodSubstitutionPlugin) {
+ MethodSubstitutionPlugin msPlugin = (MethodSubstitutionPlugin) plugin;
+ return msPlugin.getSubstitute(providers.getMetaAccess());
+ }
+ return null;
+ }
+
+ @Override
+ public StructuredGraph getSubstitution(ResolvedJavaMethod method, int invokeBci) {
+ StructuredGraph result;
+ InvocationPlugin plugin = graphBuilderPlugins.getInvocationPlugins().lookupInvocation(method);
+ if (plugin != null && (!plugin.inlineOnly() || invokeBci >= 0)) {
+ MetaAccessProvider metaAccess = providers.getMetaAccess();
+ if (plugin instanceof MethodSubstitutionPlugin) {
+ MethodSubstitutionPlugin msPlugin = (MethodSubstitutionPlugin) plugin;
+ ResolvedJavaMethod substitute = msPlugin.getSubstitute(metaAccess);
+ StructuredGraph graph = graphs.get(substitute);
+ if (graph == null) {
+ graph = makeGraph(substitute, null, method);
+ graph.freeze();
+ graphs.putIfAbsent(substitute, graph);
+ graph = graphs.get(substitute);
+ }
+ assert graph.isFrozen();
+ result = graph;
+ } else {
+ ResolvedJavaMethodBytecode code = new ResolvedJavaMethodBytecode(method);
+ ConstantReflectionProvider constantReflection = providers.getConstantReflection();
+ ConstantFieldProvider constantFieldProvider = providers.getConstantFieldProvider();
+ StampProvider stampProvider = providers.getStampProvider();
+ result = new IntrinsicGraphBuilder(metaAccess, constantReflection, constantFieldProvider, stampProvider, code, invokeBci).buildGraph(plugin);
+ }
+ } else {
+ result = null;
+ }
+ return result;
+ }
+
+ /**
+ * Creates a preprocessed graph for a snippet or method substitution.
+ *
+ * @param method the snippet or method substitution for which a graph will be created
+ * @param args
+ * @param original the original method if {@code method} is a {@linkplain MethodSubstitution
+ * substitution} otherwise null
+ */
+ @SuppressWarnings("try")
+ public StructuredGraph makeGraph(ResolvedJavaMethod method, Object[] args, ResolvedJavaMethod original) {
+ try (OverrideScope s = OptionValue.override(DeoptALot, false)) {
+ return createGraphMaker(method, original).makeGraph(args);
+ }
+ }
+
+ /**
+ * Can be overridden to return an object that specializes various parts of graph preprocessing.
+ */
+ protected GraphMaker createGraphMaker(ResolvedJavaMethod substitute, ResolvedJavaMethod original) {
+ return new GraphMaker(this, substitute, original);
+ }
+
+ /**
+ * Creates and preprocesses a graph for a replacement.
+ */
+ public static class GraphMaker {
+
+ /** The replacements object that the graphs are created for. */
+ protected final ReplacementsImpl replacements;
+
+ /**
+ * The method for which a graph is being created.
+ */
+ protected final ResolvedJavaMethod method;
+
+ /**
+ * The original method which {@link #method} is substituting. Calls to {@link #method} or
+ * {@link #substitutedMethod} will be replaced with a forced inline of
+ * {@link #substitutedMethod}.
+ */
+ protected final ResolvedJavaMethod substitutedMethod;
+
+ protected GraphMaker(ReplacementsImpl replacements, ResolvedJavaMethod substitute, ResolvedJavaMethod substitutedMethod) {
+ this.replacements = replacements;
+ this.method = substitute;
+ this.substitutedMethod = substitutedMethod;
+ }
+
+ @SuppressWarnings("try")
+ public StructuredGraph makeGraph(Object[] args) {
+ try (Scope s = Debug.scope("BuildSnippetGraph", method)) {
+ assert method.hasBytecodes() : method;
+ StructuredGraph graph = buildInitialGraph(method, args);
+
+ finalizeGraph(graph);
+
+ Debug.dump(Debug.INFO_LOG_LEVEL, graph, "%s: Final", method.getName());
+
+ return graph;
+ } catch (Throwable e) {
+ throw Debug.handle(e);
+ }
+ }
+
+ /**
+ * Does final processing of a snippet graph.
+ */
+ protected void finalizeGraph(StructuredGraph graph) {
+ if (!GraalOptions.SnippetCounters.getValue() || graph.getNodes().filter(SnippetCounterNode.class).isEmpty()) {
+ int sideEffectCount = 0;
+ assert (sideEffectCount = graph.getNodes().filter(e -> hasSideEffect(e)).count()) >= 0;
+ new ConvertDeoptimizeToGuardPhase().apply(graph, null);
+ assert sideEffectCount == graph.getNodes().filter(e -> hasSideEffect(e)).count() : "deleted side effecting node";
+
+ new DeadCodeEliminationPhase(Required).apply(graph);
+ } else {
+ // ConvertDeoptimizeToGuardPhase will eliminate snippet counters on paths
+ // that terminate in a deopt so we disable it if the graph contains
+ // snippet counters. The trade off is that we miss out on guard
+ // coalescing opportunities.
+ }
+ }
+
+ /**
+ * Filter nodes which have side effects and shouldn't be deleted from snippets when
+ * converting deoptimizations to guards. Currently this only allows exception constructors
+ * to be eliminated to cover the case when Java assertions are in the inlined code.
+ *
+ * @param node
+ * @return true for nodes that have side effects and are unsafe to delete
+ */
+ private boolean hasSideEffect(Node node) {
+ if (node instanceof StateSplit) {
+ if (((StateSplit) node).hasSideEffect()) {
+ if (node instanceof Invoke) {
+ CallTargetNode callTarget = ((Invoke) node).callTarget();
+ if (callTarget instanceof MethodCallTargetNode) {
+ ResolvedJavaMethod targetMethod = ((MethodCallTargetNode) callTarget).targetMethod();
+ if (targetMethod.isConstructor()) {
+ ResolvedJavaType throwableType = replacements.providers.getMetaAccess().lookupJavaType(Throwable.class);
+ return !throwableType.isAssignableFrom(targetMethod.getDeclaringClass());
+ }
+ }
+ }
+ // Not an exception constructor call
+ return true;
+ }
+ }
+ // Not a StateSplit
+ return false;
+ }
+
+ /**
+ * Builds the initial graph for a snippet.
+ */
+ @SuppressWarnings("try")
+ protected StructuredGraph buildInitialGraph(final ResolvedJavaMethod methodToParse, Object[] args) {
+ // Replacements cannot have optimistic assumptions since they have
+ // to be valid for the entire run of the VM.
+
+ final StructuredGraph graph = new StructuredGraph(methodToParse, AllowAssumptions.NO, NO_PROFILING_INFO, INVALID_COMPILATION_ID);
+
+ // They are not user code so they do not participate in unsafe access tracking
+ graph.disableUnsafeAccessTracking();
+
+ try (Scope s = Debug.scope("buildInitialGraph", graph)) {
+ MetaAccessProvider metaAccess = replacements.providers.getMetaAccess();
+
+ Plugins plugins = new Plugins(replacements.graphBuilderPlugins);
+ GraphBuilderConfiguration config = GraphBuilderConfiguration.getSnippetDefault(plugins);
+ if (args != null) {
+ plugins.prependParameterPlugin(new ConstantBindingParameterPlugin(args, metaAccess, replacements.snippetReflection));
+ }
+
+ IntrinsicContext initialIntrinsicContext = null;
+ if (method.getAnnotation(Snippet.class) == null) {
+ // Post-parse inlined intrinsic
+ initialIntrinsicContext = new IntrinsicContext(substitutedMethod, method, replacements.bytecodeProvider, INLINE_AFTER_PARSING);
+ } else {
+ // Snippet
+ ResolvedJavaMethod original = substitutedMethod != null ? substitutedMethod : method;
+ initialIntrinsicContext = new IntrinsicContext(original, method, replacements.bytecodeProvider, INLINE_AFTER_PARSING);
+ }
+
+ createGraphBuilder(metaAccess, replacements.providers.getStampProvider(), replacements.providers.getConstantReflection(), replacements.providers.getConstantFieldProvider(), config,
+ OptimisticOptimizations.NONE, initialIntrinsicContext).apply(graph);
+
+ new CanonicalizerPhase().apply(graph, new PhaseContext(replacements.providers));
+ } catch (Throwable e) {
+ throw Debug.handle(e);
+ }
+ return graph;
+ }
+
+ protected Instance createGraphBuilder(MetaAccessProvider metaAccess, StampProvider stampProvider, ConstantReflectionProvider constantReflection, ConstantFieldProvider constantFieldProvider,
+ GraphBuilderConfiguration graphBuilderConfig, OptimisticOptimizations optimisticOpts, IntrinsicContext initialIntrinsicContext) {
+ return new GraphBuilderPhase.Instance(metaAccess, stampProvider, constantReflection, constantFieldProvider, graphBuilderConfig, optimisticOpts,
+ initialIntrinsicContext);
+ }
+ }
+
+ @Override
+ public void registerSnippetTemplateCache(SnippetTemplateCache templates) {
+ assert snippetTemplateCache.get(templates.getClass().getName()) == null;
+ snippetTemplateCache.put(templates.getClass().getName(), templates);
+ }
+
+ @Override
+ public <T extends SnippetTemplateCache> T getSnippetTemplateCache(Class<T> templatesClass) {
+ SnippetTemplateCache ret = snippetTemplateCache.get(templatesClass.getName());
+ return templatesClass.cast(ret);
+ }
+}