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
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;
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:
}
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:
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);
}
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)
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)
}
}
-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:
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);
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);
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>(
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);
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;
}
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)) {
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() {
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)
}
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
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>();
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 &);
// .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; }
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,
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;
llvm::Optional<MemoryBufferRef> readFile(StringRef path);
+void extract(InputFile &file, StringRef reason);
+
namespace detail {
template <class CommandType, class... Types>
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>;
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))
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,
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,
CommonKind,
DylibKind,
LazyArchiveKind,
+ LazyObjectKind,
};
virtual ~Symbol() {}
}
bool isLive() const { return used; }
+ bool isLazy() const {
+ return symbolKind == LazyArchiveKind || symbolKind == LazyObjectKind;
+ }
virtual uint64_t getVA() const { return 0; }
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>
// (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)) {
# 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
# 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
# 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:
--- /dev/null
+# 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
# 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