aboutsummaryrefslogtreecommitdiff
path: root/include/EASTL/internal/function_detail.h
diff options
context:
space:
mode:
Diffstat (limited to 'include/EASTL/internal/function_detail.h')
-rw-r--r--include/EASTL/internal/function_detail.h673
1 files changed, 673 insertions, 0 deletions
diff --git a/include/EASTL/internal/function_detail.h b/include/EASTL/internal/function_detail.h
new file mode 100644
index 0000000..dc18b63
--- /dev/null
+++ b/include/EASTL/internal/function_detail.h
@@ -0,0 +1,673 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright (c) Electronic Arts Inc. All rights reserved.
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef EASTL_FUNCTION_DETAIL_H
+#define EASTL_FUNCTION_DETAIL_H
+
+#if defined(EA_PRAGMA_ONCE_SUPPORTED)
+ #pragma once
+#endif
+
+#include <EABase/eabase.h>
+#include <EABase/nullptr.h>
+#include <EABase/config/eacompilertraits.h>
+
+#include <EASTL/internal/config.h>
+#include <EASTL/internal/functional_base.h>
+#include <EASTL/internal/move_help.h>
+#include <EASTL/internal/function_help.h>
+
+#include <EASTL/type_traits.h>
+#include <EASTL/utility.h>
+#include <EASTL/allocator.h>
+
+#if EASTL_RTTI_ENABLED
+ #include <typeinfo>
+#endif
+
+#if EASTL_EXCEPTIONS_ENABLED
+ EA_DISABLE_ALL_VC_WARNINGS()
+ #include <new>
+ #include <exception>
+ EA_RESTORE_ALL_VC_WARNINGS()
+#endif
+
+namespace eastl
+{
+ #if EASTL_EXCEPTIONS_ENABLED
+ class bad_function_call : public std::exception
+ {
+ public:
+ bad_function_call() EA_NOEXCEPT = default;
+
+ const char* what() const EA_NOEXCEPT EA_OVERRIDE
+ {
+ return "bad function_detail call";
+ }
+ };
+ #endif
+
+ namespace internal
+ {
+ class unused_class {};
+
+ union functor_storage_alignment
+ {
+ void (*unused_func_ptr)(void);
+ void (unused_class::*unused_func_mem_ptr)(void);
+ void* unused_ptr;
+ };
+
+ template <int SIZE_IN_BYTES>
+ struct functor_storage
+ {
+ static_assert(SIZE_IN_BYTES >= 0, "local buffer storage cannot have a negative size!");
+ template <typename Ret>
+ Ret& GetStorageTypeRef() const
+ {
+ return *reinterpret_cast<Ret*>(const_cast<char*>(&storage[0]));
+ }
+
+ union
+ {
+ functor_storage_alignment align;
+ char storage[SIZE_IN_BYTES];
+ };
+ };
+
+ template <>
+ struct functor_storage<0>
+ {
+ template <typename Ret>
+ Ret& GetStorageTypeRef() const
+ {
+ return *reinterpret_cast<Ret*>(const_cast<char*>(&storage[0]));
+ }
+
+ union
+ {
+ functor_storage_alignment align;
+ char storage[sizeof(functor_storage_alignment)];
+ };
+ };
+
+ template <typename Functor, int SIZE_IN_BYTES>
+ struct is_functor_inplace_allocatable
+ {
+ static constexpr bool value =
+ sizeof(Functor) <= sizeof(functor_storage<SIZE_IN_BYTES>) &&
+ (eastl::alignment_of_v<functor_storage<SIZE_IN_BYTES>> % eastl::alignment_of_v<Functor>) == 0;
+ };
+
+
+ /// function_base_detail
+ ///
+ template <int SIZE_IN_BYTES>
+ class function_base_detail
+ {
+ public:
+ using FunctorStorageType = functor_storage<SIZE_IN_BYTES>;
+ FunctorStorageType mStorage;
+
+ enum ManagerOperations : int
+ {
+ MGROPS_DESTRUCT_FUNCTOR = 0,
+ MGROPS_COPY_FUNCTOR = 1,
+ MGROPS_MOVE_FUNCTOR = 2,
+ #if EASTL_RTTI_ENABLED
+ MGROPS_GET_TYPE_INFO = 3,
+ MGROPS_GET_FUNC_PTR = 4,
+ #endif
+ };
+
+ // Functor can be allocated inplace
+ template <typename Functor, typename = void>
+ class function_manager_base
+ {
+ public:
+
+ static Functor* GetFunctorPtr(const FunctorStorageType& storage) EA_NOEXCEPT
+ {
+ return &(storage.template GetStorageTypeRef<Functor>());
+ }
+
+ template <typename T>
+ static void CreateFunctor(FunctorStorageType& storage, T&& functor)
+ {
+ ::new (GetFunctorPtr(storage)) Functor(eastl::forward<T>(functor));
+ }
+
+ static void DestructFunctor(FunctorStorageType& storage)
+ {
+ GetFunctorPtr(storage)->~Functor();
+ }
+
+ static void CopyFunctor(FunctorStorageType& to, const FunctorStorageType& from)
+ {
+ ::new (GetFunctorPtr(to)) Functor(*GetFunctorPtr(from));
+ }
+
+ static void MoveFunctor(FunctorStorageType& to, FunctorStorageType& from) EA_NOEXCEPT
+ {
+ ::new (GetFunctorPtr(to)) Functor(eastl::move(*GetFunctorPtr(from)));
+ }
+
+ static void* Manager(void* to, void* from, typename function_base_detail::ManagerOperations ops) EA_NOEXCEPT
+ {
+ switch (ops)
+ {
+ case MGROPS_DESTRUCT_FUNCTOR:
+ {
+ DestructFunctor(*static_cast<FunctorStorageType*>(to));
+ }
+ break;
+ case MGROPS_COPY_FUNCTOR:
+ {
+ CopyFunctor(*static_cast<FunctorStorageType*>(to),
+ *static_cast<const FunctorStorageType*>(from));
+ }
+ break;
+ case MGROPS_MOVE_FUNCTOR:
+ {
+ MoveFunctor(*static_cast<FunctorStorageType*>(to), *static_cast<FunctorStorageType*>(from));
+ DestructFunctor(*static_cast<FunctorStorageType*>(from));
+ }
+ break;
+ default:
+ break;
+ }
+ return nullptr;
+ }
+ };
+
+ // Functor is allocated on the heap
+ template <typename Functor>
+ class function_manager_base<Functor, typename eastl::enable_if<!is_functor_inplace_allocatable<Functor, SIZE_IN_BYTES>::value>::type>
+ {
+ public:
+ static Functor* GetFunctorPtr(const FunctorStorageType& storage) EA_NOEXCEPT
+ {
+ return storage.template GetStorageTypeRef<Functor*>();
+ }
+
+ static Functor*& GetFunctorPtrRef(const FunctorStorageType& storage) EA_NOEXCEPT
+ {
+ return storage.template GetStorageTypeRef<Functor*>();
+ }
+
+ template <typename T>
+ static void CreateFunctor(FunctorStorageType& storage, T&& functor)
+ {
+ auto& allocator = *EASTLAllocatorDefault();
+ Functor* func = static_cast<Functor*>(allocator.allocate(sizeof(Functor), alignof(Functor), 0));
+
+ #if EASTL_EXCEPTIONS_ENABLED
+ if (!func)
+ {
+ throw std::bad_alloc();
+ }
+ #else
+ EASTL_ASSERT_MSG(func != nullptr, "Allocation failed!");
+ #endif
+
+ ::new (static_cast<void*>(func)) Functor(eastl::forward<T>(functor));
+ GetFunctorPtrRef(storage) = func;
+ }
+
+ static void DestructFunctor(FunctorStorageType& storage)
+ {
+ Functor* func = GetFunctorPtr(storage);
+ if (func)
+ {
+ auto& allocator = *EASTLAllocatorDefault();
+ func->~Functor();
+ allocator.deallocate(static_cast<void*>(func), sizeof(Functor));
+ }
+ }
+
+ static void CopyFunctor(FunctorStorageType& to, const FunctorStorageType& from)
+ {
+ auto& allocator = *EASTLAllocatorDefault();
+ Functor* func = static_cast<Functor*>(allocator.allocate(sizeof(Functor), alignof(Functor), 0));
+ #if EASTL_EXCEPTIONS_ENABLED
+ if (!func)
+ {
+ throw std::bad_alloc();
+ }
+ #else
+ EASTL_ASSERT_MSG(func != nullptr, "Allocation failed!");
+ #endif
+ ::new (static_cast<void*>(func)) Functor(*GetFunctorPtr(from));
+ GetFunctorPtrRef(to) = func;
+ }
+
+ static void MoveFunctor(FunctorStorageType& to, FunctorStorageType& from) EA_NOEXCEPT
+ {
+ Functor* func = GetFunctorPtr(from);
+ GetFunctorPtrRef(to) = func;
+ GetFunctorPtrRef(from) = nullptr;
+ }
+
+ static void* Manager(void* to, void* from, typename function_base_detail::ManagerOperations ops) EA_NOEXCEPT
+ {
+ switch (ops)
+ {
+ case MGROPS_DESTRUCT_FUNCTOR:
+ {
+ DestructFunctor(*static_cast<FunctorStorageType*>(to));
+ }
+ break;
+ case MGROPS_COPY_FUNCTOR:
+ {
+ CopyFunctor(*static_cast<FunctorStorageType*>(to),
+ *static_cast<const FunctorStorageType*>(from));
+ }
+ break;
+ case MGROPS_MOVE_FUNCTOR:
+ {
+ MoveFunctor(*static_cast<FunctorStorageType*>(to), *static_cast<FunctorStorageType*>(from));
+ // Moved ptr, no need to destruct ourselves
+ }
+ break;
+ default:
+ break;
+ }
+ return nullptr;
+ }
+ };
+
+ template <typename Functor, typename R, typename... Args>
+ class function_manager final : public function_manager_base<Functor>
+ {
+ public:
+ using Base = function_manager_base<Functor>;
+
+ #if EASTL_RTTI_ENABLED
+ static void* GetTypeInfo() EA_NOEXCEPT
+ {
+ return reinterpret_cast<void*>(const_cast<std::type_info*>(&typeid(Functor)));
+ }
+
+ static void* Manager(void* to, void* from, typename function_base_detail::ManagerOperations ops) EA_NOEXCEPT
+ {
+ switch (ops)
+ {
+ case MGROPS_GET_TYPE_INFO:
+ {
+ return GetTypeInfo();
+ }
+ break;
+ case MGROPS_GET_FUNC_PTR:
+ {
+ return static_cast<void*>(Base::GetFunctorPtr(*static_cast<FunctorStorageType*>(to)));
+ }
+ break;
+ default:
+ {
+ return Base::Manager(to, from, ops);
+ }
+ break;
+ }
+ }
+ #endif // EASTL_RTTI_ENABLED
+
+ /**
+ * NOTE:
+ *
+ * The order of arguments here is vital to the call optimization. Let's dig into why and look at some asm.
+ * We have two invoker signatures to consider:
+ * R Invoker(const FunctorStorageType& functor, Args... args)
+ * R Invoker(Args... args, const FunctorStorageType& functor)
+ *
+ * Assume we are using the Windows x64 Calling Convention where the first 4 arguments are passed into
+ * RCX, RDX, R8, R9. This optimization works for any Calling Convention, we are just using Windows x64 for
+ * this example.
+ *
+ * Given the following member function: void TestMemberFunc(int a, int b)
+ * RCX == this
+ * RDX == a
+ * R8 == b
+ *
+ * All three arguments to the function including the hidden this pointer, which in C++ is always the first argument
+ * are passed into the first three registers.
+ * The function call chain for eastl::function<>() is as follows:
+ * operator ()(this, Args... args) -> Invoker(Args... args, this->mStorage) -> StoredFunction(Args... arg)
+ *
+ * Let's look at what is happening at the asm level with the different Invoker function signatures and why.
+ *
+ * You will notice that operator ()() and Invoker() have the arguments reversed. operator ()() just directly calls
+ * to Invoker(), it is a tail call, so we force inline the call operator to ensure we directly call to the Invoker().
+ * Most compilers always inline it anyways by default; have been instances where it doesn't even though the asm ends
+ * up being cheaper.
+ * call -> call -> call versus call -> call
+ *
+ * eastl::function<int(int, int)> = FunctionPointer
+ *
+ * Assume we have the above eastl::function object that holds a pointer to a function as the internal callable.
+ *
+ * Invoker(this->mStorage, Args... args) is called with the follow arguments in registers:
+ * RCX = this | RDX = a | R8 = b
+ *
+ * Inside Invoker() we use RCX to deference into the eastl::function object and get the function pointer to call.
+ * This function to call has signature Func(int, int) and thus requires its arguments in registers RCX and RDX.
+ * The compiler must shift all the arguments towards the left. The full asm looks something as follows.
+ *
+ * Calling Invoker: Inside Invoker:
+ *
+ * mov rcx, this mov rax, [rcx]
+ * mov rdx, a mov rcx, rdx
+ * mov r8, b mov rdx, r8
+ * call [rcx + offset to Invoker] jmp [rax]
+ *
+ * Notice how the compiler shifts all the arguments before calling the callable and also we only use the this pointer
+ * to access the internal storage inside the eastl::function object.
+ *
+ * Invoker(Args... args, this->mStorage) is called with the following arguments in registers:
+ * RCX = a | RDX = b | R8 = this
+ *
+ * You can see we no longer have to shift the arguments down when going to call the internal stored callable.
+ *
+ * Calling Invoker: Inside Invoker:
+ *
+ * mov rcx, a mov rax, [r8]
+ * mov rdx, b jmp [rax]
+ * mov r8, this
+ * call [r8 + offset to Invoker]
+ *
+ * The generated asm does a straight tail jmp to the loaded function pointer. The arguments are already in the correct
+ * registers.
+ *
+ * For Functors or Lambdas with no captures, this gives us another free register to use to pass arguments since the this
+ * is at the end, it can be passed onto the stack if we run out of registers. Since the callable has no captures; inside
+ * the Invoker(), we won't ever need to touch this thus we can just call the operator ()() or let the compiler inline it.
+ *
+ * For a callable with captures there is no perf hit since the callable in the common case is inlined and the pointer to the callable
+ * buffer is passed in a register which the compiler can use to access the captures.
+ *
+ * For eastl::function<void(const T&, int, int)> that a holds a pointer to member function. The this pointers is implicitly
+ * the first argument in the argument list, const T&, and the member function pointer will be called on that object.
+ * This prevents any argument shifting since the this for the member function pointer is already in RCX.
+ *
+ * This is why having this at the end of the argument list is important for generating efficient Invoker() thunks.
+ */
+ static R Invoker(Args... args, const FunctorStorageType& functor)
+ {
+ return eastl::invoke(*Base::GetFunctorPtr(functor), eastl::forward<Args>(args)...);
+ }
+ };
+
+ function_base_detail() EA_NOEXCEPT = default;
+ ~function_base_detail() EA_NOEXCEPT = default;
+ };
+
+ #define EASTL_INTERNAL_FUNCTION_VALID_FUNCTION_ARGS(FUNCTOR, RET, ARGS, BASE, MYSELF) \
+ typename eastl::enable_if_t<eastl::is_invocable_r_v<RET, FUNCTOR, ARGS> && \
+ !eastl::is_base_of_v<BASE, eastl::decay_t<FUNCTOR>> && \
+ !eastl::is_same_v<eastl::decay_t<FUNCTOR>, MYSELF>>
+
+ #define EASTL_INTERNAL_FUNCTION_DETAIL_VALID_FUNCTION_ARGS(FUNCTOR, RET, ARGS, MYSELF) \
+ EASTL_INTERNAL_FUNCTION_VALID_FUNCTION_ARGS(FUNCTOR, RET, ARGS, MYSELF, MYSELF)
+
+
+ /// function_detail
+ ///
+ template <int, typename>
+ class function_detail;
+
+ template <int SIZE_IN_BYTES, typename R, typename... Args>
+ class function_detail<SIZE_IN_BYTES, R(Args...)> : public function_base_detail<SIZE_IN_BYTES>
+ {
+ public:
+ using result_type = R;
+
+ protected:
+ using Base = function_base_detail<SIZE_IN_BYTES>;
+ using FunctorStorageType = typename function_base_detail<SIZE_IN_BYTES>::FunctorStorageType;
+ using Base::mStorage;
+
+ public:
+ function_detail() EA_NOEXCEPT = default;
+ function_detail(std::nullptr_t) EA_NOEXCEPT {}
+
+ function_detail(const function_detail& other)
+ {
+ if (this != &other)
+ {
+ Copy(other);
+ }
+ }
+
+ function_detail(function_detail&& other)
+ {
+ if (this != &other)
+ {
+ Move(eastl::move(other));
+ }
+ }
+
+ template <typename Functor, typename = EASTL_INTERNAL_FUNCTION_DETAIL_VALID_FUNCTION_ARGS(Functor, R, Args..., function_detail)>
+ function_detail(Functor functor)
+ {
+ CreateForwardFunctor(eastl::move(functor));
+ }
+
+ ~function_detail() EA_NOEXCEPT
+ {
+ Destroy();
+ }
+
+ function_detail& operator=(const function_detail& other)
+ {
+ if (this != &other)
+ {
+ Destroy();
+ Copy(other);
+ }
+
+ return *this;
+ }
+
+ function_detail& operator=(function_detail&& other)
+ {
+ if(this != &other)
+ {
+ Destroy();
+ Move(eastl::move(other));
+ }
+
+ return *this;
+ }
+
+ function_detail& operator=(std::nullptr_t) EA_NOEXCEPT
+ {
+ Destroy();
+ mMgrFuncPtr = nullptr;
+ mInvokeFuncPtr = &DefaultInvoker;
+
+ return *this;
+ }
+
+ template <typename Functor, typename = EASTL_INTERNAL_FUNCTION_DETAIL_VALID_FUNCTION_ARGS(Functor, R, Args..., function_detail)>
+ function_detail& operator=(Functor&& functor)
+ {
+ Destroy();
+ CreateForwardFunctor(eastl::forward<Functor>(functor));
+ return *this;
+ }
+
+ template <typename Functor>
+ function_detail& operator=(eastl::reference_wrapper<Functor> f) EA_NOEXCEPT
+ {
+ Destroy();
+ CreateForwardFunctor(f);
+ return *this;
+ }
+
+ void swap(function_detail& other) EA_NOEXCEPT
+ {
+ if(this == &other)
+ return;
+
+ FunctorStorageType tempStorage;
+ if (other.HaveManager())
+ {
+ (void)(*other.mMgrFuncPtr)(static_cast<void*>(&tempStorage), static_cast<void*>(&other.mStorage),
+ Base::ManagerOperations::MGROPS_MOVE_FUNCTOR);
+ }
+
+ if (HaveManager())
+ {
+ (void)(*mMgrFuncPtr)(static_cast<void*>(&other.mStorage), static_cast<void*>(&mStorage),
+ Base::ManagerOperations::MGROPS_MOVE_FUNCTOR);
+ }
+
+ if (other.HaveManager())
+ {
+ (void)(*other.mMgrFuncPtr)(static_cast<void*>(&mStorage), static_cast<void*>(&tempStorage),
+ Base::ManagerOperations::MGROPS_MOVE_FUNCTOR);
+ }
+
+ eastl::swap(mMgrFuncPtr, other.mMgrFuncPtr);
+ eastl::swap(mInvokeFuncPtr, other.mInvokeFuncPtr);
+ }
+
+ explicit operator bool() const EA_NOEXCEPT
+ {
+ return HaveManager();
+ }
+
+ EASTL_FORCE_INLINE R operator ()(Args... args) const
+ {
+ return (*mInvokeFuncPtr)(eastl::forward<Args>(args)..., this->mStorage);
+ }
+
+ #if EASTL_RTTI_ENABLED
+ const std::type_info& target_type() const EA_NOEXCEPT
+ {
+ if (HaveManager())
+ {
+ void* ret = (*mMgrFuncPtr)(nullptr, nullptr, Base::ManagerOperations::MGROPS_GET_TYPE_INFO);
+ return *(static_cast<const std::type_info*>(ret));
+ }
+ return typeid(void);
+ }
+
+ template <typename Functor>
+ Functor* target() EA_NOEXCEPT
+ {
+ if (HaveManager() && target_type() == typeid(Functor))
+ {
+ void* ret = (*mMgrFuncPtr)(static_cast<void*>(&mStorage), nullptr,
+ Base::ManagerOperations::MGROPS_GET_FUNC_PTR);
+ return ret ? static_cast<Functor*>(ret) : nullptr;
+ }
+ return nullptr;
+ }
+
+ template <typename Functor>
+ const Functor* target() const EA_NOEXCEPT
+ {
+ if (HaveManager() && target_type() == typeid(Functor))
+ {
+ void* ret = (*mMgrFuncPtr)(static_cast<void*>(&mStorage), nullptr,
+ Base::ManagerOperations::MGROPS_GET_FUNC_PTR);
+ return ret ? static_cast<const Functor*>(ret) : nullptr;
+ }
+ return nullptr;
+ }
+ #endif // EASTL_RTTI_ENABLED
+
+ private:
+ bool HaveManager() const EA_NOEXCEPT
+ {
+ return (mMgrFuncPtr != nullptr);
+ }
+
+ void Destroy() EA_NOEXCEPT
+ {
+ if (HaveManager())
+ {
+ (void)(*mMgrFuncPtr)(static_cast<void*>(&mStorage), nullptr,
+ Base::ManagerOperations::MGROPS_DESTRUCT_FUNCTOR);
+ }
+ }
+
+ void Copy(const function_detail& other)
+ {
+ if (other.HaveManager())
+ {
+ (void)(*other.mMgrFuncPtr)(static_cast<void*>(&mStorage),
+ const_cast<void*>(static_cast<const void*>(&other.mStorage)),
+ Base::ManagerOperations::MGROPS_COPY_FUNCTOR);
+ }
+
+ mMgrFuncPtr = other.mMgrFuncPtr;
+ mInvokeFuncPtr = other.mInvokeFuncPtr;
+ }
+
+ void Move(function_detail&& other)
+ {
+ if (other.HaveManager())
+ {
+ (void)(*other.mMgrFuncPtr)(static_cast<void*>(&mStorage), static_cast<void*>(&other.mStorage),
+ Base::ManagerOperations::MGROPS_MOVE_FUNCTOR);
+ }
+
+ mMgrFuncPtr = other.mMgrFuncPtr;
+ mInvokeFuncPtr = other.mInvokeFuncPtr;
+ other.mMgrFuncPtr = nullptr;
+ other.mInvokeFuncPtr = &DefaultInvoker;
+ }
+
+ template <typename Functor>
+ void CreateForwardFunctor(Functor&& functor)
+ {
+ using DecayedFunctorType = typename eastl::decay<Functor>::type;
+ using FunctionManagerType = typename Base::template function_manager<DecayedFunctorType, R, Args...>;
+
+ if (internal::is_null(functor))
+ {
+ mMgrFuncPtr = nullptr;
+ mInvokeFuncPtr = &DefaultInvoker;
+ }
+ else
+ {
+ mMgrFuncPtr = &FunctionManagerType::Manager;
+ mInvokeFuncPtr = &FunctionManagerType::Invoker;
+ FunctionManagerType::CreateFunctor(mStorage, eastl::forward<Functor>(functor));
+ }
+ }
+
+ private:
+ typedef void* (*ManagerFuncPtr)(void*, void*, typename Base::ManagerOperations);
+ typedef R (*InvokeFuncPtr)(Args..., const FunctorStorageType&);
+
+ EA_DISABLE_GCC_WARNING(-Wreturn-type);
+ EA_DISABLE_CLANG_WARNING(-Wreturn-type);
+ EA_DISABLE_VC_WARNING(4716); // 'function' must return a value
+ // We cannot assume that R is default constructible.
+ // This function is called only when the function object CANNOT be called because it is empty,
+ // it will always throw or assert so we never use the return value anyways and neither should the caller.
+ static R DefaultInvoker(Args... /*args*/, const FunctorStorageType& /*functor*/)
+ {
+ #if EASTL_EXCEPTIONS_ENABLED
+ throw eastl::bad_function_call();
+ #else
+ EASTL_ASSERT_MSG(false, "function_detail call on an empty function_detail<R(Args..)>");
+ #endif
+ };
+ EA_RESTORE_VC_WARNING();
+ EA_RESTORE_CLANG_WARNING();
+ EA_RESTORE_GCC_WARNING();
+
+
+ ManagerFuncPtr mMgrFuncPtr = nullptr;
+ InvokeFuncPtr mInvokeFuncPtr = &DefaultInvoker;
+ };
+
+ } // namespace internal
+
+} // namespace eastl
+
+#endif // EASTL_FUNCTION_DETAIL_H