On Wed, 30 Sep 2020, Jason Merrill wrote:
> On 9/28/20 3:09 PM, Jason Merrill wrote:
> > On 9/28/20 3:56 AM, Richard Biener wrote:
> >> On Fri, 25 Sep 2020, Jason Merrill wrote:
> >>
> >>> On 9/25/20 2:30 AM, Richard Biener wrote:
> >>>> On Thu, 24 Sep 2020, Jason Merrill wrote:
> >>>>
> >>>>> On 9/24/20 3:43 AM, Richard Biener wrote:
> >>>>>> On Wed, 23 Sep 2020, Jason Merrill wrote:
> >>>>>>
> >>>>>>> On 9/23/20 2:42 PM, Richard Biener wrote:
> >>>>>>>> On September 23, 2020 7:53:18 PM GMT+02:00, Jason Merrill
> >>>>>>>> <ja...@redhat.com>
> >>>>>>>> wrote:
> >>>>>>>>> On 9/23/20 4:14 AM, Richard Biener wrote:
> >>>>>>>>>> C++ operator delete, when DECL_IS_REPLACEABLE_OPERATOR_DELETE_P,
> >>>>>>>>>> does not cause the deleted object to be escaped.? It also has no
> >>>>>>>>>> other interesting side-effects for PTA so skip it like we do
> >>>>>>>>>> for BUILT_IN_FREE.
> >>>>>>>>>
> >>>>>>>>> Hmm, this is true of the default implementation, but since the
> >>>>>>>>> function
> >>>>>>>>>
> >>>>>>>>> is replaceable, we don't know what a user definition might do with
> >>>>>>>>> the
> >>>>>>>>> pointer.
> >>>>>>>>
> >>>>>>>> But can the object still be 'used' after delete? Can delete fail /
> >>>>>>>> throw?
> >>>>>>>>
> >>>>>>>> What guarantee does the predicate give us?
> >>>>>>>
> >>>>>>> The deallocation function is called as part of a delete expression in
> >>>>>>> order
> >>>>>>> to
> >>>>>>> release the storage for an object, ending its lifetime (if it was not
> >>>>>>> ended
> >>>>>>> by
> >>>>>>> a destructor), so no, the object can't be used afterward.
> >>>>>>
> >>>>>> OK, but the delete operator can access the object contents if there
> >>>>>> wasn't a destructor ...
> >>>>>
> >>>>>>> A deallocation function that throws has undefined behavior.
> >>>>>>
> >>>>>> OK, so it seems the 'replaceable' operators are the global ones
> >>>>>> (for user-defined/class-specific placement variants I see arbitrary
> >>>>>> extra arguments that we'd possibly need to handle).
> >>>>>>
> >>>>>> I'm happy to revert but I'd like to have a testcase that FAILs
> >>>>>> with the patch ;)
> >>>>>>
> >>>>>> Now, the following aborts:
> >>>>>>
> >>>>>> struct X {
> >>>>>> ???? static struct X saved;
> >>>>>> ???? int *p;
> >>>>>> ???? X() { __builtin_memcpy (this, &saved, sizeof (X)); }
> >>>>>> };
> >>>>>> void operator delete (void *p)
> >>>>>> {
> >>>>>> ???? __builtin_memcpy (&X::saved, p, sizeof (X));
> >>>>>> }
> >>>>>> int main()
> >>>>>> {
> >>>>>> ???? int y = 1;
> >>>>>> ???? X *p = new X;
> >>>>>> ???? p->p = &y;
> >>>>>> ???? delete p;
> >>>>>> ???? X *q = new X;
> >>>>>> ???? *(q->p) = 2;
> >>>>>> ???? if (y != 2)
> >>>>>> ?????? __builtin_abort ();
> >>>>>> }
> >>>>>>
> >>>>>> and I could fix this by not making *p but what *p points to escape.
> >>>>>> The testcase is of course maximally awkward, but hey ... ;)
> >>>>>>
> >>>>>> Now this would all be moot if operator delete may not access
> >>>>>> the object (or if the object contents are undefined at that point).
> >>>>>>
> >>>>>> Oh, and the testcase segfaults when compiled with GCC 10 because
> >>>>>> there we elide the new X / delete p pair ... which is invalid then?
> >>>>>> Hmm, we emit
> >>>>>>
> >>>>>> ???? MEM[(struct X *)_8] ={v} {CLOBBER};
> >>>>>> ???? operator delete (_8, 8);
> >>>>>>
> >>>>>> so the object contents are undefined _before_ calling delete
> >>>>>> even when I do not have a DTOR?? That is, the above,
> >>>>>> w/o -fno-lifetime-dse, makes the PTA patch OK for the testcase.
> >>>>>
> >>>>> Yes, all classes have a destructor, even if it's trivial, so the
> >>>>> object's
> >>>>> lifetime definitely ends before the call to operator delete. This is
> >>>>> less
> >>>>> clear for scalar objects, but treating them similarly would be
> >>>>> consistent
> >>>>> with
> >>>>> other recent changes, so I think it's fine for us to assume that scalar
> >>>>> objects are also invalidated before the call to operator delete. But of
> >>>>> course this doesn't apply to explicit calls to operator delete outside
> >>>>> of a
> >>>>> delete expression.
> >>>>
> >>>> OK, so change the testcase main slightly to
> >>>>
> >>>> 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 ();
> >>>> }
> >>>>
> >>>> in this case the lifetime of *p does not end before calling
> >>>> ::operator delete() and delete can stash the object contents
> >>>> somewhere before ending its lifetime.? For the very same reason
> >>>> we may not elide a new/delete pair like in
> >>>>
> >>>> int main()
> >>>> {
> >>>> ??? int *p = new int;
> >>>> ??? *p = 1;
> >>>> ??? ::operator delete (p);
> >>>> }
> >>>
> >>> Correct; the permission to elide new/delete pairs are for the expressions,
> >>> not
> >>> the functions.
> >>>
> >>>> which we before the change did not do only because calling
> >>>> operator delete made p escape.? Unfortunately points-to analysis
> >>>> cannot really reconstruct whether delete was called as part of
> >>>> a delete expression or directly (and thus whether object lifetime
> >>>> ended already), neither can DCE.? So I guess we need to mark
> >>>> the operator delete call in some way to make those transforms
> >>>> safe.? At least currently any operator delete call makes the
> >>>> alias guarantee of a operator new call moot by forcing the object
> >>>> to be aliased with all global and escaped memory ...
> >>>>
> >>>> Looks like there are some unallocated flags for CALL_EXPR we could
> >>>> pick but I wonder if we can recycle protected_flag which is
> >>>>
> >>>> ???????? CALL_FROM_THUNK_P and
> >>>> ???????? CALL_ALLOCA_FOR_VAR_P in
> >>>> ???????????? CALL_EXPR
> >>>>
> >>>> for calls to DECL_IS_OPERATOR_{NEW,DELETE}_P, thus whether
> >>>> we have CALL_FROM_THUNK_P for those operators.? Guess picking
> >>>> a new flag is safer.
> >>>
> >>> We won't ever call those operators from a thunk, so it should be OK to
> >>> reuse
> >>> it.
> >>>
> >>>> But, does it seem correct that we need to distinguish
> >>>> delete expressions from plain calls to operator delete?
> >>>
> >>> A reason for that distinction came up in the context of omitting
> >>> new/delete
> >>> pairs: we want to consider the operator first called by the new or delete
> >>> expression, not a call from that first operator to another operator
> >>> new/delete
> >>> and exposed by inlining.
> >>>
> >>> https://gcc.gnu.org/pipermail/gcc-patches/2020-April/543404.html
> >>>
> >>>> In this context I also wonder about non-replaceable operator delete,
> >>>> specifically operator delete in classes - are there any semantic
> >>>> differences between those or why did we choose to only mark
> >>>> the replaceable ones?
> >>>
> >>> The standard says that for omitting a 'new' allocation, the operator new
> >>> has
> >>> to be a replaceable one, but does not say the same about 'delete'; it just
> >>> says that if the allocation was omitted, the delete-expression does not
> >>> call a
> >>> deallocation function.? It may not be necessary to make this distinction
> >>> for
> >>> delete.? And this distinction could be local to the front end.
> >>>
> >>> In the front end, we currently have cxx_replaceable_global_alloc_fn that
> >>> already ignores the replaceability of operator delete.? And we have
> >>> CALL_FROM_NEW_OR_DELETE_P, that would just need to move into the middle
> >>> end.
> >>> And perhaps get renamed to CALL_OMITTABLE_NEW_OR_DELETE_P, and not get set
> >>> for
> >>> calls to non-replaceable operator new.
> >>
> >> CALL_FROM_NEW_OR_DELETE_P indeed looks like the best fit - it's
> >> only evaluated when cxx_replaceable_global_alloc_fn matches in the C++
> >> FE so could be made to cover only replaceable variants.
> >>
> >> CALL_REPLACEABLE_NEW_OR_DELETE_P () maybe, since we already use
> >> REPLACEABLE for the fndecl flags?? OMITTABLE is too specific
> >> for the PTA case where it really matters whether the object
> >> lifetime ends before the delete call, not whether it can be
> >> omitted (hmm, guess that's not 100% overlap then either...).
> >
> > That seems like good overlap to me, if we agree that object lifetime ends
> > before any delete call from a delete-expression, whether or not the operator
> > delete is replaceable.
> >
> >> Mind doing the C++ side of things recycling protected_flag as suggested?
> >
> > OK.
Find attached a patch series, your patch plus the GIMPLE side of the fix.
This shows that the C++ FE side is possibly incomplete with for
example g++.dg/pr94314-2.C now FAILing. There we have
A *a = new A (argc);
delete a;
being expanded to
<<cleanup_point <<< Unknown tree: expr_stmt
(void) (a = TARGET_EXPR <D.2357, operator new (1)>;, try
{
A::A ((struct A *) D.2357, argc);
}
catch
{
operator delete (D.2357);
}, (struct A *) D.2357;) >>>>>;
<<cleanup_point <<< Unknown tree: expr_stmt
if (SAVE_EXPR <a> != 0B)
{
try
{
*SAVE_EXPR <a> = {CLOBBER};
}
finally
{
operator delete ((void *) SAVE_EXPR <a>);
}
}
else
{
<<< Unknown tree: void_cst >>>
} >>>>>;
return <retval> = 0;
where the operator delete call in the catch {} expression is
not marked as CALL_FROM_NEW_OR_DELETE_P, possibly because this
call is compiler generated.
So it's probably technically true that CALL_FROM_NEW_OR_DELETE_P is
false but we still expect the same guarantees to hold here, in
particular we expect to elide the new/delete pair?
Thanks,
Richard.
From 602853335c5f01adb8594b6b1dc49c365c5cd731 Mon Sep 17 00:00:00 2001
From: Jason Merrill <ja...@redhat.com>
Date: Thu, 1 Oct 2020 10:08:58 +0200
Subject: [PATCH 1/2] c++: Move CALL_FROM_NEW_OR_DELETE_P to tree.h
To: gcc-patches@gcc.gnu.org
As discussed with richi, we should be able to use TREE_PROTECTED for this
flag, since CALL_FROM_THUNK_P will never be set on a call to an operator new
or delete.
2020-10-01 Jason Merril <ja...@redhat.com>
gcc/cp/ChangeLog:
* lambda.c (call_from_lambda_thunk_p): New.
* cp-gimplify.c (cp_genericize_r): Use it.
* pt.c (tsubst_copy_and_build): Use it.
* typeck.c (check_return_expr): Use it.
* cp-tree.h: Declare it.
(CALL_FROM_NEW_OR_DELETE_P): Move to gcc/tree.h.
gcc/ChangeLog:
* tree.h (CALL_FROM_NEW_OR_DELETE_P): Move from cp-tree.h.
---
gcc/cp/cp-gimplify.c | 2 +-
gcc/cp/cp-tree.h | 7 +------
gcc/cp/lambda.c | 7 +++++++
gcc/cp/pt.c | 2 +-
gcc/cp/typeck.c | 2 +-
gcc/tree-core.h | 3 ++-
gcc/tree.h | 9 ++++++++-
7 files changed, 21 insertions(+), 11 deletions(-)
diff --git a/gcc/cp/cp-gimplify.c b/gcc/cp/cp-gimplify.c
index bc8a03c7b41..07549828dc9 100644
--- a/gcc/cp/cp-gimplify.c
+++ b/gcc/cp/cp-gimplify.c
@@ -962,7 +962,7 @@ cp_genericize_r (tree *stmt_p, int *walk_subtrees, void
*data)
omp_cxx_notice_variable (wtd->omp_ctx, stmt);
/* Don't dereference parms in a thunk, pass the references through. */
- if ((TREE_CODE (stmt) == CALL_EXPR && CALL_FROM_THUNK_P (stmt))
+ if ((TREE_CODE (stmt) == CALL_EXPR && call_from_lambda_thunk_p (stmt))
|| (TREE_CODE (stmt) == AGGR_INIT_EXPR && AGGR_INIT_FROM_THUNK_P (stmt)))
{
*walk_subtrees = 0;
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 48a4074b370..cf6e02c3bc5 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -464,7 +464,6 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX];
SWITCH_STMT_NO_BREAK_P (in SWITCH_STMT)
LAMBDA_EXPR_CAPTURE_OPTIMIZED (in LAMBDA_EXPR)
IMPLICIT_CONV_EXPR_BRACED_INIT (in IMPLICIT_CONV_EXPR)
- CALL_FROM_NEW_OR_DELETE_P (in CALL_EXPR)
3: IMPLICIT_RVALUE_P (in NON_LVALUE_EXPR or STATIC_CAST_EXPR)
ICS_BAD_FLAG (in _CONV)
FN_TRY_BLOCK_P (in TRY_BLOCK)
@@ -3839,11 +3838,6 @@ struct GTY(()) lang_decl {
should be performed at instantiation time. */
#define KOENIG_LOOKUP_P(NODE) TREE_LANG_FLAG_0 (CALL_EXPR_CHECK (NODE))
-/* In a CALL_EXPR, true for allocator calls from new or delete
- expressions. */
-#define CALL_FROM_NEW_OR_DELETE_P(NODE) \
- TREE_LANG_FLAG_2 (CALL_EXPR_CHECK (NODE))
-
/* True if the arguments to NODE should be evaluated in left-to-right
order regardless of PUSH_ARGS_REVERSED. */
#define CALL_EXPR_ORDERED_ARGS(NODE) \
@@ -7279,6 +7273,7 @@ extern bool lambda_fn_in_template_p (tree);
extern void maybe_add_lambda_conv_op (tree);
extern bool is_lambda_ignored_entity (tree);
extern bool lambda_static_thunk_p (tree);
+extern bool call_from_lambda_thunk_p (tree);
extern tree finish_builtin_launder (location_t, tree,
tsubst_flags_t);
extern tree cp_build_vec_convert (tree, location_t, tree,
diff --git a/gcc/cp/lambda.c b/gcc/cp/lambda.c
index 07a5401c97b..1a1647f465e 100644
--- a/gcc/cp/lambda.c
+++ b/gcc/cp/lambda.c
@@ -1325,6 +1325,13 @@ lambda_static_thunk_p (tree fn)
&& LAMBDA_TYPE_P (CP_DECL_CONTEXT (fn)));
}
+bool
+call_from_lambda_thunk_p (tree call)
+{
+ return (CALL_FROM_THUNK_P (call)
+ && lambda_static_thunk_p (current_function_decl));
+}
+
/* Returns true iff VAL is a lambda-related declaration which should
be ignored by unqualified lookup. */
diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
index 869477f2c2e..f0b6414b0e2 100644
--- a/gcc/cp/pt.c
+++ b/gcc/cp/pt.c
@@ -19955,7 +19955,7 @@ tsubst_copy_and_build (tree t,
/* Stripped-down processing for a call in a thunk. Specifically, in
the thunk template for a generic lambda. */
- if (CALL_FROM_THUNK_P (t))
+ if (call_from_lambda_thunk_p (t))
{
/* Now that we've expanded any packs, the number of call args
might be different. */
diff --git a/gcc/cp/typeck.c b/gcc/cp/typeck.c
index 9166156a5d5..95b36a92491 100644
--- a/gcc/cp/typeck.c
+++ b/gcc/cp/typeck.c
@@ -10171,7 +10171,7 @@ check_return_expr (tree retval, bool *no_warning)
/* The call in a (lambda) thunk needs no conversions. */
if (TREE_CODE (retval) == CALL_EXPR
- && CALL_FROM_THUNK_P (retval))
+ && call_from_lambda_thunk_p (retval))
converted = true;
/* First convert the value to the function's return type, then
diff --git a/gcc/tree-core.h b/gcc/tree-core.h
index 0e158784d0e..752bec31c3f 100644
--- a/gcc/tree-core.h
+++ b/gcc/tree-core.h
@@ -1220,7 +1220,8 @@ struct GTY(()) tree_base {
all decls
CALL_FROM_THUNK_P and
- CALL_ALLOCA_FOR_VAR_P in
+ CALL_ALLOCA_FOR_VAR_P and
+ CALL_FROM_NEW_OR_DELETE_P in
CALL_EXPR
OMP_CLAUSE_LINEAR_VARIABLE_STRIDE in
diff --git a/gcc/tree.h b/gcc/tree.h
index 5bb6e7bc000..f27a7399a37 100644
--- a/gcc/tree.h
+++ b/gcc/tree.h
@@ -921,7 +921,8 @@ extern void omp_clause_range_check_failed (const_tree,
const char *, int,
(TREE_CHECK (NODE, PARM_DECL)->decl_common.decl_nonshareable_flag)
/* In a CALL_EXPR, means that the call is the jump from a thunk to the
- thunked-to function. */
+ thunked-to function. Be careful to avoid using this macro when one of the
+ next two applies instead. */
#define CALL_FROM_THUNK_P(NODE) (CALL_EXPR_CHECK (NODE)->base.protected_flag)
/* In a CALL_EXPR, if the function being called is BUILT_IN_ALLOCA, means that
@@ -931,6 +932,12 @@ extern void omp_clause_range_check_failed (const_tree,
const char *, int,
#define CALL_ALLOCA_FOR_VAR_P(NODE) \
(CALL_EXPR_CHECK (NODE)->base.protected_flag)
+/* In a CALL_EXPR, if the function being called is DECL_IS_OPERATOR_NEW_P or
+ DECL_IS_OPERATOR_DELETE_P, true for allocator calls from C++ new or delete
+ expressions. */
+#define CALL_FROM_NEW_OR_DELETE_P(NODE) \
+ (CALL_EXPR_CHECK (NODE)->base.protected_flag)
+
/* Used in classes in C++. */
#define TREE_PRIVATE(NODE) ((NODE)->base.private_flag)
/* Used in classes in C++. */
--
2.26.2
From 14e187bd7a161103fc4c87869a3d50116e51fd32 Mon Sep 17 00:00:00 2001
From: Richard Biener <rguent...@suse.de>
Date: Thu, 1 Oct 2020 10:44:27 +0200
Subject: [PATCH 2/2] make use of CALL_FROM_NEW_OR_DELETE_P
To: gcc-patches@gcc.gnu.org
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 <rguent...@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.
---
gcc/gimple.c | 4 +++
gcc/gimple.h | 24 +++++++++++++++
gcc/ipa-icf-gimple.c | 1 +
gcc/testsuite/g++.dg/tree-ssa/pta-delete-1.C | 24 +++++++++++++++
gcc/tree-ssa-dce.c | 31 ++++++++++++--------
gcc/tree-ssa-structalias.c | 8 ++++-
6 files changed, 78 insertions(+), 14 deletions(-)
create mode 100644 gcc/testsuite/g++.dg/tree-ssa/pta-delete-1.C
diff --git a/gcc/gimple.c b/gcc/gimple.c
index fd4e0fac0d4..f07ddab7953 100644
--- a/gcc/gimple.c
+++ b/gcc/gimple.c
@@ -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));
diff --git a/gcc/gimple.h b/gcc/gimple.h
index 6cc7e66059d..108ae846849 100644
--- a/gcc/gimple.h
+++ b/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. */
diff --git a/gcc/ipa-icf-gimple.c b/gcc/ipa-icf-gimple.c
index 1cd5872c03d..d5423a7e9b2 100644
--- a/gcc/ipa-icf-gimple.c
+++ b/gcc/ipa-icf-gimple.c
@@ -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;
diff --git a/gcc/testsuite/g++.dg/tree-ssa/pta-delete-1.C
b/gcc/testsuite/g++.dg/tree-ssa/pta-delete-1.C
new file mode 100644
index 00000000000..5e1e322344a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/tree-ssa/pta-delete-1.C
@@ -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 ();
+}
diff --git a/gcc/tree-ssa-dce.c b/gcc/tree-ssa-dce.c
index fae5ae72340..c9e0c8fd116 100644
--- a/gcc/tree-ssa-dce.c
+++ b/gcc/tree-ssa-dce.c
@@ -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);
diff --git a/gcc/tree-ssa-structalias.c b/gcc/tree-ssa-structalias.c
index f676bf91e95..69de932b14c 100644
--- a/gcc/tree-ssa-structalias.c
+++ b/gcc/tree-ssa-structalias.c
@@ -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);
--
2.26.2