package com.oracle.svm.core.snippets;
import org.graalvm.nativeimage.CurrentIsolate;
import org.graalvm.nativeimage.ImageSingletons;
import org.graalvm.nativeimage.LogHandler;
import org.graalvm.nativeimage.StackValue;
import org.graalvm.nativeimage.c.function.CodePointer;
import org.graalvm.nativeimage.hosted.Feature;
import org.graalvm.word.LocationIdentity;
import org.graalvm.word.Pointer;
import org.graalvm.word.UnsignedWord;
import org.graalvm.word.WordFactory;
import com.oracle.svm.core.FrameAccess;
import com.oracle.svm.core.SubstrateOptions;
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.code.CodeInfoAccess;
import com.oracle.svm.core.code.CodeInfoQueryResult;
import com.oracle.svm.core.code.SimpleCodeInfoQueryResult;
import com.oracle.svm.core.code.UntetheredCodeInfo;
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.log.Log;
import com.oracle.svm.core.snippets.SnippetRuntime.SubstrateForeignCallDescriptor;
import com.oracle.svm.core.stack.JavaStackWalk;
import com.oracle.svm.core.stack.JavaStackWalker;
import com.oracle.svm.core.stack.StackOverflowCheck;
import com.oracle.svm.core.thread.VMThreads;
import com.oracle.svm.core.threadlocal.FastThreadLocalFactory;
import com.oracle.svm.core.threadlocal.FastThreadLocalObject;
public abstract class ExceptionUnwind {
public static final SubstrateForeignCallDescriptor UNWIND_EXCEPTION_WITHOUT_CALLEE_SAVED_REGISTERS = SnippetRuntime.findForeignCall(ExceptionUnwind.class,
"unwindExceptionWithoutCalleeSavedRegisters", true, LocationIdentity.any());
public static final SubstrateForeignCallDescriptor UNWIND_EXCEPTION_WITH_CALLEE_SAVED_REGISTERS = SnippetRuntime.findForeignCall(ExceptionUnwind.class, "unwindExceptionWithCalleeSavedRegisters",
true, LocationIdentity.any());
public static final SubstrateForeignCallDescriptor[] FOREIGN_CALLS = new SubstrateForeignCallDescriptor[]{
UNWIND_EXCEPTION_WITHOUT_CALLEE_SAVED_REGISTERS,
UNWIND_EXCEPTION_WITH_CALLEE_SAVED_REGISTERS
};
public static final FastThreadLocalObject<Throwable> currentException = FastThreadLocalFactory.createObject(Throwable.class);
@Uninterruptible(reason = "Called from uninterruptible callers.", mayBeInlined = true)
static boolean exceptionsAreFatal() {
return SubstrateOptions.MultiThreaded.getValue() && !VMThreads.StatusSupport.isStatusJava();
}
@SubstrateForeignCallTarget(stubCallingConvention = true)
@Uninterruptible(reason = "Must not execute recurring callbacks or a stack overflow check.", calleeMustBe = false)
@RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate when unwinding the stack.")
private static void unwindExceptionWithoutCalleeSavedRegisters(Throwable exception, Pointer callerSP) {
StackOverflowCheck.singleton().makeYellowZoneAvailable();
unwindExceptionInterruptible(exception, callerSP, false);
}
@SubstrateForeignCallTarget(stubCallingConvention = true)
@Uninterruptible(reason = "Must not execute recurring callbacks or a stack overflow check.", calleeMustBe = false)
@RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate when unwinding the stack.")
private static void unwindExceptionWithCalleeSavedRegisters(Throwable exception, Pointer callerSP) {
StackOverflowCheck.singleton().makeYellowZoneAvailable();
unwindExceptionInterruptible(exception, callerSP, true);
}
@RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate when unwinding the stack.")
private static void unwindExceptionInterruptible(Throwable exception, Pointer callerSP, boolean fromMethodWithCalleeSavedRegisters) {
if (currentException.get() != null) {
reportRecursiveUnwind(exception);
return;
}
currentException.set(exception);
if (exceptionsAreFatal()) {
reportFatalUnwind(exception);
return;
}
if (ImageSingletons.contains(ExceptionUnwind.class)) {
ImageSingletons.lookup(ExceptionUnwind.class).customUnwindException(callerSP);
} else {
defaultUnwindException(callerSP, fromMethodWithCalleeSavedRegisters);
}
reportUnhandledException(exception);
}
private static void reportRecursiveUnwind(Throwable exception) {
Log.log().string("Fatal error: recursion in exception handling: ").string(exception.getClass().getName());
Log.log().string(" thrown while unwinding ").string(currentException.get().getClass().getName()).newline();
ImageSingletons.lookup(LogHandler.class).fatalError();
}
private static void reportFatalUnwind(Throwable exception) {
Log.log().string("Fatal error: exception unwind while thread is not in Java state: ");
Log.log().exception(exception);
ImageSingletons.lookup(LogHandler.class).fatalError();
}
private static void reportUnhandledException(Throwable exception) {
Log.log().string("Fatal error: unhandled exception in isolate ").hex(CurrentIsolate.getIsolate()).string(": ");
Log.log().exception(exception);
ImageSingletons.lookup(LogHandler.class).fatalError();
}
protected abstract void customUnwindException(Pointer callerSP);
@Uninterruptible(reason = "Prevent deoptimization apart from the few places explicitly considered safe for deoptimization")
private static void defaultUnwindException(Pointer startSP, boolean fromMethodWithCalleeSavedRegisters) {
boolean hasCalleeSavedRegisters = fromMethodWithCalleeSavedRegisters;
CodePointer startIP = FrameAccess.singleton().readReturnAddress(startSP);
JavaStackWalk walk = StackValue.get(JavaStackWalk.class);
JavaStackWalker.initWalk(walk, startSP, startIP);
while (true) {
SimpleCodeInfoQueryResult codeInfoQueryResult = StackValue.get(SimpleCodeInfoQueryResult.class);
Pointer sp = walk.getSP();
CodePointer ip = walk.getPossiblyStaleIP();
DeoptimizedFrame deoptFrame = Deoptimizer.checkDeoptimized(sp);
if (deoptFrame == null) {
UntetheredCodeInfo untetheredInfo = walk.getIPCodeInfo();
if (untetheredInfo.isNull()) {
JavaStackWalker.reportUnknownFrameEncountered(sp, ip, deoptFrame);
return;
}
Object tether = CodeInfoAccess.acquireTether(untetheredInfo);
try {
CodeInfo codeInfo = CodeInfoAccess.convert(untetheredInfo, tether);
lookupCodeInfoInterruptible(codeInfo, ip, codeInfoQueryResult);
deoptFrame = Deoptimizer.checkDeoptimized(sp);
} finally {
CodeInfoAccess.releaseTether(untetheredInfo, tether);
}
}
if (deoptFrame != null && DeoptimizationSupport.enabled()) {
deoptTakeExceptionInterruptible(deoptFrame);
jumpToHandler(sp, DeoptimizationSupport.getDeoptStubPointer(), hasCalleeSavedRegisters);
return;
}
long exceptionOffset = codeInfoQueryResult.getExceptionOffset();
if (exceptionOffset != CodeInfoQueryResult.NO_EXCEPTION_OFFSET) {
CodePointer handlerIP = (CodePointer) ((UnsignedWord) ip).add(WordFactory.signed(exceptionOffset));
jumpToHandler(sp, handlerIP, hasCalleeSavedRegisters);
return;
}
hasCalleeSavedRegisters = CodeInfoQueryResult.hasCalleeSavedRegisters(codeInfoQueryResult.getEncodedFrameSize());
if (!JavaStackWalker.continueWalk(walk, codeInfoQueryResult, deoptFrame)) {
return;
}
}
}
@Uninterruptible(reason = "Prevent deoptimization while dispatching to exception handler")
private static void jumpToHandler(Pointer sp, CodePointer handlerIP, boolean hasCalleeSavedRegisters) {
Throwable exception = currentException.get();
currentException.set(null);
StackOverflowCheck.singleton().protectYellowZone();
if (hasCalleeSavedRegisters) {
KnownIntrinsics.farReturn(exception, sp, handlerIP, true);
} else {
KnownIntrinsics.farReturn(exception, sp, handlerIP, false);
}
}
@Uninterruptible(reason = "Wrap call to interruptible code.", calleeMustBe = false)
private static void deoptTakeExceptionInterruptible(DeoptimizedFrame deoptFrame) {
deoptFrame.takeException();
}
@Uninterruptible(reason = "Wrap call to interruptible code.", calleeMustBe = false)
private static void lookupCodeInfoInterruptible(CodeInfo codeInfo, CodePointer ip, SimpleCodeInfoQueryResult codeInfoQueryResult) {
CodeInfoAccess.lookupCodeInfo(codeInfo, CodeInfoAccess.relativeIP(codeInfo, ip), codeInfoQueryResult);
}
}