package com.oracle.svm.core.graal.snippets;
import java.util.HashSet;
import java.util.Set;
import org.graalvm.compiler.api.replacements.Fold;
import org.graalvm.compiler.options.Option;
import org.graalvm.nativeimage.CurrentIsolate;
import org.graalvm.nativeimage.c.function.CodePointer;
import org.graalvm.word.LocationIdentity;
import org.graalvm.word.Pointer;
import com.oracle.svm.core.annotate.NeverInline;
import com.oracle.svm.core.annotate.RestrictHeapAccess;
import com.oracle.svm.core.annotate.Uninterruptible;
import com.oracle.svm.core.code.CodeInfo;
import com.oracle.svm.core.deopt.DeoptimizationSupport;
import com.oracle.svm.core.deopt.DeoptimizedFrame;
import com.oracle.svm.core.deopt.Deoptimizer;
import com.oracle.svm.core.heap.Heap;
import com.oracle.svm.core.option.HostedOptionKey;
import com.oracle.svm.core.snippets.KnownIntrinsics;
import com.oracle.svm.core.snippets.SnippetRuntime;
import com.oracle.svm.core.snippets.SnippetRuntime.SubstrateForeignCallDescriptor;
import com.oracle.svm.core.snippets.SubstrateForeignCallTarget;
import com.oracle.svm.core.stack.JavaStackWalker;
import com.oracle.svm.core.stack.StackFrameVisitor;
import com.oracle.svm.core.thread.JavaThreads;
import com.oracle.svm.core.thread.ThreadingSupportImpl;
import com.oracle.svm.core.thread.VMOperation;
import com.oracle.svm.core.thread.VMThreads;
import com.oracle.svm.core.threadlocal.FastThreadLocalFactory;
import com.oracle.svm.core.threadlocal.FastThreadLocalInt;
import com.oracle.svm.core.util.VMError;
public class DeoptTester {
public static class Options {
@Option(help = "Compiles all methods as deoptimization targets for testing")
public static final HostedOptionKey<Boolean> DeoptimizeAll = new HostedOptionKey<>(false);
}
public static final SubstrateForeignCallDescriptor DEOPTTEST = SnippetRuntime.findForeignCall(DeoptTester.class, "deoptTest", false, LocationIdentity.any());
private static final Set<Long> handledPCs = new HashSet<>();
private static final FastThreadLocalInt inDeoptTest = FastThreadLocalFactory.createInt();
private static final StackFrameVisitor collectPcVisitor = new StackFrameVisitor() {
@Override
@RestrictHeapAccess(access = RestrictHeapAccess.Access.UNRESTRICTED, overridesCallers = true, reason = "Only deals with IPs, not Objects.")
public boolean visitFrame(Pointer sp, CodePointer ip, CodeInfo codeInfo, DeoptimizedFrame deoptimizedFrame) {
handledPCs.add(ip.rawValue());
return true;
}
};
@Fold
public static boolean enabled() {
boolean result = Options.DeoptimizeAll.getValue();
if (result) {
VMError.guarantee(DeoptimizationSupport.enabled(), "Enabling DeoptimizeAll also requires enabling deoptimization support");
}
return result;
}
@NeverInline("deoptTest must have a separate stack frame")
@SubstrateForeignCallTarget(stubCallingConvention = true)
@Uninterruptible(reason = "Prevent recursion while deoptimization testing is not suspended.", calleeMustBe = false)
public static void deoptTest() {
assert enabled();
if (inDeoptTest.get() > 0) {
return;
}
disableDeoptTesting();
try {
if (Heap.getHeap().isAllocationDisallowed() ||
!CEntryPointSnippets.isIsolateInitialized() ||
ThreadingSupportImpl.isRecurringCallbackPaused() ||
VMOperation.isInProgress() ||
VMThreads.StatusSupport.isStatusIgnoreSafepoints(CurrentIsolate.getCurrentThread()) ||
!JavaThreads.currentJavaThreadInitialized()) {
return;
}
Pointer startSp = KnownIntrinsics.readCallerStackPointer();
int numHandledPCs = handledPCs.size();
JavaStackWalker.walkCurrentThread(startSp, collectPcVisitor);
if (handledPCs.size() > numHandledPCs) {
Deoptimizer.deoptimizeAll();
}
} finally {
enableDeoptTesting();
}
}
@Uninterruptible(reason = "Prevent indirect recursion when called from deoptTest(), but safe to inline anywhere.", mayBeInlined = true)
public static void disableDeoptTesting() {
if (enabled()) {
int newValue = inDeoptTest.get() + 1;
assert newValue >= 1;
inDeoptTest.set(newValue);
}
}
@Uninterruptible(reason = "Prevent indirect recursion when called from deoptTest(), but safe to inline anywhere.", mayBeInlined = true)
public static void enableDeoptTesting() {
if (enabled()) {
int newValue = inDeoptTest.get() - 1;
assert newValue >= 0;
inDeoptTest.set(newValue);
}
}
}