Bug 107687 - [C++23] P2564 - consteval needs to propagate up
Summary: [C++23] P2564 - consteval needs to propagate up
Status: RESOLVED FIXED
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: unknown
: P3 normal
Target Milestone: ---
Assignee: Marek Polacek
URL:
Keywords:
Depends on: 110997
Blocks: c++23-core
  Show dependency treegraph
 
Reported: 2022-11-14 18:36 UTC by Marek Polacek
Modified: 2024-07-09 17:15 UTC (History)
1 user (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2022-11-14 00:00:00


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Marek Polacek 2022-11-14 18:36:22 UTC
See <https://wg21.link/p2564>.
Comment 1 Marek Polacek 2023-08-10 22:58:12 UTC
A (very) rudimentary patch that handles the simplest case so far:


From be2950f8cf3fb5ca0571054b4196de35d9807519 Mon Sep 17 00:00:00 2001
From: Marek Polacek <polacek@redhat.com>
Date: Thu, 10 Aug 2023 17:06:11 -0400
Subject: [PATCH] c++: implement P2564, consteval needs to propagate up
 [PR107687]

---
 gcc/cp/call.cc                               | 48 ++++++++++++++++++++
 gcc/cp/cp-gimplify.cc                        | 26 +++++++++--
 gcc/testsuite/g++.dg/cpp23/consteval-if10.C  |  2 +-
 gcc/testsuite/g++.dg/cpp2a/consteval-prop1.C | 36 +++++++++++++++
 4 files changed, 108 insertions(+), 4 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop1.C

diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
index 673ec91d60e..34b2fe1884d 100644
--- a/gcc/cp/call.cc
+++ b/gcc/cp/call.cc
@@ -9740,6 +9740,7 @@ build_trivial_dtor_call (tree instance, bool no_ptr_deref)
 bool
 in_immediate_context ()
 {
+  // TODO update to say that manifestly constant eval ctx is an IFC
   return (cp_unevaluated_operand != 0
 	  || (current_function_decl != NULL_TREE
 	      && DECL_IMMEDIATE_FUNCTION_P (current_function_decl))
@@ -9762,6 +9763,34 @@ immediate_invocation_p (tree fn)
 	  && !in_immediate_context ());
 }
 
+/* Return true if FN is an immediate-escalating function.  */
+
+static bool
+immediate_escalating_function_p (tree fn)
+{
+  if (!fn)
+    return false;
+
+  gcc_checking_assert (TREE_CODE (fn) == FUNCTION_DECL);
+
+  /* An immediate-escalating function is
+      -- the call operator of a lambda that is not declared with the consteval
+	 specifier  */
+  if (LAMBDA_FUNCTION_P (fn) && !DECL_IMMEDIATE_FUNCTION_P (fn))
+    return true;
+  /* -- a defaulted special member function that is not declared with the
+	consteval specifier  */
+  special_function_kind sfk = special_memfn_p (fn);
+  if (sfk != sfk_none
+      && DECL_DEFAULTED_FN (fn)
+      && !DECL_IMMEDIATE_FUNCTION_P (fn))
+    return true;
+  /* -- a function that results from the instantiation of a templated entity
+	defined with the constexpr specifier.  */
+  // TODO check if the DECL_DEFAULTED_FN part is actually OK here
+  return is_instantiation_of_constexpr (fn);
+}
+
 /* Subroutine of the various build_*_call functions.  Overload resolution
    has chosen a winning candidate CAND; build up a CALL_EXPR accordingly.
    ARGS is a TREE_LIST of the unconverted arguments to the call.  FLAGS is a
@@ -10484,6 +10513,7 @@ build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain)
       tree fndecl = STRIP_TEMPLATE (TREE_OPERAND (fn, 0));
       if (immediate_invocation_p (fndecl))
 	{
+	  tree orig_call = call;
 	  tree obj_arg = NULL_TREE;
 	  /* Undo convert_from_reference called by build_cxx_call.  */
 	  if (REFERENCE_REF_P (call))
@@ -10508,6 +10538,24 @@ build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain)
 		    obj_arg = TREE_OPERAND (addr, 0);
 		}
 	    }
