Skip to content

Commit

Permalink
[ELF] Introduce range extension thunks for ARM
Browse files Browse the repository at this point in the history
This change adds initial support for range extension thunks. All thunks must
be created within the first pass so some corner cases are not supported. A
follow up patch will add support for multiple passes.

With this change the existing tests arm-branch-error.s and
arm-thumb-branch-error.s now no longer fail with an out of range branch.
These have been renamed and tests added for the range extension thunk.

Differential Revision: https://reviews.llvm.org/D34691

llvm-svn: 316752
  • Loading branch information
smithp35 committed Oct 27, 2017
1 parent f0c70f8 commit 75030b6
Show file tree
Hide file tree
Showing 22 changed files with 1,112 additions and 81 deletions.
16 changes: 12 additions & 4 deletions lld/ELF/Arch/ARM.cpp
Expand Up @@ -40,7 +40,7 @@ class ARM final : public TargetInfo {
void addPltSymbols(InputSectionBase *IS, uint64_t Off) const override;
void addPltHeaderSymbols(InputSectionBase *ISD) const override;
bool needsThunk(RelExpr Expr, RelType Type, const InputFile *File,
const SymbolBody &S) const override;
uint64_t BranchAddr, const SymbolBody &S) const override;
bool inBranchRange(RelType Type, uint64_t Src, uint64_t Dst) const override;
void relocateOne(uint8_t *Loc, RelType Type, uint64_t Val) const override;
};
Expand Down Expand Up @@ -228,7 +228,7 @@ void ARM::addPltSymbols(InputSectionBase *ISD, uint64_t Off) const {
}

