Skip to content

Don't register EHFrameRegistrationPlugin on MachO (fixes #1782)#1783

Open
dg1sbg wants to merge 1 commit into
clasp-developers:mainfrom
dg1sbg:fix/macos-arm64-compact-unwind-1782
Open

Don't register EHFrameRegistrationPlugin on MachO (fixes #1782)#1783
dg1sbg wants to merge 1 commit into
clasp-developers:mainfrom
dg1sbg:fix/macos-arm64-compact-unwind-1782

Conversation

@dg1sbg

@dg1sbg dg1sbg commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Problem

The long-standing clasp/cando macos-latest/native "Run regression tests" CI failure (#1782): the mp regression suite reaches PROCESS-ABORT-1 and the process std::terminates instead of unwinding (startRunStop.cc:135 unhandled unknown exceptionAbort signal). Ubuntu native and the bytecode image are unaffected — only the native image on arm64-darwin.

Root cause

On macOS/arm64, native (cleavir-compiled) code unwinds via compact-unwind (__TEXT,__unwind_info), not DWARF .eh_frame — clasp's native MachO objects carry no __eh_frame. ClaspJIT_O adds orc::EHFrameRegistrationPlugin to its ObjectLinkingLayer. But that plugin's MachO path inserts a front PrePrune pass that removes the __LD,__compact_unwind section:

// llvm/lib/ExecutionEngine/Orc/EHFrameRegistrationPlugin.cpp
if (LG.getTargetTriple().isOSBinFormatMachO())
  PassConfig.PrePrunePasses.insert(PassConfig.PrePrunePasses.begin(),
    [](LinkGraph &G) {
      if (auto *CUSec = G.findSectionByName(MachOCompactUnwindSectionName))
        G.removeSection(*CUSec);
      return Error::success();
    });

That deletes the only unwind info native code has, before JITLink's CompactUnwindManager can synthesize __TEXT,__unwind_info. libunwind is then left with no description for native PCs, so a C++ exception thrown below a native frame (mp::abort_processthrow AbortProcess()) and caught above it (Process_O::runInner) cannot find its handler → std::terminate → abort.

(For the record: the __compact_unwind records are valid self-sufficient UNWIND_ARM64_MODE_FRAME entries; the original "objects lack __eh_frame" framing was a red herring — they correctly lack it, and 0x04000000 is MODE_FRAME on arm64, not MODE_DWARF.)

Fix

Only add EHFrameRegistrationPlugin for non-MachO (ELF) targets. On MachO it has no eh-frame to register anyway, so its sole effect was deleting the compact-unwind. With it gone, the stock pipeline works end-to-end: CompactUnwindManager synthesizes __unwind_info and the default setUpGenericLLVMIRPlatform's UnwindInfoRegistrationPlugin registers it with Apple libunwind. LLVM's own writer handles personality/LSDA, so landing-pad functions are covered too.

Verification

Apple silicon, LLVM 22, boehmprecise native image:

  • mp regression suite: 44 successes, 0 failuresPROCESS-ABORT-1/2/3/5 and PROCESS-EXIT pass (were aborting CI).
  • mp:abort-process now unwinds across native frames and signals PROCESS-JOIN-ERROR (the documented behavior) instead of std::terminate.
  • llvm::orc::UnwindInfoManager::registerSectionsImpl now runs per loaded object (15349×; was never called before); libunwind's findSectionsImpl resolves during the throw.
  • No regressions in the unwind / conditions / control suites.

🤖 Generated with Claude Code

…pers#1782)

On macOS/arm64, native (cleavir-compiled) code unwinds via compact-unwind
(__TEXT,__unwind_info), not DWARF .eh_frame — clasp's native MachO objects
carry no __eh_frame. ClaspJIT_O adds orc::EHFrameRegistrationPlugin to its
ObjectLinkingLayer, but that plugin's MachO path inserts a front PrePrune
pass that removes the __LD,__compact_unwind section (see LLVM
EHFrameRegistrationPlugin.cpp). That deletes the only unwind info native
code has, before JITLink's CompactUnwindManager can synthesize
__TEXT,__unwind_info. libunwind is then left with no description for native
PCs, so a C++ exception thrown below a native frame and caught above it
cannot find its handler -> std::terminate -> abort. This is the
long-standing clasp/cando macos-latest/native CI failure (mp PROCESS-ABORT).

Fix: only add EHFrameRegistrationPlugin for non-MachO (ELF) targets. On
MachO it has no eh-frame to register anyway, so its sole effect was deleting
the compact-unwind. With it gone, the stock pipeline works:
CompactUnwindManager synthesizes __unwind_info and the default platform's
UnwindInfoRegistrationPlugin registers it with Apple libunwind, so C++
exceptions unwind across native frames again.

Verified on Apple silicon (LLVM 22, boehmprecise native image): the mp
regression suite passes (PROCESS-ABORT-1/2/3/5, PROCESS-EXIT);
UnwindInfoManager::registerSectionsImpl now runs per loaded object (was
never called before); no regressions in the unwind/conditions/control
suites.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@dg1sbg dg1sbg force-pushed the fix/macos-arm64-compact-unwind-1782 branch from f11b704 to d96aef4 Compare June 2, 2026 14:01
@Bike

Bike commented Jun 8, 2026

Copy link
Copy Markdown
Member

I don't have an ARM Mac to test on right this second, and the CI is still reporting an unknown exception there. Though in the interrupt tests. I'll run it again and see what happens.

@dg1sbg

dg1sbg commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

With all PRs in, I just reran all tests here locally with

clasp --norc --non-interactive --base --feature ignore-extensions --load "sys:src;lisp;regression-tests;run-all.lisp"

and get:

Unexpected Successes:
  RANDOM-SHORT RANDOM-DOUBLE RANDOM-LONG FRAME-LOCALS FRAME-FUNCTION

Expected Failures:
  SBCL-CROSS-COMPILE-4 INCLUDE-LEVEL-2B INCLUDE-LEVEL-3 TYPES-CLASSES-10

Successes: 1963

No unhandled exceptions left ...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants