On 2/5/25 12:02 PM, Jakub Jelinek wrote:
Hi!

As the following testcase show (note, just for-{3,4,6,7,8}.C, constexpr-86769.C
and stmtexpr27.C FAIL without the patch, the rest is just that I couldn't
find coverage for some details and so added tests we don't regress or for5.C
is from Marek's attempt in the PR), we weren't correctly handling for/while
loops with declarations as conditions.

The C++ FE has the simplify_loop_decl_cond function which transforms
such loops as mentioned in the comment:
             while (A x = 42) { }
             for (; A x = 42;) { }
    becomes
             while (true) { A x = 42; if (!x) break; }
             for (;;) { A x = 42; if (!x) break; }
For for loops this is not enough, as the x declaration should be
still in scope when expr (if any) is executed, and injecting the
expr expression into the body in the FE needs to have the continue
label in between, something normally added by the c-family
genericization.  One of my thoughts was to just add there an artificial
label plus the expr expression in the FE and tell c-family about that
label, so that it doesn't create it but uses what has been emitted.

Unfortunately break/continue are resolved to labels only at c-family
genericization time and by moving the condition (and its preparation
statements such as the DECL_EXPR) into the body (and perhaps by also
moving there the (increment) expr as well) we resolve incorrectly any
break/continue statement appearing in cond (or newly perhaps also expr)
expression(s).  While in standard C++ one can't have something like that
there, with statement expressions they are possible there, and we actually
have testsuite coverage that when they appear outside of the body of the
loop they bind to an outer loop rather than the inner one.  When the FE
moves everything into the body, c-family can't distinguish any more between
the user body vs. the condition/preparation statements vs. expr expression.

So, the following patch instead keeps them separate and does the merging
only at the c-family loop genericization time.  For that the patch
introduces two new operands of FOR_STMT and WHILE_STMT, *_COND_PREP
which is forced to be a BIND_EXPR which contains the preparation statements
like DECL_EXPR, and the initialization of that variable, so basically what
{FOR,WHILE}_BODY has when we encounter the function dealing with this,
except one optional CLEANUP_STMT at the end which holds cleanups for the
variable if it needs to be destructed.  This CLEANUP_STMT is removed and
the actual cleanup saved into another new operand, *_COND_CLEANUP.

The c-family loop genericization handles such loops roughly the way
https://eel.is/c++draft/stmt.for and https://eel.is/c++draft/stmt.while
specifies, so the body is (if *_COND_CLEANUP is non-NULL)
{ A x = 42; try { if (!x) break; body; cont_label: expr; } finally { cleanup; } 
}
and otherwise
{ A x = 42; if (!x) break; body; cont_label: expr; }
i.e. the *_COND, *_BODY, optional continue label, FOR_EXPR  are appended
into the body of the *_COND_PREP BIND_EXPR.

And when doing constexpr evaluation of such FOR/WHILE loops, we treat
it similarly, first evaluate *_COND_PREP except the
       for (tree decl = BIND_EXPR_VARS (t); decl; decl = DECL_CHAIN (decl))
         destroy_value_checked (ctx, decl, non_constant_p);
part of BIND_EXPR handling for it, then evaluate *_COND (and decide based
on whether it was false or true like before), then *_BODY, then FOR_EXPR,
then *_COND_CLEANUP (similarly to the way how CLEANUP_STMT handling handles
that) and finally do those destroy_value_checked.

Note, the constexpr-86769.C testcase FAILs with both clang++ and MSVC (note,
the rest of tests PASS with clang++) but I believe it must be just a bug
in those compilers, new int is done in all the constructors and delete is
done in the destructor, so when clang++ reports one of the new int weren't
deallocated during constexpr evaluation I don't see how that would be
possible.  When the same test has all the constexpr stuff, all the new int
are properly deleted at runtime when compiled by both compilers and valgrind
is happy about it, no leaks.

Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?

2025-02-05  Jakub Jelinek  <ja...@redhat.com>

        PR c++/86769
gcc/c-family/
        * c-common.def (FOR_STMT): Add 2 operands and document them.
        (WHILE_STMT): Likewise.
        * c-common.h (WHILE_COND_PREP, WHILE_COND_CLEANUP): Define.
        (FOR_COND_PREP, FOR_COND_CLEANUP): Define.
        * c-gimplify.cc (genericize_c_loop): Add COND_PREP and COND_CLEANUP
        arguments, handle them if they are non-NULL.
        (genericize_for_stmt, genericize_while_stmt, genericize_do_stmt):
        Adjust callers.
gcc/c/
        * c-parser.cc (c_parser_while_statement): Add 2 further NULL_TREE
        operands to build_stmt.
        (c_parser_for_statement): Likewise.
gcc/cp/
        * semantics.cc (simplify_loop_decl_cond): Remove.
        (adjust_loop_decl_cond): New function.
        (begin_while_stmt): Add 2 further NULL_TREE operands to build_stmt.
        (finish_while_stmt_cond): Call adjust_loop_decl_cond instead of
        simplify_loop_decl_cond.
        (set_cond_cleanup_locs): New function.
        (finish_while_stmt): Call do_poplevel also on WHILE_COND_PREP if
        non-NULL and also use pop_stmt_list rather than do_poplevel for
        WHILE_BODY in that case.  Call set_cond_cleanup_locs.
        (begin_for_stmt): Add 2 further NULL_TREE operands to build_stmt.
        (finish_for_cond): Call adjust_loop_decl_cond instead of
        simplify_loop_decl_cond.
        (finish_for_stmt): Call do_poplevel also on FOR_COND_PREP if non-NULL
        and also use pop_stmt_list rather than do_poplevel for FOR_BODY in
        that case.  Call set_cond_cleanup_locs.
        * constexpr.cc (cxx_eval_loop_expr): Handle
        {WHILE,FOR}_COND_{PREP,CLEANUP}.
        (check_for_return_continue): Handle {WHILE,FOR}_COND_PREP.
        (potential_constant_expression_1): RECUR on
        {WHILE,FOR}_COND_{PREP,CLEANUP}.
gcc/testsuite/
        * g++.dg/diagnostic/redeclaration-7.C: New test.
        * g++.dg/expr/for3.C: New test.
        * g++.dg/expr/for4.C: New test.
        * g++.dg/expr/for5.C: New test.
        * g++.dg/expr/for6.C: New test.
        * g++.dg/expr/for7.C: New test.
        * g++.dg/expr/for8.C: New test.
        * g++.dg/ext/stmtexpr27.C: New test.
        * g++.dg/cpp2a/constexpr-86769.C: New test.
        * g++.dg/cpp26/name-independent-decl7.C: New test.
        * g++.dg/cpp26/name-independent-decl8.C: New test.

--- gcc/c-family/c-gimplify.cc.jj       2025-01-16 21:35:00.140478355 +0100
+++ gcc/c-family/c-gimplify.cc  2025-02-05 10:35:13.147724490 +0100
@@ -254,24 +254,31 @@ expr_loc_or_loc (const_tree expr, locati
     controlled by the loop.  INCR is the increment expression of a for-loop,
     or NULL_TREE.  COND_IS_FIRST indicates whether the condition is
     evaluated before the loop body as in while and for loops, or after the
-   loop body as in do-while loops.  */
+   loop body as in do-while loops.  COND_PREP and COND_CLEANUP are used
+   for C++ for/while loops with variable declaration as condition.  COND_PREP
+   is a BIND_EXPR with the declaration and preparation statements, into which

Let's clarify "declaration and preparation statements" to "declaration and initialization of the condition variable".

+   COND, BODY, continue label if needed and INCR if non-NULL should be
+   appended, and COND_CLEANUP are statements which should be evaluated after
+   that or if anything in COND, BODY or INCR throws.  */
static void
  genericize_c_loop (tree *stmt_p, location_t start_locus, tree cond, tree body,
-                  tree incr, tree name, bool cond_is_first,
-                  int *walk_subtrees, void *data, walk_tree_fn func,
-                  walk_tree_lh lh)
+                  tree incr, tree name, tree cond_prep, tree cond_cleanup,
+                  bool cond_is_first, int *walk_subtrees, void *data,
+                  walk_tree_fn func, walk_tree_lh lh)
  {
@@ -284,9 +291,66 @@ genericize_c_loop (tree *stmt_p, locatio
    if (name)
      release_named_bc (name);
- /* If condition is zero don't generate a loop construct. */
-  if (cond && integer_zerop (cond))
+  if (cond_prep)
      {
+      /* The C++ cases of
+        while (A x = 42) body;
+        for (; A x = 42; expr) body;
+        This should be expanded into:
+
+        top:
+        COND_PREP
+
+        with either
+
+        if (COND); else break;
+        BODY;
+        cont:
+        EXPR;
+        goto top;
+
+        or
+
+        try {
+          if (COND); else break;
+          BODY;
+          cont:
+          EXPR;
+        } finally {
+          COND_CLEANUP
+        }
+
+        appended into COND_PREP body.  */
+      gcc_assert (cond_is_first && TREE_CODE (cond_prep) == BIND_EXPR);
+      tree top = build1 (LABEL_EXPR, void_type_node,
+                        create_artificial_label (start_locus));
+      append_to_statement_list (top, &outer_stmt_list);
+      append_to_statement_list (cond_prep, &outer_stmt_list);
+      stmt_list = BIND_EXPR_BODY (cond_prep);
+      BIND_EXPR_BODY (cond_prep) = NULL_TREE;
+      stmt_list_p = &BIND_EXPR_BODY (cond_prep);
+      if (cond_cleanup && TREE_SIDE_EFFECTS (cond_cleanup))
+       {
+         t = build2_loc (EXPR_LOCATION (cond_cleanup), TRY_FINALLY_EXPR,
+                         void_type_node, NULL_TREE, cond_cleanup);
+         append_to_statement_list (t, &stmt_list);
+         *stmt_list_p = stmt_list;
+         stmt_list_p = &TREE_OPERAND (t, 0);
+         stmt_list = NULL_TREE;
+       }
+      exit = build1 (GOTO_EXPR, void_type_node, LABEL_EXPR_LABEL (top));

Let's move this assignment up to just after the declaration of top, to match the usual case.

+      tree after_cond = create_artificial_label (cond_locus);
+      tree goto_after_cond = build1 (GOTO_EXPR, void_type_node, after_cond);
+      t = build1 (GOTO_EXPR, void_type_node, get_bc_label (bc_break));
+      t = fold_build3_loc (cond_locus, COND_EXPR, void_type_node, cond,
+                          goto_after_cond, t);
+      append_to_statement_list (t, &stmt_list);
+      t = build1 (LABEL_EXPR, void_type_node, after_cond);
+      append_to_statement_list (t, &stmt_list);
+    }
+  else if (cond && integer_zerop (cond))
+    {
+      /* If condition is zero don't generate a loop construct.  */
        if (cond_is_first)
        {
          t = build1_loc (start_locus, GOTO_EXPR, void_type_node,
@@ -383,6 +447,11 @@ genericize_c_loop (tree *stmt_p, locatio
        append_to_statement_list (d, &stmt_list);
      }
    append_to_statement_list (exit, &stmt_list);
+  if (cond_prep)

Let's make this if (stmt_list_p).

+    {
+      *stmt_list_p = stmt_list;
+      stmt_list = outer_stmt_list;
+    }

@@ -1406,7 +1418,25 @@ finish_while_stmt_cond (tree cond, tree
                                      build_int_cst (integer_type_node,
                                                     annot_expr_no_vector_kind),
                                      integer_zero_node);
-  simplify_loop_decl_cond (&WHILE_COND (while_stmt), WHILE_BODY (while_stmt));
+  adjust_loop_decl_cond (&WHILE_BODY (while_stmt),
+                        &WHILE_COND_PREP (while_stmt),
+                        &WHILE_COND_CLEANUP (while_stmt));
+}
+
+/* Set EXPR_LOCATION of the {FOR,WHILE}_COND_CLEANUP to LOC.  */
+
+static void
+set_cond_cleanup_locs (tree cond_cleanup, location_t loc)

This should share code with set_cleanup_locs (and perhaps the setting to UNKNOWN_LOCATION in cxx_maybe_build_cleanup) rather than duplicating it.

@@ -7202,6 +7210,28 @@ cxx_eval_loop_expr (const constexpr_ctx
            cxx_eval_constant_expression (ctx, expr, vc_prvalue,
                                          non_constant_p, overflow_p,
                                          jump_target);
+
+         if (cond_cleanup && !*non_constant_p)
+           {
+             iloc_sentinel ils (loc);

What does this do, given that you're setting the locations on the cleanups?

I guess this was copied from the CLEANUP_STMT handling, but I'm not sure why it's there either; commenting it out there doesn't seem to break any constexpr* tests.

+             /* Also evaluate the cleanup.  */
+             cxx_eval_constant_expression (ctx, cond_cleanup, vc_discard,
+                                           non_constant_p, overflow_p);
+           }
+         if (cond_prep)
+           for (tree decl = BIND_EXPR_VARS (cond_prep);
+                decl; decl = DECL_CHAIN (decl))
+             destroy_value_checked (ctx, decl, non_constant_p);
+       }

Let's share this repeated code with the copy below by using a lambda, as in the attached.

Jason
From decd770bd0dc399ff375c47d6dd4edd06d4aae50 Mon Sep 17 00:00:00 2001
From: Jason Merrill <ja...@redhat.com>
Date: Thu, 6 Feb 2025 10:19:33 -0500
Subject: [PATCH] lambda

---
 gcc/cp/constexpr.cc | 36 ++++++++++++------------------------
 1 file changed, 12 insertions(+), 24 deletions(-)

diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
index cf19db20c89..7d2f6aade20 100644
--- a/gcc/cp/constexpr.cc
+++ b/gcc/cp/constexpr.cc
@@ -7154,7 +7154,6 @@ cxx_eval_loop_expr (const constexpr_ctx *ctx, tree t,
   tree body, cond = NULL_TREE, expr = NULL_TREE;
   tree cond_prep = NULL_TREE, cond_cleanup = NULL_TREE;
   int count = 0;
-  location_t loc = cp_expr_loc_or_input_loc (t);
   switch (TREE_CODE (t))
     {
     case LOOP_EXPR:
@@ -7189,6 +7188,16 @@ cxx_eval_loop_expr (const constexpr_ctx *ctx, tree t,
     }
   if (cond_prep)
     gcc_assert (TREE_CODE (cond_prep) == BIND_EXPR);
+  auto cleanup_cond = [=] {
+    /* Clean up the condition variable after each iteration.  */
+    if (cond_cleanup && !*non_constant_p)
+      cxx_eval_constant_expression (ctx, cond_cleanup, vc_discard,
+				    non_constant_p, overflow_p);
+    if (cond_prep)
+      for (tree decl = BIND_EXPR_VARS (cond_prep);
+	   decl; decl = DECL_CHAIN (decl))
+	destroy_value_checked (ctx, decl, non_constant_p);
+  };
   do
     {
       if (count != -1)
@@ -7210,18 +7219,7 @@ cxx_eval_loop_expr (const constexpr_ctx *ctx, tree t,
 	    cxx_eval_constant_expression (ctx, expr, vc_prvalue,
 					  non_constant_p, overflow_p,
 					  jump_target);
-
-	  if (cond_cleanup && !*non_constant_p)
-	    {
-	      iloc_sentinel ils (loc);
-	      /* Also evaluate the cleanup.  */
-	      cxx_eval_constant_expression (ctx, cond_cleanup, vc_discard,
-					    non_constant_p, overflow_p);
-	    }
-	  if (cond_prep)
-	    for (tree decl = BIND_EXPR_VARS (cond_prep);
-		 decl; decl = DECL_CHAIN (decl))
-	      destroy_value_checked (ctx, decl, non_constant_p);
+	  cleanup_cond ();
 	}
 
       if (cond_prep)
@@ -7269,17 +7267,7 @@ cxx_eval_loop_expr (const constexpr_ctx *ctx, tree t,
 	 && (!switches (jump_target) || count == 0)
 	 && !*non_constant_p);
 
-  if (cond_cleanup && !*non_constant_p)
-    {
-      iloc_sentinel ils (loc);
-      /* Also evaluate the cleanup.  */
-      cxx_eval_constant_expression (ctx, cond_cleanup, vc_discard,
-				    non_constant_p, overflow_p);
-    }
-  if (cond_prep)
-    for (tree decl = BIND_EXPR_VARS (cond_prep);
-	 decl; decl = DECL_CHAIN (decl))
-      destroy_value_checked (ctx, decl, non_constant_p);
+  cleanup_cond ();
 
   return NULL_TREE;
 }
-- 
2.48.0

Reply via email to