Hi, this patch updates ipa-pure-const.c to only propagate PURE flag across calls that does not bind to local defs and are not explicitly declared const. This gets memory state into shape that the callee produced by other compiler and still accessing memory is safe.
We need similar logic for -fnon-call-exceptions which I will do incrementally. We also want to track if the original unoptimized body did access memory but that needs frontend changes because memory accesses may get folded away during parsing. Bootstrapped/regtested x86_64-linux, will commit it shortly. Honza PR ipa/70018 * cgraph.c (cgraph_set_const_flag_1): Only set as pure if function does not bind to current def. * ipa-pure-const.c (worse_state): Add FROM and TO parameters; handle conservatively calls to functions that does not need to bind to current def. (check_call): Update call of worse_state. (ignore_edge_for_nothrow): Update. (ignore_edge_for_pure_const): Likewise. (propagate_pure_const): Update calls to worse_state. (skip_function_for_local_pure_const): Reformat comments. * g++.dg/ipa/pure-const-1.C: New testcase. * g++.dg/ipa/pure-const-2.C: New testcase. * g++.dg/ipa/pure-const-3.C: New testcase. Index: cgraph.c =================================================================== --- cgraph.c (revision 235063) +++ cgraph.c (working copy) @@ -2393,7 +2393,35 @@ cgraph_set_const_flag_1 (cgraph_node *no if (DECL_STATIC_DESTRUCTOR (node->decl)) DECL_STATIC_DESTRUCTOR (node->decl) = 0; } - TREE_READONLY (node->decl) = data != NULL; + + /* Consider function: + + bool a(int *p) + { + return *p==*p; + } + + During early optimization we will turn this into: + + bool a(int *p) + { + return true; + } + + Now if this function will be detected as CONST however when interposed it + may end up being just pure. We always must assume the worst scenario here. + */ + if (TREE_READONLY (node->decl)) + ; + else if (node->binds_to_current_def_p ()) + TREE_READONLY (node->decl) = data != NULL; + else + { + if (dump_file && (dump_flags & TDF_DETAILS)) + fprintf (dump_file, "Dropping state to PURE because function does " + "not bind to current def.\n"); + DECL_PURE_P (node->decl) = data != NULL; + } DECL_LOOPING_CONST_OR_PURE_P (node->decl) = ((size_t)data & 2) != 0; return false; } Index: ipa-pure-const.c =================================================================== --- ipa-pure-const.c (revision 235063) +++ ipa-pure-const.c (working copy) @@ -440,12 +440,40 @@ better_state (enum pure_const_state_e *s } /* Merge STATE and STATE2 and LOOPING and LOOPING2 and store - into STATE and LOOPING worse of the two variants. */ + into STATE and LOOPING worse of the two variants. + N is the actual node called. */ static inline void worse_state (enum pure_const_state_e *state, bool *looping, - enum pure_const_state_e state2, bool looping2) -{ + enum pure_const_state_e state2, bool looping2, + struct symtab_node *from, + struct symtab_node *to) +{ + /* Consider function: + + bool a(int *p) + { + return *p==*p; + } + + During early optimization we will turn this into: + + bool a(int *p) + { + return true; + } + + Now if this function will be detected as CONST however when interposed it + may end up being just pure. We always must assume the worst scenario here. + */ + if (*state == IPA_CONST && state2 == IPA_CONST + && to && !TREE_READONLY (to->decl) && !to->binds_to_current_def_p (from)) + { + if (dump_file && (dump_flags & TDF_DETAILS)) + fprintf (dump_file, "Dropping state to PURE because call to %s may not " + "bind to current def.\n", to->name ()); + state2 = IPA_PURE; + } *state = MAX (*state, state2); *looping = MAX (*looping, looping2); } @@ -546,7 +574,8 @@ check_call (funct_state local, gcall *ca if (special_builtin_state (&call_state, &call_looping, callee_t)) { worse_state (&local->pure_const_state, &local->looping, - call_state, call_looping); + call_state, call_looping, + NULL, NULL); return; } /* When bad things happen to bad functions, they cannot be const @@ -617,7 +646,7 @@ check_call (funct_state local, gcall *ca == (ECF_NORETURN | ECF_NOTHROW)) || (!flag_exceptions && (flags & ECF_NORETURN))); worse_state (&local->pure_const_state, &local->looping, - call_state, call_looping); + call_state, call_looping, NULL, NULL); } /* Direct functions calls are handled by IPA propagation. */ } @@ -1134,7 +1161,8 @@ ignore_edge_for_nothrow (struct cgraph_e return true; enum availability avail; - cgraph_node *n = e->callee->function_or_virtual_thunk_symbol (&avail); + cgraph_node *n = e->callee->function_or_virtual_thunk_symbol (&avail, + e->caller); return (avail <= AVAIL_INTERPOSABLE || TREE_NOTHROW (n->decl)); } @@ -1170,7 +1198,7 @@ static bool ignore_edge_for_pure_const (struct cgraph_edge *e) { enum availability avail; - e->callee->function_or_virtual_thunk_symbol (&avail); + e->callee->function_or_virtual_thunk_symbol (&avail, e->caller); return (avail <= AVAIL_INTERPOSABLE); } @@ -1232,18 +1260,25 @@ propagate_pure_const (void) pure_const_names[w_l->pure_const_state], w_l->looping); - /* First merge in function body properties. */ + /* First merge in function body properties. + We are safe to pass NULL as FROM and TO because we will take care + of possible interposition when walking callees. */ worse_state (&pure_const_state, &looping, - w_l->pure_const_state, w_l->looping); + w_l->pure_const_state, w_l->looping, + NULL, NULL); if (pure_const_state == IPA_NEITHER) break; - /* For interposable nodes we can not assume anything. */ + /* For interposable nodes we can not assume anything. + FIXME: It should be safe to remove this conditional and allow + interposable functions with non-interposable aliases next + stage 1. */ if (w->get_availability () == AVAIL_INTERPOSABLE) { worse_state (&pure_const_state, &looping, w_l->state_previously_known, - w_l->looping_previously_known); + w_l->looping_previously_known, + NULL, NULL); if (dump_file && (dump_flags & TDF_DETAILS)) { fprintf (dump_file, @@ -1268,7 +1303,8 @@ propagate_pure_const (void) { enum availability avail; struct cgraph_node *y = e->callee-> - function_or_virtual_thunk_symbol (&avail); + function_or_virtual_thunk_symbol (&avail, + e->caller); enum pure_const_state_e edge_state = IPA_CONST; bool edge_looping = false; @@ -1318,7 +1354,7 @@ propagate_pure_const (void) w_l->state_previously_known, w_l->looping_previously_known); worse_state (&pure_const_state, &looping, - edge_state, edge_looping); + edge_state, edge_looping, e->caller, e->callee); if (pure_const_state == IPA_NEITHER) break; } @@ -1340,7 +1376,7 @@ propagate_pure_const (void) w_l->state_previously_known, w_l->looping_previously_known); worse_state (&pure_const_state, &looping, - edge_state, edge_looping); + edge_state, edge_looping, NULL, NULL); if (pure_const_state == IPA_NEITHER) break; } @@ -1378,7 +1414,7 @@ propagate_pure_const (void) w_l->state_previously_known, w_l->looping_previously_known); worse_state (&pure_const_state, &looping, - ref_state, ref_looping); + ref_state, ref_looping, NULL, NULL); if (pure_const_state == IPA_NEITHER) break; } @@ -1407,7 +1443,8 @@ propagate_pure_const (void) { enum availability avail; struct cgraph_node *y = e->callee-> - function_or_virtual_thunk_symbol (&avail); + function_or_virtual_thunk_symbol (&avail, + e->caller); if (avail > AVAIL_INTERPOSABLE) can_free = get_function_state (y)->can_free; @@ -1552,7 +1589,8 @@ propagate_nothrow (void) continue; struct cgraph_node *y = e->callee-> - function_or_virtual_thunk_symbol (&avail); + function_or_virtual_thunk_symbol (&avail, + e->caller); /* We can use info about the callee only if we know it can not be interposed. */ @@ -1664,8 +1702,9 @@ make_pass_ipa_pure_const (gcc::context * static bool skip_function_for_local_pure_const (struct cgraph_node *node) { - /* Because we do not schedule pass_fixup_cfg over whole program after early optimizations - we must not promote functions that are called by already processed functions. */ + /* Because we do not schedule pass_fixup_cfg over whole program after early + optimizations we must not promote functions that are called by already + processed functions. */ if (function_called_by_processed_nodes_p ()) { @@ -1676,7 +1715,8 @@ skip_function_for_local_pure_const (stru if (node->get_availability () <= AVAIL_INTERPOSABLE) { if (dump_file) - fprintf (dump_file, "Function is not available or interposable; not analyzing.\n"); + fprintf (dump_file, + "Function is not available or interposable; not analyzing.\n"); return true; } return false; Index: testsuite/g++.dg/ipa/pure-const-1.C =================================================================== --- testsuite/g++.dg/ipa/pure-const-1.C (revision 0) +++ testsuite/g++.dg/ipa/pure-const-1.C (working copy) @@ -0,0 +1,22 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -fdump-tree-optimized" } */ +int *ptr; +static int barvar; + +/* We can not detect A to be const because it may be interposed by unoptimized + body. */ +inline +__attribute__ ((noinline)) +int a(void) +{ + return *ptr == *ptr; +} +main() +{ + int aa; + ptr = &barvar; + aa=!a(); + ptr = 0; + return aa; +} +/* { dg-final { scan-tree-dump "barvar" "optimized" } } */ Index: testsuite/g++.dg/ipa/pure-const-2.C =================================================================== --- testsuite/g++.dg/ipa/pure-const-2.C (revision 0) +++ testsuite/g++.dg/ipa/pure-const-2.C (working copy) @@ -0,0 +1,26 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -fdump-tree-optimized" } */ +int *ptr; +static int barvar; +/* We can not detect A to be const because it may be interposed by unoptimized + body. */ +inline +__attribute__ ((noinline)) +int a(void) +{ + return *ptr == *ptr; +} +__attribute__ ((noinline)) +static int b(void) +{ + return a(); +} +main() +{ + int aa; + ptr = &barvar; + aa=!b(); + ptr = 0; + return aa; +} +/* { dg-final { scan-tree-dump "barvar" "optimized" } } */ Index: testsuite/g++.dg/ipa/pure-const-3.C =================================================================== --- testsuite/g++.dg/ipa/pure-const-3.C (revision 0) +++ testsuite/g++.dg/ipa/pure-const-3.C (working copy) @@ -0,0 +1,32 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -fdump-tree-optimized" } */ +int *ptr; +static int barvar; +static int b(int a); +/* We can not detect A to be const because it may be interposed by unoptimized + body. */ +inline +__attribute__ ((noinline)) +int a(int a) +{ + if (a>0) + return b(a-1); + return *ptr == *ptr; +} +inline +__attribute__ ((noinline)) +static int b(int p) +{ + if (p<0) + return a(p+1); + return 1; +} +main() +{ + int aa; + ptr = &barvar; + aa=!b(3); + ptr = 0; + return aa; +} +/* { dg-final { scan-tree-dump "barvar" "optimized" } } */