Skip to content

Commit d698b4e

Browse files
alexander-penevmcbarton
authored andcommitted
Library autoloader
1 parent c0a4dfe commit d698b4e

File tree

9 files changed

+303
-17
lines changed

9 files changed

+303
-17
lines changed

include/clang/Interpreter/CppInterOp.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -717,6 +717,14 @@ namespace Cpp {
717717
unsigned complete_line = 1U,
718718
unsigned complete_column = 1U);
719719

720+
/// Set libraries autoload.
721+
///\param[in] autoload - true = libraries autoload is on.
722+
CPPINTEROP_API void SetLibrariesAutoload(bool autoload = true);
723+
724+
/// Get libraries autoload.
725+
///\returns LibraryAutoLoad state (true = libraries autoload is on).
726+
CPPINTEROP_API bool GetLibrariesAutoload();
727+
720728
} // end namespace Cpp
721729

722730
#endif // CPPINTEROP_CPPINTEROP_H

lib/Interpreter/CppInterOp.cpp

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include "clang/Sema/TemplateDeduction.h"
3131

3232
#include "llvm/ADT/StringRef.h"
33+
#include "llvm/ExecutionEngine/Orc/Core.h"
3334
#include "llvm/Support/Casting.h"
3435
#include "llvm/Support/Debug.h"
3536
#include "llvm/Support/raw_os_ostream.h"
@@ -3377,4 +3378,175 @@ namespace Cpp {
33773378
complete_column);
33783379
}
33793380

