Skip to content

[JITLink][LoongArch] Support R_LARCH_ALIGN relaxation #122259

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions llvm/include/llvm/ExecutionEngine/JITLink/ELF_loongarch.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ Expected<std::unique_ptr<LinkGraph>> createLinkGraphFromELFObject_loongarch(
void link_ELF_loongarch(std::unique_ptr<LinkGraph> G,
std::unique_ptr<JITLinkContext> Ctx);

/// Returns a pass that performs linker relaxation. Should be added to
/// PostAllocationPasses.
LinkGraphPassFunction createRelaxationPass_ELF_loongarch();

} // end namespace jitlink
} // end namespace llvm

Expand Down
10 changes: 10 additions & 0 deletions llvm/include/llvm/ExecutionEngine/JITLink/loongarch.h
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,13 @@ enum EdgeKind_loongarch : Edge::Kind {
/// out-of-range error will be returned.
///
Call36PCRel,

/// Alignment requirement used by linker relaxation.
///
/// Linker relaxation will use this to ensure all code sequences are properly
/// aligned and then remove these edges from the graph.
///
AlignRelaxable,
};

/// Returns a string name for the given loongarch edge. For debugging purposes
Expand Down Expand Up @@ -362,6 +369,9 @@ inline Error applyFixup(LinkGraph &G, Block &B, const Edge &E) {
*(little32_t *)(FixupPtr + 4) = Jirl | Lo16;
break;
}
case AlignRelaxable:
// Ignore when the relaxation pass did not run
break;
default:
return make_error<JITLinkError>(
"In graph " + G.getName() + ", section " + B.getSection().getName() +
Expand Down
269 changes: 263 additions & 6 deletions llvm/lib/ExecutionEngine/JITLink/ELF_loongarch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,238 @@ class ELFJITLinker_loongarch : public JITLinker<ELFJITLinker_loongarch> {
}
};

namespace {

struct SymbolAnchor {
uint64_t Offset;
Symbol *Sym;
bool End; // true for the anchor of getOffset() + getSize()
};

struct BlockRelaxAux {
// This records symbol start and end offsets which will be adjusted according
// to the nearest RelocDeltas element.
SmallVector<SymbolAnchor, 0> Anchors;
// All edges that either 1) are R_LARCH_ALIGN or 2) have a R_LARCH_RELAX edge
// at the same offset.
SmallVector<Edge *, 0> RelaxEdges;
// For RelaxEdges[I], the actual offset is RelaxEdges[I]->getOffset() - (I ?
// RelocDeltas[I - 1] : 0).
SmallVector<uint32_t, 0> RelocDeltas;
// For RelaxEdges[I], the actual type is EdgeKinds[I].
SmallVector<Edge::Kind, 0> EdgeKinds;
// List of rewritten instructions. Contains one raw encoded instruction per
// element in EdgeKinds that isn't Invalid or R_LARCH_ALIGN.
SmallVector<uint32_t, 0> Writes;
};

struct RelaxAux {
DenseMap<Block *, BlockRelaxAux> Blocks;
};

} // namespace

static bool shouldRelax(const Section &S) {
return (S.getMemProt() & orc::MemProt::Exec) != orc::MemProt::None;
}

static bool isRelaxable(const Edge &E) {
switch (E.getKind()) {
default:
return false;
case AlignRelaxable:
return true;
}
}

static RelaxAux initRelaxAux(LinkGraph &G) {
RelaxAux Aux;
for (auto &S : G.sections()) {
if (!shouldRelax(S))
continue;
for (auto *B : S.blocks()) {
auto BlockEmplaceResult = Aux.Blocks.try_emplace(B);
assert(BlockEmplaceResult.second && "Block encountered twice");
auto &BlockAux = BlockEmplaceResult.first->second;

for (auto &E : B->edges())
if (isRelaxable(E))
BlockAux.RelaxEdges.push_back(&E);

if (BlockAux.RelaxEdges.empty()) {
Aux.Blocks.erase(BlockEmplaceResult.first);
continue;
}

const auto NumEdges = BlockAux.RelaxEdges.size();
BlockAux.RelocDeltas.resize(NumEdges, 0);
BlockAux.EdgeKinds.resize_for_overwrite(NumEdges);

// Store anchors (offset and offset+size) for symbols.
for (auto *Sym : S.symbols()) {
if (!Sym->isDefined() || &Sym->getBlock() != B)
continue;

BlockAux.Anchors.push_back({Sym->getOffset(), Sym, false});
BlockAux.Anchors.push_back(
{Sym->getOffset() + Sym->getSize(), Sym, true});
}
}
}

// Sort anchors by offset so that we can find the closest relocation
// efficiently. For a zero size symbol, ensure that its start anchor precedes
// its end anchor. For two symbols with anchors at the same offset, their
// order does not matter.
for (auto &BlockAuxIter : Aux.Blocks) {
llvm::sort(BlockAuxIter.second.Anchors, [](auto &A, auto &B) {
return std::make_pair(A.Offset, A.End) < std::make_pair(B.Offset, B.End);
});
}

return Aux;
}

