On Sat, Jul 4, 2026 at 5:10 AM Andrew Pinski
<[email protected]> wrote:
>
> This adds a reduced cs-elim functionality which will be used
> from phiopt. The design is similar to cond_store_replacement except
> we are looking for a similar store right beore the condition or a
> non-trapping (no data races or local variable) load right before
> the conditional. This allows for 2 things, the reuse of the same
> aliasing set and second is allows for use in phiopt in a secondary patch.
>
> It supports the inserting on the edge for the case where there is
> no load/stores before the condition if the store is a non-trapping memory
> with no data races; this is a similar to what is done in 
> cond_store_replacement
> already. Thia part is not going to be used with phiopt though as it is too
> complex to support there and it adds a conditional load for no benifit for
> phiopt.
>
> Note pr99473-1.c of a case where can be optimized even without
> -fallow-store-data-races now. The store happens before the conditional
> and inside the conditional, so afterwards there is only one store rather
> than 2. The code was also handling it in sink in GCC 12 the same way too
> but now handling it in cselim before sink1.
>
> Changes since v1:
> * v2: Add support for loads before the conditional and support
> for doing the load on the edge. Fix formating and comments.
>
> Bootstrapped and tested on x86_64-linux-gnu.
>
> gcc/ChangeLog:
>
>         * tree-ssa-phiopt.cc (may_alias_mem): New function.
>         (can_trap_as_a_store): New funciton.
>         (cond_store_replacement_limited): New function.
>         (pass_cselim::execute): Call cond_store_replacement_limited before
>         cond_store_replacement.
>
> gcc/testsuite/ChangeLog:
>
>         * gcc.dg/tree-ssa/pr99473-1.c: Remove -fallow-store-data-races as it 
> is not
>         needed with cond_store_replacement_limited.
>
> Signed-off-by: Andrew Pinski <[email protected]>
> ---
>  gcc/testsuite/gcc.dg/tree-ssa/pr99473-1.c |   2 +-
>  gcc/tree-ssa-phiopt.cc                    | 271 ++++++++++++++++++++++
>  2 files changed, 272 insertions(+), 1 deletion(-)
>
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/pr99473-1.c 
> b/gcc/testsuite/gcc.dg/tree-ssa/pr99473-1.c
> index 0fda5663a80..e930389e9c0 100644
> --- a/gcc/testsuite/gcc.dg/tree-ssa/pr99473-1.c
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/pr99473-1.c
> @@ -1,5 +1,5 @@
>  /* { dg-do compile } */
> -/* { dg-options "-O2 -ftree-cselim -fallow-store-data-races 
> -fdump-tree-cselim-details" } */
> +/* { dg-options "-O2 -ftree-cselim -fdump-tree-cselim-details" } */
>
>  void f (int*);
>
> diff --git a/gcc/tree-ssa-phiopt.cc b/gcc/tree-ssa-phiopt.cc
> index e12dc7a8b0c..103547d538f 100644
> --- a/gcc/tree-ssa-phiopt.cc
> +++ b/gcc/tree-ssa-phiopt.cc
> @@ -3386,6 +3386,272 @@ trailing_store_in_bb (basic_block bb, tree vdef, gphi 
> *vphi, bool onlyonestore)
>    return store;
>  }
>
> +/* Takes a MEM and changes the aliasing set to be zero on it.
> +   This handles all variants include decls. */
> +static tree
> +may_alias_mem (tree mem)

IMHO this is a bad name for the function, maybe
copy_mem_with_alias_set_zero?

