diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp --- a/lld/ELF/Driver.cpp +++ b/lld/ELF/Driver.cpp @@ -1989,6 +1989,11 @@ // Two input sections with different output sections should not be folded. // ICF runs after processSectionCommands() so that we know the output sections. if (config->icf != ICFLevel::None) { + // Compute isPreemptible early to be used by ICF. We may add more symbols + // later, so this loop cannot be merged with the later computeIsPreemptible + // pass which is used by scanRelocations(). + for (Symbol *sym : symtab->symbols()) + sym->isPreemptible = computeIsPreemptible(*sym); findKeepUniqueSections(args); doIcf(); } diff --git a/lld/ELF/ICF.cpp b/lld/ELF/ICF.cpp --- a/lld/ELF/ICF.cpp +++ b/lld/ELF/ICF.cpp @@ -259,6 +259,9 @@ if (!da || !db || da->scriptDefined || db->scriptDefined) return false; + if (da->isPreemptible || db->isPreemptible) + return false; + // Relocations referring to absolute symbols are constant-equal if their // values are equal. if (!da->section && !db->section && da->value + addA == db->value + addB) diff --git a/lld/ELF/Symbols.h b/lld/ELF/Symbols.h --- a/lld/ELF/Symbols.h +++ b/lld/ELF/Symbols.h @@ -554,6 +554,8 @@ } void maybeWarnUnorderableSymbol(const Symbol *sym); +bool computeIsPreemptible(const Symbol &s); + } // namespace elf } // namespace lld diff --git a/lld/ELF/Symbols.cpp b/lld/ELF/Symbols.cpp --- a/lld/ELF/Symbols.cpp +++ b/lld/ELF/Symbols.cpp @@ -331,6 +331,37 @@ report(": unable to order discarded symbol: "); } +// Returns true if a symbol can be replaced at load-time by a symbol +// with the same name defined in other ELF executable or DSO. +bool computeIsPreemptible(const Symbol &s) { + assert(!s.isLocal()); + + // Only symbols that appear in dynsym can be preempted. + if (!s.includeInDynsym()) + return false; + + // Only default visibility symbols can be preempted. + if (s.visibility != STV_DEFAULT) + return false; + + // At this point copy relocations have not been created yet, so any + // symbol that is not defined locally is preemptible. + if (!s.isDefined()) + return true; + + if (!config->shared) + return false; + + // If the dynamic list is present, it specifies preemptable symbols in a DSO. + if (config->hasDynamicList) + return s.inDynamicList; + + // -Bsymbolic means that definitions are not preempted. + if (config->bsymbolic || (config->bsymbolicFunctions && s.isFunc())) + return false; + return true; +} + static uint8_t getMinVisibility(uint8_t va, uint8_t vb) { if (va == STV_DEFAULT) return vb; diff --git a/lld/ELF/Writer.cpp b/lld/ELF/Writer.cpp --- a/lld/ELF/Writer.cpp +++ b/lld/ELF/Writer.cpp @@ -1636,37 +1636,6 @@ } } -// Returns true if a symbol can be replaced at load-time by a symbol -// with the same name defined in other ELF executable or DSO. -static bool computeIsPreemptible(const Symbol &b) { - assert(!b.isLocal()); - - // Only symbols that appear in dynsym can be preempted. - if (!b.includeInDynsym()) - return false; - - // Only default visibility symbols can be preempted. - if (b.visibility != STV_DEFAULT) - return false; - - // At this point copy relocations have not been created yet, so any - // symbol that is not defined locally is preemptible. - if (!b.isDefined()) - return true; - - if (!config->shared) - return false; - - // If the dynamic list is present, it specifies preemptable symbols in a DSO. - if (config->hasDynamicList) - return b.inDynamicList; - - // -Bsymbolic means that definitions are not preempted. - if (config->bsymbolic || (config->bsymbolicFunctions && b.isFunc())) - return false; - return true; -} - // Create output section objects and add them to OutputSections. template void Writer::finalizeSections() { Out::preinitArray = findSection(".preinit_array"); diff --git a/lld/test/ELF/icf-preemptible.s b/lld/test/ELF/icf-preemptible.s new file mode 100644 --- /dev/null +++ b/lld/test/ELF/icf-preemptible.s @@ -0,0 +1,44 @@ +# REQUIRES: x86 +# RUN: llvm-mc -filetype=obj -triple=x86_64 %s -o %t.o + +## Non-preemptible +# RUN: ld.lld -pie %t.o --icf=all --print-icf-sections -o /dev/null | \ +# RUN: FileCheck --check-prefixes=LEAF,NONLEAF %s + +## Preemptible +# RUN: ld.lld -shared %t.o --icf=all --print-icf-sections -o /dev/null | \ +# RUN: FileCheck --check-prefix=LEAF %s --implicit-check-not=removing + +# NONLEAF: selected section {{.*}}:(.text.h1) +# NONLEAF-NEXT: removing identical section {{.*}}:(.text.h2) +# NONLEAF-NEXT: removing identical section {{.*}}:(.text.h3) +# LEAF: selected section {{.*}}:(.text.f1) +# LEAF-NEXT: removing identical section {{.*}}:(.text.f2) +# LEAF-NEXT: selected section {{.*}}:(.text.g1) +# NONLEAF: removing identical section {{.*}}:(.text.g2) +# LEAF-NEXT: removing identical section {{.*}}:(.text.g3) + +.globl f1, f2, g1, g2, g3 + +.section .text.f1 +f1: ret +.section .text.f2 +f2: ret + +## In -shared mode, .text.g1 and .text.g2 cannot be folded because f1 and f2 are +## preemptible and may refer to different functions at runtime. +.section .text.g1 +g1: jmp f1@plt +.section .text.g2 +g2: jmp f2@plt +.section .text.g3 +g3: jmp f1@plt + +## In -shared mode, the sections below cannot be folded because g1, g2 and g3 +## are preemptible and may refer to different functions at runtime. +.section .text.h1 +jmp g1@plt +.section .text.h2 +jmp g2@plt +.section .text.h3 +jmp g3@plt