+
+	  /* [expr.const]p16 "An expression or conversion is
+	     immediate-escalating if it is not initially in an immediate
+	     function context and it is either
+	      -- an immediate invocation that is not a constant expression and
+	      is not a subexpression of an immediate invocation."
+
+	      If we are in an immediate-escalating function, the
+	      immediate-escalating expression or conversion makes it an
+	      immediate function.  So CALL does not need to produce a constant
+	      expression.  ??? It's ugly to call cxx_constant_value twice in
+	      some cases.  */
+	  if (immediate_escalating_function_p (current_function_decl)
+	      && cxx_constant_value (call, obj_arg, tf_none) == error_mark_node)
+	    {
+	      SET_DECL_IMMEDIATE_FUNCTION_P (current_function_decl);
+	      return orig_call;
+	    }
 	  call = cxx_constant_value (call, obj_arg, complain);
 	  if (obj_arg && !error_operand_p (call))
 	    call = cp_build_init_expr (obj_arg, call);
diff --git a/gcc/cp/cp-gimplify.cc b/gcc/cp/cp-gimplify.cc
index 206e791fcfd..5fc0e3cc84b 100644
--- a/gcc/cp/cp-gimplify.cc
+++ b/gcc/cp/cp-gimplify.cc
@@ -1017,6 +1017,20 @@ maybe_replace_decl (tree *tp, tree decl, tree replacement)
   return true;
 }
 
+/* Maybe say that FN was initially not an immediate function, but was
+   promoted to one because its body contained an immediate-escalating
+   expression or conversion.  */
+
+static void
+maybe_explain_promoted_consteval (location_t loc, tree fn)
+{
+  /* A function cannot be marked both constexpr and consteval
+     except when we've promoted the former to the latter.  */
+  if (is_instantiation_of_constexpr (fn)
+      && DECL_DECLARED_CONSTEXPR_P (fn))
+    inform (loc, "%qD was promoted to an immediate function", fn);
+}
+
 /* Genericization context.  */
 
 struct cp_genericize_data
@@ -1050,9 +1064,13 @@ cp_fold_r (tree *stmt_p, int *walk_subtrees, void *data_)
 	  && DECL_IMMEDIATE_FUNCTION_P (PTRMEM_CST_MEMBER (stmt)))
 	{
 	  if (!data->pset.add (stmt))
-	    error_at (PTRMEM_CST_LOCATION (stmt),
-		      "taking address of an immediate function %qD",
-		      PTRMEM_CST_MEMBER (stmt));
+	    {
+	      error_at (PTRMEM_CST_LOCATION (stmt),
+			"taking address of an immediate function %qD",
+			PTRMEM_CST_MEMBER (stmt));
+	      maybe_explain_promoted_consteval (PTRMEM_CST_LOCATION (stmt),
+						PTRMEM_CST_MEMBER (stmt));
+	    }
 	  stmt = *stmt_p = build_zero_cst (TREE_TYPE (stmt));
 	  break;
 	}
@@ -1065,6 +1083,8 @@ cp_fold_r (tree *stmt_p, int *walk_subtrees, void *data_)
 	  error_at (EXPR_LOCATION (stmt),
 		    "taking address of an immediate function %qD",
 		    TREE_OPERAND (stmt, 0));
+	  maybe_explain_promoted_consteval (EXPR_LOCATION (stmt),
+					    TREE_OPERAND (stmt, 0));
 	  stmt = *stmt_p = build_zero_cst (TREE_TYPE (stmt));
 	  break;
 	}
