123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426 |
- #pragma once
- #ifdef __GNUC__
- #pragma GCC diagnostic push
- #pragma GCC diagnostic ignored "-Wunused-parameter"
- #endif
- //===- FunctionExtras.h - Function type erasure utilities -------*- C++ -*-===//
- //
- // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
- // See https://llvm.org/LICENSE.txt for license information.
- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- //
- //===----------------------------------------------------------------------===//
- /// \file
- /// This file provides a collection of function (or more generally, callable)
- /// type erasure utilities supplementing those provided by the standard library
- /// in `<function>`.
- ///
- /// It provides `unique_function`, which works like `std::function` but supports
- /// move-only callable objects and const-qualification.
- ///
- /// Future plans:
- /// - Add a `function` that provides ref-qualified support, which doesn't work
- /// with `std::function`.
- /// - Provide support for specifying multiple signatures to type erase callable
- /// objects with an overload set, such as those produced by generic lambdas.
- /// - Expand to include a copyable utility that directly replaces std::function
- /// but brings the above improvements.
- ///
- /// Note that LLVM's utilities are greatly simplified by not supporting
- /// allocators.
- ///
- /// If the standard library ever begins to provide comparable facilities we can
- /// consider switching to those.
- ///
- //===----------------------------------------------------------------------===//
- #ifndef LLVM_ADT_FUNCTIONEXTRAS_H
- #define LLVM_ADT_FUNCTIONEXTRAS_H
- #include "llvm/ADT/PointerIntPair.h"
- #include "llvm/ADT/PointerUnion.h"
- #include "llvm/ADT/STLForwardCompat.h"
- #include "llvm/Support/MemAlloc.h"
- #include "llvm/Support/type_traits.h"
- #include <cstring>
- #include <memory>
- #include <type_traits>
- namespace llvm {
- /// unique_function is a type-erasing functor similar to std::function.
- ///
- /// It can hold move-only function objects, like lambdas capturing unique_ptrs.
- /// Accordingly, it is movable but not copyable.
- ///
- /// It supports const-qualification:
- /// - unique_function<int() const> has a const operator().
- /// It can only hold functions which themselves have a const operator().
- /// - unique_function<int()> has a non-const operator().
- /// It can hold functions with a non-const operator(), like mutable lambdas.
- template <typename FunctionT> class unique_function;
- namespace detail {
- template <typename T>
- using EnableIfTrivial =
- std::enable_if_t<llvm::is_trivially_move_constructible<T>::value &&
- std::is_trivially_destructible<T>::value>;
- template <typename CallableT, typename ThisT>
- using EnableUnlessSameType =
- std::enable_if_t<!std::is_same<remove_cvref_t<CallableT>, ThisT>::value>;
- template <typename CallableT, typename Ret, typename... Params>
- using EnableIfCallable = std::enable_if_t<std::disjunction<
- std::is_void<Ret>,
- std::is_same<decltype(std::declval<CallableT>()(std::declval<Params>()...)),
- Ret>,
- std::is_same<const decltype(std::declval<CallableT>()(
- std::declval<Params>()...)),
- Ret>,
- std::is_convertible<decltype(std::declval<CallableT>()(
- std::declval<Params>()...)),
- Ret>>::value>;
- template <typename ReturnT, typename... ParamTs> class UniqueFunctionBase {
- protected:
- static constexpr size_t InlineStorageSize = sizeof(void *) * 3;
- template <typename T, class = void>
- struct IsSizeLessThanThresholdT : std::false_type {};
- template <typename T>
- struct IsSizeLessThanThresholdT<
- T, std::enable_if_t<sizeof(T) <= 2 * sizeof(void *)>> : std::true_type {};
- // Provide a type function to map parameters that won't observe extra copies
- // or moves and which are small enough to likely pass in register to values
- // and all other types to l-value reference types. We use this to compute the
- // types used in our erased call utility to minimize copies and moves unless
- // doing so would force things unnecessarily into memory.
- //
- // The heuristic used is related to common ABI register passing conventions.
- // It doesn't have to be exact though, and in one way it is more strict
- // because we want to still be able to observe either moves *or* copies.
- template <typename T> struct AdjustedParamTBase {
- static_assert(!std::is_reference<T>::value,
- "references should be handled by template specialization");
- using type = std::conditional_t<
- llvm::is_trivially_copy_constructible<T>::value &&
- llvm::is_trivially_move_constructible<T>::value &&
- IsSizeLessThanThresholdT<T>::value,
- T, T &>;
- };
- // This specialization ensures that 'AdjustedParam<V<T>&>' or
- // 'AdjustedParam<V<T>&&>' does not trigger a compile-time error when 'T' is
- // an incomplete type and V a templated type.
- template <typename T> struct AdjustedParamTBase<T &> { using type = T &; };
- template <typename T> struct AdjustedParamTBase<T &&> { using type = T &; };
- template <typename T>
- using AdjustedParamT = typename AdjustedParamTBase<T>::type;
- // The type of the erased function pointer we use as a callback to dispatch to
- // the stored callable when it is trivial to move and destroy.
- using CallPtrT = ReturnT (*)(void *CallableAddr,
- AdjustedParamT<ParamTs>... Params);
- using MovePtrT = void (*)(void *LHSCallableAddr, void *RHSCallableAddr);
- using DestroyPtrT = void (*)(void *CallableAddr);
- /// A struct to hold a single trivial callback with sufficient alignment for
- /// our bitpacking.
- struct alignas(8) TrivialCallback {
- CallPtrT CallPtr;
- };
- /// A struct we use to aggregate three callbacks when we need full set of
- /// operations.
- struct alignas(8) NonTrivialCallbacks {
- CallPtrT CallPtr;
- MovePtrT MovePtr;
- DestroyPtrT DestroyPtr;
- };
- // Create a pointer union between either a pointer to a static trivial call
- // pointer in a struct or a pointer to a static struct of the call, move, and
- // destroy pointers.
- using CallbackPointerUnionT =
- PointerUnion<TrivialCallback *, NonTrivialCallbacks *>;
- // The main storage buffer. This will either have a pointer to out-of-line
- // storage or an inline buffer storing the callable.
- union StorageUnionT {
- // For out-of-line storage we keep a pointer to the underlying storage and
- // the size. This is enough to deallocate the memory.
- struct OutOfLineStorageT {
- void *StoragePtr;
- size_t Size;
- size_t Alignment;
- } OutOfLineStorage;
- static_assert(
- sizeof(OutOfLineStorageT) <= InlineStorageSize,
- "Should always use all of the out-of-line storage for inline storage!");
- // For in-line storage, we just provide an aligned character buffer. We
- // provide three pointers worth of storage here.
- // This is mutable as an inlined `const unique_function<void() const>` may
- // still modify its own mutable members.
- mutable std::aligned_storage_t<InlineStorageSize, alignof(void *)>
- InlineStorage;
- } StorageUnion;
- // A compressed pointer to either our dispatching callback or our table of
- // dispatching callbacks and the flag for whether the callable itself is
- // stored inline or not.
- PointerIntPair<CallbackPointerUnionT, 1, bool> CallbackAndInlineFlag;
- bool isInlineStorage() const { return CallbackAndInlineFlag.getInt(); }
- bool isTrivialCallback() const {
- return CallbackAndInlineFlag.getPointer().template is<TrivialCallback *>();
- }
- CallPtrT getTrivialCallback() const {
- return CallbackAndInlineFlag.getPointer().template get<TrivialCallback *>()->CallPtr;
- }
- NonTrivialCallbacks *getNonTrivialCallbacks() const {
- return CallbackAndInlineFlag.getPointer()
- .template get<NonTrivialCallbacks *>();
- }
- CallPtrT getCallPtr() const {
- return isTrivialCallback() ? getTrivialCallback()
- : getNonTrivialCallbacks()->CallPtr;
- }
- // These three functions are only const in the narrow sense. They return
- // mutable pointers to function state.
- // This allows unique_function<T const>::operator() to be const, even if the
- // underlying functor may be internally mutable.
- //
- // const callers must ensure they're only used in const-correct ways.
- void *getCalleePtr() const {
- return isInlineStorage() ? getInlineStorage() : getOutOfLineStorage();
- }
- void *getInlineStorage() const { return &StorageUnion.InlineStorage; }
- void *getOutOfLineStorage() const {
- return StorageUnion.OutOfLineStorage.StoragePtr;
- }
- size_t getOutOfLineStorageSize() const {
- return StorageUnion.OutOfLineStorage.Size;
- }
- size_t getOutOfLineStorageAlignment() const {
- return StorageUnion.OutOfLineStorage.Alignment;
- }
- void setOutOfLineStorage(void *Ptr, size_t Size, size_t Alignment) {
- StorageUnion.OutOfLineStorage = {Ptr, Size, Alignment};
- }
- template <typename CalledAsT>
- static ReturnT CallImpl(void *CallableAddr,
- AdjustedParamT<ParamTs>... Params) {
- auto &Func = *reinterpret_cast<CalledAsT *>(CallableAddr);
- return Func(std::forward<ParamTs>(Params)...);
- }
- template <typename CallableT>
- static void MoveImpl(void *LHSCallableAddr, void *RHSCallableAddr) noexcept {
- new (LHSCallableAddr)
- CallableT(std::move(*reinterpret_cast<CallableT *>(RHSCallableAddr)));
- }
- template <typename CallableT>
- static void DestroyImpl(void *CallableAddr) noexcept {
- reinterpret_cast<CallableT *>(CallableAddr)->~CallableT();
- }
- // The pointers to call/move/destroy functions are determined for each
- // callable type (and called-as type, which determines the overload chosen).
- // (definitions are out-of-line).
- // By default, we need an object that contains all the different
- // type erased behaviors needed. Create a static instance of the struct type
- // here and each instance will contain a pointer to it.
- // Wrap in a struct to avoid https://gcc.gnu.org/PR71954
- template <typename CallableT, typename CalledAs, typename Enable = void>
- struct CallbacksHolder {
- static NonTrivialCallbacks Callbacks;
- };
- // See if we can create a trivial callback. We need the callable to be
- // trivially moved and trivially destroyed so that we don't have to store
- // type erased callbacks for those operations.
- template <typename CallableT, typename CalledAs>
- struct CallbacksHolder<CallableT, CalledAs, EnableIfTrivial<CallableT>> {
- static TrivialCallback Callbacks;
- };
- // A simple tag type so the call-as type to be passed to the constructor.
- template <typename T> struct CalledAs {};
- // Essentially the "main" unique_function constructor, but subclasses
- // provide the qualified type to be used for the call.
- // (We always store a T, even if the call will use a pointer to const T).
- template <typename CallableT, typename CalledAsT>
- UniqueFunctionBase(CallableT Callable, CalledAs<CalledAsT>) {
- bool IsInlineStorage = true;
- void *CallableAddr = getInlineStorage();
- if (sizeof(CallableT) > InlineStorageSize ||
- alignof(CallableT) > alignof(decltype(StorageUnion.InlineStorage))) {
- IsInlineStorage = false;
- // Allocate out-of-line storage. FIXME: Use an explicit alignment
- // parameter in C++17 mode.
- auto Size = sizeof(CallableT);
- auto Alignment = alignof(CallableT);
- CallableAddr = allocate_buffer(Size, Alignment);
- setOutOfLineStorage(CallableAddr, Size, Alignment);
- }
- // Now move into the storage.
- new (CallableAddr) CallableT(std::move(Callable));
- CallbackAndInlineFlag.setPointerAndInt(
- &CallbacksHolder<CallableT, CalledAsT>::Callbacks, IsInlineStorage);
- }
- ~UniqueFunctionBase() {
- if (!CallbackAndInlineFlag.getPointer())
- return;
- // Cache this value so we don't re-check it after type-erased operations.
- bool IsInlineStorage = isInlineStorage();
- if (!isTrivialCallback())
- getNonTrivialCallbacks()->DestroyPtr(
- IsInlineStorage ? getInlineStorage() : getOutOfLineStorage());
- if (!IsInlineStorage)
- deallocate_buffer(getOutOfLineStorage(), getOutOfLineStorageSize(),
- getOutOfLineStorageAlignment());
- }
- UniqueFunctionBase(UniqueFunctionBase &&RHS) noexcept {
- // Copy the callback and inline flag.
- CallbackAndInlineFlag = RHS.CallbackAndInlineFlag;
- // If the RHS is empty, just copying the above is sufficient.
- if (!RHS)
- return;
- if (!isInlineStorage()) {
- // The out-of-line case is easiest to move.
- StorageUnion.OutOfLineStorage = RHS.StorageUnion.OutOfLineStorage;
- } else if (isTrivialCallback()) {
- // Move is trivial, just memcpy the bytes across.
- memcpy(getInlineStorage(), RHS.getInlineStorage(), InlineStorageSize);
- } else {
- // Non-trivial move, so dispatch to a type-erased implementation.
- getNonTrivialCallbacks()->MovePtr(getInlineStorage(),
- RHS.getInlineStorage());
- }
- // Clear the old callback and inline flag to get back to as-if-null.
- RHS.CallbackAndInlineFlag = {};
- #ifndef NDEBUG
- // In debug builds, we also scribble across the rest of the storage.
- memset(RHS.getInlineStorage(), 0xAD, InlineStorageSize);
- #endif
- }
- UniqueFunctionBase &operator=(UniqueFunctionBase &&RHS) noexcept {
- if (this == &RHS)
- return *this;
- // Because we don't try to provide any exception safety guarantees we can
- // implement move assignment very simply by first destroying the current
- // object and then move-constructing over top of it.
- this->~UniqueFunctionBase();
- new (this) UniqueFunctionBase(std::move(RHS));
- return *this;
- }
- UniqueFunctionBase() = default;
- public:
- explicit operator bool() const {
- return (bool)CallbackAndInlineFlag.getPointer();
- }
- };
- template <typename R, typename... P>
- template <typename CallableT, typename CalledAsT, typename Enable>
- typename UniqueFunctionBase<R, P...>::NonTrivialCallbacks UniqueFunctionBase<
- R, P...>::CallbacksHolder<CallableT, CalledAsT, Enable>::Callbacks = {
- &CallImpl<CalledAsT>, &MoveImpl<CallableT>, &DestroyImpl<CallableT>};
- template <typename R, typename... P>
- template <typename CallableT, typename CalledAsT>
- typename UniqueFunctionBase<R, P...>::TrivialCallback
- UniqueFunctionBase<R, P...>::CallbacksHolder<
- CallableT, CalledAsT, EnableIfTrivial<CallableT>>::Callbacks{
- &CallImpl<CalledAsT>};
- } // namespace detail
- template <typename R, typename... P>
- class unique_function<R(P...)> : public detail::UniqueFunctionBase<R, P...> {
- using Base = detail::UniqueFunctionBase<R, P...>;
- public:
- unique_function() = default;
- unique_function(std::nullptr_t) {}
- unique_function(unique_function &&) = default;
- unique_function(const unique_function &) = delete;
- unique_function &operator=(unique_function &&) = default;
- unique_function &operator=(const unique_function &) = delete;
- template <typename CallableT>
- unique_function(
- CallableT Callable,
- detail::EnableUnlessSameType<CallableT, unique_function> * = nullptr,
- detail::EnableIfCallable<CallableT, R, P...> * = nullptr)
- : Base(std::forward<CallableT>(Callable),
- typename Base::template CalledAs<CallableT>{}) {}
- R operator()(P... Params) {
- return this->getCallPtr()(this->getCalleePtr(), Params...);
- }
- };
- template <typename R, typename... P>
- class unique_function<R(P...) const>
- : public detail::UniqueFunctionBase<R, P...> {
- using Base = detail::UniqueFunctionBase<R, P...>;
- public:
- unique_function() = default;
- unique_function(std::nullptr_t) {}
- unique_function(unique_function &&) = default;
- unique_function(const unique_function &) = delete;
- unique_function &operator=(unique_function &&) = default;
- unique_function &operator=(const unique_function &) = delete;
- template <typename CallableT>
- unique_function(
- CallableT Callable,
- detail::EnableUnlessSameType<CallableT, unique_function> * = nullptr,
- detail::EnableIfCallable<const CallableT, R, P...> * = nullptr)
- : Base(std::forward<CallableT>(Callable),
- typename Base::template CalledAs<const CallableT>{}) {}
- R operator()(P... Params) const {
- return this->getCallPtr()(this->getCalleePtr(), Params...);
- }
- };
- } // end namespace llvm
- #endif // LLVM_ADT_FUNCTIONEXTRAS_H
- #ifdef __GNUC__
- #pragma GCC diagnostic pop
- #endif
|