https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109442

--- Comment #7 from Jonathan Wakely <redi at gcc dot gnu.org> ---
(In reply to Richard Biener from comment #6)
> (In reply to Jonathan Wakely from comment #5)
> > (In reply to Richard Biener from comment #4)
> > > (In reply to Jonathan Wakely from comment #3)
> > > > Ah, maybe the problem is that the library code manually elides 
> > > > destroying
> > > > the elements, precisely because it's a no-op. So we don't actually 
> > > > destroy
> > > > the elements, which means the compiler might think they're still 
> > > > initialized
> > > > and so could be inspected.
> > > > 
> > > > If the library explicitly does vec[i].~T() for every i then would that 
> > > > help?
> > > > The compiler would know there are no valid elements in the storage, and 
> > > > so
> > > > nothing operator delete could inspect.
> > > > 
> > > > We could continue to elide destroying the elements when !defined
> > > > __OPTIMIZE__ so that we don't run a loop that does nothing, but with
> > > > optimization enabled rely on the compiler to remove that loop.
> > > 
> > > I don't think that would help.  The issue is the compiler thinks that
> > > 
> > > operator delete (_37, _49);
> > > 
> > > uses the memory at _37 and thus the stores
> > > 
> > > *_37 = _24;
> > > 
> > > and
> > > 
> > > __builtin_memmove (_37, pretmp_63, _23);
> > > 
> > > are not dead.
> > 
> > But if the library did _37->~_Tp() to destroy the element at _37 then it
> > would be dead, and accessing the element outside its lifetime would be
> > undefined. The bytes can only be accessed as char, unsigned char or
> > std::byte after that.
> > 
> > > IIRC 'operator delete' (_ZdlPvm in this case), can be
> > > overridden by the user and can inspect the memory state before "releasing"
> > > the storage?
> > 
> > That would be insane for operator delete to do that. Maybe possible, but
> > insane.
> 
> Well, the standard doesn't prohibit "insanity" so we have to generate
> correct code even in those cases, no? 

Indeed.

> After all this is what PR101480
> was about ("insane" stuff) done even to new/delete _expressions_.
> 
> Would it be correct (by the standards wording) to extend the current
> handling of new/delete expressions - which is that 'new' returns a pointer
> not aliasing to any other pointer and that 'delete' does not read from
> the memory pointed to by the first argument and this pointer also does not
> escape to direct calls of operator new/delete?

No, I don't think so. The [expr.new] p13 permission to elide calls to operator
new seems to only apply to new expressions and not direct calls to operator
new.


> Basically in gimple.cc:gimple_call_fnspec we currenty do
> 
>   /* 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 we can treat it as free.  */
>   if (fndecl
>       && DECL_IS_OPERATOR_DELETE_P (fndecl)
>       && DECL_IS_REPLACEABLE_OPERATOR (fndecl)
>       && gimple_call_from_new_or_delete (stmt))
>     return ". o ";
>   /* Similarly operator new can be treated as malloc.  */
>   if (fndecl
>       && DECL_IS_REPLACEABLE_OPERATOR_NEW_P (fndecl)
>       && gimple_call_from_new_or_delete (stmt))
>     return "m ";
> 
> where the comments do not exactly match the behavior (we also allow
> arbitrary side-effects on global memory).  That we model 'delete'
> as possibly writing to the released storage is so it serves as barrier for
> earlier loads/stores to avoid sinking them across.
> 
> > In any case, the stores to _37 would be dead now, right? So even if operator
> > delete inspects the memory as raw bytes using memcmp or similar, it's
> > reading uninitialized storage, so any value is acceptable. So the stores to
> > _37 should be DSE-able now.
> 
> There's noting in the IL indicating that though (but I haven't updated
> libstd++ a while to create a updated preprocessed source - will do so now).

I'm talking about a potential change to libstdc++ to add _37->~_Tp() so you
won't see it in the IL now.

What I meant in comment 3 is that libstdc++ uses template metaprogramming to
manually elide destroying the elements, because trivial types like 'int' have
no destructor. See libstdc++-v3/include/bits/stl_construct.h lines 155-197:

  template<bool>
    struct _Destroy_aux
    {
      template<typename _ForwardIterator>
        static _GLIBCXX20_CONSTEXPR void
        __destroy(_ForwardIterator __first, _ForwardIterator __last)
        {
          for (; __first != __last; ++__first)
            std::_Destroy(std::__addressof(*__first));
        }
    };

  template<>
    struct _Destroy_aux<true>
    {
      template<typename _ForwardIterator>
        static void
        __destroy(_ForwardIterator, _ForwardIterator) { }
    };

For vector<int> we use the _Destroy_aux<true> specialization, which is a no-op.
So the compiler thinks that the elements of the std::vector<int> are still
alive when we free the storage.

I'm suggesting that if we actually ended the lifetime of the int elements, then
the compiler would see that any stores to _37 are now dead, because we clobber
them before calling operator delete.

e.g. something like:

--- a/libstdc++-v3/include/bits/stl_construct.h
+++ b/libstdc++-v3/include/bits/stl_construct.h
@@ -181,6 +181,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     _GLIBCXX20_CONSTEXPR inline void
     _Destroy(_ForwardIterator __first, _ForwardIterator __last)
     {
+#ifdef __OPTIMIZE__
+      // Rely on the compiler to optimize the loop away for trivial types.
+      return _Destroy_aux<false>::__destroy(__first, __last);
+#else
       typedef typename iterator_traits<_ForwardIterator>::value_type
                        _Value_type;
 #if __cplusplus >= 201103L
@@ -194,6 +198,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 #endif
       std::_Destroy_aux<__has_trivial_destructor(_Value_type)>::
        __destroy(__first, __last);
+#endif
     }

   template<bool>

But this doesn't change the generated code ¯\_(ツ)_/¯


> OK, then how do we arrange things so the compiler knows it can completely
> elide the unused std::vector?  Or do you say it's not valid to do that?

I think it's valid in theory. I don't know if it's possible for GCC to do it in
practice. There doesn't seem to be anything the library can do to help, so
WONTFIX.

Reply via email to