- removed the longjmp based exception catch/rethrow mechanism and instead force-terminate in case a user exception is thrown while the VM is executing JITed code on a non-Windows system

On Windows none of this is needed, because we can generate a proper unwind frame for the JITed functions, but even on Linux, it would require manual additions to each single piece of native code that ever gets called from inside a JIT compiled function.
This is an utterly prohibitive proposition because it makes direct native calls a virtual impossibility
So, in order to get the thrown error properly presented both I_Error and ThrowAbortException will now forward to I_FatalError if it is called from inside a JIT context.
This commit is contained in:
Christoph Oelckers 2018-11-25 12:36:00 +01:00
parent b26b16e4b7
commit 42b9a41421
6 changed files with 112 additions and 174 deletions

View File

@ -130,7 +130,7 @@ void I_Quit()
extern FILE* Logfile;
bool gameisdead;
void I_FatalError(const char* const error, ...)
static void I_FatalError(const char* const error, va_list ap)
{
static bool alreadyThrown = false;
gameisdead = true;
@ -143,7 +143,7 @@ void I_FatalError(const char* const error, ...)
int index;
va_list argptr;
va_start(argptr, error);
index = vsnprintf(errortext, MAX_ERRORTEXT, error, argptr);
index = vsnprintf(errortext, MAX_ERRORTEXT, error, ap);
va_end(argptr);
extern void Mac_I_FatalError(const char*);
@ -167,16 +167,34 @@ void I_FatalError(const char* const error, ...)
}
}
void I_Error(const char* const error, ...)
void I_FatalError(const char* const error, ...)
{
va_list argptr;
va_start(argptr, error);
I_FatalError(error, argptr);
va_end(argptr);
}
extern thread_local int jit_frames;
void I_Error (const char *error, ...)
{
va_list argptr;
char errortext[MAX_ERRORTEXT];
va_start(argptr, error);
vsnprintf(errortext, MAX_ERRORTEXT, error, argptr);
va_end(argptr);
throw CRecoverableError(errortext);
if (jit_frames == 0)
{
vsprintf (errortext, error, argptr);
va_end (argptr);
throw CRecoverableError(errortext);
}
else
{
I_FatalError(error, argptr);
va_end(argptr);
}
}

View File

@ -173,7 +173,7 @@ void Linux_I_FatalError(const char* errortext)
}
#endif
void I_FatalError (const char *error, ...)
void I_FatalError (const char *error, va_list ap)
{
static bool alreadyThrown = false;
gameisdead = true;
@ -183,10 +183,7 @@ void I_FatalError (const char *error, ...)
alreadyThrown = true;
char errortext[MAX_ERRORTEXT];
int index;
va_list argptr;
va_start (argptr, error);
index = vsnprintf (errortext, MAX_ERRORTEXT, error, argptr);
va_end (argptr);
index = vsnprintf (errortext, MAX_ERRORTEXT, error, ap);
#ifdef __APPLE__
Mac_I_FatalError(errortext);
@ -214,16 +211,34 @@ void I_FatalError (const char *error, ...)
}
}
void I_FatalError(const char* const error, ...)
{
va_list argptr;
va_start(argptr, error);
I_FatalError(error, argptr);
va_end(argptr);
}
extern thread_local int jit_frames;
void I_Error (const char *error, ...)
{
va_list argptr;
char errortext[MAX_ERRORTEXT];
va_start (argptr, error);
vsprintf (errortext, error, argptr);
va_end (argptr);
va_start(argptr, error);
throw CRecoverableError (errortext);
if (jit_frames == 0)
{
vsprintf (errortext, error, argptr);
va_end (argptr);
throw CRecoverableError(errortext);
}
else
{
I_FatalError(error, argptr);
va_end(argptr);
}
}
void I_SetIWADInfo ()

View File

@ -316,19 +316,10 @@ void JitCompiler::SetupSimpleFrame()
static VMFrameStack *CreateFullVMFrame(VMScriptFunction *func, VMValue *args, int numargs)
{
try
{
VMFrameStack *stack = &GlobalVMStack;
VMFrame *newf = stack->AllocFrame(func);
CurrentJitExceptInfo->vmframes++;
VMFillParams(args, newf, numargs);
return stack;
}
catch (...)
{
VMThrowException(std::current_exception());
return nullptr;
}
VMFrameStack *stack = &GlobalVMStack;
VMFrame *newf = stack->AllocFrame(func);
VMFillParams(args, newf, numargs);
return stack;
}
void JitCompiler::SetupFullVMFrame()
@ -362,15 +353,7 @@ void JitCompiler::SetupFullVMFrame()
static void PopFullVMFrame(VMFrameStack *stack)
{
try
{
stack->PopFrame();
CurrentJitExceptInfo->vmframes--;
}
catch (...)
{
VMThrowException(std::current_exception());
}
stack->PopFrame();
}
void JitCompiler::EmitPopFrame()
@ -434,14 +417,7 @@ void JitCompiler::EmitNullPointerThrow(int index, EVMAbortException reason)
void JitCompiler::ThrowException(VMScriptFunction *func, VMOP *line, int reason)
{
try
{
ThrowAbortException(func, line, (EVMAbortException)reason, nullptr);
}
catch (...)
{
VMThrowException(std::current_exception());
}
ThrowAbortException(func, line, (EVMAbortException)reason, nullptr);
}
void JitCompiler::EmitThrowException(EVMAbortException reason)

