https://gcc.gnu.org/bugzilla/show_bug.cgi?id=121738
Bug ID: 121738
Summary: Optimization: ((x & y) == y) == ((x | y) == x) == ((~x
& y) == 0) should be target independent
Product: gcc
Version: 15.1.0
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: other
Assignee: unassigned at gcc dot gnu.org
Reporter: Explorer09 at gmail dot com
Target Milestone: ---
These three bitwise mask-and-compare expressions `((x & y) == y)`, `((x | y) ==
x)` and `((~x & y) == 0)` should be equivalent. While GCC does optimize these
into the same for some target architectures (such as x86), it can miss in other
architectures (such as RISC-V). This suggests me that GCC didn't treat these
three expressions as equivalent formally, and only optimize these in
target-specific optimization phases.
Because I don't know the details about "tree-optimization" or
"rtl-optimization", this is the best I can describe the problem. The
equivalence should be target-independent.
Test code:
```c
#include <stdbool.h>
bool test1a(unsigned int x, unsigned int y) {
return ((x & y) == y);
}
bool test1b(unsigned int x, unsigned int y) {
return ((x | y) == x);
}
bool test1c(unsigned int x, unsigned int y) {
return ((~x & y) == 0);
}
unsigned int test2a(unsigned int x, unsigned int y) {
if ((x & y) == y)
return x - y;
return 0x12345678;
}
unsigned int test2b(unsigned int x, unsigned int y) {
if ((x | y) == x)
return x - y;
return 0x12345678;
}
unsigned int test2c(unsigned int x, unsigned int y) {
if ((~x & y) == 0)
return x - y;
return 0x12345678;
}
```
GCC for RISC-V target generates different code (`-Os` option is used):
(It looks like the test1a version has the smallest code)
```assembly
test1a:
and a0,a0,a1
sub a0,a0,a1
seqz a0,a0
ret
test1b:
or a1,a0,a1
sub a0,a1,a0
seqz a0,a0
ret
test1c:
not a0,a0
and a0,a0,a1
seqz a0,a0
ret
```
GCC for x86 and ARM targets makes test1 the same code. However, when the
conditionals become slightly more complex, GCC starts to miss the optimization
even for x86 and ARM targets.
GCC for x86-64 (`-Os` option) produces:
```assembly
test2a:
movl %edi, %eax
andl %esi, %eax
subl %eax, %edi
cmpl %esi, %eax
movl $305419896, %eax
cmove %edi, %eax
ret
test2b:
movl %edi, %edx
orl %esi, %edx
movl %edx, %eax
subl %esi, %eax
cmpl %edi, %edx
movl $305419896, %edx
cmovne %edx, %eax
ret
test2c:
movl %edi, %eax
notl %edi
movl $305419896, %edx
subl %esi, %eax
testl %esi, %edi
cmovne %edx, %eax
ret
```
GCC for ARM64 (`-Os` option) produces:
```assembly
test2a:
bic w2, w0, w1
bics wzr, w1, w0
mov w0, 22136
movk w0, 0x1234, lsl 16
csel w0, w2, w0, eq
ret
test2b:
orr w2, w0, w1
cmp w2, w0
sub w1, w2, w1
mov w0, 22136
movk w0, 0x1234, lsl 16
csel w0, w1, w0, eq
ret
test2c:
sub w2, w0, w1
bics wzr, w1, w0
mov w0, 22136
movk w0, 0x1234, lsl 16
csel w0, w2, w0, eq
ret
```
Note the code became different.
Bug 121682 is related to this one. When the equivalence of ((x & y) == y) ==
((x | y) == x) == ((~x & y) == 0) has been implemented, bug 121682 would become
easier to solve.