diff mbox

Fix missed DSE opportunity with operator delete.

Message ID 5716998B.8010907@gmail.com
State New
Headers show

Commit Message

Mikhail Maltsev April 19, 2016, 8:48 p.m. UTC
On 04/18/2016 12:14 PM, Richard Biener wrote:
> 
> Enlarging tree_function_decl is bad.
Probably using 3 bits for malloc_flag, operator_new_flag and free_flag is
redundant. I packed the state into 2 bits.
> 
> Passes should get at the info via flags_from_decl_or_type () and a new
> ECF_FREE.
Fixed.

Comments

Richard Biener April 20, 2016, 2:12 p.m. UTC | #1
On Tue, Apr 19, 2016 at 10:48 PM, Mikhail Maltsev <maltsevm@gmail.com> wrote:
> On 04/18/2016 12:14 PM, Richard Biener wrote:
>>
>> Enlarging tree_function_decl is bad.
> Probably using 3 bits for malloc_flag, operator_new_flag and free_flag is
> redundant. I packed the state into 2 bits.
>>
>> Passes should get at the info via flags_from_decl_or_type () and a new
>> ECF_FREE.
> Fixed.

Thanks - much better.

@@ -2117,6 +2127,13 @@ call_may_clobber_ref_p_1 (gcall *call, ao_ref *ref)
          /* Fallthru to general call handling.  */;
       }

+  if (callee != NULL_TREE
+      && (flags_from_decl_or_type (callee) & ECF_FREE) != 0)
+    {

as you have a stmt here please use gimple_call_flags (call) & ECF_FREE.

@@ -2402,6 +2409,16 @@ stmt_kills_ref_p (gimple *stmt, ao_ref *ref)

          default:;
          }
+
+      if (callee != NULL_TREE
+         && (flags_from_decl_or_type (callee) & ECF_FREE) != 0)
+       {

Likewise.

@@ -1728,6 +1729,15 @@ ref_maybe_used_by_call_p_1 (gcall *call, ao_ref *ref)
          /* Fallthru to general call handling.  */;
       }

+  /* free-like functions may not reference their first argument.  */
+  if (callee != NULL_TREE && (flags & ECF_FREE) != 0)
+    {
+      tree ptr = gimple_call_arg (call, 0);
+      tree base = ao_ref_base (ref);
+      if (base && TREE_CODE (base) == MEM_REF && TREE_OPERAND (base, 0) == ptr)
+       return false;
+    }
+

So this is less aggressive than what we do for BUILT_IN_FREE which simply
returns false as "not reading from (any) memory".  I suspect we might want
to amend the documentation of the "free" attribute to that effect, or find
a better wording ...

Otherwise using DECL_SET_MALLOC sometimes and sometimes
DECL_ALLOC_FN_KIND () = ALLOC_FN_MALLOC looks somewhat
inconsistent.  I'd prefer removing DECL_SET_MALLOC.

You have

+static tree
+handle_free_attribute (tree *node, tree name, tree /*args*/, int /*flags*/,
+                      bool *no_add_attrs)
+{
+  tree decl = *node;
+  if (TREE_CODE (decl) == FUNCTION_DECL
+      && type_num_arguments (TREE_TYPE (decl)) != 0
+      && POINTER_TYPE_P (TREE_VALUE (TYPE_ARG_TYPES (TREE_TYPE (decl)))))
+    DECL_ALLOC_FN_KIND (decl) = ALLOC_FN_FREE;
+  else
+    {
+      warning_at (DECL_SOURCE_LOCATION (decl), OPT_Wattributes,
+                 "%qE attribute ignored", name);
+      *no_add_attrs = true;
+    }

so one can happily apply the attribute to

 void foo (void *, void *);

but then

@@ -2117,6 +2127,13 @@ call_may_clobber_ref_p_1 (gcall *call, ao_ref *ref)
          /* Fallthru to general call handling.  */;
       }

+  if (callee != NULL_TREE
+      && (flags_from_decl_or_type (callee) & ECF_FREE) != 0)
+    {
+      tree ptr = gimple_call_arg (call, 0);
+      return ptr_deref_may_alias_ref_p_1 (ptr, ref);
+    }

will ignore the 2nd argument.  I think it's better to ignore the attribute
if type_num_arguments () != 1.

Richard.

> --
> Regards,
>     Mikhail Maltsev
Mikhail Maltsev April 22, 2016, 9:37 p.m. UTC | #2
On 04/20/2016 05:12 PM, Richard Biener wrote:
> You have
> 
> +static tree
> +handle_free_attribute (tree *node, tree name, tree /*args*/, int /*flags*/,
> +                      bool *no_add_attrs)
> +{
> +  tree decl = *node;
> +  if (TREE_CODE (decl) == FUNCTION_DECL
> +      && type_num_arguments (TREE_TYPE (decl)) != 0
> +      && POINTER_TYPE_P (TREE_VALUE (TYPE_ARG_TYPES (TREE_TYPE (decl)))))
> +    DECL_ALLOC_FN_KIND (decl) = ALLOC_FN_FREE;
> +  else
> +    {
> +      warning_at (DECL_SOURCE_LOCATION (decl), OPT_Wattributes,
> +                 "%qE attribute ignored", name);
> +      *no_add_attrs = true;
> +    }
> 
> so one can happily apply the attribute to
> 
>  void foo (void *, void *);
> 
> but then
> 
> @@ -2117,6 +2127,13 @@ call_may_clobber_ref_p_1 (gcall *call, ao_ref *ref)
>           /* Fallthru to general call handling.  */;
>        }
> 
> +  if (callee != NULL_TREE
> +      && (flags_from_decl_or_type (callee) & ECF_FREE) != 0)
> +    {
> +      tree ptr = gimple_call_arg (call, 0);
> +      return ptr_deref_may_alias_ref_p_1 (ptr, ref);
> +    }
> 
> will ignore the 2nd argument.  I think it's better to ignore the attribute
> if type_num_arguments () != 1.

Actually, the C++ standard ([basic.stc.dynamic]/2) defines the following 4
deallocation functions implicitly:

void operator delete(void*);
void operator delete[](void*);
void operator delete(void*, std::size_t) noexcept;
void operator delete[](void*, std::size_t) noexcept;

And the standard library also has:

void operator delete(void*, const std::nothrow_t&);
void operator delete[](void*, const std::nothrow_t&);
void operator delete(void*, std::size_t, const std::nothrow_t&);
void operator delete[](void*, std::size_t, const std::nothrow_t&);

