1 // Copyright 2021 The Android Open Source Project
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 // Copyright 2017 The Fuchsia Authors. All rights reserved.
16 // Use of this source code is governed by a BSD-style license that can be
17 // found in the LICENSE file.
18
19 #pragma once
20
21 #include <stddef.h>
22 #include <stdlib.h>
23
24 #include <memory>
25
26 #include "Nullable.h"
27
28 #include <new>
29 #include <type_traits>
30 #include <utility>
31
32 namespace gfxstream::guest {
33 namespace fit {
34 namespace internal {
35
36 template <typename Result, typename... Args>
37 struct target_ops final {
38 const void* (*target_type_id)(void* bits, const void* impl_ops);
39 void* (*get)(void* bits);
40 Result (*invoke)(void* bits, Args... args);
41 void (*move)(void* from_bits, void* to_bits);
42 void (*destroy)(void* bits);
43 };
44
45 template <typename Callable, bool is_inline, bool is_shared, typename Result, typename... Args>
46 struct target;
47
unshared_target_type_id(void * bits,const void * impl_ops)48 inline const void* unshared_target_type_id(void* bits, const void* impl_ops) {
49 return impl_ops;
50 }
51
52 // vtable for nullptr (empty target function)
53
54 template <typename Result, typename... Args>
55 struct target<decltype(nullptr),
56 /*is_inline=*/true,
57 /*is_shared=*/false,
58 Result,
59 Args...>
60 final {
61 static Result invoke(void* bits, Args... args) { __builtin_abort(); }
62
63 static const target_ops<Result, Args...> ops;
64 };
65
66 inline void* null_target_get(void* bits) {
67 return nullptr;
68 }
69 inline void null_target_move(void* from_bits, void* to_bits) {}
70 inline void null_target_destroy(void* bits) {}
71
72 template <typename Result, typename... Args>
73 constexpr target_ops<Result, Args...> target<decltype(nullptr),
74 /*is_inline=*/true,
75 /*is_shared=*/false,
76 Result,
77 Args...>::ops = {
78 &unshared_target_type_id, &null_target_get, &target::invoke, &null_target_move,
79 &null_target_destroy};
80
81 // vtable for inline target function
82
83 template <typename Callable, typename Result, typename... Args>
84 struct target<Callable,
85 /*is_inline=*/true,
86 /*is_shared=*/false,
87 Result,
88 Args...>
89 final {
90 template <typename Callable_>
91 static void initialize(void* bits, Callable_&& target) {
92 new (bits) Callable(std::forward<Callable_>(target));
93 }
94 static Result invoke(void* bits, Args... args) {
95 auto& target = *static_cast<Callable*>(bits);
96 return target(std::forward<Args>(args)...);
97 }
98 static void move(void* from_bits, void* to_bits) {
99 auto& from_target = *static_cast<Callable*>(from_bits);
100 new (to_bits) Callable(std::move(from_target));
101 from_target.~Callable();
102 }
103 static void destroy(void* bits) {
104 auto& target = *static_cast<Callable*>(bits);
105 target.~Callable();
106 }
107
108 static const target_ops<Result, Args...> ops;
109 };
110
111 inline void* inline_target_get(void* bits) {
112 return bits;
113 }
114
115 template <typename Callable, typename Result, typename... Args>
116 constexpr target_ops<Result, Args...> target<Callable,
117 /*is_inline=*/true,
118 /*is_shared=*/false,
119 Result,
120 Args...>::ops = {
121 &unshared_target_type_id, &inline_target_get, &target::invoke, &target::move, &target::destroy};
122
123 // vtable for pointer to target function
124
125 template <typename Callable, typename Result, typename... Args>
126 struct target<Callable,
127 /*is_inline=*/false,
128 /*is_shared=*/false,
129 Result,
130 Args...>
131 final {
132 template <typename Callable_>
133 static void initialize(void* bits, Callable_&& target) {
134 auto ptr = static_cast<Callable**>(bits);
135 *ptr = new Callable(std::forward<Callable_>(target));
136 }
137 static Result invoke(void* bits, Args... args) {
138 auto& target = **static_cast<Callable**>(bits);
139 return target(std::forward<Args>(args)...);
140 }
141 static void move(void* from_bits, void* to_bits) {
142 auto from_ptr = static_cast<Callable**>(from_bits);
143 auto to_ptr = static_cast<Callable**>(to_bits);
144 *to_ptr = *from_ptr;
145 }
146 static void destroy(void* bits) {
147 auto ptr = static_cast<Callable**>(bits);
148 delete *ptr;
149 }
150
151 static const target_ops<Result, Args...> ops;
152 };
153
154 inline void* heap_target_get(void* bits) {
155 return *static_cast<void**>(bits);
156 }
157
158 template <typename Callable, typename Result, typename... Args>
159 constexpr target_ops<Result, Args...> target<Callable,
160 /*is_inline=*/false,
161 /*is_shared=*/false,
162 Result,
163 Args...>::ops = {
164 &unshared_target_type_id, &heap_target_get, &target::invoke, &target::move, &target::destroy};
165
166 // vtable for fit::function std::shared_ptr to target function
167
168 template <typename SharedFunction>
169 const void* get_target_type_id(const SharedFunction& function_or_callback) {
170 return function_or_callback.target_type_id();
171 }
172
173 // For this vtable,
174 // Callable by definition will be either a fit::function or fit::callback
175 template <typename SharedFunction, typename Result, typename... Args>
176 struct target<SharedFunction,
177 /*is_inline=*/false,
178 /*is_shared=*/true,
179 Result,
180 Args...>
181 final {
182 static void initialize(void* bits, SharedFunction target) {
183 new (bits) std::shared_ptr<SharedFunction>(
184 std::move(std::make_shared<SharedFunction>(std::move(target))));
185 }
186 static void copy_shared_ptr(void* from_bits, void* to_bits) {
187 auto& from_shared_ptr = *static_cast<std::shared_ptr<SharedFunction>*>(from_bits);
188 new (to_bits) std::shared_ptr<SharedFunction>(from_shared_ptr);
189 }
190 static const void* target_type_id(void* bits, const void* impl_ops) {
191 auto& function_or_callback = **static_cast<std::shared_ptr<SharedFunction>*>(bits);
192 return gfxstream::guest::fit::internal::get_target_type_id(function_or_callback);
193 }
194 static void* get(void* bits) {
195 auto& function_or_callback = **static_cast<std::shared_ptr<SharedFunction>*>(bits);
196 return function_or_callback.template target<SharedFunction>(
197 /*check=*/false); // void* will fail the check
198 }
199 static Result invoke(void* bits, Args... args) {
200 auto& function_or_callback = **static_cast<std::shared_ptr<SharedFunction>*>(bits);
201 return function_or_callback(std::forward<Args>(args)...);
202 }
203 static void move(void* from_bits, void* to_bits) {
204 auto from_shared_ptr = std::move(*static_cast<std::shared_ptr<SharedFunction>*>(from_bits));
205 new (to_bits) std::shared_ptr<SharedFunction>(std::move(from_shared_ptr));
206 }
207 static void destroy(void* bits) {
208 static_cast<std::shared_ptr<SharedFunction>*>(bits)->reset();
209 }
210
211 static const target_ops<Result, Args...> ops;
212 };
213
214 template <typename SharedFunction, typename Result, typename... Args>
215 constexpr target_ops<Result, Args...> target<SharedFunction,
216 /*is_inline=*/false,
217 /*is_shared=*/true,
218 Result,
219 Args...>::ops = {
220 &target::target_type_id, &target::get, &target::invoke, &target::move, &target::destroy};
221
222 template <size_t inline_target_size, bool requireInline, typename Result, typename... Args>
223 class function_base;
224
225 // Function implementation details.
226 // See |fit::function| and |fit::callback| documentation for more information.
227 template <size_t inline_target_size, bool requireInline, typename Result, typename... Args>
228 class function_base<inline_target_size, requireInline, Result(Args...)> {
229 using ops_type = const target_ops<Result, Args...>*;
230 using storage_type = typename std::aligned_storage<(
231 inline_target_size >= sizeof(void*) ? inline_target_size : sizeof(void*))>::
232 type; // avoid including <algorithm> for max
233 template <typename Callable>
234 using target_type = target<Callable,
235 (sizeof(Callable) <= sizeof(storage_type)),
236 /*is_shared=*/false,
237 Result,
238 Args...>;
239 template <typename SharedFunction>
240 using shared_target_type = target<SharedFunction,
241 /*is_inline=*/false,
242 /*is_shared=*/true,
243 Result,
244 Args...>;
245 using null_target_type = target_type<decltype(nullptr)>;
246
247 protected:
248 using result_type = Result;
249
250 function_base() { initialize_null_target(); }
251
252 function_base(decltype(nullptr)) { initialize_null_target(); }
253
254 function_base(Result (*target)(Args...)) { initialize_target(target); }
255
256 template <typename Callable,
257 typename = std::enable_if_t<
258 std::is_convertible<decltype(std::declval<Callable&>()(std::declval<Args>()...)),
259 result_type>::value>>
260 function_base(Callable&& target) {
261 initialize_target(std::forward<Callable>(target));
262 }
263
264 function_base(function_base&& other) { move_target_from(std::move(other)); }
265
266 ~function_base() { destroy_target(); }
267
268 // Returns true if the function has a non-empty target.
269 explicit operator bool() const { return ops_->get(&bits_) != nullptr; }
270
271 // Returns a pointer to the function's target.
272 // If |check| is true (the default), the function _may_ abort if the
273 // caller tries to assign the target to a varible of the wrong type. (This
274 // check is currently skipped for share()d objects.)
275 // Note the shared pointer vtable must set |check| to false to assign the
276 // target to |void*|.
277 template <typename Callable>
278 Callable* target(bool check = true) {
279 if (check)
280 check_target_type<Callable>();
281 return static_cast<Callable*>(ops_->get(&bits_));
282 }
283
284 // Returns a pointer to the function's target (const version).
285 // If |check| is true (the default), the function _may_ abort if the
286 // caller tries to assign the target to a varible of the wrong type. (This
287 // check is currently skipped for share()d objects.)
288 // Note the shared pointer vtable must set |check| to false to assign the
289 // target to |void*|.
290 template <typename Callable>
291 const Callable* target(bool check = true) const {
292 if (check)
293 check_target_type<Callable>();
294 return static_cast<Callable*>(ops_->get(&bits_));
295 }
296
297 // Used by the derived "impl" classes to implement share().
298 //
299 // The caller creates a new object of the same type as itself, and passes in
300 // the empty object. This function first checks if |this| is already shared,
301 // and if not, creates a new version of itself containing a
302 // |std::shared_ptr| to its original self, and updates |ops_| to the vtable
303 // for the shared version.
304 //
305 // Then it copies its |shared_ptr| to the |bits_| of the given |copy|,
306 // and assigns the same shared pointer vtable to the copy's |ops_|.
307 //
308 // The target itself is not copied; it is moved to the heap and its
309 // lifetime is extended until all references have been released.
310 //
311 // Note: This method is not supported on |fit::InlineFunction<>|
312 // because it may incur a heap allocation which is contrary to
313 // the stated purpose of |fit::InlineFunction<>|.
314 template <typename SharedFunction>
315 void share_with(SharedFunction& copy) {
316 static_assert(!requireInline, "Inline functions cannot be shared.");
317 if (ops_->get(&bits_) != nullptr) {
318 if (ops_ != &shared_target_type<SharedFunction>::ops) {
319 convert_to_shared_target<SharedFunction>();
320 }
321 copy_shared_target_to(copy);
322 }
323 }
324
325 // Used by derived "impl" classes to implement operator()().
326 // Invokes the function's target.
327 // Note that fit::callback will release the target immediately after
328 // invoke() (also affecting any share()d copies).
329 // Aborts if the function's target is empty.
330 Result invoke(Args... args) const { return ops_->invoke(&bits_, std::forward<Args>(args)...); }
331
332 // Used by derived "impl" classes to implement operator=().
333 // Assigns an empty target.
334 void assign(decltype(nullptr)) {
335 destroy_target();
336 initialize_null_target();
337 }
338
339 // Used by derived "impl" classes to implement operator=().
340 // Assigns the function's target.
341 // If target == nullptr, assigns an empty target.
342 template <typename Callable,
343 typename = std::enable_if_t<
344 std::is_convertible<decltype(std::declval<Callable&>()(std::declval<Args>()...)),
345 result_type>::value>>
346 void assign(Callable&& target) {
347 destroy_target();
348 initialize_target(std::forward<Callable>(target));
349 }
350
351 // Used by derived "impl" classes to implement operator=().
352 // Assigns the function with a target moved from another function,
353 // leaving the other function with an empty target.
354 void assign(function_base&& other) {
355 destroy_target();
356 move_target_from(std::move(other));
357 }
358
359 void swap(function_base& other) {
360 if (&other == this)
361 return;
362 ops_type temp_ops = ops_;
363 storage_type temp_bits;
364 ops_->move(&bits_, &temp_bits);
365
366 ops_ = other.ops_;
367 other.ops_->move(&other.bits_, &bits_);
368
369 other.ops_ = temp_ops;
370 temp_ops->move(&temp_bits, &other.bits_);
371 }
372
373 // returns an opaque ID unique to the |Callable| type of the target.
374 // Used by check_target_type.
375 const void* target_type_id() const { return ops_->target_type_id(&bits_, ops_); }
376
377 // Deleted copy constructor and assign. |function_base| implementations are
378 // move-only.
379 function_base(const function_base& other) = delete;
380 function_base& operator=(const function_base& other) = delete;
381
382 // Move assignment must be provided by subclasses.
383 function_base& operator=(function_base&& other) = delete;
384
385 private:
386 // Implements the move operation, used by move construction and move
387 // assignment. Leaves other target initialized to null.
388 void move_target_from(function_base&& other) {
389 ops_ = other.ops_;
390 other.ops_->move(&other.bits_, &bits_);
391 other.initialize_null_target();
392 }
393
394 // fit::function and fit::callback are not directly copyable, but share()
395 // will create shared references to the original object. This method
396 // implements the copy operation for the |std::shared_ptr| wrapper.
397 template <typename SharedFunction>
398 void copy_shared_target_to(SharedFunction& copy) {
399 copy.destroy_target();
400 assert(ops_ == &shared_target_type<SharedFunction>::ops);
401 shared_target_type<SharedFunction>::copy_shared_ptr(&bits_, ©.bits_);
402 copy.ops_ = ops_;
403 }
404
405 // assumes target is uninitialized
406 void initialize_null_target() { ops_ = &null_target_type::ops; }
407
408 // target may or may not be initialized.
409 template <typename Callable>
410 void initialize_target(Callable&& target) {
411 // Convert function or function references to function pointer.
412 using DecayedCallable = std::decay_t<Callable>;
413 static_assert(
414 std::alignment_of<DecayedCallable>::value <= std::alignment_of<storage_type>::value,
415 "Alignment of Callable must be <= alignment of max_align_t.");
416 static_assert(!requireInline || sizeof(DecayedCallable) <= inline_target_size,
417 "Callable too large to store inline as requested.");
418 if (is_null(target)) {
419 initialize_null_target();
420 } else {
421 ops_ = &target_type<DecayedCallable>::ops;
422 target_type<DecayedCallable>::initialize(&bits_, std::forward<Callable>(target));
423 }
424 }
425
426 // assumes target is uninitialized
427 template <typename SharedFunction>
428 void convert_to_shared_target() {
429 shared_target_type<SharedFunction>::initialize(
430 &bits_, std::move(*static_cast<SharedFunction*>(this)));
431 ops_ = &shared_target_type<SharedFunction>::ops;
432 }
433
434 // leaves target uninitialized
435 void destroy_target() { ops_->destroy(&bits_); }
436
437 // Called by target() if |check| is true.
438 // Checks the template parameter, usually inferred from the context of
439 // the call to target(), and aborts the program if it can determine that
440 // the Callable type is not compatible with the function's Result and Args.
441 template <typename Callable>
442 void check_target_type() const {
443 if (target_type<Callable>::ops.target_type_id(nullptr, &target_type<Callable>::ops) !=
444 target_type_id()) {
445 __builtin_abort();
446 }
447 }
448
449 ops_type ops_;
450 mutable storage_type bits_;
451 };
452
453 } // namespace internal
454
455 } // namespace fit
456 } // namespace gfxstream::guest
457