diff --git a/lld/COFF/Config.h b/lld/COFF/Config.h --- a/lld/COFF/Config.h +++ b/lld/COFF/Config.h @@ -80,6 +80,12 @@ Full, // Enable all protections. }; +enum class ICFLevel { + None, + Safe, + All, +}; + // Global configuration. struct Configuration { enum ManifestKind { SideBySide, Embed, No }; @@ -95,7 +101,7 @@ std::string importName; bool demangle = true; bool doGC = true; - bool doICF = true; + ICFLevel doICF = ICFLevel::None; bool tailMerge; bool relocatable = true; bool forceMultiple = false; diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp --- a/lld/COFF/Driver.cpp +++ b/lld/COFF/Driver.cpp @@ -1076,7 +1076,7 @@ markAddrsig(syms[symIndex]); cur += size; } - } else { + } else if (config->doICF == ICFLevel::Safe) { // If an object file does not have an address-significance table, // conservatively mark all of its symbols as address-significant. for (Symbol *s : syms) @@ -1553,7 +1553,7 @@ // Handle /opt. bool doGC = debug == DebugKind::None || args.hasArg(OPT_profile); unsigned icfLevel = - args.hasArg(OPT_profile) ? 0 : 1; // 0: off, 1: limited, 2: on + args.hasArg(OPT_profile) ? 0 : 1; // 0: off, 1: limited, 2: on, 3: safe unsigned tailMerge = 1; bool ltoNewPM = LLVM_ENABLE_NEW_PASS_MANAGER; bool ltoDebugPM = false; @@ -1568,6 +1568,8 @@ doGC = false; } else if (s == "icf" || s.startswith("icf=")) { icfLevel = 2; + } else if (s == "safeicf") { + icfLevel = 3; } else if (s == "noicf") { icfLevel = 0; } else if (s == "lldtailmerge") { @@ -1609,8 +1611,11 @@ if (icfLevel == 1 && !doGC) icfLevel = 0; config->doGC = doGC; - config->doICF = icfLevel > 0; - config->tailMerge = (tailMerge == 1 && config->doICF) || tailMerge == 2; + config->doICF = icfLevel == 0 + ? ICFLevel::None + : (icfLevel == 3 ? ICFLevel::Safe : ICFLevel::All); + config->tailMerge = + (tailMerge == 1 && config->doICF != ICFLevel::None) || tailMerge == 2; config->ltoNewPassManager = ltoNewPM; config->ltoDebugPassManager = ltoDebugPM; @@ -1719,8 +1724,8 @@ args.hasFlag(OPT_allowisolation, OPT_allowisolation_no, true); config->incremental = args.hasFlag(OPT_incremental, OPT_incremental_no, - !config->doGC && !config->doICF && !args.hasArg(OPT_order) && - !args.hasArg(OPT_profile)); + !config->doGC && config->doICF == ICFLevel::None && + !args.hasArg(OPT_order) && !args.hasArg(OPT_profile)); config->integrityCheck = args.hasFlag(OPT_integritycheck, OPT_integritycheck_no, false); config->cetCompat = args.hasFlag(OPT_cetcompat, OPT_cetcompat_no, false); @@ -1769,7 +1774,7 @@ config->incremental = false; } - if (config->incremental && config->doICF) { + if (config->incremental && config->doICF != ICFLevel::None) { warn("ignoring '/incremental' because ICF is enabled; use '/opt:noicf' to " "disable"); config->incremental = false; @@ -2212,7 +2217,7 @@ convertResources(); // Identify identical COMDAT sections to merge them. - if (config->doICF) { + if (config->doICF != ICFLevel::None) { findKeepUniqueSections(); doICF(symtab->getChunks()); } diff --git a/lld/COFF/ICF.cpp b/lld/COFF/ICF.cpp --- a/lld/COFF/ICF.cpp +++ b/lld/COFF/ICF.cpp @@ -78,7 +78,7 @@ bool ICF::isEligible(SectionChunk *c) { // Non-comdat chunks, dead chunks, and writable chunks are not eligible. bool writable = c->getOutputCharacteristics() & llvm::COFF::IMAGE_SCN_MEM_WRITE; - if (!c->isCOMDAT() || !c->live || writable) + if (!c->isCOMDAT() || !c->live || writable || c->keepUnique) return false; // Code sections are eligible. @@ -94,8 +94,8 @@ if (c->sym && c->sym->getName().startswith("??_7")) return true; - // Anything else not in an address-significance table is eligible. - return !c->keepUnique; + // Anything else is eligible. + return true; } // Split an equivalence class into smaller classes. diff --git a/lld/test/COFF/icf-data.test b/lld/test/COFF/icf-data.test --- a/lld/test/COFF/icf-data.test +++ b/lld/test/COFF/icf-data.test @@ -1,6 +1,6 @@ # RUN: yaml2obj %s -o %t.obj # RUN: lld-link /entry:foo /out:%t.exe /subsystem:console /include:bar \ -# RUN: /verbose %t.obj > %t.log 2>&1 +# RUN: /verbose /opt:safeicf %t.obj > %t.log 2>&1 # RUN: FileCheck %s < %t.log # CHECK-NOT: Removed foo diff --git a/lld/test/COFF/icf-local.test b/lld/test/COFF/icf-local.test --- a/lld/test/COFF/icf-local.test +++ b/lld/test/COFF/icf-local.test @@ -3,7 +3,7 @@ # RUN: yaml2obj %s -o %t1.obj # RUN: sed s/foo/main/ %s | yaml2obj > %t2.obj # RUN: lld-link /out:%t.exe /entry:main /subsystem:console /verbose \ -# RUN: %t1.obj %t2.obj > %t.log 2>&1 +# RUN: /opt:safeicf %t1.obj %t2.obj > %t.log 2>&1 # RUN: FileCheck %s < %t.log # CHECK-NOT: Removed bar diff --git a/lld/test/COFF/icf-pdata.s b/lld/test/COFF/icf-pdata.s --- a/lld/test/COFF/icf-pdata.s +++ b/lld/test/COFF/icf-pdata.s @@ -1,20 +1,14 @@ # REQUIRES: x86 # RUN: llvm-mc %s -triple x86_64-windows-msvc -filetype=obj -o %t.obj -# RUN: lld-link %t.obj -dll -noentry -out:%t.dll -merge:.xdata=.xdata -# RUN: llvm-readobj --sections --coff-exports %t.dll | FileCheck %s +# RUN: lld-link %t.obj -dll -noentry -out:%t.dll -merge:.xdata=.xdata \ +# RUN: -opt:icf,noref +# RUN: llvm-readobj --sections %t.dll | FileCheck %s # CHECK: Name: .pdata # CHECK-NEXT: VirtualSize: 0x18 # CHECK: Name: .xdata # CHECK-NEXT: VirtualSize: 0x10 -# CHECK: Name: xdata1 -# CHECK-NEXT: RVA: 0x1010 -# CHECK: Name: xdata1a -# CHECK-NEXT: RVA: 0x1010 -# CHECK: Name: xdata1b -# CHECK-NEXT: RVA: 0x1030 - .text callee: ret @@ -91,8 +85,3 @@ .section .text,"xr",one_only,xdata1b .seh_endproc # -- End function - - .section .drectve,"yn" - .ascii " -export:xdata1" - .ascii " -export:xdata1a" - .ascii " -export:xdata1b" diff --git a/lld/test/COFF/icf-safe.s b/lld/test/COFF/icf-safe.s --- a/lld/test/COFF/icf-safe.s +++ b/lld/test/COFF/icf-safe.s @@ -1,12 +1,14 @@ # REQUIRES: x86 # RUN: llvm-mc -filetype=obj -triple=x86_64-pc-win32 %s -o %t1.obj # RUN: llvm-mc -filetype=obj -triple=x86_64-pc-win32 %S/Inputs/icf-safe.s -o %t2.obj -# RUN: lld-link /dll /noentry /out:%t.dll /verbose /opt:noref,icf %t1.obj %t2.obj 2>&1 | FileCheck %s -# RUN: lld-link /dll /noentry /out:%t.dll /verbose /opt:noref,icf /export:g3 /export:g4 %t1.obj %t2.obj 2>&1 | FileCheck --check-prefix=EXPORT %s +# RUN: lld-link /dll /noentry /out:%t.dll /verbose /opt:noref,safeicf %t1.obj %t2.obj 2>&1 | FileCheck %s +# RUN: lld-link /dll /noentry /out:%t.dll /verbose /opt:noref,safeicf /export:g3 /export:g4 /export:f3 /export:f4 %t1.obj %t2.obj 2>&1 | FileCheck --check-prefix=EXPORT %s # CHECK-NOT: Selected # CHECK: Selected g3 # CHECK-NEXT: Removed g4 +# CHECK: Selected f3 +# CHECK-NEXT: Removed f4 # CHECK-NOT: Removed # CHECK-NOT: Selected @@ -32,6 +34,28 @@ g4: .byte 2 +.section .text,"xr",one_only,f1 +.globl f1 +f1: + nop + +.section .text,"xr",one_only,f2 +.globl f2 +f2: + nop + +.section .text,"xr",one_only,f3 +.globl f3 +f3: + nop + +.section .text,"xr",one_only,f4 +.globl f4 +f4: + nop + .addrsig .addrsig_sym g1 .addrsig_sym g2 +.addrsig_sym f1 +.addrsig_sym f2 diff --git a/lld/test/COFF/icf-vtables.s b/lld/test/COFF/icf-vtables.s --- a/lld/test/COFF/icf-vtables.s +++ b/lld/test/COFF/icf-vtables.s @@ -1,15 +1,12 @@ # REQUIRES: x86 # RUN: llvm-mc -triple=x86_64-windows-msvc -filetype=obj -o %t.obj %s -# RUN: lld-link %t.obj /out:%t.exe /entry:main /subsystem:console -# RUN: llvm-objdump -s %t.exe | FileCheck %s +# RUN: lld-link %t.obj /out:%t.exe /entry:main /subsystem:console /opt:icf \ +# RUN: /verbose 2>&1 | FileCheck %s -# CHECK: Contents of section .text: .globl main main: -# CHECK-NEXT: 140001000 00200040 01000000 01200040 01000000 .8byte "??_" .8byte "??_7" -# CHECK-NEXT: 140001010 01200040 01000000 .8byte "??_7a" .section .rdata,"dr",discard,"??_" @@ -26,3 +23,7 @@ .globl "??_7a" "??_7a": .byte 42 + +# CHECK: Selected ??_ +# CHECK-NEXT: Removed ??_7 +# CHECK-NEXT: Removed ??_7a diff --git a/lld/test/COFF/lto-icf.ll b/lld/test/COFF/lto-icf.ll --- a/lld/test/COFF/lto-icf.ll +++ b/lld/test/COFF/lto-icf.ll @@ -3,25 +3,25 @@ ; Previously, when we didn't enable function sections, ICF didn't work. ; RUN: llvm-as %s -o %t.bc -; RUN: lld-link -opt:icf -dll -noentry %t.bc -out:%t.dll -; RUN: llvm-readobj --coff-exports %t.dll | FileCheck %s +; RUN: lld-link -opt:icf -dll -noentry %t.bc -out:%t.dll -verbose 2>&1 | FileCheck %s -; CHECK: Export { -; CHECK: Export { -; CHECK: RVA: 0x[[RVA:.*]] -; CHECK: Export { -; CHECK: RVA: 0x[[RVA]] -; CHECK-NOT: Export +; CHECK: Selected icf_ptr +; CHECK-NEXT: Removed icf_int target datalayout = "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64-pc-windows-msvc19.12.25835" -define dllexport i8* @icf_ptr() { +define i8* @icf_ptr() { entry: ret i8* null } -define dllexport i64 @icf_int() { +define i64 @icf_int() { entry: ret i64 0 } + +@llvm.used = appending global [2 x i8*] [ + i8* bitcast (i8* ()* @icf_ptr to i8*), + i8* bitcast (i64 ()* @icf_int to i8*) +], section "llvm.metadata" diff --git a/lld/test/COFF/string-tail-merge.s b/lld/test/COFF/string-tail-merge.s --- a/lld/test/COFF/string-tail-merge.s +++ b/lld/test/COFF/string-tail-merge.s @@ -1,12 +1,12 @@ # REQUIRES: x86 # RUN: llvm-mc -triple=x86_64-windows-msvc -filetype=obj -o %t.obj %s -# RUN: lld-link %t.obj /out:%t.exe /entry:main /subsystem:console +# RUN: lld-link %t.obj /out:%t.exe /entry:main /subsystem:console /opt:safeicf # RUN: llvm-objdump -s %t.exe | FileCheck %s # RUN: lld-link %t.obj /out:%t.exe /entry:main /subsystem:console /opt:noicf /opt:lldtailmerge # RUN: llvm-objdump -s %t.exe | FileCheck %s # RUN: lld-link %t.obj /out:%t.exe /entry:main /subsystem:console /opt:noicf # RUN: llvm-objdump -s %t.exe | FileCheck --check-prefix=NOSTM %s -# RUN: lld-link %t.obj /out:%t.exe /entry:main /subsystem:console /opt:nolldtailmerge +# RUN: lld-link %t.obj /out:%t.exe /entry:main /subsystem:console /opt:nolldtailmerge /opt:safeicf # RUN: llvm-objdump -s %t.exe | FileCheck --check-prefix=NOSTM %s # CHECK: Contents of section .text: