On Fri, Nov 29, 2024 at 8:59 AM Alexandre Oliva <ol...@adacore.com> wrote:
>
>
> When ifcombining contiguous blocks, we can follow forwarder blocks and
> reverse conditions to enable combinations, but when there are
> intervening blocks, we have to constrain ourselves to paths to the
> exit that share the PHI args with all intervening blocks.
>
> Avoiding considering forwarders when intervening blocks were present
> would match the preexisting test, but we can do better, recording in
> case a forwarded path corresponds to the outer block's exit path, and
> insisting on not combining through any other path but the one that was
> verified as corresponding.  The latter is what this patch implements.
>
> While at that, I've fixed some typos, introduced early testing before
> computing the exit path to avoid it when computing it would be
> wasteful, or when avoiding it can enable other sound combinations.
>
> Regstrapped on x86_64-linux-gnu.  Ok to install?

OK.

Thanks,
Richard.

>
> for  gcc/ChangeLog
>
>         PR tree-optimization/117723
>         * tree-ssa-ifcombine.cc (tree_ssa_ifcombine_bb): Record
>         forwarder blocks in path to exit, and stick to them.  Avoid
>         computing the exit if obviously not needed, and if that
>         enables additional optimizations.
>         (tree_ssa_ifcombine_bb_1): Fix typos.
>
> for  gcc/testsuite/ChangeLog
>
>         PR tree-optimization/117723
>         * gcc.dg/torture/ifcmb-1.c: New.
> ---
>  gcc/testsuite/gcc.dg/torture/ifcmb-1.c |   63 +++++++++++++++++
>  gcc/tree-ssa-ifcombine.cc              |  116 
> +++++++++++++++++++++++++++-----
>  2 files changed, 161 insertions(+), 18 deletions(-)
>  create mode 100644 gcc/testsuite/gcc.dg/torture/ifcmb-1.c
>
> diff --git a/gcc/testsuite/gcc.dg/torture/ifcmb-1.c 
> b/gcc/testsuite/gcc.dg/torture/ifcmb-1.c
> new file mode 100644
> index 0000000000000..2431a548598fc
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/torture/ifcmb-1.c
> @@ -0,0 +1,63 @@
> +/* { dg-do run } */
> +
> +/* Test that we do NOT perform unsound transformations for any of these 
> cases.
> +   Forwarding blocks to the exit block used to enable some of them.  */
> +
> +[[gnu::noinline]]
> +int f0 (int a, int b) {
> +  if ((a & 1))
> +    return 0;
> +  if (b)
> +    return 1;
> +  if (!(a & 2))
> +    return 0;
> +  else
> +    return 1;
> +}
> +
> +[[gnu::noinline]]
> +int f1 (int a, int b) {
> +  if (!(a & 1))
> +    return 0;
> +  if (b)
> +    return 1;
> +  if ((a & 2))
> +    return 1;
> +  else
> +    return 0;
> +}
> +
> +[[gnu::noinline]]
> +int f2 (int a, int b) {
> +  if ((a & 1))
> +    return 0;
> +  if (b)
> +    return 1;
> +  if (!(a & 2))
> +    return 0;
> +  else
> +    return 1;
> +}
> +
> +[[gnu::noinline]]
> +int f3 (int a, int b) {
> +  if (!(a & 1))
> +    return 0;
> +  if (b)
> +    return 1;
> +  if ((a & 2))
> +    return 1;
> +  else
> +    return 0;
> +}
> +
> +int main() {
> +  if (f0 (0, 1) != 1)
> +    __builtin_abort();
> +  if (f1 (1, 1) != 1)
> +    __builtin_abort();
> +  if (f2 (2, 1) != 1)
> +    __builtin_abort();
> +  if (f3 (3, 1) != 1)
> +    __builtin_abort();
> +}
> diff --git a/gcc/tree-ssa-ifcombine.cc b/gcc/tree-ssa-ifcombine.cc
> index e389b12aa37db..a87bf1210776f 100644
> --- a/gcc/tree-ssa-ifcombine.cc
> +++ b/gcc/tree-ssa-ifcombine.cc
> @@ -1077,7 +1077,7 @@ tree_ssa_ifcombine_bb_1 (basic_block inner_cond_bb, 
> basic_block outer_cond_bb,
>      }
>
>    /* The || form is characterized by a common then_bb with the
> -     two edges leading to it mergable.  The latter is guaranteed
> +     two edges leading to it mergeable.  The latter is guaranteed
>       by matching PHI arguments in the then_bb and the inner cond_bb
>       having no side-effects.  */
>    if (phi_pred_bb != then_bb
> @@ -1088,7 +1088,7 @@ tree_ssa_ifcombine_bb_1 (basic_block inner_cond_bb, 
> basic_block outer_cond_bb,
>            <outer_cond_bb>
>              if (q) goto then_bb; else goto inner_cond_bb;
>            <inner_cond_bb>
> -            if (q) goto then_bb; else goto ...;
> +            if (p) goto then_bb; else goto ...;
>            <then_bb>
>              ...
>         */
> @@ -1104,7 +1104,7 @@ tree_ssa_ifcombine_bb_1 (basic_block inner_cond_bb, 
> basic_block outer_cond_bb,
>            <outer_cond_bb>
>              if (q) goto inner_cond_bb; else goto then_bb;
>            <inner_cond_bb>
> -            if (q) goto then_bb; else goto ...;
> +            if (p) goto then_bb; else goto ...;
>            <then_bb>
>              ...
>         */
> @@ -1139,13 +1139,18 @@ tree_ssa_ifcombine_bb (basic_block inner_cond_bb)
>       Look for an OUTER_COND_BBs to combine with INNER_COND_BB.  They need not
>       be contiguous, as long as inner and intervening blocks have no side
>       effects, and are either single-entry-single-exit or conditionals 
> choosing
> -     between the same EXIT_BB with the same PHI args, and the path leading to
> -     INNER_COND_BB.  ??? We could potentially handle multi-block
> -     single-entry-single-exit regions, but the loop below only deals with
> -     single-entry-single-exit individual intervening blocks.  Larger regions
> -     without side effects are presumably rare, so it's probably not worth the
> -     effort.  */
> -  for (basic_block bb = inner_cond_bb, outer_cond_bb, exit_bb = NULL;
> +     between the same EXIT_BB with the same PHI args, possibly through an
> +     EXIT_PRED, and the path leading to INNER_COND_BB.  EXIT_PRED will be set
> +     just before (along with a successful combination) or just after setting
> +     EXIT_BB, to either THEN_BB, ELSE_BB, or INNER_COND_BB.  ??? We could
> +     potentially handle multi-block single-entry-single-exit regions, but the
> +     loop below only deals with single-entry-single-exit individual 
> intervening
> +     blocks.  Larger regions without side effects are presumably rare, so 
> it's
> +     probably not worth the effort.  */
> +  for (basic_block bb = inner_cond_bb, outer_cond_bb, exit_bb = NULL,
> +        /* This initialization shouldn't be needed, but in case the compiler
> +           is not smart enough to tell, make it harmless.  */
> +        exit_pred = NULL;
>         single_pred_p (bb) && bb_no_side_effects_p (bb);
>         bb = outer_cond_bb)
>      {
> @@ -1186,10 +1191,13 @@ tree_ssa_ifcombine_bb (basic_block inner_cond_bb)
>          checking of same phi args.  */
>        if (known_succ_p (outer_cond_bb))
>         changed = false;
> -      else if (tree_ssa_ifcombine_bb_1 (inner_cond_bb, outer_cond_bb,
> -                                       then_bb, else_bb, inner_cond_bb, bb))
> -       changed = true;
> -      else if (forwarder_block_to (else_bb, then_bb))
> +      else if ((!exit_bb || exit_pred == inner_cond_bb)
> +              && tree_ssa_ifcombine_bb_1 (inner_cond_bb, outer_cond_bb,
> +                                          then_bb, else_bb, inner_cond_bb, 
> bb))
> +       changed = true, exit_pred = inner_cond_bb;
> +      else if (exit_bb
> +              ? exit_pred == else_bb
> +              : forwarder_block_to (else_bb, then_bb))
>         {
>           /* Other possibilities for the && form, if else_bb is
>              empty forwarder block to then_bb.  Compared to the above simpler
> @@ -1199,9 +1207,11 @@ tree_ssa_ifcombine_bb (basic_block inner_cond_bb)
>              edge from outer_cond_bb and the forwarder block.  */
>           if (tree_ssa_ifcombine_bb_1 (inner_cond_bb, outer_cond_bb, else_bb,
>                                        then_bb, else_bb, bb))
> -           changed = true;
> +           changed = true, exit_pred = else_bb;
>         }
> -      else if (forwarder_block_to (then_bb, else_bb))
> +      else if (exit_bb
> +              ? exit_pred == then_bb
> +              : forwarder_block_to (then_bb, else_bb))
>         {
>           /* Other possibilities for the || form, if then_bb is
>              empty forwarder block to else_bb.  Compared to the above simpler
> @@ -1211,7 +1221,7 @@ tree_ssa_ifcombine_bb (basic_block inner_cond_bb)
>              edge from outer_cond_bb and the forwarder block.  */
>           if (tree_ssa_ifcombine_bb_1 (inner_cond_bb, outer_cond_bb, else_bb,
>                                        then_bb, then_bb, bb))
> -           changed = true;
> +           changed = true, exit_pred = then_bb;
>         }
>
>        if (changed)
> @@ -1222,15 +1232,85 @@ tree_ssa_ifcombine_bb (basic_block inner_cond_bb)
>        if (changed && known_succ_p (inner_cond_bb))
>         break;
>
> +      /* Starting at this point in the loop, we start preparing to attempt
> +        combinations in which OUTER_COND_BB will be an intervening block.
> +        Checking that it has a single predecessor is a very cheap test, 
> unlike
> +        the PHI args tests below, so test it early and hopefully save the 
> more
> +        expensive tests in case we won't be able to try other blocks.  */
> +      if (!single_pred_p (outer_cond_bb))
> +       break;
> +
>        /* Record the exit path taken by the outer condition.  */
>        if (!exit_bb)
>         {
> +         /* If we have removed the outer condition entirely, we need not
> +            commit to an exit block yet, it's as if we'd merged the blocks 
> and
> +            were starting afresh.  This is sound as long as we never replace
> +            the outer condition with a constant that leads away from the 
> inner
> +            block.  Here's why we never do: when combining contiguous
> +            conditions, we replace the inner cond, and replace the outer cond
> +            with a constant that leads to inner, so this case is good.  When
> +            combining noncontiguous blocks, we normally modify outer, and
> +            replace inner with a constant or remainders of the original
> +            condition that couldn't be combined.  This test would normally 
> not
> +            hit with noncontiguous blocks, because we'd have computed EXIT_BB
> +            before reaching the noncontiguous outer block.  However, if all
> +            intervening blocks are unconditional, including those just made
> +            unconditional, we may replace outer instead of inner with the
> +            combined condition.  If the combined noncontiguous conditions are
> +            mutually exclusive, we could end up with a constant outer
> +            condition, but then, the inner condition would also be a 
> constant,
> +            and then we'd stop iterating because of the known_succ_p
> +            (inner_cond_bb) test above.  */
> +         if (changed && known_succ_p (outer_cond_bb))
> +           continue;
> +
>           if (recognize_if_then_else (outer_cond_bb, &then_bb, &bb, true))
>             exit_bb = then_bb;
>           else if (recognize_if_then_else (outer_cond_bb, &bb, &else_bb, 
> true))
>             exit_bb = else_bb;
>           else
>             break;
> +
> +         /* Find out which path from INNER_COND_BB shares PHI args with the
> +            edge (OUTER_COND_BB->EXIT_BB).  That path may involve a forwarder
> +            block, whether THEN_BB or ELSE_BB, and we need to know which one
> +            satisfies the condition to avoid combinations that could use
> +            different forwarding arrangements, because they would be unsound.
> +            E.g., given (a ? 0 : b ? 1 : c ? 1 : 0), after trying to merge b
> +            and c, we test that both share the same exit block, with the same
> +            value 1.  Whether or not that involves a forwarder block, if we
> +            don't go through the same (possibly absent) forwarder block in
> +            subsequent attempted combinations, e.g. a with c, we could find
> +            that a and inverted c share the same exit block with a different
> +            value, namely 0, which would enable an unsound merge.  We need 
> all
> +            of inner, intervening and outer blocks to reach the same exit 
> with
> +            the same value for the transformation to be sound.  So here we
> +            determine how to get to EXIT_BB from outer and inner with the 
> same
> +            PHI values, record that in EXIT_PRED, and then subsequent
> +            combination attempts that have OUTER_COND_BB as an intervening
> +            block will ensure the same path to exit is taken, skipping 
> unsound
> +            transformations.  */
> +         if (changed)
> +           /* EXIT_PRED was set along with CHANGED, and the successful
> +              combination already checked for the same PHI args.  */;
> +         else if (same_phi_args_p (outer_cond_bb, inner_cond_bb, exit_bb))
> +           exit_pred = inner_cond_bb;
> +         else if (then_bb == exit_bb
> +                  && forwarder_block_to (else_bb, then_bb)
> +                  && same_phi_args_p (outer_cond_bb, else_bb, exit_bb))
> +           exit_pred = else_bb;
> +         else if (else_bb == exit_bb
> +                  && forwarder_block_to (then_bb, else_bb)
> +                  && same_phi_args_p (outer_cond_bb, then_bb, exit_bb))
> +           exit_pred = then_bb;
> +         else
> +           /* If none of the paths share the same PHI args, no combination is
> +              viable.  */
> +           break;
> +         /* Skip the PHI args test below, it's redundant with the tests we've
> +            just performed.  */
> +         continue;
>         }
>
>        /* Before trying an earlier block, make sure INNER_COND_BB and the
> @@ -1245,7 +1325,7 @@ tree_ssa_ifcombine_bb (basic_block inner_cond_bb)
>          of outer, we'd get ([1 ? 0 :] b ? 1 : (a | c) ? 0 : -1), which would
>          yield 1 rather than 0 when (a).  */
>        if (!changed
> -         && !same_phi_args_p (outer_cond_bb, inner_cond_bb, exit_bb))
> +         && !same_phi_args_p (outer_cond_bb, exit_pred, exit_bb))
>         break;
>      }
>
>
>
> --
> Alexandre Oliva, happy hacker            https://FSFLA.org/blogs/lxo/
>    Free Software Activist                   GNU Toolchain Engineer
> More tolerance and less prejudice are key for inclusion and diversity
> Excluding neuro-others for not behaving ""normal"" is *not* inclusive

Reply via email to