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  <ja...@redhat.com>
> > 
> >     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  <ja...@redhat.com>

        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

Reply via email to