// Copyright 2021 gRPC authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef GRPC_CORE_LIB_PROMISE_ARENA_PROMISE_H #define GRPC_CORE_LIB_PROMISE_ARENA_PROMISE_H #include #include #include #include #include "absl/meta/type_traits.h" #include "src/core/lib/promise/context.h" #include "src/core/lib/promise/poll.h" #include "src/core/lib/resource_quota/arena.h" namespace grpc_core { namespace arena_promise_detail { template struct Vtable { // Poll the promise, once. Poll (*poll_once)(void** arg); // Destroy the underlying callable object if there is one. // Since we don't delete (the arena owns the memory) but we may need to call a // destructor, we expose this for when the ArenaPromise object is destroyed. void (*destroy)(void** arg); }; template struct VtableAndArg { const Vtable* vtable; void* arg; }; // Implementation of Vtable for an empty object. // Used when an empty ArenaPromise is created, or when the ArenaPromise is moved // from. Since in either case these objects should not be polled, we simply // crash if it is. template inline const Vtable* null_impl() { static const Vtable vtable = {[](void**) -> Poll { abort(); GPR_UNREACHABLE_CODE(return Pending{}); }, [](void**) {}}; return &vtable; } // Implementation of ImplInterface for a callable object. template inline const Vtable* allocated_callable_impl() { static const Vtable vtable = { [](void** arg) -> Poll { return poll_cast((*static_cast(*arg))()); }, [](void** arg) { static_cast(*arg)->~Callable(); }}; return &vtable; } // Implementation of ImplInterface for a small callable object (one that fits // within the void* arg) template inline const Vtable* inlined_callable_impl() { static const Vtable vtable = { [](void** arg) -> Poll { return poll_cast((*reinterpret_cast(arg))()); }, [](void** arg) { reinterpret_cast(arg)->~Callable(); }}; return &vtable; } // If a callable object is empty we can substitute any instance of that callable // for the one we call (for how could we tell the difference)? // Since this corresponds to a lambda with no fields, and we expect these to be // reasonably common, we can elide the arena allocation entirely and simply poll // a global shared instance. // (this comes up often when the promise only accesses context data from the // containing activity). template inline const Vtable* shared_callable_impl(Callable&& callable) { static Callable instance = std::forward(callable); static const Vtable vtable = {[](void**) -> Poll { return instance(); }, [](void**) {}}; return &vtable; } // Redirector type: given a callable type, expose a Make() function that creates // the appropriate underlying implementation. template struct ChooseImplForCallable; template struct ChooseImplForCallable< T, Callable, absl::enable_if_t::value && (sizeof(Callable) > sizeof(void*))>> { static void Make(Callable&& callable, VtableAndArg* out) { *out = {allocated_callable_impl(), GetContext()->template New( std::forward(callable))}; } }; template struct ChooseImplForCallable< T, Callable, absl::enable_if_t::value && (sizeof(Callable) <= sizeof(void*))>> { static void Make(Callable&& callable, VtableAndArg* out) { out->vtable = inlined_callable_impl(); new (&out->arg) Callable(std::forward(callable)); } }; template struct ChooseImplForCallable< T, Callable, absl::enable_if_t::value>> { static void Make(Callable&& callable, VtableAndArg* out) { out->vtable = shared_callable_impl(std::forward(callable)); } }; // Wrap ChooseImplForCallable with a friend approachable syntax. template void MakeImplForCallable(Callable&& callable, VtableAndArg* out) { ChooseImplForCallable::Make(std::forward(callable), out); } } // namespace arena_promise_detail // A promise for which the state memory is allocated from an arena. template class ArenaPromise { public: // Construct an empty, uncallable, invalid ArenaPromise. ArenaPromise() = default; // Construct an ArenaPromise that will call the given callable when polled. template ::value>> // NOLINTNEXTLINE(google-explicit-constructor) ArenaPromise(Callable&& callable) { arena_promise_detail::MakeImplForCallable( std::forward(callable), &vtable_and_arg_); } // ArenaPromise is not copyable. ArenaPromise(const ArenaPromise&) = delete; ArenaPromise& operator=(const ArenaPromise&) = delete; // ArenaPromise is movable. ArenaPromise(ArenaPromise&& other) noexcept : vtable_and_arg_(other.vtable_and_arg_) { other.vtable_and_arg_.vtable = arena_promise_detail::null_impl(); } ArenaPromise& operator=(ArenaPromise&& other) noexcept { vtable_and_arg_.vtable->destroy(&vtable_and_arg_.arg); vtable_and_arg_ = other.vtable_and_arg_; other.vtable_and_arg_.vtable = arena_promise_detail::null_impl(); return *this; } // Destruction => call Destroy on the underlying impl object. ~ArenaPromise() { vtable_and_arg_.vtable->destroy(&vtable_and_arg_.arg); } // Expose the promise interface: a call operator that returns Poll. Poll operator()() { return vtable_and_arg_.vtable->poll_once(&vtable_and_arg_.arg); } bool has_value() const { return vtable_and_arg_.vtable != arena_promise_detail::null_impl(); } private: // Underlying impl object. arena_promise_detail::VtableAndArg vtable_and_arg_ = { arena_promise_detail::null_impl(), nullptr}; }; } // namespace grpc_core #endif /* GRPC_CORE_LIB_PROMISE_ARENA_PROMISE_H */