On 10/31/19 1:10 PM, Jakub Jelinek wrote:
On Wed, Oct 30, 2019 at 11:05:29PM +0100, Jakub Jelinek wrote:
Looks like there used to be a TREE_CALLS_NEW flag in TREE_LANG_FLAG_1, but
that flag is now free for CALL_EXPR.

I'll try a CALL_EXPR flag first.

TREE_LANG_FLAG_1 is also STMT_IS_FULL_EXPR_P, and while it is mostly guarded
with STATEMENT_CODE_P checks, in add_stmt it is not.  Are we sure that we
never add_stmt a CALL_EXPR?

I wouldn't think so, but we might add the check there too.

I've picked up TREE_LANG_FLAG_2 instead which looked unused on CALL_EXPR.

OK.

Below is the patch that rejects ::operator new and ::operator delete
calls used directly by users (with the STL exteption), but doesn't try to
diagnose what is discussed below.
Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?

OK.

Another thing is that even with that change,
    std::allocator<int> a;
    auto p = a.allocate (1);
    *p = 1;
    a.deallocate (p, 1);
would be accepted during constexpr evaluation, because allocate already
has the cast which turns "heap uninit" variable into "heap " and assigns
it a type, so there is nothing that will prevent the store from succeeding.

What's wrong with the store?

If it is fine, the better.  I'd still think that
   struct S { constexpr S () : s (0) {}; int s; };
   std::allocator<S> a;
   auto p = a.allocate (1);
   p->s = 1;
   a.deallocate (p, 1);
would still be invalid though, because that type needs constructing and
the construction didn't happen in that case.  Or:
   struct S { constexpr S () : s (0) {}; int s; };
   std::allocator<S> a;
   auto p = a.allocate (1);
   std::construct_at (p);
   p->s++; // ok
   std::destruct_at (p);
   p->s = 1; // shouldn't this be an error?
   a.deallocate (p, 1);
but I admit I haven't tried to back that up by some standard wording, just a
general principle that objects with TYPE_ADDRESSABLE types need to be
constructed first and destructed last and accesses to it are only valid in
between those in normal code and constexpr should flag any UB as errors.

So, are objects in the heap with vacuous initialization ok and objects with
non-trivial constructors not ok without std::construct_at to begin the
lifetime?

Correct.

What about objects with trivial constructor but non-trivial constexpr 
destructor, is
it ok to set them after std::destruct_at or direct p->~S (); invocation
without an intervening std::construct_at?

That would seem to follow.

Also, shouldn't it be invalid if objects with non-trivial destructor are
constructed using std::construct_at but not destructed before deallocate?

It doesn't seem so: "For an object of a class type, the program is not required to call the destructor explicitly before the storage which the object occupies is reused or released; however, if there is no explicit call to the destructor or if a delete-expression is not used to release the storage, the destructor is not implicitly called and any program that depends on the side effects produced by the destructor has undefined behavior."

2019-10-31  Jakub Jelinek  <ja...@redhat.com>

        PR c++/91369 - Implement P0784R7: constexpr new
        * cp-tree.h (CALL_FROM_NEW_OR_DELETE_P): Define.
        * init.c (build_new_1, build_vec_delete_1, build_delete): Set
        CALL_FROM_NEW_OR_DELETE_P on the CALL_EXPR to allocator functions.
        * constexpr.c (is_std_allocator_allocate): Only allow
        global replaceable allocator functions if CALL_FROM_NEW_OR_DELETE_P
        or in std::allocate<T>::{,de}allocate.
        (potential_constant_expression_1): Likewise.

        * g++.dg/cpp2a/constexpr-new6.C: New test.
        * g++.dg/cpp2a/constexpr-new7.C: New test.

--- gcc/cp/cp-tree.h.jj 2019-10-31 08:10:14.515368058 +0100
+++ gcc/cp/cp-tree.h    2019-10-31 09:00:45.798418129 +0100
@@ -448,6 +448,7 @@ extern GTY(()) tree cp_global_trees[CPTI
        LAMBDA_EXPR_CAPTURE_OPTIMIZED (in LAMBDA_EXPR)
        IMPLICIT_CONV_EXPR_BRACED_INIT (in IMPLICIT_CONV_EXPR)
        TINFO_VAR_DECLARED_CONSTINIT (in TEMPLATE_INFO)
+      CALL_FROM_NEW_OR_DELETE_P (in CALL_EXPR)
     3: (TREE_REFERENCE_EXPR) (in NON_LVALUE_EXPR) (commented-out).
        ICS_BAD_FLAG (in _CONV)
        FN_TRY_BLOCK_P (in TRY_BLOCK)
@@ -3791,6 +3792,11 @@ 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) \
--- gcc/cp/init.c.jj    2019-10-05 09:36:39.249674512 +0200
+++ gcc/cp/init.c       2019-10-31 09:25:11.870698471 +0100
@@ -3404,6 +3404,10 @@ build_new_1 (vec<tree, va_gc> **placemen
        }
      }
