Skip to content

Commit

Permalink
[lld][ELF] Support relax R_LARCH_ALIGN (llvm#78692)
Browse files Browse the repository at this point in the history
Refer to commit 6611d58 ("Relax R_RISCV_ALIGN"), we can relax
R_LARCH_ALIGN by same way. Reuse `SymbolAnchor`, `RISCVRelaxAux` and
`initSymbolAnchors` to simplify codes. As `riscvFinalizeRelax` is an
arch-specific function, put it override on `TargetInfo::finalizeRelax`,
so that LoongArch can override it, too.

The flow of relax R_LARCH_ALIGN is almost consistent with RISCV. The
difference is that LoongArch only has 4-bytes NOP and all executable
insn is 4-bytes aligned. So LoongArch not need rewrite NOP sequence.
Alignment maxBytesEmit parameter is supported in psABI v2.30.
  • Loading branch information
MQ-mengqing authored Feb 6, 2024
1 parent a71147d commit 06a728f
Show file tree
Hide file tree
Showing 8 changed files with 363 additions and 35 deletions.
156 changes: 154 additions & 2 deletions lld/ELF/Arch/LoongArch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ class LoongArch final : public TargetInfo {
bool usesOnlyLowPageBits(RelType type) const override;
void relocate(uint8_t *loc, const Relocation &rel,
uint64_t val) const override;
bool relaxOnce(int pass) const override;
void finalizeRelax(int passes) const override;
};
} // end anonymous namespace

Expand Down Expand Up @@ -465,8 +467,9 @@ RelExpr LoongArch::getRelExpr(const RelType type, const Symbol &s,
case R_LARCH_TLS_GD_HI20:
return R_TLSGD_GOT;
case R_LARCH_RELAX:
// LoongArch linker relaxation is not implemented yet.
return R_NONE;
return config->relax ? R_RELAX_HINT : R_NONE;
case R_LARCH_ALIGN:
return R_RELAX_HINT;

// Other known relocs that are explicitly unimplemented:
//
Expand Down Expand Up @@ -659,6 +662,155 @@ void LoongArch::relocate(uint8_t *loc, const Relocation &rel,
}
}

