[lld-macho] Add --start-lib --end-lib
authorFangrui Song <i@maskray.me>
Wed, 19 Jan 2022 18:14:49 +0000 (10:14 -0800)
committerFangrui Song <i@maskray.me>
Wed, 19 Jan 2022 18:14:49 +0000 (10:14 -0800)
In ld.lld, when an ObjFile/BitcodeFile is read in --start-lib state, the file is
given archive semantics. --end-lib closes the previous --start-lib. A build
system can use this feature as an alternative to archives. This patch ports
the feature to lld-macho.

--start-lib and --end-lib are positional, unlike usual ld64 options.
I think the slight drawback does not matter as (a) reusing option names
make build systems convenient (b) `--start-lib a.o b.o --end-lib` conveys more
information than an alternative design: `-objlib a.o -objlib b.o` because
--start-lib makes it clear which objects are in the same conceptual archive.
This provides flexibility (c) `-objlib`/`-filelist` interaction may be weird.

Close https://github.com/llvm/llvm-project/issues/52931

Reviewed By: #lld-macho, Jez Ng, oontvoo

Differential Revision: https://reviews.llvm.org/D116913

12 files changed:
lld/MachO/Driver.cpp
lld/MachO/InputFiles.cpp
lld/MachO/InputFiles.h
lld/MachO/Options.td
lld/MachO/SymbolTable.cpp
lld/MachO/SymbolTable.h
lld/MachO/Symbols.h
lld/MachO/UnwindInfoSection.cpp
lld/test/MachO/objc-uses-custom-personality.s
lld/test/MachO/objc.s
lld/test/MachO/start-lib.s [new file with mode: 0644]
lld/test/MachO/weak-definition-direct-fetch.s