3381+
#define DEBUG_TYPE "autoload"
3382+
3383+
static inline std::string DemangleNameForDlsym(const std::string& name) {
3384+
std::string nameForDlsym = name;
3385+
3386+
#if defined(R__MACOSX) || defined(R__WIN32)
3387+
// The JIT gives us a mangled name which has an additional leading underscore
3388+
// on macOS and Windows, for instance __ZN8TRandom34RndmEv. However, dlsym
3389+
// requires us to remove it.
3390+
// FIXME: get this information from the DataLayout via getGlobalPrefix()!
3391+
if (nameForDlsym[0] == '_')
3392+
nameForDlsym.erase(0, 1);
3393+
#endif //R__MACOSX
3394+
3395+
return nameForDlsym;
3396+
}
3397+
3398+
class AutoLoadLibrarySearchGenerator;
3399+
static AutoLoadLibrarySearchGenerator *ALLSG = nullptr;
3400+
3401+
class AutoLoadLibrarySearchGenerator : public llvm::orc::DefinitionGenerator {
3402+
public:
3403+
bool Enabled = false;
3404+
3405+
// Lazy materialization unit class helper
3406+
class AutoloadLibraryMU : public llvm::orc::MaterializationUnit {
3407+
std::string lib;
3408+
llvm::orc::SymbolNameVector syms;
3409+
public:
3410+
AutoloadLibraryMU(const std::string &Library, const llvm::orc::SymbolNameVector &Symbols)
3411+
: MaterializationUnit({getSymbolFlagsMap(Symbols), nullptr}), lib(Library), syms(Symbols) {}
3412+
3413+
StringRef getName() const override {
3414+
return "<Symbols from Autoloaded Library>";
3415+
}
3416+
3417+
void materialize(std::unique_ptr<llvm::orc::MaterializationResponsibility> R) override {
3418+
if (!ALLSG || !ALLSG->Enabled) {
3419+
R->failMaterialization();
3420+
return;
3421+
}
3422+
3423+
LLVM_DEBUG(dbgs() << "Materialize " << lib << " syms=" << syms);
3424+
3425+
auto& I = getInterp();
3426+
auto DLM = I.getDynamicLibraryManager();
3427+
3428+
llvm::orc::SymbolMap loadedSymbols;
3429+
llvm::orc::SymbolNameSet failedSymbols;
3430+
bool loadedLibrary = false;
3431+
3432+
for (auto symbol : syms) {
3433+
std::string symbolStr = (*symbol).str();
3434+
std::string nameForDlsym = DemangleNameForDlsym(symbolStr);
3435+
3436+
// Check if the symbol is available without loading the library.
3437+
void *addr = llvm::sys::DynamicLibrary::SearchForAddressOfSymbol(nameForDlsym);
3438+
3439+
if (!addr && !loadedLibrary) {
3440+
// Try to load the library which should provide the symbol definition.
3441+
if (DLM->loadLibrary(lib, false) != DynamicLibraryManager::LoadLibResult::kLoadLibSuccess) {
3442+
LLVM_DEBUG(dbgs() << "MU: Failed to load library " << lib);
3443+
string err = "MU: Failed to load library! " + lib;
3444+
perror(err.c_str());
3445+
} else {
3446+
LLVM_DEBUG(dbgs() << "MU: Autoload library " << lib);
3447+
}
3448+
3449+
// Only try loading the library once.
3450+
loadedLibrary = true;
3451+
3452+
addr = llvm::sys::DynamicLibrary::SearchForAddressOfSymbol(nameForDlsym);
3453+
}
3454+
3455+
if (addr) {
3456+
loadedSymbols[symbol] =
3457+
llvm::orc::ExecutorSymbolDef(llvm::orc::ExecutorAddr::fromPtr(addr), JITSymbolFlags::Exported);
3458+
} else {
3459+
// Collect all failing symbols, delegate their responsibility and then
3460+
// fail their materialization. R->defineNonExistent() sounds like it
3461+
// should do that, but it's not implemented?!
3462+
failedSymbols.insert(symbol);
3463+
}
3464+
}
3465+
3466+
if (!failedSymbols.empty()) {
3467+
auto failingMR = R->delegate(failedSymbols);
3468+
if (failingMR) {
3469+
(*failingMR)->failMaterialization();
3470+
}
3471+
}
3472+
3473+
if (!loadedSymbols.empty()) {
3474+
llvm::cantFail(R->notifyResolved(loadedSymbols));
3475+
llvm::cantFail(R->notifyEmitted());
3476+
}
3477+
}
3478+
3479+
void discard(const llvm::orc::JITDylib &JD, const llvm::orc::SymbolStringPtr &Name) override {}
3480+
3481+
private:
3482+
static llvm::orc::SymbolFlagsMap getSymbolFlagsMap(const llvm::orc::SymbolNameVector &Symbols) {
3483+
llvm::orc::SymbolFlagsMap map;
3484+
for (auto symbolName : Symbols)
3485+
map[symbolName] = llvm::JITSymbolFlags::Exported;
3486+
return map;
3487+
}
3488+
};
3489+
3490+
llvm::Error tryToGenerate(llvm::orc::LookupState &LS, llvm::orc::LookupKind K, llvm::orc::JITDylib &JD,
3491+
llvm::orc::JITDylibLookupFlags JDLookupFlags, const llvm::orc::SymbolLookupSet &Symbols) override {
3492+
if (Enabled) {
3493+
LLVM_DEBUG(dbgs() << "tryToGenerate");
3494+
3495+
auto& I = getInterp();
3496+
auto DLM = I.getDynamicLibraryManager();
3497+
3498+
std::unordered_map<std::string, llvm::orc::SymbolNameVector> found;
3499+
llvm::orc::SymbolMap NewSymbols;
3500+
for (auto &KV : Symbols) {
3501+
auto &Name = KV.first;
3502+
if ((*Name).empty())
3503+
continue;
3504+
3505+
auto lib = DLM->searchLibrariesForSymbol(*Name, /*searchSystem=*/true); // false?
3506+
if (lib.empty())
3507+
continue;
3508+
3509+
found[lib].push_back(Name);
3510+
3511+
// Workaround: This getAddressOfGlobal call make first symbol search
3512+
// to work, immediatelly after library auto load. This approach do not
3513+
// use MU
3514+
//DLM->loadLibrary(lib, true);
3515+
//I.getAddressOfGlobal(*Name);
3516+
}
3517+
3518+
for (auto &&KV : found) {
3519+
auto MU = std::make_unique<AutoloadLibraryMU>(KV.first, std::move(KV.second));
3520+
if (auto Err = JD.define(MU))
3521+
return Err;
3522+
}
3523+
}
3524+
3525+
return llvm::Error::success();
3526+
}
3527+
};
3528+
3529+
void SetLibrariesAutoload(bool autoload /* = true */) {
3530+
auto& I = getInterp();
3531+
llvm::orc::LLJIT& EE = *compat::getExecutionEngine(I);
3532+
#if CLANG_VERSION_MAJOR < 17
3533+
llvm::orc::JITDylib& DyLib = EE.getMainJITDylib();
3534+
#else
3535+
llvm::orc::JITDylib& DyLib = *EE.getProcessSymbolsJITDylib().get();
3536+
#endif // CLANG_VERSION_MAJOR
3537+
3538+
if (!ALLSG)
3539+
ALLSG = &DyLib.addGenerator(std::make_unique<AutoLoadLibrarySearchGenerator>());
3540+
ALLSG->Enabled = autoload;
3541+
3542+
LLVM_DEBUG(dbgs() << "Autoload=" << (ALLSG && ALLSG->Enabled ? "ON" : "OFF"));
3543+
}
3544+
3545+
bool GetLibrariesAutoload() {
3546+
LLVM_DEBUG(dbgs() << "Autoload is " << (ALLSG && ALLSG->Enabled ? "ON" : "OFF"));
3547+
return ALLSG && ALLSG->Enabled;
3548+
}
3549+
3550+
#undef DEBUG_TYPE
3551+
33803552
} // end namespace Cpp

