diff --git a/gcc/ipa-modref.cc b/gcc/ipa-modref.cc index fc00acecfce..7da4d39c099 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 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 +#include +#include + +#define ERROR_STRING "012345678901234567" + +struct gdb_exception +{ + gdb_exception (const char *s) + : message (std::make_shared (s)) + {} + + explicit gdb_exception (gdb_exception &&other) noexcept + : message (std::move (other.message)) + { + volatile int a = 1; + if (a != 1) + abort (); + } + + + std::shared_ptr 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(); +}