[LLD] Add CLASS syntax to SECTIONS (#95323)

This allows the input section matching algorithm to be separated from
output section descriptions. This allows a group of sections to be
assigned to multiple output sections, providing an explicit version of
--enable-non-contiguous-regions's spilling that doesn't require altering
global linker script matching behavior with a flag. It also makes the
linker script language more expressive even if spilling is not intended,
since input section matching can be done in a different order than
sections are placed in an output section.

The implementation reuses the backend mechanism provided by
--enable-non-contiguous-regions, so it has roughly similar semantics and
limitations. In particular, sections cannot be spilled into or out of
INSERT, OVERWRITE_SECTIONS, or /DISCARD/. The former two aren't
intrinsic, so it may be possible to relax those restrictions later.
This commit is contained in:
Daniel Thornburgh
2024-08-05 13:06:45 -07:00
committed by GitHub
parent 88d288489e
commit 7e8a9020b1
10 changed files with 777 additions and 102 deletions

View File

@@ -277,6 +277,8 @@ getSymbolAssignmentValues(ArrayRef<SectionCommand *> sectionCommands) {
assign->sym->value));
continue;
}
if (isa<SectionClassDesc>(cmd))
continue;
for (SectionCommand *subCmd : cast<OutputDesc>(cmd)->osec.commands)
if (auto *assign = dyn_cast<SymbolAssignment>(subCmd))
if (assign->sym)
@@ -348,6 +350,8 @@ void LinkerScript::declareSymbols() {
declareSymbol(assign);
continue;
}
if (isa<SectionClassDesc>(cmd))
continue;
// If the output section directive has constraints,
// we can't say for sure if it is going to be included or not.
@@ -491,104 +495,136 @@ static void sortInputSections(MutableArrayRef<InputSectionBase *> vec,
SmallVector<InputSectionBase *, 0>
LinkerScript::computeInputSections(const InputSectionDescription *cmd,
ArrayRef<InputSectionBase *> sections,
const OutputSection &outCmd) {
const SectionBase &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)
ret[i] = sections[indexes[i]];
sortInputSections(
MutableArrayRef<InputSectionBase *>(ret).slice(begin, end - begin),
config->sortSection, SortSectionPolicy::None);
// Returns whether an input section's flags match the input section
// description's specifiers.
auto flagsMatch = [cmd](InputSectionBase *sec) {
return (sec->flags & cmd->withFlags) == cmd->withFlags &&
(sec->flags & cmd->withoutFlags) == 0;
};
// Collects all sections that satisfy constraints of Cmd.
size_t sizeAfterPrevSort = 0;
for (const SectionPattern &pat : cmd->sectionPatterns) {
size_t sizeBeforeCurrPat = ret.size();
if (cmd->classRef.empty()) {
DenseSet<size_t> seen;
size_t sizeAfterPrevSort = 0;
SmallVector<size_t, 0> indexes;
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)
ret[i] = sections[indexes[i]];
sortInputSections(
MutableArrayRef<InputSectionBase *>(ret).slice(begin, end - begin),
config->sortSection, SortSectionPolicy::None);
};
for (size_t i = 0, e = sections.size(); i != e; ++i) {
// 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() || seen.contains(i))
continue;
for (const SectionPattern &pat : cmd->sectionPatterns) {
size_t sizeBeforeCurrPat = ret.size();
// For --emit-relocs we have to ignore entries like
// .rela.dyn : { *(.rela.data) }
// which are common because they are in the default bfd script.
// We do not ignore SHT_REL[A] linker-synthesized sections here because
// want to support scripts that do custom layout for them.
if (isa<InputSection>(sec) &&
cast<InputSection>(sec)->getRelocatedSection())
continue;
// Check the name early to improve performance in the common case.
if (!pat.sectionPat.match(sec->name))
continue;
if (!cmd->matchesFile(sec->file) || pat.excludesFile(sec->file) ||
(sec->flags & cmd->withFlags) != cmd->withFlags ||
(sec->flags & cmd->withoutFlags) != 0)
continue;
if (sec->parent) {
// Skip if not allowing multiple matches.
if (!config->enableNonContiguousRegions)
for (size_t i = 0, e = sections.size(); i != e; ++i) {
// 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() || seen.contains(i))
continue;
// Disallow spilling into /DISCARD/; special handling would be needed
// for this in address assignment, and the semantics are nebulous.
if (outCmd.name == "/DISCARD/")
// For --emit-relocs we have to ignore entries like
// .rela.dyn : { *(.rela.data) }
// which are common because they are in the default bfd script.
// We do not ignore SHT_REL[A] linker-synthesized sections here because
// want to support scripts that do custom layout for them.
if (isa<InputSection>(sec) &&
cast<InputSection>(sec)->getRelocatedSection())
continue;
// Skip if the section's first match was /DISCARD/; such sections are
// always discarded.
if (sec->parent->name == "/DISCARD/")
// Check the name early to improve performance in the common case.
if (!pat.sectionPat.match(sec->name))
continue;
// Skip if the section was already matched by a different input section
// description within this output section.
if (sec->parent == &outCmd)
if (!cmd->matchesFile(sec->file) || pat.excludesFile(sec->file) ||
sec->parent == &outCmd || !flagsMatch(sec))
continue;
spills.insert(sec);
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;
// Class definitions cannot contain spills, nor can a class definition
// generate a spill in a subsequent match. Those behaviors belong to
// class references and additional matches.
if (!isa<SectionClass>(outCmd) && !isa<SectionClass>(sec->parent))
spills.insert(sec);
}
ret.push_back(sec);
indexes.push_back(i);
seen.insert(i);
}
ret.push_back(sec);
indexes.push_back(i);
seen.insert(i);
if (pat.sortOuter == SortSectionPolicy::Default)
continue;
// Matched sections are ordered by radix sort with the keys being (SORT*,
// --sort-section, input order), where SORT* (if present) is most
// significant.
//
// Matched sections between the previous SORT* and this SORT* are sorted
// by (--sort-alignment, input order).
sortByPositionThenCommandLine(sizeAfterPrevSort, sizeBeforeCurrPat);
// Matched sections by this SORT* pattern are sorted using all 3 keys.
// ret[sizeBeforeCurrPat,ret.size()) are already in the input order, so we
// just sort by sortOuter and sortInner.
sortInputSections(
MutableArrayRef<InputSectionBase *>(ret).slice(sizeBeforeCurrPat),
pat.sortOuter, pat.sortInner);
sizeAfterPrevSort = ret.size();
}
if (pat.sortOuter == SortSectionPolicy::Default)
continue;
// Matched sections after the last SORT* are sorted by (--sort-alignment,
// input order).
sortByPositionThenCommandLine(sizeAfterPrevSort, ret.size());
} else {
SectionClassDesc *scd =
script->sectionClasses.lookup(CachedHashStringRef(cmd->classRef));
if (!scd) {
errorOrWarn("undefined section class '" + cmd->classRef + "'");
return ret;
}
if (!scd->sc.assigned) {
errorOrWarn("section class '" + cmd->classRef + "' referenced by '" +
outCmd.name + "' before class definition");
return ret;
}
// Matched sections are ordered by radix sort with the keys being (SORT*,
// --sort-section, input order), where SORT* (if present) is most
// significant.
//
// Matched sections between the previous SORT* and this SORT* are sorted by
// (--sort-alignment, input order).
sortByPositionThenCommandLine(sizeAfterPrevSort, sizeBeforeCurrPat);
// Matched sections by this SORT* pattern are sorted using all 3 keys.
// ret[sizeBeforeCurrPat,ret.size()) are already in the input order, so we
// just sort by sortOuter and sortInner.
sortInputSections(
MutableArrayRef<InputSectionBase *>(ret).slice(sizeBeforeCurrPat),
pat.sortOuter, pat.sortInner);
sizeAfterPrevSort = ret.size();
for (InputSectionDescription *isd : scd->sc.commands) {
for (InputSectionBase *sec : isd->sectionBases) {
if (sec->parent == &outCmd || !flagsMatch(sec))
continue;
bool isSpill = sec->parent && isa<OutputSection>(sec->parent);
if (!sec->parent || (isSpill && outCmd.name == "/DISCARD/")) {
errorOrWarn("section '" + sec->name +
"' cannot spill from/to /DISCARD/");
continue;
}
if (isSpill)
spills.insert(sec);
ret.push_back(sec);
}
}
}
// 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.
// The flag --enable-non-contiguous-regions or the section CLASS syntax 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))
@@ -708,7 +744,7 @@ void LinkerScript::processSectionCommands() {
!map.try_emplace(CachedHashStringRef(osec->name), osd).second)
warn("OVERWRITE_SECTIONS specifies duplicate " + osec->name);
}
for (SectionCommand *&base : sectionCommands)
for (SectionCommand *&base : sectionCommands) {
if (auto *osd = dyn_cast<OutputDesc>(base)) {
OutputSection *osec = &osd->osec;
if (OutputDesc *overwrite = map.lookup(CachedHashStringRef(osec->name))) {
@@ -718,7 +754,50 @@ void LinkerScript::processSectionCommands() {
} else if (process(osec)) {
osec->sectionIndex = i++;
}
} else if (auto *sc = dyn_cast<SectionClassDesc>(base)) {
for (InputSectionDescription *isd : sc->sc.commands) {
isd->sectionBases =
computeInputSections(isd, ctx.inputSections, sc->sc);
for (InputSectionBase *s : isd->sectionBases) {
// A section class containing a section with different parent isn't
// necessarily an error due to --enable-non-contiguous-regions. Such
// sections all become potential spills when the class is referenced.
if (!s->parent)
s->parent = &sc->sc;
}
}
sc->sc.assigned = true;
}
}
// Check that input sections cannot spill into or out of INSERT,
// since the semantics are nebulous. This is also true for OVERWRITE_SECTIONS,
// but no check is needed, since the order of processing ensures they cannot
// legally reference classes.
if (!potentialSpillLists.empty()) {
DenseSet<StringRef> insertNames;
for (InsertCommand &ic : insertCommands)
insertNames.insert(ic.names.begin(), ic.names.end());
for (SectionCommand *&base : sectionCommands) {
auto *osd = dyn_cast<OutputDesc>(base);
if (!osd)
continue;
OutputSection *os = &osd->osec;
if (!insertNames.contains(os->name))
continue;
for (SectionCommand *sc : os->commands) {
auto *isd = dyn_cast<InputSectionDescription>(sc);
if (!isd)
continue;
for (InputSectionBase *isec : isd->sectionBases)
if (isa<PotentialSpillSection>(isec) ||
potentialSpillLists.contains(isec))
errorOrWarn("section '" + isec->name +
"' cannot spill from/to INSERT section '" + os->name +
"'");
}
}
}
// If an OVERWRITE_SECTIONS specified output section is not in
// sectionCommands, append it to the end. The section will be inserted by
@@ -726,6 +805,21 @@ void LinkerScript::processSectionCommands() {
for (OutputDesc *osd : overwriteSections)
if (osd->osec.partition == 1 && osd->osec.sectionIndex == UINT32_MAX)
sectionCommands.push_back(osd);
// Input sections cannot have a section class parent past this point; they
// must have been assigned to an output section.
for (const auto &[_, sc] : sectionClasses) {
for (InputSectionDescription *isd : sc->sc.commands) {
for (InputSectionBase *sec : isd->sectionBases) {
if (sec->parent && isa<SectionClass>(sec->parent)) {
errorOrWarn("section class '" + sec->parent->name +
"' is unreferenced");
goto nextClass;
}
}
}
nextClass:;
}
}
void LinkerScript::processSymbolAssignments() {
@@ -746,8 +840,8 @@ void LinkerScript::processSymbolAssignments() {
for (SectionCommand *cmd : sectionCommands) {
if (auto *assign = dyn_cast<SymbolAssignment>(cmd))
addSymbol(assign);
else
for (SectionCommand *subCmd : cast<OutputDesc>(cmd)->osec.commands)
else if (auto *osd = dyn_cast<OutputDesc>(cmd))
for (SectionCommand *subCmd : osd->osec.commands)
if (auto *assign = dyn_cast<SymbolAssignment>(subCmd))
addSymbol(assign);
}
@@ -1417,6 +1511,8 @@ LinkerScript::assignAddresses() {
assign->size = dot - assign->addr;
continue;
}
if (isa<SectionClassDesc>(cmd))
continue;
if (assignOffsets(&cast<OutputDesc>(cmd)->osec) && !changedOsec)
changedOsec = &cast<OutputDesc>(cmd)->osec;
}
@@ -1437,15 +1533,15 @@ static bool hasRegionOverflowed(MemoryRegion *mr) {
// Under-estimates may cause unnecessary spills, but over-estimates can always
// be corrected on the next pass.
bool LinkerScript::spillSections() {
if (!config->enableNonContiguousRegions)
if (potentialSpillLists.empty())
return false;
bool spilled = false;
for (SectionCommand *cmd : reverse(sectionCommands)) {
auto *od = dyn_cast<OutputDesc>(cmd);
if (!od)
auto *osd = dyn_cast<OutputDesc>(cmd);
if (!osd)
continue;
OutputSection *osec = &od->osec;
OutputSection *osec = &osd->osec;
if (!osec->memRegion)
continue;