Skip to content

Commit

Permalink
[lld][ARM] support absolute thunks for Armv4T Thumb and interworking
Browse files Browse the repository at this point in the history
changes:
- BLX: The Arm architecture versions that support the branch and link
  instruction (BLX), can rewrite BLs in place when a state change from Arm<->Thumb
  is required. Armv4T does not have BLX and so needs thunks for state changes.
- v4T Thumb long branches needed their own thunk. We could have used the v6M
  implementation, but v6M doesn't have Arm state and must resolve to rather
  inefficient stack reshuffling. We also can't reuse v7 thumb thunks as they use
  MOVV/MOVT, which wasn't available yet for v4T.
- Remove the `lack of BLX' warning. LLVM only supports Arm Architecture versions
  upwards of v4, which we now all support in LLD.
- renamed existing thunks to better reflect their use:
  ARMV5ABSLongThunk -> ARMV5LongLdrPcThunk,
  ARMV5PILongThunk -> ARMV4PILongThunk
- removed isCompatibleWith method from ARMV5ABSLongThunk and ARMV5PILongThunk,
  as they were identical to the ARMThunk parent class implementation.

Support for (efficient) position independent thunks for v4T will be added in a
follow-up patch, including possible related thunk renaming and code comment
cleanup.

Reviewed By: MaskRay, peter.smith

Differential Revision: https://reviews.llvm.org/D139888
  • Loading branch information
stuij committed Dec 21, 2022
1 parent a302823 commit 62c6057
Show file tree
Hide file tree
Showing 10 changed files with 412 additions and 122 deletions.
6 changes: 4 additions & 2 deletions lld/ELF/Arch/ARM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,8 @@ bool ARM::needsThunk(RelExpr expr, RelType type, const InputFile *file,
[[fallthrough]];
case R_ARM_CALL: {
uint64_t dst = (expr == R_PLT_PC) ? s.getPltVA() : s.getVA();
return !inBranchRange(type, branchAddr, dst + a);
return !inBranchRange(type, branchAddr, dst + a) ||
(!config->armHasBlx && (s.getVA() & 1));
}
case R_ARM_THM_JUMP19:
case R_ARM_THM_JUMP24:
Expand All @@ -325,7 +326,8 @@ bool ARM::needsThunk(RelExpr expr, RelType type, const InputFile *file,
[[fallthrough]];
case R_ARM_THM_CALL: {
uint64_t dst = (expr == R_PLT_PC) ? s.getPltVA() : s.getVA();
return !inBranchRange(type, branchAddr, dst + a);
return !inBranchRange(type, branchAddr, dst + a) ||
(!config->armHasBlx && (s.getVA() & 1) == 0);;
}
}
return false;
Expand Down
9 changes: 0 additions & 9 deletions lld/ELF/Driver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2786,15 +2786,6 @@ void LinkerDriver::link(opt::InputArgList &args) {

config->imageBase = getImageBase(args);

if (config->emachine == EM_ARM) {
// FIXME: These warnings can be removed when lld only uses these features
// when the input objects have been compiled with an architecture that
// supports them.
if (config->armHasBlx == false)
warn("lld uses blx instruction, no object with architecture supporting "
"feature detected");
}

// This adds a .comment section containing a version string.
if (!config->relocatable)
ctx.inputSections.push_back(createCommentSection());
Expand Down
269 changes: 195 additions & 74 deletions lld/ELF/Thunks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -161,47 +161,77 @@ class ThumbV7PILongThunk final : public ThumbThunk {
void addSymbols(ThunkSection &isec) override;
};

// Implementations of Thunks for older Arm architectures that do not support
// the movt/movw instructions. These thunks require at least Architecture v5
// as used on processors such as the Arm926ej-s. There are no Thumb entry
// points as there is no Thumb branch instruction on these architecture that
// can result in a thunk
class ARMV5ABSLongThunk final : public ARMThunk {
// Implementations of Thunks for Arm v6-M. Only Thumb instructions are permitted
class ThumbV6MABSLongThunk final : public ThumbThunk {
public:
ThumbV6MABSLongThunk(Symbol &dest, int64_t addend)
: ThumbThunk(dest, addend) {}

uint32_t sizeLong() override { return 12; }
void writeLong(uint8_t *buf) override;
void addSymbols(ThunkSection &isec) override;
};

class ThumbV6MPILongThunk final : public ThumbThunk {
public:
ThumbV6MPILongThunk(Symbol &dest, int64_t addend)
: ThumbThunk(dest, addend) {}

uint32_t sizeLong() override { return 16; }
void writeLong(uint8_t *buf) override;
void addSymbols(ThunkSection &isec) override;
};

// Architectures v4, v5 and v6 do not support the movt/movw instructions. v5 and
// v6 support BLX to which BL instructions can be rewritten inline. There are no
// Thumb entrypoints for v5 and v6 as there is no Thumb branch instruction on
// these architecture that can result in a thunk.

// LDR on v5 and v6 can switch processor state, so for v5 and v6,
// ARMV5LongLdrPcThunk can be used for both Arm->Arm and Arm->Thumb calls. v4
// can also use this thunk, but only for Arm->Arm calls.
class ARMV5LongLdrPcThunk final : public ARMThunk {
public:
ARMV5ABSLongThunk(Symbol &dest, int64_t addend) : ARMThunk(dest, addend) {}
ARMV5LongLdrPcThunk(Symbol &dest, int64_t addend) : ARMThunk(dest, addend) {}

uint32_t sizeLong() override { return 8; }
void writeLong(uint8_t *buf) override;
void addSymbols(ThunkSection &isec) override;
bool isCompatibleWith(const InputSection &isec,
const Relocation &rel) const override;
};

class ARMV5PILongThunk final : public ARMThunk {
class ARMV4PILongThunk final : public ARMThunk {
public:
ARMV5PILongThunk(Symbol &dest, int64_t addend) : ARMThunk(dest, addend) {}
ARMV4PILongThunk(Symbol &dest, int64_t addend) : ARMThunk(dest, addend) {}

uint32_t sizeLong() override { return 16; }
void writeLong(uint8_t *buf) override;
void addSymbols(ThunkSection &isec) override;
bool isCompatibleWith(const InputSection &isec,
const Relocation &rel) const override;
};

// Implementations of Thunks for Arm v6-M. Only Thumb instructions are permitted
class ThumbV6MABSLongThunk final : public ThumbThunk {
// Implementations of Thunks for v4. BLX is not supported, and loads
// will not invoke Arm/Thumb state changes.
class ARMV4ABSLongBXThunk final : public ARMThunk {
public:
ThumbV6MABSLongThunk(Symbol &dest, int64_t addend)
ARMV4ABSLongBXThunk(Symbol &dest, int64_t addend) : ARMThunk(dest, addend) {}

uint32_t sizeLong() override { return 12; }
void writeLong(uint8_t *buf) override;
void addSymbols(ThunkSection &isec) override;
};

class ThumbV4ABSLongBXThunk final : public ThumbThunk {
public:
ThumbV4ABSLongBXThunk(Symbol &dest, int64_t addend)
: ThumbThunk(dest, addend) {}

uint32_t sizeLong() override { return 12; }
void writeLong(uint8_t *buf) override;
void addSymbols(ThunkSection &isec) override;
};

class ThumbV6MPILongThunk final : public ThumbThunk {
class ThumbV4ABSLongThunk final : public ThumbThunk {
public:
ThumbV6MPILongThunk(Symbol &dest, int64_t addend)
ThumbV4ABSLongThunk(Symbol &dest, int64_t addend)
: ThumbThunk(dest, addend) {}

uint32_t sizeLong() override { return 16; }
Expand Down Expand Up @@ -504,6 +534,10 @@ void ARMThunk::writeTo(uint8_t *buf) {

bool ARMThunk::isCompatibleWith(const InputSection &isec,
const Relocation &rel) const {
// v4T does not have BLX, so also deny R_ARM_THM_CALL
if (!config->armHasBlx && rel.type == R_ARM_THM_CALL)
return false;

// Thumb branch relocations can't use BLX
return rel.type != R_ARM_THM_JUMP19 && rel.type != R_ARM_THM_JUMP24;
}
Expand Down Expand Up @@ -542,6 +576,10 @@ void ThumbThunk::writeTo(uint8_t *buf) {

bool ThumbThunk::isCompatibleWith(const InputSection &isec,
const Relocation &rel) const {
// v4T does not have BLX, so also deny R_ARM_CALL
if (!config->armHasBlx && rel.type == R_ARM_CALL)
return false;

// ARM branch relocations can't use BLX
return rel.type != R_ARM_JUMP24 && rel.type != R_ARM_PC24 && rel.type != R_ARM_PLT32;
}
Expand Down Expand Up @@ -624,54 +662,6 @@ void ThumbV7PILongThunk::addSymbols(ThunkSection &isec) {
addSymbol("$t", STT_NOTYPE, 0, isec);
}

void ARMV5ABSLongThunk::writeLong(uint8_t *buf) {
const uint8_t data[] = {
0x04, 0xf0, 0x1f, 0xe5, // ldr pc, [pc,#-4] ; L1
0x00, 0x00, 0x00, 0x00, // L1: .word S
};
memcpy(buf, data, sizeof(data));
target->relocateNoSym(buf + 4, R_ARM_ABS32, getARMThunkDestVA(destination));
}

void ARMV5ABSLongThunk::addSymbols(ThunkSection &isec) {
addSymbol(saver().save("__ARMv5ABSLongThunk_" + destination.getName()),
STT_FUNC, 0, isec);
addSymbol("$a", STT_NOTYPE, 0, isec);
addSymbol("$d", STT_NOTYPE, 4, isec);
}

bool ARMV5ABSLongThunk::isCompatibleWith(const InputSection &isec,
const Relocation &rel) const {
// Thumb branch relocations can't use BLX
return rel.type != R_ARM_THM_JUMP19 && rel.type != R_ARM_THM_JUMP24;
}

void ARMV5PILongThunk::writeLong(uint8_t *buf) {
const uint8_t data[] = {
0x04, 0xc0, 0x9f, 0xe5, // P: ldr ip, [pc,#4] ; L2
0x0c, 0xc0, 0x8f, 0xe0, // L1: add ip, pc, ip
0x1c, 0xff, 0x2f, 0xe1, // bx ip
0x00, 0x00, 0x00, 0x00, // L2: .word S - (P + (L1 - P) + 8)
};
uint64_t s = getARMThunkDestVA(destination);
uint64_t p = getThunkTargetSym()->getVA() & ~0x1;
memcpy(buf, data, sizeof(data));
target->relocateNoSym(buf + 12, R_ARM_REL32, s - p - 12);
}

void ARMV5PILongThunk::addSymbols(ThunkSection &isec) {
addSymbol(saver().save("__ARMV5PILongThunk_" + destination.getName()),
STT_FUNC, 0, isec);
addSymbol("$a", STT_NOTYPE, 0, isec);
addSymbol("$d", STT_NOTYPE, 12, isec);
}

bool ARMV5PILongThunk::isCompatibleWith(const InputSection &isec,
const Relocation &rel) const {
// Thumb branch relocations can't use BLX
return rel.type != R_ARM_THM_JUMP19 && rel.type != R_ARM_THM_JUMP24;
}

void ThumbV6MABSLongThunk::writeLong(uint8_t *buf) {
// Most Thumb instructions cannot access the high registers r8 - r15. As the
// only register we can corrupt is r12 we must instead spill a low register
Expand Down Expand Up @@ -722,6 +712,98 @@ void ThumbV6MPILongThunk::addSymbols(ThunkSection &isec) {
addSymbol("$d", STT_NOTYPE, 12, isec);
}

void ARMV5LongLdrPcThunk::writeLong(uint8_t *buf) {
const uint8_t data[] = {
0x04, 0xf0, 0x1f, 0xe5, // ldr pc, [pc,#-4] ; L1
0x00, 0x00, 0x00, 0x00, // L1: .word S
};
memcpy(buf, data, sizeof(data));
target->relocateNoSym(buf + 4, R_ARM_ABS32, getARMThunkDestVA(destination));
}

void ARMV5LongLdrPcThunk::addSymbols(ThunkSection &isec) {
addSymbol(saver().save("__ARMv5LongLdrPcThunk_" + destination.getName()),
STT_FUNC, 0, isec);
addSymbol("$a", STT_NOTYPE, 0, isec);
addSymbol("$d", STT_NOTYPE, 4, isec);
}

void ARMV4ABSLongBXThunk::writeLong(uint8_t *buf) {
const uint8_t data[] = {
0x00, 0xc0, 0x9f, 0xe5, // ldr r12, [pc] ; L1
0x1c, 0xff, 0x2f, 0xe1, // bx r12
0x00, 0x00, 0x00, 0x00, // L1: .word S
};
memcpy(buf, data, sizeof(data));
target->relocateNoSym(buf + 8, R_ARM_ABS32, getARMThunkDestVA(destination));
}

void ARMV4ABSLongBXThunk::addSymbols(ThunkSection &isec) {
addSymbol(saver().save("__ARMv4ABSLongBXThunk_" + destination.getName()),
STT_FUNC, 0, isec);
addSymbol("$a", STT_NOTYPE, 0, isec);
addSymbol("$d", STT_NOTYPE, 8, isec);
}

void ThumbV4ABSLongBXThunk::writeLong(uint8_t *buf) {
const uint8_t data[] = {
0x78, 0x47, // bx pc
0xfd, 0xe7, // b #-6 ; Arm recommended sequence to follow bx pc
0x04, 0xf0, 0x1f, 0xe5, // ldr pc, [pc, #-4] ; L1
0x00, 0x00, 0x00, 0x00, // L1: .word S
};
memcpy(buf, data, sizeof(data));
target->relocateNoSym(buf + 8, R_ARM_ABS32, getARMThunkDestVA(destination));
}

void ThumbV4ABSLongBXThunk::addSymbols(ThunkSection &isec) {
addSymbol(saver().save("__Thumbv4ABSLongBXThunk_" + destination.getName()),
STT_FUNC, 1, isec);
addSymbol("$t", STT_NOTYPE, 0, isec);
addSymbol("$a", STT_NOTYPE, 4, isec);
addSymbol("$d", STT_NOTYPE, 8, isec);
}

void ThumbV4ABSLongThunk::writeLong(uint8_t *buf) {
const uint8_t data[] = {
0x78, 0x47, // bx pc
0xfd, 0xe7, // b #-6 ; Arm recommended sequence to follow bx pc
0x00, 0xc0, 0x9f, 0xe5, // ldr r12, [pc] ; L1
0x1c, 0xff, 0x2f, 0xe1, // bx r12
0x00, 0x00, 0x00, 0x00, // L1: .word S
};
memcpy(buf, data, sizeof(data));
target->relocateNoSym(buf + 12, R_ARM_ABS32, getARMThunkDestVA(destination));
}

void ThumbV4ABSLongThunk::addSymbols(ThunkSection &isec) {
addSymbol(saver().save("__Thumbv4ABSLongThunk_" + destination.getName()),
STT_FUNC, 1, isec);
addSymbol("$t", STT_NOTYPE, 0, isec);
addSymbol("$a", STT_NOTYPE, 4, isec);
addSymbol("$d", STT_NOTYPE, 12, isec);
}

void ARMV4PILongThunk::writeLong(uint8_t *buf) {
const uint8_t data[] = {
0x04, 0xc0, 0x9f, 0xe5, // P: ldr ip, [pc,#4] ; L2
0x0c, 0xc0, 0x8f, 0xe0, // L1: add ip, pc, ip
0x1c, 0xff, 0x2f, 0xe1, // bx ip
0x00, 0x00, 0x00, 0x00, // L2: .word S - (P + (L1 - P) + 8)
};
uint64_t s = getARMThunkDestVA(destination);
uint64_t p = getThunkTargetSym()->getVA() & ~0x1;
memcpy(buf, data, sizeof(data));
target->relocateNoSym(buf + 12, R_ARM_REL32, s - p - 12);
}

void ARMV4PILongThunk::addSymbols(ThunkSection &isec) {
addSymbol(saver().save("__ARMv4PILongThunk_" + destination.getName()),
STT_FUNC, 0, isec);
addSymbol("$a", STT_NOTYPE, 0, isec);
addSymbol("$d", STT_NOTYPE, 12, isec);
}

// Write MIPS LA25 thunk code to call PIC function from the non-PIC one.
void MipsThunk::writeTo(uint8_t *buf) {
uint64_t s = destination.getVA();
Expand Down Expand Up @@ -1051,21 +1133,58 @@ static Thunk *addThunkAArch64(RelType type, Symbol &s, int64_t a) {
return make<AArch64ABSLongThunk>(s, a);
}

// Creates a thunk for Thumb-ARM interworking.
// Arm Architectures v5 and v6 do not support Thumb2 technology. This means
// Creates a thunk for long branches or Thumb-ARM interworking.
// Arm Architectures v4t does not support Thumb2 technology, and does not
// support BLX or LDR Arm/Thumb state switching. This means that
// - MOVT and MOVW instructions cannot be used.
// - We can't rewrite BL in place to BLX. We will need thunks.
//
// TODO: Support PIC interworking thunks for V4T.
// TODO: More efficient PIC non-interworking thunks for V4T.
// TODO: use B for short Thumb->Arm thunks instead of LDR (this doesn't work for
// Arm->Thumb, as in Arm state no BX PC trick; it doesn't switch state).
static Thunk *addThunkArmv4(RelType reloc, Symbol &s, int64_t a) {
bool thumb_target = s.getVA(a) & 1;

switch (reloc) {
case R_ARM_PC24:
case R_ARM_PLT32:
case R_ARM_JUMP24:
case R_ARM_CALL:
if (config->picThunk)
// can be used for both Arm->Arm and Arm->Thumb
return make<ARMV4PILongThunk>(s, a);
if (thumb_target)
return make<ARMV4ABSLongBXThunk>(s, a);
return make<ARMV5LongLdrPcThunk>(s, a);
case R_ARM_THM_CALL:
if (config->picThunk && !thumb_target)
fatal("PIC relocations across state change not supported for Armv4T");
if (config->picThunk && thumb_target)
return make<ThumbV6MPILongThunk>(s, a);
if (thumb_target)
return make<ThumbV4ABSLongThunk>(s, a);
return make<ThumbV4ABSLongBXThunk>(s, a);
}
fatal("relocation " + toString(reloc) + " to " + toString(s) +
" not supported for Armv4 or Armv4T target");
}

// Creates a thunk for Thumb-ARM interworking compatible with Armv5 and Armv6.
// Arm Architectures v5 and v6 do not support Thumb2 technology. This means that
// - MOVT and MOVW instructions cannot be used
// - Only Thumb relocation that can generate a Thunk is a BL, this can always
// be transformed into a BLX
static Thunk *addThunkPreArmv7(RelType reloc, Symbol &s, int64_t a) {
static Thunk *addThunkArmv5v6(RelType reloc, Symbol &s, int64_t a) {
switch (reloc) {
case R_ARM_PC24:
case R_ARM_PLT32:
case R_ARM_JUMP24:
case R_ARM_CALL:
case R_ARM_THM_CALL:
if (config->picThunk)
return make<ARMV5PILongThunk>(s, a);
return make<ARMV5ABSLongThunk>(s, a);
return make<ARMV4PILongThunk>(s, a);
return make<ARMV5LongLdrPcThunk>(s, a);
}
fatal("relocation " + toString(reloc) + " to " + toString(s) +
" not supported for Armv5 or Armv6 targets");
Expand Down Expand Up @@ -1108,9 +1227,11 @@ static Thunk *addThunkArm(RelType reloc, Symbol &s, int64_t a) {
// of the input objects. InputFiles.cpp contains the mapping from ARM
// architecture to flag.
if (!config->armHasMovtMovw) {
if (!config->armJ1J2BranchEncoding)
return addThunkPreArmv7(reloc, s, a);
return addThunkV6M(reloc, s, a);
if (config->armJ1J2BranchEncoding)
return addThunkV6M(reloc, s, a);
if (config->armHasBlx)
return addThunkArmv5v6(reloc, s, a);
return addThunkArmv4(reloc, s, a);
}

switch (reloc) {
Expand Down
Loading

0 comments on commit 62c6057

Please sign in to comment.