package org.graalvm.compiler.truffle.runtime;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.function.Supplier;
import org.graalvm.compiler.truffle.common.CompilableTruffleAST;
import org.graalvm.compiler.truffle.common.TruffleCallNode;
import org.graalvm.compiler.truffle.options.PolyglotCompilerOptions;
import org.graalvm.compiler.truffle.options.PolyglotCompilerOptions.ExceptionAction;
import org.graalvm.compiler.truffle.runtime.OptimizedOSRLoopNode.OSRRootNode;
import org.graalvm.options.OptionKey;
import org.graalvm.options.OptionValues;
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.CompilerDirectives.TruffleBoundary;
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.BlockNode;
import com.oracle.truffle.api.nodes.ControlFlowException;
import com.oracle.truffle.api.nodes.EncapsulatingNodeReference;
import com.oracle.truffle.api.nodes.ExecutionSignature;
import com.oracle.truffle.api.nodes.ExplodeLoop;
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.InstalledCode;
import jdk.vm.ci.meta.JavaConstant;
import jdk.vm.ci.meta.SpeculationLog;
@SuppressWarnings({"deprecation", "hiding"})
public abstract class OptimizedCallTarget implements CompilableTruffleAST, RootCallTarget, ReplaceObserver {
private static final String NODE_REWRITING_ASSUMPTION_NAME = "nodeRewritingAssumption";
private static final String VALID_ROOT_ASSUMPTION_NAME = "validRootAssumption";
static final String EXECUTE_ROOT_NODE_METHOD_NAME = "executeRootNode";
private static final AtomicReferenceFieldUpdater<OptimizedCallTarget, SpeculationLog> SPECULATION_LOG_UPDATER = AtomicReferenceFieldUpdater.newUpdater(OptimizedCallTarget.class,
SpeculationLog.class, "speculationLog");
private static final AtomicReferenceFieldUpdater<OptimizedCallTarget, Assumption> NODE_REWRITING_ASSUMPTION_UPDATER = AtomicReferenceFieldUpdater.newUpdater(OptimizedCallTarget.class,
Assumption.class, "nodeRewritingAssumption");
private static final AtomicReferenceFieldUpdater<OptimizedCallTarget, Assumption> VALID_ROOT_ASSUMPTION_UPDATER =
AtomicReferenceFieldUpdater.newUpdater(OptimizedCallTarget.class, Assumption.class, "validRootAssumption");
private static final AtomicReferenceFieldUpdater<OptimizedCallTarget, ArgumentsProfile> ARGUMENTS_PROFILE_UPDATER = AtomicReferenceFieldUpdater.newUpdater(
OptimizedCallTarget.class, ArgumentsProfile.class, "argumentsProfile");
private static final AtomicReferenceFieldUpdater<OptimizedCallTarget, ReturnProfile> RETURN_PROFILE_UPDATER = AtomicReferenceFieldUpdater.newUpdater(
OptimizedCallTarget.class, ReturnProfile.class, "returnProfile");
private static final WeakReference<OptimizedDirectCallNode> NO_CALL = new WeakReference<>(null);
private static final WeakReference<OptimizedDirectCallNode> MULTIPLE_CALLS = null;
private static final String SPLIT_LOG_FORMAT = "[poly-event] %-70s %s";
private static final int MAX_PROFILED_ARGUMENTS = 256;
private final RootNode rootNode;
@CompilationFinal protected volatile boolean initialized;
private int callCount;
private int callAndLoopCount;
@CompilationFinal private volatile ArgumentsProfile argumentsProfile;
@CompilationFinal private volatile ReturnProfile returnProfile;
@CompilationFinal private Class<? extends Throwable> profiledExceptionType;
private volatile boolean dequeueInlined = false;
public static final class ArgumentsProfile {
private static final String ARGUMENT_TYPES_ASSUMPTION_NAME = "Profiled Argument Types";
private static final Class<?>[] EMPTY_ARGUMENT_TYPES = new Class<?>[0];
private static final ArgumentsProfile INVALID = new ArgumentsProfile();
final OptimizedAssumption assumption;
@CompilationFinal(dimensions = 1) final Class<?>[] types;
private ArgumentsProfile() {
this.assumption = createInvalidAssumption(ARGUMENT_TYPES_ASSUMPTION_NAME);
this.types = null;
}
private ArgumentsProfile(Class<?>[] types, String assumptionName) {
assert types != null;
this.assumption = createValidAssumption(assumptionName);
this.types = types;
}
public OptimizedAssumption getAssumption() {
return assumption;
}
public Class<?>[] getTypes() {
return types;
}
}
public static final class ReturnProfile {
private static final String RETURN_TYPE_ASSUMPTION_NAME = "Profiled Return Type";
private static final ReturnProfile INVALID = new ReturnProfile();
final OptimizedAssumption assumption;
final Class<?> type;
private ReturnProfile() {
this.assumption = createInvalidAssumption(RETURN_TYPE_ASSUMPTION_NAME);
this.type = null;
}
private ReturnProfile(Class<?> type) {
assert type != null;
this.assumption = createValidAssumption(RETURN_TYPE_ASSUMPTION_NAME);
this.type = type;
}
public OptimizedAssumption getAssumption() {
return assumption;
}
public Class<?> getType() {
return type;
}
}
private volatile boolean compilationFailed;
@CompilationFinal private boolean callProfiled;
private volatile long initializedTimestamp;
private volatile CompilationTask compilationTask;
private volatile boolean needsSplit;
public final EngineData engine;
private volatile RootNode uninitializedRootNode;
private volatile SpeculationLog speculationLog;
private final OptimizedCallTarget sourceCallTarget;
private volatile Assumption nodeRewritingAssumption;
private volatile Assumption validRootAssumption;
private volatile int cachedNonTrivialNodeCount = -1;
private volatile int callSitesKnown;
private volatile String nameCache;
private final int uninitializedNodeCount;
private volatile WeakReference<OptimizedDirectCallNode> singleCallNode = NO_CALL;
volatile List<OptimizedCallTarget> blockCompilations;
protected OptimizedCallTarget(OptimizedCallTarget sourceCallTarget, RootNode rootNode) {
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.engine = GraalTVMCI.getEngineData(rootNode);
this.resetCompilationProfile();
this.uninitializedNodeCount = !(rootNode instanceof OSRRootNode) ? GraalRuntimeAccessor.NODES.adoptChildrenAndCount(rootNode) : -1;
GraalRuntimeAccessor.NODES.setCallTarget(rootNode, this);
}
final Assumption getNodeRewritingAssumption() {
Assumption assumption = nodeRewritingAssumption;
if (assumption == null) {
assumption = initializeNodeRewritingAssumption();
}
return assumption;
}
@Override
public JavaConstant getNodeRewritingAssumptionConstant() {
return runtime().forObject(getNodeRewritingAssumption());
}
final Assumption getValidRootAssumption() {
Assumption assumption = validRootAssumption;
if (assumption == null) {
assumption = initializeValidRootAssumption();
}
return assumption;
}
@Override
public JavaConstant getValidRootAssumptionConstant() {
return runtime().forObject(getValidRootAssumption());
}
@Override
public boolean isTrivial() {
return GraalRuntimeAccessor.NODES.isTrivial(rootNode);
}
@Override
public void dequeueInlined() {
if (!dequeueInlined) {
dequeueInlined = true;
cancelCompilation("Target inlined into only caller");
}
}
private Assumption initializeNodeRewritingAssumption() {
return initializeAssumption(NODE_REWRITING_ASSUMPTION_UPDATER, NODE_REWRITING_ASSUMPTION_NAME);
}
private Assumption initializeAssumption(AtomicReferenceFieldUpdater<OptimizedCallTarget, Assumption> updater, String name) {
Assumption newAssumption = runtime().createAssumption(!getOptionValue(PolyglotCompilerOptions.TraceAssumptions) ? name : name + " of " + rootNode);
if (updater.compareAndSet(this, null, newAssumption)) {
return newAssumption;
} else {
return Objects.requireNonNull(updater.get(this));
}
}
private void invalidateNodeRewritingAssumption() {
invalidateAssumption(NODE_REWRITING_ASSUMPTION_UPDATER, "");
}
private boolean invalidateAssumption(AtomicReferenceFieldUpdater<OptimizedCallTarget, Assumption> updater, CharSequence reason) {
Assumption oldAssumption = updater.getAndUpdate(this, prev -> (prev == null) ? null : runtime().createAssumption(prev.getName()));
if (oldAssumption == null) {
return false;
}
oldAssumption.invalidate((reason != null) ? reason.toString() : "");
return true;
}
private Assumption initializeValidRootAssumption() {
return initializeAssumption(VALID_ROOT_ASSUMPTION_UPDATER, VALID_ROOT_ASSUMPTION_NAME);
}
private boolean invalidateValidRootAssumption(CharSequence reason) {
return invalidateAssumption(VALID_ROOT_ASSUMPTION_UPDATER, reason);
}
@Override
public final RootNode getRootNode() {
return rootNode;
}
public final void resetCompilationProfile() {
this.callCount = 0;
this.callAndLoopCount = 0;
}
@Override
@TruffleBoundary
public final Object call(Object... args) {
EncapsulatingNodeReference encapsulating = EncapsulatingNodeReference.getCurrent();
Node prev = encapsulating.set(null);
try {
profileArguments(args);
return callIndirect(prev, args);
} catch (Throwable t) {
GraalRuntimeAccessor.LANGUAGE.onThrowable(prev, null, t, null);
throw rethrow(t);
} finally {
encapsulating.set(prev);
}
}
public Object callIndirect(Node location, Object... args) {
try {
return doInvoke(args);
} finally {
assert keepAlive(location);
}
}
public final Object callDirect(Node location, Object... args) {
try {
try {
Object result;
profileArguments(args);
result = doInvoke(args);
if (CompilerDirectives.inCompiledCode()) {
result = injectReturnValueProfile(result);
}
return result;
} catch (Throwable t) {
throw rethrow(profileExceptionType(t));
}
} finally {
assert keepAlive(location);
}
}
private static boolean keepAlive(@SuppressWarnings("unused") Object o) {
return true;
}
public final Object callOSR(Object... args) {
return doInvoke(args);
}
public final Object callInlined(Node location, Object... arguments) {
try {
ensureInitialized();
return executeRootNode(createFrame(getRootNode().getFrameDescriptor(), arguments));
} finally {
assert keepAlive(location);
}
}
protected Object doInvoke(Object[] args) {
return callBoundary(args);
}
@TruffleCallBoundary
protected final Object callBoundary(Object[] args) {
if (interpreterCall()) {
return doInvoke(args);
}
return profiledPERoot(args);
}
private boolean interpreterCall() {
if (isValid()) {
runtime().bypassedInstalledCode(this);
}
ensureInitialized();
int intCallCount = this.callCount;
this.callCount = intCallCount == Integer.MAX_VALUE ? intCallCount : ++intCallCount;
int intLoopCallCount = this.callAndLoopCount;
this.callAndLoopCount = intLoopCallCount == Integer.MAX_VALUE ? intLoopCallCount : ++intLoopCallCount;
if (shouldCompileImpl(intCallCount, intLoopCallCount)) {
return compile(!engine.multiTier);
}
return false;
}
private boolean shouldCompileImpl(int intCallCount, int intLoopCallCount) {
return intCallCount >= engine.callThresholdInInterpreter
&& intLoopCallCount >= engine.callAndLoopThresholdInInterpreter
&& !compilationFailed
&& !isSubmittedForCompilation()
&& !(getRootNode() instanceof OSRRootNode);
}
public final boolean shouldCompile() {
return !isValid() && shouldCompileImpl(this.callCount, this.callAndLoopCount);
}
public final boolean wasExecuted() {
return this.callCount > 0 || this.callAndLoopCount > 0;
}
protected final Object profiledPERoot(Object[] originalArguments) {
Object[] args = originalArguments;
if (GraalCompilerDirectives.inFirstTier()) {
firstTierCall();
}
if (CompilerDirectives.inCompiledCode()) {
args = injectArgumentsProfile(originalArguments);
}
Object result = executeRootNode(createFrame(getRootNode().getFrameDescriptor(), args));
profileReturnValue(result);
return result;
}
public final boolean firstTierCall() {
int firstTierCallCount = this.callCount;
this.callCount = firstTierCallCount == Integer.MAX_VALUE ? firstTierCallCount : ++firstTierCallCount;
int firstTierLoopCallCount = this.callAndLoopCount;
this.callAndLoopCount = firstTierLoopCallCount == Integer.MAX_VALUE ? firstTierLoopCallCount : ++firstTierLoopCallCount;
if (firstTierCallCount >= engine.callThresholdInFirstTier
&& firstTierLoopCallCount >= engine.callAndLoopThresholdInFirstTier
&& !compilationFailed
&& !isSubmittedForCompilation()) {
return lastTierCompile();
}
return false;
}
@TruffleBoundary
private boolean lastTierCompile() {
return compile(true);
}
private Object executeRootNode(VirtualFrame frame) {
final boolean inCompiled = CompilerDirectives.inCompilationRoot();
try {
return rootNode.execute(frame);
} catch (ControlFlowException t) {
throw rethrow(profileExceptionType(t));
} catch (Throwable t) {
Throwable profiledT = profileExceptionType(t);
GraalRuntimeAccessor.LANGUAGE.onThrowable(null, this, profiledT, frame);
throw rethrow(profiledT);
} finally {
assert frame != null && this != null;
if (CompilerDirectives.inInterpreter() && inCompiled) {
notifyDeoptimized(frame);
}
}
}
private void notifyDeoptimized(VirtualFrame frame) {
runtime().getListener().onCompilationDeoptimized(this, frame);
}
protected static GraalTruffleRuntime runtime() {
return (GraalTruffleRuntime) Truffle.getRuntime();
}
public final void ensureInitialized() {
if (!initialized) {
CompilerDirectives.transferToInterpreterAndInvalidate();
initialize(true);
}
}
public final boolean isInitialized() {
return initialized;
}
private synchronized void initialize(boolean validate) {
if (!initialized) {
if (sourceCallTarget == null && rootNode.isCloningAllowed() && !GraalRuntimeAccessor.NODES.isCloneUninitializedSupported(rootNode)) {
this.uninitializedRootNode = NodeUtil.cloneNode(rootNode);
}
GraalRuntimeAccessor.INSTRUMENT.onFirstExecution(getRootNode(), validate);
if (engine.callTargetStatistics) {
this.initializedTimestamp = System.nanoTime();
} else {
this.initializedTimestamp = 0L;
}
initialized = true;
}
}
public final OptionValues getOptionValues() {
return engine.engineOptions;
}
public final <T> T getOptionValue(OptionKey<T> key) {
return getOptionValues().get(key);
}
final boolean acceptForCompilation() {
return engine.acceptForCompilation(getRootNode());
}
final boolean isCompilationFailed() {
return compilationFailed;
}
public final boolean compile(boolean lastTierCompilation) {
if (!needsCompile(lastTierCompilation)) {
return true;
}
if (!isSubmittedForCompilation()) {
if (!engine.acceptForCompilation(getRootNode())) {
compilationFailed = true;
return false;
}
CompilationTask task = null;
synchronized (this) {
if (!needsCompile(lastTierCompilation)) {
return true;
}
ensureInitialized();
if (!isSubmittedForCompilation()) {
try {
assert compilationTask == null;
this.compilationTask = task = runtime().submitForCompilation(this, lastTierCompilation);
} catch (RejectedExecutionException e) {
return false;
}
}
}
if (task != null) {
runtime().getListener().onCompilationQueued(this);
return maybeWaitForTask(task);
}
}
return false;
}
public final boolean maybeWaitForTask(CompilationTask task) {
boolean mayBeAsynchronous = engine.backgroundCompilation;
runtime().finishCompilation(this, task, mayBeAsynchronous);
return !mayBeAsynchronous && isValid();
}
public final boolean needsCompile(boolean isLastTierCompilation) {
return !isValid() || (engine.multiTier && isLastTierCompilation && !isValidLastTier());
}
public final boolean isSubmittedForCompilation() {
return compilationTask != null;
}
public final void waitForCompilation() {
CompilationTask task = compilationTask;
if (task != null) {
runtime().finishCompilation(this, task, false);
}
}
boolean isCompiling() {
return getCompilationTask() != null;
}
public abstract long getCodeAddress();
public abstract boolean isValid();
public abstract boolean isValidLastTier();
public final boolean invalidate(CharSequence reason) {
cachedNonTrivialNodeCount = -1;
boolean invalidated = invalidateValidRootAssumption(reason);
return cancelCompilation(reason) || invalidated;
}
final OptimizedCallTarget cloneUninitialized() {
assert sourceCallTarget == null;
ensureInitialized();
RootNode clonedRoot;
if (GraalRuntimeAccessor.NODES.isCloneUninitializedSupported(rootNode)) {
assert uninitializedRootNode == null;
clonedRoot = GraalRuntimeAccessor.NODES.cloneUninitialized(rootNode);
} else {
clonedRoot = NodeUtil.cloneNode(uninitializedRootNode);
}
return runtime().createClonedCallTarget(clonedRoot, this);
}
public SpeculationLog getSpeculationLog() {
if (speculationLog == null) {
SPECULATION_LOG_UPDATER.compareAndSet(this, null, ((GraalTruffleRuntime) Truffle.getRuntime()).createSpeculationLog());
}
return speculationLog;
}
final void setSpeculationLog(SpeculationLog speculationLog) {
this.speculationLog = speculationLog;
}
@Override
public final JavaConstant asJavaConstant() {
return GraalTruffleRuntime.getRuntime().forObject(this);
}
@SuppressWarnings({"unchecked"})
static <E extends Throwable> RuntimeException rethrow(Throwable ex) throws E {
throw (E) ex;
}
@Override
public final boolean isSameOrSplit(CompilableTruffleAST ast) {
if (!(ast instanceof OptimizedCallTarget)) {
return false;
}
OptimizedCallTarget other = (OptimizedCallTarget) ast;
return this == other || this == other.sourceCallTarget || other == this.sourceCallTarget ||
(this.sourceCallTarget != null && other.sourceCallTarget != null && this.sourceCallTarget == other.sourceCallTarget);
}
@Override
public boolean cancelCompilation(CharSequence reason) {
if (!initialized) {
return false;
}
if (cancelAndResetCompilationTask()) {
runtime().getListener().onCompilationDequeued(this, null, reason);
return true;
}
return false;
}
private boolean cancelAndResetCompilationTask() {
CompilationTask task = this.compilationTask;
if (task != null) {
synchronized (this) {
task = this.compilationTask;
if (task != null) {
return task.cancel();
}
}
}
return false;
}
public final boolean computeBlockCompilations() {
if (blockCompilations == null) {
this.blockCompilations = OptimizedBlockNode.preparePartialBlockCompilations(this);
if (!blockCompilations.isEmpty()) {
return true;
}
}
return false;
}
@Override
public final void onCompilationFailed(Supplier<String> serializedException, boolean silent, boolean bailout, boolean permanentBailout, boolean graphTooBig) {
if (graphTooBig) {
if (computeBlockCompilations()) {
return;
}
}
ExceptionAction action;
if (bailout && !permanentBailout) {
action = ExceptionAction.Silent;
} else {
compilationFailed = true;
action = silent ? ExceptionAction.Silent : engine.compilationFailureAction;
}
if (action == ExceptionAction.Throw) {
final InternalError error = new InternalError(serializedException.get());
throw new OptimizationFailedException(error, this);
}
if (action.ordinal() >= ExceptionAction.Print.ordinal()) {
GraalTruffleRuntime rt = runtime();
Map<String, Object> properties = new LinkedHashMap<>();
properties.put("AST", getNonTrivialNodeCount());
rt.logEvent(this, 0, "opt fail", toString(), properties, serializedException.get());
if (action == ExceptionAction.ExitVM) {
String reason;
if (getOptionValue(PolyglotCompilerOptions.CompilationFailureAction) == ExceptionAction.ExitVM) {
reason = "engine.CompilationFailureAction=ExitVM";
} else if (getOptionValue(PolyglotCompilerOptions.CompilationExceptionsAreFatal)) {
reason = "engine.CompilationExceptionsAreFatal=true";
} else {
reason = "engine.PerformanceWarningsAreFatal=true";
}
log(String.format("Exiting VM due to %s", reason));
System.exit(-1);
}
}
}
public final void log(String message) {
runtime().log(this, message);
}
@Override
public final int getKnownCallSiteCount() {
return callSitesKnown;
}
public final OptimizedCallTarget getSourceCallTarget() {
return sourceCallTarget;
}
@Override
public final String getName() {
CompilerAsserts.neverPartOfCompilation();
String result = nameCache;
if (result == null) {
result = rootNode.toString();
nameCache = result;
}
return result;
}
@Override
public final String toString() {
CompilerAsserts.neverPartOfCompilation();
String superString = rootNode.toString();
if (sourceCallTarget != null) {
superString += " <split-" + Integer.toHexString(hashCode()) + ">";
}
return superString;
}
static final Object[] castArrayFixedLength(Object[] args, int length) {
return args;
}
@SuppressWarnings({"unchecked"})
static <T> T unsafeCast(Object value, Class<T> type, boolean condition, boolean nonNull, boolean exact) {
return (T) value;
}
public static VirtualFrame createFrame(FrameDescriptor descriptor, Object[] args) {
return new FrameWithoutBoxing(descriptor, args);
}
final void onLoopCount(int count) {
assert count >= 0;
int oldLoopCallCount = this.callAndLoopCount;
int newLoopCallCount = oldLoopCallCount + count;
this.callAndLoopCount = newLoopCallCount >= oldLoopCallCount ? newLoopCallCount : Integer.MAX_VALUE;
}
@Override
public final boolean nodeReplaced(Node oldNode, Node newNode, CharSequence reason) {
CompilerAsserts.neverPartOfCompilation();
invalidate(reason);
invalidateNodeRewritingAssumption();
return false;
}
public final void accept(NodeVisitor visitor) {
getRootNode().accept(visitor);
}
@Override
public final int getNonTrivialNodeCount() {
if (cachedNonTrivialNodeCount == -1) {
cachedNonTrivialNodeCount = calculateNonTrivialNodes(getRootNode());
}
return cachedNonTrivialNodeCount;
}
@Override
public final int getCallCount() {
return callCount;
}
public final int getCallAndLoopCount() {
return callAndLoopCount;
}
public final long getInitializedTimestamp() {
return initializedTimestamp;
}
public static int calculateNonTrivialNodes(Node node) {
NonTrivialNodeCountVisitor visitor = new NonTrivialNodeCountVisitor();
node.accept(visitor);
return visitor.nodeCount;
}
public final Map<String, Object> getDebugProperties() {
Map<String, Object> properties = new LinkedHashMap<>();
GraalTruffleRuntimeListener.addASTSizeProperty(this, properties);
String callsThresholdInInterpreter = String.format("%7d/%5d", getCallCount(), engine.callThresholdInInterpreter);
String loopsThresholdInInterpreter = String.format("%7d/%5d", getCallAndLoopCount(), engine.callAndLoopThresholdInInterpreter);
if (engine.multiTier) {
if (isValidLastTier()) {
String callsThresholdInFirstTier = String.format("%7d/%5d", getCallCount(), engine.callThresholdInFirstTier);
String loopsThresholdInFirstTier = String.format("%7d/%5d", getCallCount(), engine.callAndLoopThresholdInFirstTier);
properties.put("Tier", "Last");
properties.put("Calls/Thres", callsThresholdInFirstTier);
properties.put("CallsAndLoop/Thres", loopsThresholdInFirstTier);
} else {
properties.put("Tier", "First");
properties.put("Calls/Thres", callsThresholdInInterpreter);
properties.put("CallsAndLoop/Thres", loopsThresholdInInterpreter);
}
} else {
properties.put("Calls/Thres", callsThresholdInInterpreter);
properties.put("CallsAndLoop/Thres", loopsThresholdInInterpreter);
}
return properties;
}
@Override
public final TruffleCallNode[] getCallNodes() {
final List<OptimizedDirectCallNode> callNodes = new ArrayList<>();
getRootNode().accept(new NodeVisitor() {
@Override
public boolean visit(Node node) {
if (node instanceof OptimizedDirectCallNode) {
callNodes.add((OptimizedDirectCallNode) node);
}
return true;
}
});
return callNodes.toArray(new TruffleCallNode[0]);
}
public final CompilerOptions getCompilerOptions() {
final CompilerOptions options = rootNode.getCompilerOptions();
if (options != null) {
return options;
}
return DefaultCompilerOptions.INSTANCE;
}
final void initializeUnsafeArgumentTypes(Class<?>[] argumentTypes) {
CompilerAsserts.neverPartOfCompilation();
ArgumentsProfile newProfile = new ArgumentsProfile(argumentTypes, "Custom profiled argument types");
if (updateArgumentsProfile(null, newProfile)) {
this.callProfiled = true;
} else {
transitionToInvalidArgumentsProfile();
throw new AssertionError("Argument types already initialized. initializeArgumentTypes() must be called before any profile is initialized.");
}
}
final boolean isValidArgumentProfile(Object[] args) {
assert callProfiled;
ArgumentsProfile argumentsProfile = this.argumentsProfile;
return argumentsProfile.assumption.isValid() && checkProfiledArgumentTypes(args, argumentsProfile.types);
}
private static boolean checkProfiledArgumentTypes(Object[] args, Class<?>[] types) {
assert types != null;
if (args.length != types.length) {
throw new ArrayIndexOutOfBoundsException();
}
if (types[0] != args[0].getClass()) {
throw new ClassCastException();
}
for (int j = 1; j < types.length; j++) {
if (types[j] == null) {
continue;
}
types[j].cast(args[j]);
Objects.requireNonNull(args[j]);
}
return true;
}
public final void stopProfilingArguments() {
assert !callProfiled;
ArgumentsProfile argumentsProfile = this.argumentsProfile;
if (argumentsProfile != ArgumentsProfile.INVALID) {
CompilerDirectives.transferToInterpreter();
transitionToInvalidArgumentsProfile();
}
}
private void transitionToInvalidArgumentsProfile() {
while (true) {
ArgumentsProfile oldProfile = argumentsProfile;
if (oldProfile == ArgumentsProfile.INVALID) {
return;
}
if (updateArgumentsProfile(oldProfile, ArgumentsProfile.INVALID)) {
return;
}
}
}
private boolean updateArgumentsProfile(ArgumentsProfile oldProfile, ArgumentsProfile newProfile) {
if (oldProfile != null) {
oldProfile.assumption.invalidate();
}
return ARGUMENTS_PROFILE_UPDATER.compareAndSet(this, oldProfile, newProfile);
}
@ExplodeLoop
public final void profileArguments(Object[] args) {
assert !callProfiled;
ArgumentsProfile argumentsProfile = this.argumentsProfile;
if (argumentsProfile == null) {
if (CompilerDirectives.inInterpreter()) {
initializeProfiledArgumentTypes(args);
}
} else {
Class<?>[] types = argumentsProfile.types;
if (types != null) {
if (types.length != args.length) {
CompilerDirectives.transferToInterpreterAndInvalidate();
transitionToInvalidArgumentsProfile();
} else if (argumentsProfile.assumption.isValid()) {
for (int i = 0; i < types.length; i++) {
Class<?> type = types[i];
Object value = args[i];
if (type != null && (value == null || value.getClass() != type)) {
CompilerDirectives.transferToInterpreterAndInvalidate();
updateProfiledArgumentTypes(args, argumentsProfile);
break;
}
}
}
}
}
}
private void initializeProfiledArgumentTypes(Object[] args) {
CompilerAsserts.neverPartOfCompilation();
assert !callProfiled;
final ArgumentsProfile newProfile;
if (args.length <= MAX_PROFILED_ARGUMENTS && engine.argumentTypeSpeculation) {
Class<?>[] types = args.length == 0 ? ArgumentsProfile.EMPTY_ARGUMENT_TYPES : new Class<?>[args.length];
for (int i = 0; i < args.length; i++) {
types[i] = classOf(args[i]);
}
newProfile = new ArgumentsProfile(types, ArgumentsProfile.ARGUMENT_TYPES_ASSUMPTION_NAME);
} else {
newProfile = ArgumentsProfile.INVALID;
}
if (!updateArgumentsProfile(null, newProfile)) {
profileArguments(args);
}
}
private void updateProfiledArgumentTypes(Object[] args, ArgumentsProfile oldProfile) {
CompilerAsserts.neverPartOfCompilation();
assert !callProfiled;
Class<?>[] oldTypes = oldProfile.types;
Class<?>[] newTypes = new Class<?>[oldProfile.types.length];
for (int j = 0; j < oldTypes.length; j++) {
newTypes[j] = joinTypes(oldTypes[j], classOf(args[j]));
}
ArgumentsProfile newProfile = new ArgumentsProfile(newTypes, ArgumentsProfile.ARGUMENT_TYPES_ASSUMPTION_NAME);
if (!updateArgumentsProfile(oldProfile, newProfile)) {
profileArguments(args);
}
}
public final Object[] injectArgumentsProfile(Object[] originalArguments) {
assert CompilerDirectives.inCompiledCode();
ArgumentsProfile argumentsProfile = this.argumentsProfile;
Object[] args = originalArguments;
if (argumentsProfile != null && argumentsProfile.assumption.isValid()) {
Class<?>[] types = argumentsProfile.types;
args = unsafeCast(castArrayFixedLength(args, types.length), Object[].class, true, true, true);
args = castArgumentsImpl(args, types);
}
return args;
}
@ExplodeLoop
private Object[] castArgumentsImpl(Object[] originalArguments, Class<?>[] types) {
Object[] castArguments = new Object[types.length];
boolean isCallProfiled = callProfiled;
for (int i = 0; i < types.length; i++) {
Class<?> targetType = types[i];
boolean exact = !isCallProfiled || i == 0;
castArguments[i] = targetType != null ? unsafeCast(originalArguments[i], targetType, true, true, exact) : originalArguments[i];
}
return castArguments;
}
protected final ArgumentsProfile getInitializedArgumentsProfile() {
if (argumentsProfile == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
updateArgumentsProfile(null, ArgumentsProfile.INVALID);
assert argumentsProfile != null;
}
return argumentsProfile;
}
private void profileReturnValue(Object result) {
ReturnProfile returnProfile = this.returnProfile;
if (returnProfile == null) {
if (CompilerDirectives.inInterpreter() && engine.returnTypeSpeculation) {
final Class<?> type = classOf(result);
ReturnProfile newProfile = type == null ? ReturnProfile.INVALID : new ReturnProfile(type);
if (!RETURN_PROFILE_UPDATER.compareAndSet(this, null, newProfile)) {
profileReturnValue(result);
}
}
} else {
if (returnProfile.assumption.isValid() && (result == null || returnProfile.type != result.getClass())) {
CompilerDirectives.transferToInterpreterAndInvalidate();
returnProfile.assumption.invalidate();
ReturnProfile previous = RETURN_PROFILE_UPDATER.getAndSet(this, ReturnProfile.INVALID);
assert previous == returnProfile || previous == ReturnProfile.INVALID;
}
}
}
private Object injectReturnValueProfile(Object result) {
ReturnProfile returnProfile = this.returnProfile;
if (CompilerDirectives.inCompiledCode() && returnProfile != null && returnProfile.assumption.isValid()) {
return OptimizedCallTarget.unsafeCast(result, returnProfile.type, true, true, true);
}
return result;
}
protected final ReturnProfile getInitializedReturnProfile() {
if (returnProfile == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
RETURN_PROFILE_UPDATER.compareAndSet(this, null, ReturnProfile.INVALID);
assert returnProfile != null;
}
return returnProfile;
}
@SuppressWarnings("unchecked")
private <T extends Throwable> T profileExceptionType(T value) {
Class<? extends Throwable> clazz = profiledExceptionType;
if (clazz != Throwable.class) {
if (clazz != null && value.getClass() == clazz) {
if (CompilerDirectives.inInterpreter()) {
return value;
} else {
return (T) CompilerDirectives.castExact(value, clazz);
}
} else {
CompilerDirectives.transferToInterpreterAndInvalidate();
if (clazz == null) {
profiledExceptionType = value.getClass();
} else {
profiledExceptionType = Throwable.class;
}
}
}
return value;
}
private static Class<?> classOf(Object arg) {
return arg != null ? arg.getClass() : null;
}
private static Class<?> joinTypes(Class<?> class1, Class<?> class2) {
if (class1 == class2) {
return class1;
} else {
return null;
}
}
private static OptimizedAssumption createInvalidAssumption(String name) {
OptimizedAssumption result = createValidAssumption(name);
result.invalidate();
return result;
}
private static OptimizedAssumption createValidAssumption(String name) {
return (OptimizedAssumption) Truffle.getRuntime().createAssumption(name);
}
public final boolean isSplit() {
return sourceCallTarget != null;
}
public final OptimizedDirectCallNode getCallSiteForSplit() {
if (isSplit()) {
OptimizedDirectCallNode callNode = getSingleCallNode();
assert callNode != null;
return callNode;
} else {
return null;
}
}
final int getUninitializedNodeCount() {
assert uninitializedNodeCount >= 0;
return uninitializedNodeCount;
}
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);
}
final CompilationTask getCompilationTask() {
return compilationTask;
}
final synchronized void resetCompilationTask() {
assert this.compilationTask != null;
this.compilationTask = null;
}
@SuppressFBWarnings(value = "VO_VOLATILE_INCREMENT", justification = "All increments and decrements are synchronized.")
final synchronized void addDirectCallNode(OptimizedDirectCallNode directCallNode) {
Objects.requireNonNull(directCallNode);
WeakReference<OptimizedDirectCallNode> nodeRef = singleCallNode;
if (nodeRef != MULTIPLE_CALLS) {
if (nodeRef == NO_CALL) {
singleCallNode = new WeakReference<>(directCallNode);
} else if (nodeRef.get() == directCallNode) {
return;
} else {
singleCallNode = MULTIPLE_CALLS;
}
}
callSitesKnown++;
}
@SuppressFBWarnings(value = "VO_VOLATILE_INCREMENT", justification = "All increments and decrements are synchronized.")
final synchronized void removeDirectCallNode(OptimizedDirectCallNode directCallNode) {
Objects.requireNonNull(directCallNode);
WeakReference<OptimizedDirectCallNode> nodeRef = singleCallNode;
if (nodeRef != MULTIPLE_CALLS) {
if (nodeRef == NO_CALL) {
return;
} else if (nodeRef.get() == directCallNode) {
singleCallNode = NO_CALL;
} else {
singleCallNode = MULTIPLE_CALLS;
}
}
callSitesKnown--;
}
public final boolean isSingleCaller() {
WeakReference<OptimizedDirectCallNode> nodeRef = singleCallNode;
if (nodeRef != null) {
return nodeRef.get() != null;
}
return false;
}
public final OptimizedDirectCallNode getSingleCallNode() {
WeakReference<OptimizedDirectCallNode> nodeRef = singleCallNode;
if (nodeRef != null) {
return nodeRef.get();
}
return null;
}
final boolean isNeedsSplit() {
return needsSplit;
}
final void polymorphicSpecialize(Node source) {
List<Node> toDump = null;
if (engine.splittingDumpDecisions) {
toDump = new ArrayList<>();
pullOutParentChain(source, toDump);
}
logPolymorphicEvent(0, "Polymorphic event! Source:", source);
this.maybeSetNeedsSplit(0, toDump);
}
public final void resetNeedsSplit() {
needsSplit = false;
}
private boolean maybeSetNeedsSplit(int depth, List<Node> toDump) {
final OptimizedDirectCallNode onlyCaller = getSingleCallNode();
if (depth > engine.splittingMaxPropagationDepth || needsSplit || callSitesKnown == 0 || getCallCount() == 1) {
logEarlyReturn(depth, callSitesKnown);
return needsSplit;
}
if (onlyCaller != null) {
final RootNode callerRootNode = onlyCaller.getRootNode();
if (callerRootNode != null && callerRootNode.getCallTarget() != null) {
final OptimizedCallTarget callerTarget = (OptimizedCallTarget) callerRootNode.getCallTarget();
if (engine.splittingDumpDecisions) {
pullOutParentChain(onlyCaller, toDump);
}
logPolymorphicEvent(depth, "One caller! Analysing parent.");
if (callerTarget.maybeSetNeedsSplit(depth + 1, toDump)) {
logPolymorphicEvent(depth, "Set needs split to true via parent");
needsSplit = true;
}
}
} else {
logPolymorphicEvent(depth, "Set needs split to true");
needsSplit = true;
maybeDump(toDump);
}
logPolymorphicEvent(depth, "Return:", needsSplit);
return needsSplit;
}
private void logEarlyReturn(int depth, int numberOfKnownCallNodes) {
if (engine.splittingTraceEvents) {
logPolymorphicEvent(depth, "Early return: " + needsSplit + " callCount: " + getCallCount() + ", numberOfKnownCallNodes: " + numberOfKnownCallNodes);
}
}
private void logPolymorphicEvent(int depth, String message) {
logPolymorphicEvent(depth, message, null);
}
private void logPolymorphicEvent(int depth, String message, Object arg) {
if (engine.splittingTraceEvents) {
final String indent = new String(new char[depth]).replace("\0", " ");
final String argString = (arg == null) ? "" : " " + arg;
log(String.format(SPLIT_LOG_FORMAT, indent + message + argString, this.toString()));
}
}
private void maybeDump(List<Node> toDump) {
if (engine.splittingDumpDecisions) {
final List<OptimizedDirectCallNode> callers = new ArrayList<>();
OptimizedDirectCallNode callNode = getSingleCallNode();
if (callNode != null) {
callers.add(callNode);
}
PolymorphicSpecializeDump.dumpPolymorphicSpecialize(this, toDump);
}
}
private static void pullOutParentChain(Node node, List<Node> toDump) {
Node rootNode = node;
while (rootNode.getParent() != null) {
toDump.add(rootNode);
rootNode = rootNode.getParent();
}
toDump.add(rootNode);
}
final void setNonTrivialNodeCount(int nonTrivialNodeCount) {
this.cachedNonTrivialNodeCount = nonTrivialNodeCount;
}
public final boolean prepareForAOT() {
if (wasExecuted()) {
throw new IllegalStateException("Cannot prepare for AOT if call target was already executed.");
}
initialize(false);
ExecutionSignature profile = GraalRuntimeAccessor.NODES.prepareForAOT(rootNode);
if (profile == null) {
return false;
}
if (callProfiled) {
return true;
}
assert returnProfile == null : "return profile already initialized";
assert argumentsProfile == null : "argument profile already initialized";
Class<?>[] argumentTypes = profile.getArgumentTypes();
ArgumentsProfile newProfile;
if (argumentTypes != null && argumentTypes.length <= MAX_PROFILED_ARGUMENTS && engine.argumentTypeSpeculation) {
newProfile = new ArgumentsProfile(argumentTypes, ArgumentsProfile.ARGUMENT_TYPES_ASSUMPTION_NAME);
} else {
newProfile = ArgumentsProfile.INVALID;
}
ReturnProfile returnProfile;
Class<?> returnType = profile.getReturnType();
if (returnType != null && returnType != Object.class) {
returnProfile = new ReturnProfile(returnType);
} else {
returnProfile = ReturnProfile.INVALID;
}
this.returnProfile = returnProfile;
this.argumentsProfile = newProfile;
return true;
}
}