https://gcc.gnu.org/bugzilla/show_bug.cgi?id=127699
Bug ID: 127699
Summary: Clearing of small padded aggregates before SRA forces
stack round-trips
Product: gcc
Version: 16.2.1
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: tree-optimization
Assignee: unassigned at gcc dot gnu.org
Reporter: chfast at gmail dot com
Target Milestone: ---
Initializing a small struct with padding through aggregate initialization
(return R{s, c};) produces much worse code since
r15-3328-g673a448aa24efedd5ac140ebf7bfe652d7a6a846 ("Optimize initialization of
small padded objects").
https://godbolt.org/z/5csvafs3W
struct R
{
unsigned long value;
bool carry;
};
static inline R add(unsigned long x, unsigned long y)
{
const unsigned long s = x + y;
return R{s, s < x};
}
unsigned long f(unsigned long x, unsigned long y, unsigned long z, unsigned
long d)
{
const R r1 = add(x, y);
const R r2 = add(r1.value, z);
return r2.value - (d & -(unsigned long)r1.carry) - (d & -(unsigned
long)r2.carry);
}
GCC 14.2 -O2:
add rdi, rsi
mov eax, 0
setc al
add rdx, rdi
adc rax, 0
imul rax, rcx
sub rdx, rax
mov rax, rdx
ret
GCC 15.1, 15.2 and trunk -O2:
mov DWORD PTR [rsp-24], 0
xor eax, eax
add rdi, rsi
movabs rsi, 72057594037927935
mov DWORD PTR [rsp-21], 0
mov r8, QWORD PTR [rsp-24]
setc al
mov DWORD PTR [rsp-24], 0
and r8, rsi
mov DWORD PTR [rsp-21], 0
sal r8, 8
mov r9, r8
xor r8d, r8d
add rdx, rdi
setc r8b
and rsi, QWORD PTR [rsp-24]
mov r9b, al
sal rsi, 8
movzx eax, r9b
mov rdi, rsi
mov dil, r8b
movzx esi, dil
add rax, rsi
imul rax, rcx
sub rdx, rax
mov rax, rdx
ret
Since r15-3328 gimplify_init_constructor clears the object first because it has
padding:
s = x + y;
D.3034 = {};
D.3034.value = s;
_1 = s < x;
D.3034.carry = _1;
return D.3034;
The padding bytes now have a value that must be preserved. ESRA creates
replacements for value (offset 0, size 64) and carry (offset 64, size 8), but
the rest of the second 8 bytes stays in memory (MEM <char[8]> [(struct R
*)&D.3042 + 8B], grp_unscalarized_data = 1). This gives the stack stores of
zeros, the reload and the and/sal 8/byte merge for every R.
The comment of the change says "if there aren't any [padding bits], the
compiler is smart enough to optimize the clearing out", but here the clearing
is what creates the padding contents that cannot be optimized out.
-fno-tree-sra avoids the stack round-trips (17 instructions). So does
initializing the members another way: a constructor R(unsigned long v, bool c)
: value{v}, carry{c} {} (11 instructions) or assigning the members to a
default-initialized R. The C++ semantics of these is the same as the aggregate
initialization above: the padding is not zero-initialized in any of them.
The same change also made the testcase of PR106786 worse (7 -> 14 instructions
with stack accesses in GCC 15). This pattern (a carry flag returned as
{uint64_t, bool}) is common in multi-precision arithmetic; e.g. the intx
library is affected.