https://gcc.gnu.org/bugzilla/show_bug.cgi?id=128248
Bug ID: 128248
Summary: -O2 does not unroll a 4-iteration add-with-carry loop
("size would grow"), although the unrolled code is not
larger
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
At -O2 this loop is not unrolled:
struct u256 { unsigned long long w[4]; };
void add(struct u256 *r, const struct u256 *x, const struct u256 *y)
{
unsigned long long k = 0;
for (int i = 0; i < 4; ++i)
r->w[i] = __builtin_addcll(x->w[i], y->w[i], k, &k);
}
-fdump-tree-cunroll-details:
Loop size: 8
Estimated size after unrolling: 16-6
Not unrolling loop 1: size would grow.
GCC 16.2 -O2 (GCC 14, 15 and trunk also keep the loop):
add:
movq %rdi, %r8
xorl %eax, %eax
movq %rsi, %rdi
movq %rdx, %rsi
xorl %edx, %edx
.L2:
movq (%rdi,%rdx), %rcx
addb $-1, %al
adcq (%rsi,%rdx), %rcx
movq %rcx, (%r8,%rdx)
setc %al
addq $8, %rdx
cmpq $32, %rdx
jne .L2
ret
-O3 unrolls it into the same number of instructions, with the carry kept in the
flags:
add:
movq (%rsi), %rax
addq (%rdx), %rax
movq %rax, (%rdi)
movq 8(%rsi), %rax
adcq 8(%rdx), %rax
movq %rax, 8(%rdi)
movq 16(%rsi), %rax
adcq 16(%rdx), %rax
movq %rax, 16(%rdi)
movq 24(%rdx), %rax
adcq 24(%rsi), %rax
movq %rax, 24(%rdi)
ret
The difference grows when the sum is used further, e.g. (x + y) mod m:
void addmod(struct u256 *r, const struct u256 *x, const struct u256 *y, const
struct u256 *m)
{
struct u256 s, d;
unsigned long long k = 0, b = 0;
for (int i = 0; i < 4; ++i)
s.w[i] = __builtin_addcll(x->w[i], y->w[i], k, &k);
for (int i = 0; i < 4; ++i)
d.w[i] = __builtin_subcll(s.w[i], m->w[i], b, &b);
*r = (k || !b) ? d : s;
}
At -O2 this is 39 instructions and 10.7 ns per call. With #pragma GCC unroll 4
on both loops it is 38 instructions (s and d stay in registers) and 3.6 ns
(Ryzen 9 5950X, called in a loop). We found this in intx (a multi-precision
integer library), whose uint256 addition and subtraction are such loops.