Repository: llvm/llvm-project
Pull request #134181: [CIR] Upstream support for break and continue statements
Author: login: andykaylor
Labels: name: clang
description: Clang issues not falling into any other category
name: ClangIR
description: Anything related to the ClangIR project
Created: 2025-04-03T00:09:06Z | Merged: 2025-04-04T22:53:11Z

This adds ClangIR support for break and continue statements in loops.

Because only loops are currently implemented upstream in CIR, only breaks in loops are supported here, but this same code will work (with minor changes to the verification and cfg flattening) when switch statements are upstreamed.


Diff:
diff --git a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
index e666be0b25d75..900d78c401ebf 100644
--- a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
+++ b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
@@ -136,6 +136,16 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
     return create<cir::ForOp>(loc, condBuilder, bodyBuilder, stepBuilder);
   }
 
+  /// Create a break operation.
+  cir::BreakOp createBreak(mlir::Location loc) {
+    return create<cir::BreakOp>(loc);
+  }
+
+  /// Create a continue operation.
+  cir::ContinueOp createContinue(mlir::Location loc) {
+    return create<cir::ContinueOp>(loc);
+  }
+
   mlir::TypedAttr getConstPtrAttr(mlir::Type type, int64_t value) {
     auto valueAttr = mlir::IntegerAttr::get(
         mlir::IntegerType::get(type.getContext(), 64), value);
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 3965372755685..6030f65c1fcaa 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -477,7 +477,7 @@ def ConditionOp : CIR_Op<"condition", [
     `cir.bool` operand and, depending on its value, may branch to different
     regions:
 
-     - When in the `cond` region of a `cir.loop`, it continues the loop
+     - When in the `cond` region of a loop, it continues the loop
        if true, or exits it if false.
      - When in the `ready` region of a `cir.await`, it branches to the `resume`
        region when true, and to the `suspend` region when false.
@@ -485,12 +485,12 @@ def ConditionOp : CIR_Op<"condition", [
     Example:
 
     ```mlir
-    cir.loop for(cond : {
-      cir.condition(%arg0) // Branches to `step` region or exits.
-    }, step : {
-      [...]
-    }) {
-      [...]
+    cir.for cond {
+      cir.condition(%val) // Branches to `step` region or exits.
+    } body {
+      cir.yield
+    } step {
+      cir.yield
     }
 
     cir.await(user, ready : {
@@ -569,6 +569,36 @@ def YieldOp : CIR_Op<"yield", [ReturnLike, Terminator,
   ];
 }
 
+//===----------------------------------------------------------------------===//
+// BreakOp
+//===----------------------------------------------------------------------===//
+
+def BreakOp : CIR_Op<"break", [Terminator]> {
+  let summary = "C/C++ `break` statement equivalent";
+  let description = [{
+    The `cir.break` operation is used to cease the execution of the current loop
+    or switch operation and transfer control to the parent operation. It is only
+    allowed within a breakable operations (loops and switches).
+  }];
+  let assemblyFormat = "attr-dict";
+  let hasVerifier = 1;
+}
+
+//===----------------------------------------------------------------------===//
+// ContinueOp
+//===----------------------------------------------------------------------===//
+
+def ContinueOp : CIR_Op<"continue", [Terminator]> {
+  let summary = "C/C++ `continue` statement equivalent";
+  let description = [{
+    The `cir.continue` operation is used to end execution of the current
+    iteration of a loop and resume execution beginning at the next iteration.
+    It is only allowed within loop regions.
+  }];
+  let assemblyFormat = "attr-dict";
+  let hasVerifier = 1;
+}
+
 //===----------------------------------------------------------------------===//
 // ScopeOp
 //===----------------------------------------------------------------------===//
diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h
index 3a102d90aba8f..be16b5c063f3b 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -122,12 +122,10 @@ struct MissingFeatures {
 
   // Future CIR operations
   static bool awaitOp() { return false; }
-  static bool breakOp() { return false; }
   static bool callOp() { return false; }
   static bool complexCreateOp() { return false; }
   static bool complexImagOp() { return false; }
   static bool complexRealOp() { return false; }
-  static bool continueOp() { return false; }
   static bool ifOp() { return false; }
   static bool labelOp() { return false; }
   static bool ptrDiffOp() { return false; }
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index 5cae4d5da9516..c244e3752d7fe 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -395,6 +395,8 @@ class CIRGenFunction : public CIRGenTypeCache {
 
   LValue emitBinaryOperatorLValue(const BinaryOperator *e);
 
+  mlir::LogicalResult emitBreakStmt(const clang::BreakStmt &s);
+  mlir::LogicalResult emitContinueStmt(const clang::ContinueStmt &s);
   mlir::LogicalResult emitDoStmt(const clang::DoStmt &s);
 
   /// Emit an expression as an initializer for an object (variable, field, etc.)
diff --git a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
index b5c1f0ae2a7ef..00d33e7feddff 100644
--- a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
@@ -56,6 +56,12 @@ mlir::LogicalResult CIRGenFunction::emitStmt(const Stmt *s,
     return mlir::success();
 
   switch (s->getStmtClass()) {
+  case Stmt::BreakStmtClass:
+  case Stmt::CompoundStmtClass:
+  case Stmt::ContinueStmtClass:
+  case Stmt::DeclStmtClass:
+  case Stmt::ReturnStmtClass:
+    llvm_unreachable("should have emitted these statements as simple");
 
 #define STMT(Type, Base)
 #define ABSTRACT_STMT(Op)
@@ -88,13 +94,9 @@ mlir::LogicalResult CIRGenFunction::emitStmt(const Stmt *s,
   case Stmt::SEHFinallyStmtClass:
   case Stmt::MSDependentExistsStmtClass:
   case Stmt::NullStmtClass:
-  case Stmt::CompoundStmtClass:
-  case Stmt::DeclStmtClass:
   case Stmt::LabelStmtClass:
   case Stmt::AttributedStmtClass:
   case Stmt::GotoStmtClass:
-  case Stmt::BreakStmtClass:
-  case Stmt::ContinueStmtClass:
   case Stmt::DefaultStmtClass:
   case Stmt::CaseStmtClass:
   case Stmt::SEHLeaveStmtClass:
@@ -106,7 +108,6 @@ mlir::LogicalResult CIRGenFunction::emitStmt(const Stmt *s,
   case Stmt::CXXTryStmtClass:
   case Stmt::CXXForRangeStmtClass:
   case Stmt::IndirectGotoStmtClass:
-  case Stmt::ReturnStmtClass:
   case Stmt::GCCAsmStmtClass:
   case Stmt::MSAsmStmtClass:
   case Stmt::OMPParallelDirectiveClass:
@@ -219,7 +220,6 @@ mlir::LogicalResult CIRGenFunction::emitSimpleStmt(const Stmt *s,
                                                    bool useCurrentScope) {
   switch (s->getStmtClass()) {
   default:
-    // Only compound and return statements are supported right now.
     return mlir::failure();
   case Stmt::DeclStmtClass:
     return emitDeclStmt(cast<DeclStmt>(*s));
@@ -229,6 +229,10 @@ mlir::LogicalResult CIRGenFunction::emitSimpleStmt(const Stmt *s,
     else
       emitCompoundStmt(cast<CompoundStmt>(*s));
     break;
+  case Stmt::ContinueStmtClass:
+    return emitContinueStmt(cast<ContinueStmt>(*s));
+  case Stmt::BreakStmtClass:
+    return emitBreakStmt(cast<BreakStmt>(*s));
   case Stmt::ReturnStmtClass:
     return emitReturnStmt(cast<ReturnStmt>(*s));
   }
@@ -316,6 +320,25 @@ mlir::LogicalResult CIRGenFunction::emitReturnStmt(const ReturnStmt &s) {
   return mlir::success();
 }
 
+mlir::LogicalResult
+CIRGenFunction::emitContinueStmt(const clang::ContinueStmt &s) {
+  builder.createContinue(getLoc(s.getContinueLoc()));
+
+  // Insert the new block to continue codegen after the continue statement.
+  builder.createBlock(builder.getBlock()->getParent());
+
+  return mlir::success();
+}
+
+mlir::LogicalResult CIRGenFunction::emitBreakStmt(const clang::BreakStmt &s) {
+  builder.createBreak(getLoc(s.getBreakLoc()));
+
+  // Insert the new block to continue codegen after the break statement.
+  builder.createBlock(builder.getBlock()->getParent());
+
+  return mlir::success();
+}
+
 mlir::LogicalResult CIRGenFunction::emitForStmt(const ForStmt &s) {
   cir::ForOp forOp;
 
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 4ace083e3c081..ccbc075ae7d9d 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -151,6 +151,17 @@ void cir::AllocaOp::build(mlir::OpBuilder &odsBuilder,
   odsState.addTypes(addr);
 }
 
+//===----------------------------------------------------------------------===//
+// BreakOp
+//===----------------------------------------------------------------------===//
+
+LogicalResult cir::BreakOp::verify() {
+  assert(!cir::MissingFeatures::switchOp());
+  if (!getOperation()->getParentOfType<LoopOpInterface>())
+    return emitOpError("must be within a loop");
+  return success();
+}
+
 //===----------------------------------------------------------------------===//
 // ConditionOp
 //===----------------------------------------------------------------------===//
@@ -241,6 +252,16 @@ OpFoldResult cir::ConstantOp::fold(FoldAdaptor /*adaptor*/) {
   return getValue();
 }
 
+//===----------------------------------------------------------------------===//
+// ContinueOp
+//===----------------------------------------------------------------------===//
+
+LogicalResult cir::ContinueOp::verify() {
+  if (!getOperation()->getParentOfType<LoopOpInterface>())
+    return emitOpError("must be within a loop");
+  return success();
+}
+
 //===----------------------------------------------------------------------===//
 // CastOp
 //===----------------------------------------------------------------------===//
diff --git a/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp b/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp
index 52f4b2241505d..b49c5ffd35c00 100644
--- a/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp
@@ -148,23 +148,24 @@ class CIRLoopOpInterfaceFlattening
     // driver to customize the order that operations are visited.
 
     // Lower continue statements.
+    mlir::Block *dest = (step ? step : cond);
     op.walkBodySkippingNestedLoops([&](mlir::Operation *op) {
-      // When continue ops are supported, there will be a check for them here
-      // and a call to lowerTerminator(). The call to `advance()` handles the
-      // case where this is not a continue op.
-      assert(!cir::MissingFeatures::continueOp());
-      return mlir::WalkResult::advance();
+      if (!isa<cir::ContinueOp>(op))
+        return mlir::WalkResult::advance();
+
+      lowerTerminator(op, dest, rewriter);
+      return mlir::WalkResult::skip();
     });
 
     // Lower break statements.
     assert(!cir::MissingFeatures::switchOp());
     walkRegionSkipping<cir::LoopOpInterface>(
         op.getBody(), [&](mlir::Operation *op) {
-          // When break ops are supported, there will be a check for them here
-          // and a call to lowerTerminator(). The call to `advance()` handles
-          // the case where this is not a break op.
-          assert(!cir::MissingFeatures::breakOp());
-          return mlir::WalkResult::advance();
+          if (!isa<cir::BreakOp>(op))
+            return mlir::WalkResult::advance();
+
+          lowerTerminator(op, exit, rewriter);
+          return mlir::WalkResult::skip();
         });
 
     // Lower optional body region yield.
diff --git a/clang/test/CIR/CodeGen/loop.cpp b/clang/test/CIR/CodeGen/loop.cpp
index a950460e8838d..46fa66e2fc7aa 100644
--- a/clang/test/CIR/CodeGen/loop.cpp
+++ b/clang/test/CIR/CodeGen/loop.cpp
@@ -265,3 +265,125 @@ void test_empty_while_true() {
 // OGCG:   br label %[[WHILE_BODY:.*]]
 // OGCG: [[WHILE_BODY]]:
 // OGCG:   ret void
+
+void unreachable_after_continue() {
+  for (;;) {
+    continue;
+    int x = 1;
+  }
+}
+
+// CIR: cir.func @unreachable_after_continue
+// CIR:   cir.scope {
+// CIR:     cir.for : cond {
+// CIR:       %[[TRUE:.*]] = cir.const #true
+// CIR:       cir.condition(%[[TRUE]])
+// CIR:     } body {
+// CIR:       cir.scope {
+// CIR:         %[[X:.*]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["x", init] {alignment = 4 : i64}
+// CIR:         cir.continue
+// CIR:       ^bb1:  // no predecessors
+// CIR:         %[[ONE:.*]] = cir.const #cir.int<1> : !s32i
+// CIR:         cir.store %[[ONE]], %[[X]] : !s32i, !cir.ptr<!s32i>
+// CIR:         cir.yield
+// CIR:       }
+// CIR:       cir.yield
+// CIR:     } step {
+// CIR:       cir.yield
+// CIR:     }
+// CIR:   }
+// CIR:   cir.return
+// CIR: }
+
+// LLVM: define void @unreachable_after_continue()
+// LLVM:   %[[X:.*]] = alloca i32, i64 1, align 4
+// LLVM:   br label %[[LABEL1:.*]]
+// LLVM: [[LABEL1]]:
+// LLVM:   br label %[[LABEL2:.*]]
+// LLVM: [[LABEL2]]:
+// LLVM:   br i1 true, label %[[LABEL3:.*]], label %[[LABEL8:.*]]
+// LLVM: [[LABEL3]]:
+// LLVM:   br label %[[LABEL4:.*]]
+// LLVM: [[LABEL4]]:
+// LLVM:   br label %[[LABEL7:.*]]
+// LLVM: [[LABEL5:.*]]:
+// LLVM-SAME: ; No predecessors!
+// LLVM:   store i32 1, ptr %[[X]], align 4
+// LLVM:   br label %[[LABEL6:.*]]
+// LLVM: [[LABEL6]]:
+// LLVM:   br label %[[LABEL7:.*]]
+// LLVM: [[LABEL7]]:
+// LLVM:   br label %[[LABEL2]]
+// LLVM: [[LABEL8]]:
+// LLVM:   br label %[[LABEL9:]]
+// LLVM: [[LABEL9]]:
+// LLVM:   ret void
+
+// OGCG: define{{.*}} void @_Z26unreachable_after_continuev()
+// OGCG: entry:
+// OGCG:   %[[X:.*]] = alloca i32, align 4
+// OGCG:   br label %[[FOR_COND:.*]]
+// OGCG: [[FOR_COND]]:
+// OGCG:   br label %[[FOR_COND]]
+
+void unreachable_after_break() {
+  for (;;) {
+    break;
+    int x = 1;
+  }
+}
+
+// CIR: cir.func @unreachable_after_break
+// CIR:   cir.scope {
+// CIR:     cir.for : cond {
+// CIR:       %[[TRUE:.*]] = cir.const #true
+// CIR:       cir.condition(%[[TRUE]])
+// CIR:     } body {
+// CIR:       cir.scope {
+// CIR:         %[[X:.*]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["x", init] {alignment = 4 : i64}
+// CIR:         cir.break
+// CIR:       ^bb1:  // no predecessors
+// CIR:         %[[ONE:.*]] = cir.const #cir.int<1> : !s32i
+// CIR:         cir.store %[[ONE]], %[[X]] : !s32i, !cir.ptr<!s32i>
+// CIR:         cir.yield
+// CIR:       }
+// CIR:       cir.yield
+// CIR:     } step {
+// CIR:       cir.yield
+// CIR:     }
+// CIR:   }
+// CIR:   cir.return
+// CIR: }
+
+// LLVM: define void @unreachable_after_break()
+// LLVM:   %[[X:.*]] = alloca i32, i64 1, align 4
+// LLVM:   br label %[[LABEL1:.*]]
+// LLVM: [[LABEL1]]:
+// LLVM:   br label %[[LABEL2:.*]]
+// LLVM: [[LABEL2]]:
+// LLVM:   br i1 true, label %[[LABEL3:.*]], label %[[LABEL8:.*]]
+// LLVM: [[LABEL3]]:
+// LLVM:   br label %[[LABEL4:.*]]
+// LLVM: [[LABEL4]]:
+// LLVM:   br label %[[LABEL8]]
+// LLVM: [[LABEL5:.*]]:
+// LLVM-SAME: ; No predecessors!
+// LLVM:   store i32 1, ptr %[[X]], align 4
+// LLVM:   br label %[[LABEL6:.*]]
+// LLVM: [[LABEL6]]:
+// LLVM:   br label %[[LABEL7:.*]]
+// LLVM: [[LABEL7]]:
+// LLVM:   br label %[[LABEL2]]
+// LLVM: [[LABEL8]]:
+// LLVM:   br label %[[LABEL9:]]
+// LLVM: [[LABEL9]]:
+// LLVM:   ret void
+
+// OGCG: define{{.*}} void @_Z23unreachable_after_breakv()
+// OGCG: entry:
+// OGCG:   %[[X:.*]] = alloca i32, align 4
+// OGCG:   br label %[[FOR_COND:.*]]
+// OGCG: [[FOR_COND]]:
+// OGCG:   br label %[[FOR_END:.*]]
+// OGCG: [[FOR_END]]:
+// OGCG:   ret void


Reviews:
user: login: xlauko
body: LGTM
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T08:36:49Z
user: login: erichkeane
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-03T15:34:20Z
user: login: andykaylor
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T16:59:20Z
user: login: erichkeane
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-03T17:01:44Z
user: login: bcardosolopes
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-03T18:00:10Z
user: login: bcardosolopes
body: LGTM after other reviews are applied.
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-03T18:00:42Z
user: login: bcardosolopes
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-03T18:08:52Z
user: login: erichkeane
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-03T18:10:56Z
user: login: erichkeane
body: 1 Possible nit, else LGTM.
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-04T01:07:48Z
user: login: andykaylor
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T18:38:38Z
user: login: erichkeane
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-04T18:40:59Z

Review comments:
diff_hunk: @@ -569,6 +569,35 @@ def YieldOp : CIR_Op<"yield", [ReturnLike, Terminator,
   ];
 }
 
+//===----------------------------------------------------------------------===//
+// BreakOp
+//===----------------------------------------------------------------------===//
+
+def BreakOp : CIR_Op<"break", [Terminator]> {
+  let summary = "C/C++ `break` statement equivalent";
+  let description = [{
+    The `cir.break` operation is used to cease the control flow to the parent
+    operation, exiting its region's control flow. It is only allowed if it is
+    within a breakable operation (loops and `switch`).
+  }];
+  let assemblyFormat = "attr-dict";
+  let hasVerifier = 1;
+}
+
+//===----------------------------------------------------------------------===//
+// ContinueOp
+//===----------------------------------------------------------------------===//
+
+def ContinueOp : CIR_Op<"continue", [Terminator]> {
+  let summary = "C/C++ `continue` statement equivalent";
+  let description = [{
+    The `cir.continue` operation is used to continue execution to the next
+    iteration of a loop. It is only allowed within `cir.loop` regions.
path: clang/include/clang/CIR/Dialect/IR/CIROps.td
user: login: xlauko
body: nit: There is technically nothing like `cir.loop`. More precise would be to mention `LoopOpInterface` directly here?
created_at: 2025-04-03T08:36:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -569,6 +569,35 @@ def YieldOp : CIR_Op<"yield", [ReturnLike, Terminator,
   ];
 }
 
+//===----------------------------------------------------------------------===//
+// BreakOp
+//===----------------------------------------------------------------------===//
+
+def BreakOp : CIR_Op<"break", [Terminator]> {
+  let summary = "C/C++ `break` statement equivalent";
+  let description = [{
+    The `cir.break` operation is used to cease the control flow to the parent
path: clang/include/clang/CIR/Dialect/IR/CIROps.td
user: login: erichkeane
body: Won't let me suggest an edit here for some reason, but the 'cease ...to the parent' text seems wrong.  DO we mean 'cede' control flow to the parent?
created_at: 2025-04-03T15:30:26Z
author_association: COLLABORATOR
diff_hunk: @@ -569,6 +569,35 @@ def YieldOp : CIR_Op<"yield", [ReturnLike, Terminator,
   ];
 }
 
+//===----------------------------------------------------------------------===//
+// BreakOp
+//===----------------------------------------------------------------------===//
+
+def BreakOp : CIR_Op<"break", [Terminator]> {
+  let summary = "C/C++ `break` statement equivalent";
+  let description = [{
+    The `cir.break` operation is used to cease the control flow to the parent
+    operation, exiting its region's control flow. It is only allowed if it is
+    within a breakable operation (loops and `switch`).
+  }];
+  let assemblyFormat = "attr-dict";
+  let hasVerifier = 1;
+}
+
+//===----------------------------------------------------------------------===//
+// ContinueOp
+//===----------------------------------------------------------------------===//
+
+def ContinueOp : CIR_Op<"continue", [Terminator]> {
+  let summary = "C/C++ `continue` statement equivalent";
+  let description = [{
+    The `cir.continue` operation is used to continue execution to the next
path: clang/include/clang/CIR/Dialect/IR/CIROps.td
user: login: erichkeane
body: ```suggestion
    The `cir.continue` operation is used end execution of the current iteration of the loop, and resume execution of the next
```

Or something like that.  
created_at: 2025-04-03T15:31:20Z
author_association: COLLABORATOR
diff_hunk: @@ -229,6 +229,10 @@ mlir::LogicalResult CIRGenFunction::emitSimpleStmt(const Stmt *s,
     else
       emitCompoundStmt(cast<CompoundStmt>(*s));
     break;
+  case Stmt::ContinueStmtClass:
path: clang/lib/CIR/CodeGen/CIRGenStmt.cpp
user: login: erichkeane
body: Won't let me choose a better line, but line 222 is not accurate anymroe.
created_at: 2025-04-03T15:32:03Z
author_association: COLLABORATOR
diff_hunk: @@ -265,3 +265,125 @@ void test_empty_while_true() {
 // OGCG:   br label %[[WHILE_BODY:.*]]
 // OGCG: [[WHILE_BODY]]:
 // OGCG:   ret void
+
+void unreachable_after_continue() {
+  for (;;) {
+    continue;
+    int x = 1;
+  }
+}
+
+// CIR: cir.func @unreachable_after_continue
+// CIR:   cir.scope {
+// CIR:     cir.for : cond {
+// CIR:       %[[TRUE:.*]] = cir.const #true
+// CIR:       cir.condition(%[[TRUE]])
+// CIR:     } body {
+// CIR:       cir.scope {
+// CIR:         %[[X:.*]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["x", init] {alignment = 4 : i64}
+// CIR:         cir.continue
+// CIR:       ^bb1:  // no predecessors
+// CIR:         %[[ONE:.*]] = cir.const #cir.int<1> : !s32i
+// CIR:         cir.store %[[ONE]], %[[X]] : !s32i, !cir.ptr<!s32i>
+// CIR:         cir.yield
+// CIR:       }
+// CIR:       cir.yield
+// CIR:     } step {
+// CIR:       cir.yield
+// CIR:     }
+// CIR:   }
+// CIR:   cir.return
+// CIR: }
+
+// LLVM: define void @unreachable_after_continue()
path: clang/test/CIR/CodeGen/loop.cpp
user: login: erichkeane
body: What is going on here?  This seems like some strange unroll happening here, right?  
created_at: 2025-04-03T15:34:12Z
author_association: COLLABORATOR
diff_hunk: @@ -265,3 +265,125 @@ void test_empty_while_true() {
 // OGCG:   br label %[[WHILE_BODY:.*]]
 // OGCG: [[WHILE_BODY]]:
 // OGCG:   ret void
+
+void unreachable_after_continue() {
+  for (;;) {
+    continue;
+    int x = 1;
+  }
+}
+
+// CIR: cir.func @unreachable_after_continue
+// CIR:   cir.scope {
+// CIR:     cir.for : cond {
+// CIR:       %[[TRUE:.*]] = cir.const #true
+// CIR:       cir.condition(%[[TRUE]])
+// CIR:     } body {
+// CIR:       cir.scope {
+// CIR:         %[[X:.*]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["x", init] {alignment = 4 : i64}
+// CIR:         cir.continue
+// CIR:       ^bb1:  // no predecessors
+// CIR:         %[[ONE:.*]] = cir.const #cir.int<1> : !s32i
+// CIR:         cir.store %[[ONE]], %[[X]] : !s32i, !cir.ptr<!s32i>
+// CIR:         cir.yield
+// CIR:       }
+// CIR:       cir.yield
+// CIR:     } step {
+// CIR:       cir.yield
+// CIR:     }
+// CIR:   }
+// CIR:   cir.return
+// CIR: }
+
+// LLVM: define void @unreachable_after_continue()
path: clang/test/CIR/CodeGen/loop.cpp
user: login: andykaylor
body: Yeah, this is a bit messy. This is the result of "flattening" the CIR above and then transcribing that directly to LLVM IR. Each region ends up getting its own block, and the explicit `cir.scope` gets a different block than its implicit parent region. So, the unlabeled entry block is the function parent region. LABEL1 corresponds to the `cir.scope` on line 277 (and is where the initialization would happen). LABEL2 corresponds to the `cir.cond` region of the for loop. LABEL3 is the "step" region. LABEL4 is the entry to the for-loop body. LABEL5 is the portion of the body below the `continue` statement. LABEL6 is where the `cir.yield` on line 288 jumps. LABEL7 is the `cir.yield` statement on line 290. LABEL8 is the portion of the `cir.scope` below the loop (i.e. line 294). And label is the part of the function region below the explicit `cir.scope`.

It would make a bit more sense if most of these regions weren't empty. Ideally, we'd not cleaning all this up in the CIRCanonicalize pass. but I think right now that gets run before CIR flattening. That's something we should revisit.
created_at: 2025-04-03T16:59:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -265,3 +265,125 @@ void test_empty_while_true() {
 // OGCG:   br label %[[WHILE_BODY:.*]]
 // OGCG: [[WHILE_BODY]]:
 // OGCG:   ret void
+
+void unreachable_after_continue() {
+  for (;;) {
+    continue;
+    int x = 1;
+  }
+}
+
+// CIR: cir.func @unreachable_after_continue
+// CIR:   cir.scope {
+// CIR:     cir.for : cond {
+// CIR:       %[[TRUE:.*]] = cir.const #true
+// CIR:       cir.condition(%[[TRUE]])
+// CIR:     } body {
+// CIR:       cir.scope {
+// CIR:         %[[X:.*]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["x", init] {alignment = 4 : i64}
+// CIR:         cir.continue
+// CIR:       ^bb1:  // no predecessors
+// CIR:         %[[ONE:.*]] = cir.const #cir.int<1> : !s32i
+// CIR:         cir.store %[[ONE]], %[[X]] : !s32i, !cir.ptr<!s32i>
+// CIR:         cir.yield
+// CIR:       }
+// CIR:       cir.yield
+// CIR:     } step {
+// CIR:       cir.yield
+// CIR:     }
+// CIR:   }
+// CIR:   cir.return
+// CIR: }
+
+// LLVM: define void @unreachable_after_continue()
path: clang/test/CIR/CodeGen/loop.cpp
user: login: erichkeane
body: Can you run this and paste the CIR and outputted LLVM-IR here for me?  I'm having a hard time visualizing from what you said. 
created_at: 2025-04-03T17:01:44Z
author_association: COLLABORATOR
diff_hunk: @@ -265,3 +265,125 @@ void test_empty_while_true() {
 // OGCG:   br label %[[WHILE_BODY:.*]]
 // OGCG: [[WHILE_BODY]]:
 // OGCG:   ret void
+
+void unreachable_after_continue() {
+  for (;;) {
+    continue;
+    int x = 1;
+  }
+}
+
+// CIR: cir.func @unreachable_after_continue
+// CIR:   cir.scope {
+// CIR:     cir.for : cond {
+// CIR:       %[[TRUE:.*]] = cir.const #true
+// CIR:       cir.condition(%[[TRUE]])
+// CIR:     } body {
+// CIR:       cir.scope {
+// CIR:         %[[X:.*]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["x", init] {alignment = 4 : i64}
+// CIR:         cir.continue
+// CIR:       ^bb1:  // no predecessors
+// CIR:         %[[ONE:.*]] = cir.const #cir.int<1> : !s32i
+// CIR:         cir.store %[[ONE]], %[[X]] : !s32i, !cir.ptr<!s32i>
+// CIR:         cir.yield
+// CIR:       }
+// CIR:       cir.yield
+// CIR:     } step {
+// CIR:       cir.yield
+// CIR:     }
+// CIR:   }
+// CIR:   cir.return
+// CIR: }
+
+// LLVM: define void @unreachable_after_continue()
path: clang/test/CIR/CodeGen/loop.cpp
user: login: bcardosolopes
body: > but I think right now that gets run before CIR flattening. That's something we should revisit.

Agreed, we should probably run it again after flattening / right before lowering. Note that LLVM optimizers are really good at simplifying these, so we haven't cared much enough just yet since even -O1 will do an amazing job here.
created_at: 2025-04-03T18:00:10Z
author_association: MEMBER
diff_hunk: @@ -265,3 +265,125 @@ void test_empty_while_true() {
 // OGCG:   br label %[[WHILE_BODY:.*]]
 // OGCG: [[WHILE_BODY]]:
 // OGCG:   ret void
+
+void unreachable_after_continue() {
+  for (;;) {
+    continue;
+    int x = 1;
+  }
+}
+
+// CIR: cir.func @unreachable_after_continue
+// CIR:   cir.scope {
+// CIR:     cir.for : cond {
+// CIR:       %[[TRUE:.*]] = cir.const #true
+// CIR:       cir.condition(%[[TRUE]])
+// CIR:     } body {
+// CIR:       cir.scope {
+// CIR:         %[[X:.*]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["x", init] {alignment = 4 : i64}
+// CIR:         cir.continue
+// CIR:       ^bb1:  // no predecessors
+// CIR:         %[[ONE:.*]] = cir.const #cir.int<1> : !s32i
+// CIR:         cir.store %[[ONE]], %[[X]] : !s32i, !cir.ptr<!s32i>
+// CIR:         cir.yield
+// CIR:       }
+// CIR:       cir.yield
+// CIR:     } step {
+// CIR:       cir.yield
+// CIR:     }
+// CIR:   }
+// CIR:   cir.return
+// CIR: }
+
+// LLVM: define void @unreachable_after_continue()
path: clang/test/CIR/CodeGen/loop.cpp
user: login: bcardosolopes
body: https://godbolt.org/z/dj67e1dME
created_at: 2025-04-03T18:08:52Z
author_association: MEMBER
diff_hunk: @@ -265,3 +265,125 @@ void test_empty_while_true() {
 // OGCG:   br label %[[WHILE_BODY:.*]]
 // OGCG: [[WHILE_BODY]]:
 // OGCG:   ret void
+
+void unreachable_after_continue() {
+  for (;;) {
+    continue;
+    int x = 1;
+  }
+}
+
+// CIR: cir.func @unreachable_after_continue
+// CIR:   cir.scope {
+// CIR:     cir.for : cond {
+// CIR:       %[[TRUE:.*]] = cir.const #true
+// CIR:       cir.condition(%[[TRUE]])
+// CIR:     } body {
+// CIR:       cir.scope {
+// CIR:         %[[X:.*]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["x", init] {alignment = 4 : i64}
+// CIR:         cir.continue
+// CIR:       ^bb1:  // no predecessors
+// CIR:         %[[ONE:.*]] = cir.const #cir.int<1> : !s32i
+// CIR:         cir.store %[[ONE]], %[[X]] : !s32i, !cir.ptr<!s32i>
+// CIR:         cir.yield
+// CIR:       }
+// CIR:       cir.yield
+// CIR:     } step {
+// CIR:       cir.yield
+// CIR:     }
+// CIR:   }
+// CIR:   cir.return
+// CIR: }
+
+// LLVM: define void @unreachable_after_continue()
path: clang/test/CIR/CodeGen/loop.cpp
user: login: erichkeane
body: Oh, ooof that is hideous.  But looks like it works at least.
created_at: 2025-04-03T18:10:56Z
author_association: COLLABORATOR
diff_hunk: @@ -569,6 +569,36 @@ def YieldOp : CIR_Op<"yield", [ReturnLike, Terminator,
   ];
 }
 
+//===----------------------------------------------------------------------===//
+// BreakOp
+//===----------------------------------------------------------------------===//
+
+def BreakOp : CIR_Op<"break", [Terminator]> {
+  let summary = "C/C++ `break` statement equivalent";
+  let description = [{
+    The `cir.break` operation is used to cease the execution of the current
+    loop or switch and transfer control to the parent operation. It is only
path: clang/include/clang/CIR/Dialect/IR/CIROps.td
user: login: erichkeane
body: ```suggestion
    loop and switch and transfer control to the parent operation. It is only
```

I think?
created_at: 2025-04-04T01:06:51Z
author_association: COLLABORATOR
diff_hunk: @@ -569,6 +569,36 @@ def YieldOp : CIR_Op<"yield", [ReturnLike, Terminator,
   ];
 }
 
+//===----------------------------------------------------------------------===//
+// BreakOp
+//===----------------------------------------------------------------------===//
+
+def BreakOp : CIR_Op<"break", [Terminator]> {
+  let summary = "C/C++ `break` statement equivalent";
+  let description = [{
+    The `cir.break` operation is used to cease the execution of the current
+    loop or switch and transfer control to the parent operation. It is only
path: clang/include/clang/CIR/Dialect/IR/CIROps.td
user: login: andykaylor
body: I don't think so. We're either breaking out of a loop or a switch operation. After breaking out of the loop or switch, we're transferring control to the parent operation. Would this be better?
```suggestion
    loop or switch operation and transfer control to the parent operation. It is only
```
created_at: 2025-04-04T18:38:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -569,6 +569,36 @@ def YieldOp : CIR_Op<"yield", [ReturnLike, Terminator,
   ];
 }
 
+//===----------------------------------------------------------------------===//
+// BreakOp
+//===----------------------------------------------------------------------===//
+
+def BreakOp : CIR_Op<"break", [Terminator]> {
+  let summary = "C/C++ `break` statement equivalent";
+  let description = [{
+    The `cir.break` operation is used to cease the execution of the current
+    loop or switch and transfer control to the parent operation. It is only
path: clang/include/clang/CIR/Dialect/IR/CIROps.td
user: login: erichkeane
body: Oh, yeah, that was a thinko/misread from me.  I like the 'operation' word being there as I think it is an improvement, else LGTM.
created_at: 2025-04-04T18:40:59Z
author_association: COLLABORATOR

PR comments:
user: login: llvmbot
created_at: 2025-04-03T00:09:40Z
body: 
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clangir

@llvm/pr-subscribers-clang

Author: Andy Kaylor (andykaylor)

<details>
<summary>Changes</summary>

This adds ClangIR support for break and continue statements in loops.

Because only loops are currently implemented upstream in CIR, only breaks in loops are supported here, but this same code will work (with minor changes to the verification and cfg flattening) when switch statements are upstreamed.

---
Full diff: https://github.com/llvm/llvm-project/pull/134181.diff


8 Files Affected:

- (modified) clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h (+10) 
- (modified) clang/include/clang/CIR/Dialect/IR/CIROps.td (+29) 
- (modified) clang/include/clang/CIR/MissingFeatures.h (-2) 
- (modified) clang/lib/CIR/CodeGen/CIRGenFunction.h (+2) 
- (modified) clang/lib/CIR/CodeGen/CIRGenStmt.cpp (+23) 
- (modified) clang/lib/CIR/Dialect/IR/CIRDialect.cpp (+21) 
- (modified) clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp (+11-10) 
- (modified) clang/test/CIR/CodeGen/loop.cpp (+122) 


``````````diff
diff --git a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
index e666be0b25d75..900d78c401ebf 100644
--- a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
+++ b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
@@ -136,6 +136,16 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
     return create<cir::ForOp>(loc, condBuilder, bodyBuilder, stepBuilder);
   }
 
+  /// Create a break operation.
+  cir::BreakOp createBreak(mlir::Location loc) {
+    return create<cir::BreakOp>(loc);
+  }
+
+  /// Create a continue operation.
+  cir::ContinueOp createContinue(mlir::Location loc) {
+    return create<cir::ContinueOp>(loc);
+  }
+
   mlir::TypedAttr getConstPtrAttr(mlir::Type type, int64_t value) {
     auto valueAttr = mlir::IntegerAttr::get(
         mlir::IntegerType::get(type.getContext(), 64), value);
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 3965372755685..7ddf5e843bde2 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -569,6 +569,35 @@ def YieldOp : CIR_Op<"yield", [ReturnLike, Terminator,
   ];
 }
 
+//===----------------------------------------------------------------------===//
+// BreakOp
+//===----------------------------------------------------------------------===//
+
+def BreakOp : CIR_Op<"break", [Terminator]> {
+  let summary = "C/C++ `break` statement equivalent";
+  let description = [{
+    The `cir.break` operation is used to cease the control flow to the parent
+    operation, exiting its region's control flow. It is only allowed if it is
+    within a breakable operation (loops and `switch`).
+  }];
+  let assemblyFormat = "attr-dict";
+  let hasVerifier = 1;
+}
+
+//===----------------------------------------------------------------------===//
+// ContinueOp
+//===----------------------------------------------------------------------===//
+
+def ContinueOp : CIR_Op<"continue", [Terminator]> {
+  let summary = "C/C++ `continue` statement equivalent";
+  let description = [{
+    The `cir.continue` operation is used to continue execution to the next
+    iteration of a loop. It is only allowed within `cir.loop` regions.
+  }];
+  let assemblyFormat = "attr-dict";
+  let hasVerifier = 1;
+}
+
 //===----------------------------------------------------------------------===//
 // ScopeOp
 //===----------------------------------------------------------------------===//
diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h
index 3a102d90aba8f..be16b5c063f3b 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -122,12 +122,10 @@ struct MissingFeatures {
 
   // Future CIR operations
   static bool awaitOp() { return false; }
-  static bool breakOp() { return false; }
   static bool callOp() { return false; }
   static bool complexCreateOp() { return false; }
   static bool complexImagOp() { return false; }
   static bool complexRealOp() { return false; }
-  static bool continueOp() { return false; }
   static bool ifOp() { return false; }
   static bool labelOp() { return false; }
   static bool ptrDiffOp() { return false; }
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index 5cae4d5da9516..c244e3752d7fe 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -395,6 +395,8 @@ class CIRGenFunction : public CIRGenTypeCache {
 
   LValue emitBinaryOperatorLValue(const BinaryOperator *e);
 
+  mlir::LogicalResult emitBreakStmt(const clang::BreakStmt &s);
+  mlir::LogicalResult emitContinueStmt(const clang::ContinueStmt &s);
   mlir::LogicalResult emitDoStmt(const clang::DoStmt &s);
 
   /// Emit an expression as an initializer for an object (variable, field, etc.)
diff --git a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
index b5c1f0ae2a7ef..0c33da6c8722c 100644
--- a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
@@ -229,6 +229,10 @@ mlir::LogicalResult CIRGenFunction::emitSimpleStmt(const Stmt *s,
     else
       emitCompoundStmt(cast<CompoundStmt>(*s));
     break;
+  case Stmt::ContinueStmtClass:
+    return emitContinueStmt(cast<ContinueStmt>(*s));
+  case Stmt::BreakStmtClass:
+    return emitBreakStmt(cast<BreakStmt>(*s));
   case Stmt::ReturnStmtClass:
     return emitReturnStmt(cast<ReturnStmt>(*s));
   }
@@ -316,6 +320,25 @@ mlir::LogicalResult CIRGenFunction::emitReturnStmt(const ReturnStmt &s) {
   return mlir::success();
 }
 
+mlir::LogicalResult
+CIRGenFunction::emitContinueStmt(const clang::ContinueStmt &s) {
+  builder.createContinue(getLoc(s.getContinueLoc()));
+
+  // Insert the new block to continue codegen after the continue statement.
+  builder.createBlock(builder.getBlock()->getParent());
+
+  return mlir::success();
+}
+
+mlir::LogicalResult CIRGenFunction::emitBreakStmt(const clang::BreakStmt &s) {
+  builder.createBreak(getLoc(s.getBreakLoc()));
+
+  // Insert the new block to continue codegen after the break statement.
+  builder.createBlock(builder.getBlock()->getParent());
+
+  return mlir::success();
+}
+
 mlir::LogicalResult CIRGenFunction::emitForStmt(const ForStmt &s) {
   cir::ForOp forOp;
 
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 4ace083e3c081..ccbc075ae7d9d 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -151,6 +151,17 @@ void cir::AllocaOp::build(mlir::OpBuilder &odsBuilder,
   odsState.addTypes(addr);
 }
 
+//===----------------------------------------------------------------------===//
+// BreakOp
+//===----------------------------------------------------------------------===//
+
+LogicalResult cir::BreakOp::verify() {
+  assert(!cir::MissingFeatures::switchOp());
+  if (!getOperation()->getParentOfType<LoopOpInterface>())
+    return emitOpError("must be within a loop");
+  return success();
+}
+
 //===----------------------------------------------------------------------===//
 // ConditionOp
 //===----------------------------------------------------------------------===//
@@ -241,6 +252,16 @@ OpFoldResult cir::ConstantOp::fold(FoldAdaptor /*adaptor*/) {
   return getValue();
 }
 
+//===----------------------------------------------------------------------===//
+// ContinueOp
+//===----------------------------------------------------------------------===//
+
+LogicalResult cir::ContinueOp::verify() {
+  if (!getOperation()->getParentOfType<LoopOpInterface>())
+    return emitOpError("must be within a loop");
+  return success();
+}
+
 //===----------------------------------------------------------------------===//
 // CastOp
 //===----------------------------------------------------------------------===//
diff --git a/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp b/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp
index 52f4b2241505d..b49c5ffd35c00 100644
--- a/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp
@@ -148,23 +148,24 @@ class CIRLoopOpInterfaceFlattening
     // driver to customize the order that operations are visited.
 
     // Lower continue statements.
+    mlir::Block *dest = (step ? step : cond);
     op.walkBodySkippingNestedLoops([&](mlir::Operation *op) {
-      // When continue ops are supported, there will be a check for them here
-      // and a call to lowerTerminator(). The call to `advance()` handles the
-      // case where this is not a continue op.
-      assert(!cir::MissingFeatures::continueOp());
-      return mlir::WalkResult::advance();
+      if (!isa<cir::ContinueOp>(op))
+        return mlir::WalkResult::advance();
+
+      lowerTerminator(op, dest, rewriter);
+      return mlir::WalkResult::skip();
     });
 
     // Lower break statements.
     assert(!cir::MissingFeatures::switchOp());
     walkRegionSkipping<cir::LoopOpInterface>(
         op.getBody(), [&](mlir::Operation *op) {
-          // When break ops are supported, there will be a check for them here
-          // and a call to lowerTerminator(). The call to `advance()` handles
-          // the case where this is not a break op.
-          assert(!cir::MissingFeatures::breakOp());
-          return mlir::WalkResult::advance();
+          if (!isa<cir::BreakOp>(op))
+            return mlir::WalkResult::advance();
+
+          lowerTerminator(op, exit, rewriter);
+          return mlir::WalkResult::skip();
         });
 
     // Lower optional body region yield.
diff --git a/clang/test/CIR/CodeGen/loop.cpp b/clang/test/CIR/CodeGen/loop.cpp
index a950460e8838d..46fa66e2fc7aa 100644
--- a/clang/test/CIR/CodeGen/loop.cpp
+++ b/clang/test/CIR/CodeGen/loop.cpp
@@ -265,3 +265,125 @@ void test_empty_while_true() {
 // OGCG:   br label %[[WHILE_BODY:.*]]
 // OGCG: [[WHILE_BODY]]:
 // OGCG:   ret void
+
+void unreachable_after_continue() {
+  for (;;) {
+    continue;
+    int x = 1;
+  }
+}
+
+// CIR: cir.func @unreachable_after_continue
+// CIR:   cir.scope {
+// CIR:     cir.for : cond {
+// CIR:       %[[TRUE:.*]] = cir.const #true
+// CIR:       cir.condition(%[[TRUE]])
+// CIR:     } body {
+// CIR:       cir.scope {
+// CIR:         %[[X:.*]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["x", init] {alignment = 4 : i64}
+// CIR:         cir.continue
+// CIR:       ^bb1:  // no predecessors
+// CIR:         %[[ONE:.*]] = cir.const #cir.int<1> : !s32i
+// CIR:         cir.store %[[ONE]], %[[X]] : !s32i, !cir.ptr<!s32i>
+// CIR:         cir.yield
+// CIR:       }
+// CIR:       cir.yield
+// CIR:     } step {
+// CIR:       cir.yield
+// CIR:     }
+// CIR:   }
+// CIR:   cir.return
+// CIR: }
+
+// LLVM: define void @unreachable_after_continue()
+// LLVM:   %[[X:.*]] = alloca i32, i64 1, align 4
+// LLVM:   br label %[[LABEL1:.*]]
+// LLVM: [[LABEL1]]:
+// LLVM:   br label %[[LABEL2:.*]]
+// LLVM: [[LABEL2]]:
+// LLVM:   br i1 true, label %[[LABEL3:.*]], label %[[LABEL8:.*]]
+// LLVM: [[LABEL3]]:
+// LLVM:   br label %[[LABEL4:.*]]
+// LLVM: [[LABEL4]]:
+// LLVM:   br label %[[LABEL7:.*]]
+// LLVM: [[LABEL5:.*]]:
+// LLVM-SAME: ; No predecessors!
+// LLVM:   store i32 1, ptr %[[X]], align 4
+// LLVM:   br label %[[LABEL6:.*]]
+// LLVM: [[LABEL6]]:
+// LLVM:   br label %[[LABEL7:.*]]
+// LLVM: [[LABEL7]]:
+// LLVM:   br label %[[LABEL2]]
+// LLVM: [[LABEL8]]:
+// LLVM:   br label %[[LABEL9:]]
+// LLVM: [[LABEL9]]:
+// LLVM:   ret void
+
+// OGCG: define{{.*}} void @_Z26unreachable_after_continuev()
+// OGCG: entry:
+// OGCG:   %[[X:.*]] = alloca i32, align 4
+// OGCG:   br label %[[FOR_COND:.*]]
+// OGCG: [[FOR_COND]]:
+// OGCG:   br label %[[FOR_COND]]
+
+void unreachable_after_break() {
+  for (;;) {
+    break;
+    int x = 1;
+  }
+}
+
+// CIR: cir.func @unreachable_after_break
+// CIR:   cir.scope {
+// CIR:     cir.for : cond {
+// CIR:       %[[TRUE:.*]] = cir.const #true
+// CIR:       cir.condition(%[[TRUE]])
+// CIR:     } body {
+// CIR:       cir.scope {
+// CIR:         %[[X:.*]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["x", init] {alignment = 4 : i64}
+// CIR:         cir.break
+// CIR:       ^bb1:  // no predecessors
+// CIR:         %[[ONE:.*]] = cir.const #cir.int<1> : !s32i
+// CIR:         cir.store %[[ONE]], %[[X]] : !s32i, !cir.ptr<!s32i>
+// CIR:         cir.yield
+// CIR:       }
+// CIR:       cir.yield
+// CIR:     } step {
+// CIR:       cir.yield
+// CIR:     }
+// CIR:   }
+// CIR:   cir.return
+// CIR: }
+
+// LLVM: define void @unreachable_after_break()
+// LLVM:   %[[X:.*]] = alloca i32, i64 1, align 4
+// LLVM:   br label %[[LABEL1:.*]]
+// LLVM: [[LABEL1]]:
+// LLVM:   br label %[[LABEL2:.*]]
+// LLVM: [[LABEL2]]:
+// LLVM:   br i1 true, label %[[LABEL3:.*]], label %[[LABEL8:.*]]
+// LLVM: [[LABEL3]]:
+// LLVM:   br label %[[LABEL4:.*]]
+// LLVM: [[LABEL4]]:
+// LLVM:   br label %[[LABEL8]]
+// LLVM: [[LABEL5:.*]]:
+// LLVM-SAME: ; No predecessors!
+// LLVM:   store i32 1, ptr %[[X]], align 4
+// LLVM:   br label %[[LABEL6:.*]]
+// LLVM: [[LABEL6]]:
+// LLVM:   br label %[[LABEL7:.*]]
+// LLVM: [[LABEL7]]:
+// LLVM:   br label %[[LABEL2]]
+// LLVM: [[LABEL8]]:
+// LLVM:   br label %[[LABEL9:]]
+// LLVM: [[LABEL9]]:
+// LLVM:   ret void
+
+// OGCG: define{{.*}} void @_Z23unreachable_after_breakv()
+// OGCG: entry:
+// OGCG:   %[[X:.*]] = alloca i32, align 4
+// OGCG:   br label %[[FOR_COND:.*]]
+// OGCG: [[FOR_COND]]:
+// OGCG:   br label %[[FOR_END:.*]]
+// OGCG: [[FOR_END]]:
+// OGCG:   ret void

``````````

</details>

author_association: MEMBER
user: login: github-actions[bot]
created_at: 2025-04-03T23:46:25Z
body: <!--LLVM CODE FORMAT COMMENT: {clang-format}-->

:white_check_mark: With the latest revision this PR passed the C/C++ code formatter.
author_association: NONE