[llvm] [orc-rt] Add StandaloneMachOUnwindInfoRegistrar. (PR #206669)

Lang Hames via llvm-commits llvm-commits at lists.llvm.org
Tue Jun 30 00:36:18 PDT 2026


https://github.com/lhames created https://github.com/llvm/llvm-project/pull/206669

StandaloneMachOUnwindInfoRegistrar provides methods and SPS-CI allocation actions for registering and deregistering MachO unwind-info sections (DWARF EH-frame and compact-unwind) via libunwind's find-dynamic-unwind-sections APIs.

A Registration handle returned by enable() represents the connection with libunwind; clients must keep it alive for the lifetime of their Session, and its destructor releases the registration. Concurrent registrations are reference-counted so multiple sessions can share a single underlying libunwind hook.

Registered code ranges are stored in an interval map. Overlapping ranges are rejected; lookups for an address outside any registered range return no info, so libunwind falls back to its other lookup mechanisms safely.

A future MachO-Platform will provide integrated unwind-info registration and should be preferred when available. This class will then remain appropriate for MachO executors that deliberately omit the full platform.

Unit tests cover the UnwindInfoMap storage layer (register/deregister/lookup, overlap rejection, edge cases). The libunwind-facing layer — enable(), the Registration RAII, and the find-dynamic-unwind-sections callback — is left to regression tests, which will become possible in the near future (once initial ControllerAccess implementations land).

>From 360fc39d378e295f8de1ab999fd304d06f4e2dfb Mon Sep 17 00:00:00 2001
From: Lang Hames <lhames at gmail.com>
Date: Mon, 22 Jun 2026 17:19:44 +1000
Subject: [PATCH] [orc-rt] Add StandaloneMachOUnwindInfoRegistrar.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

StandaloneMachOUnwindInfoRegistrar provides methods and SPS-CI allocation
actions for registering and deregistering MachO unwind-info sections (DWARF
EH-frame and compact-unwind) via libunwind's find-dynamic-unwind-sections
APIs.

A Registration handle returned by enable() represents the connection with
libunwind; clients must keep it alive for the lifetime of their Session, and
its destructor releases the registration. Concurrent registrations are
reference-counted so multiple sessions can share a single underlying libunwind
hook.

Registered code ranges are stored in an interval map. Overlapping ranges are
rejected; lookups for an address outside any registered range return no info,
so libunwind falls back to its other lookup mechanisms safely.

A future MachO-Platform will provide integrated unwind-info registration and
should be preferred when available. This class will then remain appropriate for
MachO executors that deliberately omit the full platform.

Unit tests cover the UnwindInfoMap storage layer (register/deregister/lookup,
overlap rejection, edge cases). The libunwind-facing layer — enable(), the
Registration RAII, and the find-dynamic-unwind-sections callback — is left to
regression tests, which will become possible in the near future (once initial
ControllerAccess implementations land).
---
 orc-rt/include/CMakeLists.txt                 |   1 +
 .../StandaloneMachOUnwindInfoRegistrar.h      | 132 ++++++++++
 .../StandaloneMachOUnwindInfoRegistrarSPSCI.h |  27 ++
 orc-rt/lib/executor/CMakeLists.txt            |   2 +
 .../StandaloneMachOUnwindInfoRegistrar.cpp    | 234 ++++++++++++++++++
 ...tandaloneMachOUnwindInfoRegistrarSPSCI.cpp |  43 ++++
 orc-rt/unittests/CMakeLists.txt               |   1 +
 ...StandaloneMachOUnwindInfoRegistrarTest.cpp | 188 ++++++++++++++
 8 files changed, 628 insertions(+)
 create mode 100644 orc-rt/include/orc-rt/StandaloneMachOUnwindInfoRegistrar.h
 create mode 100644 orc-rt/include/orc-rt/sps-ci/StandaloneMachOUnwindInfoRegistrarSPSCI.h
 create mode 100644 orc-rt/lib/executor/StandaloneMachOUnwindInfoRegistrar.cpp
 create mode 100644 orc-rt/lib/executor/sps-ci/StandaloneMachOUnwindInfoRegistrarSPSCI.cpp
 create mode 100644 orc-rt/unittests/StandaloneMachOUnwindInfoRegistrarTest.cpp

diff --git a/orc-rt/include/CMakeLists.txt b/orc-rt/include/CMakeLists.txt
index 8ee4dfb075270..cfbba1e644fe2 100644
--- a/orc-rt/include/CMakeLists.txt
+++ b/orc-rt/include/CMakeLists.txt
@@ -26,6 +26,7 @@ set(ORC_RT_HEADERS
     orc-rt/SimpleNativeMemoryMap.h
     orc-rt/SimplePackedSerialization.h
     orc-rt/SPSAllocAction.h
+    orc-rt/StandaloneMachOUnwindInfoRegistrar.h
     orc-rt/sps-ci/AllSPSCI.h
     orc-rt/sps-ci/CallSPSCI.h
     orc-rt/sps-ci/MemoryAccessSPSCI.h
diff --git a/orc-rt/include/orc-rt/StandaloneMachOUnwindInfoRegistrar.h b/orc-rt/include/orc-rt/StandaloneMachOUnwindInfoRegistrar.h
new file mode 100644
index 0000000000000..bc7a58f96a701
--- /dev/null
+++ b/orc-rt/include/orc-rt/StandaloneMachOUnwindInfoRegistrar.h
@@ -0,0 +1,132 @@
+//===----------- StandaloneMachOUnwindInfoRegistrar.h -----------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Standalone registration service for MachO unwind info via libunwind's
+// unw_find_dynamic_unwind_sections mechanism.
+//
+// Note: Should not be used together with MachO-Platform, which provides its
+// own unwind-info registration.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef ORC_RT_STANDALONEMACHOUNWINDINFOREGISTRAR_H
+#define ORC_RT_STANDALONEMACHOUNWINDINFOREGISTRAR_H
+
+#include "orc-rt/Error.h"
+#include "orc-rt/ExecutorAddress.h"
+#include "orc-rt/SimpleSymbolTable.h"
+#include "orc-rt/move_only_function.h"
+#include "orc-rt/sps-ci/StandaloneMachOUnwindInfoRegistrarSPSCI.h"
+
+#include <cstdint>
+#include <map>
+#include <mutex>
+#include <optional>
+#include <vector>
+
+namespace orc_rt {
+
+/// Standalone MachO unwind-info registration via libunwind's
+/// unw_find_dynamic_unwind_sections mechanism.
+///
+/// MachO-Platform provides unwind-info registration as part of its broader
+/// MachO support, and should be preferred. Use this class only for MachO
+/// executors that deliberately omit the full platform.
+class StandaloneMachOUnwindInfoRegistrar {
+  // Unit-test access to the private UnwindInfoMap. The fixture is defined in
+  // orc-rt/unittests/StandaloneMachOUnwindInfoRegistrarTest.cpp.
+  friend class UnwindInfoMapTest;
+
+public:
+  /// Represents a registration with libunwind that will enable registration
+  /// of unwind info. Destruction will trigger a release of the registration,
+  /// so clients must keep this alive throughout the lifetime of their
+  /// Session.
+  class Registration {
+    friend class StandaloneMachOUnwindInfoRegistrar;
+
+  public:
+    Registration() = default;
+    Registration(Registration &&Other);
+    Registration &operator=(Registration &&Other);
+    ~Registration();
+
+  private:
+    Registration(bool Active);
+    bool Active = false;
+  };
+
+  static Expected<Registration>
+  enable(SimpleSymbolTable &ST,
+         SimpleSymbolTable::MutatorFn AddInterface =
+             sps_ci::addStandaloneMachOUnwindInfoRegistrar);
+
+  static Error registerSections(std::vector<ExecutorAddrRange> CodeRanges,
+                                ExecutorAddr DSOBase,
+                                ExecutorAddrRange DWARFEHFrame,
+                                ExecutorAddrRange CompactUnwind);
+  static Error deregisterSections(std::vector<ExecutorAddrRange> CodeRanges);
+
+private:
+  /// Describes the unwind-info sections associated with one or more code
+  /// ranges. The struct layout is identical to libunwind's
+  /// unw_dynamic_unwind_sections class so that the libunwind callback can
+  /// populate its caller with a direct assignment.
+  ///
+  /// This struct must be kept in sync with libunwind's
+  /// unw_dynamic_unwind_sections; if the two ever drift, the libunwind
+  /// callback will silently corrupt unwind info.
+  struct DynamicUnwindSections {
+    uintptr_t DSOBase;
+    uintptr_t DWARFSection;
+    size_t DWARFSectionLength;
+    uintptr_t CompactUnwindSection;
+    size_t CompactUnwindSectionLength;
+  };
+
+  /// Interval map from code-address ranges to unwind-info section
+  /// descriptors. Implementation detail; befriended for unit testing only.
+  class UnwindInfoMap {
+  public:
+    /// Register the given code ranges with the given section info. Empty
+    /// ranges are silently ignored. Overlapping ranges (with each other or
+    /// with already-registered ranges) cause the call to fail; earlier
+    /// successful inserts in this call remain registered.
+    Error registerRanges(const std::vector<ExecutorAddrRange> &CodeRanges,
+                         const DynamicUnwindSections &Info);
+
+    /// Deregister the given code ranges. Returns an error if any range isn't
+    /// found; earlier successful erasures in the failing call are not rolled
+    /// back.
+    Error deregisterRanges(const std::vector<ExecutorAddrRange> &CodeRanges);
+
+    /// Look up DynamicUnwindSections for an address. Returns std::nullopt if
+    /// no registered range contains the address.
+    std::optional<DynamicUnwindSections> lookup(uintptr_t Addr) const;
+
+  private:
+    struct Entry {
+      DynamicUnwindSections Info;
+      uintptr_t End;
+    };
+
+    mutable std::mutex M;
+    std::map<uintptr_t, Entry> Ranges;
+  };
+
+  // libunwind plumbing -- defined in StandaloneMachOUnwindInfoRegistrar.cpp.
+  static UnwindInfoMap &unwindInfoMap();
+  static int findUnwindInfoSections(uintptr_t Addr,
+                                    DynamicUnwindSections *Info);
+  static Error registerWithLibunwind();
+  static void deregisterWithLibunwind();
+};
+
+} // namespace orc_rt
+
+#endif // ORC_RT_STANDALONEMACHOUNWINDINFOREGISTRAR_H
diff --git a/orc-rt/include/orc-rt/sps-ci/StandaloneMachOUnwindInfoRegistrarSPSCI.h b/orc-rt/include/orc-rt/sps-ci/StandaloneMachOUnwindInfoRegistrarSPSCI.h
new file mode 100644
index 0000000000000..f3059f3d17e0c
--- /dev/null
+++ b/orc-rt/include/orc-rt/sps-ci/StandaloneMachOUnwindInfoRegistrarSPSCI.h
@@ -0,0 +1,27 @@
+//===--------- StandaloneMachOUnwindInfoRegistrarSPSCI.h --------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// SPS Controller Interface registration for
+// StandaloneMachOUnwindInfoRegistrar.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef ORC_RT_SPS_CI_STANDALONEMACHOUNWINDINFOREGISTRARSPSCI_H
+#define ORC_RT_SPS_CI_STANDALONEMACHOUNWINDINFOREGISTRARSPSCI_H
+
+#include "orc-rt/SimpleSymbolTable.h"
+
+namespace orc_rt::sps_ci {
+
+/// Add the StandaloneMachOUnwindInfoRegistrar SPS interface to the
+/// controller interface.
+Error addStandaloneMachOUnwindInfoRegistrar(SimpleSymbolTable &ST);
+
+} // namespace orc_rt::sps_ci
+
+#endif // ORC_RT_SPS_CI_STANDALONEMACHOUNWINDINFOREGISTRARSPSCI_H
diff --git a/orc-rt/lib/executor/CMakeLists.txt b/orc-rt/lib/executor/CMakeLists.txt
index ebf7b5663f3a2..dddd86f8c0d89 100644
--- a/orc-rt/lib/executor/CMakeLists.txt
+++ b/orc-rt/lib/executor/CMakeLists.txt
@@ -10,12 +10,14 @@ set(files
   Service.cpp
   Session.cpp
   SimpleNativeMemoryMap.cpp
+  StandaloneMachOUnwindInfoRegistrar.cpp
   ThreadPoolRunner.cpp
   sps-ci/AllSPSCI.cpp
   sps-ci/CallSPSCI.cpp
   sps-ci/MemoryAccessSPSCI.cpp
   sps-ci/NativeDylibManagerSPSCI.cpp
   sps-ci/SimpleNativeMemoryMapSPSCI.cpp
+  sps-ci/StandaloneMachOUnwindInfoRegistrarSPSCI.cpp
   )
 
 add_library(orc-rt-executor STATIC ${files})
diff --git a/orc-rt/lib/executor/StandaloneMachOUnwindInfoRegistrar.cpp b/orc-rt/lib/executor/StandaloneMachOUnwindInfoRegistrar.cpp
new file mode 100644
index 0000000000000..d27541f1b122e
--- /dev/null
+++ b/orc-rt/lib/executor/StandaloneMachOUnwindInfoRegistrar.cpp
@@ -0,0 +1,234 @@
+//===- StandaloneMachOUnwindInfoRegistrar.cpp -----------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Standalone registration service for MachO unwind info.
+//
+// Note: Should not be used together with MachO-Platform, which provides its
+// own unwind-info registration.
+//
+//===----------------------------------------------------------------------===//
+
+#include "orc-rt/StandaloneMachOUnwindInfoRegistrar.h"
+#include "orc-rt/Compiler.h"
+
+#include <mutex>
+
+// --- From libunwind/src/libunwind_ext.h ---
+struct unw_dynamic_unwind_sections;
+
+using namespace orc_rt;
+
+namespace {
+
+typedef int (*unw_find_dynamic_unwind_sections)(
+    uintptr_t addr, struct unw_dynamic_unwind_sections *info);
+
+extern "C" int __unw_add_find_dynamic_unwind_sections(
+    unw_find_dynamic_unwind_sections find_dynamic_unwind_sections)
+    ORC_RT_WEAK_IMPORT;
+
+extern "C" int __unw_remove_find_dynamic_unwind_sections(
+    unw_find_dynamic_unwind_sections find_dynamic_unwind_sections)
+    ORC_RT_WEAK_IMPORT;
+// --- end libunwind decls ---
+
+std::mutex LibunwindRegistrationMtx;
+size_t LibunwindRegistrationCount;
+
+} // anonymous namespace
+
+namespace orc_rt {
+
+// === UnwindInfoMap method definitions ===
+
+Error StandaloneMachOUnwindInfoRegistrar::UnwindInfoMap::registerRanges(
+    const std::vector<ExecutorAddrRange> &CodeRanges,
+    const DynamicUnwindSections &Info) {
+  std::scoped_lock<std::mutex> Lock(M);
+  for (auto &CodeRange : CodeRanges) {
+    // TODO: We're assuming safe conversion from ExecutorAddr to uintptr_t
+    //       here. In the future we should either check, or make sure that
+    //       invalid values aren't deserializable (by switching the wire
+    //       representation to match the pointer size).
+    if (CodeRange.Start.getValue() > std::numeric_limits<uintptr_t>::max() ||
+        CodeRange.End.getValue() > std::numeric_limits<uintptr_t>::max())
+      return make_error<StringError>(
+          "Invalid code-range for unwind-info registration");
+
+    uintptr_t Start = CodeRange.Start.getValue();
+    uintptr_t End = CodeRange.End.getValue();
+
+    // Ignore empty ranges.
+    // FIXME: Should we hard-error on these instead?
+    if (Start == End)
+      continue;
+
+    // Check for overlap with neighboring ranges (including any added earlier
+    // in this call, since each successful insertion is visible to subsequent
+    // iterations).
+    auto MakeRangeOverlapError = [] {
+      return make_error<StringError>(
+          "Code-range for unwind-info registration overlaps an existing range");
+    };
+    auto I = Ranges.upper_bound(Start);
+    if (I != Ranges.end() && I->first < End)
+      return MakeRangeOverlapError();
+
+    if (I != Ranges.begin()) {
+      auto PrevI = std::prev(I);
+      if (PrevI->second.End > Start)
+        return MakeRangeOverlapError();
+    }
+
+    Ranges.emplace_hint(I, Start, Entry{Info, End});
+  }
+
+  return Error::success();
+}
+
+Error StandaloneMachOUnwindInfoRegistrar::UnwindInfoMap::deregisterRanges(
+    const std::vector<ExecutorAddrRange> &CodeRanges) {
+  std::scoped_lock<std::mutex> Lock(M);
+  for (auto &CodeRange : CodeRanges) {
+    auto I = Ranges.find(CodeRange.Start.getValue());
+    if (I == Ranges.end())
+      return make_error<StringError>(
+          "No unwind-info sections registered for range");
+    Ranges.erase(I);
+  }
+  return Error::success();
+}
+
+std::optional<StandaloneMachOUnwindInfoRegistrar::DynamicUnwindSections>
+StandaloneMachOUnwindInfoRegistrar::UnwindInfoMap::lookup(
+    uintptr_t Addr) const {
+  std::scoped_lock<std::mutex> Lock(M);
+  auto I = Ranges.upper_bound(Addr);
+  if (I == Ranges.begin())
+    return std::nullopt;
+  --I;
+  if (Addr >= I->second.End)
+    return std::nullopt;
+  return I->second.Info;
+}
+
+// === Registrar libunwind plumbing ===
+
+StandaloneMachOUnwindInfoRegistrar::UnwindInfoMap &
+StandaloneMachOUnwindInfoRegistrar::unwindInfoMap() {
+  static UnwindInfoMap Map;
+  return Map;
+}
+
+int StandaloneMachOUnwindInfoRegistrar::findUnwindInfoSections(
+    uintptr_t Addr, DynamicUnwindSections *Info) {
+  auto S = unwindInfoMap().lookup(Addr);
+  if (!S)
+    return 0;
+  *Info = *S;
+  return 1;
+}
+
+Error StandaloneMachOUnwindInfoRegistrar::registerWithLibunwind() {
+  // Check whether the necessary libunwind APIs are available, as they were
+  // only introduced in macOS 15.
+  if (!__unw_add_find_dynamic_unwind_sections ||
+      !__unw_remove_find_dynamic_unwind_sections)
+    return make_error<StringError>(
+        "libunwind unwind-info registration APIs not available");
+
+  std::scoped_lock<std::mutex> Lock(LibunwindRegistrationMtx);
+
+  // If we've already registered then just bump the ref count and exit.
+  if (LibunwindRegistrationCount > 0) {
+    ++LibunwindRegistrationCount;
+    return Error::success();
+  }
+
+  // Try to register.
+  // findUnwindInfoSections has the same call ABI as libunwind expects, since
+  // DynamicUnwindSections is laid out identically to
+  // unw_dynamic_unwind_sections, so the reinterpret_cast here should be safe
+  // in practice.
+  if (__unw_add_find_dynamic_unwind_sections(
+          reinterpret_cast<unw_find_dynamic_unwind_sections>(
+              findUnwindInfoSections)) != 0)
+    return make_error<StringError>(
+        "libunwind find-dynamic-unwind-sections registration failed");
+
+  // If we succeeded, bump the ref count.
+  ++LibunwindRegistrationCount;
+  return Error::success();
+}
+
+void StandaloneMachOUnwindInfoRegistrar::deregisterWithLibunwind() {
+  std::scoped_lock<std::mutex> Lock(LibunwindRegistrationMtx);
+  if (--LibunwindRegistrationCount == 0)
+    __unw_remove_find_dynamic_unwind_sections(
+        reinterpret_cast<unw_find_dynamic_unwind_sections>(
+            findUnwindInfoSections));
+}
+
+// === Public API ===
+
+StandaloneMachOUnwindInfoRegistrar::Registration::Registration(bool Active)
+    : Active(Active) {}
+
+StandaloneMachOUnwindInfoRegistrar::Registration::Registration(
+    Registration &&Other)
+    : Active(Other.Active) {
+  Other.Active = false;
+}
+
+StandaloneMachOUnwindInfoRegistrar::Registration &
+StandaloneMachOUnwindInfoRegistrar::Registration::operator=(
+    Registration &&Other) {
+  if (Active)
+    deregisterWithLibunwind();
+  Active = Other.Active;
+  Other.Active = false;
+  return *this;
+}
+
+StandaloneMachOUnwindInfoRegistrar::Registration::~Registration() {
+  if (Active)
+    deregisterWithLibunwind();
+}
+
+Expected<StandaloneMachOUnwindInfoRegistrar::Registration>
+StandaloneMachOUnwindInfoRegistrar::enable(
+    SimpleSymbolTable &ST, SimpleSymbolTable::MutatorFn AddInterface) {
+
+  if (auto Err = registerWithLibunwind())
+    return std::move(Err);
+
+  Registration R(true); // Create an active registration object.
+
+  if (auto Err = AddInterface(ST))
+    return std::move(Err);
+
+  return std::move(R);
+}
+
+Error StandaloneMachOUnwindInfoRegistrar::registerSections(
+    std::vector<ExecutorAddrRange> CodeRanges, ExecutorAddr DSOBase,
+    ExecutorAddrRange DWARFEHFrame, ExecutorAddrRange CompactUnwind) {
+  DynamicUnwindSections Info{
+      DSOBase.getValue(),   DWARFEHFrame.Start.getValue(),
+      DWARFEHFrame.size(),  CompactUnwind.Start.getValue(),
+      CompactUnwind.size(),
+  };
+  return unwindInfoMap().registerRanges(CodeRanges, Info);
+}
+
+Error StandaloneMachOUnwindInfoRegistrar::deregisterSections(
+    std::vector<ExecutorAddrRange> CodeRanges) {
+  return unwindInfoMap().deregisterRanges(CodeRanges);
+}
+
+} // namespace orc_rt
diff --git a/orc-rt/lib/executor/sps-ci/StandaloneMachOUnwindInfoRegistrarSPSCI.cpp b/orc-rt/lib/executor/sps-ci/StandaloneMachOUnwindInfoRegistrarSPSCI.cpp
new file mode 100644
index 0000000000000..c6c19c9e2e057
--- /dev/null
+++ b/orc-rt/lib/executor/sps-ci/StandaloneMachOUnwindInfoRegistrarSPSCI.cpp
@@ -0,0 +1,43 @@
+//===- StandaloneMachOUnwindInfoRegistrarSPSCI.cpp ------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// SPS Controller Interface implementation for
+// StandaloneMachOUnwindInfoRegistrar.
+//
+//===----------------------------------------------------------------------===//
+
+#include "orc-rt/sps-ci/StandaloneMachOUnwindInfoRegistrarSPSCI.h"
+#include "orc-rt/SPSAllocAction.h"
+#include "orc-rt/StandaloneMachOUnwindInfoRegistrar.h"
+
+namespace orc_rt::sps_ci {
+
+ORC_RT_SPS_ALLOC_ACTION(
+    orc_rt_ci_aa_sps_MachOUnwindInfoRegistrar_registerSections,
+    (SPSSequence<SPSExecutorAddrRange>, SPSExecutorAddr, SPSExecutorAddrRange,
+     SPSExecutorAddrRange),
+    &StandaloneMachOUnwindInfoRegistrar::registerSections)
+
+ORC_RT_SPS_ALLOC_ACTION(
+    orc_rt_ci_aa_sps_MachOUnwindInfoRegistrar_deregisterSections,
+    (SPSSequence<SPSExecutorAddrRange>),
+    &StandaloneMachOUnwindInfoRegistrar::deregisterSections)
+
+static std::pair<const char *, const void *>
+    orc_rt_ci_StandaloneMachOUnwindInfoRegistrar_sps_interface[] = {
+        ORC_RT_SYMTAB_PAIR(
+            orc_rt_ci_aa_sps_MachOUnwindInfoRegistrar_registerSections),
+        ORC_RT_SYMTAB_PAIR(
+            orc_rt_ci_aa_sps_MachOUnwindInfoRegistrar_deregisterSections)};
+
+Error addStandaloneMachOUnwindInfoRegistrar(SimpleSymbolTable &ST) {
+  return ST.addUnique(
+      orc_rt_ci_StandaloneMachOUnwindInfoRegistrar_sps_interface);
+}
+
+} // namespace orc_rt::sps_ci
diff --git a/orc-rt/unittests/CMakeLists.txt b/orc-rt/unittests/CMakeLists.txt
index f7b67e283aeb0..36f9757d89a08 100644
--- a/orc-rt/unittests/CMakeLists.txt
+++ b/orc-rt/unittests/CMakeLists.txt
@@ -44,6 +44,7 @@ add_orc_rt_unittest(CoreTests
   SPSMemoryFlagsTest.cpp
   SPSWrapperFunctionTest.cpp
   SPSWrapperFunctionBufferTest.cpp
+  StandaloneMachOUnwindInfoRegistrarTest.cpp
   TaskGroupTest.cpp
   ThreadPoolRunnerTest.cpp
   WrapperFunctionBufferTest.cpp
diff --git a/orc-rt/unittests/StandaloneMachOUnwindInfoRegistrarTest.cpp b/orc-rt/unittests/StandaloneMachOUnwindInfoRegistrarTest.cpp
new file mode 100644
index 0000000000000..8fabc6df096b2
--- /dev/null
+++ b/orc-rt/unittests/StandaloneMachOUnwindInfoRegistrarTest.cpp
@@ -0,0 +1,188 @@
+//===- StandaloneMachOUnwindInfoRegistrarTest.cpp -------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Tests for the storage layer underlying StandaloneMachOUnwindInfoRegistrar
+// (its private UnwindInfoMap inner class). Exercises the
+// register/deregister/lookup API without any libunwind interaction;
+// libunwind-facing behavior is left to regression tests.
+//
+//===----------------------------------------------------------------------===//
+
+#include "orc-rt/StandaloneMachOUnwindInfoRegistrar.h"
+
+#include "gtest/gtest.h"
+
+using namespace orc_rt;
+
+namespace {
+
+// Helper: build a half-open code-address range from raw integer values.
+ExecutorAddrRange range(uint64_t Start, uint64_t End) {
+  return ExecutorAddrRange(ExecutorAddr(Start), ExecutorAddr(End));
+}
+
+} // namespace
+
+namespace orc_rt {
+// Fixture: befriended by StandaloneMachOUnwindInfoRegistrar so tests can name
+// the otherwise-private UnwindInfoMap inner class and DynamicUnwindSections
+// struct. Lives in orc_rt to match the unqualified friend declaration in the
+// registrar header.
+class UnwindInfoMapTest : public ::testing::Test {
+protected:
+  using UnwindInfoMap = StandaloneMachOUnwindInfoRegistrar::UnwindInfoMap;
+  using DynamicUnwindSections =
+      StandaloneMachOUnwindInfoRegistrar::DynamicUnwindSections;
+
+  // An arbitrary, recognisable DynamicUnwindSections for use in tests that
+  // don't care about the exact field values.
+  static DynamicUnwindSections sampleInfo() {
+    return {0x1000, 0x2000, 64, 0x3000, 32};
+  }
+};
+} // namespace orc_rt
+
+TEST_F(UnwindInfoMapTest, RegisterAndDeregisterSucceeds) {
+  UnwindInfoMap Map;
+  cantFail(Map.registerRanges({range(0x100, 0x200)}, sampleInfo()));
+  cantFail(Map.deregisterRanges({range(0x100, 0x200)}));
+}
+
+TEST_F(UnwindInfoMapTest, DeregisterUnregisteredFails) {
+  UnwindInfoMap Map;
+  auto E = Map.deregisterRanges({range(0x100, 0x200)});
+  ASSERT_TRUE(static_cast<bool>(E));
+  EXPECT_EQ(toString(std::move(E)),
+            "No unwind-info sections registered for range");
+}
+
+TEST_F(UnwindInfoMapTest, OverlappingRegistrationRejected) {
+  // [0x100, 0x300) then [0x200, 0x400): second starts inside the first.
+  UnwindInfoMap Map;
+  cantFail(Map.registerRanges({range(0x100, 0x300)}, sampleInfo()));
+
+  auto E = Map.registerRanges({range(0x200, 0x400)}, sampleInfo());
+  ASSERT_TRUE(static_cast<bool>(E));
+  EXPECT_EQ(toString(std::move(E)),
+            "Code-range for unwind-info registration overlaps an existing "
+            "range");
+}
+
+TEST_F(UnwindInfoMapTest, ContainedRegistrationRejected) {
+  // [0x100, 0x400) then [0x200, 0x300): second sits entirely inside the first.
+  UnwindInfoMap Map;
+  cantFail(Map.registerRanges({range(0x100, 0x400)}, sampleInfo()));
+
+  auto E = Map.registerRanges({range(0x200, 0x300)}, sampleInfo());
+  ASSERT_TRUE(static_cast<bool>(E));
+  EXPECT_EQ(toString(std::move(E)),
+            "Code-range for unwind-info registration overlaps an existing "
+            "range");
+}
+
+TEST_F(UnwindInfoMapTest, ExactDuplicateRegistrationRejected) {
+  UnwindInfoMap Map;
+  cantFail(Map.registerRanges({range(0x100, 0x200)}, sampleInfo()));
+
+  auto E = Map.registerRanges({range(0x100, 0x200)}, sampleInfo());
+  ASSERT_TRUE(static_cast<bool>(E));
+  EXPECT_EQ(toString(std::move(E)),
+            "Code-range for unwind-info registration overlaps an existing "
+            "range");
+}
+
+TEST_F(UnwindInfoMapTest, EmptyRangeIgnored) {
+  // Registering an empty range should succeed but produce no entry, so a
+  // subsequent deregister of the same range fails.
+  UnwindInfoMap Map;
+  cantFail(Map.registerRanges({range(0x100, 0x100)}, sampleInfo()));
+
+  auto E = Map.deregisterRanges({range(0x100, 0x100)});
+  ASSERT_TRUE(static_cast<bool>(E));
+  EXPECT_EQ(toString(std::move(E)),
+            "No unwind-info sections registered for range");
+}
+
+TEST_F(UnwindInfoMapTest, AdjacentRangesAccepted) {
+  // [0x100, 0x200) and [0x200, 0x300) touch at the boundary but don't
+  // overlap.
+  UnwindInfoMap Map;
+  cantFail(Map.registerRanges({range(0x100, 0x200)}, sampleInfo()));
+  cantFail(Map.registerRanges({range(0x200, 0x300)}, sampleInfo()));
+}
+
+TEST_F(UnwindInfoMapTest, PartialFailureLeavesEarlierRangesRegistered) {
+  // Multi-range registerRanges call where the second range overlaps an
+  // already-registered range. The first range in the failing call should
+  // remain registered, and the pre-existing range is untouched.
+  UnwindInfoMap Map;
+  cantFail(Map.registerRanges({range(0x100, 0x200)}, sampleInfo()));
+
+  auto E = Map.registerRanges({range(0x300, 0x400), range(0x150, 0x250)},
+                              sampleInfo());
+  ASSERT_TRUE(static_cast<bool>(E));
+  consumeError(std::move(E));
+
+  cantFail(Map.deregisterRanges({range(0x300, 0x400)}));
+  cantFail(Map.deregisterRanges({range(0x100, 0x200)}));
+}
+
+TEST_F(UnwindInfoMapTest, LookupInsideRegisteredRange) {
+  UnwindInfoMap Map;
+  DynamicUnwindSections Info{0x1000, 0x2000, 64, 0x3000, 32};
+  cantFail(Map.registerRanges({range(0x100, 0x200)}, Info));
+
+  // Lookups at Start, midway, and just-below-End should all hit.
+  for (uintptr_t Addr :
+       {uintptr_t{0x100}, uintptr_t{0x180}, uintptr_t{0x1FF}}) {
+    auto R = Map.lookup(Addr);
+    ASSERT_TRUE(R.has_value()) << "Expected lookup to hit at " << Addr;
+    EXPECT_EQ(R->DSOBase, 0x1000u);
+    EXPECT_EQ(R->DWARFSection, 0x2000u);
+    EXPECT_EQ(R->DWARFSectionLength, 64u);
+    EXPECT_EQ(R->CompactUnwindSection, 0x3000u);
+    EXPECT_EQ(R->CompactUnwindSectionLength, 32u);
+  }
+}
+
+TEST_F(UnwindInfoMapTest, LookupOutsideRegisteredRangeReturnsNullopt) {
+  UnwindInfoMap Map;
+  cantFail(Map.registerRanges({range(0x100, 0x200)}, sampleInfo()));
+
+  // Below any registered range.
+  EXPECT_FALSE(Map.lookup(0x0).has_value());
+  EXPECT_FALSE(Map.lookup(0xFF).has_value());
+
+  // At End (half-open: End is not part of the range).
+  EXPECT_FALSE(Map.lookup(0x200).has_value());
+
+  // Above any registered range.
+  EXPECT_FALSE(Map.lookup(0x1000).has_value());
+}
+
+TEST_F(UnwindInfoMapTest, LookupOnEmptyMapReturnsNullopt) {
+  UnwindInfoMap Map;
+  EXPECT_FALSE(Map.lookup(0).has_value());
+  EXPECT_FALSE(Map.lookup(0x1000).has_value());
+}
+
+TEST_F(UnwindInfoMapTest, LookupBetweenRegisteredRangesReturnsNullopt) {
+  // Two non-adjacent ranges; lookup in the gap must miss rather than return
+  // the lower range (regression guard for the upper_bound - 1 logic).
+  UnwindInfoMap Map;
+  cantFail(Map.registerRanges({range(0x100, 0x200)}, sampleInfo()));
+  cantFail(Map.registerRanges({range(0x300, 0x400)}, sampleInfo()));
+
+  EXPECT_FALSE(Map.lookup(0x200).has_value());
+  EXPECT_FALSE(Map.lookup(0x250).has_value());
+  EXPECT_FALSE(Map.lookup(0x2FF).has_value());
+
+  // And the second range is reachable.
+  ASSERT_TRUE(Map.lookup(0x300).has_value());
+  ASSERT_TRUE(Map.lookup(0x3FF).has_value());
+}



More information about the llvm-commits mailing list