diff --git a/lld/ELF/Arch/Hexagon.cpp b/lld/ELF/Arch/Hexagon.cpp index 23b60672f6317..2ac5029dd8b00 100644 --- a/lld/ELF/Arch/Hexagon.cpp +++ b/lld/ELF/Arch/Hexagon.cpp @@ -10,6 +10,7 @@ #include "Symbols.h" #include "SyntheticSections.h" #include "Target.h" +#include "Thunks.h" #include "lld/Common/ErrorHandler.h" #include "llvm/BinaryFormat/ELF.h" #include "llvm/Support/Endian.h" @@ -30,6 +31,10 @@ class Hexagon final : public TargetInfo { const uint8_t *loc) const override; RelType getDynRel(RelType type) const override; int64_t getImplicitAddend(const uint8_t *buf, RelType type) const override; + bool needsThunk(RelExpr expr, RelType type, const InputFile *file, + uint64_t branchAddr, const Symbol &s, + int64_t a) const override; + bool inBranchRange(RelType type, uint64_t src, uint64_t dst) const override; void relocate(uint8_t *loc, const Relocation &rel, uint64_t val) const override; void writePltHeader(uint8_t *buf) const override; @@ -57,6 +62,8 @@ Hexagon::Hexagon(Ctx &ctx) : TargetInfo(ctx) { tlsGotRel = R_HEX_TPREL_32; tlsModuleIndexRel = R_HEX_DTPMOD_32; tlsOffsetRel = R_HEX_DTPREL_32; + + needsThunks = true; } uint32_t Hexagon::calcEFlags() const { @@ -252,6 +259,34 @@ static uint32_t findMaskR16(Ctx &ctx, uint32_t insn) { static void or32le(uint8_t *p, int32_t v) { write32le(p, read32le(p) | v); } +bool Hexagon::inBranchRange(RelType type, uint64_t src, uint64_t dst) const { + int64_t offset = dst - src; + switch (type) { + case llvm::ELF::R_HEX_B22_PCREL: + case llvm::ELF::R_HEX_PLT_B22_PCREL: + case llvm::ELF::R_HEX_GD_PLT_B22_PCREL: + case llvm::ELF::R_HEX_LD_PLT_B22_PCREL: + return llvm::isInt<22>(offset >> 2); + case llvm::ELF::R_HEX_B15_PCREL: + return llvm::isInt<15>(offset >> 2); + break; + case llvm::ELF::R_HEX_B13_PCREL: + return llvm::isInt<13>(offset >> 2); + break; + case llvm::ELF::R_HEX_B9_PCREL: + return llvm::isInt<9>(offset >> 2); + default: + return true; + } + llvm_unreachable("unsupported relocation"); +} + +bool Hexagon::needsThunk(RelExpr expr, RelType type, const InputFile *file, + uint64_t branchAddr, const Symbol &s, + int64_t a) const { + return !ctx.target->inBranchRange(type, branchAddr, s.getVA(ctx, a)); +} + void Hexagon::relocate(uint8_t *loc, const Relocation &rel, uint64_t val) const { switch (rel.type) { diff --git a/lld/ELF/Relocations.cpp b/lld/ELF/Relocations.cpp index 41c04b2903456..a95dedabd9823 100644 --- a/lld/ELF/Relocations.cpp +++ b/lld/ELF/Relocations.cpp @@ -2037,17 +2037,44 @@ void ThunkCreator::mergeThunks(ArrayRef outputSections) { }); } -static int64_t getPCBias(Ctx &ctx, RelType type) { - if (ctx.arg.emachine != EM_ARM) - return 0; - switch (type) { - case R_ARM_THM_JUMP19: - case R_ARM_THM_JUMP24: - case R_ARM_THM_CALL: - return 4; - default: - return 8; +constexpr uint32_t HEXAGON_MASK_END_PACKET = 3 << 14; +constexpr uint32_t HEXAGON_END_OF_PACKET = 3 << 14; +constexpr uint32_t HEXAGON_END_OF_DUPLEX = 0 << 14; + +// Return the distance between the packet start and the instruction in the +// relocation. +static int getHexagonPacketOffset(const InputSection &isec, + const Relocation &rel) { + const ArrayRef SectContents = isec.content(); + + // Search back as many as 3 instructions. + for (unsigned i = 0;; i++) { + if (i == 3 || rel.offset < (i + 1) * 4) + return i * 4; + uint32_t instWord = 0; + const ArrayRef InstWordContents = + SectContents.drop_front(rel.offset - (i + 1) * 4); + ::memcpy(&instWord, InstWordContents.data(), sizeof(instWord)); + if (((instWord & HEXAGON_MASK_END_PACKET) == HEXAGON_END_OF_PACKET) || + ((instWord & HEXAGON_MASK_END_PACKET) == HEXAGON_END_OF_DUPLEX)) + return i * 4; + } +} +static int64_t getPCBias(Ctx &ctx, const InputSection &isec, + const Relocation &rel) { + if (ctx.arg.emachine == EM_ARM) { + switch (rel.type) { + case R_ARM_THM_JUMP19: + case R_ARM_THM_JUMP24: + case R_ARM_THM_CALL: + return 4; + default: + return 8; + } } + if (ctx.arg.emachine == EM_HEXAGON) + return -getHexagonPacketOffset(isec, rel); + return 0; } // Find or create a ThunkSection within the InputSectionDescription (ISD) that @@ -2059,7 +2086,7 @@ ThunkSection *ThunkCreator::getISDThunkSec(OutputSection *os, const Relocation &rel, uint64_t src) { // See the comment in getThunk for -pcBias below. - const int64_t pcBias = getPCBias(ctx, rel.type); + const int64_t pcBias = getPCBias(ctx, *isec, rel); for (std::pair tp : isd->thunkSections) { ThunkSection *ts = tp.first; uint64_t tsBase = os->addr + ts->outSecOff - pcBias; @@ -2220,7 +2247,7 @@ std::pair ThunkCreator::getThunk(InputSection *isec, // out in the relocation addend. We compensate for the PC bias so that // an Arm and Thumb relocation to the same destination get the same keyAddend, // which is usually 0. - const int64_t pcBias = getPCBias(ctx, rel.type); + const int64_t pcBias = getPCBias(ctx, *isec, rel); const int64_t keyAddend = rel.addend + pcBias; // We use a ((section, offset), addend) pair to find the thunk position if @@ -2379,7 +2406,7 @@ bool ThunkCreator::createThunks(uint32_t pass, // STT_SECTION + non-zero addend, clear the addend after // redirection. if (ctx.arg.emachine != EM_MIPS) - rel.addend = -getPCBias(ctx, rel.type); + rel.addend = -getPCBias(ctx, *isec, rel); } for (auto &p : isd->thunkSections) diff --git a/lld/ELF/Thunks.cpp b/lld/ELF/Thunks.cpp index 4e4e0684a3f59..8d050f5c14aca 100644 --- a/lld/ELF/Thunks.cpp +++ b/lld/ELF/Thunks.cpp @@ -402,6 +402,22 @@ class AVRThunk : public Thunk { void addSymbols(ThunkSection &isec) override; }; +// Hexagon CPUs need thunks for R_HEX_B{9,1{3,5},22}_PCREL, +// R_HEX_{,GD_}PLT_B22_PCREL when their destination is out of +// range. +class HexagonThunk : public Thunk { +public: + HexagonThunk(Ctx &ctx, const InputSection &isec, Relocation &rel, + Symbol &dest) + : Thunk(ctx, dest, 0), relOffset(rel.offset) { + alignment = 4; + } + uint32_t relOffset; + uint32_t size() override { return ctx.arg.isPic ? 12 : 8; } + void writeTo(uint8_t *buf) override; + void addSymbols(ThunkSection &isec) override; +}; + // MIPS LA25 thunk class MipsThunk final : public Thunk { public: @@ -1475,6 +1491,39 @@ bool PPC64LongBranchThunk::isCompatibleWith(const InputSection &isec, return rel.type == R_PPC64_REL24 || rel.type == R_PPC64_REL14; } +// Hexagon Target Thunks +static uint64_t getHexagonThunkDestVA(Ctx &ctx, const Symbol &s, int64_t a) { + uint64_t v = s.isInPlt(ctx) ? s.getPltVA(ctx) : s.getVA(ctx, a); + return SignExtend64<32>(v); // FIXME: sign extend to 64-bit? +} + +void HexagonThunk::writeTo(uint8_t *buf) { + uint64_t s = getHexagonThunkDestVA(ctx, destination, addend); + uint64_t p = getThunkTargetSym()->getVA(ctx); + + if (ctx.arg.isPic) { + write32(ctx, buf + 0, 0x00004000); // { immext(#0) + ctx.target->relocateNoSym(buf, R_HEX_B32_PCREL_X, s - p); + write32(ctx, buf + 4, 0x6a49c00e); // r14 = add(pc,##0) } + ctx.target->relocateNoSym(buf + 4, R_HEX_6_PCREL_X, s - p); + + write32(ctx, buf + 8, 0x528ec000); // { jumpr r14 } + } else { + write32(ctx, buf + 0, 0x00004000); // { immext + ctx.target->relocateNoSym(buf, R_HEX_B32_PCREL_X, s - p); + write32(ctx, buf + 4, 0x5800c000); // jump <> } + ctx.target->relocateNoSym(buf + 4, R_HEX_B22_PCREL_X, s - p); + } +} +void HexagonThunk::addSymbols(ThunkSection &isec) { + Symbol *enclosing = isec.getEnclosingSymbol(relOffset); + StringRef src = enclosing ? enclosing->getName() : isec.name; + + addSymbol( + saver().save("__hexagon_thunk_" + destination.getName() + "_from_" + src), + STT_FUNC, 0, isec); +} + Thunk::Thunk(Ctx &ctx, Symbol &d, int64_t a) : ctx(ctx), destination(d), addend(a), offset(0) { destination.thunkAccessed = true; @@ -1644,6 +1693,24 @@ static std::unique_ptr addThunkAVR(Ctx &ctx, RelType type, Symbol &s, } } +static std::unique_ptr addThunkHexagon(Ctx &ctx, + const InputSection &isec, + Relocation &rel, Symbol &s) { + switch (rel.type) { + case R_HEX_B9_PCREL: + case R_HEX_B13_PCREL: + case R_HEX_B15_PCREL: + case R_HEX_B22_PCREL: + case R_HEX_PLT_B22_PCREL: + case R_HEX_GD_PLT_B22_PCREL: + return std::make_unique(ctx, isec, rel, s); + default: + Fatal(ctx) << "unrecognized relocation " << rel.type << " to " << &s + << " for hexagon target"; + llvm_unreachable(""); + } +} + static std::unique_ptr addThunkMips(Ctx &ctx, RelType type, Symbol &s) { if ((s.stOther & STO_MIPS_MICROMIPS) && isMipsR6(ctx)) return std::make_unique(ctx, s); @@ -1713,8 +1780,11 @@ std::unique_ptr elf::addThunk(Ctx &ctx, const InputSection &isec, return addThunkPPC32(ctx, isec, rel, s); case EM_PPC64: return addThunkPPC64(ctx, rel.type, s, a); + case EM_HEXAGON: + return addThunkHexagon(ctx, isec, rel, s); default: - llvm_unreachable("add Thunk only supported for ARM, AVR, Mips and PowerPC"); + llvm_unreachable( + "add Thunk only supported for ARM, AVR, Hexagon, Mips and PowerPC"); } } diff --git a/lld/test/ELF/hexagon-jump-error.s b/lld/test/ELF/hexagon-jump-error.s deleted file mode 100644 index 53860b5daf2b1..0000000000000 --- a/lld/test/ELF/hexagon-jump-error.s +++ /dev/null @@ -1,32 +0,0 @@ -# REQUIRES: hexagon -# RUN: llvm-mc -filetype=obj -triple=hexagon-unknown-elf %s -o %t.o -## Use --threads=1 to keep emitted warnings across sections sequential. -# RUN: not ld.lld %t.o -o /dev/null --threads=1 2>&1 | FileCheck --implicit-check-not "out of range" %s - - .globl _start - .type _start, @function -_start: - -# CHECK: relocation R_HEX_B9_PCREL out of range: 1028 is not in [-1024, 1023] -{r0 = #0; jump #1f} -.space (1<<10) -.section b9, "ax" -1: - -# CHECK: relocation R_HEX_B13_PCREL out of range: 16388 is not in [-16384, 16383] -if (r0==#0) jump:t #1f -.space (1<<14) -.section b13, "ax" -1: - -# CHECK: relocation R_HEX_B15_PCREL out of range: 65540 is not in [-65536, 65535] -if (p0) jump #1f -.space (1<<16) -.section b15, "ax" -1: - -# CHECK: relocation R_HEX_B22_PCREL out of range: 8388612 is not in [-8388608, 8388607] -jump #1f -.space (1<<23) -.section b22, "ax" -1: diff --git a/lld/test/ELF/hexagon-thunks-packets.s b/lld/test/ELF/hexagon-thunks-packets.s new file mode 100644 index 0000000000000..5e1bdaa071909 --- /dev/null +++ b/lld/test/ELF/hexagon-thunks-packets.s @@ -0,0 +1,122 @@ +# REQUIRES: hexagon +# RUN: llvm-mc -filetype=obj -triple=hexagon-unknown-linux-musl %s -o %t.o +# RUN: ld.lld %t.o -o %t +# RUN: llvm-objdump -d %t 2>&1 | \ +# RUN: FileCheck --check-prefixes=CHECK-NONPIC,CHECK %s +# RUN: llvm-mc -filetype=obj \ +# RUN: -triple=hexagon-unknown-linux-musl %s -o %t.o +# RUN: ld.lld --pie %t.o -o %t +# RUN: llvm-objdump -d %t 2>&1 | \ +# RUN: FileCheck --check-prefixes=CHECK-PIC,CHECK %s + +## Packets with pc-relative relocations are more interesting because +## the offset must be relative to the start of the source, destination +## packets and not necessarily the instruction word containing the jump/call. + +# CHECK: Disassembly of section .text: + +# CHECK-NONPIC: 000200b4 <__hexagon_thunk_myfn_a_from_.text.thunk>: +# CHECK-NONPIC: { immext(#0x1000040) +# CHECK-NONPIC: jump 0x1020110 } + +# CHECK-PIC: 00010150 <__hexagon_thunk_myfn_a_from_.text.thunk>: +# CHECK-PIC-NEXT: { immext(#0x1000040) +# CHECK-PIC-NEXT: r14 = add(pc,##0x1000060) } +# CHECK-PIC-NEXT: { jumpr r14 } + +# CHECK-NONPIC: 000200bc : +# CHECK-NONPIC: { jumpr r31 } +# CHECK-PIC: 0001015c : +# CHECK-PIC: { jumpr r31 } + .globl myfn_b + .type myfn_b, @function +myfn_b: + jumpr r31 + .size myfn_b, .-myfn_b + +# CHECK-PIC: 00010160
: + .globl main + .type main, @function +main: + { r0 = #0 + call myfn_a } +# CHECK-PIC: { call 0x10150 +# CHECK-NONPIC: { call 0x200b4 +# CHECK-NEXT: r0 = #0x0 } + call myfn_a +# CHECK-PIC: call 0x10150 +# CHECK-NONPIC: call 0x200b4 + call myfn_b +# CHECK-PIC-NEXT: call 0x1015c +# CHECK-NONPIC-NEXT: call 0x200bc + + { r2 = add(r0, r1) + if (p0) call #myfn_b + if (!p0) call #myfn_a } +# CHECK-PIC-NEXT: { if (p0) call 0x1015c +# CHECK-PIC-NEXT: if (!p0) call 0x10150 +# CHECK-NONPIC-NEXT: { if (p0) call 0x200bc +# CHECK-NONPIC-NEXT: if (!p0) call 0x200b4 + +# CHECK-NEXT: r2 = add(r0,r1) } + + { r2 = add(r0, r1) + if (p0) call #myfn_a + if (!p0) call #myfn_a } +# CHECK-PIC-NEXT: { if (p0) call 0x10150 +# CHECK-PIC-NEXT: if (!p0) call 0x10150 +# CHECK-NONPIC-NEXT: { if (p0) call 0x200b4 +# CHECK-NONPIC-NEXT: if (!p0) call 0x200b4 +# CHECK-NEXT: r2 = add(r0,r1) } + + { r2 = add(r0, r1) + r1 = r4 + r4 = r5 + if (r0 == #0) jump:t #myfn_a } +# CHECK-PIC-NEXT: { if (r0==#0) jump:t 0x10150 +# CHECK-NONPIC-NEXT: { if (r0==#0) jump:t 0x200b4 +# CHECK-NEXT: r2 = add(r0,r1) +# CHECK-NEXT: r1 = r4; r4 = r5 } + + { r2 = add(r0, r1) + r4 = r5 + if (r0 <= #0) jump:t #myfn_a + p1 = cmp.eq(r0, #0); if (p1.new) jump:nt #myfn_a } +# CHECK-NONPIC-NEXT: { if (r0<=#0) jump:t 0x200b4 +# CHECK-NONPIC-NEXT: p1 = cmp.eq(r0,#0x0); if (p1.new) jump:nt 0x200b4 +# CHECK-PIC-NEXT: { if (r0<=#0) jump:t 0x10150 +# CHECK-PIC-NEXT: p1 = cmp.eq(r0,#0x0); if (p1.new) jump:nt 0x10150 +# CHECK-NEXT: r2 = add(r0,r1) +# CHECK-NEXT: r4 = r5 } + + {r0 = #0; jump #myfn_a} +# CHECK-PIC-NEXT: { r0 = #0x0 ; jump 0x10150 } +# CHECK-NONPIC-NEXT: { r0 = #0x0 ; jump 0x200b4 } + {r0 = #0; jump #myfn_b} +# CHECK-PIC-NEXT: { r0 = #0x0 ; jump 0x1015c } +# CHECK-NONPIC-NEXT: { r0 = #0x0 ; jump 0x200bc } + jumpr r31 + .size main, .-main + + .section .text.foo + .skip 0x1000000 + + .globl myfn_a + .type myfn_a, @function +myfn_a: + {r0 = #0; jump #myfn_b} + jumpr r31 + .size myfn_a, .-myfn_a + +# CHECK-NONPIC: 01020110 : +# CHECK-NONPIC-NEXT: { r0 = #0x0 ; jump 0x1020118 } +# CHECK-NONPIC-NEXT: { jumpr r31 } + +# CHECK-NONPIC: 01020118 <__hexagon_thunk_myfn_b_from_.text.thunk>: +# CHECK-NONPIC-NEXT: { immext(#0xfeffff80) +# CHECK-NONPIC-NEXT: jump 0x200bc } + +# CHECK-PIC: 010101b8 <__hexagon_thunk_myfn_b_from_.text.thunk>: +# CHECK-PIC-NEXT: { immext(#0xfeffff80) +# CHECK-PIC-NEXT: r14 = add(pc,##0xfeffffa4) } +# CHECK-PIC-NEXT: { jumpr r14 } diff --git a/lld/test/ELF/hexagon-thunks.s b/lld/test/ELF/hexagon-thunks.s new file mode 100644 index 0000000000000..5361557e00d10 --- /dev/null +++ b/lld/test/ELF/hexagon-thunks.s @@ -0,0 +1,53 @@ +# REQUIRES: hexagon +# RUN: rm -rf %t && split-file %s %t +# RUN: llvm-mc -filetype=obj -triple=hexagon-unknown-elf %t/a.s -o %t/a.o +# RUN: ld.lld -T %t/lds %t/a.o -o %t/a +# RUN: llvm-objdump -d %t/a 2>&1 | \ +# RUN: FileCheck --check-prefixes=CHECK-NONPIC,CHECK %s +# RUN: llvm-mc -filetype=obj \ +# RUN: -triple=hexagon-unknown-elf %t/a.s -o %t/a.o + +# RUN: ld.lld -T %t/lds --pie %t/a.o -o %t/a +# RUN: llvm-objdump -d %t/a 2>&1 | \ +# RUN: FileCheck --check-prefixes=CHECK-PIC,CHECK %s + +#--- a.s +.section .text_low, "ax", %progbits + .globl main + .type main, @function +main: + call myfn + jumpr r31 + .size main, .-main + +.section .text_high, "ax", %progbits + .globl myfn + .type myfn, @function +myfn: + jumpr r31 + .size myfn, .-myfn + +# CHECK: Disassembly of section .text_low: + +# CHECK: 000200b4 <__hexagon_thunk_myfn_from_.text.thunk>: +# CHECK-NONPIC-NEXT: { immext(#0x1000000) +# CHECK-NONPIC-NEXT: jump 0x10200bc } +# CHECK-PIC-NEXT: { immext(#0x1000000) +# CHECK-PIC-NEXT: r14 = add(pc,##0x1000008) } +# CHECK-PIC-NEXT: { jumpr r14 } + +# CHECK-NONPIC: 000200bc
: +# CHECK-NONPIC-NEXT: call 0x200b4 +# CHECK-PIC: 000200c0
: +# CHECK-PIC-NEXT: call 0x200b4 +# CHECK-NEXT: jumpr r31 + +# CHECK: Disassembly of section .text_high: +# CHECK: 010200bc : +# CHECK-NEXT: jumpr r31 + +#--- lds +SECTIONS { + .text_low 0x200b4: { *(.text_low) } + .text_high 0x10200bc : { *(.text_high) } +}