Comments for Rants from Vas https://rants.vastheman.com Take a hit with V-Real Tue, 06 Jun 2023 06:09:58 +0000 hourly 1 https://wordpress.org/?v=4.9.10 Comment on MSVC C++ ABI Member Function Pointers by vastheman https://rants.vastheman.com/2021/09/21/msvc/#comment-427639 Tue, 06 Jun 2023 06:09:58 +0000 https://rants.vastheman.com/?p=337#comment-427639 I have updated the text to cover the case where a class declares at least one virtual member function while deriving from a single base class that has no virtual member functions. Thanks again for bringing this to my attention, ykiko.

]]>
Comment on MSVC C++ ABI Member Function Pointers by vastheman https://rants.vastheman.com/2021/09/21/msvc/#comment-427581 Mon, 05 Jun 2023 15:41:25 +0000 https://rants.vastheman.com/?p=337#comment-427581 Hi ykiki, thanks for this analysis.

You are correct, I did not consider the case of a class with virtual member functions with a base class that has no virtual member functions. The “multiple inheritance” implementation is used in that case. I will update the content to reflex this. Thank you for thinking of this and pointing out the error.

I will try to explain the other statements in more detail.

“It is possible to invoke a member pointer using this representation without access to the class definition,” means it is possible to call a “single inheritance” or “multiple inheritance” member function when only a forward declaration of the class is available. Consider the following code fragment:

struct __single_inheritance   A; // forward declaration of class with single inheritance
struct __multiple_inheritance B; // forward declaration of class with multiple inheritance
struct __virtual_inheritance  C; // forward declaration of class with virtual inheritance

typedef void (A::*a_func)(int); // pointer to member function of class with single inheritance
typedef void (B::*b_func)(int); // pointer to member function of class with multiple inheritance
typedef void (C::*c_func)(int); // pointer to member function of class with virtual inheritance

void invoke_a_func(a_func f, A &obj, int i)
{
    // can call "single inheritance" pointer to member function without access to class definition
    (obj.*f)(i);
}

void invoke_b_func(b_func f, B &obj, int i)
{
    // can call "multiple inheritance" pointer to member function without access to class definition
    (obj.*f)(i);
}

void invoke_c_func(c_func f, C &obj, int i)
{
    // cannot call "virtual inheritance" pointer to member function without access to class definition
    // error C2027: use of undefined type 'C'
    (obj.*f)(i);
}

Definitions for classes A, B and C are not available (only forward declarations are available). In this situation, it is possible for the compiler to generate code to invoke a pointer to a member function of “single inheritance” class A or “multiple inheritance” class B. However, it is not possible for the compiler to generate code to invoke a pointer to a member function of “virtual inheritance” class C, so calling the member function pointer in invoke_c_func results in an error.

To understand the performance analysis, it’s necessary to look at how code is generated to invoke the function pointers. Consider this fragment of code:

struct A
{
    void test_direct() {}
    virtual void test_vtable() {}
};

typedef void (A::*a_func)();

void invoke(A &obj, a_func f)
{
    (obj.*f)();
}

void test()
{
    A obj;
    invoke(obj, &A::test_direct);
    invoke(obj, &A::test_vtable);
}

We have a “single inheritance” class called A, and we use pointers to member functions to invoke both a non-virtual member function and a virtual member function.

Here are the important parts of the intermediate assembly language output when compiled with MSVC 19.29 for x86-64, with explanatory comments added (remember this is Intel assembler syntax, so destination operands come before source operands):

this$ = 8
void A::test_direct(void) PROC
    mov     QWORD PTR [rsp+8], rcx              ; save rcx ('this' pointer) in home space on stack
    ret     0                                   ; return
void A::test_direct(void) ENDP

this$ = 8
virtual void A::test_vtable(void) PROC
    mov     QWORD PTR [rsp+8], rcx              ; save rcx ('this' pointer) in home space on stack
    ret     0                                   ; return
virtual void A::test_vtable(void) ENDP

[thunk]:A::`vcall'{0,{flat}}' }' PROC
    mov     rax, QWORD PTR [rcx]                ; load address of vtable into rax
    jmp     QWORD PTR [rax]                     ; jump to first function in vtable
[thunk]:A::`vcall'{0,{flat}}' }' ENDP

obj$ = 48
f$ = 56
void invoke(A &,void (__cdecl A::*)(void)) PROC
$LN3:
    mov     QWORD PTR [rsp+16], rdx             ; save rdx ('f') in home space on stack
    mov     QWORD PTR [rsp+8], rcx              ; save rcx ('obj') in home space on stack
    sub     rsp, 40                             ; allocate minimal stack frame

    ; (obj.*f)();
    mov     rcx, QWORD PTR obj$[rsp]            ; load 'obj' into rcx from stack ('this' argument)
    call    QWORD PTR f$[rsp]                   ; call function pointer 'f' on stack

    add     rsp, 40                             ; deallocate stack frame
    ret     0                                   ; return
