On Mon, Aug 28, 2023 at 12:58 AM Andrew Pinski via Gcc-patches
<gcc-patches@gcc.gnu.org> wrote:
>
> This adds a simple case to remove an outer condition if the two inner
> condtionals are the same and lead the same location.
> This can show up due to jump threading or inlining or someone wrote code
> like this.
>
> ifcombine-1.c shows the simple case where this is supposed to solve.
> Even though PRE can handle some cases, ifcombine is earlier and even runs
> at -O1.
>
> Note in the case of the PR here, it comes from jump threading.
>
> OK? Bootstrapped and tested on x86_64-linux-gnu with no regressions.
>
> gcc/ChangeLog:
>
>         PR tree-optimization/110891
>         * tree-ssa-ifcombine.cc (ifcombine_bb_same): New function.
>         (tree_ssa_ifcombine_bb): Call ifcombine_bb_same.
>
> gcc/testsuite/ChangeLog:
>
>         PR tree-optimization/110891
>         * gcc.dg/tree-ssa/ifcombine-1.c: New test.
>         * gcc.dg/tree-ssa/pr110891-1.c: New test.
> ---
>  gcc/testsuite/gcc.dg/tree-ssa/ifcombine-1.c |  27 ++++++
>  gcc/testsuite/gcc.dg/tree-ssa/pr110891-1.c  |  53 +++++++++++
>  gcc/tree-ssa-ifcombine.cc                   | 100 ++++++++++++++++++++
>  3 files changed, 180 insertions(+)
>  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/ifcombine-1.c
>  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/pr110891-1.c
>
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/ifcombine-1.c 
> b/gcc/testsuite/gcc.dg/tree-ssa/ifcombine-1.c
> new file mode 100644
> index 00000000000..02d08efef87
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/ifcombine-1.c
> @@ -0,0 +1,27 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O1 -fdump-tree-optimized -fdump-tree-ifcombine" } */
> +
> +int g();
> +int h();
> +
> +int j, l;
> +
> +int f(int a, int *b)
> +{
> +        if (a == 0)
> +        {
> +                if (b == &j) goto L9; else goto L7;
> +        }
> +        else
> +        {
> +                if (b == &j) goto L9; else goto L7;
> +        }
> +L7: return g();
> +L9: return h();
> +}
> +
> +/* ifcombine can optimize away the outer most if here. */
> +/* { dg-final { scan-tree-dump-times "optimized away the test from bb " 1 
> "ifcombine" } } */
> +/* We should have remove the outer if and one of the inner ifs; leaving us 
> with one if. */
> +/* { dg-final { scan-tree-dump-times "if " 1 "optimized" } } */
> +/* { dg-final { scan-tree-dump-times "goto " 3 "optimized" } } */
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/pr110891-1.c 
> b/gcc/testsuite/gcc.dg/tree-ssa/pr110891-1.c
> new file mode 100644
> index 00000000000..320d8823077
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/pr110891-1.c
> @@ -0,0 +1,53 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -fdump-tree-optimized" } */
> +
> +void foo(void);
> +static int a, c = 7, d, o, q;
> +static int *b = &a, *f, *j = &d, *n = &c, *ae;
> +static short e, m;
> +static short *i = &e;
> +static char r;
> +void __assert_fail(char *, char *, int, const char *) 
> __attribute__((__noreturn__));
> +static const short g();
> +static void h();
> +static int *k(int) {
> +    (*i)++;
> +    *j ^= *b;
> +    return &a;
> +}
> +static void l(unsigned p) {
> +    int *aa = &o;
> +    h();
> +    o = 5 ^ g() && p;
> +    if (f == &d || f == &c || f == &a)
> +        ;
> +    else {
> +        foo();
> +        __assert_fail("", "", 3, __PRETTY_FUNCTION__);
> +    }
> +    *aa ^= *n;
> +    if (*aa)
> +        if (!(((p) >= 0) && ((p) <= 0))) {
> +            __builtin_unreachable();
> +        }
> +    k(p);
> +}
> +static const short g() { return q; }
> +static void h() {
> +    unsigned ag = c;
> +    d = ag > r ? ag : 0;
> +    ae = k(c);
> +    f = ae;
> +    if (ae == &d || ae == &c || ae == &a)
> +        ;
> +    else
> +        __assert_fail("", "", 4, __PRETTY_FUNCTION__);
> +}
> +int main() {
> +    l(a);
> +    m || (*b |= 64);
> +    *b &= 5;
> +}
> +
> +/* We should be able to optimize away foo. */
> +/* { dg-final { scan-tree-dump-not "foo " "optimized" } } */
> diff --git a/gcc/tree-ssa-ifcombine.cc b/gcc/tree-ssa-ifcombine.cc
> index 46b076804f4..f79545b9a0b 100644
> --- a/gcc/tree-ssa-ifcombine.cc
> +++ b/gcc/tree-ssa-ifcombine.cc
> @@ -666,6 +666,103 @@ ifcombine_ifandif (basic_block inner_cond_bb, bool 
> inner_inv,
>    return false;
>  }
>
> +/* Function to remove an outer condition if two inner basic blocks have the 
> same condition and both empty otherwise. */
> +
> +static bool
> +ifcombine_bb_same (basic_block cond_bb, basic_block outer_cond_bb,
> +                  basic_block then_bb, basic_block else_bb)
> +{
> +  basic_block inner_cond_bbt = nullptr, inner_cond_bbf = nullptr;
> +
> +  /* See if the the outer condition is a condition. */
> +  if (!recognize_if_then_else (outer_cond_bb, &inner_cond_bbt, 
> &inner_cond_bbf))
> +    return false;
> +  basic_block other_cond_bb;
> +  if (cond_bb == inner_cond_bbt)
> +    other_cond_bb = inner_cond_bbf;
> +  else
> +    other_cond_bb = inner_cond_bbt;
> +
> +  /* The other bb has to have a single predecessor too. */
> +  if (!single_pred_p (other_cond_bb))
> +    return false;
> +
> +  /* Other inner conditional bb needs to go to the same then and else 
> blocks. */
> +  if (!recognize_if_then_else (other_cond_bb, &then_bb, &else_bb))
> +    return false;
> +
> +  /* Both edges of both inner basic blocks need to have the same values for 
> the incoming phi for both then and else basic blocks. */
> +  if (!same_phi_args_p (cond_bb, other_cond_bb, else_bb))
> +    return false;
> +
> +  if (!same_phi_args_p (cond_bb, other_cond_bb, then_bb))
> +    return false;
> +
> +  /* Both inner bbs need to be empty (besides the condition). */
> +  if (!gsi_one_before_end_p (gsi_start_nondebug_after_labels_bb (cond_bb)))
> +    return false;
> +  if (!gsi_one_before_end_p (gsi_start_nondebug_after_labels_bb 
> (other_cond_bb)))
> +    return false;

Possibly too restrictive (see empty_block_p), maybe worth a comment.  Maybe
it's possible to leverage ICF BB comparison.

> +  /* All 3 basic blocks need to have a GIMPLE_COND as the last statement. */

I think recognize_if_then_else pretty much guarantees this, though not
explicitly.

> +  gcond *cond = safe_dyn_cast <gcond *> (*gsi_last_bb (cond_bb));
> +  if (!cond)
> +    return false;
> +
> +  gcond *other_cond = safe_dyn_cast <gcond *> (*gsi_last_bb (other_cond_bb));
> +  if (!other_cond)
> +    return false;
> +
> +  gcond *outer_cond = safe_dyn_cast <gcond *> (*gsi_last_bb (outer_cond_bb));
> +  if (!outer_cond)
> +    return false;
> +
> +  /* If already done the transformation, don't need to do it again. */
> +  if (gimple_cond_true_p (outer_cond))
> +    return false;
> +  if (gimple_cond_false_p (outer_cond))
> +    return false;
> +
> +  /* Both inner bb have to have the same condition. */
> +  if (gimple_cond_code (cond) != gimple_cond_code (other_cond)
> +      || !operand_equal_p (gimple_cond_lhs (cond), gimple_cond_lhs 
> (other_cond))
> +      || !operand_equal_p (gimple_cond_rhs (cond), gimple_cond_rhs 
> (other_cond)))
> +    return false;

I can see one might be the inverted condition of the other (and thus
swapped then/else).  Possibly for a followup?

> +
> +  /* Both inner bbs need not to have phi nodes.  */

I _think_ that's guaranteed by means of the merge PHI comparison, leaving
dead PHIs case which should be OK.

> +  if (!gimple_seq_empty_p (phi_nodes (cond_bb)))
> +    return false;
> +
> +  if (!gimple_seq_empty_p (phi_nodes (other_cond_bb)))
> +    return false;
> +
> +  if (dump_file)
> +    {
> +      fprintf (dump_file, "optimized away the test from bb #%d.\n", 
> outer_cond_bb->index);
> +    }
> +
> +  /* It does not matter which basic block we use here as both
> +     are the same. Remove the one which we are processing now as
> +     the other one will be processed afterwards and maybe merged in. */
> +  bool remove_true_cond = inner_cond_bbt == cond_bb;
> +
> +  /* Remove the bb which is not reachable any more
> +     so the other one could be merged. */
> +  edge remove_edge = find_edge (outer_cond_bb, remove_true_cond ? 
> inner_cond_bbt : inner_cond_bbf);
> +  edge keep_edge = find_edge (outer_cond_bb, remove_true_cond ? 
> inner_cond_bbf : inner_cond_bbt);

I think we prefer to change the outer if to unconditonal if (true) or
if (false), leaving
the CFG manipulation to CFG cleanup.

> +  keep_edge->flags |= EDGE_FALLTHRU;
> +  keep_edge->flags &= ~(EDGE_TRUE_VALUE | EDGE_FALSE_VALUE);
> +  keep_edge->probability = profile_probability::always ();
> +  delete_basic_block (remove_edge->dest);
> +
> +  /* Eliminate the COND_EXPR at the end of COND_BLOCK.  */
> +  gimple_stmt_iterator gsi = gsi_last_bb (outer_cond_bb);
> +  gsi_remove (&gsi, true);
> +  /* Now merge the outer conditional block with the (old exectued) inner 
> one. */
> +  merge_blocks (outer_cond_bb, remove_true_cond ? inner_cond_bbf : 
> inner_cond_bbt);
> +  return true;
> +}
> +
>  /* Helper function for tree_ssa_ifcombine_bb.  Recognize a CFG pattern and
>     dispatch to the appropriate if-conversion helper for a particular
>     set of INNER_COND_BB, OUTER_COND_BB, THEN_BB and ELSE_BB.
> @@ -780,6 +877,9 @@ tree_ssa_ifcombine_bb (basic_block inner_cond_bb)
>      {
>        basic_block outer_cond_bb = single_pred (inner_cond_bb);
>
> +      if (ifcombine_bb_same (inner_cond_bb, outer_cond_bb, then_bb, else_bb))
> +       return true;

Otherwise looks OK.

Thanks,
Richard.

> +
>        if (tree_ssa_ifcombine_bb_1 (inner_cond_bb, outer_cond_bb,
>                                    then_bb, else_bb, inner_cond_bb))
>         return true;
> --
> 2.31.1
>

Reply via email to