[lld] 2515cb8 - [ELF] Parallelize input section initialization

Fangrui Song via llvm-commits llvm-commits at lists.llvm.org
Thu Aug 4 11:47:59 PDT 2022


Author: Fangrui Song
Date: 2022-08-04T11:47:52-07:00
New Revision: 2515cb80cd3ba3bcead6ec2f1f30437cc2d325d5

URL: https://github.com/llvm/llvm-project/commit/2515cb80cd3ba3bcead6ec2f1f30437cc2d325d5
DIFF: https://github.com/llvm/llvm-project/commit/2515cb80cd3ba3bcead6ec2f1f30437cc2d325d5.diff

LOG: [ELF] Parallelize input section initialization

This implements the last step of
https://discourse.llvm.org/t/parallel-input-file-parsing/60164 for the ELF port.

For an ELF object file, we previously did: parse, (parallel) initializeLocalSymbols, (parallel) postParseObjectFile.
Now we do: parse, (parallel) initSectionsAndLocalSyms, (parallel) postParseObjectFile.

initSectionsAndLocalSyms does most of input section initialization.
The sequential `parse` does SHT_ARM_ATTRIBUTES/SHT_RISCV_ATTRIBUTES/SHT_GROUP initialization for now.

Performance linking some programs with --threads=8 (glibc 2.33 malloc and mimalloc):

* clang: 1.05x as fast with glibc malloc, 1.03x as fast with mimalloc
* chrome: 1.04x as fast with glibc malloc, 1.03x as fast with mimalloc
* internal search program: 1.08x as fast with glibc malloc, 1.05x as fast with mimalloc

Reviewed By: peter.smith

Differential Revision: https://reviews.llvm.org/D130810

Added: 
    

Modified: 
    lld/ELF/Driver.cpp
    lld/ELF/InputFiles.cpp
    lld/ELF/InputFiles.h
    lld/ELF/InputSection.cpp

Removed: 
    


################################################################################
diff  --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp
index e0007846cbea..1f7c56dd389b 100644
--- a/lld/ELF/Driver.cpp
+++ b/lld/ELF/Driver.cpp
@@ -2393,19 +2393,19 @@ static uint32_t getAndFeatures() {
   return ret;
 }
 
-static void initializeLocalSymbols(ELFFileBase *file) {
+static void initSectionsAndLocalSyms(ELFFileBase *file, bool ignoreComdats) {
   switch (config->ekind) {
   case ELF32LEKind:
-    cast<ObjFile<ELF32LE>>(file)->initializeLocalSymbols();
+    cast<ObjFile<ELF32LE>>(file)->initSectionsAndLocalSyms(ignoreComdats);
     break;
   case ELF32BEKind:
-    cast<ObjFile<ELF32BE>>(file)->initializeLocalSymbols();
+    cast<ObjFile<ELF32BE>>(file)->initSectionsAndLocalSyms(ignoreComdats);
     break;
   case ELF64LEKind:
-    cast<ObjFile<ELF64LE>>(file)->initializeLocalSymbols();
+    cast<ObjFile<ELF64LE>>(file)->initSectionsAndLocalSyms(ignoreComdats);
     break;
   case ELF64BEKind:
-    cast<ObjFile<ELF64BE>>(file)->initializeLocalSymbols();
+    cast<ObjFile<ELF64BE>>(file)->initSectionsAndLocalSyms(ignoreComdats);
     break;
   default:
     llvm_unreachable("");
@@ -2556,7 +2556,9 @@ void LinkerDriver::link(opt::InputArgList &args) {
 
   // No more lazy bitcode can be extracted at this point. Do post parse work
   // like checking duplicate symbols.
-  parallelForEach(ctx->objectFiles, initializeLocalSymbols);
+  parallelForEach(ctx->objectFiles, [](ELFFileBase *file) {
+    initSectionsAndLocalSyms(file, /*ignoreComdats=*/false);
+  });
   parallelForEach(ctx->objectFiles, postParseObjectFile);
   parallelForEach(ctx->bitcodeFiles,
                   [](BitcodeFile *file) { file->postParse(); });
@@ -2640,7 +2642,9 @@ void LinkerDriver::link(opt::InputArgList &args) {
   // compileBitcodeFiles may have produced lto.tmp object files. After this, no
   // more file will be added.
   auto newObjectFiles = makeArrayRef(ctx->objectFiles).slice(numObjsBeforeLTO);
-  parallelForEach(newObjectFiles, initializeLocalSymbols);
+  parallelForEach(newObjectFiles, [](ELFFileBase *file) {
+    initSectionsAndLocalSyms(file, /*ignoreComdats=*/true);
+  });
   parallelForEach(newObjectFiles, postParseObjectFile);
   for (const DuplicateSymbol &d : ctx->duplicates)
     reportDuplicate(*d.sym, d.file, d.section, d.value);

diff  --git a/lld/ELF/InputFiles.cpp b/lld/ELF/InputFiles.cpp
index 560e24f5e923..36500652d07f 100644
--- a/lld/ELF/InputFiles.cpp
+++ b/lld/ELF/InputFiles.cpp
@@ -516,10 +516,121 @@ uint32_t ObjFile<ELFT>::getSectionIndex(const Elf_Sym &sym) const {
 template <class ELFT> void ObjFile<ELFT>::parse(bool ignoreComdats) {
   object::ELFFile<ELFT> obj = this->getObj();
   // Read a section table. justSymbols is usually false.
-  if (this->justSymbols)
+  if (this->justSymbols) {
     initializeJustSymbols();
-  else
-    initializeSections(ignoreComdats, obj);
+    initializeSymbols(obj);
+    return;
+  }
+
+  // Handle dependent libraries and selection of section groups as these are not
+  // done in parallel.
+  ArrayRef<Elf_Shdr> objSections = getELFShdrs<ELFT>();
+  StringRef shstrtab = CHECK(obj.getSectionStringTable(objSections), this);
+  uint64_t size = objSections.size();
+  sections.resize(size);
+  for (size_t i = 0; i != size; ++i) {
+    const Elf_Shdr &sec = objSections[i];
+    if (sec.sh_type == SHT_LLVM_DEPENDENT_LIBRARIES && !config->relocatable) {
+      StringRef name = check(obj.getSectionName(sec, shstrtab));
+      ArrayRef<char> data = CHECK(
+          this->getObj().template getSectionContentsAsArray<char>(sec), this);
+      if (!data.empty() && data.back() != '\0') {
+        error(
+            toString(this) +
+            ": corrupted dependent libraries section (unterminated string): " +
+            name);
+      } else {
+        for (const char *d = data.begin(), *e = data.end(); d < e;) {
+          StringRef s(d);
+          addDependentLibrary(s, this);
+          d += s.size() + 1;
+        }
+      }
+      this->sections[i] = &InputSection::discarded;
+      continue;
+    }
+
+    if (sec.sh_type == SHT_ARM_ATTRIBUTES && config->emachine == EM_ARM) {
+      ARMAttributeParser attributes;
+      ArrayRef<uint8_t> contents =
+          check(this->getObj().getSectionContents(sec));
+      StringRef name = check(obj.getSectionName(sec, shstrtab));
+      this->sections[i] = &InputSection::discarded;
+      if (Error e = attributes.parse(contents, config->ekind == ELF32LEKind
+                                                   ? support::little
+                                                   : support::big)) {
+        InputSection isec(*this, sec, name);
+        warn(toString(&isec) + ": " + llvm::toString(std::move(e)));
+      } else {
+        updateSupportedARMFeatures(attributes);
+        updateARMVFPArgs(attributes, this);
+
+        // FIXME: Retain the first attribute section we see. The eglibc ARM
+        // dynamic loaders require the presence of an attribute section for
+        // dlopen to work. In a full implementation we would merge all attribute
+        // sections.
+        if (in.attributes == nullptr) {
+          in.attributes = std::make_unique<InputSection>(*this, sec, name);
+          this->sections[i] = in.attributes.get();
+        }
+      }
+    }
+
+    if (sec.sh_type == SHT_RISCV_ATTRIBUTES && config->emachine == EM_RISCV) {
+      RISCVAttributeParser attributes;
+      ArrayRef<uint8_t> contents =
+          check(this->getObj().getSectionContents(sec));
+      StringRef name = check(obj.getSectionName(sec, shstrtab));
+      this->sections[i] = &InputSection::discarded;
+      if (Error e = attributes.parse(contents, support::little)) {
+        InputSection isec(*this, sec, name);
+        warn(toString(&isec) + ": " + llvm::toString(std::move(e)));
+      } else {
+        // FIXME: Validate arch tag contains C if and only if EF_RISCV_RVC is
+        // present.
+
+        // FIXME: Retain the first attribute section we see. Tools such as
+        // llvm-objdump make use of the attribute section to determine which
+        // standard extensions to enable. In a full implementation we would
+        // merge all attribute sections.
+        if (in.attributes == nullptr) {
+          in.attributes = std::make_unique<InputSection>(*this, sec, name);
+          this->sections[i] = in.attributes.get();
+        }
+      }
+    }
+
+    if (sec.sh_type != SHT_GROUP)
+      continue;
+    StringRef signature = getShtGroupSignature(objSections, sec);
+    ArrayRef<Elf_Word> entries =
+        CHECK(obj.template getSectionContentsAsArray<Elf_Word>(sec), this);
+    if (entries.empty())
+      fatal(toString(this) + ": empty SHT_GROUP");
+
+    Elf_Word flag = entries[0];
+    if (flag && flag != GRP_COMDAT)
+      fatal(toString(this) + ": unsupported SHT_GROUP format");
+
+    bool keepGroup =
+        (flag & GRP_COMDAT) == 0 || ignoreComdats ||
+        symtab->comdatGroups.try_emplace(CachedHashStringRef(signature), this)
+            .second;
+    if (keepGroup) {
+      if (config->relocatable)
+        this->sections[i] = createInputSection(
+            i, sec, check(obj.getSectionName(sec, shstrtab)));
+      continue;
+    }
+
+    // Otherwise, discard group members.
+    for (uint32_t secIndex : entries.slice(1)) {
+      if (secIndex >= size)
+        fatal(toString(this) +
+              ": invalid section index in group: " + Twine(secIndex));
+      this->sections[secIndex] = &InputSection::discarded;
+    }
+  }
 
   // Read a symbol table.
   initializeSymbols(obj);
@@ -601,10 +712,7 @@ void ObjFile<ELFT>::initializeSections(bool ignoreComdats,
   ArrayRef<Elf_Shdr> objSections = getELFShdrs<ELFT>();
   StringRef shstrtab = CHECK(obj.getSectionStringTable(objSections), this);
   uint64_t size = objSections.size();
-  this->sections.resize(size);
-
-  std::vector<ArrayRef<Elf_Word>> selectedGroups;
-
+  SmallVector<ArrayRef<Elf_Word>, 0> selectedGroups;
   for (size_t i = 0; i != size; ++i) {
     if (this->sections[i] == &InputSection::discarded)
       continue;
@@ -637,38 +745,16 @@ void ObjFile<ELFT>::initializeSections(bool ignoreComdats,
 
     switch (sec.sh_type) {
     case SHT_GROUP: {
-      // De-duplicate section groups by their signatures.
-      StringRef signature = getShtGroupSignature(objSections, sec);
-      this->sections[i] = &InputSection::discarded;
-
+      if (!config->relocatable)
+        sections[i] = &InputSection::discarded;
+      StringRef signature =
+          cantFail(this->getELFSyms<ELFT>()[sec.sh_info].getName(stringTable));
       ArrayRef<Elf_Word> entries =
-          CHECK(obj.template getSectionContentsAsArray<Elf_Word>(sec), this);
-      if (entries.empty())
-        fatal(toString(this) + ": empty SHT_GROUP");
-
-      Elf_Word flag = entries[0];
-      if (flag && flag != GRP_COMDAT)
-        fatal(toString(this) + ": unsupported SHT_GROUP format");
-
-      bool keepGroup =
-          (flag & GRP_COMDAT) == 0 || ignoreComdats ||
-          symtab->comdatGroups.try_emplace(CachedHashStringRef(signature), this)
-              .second;
-      if (keepGroup) {
-        if (config->relocatable)
-          this->sections[i] = createInputSection(
-              i, sec, check(obj.getSectionName(sec, shstrtab)));
+          cantFail(obj.template getSectionContentsAsArray<Elf_Word>(sec));
+      if ((entries[0] & GRP_COMDAT) == 0 || ignoreComdats ||
+          symtab->comdatGroups.find(CachedHashStringRef(signature))->second ==
+              this)
         selectedGroups.push_back(entries);
-        continue;
-      }
-
-      // Otherwise, discard group members.
-      for (uint32_t secIndex : entries.slice(1)) {
-        if (secIndex >= size)
-          fatal(toString(this) +
-                ": invalid section index in group: " + Twine(secIndex));
-        this->sections[secIndex] = &InputSection::discarded;
-      }
       break;
     }
     case SHT_SYMTAB_SHNDX:
@@ -718,8 +804,8 @@ void ObjFile<ELFT>::initializeSections(bool ignoreComdats,
       // simply handle such sections as non-mergeable ones. Degrading like this
       // is acceptable because section merging is optional.
       if (auto *ms = dyn_cast<MergeInputSection>(s)) {
-        s = make<InputSection>(ms->file, ms->flags, ms->type, ms->alignment,
-                               ms->data(), ms->name);
+        s = makeThreadLocal<InputSection>(ms->file, ms->flags, ms->type,
+                                          ms->alignment, ms->data(), ms->name);
         sections[info] = s;
       }
 
@@ -734,7 +820,7 @@ void ObjFile<ELFT>::initializeSections(bool ignoreComdats,
       // specified, we need to copy them to the output. (Some post link analysis
       // tools specify --emit-relocs to obtain the information.)
       if (config->copyRelocs) {
-        auto *isec = make<InputSection>(
+        auto *isec = makeThreadLocal<InputSection>(
             *this, sec, check(obj.getSectionName(sec, shstrtab)));
         // If the relocated section is discarded (due to /DISCARD/ or
         // --gc-sections), the relocation section should be discarded as well.
@@ -861,73 +947,12 @@ InputSectionBase *ObjFile<ELFT>::getRelocTarget(uint32_t idx,
   return nullptr;
 }
 
+// The function may be called concurrently for 
diff erent input files. For
+// allocation, prefer makeThreadLocal which does not require holding a lock.
 template <class ELFT>
 InputSectionBase *ObjFile<ELFT>::createInputSection(uint32_t idx,
                                                     const Elf_Shdr &sec,
                                                     StringRef name) {
-  if (sec.sh_type == SHT_ARM_ATTRIBUTES && config->emachine == EM_ARM) {
-    ARMAttributeParser attributes;
-    ArrayRef<uint8_t> contents = check(this->getObj().getSectionContents(sec));
-    if (Error e = attributes.parse(contents, config->ekind == ELF32LEKind
-                                                 ? support::little
-                                                 : support::big)) {
-      InputSection isec(*this, sec, name);
-      warn(toString(&isec) + ": " + llvm::toString(std::move(e)));
-    } else {
-      updateSupportedARMFeatures(attributes);
-      updateARMVFPArgs(attributes, this);
-
-      // FIXME: Retain the first attribute section we see. The eglibc ARM
-      // dynamic loaders require the presence of an attribute section for dlopen
-      // to work. In a full implementation we would merge all attribute
-      // sections.
-      if (in.attributes == nullptr) {
-        in.attributes = std::make_unique<InputSection>(*this, sec, name);
-        return in.attributes.get();
-      }
-      return &InputSection::discarded;
-    }
-  }
-
-  if (sec.sh_type == SHT_RISCV_ATTRIBUTES && config->emachine == EM_RISCV) {
-    RISCVAttributeParser attributes;
-    ArrayRef<uint8_t> contents = check(this->getObj().getSectionContents(sec));
-    if (Error e = attributes.parse(contents, support::little)) {
-      InputSection isec(*this, sec, name);
-      warn(toString(&isec) + ": " + llvm::toString(std::move(e)));
-    } else {
-      // FIXME: Validate arch tag contains C if and only if EF_RISCV_RVC is
-      // present.
-
-      // FIXME: Retain the first attribute section we see. Tools such as
-      // llvm-objdump make use of the attribute section to determine which
-      // standard extensions to enable. In a full implementation we would merge
-      // all attribute sections.
-      if (in.attributes == nullptr) {
-        in.attributes = std::make_unique<InputSection>(*this, sec, name);
-        return in.attributes.get();
-      }
-      return &InputSection::discarded;
-    }
-  }
-
-  if (sec.sh_type == SHT_LLVM_DEPENDENT_LIBRARIES && !config->relocatable) {
-    ArrayRef<char> data =
-        CHECK(this->getObj().template getSectionContentsAsArray<char>(sec), this);
-    if (!data.empty() && data.back() != '\0') {
-      error(toString(this) +
-            ": corrupted dependent libraries section (unterminated string): " +
-            name);
-      return &InputSection::discarded;
-    }
-    for (const char *d = data.begin(), *e = data.end(); d < e;) {
-      StringRef s(d);
-      addDependentLibrary(s, this);
-      d += s.size() + 1;
-    }
-    return &InputSection::discarded;
-  }
-
   if (name.startswith(".n")) {
     // The GNU linker uses .note.GNU-stack section as a marker indicating
     // that the code in the object file does not expect that the stack is
@@ -993,11 +1018,11 @@ InputSectionBase *ObjFile<ELFT>::createInputSection(uint32_t idx,
   // .eh_frame_hdr section for runtime. So we handle them with a special
   // class. For relocatable outputs, they are just passed through.
   if (name == ".eh_frame" && !config->relocatable)
-    return make<EhInputSection>(*this, sec, name);
+    return makeThreadLocal<EhInputSection>(*this, sec, name);
 
   if ((sec.sh_flags & SHF_MERGE) && shouldMerge(sec, name))
-    return make<MergeInputSection>(*this, sec, name);
-  return make<InputSection>(*this, sec, name);
+    return makeThreadLocal<MergeInputSection>(*this, sec, name);
+  return makeThreadLocal<InputSection>(*this, sec, name);
 }
 
 // Initialize this->Symbols. this->Symbols is a parallel array as
@@ -1063,7 +1088,11 @@ void ObjFile<ELFT>::initializeSymbols(const object::ELFFile<ELFT> &obj) {
   }
 }
 
-template <class ELFT> void ObjFile<ELFT>::initializeLocalSymbols() {
+template <class ELFT>
+void ObjFile<ELFT>::initSectionsAndLocalSyms(bool ignoreComdats) {
+  if (!justSymbols)
+    initializeSections(ignoreComdats, getObj());
+
   if (!firstGlobal)
     return;
   SymbolUnion *locals = makeThreadLocalN<SymbolUnion>(firstGlobal);

diff  --git a/lld/ELF/InputFiles.h b/lld/ELF/InputFiles.h
index dd9f7328a103..0cb6a67ca00b 100644
--- a/lld/ELF/InputFiles.h
+++ b/lld/ELF/InputFiles.h
@@ -274,7 +274,7 @@ template <class ELFT> class ObjFile : public ELFFileBase {
   // Get cached DWARF information.
   DWARFCache *getDwarf();
 
-  void initializeLocalSymbols();
+  void initSectionsAndLocalSyms(bool ignoreComdats);
   void postParse();
 
 private:

diff  --git a/lld/ELF/InputSection.cpp b/lld/ELF/InputSection.cpp
index 57bb48561dba..71810316eee3 100644
--- a/lld/ELF/InputSection.cpp
+++ b/lld/ELF/InputSection.cpp
@@ -283,7 +283,8 @@ std::string InputSectionBase::getObjMsg(uint64_t off) {
     archive = (" in archive " + file->archiveName).str();
 
   // Find a symbol that encloses a given location. getObjMsg may be called
-  // before ObjFile::initializeLocalSymbols where local symbols are initialized.
+  // before ObjFile::initSectionsAndLocalSyms where local symbols are
+  // initialized.
   for (Symbol *b : file->getSymbols())
     if (auto *d = dyn_cast_or_null<Defined>(b))
       if (d->section == this && d->value <= off && off < d->value + d->size)


        


More information about the llvm-commits mailing list