[llvm-branch-commits] [lld] [lld][MachO] Support Objective-C class stubs (PR #219744)

via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Sat Aug 29 18:50:55 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-lld

Author: Kyungwoo Lee (kyulee-com)

<details>
<summary>Changes</summary>

Teach Mach-O objc stubs to synthesize class-message stubs that load the class object, selector, and objc_msgSend target.

lld already supports the Apple clang _objc_msgSend$<selector> stub form, even though upstream clang does not currently expose a driver or cc1 flag for emitting it. Apple clang also emits _objc_msgSendClass$<selector>$_OBJC_CLASS_$_<class>; handling that form completes the existing selector-stub support.

Cover local classes, dylib classes, archives, dynamic lookup via -U, missing class symbols, malformed names, unsupported architectures, and dead stripping.

---

<sub>Stack created with <a href="https://github.com/github/gh-stack">GitHub Stacks CLI</a> • <a href="https://gh.io/stacks-feedback">Give Feedback 💬</a></sub>

---

Patch is 31.64 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/219744.diff


10 Files Affected:

- (modified) lld/MachO/Arch/ARM64.cpp (+92) 
- (modified) lld/MachO/Driver.cpp (+32-6) 
- (modified) lld/MachO/MarkLive.cpp (+4) 
- (modified) lld/MachO/SyntheticSections.cpp (+98-9) 
- (modified) lld/MachO/SyntheticSections.h (+12-1) 
- (modified) lld/MachO/Target.h (+8) 
- (modified) lld/docs/ReleaseNotes.md (+4) 
- (added) lld/test/MachO/arm64-objc-class-stubs-dead.s (+75) 
- (added) lld/test/MachO/arm64-objc-class-stubs.s (+292) 
- (added) lld/test/MachO/objc-class-stubs-unsupported.s (+55) 


``````````diff
diff --git a/lld/MachO/Arch/ARM64.cpp b/lld/MachO/Arch/ARM64.cpp
index 7acf48cbbf123..1a128c3be3990 100644
--- a/lld/MachO/Arch/ARM64.cpp
+++ b/lld/MachO/Arch/ARM64.cpp
@@ -33,6 +33,13 @@ struct ARM64 : ARM64Common {
   void writeObjCMsgSendStub(uint8_t *buf, Symbol *sym, uint64_t stubsAddr,
                             uint64_t &stubOffset, uint64_t selrefVA,
                             Symbol *objcMsgSend) const override;
+  void writeObjCMsgSendClassStub(uint8_t *buf, Symbol *sym, uint64_t stubsAddr,
+                                 uint64_t &stubOffset, uint64_t selrefVA,
+                                 Symbol *classSym,
+                                 Symbol *objcMsgSend) const override;
+  bool supportsObjCClassStubs() const override {
+    return config->arch() == AK_arm64;
+  }
   void populateThunk(InputSection *thunk, Symbol *funcSym,
                      int64_t addend) override;
 
@@ -121,6 +128,25 @@ static constexpr uint32_t objcStubsSmallCode[] = {
     0x14000000, // b     _objc_msgSend
 };
 
+static constexpr uint32_t objcClassStubsFastCode[] = {
+    0x90000000, // adrp  x0, _OBJC_CLASS_$_Foo at page
+    0xf9400000, // ldr   x0, [x0, _OBJC_CLASS_$_Foo at pageoff]
+    0x90000001, // adrp  x1, __objc_selrefs at page
+    0xf9400021, // ldr   x1, [x1, @selector("foo")@pageoff]
+    0x90000010, // adrp  x16, _got at page
+    0xf9400210, // ldr   x16, [x16, _objc_msgSend at pageoff]
+    0xd61f0200, // br    x16
+    0xd4200020, // brk   #0x1
+};
+
+static constexpr uint32_t objcClassStubsSmallCode[] = {
+    0x90000000, // adrp  x0, _OBJC_CLASS_$_Foo at page
+    0xf9400000, // ldr   x0, [x0, _OBJC_CLASS_$_Foo at pageoff]
+    0x90000001, // adrp  x1, __objc_selrefs at page
+    0xf9400021, // ldr   x1, [x1, @selector("foo")@pageoff]
+    0x14000000, // b     _objc_msgSend
+};
+
 void ARM64::writeObjCMsgSendStub(uint8_t *buf, Symbol *sym, uint64_t stubsAddr,
                                  uint64_t &stubOffset, uint64_t selrefVA,
                                  Symbol *objcMsgSend) const {
@@ -152,6 +178,70 @@ void ARM64::writeObjCMsgSendStub(uint8_t *buf, Symbol *sym, uint64_t stubsAddr,
   stubOffset += objcStubSize;
 }
 
+void ARM64::writeObjCMsgSendClassStub(uint8_t *buf, Symbol *stubSym,
+                                      uint64_t stubsAddr, uint64_t &stubOffset,
+                                      uint64_t selrefVA, Symbol *classSym,
+                                      Symbol *objcMsgSend) const {
+  SymbolDiagnostic d = {stubSym, stubSym->getName()};
+  auto *buf32 = reinterpret_cast<uint32_t *>(buf);
+
+  auto pcPageBits = [stubsAddr, stubOffset](int i) {
+    return pageBits(stubsAddr + stubOffset + i * sizeof(uint32_t));
+  };
+  auto writeClassLoad = [&](const uint32_t *code) {
+    uint64_t classVA;
+    uint32_t pageOffCode = code[1];
+    if (auto *defined = dyn_cast<Defined>(classSym)) {
+      classVA = defined->getVA();
+      pageOffCode = 0x91000000; // add x0, x0, _OBJC_CLASS_$_Foo at pageoff
+    } else {
+      classVA = in.got->addr + classSym->gotIndex * LP64::wordSize;
+    }
+    encodePage21(&buf32[0], d, code[0], pageBits(classVA) - pcPageBits(0));
+    encodePageOff12(&buf32[1], d, pageOffCode, classVA);
+  };
+
+  uint64_t objcMsgSendAddr;
+  uint64_t objcMsgSendIndex;
+
+  if (config->objcStubsMode == ObjCStubsMode::fast) {
+    writeClassLoad(objcClassStubsFastCode);
+    encodePage21(&buf32[2], d, objcClassStubsFastCode[2],
+                 pageBits(selrefVA) - pcPageBits(2));
+    encodePageOff12(&buf32[3], d, objcClassStubsFastCode[3], selrefVA);
+    objcMsgSendAddr = in.got->addr;
+    objcMsgSendIndex = objcMsgSend->gotIndex;
+    uint64_t gotOffset = objcMsgSendIndex * LP64::wordSize;
+    encodePage21(&buf32[4], d, objcClassStubsFastCode[4],
+                 pageBits(objcMsgSendAddr + gotOffset) - pcPageBits(4));
+    encodePageOff12(&buf32[5], d, objcClassStubsFastCode[5],
+                    objcMsgSendAddr + gotOffset);
+    buf32[6] = objcClassStubsFastCode[6];
+    buf32[7] = objcClassStubsFastCode[7];
+    stubOffset += target->objcClassStubsFastSize;
+    return;
+  }
+
+  assert(config->objcStubsMode == ObjCStubsMode::small);
+  writeClassLoad(objcClassStubsSmallCode);
+  encodePage21(&buf32[2], d, objcClassStubsSmallCode[2],
+               pageBits(selrefVA) - pcPageBits(2));
+  encodePageOff12(&buf32[3], d, objcClassStubsSmallCode[3], selrefVA);
+  if (auto *defined = dyn_cast<Defined>(objcMsgSend)) {
+    objcMsgSendAddr = defined->getVA();
+    objcMsgSendIndex = 0;
+  } else {
+    objcMsgSendAddr = in.stubs->addr;
+    objcMsgSendIndex = objcMsgSend->stubsIndex;
+  }
+  uint64_t msgSendStubVA =
+      objcMsgSendAddr + objcMsgSendIndex * target->stubSize;
+  uint64_t pcVA = stubsAddr + stubOffset + 4 * sizeof(uint32_t);
+  encodeBranch26(&buf32[4], {nullptr, "objc_msgSend class stub"},
+                 objcClassStubsSmallCode[4], msgSendStubVA - pcVA);
+  stubOffset += target->objcClassStubsSmallSize;
+}
+
 // A thunk is the relaxed variation of stubCode. We don't need the
 // extra indirection through a lazy pointer because the target address
 // is known at link time.
@@ -215,6 +305,8 @@ ARM64::ARM64() : ARM64Common(LP64()) {
   objcStubsFastAlignment = 32;
   objcStubsSmallSize = sizeof(objcStubsSmallCode);
   objcStubsSmallAlignment = 4;
+  objcClassStubsFastSize = sizeof(objcClassStubsFastCode);
+  objcClassStubsSmallSize = sizeof(objcClassStubsSmallCode);
 
   // Branch immediate is two's complement 26 bits, which is implicitly
   // multiplied by 4 (since all functions are 4-aligned: The branch range
diff --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp
index 632c48120c8cf..9cd54b13132bf 100644
--- a/lld/MachO/Driver.cpp
+++ b/lld/MachO/Driver.cpp
@@ -1595,13 +1595,39 @@ static void foldIdenticalLiterals() {
   in.wordLiteralSection->finalizeContents();
 }
 
-static void addSynthenticMethnames() {
+static void prepareObjCStubs() {
   std::string &data = *make<std::string>();
   llvm::raw_string_ostream os(data);
-  for (Symbol *sym : symtab->getSymbols())
-    if (isa<Undefined>(sym))
-      if (ObjCStubsSection::isObjCStubSymbol(sym))
-        os << ObjCStubsSection::getMethname(sym) << '\0';
+  for (size_t i = 0; i < symtab->getSymbols().size(); ++i) {
+    Symbol *sym = symtab->getSymbols()[i];
+    if (!isa<Undefined>(sym) || !ObjCStubsSection::isObjCStubSymbol(sym))
+      continue;
+
+    if (!ObjCStubsSection::isObjCClassStubSymbol(sym)) {
+      os << ObjCStubsSection::getMethname(sym) << '\0';
+      continue;
+    }
+
+    std::optional<std::pair<StringRef, StringRef>> parsed =
+        ObjCStubsSection::parseObjCClassStubSymbol(sym);
+    if (!parsed)
+      continue; // Diagnosed in ObjCStubsSection::addEntry().
+
+    os << parsed->first << '\0';
+    if (!target->supportsObjCClassStubs())
+      continue; // Diagnosed in ObjCStubsSection::addEntry().
+
+    Symbol *classSym = symtab->find(parsed->second);
+    // Avoid calling addUndefined() for an already-known dylib symbol here: it
+    // would mark the dylib strongly referenced before dead stripping, defeating
+    // -dead_strip_dylibs for dead class stubs. Missing or lazy symbols still
+    // need addUndefined() before markLive so live class stubs can pull their
+    // class definitions out of archives.
+    if (!classSym || isa<LazyArchive>(classSym) || isa<LazyObject>(classSym))
+      classSym = symtab->addUndefined(parsed->second, /*file=*/nullptr,
+                                      /*isWeakRef=*/false);
+    in.objcStubs->recordClassSymbol(sym, classSym);
+  }
 
   if (data.empty())
     return;
@@ -2458,7 +2484,7 @@ bool link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
     if (config->thinLTOIndexOnly || config->emitLLVM)
       return errorCount() == 0;
 
-    addSynthenticMethnames();
+    prepareObjCStubs();
 
     // LTO may emit a non-hidden (extern) object file symbol even if the
     // corresponding bitcode symbol is hidden. In particular, this happens for
diff --git a/lld/MachO/MarkLive.cpp b/lld/MachO/MarkLive.cpp
index a51c800bf4ef1..5d4c4efd0e3c4 100644
--- a/lld/MachO/MarkLive.cpp
+++ b/lld/MachO/MarkLive.cpp
@@ -11,6 +11,7 @@
 #include "OutputSegment.h"
 #include "SymbolTable.h"
 #include "Symbols.h"
+#include "SyntheticSections.h"
 #include "UnwindInfoSection.h"
 
 #include "lld/Common/ErrorHandler.h"
@@ -107,6 +108,9 @@ void MarkLiveImpl<RecordWhyLive>::addSym(
   if constexpr (RecordWhyLive)
     if (!config->whyLive.empty() && config->whyLive.match(s->getName()))
       printWhyLive(s, prev);
+  if (ObjCStubsSection::isObjCClassStubSymbol(s))
+    if (auto *classSym = in.objcStubs->lookupClassSymbol(s))
+      addSym(classSym, prev);
   if (auto *d = dyn_cast<Defined>(s)) {
     if (d->isec())
       enqueue(d->isec(), d->value, prev);
diff --git a/lld/MachO/SyntheticSections.cpp b/lld/MachO/SyntheticSections.cpp
index 5f7bd29e323ff..2492ee9ad403e 100644
--- a/lld/MachO/SyntheticSections.cpp
+++ b/lld/MachO/SyntheticSections.cpp
@@ -889,28 +889,109 @@ ObjCStubsSection::ObjCStubsSection()
 }
 
 bool ObjCStubsSection::isObjCStubSymbol(Symbol *sym) {
-  return sym->getName().starts_with(objcMsgSendStubPrefix);
+  return sym->getName().starts_with(objcMsgSendStubPrefix) ||
+         isObjCClassStubSymbol(sym);
 }
 
 StringRef ObjCStubsSection::getMethname(Symbol *sym) {
   assert(isObjCStubSymbol(sym) && "not an objc stub");
   auto name = sym->getName();
+  if (isObjCClassStubSymbol(sym)) {
+    std::optional<std::pair<StringRef, StringRef>> parsed =
+        parseObjCClassStubSymbol(sym);
+    assert(parsed && "malformed objc class stub");
+    return parsed->first;
+  }
   return name.drop_front(objcMsgSendStubPrefix.size());
 }
 
-size_t ObjCStubsSection::getStubSize(Symbol *) const {
-  return config->objcStubsMode == ObjCStubsMode::fast
-             ? target->objcStubsFastSize
-             : target->objcStubsSmallSize;
+bool ObjCStubsSection::isObjCClassStubSymbol(Symbol *sym) {
+  return sym->getName().starts_with(objcMsgSendClassStubPrefix);
+}
+
+std::optional<std::pair<StringRef, StringRef>>
+ObjCStubsSection::parseObjCClassStubSymbol(Symbol *sym) {
+  assert(isObjCClassStubSymbol(sym) && "not an objc class stub");
+  StringRef name = sym->getName().drop_front(objcMsgSendClassStubPrefix.size());
+  size_t classSymbolStart = name.find(classSymbolPrefix);
+  if (classSymbolStart == StringRef::npos)
+    return std::nullopt;
+  StringRef methname = name.take_front(classSymbolStart);
+  StringRef className = name.drop_front(classSymbolStart + 1);
+  if (methname.empty() || className.empty())
+    return std::nullopt;
+  return std::make_pair(methname, className);
+}
+
+void ObjCStubsSection::recordClassSymbol(Symbol *stubSym, Symbol *classSym) {
+  // SymbolTable updates symbols in place with replaceSymbol(), so this pointer
+  // remains valid if an Undefined later becomes a DylibSymbol or Defined.
+  classSymbols[stubSym] = classSym;
+}
+
+Symbol *ObjCStubsSection::lookupClassSymbol(Symbol *stubSym) const {
+  return classSymbols.lookup(stubSym);
+}
+
+size_t ObjCStubsSection::getStubSize(Symbol *sym) const {
+  if (config->objcStubsMode == ObjCStubsMode::fast)
+    return isObjCClassStubSymbol(sym) ? target->objcClassStubsFastSize
+                                      : target->objcStubsFastSize;
+  return isObjCClassStubSymbol(sym) ? target->objcClassStubsSmallSize
+                                    : target->objcStubsSmallSize;
 }
 
 void ObjCStubsSection::addEntry(Symbol *sym) {
-  StringRef methname = getMethname(sym);
+  auto replaceWithEmptyStub = [&] {
+    replaceSymbol<Defined>(
+        sym, sym->getName(), nullptr, isec, /*value=*/stubsSize, /*size=*/0,
+        /*isWeakDef=*/false, /*isExternal=*/true, /*isPrivateExtern=*/true,
+        /*includeInSymtab=*/true, /*isReferencedDynamically=*/false,
+        /*noDeadStrip=*/false);
+  };
+
+  StringRef methname;
+  Symbol *classSym = nullptr;
+  if (isObjCClassStubSymbol(sym)) {
+    if (!target->supportsObjCClassStubs()) {
+      error("objc class stubs are not supported for " +
+            getArchitectureName(config->arch()));
+      return replaceWithEmptyStub();
+    }
+    std::optional<std::pair<StringRef, StringRef>> parsed =
+        parseObjCClassStubSymbol(sym);
+    if (!parsed) {
+      error("malformed objc class stub symbol " + sym->getName() +
+            "; expected " + objcMsgSendClassStubPrefix + "<selector>" +
+            classSymbolPrefix + "<class>");
+      return replaceWithEmptyStub();
+    }
+    methname = parsed->first;
+    classSym = lookupClassSymbol(sym);
+    if (!classSym) {
+      error("objc class stub symbol " + sym->getName() +
+            " references missing class symbol " + parsed->second);
+      return replaceWithEmptyStub();
+    }
+    if (auto *undefined = dyn_cast<Undefined>(classSym)) {
+      treatUndefinedSymbol(*undefined, "objc class stub");
+      if (isa<Undefined>(classSym))
+        return replaceWithEmptyStub();
+    }
+    if (auto *dysym = dyn_cast<DylibSymbol>(classSym)) {
+      dysym->reference(RefState::Strong);
+      in.got->addEntry(dysym);
+    }
+    classSym->used = true;
+  } else {
+    methname = getMethname(sym);
+  }
+
+  size_t stubSize = getStubSize(sym);
   // We create a selref entry for each unique methname.
   if (!ObjCSelRefsHelper::getSelRef(methname))
     ObjCSelRefsHelper::makeSelRef(methname);
 
-  size_t stubSize = getStubSize(sym);
   Defined *newSym = replaceSymbol<Defined>(
       sym, sym->getName(), nullptr, isec,
       /*value=*/stubsSize,
@@ -971,8 +1052,16 @@ void ObjCStubsSection::writeTo(uint8_t *buf) const {
     InputSection *selRef = ObjCSelRefsHelper::getSelRef(methname);
     assert(selRef != nullptr && "no selref for methname");
     auto selrefAddr = selRef->getVA(0);
-    target->writeObjCMsgSendStub(buf + stubOffset, sym, in.objcStubs->addr,
-                                 stubOffset, selrefAddr, objcMsgSend);
+    if (isObjCClassStubSymbol(sym)) {
+      Symbol *classSym = classSymbols.lookup(sym);
+      assert(classSym != nullptr && "no class symbol for objc class stub");
+      target->writeObjCMsgSendClassStub(buf + stubOffset, sym,
+                                        in.objcStubs->addr, stubOffset,
+                                        selrefAddr, classSym, objcMsgSend);
+    } else {
+      target->writeObjCMsgSendStub(buf + stubOffset, sym, in.objcStubs->addr,
+                                   stubOffset, selrefAddr, objcMsgSend);
+    }
   }
 }
 
diff --git a/lld/MachO/SyntheticSections.h b/lld/MachO/SyntheticSections.h
index a747cbc164b3a..9bf6121056b9d 100644
--- a/lld/MachO/SyntheticSections.h
+++ b/lld/MachO/SyntheticSections.h
@@ -24,6 +24,8 @@
 #include "llvm/Support/MathExtras.h"
 #include "llvm/Support/raw_ostream.h"
 
+#include <optional>
+
 namespace llvm {
 class DWARFUnit;
 } // namespace llvm
@@ -343,17 +345,26 @@ class ObjCStubsSection final : public SyntheticSection {
   void setUp();
 
   static constexpr llvm::StringLiteral objcMsgSendStubPrefix = "_objc_msgSend$";
+  static constexpr llvm::StringLiteral objcMsgSendClassStubPrefix =
+      "_objc_msgSendClass$";
+  static constexpr llvm::StringLiteral classSymbolPrefix = "$_OBJC_CLASS_$_";
   static bool isObjCStubSymbol(Symbol *sym);
   static StringRef getMethname(Symbol *sym);
+  static bool isObjCClassStubSymbol(Symbol *sym);
+  static std::optional<std::pair<StringRef, StringRef>>
+  parseObjCClassStubSymbol(Symbol *sym);
+  void recordClassSymbol(Symbol *stubSym, Symbol *classSym);
+  Symbol *lookupClassSymbol(Symbol *stubSym) const;
 
   /// Stably sort the stubs by \p priorities and reassign their offsets. Must
   /// run before addresses are assigned.
   void sortSymbols(const llvm::DenseMap<const Symbol *, int> &priorities);
 
 private:
-  size_t getStubSize(Symbol *) const;
+  size_t getStubSize(Symbol *sym) const;
 
   std::vector<Defined *> symbols;
+  llvm::DenseMap<Symbol *, Symbol *> classSymbols;
   size_t stubsSize = 0;
   Symbol *objcMsgSend = nullptr;
 };
diff --git a/lld/MachO/Target.h b/lld/MachO/Target.h
index 0c7c0c3817c97..4165b159c633f 100644
--- a/lld/MachO/Target.h
+++ b/lld/MachO/Target.h
@@ -73,6 +73,12 @@ class TargetInfo {
                                     uint64_t stubsAddr, uint64_t &stubOffset,
                                     uint64_t selrefVA,
                                     Symbol *objcMsgSend) const = 0;
+  virtual void writeObjCMsgSendClassStub(uint8_t *, Symbol *, uint64_t,
+                                         uint64_t &, uint64_t, Symbol *,
+                                         Symbol *) const {
+    llvm_unreachable("target does not support objc class stubs");
+  }
+  virtual bool supportsObjCClassStubs() const { return false; }
 
   // Init 'thunk' so that it be a direct jump to 'branchTarget'.
   virtual void initICFSafeThunkBody(InputSection *thunk,
@@ -136,6 +142,8 @@ class TargetInfo {
   size_t stubHelperEntrySize;
   size_t objcStubsFastSize;
   size_t objcStubsSmallSize;
+  size_t objcClassStubsFastSize = 0;
+  size_t objcClassStubsSmallSize = 0;
   size_t objcStubsFastAlignment;
   size_t objcStubsSmallAlignment;
   uint8_t p2WordSize;
diff --git a/lld/docs/ReleaseNotes.md b/lld/docs/ReleaseNotes.md
index 00c9f5c85af8f..1df382ec56a52 100644
--- a/lld/docs/ReleaseNotes.md
+++ b/lld/docs/ReleaseNotes.md
@@ -40,6 +40,10 @@ from the [LLVM releases web site](https://llvm.org/releases/).
 * `__objc_stubs` entries are now ordered by the priority of the sections that
   call them, so that stubs reached from prioritized code are laid out together.
   This applies whenever section priorities exist, such as with `-order_file`.
+* Added support for Objective-C class-message stubs
+  (`_objc_msgSendClass$<selector>$_OBJC_CLASS_$_<class>`), which load the
+  class object, selector, and `objc_msgSend` target. Supported on arm64; other
+  architectures report an error.
 
 ### WebAssembly Improvements
 
diff --git a/lld/test/MachO/arm64-objc-class-stubs-dead.s b/lld/test/MachO/arm64-objc-class-stubs-dead.s
new file mode 100644
index 0000000000000..83cd1b6295175
--- /dev/null
+++ b/lld/test/MachO/arm64-objc-class-stubs-dead.s
@@ -0,0 +1,75 @@
+# REQUIRES: aarch64
+
+# RUN: rm -rf %t && split-file %s %t
+# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/main.s -o %t/main.o
+# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/dylib.s \
+# RUN:   -o %t/dylib.o
+# RUN: %lld -arch arm64 -dylib -install_name @executable_path/libdead.dylib \
+# RUN:   -o %t/libdead.dylib %t/dylib.o
+# RUN: %lld -arch arm64 -lSystem -dead_strip -dead_strip_dylibs \
+# RUN:   -o %t.out %t/main.o %t/libdead.dylib -U _objc_msgSend \
+# RUN:   -objc_stubs_small
+# RUN: llvm-objdump --no-show-raw-insn --section=__TEXT,__objc_stubs \
+# RUN:   --macho %t.out | FileCheck %s --check-prefix=STUBS
+# RUN: llvm-nm %t.out | FileCheck %s --check-prefix=SYMS
+# RUN: llvm-otool -L %t.out | FileCheck %s --check-prefix=LOAD
+
+# STUBS:      Contents of (__TEXT,__objc_stubs) section
+# STUBS-NEXT: _objc_msgSendClass$live$_OBJC_CLASS_$_LiveClass:
+# STUBS-NOT:  _objc_msgSendClass$dead$_OBJC_CLASS_$_DeadClass:
+# STUBS-NOT:  _objc_msgSendClass$missing$_OBJC_CLASS_$_NeverDefined:
+# STUBS-NOT:  _objc_msgSendClass$dylib$_OBJC_CLASS_$_DylibClass:
+
+# SYMS-NOT: _OBJC_CLASS_$_DeadClass
+# SYMS-NOT: _OBJC_CLASS_$_NeverDefined
+# SYMS:     _OBJC_CLASS_$_LiveClass
+# SYMS-NOT: _OBJC_CLASS_$_DeadClass
+# SYMS-NOT: _OBJC_CLASS_$_NeverDefined
+
+# LOAD-NOT: libdead.dylib
+# LOAD:     /usr/lib/libSystem.dylib
+# LOAD-NOT: libdead.dylib
+
+#--- main.s
+.text
+.globl _main
+_main:
+  bl _live
+  ret
+
+.globl _live
+_live:
+  bl _objc_msgSendClass$live$_OBJC_CLASS_$_LiveClass
+  ret
+
+.globl _dead
+_dead:
+  bl _objc_msgSendClass$dead$_OBJC_CLASS_$_DeadClass
+  ret
+
+.globl _missing
+_missing:
+  bl _objc_msgSendClass$missing$_OBJC_CLASS_$_NeverDefined
+  ret
+
+.globl _dylibdead
+_dylibdead:
+  bl _objc_msgSendClass$dylib$_OBJC_CLASS_$_DylibClass
+  ret
+
+.data
+.globl _OBJC_CLASS_$_LiveClass
+_OBJC_CLASS_$_LiveClass:
+  .quad 0
+
+.globl _OBJC_CLASS_$_DeadClass
+_OBJC_CLASS_$_DeadClass:
+  .quad 0
+
+.subsections_via_symbols
+
+#--- dylib.s
+.data
+.globl _OBJC_CLASS_$_DylibClass
+_OBJC_CLASS_$_DylibClass:
+  .quad 0
diff --git a/lld/test/MachO/arm64-objc-class-stubs.s b/lld/test/MachO/arm64-objc-class-stubs.s
new file mode 100644
index 0000000000000..24d5584f275bc
--- /dev/null
+++ b/lld/test/MachO/arm64-o...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/219744


More information about the llvm-branch-commits mailing list