On 4/12/25 12:20 PM, Patrick Palka wrote:
On Sat, 12 Apr 2025, Jason Merrill wrote:

On 4/11/25 4:36 PM, Patrick Palka wrote:
Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for
GCC 15 perhaps?

-- >8 --

Here checking

    static_assert(0 == big_calc());

takes twice as much time as

    constexpr int ret = big_calc();
    static_assert(0 == ret);

ultimately because in the former, we first constexpr evaluate big_calc()
with mce_unknown (as part of warning-dependent folding from
cp_build_binary_op).  We then constant evaluate it a second time, with
mce_true, during finish_static_assert.  The result of the first
evaluation isn't reused because of the different mce_value.

But the call doesn't depend on mce_value at all here (i.e. there's no if
consteval or __builtin_is_constant_evaluated calls, nested or otherwise)
so we should be able to reuse the result in this case.  Specifically if a
constexpr call with mce_unknown gets successfully evaluated, we can safely
reuse the result during a subsequent mce_true or mce_false evaluation.

This patch implements this by caching a successful mce_unknown call
result into the corresponding mce_true and mce_false slots, so that
such a subsequent evaluation effectively reuses the mce_unknown result.
To make it more convenient to access the cache slot for the same call
with different mce_value, this patch gives each constexpr_call entry
three result slots, one per mce_value, instead of having a distinct
constexpr_call entry for each mce_value.  And we can no longer use
NULL_TREE as the "in progress" result, so instead use unknown_type_node.

After this patch compile time for the above two fragments is the same.

        PR c++/115639

gcc/cp/ChangeLog:

        * constexpr.cc (struct constexpr_call): Add NSDMIs to each
        field.  Replace 'result' data member with 3-element 'results'
        array and a 'result' accessor function.  Remove
        'manifestly_const_eval' data member.
        (constexpr_call_hasher::equal): Adjust after constexpr_call
        layout change.
        (cxx_eval_call_expression): Likewise.  Define some local
        variables closer to their first use.  Use unknown_type_node
        instead of NULL_TREE as the "in progress" result.  After
        successully evaluating a call with mce_unknown, also cache the
        result in the corresponding mce_true and mce_false slots.
---
   gcc/cp/constexpr.cc | 59 +++++++++++++++++++++++++++------------------
   1 file changed, 35 insertions(+), 24 deletions(-)

diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
index 0242425370f4..a4fd0d072f65 100644
--- a/gcc/cp/constexpr.cc
+++ b/gcc/cp/constexpr.cc
@@ -1119,20 +1119,22 @@ explain_invalid_constexpr_fn (tree fun)
     struct GTY((for_user)) constexpr_call {
     /* Description of the constexpr function definition.  */
-  constexpr_fundef *fundef;
+  constexpr_fundef *fundef = nullptr;
     /* Parameter bindings environment.  A TREE_VEC of arguments.  */
-  tree bindings;
-  /* Result of the call.
-       NULL means the call is being evaluated.
+  tree bindings = NULL_TREE;
+  /* Result of the call, indexed by the value of
+     constexpr_ctx::manifestly_const_eval.
+       unknown_type_node means the call is being evaluated.
          error_mark_node means that the evaluation was erroneous or
otherwise
          uncacheable (e.g. because it depends on the caller).
          Otherwise, the actual value of the call.  */
-  tree result;
+  tree results[3] = { NULL_TREE, NULL_TREE, NULL_TREE };
     /* The hash of this call; we remember it here to avoid having to
        recalculate it when expanding the hash table.  */
-  hashval_t hash;
-  /* The value of constexpr_ctx::manifestly_const_eval.  */
-  enum mce_value manifestly_const_eval;
+  hashval_t hash = 0;
+
+  /* The result slot corresponding to the given mce_value.  */
+  tree& result (mce_value mce) { return results[1 + int(mce)]; }
   };
     struct constexpr_call_hasher : ggc_ptr_hash<constexpr_call>
@@ -1427,8 +1429,6 @@ constexpr_call_hasher::equal (constexpr_call *lhs,
constexpr_call *rhs)
       return true;
     if (lhs->hash != rhs->hash)
       return false;
-  if (lhs->manifestly_const_eval != rhs->manifestly_const_eval)
-    return false;
     if (!constexpr_fundef_hasher::equal (lhs->fundef, rhs->fundef))
       return false;
     return cp_tree_equal (lhs->bindings, rhs->bindings);
