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

Reply via email to