+ tree alloc_call_expr = extract_call_expr (alloc_call);
+  if (TREE_CODE (alloc_call_expr) == CALL_EXPR)
+    CALL_FROM_NEW_OR_DELETE_P (alloc_call_expr) = 1;
+
    if (cookie_size)
      alloc_call = maybe_wrap_new_for_constexpr (alloc_call, elt_type,
                                               cookie_size);
@@ -4046,6 +4050,10 @@ build_vec_delete_1 (tree base, tree maxi
                                              /*placement=*/NULL_TREE,
                                              /*alloc_fn=*/NULL_TREE,
                                              complain);
+
+      tree deallocate_call_expr = extract_call_expr (deallocate_expr);
+      if (TREE_CODE (deallocate_call_expr) == CALL_EXPR)
+       CALL_FROM_NEW_OR_DELETE_P (deallocate_call_expr) = 1;
      }
body = loop;
@@ -4955,6 +4963,13 @@ build_delete (tree otype, tree addr, spe
    if (!deleting)
      return expr;
+ if (do_delete)
+    {
+      tree do_delete_call_expr = extract_call_expr (do_delete);
+      if (TREE_CODE (do_delete_call_expr) == CALL_EXPR)
+       CALL_FROM_NEW_OR_DELETE_P (do_delete_call_expr) = 1;
+    }
+
    if (do_delete && !TREE_SIDE_EFFECTS (expr))
      expr = do_delete;
    else if (do_delete)
--- gcc/cp/constexpr.c.jj       2019-10-30 22:54:29.574952119 +0100
+++ gcc/cp/constexpr.c  2019-10-31 10:12:27.074761211 +0100
@@ -1638,6 +1638,28 @@ is_std_construct_at (tree fndecl)
    return name && id_equal (name, "construct_at");
  }
+/* Return true if FNDECL is std::allocator<T>::{,de}allocate. */
+
+static inline bool
+is_std_allocator_allocate (tree fndecl)
+{
+  tree name = DECL_NAME (fndecl);
+  if (name == NULL_TREE
+      || !(id_equal (name, "allocate") || id_equal (name, "deallocate")))
+    return false;
+
+  tree ctx = DECL_CONTEXT (fndecl);
+  if (ctx == NULL_TREE || !CLASS_TYPE_P (ctx) || !TYPE_MAIN_DECL (ctx))
+    return false;
+
+  tree decl = TYPE_MAIN_DECL (ctx);
+  name = DECL_NAME (decl);
+  if (name == NULL_TREE || !id_equal (name, "allocator"))
+    return false;
+
+  return decl_in_std_namespace_p (decl);
+}
+
  /* Subroutine of cxx_eval_constant_expression.
     Evaluate the call expression tree T in the context of OLD_CALL expression
     evaluation.  */
