Index: lld/MachO/InputFiles.h =================================================================== --- lld/MachO/InputFiles.h +++ lld/MachO/InputFiles.h @@ -219,6 +219,7 @@ explicit DylibFile(const llvm::MachO::InterfaceFile &interface, DylibFile *umbrella, bool isBundleLoader, bool explicitlyLinked); + explicit DylibFile(DylibFile *umbrella); void parseLoadCommands(MemoryBufferRef mb); void parseReexports(const llvm::MachO::InterfaceFile &interface); @@ -239,14 +240,25 @@ bool forceNeeded = false; bool forceWeakImport = false; bool deadStrippable = false; + bool explicitlyLinked = false; + bool isExplicitlyLinked() const; + // An executable can be used as a bundle loader that will load the output // file being linked, and that contains symbols referenced, but not // implemented in the bundle. When used like this, it is very similar // to a dylib, so we've used the same class to represent it. bool isBundleLoader; + // Synthetic Dylib objects created by $ld$previous symbols in this dylib. + // Usually empty. These synthetic dylibs won't have synthetic dylibs + // themselves. + SmallVector extraDylibs; + private: + DylibFile *getSyntheticDylib(StringRef installName, uint32_t currentVersion, + uint32_t compatVersion); + bool handleLDSymbol(StringRef originalName); void handleLDPreviousSymbol(StringRef name, StringRef originalName); void handleLDInstallNameSymbol(StringRef name, StringRef originalName); Index: lld/MachO/InputFiles.cpp =================================================================== --- lld/MachO/InputFiles.cpp +++ lld/MachO/InputFiles.cpp @@ -1944,6 +1944,14 @@ } } +DylibFile::DylibFile(DylibFile *umbrella) + : InputFile(DylibKind, MemoryBufferRef{}), refState(RefState::Unreferenced), + explicitlyLinked(false), isBundleLoader(false) { + if (umbrella == nullptr) + umbrella = this; + this->umbrella = umbrella; +} + void DylibFile::parseReexports(const InterfaceFile &interface) { const InterfaceFile *topLevel = interface.getParent() == nullptr ? &interface : interface.getParent(); @@ -1955,6 +1963,39 @@ } } +bool DylibFile::isExplicitlyLinked() const { + if (!explicitlyLinked) + return false; + + // If this dylib was explicitly linked, but at least one of the symbols + // of the synthetic dylibs it created via $ld$previous symbols is + // referenced, then that synthetic dylib fulfils the explicit linkedness + // and we can deadstrip this dylib if it's unreferenced. + for (const auto *dylib : extraDylibs) + if (dylib->isReferenced()) + return false; + + return true; +} + +DylibFile *DylibFile::getSyntheticDylib(StringRef installName, + uint32_t currentVersion, + uint32_t compatVersion) { + for (DylibFile *dylib : extraDylibs) + if (dylib->installName == installName) { + // FIXME: Check what to do if different $ld$previous symbols + // request the same dylib, but with different versions. + return dylib; + } + + auto *dylib = make(umbrella == this ? nullptr : umbrella); + dylib->installName = saver().save(installName); + dylib->currentVersion = currentVersion; + dylib->compatibilityVersion = compatVersion; + extraDylibs.push_back(dylib); + return dylib; +} + // $ld$ symbols modify the properties/behavior of the library (e.g. its install // name, compatibility version or hide/add symbols) for specific target // versions. @@ -1990,10 +2031,9 @@ std::tie(platformStr, name) = name.split('$'); std::tie(startVersion, name) = name.split('$'); std::tie(endVersion, name) = name.split('$'); - std::tie(symbolName, rest) = name.split('$'); - // TODO: ld64 contains some logic for non-empty symbolName as well. - if (!symbolName.empty()) - return; + std::tie(symbolName, rest) = name.rsplit('$'); + + // FIXME: Does this do the right thing for zippered files? unsigned platform; if (platformStr.getAsInteger(10, platform) || platform != static_cast(config->platform())) @@ -2014,8 +2054,9 @@ config->platformInfo.minimum >= end) return; - this->installName = saver().save(installName); - + // Initialized to compatibilityVersion for the symbolName branch below. + uint32_t newCompatibilityVersion = compatibilityVersion; + uint32_t newCurrentVersionForSymbol = currentVersion; if (!compatVersion.empty()) { VersionTuple cVersion; if (cVersion.tryParse(compatVersion)) { @@ -2023,8 +2064,27 @@ "' ignored"); return; } - compatibilityVersion = encodeVersion(cVersion); + newCompatibilityVersion = encodeVersion(cVersion); + newCurrentVersionForSymbol = newCompatibilityVersion; + } + + if (!symbolName.empty()) { + // A $ld$previous$ symbol with symbol name adds a symbol with that name to + // a dylib with given name and version. + auto *dylib = getSyntheticDylib(installName, newCurrentVersionForSymbol, + newCompatibilityVersion); + + // Just adding the symbol to the symtab works because dylibs contain their + // symbols in alphabetical order, guaranteeing $ld$ symbols to precede + // normal symbols. + dylib->symbols.push_back(symtab->addDylib( + saver().save(symbolName), dylib, /*isWeakDef=*/false, /*isTlv=*/false)); + return; } + + // A $ld$previous$ symbol without symbol name modifies the dylib it's in. + this->installName = saver().save(installName); + this->compatibilityVersion = newCompatibilityVersion; } void DylibFile::handleLDInstallNameSymbol(StringRef name, Index: lld/MachO/Writer.cpp =================================================================== --- lld/MachO/Writer.cpp +++ lld/MachO/Writer.cpp @@ -780,70 +780,80 @@ if (config->outputType == MH_EXECUTE) in.header->addLoadCommand(make()); + // See ld64's OutputFile::buildDylibOrdinalMapping for the corresponding + // library ordinal computation code in ld64. int64_t dylibOrdinal = 1; DenseMap ordinalForInstallName; + + std::vector dylibFiles; for (InputFile *file : inputFiles) { - if (auto *dylibFile = dyn_cast(file)) { - if (dylibFile->isBundleLoader) { - dylibFile->ordinal = BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE; - // Shortcut since bundle-loader does not re-export the symbols. + if (auto *dylibFile = dyn_cast(file)) + dylibFiles.push_back(dylibFile); + } + for (size_t i = 0; i < dylibFiles.size(); ++i) + dylibFiles.insert(dylibFiles.end(), dylibFiles[i]->extraDylibs.begin(), + dylibFiles[i]->extraDylibs.end()); - dylibFile->reexport = false; - continue; - } + for (DylibFile *dylibFile : dylibFiles) { + if (dylibFile->isBundleLoader) { + dylibFile->ordinal = BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE; + // Shortcut since bundle-loader does not re-export the symbols. - // 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->forceNeeded && - (!dylibFile->explicitlyLinked || dylibFile->deadStrippable || - config->deadStripDylibs)) - continue; + dylibFile->reexport = false; + continue; + } - // Several DylibFiles can have the same installName. Only emit a single - // load command for that installName and give all these DylibFiles the - // same ordinal. - // This can happen in several cases: - // - a new framework could change its installName to an older - // framework name via an $ld$ symbol depending on platform_version - // - symlinks (for example, libpthread.tbd is a symlink to libSystem.tbd; - // Foo.framework/Foo.tbd is usually a symlink to - // Foo.framework/Versions/Current/Foo.tbd, where - // Foo.framework/Versions/Current is usually a symlink to - // Foo.framework/Versions/A) - // - a framework can be linked both explicitly on the linker - // command line and implicitly as a reexport from a different - // framework. The re-export will usually point to the tbd file - // in Foo.framework/Versions/A/Foo.tbd, while the explicit link will - // usually find Foo.framework/Foo.tbd. These are usually symlinks, - // but in a --reproduce archive they will be identical but distinct - // files. - // In the first case, *semantically distinct* DylibFiles will have the - // same installName. - int64_t &ordinal = ordinalForInstallName[dylibFile->installName]; - if (ordinal) { - dylibFile->ordinal = ordinal; - 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->forceNeeded && + (!dylibFile->isExplicitlyLinked() || dylibFile->deadStrippable || + config->deadStripDylibs)) + continue; - ordinal = dylibFile->ordinal = dylibOrdinal++; - LoadCommandType lcType = - dylibFile->forceWeakImport || dylibFile->refState == RefState::Weak - ? LC_LOAD_WEAK_DYLIB - : LC_LOAD_DYLIB; - in.header->addLoadCommand(make(lcType, dylibFile->installName, - dylibFile->compatibilityVersion, - dylibFile->currentVersion)); - - if (dylibFile->reexport) - in.header->addLoadCommand( - make(LC_REEXPORT_DYLIB, dylibFile->installName)); + // Several DylibFiles can have the same installName. Only emit a single + // load command for that installName and give all these DylibFiles the + // same ordinal. + // This can happen in several cases: + // - a new framework could change its installName to an older + // framework name via an $ld$ symbol depending on platform_version + // - symlinks (for example, libpthread.tbd is a symlink to libSystem.tbd; + // Foo.framework/Foo.tbd is usually a symlink to + // Foo.framework/Versions/Current/Foo.tbd, where + // Foo.framework/Versions/Current is usually a symlink to + // Foo.framework/Versions/A) + // - a framework can be linked both explicitly on the linker + // command line and implicitly as a reexport from a different + // framework. The re-export will usually point to the tbd file + // in Foo.framework/Versions/A/Foo.tbd, while the explicit link will + // usually find Foo.framework/Foo.tbd. These are usually symlinks, + // but in a --reproduce archive they will be identical but distinct + // files. + // In the first case, *semantically distinct* DylibFiles will have the + // same installName. + int64_t &ordinal = ordinalForInstallName[dylibFile->installName]; + if (ordinal) { + dylibFile->ordinal = ordinal; + continue; } + + ordinal = dylibFile->ordinal = dylibOrdinal++; + LoadCommandType lcType = + dylibFile->forceWeakImport || dylibFile->refState == RefState::Weak + ? LC_LOAD_WEAK_DYLIB + : LC_LOAD_DYLIB; + in.header->addLoadCommand(make(lcType, dylibFile->installName, + dylibFile->compatibilityVersion, + dylibFile->currentVersion)); + + if (dylibFile->reexport) + in.header->addLoadCommand( + make(LC_REEXPORT_DYLIB, dylibFile->installName)); } if (functionStartsSection) Index: lld/test/MachO/special-symbol-ld-previous.s =================================================================== --- lld/test/MachO/special-symbol-ld-previous.s +++ lld/test/MachO/special-symbol-ld-previous.s @@ -2,52 +2,107 @@ # RUN: rm -rf %t; split-file --no-leading-lines %s %t -# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/foo.s -o %t/foo.o +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/ref_xxx.s -o %t/ref_xxx.o +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/ref_ySyy.s -o %t/ref_ySyy.o +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/ref_zzz.s -o %t/ref_zzz.o ## Case 1: special symbol $ld$previous affects the install name / compatibility version ## since the specified version 11.0.0 is within the affected range [3.0, 14.0). -# RUN: %lld -o %t/libfoo1.dylib %t/libLDPreviousInstallName.tbd %t/foo.o -dylib -platform_version macos 11.0.0 11.0.0 +# RUN: %lld -o %t/libfoo1.dylib %t/libLDPreviousInstallName.tbd %t/ref_xxx.o -dylib -platform_version macos 11.0.0 11.0.0 # RUN: llvm-objdump --macho --dylibs-used %t/libfoo1.dylib | FileCheck --check-prefix=CASE1 %s -# CASE1: /New (compatibility version 1.2.3, current version 5.0.0) +# CASE1: /Old (compatibility version 1.2.3, current version 5.0.0) ## Case 2: special symbol $ld$previous does not affect the install name / compatibility version ## since the specified version 2.0.0 is lower than the affected range [3.0, 14.0). -# RUN: %lld -o %t/libfoo2.dylib %t/libLDPreviousInstallName.tbd %t/foo.o -dylib -platform_version macos 2.0.0 2.0.0 +# RUN: %lld -o %t/libfoo2.dylib %t/libLDPreviousInstallName.tbd %t/ref_xxx.o -dylib -platform_version macos 2.0.0 2.0.0 # RUN: llvm-objdump --macho --dylibs-used %t/libfoo2.dylib | FileCheck --check-prefix=CASE2 %s -# CASE2: /Old (compatibility version 1.1.1, current version 5.0.0) +# CASE2: /New (compatibility version 1.1.1, current version 5.0.0) ## Case 3: special symbol $ld$previous does not affect the install name / compatibility version ## since the specified version 14.0.0 is higher than the affected range [3.0, 14.0). -# RUN: %lld -o %t/libfoo3.dylib %t/libLDPreviousInstallName.tbd %t/foo.o -dylib -platform_version macos 2.0.0 2.0.0 +# RUN: %lld -o %t/libfoo3.dylib %t/libLDPreviousInstallName.tbd %t/ref_xxx.o -dylib -platform_version macos 2.0.0 2.0.0 # RUN: llvm-objdump --macho --dylibs-used %t/libfoo3.dylib | FileCheck --check-prefix=CASE3 %s -# CASE3: /Old (compatibility version 1.1.1, current version 5.0.0) +# CASE3: /New (compatibility version 1.1.1, current version 5.0.0) + +## The remaining cases test handling when a symbol name is part of $ld$previous. + +## Case 4: special symbol $ld$previous affects the install name / compatibility version +## when the specified version 11.0.0 is within the affected range [3.0, 14.0) when a symbol +## is part of $previous$ if and only if that symbol is referenced. + +# RUN: %lld -o %t/libfoo4_yes.dylib %t/libLDPreviousInstallName-Symbol.tbd %t/ref_ySyy.o -dylib -platform_version macos 11.0.0 11.0.0 +# RUN: llvm-otool -L %t/libfoo4_yes.dylib | FileCheck --check-prefix=CASE4-YES --implicit-check-not=/New %s +# CASE4-YES: /Old (compatibility version 1.2.3, current version 1.2.3) + +## $previous has no effect because deployment target is too new. +# RUN: %lld -o %t/libfoo4_no.dylib %t/libLDPreviousInstallName-Symbol.tbd %t/ref_ySyy.o -dylib -platform_version macos 14.0.0 14.0.0 +# RUN: llvm-otool -L %t/libfoo4_no.dylib | FileCheck --check-prefix=CASE4-NO --implicit-check-not=/Old %s +# CASE4-NO: /New (compatibility version 1.1.1, current version 5.0.0) + +## $previous has no effect because named symbol isn't referenced. +# RUN: %lld -o %t/libfoo4_no.dylib %t/libLDPreviousInstallName-Symbol.tbd %t/ref_zzz.o -dylib -platform_version macos 11.0.0 11.0.0 +# RUN: llvm-otool -L %t/libfoo4_no.dylib | FileCheck --check-prefix=CASE4-NO %s + +## Case 5: Reference two symbols that add different $previous names each, +## and one that references the "normal" dylib. +## This should produce three different load commands. +# RUN: %lld -o %t/libfoo5.dylib %t/libLDPreviousInstallName-Symbol.tbd %t/ref_xxx.o %t/ref_ySyy.o %t/ref_zzz.o -dylib -platform_version macos 11.0.0 11.0.0 +# RUN: llvm-otool -L %t/libfoo5.dylib | FileCheck --check-prefix=CASE5 %s +# CASE5: /New (compatibility version 1.1.1, current version 5.0.0) +# CASE5-DAG: /Another (compatibility version 1.2.3, current version 1.2.3) +# CASE5-DAG: /Old (compatibility version 1.2.3, current version 1.2.3) ## Check that we emit a warning for an invalid start, end and compatibility versions. -# RUN: %no-fatal-warnings-lld -o %t/libfoo1.dylib %t/libLDPreviousInvalid.tbd %t/foo.o -dylib \ +# RUN: %no-fatal-warnings-lld -o %t/libfoo1.dylib %t/libLDPreviousInvalid.tbd %t/ref_xxx.o -dylib \ # RUN: -platform_version macos 11.0.0 11.0.0 2>&1 | FileCheck --check-prefix=INVALID-VERSION %s # INVALID-VERSION-DAG: failed to parse start version, symbol '$ld$previous$/New$1.2.3$1$3.a$14.0$$' ignored # INVALID-VERSION-DAG: failed to parse end version, symbol '$ld$previous$/New$1.2.3$1$3.0$14.b$$' ignored # INVALID-VERSION-DAG: failed to parse compatibility version, symbol '$ld$previous$/New$1.2.c$1$3.0$14.0$$' ignored -#--- foo.s +#--- ref_xxx.s .long _xxx@GOTPCREL +#--- ref_ySyy.s +.long _y$yy@GOTPCREL + +#--- ref_zzz.s +.long _zzz@GOTPCREL + #--- libLDPreviousInstallName.tbd --- !tapi-tbd-v3 archs: [ x86_64 ] uuids: [ 'x86_64: 19311019-01AB-342E-812B-73A74271A715' ] platform: macosx -install-name: '/Old' +install-name: '/New' +current-version: 5 +compatibility-version: 1.1.1 +exports: + - archs: [ x86_64 ] + symbols: [ '$ld$previous$/Old$1.2.3$1$3.0$14.0$$', _xxx ] +... + +#--- libLDPreviousInstallName-Symbol.tbd +--- !tapi-tbd-v3 +archs: [ x86_64 ] +uuids: [ 'x86_64: 19311019-01AB-342E-812B-73A74271A715' ] +platform: macosx +install-name: '/New' current-version: 5 compatibility-version: 1.1.1 exports: - archs: [ x86_64 ] - symbols: [ '$ld$previous$/New$1.2.3$1$3.0$14.0$$', _xxx ] + symbols: [ + '$ld$previous$/Another$1.2.3$1$3.0$14.0$_xxx$', + '$ld$previous$/Old$1.2.3$1$3.0$14.0$_y$yy$', + _xxx, + '_y$yy', + _zzz, + ] ... #--- libLDPreviousInvalid.tbd