https://gcc.gnu.org/bugzilla/show_bug.cgi?id=127317
Bug ID: 127317
Summary: [x86-64] the rewritten guard of an inlined safe-math
helper re-loads the operand from memory instead of
testing the register — same global read twice under
-O2/-O3, regression since gcc-15
Product: gcc
Version: 16.2.0
Status: UNCONFIRMED
Keywords: missed-optimization
Severity: normal
Priority: P3
Component: c
Assignee: unassigned at gcc dot gnu.org
Reporter: 220255624 at seu dot edu.cn
CC: jianhao.xu at seu dot edu.cn
Target Milestone: ---
Hi, I found such a case:
#include <stdint.h>
int8_t g = 7;
__attribute__((noinline)) int func_guard(int8_t y)
{
int8_t a = (int8_t)(0x85 ^ g);
if (y == 0 || (a == -128 && y == -1)) return (int)a | 8;
return (int)(int8_t)(a / y) | 8;
}
Compile: gcc -O2 -S t.c (Live: https://gcc.godbolt.org/z/98vYP53fe)
`a == -128 && y == -1` is the guard gcc generates for inlined safe_div /
safe_mod helpers (the case was originally found in csmith-generated code
using safe_mod_func_int8_t_s_s). gcc 16.2 and 15.2 would produce under -O2:
func_guard:
movzbl g(%rip),%eax # first load of g
xor $0xffffff85,%eax # a = g ^ 0x85, now in %al
test %dil,%dil
je .L
cmpb $0x5,g(%rip) # second load of the same byte: the
rewritten
jne .L # guard (g == 5) is a memory compare
cmp $0xff,%dil
je .L
movsbl %al,%eax # a is used again right here
...
and gcc 14.2 (also 16.2 with -O1):
func_guard:
movzbl g(%rip),%edx
mov %edx,%eax
xor $0xffffff85,%eax
test %dil,%dil
je .L
cmp $0x5,%dl # the value stays in a register
...
I guess the latter is better? Rewriting `(g ^ 0x85) == 0x80` into `g == 5`
is fine, but the rewritten compare then re-reads g from memory even though
the byte is already in a register and is used again two instructions later
(testing `a == -128` as `cmpb $0x80,%al` would need no load at all).
With this testcase gcc 14.2 is clean at -O2 and -O3, gcc 15.2 and 16.2 are
not, so it looks like a regression in how the comparison is materialized.