blob: 37ae9c1eeb048b5e99a751bd1afead645062ef6e [file] [log] [blame]
/*
* Copyright (c) 2014, 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.nodes.StructuredGraph.NO_PROFILING_INFO;
import static org.graalvm.compiler.nodes.graphbuilderconf.IntrinsicContext.CompilationContext.INLINE_AFTER_PARSING;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import org.graalvm.compiler.core.common.spi.ConstantFieldProvider;
import org.graalvm.compiler.core.common.type.StampFactory;
import org.graalvm.compiler.core.common.type.StampPair;
import org.graalvm.compiler.graph.Graph;
import org.graalvm.compiler.graph.Node.ValueNumberable;
import org.graalvm.compiler.java.FrameStateBuilder;
import org.graalvm.compiler.java.GraphBuilderPhase;
import org.graalvm.compiler.nodes.AbstractBeginNode;
import org.graalvm.compiler.nodes.AbstractMergeNode;
import org.graalvm.compiler.nodes.BeginNode;
import org.graalvm.compiler.nodes.CallTargetNode.InvokeKind;
import org.graalvm.compiler.nodes.EndNode;
import org.graalvm.compiler.nodes.FixedNode;
import org.graalvm.compiler.nodes.FixedWithNextNode;
import org.graalvm.compiler.nodes.IfNode;
import org.graalvm.compiler.nodes.InvokeNode;
import org.graalvm.compiler.nodes.LogicNode;
import org.graalvm.compiler.nodes.MergeNode;
import org.graalvm.compiler.nodes.StructuredGraph;
import org.graalvm.compiler.nodes.StructuredGraph.AllowAssumptions;
import org.graalvm.compiler.nodes.ValueNode;
import org.graalvm.compiler.nodes.calc.FloatingNode;
import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration;
import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration.Plugins;
import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderTool;
import org.graalvm.compiler.nodes.graphbuilderconf.IntrinsicContext;
import org.graalvm.compiler.nodes.java.MethodCallTargetNode;
import org.graalvm.compiler.nodes.spi.StampProvider;
import org.graalvm.compiler.nodes.type.StampTool;
import org.graalvm.compiler.phases.OptimisticOptimizations;
import org.graalvm.compiler.phases.common.DeadCodeEliminationPhase;
import org.graalvm.compiler.phases.common.DeadCodeEliminationPhase.Optionality;
import org.graalvm.compiler.phases.common.inlining.InliningUtil;
import org.graalvm.compiler.phases.util.Providers;
import org.graalvm.compiler.word.WordTypes;
import jdk.vm.ci.code.BytecodeFrame;
import jdk.vm.ci.meta.ConstantReflectionProvider;
import jdk.vm.ci.meta.JavaKind;
import jdk.vm.ci.meta.JavaType;
import jdk.vm.ci.meta.MetaAccessProvider;
import jdk.vm.ci.meta.ResolvedJavaMethod;
import jdk.vm.ci.meta.ResolvedJavaType;
import jdk.vm.ci.meta.Signature;
/**
* A utility for manually creating a graph. This will be expanded as necessary to support all
* subsystems that employ manual graph creation (as opposed to {@linkplain GraphBuilderPhase
* bytecode parsing} based graph creation).
*/
public class GraphKit implements GraphBuilderTool {
protected final Providers providers;
protected final StructuredGraph graph;
protected final WordTypes wordTypes;
protected final GraphBuilderConfiguration.Plugins graphBuilderPlugins;
protected FixedWithNextNode lastFixedNode;
private final List<Structure> structures;
abstract static class Structure {
}
public GraphKit(StructuredGraph graph, Providers providers, WordTypes wordTypes, GraphBuilderConfiguration.Plugins graphBuilderPlugins) {
this.providers = providers;
this.graph = graph;
this.wordTypes = wordTypes;
this.graphBuilderPlugins = graphBuilderPlugins;
this.lastFixedNode = graph.start();
structures = new ArrayList<>();
/*
* Add a dummy element, so that the access of the last element never leads to an exception.
*/
structures.add(new Structure() {
});
}
@Override
public StructuredGraph getGraph() {
return graph;
}
@Override
public ConstantReflectionProvider getConstantReflection() {
return providers.getConstantReflection();
}
@Override
public ConstantFieldProvider getConstantFieldProvider() {
return providers.getConstantFieldProvider();
}
@Override
public MetaAccessProvider getMetaAccess() {
return providers.getMetaAccess();
}
@Override
public StampProvider getStampProvider() {
return providers.getStampProvider();
}
@Override
public boolean parsingIntrinsic() {
return true;
}
/**
* Ensures a floating node is added to or already present in the graph via {@link Graph#unique}.
*
* @return a node similar to {@code node} if one exists, otherwise {@code node}
*/
public <T extends FloatingNode & ValueNumberable> T unique(T node) {
return graph.unique(changeToWord(node));
}
public <T extends ValueNode> T add(T node) {
return graph.add(changeToWord(node));
}
public <T extends ValueNode> T changeToWord(T node) {
if (wordTypes != null && wordTypes.isWord(node)) {
node.setStamp(wordTypes.getWordStamp(StampTool.typeOrNull(node)));
}
return node;
}
@Override
public <T extends ValueNode> T append(T node) {
T result = graph.addOrUnique(changeToWord(node));
if (result instanceof FixedNode) {
updateLastFixed((FixedNode) result);
}
return result;
}
@Override
public <T extends ValueNode> T recursiveAppend(T node) {
T result = graph.addOrUniqueWithInputs(node);
if (result instanceof FixedNode) {
updateLastFixed((FixedNode) result);
}
return result;
}
private void updateLastFixed(FixedNode result) {
assert lastFixedNode != null;
assert result.predecessor() == null;
graph.addAfterFixed(lastFixedNode, result);
if (result instanceof FixedWithNextNode) {
lastFixedNode = (FixedWithNextNode) result;
} else {
lastFixedNode = null;
}
}
public InvokeNode createInvoke(Class<?> declaringClass, String name, ValueNode... args) {
return createInvoke(declaringClass, name, InvokeKind.Static, null, BytecodeFrame.UNKNOWN_BCI, args);
}
/**
* Creates and appends an {@link InvokeNode} for a call to a given method with a given set of
* arguments. The method is looked up via reflection based on the declaring class and name.
*
* @param declaringClass the class declaring the invoked method
* @param name the name of the invoked method
* @param args the arguments to the invocation
*/
public InvokeNode createInvoke(Class<?> declaringClass, String name, InvokeKind invokeKind, FrameStateBuilder frameStateBuilder, int bci, ValueNode... args) {
boolean isStatic = invokeKind == InvokeKind.Static;
ResolvedJavaMethod method = findMethod(declaringClass, name, isStatic);
return createInvoke(method, invokeKind, frameStateBuilder, bci, args);
}
public ResolvedJavaMethod findMethod(Class<?> declaringClass, String name, boolean isStatic) {
ResolvedJavaMethod method = null;
for (Method m : declaringClass.getDeclaredMethods()) {
if (Modifier.isStatic(m.getModifiers()) == isStatic && m.getName().equals(name)) {
assert method == null : "found more than one method in " + declaringClass + " named " + name;
method = providers.getMetaAccess().lookupJavaMethod(m);
}
}
assert method != null : "did not find method in " + declaringClass + " named " + name;
return method;
}
public ResolvedJavaMethod findMethod(Class<?> declaringClass, String name, Class<?>... parameterTypes) {
try {
Method m = declaringClass.getDeclaredMethod(name, parameterTypes);
return providers.getMetaAccess().lookupJavaMethod(m);
} catch (NoSuchMethodException | SecurityException e) {
throw new AssertionError(e);
}
}
/**
* Creates and appends an {@link InvokeNode} for a call to a given method with a given set of
* arguments.
*/
public InvokeNode createInvoke(ResolvedJavaMethod method, InvokeKind invokeKind, FrameStateBuilder frameStateBuilder, int bci, ValueNode... args) {
assert method.isStatic() == (invokeKind == InvokeKind.Static);
Signature signature = method.getSignature();
JavaType returnType = signature.getReturnType(null);
assert checkArgs(method, args);
StampPair returnStamp = graphBuilderPlugins.getOverridingStamp(this, returnType, false);
if (returnStamp == null) {
returnStamp = StampFactory.forDeclaredType(graph.getAssumptions(), returnType, false);
}
MethodCallTargetNode callTarget = graph.add(createMethodCallTarget(invokeKind, method, args, returnStamp, bci));
InvokeNode invoke = append(new InvokeNode(callTarget, bci));
if (frameStateBuilder != null) {
if (invoke.getStackKind() != JavaKind.Void) {
frameStateBuilder.push(returnType.getJavaKind(), invoke);
}
invoke.setStateAfter(frameStateBuilder.create(bci, invoke));
if (invoke.getStackKind() != JavaKind.Void) {
frameStateBuilder.pop(returnType.getJavaKind());
}
}
return invoke;
}
protected MethodCallTargetNode createMethodCallTarget(InvokeKind invokeKind, ResolvedJavaMethod targetMethod, ValueNode[] args, StampPair returnStamp, @SuppressWarnings("unused") int bci) {
return new MethodCallTargetNode(invokeKind, targetMethod, args, returnStamp, null);
}
/**
* Determines if a given set of arguments is compatible with the signature of a given method.
*
* @return true if {@code args} are compatible with the signature if {@code method}
* @throws AssertionError if {@code args} are not compatible with the signature if
* {@code method}
*/
public boolean checkArgs(ResolvedJavaMethod method, ValueNode... args) {
Signature signature = method.getSignature();
boolean isStatic = method.isStatic();
if (signature.getParameterCount(!isStatic) != args.length) {
throw new AssertionError(graph + ": wrong number of arguments to " + method);
}
int argIndex = 0;
if (!isStatic) {
ResolvedJavaType expectedType = method.getDeclaringClass();
JavaKind expected = wordTypes == null ? expectedType.getJavaKind() : wordTypes.asKind(expectedType);
JavaKind actual = args[argIndex++].stamp().getStackKind();
assert expected == actual : graph + ": wrong kind of value for receiver argument of call to " + method + " [" + actual + " != " + expected + "]";
}
for (int i = 0; i != signature.getParameterCount(false); i++) {
JavaType expectedType = signature.getParameterType(i, method.getDeclaringClass());
JavaKind expected = wordTypes == null ? expectedType.getJavaKind().getStackKind() : wordTypes.asKind(expectedType).getStackKind();
JavaKind actual = args[argIndex++].stamp().getStackKind();
if (expected != actual) {
throw new AssertionError(graph + ": wrong kind of value for argument " + i + " of call to " + method + " [" + actual + " != " + expected + "]");
}
}
return true;
}
/**
* Recursively {@linkplain #inline inlines} all invocations currently in the graph.
*/
public void inlineInvokes() {
while (!graph.getNodes().filter(InvokeNode.class).isEmpty()) {
for (InvokeNode invoke : graph.getNodes().filter(InvokeNode.class).snapshot()) {
inline(invoke);
}
}
// Clean up all code that is now dead after inlining.
new DeadCodeEliminationPhase().apply(graph);
}
/**
* Inlines a given invocation to a method. The graph of the inlined method is processed in the
* same manner as for snippets and method substitutions.
*/
public void inline(InvokeNode invoke) {
ResolvedJavaMethod method = ((MethodCallTargetNode) invoke.callTarget()).targetMethod();
MetaAccessProvider metaAccess = providers.getMetaAccess();
Plugins plugins = new Plugins(graphBuilderPlugins);
GraphBuilderConfiguration config = GraphBuilderConfiguration.getSnippetDefault(plugins);
StructuredGraph calleeGraph = new StructuredGraph(method, AllowAssumptions.NO, NO_PROFILING_INFO, INVALID_COMPILATION_ID);
IntrinsicContext initialReplacementContext = new IntrinsicContext(method, method, providers.getReplacements().getReplacementBytecodeProvider(), INLINE_AFTER_PARSING);
GraphBuilderPhase.Instance instance = new GraphBuilderPhase.Instance(metaAccess, providers.getStampProvider(), providers.getConstantReflection(), providers.getConstantFieldProvider(), config,
OptimisticOptimizations.NONE,
initialReplacementContext);
instance.apply(calleeGraph);
// Remove all frame states from inlinee
calleeGraph.clearAllStateAfter();
new DeadCodeEliminationPhase(Optionality.Required).apply(calleeGraph);
InliningUtil.inline(invoke, calleeGraph, false, null, method);
}
protected void pushStructure(Structure structure) {
structures.add(structure);
}
protected <T extends Structure> T getTopStructure(Class<T> expectedClass) {
return expectedClass.cast(structures.get(structures.size() - 1));
}
protected void popStructure() {
structures.remove(structures.size() - 1);
}
protected enum IfState {
CONDITION,
THEN_PART,
ELSE_PART,
FINISHED
}
static class IfStructure extends Structure {
protected IfState state;
protected FixedNode thenPart;
protected FixedNode elsePart;
}
/**
* Starts an if-block. This call can be followed by a call to {@link #thenPart} to start
* emitting the code executed when the condition hold; and a call to {@link #elsePart} to start
* emititng the code when the condition does not hold. It must be followed by a call to
* {@link #endIf} to close the if-block.
*
* @param condition The condition for the if-block
* @param trueProbability The estimated probability the the condition is true
*/
public void startIf(LogicNode condition, double trueProbability) {
AbstractBeginNode thenSuccessor = graph.add(new BeginNode());
AbstractBeginNode elseSuccessor = graph.add(new BeginNode());
append(new IfNode(condition, thenSuccessor, elseSuccessor, trueProbability));
lastFixedNode = null;
IfStructure s = new IfStructure();
s.state = IfState.CONDITION;
s.thenPart = thenSuccessor;
s.elsePart = elseSuccessor;
pushStructure(s);
}
private IfStructure saveLastNode() {
IfStructure s = getTopStructure(IfStructure.class);
switch (s.state) {
case CONDITION:
assert lastFixedNode == null;
break;
case THEN_PART:
s.thenPart = lastFixedNode;
break;
case ELSE_PART:
s.elsePart = lastFixedNode;
break;
case FINISHED:
assert false;
break;
}
lastFixedNode = null;
return s;
}
public void thenPart() {
IfStructure s = saveLastNode();
lastFixedNode = (FixedWithNextNode) s.thenPart;
s.state = IfState.THEN_PART;
}
public void elsePart() {
IfStructure s = saveLastNode();
lastFixedNode = (FixedWithNextNode) s.elsePart;
s.state = IfState.ELSE_PART;
}
public void endIf() {
IfStructure s = saveLastNode();
FixedWithNextNode thenPart = s.thenPart instanceof FixedWithNextNode ? (FixedWithNextNode) s.thenPart : null;
FixedWithNextNode elsePart = s.elsePart instanceof FixedWithNextNode ? (FixedWithNextNode) s.elsePart : null;
if (thenPart != null && elsePart != null) {
/* Both parts are alive, we need a real merge. */
EndNode thenEnd = graph.add(new EndNode());
graph.addAfterFixed(thenPart, thenEnd);
EndNode elseEnd = graph.add(new EndNode());
graph.addAfterFixed(elsePart, elseEnd);
AbstractMergeNode merge = graph.add(new MergeNode());
merge.addForwardEnd(thenEnd);
merge.addForwardEnd(elseEnd);
lastFixedNode = merge;
} else if (thenPart != null) {
/* elsePart ended with a control sink, so we can continue with thenPart. */
lastFixedNode = thenPart;
} else if (elsePart != null) {
/* thenPart ended with a control sink, so we can continue with elsePart. */
lastFixedNode = elsePart;
} else {
/* Both parts ended with a control sink, so no nodes can be added after the if. */
assert lastFixedNode == null;
}
s.state = IfState.FINISHED;
popStructure();
}
}