Skip to content

Commit

Permalink
[COFF] Create range extension thunks for ARM64
Browse files Browse the repository at this point in the history
On ARM64, this is normally necessary only after a module exceeds
128 MB in size (while the limit for thumb is 16 MB). For conditional
branches, the range limit is only 1 MB though (the same as for thumb),
and for the tbz instruction, the range is only 32 KB, which allows for
a test much smaller than the full 128 MB.

This fixes PR40467.

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

llvm-svn: 352929
  • Loading branch information
mstorsjo committed Feb 1, 2019
1 parent b2b0cab commit c9f4d25
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 32 deletions.
24 changes: 22 additions & 2 deletions lld/COFF/Chunks.cpp
Expand Up @@ -670,18 +670,38 @@ const uint8_t ArmThunk[] = {
0xe7, 0x44, // L1: add pc, ip
};

size_t RangeExtensionThunk::getSize() const {
size_t RangeExtensionThunkARM::getSize() const {
assert(Config->Machine == ARMNT);
return sizeof(ArmThunk);
}

void RangeExtensionThunk::writeTo(uint8_t *Buf) const {
void RangeExtensionThunkARM::writeTo(uint8_t *Buf) const {
assert(Config->Machine == ARMNT);
uint64_t Offset = Target->getRVA() - RVA - 12;
memcpy(Buf + OutputSectionOff, ArmThunk, sizeof(ArmThunk));
applyMOV32T(Buf + OutputSectionOff, uint32_t(Offset));
}

// A position independent ARM64 adrp+add thunk, with a maximum range of
// +/- 4 GB, which is enough for any PE-COFF.
const uint8_t Arm64Thunk[] = {
0x10, 0x00, 0x00, 0x90, // adrp x16, Dest
0x10, 0x02, 0x00, 0x91, // add x16, x16, :lo12:Dest
0x00, 0x02, 0x1f, 0xd6, // br x16
};

size_t RangeExtensionThunkARM64::getSize() const {
assert(Config->Machine == ARM64);
return sizeof(Arm64Thunk);
}

void RangeExtensionThunkARM64::writeTo(uint8_t *Buf) const {
assert(Config->Machine == ARM64);
memcpy(Buf + OutputSectionOff, Arm64Thunk, sizeof(Arm64Thunk));
applyArm64Addr(Buf + OutputSectionOff + 0, Target->getRVA(), RVA, 12);
applyArm64Imm(Buf + OutputSectionOff + 4, Target->getRVA() & 0xfff, 0);
}

void LocalImportChunk::getBaserels(std::vector<Baserel> *Res) {
Res->emplace_back(getRVA());
}
Expand Down
13 changes: 11 additions & 2 deletions lld/COFF/Chunks.h
Expand Up @@ -357,9 +357,18 @@ class ImportThunkChunkARM64 : public Chunk {
Defined *ImpSymbol;
};

class RangeExtensionThunk : public Chunk {
class RangeExtensionThunkARM : public Chunk {
public:
explicit RangeExtensionThunk(Defined *T) : Target(T) {}
explicit RangeExtensionThunkARM(Defined *T) : Target(T) {}
size_t getSize() const override;
void writeTo(uint8_t *Buf) const override;

Defined *Target;
};

class RangeExtensionThunkARM64 : public Chunk {
public:
explicit RangeExtensionThunkARM64(Defined *T) : Target(T) {}
size_t getSize() const override;
void writeTo(uint8_t *Buf) const override;

Expand Down
49 changes: 37 additions & 12 deletions lld/COFF/Writer.cpp
Expand Up @@ -335,16 +335,31 @@ void OutputSection::writeHeaderTo(uint8_t *Buf) {
// Check whether the target address S is in range from a relocation
// of type RelType at address P.
static bool isInRange(uint16_t RelType, uint64_t S, uint64_t P, int Margin) {
assert(Config->Machine == ARMNT);
int64_t Diff = AbsoluteDifference(S, P + 4) + Margin;
switch (RelType) {
case IMAGE_REL_ARM_BRANCH20T:
return isInt<21>(Diff);
case IMAGE_REL_ARM_BRANCH24T:
case IMAGE_REL_ARM_BLX23T:
return isInt<25>(Diff);
default:
return true;
if (Config->Machine == ARMNT) {
int64_t Diff = AbsoluteDifference(S, P + 4) + Margin;
switch (RelType) {
case IMAGE_REL_ARM_BRANCH20T:
return isInt<21>(Diff);
case IMAGE_REL_ARM_BRANCH24T:
case IMAGE_REL_ARM_BLX23T:
return isInt<25>(Diff);
default:
return true;
}
} else if (Config->Machine == ARM64) {
int64_t Diff = AbsoluteDifference(S, P) + Margin;
switch (RelType) {
case IMAGE_REL_ARM64_BRANCH26:
return isInt<28>(Diff);
case IMAGE_REL_ARM64_BRANCH19:
return isInt<21>(Diff);
case IMAGE_REL_ARM64_BRANCH14:
return isInt<16>(Diff);
default:
return true;
}
} else {
llvm_unreachable("Unexpected architecture");
}
}

Expand All @@ -356,7 +371,17 @@ getThunk(DenseMap<uint64_t, Defined *> &LastThunks, Defined *Target, uint64_t P,
Defined *&LastThunk = LastThunks[Target->getRVA()];
if (LastThunk && isInRange(Type, LastThunk->getRVA(), P, Margin))
return {LastThunk, false};
RangeExtensionThunk *C = make<RangeExtensionThunk>(Target);
Chunk *C;
switch (Config->Machine) {
case ARMNT:
C = make<RangeExtensionThunkARM>(Target);
break;
case ARM64:
C = make<RangeExtensionThunkARM64>(Target);
break;
default:
llvm_unreachable("Unexpected architecture");
}
Defined *D = make<DefinedSynthetic>("", C);
LastThunk = D;
return {D, true};
Expand Down Expand Up @@ -458,7 +483,7 @@ static bool verifyRanges(const std::vector<Chunk *> Chunks) {
// Assign addresses and add thunks if necessary.
void Writer::finalizeAddresses() {
assignAddresses();
if (Config->Machine != ARMNT)
if (Config->Machine != ARMNT && Config->Machine != ARM64)
return;

size_t OrigNumChunks = 0;
Expand Down
16 changes: 0 additions & 16 deletions lld/test/COFF/arm64-branch-range.test

This file was deleted.

27 changes: 27 additions & 0 deletions lld/test/COFF/arm64-thunks.s
@@ -0,0 +1,27 @@
// REQUIRES: aarch64
// RUN: llvm-mc -filetype=obj -triple=aarch64-windows %s -o %t.obj
// RUN: lld-link -entry:main -subsystem:console %t.obj -out:%t.exe -verbose 2>&1 | FileCheck -check-prefix=VERBOSE %s
// RUN: llvm-objdump -d %t.exe | FileCheck -check-prefix=DISASM %s

// VERBOSE: Added 1 thunks with margin {{.*}} in 1 passes

.globl main
.globl func1
.text
main:
tbz w0, #0, func1
ret
.section .text$a, "xr"
.space 0x8000
.section .text$b, "xr"
func1:
ret

// DISASM: 0000000140001000 .text:
// DISASM: 140001000: 40 00 00 36 tbz w0, #0, #8 <.text+0x8>
// DISASM: 140001004: c0 03 5f d6 ret
// DISASM: 140001008: 50 00 00 90 adrp x16, #32768
// DISASM: 14000100c: 10 52 00 91 add x16, x16, #20
// DISASM: 140001010: 00 02 1f d6 br x16

// DISASM: 140009014: c0 03 5f d6 ret

0 comments on commit c9f4d25

Please sign in to comment.