/* * Copyright 2020 The WebRTC Project Authors. All rights reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef RTC_BASE_UNTYPED_FUNCTION_H_ #define RTC_BASE_UNTYPED_FUNCTION_H_ #include #include #include #include #include #include "rtc_base/system/assume.h" namespace webrtc { namespace webrtc_function_impl { using FunVoid = void(); // Inline storage size is this many machine words. enum : size_t { kInlineStorageWords = 4 }; union VoidUnion { void* void_ptr; FunVoid* fun_ptr; typename std::aligned_storage::type inline_storage; }; // Returns the number of elements of the `inline_storage` array required to // store an object of type T. template constexpr size_t InlineStorageSize() { // sizeof(T) / sizeof(uintptr_t), but rounded up. return (sizeof(T) + sizeof(uintptr_t) - 1) / sizeof(uintptr_t); } template struct CallHelpers; template struct CallHelpers { // Return type of the three helpers below. using return_type = RetT; // Complete function type of the three helpers below. using function_type = RetT(VoidUnion*, ArgT...); // Helper for calling the `void_ptr` case of VoidUnion. template static RetT CallVoidPtr(VoidUnion* vu, ArgT... args) { return (*static_cast(vu->void_ptr))(std::forward(args)...); } // Helper for calling the `fun_ptr` case of VoidUnion. static RetT CallFunPtr(VoidUnion* vu, ArgT... args) { return (reinterpret_cast(vu->fun_ptr))( std::forward(args)...); } // Helper for calling the `inline_storage` case of VoidUnion. template static RetT CallInlineStorage(VoidUnion* vu, ArgT... args) { return (*reinterpret_cast(&vu->inline_storage))( std::forward(args)...); } }; } // namespace webrtc_function_impl // A class that holds (and owns) any callable. The same function call signature // must be provided when constructing and calling the object. // // The point of not having the call signature as a class template parameter is // to have one single concrete type for all signatures; this reduces binary // size. class UntypedFunction final { public: // Callables of at most this size can be stored inline, if they are trivial. // (Useful in tests and benchmarks; avoid using this in production code.) enum : size_t { kInlineStorageSize = sizeof(webrtc_function_impl::VoidUnion::inline_storage) }; static_assert(kInlineStorageSize == webrtc_function_impl::kInlineStorageWords * sizeof(uintptr_t), ""); // The *UntypedFunctionArgs structs are used to transfer arguments from // PrepareArgs() to Create(). They are trivial, but may own heap allocations, // so make sure to pass them to Create() exactly once! // // The point of doing Create(PrepareArgs(foo)) instead of just Create(foo) is // to separate the code that has to be inlined (PrepareArgs) from the code // that can be noninlined (Create); the *UntypedFunctionArgs types are // designed to efficiently carry the required information from one to the // other. template struct TrivialUntypedFunctionArgs { static_assert(N >= 1, ""); static_assert(N <= webrtc_function_impl::kInlineStorageWords, ""); // We use an uintptr_t array here instead of std::aligned_storage, because // the former can be efficiently passed in registers when using // TrivialUntypedFunctionArgs as a function argument. (We can't do the same // in VoidUnion, because std::aligned_storage but not uintptr_t can be // legally reinterpret_casted to arbitrary types. // TrivialUntypedFunctionArgs, on the other hand, only needs to handle // placement new and memcpy.) alignas(std::max_align_t) uintptr_t inline_storage[N]; webrtc_function_impl::FunVoid* call; }; struct NontrivialUntypedFunctionArgs { void* void_ptr; webrtc_function_impl::FunVoid* call; void (*del)(webrtc_function_impl::VoidUnion*); }; struct FunctionPointerUntypedFunctionArgs { webrtc_function_impl::FunVoid* fun_ptr; webrtc_function_impl::FunVoid* call; }; // Create function for lambdas and other callables that are trivial and small; // it accepts every type of argument except those noted in its enable_if call. template < typename Signature, typename F, typename F_deref = typename std::remove_reference::type, typename std::enable_if< // Not for function pointers; we have another overload for that below. !std::is_function< typename std::remove_pointer::type>::value && // Not for nullptr; we have a constructor for that below. !std::is_same::type>::value && // Not for UntypedFunction objects; use move construction or // assignment. !std::is_same::type>::value && // Only for trivial callables that will fit in inline storage. std::is_trivially_move_constructible::value && std::is_trivially_destructible::value && sizeof(F_deref) <= kInlineStorageSize>::type* = nullptr, size_t InlineSize = webrtc_function_impl::InlineStorageSize()> static TrivialUntypedFunctionArgs PrepareArgs(F&& f) { // The callable is trivial and small enough, so we just store its bytes // in the inline storage. TrivialUntypedFunctionArgs args; new (&args.inline_storage) F_deref(std::forward(f)); args.call = reinterpret_cast( webrtc_function_impl::CallHelpers< Signature>::template CallInlineStorage); return args; } template static UntypedFunction Create(TrivialUntypedFunctionArgs args) { webrtc_function_impl::VoidUnion vu; std::memcpy(&vu.inline_storage, args.inline_storage, sizeof(args.inline_storage)); return UntypedFunction(vu, args.call, nullptr); } // Create function for lambdas and other callables that are nontrivial or // large; it accepts every type of argument except those noted in its // enable_if call. template ::type, typename std::enable_if< // Not for function pointers; we have another overload for that // below. !std::is_function< typename std::remove_pointer::type>::value && // Not for nullptr; we have a constructor for that below. !std::is_same::type>::value && // Not for UntypedFunction objects; use move construction or // assignment. !std::is_same::type>::value && // Only for nontrivial callables, or callables that won't fit in // inline storage. !(std::is_trivially_move_constructible::value && std::is_trivially_destructible::value && sizeof(F_deref) <= kInlineStorageSize)>::type* = nullptr> static NontrivialUntypedFunctionArgs PrepareArgs(F&& f) { // The callable is either nontrivial or too large, so we can't keep it // in the inline storage; use the heap instead. NontrivialUntypedFunctionArgs args; args.void_ptr = new F_deref(std::forward(f)); args.call = reinterpret_cast( webrtc_function_impl::CallHelpers::template CallVoidPtr< F_deref>); args.del = static_cast( [](webrtc_function_impl::VoidUnion* vu) { // Assuming that this pointer isn't null allows the // compiler to eliminate a null check in the (inlined) // delete operation. RTC_ASSUME(vu->void_ptr != nullptr); delete reinterpret_cast(vu->void_ptr); }); return args; } static UntypedFunction Create(NontrivialUntypedFunctionArgs args) { webrtc_function_impl::VoidUnion vu; vu.void_ptr = args.void_ptr; return UntypedFunction(vu, args.call, args.del); } // Create function that accepts function pointers. If the argument is null, // the result is an empty UntypedFunction. template static FunctionPointerUntypedFunctionArgs PrepareArgs(Signature* f) { FunctionPointerUntypedFunctionArgs args; args.fun_ptr = reinterpret_cast(f); args.call = reinterpret_cast( webrtc_function_impl::CallHelpers::CallFunPtr); return args; } static UntypedFunction Create(FunctionPointerUntypedFunctionArgs args) { webrtc_function_impl::VoidUnion vu; vu.fun_ptr = args.fun_ptr; return UntypedFunction(vu, args.fun_ptr == nullptr ? nullptr : args.call, nullptr); } // Prepares arguments and creates an UntypedFunction in one go. template static UntypedFunction Create(F&& f) { return Create(PrepareArgs(std::forward(f))); } // Default constructor. Creates an empty UntypedFunction. UntypedFunction() : call_(nullptr), delete_(nullptr) {} // Nullptr constructor and assignment. Creates an empty UntypedFunction. UntypedFunction(std::nullptr_t) // NOLINT(runtime/explicit) : call_(nullptr), delete_(nullptr) {} UntypedFunction& operator=(std::nullptr_t) { call_ = nullptr; if (delete_) { delete_(&f_); delete_ = nullptr; } return *this; } // Not copyable. UntypedFunction(const UntypedFunction&) = delete; UntypedFunction& operator=(const UntypedFunction&) = delete; // Move construction and assignment. UntypedFunction(UntypedFunction&& other) : f_(other.f_), call_(other.call_), delete_(other.delete_) { other.delete_ = nullptr; } UntypedFunction& operator=(UntypedFunction&& other) { if (delete_) { delete_(&f_); } f_ = other.f_; call_ = other.call_; delete_ = other.delete_; other.delete_ = nullptr; return *this; } ~UntypedFunction() { if (delete_) { delete_(&f_); } } friend void swap(UntypedFunction& a, UntypedFunction& b) { using std::swap; swap(a.f_, b.f_); swap(a.call_, b.call_); swap(a.delete_, b.delete_); } // Returns true if we have a function, false if we don't (i.e., we're null). explicit operator bool() const { return call_ != nullptr; } template typename webrtc_function_impl::CallHelpers::return_type Call( ArgT&&... args) { return reinterpret_cast< typename webrtc_function_impl::CallHelpers::function_type*>( call_)(&f_, std::forward(args)...); } // Returns true iff we don't need to call a destructor. This is guaranteed // to hold for a moved-from object. bool IsTriviallyDestructible() { return delete_ == nullptr; } private: UntypedFunction(webrtc_function_impl::VoidUnion f, webrtc_function_impl::FunVoid* call, void (*del)(webrtc_function_impl::VoidUnion*)) : f_(f), call_(call), delete_(del) {} // The callable thing, or a pointer to it. webrtc_function_impl::VoidUnion f_; // Pointer to a dispatch function that knows the type of the callable thing // that's stored in f_, and how to call it. An UntypedFunction object is empty // (null) iff call_ is null. webrtc_function_impl::FunVoid* call_; // Pointer to a function that knows how to delete the callable thing that's // stored in f_. Null if `f_` is trivially deletable. void (*delete_)(webrtc_function_impl::VoidUnion*); }; } // namespace webrtc #endif // RTC_BASE_UNTYPED_FUNCTION_H_