lib/Interpreter/DynamicLibraryManager.cpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ namespace Cpp {
243243
// get canonical path name and check if already loaded
244244
const std::string Path = platform::NormalizePath(foundDyLib);
245245
if (Path.empty()) {
246-
LLVM_DEBUG(dbgs() << "cling::DynamicLibraryManager::lookupLibMaybeAddExt(): "
246+
LLVM_DEBUG(dbgs() << "DynamicLibraryManager::lookupLibMaybeAddExt(): "
247247
<< "error getting real (canonical) path of library " << foundDyLib << '\n');
248248
return foundDyLib;
249249
}
@@ -392,8 +392,7 @@ namespace Cpp {
392392
return;
393393

394394
DyLibHandle dyLibHandle = nullptr;
395-
for (DyLibs::const_iterator I = m_DyLibs.begin(), E = m_DyLibs.end();
396-
I != E; ++I) {
395+
for (DyLibs::const_iterator I = m_DyLibs.begin(), E = m_DyLibs.end(); I != E; ++I) {
397396
if (I->second == canonicalLoadedLib) {
398397
dyLibHandle = I->first;
399398
break;
@@ -405,7 +404,7 @@ namespace Cpp {
405404
std::string errMsg;
406405
platform::DLClose(dyLibHandle, &errMsg);
407406
if (!errMsg.empty()) {
408-
LLVM_DEBUG(dbgs() << "cling::DynamicLibraryManager::unloadLibrary(): "
407+
LLVM_DEBUG(dbgs() << "DynamicLibraryManager::unloadLibrary(): "
409408
<< errMsg << '\n');
410409
}
411410

lib/Interpreter/DynamicLibraryManagerSymbol.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1153,8 +1153,8 @@ namespace Cpp {
11531153
std::string Dyld::searchLibrariesForSymbol(StringRef mangledName,
11541154
bool searchSystem/* = true*/) {
11551155
#define DEBUG_TYPE "Dyld:searchLibrariesForSymbol:"
1156-
assert(!llvm::sys::DynamicLibrary::SearchForAddressOfSymbol(mangledName.str()) &&
1157-
"Library already loaded, please use dlsym!");
1156+
// assert(!llvm::sys::DynamicLibrary::SearchForAddressOfSymbol(mangledName.str()) &&
1157+
// "Library already loaded, please use dlsym!");
11581158
assert(!mangledName.empty());
11591159

11601160
using namespace llvm::sys::path;

unittests/CppInterOp/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,7 @@ set_output_directory(DynamicLibraryManagerTests BINARY_DIR ${CMAKE_BINARY_DIR}/u
4242
add_dependencies(DynamicLibraryManagerTests TestSharedLib)
4343
#export_executable_symbols_for_plugins(TestSharedLib)
4444
add_subdirectory(TestSharedLib)
45+
# TODO: Remove when libraryunload work correctly
46+
add_dependencies(DynamicLibraryManagerTests TestSharedLib1)
47+
#export_executable_symbols_for_plugins(TestSharedLib1)
48+
add_subdirectory(TestSharedLib1)

unittests/CppInterOp/DynamicLibraryManagerTest.cpp

Lines changed: 84 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
#include "llvm/Support/FileSystem.h"
66
#include "llvm/Support/Path.h"
77

8+
9+
// Helper functions
10+
811
// This function isn't referenced outside its translation unit, but it
912
// can't use the "static" keyword because its address is used for
1013
// GetMainExecutable (since some platforms don't support taking the
@@ -17,9 +20,20 @@ std::string GetExecutablePath(const char* Argv0) {
1720
return llvm::sys::fs::getMainExecutable(Argv0, MainAddr);
1821
}
1922

23+
static inline std::string MangleNameForDlsym(const std::string& name) {
24+
std::string nameForDlsym = name;
25+
#if defined(R__MACOSX) || defined(R__WIN32)
26+
if (nameForDlsym[0] != '_')
27+
nameForDlsym.insert (0, 1, '_');
28+
#endif //R__MACOSX
29+
return nameForDlsym;
30+
}
31+
32+
// Tests
33+
2034
TEST(DynamicLibraryManagerTest, Sanity) {
2135
EXPECT_TRUE(Cpp::CreateInterpreter());
22-
EXPECT_FALSE(Cpp::GetFunctionAddress("ret_zero"));
36+
EXPECT_FALSE(Cpp::GetFunctionAddress(MangleNameForDlsym("ret_zero").c_str()));
2337

2438
std::string BinaryPath = GetExecutablePath(/*Argv0=*/nullptr);
2539
llvm::StringRef Dir = llvm::sys::path::parent_path(BinaryPath);
@@ -28,13 +42,9 @@ TEST(DynamicLibraryManagerTest, Sanity) {
2842
// FIXME: dlsym on mach-o takes the C-level name, however, the macho-o format
2943
// adds an additional underscore (_) prefix to the lowered names. Figure out
3044
// how to harmonize that API.
31-
#ifdef __APPLE__
32-
std::string PathToTestSharedLib =
33-
Cpp::SearchLibrariesForSymbol("_ret_zero", /*system_search=*/false);
34-
#else
45+
3546
std::string PathToTestSharedLib =
36-
Cpp::SearchLibrariesForSymbol("ret_zero", /*system_search=*/false);
37-
#endif // __APPLE__
47+
Cpp::SearchLibrariesForSymbol(MangleNameForDlsym("ret_zero").c_str(), /*system_search=*/false);
3848

3949
EXPECT_STRNE("", PathToTestSharedLib.c_str())
4050
<< "Cannot find: '" << PathToTestSharedLib << "' in '" << Dir.str()
@@ -44,13 +54,76 @@ TEST(DynamicLibraryManagerTest, Sanity) {
4454
// Force ExecutionEngine to be created.
4555
Cpp::Process("");
4656
// FIXME: Conda returns false to run this code on osx.
47-
#ifndef __APPLE__
48-
EXPECT_TRUE(Cpp::GetFunctionAddress("ret_zero"));
49-
#endif //__APPLE__
57+
EXPECT_TRUE(Cpp::GetFunctionAddress(MangleNameForDlsym("ret_zero").c_str()));
5058

5159
Cpp::UnloadLibrary("TestSharedLib");
5260
// We have no reliable way to check if it was unloaded because posix does not
5361
// require the library to be actually unloaded but just the handle to be
5462
// invalidated...
55-
// EXPECT_FALSE(Cpp::GetFunctionAddress("ret_zero"));
63+
// EXPECT_FALSE(Cpp::GetFunctionAddress(MangleNameForDlsym("ret_zero").c_str()));
64+
}
65+
66+
TEST(DynamicLibraryManagerTest, LibrariesAutoload) {
67+
EXPECT_TRUE(Cpp::CreateInterpreter());
68+
69+
// Autoload by default is OFF. Symbol search must fail.
70+
EXPECT_FALSE(Cpp::GetFunctionAddress(MangleNameForDlsym("ret_one").c_str()));
71+
72+
// Libraries Search path by default is set to main executable directory.
73+
std::string BinaryPath = GetExecutablePath(/*Argv0=*/nullptr);
74+
llvm::StringRef Dir = llvm::sys::path::parent_path(BinaryPath);
75+
Cpp::AddSearchPath(Dir.str().c_str());
76+
77+
// Find library with "rec_one" symbol defined and exported
78+
//
79+
// FIXME: dlsym on mach-o takes the C-level name, however, the macho-o format
80+
// adds an additional underscore (_) prefix to the lowered names. Figure out
81+
// how to harmonize that API. For now we use out minimal implementation of
82+
// helper function.
83+
std::string PathToTestSharedLib1 =
84+
Cpp::SearchLibrariesForSymbol(MangleNameForDlsym("ret_one").c_str(), /*system_search=*/false);
85+
86+
// If result is "" then we cannot find this library.
87+
EXPECT_STRNE("", PathToTestSharedLib1.c_str())
88+
<< "Cannot find: '" << PathToTestSharedLib1 << "' in '" << Dir.str() << "'";
89+
90+
// Force ExecutionEngine to be created.
91+
Cpp::Process("");
92+
93+
// Check default Autoload is OFF
94+
EXPECT_FALSE(Cpp::GetLibrariesAutoload());
95+
// Find symbol must fail (when auotload=off)
96+
EXPECT_FALSE(Cpp::GetFunctionAddress(MangleNameForDlsym("ret_one").c_str()));
97+
98+
// Autoload turn ON
99+
Cpp::SetLibrariesAutoload(true);
100+
// Check autorum status (must be turned ON)
101+
EXPECT_TRUE(Cpp::GetLibrariesAutoload());
102+
103+
// FIXME: Conda returns false to run this code on osx.
104+
EXPECT_TRUE(Cpp::GetFunctionAddress(MangleNameForDlsym("ret_one").c_str()));
105+
// Check for some symbols (exists and not exists)
106+
EXPECT_FALSE(Cpp::GetFunctionAddress(MangleNameForDlsym("ret_not_exist").c_str()));
107+
EXPECT_FALSE(Cpp::GetFunctionAddress(MangleNameForDlsym("ret_not_exist1").c_str()));
108+
EXPECT_TRUE(Cpp::GetFunctionAddress(MangleNameForDlsym("ret_two").c_str()));
109+
110+
//Cpp::UnloadLibrary("TestSharedLib1");
111+
//// We have no reliable way to check if it was unloaded because posix does not
112+
//// require the library to be actually unloaded but just the handle to be
113+
//// invalidated...
114+
//EXPECT_FALSE(Cpp::GetFunctionAddress(MangleNameForDlsym("ret_one").c_str()));
115+
116+
// Autoload turn OFF
117+
Cpp::SetLibrariesAutoload(false);
118+
// Check autorum status (must be turned OFF)
119+
EXPECT_FALSE(Cpp::GetLibrariesAutoload());
120+
//EXPECT_FALSE(Cpp::GetFunctionAddress(MangleNameForDlsym("ret_one").c_str())); // if unload works
121+
//EXPECT_TRUE(Cpp::GetFunctionAddress(MangleNameForDlsym("ret_one").c_str()));
122+
123+
// Autoload turn ON again
124+
Cpp::SetLibrariesAutoload(true);
125+
// Check autorum status (must be turned ON)
126+
EXPECT_TRUE(Cpp::GetLibrariesAutoload());
127+
//EXPECT_FALSE(Cpp::GetFunctionAddress(MangleNameForDlsym("ret_one").c_str())); // if unload works
128+
//EXPECT_TRUE(Cpp::GetFunctionAddress(MangleNameForDlsym("ret_one").c_str()));
56129
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# TODO: Remove when library unload work correctly
2+
add_llvm_library(TestSharedLib1
3+
SHARED
4+
DISABLE_LLVM_LINK_LLVM_DYLIB
5+
BUILDTREE_ONLY
6+
TestSharedLib1.cpp)
7+
# Put TestSharedLib1 next to the unit test executable.
8+
set_output_directory(TestSharedLib1
9+
BINARY_DIR ${CMAKE_BINARY_DIR}/unittests/bin/$<CONFIG>/
10+
LIBRARY_DIR ${CMAKE_BINARY_DIR}/unittests/bin/$<CONFIG>/
11+
)
12+
set_target_properties(TestSharedLib1 PROPERTIES FOLDER "Tests")
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#include "TestSharedLib1.h"
2+
3+
int ret_one() { return 1; }
4+
5+
int ret_two() { return 2; }

0 commit comments

Comments
 (0)