[lld] [WIP/RFC][ELF][PowerPC] Don't assume TOC pointer is valid in IPLT entries (PR #207555)

Jessica Clarke via llvm-commits llvm-commits at lists.llvm.org
Sat Jul 4 20:17:33 PDT 2026


https://github.com/jrtc27 updated https://github.com/llvm/llvm-project/pull/207555

>From ab1f01e03755484bb8334a5fd364d48901b69222 Mon Sep 17 00:00:00 2001
From: Jessica Clarke <jrtc27 at jrtc27.com>
Date: Sun, 5 Jul 2026 04:17:22 +0100
Subject: [PATCH] [ELF][PowerPC] Don't assume TOC pointer is valid in IPLT
 entries

Unlike normal PLT entries, IPLT entries can be called indirectly even
when in PIEs/DSOs, and so there's no guarantee on what's in the TOC
pointer register at that time. Therefore we must emit variants of the
existing code that work without it, whether r12-relative (playing the
same role as MIPS's $25) in the same number of instructions, or first
retrieving PC in an i386-like manner, being careful not to clobber LR.

Normal canonical PLTs still look broken on 64-bit PowerPC as they use
the TOC pointer register too (whereas on 32-bit PowerPC, since they
should only exist in PDEs, the non-PIC absolute addressing case should
handle them), but that's more awkward as they live in the normal PLT.
However, the extensive use of the TOC may mean in practice we rarely, if
ever, have need for normal canonical PLTs. We should probably treat this
case the same as on i386, where it's an error due to the use of %ebx in
PLT entries.
---
 lld/ELF/Arch/PPC.cpp                          | 12 ++--
 lld/ELF/Arch/PPC64.cpp                        |  6 +-
 lld/ELF/Thunks.cpp                            | 56 +++++++++++++------
 lld/ELF/Thunks.h                              |  8 ++-
 lld/test/ELF/ppc32-ifunc-nonpreemptible-pic.s |  8 +--
 lld/test/ELF/ppc64-ifunc.s                    | 16 +++---
 6 files changed, 66 insertions(+), 40 deletions(-)

diff --git a/lld/ELF/Arch/PPC.cpp b/lld/ELF/Arch/PPC.cpp
index fa64271e12380..901757ccbaced 100644
--- a/lld/ELF/Arch/PPC.cpp
+++ b/lld/ELF/Arch/PPC.cpp
@@ -91,7 +91,7 @@ void elf::writePPC32GlinkSection(Ctx &ctx, uint8_t *buf, size_t numEntries) {
   if (!ctx.arg.isPic) {
     for (const Symbol *sym :
          cast<PPC32GlinkSection>(*ctx.in.plt).canonical_plts) {
-      writePPC32PltCallStub(ctx, buf, sym->getGotPltVA(ctx), nullptr, 0);
+      writePPC32PltCallStub(ctx, buf, glink, sym->getGotPltVA(ctx), nullptr, 0);
       buf += 16;
       glink += 16;
     }
@@ -179,7 +179,7 @@ PPC::PPC(Ctx &ctx) : TargetInfo(ctx) {
   gotPltHeaderEntriesNum = 0;
   pltHeaderSize = 0;
   pltEntrySize = 4;
-  ipltEntrySize = 16;
+  ipltEntrySize = ctx.arg.isPic ? 32 : 16;
 
   needsThunks = true;
 
@@ -199,10 +199,10 @@ void PPC::initTargetSpecificSections() {
 }
 
 void PPC::writeIplt(uint8_t *buf, const Symbol &sym,
-                    uint64_t /*pltEntryAddr*/) const {
-  // In -pie or -shared mode, assume r30 points to .got2+0x8000, and use a
-  // .got2.plt_pic32. thunk.
-  writePPC32PltCallStub(ctx, buf, sym.getGotPltVA(ctx), sym.file, 0x8000);
+                    uint64_t pltEntryAddr) const {
+  // In -pie or -shared mode we can't rely on r30 for indirect calls.
+  writePPC32PltCallStub(ctx, buf, pltEntryAddr, sym.getGotPltVA(ctx), sym.file,
+                        std::nullopt);
 }
 
 void PPC::writeGotHeader(uint8_t *buf) const {
diff --git a/lld/ELF/Arch/PPC64.cpp b/lld/ELF/Arch/PPC64.cpp
index 9f49605d89c58..3753920187a3d 100644
--- a/lld/ELF/Arch/PPC64.cpp
+++ b/lld/ELF/Arch/PPC64.cpp
@@ -1061,9 +1061,9 @@ void PPC64::writePlt(uint8_t *buf, const Symbol &sym,
 }
 
 void PPC64::writeIplt(uint8_t *buf, const Symbol &sym,
-                      uint64_t /*pltEntryAddr*/) const {
-  writePPC64LoadAndBranch(ctx, buf,
-                          sym.getGotPltVA(ctx) - getPPC64TocBase(ctx));
+                      uint64_t pltEntryAddr) const {
+  writePPC64LoadAndBranch(ctx, buf, pltEntryAddr, sym.getGotPltVA(ctx),
+                          /*toc=*/false);
 }
 
 static bool isTocOptType(RelType type) {
diff --git a/lld/ELF/Thunks.cpp b/lld/ELF/Thunks.cpp
index 1f161685f178d..34f78ec08d4f7 100644
--- a/lld/ELF/Thunks.cpp
+++ b/lld/ELF/Thunks.cpp
@@ -1316,8 +1316,9 @@ InputSection *MicroMipsR6Thunk::getTargetInputSection() const {
   return dyn_cast<InputSection>(dr.section);
 }
 
-void elf::writePPC32PltCallStub(Ctx &ctx, uint8_t *buf, uint64_t gotPltVA,
-                                const InputFile *file, int64_t addend) {
+void elf::writePPC32PltCallStub(Ctx &ctx, uint8_t *buf, uint64_t p,
+                                uint64_t gotPltVA, const InputFile *file,
+                                std::optional<int64_t> addend) {
   if (!ctx.arg.isPic) {
     write32(ctx, buf + 0, 0x3d600000 | (gotPltVA + 0x8000) >> 16); // lis r11,ha
     write32(ctx, buf + 4, 0x816b0000 | (uint16_t)gotPltVA); // lwz r11,l(r11)
@@ -1326,13 +1327,23 @@ void elf::writePPC32PltCallStub(Ctx &ctx, uint8_t *buf, uint64_t gotPltVA,
     return;
   }
   uint32_t offset;
-  if (addend >= 0x8000) {
+  if (!addend) {
+    // We're a position-independent IPLT entry, so cannot assume anything about
+    // what value the caller left in r30 as this could be an indirect call.
+    write32(ctx, buf + 0, 0x7d6802a6);  //    mflr r11
+    write32(ctx, buf + 4, 0x429f0005);  //    bcl 20, 31, 1f
+    write32(ctx, buf + 8, 0x7fc802a6);  // 1: mflr r30
+    write32(ctx, buf + 12, 0x7d6803a6); //    mtlr r11
+    offset = gotPltVA - p - 8;
+    buf += 16;
+    p += 16;
+  } else if (*addend >= 0x8000) {
     // The stub loads an address relative to r30 (.got2+Addend). Addend is
     // almost always 0x8000. The address of .got2 is different in another object
     // file, so a stub cannot be shared.
     offset = gotPltVA -
              (ctx.in.ppc32Got2->getParent()->getVA() +
-              (file->ppc32Got2 ? file->ppc32Got2->outSecOff : 0) + addend);
+              (file->ppc32Got2 ? file->ppc32Got2->outSecOff : 0) + *addend);
   } else {
     // The stub loads an address relative to _GLOBAL_OFFSET_TABLE_ (which is
     // currently the address of .got).
@@ -1353,7 +1364,8 @@ void elf::writePPC32PltCallStub(Ctx &ctx, uint8_t *buf, uint64_t gotPltVA,
 }
 
 void PPC32PltCallStub::writeTo(uint8_t *buf) {
-  writePPC32PltCallStub(ctx, buf, destination.getGotPltVA(ctx), file, addend);
+  writePPC32PltCallStub(ctx, buf, getThunkTargetSym()->getVA(ctx),
+                        destination.getGotPltVA(ctx), file, addend);
 }
 
 void PPC32PltCallStub::addSymbols(ThunkSection &isec) {
@@ -1402,21 +1414,32 @@ void PPC32LongThunk::writeTo(uint8_t *buf) {
   write32(ctx, buf + 4, 0x4e800420); // bctr
 }
 
-void elf::writePPC64LoadAndBranch(Ctx &ctx, uint8_t *buf, int64_t offset) {
+void elf::writePPC64LoadAndBranch(Ctx &ctx, uint8_t *buf, uint64_t p,
+                                  uint64_t addr, bool toc) {
+  uint64_t offset;
+  uint32_t reg;
+  if (toc) {
+    offset = addr - getPPC64TocBase(ctx);
+    reg = 2;
+  } else {
+    offset = addr - p;
+    reg = 12;
+  }
   uint16_t offHa = (offset + 0x8000) >> 16;
   uint16_t offLo = offset & 0xffff;
 
-  write32(ctx, buf + 0, 0x3d820000 | offHa); // addis r12, r2, OffHa
+  write32(ctx, buf + 0,
+          0x3d800000 | (reg << 16) | offHa); // addis r12, r[2|12], OffHa
   write32(ctx, buf + 4, 0xe98c0000 | offLo); // ld    r12, OffLo(r12)
   write32(ctx, buf + 8, 0x7d8903a6);         // mtctr r12
   write32(ctx, buf + 12, 0x4e800420);        // bctr
 }
 
 void PPC64PltCallStub::writeTo(uint8_t *buf) {
-  int64_t offset = destination.getGotPltVA(ctx) - getPPC64TocBase(ctx);
   // Save the TOC pointer to the save-slot reserved in the call frame.
   write32(ctx, buf + 0, 0xf8410018); // std     r2,24(r1)
-  writePPC64LoadAndBranch(ctx, buf + 4, offset);
+  writePPC64LoadAndBranch(ctx, buf + 4, getThunkTargetSym()->getVA(ctx) + 4,
+                          destination.getGotPltVA(ctx));
 }
 
 void PPC64PltCallStub::addSymbols(ThunkSection &isec) {
@@ -1456,10 +1479,10 @@ void PPC64R2SaveStub::writeTo(uint8_t *buf) {
     write32(ctx, buf + nextInstOffset + 4, BCTR);  // bctr
   } else {
     ctx.in.ppc64LongBranchTarget->addEntry(&destination, addend);
-    const int64_t offsetFromTOC =
-        ctx.in.ppc64LongBranchTarget->getEntryVA(&destination, addend) -
-        getPPC64TocBase(ctx);
-    writePPC64LoadAndBranch(ctx, buf + 4, offsetFromTOC);
+    const uint64_t addr =
+        ctx.in.ppc64LongBranchTarget->getEntryVA(&destination, addend);
+    writePPC64LoadAndBranch(ctx, buf + 4, getThunkTargetSym()->getVA(ctx) + 4,
+                            addr);
   }
 }
 
@@ -1519,10 +1542,9 @@ bool PPC64R12SetupStub::isCompatibleWith(const InputSection &isec,
 }
 
 void PPC64LongBranchThunk::writeTo(uint8_t *buf) {
-  int64_t offset =
-      ctx.in.ppc64LongBranchTarget->getEntryVA(&destination, addend) -
-      getPPC64TocBase(ctx);
-  writePPC64LoadAndBranch(ctx, buf, offset);
+  uint64_t addr =
+      ctx.in.ppc64LongBranchTarget->getEntryVA(&destination, addend);
+  writePPC64LoadAndBranch(ctx, buf, getThunkTargetSym()->getVA(ctx), addr);
 }
 
 void PPC64LongBranchThunk::addSymbols(ThunkSection &isec) {
diff --git a/lld/ELF/Thunks.h b/lld/ELF/Thunks.h
index 446345b8517f9..ee5befeee843a 100644
--- a/lld/ELF/Thunks.h
+++ b/lld/ELF/Thunks.h
@@ -83,9 +83,11 @@ std::unique_ptr<Thunk> addThunk(Ctx &, const InputSection &isec,
 // are restricted.
 std::unique_ptr<Thunk> addLandingPadThunk(Ctx &, Symbol &s, int64_t a);
 
-void writePPC32PltCallStub(Ctx &, uint8_t *buf, uint64_t gotPltVA,
-                           const InputFile *file, int64_t addend);
-void writePPC64LoadAndBranch(Ctx &, uint8_t *buf, int64_t offset);
+void writePPC32PltCallStub(Ctx &, uint8_t *buf, uint64_t p, uint64_t gotPltVA,
+                           const InputFile *file,
+                           std::optional<int64_t> addend);
+void writePPC64LoadAndBranch(Ctx &, uint8_t *buf, uint64_t p, uint64_t addr,
+                             bool toc = true);
 
 } // namespace lld::elf
 
diff --git a/lld/test/ELF/ppc32-ifunc-nonpreemptible-pic.s b/lld/test/ELF/ppc32-ifunc-nonpreemptible-pic.s
index c9a0381b610a4..62c46586ed1a3 100644
--- a/lld/test/ELF/ppc32-ifunc-nonpreemptible-pic.s
+++ b/lld/test/ELF/ppc32-ifunc-nonpreemptible-pic.s
@@ -10,16 +10,16 @@
 # RUN: llvm-readelf -x .got2 %t | FileCheck --check-prefix=HEX2 %s
 
 # RELOC:      .rela.dyn {
-# RELOC-NEXT:   0x3022C R_PPC_RELATIVE - 0x101A0
-# RELOC-NEXT:   0x30230 R_PPC_IRELATIVE - 0x10188
+# RELOC-NEXT:   0x3023C R_PPC_RELATIVE - 0x101A0
+# RELOC-NEXT:   0x30240 R_PPC_IRELATIVE - 0x10188
 # RELOC-NEXT: }
 
 # SYM: 000101a0 0 FUNC GLOBAL DEFAULT {{.*}} func
 # HEX:      Hex dump of section '.got2':
-# HEX-NEXT: 0x0003022c 00000000 ....
+# HEX-NEXT: 0x0003023c 00000000 ....
 
 # HEX2:      Hex dump of section '.got2':
-# HEX2-NEXT: 0x0003022c 000101a0 ....
+# HEX2-NEXT: 0x0003023c 000101a0 ....
 
 .section .got2,"aw"
 .long func
diff --git a/lld/test/ELF/ppc64-ifunc.s b/lld/test/ELF/ppc64-ifunc.s
index d024c5d5e36c4..2b0cc4c75171d 100644
--- a/lld/test/ELF/ppc64-ifunc.s
+++ b/lld/test/ELF/ppc64-ifunc.s
@@ -55,22 +55,24 @@
 # CHECK-EMPTY:
 
 ## .glink has 3 IPLT entries for ifunc1, ifunc2 and ifunc3.
-## ifunc2 and ifunc3 have the same code sequence as their PLT call stubs.
+## ifunc1 at toc - ifunc1 at iplt = 0x100302a0 - 0x10010268 = (2<<16) + 56
+## ifunc2 at toc - ifunc2 at iplt = 0x100302a8 - 0x10010278 = (2<<16) + 48
+## ifunc3 at toc - ifunc3 at iplt = 0x100302b0 - 0x10010288 = (2<<16) + 40
 # CHECK:      Disassembly of section .glink:
 # CHECK-EMPTY:
 # CHECK-NEXT: 0000000010010268 <ifunc1>:
-# CHECK-NEXT:     addis 12, 2, 1
-# CHECK-NEXT:     ld 12, -32760(12)
+# CHECK-NEXT:     addis 12, 12, 2
+# CHECK-NEXT:     ld 12, 56(12)
 # CHECK-NEXT:     mtctr 12
 # CHECK-NEXT:     bctr
-# CHECK-NEXT:     addis 12, 2, 1
-# CHECK-NEXT:     ld 12, -32752(12)
+# CHECK-NEXT:     addis 12, 12, 2
+# CHECK-NEXT:     ld 12, 48(12)
 # CHECK-NEXT:     mtctr 12
 # CHECK-NEXT:     bctr
 # CHECK-EMPTY:
 # CHECK-NEXT: 0000000010010288 <ifunc3>:
-# CHECK-NEXT:     addis 12, 2, 1
-# CHECK-NEXT:     ld 12, -32744(12)
+# CHECK-NEXT:     addis 12, 12, 2
+# CHECK-NEXT:     ld 12, 40(12)
 # CHECK-NEXT:     mtctr 12
 # CHECK-NEXT:     bctr
 



More information about the llvm-commits mailing list