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

Reply via email to