View File

@ -47,14 +47,7 @@ void JitCompiler::EmitIJMP()
static void ValidateCall(DObject *o, VMFunction *f, int b)
{
try
{
FScopeBarrier::ValidateCall(o->GetClass(), f, b - 1);
}
catch (...)
{
VMThrowException(std::current_exception());
}
FScopeBarrier::ValidateCall(o->GetClass(), f, b - 1);
}
void JitCompiler::EmitSCOPE()
@ -241,30 +234,22 @@ void JitCompiler::EmitRETI()
static DObject* CreateNew(PClass *cls, int c)
{
try
if (!cls->ConstructNative)
{
if (!cls->ConstructNative)
{
ThrowAbortException(X_OTHER, "Class %s requires native construction", cls->TypeName.GetChars());
}
else if (cls->bAbstract)
{
ThrowAbortException(X_OTHER, "Cannot instantiate abstract class %s", cls->TypeName.GetChars());
}
else if (cls->IsDescendantOf(NAME_Actor)) // Creating actors here must be outright prohibited
{
ThrowAbortException(X_OTHER, "Cannot create actors with 'new'");
}
ThrowAbortException(X_OTHER, "Class %s requires native construction", cls->TypeName.GetChars());
}
else if (cls->bAbstract)
{
ThrowAbortException(X_OTHER, "Cannot instantiate abstract class %s", cls->TypeName.GetChars());
}
else if (cls->IsDescendantOf(NAME_Actor)) // Creating actors here must be outright prohibited
{
ThrowAbortException(X_OTHER, "Cannot create actors with 'new'");
}
// [ZZ] validate readonly and between scope construction
if (c) FScopeBarrier::ValidateNew(cls, c - 1);
return cls->CreateNew();
}
catch (...)
{
VMThrowException(std::current_exception());
return nullptr;
}
// [ZZ] validate readonly and between scope construction
if (c) FScopeBarrier::ValidateNew(cls, c - 1);
return cls->CreateNew();
}
void JitCompiler::EmitNEW()
@ -280,39 +265,24 @@ void JitCompiler::EmitNEW()
static void ThrowNewK(PClass *cls, int c)
{
try
if (!cls->ConstructNative)
{
if (!cls->ConstructNative)
{
ThrowAbortException(X_OTHER, "Class %s requires native construction", cls->TypeName.GetChars());
}
else if (cls->bAbstract)
{
ThrowAbortException(X_OTHER, "Cannot instantiate abstract class %s", cls->TypeName.GetChars());
}
else // if (cls->IsDescendantOf(NAME_Actor)) // Creating actors here must be outright prohibited
{
ThrowAbortException(X_OTHER, "Cannot create actors with 'new'");
}
ThrowAbortException(X_OTHER, "Class %s requires native construction", cls->TypeName.GetChars());
}
catch (...)
else if (cls->bAbstract)
{
VMThrowException(std::current_exception());
ThrowAbortException(X_OTHER, "Cannot instantiate abstract class %s", cls->TypeName.GetChars());
}
else // if (cls->IsDescendantOf(NAME_Actor)) // Creating actors here must be outright prohibited
{
ThrowAbortException(X_OTHER, "Cannot create actors with 'new'");
}
}
static DObject *CreateNewK(PClass *cls, int c)
{
try
{
if (c) FScopeBarrier::ValidateNew(cls, c - 1);
return cls->CreateNew();
}
catch (...)
{
VMThrowException(std::current_exception());
return nullptr;
}
if (c) FScopeBarrier::ValidateNew(cls, c - 1);
return cls->CreateNew();
}
void JitCompiler::EmitNEW_K()
@ -393,19 +363,12 @@ void JitCompiler::EmitBOUND_R()
void JitCompiler::ThrowArrayOutOfBounds(VMScriptFunction *func, VMOP *line, int index, int size)
{
try
if (index >= size)
{
if (index >= size)
{
ThrowAbortException(X_ARRAY_OUT_OF_BOUNDS, "Max.index = %u, current index = %u\n", size, index);
}
else
{
ThrowAbortException(X_ARRAY_OUT_OF_BOUNDS, "Negative current index = %i\n", index);
}
ThrowAbortException(X_ARRAY_OUT_OF_BOUNDS, "Max.index = %u, current index = %u\n", size, index);
}
catch (...)
else
{
VMThrowException(std::current_exception());
ThrowAbortException(X_ARRAY_OUT_OF_BOUNDS, "Negative current index = %i\n", index);
}
}

