blob: 0b2d562b71c487d33bf79354eb4b81b7d3b7dd2d [file] [log] [blame]
//===--- 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,
Delta64,
Delta32,
NegDelta32,
Delta16,
Delta16HA,
Delta16LO,
TOCDelta16HA,
TOCDelta16LO,
TOCDelta16DS,
TOCDelta16LODS,
CallBranchDelta,
// Need to restore r2 after the bl, suggesting the bl is followed by a nop.
CallBranchDeltaRestoreTOC,
// Need PLT call stub using TOC, TOC pointer is not saved before branching.
RequestPLTCallStub,
// Need PLT call stub using TOC, TOC pointer is saved before branching.
RequestPLTCallStubSaveTOC,
// Need PLT call stub without using TOC.
RequestPLTCallStubNoTOC,
};
enum PLTCallStubKind {
LongBranch,
LongBranchSaveR2,
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 <support::endianness Endianness>
inline PLTCallStubInfo pickStub(PLTCallStubKind StubKind) {
constexpr bool isLE = Endianness == support::endianness::little;
switch (StubKind) {
case LongBranch: {
ArrayRef<char> Content =
isLE ? PointerJumpStubContent_little : PointerJumpStubContent_big;
// Skip save r2.
Content = Content.slice(4);
return PLTCallStubInfo{
Content,
{{TOCDelta16HA, 0, 0}, {TOCDelta16LO, 4, 0}},
};
}
case LongBranchSaveR2: {
ArrayRef<char> Content =
isLE ? PointerJumpStubContent_little : PointerJumpStubContent_big;
return PLTCallStubInfo{
Content,
{{TOCDelta16HA, 4, 0}, {TOCDelta16LO, 8, 0}},
};
}
case LongBranchNoTOC: {
ArrayRef<char> Content = isLE ? PointerJumpStubNoTOCContent_little
: PointerJumpStubNoTOCContent_big;
return PLTCallStubInfo{
Content,
{{Delta16HA, 16, 8}, {Delta16LO, 20, 12}},
};
}
}
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 <support::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 <support::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 RequestPLTCallStub:
case RequestPLTCallStubSaveTOC:
// Create TOC section if TOC relocation, PLT or GOT is used.
getOrCreateTOCSection(G);
return false;
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 <support::endianness Endianness>
class PLTTableManager : public TableManager<PLTTableManager<Endianness>> {
public:
PLTTableManager(TOCTableManager<Endianness> &TOC) : TOC(TOC) {}
static StringRef getSectionName() { return "$__STUBS"; }
bool visitEdge(LinkGraph &G, Block *B, Edge &E) {
Edge::Kind K = E.getKind();
if (K == ppc64::RequestPLTCallStubSaveTOC && E.getTarget().isExternal()) {
E.setKind(ppc64::CallBranchDeltaRestoreTOC);
this->StubKind = LongBranchSaveR2;
E.setTarget(this->getEntryForTarget(G, E.getTarget()));
return true;
}
if (K == ppc64::RequestPLTCallStubNoTOC && E.getTarget().isExternal()) {
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 ha16(uint64_t x) { return (x + 0x8000) >> 16; }
inline static uint16_t lo16(uint64_t x) { return x & 0xffff; }
/// Apply fixup expression for edge to block content.
template <support::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 Delta16HA:
case Delta16LO: {
int64_t Value = S + A - P;
if (LLVM_UNLIKELY(!isInt<32>(Value))) {
return makeTargetOutOfRangeError(G, B, E);
}
if (K == Delta16LO)
support::endian::write16<Endianness>(FixupPtr, lo16(Value));
else
support::endian::write16<Endianness>(FixupPtr, ha16(Value));
break;
}
case TOCDelta16HA:
case TOCDelta16LO: {
int64_t Value = S + A - TOCBase;
if (LLVM_UNLIKELY(!isInt<32>(Value))) {
return makeTargetOutOfRangeError(G, B, E);
}
if (K == TOCDelta16LO)
support::endian::write16<Endianness>(FixupPtr, lo16(Value));
else
support::endian::write16<Endianness>(FixupPtr, ha16(Value));
break;
}
case TOCDelta16DS:
case TOCDelta16LODS: {
int64_t Value = S + A - TOCBase;
if (LLVM_UNLIKELY(!isInt<32>(Value))) {
return makeTargetOutOfRangeError(G, B, E);
}
if (K == TOCDelta16LODS)
support::endian::write16<Endianness>(FixupPtr, lo16(Value) & ~3);
else
support::endian::write16<Endianness>(FixupPtr, Value & ~3);
break;
}
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 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