This PR reapplies the changes previously introduced in https://github.com/llvm/llvm-project/pull/148410. It introduces a redesigned and rebuilt Cling-based auto-loading workaround that enables scanning libraries and resolving unresolved symbols within those libraries. Fix build failures in LibraryResolverTest and silence symlink warning This commit resolves issues observed in the build bots: 1. Silences the -Wunused-result warning by handling the return value of ::symlink in LibraryResolverTest.cpp. Previously, ignoring the return value triggered compiler warnings. 2. Fixes a linker error in OrcJITTests caused by an undefined symbol: llvm::yaml::convertYAML. The test setup in LibraryResolverTest.cpp now correctly links against the required LLVM YAML library symbols. 3. Fixes persistent build bot failure caused by a path difference issue. This resolves the build failures for PR https://github.com/llvm/llvm-project/pull/148410 on the affected bots.
371 lines
11 KiB
C++
371 lines
11 KiB
C++
//===- LibraryResolver.cpp - Library Resolution of Unresolved Symbols ---===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// Library resolution impl for unresolved symbols
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "llvm/ExecutionEngine/Orc/TargetProcess/LibraryResolver.h"
|
|
#include "llvm/ExecutionEngine/Orc/TargetProcess/LibraryScanner.h"
|
|
|
|
#include "llvm/ADT/StringSet.h"
|
|
|
|
#include "llvm/BinaryFormat/MachO.h"
|
|
#include "llvm/Object/COFF.h"
|
|
#include "llvm/Object/ELF.h"
|
|
#include "llvm/Object/ELFObjectFile.h"
|
|
#include "llvm/Object/MachO.h"
|
|
#include "llvm/Object/ObjectFile.h"
|
|
#include "llvm/Support/Error.h"
|
|
|
|
#include <mutex>
|
|
#include <thread>
|
|
|
|
#define DEBUG_TYPE "orc-resolver"
|
|
|
|
namespace llvm::orc {
|
|
|
|
LibraryResolver::LibraryResolver(const LibraryResolver::Setup &S)
|
|
: LibPathCache(S.Cache ? S.Cache : std::make_shared<LibraryPathCache>()),
|
|
LibPathResolver(S.PResolver
|
|
? S.PResolver
|
|
: std::make_shared<PathResolver>(LibPathCache)),
|
|
ScanHelper(S.BasePaths, LibPathCache, LibPathResolver),
|
|
FB(S.FilterBuilder), LibMgr(),
|
|
ShouldScanCall(S.ShouldScanCall ? S.ShouldScanCall
|
|
: [](StringRef) -> bool { return true; }),
|
|
scanBatchSize(S.ScanBatchSize) {
|
|
|
|
if (ScanHelper.getAllUnits().empty()) {
|
|
LLVM_DEBUG(dbgs() << "Warning: No base paths provided for scanning.\n");
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<LibraryResolutionDriver>
|
|
LibraryResolutionDriver::create(const LibraryResolver::Setup &S) {
|
|
auto LR = std::make_unique<LibraryResolver>(S);
|
|
return std::unique_ptr<LibraryResolutionDriver>(
|
|
new LibraryResolutionDriver(std::move(LR)));
|
|
}
|
|
|
|
void LibraryResolutionDriver::addScanPath(const std::string &Path, PathType K) {
|
|
LR->ScanHelper.addBasePath(Path, K);
|
|
}
|
|
|
|
bool LibraryResolutionDriver::markLibraryLoaded(StringRef Path) {
|
|
auto Lib = LR->LibMgr.getLibrary(Path);
|
|
if (!Lib)
|
|
return false;
|
|
|
|
Lib->setState(LibraryManager::LibState::Loaded);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool LibraryResolutionDriver::markLibraryUnLoaded(StringRef Path) {
|
|
auto Lib = LR->LibMgr.getLibrary(Path);
|
|
if (!Lib)
|
|
return false;
|
|
|
|
Lib->setState(LibraryManager::LibState::Unloaded);
|
|
|
|
return true;
|
|
}
|
|
|
|
void LibraryResolutionDriver::resolveSymbols(
|
|
std::vector<std::string> Syms,
|
|
LibraryResolver::OnSearchComplete OnCompletion,
|
|
const SearchConfig &Config) {
|
|
LR->searchSymbolsInLibraries(Syms, std::move(OnCompletion), Config);
|
|
}
|
|
|
|
static bool shouldIgnoreSymbol(const object::SymbolRef &Sym,
|
|
uint32_t IgnoreFlags) {
|
|
Expected<uint32_t> FlagsOrErr = Sym.getFlags();
|
|
if (!FlagsOrErr) {
|
|
consumeError(FlagsOrErr.takeError());
|
|
return true;
|
|
}
|
|
|
|
uint32_t Flags = *FlagsOrErr;
|
|
|
|
using Filter = SymbolEnumeratorOptions;
|
|
if ((IgnoreFlags & Filter::IgnoreUndefined) &&
|
|
(Flags & object::SymbolRef::SF_Undefined))
|
|
return true;
|
|
if ((IgnoreFlags & Filter::IgnoreIndirect) &&
|
|
(Flags & object::SymbolRef::SF_Indirect))
|
|
return true;
|
|
if ((IgnoreFlags & Filter::IgnoreWeak) &&
|
|
(Flags & object::SymbolRef::SF_Weak))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool SymbolEnumerator::enumerateSymbols(StringRef Path, OnEachSymbolFn OnEach,
|
|
const SymbolEnumeratorOptions &Opts) {
|
|
if (Path.empty())
|
|
return false;
|
|
|
|
ObjectFileLoader ObjLoader(Path);
|
|
|
|
auto ObjOrErr = ObjLoader.getObjectFile();
|
|
if (!ObjOrErr) {
|
|
std::string ErrMsg;
|
|
handleAllErrors(ObjOrErr.takeError(),
|
|
[&](const ErrorInfoBase &EIB) { ErrMsg = EIB.message(); });
|
|
LLVM_DEBUG(dbgs() << "Failed loading object file: " << Path
|
|
<< "\nError: " << ErrMsg << "\n");
|
|
return false;
|
|
}
|
|
|
|
object::ObjectFile *Obj = &ObjOrErr.get();
|
|
|
|
auto processSymbolRange =
|
|
[&](object::ObjectFile::symbol_iterator_range Range) -> EnumerateResult {
|
|
for (const auto &Sym : Range) {
|
|
if (shouldIgnoreSymbol(Sym, Opts.FilterFlags))
|
|
continue;
|
|
|
|
auto NameOrErr = Sym.getName();
|
|
if (!NameOrErr) {
|
|
consumeError(NameOrErr.takeError());
|
|
continue;
|
|
}
|
|
|
|
StringRef Name = *NameOrErr;
|
|
if (Name.empty())
|
|
continue;
|
|
|
|
EnumerateResult Res = OnEach(Name);
|
|
if (Res != EnumerateResult::Continue)
|
|
return Res;
|
|
}
|
|
return EnumerateResult::Continue;
|
|
};
|
|
|
|
EnumerateResult Res = processSymbolRange(Obj->symbols());
|
|
if (Res != EnumerateResult::Continue)
|
|
return Res == EnumerateResult::Stop;
|
|
|
|
if (Obj->isELF()) {
|
|
const auto *ElfObj = cast<object::ELFObjectFileBase>(Obj);
|
|
Res = processSymbolRange(ElfObj->getDynamicSymbolIterators());
|
|
if (Res != EnumerateResult::Continue)
|
|
return Res == EnumerateResult::Stop;
|
|
} else if (Obj->isCOFF()) {
|
|
const auto *CoffObj = cast<object::COFFObjectFile>(Obj);
|
|
for (auto I = CoffObj->export_directory_begin(),
|
|
E = CoffObj->export_directory_end();
|
|
I != E; ++I) {
|
|
StringRef Name;
|
|
if (I->getSymbolName(Name))
|
|
continue;
|
|
if (Name.empty())
|
|
continue;
|
|
|
|
EnumerateResult Res = OnEach(Name);
|
|
if (Res != EnumerateResult::Continue)
|
|
return Res == EnumerateResult::Stop;
|
|
}
|
|
} else if (Obj->isMachO()) {
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
class SymbolSearchContext {
|
|
public:
|
|
SymbolSearchContext(SymbolQuery &Q) : Q(Q) {}
|
|
|
|
bool hasSearched(LibraryInfo *Lib) const { return Searched.count(Lib); }
|
|
|
|
void markSearched(LibraryInfo *Lib) { Searched.insert(Lib); }
|
|
|
|
inline bool allResolved() const { return Q.allResolved(); }
|
|
|
|
SymbolQuery &query() { return Q; }
|
|
|
|
private:
|
|
SymbolQuery &Q;
|
|
DenseSet<LibraryInfo *> Searched;
|
|
};
|
|
|
|
void LibraryResolver::resolveSymbolsInLibrary(
|
|
LibraryInfo &Lib, SymbolQuery &UnresolvedSymbols,
|
|
const SymbolEnumeratorOptions &Opts) {
|
|
LLVM_DEBUG(dbgs() << "Checking unresolved symbols "
|
|
<< " in library : " << Lib.getFileName() << "\n";);
|
|
StringSet<> DiscoveredSymbols;
|
|
|
|
if (!UnresolvedSymbols.hasUnresolved()) {
|
|
LLVM_DEBUG(dbgs() << "Skipping library: " << Lib.getFullPath()
|
|
<< " — unresolved symbols exist.\n";);
|
|
return;
|
|
}
|
|
|
|
bool HasEnumerated = false;
|
|
auto enumerateSymbolsIfNeeded = [&]() {
|
|
if (HasEnumerated)
|
|
return;
|
|
|
|
HasEnumerated = true;
|
|
|
|
LLVM_DEBUG(dbgs() << "Enumerating symbols in library: " << Lib.getFullPath()
|
|
<< "\n";);
|
|
SymbolEnumerator::enumerateSymbols(
|
|
Lib.getFullPath(),
|
|
[&](StringRef sym) {
|
|
DiscoveredSymbols.insert(sym);
|
|
return EnumerateResult::Continue;
|
|
},
|
|
Opts);
|
|
|
|
if (DiscoveredSymbols.empty()) {
|
|
LLVM_DEBUG(dbgs() << " No symbols and remove library : "
|
|
<< Lib.getFullPath() << "\n";);
|
|
LibMgr.removeLibrary(Lib.getFullPath());
|
|
return;
|
|
}
|
|
};
|
|
|
|
if (!Lib.hasFilter()) {
|
|
LLVM_DEBUG(dbgs() << "Building filter for library: " << Lib.getFullPath()
|
|
<< "\n";);
|
|
enumerateSymbolsIfNeeded();
|
|
SmallVector<StringRef> SymbolVec;
|
|
SymbolVec.reserve(DiscoveredSymbols.size());
|
|
for (const auto &KV : DiscoveredSymbols)
|
|
SymbolVec.push_back(KV.first());
|
|
|
|
Lib.ensureFilterBuilt(FB, SymbolVec);
|
|
LLVM_DEBUG({
|
|
dbgs() << "DiscoveredSymbols : " << DiscoveredSymbols.size() << "\n";
|
|
for (const auto &KV : DiscoveredSymbols)
|
|
dbgs() << "DiscoveredSymbols : " << KV.first() << "\n";
|
|
});
|
|
}
|
|
|
|
const auto &Unresolved = UnresolvedSymbols.getUnresolvedSymbols();
|
|
bool HadAnySym = false;
|
|
LLVM_DEBUG(dbgs() << "Total unresolved symbols : " << Unresolved.size()
|
|
<< "\n";);
|
|
for (const auto &Sym : Unresolved) {
|
|
if (Lib.mayContain(Sym)) {
|
|
LLVM_DEBUG(dbgs() << "Checking symbol '" << Sym
|
|
<< "' in library: " << Lib.getFullPath() << "\n";);
|
|
enumerateSymbolsIfNeeded();
|
|
if (DiscoveredSymbols.count(Sym) > 0) {
|
|
LLVM_DEBUG(dbgs() << " Resolved symbol: " << Sym
|
|
<< " in library: " << Lib.getFullPath() << "\n";);
|
|
UnresolvedSymbols.resolve(Sym, Lib.getFullPath());
|
|
HadAnySym = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
using LibraryState = LibraryManager::LibState;
|
|
if (HadAnySym && Lib.getState() != LibraryState::Loaded)
|
|
Lib.setState(LibraryState::Queried);
|
|
}
|
|
|
|
void LibraryResolver::searchSymbolsInLibraries(
|
|
std::vector<std::string> &SymbolList, OnSearchComplete OnComplete,
|
|
const SearchConfig &Config) {
|
|
SymbolQuery Q(SymbolList);
|
|
|
|
using LibraryState = LibraryManager::LibState;
|
|
using LibraryType = PathType;
|
|
auto tryResolveFrom = [&](LibraryState S, LibraryType K) {
|
|
LLVM_DEBUG(dbgs() << "Trying resolve from state=" << static_cast<int>(S)
|
|
<< " type=" << static_cast<int>(K) << "\n";);
|
|
|
|
SymbolSearchContext Ctx(Q);
|
|
while (!Ctx.allResolved()) {
|
|
|
|
for (auto &Lib : LibMgr.getView(S, K)) {
|
|
if (Ctx.hasSearched(Lib.get()))
|
|
continue;
|
|
|
|
// can use Async here?
|
|
resolveSymbolsInLibrary(*Lib, Ctx.query(), Config.Options);
|
|
Ctx.markSearched(Lib.get());
|
|
|
|
if (Ctx.allResolved())
|
|
return;
|
|
}
|
|
|
|
if (Ctx.allResolved())
|
|
return;
|
|
|
|
if (!scanLibrariesIfNeeded(K, scanBatchSize))
|
|
break; // no more new libs to scan
|
|
}
|
|
};
|
|
|
|
for (const auto &[St, Ty] : Config.Policy.Plan) {
|
|
tryResolveFrom(St, Ty);
|
|
if (Q.allResolved())
|
|
break;
|
|
}
|
|
|
|
// done:
|
|
LLVM_DEBUG({
|
|
dbgs() << "Search complete.\n";
|
|
for (const auto &r : Q.getAllResults())
|
|
dbgs() << "Resolved Symbol:" << r->Name << " -> " << r->ResolvedLibPath
|
|
<< "\n";
|
|
});
|
|
|
|
OnComplete(Q);
|
|
}
|
|
|
|
bool LibraryResolver::scanLibrariesIfNeeded(PathType PK, size_t BatchSize) {
|
|
LLVM_DEBUG(dbgs() << "LibraryResolver::scanLibrariesIfNeeded: Scanning for "
|
|
<< (PK == PathType::User ? "User" : "System")
|
|
<< " libraries\n";);
|
|
if (!ScanHelper.leftToScan(PK))
|
|
return false;
|
|
|
|
LibraryScanner Scanner(ScanHelper, LibMgr, ShouldScanCall);
|
|
Scanner.scanNext(PK, BatchSize);
|
|
return true;
|
|
}
|
|
|
|
bool LibraryResolver::symbolExistsInLibrary(const LibraryInfo &Lib,
|
|
StringRef SymName,
|
|
std::vector<std::string> *AllSyms) {
|
|
SymbolEnumeratorOptions Opts;
|
|
return symbolExistsInLibrary(Lib, SymName, AllSyms, Opts);
|
|
}
|
|
|
|
bool LibraryResolver::symbolExistsInLibrary(
|
|
const LibraryInfo &Lib, StringRef SymName,
|
|
std::vector<std::string> *AllSyms, const SymbolEnumeratorOptions &Opts) {
|
|
bool Found = false;
|
|
|
|
SymbolEnumerator::enumerateSymbols(
|
|
Lib.getFullPath(),
|
|
[&](StringRef Sym) {
|
|
if (AllSyms)
|
|
AllSyms->emplace_back(Sym.str());
|
|
|
|
if (Sym == SymName) {
|
|
Found = true;
|
|
}
|
|
|
|
return EnumerateResult::Continue;
|
|
},
|
|
Opts);
|
|
|
|
return Found;
|
|
}
|
|
|
|
} // end namespace llvm::orc
|