index 5761e85..7692670 100644 (file)
@@ -249,7 +249,8 @@ static llvm::CachePruningPolicy getLTOCachePolicy(InputArgList &args) {
 static DenseMap<StringRef, ArchiveFile *> loadedArchives;
 
 static InputFile *addFile(StringRef path, ForceLoad forceLoadArchive,
-                          bool isExplicit = true, bool isBundleLoader = false) {
+                          bool isLazy = false, bool isExplicit = true,
+                          bool isBundleLoader = false) {
   Optional<MemoryBufferRef> buffer = readFile(path);
   if (!buffer)
     return nullptr;
@@ -319,7 +320,7 @@ static InputFile *addFile(StringRef path, ForceLoad forceLoadArchive,
     break;
   }
   case file_magic::macho_object:
-    newFile = make<ObjFile>(mbref, getModTime(path), "");
+    newFile = make<ObjFile>(mbref, getModTime(path), "", isLazy);
     break;
   case file_magic::macho_dynamically_linked_shared_lib:
   case file_magic::macho_dynamically_linked_shared_lib_stub:
@@ -331,7 +332,7 @@ static InputFile *addFile(StringRef path, ForceLoad forceLoadArchive,
     }
     break;
   case file_magic::bitcode:
-    newFile = make<BitcodeFile>(mbref, "", 0);
+    newFile = make<BitcodeFile>(mbref, "", 0, isLazy);
     break;
   case file_magic::macho_executable:
   case file_magic::macho_bundle:
@@ -346,9 +347,20 @@ static InputFile *addFile(StringRef path, ForceLoad forceLoadArchive,
     error(path + ": unhandled file type");
   }
   if (newFile && !isa<DylibFile>(newFile)) {
+    if ((isa<ObjFile>(newFile) || isa<BitcodeFile>(newFile)) && newFile->lazy &&
+        config->forceLoadObjC) {
+      for (Symbol *sym : newFile->symbols)
+        if (sym && sym->getName().startswith(objc::klass)) {
+          extract(*newFile, "-ObjC");
+          break;
+        }
+      if (newFile->lazy && hasObjCSection(mbref))
+        extract(*newFile, "-ObjC");
+    }
+
     // printArchiveMemberLoad() prints both .a and .o names, so no need to
-    // print the .a name here.
-    if (config->printEachFile && magic != file_magic::archive)
+    // print the .a name here. Similarly skip lazy files.
+    if (config->printEachFile && magic != file_magic::archive && !isLazy)
       message(toString(newFile));
     inputFiles.insert(newFile);
   }
@@ -360,7 +372,7 @@ static void addLibrary(StringRef name, bool isNeeded, bool isWeak,
                        ForceLoad forceLoadArchive) {
   if (Optional<StringRef> path = findLibrary(name)) {
     if (auto *dylibFile = dyn_cast_or_null<DylibFile>(
-            addFile(*path, forceLoadArchive, isExplicit))) {
+            addFile(*path, forceLoadArchive, /*isLazy=*/false, isExplicit))) {
       if (isNeeded)
         dylibFile->forceNeeded = true;
       if (isWeak)
@@ -380,7 +392,7 @@ static void addFramework(StringRef name, bool isNeeded, bool isWeak,
                          ForceLoad forceLoadArchive) {
   if (Optional<StringRef> path = findFramework(name)) {
     if (auto *dylibFile = dyn_cast_or_null<DylibFile>(
-            addFile(*path, forceLoadArchive, isExplicit))) {
+            addFile(*path, forceLoadArchive, /*isLazy=*/false, isExplicit))) {
       if (isNeeded)
         dylibFile->forceNeeded = true;
       if (isWeak)
@@ -425,13 +437,13 @@ void macho::parseLCLinkerOption(InputFile *f, unsigned argc, StringRef data) {
   }
 }
 
-static void addFileList(StringRef path) {
+static void addFileList(StringRef path, bool isLazy) {
   Optional<MemoryBufferRef> buffer = readFile(path);
   if (!buffer)
     return;
   MemoryBufferRef mbref = *buffer;
   for (StringRef path : args::getLines(mbref))
-    addFile(rerootPath(path), ForceLoad::Default);
+    addFile(rerootPath(path), ForceLoad::Default, isLazy);
 }
 
 // An order file has one entry per line, in the following format:
@@ -545,7 +557,8 @@ static void compileBitcodeFiles() {
   auto *lto = make<BitcodeCompiler>();
   for (InputFile *file : inputFiles)
     if (auto *bitcodeFile = dyn_cast<BitcodeFile>(file))
-      lto->add(*bitcodeFile);
+      if (!file->lazy)
+        lto->add(*bitcodeFile);
 
   for (ObjFile *file : lto->compile())
     inputFiles.insert(file);
@@ -962,6 +975,7 @@ static void createFiles(const InputArgList &args) {
   TimeTraceScope timeScope("Load input files");
   // This loop should be reserved for options whose exact ordering matters.
   // Other options should be handled via filtered() and/or getLastArg().
+  bool isLazy = false;
   for (const Arg *arg : args) {
     const Option &opt = arg->getOption();
     warnIfDeprecatedOption(opt);
@@ -969,7 +983,7 @@ static void createFiles(const InputArgList &args) {
 
     switch (opt.getID()) {
     case OPT_INPUT:
-      addFile(rerootPath(arg->getValue()), ForceLoad::Default);
+      addFile(rerootPath(arg->getValue()), ForceLoad::Default, isLazy);
       break;
     case OPT_needed_library:
       if (auto *dylibFile = dyn_cast_or_null<DylibFile>(
@@ -989,7 +1003,7 @@ static void createFiles(const InputArgList &args) {
         dylibFile->forceWeakImport = true;
       break;
     case OPT_filelist:
-      addFileList(arg->getValue());
+      addFileList(arg->getValue(), isLazy);
       break;
     case OPT_force_load:
       addFile(rerootPath(arg->getValue()), ForceLoad::Yes);
@@ -1011,6 +1025,16 @@ static void createFiles(const InputArgList &args) {
                    opt.getID() == OPT_reexport_framework, /*isExplicit=*/true,
                    ForceLoad::Default);
       break;
+    case OPT_start_lib:
+      if (isLazy)
+        error("nested --start-lib");
+      isLazy = true;
+      break;
+    case OPT_end_lib:
+      if (!isLazy)
+        error("stray --end-lib");
+      isLazy = false;
+      break;
     default:
       break;
     }
@@ -1247,7 +1271,8 @@ bool macho::link(ArrayRef<const char *> argsArr, bool canExitEarly,
   if (const Arg *arg = args.getLastArg(OPT_bundle_loader)) {
     if (config->outputType != MH_BUNDLE)
       error("-bundle_loader can only be used with MachO bundle output");
-    addFile(arg->getValue(), ForceLoad::Default, /*isExplicit=*/false,
+    addFile(arg->getValue(), ForceLoad::Default, /*isLazy=*/false,
+            /*isExplicit=*/false,
             /*isBundleLoader=*/true);
   }
   if (const Arg *arg = args.getLastArg(OPT_umbrella)) {
index cebf22b..9ab5a18 100644 (file)
@@ -836,13 +836,21 @@ OpaqueFile::OpaqueFile(MemoryBufferRef mb, StringRef segName,
   sections.back().subsections.push_back({0, isec});
 }
 
-ObjFile::ObjFile(MemoryBufferRef mb, uint32_t modTime, StringRef archiveName)
-    : InputFile(ObjKind, mb), modTime(modTime) {
+ObjFile::ObjFile(MemoryBufferRef mb, uint32_t modTime, StringRef archiveName,
+                 bool lazy)
+    : InputFile(ObjKind, mb, lazy), modTime(modTime) {
   this->archiveName = std::string(archiveName);
-  if (target->wordSize == 8)
-    parse<LP64>();
-  else
-    parse<ILP32>();
+  if (lazy) {
+    if (target->wordSize == 8)
+      parseLazy<LP64>();
+    else
+      parseLazy<ILP32>();
+  } else {
+    if (target->wordSize == 8)
+      parse<LP64>();
+    else
+      parse<ILP32>();
+  }
 }
 
 template <class LP> void ObjFile::parse() {
@@ -904,6 +912,32 @@ template <class LP> void ObjFile::parse() {
     registerCompactUnwind();
 }
 
+template <class LP> void ObjFile::parseLazy() {
+  using Header = typename LP::mach_header;
+  using NList = typename LP::nlist;
+
+  auto *buf = reinterpret_cast<const uint8_t *>(mb.getBufferStart());
+  auto *hdr = reinterpret_cast<const Header *>(mb.getBufferStart());
+  const load_command *cmd = findCommand(hdr, LC_SYMTAB);
+  if (!cmd)
+    return;
+  auto *c = reinterpret_cast<const symtab_command *>(cmd);
+  ArrayRef<NList> nList(reinterpret_cast<const NList *>(buf + c->symoff),
+                        c->nsyms);
+  const char *strtab = reinterpret_cast<const char *>(buf) + c->stroff;
+  symbols.resize(nList.size());
+  for (auto it : llvm::enumerate(nList)) {
+    const NList &sym = it.value();
+    if ((sym.n_type & N_EXT) && !isUndef(sym)) {
+      // TODO: Bound checking
+      StringRef name = strtab + sym.n_strx;
+      symbols[it.index()] = symtab->addLazyObject(name, *this);
+      if (!lazy)
+        break;
+    }
+  }
+}
+
 void ObjFile::parseDebugInfo() {
   std::unique_ptr<DwarfObject> dObj = DwarfObject::create(this);
   if (!dObj)
@@ -1548,8 +1582,8 @@ static macho::Symbol *createBitcodeSymbol(const lto::InputFile::Symbol &objSym,
 }
 
 BitcodeFile::BitcodeFile(MemoryBufferRef mb, StringRef archiveName,
-                         uint64_t offsetInArchive)
-    : InputFile(BitcodeKind, mb) {
+                         uint64_t offsetInArchive, bool lazy)
+    : InputFile(BitcodeKind, mb, lazy) {
   this->archiveName = std::string(archiveName);
   std::string path = mb.getBufferIdentifier().str();
   // ThinLTO assumes that all MemoryBufferRefs given to it have a unique
@@ -1565,12 +1599,47 @@ BitcodeFile::BitcodeFile(MemoryBufferRef mb, StringRef archiveName,
                                            utostr(offsetInArchive)));
 
   obj = check(lto::InputFile::create(mbref));
+  if (lazy)
+    parseLazy();
+  else
+    parse();
+}
 
+void BitcodeFile::parse() {
   // Convert LTO Symbols to LLD Symbols in order to perform resolution. The
   // "winning" symbol will then be marked as Prevailing at LTO compilation
   // time.
+  symbols.clear();
   for (const lto::InputFile::Symbol &objSym : obj->symbols())
     symbols.push_back(createBitcodeSymbol(objSym, *this));
 }
 
+void BitcodeFile::parseLazy() {
+  symbols.resize(obj->symbols().size());
+  for (auto it : llvm::enumerate(obj->symbols())) {
+    const lto::InputFile::Symbol &objSym = it.value();
+    if (!objSym.isUndefined()) {
+      symbols[it.index()] =
+          symtab->addLazyObject(saver.save(objSym.getName()), *this);
+      if (!lazy)
+        break;
+    }
+  }
+}
+
+void macho::extract(InputFile &file, StringRef reason) {
+  assert(file.lazy);
+  file.lazy = false;
+  printArchiveMemberLoad(reason, &file);
+  if (auto *bitcode = dyn_cast<BitcodeFile>(&file)) {
+    bitcode->parse();
+  } else {
+    auto &f = cast<ObjFile>(file);
+    if (target->wordSize == 8)
+      f.parse<LP64>();
+    else
+      f.parse<ILP32>();
+  }
+}
+
 template void ObjFile::parse<LP64>();
index a29e688..6a4a4fd 100644 (file)
@@ -94,16 +94,21 @@ public:
 
   std::vector<Symbol *> symbols;
   std::vector<Section> sections;
-  // Provides an easy way to sort InputFiles deterministically.
-  const int id;
 
   // If not empty, this stores the name of the archive containing this file.
   // We use this string for creating error messages.
   std::string archiveName;
 
+  // Provides an easy way to sort InputFiles deterministically.
+  const int id;
+
+  // True if this is a lazy ObjFile or BitcodeFile.
+  bool lazy = false;
+
 protected:
-  InputFile(Kind kind, MemoryBufferRef mb)
-      : mb(mb), id(idCount++), fileKind(kind), name(mb.getBufferIdentifier()) {}
+  InputFile(Kind kind, MemoryBufferRef mb, bool lazy = false)
+      : mb(mb), id(idCount++), lazy(lazy), fileKind(kind),
+        name(mb.getBufferIdentifier()) {}
 
   InputFile(Kind, const llvm::MachO::InterfaceFile &);
 
@@ -117,8 +122,10 @@ private:
 // .o file
 class ObjFile final : public InputFile {
 public:
-  ObjFile(MemoryBufferRef mb, uint32_t modTime, StringRef archiveName);
+  ObjFile(MemoryBufferRef mb, uint32_t modTime, StringRef archiveName,
+          bool lazy = false);
   ArrayRef<llvm::MachO::data_in_code_entry> getDataInCode() const;
+  template <class LP> void parse();
 
   static bool classof(const InputFile *f) { return f->kind() == ObjKind; }
 
@@ -130,7 +137,7 @@ public:
 private:
   Section *compactUnwindSection = nullptr;
 
-  template <class LP> void parse();
+  template <class LP> void parseLazy();
   template <class SectionHeader> void parseSections(ArrayRef<SectionHeader>);
   template <class LP>
   void parseSymbols(ArrayRef<typename LP::section> sectionHeaders,
@@ -229,10 +236,14 @@ private:
 class BitcodeFile final : public InputFile {
 public:
   explicit BitcodeFile(MemoryBufferRef mb, StringRef archiveName,
-                       uint64_t offsetInArchive);
+                       uint64_t offsetInArchive, bool lazy = false);
   static bool classof(const InputFile *f) { return f->kind() == BitcodeKind; }
+  void parse();
 
   std::unique_ptr<llvm::lto::InputFile> obj;
+
+private:
+  void parseLazy();
 };
 
 extern llvm::SetVector<InputFile *> inputFiles;
@@ -240,6 +251,8 @@ extern llvm::DenseMap<llvm::CachedHashStringRef, MemoryBufferRef> cachedReads;
 
 llvm::Optional<MemoryBufferRef> readFile(StringRef path);
 
+void extract(InputFile &file, StringRef reason);
+
 namespace detail {
 
 template <class CommandType, class... Types>
index b4b9ca6..1183d0e 100644 (file)
@@ -73,6 +73,10 @@ def thinlto_cache_policy: Joined<["--"], "thinlto-cache-policy=">,
     Group<grp_lld>;
 def O : JoinedOrSeparate<["-"], "O">,
     HelpText<"Optimize output file size">;
+def start_lib: Flag<["--"], "start-lib">,
+    HelpText<"Start a grouping of objects that should be treated as if they were together in an archive">;
+def end_lib: Flag<["--"], "end-lib">,
+    HelpText<"End a grouping of objects that should be treated as if they were together in an archive">;
 def no_warn_dylib_install_name: Joined<["--"], "no-warn-dylib-install-name">,
     HelpText<"Do not warn on -install-name if -dylib is not passed (default)">,
     Group<grp_lld>;
index ffae112..c7017c8 100644 (file)
@@ -115,6 +115,8 @@ Symbol *SymbolTable::addUndefined(StringRef name, InputFile *file,
     replaceSymbol<Undefined>(s, name, file, refState);
   else if (auto *lazy = dyn_cast<LazyArchive>(s))
     lazy->fetchArchiveMember();
+  else if (isa<LazyObject>(s))
+    extract(*s->getFile(), s->getName());
   else if (auto *dynsym = dyn_cast<DylibSymbol>(s))
     dynsym->reference(refState);
   else if (auto *undefined = dyn_cast<Undefined>(s))
@@ -199,6 +201,26 @@ Symbol *SymbolTable::addLazyArchive(StringRef name, ArchiveFile *file,
   return s;
 }
 
+Symbol *SymbolTable::addLazyObject(StringRef name, InputFile &file) {
+  Symbol *s;
+  bool wasInserted;
+  std::tie(s, wasInserted) = insert(name, &file);
+
+  if (wasInserted) {
+    replaceSymbol<LazyObject>(s, file, name);
+  } else if (isa<Undefined>(s)) {
+    extract(file, name);
+  } else if (auto *dysym = dyn_cast<DylibSymbol>(s)) {
+    if (dysym->isWeakDef()) {
+      if (dysym->getRefState() != RefState::Unreferenced)
+        extract(file, name);
+      else
+        replaceSymbol<LazyObject>(s, file, name);
+    }
+  }
+  return s;
+}
+
 Defined *SymbolTable::addSynthetic(StringRef name, InputSection *isec,
                                    uint64_t value, bool isPrivateExtern,
                                    bool includeInSymtab,
index 2a5f723..5f84417 100644 (file)
@@ -53,6 +53,7 @@ public:
 
   Symbol *addLazyArchive(StringRef name, ArchiveFile *file,
                          const llvm::object::Archive::Symbol &sym);
+  Symbol *addLazyObject(StringRef name, InputFile &file);
 
   Defined *addSynthetic(StringRef name, InputSection *, uint64_t value,
                         bool isPrivateExtern, bool includeInSymtab,
index ae369a6..b3d86b0 100644 (file)
@@ -38,6 +38,7 @@ public:
     CommonKind,
     DylibKind,
     LazyArchiveKind,
+    LazyObjectKind,
   };
 
   virtual ~Symbol() {}
@@ -51,6 +52,9 @@ public:
   }
 
   bool isLive() const { return used; }
+  bool isLazy() const {
+    return symbolKind == LazyArchiveKind || symbolKind == LazyObjectKind;
+  }
 
   virtual uint64_t getVA() const { return 0; }
 
@@ -294,12 +298,25 @@ private:
   const llvm::object::Archive::Symbol sym;
 };
 
+// A defined symbol in an ObjFile/BitcodeFile surrounded by --start-lib and
+// --end-lib.
+class LazyObject : public Symbol {
+public:
+  LazyObject(InputFile &file, StringRef name)
+      : Symbol(LazyObjectKind, name, &file) {
+    isUsedInRegularObj = false;
+  }
+
+  static bool classof(const Symbol *s) { return s->kind() == LazyObjectKind; }
+};
+
 union SymbolUnion {
   alignas(Defined) char a[sizeof(Defined)];
   alignas(Undefined) char b[sizeof(Undefined)];
   alignas(CommonSymbol) char c[sizeof(CommonSymbol)];
   alignas(DylibSymbol) char d[sizeof(DylibSymbol)];
   alignas(LazyArchive) char e[sizeof(LazyArchive)];
+  alignas(LazyObject) char f[sizeof(LazyObject)];
 };
 
 template <typename T, typename... ArgT>
index c4baf93..49af2f6 100644 (file)
@@ -226,7 +226,7 @@ void UnwindInfoSectionImpl<Ptr>::prepareRelocations(ConcatInputSection *isec) {
         // (See discussions/alternatives already considered on D107533)
         if (!defined->isExternal())
           if (Symbol *sym = symtab->find(defined->getName()))
-            if (sym->kind() != Symbol::LazyArchiveKind)
+            if (!sym->isLazy())
               r.referent = s = sym;
       }
       if (auto *undefined = dyn_cast<Undefined>(s)) {
index 4c2cafa..403c900 100644 (file)
@@ -7,6 +7,8 @@
 # RUN: llvm-ar r %t/pack.a %t/defined.o %t/combined.o
 # RUN: %lld -dylib -arch x86_64 -platform_version ios-simulator 12.0.0 15.0 -ObjC %t/pack.a -o %t/a.dylib
 # RUN: llvm-objdump --macho --syms %t/a.dylib | FileCheck %s
+# RUN: %lld -dylib -arch x86_64 -platform_version ios-simulator 12.0.0 15.0 -ObjC --start-lib %t/defined.o %t/combined.o --end-lib -o %t/a.dylib
+# RUN: llvm-objdump --macho --syms %t/a.dylib | FileCheck %s
 
 # CHECK: SYMBOL TABLE:
 # CHECK: {{.*}}  l     F __TEXT,__text _my_personality
index 6d494d4..9cfd528 100644 (file)
@@ -19,6 +19,9 @@
 # RUN: %lld -lSystem %t/test.o -o %t/test -L%t -lHasSomeObjC2 -ObjC
 # RUN: llvm-objdump --section-headers --syms %t/test | FileCheck %s --check-prefix=OBJC
 
+# RUN: %lld -lSystem %t/test.o -o %t/test --start-lib %t/no-objc.o %t/has-objc-symbol.o %t/has-objc-category.o %t/has-swift.o %t/wrong-arch.o --end-lib -ObjC
+# RUN: llvm-objdump --section-headers --syms %t/test | FileCheck %s --check-prefix=OBJC
+
 # OBJC:       Sections:
 # OBJC-NEXT:  Idx Name            Size   VMA  Type
 # OBJC-NEXT:    0 __text          {{.*}}      TEXT
@@ -27,8 +30,8 @@
 # OBJC-NEXT:    3 has_objc_symbol {{.*}}      DATA
 # OBJC-EMPTY:
 # OBJC-NEXT:  SYMBOL TABLE:
-# OBJC-NEXT:  g     F __TEXT,__text _main
-# OBJC-NEXT:  g     F __TEXT,__text _OBJC_CLASS_$_MyObject
+# OBJC-DAG:   g     F __TEXT,__text _main
+# OBJC-DAG:   g     F __TEXT,__text _OBJC_CLASS_$_MyObject
 
 # RUN: %lld -lSystem %t/test.o -o %t/test -L%t -lHasSomeObjC
 # RUN: llvm-objdump --section-headers --syms %t/test | FileCheck %s --check-prefix=NO-OBJC
 # RUN:   -lHasSomeObjC 2>&1 | FileCheck %s --check-prefix=DUP-ERROR
 # DUP-ERROR: error: duplicate symbol: _has_dup
 
+## TODO: Load has-objc-symbol.o prior to symbol resolution to match the archive behavior.
+# RUN: not %lld -dylib %t/refs-dup.o %t/refs-objc.o -o %t/refs-dup --start-lib %t/no-objc.o \
+# RUN:   %t/has-objc-symbol.o %t/has-objc-category.o %t/has-swift.o %t/wrong-arch.o --end-lib \
+# RUN:   -ObjC  --check-prefix=DUP-FROM-OBJC
+
 #--- has-objc-symbol.s
 .globl _OBJC_CLASS_$_MyObject, _has_dup
 _OBJC_CLASS_$_MyObject:
diff --git a/lld/test/MachO/start-lib.s b/lld/test/MachO/start-lib.s
new file mode 100644 (file)
index 0000000..aaf3824
--- /dev/null
@@ -0,0 +1,129 @@
+# REQUIRES: x86
+
+# RUN: rm -rf %t; split-file %s %t && cd %t
+# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin main.s -o main.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin calls-foo.s -o calls-foo.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin 1.s -o 1.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin 2.s -o 2.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin common.s -o common.o
+
+# RUN: llvm-as 1.ll -o 1.bc
+# RUN: llvm-as 2.ll -o 2.bc
+
+## Neither 1.o nor 2.o is loaded.
+# RUN: %lld main.o --start-lib 1.o 2.o --end-lib -why_load | count 0
+# RUN: %lld main.o --start-lib -filelist filelist --end-lib -why_load | count 0
+# RUN: llvm-readobj -s a.out | FileCheck %s
+# CHECK-NOT: Name: _foo
+# CHECK-NOT: Name: _bar
+
+## _bar loads 2.o. The last --end-lib can be omitted.
+# RUN: %lld main.o -u _bar --start-lib 1.o 2.o -t -why_load | FileCheck %s --check-prefix=CHECK2WHY
+# RUN: %lld main.o -u _bar --start-lib -filelist filelist -t -why_load | FileCheck %s --check-prefix=CHECK2WHY
+# RUN: llvm-readobj -s a.out | FileCheck --check-prefix=CHECK2 %s
+# CHECK2WHY:      main.o
+# CHECK2WHY-NEXT: 2.o
+# CHECK2WHY-NEXT: _bar forced load of 2.o
+# CHECK2WHY-EMPTY:
+# CHECK2-NOT: Name: _foo
+# CHECK2: Name: _bar
+# CHECK2-NOT: Name: _foo
+
+## _foo loads 1.o. 1.o loads 2.o.
+# RUN: %lld main.o -u _foo --start-lib 1.o 2.o -why_load | FileCheck %s --check-prefix=CHECK3WHY
+# RUN: llvm-readobj -s a.out | FileCheck --check-prefix=CHECK3 %s
+# RUN: %lld main.o -u _foo --start-lib 2.o --end-lib --start-lib 1.o -why_load | FileCheck %s --check-prefix=CHECK3WHY
+# RUN: llvm-readobj -s a.out | FileCheck --check-prefix=CHECK3 %s
+# CHECK3WHY:      _foo forced load of 1.o
+# CHECK3WHY-NEXT: _bar forced load of 2.o
+# CHECK3WHY-EMPTY:
+# CHECK3-DAG: Name: _foo
+# CHECK3-DAG: Name: _bar
+
+## Don't treat undefined _bar in 1.o as a lazy definition.
+# RUN: not %lld main.o -u _bar --start-lib 1.o 2>&1 | FileCheck %s --check-prefix=CHECK4
+# CHECK4: error: undefined symbol: _bar
+
+# RUN: %lld main.o -u _common --start-lib common.o
+# RUN: llvm-readobj -s a.out | FileCheck %s --check-prefix=COMMON1
+# COMMON1: Name: _common
+
+# RUN: %lld main.o --start-lib common.o
+# RUN: llvm-readobj -s a.out | FileCheck %s --check-prefix=COMMON2
+# COMMON2-NOT: Name: _common
+
+## Neither 1.bc nor 2.bc is loaded.
+# RUN: %lld main.o --start-lib 1.bc 2.bc -why_load | count 0
+# RUN: llvm-readobj -s a.out | FileCheck %s --check-prefix=BITCODE
+# BITCODE-NOT: Name: _foo
+# BITCODE-NOT: Name: _bar
+
+## _bar loads 2.bc.
+# RUN: %lld main.o -u _bar --start-lib 1.bc 2.bc -why_load | FileCheck %s --check-prefix=BITCODE2WHY
+# RUN: llvm-readobj -s a.out | FileCheck %s --check-prefix=BITCODE2
+# BITCODE2WHY:      _bar forced load of 2.bc
+# BITCODE2WHY-EMPTY:
+# BITCODE2-NOT: Name: _foo
+# BITCODE2: Name: _bar
+# BITCODE2-NOT: Name: _foo
+
+## calls-foo.o loads 1.bc. 1.bc loads 2.bc.
+# RUN: %lld calls-foo.o --start-lib 1.bc 2.bc -why_load | FileCheck %s --check-prefix=BITCODE3WHY
+# RUN: llvm-readobj -s a.out | FileCheck --check-prefix=BITCODE3 %s
+# RUN: %lld calls-foo.o --start-lib 2.bc --end-lib --start-lib 1.bc -why_load | FileCheck %s --check-prefix=BITCODE3WHY
+# RUN: llvm-readobj -s a.out | FileCheck --check-prefix=BITCODE3 %s
+# BITCODE3WHY:      _foo forced load of 1.bc
+# BITCODE3WHY-NEXT: _bar forced load of 2.bc
+# BITCODE3WHY-EMPTY:
+# BITCODE3-DAG: Name: _foo
+
+# RUN: not %lld main.o --start-lib --start-lib 2>&1 | FileCheck -check-prefix=NESTED-LIB %s
+# NESTED-LIB: error: nested --start-lib
+
+# RUN: not %lld --end-lib 2>&1 | FileCheck %s --check-prefix=STRAY
+# STRAY: error: stray --end-lib
+
+#--- main.s
+.globl _main
+_main:
+
+#--- calls-foo.s
+.globl _main
+_main:
+  call _foo
+
+#--- 1.s
+.globl _foo
+_foo:
+  call _bar
+
+#--- 2.s
+.globl _bar
+_bar:
+  ret
+
+#--- common.s
+.comm _common, 1
+
+#--- 1.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"
+
+define void @foo() {
+  tail call void () @bar()
+  ret void
+}
+
+declare void @bar()
+
+#--- 2.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"
+
+define void @bar() {
+  ret void
+}
+
+#--- filelist
+1.o
+2.o
index 6dd458f..6db3fd3 100644 (file)
 # RUN: %lld -lSystem -o %t/weak-ar-weak-dylib -L%t %t/weakfoo.a -lweakfoo %t/refs-foo.o
 # RUN: llvm-objdump --macho --lazy-bind --syms %t/weak-ar-weak-dylib | FileCheck %s --check-prefix=PREFER-WEAK-OBJECT
 
+# RUN: %lld -lSystem -o %t/weak-dylib-weak-ar -L%t -lweakfoo --start-lib %t/weakfoo.o --end-lib %t/refs-foo.o
+# RUN: llvm-objdump --macho --lazy-bind --syms %t/weak-dylib-weak-ar | FileCheck %s --check-prefix=PREFER-WEAK-OBJECT
+# RUN: %lld -lSystem -o %t/weak-ar-weak-dylib -L%t --start-lib %t/weakfoo.o --end-lib -lweakfoo %t/refs-foo.o
+# RUN: llvm-objdump --macho --lazy-bind --syms %t/weak-ar-weak-dylib | FileCheck %s --check-prefix=PREFER-WEAK-OBJECT
+
 ## (Weak) archive symbols have the same precedence as dylib symbols.
 # RUN: %lld -lSystem -o %t/weak-ar-nonweak-dylib -L%t %t/weakfoo.a -lfoo %t/refs-foo.o
 # RUN: llvm-objdump --macho --lazy-bind --syms %t/weak-ar-nonweak-dylib | FileCheck %s --check-prefix=PREFER-WEAK-OBJECT
 # RUN: %lld -dylib -lSystem -o %t/weak-unref-dylib-weak-ar -L%t -lweakfoo %t/weakfoo.a
 # RUN: llvm-objdump --macho --lazy-bind --syms %t/weak-unref-dylib-weak-ar | FileCheck %s --check-prefix=NO-SYM
 
+# RUN: %lld -dylib -lSystem -o %t/weak-ar-weak-unref-dylib -L%t --start-lib %t/weakfoo.o --end-lib -lweakfoo
+# RUN: llvm-objdump --macho --lazy-bind --syms %t/weak-ar-weak-unref-dylib | FileCheck %s --check-prefix=NO-SYM
+# RUN: %lld -dylib -lSystem -o %t/weak-unref-dylib-weak-ar -L%t -lweakfoo --start-lib %t/weakfoo.o --end-lib
+# RUN: llvm-objdump --macho --lazy-bind --syms %t/weak-unref-dylib-weak-ar | FileCheck %s --check-prefix=NO-SYM
+
 ## However, weak references are sufficient to cause the archive to be loaded.
 # RUN: %lld -dylib -lSystem -o %t/weak-ar-weak-ref-weak-dylib -L%t %t/weakfoo.a -lweakfoo %t/weak-refs-foo.o
 # RUN: llvm-objdump --macho --lazy-bind --syms %t/weak-ar-weak-ref-weak-dylib | FileCheck %s --check-prefix=PREFER-WEAK-OBJECT
 # RUN: %lld -dylib -lSystem -o %t/weak-ref-weak-dylib-weak-ar -L%t -lweakfoo %t/weak-refs-foo.o %t/weakfoo.a
 # RUN: llvm-objdump --macho --lazy-bind --syms %t/weak-ref-weak-dylib-weak-ar | FileCheck %s --check-prefix=PREFER-WEAK-OBJECT
 
+# RUN: %lld -dylib -lSystem -o %t/weak-ar-weak-ref-weak-dylib -L%t --start-lib %t/weakfoo.o --end-lib -lweakfoo %t/weak-refs-foo.o
+# RUN: llvm-objdump --macho --lazy-bind --syms %t/weak-ar-weak-ref-weak-dylib | FileCheck %s --check-prefix=PREFER-WEAK-OBJECT
+# RUN: %lld -dylib -lSystem -o %t/weak-ref-weak-dylib-weak-ar -L%t -lweakfoo --start-lib %t/weakfoo.o --end-lib %t/weak-refs-foo.o
+# RUN: llvm-objdump --macho --lazy-bind --syms %t/weak-ref-weak-dylib-weak-ar | FileCheck %s --check-prefix=PREFER-WEAK-OBJECT
+# RUN: %lld -dylib -lSystem -o %t/weak-ref-weak-dylib-weak-ar -L%t -lweakfoo %t/weak-refs-foo.o --start-lib %t/weakfoo.o --end-lib
+# RUN: llvm-objdump --macho --lazy-bind --syms %t/weak-ref-weak-dylib-weak-ar | FileCheck %s --check-prefix=PREFER-WEAK-OBJECT
+
 #--- foo.s
 .globl _foo
 .section __TEXT,nonweak