static bool relax(InputSection &sec) {
const uint64_t secAddr = sec.getVA();
const MutableArrayRef<Relocation> relocs = sec.relocs();
auto &aux = *sec.relaxAux;
bool changed = false;
ArrayRef<SymbolAnchor> sa = ArrayRef(aux.anchors);
uint64_t delta = 0;

std::fill_n(aux.relocTypes.get(), relocs.size(), R_LARCH_NONE);
aux.writes.clear();
for (auto [i, r] : llvm::enumerate(relocs)) {
const uint64_t loc = secAddr + r.offset - delta;
uint32_t &cur = aux.relocDeltas[i], remove = 0;
switch (r.type) {
case R_LARCH_ALIGN: {
const uint64_t addend =
r.sym->isUndefined() ? Log2_64(r.addend) + 1 : r.addend;
const uint64_t allBytes = (1 << (addend & 0xff)) - 4;
const uint64_t align = 1 << (addend & 0xff);
const uint64_t maxBytes = addend >> 8;
const uint64_t off = loc & (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;
// If we can't satisfy this alignment, we've found a bad input.
if (LLVM_UNLIKELY(static_cast<int32_t>(remove) < 0)) {
errorOrWarn(getErrorLocation((const uint8_t *)loc) +
"insufficient padding bytes for " + lld::toString(r.type) +
": " + Twine(allBytes) + " bytes available for " +
"requested alignment of " + Twine(align) + " bytes");
remove = 0;
}
break;
}
}

// For all anchors whose offsets are <= r.offset, they are preceded by
// the previous relocation whose `relocDeltas` value equals `delta`.
// Decrease their st_value and update their st_size.
for (; sa.size() && sa[0].offset <= r.offset; sa = sa.slice(1)) {
if (sa[0].end)
sa[0].d->size = sa[0].offset - delta - sa[0].d->value;
else
sa[0].d->value = sa[0].offset - delta;
}
delta += remove;
if (delta != cur) {
cur = delta;
changed = true;
}
}

for (const SymbolAnchor &a : sa) {
if (a.end)
a.d->size = a.offset - delta - a.d->value;
else
a.d->value = a.offset - delta;
}
// Inform assignAddresses that the size has changed.
if (!isUInt<32>(delta))
fatal("section size decrease is too large: " + Twine(delta));
sec.bytesDropped = delta;
return changed;
}

// When relaxing just R_LARCH_ALIGN, relocDeltas is usually changed only once in
// the absence of a linker script. For call and load/store R_LARCH_RELAX, code
// shrinkage may reduce displacement and make more relocations eligible for
// relaxation. Code shrinkage may increase displacement to a call/load/store
// target at a higher fixed address, invalidating an earlier relaxation. Any
// change in section sizes can have cascading effect and require another
// relaxation pass.
bool LoongArch::relaxOnce(int pass) const {
if (config->relocatable)
return false;

if (pass == 0)
initSymbolAnchors();

SmallVector<InputSection *, 0> storage;
bool changed = false;
for (OutputSection *osec : outputSections) {
if (!(osec->flags & SHF_EXECINSTR))
continue;
for (InputSection *sec : getInputSections(*osec, storage))
changed |= relax(*sec);
}
return changed;
}

void LoongArch::finalizeRelax(int passes) const {
log("relaxation passes: " + Twine(passes));
SmallVector<InputSection *, 0> storage;
for (OutputSection *osec : outputSections) {
if (!(osec->flags & SHF_EXECINSTR))
continue;
for (InputSection *sec : getInputSections(*osec, storage)) {
RelaxAux &aux = *sec->relaxAux;
if (!aux.relocDeltas)
continue;

MutableArrayRef<Relocation> rels = sec->relocs();
ArrayRef<uint8_t> old = sec->content();
size_t newSize = old.size() - aux.relocDeltas[rels.size() - 1];
uint8_t *p = context().bAlloc.Allocate<uint8_t>(newSize);
uint64_t offset = 0;
int64_t delta = 0;
sec->content_ = p;
sec->size = newSize;
sec->bytesDropped = 0;

// Update section content: remove NOPs for R_LARCH_ALIGN and rewrite
// instructions for relaxed relocations.
for (size_t i = 0, e = rels.size(); i != e; ++i) {
uint32_t remove = aux.relocDeltas[i] - delta;
delta = aux.relocDeltas[i];
if (remove == 0 && aux.relocTypes[i] == R_LARCH_NONE)
continue;

// Copy from last location to the current relocated location.
const Relocation &r = rels[i];
uint64_t size = r.offset - offset;
memcpy(p, old.data() + offset, size);
p += size;
offset = r.offset + remove;
}
memcpy(p, old.data() + offset, old.size() - offset);

// Subtract the previous relocDeltas value from the relocation offset.
// For a pair of R_LARCH_XXX/R_LARCH_RELAX with the same offset, decrease
// their r_offset by the same delta.
delta = 0;
for (size_t i = 0, e = rels.size(); i != e;) {
uint64_t cur = rels[i].offset;
do {
rels[i].offset -= delta;
if (aux.relocTypes[i] != R_LARCH_NONE)
rels[i].type = aux.relocTypes[i];
} while (++i != e && rels[i].offset == cur);
delta = aux.relocDeltas[i - 1];
}
}
}
}

TargetInfo *elf::getLoongArchTargetInfo() {
static LoongArch target;
return &target;
Expand Down
29 changes: 5 additions & 24 deletions lld/ELF/Arch/RISCV.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class RISCV final : public TargetInfo {
uint64_t val) const override;
void relocateAlloc(InputSectionBase &sec, uint8_t *buf) const override;
bool relaxOnce(int pass) const override;
void finalizeRelax(int passes) const override;
};

} // end anonymous namespace
Expand Down Expand Up @@ -104,26 +105,6 @@ static uint32_t setLO12_S(uint32_t insn, uint32_t imm) {
(extractBits(imm, 4, 0) << 7);
}

namespace {
struct SymbolAnchor {
uint64_t offset;
Defined *d;
bool end; // true for the anchor of st_value+st_size
};
} // namespace

struct elf::RISCVRelaxAux {
// This records symbol start and end offsets which will be adjusted according
// to the nearest relocDeltas element.
SmallVector<SymbolAnchor, 0> anchors;
// For relocations[i], the actual offset is
// r_offset - (i ? relocDeltas[i-1] : 0).
std::unique_ptr<uint32_t[]> relocDeltas;
// For relocations[i], the actual type is relocTypes[i].
std::unique_ptr<RelType[]> relocTypes;
SmallVector<uint32_t, 0> writes;
};

RISCV::RISCV() {
copyRel = R_RISCV_COPY;
pltRel = R_RISCV_JUMP_SLOT;
Expand Down Expand Up @@ -695,13 +676,13 @@ void RISCV::relocateAlloc(InputSectionBase &sec, uint8_t *buf) const {
}
}

