diff --git a/lld/MachO/Config.h b/lld/MachO/Config.h --- a/lld/MachO/Config.h +++ b/lld/MachO/Config.h @@ -109,6 +109,7 @@ llvm::StringRef outputFile; llvm::StringRef ltoObjPath; llvm::StringRef thinLTOJobs; + bool deadStripDylibs = false; bool demangle = false; PlatformInfo platformInfo; NamespaceKind namespaceKind = NamespaceKind::twolevel; diff --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp --- a/lld/MachO/Driver.cpp +++ b/lld/MachO/Driver.cpp @@ -293,8 +293,10 @@ case file_magic::macho_dynamically_linked_shared_lib: case file_magic::macho_dynamically_linked_shared_lib_stub: case file_magic::tapi_file: - if (Optional dylibFile = loadDylib(mbref)) + if (Optional dylibFile = loadDylib(mbref)) { + (*dylibFile)->explicitlyLinked = true; newFile = *dylibFile; + } break; case file_magic::bitcode: newFile = make(mbref); @@ -322,21 +324,25 @@ return newFile; } -static void addLibrary(StringRef name, bool isWeak) { +static void addLibrary(StringRef name, bool isWeak, bool isExplicit = true) { if (Optional path = findLibrary(name)) { - auto *dylibFile = dyn_cast_or_null(addFile(*path, false)); - if (isWeak && dylibFile) - dylibFile->forceWeakImport = true; + if (auto *dylibFile = dyn_cast_or_null(addFile(*path, false))) { + dylibFile->explicitlyLinked = isExplicit; + if (isWeak) + dylibFile->forceWeakImport = true; + } return; } error("library not found for -l" + name); } -static void addFramework(StringRef name, bool isWeak) { +static void addFramework(StringRef name, bool isWeak, bool isExplicit = true) { if (Optional path = findFramework(name)) { - auto *dylibFile = dyn_cast_or_null(addFile(*path, false)); - if (isWeak && dylibFile) - dylibFile->forceWeakImport = true; + if (auto *dylibFile = dyn_cast_or_null(addFile(*path, false))) { + dylibFile->explicitlyLinked = isExplicit; + if (isWeak) + dylibFile->forceWeakImport = true; + } return; } error("framework not found for -framework " + name); @@ -365,10 +371,10 @@ for (const Arg *arg : args) { switch (arg->getOption().getID()) { case OPT_l: - addLibrary(arg->getValue(), false); + addLibrary(arg->getValue(), /*isWeak=*/false, /*isExplicit=*/false); break; case OPT_framework: - addFramework(arg->getValue(), false); + addFramework(arg->getValue(), /*isWeak=*/false, /*isExplicit=*/false); break; default: error(arg->getSpelling() + " is not allowed in LC_LINKER_OPTION"); @@ -981,6 +987,7 @@ config->runtimePaths = args::getStrings(args, OPT_rpath); config->allLoad = args.hasArg(OPT_all_load); config->forceLoadObjC = args.hasArg(OPT_ObjC); + config->deadStripDylibs = args.hasArg(OPT_dead_strip_dylibs); config->demangle = args.hasArg(OPT_demangle); config->implicitDylibs = !args.hasArg(OPT_no_implicit_dylibs); config->emitFunctionStarts = !args.hasArg(OPT_no_function_starts); diff --git a/lld/MachO/InputFiles.h b/lld/MachO/InputFiles.h --- a/lld/MachO/InputFiles.h +++ b/lld/MachO/InputFiles.h @@ -157,6 +157,14 @@ RefState refState; bool reexport = false; bool forceWeakImport = false; + bool deadStrippable = false; + bool explicitlyLinked = false; + + unsigned numReferencedSymbols = 0; + + bool isReferenced() const { + return numReferencedSymbols > 0; + } // An executable can be used as a bundle loader that will load the output // file being linked, and that contains symbols referenced, but not diff --git a/lld/MachO/InputFiles.cpp b/lld/MachO/InputFiles.cpp --- a/lld/MachO/InputFiles.cpp +++ b/lld/MachO/InputFiles.cpp @@ -828,6 +828,8 @@ return; } + deadStrippable = hdr->flags & MH_DEAD_STRIPPABLE_DYLIB; + if (!checkCompatibility(this)) return; diff --git a/lld/MachO/Options.td b/lld/MachO/Options.td --- a/lld/MachO/Options.td +++ b/lld/MachO/Options.td @@ -702,7 +702,6 @@ Group; def dead_strip_dylibs : Flag<["-"], "dead_strip_dylibs">, HelpText<"Remove dylibs that are unreachable by the entry point or exported symbols">, - Flags<[HelpHidden]>, Group; def allow_sub_type_mismatches : Flag<["-"], "allow_sub_type_mismatches">, HelpText<"Permit mixing objects compiled for different ARM CPU subtypes">, diff --git a/lld/MachO/SymbolTable.cpp b/lld/MachO/SymbolTable.cpp --- a/lld/MachO/SymbolTable.cpp +++ b/lld/MachO/SymbolTable.cpp @@ -81,6 +81,7 @@ toString(file)); } else if (auto *dysym = dyn_cast(s)) { overridesWeakDef = !isWeakDef && dysym->isWeakDef(); + dysym->unreference(); } // Defined symbols take priority over other types of symbols, so in case // of a name conflict, we fall through to the replaceSymbol() call below. @@ -106,7 +107,7 @@ else if (auto *lazy = dyn_cast(s)) lazy->fetchArchiveMember(); else if (auto *dynsym = dyn_cast(s)) - dynsym->refState = std::max(dynsym->refState, refState); + dynsym->reference(refState); else if (auto *undefined = dyn_cast(s)) undefined->refState = std::max(undefined->refState, refState); return s; @@ -147,7 +148,7 @@ } else if (auto *undefined = dyn_cast(s)) { refState = undefined->refState; } else if (auto *dysym = dyn_cast(s)) { - refState = dysym->refState; + refState = dysym->getRefState(); } } @@ -155,8 +156,11 @@ if (wasInserted || isa(s) || (isa(s) && ((!isWeakDef && s->isWeakDef()) || - (!isDynamicLookup && cast(s)->isDynamicLookup())))) + (!isDynamicLookup && cast(s)->isDynamicLookup())))) { + if (auto *dynsym = dyn_cast(s)) + dynsym->unreference(); replaceSymbol(s, file, name, isWeakDef, refState, isTlv); + } return s; } diff --git a/lld/MachO/Symbols.h b/lld/MachO/Symbols.h --- a/lld/MachO/Symbols.h +++ b/lld/MachO/Symbols.h @@ -221,7 +221,10 @@ DylibSymbol(DylibFile *file, StringRefZ name, bool isWeakDef, RefState refState, bool isTlv) : Symbol(DylibKind, name, file), refState(refState), weakDef(isWeakDef), - tlv(isTlv) {} + tlv(isTlv) { + if (file && refState > RefState::Unreferenced) + file->numReferencedSymbols++; + } uint64_t getVA() const override; bool isWeakDef() const override { return weakDef; } @@ -241,9 +244,25 @@ uint32_t stubsHelperIndex = UINT32_MAX; uint32_t lazyBindOffset = UINT32_MAX; - RefState refState : 2; + RefState getRefState() const { return refState; } + + void reference(RefState newState) { + assert(newState > RefState::Unreferenced); + if (refState == RefState::Unreferenced && file) + getFile()->numReferencedSymbols++; + refState = std::max(refState, newState); + } + + void unreference() { + // dynamic_lookup symbols have no file. + if (refState > RefState::Unreferenced && file) { + assert(getFile()->numReferencedSymbols > 0); + getFile()->numReferencedSymbols--; + } + } private: + RefState refState : 2; const bool weakDef : 1; const bool tlv : 1; }; diff --git a/lld/MachO/SyntheticSections.cpp b/lld/MachO/SyntheticSections.cpp --- a/lld/MachO/SyntheticSections.cpp +++ b/lld/MachO/SyntheticSections.cpp @@ -312,9 +312,10 @@ // Non-weak bindings need to have their dylib ordinal encoded as well. static int16_t ordinalForDylibSymbol(const DylibSymbol &dysym) { - return config->namespaceKind == NamespaceKind::flat || dysym.isDynamicLookup() - ? static_cast(BIND_SPECIAL_DYLIB_FLAT_LOOKUP) - : dysym.getFile()->ordinal; + if (config->namespaceKind == NamespaceKind::flat || dysym.isDynamicLookup()) + return static_cast(BIND_SPECIAL_DYLIB_FLAT_LOOKUP); + assert(dysym.getFile()->isReferenced()); + return dysym.getFile()->ordinal; } static void encodeDylibOrdinal(int16_t ordinal, raw_svector_ostream &os) { @@ -464,7 +465,7 @@ "Needed to perform lazy binding."); return; } - stubBinder->refState = RefState::Strong; + stubBinder->reference(RefState::Strong); in.got->addEntry(stubBinder); inputSections.push_back(in.imageLoaderCache); diff --git a/lld/MachO/Writer.cpp b/lld/MachO/Writer.cpp --- a/lld/MachO/Writer.cpp +++ b/lld/MachO/Writer.cpp @@ -615,7 +615,7 @@ if (dysym->isDynamicLookup()) continue; dysym->getFile()->refState = - std::max(dysym->getFile()->refState, dysym->refState); + std::max(dysym->getFile()->refState, dysym->getRefState()); } } } @@ -688,6 +688,19 @@ continue; } + // Don't emit load commands for a dylib that is not referenced if: + // - it was added implicitly (via a reexport, an LC_LOAD_DYLINKER -- + // if it's on the linker command line, it's explicit) + // - or it's marked MH_DEAD_STRIPPABLE_DYLIB + // - or the flag -dead_strip_dylibs is used + // FIXME: `isReferenced()` is currently computed before dead code + // stripping, so references from dead code keep a dylib alive. This + // matches ld64, but it's something we should do better. + if (!dylibFile->isReferenced() && + (!dylibFile->explicitlyLinked || dylibFile->deadStrippable || + config->deadStripDylibs)) + continue; + dylibFile->ordinal = dylibOrdinal++; LoadCommandType lcType = dylibFile->forceWeakImport || dylibFile->refState == RefState::Weak diff --git a/lld/test/MachO/dead-strip-dylibs.s b/lld/test/MachO/dead-strip-dylibs.s new file mode 100644 --- /dev/null +++ b/lld/test/MachO/dead-strip-dylibs.s @@ -0,0 +1,115 @@ +# REQUIRES: x86 + +# RUN: rm -rf %t; split-file %s %t + +# RUN: llvm-mc %t/bar.s -triple=x86_64-apple-macos -filetype=obj -o %t/bar.o +# RUN: %lld -dylib %t/bar.o -o %t/bar.dylib +# RUN: %lld -dylib -mark_dead_strippable_dylib %t/bar.o -o %t/bar-strip.dylib + +# RUN: llvm-mc %t/foo.s -triple=x86_64-apple-macos -filetype=obj -o %t/foo.o +# RUN: %lld -dylib %t/foo.o -o %t/foo_with_bar.dylib %t/bar.dylib -sub_library bar +# RUN: %lld -dylib %t/foo.o -o %t/foo.dylib + +# RUN: llvm-mc %t/weak-foo.s -triple=x86_64-apple-macos -filetype=obj -o %t/weak-foo.o +# RUN: %lld -dylib %t/weak-foo.o -o %t/weak-foo.dylib + +# RUN: llvm-mc %t/main.s -triple=x86_64-apple-macos -filetype=obj -o %t/main.o + +## foo_with_bar.dylib's reexport should be dropped since it's linked implicitly. +# RUN: %lld -lSystem %t/main.o -o %t/main %t/foo_with_bar.dylib +# RUN: llvm-otool -L %t/main | FileCheck --check-prefix=NOBAR %s +# NOBAR-NOT: bar.dylib +# NOBAR: /usr/lib/libSystem.dylib +# NOBAR-NOT: bar.dylib +# NOBAR: foo_with_bar.dylib +# NOBAR-NOT: bar.dylib + +## If bar.dylib is linked explicitly, it should not be dropped. +# RUN: %lld -lSystem %t/main.o -o %t/main %t/foo_with_bar.dylib %t/bar.dylib +# RUN: llvm-otool -L %t/main | FileCheck --check-prefix=BAR %s +# BAR: /usr/lib/libSystem.dylib +# BAR: foo_with_bar.dylib +# BAR: bar.dylib + +## ...except if -dead-strip_dylibs is passed... +# RUN: %lld -lSystem %t/main.o -o %t/main %t/foo_with_bar.dylib %t/bar.dylib \ +# RUN: -dead_strip_dylibs +# RUN: llvm-otool -L %t/main | FileCheck --check-prefix=NOBAR %s + +## ...or bar is explicitly marked dead-strippable +# RUN: %lld -lSystem %t/main.o -o %t/main %t/foo.dylib %t/bar-strip.dylib +# RUN: llvm-otool -L %t/main | FileCheck --check-prefix=NOBARSTRIP %s +# NOBARSTRIP-NOT: bar-strip.dylib +# NOBARSTRIP: /usr/lib/libSystem.dylib +# NOBARSTRIP-NOT: bar-strip.dylib +# NOBARSTRIP: foo.dylib +# NOBARSTRIP-NOT: bar-strip.dylib + +## LC_LINKER_OPTION does not count as an explicit reference. +# RUN: llvm-mc %t/linkopt_bar.s -triple=x86_64-apple-macos -filetype=obj -o %t/linkopt_bar.o +# RUN: %lld -dylib %t/bar.o -o %t/libbar.dylib +# RUN: %lld -lSystem %t/main.o %t/linkopt_bar.o -o %t/main -L %t %t/foo_with_bar.dylib +# RUN: llvm-otool -L %t/main | FileCheck --check-prefix=NOBAR %s + +## Test that a DylibSymbol being replaced by a DefinedSymbol marks the +## dylib as unreferenced. +## (Note: Since there's no dynamic linking in this example, libSystem is +## stripped too. Since libSystem.dylib is needed to run an executable for +## LC_MAIN, the output would crash when run. This matches ld64's behavior (!) +## In practice, every executable uses dynamic linking, which uses +## dyld_stub_binder, which keeps libSystem alive.) +## Test all permutations of (Undefined, Defined, DylibSymbol). +# RUN: %lld -lSystem -dead_strip_dylibs %t/main.o %t/foo.o %t/foo.dylib -o %t/main +# RUN: llvm-otool -L %t/main | FileCheck --check-prefix=NOFOO %s +# RUN: %lld -lSystem -dead_strip_dylibs %t/main.o %t/foo.dylib %t/foo.dylib %t/foo.o -o %t/main +# RUN: llvm-otool -L %t/main | FileCheck --check-prefix=NOFOO %s + +# RUN: %lld -lSystem -dead_strip_dylibs %t/foo.o %t/main.o %t/foo.dylib -o %t/main +# RUN: llvm-otool -L %t/main | FileCheck --check-prefix=NOFOO %s +# RUN: %lld -lSystem -dead_strip_dylibs %t/foo.dylib %t/foo.dylib %t/main.o %t/foo.o -o %t/main +# RUN: llvm-otool -L %t/main | FileCheck --check-prefix=NOFOO %s + +# RUN: %lld -lSystem -dead_strip_dylibs %t/foo.o %t/foo.dylib %t/main.o -o %t/main +# RUN: llvm-otool -L %t/main | FileCheck --check-prefix=NOFOO %s +# RUN: %lld -lSystem -dead_strip_dylibs %t/foo.dylib %t/foo.dylib %t/foo.o %t/main.o -o %t/main +# RUN: llvm-otool -L %t/main | FileCheck --check-prefix=NOFOO %s +# NOFOO-NOT: foo.dylib + +## When linking a weak and a strong symbol from two dylibs, we should keep the +## strong one. +# RUN: %lld -lSystem -dead_strip_dylibs %t/main.o %t/foo.dylib %t/weak-foo.dylib -o %t/main +# RUN: llvm-otool -L %t/main | FileCheck --check-prefix=NOWEAK %s +# RUN: %lld -lSystem -dead_strip_dylibs %t/main.o %t/weak-foo.dylib %t/foo.dylib -o %t/main +# RUN: llvm-otool -L %t/main | FileCheck --check-prefix=NOWEAK %s +# RUN: %lld -lSystem -dead_strip_dylibs %t/foo.dylib %t/weak-foo.dylib %t/main.o -o %t/main +# RUN: llvm-otool -L %t/main | FileCheck --check-prefix=NOWEAK %s +# RUN: %lld -lSystem -dead_strip_dylibs %t/weak-foo.dylib %t/foo.dylib %t/main.o -o %t/main +# RUN: llvm-otool -L %t/main | FileCheck --check-prefix=NOWEAK %s +# NOWEAK-NOT: weak-foo.dylib +# NOWEAK: /foo.dylib +# NOWEAK-NOT: weak-foo.dylib + +#--- main.s +.globl _foo, _main +_main: + callq _foo + retq + +#--- foo.s +.globl _foo +_foo: + retq + +#--- bar.s +.globl _bar +_bar: + retq + +#--- linkopt_bar.s +.linker_option "-lbar" + +#--- weak-foo.s +.globl _foo +.weak_definition _foo +_foo: + retq diff --git a/lld/test/MachO/implicit-dylibs.s b/lld/test/MachO/implicit-dylibs.s --- a/lld/test/MachO/implicit-dylibs.s +++ b/lld/test/MachO/implicit-dylibs.s @@ -58,11 +58,11 @@ # CHECK-DAG: __DATA __data {{.*}} pointer 0 libreexporter _framework_baz # CHECK-DAG: __DATA __data {{.*}} pointer 0 libc++abi ___gxx_personality_v0 -# RUN: llvm-objdump --macho --all-headers %t/test | FileCheck %s \ +# RUN: llvm-otool -l %t/test | FileCheck %s \ # RUN: --check-prefix=LOAD -DDIR=%t --implicit-check-not LC_LOAD_DYLIB ## Check that we don't create duplicate LC_LOAD_DYLIBs. # RUN: %lld -syslibroot %t -o %t/test -lSystem -L%t -lreexporter -ltoplevel %t/test.o -# RUN: llvm-objdump --macho --all-headers %t/test | FileCheck %s \ +# RUN: llvm-otool -l %t/test | FileCheck %s \ # RUN: --check-prefix=LOAD -DDIR=%t --implicit-check-not LC_LOAD_DYLIB # LOAD: cmd LC_LOAD_DYLIB @@ -76,15 +76,9 @@ # LOAD-NEXT: name /usr/lib/libc++abi.dylib # LOAD: cmd LC_LOAD_DYLIB # LOAD-NEXT: cmdsize -# LOAD-NEXT: name /usr/lib/libc++.dylib -# LOAD: cmd LC_LOAD_DYLIB -# LOAD-NEXT: cmdsize # LOAD-NEXT: name /usr/lib/libtoplevel.dylib # LOAD: cmd LC_LOAD_DYLIB # LOAD-NEXT: cmdsize -# LOAD-NEXT: name /usr/lib/libunused.dylib -# LOAD: cmd LC_LOAD_DYLIB -# LOAD-NEXT: cmdsize # LOAD-NEXT: name [[DIR]]/libreexporter.dylib # RUN: %lld -no_implicit_dylibs -syslibroot %t -o %t/no-implicit -lSystem -L%t -lreexporter %t/test.o diff --git a/lld/test/MachO/lc-linker-option.ll b/lld/test/MachO/lc-linker-option.ll --- a/lld/test/MachO/lc-linker-option.ll +++ b/lld/test/MachO/lc-linker-option.ll @@ -2,30 +2,38 @@ ; RUN: rm -rf %t; split-file %s %t ; ; RUN: llvm-as %t/framework.ll -o %t/framework.o -; RUN: %lld %t/framework.o -o %t/frame -; RUN: llvm-objdump --macho --all-headers %t/frame | FileCheck --check-prefix=FRAME %s \ +; RUN: %lld -lSystem %t/framework.o -o %t/frame +; RUN: llvm-otool -l %t/frame | FileCheck --check-prefix=FRAME %s \ ; RUN: --implicit-check-not LC_LOAD_DYLIB ; FRAME: cmd LC_LOAD_DYLIB ; FRAME-NEXT: cmdsize +; FRAME-NEXT: name /usr/lib/libSystem.dylib +; FRAME: cmd LC_LOAD_DYLIB +; FRAME-NEXT: cmdsize ; FRAME-NEXT: name /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation -; + ; RUN: llvm-as %t/l.ll -o %t/l.o -; RUN: %lld %t/l.o -o %t/l -; RUN: llvm-objdump --macho --all-headers %t/l | FileCheck --check-prefix=LIB %s \ -; RUN: --implicit-check-not LC_LOAD_DYLIB -; -;; Check that we don't create duplicate LC_LOAD_DYLIBs. -; RUN: %lld -lSystem %t/l.o -o %t/l -; RUN: llvm-objdump --macho --all-headers %t/l | FileCheck --check-prefix=LIB %s \ +;; The dynamic call to _CFBigNumGetInt128 uses dyld_stub_binder, +;; which needs -lSystem from LC_LINKER_OPTION to get resolved. +; RUN: %lld %t/l.o -o %t/l -framework CoreFoundation +; RUN: llvm-otool -l %t/l | FileCheck --check-prefix=LIB %s \ ; RUN: --implicit-check-not LC_LOAD_DYLIB ; LIB: cmd LC_LOAD_DYLIB ; LIB-NEXT: cmdsize +; LIB-NEXT: name /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation +; LIB: cmd LC_LOAD_DYLIB +; LIB-NEXT: cmdsize ; LIB-NEXT: name /usr/lib/libSystem.dylib + +;; Check that we don't create duplicate LC_LOAD_DYLIBs. +; RUN: %lld -lSystem %t/l.o -o %t/l -framework CoreFoundation +; RUN: llvm-otool -l %t/l | FileCheck --check-prefix=FRAME %s \ +; RUN: --implicit-check-not LC_LOAD_DYLIB ; ; RUN: llvm-as %t/invalid.ll -o %t/invalid.o ; RUN: not %lld %t/invalid.o -o /dev/null 2>&1 | FileCheck --check-prefix=INVALID %s ; INVALID: error: -why_load is not allowed in LC_LINKER_OPTION -; + ;--- framework.ll target triple = "x86_64-apple-macosx10.15.0" target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" @@ -33,7 +41,10 @@ !0 = !{!"-framework", !"CoreFoundation"} !llvm.linker.options = !{!0} +declare void @_CFBigNumGetInt128(...) + define void @main() { + call void bitcast (void (...)* @_CFBigNumGetInt128 to void ()*)() ret void } @@ -44,7 +55,10 @@ !0 = !{!"-lSystem"} !llvm.linker.options = !{!0, !0} +declare void @_CFBigNumGetInt128(...) + define void @main() { + call void bitcast (void (...)* @_CFBigNumGetInt128 to void ()*)() ret void }