```c #include <limits.h> #include <stdbool.h> bool func1(unsigned long long x, unsigned long long y) { return x > (ULLONG_MAX / y); } bool func2(unsigned long long x, unsigned long long y) { return y != 0 && x > (ULLONG_MAX / y); } bool func3(unsigned long long x, unsigned long long y) { unsigned long long res; return __builtin_umulll_overflow(x, y, &res); } ``` Expected result: All three functions produce the same code. Actual result: func1() and func3() optimize to same code, but func2() had a redundant (y != 0) check that is not optimized out. x86-64 gcc with "-Os" option (tested in Compiler Explorer, a.k.a. godbolt.org) ```x86asm func1: movq %rdi, %rax mulq %rsi seto %al ret func2: xorl %eax, %eax testq %rsi, %rsi je .L5 movq %rsi, %rax mulq %rdi seto %al movzbl %al, %eax .L5: andl $1, %eax ret ```
<bb 2> [local count: 1073741824]: if (y_4(D) != 0) goto <bb 3>; [50.00%] else goto <bb 4>; [50.00%] <bb 3> [local count: 536870912]: _1 = .MUL_OVERFLOW (y_4(D), x_5(D)); _2 = IMAGPART_EXPR <_1>; _8 = (bool) _2; <bb 4> [local count: 1073741824]: # iftmp.0_3 = PHI <_8(3), 0(2)> return iftmp.0_3;
Reduced down to just: ``` bool func4(unsigned long long x, unsigned long long y) { if (y == 0) return 0; unsigned long long res; return __builtin_umulll_overflow(x, y, &res); } ```
The func4 example that Andrew Pinski provided makes me realize that this func5 should optimize to the same code: ```c bool func5(unsigned long long x, unsigned long long y) { if ((x | y) == 0) return 0; unsigned long long res; return __builtin_umulll_overflow(x, y, &res); } ```
Oops. Correction. I mean this: ```c bool func5(unsigned long long x, unsigned long long y) { /* (x == 0 || y == 0) */ if ((x & y) == 0) return 0; unsigned long long res; return __builtin_umulll_overflow(x, y, &res); } ```
Sorry again. My mind wasn't cleaned up when I wrote the comment. It's (x == 0 || y == 0) and the logic is not equivalent to ((x & y) == 0).