On Thu, Mar 5, 2026 at 2:44 PM Jan Hubicka <[email protected]> wrote:
>
> Hi,
> I am testing this variant of the patch with comment and more explicit
> merging along the inline path.
Thank you for taking this over.
Thanks,
Andrew
>
> Honza
>
> diff --git a/gcc/ipa-modref.cc b/gcc/ipa-modref.cc
> index fc00acecfce..312beebf07c 100644
> --- a/gcc/ipa-modref.cc
> +++ b/gcc/ipa-modref.cc
> @@ -5342,13 +5342,41 @@ ipa_merge_modref_summary_after_inlining (cgraph_edge
> *edge)
> : NULL;
> class modref_summary_lto *callee_info_lto
> = summaries_lto ? summaries_lto->get (edge->callee) : NULL;
> +
> + /* Compute effective ECF_CONST, ECF_PURE, ECF_NOVOPS,
> + ECF_LOOPING_CONST_OR_PURE and ignore_stores of the inlined function from
> + the point of view of caller of the function it is transitively inlined
> to.
> + If we have inline chaing A->B->C
> + then ECF_CONST, ECF_PURE_ECF, ECF_NOVOPS and ignore_stores is the
> strongest
> + flag seen on the inline path.
> +
> + ECF_LOOPING_CONST_OR_PURE is bit special since, for example if C
> + is ECF_CONST | ECF_LOOPING_CONST_OR_PURE and B is ECF_PURE, then outcome
> + is ECF_CONST and !ECF_LOOPING_CONST_OR_PURE.
> +
> + Flags are later used to avoid merging info about side-effects of C which
> + are invisible to to the caller of A. For example, it is possible for
> const
> + function to have local array and call functions modifying it. */
> int flags = flags_from_decl_or_type (edge->callee->decl);
> - /* Combine in outer flags. */
> - cgraph_node *n;
> - for (n = edge->caller; n->inlined_to; n = n->callers->caller)
> - flags |= flags_from_decl_or_type (n->decl);
> - flags |= flags_from_decl_or_type (n->decl);
> - bool ignore_stores = ignore_stores_p (edge->caller->decl, flags);
> + bool ignore_stores = ignore_stores_p (edge->callee->decl, flags);
> + for (cgraph_node *n = edge->caller; n;
> + n = n->inlined_to ? n->callers->caller : NULL)
> + {
> + int f = flags_from_decl_or_type (n->decl);
> +
> + ignore_stores |= ignore_stores_p (n->decl, f);
> + /* If we see first CONST/PURE flag in the chain, take its
> + ECF_LOOPING_CONST_OR_PURE */
> + if (!(flags & (ECF_CONST | ECF_PURE)) && (f & (ECF_CONST | ECF_PURE)))
> + flags |= (f & ECF_LOOPING_CONST_OR_PURE);
> + /* If we already have ECF_CONST or ECF_PURE flag
> + just improve ECF_LOOPING_CONST_OR_PURE if possible. */
> + if ((flags & (ECF_CONST | ECF_PURE))
> + && (flags & ECF_LOOPING_CONST_OR_PURE)
> + && (f & (ECF_CONST | ECF_PURE)) && !(f & ECF_LOOPING_CONST_OR_PURE))
> + flags &= ECF_LOOPING_CONST_OR_PURE;
> + flags |= f & (ECF_CONST | ECF_PURE | ECF_NOVOPS);
> + }
>
> if (!callee_info && to_info)
> {
> diff --git a/gcc/testsuite/g++.dg/torture/pr120987-1.C
> b/gcc/testsuite/g++.dg/torture/pr120987-1.C
> new file mode 100644
> index 00000000000..4209679bc03
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/torture/pr120987-1.C
> @@ -0,0 +1,57 @@
> +// { dg-do run { target c++11 } }
> +// { dg-skip-if "requires hosted libstdc++ for string/memory" { ! hostedlib
> } }
> +// PR tree-optimization/120987
> +
> +#include <memory>
> +#include <string>
> +#include <cstdlib>
> +
> +#define ERROR_STRING "012345678901234567"
> +
> +struct gdb_exception
> +{
> + gdb_exception (const char *s)
> + : message (std::make_shared<std::string> (s))
> + {}
> +
> + explicit gdb_exception (gdb_exception &&other) noexcept
> + : message (std::move (other.message))
> + {
> + volatile int a = 1;
> + if (a != 1)
> + abort ();
> + }
> +
> +
> + std::shared_ptr<std::string> message;
> +};
> +
> +void __attribute__((noinline, noclone))
> +throw_exception (gdb_exception &&exception)
> +{
> + throw gdb_exception (std::move (exception));
> +}
> +
> +static void __attribute__((noinline, noclone))
> +parse_linespec (void)
> +{
> + struct gdb_exception file_exception (ERROR_STRING);
> + throw_exception (std::move (file_exception));
> +}
> +
> +int
> +main (void)
> +{
> + try
> + {
> + parse_linespec ();
> + }
> + catch (const gdb_exception &e)
> + {
> + if (*e.message != ERROR_STRING)
> + __builtin_abort();
> + return 0;
> + }
> +
> + __builtin_abort();
> +}