[llvm] 6ebd9e1 - [ORC] Add VoidVoid and IntVoid Callers, with tests. (#213211)

via llvm-commits llvm-commits at lists.llvm.org
Fri Jul 31 04:06:28 PDT 2026


Author: Lang Hames
Date: 2026-07-31T21:06:23+10:00
New Revision: 6ebd9e1a12235fdc42e3e7b157678e6711af9c62

URL: https://github.com/llvm/llvm-project/commit/6ebd9e1a12235fdc42e3e7b157678e6711af9c62
DIFF: https://github.com/llvm/llvm-project/commit/6ebd9e1a12235fdc42e3e7b157678e6711af9c62.diff

LOG: [ORC] Add VoidVoid and IntVoid Callers, with tests. (#213211)

Add rt::VoidVoidCaller (void()) and rt::IntVoidCaller (int64_t())
runtime-agnostic interfaces and their rt::sps implementations, targeting
the orc_rt_ci_sps_call_void_void and orc_rt_ci_sps_call_int_void
controller-interface wrappers. Both are experimental and may be removed.

Extend SPSCallersTest to cover the new callers. VoidVoidCaller is the
first caller instantiated with a void result type, which exposed a
latent bug in sps::Caller::callAsync: its result handler declared a
CalleeRetT parameter, ill-formed when CalleeRetT is void. It now
special-cases void via `if constexpr`.

Added: 
    

Modified: 
    llvm/include/llvm/ExecutionEngine/Orc/RTBridge/Calls.h
    llvm/include/llvm/ExecutionEngine/Orc/RTBridge/SPS/Calls.h
    llvm/unittests/ExecutionEngine/Orc/SPSCallersTest.cpp

Removed: 
    


################################################################################
diff  --git a/llvm/include/llvm/ExecutionEngine/Orc/RTBridge/Calls.h b/llvm/include/llvm/ExecutionEngine/Orc/RTBridge/Calls.h
index 61eb6d54f4ebd..29cf268c71803 100644
--- a/llvm/include/llvm/ExecutionEngine/Orc/RTBridge/Calls.h
+++ b/llvm/include/llvm/ExecutionEngine/Orc/RTBridge/Calls.h
@@ -84,6 +84,29 @@ template <typename RetT, typename... ArgTs> class Caller<RetT(ArgTs...)> {
 /// argument vector, and its int64_t result is returned.
 class MainCaller : public Caller<int64_t(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()> {};
+
+/// Runtime-agnostic interface for running an int32_t() function in the
+/// executor.
+///
+/// The function to run is given by its ExecutorAddr.
+///
+/// WARNING: This Caller is experimental and may be removed.
+class Int32VoidCaller : public Caller<int32_t()> {};
+
+/// Runtime-agnostic interface for running an int32_t(int32_t) function in the
+/// executor.
+///
+/// The function to run is given by its ExecutorAddr.
+///
+/// WARNING: This Caller is experimental and may be removed.
+class Int32Int32Caller : public Caller<int32_t(int32_t)> {};
+
 } // namespace llvm::orc::rt
 
 #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 86e89acdee3ff..d11cc16a7b7e7 100644
--- a/llvm/include/llvm/ExecutionEngine/Orc/RTBridge/SPS/Calls.h
+++ b/llvm/include/llvm/ExecutionEngine/Orc/RTBridge/SPS/Calls.h
@@ -72,16 +72,27 @@ class Caller<BaseT, SPSSigT, CINameV, RetT(ArgTs...)> : public BaseT {
                         ExecutionSession &ES, ExecutorAddr CallerFnAddr,
                         ExecutorAddr FnAddr, const ArgTs &...Args) {
     using namespace llvm::orc::shared;
-    ES.callSPSWrapperAsync<SPSSigT>(
-        CallerFnAddr,
-        [OnComplete = std::move(OnComplete)](Error SerErr,
-                                             CalleeRetT Result) mutable {
-          if (SerErr)
-            return OnComplete(std::move(SerErr));
-          else
-            return OnComplete(std::move(Result));
-        },
-        FnAddr, Args...);
+    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,
+          [OnComplete = std::move(OnComplete)](Error SerErr) mutable {
+            OnComplete(std::move(SerErr));
+          },
+          FnAddr, Args...);
+    } else {
+      ES.callSPSWrapperAsync<SPSSigT>(
+          CallerFnAddr,
+          [OnComplete = std::move(OnComplete)](Error SerErr,
+                                               CalleeRetT Result) mutable {
+            if (SerErr)
+              return OnComplete(std::move(SerErr));
+            else
+              return OnComplete(std::move(Result));
+          },
+          FnAddr, Args...);
+    }
   }
 
   void operator()(unique_function<void(ErrorRetT)> OnComplete,
@@ -103,6 +114,29 @@ inline constexpr char CallMainCIName[] = "orc_rt_ci_sps_call_main";
 /// (int(int argc, char *argv[])) in the executor.
 using MainCaller = Caller<rt::MainCaller, CallMainSPSSig, CallMainCIName>;
 
+using CallVoidVoidSPSSig = void(shared::SPSExecutorAddr);
+inline constexpr char CallVoidVoidCIName[] = "orc_rt_ci_sps_call_void_void";
+/// SPS caller for rt::VoidVoidCaller: runs a void() function in the executor.
+/// WARNING: This Caller is experimental and may be removed.
+using VoidVoidCaller =
+    Caller<rt::VoidVoidCaller, CallVoidVoidSPSSig, CallVoidVoidCIName>;
+
+using CallInt32VoidSPSSig = int32_t(shared::SPSExecutorAddr);
+inline constexpr char CallInt32VoidCIName[] = "orc_rt_ci_sps_call_int32_void";
+/// SPS caller for rt::Int32VoidCaller: runs an int32_t() function in the
+/// executor.
+/// WARNING: This Caller is experimental and may be removed.
+using Int32VoidCaller =
+    Caller<rt::Int32VoidCaller, CallInt32VoidSPSSig, CallInt32VoidCIName>;
+
+using CallInt32Int32SPSSig = int32_t(shared::SPSExecutorAddr, int32_t);
+inline constexpr char CallInt32Int32CIName[] = "orc_rt_ci_sps_call_int32_int32";
+/// SPS caller for rt::Int32Int32Caller: runs an int32_t(int32_t) function in
+/// the executor.
+/// WARNING: This Caller is experimental and may be removed.
+using Int32Int32Caller =
+    Caller<rt::Int32Int32Caller, CallInt32Int32SPSSig, CallInt32Int32CIName>;
+
 } // namespace llvm::orc::rt::sps
 
 #endif // LLVM_EXECUTIONENGINE_ORC_RTBRIDGE_SPS_CALLS_H

diff  --git a/llvm/unittests/ExecutionEngine/Orc/SPSCallersTest.cpp b/llvm/unittests/ExecutionEngine/Orc/SPSCallersTest.cpp
index 067fa6dbeb574..b2b41857cb976 100644
--- a/llvm/unittests/ExecutionEngine/Orc/SPSCallersTest.cpp
+++ b/llvm/unittests/ExecutionEngine/Orc/SPSCallersTest.cpp
@@ -23,7 +23,11 @@
 using namespace llvm;
 using namespace llvm::orc;
 using namespace llvm::orc::shared;
+using llvm::orc::rt::sps::Int32Int32Caller;
+using llvm::orc::rt::sps::Int32VoidCaller;
 using llvm::orc::rt::sps::MainCaller;
+using llvm::orc::rt::sps::VoidVoidCaller;
+
 // Test "main" function. Returns argc plus the length of the first element of
 // argv (if argv is non-empty). Does not inspect argv entries beyond the first.
 static int testMain(int argc, char *argv[]) {
@@ -142,3 +146,113 @@ TEST(SPSCallersTest, CreateLooksUpCallMainInBootstrapJD) {
 
   cantFail(ES.endSession());
 }
+
+// Target for VoidVoidCaller: records that it ran via a counter (there is no
+// return value to observe).
+static int VoidVoidCallCount = 0;
+static void voidVoidTarget() { ++VoidVoidCallCount; }
+
+// Executor-side wrapper for VoidVoidCaller. Decodes the target address and
+// invokes it as a void() function.
+static CWrapperFunctionBuffer callVoidVoidWrapper(const char *ArgData,
+                                                  size_t ArgSize) {
+  return WrapperFunction<void(SPSExecutorAddr)>::handle(
+             ArgData, ArgSize,
+             [](ExecutorAddr FnAddr) { FnAddr.toPtr<void()>()(); })
+      .release();
+}
+
+// Target for Int32Int32Caller: doubles its argument, so the forwarded value is
+// observable in the result.
+static int32_t int32Int32Target(int32_t X) { return X * 2; }
+
+// Executor-side wrapper for Int32Int32Caller. Decodes the target address and
+// the int32_t argument, invokes the target, and returns the result.
+static CWrapperFunctionBuffer callInt32Int32Wrapper(const char *ArgData,
+                                                    size_t ArgSize) {
+  return WrapperFunction<int32_t(SPSExecutorAddr, int32_t)>::handle(
+             ArgData, ArgSize,
+             [](ExecutorAddr FnAddr, int32_t X) -> int32_t {
+               return FnAddr.toPtr<int32_t(int32_t)>()(X);
+             })
+      .release();
+}
+
+// Exercises the void-return path (ErrorRetT == Error) and the empty argument
+// pack, through both the synchronous and asynchronous call operators.
+TEST(SPSCallersTest, VoidVoidSyncAndAsync) {
+  ExecutionSession ES(cantFail(SelfExecutorProcessControl::Create()));
+
+  VoidVoidCaller Call(ES, ExecutorAddr::fromPtr(callVoidVoidWrapper));
+  ExecutorAddr TargetAddr = ExecutorAddr::fromPtr(voidVoidTarget);
+
+  // Synchronous: the call operator returns Error, not Expected<T>.
+  VoidVoidCallCount = 0;
+  EXPECT_THAT_ERROR(Call(TargetAddr), Succeeded());
+  EXPECT_EQ(VoidVoidCallCount, 1);
+
+  // Asynchronous: the result is delivered as an Error.
+  VoidVoidCallCount = 0;
+  std::promise<MSVCPError> P;
+  auto F = P.get_future();
+  Call([&](Error Err) { P.set_value(std::move(Err)); }, TargetAddr);
+  EXPECT_THAT_ERROR(Error(F.get()), Succeeded());
+  EXPECT_EQ(VoidVoidCallCount, 1);
+
+  cantFail(ES.endSession());
+}
+
+// Exercises a non-void caller with an argument (so argument forwarding through
+// the pack is covered), and the Create / bootstrap lookup path for a caller
+// other than MainCaller.
+TEST(SPSCallersTest, Int32Int32SyncAndCreate) {
+  ExecutionSession ES(cantFail(SelfExecutorProcessControl::Create()));
+
+  Int32Int32Caller Call(ES, ExecutorAddr::fromPtr(callInt32Int32Wrapper));
+  Expected<int32_t> RDirect = Call(ExecutorAddr::fromPtr(int32Int32Target), 21);
+  ASSERT_THAT_EXPECTED(RDirect, Succeeded());
+  EXPECT_EQ(*RDirect, 42); // 21 * 2.
+
+  auto &BootstrapJD = ES.getBootstrapJITDylib();
+  cantFail(BootstrapJD.define(
+      absoluteSymbols({{ES.intern(Int32Int32Caller::CIName),
+                        {ExecutorAddr::fromPtr(callInt32Int32Wrapper),
+                         JITSymbolFlags::Exported}}})));
+
+  Expected<Int32Int32Caller> CreatedCall = Int32Int32Caller::Create(ES);
+  ASSERT_THAT_EXPECTED(CreatedCall, Succeeded());
+  Expected<int32_t> RCreated =
+      (*CreatedCall)(ExecutorAddr::fromPtr(int32Int32Target), 21);
+  ASSERT_THAT_EXPECTED(RCreated, Succeeded());
+  EXPECT_EQ(*RCreated, 42); // 21 * 2.
+
+  cantFail(ES.endSession());
+}
+
+// Target for Int32VoidCaller.
+static int32_t int32VoidTarget() { return 42; }
+
+// Executor-side wrapper for Int32VoidCaller. Decodes the target address,
+// invokes it as an int32_t() function, and returns the result.
+static CWrapperFunctionBuffer callInt32VoidWrapper(const char *ArgData,
+                                                   size_t ArgSize) {
+  return WrapperFunction<int32_t(SPSExecutorAddr)>::handle(
+             ArgData, ArgSize,
+             [](ExecutorAddr FnAddr) -> int32_t {
+               return FnAddr.toPtr<int32_t()>()();
+             })
+      .release();
+}
+
+// Exercises a non-void, zero-argument caller. (Void return and argument
+// forwarding are covered by VoidVoidCaller and Int32Int32Caller respectively.)
+TEST(SPSCallersTest, Int32VoidSync) {
+  ExecutionSession ES(cantFail(SelfExecutorProcessControl::Create()));
+
+  Int32VoidCaller Call(ES, ExecutorAddr::fromPtr(callInt32VoidWrapper));
+  Expected<int32_t> R = Call(ExecutorAddr::fromPtr(int32VoidTarget));
+  ASSERT_THAT_EXPECTED(R, Succeeded());
+  EXPECT_EQ(*R, 42);
+
+  cantFail(ES.endSession());
+}


        


More information about the llvm-commits mailing list