From e6321c4508b2a85c21246c1c06a8208e2a151e48 Mon Sep 17 00:00:00 2001 From: Jason Merrill Date: Wed, 24 Jun 2020 20:46:09 -0400 Subject: [PATCH] c++: Support C++20 virtual consteval functions. [PR88335] 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 --- gcc/c-family/c-cppbuiltin.c | 2 +- gcc/cp/constexpr.c | 48 +++++++++++++++ gcc/cp/cvt.c | 11 ++-- gcc/cp/decl.c | 9 --- gcc/cp/decl2.c | 39 ++++++++++-- gcc/cp/search.c | 36 +++++++---- .../g++.dg/cpp2a/consteval-virtual1.C | 12 ++++ .../g++.dg/cpp2a/consteval-virtual2.C | 22 +++++++ .../g++.dg/cpp2a/consteval-virtual3.C | 53 ++++++++++++++++ .../g++.dg/cpp2a/consteval-virtual4.C | 48 +++++++++++++++ .../g++.dg/cpp2a/consteval-virtual5.C | 61 +++++++++++++++++++ 11 files changed, 307 insertions(+), 34 deletions(-) create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-virtual1.C create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-virtual2.C create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-virtual3.C create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-virtual4.C create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-virtual5.C diff --git a/gcc/c-family/c-cppbuiltin.c b/gcc/c-family/c-cppbuiltin.c index a7d65d63934..83f52fdf5d8 100644 --- a/gcc/c-family/c-cppbuiltin.c +++ b/gcc/c-family/c-cppbuiltin.c @@ -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"); diff --git a/gcc/cp/constexpr.c b/gcc/cp/constexpr.c index f766abd3a11..1939166e907 100644 --- a/gcc/cp/constexpr.c +++ b/gcc/cp/constexpr.c @@ -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-% 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 diff --git a/gcc/cp/cvt.c b/gcc/cp/cvt.c index 371002833d0..c9e7b1ff044 100644 --- a/gcc/cp/cvt.c +++ b/gcc/cp/cvt.c @@ -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; } diff --git a/gcc/cp/decl.c b/gcc/cp/decl.c index 45c871af741..1eb5c2a29ac 100644 --- a/gcc/cp/decl.c +++ b/gcc/cp/decl.c @@ -9560,15 +9560,6 @@ grokfndecl (tree ctype, } } - /* FIXME: For now. */ - if (virtualp && (inlinep & 8) != 0) - { - sorry_at (DECL_SOURCE_LOCATION (decl), - "% % 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); diff --git a/gcc/cp/decl2.c b/gcc/cp/decl2.c index 93e30340454..ddc2023a75b 100644 --- a/gcc/cp/decl2.c +++ b/gcc/cp/decl2.c @@ -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 &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 &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 &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 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. */ diff --git a/gcc/cp/search.c b/gcc/cp/search.c index a1a45a5ee6b..e36a8aed8f8 100644 --- a/gcc/cp/search.c +++ b/gcc/cp/search.c @@ -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 ("% function %q+D overriding non-% " + "function", overrider); + else + error ("non-% function %q+D overriding % " + "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 index 00000000000..5cdb75ab89a --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-virtual1.C @@ -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 index 00000000000..d5d8f798cab --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-virtual2.C @@ -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 index 00000000000..376e3ba50d3 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-virtual3.C @@ -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 index 00000000000..83405fe7aee --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-virtual4.C @@ -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 index 00000000000..85ad1182fb0 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-virtual5.C @@ -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); -- 2.30.2