@@ -1716,7 +1738,12 @@ cxx_eval_call_expression (const constexp
                                           lval, non_constant_p, overflow_p);
    if (!DECL_DECLARED_CONSTEXPR_P (fun))
      {
-      if (cxx_replaceable_global_alloc_fn (fun))
+      if (TREE_CODE (t) == CALL_EXPR
+         && cxx_replaceable_global_alloc_fn (fun)
+         && (CALL_FROM_NEW_OR_DELETE_P (t)
+             || (ctx->call
+                 && ctx->call->fundef
+                 && is_std_allocator_allocate (ctx->call->fundef->decl))))
        {
          const int nargs = call_expr_nargs (t);
          tree arg0 = NULL_TREE;
@@ -1774,7 +1801,8 @@ cxx_eval_call_expression (const constexp
        }
        /* Allow placement new in std::construct_at, just return the second
         argument.  */
-      if (cxx_placement_new_fn (fun)
+      if (TREE_CODE (t) == CALL_EXPR
+         && cxx_placement_new_fn (fun)
          && ctx->call
          && ctx->call->fundef
          && is_std_construct_at (ctx->call->fundef->decl))
@@ -6508,9 +6536,15 @@ potential_constant_expression_1 (tree t,
                    && !fndecl_built_in_p (fun)
                    /* In C++2a, replaceable global allocation functions
                       are constant expressions.  */
-                   && !cxx_replaceable_global_alloc_fn (fun)
+                   && (!cxx_replaceable_global_alloc_fn (fun)
+                       || TREE_CODE (t) != CALL_EXPR
+                       || (!CALL_FROM_NEW_OR_DELETE_P (t)
+                           && (current_function_decl == NULL_TREE
+                               || !is_std_allocator_allocate
+                                               (current_function_decl))))
                    /* Allow placement new in std::construct_at.  */
                    && (!cxx_placement_new_fn (fun)
+                       || TREE_CODE (t) != CALL_EXPR
                        || current_function_decl == NULL_TREE
                        || !is_std_construct_at (current_function_decl)))
                  {
--- gcc/testsuite/g++.dg/cpp2a/constexpr-new6.C.jj      2019-10-31 
09:53:48.606090420 +0100
+++ gcc/testsuite/g++.dg/cpp2a/constexpr-new6.C 2019-10-31 10:59:59.168577427 
+0100
@@ -0,0 +1,83 @@
+// P0784R7
+// { dg-do compile { target c++2a } }
+
+namespace std
+{
+  inline namespace _8 { }
+  namespace _8 {
+
+    typedef __SIZE_TYPE__ size_t;
+
+    template <typename T>
+    struct allocator
+    {
+      constexpr allocator () noexcept {}
+
+      constexpr T *allocate (size_t n)
+      { return static_cast<T *> (::operator new (n * sizeof(T))); }
+
+      constexpr void
+      deallocate (T *p, size_t n)
+      { ::operator delete (p); }
+    };
+
+    template <typename T, typename U = T &&>
+    U __declval (int);
+    template <typename T>
+    T __declval (long);
+    template <typename T>
+    auto declval () noexcept -> decltype (__declval<T> (0));
+
+    template <typename T>
+    struct remove_reference
+    { typedef T type; };
+    template <typename T>
+    struct remove_reference<T &>
+    { typedef T type; };
+    template <typename T>
+    struct remove_reference<T &&>
+    { typedef T type; };
+
+    template <typename T>
+    constexpr T &&
+    forward (typename std::remove_reference<T>::type &t) noexcept
+    { return static_cast<T&&> (t); }
+
+    template<typename T>
+    constexpr T &&
+    forward (typename std::remove_reference<T>::type &&t) noexcept
+    { return static_cast<T&&> (t); }
+
+    template <typename T, typename... A>
+    constexpr auto
+    construct_at (T *l, A &&... a)
+    noexcept (noexcept (::new ((void *) 0) T (std::declval<A> ()...)))
+    -> decltype (::new ((void *) 0) T (std::declval<A> ()...))
+    { return ::new ((void *) l) T (std::forward<A> (a)...); }
+
+    template <typename T>
+    constexpr inline void
+    destroy_at (T *l)
+    { l->~T (); }
+  }
+}
+
+inline void *operator new (std::size_t, void *p) noexcept
+{ return p; }
+
+constexpr bool
+foo ()
+{
+  std::allocator<int> a;
+  auto p = a.allocate (2);
+  std::construct_at (p, 1);
+  std::construct_at (p + 1, 2);
+  if (p[0] != 1 || p[1] != 2)
+    throw 1;
+  std::destroy_at (p);
+  std::destroy_at (p + 1);
+  a.deallocate (p, 2);
+  return true;
+}
+
+static_assert (foo ());
--- gcc/testsuite/g++.dg/cpp2a/constexpr-new7.C.jj      2019-10-31 
10:58:33.227908692 +0100
+++ gcc/testsuite/g++.dg/cpp2a/constexpr-new7.C 2019-10-31 10:14:10.702155762 
+0100
@@ -0,0 +1,33 @@
+// P0784R7
+// { dg-do compile { target c++2a } }
+
+namespace std
+{
+  typedef __SIZE_TYPE__ size_t;
+}
+
+inline void *operator new (std::size_t, void *p) noexcept
+{ return p; }
+void *operator new (std::size_t) noexcept;
+
+constexpr bool
+foo ()
+{
+  auto p = static_cast<int *> (::operator new (sizeof (int)));   // { dg-error 
"call to non-'constexpr' function" }
+  *p = 1;
+  ::operator delete (p);
+  return false;
+}
+
+struct S { constexpr S () : s (0) {} int s; };
+
+constexpr bool
+bar ()
+{
+  auto p = static_cast<S *> (::operator new (sizeof (S)));       // { dg-error 
"call to non-'constexpr' function" }
+  auto q = new (p) S ();
+  q->s++;
+  q->~S ();
+  ::operator delete (p);
+  return false;
+}


        Jakub


Reply via email to