https://gcc.gnu.org/bugzilla/show_bug.cgi?id=128288
Bug ID: 128288
Summary: Struct filled by a 4-iteration loop is copied through
the stack with 16-byte loads after 8-byte stores (loop
unrolled only after SRA)
Product: gcc
Version: 16.2.0
Status: UNCONFIRMED
Keywords: missed-optimization
Severity: normal
Priority: P3
Component: tree-optimization
Assignee: unassigned at gcc dot gnu.org
Reporter: chfast at gmail dot com
CC: chfast at gmail dot com
Target Milestone: ---
Target: x86_64-pc-linux-gnu
struct u256 { unsigned long long w[4]; };
void add(struct u256 *r, const struct u256 *x, const struct u256 *y)
{
struct u256 s;
unsigned long long k = 0;
for (int i = 0; i < 4; ++i)
s.w[i] = __builtin_addcll(x->w[i], y->w[i], k, &k);
*r = s;
}
GCC 16.2 -O3 (the same with GCC 14, 15 and trunk):
add:
movq (%rsi), %rax
addq (%rdx), %rax
movq %rax, -40(%rsp)
movq 8(%rsi), %rax
adcq 8(%rdx), %rax
movq %rax, -32(%rsp)
movq 16(%rsi), %rax
movdqa -40(%rsp), %xmm0
adcq 16(%rdx), %rax
movq %rax, -24(%rsp)
movq 24(%rdx), %rax
adcq 24(%rsi), %rax
movups %xmm0, (%rdi)
movq %rax, -16(%rsp)
movdqa -24(%rsp), %xmm0
movups %xmm0, 16(%rdi)
ret
The words of s are stored with 8-byte stores and read back with 16-byte loads,
so store-to-load forwarding fails. cunrolli does not unroll the loop ("Not
unrolling loop 1: size would grow."), so SRA sees s.w[i] with a variable index
("Disqualifying s - No scalar replacements to be created."). cunroll unrolls
the loop later, after SRA.
With #pragma GCC unroll 4 the loop is unrolled by cunrolli, s is scalarized and
the words are stored to *r directly:
add:
movq (%rdx), %rax
addq (%rsi), %rax
movq 8(%rsi), %rcx
movq %rax, %r8
adcq 8(%rdx), %rcx
movq 16(%rsi), %rax
movq 24(%rsi), %rsi
adcq 16(%rdx), %rax
adcq 24(%rdx), %rsi
movq %r8, (%rdi)
movq %rcx, 8(%rdi)
movq %rax, 16(%rdi)
movq %rsi, 24(%rdi)
ret
Calling add in a loop takes 9.2 ns per call at -O3, 1.8 ns with the pragma and
1.2 ns with clang -O3 (Ryzen 9 5950X). We see the same store and copy pattern
in intx (a multi-precision integer library), in the results of its division and
addmod code.
Related: PR80689 (struct copy with 128-bit loads after scalar stores), PR128248
(the same loop is not unrolled at -O2).