• 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 
20 #include <stdlib.h>
21 
22 #include <cstddef>
23 #include <memory>
24 #include <type_traits>
25 #include <utility>
26 
27 #include "absl/meta/type_traits.h"
28 
29 #include "src/core/lib/gprpp/construct_destruct.h"
30 #include "src/core/lib/promise/context.h"
31 #include "src/core/lib/promise/poll.h"
32 #include "src/core/lib/resource_quota/arena.h"
33 
34 namespace grpc_core {
35 
36 namespace arena_promise_detail {
37 
38 struct ArgType {
39   alignas(std::max_align_t) char buffer[sizeof(void*)];
40 };
41 
42 template <typename T>
ArgAsPtr(ArgType * arg)43 T*& ArgAsPtr(ArgType* arg) {
44   static_assert(sizeof(ArgType) >= sizeof(T**),
45                 "Must have ArgType of at least one pointer size");
46   return *reinterpret_cast<T**>(arg);
47 }
48 
49 template <typename T>
50 struct Vtable {
51   // Poll the promise, once.
52   Poll<T> (*poll_once)(ArgType* arg);
53   // Destroy the underlying callable object if there is one.
54   // Since we don't delete (the arena owns the memory) but we may need to call a
55   // destructor, we expose this for when the ArenaPromise object is destroyed.
56   void (*destroy)(ArgType* arg);
57 };
58 
59 template <typename T>
60 struct VtableAndArg {
61   const Vtable<T>* vtable;
62   ArgType arg;
63 };
64 
65 // Implementation of Vtable for an empty object.
66 // Used when an empty ArenaPromise is created, or when the ArenaPromise is moved
67 // from. Since in either case these objects should not be polled, we simply
68 // crash if it is.
69 template <typename T>
70 struct Null {
71   static const Vtable<T> vtable;
72 
PollOnceNull73   static Poll<T> PollOnce(ArgType*) {
74     abort();
75     GPR_UNREACHABLE_CODE(return Pending{});
76   }
77 
DestroyNull78   static void Destroy(ArgType*) {}
79 };
80 
81 template <typename T>
82 const Vtable<T> Null<T>::vtable = {PollOnce, Destroy};
83 
84 // Implementation of ImplInterface for a callable object.
85 template <typename T, typename Callable>
86 struct AllocatedCallable {
87   static const Vtable<T> vtable;
88 
PollOnceAllocatedCallable89   static Poll<T> PollOnce(ArgType* arg) {
90     return poll_cast<T>((*ArgAsPtr<Callable>(arg))());
91   }
92 
DestroyAllocatedCallable93   static void Destroy(ArgType* arg) { Destruct(ArgAsPtr<Callable>(arg)); }
94 };
95 
96 template <typename T, typename Callable>
97 const Vtable<T> AllocatedCallable<T, Callable>::vtable = {PollOnce, Destroy};
98 
99 // Implementation of ImplInterface for a small callable object (one that fits
100 // within the ArgType arg)
101 template <typename T, typename Callable>
102 struct Inlined {
103   static const Vtable<T> vtable;
104 
PollOnceInlined105   static Poll<T> PollOnce(ArgType* arg) {
106     return poll_cast<T>((*reinterpret_cast<Callable*>(arg))());
107   }
108 
DestroyInlined109   static void Destroy(ArgType* arg) {
110     Destruct(reinterpret_cast<Callable*>(arg));
111   }
112 };
113 
114 template <typename T, typename Callable>
115 const Vtable<T> Inlined<T, Callable>::vtable = {PollOnce, Destroy};
116 
117 // If a callable object is empty we can substitute any instance of that callable
118 // for the one we call (for how could we tell the difference)?
119 // Since this corresponds to a lambda with no fields, and we expect these to be
120 // reasonably common, we can elide the arena allocation entirely and simply poll
121 // a global shared instance.
122 // (this comes up often when the promise only accesses context data from the
123 // containing activity).
124 template <typename T, typename Callable>
125 struct SharedCallable {
126   static const Vtable<T> vtable;
127 
PollOnceSharedCallable128   static Poll<T> PollOnce(ArgType* arg) {
129     return (*reinterpret_cast<Callable*>(arg))();
130   }
131 };
132 
133 template <typename T, typename Callable>
134 const Vtable<T> SharedCallable<T, Callable>::vtable = {PollOnce,
135                                                        Null<T>::Destroy};
136 
137 // Redirector type: given a callable type, expose a Make() function that creates
138 // the appropriate underlying implementation.
139 template <typename T, typename Callable, typename Ignored = void>
140 struct ChooseImplForCallable;
141 
142 template <typename T, typename Callable>
143 struct ChooseImplForCallable<
144     T, Callable,
145     absl::enable_if_t<!std::is_empty<Callable>::value &&
146                       (sizeof(Callable) > sizeof(ArgType))>> {
147   static void Make(Callable&& callable, VtableAndArg<T>* out) {
148     out->vtable = &AllocatedCallable<T, Callable>::vtable;
149     ArgAsPtr<Callable>(&out->arg) = GetContext<Arena>()->template New<Callable>(
150         std::forward<Callable>(callable));
151   }
152 };
153 
154 template <typename T, typename Callable>
155 struct ChooseImplForCallable<
156     T, Callable,
157     absl::enable_if_t<!std::is_empty<Callable>::value &&
158                       (sizeof(Callable) <= sizeof(ArgType))>> {
159   static void Make(Callable&& callable, VtableAndArg<T>* out) {
160     out->vtable = &Inlined<T, Callable>::vtable;
161     Construct(reinterpret_cast<Callable*>(&out->arg),
162               std::forward<Callable>(callable));
163   }
164 };
165 
166 template <typename T, typename Callable>
167 struct ChooseImplForCallable<
168     T, Callable, absl::enable_if_t<std::is_empty<Callable>::value>> {
169   static void Make(Callable&&, VtableAndArg<T>* out) {
170     out->vtable = &SharedCallable<T, Callable>::vtable;
171   }
172 };
173 
174 // Wrap ChooseImplForCallable with a friend approachable syntax.
175 template <typename T, typename Callable>
176 void MakeImplForCallable(Callable&& callable, VtableAndArg<T>* out) {
177   ChooseImplForCallable<T, Callable>::Make(std::forward<Callable>(callable),
178                                            out);
179 }
180 
181 }  // namespace arena_promise_detail
182 
183 // A promise for which the state memory is allocated from an arena.
184 template <typename T>
185 class ArenaPromise {
186  public:
187   // Construct an empty, uncallable, invalid ArenaPromise.
188   ArenaPromise() = default;
189 
190   // Construct an ArenaPromise that will call the given callable when polled.
191   template <typename Callable,
192             typename Ignored =
193                 absl::enable_if_t<!std::is_same<Callable, ArenaPromise>::value>>
194   // NOLINTNEXTLINE(google-explicit-constructor)
195   ArenaPromise(Callable&& callable) {
196     arena_promise_detail::MakeImplForCallable(std::forward<Callable>(callable),
197                                               &vtable_and_arg_);
198   }
199 
200   // ArenaPromise is not copyable.
201   ArenaPromise(const ArenaPromise&) = delete;
202   ArenaPromise& operator=(const ArenaPromise&) = delete;
203   // ArenaPromise is movable.
204   ArenaPromise(ArenaPromise&& other) noexcept
205       : vtable_and_arg_(other.vtable_and_arg_) {
206     other.vtable_and_arg_.vtable = &arena_promise_detail::Null<T>::vtable;
207   }
208   ArenaPromise& operator=(ArenaPromise&& other) noexcept {
209     vtable_and_arg_.vtable->destroy(&vtable_and_arg_.arg);
210     vtable_and_arg_ = other.vtable_and_arg_;
211     other.vtable_and_arg_.vtable = &arena_promise_detail::Null<T>::vtable;
212     return *this;
213   }
214 
215   // Destruction => call Destroy on the underlying impl object.
216   ~ArenaPromise() { vtable_and_arg_.vtable->destroy(&vtable_and_arg_.arg); }
217 
218   // Expose the promise interface: a call operator that returns Poll<T>.
219   Poll<T> operator()() {
220     return vtable_and_arg_.vtable->poll_once(&vtable_and_arg_.arg);
221   }
222 
223   bool has_value() const {
224     return vtable_and_arg_.vtable != &arena_promise_detail::Null<T>::vtable;
225   }
226 
227  private:
228   // Underlying impl object.
229   arena_promise_detail::VtableAndArg<T> vtable_and_arg_ = {
230       &arena_promise_detail::Null<T>::vtable, {}};
231 };
232 
233 }  // namespace grpc_core
234 
235 #endif  // GRPC_SRC_CORE_LIB_PROMISE_ARENA_PROMISE_H
236