On Thu, Nov 12, 2026 at 9:56 AM Andrea Pinski
<[email protected]> wrote:
>
> 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?
I forgot to mention I like the idea of the patch.
Thanks,
Andrea
>
> > + && (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)
> >