/*
 * 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.
/** * 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.
Returns:an object specifying how method is to be inlined or null if it should not be inlined based on substitution related criteria
/** * 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.
Params:
  • method – the snippet or method substitution for which a graph will be created
  • args –
  • original – the original method if method is a substitution otherwise null
/** * 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.
/** * 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.
/** * Creates and preprocesses a graph for a replacement. */
public static class GraphMaker {
The replacements object that the graphs are created for.
/** The replacements object that the graphs are created for. */
protected final ReplacementsImpl replacements;
The method for which a graph is being created.
/** * The method for which a graph is being created. */
protected final ResolvedJavaMethod method;
The original method which method is substituting. Calls to method or substitutedMethod will be replaced with a forced inline of substitutedMethod.
/** * 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.
/** * 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.
Params:
  • node –
Returns:true for nodes that have side effects and are unsafe to delete
/** * 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.
/** * 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); } }