@@ -2855,9 +2855,6 @@ cxx_eval_call_expression (const constexpr_ctx *ctx,
tree t,
   {
     location_t loc = cp_expr_loc_or_input_loc (t);
     tree fun = get_function_named_in_call (t);
-  constexpr_call new_call
-    = { NULL, NULL, NULL, 0, ctx->manifestly_const_eval };
-  int depth_ok;
       if (fun == NULL_TREE)
       return cxx_eval_internal_function (ctx, t, lval,
@@ -3099,7 +3096,6 @@ cxx_eval_call_expression (const constexpr_ctx *ctx,
tree t,
     if (DECL_IMMEDIATE_FUNCTION_P (fun))
       {
         new_ctx.manifestly_const_eval = mce_true;
-      new_call.manifestly_const_eval = mce_true;
         ctx = &new_ctx;
       }
   @@ -3112,6 +3108,7 @@ cxx_eval_call_expression (const constexpr_ctx *ctx,
tree t,
                       || cp_noexcept_operand);
       bool non_constant_args = false;
+  constexpr_call new_call;
     new_call.bindings
       = cxx_bind_parameters_in_call (ctx, t, fun, non_constant_p,
                                   overflow_p, &non_constant_args);
@@ -3185,7 +3182,7 @@ cxx_eval_call_expression (const constexpr_ctx *ctx,
tree t,
           }
       }
   -  depth_ok = push_cx_call_context (t);
+  int depth_ok = push_cx_call_context (t);
       /* Remember the object we are constructing or destructing.  */
     tree new_obj = NULL_TREE;
@@ -3227,8 +3224,6 @@ cxx_eval_call_expression (const constexpr_ctx *ctx,
tree t,
         new_call.hash = constexpr_fundef_hasher::hash (new_call.fundef);
         new_call.hash
        = iterative_hash_template_arg (new_call.bindings, new_call.hash);
-      new_call.hash
-       = iterative_hash_object (ctx->manifestly_const_eval, new_call.hash);
           /* If we have seen this call before, we are done.  */
         maybe_initialize_constexpr_call_table ();
@@ -3246,22 +3241,23 @@ cxx_eval_call_expression (const constexpr_ctx *ctx,
tree t,
                 the slot can move during evaluation of the body.  */
              *slot = entry = ggc_alloc<constexpr_call> ();
              *entry = new_call;
+             entry->result (ctx->manifestly_const_eval) = unknown_type_node;
              fb.preserve ();
            }
        }
-      /* Calls that are in progress have their result set to NULL, so that
we
-        can detect circular dependencies.  Now that we only cache up to
-        constexpr_cache_depth this won't catch circular dependencies that
+      /* Calls that are in progress have their result set to
unknown_type_node,
+        so that we can detect circular dependencies.  Now that we only cache
+        up to constexpr_cache_depth this won't catch circular dependencies
that
         start deeper, but they'll hit the recursion or ops limit.  */
-      else if (entry->result == NULL)
+      else if (entry->result (ctx->manifestly_const_eval) ==
unknown_type_node)
        {
          if (!ctx->quiet)
            error ("call has circular dependency");
          *non_constant_p = true;
-         entry->result = result = error_mark_node;
+         entry->result (ctx->manifestly_const_eval) = result =
error_mark_node;
        }
         else
-       result = entry->result;
+       result = entry->result (ctx->manifestly_const_eval);
       }
       if (!depth_ok)
@@ -3482,7 +3478,22 @@ cxx_eval_call_expression (const constexpr_ctx *ctx,
tree t,
         else if (!result)
        result = void_node;
         if (entry)
-       entry->result = cacheable ? result : error_mark_node;
+       {
+         entry->result (ctx->manifestly_const_eval)
+           = cacheable ? result : error_mark_node;
+
+         if (result != error_mark_node
+             && ctx->manifestly_const_eval == mce_unknown)

This looks like it will set the true/false results to the actual result even
if we set the unknown result to error_mark_node because !cacheable?

The code path sets the true/false results by directly copying the
current (i.e. mce_unknown) result:

   entry->result (mce_true) = entry->result (mce_unknown);
   entry->result (mce_false) = entry->result (mce_unknown);

so it'll mirror whatever logic is used to set the unknow result.

Ah, right.  The patch is OK.


+           {
+             /* Evaluation succeeded and was independent of whether we're in
a
+                manifestly constant-evaluated context, so we can also reuse
+                this result with a fixed context.  */
+             if (!entry->result (mce_true))
+               entry->result (mce_true) = entry->result (mce_unknown);
+             if (!entry->result (mce_false))
+               entry->result (mce_false) = entry->result (mce_unknown);
+           }
+       }
       }
       /* The result of a constexpr function must be completely initialized.




Reply via email to