213 changes: 170 additions & 43 deletions clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1136,9 +1136,125 @@ std::optional<std::string> searchLibrary(StringRef Input, StringRef Root,
return searchLibraryBaseName(Input, Root, SearchPaths);
}

/// Search the input files and libraries for embedded device offloading code and
/// add it to the list of files to be linked. Files coming from static libraries
/// are only added to the input if they are used by an existing input file.
/// Common redeclaration of needed symbol flags.
enum Symbol : uint32_t {
Sym_None = 0,
Sym_Undefined = 1U << 1,
Sym_Weak = 1U << 2,
};

/// Scan the symbols from a BitcodeFile \p Buffer and record if we need to
/// extract any symbols from it.
Expected<bool> getSymbolsFromBitcode(MemoryBufferRef Buffer, StringSaver &Saver,
DenseMap<StringRef, Symbol> &Syms) {
Expected<IRSymtabFile> IRSymtabOrErr = readIRSymtab(Buffer);
if (!IRSymtabOrErr)
return IRSymtabOrErr.takeError();

bool ShouldExtract = false;
for (unsigned I = 0; I != IRSymtabOrErr->Mods.size(); ++I) {
for (const auto &Sym : IRSymtabOrErr->TheReader.module_symbols(I)) {
if (Sym.isFormatSpecific() || !Sym.isGlobal())
continue;

bool NewSymbol = Syms.count(Sym.getName()) == 0;
auto &OldSym = Syms[Saver.save(Sym.getName())];

// We will extract if it defines a currenlty undefined non-weak symbol.
bool ResolvesStrongReference =
((OldSym & Sym_Undefined && !(OldSym & Sym_Weak)) &&
!Sym.isUndefined());
// We will extract if it defines a new global symbol visible to the host.
bool NewGlobalSymbol =
((NewSymbol || (OldSym & Sym_Undefined)) && !Sym.isUndefined() &&
!Sym.canBeOmittedFromSymbolTable() &&
(Sym.getVisibility() != GlobalValue::HiddenVisibility));
ShouldExtract |= ResolvesStrongReference | NewGlobalSymbol;

// Update this symbol in the "table" with the new information.
if (OldSym & Sym_Undefined && !Sym.isUndefined())
OldSym = static_cast<Symbol>(OldSym & ~Sym_Undefined);
if (Sym.isUndefined() && NewSymbol)
OldSym = static_cast<Symbol>(OldSym | Sym_Undefined);
if (Sym.isWeak())
OldSym = static_cast<Symbol>(OldSym | Sym_Weak);
}
}

return ShouldExtract;
}

/// Scan the symbols from an ObjectFile \p Obj and record if we need to extract
/// any symbols from it.
Expected<bool> getSymbolsFromObject(const ObjectFile &Obj, StringSaver &Saver,
DenseMap<StringRef, Symbol> &Syms) {
bool ShouldExtract = false;
for (SymbolRef Sym : Obj.symbols()) {
auto FlagsOrErr = Sym.getFlags();
if (!FlagsOrErr)
return FlagsOrErr.takeError();

if (!(*FlagsOrErr & SymbolRef::SF_Global) ||
(*FlagsOrErr & SymbolRef::SF_FormatSpecific))
continue;

auto NameOrErr = Sym.getName();
if (!NameOrErr)
return NameOrErr.takeError();

bool NewSymbol = Syms.count(*NameOrErr) == 0;
auto &OldSym = Syms[Saver.save(*NameOrErr)];

// We will extract if it defines a currenlty undefined non-weak symbol.
bool ResolvesStrongReference = (OldSym & Sym_Undefined) &&
!(OldSym & Sym_Weak) &&
!(*FlagsOrErr & SymbolRef::SF_Undefined);

// We will extract if it defines a new global symbol visible to the host.
bool NewGlobalSymbol = ((NewSymbol || (OldSym & Sym_Undefined)) &&
!(*FlagsOrErr & SymbolRef::SF_Undefined) &&
!(*FlagsOrErr & SymbolRef::SF_Hidden));
ShouldExtract |= ResolvesStrongReference | NewGlobalSymbol;

// Update this symbol in the "table" with the new information.
if (OldSym & Sym_Undefined && !(*FlagsOrErr & SymbolRef::SF_Undefined))
OldSym = static_cast<Symbol>(OldSym & ~Sym_Undefined);
if (*FlagsOrErr & SymbolRef::SF_Undefined && NewSymbol)
OldSym = static_cast<Symbol>(OldSym | Sym_Undefined);
if (*FlagsOrErr & SymbolRef::SF_Weak)
OldSym = static_cast<Symbol>(OldSym | Sym_Weak);
}
return ShouldExtract;
}

/// Attempt to 'resolve' symbols found in input files. We use this to
/// determine if an archive member needs to be extracted. An archive member
/// will be extracted if any of the following is true.
/// 1) It defines an undefined symbol in a regular object filie.
/// 2) It defines a global symbol without hidden visibility that has not
/// yet been defined.
Expected<bool> getSymbols(StringRef Image, StringSaver &Saver,
DenseMap<StringRef, Symbol> &Syms) {
MemoryBufferRef Buffer = MemoryBufferRef(Image, "");
switch (identify_magic(Image)) {
case file_magic::bitcode:
return getSymbolsFromBitcode(Buffer, Saver, Syms);
case file_magic::elf_relocatable: {
Expected<std::unique_ptr<ObjectFile>> ObjFile =
ObjectFile::createObjectFile(Buffer);
if (!ObjFile)
return ObjFile.takeError();
return getSymbolsFromObject(**ObjFile, Saver, Syms);
}
default:
return false;
}
}