View File

@ -58,6 +58,17 @@ CUSTOM_CVAR(Bool, vm_jit, true, CVAR_NOINITCALL)
CVAR(Bool, vm_jit, false, CVAR_NOINITCALL|CVAR_NOSET)
#endif
// On Windows we can rely on the system's unwinder to deal with JITed code.
// On Linux we can not and need to be able to tell the error management that we have some JITed code in the call chain.
#ifdef _WIN32
inline void beginVM() { }
inline void endVM() { }
#else
thread_local int jit_frames;
inline void beginVM() { if (vm_jit) jit_frames++; }
inline void endVM() { if (vm_jit) jit_frames--; }
#endif
cycle_t VMCycles[10];
int VMCalls[10];
@ -314,13 +325,7 @@ int VMNativeFunction::NativeScriptCall(VMFunction *func, VMValue *params, int nu
{
err.MaybePrintMessage();
err.stacktrace.AppendFormat("Called from %s\n", func->PrintableName.GetChars());
VMThrowException(std::current_exception());
return 0;
}
catch (...)
{
VMThrowException(std::current_exception());
return 0;
throw;
}
}
@ -534,32 +539,6 @@ VMFrame *VMFrameStack::PopFrame()
return parent;
}
//===========================================================================
//
// The jitted code does not implement C++ exception handling.
// Catch them, longjmp out of the jitted functions, perform vmframe cleanup
// then rethrow the C++ exception
//
//===========================================================================
thread_local JitExceptionInfo *CurrentJitExceptInfo;
void VMThrowException(std::exception_ptr cppException)
{
CurrentJitExceptInfo->cppException = cppException;
longjmp(CurrentJitExceptInfo->sjljbuf, 1);
}
static void VMRethrowException(JitExceptionInfo *exceptInfo)
{
int c = exceptInfo->vmframes;
VMFrameStack *stack = &GlobalVMStack;
for (int i = 0; i < c; i++)
stack->PopFrame();
std::rethrow_exception(exceptInfo->cppException);
}
//===========================================================================
//
// VMFrameStack :: Call
@ -601,25 +580,12 @@ int VMCall(VMFunction *func, VMValue *params, int numparams, VMReturn *results,
{
VMCycles[0].Clock();
JitExceptionInfo *prevExceptInfo = CurrentJitExceptInfo;
JitExceptionInfo newExceptInfo;
CurrentJitExceptInfo = &newExceptInfo;
if (setjmp(CurrentJitExceptInfo->sjljbuf) == 0)
{
auto sfunc = static_cast<VMScriptFunction *>(func);
int numret = sfunc->ScriptCall(sfunc, params, numparams, results, numresults);
CurrentJitExceptInfo = prevExceptInfo;
VMCycles[0].Unclock();
return numret;
}
else
{
VMCycles[0].Unclock();
auto exceptInfo = CurrentJitExceptInfo;
CurrentJitExceptInfo = prevExceptInfo;
VMRethrowException(exceptInfo);
return 0;
}
auto sfunc = static_cast<VMScriptFunction *>(func);
beginVM();
int numret = sfunc->ScriptCall(sfunc, params, numparams, results, numresults);
endVM();
VMCycles[0].Unclock();
return numret;
}
}
}
@ -733,7 +699,18 @@ void ThrowAbortException(VMScriptFunction *sfunc, VMOP *line, EVMAbortException
{
va_list ap;
va_start(ap, moreinfo);
CVMAbortException err(reason, moreinfo, ap);
#ifndef _WIN32)
// Without a usable unwinder, aborting is the only real option here. :(
if (jit_frames)
{
I_FatalError("VM error: %s", err.what());
}
#endif
err.stacktrace.AppendFormat("Called from %s at %s, line %d\n", sfunc->PrintableName.GetChars(), sfunc->SourceFileName.GetChars(), sfunc->PCToLine(line));
throw err;
va_end(ap);

View File

@ -439,17 +439,6 @@ extern thread_local VMFrameStack GlobalVMStack;
typedef std::pair<const class PType *, unsigned> FTypeAndOffset;
struct JitExceptionInfo
{
std::exception_ptr cppException;
std::jmp_buf sjljbuf;
int vmframes = 0;
};
extern thread_local JitExceptionInfo *CurrentJitExceptInfo;
void VMThrowException(std::exception_ptr cppException);
typedef int(*JitFuncPtr)(VMFunction *func, VMValue *params, int numparams, VMReturn *ret, int numret);
class VMScriptFunction : public VMFunction