void invoke(A &,void (__cdecl A::*)(void)) ENDP

obj$ = 32
void test(void) PROC
$LN3:
    sub     rsp, 56                                             ; allocate stack frame

    ; A obj;
    lea     rcx, QWORD PTR obj$[rsp]                            ; construct instance of A on stack ('obj')
    call    A::A(void)

    ; invoke(obj, &A::test_direct);
    lea     rdx, OFFSET FLAT:void A::test_direct(void)          ; put address of 'A::test_direct' in rdx ('f' argument)
    lea     rcx, QWORD PTR obj$[rsp]                            ; put pointer to 'obj' in rcx ('obj' argument)
    call    void invoke(A &,void (__cdecl A::*)(void))          ; call 'invoke'

    ; invoke(obj, &A::test_vtable);
    lea     rdx, OFFSET FLAT:[thunk]:A::`vcall'{0,{flat}}' }'   ; put address of virtual function call stub in rdx ('f' argument)
    lea     rcx, QWORD PTR obj$[rsp]                            ; put pointer to 'obj' in rcx ('obj' argument)
    call    void invoke(A &,void (__cdecl A::*)(void))          ; call 'invoke'

    add     rsp, 56                                             ; deallocate stack frame
    ret     0                                                   ; return
void test(void) ENDP

Let’s analyse the code one piece at a time:

  • The content of A::test_direct and A::test_vtable is not important. They do nothing useful anyway. They’re just included to show that they’re regular functions.
  • The compiler has generated a stub function for calling the first member function from the vtable of an instance of class A. Note that it consists of two instructions: an integer load to get the vtable address (one fetch), and an indirect jump to the first member function in the vtable (one fetch, with address dependent on previous fetch).
  • The function invoke calls the member function pointer in the same way it would call a pointer to a non-member function, using a single indirect call (after setting up arguments).
  • To create the pointer to the non-virtual member function in test, the address of the function A::test_direct is used directly.
  • To create the pointer to the virtual member function in test, the address of the generated stub function for calling the member function from vtable is used.

Note that there are no conditional jump or conditional move instructions. This is beneficial for performance on CPUs that can execute large numbers of instructions in parallel (e.g. with a large number of execution units and/or a long multi-stage instruction execution pipeline). Conditional branches can harm performance in multiple ways. They consume limited branch prediction resources, and if a conditional branch is predicted incorrectly any speculatively executed instructions following the branch need to be discarded.

Considering what happens when the member function pointers are called:

  • When the pointer to the non-virtual member function is called, the indirect call instruction will jump directly to A::test_direct. There is no additional overhead compared to calling a non-member function pointer.
  • When the pointer to the virtual member function is called, the indirect call instruction will jump to the stub function for calling the first member function from the vtable. This function consists of two instructions (a load and an indirect jump), and performs two memory fetches (the vtable address and the virtual member function address) in order to jump to the correct implementation of the virtual member function.

The additional overhead for calling a pointer to a virtual member function comes from the generated stub function used to call the member function at the appropriate position in the vtable.

Now consider what will happen when this code is compiled for the Itanium C++ ABI. Here are the important parts of the intermediate assembly language output when compiled with MinGW GCC 11.3 for x86-64, with explanatory comments added (remember this is Intel assembler syntax, so destination operands come before source operands):

A::test_direct():
    ret                                 ; return

A::test_vtable():
    ret                                 ; return

invoke(A&, void (A::*)()):
    sub     rsp, 40                     ; allocate minimal stack frame

    ; (obj.*f)();
    mov     rax, QWORD PTR [rdx]        ; load function address/vtable index union into rax
    mov     r8, QWORD PTR 8[rdx]        ; load 'this' pointer offset into r8
    mov     rdx, rax                    ; copy function address/vtable index union into rdx
    test    al, 1                       ; test virtual member function flag
    je      .L4                         ; if non-virtual member function, jump to local label .L4
    mov     rdx, QWORD PTR [rcx+r8]     ; load address of vtable into rdx
    mov     rdx, QWORD PTR -1[rdx+rax]  ; load address of appropriate member function from vtable into rdx
.L4:
    add     rcx, r8                     ; add offset to 'this' pointer
    call    rdx                         ; call member function

    nop                                 ; padding
    add     rsp, 40                     ; deallocate stack frame
    ret                                 ; return

test():
    push    rsi                         ; save callee-preserved register rsi on stack
    push    rbx                         ; save callee-preserved register rbx on stack
    sub     rsp, 72                     ; allocate stack frame

    ; A obj;
    lea     rax, vtable for A[rip+16]   ; get pointer to vtable for A in rax
    mov     QWORD PTR 56[rsp], rax      ; set vtable pointer in instance of A 'obj' on stack

    ; invoke(obj, &A::test_direct);
    lea     rax, A::test_direct()[rip]  ; put address of 'A::test_direct' in rax
    mov     QWORD PTR 32[rsp], rax      ; store address on stack for function address/virtual table index union
    mov     QWORD PTR 40[rsp], 0        ; store zero on stack for 'this' pointer offset
    lea     rsi, 32[rsp]                ; put address of member function pointer into rsi so it will be saved across call
    lea     rbx, 56[rsp]                ; put address of 'obj' into rbx so it will be saved across call
    mov     rdx, rsi                    ; put address of member function pointer into rdx ('f' argument)
    mov     rcx, rbx                    ; put address of 'obj' into rcx ('obj' argument)
    call    invoke(A&, void (A::*)())   ; call 'invoke'

    ; invoke(obj, &A::test_direct);
    mov     QWORD PTR 32[rsp], 1        ; store virtual member function index and flag on stack for function address/virtual table index union
    mov     QWORD PTR 40[rsp], 0        ; store zero on stack for 'this' pointer offset
    mov     rdx, rsi                    ; put address of member function pointer into rdx ('f' argument)
    mov     rcx, rbx                    ; put address of 'obj' into rcx ('obj' argument)
    call    invoke(A&, void (A::*)())   ; call 'invoke'

    nop                                 ; padding
    add     rsp, 72                     ; deallocate stack frame
    pop     rbx                         ; restore callee-preserved register rbx from stack
    pop     rsi                         ; restore callee-preserved register rsi from stack
    ret                                 ; return

Let’s analyse this version of the generated code:

  • Once again, the content of A::test_direct and A::test_vtable is not important. They’re included to show that they’re regular functions.
  • The function invoke must test the flag indicating whether the member function pointer f represents a virtual member function or a non-virtual member function, and fetch the address of the member function from the vtable if necessary. This involves a conditional jump (je instruction).
  • Even though it can be determined that class A does not use multiple inheritance, the member function pointer implementation still provides support for adjusting the this pointer.
  • To create the pointer to the non-virtual member function in test, the address of the function A::test_direct is used directly.
  • To create the pointer to the virtual member function in test, the offset to the address of A::test_vtable in the vtable is used with the least significant bit set to indicate that it represents a virtual member function.

The following conclusions can be made:

  • With the Itanium ABI, there is always additional overhead for calling a pointer to a member function compared to calling a pointer to a non-member function. The caller must determine whether the pointer represents a virtual member function before the address to call can be determined. This involves a conditional jump, which can have a detrimental effect on performance.
  • With the Itanium ABI, provisions are always made for multiple inheritance. This causes additional overhead even in the simplest cases.
  • If the same member function pointer is to be called multiple times, the function address and adjusted this pointer can be cached. This amortises the overhead across multiple calls.

I hope this adequately explains the statements. Please let me know if you require further clarification.

]]>
Comment on MSVC C++ ABI Member Function Pointers by ykiki https://rants.vastheman.com/2021/09/21/msvc/#comment-427561 Mon, 05 Jun 2023 07:21:14 +0000 https://rants.vastheman.com/?p=337#comment-427561 Thank you for your reply. The article is long and fantastic, but there may be something wrong. In fact, in the part about single inheritance, you mentioned:

This minimal representation can be used because two assumptions can be made:

With non-virtual single inheritance, the base class (if any) always appears at the start of the class. A pointer to an instance of the class will not require adjustment when cast to or from a base class. Therefore, a pointer to a base class member function will not require this pointer adjustment when called.
For virtual member functions, the compiler will generate an out-of-line stub that fetches the appropriate virtual table entry and jumps to it.

I have found that for virtual member functions, things are not always the same.:

When the base class has a virtual pointer:

struct A
{
    void Test_A() {}
    virtual void Test() {}
};

struct B : public A
{
    void Test_B() {}
};

int main()
{
    auto a = &A::Test_A;
    auto b = &B::Test_B;
    std::cout << sizeof(a) << std::endl;
    std::cout << sizeof(b) << std::endl;
}

Both A and B have a virtual pointer at the start of their memory layout, so there is no need to store extra information.

However, if A does not have a virtual pointer but B does, because the virtual pointer of B is still at the start of its memory layout, at this time, the class A needs to offset by 8 bytes (the size of the virtual pointer).

struct A
{
    void Test_A() {}
    char data[32];
};

struct B : public A
{
    virtual void Test_B() {}
};

struct fp
{
    void* address;
    int offset;
};

int main()
{
    auto a = &A::Test_A;
    decltype(&B::Test_B) b = &B::Test_A;
    std::cout << sizeof(a) << std::endl;
    std::cout << sizeof(b) << std::endl;
    std::cout << reinterpret_cast<fp*>(&b)->offset << std::endl;
}

The sizeof(b) is 16 and the offset is 8. Although it is single inheritance, the reason behind this can be easily found.
So in this situation, it is similar to "Multiple inheritance"

Besides, I am not sure about the meaning of the following statement "It is possible to invoke a member pointer using this representation without access to the class definition. Performance for calling this representation is similar to calling a non-member function pointer for non-virtual member functions. For virtual member functions, there is an additional fetch and indirect branch. However, there are no conditional branches involved, which avoids performance penalties on deeply pipelined and/or highly parallel processors."

Could you please provide me with some examples to help me understand better? Thank you very much. Best wishes.

]]>
Comment on MSVC C++ ABI Member Function Pointers by vastheman https://rants.vastheman.com/2021/09/21/msvc/#comment-425298 Tue, 23 May 2023 15:19:51 +0000 https://rants.vastheman.com/?p=337#comment-425298 Hi ykiko. Thanks for your kind words. I’d be happy for you to publish a Chinese translation as long as you provide proper attribution. If you make your translated version publicly available, please link to it in a comment here.

]]>
Comment on MSVC C++ ABI Member Function Pointers by ykiko https://rants.vastheman.com/2021/09/21/msvc/#comment-425242 Tue, 23 May 2023 11:25:21 +0000 https://rants.vastheman.com/?p=337#comment-425242 Apologies for the grammar errors in my previous email. Here is the improved version:

Hi, I am a student from China. I have read the entire article and tried all the member function pointer examples on my computer. The results are consistent with what you mentioned above. The article is excellent, and I would like to translate it into Chinese and publish it in our community. I kindly request your permission, and I will properly attribute the original source and include your name.

]]>
Comment on MSVC C++ ABI Member Function Pointers by ykiko https://rants.vastheman.com/2021/09/21/msvc/#comment-425234 Tue, 23 May 2023 11:03:26 +0000 https://rants.vastheman.com/?p=337#comment-425234 Hi, I am a student from China. I have read the whole article and try all the description of member function pointer on my computer. The result is same as what you say above. The article is so nice that I want to translate it to Chinese and publish in our community. I would like to request your permission. I will mark the original source and include the your name. Thank you very much.

]]>
Comment on I Hate Firefox! by NotLoyalToAnyBrowserAnymore https://rants.vastheman.com/2007/08/19/firefox/#comment-415814 Mon, 10 Apr 2023 18:54:20 +0000 http://rants.vastheman.com/index.php/2007/08/19/firefox/#comment-415814 Firefox is dead, they sold out to Google and now steal all your private information.It was nice IMOP for a while. I use Vivaldi now. While I want privacy, I’ll have to settle for power user controls. (Sort if they don’t give a huge amount and no about: config.) It’s still better then firefu** fullbon Google (I say we call it Gobble as it eats up companys…)

Now all I care about is having a browser that works, lets ke have a home page, tabs, bookmarks, history and extensions.

]]>
Comment on The Q Factor by Lorenzo https://rants.vastheman.com/2018/03/20/qfactor/#comment-406701 Tue, 14 Feb 2023 20:46:43 +0000 https://rants.vastheman.com/?p=315#comment-406701 Yes, I could definitely hear the spatial effect on it. The other game where I could hear something similar was the lasergame Space Ace, maybe just because of a particularly good sound mix and not because of intended spatialization effect.

]]>
Comment on Lifting a jinx? by vastheman https://rants.vastheman.com/2019/08/13/jinx/#comment-294818 Wed, 13 Apr 2022 21:51:43 +0000 https://rants.vastheman.com/?p=332#comment-294818 Unfortunately I still have no idea which Jenny Molloy the loco was named after. One doesn’t see her as frequently at Kensington now that they tend to pair a P class loco with an S class for that run. Perhaps the drivers started demanding cab air conditioning?

]]>
Comment on Lifting a jinx? by Anna https://rants.vastheman.com/2019/08/13/jinx/#comment-294184 Wed, 13 Apr 2022 00:04:07 +0000 https://rants.vastheman.com/?p=332#comment-294184 I too want to know why they renamed the S317 after Jenny Molloy. I have searched the name and came up with 2 contenders for the name sake, one is a scientist and one an author. Any ideas since?

]]>