> +{
> +  mem = unshare_expr (mem);
> +  tree *basep = &mem;
> +  while (handled_component_p (*basep))
> +    basep = &TREE_OPERAND (*basep, 0);
> +  if (TREE_CODE (*basep) == MEM_REF
> +      || TREE_CODE (*basep) == TARGET_MEM_REF)
> +    TREE_OPERAND (*basep, 1)
> +      = fold_convert (ptr_type_node, TREE_OPERAND (*basep, 1));
> +  else
> +    *basep = build2 (MEM_REF, TREE_TYPE (*basep),
> +                    build_fold_addr_expr (*basep),
> +                    build_zero_cst (ptr_type_node));
> +  return mem;
> +}
> +
> +/* Returns true if LHS is known not to trap as a store.  */
> +static bool
> +can_trap_as_a_store (tree lhs)

We can probably re-use this in LIM, maybe put it in
tree-eh.cc alongside tree_could_trap_p and name
it similarly, tree_could_trap_as_store_p or as
lhs_could_trap_p?

Otherwise looks good to me.

Thanks,
Richard.


> +{
> +  tree lhsbase = get_base_address (lhs);
> +  if (tree_could_trap_p (lhs))
> +    return true;
> +  /* tree_could_trap_p is a predicate for loads, so check
> +     for readonly memory explicitly.  */
> +  if ((DECL_P (lhsbase) && TREE_READONLY (lhsbase))
> +      || TREE_CODE (lhsbase) == STRING_CST)
> +    return true;
> +  return false;
> +}
> +
> +/* Do the main work of a limited conditional store replacement.
> +   This recognized pattern like so:
> +
> +   COND_BB:
> +     store = a_1;
> +     // no loads
> +     if (cond) goto MIDDLE_BB; else goto JOIN_BB (edge E1)
> +   MIDDLE_BB:
> +     something // no loads
> +     store = a_2;
> +     something // no loads
> +     fallthrough (edge E0)
> +   JOIN_BB:
> +     some more
> +
> +  This is a limited form of the full cond_store_replacement
> +  to be allowed from use from phiopt and can be done
> +  without calculating the non-trapping cases.  */
> +static bool
> +cond_store_replacement_limited (basic_block middle_bb, basic_block join_bb,
> +                               basic_block cond_bb,
> +                               edge e0, edge e1,
> +                               bool caninsert_edge)
> +{
> +  tree lhs, rhs;
> +  location_t locus;
> +  /* Currently don't handle more than 2 incoming edges
> +     into the merge bb. */
> +  if (EDGE_COUNT (join_bb->preds) > 2)
> +    return false;
> +  /* The middle bb needs to have a single predecessor of the cond_bb.  */
> +  if (!single_pred_p (middle_bb))
> +    return false;
> +  gphi *vphi = get_virtual_phi (join_bb);
> +  if (!vphi)
> +    return false;
> +  tree middle_vdef = gimple_phi_arg_def_from_edge (vphi, e0);
> +  /* Check if middle_bb contains of only one store.  */
> +  gimple *store_middle;
> +  store_middle = trailing_store_in_bb (middle_bb, middle_vdef,
> +                                      vphi, true);
> +
> +  if (!store_middle
> +      || !gimple_assign_single_p (store_middle)
> +      || gimple_has_volatile_ops (store_middle))
> +    return false;
> +
> +  locus = gimple_location (store_middle);
> +  lhs = gimple_assign_lhs (store_middle);
> +  rhs = gimple_assign_rhs1 (store_middle);
> +  if ((!REFERENCE_CLASS_P (lhs)
> +       && !DECL_P (lhs))
> +      || !is_gimple_reg_type (TREE_TYPE (lhs)))
> +    return false;
> +  if (TREE_CODE (rhs) != SSA_NAME)
> +    return false;
> +
> +  /* Three cases that can be handled:
> +     1) the lhs is stored to right before the condition.
> +       Will remove the store before the condition.
> +     2) Or the lhs is loaded from right before the condition.
> +     3) Neither of these. (this will insert a load in the other edge)
> +     For case 2 and 3, check for data races.
> +     For case 2, the load can either be based on a local variable
> +     or a known non-trapping decl.
> +     For case 3, the store needs to known to be non-trapping.  */
> +  tree vuse = gimple_vuse (store_middle);
> +  gimple *beforestore = nullptr;
> +  gimple *vdef_before = SSA_NAME_DEF_STMT (vuse);
> +  tree other_rhs = nullptr;
> +
> +  /* See if there is a store before the condition case.  */
> +  if (gimple_assign_single_p (vdef_before))
> +    {
> +      tree beforelhs = gimple_assign_lhs (vdef_before);
> +      /* Only allow the store to be right before the condition.  */
> +      if (gimple_bb (vdef_before) == cond_bb
> +         /* An exact match is only supported.
> +            FIXME: Allow for clique/base mismatch?  */
> +         && operand_equal_p (lhs, beforelhs))
> +       {
> +         /* The vuse of the of store in the middle should be also
> +            the entry in the phi for the other edge.  */
> +         gcc_assert (vuse == gimple_phi_arg_def_from_edge (vphi, e1));
> +         tree vuse = gimple_vuse (store_middle);
> +         imm_use_iterator iter;
> +         gimple *use_stmt;
> +         /* There can't be any loads between the store and
> +            the previous store as that might depend on the store.
> +            FIXME: use alias oracle to check dependancies.  */
> +         FOR_EACH_IMM_USE_STMT (use_stmt, iter, vuse)
> +           {
> +             if (use_stmt != store_middle
> +                 && use_stmt != vphi)
> +               return false;
> +           }
> +         other_rhs = gimple_assign_rhs1 (vdef_before);
> +         beforestore = vdef_before;
> +       }
> +    }
> +  /*
> +    case 2:
> +    a = local_var[n];
> +    if (b)
> +      local_var[n] = c;
> +
> +    case 3:
> +    if (b)
> +      nontrapping = c;
> +
> +    For case 3, nontrapping needs to satisfy tree_could_trap_p.
> +    In both cases ref_can_have_store_data_races needs to be satisfy.
> +  */
> +
> +  /* Maybe the load/local non-escaped variable case.  */
> +  if (!other_rhs)
> +    {
> +      tree lhsbase = get_base_address (lhs);
> +      /* If this store ref can't have data races, a store
> +        that was conditional can't become unconditional.  */
> +      if (ref_can_have_store_data_races (lhs))
> +       return false;
> +      tree vuse = gimple_vuse (store_middle);
> +      imm_use_iterator iter;
> +      gimple *use_stmt;
> +      /* Try to find the load before the store that matches
> +        if we have a local variable or a non trapping store.  */
> +      if ((auto_var_p (lhsbase) && !TREE_ADDRESSABLE (lhsbase))
> +         || !can_trap_as_a_store (lhs))
> +       {
> +         FOR_EACH_IMM_USE_STMT (use_stmt, iter, vuse)
> +           {
> +             if (gimple_bb (use_stmt) != cond_bb)
> +               continue;
> +             /* Looking for a load only.  */
> +             if (!gimple_assign_load_p (use_stmt))
> +               continue;
> +             tree rhs = gimple_assign_rhs1 (use_stmt);
> +             if (!operand_equal_p (rhs, lhs))
> +               continue;
> +             other_rhs = gimple_assign_lhs (use_stmt);
> +             lhs = may_alias_mem (lhs);
> +             break;
> +           }
> +       }
> +      if (!other_rhs)
> +       {
> +         gassign *new_stmt;
> +         /* If not allowing inserting on the edge, then don't.  */
> +         if (!caninsert_edge)
> +           return false;
> +         /* If LHS is an access to a local variable without address-taken
> +            (or when we allow data races) and known not to trap, we could
> +            always safely move down the store.  */
> +         if (can_trap_as_a_store (lhs))
> +           return false;
> +         lhs = may_alias_mem (lhs);
> +         /* Insert a load from the memory of the store to the temporary
> +            on the edge which did not contain the store.  */
> +         other_rhs = make_temp_ssa_name (TREE_TYPE (lhs), NULL, "cstore");
> +         new_stmt = gimple_build_assign (other_rhs, lhs);
> +         gimple_set_location (new_stmt, locus);
> +         /* Set the vuse for the new load.  */
> +         gimple_set_vuse (new_stmt,
> +                          gimple_phi_arg_def (vphi, e1->dest_idx));
> +         lhs = unshare_expr (lhs);
> +         {
> +           /* Set the no-warning bit on the rhs of the load to avoid
> +              uninit warnings.  */
> +           tree rhs1 = gimple_assign_rhs1 (new_stmt);
> +           suppress_warning (rhs1, OPT_Wuninitialized);
> +         }
> +         gsi_insert_on_edge (e1, new_stmt);
> +       }
> +    }
> +
> +  gphi *newphi;
> +  gassign *new_stmt;
> +  gimple_stmt_iterator gsi;
> +  /* Now we've checked the constraints, so do the transformation:
> +     1) Remove the store(s).  */
> +  gsi = gsi_for_stmt (store_middle);
> +  unlink_stmt_vdef (store_middle);
> +  gsi_remove (&gsi, true);
> +  release_defs (store_middle);
> +
> +  if (beforestore)
> +    {
> +      gsi = gsi_for_stmt (beforestore);
> +      unlink_stmt_vdef (beforestore);
> +      gsi_remove (&gsi, true);
> +      release_defs (beforestore);
> +    }
> +
> +
> +  /* 2) Create a PHI node at the join block, with one argument
> +       holding the old RHS, and the other holding the temporary
> +       where we stored the old memory contents.  */
> +  tree phiname = make_temp_ssa_name (TREE_TYPE (lhs), NULL, "cstore");
> +  newphi = create_phi_node (phiname, join_bb);
> +  add_phi_arg (newphi, rhs, e0, locus);
> +  add_phi_arg (newphi, other_rhs, e1, locus);
> +
> +  /* 3. Create the new store.  */
> +  new_stmt = gimple_build_assign (lhs, phiname);
> +
> +  /* Update the vdef for the new store statement. */
> +  tree newvphilhs = make_ssa_name (gimple_vop (cfun));
> +  tree vdef = gimple_phi_result (vphi);
> +  gimple_set_vuse (new_stmt, newvphilhs);
> +  gimple_set_vdef (new_stmt, vdef);
> +  gimple_phi_set_result (vphi, newvphilhs);
> +  SSA_NAME_DEF_STMT (vdef) = new_stmt;
> +  update_stmt (vphi);
> +
> +  gsi = gsi_after_labels (join_bb);
> +  gsi_insert_before (&gsi, new_stmt, GSI_SAME_STMT);
> +
> +  if (dump_file && (dump_flags & TDF_DETAILS))
> +    {
> +      fprintf (dump_file, "\nConditional store replacement happened!");
> +      if (beforestore)
> +       fprintf (dump_file, "\nRemoved the store before the condition.");
> +      fprintf (dump_file, "\nInserted a new PHI statement in joint 
> block:\n");
> +      print_gimple_stmt (dump_file, new_stmt, 0, TDF_VOPS|TDF_MEMSYMS);
> +    }
> +  statistics_counter_event (cfun, "conditional store replacement", 1);
> +  return true;
> +}
> +
> +
>  /* Return the only store in MIDDLE_BB as the candidate store for cselim.  
> Return
>     NULL if no candidate can be found.  */
>
> @@ -4632,6 +4898,11 @@ pass_cselim::execute (function *)
>        if (EDGE_COUNT (bb2->preds) > 2)
>         return;
>
> +      if (cond_store_replacement_limited (bb1, bb2, bb, e1, e2, true))
> +       {
> +         cfgchanged = true;
> +         return;
> +       }
>        gimple *assign = cselim_candidate (bb1, bb2, e1);
>        if (cond_store_replacement (bb1, bb2, e1, e2, assign, nontrap))
>         cfgchanged = true;
> --
> 2.43.0
>

Reply via email to