diff --git a/gcc/testsuite/g++.dg/cpp23/consteval-if10.C b/gcc/testsuite/g++.dg/cpp23/consteval-if10.C
index 4c0523fe1d0..f799e81113d 100644
--- a/gcc/testsuite/g++.dg/cpp23/consteval-if10.C
+++ b/gcc/testsuite/g++.dg/cpp23/consteval-if10.C
@@ -10,7 +10,7 @@ bar (int x)
   int r = 0;
   if consteval		// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
     {
-      auto y = [=] { foo (x); };	// { dg-error "'x' is not a constant expression" }
+      auto y = [=] { foo (x); };
       y ();
     }
   return r;
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop1.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop1.C
new file mode 100644
index 00000000000..9a791b9cf0b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop1.C
@@ -0,0 +1,36 @@
+// P2564R3
+// { dg-do compile { target c++20 } }
+
+consteval int id(int i) { return i; }
+
+template <typename T>
+constexpr int
+f (T t)
+{
+  // OK, f promoted to consteval.
+  return id (t);
+}
+
+constexpr auto a1 = f (3);
+
+// As a consequence of f<int> being promoted to an immediate function, we
+// can't take its address.
+auto p1 = &f<int>; // { dg-error "taking address of an immediate function" }
+
+template <typename T>
+constexpr int
+f2 (T)
+{
+  // This produces a constant; f2 *not* promoted to consteval.
+  return id (42);
+}
+
+// ... so we can take its address.
+auto p2 = &f2<int>;
+
+constexpr int
+f3 (int i)
+{
+  // f3 isn't a function template and those don't get upgraded to consteval.
+  return id (i); // { dg-error "not a constant expression" }
+}

base-commit: ecfd8c7ffecf9e8f851c996ec149fbda7ef202f5
-- 
2.41.0
Comment 2 Marek Polacek 2023-08-11 19:39:52 UTC
I don't understand why clang++ doesn't error here:

--
consteval int id(int i) { return i; }

template<typename T>
constexpr int
f (T t)
{
  auto p = id; // immediate-escalating expr
  return t;
}

auto q = &f<int>; // error?
--

I though naming 'id' should promote f<int> to consteval and therefore you can't take its address.
Comment 3 GCC Commits 2023-12-05 00:42:36 UTC
The trunk branch has been updated by Marek Polacek <mpolacek@gcc.gnu.org>:

https://gcc.gnu.org/g:1f1c432226cf3db399b2a2a627e3c5720b02b1d6

