make use of CALL_FROM_NEW_OR_DELETE_P
This fixes points-to analysis and DCE to only consider new/delete operator calls from new or delete expressions and not direct calls. 2020-10-01 Richard Biener <rguenther@suse.de> * gimple.h (GF_CALL_FROM_NEW_OR_DELETE): New call flag. (gimple_call_set_from_new_or_delete): New. (gimple_call_from_new_or_delete): Likewise. * gimple.c (gimple_build_call_from_tree): Set GF_CALL_FROM_NEW_OR_DELETE appropriately. * ipa-icf-gimple.c (func_checker::compare_gimple_call): Compare gimple_call_from_new_or_delete. * tree-ssa-dce.c (mark_all_reaching_defs_necessary_1): Make sure to only consider new/delete calls from new or delete expressions. (propagate_necessity): Likewise. (eliminate_unnecessary_stmts): Likewise. * tree-ssa-structalias.c (find_func_aliases_for_call): Likewise. * g++.dg/tree-ssa/pta-delete-1.C: New testcase.
This commit is contained in:
parent
b6158faacb
commit
0b945f959f
6 changed files with 78 additions and 14 deletions
|
@ -387,6 +387,10 @@ gimple_build_call_from_tree (tree t, tree fnptrtype)
|
|||
&& fndecl_built_in_p (fndecl, BUILT_IN_NORMAL)
|
||||
&& ALLOCA_FUNCTION_CODE_P (DECL_FUNCTION_CODE (fndecl)))
|
||||
gimple_call_set_alloca_for_var (call, CALL_ALLOCA_FOR_VAR_P (t));
|
||||
else if (fndecl
|
||||
&& (DECL_IS_OPERATOR_NEW_P (fndecl)
|
||||
|| DECL_IS_OPERATOR_DELETE_P (fndecl)))
|
||||
gimple_call_set_from_new_or_delete (call, CALL_FROM_NEW_OR_DELETE_P (t));
|
||||
else
|
||||
gimple_call_set_from_thunk (call, CALL_FROM_THUNK_P (t));
|
||||
gimple_call_set_va_arg_pack (call, CALL_EXPR_VA_ARG_PACK (t));
|
||||
|
|
24
gcc/gimple.h
24
gcc/gimple.h
|
@ -149,6 +149,7 @@ enum gf_mask {
|
|||
GF_CALL_MUST_TAIL_CALL = 1 << 9,
|
||||
GF_CALL_BY_DESCRIPTOR = 1 << 10,
|
||||
GF_CALL_NOCF_CHECK = 1 << 11,
|
||||
GF_CALL_FROM_NEW_OR_DELETE = 1 << 12,
|
||||
GF_OMP_PARALLEL_COMBINED = 1 << 0,
|
||||
GF_OMP_TASK_TASKLOOP = 1 << 0,
|
||||
GF_OMP_TASK_TASKWAIT = 1 << 1,
|
||||
|
@ -3387,6 +3388,29 @@ gimple_call_from_thunk_p (gcall *s)
|
|||
}
|
||||
|
||||
|
||||
/* If FROM_NEW_OR_DELETE_P is true, mark GIMPLE_CALL S as being a call
|
||||
to operator new or delete created from a new or delete expression. */
|
||||
|
||||
static inline void
|
||||
gimple_call_set_from_new_or_delete (gcall *s, bool from_new_or_delete_p)
|
||||
{
|
||||
if (from_new_or_delete_p)
|
||||
s->subcode |= GF_CALL_FROM_NEW_OR_DELETE;
|
||||
else
|
||||
s->subcode &= ~GF_CALL_FROM_NEW_OR_DELETE;
|
||||
}
|
||||
|
||||
|
||||
/* Return true if GIMPLE_CALL S is a call to operator new or delete from
|
||||
from a new or delete expression. */
|
||||
|
||||
static inline bool
|
||||
gimple_call_from_new_or_delete (gcall *s)
|
||||
{
|
||||
return (s->subcode & GF_CALL_FROM_NEW_OR_DELETE) != 0;
|
||||
}
|
||||
|
||||
|
||||
/* If PASS_ARG_PACK_P is true, GIMPLE_CALL S is a stdarg call that needs the
|
||||
argument pack in its argument list. */
|
||||
|
||||
|
|
|
@ -556,6 +556,7 @@ func_checker::compare_gimple_call (gcall *s1, gcall *s2)
|
|||
|| gimple_call_tail_p (s1) != gimple_call_tail_p (s2)
|
||||
|| gimple_call_return_slot_opt_p (s1) != gimple_call_return_slot_opt_p (s2)
|
||||
|| gimple_call_from_thunk_p (s1) != gimple_call_from_thunk_p (s2)
|
||||
|| gimple_call_from_new_or_delete (s1) != gimple_call_from_new_or_delete (s2)
|
||||
|| gimple_call_va_arg_pack_p (s1) != gimple_call_va_arg_pack_p (s2)
|
||||
|| gimple_call_alloca_for_var_p (s1) != gimple_call_alloca_for_var_p (s2))
|
||||
return false;
|
||||
|
|
24
gcc/testsuite/g++.dg/tree-ssa/pta-delete-1.C
Normal file
24
gcc/testsuite/g++.dg/tree-ssa/pta-delete-1.C
Normal file
|
@ -0,0 +1,24 @@
|
|||
// { dg-do run }
|
||||
// { dg-options "-O2" }
|
||||
|
||||
struct X {
|
||||
static struct X saved;
|
||||
int *p;
|
||||
X() { __builtin_memcpy (this, &saved, sizeof (X)); }
|
||||
};
|
||||
X X::saved;
|
||||
void __attribute__((noinline)) operator delete (void *p)
|
||||
{
|
||||
__builtin_memcpy (&X::saved, p, sizeof (X));
|
||||
}
|
||||
int main()
|
||||
{
|
||||
int y = 1;
|
||||
X *p = new X;
|
||||
p->p = &y;
|
||||
::operator delete (p);
|
||||
X *q = new X;
|
||||
*(q->p) = 2;
|
||||
if (y != 2)
|
||||
__builtin_abort ();
|
||||
}
|
|
@ -593,9 +593,9 @@ mark_all_reaching_defs_necessary_1 (ao_ref *ref ATTRIBUTE_UNUSED,
|
|||
|
||||
/* We want to skip statments that do not constitute stores but have
|
||||
a virtual definition. */
|
||||
if (is_gimple_call (def_stmt))
|
||||
if (gcall *call = dyn_cast <gcall *> (def_stmt))
|
||||
{
|
||||
tree callee = gimple_call_fndecl (def_stmt);
|
||||
tree callee = gimple_call_fndecl (call);
|
||||
if (callee != NULL_TREE
|
||||
&& fndecl_built_in_p (callee, BUILT_IN_NORMAL))
|
||||
switch (DECL_FUNCTION_CODE (callee))
|
||||
|
@ -612,7 +612,8 @@ mark_all_reaching_defs_necessary_1 (ao_ref *ref ATTRIBUTE_UNUSED,
|
|||
|
||||
if (callee != NULL_TREE
|
||||
&& (DECL_IS_REPLACEABLE_OPERATOR_NEW_P (callee)
|
||||
|| DECL_IS_REPLACEABLE_OPERATOR_DELETE_P (callee)))
|
||||
|| DECL_IS_REPLACEABLE_OPERATOR_DELETE_P (callee))
|
||||
&& gimple_call_from_new_or_delete (call))
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -875,23 +876,25 @@ propagate_necessity (bool aggressive)
|
|||
processing the argument. */
|
||||
bool is_delete_operator
|
||||
= (is_gimple_call (stmt)
|
||||
&& gimple_call_from_new_or_delete (as_a <gcall *> (stmt))
|
||||
&& gimple_call_replaceable_operator_delete_p (as_a <gcall *> (stmt)));
|
||||
if (is_delete_operator
|
||||
|| gimple_call_builtin_p (stmt, BUILT_IN_FREE))
|
||||
{
|
||||
tree ptr = gimple_call_arg (stmt, 0);
|
||||
gimple *def_stmt;
|
||||
gcall *def_stmt;
|
||||
tree def_callee;
|
||||
/* If the pointer we free is defined by an allocation
|
||||
function do not add the call to the worklist. */
|
||||
if (TREE_CODE (ptr) == SSA_NAME
|
||||
&& is_gimple_call (def_stmt = SSA_NAME_DEF_STMT (ptr))
|
||||
&& (def_stmt = dyn_cast <gcall *> (SSA_NAME_DEF_STMT (ptr)))
|
||||
&& (def_callee = gimple_call_fndecl (def_stmt))
|
||||
&& ((DECL_BUILT_IN_CLASS (def_callee) == BUILT_IN_NORMAL
|
||||
&& (DECL_FUNCTION_CODE (def_callee) == BUILT_IN_ALIGNED_ALLOC
|
||||
|| DECL_FUNCTION_CODE (def_callee) == BUILT_IN_MALLOC
|
||||
|| DECL_FUNCTION_CODE (def_callee) == BUILT_IN_CALLOC))
|
||||
|| DECL_IS_REPLACEABLE_OPERATOR_NEW_P (def_callee)))
|
||||
|| (DECL_IS_REPLACEABLE_OPERATOR_NEW_P (def_callee)
|
||||
&& gimple_call_from_new_or_delete (def_stmt))))
|
||||
{
|
||||
if (is_delete_operator)
|
||||
{
|
||||
|
@ -947,9 +950,9 @@ propagate_necessity (bool aggressive)
|
|||
in 1). By keeping a global visited bitmap for references
|
||||
we walk for 2) we avoid quadratic behavior for those. */
|
||||
|
||||
if (is_gimple_call (stmt))
|
||||
if (gcall *call = dyn_cast <gcall *> (stmt))
|
||||
{
|
||||
tree callee = gimple_call_fndecl (stmt);
|
||||
tree callee = gimple_call_fndecl (call);
|
||||
unsigned i;
|
||||
|
||||
/* Calls to functions that are merely acting as barriers
|
||||
|
@ -972,22 +975,23 @@ propagate_necessity (bool aggressive)
|
|||
|
||||
if (callee != NULL_TREE
|
||||
&& (DECL_IS_REPLACEABLE_OPERATOR_NEW_P (callee)
|
||||
|| DECL_IS_REPLACEABLE_OPERATOR_DELETE_P (callee)))
|
||||
|| DECL_IS_REPLACEABLE_OPERATOR_DELETE_P (callee))
|
||||
&& gimple_call_from_new_or_delete (call))
|
||||
continue;
|
||||
|
||||
/* Calls implicitly load from memory, their arguments
|
||||
in addition may explicitly perform memory loads. */
|
||||
mark_all_reaching_defs_necessary (stmt);
|
||||
for (i = 0; i < gimple_call_num_args (stmt); ++i)
|
||||
mark_all_reaching_defs_necessary (call);
|
||||
for (i = 0; i < gimple_call_num_args (call); ++i)
|
||||
{
|
||||
tree arg = gimple_call_arg (stmt, i);
|
||||
tree arg = gimple_call_arg (call, i);
|
||||
if (TREE_CODE (arg) == SSA_NAME
|
||||
|| is_gimple_min_invariant (arg))
|
||||
continue;
|
||||
if (TREE_CODE (arg) == WITH_SIZE_EXPR)
|
||||
arg = TREE_OPERAND (arg, 0);
|
||||
if (!ref_may_be_aliased (arg))
|
||||
mark_aliased_reaching_defs_necessary (stmt, arg);
|
||||
mark_aliased_reaching_defs_necessary (call, arg);
|
||||
}
|
||||
}
|
||||
else if (gimple_assign_single_p (stmt))
|
||||
|
@ -1397,6 +1401,7 @@ eliminate_unnecessary_stmts (void)
|
|||
if (gimple_plf (stmt, STMT_NECESSARY)
|
||||
&& (gimple_call_builtin_p (stmt, BUILT_IN_FREE)
|
||||
|| (is_gimple_call (stmt)
|
||||
&& gimple_call_from_new_or_delete (as_a <gcall *> (stmt))
|
||||
&& gimple_call_replaceable_operator_delete_p (as_a <gcall *> (stmt)))))
|
||||
{
|
||||
tree ptr = gimple_call_arg (stmt, 0);
|
||||
|
|
|
@ -4857,7 +4857,13 @@ find_func_aliases_for_call (struct function *fn, gcall *t)
|
|||
point for reachable memory of their arguments. */
|
||||
else if (flags & (ECF_PURE|ECF_LOOPING_CONST_OR_PURE))
|
||||
handle_pure_call (t, &rhsc);
|
||||
else if (fndecl && DECL_IS_REPLACEABLE_OPERATOR_DELETE_P (fndecl))
|
||||
/* If the call is to a replaceable operator delete and results
|
||||
from a delete expression as opposed to a direct call to
|
||||
such operator, then the effects for PTA (in particular
|
||||
the escaping of the pointer) can be ignored. */
|
||||
else if (fndecl
|
||||
&& DECL_IS_REPLACEABLE_OPERATOR_DELETE_P (fndecl)
|
||||
&& gimple_call_from_new_or_delete (t))
|
||||
;
|
||||
else
|
||||
handle_rhs_call (t, &rhsc);
|
||||
|
|
Loading…
Add table
Reference in a new issue