https://gcc.gnu.org/g:05a6289f30bb13a70f9dbcf41673db1749454d2e
commit r14-12604-g05a6289f30bb13a70f9dbcf41673db1749454d2e Author: Jason Merrill <[email protected]> Date: Thu May 14 12:20:23 2026 -0400 c++: constexpr call hashing and gc [PR124632] In cxx_eval_call_expression, we unshare an argument that goes into constexpr_call::bindings so it's independent, and then if it's a CONSTRUCTOR we unshare it again before putting it in the constexpr value hashtable so any modifications within the function don't affect the bindings. And then we free these latter CONSTRUCTOR if they aren't part of the return value. In explicit-obj-lambda2.C, cl5 is a recursive constexpr lambda that takes a class (the closure) by value. If -Wtautological-compare is enabled, we try to constant-fold cl5(5) to see if has a constant value. In evaluating the outer call we have an empty CONSTRUCTOR argument {} that we unshare twice. When we try to evaluate the recursive call, the second unshare from the first call initially goes into bindings. But since this is a recursive call get_fundef_copy needs need to make a copy of the FUNCTION_DECL, and since this evaluation is for a warning uid_sensitive_constexpr_evaluation_p is true, so we can't make a copy, so evaluating the recursive copy fails without ever unsharing the argument. Then when we get back to finishing up the outer call we ggc_free the unshared CONSTRUCTOR that we don't need anymore. But the recursive call already added it to the constexpr_call_table, so now the entry there is referring to freed memory. The solution is doing the first unshare immediately when we create the entry in the constexpr_call_table, not later after get_fundef_copy. This should also mean less unsharing, as we now only do the first unshare when creating the constexpr_call_table entry, not on subsequent evaluations. This only affects uncacheable calls, but that's a significant subset. PR c++/124632 gcc/cp/ChangeLog: * constexpr.cc (cxx_eval_call_expression): Unshare bindings sooner. gcc/testsuite/ChangeLog: * g++.dg/cpp23/explicit-obj-lambda2.C: Do strict GC and -Wall. (cherry picked from commit 219d9f4b006e19ea49b0e1ca6706fc1204211eff) Diff: --- gcc/cp/constexpr.cc | 18 +++++++++++------- gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda2.C | 3 +++ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc index 9ecb15cf5edc..6ec87be560b6 100644 --- a/gcc/cp/constexpr.cc +++ b/gcc/cp/constexpr.cc @@ -3228,6 +3228,16 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t, *slot = entry = ggc_alloc<constexpr_call> (); *entry = new_call; fb.preserve (); + + /* Unshare args going into the hash table to separate them + from the caller's context, for better GC and to avoid + problems with verify_gimple. */ + tree bound = new_call.bindings; + for (int i = 0; i < TREE_VEC_LENGTH (bound); ++i) + { + tree &arg = TREE_VEC_ELT (bound, i); + arg = unshare_expr_without_location (arg); + } } } /* Calls that are in progress have their result set to NULL, so that we @@ -3286,13 +3296,7 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t, tree arg = TREE_VEC_ELT (bound, i); if (entry) { - /* Unshare args going into the hash table to separate them - from the caller's context, for better GC and to avoid - problems with verify_gimple. */ - arg = unshare_expr_without_location (arg); - TREE_VEC_ELT (bound, i) = arg; - - /* And then unshare again so the callee doesn't change the + /* Unshare again so the callee doesn't change the argument values in the hash table. XXX Could we unshare lazily in cxx_eval_store_expression? */ arg = unshare_constructor (arg); diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda2.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda2.C index 827197a66674..4979ad57a14d 100644 --- a/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda2.C +++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda2.C @@ -1,6 +1,9 @@ // P0847R7 // { dg-do run { target c++23 } } +// PR c++/124632 +// { dg-additional-options "-Wall --param ggc-min-expand=0 --param ggc-min-heapsize=0" } + // recursive lambdas inline constexpr int correct_result = 5 + 4 + 3 + 2 + 1;
