[ORC][MachO] arm64 Darwin exception unwinding terminates as uncaught, ignoring catch
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
Hello, we were getting crashes when throwing exceptions in host C++ through JITted code and trying to catch them in host C++ again. I have tried to construct a minimal reproducer. This is on arm64 macOS 26.5 and I could reproduce this on LLVM 22 and HEAD 3848a2fceab7d4682771c91d28bad9d0304e2b81 . I could not reproduce this on x86_64 Linux.
The reproducer uses `LLJIT::addIRModule()`: LLVM parses a small embedded LLVM IR module, emits the object internally, links it, and then calls the JIT entry point from host C++ code.
The expected behavior is that a host `catch (const std::runtime_error &)` catches an exception thrown by a host function called from JIT code.
The actual behavior is that the process terminates with an uncaught exception.
I think this might be related to something that was fixed in LLD in the past: https://github.com/llvm/llvm-project/issues/59070
The reproducer:
```c++
#include "llvm/AsmParser/Parser.h"
#include "llvm/ExecutionEngine/Orc/ExecutionUtils.h"
#include "llvm/ExecutionEngine/Orc/LLJIT.h"
#include "llvm/Support/SourceMgr.h"
#include "llvm/Support/TargetSelect.h"
#include
#include
using namespace llvm;
using namespace llvm::orc;
static ExitOnError exitOnErr;
static const char ir[] = R"(
declare void @throw_from_host()
define void @jit_c() #0 {
entry:
call void @throw_from_host()
ret void
}
define void @jit_b() #0 {
entry:
call void @jit_c()
ret void
}
define void @jit_a() #0 {
entry:
call void @jit_b()
ret void
}
attributes #0 = { noinline optnone uwtable(sync) "frame-pointer"="non-leaf-no-reserve" }
!llvm.module.flags = !{!0}
!0 = !{i32 7, !"frame-pointer", i32 4}
)";
extern "C" __attribute__((noinline)) void throw_from_host() {
throw std::runtime_error("boom");
}
int main(int argc, char **argv) {
InitializeNativeTarget();
InitializeNativeTargetAsmPrinter();
auto jit = exitOnErr(LLJITBuilder().create());
auto thrower = jit->mangleAndIntern("throw_from_host");
auto throwerAddress = ExecutorAddr::fromPtr(&throw_from_host);
exitOnErr(jit->getMainJITDylib().define(absoluteSymbols({
{thrower, {throwerAddress, JITSymbolFlags::Exported}},
})));
auto context = std::make_unique();
SMDiagnostic error;
auto module = parseAssemblyString(ir, error, *context);
if (!module) {
error.print(argv[0], errs());
return 1;
}
exitOnErr(jit->addIRModule(ThreadSafeModule(std::move(module), std::move(context))));
auto jitA = exitOnErr(jit->lookup("jit_a")).toPtr();
try {
jitA();
} catch (const std::runtime_error &) {
puts("caught");
return 0;
}
puts("returned");
return 1;
}
```
I run that with:
```sh
#!/usr/bin/env bash
set -euo pipefail
llvm_config=${LLVM_CONFIG:-llvm-config-22}
cxx="$("$llvm_config" --bindir)/clang++"
"$cxx" \
driver.cpp \
-o repro-ir \
$("$llvm_config" --cxxflags --ldflags --libs core asmparser orcjit native) \
-Wl,-rpath,"$("$llvm_config" --libdir)" \
-fexceptions
./repro-ir
```
I get the output:
```
libc++abi: terminating due to uncaught exception of type std::runtime_error: boom
./repro.sh: line 13: 85435 Abort trap: 6 ./repro-ir
```
Contributor guide
Research direction
Build and run the driver.cpp reproducer using LLJIT::addIRModule() on arm64 Darwin, then compare its exception-unwinding behavior with x86_64 Linux. Trace the ORC JIT Mach-O path and review the related LLD issue 59070; done means the host catch receives the runtime_error and the reproducer prints "caught" instead of terminating.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100