[PowerPC] Support PCRelative Callees for R_PPC64_REL24 Relocation

The R_PPC64_REL24 is used in function calls when the caller requires a
valid TOC pointer. If the callee shares the same TOC or does not clobber
the TOC pointer then a direct call can be made. If the callee does not
share the TOC a thunk must be added to save the TOC pointer for the caller.

Up until PC Relative was introduced all local calls on medium and large code
models were assumed to share a TOC. This is no longer the case because
if the caller requires a TOC and the callee is PC Relative then the callee
can clobber the TOC even if it is in the same DSO.

This patch is to add support for a TOC caller calling a PC Relative callee that
clobbers the TOC.

Reviewed By: sfertile, MaskRay

Differential Revision: https://reviews.llvm.org/D82950
This commit is contained in:
Stefan Pintilie 2020-07-09 09:50:19 -05:00 committed by Kamau Bridgeman
parent 5d075beae7
commit beb52b12cb
5 changed files with 178 additions and 0 deletions

View File

@ -1039,6 +1039,11 @@ bool PPC64::needsThunk(RelExpr expr, RelType type, const InputFile *file,
if (s.isInPlt())
return true;
// 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)
return true;
// If a symbol is a weak undefined and we are compiling an executable
// it doesn't need a range-extending thunk since it can't be called.
if (s.isUndefWeak() && !config->shared)

View File

@ -279,6 +279,20 @@ public:
void addSymbols(ThunkSection &isec) override;
};
// PPC64 R2 Save Stub
// When the caller requires a valid R2 TOC pointer but the callee does not
// require a TOC pointer and the callee cannot guarantee that it doesn't
// clobber R2 then we need to save R2. This stub:
// 1) Saves the TOC pointer to the stack.
// 2) Tail calls the callee.
class PPC64R2SaveStub final : public Thunk {
public:
PPC64R2SaveStub(Symbol &dest) : Thunk(dest, 0) {}
uint32_t size() override { return 8; }
void writeTo(uint8_t *buf) override;
void addSymbols(ThunkSection &isec) override;
};
// 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
@ -822,6 +836,21 @@ void PPC64PltCallStub::addSymbols(ThunkSection &isec) {
s->file = destination.file;
}
void PPC64R2SaveStub::writeTo(uint8_t *buf) {
int64_t offset = destination.getVA() - (getThunkTargetSym()->getVA() + 4);
// The branch offset needs to fit in 26 bits.
if (!isInt<26>(offset))
fatal("R2 save stub branch offset is too large: " + Twine(offset));
write32(buf + 0, 0xf8410018); // std r2,24(r1)
write32(buf + 4, 0x48000000 | (offset & 0x03fffffc)); // b <offset>
}
void PPC64R2SaveStub::addSymbols(ThunkSection &isec) {
Defined *s = addSymbol(saver.save("__toc_save_" + destination.getName()),
STT_FUNC, 0, isec);
s->needsTocRestore = true;
}
void PPC64LongBranchThunk::writeTo(uint8_t *buf) {
int64_t offset = in.ppc64LongBranchTarget->getEntryVA(&destination, addend) -
getPPC64TocBase();
@ -950,6 +979,11 @@ static Thunk *addThunkPPC64(RelType type, Symbol &s, int64_t a) {
if (s.isInPlt())
return make<PPC64PltCallStub>(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)
return make<PPC64R2SaveStub>(s);
if (config->picThunk)
return make<PPC64PILongBranchThunk>(s, a);

View File

@ -0,0 +1,32 @@
# RUN: llvm-mc -filetype=obj -triple=powerpc64le %s -o %t.o
# RUN: not ld.lld %t.o -o /dev/null 2>&1 | FileCheck %s
# RUN: llvm-mc -filetype=obj -triple=powerpc64 %s -o %t.o
# RUN: not ld.lld %t.o -o /dev/null 2>&1 | FileCheck %s
## This test checks that the linker produces errors when it is missing the nop
## after a local call to a callee with st_other=1.
# CHECK: (.text+0xC): call to save_callee lacks nop, can't restore toc
# CHECK: (.text+0x1C): call to save_callee lacks nop, can't restore toc
callee:
.localentry callee, 1
blr # 0x0
caller:
.Lfunc_gep1:
addis 2, 12, .TOC.-.Lfunc_gep1@ha
addi 2, 2, .TOC.-.Lfunc_gep1@l
.Lfunc_lep1:
.localentry caller, .Lfunc_lep1-.Lfunc_gep1
bl callee # 0xC
blr
caller_tail:
.Lfunc_gep2:
addis 2, 12, .TOC.-.Lfunc_gep2@ha
addi 2, 2, .TOC.-.Lfunc_gep2@l
.Lfunc_lep2:
.localentry caller_tail, .Lfunc_lep2-.Lfunc_gep2
b callee # 0x1C

View File

@ -0,0 +1,33 @@
# REQUIRES: ppc
# RUN: echo 'SECTIONS { \
# RUN: .text_callee 0x10010000 : { *(.text_callee) } \
# RUN: .text_caller 0x20020000 : { *(.text_caller) } \
# RUN: }' > %t.script
# RUN: llvm-mc -filetype=obj -triple=powerpc64le %s -o %t.o
# RUN: not ld.lld -T %t.script %t.o -o %t 2>&1 >/dev/null | FileCheck %s
# RUN: llvm-mc -filetype=obj -triple=powerpc64 %s -o %t.o
# RUN: not ld.lld -T %t.script %t.o -o %t 2>&1 >/dev/null | FileCheck %s
# CHECK: error: R2 save stub branch offset is too large: -268501028
.section .text_callee, "ax", %progbits
callee:
.localentry callee, 1
blr
.section .text_caller, "ax", %progbits
caller:
.Lfunc_gep1:
addis 2, 12, .TOC.-.Lfunc_gep1@ha
addi 2, 2, .TOC.-.Lfunc_gep1@l
.Lfunc_lep1:
.localentry caller, .Lfunc_lep1-.Lfunc_gep1
addis 30, 2, global@toc@ha
lwz 3, global@toc@l(30)
bl callee
nop
blr
global:
.long 0

View File

@ -0,0 +1,74 @@
# REQUIRES: ppc
# RUN: echo 'SECTIONS { \
# RUN: .text_callee 0x10010000 : { *(.text_callee) } \
# RUN: .text_caller 0x10020000 : { *(.text_caller) } \
# RUN: }' > %t.script
# RUN: llvm-mc -filetype=obj -triple=powerpc64le %s -o %t.o
# RUN: ld.lld -T %t.script %t.o -o %t
# RUN: llvm-readelf -s %t | FileCheck %s --check-prefix=SYMBOL
# RUN: llvm-objdump -d --no-show-raw-insn --mcpu=future %t | FileCheck %s
# RUN: llvm-mc -filetype=obj -triple=powerpc64 %s -o %t.o
# RUN: ld.lld -T %t.script %t.o -o %t
# RUN: llvm-readelf -s %t | FileCheck %s --check-prefix=SYMBOL
# RUN: llvm-objdump -d --no-show-raw-insn --mcpu=future %t | FileCheck %s
# The point of this test is to make sure that when a function with TOC access
# a local function with st_other=1, a TOC save stub is inserted.
# SYMBOL: Symbol table '.symtab' contains 7 entries
# SYMBOL: 10010000 0 NOTYPE LOCAL DEFAULT [<other: 0x20>] 1 callee
# SYMBOL: 10020000 0 NOTYPE LOCAL DEFAULT [<other: 0x60>] 2 caller
# SYMBOL: 10020020 0 NOTYPE LOCAL DEFAULT [<other: 0x60>] 2 caller_14
# SYMBOL: 1002003c 8 FUNC LOCAL DEFAULT 2 __toc_save_callee
# CHECK-LABEL: callee
# CHECK: blr
# CHECK-LABEL: caller
# CHECK: bl 0x1002003c
# CHECK-NEXT: ld 2, 24(1)
# CHECK-NEXT: blr
# CHECK-LABEL: caller_14
# CHECK: bfl 0, 0x1002003c
# CHECK-NEXT: ld 2, 24(1)
# CHECK-NEXT: blr
# CHECK-LABEL: __toc_save_callee
# CHECK-NEXT: std 2, 24(1)
# CHECK-NEXT: b 0x10010000
.section .text_callee, "ax", %progbits
callee:
.localentry callee, 1
blr
.section .text_caller, "ax", %progbits
caller:
.Lfunc_gep1:
addis 2, 12, .TOC.-.Lfunc_gep1@ha
addi 2, 2, .TOC.-.Lfunc_gep1@l
.Lfunc_lep1:
.localentry caller, .Lfunc_lep1-.Lfunc_gep1
addis 30, 2, global@toc@ha
lwz 3, global@toc@l(30)
bl callee
nop
blr
global:
.long 0
caller_14:
.Lfunc_gep2:
addis 2, 12, .TOC.-.Lfunc_gep1@ha
addi 2, 2, .TOC.-.Lfunc_gep1@l
.Lfunc_lep2:
.localentry caller_14, .Lfunc_lep2-.Lfunc_gep2
addis 30, 2, global@toc@ha
lwz 3, global@toc@l(30)
bcl 4, 0, callee
nop
blr