c++: Support C++20 virtual consteval functions. [PR88335]
authorJason Merrill <jason@redhat.com>
Thu, 25 Jun 2020 00:46:09 +0000 (20:46 -0400)
committerJason Merrill <jason@redhat.com>
Thu, 2 Jul 2020 17:20:23 +0000 (13:20 -0400)
Jakub's partial implementation of consteval virtual had trouble with the
current ABI requirement that we omit the vtable slot for a consteval virtual
function; it's difficult to use the normal code for constant evaluation and
also magically make the slots disappear if the vtables get written out.  I
notice that Clang trunk also doesn't implement that requirement, and it
seems unnecessary to me; I expect consteval virtual functions to be
extremely rare, so it should be fine to just give them a vtable slot as
normal but put zero in it if the vtable gets emitted.  I've commented as
much to the ABI committee.

One of Jakub's testcases points out that we weren't handling thunks in
our constexpr virtual handling; that is fixed here as well.

Incidentally, being able to use C++11 range-for definitely simplified
clear_consteval_vfns.

gcc/c-family/ChangeLog:

* c-cppbuiltin.c (c_cpp_builtins): Define __cpp_consteval.

gcc/cp/ChangeLog:

* decl.c (grokfndecl): Allow consteval virtual.
* search.c (check_final_overrider): Check consteval mismatch.
* constexpr.c (cxx_eval_thunk_call): New.
(cxx_eval_call_expression): Call it.
* cvt.c (cp_get_fndecl_from_callee): Handle FDESC_EXPR.
* decl2.c (mark_vtable_entries): Track vtables with consteval.
(maybe_emit_vtables): Pass consteval_vtables through.
(clear_consteval_vfns): Replace consteval with nullptr.
(c_parse_final_cleanups): Call it.

gcc/testsuite/ChangeLog:

* g++.dg/cpp2a/consteval-virtual1.C: New test.
* g++.dg/cpp2a/consteval-virtual2.C: New test.
* g++.dg/cpp2a/consteval-virtual3.C: New test.
* g++.dg/cpp2a/consteval-virtual4.C: New test.
* g++.dg/cpp2a/consteval-virtual5.C: New test.

Co-authored-by: Jakub Jelinek <jakub@redhat.com>
gcc/c-family/c-cppbuiltin.c
gcc/cp/constexpr.c
gcc/cp/cvt.c
gcc/cp/decl.c
gcc/cp/decl2.c
gcc/cp/search.c
gcc/testsuite/g++.dg/cpp2a/consteval-virtual1.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/consteval-virtual2.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/consteval-virtual3.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/consteval-virtual4.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/consteval-virtual5.C [new file with mode: 0644]

index a7d65d63934bdd77747344a6d27eecca3ad36af5..83f52fdf5d872a250aef5fdbad51510188e0ca47 100644 (file)
@@ -995,7 +995,7 @@ c_cpp_builtins (cpp_reader *pfile)
          cpp_define (pfile, "__cpp_constexpr=201907L");
          cpp_define (pfile, "__cpp_constexpr_in_decltype=201711L");
          cpp_define (pfile, "__cpp_conditional_explicit=201806L");
-         /* cpp_define (pfile, "__cpp_consteval=201811L"); */
+         cpp_define (pfile, "__cpp_consteval=201811L");
          cpp_define (pfile, "__cpp_constinit=201907L");
          cpp_define (pfile, "__cpp_deduction_guides=201907L");
          cpp_define (pfile, "__cpp_nontype_template_parameter_class=201806L");
index f766abd3a11d4e534007a5afe34c6adc6f783a62..1939166e90792219a2564ddb26913de06a9388d8 100644 (file)
@@ -2129,6 +2129,52 @@ replace_result_decl (tree *tp, tree decl, tree replacement)
   return data.changed;
 }
 