static void initSymbolAnchors() {
void elf::initSymbolAnchors() {
SmallVector<InputSection *, 0> storage;
for (OutputSection *osec : outputSections) {
if (!(osec->flags & SHF_EXECINSTR))
continue;
for (InputSection *sec : getInputSections(*osec, storage)) {
sec->relaxAux = make<RISCVRelaxAux>();
sec->relaxAux = make<RelaxAux>();
if (sec->relocs().size()) {
sec->relaxAux->relocDeltas =
std::make_unique<uint32_t[]>(sec->relocs().size());
Expand Down Expand Up @@ -948,15 +929,15 @@ bool RISCV::relaxOnce(int pass) const {
return changed;
}

void elf::riscvFinalizeRelax(int passes) {
void RISCV::finalizeRelax(int passes) const {
llvm::TimeTraceScope timeScope("Finalize RISC-V relaxation");
log("relaxation passes: " + Twine(passes));
SmallVector<InputSection *, 0> storage;
for (OutputSection *osec : outputSections) {
if (!(osec->flags & SHF_EXECINSTR))
continue;
for (InputSection *sec : getInputSections(*osec, storage)) {
RISCVRelaxAux &aux = *sec->relaxAux;
RelaxAux &aux = *sec->relaxAux;
if (!aux.relocDeltas)
continue;

Expand Down
7 changes: 4 additions & 3 deletions lld/ELF/InputSection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -354,9 +354,10 @@ InputSectionBase *InputSection::getRelocatedSection() const {

template <class ELFT, class RelTy>
void InputSection::copyRelocations(uint8_t *buf) {
if (config->relax && !config->relocatable && config->emachine == EM_RISCV) {
// On RISC-V, relaxation might change relocations: copy from internal ones
// that are updated by relaxation.
if (config->relax && !config->relocatable &&
(config->emachine == EM_RISCV || config->emachine == EM_LOONGARCH)) {
// On LoongArch and RISC-V, relaxation might change relocations: copy
// from internal ones that are updated by relaxation.
InputSectionBase *sec = getRelocatedSection();
copyRelocations<ELFT, RelTy>(buf, llvm::make_range(sec->relocations.begin(),
sec->relocations.end()));
Expand Down
24 changes: 20 additions & 4 deletions lld/ELF/InputSection.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,23 @@ class SectionBase {
link(link), info(info) {}
};

struct RISCVRelaxAux;
struct SymbolAnchor {
uint64_t offset;
Defined *d;
bool end; // true for the anchor of st_value+st_size
};

struct RelaxAux {
// This records symbol start and end offsets which will be adjusted according
// to the nearest relocDeltas element.
SmallVector<SymbolAnchor, 0> anchors;
// For relocations[i], the actual offset is
// r_offset - (i ? relocDeltas[i-1] : 0).
std::unique_ptr<uint32_t[]> relocDeltas;
// For relocations[i], the actual type is relocTypes[i].
std::unique_ptr<RelType[]> relocTypes;
SmallVector<uint32_t, 0> writes;
};

// This corresponds to a section of an input file.
class InputSectionBase : public SectionBase {
Expand Down Expand Up @@ -227,9 +243,9 @@ class InputSectionBase : public SectionBase {
// basic blocks.
JumpInstrMod *jumpInstrMod = nullptr;

// Auxiliary information for RISC-V linker relaxation. RISC-V does not use
// jumpInstrMod.
RISCVRelaxAux *relaxAux;
// Auxiliary information for RISC-V and LoongArch linker relaxation.
// They do not use jumpInstrMod.
RelaxAux *relaxAux;

// The compressed content size when `compressed` is true.
size_t compressedSize;
Expand Down
3 changes: 3 additions & 0 deletions lld/ELF/Target.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ class TargetInfo {

// Do a linker relaxation pass and return true if we changed something.
virtual bool relaxOnce(int pass) const { return false; }
// Do finalize relaxation after collecting relaxation infos.
virtual void finalizeRelax(int passes) const {}

virtual void applyJumpInstrMod(uint8_t *loc, JumpModType type,
JumpModType val) const {}
Expand Down Expand Up @@ -236,6 +238,7 @@ void addArmSyntheticSectionMappingSymbol(Defined *);
void sortArmMappingSymbols();
void convertArmInstructionstoBE8(InputSection *sec, uint8_t *buf);
void createTaggedSymbols(const SmallVector<ELFFileBase *, 0> &files);
void initSymbolAnchors();

LLVM_LIBRARY_VISIBILITY extern const TargetInfo *target;
TargetInfo *getTarget();
Expand Down
4 changes: 2 additions & 2 deletions lld/ELF/Writer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1752,8 +1752,8 @@ template <class ELFT> void Writer<ELFT>::finalizeAddressDependentContent() {
}
}
}
if (!config->relocatable && config->emachine == EM_RISCV)
riscvFinalizeRelax(pass);
if (!config->relocatable)
target->finalizeRelax(pass);

if (config->relocatable)
for (OutputSection *sec : outputSections)
Expand Down
Loading

0 comments on commit 06a728f

Please sign in to comment.