• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2021 gRPC authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #ifndef GRPC_SRC_CORE_LIB_PROMISE_ARENA_PROMISE_H
16 #define GRPC_SRC_CORE_LIB_PROMISE_ARENA_PROMISE_H
17 
18 #include <grpc/support/port_platform.h>
19 #include <stdlib.h>
20 
21 #include <cstddef>
22 #include <memory>
23 #include <type_traits>
24 #include <utility>
25 
26 #include "absl/meta/type_traits.h"
27 #include "src/core/lib/promise/context.h"
28 #include "src/core/lib/promise/poll.h"
29 #include "src/core/lib/resource_quota/arena.h"
30 #include "src/core/util/construct_destruct.h"
31 
32 namespace grpc_core {
33 
34 namespace arena_promise_detail {
35 
36 struct ArgType {
37   alignas(std::max_align_t) char buffer[sizeof(void*)];
38 };
39 
40 template <typename T>
ArgAsPtr(ArgType * arg)41 T*& ArgAsPtr(ArgType* arg) {
42   static_assert(sizeof(ArgType) >= sizeof(T**),
43                 "Must have ArgType of at least one pointer size");
44   return *reinterpret_cast<T**>(arg);
45 }
46 
47 template <typename T>
48 struct Vtable {
49   // Poll the promise, once.
50   Poll<T> (*poll_once)(ArgType* arg);
51   // Destroy the underlying callable object if there is one.
52   // Since we don't delete (the arena owns the memory) but we may need to call a
53   // destructor, we expose this for when the ArenaPromise object is destroyed.
54   void (*destroy)(ArgType* arg);
55 };
56 
57 template <typename T>
58 struct VtableAndArg {
59   const Vtable<T>* vtable;
60   ArgType arg;
61 };
62 
63 // Implementation of Vtable for an empty object.
64 // Used when an empty ArenaPromise is created, or when the ArenaPromise is moved
65 // from. Since in either case these objects should not be polled, we simply
66 // crash if it is.
67 template <typename T>
68 struct Null {
69   static const Vtable<T> vtable;
70 
PollOnceNull71   static Poll<T> PollOnce(ArgType*) {
72     abort();
73     GPR_UNREACHABLE_CODE(return Pending{});
74   }
75 
DestroyNull76   static void Destroy(ArgType*) {}
77 };
78 
79 template <typename T>
80 const Vtable<T> Null<T>::vtable = {PollOnce, Destroy};
81 
82 // Implementation of ImplInterface for a callable object.
83 template <typename T, typename Callable>
84 struct AllocatedCallable {
85   static const Vtable<T> vtable;
86 
PollOnceAllocatedCallable87   static Poll<T> PollOnce(ArgType* arg) {
88     return poll_cast<T>((*ArgAsPtr<Callable>(arg))());
89   }
90 
DestroyAllocatedCallable91   static void Destroy(ArgType* arg) { Destruct(ArgAsPtr<Callable>(arg)); }
92 };
93 
94 template <typename T, typename Callable>
95 const Vtable<T> AllocatedCallable<T, Callable>::vtable = {PollOnce, Destroy};
96 
97 // Implementation of ImplInterface for a small callable object (one that fits
98 // within the ArgType arg)
99 template <typename T, typename Callable>
100 struct Inlined {
101   static const Vtable<T> vtable;
102 
PollOnceInlined103   static Poll<T> PollOnce(ArgType* arg) {
104     return poll_cast<T>((*reinterpret_cast<Callable*>(arg))());
105   }
106 
DestroyInlined107   static void Destroy(ArgType* arg) {
108     Destruct(reinterpret_cast<Callable*>(arg));
109   }
110 };
111 
112 template <typename T, typename Callable>
113 const Vtable<T> Inlined<T, Callable>::vtable = {PollOnce, Destroy};
114 
115 // If a callable object is empty we can substitute any instance of that callable
116 // for the one we call (for how could we tell the difference)?
117 // Since this corresponds to a lambda with no fields, and we expect these to be
118 // reasonably common, we can elide the arena allocation entirely and simply poll
119 // a global shared instance.
120 // (this comes up often when the promise only accesses context data from the
121 // containing activity).
122 template <typename T, typename Callable>
123 struct SharedCallable {
124   static const Vtable<T> vtable;
125 
PollOnceSharedCallable126   static Poll<T> PollOnce(ArgType* arg) {
127     return (*reinterpret_cast<Callable*>(arg))();
128   }
129 };
130 
131 template <typename T, typename Callable>
132 const Vtable<T> SharedCallable<T, Callable>::vtable = {PollOnce,
133                                                        Null<T>::Destroy};
134 
135 // Redirector type: given a callable type, expose a Make() function that creates
136 // the appropriate underlying implementation.
137 template <typename T, typename Callable, typename Ignored = void>
138 struct ChooseImplForCallable;
139 
140 template <typename T, typename Callable>
141 struct ChooseImplForCallable<
142     T, Callable,
143     absl::enable_if_t<!std::is_empty<Callable>::value &&
144                       (sizeof(Callable) > sizeof(ArgType))>> {
145   static void Make(Callable&& callable, VtableAndArg<T>* out) {
146     out->vtable = &AllocatedCallable<T, Callable>::vtable;
147     ArgAsPtr<Callable>(&out->arg) = GetContext<Arena>()->template New<Callable>(
148         std::forward<Callable>(callable));
149   }
150 };
151 
152 template <typename T, typename Callable>
153 struct ChooseImplForCallable<
154     T, Callable,
155     absl::enable_if_t<!std::is_empty<Callable>::value &&
156                       (sizeof(Callable) <= sizeof(ArgType))>> {
157   static void Make(Callable&& callable, VtableAndArg<T>* out) {
158     out->vtable = &Inlined<T, Callable>::vtable;
159     Construct(reinterpret_cast<Callable*>(&out->arg),
160               std::forward<Callable>(callable));
161   }
162 };
163 
164 template <typename T, typename Callable>
165 struct ChooseImplForCallable<
166     T, Callable, absl::enable_if_t<std::is_empty<Callable>::value>> {
167   static void Make(Callable&&, VtableAndArg<T>* out) {
168     out->vtable = &SharedCallable<T, Callable>::vtable;
169   }
170 };
171 
172 // Wrap ChooseImplForCallable with a friend approachable syntax.
173 template <typename T, typename Callable>
174 void MakeImplForCallable(Callable&& callable, VtableAndArg<T>* out) {
175   ChooseImplForCallable<T, Callable>::Make(std::forward<Callable>(callable),
176                                            out);
177 }
178 
179 }  // namespace arena_promise_detail
180 
181 // A promise for which the state memory is allocated from an arena.
182 template <typename T>
183 class ArenaPromise {
184  public:
185   // Construct an empty, uncallable, invalid ArenaPromise.
186   ArenaPromise() = default;
187 
188   // Construct an ArenaPromise that will call the given callable when polled.
189   template <typename Callable,
190             typename Ignored =
191                 absl::enable_if_t<!std::is_same<Callable, ArenaPromise>::value>>
192   // NOLINTNEXTLINE(google-explicit-constructor)
193   ArenaPromise(Callable&& callable) {
194     arena_promise_detail::MakeImplForCallable(std::forward<Callable>(callable),
195                                               &vtable_and_arg_);
196   }
197 
198   // ArenaPromise is not copyable.
199   ArenaPromise(const ArenaPromise&) = delete;
200   ArenaPromise& operator=(const ArenaPromise&) = delete;
201   // ArenaPromise is movable.
202   ArenaPromise(ArenaPromise&& other) noexcept
203       : vtable_and_arg_(other.vtable_and_arg_) {
204     other.vtable_and_arg_.vtable = &arena_promise_detail::Null<T>::vtable;
205   }
206   ArenaPromise& operator=(ArenaPromise&& other) noexcept {
207     vtable_and_arg_.vtable->destroy(&vtable_and_arg_.arg);
208     vtable_and_arg_ = other.vtable_and_arg_;
209     other.vtable_and_arg_.vtable = &arena_promise_detail::Null<T>::vtable;
210     return *this;
211   }
212 
213   // Destruction => call Destroy on the underlying impl object.
214   ~ArenaPromise() { vtable_and_arg_.vtable->destroy(&vtable_and_arg_.arg); }
215 
216   // Expose the promise interface: a call operator that returns Poll<T>.
217   Poll<T> operator()() {
218     return vtable_and_arg_.vtable->poll_once(&vtable_and_arg_.arg);
219   }
220 
221   bool has_value() const {
222     return vtable_and_arg_.vtable != &arena_promise_detail::Null<T>::vtable;
223   }
224 
225  private:
226   // Underlying impl object.
227   arena_promise_detail::VtableAndArg<T> vtable_and_arg_ = {
228       &arena_promise_detail::Null<T>::vtable, {}};
229 };
230 
231 }  // namespace grpc_core
232 
233 #endif  // GRPC_SRC_CORE_LIB_PROMISE_ARENA_PROMISE_H
234