| //===- ProxyTest.cpp - Test Proxy -----------------------------------------===// |
| // |
| // 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 |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // Tests for Proxy that are independent of any serialization protocol. |
| // A trivial in-process dispatch (interpreting the callee address as a local |
| // function pointer, no serialization) is used throughout -- this exercises the |
| // Proxy plumbing directly and demonstrates that Proxy is protocol-agnostic. |
| // The SPS protocol itself is tested in SPSProxySpecTest.cpp. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "llvm/ExecutionEngine/Orc/Proxy.h" |
| #include "llvm/ExecutionEngine/Orc/AbsoluteSymbols.h" |
| #include "llvm/ExecutionEngine/Orc/LookupAndApply.h" |
| #include "llvm/ExecutionEngine/Orc/Mangling.h" |
| #include "llvm/ExecutionEngine/Orc/RecordProxy.h" |
| #include "llvm/ExecutionEngine/Orc/SelfExecutorProcessControl.h" |
| #include "llvm/Support/MSVCErrorWorkarounds.h" |
| #include "llvm/Testing/Support/Error.h" |
| |
| #include <future> |
| |
| #include "gtest/gtest.h" |
| |
| using namespace llvm; |
| using namespace llvm::orc; |
| |
| namespace { |
| |
| // Target invoked in-process by the test dispatch below. |
| int32_t addOne(int32_t X) { return X + 1; } |
| |
| // A protocol-free dispatch: interpret the callee address as a local function |
| // pointer and call it directly. This drives Proxy without any |
| // serialization, so the tests exercise Proxy's own logic (result plumbing, |
| // operator bool, lookup) rather than a particular protocol. |
| template <typename RetT, typename... ArgTs> |
| void inProcessDispatch( |
| unique_function<void(typename Proxy<RetT(ArgTs...)>::ErrorRetT)> OnComplete, |
| ExecutionSession &ES, ExecutorAddr Callee, const ArgTs &...Args) { |
| auto *Fn = Callee.toPtr<RetT(ArgTs...)>(); |
| if constexpr (std::is_void_v<RetT>) { |
| Fn(Args...); |
| OnComplete(Error::success()); |
| } else |
| OnComplete(Fn(Args...)); |
| } |
| |
| using AddOneProxy = Proxy<int32_t(int32_t)>; |
| constexpr AddOneProxy::DispatchFn AddOneDispatch = |
| &inProcessDispatch<int32_t, int32_t>; |
| |
| // Callee returning Error: fails iff ShouldFail. Exercises the Error -> Error |
| // mapping. |
| Error maybeFail(bool ShouldFail) { |
| if (ShouldFail) |
| return make_error<StringError>("requested failure", |
| inconvertibleErrorCode()); |
| return Error::success(); |
| } |
| using MaybeFailProxy = Proxy<Error(bool)>; |
| constexpr MaybeFailProxy::DispatchFn MaybeFailDispatch = |
| &inProcessDispatch<Error, bool>; |
| |
| // Callee returning Expected<T>: fails iff Arg is negative, else returns Arg |
| // + 1. Exercises the Expected<T> -> Expected<T> (flattening) mapping. |
| Expected<int32_t> addOneOrFail(int32_t Arg) { |
| if (Arg < 0) |
| return make_error<StringError>("negative argument", |
| inconvertibleErrorCode()); |
| return Arg + 1; |
| } |
| using AddOneOrFailProxy = Proxy<Expected<int32_t>(int32_t)>; |
| constexpr AddOneOrFailProxy::DispatchFn AddOneOrFailDispatch = |
| &inProcessDispatch<Expected<int32_t>, int32_t>; |
| |
| // The callee return type maps to the client-facing (ErrorRetT) type as: |
| // void -> Error |
| // Error -> Error |
| // T -> Expected<T> |
| // Expected<T> -> Expected<T> |
| static_assert(std::is_same_v<Proxy<void(int)>::ErrorRetT, Error>); |
| static_assert(std::is_same_v<Proxy<Error(int)>::ErrorRetT, Error>); |
| static_assert(std::is_same_v<Proxy<int(int)>::ErrorRetT, Expected<int>>); |
| static_assert( |
| std::is_same_v<Proxy<Expected<int>(int)>::ErrorRetT, Expected<int>>); |
| |
| // A minimal ProxySpec-shaped type (static dispatch + Name) for exercising the |
| // recordProxy client path without depending on a protocol. |
| struct AddOneSpec { |
| static constexpr SymbolNameSpec Name = SymbolNameSpec::verbatim("add_one"); |
| static void dispatch(unique_function<void(Expected<int32_t>)> OnComplete, |
| ExecutionSession &ES, ExecutorAddr Callee, |
| const int32_t &Arg) { |
| inProcessDispatch<int32_t, int32_t>(std::move(OnComplete), ES, Callee, Arg); |
| } |
| }; |
| |
| } // namespace |
| |
| // The synchronous and asynchronous call operators forward the arguments to the |
| // dispatch function and deliver its result. |
| TEST(ProxyTest, SyncAndAsync) { |
| ExecutionSession ES(cantFail(SelfExecutorProcessControl::Create())); |
| |
| AddOneProxy Call(AddOneDispatch, ExecutorAddr::fromPtr(addOne)); |
| |
| Expected<int32_t> RSync = Call(ES, 41); |
| ASSERT_THAT_EXPECTED(RSync, Succeeded()); |
| EXPECT_EQ(*RSync, 42); |
| |
| std::promise<MSVCPExpected<int32_t>> P; |
| auto F = P.get_future(); |
| Call([&](Expected<int32_t> R) { P.set_value(std::move(R)); }, ES, 41); |
| Expected<int32_t> RAsync = F.get(); |
| ASSERT_THAT_EXPECTED(RAsync, Succeeded()); |
| EXPECT_EQ(*RAsync, 42); |
| |
| cantFail(ES.endSession()); |
| } |
| |
| // operator bool reflects whether the proxy has a non-null callee address, and |
| // calleeAddr() returns the address the proxy was constructed with. |
| TEST(ProxyTest, OperatorBoolAndAccessors) { |
| ExecutionSession ES(cantFail(SelfExecutorProcessControl::Create())); |
| |
| ExecutorAddr CalleeAddr = ExecutorAddr::fromPtr(addOne); |
| AddOneProxy Call(AddOneDispatch, CalleeAddr); |
| EXPECT_TRUE(static_cast<bool>(Call)); |
| EXPECT_EQ(Call.calleeAddr(), CalleeAddr); |
| |
| // A default-constructed proxy has a null callee address and is falsey. |
| AddOneProxy Null; |
| EXPECT_FALSE(static_cast<bool>(Null)); |
| |
| cantFail(ES.endSession()); |
| } |
| |
| // A required (default) recordProxy against a missing symbol fails the whole |
| // lookup, rather than yielding a null proxy as the weakly-referenced form does. |
| TEST(ProxyTest, RecordProxyRequiredAbsentFails) { |
| ExecutionSession ES(cantFail(SelfExecutorProcessControl::Create())); |
| |
| AddOneProxy Call; |
| EXPECT_THAT_ERROR(lookupAndApply(ES.getBootstrapJITDylib(), |
| {recordProxy<AddOneSpec>(&Call)}), |
| Failed()); |
| |
| cantFail(ES.endSession()); |
| } |
| |
| // A weakly-referenced recordProxy against a present symbol resolves it, |
| // yielding a usable proxy (truthy) bound to the registered address. |
| TEST(ProxyTest, RecordProxyWeaklyReferencedPresent) { |
| ExecutionSession ES(cantFail(SelfExecutorProcessControl::Create())); |
| |
| auto &JD = ES.getBootstrapJITDylib(); |
| ExecutorAddr CalleeAddr = ExecutorAddr::fromPtr(addOne); |
| cantFail( |
| JD.define(absoluteSymbols({{MangleAndInterner(ES)(AddOneSpec::Name), |
| {CalleeAddr, JITSymbolFlags::Exported}}}))); |
| |
| AddOneProxy Call; |
| cantFail(lookupAndApply( |
| JD, {recordProxy<AddOneSpec>( |
| &Call, SymbolLookupFlags::WeaklyReferencedSymbol)})); |
| EXPECT_TRUE(static_cast<bool>(Call)); |
| EXPECT_EQ(Call.calleeAddr(), CalleeAddr); |
| |
| cantFail(ES.endSession()); |
| } |
| |
| // recordProxy resolves a proxy from the bootstrap JITDylib via its spec, |
| // exercising the recordProxy / lookupAndApply client entry point. |
| TEST(ProxyTest, RecordProxy) { |
| ExecutionSession ES(cantFail(SelfExecutorProcessControl::Create())); |
| |
| auto &JD = ES.getBootstrapJITDylib(); |
| cantFail(JD.define(absoluteSymbols( |
| {{MangleAndInterner(ES)(AddOneSpec::Name), |
| {ExecutorAddr::fromPtr(addOne), JITSymbolFlags::Exported}}}))); |
| |
| AddOneProxy Call; |
| cantFail(lookupAndApply(JD, {recordProxy<AddOneSpec>(&Call)})); |
| ASSERT_TRUE(static_cast<bool>(Call)); |
| |
| Expected<int32_t> R = Call(ES, 41); |
| ASSERT_THAT_EXPECTED(R, Succeeded()); |
| EXPECT_EQ(*R, 42); |
| |
| cantFail(ES.endSession()); |
| } |
| |
| // recordProxy with an explicitly-supplied dispatch function and name -- the |
| // overload that takes no spec type. |
| TEST(ProxyTest, RecordProxyExplicitDispatch) { |
| ExecutionSession ES(cantFail(SelfExecutorProcessControl::Create())); |
| |
| auto &JD = ES.getBootstrapJITDylib(); |
| cantFail(JD.define(absoluteSymbols( |
| {{MangleAndInterner(ES)(AddOneSpec::Name), |
| {ExecutorAddr::fromPtr(addOne), JITSymbolFlags::Exported}}}))); |
| |
| AddOneProxy Call; |
| cantFail(lookupAndApply( |
| JD, {recordProxy(&Call, AddOneDispatch, AddOneSpec::Name)})); |
| ASSERT_TRUE(static_cast<bool>(Call)); |
| |
| Expected<int32_t> R = Call(ES, 41); |
| ASSERT_THAT_EXPECTED(R, Succeeded()); |
| EXPECT_EQ(*R, 42); |
| |
| cantFail(ES.endSession()); |
| } |
| |
| // recordProxy with a spec but an overridden lookup name -- the overload that |
| // takes a spec type plus an explicit name. The symbol is defined only under the |
| // override name, so resolving against the spec's default Name would fail; |
| // success proves the override is used. |
| TEST(ProxyTest, RecordProxySpecNameOverride) { |
| ExecutionSession ES(cantFail(SelfExecutorProcessControl::Create())); |
| |
| auto &JD = ES.getBootstrapJITDylib(); |
| cantFail(JD.define(absoluteSymbols( |
| {{ES.intern("add_one_alias"), |
| {ExecutorAddr::fromPtr(addOne), JITSymbolFlags::Exported}}}))); |
| |
| AddOneProxy Call; |
| cantFail(lookupAndApply( |
| JD, {recordProxy<AddOneSpec>( |
| &Call, SymbolNameSpec::verbatim("add_one_alias"))})); |
| ASSERT_TRUE(static_cast<bool>(Call)); |
| |
| Expected<int32_t> R = Call(ES, 41); |
| ASSERT_THAT_EXPECTED(R, Succeeded()); |
| EXPECT_EQ(*R, 42); |
| |
| cantFail(ES.endSession()); |
| } |
| |
| // lookupAndApply propagates the lookup flags: a weakly-referenced recordProxy |
| // for a missing symbol yields a null proxy rather than failing the lookup. |
| TEST(ProxyTest, RecordProxyWeaklyReferencedAbsent) { |
| ExecutionSession ES(cantFail(SelfExecutorProcessControl::Create())); |
| |
| AddOneProxy Call; |
| cantFail( |
| lookupAndApply(ES.getBootstrapJITDylib(), |
| {recordProxy<AddOneSpec>( |
| &Call, SymbolLookupFlags::WeaklyReferencedSymbol)})); |
| EXPECT_FALSE(static_cast<bool>(Call)); |
| |
| cantFail(ES.endSession()); |
| } |
| |
| // recordProxy with an explicitly-supplied dispatch function and an |
| // already-interned name -- the SymbolStringPtr counterpart of |
| // RecordProxyExplicitDispatch above. |
| TEST(ProxyTest, RecordProxySymbolStringPtrExplicitDispatch) { |
| ExecutionSession ES(cantFail(SelfExecutorProcessControl::Create())); |
| |
| auto &JD = ES.getBootstrapJITDylib(); |
| cantFail(JD.define(absoluteSymbols( |
| {{MangleAndInterner(ES)(AddOneSpec::Name), |
| {ExecutorAddr::fromPtr(addOne), JITSymbolFlags::Exported}}}))); |
| |
| AddOneProxy Call; |
| cantFail(lookupAndApply( |
| JD, {recordProxy(&Call, AddOneDispatch, |
| MangleAndInterner(ES)(AddOneSpec::Name))})); |
| ASSERT_TRUE(static_cast<bool>(Call)); |
| |
| Expected<int32_t> R = Call(ES, 41); |
| ASSERT_THAT_EXPECTED(R, Succeeded()); |
| EXPECT_EQ(*R, 42); |
| |
| cantFail(ES.endSession()); |
| } |
| |
| // recordProxy with a spec but an overridden, already-interned lookup name -- |
| // the SymbolStringPtr counterpart of RecordProxySpecNameOverride above. |
| TEST(ProxyTest, RecordProxySpecSymbolStringPtrNameOverride) { |
| ExecutionSession ES(cantFail(SelfExecutorProcessControl::Create())); |
| |
| auto &JD = ES.getBootstrapJITDylib(); |
| cantFail(JD.define(absoluteSymbols( |
| {{ES.intern("add_one_alias"), |
| {ExecutorAddr::fromPtr(addOne), JITSymbolFlags::Exported}}}))); |
| |
| AddOneProxy Call; |
| cantFail(lookupAndApply( |
| JD, {recordProxy<AddOneSpec>(&Call, ES.intern("add_one_alias"))})); |
| ASSERT_TRUE(static_cast<bool>(Call)); |
| |
| Expected<int32_t> R = Call(ES, 41); |
| ASSERT_THAT_EXPECTED(R, Succeeded()); |
| EXPECT_EQ(*R, 42); |
| |
| cantFail(ES.endSession()); |
| } |
| |
| // lookupAndApply propagates the lookup flags for the SymbolStringPtr |
| // overload too: a weakly-referenced recordProxy for a missing symbol yields a |
| // null proxy rather than failing the lookup. |
| TEST(ProxyTest, RecordProxySymbolStringPtrWeaklyReferencedAbsent) { |
| ExecutionSession ES(cantFail(SelfExecutorProcessControl::Create())); |
| |
| AddOneProxy Call; |
| cantFail(lookupAndApply( |
| ES.getBootstrapJITDylib(), |
| {recordProxy<AddOneSpec>(&Call, MangleAndInterner(ES)(AddOneSpec::Name), |
| SymbolLookupFlags::WeaklyReferencedSymbol)})); |
| EXPECT_FALSE(static_cast<bool>(Call)); |
| |
| cantFail(ES.endSession()); |
| } |
| |
| // A callee returning Error delivers its result as Error (not Expected<Error>), |
| // through both call operators, for both success and failure. |
| TEST(ProxyTest, ErrorReturn) { |
| ExecutionSession ES(cantFail(SelfExecutorProcessControl::Create())); |
| |
| MaybeFailProxy Call(MaybeFailDispatch, ExecutorAddr::fromPtr(maybeFail)); |
| |
| EXPECT_THAT_ERROR(Call(ES, false), Succeeded()); |
| EXPECT_THAT_ERROR(Call(ES, true), Failed()); |
| |
| std::promise<MSVCPError> P; |
| auto F = P.get_future(); |
| Call([&](Error E) { P.set_value(std::move(E)); }, ES, true); |
| EXPECT_THAT_ERROR(Error(F.get()), Failed()); |
| |
| cantFail(ES.endSession()); |
| } |
| |
| // A callee returning Expected<T> delivers its result flattened as Expected<T> |
| // (not Expected<Expected<T>>): the callee's value or error passes through |
| // directly. |
| TEST(ProxyTest, ExpectedReturn) { |
| ExecutionSession ES(cantFail(SelfExecutorProcessControl::Create())); |
| |
| AddOneOrFailProxy Call(AddOneOrFailDispatch, |
| ExecutorAddr::fromPtr(addOneOrFail)); |
| |
| Expected<int32_t> R = Call(ES, 41); |
| ASSERT_THAT_EXPECTED(R, Succeeded()); |
| EXPECT_EQ(*R, 42); |
| |
| EXPECT_THAT_EXPECTED(Call(ES, -1), Failed()); |
| |
| std::promise<MSVCPExpected<int32_t>> P; |
| auto F = P.get_future(); |
| Call([&](Expected<int32_t> RA) { P.set_value(std::move(RA)); }, ES, 41); |
| Expected<int32_t> RAsync = F.get(); |
| ASSERT_THAT_EXPECTED(RAsync, Succeeded()); |
| EXPECT_EQ(*RAsync, 42); |
| |
| cantFail(ES.endSession()); |
| } |