https://gcc.gnu.org/bugzilla/show_bug.cgi?id=126329

--- Comment #3 from Andrew Macleod <amacleod at redhat dot com> ---
Hmm. This is... unpleasant.`

Early removal of builtin_unreachable is very conservative, and it can only be
done if the branch leading to the __builtin_unreachable call dominates all uses
of all exports from the block.

ie
  a_1 = b_2 + 6
  if (a_1 == 6)
     __builtin_unreachable ()

 we can remove this call and adjust the range of b_2 globally to be [-INF,
-1][1, +INF] if this block dominates all uses of b_2. We currently invoke DCE
to remove the a_1 = b_2 + 6 so that as we walk through the IL, if we encounter
another case using b_2 later, ie

  a_6 = b_2 & 0x1
   if (a_1 == 0)
     __builtin_unreachable ()

we can also remove this unreachable call.  If the previous statement was not
deleted, this condition will not dominate  a_1 = b_2 + 6, and we wont be able
to remove this __builtin_unreachable.

This is conservative and required because without careful examination of calls
and statements in between the branch and any previous use, there may be side
effects unaccounted for.

I originally created the testcase gcc.dg/tree-ssa/vrp-unreachable.c to test
this particular kind of linear sequence.  It has a few comparisons and AND
operations, and by the time you get to the bottom, ranger has sorted out the
correct ranges for the 2 ssa-names, communicating the information that the
final 3 bits must be clear.   And all the unreachable calls, except the final
one, one have been removed.


=========== BB 18 ============
n_4(D)  [irange] unsigned int [8, 4294967288] MASK 0xfffffff8 VALUE 0x0
m_6(D)  [irange] unsigned int [8, 4294967288] MASK 0xfffffff8 VALUE 0x0
Equivalence set : [n_4(D), m_6(D)]
    <bb 18> [local count: 1073741824]:
    alive (n_4(D));
    alive (m_6(D));
    return;

If we cannot remove the feeding statements during the pass, we cannot remove
all the __builtin_unreachable calls.  This causes us to lose a bit of
information along the way and we will end upwith some subset of knowledge.

If I turn off early handling of builtin_unreachable, we end up in .optimized
with:

Non-varying global ranges:
=========================:
n_4(D)  : [irange] unsigned int [3, 3][5, +INF]
m_6(D)  : [irange] unsigned int [3, 3][5, +INF]

because VRP2 is presented with dramatically different IL than VRP1, so we can't
copnclude the same info.  This happens for multiple reasons, but primarily
because DOM2 :

  <bb 2> [local count: 1073741824]:
    if (n_4(D) == 1)
      goto <bb 3>; [0.00%]
    else
      goto <bb 4>; [100.00%]

    <bb 3> [count: 0]:
    __builtin_unreachable ();

    <bb 4> [local count: 1073741824]:
    _1 = n_4(D) & 1;
    if (_1 != 0)
      goto <bb 5>; [0.00%]
    else
      goto <bb 6>; [100.00%]

  <bb 5> [count: 0]:
    __builtin_unreachable ();


DOM2 decided BB4 can be rewritten and the branch removed due to the values it
dominates never caring that n_4 is even.. so the mask is not needed

  Optimizing block #6

  1>>> STMT 1 = n_4(D) ne_expr 1
  1>>> STMT 0 = n_4(D) eq_expr 1
  Optimizing statement _1 = n_4(D) & 1;
  LKUP STMT _1 = n_4(D) bit_and_expr 1
  2>>> STMT _1 = n_4(D) bit_and_expr 1
  Optimizing statement if (_1 != 0)

  Visiting conditional with predicate: if (_1 != 0)

  With known ranges
          _1: [irange] unsigned int [0, 1] MASK 0x1 VALUE 0x0

  Predicate evaluates to: DON'T KNOW
  LKUP STMT _1 ne_expr 0


  Optimizing block #8

  0>>> COPY _1 = 0
  Global Exported: _1 = [irange] unsigned int [0, 0] MASK 0x0 VALUE 0x0


so it throws away the knowledge that n_4 is even.   DOM does it for the other
two mask operations  as well, so we lose the information that in the calls the
2 variables must have 0 for the last 3 bits.

If we disable DOM, then PRE messes things up before VRP2 by combining
comparisons. At this point it becomes impossible to determine globally that the
1 bit cannot be set as only one of the 2 conditions need to be satisfied.

  <bb 3> [count: 0]:
  __builtin_unreachable ();

 <bb 4> [local count: 1073741824]:
  _1 = n_4(D) & 1;
  _9 = n_4(D) == 2;
  _10 = _1 != 0;
  _11 = _9 | _10;
  if (_11 != 0)
    goto <bb 3>; [0.00%]
  else
    goto <bb 5>; [100.00%]


I can mark the statements for DCE and do the actual removal at the end of the
pass, but that doesn't help early removal because they are still there for the
dominator check. 

During the dominator query I can check if the stmt is in the DCE queued list,
and then ignore that one. That helps the testcase, but it wouldn't get cases
where there are cascades of DCE statements.  Maybe that would be sufficient.

Anyway, still considering it, figured I would provide an update.

Reply via email to