#include "InputFiles.h"
#include "OutputSections.h"
#include "SymbolTable.h"
#include "Symbols.h"
#include "SyntheticSections.h"
#include "Target.h"
#include "Thunks.h"
#include "lld/Common/CommonLinkerContext.h"
#include "llvm/Support/Endian.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();
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;
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(InputSectionBase &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() {
uint64_t tocVA = in.got->getVA();
return tocVA + ppc64TocOffset;
}
unsigned elf::getPPC64GlobalEntryToLocalEntryOffset(uint8_t stOther) {
uint8_t gepToLep = (stOther >> 5) & 7;
if (gepToLep < 2)
return 0;
if (gepToLep < 7)
return 1 << gepToLep;
error("reserved value of 7 in the 3 most-significant-bits of st_other");
return 0;
}
void elf::writePrefixedInstruction(uint8_t *loc, uint64_t insn) {
insn = config->isLE ? insn << 32 | insn >> 32 : insn;
write64(loc, insn);
}
static bool addOptional(StringRef name, uint64_t value,
std::vector<Defined *> &defined) {
Symbol *sym = symtab.find(name);
if (!sym || sym->isDefined())
return false;
sym->resolve(Defined{ctx.internalFile, StringRef(), STB_GLOBAL, STV_HIDDEN,
STT_FUNC, value,
0, nullptr});
defined.push_back(cast<Defined>(sym));
return true;
}
static void writeSequence(MutableArrayRef<uint32_t> buf, const char *prefix,
int from, uint32_t firstInsn,
ArrayRef<uint32_t> tail) {
std::vector<Defined *> defined;
char name[16];
int first;
uint32_t *ptr = buf.data();
for (int r = from; r < 32; ++r) {
format("%s%d", prefix, r).snprint(name, sizeof(name));
if (addOptional(name, 4 * (r - from), defined) && defined.size() == 1)
first = r - from;
write32(ptr++, firstInsn + 0x200008 * (r - from));
}
for (uint32_t insn : tail)
write32(ptr++, insn);
assert(ptr == &*buf.end());
if (defined.empty())
return;
auto *sec = make<InputSection>(
ctx.internalFile, SHF_ALLOC, SHT_PROGBITS, 4,
ArrayRef(reinterpret_cast<uint8_t *>(buf.data() + first),
4 * (buf.size() - first)),
".text");
ctx.inputSections.push_back(sec);
for (Defined *sym : defined) {
sym->section = sec;
sym->value -= 4 * first;
}
}
void elf::addPPC64SaveRestore() {
static uint32_t savegpr0[20], restgpr0[21], savegpr1[19], restgpr1[19];
constexpr uint32_t blr = 0x4e800020, mtlr_0 = 0x7c0803a6;
writeSequence(restgpr0, "_restgpr0_", 14, 0xe9c1ff70,
{0xe8010010, mtlr_0, blr});
writeSequence(restgpr1, "_restgpr1_", 14, 0xe9ccff70, {blr});
writeSequence(savegpr0, "_savegpr0_", 14, 0xf9c1ff70, {0xf8010010, blr});
writeSequence(savegpr1, "_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(const Relocation &rel,
uint8_t *bufLoc) {
assert(config->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) =
config->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(addend) - getPPC64TocBase();
if (!isInt<32>(tocRelative))
return false;
static_cast<const PPC64 &>(*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(uint8_t *loc, uint32_t insn) {
write32(config->isLE ? loc : loc - 2, insn);
}
static uint32_t readFromHalf16(const uint8_t *loc) {
return read32(config->isLE ? loc : loc - 2);
}
static uint64_t readPrefixedInstruction(const uint8_t *loc) {
uint64_t fullInstr = read64(loc);
return config->isLE ? (fullInstr << 32 | fullInstr >> 32) : fullInstr;
}
PPC64::PPC64() {
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(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)
error(toString(f) + ": ABI version 1 is not supported");
else if (flag > 2)
error(toString(f) + ": unrecognized e_flags: " + Twine(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(loc);
if (getPrimaryOpCode(insn) != LD)
error("expected a 'ld' for got-indirect to toc-relative relaxing");
writeFromHalf16(loc, (insn & 0x03ffffff) | 0x38000000);
relocateNoSym(loc, R_PPC64_TOC16_LO, val);
break;
}
case R_PPC64_GOT_PCREL34: {
uint64_t insn = readPrefixedInstruction(loc);
if ((insn & 0xfc000000) != 0xe4000000)
error("expected a 'pld' for got-indirect to pc-relative relaxing");
insn &= ~0xff000000fc000000;
insn |= 0x600000038000000;
writePrefixedInstruction(loc, insn);
relocate(loc, rel, val);
break;
}
case R_PPC64_PCREL_OPT: {
uint64_t insn = readPrefixedInstruction(loc);
uint32_t accessInsn = read32(loc + rel.addend);
uint64_t pcRelInsn = getPCRelativeForm(accessInsn);
if (pcRelInsn == UINT64_C(-1)) {
errorOrWarn(
"unrecognized instruction for R_PPC64_PCREL_OPT relaxation: 0x" +
Twine::utohexstr(accessInsn));
break;
}
int64_t totalDisp = getTotalDisp(insn, accessInsn);
if (!isInt<34>(totalDisp))
break;
writePrefixedInstruction(loc, pcRelInsn |
((totalDisp & 0x3ffff0000) << 16) |
(totalDisp & 0xffff));
write32(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(loc, NOP);
break;
case R_PPC64_GOT_TLSGD16:
case R_PPC64_GOT_TLSGD16_LO:
writeFromHalf16(loc, 0x3c6d0000);
relocateNoSym(loc, R_PPC64_TPREL16_HA, val);
break;
case R_PPC64_GOT_TLSGD_PCREL34:
writePrefixedInstruction(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(loc, NOP);
write32(loc + 4, 0x38630000);
relocateNoSym(loc + 4 + (config->ekind == ELF64BEKind ? 2 : 0),
R_PPC64_TPREL16_LO, val);
} else if (locAsInt % 4 == 1) {
write32(loc - 1, NOP);
} else {
errorOrWarn("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(loc, NOP);
break;
case R_PPC64_GOT_TLSLD16_LO:
writeFromHalf16(loc, 0x3c6d0000);
break;
case R_PPC64_GOT_TLSLD_PCREL34:
writePrefixedInstruction(loc, 0x06000000386d1000);
break;
case R_PPC64_TLSLD: {
const uintptr_t locAsInt = reinterpret_cast<uintptr_t>(loc);
if (locAsInt % 4 == 0) {
write32(loc, NOP);
write32(loc + 4, 0x38631000);
} else if (locAsInt % 4 == 1) {
write32(loc - 1, NOP);
} else {
errorOrWarn("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 = (config->ekind == ELF64BEKind) ? 2 : 0;
switch (rel.type) {
case R_PPC64_GOT_TPREL16_HA:
write32(loc - offset, NOP);
break;
case R_PPC64_GOT_TPREL16_LO_DS:
case R_PPC64_GOT_TPREL16_DS: {
uint32_t regNo = read32(loc - offset) & 0x03E00000;
write32(loc - offset, 0x3C0D0000 | regNo);
relocateNoSym(loc, R_PPC64_TPREL16_HA, val);
break;
}
case R_PPC64_GOT_TPREL_PCREL34: {
const uint64_t pldRT = readPrefixedInstruction(loc) & 0x0000000003e00000;
writePrefixedInstruction(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(loc));
if (primaryOp != 31)
error("unrecognized instruction for IE to LE R_PPC64_TLS");
uint32_t secondaryOp = (read32(loc) & 0x000007FE) >> 1;
uint32_t dFormOp = getPPCDFormOp(secondaryOp);
uint32_t finalReloc;
if (dFormOp == 0) {
dFormOp = getPPCDSFormOp(secondaryOp);
if (dFormOp == 0)
error("unrecognized instruction for IE to LE R_PPC64_TLS");
finalReloc = R_PPC64_TPREL16_LO_DS;
} else
finalReloc = R_PPC64_TPREL16_LO;
write32(loc, dFormOp | (read32(loc) & 0x03ff0000));
relocateNoSym(loc + offset, finalReloc, val);
} else if (locAsInt % 4 == 1) {
uint32_t tlsInstr = read32(loc - 1);
uint32_t primaryOp = getPrimaryOpCode(tlsInstr);
if (primaryOp != 31)
errorOrWarn("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(loc - 1, NOP);
} else {
write32(loc - 1, 0x7C000378 | (rt << 16) | (ra << 21) | (ra << 11));
}
} else {
uint32_t dFormOp = getPPCDFormOp(secondaryOp);
if (dFormOp == 0) {
dFormOp = getPPCDSFormOp(secondaryOp);
if (dFormOp == 0)
errorOrWarn("unrecognized instruction for IE to LE R_PPC64_TLS");
}
write32(loc - 1, (dFormOp | (tlsInstr & 0x03ff0000)));
}
} else {
errorOrWarn("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 config->tocOptimize ? R_PPC64_RELAX_TOC : R_GOTREL;
case R_PPC64_TOC:
return R_PPC64_TOCBASE;
case R_PPC64_REL14:
case R_PPC64_REL24:
return R_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:
error(getErrorLocation(loc) + "unknown relocation (" + Twine(type) +
") against symbol " + toString(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(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(buf);
default:
internalLinkerError(getErrorLocation(buf),
"cannot read addend for relocation " + toString(type));
return 0;
}
}
void PPC64::writeGotHeader(uint8_t *buf) const {
write64(buf, getPPC64TocBase());
}
void PPC64::writePltHeader(uint8_t *buf) const {
write32(buf + 0, 0x7c0802a6);
write32(buf + 4, 0x429f0005);
write32(buf + 8, 0x7d6802a6);
write32(buf + 12, 0x7c0803a6);
write32(buf + 16, 0x7d8b6050);
write32(buf + 20, 0x380cffcc);
write32(buf + 24, 0x7800f082);
write32(buf + 28, 0xe98b002c);
write32(buf + 32, 0x7d6c5a14);
write32(buf + 36, 0xe98b0000);
write32(buf + 40, 0xe96b0008);
write32(buf + 44, 0x7d8903a6);
write32(buf + 48, 0x4e800420);
int64_t gotPltOffset = in.gotPlt->getVA() - (in.plt->getVA() + 8);
write64(buf + 52, gotPltOffset);
}
void PPC64::writePlt(uint8_t *buf, const Symbol &sym,
uint64_t ) const {
int32_t offset = pltHeaderSize + sym.getPltIdx() * pltEntrySize;
write32(buf, 0x48000000 | ((-offset) & 0x03FFFFFc));
}
void PPC64::writeIplt(uint8_t *buf, const Symbol &sym,
uint64_t ) const {
writePPC64LoadAndBranch(buf, sym.getGotPltVA() - getPPC64TocBase());
}
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;
}
}
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(loc, val, 4, rel);
uint8_t aalk = loc[3];
write16(loc + 2, (aalk & 3) | (val & 0xfffc));
break;
}
case R_PPC64_ADDR16:
checkIntUInt(loc, val, 16, rel);
write16(loc, val);
break;
case R_PPC64_ADDR32:
checkIntUInt(loc, val, 32, rel);
write32(loc, val);
break;
case R_PPC64_ADDR16_DS:
case R_PPC64_TPREL16_DS: {
checkInt(loc, val, 16, rel);
uint16_t mask = isDQFormInstruction(readFromHalf16(loc)) ? 0xf : 0x3;
checkAlignment(loc, lo(val), mask + 1, rel);
write16(loc, (read16(loc) & mask) | lo(val));
} break;
case R_PPC64_ADDR16_HA:
case R_PPC64_REL16_HA:
case R_PPC64_TPREL16_HA:
if (config->tocOptimize && shouldTocOptimize && ha(val) == 0)
writeFromHalf16(loc, NOP);
else {
checkInt(loc, val + 0x8000, 32, rel);
write16(loc, ha(val));
}
break;
case R_PPC64_ADDR16_HI:
case R_PPC64_REL16_HI:
case R_PPC64_TPREL16_HI:
checkInt(loc, val, 32, rel);
write16(loc, hi(val));
break;
case R_PPC64_ADDR16_HIGH:
write16(loc, hi(val));
break;
case R_PPC64_ADDR16_HIGHER:
case R_PPC64_TPREL16_HIGHER:
write16(loc, higher(val));
break;
case R_PPC64_ADDR16_HIGHERA:
case R_PPC64_TPREL16_HIGHERA:
write16(loc, highera(val));
break;
case R_PPC64_ADDR16_HIGHEST:
case R_PPC64_TPREL16_HIGHEST:
write16(loc, highest(val));
break;
case R_PPC64_ADDR16_HIGHESTA:
case R_PPC64_TPREL16_HIGHESTA:
write16(loc, highesta(val));
break;
case R_PPC64_ADDR16_LO:
case R_PPC64_REL16_LO:
case R_PPC64_TPREL16_LO:
if (config->tocOptimize && shouldTocOptimize && ha(val) == 0) {
uint32_t insn = readFromHalf16(loc);
if (isInstructionUpdateForm(insn))
error(getErrorLocation(loc) +
"can't toc-optimize an update instruction: 0x" +
utohexstr(insn));
writeFromHalf16(loc, (insn & 0xffe00000) | 0x00020000 | lo(val));
} else {
write16(loc, lo(val));
}
break;
case R_PPC64_ADDR16_LO_DS:
case R_PPC64_TPREL16_LO_DS: {
uint32_t insn = readFromHalf16(loc);
uint16_t mask = isDQFormInstruction(insn) ? 0xf : 0x3;
checkAlignment(loc, lo(val), mask + 1, rel);
if (config->tocOptimize && shouldTocOptimize && ha(val) == 0) {
if (isInstructionUpdateForm(insn))
error(getErrorLocation(loc) +
"Can't toc-optimize an update instruction: 0x" +
Twine::utohexstr(insn));
insn &= 0xffe00000 | mask;
writeFromHalf16(loc, insn | 0x00020000 | lo(val));
} else {
write16(loc, (read16(loc) & mask) | lo(val));
}
} break;
case R_PPC64_TPREL16:
checkInt(loc, val, 16, rel);
write16(loc, val);
break;
case R_PPC64_REL32:
checkInt(loc, val, 32, rel);
write32(loc, val);
break;
case R_PPC64_ADDR64:
case R_PPC64_REL64:
case R_PPC64_TOC:
write64(loc, val);
break;
case R_PPC64_REL14: {
uint32_t mask = 0x0000FFFC;
checkInt(loc, val, 16, rel);
checkAlignment(loc, val, 4, rel);
write32(loc, (read32(loc) & ~mask) | (val & mask));
break;
}
case R_PPC64_REL24:
case R_PPC64_REL24_NOTOC: {
uint32_t mask = 0x03FFFFFC;
checkInt(loc, val, 26, rel);
checkAlignment(loc, val, 4, rel);
write32(loc, (read32(loc) & ~mask) | (val & mask));
break;
}
case R_PPC64_DTPREL64:
write64(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(loc, val, 34, rel);
uint64_t instr = readPrefixedInstruction(loc) & ~fullMask;
writePrefixedInstruction(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())
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(a) +
getPPC64GlobalEntryToLocalEntryOffset(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) &&
config->pcRelOptimize) {
if ((readPrefixedInstruction(loc) & 0xfc000000) == 0xe4000000)
return R_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(loc) & (0x1f << 16));
writeFromHalf16(loc, 0xe8600000 | ra);
relocateNoSym(loc, R_PPC64_GOT_TPREL16_LO_DS, val);
return;
}
case R_PPC64_GOT_TLSGD_PCREL34: {
writePrefixedInstruction(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(loc, NOP);
write32(loc + 4, 0x7c636A14);
} else if (locAsInt % 4 == 1) {
write32(loc - 1, 0x7c636a14);
} else {
errorOrWarn("R_PPC64_TLSGD has unexpected byte alignment");
}
return;
}
default:
llvm_unreachable("unsupported relocation for TLS GD to IE relaxation");
}
}
void PPC64::relocateAlloc(InputSectionBase &sec, uint8_t *buf) const {
uint64_t secAddr = sec.getOutputSection()->addr;
if (auto *s = dyn_cast<InputSection>(&sec))
secAddr += s->outSecOff;
else if (auto *ehIn = dyn_cast<EhInputSection>(&sec))
secAddr += ehIn->getParent()->outSecOff;
uint64_t lastPPCRelaxedRelocOff = -1;
for (const Relocation &rel : sec.relocs()) {
uint8_t *loc = buf + rel.offset;
const uint64_t val =
sec.getRelocTargetVA(sec.file, rel.type, rel.addend,
secAddr + rel.offset, *rel.sym, rel.expr);
switch (rel.expr) {
case R_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 R_PPC64_RELAX_TOC:
if (ppc64noTocRelax.count({rel.sym, rel.addend}) ||
!tryRelaxPPC64TocIndirection(rel, loc))
relocate(loc, rel, val);
break;
case R_PPC64_CALL:
if (read32(loc) == 0x60000000)
break;
if (rel.sym->needsTocRestore()) {
if ((rel.offset + 8 > sec.content().size() ||
read32(loc + 4) != 0x60000000) &&
rel.sym->file != sec.file) {
errorOrWarn(getErrorLocation(loc) + "call to " +
lld::toString(*rel.sym).substr(6) +
" lacks nop, can't restore toc");
break;
}
write32(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(stOther);
if (loc + 12 >= end)
return false;
if (read32(loc) != 0xe80d8fc0)
return false;
int16_t hiImm = 0;
int16_t loImm = 0;
int32_t firstInstr = read32(loc + 4);
if (getPrimaryOpCode(firstInstr) == 15) {
hiImm = firstInstr & 0xFFFF;
} else if (getPrimaryOpCode(firstInstr) == 14) {
loImm = firstInstr & 0xFFFF;
} else {
return false;
}
uint32_t secondInstr = read32(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 < config->splitStackAdjustSize + INT32_MIN) {
error(getErrorLocation(loc) + "split-stack prologue adjustment overflows");
return false;
}
int32_t adjustedStackFrameSize =
stackFrameSize - config->splitStackAdjustSize;
loImm = adjustedStackFrameSize & 0xFFFF;
hiImm = (adjustedStackFrameSize + 0x8000) >> 16;
if (hiImm) {
write32(loc + 4, 0x3D810000 | (uint16_t)hiImm);
secondInstr = loImm ? 0x398C0000 | (uint16_t)loImm : NOP;
write32(loc + 8, secondInstr);
} else {
write32(loc + 4, (0x39810000) | (uint16_t)loImm);
write32(loc + 8, NOP);
}
return true;
}
TargetInfo *elf::getPPC64TargetInfo() {
static PPC64 target;
return ⌖
}