commit r14-6129-g1f1c432226cf3db399b2a2a627e3c5720b02b1d6
Author: Marek Polacek <polacek@redhat.com>
Date:   Tue Sep 19 16:31:17 2023 -0400

    c++: implement P2564, consteval needs to propagate up [PR107687]
    
    This patch implements P2564, described at <wg21.link/p2564>, whereby
    certain functions are promoted to consteval.  For example:
    
      consteval int id(int i) { return i; }
    
      template <typename T>
      constexpr int f(T t)
      {
        return t + id(t); // id causes f<int> to be promoted to consteval
      }
    
      void g(int i)
      {
        f (3);
      }
    
    now compiles.  Previously the code was ill-formed: we would complain
    that 't' in 'f' is not a constant expression.  Since 'f' is now
    consteval, it means that the call to id(t) is in an immediate context,
    so doesn't have to produce a constant -- this is how we allow consteval
    functions composition.  But making 'f<int>' consteval also means that
    the call to 'f' in 'g' must yield a constant; failure to do so results
    in an error.  I made the effort to have cc1plus explain to us what's
    going on.  For example, calling f(i) produces this neat diagnostic:
    
    w.C:11:11: error: call to consteval function 'f<int>(i)' is not a constant expression
       11 |         f (i);
          |         ~~^~~
    w.C:11:11: error: 'i' is not a constant expression
    w.C:6:22: note: 'constexpr int f(T) [with T = int]' was promoted to an immediate function because its body contains an immediate-escalating expression 'id(t)'
        6 |         return t + id(t); // id causes f<int> to be promoted to consteval
          |                    ~~^~~
    
    which hopefully makes it clear what's going on.
    
    Implementing this proposal has been tricky.  One problem was delayed
    instantiation: instantiating a function can set off a domino effect
    where one call promotes a function to consteval but that then means
    that another function should also be promoted, etc.
    
    In v1, I addressed the delayed instantiation problem by instantiating
    trees early, so that we can escalate functions right away.  That caused
    a number of problems, and in certain cases, like consteval-prop3.C, it
    can't work, because we need to wait till EOF to see the definition of
    the function anyway.  Overeager instantiation tends to cause diagnostic
    problems too.
    
    In v2, I attempted to move the escalation to the gimplifier, at which
    point all templates have been instantiated.  That attempt flopped,
    however, because once we've gimplified a function, its body is discarded
    and as a consequence, you can no longer evaluate a call to that function
    which is required for escalating, which needs to decide if a call is
    a constant expression or not.
    
    Therefore, we have to perform the escalation before gimplifying, but
    after instantiate_pending_templates.  That's not easy because we have
    no way to walk all the trees.  In the v2 patch, I use two vectors: one
    to store function decls that may become consteval, and another to
    remember references to immediate-escalating functions.  Unfortunately
    the latter must also stash functions that call immediate-escalating
    functions.  Consider:
    
      int g(int i)
      {
        f<int>(i); // f is immediate-escalating
      }
    
    where g itself is not immediate-escalating, but we have to make sure
    that if f gets promoted to consteval, we give an error.
    
    A new option, -fno-immediate-escalation, is provided to suppress
    escalating functions.
    
    v2 also adds a new flag, DECL_ESCALATION_CHECKED_P, so that we don't
    escalate a function multiple times, and so that we can distinguish between
    explicitly consteval functions and functions that have been promoted
    to consteval.
    
    In v3, I removed one of the new vectors and changed the other one
    to a hash set.  This version also contains numerous cleanups.
    
    v4 merges find_escalating_expr_r into cp_fold_immediate_r.  It also
    adds a new optimization in cp_fold_function.
    
    v5 greatly simplifies the code.
    
    v6 simplifies the code further and removes an ff_ flag.
    
    v7 removes maybe_promote_function_to_consteval and further simplifies
    cp_fold_immediate_r logic.
    
    v8 removes maybe_store_immediate_escalating_fn.
    
            PR c++/107687
            PR c++/110997
    
    gcc/c-family/ChangeLog:
    
            * c-cppbuiltin.cc (c_cpp_builtins): Update __cpp_consteval.
            * c-opts.cc (c_common_post_options): Pre-C++20, unset
            flag_immediate_escalation.
            * c.opt (fimmediate-escalation): New option.
    
    gcc/cp/ChangeLog:
    
            * call.cc (in_immediate_context): No longer static.
            * constexpr.cc (cxx_eval_call_expression): Adjust assert.
            * cp-gimplify.cc (deferred_escalating_exprs): New vec.
            (remember_escalating_expr): New.
            (enum fold_flags): Remove ff_fold_immediate.
            (immediate_escalating_function_p): New.
            (unchecked_immediate_escalating_function_p): New.
            (promote_function_to_consteval): New.
            (cp_fold_immediate): Move above.  Return non-null if any errors were
            emitted.
            (maybe_explain_promoted_consteval): New.
            (cp_gimplify_expr) <case CALL_EXPR>: Assert we've handled all
            immediate invocations.
            (taking_address_of_imm_fn_error): New.
            (cp_fold_immediate_r): Merge ADDR_EXPR and PTRMEM_CST cases.  Implement
            P2564 - promoting functions to consteval.
            <case CALL_EXPR>: Implement P2564 - promoting functions to consteval.
            (cp_fold_r): If an expression turns into a CALL_EXPR after cp_fold,
            call cp_fold_immediate_r on the CALL_EXPR.
            (cp_fold_function): Set DECL_ESCALATION_CHECKED_P if
            deferred_escalating_exprs does not contain current_function_decl.
            (process_and_check_pending_immediate_escalating_fns): New.
            * cp-tree.h (struct lang_decl_fn): Add escalated_p bit-field.
            (DECL_ESCALATION_CHECKED_P): New.
            (immediate_invocation_p): Declare.
            (process_pending_immediate_escalating_fns): Likewise.
            * decl2.cc (c_parse_final_cleanups): Set at_eof to 2 after all
            templates have been instantiated; and to 3 at the end of the function.
            Call process_pending_immediate_escalating_fns.
            * error.cc (dump_template_bindings): Check at_eof against an updated
            value.
            * module.cc (trees_out::lang_decl_bools): Stream escalated_p.
            (trees_in::lang_decl_bools): Likewise.
            * pt.cc (push_tinst_level_loc): Set at_eof to 3, not 2.
            * typeck.cc (cp_build_addr_expr_1): Don't check
            DECL_IMMEDIATE_FUNCTION_P.
    
    gcc/ChangeLog:
    
            * doc/invoke.texi: Document -fno-immediate-escalation.
    
    libstdc++-v3/ChangeLog:
    
            * testsuite/18_support/comparisons/categories/zero_neg.cc: Add
            dg-prune-output.
            * testsuite/std/format/string_neg.cc: Add dg-error.
    
    gcc/testsuite/ChangeLog:
    
            * g++.dg/cpp23/consteval-if10.C: Remove dg-error.
            * g++.dg/cpp23/consteval-if2.C: Likewise.
            * g++.dg/cpp23/feat-cxx2b.C: Adjust expected value of __cpp_consteval.
            * g++.dg/cpp26/feat-cxx26.C: Likewise.
            * g++.dg/cpp2a/consteval-memfn1.C: Add dg-error.
            * g++.dg/cpp2a/consteval11.C: Likewise.
            * g++.dg/cpp2a/consteval3.C: Adjust dg-error.
            * g++.dg/cpp2a/consteval34.C: Add dg-error.
            * g++.dg/cpp2a/consteval36.C: Likewise.
            * g++.dg/cpp2a/consteval9.C: Likewise.
            * g++.dg/cpp2a/feat-cxx2a.C: Adjust expected value of __cpp_consteval.
            * g++.dg/cpp2a/spaceship-synth9.C: Adjust dg-error.
            * g++.dg/cpp2a/consteval-prop1.C: New test.
            * g++.dg/cpp2a/consteval-prop10.C: New test.
            * g++.dg/cpp2a/consteval-prop11.C: New test.
            * g++.dg/cpp2a/consteval-prop12.C: New test.
            * g++.dg/cpp2a/consteval-prop13.C: New test.
            * g++.dg/cpp2a/consteval-prop14.C: New test.
            * g++.dg/cpp2a/consteval-prop15.C: New test.
            * g++.dg/cpp2a/consteval-prop16.C: New test.
            * g++.dg/cpp2a/consteval-prop17.C: New test.
            * g++.dg/cpp2a/consteval-prop18.C: New test.
            * g++.dg/cpp2a/consteval-prop19.C: New test.
            * g++.dg/cpp2a/consteval-prop20.C: New test.
            * g++.dg/cpp2a/consteval-prop2.C: New test.
            * g++.dg/cpp2a/consteval-prop3.C: New test.
            * g++.dg/cpp2a/consteval-prop4.C: New test.
            * g++.dg/cpp2a/consteval-prop5.C: New test.
            * g++.dg/cpp2a/consteval-prop6.C: New test.
            * g++.dg/cpp2a/consteval-prop7.C: New test.
            * g++.dg/cpp2a/consteval-prop8.C: New test.
            * g++.dg/cpp2a/consteval-prop9.C: New test.
Comment 4 Marek Polacek 2023-12-05 00:45:19 UTC
Implemented in GCC 14.