static void relaxAlign(orc::ExecutorAddr Loc, const Edge &E, uint32_t &Remove,
Edge::Kind &NewEdgeKind) {
const uint64_t Addend =
!E.getTarget().isDefined() ? Log2_64(E.getAddend()) + 1 : E.getAddend();
const uint64_t AllBytes = (1ULL << (Addend & 0xff)) - 4;
const uint64_t Align = 1ULL << (Addend & 0xff);
const uint64_t MaxBytes = Addend >> 8;
const uint64_t Off = Loc.getValue() & (Align - 1);
const uint64_t CurBytes = Off == 0 ? 0 : Align - Off;
// All bytes beyond the alignment boundary should be removed.
// If emit bytes more than max bytes to emit, remove all.
if (MaxBytes != 0 && CurBytes > MaxBytes)
Remove = AllBytes;
else
Remove = AllBytes - CurBytes;

assert(static_cast<int32_t>(Remove) >= 0 &&
"R_LARCH_ALIGN needs expanding the content");
NewEdgeKind = AlignRelaxable;
}

static bool relaxBlock(LinkGraph &G, Block &Block, BlockRelaxAux &Aux) {
const auto BlockAddr = Block.getAddress();
bool Changed = false;
ArrayRef<SymbolAnchor> SA = ArrayRef(Aux.Anchors);
uint32_t Delta = 0;

Aux.EdgeKinds.assign(Aux.EdgeKinds.size(), Edge::Invalid);
Aux.Writes.clear();

for (auto [I, E] : llvm::enumerate(Aux.RelaxEdges)) {
const auto Loc = BlockAddr + E->getOffset() - Delta;
auto &Cur = Aux.RelocDeltas[I];
uint32_t Remove = 0;
switch (E->getKind()) {
case AlignRelaxable:
relaxAlign(Loc, *E, Remove, Aux.EdgeKinds[I]);
break;
default:
llvm_unreachable("Unexpected relaxable edge kind");
}

// For all anchors whose offsets are <= E->getOffset(), they are preceded by
// the previous relocation whose RelocDeltas value equals Delta.
// Decrease their offset and update their size.
for (; SA.size() && SA[0].Offset <= E->getOffset(); SA = SA.slice(1)) {
if (SA[0].End)
SA[0].Sym->setSize(SA[0].Offset - Delta - SA[0].Sym->getOffset());
else
SA[0].Sym->setOffset(SA[0].Offset - Delta);
}

Delta += Remove;
if (Delta != Cur) {
Cur = Delta;
Changed = true;
}
}

for (const SymbolAnchor &A : SA) {
if (A.End)
A.Sym->setSize(A.Offset - Delta - A.Sym->getOffset());
else
A.Sym->setOffset(A.Offset - Delta);
}

return Changed;
}

static bool relaxOnce(LinkGraph &G, RelaxAux &Aux) {
bool Changed = false;

for (auto &[B, BlockAux] : Aux.Blocks)
Changed |= relaxBlock(G, *B, BlockAux);

return Changed;
}

static void finalizeBlockRelax(LinkGraph &G, Block &Block, BlockRelaxAux &Aux) {
auto Contents = Block.getAlreadyMutableContent();
auto *Dest = Contents.data();
uint32_t Offset = 0;
uint32_t Delta = 0;

// Update section content: remove NOPs for R_LARCH_ALIGN and rewrite
// instructions for relaxed relocations.
for (auto [I, E] : llvm::enumerate(Aux.RelaxEdges)) {
uint32_t Remove = Aux.RelocDeltas[I] - Delta;
Delta = Aux.RelocDeltas[I];
if (Remove == 0 && Aux.EdgeKinds[I] == Edge::Invalid)
continue;

// Copy from last location to the current relocated location.
const auto Size = E->getOffset() - Offset;
std::memmove(Dest, Contents.data() + Offset, Size);
Dest += Size;
Offset = E->getOffset() + Remove;
}

std::memmove(Dest, Contents.data() + Offset, Contents.size() - Offset);

// Fixup edge offsets and kinds.
Delta = 0;
size_t I = 0;
for (auto &E : Block.edges()) {
E.setOffset(E.getOffset() - Delta);

if (I < Aux.RelaxEdges.size() && Aux.RelaxEdges[I] == &E) {
if (Aux.EdgeKinds[I] != Edge::Invalid)
E.setKind(Aux.EdgeKinds[I]);

Delta = Aux.RelocDeltas[I];
++I;
}
}

// Remove AlignRelaxable edges: all other relaxable edges got modified and
// will be used later while linking. Alignment is entirely handled here so we
// don't need these edges anymore.
for (auto IE = Block.edges().begin(); IE != Block.edges().end();) {
if (IE->getKind() == AlignRelaxable)
IE = Block.removeEdge(IE);
else
++IE;
}
}

