/*
* Copyright (c) 2013, 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.truffle;
import static org.graalvm.compiler.truffle.TruffleCompilerOptions.TraceTruffleAssumptions;
import static org.graalvm.compiler.truffle.TruffleCompilerOptions.TruffleBackgroundCompilation;
import static org.graalvm.compiler.truffle.TruffleCompilerOptions.TruffleCallTargetProfiling;
import static org.graalvm.compiler.truffle.TruffleCompilerOptions.TruffleCompilationExceptionsAreFatal;
import static org.graalvm.compiler.truffle.TruffleCompilerOptions.TruffleCompilationExceptionsArePrinted;
import static org.graalvm.compiler.truffle.TruffleCompilerOptions.TruffleCompilationExceptionsAreThrown;
import static org.graalvm.compiler.truffle.TruffleCompilerOptions.TrufflePerformanceWarningsAreFatal;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.function.UnaryOperator;
import org.graalvm.compiler.core.common.SuppressFBWarnings;
import org.graalvm.compiler.debug.GraalError;
import org.graalvm.compiler.truffle.GraalTruffleRuntime.LazyFrameBoxingQuery;
import org.graalvm.compiler.truffle.debug.AbstractDebugCompilationListener;
import org.graalvm.compiler.truffle.substitutions.TruffleGraphBuilderPlugins;
import com.oracle.truffle.api.Assumption;
import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.CompilerOptions;
import com.oracle.truffle.api.OptimizationFailedException;
import com.oracle.truffle.api.ReplaceObserver;
import com.oracle.truffle.api.RootCallTarget;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.frame.FrameDescriptor;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.impl.DefaultCompilerOptions;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.NodeUtil;
import com.oracle.truffle.api.nodes.NodeVisitor;
import com.oracle.truffle.api.nodes.RootNode;
import jdk.vm.ci.code.BailoutException;
import jdk.vm.ci.code.InstalledCode;
import jdk.vm.ci.meta.SpeculationLog;
/**
* Call target that is optimized by Graal upon surpassing a specific invocation threshold.
*/
@SuppressWarnings("deprecation")
public class OptimizedCallTarget extends InstalledCode implements RootCallTarget, ReplaceObserver, com.oracle.truffle.api.LoopCountReceiver {
private static final String NODE_REWRITING_ASSUMPTION_NAME = "nodeRewritingAssumption";
/** The AST to be executed when this call target is called. */
private final RootNode rootNode;
/** Information about when and how the call target should get compiled. */
@CompilationFinal protected volatile OptimizedCompilationProfile compilationProfile;
/** Source target if this target was duplicated. */
private final OptimizedCallTarget sourceCallTarget;
/** Only set for a source CallTarget with a clonable RootNode. */
private volatile RootNode uninitializedRootNode;
private volatile int cachedNonTrivialNodeCount = -1;
private volatile SpeculationLog speculationLog;
private volatile int callSitesKnown;
private volatile CancellableCompileTask compilationTask;
/**
* When this call target is inlined, the inlining {@link InstalledCode} registers this
* assumption. It gets invalidated when a node rewriting is performed. This ensures that all
* compiled methods that have this call target inlined are properly invalidated.
*/
private volatile Assumption nodeRewritingAssumption;
private static final AtomicReferenceFieldUpdater<OptimizedCallTarget, Assumption> NODE_REWRITING_ASSUMPTION_UPDATER = AtomicReferenceFieldUpdater.newUpdater(OptimizedCallTarget.class,
Assumption.class, "nodeRewritingAssumption");
public OptimizedCallTarget(OptimizedCallTarget sourceCallTarget, RootNode rootNode) {
super(rootNode.toString());
assert sourceCallTarget == null || sourceCallTarget.sourceCallTarget == null : "Cannot create a clone of a cloned CallTarget";
this.sourceCallTarget = sourceCallTarget;
this.speculationLog = sourceCallTarget != null ? sourceCallTarget.getSpeculationLog() : null;
this.rootNode = rootNode;
this.rootNode.adoptChildren();
}
public Assumption getNodeRewritingAssumption() {
Assumption assumption = nodeRewritingAssumption;
if (assumption == null) {
assumption = initializeNodeRewritingAssumption();
}
return assumption;
}
/**
* @return an existing or the newly initialized node rewriting assumption.
*/
private Assumption initializeNodeRewritingAssumption() {
Assumption newAssumption = runtime().createAssumption(
!TruffleCompilerOptions.getValue(TraceTruffleAssumptions) ? NODE_REWRITING_ASSUMPTION_NAME : NODE_REWRITING_ASSUMPTION_NAME + " of " + rootNode);
if (NODE_REWRITING_ASSUMPTION_UPDATER.compareAndSet(this, null, newAssumption)) {
return newAssumption;
} else {
// if CAS failed, assumption is already initialized; cannot be null after that.
return Objects.requireNonNull(nodeRewritingAssumption);
}
}
/**
* Invalidate node rewriting assumption iff it has been initialized.
*/
private void invalidateNodeRewritingAssumption() {
Assumption oldAssumption = NODE_REWRITING_ASSUMPTION_UPDATER.getAndUpdate(this, new UnaryOperator<Assumption>() {
@Override
public Assumption apply(Assumption prev) {
return prev == null ? null : runtime().createAssumption(prev.getName());
}
});
if (oldAssumption != null) {
oldAssumption.invalidate();
}
}
@Override
public final RootNode getRootNode() {
return rootNode;
}
public final OptimizedCompilationProfile getCompilationProfile() {
OptimizedCompilationProfile profile = compilationProfile;
if (profile != null) {
return profile;
} else {
CompilerDirectives.transferToInterpreterAndInvalidate();
initialize();
return compilationProfile;
}
}
protected Class<?>[] getProfiledArgumentTypes() {
return getCompilationProfile().getProfiledArgumentTypes();
}
protected Class<?> getProfiledReturnType() {
return getCompilationProfile().getProfiledReturnType();
}
@Override
public final Object call(Object... args) {
getCompilationProfile().profileIndirectCall();
return doInvoke(args);
}
public final Object callDirect(Object... args) {
try {
getCompilationProfile().profileDirectCall(args);
Object result = doInvoke(args);
if (CompilerDirectives.inCompiledCode()) {
result = compilationProfile.injectReturnValueProfile(result);
}
return result;
} catch (Throwable t) {
throw rethrow(compilationProfile.profileExceptionType(t));
}
}
public final Object callInlined(Object... arguments) {
getCompilationProfile().profileInlinedCall();
return callProxy(createFrame(getRootNode().getFrameDescriptor(), arguments));
}
protected Object doInvoke(Object[] args) {
return callBoundary(args);
}
@TruffleCallBoundary
protected final Object callBoundary(Object[] args) {
if (CompilerDirectives.inInterpreter()) {
// We are called and we are still in Truffle interpreter mode.
compilationProfile.interpreterCall(this);
if (isValid()) {
// Stubs were deoptimized => reinstall.
runtime().reinstallStubs();
}
} else {
// We come here from compiled code
}
return callRoot(args);
}
/* TODO needs to remain public? */
public final Object callRoot(Object[] originalArguments) {
Object[] args = originalArguments;
OptimizedCompilationProfile profile = this.compilationProfile;
if (CompilerDirectives.inCompiledCode() && profile != null) {
args = profile.injectArgumentProfile(originalArguments);
}
Object result = callProxy(createFrame(getRootNode().getFrameDescriptor(), args));
if (profile != null) {
profile.profileReturnValue(result);
}
return result;
}
protected final Object callProxy(VirtualFrame frame) {
final boolean inCompiled = CompilerDirectives.inCompiledCode();
try {
return getRootNode().execute(frame);
} finally {
// this assertion is needed to keep the values from being cleared as non-live locals
assert frame != null && this != null;
if (CompilerDirectives.inInterpreter() && inCompiled) {
notifyDeoptimized(frame);
}
}
}
private void notifyDeoptimized(VirtualFrame frame) {
runtime().getCompilationNotify().notifyCompilationDeoptimized(this, frame);
}
private static GraalTruffleRuntime runtime() {
return (GraalTruffleRuntime) Truffle.getRuntime();
}
private synchronized void initialize() {
if (compilationProfile == null) {
GraalTVMCI tvmci = runtime().getTvmci();
if (sourceCallTarget == null && rootNode.isCloningAllowed() && !tvmci.isCloneUninitializedSupported(rootNode)) {
// We are the source CallTarget, so make a copy.
this.uninitializedRootNode = NodeUtil.cloneNode(rootNode);
}
tvmci.onFirstExecution(this);
this.compilationProfile = createCompilationProfile();
}
}
private static OptimizedCompilationProfile createCompilationProfile() {
if (TruffleCompilerOptions.getValue(TruffleCallTargetProfiling)) {
return TraceCompilationProfile.create();
} else {
return OptimizedCompilationProfile.create();
}
}
public final void compile() {
if (!isCompiling()) {
if (compilationProfile == null) {
initialize();
}
if (!runtime().acceptForCompilation(getRootNode())) {
return;
}
CancellableCompileTask task = null;
// Do not try to compile this target concurrently,
// but do not block other threads if compilation is not asynchronous.
synchronized (this) {
if (!isCompiling()) {
compilationTask = task = runtime().submitForCompilation(this);
}
}
if (task != null) {
Future<?> submitted = task.getFuture();
if (submitted != null) {
boolean allowBackgroundCompilation = !(TruffleCompilerOptions.getValue(TrufflePerformanceWarningsAreFatal) &&
!TruffleCompilerOptions.getValue(TruffleCompilationExceptionsAreThrown));
boolean mayBeAsynchronous = TruffleCompilerOptions.getValue(TruffleBackgroundCompilation) && allowBackgroundCompilation;
runtime().finishCompilation(this, submitted, mayBeAsynchronous);
}
}
}
}
public final boolean isCompiling() {
CancellableCompileTask task = getCompilationTask();
if (task != null) {
if (task.getFuture() != null) {
return true;
}
}
return false;
}
@Override
public void invalidate() {
invalidate(null, null);
}
protected void invalidate(Object source, CharSequence reason) {
cachedNonTrivialNodeCount = -1;
if (isValid()) {
runtime().invalidateInstalledCode(this, source, reason);
}
runtime().cancelInstalledTask(this, source, reason);
}
OptimizedCallTarget cloneUninitialized() {
assert sourceCallTarget == null;
if (compilationProfile == null) {
initialize();
}
RootNode clonedRoot;
GraalTVMCI tvmci = runtime().getTvmci();
if (tvmci.isCloneUninitializedSupported(rootNode)) {
assert uninitializedRootNode == null;
clonedRoot = tvmci.cloneUninitialized(rootNode);
} else {
clonedRoot = NodeUtil.cloneNode(uninitializedRootNode);
}
return (OptimizedCallTarget) runtime().createClonedCallTarget(this, clonedRoot);
}
protected synchronized SpeculationLog getSpeculationLog() {
if (speculationLog == null) {
speculationLog = ((GraalTruffleRuntime) Truffle.getRuntime()).createSpeculationLog();
}
return speculationLog;
}
synchronized void setSpeculationLog(SpeculationLog speculationLog) {
this.speculationLog = speculationLog;
}
@SuppressWarnings({"unchecked"})
private static <E extends Throwable> RuntimeException rethrow(Throwable ex) throws E {
throw (E) ex;
}
boolean cancelInstalledTask(Node source, CharSequence reason) {
return runtime().cancelInstalledTask(this, source, reason);
}
void notifyCompilationFailed(Throwable t) {
if (t instanceof BailoutException && !((BailoutException) t).isPermanent()) {
/*
* Non permanent bailouts are expected cases. A non permanent bailout would be for
* example class redefinition during code installation. As opposed to permanent
* bailouts, non permanent bailouts will trigger recompilation and are not considered a
* failure state.
*/
} else {
compilationProfile.reportCompilationFailure();
if (TruffleCompilerOptions.getValue(TruffleCompilationExceptionsAreThrown)) {
throw new OptimizationFailedException(t, this);
}
/*
* Automatically enable TruffleCompilationExceptionsAreFatal when asserts are enabled
* but respect TruffleCompilationExceptionsAreFatal if it's been explicitly set.
*/
boolean truffleCompilationExceptionsAreFatal = TruffleCompilerOptions.getValue(TruffleCompilationExceptionsAreFatal);
assert TruffleCompilationExceptionsAreFatal.hasBeenSet(TruffleCompilerOptions.getOptions()) || (truffleCompilationExceptionsAreFatal = true) == true;
truffleCompilationExceptionsAreFatal = truffleCompilationExceptionsAreFatal || TruffleCompilerOptions.getValue(TrufflePerformanceWarningsAreFatal);
if (TruffleCompilerOptions.getValue(TruffleCompilationExceptionsArePrinted) || truffleCompilationExceptionsAreFatal) {
printException(t);
if (truffleCompilationExceptionsAreFatal) {
System.exit(-1);
}
}
}
}
private static void printException(Throwable e) {
StringWriter string = new StringWriter();
e.printStackTrace(new PrintWriter(string));
log(string.toString());
}
public static final void log(String message) {
runtime().log(message);
}
final int getKnownCallSiteCount() {
return callSitesKnown;
}
@SuppressFBWarnings(value = "VO_VOLATILE_INCREMENT", justification = "All increments and decrements are synchronized.")
final synchronized void incrementKnownCallSites() {
callSitesKnown++;
}
@SuppressFBWarnings(value = "VO_VOLATILE_INCREMENT", justification = "All increments and decrements are synchronized.")
final synchronized void decrementKnownCallSites() {
callSitesKnown--;
}
public final OptimizedCallTarget getSourceCallTarget() {
return sourceCallTarget;
}
@Override
public String toString() {
CompilerAsserts.neverPartOfCompilation();
String superString = rootNode.toString();
if (isValid()) {
superString += " <opt>";
}
if (sourceCallTarget != null) {
superString += " <split-" + Integer.toHexString(hashCode()) + ">";
}
return superString;
}
/**
* Intrinsified in {@link TruffleGraphBuilderPlugins}.
*
* @param length avoid warning
*/
static Object castArrayFixedLength(Object[] args, int length) {
return args;
}
/**
* Intrinsified in {@link TruffleGraphBuilderPlugins}.
*
* @param type avoid warning
* @param condition avoid warning
* @param nonNull avoid warning
*/
@SuppressWarnings({"unchecked"})
static <T> T unsafeCast(Object value, Class<T> type, boolean condition, boolean nonNull) {
return (T) value;
}
/** Intrinsified in {@link TruffleGraphBuilderPlugins}. */
public static VirtualFrame createFrame(FrameDescriptor descriptor, Object[] args) {
if (LazyFrameBoxingQuery.useFrameWithoutBoxing) {
return new FrameWithoutBoxing(descriptor, args);
} else {
return new FrameWithBoxing(descriptor, args);
}
}
final void onLoopCount(int count) {
getCompilationProfile().reportLoopCount(count);
}
/*
* For compatibility of Graal runtime with older Truffle runtime. Remove after 0.12.
*/
@Override
public void reportLoopCount(int count) {
getCompilationProfile().reportLoopCount(count);
}
@Override
public boolean nodeReplaced(Node oldNode, Node newNode, CharSequence reason) {
CompilerAsserts.neverPartOfCompilation();
if (isValid()) {
invalidate(newNode, reason);
}
/* Notify compiled method that have inlined this call target that the tree changed. */
invalidateNodeRewritingAssumption();
OptimizedCompilationProfile profile = this.compilationProfile;
if (profile != null) {
profile.reportNodeReplaced();
if (cancelInstalledTask(newNode, reason)) {
profile.reportInvalidated();
}
}
return false;
}
public void accept(NodeVisitor visitor, TruffleInlining inlingDecision) {
if (inlingDecision != null) {
inlingDecision.accept(this, visitor);
} else {
getRootNode().accept(visitor);
}
}
public Iterable<Node> nodeIterable(TruffleInlining inliningDecision) {
Iterator<Node> iterator = nodeIterator(inliningDecision);
return () -> iterator;
}
public Iterator<Node> nodeIterator(TruffleInlining inliningDecision) {
Iterator<Node> iterator;
if (inliningDecision != null) {
iterator = inliningDecision.makeNodeIterator(this);
} else {
iterator = NodeUtil.makeRecursiveIterator(this.getRootNode());
}
return iterator;
}
public final int getNonTrivialNodeCount() {
if (cachedNonTrivialNodeCount == -1) {
cachedNonTrivialNodeCount = calculateNonTrivialNodes(getRootNode());
}
return cachedNonTrivialNodeCount;
}
public static int calculateNonTrivialNodes(Node node) {
NonTrivialNodeCountVisitor visitor = new NonTrivialNodeCountVisitor();
node.accept(visitor);
return visitor.nodeCount;
}
public Map<String, Object> getDebugProperties(TruffleInlining inlining) {
Map<String, Object> properties = new LinkedHashMap<>();
AbstractDebugCompilationListener.addASTSizeProperty(this, inlining, properties);
properties.putAll(getCompilationProfile().getDebugProperties());
return properties;
}
static Method getCallDirectMethod() {
try {
return OptimizedCallTarget.class.getDeclaredMethod("callDirect", Object[].class);
} catch (NoSuchMethodException | SecurityException e) {
throw new GraalError(e);
}
}
static Method getCallInlinedMethod() {
try {
return OptimizedCallTarget.class.getDeclaredMethod("callInlined", Object[].class);
} catch (NoSuchMethodException | SecurityException e) {
throw new GraalError(e);
}
}
CompilerOptions getCompilerOptions() {
final CompilerOptions options = rootNode.getCompilerOptions();
if (options != null) {
return options;
}
return DefaultCompilerOptions.INSTANCE;
}
private static final class NonTrivialNodeCountVisitor implements NodeVisitor {
public int nodeCount;
@Override
public boolean visit(Node node) {
if (!node.getCost().isTrivial()) {
nodeCount++;
}
return true;
}
}
@Override
public final boolean equals(Object obj) {
return obj == this;
}
@Override
public final int hashCode() {
return System.identityHashCode(this);
}
CancellableCompileTask getCompilationTask() {
return compilationTask;
}
void resetCompilationTask() {
this.compilationTask = null;
}
}