bool ARM::needsThunk(RelExpr Expr, RelType Type, const InputFile *File,
const SymbolBody &S) const {
uint64_t BranchAddr, const SymbolBody &S) const {
// If S is an undefined weak symbol in an executable we don't need a Thunk.
// In a DSO calls to undefined symbols, including weak ones get PLT entries
// which may need a thunk.
Expand All @@ -245,14 +245,22 @@ bool ARM::needsThunk(RelExpr Expr, RelType Type, const InputFile *File,
// Otherwise we need to interwork if Symbol has bit 0 set (Thumb).
if (Expr == R_PC && ((S.getVA() & 1) == 1))
return true;
break;
LLVM_FALLTHROUGH;
case R_ARM_CALL: {
uint64_t Dst = (Expr == R_PLT_PC) ? S.getPltVA() : S.getVA();
return !inBranchRange(Type, BranchAddr, Dst);
}
case R_ARM_THM_JUMP19:
case R_ARM_THM_JUMP24:
// Source is Thumb, all PLT entries are ARM so interworking is required.
// Otherwise we need to interwork if Symbol has bit 0 clear (ARM).
if (Expr == R_PLT_PC || ((S.getVA() & 1) == 0))
return true;
break;
LLVM_FALLTHROUGH;
case R_ARM_THM_CALL: {
uint64_t Dst = (Expr == R_PLT_PC) ? S.getPltVA() : S.getVA();
return !inBranchRange(Type, BranchAddr, Dst);
}
}
return false;
}
Expand Down
4 changes: 2 additions & 2 deletions lld/ELF/Arch/Mips.cpp
Expand Up @@ -39,7 +39,7 @@ template <class ELFT> class MIPS final : public TargetInfo {
void writePlt(uint8_t *Buf, uint64_t GotPltEntryAddr, uint64_t PltEntryAddr,
int32_t Index, unsigned RelOff) const override;
bool needsThunk(RelExpr Expr, RelType Type, const InputFile *File,
const SymbolBody &S) const override;
uint64_t BranchAddr, const SymbolBody &S) const override;
void relocateOne(uint8_t *Loc, RelType Type, uint64_t Val) const override;
bool usesOnlyLowPageBits(RelType Type) const override;
};
Expand Down Expand Up @@ -331,7 +331,7 @@ void MIPS<ELFT>::writePlt(uint8_t *Buf, uint64_t GotPltEntryAddr,

template <class ELFT>
bool MIPS<ELFT>::needsThunk(RelExpr Expr, RelType Type, const InputFile *File,
const SymbolBody &S) const {
uint64_t BranchAddr, const SymbolBody &S) const {
// Any MIPS PIC code function is invoked with its address in register $t9.
// So if we have a branch instruction from non-PIC code to the PIC one
// we cannot make the jump directly and need to create a small stubs
Expand Down
68 changes: 45 additions & 23 deletions lld/ELF/Relocations.cpp
Expand Up @@ -1072,12 +1072,18 @@ void ThunkCreator::mergeThunks(ArrayRef<OutputSection *> OutputSections) {
// std::merge requires a strict weak ordering.
if (A->OutSecOff < B->OutSecOff)
return true;
if (A->OutSecOff == B->OutSecOff)
if (A->OutSecOff == B->OutSecOff) {
auto *TA = dyn_cast<ThunkSection>(A);
auto *TB = dyn_cast<ThunkSection>(B);
// Check if Thunk is immediately before any specific Target
// InputSection for example Mips LA25 Thunks.
if (auto *TA = dyn_cast<ThunkSection>(A))
if (TA && TA->getTargetInputSection() == B)
return true;
if (TA && TA->getTargetInputSection() == B)
return true;
if (TA && !TB && !TA->getTargetInputSection())
// Place Thunk Sections without specific targets before
// non-Thunk Sections.
return true;
}
return false;
};
std::merge(ISD->Sections.begin(), ISD->Sections.end(),
Expand All @@ -1088,17 +1094,32 @@ void ThunkCreator::mergeThunks(ArrayRef<OutputSection *> OutputSections) {
});
}

ThunkSection *ThunkCreator::getISDThunkSec(OutputSection *OS,
InputSectionDescription *ISD) {
// FIXME: When range extension thunks are supported we will need to check
// that the ThunkSection is in range of the caller.
if (!ISD->ThunkSections.empty())
return ISD->ThunkSections.front();

// FIXME: When range extension thunks are supported we must handle the case
// where no pre-created ThunkSections are in range by creating a new one in
// range; for now, it is unreachable.
llvm_unreachable("Must have created at least one ThunkSection per ISR");
// Find or create a ThunkSection within the InputSectionDescription (ISD) that
// is in range of Src. An ISR maps to a range of InputSections described by a
// linker script section pattern such as { .text .text.* }.
ThunkSection *ThunkCreator::getISDThunkSec(OutputSection *OS, InputSection *IS,
InputSectionDescription *ISD,
uint32_t Type, uint64_t Src) {
for (ThunkSection *TS : ISD->ThunkSections) {
uint64_t TSBase = OS->Addr + TS->OutSecOff;
uint64_t TSLimit = TSBase + TS->getSize();
if (Target->inBranchRange(Type, Src, (Src > TSLimit) ? TSBase : TSLimit))
return TS;
}

// No suitable ThunkSection exists. This can happen when there is a branch
// with lower range than the ThunkSection spacing or when there are too
// many Thunks. Create a new ThunkSection as close to the InputSection as
// possible. Error if InputSection is so large we cannot place ThunkSection
// anywhere in Range.
uint64_t ThunkSecOff = IS->OutSecOff;
if (!Target->inBranchRange(Type, Src, OS->Addr + ThunkSecOff)) {
ThunkSecOff = IS->OutSecOff + IS->getSize();
if (!Target->inBranchRange(Type, Src, OS->Addr + ThunkSecOff))
fatal("InputSection too large for range extension thunk " +
IS->getObjMsg(Src - (OS->Addr + IS->OutSecOff)));
}
return addThunkSection(OS, ISD, ThunkSecOff);
}

// Add a Thunk that needs to be placed in a ThunkSection that immediately
Expand Down Expand Up @@ -1165,13 +1186,14 @@ ThunkSection *ThunkCreator::addThunkSection(OutputSection *OS,
return TS;
}

std::pair<Thunk *, bool> ThunkCreator::getThunk(SymbolBody &Body,
RelType Type) {
std::pair<Thunk *, bool> ThunkCreator::getThunk(SymbolBody &Body, RelType Type,
uint64_t Src) {
auto Res = ThunkedSymbols.insert({&Body, std::vector<Thunk *>()});
if (!Res.second) {
// Check existing Thunks for Body to see if they can be reused
for (Thunk *ET : Res.first->second)
if (ET->isCompatibleWith(Type))
if (ET->isCompatibleWith(Type) &&
Target->inBranchRange(Type, Src, ET->ThunkSym->getVA()))
return std::make_pair(ET, false);
}
// No existing compatible Thunk in range, create a new one
Expand Down Expand Up @@ -1202,8 +1224,7 @@ void ThunkCreator::forEachInputSectionDescription(
// finalized. If any Thunks are added to an InputSectionDescription the
// offsets within the OutputSection of the InputSections will change.
//
// FIXME: All Thunks are assumed to be in range of the relocation. Range
// extension Thunks are not yet supported.
// FIXME: Initial support for RangeThunks; only one pass supported.
bool ThunkCreator::createThunks(ArrayRef<OutputSection *> OutputSections) {
bool AddressesChanged = false;
if (Pass == 0 && Target->ThunkSectionSpacing)
Expand All @@ -1219,20 +1240,21 @@ bool ThunkCreator::createThunks(ArrayRef<OutputSection *> OutputSections) {
for (InputSection *IS : ISD->Sections)
for (Relocation &Rel : IS->Relocations) {
SymbolBody &Body = *Rel.Sym;
uint64_t Src = OS->Addr + IS->OutSecOff + Rel.Offset;
if (Thunks.find(&Body) != Thunks.end() ||
!Target->needsThunk(Rel.Expr, Rel.Type, IS->File, Body))
!Target->needsThunk(Rel.Expr, Rel.Type, IS->File, Src, Body))
continue;
Thunk *T;
bool IsNew;
std::tie(T, IsNew) = getThunk(Body, Rel.Type);
std::tie(T, IsNew) = getThunk(Body, Rel.Type, Src);
if (IsNew) {
AddressesChanged = true;
// Find or create a ThunkSection for the new Thunk
ThunkSection *TS;
if (auto *TIS = T->getTargetInputSection())
TS = getISThunkSec(TIS);
else
TS = getISDThunkSec(OS, ISD);
TS = getISDThunkSec(OS, IS, ISD, Rel.Type, Src);
TS->addThunk(T);
Thunks[T->ThunkSym] = T;
}
Expand Down
9 changes: 7 additions & 2 deletions lld/ELF/Relocations.h
Expand Up @@ -139,7 +139,11 @@ class ThunkCreator {

private:
void mergeThunks(ArrayRef<OutputSection *> OutputSections);
ThunkSection *getISDThunkSec(OutputSection *OS, InputSectionDescription *ISD);

ThunkSection *getISDThunkSec(OutputSection *OS, InputSection *IS,
InputSectionDescription *ISD, uint32_t Type,
uint64_t Src);

ThunkSection *getISThunkSec(InputSection *IS);

void createInitialThunkSections(ArrayRef<OutputSection *> OutputSections);
Expand All @@ -148,7 +152,8 @@ class ThunkCreator {
ArrayRef<OutputSection *> OutputSections,
std::function<void(OutputSection *, InputSectionDescription *)> Fn);

std::pair<Thunk *, bool> getThunk(SymbolBody &Body, RelType Type);
std::pair<Thunk *, bool> getThunk(SymbolBody &Body, RelType Type,
uint64_t Src);

ThunkSection *addThunkSection(OutputSection *OS, InputSectionDescription *,
uint64_t Off);
Expand Down
2 changes: 1 addition & 1 deletion lld/ELF/Target.cpp
Expand Up @@ -124,7 +124,7 @@ int64_t TargetInfo::getImplicitAddend(const uint8_t *Buf, RelType Type) const {
bool TargetInfo::usesOnlyLowPageBits(RelType Type) const { return false; }

bool TargetInfo::needsThunk(RelExpr Expr, RelType Type, const InputFile *File,
const SymbolBody &S) const {
uint64_t BranchAddr, const SymbolBody &S) const {
return false;
}

Expand Down
10 changes: 5 additions & 5 deletions lld/ELF/Target.h
Expand Up @@ -51,12 +51,12 @@ class TargetInfo {

// Decide whether a Thunk is needed for the relocation from File
// targeting S.
virtual bool needsThunk(RelExpr Expr, RelType Type, const InputFile *File,
virtual bool needsThunk(RelExpr Expr, RelType RelocType,
const InputFile *File, uint64_t BranchAddr,
const SymbolBody &S) const;

// Return true if we can reach Dst from Src with Relocation Type
virtual bool inBranchRange(RelType Type, uint64_t Src, uint64_t Dst) const;

// Return true if we can reach Dst from Src with Relocation RelocType
virtual bool inBranchRange(RelType Type, uint64_t Src,
uint64_t Dst) const;
virtual RelExpr getRelExpr(RelType Type, const SymbolBody &S,
const uint8_t *Loc) const = 0;

Expand Down
2 changes: 2 additions & 0 deletions lld/ELF/Thunks.cpp
Expand Up @@ -318,11 +318,13 @@ static Thunk *addThunkArm(RelType Reloc, SymbolBody &S) {
case R_ARM_PC24:
case R_ARM_PLT32:
case R_ARM_JUMP24:
case R_ARM_CALL:
if (Config->Pic)
return make<ARMV7PILongThunk>(S);
return make<ARMV7ABSLongThunk>(S);
case R_ARM_THM_JUMP19:
case R_ARM_THM_JUMP24:
case R_ARM_THM_CALL:
if (Config->Pic)
return make<ThumbV7PILongThunk>(S);
return make<ThumbV7ABSLongThunk>(S);
Expand Down
7 changes: 1 addition & 6 deletions lld/ELF/Writer.cpp
Expand Up @@ -1352,17 +1352,12 @@ template <class ELFT> void Writer<ELFT>::finalizeSections() {
// It is linker's responsibility to create thunks containing long
// jump instructions if jump targets are too far. Create thunks.
if (Target->NeedsThunks) {
// FIXME: only ARM Interworking and Mips LA25 Thunks are implemented,
// these
// do not require address information. To support range extension Thunks
// we need to assign addresses so that we can tell if jump instructions
// are out of range. This will need to turn into a loop that converges
// when no more Thunks are added
ThunkCreator TC;
Script->assignAddresses();
if (TC.createThunks(OutputSections)) {
applySynthetic({InX::MipsGot},
[](SyntheticSection *SS) { SS->updateAllocSize(); });
Script->assignAddresses();
if (TC.createThunks(OutputSections))
fatal("All non-range thunks should be created in first call");
}
Expand Down
19 changes: 0 additions & 19 deletions lld/test/ELF/arm-branch-error.s

This file was deleted.

34 changes: 34 additions & 0 deletions lld/test/ELF/arm-branch-rangethunk.s
@@ -0,0 +1,34 @@
// RUN: llvm-mc -filetype=obj -triple=armv7a-none-linux-gnueabi %s -o %t
// RUN: llvm-mc -filetype=obj -triple=armv7a-none-linux-gnueabi %S/Inputs/far-arm-abs.s -o %tfar
// RUN: ld.lld %t %tfar -o %t2 2>&1
// RUN: llvm-objdump -d -triple=armv7a-none-linux-gnueabi %t2 | FileCheck %s
// REQUIRES: arm
.syntax unified
.section .text, "ax",%progbits
.globl _start
.balign 0x10000
.type _start,%function
_start:
// address of too_far symbols are just out of range of ARM branch with
// 26-bit immediate field and an addend of -8
bl too_far1
b too_far2
beq too_far3

// CHECK: Disassembly of section .text:
// CHECK-NEXT: _start:
// CHECK-NEXT: 20000: 01 00 00 eb bl #4 <__ARMv7ABSLongThunk_too_far1>
// CHECK-NEXT: 20004: 03 00 00 ea b #12 <__ARMv7ABSLongThunk_too_far2>
// CHECK-NEXT: 20008: 05 00 00 0a beq #20 <__ARMv7ABSLongThunk_too_far3>
// CHECK: __ARMv7ABSLongThunk_too_far1:
// CHECK-NEXT: 2000c: 08 c0 00 e3 movw r12, #8
// CHECK-NEXT: 20010: 02 c2 40 e3 movt r12, #514
// CHECK-NEXT: 20014: 1c ff 2f e1 bx r12
// CHECK: __ARMv7ABSLongThunk_too_far2:
// CHECK-NEXT: 20018: 0c c0 00 e3 movw r12, #12
// CHECK-NEXT: 2001c: 02 c2 40 e3 movt r12, #514
// CHECK-NEXT: 20020: 1c ff 2f e1 bx r12
// CHECK: __ARMv7ABSLongThunk_too_far3:
// CHECK-NEXT: 20024: 10 c0 00 e3 movw r12, #16
// CHECK-NEXT: 20028: 02 c2 40 e3 movt r12, #514
// CHECK-NEXT: 2002c: 1c ff 2f e1 bx r12
19 changes: 0 additions & 19 deletions lld/test/ELF/arm-thumb-branch-error.s

This file was deleted.

36 changes: 36 additions & 0 deletions lld/test/ELF/arm-thumb-branch-rangethunk.s
@@ -0,0 +1,36 @@
// RUN: llvm-mc -filetype=obj -triple=thumbv7a-none-linux-gnueabi %s -o %t
// RUN: llvm-mc -filetype=obj -triple=thumbv7a-none-linux-gnueabi %S/Inputs/far-arm-thumb-abs.s -o %tfar
// RUN: ld.lld %t %tfar -o %t2 2>&1
// RUN: llvm-objdump -d -triple=thumbv7a-none-linux-gnueabi %t2
// REQUIRES: arm
.syntax unified
.thumb
.section .text, "ax",%progbits
.globl _start
.balign 0x10000
.type _start,%function
_start:
// address of too_far symbols are just out of range of ARM branch with
// 26-bit immediate field and an addend of -8
bl too_far1
b too_far2
beq.w too_far3

// CHECK: Disassembly of section .text:
// CHECK-NEXT: _start:
// CHECK-NEXT: 20000: 00 f0 04 f8 bl #8
// CHECK-NEXT: 20004: 00 f0 07 b8 b.w #14 <__Thumbv7ABSLongThunk_too_far2>
// CHECK-NEXT: 20008: 00 f0 0a 80 beq.w #20 <__Thumbv7ABSLongThunk_too_far3>
// CHECK: __Thumbv7ABSLongThunk_too_far1:
// CHECK-NEXT: 2000c: 40 f2 05 0c movw r12, #5
// CHECK-NEXT: 20010: c0 f2 02 1c movt r12, #258
// CHECK-NEXT: 20014: 60 47 bx r12
// CHECK: __Thumbv7ABSLongThunk_too_far2:
// CHECK-NEXT: 20016: 40 f2 09 0c movw r12, #9
// CHECK-NEXT: 2001a: c0 f2 02 1c movt r12, #258
// CHECK-NEXT: 2001e: 60 47 bx r12
// CHECK: __Thumbv7ABSLongThunk_too_far3:
// CHECK-NEXT: 20020: 40 f2 0d 0c movw r12, #13
// CHECK-NEXT: 20024: c0 f2 12 0c movt r12, #18
// CHECK-NEXT: 20028: 60 47 bx r12
// CHECK-NEXT: 2002a: 00 00 movs r0, r0

0 comments on commit 75030b6

Please sign in to comment.