Nugget
Public Types | Static Public Member Functions | List of all members
eastl::internal::function_base_detail< SIZE_IN_BYTES >::function_manager< Functor, R, Args > Class Template Referencefinal
Inheritance diagram for eastl::internal::function_base_detail< SIZE_IN_BYTES >::function_manager< Functor, R, Args >:
Inheritance graph
[legend]
Collaboration diagram for eastl::internal::function_base_detail< SIZE_IN_BYTES >::function_manager< Functor, R, Args >:
Collaboration graph
[legend]

Public Types

using Base = function_manager_base< Functor >
 

Static Public Member Functions

static R Invoker (Args... args, const FunctorStorageType &functor)
 
- Static Public Member Functions inherited from eastl::internal::function_base_detail< SIZE_IN_BYTES >::function_manager_base< Functor, typename >
static Functor * GetFunctorPtr (const FunctorStorageType &storage) EA_NOEXCEPT
 
template<typename T >
static void CreateFunctor (FunctorStorageType &storage, T &&functor)
 
static void DestructFunctor (FunctorStorageType &storage)
 
static void CopyFunctor (FunctorStorageType &to, const FunctorStorageType &from)
 
static void MoveFunctor (FunctorStorageType &to, FunctorStorageType &from) EA_NOEXCEPT
 
static void * Manager (void *to, void *from, typename function_base_detail::ManagerOperations ops) EA_NOEXCEPT
 

Member Function Documentation

◆ Invoker()

template<int SIZE_IN_BYTES>
template<typename Functor , typename R , typename... Args>
static R eastl::internal::function_base_detail< SIZE_IN_BYTES >::function_manager< Functor, R, Args >::Invoker ( Args...  args,
const FunctorStorageType functor 
)
inlinestatic

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.


The documentation for this class was generated from the following file: