Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[lld-macho][arm64] implement -objc_stubs_small #78665

Merged
merged 5 commits into from
Jan 23, 2024

Conversation

kyulee-com
Copy link
Contributor

This patch implements -objc_stubs_small targeting arm64, aiming to align with ld64's behavior.

  1. -objc_stubs_fast: As previously implemented, this always uses the Global Offset Table (GOT) to invoke objc_msgSend. The alignment of the objc stub is 32 bytes.
  2. -objc_stubs_small: This behavior depends on whether objc_msgSend is defined. If it is, it directly jumps to objc_msgSend. If not, it creates another stub to indirectly jump to objc_msgSend, minimizing the size. The alignment of the objc stub in this case is 4 bytes.

This patch implements `-objc_stubs_small` targeting arm64, aiming to align with ld64's behavior.
  1. `-objc_stubs_fast`: As previously implemented, this always uses the Global Offset Table (GOT) to invoke objc_msgSend. The alignment of the objc stub is 32 bytes.
  2. `-objc_stubs_small`: This behavior depends on whether objc_msgSend is defined. If it is, it directly jumps to objc_msgSend. If not, it creates another stub to indirectly jump to objc_msgSend, minimizing the size. The alignment of the objc stub in this case is 4 bytes.
@llvmbot
Copy link
Collaborator

llvmbot commented Jan 19, 2024

@llvm/pr-subscribers-lld

Author: Kyungwoo Lee (kyulee-com)

Changes

This patch implements -objc_stubs_small targeting arm64, aiming to align with ld64's behavior.

  1. -objc_stubs_fast: As previously implemented, this always uses the Global Offset Table (GOT) to invoke objc_msgSend. The alignment of the objc stub is 32 bytes.
  2. -objc_stubs_small: This behavior depends on whether objc_msgSend is defined. If it is, it directly jumps to objc_msgSend. If not, it creates another stub to indirectly jump to objc_msgSend, minimizing the size. The alignment of the objc stub in this case is 4 bytes.

Full diff: https://github.com/llvm/llvm-project/pull/78665.diff

9 Files Affected:

  • (modified) lld/MachO/Arch/ARM64.cpp (+18-5)
  • (modified) lld/MachO/Arch/ARM64Common.h (+28-4)
  • (modified) lld/MachO/Arch/X86_64.cpp (+1-1)
  • (modified) lld/MachO/Driver.cpp (+7-3)
  • (modified) lld/MachO/SyntheticSections.cpp (+52-11)
  • (modified) lld/MachO/SyntheticSections.h (+1-1)
  • (modified) lld/MachO/Target.h (+3-1)
  • (added) lld/test/MachO/arm64-objc-stubs-dyn.s (+76)
  • (modified) lld/test/MachO/arm64-objc-stubs.s (+28-4)
diff --git a/lld/MachO/Arch/ARM64.cpp b/lld/MachO/Arch/ARM64.cpp
index e3781763c6102b..7b18292da6d8c0 100644
--- a/lld/MachO/Arch/ARM64.cpp
+++ b/lld/MachO/Arch/ARM64.cpp
@@ -117,13 +117,24 @@ static constexpr uint32_t objcStubsFastCode[] = {
     0xd4200020, // brk   #0x1
 };
 
+static constexpr uint32_t objcStubsSmallCode[] = {
+    0x90000001, // adrp  x1, __objc_selrefs@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 selrefsVA,
-                                 uint64_t selectorIndex, uint64_t gotAddr,
+                                 uint64_t selectorIndex, uint64_t msgSendAddr,
                                  uint64_t msgSendIndex) const {
-  ::writeObjCMsgSendStub<LP64>(buf, objcStubsFastCode, sym, stubsAddr,
-                               stubOffset, selrefsVA, selectorIndex, gotAddr,
-                               msgSendIndex);
+  if (config->objcStubsMode == ObjCStubsMode::fast)
+    ::writeObjCMsgSendFastStub<LP64>(buf, objcStubsFastCode, sym, stubsAddr,
+                                     stubOffset, selrefsVA, selectorIndex,
+                                     msgSendAddr, msgSendIndex);
+  else
+    ::writeObjCMsgSendSmallStub<LP64>(buf, objcStubsSmallCode, sym, stubsAddr,
+                                      stubOffset, selrefsVA, selectorIndex,
+                                      msgSendAddr, msgSendIndex);
 }
 
 // A thunk is the relaxed variation of stubCode. We don't need the
