#include "llvm/ADT/StringSwitch.h"
#include "llvm/Config/llvm-config.h"
#include "llvm/LTO/LTO.h"
+#include "llvm/Object/Archive.h"
#include "llvm/Remarks/HotnessThresholdParser.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Compression.h"
inputSections.clear();
outputSections.clear();
memoryBuffers.clear();
- archiveFiles.clear();
binaryFiles.clear();
bitcodeFiles.clear();
lazyBitcodeFiles.clear();
return;
}
- std::unique_ptr<Archive> file =
- CHECK(Archive::create(mbref), path + ": failed to parse archive");
-
- // If an archive file has no symbol table, it may be intentional (used as a
- // group of lazy object files where the symbol table is not useful), or the
- // user is attempting LTO and using a default ar command that doesn't
- // understand the LLVM bitcode file. Treat the archive as a group of lazy
- // object files.
- if (file->isEmpty() || file->hasSymbolTable()) {
- // Handle the regular case.
- files.push_back(make<ArchiveFile>(std::move(file)));
- return;
- }
-
+ auto members = getArchiveMembers(mbref);
+ archiveFiles.emplace_back(path, members.size());
+
+ // Handle archives and --start-lib/--end-lib using the same code path. This
+ // scans all the ELF relocatable object files and bitcode files in the
+ // archive rather than just the index file, with the benefit that the
+ // symbols are only loaded once. For many projects archives see high
+ // utilization rates and it is a net performance win. --start-lib scans
+ // symbols in the same order that llvm-ar adds them to the index, so in the
+ // common case the semantics are identical. If the archive symbol table was
+ // created in a different order, or is incomplete, this strategy has
+ // different semantics. Such output differences are considered user error.
+ //
// All files within the archive get the same group ID to allow mutual
// references for --warn-backrefs.
bool saved = InputFile::isInGroup;
InputFile::isInGroup = true;
- for (const std::pair<MemoryBufferRef, uint64_t> &p :
- getArchiveMembers(mbref)) {
+ for (const std::pair<MemoryBufferRef, uint64_t> &p : members) {
auto magic = identify_magic(p.first.getBuffer());
if (magic == file_magic::bitcode || magic == file_magic::elf_relocatable)
files.push_back(createLazyFile(p.first, path, p.second));
else
- error(path + ": archive member '" + p.first.getBufferIdentifier() +
- "' is neither ET_REL nor LLVM bitcode");
+ warn(path + ": archive member '" + p.first.getBufferIdentifier() +
+ "' is neither ET_REL nor LLVM bitcode");
}
InputFile::isInGroup = saved;
if (!saved)
// Iterate over argv to process input files and positional arguments.
InputFile::isInGroup = false;
+ bool hasInput = false;
for (auto *arg : args) {
switch (arg->getOption().getID()) {
case OPT_library:
addLibrary(arg->getValue());
+ hasInput = true;
break;
case OPT_INPUT:
addFile(arg->getValue(), /*withLOption=*/false);
+ hasInput = true;
break;
case OPT_defsym: {
StringRef from;
}
}
- if (files.empty() && errorCount() == 0)
+ if (files.empty() && !hasInput && errorCount() == 0)
error("no input files");
}
return;
MemoryBufferRef mb;
- if (auto *lo = dyn_cast<LazyObject>(sym))
- mb = lo->file->mb;
- else
- mb = cast<LazyArchive>(sym)->getMemberBuffer();
+ mb = cast<LazyObject>(sym)->file->mb;
if (isBitcode(mb))
sym->extract();
std::unique_ptr<BitcodeCompiler> lto;
std::vector<InputFile *> files;
+
+public:
+ SmallVector<std::pair<StringRef, unsigned>, 0> archiveFiles;
};
// Parses command line options.
uint32_t InputFile::nextGroupId;
SmallVector<std::unique_ptr<MemoryBuffer>> elf::memoryBuffers;
-SmallVector<ArchiveFile *, 0> elf::archiveFiles;
SmallVector<BinaryFile *, 0> elf::binaryFiles;
SmallVector<BitcodeFile *, 0> elf::bitcodeFiles;
SmallVector<BitcodeFile *, 0> elf::lazyBitcodeFiles;
return;
}
- // .a file
- if (auto *f = dyn_cast<ArchiveFile>(file)) {
- archiveFiles.push_back(f);
- f->parse();
- return;
- }
-
// Lazy object file
if (file->lazy) {
if (auto *f = dyn_cast<BitcodeFile>(file)) {
// defined symbol in a .eh_frame becomes dangling symbols.
if (sec == &InputSection::discarded) {
Undefined und{this, StringRef(), binding, stOther, type, secIdx};
- // !ArchiveFile::parsed or !LazyObjFile::lazy means that the file
- // containing this object has not finished processing, i.e. this symbol is
- // a result of a lazy symbol extract. We should demote the lazy symbol to
- // an Undefined so that any relocations outside of the group to it will
- // trigger a discarded section error.
- if ((sym->symbolKind == Symbol::LazyArchiveKind &&
- !cast<ArchiveFile>(sym->file)->parsed) ||
- (sym->symbolKind == Symbol::LazyObjectKind && !sym->file->lazy)) {
+ // !LazyObjFile::lazy indicates that the file containing this object has
+ // not finished processing, i.e. this symbol is a result of a lazy symbol
+ // extract. We should demote the lazy symbol to an Undefined so that any
+ // relocations outside of the group to it will trigger a discarded section
+ // error.
+ if (sym->symbolKind == Symbol::LazyObjectKind && !sym->file->lazy) {
sym->replace(und);
// Prevent LTO from internalizing the symbol in case there is a
// reference to this symbol from this file.
}
}
-ArchiveFile::ArchiveFile(std::unique_ptr<Archive> &&file)
- : InputFile(ArchiveKind, file->getMemoryBufferRef()),
- file(std::move(file)) {}
-
-void ArchiveFile::parse() {
- SymbolTable &symtab = *elf::symtab;
- for (const Archive::Symbol &sym : file->symbols())
- symtab.addSymbol(LazyArchive{*this, sym});
-
- // Inform a future invocation of ObjFile<ELFT>::initializeSymbols() that this
- // archive has been processed.
- parsed = true;
-}
-
-// Returns a buffer pointing to a member file containing a given symbol.
-void ArchiveFile::extract(const Archive::Symbol &sym) {
- Archive::Child c =
- CHECK(sym.getMember(), toString(this) +
- ": could not get the member for symbol " +
- toELFString(sym));
-
- if (!seen.insert(c.getChildOffset()).second)
- return;
-
- MemoryBufferRef mb =
- CHECK(c.getMemoryBufferRef(),
- toString(this) +
- ": could not get the buffer for the member defining symbol " +
- toELFString(sym));
-
- if (tar && c.getParent()->isThin())
- tar->append(relativeToRoot(CHECK(c.getFullName(), this)), mb.getBuffer());
-
- InputFile *file = createObjectFile(mb, getName(), c.getChildOffset());
- file->groupId = groupId;
- parseFile(file);
-}
-
// The handling of tentative definitions (COMMON symbols) in archives is murky.
// A tentative definition will be promoted to a global definition if there are
// no non-tentative definitions to dominate it. When we hold a tentative
}
}
-bool ArchiveFile::shouldExtractForCommon(const Archive::Symbol &sym) {
- Archive::Child c =
- CHECK(sym.getMember(), toString(this) +
- ": could not get the member for symbol " +
- toELFString(sym));
- MemoryBufferRef mb =
- CHECK(c.getMemoryBufferRef(),
- toString(this) +
- ": could not get the buffer for the member defining symbol " +
- toELFString(sym));
-
- if (isBitcode(mb))
- return isBitcodeNonCommonDef(mb, sym.getName(), getName());
-
- return isNonCommonDef(mb, sym.getName(), getName());
-}
-
-size_t ArchiveFile::getMemberCount() const {
- size_t count = 0;
- Error err = Error::success();
- for (const Archive::Child &c : file->children(err)) {
- (void)c;
- ++count;
- }
- // This function is used by --print-archive-stats=, where an error does not
- // really matter.
- consumeError(std::move(err));
- return count;
-}
-
unsigned SharedFile::vernauxNum;
// Parse the version definitions in the object file if present, and return a
#include "lld/Common/Reproduce.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/BinaryFormat/Magic.h"
-#include "llvm/Object/Archive.h"
#include "llvm/Object/ELF.h"
+#include "llvm/Support/MemoryBufferRef.h"
#include "llvm/Support/Threading.h"
namespace llvm {
namespace elf {
-using llvm::object::Archive;
-
class InputSection;
class Symbol;
llvm::once_flag initDwarf;
};
-// An ArchiveFile object represents a .a file.
-class ArchiveFile : public InputFile {
-public:
- explicit ArchiveFile(std::unique_ptr<Archive> &&file);
- static bool classof(const InputFile *f) { return f->kind() == ArchiveKind; }
- void parse();
-
- // Pulls out an object file that contains a definition for Sym and
- // returns it. If the same file was instantiated before, this
- // function does nothing (so we don't instantiate the same file
- // more than once.)
- void extract(const Archive::Symbol &sym);
-
- // Check if a non-common symbol should be extracted to override a common
- // definition.
- bool shouldExtractForCommon(const Archive::Symbol &sym);
-
- size_t getMemberCount() const;
- size_t getExtractedMemberCount() const { return seen.size(); }
-
- bool parsed = false;
-
-private:
- std::unique_ptr<Archive> file;
- llvm::DenseSet<uint64_t> seen;
-};
-
class BitcodeFile : public InputFile {
public:
BitcodeFile(MemoryBufferRef m, StringRef archiveName,
std::string replaceThinLTOSuffix(StringRef path);
extern SmallVector<std::unique_ptr<MemoryBuffer>> memoryBuffers;
-extern SmallVector<ArchiveFile *, 0> archiveFiles;
extern SmallVector<BinaryFile *, 0> binaryFiles;
extern SmallVector<BitcodeFile *, 0> bitcodeFiles;
extern SmallVector<BitcodeFile *, 0> lazyBitcodeFiles;
//===----------------------------------------------------------------------===//
#include "MapFile.h"
+#include "Driver.h"
#include "InputFiles.h"
#include "LinkerScript.h"
#include "OutputSections.h"
}
os << "members\textracted\tarchive\n";
- for (const ArchiveFile *f : archiveFiles)
- os << f->getMemberCount() << '\t' << f->getExtractedMemberCount() << '\t'
- << f->getName() << '\n';
+
+ SmallVector<StringRef, 0> archives;
+ DenseMap<CachedHashStringRef, unsigned> all, extracted;
+ for (ELFFileBase *file : objectFiles)
+ if (file->archiveName.size())
+ ++extracted[CachedHashStringRef(file->archiveName)];
+ for (BitcodeFile *file : bitcodeFiles)
+ if (file->archiveName.size())
+ ++extracted[CachedHashStringRef(file->archiveName)];
+ for (std::pair<StringRef, unsigned> f : driver->archiveFiles) {
+ unsigned &v = extracted[CachedHashString(f.first)];
+ os << f.second << '\t' << v << '\t' << f.first << '\n';
+ // If the archive occurs multiple times, other instances have a count of 0.
+ v = 0;
+ }
}
return ret;
}
-std::string lld::toELFString(const Archive::Symbol &b) {
- return demangle(b.getName(), config->demangle);
-}
-
Defined *ElfSym::bss;
Defined *ElfSym::etext1;
Defined *ElfSym::etext2;
case Symbol::SharedKind:
case Symbol::UndefinedKind:
return 0;
- case Symbol::LazyArchiveKind:
case Symbol::LazyObjectKind:
llvm_unreachable("lazy symbol reached writer");
case Symbol::CommonKind:
}
void Symbol::extract() const {
- if (auto *sym = dyn_cast<LazyArchive>(this)) {
- cast<ArchiveFile>(sym->file)->extract(sym->sym);
- } else if (file->lazy) {
+ if (file->lazy) {
file->lazy = false;
parseFile(file);
}
}
-MemoryBufferRef LazyArchive::getMemberBuffer() {
- Archive::Child c =
- CHECK(sym.getMember(),
- "could not get the member for symbol " + toELFString(sym));
-
- return CHECK(c.getMemoryBufferRef(),
- "could not get the buffer for the member defining symbol " +
- toELFString(sym));
-}
-
uint8_t Symbol::computeBinding() const {
if ((visibility != STV_DEFAULT && visibility != STV_PROTECTED) ||
versionId == VER_NDX_LOCAL)
case Symbol::DefinedKind:
resolveDefined(cast<Defined>(other));
break;
- case Symbol::LazyArchiveKind:
- resolveLazy(cast<LazyArchive>(other));
- break;
case Symbol::LazyObjectKind:
resolveLazy(cast<LazyObject>(other));
break;
// For common objects, we want to look for global or weak definitions that
// should be extracted as the canonical definition instead.
if (isCommon() && elf::config->fortranCommon) {
- if (auto *laSym = dyn_cast<LazyArchive>(&other)) {
- ArchiveFile *archive = cast<ArchiveFile>(laSym->file);
- const Archive::Symbol &archiveSym = laSym->sym;
- if (archive->shouldExtractForCommon(archiveSym)) {
- replaceCommon(*this, other);
- return;
- }
- } else if (auto *loSym = dyn_cast<LazyObject>(&other)) {
+ if (auto *loSym = dyn_cast<LazyObject>(&other)) {
if (loSym->file->shouldExtractForCommon(getName())) {
replaceCommon(*this, other);
return;
#include "lld/Common/LLVM.h"
#include "lld/Common/Memory.h"
#include "llvm/ADT/DenseMap.h"
-#include "llvm/Object/Archive.h"
#include "llvm/Object/ELF.h"
#include <tuple>
// Returns a string representation for a symbol for diagnostics.
std::string toString(const elf::Symbol &);
-// There are two different ways to convert an Archive::Symbol to a string:
-// One for Microsoft name mangling and one for Itanium name mangling.
-// Call the functions toCOFFString and toELFString, not just toString.
-std::string toELFString(const llvm::object::Archive::Symbol &);
-
namespace elf {
class CommonSymbol;
class Defined;
CommonKind,
SharedKind,
UndefinedKind,
- LazyArchiveKind,
LazyObjectKind,
};
bool isLocal() const { return binding == llvm::ELF::STB_LOCAL; }
- bool isLazy() const {
- return symbolKind == LazyArchiveKind || symbolKind == LazyObjectKind;
- }
+ bool isLazy() const { return symbolKind == LazyObjectKind; }
// True if this is an undefined weak symbol. This only works once
// all input files have been added.
uint32_t alignment;
};
-// LazyArchive and LazyObject represent a symbols that is not yet in the link,
-// but we know where to find it if needed. If the resolver finds both Undefined
-// and Lazy for the same name, it will ask the Lazy to load a file.
+// LazyObject symbols represent symbols in object files between --start-lib and
+// --end-lib options. LLD also handles traditional archives as if all the files
+// in the archive are surrounded by --start-lib and --end-lib.
//
// A special complication is the handling of weak undefined symbols. They should
// not load a file, but we have to remember we have seen both the weak undefined
// and the lazy. We represent that with a lazy symbol with a weak binding. This
// means that code looking for undefined symbols normally also has to take lazy
// symbols into consideration.
-
-// This class represents a symbol defined in an archive file. It is
-// created from an archive file header, and it knows how to load an
-// object file from an archive to replace itself with a defined
-// symbol.
-class LazyArchive : public Symbol {
-public:
- LazyArchive(InputFile &file, const llvm::object::Archive::Symbol s)
- : Symbol(LazyArchiveKind, &file, s.getName(), llvm::ELF::STB_GLOBAL,
- llvm::ELF::STV_DEFAULT, llvm::ELF::STT_NOTYPE),
- sym(s) {}
-
- static bool classof(const Symbol *s) { return s->kind() == LazyArchiveKind; }
-
- MemoryBufferRef getMemberBuffer();
-
- const llvm::object::Archive::Symbol sym;
-};
-
-// LazyObject symbols represents symbols in object files between
-// --start-lib and --end-lib options.
class LazyObject : public Symbol {
public:
LazyObject(InputFile &file)
alignas(CommonSymbol) char b[sizeof(CommonSymbol)];
alignas(Undefined) char c[sizeof(Undefined)];
alignas(SharedSymbol) char d[sizeof(SharedSymbol)];
- alignas(LazyArchive) char e[sizeof(LazyArchive)];
- alignas(LazyObject) char f[sizeof(LazyObject)];
+ alignas(LazyObject) char e[sizeof(LazyObject)];
};
// It is important to keep the size of SymbolUnion small for performance and
AssertSymbol<CommonSymbol>();
AssertSymbol<Undefined>();
AssertSymbol<SharedSymbol>();
- AssertSymbol<LazyArchive>();
AssertSymbol<LazyObject>();
}
return sizeof(CommonSymbol);
case DefinedKind:
return sizeof(Defined);
- case LazyArchiveKind:
- return sizeof(LazyArchive);
case LazyObjectKind:
return sizeof(LazyObject);
case SharedKind:
--- /dev/null
+# REQUIRES: x86
+
+# RUN: rm -rf %t && split-file %s %t && cd %t
+# RUN: llvm-mc -filetype=obj -triple=x86_64 a.s -o a.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64 b.s -o b.o
+
+## Create an archive with incomplete index: foo is missing.
+# RUN: llvm-ar --format=gnu rc a.a a.o
+# RUN: llvm-ar --format=gnu rcS b.a b.o && tail -c +9 b.a > b-tail
+# RUN: cat a.a b-tail > weird.a
+# RUN: llvm-nm --print-armap weird.a | FileCheck %s --check-prefix=ARMAP
+
+# ARMAP: Archive map
+# ARMAP-NEXT: _start in a.o
+# ARMAP-EMPTY:
+
+## The incomplete archive index is ignored. -u foo extracts weird.a(b.o).
+## In GNU ld, foo is undefined.
+# RUN: ld.lld -m elf_x86_64 -u foo weird.a -o lazy
+# RUN: llvm-nm lazy | FileCheck %s --implicit-check-not={{.}}
+
+# CHECK: [[#%x,]] T _start
+# CHECK: [[#%x,]] T foo
+
+#--- a.s
+.globl _start
+_start:
+
+#--- b.s
+.globl foo
+foo:
# RUN: ld.lld -shared %t.archive.o -o %t.so
# RUN: llvm-ar crS %t.a %t.so
-# RUN: not ld.lld %t.o %t.a --noinhibit-exec -o /dev/null 2>&1 | FileCheck %s --check-prefix=ERR
+# RUN: ld.lld %t.o %t.a -o /dev/null 2>&1 | FileCheck %s --check-prefix=WARN
-# ERR: error: {{.*}}.a: archive member '{{.*}}.so' is neither ET_REL nor LLVM bitcode
+# WARN: warning: {{.*}}.a: archive member '{{.*}}.so' is neither ET_REL nor LLVM bitcode
.globl _start
_start:
# Test error when thin archive has symbol table but member is missing.
# RUN: not ld.lld --entry=_Z1fi -m elf_amd64_fbsd %t-syms.a -o /dev/null 2>&1 | FileCheck -DMSG=%errc_ENOENT %s --check-prefix=ERR2
-# ERR2: {{.*}}-syms.a: could not get the buffer for the member defining symbol f(int): '{{.*}}.o': [[MSG]]
-# RUN: not ld.lld --entry=_Z1fi --no-demangle -m elf_amd64_fbsd %t-syms.a -o /dev/null 2>&1 | FileCheck -DMSG=%errc_ENOENT %s --check-prefix=ERR2MANGLE
-# ERR2MANGLE: {{.*}}-syms.a: could not get the buffer for the member defining symbol _Z1fi: '{{.*}}.o': [[MSG]]
+# RUN: not ld.lld --entry=_Z1fi --no-demangle -m elf_amd64_fbsd %t-syms.a -o /dev/null 2>&1 | FileCheck -DMSG=%errc_ENOENT %s --check-prefix=ERR2
# Test error when thin archive is linked using --whole-archive but member is missing.
-# RUN: not ld.lld --entry=_Z1fi --whole-archive %t-syms.a -o /dev/null 2>&1 | FileCheck -DMSG=%errc_ENOENT %s --check-prefix=ERR3
-# ERR3: {{.*}}-syms.a: could not get the buffer for a child of the archive: '{{.*}}.o': [[MSG]]
+# RUN: not ld.lld --entry=_Z1fi --whole-archive %t-syms.a -o /dev/null 2>&1 | FileCheck -DMSG=%errc_ENOENT %s --check-prefix=ERR2
+# ERR2: {{.*}}-syms.a: could not get the buffer for a child of the archive: '{{.*}}.o': [[MSG]]
.global _Z1fi
_Z1fi:
// We used to crash when
// * The first object seen by the symbol table is from an archive.
// * -m was not used.
-// CHECK: .a({{.*}}a.o) is incompatible with {{.*}}b.o
-// RUN: not ld.lld --start-lib %ta.o --end-lib %tb.o -o /dev/null 2>&1 | FileCheck %s --check-prefix=CHECK2
+// RUN: not ld.lld --start-lib %ta.o --end-lib %tb.o -o /dev/null 2>&1 | FileCheck %s
-// CHECK2: {{.*}}b.o is incompatible{{$}}
+// CHECK: {{.*}}b.o is incompatible{{$}}
// RUN: FileCheck --check-prefix=A-AND-FREEBSD-SCRIPT %s
// A-AND-FREEBSD-SCRIPT: a.o is incompatible with elf32-i386-freebsd
+/// %tb.a is not extracted, but we report an error anyway.
+// RUN: rm -f %tb.a && llvm-ar rc %tb.a %tb.o
+// RUN: not ld.lld %ta.o %tb.a -o /dev/null 2>&1 | FileCheck --check-prefix=UNEXTRACTED-ARCHIVE %s
+// UNEXTRACTED-ARCHIVE: {{.*}}.a({{.*}}b.o) is incompatible with {{.*}}a.o
+
// We used to fail to identify this incompatibility and crash trying to
// read a 64 bit file as a 32 bit one.
-// RUN: llvm-mc -filetype=obj -triple=x86_64-pc-linux %p/Inputs/archive2.s -o %ta.o
+// RUN: llvm-mc -filetype=obj -triple=x86_64-pc-linux %p/Inputs/archive2.s -o %tc.o
// RUN: rm -f %t.a
-// RUN: llvm-ar rc %t.a %ta.o
-// RUN: llvm-mc -filetype=obj -triple=i686-linux %s -o %tb.o
-// RUN: not ld.lld %t.a %tb.o 2>&1 -o /dev/null | FileCheck --check-prefix=ARCHIVE %s
-// ARCHIVE: .a({{.*}}a.o) is incompatible with {{.*}}b.o
+// RUN: llvm-ar rc %t.a %tc.o
+// RUN: llvm-mc -filetype=obj -triple=i686-linux %s -o %td.o
+// RUN: not ld.lld %t.a %td.o 2>&1 -o /dev/null | FileCheck --check-prefix=ARCHIVE %s
+// ARCHIVE: {{.*}}d.o is incompatible
.global _start
_start:
.data
BCSYM-NEXT: W foo
;; Check that the symbols are handled in the expected order.
-TRACE: lib.a: lazy definition of foo
-TRACE-NEXT: lib.a: lazy definition of bar
+TRACE: lib.a(obj.o): lazy definition of foo
+TRACE-NEXT: lib.a(obj.o): lazy definition of bar
TRACE-NEXT: lib.a(bc.bc): reference to bar
TRACE-NEXT: lib.a(obj.o): reference to foo
TRACE-NEXT: lib.a(obj.o): definition of bar
; RUN: ld.lld -shared --exclude-libs=b.a %t/a.bc %t/b.a -o %t.so -y __divti3 2>&1 | FileCheck %s --check-prefix=TRACE
; RUN: llvm-readelf --dyn-syms %t.so | FileCheck %s
-; TRACE: {{.*}}/b.a: lazy definition of __divti3
+; TRACE: {{.*}}/b.a(b.o): lazy definition of __divti3
; TRACE-NEXT: lto.tmp: reference to __divti3
; TRACE-NEXT: {{.*}}/b.a(b.o): definition of __divti3
// RUN: llvm-mc -filetype=obj -triple=x86_64-pc-linux %s -o %t.o
// RUN: rm -f %t.a
// RUN: llvm-ar rcs %t.a %t.o
-// RUN: not ld.lld -o /dev/null -u _start %t.a 2>&1 | FileCheck %s
-
-// CHECK: target emulation unknown: -m or at least one .o file required
+// RUN: ld.lld -o /dev/null -u _start %t.a 2>&1 | count 0
.global _start
_start:
# RUN: FileCheck -check-prefix=FOO_AND_COMMON %s
# FOO_AND_COMMON: trace-symbols.s.tmp: reference to foo
# FOO_AND_COMMON: trace-symbols.s.tmp2: definition of foo
-# FOO_AND_COMMON: trace-symbols.s.tmp1.a: lazy definition of common
+# FOO_AND_COMMON: trace-symbols.s.tmp1.a({{.*}}.tmp1): lazy definition of common
# RUN: ld.lld -y foo -y common %t %t1.so %t2 -o %t3 | \
# RUN: FileCheck -check-prefix=SHLIBDCOMMON %s