C++ Logo

std-proposals

Advanced search

Re: [std-proposals] std::any::base

From: Frederick Virchanza Gotham <cauldwell.thomas_at_[hidden]>
Date: Tue, 16 Apr 2024 15:51:34 +0100
On Tue, Apr 16, 2024 at 12:08 PM Frederick Virchanza Gotham wrote:
>
> On the Microsoft compiler, we really need to test it with the following class:
>
> class Base { int i, j; }
> class Derived : public Base { virtual ~Derived(void){} };
>
> An object of type 'Derived' will be laid out in memory by the
> Microsoft compiler as follows:
>
> - - - 4 bytes = i
> - - - 4 bytes = j
> - - - 8 bytes = pointer to vtable


I know some of you don't want me reverse-engineering Microsoft's
compiler here on the mailing list but we need to find out if this can
be implemented without an ABI break. So I fed the following into
Visual Studio 2022:

    struct A {
        int a, b;
    };

    struct B {
        float c, d;
        virtual ~B(void){}
    };

    struct Donkey : A, B {
        double d;
    };

    Donkey d;

    void Func(void) noexcept(false)
    {
        throw d;
    }

Then I hit Ctrl + Alt + D to see the assembler generated for 'Func' as follows:

Func:
    1: sub rsp,48h
    2: lea rdx,[d]
    3: lea rcx,[rsp+20h]
    4: call Donkey::Donkey
    5: lea rdx,[_TI3?AUDonkey@@]
    6: lea rcx,[rsp+20h]
    7: call _CxxThrowException

which can be explained line by line as follows:

Func:
    1: Allocate 72 bytes on the stack
    2: Set 2nd argument to address of 'd'
    3: Set 1st argument to address of 40 bytes of stack space
    4: Invoke the copy-constructor of Donkey with above two arguments
    5: Set 2nd argument to address of ThrowInfo for Donkey
    6: Set 1st argument to address of cloned object on the stack
    7: Call _CxxThrowException with above two arguments

We can convert the above 7 instructions to the following C++ code:

    void Func(void) noexcept(false)
    {
        char stack_space[72u]; // Allocate space on the stack
        ::new(stack_space + 32) Donkey(&d); // Create a cloned object
        _CxxThrowException( stack_space + 32, __get_throw_info(Donkey)
); // Throw an exception
    }

We can see the function signature of _CxxThrowException on the MSDN
website as follows:

    void _CxxThrowException( void *pExceptionObject, ThrowInfo *pThrowInfo );

There is something promising here: The function '_CxxThrowException'
is not passed the address of the vtable pointer inside Donkey, and
therefore it must be getting it from the ThrowInfo struct.

So to summarise: It will be possible to implement this on the
Microsoft compiler for any class type (including the most complex
cases of virtual multiple inheritance) so long as we can utilise the
'type_info' to get the 'ThrowInfo'.

So the missing piece now is just to come up with a function as follows:

    ThrowInfo const *get_ThrowInfo_from_type_info(type_info const*);

I'm confident that this can be implemented, because if you look at the
signature of "__RTDynamicCast", it's able to perform a 'dynamic_cast'
using only the 'type_info'. So there must be a link from the type_info
to either:
    (a) RTTICompleteObjectLocator
or
    (b) ThrowInfo

And there's probably a link from RTTICompleteObjectLocator to
ThrowInfo. I'll disassemble "__RTDynamicCast" and try to find out how
Microsoft is doing it, and come up with an implementation of
std::any::base that works properly in every scenario.

Received on 2024-04-16 14:51:48