From a431dd7ae7dcf4bd8c6889291c35d14d7152caa1 Mon Sep 17 00:00:00 2001 From: Reid Kleckner Date: Fri, 24 May 2019 20:25:40 +0000 Subject: [PATCH] [COFF] De-virtualize Chunk and SectionChunk Shaves another pointer off of SectionChunk, reducing the size from 96 to 88 bytes, down from 144 before I started working on this. Combined with D62356, this reduced peak memory usage when linking chrome_child.dll from 713MB to 675MB, or 5%. Create NonSectionChunk to provide virtual dispatch to the rest of the chunk types. Reviewers: ruiu, aganea Differential Revision: https://reviews.llvm.org/D62362 llvm-svn: 361667 --- lld/COFF/Chunks.cpp | 17 ++-- lld/COFF/Chunks.h | 186 ++++++++++++++++++++++++++++++++++---------- lld/COFF/DLL.cpp | 33 ++++---- lld/COFF/Writer.cpp | 4 +- 4 files changed, 169 insertions(+), 71 deletions(-) diff --git a/lld/COFF/Chunks.cpp b/lld/COFF/Chunks.cpp index 5af3b52eda93..688d69ba087e 100644 --- a/lld/COFF/Chunks.cpp +++ b/lld/COFF/Chunks.cpp @@ -43,6 +43,8 @@ SectionChunk::SectionChunk(ObjFile *F, const coff_section *H) setAlignment(Header->getAlignment()); + HasData = !(Header->Characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA); + // If linker GC is disabled, every chunk starts out alive. If linker GC is // enabled, treat non-comdat sections as roots. Generally optimized object // files will be built with -ffunction-sections or /Gy, so most things worth @@ -53,7 +55,7 @@ SectionChunk::SectionChunk(ObjFile *F, const coff_section *H) // SectionChunk is one of the most frequently allocated classes, so it is // important to keep it as compact as possible. As of this writing, the number // below is the size of this class on x64 platforms. -static_assert(sizeof(SectionChunk) <= 96, "SectionChunk grew unexpectedly"); +static_assert(sizeof(SectionChunk) <= 88, "SectionChunk grew unexpectedly"); static void add16(uint8_t *P, int16_t V) { write16le(P, read16le(P) + V); } static void add32(uint8_t *P, int32_t V) { write32le(P, read32le(P) + V); } @@ -559,14 +561,6 @@ void SectionChunk::getRuntimePseudoRelocs( } } -bool SectionChunk::hasData() const { - return !(Header->Characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA); -} - -uint32_t SectionChunk::getOutputCharacteristics() const { - return Header->Characteristics & (PermMask | TypeMask); -} - bool SectionChunk::isCOMDAT() const { return Header->Characteristics & IMAGE_SCN_LNK_COMDAT; } @@ -578,7 +572,7 @@ void SectionChunk::printDiscardedMessage() const { message("Discarded " + Sym->getName()); } -StringRef SectionChunk::getDebugName() { +StringRef SectionChunk::getDebugName() const { if (Sym) return Sym->getName(); return ""; @@ -642,6 +636,7 @@ CommonChunk::CommonChunk(const COFFSymbolRef S) : Sym(S) { // than 32 bytes naturally, i.e. round the size up to the next power of two. // This is what MSVC link.exe does. setAlignment(std::min(32U, uint32_t(PowerOf2Ceil(Sym.getValue())))); + HasData = false; } uint32_t CommonChunk::getOutputCharacteristics() const { @@ -858,7 +853,7 @@ uint8_t Baserel::getDefaultType() { MergeChunk *MergeChunk::Instances[Log2MaxSectionAlignment + 1] = {}; MergeChunk::MergeChunk(uint32_t Alignment) - : Chunk(OtherKind), Builder(StringTableBuilder::RAW, Alignment) { + : Builder(StringTableBuilder::RAW, Alignment) { setAlignment(Alignment); } diff --git a/lld/COFF/Chunks.h b/lld/COFF/Chunks.h index 619a4886cd93..cfa71e3ff1c6 100644 --- a/lld/COFF/Chunks.h +++ b/lld/COFF/Chunks.h @@ -13,6 +13,7 @@ #include "InputFiles.h" #include "lld/Common/LLVM.h" #include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/PointerIntPair.h" #include "llvm/ADT/iterator.h" #include "llvm/ADT/iterator_range.h" #include "llvm/MC/StringTableBuilder.h" @@ -55,14 +56,16 @@ class Chunk { public: enum Kind : uint8_t { SectionKind, OtherKind }; Kind kind() const { return ChunkKind; } - virtual ~Chunk() = default; // Returns the size of this chunk (even if this is a common or BSS.) - virtual size_t getSize() const = 0; + size_t getSize() const; // Returns chunk alignment in power of two form. Value values are powers of // two from 1 to 8192. uint32_t getAlignment() const { return 1U << P2Align; } + + // Update the chunk section alignment measured in bytes. Internally alignment + // is stored in log2. void setAlignment(uint32_t Align) { // Treat zero byte alignment as 1 byte alignment. Align = Align ? Align : 1; @@ -76,7 +79,7 @@ public: // beginning of the file. Because this function may use RVA values // of other chunks for relocations, you need to set them properly // before calling this function. - virtual void writeTo(uint8_t *Buf) const {} + void writeTo(uint8_t *Buf) const; // The writer sets and uses the addresses. In practice, PE images cannot be // larger than 2GB. Chunks are always laid as part of the image, so Chunk RVAs @@ -90,16 +93,14 @@ public: // Returns true if this has non-zero data. BSS chunks return // false. If false is returned, the space occupied by this chunk // will be filled with zeros. - virtual bool hasData() const { return true; } + bool hasData() const { return HasData; } // Returns readable/writable/executable bits. - virtual uint32_t getOutputCharacteristics() const { return 0; } + uint32_t getOutputCharacteristics() const; // Returns the section name if this is a section chunk. // It is illegal to call this function on non-section chunks. - virtual StringRef getSectionName() const { - llvm_unreachable("unimplemented getSectionName"); - } + StringRef getSectionName() const; // An output section has pointers to chunks in the section, and each // chunk has a back pointer to an output section. @@ -109,22 +110,29 @@ public: // Windows-specific. // Collect all locations that contain absolute addresses for base relocations. - virtual void getBaserels(std::vector *Res) {} + void getBaserels(std::vector *Res); // Returns a human-readable name of this chunk. Chunks are unnamed chunks of // bytes, so this is used only for logging or debugging. - virtual StringRef getDebugName() { return ""; } + StringRef getDebugName() const; - virtual bool isHotPatchable() const { return false; } + // Return true if this file has the hotpatch flag set to true in the + // S_COMPILE3 record in codeview debug info. Also returns true for some thunks + // synthesized by the linker. + bool isHotPatchable() const; protected: - Chunk(Kind K = OtherKind) : ChunkKind(K) {} + Chunk(Kind K = OtherKind) : ChunkKind(K), HasData(true), P2Align(0) {} const Kind ChunkKind; + // True if the section has data. Corresponds to the + // IMAGE_SCN_CNT_UNINITIALIZED_DATA section characteristic bit. + uint8_t HasData : 1; + // The alignment of this chunk, stored in log2 form. The writer uses the // value. - uint8_t P2Align = 0; + uint8_t P2Align : 7; // The output section index for this chunk. The first valid section number is // one. @@ -134,6 +142,46 @@ protected: uint32_t RVA = 0; }; +class NonSectionChunk : public Chunk { +public: + virtual ~NonSectionChunk() = default; + + // Returns the size of this chunk (even if this is a common or BSS.) + virtual size_t getSize() const = 0; + + virtual uint32_t getOutputCharacteristics() const { return 0; } + + // Write this chunk to a mmap'ed file, assuming Buf is pointing to + // beginning of the file. Because this function may use RVA values + // of other chunks for relocations, you need to set them properly + // before calling this function. + virtual void writeTo(uint8_t *Buf) const {} + + // Returns the section name if this is a section chunk. + // It is illegal to call this function on non-section chunks. + virtual StringRef getSectionName() const { + llvm_unreachable("unimplemented getSectionName"); + } + + // Windows-specific. + // Collect all locations that contain absolute addresses for base relocations. + virtual void getBaserels(std::vector *Res) {} + + // Return true if this file has the hotpatch flag set to true in the + // S_COMPILE3 record in codeview debug info. Also returns true for some thunks + // synthesized by the linker. + virtual bool isHotPatchable() const { return false; } + + // Returns a human-readable name of this chunk. Chunks are unnamed chunks of + // bytes, so this is used only for logging or debugging. + virtual StringRef getDebugName() const { return ""; } + + static bool classof(const Chunk *C) { return C->kind() == OtherKind; } + +protected: + NonSectionChunk() : Chunk(OtherKind) {} +}; + // A chunk corresponding a section of an input file. class SectionChunk final : public Chunk { // Identical COMDAT Folding feature accesses section internal data. @@ -158,15 +206,17 @@ public: SectionChunk(ObjFile *File, const coff_section *Header); static bool classof(const Chunk *C) { return C->kind() == SectionKind; } - size_t getSize() const override { return Header->SizeOfRawData; } + size_t getSize() const { return Header->SizeOfRawData; } ArrayRef getContents() const; - void writeTo(uint8_t *Buf) const override; - bool hasData() const override; - uint32_t getOutputCharacteristics() const override; - StringRef getSectionName() const override { + void writeTo(uint8_t *Buf) const; + + uint32_t getOutputCharacteristics() const { + return Header->Characteristics & (PermMask | TypeMask); + } + StringRef getSectionName() const { return StringRef(SectionNameData, SectionNameSize); } - void getBaserels(std::vector *Res) override; + void getBaserels(std::vector *Res); bool isCOMDAT() const; void applyRelX64(uint8_t *Off, uint16_t Type, OutputSection *OS, uint64_t S, uint64_t P) const; @@ -187,7 +237,7 @@ public: // and its children are treated as a group by the garbage collector. void addAssociative(SectionChunk *Child); - StringRef getDebugName() override; + StringRef getDebugName() const; // True if this is a codeview debug info chunk. These will not be laid out in // the image. Instead they will end up in the PDB, if one is requested. @@ -200,6 +250,8 @@ public: return getSectionName().startswith(".debug_") || getSectionName() == ".eh_frame"; } + bool isHotPatchable() const { return File->HotPatchable; } + // Allow iteration over the bodies of this chunk's relocated symbols. llvm::iterator_range symbols() const { return llvm::make_range(symbol_iterator(File, RelocsData), @@ -257,8 +309,6 @@ public: static SectionChunk *findByName(ArrayRef Sections, StringRef Name); - bool isHotPatchable() const override { return File->HotPatchable; } - // The file that this chunk was created from. ObjFile *File; @@ -305,6 +355,58 @@ private: uint32_t SectionNameSize = 0; }; +// Inline methods to implement faux-virtual dispatch for SectionChunk. + +inline size_t Chunk::getSize() const { + if (isa(this)) + return static_cast(this)->getSize(); + else + return static_cast(this)->getSize(); +} + +inline uint32_t Chunk::getOutputCharacteristics() const { + if (isa(this)) + return static_cast(this)->getOutputCharacteristics(); + else + return static_cast(this) + ->getOutputCharacteristics(); +} + +inline void Chunk::writeTo(uint8_t *Buf) const { + if (isa(this)) + static_cast(this)->writeTo(Buf); + else + static_cast(this)->writeTo(Buf); +} + +inline bool Chunk::isHotPatchable() const { + if (isa(this)) + return static_cast(this)->isHotPatchable(); + else + return static_cast(this)->isHotPatchable(); +} + +inline StringRef Chunk::getSectionName() const { + if (isa(this)) + return static_cast(this)->getSectionName(); + else + return static_cast(this)->getSectionName(); +} + +inline void Chunk::getBaserels(std::vector *Res) { + if (isa(this)) + static_cast(this)->getBaserels(Res); + else + static_cast(this)->getBaserels(Res); +} + +inline StringRef Chunk::getDebugName() const { + if (isa(this)) + return static_cast(this)->getDebugName(); + else + return static_cast(this)->getDebugName(); +} + // This class is used to implement an lld-specific feature (not implemented in // MSVC) that minimizes the output size by finding string literals sharing tail // parts and merging them. @@ -314,7 +416,7 @@ private: // The MergeChunk then tail merges the strings using the StringTableBuilder // class and assigns RVAs and section offsets to each of the member chunks based // on the offsets assigned by the StringTableBuilder. -class MergeChunk : public Chunk { +class MergeChunk : public NonSectionChunk { public: MergeChunk(uint32_t Alignment); static void addSection(SectionChunk *C); @@ -335,11 +437,10 @@ private: }; // A chunk for common symbols. Common chunks don't have actual data. -class CommonChunk : public Chunk { +class CommonChunk : public NonSectionChunk { public: CommonChunk(const COFFSymbolRef Sym); size_t getSize() const override { return Sym.getValue(); } - bool hasData() const override { return false; } uint32_t getOutputCharacteristics() const override; StringRef getSectionName() const override { return ".bss"; } @@ -348,7 +449,7 @@ private: }; // A chunk for linker-created strings. -class StringChunk : public Chunk { +class StringChunk : public NonSectionChunk { public: explicit StringChunk(StringRef S) : Str(S) {} size_t getSize() const override { return Str.size() + 1; } @@ -377,7 +478,7 @@ static const uint8_t ImportThunkARM64[] = { // Windows-specific. // A chunk for DLL import jump table entry. In a final output, its // contents will be a JMP instruction to some __imp_ symbol. -class ImportThunkChunkX64 : public Chunk { +class ImportThunkChunkX64 : public NonSectionChunk { public: explicit ImportThunkChunkX64(Defined *S); size_t getSize() const override { return sizeof(ImportThunkX86); } @@ -389,9 +490,10 @@ private: Defined *ImpSymbol; }; -class ImportThunkChunkX86 : public Chunk { +class ImportThunkChunkX86 : public NonSectionChunk { public: - explicit ImportThunkChunkX86(Defined *S) : ImpSymbol(S) {} + explicit ImportThunkChunkX86(Defined *S) : ImpSymbol(S) { + } size_t getSize() const override { return sizeof(ImportThunkX86); } void getBaserels(std::vector *Res) override; void writeTo(uint8_t *Buf) const override; @@ -402,9 +504,10 @@ private: Defined *ImpSymbol; }; -class ImportThunkChunkARM : public Chunk { +class ImportThunkChunkARM : public NonSectionChunk { public: - explicit ImportThunkChunkARM(Defined *S) : ImpSymbol(S) {} + explicit ImportThunkChunkARM(Defined *S) : ImpSymbol(S) { + } size_t getSize() const override { return sizeof(ImportThunkARM); } void getBaserels(std::vector *Res) override; void writeTo(uint8_t *Buf) const override; @@ -415,9 +518,10 @@ private: Defined *ImpSymbol; }; -class ImportThunkChunkARM64 : public Chunk { +class ImportThunkChunkARM64 : public NonSectionChunk { public: - explicit ImportThunkChunkARM64(Defined *S) : ImpSymbol(S) {} + explicit ImportThunkChunkARM64(Defined *S) : ImpSymbol(S) { + } size_t getSize() const override { return sizeof(ImportThunkARM64); } void writeTo(uint8_t *Buf) const override; @@ -427,7 +531,7 @@ private: Defined *ImpSymbol; }; -class RangeExtensionThunkARM : public Chunk { +class RangeExtensionThunkARM : public NonSectionChunk { public: explicit RangeExtensionThunkARM(Defined *T) : Target(T) {} size_t getSize() const override; @@ -436,7 +540,7 @@ public: Defined *Target; }; -class RangeExtensionThunkARM64 : public Chunk { +class RangeExtensionThunkARM64 : public NonSectionChunk { public: explicit RangeExtensionThunkARM64(Defined *T) : Target(T) {} size_t getSize() const override; @@ -447,7 +551,7 @@ public: // Windows-specific. // See comments for DefinedLocalImport class. -class LocalImportChunk : public Chunk { +class LocalImportChunk : public NonSectionChunk { public: explicit LocalImportChunk(Defined *S) : Sym(S) { setAlignment(Config->Wordsize); @@ -487,7 +591,7 @@ struct ChunkAndOffset { using SymbolRVASet = llvm::DenseSet; // Table which contains symbol RVAs. Used for /safeseh and /guard:cf. -class RVATableChunk : public Chunk { +class RVATableChunk : public NonSectionChunk { public: explicit RVATableChunk(SymbolRVASet S) : Syms(std::move(S)) {} size_t getSize() const override { return Syms.size() * 4; } @@ -500,7 +604,7 @@ private: // Windows-specific. // This class represents a block in .reloc section. // See the PE/COFF spec 5.6 for details. -class BaserelChunk : public Chunk { +class BaserelChunk : public NonSectionChunk { public: BaserelChunk(uint32_t Page, Baserel *Begin, Baserel *End); size_t getSize() const override { return Data.size(); } @@ -524,7 +628,7 @@ public: // specific place in a section, without any data. This is used for the MinGW // specific symbol __RUNTIME_PSEUDO_RELOC_LIST_END__, even though the concept // of an empty chunk isn't MinGW specific. -class EmptyChunk : public Chunk { +class EmptyChunk : public NonSectionChunk { public: EmptyChunk() {} size_t getSize() const override { return 0; } @@ -537,7 +641,7 @@ public: // the reference didn't use the dllimport attribute. The MinGW runtime will // process this table after loading, before handling control over to user // code. -class PseudoRelocTableChunk : public Chunk { +class PseudoRelocTableChunk : public NonSectionChunk { public: PseudoRelocTableChunk(std::vector &Relocs) : Relocs(std::move(Relocs)) { @@ -568,7 +672,7 @@ public: }; // MinGW specific. A Chunk that contains one pointer-sized absolute value. -class AbsolutePointerChunk : public Chunk { +class AbsolutePointerChunk : public NonSectionChunk { public: AbsolutePointerChunk(uint64_t Value) : Value(Value) { setAlignment(getSize()); diff --git a/lld/COFF/DLL.cpp b/lld/COFF/DLL.cpp index b2bf53285af2..769f02e7d0d4 100644 --- a/lld/COFF/DLL.cpp +++ b/lld/COFF/DLL.cpp @@ -35,7 +35,7 @@ namespace { // Import table // A chunk for the import descriptor table. -class HintNameChunk : public Chunk { +class HintNameChunk : public NonSectionChunk { public: HintNameChunk(StringRef N, uint16_t H) : Name(N), Hint(H) {} @@ -57,7 +57,7 @@ private: }; // A chunk for the import descriptor table. -class LookupChunk : public Chunk { +class LookupChunk : public NonSectionChunk { public: explicit LookupChunk(Chunk *C) : HintName(C) { setAlignment(Config->Wordsize); @@ -77,7 +77,7 @@ public: // A chunk for the import descriptor table. // This chunk represent import-by-ordinal symbols. // See Microsoft PE/COFF spec 7.1. Import Header for details. -class OrdinalOnlyChunk : public Chunk { +class OrdinalOnlyChunk : public NonSectionChunk { public: explicit OrdinalOnlyChunk(uint16_t V) : Ordinal(V) { setAlignment(Config->Wordsize); @@ -98,7 +98,7 @@ public: }; // A chunk for the import descriptor table. -class ImportDirectoryChunk : public Chunk { +class ImportDirectoryChunk : public NonSectionChunk { public: explicit ImportDirectoryChunk(Chunk *N) : DLLName(N) {} size_t getSize() const override { return sizeof(ImportDirectoryTableEntry); } @@ -119,10 +119,9 @@ public: // A chunk representing null terminator in the import table. // Contents of this chunk is always null bytes. -class NullChunk : public Chunk { +class NullChunk : public NonSectionChunk { public: - explicit NullChunk(size_t N) : Size(N) {} - bool hasData() const override { return false; } + explicit NullChunk(size_t N) : Size(N) { HasData = false; } size_t getSize() const override { return Size; } void writeTo(uint8_t *Buf) const override { @@ -162,7 +161,7 @@ binImports(const std::vector &Imports) { // See Microsoft PE/COFF spec 4.3 for details. // A chunk for the delay import descriptor table etnry. -class DelayDirectoryChunk : public Chunk { +class DelayDirectoryChunk : public NonSectionChunk { public: explicit DelayDirectoryChunk(Chunk *N) : DLLName(N) {} @@ -274,7 +273,7 @@ static const uint8_t ThunkARM64[] = { }; // A chunk for the delay import thunk. -class ThunkChunkX64 : public Chunk { +class ThunkChunkX64 : public NonSectionChunk { public: ThunkChunkX64(Defined *I, Chunk *D, Defined *H) : Imp(I), Desc(D), Helper(H) {} @@ -293,7 +292,7 @@ public: Defined *Helper = nullptr; }; -class ThunkChunkX86 : public Chunk { +class ThunkChunkX86 : public NonSectionChunk { public: ThunkChunkX86(Defined *I, Chunk *D, Defined *H) : Imp(I), Desc(D), Helper(H) {} @@ -317,7 +316,7 @@ public: Defined *Helper = nullptr; }; -class ThunkChunkARM : public Chunk { +class ThunkChunkARM : public NonSectionChunk { public: ThunkChunkARM(Defined *I, Chunk *D, Defined *H) : Imp(I), Desc(D), Helper(H) {} @@ -341,7 +340,7 @@ public: Defined *Helper = nullptr; }; -class ThunkChunkARM64 : public Chunk { +class ThunkChunkARM64 : public NonSectionChunk { public: ThunkChunkARM64(Defined *I, Chunk *D, Defined *H) : Imp(I), Desc(D), Helper(H) {} @@ -363,7 +362,7 @@ public: }; // A chunk for the import descriptor table. -class DelayAddressChunk : public Chunk { +class DelayAddressChunk : public NonSectionChunk { public: explicit DelayAddressChunk(Chunk *C) : Thunk(C) { setAlignment(Config->Wordsize); @@ -393,7 +392,7 @@ public: // Read Microsoft PE/COFF spec 5.3 for details. // A chunk for the export descriptor table. -class ExportDirectoryChunk : public Chunk { +class ExportDirectoryChunk : public NonSectionChunk { public: ExportDirectoryChunk(int I, int J, Chunk *D, Chunk *A, Chunk *N, Chunk *O) : MaxOrdinal(I), NameTabSize(J), DLLName(D), AddressTab(A), NameTab(N), @@ -424,7 +423,7 @@ public: Chunk *OrdinalTab; }; -class AddressTableChunk : public Chunk { +class AddressTableChunk : public NonSectionChunk { public: explicit AddressTableChunk(size_t MaxOrdinal) : Size(MaxOrdinal + 1) {} size_t getSize() const override { return Size * 4; } @@ -450,7 +449,7 @@ private: size_t Size; }; -class NamePointersChunk : public Chunk { +class NamePointersChunk : public NonSectionChunk { public: explicit NamePointersChunk(std::vector &V) : Chunks(V) {} size_t getSize() const override { return Chunks.size() * 4; } @@ -466,7 +465,7 @@ private: std::vector Chunks; }; -class ExportOrdinalChunk : public Chunk { +class ExportOrdinalChunk : public NonSectionChunk { public: explicit ExportOrdinalChunk(size_t I) : Size(I) {} size_t getSize() const override { return Size * 2; } diff --git a/lld/COFF/Writer.cpp b/lld/COFF/Writer.cpp index 0e6cc9de853f..8415c38dcdc2 100644 --- a/lld/COFF/Writer.cpp +++ b/lld/COFF/Writer.cpp @@ -88,7 +88,7 @@ OutputSection *Chunk::getOutputSection() const { namespace { -class DebugDirectoryChunk : public Chunk { +class DebugDirectoryChunk : public NonSectionChunk { public: DebugDirectoryChunk(const std::vector &R, bool WriteRepro) : Records(R), WriteRepro(WriteRepro) {} @@ -143,7 +143,7 @@ private: bool WriteRepro; }; -class CVDebugRecordChunk : public Chunk { +class CVDebugRecordChunk : public NonSectionChunk { public: size_t getSize() const override { return sizeof(codeview::DebugInfo) + Config->PDBAltPath.size() + 1;