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

Daniel Thornburgh via llvm-commits llvm-commits at lists.llvm.org
Mon Jul 22 16:31:59 PDT 2024


https://github.com/mysterymath updated https://github.com/llvm/llvm-project/pull/95323

>From 2008e1b67829e2ed1b47e59c65713ed0ab9e1fdd Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <dthorn at google.com>
Date: Mon, 13 May 2024 11:21:26 -0700
Subject: [PATCH 1/7] [LLD] Add CLASS syntax to SECTIONS

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                     | 239 ++++++++----
 lld/ELF/LinkerScript.h                       |  24 +-
 lld/ELF/MapFile.cpp                          |   2 +
 lld/ELF/OutputSections.h                     |  19 +
 lld/ELF/ScriptParser.cpp                     |  59 ++-
 lld/docs/ELF/linker_script.rst               |  53 ++-
 lld/docs/ReleaseNotes.rst                    |   7 +
 lld/test/ELF/linkerscript/section-class.test | 379 +++++++++++++++++++
 10 files changed, 693 insertions(+), 97 deletions(-)
 create mode 100644 lld/test/ELF/linkerscript/section-class.test

diff --git a/lld/ELF/InputSection.cpp b/lld/ELF/InputSection.cpp
index f4287bc94ee5e..5175d26687178 100644
--- a/lld/ELF/InputSection.cpp
+++ b/lld/ELF/InputSection.cpp
@@ -159,6 +159,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 58e5306fd6dcd..1c8550ba19baf 100644
--- a/lld/ELF/InputSection.h
+++ b/lld/ELF/InputSection.h
@@ -48,7 +48,7 @@ template <class ELFT> 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; }
 
@@ -132,7 +132,9 @@ class InputSectionBase : public SectionBase {
                    uint32_t addralign, ArrayRef<uint8_t> 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<ELFT>, but may be an InputFile of InternalKind (for a synthetic
diff --git a/lld/ELF/LinkerScript.cpp b/lld/ELF/LinkerScript.cpp
index 68f5240ddc690..2666ab780975f 100644
--- a/lld/ELF/LinkerScript.cpp
+++ b/lld/ELF/LinkerScript.cpp
@@ -276,6 +276,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)
@@ -347,6 +349,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.
@@ -490,99 +494,130 @@ 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);
+
+  const 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<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)
+  if (cmd->className.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 (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<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) ||
+            !flagsMatch(sec))
           continue;
 
-        spills.insert(sec);
+        if (sec->parent) {
+          // Skip if not allowing multiple matches.
+          if (!config->enableNonContiguousRegions)
+            continue;
+
+          // Disallow spilling out of or into section classes; that's already a
+          // mechanism for spilling.
+          if (isa<SectionClass>(sec->parent) || isa<SectionClass>(outCmd))
+            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 was already matched by a different input
+          // section description within this output section or class.
+          if (sec->parent == &outCmd)
+            continue;
+
+          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(cmd->className);
+    if (!scd) {
+      error("undefined section class '" + cmd->className + "'");
+      return ret;
+    }
+    if (!scd->sc.assigned) {
+      error("section class '" + cmd->className + "' used before assigned");
+      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/"))
+          error("section '" + sec->name + "' cannot spill from/to /DISCARD/");
+        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
@@ -707,7 +742,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))) {
@@ -717,7 +752,44 @@ 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)
+          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))
+            error("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
@@ -725,6 +797,15 @@ 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))
+          error("section '" + sec->name + "' assigned to class '" +
+                sec->parent->name + "' but to no output section");
 }
 
 void LinkerScript::processSymbolAssignments() {
@@ -745,8 +826,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 *od = dyn_cast<OutputDesc>(cmd))
+      for (SectionCommand *subCmd : od->osec.commands)
         if (auto *assign = dyn_cast<SymbolAssignment>(subCmd))
           addSymbol(assign);
   }
@@ -1421,6 +1502,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;
   }
@@ -1441,7 +1524,7 @@ 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;
diff --git a/lld/ELF/LinkerScript.h b/lld/ELF/LinkerScript.h
index 36feab36e26ba..a6bbc1019774a 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 <sym> = expr
   OutputSectionKind,
   InputSectionKind,
-  ByteKind    // BYTE(expr), SHORT(expr), LONG(expr) or QUAD(expr)
+  ByteKind, // BYTE(expr), SHORT(expr), LONG(expr) or QUAD(expr)
+  ClassKind,
 };
 
 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 className = {})
       : SectionCommand(InputSectionKind), filePat(filePattern),
-        withFlags(withFlags), withoutFlags(withoutFlags) {}
+        withFlags(withFlags), withoutFlags(withoutFlags), className(className) {
+    assert((filePattern.empty() || className.empty()) &&
+           "file pattern and class name are mutually exclusive");
+  }
 
   static bool classof(const SectionCommand *c) {
     return c->kind == InputSectionKind;
@@ -228,6 +234,10 @@ class InputSectionDescription : public SectionCommand {
   // SectionPatterns can be filtered with the INPUT_SECTION_FLAGS command.
   uint64_t withFlags;
   uint64_t withoutFlags;
+
+  // If present, input section matching uses class membership instead of file
+  // and section patterns (mutually exclusive).
+  StringRef className;
 };
 
 // Represents BYTE(), SHORT(), LONG(), or QUAD().
@@ -288,8 +298,7 @@ class LinkerScript final {
 
   SmallVector<InputSectionBase *, 0>
   computeInputSections(const InputSectionDescription *,
-                       ArrayRef<InputSectionBase *>,
-                       const OutputSection &outCmd);
+                       ArrayRef<InputSectionBase *>, const SectionBase &outCmd);
 
   SmallVector<InputSectionBase *, 0> createInputSectionList(OutputSection &cmd);
 
@@ -413,6 +422,11 @@ class LinkerScript final {
     PotentialSpillSection *tail;
   };
   llvm::DenseMap<InputSectionBase *, PotentialSpillList> 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::StringMap<SectionClassDesc *> sectionClasses;
 };
 
 struct ScriptWrapper {
diff --git a/lld/ELF/MapFile.cpp b/lld/ELF/MapFile.cpp
index c4f3fdde30f36..1bad529b40329 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<SectionClassDesc>(cmd))
+      continue;
 
     osec = &cast<OutputDesc>(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 78fede48a23f2..c5f03cb8d846a 100644
--- a/lld/ELF/OutputSections.h
+++ b/lld/ELF/OutputSections.h
@@ -137,6 +137,25 @@ struct OutputDesc final : SectionCommand {
   }
 };
 
+// A list of input sections that can be referenced in output descriptions.
+// Multiple references allow sections to spill from one output section to the
+// next.
+struct SectionClass final : public SectionBase {
+  SmallVector<InputSectionDescription *, 0> 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 f90ce6fa74075..422bb63b8c03a 100644
--- a/lld/ELF/ScriptParser.cpp
+++ b/lld/ELF/ScriptParser.cpp
@@ -96,6 +96,8 @@ class ScriptParser final : ScriptLexer {
   OutputDesc *readOverlaySectionDescription();
   OutputDesc *readOutputSectionDescription(StringRef outSec);
   SmallVector<SectionCommand *, 0> readOverlay();
+  SectionClassDesc *readSectionClassDescription();
+  StringRef readSectionClassName();
   SmallVector<StringRef, 0> readOutputSectionPhdrs();
   std::pair<uint64_t, uint64_t> readInputSectionFlags();
   InputSectionDescription *readInputSectionDescription(StringRef tok);
@@ -585,6 +587,35 @@ SmallVector<SectionCommand *, 0> ScriptParser::readOverlay() {
   return v;
 }
 
+SectionClassDesc *ScriptParser::readSectionClassDescription() {
+  std::string loc = getCurrentLocation();
+  StringRef name = readSectionClassName();
+  SectionClassDesc *desc = make<SectionClassDesc>(name);
+  if (!script->sectionClasses.insert({name, desc}).second)
+    setError("section class '" + name + "' already defined");
+  expect("{");
+  while (!errorCount() && !consume("}")) {
+    StringRef tok = next();
+    if (tok == "(" || tok == ")")
+      setError("expected filename pattern");
+    else if (peek() == "(") {
+      InputSectionDescription *isd = readInputSectionDescription(tok);
+      if (!isd->className.empty())
+        setError("section class '" + name + "' references class '" +
+                 isd->className + "'");
+      desc->sc.commands.push_back(isd);
+    }
+  }
+  return desc;
+}
+
+StringRef ScriptParser::readSectionClassName() {
+  expect("(");
+  StringRef name = next();
+  expect(")");
+  return name;
+}
+
 void ScriptParser::readOverwriteSections() {
   expect("{");
   while (!errorCount() && !consume("}"))
@@ -600,7 +631,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;
     }
@@ -803,8 +839,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<InputSectionDescription>(StringRef{}, withFlags, withoutFlags,
+                                          readSectionClassName());
+    else
+      cmd = readInputSectionRules(tok, withFlags, withoutFlags);
     expect(")");
     script->keptSections.push_back(cmd);
     return cmd;
@@ -813,6 +855,9 @@ ScriptParser::readInputSectionDescription(StringRef tok) {
     std::tie(withFlags, withoutFlags) = readInputSectionFlags();
     tok = next();
   }
+  if (tok == "CLASS")
+    return make<InputSectionDescription>(StringRef{}, withFlags, withoutFlags,
+                                         readSectionClassName());
   return readInputSectionRules(tok, withFlags, withoutFlags);
 }
 
@@ -927,8 +972,12 @@ OutputDesc *ScriptParser::readOverlaySectionDescription() {
     uint64_t withoutFlags = 0;
     if (consume("INPUT_SECTION_FLAGS"))
       std::tie(withFlags, withoutFlags) = readInputSectionFlags();
-    osd->osec.commands.push_back(
-        readInputSectionRules(next(), withFlags, withoutFlags));
+    if (peek() == "CLASS")
+      osd->osec.commands.push_back(make<InputSectionDescription>(
+          StringRef{}, withFlags, withoutFlags, next()));
+    else
+      osd->osec.commands.push_back(
+          readInputSectionRules(next(), 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 7a35534be096c..c13bc0dac1fbe 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
+~~~~~~~~~~~~~~~
+
+``SECTIONS`` commands can define classes of input sections:
+
+::
+
+  SECTIONS {
+    CLASS(class_name) {
+      input-section-description
+      input-section-description
+      ...
+    }
+  }
+
+Input section descriptions can 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 cannot be matched by later
+wildcards, 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 wildcard matches. (This globally changes the
+behavior of wildcards.) 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 12ea6de0fc15c..685ad89a458d5 100644
--- a/lld/docs/ReleaseNotes.rst
+++ b/lld/docs/ReleaseNotes.rst
@@ -38,6 +38,13 @@ 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>`_)
+* Section ``CLASS`` syntax allows binding input section to named classes. This
+  allows the linker to automatically pack the input sections into memory
+  regions by automatically spilling to later class references 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. (TODO: PR
+  Reference)
 * ``--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
diff --git a/lld/test/ELF/linkerscript/section-class.test b/lld/test/ELF/linkerscript/section-class.test
new file mode 100644
index 0000000000000..6a50cd0602784
--- /dev/null
+++ b/lld/test/ELF/linkerscript/section-class.test
@@ -0,0 +1,379 @@
+# REQUIRES: x86
+
+# RUN: rm -rf %t && split-file %s %t && cd %t
+
+# RUN: llvm-mc -n -filetype=obj -triple=x86_64 matching.s -o matching.o
+
+## CLASS definitions match sections in linker script order. The sections may be
+## placed in a different order. Classes may derive from one another, and class
+## references may be restricted by INPUT_SECTION_FLAGS.
+
+# RUN: ld.lld -T matching.ld matching.o -o matching
+# RUN: llvm-readobj -x .rodata matching | FileCheck %s --check-prefix=MATCHING
+
+# MATCHING: 02030104
+
+
+## An error is reported when a section class has more than one description.
+
+# RUN: not ld.lld -T already-defined.ld matching.o 2>&1 | \
+# RUN:   FileCheck %s --check-prefix=ALREADY-DEFINED --implicit-check-not=error:
+
+# ALREADY-DEFINED: error: already-defined.ld:3: section class 'a' already defined
+
+
+## An error is reported when a filename pattern is missing in a section class
+## description.
+
+# RUN: not ld.lld -T missing-filename-pattern.ld matching.o 2>&1 | \
+# RUN:   FileCheck %s --check-prefix=MISSING-FILENAME-PATTERN --implicit-check-not=error:
+
+# MISSING-FILENAME-PATTERN: error: missing-filename-pattern.ld:2: expected filename pattern
+
+
+## An error is reported when the content of section classes is demanded before
+## input sections have been assigned to them.
+
+# RUN: not ld.lld -T used-before-assigned.ld matching.o 2>&1 | \
+# RUN:   FileCheck %s --check-prefix=USED-BEFORE-ASSIGNED
+
+# USED-BEFORE-ASSIGNED: error: section class 'a' used before assigned
+
+
+## An error is reported when an input section is bound to a section class but
+## is not referenced by at least one output section.
+
+# RUN: not ld.lld -T unreferenced.ld matching.o 2>&1 | \
+# RUN:   FileCheck %s --check-prefix=UNREFERENCED
+
+# UNREFERENCED: error: section '.rodata.a' assigned to class 'a' but to no output section
+
+
+## An error is reported when one section class references another.
+
+# RUN: not ld.lld -T class-references-class.ld matching.o 2>&1 | \
+# RUN:   FileCheck %s --check-prefix=CLASS-REFERENCES-CLASS --implicit-check-not=error:
+
+# CLASS-REFERENCES-CLASS: error: class-references-class.ld:3: section class 'b' references class 'a'
+
+
+# RUN: llvm-mc -n -filetype=obj -triple=x86_64 spill.s -o spill.o
+
+
+## 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.
+
+# RUN: ld.lld -T spill.ld 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
+
+
+## A spill off the end still fails the link.
+
+# RUN: not ld.lld -T spill-fail.ld 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
+
+
+## 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
+# 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 class ref after the first.
+
+# RUN: ld.lld -T spill-later.ld 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
+
+
+## A later overflow causes an earlier section to spill.
+
+# RUN: ld.lld -T spill-earlier.ld 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
+
+
+## SHF_MERGEd sections are spilled according to the class refs 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
+# 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
+
+
+## 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
+# RUN: llvm-readobj -x .order link-order | FileCheck %s --check-prefix=LINK-ORDER
+
+# LINK-ORDER: 020301
+
+
+## An error is reported when a section might spill from INSERT.
+
+# RUN: not ld.lld -T from-insert.ld spill.o 2>&1 |\
+# RUN:   FileCheck %s --check-prefix=FROM-INSERT
+
+# FROM-INSERT: error: section '.two_byte_section' cannot spill from/to INSERT section '.b'
+
+
+## An error is reported when a section might spill to INSERT.
+
+# RUN: not ld.lld -T to-insert.ld spill.o 2>&1 |\
+# RUN:   FileCheck %s --check-prefix=TO-INSERT
+
+# TO-INSERT: error: section '.two_byte_section' cannot spill from/to INSERT section '.b'
+
+
+## An error is reported when a section might spill from /DISCARD/.
+
+# RUN: not ld.lld -T from-discard.ld spill.o 2>&1 |\
+# RUN:   FileCheck %s --check-prefix=FROM-DISCARD
+
+# FROM-DISCARD: error: section '.two_byte_section' cannot spill from/to /DISCARD/
+
+
+## An error is reported when a section might spill to /DISCARD/.
+
+# RUN: not ld.lld -T to-discard.ld spill.o 2>&1 |\
+# RUN:   FileCheck %s --check-prefix=TO-DISCARD
+
+# TO-DISCARD: error: section '.two_byte_section' cannot spill from/to /DISCARD/
+
+#--- matching.s
+.section .rodata.a,"a", at progbits
+.byte 1
+
+.section .rodata.b,"a", at progbits
+.byte 2
+
+.section .rodata.c,"ax", at progbits
+.byte 3
+
+.section .rodata.d,"a", at progbits
+.byte 4
+
+#--- matching.ld
+MEMORY {
+ m : ORIGIN = 0, LENGTH = 4
+}
+
+SECTIONS {
+  CLASS(a) { *(.rodata.a) }
+  CLASS(cd) { *(.rodata.c) *(.rodata.d) }
+  .rodata : {
+    *(.rodata.*)
+    INPUT_SECTION_FLAGS(SHF_EXECINSTR) CLASS(cd)
+    CLASS(a)
+    CLASS(cd)
+  } >m
+}
+
+#--- already-defined.ld
+SECTIONS {
+  CLASS(a) { *(.rodata.a) }
+  CLASS(a) { *(.rodata.b) }
+}
+
+#--- missing-filename-pattern.ld
+SECTIONS {
+  CLASS(a) { (.rodata.a) }
+}
+
+#--- used-before-assigned.ld
+SECTIONS {
+  .rodata : { CLASS(a) }
+  CLASS(a) { *(.rodata.a) }
+}
+
+#--- unreferenced.ld
+SECTIONS {
+  CLASS(a) { *(.rodata.*) }
+}
+
+#--- class-references-class.ld
+SECTIONS {
+  CLASS(a) { *(.rodata.a) }
+  CLASS(b) { CLASS(a) }
+}
+
+#--- spill.s
+.section .one_byte_section,"a", at progbits
+.fill 1
+
+.section .two_byte_section,"a", at progbits
+.fill 2
+
+#--- spill.ld
+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
+}
+
+#--- spill-fail.ld
+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
+}
+
+#--- 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 {
+  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
+}
+
+#--- spill-later.ld
+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
+}
+
+#--- spill-earlier.ld
+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
+}
+
+#--- merge.s
+.section .a,"aM", at progbits,1
+.byte 0x12, 0x34
+
+.section .b,"aM", at progbits,1
+.byte 0x12
+
+#--- spill-merge.ld
+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
+}
+
+#--- link-order.s
+.section .a,"a", at progbits
+.fill 1
+
+.section .b,"a", at progbits
+.fill 1
+
+.section .c,"a", at progbits
+.fill 1
+
+.section .link_order.a,"ao", at progbits,.a
+.byte 1
+
+.section .link_order.b,"ao", at progbits,.b
+.byte 2
+
+.section .link_order.c,"ao", at 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 {
+  CLASS(a) { *(.a) }
+  .order :  { *(.link_order.*) } > order
+  .potential_a : { CLASS(a) } >potential_a
+  .bc : { *(.b) *(.c) } >bc
+  .actual_a : { CLASS(a) } >actual_a
+}
+
+#--- from-insert.ld
+SECTIONS {
+  CLASS(class) { *(.two_byte_section) }
+  .a : { *(.one_byte_section) }
+}
+SECTIONS { .b : { CLASS(class) } } INSERT AFTER .a;
+SECTIONS { .c : { CLASS(class) } }
+
+#--- to-insert.ld
+SECTIONS {
+  CLASS(class) { *(.two_byte_section) }
+  .a : { CLASS(class) *(.one_byte_section) }
+}
+SECTIONS { .b : { CLASS(class) } } INSERT AFTER .a;
+
+#--- from-discard.ld
+SECTIONS {
+  CLASS(class) { *(.two_byte_section) }
+  /DISCARD/ : { CLASS(class) }
+  .c : { CLASS(class) }
+}
+
+#--- to-discard.ld
+SECTIONS {
+  CLASS(class) { *(.two_byte_section) }
+  .a : { CLASS(class) }
+  /DISCARD/ : { CLASS(class) }
+}

>From 7715fc99d8b04d257d6de06d56f9548e6b2b8fab Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <dthorn at google.com>
Date: Thu, 13 Jun 2024 14:04:11 -0700
Subject: [PATCH 2/7] Address review comments

- Used consistent terminology for error when section classes are
  referenced before their definition.
- " for error when sections assigned to a class are never referenced
- Add test for class references in OVERLAY; fix parser.
---
 lld/ELF/LinkerScript.cpp                     |  5 ++--
 lld/ELF/ScriptParser.cpp                     |  7 ++---
 lld/test/ELF/linkerscript/section-class.test | 28 +++++++++-----------
 3 files changed, 20 insertions(+), 20 deletions(-)

diff --git a/lld/ELF/LinkerScript.cpp b/lld/ELF/LinkerScript.cpp
index 2666ab780975f..39b428b0ea759 100644
--- a/lld/ELF/LinkerScript.cpp
+++ b/lld/ELF/LinkerScript.cpp
@@ -601,7 +601,8 @@ LinkerScript::computeInputSections(const InputSectionDescription *cmd,
       return ret;
     }
     if (!scd->sc.assigned) {
-      error("section class '" + cmd->className + "' used before assigned");
+      error("section class '" + cmd->className + "' referenced by '" +
+            outCmd.name + "' before class definition");
       return ret;
     }
 
@@ -805,7 +806,7 @@ void LinkerScript::processSectionCommands() {
       for (InputSectionBase *sec : isd->sectionBases)
         if (sec->parent && isa<SectionClass>(sec->parent))
           error("section '" + sec->name + "' assigned to class '" +
-                sec->parent->name + "' but to no output section");
+                sec->parent->name + "' but unreferenced by any output section");
 }
 
 void LinkerScript::processSymbolAssignments() {
diff --git a/lld/ELF/ScriptParser.cpp b/lld/ELF/ScriptParser.cpp
index 422bb63b8c03a..5b3dab1f20756 100644
--- a/lld/ELF/ScriptParser.cpp
+++ b/lld/ELF/ScriptParser.cpp
@@ -972,12 +972,13 @@ OutputDesc *ScriptParser::readOverlaySectionDescription() {
     uint64_t withoutFlags = 0;
     if (consume("INPUT_SECTION_FLAGS"))
       std::tie(withFlags, withoutFlags) = readInputSectionFlags();
-    if (peek() == "CLASS")
+    StringRef tok = next();
+    if (tok == "CLASS")
       osd->osec.commands.push_back(make<InputSectionDescription>(
-          StringRef{}, withFlags, withoutFlags, next()));
+          StringRef{}, withFlags, withoutFlags, readSectionClassName()));
     else
       osd->osec.commands.push_back(
-          readInputSectionRules(next(), withFlags, withoutFlags));
+          readInputSectionRules(tok, withFlags, withoutFlags));
   }
   osd->osec.phdrs = readOutputSectionPhdrs();
   return osd;
diff --git a/lld/test/ELF/linkerscript/section-class.test b/lld/test/ELF/linkerscript/section-class.test
index 6a50cd0602784..5a3b07a0e485b 100644
--- a/lld/test/ELF/linkerscript/section-class.test
+++ b/lld/test/ELF/linkerscript/section-class.test
@@ -9,9 +9,12 @@
 ## references may be restricted by INPUT_SECTION_FLAGS.
 
 # RUN: ld.lld -T matching.ld matching.o -o matching
-# RUN: llvm-readobj -x .rodata matching | FileCheck %s --check-prefix=MATCHING
+# RUN: llvm-readobj -x .rodata -x .rodata.d matching | FileCheck %s --check-prefix=MATCHING
 
-# MATCHING: 02030104
+# MATCHING:      .rodata
+# MATCHING-NEXT: 020301
+# MATCHING:      .rodata.d
+# MATCHING-NEXT: 04
 
 
 ## An error is reported when a section class has more than one description.
@@ -32,13 +35,12 @@
 
 
 ## An error is reported when the content of section classes is demanded before
-## input sections have been assigned to them.
+## its definition is processed.
 
-# RUN: not ld.lld -T used-before-assigned.ld matching.o 2>&1 | \
-# RUN:   FileCheck %s --check-prefix=USED-BEFORE-ASSIGNED
-
-# USED-BEFORE-ASSIGNED: error: section class 'a' used before assigned
+# RUN: not ld.lld -T referenced-before-defined.ld matching.o 2>&1 | \
+# RUN:   FileCheck %s --check-prefix=REFERENCED-BEFORE-DEFINED
 
+# REFERENCED-BEFORE-DEFINED: error: section class 'a' referenced by '.rodata' before class definition
 
 ## An error is reported when an input section is bound to a section class but
 ## is not referenced by at least one output section.
@@ -46,7 +48,7 @@
 # RUN: not ld.lld -T unreferenced.ld matching.o 2>&1 | \
 # RUN:   FileCheck %s --check-prefix=UNREFERENCED
 
-# UNREFERENCED: error: section '.rodata.a' assigned to class 'a' but to no output section
+# UNREFERENCED: error: section '.rodata.a' assigned to class 'a' but unreferenced by any output section
 
 
 ## An error is reported when one section class references another.
@@ -178,10 +180,6 @@
 .byte 4
 
 #--- matching.ld
-MEMORY {
- m : ORIGIN = 0, LENGTH = 4
-}
-
 SECTIONS {
   CLASS(a) { *(.rodata.a) }
   CLASS(cd) { *(.rodata.c) *(.rodata.d) }
@@ -189,8 +187,8 @@ SECTIONS {
     *(.rodata.*)
     INPUT_SECTION_FLAGS(SHF_EXECINSTR) CLASS(cd)
     CLASS(a)
-    CLASS(cd)
-  } >m
+  }
+  OVERLAY : { .rodata.d { INPUT_SECTION_FLAGS(!SHF_EXECINSTR) CLASS(cd) } }
 }
 
 #--- already-defined.ld
@@ -204,7 +202,7 @@ SECTIONS {
   CLASS(a) { (.rodata.a) }
 }
 
-#--- used-before-assigned.ld
+#--- referenced-before-defined.ld
 SECTIONS {
   .rodata : { CLASS(a) }
   CLASS(a) { *(.rodata.a) }

>From 57d1f372d9043104a27f8f14ac4ee2c4b9ae1ef2 Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <dthorn at google.com>
Date: Tue, 18 Jun 2024 15:54:36 -0700
Subject: [PATCH 3/7] Address review feedback

- Add an explicit test that CLASS(a b) doesn't parse as a reference.
- Add SORT_BY_ALIGNMENT in class def to demonstrate more complex
  matching.
- Rename InputSectionDescription className to classRef to make it
  clearer that the class is being referenced, not defined.
- Define the interaction between --enable-non-contiguous-regions and
  CLASS and add tests.
- Add alreadyAssignedToOutCmd lambda to centralize comments.
---
 lld/ELF/LinkerScript.cpp                     | 43 ++++++++-------
 lld/ELF/LinkerScript.h                       | 10 ++--
 lld/ELF/ScriptParser.cpp                     |  4 +-
 lld/test/ELF/linkerscript/section-class.test | 58 +++++++++++++++++++-
 4 files changed, 88 insertions(+), 27 deletions(-)

diff --git a/lld/ELF/LinkerScript.cpp b/lld/ELF/LinkerScript.cpp
index 39b428b0ea759..dcf397b618814 100644
--- a/lld/ELF/LinkerScript.cpp
+++ b/lld/ELF/LinkerScript.cpp
@@ -498,13 +498,20 @@ LinkerScript::computeInputSections(const InputSectionDescription *cmd,
   SmallVector<InputSectionBase *, 0> ret;
   DenseSet<InputSectionBase *> spills;
 
+  // Returns whether an input section was already assigned to an earlier input
+  // section description in this output section or section class.
+  const auto alreadyAssignedToOutCmd =
+      [&outCmd](InputSectionBase *sec) { return sec->parent == &outCmd; };
+
+  // Returns whether an input section's flags match the input section
+  // description's specifiers.
   const 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.
-  if (cmd->className.empty()) {
+  if (cmd->classRef.empty()) {
     DenseSet<size_t> seen;
     size_t sizeAfterPrevSort = 0;
     SmallVector<size_t, 0> indexes;
@@ -541,7 +548,7 @@ LinkerScript::computeInputSections(const InputSectionDescription *cmd,
           continue;
 
         if (!cmd->matchesFile(sec->file) || pat.excludesFile(sec->file) ||
-            !flagsMatch(sec))
+            alreadyAssignedToOutCmd(sec) || !flagsMatch(sec))
           continue;
 
         if (sec->parent) {
@@ -549,22 +556,16 @@ LinkerScript::computeInputSections(const InputSectionDescription *cmd,
           if (!config->enableNonContiguousRegions)
             continue;
 
-          // Disallow spilling out of or into section classes; that's already a
-          // mechanism for spilling.
-          if (isa<SectionClass>(sec->parent) || isa<SectionClass>(outCmd))
-            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 was already matched by a different input
-          // section description within this output section or class.
-          if (sec->parent == &outCmd)
-            continue;
-
-          spills.insert(sec);
+          // 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);
@@ -595,20 +596,20 @@ LinkerScript::computeInputSections(const InputSectionDescription *cmd,
     // input order).
     sortByPositionThenCommandLine(sizeAfterPrevSort, ret.size());
   } else {
-    SectionClassDesc *scd = script->sectionClasses.lookup(cmd->className);
+    SectionClassDesc *scd = script->sectionClasses.lookup(cmd->classRef);
     if (!scd) {
-      error("undefined section class '" + cmd->className + "'");
+      error("undefined section class '" + cmd->classRef + "'");
       return ret;
     }
     if (!scd->sc.assigned) {
-      error("section class '" + cmd->className + "' referenced by '" +
+      error("section class '" + cmd->classRef + "' referenced by '" +
             outCmd.name + "' before class definition");
       return ret;
     }
 
     for (InputSectionDescription *isd : scd->sc.commands) {
       for (InputSectionBase *sec : isd->sectionBases) {
-        if (sec->parent == &outCmd || !flagsMatch(sec))
+        if (alreadyAssignedToOutCmd(sec) || !flagsMatch(sec))
           continue;
         bool isSpill = sec->parent && isa<OutputSection>(sec->parent);
         if (!sec->parent || (isSpill && outCmd.name == "/DISCARD/"))
@@ -757,8 +758,12 @@ void LinkerScript::processSectionCommands() {
       for (InputSectionDescription *isd : sc->sc.commands) {
         isd->sectionBases =
             computeInputSections(isd, ctx.inputSections, sc->sc);
-        for (InputSectionBase *s : isd->sectionBases)
-          s->parent = &sc->sc;
+        for (InputSectionBase *s : isd->sectionBases) {
+          // Section classes with --enable-non-contiguous-regions may contain
+          // parented classes; spills for these are generated on reference.
+          if (!s->parent)
+            s->parent = &sc->sc;
+        }
       }
       sc->sc.assigned = true;
     }
diff --git a/lld/ELF/LinkerScript.h b/lld/ELF/LinkerScript.h
index a6bbc1019774a..aa7b1853adc9f 100644
--- a/lld/ELF/LinkerScript.h
+++ b/lld/ELF/LinkerScript.h
@@ -201,11 +201,11 @@ class InputSectionDescription : public SectionCommand {
 
 public:
   InputSectionDescription(StringRef filePattern, uint64_t withFlags = 0,
-                          uint64_t withoutFlags = 0, StringRef className = {})
+                          uint64_t withoutFlags = 0, StringRef classRef = {})
       : SectionCommand(InputSectionKind), filePat(filePattern),
-        withFlags(withFlags), withoutFlags(withoutFlags), className(className) {
-    assert((filePattern.empty() || className.empty()) &&
-           "file pattern and class name are mutually exclusive");
+        withFlags(withFlags), withoutFlags(withoutFlags), classRef(classRef) {
+    assert((filePattern.empty() || classRef.empty()) &&
+           "file pattern and class reference are mutually exclusive");
   }
 
   static bool classof(const SectionCommand *c) {
@@ -237,7 +237,7 @@ class InputSectionDescription : public SectionCommand {
 
   // If present, input section matching uses class membership instead of file
   // and section patterns (mutually exclusive).
-  StringRef className;
+  StringRef classRef;
 };
 
 // Represents BYTE(), SHORT(), LONG(), or QUAD().
diff --git a/lld/ELF/ScriptParser.cpp b/lld/ELF/ScriptParser.cpp
index 5b3dab1f20756..908ad4334055c 100644
--- a/lld/ELF/ScriptParser.cpp
+++ b/lld/ELF/ScriptParser.cpp
@@ -600,9 +600,9 @@ SectionClassDesc *ScriptParser::readSectionClassDescription() {
       setError("expected filename pattern");
     else if (peek() == "(") {
       InputSectionDescription *isd = readInputSectionDescription(tok);
-      if (!isd->className.empty())
+      if (!isd->classRef.empty())
         setError("section class '" + name + "' references class '" +
-                 isd->className + "'");
+                 isd->classRef + "'");
       desc->sc.commands.push_back(isd);
     }
   }
diff --git a/lld/test/ELF/linkerscript/section-class.test b/lld/test/ELF/linkerscript/section-class.test
index 5a3b07a0e485b..8afe6a87066a2 100644
--- a/lld/test/ELF/linkerscript/section-class.test
+++ b/lld/test/ELF/linkerscript/section-class.test
@@ -12,7 +12,7 @@
 # RUN: llvm-readobj -x .rodata -x .rodata.d matching | FileCheck %s --check-prefix=MATCHING
 
 # MATCHING:      .rodata
-# MATCHING-NEXT: 020301
+# MATCHING-NEXT: 020301cc 0605
 # MATCHING:      .rodata.d
 # MATCHING-NEXT: 04
 
@@ -34,6 +34,14 @@
 # MISSING-FILENAME-PATTERN: error: missing-filename-pattern.ld:2: expected filename pattern
 
 
+## An error is reported when more than one class is mentioned in a reference.
+
+# RUN: not ld.lld -T multiple-class-names.ld matching.o 2>&1 | \
+# RUN:   FileCheck %s --check-prefix=MULTIPLE-CLASS-NAMES --implicit-check-not=error:
+
+# MULTIPLE-CLASS-NAMES: error: multiple-class-names.ld:4: ) expected, but got b
+
+
 ## An error is reported when the content of section classes is demanded before
 ## its definition is processed.
 
@@ -113,6 +121,19 @@
 # SPILL-EARLIER-NEXT: .last_chance  PROGBITS 0000000000000002 001002 000001
 
 
+## 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.
+
+# RUN: ld.lld -T enable-non-contiguous-regions.ld 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
+
+
 ## SHF_MERGEd sections are spilled according to the class refs of the first
 ## merged input section (the one giving the resulting section its name).
 
@@ -179,14 +200,23 @@
 .section .rodata.d,"a", at progbits
 .byte 4
 
+.section .rodata.e,"a", at progbits
+.byte 5
+
+.section .rodata.f,"a", at progbits
+.balign 2
+.byte 6
+
 #--- matching.ld
 SECTIONS {
   CLASS(a) { *(.rodata.a) }
   CLASS(cd) { *(.rodata.c) *(.rodata.d) }
+  CLASS(ef) { *(SORT_BY_ALIGNMENT(.rodata.e .rodata.f)) }
   .rodata : {
     *(.rodata.*)
     INPUT_SECTION_FLAGS(SHF_EXECINSTR) CLASS(cd)
     CLASS(a)
+    CLASS(ef)
   }
   OVERLAY : { .rodata.d { INPUT_SECTION_FLAGS(!SHF_EXECINSTR) CLASS(cd) } }
 }
@@ -202,6 +232,13 @@ SECTIONS {
   CLASS(a) { (.rodata.a) }
 }
 
+#--- multiple-class-names.ld
+SECTIONS {
+  CLASS(a) { *(.rodata.a) }
+  CLASS(b) { *(.rodata.b) }
+  .rodata : { CLASS(a b) }
+}
+
 #--- referenced-before-defined.ld
 SECTIONS {
   .rodata : { CLASS(a) }
@@ -290,6 +327,25 @@ SECTIONS {
   .last_chance : { CLASS(c) } >b
 }
 
+#--- enable-non-contiguous-regions.ld
+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
+}
+
 #--- merge.s
 .section .a,"aM", at progbits,1
 .byte 0x12, 0x34

>From 6515d23eec5a7b7c5b848f6c80ca1a07ee4f8b2d Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <dthorn at google.com>
Date: Thu, 27 Jun 2024 15:22:52 -0700
Subject: [PATCH 4/7] Address review feedback

- `auto` over `const auto` for lambdas
- Inline `alreadyAssignedToOutCmd`
- Use `.lds` as linker script file extension in test
- Move RUN, CHECK and test descriptors into split files
- Add another duplicate class definition to test
- Remove dead `loc` variable
- Add missing braces to an `if` statement with braced `else`
- Remove "An error is reported when" from test descriptions
- Remove the 'd' from "SHF_MERGEd sections"
- Use `errorOrWarn` for errors that still allow an object file to be
  produced
- Add tests that a class reference can be used in /DISCARD/ or INSERT.
- Update spill comment.
- Tighten doc wording.
- Add PR number to release notes.
---
 lld/ELF/LinkerScript.cpp                     |  41 +-
 lld/ELF/ScriptParser.cpp                     |   5 +-
 lld/docs/ELF/linker_script.rst               |  28 +-
 lld/docs/ReleaseNotes.rst                    |   4 +-
 lld/test/ELF/linkerscript/section-class.test | 407 +++++++++----------
 5 files changed, 241 insertions(+), 244 deletions(-)

diff --git a/lld/ELF/LinkerScript.cpp b/lld/ELF/LinkerScript.cpp
index dcf397b618814..cf8ce9ee49991 100644
--- a/lld/ELF/LinkerScript.cpp
+++ b/lld/ELF/LinkerScript.cpp
@@ -498,14 +498,9 @@ LinkerScript::computeInputSections(const InputSectionDescription *cmd,
   SmallVector<InputSectionBase *, 0> ret;
   DenseSet<InputSectionBase *> spills;
 
-  // Returns whether an input section was already assigned to an earlier input
-  // section description in this output section or section class.
-  const auto alreadyAssignedToOutCmd =
-      [&outCmd](InputSectionBase *sec) { return sec->parent == &outCmd; };
-
   // Returns whether an input section's flags match the input section
   // description's specifiers.
-  const auto flagsMatch = [cmd](InputSectionBase *sec) {
+  auto flagsMatch = [cmd](InputSectionBase *sec) {
     return (sec->flags & cmd->withFlags) == cmd->withFlags &&
            (sec->flags & cmd->withoutFlags) == 0;
   };
@@ -548,7 +543,7 @@ LinkerScript::computeInputSections(const InputSectionDescription *cmd,
           continue;
 
         if (!cmd->matchesFile(sec->file) || pat.excludesFile(sec->file) ||
-            alreadyAssignedToOutCmd(sec) || !flagsMatch(sec))
+            sec->parent == &outCmd || !flagsMatch(sec))
           continue;
 
         if (sec->parent) {
@@ -598,22 +593,24 @@ LinkerScript::computeInputSections(const InputSectionDescription *cmd,
   } else {
     SectionClassDesc *scd = script->sectionClasses.lookup(cmd->classRef);
     if (!scd) {
-      error("undefined section class '" + cmd->classRef + "'");
+      errorOrWarn("undefined section class '" + cmd->classRef + "'");
       return ret;
     }
     if (!scd->sc.assigned) {
-      error("section class '" + cmd->classRef + "' referenced by '" +
-            outCmd.name + "' before class definition");
+      errorOrWarn("section class '" + cmd->classRef + "' referenced by '" +
+                  outCmd.name + "' before class definition");
       return ret;
     }
 
     for (InputSectionDescription *isd : scd->sc.commands) {
       for (InputSectionBase *sec : isd->sectionBases) {
-        if (alreadyAssignedToOutCmd(sec) || !flagsMatch(sec))
+        if (sec->parent == &outCmd || !flagsMatch(sec))
           continue;
         bool isSpill = sec->parent && isa<OutputSection>(sec->parent);
-        if (!sec->parent || (isSpill && outCmd.name == "/DISCARD/"))
-          error("section '" + sec->name + "' cannot spill from/to /DISCARD/");
+        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);
@@ -621,10 +618,10 @@ LinkerScript::computeInputSections(const InputSectionDescription *cmd,
     }
   }
 
-  // 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))
@@ -791,8 +788,9 @@ void LinkerScript::processSectionCommands() {
         for (InputSectionBase *isec : isd->sectionBases)
           if (isa<PotentialSpillSection>(isec) ||
               potentialSpillLists.contains(isec))
-            error("section '" + isec->name +
-                  "' cannot spill from/to INSERT section '" + os->name + "'");
+            errorOrWarn("section '" + isec->name +
+                        "' cannot spill from/to INSERT section '" + os->name +
+                        "'");
       }
     }
   }
@@ -810,8 +808,9 @@ void LinkerScript::processSectionCommands() {
     for (InputSectionDescription *isd : sc->sc.commands)
       for (InputSectionBase *sec : isd->sectionBases)
         if (sec->parent && isa<SectionClass>(sec->parent))
-          error("section '" + sec->name + "' assigned to class '" +
-                sec->parent->name + "' but unreferenced by any output section");
+          errorOrWarn("section '" + sec->name + "' assigned to class '" +
+                      sec->parent->name +
+                      "' but unreferenced by any output section");
 }
 
 void LinkerScript::processSymbolAssignments() {
diff --git a/lld/ELF/ScriptParser.cpp b/lld/ELF/ScriptParser.cpp
index 908ad4334055c..cdb1e5a6e6592 100644
--- a/lld/ELF/ScriptParser.cpp
+++ b/lld/ELF/ScriptParser.cpp
@@ -588,7 +588,6 @@ SmallVector<SectionCommand *, 0> ScriptParser::readOverlay() {
 }
 
 SectionClassDesc *ScriptParser::readSectionClassDescription() {
-  std::string loc = getCurrentLocation();
   StringRef name = readSectionClassName();
   SectionClassDesc *desc = make<SectionClassDesc>(name);
   if (!script->sectionClasses.insert({name, desc}).second)
@@ -596,9 +595,9 @@ SectionClassDesc *ScriptParser::readSectionClassDescription() {
   expect("{");
   while (!errorCount() && !consume("}")) {
     StringRef tok = next();
-    if (tok == "(" || tok == ")")
+    if (tok == "(" || tok == ")") {
       setError("expected filename pattern");
-    else if (peek() == "(") {
+    } else if (peek() == "(") {
       InputSectionDescription *isd = readInputSectionDescription(tok);
       if (!isd->classRef.empty())
         setError("section class '" + name + "' references class '" +
diff --git a/lld/docs/ELF/linker_script.rst b/lld/docs/ELF/linker_script.rst
index c13bc0dac1fbe..c9cb47fc0553e 100644
--- a/lld/docs/ELF/linker_script.rst
+++ b/lld/docs/ELF/linker_script.rst
@@ -201,7 +201,8 @@ using ``DATA_SEGMENT_RELRO_END``.
 Section Classes
 ~~~~~~~~~~~~~~~
 
-``SECTIONS`` commands can define classes of input sections:
+The ``CLASS`` keyword inside a ``SECTIONS`` command defines classes of input
+sections:
 
 ::
 
@@ -213,7 +214,7 @@ Section Classes
     }
   }
 
-Input section descriptions can refer to a class using ``CLASS(class_name)``
+Input section descriptions refer to a class using ``CLASS(class_name)``
 instead of the usual filename and section name patterns. For example:
 
 ::
@@ -223,11 +224,11 @@ instead of the usual filename and section name patterns. For example:
     .rodata { *(.rodata) CLASS(c) (*.rodata.later) }
   }
 
-Input sections that are assigned to a class cannot be matched by later
-wildcards, 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.
+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.
@@ -240,10 +241,9 @@ Non-contiguous regions
 
 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 wildcard matches. (This globally changes the
-behavior of wildcards.) 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.
+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 685ad89a458d5..2c290e14a84fd 100644
--- a/lld/docs/ReleaseNotes.rst
+++ b/lld/docs/ReleaseNotes.rst
@@ -43,8 +43,8 @@ ELF Improvements
   regions by automatically spilling to later class references 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. (TODO: PR
-  Reference)
+  currently prevents the linker script from referring to input files.
+  (`#95323 <https://github.com/llvm/llvm-project/pull/95323>`_)
 * ``--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
diff --git a/lld/test/ELF/linkerscript/section-class.test b/lld/test/ELF/linkerscript/section-class.test
index 8afe6a87066a2..db79867899170 100644
--- a/lld/test/ELF/linkerscript/section-class.test
+++ b/lld/test/ELF/linkerscript/section-class.test
@@ -2,191 +2,6 @@
 
 # RUN: rm -rf %t && split-file %s %t && cd %t
 
-# RUN: llvm-mc -n -filetype=obj -triple=x86_64 matching.s -o matching.o
-
-## CLASS definitions match sections in linker script order. The sections may be
-## placed in a different order. Classes may derive from one another, and class
-## references may be restricted by INPUT_SECTION_FLAGS.
-
-# RUN: ld.lld -T matching.ld matching.o -o matching
-# RUN: llvm-readobj -x .rodata -x .rodata.d matching | FileCheck %s --check-prefix=MATCHING
-
-# MATCHING:      .rodata
-# MATCHING-NEXT: 020301cc 0605
-# MATCHING:      .rodata.d
-# MATCHING-NEXT: 04
-
-
-## An error is reported when a section class has more than one description.
-
-# RUN: not ld.lld -T already-defined.ld matching.o 2>&1 | \
-# RUN:   FileCheck %s --check-prefix=ALREADY-DEFINED --implicit-check-not=error:
-
-# ALREADY-DEFINED: error: already-defined.ld:3: section class 'a' already defined
-
-
-## An error is reported when a filename pattern is missing in a section class
-## description.
-
-# RUN: not ld.lld -T missing-filename-pattern.ld matching.o 2>&1 | \
-# RUN:   FileCheck %s --check-prefix=MISSING-FILENAME-PATTERN --implicit-check-not=error:
-
-# MISSING-FILENAME-PATTERN: error: missing-filename-pattern.ld:2: expected filename pattern
-
-
-## An error is reported when more than one class is mentioned in a reference.
-
-# RUN: not ld.lld -T multiple-class-names.ld matching.o 2>&1 | \
-# RUN:   FileCheck %s --check-prefix=MULTIPLE-CLASS-NAMES --implicit-check-not=error:
-
-# MULTIPLE-CLASS-NAMES: error: multiple-class-names.ld:4: ) expected, but got b
-
-
-## An error is reported when the content of section classes is demanded before
-## its definition is processed.
-
-# RUN: not ld.lld -T referenced-before-defined.ld matching.o 2>&1 | \
-# RUN:   FileCheck %s --check-prefix=REFERENCED-BEFORE-DEFINED
-
-# REFERENCED-BEFORE-DEFINED: error: section class 'a' referenced by '.rodata' before class definition
-
-## An error is reported when an input section is bound to a section class but
-## is not referenced by at least one output section.
-
-# RUN: not ld.lld -T unreferenced.ld matching.o 2>&1 | \
-# RUN:   FileCheck %s --check-prefix=UNREFERENCED
-
-# UNREFERENCED: error: section '.rodata.a' assigned to class 'a' but unreferenced by any output section
-
-
-## An error is reported when one section class references another.
-
-# RUN: not ld.lld -T class-references-class.ld matching.o 2>&1 | \
-# RUN:   FileCheck %s --check-prefix=CLASS-REFERENCES-CLASS --implicit-check-not=error:
-
-# CLASS-REFERENCES-CLASS: error: class-references-class.ld:3: section class 'b' references class 'a'
-
-
-# RUN: llvm-mc -n -filetype=obj -triple=x86_64 spill.s -o spill.o
-
-
-## 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.
-
-# RUN: ld.lld -T spill.ld 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
-
-
-## A spill off the end still fails the link.
-
-# RUN: not ld.lld -T spill-fail.ld 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
-
-
-## 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
-# 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 class ref after the first.
-
-# RUN: ld.lld -T spill-later.ld 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
-
-
-## A later overflow causes an earlier section to spill.
-
-# RUN: ld.lld -T spill-earlier.ld 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
-
-
-## 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.
-
-# RUN: ld.lld -T enable-non-contiguous-regions.ld 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
-
-
-## SHF_MERGEd sections are spilled according to the class refs 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
-# 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
-
-
-## 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
-# RUN: llvm-readobj -x .order link-order | FileCheck %s --check-prefix=LINK-ORDER
-
-# LINK-ORDER: 020301
-
-
-## An error is reported when a section might spill from INSERT.
-
-# RUN: not ld.lld -T from-insert.ld spill.o 2>&1 |\
-# RUN:   FileCheck %s --check-prefix=FROM-INSERT
-
-# FROM-INSERT: error: section '.two_byte_section' cannot spill from/to INSERT section '.b'
-
-
-## An error is reported when a section might spill to INSERT.
-
-# RUN: not ld.lld -T to-insert.ld spill.o 2>&1 |\
-# RUN:   FileCheck %s --check-prefix=TO-INSERT
-
-# TO-INSERT: error: section '.two_byte_section' cannot spill from/to INSERT section '.b'
-
-
-## An error is reported when a section might spill from /DISCARD/.
-
-# RUN: not ld.lld -T from-discard.ld spill.o 2>&1 |\
-# RUN:   FileCheck %s --check-prefix=FROM-DISCARD
-
-# FROM-DISCARD: error: section '.two_byte_section' cannot spill from/to /DISCARD/
-
-
-## An error is reported when a section might spill to /DISCARD/.
-
-# RUN: not ld.lld -T to-discard.ld spill.o 2>&1 |\
-# RUN:   FileCheck %s --check-prefix=TO-DISCARD
-
-# TO-DISCARD: error: section '.two_byte_section' cannot spill from/to /DISCARD/
-
 #--- matching.s
 .section .rodata.a,"a", at progbits
 .byte 1
@@ -207,11 +22,25 @@
 .balign 2
 .byte 6
 
-#--- matching.ld
+.section .rodata.g,"a", at progbits
+.byte 7
+
+.section .rodata.h,"a", at 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)
@@ -219,43 +48,103 @@ SECTIONS {
     CLASS(ef)
   }
   OVERLAY : { .rodata.d { INPUT_SECTION_FLAGS(!SHF_EXECINSTR) CLASS(cd) } }
+  /DISCARD/ : { CLASS(g) }
 }
 
-#--- already-defined.ld
+SECTIONS {
+  .rodata.h : { CLASS(h) }
+} INSERT AFTER .rodata;
+
+# RUN: ld.lld -T matching.lds matching.o -o matching
+# RUN: llvm-readobj -x .rodata -x .rodata.d -x .rodata.h 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) }
 }
 
-#--- missing-filename-pattern.ld
+# 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.lds
+## A filename pattern is missing in a section class description.
 SECTIONS {
   CLASS(a) { (.rodata.a) }
 }
 
-#--- multiple-class-names.ld
+# RUN: not ld.lld -T missing-filename-pattern.lds matching.o 2>&1 | \
+# RUN:   FileCheck %s --check-prefix=MISSING-FILENAME-PATTERN --implicit-check-not=error:
+
+# MISSING-FILENAME-PATTERN: error: missing-filename-pattern.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) }
 }
 
