This is the mail archive of the gcc-help@gcc.gnu.org mailing list for the GCC project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Empty destructor definition disables optimization.


Hello,

I was reading the assembly output for a specific usecase of a custom
optional<T> implementation I'm writing to see if the `optional<T>` abstraction
would incur in more overhead than what I was expecting.

This is the samallest and simplest sample code I came up with that illustrates
the problem I'm having:

    #include <exception>

    class M
    {
    public:
        int  value;
        bool valid;

       ~M() { }

        int get() const
        {
            if (valid)
                return value;
            std::terminate();
        }
    };

    M    func_1();
    void func_2(int a);

    int main(int, char**)
    {
        const auto i = func_1();
        if (i.valid)
        {
            func_2(i.get());
            func_2(i.get());
            func_2(i.get());
        }

        return 0;
    }

Now the issue is that the compiler checks `i.valid` to be true on each call to
`i.get()` even though the enclosing `if` statement has already done so.

This is the assembly output from g++ 6.2.1 (compiled with -O3)

    main:
            sub     rsp, 24
            mov     rdi, rsp
            call    func_1()
            cmp     BYTE PTR [rsp+4], 0
            jne     .L7
    .L2:
            xor     eax, eax
            add     rsp, 24
            ret
    .L7:
            mov     edi, DWORD PTR [rsp]
            call    func_2(int)
            cmp     BYTE PTR [rsp+4], 0
            mov     edi, DWORD PTR [rsp]
            je      .L3
            call    func_2(int)
            cmp     BYTE PTR [rsp+4], 0
            mov     edi, DWORD PTR [rsp]
            je      .L3
            call    func_2(int)
            jmp     .L2
    .L3:
            call    std::terminate()

The following slight modification produces better code:

    // class M ....

    M    func_1();
    void func_2(...);

    int main(int, char**)
    {
        const auto i = func_1();
        if (i.valid)
        {
            func_2(i.get(), i.get(), i.get());
        }

        return 0;
    }

--> (compiled with -O3)

    main:
            sub     rsp, 24
            mov     rdi, rsp
            call    func_1()
            cmp     BYTE PTR [rsp+4], 0
            je      .L2
            mov     edi, DWORD PTR [rsp]
            xor     eax, eax
            mov     edx, edi
            mov     esi, edi
            call    func_2(...)
    .L2:
            xor     eax, eax
            add     rsp, 24
            ret

After doing a bunch of tests I found out that by removing the destructor
definition from `M` the generated code for the first code example becomes:

    main:
            push    rbx
            call    func_1()
            mov     rbx, rax
            shr     rax, 32
            test    al, al
            je      .L2
            mov     edi, ebx
            call    func_2(int)
            mov     edi, ebx
            call    func_2(int)
            mov     edi, ebx
            call    func_2(int)
    .L2:
            xor     eax, eax
            pop     rbx
            ret

That's basically what I was expecting.
Of course removing the destructor definition is not an option for the the real
code I'm working on (the `value` is inside a union, etc).

Why is the compiling not able to optimize those comparison away?
clang++ also generates basically the same code as g++ in all cases which makes
me think I must be overlooking something important there.

Thanks a lot!
Juan.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]