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)
> >

Reply via email to