https://gcc.gnu.org/bugzilla/show_bug.cgi?id=128662
Bug ID: 128662
Summary: i386: bsr for __builtin_clz without lzcnt depends on
its destination
Product: gcc
Version: 17.0
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: target
Assignee: unassigned at gcc dot gnu.org
Reporter: hongyuw at gcc dot gnu.org
Target Milestone: ---
Testcase:
#include <stdio.h>
#include <x86intrin.h>
/* 31 - clz is bsr without LZCNT. */
__attribute__((noipa)) void
f (const unsigned *s, unsigned char *o, unsigned n)
{
for (unsigned i = 0; i < n; i++)
o[i] = 31 - __builtin_clz (s[i]);
}
static unsigned s[1024];
static unsigned char o[1024];
int
main (void)
{
for (int i = 0; i < 1024; i++)
s[i] = (i * 2654435761u) | 1;
unsigned long long t0 = __rdtsc ();
for (int r = 0; r < 1000000; r++)
{
f (s, o, 1024);
__asm__ volatile ("" ::: "memory");
}
unsigned long long t1 = __rdtsc ();
printf ("%d %.2f tsc/elt\n", o[5], (t1 - t0) / 1024e6);
return 0;
}
trunk (17.0.0, r17-5731-g48d3efd1bf4) -O2:
.L3:
bsrl (%rdi,%rax,4), %ecx
movl $31, %esi
xorl $31, %ecx
subl %ecx, %esi
movb %sil, (%r8,%rax)
addq $1, %rax
cmpq %rax, %rdx
jne .L3
The sematic of bsr/bsf defined in Intel SDM was "if the content of the source
operand is zero, the destination operand is unmodified", so the dependency was
a true wait on its dest. For the loop, each iteration's bsrl writes %ecx, which
the previous iteration's "xorl $31, %ecx" wrote, so bsr -> xor -> bsr is a
loop-carried chain even though the elements are independent (s[i] is never
zero).
Clearing the destination before the bsr breaks it:
.L3:
xorl %ecx, %ecx
bsrl (%rdi,%rax,4), %ecx
...
Result tsc:
bsr xor;bsr
Alder Lake P (12900K) 2.50 0.86
Zen 5 (9950X) 1.54 1.18
As in GCC rtl the bsr/bsf patterns are defined as overwrite the dest, and
CLZ/CTZ_DEFINED_VALUE_AT_ZERO is 0 when no BMI/LZCNT - we take the src==0 value
as unspecified. So there's no risk to insert this clear via postreload splitter
unless src==dest.