On Fri, Jul 17, 2026 at 6:47 AM <[email protected]> wrote:
>
> From: Dhruv Chawla <[email protected]>
>
> Leave iteration-private decls in place instead of hoisting them, so
> move_sese_region_to_fn gives each thread function its own copy. This fixes the
> testcase mentioned in PR83064, which involves a stack array used as a callee
> return slot getting shared across multiple threads leading to a data race and
> therefore multiple writes to the same location.
>
> This is a conservative way to solve this issue, by assuming that any memory
> decl not referenced outside the loop is private to the iteration. It
> would still be possible to hoist such memory if it was provable that the
> same location would not be written across multiple threads, but that
> analysis would be much more complicated and potentially unlikely to have
> much benefit. Globals are always hoisted.
>
> The gather_escaping_decls function collects those that escape before the loop
> is duplicated by versioning or the exit-first transformation, as that would
> otherwise make a private decl look like it escapes.
>
> This is an alternate attempt to fix PR125717, as this brings back the
> performance on the 800.pot3d_s benchmark to the expected levels. The previous
> attempt was at
> https://gcc.gnu.org/pipermail/gcc-patches/2026-June/720592.html.
>
> Bootstrapped and regtested on aarch64-linux-gnu.
>
> Signed-off-by: Dhruv Chawla <[email protected]>
>
>         PR tree-optimization/83064
>
> gcc/fortran/ChangeLog:
>
>         * trans-stmt.cc (gfc_trans_forall_loop): Emit
>         annot_expr_parallel_kind for do-concurrent loops.
>
> gcc/ChangeLog:
>
>         * tree-parloops.cc (struct elv_data): Add escaping hash_set.
>         (eliminate_local_variables_1): Only hoist escaping local
>         variables.
>         (eliminate_local_variables_stmt): Pass the escaping set through
>         to eliminate_local_variables_1.
>         (record_memory_decl): New function.
>         (gather_escaping_decls): Likewise.
>         (eliminate_local_variables): Pass the escaping set through to
>         eliminate_local_variables_stmt.
>         (gen_parallel_loop): Compute escaping set via
>         gather_escaping_decls and pass to
>         eliminate_local_variables.
>
> gcc/testsuite/ChangeLog:
>
>         * gfortran.dg/do_concurrent_6.f90: Update to handle parallel
>         annotation.
>         * gfortran.dg/do_concurrent_7.f90: Likewise.
>         * gfortran.dg/do_concurrent_16.f90: New test.
> ---
>  gcc/fortran/trans-stmt.cc                     | 12 +--
>  .../gfortran.dg/do_concurrent_16.f90          | 68 +++++++++++++++
>  gcc/testsuite/gfortran.dg/do_concurrent_6.f90 |  2 +-
>  gcc/testsuite/gfortran.dg/do_concurrent_7.f90 |  6 +-
>  gcc/tree-parloops.cc                          | 82 +++++++++++++++++--
>  5 files changed, 154 insertions(+), 16 deletions(-)
>  create mode 100644 gcc/testsuite/gfortran.dg/do_concurrent_16.f90
>
> diff --git a/gcc/fortran/trans-stmt.cc b/gcc/fortran/trans-stmt.cc
> index 526f42febf7..3c9ebdd0274 100644
> --- a/gcc/fortran/trans-stmt.cc
> +++ b/gcc/fortran/trans-stmt.cc
> @@ -4349,13 +4349,13 @@ gfc_trans_forall_loop (forall_info *forall_tmp, tree 
> body,
>        cond = fold_build2_loc (input_location, LE_EXPR, logical_type_node,
>                               count, build_int_cst (TREE_TYPE (count), 0));
>
> -      /* PR 83064 means that we cannot use annot_expr_parallel_kind until
> -       the autoparallelizer can handle this.  */
> +      annot_expr_kind kind = forall_tmp->do_concurrent
> +                              ? annot_expr_parallel_kind
> +                              : annot_expr_ivdep_kind;
>        if (forall_tmp->do_concurrent || iter->annot.ivdep)
> -       cond = build3 (ANNOTATE_EXPR, TREE_TYPE (cond), cond,
> -                      build_int_cst (integer_type_node,
> -                                     annot_expr_ivdep_kind),
> -                      integer_zero_node);
> +       cond
> +         = build3 (ANNOTATE_EXPR, TREE_TYPE (cond), cond,
> +                   build_int_cst (integer_type_node, kind), 
> integer_zero_node);
>
>        if (iter->annot.unroll && cond != error_mark_node)
>         cond = build3 (ANNOTATE_EXPR, TREE_TYPE (cond), cond,
> diff --git a/gcc/testsuite/gfortran.dg/do_concurrent_16.f90 
> b/gcc/testsuite/gfortran.dg/do_concurrent_16.f90
> new file mode 100644
> index 00000000000..a3df15815bb
> --- /dev/null
> +++ b/gcc/testsuite/gfortran.dg/do_concurrent_16.f90
> @@ -0,0 +1,68 @@
> +! { dg-do run }
> +! { dg-require-effective-target pthread }
> +! { dg-additional-options "-O2 -ftree-parallelize-loops=2 
> -fdump-tree-parloops2-details" }
> +!
> +! PR tree-optimization/83064
> +!
> +! Run two loops, one parallelized via do-concurrent and one serial. Compare
> +! their results to make sure that the loop that is parallelized computes the
> +! correct and expected result.
> +
> +program main
> +    use, intrinsic :: iso_fortran_env
> +    implicit none
> +
> +    integer, parameter :: nsplit = 4
> +    integer(int64), parameter :: ne = 100000
> +    integer(int64) :: stride, low(nsplit), high(nsplit), edof(ne), i
> +    real(real64), dimension(nsplit) :: pi
> +    real(real64) :: par, ser
> +
> +    edof(1::4) = 1
> +    edof(2::4) = 2
> +    edof(3::4) = 3
> +    edof(4::4) = 4
> +
> +    stride = ceiling(real(ne)/nsplit)
> +    do i = 1, nsplit
> +        high(i) = stride*i
> +    end do
> +    do i = 2, nsplit
> +        low(i) = high(i-1) + 1
> +    end do
> +    low(1) = 1
> +    high(nsplit) = ne
> +
> +    ! Parallel loop.
> +    pi = 0
> +    do concurrent (i = 1:nsplit)
> +        pi(i) = sum(compute( low(i), high(i) ))
> +    end do
> +    par = 4*sum(pi)
> +
> +    ! Serial loop as a reference.
> +    pi = 0
> +    do i = 1, nsplit
> +        pi(i) = sum(compute( low(i), high(i) ))
> +    end do
> +    ser = 4*sum(pi)
> +
> +    if (abs(par - ser) > 1.0e-9_real64 * abs(ser)) stop 1
> +
> +contains
> +
> +    pure function compute( low, high ) result( tmp )
> +        integer(int64), intent(in) :: low, high
> +        real(real64), dimension(nsplit) :: tmp
> +        integer(int64) :: j, k
> +
> +        tmp = 0
> +        do j = low, high
> +            k = edof(j)
> +            tmp(k) = tmp(k) + (-1.0_real64)**(j+1) / real( 2*j-1 )
> +        end do
> +    end function
> +
> +end program main
> +
> +! { dg-final { scan-tree-dump "parallelizing \[^\n\r\]*loop" "parloops2" } }
> diff --git a/gcc/testsuite/gfortran.dg/do_concurrent_6.f90 
> b/gcc/testsuite/gfortran.dg/do_concurrent_6.f90
> index 9585a9f96f4..5320fd108a1 100644
> --- a/gcc/testsuite/gfortran.dg/do_concurrent_6.f90
> +++ b/gcc/testsuite/gfortran.dg/do_concurrent_6.f90
> @@ -10,4 +10,4 @@ program main
>    print *,sum(a)
>  end program main
>
> -! { dg-final { scan-tree-dump-times "ivdep" 1 "original" } }
> +! { dg-final { scan-tree-dump-times "parallel" 1 "original" } }
> diff --git a/gcc/testsuite/gfortran.dg/do_concurrent_7.f90 
> b/gcc/testsuite/gfortran.dg/do_concurrent_7.f90
> index 604f6712d05..c4c73094960 100644
> --- a/gcc/testsuite/gfortran.dg/do_concurrent_7.f90
> +++ b/gcc/testsuite/gfortran.dg/do_concurrent_7.f90
> @@ -21,6 +21,6 @@ program dc
>    end do
>  end program
>
> -! { dg-final { scan-tree-dump "ANNOTATE_EXPR .* ivdep>, vector" "original" } 
> }
> -! { dg-final { scan-tree-dump "ANNOTATE_EXPR .* ivdep>, no-vector" 
> "original" } }
> -! { dg-final { scan-tree-dump "ANNOTATE_EXPR .* ivdep>, unroll 4>, 
> no-vector" "original" } }
> +! { dg-final { scan-tree-dump "ANNOTATE_EXPR .* parallel>, vector" 
> "original" } }
> +! { dg-final { scan-tree-dump "ANNOTATE_EXPR .* parallel>, no-vector" 
> "original" } }
> +! { dg-final { scan-tree-dump "ANNOTATE_EXPR .* parallel>, unroll 4>, 
> no-vector" "original" } }
> diff --git a/gcc/tree-parloops.cc b/gcc/tree-parloops.cc
> index 1da23f8d3c8..c16ebc7ba76 100644
> --- a/gcc/tree-parloops.cc
> +++ b/gcc/tree-parloops.cc
> @@ -1151,6 +1151,7 @@ struct elv_data
>    struct walk_stmt_info info;
>    edge entry;
>    int_tree_htab_type *decl_address;
> +  hash_set<tree> *escaping;
>    gimple_stmt_iterator *gsi;
>    bool changed;
>    bool reset;
> @@ -1175,6 +1176,9 @@ eliminate_local_variables_1 (tree *tp, int 
> *walk_subtrees, void *data)
>        if (!SSA_VAR_P (t) || DECL_EXTERNAL (t))
>         return NULL_TREE;
>
> +      if (dta->escaping && !is_global_var (t) && !dta->escaping->contains 
> (t))
> +       return NULL_TREE;
> +
>        type = TREE_TYPE (t);
>        addr_type = build_pointer_type (type);
>        addr = take_address_of (t, addr_type, dta->entry, dta->decl_address,
> @@ -1213,6 +1217,10 @@ eliminate_local_variables_1 (tree *tp, int 
> *walk_subtrees, void *data)
>        if (!var || !SSA_VAR_P (var) || DECL_EXTERNAL (var))
>         return NULL_TREE;
>
> +      if (dta->escaping && !is_global_var (var)
> +         && !dta->escaping->contains (var))
> +       return NULL_TREE;
> +
>        addr_type = TREE_TYPE (t);
>        addr = take_address_of (obj, addr_type, dta->entry, dta->decl_address,
>                               dta->gsi);
> @@ -1240,7 +1248,8 @@ eliminate_local_variables_1 (tree *tp, int 
> *walk_subtrees, void *data)
>
>  static void
>  eliminate_local_variables_stmt (edge entry, gimple_stmt_iterator *gsi,
> -                               int_tree_htab_type *decl_address)
> +                               int_tree_htab_type *decl_address,
> +                               hash_set<tree> *escaping)
>  {
>    struct elv_data dta;
>    gimple *stmt = gsi_stmt (*gsi);
> @@ -1248,6 +1257,7 @@ eliminate_local_variables_stmt (edge entry, 
> gimple_stmt_iterator *gsi,
>    memset (&dta.info, '\0', sizeof (dta.info));
>    dta.entry = entry;
>    dta.decl_address = decl_address;
> +  dta.escaping = escaping;
>    dta.changed = false;
>    dta.reset = false;
>
> @@ -1279,6 +1289,53 @@ eliminate_local_variables_stmt (edge entry, 
> gimple_stmt_iterator *gsi,
>      update_stmt (stmt);
>  }
>
> +/* Callback for walk_stmt_load_store_addr_ops to record into DATA any memory
> +   decls referenced by the gimple stmt.  */
> +
> +static bool
> +record_memory_decl (gimple *, tree base, tree, void *data)
> +{
> +  hash_set<tree> *decls = (hash_set<tree> *) data;
> +  base = get_base_address (base);
> +  if (DECL_P (base) && SSA_VAR_P (base) && !is_global_var (base)
> +      && !DECL_EXTERNAL (base))

This looks like you want

    if (VAR_P (base) && auto_var_in_fn_p (base, current_function_decl))

> +    decls->add (base);
> +  return false;
> +}
> +
> +/* Collect into ESCAPING the memory decls referenced outside LOOP.  Must run
> +   before LOOP is duplicated as a duplicated body would otherwise make a 
> private
> +   decl look like it escapes.  */
> +
> +static void
> +gather_escaping_decls (class loop *loop, hash_set<tree> *escaping)
> +{
> +  basic_block *bbs = get_loop_body (loop);
> +  unsigned i;
> +
> +  auto_bitmap in_loop;
> +  for (i = 0; i < loop->num_nodes; i++)
> +    bitmap_set_bit (in_loop, bbs[i]->index);
> +  free (bbs);
> +
> +  basic_block bb;
> +  FOR_EACH_BB_FN (bb, cfun)
> +    if (!bitmap_bit_p (in_loop, bb->index))
> +      {
> +       /* PHI arguments may contain decl addresses.  */
> +       for (gphi_iterator gpi = gsi_start_phis (bb); !gsi_end_p (gpi);
> +            gsi_next (&gpi))
> +         walk_stmt_load_store_addr_ops (gpi.phi (), escaping, NULL, NULL,
> +                                        record_memory_decl);
> +
> +       for (gimple_stmt_iterator gsi = gsi_start_nondebug_bb (bb);
> +            !gsi_end_p (gsi); gsi_next_nondebug (&gsi))
> +         walk_stmt_load_store_addr_ops (gsi_stmt (gsi), escaping,
> +                                        record_memory_decl, 
> record_memory_decl,
> +                                        record_memory_decl);

I'll note this is quadratic in the number of parallelized loops and
function size.

To me this also looks like optimization, not a safety check that's
still missing.
What happens if we hoisted/sunk the decl or if we peeled the loop?  You then
get sharing and thus still miscompilation, no?

> +      }
> +}
> +
>  /* Eliminates the references to local variables from the single entry
>     single exit region between the ENTRY and EXIT edges.
>
> @@ -1288,10 +1345,15 @@ eliminate_local_variables_stmt (edge entry, 
> gimple_stmt_iterator *gsi,
>     necessary).
>
>     2) Dereferencing a local variable -- these are replaced with indirect
> -   references.  */
> +   references.
> +
> +   A memory decl not in ESCAPING is iteration-private and left untouched, so 
> the
> +   outliner gives each thread its own copy.  This is a conservative approach 
> to
> +   avoid hoisting memory that would otherwise end up being overwritten and 
> cause
> +   data races when written by multiple threads.  */
>
>  static void
> -eliminate_local_variables (edge entry, edge exit)
> +eliminate_local_variables (edge entry, edge exit, hash_set<tree> *escaping)
>  {
>    basic_block bb;
>    auto_vec<basic_block, 3> body;
> @@ -1314,7 +1376,8 @@ eliminate_local_variables (edge entry, edge exit)
>                 has_debug_stmt = true;
>             }
>           else
> -           eliminate_local_variables_stmt (entry, &gsi, &decl_address);
> +           eliminate_local_variables_stmt (entry, &gsi, &decl_address,
> +                                           escaping);
>        }
>
>    if (has_debug_stmt)
> @@ -1322,7 +1385,8 @@ eliminate_local_variables (edge entry, edge exit)
>        if (bb != entry_bb && bb != exit_bb)
>         for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
>           if (gimple_debug_bind_p (gsi_stmt (gsi)))
> -           eliminate_local_variables_stmt (entry, &gsi, &decl_address);
> +           eliminate_local_variables_stmt (entry, &gsi, &decl_address,
> +                                           escaping);
>  }
>
>  /* Returns true if expression EXPR is not defined between ENTRY and
> @@ -2822,6 +2886,11 @@ gen_parallel_loop (class loop *loop,
>    location_t loc;
>    gimple *cond_stmt;
>
> +  /* Compute this before the loop is duplicated below.  */
> +  hash_set<tree> escaping;
> +  if (loop->can_be_parallel)
> +    gather_escaping_decls (loop, &escaping);
> +
>    /* From
>
>       ---------------------------------------------------------------------
> @@ -2994,7 +3063,8 @@ gen_parallel_loop (class loop *loop,
>       been done for oacc_kernels_p in pass_lower_omp/lower_omp ().  */
>    if (!oacc_kernels_p)
>      {
> -      eliminate_local_variables (entry, exit);
> +      eliminate_local_variables (entry, exit,
> +                                loop->can_be_parallel ? &escaping : NULL);
>        /* In the old loop, move all variables non-local to the loop to a
>          structure and back, and create separate decls for the variables used 
> in
>          loop.  */
> --
> 2.43.0
>

Reply via email to