• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_, &copy.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