[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