#include "InputFiles.h"
#include "OutputSections.h"
#include "RelocScan.h"
#include "SymbolTable.h"
#include "Symbols.h"
#include "SyntheticSections.h"
#include "Target.h"
#include "Thunks.h"
using namespace llvm;
using namespace llvm::object;
using namespace llvm::support::endian;
using namespace llvm::ELF;
using namespace lld;
using namespace lld::elf;
constexpr uint64_t ppc64TocOffset = 0x8000;
constexpr uint64_t dynamicThreadPointerOffset = 0x8000;
namespace {
enum XFormOpcd {
LBZX = 87,
LHZX = 279,
LWZX = 23,
LDX = 21,
STBX = 215,
STHX = 407,
STWX = 151,
STDX = 149,
LHAX = 343,
LWAX = 341,
LFSX = 535,
LFDX = 599,
STFSX = 663,
STFDX = 727,
ADD = 266,
};
enum DFormOpcd {
LBZ = 34,
LBZU = 35,
LHZ = 40,
LHZU = 41,
LHAU = 43,
LWZ = 32,
LWZU = 33,
LFSU = 49,
LFDU = 51,
STB = 38,
STBU = 39,
STH = 44,
STHU = 45,
STW = 36,
STWU = 37,
STFSU = 53,
STFDU = 55,
LHA = 42,
LFS = 48,
LFD = 50,
STFS = 52,
STFD = 54,
ADDI = 14
};
enum DSFormOpcd {
LD = 58,
LWA = 58,
STD = 62
};
constexpr uint32_t NOP = 0x60000000;
enum class PPCLegacyInsn : uint32_t {
NOINSN = 0,
LBZ = 0x88000000,
LHZ = 0xa0000000,
LWZ = 0x80000000,
LHA = 0xa8000000,
LWA = 0xe8000002,
LD = 0xe8000000,
LFS = 0xC0000000,
LXSSP = 0xe4000003,
LFD = 0xc8000000,
LXSD = 0xe4000002,
LXV = 0xf4000001,
LXVP = 0x18000000,
STB = 0x98000000,
STH = 0xb0000000,
STW = 0x90000000,
STD = 0xf8000000,
STFS = 0xd0000000,
STXSSP = 0xf4000003,
STFD = 0xd8000000,
STXSD = 0xf4000002,
STXV = 0xf4000005,
STXVP = 0x18000001
};
enum class PPCPrefixedInsn : uint64_t {
NOINSN = 0,
PREFIX_MLS = 0x0610000000000000,
PREFIX_8LS = 0x0410000000000000,
PLBZ = PREFIX_MLS,
PLHZ = PREFIX_MLS,
PLWZ = PREFIX_MLS,
PLHA = PREFIX_MLS,
PLWA = PREFIX_8LS | 0xa4000000,
PLD = PREFIX_8LS | 0xe4000000,
PLFS = PREFIX_MLS,
PLXSSP = PREFIX_8LS | 0xac000000,
PLFD = PREFIX_MLS,
PLXSD = PREFIX_8LS | 0xa8000000,
PLXV = PREFIX_8LS | 0xc8000000,
PLXVP = PREFIX_8LS | 0xe8000000,
PSTB = PREFIX_MLS,
PSTH = PREFIX_MLS,
PSTW = PREFIX_MLS,
PSTD = PREFIX_8LS | 0xf4000000,
PSTFS = PREFIX_MLS,
PSTXSSP = PREFIX_8LS | 0xbc000000,
PSTFD = PREFIX_MLS,
PSTXSD = PREFIX_8LS | 0xb8000000,
PSTXV = PREFIX_8LS | 0xd8000000,
PSTXVP = PREFIX_8LS | 0xf8000000
};
static bool checkPPCLegacyInsn(uint32_t encoding) {
PPCLegacyInsn insn = static_cast<PPCLegacyInsn>(encoding);
if (insn == PPCLegacyInsn::NOINSN)
return false;
#define PCREL_OPT(Legacy, PCRel, InsnMask) \
if (insn == PPCLegacyInsn::Legacy) \
return true;
#include "PPCInsns.def"
#undef PCREL_OPT
return false;
}
enum class LegacyToPrefixMask : uint64_t {
NOMASK = 0x0,
OPC_AND_RST = 0xffe00000,
ONLY_RST = 0x3e00000,
ST_STX28_TO5 =
0x8000000003e00000,
};
class PPC64 final : public TargetInfo {
public:
PPC64(Ctx &);
int getTlsGdRelaxSkip(RelType type) const override;
uint32_t calcEFlags() const override;
RelExpr getRelExpr(RelType type, const Symbol &s,
const uint8_t *loc) const override;
RelType getDynRel(RelType type) const override;
int64_t getImplicitAddend(const uint8_t *buf, RelType type) const override;
void writePltHeader(uint8_t *buf) const override;
void writePlt(uint8_t *buf, const Symbol &sym,
uint64_t pltEntryAddr) const override;
void writeIplt(uint8_t *buf, const Symbol &sym,
uint64_t pltEntryAddr) const override;
template <class ELFT, class RelTy>
void scanSectionImpl(InputSectionBase &, Relocs<RelTy>);
template <class ELFT> void scanSection1(InputSectionBase &);
void scanSection(InputSectionBase &) override;
void relocate(uint8_t *loc, const Relocation &rel,
uint64_t val) const override;
void writeGotHeader(uint8_t *buf) const override;
bool needsThunk(RelExpr expr, RelType type, const InputFile *file,
uint64_t branchAddr, const Symbol &s,
int64_t a) const override;
uint32_t getThunkSectionSpacing() const override;
bool inBranchRange(RelType type, uint64_t src, uint64_t dst) const override;
RelExpr adjustTlsExpr(RelType type, RelExpr expr) const override;
RelExpr adjustGotPcExpr(RelType type, int64_t addend,
const uint8_t *loc) const override;
void relaxGot(uint8_t *loc, const Relocation &rel, uint64_t val) const;
void relocateAlloc(InputSection &sec, uint8_t *buf) const override;
bool adjustPrologueForCrossSplitStack(uint8_t *loc, uint8_t *end,
uint8_t stOther) const override;
private:
void relaxTlsGdToIe(uint8_t *loc, const Relocation &rel, uint64_t val) const;
void relaxTlsGdToLe(uint8_t *loc, const Relocation &rel, uint64_t val) const;
void relaxTlsLdToLe(uint8_t *loc, const Relocation &rel, uint64_t val) const;
void relaxTlsIeToLe(uint8_t *loc, const Relocation &rel, uint64_t val) const;
};
}
uint64_t elf::getPPC64TocBase(Ctx &ctx) {
uint64_t tocVA = ctx.in.got->getVA();
return tocVA + ppc64TocOffset;
}
unsigned elf::getPPC64GlobalEntryToLocalEntryOffset(Ctx &ctx, uint8_t stOther) {
uint8_t gepToLep = (stOther >> 5) & 7;
if (gepToLep < 2)
return 0;
if (gepToLep < 7)
return 1 << gepToLep;
ErrAlways(ctx)
<< "reserved value of 7 in the 3 most-significant-bits of st_other";
return 0;
}
void elf::writePrefixedInst(Ctx &ctx, uint8_t *loc, uint64_t insn) {
insn = ctx.arg.isLE ? insn << 32 | insn >> 32 : insn;
write64(ctx, loc, insn);
}
static bool addOptional(Ctx &ctx, StringRef name, uint64_t value,
std::vector<Defined *> &defined) {
Symbol *sym = ctx.symtab->find(name);
if (!sym || sym->isDefined())
return false;
sym->resolve(ctx, Defined{ctx, ctx.internalFile, StringRef(), STB_GLOBAL,
STV_HIDDEN, STT_FUNC, value,
0, nullptr});
defined.push_back(cast<Defined>(sym));
return true;
}
static void writeSequence(Ctx &ctx, const char *prefix, int from,
uint32_t firstInsn, ArrayRef<uint32_t> tail) {
std::vector<Defined *> defined;
char name[16];
int first;
const size_t size = 32 - from + tail.size();
MutableArrayRef<uint32_t> buf(ctx.bAlloc.Allocate<uint32_t>(size), size);
uint32_t *ptr = buf.data();
for (int r = from; r < 32; ++r) {
format("%s%d", prefix, r).snprint(name, sizeof(name));
if (addOptional(ctx, name, 4 * (r - from), defined) && defined.size() == 1)
first = r - from;
write32(ctx, ptr++, firstInsn + 0x200008 * (r - from));
}
for (uint32_t insn : tail)
write32(ctx, ptr++, insn);
assert(ptr == &*buf.end());
if (defined.empty())
return;
auto *sec = make<InputSection>(
ctx.internalFile, ".text", SHT_PROGBITS, SHF_ALLOC, 4,
0,
ArrayRef(reinterpret_cast<uint8_t *>(buf.data() + first),
4 * (buf.size() - first)));
ctx.inputSections.push_back(sec);
for (Defined *sym : defined) {
sym->section = sec;
sym->value -= 4 * first;
}
}
void elf::addPPC64SaveRestore(Ctx &ctx) {
constexpr uint32_t blr = 0x4e800020, mtlr_0 = 0x7c0803a6;
writeSequence(ctx, "_restgpr0_", 14, 0xe9c1ff70, {0xe8010010, mtlr_0, blr});
writeSequence(ctx, "_restgpr1_", 14, 0xe9ccff70, {blr});
writeSequence(ctx, "_savegpr0_", 14, 0xf9c1ff70, {0xf8010010, blr});
writeSequence(ctx, "_savegpr1_", 14, 0xf9ccff70, {blr});
}
template <typename ELFT>
static std::pair<Defined *, int64_t>
getRelaTocSymAndAddend(InputSectionBase *tocSec, uint64_t offset) {
ArrayRef<typename ELFT::Rela> relas =
tocSec->template relsOrRelas<ELFT>().relas;
if (relas.empty())
return {};
uint64_t index = std::min<uint64_t>(offset / 8, relas.size() - 1);
for (;;) {
if (relas[index].r_offset == offset) {
Symbol &sym = tocSec->file->getRelocTargetSym(relas[index]);
return {dyn_cast<Defined>(&sym), getAddend<ELFT>(relas[index])};
}
if (relas[index].r_offset < offset || index == 0)
break;
--index;
}
return {};
}
static bool tryRelaxPPC64TocIndirection(Ctx &ctx, const Relocation &rel,
uint8_t *bufLoc) {
assert(ctx.arg.tocOptimize);
if (rel.addend < 0)
return false;
Defined *defSym = dyn_cast<Defined>(rel.sym);
if (!defSym || !defSym->isSection() || defSym->section->name != ".toc")
return false;
Defined *d;
int64_t addend;
auto *tocISB = cast<InputSectionBase>(defSym->section);
std::tie(d, addend) =
ctx.arg.isLE ? getRelaTocSymAndAddend<ELF64LE>(tocISB, rel.addend)
: getRelaTocSymAndAddend<ELF64BE>(tocISB, rel.addend);
if (!d || d->isPreemptible)
return false;
assert(!d->isGnuIFunc());
uint64_t tocRelative = d->getVA(ctx, addend) - getPPC64TocBase(ctx);
if (!isInt<32>(tocRelative))
return false;
static_cast<const PPC64 &>(*ctx.target)
.relaxGot(bufLoc, rel, tocRelative + ppc64TocOffset);
return true;
}
static uint16_t lo(uint64_t v) { return v; }
static uint16_t hi(uint64_t v) { return v >> 16; }
static uint64_t ha(uint64_t v) { return (v + 0x8000) >> 16; }
static uint16_t higher(uint64_t v) { return v >> 32; }
static uint16_t highera(uint64_t v) { return (v + 0x8000) >> 32; }
static uint16_t highest(uint64_t v) { return v >> 48; }
static uint16_t highesta(uint64_t v) { return (v + 0x8000) >> 48; }
static uint8_t getPrimaryOpCode(uint32_t encoding) { return (encoding >> 26); }
static bool isDQFormInstruction(uint32_t encoding) {
switch (getPrimaryOpCode(encoding)) {
default:
return false;
case 6:
case 56:
return true;
case 61:
return (encoding & 3) == 0x1;
}
}
static bool isDSFormInstruction(PPCLegacyInsn insn) {
switch (insn) {
default:
return false;
case PPCLegacyInsn::LWA:
case PPCLegacyInsn::LD:
case PPCLegacyInsn::LXSD:
case PPCLegacyInsn::LXSSP:
case PPCLegacyInsn::STD:
case PPCLegacyInsn::STXSD:
case PPCLegacyInsn::STXSSP:
return true;
}
}
static PPCLegacyInsn getPPCLegacyInsn(uint32_t encoding) {
uint32_t opc = encoding & 0xfc000000;
if ((opc == 0xe4000000 || opc == 0xe8000000 || opc == 0xf4000000 ||
opc == 0xf8000000) &&
!isDQFormInstruction(encoding))
opc = encoding & 0xfc000003;
else if (opc == 0xf4000000)
opc = encoding & 0xfc000007;
else if (opc == 0x18000000)
opc = encoding & 0xfc00000f;
if (!checkPPCLegacyInsn(opc))
return PPCLegacyInsn::NOINSN;
return static_cast<PPCLegacyInsn>(opc);
}
static PPCPrefixedInsn getPCRelativeForm(PPCLegacyInsn insn) {
switch (insn) {
#define PCREL_OPT(Legacy, PCRel, InsnMask) \
case PPCLegacyInsn::Legacy: \
return PPCPrefixedInsn::PCRel
#include "PPCInsns.def"
#undef PCREL_OPT
}
return PPCPrefixedInsn::NOINSN;
}
static LegacyToPrefixMask getInsnMask(PPCLegacyInsn insn) {
switch (insn) {
#define PCREL_OPT(Legacy, PCRel, InsnMask) \
case PPCLegacyInsn::Legacy: \
return LegacyToPrefixMask::InsnMask
#include "PPCInsns.def"
#undef PCREL_OPT
}
return LegacyToPrefixMask::NOMASK;
}
static uint64_t getPCRelativeForm(uint32_t encoding) {
PPCLegacyInsn origInsn = getPPCLegacyInsn(encoding);
PPCPrefixedInsn pcrelInsn = getPCRelativeForm(origInsn);
if (pcrelInsn == PPCPrefixedInsn::NOINSN)
return UINT64_C(-1);
LegacyToPrefixMask origInsnMask = getInsnMask(origInsn);
uint64_t pcrelEncoding =
(uint64_t)pcrelInsn | (encoding & (uint64_t)origInsnMask);
if (origInsnMask == LegacyToPrefixMask::ST_STX28_TO5)
pcrelEncoding |= (encoding & 0x8) << 23;
return pcrelEncoding;
}
static bool isInstructionUpdateForm(uint32_t encoding) {
switch (getPrimaryOpCode(encoding)) {
default:
return false;
case LBZU:
case LHAU:
case LHZU:
case LWZU:
case LFSU:
case LFDU:
case STBU:
case STHU:
case STWU:
case STFSU:
case STFDU:
return true;
case LD:
case STD:
return (encoding & 3) == 1;
}
}
static int64_t getTotalDisp(uint64_t prefixedInsn, uint32_t accessInsn) {
int64_t disp34 = llvm::SignExtend64(
((prefixedInsn & 0x3ffff00000000) >> 16) | (prefixedInsn & 0xffff), 34);
int32_t disp16 = llvm::SignExtend32(accessInsn & 0xffff, 16);
if (isDQFormInstruction(accessInsn))
disp16 &= ~0xf;
else if (isDSFormInstruction(getPPCLegacyInsn(accessInsn)))
disp16 &= ~0x3;
return disp34 + disp16;
}
static void writeFromHalf16(Ctx &ctx, uint8_t *loc, uint32_t insn) {
write32(ctx, ctx.arg.isLE ? loc : loc - 2, insn);
}
static uint32_t readFromHalf16(Ctx &ctx, const uint8_t *loc) {
return read32(ctx, ctx.arg.isLE ? loc : loc - 2);
}
static uint64_t readPrefixedInst(Ctx &ctx, const uint8_t *loc) {
uint64_t fullInstr = read64(ctx, loc);
return ctx.arg.isLE ? (fullInstr << 32 | fullInstr >> 32) : fullInstr;
}
PPC64::PPC64(Ctx &ctx) : TargetInfo(ctx) {
copyRel = R_PPC64_COPY;
gotRel = R_PPC64_GLOB_DAT;
pltRel = R_PPC64_JMP_SLOT;
relativeRel = R_PPC64_RELATIVE;
iRelativeRel = R_PPC64_IRELATIVE;
symbolicRel = R_PPC64_ADDR64;
pltHeaderSize = 60;
pltEntrySize = 4;
ipltEntrySize = 16;
gotHeaderEntriesNum = 1;
gotPltHeaderEntriesNum = 2;
needsThunks = true;
tlsModuleIndexRel = R_PPC64_DTPMOD64;
tlsOffsetRel = R_PPC64_DTPREL64;
tlsGotRel = R_PPC64_TPREL64;
needsMoreStackNonSplit = false;
defaultMaxPageSize = 65536;
defaultImageBase = 0x10000000;
write32(ctx, trapInstr.data(), 0x7fe00008);
}
int PPC64::getTlsGdRelaxSkip(RelType type) const {
if (type == R_PPC64_TLSGD || type == R_PPC64_TLSLD)
return 2;
return 1;
}
static uint32_t getEFlags(InputFile *file) {
if (file->ekind == ELF64BEKind)
return cast<ObjFile<ELF64BE>>(file)->getObj().getHeader().e_flags;
return cast<ObjFile<ELF64LE>>(file)->getObj().getHeader().e_flags;
}
uint32_t PPC64::calcEFlags() const {
for (InputFile *f : ctx.objectFiles) {
uint32_t flag = getEFlags(f);
if (flag == 1)
ErrAlways(ctx) << f << ": ABI version 1 is not supported";
else if (flag > 2)
ErrAlways(ctx) << f << ": unrecognized e_flags: " << flag;
}
return 2;
}
void PPC64::relaxGot(uint8_t *loc, const Relocation &rel, uint64_t val) const {
switch (rel.type) {
case R_PPC64_TOC16_HA:
relocate(loc, rel, val);
break;
case R_PPC64_TOC16_LO_DS: {
uint32_t insn = readFromHalf16(ctx, loc);
if (getPrimaryOpCode(insn) != LD)
ErrAlways(ctx)
<< "expected a 'ld' for got-indirect to toc-relative relaxing";
writeFromHalf16(ctx, loc, (insn & 0x03ffffff) | 0x38000000);
relocateNoSym(loc, R_PPC64_TOC16_LO, val);
break;
}
case R_PPC64_GOT_PCREL34: {
uint64_t insn = readPrefixedInst(ctx, loc);
if ((insn & 0xfc000000) != 0xe4000000)
ErrAlways(ctx)
<< "expected a 'pld' for got-indirect to pc-relative relaxing";
insn &= ~0xff000000fc000000;
insn |= 0x600000038000000;
writePrefixedInst(ctx, loc, insn);
relocate(loc, rel, val);
break;
}
case R_PPC64_PCREL_OPT: {
uint64_t insn = readPrefixedInst(ctx, loc);
uint32_t accessInsn = read32(ctx, loc + rel.addend);
uint64_t pcRelInsn = getPCRelativeForm(accessInsn);
if (pcRelInsn == UINT64_C(-1)) {
Err(ctx)
<< "unrecognized instruction for R_PPC64_PCREL_OPT relaxation: 0x"
<< utohexstr(accessInsn, true);
break;
}
int64_t totalDisp = getTotalDisp(insn, accessInsn);
if (!isInt<34>(totalDisp))
break;
writePrefixedInst(ctx, loc,
pcRelInsn | ((totalDisp & 0x3ffff0000) << 16) |
(totalDisp & 0xffff));
write32(ctx, loc + rel.addend, NOP);
break;
}
default:
llvm_unreachable("unexpected relocation type");
}
}
void PPC64::relaxTlsGdToLe(uint8_t *loc, const Relocation &rel,
uint64_t val) const {
switch (rel.type) {
case R_PPC64_GOT_TLSGD16_HA:
writeFromHalf16(ctx, loc, NOP);
break;
case R_PPC64_GOT_TLSGD16:
case R_PPC64_GOT_TLSGD16_LO:
writeFromHalf16(ctx, loc, 0x3c6d0000);
relocateNoSym(loc, R_PPC64_TPREL16_HA, val);
break;
case R_PPC64_GOT_TLSGD_PCREL34:
writePrefixedInst(ctx, loc, 0x06000000386d0000);
relocateNoSym(loc, R_PPC64_TPREL34, val);
break;
case R_PPC64_TLSGD: {
const uintptr_t locAsInt = reinterpret_cast<uintptr_t>(loc);
if (locAsInt % 4 == 0) {
write32(ctx, loc, NOP);
write32(ctx, loc + 4, 0x38630000);
relocateNoSym(loc + 4 + (ctx.arg.ekind == ELF64BEKind ? 2 : 0),
R_PPC64_TPREL16_LO, val);
} else if (locAsInt % 4 == 1) {
write32(ctx, loc - 1, NOP);
} else {
Err(ctx) << "R_PPC64_TLSGD has unexpected byte alignment";
}
break;
}
default:
llvm_unreachable("unsupported relocation for TLS GD to LE relaxation");
}
}
void PPC64::relaxTlsLdToLe(uint8_t *loc, const Relocation &rel,
uint64_t val) const {
switch (rel.type) {
case R_PPC64_GOT_TLSLD16_HA:
writeFromHalf16(ctx, loc, NOP);
break;
case R_PPC64_GOT_TLSLD16_LO:
writeFromHalf16(ctx, loc, 0x3c6d0000);
break;
case R_PPC64_GOT_TLSLD_PCREL34:
writePrefixedInst(ctx, loc, 0x06000000386d1000);
break;
case R_PPC64_TLSLD: {
const uintptr_t locAsInt = reinterpret_cast<uintptr_t>(loc);
if (locAsInt % 4 == 0) {
write32(ctx, loc, NOP);
write32(ctx, loc + 4, 0x38631000);
} else if (locAsInt % 4 == 1) {
write32(ctx, loc - 1, NOP);
} else {
Err(ctx) << "R_PPC64_TLSLD has unexpected byte alignment";
}
break;
}
case R_PPC64_DTPREL16:
case R_PPC64_DTPREL16_HA:
case R_PPC64_DTPREL16_HI:
case R_PPC64_DTPREL16_DS:
case R_PPC64_DTPREL16_LO:
case R_PPC64_DTPREL16_LO_DS:
case R_PPC64_DTPREL34:
relocate(loc, rel, val);
break;
default:
llvm_unreachable("unsupported relocation for TLS LD to LE relaxation");
}
}
unsigned elf::getPPCDSFormOp(unsigned secondaryOp) {
switch (secondaryOp) {
case LWAX:
return (LWA << 26) | 0x2;
case LDX:
return LD << 26;
case STDX:
return STD << 26;
default:
return 0;
}
}
unsigned elf::getPPCDFormOp(unsigned secondaryOp) {
switch (secondaryOp) {
case LBZX:
return LBZ << 26;
case LHZX:
return LHZ << 26;
case LWZX:
return LWZ << 26;
case STBX:
return STB << 26;
case STHX:
return STH << 26;
case STWX:
return STW << 26;
case LHAX:
return LHA << 26;
case LFSX:
return LFS << 26;
case LFDX:
return LFD << 26;
case STFSX:
return STFS << 26;
case STFDX:
return STFD << 26;
case ADD:
return ADDI << 26;
default:
return 0;
}
}
void PPC64::relaxTlsIeToLe(uint8_t *loc, const Relocation &rel,
uint64_t val) const {
unsigned offset = (ctx.arg.ekind == ELF64BEKind) ? 2 : 0;
switch (rel.type) {
case R_PPC64_GOT_TPREL16_HA:
write32(ctx, loc - offset, NOP);
break;
case R_PPC64_GOT_TPREL16_LO_DS:
case R_PPC64_GOT_TPREL16_DS: {
uint32_t regNo = read32(ctx, loc - offset) & 0x03e00000;
write32(ctx, loc - offset, 0x3c0d0000 | regNo);
relocateNoSym(loc, R_PPC64_TPREL16_HA, val);
break;
}
case R_PPC64_GOT_TPREL_PCREL34: {
const uint64_t pldRT = readPrefixedInst(ctx, loc) & 0x0000000003e00000;
writePrefixedInst(ctx, loc, 0x06000000380d0000 | pldRT);
relocateNoSym(loc, R_PPC64_TPREL34, val);
break;
}
case R_PPC64_TLS: {
const uintptr_t locAsInt = reinterpret_cast<uintptr_t>(loc);
if (locAsInt % 4 == 0) {
uint32_t primaryOp = getPrimaryOpCode(read32(ctx, loc));
if (primaryOp != 31)
ErrAlways(ctx) << "unrecognized instruction for IE to LE R_PPC64_TLS";
uint32_t secondaryOp = (read32(ctx, loc) & 0x000007fe) >> 1;
uint32_t dFormOp = getPPCDFormOp(secondaryOp);
uint32_t finalReloc;
if (dFormOp == 0) {
dFormOp = getPPCDSFormOp(secondaryOp);
if (dFormOp == 0)
ErrAlways(ctx) << "unrecognized instruction for IE to LE R_PPC64_TLS";
finalReloc = R_PPC64_TPREL16_LO_DS;
} else
finalReloc = R_PPC64_TPREL16_LO;
write32(ctx, loc, dFormOp | (read32(ctx, loc) & 0x03ff0000));
relocateNoSym(loc + offset, finalReloc, val);
} else if (locAsInt % 4 == 1) {
uint32_t tlsInstr = read32(ctx, loc - 1);
uint32_t primaryOp = getPrimaryOpCode(tlsInstr);
if (primaryOp != 31)
Err(ctx) << "unrecognized instruction for IE to LE R_PPC64_TLS";
uint32_t secondaryOp = (tlsInstr & 0x000007FE) >> 1;
if (secondaryOp == 266) {
uint32_t rt = (tlsInstr & 0x03E00000) >> 21;
uint32_t ra = (tlsInstr & 0x001F0000) >> 16;
if (ra == rt) {
write32(ctx, loc - 1, NOP);
} else {
write32(ctx, loc - 1,
0x7C000378 | (rt << 16) | (ra << 21) | (ra << 11));
}
} else {
uint32_t dFormOp = getPPCDFormOp(secondaryOp);
if (dFormOp == 0) {
dFormOp = getPPCDSFormOp(secondaryOp);
if (dFormOp == 0)
Err(ctx) << "unrecognized instruction for IE to LE R_PPC64_TLS";
}
write32(ctx, loc - 1, (dFormOp | (tlsInstr & 0x03ff0000)));
}
} else {
Err(ctx) << "R_PPC64_TLS must be either 4 byte aligned or one byte "
"offset from 4 byte aligned";
}
break;
}
default:
llvm_unreachable("unknown relocation for IE to LE");
break;
}
}
RelExpr PPC64::getRelExpr(RelType type, const Symbol &s,
const uint8_t *loc) const {
switch (type) {
case R_PPC64_NONE:
return R_NONE;
case R_PPC64_ADDR16:
case R_PPC64_ADDR16_DS:
case R_PPC64_ADDR16_HA:
case R_PPC64_ADDR16_HI:
case R_PPC64_ADDR16_HIGH:
case R_PPC64_ADDR16_HIGHER:
case R_PPC64_ADDR16_HIGHERA:
case R_PPC64_ADDR16_HIGHEST:
case R_PPC64_ADDR16_HIGHESTA:
case R_PPC64_ADDR16_LO:
case R_PPC64_ADDR16_LO_DS:
case R_PPC64_ADDR32:
case R_PPC64_ADDR64:
return R_ABS;
case R_PPC64_GOT16:
case R_PPC64_GOT16_DS:
case R_PPC64_GOT16_HA:
case R_PPC64_GOT16_HI:
case R_PPC64_GOT16_LO:
case R_PPC64_GOT16_LO_DS:
return R_GOT_OFF;
case R_PPC64_TOC16:
case R_PPC64_TOC16_DS:
case R_PPC64_TOC16_HI:
case R_PPC64_TOC16_LO:
return R_GOTREL;
case R_PPC64_GOT_PCREL34:
case R_PPC64_GOT_TPREL_PCREL34:
case R_PPC64_PCREL_OPT:
return R_GOT_PC;
case R_PPC64_TOC16_HA:
case R_PPC64_TOC16_LO_DS:
return ctx.arg.tocOptimize ? RE_PPC64_RELAX_TOC : R_GOTREL;
case R_PPC64_TOC:
return RE_PPC64_TOCBASE;
case R_PPC64_REL14:
case R_PPC64_REL24:
return RE_PPC64_CALL_PLT;
case R_PPC64_REL24_NOTOC:
return R_PLT_PC;
case R_PPC64_REL16_LO:
case R_PPC64_REL16_HA:
case R_PPC64_REL16_HI:
case R_PPC64_REL32:
case R_PPC64_REL64:
case R_PPC64_PCREL34:
return R_PC;
case R_PPC64_GOT_TLSGD16:
case R_PPC64_GOT_TLSGD16_HA:
case R_PPC64_GOT_TLSGD16_HI:
case R_PPC64_GOT_TLSGD16_LO:
return R_TLSGD_GOT;
case R_PPC64_GOT_TLSGD_PCREL34:
return R_TLSGD_PC;
case R_PPC64_GOT_TLSLD16:
case R_PPC64_GOT_TLSLD16_HA:
case R_PPC64_GOT_TLSLD16_HI:
case R_PPC64_GOT_TLSLD16_LO:
return R_TLSLD_GOT;
case R_PPC64_GOT_TLSLD_PCREL34:
return R_TLSLD_PC;
case R_PPC64_GOT_TPREL16_HA:
case R_PPC64_GOT_TPREL16_LO_DS:
case R_PPC64_GOT_TPREL16_DS:
case R_PPC64_GOT_TPREL16_HI:
return R_GOT_OFF;
case R_PPC64_GOT_DTPREL16_HA:
case R_PPC64_GOT_DTPREL16_LO_DS:
case R_PPC64_GOT_DTPREL16_DS:
case R_PPC64_GOT_DTPREL16_HI:
return R_TLSLD_GOT_OFF;
case R_PPC64_TPREL16:
case R_PPC64_TPREL16_HA:
case R_PPC64_TPREL16_LO:
case R_PPC64_TPREL16_HI:
case R_PPC64_TPREL16_DS:
case R_PPC64_TPREL16_LO_DS:
case R_PPC64_TPREL16_HIGHER:
case R_PPC64_TPREL16_HIGHERA:
case R_PPC64_TPREL16_HIGHEST:
case R_PPC64_TPREL16_HIGHESTA:
case R_PPC64_TPREL34:
return R_TPREL;
case R_PPC64_DTPREL16:
case R_PPC64_DTPREL16_DS:
case R_PPC64_DTPREL16_HA:
case R_PPC64_DTPREL16_HI:
case R_PPC64_DTPREL16_HIGHER:
case R_PPC64_DTPREL16_HIGHERA:
case R_PPC64_DTPREL16_HIGHEST:
case R_PPC64_DTPREL16_HIGHESTA:
case R_PPC64_DTPREL16_LO:
case R_PPC64_DTPREL16_LO_DS:
case R_PPC64_DTPREL64:
case R_PPC64_DTPREL34:
return R_DTPREL;
case R_PPC64_TLSGD:
return R_TLSDESC_CALL;
case R_PPC64_TLSLD:
return R_TLSLD_HINT;
case R_PPC64_TLS:
return R_TLSIE_HINT;
default:
Err(ctx) << getErrorLoc(ctx, loc) << "unknown relocation (" << type.v
<< ") against symbol " << &s;
return R_NONE;
}
}
RelType PPC64::getDynRel(RelType type) const {
if (type == R_PPC64_ADDR64 || type == R_PPC64_TOC)
return R_PPC64_ADDR64;
return R_PPC64_NONE;
}
int64_t PPC64::getImplicitAddend(const uint8_t *buf, RelType type) const {
switch (type) {
case R_PPC64_NONE:
case R_PPC64_GLOB_DAT:
case R_PPC64_JMP_SLOT:
return 0;
case R_PPC64_REL32:
return SignExtend64<32>(read32(ctx, buf));
case R_PPC64_ADDR64:
case R_PPC64_REL64:
case R_PPC64_RELATIVE:
case R_PPC64_IRELATIVE:
case R_PPC64_DTPMOD64:
case R_PPC64_DTPREL64:
case R_PPC64_TPREL64:
return read64(ctx, buf);
default:
InternalErr(ctx, buf) << "cannot read addend for relocation " << type;
return 0;
}
}
void PPC64::writeGotHeader(uint8_t *buf) const {
write64(ctx, buf, getPPC64TocBase(ctx));
}
void PPC64::writePltHeader(uint8_t *buf) const {
write32(ctx, buf + 0, 0x7c0802a6);
write32(ctx, buf + 4, 0x429f0005);
write32(ctx, buf + 8, 0x7d6802a6);
write32(ctx, buf + 12, 0x7c0803a6);
write32(ctx, buf + 16, 0x7d8b6050);
write32(ctx, buf + 20, 0x380cffcc);
write32(ctx, buf + 24, 0x7800f082);
write32(ctx, buf + 28, 0xe98b002c);
write32(ctx, buf + 32, 0x7d6c5a14);
write32(ctx, buf + 36, 0xe98b0000);
write32(ctx, buf + 40, 0xe96b0008);
write32(ctx, buf + 44, 0x7d8903a6);
write32(ctx, buf + 48, 0x4e800420);
int64_t gotPltOffset = ctx.in.gotPlt->getVA() - (ctx.in.plt->getVA() + 8);
write64(ctx, buf + 52, gotPltOffset);
}
void PPC64::writePlt(uint8_t *buf, const Symbol &sym,
uint64_t ) const {
int32_t offset = pltHeaderSize + sym.getPltIdx(ctx) * pltEntrySize;
write32(ctx, buf, 0x48000000 | ((-offset) & 0x03fffffc));
}
void PPC64::writeIplt(uint8_t *buf, const Symbol &sym,
uint64_t ) const {
writePPC64LoadAndBranch(ctx, buf,
sym.getGotPltVA(ctx) - getPPC64TocBase(ctx));
}
static std::pair<RelType, uint64_t> toAddr16Rel(RelType type, uint64_t val) {
uint64_t tocBiasedVal = val - ppc64TocOffset;
uint64_t dtpBiasedVal = val - dynamicThreadPointerOffset;
switch (type) {
case R_PPC64_GOT16:
case R_PPC64_GOT_TLSGD16:
case R_PPC64_GOT_TLSLD16:
case R_PPC64_TOC16:
return {R_PPC64_ADDR16, tocBiasedVal};
case R_PPC64_GOT16_DS:
case R_PPC64_TOC16_DS:
case R_PPC64_GOT_TPREL16_DS:
case R_PPC64_GOT_DTPREL16_DS:
return {R_PPC64_ADDR16_DS, tocBiasedVal};
case R_PPC64_GOT16_HA:
case R_PPC64_GOT_TLSGD16_HA:
case R_PPC64_GOT_TLSLD16_HA:
case R_PPC64_GOT_TPREL16_HA:
case R_PPC64_GOT_DTPREL16_HA:
case R_PPC64_TOC16_HA:
return {R_PPC64_ADDR16_HA, tocBiasedVal};
case R_PPC64_GOT16_HI:
case R_PPC64_GOT_TLSGD16_HI:
case R_PPC64_GOT_TLSLD16_HI:
case R_PPC64_GOT_TPREL16_HI:
case R_PPC64_GOT_DTPREL16_HI:
case R_PPC64_TOC16_HI:
return {R_PPC64_ADDR16_HI, tocBiasedVal};
case R_PPC64_GOT16_LO:
case R_PPC64_GOT_TLSGD16_LO:
case R_PPC64_GOT_TLSLD16_LO:
case R_PPC64_TOC16_LO:
return {R_PPC64_ADDR16_LO, tocBiasedVal};
case R_PPC64_GOT16_LO_DS:
case R_PPC64_TOC16_LO_DS:
case R_PPC64_GOT_TPREL16_LO_DS:
case R_PPC64_GOT_DTPREL16_LO_DS:
return {R_PPC64_ADDR16_LO_DS, tocBiasedVal};
case R_PPC64_DTPREL16:
return {R_PPC64_ADDR16, dtpBiasedVal};
case R_PPC64_DTPREL16_DS:
return {R_PPC64_ADDR16_DS, dtpBiasedVal};
case R_PPC64_DTPREL16_HA:
return {R_PPC64_ADDR16_HA, dtpBiasedVal};
case R_PPC64_DTPREL16_HI:
return {R_PPC64_ADDR16_HI, dtpBiasedVal};
case R_PPC64_DTPREL16_HIGHER:
return {R_PPC64_ADDR16_HIGHER, dtpBiasedVal};
case R_PPC64_DTPREL16_HIGHERA:
return {R_PPC64_ADDR16_HIGHERA, dtpBiasedVal};
case R_PPC64_DTPREL16_HIGHEST:
return {R_PPC64_ADDR16_HIGHEST, dtpBiasedVal};
case R_PPC64_DTPREL16_HIGHESTA:
return {R_PPC64_ADDR16_HIGHESTA, dtpBiasedVal};
case R_PPC64_DTPREL16_LO:
return {R_PPC64_ADDR16_LO, dtpBiasedVal};
case R_PPC64_DTPREL16_LO_DS:
return {R_PPC64_ADDR16_LO_DS, dtpBiasedVal};
case R_PPC64_DTPREL64:
return {R_PPC64_ADDR64, dtpBiasedVal};
default:
return {type, val};
}
}
static bool isTocOptType(RelType type) {
switch (type) {
case R_PPC64_GOT16_HA:
case R_PPC64_GOT16_LO_DS:
case R_PPC64_TOC16_HA:
case R_PPC64_TOC16_LO_DS:
case R_PPC64_TOC16_LO:
return true;
default:
return false;
}
}
template <class RelTy>
static void checkPPC64TLSRelax(InputSectionBase &sec, Relocs<RelTy> rels) {
if (!sec.file || sec.file->ppc64DisableTLSRelax)
return;
bool hasGDLD = false;
for (const RelTy &rel : rels) {
RelType type = rel.getType(false);
switch (type) {
case R_PPC64_TLSGD:
case R_PPC64_TLSLD:
return;
case R_PPC64_GOT_TLSGD16:
case R_PPC64_GOT_TLSGD16_HA:
case R_PPC64_GOT_TLSGD16_HI:
case R_PPC64_GOT_TLSGD16_LO:
case R_PPC64_GOT_TLSLD16:
case R_PPC64_GOT_TLSLD16_HA:
case R_PPC64_GOT_TLSLD16_HI:
case R_PPC64_GOT_TLSLD16_LO:
hasGDLD = true;
break;
}
}
if (hasGDLD) {
sec.file->ppc64DisableTLSRelax = true;
Warn(sec.file->ctx)
<< sec.file
<< ": disable TLS relaxation due to R_PPC64_GOT_TLS* relocations "
"without "
"R_PPC64_TLSGD/R_PPC64_TLSLD relocations";
}
}
template <class ELFT, class RelTy>
void PPC64::scanSectionImpl(InputSectionBase &sec, Relocs<RelTy> rels) {
RelocScan rs(ctx, &sec);
sec.relocations.reserve(rels.size());
checkPPC64TLSRelax<RelTy>(sec, rels);
for (auto it = rels.begin(); it != rels.end(); ++it) {
const RelTy &rel = *it;
uint64_t offset = rel.r_offset;
uint32_t symIdx = rel.getSymbol(false);
Symbol &sym = sec.getFile<ELFT>()->getSymbol(symIdx);
RelType type = rel.getType(false);
RelExpr expr =
ctx.target->getRelExpr(type, sym, sec.content().data() + offset);
if (expr == R_NONE)
continue;
if (sym.isUndefined() && symIdx != 0 &&
rs.maybeReportUndefined(cast<Undefined>(sym), offset))
continue;
auto addend = getAddend<ELFT>(rel);
if (ctx.arg.isPic && type == R_PPC64_TOC)
addend += getPPC64TocBase(ctx);
if (type == R_PPC64_TOC16 || type == R_PPC64_TOC16_DS)
sec.file->ppc64SmallCodeModelTocRelocs = true;
if (type == R_PPC64_TOC16_LO && sym.isSection() && isa<Defined>(sym) &&
cast<Defined>(sym).section->name == ".toc")
ctx.ppc64noTocRelax.insert({&sym, addend});
if ((type == R_PPC64_TLSGD && expr == R_TLSDESC_CALL) ||
(type == R_PPC64_TLSLD && expr == R_TLSLD_HINT)) {
auto it1 = it;
++it1;
if (it1 == rels.end()) {
auto diag = Err(ctx);
diag << "R_PPC64_TLSGD/R_PPC64_TLSLD may not be the last "
"relocation";
printLocation(diag, sec, sym, offset);
continue;
}
if (it1->getType(false) == R_PPC64_REL24_NOTOC)
++offset;
}
if (oneof<R_GOTREL, RE_PPC64_TOCBASE, RE_PPC64_RELAX_TOC>(expr))
ctx.in.got->hasGotOffRel.store(true, std::memory_order_relaxed);
if (sym.isTls()) {
if (unsigned processed =
rs.handleTlsRelocation(expr, type, offset, sym, addend)) {
it += processed - 1;
continue;
}
}
rs.process(expr, type, offset, sym, addend);
}
}
template <class ELFT> void PPC64::scanSection1(InputSectionBase &sec) {
auto relocs = sec.template relsOrRelas<ELFT>();
if (relocs.areRelocsCrel())
scanSectionImpl<ELFT>(sec, relocs.crels);
else
scanSectionImpl<ELFT>(sec, relocs.relas);
}
void PPC64::scanSection(InputSectionBase &sec) {
if (ctx.arg.isLE)
scanSection1<ELF64LE>(sec);
else
scanSection1<ELF64BE>(sec);
}
void PPC64::relocate(uint8_t *loc, const Relocation &rel, uint64_t val) const {
RelType type = rel.type;
bool shouldTocOptimize = isTocOptType(type);
std::tie(type, val) = toAddr16Rel(type, val);
switch (type) {
case R_PPC64_ADDR14: {
checkAlignment(ctx, loc, val, 4, rel);
uint8_t aalk = loc[3];
write16(ctx, loc + 2, (aalk & 3) | (val & 0xfffc));
break;
}
case R_PPC64_ADDR16:
checkIntUInt(ctx, loc, val, 16, rel);
write16(ctx, loc, val);
break;
case R_PPC64_ADDR32:
checkIntUInt(ctx, loc, val, 32, rel);
write32(ctx, loc, val);
break;
case R_PPC64_ADDR16_DS:
case R_PPC64_TPREL16_DS: {
checkInt(ctx, loc, val, 16, rel);
uint16_t mask = isDQFormInstruction(readFromHalf16(ctx, loc)) ? 0xf : 0x3;
checkAlignment(ctx, loc, lo(val), mask + 1, rel);
write16(ctx, loc, (read16(ctx, loc) & mask) | lo(val));
} break;
case R_PPC64_ADDR16_HA:
case R_PPC64_REL16_HA:
case R_PPC64_TPREL16_HA:
if (ctx.arg.tocOptimize && shouldTocOptimize && ha(val) == 0)
writeFromHalf16(ctx, loc, NOP);
else {
checkInt(ctx, loc, val + 0x8000, 32, rel);
write16(ctx, loc, ha(val));
}
break;
case R_PPC64_ADDR16_HI:
case R_PPC64_REL16_HI:
case R_PPC64_TPREL16_HI:
checkInt(ctx, loc, val, 32, rel);
write16(ctx, loc, hi(val));
break;
case R_PPC64_ADDR16_HIGH:
write16(ctx, loc, hi(val));
break;
case R_PPC64_ADDR16_HIGHER:
case R_PPC64_TPREL16_HIGHER:
write16(ctx, loc, higher(val));
break;
case R_PPC64_ADDR16_HIGHERA:
case R_PPC64_TPREL16_HIGHERA:
write16(ctx, loc, highera(val));
break;
case R_PPC64_ADDR16_HIGHEST:
case R_PPC64_TPREL16_HIGHEST:
write16(ctx, loc, highest(val));
break;
case R_PPC64_ADDR16_HIGHESTA:
case R_PPC64_TPREL16_HIGHESTA:
write16(ctx, loc, highesta(val));
break;
case R_PPC64_ADDR16_LO:
case R_PPC64_REL16_LO:
case R_PPC64_TPREL16_LO:
if (ctx.arg.tocOptimize && shouldTocOptimize && ha(val) == 0) {
uint32_t insn = readFromHalf16(ctx, loc);
if (isInstructionUpdateForm(insn))
Err(ctx) << getErrorLoc(ctx, loc)
<< "can't toc-optimize an update instruction: 0x"
<< utohexstr(insn, true);
writeFromHalf16(ctx, loc, (insn & 0xffe00000) | 0x00020000 | lo(val));
} else {
write16(ctx, loc, lo(val));
}
break;
case R_PPC64_ADDR16_LO_DS:
case R_PPC64_TPREL16_LO_DS: {
uint32_t insn = readFromHalf16(ctx, loc);
uint16_t mask = isDQFormInstruction(insn) ? 0xf : 0x3;
checkAlignment(ctx, loc, lo(val), mask + 1, rel);
if (ctx.arg.tocOptimize && shouldTocOptimize && ha(val) == 0) {
if (isInstructionUpdateForm(insn))
Err(ctx) << getErrorLoc(ctx, loc)
<< "can't toc-optimize an update instruction: 0x"
<< utohexstr(insn, true);
insn &= 0xffe00000 | mask;
writeFromHalf16(ctx, loc, insn | 0x00020000 | lo(val));
} else {
write16(ctx, loc, (read16(ctx, loc) & mask) | lo(val));
}
} break;
case R_PPC64_TPREL16:
checkInt(ctx, loc, val, 16, rel);
write16(ctx, loc, val);
break;
case R_PPC64_REL32:
checkInt(ctx, loc, val, 32, rel);
write32(ctx, loc, val);
break;
case R_PPC64_ADDR64:
case R_PPC64_REL64:
case R_PPC64_TOC:
write64(ctx, loc, val);
break;
case R_PPC64_REL14: {
uint32_t mask = 0x0000FFFC;
checkInt(ctx, loc, val, 16, rel);
checkAlignment(ctx, loc, val, 4, rel);
write32(ctx, loc, (read32(ctx, loc) & ~mask) | (val & mask));
break;
}
case R_PPC64_REL24:
case R_PPC64_REL24_NOTOC: {
uint32_t mask = 0x03FFFFFC;
checkInt(ctx, loc, val, 26, rel);
checkAlignment(ctx, loc, val, 4, rel);
write32(ctx, loc, (read32(ctx, loc) & ~mask) | (val & mask));
break;
}
case R_PPC64_DTPREL64:
write64(ctx, loc, val - dynamicThreadPointerOffset);
break;
case R_PPC64_DTPREL34:
val -= dynamicThreadPointerOffset;
[[fallthrough]];
case R_PPC64_PCREL34:
case R_PPC64_GOT_PCREL34:
case R_PPC64_GOT_TLSGD_PCREL34:
case R_PPC64_GOT_TLSLD_PCREL34:
case R_PPC64_GOT_TPREL_PCREL34:
case R_PPC64_TPREL34: {
const uint64_t si0Mask = 0x00000003ffff0000;
const uint64_t si1Mask = 0x000000000000ffff;
const uint64_t fullMask = 0x0003ffff0000ffff;
checkInt(ctx, loc, val, 34, rel);
uint64_t instr = readPrefixedInst(ctx, loc) & ~fullMask;
writePrefixedInst(ctx, loc,
instr | ((val & si0Mask) << 16) | (val & si1Mask));
break;
}
case R_PPC64_PCREL_OPT:
break;
default:
llvm_unreachable("unknown relocation");
}
}
bool PPC64::needsThunk(RelExpr expr, RelType type, const InputFile *file,
uint64_t branchAddr, const Symbol &s, int64_t a) const {
if (type != R_PPC64_REL14 && type != R_PPC64_REL24 &&
type != R_PPC64_REL24_NOTOC)
return false;
if (s.isInPlt(ctx))
return true;
if (type != R_PPC64_REL24_NOTOC && (s.stOther >> 5) == 1)
return true;
if (type == R_PPC64_REL24_NOTOC && (s.stOther >> 5) > 1)
return true;
if (s.isUndefined())
return false;
return !inBranchRange(
type, branchAddr,
s.getVA(ctx, a) + getPPC64GlobalEntryToLocalEntryOffset(ctx, s.stOther));
}
uint32_t PPC64::getThunkSectionSpacing() const {
return 0x2000000;
}
bool PPC64::inBranchRange(RelType type, uint64_t src, uint64_t dst) const {
int64_t offset = dst - src;
if (type == R_PPC64_REL14)
return isInt<16>(offset);
if (type == R_PPC64_REL24 || type == R_PPC64_REL24_NOTOC)
return isInt<26>(offset);
llvm_unreachable("unsupported relocation type used in branch");
}
RelExpr PPC64::adjustTlsExpr(RelType type, RelExpr expr) const {
if (type != R_PPC64_GOT_TLSGD_PCREL34 && expr == R_RELAX_TLS_GD_TO_IE)
return R_RELAX_TLS_GD_TO_IE_GOT_OFF;
if (expr == R_RELAX_TLS_LD_TO_LE)
return R_RELAX_TLS_LD_TO_LE_ABS;
return expr;
}
RelExpr PPC64::adjustGotPcExpr(RelType type, int64_t addend,
const uint8_t *loc) const {
if ((type == R_PPC64_GOT_PCREL34 || type == R_PPC64_PCREL_OPT) &&
ctx.arg.pcRelOptimize) {
if ((readPrefixedInst(ctx, loc) & 0xfc000000) == 0xe4000000)
return RE_PPC64_RELAX_GOT_PC;
}
return R_GOT_PC;
}
void PPC64::relaxTlsGdToIe(uint8_t *loc, const Relocation &rel,
uint64_t val) const {
switch (rel.type) {
case R_PPC64_GOT_TLSGD16_HA:
relocateNoSym(loc, R_PPC64_GOT_TPREL16_HA, val);
return;
case R_PPC64_GOT_TLSGD16:
case R_PPC64_GOT_TLSGD16_LO: {
uint32_t ra = (readFromHalf16(ctx, loc) & (0x1f << 16));
writeFromHalf16(ctx, loc, 0xe8600000 | ra);
relocateNoSym(loc, R_PPC64_GOT_TPREL16_LO_DS, val);
return;
}
case R_PPC64_GOT_TLSGD_PCREL34: {
writePrefixedInst(ctx, loc, 0x04100000e4600000);
relocateNoSym(loc, R_PPC64_GOT_TPREL_PCREL34, val);
return;
}
case R_PPC64_TLSGD: {
const uintptr_t locAsInt = reinterpret_cast<uintptr_t>(loc);
if (locAsInt % 4 == 0) {
write32(ctx, loc, NOP);
write32(ctx, loc + 4, 0x7c636a14);
} else if (locAsInt % 4 == 1) {
write32(ctx, loc - 1, 0x7c636a14);
} else {
Err(ctx) << "R_PPC64_TLSGD has unexpected byte alignment";
}
return;
}
default:
llvm_unreachable("unsupported relocation for TLS GD to IE relaxation");
}
}
void PPC64::relocateAlloc(InputSection &sec, uint8_t *buf) const {
uint64_t secAddr = sec.getOutputSection()->addr + sec.outSecOff;
uint64_t lastPPCRelaxedRelocOff = -1;
for (const Relocation &rel : sec.relocs()) {
uint8_t *loc = buf + rel.offset;
const uint64_t val = sec.getRelocTargetVA(ctx, rel, secAddr + rel.offset);
switch (rel.expr) {
case RE_PPC64_RELAX_GOT_PC: {
if (rel.type == R_PPC64_GOT_PCREL34)
lastPPCRelaxedRelocOff = rel.offset;
if (rel.type == R_PPC64_PCREL_OPT && rel.offset != lastPPCRelaxedRelocOff)
break;
relaxGot(loc, rel, val);
break;
}
case RE_PPC64_RELAX_TOC:
if (ctx.ppc64noTocRelax.count({rel.sym, rel.addend}) ||
!tryRelaxPPC64TocIndirection(ctx, rel, loc))
relocate(loc, rel, val);
break;
case RE_PPC64_CALL:
if (read32(ctx, loc) == 0x60000000)
break;
if (rel.sym->needsTocRestore()) {
if ((rel.offset + 8 > sec.content().size() ||
read32(ctx, loc + 4) != 0x60000000) &&
rel.sym->file != sec.file) {
Err(ctx) << getErrorLoc(ctx, loc) << "call to "
<< toStr(ctx, *rel.sym).substr(6)
<< " lacks nop, can't restore toc";
break;
}
write32(ctx, loc + 4, 0xe8410018);
}
relocate(loc, rel, val);
break;
case R_RELAX_TLS_GD_TO_IE:
case R_RELAX_TLS_GD_TO_IE_GOT_OFF:
relaxTlsGdToIe(loc, rel, val);
break;
case R_RELAX_TLS_GD_TO_LE:
relaxTlsGdToLe(loc, rel, val);
break;
case R_RELAX_TLS_LD_TO_LE_ABS:
relaxTlsLdToLe(loc, rel, val);
break;
case R_RELAX_TLS_IE_TO_LE:
relaxTlsIeToLe(loc, rel, val);
break;
default:
relocate(loc, rel, val);
break;
}
}
}
bool PPC64::adjustPrologueForCrossSplitStack(uint8_t *loc, uint8_t *end,
uint8_t stOther) const {
loc += getPPC64GlobalEntryToLocalEntryOffset(ctx, stOther);
if (loc + 12 >= end)
return false;
if (read32(ctx, loc) != 0xe80d8fc0)
return false;
int16_t hiImm = 0;
int16_t loImm = 0;
int32_t firstInstr = read32(ctx, loc + 4);
if (getPrimaryOpCode(firstInstr) == 15) {
hiImm = firstInstr & 0xFFFF;
} else if (getPrimaryOpCode(firstInstr) == 14) {
loImm = firstInstr & 0xFFFF;
} else {
return false;
}
uint32_t secondInstr = read32(ctx, loc + 8);
if (!loImm && getPrimaryOpCode(secondInstr) == 14) {
loImm = secondInstr & 0xFFFF;
} else if (secondInstr != NOP) {
return false;
}
auto checkRegOperands = [](uint32_t instr, uint8_t expectedRT,
uint8_t expectedRA) {
return ((instr & 0x3E00000) >> 21 == expectedRT) &&
((instr & 0x1F0000) >> 16 == expectedRA);
};
if (!checkRegOperands(firstInstr, 12, 1))
return false;
if (secondInstr != NOP && !checkRegOperands(secondInstr, 12, 12))
return false;
int32_t stackFrameSize = (hiImm * 65536) + loImm;
if (stackFrameSize < ctx.arg.splitStackAdjustSize + INT32_MIN) {
Err(ctx) << getErrorLoc(ctx, loc)
<< "split-stack prologue adjustment overflows";
return false;
}
int32_t adjustedStackFrameSize =
stackFrameSize - ctx.arg.splitStackAdjustSize;
loImm = adjustedStackFrameSize & 0xFFFF;
hiImm = (adjustedStackFrameSize + 0x8000) >> 16;
if (hiImm) {
write32(ctx, loc + 4, 0x3d810000 | (uint16_t)hiImm);
secondInstr = loImm ? 0x398C0000 | (uint16_t)loImm : NOP;
write32(ctx, loc + 8, secondInstr);
} else {
write32(ctx, loc + 4, (0x39810000) | (uint16_t)loImm);
write32(ctx, loc + 8, NOP);
}
return true;
}
void elf::setPPC64TargetInfo(Ctx &ctx) { ctx.target.reset(new PPC64(ctx)); }