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