[llvm-branch-commits] [llvm] 7d23f0d - [ORC] Fix malformed Mach-O `__unwind_info` tables (#217781)
Tobias Hieta via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Sat Sep 5 00:12:32 PDT 2026
Author: Jeaye Wilkerson
Date: 2026-09-05T09:12:04+02:00
New Revision: 7d23f0d618e1c967c552ae57faa00a13f4feba26
URL: https://github.com/llvm/llvm-project/commit/7d23f0d618e1c967c552ae57faa00a13f4feba26
DIFF: https://github.com/llvm/llvm-project/commit/7d23f0d618e1c967c552ae57faa00a13f4feba26.diff
LOG: [ORC] Fix malformed Mach-O `__unwind_info` tables (#217781)
The malformed tables are produced by `JITLink` after compact-unwind
record merging.
`CompactUnwindManager::mergeRecords()` coalesces adjacent function
records with identical mergeable encodings. When the final raw record is
merged away, `writeIndexes()` generates the top-level index terminator
from the final remaining record instead of the final original function.
This truncates the final index range.
On macOS arm64, libunwind resolves a PC in the merged-away final
function through the terminator entry. Its second-level-page offset is
zero by design, causing libunwind to interpret the `__unwind_info`
header as a second-level page and report:
```
libunwind: malformed __unwind_info ... bad second level page
```
The fix retains the final raw function symbol before records are merged
and uses its post-allocation range end when writing the terminator index
entry. This keeps the compact-unwind index range valid for all merged
functions.
To test this, there's a new ORC Darwin test that:
1. Creates adjacent mergeable compact-unwind records.
2. Throws from the final merged function.
3. Catches the exception in `main`.
The test reproduces the malformed table failure before the fix and exits
successfully with the fix.
----
Note that I used Github copilot to diagnose and fix this issue. I am not
familiar with this code and so I don't know for certain that this change
makes sense. However, I do know that it fixes the issues I'm seeing with
jank on macOS and that the provided test fails on main and passes with
this change.
(cherry picked from commit 3700e7cebbae20566f84025ecf5a40ab7e7b658b)
Added:
llvm/test/ExecutionEngine/Orc/throw-catch-merged-compact-unwind.ll
Modified:
llvm/lib/ExecutionEngine/JITLink/CompactUnwindSupport.h
Removed:
################################################################################
diff --git a/llvm/lib/ExecutionEngine/JITLink/CompactUnwindSupport.h b/llvm/lib/ExecutionEngine/JITLink/CompactUnwindSupport.h
index e510ec391f7da..9dae7e03f9b8e 100644
--- a/llvm/lib/ExecutionEngine/JITLink/CompactUnwindSupport.h
+++ b/llvm/lib/ExecutionEngine/JITLink/CompactUnwindSupport.h
@@ -478,6 +478,8 @@ template <typename CURecTraits> class CompactUnwindManager {
SmallVector<CompactUnwindRecord> NonUniqued = std::move(Records);
Records.reserve(NonUniqued.size());
+ // Retain the final raw record: merging may remove it.
+ LastFunction = NonUniqued.back().Fn;
Records.push_back(NonUniqued.front());
for (size_t I = 1; I != NonUniqued.size(); ++I) {
auto &Next = NonUniqued[I];
@@ -557,13 +559,13 @@ template <typename CURecTraits> class CompactUnwindManager {
// Write the index array terminator.
{
auto FnEndDelta =
- Records.back().Fn->getRange().End - CompactUnwindBase->getAddress();
+ LastFunction->getRange().End - CompactUnwindBase->getAddress();
if (LLVM_UNLIKELY(!isUInt<32>(FnEndDelta)))
return make_error<JITLinkError>(
"In " + G.getName() + " " + UnwindInfoSectionName +
", delta to end of functions " +
- formatv("{0:x}", Records.back().Fn->getRange().End) +
+ formatv("{0:x}", LastFunction->getRange().End) +
" exceeds 32 bits");
cantFail(W.writeInteger<uint32_t>(FnEndDelta));
@@ -688,6 +690,7 @@ template <typename CURecTraits> class CompactUnwindManager {
size_t NumLSDAs = 0;
size_t NumSecondLevelPages = 0;
+ Symbol *LastFunction = nullptr;
SmallVector<Symbol *, MaxPersonalities> Personalities;
SmallVector<CompactUnwindRecord> Records;
};
diff --git a/llvm/test/ExecutionEngine/Orc/throw-catch-merged-compact-unwind.ll b/llvm/test/ExecutionEngine/Orc/throw-catch-merged-compact-unwind.ll
new file mode 100644
index 0000000000000..df562f3187e60
--- /dev/null
+++ b/llvm/test/ExecutionEngine/Orc/throw-catch-merged-compact-unwind.ll
@@ -0,0 +1,59 @@
+; REQUIRES: system-darwin && host-unwind-supports-jit
+; RUN: lli -jit-kind=orc %s
+;
+; Check that the compact-unwind index terminator covers the final function when
+; its record is merged with the preceding function's record.
+
+ at _ZTIi = external constant ptr
+
+declare i32 @getpid()
+declare ptr @__cxa_allocate_exception(i64)
+declare void @__cxa_throw(ptr, ptr, ptr)
+declare ptr @__cxa_begin_catch(ptr)
+declare void @__cxa_end_catch()
+declare i32 @__gxx_personality_v0(...)
+declare i32 @llvm.eh.typeid.for.p0(ptr)
+
+define i32 @main(i32 %argc, ptr %argv) "frame-pointer"="all" personality ptr @__gxx_personality_v0 {
+entry:
+ %ignored = call i32 @first()
+ invoke void @thrower()
+ to label %return.unreachable unwind label %lpad
+
+lpad:
+ %0 = landingpad { ptr, i32 }
+ catch ptr @_ZTIi
+ %1 = extractvalue { ptr, i32 } %0, 1
+ %2 = tail call i32 @llvm.eh.typeid.for.p0(ptr nonnull @_ZTIi)
+ %matches = icmp eq i32 %1, %2
+ br i1 %matches, label %catch, label %eh.resume
+
+catch:
+ %3 = extractvalue { ptr, i32 } %0, 0
+ %4 = tail call ptr @__cxa_begin_catch(ptr %3)
+ %5 = load i32, ptr %4
+ %sub = sub nsw i32 42, %5
+ tail call void @__cxa_end_catch()
+ ret i32 %sub
+
+return.unreachable:
+ unreachable
+
+eh.resume:
+ resume { ptr, i32 } %0
+}
+
+define i32 @first() noinline optnone "frame-pointer"="all" {
+entry:
+ %pid = call i32 @getpid()
+ ret i32 %pid
+}
+
+define void @thrower() noinline optnone "frame-pointer"="all" {
+entry:
+ %pid = call i32 @getpid()
+ %exception = call ptr @__cxa_allocate_exception(i64 4)
+ store i32 42, ptr %exception
+ call void @__cxa_throw(ptr %exception, ptr nonnull @_ZTIi, ptr null)
+ unreachable
+}
More information about the llvm-branch-commits
mailing list