[LLD] Implement --enable-non-contiguous-regions (#90007)
When enabled, input sections that would otherwise overflow a memory region are instead spilled to the next matching output section. This feature parallels the one in GNU LD, but there are some differences from its documented behavior: - /DISCARD/ only matches previously-unmatched sections (i.e., the flag does not affect it). - If a section fails to fit at any of its matches, the link fails instead of discarding the section. - The flag --enable-non-contiguous-regions-warnings is not implemented, as it exists to warn about such occurrences. The implementation places stubs at possible spill locations, and replaces them with the original input section when effecting spills. Spilling decisions occur after address assignment. Sections are spilled in reverse order of assignment, with each spill naively decreasing the size of the affected memory regions. This continues until the memory regions are brought back under size. Spilling anything causes another pass of address assignment, and this continues to fixed point. Spilling after rather than during assignment allows the algorithm to consider the size effects of unspillable input sections that appear later in the assignment. Otherwise, such sections (e.g. thunks) may force an overflow, even if spilling something earlier could have avoided it. A few notable feature interactions occur: - Stubs affect alignment, ONLY_IF_RO, etc, broadly as if a copy of the input section were actually placed there. - SHF_MERGE synthetic sections use the spill list of their first contained input section (the one that gives the section its name). - ICF occurs oblivious to spill sections; spill lists for merged-away sections become inert and are removed after assignment. - SHF_LINK_ORDER and .ARM.exidx are ordered according to the final section ordering, after all spilling has completed. - INSERT BEFORE/AFTER and OVERWRITE_SECTIONS are explicitly disallowed.
This commit is contained in:
parent
8ef2011b2c
commit
673114447b
@ -238,6 +238,7 @@ struct Config {
|
||||
bool emitLLVM;
|
||||
bool emitRelocs;
|
||||
bool enableNewDtags;
|
||||
bool enableNonContiguousRegions;
|
||||
bool executeOnly;
|
||||
bool exportDynamic;
|
||||
bool fixCortexA53Errata843419;
|
||||
|
@ -1250,6 +1250,8 @@ static void readConfigs(opt::InputArgList &args) {
|
||||
config->emitRelocs = args.hasArg(OPT_emit_relocs);
|
||||
config->enableNewDtags =
|
||||
args.hasFlag(OPT_enable_new_dtags, OPT_disable_new_dtags, true);
|
||||
config->enableNonContiguousRegions =
|
||||
args.hasArg(OPT_enable_non_contiguous_regions);
|
||||
config->entry = args.getLastArgValue(OPT_entry);
|
||||
|
||||
errorHandler().errorHandlingScript =
|
||||
@ -3085,7 +3087,7 @@ template <class ELFT> void LinkerDriver::link(opt::InputArgList &args) {
|
||||
// sectionBases.
|
||||
for (SectionCommand *cmd : script->sectionCommands)
|
||||
if (auto *osd = dyn_cast<OutputDesc>(cmd))
|
||||
osd->osec.finalizeInputSections();
|
||||
osd->osec.finalizeInputSections(script.get());
|
||||
}
|
||||
|
||||
// Two input sections with different output sections should not be folded.
|
||||
|
@ -161,6 +161,7 @@ uint64_t SectionBase::getOffset(uint64_t offset) const {
|
||||
}
|
||||
case Regular:
|
||||
case Synthetic:
|
||||
case Spill:
|
||||
return cast<InputSection>(this)->outSecOff + offset;
|
||||
case EHFrame: {
|
||||
// Two code paths may reach here. First, clang_rt.crtbegin.o and GCC
|
||||
@ -309,6 +310,12 @@ std::string InputSectionBase::getObjMsg(uint64_t off) const {
|
||||
.str();
|
||||
}
|
||||
|
||||
PotentialSpillSection::PotentialSpillSection(const InputSectionBase &source,
|
||||
InputSectionDescription &isd)
|
||||
: InputSection(source.file, source.flags, source.type, source.addralign, {},
|
||||
source.name, SectionBase::Spill),
|
||||
isd(&isd) {}
|
||||
|
||||
InputSection InputSection::discarded(nullptr, 0, 0, 0, ArrayRef<uint8_t>(), "");
|
||||
|
||||
InputSection::InputSection(InputFile *f, uint64_t flags, uint32_t type,
|
||||
|
@ -48,7 +48,7 @@ template <class ELFT> struct RelsOrRelas {
|
||||
// sections.
|
||||
class SectionBase {
|
||||
public:
|
||||
enum Kind { Regular, Synthetic, EHFrame, Merge, Output };
|
||||
enum Kind { Regular, Synthetic, Spill, EHFrame, Merge, Output };
|
||||
|
||||
Kind kind() const { return (Kind)sectionKind; }
|
||||
|
||||
@ -382,7 +382,8 @@ public:
|
||||
|
||||
static bool classof(const SectionBase *s) {
|
||||
return s->kind() == SectionBase::Regular ||
|
||||
s->kind() == SectionBase::Synthetic;
|
||||
s->kind() == SectionBase::Synthetic ||
|
||||
s->kind() == SectionBase::Spill;
|
||||
}
|
||||
|
||||
// Write this section to a mmap'ed file, assuming Buf is pointing to
|
||||
@ -425,6 +426,26 @@ private:
|
||||
template <class ELFT> void copyShtGroup(uint8_t *buf);
|
||||
};
|
||||
|
||||
// A marker for a potential spill location for another input section. This
|
||||
// broadly acts as if it were the original section until address assignment.
|
||||
// Then it is either replaced with the real input section or removed.
|
||||
class PotentialSpillSection : public InputSection {
|
||||
public:
|
||||
// The containing input section description; used to quickly replace this stub
|
||||
// with the actual section.
|
||||
InputSectionDescription *isd;
|
||||
|
||||
// Next potential spill location for the same source input section.
|
||||
PotentialSpillSection *next = nullptr;
|
||||
|
||||
PotentialSpillSection(const InputSectionBase &source,
|
||||
InputSectionDescription &isd);
|
||||
|
||||
static bool classof(const SectionBase *sec) {
|
||||
return sec->kind() == InputSectionBase::Spill;
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(InputSection) <= 160, "InputSection is too big");
|
||||
|
||||
class SyntheticSection : public InputSection {
|
||||
|
@ -304,6 +304,9 @@ getChangedSymbolAssignment(const SymbolAssignmentMap &oldValues) {
|
||||
void LinkerScript::processInsertCommands() {
|
||||
SmallVector<OutputDesc *, 0> moves;
|
||||
for (const InsertCommand &cmd : insertCommands) {
|
||||
if (config->enableNonContiguousRegions)
|
||||
error("INSERT cannot be used with --enable-non-contiguous-regions");
|
||||
|
||||
for (StringRef name : cmd.names) {
|
||||
// If base is empty, it may have been discarded by
|
||||
// adjustOutputSections(). We do not handle such output sections.
|
||||
@ -486,10 +489,12 @@ static void sortInputSections(MutableArrayRef<InputSectionBase *> vec,
|
||||
// Compute and remember which sections the InputSectionDescription matches.
|
||||
SmallVector<InputSectionBase *, 0>
|
||||
LinkerScript::computeInputSections(const InputSectionDescription *cmd,
|
||||
ArrayRef<InputSectionBase *> sections) {
|
||||
ArrayRef<InputSectionBase *> sections,
|
||||
const OutputSection &outCmd) {
|
||||
SmallVector<InputSectionBase *, 0> ret;
|
||||
SmallVector<size_t, 0> indexes;
|
||||
DenseSet<size_t> seen;
|
||||
DenseSet<InputSectionBase *> spills;
|
||||
auto sortByPositionThenCommandLine = [&](size_t begin, size_t end) {
|
||||
llvm::sort(MutableArrayRef<size_t>(indexes).slice(begin, end - begin));
|
||||
for (size_t i = begin; i != end; ++i)
|
||||
@ -505,10 +510,10 @@ LinkerScript::computeInputSections(const InputSectionDescription *cmd,
|
||||
size_t sizeBeforeCurrPat = ret.size();
|
||||
|
||||
for (size_t i = 0, e = sections.size(); i != e; ++i) {
|
||||
// Skip if the section is dead or has been matched by a previous input
|
||||
// section description or a previous pattern.
|
||||
// Skip if the section is dead or has been matched by a previous pattern
|
||||
// in this input section description.
|
||||
InputSectionBase *sec = sections[i];
|
||||
if (!sec->isLive() || sec->parent || seen.contains(i))
|
||||
if (!sec->isLive() || seen.contains(i))
|
||||
continue;
|
||||
|
||||
// For --emit-relocs we have to ignore entries like
|
||||
@ -529,6 +534,29 @@ LinkerScript::computeInputSections(const InputSectionDescription *cmd,
|
||||
(sec->flags & cmd->withoutFlags) != 0)
|
||||
continue;
|
||||
|
||||
if (sec->parent) {
|
||||
// Skip if not allowing multiple matches.
|
||||
if (!config->enableNonContiguousRegions)
|
||||
continue;
|
||||
|
||||
// Disallow spilling into /DISCARD/; special handling would be needed
|
||||
// for this in address assignment, and the semantics are nebulous.
|
||||
if (outCmd.name == "/DISCARD/")
|
||||
continue;
|
||||
|
||||
// Skip if the section's first match was /DISCARD/; such sections are
|
||||
// always discarded.
|
||||
if (sec->parent->name == "/DISCARD/")
|
||||
continue;
|
||||
|
||||
// Skip if the section was already matched by a different input section
|
||||
// description within this output section.
|
||||
if (sec->parent == &outCmd)
|
||||
continue;
|
||||
|
||||
spills.insert(sec);
|
||||
}
|
||||
|
||||
ret.push_back(sec);
|
||||
indexes.push_back(i);
|
||||
seen.insert(i);
|
||||
@ -555,6 +583,30 @@ LinkerScript::computeInputSections(const InputSectionDescription *cmd,
|
||||
// Matched sections after the last SORT* are sorted by (--sort-alignment,
|
||||
// input order).
|
||||
sortByPositionThenCommandLine(sizeAfterPrevSort, ret.size());
|
||||
|
||||
// The flag --enable-non-contiguous-regions may cause sections to match an
|
||||
// InputSectionDescription in more than one OutputSection. Matches after the
|
||||
// first were collected in the spills set, so replace these with potential
|
||||
// spill sections.
|
||||
if (!spills.empty()) {
|
||||
for (InputSectionBase *&sec : ret) {
|
||||
if (!spills.contains(sec))
|
||||
continue;
|
||||
|
||||
// Append the spill input section to the list for the input section,
|
||||
// creating it if necessary.
|
||||
PotentialSpillSection *pss = make<PotentialSpillSection>(
|
||||
*sec, const_cast<InputSectionDescription &>(*cmd));
|
||||
auto [it, inserted] =
|
||||
potentialSpillLists.try_emplace(sec, PotentialSpillList{pss, pss});
|
||||
if (!inserted) {
|
||||
PotentialSpillSection *&tail = it->second.tail;
|
||||
tail = tail->next = pss;
|
||||
}
|
||||
sec = pss;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -577,7 +629,7 @@ void LinkerScript::discardSynthetic(OutputSection &outCmd) {
|
||||
part.armExidx->exidxSections.end());
|
||||
for (SectionCommand *cmd : outCmd.commands)
|
||||
if (auto *isd = dyn_cast<InputSectionDescription>(cmd))
|
||||
for (InputSectionBase *s : computeInputSections(isd, secs))
|
||||
for (InputSectionBase *s : computeInputSections(isd, secs, outCmd))
|
||||
discard(*s);
|
||||
}
|
||||
}
|
||||
@ -588,7 +640,7 @@ LinkerScript::createInputSectionList(OutputSection &outCmd) {
|
||||
|
||||
for (SectionCommand *cmd : outCmd.commands) {
|
||||
if (auto *isd = dyn_cast<InputSectionDescription>(cmd)) {
|
||||
isd->sectionBases = computeInputSections(isd, ctx.inputSections);
|
||||
isd->sectionBases = computeInputSections(isd, ctx.inputSections, outCmd);
|
||||
for (InputSectionBase *s : isd->sectionBases)
|
||||
s->parent = &outCmd;
|
||||
ret.insert(ret.end(), isd->sectionBases.begin(), isd->sectionBases.end());
|
||||
@ -644,6 +696,9 @@ void LinkerScript::processSectionCommands() {
|
||||
|
||||
// Process OVERWRITE_SECTIONS first so that it can overwrite the main script
|
||||
// or orphans.
|
||||
if (config->enableNonContiguousRegions && !overwriteSections.empty())
|
||||
error("OVERWRITE_SECTIONS cannot be used with "
|
||||
"--enable-non-contiguous-regions");
|
||||
DenseMap<CachedHashStringRef, OutputDesc *> map;
|
||||
size_t i = 0;
|
||||
for (OutputDesc *osd : overwriteSections) {
|
||||
@ -1066,8 +1121,12 @@ void LinkerScript::assignOffsets(OutputSection *sec) {
|
||||
// Handle a single input section description command.
|
||||
// It calculates and assigns the offsets for each section and also
|
||||
// updates the output section size.
|
||||
for (InputSection *isec : cast<InputSectionDescription>(cmd)->sections) {
|
||||
|
||||
auto §ions = cast<InputSectionDescription>(cmd)->sections;
|
||||
for (InputSection *isec : sections) {
|
||||
assert(isec->getParent() == sec);
|
||||
if (isa<PotentialSpillSection>(isec))
|
||||
continue;
|
||||
const uint64_t pos = dot;
|
||||
dot = alignToPowerOf2(dot, isec->addralign);
|
||||
isec->outSecOff = dot - sec->addr;
|
||||
@ -1364,6 +1423,114 @@ const Defined *LinkerScript::assignAddresses() {
|
||||
return getChangedSymbolAssignment(oldValues);
|
||||
}
|
||||
|
||||
static bool hasRegionOverflowed(MemoryRegion *mr) {
|
||||
if (!mr)
|
||||
return false;
|
||||
return mr->curPos - mr->getOrigin() > mr->getLength();
|
||||
}
|
||||
|
||||
// Spill input sections in reverse order of address assignment to (potentially)
|
||||
// bring memory regions out of overflow. The size savings of a spill can only be
|
||||
// estimated, since general linker script arithmetic may occur afterwards.
|
||||
// Under-estimates may cause unnecessary spills, but over-estimates can always
|
||||
// be corrected on the next pass.
|
||||
bool LinkerScript::spillSections() {
|
||||
if (!config->enableNonContiguousRegions)
|
||||
return false;
|
||||
|
||||
bool spilled = false;
|
||||
for (SectionCommand *cmd : reverse(sectionCommands)) {
|
||||
auto *od = dyn_cast<OutputDesc>(cmd);
|
||||
if (!od)
|
||||
continue;
|
||||
OutputSection *osec = &od->osec;
|
||||
if (!osec->memRegion)
|
||||
continue;
|
||||
|
||||
// Input sections that have replaced a potential spill and should be removed
|
||||
// from their input section description.
|
||||
DenseSet<InputSection *> spilledInputSections;
|
||||
|
||||
for (SectionCommand *cmd : reverse(osec->commands)) {
|
||||
if (!hasRegionOverflowed(osec->memRegion) &&
|
||||
!hasRegionOverflowed(osec->lmaRegion))
|
||||
break;
|
||||
|
||||
auto *isd = dyn_cast<InputSectionDescription>(cmd);
|
||||
if (!isd)
|
||||
continue;
|
||||
for (InputSection *isec : reverse(isd->sections)) {
|
||||
// Potential spill locations cannot be spilled.
|
||||
if (isa<PotentialSpillSection>(isec))
|
||||
continue;
|
||||
|
||||
// Find the next potential spill location and remove it from the list.
|
||||
auto it = potentialSpillLists.find(isec);
|
||||
if (it == potentialSpillLists.end())
|
||||
continue;
|
||||
PotentialSpillList &list = it->second;
|
||||
PotentialSpillSection *spill = list.head;
|
||||
if (spill->next)
|
||||
list.head = spill->next;
|
||||
else
|
||||
potentialSpillLists.erase(isec);
|
||||
|
||||
// Replace the next spill location with the spilled section and adjust
|
||||
// its properties to match the new location. Note that the alignment of
|
||||
// the spill section may have diverged from the original due to e.g. a
|
||||
// SUBALIGN. Correct assignment requires the spill's alignment to be
|
||||
// used, not the original.
|
||||
spilledInputSections.insert(isec);
|
||||
*llvm::find(spill->isd->sections, spill) = isec;
|
||||
isec->parent = spill->parent;
|
||||
isec->addralign = spill->addralign;
|
||||
|
||||
// Record the (potential) reduction in the region's end position.
|
||||
osec->memRegion->curPos -= isec->getSize();
|
||||
if (osec->lmaRegion)
|
||||
osec->lmaRegion->curPos -= isec->getSize();
|
||||
|
||||
// Spilling continues until the end position no longer overflows the
|
||||
// region. Then, another round of address assignment will either confirm
|
||||
// the spill's success or lead to yet more spilling.
|
||||
if (!hasRegionOverflowed(osec->memRegion) &&
|
||||
!hasRegionOverflowed(osec->lmaRegion))
|
||||
break;
|
||||
}
|
||||
|
||||
// Remove any spilled input sections to complete their move.
|
||||
if (!spilledInputSections.empty()) {
|
||||
spilled = true;
|
||||
llvm::erase_if(isd->sections, [&](InputSection *isec) {
|
||||
return spilledInputSections.contains(isec);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return spilled;
|
||||
}
|
||||
|
||||
// Erase any potential spill sections that were not used.
|
||||
void LinkerScript::erasePotentialSpillSections() {
|
||||
if (potentialSpillLists.empty())
|
||||
return;
|
||||
|
||||
// Collect the set of input section descriptions that contain potential
|
||||
// spills.
|
||||
DenseSet<InputSectionDescription *> isds;
|
||||
for (const auto &[_, list] : potentialSpillLists)
|
||||
for (PotentialSpillSection *s = list.head; s; s = s->next)
|
||||
isds.insert(s->isd);
|
||||
|
||||
for (InputSectionDescription *isd : isds)
|
||||
llvm::erase_if(isd->sections, [](InputSection *s) {
|
||||
return isa<PotentialSpillSection>(s);
|
||||
});
|
||||
|
||||
potentialSpillLists.clear();
|
||||
}
|
||||
|
||||
// Creates program headers as instructed by PHDRS linker script command.
|
||||
SmallVector<PhdrEntry *, 0> LinkerScript::createPhdrs() {
|
||||
SmallVector<PhdrEntry *, 0> ret;
|
||||
|
@ -10,6 +10,7 @@
|
||||
#define LLD_ELF_LINKER_SCRIPT_H
|
||||
|
||||
#include "Config.h"
|
||||
#include "InputSection.h"
|
||||
#include "Writer.h"
|
||||
#include "lld/Common/LLVM.h"
|
||||
#include "lld/Common/Strings.h"
|
||||
@ -287,7 +288,8 @@ class LinkerScript final {
|
||||
|
||||
SmallVector<InputSectionBase *, 0>
|
||||
computeInputSections(const InputSectionDescription *,
|
||||
ArrayRef<InputSectionBase *>);
|
||||
ArrayRef<InputSectionBase *>,
|
||||
const OutputSection &outCmd);
|
||||
|
||||
SmallVector<InputSectionBase *, 0> createInputSectionList(OutputSection &cmd);
|
||||
|
||||
@ -333,6 +335,8 @@ public:
|
||||
|
||||
bool shouldKeep(InputSectionBase *s);
|
||||
const Defined *assignAddresses();
|
||||
bool spillSections();
|
||||
void erasePotentialSpillSections();
|
||||
void allocateHeaders(SmallVector<PhdrEntry *, 0> &phdrs);
|
||||
void processSectionCommands();
|
||||
void processSymbolAssignments();
|
||||
@ -400,6 +404,15 @@ public:
|
||||
//
|
||||
// then provideMap should contain the mapping: 'v' -> ['a', 'b', 'c']
|
||||
llvm::MapVector<StringRef, SmallVector<StringRef, 0>> provideMap;
|
||||
|
||||
// List of potential spill locations (PotentialSpillSection) for an input
|
||||
// section.
|
||||
struct PotentialSpillList {
|
||||
// Never nullptr.
|
||||
PotentialSpillSection *head;
|
||||
PotentialSpillSection *tail;
|
||||
};
|
||||
llvm::DenseMap<InputSectionBase *, PotentialSpillList> potentialSpillLists;
|
||||
};
|
||||
|
||||
struct ScriptWrapper {
|
||||
|
@ -197,6 +197,9 @@ def emit_relocs: F<"emit-relocs">, HelpText<"Generate relocations in output">;
|
||||
def enable_new_dtags: F<"enable-new-dtags">,
|
||||
HelpText<"Enable new dynamic tags (default)">;
|
||||
|
||||
def enable_non_contiguous_regions : FF<"enable-non-contiguous-regions">,
|
||||
HelpText<"Spill input sections to later matching output sections to avoid memory region overflow">;
|
||||
|
||||
def end_group: F<"end-group">,
|
||||
HelpText<"Ignored for compatibility with GNU unless you pass --warn-backrefs">;
|
||||
|
||||
|
@ -186,7 +186,7 @@ static MergeSyntheticSection *createMergeSynthetic(StringRef name,
|
||||
// new synthetic sections at the location of the first input section
|
||||
// that it replaces. It then finalizes each synthetic section in order
|
||||
// to compute an output offset for each piece of each input section.
|
||||
void OutputSection::finalizeInputSections() {
|
||||
void OutputSection::finalizeInputSections(LinkerScript *script) {
|
||||
std::vector<MergeSyntheticSection *> mergeSections;
|
||||
for (SectionCommand *cmd : commands) {
|
||||
auto *isd = dyn_cast<InputSectionDescription>(cmd);
|
||||
@ -226,6 +226,11 @@ void OutputSection::finalizeInputSections() {
|
||||
i = std::prev(mergeSections.end());
|
||||
syn->entsize = ms->entsize;
|
||||
isd->sections.push_back(syn);
|
||||
// The merge synthetic section inherits the potential spill locations of
|
||||
// its first contained section.
|
||||
auto it = script->potentialSpillLists.find(ms);
|
||||
if (it != script->potentialSpillLists.end())
|
||||
script->potentialSpillLists.try_emplace(syn, it->second);
|
||||
}
|
||||
(*i)->addSection(ms);
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ public:
|
||||
|
||||
void recordSection(InputSectionBase *isec);
|
||||
void commitSection(InputSection *isec);
|
||||
void finalizeInputSections();
|
||||
void finalizeInputSections(LinkerScript *script = nullptr);
|
||||
|
||||
// The following members are normally only used in linker scripts.
|
||||
MemoryRegion *memRegion = nullptr;
|
||||
|
@ -4074,6 +4074,13 @@ static bool isDuplicateArmExidxSec(InputSection *prev, InputSection *cur) {
|
||||
// InputSection with the highest address and any InputSections that have
|
||||
// mergeable .ARM.exidx table entries are removed from it.
|
||||
void ARMExidxSyntheticSection::finalizeContents() {
|
||||
// Ensure that any fixed-point iterations after the first see the original set
|
||||
// of sections.
|
||||
if (!originalExecutableSections.empty())
|
||||
executableSections = originalExecutableSections;
|
||||
else if (config->enableNonContiguousRegions)
|
||||
originalExecutableSections = executableSections;
|
||||
|
||||
// The executableSections and exidxSections that we use to derive the final
|
||||
// contents of this SyntheticSection are populated before
|
||||
// processSectionCommands() and ICF. A /DISCARD/ entry in SECTIONS command or
|
||||
|
@ -1255,6 +1255,10 @@ private:
|
||||
// either find the .ARM.exidx section or know that we need to generate one.
|
||||
SmallVector<InputSection *, 0> executableSections;
|
||||
|
||||
// Value of executableSecitons before finalizeContents(), so that it can be
|
||||
// run repeateadly during fixed point iteration.
|
||||
SmallVector<InputSection *, 0> originalExecutableSections;
|
||||
|
||||
// The executable InputSection with the highest address to use for the
|
||||
// sentinel. We store separately from ExecutableSections as merging of
|
||||
// duplicate entries may mean this InputSection is removed from
|
||||
|
@ -1403,13 +1403,18 @@ template <class ELFT> void Writer<ELFT>::finalizeAddressDependentContent() {
|
||||
AArch64Err843419Patcher a64p;
|
||||
ARMErr657417Patcher a32p;
|
||||
script->assignAddresses();
|
||||
|
||||
// .ARM.exidx and SHF_LINK_ORDER do not require precise addresses, but they
|
||||
// do require the relative addresses of OutputSections because linker scripts
|
||||
// can assign Virtual Addresses to OutputSections that are not monotonically
|
||||
// increasing.
|
||||
for (Partition &part : partitions)
|
||||
finalizeSynthetic(part.armExidx.get());
|
||||
resolveShfLinkOrder();
|
||||
// increasing. Anything here must be repeatable, since spilling may change
|
||||
// section order.
|
||||
const auto finalizeOrderDependentContent = [this] {
|
||||
for (Partition &part : partitions)
|
||||
finalizeSynthetic(part.armExidx.get());
|
||||
resolveShfLinkOrder();
|
||||
};
|
||||
finalizeOrderDependentContent();
|
||||
|
||||
// Converts call x@GDPLT to call __tls_get_addr
|
||||
if (config->emachine == EM_HEXAGON)
|
||||
@ -1419,6 +1424,8 @@ template <class ELFT> void Writer<ELFT>::finalizeAddressDependentContent() {
|
||||
for (;;) {
|
||||
bool changed = target->needsThunks ? tc.createThunks(pass, outputSections)
|
||||
: target->relaxOnce(pass);
|
||||
bool spilled = script->spillSections();
|
||||
changed |= spilled;
|
||||
++pass;
|
||||
|
||||
// With Thunk Size much smaller than branch range we expect to
|
||||
@ -1464,6 +1471,9 @@ template <class ELFT> void Writer<ELFT>::finalizeAddressDependentContent() {
|
||||
" does not converge");
|
||||
break;
|
||||
}
|
||||
} else if (spilled) {
|
||||
// Spilling can change relative section order.
|
||||
finalizeOrderDependentContent();
|
||||
}
|
||||
}
|
||||
if (!config->relocatable)
|
||||
@ -1483,6 +1493,10 @@ template <class ELFT> void Writer<ELFT>::finalizeAddressDependentContent() {
|
||||
osec->name + " is not a multiple of alignment (" +
|
||||
Twine(osec->addralign) + ")");
|
||||
}
|
||||
|
||||
// Sizes are no longer allowed to grow, so all allowable spills have been
|
||||
// taken. Remove any leftover potential spills.
|
||||
script->erasePotentialSpillSections();
|
||||
}
|
||||
|
||||
// If Input Sections have been shrunk (basic block sections) then
|
||||
|
@ -197,3 +197,14 @@ the current location to a max-page-size boundary, ensuring that the next
|
||||
|
||||
LLD will insert ``.relro_padding`` immediately before the symbol assignment
|
||||
using ``DATA_SEGMENT_RELRO_END``.
|
||||
|
||||
Non-contiguous regions
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The flag ``--enable-non-contiguous-regions`` allows input sections to spill to
|
||||
later matches rather than causing the link to fail by overflowing a memory
|
||||
region. Unlike GNU ld, ``/DISCARD/`` only matches previously-unmatched sections
|
||||
(i.e., the flag does not affect it). Also, if a section fails to fit at any of
|
||||
its matches, the link fails instead of discarding the section. Accordingly, the
|
||||
GNU flag ``--enable-non-contiguous-regions-warnings`` is not implemented, as it
|
||||
exists to warn about such occurrences.
|
||||
|
@ -38,6 +38,12 @@ ELF Improvements
|
||||
* ``--debug-names`` is added to create a merged ``.debug_names`` index
|
||||
from input ``.debug_names`` sections. Type units are not handled yet.
|
||||
(`#86508 <https://github.com/llvm/llvm-project/pull/86508>`_)
|
||||
* ``--enable-non-contiguous-regions`` option allows automatically packing input
|
||||
sections into memory regions by automatically spilling to later matches if a
|
||||
region would overflow. This reduces the toil of manually packing regions
|
||||
(typical for embedded). It also makes full LTO feasible in such cases, since
|
||||
IR merging currently prevents the linker script from referring to input
|
||||
files. (`#90007 <https://github.com/llvm/llvm-project/pull/90007>`_)
|
||||
|
||||
Breaking changes
|
||||
----------------
|
||||
|
@ -222,6 +222,8 @@ segment header.
|
||||
Generate relocations in the output.
|
||||
.It Fl -enable-new-dtags
|
||||
Enable new dynamic tags.
|
||||
.It Fl -enable-non-contiguous-regions
|
||||
Spill input sections to later matching output sections to avoid memory region overflow.
|
||||
.It Fl -end-lib
|
||||
End a grouping of objects that should be treated as if they were together
|
||||
in an archive.
|
||||
|
@ -0,0 +1,55 @@
|
||||
## When spilling reorders input sections, the .ARM.exidx table is rebuilt using
|
||||
## the new order.
|
||||
|
||||
# REQUIRES: arm
|
||||
# RUN: rm -rf %t && split-file %s %t && cd %t
|
||||
# RUN: llvm-mc -filetype=obj -triple=armv7a-none-linux-gnueabi test.s -o test.o
|
||||
# RUN: ld.lld -T test.ld test.o -o test --enable-non-contiguous-regions
|
||||
# RUN: llvm-readobj -x .ARM.exidx test | FileCheck %s
|
||||
|
||||
# CHECK: 20000000 08849780 1c000000 10849880
|
||||
# CHECK-NEXT: 1c000000 01000000
|
||||
|
||||
#--- test.ld
|
||||
MEMORY {
|
||||
exidx : ORIGIN = 0, LENGTH = 32
|
||||
a : ORIGIN = 32, LENGTH = 4
|
||||
b : ORIGIN = 36, LENGTH = 4
|
||||
c : ORIGIN = 40, LENGTH = 4
|
||||
}
|
||||
|
||||
SECTIONS {
|
||||
.ARM.exidx : { *(.ARM.exidx) } >exidx
|
||||
.first_chance : { *(.text .text.f2) } >a
|
||||
.text.f1 : { *(.text.f1) } >b
|
||||
.last_chance : { *(.text.f2) } >c
|
||||
}
|
||||
|
||||
#--- test.s
|
||||
.syntax unified
|
||||
.section .text, "ax",%progbits
|
||||
.globl _start
|
||||
_start:
|
||||
.fnstart
|
||||
bx lr
|
||||
.save {r7, lr}
|
||||
.setfp r7, sp, #0
|
||||
.fnend
|
||||
|
||||
.section .text.f1, "ax", %progbits
|
||||
.globl f1
|
||||
f1:
|
||||
.fnstart
|
||||
bx lr
|
||||
.save {r8, lr}
|
||||
.setfp r8, sp, #0
|
||||
.fnend
|
||||
|
||||
.section .text.f2, "ax", %progbits
|
||||
.globl f2
|
||||
f2:
|
||||
.fnstart
|
||||
bx lr
|
||||
.save {r8, lr}
|
||||
.setfp r8, sp, #0
|
||||
.fnend
|
265
lld/test/ELF/linkerscript/enable-non-contiguous-regions.test
Normal file
265
lld/test/ELF/linkerscript/enable-non-contiguous-regions.test
Normal file
@ -0,0 +1,265 @@
|
||||
# REQUIRES: x86
|
||||
|
||||
# RUN: rm -rf %t && split-file %s %t && cd %t
|
||||
# RUN: llvm-mc -n -filetype=obj -triple=x86_64 spill.s -o spill.o
|
||||
|
||||
## An input section spills to a later match when the region of its first match
|
||||
## would overflow. The spill uses the alignment of the later match.
|
||||
|
||||
# RUN: ld.lld -T spill.ld spill.o -o spill --enable-non-contiguous-regions
|
||||
# RUN: llvm-readelf -S spill | FileCheck %s --check-prefix=SPILL
|
||||
|
||||
# SPILL: Name Type Address Off Size
|
||||
# SPILL: .first_chance PROGBITS 0000000000000000 001000 000001
|
||||
# SPILL-NEXT: .last_chance PROGBITS 0000000000000008 001008 000002
|
||||
|
||||
## A spill off the end still fails the link.
|
||||
|
||||
# RUN: not ld.lld -T spill-fail.ld spill.o --enable-non-contiguous-regions 2>&1 |\
|
||||
# RUN: FileCheck %s --check-prefix=SPILL-FAIL --implicit-check-not=error:
|
||||
|
||||
# SPILL-FAIL: error: section '.last_chance' will not fit in region 'b': overflowed by 2 bytes
|
||||
|
||||
## The above spill still occurs when the LMA would overflow, even though the
|
||||
## VMA would fit.
|
||||
|
||||
# RUN: ld.lld -T spill-lma.ld spill.o -o spill-lma --enable-non-contiguous-regions
|
||||
# RUN: llvm-readelf -S spill-lma | FileCheck %s --check-prefix=SPILL-LMA
|
||||
|
||||
# SPILL-LMA: Name Type Address Off Size
|
||||
# SPILL-LMA: .first_chance PROGBITS 0000000000000000 001000 000001
|
||||
# SPILL-LMA-NEXT: .last_chance PROGBITS 0000000000000003 001003 000002
|
||||
|
||||
## A spill occurs to an additional match after the first.
|
||||
|
||||
# RUN: ld.lld -T spill-later.ld spill.o -o spill-later --enable-non-contiguous-regions
|
||||
# RUN: llvm-readelf -S spill-later | FileCheck %s --check-prefix=SPILL-LATER
|
||||
|
||||
# SPILL-LATER: Name Type Address Off Size
|
||||
# SPILL-LATER: .first_chance PROGBITS 0000000000000000 001000 000001
|
||||
# SPILL-LATER-NEXT: .second_chance PROGBITS 0000000000000002 001001 000000
|
||||
# SPILL-LATER-NEXT: .last_chance PROGBITS 0000000000000003 001003 000002
|
||||
|
||||
## A later overflow causes an earlier section to spill.
|
||||
|
||||
# RUN: ld.lld -T spill-earlier.ld spill.o -o spill-earlier --enable-non-contiguous-regions
|
||||
# RUN: llvm-readelf -S spill-earlier | FileCheck %s --check-prefix=SPILL-EARLIER
|
||||
|
||||
# SPILL-EARLIER: Name Type Address Off Size
|
||||
# SPILL-EARLIER: .first_chance PROGBITS 0000000000000000 001000 000002
|
||||
# SPILL-EARLIER-NEXT: .last_chance PROGBITS 0000000000000002 001002 000001
|
||||
|
||||
## An additional match in /DISCARD/ has no effect.
|
||||
|
||||
# RUN: not ld.lld -T no-spill-into-discard.ld spill.o --enable-non-contiguous-regions 2>&1 |\
|
||||
# RUN: FileCheck %s --check-prefix=NO-SPILL-INTO-DISCARD --implicit-check-not=error:
|
||||
|
||||
# NO-SPILL-INTO-DISCARD: error: section '.osec' will not fit in region 'a': overflowed by 1 bytes
|
||||
|
||||
## An additional match after /DISCARD/ has no effect.
|
||||
|
||||
# RUN: ld.lld -T no-spill-from-discard.ld spill.o -o no-spill-from-discard --enable-non-contiguous-regions
|
||||
# RUN: llvm-readelf -S no-spill-from-discard | FileCheck %s --check-prefix=NO-SPILL-FROM-DISCARD
|
||||
|
||||
# NO-SPILL-FROM-DISCARD: Name Type Address Off Size
|
||||
# NO-SPILL-FROM-DISCARD-NOT: .osec
|
||||
|
||||
## SHF_MERGEd sections are spilled according to the matches of the first merged
|
||||
## input section (the one giving the resulting section its name).
|
||||
|
||||
# RUN: llvm-mc -n -filetype=obj -triple=x86_64 merge.s -o merge.o
|
||||
# RUN: ld.lld -T spill-merge.ld merge.o -o spill-merge --enable-non-contiguous-regions
|
||||
# RUN: llvm-readelf -S spill-merge | FileCheck %s --check-prefix=SPILL-MERGE
|
||||
|
||||
# SPILL-MERGE: Name Type Address Off Size
|
||||
# SPILL-MERGE: .first PROGBITS 0000000000000000 000190 000000
|
||||
# SPILL-MERGE-NEXT: .second PROGBITS 0000000000000001 001001 000002
|
||||
# SPILL-MERGE-NEXT: .third PROGBITS 0000000000000003 001003 000000
|
||||
|
||||
## An error is reported for INSERT.
|
||||
|
||||
# RUN: not ld.lld -T insert.ld spill.o --enable-non-contiguous-regions 2>&1 |\
|
||||
# RUN: FileCheck %s --check-prefix=INSERT
|
||||
|
||||
# INSERT: error: INSERT cannot be used with --enable-non-contiguous-regions
|
||||
|
||||
## An error is reported for OVERWRITE_SECTIONS.
|
||||
|
||||
# RUN: not ld.lld -T overwrite-sections.ld spill.o --enable-non-contiguous-regions 2>&1 |\
|
||||
# RUN: FileCheck %s --check-prefix=OVERWRITE_SECTIONS
|
||||
|
||||
# OVERWRITE_SECTIONS: error: OVERWRITE_SECTIONS cannot be used with --enable-non-contiguous-regions
|
||||
|
||||
## SHF_LINK_ORDER is reordered when spilling changes relative section order.
|
||||
|
||||
# RUN: llvm-mc -n -filetype=obj -triple=x86_64 link-order.s -o link-order.o
|
||||
# RUN: ld.lld -T link-order.ld link-order.o -o link-order --enable-non-contiguous-regions
|
||||
# RUN: llvm-readobj -x .order link-order | FileCheck %s --check-prefix=LINK-ORDER
|
||||
|
||||
# LINK-ORDER: 020301
|
||||
|
||||
#--- spill.s
|
||||
.section .one_byte_section,"a",@progbits
|
||||
.fill 1
|
||||
|
||||
.section .two_byte_section,"a",@progbits
|
||||
.fill 2
|
||||
|
||||
#--- spill.ld
|
||||
MEMORY {
|
||||
a : ORIGIN = 0, LENGTH = 2
|
||||
b : ORIGIN = 2, LENGTH = 16
|
||||
}
|
||||
|
||||
SECTIONS {
|
||||
.first_chance : SUBALIGN(1) { *(.one_byte_section) *(.two_byte_section) } >a
|
||||
.last_chance : SUBALIGN(8) { *(.two_byte_section) } >b
|
||||
}
|
||||
|
||||
#--- spill-fail.ld
|
||||
MEMORY {
|
||||
a : ORIGIN = 0, LENGTH = 1
|
||||
b : ORIGIN = 2, LENGTH = 0
|
||||
}
|
||||
|
||||
SECTIONS {
|
||||
.first_chance : { *(.one_byte_section) *(.two_byte_section) } >a
|
||||
.last_chance : { *(.two_byte_section) } >b
|
||||
}
|
||||
|
||||
#--- spill-lma.ld
|
||||
MEMORY {
|
||||
vma_a : ORIGIN = 0, LENGTH = 3
|
||||
vma_b : ORIGIN = 3, LENGTH = 3
|
||||
lma_a : ORIGIN = 6, LENGTH = 2
|
||||
lma_b : ORIGIN = 8, LENGTH = 2
|
||||
}
|
||||
|
||||
SECTIONS {
|
||||
.first_chance : { *(.one_byte_section) *(.two_byte_section) } >vma_a AT>lma_a
|
||||
.last_chance : { *(.two_byte_section) } >vma_b AT>lma_b
|
||||
}
|
||||
|
||||
#--- spill-later.ld
|
||||
MEMORY {
|
||||
a : ORIGIN = 0, LENGTH = 2
|
||||
b : ORIGIN = 2, LENGTH = 1
|
||||
c : ORIGIN = 3, LENGTH = 2
|
||||
}
|
||||
|
||||
SECTIONS {
|
||||
.first_chance : { *(.one_byte_section) *(.two_byte_section) } >a
|
||||
.second_chance : { *(.two_byte_section) } >b
|
||||
.last_chance : { *(.two_byte_section) } >c
|
||||
}
|
||||
|
||||
#--- spill-earlier.ld
|
||||
MEMORY {
|
||||
a : ORIGIN = 0, LENGTH = 2
|
||||
b : ORIGIN = 2, LENGTH = 1
|
||||
}
|
||||
|
||||
SECTIONS {
|
||||
.first_chance : { *(.one_byte_section) *(.two_byte_section) } >a
|
||||
.last_chance : { *(.one_byte_section) } >b
|
||||
}
|
||||
|
||||
#--- no-spill-into-discard.ld
|
||||
MEMORY {
|
||||
a : ORIGIN = 0, LENGTH = 1
|
||||
}
|
||||
|
||||
SECTIONS {
|
||||
.osec : { *(.two_byte_section) } >a
|
||||
/DISCARD/ : { *(.one_byte_section) *(.two_byte_section) }
|
||||
}
|
||||
|
||||
#--- no-spill-from-discard.ld
|
||||
MEMORY {
|
||||
a : ORIGIN = 0, LENGTH = 2
|
||||
}
|
||||
|
||||
SECTIONS {
|
||||
/DISCARD/ : { *(.one_byte_section) *(.two_byte_section) }
|
||||
.osec : { *(.two_byte_section) } >a
|
||||
}
|
||||
|
||||
#--- merge.s
|
||||
.section .a,"aM",@progbits,1
|
||||
.byte 0x12, 0x34
|
||||
|
||||
.section .b,"aM",@progbits,1
|
||||
.byte 0x12
|
||||
|
||||
#--- spill-merge.ld
|
||||
MEMORY {
|
||||
a : ORIGIN = 0, LENGTH = 1
|
||||
b : ORIGIN = 1, LENGTH = 2
|
||||
c : ORIGIN = 3, LENGTH = 2
|
||||
}
|
||||
|
||||
SECTIONS {
|
||||
.first : { *(.a) *(.b) } >a
|
||||
.second : { *(.a) } >b
|
||||
.third : { *(.b) } >c
|
||||
}
|
||||
|
||||
#--- insert.ld
|
||||
MEMORY {
|
||||
a : ORIGIN = 0, LENGTH = 1
|
||||
}
|
||||
|
||||
SECTIONS {
|
||||
.a : { *(.two_byte_section) } >a
|
||||
}
|
||||
|
||||
SECTIONS {
|
||||
.b : { *(.one_byte_section) } >a
|
||||
} INSERT AFTER .a;
|
||||
|
||||
#--- overwrite-sections.ld
|
||||
MEMORY {
|
||||
a : ORIGIN = 0, LENGTH = 1
|
||||
}
|
||||
|
||||
SECTIONS {
|
||||
.a : { *(.two_byte_section) } >a
|
||||
}
|
||||
|
||||
OVERWRITE_SECTIONS {
|
||||
.b : { *(.one_byte_section) } >a
|
||||
}
|
||||
|
||||
#--- link-order.s
|
||||
.section .a,"a",@progbits
|
||||
.fill 1
|
||||
|
||||
.section .b,"a",@progbits
|
||||
.fill 1
|
||||
|
||||
.section .c,"a",@progbits
|
||||
.fill 1
|
||||
|
||||
.section .link_order.a,"ao",@progbits,.a
|
||||
.byte 1
|
||||
|
||||
.section .link_order.b,"ao",@progbits,.b
|
||||
.byte 2
|
||||
|
||||
.section .link_order.c,"ao",@progbits,.c
|
||||
.byte 3
|
||||
|
||||
#--- link-order.ld
|
||||
MEMORY {
|
||||
order : ORIGIN = 0, LENGTH = 3
|
||||
potential_a : ORIGIN = 3, LENGTH = 0
|
||||
bc : ORIGIN = 3, LENGTH = 2
|
||||
actual_a : ORIGIN = 5, LENGTH = 1
|
||||
}
|
||||
|
||||
SECTIONS {
|
||||
.order : { *(.link_order.*) } > order
|
||||
.potential_a : { *(.a) } >potential_a
|
||||
.bc : { *(.b) *(.c) } >bc
|
||||
.actual_a : { *(.a) } >actual_a
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user