+/* Evaluate the call T to virtual function thunk THUNK_FNDECL.  */
+
+static tree
+cxx_eval_thunk_call (const constexpr_ctx *ctx, tree t, tree thunk_fndecl,
+                    bool lval,
+                    bool *non_constant_p, bool *overflow_p)
+{
+  tree function = THUNK_TARGET (thunk_fndecl);
+
+  /* virtual_offset is only set in the presence of virtual bases, which make
+     the class non-literal, so we don't need to handle it here.  */
+  if (THUNK_VIRTUAL_OFFSET (thunk_fndecl))
+    {
+      gcc_assert (!DECL_DECLARED_CONSTEXPR_P (function));
+      if (!ctx->quiet)
+       {
+         error ("call to non-%<constexpr%> function %qD", function);
+         explain_invalid_constexpr_fn (function);
+       }
+      *non_constant_p = true;
+      return t;
+    }
+
+  tree new_call = copy_node (t);
+  CALL_EXPR_FN (new_call) = function;
+  TREE_TYPE (new_call) = TREE_TYPE (TREE_TYPE (function));
+
+  tree offset = size_int (THUNK_FIXED_OFFSET (thunk_fndecl));
+
+  if (DECL_THIS_THUNK_P (thunk_fndecl))
+    {
+      /* 'this'-adjusting thunk.  */
+      tree this_arg = CALL_EXPR_ARG (t, 0);
+      this_arg = build2 (POINTER_PLUS_EXPR, TREE_TYPE (this_arg),
+                        this_arg, offset);
+      CALL_EXPR_ARG (new_call, 0) = this_arg;
+    }
+  else
+    /* Return-adjusting thunk.  */
+    new_call = build2 (POINTER_PLUS_EXPR, TREE_TYPE (new_call),
+                      new_call, offset);
+
+  return cxx_eval_constant_expression (ctx, new_call, lval,
+                                      non_constant_p, overflow_p);
+}
+
 /* Subroutine of cxx_eval_constant_expression.
    Evaluate the call expression tree T in the context of OLD_CALL expression
    evaluation.  */
@@ -2209,6 +2255,8 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
   if (fndecl_built_in_p (fun))
     return cxx_eval_builtin_function_call (ctx, t, fun,
                                           lval, non_constant_p, overflow_p);
+  if (DECL_THUNK_P (fun))
+    return cxx_eval_thunk_call (ctx, t, fun, lval, non_constant_p, overflow_p);
   if (!DECL_DECLARED_CONSTEXPR_P (fun))
     {
       if (TREE_CODE (t) == CALL_EXPR
index 371002833d0de321a0a90f20f6e5d8247d57b3c7..c9e7b1ff04415d142a67e0fd1fdf8a73eff0cf43 100644 (file)
@@ -1000,12 +1000,11 @@ cp_get_fndecl_from_callee (tree fn, bool fold /* = true */)
   if (fold)
     fn = maybe_constant_init (fn);
   STRIP_NOPS (fn);
-  if (TREE_CODE (fn) == ADDR_EXPR)
-    {
-      fn = TREE_OPERAND (fn, 0);
-      if (TREE_CODE (fn) == FUNCTION_DECL)
-       return fn;
-    }
+  if (TREE_CODE (fn) == ADDR_EXPR
+      || TREE_CODE (fn) == FDESC_EXPR)
+    fn = TREE_OPERAND (fn, 0);
+  if (TREE_CODE (fn) == FUNCTION_DECL)
+    return fn;
   return NULL_TREE;
 }
 
index 45c871af7415e86d722a723be72d47117f15c309..1eb5c2a29ac48998d81a64fb3aa55478442311f5 100644 (file)
@@ -9560,15 +9560,6 @@ grokfndecl (tree ctype,
          }
     }
 
-  /* FIXME: For now.  */
-  if (virtualp && (inlinep & 8) != 0)
-    {
-      sorry_at (DECL_SOURCE_LOCATION (decl),
-               "%<virtual%> %<consteval%> method %qD not supported yet",
-               decl);
-      inlinep &= ~8;
-    }
-
   /* If this decl has namespace scope, set that up.  */
   if (in_namespace)
     set_decl_namespace (decl, in_namespace, friendp);
index 93e3034045404b5453805957f45214d4151bb061..ddc2023a75bed8fe18a4bce5e63f45baf171968e 100644 (file)
@@ -65,8 +65,6 @@ typedef struct priority_info_s {
   int destructions_p;
 } *priority_info;
 
-static void mark_vtable_entries (tree);
-static bool maybe_emit_vtables (tree);
 static tree start_objects (int, int);
 static void finish_objects (int, int, tree);
 static tree start_static_storage_duration_function (unsigned);
@@ -1879,7 +1877,7 @@ coerce_delete_type (tree decl, location_t loc)
    and mark them as needed.  */
 
 static void