@@ -157,7 +168,9 @@ ARM64::ARM64() : ARM64Common(LP64()) {
   thunkSize = sizeof(thunkCode);
 
   objcStubsFastSize = sizeof(objcStubsFastCode);
-  objcStubsAlignment = 32;
+  objcStubsFastAlignment = 32;
+  objcStubsSmallSize = sizeof(objcStubsSmallCode);
+  objcStubsSmallAlignment = 4;
 
   // 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/Arch/ARM64Common.h b/lld/MachO/Arch/ARM64Common.h
index 9cfccb6cec7614..b038b6200f4d57 100644
--- a/lld/MachO/Arch/ARM64Common.h
+++ b/lld/MachO/Arch/ARM64Common.h
@@ -154,10 +154,10 @@ inline void writeStubHelperEntry(uint8_t *buf8,
 
 template <class LP>
 inline void
-writeObjCMsgSendStub(uint8_t *buf, const uint32_t objcStubsFastCode[8],
-                     Symbol *sym, uint64_t stubsAddr, uint64_t stubOffset,
-                     uint64_t selrefsVA, uint64_t selectorIndex,
-                     uint64_t gotAddr, uint64_t msgSendIndex) {
+writeObjCMsgSendFastStub(uint8_t *buf, const uint32_t objcStubsFastCode[8],
+                         Symbol *sym, uint64_t stubsAddr, uint64_t stubOffset,
+                         uint64_t selrefsVA, uint64_t selectorIndex,
+                         uint64_t gotAddr, uint64_t msgSendIndex) {
   SymbolDiagnostic d = {sym, sym->getName()};
   auto *buf32 = reinterpret_cast<uint32_t *>(buf);
 
@@ -180,6 +180,30 @@ writeObjCMsgSendStub(uint8_t *buf, const uint32_t objcStubsFastCode[8],
   buf32[7] = objcStubsFastCode[7];
 }
 
+template <class LP>
+inline void
+writeObjCMsgSendSmallStub(uint8_t *buf, const uint32_t objcStubsSmallCode[3],
+                          Symbol *sym, uint64_t stubsAddr, uint64_t stubOffset,
+                          uint64_t selrefsVA, uint64_t selectorIndex,
+                          uint64_t msgSendAddr, uint64_t msgSendIndex) {
+  SymbolDiagnostic d = {sym, sym->getName()};
+  auto *buf32 = reinterpret_cast<uint32_t *>(buf);
+
+  auto pcPageBits = [stubsAddr, stubOffset](int i) {
+    return pageBits(stubsAddr + stubOffset + i * sizeof(uint32_t));
+  };
+
+  uint64_t selectorOffset = selectorIndex * LP::wordSize;
+  encodePage21(&buf32[0], d, objcStubsSmallCode[0],
+               pageBits(selrefsVA + selectorOffset) - pcPageBits(0));
+  encodePageOff12(&buf32[1], d, objcStubsSmallCode[1],
+                  selrefsVA + selectorOffset);
+  uint64_t msgSendStubVA = msgSendAddr + msgSendIndex * target->stubSize;
+  uint64_t pcVA = stubsAddr + stubOffset + 2 * sizeof(uint32_t);
+  encodeBranch26(&buf32[2], {nullptr, "objc_msgSend stub"},
+                 objcStubsSmallCode[2], msgSendStubVA - pcVA);
+}
+
 } // namespace lld::macho
 
 #endif
diff --git a/lld/MachO/Arch/X86_64.cpp b/lld/MachO/Arch/X86_64.cpp
index a0d4e1a28a14a2..55fbdefb75db28 100644
--- a/lld/MachO/Arch/X86_64.cpp
+++ b/lld/MachO/Arch/X86_64.cpp
@@ -214,7 +214,7 @@ X86_64::X86_64() : TargetInfo(LP64()) {
   stubHelperEntrySize = sizeof(stubHelperEntry);
 
   objcStubsFastSize = sizeof(objcStubsFastCode);
-  objcStubsAlignment = 1;
+  objcStubsFastAlignment = 1;
 
   relocAttrs = {relocAttrsArray.data(), relocAttrsArray.size()};
 }
diff --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp
index 401459a054394e..d3d9a56f85fe0e 100644
--- a/lld/MachO/Driver.cpp
+++ b/lld/MachO/Driver.cpp
@@ -823,9 +823,13 @@ static ObjCStubsMode getObjCStubsMode(const ArgList &args) {
   if (!arg)
     return ObjCStubsMode::fast;
 
-  if (arg->getOption().getID() == OPT_objc_stubs_small)
-    warn("-objc_stubs_small is not yet implemented, defaulting to "
-         "-objc_stubs_fast");
+  if (arg->getOption().getID() == OPT_objc_stubs_small) {
+    if (is_contained({AK_arm64e, AK_arm64}, config->arch()))
+      return ObjCStubsMode::small;
+    else
+      warn("-objc_stubs_small is not yet implemented, defaulting to "
+           "-objc_stubs_fast");
+  }
   return ObjCStubsMode::fast;
 }
 
diff --git a/lld/MachO/SyntheticSections.cpp b/lld/MachO/SyntheticSections.cpp
index e123dcb6803c1e..6e9effe3d3f3f5 100644
--- a/lld/MachO/SyntheticSections.cpp
+++ b/lld/MachO/SyntheticSections.cpp
@@ -809,7 +809,9 @@ void StubHelperSection::setUp() {
 ObjCStubsSection::ObjCStubsSection()
     : SyntheticSection(segment_names::text, section_names::objcStubs) {
   flags = S_ATTR_SOME_INSTRUCTIONS | S_ATTR_PURE_INSTRUCTIONS;
-  align = target->objcStubsAlignment;
+  align = config->objcStubsMode == ObjCStubsMode::fast
+              ? target->objcStubsFastAlignment
+              : target->objcStubsSmallAlignment;
 }
 
 void ObjCStubsSection::addEntry(Symbol *sym) {
@@ -817,10 +819,14 @@ void ObjCStubsSection::addEntry(Symbol *sym) {
   StringRef methname = sym->getName().drop_front(symbolPrefix.size());
   offsets.push_back(
       in.objcMethnameSection->getStringOffset(methname).outSecOff);
+
+  auto stubSize = config->objcStubsMode == ObjCStubsMode::fast
+                      ? target->objcStubsFastSize
+                      : target->objcStubsSmallSize;
   Defined *newSym = replaceSymbol<Defined>(
       sym, sym->getName(), nullptr, isec,
-      /*value=*/symbols.size() * target->objcStubsFastSize,
-      /*size=*/target->objcStubsFastSize,
+      /*value=*/symbols.size() * stubSize,
+      /*size=*/stubSize,
       /*isWeakDef=*/false, /*isExternal=*/true, /*isPrivateExtern=*/true,
       /*includeInSymtab=*/true, /*isReferencedDynamically=*/false,
       /*noDeadStrip=*/false);
@@ -828,12 +834,25 @@ void ObjCStubsSection::addEntry(Symbol *sym) {
 }
 
 void ObjCStubsSection::setUp() {
-  Symbol *objcMsgSend = symtab->addUndefined("_objc_msgSend", /*file=*/nullptr,
-                                             /*isWeakRef=*/false);
+  objcMsgSend = symtab->addUndefined("_objc_msgSend", /*file=*/nullptr,
+                                     /*isWeakRef=*/false);
+  if (auto *undefined = dyn_cast<Undefined>(objcMsgSend))
+    treatUndefinedSymbol(*undefined,
+                         "lazy binding (normally in libobjc.dylib)");
   objcMsgSend->used = true;
-  in.got->addEntry(objcMsgSend);
-  assert(objcMsgSend->isInGot());
-  objcMsgSendGotIndex = objcMsgSend->gotIndex;
+  if (config->objcStubsMode == ObjCStubsMode::fast) {
+    in.got->addEntry(objcMsgSend);
+    assert(objcMsgSend->isInGot());
+  } else {
+    assert(config->objcStubsMode == ObjCStubsMode::small);
+    // In line with ld64's behavior, when objc_msgSend is a direct symbol,
+    // we directly reference it.
+    // In other cases, typically when binding in libobjc.dylib,
+    // we generate a stub to invoke objc_msgSend.
+    auto *d = dyn_cast<Defined>(objcMsgSend);
+    if (!d)
+      in.stubs->addEntry(objcMsgSend);
+  }
 
   size_t size = offsets.size() * target->wordSize;
   uint8_t *selrefsData = bAlloc().Allocate<uint8_t>(size);
@@ -863,7 +882,10 @@ void ObjCStubsSection::setUp() {
 }
 
 uint64_t ObjCStubsSection::getSize() const {
-  return target->objcStubsFastSize * symbols.size();
+  auto stubSize = config->objcStubsMode == ObjCStubsMode::fast
+                      ? target->objcStubsFastSize
+                      : target->objcStubsSmallSize;
+  return stubSize * symbols.size();
 }
 
 void ObjCStubsSection::writeTo(uint8_t *buf) const {
@@ -871,12 +893,31 @@ void ObjCStubsSection::writeTo(uint8_t *buf) const {
   assert(in.objcSelrefs->isFinal);
 
   uint64_t stubOffset = 0;
+  uint64_t objcMsgSendAddr;
+  uint64_t objcStubSize;
+  uint64_t objcMsgSendIndex;
+  if (config->objcStubsMode == ObjCStubsMode::fast) {
+    objcStubSize = target->objcStubsFastSize;
+    objcMsgSendAddr = in.got->addr;
+    objcMsgSendIndex = objcMsgSend->gotIndex;
+  } else {
+    assert(config->objcStubsMode == ObjCStubsMode::small);
+    objcStubSize = target->objcStubsSmallSize;
+    if (auto *d = dyn_cast<Defined>(objcMsgSend)) {
+      objcMsgSendAddr = d->getVA();
+      objcMsgSendIndex = 0;
+    } else {
+      objcMsgSendAddr = in.stubs->addr;
+      objcMsgSendIndex = objcMsgSend->stubsIndex;
+    }
+  }
+
   for (size_t i = 0, n = symbols.size(); i < n; ++i) {
     Defined *sym = symbols[i];
     target->writeObjCMsgSendStub(buf + stubOffset, sym, in.objcStubs->addr,
                                  stubOffset, in.objcSelrefs->getVA(), i,
-                                 in.got->addr, objcMsgSendGotIndex);
-    stubOffset += target->objcStubsFastSize;
+                                 objcMsgSendAddr, objcMsgSendIndex);
+    stubOffset += objcStubSize;
   }
 }
 
diff --git a/lld/MachO/SyntheticSections.h b/lld/MachO/SyntheticSections.h
index e9d564f3c83615..5fb7b6e09e8e63 100644
--- a/lld/MachO/SyntheticSections.h
+++ b/lld/MachO/SyntheticSections.h
@@ -336,7 +336,7 @@ class ObjCStubsSection final : public SyntheticSection {
 private:
   std::vector<Defined *> symbols;
   std::vector<uint32_t> offsets;
-  int objcMsgSendGotIndex = 0;
+  Symbol *objcMsgSend = nullptr;
 };
 
 // Note that this section may also be targeted by non-lazy bindings. In
diff --git a/lld/MachO/Target.h b/lld/MachO/Target.h
index bc7e09d394d24f..e55e28082c4fea 100644
--- a/lld/MachO/Target.h
+++ b/lld/MachO/Target.h
@@ -121,7 +121,9 @@ class TargetInfo {
   size_t stubHelperHeaderSize;
   size_t stubHelperEntrySize;
   size_t objcStubsFastSize;
-  size_t objcStubsAlignment;
+  size_t objcStubsSmallSize;
+  size_t objcStubsFastAlignment;
+  size_t objcStubsSmallAlignment;
   uint8_t p2WordSize;
   size_t wordSize;
 
diff --git a/lld/test/MachO/arm64-objc-stubs-dyn.s b/lld/test/MachO/arm64-objc-stubs-dyn.s
new file mode 100644
index 00000000000000..9358fc5b31c2ba
--- /dev/null
+++ b/lld/test/MachO/arm64-objc-stubs-dyn.s
@@ -0,0 +1,76 @@
+# REQUIRES: aarch64
+
+# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %s -o %t.o
+# RUN: %lld -arch arm64 -lSystem -U _objc_msgSend -o %t.out %t.o
+# RUN: llvm-otool -vs __TEXT __objc_stubs %t.out | FileCheck %s
+# RUN: %lld -arch arm64 -lSystem -U _objc_msgSend -o %t.out %t.o -dead_strip
+# RUN: llvm-otool -vs __TEXT __objc_stubs %t.out | FileCheck %s
+# RUN: %lld -arch arm64 -lSystem -U _objc_msgSend -o %t.out %t.o -objc_stubs_fast
+# RUN: llvm-otool -vs __TEXT __objc_stubs %t.out | FileCheck %s
+# RUN: %lld -arch arm64 -lSystem -U _objc_msgSend -o %t.out %t.o -objc_stubs_small
+# RUN: llvm-otool -vs __TEXT __stubs  %t.out | FileCheck %s --check-prefix=STUB
+# RUN: llvm-otool -vs __TEXT __objc_stubs %t.out | FileCheck %s --check-prefix=SMALL
+
+# Unlike arm64-objc-stubs.s, in this test, _objc_msgSend is not defined,
+#  which usually binds with libobjc.dylib.
+# 1. -objc_stubs_fast: No change as it uses GOT.
+# 2. -objc_stubs_small: Create a (shared) stub to invoke _objc_msgSend, to minimize the size.
+
+# CHECK: Contents of (__TEXT,__objc_stubs) section
+
+# CHECK-NEXT: _objc_msgSend$foo:
+# CHECK-NEXT: adrp    x1, 8 ; 0x100008000
+# CHECK-NEXT: ldr     x1, [x1, #0x10]
+# CHECK-NEXT: adrp    x16, 4 ; 0x100004000
+# CHECK-NEXT: ldr     x16, [x16]
+# CHECK-NEXT: br      x16
+# CHECK-NEXT: brk     #0x1
+# CHECK-NEXT: brk     #0x1
+# CHECK-NEXT: brk     #0x1
+
+# CHECK-NEXT: _objc_msgSend$length:
+# CHECK-NEXT: adrp    x1, 8 ; 0x100008000
+# CHECK-NEXT: ldr     x1, [x1, #0x18]
+# CHECK-NEXT: adrp    x16, 4 ; 0x100004000
+# CHECK-NEXT: ldr     x16, [x16]
+# CHECK-NEXT: br      x16
+# CHECK-NEXT: brk     #0x1
+# CHECK-NEXT: brk     #0x1
+# CHECK-NEXT: brk     #0x1
+
+# CHECK-EMPTY:
+
+# STUB: Contents of (__TEXT,__stubs) section
+# STUB-NEXT:  adrp    x16, 8 ; 0x100008000
+# STUB-NEXT:  ldr     x16, [x16]
+# STUB-NEXT:  br      x16
+
+# SMALL: Contents of (__TEXT,__objc_stubs) section
+# SMALL-NEXT: _objc_msgSend$foo:
+# SMALL-NEXT: adrp    x1, 8 ; 0x100008000
+# SMALL-NEXT: ldr     x1, [x1, #0x18]
+# SMALL-NEXT: b
+# SMALL-NEXT: _objc_msgSend$length:
+# SMALL-NEXT: adrp    x1, 8 ; 0x100008000
+# SMALL-NEXT: ldr     x1, [x1, #0x20]
+# SMALL-NEXT: b
+
+.section  __TEXT,__objc_methname,cstring_literals
+lselref1:
+  .asciz  "foo"
+lselref2:
+  .asciz  "bar"
+
+.section  __DATA,__objc_selrefs,literal_pointers,no_dead_strip
+.p2align  3
+.quad lselref1
+.quad lselref2
+
+.text
+
+.globl _main
+_main:
+  bl  _objc_msgSend$length
+  bl  _objc_msgSend$foo
+  bl  _objc_msgSend$foo
+  ret
diff --git a/lld/test/MachO/arm64-objc-stubs.s b/lld/test/MachO/arm64-objc-stubs.s
index feba40ac36d840..1b8ebff9243004 100644
--- a/lld/test/MachO/arm64-objc-stubs.s
+++ b/lld/test/MachO/arm64-objc-stubs.s
@@ -7,10 +7,10 @@
 # RUN: llvm-otool -vs __TEXT __objc_stubs %t.out | FileCheck %s
 # RUN: %lld -arch arm64 -lSystem -o %t.out %t.o -objc_stubs_fast
 # RUN: llvm-otool -vs __TEXT __objc_stubs %t.out | FileCheck %s
-# RUN: %no-fatal-warnings-lld -arch arm64 -lSystem -o %t.out %t.o -objc_stubs_small 2>&1 | FileCheck %s --check-prefix=WARNING
-# RUN: llvm-otool -vs __TEXT __objc_stubs %t.out | FileCheck %s
-
-# WARNING: warning: -objc_stubs_small is not yet implemented, defaulting to -objc_stubs_fast
+# RUN: llvm-otool -l %t.out | FileCheck %s --check-prefix=FASTALIGN
+# RUN: %lld -arch arm64 -lSystem -o %t.out %t.o -objc_stubs_small
+# RUN: llvm-otool -vs __TEXT __objc_stubs  %t.out | FileCheck %s --check-prefix=SMALL
+# RUN: llvm-otool -l %t.out | FileCheck %s --check-prefix=SMALLALIGN
 
 # CHECK: Contents of (__TEXT,__objc_stubs) section
 
@@ -36,6 +36,30 @@
 
 # CHECK-EMPTY:
 
+# FASTALIGN:       sectname __objc_stubs
+# FASTALIGN-NEXT:   segname __TEXT
+# FASTALIGN-NEXT:      addr
+# FASTALIGN-NEXT:      size
+# FASTALIGN-NEXT:    offset
+# FASTALIGN-NEXT:     align 2^5 (32)
+
+# SMALL: _objc_msgSend$foo:
+# SMALL-NEXT: adrp    x1, 4 ; 0x100004000
+# SMALL-NEXT: ldr     x1, [x1, #0x10]
+# SMALL-NEXT: b
+
+# SMALL-NEXT: _objc_msgSend$length:
+# SMALL-NEXT: adrp    x1, 4 ; 0x100004000
+# SMALL-NEXT: ldr     x1, [x1, #0x18]
+# SMALL-NEXT: b
+
+# SMALLALIGN:       sectname __objc_stubs
+# SMALLALIGN-NEXT:   segname __TEXT
+# SMALLALIGN-NEXT:      addr
+# SMALLALIGN-NEXT:      size
+# SMALLALIGN-NEXT:    offset
+# SMALLALIGN-NEXT:     align 2^2 (4)
+
 .section  __TEXT,__objc_methname,cstring_literals
 lselref1:
   .asciz  "foo"

@llvmbot
Copy link
Collaborator

llvmbot commented Jan 19, 2024

@llvm/pr-subscribers-lld-macho

Author: Kyungwoo Lee (kyulee-com)

Changes

This patch implements -objc_stubs_small targeting arm64, aiming to align with ld64's behavior.

  1. -objc_stubs_fast: As previously implemented, this always uses the Global Offset Table (GOT) to invoke objc_msgSend. The alignment of the objc stub is 32 bytes.
  2. -objc_stubs_small: This behavior depends on whether objc_msgSend is defined. If it is, it directly jumps to objc_msgSend. If not, it creates another stub to indirectly jump to objc_msgSend, minimizing the size. The alignment of the objc stub in this case is 4 bytes.

Full diff: https://github.com/llvm/llvm-project/pull/78665.diff

9 Files Affected:

  • (modified) lld/MachO/Arch/ARM64.cpp (+18-5)
  • (modified) lld/MachO/Arch/ARM64Common.h (+28-4)
  • (modified) lld/MachO/Arch/X86_64.cpp (+1-1)
  • (modified) lld/MachO/Driver.cpp (+7-3)
  • (modified) lld/MachO/SyntheticSections.cpp (+52-11)
  • (modified) lld/MachO/SyntheticSections.h (+1-1)
  • (modified) lld/MachO/Target.h (+3-1)
  • (added) lld/test/MachO/arm64-objc-stubs-dyn.s (+76)
  • (modified) lld/test/MachO/arm64-objc-stubs.s (+28-4)
diff --git a/lld/MachO/Arch/ARM64.cpp b/lld/MachO/Arch/ARM64.cpp
index e3781763c6102b..7b18292da6d8c0 100644
--- a/lld/MachO/Arch/ARM64.cpp
+++ b/lld/MachO/Arch/ARM64.cpp
@@ -117,13 +117,24 @@ static constexpr uint32_t objcStubsFastCode[] = {
     0xd4200020, // brk   #0x1
 };
 
+static constexpr uint32_t objcStubsSmallCode[] = {
+    0x90000001, // adrp  x1, __objc_selrefs@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 selrefsVA,
-                                 uint64_t selectorIndex, uint64_t gotAddr,
+                                 uint64_t selectorIndex, uint64_t msgSendAddr,
                                  uint64_t msgSendIndex) const {
-  ::writeObjCMsgSendStub<LP64>(buf, objcStubsFastCode, sym, stubsAddr,
-                               stubOffset, selrefsVA, selectorIndex, gotAddr,
-                               msgSendIndex);
+  if (config->objcStubsMode == ObjCStubsMode::fast)
+    ::writeObjCMsgSendFastStub<LP64>(buf, objcStubsFastCode, sym, stubsAddr,
+                                     stubOffset, selrefsVA, selectorIndex,
+                                     msgSendAddr, msgSendIndex);
+  else
+    ::writeObjCMsgSendSmallStub<LP64>(buf, objcStubsSmallCode, sym, stubsAddr,
+                                      stubOffset, selrefsVA, selectorIndex,
+                                      msgSendAddr, msgSendIndex);
 }
 
 // A thunk is the relaxed variation of stubCode. We don't need the
@@ -157,7 +168,9 @@ ARM64::ARM64() : ARM64Common(LP64()) {
   thunkSize = sizeof(thunkCode);
 
   objcStubsFastSize = sizeof(objcStubsFastCode);
-  objcStubsAlignment = 32;
+  objcStubsFastAlignment = 32;
+  objcStubsSmallSize = sizeof(objcStubsSmallCode);
+  objcStubsSmallAlignment = 4;
 
   // 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/Arch/ARM64Common.h b/lld/MachO/Arch/ARM64Common.h
index 9cfccb6cec7614..b038b6200f4d57 100644
--- a/lld/MachO/Arch/ARM64Common.h
+++ b/lld/MachO/Arch/ARM64Common.h
@@ -154,10 +154,10 @@ inline void writeStubHelperEntry(uint8_t *buf8,
 
 template <class LP>
 inline void
-writeObjCMsgSendStub(uint8_t *buf, const uint32_t objcStubsFastCode[8],
-                     Symbol *sym, uint64_t stubsAddr, uint64_t stubOffset,
-                     uint64_t selrefsVA, uint64_t selectorIndex,
-                     uint64_t gotAddr, uint64_t msgSendIndex) {
+writeObjCMsgSendFastStub(uint8_t *buf, const uint32_t objcStubsFastCode[8],
+                         Symbol *sym, uint64_t stubsAddr, uint64_t stubOffset,
+                         uint64_t selrefsVA, uint64_t selectorIndex,
+                         uint64_t gotAddr, uint64_t msgSendIndex) {
   SymbolDiagnostic d = {sym, sym->getName()};
   auto *buf32 = reinterpret_cast<uint32_t *>(buf);
 
@@ -180,6 +180,30 @@ writeObjCMsgSendStub(uint8_t *buf, const uint32_t objcStubsFastCode[8],
   buf32[7] = objcStubsFastCode[7];
 }
 
+template <class LP>
+inline void
+writeObjCMsgSendSmallStub(uint8_t *buf, const uint32_t objcStubsSmallCode[3],
+                          Symbol *sym, uint64_t stubsAddr, uint64_t stubOffset,
+                          uint64_t selrefsVA, uint64_t selectorIndex,
+                          uint64_t msgSendAddr, uint64_t msgSendIndex) {
+  SymbolDiagnostic d = {sym, sym->getName()};
+  auto *buf32 = reinterpret_cast<uint32_t *>(buf);
+
+  auto pcPageBits = [stubsAddr, stubOffset](int i) {
+    return pageBits(stubsAddr + stubOffset + i * sizeof(uint32_t));
+  };
+
+  uint64_t selectorOffset = selectorIndex * LP::wordSize;
+  encodePage21(&buf32[0], d, objcStubsSmallCode[0],
+               pageBits(selrefsVA + selectorOffset) - pcPageBits(0));
+  encodePageOff12(&buf32[1], d, objcStubsSmallCode[1],
+                  selrefsVA + selectorOffset);
+  uint64_t msgSendStubVA = msgSendAddr + msgSendIndex * target->stubSize;
+  uint64_t pcVA = stubsAddr + stubOffset + 2 * sizeof(uint32_t);
+  encodeBranch26(&buf32[2], {nullptr, "objc_msgSend stub"},
+                 objcStubsSmallCode[2], msgSendStubVA - pcVA);
+}
+
 } // namespace lld::macho
 
 #endif
diff --git a/lld/MachO/Arch/X86_64.cpp b/lld/MachO/Arch/X86_64.cpp
index a0d4e1a28a14a2..55fbdefb75db28 100644
--- a/lld/MachO/Arch/X86_64.cpp
+++ b/lld/MachO/Arch/X86_64.cpp
@@ -214,7 +214,7 @@ X86_64::X86_64() : TargetInfo(LP64()) {
   stubHelperEntrySize = sizeof(stubHelperEntry);
 
   objcStubsFastSize = sizeof(objcStubsFastCode);
-  objcStubsAlignment = 1;
+  objcStubsFastAlignment = 1;
 
   relocAttrs = {relocAttrsArray.data(), relocAttrsArray.size()};
 }
diff --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp
index 401459a054394e..d3d9a56f85fe0e 100644
--- a/lld/MachO/Driver.cpp
+++ b/lld/MachO/Driver.cpp
@@ -823,9 +823,13 @@ static ObjCStubsMode getObjCStubsMode(const ArgList &args) {
   if (!arg)
     return ObjCStubsMode::fast;
 
-  if (arg->getOption().getID() == OPT_objc_stubs_small)
-    warn("-objc_stubs_small is not yet implemented, defaulting to "
-         "-objc_stubs_fast");
+  if (arg->getOption().getID() == OPT_objc_stubs_small) {
+    if (is_contained({AK_arm64e, AK_arm64}, config->arch()))
+      return ObjCStubsMode::small;
+    else
+      warn("-objc_stubs_small is not yet implemented, defaulting to "
+           "-objc_stubs_fast");
+  }
   return ObjCStubsMode::fast;
 }
 
diff --git a/lld/MachO/SyntheticSections.cpp b/lld/MachO/SyntheticSections.cpp
index e123dcb6803c1e..6e9effe3d3f3f5 100644
--- a/lld/MachO/SyntheticSections.cpp
+++ b/lld/MachO/SyntheticSections.cpp
@@ -809,7 +809,9 @@ void StubHelperSection::setUp() {
 ObjCStubsSection::ObjCStubsSection()
     : SyntheticSection(segment_names::text, section_names::objcStubs) {
   flags = S_ATTR_SOME_INSTRUCTIONS | S_ATTR_PURE_INSTRUCTIONS;
-  align = target->objcStubsAlignment;
+  align = config->objcStubsMode == ObjCStubsMode::fast
+              ? target->objcStubsFastAlignment
+              : target->objcStubsSmallAlignment;
 }
 
 void ObjCStubsSection::addEntry(Symbol *sym) {
@@ -817,10 +819,14 @@ void ObjCStubsSection::addEntry(Symbol *sym) {
   StringRef methname = sym->getName().drop_front(symbolPrefix.size());
   offsets.push_back(
       in.objcMethnameSection->getStringOffset(methname).outSecOff);
+
+  auto stubSize = config->objcStubsMode == ObjCStubsMode::fast
+                      ? target->objcStubsFastSize
+                      : target->objcStubsSmallSize;
   Defined *newSym = replaceSymbol<Defined>(
       sym, sym->getName(), nullptr, isec,
-      /*value=*/symbols.size() * target->objcStubsFastSize,
-      /*size=*/target->objcStubsFastSize,
+      /*value=*/symbols.size() * stubSize,
+      /*size=*/stubSize,
       /*isWeakDef=*/false, /*isExternal=*/true, /*isPrivateExtern=*/true,
       /*includeInSymtab=*/true, /*isReferencedDynamically=*/false,
       /*noDeadStrip=*/false);
@@ -828,12 +834,25 @@ void ObjCStubsSection::addEntry(Symbol *sym) {
 }
 
 void ObjCStubsSection::setUp() {
-  Symbol *objcMsgSend = symtab->addUndefined("_objc_msgSend", /*file=*/nullptr,
-                                             /*isWeakRef=*/false);
+  objcMsgSend = symtab->addUndefined("_objc_msgSend", /*file=*/nullptr,
+                                     /*isWeakRef=*/false);
+  if (auto *undefined = dyn_cast<Undefined>(objcMsgSend))
+    treatUndefinedSymbol(*undefined,
+                         "lazy binding (normally in libobjc.dylib)");
   objcMsgSend->used = true;
-  in.got->addEntry(objcMsgSend);
-  assert(objcMsgSend->isInGot());
-  objcMsgSendGotIndex = objcMsgSend->gotIndex;
+  if (config->objcStubsMode == ObjCStubsMode::fast) {
+    in.got->addEntry(objcMsgSend);
+    assert(objcMsgSend->isInGot());
+  } else {
+    assert(config->objcStubsMode == ObjCStubsMode::small);
+    // In line with ld64's behavior, when objc_msgSend is a direct symbol,
+    // we directly reference it.
+    // In other cases, typically when binding in libobjc.dylib,
+    // we generate a stub to invoke objc_msgSend.
+    auto *d = dyn_cast<Defined>(objcMsgSend);
+    if (!d)
+      in.stubs->addEntry(objcMsgSend);
+  }
 
   size_t size = offsets.size() * target->wordSize;
   uint8_t *selrefsData = bAlloc().Allocate<uint8_t>(size);
@@ -863,7 +882,10 @@ void ObjCStubsSection::setUp() {
 }
 
 uint64_t ObjCStubsSection::getSize() const {
-  return target->objcStubsFastSize * symbols.size();
+  auto stubSize = config->objcStubsMode == ObjCStubsMode::fast
+                      ? target->objcStubsFastSize
+                      : target->objcStubsSmallSize;
+  return stubSize * symbols.size();
 }
 
 void ObjCStubsSection::writeTo(uint8_t *buf) const {
@@ -871,12 +893,31 @@ void ObjCStubsSection::writeTo(uint8_t *buf) const {
   assert(in.objcSelrefs->isFinal);
 
   uint64_t stubOffset = 0;
+  uint64_t objcMsgSendAddr;
+  uint64_t objcStubSize;
+  uint64_t objcMsgSendIndex;
+  if (config->objcStubsMode == ObjCStubsMode::fast) {
+    objcStubSize = target->objcStubsFastSize;
+    objcMsgSendAddr = in.got->addr;
+    objcMsgSendIndex = objcMsgSend->gotIndex;
+  } else {
+    assert(config->objcStubsMode == ObjCStubsMode::small);
+    objcStubSize = target->objcStubsSmallSize;
+    if (auto *d = dyn_cast<Defined>(objcMsgSend)) {
+      objcMsgSendAddr = d->getVA();
+      objcMsgSendIndex = 0;
+    } else {
+      objcMsgSendAddr = in.stubs->addr;
+      objcMsgSendIndex = objcMsgSend->stubsIndex;
+    }
+  }
+
   for (size_t i = 0, n = symbols.size(); i < n; ++i) {
     Defined *sym = symbols[i];
     target->writeObjCMsgSendStub(buf + stubOffset, sym, in.objcStubs->addr,
                                  stubOffset, in.objcSelrefs->getVA(), i,
-                                 in.got->addr, objcMsgSendGotIndex);
-    stubOffset += target->objcStubsFastSize;
+                                 objcMsgSendAddr, objcMsgSendIndex);
+    stubOffset += objcStubSize;
   }
 }
 
diff --git a/lld/MachO/SyntheticSections.h b/lld/MachO/SyntheticSections.h
index e9d564f3c83615..5fb7b6e09e8e63 100644
--- a/lld/MachO/SyntheticSections.h
+++ b/lld/MachO/SyntheticSections.h
@@ -336,7 +336,7 @@ class ObjCStubsSection final : public SyntheticSection {
 private:
   std::vector<Defined *> symbols;
   std::vector<uint32_t> offsets;
-  int objcMsgSendGotIndex = 0;
+  Symbol *objcMsgSend = nullptr;
 };
 
 // Note that this section may also be targeted by non-lazy bindings. In
diff --git a/lld/MachO/Target.h b/lld/MachO/Target.h
index bc7e09d394d24f..e55e28082c4fea 100644
--- a/lld/MachO/Target.h
+++ b/lld/MachO/Target.h
@@ -121,7 +121,9 @@ class TargetInfo {
   size_t stubHelperHeaderSize;
   size_t stubHelperEntrySize;
   size_t objcStubsFastSize;
-  size_t objcStubsAlignment;
+  size_t objcStubsSmallSize;
+  size_t objcStubsFastAlignment;
+  size_t objcStubsSmallAlignment;
   uint8_t p2WordSize;
   size_t wordSize;
 
diff --git a/lld/test/MachO/arm64-objc-stubs-dyn.s b/lld/test/MachO/arm64-objc-stubs-dyn.s
new file mode 100644
index 00000000000000..9358fc5b31c2ba
--- /dev/null
+++ b/lld/test/MachO/arm64-objc-stubs-dyn.s
@@ -0,0 +1,76 @@
+# REQUIRES: aarch64
+
+# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %s -o %t.o
+# RUN: %lld -arch arm64 -lSystem -U _objc_msgSend -o %t.out %t.o
+# RUN: llvm-otool -vs __TEXT __objc_stubs %t.out | FileCheck %s
+# RUN: %lld -arch arm64 -lSystem -U _objc_msgSend -o %t.out %t.o -dead_strip
+# RUN: llvm-otool -vs __TEXT __objc_stubs %t.out | FileCheck %s
+# RUN: %lld -arch arm64 -lSystem -U _objc_msgSend -o %t.out %t.o -objc_stubs_fast
+# RUN: llvm-otool -vs __TEXT __objc_stubs %t.out | FileCheck %s
+# RUN: %lld -arch arm64 -lSystem -U _objc_msgSend -o %t.out %t.o -objc_stubs_small
+# RUN: llvm-otool -vs __TEXT __stubs  %t.out | FileCheck %s --check-prefix=STUB
+# RUN: llvm-otool -vs __TEXT __objc_stubs %t.out | FileCheck %s --check-prefix=SMALL
+
+# Unlike arm64-objc-stubs.s, in this test, _objc_msgSend is not defined,
+#  which usually binds with libobjc.dylib.
+# 1. -objc_stubs_fast: No change as it uses GOT.
+# 2. -objc_stubs_small: Create a (shared) stub to invoke _objc_msgSend, to minimize the size.
+
+# CHECK: Contents of (__TEXT,__objc_stubs) section
+
+# CHECK-NEXT: _objc_msgSend$foo:
+# CHECK-NEXT: adrp    x1, 8 ; 0x100008000
+# CHECK-NEXT: ldr     x1, [x1, #0x10]
+# CHECK-NEXT: adrp    x16, 4 ; 0x100004000
+# CHECK-NEXT: ldr     x16, [x16]
+# CHECK-NEXT: br      x16
+# CHECK-NEXT: brk     #0x1
+# CHECK-NEXT: brk     #0x1
+# CHECK-NEXT: brk     #0x1
+
+# CHECK-NEXT: _objc_msgSend$length:
+# CHECK-NEXT: adrp    x1, 8 ; 0x100008000
+# CHECK-NEXT: ldr     x1, [x1, #0x18]
+# CHECK-NEXT: adrp    x16, 4 ; 0x100004000
+# CHECK-NEXT: ldr     x16, [x16]
+# CHECK-NEXT: br      x16
+# CHECK-NEXT: brk     #0x1
+# CHECK-NEXT: brk     #0x1
+# CHECK-NEXT: brk     #0x1
+
+# CHECK-EMPTY:
+
+# STUB: Contents of (__TEXT,__stubs) section
+# STUB-NEXT:  adrp    x16, 8 ; 0x100008000
+# STUB-NEXT:  ldr     x16, [x16]
+# STUB-NEXT:  br      x16
+
+# SMALL: Contents of (__TEXT,__objc_stubs) section
+# SMALL-NEXT: _objc_msgSend$foo:
+# SMALL-NEXT: adrp    x1, 8 ; 0x100008000
+# SMALL-NEXT: ldr     x1, [x1, #0x18]
+# SMALL-NEXT: b
+# SMALL-NEXT: _objc_msgSend$length:
+# SMALL-NEXT: adrp    x1, 8 ; 0x100008000
+# SMALL-NEXT: ldr     x1, [x1, #0x20]
+# SMALL-NEXT: b
+
+.section  __TEXT,__objc_methname,cstring_literals
+lselref1:
+  .asciz  "foo"
+lselref2:
+  .asciz  "bar"
+
+.section  __DATA,__objc_selrefs,literal_pointers,no_dead_strip
+.p2align  3
+.quad lselref1
+.quad lselref2
+
+.text
+
+.globl _main
+_main:
+  bl  _objc_msgSend$length
+  bl  _objc_msgSend$foo
+  bl  _objc_msgSend$foo
+  ret
diff --git a/lld/test/MachO/arm64-objc-stubs.s b/lld/test/MachO/arm64-objc-stubs.s
index feba40ac36d840..1b8ebff9243004 100644
--- a/lld/test/MachO/arm64-objc-stubs.s
+++ b/lld/test/MachO/arm64-objc-stubs.s
@@ -7,10 +7,10 @@
 # RUN: llvm-otool -vs __TEXT __objc_stubs %t.out | FileCheck %s
 # RUN: %lld -arch arm64 -lSystem -o %t.out %t.o -objc_stubs_fast
 # RUN: llvm-otool -vs __TEXT __objc_stubs %t.out | FileCheck %s
-# RUN: %no-fatal-warnings-lld -arch arm64 -lSystem -o %t.out %t.o -objc_stubs_small 2>&1 | FileCheck %s --check-prefix=WARNING
-# RUN: llvm-otool -vs __TEXT __objc_stubs %t.out | FileCheck %s
-
-# WARNING: warning: -objc_stubs_small is not yet implemented, defaulting to -objc_stubs_fast
+# RUN: llvm-otool -l %t.out | FileCheck %s --check-prefix=FASTALIGN
+# RUN: %lld -arch arm64 -lSystem -o %t.out %t.o -objc_stubs_small
+# RUN: llvm-otool -vs __TEXT __objc_stubs  %t.out | FileCheck %s --check-prefix=SMALL
+# RUN: llvm-otool -l %t.out | FileCheck %s --check-prefix=SMALLALIGN
 
 # CHECK: Contents of (__TEXT,__objc_stubs) section
 
@@ -36,6 +36,30 @@
 
 # CHECK-EMPTY:
 
+# FASTALIGN:       sectname __objc_stubs
+# FASTALIGN-NEXT:   segname __TEXT
+# FASTALIGN-NEXT:      addr
+# FASTALIGN-NEXT:      size
+# FASTALIGN-NEXT:    offset
+# FASTALIGN-NEXT:     align 2^5 (32)
+
+# SMALL: _objc_msgSend$foo:
+# SMALL-NEXT: adrp    x1, 4 ; 0x100004000
+# SMALL-NEXT: ldr     x1, [x1, #0x10]
+# SMALL-NEXT: b
+
+# SMALL-NEXT: _objc_msgSend$length:
+# SMALL-NEXT: adrp    x1, 4 ; 0x100004000
+# SMALL-NEXT: ldr     x1, [x1, #0x18]
+# SMALL-NEXT: b
+
+# SMALLALIGN:       sectname __objc_stubs
+# SMALLALIGN-NEXT:   segname __TEXT
+# SMALLALIGN-NEXT:      addr
+# SMALLALIGN-NEXT:      size
+# SMALLALIGN-NEXT:    offset
+# SMALLALIGN-NEXT:     align 2^2 (4)
+
 .section  __TEXT,__objc_methname,cstring_literals
 lselref1:
   .asciz  "foo"

lld/MachO/SyntheticSections.cpp Outdated Show resolved Hide resolved
lld/test/MachO/arm64-objc-stubs-dyn.s Show resolved Hide resolved
lld/test/MachO/arm64-objc-stubs.s Show resolved Hide resolved
lld/test/MachO/arm64-objc-stubs-dyn.s Show resolved Hide resolved
lld/test/MachO/arm64-objc-stubs.s Show resolved Hide resolved
Copy link
Contributor

@int3 int3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@kyulee-com kyulee-com merged commit 77e204c into llvm:main Jan 23, 2024
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants