[llvm] 4b0d422 - [ORC] Support scanning "fallback" slices for interfaces. (#168472)

via llvm-commits llvm-commits at lists.llvm.org
Mon Nov 17 18:54:06 PST 2025


Author: Lang Hames
Date: 2025-11-18T13:54:02+11:00
New Revision: 4b0d42275382e4cff2c30070efd5625dae27e330

URL: https://github.com/llvm/llvm-project/commit/4b0d42275382e4cff2c30070efd5625dae27e330
DIFF: https://github.com/llvm/llvm-project/commit/4b0d42275382e4cff2c30070efd5625dae27e330.diff

LOG: [ORC] Support scanning "fallback" slices for interfaces. (#168472)

When scanning an interface source (dylib or TBD file), consider
"fallback" architectures (CPUType / CPUSubType pairs) in addition to the
process's CPUType / CPUSubType.

Background:

When dyld loads a dylib into a process it may load dylib or slice whose
CPU type / subtype isn't an exact match for the process's CPU type /
subtype. E.g. arm64 processes can load arm64e dylibs / slices.

When building an interface we need to follow the same logic, otherwise
we risk generating a spurious "does not contain a compatible slice"
error. E.g. If we're running an arm64 JIT'd program and loading an
interface from a TBD file, and if no arm64 slice is present in that
file, then we should fall back to looking for an arm64e slice.

rdar://164510783

Added: 
    llvm/test/ExecutionEngine/JITLink/AArch64/Inputs/MachO_Foo_arm64.tbd
    llvm/test/ExecutionEngine/JITLink/AArch64/Inputs/MachO_Foo_arm64e.tbd

Modified: 
    llvm/include/llvm/ExecutionEngine/Orc/MachO.h
    llvm/lib/ExecutionEngine/Orc/MachO.cpp
    llvm/test/ExecutionEngine/JITLink/AArch64/MachO_weak_link.test

Removed: 
    llvm/test/ExecutionEngine/JITLink/AArch64/Inputs/MachO_Foo.tbd


################################################################################
diff  --git a/llvm/include/llvm/ExecutionEngine/Orc/MachO.h b/llvm/include/llvm/ExecutionEngine/Orc/MachO.h
index 049595c330f5c..0e789b5e05a75 100644
--- a/llvm/include/llvm/ExecutionEngine/Orc/MachO.h
+++ b/llvm/include/llvm/ExecutionEngine/Orc/MachO.h
@@ -95,21 +95,36 @@ class ForceLoadMachOArchiveMembers {
   bool ObjCOnly;
 };
 
+using GetFallbackArchsFn =
+    unique_function<SmallVector<std::pair<uint32_t, uint32_t>>(
+        uint32_t CPUType, uint32_t CPUSubType)>;
+
+/// Match the exact CPU type/subtype only.
+LLVM_ABI SmallVector<std::pair<uint32_t, uint32_t>>
+noFallbackArchs(uint32_t CPUType, uint32_t CPUSubType);
+
+/// Match standard dynamic loader fallback rules.
+LLVM_ABI SmallVector<std::pair<uint32_t, uint32_t>>
+standardMachOFallbackArchs(uint32_t CPUType, uint32_t CPUSubType);
+
 /// Returns a SymbolNameSet containing the exported symbols defined in the
 /// given dylib.
-LLVM_ABI Expected<SymbolNameSet>
-getDylibInterfaceFromDylib(ExecutionSession &ES, Twine Path);
+LLVM_ABI Expected<SymbolNameSet> getDylibInterfaceFromDylib(
+    ExecutionSession &ES, Twine Path,
+    GetFallbackArchsFn GetFallbackArchs = standardMachOFallbackArchs);
 
 /// Returns a SymbolNameSet containing the exported symbols defined in the
 /// relevant slice of the TapiUniversal file.
-LLVM_ABI Expected<SymbolNameSet>
-getDylibInterfaceFromTapiFile(ExecutionSession &ES, Twine Path);
+LLVM_ABI Expected<SymbolNameSet> getDylibInterfaceFromTapiFile(
+    ExecutionSession &ES, Twine Path,
+    GetFallbackArchsFn GetFallbackArchs = standardMachOFallbackArchs);
 
 /// Returns a SymbolNameSet containing the exported symbols defined in the
 /// relevant slice of the given file, which may be either a dylib or a tapi
 /// file.
-LLVM_ABI Expected<SymbolNameSet> getDylibInterface(ExecutionSession &ES,
-                                                   Twine Path);
+LLVM_ABI Expected<SymbolNameSet> getDylibInterface(
+    ExecutionSession &ES, Twine Path,
+    GetFallbackArchsFn GetFallbackArchs = standardMachOFallbackArchs);
 
 } // namespace orc
 } // namespace llvm

diff  --git a/llvm/lib/ExecutionEngine/Orc/MachO.cpp b/llvm/lib/ExecutionEngine/Orc/MachO.cpp
index 14d1c843bf6e4..731d24d1272d4 100644
--- a/llvm/lib/ExecutionEngine/Orc/MachO.cpp
+++ b/llvm/lib/ExecutionEngine/Orc/MachO.cpp
@@ -282,15 +282,48 @@ Expected<bool> ForceLoadMachOArchiveMembers::operator()(
   return true;
 }
 
-Expected<SymbolNameSet> getDylibInterfaceFromDylib(ExecutionSession &ES,
-                                                   Twine Path) {
-  auto CPUType = MachO::getCPUType(ES.getTargetTriple());
-  if (!CPUType)
-    return CPUType.takeError();
+LLVM_ABI SmallVector<std::pair<uint32_t, uint32_t>>
+noFallbackArchs(uint32_t CPUType, uint32_t CPUSubType) {
+  SmallVector<std::pair<uint32_t, uint32_t>> Result;
+  Result.push_back({CPUType, CPUSubType});
+  return Result;
+}
+
+SmallVector<std::pair<uint32_t, uint32_t>>
+standardMachOFallbackArchs(uint32_t CPUType, uint32_t CPUSubType) {
+  SmallVector<std::pair<uint32_t, uint32_t>> Archs;
+
+  // Match given CPU type/subtype first.
+  Archs.push_back({CPUType, CPUSubType});
+
+  switch (CPUType) {
+  case MachO::CPU_TYPE_ARM64:
+    // Handle arm64 variants.
+    switch (CPUSubType) {
+    case MachO::CPU_SUBTYPE_ARM64_ALL:
+      Archs.push_back({CPUType, MachO::CPU_SUBTYPE_ARM64E});
+      break;
+    default:
+      break;
+    }
+    break;
+  default:
+    break;
+  }
+
+  return Archs;
+}
+
+Expected<SymbolNameSet>
+getDylibInterfaceFromDylib(ExecutionSession &ES, Twine Path,
+                           GetFallbackArchsFn GetFallbackArchs) {
+  auto InitCPUType = MachO::getCPUType(ES.getTargetTriple());
+  if (!InitCPUType)
+    return InitCPUType.takeError();
 
-  auto CPUSubType = MachO::getCPUSubType(ES.getTargetTriple());
-  if (!CPUSubType)
-    return CPUSubType.takeError();
+  auto InitCPUSubType = MachO::getCPUSubType(ES.getTargetTriple());
+  if (!InitCPUSubType)
+    return InitCPUSubType.takeError();
 
   auto Buf = MemoryBuffer::getFile(Path);
   if (!Buf)
@@ -301,25 +334,38 @@ Expected<SymbolNameSet> getDylibInterfaceFromDylib(ExecutionSession &ES,
     return BinFile.takeError();
 
   std::unique_ptr<object::MachOObjectFile> MachOFile;
-  if (isa<object::MachOObjectFile>(**BinFile))
+  if (isa<object::MachOObjectFile>(**BinFile)) {
     MachOFile.reset(dyn_cast<object::MachOObjectFile>(BinFile->release()));
-  else if (auto *MachOUni =
-               dyn_cast<object::MachOUniversalBinary>(BinFile->get())) {
-    for (auto &O : MachOUni->objects()) {
-      if (O.getCPUType() == *CPUType &&
-          (O.getCPUSubType() & ~MachO::CPU_SUBTYPE_MASK) == *CPUSubType) {
-        if (auto Obj = O.getAsObjectFile())
-          MachOFile = std::move(*Obj);
-        else
-          return Obj.takeError();
-        break;
+
+    // TODO: Check that dylib arch is compatible.
+  } else if (auto *MachOUni =
+                 dyn_cast<object::MachOUniversalBinary>(BinFile->get())) {
+    SmallVector<std::pair<uint32_t, uint32_t>> ArchsToTry;
+    if (GetFallbackArchs)
+      ArchsToTry = GetFallbackArchs(*InitCPUType, *InitCPUSubType);
+    else
+      ArchsToTry.push_back({*InitCPUType, *InitCPUSubType});
+
+    for (auto &[CPUType, CPUSubType] : ArchsToTry) {
+      for (auto &O : MachOUni->objects()) {
+        if (O.getCPUType() == CPUType &&
+            (O.getCPUSubType() & ~MachO::CPU_SUBTYPE_MASK) == CPUSubType) {
+          if (auto Obj = O.getAsObjectFile())
+            MachOFile = std::move(*Obj);
+          else
+            return Obj.takeError();
+          break;
+        }
       }
+      if (MachOFile) // If found, break out.
+        break;
     }
     if (!MachOFile)
-      return make_error<StringError>("MachO universal binary at " + Path +
-                                         " does not contain a slice for " +
-                                         ES.getTargetTriple().str(),
-                                     inconvertibleErrorCode());
+      return make_error<StringError>(
+          "MachO universal binary at " + Path +
+              " does not contain a compatible slice for " +
+              ES.getTargetTriple().str(),
+          inconvertibleErrorCode());
   } else
     return make_error<StringError>("File at " + Path + " is not a MachO",
                                    inconvertibleErrorCode());
@@ -339,8 +385,9 @@ Expected<SymbolNameSet> getDylibInterfaceFromDylib(ExecutionSession &ES,
   return std::move(Symbols);
 }
 
-Expected<SymbolNameSet> getDylibInterfaceFromTapiFile(ExecutionSession &ES,
-                                                      Twine Path) {
+Expected<SymbolNameSet>
+getDylibInterfaceFromTapiFile(ExecutionSession &ES, Twine Path,
+                              GetFallbackArchsFn GetFallbackArchs) {
   SymbolNameSet Symbols;
 
   auto TapiFileBuffer = MemoryBuffer::getFile(Path);
@@ -352,27 +399,44 @@ Expected<SymbolNameSet> getDylibInterfaceFromTapiFile(ExecutionSession &ES,
   if (!Tapi)
     return Tapi.takeError();
 
-  auto CPUType = MachO::getCPUType(ES.getTargetTriple());
-  if (!CPUType)
-    return CPUType.takeError();
+  auto InitCPUType = MachO::getCPUType(ES.getTargetTriple());
+  if (!InitCPUType)
+    return InitCPUType.takeError();
 
-  auto CPUSubType = MachO::getCPUSubType(ES.getTargetTriple());
-  if (!CPUSubType)
-    return CPUSubType.takeError();
+  auto InitCPUSubType = MachO::getCPUSubType(ES.getTargetTriple());
+  if (!InitCPUSubType)
+    return InitCPUSubType.takeError();
+
+  SmallVector<std::pair<uint32_t, uint32_t>> ArchsToTry;
+  if (GetFallbackArchs)
+    ArchsToTry = GetFallbackArchs(*InitCPUType, *InitCPUSubType);
+  else
+    ArchsToTry.push_back({*InitCPUType, *InitCPUSubType});
 
   auto &IF = (*Tapi)->getInterfaceFile();
-  auto Interface =
-      IF.extract(MachO::getArchitectureFromCpuType(*CPUType, *CPUSubType));
-  if (!Interface)
-    return Interface.takeError();
 
-  for (auto *Sym : (*Interface)->exports())
-    Symbols.insert(ES.intern(Sym->getName()));
+  auto ArchSet = IF.getArchitectures();
+  for (auto [CPUType, CPUSubType] : ArchsToTry) {
+    auto A = MachO::getArchitectureFromCpuType(CPUType, CPUSubType);
+    if (ArchSet.has(A)) {
+      if (auto Interface = IF.extract(A)) {
+        for (auto *Sym : (*Interface)->exports())
+          Symbols.insert(ES.intern(Sym->getName()));
+        return Symbols;
+      } else
+        return Interface.takeError();
+    }
+  }
 
-  return Symbols;
+  return make_error<StringError>(
+      "MachO interface file at " + Path +
+          " does not contain a compatible slice for " +
+          ES.getTargetTriple().str(),
+      inconvertibleErrorCode());
 }
 
-Expected<SymbolNameSet> getDylibInterface(ExecutionSession &ES, Twine Path) {
+Expected<SymbolNameSet> getDylibInterface(ExecutionSession &ES, Twine Path,
+                                          GetFallbackArchsFn GetFallbackArchs) {
   file_magic Magic;
   if (auto EC = identify_magic(Path, Magic))
     return createFileError(Path, EC);
@@ -380,9 +444,9 @@ Expected<SymbolNameSet> getDylibInterface(ExecutionSession &ES, Twine Path) {
   switch (Magic) {
   case file_magic::macho_universal_binary:
   case file_magic::macho_dynamically_linked_shared_lib:
-    return getDylibInterfaceFromDylib(ES, Path);
+    return getDylibInterfaceFromDylib(ES, Path, std::move(GetFallbackArchs));
   case file_magic::tapi_file:
-    return getDylibInterfaceFromTapiFile(ES, Path);
+    return getDylibInterfaceFromTapiFile(ES, Path, std::move(GetFallbackArchs));
   default:
     return make_error<StringError>("Cannot get interface for " + Path +
                                        " unrecognized file type",

diff  --git a/llvm/test/ExecutionEngine/JITLink/AArch64/Inputs/MachO_Foo.tbd b/llvm/test/ExecutionEngine/JITLink/AArch64/Inputs/MachO_Foo_arm64.tbd
similarity index 100%
rename from llvm/test/ExecutionEngine/JITLink/AArch64/Inputs/MachO_Foo.tbd
rename to llvm/test/ExecutionEngine/JITLink/AArch64/Inputs/MachO_Foo_arm64.tbd

diff  --git a/llvm/test/ExecutionEngine/JITLink/AArch64/Inputs/MachO_Foo_arm64e.tbd b/llvm/test/ExecutionEngine/JITLink/AArch64/Inputs/MachO_Foo_arm64e.tbd
new file mode 100644
index 0000000000000..7b21ab0cff165
--- /dev/null
+++ b/llvm/test/ExecutionEngine/JITLink/AArch64/Inputs/MachO_Foo_arm64e.tbd
@@ -0,0 +1,23 @@
+--- !tapi-tbd
+tbd-version: 4
+targets:  [ arm64e-macos ]
+uuids:
+  - target: arm64e-macos
+    value: 00000000-0000-0000-0000-000000000000
+flags: [ installapi ]
+install-name: Foo.framework/Foo
+current-version: 1.2.3
+compatibility-version: 1.2
+swift-abi-version: 5
+parent-umbrella:
+  - targets: [ arm64e-macos ]
+    umbrella: System
+exports:
+  - targets: [ arm64e-macos ]
+    symbols: [ _foo ]
+    objc-classes: []
+    objc-eh-types: []
+    objc-ivars: []
+    weak-symbols: []
+    thread-local-symbols: []
+...

diff  --git a/llvm/test/ExecutionEngine/JITLink/AArch64/MachO_weak_link.test b/llvm/test/ExecutionEngine/JITLink/AArch64/MachO_weak_link.test
index 4326a604297b6..37847918bfe88 100644
--- a/llvm/test/ExecutionEngine/JITLink/AArch64/MachO_weak_link.test
+++ b/llvm/test/ExecutionEngine/JITLink/AArch64/MachO_weak_link.test
@@ -1,8 +1,14 @@
 # RUN: rm -rf %t && mkdir -p %t
 # RUN: llvm-mc -triple=arm64-apple-darwin19 -filetype=obj -o %t/main.o \
 # RUN:         %S/Inputs/MachO_main_ret_foo.s
-# RUN: llvm-jitlink -noexec %t/main.o -weak_library %S/Inputs/MachO_Foo.tbd
-
+# RUN: llvm-jitlink -noexec %t/main.o -weak_library \
+# RUN:     %S/Inputs/MachO_Foo_arm64.tbd
+# RUN: llvm-jitlink -noexec %t/main.o -weak_library \
+# RUN:     %S/Inputs/MachO_Foo_arm64e.tbd
+#
 # Check that we can load main.o, which unconditionally uses symbol foo, by
 # using -weak_library on a TBD file to emulate forced weak linking against
 # a library that supplies foo, but is missing at runtime.
+#
+# Check that weak linking works for arm64 JIT'd programs even if the TBD
+# file contains only an arm64e interface.


        


More information about the llvm-commits mailing list