[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