From f41fe121b0a972ec4d0d8d87cd1d0453d3b91028 Mon Sep 17 00:00:00 2001 From: Daniel Thornburgh Date: Mon, 5 Aug 2024 13:06:45 -0700 Subject: [PATCH] [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. --- lld/ELF/InputSection.cpp | 2 + lld/ELF/InputSection.h | 6 +- lld/ELF/LinkerScript.cpp | 268 +++++++---- lld/ELF/LinkerScript.h | 24 +- lld/ELF/MapFile.cpp | 2 + lld/ELF/OutputSections.h | 19 + lld/ELF/ScriptParser.cpp | 57 ++- lld/docs/ELF/linker_script.rst | 53 ++- lld/docs/ReleaseNotes.rst | 6 + lld/test/ELF/linkerscript/section-class.test | 448 +++++++++++++++++++ 10 files changed, 780 insertions(+), 105 deletions(-) create mode 100644 lld/test/ELF/linkerscript/section-class.test diff --git a/lld/ELF/InputSection.cpp b/lld/ELF/InputSection.cpp index da4c90516ec3000..cf34a814fed4154 100644 --- a/lld/ELF/InputSection.cpp +++ b/lld/ELF/InputSection.cpp @@ -194,6 +194,8 @@ uint64_t SectionBase::getOffset(uint64_t offset) const { // For output sections we treat offset -1 as the end of the section. return offset == uint64_t(-1) ? os->size : offset; } + case Class: + llvm_unreachable("section classes do not have offsets"); case Regular: case Synthetic: case Spill: diff --git a/lld/ELF/InputSection.h b/lld/ELF/InputSection.h index 6659530a9c9c26e..e3b7af13d066da9 100644 --- a/lld/ELF/InputSection.h +++ b/lld/ELF/InputSection.h @@ -61,7 +61,7 @@ template struct RelsOrRelas { // sections. class SectionBase { public: - enum Kind { Regular, Synthetic, Spill, EHFrame, Merge, Output }; + enum Kind { Regular, Synthetic, Spill, EHFrame, Merge, Output, Class }; Kind kind() const { return (Kind)sectionKind; } @@ -148,7 +148,9 @@ class InputSectionBase : public SectionBase { uint32_t addralign, ArrayRef data, StringRef name, Kind sectionKind); - static bool classof(const SectionBase *s) { return s->kind() != Output; } + static bool classof(const SectionBase *s) { + return s->kind() != Output && s->kind() != Class; + } // The file which contains this section. Its dynamic type is usually // ObjFile, but may be an InputFile of InternalKind (for a synthetic diff --git a/lld/ELF/LinkerScript.cpp b/lld/ELF/LinkerScript.cpp index 577731164076b1f..0c4ba1abb4778c2 100644 --- a/lld/ELF/LinkerScript.cpp +++ b/lld/ELF/LinkerScript.cpp @@ -277,6 +277,8 @@ getSymbolAssignmentValues(ArrayRef sectionCommands) { assign->sym->value)); continue; } + if (isa(cmd)) + continue; for (SectionCommand *subCmd : cast(cmd)->osec.commands) if (auto *assign = dyn_cast(subCmd)) if (assign->sym) @@ -348,6 +350,8 @@ void LinkerScript::declareSymbols() { declareSymbol(assign); continue; } + if (isa(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 vec, SmallVector LinkerScript::computeInputSections(const InputSectionDescription *cmd, ArrayRef sections, - const OutputSection &outCmd) { + const SectionBase &outCmd) { SmallVector ret; - SmallVector indexes; - DenseSet seen; DenseSet spills; - auto sortByPositionThenCommandLine = [&](size_t begin, size_t end) { - llvm::sort(MutableArrayRef(indexes).slice(begin, end - begin)); - for (size_t i = begin; i != end; ++i) - ret[i] = sections[indexes[i]]; - sortInputSections( - MutableArrayRef(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(); - - 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 --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(sec) && - cast(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) + if (cmd->classRef.empty()) { + DenseSet seen; + size_t sizeAfterPrevSort = 0; + SmallVector indexes; + auto sortByPositionThenCommandLine = [&](size_t begin, size_t end) { + llvm::sort(MutableArrayRef(indexes).slice(begin, end - begin)); + for (size_t i = begin; i != end; ++i) + ret[i] = sections[indexes[i]]; + sortInputSections( + MutableArrayRef(ret).slice(begin, end - begin), + config->sortSection, SortSectionPolicy::None); + }; + + for (const SectionPattern &pat : cmd->sectionPatterns) { + 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 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(sec) && + cast(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(outCmd) && !isa(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(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(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(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(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(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 insertNames; + for (InsertCommand &ic : insertCommands) + insertNames.insert(ic.names.begin(), ic.names.end()); + for (SectionCommand *&base : sectionCommands) { + auto *osd = dyn_cast(base); + if (!osd) + continue; + OutputSection *os = &osd->osec; + if (!insertNames.contains(os->name)) + continue; + for (SectionCommand *sc : os->commands) { + auto *isd = dyn_cast(sc); + if (!isd) + continue; + for (InputSectionBase *isec : isd->sectionBases) + if (isa(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(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(cmd)) addSymbol(assign); - else - for (SectionCommand *subCmd : cast(cmd)->osec.commands) + else if (auto *osd = dyn_cast(cmd)) + for (SectionCommand *subCmd : osd->osec.commands) if (auto *assign = dyn_cast(subCmd)) addSymbol(assign); } @@ -1417,6 +1511,8 @@ LinkerScript::assignAddresses() { assign->size = dot - assign->addr; continue; } + if (isa(cmd)) + continue; if (assignOffsets(&cast(cmd)->osec) && !changedOsec) changedOsec = &cast(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(cmd); - if (!od) + auto *osd = dyn_cast(cmd); + if (!osd) continue; - OutputSection *osec = &od->osec; + OutputSection *osec = &osd->osec; if (!osec->memRegion) continue; diff --git a/lld/ELF/LinkerScript.h b/lld/ELF/LinkerScript.h index b86521a429f0444..90090ce16de547e 100644 --- a/lld/ELF/LinkerScript.h +++ b/lld/ELF/LinkerScript.h @@ -35,6 +35,8 @@ class OutputSection; class SectionBase; class ThunkSection; struct OutputDesc; +struct SectionClass; +struct SectionClassDesc; // This represents an r-value in the linker script. struct ExprValue { @@ -78,7 +80,8 @@ enum SectionsCommandKind { AssignmentKind, // . = expr or = expr OutputSectionKind, InputSectionKind, - ByteKind // BYTE(expr), SHORT(expr), LONG(expr) or QUAD(expr) + ByteKind, // BYTE(expr), SHORT(expr), LONG(expr) or QUAD(expr) + ClassKind, // CLASS(class_name) }; struct SectionCommand { @@ -198,9 +201,12 @@ class InputSectionDescription : public SectionCommand { public: InputSectionDescription(StringRef filePattern, uint64_t withFlags = 0, - uint64_t withoutFlags = 0) + uint64_t withoutFlags = 0, StringRef classRef = {}) : SectionCommand(InputSectionKind), filePat(filePattern), - withFlags(withFlags), withoutFlags(withoutFlags) {} + classRef(classRef), withFlags(withFlags), withoutFlags(withoutFlags) { + assert((filePattern.empty() || classRef.empty()) && + "file pattern and class reference are mutually exclusive"); + } static bool classof(const SectionCommand *c) { return c->kind == InputSectionKind; @@ -212,6 +218,10 @@ class InputSectionDescription : public SectionCommand { // will be associated with this InputSectionDescription. SmallVector sectionPatterns; + // If present, input section matching uses class membership instead of file + // and section patterns (mutually exclusive). + StringRef classRef; + // Includes InputSections and MergeInputSections. Used temporarily during // assignment of input sections to output sections. SmallVector sectionBases; @@ -298,8 +308,7 @@ class LinkerScript final { SmallVector computeInputSections(const InputSectionDescription *, - ArrayRef, - const OutputSection &outCmd); + ArrayRef, const SectionBase &outCmd); SmallVector createInputSectionList(OutputSection &cmd); @@ -429,6 +438,11 @@ class LinkerScript final { PotentialSpillSection *tail; }; llvm::DenseMap potentialSpillLists; + + // Named lists of input sections that can be collectively referenced in output + // section descriptions. Multiple references allow for sections to spill from + // one output section to another. + llvm::DenseMap sectionClasses; }; struct ScriptWrapper { diff --git a/lld/ELF/MapFile.cpp b/lld/ELF/MapFile.cpp index c4f3fdde30f36a7..1bad529b4032997 100644 --- a/lld/ELF/MapFile.cpp +++ b/lld/ELF/MapFile.cpp @@ -167,6 +167,8 @@ static void writeMapFile(raw_fd_ostream &os) { os << assign->commandString << '\n'; continue; } + if (isa(cmd)) + continue; osec = &cast(cmd)->osec; writeHeader(os, osec->addr, osec->getLMA(), osec->size, osec->addralign); diff --git a/lld/ELF/OutputSections.h b/lld/ELF/OutputSections.h index bf48f808fdbcc6e..909b284a75a2ef0 100644 --- a/lld/ELF/OutputSections.h +++ b/lld/ELF/OutputSections.h @@ -143,6 +143,25 @@ struct OutputDesc final : SectionCommand { } }; +// This represents a CLASS(class_name) { ... } that can be referenced by output +// section descriptions. If referenced more than once, the sections can be +// spilled to the next reference like --enable-non-contiguous-regions. +struct SectionClass final : public SectionBase { + SmallVector commands; + bool assigned = false; + + SectionClass(StringRef name) : SectionBase(Class, name, 0, 0, 0, 0, 0, 0) {} + static bool classof(const SectionBase *s) { return s->kind() == Class; } +}; + +struct SectionClassDesc : SectionCommand { + SectionClass sc; + + SectionClassDesc(StringRef name) : SectionCommand(ClassKind), sc(name) {} + + static bool classof(const SectionCommand *c) { return c->kind == ClassKind; } +}; + int getPriority(StringRef s); InputSection *getFirstInputSection(const OutputSection *os); diff --git a/lld/ELF/ScriptParser.cpp b/lld/ELF/ScriptParser.cpp index 107b4c6e7af067c..bdbce396cba1f81 100644 --- a/lld/ELF/ScriptParser.cpp +++ b/lld/ELF/ScriptParser.cpp @@ -87,6 +87,8 @@ class ScriptParser final : ScriptLexer { OutputDesc *readOverlaySectionDescription(); OutputDesc *readOutputSectionDescription(StringRef outSec); SmallVector readOverlay(); + SectionClassDesc *readSectionClassDescription(); + StringRef readSectionClassName(); SmallVector readOutputSectionPhdrs(); std::pair readInputSectionFlags(); InputSectionDescription *readInputSectionDescription(StringRef tok); @@ -605,6 +607,33 @@ SmallVector ScriptParser::readOverlay() { return v; } +SectionClassDesc *ScriptParser::readSectionClassDescription() { + StringRef name = readSectionClassName(); + SectionClassDesc *desc = make(name); + if (!script->sectionClasses.insert({CachedHashStringRef(name), desc}).second) + setError("section class '" + name + "' already defined"); + expect("{"); + while (auto tok = till("}")) { + if (tok == "(" || tok == ")") { + setError("expected filename pattern"); + } else if (peek() == "(") { + InputSectionDescription *isd = readInputSectionDescription(tok); + if (!isd->classRef.empty()) + setError("section class '" + name + "' references class '" + + isd->classRef + "'"); + desc->sc.commands.push_back(isd); + } + } + return desc; +} + +StringRef ScriptParser::readSectionClassName() { + expect("("); + StringRef name = unquote(next()); + expect(")"); + return name; +} + void ScriptParser::readOverwriteSections() { expect("{"); while (auto tok = till("}")) @@ -619,7 +648,12 @@ void ScriptParser::readSections() { for (SectionCommand *cmd : readOverlay()) v.push_back(cmd); continue; - } else if (tok == "INCLUDE") { + } + if (tok == "CLASS") { + v.push_back(readSectionClassDescription()); + continue; + } + if (tok == "INCLUDE") { readInclude(); continue; } @@ -822,8 +856,14 @@ ScriptParser::readInputSectionDescription(StringRef tok) { expect("("); if (consume("INPUT_SECTION_FLAGS")) std::tie(withFlags, withoutFlags) = readInputSectionFlags(); - InputSectionDescription *cmd = - readInputSectionRules(next(), withFlags, withoutFlags); + + tok = next(); + InputSectionDescription *cmd; + if (tok == "CLASS") + cmd = make(StringRef{}, withFlags, withoutFlags, + readSectionClassName()); + else + cmd = readInputSectionRules(tok, withFlags, withoutFlags); expect(")"); script->keptSections.push_back(cmd); return cmd; @@ -832,6 +872,9 @@ ScriptParser::readInputSectionDescription(StringRef tok) { std::tie(withFlags, withoutFlags) = readInputSectionFlags(); tok = next(); } + if (tok == "CLASS") + return make(StringRef{}, withFlags, withoutFlags, + readSectionClassName()); return readInputSectionRules(tok, withFlags, withoutFlags); } @@ -951,8 +994,12 @@ OutputDesc *ScriptParser::readOverlaySectionDescription() { std::tie(withFlags, withoutFlags) = readInputSectionFlags(); tok = till(""); } - osd->osec.commands.push_back( - readInputSectionRules(tok, withFlags, withoutFlags)); + if (tok == "CLASS") + osd->osec.commands.push_back(make( + StringRef{}, withFlags, withoutFlags, readSectionClassName())); + else + osd->osec.commands.push_back( + readInputSectionRules(tok, withFlags, withoutFlags)); } osd->osec.phdrs = readOutputSectionPhdrs(); return osd; diff --git a/lld/docs/ELF/linker_script.rst b/lld/docs/ELF/linker_script.rst index 7a35534be096c26..c9cb47fc0553e02 100644 --- a/lld/docs/ELF/linker_script.rst +++ b/lld/docs/ELF/linker_script.rst @@ -198,13 +198,52 @@ 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``. +Section Classes +~~~~~~~~~~~~~~~ + +The ``CLASS`` keyword inside a ``SECTIONS`` command defines classes of input +sections: + +:: + + SECTIONS { + CLASS(class_name) { + input-section-description + input-section-description + ... + } + } + +Input section descriptions refer to a class using ``CLASS(class_name)`` +instead of the usual filename and section name patterns. For example: + +:: + + SECTIONS { + CLASS(c) { *(.rodata.earlier) } + .rodata { *(.rodata) CLASS(c) (*.rodata.later) } + } + +Input sections that are assigned to a class are not matched by later patterns, +just as if they had been assigned to an earlier output section. If a class is +referenced in multiple output sections, when a memory region would overflow, +the linker spills input sections from a reference to later references rather +than failing the link. + +Classes cannot reference other classes; an input section is assigned to at most +one class. + +Sections cannot be specified to possibly spill into or out of +``INSERT [AFTER|BEFORE]``, ``OVERWRITE_SECTIONS``, or ``/DISCARD/``. + 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. +The flag ``--enable-non-contiguous-regions`` provides a version of the above +spilling functionality that is more compatible with GNU LD. It allows input +sections to spill to later pattern matches. (This globally changes the behavior +of patterns.) 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. diff --git a/lld/docs/ReleaseNotes.rst b/lld/docs/ReleaseNotes.rst index e9d3c12b7654508..6d09de10e7195e1 100644 --- a/lld/docs/ReleaseNotes.rst +++ b/lld/docs/ReleaseNotes.rst @@ -29,6 +29,12 @@ ELF Improvements * ``-z nosectionheader`` has been implemented to omit the section header table. The operation is similar to ``llvm-objcopy --strip-sections``. (`#101286 `_) +* Section ``CLASS`` linker script syntax binds input sections to named classes, + which are referenced later one or more times. This provides access to the + automatic spilling mechanism of `--enable-non-contiguous-regions` without + globally changing the semantics of section matching. It also independently + increases the expressive power of linker scripts. + (`#95323 `_) Breaking changes ---------------- diff --git a/lld/test/ELF/linkerscript/section-class.test b/lld/test/ELF/linkerscript/section-class.test new file mode 100644 index 000000000000000..7fce13bfe3e025d --- /dev/null +++ b/lld/test/ELF/linkerscript/section-class.test @@ -0,0 +1,448 @@ +# REQUIRES: x86 + +# RUN: rm -rf %t && split-file %s %t && cd %t + +#--- matching.s +.section .rodata.a,"a",@progbits +.byte 1 + +.section .rodata.b,"a",@progbits +.byte 2 + +.section .rodata.c,"ax",@progbits +.byte 3 + +.section .rodata.d,"a",@progbits +.byte 4 + +.section .rodata.e,"a",@progbits +.byte 5 + +.section .rodata.f,"a",@progbits +.balign 2 +.byte 6 + +.section .rodata.g,"a",@progbits +.byte 7 + +.section .rodata.h,"a",@progbits +.byte 8 + +# RUN: llvm-mc -n -filetype=obj -triple=x86_64 matching.s -o matching.o + +#--- matching.lds +## CLASS definitions match sections in linker script order. The sections may be +## placed in a different order. Classes may derive from one another. Class +## references can be restricted by INPUT_SECTION_FLAGS. Classes can be referenced +## in /DISCARD/ and INSERT. +SECTIONS { + CLASS(a) { *(.rodata.a) } + CLASS(cd) { *(.rodata.c) *(.rodata.d) } + CLASS(ef) { *(SORT_BY_ALIGNMENT(.rodata.e .rodata.f)) } + CLASS(g) { *(.rodata.g) } + CLASS("h)") { *(.rodata.h) } + .rodata : { + *(.rodata.*) + INPUT_SECTION_FLAGS(SHF_EXECINSTR) CLASS( cd) + CLASS(a)CLASS(ef ) + } + OVERLAY : { .rodata.d { INPUT_SECTION_FLAGS(!SHF_EXECINSTR) CLASS(cd) } } + /DISCARD/ : { CLASS(g) } +} + +SECTIONS { + .rodata.h : { CLASS("h)") } +} INSERT AFTER .rodata; + +# RUN: ld.lld -T matching.lds matching.o -o matching +# RUN: llvm-objdump -s matching |\ +# RUN: FileCheck %s --check-prefix=MATCHING +# MATCHING: .rodata +# MATCHING-NEXT: 020301cc 0605 ......{{$}} +# MATCHING: .rodata.h +# MATCHING-NEXT: 08 .{{$}} +# MATCHING: .rodata.d +# MATCHING-NEXT: 04 .{{$}} + +#--- already-defined.lds +## A section class has more than one description. +SECTIONS { + CLASS(a) { *(.rodata.a) } + CLASS(a) { *(.rodata.b) } + CLASS(b) { *(.rodata.c) } + CLASS(b) { *(.rodata.d) } +} + +# RUN: not ld.lld -T already-defined.lds matching.o 2>&1 | \ +# RUN: FileCheck %s --check-prefix=ALREADY-DEFINED --implicit-check-not=error: + +# ALREADY-DEFINED: error: already-defined.lds:4: section class 'a' already defined + +#--- missing-filename-pattern-1.lds +## A filename pattern is missing in a section class description. +SECTIONS { + CLASS(a) { (.rodata.a) } +} +#--- missing-filename-pattern-2.lds +## A filename pattern is missing in a section class description. +SECTIONS { + CLASS(a) { .rodata.a) } +} + +# RUN: not ld.lld -T missing-filename-pattern-1.lds matching.o 2>&1 | \ +# RUN: FileCheck %s --check-prefix=MISSING-FILENAME-PATTERN --implicit-check-not=error: +# RUN: not ld.lld -T missing-filename-pattern-2.lds matching.o 2>&1 | \ +# RUN: FileCheck %s --check-prefix=MISSING-FILENAME-PATTERN --implicit-check-not=error: + +# MISSING-FILENAME-PATTERN: error: missing-filename-pattern-{{[1-2]}}.lds:3: expected filename pattern + +#--- multiple-class-names.lds +## More than one class is mentioned in a reference. +SECTIONS { + CLASS(a) { *(.rodata.a) } + CLASS(b) { *(.rodata.b) } + .rodata : { CLASS(a b) } +} + +# RUN: not ld.lld -T multiple-class-names.lds matching.o 2>&1 | \ +# RUN: FileCheck %s --check-prefix=MULTIPLE-CLASS-NAMES --implicit-check-not=error: + +# MULTIPLE-CLASS-NAMES: error: multiple-class-names.lds:5: ) expected, but got b + +#--- undefined.lds +## A section class is referenced but never defined +SECTIONS { + .rodata : { CLASS(a) } +} + +# RUN: not ld.lld -T undefined.lds matching.o 2>&1 | \ +# RUN: FileCheck %s --check-prefix=UNDEFINED --implicit-check-not=error: + +# UNDEFINED: error: undefined section class 'a' + +#--- referenced-before-defined.lds +## The content of section classes is demanded before its definition is processed. +SECTIONS { + .rodata : { CLASS(a) } + CLASS(a) { *(.rodata.a) } +} + +# RUN: not ld.lld -T referenced-before-defined.lds matching.o 2>&1 | \ +# RUN: FileCheck %s --check-prefix=REFERENCED-BEFORE-DEFINED +# RUN: ld.lld -T referenced-before-defined.lds matching.o -o out --noinhibit-exec 2>&1 | \ +# RUN: FileCheck %s --check-prefix=REFERENCED-BEFORE-DEFINED-WARN + +# REFERENCED-BEFORE-DEFINED: error: section class 'a' referenced by '.rodata' before class definition +# REFERENCED-BEFORE-DEFINED-WARN: warning: section class 'a' referenced by '.rodata' before class definition + +#--- unreferenced.lds +## An input section is bound to a section class but is not referenced. +SECTIONS { + CLASS(a) { *(.rodata.*) } +} + +# RUN: not ld.lld -T unreferenced.lds matching.o 2>&1 | \ +# RUN: FileCheck %s --check-prefix=UNREFERENCED -implicit-check-not=error: +# RUN: ld.lld -T unreferenced.lds matching.o -o out --noinhibit-exec 2>&1 | \ +# RUN: FileCheck %s --check-prefix=UNREFERENCED-WARN -implicit-check-not=error: + +# UNREFERENCED: error: section class 'a' is unreferenced +# UNREFERENCED-WARN: warning: section class 'a' is unreferenced + +#--- class-references-class.lds +## One section class references another. +SECTIONS { + CLASS(a) { *(.rodata.a) } + CLASS(b) { CLASS(a) } +} + +# RUN: not ld.lld -T class-references-class.lds matching.o 2>&1 | \ +# RUN: FileCheck %s --check-prefix=CLASS-REFERENCES-CLASS --implicit-check-not=error: + +# CLASS-REFERENCES-CLASS: error: class-references-class.lds:4: section class 'b' references class 'a' + +#--- spill.s +.section .one_byte_section,"a",@progbits +.fill 1 + +.section .two_byte_section,"a",@progbits +.fill 2 + +# RUN: llvm-mc -n -filetype=obj -triple=x86_64 spill.s -o spill.o + +#--- spill.lds +## An input section in a class spills to a later class ref when the region of +## its first ref would overflow. The spill uses the alignment of the later ref. +MEMORY { + a : ORIGIN = 0, LENGTH = 2 + b : ORIGIN = 2, LENGTH = 16 +} + +SECTIONS { + CLASS(c) { *(.two_byte_section) } + .first_chance : SUBALIGN(1) { *(.one_byte_section) CLASS(c) } >a + .last_chance : SUBALIGN(8) { CLASS (c) } >b +} + +# RUN: ld.lld -T spill.lds spill.o -o spill +# 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 + +#--- spill-fail.lds +## A spill off the end still fails the link. +MEMORY { + a : ORIGIN = 0, LENGTH = 1 + b : ORIGIN = 2, LENGTH = 0 +} + +SECTIONS { + CLASS(c) { *(.two_byte_section) } + .first_chance : { *(.one_byte_section) CLASS(c) } >a + .last_chance : { CLASS(c) } >b +} + +# RUN: not ld.lld -T spill-fail.lds spill.o 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 + +#--- spill-lma.lds +## The above spill still occurs when the LMA would overflow, even though the +## VMA would fit. +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 { + CLASS(c) { *(.two_byte_section) } + .first_chance : { *(.one_byte_section) CLASS(c) } >vma_a AT>lma_a + .last_chance : { CLASS(c) } >vma_b AT>lma_b +} + +# RUN: ld.lld -T spill-lma.lds spill.o -o spill-lma +# 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 + +#--- spill-later.lds +## A spill occurs to an additional class ref after the first. +MEMORY { + a : ORIGIN = 0, LENGTH = 2 + b : ORIGIN = 2, LENGTH = 1 + c : ORIGIN = 3, LENGTH = 2 +} + +SECTIONS { + CLASS(c) { *(.two_byte_section) } + .first_chance : { *(.one_byte_section) CLASS(c) } >a + .second_chance : { CLASS(c) } >b + .last_chance : { CLASS(c) } >c +} + +# RUN: ld.lld -T spill-later.lds spill.o -o spill-later +# 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 + +#--- spill-earlier.lds +## A later overflow causes an earlier section to spill. +MEMORY { + a : ORIGIN = 0, LENGTH = 2 + b : ORIGIN = 2, LENGTH = 1 +} + +SECTIONS { + CLASS(c) { *(.one_byte_section) } + .first_chance : { CLASS(c) *(.two_byte_section) } >a + .last_chance : { CLASS(c) } >b +} + +# RUN: ld.lld -T spill-earlier.lds spill.o -o spill-earlier +# 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 + +#--- enable-non-contiguous-regions.lds +## Class definitions do not preclude additional matches when used with +## --enable-non-contiguous-regions, and additional matches in class +## definitions become spills at class references. +MEMORY { + a : ORIGIN = 0, LENGTH = 1 + b : ORIGIN = 1, LENGTH = 2 + c : ORIGIN = 3, LENGTH = 1 +} + +SECTIONS { + .first_chance : { *(.two_byte_section) } >a + /* An additional match in a class defers a spill. */ + CLASS(two) { *(.two_byte_section) } + /* A class references actualizes deferred spills. */ + .last_chance : { CLASS(two) } >b + + /* Section classes do not preclude other matches. */ + CLASS(one) { *(.one_byte_section) } + .one_byte_section : { *(.one_byte_section) } >c +} + +# RUN: ld.lld -T enable-non-contiguous-regions.lds spill.o -o enable-non-contiguous-regions --enable-non-contiguous-regions +# RUN: llvm-readelf -S enable-non-contiguous-regions | FileCheck %s --check-prefix=ENABLE-NON-CONTIGUOUS-REGIONS + +# ENABLE-NON-CONTIGUOUS-REGIONS: Name Type Address Off Size +# ENABLE-NON-CONTIGUOUS-REGIONS: .first_chance PROGBITS 0000000000000000 000190 000000 +# ENABLE-NON-CONTIGUOUS-REGIONS-NEXT: .last_chance PROGBITS 0000000000000001 001001 000002 +# ENABLE-NON-CONTIGUOUS-REGIONS-NEXT: .one_byte_section PROGBITS 0000000000000003 001003 000001 + +#--- merge.s +.section .a,"aM",@progbits,1 +.byte 0x12, 0x34 + +.section .b,"aM",@progbits,1 +.byte 0x12 + +# RUN: llvm-mc -n -filetype=obj -triple=x86_64 merge.s -o merge.o + +#--- spill-merge.lds +## SHF_MERGE sections are spilled according to the class refs of the first +## merged input section (the one giving the resulting section its name). +MEMORY { + a : ORIGIN = 0, LENGTH = 1 + b : ORIGIN = 1, LENGTH = 2 + c : ORIGIN = 3, LENGTH = 2 +} + +SECTIONS { + CLASS(a) { *(.a) } + CLASS(b) { *(.b) } + .first : { CLASS(a) CLASS(b) } >a + .second : { CLASS(a) } >b + .third : { CLASS(b) } >c +} + +# RUN: ld.lld -T spill-merge.lds merge.o -o spill-merge +# 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 + +#--- 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 + +# RUN: llvm-mc -n -filetype=obj -triple=x86_64 link-order.s -o link-order.o + +#--- link-order.lds +## SHF_LINK_ORDER is reordered when spilling changes relative section order. +MEMORY { + order : ORIGIN = 0, LENGTH = 3 + potential_a : ORIGIN = 3, LENGTH = 0 + bc : ORIGIN = 3, LENGTH = 2 + actual_a : ORIGIN = 5, LENGTH = 1 +} + +SECTIONS { + CLASS(a) { *(.a) } + .order : { *(.link_order.*) } > order + .potential_a : { CLASS(a) } >potential_a + .bc : { *(.b) *(.c) } >bc + .actual_a : { CLASS(a) } >actual_a +} + +# RUN: ld.lld -T link-order.lds link-order.o -o link-order +# RUN: llvm-objdump -s link-order | FileCheck %s --check-prefix=LINK-ORDER + +# LINK-ORDER: 020301 ...{{$}} + +#--- from-insert.lds +## A section might spill from INSERT. +SECTIONS { + CLASS(class) { *(.two_byte_section) } + .a : { *(.one_byte_section) } +} +SECTIONS { .b : { CLASS(class) } } INSERT AFTER .a; +SECTIONS { .c : { CLASS(class) } } + +# RUN: not ld.lld -T from-insert.lds spill.o 2>&1 |\ +# RUN: FileCheck %s --check-prefix=FROM-INSERT +# RUN: ld.lld -T from-insert.lds spill.o -o out --noinhibit-exec 2>&1 |\ +# RUN: FileCheck %s --check-prefix=FROM-INSERT-WARN + +# FROM-INSERT: error: section '.two_byte_section' cannot spill from/to INSERT section '.b' +# FROM-INSERT-WARN: warning: section '.two_byte_section' cannot spill from/to INSERT section '.b' + +#--- to-insert.lds +## A section might spill to INSERT. +SECTIONS { + CLASS(class) { *(.two_byte_section) } + .a : { CLASS(class) *(.one_byte_section) } +} +SECTIONS { .b : { CLASS(class) } } INSERT AFTER .a; + +# RUN: not ld.lld -T to-insert.lds spill.o 2>&1 |\ +# RUN: FileCheck %s --check-prefix=TO-INSERT +# RUN: ld.lld -T to-insert.lds spill.o -o out --noinhibit-exec 2>&1 |\ +# RUN: FileCheck %s --check-prefix=TO-INSERT-WARN + +# TO-INSERT: error: section '.two_byte_section' cannot spill from/to INSERT section '.b' +# TO-INSERT-WARN: warning: section '.two_byte_section' cannot spill from/to INSERT section '.b' + +#--- from-discard.lds +## A section might spill from /DISCARD/. +SECTIONS { + CLASS(class) { *(.two_byte_section) } + /DISCARD/ : { CLASS(class) } + .c : { CLASS(class) } +} + +# RUN: not ld.lld -T from-discard.lds spill.o 2>&1 |\ +# RUN: FileCheck %s --check-prefix=FROM-DISCARD +# RUN: ld.lld -T from-discard.lds spill.o -o out --noinhibit-exec 2>&1 |\ +# RUN: FileCheck %s --check-prefix=FROM-DISCARD-WARN + +# FROM-DISCARD: error: section '.two_byte_section' cannot spill from/to /DISCARD/ +# FROM-DISCARD-WARN: warning: section '.two_byte_section' cannot spill from/to /DISCARD/ + +#--- to-discard.lds +## A section might spill to /DISCARD/. +SECTIONS { + CLASS(class) { *(.two_byte_section) } + .a : { CLASS(class) } + /DISCARD/ : { CLASS(class) } +} + +# RUN: not ld.lld -T to-discard.lds spill.o 2>&1 |\ +# RUN: FileCheck %s --check-prefix=TO-DISCARD +# RUN: ld.lld -T to-discard.lds spill.o -o out --noinhibit-exec 2>&1 |\ +# RUN: FileCheck %s --check-prefix=TO-DISCARD-WARN + +# TO-DISCARD: error: section '.two_byte_section' cannot spill from/to /DISCARD/ +# TO-DISCARD-WARN: warning: section '.two_byte_section' cannot spill from/to /DISCARD/