diff --git a/lld/MachO/Driver.h b/lld/MachO/Driver.h --- a/lld/MachO/Driver.h +++ b/lld/MachO/Driver.h @@ -15,6 +15,10 @@ #include "llvm/Option/OptTable.h" #include "llvm/Support/MemoryBuffer.h" +namespace llvm::MachO { +class InterfaceFile; +} + namespace lld { namespace macho { @@ -43,9 +47,10 @@ // Check for both libfoo.dylib and libfoo.tbd (in that order). llvm::Optional resolveDylibPath(llvm::StringRef path); -llvm::Optional loadDylib(llvm::MemoryBufferRef mbref, - DylibFile *umbrella = nullptr, - bool isBundleLoader = false); +llvm::Optional +loadDylib(llvm::MemoryBufferRef mbref, + const llvm::MachO::InterfaceFile *currentTopLevelTapi, + DylibFile *umbrella = nullptr, bool isBundleLoader = false); llvm::Optional loadArchiveMember(MemoryBufferRef, uint32_t modTime, StringRef archiveName, diff --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp --- a/lld/MachO/Driver.cpp +++ b/lld/MachO/Driver.cpp @@ -320,7 +320,7 @@ 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, nullptr)) newFile = *dylibFile; break; case file_magic::bitcode: @@ -333,7 +333,7 @@ if (!isBundleLoader) error(path + ": unhandled file type"); if (Optional dylibFile = - loadDylib(mbref, nullptr, isBundleLoader)) + loadDylib(mbref, nullptr, nullptr, isBundleLoader)) newFile = *dylibFile; break; default: diff --git a/lld/MachO/DriverUtils.cpp b/lld/MachO/DriverUtils.cpp --- a/lld/MachO/DriverUtils.cpp +++ b/lld/MachO/DriverUtils.cpp @@ -175,9 +175,10 @@ // especially if it's a commonly re-exported core library. static DenseMap loadedDylibs; -Optional macho::loadDylib(MemoryBufferRef mbref, - DylibFile *umbrella, - bool isBundleLoader) { +Optional +macho::loadDylib(MemoryBufferRef mbref, + const llvm::MachO::InterfaceFile *currentTopLevelTapi, + DylibFile *umbrella, bool isBundleLoader) { StringRef path = mbref.getBufferIdentifier(); DylibFile *&file = loadedDylibs[CachedHashStringRef(path)]; if (file) @@ -191,13 +192,15 @@ ": " + toString(result.takeError())); return {}; } - file = make(**result, umbrella, isBundleLoader); + file = make(**result, currentTopLevelTapi, umbrella, + isBundleLoader); } else { assert(magic == file_magic::macho_dynamically_linked_shared_lib || magic == file_magic::macho_dynamically_linked_shared_lib_stub || magic == file_magic::macho_executable || magic == file_magic::macho_bundle); - file = make(mbref, umbrella, isBundleLoader); + file = + make(mbref, currentTopLevelTapi, umbrella, isBundleLoader); } return file; } diff --git a/lld/MachO/InputFiles.h b/lld/MachO/InputFiles.h --- a/lld/MachO/InputFiles.h +++ b/lld/MachO/InputFiles.h @@ -125,10 +125,13 @@ // the root dylib to ensure symbols in the child library are correctly bound // to the root. On the other hand, if a dylib is being directly loaded // (through an -lfoo flag), then `umbrella` should be a nullptr. - explicit DylibFile(MemoryBufferRef mb, DylibFile *umbrella = nullptr, + explicit DylibFile(MemoryBufferRef mb, + const llvm::MachO::InterfaceFile *currentTopLevelTapi, + DylibFile *umbrella = nullptr, bool isBundleLoader = false); explicit DylibFile(const llvm::MachO::InterfaceFile &interface, + const llvm::MachO::InterfaceFile *currentTopLevelTapi, DylibFile *umbrella = nullptr, bool isBundleLoader = false); diff --git a/lld/MachO/InputFiles.cpp b/lld/MachO/InputFiles.cpp --- a/lld/MachO/InputFiles.cpp +++ b/lld/MachO/InputFiles.cpp @@ -530,36 +530,33 @@ } // The path can point to either a dylib or a .tbd file. -static Optional loadDylib(StringRef path, DylibFile *umbrella) { +static Optional +loadDylib(StringRef path, DylibFile *umbrella, + const InterfaceFile *currentTopLevelTapi) { Optional mbref = readFile(path); if (!mbref) { error("could not read dylib file at " + path); return {}; } - return loadDylib(*mbref, umbrella); + return loadDylib(*mbref, currentTopLevelTapi, umbrella); } // TBD files are parsed into a series of TAPI documents (InterfaceFiles), with // the first document storing child pointers to the rest of them. When we are // processing a given TBD file, we store that top-level document here. When // processing re-exports, we search its children for potentially matching -// documents in the same TBD file. Note that the children themselves don't -// point to further documents, i.e. this is a two-level tree. -// -// ld64 allows a TAPI re-export to reference documents nested within other TBD -// files, but that seems like a strange design, so this is an intentional -// deviation. -const InterfaceFile *currentTopLevelTapi = nullptr; +// documents in the same TBD file. // Re-exports can either refer to on-disk files, or to documents within .tbd // files. -static Optional loadReexportHelper(StringRef path, - DylibFile *umbrella) { +static Optional +loadReexportHelper(StringRef path, DylibFile *umbrella, + const InterfaceFile *currentTopLevelTapi) { if (path::is_absolute(path, path::Style::posix)) for (StringRef root : config->systemLibraryRoots) if (Optional dylibPath = resolveDylibPath((root + path).str())) - return loadDylib(*dylibPath, umbrella); + return loadDylib(*dylibPath, umbrella, currentTopLevelTapi); // TODO: Expand @loader_path, @executable_path etc @@ -567,13 +564,13 @@ for (InterfaceFile &child : make_pointee_range(currentTopLevelTapi->documents())) { if (path == child.getInstallName()) - return make(child, umbrella); + return make(child, currentTopLevelTapi, umbrella); assert(child.documents().empty()); } } if (Optional dylibPath = resolveDylibPath(path)) - return loadDylib(*dylibPath, umbrella); + return loadDylib(*dylibPath, umbrella, currentTopLevelTapi); error("unable to locate re-export with install name " + path); return {}; @@ -599,14 +596,17 @@ return false; } -void loadReexport(StringRef path, DylibFile *umbrella) { - Optional reexport = loadReexportHelper(path, umbrella); +void loadReexport(StringRef path, DylibFile *umbrella, + const InterfaceFile *currentTopLevelTapi) { + Optional reexport = + loadReexportHelper(path, umbrella, currentTopLevelTapi); if (reexport && isImplicitlyLinked(path)) inputFiles.insert(*reexport); } -DylibFile::DylibFile(MemoryBufferRef mb, DylibFile *umbrella, - bool isBundleLoader) +DylibFile::DylibFile(MemoryBufferRef mb, + const InterfaceFile *currentTopLevelTapi, + DylibFile *umbrella, bool isBundleLoader) : InputFile(DylibKind, mb), refState(RefState::Unreferenced), isBundleLoader(isBundleLoader) { assert(!isBundleLoader || !umbrella); @@ -659,12 +659,13 @@ auto *c = reinterpret_cast(cmd); StringRef reexportPath = reinterpret_cast(c) + read32le(&c->dylib.name); - loadReexport(reexportPath, umbrella); + loadReexport(reexportPath, umbrella, currentTopLevelTapi); } } -DylibFile::DylibFile(const InterfaceFile &interface, DylibFile *umbrella, - bool isBundleLoader) +DylibFile::DylibFile(const InterfaceFile &interface, + const InterfaceFile *currentTopLevelTapi, + DylibFile *umbrella, bool isBundleLoader) : InputFile(DylibKind, interface), refState(RefState::Unreferenced), isBundleLoader(isBundleLoader) { // FIXME: Add test for the missing TBD code path. @@ -706,17 +707,16 @@ } } - bool isTopLevelTapi = false; - if (currentTopLevelTapi == nullptr) { - currentTopLevelTapi = &interface; - isTopLevelTapi = true; - } + const InterfaceFile *top_level = nullptr; + // If there is no top-level (mean this tbd the top-level) or if the current + // interface has no parent + if (currentTopLevelTapi == nullptr || interface.getParent() == nullptr) + top_level = &interface; + else + top_level = interface.getParent(); // Second+-level exporters. for (InterfaceFileRef intfRef : interface.reexportedLibraries()) - loadReexport(intfRef.getInstallName(), umbrella); - - if (isTopLevelTapi) - currentTopLevelTapi = nullptr; + loadReexport(intfRef.getInstallName(), umbrella, top_level); } ArchiveFile::ArchiveFile(std::unique_ptr &&f) diff --git a/lld/test/MachO/Inputs/iPhoneSimulator.sdk/usr/lib/libSystem.tbd b/lld/test/MachO/Inputs/iPhoneSimulator.sdk/usr/lib/libSystem.tbd --- a/lld/test/MachO/Inputs/iPhoneSimulator.sdk/usr/lib/libSystem.tbd +++ b/lld/test/MachO/Inputs/iPhoneSimulator.sdk/usr/lib/libSystem.tbd @@ -4,10 +4,13 @@ platform: ios install-name: '/usr/lib/libSystem.B.dylib' current-version: 1281 +reexported-libraries: + - archs: [i386, x86_64] + libraries: ['/usr/lib/system/libsystem_sim_pthread.dylib'] exports: - archs: [ i386, x86_64 ] re-exports: [ '/usr/lib/system/libcache.dylib' ] - symbols: [ __crashreporter_info__ ] + symbols: [ __crashreporter_info__, _from_second_level_dylib] --- !tapi-tbd-v3 archs: [ i386, x86_64 ] uuids: [ 'i386: 00000000-0000-0000-0000-000000000002', 'x86_64: 00000000-0000-0000-0000-000000000003' ] @@ -31,4 +34,32 @@ exports: - archs: [ i386, x86_64 ] symbols: [ _from_non_reexported_tapi_dylib ] + +--- !tapi-tbd-v3 +archs: [ i386, x86_64 ] +uuids: [ 'i386: 00000000-0000-0000-0000-000000000006', 'x86_64: 00000000-0000-0000-0000-000000000006' ] +platform: ios +install-name: '/usr/lib/system/libsystem_sim_pthread.dylib' +parent-umbrella: + - archs: [ i386, x86_64 ] + umbrella: System +reexported-libraries: + - archs: [i386, x86_64] + libraries: ['/usr/lib/system/libsystem_sim_pthread_host.dylib'] +exports: + - archs: [ i386, x86_64 ] + symbols: [ _from_second_level_dylib ] + +--- !tapi-tbd-v3 +archs: [ i386, x86_64 ] +uuids: [ 'i386: 00000000-0000-0000-0000-000000000006', 'x86_64: 00000000-0000-0000-0000-000000000006' ] +platform: ios +install-name: '/usr/lib/system/libsystem_sim_pthread_host.dylib' +parent-umbrella: + - archs: [ i386, x86_64 ] + umbrella: System +exports: + - archs: [ i386, x86_64 ] + symbols: [ _from_second_level_dylib ] + ... diff --git a/lld/test/MachO/reexport-nested-lib.s b/lld/test/MachO/reexport-nested-lib.s new file mode 100644 --- /dev/null +++ b/lld/test/MachO/reexport-nested-lib.s @@ -0,0 +1,14 @@ +# REQUIRES: x86 +# RUN: mkdir -p %t + +# RUN: llvm-mc -filetype obj -triple x86_64-apple-darwin %s -o %t/test.o +# RUN: %lld -o %t/test -syslibroot %S/Inputs/iPhoneSimulator.sdk -lSystem %t/test.o + +.text +.globl _main + +_main: + ret + +.data + .quad _from_second_level_dylib diff --git a/llvm/include/llvm/TextAPI/MachO/InterfaceFile.h b/llvm/include/llvm/TextAPI/MachO/InterfaceFile.h --- a/llvm/include/llvm/TextAPI/MachO/InterfaceFile.h +++ b/llvm/include/llvm/TextAPI/MachO/InterfaceFile.h @@ -338,6 +338,9 @@ ///\param Document The library to inline with top level library. void addDocument(std::shared_ptr &&Document); + /// Returns the pointer to parent document if exists or nullptr otherwise. + InterfaceFile *getParent() const { return parent; } + /// Get the list of inlined libraries. /// /// \return Returns a list of the inlined frameworks. @@ -404,6 +407,8 @@ bool operator!=(const InterfaceFile &O) const { return !(*this == O); } private: + InterfaceFile *parent = nullptr; + llvm::BumpPtrAllocator Allocator; StringRef copyString(StringRef String) { if (String.empty()) diff --git a/llvm/lib/TextAPI/MachO/InterfaceFile.cpp b/llvm/lib/TextAPI/MachO/InterfaceFile.cpp --- a/llvm/lib/TextAPI/MachO/InterfaceFile.cpp +++ b/llvm/lib/TextAPI/MachO/InterfaceFile.cpp @@ -124,6 +124,7 @@ const std::shared_ptr &RHS) { return LHS->InstallName < RHS->InstallName; }); + Document->parent = this; Documents.insert(Pos, Document); }