Index: lld/trunk/COFF/Chunks.h =================================================================== --- lld/trunk/COFF/Chunks.h +++ lld/trunk/COFF/Chunks.h @@ -159,13 +159,23 @@ StringRef getDebugName() override; void setSymbol(DefinedRegular *S) { if (!Sym) Sym = S; } + // Returns true if the chunk was not dropped by GC or COMDAT deduplication. + bool isLive() { return Live && !Discarded; } + // Used by the garbage collector. - bool isLive() { return !Config->DoGC || Live; } void markLive() { + assert(Config->DoGC && "should only mark things live from GC"); assert(!isLive() && "Cannot mark an already live section!"); Live = true; } + // Returns true if this chunk was dropped by COMDAT deduplication. + bool isDiscarded() const { return Discarded; } + + // Used by the SymbolTable when discarding unused comdat sections. This is + // redundant when GC is enabled, as all comdat sections will start out dead. + void markDiscarded() { Discarded = true; } + // Allow iteration over the bodies of this chunk's relocated symbols. llvm::iterator_range symbols() const { return llvm::make_range(symbol_iterator(File, Relocs.begin()), @@ -196,6 +206,9 @@ llvm::iterator_range Relocs; size_t NumRelocs; + // True if this chunk was discarded because it was a duplicate comdat section. + bool Discarded; + // Used by the garbage collector. bool Live; Index: lld/trunk/COFF/Chunks.cpp =================================================================== --- lld/trunk/COFF/Chunks.cpp +++ lld/trunk/COFF/Chunks.cpp @@ -37,8 +37,14 @@ Align = Header->getAlignment(); - // Only COMDAT sections are subject of dead-stripping. - Live = !isCOMDAT(); + // Chunks may be discarded during comdat merging. + Discarded = false; + + // 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 + // stripping will be in a comdat. + Live = !Config->DoGC || !isCOMDAT(); } static void add16(uint8_t *P, int16_t V) { write16le(P, read16le(P) + V); } @@ -226,8 +232,12 @@ void SectionChunk::printDiscardedMessage() const { // Removed by dead-stripping. If it's removed by ICF, ICF already // printed out the name, so don't repeat that here. - if (Sym && this == Repl) - message("Discarded " + Sym->getName()); + if (Sym && this == Repl) { + if (Discarded) + message("Discarded comdat symbol " + Sym->getName()); + else if (!Live) + message("Discarded " + Sym->getName()); + } } StringRef SectionChunk::getDebugName() { Index: lld/trunk/COFF/InputFiles.cpp =================================================================== --- lld/trunk/COFF/InputFiles.cpp +++ lld/trunk/COFF/InputFiles.cpp @@ -249,8 +249,12 @@ auto *Aux = reinterpret_cast(AuxP); if (Aux->Selection == IMAGE_COMDAT_SELECT_ASSOCIATIVE) if (auto *ParentSC = cast_or_null( - SparseChunks[Aux->getNumber(Sym.isBigObj())])) + SparseChunks[Aux->getNumber(Sym.isBigObj())])) { ParentSC->addAssociative(SC); + // If we already discarded the parent, discard the child. + if (ParentSC->isDiscarded()) + SC->markDiscarded(); + } SC->Checksum = Aux->CheckSum; } Index: lld/trunk/COFF/SymbolTable.cpp =================================================================== --- lld/trunk/COFF/SymbolTable.cpp +++ lld/trunk/COFF/SymbolTable.cpp @@ -244,6 +244,12 @@ reportDuplicate(S, F); } else if (SP == SP_NEW) { replaceBody(S, F, N, IsCOMDAT, /*IsExternal*/ true, Sym, C); + } else if (SP == SP_EXISTING && IsCOMDAT && C) { + C->markDiscarded(); + // Discard associative chunks that we've parsed so far. No need to recurse + // because an associative section cannot have children. + for (SectionChunk *Child : C->children()) + Child->markDiscarded(); } return S; } Index: lld/trunk/test/COFF/Inputs/associative-comdat-2.s =================================================================== --- lld/trunk/test/COFF/Inputs/associative-comdat-2.s +++ lld/trunk/test/COFF/Inputs/associative-comdat-2.s @@ -0,0 +1,13 @@ +# Defines foo and foo_assoc globals. foo is comdat, and foo_assoc is comdat +# associative with it. foo_assoc should be discarded iff foo is discarded, +# either by linker GC or normal comdat merging. + + .section .rdata,"dr",associative,foo + .p2align 3 + .quad foo + + .section .data,"dw",discard,foo + .globl foo # @foo + .p2align 2 +foo: + .long 42 Index: lld/trunk/test/COFF/associative-comdat.s =================================================================== --- lld/trunk/test/COFF/associative-comdat.s +++ lld/trunk/test/COFF/associative-comdat.s @@ -0,0 +1,46 @@ +# RUN: llvm-mc %s -filetype=obj -o %t1.obj +# RUN: llvm-mc %S/Inputs/associative-comdat-2.s -filetype=obj -o %t2.obj + +# RUN: lld-link -entry:main %t1.obj %t2.obj -out:%t.gc.exe +# RUN: llvm-readobj -sections %t.gc.exe | FileCheck %s + +# RUN: lld-link -entry:main %t1.obj %t2.obj -opt:noref -out:%t.nogc.exe +# RUN: llvm-readobj -sections %t.nogc.exe | FileCheck %s + +# CHECK: Sections [ +# CHECK: Section { +# CHECK: Number: 1 +# CHECK-LABEL: Name: .data (2E 64 61 74 61 00 00 00) +# CHECK-NEXT: VirtualSize: 0x4 +# CHECK: Section { +# CHECK-LABEL: Name: .rdata (2E 72 64 61 74 61 00 00) +# This is the critical check to show that only *one* definition of +# foo_assoc was retained. This *must* be 8, not 16. +# CHECK-NEXT: VirtualSize: 0x8 + + .text + .def main; + .scl 2; + .type 32; + .endef + .globl main # -- Begin function main + .p2align 4, 0x90 +main: # @main +# BB#0: + movl foo(%rip), %eax + retq + # -- End function + +# Defines foo and foo_assoc globals. foo is comdat, and foo_assoc is comdat +# associative with it. foo_assoc should be discarded iff foo is discarded, +# either by linker GC or normal comdat merging. + + .section .rdata,"dr",associative,foo + .p2align 3 + .quad foo + + .section .data,"dw",discard,foo + .globl foo # @foo + .p2align 2 +foo: + .long 42