-#--- referenced-before-defined.ld
+# 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
+
+#--- referenced-before-defined.lds
+## The content of section classes is demanded before its definition is processed.
 SECTIONS {
   .rodata : { CLASS(a) }
   CLASS(a) { *(.rodata.a) }
 }
 
-#--- unreferenced.ld
+# 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 by at
+## least one output section.
 SECTIONS {
   CLASS(a) { *(.rodata.*) }
 }
 
-#--- class-references-class.ld
+# RUN: not ld.lld -T unreferenced.lds matching.o 2>&1 | \
+# RUN:   FileCheck %s --check-prefix=UNREFERENCED
+# RUN: ld.lld -T unreferenced.lds matching.o -o out --noinhibit-exec 2>&1 | \
+# RUN:   FileCheck %s --check-prefix=UNREFERENCED-WARN
+
+# UNREFERENCED: error: section '.rodata.a' assigned to class 'a' but unreferenced by any output section
+# UNREFERENCED-WARN: warning: section '.rodata.a' assigned to class 'a' but unreferenced by any output section
+
+#--- 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", at progbits
 .fill 1
@@ -263,7 +152,11 @@ SECTIONS {
 .section .two_byte_section,"a", at progbits
 .fill 2
 
-#--- spill.ld
+# 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
@@ -275,7 +168,15 @@ SECTIONS {
   .last_chance : SUBALIGN(8) { CLASS (c) } >b
 }
 
-#--- spill-fail.ld
+# 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
@@ -287,7 +188,14 @@ SECTIONS {
   .last_chance : { CLASS(c) } >b
 }
 
-#--- spill-lma.ld
+# 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
@@ -301,7 +209,15 @@ SECTIONS {
   .last_chance : { CLASS(c) } >vma_b AT>lma_b
 }
 
-#--- spill-later.ld
+# 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
@@ -315,7 +231,16 @@ SECTIONS {
   .last_chance : { CLASS(c) } >c
 }
 
-#--- spill-earlier.ld
+# 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
@@ -327,7 +252,17 @@ SECTIONS {
   .last_chance : { CLASS(c) } >b
 }
 
-#--- enable-non-contiguous-regions.ld
+# 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
@@ -346,6 +281,14 @@ SECTIONS {
   .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", at progbits,1
 .byte 0x12, 0x34
@@ -353,7 +296,11 @@ SECTIONS {
 .section .b,"aM", at progbits,1
 .byte 0x12
 
-#--- spill-merge.ld
+# 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
@@ -368,6 +315,14 @@ SECTIONS {
   .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", at progbits
 .fill 1
@@ -387,7 +342,10 @@ SECTIONS {
 .section .link_order.c,"ao", at progbits,.c
 .byte 3
 
-#--- link-order.ld
+# 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
@@ -403,7 +361,13 @@ SECTIONS {
   .actual_a : { CLASS(a) } >actual_a
 }
 
-#--- from-insert.ld
+# RUN: ld.lld -T link-order.lds link-order.o -o link-order
+# RUN: llvm-readobj -x .order 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) }
@@ -411,23 +375,58 @@ SECTIONS {
 SECTIONS { .b : { CLASS(class) } } INSERT AFTER .a;
 SECTIONS { .c : { CLASS(class) } }
 
-#--- to-insert.ld
+# 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;
 
-#--- from-discard.ld
+# 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) }
 }
 
-#--- to-discard.ld
+# 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/

>From b641ee57c252602818bcb67f5cae5e53cb98c4bb Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <dthorn at google.com>
Date: Fri, 28 Jun 2024 16:34:02 -0700
Subject: [PATCH 5/7] Add spaces around select class refs

---
 lld/test/ELF/linkerscript/section-class.test | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lld/test/ELF/linkerscript/section-class.test b/lld/test/ELF/linkerscript/section-class.test
index db79867899170..c73dad71a0895 100644
--- a/lld/test/ELF/linkerscript/section-class.test
+++ b/lld/test/ELF/linkerscript/section-class.test
@@ -43,9 +43,9 @@ SECTIONS {
   CLASS(h) { *(.rodata.h) }
   .rodata : {
     *(.rodata.*)
-    INPUT_SECTION_FLAGS(SHF_EXECINSTR) CLASS(cd)
+    INPUT_SECTION_FLAGS(SHF_EXECINSTR) CLASS( cd)
     CLASS(a)
-    CLASS(ef)
+    CLASS(ef )
   }
   OVERLAY : { .rodata.d { INPUT_SECTION_FLAGS(!SHF_EXECINSTR) CLASS(cd) } }
   /DISCARD/ : { CLASS(g) }

>From cb8b3f5975c9988f316cff26074d9d17875f06af Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <dthorn at google.com>
Date: Fri, 28 Jun 2024 17:22:41 -0700
Subject: [PATCH 6/7] Correct formatting

---
 lld/ELF/LinkerScript.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/lld/ELF/LinkerScript.cpp b/lld/ELF/LinkerScript.cpp
index cf8ce9ee49991..bce8413cb418e 100644
--- a/lld/ELF/LinkerScript.cpp
+++ b/lld/ELF/LinkerScript.cpp
@@ -608,7 +608,8 @@ LinkerScript::computeInputSections(const InputSectionDescription *cmd,
           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/");
+          errorOrWarn("section '" + sec->name +
+                      "' cannot spill from/to /DISCARD/");
           continue;
         }
         if (isSpill)

>From 042d1e91be9b64bc018566f2560d1197decc089a Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <dthorn at google.com>
Date: Mon, 22 Jul 2024 15:27:22 -0700
Subject: [PATCH 7/7] Address review comments

- Remove trailing hashes from test
- Joined test class references together to demonstrate spaces not required
- Migrate to llvm-objdump -s for content tests; add end of line checks
- Add test for missing open paren not being interpreted as a filename
- Add test for undefined section class
---
 lld/test/ELF/linkerscript/section-class.test | 41 ++++++++++++++------
 1 file changed, 29 insertions(+), 12 deletions(-)

diff --git a/lld/test/ELF/linkerscript/section-class.test b/lld/test/ELF/linkerscript/section-class.test
index c73dad71a0895..548f7f3405183 100644
--- a/lld/test/ELF/linkerscript/section-class.test
+++ b/lld/test/ELF/linkerscript/section-class.test
@@ -32,7 +32,7 @@
 
 #--- 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 # #
+## 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 {
@@ -44,8 +44,7 @@ SECTIONS {
   .rodata : {
     *(.rodata.*)
     INPUT_SECTION_FLAGS(SHF_EXECINSTR) CLASS( cd)
-    CLASS(a)
-    CLASS(ef )
+    CLASS(a)CLASS(ef )
   }
   OVERLAY : { .rodata.d { INPUT_SECTION_FLAGS(!SHF_EXECINSTR) CLASS(cd) } }
   /DISCARD/ : { CLASS(g) }
@@ -56,14 +55,14 @@ SECTIONS {
 } INSERT AFTER .rodata;
 
 # RUN: ld.lld -T matching.lds matching.o -o matching
-# RUN: llvm-readobj -x .rodata -x .rodata.d -x .rodata.h matching |\
+# RUN: llvm-objdump -s matching |\
 # RUN:   FileCheck %s --check-prefix=MATCHING
 # MATCHING:      .rodata
-# MATCHING-NEXT: 020301cc 0605
+# MATCHING-NEXT: 020301cc 0605 ......{{$}}
 # MATCHING:      .rodata.h
-# MATCHING-NEXT: 08
+# MATCHING-NEXT: 08 .{{$}}
 # MATCHING:      .rodata.d
-# MATCHING-NEXT: 04
+# MATCHING-NEXT: 04 .{{$}}
 
 #--- already-defined.lds
 ## A section class has more than one description.
@@ -79,16 +78,23 @@ SECTIONS {
 
 # ALREADY-DEFINED: error: already-defined.lds:4: section class 'a' already defined
 
-#--- missing-filename-pattern.lds
+#--- 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.lds matching.o 2>&1 | \
+# 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.lds:3: expected filename pattern
+# 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.
@@ -103,6 +109,17 @@ SECTIONS {
 
 # 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 {
@@ -362,9 +379,9 @@ SECTIONS {
 }
 
 # RUN: ld.lld -T link-order.lds link-order.o -o link-order
-# RUN: llvm-readobj -x .order link-order | FileCheck %s --check-prefix=LINK-ORDER
+# RUN: llvm-objdump -s link-order | FileCheck %s --check-prefix=LINK-ORDER
 
-# LINK-ORDER: 020301
+# LINK-ORDER: 020301 ...{{$}}
 
 #--- from-insert.lds
 ## A section might spill from INSERT.



More information about the llvm-commits mailing list