static void finalizeRelax(LinkGraph &G, RelaxAux &Aux) {
for (auto &[B, BlockAux] : Aux.Blocks)
finalizeBlockRelax(G, *B, BlockAux);
}

static Error relax(LinkGraph &G) {
auto Aux = initRelaxAux(G);
while (relaxOnce(G, Aux)) {
}
finalizeRelax(G, Aux);
return Error::success();
}

template <typename ELFT>
class ELFLinkGraphBuilder_loongarch : public ELFLinkGraphBuilder<ELFT> {
private:
Expand Down Expand Up @@ -74,13 +306,20 @@ class ELFLinkGraphBuilder_loongarch : public ELFLinkGraphBuilder<ELFT> {
return RequestGOTAndTransformToPageOffset12;
case ELF::R_LARCH_CALL36:
return Call36PCRel;
case ELF::R_LARCH_ALIGN:
return AlignRelaxable;
}

return make_error<JITLinkError>(
"Unsupported loongarch relocation:" + formatv("{0:d}: ", Type) +
object::getELFRelocationTypeName(ELF::EM_LOONGARCH, Type));
}

EdgeKind_loongarch getRelaxableRelocationKind(EdgeKind_loongarch Kind) {
// TODO: Implement more. Just ignore all relaxations now.
return Kind;
}

Error addRelocations() override {
LLVM_DEBUG(dbgs() << "Processing relocations:\n");

Expand All @@ -99,6 +338,25 @@ class ELFLinkGraphBuilder_loongarch : public ELFLinkGraphBuilder<ELFT> {
Block &BlockToFix) {
using Base = ELFLinkGraphBuilder<ELFT>;

uint32_t Type = Rel.getType(false);
int64_t Addend = Rel.r_addend;

if (Type == ELF::R_LARCH_RELAX) {
if (BlockToFix.edges_empty())
return make_error<StringError>(
"R_LARCH_RELAX without preceding relocation",
inconvertibleErrorCode());

auto &PrevEdge = *std::prev(BlockToFix.edges().end());
auto Kind = static_cast<EdgeKind_loongarch>(PrevEdge.getKind());
PrevEdge.setKind(getRelaxableRelocationKind(Kind));
return Error::success();
}

Expected<loongarch::EdgeKind_loongarch> Kind = getRelocationKind(Type);
if (!Kind)
return Kind.takeError();

uint32_t SymbolIndex = Rel.getSymbol(false);
auto ObjSymbol = Base::Obj.getRelocationSymbol(Rel, Base::SymTabSec);
if (!ObjSymbol)
Expand All @@ -113,12 +371,6 @@ class ELFLinkGraphBuilder_loongarch : public ELFLinkGraphBuilder<ELFT> {
Base::GraphSymbols.size()),
inconvertibleErrorCode());

uint32_t Type = Rel.getType(false);
Expected<loongarch::EdgeKind_loongarch> Kind = getRelocationKind(Type);
if (!Kind)
return Kind.takeError();

int64_t Addend = Rel.r_addend;
auto FixupAddress = orc::ExecutorAddr(FixupSect.sh_addr) + Rel.r_offset;
Edge::OffsetT Offset = FixupAddress - BlockToFix.getAddress();
Edge GE(*Kind, Offset, *GraphSymbol, Addend);
Expand Down Expand Up @@ -209,6 +461,9 @@ void link_ELF_loongarch(std::unique_ptr<LinkGraph> G,

// Add an in-place GOT/PLTStubs build pass.
Config.PostPrunePasses.push_back(buildTables_ELF_loongarch);

// Add a linker relaxation pass.
Config.PostAllocationPasses.push_back(relax);
}

if (auto Err = Ctx->modifyPassConfig(*G, Config))
Expand All @@ -217,5 +472,7 @@ void link_ELF_loongarch(std::unique_ptr<LinkGraph> G,
ELFJITLinker_loongarch::link(std::move(Ctx), std::move(G), std::move(Config));
}

LinkGraphPassFunction createRelaxationPass_ELF_loongarch() { return relax; }

} // namespace jitlink
} // namespace llvm
1 change: 1 addition & 0 deletions llvm/lib/ExecutionEngine/JITLink/loongarch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const char *getEdgeKindName(Edge::Kind K) {
KIND_NAME_CASE(RequestGOTAndTransformToPage20)
KIND_NAME_CASE(RequestGOTAndTransformToPageOffset12)
KIND_NAME_CASE(Call36PCRel)
KIND_NAME_CASE(AlignRelaxable)
default:
return getGenericEdgeKindName(K);
}
Expand Down
Loading
Loading