Re: [PATCH v3] forwprop: add simplify_phi_result_op() [PR101179]

Andrew Pinski <[email protected]> Mon, 3 Aug 2026 10:40:03 -0700
Newsgroups gmane.comp.gcc.patches
Message-ID <CA+=Sn1=k0biafMeiMb7m46Sor7a53T0LDK6yujnUcprB5Uh-gw@mail.gmail.com>
On Mon, Aug 3, 2026 at 10:26 AM Daniel Henrique Barboza
<[email protected]> wrote:
>
>
>
> On 8/2/2026 11:12 PM, Andrea Pinski wrote:
> > On Fri, Jul 31, 2026 at 4:57 AM Daniel Barboza
> > <[email protected]> wrote:
> >>
> >> This new forwprop  step is my attempt to implement Richi's suggestions from
> >> v1 of this work [1] where he suggested to push things out of match.pd.
> >>
> >> The idea is to simplify DIV/MOD into RSHIFT/BIT_AND ops in which the
> >> divisor are pow2 integers in a PHI.  E.g.:
> >>
> >> phi_var = PHI <16,4>
> >> _x = _y % phi_var
> >>
> >> Can be turned into:
> >>
> >> phi_var = PHI <15,3>
> >> _x = _y & phi_var
> >>
> >> As long as we know that _y is a positive number or '_x' is a single use
> >> with a zero comparison.
> >>
> >> Most of 101179 use cases are solved by this change.
> >>
> >> Boostrapped and regression tested with x86_64, aarch64 and riscv64.
> >>
> >> [1] https://gcc.gnu.org/pipermail/gcc-patches/2026-May/716303.html
> >>
> >>          PR tree-optimization/101179
> >>
> >> gcc/ChangeLog:
> >>
> >>          * tree-ssa-forwprop.cc (simplify_phi_result_op): New phiopt step
> >>          where MOD/DIV ops with pow2 divisors can be simplified to
> >>          BIT_AND/RSHIFT.
> >>          (pass_forwprop::execute): Call simplify_phi_result_op.
> >>
> >> gcc/testsuite/ChangeLog:
> >>
> >>          * gcc.dg/tree-ssa/pr101179.c: New test.
> >> ---
> >>
> >> Changes from v2:
> >> - moved the implementation from phiopt to forwprop.  It resulted in a
> >>    smaller and a bit cleaner logic.
> >> - v2 link: https://gcc.gnu.org/pipermail/gcc-patches/2026-July/724039.html
> >>
> >>   gcc/testsuite/gcc.dg/tree-ssa/pr101179.c |  56 ++++++++++++
> >>   gcc/tree-ssa-forwprop.cc                 | 110 +++++++++++++++++++++++
> >>   2 files changed, 166 insertions(+)
> >>   create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/pr101179.c
> >>
> >> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/pr101179.c b/gcc/testsuite/gcc.dg/tree-ssa/pr101179.c
> >> new file mode 100644
> >> index 00000000000..478bdc6fcd3
> >> --- /dev/null
> >> +++ b/gcc/testsuite/gcc.dg/tree-ssa/pr101179.c
> >> @@ -0,0 +1,56 @@
> >> +/* { dg-do compile } */
> >> +/* { dg-options "-O1 -fdump-tree-phiopt1" } */
> >> +
> >> +typedef unsigned uint;
> >> +
> >> +int f1 (int y, _Bool x)
> >> +{
> >> +  return y % (x ? 16 : 4) == 0;
> >> +}
> >> +
> >> +/* We can't turn this into bit_and because there's no
> >> +   guarantee 'y' is a positive val.  */
> >> +int f2 (int y, _Bool x)
> >> +{
> >> +  return y % (x ? 16 : 4);
> >> +}
> >> +
> >> +uint f3 (uint y, _Bool x)
> >> +{
> >> +  return y % (x ? 16 : 4) == 0;
> >> +}
> >> +
> >> +uint f4 (uint y, _Bool x)
> >> +{
> >> +  return y % (x ? 16 : 4);
> >> +}
> >> +
> >> +int g1 (int y, _Bool x)
> >> +{
> >> +  return y / (x ? 16 : 4) == 0;
> >> +}
> >> +
> >> +/* We can't turn this into rshift because there's no
> >> +   guarantee 'y' is a positive val.  */
> >> +int g2 (int y, _Bool x)
> >> +{
> >> +  return y / (x ? 16 : 4);
> >> +}
> >> +
> >> +/* This will be turned by match.pd into:
> >> +   "(X / Y) == 0 -> X < Y if X, Y are unsigned."
> >> +   We're adding it here for completioness.  */
> >> +uint g3 (uint y, _Bool x)
> >> +{
> >> +  return y / (x ? 16 : 4) == 0;
> >> +}
> >> +
> >> +uint g4 (uint y, _Bool x)
> >> +{
> >> +  return y / (x ? 16 : 4);
> >> +}
> >> +
> >> +/* { dg-final { scan-tree-dump-times " \& " 3 "phiopt1" } } */
> >> +/* { dg-final { scan-tree-dump-times " \% " 1 "phiopt1" } } */
> >> +/* { dg-final { scan-tree-dump-times " >> " 2 "phiopt1" } } */
> >> +/* { dg-final { scan-tree-dump-times " \\/ " 1 "phiopt1" } } */
> >> diff --git a/gcc/tree-ssa-forwprop.cc b/gcc/tree-ssa-forwprop.cc
> >> index 75f06c6ba41..32a9fee560c 100644
> >> --- a/gcc/tree-ssa-forwprop.cc
> >> +++ b/gcc/tree-ssa-forwprop.cc
> >> @@ -3623,6 +3623,108 @@ simplify_count_zeroes (gimple_stmt_iterator *gsi)
> >>     return true;
> >>   }
> >>
> >> +/* Verify if we have the following structure:
> >> +
> >> +   iftmp1 = PHI <pow2a, pow2b>
> >> +   _ssa1 = _ssa2 MOD|DIV iftmp1;
> >> +   _ssa3 = _ssa1 EQ|NE 0;
> >> +
> >> +   And, as long as "_ssa2" is either known to be positive or
> >> +   "_ssa1" is single use in a zero comparison, change the PHI
> >> +   args and "_ssa1" stmt to a cheaper alternative.
> >> +
> >> +   For MOD:
> >> +
> >> +   iftmp1 = PHI <(pow2a - 1), (pow2b - 1)>
> >> +   _ssa1 = _ssa2 & iftmp1;
> >> +
> >> +   For DIV:
> >> +
> >> +   iftmp1 = PHI <log2 (pow2a), log2 (pow2b)>
> >> +   _ssa1 = _ssa2 >> iftmp1;  */
> >> +static bool
> >> +simplify_phi_result_op (gimple *stmt, tree_code code)
> >> +{
> >> +  gphi *phi = dyn_cast<gphi *> (SSA_NAME_DEF_STMT (gimple_assign_rhs2 (stmt)));
> > Need a check for nullptr after dyn_cast.
>
>
> Hmmmm I thought the nullptr case would be covered by the checks I'm doing before calling
> the function:
>
>
> +                            && SSA_NAME_DEF_STMT (gimple_assign_rhs2 (stmt))
> +                            && is_a<gphi*> (SSA_NAME_DEF_STMT (
> +                                               gimple_assign_rhs2 (stmt))))
> +                     changed |= simplify_phi_result_op (stmt, code);
>
>
> Maybe it's clearer to fold these checks inside the function ...

