I am looking through the source and Support/ErrorHandling.cpp/.h says, in so
many words, "don't throw exceptions from inside an installed fatal error
handling routine".
So how are people handling errors during JIT? For command-line compilers,
calling exit() or abort() isn't as big a deal -- the compiler is probably
going to exit anyway. But say, for instance, the host application is Firefox
-- my guess is that just letting exit() handle the situation isn't the
solution.
So what is the "proper" way to handle exceptional LLVM conditions ("cannot
select", for instance)? Yes, I know this is something that needs to be
brought to the programmer's attention, but having the user's application
crash out on them isn't the way I expect most actual LLVM-JIT-based
applications are handling these things...
Thanks
Greg
I use setjmp/longjmp in ClamAV. Would be better if instead of throwing a fatal error
the code generators would simply return an error code, but that would be too much work.
Although this might leak memory in the case of a fatal error, it is better than crashing (for example
in ClamAV we simply turn off the JIT and fallback to our own interpreter).
I wrap all my toplevel functions that call into LLVM with (there are only 3):
HANDLER_TRY(handler) {
...
return 0;
} HANDLER_END(handler);
return ...some_errorcode...
class ScopedExceptionHandler {
public:
jmp_buf &getEnv() { return env;}
void Set() {
/* set the exception handler's return location to here for the
* current thread */
ExceptionReturn.set((const jmp_buf*)&env);
}
~ScopedExceptionHandler() {
/* leaving scope, remove exception handler for current thread */
ExceptionReturn.erase();
}
private:
jmp_buf env;
};
static sys::ThreadLocal<const jmp_buf> ExceptionReturn;
static void NORETURN jit_exception_handler(void)
{
jmp_buf* buf = const_cast<jmp_buf*>(ExceptionReturn.get());
if (buf) {
// For errors raised during bytecode generation and execution.
longjmp(*buf, 1);
} else {
// Oops, got no error recovery pointer set up,
// this is probably an error raised during shutdown.
cli_errmsg("[Bytecode JIT]: exception handler called, but no recovery point set up");
// should never happen, we remove the error handler when we don't use
// LLVM anymore, and when we use it, we do set an error recovery point.
llvm_unreachable("Bytecode JIT]: no exception handler recovery installed, but exception hit!");
}
}
void llvm_error_handler(void *user_data, const std::string &reason)
{
// Output it to stderr, it might exceed the 1k/4k limit of cli_errmsg
cli_errmsg("[Bytecode JIT]: [LLVM error] %s\n", reason.c_str());
jit_exception_handler();
}
};
#define HANDLER_TRY(handler) \
if (setjmp(handler.getEnv()) == 0) {\
handler.Set();
#define HANDLER_END(handler) \
} else cli_warnmsg("[Bytecode JIT]: recovered from error\n");
The best way to reliably isolate software errors without leaking memory is to give the code it’s own address space which you can cleanly throw away afterwards. In other words, fork a child process.