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. > > > + { > > + /* 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. > >