On Thu, Jul 16, 2026 at 9:16 AM Naveen
<[email protected]> wrote:
>
> PR tree-optimization/81549 exposes delayed loop values represented by a
> chain of loop-header PHIs.
> When the loop executes at least twice, the final value of t0 is 2.
> Scalar-evolution analysis does not always reduce such peeled PHI chains
> to a usable final value.
>
> Extend compute_final_value_from_loop_phi_latch to follow loop-header PHI
> latch arguments until a loop-invariant value is reached.  Record the
> minimum number of latch executions required by the chain and only use
> the invariant value when the iteration count proves that minimum.
>
> Keep the existing exit-PHI final-value replacement path rather than
> rewriting arbitrary uses outside the loop.

The testcase you add already works, so does the original one from the PR.
So I must be missing something?

Richard.

> gcc/ChangeLog:
>
>         PR tree-optimization/81549
>         * tree-scalar-evolution.cc (latch_executions_at_least): New function.
>         (compute_final_value_from_loop_phi_latch): Follow loop-header PHI
>         latch chains when scalar-evolution analysis cannot produce a final
>         value.
>
> gcc/testsuite/ChangeLog:
>
>         PR tree-optimization/81549
>         * gcc.dg/tree-ssa/pr81549.c: New test.
>         * gcc.dg/tree-ssa/pr81549-2.c: New test.
>
> Signed-off-by: Naveen <[email protected]>
> ---
>  gcc/testsuite/gcc.dg/tree-ssa/pr81549-2.c | 46 +++++++++++
>  gcc/testsuite/gcc.dg/tree-ssa/pr81549.c   | 28 +++++++
>  gcc/tree-scalar-evolution.cc              | 95 ++++++++++++++++++-----
>  3 files changed, 148 insertions(+), 21 deletions(-)
>  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/pr81549-2.c
>  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/pr81549.c
>
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/pr81549-2.c 
> b/gcc/testsuite/gcc.dg/tree-ssa/pr81549-2.c
> new file mode 100644
> index 00000000000..236ef0cabd0
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/pr81549-2.c
> @@ -0,0 +1,46 @@
> +/* PR tree-optimization/81549 */
> +/* { dg-do run } */
> +/* { dg-options "-O2" } */
> +
> +int a[4];
> +
> +__attribute__((noipa)) int
> +f (int n)
> +{
> +  int t0 = a[0];
> +  int t1 = a[1];
> +
> +  for (int i = 0; i < n; ++i)
> +    {
> +      t0 = t1;
> +      t1 = 2;
> +    }
> +
> +  return t0;
> +}
> +
> +__attribute__((noipa)) int
> +g (void)
> +{
> +  int t0 = a[0];
> +  int t1 = a[1];
> +
> +  for (int i = 0; i < 1; ++i)
> +    {
> +      t0 = t1;
> +      t1 = 2;
> +    }
> +
> +  return t0;
> +}
> +
> +int
> +main (void)
> +{
> +  a[0] = 11;
> +  a[1] = 22;
> +
> +  if (f (0) != 11 || f (1) != 22 || f (2) != 2 || g () != 22)
> +    __builtin_abort ();
> +  return 0;
> +}
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/pr81549.c 
> b/gcc/testsuite/gcc.dg/tree-ssa/pr81549.c
> new file mode 100644
> index 00000000000..fdbe79ab1c5
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/pr81549.c
> @@ -0,0 +1,28 @@
> +/* PR tree-optimization/81549 */
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -fdump-tree-sccp-details -fdump-tree-optimized" } */
> +
> +int a[10000];
> +
> +int
> +f (void)
> +{
> +  int t0 = a[0];
> +  int t1 = a[1];
> +
> +  for (int i = 0; i < 100; ++i)
> +    {
> +      a[i] = 1;
> +      t0 = t1;
> +      t1 = 2;
> +    }
> +
> +  a[100] = t0;
> +  a[101] = t1;
> +  return t0 + t1;
> +}
> +
> +/* { dg-final { scan-tree-dump-times "with expr: 2" 1 "sccp" } } */
> +/* { dg-final { scan-tree-dump {a\[100\] = 2;} "sccp" } } */
> +/* { dg-final { scan-tree-dump {a\[101\] = 2;} "sccp" } } */
> +/* { dg-final { scan-tree-dump "return 4;" "optimized" } } */
> diff --git a/gcc/tree-scalar-evolution.cc b/gcc/tree-scalar-evolution.cc
> index cd99cbe86ce..ce4ee03bb1d 100644
> --- a/gcc/tree-scalar-evolution.cc
> +++ b/gcc/tree-scalar-evolution.cc
> @@ -3862,6 +3862,18 @@ analyze_and_compute_bitop_with_inv_effect (class loop* 
> loop, tree phidef,
>    return fold_build2 (code1, type, inv, match_op[0]);
>  }
>
> +/* Return true if NITER is known to be at least MIN_NITER.  */
> +
> +static bool
> +latch_executions_at_least (tree niter, unsigned min_niter)
> +{
> +  if (min_niter == 1)
> +    return tree_expr_nonzero_p (niter);
> +
> +  return (tree_fits_uhwi_p (niter)
> +         && tree_to_uhwi (niter) >= min_niter);
> +}
> +
>  /* Try to compute the final value of PHIDEF when PHIDEF is the result of a
>     loop-header PHI.
>
> @@ -3894,34 +3906,75 @@ compute_final_value_from_loop_phi_latch (class loop 
> *loop,
>                                               loop,
>                                               latch_arg,
>                                               folded_casts);
> -  if (ev == chrec_dont_know)
> -    return NULL_TREE;
>
> -  bool invariant_p;
> -  if (no_evolution_in_loop_p (ev, ex_loop->num, &invariant_p) && invariant_p)
> -    return ev;
> -  else if (TREE_CODE (ev) == POLYNOMIAL_CHREC
> -          && get_chrec_loop (ev) == ex_loop)
> -  {
> -    tree niter_type = TREE_TYPE (niter);
> -    tree prev_iter = fold_build2 (MINUS_EXPR,
> -                                 niter_type,
> -                                 niter,
> -                                 build_one_cst (niter_type));
> +  if (ev != chrec_dont_know)
> +    {
> +      bool invariant_p;
> +      if (no_evolution_in_loop_p (ev, ex_loop->num, &invariant_p)
> +         && invariant_p)
> +       return ev;
> +      else if (TREE_CODE (ev) == POLYNOMIAL_CHREC
> +              && get_chrec_loop (ev) == ex_loop)
> +       {
> +         tree niter_type = TREE_TYPE (niter);
> +         tree prev_iter = fold_build2 (MINUS_EXPR,
> +                                       niter_type,
> +                                       niter,
> +                                       build_one_cst (niter_type));
> +
> +         tree res = chrec_apply (ex_loop->num, ev, prev_iter);
> +         if (res == chrec_dont_know)
> +           return NULL_TREE;
> +
> +         if (chrec_contains_symbols_defined_in_loop (res, ex_loop->num))
> +           {
> +             res = instantiate_parameters (ex_loop, res);
> +             if (res == chrec_dont_know)
> +               return NULL_TREE;
> +           }
> +
> +         return res;
> +       }
> +    }
> +
> +  /* SCEV can leave a delayed value as a chain of loop-header PHIs:
>
> -    tree res = chrec_apply (ex_loop->num, ev, prev_iter);
> -    if (res == chrec_dont_know)
> -      return NULL_TREE;
> +       P0 = PHI <INIT0, P1>
> +       P1 = PHI <INIT1, INV>
>
> -    if (chrec_contains_symbols_defined_in_loop (res, ex_loop->num))
> +     After two latch executions P0 has the loop-invariant value INV.
> +     Follow such chains explicitly and keep track of the required number
> +     of latch executions.  */
> +  edge latch = loop_latch_edge (loop);
> +  auto_vec<tree, 8> visited;
> +  tree val = latch_arg;
> +  unsigned min_niter = 1;
> +
> +  while (!expr_invariant_in_loop_p (loop, val))
>      {
> -      res = instantiate_parameters (ex_loop, res);
> -      if (res == chrec_dont_know)
> +      if (TREE_CODE (val) != SSA_NAME)
> +       return NULL_TREE;
> +
> +      for (unsigned i = 0; i < visited.length (); ++i)
> +       if (visited[i] == val)
> +         return NULL_TREE;
> +      visited.safe_push (val);
> +
> +      gphi *phi = dyn_cast <gphi *> (SSA_NAME_DEF_STMT (val));
> +      if (!phi
> +         || gimple_bb (phi) != loop->header
> +         || gimple_phi_num_args (phi) != 2
> +         || virtual_operand_p (PHI_RESULT (phi))
> +         || SSA_NAME_OCCURS_IN_ABNORMAL_PHI (PHI_RESULT (phi)))
>         return NULL_TREE;
> +
> +      val = PHI_ARG_DEF_FROM_EDGE (phi, latch);
> +      ++min_niter;
>      }
>
> -    return res;
> -  }
> +  if (latch_executions_at_least (niter, min_niter))
> +    return val;
> +
>    return NULL_TREE;
>  }
>
> --
> 2.34.1
>

Reply via email to