https://gcc.gnu.org/bugzilla/show_bug.cgi?id=126600
Bug ID: 126600
Summary: [15/16/17 Regression] Wrong code with widening mul and
maybe_optimize_guarding_check
Product: gcc
Version: 17.0
Status: UNCONFIRMED
Keywords: wrong-code
Severity: normal
Priority: P3
Component: tree-optimization
Assignee: unassigned at gcc dot gnu.org
Reporter: ktkachov at gcc dot gnu.org
Target Milestone: ---
/* Wrong code: maybe_optimize_guarding_check folds away the "x != 0" guard
after matching the WRONG successor edge of the overflow compare.
Observed versus expected:
f (0) returns 0 and calls sink () once; it must return 1 and must not
call sink (). The program prints "x=0 expect=1 got=0" and aborts.
Attribution: BAD at -O2, ok at "-O2 -fdisable-tree-widening_mul".
The -fdump-tree-widening_mul-details dump of f shows
Merging blocks 2 and 3
<bb 2>:
_8 = .MUL_OVERFLOW (x_4(D), 3);
r_5 = REALPART_EXPR <_8>;
_9 = IMAGPART_EXPR <_8>;
if (_9 == 0) goto <bb 3>; else goto <bb 4>;
<bb 3>: sink ();
i.e. the "if (x_4(D) != 0)" guard is gone, so x == 0 reaches
.MUL_OVERFLOW (0, 3), which does not overflow, takes the true edge and
calls sink () and returns 0.
Root cause, gcc/tree-ssa-math-opts.cc maybe_optimize_guarding_check:
other_succ_edge = EDGE_SUCC (bb, 0);
if (gimple_cond_code (cond_stmt) == NE_EXPR)
{
if (other_succ_edge->flags & EDGE_TRUE_VALUE)
other_succ_edge = EDGE_SUCC (bb, 1);
}
else if (other_succ_edge->flags & EDGE_FALSE_VALUE)
other_succ_edge = EDGE_SUCC (bb, 0); <-- self assignment, no-op
For the EQ_EXPR form the no-overflow path is the TRUE edge, so the last
line must be EDGE_SUCC (bb, 1). As written, when the successor vector is
ordered (false, true) the function checks the FALSE (overflow) edge
against the x == 0 edge and folds the guard on a CFG where the two do not
join. convert_single_case_switch in tree-cfgcleanup.cc produces exactly
that order: it rewrites the single-case switch into "if (_1 == 3)" while
keeping the switch edge vector, whose first entry is the default edge.
Pre-pass CFG of f, note the succ order of bb 3:
bb 3: r_5 = x_4(D) * 3; _1 = r_5 / x_4(D); if (_1 == 3) ...
;; succ: 6 <- FALSE, and bb 2's x == 0 edge also goes to 6
;; 4 <- TRUE
Positive control: the same EQ shape whose x == 0 path joins the TRUE edge
destination is folded correctly, so only this line is at fault. */
typedef unsigned int u32;
typedef unsigned long long u64;
int sink_calls;
__attribute__((noipa)) void sink (void) { sink_calls++; }
__attribute__((noipa)) int f (u32 x)
{
if (x != 0)
{
u32 r = x * 3;
switch (r / x)
{
case 3:
sink ();
return 0;
default:
break;
}
}
return 1;
}
/* Same semantics as f, expressed with an opaque overflow primitive.
For unsigned x != 0, (x * 3) / x == 3 holds exactly when x * 3 does not
overflow. */
__attribute__((noipa)) int ref (u32 x, int *calls)
{
u32 r;
if (x == 0)
return 1;
if (__builtin_mul_overflow (x, 3u, &r))
return 1;
++*calls;
return 0;
}
int main (void)
{
static const u32 vals[] = {
0u, 1u, 2u, 3u, 0x55555555u, 0x55555556u, 0x80000000u, 0xffffffffu, 1000u
};
for (unsigned i = 0; i < sizeof (vals) / sizeof (vals[0]); i++)
{
volatile u32 vx = vals[i];
u32 x = vx;
int expect_calls = 0;
int expect = ref (x, &expect_calls);
sink_calls = 0;
int got = f (x);
if (got != expect || sink_calls != expect_calls)
__builtin_abort ();
}
return 0;
}
aborts on aarch64 at -O2 and above