IIUC, 'delete(void*, std::size_t)' is used by default in C++14
(https://gcc.gnu.org/ml/gcc-patches/2014-12/msg01266.html). How should we handle
this?
Bernd Schmidt April 25, 2016, 9:02 a.m. UTC | #3
On 04/19/2016 10:48 PM, Mikhail Maltsev wrote:
> On 04/18/2016 12:14 PM, Richard Biener wrote:
>>
>> Enlarging tree_function_decl is bad.
> Probably using 3 bits for malloc_flag, operator_new_flag and free_flag is
> redundant. I packed the state into 2 bits.
>>
>> Passes should get at the info via flags_from_decl_or_type () and a new
>> ECF_FREE.
> Fixed.

I think we should also have a testcase that verifies that no DSE happens 
for something that has a destructor.


Bernd
Richard Biener April 25, 2016, 10:05 a.m. UTC | #4
On Mon, Apr 25, 2016 at 11:02 AM, Bernd Schmidt <bschmidt@redhat.com> wrote:
> On 04/19/2016 10:48 PM, Mikhail Maltsev wrote:
>>
>> On 04/18/2016 12:14 PM, Richard Biener wrote:
>>>
>>>
>>> Enlarging tree_function_decl is bad.
>>
>> Probably using 3 bits for malloc_flag, operator_new_flag and free_flag is
>> redundant. I packed the state into 2 bits.
>>>
>>>
>>> Passes should get at the info via flags_from_decl_or_type () and a new
>>> ECF_FREE.
>>
>> Fixed.
>
>
> I think we should also have a testcase that verifies that no DSE happens for
> something that has a destructor.

Well, we should verify that the FE emits the call to the attribute
annotated delete
_after_ emitting the call to the destructor.  Which it already should do.

Richard.

>
> Bernd
>
Richard Biener April 25, 2016, 10:08 a.m. UTC | #5
On Fri, Apr 22, 2016 at 11:37 PM, Mikhail Maltsev <maltsevm@gmail.com> wrote:
> On 04/20/2016 05:12 PM, Richard Biener wrote:
>> You have
>>
>> +static tree
>> +handle_free_attribute (tree *node, tree name, tree /*args*/, int /*flags*/,
>> +                      bool *no_add_attrs)
>> +{
>> +  tree decl = *node;
>> +  if (TREE_CODE (decl) == FUNCTION_DECL
>> +      && type_num_arguments (TREE_TYPE (decl)) != 0
>> +      && POINTER_TYPE_P (TREE_VALUE (TYPE_ARG_TYPES (TREE_TYPE (decl)))))
>> +    DECL_ALLOC_FN_KIND (decl) = ALLOC_FN_FREE;
>> +  else
>> +    {
>> +      warning_at (DECL_SOURCE_LOCATION (decl), OPT_Wattributes,
>> +                 "%qE attribute ignored", name);
>> +      *no_add_attrs = true;
>> +    }
>>
>> so one can happily apply the attribute to
>>
>>  void foo (void *, void *);
>>
>> but then
>>
>> @@ -2117,6 +2127,13 @@ call_may_clobber_ref_p_1 (gcall *call, ao_ref *ref)
>>           /* Fallthru to general call handling.  */;
>>        }
>>
>> +  if (callee != NULL_TREE
>> +      && (flags_from_decl_or_type (callee) & ECF_FREE) != 0)
>> +    {
>> +      tree ptr = gimple_call_arg (call, 0);
>> +      return ptr_deref_may_alias_ref_p_1 (ptr, ref);
>> +    }
>>
>> will ignore the 2nd argument.  I think it's better to ignore the attribute
>> if type_num_arguments () != 1.
>
> Actually, the C++ standard ([basic.stc.dynamic]/2) defines the following 4
> deallocation functions implicitly:
>
> void operator delete(void*);
> void operator delete[](void*);
> void operator delete(void*, std::size_t) noexcept;
> void operator delete[](void*, std::size_t) noexcept;
>
> And the standard library also has:
>
> void operator delete(void*, const std::nothrow_t&);
> void operator delete[](void*, const std::nothrow_t&);
> void operator delete(void*, std::size_t, const std::nothrow_t&);
> void operator delete[](void*, std::size_t, const std::nothrow_t&);
>
> IIUC, 'delete(void*, std::size_t)' is used by default in C++14
> (https://gcc.gnu.org/ml/gcc-patches/2014-12/msg01266.html). How should we handle
> this?

Hmm.  I guess by adjusting the documentation of the attribute to
explicitely mention
the behavior on the rest of the argument pointed-to memory (the
function is assumed
to neither write nor read from that memory).  Also explicitely mention
that 'this' is
always the first argument if present.

Richard.

> --
> Regards,
>     Mikhail Maltsev
Jason Merrill April 25, 2016, 7:57 p.m. UTC | #6
Hmm, this seems to assume that operator delete itself doesn't do
anything with the object being deleted.  This is true of the default
implementation, but I don't see anything in the standard that
prohibits a user-supplied replacement or class-specific deallocation
function from accessing the memory.

Jason


On Mon, Apr 25, 2016 at 6:08 AM, Richard Biener
<richard.guenther@gmail.com> wrote:
> On Fri, Apr 22, 2016 at 11:37 PM, Mikhail Maltsev <maltsevm@gmail.com> wrote:
>> On 04/20/2016 05:12 PM, Richard Biener wrote:
>>> You have
>>>
>>> +static tree
>>> +handle_free_attribute (tree *node, tree name, tree /*args*/, int /*flags*/,
>>> +                      bool *no_add_attrs)
>>> +{
>>> +  tree decl = *node;
>>> +  if (TREE_CODE (decl) == FUNCTION_DECL
>>> +      && type_num_arguments (TREE_TYPE (decl)) != 0
>>> +      && POINTER_TYPE_P (TREE_VALUE (TYPE_ARG_TYPES (TREE_TYPE (decl)))))
>>> +    DECL_ALLOC_FN_KIND (decl) = ALLOC_FN_FREE;
>>> +  else
>>> +    {
>>> +      warning_at (DECL_SOURCE_LOCATION (decl), OPT_Wattributes,
>>> +                 "%qE attribute ignored", name);
>>> +      *no_add_attrs = true;
>>> +    }
>>>
>>> so one can happily apply the attribute to
>>>
>>>  void foo (void *, void *);
>>>
>>> but then
>>>
>>> @@ -2117,6 +2127,13 @@ call_may_clobber_ref_p_1 (gcall *call, ao_ref *ref)
>>>           /* Fallthru to general call handling.  */;
>>>        }
>>>
>>> +  if (callee != NULL_TREE
>>> +      && (flags_from_decl_or_type (callee) & ECF_FREE) != 0)
>>> +    {
>>> +      tree ptr = gimple_call_arg (call, 0);
>>> +      return ptr_deref_may_alias_ref_p_1 (ptr, ref);
>>> +    }
>>>
>>> will ignore the 2nd argument.  I think it's better to ignore the attribute
>>> if type_num_arguments () != 1.
>>
>> Actually, the C++ standard ([basic.stc.dynamic]/2) defines the following 4
>> deallocation functions implicitly:
>>
>> void operator delete(void*);
>> void operator delete[](void*);
>> void operator delete(void*, std::size_t) noexcept;
>> void operator delete[](void*, std::size_t) noexcept;
>>
>> And the standard library also has:
>>
>> void operator delete(void*, const std::nothrow_t&);
>> void operator delete[](void*, const std::nothrow_t&);
>> void operator delete(void*, std::size_t, const std::nothrow_t&);
>> void operator delete[](void*, std::size_t, const std::nothrow_t&);
>>
>> IIUC, 'delete(void*, std::size_t)' is used by default in C++14
>> (https://gcc.gnu.org/ml/gcc-patches/2014-12/msg01266.html). How should we handle
>> this?
>
> Hmm.  I guess by adjusting the documentation of the attribute to
> explicitely mention
> the behavior on the rest of the argument pointed-to memory (the
> function is assumed
> to neither write nor read from that memory).  Also explicitely mention
> that 'this' is
> always the first argument if present.
>
> Richard.
>
>> --
>> Regards,
>>     Mikhail Maltsev
Richard Biener April 26, 2016, 9:07 a.m. UTC | #7
On Mon, Apr 25, 2016 at 9:57 PM, Jason Merrill <jason@redhat.com> wrote:
> Hmm, this seems to assume that operator delete itself doesn't do
> anything with the object being deleted.  This is true of the default
> implementation, but I don't see anything in the standard that
> prohibits a user-supplied replacement or class-specific deallocation
> function from accessing the memory.

Hmm, but the delete expression invokes the (default) destructor which
ends the lifetime of the object and thus invalidates all memory.  Don't
we place a CLOBBER there as well nowadays (seems not).

For

struct A { A(); ~A(); int i; };

int main()
{
  A *a = new A;
  delete a;
}

I see

    struct A * a;
  <<cleanup_point <<< Unknown tree: expr_stmt
  (void) (a = TARGET_EXPR <D.2346, operator new (4)>;, try
    {
      A::A ((struct A *) D.2346);
    }
  catch
    {
      operator delete (D.2346);
    }, (struct A *) D.2346;) >>>>>;
  <<cleanup_point <<< Unknown tree: expr_stmt
  if (SAVE_EXPR <a> != 0B)
    {
      A::~A (SAVE_EXPR <a>);, operator delete ((void *) SAVE_EXPR <a>);;
    }
  else
    {
      <<< Unknown tree: void_cst >>>
    } >>>>>;

so after the destructor is invoked the objects lifetime ends.

Richard.

> Jason
>
>
> On Mon, Apr 25, 2016 at 6:08 AM, Richard Biener
> <richard.guenther@gmail.com> wrote:
>> On Fri, Apr 22, 2016 at 11:37 PM, Mikhail Maltsev <maltsevm@gmail.com> wrote:
>>> On 04/20/2016 05:12 PM, Richard Biener wrote:
>>>> You have
>>>>
>>>> +static tree
>>>> +handle_free_attribute (tree *node, tree name, tree /*args*/, int /*flags*/,
>>>> +                      bool *no_add_attrs)
>>>> +{
>>>> +  tree decl = *node;
>>>> +  if (TREE_CODE (decl) == FUNCTION_DECL
>>>> +      && type_num_arguments (TREE_TYPE (decl)) != 0
>>>> +      && POINTER_TYPE_P (TREE_VALUE (TYPE_ARG_TYPES (TREE_TYPE (decl)))))
>>>> +    DECL_ALLOC_FN_KIND (decl) = ALLOC_FN_FREE;
>>>> +  else
>>>> +    {
>>>> +      warning_at (DECL_SOURCE_LOCATION (decl), OPT_Wattributes,
>>>> +                 "%qE attribute ignored", name);
>>>> +      *no_add_attrs = true;
>>>> +    }
>>>>
>>>> so one can happily apply the attribute to
>>>>
>>>>  void foo (void *, void *);
>>>>
>>>> but then
>>>>
>>>> @@ -2117,6 +2127,13 @@ call_may_clobber_ref_p_1 (gcall *call, ao_ref *ref)
>>>>           /* Fallthru to general call handling.  */;
>>>>        }
>>>>
>>>> +  if (callee != NULL_TREE
>>>> +      && (flags_from_decl_or_type (callee) & ECF_FREE) != 0)
>>>> +    {
>>>> +      tree ptr = gimple_call_arg (call, 0);
>>>> +      return ptr_deref_may_alias_ref_p_1 (ptr, ref);
>>>> +    }
>>>>
>>>> will ignore the 2nd argument.  I think it's better to ignore the attribute
>>>> if type_num_arguments () != 1.
>>>
>>> Actually, the C++ standard ([basic.stc.dynamic]/2) defines the following 4
>>> deallocation functions implicitly:
>>>
>>> void operator delete(void*);
>>> void operator delete[](void*);
>>> void operator delete(void*, std::size_t) noexcept;
>>> void operator delete[](void*, std::size_t) noexcept;
>>>
>>> And the standard library also has:
>>>
>>> void operator delete(void*, const std::nothrow_t&);
>>> void operator delete[](void*, const std::nothrow_t&);
>>> void operator delete(void*, std::size_t, const std::nothrow_t&);
>>> void operator delete[](void*, std::size_t, const std::nothrow_t&);
>>>
>>> IIUC, 'delete(void*, std::size_t)' is used by default in C++14
>>> (https://gcc.gnu.org/ml/gcc-patches/2014-12/msg01266.html). How should we handle
>>> this?
>>
>> Hmm.  I guess by adjusting the documentation of the attribute to
>> explicitely mention
>> the behavior on the rest of the argument pointed-to memory (the
>> function is assumed
>> to neither write nor read from that memory).  Also explicitely mention
>> that 'this' is
>> always the first argument if present.
>>
>> Richard.
>>
>>> --
>>> Regards,
>>>     Mikhail Maltsev
Marc Glisse April 26, 2016, 9:28 a.m. UTC | #8
On Tue, 26 Apr 2016, Richard Biener wrote:

> On Mon, Apr 25, 2016 at 9:57 PM, Jason Merrill <jason@redhat.com> wrote:
>> Hmm, this seems to assume that operator delete itself doesn't do
>> anything with the object being deleted.  This is true of the default
>> implementation, but I don't see anything in the standard that
>> prohibits a user-supplied replacement or class-specific deallocation
>> function from accessing the memory.
>
> Hmm, but the delete expression invokes the (default) destructor which
> ends the lifetime of the object and thus invalidates all memory.  Don't
> we place a CLOBBER there as well nowadays (seems not).
>
> For
>
> struct A { A(); ~A(); int i; };
>
> int main()
> {
>  A *a = new A;
>  delete a;
> }
>
> I see
>
>    struct A * a;
>  <<cleanup_point <<< Unknown tree: expr_stmt
>  (void) (a = TARGET_EXPR <D.2346, operator new (4)>;, try
>    {
>      A::A ((struct A *) D.2346);
>    }
>  catch
>    {
>      operator delete (D.2346);
>    }, (struct A *) D.2346;) >>>>>;
>  <<cleanup_point <<< Unknown tree: expr_stmt
>  if (SAVE_EXPR <a> != 0B)
>    {
>      A::~A (SAVE_EXPR <a>);, operator delete ((void *) SAVE_EXPR <a>);;
>    }
>  else
>    {
>      <<< Unknown tree: void_cst >>>
>    } >>>>>;
>
> so after the destructor is invoked the objects lifetime ends.

You can also call operator new and operator delete directly in C++, not 
just through new/delete expressions, and that's what std::allocator does.

Especially in C++17 where we should have aligned allocation, I can imagine 
operator new / delete implemented like gcc/config/i386/gmm_malloc.h on 
some platforms, which requires reading in operator delete stuff that 
operator new wrote there. Depending on inline decisions, DSE-ing part of 
new could be problematic (IIRC those functions are technically not allowed 
to be marked 'inline', but that's not quite the same thing).
Richard Biener April 26, 2016, 10:35 a.m. UTC | #9
On Tue, Apr 26, 2016 at 11:28 AM, Marc Glisse <marc.glisse@inria.fr> wrote:
> On Tue, 26 Apr 2016, Richard Biener wrote:
>
>> On Mon, Apr 25, 2016 at 9:57 PM, Jason Merrill <jason@redhat.com> wrote:
>>>
>>> Hmm, this seems to assume that operator delete itself doesn't do
>>> anything with the object being deleted.  This is true of the default
>>> implementation, but I don't see anything in the standard that
>>> prohibits a user-supplied replacement or class-specific deallocation
>>> function from accessing the memory.
>>
>>
>> Hmm, but the delete expression invokes the (default) destructor which
>> ends the lifetime of the object and thus invalidates all memory.  Don't
>> we place a CLOBBER there as well nowadays (seems not).
>>
>> For
>>
>> struct A { A(); ~A(); int i; };
>>
>> int main()
>> {
>>  A *a = new A;
>>  delete a;
>> }
>>
>> I see
>>
>>    struct A * a;
>>  <<cleanup_point <<< Unknown tree: expr_stmt
>>  (void) (a = TARGET_EXPR <D.2346, operator new (4)>;, try
>>    {
>>      A::A ((struct A *) D.2346);
>>    }
>>  catch
>>    {
>>      operator delete (D.2346);
>>    }, (struct A *) D.2346;) >>>>>;
>>  <<cleanup_point <<< Unknown tree: expr_stmt
>>  if (SAVE_EXPR <a> != 0B)
>>    {
>>      A::~A (SAVE_EXPR <a>);, operator delete ((void *) SAVE_EXPR <a>);;
>>    }
>>  else
>>    {
>>      <<< Unknown tree: void_cst >>>
>>    } >>>>>;
>>
>> so after the destructor is invoked the objects lifetime ends.
>
>
> You can also call operator new and operator delete directly in C++, not just
> through new/delete expressions, and that's what std::allocator does.
>
> Especially in C++17 where we should have aligned allocation, I can imagine
> operator new / delete implemented like gcc/config/i386/gmm_malloc.h on some
> platforms, which requires reading in operator delete stuff that operator new
> wrote there. Depending on inline decisions, DSE-ing part of new could be
> problematic (IIRC those functions are technically not allowed to be marked
> 'inline', but that's not quite the same thing).

Ok.  Is it then a matter of also seeing the size of the object and only treating
[p, p + size[ as DCEable thus only the object itself becomes dead?

Btw, similar issues would arise if we'd inline malloc() from glibc and that used
a malloc annotated internal helper and we run into a (not inlined) free() call.

Richard.

> --
> Marc Glisse
Jason Merrill April 26, 2016, 12:58 p.m. UTC | #10
On Tue, Apr 26, 2016 at 5:07 AM, Richard Biener
<richard.guenther@gmail.com> wrote:
> On Mon, Apr 25, 2016 at 9:57 PM, Jason Merrill <jason@redhat.com> wrote:
>> Hmm, this seems to assume that operator delete itself doesn't do
>> anything with the object being deleted.  This is true of the default
>> implementation, but I don't see anything in the standard that
>> prohibits a user-supplied replacement or class-specific deallocation
>> function from accessing the memory.
>
> Hmm, but the delete expression invokes the (default) destructor which
> ends the lifetime of the object and thus invalidates all memory.  Don't
> we place a CLOBBER there as well nowadays (seems not).

We put a CLOBBER inside the destructor.  Now that you mention it, I
suppose we would get more DSE if we put it in the calling context
instead.

But for types with trivial destructors, including scalar types, the
lifetime of the object doesn't end until the storage is released,
somewhere within the call to operator delete.  The clang folks have
been arguing recently that this asymmetry is harmful, so we might move
toward a model where trivial destruction also ends the lifetime of an
object, but that's currently unclear.

operator delete is like free in that it renders the pointer invalid.

Jason
diff mbox

Patch

diff --git a/gcc/ada/gcc-interface/trans.c b/gcc/ada/gcc-interface/trans.c
index 357d26f..00e4f84 100644
--- a/gcc/ada/gcc-interface/trans.c
+++ b/gcc/ada/gcc-interface/trans.c
@@ -400,7 +400,7 @@  gigi (Node_Id gnat_root,
 			   ftype,
 			   NULL_TREE, is_disabled, false, true, true, false,
 			   true, false, NULL, Empty);
-  DECL_IS_MALLOC (malloc_decl) = 1;
+  DECL_SET_MALLOC (malloc_decl);
 
   /* free is a function declaration tree for a function to free memory.  */
   free_decl
diff --git a/gcc/ada/gcc-interface/utils.c b/gcc/ada/gcc-interface/utils.c
index d568dff..5b12e3d 100644
--- a/gcc/ada/gcc-interface/utils.c
+++ b/gcc/ada/gcc-interface/utils.c
@@ -6026,7 +6026,7 @@  handle_malloc_attribute (tree *node, tree name, tree ARG_UNUSED (args),
 {
   if (TREE_CODE (*node) == FUNCTION_DECL
       && POINTER_TYPE_P (TREE_TYPE (TREE_TYPE (*node))))
-    DECL_IS_MALLOC (*node) = 1;
+    DECL_SET_MALLOC (*node);
   else
     {
       warning (OPT_Wattributes, "%qs attribute ignored",
diff --git a/gcc/builtin-attrs.def b/gcc/builtin-attrs.def
index 089817a..ddaf3e6 100644
--- a/gcc/builtin-attrs.def
+++ b/gcc/builtin-attrs.def
@@ -88,6 +88,7 @@  DEF_ATTR_IDENT (ATTR_CONST, "const")
 DEF_ATTR_IDENT (ATTR_FORMAT, "format")
 DEF_ATTR_IDENT (ATTR_FORMAT_ARG, "format_arg")
 DEF_ATTR_IDENT (ATTR_MALLOC, "malloc")
+DEF_ATTR_IDENT (ATTR_FREE, "free")
 DEF_ATTR_IDENT (ATTR_NONNULL, "nonnull")
 DEF_ATTR_IDENT (ATTR_NORETURN, "noreturn")
 DEF_ATTR_IDENT (ATTR_NOTHROW, "nothrow")
@@ -141,6 +142,10 @@  DEF_ATTR_TREE_LIST (ATTR_MALLOC_NOTHROW_LIST, ATTR_MALLOC,	\
 			ATTR_NULL, ATTR_NOTHROW_LIST)
 DEF_ATTR_TREE_LIST (ATTR_MALLOC_NOTHROW_LEAF_LIST, ATTR_MALLOC,	\
 			ATTR_NULL, ATTR_NOTHROW_LEAF_LIST)
+DEF_ATTR_TREE_LIST (ATTR_FREE_NOTHROW_LIST, ATTR_FREE,		\
+			ATTR_NULL, ATTR_NOTHROW_LIST)
+DEF_ATTR_TREE_LIST (ATTR_FREE_NOTHROW_LEAF_LIST, ATTR_FREE,	\
+			ATTR_NULL, ATTR_NOTHROW_LEAF_LIST)
 DEF_ATTR_TREE_LIST (ATTR_SENTINEL_NOTHROW_LIST, ATTR_SENTINEL,	\
 			ATTR_NULL, ATTR_NOTHROW_LIST)
 DEF_ATTR_TREE_LIST (ATTR_SENTINEL_NOTHROW_LEAF_LIST, ATTR_SENTINEL,	\
@@ -269,8 +274,10 @@  DEF_ATTR_TREE_LIST (ATTR_TM_NOTHROW_RT_LIST,
 DEF_ATTR_TREE_LIST (ATTR_TMPURE_MALLOC_NOTHROW_LIST,
 		   ATTR_TM_TMPURE, ATTR_NULL, ATTR_MALLOC_NOTHROW_LIST)
 /* Same attributes used for BUILT_IN_FREE except with TM_PURE thrown in.  */
-DEF_ATTR_TREE_LIST (ATTR_TMPURE_NOTHROW_LIST,
-		   ATTR_TM_TMPURE, ATTR_NULL, ATTR_NOTHROW_LIST)
+DEF_ATTR_TREE_LIST (ATTR_TMPURE_FREE_NOTHROW_LIST,
+		   ATTR_TM_TMPURE, ATTR_NULL, ATTR_FREE_NOTHROW_LIST)
+DEF_ATTR_TREE_LIST (ATTR_TMPURE_FREE_NOTHROW_LEAF_LIST,
+		   ATTR_TM_TMPURE, ATTR_NULL, ATTR_FREE_NOTHROW_LEAF_LIST)
 
 DEF_ATTR_TREE_LIST (ATTR_TMPURE_NOTHROW_LEAF_LIST,
 		    ATTR_TM_TMPURE, ATTR_NULL, ATTR_NOTHROW_LEAF_LIST)
diff --git a/gcc/builtins.def b/gcc/builtins.def
index 2fc7f65..e3d1614 100644
--- a/gcc/builtins.def
+++ b/gcc/builtins.def
@@ -781,7 +781,7 @@  DEF_EXT_LIB_BUILTIN    (BUILT_IN_FFSLL, "ffsll", BT_FN_INT_LONGLONG, ATTR_CONST_
 DEF_EXT_LIB_BUILTIN        (BUILT_IN_FORK, "fork", BT_FN_PID, ATTR_NOTHROW_LIST)
 DEF_GCC_BUILTIN        (BUILT_IN_FRAME_ADDRESS, "frame_address", BT_FN_PTR_UINT, ATTR_NULL)
 /* [trans-mem]: Adjust BUILT_IN_TM_FREE if BUILT_IN_FREE is changed.  */
-DEF_LIB_BUILTIN        (BUILT_IN_FREE, "free", BT_FN_VOID_PTR, ATTR_NOTHROW_LEAF_LIST)
+DEF_LIB_BUILTIN        (BUILT_IN_FREE, "free", BT_FN_VOID_PTR, ATTR_FREE_NOTHROW_LEAF_LIST)
 DEF_GCC_BUILTIN        (BUILT_IN_FROB_RETURN_ADDR, "frob_return_addr", BT_FN_PTR_PTR, ATTR_NULL)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_GETTEXT, "gettext", BT_FN_STRING_CONST_STRING, ATTR_FORMAT_ARG_1)
 DEF_C99_BUILTIN        (BUILT_IN_IMAXABS, "imaxabs", BT_FN_INTMAX_INTMAX, ATTR_CONST_NOTHROW_LEAF_LIST)
diff --git a/gcc/c-family/c-common.c b/gcc/c-family/c-common.c
index cae2faf..12d7924 100644
--- a/gcc/c-family/c-common.c
+++ b/gcc/c-family/c-common.c
@@ -355,6 +355,7 @@  static tree handle_tls_model_attribute (tree *, tree, tree, int,
 static tree handle_no_instrument_function_attribute (tree *, tree,
 						     tree, int, bool *);
 static tree handle_malloc_attribute (tree *, tree, tree, int, bool *);
+static tree handle_free_attribute (tree *, tree, tree, int, bool *);
 static tree handle_returns_twice_attribute (tree *, tree, tree, int, bool *);
 static tree handle_no_limit_stack_attribute (tree *, tree, tree, int,
 					     bool *);
@@ -720,6 +721,8 @@  const struct attribute_spec c_common_attribute_table[] =
 			      false },
   { "malloc",                 0, 0, true,  false, false,
 			      handle_malloc_attribute, false },
+  { "free",                   0, 0, true,  false, false,
+			      handle_free_attribute, false },
   { "returns_twice",          0, 0, true,  false, false,
 			      handle_returns_twice_attribute, false },
   { "no_stack_limit",         0, 0, true,  false, false,
@@ -8315,7 +8318,7 @@  handle_malloc_attribute (tree *node, tree name, tree ARG_UNUSED (args),
 {
   if (TREE_CODE (*node) == FUNCTION_DECL
       && POINTER_TYPE_P (TREE_TYPE (TREE_TYPE (*node))))
-    DECL_IS_MALLOC (*node) = 1;
+    DECL_ALLOC_FN_KIND (*node) = ALLOC_FN_MALLOC;
   else
     {
       warning (OPT_Wattributes, "%qE attribute ignored", name);
@@ -8325,6 +8328,27 @@  handle_malloc_attribute (tree *node, tree name, tree ARG_UNUSED (args),
   return NULL_TREE;
 }
 
+/* Handle a "free" attribute; arguments as in struct attribute_spec.handler.  */
+
+static tree
+handle_free_attribute (tree *node, tree name, tree /*args*/, int /*flags*/,
+		       bool *no_add_attrs)
+{
+  tree decl = *node;
+  if (TREE_CODE (decl) == FUNCTION_DECL
+      && type_num_arguments (TREE_TYPE (decl)) != 0
+      && POINTER_TYPE_P (TREE_VALUE (TYPE_ARG_TYPES (TREE_TYPE (decl)))))
+    DECL_ALLOC_FN_KIND (decl) = ALLOC_FN_FREE;
+  else
+    {
+      warning_at (DECL_SOURCE_LOCATION (decl), OPT_Wattributes,
+		  "%qE attribute ignored", name);
+      *no_add_attrs = true;
+    }
+
+  return NULL_TREE;
+}
+
 /* Handle a "alloc_size" attribute; arguments as in
    struct attribute_spec.handler.  */
 
diff --git a/gcc/c/c-decl.c b/gcc/c/c-decl.c
index f0c677b..b1178b1 100644
--- a/gcc/c/c-decl.c
+++ b/gcc/c/c-decl.c
@@ -2476,8 +2476,8 @@  merge_decls (tree newdecl, tree olddecl, tree newtype, tree oldtype)
 	  DECL_NO_INSTRUMENT_FUNCTION_ENTRY_EXIT (newdecl)
 	    |= DECL_NO_INSTRUMENT_FUNCTION_ENTRY_EXIT (olddecl);
 	  TREE_THIS_VOLATILE (newdecl) |= TREE_THIS_VOLATILE (olddecl);
-	  DECL_IS_MALLOC (newdecl) |= DECL_IS_MALLOC (olddecl);
-	  DECL_IS_OPERATOR_NEW (newdecl) |= DECL_IS_OPERATOR_NEW (olddecl);
+	  if (DECL_ALLOC_FN_KIND (olddecl) != ALLOC_FN_NONE)
+	    DECL_ALLOC_FN_KIND (newdecl) = DECL_ALLOC_FN_KIND (olddecl);
 	  TREE_READONLY (newdecl) |= TREE_READONLY (olddecl);
 	  DECL_PURE_P (newdecl) |= DECL_PURE_P (olddecl);
 	  DECL_IS_NOVOPS (newdecl) |= DECL_IS_NOVOPS (olddecl);
diff --git a/gcc/calls.c b/gcc/calls.c
index 6415e08..bf9b0c7 100644
--- a/gcc/calls.c
+++ b/gcc/calls.c
@@ -719,9 +719,11 @@  flags_from_decl_or_type (const_tree exp)
 
   if (DECL_P (exp))
     {
-      /* The function exp may have the `malloc' attribute.  */
+      /* The function exp may have the `malloc' and `free' attributes.  */
       if (DECL_IS_MALLOC (exp))
 	flags |= ECF_MALLOC;
+      if (DECL_IS_FREE (exp))
+	flags |= ECF_FREE;
 
       /* The function exp may have the `returns_twice' attribute.  */
       if (DECL_IS_RETURNS_TWICE (exp))
diff --git a/gcc/cp/decl.c b/gcc/cp/decl.c
index 461822b..f213a9c 100644
--- a/gcc/cp/decl.c
+++ b/gcc/cp/decl.c
@@ -2184,8 +2184,8 @@  duplicate_decls (tree newdecl, tree olddecl, bool newdecl_is_friend)
 	  DECL_NO_LIMIT_STACK (newdecl) |= DECL_NO_LIMIT_STACK (olddecl);
 	  TREE_THIS_VOLATILE (newdecl) |= TREE_THIS_VOLATILE (olddecl);
 	  TREE_NOTHROW (newdecl) |= TREE_NOTHROW (olddecl);
-	  DECL_IS_MALLOC (newdecl) |= DECL_IS_MALLOC (olddecl);
-	  DECL_IS_OPERATOR_NEW (newdecl) |= DECL_IS_OPERATOR_NEW (olddecl);
+	  if (DECL_ALLOC_FN_KIND (olddecl) != ALLOC_FN_NONE)
+	    DECL_ALLOC_FN_KIND (newdecl) = DECL_ALLOC_FN_KIND (olddecl);
 	  DECL_PURE_P (newdecl) |= DECL_PURE_P (olddecl);
 	  TREE_READONLY (newdecl) |= TREE_READONLY (olddecl);
 	  DECL_LOOPING_CONST_OR_PURE_P (newdecl) 
@@ -4146,13 +4146,14 @@  cxx_init_decl_processing (void)
     deltype = cp_build_type_attribute_variant (void_ftype_ptr, extvisattr);
     deltype = build_exception_variant (deltype, empty_except_spec);
     tree opnew = push_cp_library_fn (NEW_EXPR, newtype, 0);
-    DECL_IS_MALLOC (opnew) = 1;
-    DECL_IS_OPERATOR_NEW (opnew) = 1;
+    DECL_ALLOC_FN_KIND (opnew) = ALLOC_FN_OPERATOR_NEW;
     opnew = push_cp_library_fn (VEC_NEW_EXPR, newtype, 0);
-    DECL_IS_MALLOC (opnew) = 1;
-    DECL_IS_OPERATOR_NEW (opnew) = 1;
-    push_cp_library_fn (DELETE_EXPR, deltype, ECF_NOTHROW);
-    push_cp_library_fn (VEC_DELETE_EXPR, deltype, ECF_NOTHROW);
+    DECL_ALLOC_FN_KIND (opnew) = ALLOC_FN_OPERATOR_NEW;
+
+    tree opdelete = push_cp_library_fn (DELETE_EXPR, deltype, ECF_NOTHROW);
+    DECL_ALLOC_FN_KIND (opdelete) = ALLOC_FN_FREE;
+    opdelete = push_cp_library_fn (VEC_DELETE_EXPR, deltype, ECF_NOTHROW);
+    DECL_ALLOC_FN_KIND (opdelete) = ALLOC_FN_FREE;
     if (flag_sized_deallocation)
       {
 	/* Also push the sized deallocation variants:
@@ -4164,8 +4165,10 @@  cxx_init_decl_processing (void)
 	deltype = cp_build_type_attribute_variant (void_ftype_ptr_size,
 						   extvisattr);
 	deltype = build_exception_variant (deltype, empty_except_spec);
-	push_cp_library_fn (DELETE_EXPR, deltype, ECF_NOTHROW);
-	push_cp_library_fn (VEC_DELETE_EXPR, deltype, ECF_NOTHROW);
+	opdelete = push_cp_library_fn (DELETE_EXPR, deltype, ECF_NOTHROW);
+	DECL_ALLOC_FN_KIND (opdelete) = ALLOC_FN_FREE;
+	opdelete = push_cp_library_fn (VEC_DELETE_EXPR, deltype, ECF_NOTHROW);
+	DECL_ALLOC_FN_KIND (opdelete) = ALLOC_FN_FREE;
       }
 
     nullptr_type_node = make_node (NULLPTR_TYPE);
@@ -12184,10 +12187,13 @@  grok_op_properties (tree decl, bool complain)
   if (operator_code == NEW_EXPR || operator_code == VEC_NEW_EXPR)
     {
       TREE_TYPE (decl) = coerce_new_type (TREE_TYPE (decl));
-      DECL_IS_OPERATOR_NEW (decl) = 1;
+      DECL_ALLOC_FN_KIND (decl) = ALLOC_FN_OPERATOR_NEW;
     }
   else if (operator_code == DELETE_EXPR || operator_code == VEC_DELETE_EXPR)
-    TREE_TYPE (decl) = coerce_delete_type (TREE_TYPE (decl));
+    {
+      TREE_TYPE (decl) = coerce_delete_type (TREE_TYPE (decl));
+      DECL_ALLOC_FN_KIND (decl) = ALLOC_FN_FREE;
+    }
   else
     {
       /* An operator function must either be a non-static member function
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index a5a8b23..fcf0f0a 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -2825,6 +2825,17 @@  a pointer to uninitialized or zeroed-out storage.  However, functions
 like @code{realloc} do not have this property, as they can return a
 pointer to storage containing pointers.
 
+@item free
+@cindex @code{free} function attribute
+@cindex functions that behave like free
+This tells the compiler that a function is @code{free}-like, i.e., that it
+does not access the storage addressed by the pointer @var{P} passed to the
+function.  Moreover, accessing the storage after the function returns invokes
+undefined behavior.
+
+Using this attribute can expose more opportunities to dead store elimination
+optimization.
+
 @item no_icf
 @cindex @code{no_icf} function attribute
 This function attribute prevents a functions from being merged with another
diff --git a/gcc/fortran/f95-lang.c b/gcc/fortran/f95-lang.c
index b89a291..f138289 100644
--- a/gcc/fortran/f95-lang.c
+++ b/gcc/fortran/f95-lang.c
@@ -969,7 +969,7 @@  gfc_init_builtin_functions (void)
 				    size_type_node, NULL_TREE);
   gfc_define_builtin ("__builtin_calloc", ftype, BUILT_IN_CALLOC,
 		      "calloc", ATTR_NOTHROW_LEAF_MALLOC_LIST);
-  DECL_IS_MALLOC (builtin_decl_explicit (BUILT_IN_CALLOC)) = 1;
+  DECL_SET_MALLOC (builtin_decl_explicit (BUILT_IN_CALLOC));
 
   ftype = build_function_type_list (pvoid_type_node,
                                     size_type_node, pvoid_type_node,
diff --git a/gcc/gtm-builtins.def b/gcc/gtm-builtins.def
index 6d5cfb9..556391b 100644
--- a/gcc/gtm-builtins.def
+++ b/gcc/gtm-builtins.def
@@ -32,7 +32,7 @@  DEF_TM_BUILTIN (BUILT_IN_TM_MALLOC, "_ITM_malloc",
 DEF_TM_BUILTIN (BUILT_IN_TM_CALLOC, "_ITM_calloc",
 		BT_FN_PTR_SIZE_SIZE, ATTR_TMPURE_MALLOC_NOTHROW_LIST)
 DEF_TM_BUILTIN (BUILT_IN_TM_FREE, "_ITM_free",
-		BT_FN_VOID_PTR, ATTR_TMPURE_NOTHROW_LEAF_LIST)
+		BT_FN_VOID_PTR, ATTR_TMPURE_FREE_NOTHROW_LEAF_LIST)
 
 /* Logging builtins.  */
 DEF_TM_BUILTIN (BUILT_IN_TM_LOG_1, "_ITM_LU1",
diff --git a/gcc/java/decl.c b/gcc/java/decl.c
index 93304da..69465d0 100644
--- a/gcc/java/decl.c
+++ b/gcc/java/decl.c
@@ -969,11 +969,11 @@  java_init_decl_processing (void)
   t = build_function_type_list (ptr_type_node, class_ptr_type, NULL_TREE);
   alloc_object_node = add_builtin_function ("_Jv_AllocObject", t,
 					    0, NOT_BUILT_IN, NULL, NULL_TREE);
-  DECL_IS_MALLOC (alloc_object_node) = 1;
+  DECL_SET_MALLOC (alloc_object_node);
   alloc_no_finalizer_node =
     add_builtin_function ("_Jv_AllocObjectNoFinalizer", t,
 			  0, NOT_BUILT_IN, NULL, NULL_TREE);
-  DECL_IS_MALLOC (alloc_no_finalizer_node) = 1;
+  DECL_SET_MALLOC (alloc_no_finalizer_node);
 
   t = build_function_type_list (void_type_node, ptr_type_node, NULL_TREE);
   soft_initclass_node = add_builtin_function ("_Jv_InitClass", t,
@@ -1005,7 +1005,7 @@  java_init_decl_processing (void)
   soft_newarray_node
       = add_builtin_function ("_Jv_NewPrimArray", t,
 			      0, NOT_BUILT_IN, NULL, NULL_TREE);
-  DECL_IS_MALLOC (soft_newarray_node) = 1;
+  DECL_SET_MALLOC (soft_newarray_node);
 
   t = build_function_type_list (ptr_type_node,
 				int_type_node, class_ptr_type,
@@ -1013,7 +1013,7 @@  java_init_decl_processing (void)
   soft_anewarray_node
       = add_builtin_function ("_Jv_NewObjectArray", t,
 			      0, NOT_BUILT_IN, NULL, NULL_TREE);
-  DECL_IS_MALLOC (soft_anewarray_node) = 1;
+  DECL_SET_MALLOC (soft_anewarray_node);
 
   t = build_varargs_function_type_list (ptr_type_node,
 					ptr_type_node, int_type_node,
@@ -1021,7 +1021,7 @@  java_init_decl_processing (void)
   soft_multianewarray_node
       = add_builtin_function ("_Jv_NewMultiArray", t,
 			      0, NOT_BUILT_IN, NULL, NULL_TREE);
-  DECL_IS_MALLOC (soft_multianewarray_node) = 1;
+  DECL_SET_MALLOC (soft_multianewarray_node);
 
   t = build_function_type_list (void_type_node, int_type_node, NULL_TREE);
   soft_badarrayindex_node
diff --git a/gcc/lto-cgraph.c b/gcc/lto-cgraph.c
index 95c446d..c696004 100644
--- a/gcc/lto-cgraph.c
+++ b/gcc/lto-cgraph.c
@@ -277,6 +277,7 @@  lto_output_edge (struct lto_simple_output_block *ob, struct cgraph_edge *edge,
       bp_pack_value (&bp, (flags & ECF_PURE) != 0, 1);
       bp_pack_value (&bp, (flags & ECF_NORETURN) != 0, 1);
       bp_pack_value (&bp, (flags & ECF_MALLOC) != 0, 1);
+      bp_pack_value (&bp, (flags & ECF_FREE) != 0, 1);
       bp_pack_value (&bp, (flags & ECF_NOTHROW) != 0, 1);
       bp_pack_value (&bp, (flags & ECF_RETURNS_TWICE) != 0, 1);
       /* Flags that should not appear on indirect calls.  */
@@ -1505,6 +1506,8 @@  input_edge (struct lto_input_block *ib, vec<symtab_node *> nodes,
       if (bp_unpack_value (&bp, 1))
 	ecf_flags |= ECF_MALLOC;
       if (bp_unpack_value (&bp, 1))
+	ecf_flags |= ECF_FREE;
+      if (bp_unpack_value (&bp, 1))
 	ecf_flags |= ECF_NOTHROW;
       if (bp_unpack_value (&bp, 1))
 	ecf_flags |= ECF_RETURNS_TWICE;
diff --git a/gcc/lto-streamer-out.c b/gcc/lto-streamer-out.c
index 6703d41..b3bc5ae 100644
--- a/gcc/lto-streamer-out.c
+++ b/gcc/lto-streamer-out.c
@@ -1120,6 +1120,7 @@  hash_tree (struct streamer_tree_cache_d *cache, hash_map<tree, hashval_t> *map,
       hstate.add_flag (DECL_IS_RETURNS_TWICE (t));
       hstate.add_flag (DECL_IS_MALLOC (t));
       hstate.add_flag (DECL_IS_OPERATOR_NEW (t));
+      hstate.add_flag (DECL_IS_FREE (t));
       hstate.add_flag (DECL_DECLARED_INLINE_P (t));
       hstate.add_flag (DECL_STATIC_CHAIN (t));
       hstate.add_flag (DECL_NO_INLINE_WARNING_P (t));
diff --git a/gcc/lto/lto-lang.c b/gcc/lto/lto-lang.c
index b5efe3a..060044b 100644
--- a/gcc/lto/lto-lang.c
+++ b/gcc/lto/lto-lang.c
@@ -288,7 +288,7 @@  handle_malloc_attribute (tree *node, tree ARG_UNUSED (name),
 {
   if (TREE_CODE (*node) == FUNCTION_DECL
       && POINTER_TYPE_P (TREE_TYPE (TREE_TYPE (*node))))
-    DECL_IS_MALLOC (*node) = 1;
+    DECL_ALLOC_FN_KIND (*node) = ALLOC_FN_MALLOC;
   else
     gcc_unreachable ();
 
diff --git a/gcc/testsuite/g++.dg/opt/op-delete-dse.C b/gcc/testsuite/g++.dg/opt/op-delete-dse.C
new file mode 100644
index 0000000..4df869f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/opt/op-delete-dse.C
@@ -0,0 +1,14 @@ 
+// { dg-do compile }
+// { dg-options "-O -fdump-tree-dse1-details" }
+// { dg-final { scan-tree-dump-times "Deleted dead call" 1 "dse1" } }
+
+void use (void *);
+
+void
+test_delete_pod ()
+{
+  int *data = new int;
+  use (data);
+  __builtin_memset (data, 0, sizeof (int));
+  delete data;
+}
diff --git a/gcc/testsuite/gcc.dg/attr-free.c b/gcc/testsuite/gcc.dg/attr-free.c
new file mode 100644
index 0000000..6aa9153
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/attr-free.c
@@ -0,0 +1,15 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O1" } */
+
+void *malloc (__SIZE_TYPE__);
+void custom_free (void *) __attribute__((free));
+
+void
+test (void)
+{
+  char *data = (char *) malloc (1);
+  data[0] = 42;
+  custom_free (data);
+}
+
+/* { dg-final { scan-assembler-not "42" } } */
diff --git a/gcc/tree-core.h b/gcc/tree-core.h
index 0d48ff5..fa96d93 100644
--- a/gcc/tree-core.h
+++ b/gcc/tree-core.h
@@ -87,6 +87,9 @@  struct die_struct;
 /* Nonzero if this call is into the transaction runtime library.  */
 #define ECF_TM_BUILTIN		  (1 << 12)
 
+/* Nonzero if this is a call to free or a related function.  */
+#define ECF_FREE		  (1 << 13)
+
 /* Call argument flags.  */
 /* Nonzero if the argument is not dereferenced recursively, thus only
    directly reachable memory is read or written.  */
@@ -1647,6 +1650,18 @@  struct GTY(()) tree_decl_non_common {
   tree result;
 };
 
+/* Functions related to memory allocation.  */
+
+enum alloc_fn_kind {
+  ALLOC_FN_NONE,
+  /* malloc and alloca.  */
+  ALLOC_FN_MALLOC,
+  /* C++ operator new.  */
+  ALLOC_FN_OPERATOR_NEW,
+  /* Any deallocation function (free, operator delete). */
+  ALLOC_FN_FREE
+};
+
 /* FUNCTION_DECL inherits from DECL_NON_COMMON because of the use of the
    arguments/result/saved_tree fields by front ends.   It was either inherit
    FUNCTION_DECL from non_common, or inherit non_common from FUNCTION_DECL,
@@ -1685,8 +1700,7 @@  struct GTY(()) tree_function_decl {
   unsigned possibly_inlined : 1;
   unsigned novops_flag : 1;
   unsigned returns_twice_flag : 1;
-  unsigned malloc_flag : 1;
-  unsigned operator_new_flag : 1;
+  unsigned alloc_fn : 2;
   unsigned declared_inline_flag : 1;
   unsigned no_inline_warning_flag : 1;
 
diff --git a/gcc/tree-ssa-alias.c b/gcc/tree-ssa-alias.c
index 08f10e5..26a3845 100644
--- a/gcc/tree-ssa-alias.c
+++ b/gcc/tree-ssa-alias.c
@@ -32,6 +32,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "tree-pretty-print.h"
 #include "alias.h"
 #include "fold-const.h"
+#include "calls.h"
 
 #include "langhooks.h"
 #include "dumpfile.h"
@@ -1728,6 +1729,15 @@  ref_maybe_used_by_call_p_1 (gcall *call, ao_ref *ref)
 	  /* Fallthru to general call handling.  */;
       }
 
+  /* free-like functions may not reference their first argument.  */
+  if (callee != NULL_TREE && (flags & ECF_FREE) != 0)
+    {
+      tree ptr = gimple_call_arg (call, 0);
+      tree base = ao_ref_base (ref);
+      if (base && TREE_CODE (base) == MEM_REF && TREE_OPERAND (base, 0) == ptr)
+	return false;
+    }
+
   /* Check if base is a global static variable that is not read
      by the function.  */
   if (callee != NULL_TREE
@@ -2117,6 +2127,13 @@  call_may_clobber_ref_p_1 (gcall *call, ao_ref *ref)
 	  /* Fallthru to general call handling.  */;
       }
 
+  if (callee != NULL_TREE
+      && (flags_from_decl_or_type (callee) & ECF_FREE) != 0)
+    {
+      tree ptr = gimple_call_arg (call, 0);
+      return ptr_deref_may_alias_ref_p_1 (ptr, ref);
+    }
+
   /* Check if base is a global static variable that is not written
      by the function.  */
   if (callee != NULL_TREE
@@ -2332,16 +2349,6 @@  stmt_kills_ref_p (gimple *stmt, ao_ref *ref)
 	  && gimple_call_builtin_p (stmt, BUILT_IN_NORMAL))
 	switch (DECL_FUNCTION_CODE (callee))
 	  {
-	  case BUILT_IN_FREE:
-	    {
-	      tree ptr = gimple_call_arg (stmt, 0);
-	      tree base = ao_ref_base (ref);
-	      if (base && TREE_CODE (base) == MEM_REF
-		  && TREE_OPERAND (base, 0) == ptr)
-		return true;
-	      break;
-	    }
-
 	  case BUILT_IN_MEMCPY:
 	  case BUILT_IN_MEMPCPY:
 	  case BUILT_IN_MEMMOVE:
@@ -2402,6 +2409,16 @@  stmt_kills_ref_p (gimple *stmt, ao_ref *ref)
 
 	  default:;
 	  }
+
+      if (callee != NULL_TREE
+	  && (flags_from_decl_or_type (callee) & ECF_FREE) != 0)
+	{
+	  tree ptr = gimple_call_arg (stmt, 0);
+	  tree base = ao_ref_base (ref);
+	  if (base && TREE_CODE (base) == MEM_REF
+	      && TREE_OPERAND (base, 0) == ptr)
+	    return true;
+	}
     }
   return false;
 }
diff --git a/gcc/tree-streamer-in.c b/gcc/tree-streamer-in.c
index 2ad2f92..4f01eac 100644
--- a/gcc/tree-streamer-in.c
+++ b/gcc/tree-streamer-in.c
@@ -322,8 +322,7 @@  unpack_ts_function_decl_value_fields (struct bitpack_d *bp, tree expr)
   DECL_POSSIBLY_INLINED (expr) = (unsigned) bp_unpack_value (bp, 1);
   DECL_IS_NOVOPS (expr) = (unsigned) bp_unpack_value (bp, 1);
   DECL_IS_RETURNS_TWICE (expr) = (unsigned) bp_unpack_value (bp, 1);
-  DECL_IS_MALLOC (expr) = (unsigned) bp_unpack_value (bp, 1);
-  DECL_IS_OPERATOR_NEW (expr) = (unsigned) bp_unpack_value (bp, 1);
+  DECL_ALLOC_FN_KIND (expr) = (alloc_fn_kind) bp_unpack_value (bp, 2);
   DECL_DECLARED_INLINE_P (expr) = (unsigned) bp_unpack_value (bp, 1);
   DECL_STATIC_CHAIN (expr) = (unsigned) bp_unpack_value (bp, 1);
   DECL_NO_INLINE_WARNING_P (expr) = (unsigned) bp_unpack_value (bp, 1);
diff --git a/gcc/tree-streamer-out.c b/gcc/tree-streamer-out.c
index c37755d..41acc1d 100644
--- a/gcc/tree-streamer-out.c
+++ b/gcc/tree-streamer-out.c
@@ -290,8 +290,7 @@  pack_ts_function_decl_value_fields (struct bitpack_d *bp, tree expr)
   bp_pack_value (bp, DECL_POSSIBLY_INLINED (expr), 1);
   bp_pack_value (bp, DECL_IS_NOVOPS (expr), 1);
   bp_pack_value (bp, DECL_IS_RETURNS_TWICE (expr), 1);
-  bp_pack_value (bp, DECL_IS_MALLOC (expr), 1);
-  bp_pack_value (bp, DECL_IS_OPERATOR_NEW (expr), 1);
+  bp_pack_value (bp, DECL_ALLOC_FN_KIND (expr), 2);
   bp_pack_value (bp, DECL_DECLARED_INLINE_P (expr), 1);
   bp_pack_value (bp, DECL_STATIC_CHAIN (expr), 1);
   bp_pack_value (bp, DECL_NO_INLINE_WARNING_P (expr), 1);
diff --git a/gcc/tree.c b/gcc/tree.c
index 6de46a8..527d0ac 100644
--- a/gcc/tree.c
+++ b/gcc/tree.c
@@ -10335,7 +10335,9 @@  set_call_expr_flags (tree decl, int flags)
   if (flags & ECF_NORETURN)
     TREE_THIS_VOLATILE (decl) = 1;
   if (flags & ECF_MALLOC)
-    DECL_IS_MALLOC (decl) = 1;
+    DECL_ALLOC_FN_KIND (decl) = ALLOC_FN_MALLOC;
+  else if (flags & ECF_FREE)
+    DECL_ALLOC_FN_KIND (decl) = ALLOC_FN_FREE;
   if (flags & ECF_RETURNS_TWICE)
     DECL_IS_RETURNS_TWICE (decl) = 1;
   if (flags & ECF_LEAF)
diff --git a/gcc/tree.h b/gcc/tree.h
index 33833a7..ebfbb53 100644
--- a/gcc/tree.h
+++ b/gcc/tree.h
@@ -2789,17 +2789,34 @@  extern void decl_fini_priority_insert (tree, priority_type);
 #define DECL_SAVED_TREE(NODE) \
   (FUNCTION_DECL_CHECK (NODE)->function_decl.saved_tree)
 
+/* In a FUNCTION_DECL, non-zero means that this function is related to
+   memory allocation, one of: ALLOC_FN_MALLOC, ALLOC_FN_OPERATOR_NEW and
+   ALLOC_FN_FREE.  The semantics of each kind is described below.  */
+#define DECL_ALLOC_FN_KIND(NODE) \
+  (FUNCTION_DECL_CHECK (NODE)->function_decl.alloc_fn)
+
 /* Nonzero in a FUNCTION_DECL means this function should be treated
    as if it were a malloc, meaning it returns a pointer that is
    not an alias.  */
 #define DECL_IS_MALLOC(NODE) \
-  (FUNCTION_DECL_CHECK (NODE)->function_decl.malloc_flag)
+  (FUNCTION_DECL_CHECK (NODE)->function_decl.alloc_fn == ALLOC_FN_MALLOC \
+   || FUNCTION_DECL_CHECK (NODE)->function_decl.alloc_fn == ALLOC_FN_OPERATOR_NEW)
+
+/* Mark the function as malloc-like.  */
+#define DECL_SET_MALLOC(NODE) \
+  (FUNCTION_DECL_CHECK (NODE)->function_decl.alloc_fn = ALLOC_FN_MALLOC)
 
 /* Nonzero in a FUNCTION_DECL means this function should be treated as
    C++ operator new, meaning that it returns a pointer for which we
    should not use type based aliasing.  */
 #define DECL_IS_OPERATOR_NEW(NODE) \
-  (FUNCTION_DECL_CHECK (NODE)->function_decl.operator_new_flag)
+  (FUNCTION_DECL_CHECK (NODE)->function_decl.alloc_fn == ALLOC_FN_OPERATOR_NEW)
+
+/* Nonzero in a FUNCTION_DECL means this function should be treated as
+   if it were free or C++ operator delete (first parameter is the object being
+   freed) for the purpose of DSE.  */
+#define DECL_IS_FREE(NODE) \
+  (FUNCTION_DECL_CHECK (NODE)->function_decl.alloc_fn == ALLOC_FN_FREE)
 
 /* Nonzero in a FUNCTION_DECL means this function may return more
    than once.  */