diff --git a/lld/ELF/Thunks.cpp b/lld/ELF/Thunks.cpp --- a/lld/ELF/Thunks.cpp +++ b/lld/ELF/Thunks.cpp @@ -326,7 +326,7 @@ // A bl instruction uses a signed 24 bit offset, with an implicit 4 byte // alignment. This gives a possible 26 bits of 'reach'. If the call offset is -// larger then that we need to emit a long-branch thunk. The target address +// larger than that we need to emit a long-branch thunk. The target address // of the callee is stored in a table to be accessed TOC-relative. Since the // call must be local (a non-local call will have a PltCallStub instead) the // table stores the address of the callee's local entry point. For @@ -337,6 +337,8 @@ uint32_t size() override { return 16; } void writeTo(uint8_t *buf) override; void addSymbols(ThunkSection &isec) override; + bool isCompatibleWith(const InputSection &isec, + const Relocation &rel) const override; protected: PPC64LongBranchThunk(Symbol &dest, int64_t addend) : Thunk(dest, addend) {} @@ -365,6 +367,52 @@ } }; +// A bl instruction uses a signed 24 bit offset, with an implicit 4 byte +// alignment. This gives a possible 26 bits of 'reach'. If the call offset is +// larger than that we need to emit a long-branch thunk. The target address +// of the callee is stored in a table to be accessed PC-relative. Since the +// call must be local (a non-local call will have a PCRelPltCallStub instead) +// the table stores the address of the callee's local entry point. For +// position-independent code a corresponding relative dynamic relocation is +// used. +class PPC64PCRelLongBranchThunk : public Thunk { +public: + uint32_t size() override { return 16; } + void writeTo(uint8_t *buf) override; + void addSymbols(ThunkSection &isec) override; + bool isCompatibleWith(const InputSection &isec, + const Relocation &rel) const override; + +protected: + PPC64PCRelLongBranchThunk(Symbol &dest, int64_t addend) + : Thunk(dest, addend) { + alignment = 16; + } +}; + +class PPC64PCRelPILongBranchThunk final : public PPC64PCRelLongBranchThunk { +public: + PPC64PCRelPILongBranchThunk(Symbol &dest, int64_t addend) + : PPC64PCRelLongBranchThunk(dest, addend) { + assert(!dest.isPreemptible); + if (Optional index = + in.ppc64LongBranchTarget->addEntry(&dest, addend)) { + mainPart->relaDyn->addReloc( + {target->relativeRel, in.ppc64LongBranchTarget, *index * UINT64_C(8), + true, &dest, + addend + getPPC64GlobalEntryToLocalEntryOffset(dest.stOther)}); + } + } +}; + +class PPC64PCRelPDLongBranchThunk final : public PPC64PCRelLongBranchThunk { +public: + PPC64PCRelPDLongBranchThunk(Symbol &dest, int64_t addend) + : PPC64PCRelLongBranchThunk(dest, addend) { + in.ppc64LongBranchTarget->addEntry(&dest, addend); + } +}; + } // end anonymous namespace Defined *Thunk::addSymbol(StringRef name, uint8_t type, uint64_t value, @@ -937,6 +985,33 @@ isec); } +bool PPC64LongBranchThunk::isCompatibleWith(const InputSection &isec, + const Relocation &rel) const { + return rel.type == R_PPC64_REL24 || rel.type == R_PPC64_REL14; +} + +void PPC64PCRelLongBranchThunk::writeTo(uint8_t *buf) { + int64_t offset = in.ppc64LongBranchTarget->getEntryVA(&destination, addend) - + getThunkTargetSym()->getVA(); + if (!isInt<34>(offset)) + fatal("offset overflow 34 bits, use large code model with mcmodel=large"); + uint64_t pld = + PLD_R12_NO_DISP | (((offset >> 16) & 0x3ffff) << 32) | (offset & 0xffff); + writePrefixedInstruction(buf + 0, pld); // pld r12, func@branch_lt@pcrel + write32(buf + 8, MTCTR_R12); // mtctr r12 + write32(buf + 12, BCTR); // bctr +} + +void PPC64PCRelLongBranchThunk::addSymbols(ThunkSection &isec) { + addSymbol(saver.save("__long_branch_pcrel_" + destination.getName()), + STT_FUNC, 0, isec); +} + +bool PPC64PCRelLongBranchThunk::isCompatibleWith(const InputSection &isec, + const Relocation &rel) const { + return rel.type == R_PPC64_REL24_NOTOC; +} + Thunk::Thunk(Symbol &d, int64_t a) : destination(d), addend(a), offset(0) {} Thunk::~Thunk() = default; @@ -1057,17 +1132,22 @@ : (Thunk *)make(s); // This check looks at the st_other bits of the callee. If the value is 1 - // then the callee clobbers the TOC and we need an R2 save stub. - if ((s.stOther >> 5) == 1) + // then the callee clobbers the TOC and we need an R2 save stub when RelType + // is R_PPC64_REL14 or R_PPC64_REL24. + if ((type == R_PPC64_REL14 || type == R_PPC64_REL24) && (s.stOther >> 5) == 1) return make(s); if (type == R_PPC64_REL24_NOTOC && (s.stOther >> 5) > 1) return make(s); if (config->picThunk) - return make(s, a); + return type == R_PPC64_REL24_NOTOC + ? (Thunk *)make(s, a) + : (Thunk *)make(s, a); - return make(s, a); + return type == R_PPC64_REL24_NOTOC + ? (Thunk *)make(s, a) + : (Thunk *)make(s, a); } Thunk *elf::addThunk(const InputSection &isec, Relocation &rel) { diff --git a/lld/test/ELF/ppc64-pcrel-long-branch-error.s b/lld/test/ELF/ppc64-pcrel-long-branch-error.s new file mode 100644 --- /dev/null +++ b/lld/test/ELF/ppc64-pcrel-long-branch-error.s @@ -0,0 +1,26 @@ +# REQUIRES: ppc +# RUN: echo 'SECTIONS { \ +# RUN: .text_low 0x2000: { *(.text_low) } \ +# RUN: .text_high 0x800002000 : { *(.text_high) } \ +# RUN: }' > %t.script + +# RUN: llvm-mc -filetype=obj -triple=ppc64le %s -o %t.o +# RUN: not ld.lld -T %t.script %t.o -o %t 2>&1 >/dev/null | FileCheck %s +# RUN: not ld.lld -pie -T %t.script %t.o -o %t 2>&1 >/dev/null | FileCheck %s + +# RUN: llvm-mc -filetype=obj -triple=ppc64 %s -o %t.o +# RUN: not ld.lld -T %t.script %t.o -o %t 2>&1 >/dev/null | FileCheck %s +# RUN: not ld.lld -pie -T %t.script %t.o -o %t 2>&1 >/dev/null | FileCheck %s + +# CHECK: error: offset overflow 34 bits, use large code model with mcmodel=large + +.section .text_low, "ax", %progbits +.globl _start +_start: + bl high@notoc + blr + +.section .text_high, "ax", %progbits +.globl high +high: + blr diff --git a/lld/test/ELF/ppc64-pcrel-long-branch-pi.s b/lld/test/ELF/ppc64-pcrel-long-branch-pi.s new file mode 100644 --- /dev/null +++ b/lld/test/ELF/ppc64-pcrel-long-branch-pi.s @@ -0,0 +1,97 @@ +# REQUIRES: ppc +# RUN: echo 'SECTIONS { \ +# RUN: .text_low 0x2000: { *(.text_low) } \ +# RUN: .text_high 0x2002000 : { *(.text_high) } \ +# RUN: }' > %t.script + +# RUN: llvm-mc -filetype=obj -triple=ppc64le %s -o %t.o +# RUN: ld.lld -pie -T %t.script %t.o -o %t +# RUN: llvm-readelf -S %t | FileCheck --check-prefix=SEC-PIE %s +# RUN: llvm-readobj -r %t | FileCheck --check-prefix=RELOC-PIE %s +# RUN: llvm-objdump -d --no-show-raw-insn --mcpu=pwr10 %t | FileCheck --check-prefix=CHECK-PIE %s + +# RUN: llvm-mc -filetype=obj -triple=ppc64le -defsym HIDDEN=1 %s -o %t.o +# RUN: ld.lld -shared -T %t.script %t.o -o %t.so +# RUN: llvm-readelf -S %t.so | FileCheck --check-prefix=SEC-SHARED %s +# RUN: llvm-readobj -r %t.so | FileCheck --check-prefix=RELOC-SHARED %s +# RUN: llvm-objdump -d --no-show-raw-insn --mcpu=pwr10 %t.so | FileCheck --check-prefix=CHECK-SHARED %s + +# RUN: llvm-mc -filetype=obj -triple=ppc64 %s -o %t.o +# RUN: ld.lld -pie -T %t.script %t.o -o %t +# RUN: llvm-readelf -S %t | FileCheck --check-prefix=SEC-PIE %s +# RUN: llvm-readobj -r %t | FileCheck --check-prefix=RELOC-PIE %s +# RUN: llvm-objdump -d --no-show-raw-insn --mcpu=pwr10 %t | FileCheck --check-prefix=CHECK-PIE %s + +# RUN: llvm-mc -filetype=obj -triple=ppc64 -defsym HIDDEN=1 %s -o %t.o +# RUN: ld.lld -shared -T %t.script %t.o -o %t.so +# RUN: llvm-readelf -S %t.so | FileCheck --check-prefix=SEC-SHARED %s +# RUN: llvm-readobj -r %t.so | FileCheck --check-prefix=RELOC-SHARED %s +# RUN: llvm-objdump -d --no-show-raw-insn --mcpu=pwr10 %t.so | FileCheck --check-prefix=CHECK-SHARED %s + + +# SEC-PIE: Name Type Address Off Size ES Flg Lk Inf Al +# SEC-PIE: .got PROGBITS 00000000020020d8 20120d8 000008 00 WA 0 0 8 +# SEC-PIE: .branch_lt NOBITS 00000000020020e8 20120e8 000008 00 WA 0 0 8 + +# SEC-SHARED: Name Type Address Off Size ES Flg Lk Inf Al +# SEC-SHARED: .got PROGBITS 00000000020020b8 20120b8 000008 00 WA 0 0 8 +# SEC-SHARED: .branch_lt NOBITS 00000000020020c8 20120c8 000008 00 WA 0 0 8 + +# RELOC-PIE: .rela.dyn { +# RELOC-PIE-NEXT: 0x20020E0 R_PPC64_RELATIVE - 0x8000 +# RELOC-PIE-NEXT: 0x20020E8 R_PPC64_RELATIVE - 0x2002000 +# RELOC-PIE-NEXT: } + +# RELOC-SHARED: .rela.dyn { +# RELOC-SHARED-NEXT: 0x20020C0 R_PPC64_RELATIVE - 0x8000 +# RELOC-SHARED-NEXT: 0x20020C8 R_PPC64_RELATIVE - 0x2002000 +# RELOC-SHARED-NEXT: } + +# CHECK-PIE-LABEL: <_start>: +# CHECK-PIE-NEXT: 2000: bl 0x2010 +# CHECK-PIE-NEXT: blr +# CHECK-PIE-NEXT: trap +# CHECK-PIE-NEXT: trap + +## &.branch_lt[0] - 0x2010 = 0x20020E8 - 0x2010 = 33554648 +# CHECK-PIE-LABEL: <__long_branch_pcrel_high>: +# CHECK-PIE-NEXT: 2010: pld 12, 33554648(0), 1 +# CHECK-PIE-NEXT: mtctr 12 +# CHECK-PIE-NEXT: bctr + +# CHECK-PIE-LABEL: : +# CHECK-PIE-NEXT: 2002000: blr + +# CHECK-SHARED-LABEL: <_start>: +# CHECK-SHARED-NEXT: 2000: bl 0x2010 +# CHECK-SHARED-NEXT: blr +# CHECK-SHARED-NEXT: trap +# CHECK-SHARED-NEXT: trap + +## &.branch_lt[0] - 0x2010 = 0x20020C8 - 0x2010 = 33554616 +# CHECK-SHARED-LABEL: <__long_branch_pcrel_high>: +# CHECK-SHARED-NEXT: 2010: pld 12, 33554616(0), 1 +# CHECK-SHARED-NEXT: mtctr 12 +# CHECK-SHARED-NEXT: bctr + +# CHECK-SHARED-LABEL: : +# CHECK-SHARED-NEXT: 2002000: blr + +.section .text_low, "ax", %progbits +.globl _start +_start: + bl high@notoc + blr + +.section .text_high, "ax", %progbits +.ifdef HIDDEN +.hidden high +.endif +.globl high +high: + blr + +## Force creation of .got +## The R_PPC64_RELATIVE makes sure .rela.dyn survives removeUnusedSyntheticSections. +.section .data +.quad .TOC.@tocbase diff --git a/lld/test/ELF/ppc64-pcrel-long-branch.s b/lld/test/ELF/ppc64-pcrel-long-branch.s new file mode 100644 --- /dev/null +++ b/lld/test/ELF/ppc64-pcrel-long-branch.s @@ -0,0 +1,59 @@ +# REQUIRES: ppc +# RUN: echo 'SECTIONS { \ +# RUN: .text_low 0x2000: { *(.text_low) } \ +# RUN: .text_high 0x2002000 : { *(.text_high) } \ +# RUN: }' > %t.script + +# RUN: llvm-mc -filetype=obj -triple=ppc64le %s -o %t.o +# RUN: ld.lld -T %t.script %t.o -o %t +# RUN: llvm-readelf -S -r %t | FileCheck --check-prefix=SEC %s +# RUN: llvm-readelf -x .branch_lt %t | FileCheck --check-prefix=BRANCH-LE %s +# RUN: llvm-objdump -d --no-show-raw-insn --mcpu=pwr10 %t | FileCheck %s +# RUN: llvm-nm --no-sort %t | FileCheck --check-prefix=NM %s + +# RUN: llvm-mc -filetype=obj -triple=ppc64 %s -o %t.o +# RUN: ld.lld -T %t.script %t.o -o %t +# RUN: llvm-readelf -S -r %t | FileCheck --check-prefix=SEC %s +# RUN: llvm-readelf -x .branch_lt %t | FileCheck --check-prefix=BRANCH-BE %s +# RUN: llvm-objdump -d --no-show-raw-insn --mcpu=pwr10 %t | FileCheck %s +# RUN: llvm-nm --no-sort %t | FileCheck --check-prefix=NM %s + +# SEC: Name Type Address Off Size ES Flg Lk Inf Al +# SEC: .branch_lt PROGBITS 0000000002002008 2002008 000008 00 WA 0 0 8 +# SEC: There are no relocations in this file. + +# BRANCH-LE: Hex dump of section '.branch_lt': +# BRANCH-LE-NEXT: 0x02002008 00200002 00000000 . ...... + +# BRANCH-BE: Hex dump of section '.branch_lt': +# BRANCH-BE-NEXT: 0x02002008 00000000 02002000 ...... . + +# CHECK-LABEL: <_start>: +# CHECK-NEXT: 2000: bl 0x2010 +# CHECK-NEXT: blr +# CHECK-NEXT: trap +# CHECK-NEXT: trap + +## &.branch_lt[0] - 0x2010 = 0x2002008 - 0x2010 = 33554424 +# CHECK-LABEL: <__long_branch_pcrel_high>: +# CHECK-NEXT: 2010: pld 12, 33554424(0), 1 +# CHECK-NEXT: mtctr 12 +# CHECK-NEXT: bctr + +# CHECK-LABEL: : +# CHECK-NEXT: 2002000: blr + +.section .text_low, "ax", %progbits +.globl _start +_start: + bl high@notoc + blr + +.section .text_high, "ax", %progbits +.globl high +high: + blr + +# NM: 0000000000002010 t __long_branch_pcrel_high +# NM-NEXT: 0000000000002000 T _start +# NM-NEXT: 0000000002002000 T high