[llvm] [orc-rt] Treat empty path as "process symbols" in NativeDylibManager. (PR #202905)
Lang Hames via llvm-commits
llvm-commits at lists.llvm.org
Wed Jun 10 02:41:53 PDT 2026
https://github.com/lhames created https://github.com/llvm/llvm-project/pull/202905
NativeDylibManager::load now handles an empty path by returning the process's global lookup handle (RTLD_DEFAULT on POSIX) directly, bypassing dlopen and the shutdown-time dlclose registration. This matches the behavior of OrcTargetProcess's SimpleExecutorDylibManager.
>From 3c444bb284db58a9a390c3d8e7d41d02fed3de3d Mon Sep 17 00:00:00 2001
From: Lang Hames <lhames at gmail.com>
Date: Wed, 10 Jun 2026 17:17:05 +1000
Subject: [PATCH] [orc-rt] Treat empty path as "process symbols" in
NativeDylibManager.
NativeDylibManager::load now handles an empty path by returning the process's
global lookup handle (RTLD_DEFAULT on POSIX) directly, bypassing dlopen and the
shutdown-time dlclose registration. This matches the behavior of
OrcTargetProcess's SimpleExecutorDylibManager.
---
orc-rt/include/orc-rt/NativeDylibManager.h | 3 +++
orc-rt/lib/executor/NativeDylibManager.cpp | 5 +++++
orc-rt/lib/executor/Unix/NativeDylibAPIs.inc | 10 ++++------
.../unittests/NativeDylibManagerSPSCITest.cpp | 17 ++++++++++++++++
orc-rt/unittests/NativeDylibManagerTest.cpp | 20 +++++++++++++++++++
5 files changed, 49 insertions(+), 6 deletions(-)
diff --git a/orc-rt/include/orc-rt/NativeDylibManager.h b/orc-rt/include/orc-rt/NativeDylibManager.h
index 6234e34360a0c..5971318f19949 100644
--- a/orc-rt/include/orc-rt/NativeDylibManager.h
+++ b/orc-rt/include/orc-rt/NativeDylibManager.h
@@ -56,6 +56,9 @@ class NativeDylibManager : public Service {
///
/// Returns an Expected handle. On success, registers a Session shutdown
/// callback to unload the library.
+ ///
+ /// As a special case, an empty Path returns the process's global lookup
+ /// handle.
using OnLoadCompleteFn = move_only_function<void(Expected<void *>)>;
void load(OnLoadCompleteFn &&OnComplete, std::string Path);
diff --git a/orc-rt/lib/executor/NativeDylibManager.cpp b/orc-rt/lib/executor/NativeDylibManager.cpp
index 51816407d229c..b4e9bf30f6949 100644
--- a/orc-rt/lib/executor/NativeDylibManager.cpp
+++ b/orc-rt/lib/executor/NativeDylibManager.cpp
@@ -45,6 +45,11 @@ NativeDylibManager::Create(Session &S, SimpleSymbolTable &ST,
}
void NativeDylibManager::load(OnLoadCompleteFn &&OnComplete, std::string Path) {
+ // Empty path -> global handle; no shutdown callback (RTLD_DEFAULT
+ // mustn't be dlclose'd).
+ if (Path.empty())
+ return OnComplete(hostOSGetGlobalLookupHandle());
+
auto H = hostOSLoadLibrary(Path);
if (!H)
return OnComplete(H.takeError());
diff --git a/orc-rt/lib/executor/Unix/NativeDylibAPIs.inc b/orc-rt/lib/executor/Unix/NativeDylibAPIs.inc
index 2c2cbec912e2e..39929ba79bf82 100644
--- a/orc-rt/lib/executor/Unix/NativeDylibAPIs.inc
+++ b/orc-rt/lib/executor/Unix/NativeDylibAPIs.inc
@@ -16,16 +16,14 @@
namespace {
+inline void *hostOSGetGlobalLookupHandle() { return RTLD_DEFAULT; }
+
orc_rt::Expected<void *> hostOSLoadLibrary(const std::string &Path) {
+ assert(!Path.empty() && "hostOSLoadLibray doesn't support empty paths");
void *H = dlopen(Path.c_str(), RTLD_LAZY | RTLD_LOCAL);
if (H == nullptr) {
std::ostringstream ErrMsg;
- ErrMsg << "error loading ";
- if (!Path.empty())
- ErrMsg << "\"" << Path << "\"";
- else
- ErrMsg << "process symbols";
- ErrMsg << ": " << dlerror();
+ ErrMsg << "error loading \"" << Path << "\": " << dlerror();
return orc_rt::make_error<orc_rt::StringError>(ErrMsg.str());
}
diff --git a/orc-rt/unittests/NativeDylibManagerSPSCITest.cpp b/orc-rt/unittests/NativeDylibManagerSPSCITest.cpp
index 4b1587a0d2cc3..4ce65f8daaae2 100644
--- a/orc-rt/unittests/NativeDylibManagerSPSCITest.cpp
+++ b/orc-rt/unittests/NativeDylibManagerSPSCITest.cpp
@@ -121,6 +121,23 @@ TEST_F(NativeDylibManagerSPSCITest, LoadNonExistent) {
consumeError(Handle.takeError());
}
+TEST_F(NativeDylibManagerSPSCITest, LoadEmptyPathReturnsGlobalHandle) {
+ // The global handle's value is implementation-defined, so verify by looking
+ // up through it.
+ std::future<Expected<Expected<void *>>> LoadResult;
+ spsLoad(waitFor(LoadResult), "");
+ void *Handle = cantFail(cantFail(LoadResult.get()));
+
+ std::future<Expected<Expected<std::vector<std::optional<void *>>>>>
+ LookupResult;
+ spsLookup(waitFor(LookupResult), Handle, {{"malloc", Req}});
+ auto Addrs = cantFail(cantFail(LookupResult.get()));
+ ASSERT_EQ(Addrs.size(), 1U);
+ ASSERT_TRUE(Addrs[0].has_value())
+ << "malloc should be findable via the process's global lookup handle";
+ EXPECT_NE(*Addrs[0], nullptr);
+}
+
TEST_F(NativeDylibManagerSPSCITest, LookupSingleSymbol) {
std::future<Expected<Expected<void *>>> LoadResult;
spsLoad(waitFor(LoadResult), NDM_TEST_LIB_PATH);
diff --git a/orc-rt/unittests/NativeDylibManagerTest.cpp b/orc-rt/unittests/NativeDylibManagerTest.cpp
index e27867d37abb1..a846c56fb42d4 100644
--- a/orc-rt/unittests/NativeDylibManagerTest.cpp
+++ b/orc-rt/unittests/NativeDylibManagerTest.cpp
@@ -82,6 +82,26 @@ TEST(NativeDylibManagerTest, LoadNonExistent) {
consumeError(LoadResult.takeError());
}
+TEST(NativeDylibManagerTest, LoadEmptyPathReturnsGlobalHandle) {
+ Session S(mockExecutorProcessInfo(), std::make_unique<NoDispatcher>(),
+ noErrors);
+ SimpleSymbolTable ST;
+ auto NDM = cantFail(NativeDylibManager::Create(S, ST));
+
+ // The global handle's value is implementation-defined, so verify by looking
+ // up through it.
+ auto LoadResult = syncLoad(*NDM, "");
+ ASSERT_TRUE(!!LoadResult) << toString(LoadResult.takeError());
+ void *Handle = *LoadResult;
+
+ auto Result = syncLookup(*NDM, Handle, {{"malloc", Req}});
+ ASSERT_TRUE(!!Result) << toString(Result.takeError());
+ ASSERT_EQ(Result->size(), 1U);
+ ASSERT_TRUE((*Result)[0].has_value())
+ << "malloc should be findable via the process's global lookup handle";
+ EXPECT_NE(*(*Result)[0], nullptr);
+}
+
TEST(NativeDylibManagerTest, LookupSingleSymbol) {
Session S(mockExecutorProcessInfo(), std::make_unique<NoDispatcher>(),
noErrors);
More information about the llvm-commits
mailing list