/// Search the input files and libraries for embedded device offloading code
/// and add it to the list of files to be linked. Files coming from static
/// libraries are only added to the input if they are used by an existing
/// input file.
Expected<SmallVector<OffloadFile>> getDeviceInput(const ArgList &Args) {
llvm::TimeTraceScope TimeScope("ExtractDeviceCode");

Expand All @@ -1147,47 +1263,68 @@ Expected<SmallVector<OffloadFile>> getDeviceInput(const ArgList &Args) {
for (const opt::Arg *Arg : Args.filtered(OPT_library_path))
LibraryPaths.push_back(Arg->getValue());

BumpPtrAllocator Alloc;
StringSaver Saver(Alloc);

// Try to extract device code from the linker input files.
SmallVector<OffloadFile> InputFiles;
SmallVector<OffloadFile> LazyInputFiles;
for (const opt::Arg *Arg : Args.filtered(OPT_INPUT)) {
StringRef Filename = Arg->getValue();
if (!sys::fs::exists(Filename) || sys::fs::is_directory(Filename))
DenseMap<OffloadFile::TargetID, DenseMap<StringRef, Symbol>> Syms;
for (const opt::Arg *Arg : Args.filtered(OPT_INPUT, OPT_library)) {
std::optional<std::string> Filename =
Arg->getOption().matches(OPT_library)
? searchLibrary(Arg->getValue(), Root, LibraryPaths)
: std::string(Arg->getValue());

if (!Filename && Arg->getOption().matches(OPT_library))
reportError(createStringError(inconvertibleErrorCode(),
"unable to find library -l%s",
Arg->getValue()));

if (!Filename || !sys::fs::exists(*Filename) ||
sys::fs::is_directory(*Filename))
continue;

ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrErr =
MemoryBuffer::getFileOrSTDIN(Filename);
MemoryBuffer::getFileOrSTDIN(*Filename);
if (std::error_code EC = BufferOrErr.getError())
return createFileError(Filename, EC);
return createFileError(*Filename, EC);

if (identify_magic((*BufferOrErr)->getBuffer()) ==
file_magic::elf_shared_object)
MemoryBufferRef Buffer = **BufferOrErr;
if (identify_magic(Buffer.getBuffer()) == file_magic::elf_shared_object)
continue;

bool IsLazy =
identify_magic((*BufferOrErr)->getBuffer()) == file_magic::archive;
if (Error Err = extractOffloadBinaries(
**BufferOrErr, IsLazy ? LazyInputFiles : InputFiles))
SmallVector<OffloadFile> Binaries;
if (Error Err = extractOffloadBinaries(Buffer, Binaries))
return std::move(Err);
}

// Try to extract input from input archive libraries.
for (const opt::Arg *Arg : Args.filtered(OPT_library)) {
if (auto Library = searchLibrary(Arg->getValue(), Root, LibraryPaths)) {
ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrErr =
MemoryBuffer::getFileOrSTDIN(*Library);
if (std::error_code EC = BufferOrErr.getError())
reportError(createFileError(*Library, EC));

if (identify_magic((*BufferOrErr)->getBuffer()) != file_magic::archive)
continue;

if (Error Err = extractOffloadBinaries(**BufferOrErr, LazyInputFiles))
return std::move(Err);
} else {
reportError(createStringError(inconvertibleErrorCode(),
"unable to find library -l%s",
Arg->getValue()));
// We only extract archive members that are needed.
bool IsArchive = identify_magic(Buffer.getBuffer()) == file_magic::archive;
bool Extracted = true;
while (Extracted) {
Extracted = false;
for (OffloadFile &Binary : Binaries) {
if (!Binary.getBinary())
continue;

// If we don't have an object file for this architecture do not
// extract.
if (IsArchive && !Syms.count(Binary))
continue;

Expected<bool> ExtractOrErr =
getSymbols(Binary.getBinary()->getImage(), Saver, Syms[Binary]);
if (!ExtractOrErr)
return ExtractOrErr.takeError();

Extracted = IsArchive && *ExtractOrErr;

if (!IsArchive || Extracted)
InputFiles.emplace_back(std::move(Binary));

// If we extracted any files we need to check all the symbols again.
if (Extracted)
break;
}
}
}

Expand All @@ -1198,16 +1335,6 @@ Expected<SmallVector<OffloadFile>> getDeviceInput(const ArgList &Args) {
InputFiles.push_back(std::move(*FileOrErr));
}

DenseSet<OffloadFile::TargetID> IsTargetUsed;
for (const auto &File : InputFiles)
IsTargetUsed.insert(File);

// We should only include input files that are used.
// TODO: Only load a library if it defined undefined symbols in the input.
for (auto &LazyFile : LazyInputFiles)
if (IsTargetUsed.contains(LazyFile))
InputFiles.emplace_back(std::move(LazyFile));

return std::move(InputFiles);
}

Expand Down
2 changes: 0 additions & 2 deletions openmp/libomptarget/test/lit.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,6 @@ else: # Unices
if config.cuda_libdir:
config.test_flags += " -Wl,-rpath," + config.cuda_libdir
if config.libomptarget_current_target.startswith('amdgcn'):
config.test_flags += " --libomptarget-amdgcn-bc-path=" + config.library_dir
if config.libomptarget_current_target.startswith('nvptx'):
config.test_flags += " --libomptarget-nvptx-bc-path=" + config.library_dir
if config.libomptarget_current_target.endswith('-LTO'):
config.test_flags += " -foffload-lto"
Expand Down