From 8f6204f8937d9a670de5fd719424ef16b65eb8bd Mon Sep 17 00:00:00 2001 From: Lang Hames Date: Wed, 3 Dec 2025 22:08:32 +1100 Subject: [PATCH] [ORC] Add CallViaEPC and CallSPSViaEPC utilities. These utilities simplify making typed async calls via ExecutorProcessControl::callWrapperAsync. CallViaEPC.h provides utilities for making typed async calls using a given Serializer to serialize arguments and deserialize results. callViaEPC takes a result handler function object (accepting Expected), an EPC reference, a Serializer, a target function address, and arguments. The return type T is inferred from the handler's argument type using CallableTraitsHelper. EPCCaller wraps an ExecutorProcessControl& and Serializer, simplifying repeated calls with the same serialization. EPCCall wraps an EPCCaller and target function address, simplifying repeated calls to a specific wrapper function. CallSPSViaEPC.h provides utilities for using CallViaEPC with SPS serialization. SPSCallSerializer is a CallViaEPC-compatible serializer using SPS. SPSEPCCaller takes an SPS function signature as its template argument and applies SPS serialization to arguments and return values. SPSEPCCall wraps an SPSCaller and a target function address, simplifying repeated calls to a specific SPS wrapper function. --- .../llvm/ExecutionEngine/Orc/CallSPSViaEPC.h | 79 ++++++++++++++ .../llvm/ExecutionEngine/Orc/CallViaEPC.h | 102 ++++++++++++++++++ .../ExecutionEngine/Orc/CMakeLists.txt | 1 + .../ExecutionEngine/Orc/CallSPSViaEPCTest.cpp | 90 ++++++++++++++++ 4 files changed, 272 insertions(+) create mode 100644 llvm/include/llvm/ExecutionEngine/Orc/CallSPSViaEPC.h create mode 100644 llvm/include/llvm/ExecutionEngine/Orc/CallViaEPC.h create mode 100644 llvm/unittests/ExecutionEngine/Orc/CallSPSViaEPCTest.cpp diff --git a/llvm/include/llvm/ExecutionEngine/Orc/CallSPSViaEPC.h b/llvm/include/llvm/ExecutionEngine/Orc/CallSPSViaEPC.h new file mode 100644 index 0000000000000..978c9be06ad98 --- /dev/null +++ b/llvm/include/llvm/ExecutionEngine/Orc/CallSPSViaEPC.h @@ -0,0 +1,79 @@ +//===---- CallSPSViaEPC.h - EPCCalls using SPS serialization ----*- 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 +// +//===----------------------------------------------------------------------===// +// +// EPCCalls using SimplePackedSerialization. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_EXECUTIONENGINE_ORC_CALLSPSVIAEPC_H +#define LLVM_EXECUTIONENGINE_ORC_CALLSPSVIAEPC_H + +#include "llvm/ExecutionEngine/Orc/CallViaEPC.h" +#include "llvm/ExecutionEngine/Orc/CallableTraitsHelper.h" +#include "llvm/ExecutionEngine/Orc/Shared/SimplePackedSerialization.h" + +namespace llvm::orc { + +namespace detail { +template struct SPSCallSerializationImpl { + using RetSerialization = shared::SPSArgList; + using ArgSerialization = shared::SPSArgList; +}; +} // namespace detail + +template +struct SPSCallSerialization + : public CallableTraitsHelper {}; + +template class SPSCallSerializer { +public: + template + Expected serialize(ArgTs &&...Args) { + using ArgSerialization = + typename SPSCallSerialization::ArgSerialization; + auto Buffer = shared::WrapperFunctionResult::allocate( + ArgSerialization::size(Args...)); + shared::SPSOutputBuffer OB(Buffer.data(), Buffer.size()); + if (!ArgSerialization::serialize(OB, Args...)) + return make_error("Could not serialize arguments", + inconvertibleErrorCode()); + return std::move(Buffer); + } + + template + Expected deserialize(shared::WrapperFunctionResult ResultBytes) { + using RetDeserialization = + typename SPSCallSerialization::RetSerialization; + shared::SPSInputBuffer IB(ResultBytes.data(), ResultBytes.size()); + RetT ReturnValue; + if (!RetDeserialization::deserialize(IB, ReturnValue)) + return make_error("Could not deserialize return value", + inconvertibleErrorCode()); + return ReturnValue; + } +}; + +template +class SPSEPCCaller : public EPCCaller> { +public: + SPSEPCCaller(ExecutorProcessControl &EPC) + : EPCCaller>(EPC, SPSCallSerializer()) { + } +}; + +template +class SPSEPCCall : public EPCCall> { +public: + SPSEPCCall(ExecutorProcessControl &EPC, ExecutorSymbolDef Fn) + : EPCCall>(EPC, SPSCallSerializer(), + std::move(Fn)) {} +}; + +} // namespace llvm::orc + +#endif // LLVM_EXECUTIONENGINE_ORC_CALLSPSVIAEPC_H diff --git a/llvm/include/llvm/ExecutionEngine/Orc/CallViaEPC.h b/llvm/include/llvm/ExecutionEngine/Orc/CallViaEPC.h new file mode 100644 index 0000000000000..36acf05cb48e9 --- /dev/null +++ b/llvm/include/llvm/ExecutionEngine/Orc/CallViaEPC.h @@ -0,0 +1,102 @@ +//===------ CallViaEPC.h - Call wrapper functions via EPC -------*- 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 +// +//===----------------------------------------------------------------------===// +// +// Call executor functions with common signatures via +// ExecutorProcessControl::callWrapperAsync. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_EXECUTIONENGINE_ORC_CALLVIAEPC_H +#define LLVM_EXECUTIONENGINE_ORC_CALLVIAEPC_H + +#include "llvm/ExecutionEngine/Orc/CallableTraitsHelper.h" +#include "llvm/ExecutionEngine/Orc/ExecutorProcessControl.h" + +namespace llvm::orc { + +namespace detail { + +// Helper to extract the Expected argument type from a handler callable. +template struct HandlerTraits { + using ArgInfo = CallableArgInfo; + using ArgsTuple = typename ArgInfo::ArgsTupleType; + static_assert(std::tuple_size_v == 1, + "Handler must take exactly one argument"); + using ExpectedArgType = std::tuple_element_t<0, ArgsTuple>; + using RetT = typename std::remove_cv_t< + std::remove_reference_t>::value_type; +}; + +} // namespace detail + +/// Call a wrapper function via EPC. +template +void callViaEPC(HandlerT &&H, ExecutorProcessControl &EPC, Serializer S, + ExecutorSymbolDef Fn, ArgTs &&...Args) { + using RetT = typename detail::HandlerTraits::RetT; + + if (auto ArgBytes = S.serialize(std::forward(Args)...)) + EPC.callWrapperAsync( + Fn.getAddress(), + [S = std::move(S), H = std::forward(H)]( + shared::WrapperFunctionResult R) mutable { + if (const char *ErrMsg = R.getOutOfBandError()) + H(make_error(ErrMsg, inconvertibleErrorCode())); + else + H(S.template deserialize(std::move(R))); + }, + {ArgBytes->data(), ArgBytes->size()}); + else + H(ArgBytes.takeError()); +} + +/// Encapsulates calls via EPC to any function that's compatible with the given +/// serialization scheme. +template class EPCCaller { +public: + EPCCaller(ExecutorProcessControl &EPC, Serializer &&S) + : EPC(EPC), S(std::move(S)) {} + + // TODO: Add an ExecutionSession constructor once ExecutionSession has been + // moved to its own header. + + // Async call version. + template + void operator()(HandlerT &&H, ExecutorSymbolDef Fn, ArgTs &&...Args) { + callViaEPC(std::forward(H), EPC, S, Fn, + std::forward(Args)...); + } + +private: + ExecutorProcessControl &EPC; + Serializer S; +}; + +/// Encapsulates calls via EPC to a specific function, using the given +/// serialization scheme. +template class EPCCall { +public: + EPCCall(ExecutorProcessControl &EPC, Serializer &&S, ExecutorSymbolDef Fn) + : Caller(EPC, std::move(S)), Fn(std::move(Fn)) {} + + // TODO: Add an ExecutionSession constructor once ExecutionSession has been + // moved to its own header. + + template + void operator()(HandlerT &&H, ArgTs &&...Args) { + Caller(std::forward(H), Fn, std::forward(Args)...); + } + +private: + EPCCaller Caller; + ExecutorSymbolDef Fn; +}; + +} // namespace llvm::orc + +#endif // LLVM_EXECUTIONENGINE_ORC_CALLVIAEPC_H diff --git a/llvm/unittests/ExecutionEngine/Orc/CMakeLists.txt b/llvm/unittests/ExecutionEngine/Orc/CMakeLists.txt index e5b5633c5a096..ea45a03130073 100644 --- a/llvm/unittests/ExecutionEngine/Orc/CMakeLists.txt +++ b/llvm/unittests/ExecutionEngine/Orc/CMakeLists.txt @@ -19,6 +19,7 @@ set(LLVM_LINK_COMPONENTS add_llvm_unittest(OrcJITTests CallableTraitsHelperTest.cpp + CallSPSViaEPCTest.cpp CoreAPIsTest.cpp ExecutorAddressTest.cpp ExecutionSessionWrapperFunctionCallsTest.cpp diff --git a/llvm/unittests/ExecutionEngine/Orc/CallSPSViaEPCTest.cpp b/llvm/unittests/ExecutionEngine/Orc/CallSPSViaEPCTest.cpp new file mode 100644 index 0000000000000..6ad5835ed6e8f --- /dev/null +++ b/llvm/unittests/ExecutionEngine/Orc/CallSPSViaEPCTest.cpp @@ -0,0 +1,90 @@ +//===----------- CallSPSViaEPC.cpp - Test CallSPSViaEPC.h APIs ------------===// +// +// 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/CallSPSViaEPC.h" +#include "llvm/ExecutionEngine/Orc/SelfExecutorProcessControl.h" + +#include "llvm/Testing/Support/Error.h" + +#include "gtest/gtest.h" + +using namespace llvm; +using namespace llvm::orc; +using namespace llvm::orc::shared; + +static CWrapperFunctionResult mainWrapper(const char *ArgData, size_t ArgSize) { + return WrapperFunction)>::handle( + ArgData, ArgSize, + [](std::vector Args) -> int32_t { + return Args.size(); + }) + .release(); +} + +TEST(CallSPSViaEPCTest, CallMainViaCaller) { + auto EPC = cantFail(SelfExecutorProcessControl::Create()); + SPSEPCCaller)> C(*EPC); + std::vector Args; + + std::optional> R1; + C([&](Expected R) { R1 = std::move(R); }, + ExecutorSymbolDef::fromPtr(mainWrapper), Args); + ASSERT_THAT_EXPECTED(*R1, Succeeded()); + EXPECT_EQ(**R1, 0); + + Args.push_back("foo"); + std::optional> R2; + C([&](Expected R) { R2 = std::move(R); }, + ExecutorSymbolDef::fromPtr(mainWrapper), Args); + ASSERT_THAT_EXPECTED(*R2, Succeeded()); + EXPECT_EQ(**R2, 1); + + Args.push_back("foo"); + std::optional> R3; + C([&](Expected R) { R3 = std::move(R); }, + ExecutorSymbolDef::fromPtr(mainWrapper), Args); + ASSERT_THAT_EXPECTED(*R3, Succeeded()); + EXPECT_EQ(**R3, 2); + + Args.clear(); + std::optional> R4; + C([&](Expected R) { R4 = std::move(R); }, + ExecutorSymbolDef::fromPtr(mainWrapper), Args); + ASSERT_THAT_EXPECTED(*R4, Succeeded()); + EXPECT_EQ(**R4, 0); +} + +TEST(CallSPSViaEPCTest, CallMainViaGenericCall) { + auto EPC = cantFail(SelfExecutorProcessControl::Create()); + SPSEPCCall)> C( + *EPC, ExecutorSymbolDef::fromPtr(mainWrapper)); + std::vector Args; + + std::optional> R1; + C([&](Expected R) { R1 = std::move(R); }, Args); + ASSERT_THAT_EXPECTED(*R1, Succeeded()); + EXPECT_EQ(**R1, 0); + + Args.push_back("foo"); + std::optional> R2; + C([&](Expected R) { R2 = std::move(R); }, Args); + ASSERT_THAT_EXPECTED(*R2, Succeeded()); + EXPECT_EQ(**R2, 1); + + Args.push_back("foo"); + std::optional> R3; + C([&](Expected R) { R3 = std::move(R); }, Args); + ASSERT_THAT_EXPECTED(*R3, Succeeded()); + EXPECT_EQ(**R3, 2); + + Args.clear(); + std::optional> R4; + C([&](Expected R) { R4 = std::move(R); }, Args); + ASSERT_THAT_EXPECTED(*R4, Succeeded()); + EXPECT_EQ(**R4, 0); +}