clang 20.0.0 (based on r547379) from build 12806354. Bug: http://b/379133546 Test: N/A Change-Id: I2eb8938af55d809de674be63cb30cf27e801862b Upstream-Commit: ad834e67b1105d15ef907f6255d4c96e8e733f57
499 lines
17 KiB
C++
499 lines
17 KiB
C++
//===--- ppc64.h - Generic JITLink ppc64 edge kinds, utilities --*- C++ -*-===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// Generic utilities for graphs representing 64-bit PowerPC objects.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#ifndef LLVM_EXECUTIONENGINE_JITLINK_PPC64_H
|
|
#define LLVM_EXECUTIONENGINE_JITLINK_PPC64_H
|
|
|
|
#include "llvm/ExecutionEngine/JITLink/JITLink.h"
|
|
#include "llvm/ExecutionEngine/JITLink/TableManager.h"
|
|
#include "llvm/Support/Endian.h"
|
|
|
|
namespace llvm::jitlink::ppc64 {
|
|
|
|
/// Represents ppc64 fixups and other ppc64-specific edge kinds.
|
|
enum EdgeKind_ppc64 : Edge::Kind {
|
|
Pointer64 = Edge::FirstRelocation,
|
|
Pointer32,
|
|
Pointer16,
|
|
Pointer16DS,
|
|
Pointer16HA,
|
|
Pointer16HI,
|
|
Pointer16HIGH,
|
|
Pointer16HIGHA,
|
|
Pointer16HIGHER,
|
|
Pointer16HIGHERA,
|
|
Pointer16HIGHEST,
|
|
Pointer16HIGHESTA,
|
|
Pointer16LO,
|
|
Pointer16LODS,
|
|
Pointer14,
|
|
Delta64,
|
|
Delta34,
|
|
Delta32,
|
|
NegDelta32,
|
|
Delta16,
|
|
Delta16HA,
|
|
Delta16HI,
|
|
Delta16LO,
|
|
TOC,
|
|
TOCDelta16,
|
|
TOCDelta16DS,
|
|
TOCDelta16HA,
|
|
TOCDelta16HI,
|
|
TOCDelta16LO,
|
|
TOCDelta16LODS,
|
|
RequestGOTAndTransformToDelta34,
|
|
CallBranchDelta,
|
|
// Need to restore r2 after the bl, suggesting the bl is followed by a nop.
|
|
CallBranchDeltaRestoreTOC,
|
|
// Request calling function with TOC.
|
|
RequestCall,
|
|
// Request calling function without TOC.
|
|
RequestCallNoTOC,
|
|
RequestTLSDescInGOTAndTransformToTOCDelta16HA,
|
|
RequestTLSDescInGOTAndTransformToTOCDelta16LO,
|
|
RequestTLSDescInGOTAndTransformToDelta34,
|
|
};
|
|
|
|
enum PLTCallStubKind {
|
|
// Setup function entry(r12) and long branch to target using TOC.
|
|
LongBranch,
|
|
// Save TOC pointer, setup function entry and long branch to target using TOC.
|
|
LongBranchSaveR2,
|
|
// Setup function entry(r12) and long branch to target without using TOC.
|
|
LongBranchNoTOC,
|
|
};
|
|
|
|
extern const char NullPointerContent[8];
|
|
extern const char PointerJumpStubContent_big[20];
|
|
extern const char PointerJumpStubContent_little[20];
|
|
extern const char PointerJumpStubNoTOCContent_big[32];
|
|
extern const char PointerJumpStubNoTOCContent_little[32];
|
|
|
|
struct PLTCallStubReloc {
|
|
Edge::Kind K;
|
|
size_t Offset;
|
|
Edge::AddendT A;
|
|
};
|
|
|
|
struct PLTCallStubInfo {
|
|
ArrayRef<char> Content;
|
|
SmallVector<PLTCallStubReloc, 2> Relocs;
|
|
};
|
|
|
|
template <llvm::endianness Endianness>
|
|
inline PLTCallStubInfo pickStub(PLTCallStubKind StubKind) {
|
|
constexpr bool isLE = Endianness == llvm::endianness::little;
|
|
switch (StubKind) {
|
|
case LongBranch: {
|
|
ArrayRef<char> Content =
|
|
isLE ? PointerJumpStubContent_little : PointerJumpStubContent_big;
|
|
// Skip save r2.
|
|
Content = Content.slice(4);
|
|
size_t Offset = isLE ? 0 : 2;
|
|
return PLTCallStubInfo{
|
|
Content,
|
|
{{TOCDelta16HA, Offset, 0}, {TOCDelta16LO, Offset + 4, 0}},
|
|
};
|
|
}
|
|
case LongBranchSaveR2: {
|
|
ArrayRef<char> Content =
|
|
isLE ? PointerJumpStubContent_little : PointerJumpStubContent_big;
|
|
size_t Offset = isLE ? 4 : 6;
|
|
return PLTCallStubInfo{
|
|
Content,
|
|
{{TOCDelta16HA, Offset, 0}, {TOCDelta16LO, Offset + 4, 0}},
|
|
};
|
|
}
|
|
case LongBranchNoTOC: {
|
|
ArrayRef<char> Content = isLE ? PointerJumpStubNoTOCContent_little
|
|
: PointerJumpStubNoTOCContent_big;
|
|
size_t Offset = isLE ? 16 : 18;
|
|
Edge::AddendT Addend = isLE ? 8 : 10;
|
|
return PLTCallStubInfo{
|
|
Content,
|
|
{{Delta16HA, Offset, Addend}, {Delta16LO, Offset + 4, Addend + 4}},
|
|
};
|
|
}
|
|
}
|
|
llvm_unreachable("Unknown PLTCallStubKind enum");
|
|
}
|
|
|
|
inline Symbol &createAnonymousPointer(LinkGraph &G, Section &PointerSection,
|
|
Symbol *InitialTarget = nullptr,
|
|
uint64_t InitialAddend = 0) {
|
|
assert(G.getPointerSize() == sizeof(NullPointerContent) &&
|
|
"LinkGraph's pointer size should be consistent with size of "
|
|
"NullPointerContent");
|
|
Block &B = G.createContentBlock(PointerSection, NullPointerContent,
|
|
orc::ExecutorAddr(), G.getPointerSize(), 0);
|
|
if (InitialTarget)
|
|
B.addEdge(Pointer64, 0, *InitialTarget, InitialAddend);
|
|
return G.addAnonymousSymbol(B, 0, G.getPointerSize(), false, false);
|
|
}
|
|
|
|
template <llvm::endianness Endianness>
|
|
inline Symbol &createAnonymousPointerJumpStub(LinkGraph &G,
|
|
Section &StubSection,
|
|
Symbol &PointerSymbol,
|
|
PLTCallStubKind StubKind) {
|
|
PLTCallStubInfo StubInfo = pickStub<Endianness>(StubKind);
|
|
Block &B = G.createContentBlock(StubSection, StubInfo.Content,
|
|
orc::ExecutorAddr(), 4, 0);
|
|
for (auto const &Reloc : StubInfo.Relocs)
|
|
B.addEdge(Reloc.K, Reloc.Offset, PointerSymbol, Reloc.A);
|
|
return G.addAnonymousSymbol(B, 0, StubInfo.Content.size(), true, false);
|
|
}
|
|
|
|
template <llvm::endianness Endianness>
|
|
class TOCTableManager : public TableManager<TOCTableManager<Endianness>> {
|
|
public:
|
|
// FIXME: `llvm-jitlink -check` relies this name to be $__GOT.
|
|
static StringRef getSectionName() { return "$__GOT"; }
|
|
|
|
bool visitEdge(LinkGraph &G, Block *B, Edge &E) {
|
|
Edge::Kind K = E.getKind();
|
|
switch (K) {
|
|
case TOCDelta16HA:
|
|
case TOCDelta16LO:
|
|
case TOCDelta16DS:
|
|
case TOCDelta16LODS:
|
|
case CallBranchDeltaRestoreTOC:
|
|
case RequestCall:
|
|
// Create TOC section if TOC relocation, PLT or GOT is used.
|
|
getOrCreateTOCSection(G);
|
|
return false;
|
|
case RequestGOTAndTransformToDelta34:
|
|
E.setKind(ppc64::Delta34);
|
|
E.setTarget(createEntry(G, E.getTarget()));
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Symbol &createEntry(LinkGraph &G, Symbol &Target) {
|
|
return createAnonymousPointer(G, getOrCreateTOCSection(G), &Target);
|
|
}
|
|
|
|
private:
|
|
Section &getOrCreateTOCSection(LinkGraph &G) {
|
|
TOCSection = G.findSectionByName(getSectionName());
|
|
if (!TOCSection)
|
|
TOCSection = &G.createSection(getSectionName(), orc::MemProt::Read);
|
|
return *TOCSection;
|
|
}
|
|
|
|
Section *TOCSection = nullptr;
|
|
};
|
|
|
|
template <llvm::endianness Endianness>
|
|
class PLTTableManager : public TableManager<PLTTableManager<Endianness>> {
|
|
public:
|
|
PLTTableManager(TOCTableManager<Endianness> &TOC) : TOC(TOC) {}
|
|
|
|
static StringRef getSectionName() { return "$__STUBS"; }
|
|
|
|
// FIXME: One external symbol can only have one PLT stub in a object file.
|
|
// This is a limitation when we need different PLT stubs for the same symbol.
|
|
// For example, we need two different PLT stubs for `bl __tls_get_addr` and
|
|
// `bl __tls_get_addr@notoc`.
|
|
bool visitEdge(LinkGraph &G, Block *B, Edge &E) {
|
|
bool isExternal = E.getTarget().isExternal();
|
|
Edge::Kind K = E.getKind();
|
|
if (K == ppc64::RequestCall) {
|
|
if (isExternal) {
|
|
E.setKind(ppc64::CallBranchDeltaRestoreTOC);
|
|
this->StubKind = LongBranchSaveR2;
|
|
// FIXME: We assume the addend to the external target is zero. It's
|
|
// quite unusual that the addend of an external target to be non-zero as
|
|
// if we have known the layout of the external object.
|
|
E.setTarget(this->getEntryForTarget(G, E.getTarget()));
|
|
// Addend to the stub is zero.
|
|
E.setAddend(0);
|
|
} else
|
|
// TODO: There are cases a local function call need a call stub.
|
|
// 1. Caller uses TOC, the callee doesn't, need a r2 save stub.
|
|
// 2. Caller doesn't use TOC, the callee does, need a r12 setup stub.
|
|
// 3. Branching target is out of range.
|
|
E.setKind(ppc64::CallBranchDelta);
|
|
return true;
|
|
}
|
|
if (K == ppc64::RequestCallNoTOC) {
|
|
E.setKind(ppc64::CallBranchDelta);
|
|
this->StubKind = LongBranchNoTOC;
|
|
E.setTarget(this->getEntryForTarget(G, E.getTarget()));
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Symbol &createEntry(LinkGraph &G, Symbol &Target) {
|
|
return createAnonymousPointerJumpStub<Endianness>(
|
|
G, getOrCreateStubsSection(G), TOC.getEntryForTarget(G, Target),
|
|
this->StubKind);
|
|
}
|
|
|
|
private:
|
|
Section &getOrCreateStubsSection(LinkGraph &G) {
|
|
PLTSection = G.findSectionByName(getSectionName());
|
|
if (!PLTSection)
|
|
PLTSection = &G.createSection(getSectionName(),
|
|
orc::MemProt::Read | orc::MemProt::Exec);
|
|
return *PLTSection;
|
|
}
|
|
|
|
TOCTableManager<Endianness> &TOC;
|
|
Section *PLTSection = nullptr;
|
|
PLTCallStubKind StubKind;
|
|
};
|
|
|
|
/// Returns a string name for the given ppc64 edge. For debugging purposes
|
|
/// only.
|
|
const char *getEdgeKindName(Edge::Kind K);
|
|
|
|
inline static uint16_t ha(uint64_t x) { return (x + 0x8000) >> 16; }
|
|
inline static uint64_t lo(uint64_t x) { return x & 0xffff; }
|
|
inline static uint16_t hi(uint64_t x) { return x >> 16; }
|
|
inline static uint64_t high(uint64_t x) { return (x >> 16) & 0xffff; }
|
|
inline static uint64_t higha(uint64_t x) {
|
|
return ((x + 0x8000) >> 16) & 0xffff;
|
|
}
|
|
inline static uint64_t higher(uint64_t x) { return (x >> 32) & 0xffff; }
|
|
inline static uint64_t highera(uint64_t x) {
|
|
return ((x + 0x8000) >> 32) & 0xffff;
|
|
}
|
|
inline static uint16_t highest(uint64_t x) { return x >> 48; }
|
|
inline static uint16_t highesta(uint64_t x) { return (x + 0x8000) >> 48; }
|
|
|
|
// Prefixed instruction introduced in ISAv3.1 consists of two 32-bit words,
|
|
// prefix word and suffix word, i.e., prefixed_instruction = concat(prefix_word,
|
|
// suffix_word). That's to say, for a prefixed instruction encoded in uint64_t,
|
|
// the most significant 32 bits belong to the prefix word. The prefix word is at
|
|
// low address for both big/little endian. Byte order in each word still follows
|
|
// its endian.
|
|
template <llvm::endianness Endianness>
|
|
inline static uint64_t readPrefixedInstruction(const char *Loc) {
|
|
constexpr bool isLE = Endianness == llvm::endianness::little;
|
|
uint64_t Inst = support::endian::read64<Endianness>(Loc);
|
|
return isLE ? (Inst << 32) | (Inst >> 32) : Inst;
|
|
}
|
|
|
|
template <llvm::endianness Endianness>
|
|
inline static void writePrefixedInstruction(char *Loc, uint64_t Inst) {
|
|
constexpr bool isLE = Endianness == llvm::endianness::little;
|
|
Inst = isLE ? (Inst << 32) | (Inst >> 32) : Inst;
|
|
support::endian::write64<Endianness>(Loc, Inst);
|
|
}
|
|
|
|
template <llvm::endianness Endianness>
|
|
inline Error relocateHalf16(char *FixupPtr, int64_t Value, Edge::Kind K) {
|
|
switch (K) {
|
|
case Delta16:
|
|
case Pointer16:
|
|
case TOCDelta16:
|
|
support::endian::write16<Endianness>(FixupPtr, Value);
|
|
break;
|
|
case Pointer16DS:
|
|
case TOCDelta16DS:
|
|
support::endian::write16<Endianness>(FixupPtr, Value & ~3);
|
|
break;
|
|
case Delta16HA:
|
|
case Pointer16HA:
|
|
case TOCDelta16HA:
|
|
support::endian::write16<Endianness>(FixupPtr, ha(Value));
|
|
break;
|
|
case Delta16HI:
|
|
case Pointer16HI:
|
|
case TOCDelta16HI:
|
|
support::endian::write16<Endianness>(FixupPtr, hi(Value));
|
|
break;
|
|
case Pointer16HIGH:
|
|
support::endian::write16<Endianness>(FixupPtr, high(Value));
|
|
break;
|
|
case Pointer16HIGHA:
|
|
support::endian::write16<Endianness>(FixupPtr, higha(Value));
|
|
break;
|
|
case Pointer16HIGHER:
|
|
support::endian::write16<Endianness>(FixupPtr, higher(Value));
|
|
break;
|
|
case Pointer16HIGHERA:
|
|
support::endian::write16<Endianness>(FixupPtr, highera(Value));
|
|
break;
|
|
case Pointer16HIGHEST:
|
|
support::endian::write16<Endianness>(FixupPtr, highest(Value));
|
|
break;
|
|
case Pointer16HIGHESTA:
|
|
support::endian::write16<Endianness>(FixupPtr, highesta(Value));
|
|
break;
|
|
case Delta16LO:
|
|
case Pointer16LO:
|
|
case TOCDelta16LO:
|
|
support::endian::write16<Endianness>(FixupPtr, lo(Value));
|
|
break;
|
|
case Pointer16LODS:
|
|
case TOCDelta16LODS:
|
|
support::endian::write16<Endianness>(FixupPtr, lo(Value) & ~3);
|
|
break;
|
|
default:
|
|
return make_error<JITLinkError>(
|
|
StringRef(getEdgeKindName(K)) +
|
|
" relocation does not write at half16 field");
|
|
}
|
|
return Error::success();
|
|
}
|
|
|
|
/// Apply fixup expression for edge to block content.
|
|
template <llvm::endianness Endianness>
|
|
inline Error applyFixup(LinkGraph &G, Block &B, const Edge &E,
|
|
const Symbol *TOCSymbol) {
|
|
char *BlockWorkingMem = B.getAlreadyMutableContent().data();
|
|
char *FixupPtr = BlockWorkingMem + E.getOffset();
|
|
orc::ExecutorAddr FixupAddress = B.getAddress() + E.getOffset();
|
|
int64_t S = E.getTarget().getAddress().getValue();
|
|
int64_t A = E.getAddend();
|
|
int64_t P = FixupAddress.getValue();
|
|
int64_t TOCBase = TOCSymbol ? TOCSymbol->getAddress().getValue() : 0;
|
|
Edge::Kind K = E.getKind();
|
|
|
|
DEBUG_WITH_TYPE("jitlink", {
|
|
dbgs() << " Applying fixup on " << G.getEdgeKindName(K)
|
|
<< " edge, (S, A, P, .TOC.) = (" << formatv("{0:x}", S) << ", "
|
|
<< formatv("{0:x}", A) << ", " << formatv("{0:x}", P) << ", "
|
|
<< formatv("{0:x}", TOCBase) << ")\n";
|
|
});
|
|
|
|
switch (K) {
|
|
case Pointer64: {
|
|
uint64_t Value = S + A;
|
|
support::endian::write64<Endianness>(FixupPtr, Value);
|
|
break;
|
|
}
|
|
case Delta16:
|
|
case Delta16HA:
|
|
case Delta16HI:
|
|
case Delta16LO: {
|
|
int64_t Value = S + A - P;
|
|
if (LLVM_UNLIKELY(!isInt<32>(Value))) {
|
|
return makeTargetOutOfRangeError(G, B, E);
|
|
}
|
|
return relocateHalf16<Endianness>(FixupPtr, Value, K);
|
|
}
|
|
case TOC:
|
|
support::endian::write64<Endianness>(FixupPtr, TOCBase);
|
|
break;
|
|
case Pointer16:
|
|
case Pointer16DS:
|
|
case Pointer16HA:
|
|
case Pointer16HI:
|
|
case Pointer16HIGH:
|
|
case Pointer16HIGHA:
|
|
case Pointer16HIGHER:
|
|
case Pointer16HIGHERA:
|
|
case Pointer16HIGHEST:
|
|
case Pointer16HIGHESTA:
|
|
case Pointer16LO:
|
|
case Pointer16LODS: {
|
|
uint64_t Value = S + A;
|
|
if (LLVM_UNLIKELY(!isInt<32>(Value))) {
|
|
return makeTargetOutOfRangeError(G, B, E);
|
|
}
|
|
return relocateHalf16<Endianness>(FixupPtr, Value, K);
|
|
}
|
|
case Pointer14: {
|
|
static const uint32_t Low14Mask = 0xfffc;
|
|
uint64_t Value = S + A;
|
|
assert((Value & 3) == 0 && "Pointer14 requires 4-byte alignment");
|
|
if (LLVM_UNLIKELY(!isInt<16>(Value))) {
|
|
return makeTargetOutOfRangeError(G, B, E);
|
|
}
|
|
uint32_t Inst = support::endian::read32<Endianness>(FixupPtr);
|
|
support::endian::write32<Endianness>(FixupPtr, (Inst & ~Low14Mask) |
|
|
(Value & Low14Mask));
|
|
break;
|
|
}
|
|
case TOCDelta16:
|
|
case TOCDelta16DS:
|
|
case TOCDelta16HA:
|
|
case TOCDelta16HI:
|
|
case TOCDelta16LO:
|
|
case TOCDelta16LODS: {
|
|
int64_t Value = S + A - TOCBase;
|
|
if (LLVM_UNLIKELY(!isInt<32>(Value))) {
|
|
return makeTargetOutOfRangeError(G, B, E);
|
|
}
|
|
return relocateHalf16<Endianness>(FixupPtr, Value, K);
|
|
}
|
|
case CallBranchDeltaRestoreTOC:
|
|
case CallBranchDelta: {
|
|
int64_t Value = S + A - P;
|
|
if (LLVM_UNLIKELY(!isInt<26>(Value))) {
|
|
return makeTargetOutOfRangeError(G, B, E);
|
|
}
|
|
uint32_t Inst = support::endian::read32<Endianness>(FixupPtr);
|
|
support::endian::write32<Endianness>(FixupPtr, (Inst & 0xfc000003) |
|
|
(Value & 0x03fffffc));
|
|
if (K == CallBranchDeltaRestoreTOC) {
|
|
uint32_t NopInst = support::endian::read32<Endianness>(FixupPtr + 4);
|
|
assert(NopInst == 0x60000000 &&
|
|
"NOP should be placed here for restoring r2");
|
|
(void)NopInst;
|
|
// Restore r2 by instruction 0xe8410018 which is `ld r2, 24(r1)`.
|
|
support::endian::write32<Endianness>(FixupPtr + 4, 0xe8410018);
|
|
}
|
|
break;
|
|
}
|
|
case Delta64: {
|
|
int64_t Value = S + A - P;
|
|
support::endian::write64<Endianness>(FixupPtr, Value);
|
|
break;
|
|
}
|
|
case Delta34: {
|
|
int64_t Value = S + A - P;
|
|
if (!LLVM_UNLIKELY(isInt<34>(Value)))
|
|
return makeTargetOutOfRangeError(G, B, E);
|
|
static const uint64_t SI0Mask = 0x00000003ffff0000;
|
|
static const uint64_t SI1Mask = 0x000000000000ffff;
|
|
static const uint64_t FullMask = 0x0003ffff0000ffff;
|
|
uint64_t Inst = readPrefixedInstruction<Endianness>(FixupPtr) & ~FullMask;
|
|
writePrefixedInstruction<Endianness>(
|
|
FixupPtr, Inst | ((Value & SI0Mask) << 16) | (Value & SI1Mask));
|
|
break;
|
|
}
|
|
case Delta32: {
|
|
int64_t Value = S + A - P;
|
|
if (LLVM_UNLIKELY(!isInt<32>(Value))) {
|
|
return makeTargetOutOfRangeError(G, B, E);
|
|
}
|
|
support::endian::write32<Endianness>(FixupPtr, Value);
|
|
break;
|
|
}
|
|
case NegDelta32: {
|
|
int64_t Value = P - S + A;
|
|
if (LLVM_UNLIKELY(!isInt<32>(Value))) {
|
|
return makeTargetOutOfRangeError(G, B, E);
|
|
}
|
|
support::endian::write32<Endianness>(FixupPtr, Value);
|
|
break;
|
|
}
|
|
default:
|
|
return make_error<JITLinkError>(
|
|
"In graph " + G.getName() + ", section " + B.getSection().getName() +
|
|
" unsupported edge kind " + getEdgeKindName(E.getKind()));
|
|
}
|
|
return Error::success();
|
|
}
|
|
|
|
} // end namespace llvm::jitlink::ppc64
|
|
|
|
#endif // LLVM_EXECUTIONENGINE_JITLINK_PPC64_H
|