Commit cc950f98 by Jan Hubicka Committed by Jan Hubicka

re PR c++/70018 (Possible issue around IPO and C++ comdats discovered as pure/const)


	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.

From-SVN: r235065
parent f13fe18b
2016-04-15 Jan Hubicka <jh@suse.cz> 2016-04-15 Jan Hubicka <jh@suse.cz>
PR ipa/70018 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.
2016-04-15 Jan Hubicka <jh@suse.cz>
PR ipa/70018
* cgraph.c (cgraph_node::get_availability): Add REF parameter. * cgraph.c (cgraph_node::get_availability): Add REF parameter.
(cgraph_node::function_symbol): Likewise. (cgraph_node::function_symbol): Likewise.
(cgraph_node::function_or_virtual_thunk_symbol): Likewise. (cgraph_node::function_or_virtual_thunk_symbol): Likewise.
......
...@@ -2393,7 +2393,35 @@ cgraph_set_const_flag_1 (cgraph_node *node, void *data) ...@@ -2393,7 +2393,35 @@ cgraph_set_const_flag_1 (cgraph_node *node, void *data)
if (DECL_STATIC_DESTRUCTOR (node->decl)) if (DECL_STATIC_DESTRUCTOR (node->decl))
DECL_STATIC_DESTRUCTOR (node->decl) = 0; 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; DECL_LOOPING_CONST_OR_PURE_P (node->decl) = ((size_t)data & 2) != 0;
return false; return false;
} }
......
...@@ -440,12 +440,40 @@ better_state (enum pure_const_state_e *state, bool *looping, ...@@ -440,12 +440,40 @@ better_state (enum pure_const_state_e *state, bool *looping,
} }
/* Merge STATE and STATE2 and LOOPING and LOOPING2 and store /* 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 static inline void
worse_state (enum pure_const_state_e *state, bool *looping, 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); *state = MAX (*state, state2);
*looping = MAX (*looping, looping2); *looping = MAX (*looping, looping2);
} }
...@@ -546,7 +574,8 @@ check_call (funct_state local, gcall *call, bool ipa) ...@@ -546,7 +574,8 @@ check_call (funct_state local, gcall *call, bool ipa)
if (special_builtin_state (&call_state, &call_looping, callee_t)) if (special_builtin_state (&call_state, &call_looping, callee_t))
{ {
worse_state (&local->pure_const_state, &local->looping, worse_state (&local->pure_const_state, &local->looping,
call_state, call_looping); call_state, call_looping,
NULL, NULL);
return; return;
} }
/* When bad things happen to bad functions, they cannot be const /* When bad things happen to bad functions, they cannot be const
...@@ -617,7 +646,7 @@ check_call (funct_state local, gcall *call, bool ipa) ...@@ -617,7 +646,7 @@ check_call (funct_state local, gcall *call, bool ipa)
== (ECF_NORETURN | ECF_NOTHROW)) == (ECF_NORETURN | ECF_NOTHROW))
|| (!flag_exceptions && (flags & ECF_NORETURN))); || (!flag_exceptions && (flags & ECF_NORETURN)));
worse_state (&local->pure_const_state, &local->looping, 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. */ /* Direct functions calls are handled by IPA propagation. */
} }
...@@ -902,8 +931,7 @@ add_new_function (struct cgraph_node *node, void *data ATTRIBUTE_UNUSED) ...@@ -902,8 +931,7 @@ add_new_function (struct cgraph_node *node, void *data ATTRIBUTE_UNUSED)
static declarations. We do not need to scan them more than once static declarations. We do not need to scan them more than once
since all we would be interested in are the addressof since all we would be interested in are the addressof
operations. */ operations. */
if (node->get_availability () > AVAIL_INTERPOSABLE if (opt_for_fn (node->decl, flag_ipa_pure_const))
&& opt_for_fn (node->decl, flag_ipa_pure_const))
set_function_state (node, analyze_function (node, true)); set_function_state (node, analyze_function (node, true));
} }
...@@ -973,8 +1001,7 @@ pure_const_generate_summary (void) ...@@ -973,8 +1001,7 @@ pure_const_generate_summary (void)
when function got cloned and the clone is AVAILABLE. */ when function got cloned and the clone is AVAILABLE. */
FOR_EACH_DEFINED_FUNCTION (node) FOR_EACH_DEFINED_FUNCTION (node)
if (node->get_availability () >= AVAIL_INTERPOSABLE if (opt_for_fn (node->decl, flag_ipa_pure_const))
&& opt_for_fn (node->decl, flag_ipa_pure_const))
set_function_state (node, analyze_function (node, true)); set_function_state (node, analyze_function (node, true));
} }
...@@ -1105,7 +1132,7 @@ pure_const_read_summary (void) ...@@ -1105,7 +1132,7 @@ pure_const_read_summary (void)
fprintf (dump_file, "\n pure const state: %s\n", fprintf (dump_file, "\n pure const state: %s\n",
pure_const_names[fs->pure_const_state]); pure_const_names[fs->pure_const_state]);
fprintf (dump_file, " previously known state: %s\n", fprintf (dump_file, " previously known state: %s\n",
pure_const_names[fs->looping_previously_known]); pure_const_names[fs->state_previously_known]);
if (fs->looping) if (fs->looping)
fprintf (dump_file," function is locally looping\n"); fprintf (dump_file," function is locally looping\n");
if (fs->looping_previously_known) if (fs->looping_previously_known)
...@@ -1134,7 +1161,8 @@ ignore_edge_for_nothrow (struct cgraph_edge *e) ...@@ -1134,7 +1161,8 @@ ignore_edge_for_nothrow (struct cgraph_edge *e)
return true; return true;
enum availability avail; 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)); return (avail <= AVAIL_INTERPOSABLE || TREE_NOTHROW (n->decl));
} }
...@@ -1170,7 +1198,7 @@ static bool ...@@ -1170,7 +1198,7 @@ static bool
ignore_edge_for_pure_const (struct cgraph_edge *e) ignore_edge_for_pure_const (struct cgraph_edge *e)
{ {
enum availability avail; 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); return (avail <= AVAIL_INTERPOSABLE);
} }
...@@ -1232,18 +1260,25 @@ propagate_pure_const (void) ...@@ -1232,18 +1260,25 @@ propagate_pure_const (void)
pure_const_names[w_l->pure_const_state], pure_const_names[w_l->pure_const_state],
w_l->looping); 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, 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) if (pure_const_state == IPA_NEITHER)
break; 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) if (w->get_availability () == AVAIL_INTERPOSABLE)
{ {
worse_state (&pure_const_state, &looping, worse_state (&pure_const_state, &looping,
w_l->state_previously_known, w_l->state_previously_known,
w_l->looping_previously_known); w_l->looping_previously_known,
NULL, NULL);
if (dump_file && (dump_flags & TDF_DETAILS)) if (dump_file && (dump_flags & TDF_DETAILS))
{ {
fprintf (dump_file, fprintf (dump_file,
...@@ -1268,7 +1303,8 @@ propagate_pure_const (void) ...@@ -1268,7 +1303,8 @@ propagate_pure_const (void)
{ {
enum availability avail; enum availability avail;
struct cgraph_node *y = e->callee-> 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; enum pure_const_state_e edge_state = IPA_CONST;
bool edge_looping = false; bool edge_looping = false;
...@@ -1318,7 +1354,7 @@ propagate_pure_const (void) ...@@ -1318,7 +1354,7 @@ propagate_pure_const (void)
w_l->state_previously_known, w_l->state_previously_known,
w_l->looping_previously_known); w_l->looping_previously_known);
worse_state (&pure_const_state, &looping, worse_state (&pure_const_state, &looping,
edge_state, edge_looping); edge_state, edge_looping, e->caller, e->callee);
if (pure_const_state == IPA_NEITHER) if (pure_const_state == IPA_NEITHER)
break; break;
} }
...@@ -1340,7 +1376,7 @@ propagate_pure_const (void) ...@@ -1340,7 +1376,7 @@ propagate_pure_const (void)
w_l->state_previously_known, w_l->state_previously_known,
w_l->looping_previously_known); w_l->looping_previously_known);
worse_state (&pure_const_state, &looping, worse_state (&pure_const_state, &looping,
edge_state, edge_looping); edge_state, edge_looping, NULL, NULL);
if (pure_const_state == IPA_NEITHER) if (pure_const_state == IPA_NEITHER)
break; break;
} }
...@@ -1378,7 +1414,7 @@ propagate_pure_const (void) ...@@ -1378,7 +1414,7 @@ propagate_pure_const (void)
w_l->state_previously_known, w_l->state_previously_known,
w_l->looping_previously_known); w_l->looping_previously_known);
worse_state (&pure_const_state, &looping, worse_state (&pure_const_state, &looping,
ref_state, ref_looping); ref_state, ref_looping, NULL, NULL);
if (pure_const_state == IPA_NEITHER) if (pure_const_state == IPA_NEITHER)
break; break;
} }
...@@ -1407,7 +1443,8 @@ propagate_pure_const (void) ...@@ -1407,7 +1443,8 @@ propagate_pure_const (void)
{ {
enum availability avail; enum availability avail;
struct cgraph_node *y = e->callee-> struct cgraph_node *y = e->callee->
function_or_virtual_thunk_symbol (&avail); function_or_virtual_thunk_symbol (&avail,
e->caller);
if (avail > AVAIL_INTERPOSABLE) if (avail > AVAIL_INTERPOSABLE)
can_free = get_function_state (y)->can_free; can_free = get_function_state (y)->can_free;
...@@ -1552,7 +1589,8 @@ propagate_nothrow (void) ...@@ -1552,7 +1589,8 @@ propagate_nothrow (void)
continue; continue;
struct cgraph_node *y = e->callee-> 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 /* We can use info about the callee only if we know it can
not be interposed. */ not be interposed. */
...@@ -1664,8 +1702,9 @@ make_pass_ipa_pure_const (gcc::context *ctxt) ...@@ -1664,8 +1702,9 @@ make_pass_ipa_pure_const (gcc::context *ctxt)
static bool static bool
skip_function_for_local_pure_const (struct cgraph_node *node) skip_function_for_local_pure_const (struct cgraph_node *node)
{ {
/* Because we do not schedule pass_fixup_cfg over whole program after early optimizations /* Because we do not schedule pass_fixup_cfg over whole program after early
we must not promote functions that are called by already processed functions. */ optimizations we must not promote functions that are called by already
processed functions. */
if (function_called_by_processed_nodes_p ()) if (function_called_by_processed_nodes_p ())
{ {
...@@ -1676,7 +1715,8 @@ skip_function_for_local_pure_const (struct cgraph_node *node) ...@@ -1676,7 +1715,8 @@ skip_function_for_local_pure_const (struct cgraph_node *node)
if (node->get_availability () <= AVAIL_INTERPOSABLE) if (node->get_availability () <= AVAIL_INTERPOSABLE)
{ {
if (dump_file) 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 true;
} }
return false; return false;
......
2016-04-15 Jan Hubicka <jh@suse.cz>
PR ipa/70018
* 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.
2016-04-15 Marek Polacek <polacek@redhat.com> 2016-04-15 Marek Polacek <polacek@redhat.com>
PR c/70671 PR c/70671
......
/* { 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" } } */
/* { 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" } } */
/* { 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" } } */
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment