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

Kyungwoo Lee via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Tue Sep 1 15:49:10 PDT 2026


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

>From 14160bcb293306e74a31b747024200c5b7065d15 Mon Sep 17 00:00:00 2001
From: Kyungwoo Lee <kyulee at meta.com>
Date: Sat, 29 Aug 2026 15:40:39 -0700
Subject: [PATCH] [lld][MachO] Support Objective-C class stubs

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.
---
 lld/MachO/Arch/ARM64.cpp                      |  92 ++++++
 lld/MachO/Driver.cpp                          |  38 ++-
 lld/MachO/MarkLive.cpp                        |   4 +
 lld/MachO/SyntheticSections.cpp               | 109 ++++++-
 lld/MachO/SyntheticSections.h                 |  13 +-
 lld/MachO/Target.h                            |   8 +
 lld/docs/ReleaseNotes.md                      |   4 +
 lld/test/MachO/arm64-objc-class-stubs-dead.s  |  75 +++++
 lld/test/MachO/arm64-objc-class-stubs.s       | 292 ++++++++++++++++++
 lld/test/MachO/objc-class-stubs-unsupported.s |  55 ++++
 10 files changed, 673 insertions(+), 17 deletions(-)
 create mode 100644 lld/test/MachO/arm64-objc-class-stubs-dead.s
 create mode 100644 lld/test/MachO/arm64-objc-class-stubs.s
 create mode 100644 lld/test/MachO/objc-class-stubs-unsupported.s

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 b05519f3a8c11..dc6a39e29dbb1 100644
--- a/lld/MachO/Driver.cpp
+++ b/lld/MachO/Driver.cpp
@@ -1604,13 +1604,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;
@@ -2470,7 +2496,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 bd39f49ba09e6..cd838a1069f49 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() 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();
   Defined *newSym = replaceSymbol<Defined>(
       sym, sym->getName(), nullptr, isec,
       /*value=*/stubsSize,
@@ -958,7 +1039,7 @@ void ObjCStubsSection::sortSymbols(
   size_t stubOffset = 0;
   for (Defined *sym : symbols) {
     sym->value = stubOffset;
-    stubOffset += getStubSize();
+    stubOffset += getStubSize(sym);
   }
 }
 
@@ -969,8 +1050,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 6642ed64112cf..46bdc842ed835 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() const;
+  size_t getStubSize(Symbol *sym) const;
 
   std::vector<Defined *> symbols;
+  llvm::DenseMap<Symbol *, Symbol *> classSymbols;
   // Total byte size of all stubs added so far.
   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-objc-class-stubs.s
@@ -0,0 +1,292 @@
+# 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/external.s -o %t/external.o
+# RUN: %lld -arch arm64 -dylib -install_name @executable_path/libexternal.dylib \
+# RUN:   -o %t/libexternal.dylib %t/external.o
+# RUN: %lld -arch arm64 -lSystem -o %t/fast.out %t/main.o \
+# RUN:   %t/libexternal.dylib -objc_stubs_fast
+# RUN: llvm-objdump --no-show-raw-insn --section=__TEXT,__objc_stubs \
+# RUN:   --macho %t/fast.out | FileCheck %s --check-prefix=FAST
+# RUN: %lld -arch arm64 -lSystem -o %t/small.out %t/main.o \
+# RUN:   %t/libexternal.dylib -objc_stubs_small
+# RUN: llvm-objdump --no-show-raw-insn --section=__TEXT,__objc_stubs \
+# RUN:   --macho %t/small.out | FileCheck %s --check-prefix=SMALL
+# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/archive-main.s \
+# RUN:   -o %t/archive-main.o
+# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/archive-class.s \
+# RUN:   -o %t/archive-class.o
+# RUN: llvm-ar rcs %t/libarchive.a %t/archive-class.o
+# RUN: %lld -arch arm64 -lSystem -o %t/archive.out %t/archive-main.o \
+# RUN:   %t/libarchive.a -objc_stubs_fast -U _objc_msgSend
+# RUN: llvm-objdump --no-show-raw-insn --section=__TEXT,__objc_stubs \
+# RUN:   --macho %t/archive.out | FileCheck %s --check-prefix=ARCHIVE
+# RUN: llvm-nm %t/archive.out | FileCheck %s --check-prefix=ARCHIVE-SYMS
+# RUN: %lld -arch arm64 -lSystem -o %t/start-lib.out %t/archive-main.o \
+# RUN:   --start-lib %t/archive-class.o --end-lib -objc_stubs_fast \
+# RUN:   -U _objc_msgSend
+# RUN: llvm-objdump --no-show-raw-insn --section=__TEXT,__objc_stubs \
+# RUN:   --macho %t/start-lib.out | FileCheck %s --check-prefix=ARCHIVE
+# RUN: llvm-nm %t/start-lib.out | FileCheck %s --check-prefix=ARCHIVE-SYMS
+# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/autolink-main-a.s \
+# RUN:   -o %t/autolink-main-a.o
+# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/autolink-dep-a.s \
+# RUN:   -o %t/autolink-dep-a.o
+# RUN: llvm-ar rcs %t/libautolinka.a %t/autolink-dep-a.o
+# RUN: %lld -arch arm64 -lSystem -o %t/autolink-a.out \
+# RUN:   %t/autolink-main-a.o -L%t -objc_stubs_fast -U _objc_msgSend
+# RUN: llvm-objdump --no-show-raw-insn --section=__TEXT,__objc_stubs \
+# RUN:   --macho %t/autolink-a.out | FileCheck %s --check-prefix=AUTOLINK-A
+# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/autolink-main-b.s \
+# RUN:   -o %t/autolink-main-b.o
+# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/autolink-dep-b.s \
+# RUN:   -o %t/autolink-dep-b.o
+# RUN: llvm-ar rcs %t/libautolinkb.a %t/autolink-dep-b.o
+# RUN: %lld -arch arm64 -lSystem -dead_strip -o %t/autolink-b.out \
+# RUN:   %t/autolink-main-b.o -L%t -objc_stubs_fast -U _objc_msgSend
+# RUN: llvm-objdump --no-show-raw-insn --section=__TEXT,__objc_stubs \
+# RUN:   --macho %t/autolink-b.out | FileCheck %s --check-prefix=AUTOLINK-B
+# RUN: llvm-nm %t/autolink-b.out | FileCheck %s --check-prefix=AUTOLINK-B-SYMS
+# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/dynamic.s \
+# RUN:   -o %t/dynamic.o
+# RUN: %lld -arch arm64 -lSystem -o %t/dynamic.out %t/dynamic.o \
+# RUN:   -objc_stubs_fast -U '_OBJC_CLASS_$_DynamicClass' -U _objc_msgSend
+# RUN: llvm-objdump --no-show-raw-insn --section=__TEXT,__objc_stubs \
+# RUN:   --macho %t/dynamic.out | FileCheck %s --check-prefix=DYNAMIC
+# RUN: %lld -arch arm64 -lSystem -o %t/dynamic-lookup.out %t/dynamic.o \
+# RUN:   -objc_stubs_fast -undefined dynamic_lookup
+# RUN: llvm-objdump --no-show-raw-insn --section=__TEXT,__objc_stubs \
+# RUN:   --macho %t/dynamic-lookup.out | FileCheck %s --check-prefix=DYNAMIC
+# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/missing.s \
+# RUN:   -o %t/missing.o
+# RUN: not %lld -arch arm64 -lSystem -o /dev/null %t/missing.o \
+# RUN:   -objc_stubs_fast 2>&1 | FileCheck %s --check-prefix=MISSING
+# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/malformed.s \
+# RUN:   -o %t/malformed.o
+# RUN: not %lld -arch arm64 -lSystem -o /dev/null %t/malformed.o \
+# RUN:   -objc_stubs_fast 2>&1 | FileCheck %s --check-prefix=MALFORMED
+
+# FAST:      Contents of (__TEXT,__objc_stubs) section
+# FAST:      _objc_msgSend$instance:
+# FAST-NEXT: adrp    x1,
+# FAST-NEXT: ldr     x1, {{.*}} ; Objc selector ref: instance
+# FAST-NEXT: adrp    x16,
+# FAST-NEXT: ldr     x16,
+# FAST-NEXT: br      x16
+# FAST-NEXT: brk     #0x1
+# FAST-NEXT: brk     #0x1
+# FAST-NEXT: brk     #0x1
+# FAST-NEXT: _objc_msgSendClass$external$_OBJC_CLASS_$_ExternalClass:
+# FAST-NEXT: adrp    x0,
+# FAST-NEXT: ldr     x0, {{.*}} ; literal pool symbol address: _OBJC_CLASS_$_ExternalClass
+# FAST-NEXT: adrp    x1,
+# FAST-NEXT: ldr     x1, {{.*}} ; Objc selector ref: external
+# FAST-NEXT: adrp    x16,
+# FAST-NEXT: ldr     x16,
+# FAST-NEXT: br      x16
+# FAST-NEXT: brk     #0x1
+# FAST-NEXT: _objc_msgSendClass$local$_OBJC_CLASS_$_LocalClass:
+# FAST-NEXT: adrp    x0,
+# FAST-NEXT: add     x0, x0,
+# FAST-NEXT: adrp    x1,
+# FAST-NEXT: ldr     x1, {{.*}} ; Objc selector ref: local
+# FAST-NEXT: adrp    x16,
+# FAST-NEXT: ldr     x16,
+# FAST-NEXT: br      x16
+# FAST-NEXT: brk     #0x1
+
+# SMALL:      Contents of (__TEXT,__objc_stubs) section
+# SMALL:      _objc_msgSend$instance:
+# SMALL-NEXT: adrp    x1,
+# SMALL-NEXT: ldr     x1, {{.*}} ; Objc selector ref: instance
+# SMALL-NEXT: : b
+# SMALL-NEXT: _objc_msgSendClass$external$_OBJC_CLASS_$_ExternalClass:
+# SMALL-NEXT: adrp    x0,
+# SMALL-NEXT: ldr     x0, {{.*}} ; literal pool symbol address: _OBJC_CLASS_$_ExternalClass
+# SMALL-NEXT: adrp    x1,
+# SMALL-NEXT: ldr     x1, {{.*}} ; Objc selector ref: external
+# SMALL-NEXT: : b
+# SMALL-NEXT: _objc_msgSendClass$local$_OBJC_CLASS_$_LocalClass:
+# SMALL-NEXT: adrp    x0,
+# SMALL-NEXT: add     x0, x0,
+# SMALL-NEXT: adrp    x1,
+# SMALL-NEXT: ldr     x1, {{.*}} ; Objc selector ref: local
+# SMALL-NEXT: : b
+
+# DYNAMIC:      Contents of (__TEXT,__objc_stubs) section
+# DYNAMIC-NEXT: _objc_msgSendClass$dynamic$_OBJC_CLASS_$_DynamicClass:
+# DYNAMIC-NEXT: adrp    x0,
+# DYNAMIC-NEXT: ldr     x0, {{.*}} ; literal pool symbol address: _OBJC_CLASS_$_DynamicClass
+# DYNAMIC-NEXT: adrp    x1,
+# DYNAMIC-NEXT: ldr     x1, {{.*}} ; Objc selector ref: dynamic
+# DYNAMIC-NEXT: adrp    x16,
+# DYNAMIC-NEXT: ldr     x16, {{.*}} ; literal pool symbol address: _objc_msgSend
+# DYNAMIC-NEXT: br      x16
+# DYNAMIC-NEXT: brk     #0x1
+
+# ARCHIVE:      Contents of (__TEXT,__objc_stubs) section
+# ARCHIVE-NEXT: _objc_msgSendClass$archive$_OBJC_CLASS_$_ArchiveClass:
+# ARCHIVE-NEXT: adrp    x0,
+# ARCHIVE-NEXT: add     x0, x0,
+# ARCHIVE-NEXT: adrp    x1,
+# ARCHIVE-NEXT: ldr     x1, {{.*}} ; Objc selector ref: archive
+#
+# ARCHIVE-SYMS: _OBJC_CLASS_$_ArchiveClass
+
+# AUTOLINK-A:      Contents of (__TEXT,__objc_stubs) section
+# AUTOLINK-A-NEXT: _objc_msgSendClass$auto$_OBJC_CLASS_$_AutoClass:
+# AUTOLINK-A-NEXT: adrp    x0,
+# AUTOLINK-A-NEXT: add     x0, x0,
+# AUTOLINK-A-NEXT: adrp    x1,
+# AUTOLINK-A-NEXT: ldr     x1, {{.*}} ; Objc selector ref: auto
+#
+# AUTOLINK-B:      Contents of (__TEXT,__objc_stubs) section
+# AUTOLINK-B-NEXT: _objc_msgSendClass$late$_OBJC_CLASS_$_LateClass:
+# AUTOLINK-B-NEXT: adrp    x0,
+# AUTOLINK-B-NEXT: add     x0, x0,
+# AUTOLINK-B-NEXT: adrp    x1,
+# AUTOLINK-B-NEXT: ldr     x1, {{.*}} ; Objc selector ref: late
+#
+# AUTOLINK-B-SYMS: _OBJC_CLASS_$_LateClass
+
+# MISSING: error: undefined symbol: _OBJC_CLASS_$_MissingClass
+# MISSING-NEXT: >>> referenced by objc class stub
+
+# MALFORMED: error: malformed objc class stub symbol _objc_msgSendClass$malformed; expected _objc_msgSendClass$<selector>$_OBJC_CLASS_$_<class>
+# MALFORMED-NOT: Objc selector ref:
+# MALFORMED-NOT: undefined symbol: _objc_msgSendClass$malformed
+
+#--- main.s
+.section __TEXT,__objc_methname,cstring_literals
+Linstance:
+  .asciz "instance"
+Llocal:
+  .asciz "local"
+Lexternal:
+  .asciz "external"
+
+.section __DATA,__objc_selrefs,literal_pointers,no_dead_strip
+.p2align 3
+  .quad Linstance
+  .quad Llocal
+  .quad Lexternal
+
+.text
+.globl _objc_msgSend
+_objc_msgSend:
+  ret
+
+.globl _main
+_main:
+  bl _objc_msgSend$instance
+  bl _objc_msgSendClass$local$_OBJC_CLASS_$_LocalClass
+  bl _objc_msgSendClass$external$_OBJC_CLASS_$_ExternalClass
+  ret
+
+.data
+.globl _OBJC_CLASS_$_LocalClass
+_OBJC_CLASS_$_LocalClass:
+  .quad 0
+
+.subsections_via_symbols
+
+#--- external.s
+.data
+.globl _OBJC_CLASS_$_ExternalClass
+_OBJC_CLASS_$_ExternalClass:
+  .quad 0
+
+#--- dynamic.s
+.section __TEXT,__objc_methname,cstring_literals
+Ldynamic:
+  .asciz "dynamic"
+
+.section __DATA,__objc_selrefs,literal_pointers,no_dead_strip
+.p2align 3
+  .quad Ldynamic
+
+.text
+.globl _main
+_main:
+  bl _objc_msgSendClass$dynamic$_OBJC_CLASS_$_DynamicClass
+  ret
+
+#--- archive-main.s
+.text
+.globl _main
+_main:
+  bl _objc_msgSendClass$archive$_OBJC_CLASS_$_ArchiveClass
+  ret
+
+#--- archive-class.s
+.data
+.globl _OBJC_CLASS_$_ArchiveClass
+_OBJC_CLASS_$_ArchiveClass:
+  .quad 0
+
+#--- autolink-main-a.s
+.linker_option "-lautolinka"
+.text
+.globl _main
+_main:
+  bl _dep_a
+  ret
+
+#--- autolink-dep-a.s
+.text
+.globl _dep_a
+_dep_a:
+  bl _objc_msgSendClass$auto$_OBJC_CLASS_$_AutoClass
+  ret
+
+.data
+.globl _OBJC_CLASS_$_AutoClass
+_OBJC_CLASS_$_AutoClass:
+  .quad 0
+
+#--- autolink-main-b.s
+.linker_option "-lautolinkb"
+.text
+.globl _main
+_main:
+  bl _dep_b
+  ret
+
+.data
+.globl _OBJC_CLASS_$_LateClass
+_OBJC_CLASS_$_LateClass:
+  .quad 0
+
+#--- autolink-dep-b.s
+.text
+.globl _dep_b
+_dep_b:
+  bl _objc_msgSendClass$late$_OBJC_CLASS_$_LateClass
+  ret
+
+#--- missing.s
+.section __TEXT,__objc_methname,cstring_literals
+Lmissing:
+  .asciz "missing"
+
+.section __DATA,__objc_selrefs,literal_pointers,no_dead_strip
+.p2align 3
+  .quad Lmissing
+
+.text
+.globl _objc_msgSend
+_objc_msgSend:
+  ret
+
+.globl _main
+_main:
+  bl _objc_msgSendClass$missing$_OBJC_CLASS_$_MissingClass
+  ret
+
+#--- malformed.s
+.text
+.globl _main
+_main:
+  bl _objc_msgSendClass$malformed
+  ret
diff --git a/lld/test/MachO/objc-class-stubs-unsupported.s b/lld/test/MachO/objc-class-stubs-unsupported.s
new file mode 100644
index 0000000000000..5f9688a24a799
--- /dev/null
+++ b/lld/test/MachO/objc-class-stubs-unsupported.s
@@ -0,0 +1,55 @@
+# REQUIRES: aarch64, x86
+
+# RUN: rm -rf %t && split-file %s %t
+# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/x86_64.s \
+# RUN:   -o %t/x86_64.o
+# RUN: not %lld -arch x86_64 -lSystem -o /dev/null %t/x86_64.o \
+# RUN:   -objc_stubs_fast 2>&1 | FileCheck %s --check-prefix=X86_64
+# RUN: llvm-mc -filetype=obj -triple=arm64e-apple-darwin %t/arm64.s \
+# RUN:   -o %t/arm64e.o
+# RUN: not %lld -arch arm64e -lSystem -o /dev/null %t/arm64e.o \
+# RUN:   -objc_stubs_fast 2>&1 | FileCheck %s --check-prefix=ARM64E
+# RUN: llvm-mc -filetype=obj -triple=arm64_32-apple-watchos %t/arm64_32.s \
+# RUN:   -o %t/arm64_32.o
+# RUN: not %lld-watchos -arch arm64_32 -lSystem -o /dev/null %t/arm64_32.o \
+# RUN:   -objc_stubs_fast 2>&1 | FileCheck %s --check-prefix=ARM64_32
+
+# X86_64: error: objc class stubs are not supported for x86_64
+# ARM64E: error: objc class stubs are not supported for arm64e
+# ARM64_32: error: objc class stubs are not supported for arm64_32
+
+#--- x86_64.s
+.text
+.globl _main
+_main:
+  callq _objc_msgSendClass$foo$_OBJC_CLASS_$_Foo
+  ret
+
+.data
+.globl _OBJC_CLASS_$_Foo
+_OBJC_CLASS_$_Foo:
+  .quad 0
+
+#--- arm64.s
+.text
+.globl _main
+_main:
+  bl _objc_msgSendClass$foo$_OBJC_CLASS_$_Foo
+  ret
+
+.data
+.globl _OBJC_CLASS_$_Foo
+_OBJC_CLASS_$_Foo:
+  .quad 0
+
+#--- arm64_32.s
+.text
+.globl _main
+_main:
+  bl _objc_msgSendClass$foo$_OBJC_CLASS_$_Foo
+  ret
+
+.data
+.globl _OBJC_CLASS_$_Foo
+_OBJC_CLASS_$_Foo:
+  .long 0



More information about the llvm-branch-commits mailing list