[llvm] ba37179 - [ORC] Add rt::sps::MainCaller for running main-like functions. (#212948)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Jul 30 03:16:14 PDT 2026
Author: Lang Hames
Date: 2026-07-30T20:16:08+10:00
New Revision: ba371791db4f9e7ef05f0a1c26529298b2951a4b
URL: https://github.com/llvm/llvm-project/commit/ba371791db4f9e7ef05f0a1c26529298b2951a4b
DIFF: https://github.com/llvm/llvm-project/commit/ba371791db4f9e7ef05f0a1c26529298b2951a4b.diff
LOG: [ORC] Add rt::sps::MainCaller for running main-like functions. (#212948)
rt::MainCaller is a controller-side interface for running functions with
a main-like signature (int(int argc, char *argv[])) in the executor.
rt::sps::MainCaller is rt::MainCaller's first implementation, which
invokes main functions using on ORC runtime's orc_rt_ci_sps_call_main
entrypoint (using Simple Packed Serialization for argument/return
encoding / decoding).
These utilities live under the ExecutionEngine/Orc/RTBridge directory,
which is intended to hold controller-side utilities for invoking ORC
runtime systems. Subdirectories (e.g. SPS) hold implementations for
specific serialization schemes.
Adds SPSCallersTest with coverage for direct construction, the
synchronous and asynchronous call operators, use through the
rt::MainCaller interface, and the Create / bootstrap-JITDylib lookup
path.
Added:
llvm/include/llvm/ExecutionEngine/Orc/RTBridge/Calls.h
llvm/include/llvm/ExecutionEngine/Orc/RTBridge/SPS/Calls.h
llvm/unittests/ExecutionEngine/Orc/SPSCallersTest.cpp
Modified:
llvm/lib/ExecutionEngine/Orc/Shared/OrcRTBridge.cpp
llvm/unittests/ExecutionEngine/Orc/CMakeLists.txt
Removed:
################################################################################
diff --git a/llvm/include/llvm/ExecutionEngine/Orc/RTBridge/Calls.h b/llvm/include/llvm/ExecutionEngine/Orc/RTBridge/Calls.h
new file mode 100644
index 0000000000000..0122d2666e8b9
--- /dev/null
+++ b/llvm/include/llvm/ExecutionEngine/Orc/RTBridge/Calls.h
@@ -0,0 +1,68 @@
+//===------- Calls.h - Runtime-agnostic executor call APIs ------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Runtime-agnostic interfaces for invoking executor-side operations. These
+// abstract over how a call reaches the executor, so clients can be written
+// once and used whether the operation is provided by a full ORC runtime or by
+// LLVM's own ORC-runtime-lite. Concrete implementations live in subdirectories
+// (e.g. RTBridge/SPS).
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_EXECUTIONENGINE_ORC_RTBRIDGE_CALLS_H
+#define LLVM_EXECUTIONENGINE_ORC_RTBRIDGE_CALLS_H
+
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/FunctionExtras.h"
+#include "llvm/ExecutionEngine/Orc/Shared/ExecutorAddress.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/MSVCErrorWorkarounds.h"
+
+#include <cstdint>
+#include <future>
+#include <string>
+
+namespace llvm::orc::rt {
+
+/// Interface for running a main-like function (int(int argc, char *argv[])) in
+/// the executor.
+///
+/// Given the address of the function to run and an argument vector, invokes the
+/// function in the executor and returns its integer result. Calls can be made
+/// asynchronously (delivering the result to an OnComplete continuation) or
+/// synchronously (blocking until the result is available). Concrete
+/// implementations (e.g. rt::sps::MainCaller) determine how the call reaches
+/// the executor.
+class MainCaller {
+public:
+ virtual ~MainCaller();
+
+ /// Asynchronously run the main-like function at MainFnAddr with the given
+ /// Args, delivering its result (or an error) to OnComplete.
+ virtual void operator()(unique_function<void(Expected<int64_t>)> OnComplete,
+ ExecutorAddr MainFnAddr,
+ ArrayRef<std::string> Args) = 0;
+
+ /// Run the main-like function at MainFnAddr with the given Args, blocking
+ /// until its result (or an error) is available.
+ Expected<int64_t> operator()(ExecutorAddr MainFnAddr,
+ ArrayRef<std::string> Args) {
+ std::promise<MSVCPExpected<int64_t>> P;
+ auto F = P.get_future();
+ this->operator()(
+ [P = std::move(P)](Expected<int64_t> R) mutable {
+ P.set_value(std::move(R));
+ },
+ MainFnAddr, Args);
+ return F.get();
+ }
+};
+
+} // 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
new file mode 100644
index 0000000000000..bba54bda8a3c8
--- /dev/null
+++ b/llvm/include/llvm/ExecutionEngine/Orc/RTBridge/SPS/Calls.h
@@ -0,0 +1,72 @@
+//===------------- Calls.h - SPS-based Call Wrappers ------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// SPS-based wrappers for calling functions.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_EXECUTIONENGINE_ORC_RTBRIDGE_SPS_CALLS_H
+#define LLVM_EXECUTIONENGINE_ORC_RTBRIDGE_SPS_CALLS_H
+
+#include "llvm/ExecutionEngine/Orc/Core.h"
+#include "llvm/ExecutionEngine/Orc/RTBridge/Calls.h"
+#include "llvm/ExecutionEngine/Orc/Shared/WrapperFunctionUtils.h"
+
+namespace llvm::orc::rt::sps {
+
+/// Calls the executor-side "call-main" wrapper, which runs a program's main
+/// function with the given arguments and returns its result.
+class MainCaller : public rt::MainCaller {
+public:
+ /// Name of the controller interface wrapper function.
+ static constexpr const char *CIName = "orc_rt_ci_sps_call_main";
+
+ static void callAsync(unique_function<void(Expected<int64_t>)> OnComplete,
+ ExecutionSession &ES, ExecutorAddr CallMainFnAddr,
+ ExecutorAddr MainFnAddr, ArrayRef<std::string> Args) {
+ using namespace llvm::orc::shared;
+ ES.callSPSWrapperAsync<int64_t(SPSExecutorAddr, SPSSequence<SPSString>)>(
+ CallMainFnAddr,
+ [OnComplete = std::move(OnComplete)](Error SerErr,
+ int64_t Result) mutable {
+ if (SerErr)
+ return OnComplete(std::move(SerErr));
+ else
+ return OnComplete(Result);
+ },
+ MainFnAddr, Args);
+ }
+
+ MainCaller(ExecutionSession &ES, ExecutorAddr CallMainFnAddr)
+ : ES(ES), CallMainFnAddr(CallMainFnAddr) {}
+
+ /// Look up the call-main wrapper in the executor's bootstrap JITDylib and
+ /// build a MainCaller for it.
+ static Expected<MainCaller> Create(ExecutionSession &ES) {
+ if (auto CallMainSym = ES.lookup({&ES.getBootstrapJITDylib()}, CIName))
+ return MainCaller(ES, CallMainSym->getAddress());
+ else
+ return CallMainSym.takeError();
+ }
+
+ void operator()(unique_function<void(Expected<int64_t>)> OnComplete,
+ ExecutorAddr MainFnAddr,
+ ArrayRef<std::string> Args) override {
+ callAsync(std::move(OnComplete), ES, CallMainFnAddr, MainFnAddr, Args);
+ }
+
+ using rt::MainCaller::operator();
+
+private:
+ ExecutionSession &ES;
+ ExecutorAddr CallMainFnAddr;
+};
+
+} // namespace llvm::orc::rt::sps
+
+#endif // LLVM_EXECUTIONENGINE_ORC_RTBRIDGE_SPS_CALLS_H
diff --git a/llvm/lib/ExecutionEngine/Orc/Shared/OrcRTBridge.cpp b/llvm/lib/ExecutionEngine/Orc/Shared/OrcRTBridge.cpp
index 24c1ca6e4904c..7239582715f9e 100644
--- a/llvm/lib/ExecutionEngine/Orc/Shared/OrcRTBridge.cpp
+++ b/llvm/lib/ExecutionEngine/Orc/Shared/OrcRTBridge.cpp
@@ -8,10 +8,14 @@
#include "llvm/ExecutionEngine/Orc/Shared/OrcRTBridge.h"
+#include "llvm/ExecutionEngine/Orc/RTBridge/Calls.h"
+
namespace llvm {
namespace orc {
namespace rt {
+MainCaller::~MainCaller() = default;
+
const char *SimpleExecutorDylibManagerInstanceName =
"__llvm_orc_SimpleExecutorDylibManager_Instance";
const char *SimpleExecutorDylibManagerOpenWrapperName =
diff --git a/llvm/unittests/ExecutionEngine/Orc/CMakeLists.txt b/llvm/unittests/ExecutionEngine/Orc/CMakeLists.txt
index 82744eafd3e33..fa134ff212043 100644
--- a/llvm/unittests/ExecutionEngine/Orc/CMakeLists.txt
+++ b/llvm/unittests/ExecutionEngine/Orc/CMakeLists.txt
@@ -47,6 +47,7 @@ add_llvm_unittest(OrcJITTests
SharedMemoryMapperTest.cpp
SimpleExecutorMemoryManagerTest.cpp
SimplePackedSerializationTest.cpp
+ SPSCallersTest.cpp
SymbolStringPoolTest.cpp
TaskDispatchTest.cpp
ThreadSafeModuleTest.cpp
diff --git a/llvm/unittests/ExecutionEngine/Orc/SPSCallersTest.cpp b/llvm/unittests/ExecutionEngine/Orc/SPSCallersTest.cpp
new file mode 100644
index 0000000000000..067fa6dbeb574
--- /dev/null
+++ b/llvm/unittests/ExecutionEngine/Orc/SPSCallersTest.cpp
@@ -0,0 +1,144 @@
+//===- SPSCallersTest.cpp - Test SPS call wrappers ------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/ExecutionEngine/Orc/AbsoluteSymbols.h"
+#include "llvm/ExecutionEngine/Orc/RTBridge/SPS/Calls.h"
+#include "llvm/ExecutionEngine/Orc/SelfExecutorProcessControl.h"
+#include "llvm/ExecutionEngine/Orc/Shared/WrapperFunctionUtils.h"
+#include "llvm/Support/MSVCErrorWorkarounds.h"
+#include "llvm/Testing/Support/Error.h"
+
+#include <cstring>
+#include <future>
+#include <string>
+#include <vector>
+
+#include "gtest/gtest.h"
+
+using namespace llvm;
+using namespace llvm::orc;
+using namespace llvm::orc::shared;
+using llvm::orc::rt::sps::MainCaller;
+// 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[]) {
+ int Result = argc;
+ if (argc > 0)
+ Result += static_cast<int>(std::strlen(argv[0]));
+ return Result;
+}
+
+// Executor-side "call-main" wrapper. Decodes the main-function address and the
+// argument vector, then invokes the main function with a C-style (argc, argv).
+static CWrapperFunctionBuffer callMainWrapper(const char *ArgData,
+ size_t ArgSize) {
+ return WrapperFunction<int64_t(SPSExecutorAddr, SPSSequence<SPSString>)>::
+ handle(ArgData, ArgSize,
+ [](ExecutorAddr MainFnAddr,
+ std::vector<std::string> Args) -> int64_t {
+ std::vector<char *> ArgV;
+ ArgV.reserve(Args.size() + 1);
+ for (auto &Arg : Args)
+ ArgV.push_back(Arg.data());
+ ArgV.push_back(nullptr);
+ auto *Main = MainFnAddr.toPtr<int(int, char **)>();
+ return Main(static_cast<int>(Args.size()), ArgV.data());
+ })
+ .release();
+}
+
+TEST(SPSCallersTest, CallMainSyncViaDirectConstruction) {
+ ExecutionSession ES(cantFail(SelfExecutorProcessControl::Create()));
+
+ MainCaller CallMain(ES, ExecutorAddr::fromPtr(callMainWrapper));
+ ExecutorAddr MainAddr = ExecutorAddr::fromPtr(testMain);
+
+ std::vector<std::string> Args;
+ Expected<int64_t> R0 = CallMain(MainAddr, Args);
+ ASSERT_THAT_EXPECTED(R0, Succeeded());
+ EXPECT_EQ(*R0, 0); // argc == 0, no argv[0].
+
+ Args = {"hello"};
+ Expected<int64_t> R1 = CallMain(MainAddr, Args);
+ ASSERT_THAT_EXPECTED(R1, Succeeded());
+ EXPECT_EQ(*R1, 1 + 5); // argc == 1, strlen("hello") == 5.
+
+ Args = {"a", "bb"};
+ Expected<int64_t> R2 = CallMain(MainAddr, Args);
+ ASSERT_THAT_EXPECTED(R2, Succeeded());
+ EXPECT_EQ(*R2, 2 + 1); // argc == 2, strlen("a") == 1.
+
+ cantFail(ES.endSession());
+}
+
+TEST(SPSCallersTest, CallMainAsyncViaCallOperator) {
+ ExecutionSession ES(cantFail(SelfExecutorProcessControl::Create()));
+
+ MainCaller CallMain(ES, ExecutorAddr::fromPtr(callMainWrapper));
+
+ std::vector<std::string> Args = {"foo", "bar"};
+ std::promise<MSVCPExpected<int64_t>> P;
+ auto F = P.get_future();
+ CallMain([&](Expected<int64_t> R) { P.set_value(std::move(R)); },
+ ExecutorAddr::fromPtr(testMain), Args);
+
+ Expected<int64_t> R = F.get();
+ ASSERT_THAT_EXPECTED(R, Succeeded());
+ EXPECT_EQ(*R, 2 + 3); // argc == 2, strlen("foo") == 3.
+
+ cantFail(ES.endSession());
+}
+
+TEST(SPSCallersTest, CallMainThroughRTInterface) {
+ ExecutionSession ES(cantFail(SelfExecutorProcessControl::Create()));
+
+ MainCaller CallMain(ES, ExecutorAddr::fromPtr(callMainWrapper));
+
+ // Drive the caller through the runtime-agnostic rt::MainCaller interface to
+ // exercise the virtual dispatch path (and to guard the interface's public
+ // accessibility).
+ rt::MainCaller &Base = CallMain;
+ ExecutorAddr MainAddr = ExecutorAddr::fromPtr(testMain);
+
+ // Synchronous call operator (inherited from rt::MainCaller).
+ std::vector<std::string> Args = {"hello"};
+ Expected<int64_t> RSync = Base(MainAddr, Args);
+ ASSERT_THAT_EXPECTED(RSync, Succeeded());
+ EXPECT_EQ(*RSync, 1 + 5); // argc == 1, strlen("hello") == 5.
+
+ // Asynchronous call operator (virtual).
+ std::promise<MSVCPExpected<int64_t>> P;
+ auto F = P.get_future();
+ Base([&](Expected<int64_t> R) { P.set_value(std::move(R)); }, MainAddr, Args);
+ Expected<int64_t> RAsync = F.get();
+ ASSERT_THAT_EXPECTED(RAsync, Succeeded());
+ EXPECT_EQ(*RAsync, 1 + 5);
+
+ cantFail(ES.endSession());
+}
+
+TEST(SPSCallersTest, CreateLooksUpCallMainInBootstrapJD) {
+ ExecutionSession ES(cantFail(SelfExecutorProcessControl::Create()));
+
+ // Register the call-main wrapper in the bootstrap JITDylib under the name
+ // MainCaller::Create looks for.
+ auto &BootstrapJD = ES.getBootstrapJITDylib();
+ cantFail(BootstrapJD.define(absoluteSymbols(
+ {{ES.intern(MainCaller::CIName),
+ {ExecutorAddr::fromPtr(callMainWrapper), JITSymbolFlags::Exported}}})));
+
+ Expected<MainCaller> CallMain = MainCaller::Create(ES);
+ ASSERT_THAT_EXPECTED(CallMain, Succeeded());
+
+ std::vector<std::string> Args = {"x", "y", "z"};
+ Expected<int64_t> R = (*CallMain)(ExecutorAddr::fromPtr(testMain), Args);
+ ASSERT_THAT_EXPECTED(R, Succeeded());
+ EXPECT_EQ(*R, 3 + 1); // argc == 3, strlen("x") == 1.
+
+ cantFail(ES.endSession());
+}
More information about the llvm-commits
mailing list