On Thu, Aug 27, 2026 at 2:09 PM <[email protected]> wrote:
>
> From: Kyrylo Tkachov <[email protected]>
>
> tree_could_trap_p already knows that the this pointer of a method points to
> an object of at least the size of the method base type, so a load from that
> object cannot trap and loop invariant motion may speculate it
> (PR tree-optimization/121685). A parameter of reference type carries the
> same guarantee, because it is bound to a valid object of the referenced
> type. nonnull_arg_p already relies on exactly that when it reports such a
> parameter as non-null, so only the size half of the fact was missing.
Is that so? At least nonnull_arg_p checks flag_delete_null_pointer_checks
for 'this' as well. There's also targetm.ddr_space.zero_address_valid.
Can we at least call nonnull_arg_p from your whole_object_param_size
to bring natural consistency between both?
ISTR we've been here before (reference types) and rejected this as
too strong (so I'm surprised about nonnull_arg_p).
I'll note D, fortran and Ada also have referenecs to REFERENCE_TYPE.
Neither the REFERENCE_TYPE nor the PARM_DECL documentation
mention any constraints about non-NULLness or trapping guarantees.
METHOD_TYPE is used less (but again Ada references it).
Richard.
> Split the object size query out of tree_could_trap_p into
> whole_object_param_size and let it answer for reference parameters as well.
> The assumption is the one nonnull_arg_p makes, so
> -fno-delete-null-pointer-checks turns it off.
>
> GAP Benchmark Suite bfs is the motivating workload. BUStep takes the
> graph and the two frontier bitmaps by reference:
>
> for (NodeID u = 0; u < g.num_nodes (); u++)
> if (parent[u] < 0)
> for (NodeID v : g.in_neigh (u))
> if (front.get_bit (v))
> { parent[u] = v; awake_count++; next.set_bit (u); break; }
>
> g.in_index_ and front.start_ are invariant in the loop over u, but they sit
> under the parent[u] < 0 guard, so ALWAYS_EXECUTED_IN is null for their block
> and compute_invariantness discards them before asking any dependence
> question:
>
> <bb 5> _19 = parent_17(D)->start_;
> _2 = *_29; if (_2 < 0) goto <bb 6>; else goto <bb 10>;
> <bb 6> _30 = MEM[(const CSRGraph *)g_15(D)].in_index_;
> <bb 7> _38 = MEM[(const Bitmap *)front_22(D)].start_;
>
> The alias oracle already disambiguates both refs. The only missing input
> was that the loads are safe to speculate. With the patch lim2 reports
>
> Moving statement _30 = MEM[(const CSRGraph *)g_15(D)].in_index_;
> (cost 20) out of loop 1.
> Moving statement _38 = MEM[(const Bitmap *)front_22(D)].start_;
> (cost 20) out of loop 1.
>
> and the loop over the nodes reloads neither, which also removes the
> induction variable the in_index_ reload was forcing.
>
> Measured on Grace for GAPBS bfs, kron-23, serial, -O3
> -mcpu=grace the runtime improves by 6.3%.
>
> Bootstrapped and tested on aarch64-none-linux-gnu.
> Ok for trunk?
> Thanks,
> Kyrill
>
> gcc/ChangeLog:
>
> * tree-eh.cc (whole_object_param_size): New function, carrying the
> this pointer case out of ...
> (tree_could_trap_p): ... here. Use it, so that a parameter of
> reference type is handled as well.
>
> gcc/testsuite/ChangeLog:
>
> * g++.dg/tree-ssa/lim-ref-1.C: New test.
> * g++.dg/tree-ssa/lim-ref-2.C: New test.
> * g++.dg/tree-ssa/lim-ref-3.C: New test.
>
> Signed-off-by: Kyrylo Tkachov <[email protected]>
> ---
> gcc/testsuite/g++.dg/tree-ssa/lim-ref-1.C | 20 ++++++++++
> gcc/testsuite/g++.dg/tree-ssa/lim-ref-2.C | 20 ++++++++++
> gcc/testsuite/g++.dg/tree-ssa/lim-ref-3.C | 20 ++++++++++
> gcc/tree-eh.cc | 48 ++++++++++++++++++-----
> 4 files changed, 99 insertions(+), 9 deletions(-)
> create mode 100644 gcc/testsuite/g++.dg/tree-ssa/lim-ref-1.C
> create mode 100644 gcc/testsuite/g++.dg/tree-ssa/lim-ref-2.C
> create mode 100644 gcc/testsuite/g++.dg/tree-ssa/lim-ref-3.C
>
> diff --git a/gcc/testsuite/g++.dg/tree-ssa/lim-ref-1.C
> b/gcc/testsuite/g++.dg/tree-ssa/lim-ref-1.C
> new file mode 100644
> index 00000000000..05f474fbeae
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/tree-ssa/lim-ref-1.C
> @@ -0,0 +1,20 @@
> +// A parameter of reference type is bound to a valid object of the referenced
> +// type, so a load from it cannot trap and may be hoisted out of the loop
> even
> +// though it is only executed on some iterations.
> +
> +// { dg-do compile }
> +// { dg-options "-O2 -fdump-tree-lim2-details" }
> +
> +struct S { int n; int *p; };
> +
> +int
> +f (S &s, int n)
> +{
> + int r = 0;
> + for (int i = 0; i < n; ++i)
> + if (i & 1)
> + r += s.p[i];
> + return r;
> +}
> +
> +// { dg-final { scan-tree-dump "Moving statement \[^\n\r]*->p;" "lim2" } }
> diff --git a/gcc/testsuite/g++.dg/tree-ssa/lim-ref-2.C
> b/gcc/testsuite/g++.dg/tree-ssa/lim-ref-2.C
> new file mode 100644
> index 00000000000..4b089df293a
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/tree-ssa/lim-ref-2.C
> @@ -0,0 +1,20 @@
> +// -fno-delete-null-pointer-checks tells the compiler not to rely on a C++
> +// reference designating a valid object, so the conditional load must stay in
> +// the loop.
> +
> +// { dg-do compile }
> +// { dg-options "-O2 -fno-delete-null-pointer-checks
> -fdump-tree-lim2-details" }
> +
> +struct S { int n; int *p; };
> +
> +int
> +f (S &s, int n)
> +{
> + int r = 0;
> + for (int i = 0; i < n; ++i)
> + if (i & 1)
> + r += s.p[i];
> + return r;
> +}
> +
> +// { dg-final { scan-tree-dump-not "Moving statement \[^\n\r]*->p;" "lim2" }
> }
> diff --git a/gcc/testsuite/g++.dg/tree-ssa/lim-ref-3.C
> b/gcc/testsuite/g++.dg/tree-ssa/lim-ref-3.C
> new file mode 100644
> index 00000000000..95b96df5051
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/tree-ssa/lim-ref-3.C
> @@ -0,0 +1,20 @@
> +// The referenced type bounds what may be speculated: an access past the end
> +// of the referenced object still counts as trapping and stays in the loop.
> +
> +// { dg-do compile }
> +// { dg-options "-O2 -fdump-tree-lim2-details" }
> +
> +struct Small { int a; };
> +struct Big { int a; long b[8]; };
> +
> +long
> +g (Small &s, int n)
> +{
> + long r = 0;
> + for (int i = 0; i < n; ++i)
> + if (i & 1)
> + r += reinterpret_cast<Big &> (s).b[3];
> + return r;
> +}
> +
> +// { dg-final { scan-tree-dump-not "Moving statement \[^\n\r]*struct Big"
> "lim2" } }
> diff --git a/gcc/tree-eh.cc b/gcc/tree-eh.cc
> index 0835f55af08..17c56e44d21 100644
> --- a/gcc/tree-eh.cc
> +++ b/gcc/tree-eh.cc
> @@ -2752,6 +2752,44 @@ ref_outside_object_p (tree size, poly_offset_int off,
> tree refsz)
> return false;
> }
>
> +/* If PTR is a parameter of the current function, or the default definition
> + of one, that is known to point to a whole object, return the size of that
> + object in bytes. Otherwise return NULL_TREE.
> +
> + Two kinds of parameter qualify. The this pointer of a method points to
> + an object of the method base type. A parameter of reference type is
> + bound to a valid object of the referenced type. In both cases the
> + pointed-to object is at least as large as that type. */
> +
> +static tree
> +whole_object_param_size (tree ptr)
> +{
> + if (!cfun)
> + return NULL_TREE;
> +
> + if (TREE_CODE (ptr) == SSA_NAME)
> + {
> + if (!SSA_NAME_IS_DEFAULT_DEF (ptr))
> + return NULL_TREE;
> + ptr = SSA_NAME_VAR (ptr);
> + }
> +
> + if (!ptr || TREE_CODE (ptr) != PARM_DECL)
> + return NULL_TREE;
> +
> + /* Reference validity is the same assumption nonnull_arg_p makes, so it
> + is disabled by the same flag. */
> + if (TREE_CODE (TREE_TYPE (ptr)) == REFERENCE_TYPE
> + && flag_delete_null_pointer_checks)
> + return TYPE_SIZE_UNIT (TREE_TYPE (TREE_TYPE (ptr)));
> +
> + if (TREE_CODE (TREE_TYPE (cfun->decl)) == METHOD_TYPE
> + && ptr == DECL_ARGUMENTS (cfun->decl))
> + return TYPE_SIZE_UNIT (TYPE_METHOD_BASETYPE (TREE_TYPE (cfun->decl)));
> +
> + return NULL_TREE;
> +}
> +
> /* Return true if EXPR can trap, as in dereferencing an invalid pointer
> location or floating point arithmetic. C.f. the rtl version, may_trap_p.
> This routine expects only GIMPLE lhs or rhs input. */
> @@ -2851,19 +2889,11 @@ tree_could_trap_p (tree expr)
> tree refsz = TYPE_SIZE_UNIT (TREE_TYPE (expr));
> return ref_outside_object_p (size, off, refsz);
> }
> - if (cfun
> - && TREE_CODE (TREE_TYPE (cfun->decl)) == METHOD_TYPE
> - && ((TREE_CODE (TREE_OPERAND (expr, 0)) == SSA_NAME
> - && SSA_NAME_IS_DEFAULT_DEF (TREE_OPERAND (expr, 0))
> - && (SSA_NAME_VAR (TREE_OPERAND (expr, 0))
> - == DECL_ARGUMENTS (cfun->decl)))
> - || TREE_OPERAND (expr, 0) == DECL_ARGUMENTS (cfun->decl)))
> + if (tree size = whole_object_param_size (TREE_OPERAND (expr, 0)))
> {
> poly_offset_int off = mem_ref_offset (expr);
> if (maybe_lt (off, 0))
> return true;
> - tree size = TYPE_SIZE_UNIT
> - (TYPE_METHOD_BASETYPE (TREE_TYPE (cfun->decl)));
> tree refsz = TYPE_SIZE_UNIT (TREE_TYPE (expr));
> return ref_outside_object_p (size, off, refsz);
> }
> --
> 2.50.1 (Apple Git-155)
>