[llvm] [ORC] Hoist Caller state into the base; add operator bool (PR #214483)

Lang Hames via llvm-commits llvm-commits at lists.llvm.org
Thu Aug 6 05:59:23 PDT 2026


https://github.com/lhames created https://github.com/llvm/llvm-project/pull/214483

Move the ExecutionSession reference and callee address up from the SPS implementation into the rt::Caller base, together with their constructor and new executionSession() / calleeAddr() accessors. The named callers (MainCaller, VoidVoidCaller, ...) become plain aliases of Caller rather than subclasses, and rt::sps::Caller inherits the base constructor.

Add an explicit operator bool() reporting whether the caller has a non-null callee address.

Give rt::sps::Caller::Create a SymbolLookupFlags parameter. Looking the callee up as a weakly-referenced symbol now yields a caller with a null callee (operator bool == false) when the symbol is absent, rather than an error -- so callers for optional runtime functions can be constructed and then tested for availability.

Adds SPSCallersTest coverage for operator bool and the accessors, and for the required/weak x present/absent Create paths.

>From 2f62c1942833341001676652decc103bbcf122c3 Mon Sep 17 00:00:00 2001
From: Lang Hames <lhames at gmail.com>
Date: Thu, 6 Aug 2026 14:29:31 +1000
Subject: [PATCH] [ORC] Hoist Caller state into the base; add operator bool

Move the ExecutionSession reference and callee address up from the SPS
implementation into the rt::Caller base, together with their constructor
and new executionSession() / calleeAddr() accessors. The named callers
(MainCaller, VoidVoidCaller, ...) become plain aliases of Caller rather
than subclasses, and rt::sps::Caller inherits the base constructor.

Add an explicit operator bool() reporting whether the caller has a
non-null callee address.

Give rt::sps::Caller::Create a SymbolLookupFlags parameter. Looking the
callee up as a weakly-referenced symbol now yields a caller with a null
callee (operator bool == false) when the symbol is absent, rather than
an error -- so callers for optional runtime functions can be constructed
and then tested for availability.

Adds SPSCallersTest coverage for operator bool and the accessors, and
for the required/weak x present/absent Create paths.
---
 .../llvm/ExecutionEngine/Orc/RTBridge/Calls.h | 34 +++++++--
 .../ExecutionEngine/Orc/RTBridge/SPS/Calls.h  | 40 ++++++-----
 .../ExecutionEngine/Orc/SPSCallersTest.cpp    | 72 +++++++++++++++++++
 3 files changed, 120 insertions(+), 26 deletions(-)

diff --git a/llvm/include/llvm/ExecutionEngine/Orc/RTBridge/Calls.h b/llvm/include/llvm/ExecutionEngine/Orc/RTBridge/Calls.h
index b49e6457754bf..1a273b0f3c0fb 100644
--- a/llvm/include/llvm/ExecutionEngine/Orc/RTBridge/Calls.h
+++ b/llvm/include/llvm/ExecutionEngine/Orc/RTBridge/Calls.h
@@ -28,7 +28,11 @@
 #include <string>
 #include <type_traits>
 
-namespace llvm::orc::rt {
+namespace llvm::orc {
+
+class ExecutionSession;
+
+namespace rt {
 
 template <typename FnT> class Caller;
 
@@ -54,8 +58,20 @@ template <typename RetT, typename... ArgTs> class Caller<RetT(ArgTs...)> {
   using ErrorRetT =
       std::conditional_t<std::is_void_v<RetT>, Error, Expected<RetT>>;
 
+  Caller(ExecutionSession &ES, ExecutorAddr CalleeAddr)
+      : ES(ES), CalleeAddr(CalleeAddr) {}
+
   virtual ~Caller() = default;
 
+  /// Returns the ExecutionSession on which this call will be made.
+  ExecutionSession &executionSession() const { return ES; }
+
+  /// Returns the address of the callee in the executor.
+  const ExecutorAddr &calleeAddr() const { return CalleeAddr; }
+
+  /// Evaluates to true if the callee is non-null.
+  explicit operator bool() const { return !!CalleeAddr; }
+
   /// Asynchronously invoke the operation with the given Args, delivering its
   /// result (or an error) to OnComplete.
   virtual void operator()(unique_function<void(ErrorRetT)> OnComplete,
@@ -73,6 +89,10 @@ template <typename RetT, typename... ArgTs> class Caller<RetT(ArgTs...)> {
         std::move(Args)...);
     return F.get();
   }
+
+private:
+  ExecutionSession &ES;
+  ExecutorAddr CalleeAddr;
 };
 
 /// Runtime-agnostic interface for running a main-like function
@@ -80,15 +100,14 @@ template <typename RetT, typename... ArgTs> class Caller<RetT(ArgTs...)> {
 ///
 /// The function to run is given by its ExecutorAddr, its arguments as an
 /// argument vector, and its int64_t result is returned.
-class MainCaller : public Caller<int64_t(ExecutorAddr, ArrayRef<std::string>)> {
-};
+using MainCaller = Caller<int64_t(ExecutorAddr, ArrayRef<std::string>)>;
 
 /// Runtime-agnostic interface for running a void() function in the executor.
 ///
 /// The function to run is given by its ExecutorAddr.
 ///
 /// WARNING: This Caller is experimental and may be removed.
-class VoidVoidCaller : public Caller<void(ExecutorAddr)> {};
+using VoidVoidCaller = Caller<void(ExecutorAddr)>;
 
 /// Runtime-agnostic interface for running an int32_t() function in the
 /// executor.
@@ -96,7 +115,7 @@ class VoidVoidCaller : public Caller<void(ExecutorAddr)> {};
 /// The function to run is given by its ExecutorAddr.
 ///
 /// WARNING: This Caller is experimental and may be removed.
-class Int32VoidCaller : public Caller<int32_t(ExecutorAddr)> {};
+using Int32VoidCaller = Caller<int32_t(ExecutorAddr)>;
 
 /// Runtime-agnostic interface for running an int32_t(int32_t) function in the
 /// executor.
@@ -104,8 +123,9 @@ class Int32VoidCaller : public Caller<int32_t(ExecutorAddr)> {};
 /// The function to run is given by its ExecutorAddr.
 ///
 /// WARNING: This Caller is experimental and may be removed.
-class Int32Int32Caller : public Caller<int32_t(ExecutorAddr, int32_t)> {};
+using Int32Int32Caller = Caller<int32_t(ExecutorAddr, int32_t)>;
 
-} // namespace llvm::orc::rt
+} // namespace rt
+} // namespace llvm::orc
 
 #endif // LLVM_EXECUTIONENGINE_ORC_RTBRIDGE_CALLS_H
diff --git a/llvm/include/llvm/ExecutionEngine/Orc/RTBridge/SPS/Calls.h b/llvm/include/llvm/ExecutionEngine/Orc/RTBridge/SPS/Calls.h
index 4102327f5d394..8269cc34a37e2 100644
--- a/llvm/include/llvm/ExecutionEngine/Orc/RTBridge/SPS/Calls.h
+++ b/llvm/include/llvm/ExecutionEngine/Orc/RTBridge/SPS/Calls.h
@@ -51,38 +51,45 @@ class Caller<BaseT, SPSSigT, CINameV, RetT(ArgTs...)> : public BaseT {
   /// Name of the controller-interface wrapper this caller targets.
   static constexpr const char *CIName = CINameV;
 
-  Caller(ExecutionSession &ES, ExecutorAddr CallerFnAddr)
-      : ES(ES), CallerFnAddr(CallerFnAddr) {}
+  using BaseT::BaseT;
+  using BaseT::operator();
 
   /// Look the wrapper up in the executor's bootstrap JITDylib and build a
   /// caller for it.
-  static Expected<Caller> Create(ExecutionSession &ES,
-                                 const char *Name = CIName) {
-    if (auto CallerSym = ES.lookup({&ES.getBootstrapJITDylib()}, Name))
-      return Caller(ES, CallerSym->getAddress());
-    else
-      return CallerSym.takeError();
+  static Expected<Caller>
+  Create(ExecutionSession &ES,
+         SymbolLookupFlags SLF = SymbolLookupFlags::RequiredSymbol,
+         const char *Name = CIName) {
+    if (auto CalleeSyms =
+            ES.lookup(makeJITDylibSearchOrder(&ES.getBootstrapJITDylib()),
+                      SymbolLookupSet{ES.intern(Name), SLF})) {
+      if (!CalleeSyms->empty())
+        return Caller(ES, CalleeSyms->begin()->second.getAddress());
+      assert(SLF == SymbolLookupFlags::WeaklyReferencedSymbol);
+      return Caller(ES, ExecutorAddr());
+    } else
+      return CalleeSyms.takeError();
   }
 
-  /// Asynchronously call the SPS wrapper at CallerFnAddr with the given Args,
+  /// Asynchronously call the SPS wrapper at CalleeAddr with the given Args,
   /// delivering the result (or an error) to OnComplete. Serialization failures
   /// are reported through OnComplete's error channel.
   static void callAsync(unique_function<void(ErrorRetT)> OnComplete,
-                        ExecutionSession &ES, ExecutorAddr CallerFnAddr,
+                        ExecutionSession &ES, ExecutorAddr CalleeAddr,
                         const ArgTs &...Args) {
     using namespace llvm::orc::shared;
     if constexpr (std::is_void_v<CalleeRetT>) {
       // Void result: the executor-side function produces no value, so the only
       // thing to report is the dispatch error (success if the call ran).
       ES.callSPSWrapperAsync<SPSSigT>(
-          CallerFnAddr,
+          CalleeAddr,
           [OnComplete = std::move(OnComplete)](Error SerErr) mutable {
             OnComplete(std::move(SerErr));
           },
           Args...);
     } else {
       ES.callSPSWrapperAsync<SPSSigT>(
-          CallerFnAddr,
+          CalleeAddr,
           [OnComplete = std::move(OnComplete)](Error SerErr,
                                                CalleeRetT Result) mutable {
             if (SerErr)
@@ -96,14 +103,9 @@ class Caller<BaseT, SPSSigT, CINameV, RetT(ArgTs...)> : public BaseT {
 
   void operator()(unique_function<void(ErrorRetT)> OnComplete,
                   ArgTs... Args) override {
-    callAsync(std::move(OnComplete), ES, CallerFnAddr, Args...);
+    callAsync(std::move(OnComplete), this->executionSession(),
+              this->calleeAddr(), Args...);
   }
-
-  using BaseT::operator();
-
-private:
-  ExecutionSession &ES;
-  ExecutorAddr CallerFnAddr;
 };
 
 using CallMainSPSSig = int64_t(shared::SPSExecutorAddr,
diff --git a/llvm/unittests/ExecutionEngine/Orc/SPSCallersTest.cpp b/llvm/unittests/ExecutionEngine/Orc/SPSCallersTest.cpp
index b2b41857cb976..d9dd59b9ec194 100644
--- a/llvm/unittests/ExecutionEngine/Orc/SPSCallersTest.cpp
+++ b/llvm/unittests/ExecutionEngine/Orc/SPSCallersTest.cpp
@@ -256,3 +256,75 @@ TEST(SPSCallersTest, Int32VoidSync) {
 
   cantFail(ES.endSession());
 }
+
+// operator bool reflects whether the caller has a non-null callee address, and
+// the accessors return the values the caller was constructed with.
+TEST(SPSCallersTest, OperatorBoolAndAccessors) {
+  ExecutionSession ES(cantFail(SelfExecutorProcessControl::Create()));
+
+  ExecutorAddr CalleeAddr = ExecutorAddr::fromPtr(callMainWrapper);
+  MainCaller CallMain(ES, CalleeAddr);
+  EXPECT_TRUE(static_cast<bool>(CallMain));
+  EXPECT_EQ(CallMain.calleeAddr(), CalleeAddr);
+  EXPECT_EQ(&CallMain.executionSession(), &ES);
+
+  // A caller with a null callee address is falsey.
+  MainCaller NullCall(ES, ExecutorAddr());
+  EXPECT_FALSE(static_cast<bool>(NullCall));
+
+  cantFail(ES.endSession());
+}
+
+// A weakly-referenced Create against a missing symbol succeeds, yielding a
+// caller with a null callee (falsey) rather than an error.
+TEST(SPSCallersTest, CreateWeaklyReferencedAbsent) {
+  ExecutionSession ES(cantFail(SelfExecutorProcessControl::Create()));
+
+  // Nothing is defined for MainCaller::CIName in the bootstrap JITDylib.
+  Expected<MainCaller> CallMain =
+      MainCaller::Create(ES, SymbolLookupFlags::WeaklyReferencedSymbol);
+  ASSERT_THAT_EXPECTED(CallMain, Succeeded());
+  EXPECT_FALSE(static_cast<bool>(*CallMain));
+  EXPECT_EQ(CallMain->calleeAddr(), ExecutorAddr());
+
+  cantFail(ES.endSession());
+}
+
+// A weakly-referenced Create against a present symbol resolves it, yielding a
+// usable caller (truthy) bound to the registered address.
+TEST(SPSCallersTest, CreateWeaklyReferencedPresent) {
+  ExecutionSession ES(cantFail(SelfExecutorProcessControl::Create()));
+
+  auto &BootstrapJD = ES.getBootstrapJITDylib();
+  ExecutorAddr CalleeAddr = ExecutorAddr::fromPtr(callMainWrapper);
+  cantFail(BootstrapJD.define(
+      absoluteSymbols({{ES.intern(MainCaller::CIName),
+                        {CalleeAddr, JITSymbolFlags::Exported}}})));
+
+  Expected<MainCaller> CallMain =
+      MainCaller::Create(ES, SymbolLookupFlags::WeaklyReferencedSymbol);
+  ASSERT_THAT_EXPECTED(CallMain, Succeeded());
+  EXPECT_TRUE(static_cast<bool>(*CallMain));
+  EXPECT_EQ(CallMain->calleeAddr(), CalleeAddr);
+
+  // The resolved caller is usable.
+  std::vector<std::string> Args = {"a", "bb"};
+  Expected<int64_t> R = (*CallMain)(ExecutorAddr::fromPtr(testMain), Args);
+  ASSERT_THAT_EXPECTED(R, Succeeded());
+  EXPECT_EQ(*R, 2 + 1); // argc == 2, strlen("a") == 1.
+
+  cantFail(ES.endSession());
+}
+
+// A required (default) Create against a missing symbol fails, rather than
+// yielding a null caller as the weakly-referenced form does.
+TEST(SPSCallersTest, CreateRequiredAbsentFails) {
+  ExecutionSession ES(cantFail(SelfExecutorProcessControl::Create()));
+
+  // Nothing is defined for MainCaller::CIName in the bootstrap JITDylib, and
+  // the default lookup requires the symbol.
+  Expected<MainCaller> CallMain = MainCaller::Create(ES);
+  EXPECT_THAT_EXPECTED(CallMain, Failed());
+
+  cantFail(ES.endSession());
+}



More information about the llvm-commits mailing list