-mark_vtable_entries (tree decl)
+mark_vtable_entries (tree decl, vec<tree> &consteval_vtables)
 {
   tree fnaddr;
   unsigned HOST_WIDE_INT idx;
@@ -1887,6 +1885,8 @@ mark_vtable_entries (tree decl)
   /* It's OK for the vtable to refer to deprecated virtual functions.  */
   warning_sentinel w(warn_deprecated_decl);
 
+  bool consteval_seen = false;
+
   FOR_EACH_CONSTRUCTOR_VALUE (CONSTRUCTOR_ELTS (DECL_INITIAL (decl)),
                              idx, fnaddr)
     {
@@ -1901,6 +1901,15 @@ mark_vtable_entries (tree decl)
        continue;
 
       fn = TREE_OPERAND (fnaddr, 0);
+      if (TREE_CODE (fn) == FUNCTION_DECL && DECL_IMMEDIATE_FUNCTION_P (fn))
+       {
+         if (!consteval_seen)
+           {
+             consteval_seen = true;
+             consteval_vtables.safe_push (decl);
+           }
+         continue;
+       }
       TREE_ADDRESSABLE (fn) = 1;
       /* When we don't have vcall offsets, we output thunks whenever
         we output the vtables that contain them.  With vcall offsets,
@@ -1917,6 +1926,20 @@ mark_vtable_entries (tree decl)
     }
 }
 
+/* Replace any consteval functions in vtables with null pointers.  */
+
+static void
+clear_consteval_vfns (vec<tree> &consteval_vtables)
+{
+  for (tree vtable : consteval_vtables)
+    for (constructor_elt &elt : *CONSTRUCTOR_ELTS (DECL_INITIAL (vtable)))
+      {
+       tree fn = cp_get_fndecl_from_callee (elt.value, /*fold*/false);
+       if (fn && DECL_IMMEDIATE_FUNCTION_P (fn))
+         elt.value = build_zero_cst (vtable_entry_type);
+      }
+}
+
 /* Adjust the TLS model on variable DECL if need be, typically after
    the linkage of DECL has been modified.  */
 
@@ -2228,7 +2251,7 @@ decl_needed_p (tree decl)
    Returns true if any vtables were emitted.  */
 
 static bool
-maybe_emit_vtables (tree ctype)
+maybe_emit_vtables (tree ctype, vec<tree> &consteval_vtables)
 {
   tree vtbl;
   tree primary_vtbl;
@@ -2273,7 +2296,7 @@ maybe_emit_vtables (tree ctype)
   for (vtbl = CLASSTYPE_VTABLES (ctype); vtbl; vtbl = DECL_CHAIN (vtbl))
     {
       /* Mark entities references from the virtual table as used.  */
-      mark_vtable_entries (vtbl);
+      mark_vtable_entries (vtbl, consteval_vtables);
 
       if (TREE_TYPE (DECL_INITIAL (vtbl)) == 0)
        {
@@ -4887,6 +4910,9 @@ c_parse_final_cleanups (void)
 
   emit_support_tinfos ();
 
+  /* Track vtables we want to emit that refer to consteval functions.  */
+  auto_vec<tree> consteval_vtables;
+
   do
     {
       tree t;
@@ -4906,7 +4932,7 @@ c_parse_final_cleanups (void)
         have to look at it again.  */
       for (i = keyed_classes->length ();
           keyed_classes->iterate (--i, &t);)
-       if (maybe_emit_vtables (t))
+       if (maybe_emit_vtables (t, consteval_vtables))
          {
            reconsider = true;
            keyed_classes->unordered_remove (i);
@@ -5177,6 +5203,7 @@ c_parse_final_cleanups (void)
   perform_deferred_noexcept_checks ();
 
   fini_constexpr ();
+  clear_consteval_vfns (consteval_vtables);
 
   /* The entire file is now complete.  If requested, dump everything
      to a file.  */
index a1a45a5ee6b9ededc995b5b315074e2f0af02da7..e36a8aed8f852916d57548ddf00547b988cebff0 100644 (file)
@@ -1958,20 +1958,13 @@ check_final_overrider (tree overrider, tree basefn)
     /* OK */;
   else
     {
+      auto_diagnostic_group d;
       if (fail == 1)
-       {
-         auto_diagnostic_group d;
-         error ("invalid covariant return type for %q+#D", overrider);
-         inform (DECL_SOURCE_LOCATION (basefn),
-                 "overridden function is %q#D", basefn);
-       }
+       error ("invalid covariant return type for %q+#D", overrider);
       else
-       {
-         auto_diagnostic_group d;
-         error ("conflicting return type specified for %q+#D", overrider);
-         inform (DECL_SOURCE_LOCATION (basefn),
-                 "overridden function is %q#D", basefn);
-       }
+       error ("conflicting return type specified for %q+#D", overrider);
+      inform (DECL_SOURCE_LOCATION (basefn),
+             "overridden function is %q#D", basefn);
       DECL_INVALID_OVERRIDER_P (overrider) = 1;
       return 0;
     }
@@ -1993,6 +1986,25 @@ check_final_overrider (tree overrider, tree basefn)
       return 0;
     }
 
+  /* A consteval virtual function shall not override a virtual function that is
+     not consteval. A consteval virtual function shall not be overridden by a
+     virtual function that is not consteval.  */
+  if (DECL_IMMEDIATE_FUNCTION_P (overrider)
+      != DECL_IMMEDIATE_FUNCTION_P (basefn))
+    {
+      auto_diagnostic_group d;
+      if (DECL_IMMEDIATE_FUNCTION_P (overrider))
+       error ("%<consteval%> function %q+D overriding non-%<consteval%> "
+              "function", overrider);
+      else
+       error ("non-%<consteval%> function %q+D overriding %<consteval%> "
+              "function", overrider);
+      inform (DECL_SOURCE_LOCATION (basefn),
+             "overridden function is %qD", basefn);
+      DECL_INVALID_OVERRIDER_P (overrider) = 1;
+      return 0;
+    }
+
   /* A function declared transaction_safe_dynamic that overrides a function
      declared transaction_safe (but not transaction_safe_dynamic) is
      ill-formed.  */
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-virtual1.C b/gcc/testsuite/g++.dg/cpp2a/consteval-virtual1.C
new file mode 100644 (file)
index 0000000..5cdb75a
--- /dev/null
@@ -0,0 +1,12 @@
+// { dg-do compile { target c++20 } }
+
+struct S {
+  virtual int foo () { return 42; }            // { dg-message "overridden function is 'virtual int S::foo\\\(\\\)'" }
+  consteval virtual int bar () { return 43; }  // { dg-message "overridden function is 'virtual consteval int S::bar\\\(\\\)'" }
+};
+struct T : public S {
+  int bar () { return 44; }    // { dg-error "non-'consteval' function 'virtual int T::bar\\\(\\\)' overriding 'consteval' function" }
+};
+struct U : public S {
+  consteval virtual int foo () { return 45; }  // { dg-error "'consteval' function 'virtual consteval int U::foo\\\(\\\)' overriding non-'consteval' function" }
+};
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-virtual2.C b/gcc/testsuite/g++.dg/cpp2a/consteval-virtual2.C
new file mode 100644 (file)
index 0000000..d5d8f79
--- /dev/null
@@ -0,0 +1,22 @@
+// { dg-do compile { target c++20 } }
+
+struct A
+{
+  virtual consteval int f() const { return 1; };
+};
+
+struct B: A
+{
+  virtual consteval int f() const { return 2; };
+  virtual void g() { }
+};
+
+consteval int f()
+{
+  const A& ar = B();
+  return ar.f();
+}
+
+static_assert (f() == 2);
+
+B b;
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-virtual3.C b/gcc/testsuite/g++.dg/cpp2a/consteval-virtual3.C
new file mode 100644 (file)
index 0000000..376e3ba
--- /dev/null
@@ -0,0 +1,53 @@
+// { dg-do compile { target c++20 } }
+
+struct S {
+  constexpr S () : s (0) {}
+  virtual int foo () const { return 42; }
+  consteval virtual int bar () const { return 43; }
+  consteval virtual int baz () const { return 44; }
+  consteval virtual int qux () const { return 47; }
+  int s;
+};
+struct T : public S {
+  constexpr T () : t (0) {}
+  consteval int bar () const { return 45; }
+  consteval virtual int baz () const { return 46; }
+  consteval virtual int grault () const { return 48; }
+  int t;
+};
+
+consteval int
+foo ()
+{
+  S s;
+  T t;
+  S *u = (S *) &t;
+  T *v = &t;
+  if (s.bar () != 43) throw 1;
+  if (s.baz () != 44) throw 2;
+  if (t.bar () != 45) throw 3;
+  if (t.baz () != 46) throw 4;
+  if (u->bar () != 45) throw 5;
+  if (u->baz () != 46) throw 6;
+  if (s.qux () != 47) throw 7;
+  if (t.qux () != 47) throw 8;
+  if (u->qux () != 47) throw 9;
+  if (v->qux () != 47) throw 10;
+  if (v->grault () != 48) throw 11;
+  return 0;
+}
+
+constexpr S s;
+constexpr T t;
+
+constexpr const S *
+bar (bool x)
+{
+  return x ? &s : (const S *) &t;
+}
+
+int a = foo ();
+int b = bar (false)->bar ();
+int c = bar (true)->baz ();
+static_assert (bar (false)->bar () == 45);
+static_assert (bar (true)->baz () == 44);
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-virtual4.C b/gcc/testsuite/g++.dg/cpp2a/consteval-virtual4.C
new file mode 100644 (file)
index 0000000..83405fe
--- /dev/null
@@ -0,0 +1,48 @@
+// { dg-do compile { target c++20 } }
+
+struct S {
+  constexpr S () : s (0) {}
+  virtual int foo () const { return 42; }
+  consteval virtual int bar () const { return 43; }
+  consteval virtual int baz () const { return 44; }
+  int s;
+};
+struct T : public S {
+  constexpr T () : t (0) {}
+  consteval int bar () const { return 45; }
+  consteval virtual int baz () const { return 46; }
+  int t;
+};
+
+consteval int
+foo ()
+{
+  S s;
+  T t;
+  S *u = (S *) &t;
+  T *v = &t;
+  auto pmf1 = &S::bar;
+  auto pmf2 = &S::baz;
+  if ((s.*pmf1) () != 43) throw 1;
+  if ((s.*pmf2) () != 44) throw 2;
+  if ((t.*pmf1) () != 45) throw 3;
+  if ((t.*pmf2) () != 46) throw 4;
+  if ((u->*pmf1) () != 45) throw 5;
+  if ((u->*pmf2) () != 46) throw 6;
+  return 0;
+}
+
+constexpr S s;
+constexpr T t;
+
+constexpr const S *
+bar (bool x)
+{
+  return x ? &s : (const S *) &t;
+}
+
+int a = foo ();
+int b = bar (false)->bar ();
+int c = bar (true)->baz ();
+static_assert (bar (false)->bar () == 45);
+static_assert (bar (true)->baz () == 44);
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-virtual5.C b/gcc/testsuite/g++.dg/cpp2a/consteval-virtual5.C
new file mode 100644 (file)
index 0000000..85ad118
--- /dev/null
@@ -0,0 +1,61 @@
+// { dg-do compile { target c++20 } }
+
+struct B1;
+struct B2;
+struct D;
+
+struct B1
+{
+  virtual consteval const B1 *foo1 () const {return this;}
+  virtual consteval const B2 *foo2 (const D *) const;
+};
+struct B2
+{
+  virtual consteval const B2 *baz1 () const {return this;}
+  virtual consteval const B1 *baz2 (const D *) const;
+};
+
+struct D : public B1, B2
+{
+  virtual consteval const D *foo1 () const {return this;}
+  virtual consteval const D *foo2 (const D *d) const {return d;}
+  virtual consteval const D *baz1 () const {return this;}
+  virtual consteval const D *baz2 (const D *d) const {return d;}
+};
+
+consteval const B2 *B1::foo2 (const D *d) const {return d;}
+consteval const B1 *B2::baz2 (const D *d) const {return d;}
+
+consteval int
+test (const B1 *b1, const B2 *b2, const D *d)
+{
+  if (b1->foo1 () != b1)
+    return 1;
+  if (b2->baz1 () != b2)
+    return 2;
+  if (b1->foo2 (d) != b2)
+    return 3;
+  if (b2->baz2 (d) != b1)
+    return 4;
+  return 0;
+}
+
+consteval int
+test (const D *d)
+{
+  if (d->foo2 (d) != d)
+    return 11;
+  if (d->baz2 (d) != d)
+    return 12;
+  if (d->foo1 () != d)
+    return 13;
+  if (d->baz1 () != d)
+    return 14;
+  return 0;
+}
+
+constexpr D d;
+constexpr auto e = test (&d, &d, &d);
+constexpr auto f = test (&d);
+static_assert (e == 0);
+static_assert (f == 0);