On Wed, Mar 12, 2025 at 05:39:46PM -0400, Jason Merrill wrote:
> On 3/7/25 11:54 AM, Jakub Jelinek wrote:
> > Hi!
> >
> > The r14-4140 change moved consteval evaluation from build_over_call to
> > cp_fold_r.
> >
> > The following testcase is a regression caused by that change. There
> > is a cast around immediate invocation, (int) foo (0x23)
> > where consteval for returns unsigned char. When the consteval call
> > has been folded early to 0x23 (with unsigned char type), cp_fold sees
> > (int) 0x23 and folds that to 0x23 with int type.
> > But when the immediate invocation is handled only during cp_fold_r,
> > cp_fold_r first calls cp_fold on the NOP_EXPR, which calls cp_fold
> > on its operand, it is CALL_EXPR, nothing is folded at that point.
> > Then cp_fold_r continues to walk the NOP_EXPR's operand, sees it is
> > an immediate function invocation and cp_fold_immediate_r calls
> > cxx_constant_value on it and replaces the CALL_EXPR with the INTEGER_CST
> > 0x23. Nothing comes back to folding the containing NOP_EXPR though.
> > Sure, with optimizations enabled some GIMPLE optimization folds that later,
> > but e.g. with -O0 nothing does that. I think there could be arbitrarily
> > complex expressions on top of the immediate invocation(s) that used to be
> > folded by cp_fold before and aren't folded anymore.
> >
> > One possibility would be to do the immediate invocation evaluation in
> > cp_fold rather than cp_fold_r (or in addition to cp_fold_r), but that
> > could mean we evaluate it multiple times and e.g. if there is an error
> > emit multiple errors.
> >
> > The following patch instead first evaluates all immediate invocations and
> > does cp_fold_r in a separate step. That not only allows the folding of
> > expressions which contain immediate invocations, but also simplifies some
> > of the quirks that had to be done when it was in cp_fold_r.
> > Though, I had to add an extra case to cp_genericize_r RETURN_EXPR handling
> > to avoid a regression where after emitting errors in RETURN_EXPR argument
> > we've emitted a -Wreturn-type false positive. Previously we ended up with
> > RETURN_EXPR with CLEANUP_POINT_EXPR with INIT_EXPR of RESULT_DECL to
> > error_mark_node, now we fold it more and have RETURN_EXPR with
> > error_mark_node operand. The former would result during gimplification
> > into something -Wresult-type was quiet about, the latter doesn't.
> >
> > Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?
> >
> > BTW, r14-4140 changed behavior on
> > consteval bool foo (bool x) { if (x) throw 1; return false; }
> >
> > constexpr void
> > foo ()
> > {
> > if constexpr (false)
> > {
> > bool a = foo (true);
> > }
> > }
> > where GCC 13 emitted
> > error: expression ‘<throw-expression>’ is not a constant expression
> > error and GCC 14/trunk including the patch below doesn't reject it.
> > And clang++ trunk rejects it. It isn't immediately clear to me what
> > is right, if immediate invocations in discarded statements should
> > be evaluated or not.
> >
> > 2025-03-07 Jakub Jelinek <[email protected]>
> >
> > PR target/118068
> > gcc/cp/
> > * cp-gimplify.cc (cp_fold_immediate): Use cp_walk_tree rather than
> > cp_walk_tree_without_duplicates.
> > (cp_fold_immediate_r): For IF_STMT_CONSTEVAL_P IF_STMT don't walk
> > into THEN_CLAUSE subtree, only ELSE_CLAUSE. For non-call related
> > stmts call data->pset.add and if it returns true, don't walk subtrees.
> > (cp_fold_r): Don't call cp_fold_immediate_r here.
> > (cp_fold_function): For C++20 or later call cp_walk_tree
> > with cp_fold_immediate_r callback first before calling cp_walk_tree
> > with cp_fold_r callback and call data.pset.empty () in between.
> > (cp_fully_fold_init): Likewise.
> > (cp_genericize_r) <case RETURN_EXPR>: Suppress -Wreturn-type warning
> > if RETURN_EXPR has erroneous argument.
> > gcc/testsuite/
> > * g++.target/i386/pr118068.C: New test.
> >
> > --- gcc/cp/cp-gimplify.cc.jj 2025-03-06 17:26:00.547975990 +0100
> > +++ gcc/cp/cp-gimplify.cc 2025-03-07 12:07:41.578078222 +0100
> > @@ -519,7 +519,7 @@ cp_fold_immediate (tree *tp, mce_value m
> > cp_fold_data data (flags);
> > int save_errorcount = errorcount;
> > - tree r = cp_walk_tree_without_duplicates (tp, cp_fold_immediate_r,
> > &data);
> > + tree r = cp_walk_tree (tp, cp_fold_immediate_r, &data, NULL);
> > if (errorcount > save_errorcount)
> > return integer_one_node;
> > return r;
> > @@ -1204,13 +1204,14 @@ cp_build_init_expr_for_ctor (tree call,
> > return init;
> > }
> > -/* A subroutine of cp_fold_r to handle immediate functions. */
> > +/* A walk_tree callback for cp_fold_function and cp_fully_fold_init to
> > handle
> > + immediate functions. */
> > static tree
> > cp_fold_immediate_r (tree *stmt_p, int *walk_subtrees, void *data_)
> > {
> > auto data = static_cast<cp_fold_data *>(data_);
> > tree stmt = *stmt_p;
> > /* The purpose of this is not to emit errors for mce_unknown. */
> > const tsubst_flags_t complain = (data->flags & ff_mce_false
> > ? tf_error : tf_none);
> > @@ -1250,7 +1251,19 @@ cp_fold_immediate_r (tree *stmt_p, int *
> > if (!ADDR_EXPR_DENOTES_CALL_P (stmt))
> > decl = TREE_OPERAND (stmt, 0);
> > break;
> > + case IF_STMT:
> > + if (IF_STMT_CONSTEVAL_P (stmt))
> > + {
> > + if (!data->pset.add (stmt))
> > + cp_walk_tree (&ELSE_CLAUSE (stmt), cp_fold_immediate_r, data_,
> > + NULL);
> > + *walk_subtrees = 0;
> > + return NULL_TREE;
> > + }
> > + /* FALLTHRU */
> > default:
> > + if (data->pset.add (stmt))
> > + *walk_subtrees = 0;
> > return NULL_TREE;
> > }
> > @@ -1370,45 +1383,8 @@ cp_fold_r (tree *stmt_p, int *walk_subtr
> > tree stmt = *stmt_p;
> > enum tree_code code = TREE_CODE (stmt);
> > - if (cxx_dialect >= cxx20)
> > - {
> > - /* Unfortunately we must handle code like
> > - false ? bar () : 42
> > - where we have to check bar too. The cp_fold call below could
> > - fold the ?: into a constant before we've checked it. */
> > - if (code == COND_EXPR)
> > - {
> > - auto then_fn = cp_fold_r, else_fn = cp_fold_r;
> > - /* See if we can figure out if either of the branches is dead. If it
> > - is, we don't need to do everything that cp_fold_r does. */
> > - cp_walk_tree (&TREE_OPERAND (stmt, 0), cp_fold_r, data, nullptr);
> > - if (integer_zerop (TREE_OPERAND (stmt, 0)))
> > - then_fn = cp_fold_immediate_r;
> > - else if (integer_nonzerop (TREE_OPERAND (stmt, 0)))
> > - else_fn = cp_fold_immediate_r;
> > -
> > - if (TREE_OPERAND (stmt, 1))
> > - cp_walk_tree (&TREE_OPERAND (stmt, 1), then_fn, data,
> > - nullptr);
> > - if (TREE_OPERAND (stmt, 2))
> > - cp_walk_tree (&TREE_OPERAND (stmt, 2), else_fn, data,
> > - nullptr);
> > - *walk_subtrees = 0;
> > - /* Don't return yet, still need the cp_fold below. */
> > - }
> > - else
> > - cp_fold_immediate_r (stmt_p, walk_subtrees, data);
> > - }
>
> If we're removing the call to cp_fold_immediate_r, we should copy over the
> early "this affects cp_fold_r" exit from that function that was previously
> removed as redundant.
You mean
/* No need to look into types or unevaluated operands.
NB: This affects cp_fold_r as well. */
if (TYPE_P (stmt) || unevaluated_p (TREE_CODE (stmt)))
{
*walk_subtrees = 0;
return NULL_TREE;
}
? Note, GCC 13 didn't have anything like that in cp_fold_r.
> > @@ -1537,6 +1513,17 @@ cp_fold_function (tree fndecl)
> > been constant-evaluated already if possible, so we can safely
> > pass ff_mce_false. */
> > cp_fold_data data (ff_genericize | ff_mce_false);
> > + /* Do cp_fold_immediate_r in separate whole IL walk instead of during
> > + cp_fold_r, as otherwise expressions using results of immediate
> > functions
> > + might not be folded as cp_fold is called on those before cp_fold_r is
> > + called on their argument. And calling cp_fold_immediate_r during
> > + cp_fold can mean evaluation of the immediate functions many times. */
>
> Hmm, fold_cache should prevent multiple evaluation?
So would you prefer something like the patch below instead?
Currently it regresses
FAIL: g++.dg/cpp2a/consteval-prop15.C -std=gnu++20 (test for excess errors)
FAIL: g++.dg/cpp2a/consteval-prop15.C -std=gnu++23 (test for excess errors)
FAIL: g++.dg/cpp2a/consteval-prop15.C -std=gnu++26 (test for excess errors)
FAIL: g++.dg/cpp2a/consteval-prop2.C -std=c++20 (test for warnings, line 10)
FAIL: g++.dg/cpp2a/consteval-prop2.C -std=c++23 (test for warnings, line 10)
FAIL: g++.dg/cpp2a/consteval-prop2.C -std=c++26 (test for warnings, line 10)
and fixes the new testcase in the patch.
The excess errors are
/usr/src/gcc/gcc/testsuite/g++.dg/cpp2a/consteval-prop15.C:104:6: error: call
to consteval function 'f9<int>(non_const)' is not a constant expression
/usr/src/gcc/gcc/testsuite/g++.dg/cpp2a/consteval-prop15.C:104:6: error:
'non_const' is not a constant expression
/usr/src/gcc/gcc/testsuite/g++.dg/cpp2a/consteval-prop15.C:106:7: error: call
to consteval function 'f10<int>(non_const)' is not a constant expression
/usr/src/gcc/gcc/testsuite/g++.dg/cpp2a/consteval-prop15.C:106:7: error:
'non_const' is not a constant expression
And in the other tests the differences on stderr are
-consteval-prop2.C:10:16: note: ‘constexpr int f(T) [with T = int]’ was
promoted to an immediate function because its body contains an
immediate-escalating expression ‘id(t)’
- 10 | return t + id(t); // { dg-message "immediate-escalating
expression .id\\(t\\)." }
- | ~~^~~
+consteval-prop2.C:14:10: note: ‘constexpr int f(T) [with T = int]’ was
promoted to an immediate function
and
-consteval-prop2.C:71:21: error: ‘i’ is not a constant expression
+consteval-prop2.C:71:22: error: ‘i’ is not a constant expression
+ 71 | + k<int>(i); // { dg-bogus "call to|constant" "" { xfail
*-*-* } }
+ | ^
2025-03-17 Jakub Jelinek <[email protected]>
PR target/118068
gcc/cp/
* cp-gimplify.cc (get_fold_cache): Add prototype.
(cp_fold_immediate_r): For CALL_EXPR or ADDR_EXPR, query fold_cache
and if stmt is there already and not mapped to self, exit early.
(cp_genericize_r) <case RETURN_EXPR>: Suppress -Wreturn-type warning
if RETURN_EXPR has erroneous argument.
(cp_fold) <case ADDR_EXPR>: For C++20 call cp_fold_immediate_r.
(cp_fold) <case CALL_EXPR>: Likewise.
gcc/testsuite/
* g++.target/i386/pr118068.C: New test.
--- gcc/cp/cp-gimplify.cc.jj 2025-03-14 15:31:40.170197353 +0100
+++ gcc/cp/cp-gimplify.cc 2025-03-17 16:51:14.644040172 +0100
@@ -1204,6 +1204,8 @@ cp_build_init_expr_for_ctor (tree call,
return init;
}
+static hash_map<tree, tree> *&get_fold_cache (fold_flags_t);
+
/* A subroutine of cp_fold_r to handle immediate functions. */
static tree
@@ -1257,6 +1259,22 @@ cp_fold_immediate_r (tree *stmt_p, int *
if (!decl || TREE_CODE (decl) != FUNCTION_DECL)
return NULL_TREE;
+ /* If this call has been evaluated before by cp_fold and
+ stored into the fold cache, just use earlier computed replacement. */
+ if (code == CALL_EXPR || code == ADDR_EXPR)
+ {
+ auto &fold_cache = get_fold_cache (data->flags);
+ if (fold_cache != NULL)
+ {
+ if (tree *cached = fold_cache->get (stmt))
+ if (*cached != stmt)
+ {
+ *stmt_p = *cached;
+ return NULL_TREE;
+ }
+ }
+ }
+
/* Fully escalate once all templates have been instantiated. What we're
calling is not a consteval function but it may become one. This
requires recursing; DECL may be promoted to consteval because it
@@ -1717,6 +1735,11 @@ cp_genericize_r (tree *stmt_p, int *walk
case RETURN_EXPR:
if (TREE_OPERAND (stmt, 0))
{
+ if (error_operand_p (TREE_OPERAND (stmt, 0))
+ && warn_return_type)
+ /* Suppress -Wreturn-type for this function. */
+ suppress_warning (current_function_decl, OPT_Wreturn_type);
+
if (is_invisiref_parm (TREE_OPERAND (stmt, 0)))
/* Don't dereference an invisiref RESULT_DECL inside a
RETURN_EXPR. */
@@ -3082,6 +3105,19 @@ cp_fold (tree x, fold_flags_t flags)
goto unary;
case ADDR_EXPR:
+ if (cxx_dialect >= cxx20
+ && !ADDR_EXPR_DENOTES_CALL_P (x)
+ && TREE_CODE (TREE_OPERAND (x, 0)) == FUNCTION_DECL)
+ {
+ int walk_subtrees = 0;
+ cp_fold_data data (flags);
+ cp_fold_immediate_r (&x, &walk_subtrees, &data);
+ if (x != org_x)
+ {
+ x = cp_fold (x, flags);
+ break;
+ }
+ }
loc = EXPR_LOCATION (x);
op0 = cp_fold_maybe_rvalue (TREE_OPERAND (x, 0), false, flags);
@@ -3320,6 +3356,17 @@ cp_fold (tree x, fold_flags_t flags)
case CALL_EXPR:
{
+ if (cxx_dialect >= cxx20)
+ {
+ int walk_subtrees = 0;
+ cp_fold_data data (flags);
+ cp_fold_immediate_r (&x, &walk_subtrees, &data);
+ if (x != org_x)
+ {
+ x = cp_fold (x, flags);
+ break;
+ }
+ }
tree callee = get_callee_fndecl (x);
/* "Inline" calls to std::move/forward and other cast-like functions
--- gcc/testsuite/g++.target/i386/pr118068.C.jj 2025-03-07 10:38:15.551662990
+0100
+++ gcc/testsuite/g++.target/i386/pr118068.C 2025-03-07 10:38:15.551662990
+0100
@@ -0,0 +1,17 @@
+// PR target/118068
+// { dg-do compile { target c++20 } }
+// { dg-options "-O0 -mavx" }
+
+typedef float V __attribute__((vector_size (32)));
+
+consteval unsigned char
+foo (int x)
+{
+ return x;
+}
+
+V
+bar (V x, V y)
+{
+ return __builtin_ia32_blendps256 (x, y, (int) foo (0x23));
+}
Jakub