llvm / llvm/llvm-project

[ORC][MachO] arm64 Darwin exception unwinding terminates as uncaught, ignoring catch

Open
#207,039 1 comment 0 reactions 0 assignees View on GitHub
orcjit
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.