Take: ``` void foo(void); int il=1000; int main(void) { short t = il; unsigned t1 = t; if (t1 == 0) { #if 1 char b = t1; #else char b = il; #endif if (b != 1) __builtin_unreachable(); foo(); } } ``` The call to foo should be optimized away since `b` is never 1. as il&0xffff and therefor b is always 0.
Im not sure __builtin_unreachable works this way. It does tell the compiler that a basic block may not be reached, but its a hint.. It doesn't guarantee any code after it will be eliminated. It generally maps to nothing, so the net effect may be to simply fold away the if. A more correct test would be to change the unreachable call to a return. Then foo() should be eliminated. I have adjusted the testcase to void foo(void); int il=1000; int m1(void) { short t = il; unsigned t1 = t; if (t1 == 0) { char b = t1; if (b != 1) return 0; foo(); } return 0; } int m2(void) { short t = il; unsigned t1 = t; if (t1 == 0) { char b = il; if (b != 1) return 0; foo(); } return 0; } Which does accurately demonstrate that we do miss out on this during casts. the m1() quite surprised me. After looking into it, it seems to be for 2 reasons: 1) when performing a cast, we cast sub pairs, and when this gets to VARYING< we immediately return to "save time". This bypasses updating the bitmask for a truncating cast usually. 2) furthermore operator_cast::op1_range makes no attempt to set a bitmask for truncating casts: =========== BB 2 ============ Partial equiv (t_4 pe16 il.0_1) <bb 2> : il.0_1 = il; t_4 = (short int) il.0_1; t1_5 = (unsigned int) t_4; if (t_4 == 0) goto <bb 3>; [INV] else goto <bb 6>; [INV] t1_5 : [irange] unsigned int [0, 32767][4294934528, +INF] 2->3 (T) il.0_1 : [irange] int [-INF, -65536][0, 0][65536, +INF] 2->3 (T) t_4 : [irange] short int [0, 0] 2->3 (T) t1_5 : [irange] unsigned int [0, 32767][4294934528, +INF] =========== BB 3 ============ Imports: il.0_1 Exports: il.0_1 b_6 b_6 : il.0_1(I) il.0_1 [irange] int [-INF, -65536][0, 0][65536, +INF] Partial equiv (b_6 pe8 il.0_1) <bb 3> : b_6 = (char) il.0_1; if (b_6 != 1) goto <bb 4>; [INV] else goto <bb 5>; [INV] 3->4 (T) il.0_1 : [irange] int [-INF, -65536][0, 0][65536, +INF] 3->4 (T) b_6 : [irange] char [-INF, 0][2, +INF] We know in BB2 that t_4 shares the lower 16 bits with il.0_1 as shown by the PE16 partial equivalency relation. We also know in BB3 that t_4 is [0, 0], AND we know that b_6 shares the lower 8 bits of i1.0_1 via the PE8 partial equivalency. Whats lost is that although we know that the lower 16 bits of il.0_1 and t_4 are the same, we only adjust il.0_1... but do not reflect it in a bitmask. When looking at the range calculated for i1.0_1 on exit from bb2 it calculates: 32 GORI compute op 1 (il.0_1) at t_4 = (short int) il.0_1; GORI LHS =[irange] short int [0, 0] GORI Computes il.0_1 = [irange] int [-INF, -65536][0, 0][65536, +INF] intersect Known range : [irange] int VARYING GORI TRUE : (32) produces (il.0_1) [irange] int [-INF, -65536][0, 0][65536, +INF] If we set the bitmask on this to match the LHS range, we get int [-INF, -65536][0, 0][65536, 2147418112] MASK 0xffff0000 VALUE 0x0 And when we allow the bitmask to be updated in fold_range properly, this will set b_6 to the expected [0, 0] range. Patch is in testing, and causes both functions to fold to return 0
Fixed by commit: commit 3d102b7a40bd179c93eccc31b74f95c69f81f45e Author: Andrew MacLeod <amacleod@redhat.com> Date: Tue Oct 21 16:05:22 2025 -0400 Create and apply bitmasks for truncating casts.
The master branch has been updated by Andrew Macleod <amacleod@gcc.gnu.org>: https://gcc.gnu.org/g:3d102b7a40bd179c93eccc31b74f95c69f81f45e commit r16-4603-g3d102b7a40bd179c93eccc31b74f95c69f81f45e Author: Andrew MacLeod <amacleod@redhat.com> Date: Tue Oct 21 16:05:22 2025 -0400 Create and apply bitmasks for truncating casts. When folding a cast, we were not applying the bitmask if we reached a VARYING result. We were also not creating a bitmask to represent the lower bits of a truncating cast in op1_range. So GORI was losing bits. PR tree-optimization/118254 PR tree-optimization/114331 gcc/ * range-op.cc (operator_cast::fold_range): When VARYING is reached, update the bitmask if we reach VARYING. (operator_cast::op1_range): For truncating casts, create a bitmask bit in LHS. gcc/testsuite/ * gcc.dg/pr114331.c: New. * gcc.dg/pr118254.c: New.