On Tue, Oct 20, 2026 at 2:01 AM <[email protected]> wrote:
>
> From: Kyrylo Tkachov <[email protected]>
>
> The early inliner charges an aggregate copy into the return value even
> when its source is a local variable that SRA can remove after inlining.
> For a value-type wrapper such as
>
> T operator* (const T &a, const T &b)
> {
> T t = a;
> return t *= b;
> }
>
> the copy appears as
>
> <retval> = t;
>
> Charging this copy can put the wrapper above early-inlining-insns and
> leave its operands and result in memory at each call.
>
> Credit aggregate return copies from automatic local variables and
> parameters whose address is not taken. Recognize both RESULT_DECL and
> an invisible-reference result. Preserve the existing half credit for
> unmodified parameters. Keep the cost of copies from globals, statics,
> pointers and address-taken locals. Exclude volatile accesses before
> applying any elimination credit.
>
> Add tests for early inlining, source restrictions, invisible-reference
> returns and volatile scalar and aggregate accesses.
>
> Bootstrapped and tested on aarch64-none-linux-gnu.
> Ok for trunk?
> Thanks,
> Kyrill
>
> Cc: Martin Jambor <[email protected]>
>
> Signed-off-by: Kyrylo Tkachov <[email protected]>
>
> gcc/ChangeLog:
>
> * ipa-fnsummary.cc (eliminated_by_inlining_prob): Do not credit
> volatile accesses. Credit aggregate copies into the return value
> from automatic local variables and parameters whose address is not
> taken as eliminated by inlining.
>
> gcc/testsuite/ChangeLog:
>
> * g++.dg/ipa/inline-aggregate-retval.C: New test.
> * g++.dg/ipa/inline-aggregate-retval-2.C: Likewise.
> * gcc.dg/ipa/inline-aggregate-retval.c: Likewise.
> * gcc.dg/ipa/inline-aggregate-retval-2.c: Likewise.
> * gcc.dg/ipa/inline-aggregate-retval-3.c: Likewise.
> ---
> gcc/ipa-fnsummary.cc | 17 ++++-
> .../g++.dg/ipa/inline-aggregate-retval-2.C | 21 ++++++
> .../g++.dg/ipa/inline-aggregate-retval.C | 18 +++++
> .../gcc.dg/ipa/inline-aggregate-retval-2.c | 75 +++++++++++++++++++
> .../gcc.dg/ipa/inline-aggregate-retval-3.c | 26 +++++++
> .../gcc.dg/ipa/inline-aggregate-retval.c | 27 +++++++
> 6 files changed, 183 insertions(+), 1 deletion(-)
> create mode 100644 gcc/testsuite/g++.dg/ipa/inline-aggregate-retval-2.C
> create mode 100644 gcc/testsuite/g++.dg/ipa/inline-aggregate-retval.C
> create mode 100644 gcc/testsuite/gcc.dg/ipa/inline-aggregate-retval-2.c
> create mode 100644 gcc/testsuite/gcc.dg/ipa/inline-aggregate-retval-3.c
> create mode 100644 gcc/testsuite/gcc.dg/ipa/inline-aggregate-retval.c
>
> diff --git a/gcc/ipa-fnsummary.cc b/gcc/ipa-fnsummary.cc
> index 09f50846903..549504375d6 100644
> --- a/gcc/ipa-fnsummary.cc
> +++ b/gcc/ipa-fnsummary.cc
> @@ -1378,7 +1378,7 @@ eliminated_by_inlining_prob (ipa_func_body_info *fbi,
> gimple *stmt)
> enum gimple_code code = gimple_code (stmt);
> enum tree_code rhs_code;
>
> - if (!optimize)
> + if (!optimize || gimple_has_volatile_ops (stmt))
> return 0;
>
> switch (code)
> @@ -1491,6 +1491,21 @@ eliminated_by_inlining_prob (ipa_func_body_info *fbi,
> gimple *stmt)
> rhs_free = true;
> if (lhs_free && rhs_free)
> return 1;
> + /* Credit an aggregate copy into the return value from a local
> + variable or parameter whose address is not taken as eliminated
> + by inlining. Field accesses in the caller can allow SRA to
> + remove the copy. */
> + if (AGGREGATE_TYPE_P (TREE_TYPE (lhs))
> + && (TREE_CODE (inner_lhs) == RESULT_DECL
> + || (TREE_CODE (inner_lhs) == MEM_REF
> + && TREE_CODE (TREE_OPERAND (inner_lhs, 0)) == SSA_NAME
> + && SSA_NAME_VAR (TREE_OPERAND (inner_lhs, 0))
> + && TREE_CODE (SSA_NAME_VAR (TREE_OPERAND (inner_lhs,
> 0)))
> + == RESULT_DECL))
I see this check is basically done before. Why not refactor it
slightly to reuse the check on result decl?
> + && (VAR_P (inner_rhs) || TREE_CODE (inner_rhs) == PARM_DECL)
> + && auto_var_in_fn_p (inner_rhs, current_function_decl)
> + && !TREE_ADDRESSABLE (inner_rhs))
> + return 2;
> }
> return 0;
> default:
> diff --git a/gcc/testsuite/g++.dg/ipa/inline-aggregate-retval-2.C
> b/gcc/testsuite/g++.dg/ipa/inline-aggregate-retval-2.C
> new file mode 100644
> index 00000000000..1e991b1bd5b
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/ipa/inline-aggregate-retval-2.C
> @@ -0,0 +1,21 @@
> +// A user-provided destructor makes the result an invisible reference.
> +// The comma expression requires a copy from the local into that result.
> +// Early inlining removes the empty destructor and its use of the local's
> +// address. Keep the copy visible by limiting total scalarization.
> +// { dg-do compile }
> +// { dg-options "-O2 --param sra-max-scalarization-size-Ospeed=1
> -fdump-ipa-fnsummary-details -fno-partial-inlining -fno-ipa-cp" }
> +
> +struct V
> +{
> + long d[8];
> + ~V () {}
> +};
> +
> +V
> +from_local (const V &a)
> +{
> + V t = a;
> + return (t.d[0] += 1, t);
> +}
> +
> +// { dg-final { scan-ipa-dump {\*_[0-9]+\(D\) = t;\n[^\n]*\n\t\tWill be
> eliminated by inlining} "fnsummary" } }
> diff --git a/gcc/testsuite/g++.dg/ipa/inline-aggregate-retval.C
> b/gcc/testsuite/g++.dg/ipa/inline-aggregate-retval.C
> new file mode 100644
> index 00000000000..eb9e3f72ad4
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/ipa/inline-aggregate-retval.C
> @@ -0,0 +1,18 @@
> +// A by-value 'operator*' copies its local result into the return value.
> +// The copy is eliminated by inlining, so it does not count towards the early
> +// inliner growth and the operator fits in --param early-inlining-insns.
> +// { dg-do compile }
> +// { dg-options "-O3 --param early-inlining-insns=14 -fno-inline-functions
> -fno-partial-inlining -fdump-tree-einline-optimized" }
> +
> +struct V {
> + long d[8];
> + V &operator*= (const V &o) { for (int i = 0; i < 8; i++) d[i] *= o.d[i];
> return *this; }
> +};
> +
> +static V operator* (const V &a, const V &b) { V t = a; return t *= b; }
> +
> +V ga, gb, gc, r;
> +
> +void use () { r = ga * gb * gc; }
> +
> +// { dg-final { scan-tree-dump-times {Inlining V operator\*\(const V&, const
> V&\)/[0-9]+ into void use\(\)/[0-9]+} 2 "einline" } }
> diff --git a/gcc/testsuite/gcc.dg/ipa/inline-aggregate-retval-2.c
> b/gcc/testsuite/gcc.dg/ipa/inline-aggregate-retval-2.c
> new file mode 100644
> index 00000000000..9d2b4a2b2be
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/ipa/inline-aggregate-retval-2.c
> @@ -0,0 +1,75 @@
> +/* An aggregate copy into the return value is eliminated by inlining only
> + when its source is a local variable or a parameter whose address is not
> + taken. */
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -fdump-ipa-fnsummary-details -fno-early-inlining
> -fno-partial-inlining -fno-ipa-cp" } */
> +
> +struct V { long d[8]; };
> +struct H { struct V *p; };
> +struct V glob;
> +void sink (struct V *);
> +
> +struct V
> +from_local (const struct V *a, const struct V *b)
> +{
> + struct V loc = *a;
> + for (int i = 0; i < 8; i++)
> + loc.d[i] *= b->d[i];
> + return loc;
> +}
> +
> +struct V
> +from_parm (struct V parm, const struct V *b)
> +{
> + for (int i = 0; i < 8; i++)
> + parm.d[i] *= b->d[i];
> + return parm;
> +}
> +
> +struct V
> +from_unmodified_parm (struct V uparm)
> +{
> + return uparm;
> +}
> +
> +struct V
> +from_escaped_local (const struct V *a)
> +{
> + struct V esc = *a;
> + sink (&esc);
> + return esc;
> +}
> +
> +struct V
> +from_global (const struct V *b)
> +{
> + for (int i = 0; i < 8; i++)
> + glob.d[i] *= b->d[i];
> + return glob;
> +}
> +
> +struct V
> +from_static (const struct V *b)
> +{
> + static struct V stat;
> + for (int i = 0; i < 8; i++)
> + stat.d[i] += b->d[i];
> + return stat;
> +}
> +
> +struct V
> +from_pointer (const struct H *h, const struct V *b)
> +{
> + struct V *p = h->p;
> + for (int i = 0; i < 8; i++)
> + p->d[i] *= b->d[i];
> + return *p;
> +}
> +
> +/* { dg-final { scan-ipa-dump {<retval> = loc;\n[^\n]*\n\t\tWill be
> eliminated by inlining} "fnsummary" } } */
> +/* { dg-final { scan-ipa-dump {<retval> = parm;\n[^\n]*\n\t\tWill be
> eliminated by inlining} "fnsummary" } } */
> +/* { dg-final { scan-ipa-dump {<retval> = uparm;\n[^\n]*\n\t\t50% will be
> eliminated by inlining} "fnsummary" } } */
> +/* { dg-final { scan-ipa-dump-not {<retval> = esc;\n[^\n]*\n\t\tWill be
> eliminated by inlining} "fnsummary" } } */
> +/* { dg-final { scan-ipa-dump-not {<retval> = glob;\n[^\n]*\n\t\tWill be
> eliminated by inlining} "fnsummary" } } */
> +/* { dg-final { scan-ipa-dump-not {<retval> = stat;\n[^\n]*\n\t\tWill be
> eliminated by inlining} "fnsummary" } } */
> +/* { dg-final { scan-ipa-dump-not {<retval> = \*p_[0-9]+;\n[^\n]*\n\t\tWill
> be eliminated by inlining} "fnsummary" } } */
> diff --git a/gcc/testsuite/gcc.dg/ipa/inline-aggregate-retval-3.c
> b/gcc/testsuite/gcc.dg/ipa/inline-aggregate-retval-3.c
> new file mode 100644
> index 00000000000..dc08d688e3d
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/ipa/inline-aggregate-retval-3.c
> @@ -0,0 +1,26 @@
> +/* Volatile accesses remain after inlining. */
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -fdump-ipa-fnsummary-details -fno-early-inlining
> -fno-partial-inlining -fno-ipa-cp" } */
> +
> +int
> +from_pointer (volatile int *p)
> +{
> + return *p;
> +}
> +
> +int
> +from_parameter (volatile int p)
> +{
> + return p;
> +}
> +
> +struct V { long d[8]; };
> +
> +struct V
> +from_aggregate_parameter (volatile struct V p)
> +{
> + return p;
> +}
> +
> +/* { dg-final { scan-ipa-dump-times { =\{v\}
> [^\n]*;\n[^\n]*\n\t\tAccounting} 3 "fnsummary" } } */
> +/* { dg-final { scan-ipa-dump-not { =\{v\}
> [^\n]*;\n[^\n]*\n\t\t[^\n]*[Ww]ill be eliminated by inlining} "fnsummary" } }
> */
> diff --git a/gcc/testsuite/gcc.dg/ipa/inline-aggregate-retval.c
> b/gcc/testsuite/gcc.dg/ipa/inline-aggregate-retval.c
> new file mode 100644
> index 00000000000..8220a69241c
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/ipa/inline-aggregate-retval.c
> @@ -0,0 +1,27 @@
> +/* The copy of the local result of 'mul' into its return value is eliminated
> + by inlining, so it does not count towards the early inliner growth and
> + 'mul' fits in --param early-inlining-insns. */
> +/* { dg-do compile } */
> +/* { dg-options "-O3 --param early-inlining-insns=14 -fno-inline-functions
> -fno-partial-inlining -fdump-tree-einline-optimized" } */
> +
> +struct V { long d[8]; };
> +
> +static struct V
> +mul (const struct V *a, const struct V *b)
> +{
> + struct V t = *a;
> + for (int i = 0; i < 8; i++)
> + t.d[i] *= b->d[i];
> + return t;
> +}
> +
> +struct V ga, gb, gc, r;
> +
> +void
> +use (void)
> +{
> + struct V x = mul (&ga, &gb);
> + r = mul (&x, &gc);
> +}
> +
> +/* { dg-final { scan-tree-dump-times {Inlining mul/[0-9]+ into use/[0-9]+} 2
> "einline" } } */
> --
> 2.54.0 (Apple Git-157)
>