https://gcc.gnu.org/g:27b4500ec3f97e0e8da9949b352a32147af8edf5
commit r16-7968-g27b4500ec3f97e0e8da9949b352a32147af8edf5 Author: Jan Hubicka <[email protected]> Date: Tue Mar 10 07:56:23 2026 +0100 Fix merging of flags in ipa_merge_modref_summary_after_inlining When merging the modref summary after inlining, we merge all of the flags of the outer functions that was inlined into. But things go wrong as now the flags includes both ECF_NORETURN and ECF_NOTHROW. This happens because the function which was being inlined had ECF_NOTHROW while caller had ECF_NORETURN. When both of these are set, ignore_stores_p and ignore_nondeterminism_p return true. But in this case the inner function is still just nothrow and not noreturn. Originally the code was written to only merge ECF_PURE and ECF_CONST where this logic is correct. This fixes merigng of ignore_stores and ECF_LOOPING_CONST_OR_PURE flags. Bootstrapped and tested on x86_64-linux-gnu. PR tree-optimization/120987 gcc/ChangeLog: * ipa-modref.cc (ipa_merge_modref_summary_after_inlining): Mask off non const/pure/novops related flags when combining of outer functions. gcc/testsuite/ChangeLog: * g++.dg/torture/pr120987-1.C: New test. Co-authored-by: Andrew Pinski <[email protected]> Diff: --- gcc/ipa-modref.cc | 45 ++++++++++++++++++++---- gcc/testsuite/g++.dg/torture/pr120987-1.C | 57 +++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 6 deletions(-) diff --git a/gcc/ipa-modref.cc b/gcc/ipa-modref.cc index fc00acecfce7..7da4d39c0992 100644 --- a/gcc/ipa-modref.cc +++ b/gcc/ipa-modref.cc @@ -5342,13 +5342,46 @@ 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. + + Consider inline chain A->B->C, where (edge is the edge B->C). + 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 non-const 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. */ + else 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 000000000000..4209679bc036 --- /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(); +}