Then you can just use as_a<gphi*> instead of dyn_cast. The dyn_cast is
what confused me really; as_a would have made it clear that it was
asserted a phi beforehand.

>
>
>
> >
> >> +  if (gimple_phi_num_args (phi) != 2)
> >> +    return false;
> >
> > Instead of just handling 2 argument phis. why not handle N argument phis here.
>
>
> Mostly because I didn't though about that :D I just came up with a simple
> C code that generates a PHI with 4 args that we could reduce.
>
> I'll rework the code to support this use case.


Thanks,
Andrea

>
> >
> >
> >> +
> >> +  tree arg0 = gimple_phi_arg_def (phi, 0);
> >> +  tree arg1 = gimple_phi_arg_def (phi, 1);
> >> +
> >> +  if (!integer_pow2p (arg0) || !tree_fits_uhwi_p (arg0)
> >> +      || !integer_pow2p (arg1) || !tree_fits_uhwi_p (arg1))
> >> +    return false;
> >
> > That is 2 loops.
> > One to reject if the argument is not a power of 2. I don't think you
> > need tree_fits_uhwi_p (see below on why).
> >
> >> +
> >> +  tree_code new_code;
> >> +  switch (code)
> >> +    {
> >> +      case TRUNC_MOD_EXPR:
> >> +      case CEIL_MOD_EXPR:
> >> +      case FLOOR_MOD_EXPR:
> >> +      case ROUND_MOD_EXPR:
> >> +       new_code = BIT_AND_EXPR;
> >> +       break;
> >> +      case TRUNC_DIV_EXPR:
> >> +      case CEIL_DIV_EXPR:
> >> +      case FLOOR_DIV_EXPR:
> >> +      case ROUND_DIV_EXPR:
> >> +       new_code = RSHIFT_EXPR;
> >> +       break;
> >> +
> >> +     default:
> >> +       return false;
> >> +    }
> >> +
> >> +  /* If rhs1 is a known positive value we can always apply these
> >> +     simplification.  Otherwise see if lhs is single_use with a
> >> +     EQ|NE 0 cmp.  */
> >> +  tree rhs1 = gimple_assign_rhs1 (stmt);
> >> +  if (!tree_expr_nonnegative_p (rhs1))
> >> +    {
> >> +      gimple *cmp_stmt;
> >> +      use_operand_p use_p;
> >> +
> >> +      if (!single_imm_use (gimple_assign_lhs (stmt), &use_p, &cmp_stmt)
> >> +         || !cmp_stmt
> >> +         || !is_gimple_assign (cmp_stmt))
> >> +       return false;
> >> +
> >> +      if (!(gimple_assign_rhs_code (cmp_stmt) == NE_EXPR
> >> +           || gimple_assign_rhs_code (cmp_stmt) == EQ_EXPR))
> >> +       return false;
> >> +
> >> +      if (!integer_zerop (gimple_assign_rhs2 (cmp_stmt)))
> >> +       return false;
> >> +    }
> >
> > Put this before the phi check loop.
> >
> >> +
> >> +  tree phires = gimple_phi_result (phi);
> >> +  tree type = TREE_TYPE (phires);
> >> +  tree new_arg0, new_arg1;
> >> +
> >> +  if (new_code == RSHIFT_EXPR)
> >> +    {
> >> +      new_arg0 = build_int_cst (type, wi::exact_log2 (tree_to_uhwi (arg0)));
> >> +      new_arg1 = build_int_cst (type, wi::exact_log2 (tree_to_uhwi (arg1)));
> >
> > Use wi::to_wide here.
> > For RSHIFT_EXPR, add a gimple_convert to integer_type_node for the new
> > rhs. That is it might be a long long which we want to convert into an
> > integer or a bit_int that we want to convert into an integer.
> >
> >> +    }
> >> +  else
> >> +    {
> >> +      new_arg0 = build_int_cst (type, tree_to_uhwi (arg0) - 1);
> >> +      new_arg1 = build_int_cst (type, tree_to_uhwi (arg1) - 1);
> >
> > Likewise. (this removes the need for the tree_fits_uhwi_p check)
> >
> >> +    }
> >> +
> >> +  SET_PHI_ARG_DEF (phi, 0, new_arg0);
> >> +  SET_PHI_ARG_DEF (phi, 1, new_arg1);
> >> +  if (SSA_NAME_RANGE_INFO (phires))
> >> +    reset_flow_sensitive_info (phires);
> >
> > Maybe it is better to insert a new phi rather than reuse the current
> > phi for debug info reasons. You might want to fixup the debug info
> > too.
>
> If we create a new phi do we need to do stuff w.r.t debug info of the older
> phi?
>
>
> Thanks,
> Daniel
>
> >
> > Thanks,
> > Andrea
> >
> >> +
> >> +  gimple_assign_set_rhs1 (stmt, rhs1);
> >> +  gimple_assign_set_rhs2 (stmt, phires);
> >> +  gimple_assign_set_rhs_code (stmt, new_code);
> >> +  update_stmt (stmt);
> >> +
> >> +  return true;
> >> +}
> >>
> >>   /* Determine whether applying the 2 permutations (mask1 then mask2)
> >>      gives back one of the input.  */
> >> @@ -5894,6 +5996,14 @@ pass_forwprop::execute (function *fun)
> >>                        changed |= simplify_vector_constructor (&gsi);
> >>                      else if (code == ARRAY_REF)
> >>                        changed |= simplify_count_zeroes (&gsi);
> >> +                   else if (get_gimple_rhs_class (code) == GIMPLE_BINARY_RHS
> >> +                            && TREE_CODE (
> >> +                                   gimple_assign_rhs2 (stmt)) == SSA_NAME
> >> +                            && has_single_use (gimple_assign_rhs2 (stmt))
> >> +                            && SSA_NAME_DEF_STMT (gimple_assign_rhs2 (stmt))
> >> +                            && is_a<gphi*> (SSA_NAME_DEF_STMT (
> >> +                                               gimple_assign_rhs2 (stmt))))
> >> +                     changed |= simplify_phi_result_op (stmt, code);
> >>                      break;
> >>                    }
> >>
> >> --
> >> 2.43.0
> >>
>