https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105260
Bug ID: 105260
Summary: Union with user-defined empty destructor leads to
worse code-gen
Product: gcc
Version: 12.0
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: c++
Assignee: unassigned at gcc dot gnu.org
Reporter: m.cencora at gmail dot com
Target Milestone: ---
Following code leads to unnecessary stack spill of deserialized Foo variable:
g++ -std=c++17 -O2
#include <new>
inline unsigned deserializeUInt(const unsigned char* &in)
{
unsigned out;
__builtin_memcpy(&out, in, sizeof(out));
in += sizeof(out);
out = __builtin_bswap32(out);
return out;
}
struct Foo
{
unsigned a;
unsigned b;
static Foo deserialize(const unsigned char* &in)
{
return Foo{
deserializeUInt(in),
deserializeUInt(in)
};
}
};
struct Result
{
unsigned idx;
union
{
unsigned a;
const void* ptr;
};
};
Result dummyFunc(Foo);
void deserializeAndInvoke(const unsigned char* it)
{
#ifndef WORKAROUND
union NoDestroy
{
~NoDestroy() {}
Foo value;
};
NoDestroy un{
Foo::deserialize(it)
};
auto& arg = un.value;
#elif WORKAROUND == 1
union NoDestroy
{
Foo value;
};
NoDestroy un{
Foo::deserialize(it)
};
auto& arg = un.value;
#elif WORKAROUND == 2
alignas(Foo) char rawStorage[sizeof(Foo)];
auto& arg = *new (&rawStorage[0]) Foo{
Foo::deserialize(it)
};
#endif
dummyFunc(arg);
}
deserializeAndInvoke(unsigned char const*):
mov edx, DWORD PTR [rdi]
mov eax, DWORD PTR [rdi+4]
bswap edx
bswap eax
mov DWORD PTR [rsp-16], edx
mov DWORD PTR [rsp-12], eax
mov rdi, QWORD PTR [rsp-16]
jmp dummyFunc(Foo)
The spill can be avoided, when we remove user-defined destructor of NoDestroy
union, or
when we construct Foo via placement new in raw buffer.
Generated code with either of the workarounds:
g++ -std=c++17 -O2 -DWORKAROUND=1
or
g++ -std=c++17 -O2 -DWORKAROUND=2
deserializeAndInvoke(unsigned char const*):
mov rax, rdi
mov edi, DWORD PTR [rdi]
mov eax, DWORD PTR [rax+4]
bswap edi
mov edi, edi
bswap eax
sal rax, 32
or rdi, rax
jmp dummyFunc(Foo)