[Mlir-commits] [mlir] [mlir][emitc] Add 'emitc.while' and 'emitc.do' ops to the dialect (PR #143008)

Vlad Lazar llvmlistbot at llvm.org
Wed Sep 3 10:39:06 PDT 2025


https://github.com/Vladislave0-0 updated https://github.com/llvm/llvm-project/pull/143008

>From cd219b054092490f8a10a21d4f177d4acc7a7e6f Mon Sep 17 00:00:00 2001
From: Vlad Lazar <lazar_2004 at list.ru>
Date: Thu, 5 Jun 2025 19:41:52 +0300
Subject: [PATCH 1/6] [mlir][emitc] Add 'emitc.while' and 'emitc.do' ops to the
 dialect

This MR adds:
- 'emitc::WhileOp' and 'emitc::DoOp' to the EmitC dialect
- Emission of the corresponding ops in the CppEmitter
- Conversion from the SCF dialect to the EmitC dialect for the ops
- Corresponding tests
---
 mlir/include/mlir/Dialect/EmitC/IR/EmitC.td   | 154 +++++++++++-
 mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp | 236 +++++++++++++++++-
 mlir/lib/Dialect/EmitC/IR/EmitC.cpp           | 116 ++++++++-
 mlir/lib/Target/Cpp/TranslateToCpp.cpp        | 122 ++++++++-
 mlir/test/Conversion/SCFToEmitC/while.mlir    | 235 +++++++++++++++++
 mlir/test/Dialect/EmitC/invalid_ops.mlir      | 196 ++++++++++++++-
 mlir/test/Dialect/EmitC/ops.mlir              |  32 +++
 mlir/test/Target/Cpp/do.mlir                  |  69 +++++
 mlir/test/Target/Cpp/while.mlir               |  69 +++++
 9 files changed, 1212 insertions(+), 17 deletions(-)
 create mode 100644 mlir/test/Conversion/SCFToEmitC/while.mlir
 create mode 100644 mlir/test/Target/Cpp/do.mlir
 create mode 100644 mlir/test/Target/Cpp/while.mlir

diff --git a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
index fb7a108b39fc0..889447524e090 100644
--- a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
+++ b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
@@ -1384,7 +1384,7 @@ def EmitC_AssignOp : EmitC_Op<"assign", []> {
 }
 
 def EmitC_YieldOp : EmitC_Op<"yield",
-      [Pure, Terminator, ParentOneOf<["ExpressionOp", "IfOp", "ForOp", "SwitchOp"]>]> {
+      [Pure, Terminator, ParentOneOf<["DoOp", "ExpressionOp", "ForOp", "IfOp", "SwitchOp", "WhileOp"]>]> {
   let summary = "Block termination operation";
   let description = [{
     The `emitc.yield` terminates its parent EmitC op's region, optionally yielding
@@ -1718,4 +1718,156 @@ def EmitC_GetFieldOp
   let hasVerifier = 1;
 }
 
+def EmitC_WhileOp : EmitC_Op<"while",
+      [HasOnlyGraphRegion, RecursiveMemoryEffects, NoRegionArguments, OpAsmOpInterface, NoTerminator]> {
+  let summary = "While operation";
+ let description = [{
+    The `emitc.while` operation represents a C/C++ while loop construct that
+    repeatedly executes a body region as long as a condition region evaluates to
+    true. The operation has two regions:
+    
+    1. A condition region that must yield a boolean value (i1) 
+    2. A body region that contains the loop body
+
+    The condition region is evaluated before each iteration. If it yields true,
+    the body region is executed. The loop terminates when the condition yields
+    false. The condition region must contain exactly one block that terminates
+    with an `emitc.yield` operation producing an i1 value.
+
+    Example:
+
+    ```mlir
+    emitc.func @foo(%arg0 : !emitc.ptr<i32>) {
+      %var = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue<i32>
+      %0 = emitc.literal "10" : i32
+      %1 = emitc.literal "1" : i32
+
+      emitc.while {
+        %var_load = load %var : <i32>
+        %res = emitc.cmp le, %var_load, %0 : (i32, i32) -> i1
+        emitc.yield %res : i1
+      } do {
+        emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
+        %var_load = load %var : <i32>
+        %tmp_add = add %var_load, %1 : (i32, i32) -> i32
+        "emitc.assign"(%var, %tmp_add) : (!emitc.lvalue<i32>, i32) -> ()
+      }
+
+      return
+    }
+    ```
+
+    ```c++
+    // Code emitted for the operation above.
+    void foo(int32_t* v1) {
+      int32_t v2 = 0;
+      while (v2 <= 10) {
+        printf("%d", *v1);
+        int32_t v3 = v2;
+        int32_t v4 = v3 + 1;
+        v2 = v4;
+      }
+      return;
+    }
+    ```
+  }];
+
+  let arguments = (ins);
+  let results = (outs); 
+  let regions = (region MaxSizedRegion<1>:$conditionRegion,
+                        MaxSizedRegion<1>:$bodyRegion);
+
+  let hasCustomAssemblyFormat = 1;
+  let hasVerifier = 1;
+
+  let extraClassDeclaration = [{
+    Operation *getRootOp();
+
+    //===------------------------------------------------------------------===//
+    // OpAsmOpInterface Methods
+    //===------------------------------------------------------------------===//
+
+    /// EmitC ops in the body can omit their 'emitc.' prefix in the assembly.
+    static ::llvm::StringRef getDefaultDialect() {
+      return "emitc";
+    }
+  }];
+}
+
+def EmitC_DoOp : EmitC_Op<"do",
+      [RecursiveMemoryEffects, NoRegionArguments, OpAsmOpInterface, NoTerminator]> {
+  let summary = "Do-while operation";
+ let description = [{
+    The `emitc.do` operation represents a C/C++ do-while loop construct that
+    executes a body region first and then repeatedly executes it as long as a
+    condition region evaluates to true. The operation has two regions:
+
+    1. A body region that contains the loop body
+    2. A condition region that must yield a boolean value (i1)
+
+    Unlike a while loop, the body region is executed before the first evaluation
+    of the condition. The loop terminates when the condition yields false. The
+    condition region must contain exactly one block that terminates with an
+    `emitc.yield` operation producing an i1 value.
+
+    Example:
+
+    ```mlir
+    emitc.func @foo(%arg0 : !emitc.ptr<i32>) {
+      %var = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue<i32>
+      %0 = emitc.literal "10" : i32
+      %1 = emitc.literal "1" : i32
+
+      emitc.do {
+        emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
+        %var_load = load %var : <i32>
+        %tmp_add = add %var_load, %1 : (i32, i32) -> i32
+        "emitc.assign"(%var, %tmp_add) : (!emitc.lvalue<i32>, i32) -> ()
+      } while {
+        %var_load = load %var : <i32>
+        %res = emitc.cmp le, %var_load, %0 : (i32, i32) -> i1
+        emitc.yield %res : i1
+      }
+
+      return
+    }
+    ```
+
+    ```c++
+    // Code emitted for the operation above.
+    void foo(int32_t* v1) {
+      int32_t v2 = 0;
+      do {
+        printf("%d", *v1);
+        int32_t v3 = v2;
+        int32_t v4 = v3 + 1;
+        v2 = v4;
+      } while (v2 <= 10);
+      return;
+    }
+    ```
+  }];
+
+  let arguments = (ins);
+  let results = (outs); 
+  let regions = (region MaxSizedRegion<1>:$bodyRegion,
+                        MaxSizedRegion<1>:$conditionRegion);
+
+  let hasCustomAssemblyFormat = 1;
+  let hasVerifier = 1;
+
+  let extraClassDeclaration = [{
+    Operation *getRootOp();
+
+    //===------------------------------------------------------------------===//
+    // OpAsmOpInterface Methods
+    //===------------------------------------------------------------------===//
+
+    /// EmitC ops in the body can omit their 'emitc.' prefix in the assembly.
+    static ::llvm::StringRef getDefaultDialect() {
+      return "emitc";
+    }
+  }];
+}
+
 #endif // MLIR_DIALECT_EMITC_IR_EMITC
diff --git a/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp b/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp
index 1f239aa5d1aa3..6aff0ce98ab91 100644
--- a/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp
+++ b/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp
@@ -336,11 +336,244 @@ LogicalResult IndexSwitchOpLowering::matchAndRewrite(
   return success();
 }
 
+// Lower scf::while to either emitc::while or emitc::do based on argument usage
+// patterns. Uses mutable variables to maintain loop state across iterations.
+struct WhileLowering : public OpConversionPattern<WhileOp> {
+  using OpConversionPattern::OpConversionPattern;
+
+  LogicalResult
+  matchAndRewrite(WhileOp whileOp, OpAdaptor adaptor,
+                  ConversionPatternRewriter &rewriter) const override {
+    Location loc = whileOp.getLoc();
+    MLIRContext *context = loc.getContext();
+
+    // Create variable storage for loop-carried values to enable imperative
+    // updates while maintaining SSA semantics at conversion boundaries.
+    SmallVector<Value> variables;
+    if (failed(
+            createInitVariables(whileOp, rewriter, variables, loc, context))) {
+      return failure();
+    }
+
+    // Select lowering strategy based on condition argument usage:
+    // - emitc.while when condition args match region inputs (direct mapping);
+    // - emitc.do when condition args differ (requires state synchronization).
+    Region &beforeRegion = adaptor.getBefore();
+    Block &beforeBlock = beforeRegion.front();
+    auto condOp = cast<scf::ConditionOp>(beforeRegion.back().getTerminator());
+
+    bool isDoOp = !llvm::equal(beforeBlock.getArguments(), condOp.getArgs());
+
+    LogicalResult result =
+        isDoOp ? lowerDoWhile(whileOp, variables, context, rewriter, loc)
+               : lowerWhile(whileOp, variables, context, rewriter, loc);
+
+    if (failed(result))
+      return failure();
+
+    // Create an emitc::variable op for each result. These variables will be
+    // assigned to by emitc::assign ops within the loop body.
+    SmallVector<Value> resultVariables;
+    if (failed(createVariablesForResults(whileOp, getTypeConverter(), rewriter,
+                                         resultVariables))) {
+      return rewriter.notifyMatchFailure(whileOp,
+                                         "Failed to create result variables");
+    }
+
+    rewriter.setInsertionPointAfter(whileOp);
+
+    // Transfer final loop state to result variables and get final SSA results.
+    SmallVector<Value> finalResults =
+        finalizeLoopResults(resultVariables, variables, rewriter, loc);
+
+    rewriter.replaceOp(whileOp, finalResults);
+    return success();
+  }
+
+private:
+  // Initialize variables for loop-carried values to enable state updates
+  // across iterations without SSA argument passing.
+  static LogicalResult createInitVariables(WhileOp whileOp,
+                                           ConversionPatternRewriter &rewriter,
+                                           SmallVectorImpl<Value> &outVars,
+                                           Location loc, MLIRContext *context) {
+    emitc::OpaqueAttr noInit = emitc::OpaqueAttr::get(context, "");
+
+    for (Value init : whileOp.getInits()) {
+      emitc::VariableOp var = rewriter.create<emitc::VariableOp>(
+          loc, emitc::LValueType::get(init.getType()), noInit);
+      rewriter.create<emitc::AssignOp>(loc, var.getResult(), init);
+      outVars.push_back(var.getResult());
+    }
+
+    return success();
+  }
+
+  // Transition from SSA block arguments to variable-based state management by
+  // replacing argument uses with variable loads and cleaning up block
+  // interface.
+  void replaceBlockArgsWithVarLoads(Block *block, ArrayRef<Value> vars,
+                                    ConversionPatternRewriter &rewriter,
+                                    Location loc) const {
+    rewriter.setInsertionPointToStart(block);
+
+    for (auto [arg, var] : llvm::zip(block->getArguments(), vars)) {
+      Type loadedType = cast<emitc::LValueType>(var.getType()).getValueType();
+      Value load = rewriter.create<emitc::LoadOp>(loc, loadedType, var);
+      arg.replaceAllUsesWith(load);
+    }
+
+    // Remove arguments after replacement to simplify block structure.
+    block->eraseArguments(0, block->getNumArguments());
+  }
+
+  // Convert SCF yield terminators to imperative assignments to update loop
+  // variables, maintaining loop semantics while transitioning to emitc model.
+  void processYieldTerminator(Operation *terminator, ArrayRef<Value> vars,
+                              ConversionPatternRewriter &rewriter,
+                              Location loc) const {
+    auto yieldOp = cast<scf::YieldOp>(terminator);
+    SmallVector<Value> yields(yieldOp.getOperands());
+    rewriter.eraseOp(yieldOp);
+
+    rewriter.setInsertionPointToEnd(yieldOp->getBlock());
+    for (auto [var, val] : llvm::zip(vars, yields))
+      rewriter.create<emitc::AssignOp>(loc, var, val);
+  }
+
+  // Transfers final loop state from mutable variables to result variables,
+  // then returns the final SSA values to replace the original scf::while
+  // results.
+  static SmallVector<Value>
+  finalizeLoopResults(ArrayRef<Value> resultVariables,
+                      ArrayRef<Value> loopVariables,
+                      ConversionPatternRewriter &rewriter, Location loc) {
+    // Transfer final loop state to result variables to bridge imperative loop
+    // variables with SSA result expectations of the original op.
+    for (auto [resultVar, var] : llvm::zip(resultVariables, loopVariables)) {
+      Type loadedType = cast<emitc::LValueType>(var.getType()).getValueType();
+      Value load = rewriter.create<emitc::LoadOp>(loc, loadedType, var);
+      rewriter.create<emitc::AssignOp>(loc, resultVar, load);
+    }
+
+    // Replace op with loaded values to integrate with converted SSA graph.
+    SmallVector<Value> finalResults;
+    for (Value resultVar : resultVariables) {
+      Type loadedType =
+          cast<emitc::LValueType>(resultVar.getType()).getValueType();
+      finalResults.push_back(
+          rewriter.create<emitc::LoadOp>(loc, loadedType, resultVar));
+    }
+
+    return finalResults;
+  }
+
+  // Direct lowering to emitc.while when condition arguments match region
+  // inputs.
+  LogicalResult lowerWhile(WhileOp whileOp, ArrayRef<Value> vars,
+                           MLIRContext *context,
+                           ConversionPatternRewriter &rewriter,
+                           Location loc) const {
+    auto loweredWhile = rewriter.create<emitc::WhileOp>(loc);
+
+    // Lower before region to condition region.
+    rewriter.inlineRegionBefore(whileOp.getBefore(),
+                                loweredWhile.getConditionRegion(),
+                                loweredWhile.getConditionRegion().end());
+
+    Block *condBlock = &loweredWhile.getConditionRegion().front();
+    replaceBlockArgsWithVarLoads(condBlock, vars, rewriter, loc);
+
+    Operation *condTerminator =
+        loweredWhile.getConditionRegion().back().getTerminator();
+    auto condOp = cast<scf::ConditionOp>(condTerminator);
+    rewriter.setInsertionPoint(condOp);
+    Value condition = rewriter.getRemappedValue(condOp.getCondition());
+    rewriter.create<emitc::YieldOp>(condOp.getLoc(), condition);
+    rewriter.eraseOp(condOp);
+
+    // Lower after region to body region.
+    rewriter.inlineRegionBefore(whileOp.getAfter(),
+                                loweredWhile.getBodyRegion(),
+                                loweredWhile.getBodyRegion().end());
+
+    Block *bodyBlock = &loweredWhile.getBodyRegion().front();
+    replaceBlockArgsWithVarLoads(bodyBlock, vars, rewriter, loc);
+
+    // Convert scf.yield to variable assignments for state updates.
+    processYieldTerminator(bodyBlock->getTerminator(), vars, rewriter, loc);
+
+    return success();
+  }
+
+  // Lower to emitc.do when condition arguments differ from region inputs.
+  LogicalResult lowerDoWhile(WhileOp whileOp, ArrayRef<Value> vars,
+                             MLIRContext *context,
+                             ConversionPatternRewriter &rewriter,
+                             Location loc) const {
+    Type i1Type = IntegerType::get(context, 1);
+    auto globalCondition =
+        rewriter.create<emitc::VariableOp>(loc, emitc::LValueType::get(i1Type),
+                                           emitc::OpaqueAttr::get(context, ""));
+    Value conditionVal = globalCondition.getResult();
+
+    auto loweredDo = rewriter.create<emitc::DoOp>(loc);
+
+    // Lower before region as body.
+    rewriter.inlineRegionBefore(whileOp.getBefore(), loweredDo.getBodyRegion(),
+                                loweredDo.getBodyRegion().end());
+
+    Block *bodyBlock = &loweredDo.getBodyRegion().front();
+    replaceBlockArgsWithVarLoads(bodyBlock, vars, rewriter, loc);
+
+    // Convert scf.condition to condition variable assignment.
+    Operation *condTerminator =
+        loweredDo.getBodyRegion().back().getTerminator();
+    scf::ConditionOp condOp = cast<scf::ConditionOp>(condTerminator);
+    rewriter.setInsertionPoint(condOp);
+    Value condition = rewriter.getRemappedValue(condOp.getCondition());
+    rewriter.create<emitc::AssignOp>(loc, conditionVal, condition);
+
+    // Wrap body region in conditional to preserve scf semantics.
+    auto ifOp = rewriter.create<emitc::IfOp>(loc, condition, false, false);
+
+    // Lower after region as then-block of conditional.
+    rewriter.inlineRegionBefore(whileOp.getAfter(), ifOp.getBodyRegion(),
+                                ifOp.getBodyRegion().begin());
+
+    if (!ifOp.getBodyRegion().empty()) {
+      Block *ifBlock = &ifOp.getBodyRegion().front();
+
+      // Handle argument mapping from condition op to body region.
+      auto args = condOp.getArgs();
+      for (auto [arg, val] : llvm::zip(ifBlock->getArguments(), args))
+        arg.replaceAllUsesWith(rewriter.getRemappedValue(val));
+
+      ifBlock->eraseArguments(0, ifBlock->getNumArguments());
+
+      // Convert scf.yield to variable assignments for state updates.
+      processYieldTerminator(ifBlock->getTerminator(), vars, rewriter, loc);
+      rewriter.create<emitc::YieldOp>(loc);
+    }
+
+    rewriter.eraseOp(condOp);
+
+    // Create condition region that loads from the flag variable.
+    Block *condBlock = rewriter.createBlock(&loweredDo.getConditionRegion());
+    rewriter.setInsertionPointToStart(condBlock);
+    Value cond = rewriter.create<emitc::LoadOp>(loc, i1Type, conditionVal);
+    rewriter.create<emitc::YieldOp>(loc, cond);
+
+    return success();
+  }
+};
+
 void mlir::populateSCFToEmitCConversionPatterns(RewritePatternSet &patterns,
                                                 TypeConverter &typeConverter) {
   patterns.add<ForLowering>(typeConverter, patterns.getContext());
   patterns.add<IfLowering>(typeConverter, patterns.getContext());
   patterns.add<IndexSwitchOpLowering>(typeConverter, patterns.getContext());
+  patterns.add<WhileLowering>(typeConverter, patterns.getContext());
 }
 
 void SCFToEmitCPass::runOnOperation() {
@@ -357,7 +590,8 @@ void SCFToEmitCPass::runOnOperation() {
 
   // Configure conversion to lower out SCF operations.
   ConversionTarget target(getContext());
-  target.addIllegalOp<scf::ForOp, scf::IfOp, scf::IndexSwitchOp>();
+  target
+      .addIllegalOp<scf::ForOp, scf::IfOp, scf::IndexSwitchOp, scf::WhileOp>();
   target.markUnknownOpDynamicallyLegal([](Operation *) { return true; });
   if (failed(
           applyPartialConversion(getOperation(), target, std::move(patterns))))
diff --git a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
index 00ce3b59bf870..b6d1704a14d6c 100644
--- a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
+++ b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
@@ -950,10 +950,12 @@ LogicalResult emitc::YieldOp::verify() {
   Value result = getResult();
   Operation *containingOp = getOperation()->getParentOp();
 
-  if (result && containingOp->getNumResults() != 1)
+  if (result && containingOp->getNumResults() != 1 &&
+      !isa<WhileOp, DoOp>(containingOp))
     return emitOpError() << "yields a value not returned by parent";
 
-  if (!result && containingOp->getNumResults() != 0)
+  if (!result && containingOp->getNumResults() != 0 &&
+      !isa<WhileOp, DoOp>(containingOp))
     return emitOpError() << "does not yield a value to be returned by parent";
 
   return success();
@@ -1533,6 +1535,116 @@ LogicalResult GetFieldOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
   return success();
 }
 
+//===----------------------------------------------------------------------===//
+// Common functions for WhileOp and DoOp
+//===----------------------------------------------------------------------===//
+
+static Operation *getRootOpFromLoopCondition(Region &condRegion) {
+  auto yieldOp = cast<emitc::YieldOp>(condRegion.front().getTerminator());
+  return yieldOp.getResult().getDefiningOp();
+}
+
+static LogicalResult verifyLoopRegions(Operation &op, Region &condition,
+                                       Region &body) {
+  if (condition.empty())
+    return op.emitOpError("condition region cannot be empty");
+
+  Block &condBlock = condition.front();
+  for (Operation &inner : condBlock.without_terminator()) {
+    if (!inner.hasTrait<OpTrait::emitc::CExpression>())
+      return op.emitOpError(
+                 "expected all operations in condition region must implement "
+                 "CExpression trait, but ")
+             << inner.getName() << " does not";
+  }
+
+  auto condYield = dyn_cast<emitc::YieldOp>(condBlock.back());
+  if (!condYield)
+    return op.emitOpError(
+               "expected condition region to end with emitc.yield, but got ")
+           << condBlock.back().getName();
+
+  if (condYield.getNumOperands() != 1 ||
+      !condYield.getOperand(0).getType().isInteger(1))
+    return op.emitOpError("condition region must yield a single i1 value");
+
+  if (body.empty())
+    return op.emitOpError("body region cannot be empty");
+
+  Block &bodyBlock = body.front();
+  if (auto bodyYield = dyn_cast<emitc::YieldOp>(bodyBlock.back()))
+    if (bodyYield.getNumOperands() != 0)
+      return op.emitOpError(
+                 "expected body region to return 0 values, but body returns ")
+             << bodyYield.getNumOperands();
+
+  return success();
+}
+
+static void printLoop(OpAsmPrinter &p, Operation *self, Region &first,
+                      StringRef midKeyword, Region &second) {
+  p << ' ';
+  p.printRegion(first, /*printEntryBlockArgs=*/false);
+  p << ' ' << midKeyword << ' ';
+  p.printRegion(second);
+  p.printOptionalAttrDictWithKeyword(self->getAttrs());
+}
+
+static ParseResult parseLoop(OpAsmParser &parser, OperationState &res,
+                             StringRef midKeyword) {
+  Region *firstRegion = res.addRegion();
+  Region *secondRegion = res.addRegion();
+
+  if (parser.parseRegion(*firstRegion))
+    return failure();
+  if (parser.parseKeyword(midKeyword) || parser.parseRegion(*secondRegion))
+    return failure();
+
+  return parser.parseOptionalAttrDictWithKeyword(res.attributes);
+}
+
+//===----------------------------------------------------------------------===//
+// WhileOp
+//===----------------------------------------------------------------------===//
+
+Operation *WhileOp::getRootOp() {
+  return getRootOpFromLoopCondition(getConditionRegion());
+}
+
+void WhileOp::print(OpAsmPrinter &p) {
+  printLoop(p, getOperation(), getConditionRegion(), "do", getBodyRegion());
+}
+
+LogicalResult emitc::WhileOp::verify() {
+  return verifyLoopRegions(*getOperation(), getConditionRegion(),
+                           getBodyRegion());
+}
+
+ParseResult WhileOp::parse(OpAsmParser &parser, OperationState &result) {
+  return parseLoop(parser, result, "do");
+}
+
+//===----------------------------------------------------------------------===//
+// DoOp
+//===----------------------------------------------------------------------===//
+
+Operation *DoOp::getRootOp() {
+  return getRootOpFromLoopCondition(getConditionRegion());
+}
+
+void DoOp::print(OpAsmPrinter &p) {
+  printLoop(p, getOperation(), getBodyRegion(), "while", getConditionRegion());
+}
+
+LogicalResult emitc::DoOp::verify() {
+  return verifyLoopRegions(*getOperation(), getConditionRegion(),
+                           getBodyRegion());
+}
+
+ParseResult DoOp::parse(OpAsmParser &parser, OperationState &result) {
+  return parseLoop(parser, result, "while");
+}
+
 //===----------------------------------------------------------------------===//
 // TableGen'd op method definitions
 //===----------------------------------------------------------------------===//
diff --git a/mlir/lib/Target/Cpp/TranslateToCpp.cpp b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
index 570f38c60020b..937d91f4246f2 100644
--- a/mlir/lib/Target/Cpp/TranslateToCpp.cpp
+++ b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
@@ -178,6 +178,12 @@ struct CppEmitter {
   /// Emit an expression as a C expression.
   LogicalResult emitExpression(ExpressionOp expressionOp);
 
+  /// Emit while as a C while.
+  LogicalResult emitWhile(WhileOp expressionOp);
+
+  /// Emit do-while as a C do-while.
+  LogicalResult emitDo(DoOp expressionOp);
+
   /// Insert the expression representing the operation into the value cache.
   void cacheDeferredOpResult(Value value, StringRef str);
 
@@ -200,6 +206,8 @@ struct CppEmitter {
   /// Return the existing or a new label of a Block.
   StringRef getOrCreateName(Block &block);
 
+  LogicalResult emitInlinedExpression(Value value);
+
   /// Whether to map an mlir integer to a unsigned integer in C++.
   bool shouldMapToUnsigned(IntegerType::SignednessSemantics val);
 
@@ -257,7 +265,7 @@ struct CppEmitter {
   }
 
   /// Get expression currently being emitted.
-  ExpressionOp getEmittedExpression() { return emittedExpression; }
+  Operation *getEmittedExpression() { return emittedExpression; }
 
   /// Determine whether given value is part of the expression potentially being
   /// emitted.
@@ -267,7 +275,8 @@ struct CppEmitter {
     Operation *def = value.getDefiningOp();
     if (!def)
       return false;
-    auto operandExpression = dyn_cast<ExpressionOp>(def->getParentOp());
+
+    Operation *operandExpression = def->getParentOp();
     return operandExpression == emittedExpression;
   };
 
@@ -315,7 +324,7 @@ struct CppEmitter {
   unsigned int valueCount{0};
 
   /// State of the current expression being emitted.
-  ExpressionOp emittedExpression;
+  Operation *emittedExpression = nullptr;
   SmallVector<int> emittedExpressionPrecedence;
 
   void pushExpressionPrecedence(int precedence) {
@@ -547,6 +556,16 @@ static LogicalResult printOperation(CppEmitter &emitter,
   return success();
 }
 
+static LogicalResult printOperation(CppEmitter &emitter,
+                                    emitc::WhileOp whileOp) {
+  return emitter.emitWhile(whileOp);
+}
+
+static LogicalResult printOperation(CppEmitter &emitter,
+                                    emitc::DoOp doWhileOp) {
+  return emitter.emitDo(doWhileOp);
+}
+
 static LogicalResult printOperation(CppEmitter &emitter, emitc::CmpOp cmpOp) {
   Operation *operation = cmpOp.getOperation();
 
@@ -1504,6 +1523,84 @@ LogicalResult CppEmitter::emitExpression(ExpressionOp expressionOp) {
   return success();
 }
 
+LogicalResult CppEmitter::emitWhile(WhileOp whileOp) {
+  assert(emittedExpressionPrecedence.empty() &&
+         "Expected precedence stack to be empty");
+  Operation *rootOp = whileOp.getRootOp();
+
+  emittedExpression = whileOp;
+  FailureOr<int> precedence = getOperatorPrecedence(rootOp);
+  if (failed(precedence))
+    return failure();
+  pushExpressionPrecedence(precedence.value());
+
+  os << "while (";
+  if (failed(emitOperation(*rootOp, /*trailingSemicolon=*/false)))
+    return failure();
+  os << ") {\n";
+
+  popExpressionPrecedence();
+  assert(emittedExpressionPrecedence.empty() &&
+         "Expected precedence stack to be empty");
+  emittedExpression = nullptr;
+
+  os.indent();
+
+  Region &bodyRegion = whileOp.getBodyRegion();
+  auto regionOps = bodyRegion.getOps();
+
+  for (Operation &op : regionOps) {
+    if (isa<emitc::YieldOp>(op))
+      continue;
+
+    if (failed(emitOperation(op, /*trailingSemicolon=*/true)))
+      return failure();
+  }
+
+  os.unindent() << "}";
+
+  return success();
+}
+
+LogicalResult CppEmitter::emitDo(DoOp doWhileOp) {
+  os << "do {\n";
+  os.indent();
+
+  Region &bodyRegion = doWhileOp.getBodyRegion();
+  auto regionOps = bodyRegion.getOps();
+
+  for (Operation &op : regionOps) {
+    if (isa<emitc::YieldOp>(op))
+      continue;
+
+    if (failed(emitOperation(op, /*trailingSemicolon=*/true)))
+      return failure();
+  }
+
+  os.unindent() << "} while (";
+
+  assert(emittedExpressionPrecedence.empty() &&
+         "Expected precedence stack to be empty");
+  Operation *rootOp = doWhileOp.getRootOp();
+
+  emittedExpression = doWhileOp;
+  FailureOr<int> precedence = getOperatorPrecedence(rootOp);
+  if (failed(precedence))
+    return failure();
+  pushExpressionPrecedence(precedence.value());
+
+  if (failed(emitOperation(*rootOp, /*trailingSemicolon=*/false)))
+    return failure();
+  os << ");";
+
+  popExpressionPrecedence();
+  assert(emittedExpressionPrecedence.empty() &&
+         "Expected precedence stack to be empty");
+  emittedExpression = nullptr;
+
+  return success();
+}
+
 LogicalResult CppEmitter::emitOperand(Value value) {
   if (isPartOfCurrentExpression(value)) {
     Operation *def = value.getDefiningOp();
@@ -1701,13 +1798,14 @@ LogicalResult CppEmitter::emitOperation(Operation &op, bool trailingSemicolon) {
                 emitc::BitwiseRightShiftOp, emitc::BitwiseXorOp, emitc::CallOp,
                 emitc::CallOpaqueOp, emitc::CastOp, emitc::ClassOp,
                 emitc::CmpOp, emitc::ConditionalOp, emitc::ConstantOp,
-                emitc::DeclareFuncOp, emitc::DivOp, emitc::ExpressionOp,
+                emitc::DeclareFuncOp, emitc::DivOp, emitc::DoOp, emitc::ExpressionOp,
                 emitc::FieldOp, emitc::FileOp, emitc::ForOp, emitc::FuncOp,
-                emitc::GlobalOp, emitc::IfOp, emitc::IncludeOp, emitc::LoadOp,
-                emitc::LogicalAndOp, emitc::LogicalNotOp, emitc::LogicalOrOp,
-                emitc::MulOp, emitc::RemOp, emitc::ReturnOp, emitc::SubOp,
-                emitc::SwitchOp, emitc::UnaryMinusOp, emitc::UnaryPlusOp,
-                emitc::VariableOp, emitc::VerbatimOp>(
+                emitc::GetFieldOp, emitc::GlobalOp, emitc::IfOp,
+                emitc::IncludeOp, emitc::LoadOp, emitc::LogicalAndOp,
+                emitc::LogicalNotOp, emitc::LogicalOrOp, emitc::MulOp,
+                emitc::RemOp, emitc::ReturnOp, emitc::SubOp, emitc::SwitchOp,
+                emitc::UnaryMinusOp, emitc::UnaryPlusOp, emitc::VariableOp,
+                emitc::VerbatimOp, emitc::WhileOp>(
 
               [&](auto op) { return printOperation(*this, op); })
           // Func ops.
@@ -1755,9 +1853,9 @@ LogicalResult CppEmitter::emitOperation(Operation &op, bool trailingSemicolon) {
   // Never emit a semicolon for some operations, especially if endening with
   // `}`.
   trailingSemicolon &=
-      !isa<cf::CondBranchOp, emitc::DeclareFuncOp, emitc::FileOp, emitc::ForOp,
-           emitc::IfOp, emitc::IncludeOp, emitc::SwitchOp, emitc::VerbatimOp>(
-          op);
+      !isa<cf::CondBranchOp, emitc::DeclareFuncOp, emitc::DoOp, emitc::FileOp,
+           emitc::ForOp, emitc::IfOp, emitc::IncludeOp, emitc::SwitchOp,
+           emitc::VerbatimOp, emitc::WhileOp>(op);
 
   os << (trailingSemicolon ? ";\n" : "\n");
 
diff --git a/mlir/test/Conversion/SCFToEmitC/while.mlir b/mlir/test/Conversion/SCFToEmitC/while.mlir
new file mode 100644
index 0000000000000..d4d64fac29280
--- /dev/null
+++ b/mlir/test/Conversion/SCFToEmitC/while.mlir
@@ -0,0 +1,235 @@
+// RUN: mlir-opt -allow-unregistered-dialect -convert-scf-to-emitc %s | FileCheck %s
+// RUN: mlir-opt -allow-unregistered-dialect -convert-to-emitc="filter-dialects=scf" %s | FileCheck %s
+
+emitc.func @payload_whileOp(%arg: i32) -> i32 {
+  %result = emitc.add %arg, %arg : (i32, i32) -> i32
+  return %result : i32
+}
+
+func.func @whileOp() -> i32 {
+  %init = emitc.literal "1.0" : i32
+  %var  = emitc.literal "1.0" : i32
+  %exit = emitc.literal "10.0" : i32
+
+  %res = scf.while (%arg1 = %init) : (i32) -> i32 {
+    %sum = emitc.add %arg1, %var : (i32, i32) -> i32
+    %condition = emitc.cmp lt, %sum, %exit : (i32, i32) -> i1
+    scf.condition(%condition) %arg1 : i32
+  } do {
+  ^bb0(%arg2: i32):
+    %next_arg1 = emitc.call @payload_whileOp(%arg2) : (i32) -> i32
+    scf.yield %next_arg1 : i32
+  }
+  
+  return %res : i32
+}
+// CHECK-LABEL:   emitc.func @payload_whileOp(
+// CHECK-SAME:      %[[ARG0:.*]]: i32) -> i32 {
+// CHECK:           %[[VAL_0:.*]] = add %[[ARG0]], %[[ARG0]] : (i32, i32) -> i32
+// CHECK:           return %[[VAL_0]] : i32
+// CHECK:         }
+
+// CHECK-LABEL:   func.func @whileOp() -> i32 {
+// CHECK:           %[[VAL_0:.*]] = emitc.literal "1.0" : i32
+// CHECK:           %[[VAL_1:.*]] = emitc.literal "1.0" : i32
+// CHECK:           %[[VAL_2:.*]] = emitc.literal "10.0" : i32
+// CHECK:           %[[VAL_3:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
+// CHECK:           emitc.assign %[[VAL_0]] : i32 to %[[VAL_3]] : <i32>
+// CHECK:           emitc.while {
+// CHECK:             %[[VAL_4:.*]] = load %[[VAL_3]] : <i32>
+// CHECK:             %[[VAL_5:.*]] = add %[[VAL_4]], %[[VAL_1]] : (i32, i32) -> i32
+// CHECK:             %[[VAL_6:.*]] = cmp lt, %[[VAL_5]], %[[VAL_2]] : (i32, i32) -> i1
+// CHECK:             yield %[[VAL_6]] : i1
+// CHECK:           } do {
+// CHECK:             %[[VAL_7:.*]] = load %[[VAL_3]] : <i32>
+// CHECK:             %[[VAL_8:.*]] = call @payload_whileOp(%[[VAL_7]]) : (i32) -> i32
+// CHECK:             assign %[[VAL_8]] : i32 to %[[VAL_3]] : <i32>
+// CHECK:           }
+// CHECK:           %[[VAL_9:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
+// CHECK:           %[[VAL_10:.*]] = emitc.load %[[VAL_3]] : <i32>
+// CHECK:           emitc.assign %[[VAL_10]] : i32 to %[[VAL_9]] : <i32>
+// CHECK:           %[[VAL_11:.*]] = emitc.load %[[VAL_9]] : <i32>
+// CHECK:           return %[[VAL_11]] : i32
+// CHECK:         }
+
+emitc.func @payload_doOp(%arg: i32) -> i32 {
+  %result = emitc.add %arg, %arg : (i32, i32) -> i32
+  return %result : i32
+}
+
+func.func @doOp() -> i32 {
+  %init = emitc.literal "1.0" : i32
+  %var  = emitc.literal "1.0" : i32
+  %exit = emitc.literal "10.0" : i32
+
+  %res = scf.while (%arg1 = %init) : (i32) -> i32 {
+    %sum = emitc.add %arg1, %var : (i32, i32) -> i32
+    %condition = emitc.cmp lt, %sum, %exit : (i32, i32) -> i1
+    %next = emitc.add %arg1, %arg1 : (i32, i32) -> i32
+    scf.condition(%condition) %next : i32
+  } do {
+  ^bb0(%arg2: i32):
+    %next_arg1 = emitc.call @payload_doOp(%arg2) : (i32) -> i32
+    scf.yield %next_arg1 : i32
+  }
+  
+  return %res : i32
+}
+// CHECK-LABEL:   emitc.func @payload_doOp(
+// CHECK-SAME:      %[[ARG0:.*]]: i32) -> i32 {
+// CHECK:           %[[VAL_0:.*]] = add %[[ARG0]], %[[ARG0]] : (i32, i32) -> i32
+// CHECK:           return %[[VAL_0]] : i32
+// CHECK:         }
+
+// CHECK-LABEL:   func.func @doOp() -> i32 {
+// CHECK:           %[[VAL_0:.*]] = emitc.literal "1.0" : i32
+// CHECK:           %[[VAL_1:.*]] = emitc.literal "1.0" : i32
+// CHECK:           %[[VAL_2:.*]] = emitc.literal "10.0" : i32
+// CHECK:           %[[VAL_3:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
+// CHECK:           emitc.assign %[[VAL_0]] : i32 to %[[VAL_3]] : <i32>
+// CHECK:           %[[VAL_4:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i1>
+// CHECK:           emitc.do {
+// CHECK:             %[[VAL_5:.*]] = load %[[VAL_3]] : <i32>
+// CHECK:             %[[VAL_6:.*]] = add %[[VAL_5]], %[[VAL_1]] : (i32, i32) -> i32
+// CHECK:             %[[VAL_7:.*]] = cmp lt, %[[VAL_6]], %[[VAL_2]] : (i32, i32) -> i1
+// CHECK:             %[[VAL_8:.*]] = add %[[VAL_5]], %[[VAL_5]] : (i32, i32) -> i32
+// CHECK:             assign %[[VAL_7]] : i1 to %[[VAL_4]] : <i1>
+// CHECK:             if %[[VAL_7]] {
+// CHECK:               %[[VAL_9:.*]] = call @payload_doOp(%[[VAL_8]]) : (i32) -> i32
+// CHECK:               assign %[[VAL_9]] : i32 to %[[VAL_3]] : <i32>
+// CHECK:             }
+// CHECK:           } while {
+// CHECK:             %[[VAL_10:.*]] = load %[[VAL_4]] : <i1>
+// CHECK:             yield %[[VAL_10]] : i1
+// CHECK:           }
+// CHECK:           %[[VAL_11:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
+// CHECK:           %[[VAL_12:.*]] = emitc.load %[[VAL_3]] : <i32>
+// CHECK:           emitc.assign %[[VAL_12]] : i32 to %[[VAL_11]] : <i32>
+// CHECK:           %[[VAL_13:.*]] = emitc.load %[[VAL_11]] : <i32>
+// CHECK:           return %[[VAL_13]] : i32
+// CHECK:         }
+
+emitc.func @payload_whileOp_two_results(%arg: i32) -> i32 {
+  %result = emitc.add %arg, %arg : (i32, i32) -> i32
+  return %result : i32
+}
+
+func.func @whileOp_two_results() -> i32 {
+  %init = emitc.literal "1.0" : i32
+  %exit = emitc.literal "10.0" : i32
+
+  %res1, %res2 = scf.while (%arg1_1 = %init, %arg1_2 = %init) : (i32, i32) -> (i32, i32) {
+    %sum = emitc.add %arg1_1, %arg1_2 : (i32, i32) -> i32
+    %condition = emitc.cmp lt, %sum, %exit : (i32, i32) -> i1
+    scf.condition(%condition) %arg1_1, %arg1_2  : i32, i32
+  } do {
+  ^bb0(%arg2_1 : i32, %arg2_2 : i32):
+    %next1 = emitc.call @payload_whileOp_two_results(%arg2_1) : (i32) -> i32
+    %next2 = emitc.call @payload_whileOp_two_results(%arg2_2) : (i32) -> i32
+    scf.yield %next1, %next2 : i32, i32
+  }
+  
+  return %res1 : i32
+}
+
+// CHECK-LABEL:   emitc.func @payload_whileOp_two_results(
+// CHECK-SAME:      %[[ARG0:.*]]: i32) -> i32 {
+// CHECK:           %[[VAL_0:.*]] = add %[[ARG0]], %[[ARG0]] : (i32, i32) -> i32
+// CHECK:           return %[[VAL_0]] : i32
+// CHECK:         }
+
+// CHECK-LABEL:   func.func @whileOp_two_results() -> i32 {
+// CHECK:           %[[VAL_0:.*]] = emitc.literal "1.0" : i32
+// CHECK:           %[[VAL_1:.*]] = emitc.literal "10.0" : i32
+// CHECK:           %[[VAL_2:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
+// CHECK:           emitc.assign %[[VAL_0]] : i32 to %[[VAL_2]] : <i32>
+// CHECK:           %[[VAL_3:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
+// CHECK:           emitc.assign %[[VAL_0]] : i32 to %[[VAL_3]] : <i32>
+// CHECK:           emitc.while {
+// CHECK:             %[[VAL_4:.*]] = load %[[VAL_2]] : <i32>
+// CHECK:             %[[VAL_5:.*]] = load %[[VAL_3]] : <i32>
+// CHECK:             %[[VAL_6:.*]] = add %[[VAL_4]], %[[VAL_5]] : (i32, i32) -> i32
+// CHECK:             %[[VAL_7:.*]] = cmp lt, %[[VAL_6]], %[[VAL_1]] : (i32, i32) -> i1
+// CHECK:             yield %[[VAL_7]] : i1
+// CHECK:           } do {
+// CHECK:             %[[VAL_8:.*]] = load %[[VAL_2]] : <i32>
+// CHECK:             %[[VAL_9:.*]] = load %[[VAL_3]] : <i32>
+// CHECK:             %[[VAL_10:.*]] = call @payload_whileOp_two_results(%[[VAL_8]]) : (i32) -> i32
+// CHECK:             %[[VAL_11:.*]] = call @payload_whileOp_two_results(%[[VAL_9]]) : (i32) -> i32
+// CHECK:             assign %[[VAL_10]] : i32 to %[[VAL_2]] : <i32>
+// CHECK:             assign %[[VAL_11]] : i32 to %[[VAL_3]] : <i32>
+// CHECK:           }
+// CHECK:           %[[VAL_12:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
+// CHECK:           %[[VAL_13:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
+// CHECK:           %[[VAL_14:.*]] = emitc.load %[[VAL_2]] : <i32>
+// CHECK:           emitc.assign %[[VAL_14]] : i32 to %[[VAL_12]] : <i32>
+// CHECK:           %[[VAL_15:.*]] = emitc.load %[[VAL_3]] : <i32>
+// CHECK:           emitc.assign %[[VAL_15]] : i32 to %[[VAL_13]] : <i32>
+// CHECK:           %[[VAL_16:.*]] = emitc.load %[[VAL_12]] : <i32>
+// CHECK:           %[[VAL_17:.*]] = emitc.load %[[VAL_13]] : <i32>
+// CHECK:           return %[[VAL_16]] : i32
+// CHECK:         }
+
+emitc.func @payload_doOp_two_results(%arg: i32) -> i32 {
+  %result = emitc.add %arg, %arg : (i32, i32) -> i32
+  return %result : i32
+}
+
+func.func @doOp_two_results() -> i32 {
+  %init = emitc.literal "1.0" : i32
+  %exit = emitc.literal "10.0" : i32
+
+  %res1, %res2 = scf.while (%arg1_1 = %init, %arg1_2 = %init) : (i32, i32) -> (i32, i32) {
+    %sum = emitc.add %arg1_1, %arg1_2 : (i32, i32) -> i32
+    %condition = emitc.cmp lt, %sum, %exit : (i32, i32) -> i1
+    scf.condition(%condition) %init, %arg1_2  : i32, i32
+  } do {
+  ^bb0(%arg2_1 : i32, %arg2_2 : i32):
+    %next1 = emitc.call @payload_doOp_two_results(%arg2_1) : (i32) -> i32
+    %next2 = emitc.call @payload_doOp_two_results(%arg2_2) : (i32) -> i32
+    scf.yield %next1, %next2 : i32, i32
+  }
+  
+  return %res1 : i32
+}
+
+// CHECK-LABEL:   emitc.func @payload_doOp_two_results(
+// CHECK-SAME:      %[[ARG0:.*]]: i32) -> i32 {
+// CHECK:           %[[VAL_0:.*]] = add %[[ARG0]], %[[ARG0]] : (i32, i32) -> i32
+// CHECK:           return %[[VAL_0]] : i32
+// CHECK:         }
+
+// CHECK-LABEL:   func.func @doOp_two_results() -> i32 {
+// CHECK:           %[[VAL_0:.*]] = emitc.literal "1.0" : i32
+// CHECK:           %[[VAL_1:.*]] = emitc.literal "10.0" : i32
+// CHECK:           %[[VAL_2:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
+// CHECK:           emitc.assign %[[VAL_0]] : i32 to %[[VAL_2]] : <i32>
+// CHECK:           %[[VAL_3:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
+// CHECK:           emitc.assign %[[VAL_0]] : i32 to %[[VAL_3]] : <i32>
+// CHECK:           %[[VAL_4:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i1>
+// CHECK:           emitc.do {
+// CHECK:             %[[VAL_5:.*]] = load %[[VAL_2]] : <i32>
+// CHECK:             %[[VAL_6:.*]] = load %[[VAL_3]] : <i32>
+// CHECK:             %[[VAL_7:.*]] = add %[[VAL_5]], %[[VAL_6]] : (i32, i32) -> i32
+// CHECK:             %[[VAL_8:.*]] = cmp lt, %[[VAL_7]], %[[VAL_1]] : (i32, i32) -> i1
+// CHECK:             assign %[[VAL_8]] : i1 to %[[VAL_4]] : <i1>
+// CHECK:             if %[[VAL_8]] {
+// CHECK:               %[[VAL_9:.*]] = call @payload_doOp_two_results(%[[VAL_0]]) : (i32) -> i32
+// CHECK:               %[[VAL_10:.*]] = call @payload_doOp_two_results(%[[VAL_6]]) : (i32) -> i32
+// CHECK:               assign %[[VAL_9]] : i32 to %[[VAL_2]] : <i32>
+// CHECK:               assign %[[VAL_10]] : i32 to %[[VAL_3]] : <i32>
+// CHECK:             }
+// CHECK:           } while {
+// CHECK:             %[[VAL_11:.*]] = load %[[VAL_4]] : <i1>
+// CHECK:             yield %[[VAL_11]] : i1
+// CHECK:           }
+// CHECK:           %[[VAL_12:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
+// CHECK:           %[[VAL_13:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
+// CHECK:           %[[VAL_14:.*]] = emitc.load %[[VAL_2]] : <i32>
+// CHECK:           emitc.assign %[[VAL_14]] : i32 to %[[VAL_12]] : <i32>
+// CHECK:           %[[VAL_15:.*]] = emitc.load %[[VAL_3]] : <i32>
+// CHECK:           emitc.assign %[[VAL_15]] : i32 to %[[VAL_13]] : <i32>
+// CHECK:           %[[VAL_16:.*]] = emitc.load %[[VAL_12]] : <i32>
+// CHECK:           %[[VAL_17:.*]] = emitc.load %[[VAL_13]] : <i32>
+// CHECK:           return %[[VAL_16]] : i32
+// CHECK:         }
diff --git a/mlir/test/Dialect/EmitC/invalid_ops.mlir b/mlir/test/Dialect/EmitC/invalid_ops.mlir
index fdfb0eb46f7c5..3d29844b21c80 100644
--- a/mlir/test/Dialect/EmitC/invalid_ops.mlir
+++ b/mlir/test/Dialect/EmitC/invalid_ops.mlir
@@ -252,7 +252,7 @@ func.func @sub_pointer_pointer(%arg0: !emitc.ptr<f32>, %arg1: !emitc.ptr<f32>) {
 // -----
 
 func.func @test_misplaced_yield() {
-  // expected-error @+1 {{'emitc.yield' op expects parent op to be one of 'emitc.expression, emitc.if, emitc.for, emitc.switch'}}
+  // expected-error @+1 {{'emitc.yield' op expects parent op to be one of 'emitc.do, emitc.expression, emitc.for, emitc.if, emitc.switch, emitc.while'}}
   emitc.yield
   return
 }
@@ -719,3 +719,197 @@ emitc.class @testClass {
     return
   }
 }
+
+// -----
+
+func.func @test_while(%arg0 : !emitc.ptr<i32>) {
+  // expected-error @+1 {{'emitc.while' op condition region cannot be empty}}
+  emitc.while {
+  } do {
+    emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
+  }
+
+  return
+}
+
+// -----
+
+emitc.func @test_while(%arg0 : !emitc.ptr<i32>) {
+  %1 = emitc.literal "1" : i32
+
+  // expected-error @+1 {{'emitc.while' op expected all operations in condition region must implement CExpression trait, but emitc.literal does not}}
+  emitc.while {
+    %2 = emitc.literal "2" : i32
+    %3 = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
+    emitc.yield %3 : i1
+  } do {
+    emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
+  }
+
+  return
+}
+
+// -----
+
+func.func @test_while(%arg0 : !emitc.ptr<i32>) {
+  %1 = emitc.literal "1" : i32
+  %2 = emitc.literal "2" : i32
+
+  // expected-error @+1 {{'emitc.while' op expected condition region to end with emitc.yield, but got emitc.cmp}}
+  emitc.while {
+    %3 = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
+  } do {
+    emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
+  }
+
+  return
+}
+
+// -----
+
+func.func @test_while(%arg0 : !emitc.ptr<i32>) {
+  %1 = emitc.literal "1" : i32
+  %2 = emitc.literal "2" : i32
+
+  // expected-error @+1 {{'emitc.while' op condition region must yield a single i1 value}}
+  emitc.while {
+    %3 = emitc.add %1, %2 : (i32, i32) -> i32
+    emitc.yield %3 : i32
+  } do {
+    emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
+  }
+
+  return
+}
+
+// -----
+
+func.func @test_while() {
+  %1 = emitc.literal "1" : i32
+  %2 = emitc.literal "2" : i32
+
+  // expected-error @+1 {{'emitc.while' op body region cannot be empty}}
+  emitc.while {
+    %3 = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
+    emitc.yield %3 : i1
+  } do {
+  }
+
+  return
+}
+
+// -----
+
+emitc.func @test_while(%arg0 : !emitc.ptr<i32>) {
+  %1 = emitc.literal "1" : i32
+  %2 = emitc.literal "2" : i32
+
+  // expected-error @+1 {{'emitc.while' op expected body region to return 0 values, but body returns 1}}
+  emitc.while {
+    %3 = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
+    emitc.yield %3 : i1
+  } do {
+    emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
+    %4 = emitc.add %1, %2 : (i32, i32) -> i32
+    emitc.yield %4 : i32
+  }
+
+  return
+}
+
+// -----
+
+func.func @test_do_while(%arg0 : !emitc.ptr<i32>) {
+  // expected-error @+1 {{'emitc.do' op condition region cannot be empty}}
+  emitc.do {
+    emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
+  } while {
+  }
+
+  return
+}
+
+// -----
+
+emitc.func @test_do_while(%arg0 : !emitc.ptr<i32>) {
+  %1 = emitc.literal "1" : i32
+
+  // expected-error @+1 {{'emitc.do' op expected all operations in condition region must implement CExpression trait, but emitc.literal does not}}
+  emitc.do {
+    emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
+  } while {
+    %2 = emitc.literal "2" : i32
+    %3 = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
+    emitc.yield %3 : i1
+  }
+
+  return
+}
+
+// -----
+
+func.func @test_do_while(%arg0 : !emitc.ptr<i32>) {
+  %1 = emitc.literal "1" : i32
+  %2 = emitc.literal "2" : i32
+
+  // expected-error @+1 {{'emitc.do' op expected condition region to end with emitc.yield, but got emitc.cmp}}
+  emitc.do {
+    emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
+  } while {
+    %3 = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
+  }
+
+  return
+}
+
+// -----
+
+func.func @test_do_while(%arg0 : !emitc.ptr<i32>) {
+  %1 = emitc.literal "1" : i32
+  %2 = emitc.literal "2" : i32
+
+  // expected-error @+1 {{'emitc.do' op condition region must yield a single i1 value}}
+  emitc.do {
+    emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
+  } while {
+    %3 = emitc.add %1, %2 : (i32, i32) -> i32
+    emitc.yield %3 : i32
+  }
+
+  return
+}
+
+// -----
+
+func.func @test_do_while() {
+  %1 = emitc.literal "1" : i32
+  %2 = emitc.literal "2" : i32
+
+  // expected-error @+1 {{'emitc.do' op body region cannot be empty}}
+  emitc.do {
+  } while {
+    %3 = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
+    emitc.yield %3 : i1
+  }
+
+  return
+}
+
+// -----
+
+emitc.func @test_do_while(%arg0 : !emitc.ptr<i32>) {
+  %1 = emitc.literal "1" : i32
+  %2 = emitc.literal "2" : i32
+
+  // expected-error @+1 {{'emitc.do' op expected body region to return 0 values, but body returns 1}}
+  emitc.do {
+    emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
+    %4 = emitc.add %1, %2 : (i32, i32) -> i32
+    emitc.yield %4 : i32
+  } while {
+    %3 = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
+    emitc.yield %3 : i1
+  }
+
+  return
+}
diff --git a/mlir/test/Dialect/EmitC/ops.mlir b/mlir/test/Dialect/EmitC/ops.mlir
index e890f77173de7..a8b945d1bfb5d 100644
--- a/mlir/test/Dialect/EmitC/ops.mlir
+++ b/mlir/test/Dialect/EmitC/ops.mlir
@@ -325,3 +325,35 @@ emitc.class final @finalClass {
     return
   }
 }
+
+func.func @while(%arg0 : !emitc.ptr<i32>) {
+  %1 = emitc.literal "1" : i32
+  %2 = emitc.literal "2" : i32
+  %3 = emitc.literal "3" : i32
+
+  emitc.while {
+    %add = emitc.add %1, %2 : (i32, i32) -> i32
+    %5 = emitc.cmp eq, %add, %3 : (i32, i32) -> i1
+    emitc.yield %5 : i1
+  } do {
+    emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
+  }
+
+  return
+}
+
+func.func @do(%arg0 : !emitc.ptr<i32>) {
+  %1 = emitc.literal "1" : i32
+  %2 = emitc.literal "2" : i32
+  %3 = emitc.literal "3" : i32
+
+  emitc.do {
+    emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
+  } while {
+    %add = emitc.add %1, %2 : (i32, i32) -> i32
+    %5 = emitc.cmp eq, %add, %3 : (i32, i32) -> i1
+    emitc.yield %5 : i1
+  }
+
+  return
+}
diff --git a/mlir/test/Target/Cpp/do.mlir b/mlir/test/Target/Cpp/do.mlir
new file mode 100644
index 0000000000000..034fe77ed22c4
--- /dev/null
+++ b/mlir/test/Target/Cpp/do.mlir
@@ -0,0 +1,69 @@
+// RUN: mlir-translate -mlir-to-cpp %s | FileCheck --match-full-lines %s -check-prefix=CPP-DEFAULT
+
+
+// CPP-DEFAULT-LABEL: void emitc_do(int32_t* v1) {
+// CPP-DEFAULT:         int32_t v2 = 0;
+// CPP-DEFAULT:         do {
+// CPP-DEFAULT:           printf("%d", *v1);
+// CPP-DEFAULT:           int32_t v3 = v2;
+// CPP-DEFAULT:           int32_t v4 = v3 + 1;
+// CPP-DEFAULT:           v2 = v4;
+// CPP-DEFAULT:         } while (v2 <= 10);
+// CPP-DEFAULT:         return;
+// CPP-DEFAULT:       }
+
+emitc.func @emitc_do(%arg0 : !emitc.ptr<i32>) {
+  %var = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue<i32>
+  %0 = emitc.literal "10" : i32
+  %1 = emitc.literal "1" : i32
+
+  emitc.do {
+    emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
+    %var_load = load %var : <i32>
+    %tmp_add = add %var_load, %1 : (i32, i32) -> i32
+    "emitc.assign"(%var, %tmp_add) : (!emitc.lvalue<i32>, i32) -> ()
+  } while {
+    %var_load = load %var : <i32>
+    %res = emitc.cmp le, %var_load, %0 : (i32, i32) -> i1
+    emitc.yield %res : i1
+  }
+
+  return
+}
+
+
+// CPP-DEFAULT-LABEL: void emitc_do_with_expression(int32_t* v1) {
+// CPP-DEFAULT:         int32_t v2 = 0;
+// CPP-DEFAULT:         int32_t v3 = 10 + 1;
+// CPP-DEFAULT:         do {
+// CPP-DEFAULT:           printf("%d", *v1);
+// CPP-DEFAULT:           int32_t v4 = v2;
+// CPP-DEFAULT:           int32_t v5 = v4 + 1;
+// CPP-DEFAULT:           v2 = v5;
+// CPP-DEFAULT:         } while (v2 <= v3);
+// CPP-DEFAULT:         return;
+// CPP-DEFAULT:       }
+
+emitc.func @emitc_do_with_expression(%arg0 : !emitc.ptr<i32>) {
+  %var = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue<i32>
+  %0 = emitc.literal "10" : i32
+  %1 = emitc.literal "1" : i32
+
+  %add = emitc.expression : i32 {
+    %add = add %0, %1 : (i32, i32) -> i32
+    yield %add : i32
+  }
+
+  emitc.do {
+    emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
+    %var_load = load %var : <i32>
+    %tmp_add = add %var_load, %1 : (i32, i32) -> i32
+    "emitc.assign"(%var, %tmp_add) : (!emitc.lvalue<i32>, i32) -> ()
+  } while {
+    %var_load = load %var : <i32>
+    %res = emitc.cmp le, %var_load, %add : (i32, i32) -> i1
+    emitc.yield %res : i1
+  }
+
+  return
+}
diff --git a/mlir/test/Target/Cpp/while.mlir b/mlir/test/Target/Cpp/while.mlir
new file mode 100644
index 0000000000000..76cf7c2f752cf
--- /dev/null
+++ b/mlir/test/Target/Cpp/while.mlir
@@ -0,0 +1,69 @@
+// RUN: mlir-translate -mlir-to-cpp %s | FileCheck --match-full-lines %s -check-prefix=CPP-DEFAULT
+
+
+// CPP-DEFAULT-LABEL: void emitc_while(int32_t* v1) {
+// CPP-DEFAULT:         int32_t v2 = 0;
+// CPP-DEFAULT:         while (v2 <= 10) {
+// CPP-DEFAULT:           printf("%d", *v1);
+// CPP-DEFAULT:           int32_t v3 = v2;
+// CPP-DEFAULT:           int32_t v4 = v3 + 1;
+// CPP-DEFAULT:           v2 = v4;
+// CPP-DEFAULT:         }
+// CPP-DEFAULT:         return;
+// CPP-DEFAULT:       }
+
+emitc.func @emitc_while(%arg0 : !emitc.ptr<i32>) {
+  %var = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue<i32>
+  %0 = emitc.literal "10" : i32
+  %1 = emitc.literal "1" : i32
+
+  emitc.while {
+    %var_load = load %var : <i32>
+    %res = emitc.cmp le, %var_load, %0 : (i32, i32) -> i1
+    emitc.yield %res : i1
+  } do {
+    emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
+    %var_load = load %var : <i32>
+    %tmp_add = add %var_load, %1 : (i32, i32) -> i32
+    "emitc.assign"(%var, %tmp_add) : (!emitc.lvalue<i32>, i32) -> ()
+  }
+
+  return
+}
+
+
+// CPP-DEFAULT-LABEL: void emitc_while_with_expression(int32_t* v1) {
+// CPP-DEFAULT:         int32_t v2 = 0;
+// CPP-DEFAULT:         int32_t v3 = 10 + 1;
+// CPP-DEFAULT:         while (v2 <= v3) {
+// CPP-DEFAULT:           printf("%d", *v1);
+// CPP-DEFAULT:           int32_t v4 = v2;
+// CPP-DEFAULT:           int32_t v5 = v4 + 1;
+// CPP-DEFAULT:           v2 = v5;
+// CPP-DEFAULT:         }
+// CPP-DEFAULT:         return;
+// CPP-DEFAULT:       }
+
+emitc.func @emitc_while_with_expression(%arg0 : !emitc.ptr<i32>) {
+  %var = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue<i32>
+  %0 = emitc.literal "10" : i32
+  %1 = emitc.literal "1" : i32
+
+  %add = emitc.expression : i32 {
+    %add = add %0, %1 : (i32, i32) -> i32
+    yield %add : i32
+  }
+
+  emitc.while {
+    %var_load = load %var : <i32>
+    %res = emitc.cmp le, %var_load, %add : (i32, i32) -> i1
+    emitc.yield %res : i1
+  } do {
+    emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
+    %var_load = load %var : <i32>
+    %tmp_add = add %var_load, %1 : (i32, i32) -> i32
+    "emitc.assign"(%var, %tmp_add) : (!emitc.lvalue<i32>, i32) -> ()
+  }
+
+  return
+}

>From 32a25db56796a628377786d9def08edd1e2dfd2b Mon Sep 17 00:00:00 2001
From: Vlad Lazar <lazar_2004 at list.ru>
Date: Wed, 23 Jul 2025 15:58:05 +0300
Subject: [PATCH 2/6] [mlir][emitc] Change conditional region structure

Change the canonical structure of a conditional region:
- The condition region must contain exactly one block with:
  1. An `emitc.expression` operation producing an i1 value
  2. An `emitc.yield` passing through the expression result
- The body region must not yield any values
---
 mlir/include/mlir/Dialect/EmitC/IR/EmitC.td   | 172 ++++++++-----
 mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp |  48 ++--
 mlir/lib/Dialect/EmitC/IR/EmitC.cpp           |  61 +++--
 mlir/lib/Target/Cpp/TranslateToCpp.cpp        | 147 ++++-------
 mlir/test/Conversion/SCFToEmitC/while.mlir    | 154 ++++++------
 mlir/test/Dialect/EmitC/invalid_ops.mlir      | 229 ++++++++++++++----
 mlir/test/Dialect/EmitC/ops.mlir              |  20 +-
 mlir/test/Target/Cpp/do.mlir                  |  97 ++++++--
 mlir/test/Target/Cpp/while.mlir               |  97 ++++++--
 9 files changed, 680 insertions(+), 345 deletions(-)

diff --git a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
index 889447524e090..48d40c5796618 100644
--- a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
+++ b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
@@ -1719,63 +1719,88 @@ def EmitC_GetFieldOp
 }
 
 def EmitC_WhileOp : EmitC_Op<"while",
-      [HasOnlyGraphRegion, RecursiveMemoryEffects, NoRegionArguments, OpAsmOpInterface, NoTerminator]> {
+      [NoTerminator, OpAsmOpInterface, RecursiveMemoryEffects]> {
   let summary = "While operation";
- let description = [{
+  let description = [{
     The `emitc.while` operation represents a C/C++ while loop construct that
     repeatedly executes a body region as long as a condition region evaluates to
     true. The operation has two regions:
     
-    1. A condition region that must yield a boolean value (i1) 
+    1. A condition region that must yield a boolean value (i1)
     2. A body region that contains the loop body
 
-    The condition region is evaluated before each iteration. If it yields true,
-    the body region is executed. The loop terminates when the condition yields
-    false. The condition region must contain exactly one block that terminates
-    with an `emitc.yield` operation producing an i1 value.
+    The condition is evaluated before each iteration as follows:
+    - The condition region must contain exactly one block with:
+      1. An `emitc.expression` operation producing an i1 value
+      2. An `emitc.yield` passing through the expression result
+    - The expression's body contains the actual condition logic
+
+    If the condition yields true, the body region is executed. The loop terminates
+    when the condition yields false. The body region must not yield any values.
+
+    The canonical structure of `emitc.while` is:
+    
+    ```mlir
+    emitc.while {
+      // Condition region (must yield i1)
+      %condition = emitc.expression : i1 {
+        // Condition computation...
+        %result = ... : i1  // Last operation must produce i1
+        emitc.yield %result : i1
+      }
+      // Forward expression result
+      emitc.yield %condition : i1  
+    } do {
+      // Body region (no terminator required).
+      // Loop body operations...
+    }
+    ```
 
     Example:
 
     ```mlir
-    emitc.func @foo(%arg0 : !emitc.ptr<i32>) {
-      %var = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue<i32>
-      %0 = emitc.literal "10" : i32
-      %1 = emitc.literal "1" : i32
+    emitc.func @while_example() {
+      %counter = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue<i32>
+      %end = emitc.literal "10" : i32
+      %step = emitc.literal "1" : i32
 
       emitc.while {
-        %var_load = load %var : <i32>
-        %res = emitc.cmp le, %var_load, %0 : (i32, i32) -> i1
-        emitc.yield %res : i1
+        %condition = emitc.expression : i1 {
+          %current = emitc.load %counter : !emitc.lvalue<i32>
+          %cmp_res = emitc.cmp lt, %current, %end : (i32, i32) -> i1
+          emitc.yield %cmp_res : i1
+        }
+        emitc.yield %condition : i1
       } do {
-        emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
-        %var_load = load %var : <i32>
-        %tmp_add = add %var_load, %1 : (i32, i32) -> i32
-        "emitc.assign"(%var, %tmp_add) : (!emitc.lvalue<i32>, i32) -> ()
-      }
+        // Print current value
+        %val = emitc.load %counter : !emitc.lvalue<i32>
+        emitc.verbatim "printf(\"%d\\n\", {});" args %val : i32
 
+        // Increment counter
+        %new_val = emitc.add %val, %step : (i32, i32) -> i32
+        "emitc.assign"(%counter, %new_val) : (!emitc.lvalue<i32>, i32) -> ()
+      }
       return
     }
     ```
-
     ```c++
     // Code emitted for the operation above.
-    void foo(int32_t* v1) {
-      int32_t v2 = 0;
-      while (v2 <= 10) {
-        printf("%d", *v1);
-        int32_t v3 = v2;
-        int32_t v4 = v3 + 1;
-        v2 = v4;
+    void while_example() {
+      int32_t v1 = 0;
+      while (v1 < 10) {
+        int32_t v2 = v1;
+        printf("%d\n", v2);
+        int32_t v3 = v2 + 1;
+        v1 = v3;
       }
       return;
     }
     ```
   }];
 
-  let arguments = (ins);
-  let results = (outs); 
-  let regions = (region MaxSizedRegion<1>:$conditionRegion,
-                        MaxSizedRegion<1>:$bodyRegion);
+  let results = (outs);
+  let regions = (region SizedRegion<1>:$conditionRegion,
+                        SizedRegion<1>:$bodyRegion);
 
   let hasCustomAssemblyFormat = 1;
   let hasVerifier = 1;
@@ -1795,54 +1820,81 @@ def EmitC_WhileOp : EmitC_Op<"while",
 }
 
 def EmitC_DoOp : EmitC_Op<"do",
-      [RecursiveMemoryEffects, NoRegionArguments, OpAsmOpInterface, NoTerminator]> {
+      [NoTerminator, OpAsmOpInterface, RecursiveMemoryEffects]> {
   let summary = "Do-while operation";
- let description = [{
+  let description = [{
     The `emitc.do` operation represents a C/C++ do-while loop construct that
-    executes a body region first and then repeatedly executes it as long as a
-    condition region evaluates to true. The operation has two regions:
+    repeatedly executes a body region as long as a condition region evaluates to
+    true. The operation has two regions:
 
     1. A body region that contains the loop body
     2. A condition region that must yield a boolean value (i1)
 
+    The condition is evaluated before each iteration as follows:
+    - The condition region must contain exactly one block with:
+      1. An `emitc.expression` operation producing an i1 value
+      2. An `emitc.yield` passing through the expression result
+    - The expression's body contains the actual condition logic
+
     Unlike a while loop, the body region is executed before the first evaluation
-    of the condition. The loop terminates when the condition yields false. The
-    condition region must contain exactly one block that terminates with an
-    `emitc.yield` operation producing an i1 value.
+    of the condition. Thus, there is a guarantee that the loop will be executed 
+    at least once. The loop terminates when the condition yields false.
+
+    The canonical structure of `emitc.do` is:
+
+    ```mlir
+    emitc.do {
+      // Body region (no terminator required).
+      // Loop body operations...
+    } while {
+      // Condition region (must yield i1)
+      %condition = emitc.expression : i1 {
+        // Condition computation...
+        %result = ... : i1  // Last operation must produce i1
+        emitc.yield %result : i1
+      }
+      // Forward expression result
+      emitc.yield %condition : i1  
+    }
+    ```
 
     Example:
 
     ```mlir
-    emitc.func @foo(%arg0 : !emitc.ptr<i32>) {
-      %var = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue<i32>
-      %0 = emitc.literal "10" : i32
-      %1 = emitc.literal "1" : i32
+    emitc.func @do_example() {
+      %counter = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue<i32>
+      %end = emitc.literal "10" : i32
+      %step = emitc.literal "1" : i32
 
       emitc.do {
-        emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
-        %var_load = load %var : <i32>
-        %tmp_add = add %var_load, %1 : (i32, i32) -> i32
-        "emitc.assign"(%var, %tmp_add) : (!emitc.lvalue<i32>, i32) -> ()
+        // Print current value
+        %val = emitc.load %counter : !emitc.lvalue<i32>
+        emitc.verbatim "printf(\"%d\\n\", {});" args %val : i32
+
+        // Increment counter
+        %new_val = emitc.add %val, %step : (i32, i32) -> i32
+        "emitc.assign"(%counter, %new_val) : (!emitc.lvalue<i32>, i32) -> ()
       } while {
-        %var_load = load %var : <i32>
-        %res = emitc.cmp le, %var_load, %0 : (i32, i32) -> i1
-        emitc.yield %res : i1
+        %condition = emitc.expression : i1 {
+          %current = emitc.load %counter : !emitc.lvalue<i32>
+          %cmp_res = emitc.cmp lt, %current, %end : (i32, i32) -> i1
+          emitc.yield %cmp_res : i1
+        }
+        emitc.yield %condition : i1
       }
-
       return
     }
     ```
-
     ```c++
     // Code emitted for the operation above.
-    void foo(int32_t* v1) {
-      int32_t v2 = 0;
+    void do_example() {
+      int32_t v1 = 0;
       do {
-        printf("%d", *v1);
-        int32_t v3 = v2;
-        int32_t v4 = v3 + 1;
-        v2 = v4;
-      } while (v2 <= 10);
+        int32_t v2 = v1;
+        printf("%d\n", v2);
+        int32_t v3 = v2 + 1;
+        v1 = v3;
+      } while (v1 < 10);
       return;
     }
     ```
@@ -1850,8 +1902,8 @@ def EmitC_DoOp : EmitC_Op<"do",
 
   let arguments = (ins);
   let results = (outs); 
-  let regions = (region MaxSizedRegion<1>:$bodyRegion,
-                        MaxSizedRegion<1>:$conditionRegion);
+  let regions = (region SizedRegion<1>:$bodyRegion,
+                        SizedRegion<1>:$conditionRegion);
 
   let hasCustomAssemblyFormat = 1;
   let hasVerifier = 1;
diff --git a/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp b/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp
index 6aff0ce98ab91..c98609915b942 100644
--- a/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp
+++ b/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp
@@ -477,27 +477,34 @@ struct WhileLowering : public OpConversionPattern<WhileOp> {
     auto loweredWhile = rewriter.create<emitc::WhileOp>(loc);
 
     // Lower before region to condition region.
-    rewriter.inlineRegionBefore(whileOp.getBefore(),
-                                loweredWhile.getConditionRegion(),
-                                loweredWhile.getConditionRegion().end());
+    Region &condRegion = loweredWhile.getConditionRegion();
+    Block *condBlock = rewriter.createBlock(&condRegion);
+    rewriter.setInsertionPointToStart(condBlock);
 
-    Block *condBlock = &loweredWhile.getConditionRegion().front();
-    replaceBlockArgsWithVarLoads(condBlock, vars, rewriter, loc);
+    Type i1Type = IntegerType::get(context, 1);
+    auto exprOp = rewriter.create<emitc::ExpressionOp>(loc, TypeRange{i1Type});
+    Region &exprRegion = exprOp.getBodyRegion();
 
-    Operation *condTerminator =
-        loweredWhile.getConditionRegion().back().getTerminator();
-    auto condOp = cast<scf::ConditionOp>(condTerminator);
-    rewriter.setInsertionPoint(condOp);
+    rewriter.inlineRegionBefore(whileOp.getBefore(), exprRegion,
+                                exprRegion.begin());
+
+    Block *exprBlock = &exprRegion.front();
+    replaceBlockArgsWithVarLoads(exprBlock, vars, rewriter, loc);
+
+    auto condOp = cast<scf::ConditionOp>(exprBlock->getTerminator());
     Value condition = rewriter.getRemappedValue(condOp.getCondition());
-    rewriter.create<emitc::YieldOp>(condOp.getLoc(), condition);
-    rewriter.eraseOp(condOp);
+    rewriter.setInsertionPointAfter(condOp);
+    rewriter.replaceOpWithNewOp<emitc::YieldOp>(condOp, condition);
+
+    rewriter.setInsertionPointToEnd(condBlock);
+    rewriter.create<emitc::YieldOp>(loc, exprOp);
 
     // Lower after region to body region.
-    rewriter.inlineRegionBefore(whileOp.getAfter(),
-                                loweredWhile.getBodyRegion(),
-                                loweredWhile.getBodyRegion().end());
+    Region &bodyRegion = loweredWhile.getBodyRegion();
+    rewriter.inlineRegionBefore(whileOp.getAfter(), bodyRegion,
+                                bodyRegion.end());
 
-    Block *bodyBlock = &loweredWhile.getBodyRegion().front();
+    Block *bodyBlock = &bodyRegion.front();
     replaceBlockArgsWithVarLoads(bodyBlock, vars, rewriter, loc);
 
     // Convert scf.yield to variable assignments for state updates.
@@ -559,11 +566,20 @@ struct WhileLowering : public OpConversionPattern<WhileOp> {
     rewriter.eraseOp(condOp);
 
     // Create condition region that loads from the flag variable.
-    Block *condBlock = rewriter.createBlock(&loweredDo.getConditionRegion());
+    Region &condRegion = loweredDo.getConditionRegion();
+    Block *condBlock = rewriter.createBlock(&condRegion);
     rewriter.setInsertionPointToStart(condBlock);
+
+    auto exprOp = rewriter.create<emitc::ExpressionOp>(loc, TypeRange{i1Type});
+    Block *exprBlock = rewriter.createBlock(&exprOp.getBodyRegion());
+    rewriter.setInsertionPointToStart(exprBlock);
+
     Value cond = rewriter.create<emitc::LoadOp>(loc, i1Type, conditionVal);
     rewriter.create<emitc::YieldOp>(loc, cond);
 
+    rewriter.setInsertionPointToEnd(condBlock);
+    rewriter.create<emitc::YieldOp>(loc, exprOp);
+
     return success();
   }
 };
diff --git a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
index b6d1704a14d6c..2284215b772b6 100644
--- a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
+++ b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
@@ -1546,37 +1546,50 @@ static Operation *getRootOpFromLoopCondition(Region &condRegion) {
 
 static LogicalResult verifyLoopRegions(Operation &op, Region &condition,
                                        Region &body) {
-  if (condition.empty())
-    return op.emitOpError("condition region cannot be empty");
-
   Block &condBlock = condition.front();
-  for (Operation &inner : condBlock.without_terminator()) {
-    if (!inner.hasTrait<OpTrait::emitc::CExpression>())
-      return op.emitOpError(
-                 "expected all operations in condition region must implement "
-                 "CExpression trait, but ")
-             << inner.getName() << " does not";
-  }
 
-  auto condYield = dyn_cast<emitc::YieldOp>(condBlock.back());
-  if (!condYield)
+  if (condBlock.getOperations().size() != 2)
     return op.emitOpError(
-               "expected condition region to end with emitc.yield, but got ")
-           << condBlock.back().getName();
+               "condition region must contain exactly two operations: "
+               "'emitc.expression' followed by 'emitc.yield', but found ")
+           << condBlock.getOperations().size() << " operations";
+
+  Operation &first = condBlock.front();
+  auto exprOp = dyn_cast<emitc::ExpressionOp>(first);
+  if (!exprOp)
+    return op.emitOpError("expected first op in condition region to be "
+                          "'emitc.expression', "
+                          "but got ")
+           << first.getName();
+
+  if (!exprOp.getResult().getType().isInteger(1))
+    return op.emitOpError("emitc.expression in condition region must return "
+                          "'i1', but returns ")
+           << exprOp.getResult().getType();
+
+  Operation &last = *std::next(condBlock.begin());
+  auto condYield = dyn_cast<emitc::YieldOp>(last);
+  if (!condYield)
+    return op.emitOpError("expected last op in condition region to be "
+                          "'emitc.yield', but got ")
+           << last.getName();
+
+  if (condYield.getNumOperands() != 1) {
+    return op.emitOpError("expected condition region to return 1 value, but "
+                          "it returns ")
+           << condYield.getNumOperands() << " values";
+  }
 
-  if (condYield.getNumOperands() != 1 ||
-      !condYield.getOperand(0).getType().isInteger(1))
-    return op.emitOpError("condition region must yield a single i1 value");
+  if (condYield.getOperand(0) != exprOp.getResult())
+    return op.emitError("'emitc.yield' must return result of "
+                        "'emitc.expression' from this condition region");
 
-  if (body.empty())
+  Block &bodyBlock = body.front();
+  if (bodyBlock.empty())
     return op.emitOpError("body region cannot be empty");
 
-  Block &bodyBlock = body.front();
-  if (auto bodyYield = dyn_cast<emitc::YieldOp>(bodyBlock.back()))
-    if (bodyYield.getNumOperands() != 0)
-      return op.emitOpError(
-                 "expected body region to return 0 values, but body returns ")
-             << bodyYield.getNumOperands();
+  if (bodyBlock.mightHaveTerminator())
+    return op.emitOpError("body region must not contain terminator");
 
   return success();
 }
diff --git a/mlir/lib/Target/Cpp/TranslateToCpp.cpp b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
index 937d91f4246f2..0afd626716949 100644
--- a/mlir/lib/Target/Cpp/TranslateToCpp.cpp
+++ b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
@@ -178,12 +178,6 @@ struct CppEmitter {
   /// Emit an expression as a C expression.
   LogicalResult emitExpression(ExpressionOp expressionOp);
 
-  /// Emit while as a C while.
-  LogicalResult emitWhile(WhileOp expressionOp);
-
-  /// Emit do-while as a C do-while.
-  LogicalResult emitDo(DoOp expressionOp);
-
   /// Insert the expression representing the operation into the value cache.
   void cacheDeferredOpResult(Value value, StringRef str);
 
@@ -558,12 +552,51 @@ static LogicalResult printOperation(CppEmitter &emitter,
 
 static LogicalResult printOperation(CppEmitter &emitter,
                                     emitc::WhileOp whileOp) {
-  return emitter.emitWhile(whileOp);
+  raw_indented_ostream &os = emitter.ostream();
+
+  os << "while (";
+
+  Block &condBlock = whileOp.getConditionRegion().front();
+  auto condYield = cast<emitc::YieldOp>(condBlock.back());
+  if (failed(emitter.emitExpression(
+          cast<emitc::ExpressionOp>(condYield.getOperand(0).getDefiningOp()))))
+    return failure();
+
+  os << ") {\n";
+  os.indent();
+
+  Block &bodyBlock = whileOp.getBodyRegion().front();
+  for (Operation &op : bodyBlock) {
+    if (failed(emitter.emitOperation(op, /*trailingSemicolon=*/true)))
+      return failure();
+  }
+
+  os.unindent() << "}";
+  return success();
 }
 
-static LogicalResult printOperation(CppEmitter &emitter,
-                                    emitc::DoOp doWhileOp) {
-  return emitter.emitDo(doWhileOp);
+static LogicalResult printOperation(CppEmitter &emitter, emitc::DoOp doOp) {
+  raw_indented_ostream &os = emitter.ostream();
+
+  os << "do {\n";
+  os.indent();
+
+  Block &bodyBlock = doOp.getBodyRegion().front();
+  for (Operation &op : bodyBlock) {
+    if (failed(emitter.emitOperation(op, /*trailingSemicolon=*/true)))
+      return failure();
+  }
+
+  os.unindent() << "} while (";
+
+  Block &condBlock = doOp.getConditionRegion().front();
+  auto condYield = cast<emitc::YieldOp>(condBlock.back());
+  if (failed(emitter.emitExpression(
+          cast<emitc::ExpressionOp>(condYield.getOperand(0).getDefiningOp()))))
+    return failure();
+
+  os << ");";
+  return success();
 }
 
 static LogicalResult printOperation(CppEmitter &emitter, emitc::CmpOp cmpOp) {
@@ -1523,84 +1556,6 @@ LogicalResult CppEmitter::emitExpression(ExpressionOp expressionOp) {
   return success();
 }
 
-LogicalResult CppEmitter::emitWhile(WhileOp whileOp) {
-  assert(emittedExpressionPrecedence.empty() &&
-         "Expected precedence stack to be empty");
-  Operation *rootOp = whileOp.getRootOp();
-
-  emittedExpression = whileOp;
-  FailureOr<int> precedence = getOperatorPrecedence(rootOp);
-  if (failed(precedence))
-    return failure();
-  pushExpressionPrecedence(precedence.value());
-
-  os << "while (";
-  if (failed(emitOperation(*rootOp, /*trailingSemicolon=*/false)))
-    return failure();
-  os << ") {\n";
-
-  popExpressionPrecedence();
-  assert(emittedExpressionPrecedence.empty() &&
-         "Expected precedence stack to be empty");
-  emittedExpression = nullptr;
-
-  os.indent();
-
-  Region &bodyRegion = whileOp.getBodyRegion();
-  auto regionOps = bodyRegion.getOps();
-
-  for (Operation &op : regionOps) {
-    if (isa<emitc::YieldOp>(op))
-      continue;
-
-    if (failed(emitOperation(op, /*trailingSemicolon=*/true)))
-      return failure();
-  }
-
-  os.unindent() << "}";
-
-  return success();
-}
-
-LogicalResult CppEmitter::emitDo(DoOp doWhileOp) {
-  os << "do {\n";
-  os.indent();
-
-  Region &bodyRegion = doWhileOp.getBodyRegion();
-  auto regionOps = bodyRegion.getOps();
-
-  for (Operation &op : regionOps) {
-    if (isa<emitc::YieldOp>(op))
-      continue;
-
-    if (failed(emitOperation(op, /*trailingSemicolon=*/true)))
-      return failure();
-  }
-
-  os.unindent() << "} while (";
-
-  assert(emittedExpressionPrecedence.empty() &&
-         "Expected precedence stack to be empty");
-  Operation *rootOp = doWhileOp.getRootOp();
-
-  emittedExpression = doWhileOp;
-  FailureOr<int> precedence = getOperatorPrecedence(rootOp);
-  if (failed(precedence))
-    return failure();
-  pushExpressionPrecedence(precedence.value());
-
-  if (failed(emitOperation(*rootOp, /*trailingSemicolon=*/false)))
-    return failure();
-  os << ");";
-
-  popExpressionPrecedence();
-  assert(emittedExpressionPrecedence.empty() &&
-         "Expected precedence stack to be empty");
-  emittedExpression = nullptr;
-
-  return success();
-}
-
 LogicalResult CppEmitter::emitOperand(Value value) {
   if (isPartOfCurrentExpression(value)) {
     Operation *def = value.getDefiningOp();
@@ -1798,14 +1753,14 @@ LogicalResult CppEmitter::emitOperation(Operation &op, bool trailingSemicolon) {
                 emitc::BitwiseRightShiftOp, emitc::BitwiseXorOp, emitc::CallOp,
                 emitc::CallOpaqueOp, emitc::CastOp, emitc::ClassOp,
                 emitc::CmpOp, emitc::ConditionalOp, emitc::ConstantOp,
-                emitc::DeclareFuncOp, emitc::DivOp, emitc::DoOp, emitc::ExpressionOp,
-                emitc::FieldOp, emitc::FileOp, emitc::ForOp, emitc::FuncOp,
-                emitc::GetFieldOp, emitc::GlobalOp, emitc::IfOp,
-                emitc::IncludeOp, emitc::LoadOp, emitc::LogicalAndOp,
-                emitc::LogicalNotOp, emitc::LogicalOrOp, emitc::MulOp,
-                emitc::RemOp, emitc::ReturnOp, emitc::SubOp, emitc::SwitchOp,
-                emitc::UnaryMinusOp, emitc::UnaryPlusOp, emitc::VariableOp,
-                emitc::VerbatimOp, emitc::WhileOp>(
+                emitc::DeclareFuncOp, emitc::DivOp, emitc::DoOp,
+                emitc::ExpressionOp, emitc::FieldOp, emitc::FileOp,
+                emitc::ForOp, emitc::FuncOp, emitc::GetFieldOp, emitc::GlobalOp,
+                emitc::IfOp, emitc::IncludeOp, emitc::LoadOp,
+                emitc::LogicalAndOp, emitc::LogicalNotOp, emitc::LogicalOrOp,
+                emitc::MulOp, emitc::RemOp, emitc::ReturnOp, emitc::SubOp,
+                emitc::SwitchOp, emitc::UnaryMinusOp, emitc::UnaryPlusOp,
+                emitc::VariableOp, emitc::VerbatimOp, emitc::WhileOp>(
 
               [&](auto op) { return printOperation(*this, op); })
           // Func ops.
diff --git a/mlir/test/Conversion/SCFToEmitC/while.mlir b/mlir/test/Conversion/SCFToEmitC/while.mlir
index d4d64fac29280..9ded0123e3cca 100644
--- a/mlir/test/Conversion/SCFToEmitC/while.mlir
+++ b/mlir/test/Conversion/SCFToEmitC/while.mlir
@@ -1,12 +1,12 @@
 // RUN: mlir-opt -allow-unregistered-dialect -convert-scf-to-emitc %s | FileCheck %s
 // RUN: mlir-opt -allow-unregistered-dialect -convert-to-emitc="filter-dialects=scf" %s | FileCheck %s
 
-emitc.func @payload_whileOp(%arg: i32) -> i32 {
-  %result = emitc.add %arg, %arg : (i32, i32) -> i32
+emitc.func @payload_while(%arg: i32) -> i32 {
+  %result = add %arg, %arg : (i32, i32) -> i32
   return %result : i32
 }
 
-func.func @whileOp() -> i32 {
+func.func @while() -> i32 {
   %init = emitc.literal "1.0" : i32
   %var  = emitc.literal "1.0" : i32
   %exit = emitc.literal "10.0" : i32
@@ -17,47 +17,50 @@ func.func @whileOp() -> i32 {
     scf.condition(%condition) %arg1 : i32
   } do {
   ^bb0(%arg2: i32):
-    %next_arg1 = emitc.call @payload_whileOp(%arg2) : (i32) -> i32
+    %next_arg1 = emitc.call @payload_while(%arg2) : (i32) -> i32
     scf.yield %next_arg1 : i32
   }
   
   return %res : i32
 }
-// CHECK-LABEL:   emitc.func @payload_whileOp(
+// CHECK-LABEL:   emitc.func @payload_while(
 // CHECK-SAME:      %[[ARG0:.*]]: i32) -> i32 {
 // CHECK:           %[[VAL_0:.*]] = add %[[ARG0]], %[[ARG0]] : (i32, i32) -> i32
 // CHECK:           return %[[VAL_0]] : i32
 // CHECK:         }
 
-// CHECK-LABEL:   func.func @whileOp() -> i32 {
+// CHECK-LABEL:   func.func @while() -> i32 {
 // CHECK:           %[[VAL_0:.*]] = emitc.literal "1.0" : i32
 // CHECK:           %[[VAL_1:.*]] = emitc.literal "1.0" : i32
 // CHECK:           %[[VAL_2:.*]] = emitc.literal "10.0" : i32
 // CHECK:           %[[VAL_3:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
 // CHECK:           emitc.assign %[[VAL_0]] : i32 to %[[VAL_3]] : <i32>
 // CHECK:           emitc.while {
-// CHECK:             %[[VAL_4:.*]] = load %[[VAL_3]] : <i32>
-// CHECK:             %[[VAL_5:.*]] = add %[[VAL_4]], %[[VAL_1]] : (i32, i32) -> i32
-// CHECK:             %[[VAL_6:.*]] = cmp lt, %[[VAL_5]], %[[VAL_2]] : (i32, i32) -> i1
-// CHECK:             yield %[[VAL_6]] : i1
+// CHECK:             %[[VAL_4:.*]] = expression : i1 {
+// CHECK:               %[[VAL_5:.*]] = load %[[VAL_3]] : <i32>
+// CHECK:               %[[VAL_6:.*]] = add %[[VAL_5]], %[[VAL_1]] : (i32, i32) -> i32
+// CHECK:               %[[VAL_7:.*]] = cmp lt, %[[VAL_6]], %[[VAL_2]] : (i32, i32) -> i1
+// CHECK:               yield %[[VAL_7]] : i1
+// CHECK:             }
+// CHECK:             yield %[[VAL_4]] : i1
 // CHECK:           } do {
-// CHECK:             %[[VAL_7:.*]] = load %[[VAL_3]] : <i32>
-// CHECK:             %[[VAL_8:.*]] = call @payload_whileOp(%[[VAL_7]]) : (i32) -> i32
-// CHECK:             assign %[[VAL_8]] : i32 to %[[VAL_3]] : <i32>
+// CHECK:             %[[VAL_8:.*]] = load %[[VAL_3]] : <i32>
+// CHECK:             %[[VAL_9:.*]] = call @payload_while(%[[VAL_8]]) : (i32) -> i32
+// CHECK:             assign %[[VAL_9]] : i32 to %[[VAL_3]] : <i32>
 // CHECK:           }
-// CHECK:           %[[VAL_9:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
-// CHECK:           %[[VAL_10:.*]] = emitc.load %[[VAL_3]] : <i32>
-// CHECK:           emitc.assign %[[VAL_10]] : i32 to %[[VAL_9]] : <i32>
-// CHECK:           %[[VAL_11:.*]] = emitc.load %[[VAL_9]] : <i32>
-// CHECK:           return %[[VAL_11]] : i32
+// CHECK:           %[[VAL_10:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
+// CHECK:           %[[VAL_11:.*]] = emitc.load %[[VAL_3]] : <i32>
+// CHECK:           emitc.assign %[[VAL_11]] : i32 to %[[VAL_10]] : <i32>
+// CHECK:           %[[VAL_12:.*]] = emitc.load %[[VAL_10]] : <i32>
+// CHECK:           return %[[VAL_12]] : i32
 // CHECK:         }
 
-emitc.func @payload_doOp(%arg: i32) -> i32 {
-  %result = emitc.add %arg, %arg : (i32, i32) -> i32
+emitc.func @payload_do(%arg: i32) -> i32 {
+  %result = add %arg, %arg : (i32, i32) -> i32
   return %result : i32
 }
 
-func.func @doOp() -> i32 {
+func.func @do() -> i32 {
   %init = emitc.literal "1.0" : i32
   %var  = emitc.literal "1.0" : i32
   %exit = emitc.literal "10.0" : i32
@@ -69,19 +72,19 @@ func.func @doOp() -> i32 {
     scf.condition(%condition) %next : i32
   } do {
   ^bb0(%arg2: i32):
-    %next_arg1 = emitc.call @payload_doOp(%arg2) : (i32) -> i32
+    %next_arg1 = emitc.call @payload_do(%arg2) : (i32) -> i32
     scf.yield %next_arg1 : i32
   }
   
   return %res : i32
 }
-// CHECK-LABEL:   emitc.func @payload_doOp(
+// CHECK-LABEL:   emitc.func @payload_do(
 // CHECK-SAME:      %[[ARG0:.*]]: i32) -> i32 {
 // CHECK:           %[[VAL_0:.*]] = add %[[ARG0]], %[[ARG0]] : (i32, i32) -> i32
 // CHECK:           return %[[VAL_0]] : i32
 // CHECK:         }
 
-// CHECK-LABEL:   func.func @doOp() -> i32 {
+// CHECK-LABEL:   func.func @do() -> i32 {
 // CHECK:           %[[VAL_0:.*]] = emitc.literal "1.0" : i32
 // CHECK:           %[[VAL_1:.*]] = emitc.literal "1.0" : i32
 // CHECK:           %[[VAL_2:.*]] = emitc.literal "10.0" : i32
@@ -95,26 +98,29 @@ func.func @doOp() -> i32 {
 // CHECK:             %[[VAL_8:.*]] = add %[[VAL_5]], %[[VAL_5]] : (i32, i32) -> i32
 // CHECK:             assign %[[VAL_7]] : i1 to %[[VAL_4]] : <i1>
 // CHECK:             if %[[VAL_7]] {
-// CHECK:               %[[VAL_9:.*]] = call @payload_doOp(%[[VAL_8]]) : (i32) -> i32
+// CHECK:               %[[VAL_9:.*]] = call @payload_do(%[[VAL_8]]) : (i32) -> i32
 // CHECK:               assign %[[VAL_9]] : i32 to %[[VAL_3]] : <i32>
 // CHECK:             }
 // CHECK:           } while {
-// CHECK:             %[[VAL_10:.*]] = load %[[VAL_4]] : <i1>
+// CHECK:             %[[VAL_10:.*]] = expression : i1 {
+// CHECK:               %[[VAL_11:.*]] = load %[[VAL_4]] : <i1>
+// CHECK:               yield %[[VAL_11]] : i1
+// CHECK:             }
 // CHECK:             yield %[[VAL_10]] : i1
 // CHECK:           }
-// CHECK:           %[[VAL_11:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
-// CHECK:           %[[VAL_12:.*]] = emitc.load %[[VAL_3]] : <i32>
-// CHECK:           emitc.assign %[[VAL_12]] : i32 to %[[VAL_11]] : <i32>
-// CHECK:           %[[VAL_13:.*]] = emitc.load %[[VAL_11]] : <i32>
-// CHECK:           return %[[VAL_13]] : i32
+// CHECK:           %[[VAL_12:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
+// CHECK:           %[[VAL_13:.*]] = emitc.load %[[VAL_3]] : <i32>
+// CHECK:           emitc.assign %[[VAL_13]] : i32 to %[[VAL_12]] : <i32>
+// CHECK:           %[[VAL_14:.*]] = emitc.load %[[VAL_12]] : <i32>
+// CHECK:           return %[[VAL_14]] : i32
 // CHECK:         }
 
-emitc.func @payload_whileOp_two_results(%arg: i32) -> i32 {
-  %result = emitc.add %arg, %arg : (i32, i32) -> i32
+emitc.func @payload_while_two_results(%arg: i32) -> i32 {
+  %result = add %arg, %arg : (i32, i32) -> i32
   return %result : i32
 }
 
-func.func @whileOp_two_results() -> i32 {
+func.func @while_two_results() -> i32 {
   %init = emitc.literal "1.0" : i32
   %exit = emitc.literal "10.0" : i32
 
@@ -124,21 +130,21 @@ func.func @whileOp_two_results() -> i32 {
     scf.condition(%condition) %arg1_1, %arg1_2  : i32, i32
   } do {
   ^bb0(%arg2_1 : i32, %arg2_2 : i32):
-    %next1 = emitc.call @payload_whileOp_two_results(%arg2_1) : (i32) -> i32
-    %next2 = emitc.call @payload_whileOp_two_results(%arg2_2) : (i32) -> i32
+    %next1 = emitc.call @payload_while_two_results(%arg2_1) : (i32) -> i32
+    %next2 = emitc.call @payload_while_two_results(%arg2_2) : (i32) -> i32
     scf.yield %next1, %next2 : i32, i32
   }
   
   return %res1 : i32
 }
 
-// CHECK-LABEL:   emitc.func @payload_whileOp_two_results(
+// CHECK-LABEL:   emitc.func @payload_while_two_results(
 // CHECK-SAME:      %[[ARG0:.*]]: i32) -> i32 {
 // CHECK:           %[[VAL_0:.*]] = add %[[ARG0]], %[[ARG0]] : (i32, i32) -> i32
 // CHECK:           return %[[VAL_0]] : i32
 // CHECK:         }
 
-// CHECK-LABEL:   func.func @whileOp_two_results() -> i32 {
+// CHECK-LABEL:   func.func @while_two_results() -> i32 {
 // CHECK:           %[[VAL_0:.*]] = emitc.literal "1.0" : i32
 // CHECK:           %[[VAL_1:.*]] = emitc.literal "10.0" : i32
 // CHECK:           %[[VAL_2:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
@@ -146,36 +152,39 @@ func.func @whileOp_two_results() -> i32 {
 // CHECK:           %[[VAL_3:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
 // CHECK:           emitc.assign %[[VAL_0]] : i32 to %[[VAL_3]] : <i32>
 // CHECK:           emitc.while {
-// CHECK:             %[[VAL_4:.*]] = load %[[VAL_2]] : <i32>
-// CHECK:             %[[VAL_5:.*]] = load %[[VAL_3]] : <i32>
-// CHECK:             %[[VAL_6:.*]] = add %[[VAL_4]], %[[VAL_5]] : (i32, i32) -> i32
-// CHECK:             %[[VAL_7:.*]] = cmp lt, %[[VAL_6]], %[[VAL_1]] : (i32, i32) -> i1
-// CHECK:             yield %[[VAL_7]] : i1
+// CHECK:             %[[VAL_4:.*]] = expression : i1 {
+// CHECK:               %[[VAL_5:.*]] = load %[[VAL_2]] : <i32>
+// CHECK:               %[[VAL_6:.*]] = load %[[VAL_3]] : <i32>
+// CHECK:               %[[VAL_7:.*]] = add %[[VAL_5]], %[[VAL_6]] : (i32, i32) -> i32
+// CHECK:               %[[VAL_8:.*]] = cmp lt, %[[VAL_7]], %[[VAL_1]] : (i32, i32) -> i1
+// CHECK:               yield %[[VAL_8]] : i1
+// CHECK:             }
+// CHECK:             yield %[[VAL_4]] : i1
 // CHECK:           } do {
-// CHECK:             %[[VAL_8:.*]] = load %[[VAL_2]] : <i32>
-// CHECK:             %[[VAL_9:.*]] = load %[[VAL_3]] : <i32>
-// CHECK:             %[[VAL_10:.*]] = call @payload_whileOp_two_results(%[[VAL_8]]) : (i32) -> i32
-// CHECK:             %[[VAL_11:.*]] = call @payload_whileOp_two_results(%[[VAL_9]]) : (i32) -> i32
-// CHECK:             assign %[[VAL_10]] : i32 to %[[VAL_2]] : <i32>
-// CHECK:             assign %[[VAL_11]] : i32 to %[[VAL_3]] : <i32>
+// CHECK:             %[[VAL_9:.*]] = load %[[VAL_2]] : <i32>
+// CHECK:             %[[VAL_10:.*]] = load %[[VAL_3]] : <i32>
+// CHECK:             %[[VAL_11:.*]] = call @payload_while_two_results(%[[VAL_9]]) : (i32) -> i32
+// CHECK:             %[[VAL_12:.*]] = call @payload_while_two_results(%[[VAL_10]]) : (i32) -> i32
+// CHECK:             assign %[[VAL_11]] : i32 to %[[VAL_2]] : <i32>
+// CHECK:             assign %[[VAL_12]] : i32 to %[[VAL_3]] : <i32>
 // CHECK:           }
-// CHECK:           %[[VAL_12:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
 // CHECK:           %[[VAL_13:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
-// CHECK:           %[[VAL_14:.*]] = emitc.load %[[VAL_2]] : <i32>
-// CHECK:           emitc.assign %[[VAL_14]] : i32 to %[[VAL_12]] : <i32>
-// CHECK:           %[[VAL_15:.*]] = emitc.load %[[VAL_3]] : <i32>
+// CHECK:           %[[VAL_14:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
+// CHECK:           %[[VAL_15:.*]] = emitc.load %[[VAL_2]] : <i32>
 // CHECK:           emitc.assign %[[VAL_15]] : i32 to %[[VAL_13]] : <i32>
-// CHECK:           %[[VAL_16:.*]] = emitc.load %[[VAL_12]] : <i32>
+// CHECK:           %[[VAL_16:.*]] = emitc.load %[[VAL_3]] : <i32>
+// CHECK:           emitc.assign %[[VAL_16]] : i32 to %[[VAL_14]] : <i32>
 // CHECK:           %[[VAL_17:.*]] = emitc.load %[[VAL_13]] : <i32>
-// CHECK:           return %[[VAL_16]] : i32
+// CHECK:           %[[VAL_18:.*]] = emitc.load %[[VAL_14]] : <i32>
+// CHECK:           return %[[VAL_17]] : i32
 // CHECK:         }
 
-emitc.func @payload_doOp_two_results(%arg: i32) -> i32 {
-  %result = emitc.add %arg, %arg : (i32, i32) -> i32
+emitc.func @payload_do_two_results(%arg: i32) -> i32 {
+  %result = add %arg, %arg : (i32, i32) -> i32
   return %result : i32
 }
 
-func.func @doOp_two_results() -> i32 {
+func.func @do_two_results() -> i32 {
   %init = emitc.literal "1.0" : i32
   %exit = emitc.literal "10.0" : i32
 
@@ -185,21 +194,21 @@ func.func @doOp_two_results() -> i32 {
     scf.condition(%condition) %init, %arg1_2  : i32, i32
   } do {
   ^bb0(%arg2_1 : i32, %arg2_2 : i32):
-    %next1 = emitc.call @payload_doOp_two_results(%arg2_1) : (i32) -> i32
-    %next2 = emitc.call @payload_doOp_two_results(%arg2_2) : (i32) -> i32
+    %next1 = emitc.call @payload_do_two_results(%arg2_1) : (i32) -> i32
+    %next2 = emitc.call @payload_do_two_results(%arg2_2) : (i32) -> i32
     scf.yield %next1, %next2 : i32, i32
   }
   
   return %res1 : i32
 }
 
-// CHECK-LABEL:   emitc.func @payload_doOp_two_results(
+// CHECK-LABEL:   emitc.func @payload_do_two_results(
 // CHECK-SAME:      %[[ARG0:.*]]: i32) -> i32 {
 // CHECK:           %[[VAL_0:.*]] = add %[[ARG0]], %[[ARG0]] : (i32, i32) -> i32
 // CHECK:           return %[[VAL_0]] : i32
 // CHECK:         }
 
-// CHECK-LABEL:   func.func @doOp_two_results() -> i32 {
+// CHECK-LABEL:   func.func @do_two_results() -> i32 {
 // CHECK:           %[[VAL_0:.*]] = emitc.literal "1.0" : i32
 // CHECK:           %[[VAL_1:.*]] = emitc.literal "10.0" : i32
 // CHECK:           %[[VAL_2:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
@@ -214,22 +223,25 @@ func.func @doOp_two_results() -> i32 {
 // CHECK:             %[[VAL_8:.*]] = cmp lt, %[[VAL_7]], %[[VAL_1]] : (i32, i32) -> i1
 // CHECK:             assign %[[VAL_8]] : i1 to %[[VAL_4]] : <i1>
 // CHECK:             if %[[VAL_8]] {
-// CHECK:               %[[VAL_9:.*]] = call @payload_doOp_two_results(%[[VAL_0]]) : (i32) -> i32
-// CHECK:               %[[VAL_10:.*]] = call @payload_doOp_two_results(%[[VAL_6]]) : (i32) -> i32
+// CHECK:               %[[VAL_9:.*]] = call @payload_do_two_results(%[[VAL_0]]) : (i32) -> i32
+// CHECK:               %[[VAL_10:.*]] = call @payload_do_two_results(%[[VAL_6]]) : (i32) -> i32
 // CHECK:               assign %[[VAL_9]] : i32 to %[[VAL_2]] : <i32>
 // CHECK:               assign %[[VAL_10]] : i32 to %[[VAL_3]] : <i32>
 // CHECK:             }
 // CHECK:           } while {
-// CHECK:             %[[VAL_11:.*]] = load %[[VAL_4]] : <i1>
+// CHECK:             %[[VAL_11:.*]] = expression : i1 {
+// CHECK:               %[[VAL_12:.*]] = load %[[VAL_4]] : <i1>
+// CHECK:               yield %[[VAL_12]] : i1
+// CHECK:             }
 // CHECK:             yield %[[VAL_11]] : i1
 // CHECK:           }
-// CHECK:           %[[VAL_12:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
 // CHECK:           %[[VAL_13:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
-// CHECK:           %[[VAL_14:.*]] = emitc.load %[[VAL_2]] : <i32>
-// CHECK:           emitc.assign %[[VAL_14]] : i32 to %[[VAL_12]] : <i32>
-// CHECK:           %[[VAL_15:.*]] = emitc.load %[[VAL_3]] : <i32>
+// CHECK:           %[[VAL_14:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
+// CHECK:           %[[VAL_15:.*]] = emitc.load %[[VAL_2]] : <i32>
 // CHECK:           emitc.assign %[[VAL_15]] : i32 to %[[VAL_13]] : <i32>
-// CHECK:           %[[VAL_16:.*]] = emitc.load %[[VAL_12]] : <i32>
+// CHECK:           %[[VAL_16:.*]] = emitc.load %[[VAL_3]] : <i32>
+// CHECK:           emitc.assign %[[VAL_16]] : i32 to %[[VAL_14]] : <i32>
 // CHECK:           %[[VAL_17:.*]] = emitc.load %[[VAL_13]] : <i32>
-// CHECK:           return %[[VAL_16]] : i32
+// CHECK:           %[[VAL_18:.*]] = emitc.load %[[VAL_14]] : <i32>
+// CHECK:           return %[[VAL_17]] : i32
 // CHECK:         }
diff --git a/mlir/test/Dialect/EmitC/invalid_ops.mlir b/mlir/test/Dialect/EmitC/invalid_ops.mlir
index 3d29844b21c80..68d2a7f0b3545 100644
--- a/mlir/test/Dialect/EmitC/invalid_ops.mlir
+++ b/mlir/test/Dialect/EmitC/invalid_ops.mlir
@@ -723,8 +723,55 @@ emitc.class @testClass {
 // -----
 
 func.func @test_while(%arg0 : !emitc.ptr<i32>) {
-  // expected-error @+1 {{'emitc.while' op condition region cannot be empty}}
+  %1 = emitc.literal "1" : i32
+  %2 = emitc.literal "2" : i32
+
+  // expected-error @+1 {{'emitc.while' op condition region must contain exactly two operations: 'emitc.expression' followed by 'emitc.yield', but found 3 operations}}
+  emitc.while {
+    %r = emitc.expression : i1 {
+      %cmp = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
+      emitc.yield %cmp : i1
+    }
+
+    %3 = emitc.literal "3" : i32
+
+    emitc.yield %r : i1
+  } do {
+    emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
+  }
+
+  return
+}
+
+// -----
+
+func.func @test_while(%arg0 : !emitc.ptr<i32>) {
+  // expected-error @+1 {{'emitc.while' op expected first op in condition region to be 'emitc.expression', but got emitc.literal}}
+  emitc.while {
+    %true = emitc.literal "true" : i1
+
+    emitc.yield %true : i1
+  } do {
+    emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
+  }
+
+  return
+}
+
+// -----
+
+func.func @test_while(%arg0 : !emitc.ptr<i32>) {
+  %1 = emitc.literal "1" : i32
+  %2 = emitc.literal "2" : i32
+
+  // expected-error @+1 {{'emitc.while' op emitc.expression in condition region must return 'i1', but returns 'i32'}}
   emitc.while {
+    %r = emitc.expression : i32 {
+      %add = emitc.add %1, %2 : (i32, i32) -> i32
+      emitc.yield %add : i32
+    }
+
+    emitc.yield %r : i32
   } do {
     emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
   }
@@ -734,14 +781,21 @@ func.func @test_while(%arg0 : !emitc.ptr<i32>) {
 
 // -----
 
-emitc.func @test_while(%arg0 : !emitc.ptr<i32>) {
+func.func @test_while(%arg0 : !emitc.ptr<i32>) {
   %1 = emitc.literal "1" : i32
+  %2 = emitc.literal "2" : i32
 
-  // expected-error @+1 {{'emitc.while' op expected all operations in condition region must implement CExpression trait, but emitc.literal does not}}
+  // expected-error @+1 {{'emitc.while' op expected last op in condition region to be 'emitc.yield', but got emitc.expression}}
   emitc.while {
-    %2 = emitc.literal "2" : i32
-    %3 = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
-    emitc.yield %3 : i1
+    %r1 = emitc.expression : i1 {
+      %cmp = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
+      emitc.yield %cmp : i1
+    }
+
+    %r2 = emitc.expression : i32 {
+      %add = emitc.add %1, %2 : (i32, i32) -> i32
+      emitc.yield %add : i32
+    }
   } do {
     emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
   }
@@ -755,9 +809,14 @@ func.func @test_while(%arg0 : !emitc.ptr<i32>) {
   %1 = emitc.literal "1" : i32
   %2 = emitc.literal "2" : i32
 
-  // expected-error @+1 {{'emitc.while' op expected condition region to end with emitc.yield, but got emitc.cmp}}
+  // expected-error @+1 {{'emitc.while' op expected condition region to return 1 value, but it returns 0 values}}
   emitc.while {
-    %3 = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
+    %r = emitc.expression : i1 {
+      %cmp = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
+      emitc.yield %cmp : i1
+    }
+
+    emitc.yield
   } do {
     emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
   }
@@ -771,10 +830,16 @@ func.func @test_while(%arg0 : !emitc.ptr<i32>) {
   %1 = emitc.literal "1" : i32
   %2 = emitc.literal "2" : i32
 
-  // expected-error @+1 {{'emitc.while' op condition region must yield a single i1 value}}
+  %true = emitc.literal "true" : i1
+
+  // expected-error @+1 {{'emitc.yield' must return result of 'emitc.expression' from this condition region}}
   emitc.while {
-    %3 = emitc.add %1, %2 : (i32, i32) -> i32
-    emitc.yield %3 : i32
+    %r = emitc.expression : i1 {
+      %cmp = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
+      emitc.yield %cmp : i1
+    }
+
+    emitc.yield %true: i1
   } do {
     emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
   }
@@ -790,9 +855,14 @@ func.func @test_while() {
 
   // expected-error @+1 {{'emitc.while' op body region cannot be empty}}
   emitc.while {
-    %3 = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
-    emitc.yield %3 : i1
+    %r = emitc.expression : i1 {
+      %cmp = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
+      emitc.yield %cmp : i1
+    }
+
+    emitc.yield %r: i1
   } do {
+    ^bb0:
   }
 
   return
@@ -800,18 +870,43 @@ func.func @test_while() {
 
 // -----
 
-emitc.func @test_while(%arg0 : !emitc.ptr<i32>) {
+func.func @test_while(%arg0 : !emitc.ptr<i32>) {
   %1 = emitc.literal "1" : i32
   %2 = emitc.literal "2" : i32
 
-  // expected-error @+1 {{'emitc.while' op expected body region to return 0 values, but body returns 1}}
+  // expected-error @+1 {{'emitc.while' op body region must not contain terminator}}
   emitc.while {
-    %3 = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
-    emitc.yield %3 : i1
+    %r = emitc.expression : i1 {
+      %cmp = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
+      emitc.yield %cmp : i1
+    }
+
+    emitc.yield %r: i1
   } do {
     emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
-    %4 = emitc.add %1, %2 : (i32, i32) -> i32
-    emitc.yield %4 : i32
+    emitc.yield
+  }
+
+  return
+}
+
+// -----
+
+func.func @test_do(%arg0 : !emitc.ptr<i32>) {
+  %1 = emitc.literal "1" : i32
+  %2 = emitc.literal "2" : i32
+
+  // expected-error @+1 {{'emitc.do' op condition region must contain exactly two operations: 'emitc.expression' followed by 'emitc.yield', but found 3 operations}}
+  emitc.do {
+    emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
+  } while {
+    %r = emitc.expression : i1 {
+      %cmp = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
+      emitc.yield %cmp : i1
+    }
+
+    %3 = emitc.literal "3" : i32
+    emitc.yield %r : i1
   }
 
   return
@@ -819,11 +914,13 @@ emitc.func @test_while(%arg0 : !emitc.ptr<i32>) {
 
 // -----
 
-func.func @test_do_while(%arg0 : !emitc.ptr<i32>) {
-  // expected-error @+1 {{'emitc.do' op condition region cannot be empty}}
+func.func @test_do(%arg0 : !emitc.ptr<i32>) {
+  // expected-error @+1 {{'emitc.do' op expected first op in condition region to be 'emitc.expression', but got emitc.literal}}
   emitc.do {
     emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
   } while {
+    %true = emitc.literal "true" : i1
+    emitc.yield %true : i1
   }
 
   return
@@ -831,16 +928,20 @@ func.func @test_do_while(%arg0 : !emitc.ptr<i32>) {
 
 // -----
 
-emitc.func @test_do_while(%arg0 : !emitc.ptr<i32>) {
+func.func @test_do(%arg0 : !emitc.ptr<i32>) {
   %1 = emitc.literal "1" : i32
+  %2 = emitc.literal "2" : i32
 
-  // expected-error @+1 {{'emitc.do' op expected all operations in condition region must implement CExpression trait, but emitc.literal does not}}
+  // expected-error @+1 {{'emitc.do' op emitc.expression in condition region must return 'i1', but returns 'i32'}}
   emitc.do {
     emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
   } while {
-    %2 = emitc.literal "2" : i32
-    %3 = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
-    emitc.yield %3 : i1
+    %r = emitc.expression : i32 {
+      %add = emitc.add %1, %2 : (i32, i32) -> i32
+      emitc.yield %add : i32
+    }
+
+    emitc.yield %r : i32
   }
 
   return
@@ -848,15 +949,23 @@ emitc.func @test_do_while(%arg0 : !emitc.ptr<i32>) {
 
 // -----
 
-func.func @test_do_while(%arg0 : !emitc.ptr<i32>) {
+func.func @test_do(%arg0 : !emitc.ptr<i32>) {
   %1 = emitc.literal "1" : i32
   %2 = emitc.literal "2" : i32
 
-  // expected-error @+1 {{'emitc.do' op expected condition region to end with emitc.yield, but got emitc.cmp}}
+  // expected-error @+1 {{'emitc.do' op expected last op in condition region to be 'emitc.yield', but got emitc.expression}}
   emitc.do {
     emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
   } while {
-    %3 = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
+    %r1 = emitc.expression : i1 {
+      %cmp = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
+      emitc.yield %cmp : i1
+    }
+
+    %r2 = emitc.expression : i32 {
+      %add = emitc.add %1, %2 : (i32, i32) -> i32
+      emitc.yield %add : i32
+    }
   }
 
   return
@@ -864,16 +973,20 @@ func.func @test_do_while(%arg0 : !emitc.ptr<i32>) {
 
 // -----
 
-func.func @test_do_while(%arg0 : !emitc.ptr<i32>) {
+func.func @test_do(%arg0 : !emitc.ptr<i32>) {
   %1 = emitc.literal "1" : i32
   %2 = emitc.literal "2" : i32
 
-  // expected-error @+1 {{'emitc.do' op condition region must yield a single i1 value}}
+  // expected-error @+1 {{'emitc.do' op expected condition region to return 1 value, but it returns 0 values}}
   emitc.do {
     emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
   } while {
-    %3 = emitc.add %1, %2 : (i32, i32) -> i32
-    emitc.yield %3 : i32
+    %r = emitc.expression : i1 {
+      %cmp = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
+      emitc.yield %cmp : i1
+    }
+
+    emitc.yield
   }
 
   return
@@ -881,15 +994,44 @@ func.func @test_do_while(%arg0 : !emitc.ptr<i32>) {
 
 // -----
 
-func.func @test_do_while() {
+func.func @test_do(%arg0 : !emitc.ptr<i32>) {
+  %1 = emitc.literal "1" : i32
+  %2 = emitc.literal "2" : i32
+
+  %true = emitc.literal "true" : i1
+
+  // expected-error @+1 {{'emitc.yield' must return result of 'emitc.expression' from this condition region}}
+  emitc.do {
+    emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
+  } while {
+    %r = emitc.expression : i1 {
+      %cmp = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
+      emitc.yield %cmp : i1
+    }
+
+    emitc.yield %true: i1
+  }
+
+  return
+}
+
+// -----
+
+
+func.func @test_do() {
   %1 = emitc.literal "1" : i32
   %2 = emitc.literal "2" : i32
 
   // expected-error @+1 {{'emitc.do' op body region cannot be empty}}
   emitc.do {
+    ^bb0:
   } while {
-    %3 = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
-    emitc.yield %3 : i1
+    %r = emitc.expression : i1 {
+      %cmp = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
+      emitc.yield %cmp : i1
+    }
+
+    emitc.yield %r: i1
   }
 
   return
@@ -897,18 +1039,21 @@ func.func @test_do_while() {
 
 // -----
 
-emitc.func @test_do_while(%arg0 : !emitc.ptr<i32>) {
+func.func @test_do(%arg0 : !emitc.ptr<i32>) {
   %1 = emitc.literal "1" : i32
   %2 = emitc.literal "2" : i32
 
-  // expected-error @+1 {{'emitc.do' op expected body region to return 0 values, but body returns 1}}
+  // expected-error @+1 {{'emitc.do' op body region must not contain terminator}}
   emitc.do {
     emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
-    %4 = emitc.add %1, %2 : (i32, i32) -> i32
-    emitc.yield %4 : i32
+    emitc.yield
   } while {
-    %3 = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
-    emitc.yield %3 : i1
+    %r = emitc.expression : i1 {
+      %cmp = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
+      emitc.yield %cmp : i1
+    }
+
+    emitc.yield %r: i1
   }
 
   return
diff --git a/mlir/test/Dialect/EmitC/ops.mlir b/mlir/test/Dialect/EmitC/ops.mlir
index a8b945d1bfb5d..17be4875127ff 100644
--- a/mlir/test/Dialect/EmitC/ops.mlir
+++ b/mlir/test/Dialect/EmitC/ops.mlir
@@ -332,9 +332,13 @@ func.func @while(%arg0 : !emitc.ptr<i32>) {
   %3 = emitc.literal "3" : i32
 
   emitc.while {
-    %add = emitc.add %1, %2 : (i32, i32) -> i32
-    %5 = emitc.cmp eq, %add, %3 : (i32, i32) -> i1
-    emitc.yield %5 : i1
+    %r = emitc.expression : i1 {
+      %add = emitc.add %1, %2 : (i32, i32) -> i32
+      %cmp = emitc.cmp eq, %add, %3 : (i32, i32) -> i1
+      emitc.yield %cmp : i1
+    }
+
+    emitc.yield %r : i1
   } do {
     emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
   }
@@ -350,9 +354,13 @@ func.func @do(%arg0 : !emitc.ptr<i32>) {
   emitc.do {
     emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
   } while {
-    %add = emitc.add %1, %2 : (i32, i32) -> i32
-    %5 = emitc.cmp eq, %add, %3 : (i32, i32) -> i1
-    emitc.yield %5 : i1
+    %r = emitc.expression : i1 {
+      %add = emitc.add %1, %2 : (i32, i32) -> i32
+      %cmp = emitc.cmp eq, %add, %3 : (i32, i32) -> i1
+      emitc.yield %cmp : i1
+    }
+    
+    emitc.yield %r : i1
   }
 
   return
diff --git a/mlir/test/Target/Cpp/do.mlir b/mlir/test/Target/Cpp/do.mlir
index 034fe77ed22c4..9ffbb38f25239 100644
--- a/mlir/test/Target/Cpp/do.mlir
+++ b/mlir/test/Target/Cpp/do.mlir
@@ -14,18 +14,22 @@
 
 emitc.func @emitc_do(%arg0 : !emitc.ptr<i32>) {
   %var = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue<i32>
-  %0 = emitc.literal "10" : i32
-  %1 = emitc.literal "1" : i32
+  %0 = literal "10" : i32
+  %1 = literal "1" : i32
 
-  emitc.do {
-    emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
+  do {
+    verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
     %var_load = load %var : <i32>
     %tmp_add = add %var_load, %1 : (i32, i32) -> i32
     "emitc.assign"(%var, %tmp_add) : (!emitc.lvalue<i32>, i32) -> ()
   } while {
-    %var_load = load %var : <i32>
-    %res = emitc.cmp le, %var_load, %0 : (i32, i32) -> i1
-    emitc.yield %res : i1
+    %r = expression : i1 {
+      %var_load = load %var : <i32>
+      %cmp = cmp le, %var_load, %0 : (i32, i32) -> i1
+      yield %cmp : i1
+    }
+    
+    yield %r : i1
   }
 
   return
@@ -46,23 +50,86 @@ emitc.func @emitc_do(%arg0 : !emitc.ptr<i32>) {
 
 emitc.func @emitc_do_with_expression(%arg0 : !emitc.ptr<i32>) {
   %var = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue<i32>
-  %0 = emitc.literal "10" : i32
-  %1 = emitc.literal "1" : i32
+  %0 = literal "10" : i32
+  %1 = literal "1" : i32
 
-  %add = emitc.expression : i32 {
+  %add = expression : i32 {
     %add = add %0, %1 : (i32, i32) -> i32
     yield %add : i32
   }
 
-  emitc.do {
-    emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
+  do {
+    verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
     %var_load = load %var : <i32>
     %tmp_add = add %var_load, %1 : (i32, i32) -> i32
     "emitc.assign"(%var, %tmp_add) : (!emitc.lvalue<i32>, i32) -> ()
   } while {
-    %var_load = load %var : <i32>
-    %res = emitc.cmp le, %var_load, %add : (i32, i32) -> i1
-    emitc.yield %res : i1
+    %r = expression : i1 {
+      %var_load = load %var : <i32>
+      %cmp = cmp le, %var_load, %add : (i32, i32) -> i1
+      yield %cmp : i1
+    }
+
+    yield %r : i1
+  }
+
+  return
+}
+
+
+// CPP-DEFAULT-LABEL: void emitc_double_do() {
+// CPP-DEFAULT:         int32_t v1 = 0;
+// CPP-DEFAULT:         int32_t v2 = 0;
+// CPP-DEFAULT:         do {
+// CPP-DEFAULT:           int32_t v3 = v1;
+// CPP-DEFAULT:           do {
+// CPP-DEFAULT:             int32_t v4 = v2;
+// CPP-DEFAULT:             printf("i = %d, j = %d", v3, v4);
+// CPP-DEFAULT:             int32_t v5 = v4 + 1;
+// CPP-DEFAULT:             v2 = v5;
+// CPP-DEFAULT:           } while (v2 <= 5);
+// CPP-DEFAULT:           int32_t v6 = v3 + 1;
+// CPP-DEFAULT:           v1 = v6;
+// CPP-DEFAULT:         } while (v1 <= 3);
+// CPP-DEFAULT:         return;
+// CPP-DEFAULT:       }
+
+emitc.func @emitc_double_do() {
+  %var_1 = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue<i32>
+  %var_2 = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue<i32>
+  
+  %step = literal "1" : i32
+  %end_1 = literal "3" : i32
+  %end_2 = literal "5" : i32
+
+  do {
+    %var_1_load = load %var_1 : <i32>
+    
+    do {
+      %var_2_load = load %var_2 : <i32>
+      verbatim "printf(\"i = %d, j = %d\", {}, {});" args %var_1_load, %var_2_load : i32, i32
+      %tmp_add = add %var_2_load, %step : (i32, i32) -> i32
+      "emitc.assign"(%var_2, %tmp_add) : (!emitc.lvalue<i32>, i32) -> ()
+    } while {
+      %r = expression : i1 {
+        %var_2_load = load %var_2 : <i32>
+        %cmp = cmp le, %var_2_load, %end_2 : (i32, i32) -> i1
+        yield %cmp : i1
+      }
+      
+      yield %r : i1
+    }
+
+    %tmp_add = add %var_1_load, %step : (i32, i32) -> i32
+    "emitc.assign"(%var_1, %tmp_add) : (!emitc.lvalue<i32>, i32) -> ()
+  } while {
+    %r = expression : i1 {
+      %var_1_load = load %var_1 : <i32>
+      %cmp = cmp le, %var_1_load, %end_1 : (i32, i32) -> i1
+      yield %cmp : i1
+    }
+    
+    yield %r : i1
   }
 
   return
diff --git a/mlir/test/Target/Cpp/while.mlir b/mlir/test/Target/Cpp/while.mlir
index 76cf7c2f752cf..8770550f3ad4d 100644
--- a/mlir/test/Target/Cpp/while.mlir
+++ b/mlir/test/Target/Cpp/while.mlir
@@ -14,15 +14,19 @@
 
 emitc.func @emitc_while(%arg0 : !emitc.ptr<i32>) {
   %var = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue<i32>
-  %0 = emitc.literal "10" : i32
-  %1 = emitc.literal "1" : i32
+  %0 = literal "10" : i32
+  %1 = literal "1" : i32
 
-  emitc.while {
-    %var_load = load %var : <i32>
-    %res = emitc.cmp le, %var_load, %0 : (i32, i32) -> i1
-    emitc.yield %res : i1
+  while {
+    %r = expression : i1 {
+      %var_load = load %var : <i32>
+      %cmp = cmp le, %var_load, %0 : (i32, i32) -> i1
+      yield %cmp : i1
+    }
+    
+    yield %r : i1
   } do {
-    emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
+    verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
     %var_load = load %var : <i32>
     %tmp_add = add %var_load, %1 : (i32, i32) -> i32
     "emitc.assign"(%var, %tmp_add) : (!emitc.lvalue<i32>, i32) -> ()
@@ -46,20 +50,24 @@ emitc.func @emitc_while(%arg0 : !emitc.ptr<i32>) {
 
 emitc.func @emitc_while_with_expression(%arg0 : !emitc.ptr<i32>) {
   %var = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue<i32>
-  %0 = emitc.literal "10" : i32
-  %1 = emitc.literal "1" : i32
+  %0 = literal "10" : i32
+  %1 = literal "1" : i32
 
-  %add = emitc.expression : i32 {
+  %add = expression : i32 {
     %add = add %0, %1 : (i32, i32) -> i32
     yield %add : i32
   }
 
-  emitc.while {
-    %var_load = load %var : <i32>
-    %res = emitc.cmp le, %var_load, %add : (i32, i32) -> i1
-    emitc.yield %res : i1
+  while {
+    %r = expression : i1 {
+      %var_load = load %var : <i32>
+      %cmp = cmp le, %var_load, %add : (i32, i32) -> i1
+      yield %cmp : i1
+    }
+
+    yield %r : i1
   } do {
-    emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
+    verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
     %var_load = load %var : <i32>
     %tmp_add = add %var_load, %1 : (i32, i32) -> i32
     "emitc.assign"(%var, %tmp_add) : (!emitc.lvalue<i32>, i32) -> ()
@@ -67,3 +75,62 @@ emitc.func @emitc_while_with_expression(%arg0 : !emitc.ptr<i32>) {
 
   return
 }
+
+
+// CPP-DEFAULT-LABEL: void emitc_double_while() {
+// CPP-DEFAULT:         int32_t v1 = 0;
+// CPP-DEFAULT:         int32_t v2 = 0;
+// CPP-DEFAULT:         while (v1 <= 3) {
+// CPP-DEFAULT:           int32_t v3 = v1;
+// CPP-DEFAULT:           while (v2 <= 5) {
+// CPP-DEFAULT:             int32_t v4 = v2;
+// CPP-DEFAULT:             printf("i = %d, j = %d", v3, v4);
+// CPP-DEFAULT:             int32_t v5 = v4 + 1;
+// CPP-DEFAULT:             v2 = v5;
+// CPP-DEFAULT:           }
+// CPP-DEFAULT:           int32_t v6 = v3 + 1;
+// CPP-DEFAULT:           v1 = v6;
+// CPP-DEFAULT:         }
+// CPP-DEFAULT:         return;
+// CPP-DEFAULT:       }
+
+emitc.func @emitc_double_while() {
+  %var_1 = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue<i32>
+  %var_2 = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue<i32>
+
+  %step = literal "1" : i32
+  %end_1 = literal "3" : i32
+  %end_2 = literal "5" : i32
+
+  while {
+    %r = expression : i1 {
+      %var_1_load = load %var_1 : <i32>
+      %cmp = cmp le, %var_1_load, %end_1 : (i32, i32) -> i1
+      yield %cmp : i1
+    }
+    
+    yield %r : i1
+  } do {
+    %var_1_load = load %var_1 : <i32>
+    
+    while {
+      %r = expression : i1 {
+        %var_2_load = load %var_2 : <i32>
+        %cmp = cmp le, %var_2_load, %end_2 : (i32, i32) -> i1
+        yield %cmp : i1
+      }
+      
+      yield %r : i1
+    } do {
+      %var_2_load = load %var_2 : <i32>
+      verbatim "printf(\"i = %d, j = %d\", {}, {});" args %var_1_load, %var_2_load : i32, i32
+      %tmp_add = add %var_2_load, %step : (i32, i32) -> i32
+      "emitc.assign"(%var_2, %tmp_add) : (!emitc.lvalue<i32>, i32) -> ()
+    }
+
+    %tmp_add = add %var_1_load, %step : (i32, i32) -> i32
+    "emitc.assign"(%var_1, %tmp_add) : (!emitc.lvalue<i32>, i32) -> ()
+  }
+
+  return
+}

>From 4fbac82c28f02fcac95db2496788ff5ea610af72 Mon Sep 17 00:00:00 2001
From: Vlad Lazar <lazar_2004 at list.ru>
Date: Tue, 19 Aug 2025 16:30:28 +0300
Subject: [PATCH 3/6] [mlir][emitc] Changes after review

---
 mlir/lib/Dialect/EmitC/IR/EmitC.cpp    |  2 +-
 mlir/lib/Target/Cpp/TranslateToCpp.cpp | 16 ++++++++--------
 2 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
index 2284215b772b6..0a266cc7261c6 100644
--- a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
+++ b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
@@ -1567,7 +1567,7 @@ static LogicalResult verifyLoopRegions(Operation &op, Region &condition,
                           "'i1', but returns ")
            << exprOp.getResult().getType();
 
-  Operation &last = *std::next(condBlock.begin());
+  Operation &last = condBlock.back();
   auto condYield = dyn_cast<emitc::YieldOp>(last);
   if (!condYield)
     return op.emitOpError("expected last op in condition region to be "
diff --git a/mlir/lib/Target/Cpp/TranslateToCpp.cpp b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
index 0afd626716949..87dae42314af7 100644
--- a/mlir/lib/Target/Cpp/TranslateToCpp.cpp
+++ b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
@@ -259,7 +259,7 @@ struct CppEmitter {
   }
 
   /// Get expression currently being emitted.
-  Operation *getEmittedExpression() { return emittedExpression; }
+  ExpressionOp getEmittedExpression() { return emittedExpression; }
 
   /// Determine whether given value is part of the expression potentially being
   /// emitted.
@@ -318,7 +318,7 @@ struct CppEmitter {
   unsigned int valueCount{0};
 
   /// State of the current expression being emitted.
-  Operation *emittedExpression = nullptr;
+  ExpressionOp emittedExpression;
   SmallVector<int> emittedExpressionPrecedence;
 
   void pushExpressionPrecedence(int precedence) {
@@ -1755,12 +1755,12 @@ LogicalResult CppEmitter::emitOperation(Operation &op, bool trailingSemicolon) {
                 emitc::CmpOp, emitc::ConditionalOp, emitc::ConstantOp,
                 emitc::DeclareFuncOp, emitc::DivOp, emitc::DoOp,
                 emitc::ExpressionOp, emitc::FieldOp, emitc::FileOp,
-                emitc::ForOp, emitc::FuncOp, emitc::GetFieldOp, emitc::GlobalOp,
-                emitc::IfOp, emitc::IncludeOp, emitc::LoadOp,
-                emitc::LogicalAndOp, emitc::LogicalNotOp, emitc::LogicalOrOp,
-                emitc::MulOp, emitc::RemOp, emitc::ReturnOp, emitc::SubOp,
-                emitc::SwitchOp, emitc::UnaryMinusOp, emitc::UnaryPlusOp,
-                emitc::VariableOp, emitc::VerbatimOp, emitc::WhileOp>(
+                emitc::ForOp, emitc::FuncOp, emitc::GlobalOp, emitc::IfOp,
+                emitc::IncludeOp, emitc::LoadOp, emitc::LogicalAndOp,
+                emitc::LogicalNotOp, emitc::LogicalOrOp, emitc::MulOp,
+                emitc::RemOp, emitc::ReturnOp, emitc::SubOp, emitc::SwitchOp,
+                emitc::UnaryMinusOp, emitc::UnaryPlusOp, emitc::VariableOp,
+                emitc::VerbatimOp, emitc::WhileOp>(
 
               [&](auto op) { return printOperation(*this, op); })
           // Func ops.

>From ee99df2622f578a9b40acdbf50184973b8f05d2e Mon Sep 17 00:00:00 2001
From: Vlad Lazar <lazar_2004 at list.ru>
Date: Tue, 26 Aug 2025 20:03:53 +0300
Subject: [PATCH 4/6] [mlir][emitc] Changes after review

Deleted lovering from scf.while to emitc.while. Minor code style
changes have been made.
---
 mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp |  19 +-
 mlir/lib/Dialect/EmitC/IR/EmitC.cpp           |   8 +-
 mlir/test/Conversion/SCFToEmitC/while.mlir    | 190 +++++++-----------
 3 files changed, 79 insertions(+), 138 deletions(-)

diff --git a/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp b/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp
index c98609915b942..5661d19452322 100644
--- a/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp
+++ b/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp
@@ -350,25 +350,10 @@ struct WhileLowering : public OpConversionPattern<WhileOp> {
     // Create variable storage for loop-carried values to enable imperative
     // updates while maintaining SSA semantics at conversion boundaries.
     SmallVector<Value> variables;
-    if (failed(
-            createInitVariables(whileOp, rewriter, variables, loc, context))) {
+    if (failed(createInitVariables(whileOp, rewriter, variables, loc, context)))
       return failure();
-    }
-
-    // Select lowering strategy based on condition argument usage:
-    // - emitc.while when condition args match region inputs (direct mapping);
-    // - emitc.do when condition args differ (requires state synchronization).
-    Region &beforeRegion = adaptor.getBefore();
-    Block &beforeBlock = beforeRegion.front();
-    auto condOp = cast<scf::ConditionOp>(beforeRegion.back().getTerminator());
-
-    bool isDoOp = !llvm::equal(beforeBlock.getArguments(), condOp.getArgs());
-
-    LogicalResult result =
-        isDoOp ? lowerDoWhile(whileOp, variables, context, rewriter, loc)
-               : lowerWhile(whileOp, variables, context, rewriter, loc);
 
-    if (failed(result))
+    if (failed(lowerDoWhile(whileOp, variables, context, rewriter, loc)))
       return failure();
 
     // Create an emitc::variable op for each result. These variables will be
diff --git a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
index 0a266cc7261c6..f2b5b452e4df5 100644
--- a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
+++ b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
@@ -1574,11 +1574,10 @@ static LogicalResult verifyLoopRegions(Operation &op, Region &condition,
                           "'emitc.yield', but got ")
            << last.getName();
 
-  if (condYield.getNumOperands() != 1) {
+  if (condYield.getNumOperands() != 1)
     return op.emitOpError("expected condition region to return 1 value, but "
                           "it returns ")
            << condYield.getNumOperands() << " values";
-  }
 
   if (condYield.getOperand(0) != exprOp.getResult())
     return op.emitError("'emitc.yield' must return result of "
@@ -1608,9 +1607,8 @@ static ParseResult parseLoop(OpAsmParser &parser, OperationState &res,
   Region *firstRegion = res.addRegion();
   Region *secondRegion = res.addRegion();
 
-  if (parser.parseRegion(*firstRegion))
-    return failure();
-  if (parser.parseKeyword(midKeyword) || parser.parseRegion(*secondRegion))
+  if (parser.parseRegion(*firstRegion) || parser.parseKeyword(midKeyword) ||
+      parser.parseRegion(*secondRegion))
     return failure();
 
   return parser.parseOptionalAttrDictWithKeyword(res.attributes);
diff --git a/mlir/test/Conversion/SCFToEmitC/while.mlir b/mlir/test/Conversion/SCFToEmitC/while.mlir
index 9ded0123e3cca..59a4c20387fba 100644
--- a/mlir/test/Conversion/SCFToEmitC/while.mlir
+++ b/mlir/test/Conversion/SCFToEmitC/while.mlir
@@ -1,66 +1,12 @@
 // RUN: mlir-opt -allow-unregistered-dialect -convert-scf-to-emitc %s | FileCheck %s
 // RUN: mlir-opt -allow-unregistered-dialect -convert-to-emitc="filter-dialects=scf" %s | FileCheck %s
 
-emitc.func @payload_while(%arg: i32) -> i32 {
+emitc.func @payload_one_result(%arg: i32) -> i32 {
   %result = add %arg, %arg : (i32, i32) -> i32
   return %result : i32
 }
 
-func.func @while() -> i32 {
-  %init = emitc.literal "1.0" : i32
-  %var  = emitc.literal "1.0" : i32
-  %exit = emitc.literal "10.0" : i32
-
-  %res = scf.while (%arg1 = %init) : (i32) -> i32 {
-    %sum = emitc.add %arg1, %var : (i32, i32) -> i32
-    %condition = emitc.cmp lt, %sum, %exit : (i32, i32) -> i1
-    scf.condition(%condition) %arg1 : i32
-  } do {
-  ^bb0(%arg2: i32):
-    %next_arg1 = emitc.call @payload_while(%arg2) : (i32) -> i32
-    scf.yield %next_arg1 : i32
-  }
-  
-  return %res : i32
-}
-// CHECK-LABEL:   emitc.func @payload_while(
-// CHECK-SAME:      %[[ARG0:.*]]: i32) -> i32 {
-// CHECK:           %[[VAL_0:.*]] = add %[[ARG0]], %[[ARG0]] : (i32, i32) -> i32
-// CHECK:           return %[[VAL_0]] : i32
-// CHECK:         }
-
-// CHECK-LABEL:   func.func @while() -> i32 {
-// CHECK:           %[[VAL_0:.*]] = emitc.literal "1.0" : i32
-// CHECK:           %[[VAL_1:.*]] = emitc.literal "1.0" : i32
-// CHECK:           %[[VAL_2:.*]] = emitc.literal "10.0" : i32
-// CHECK:           %[[VAL_3:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
-// CHECK:           emitc.assign %[[VAL_0]] : i32 to %[[VAL_3]] : <i32>
-// CHECK:           emitc.while {
-// CHECK:             %[[VAL_4:.*]] = expression : i1 {
-// CHECK:               %[[VAL_5:.*]] = load %[[VAL_3]] : <i32>
-// CHECK:               %[[VAL_6:.*]] = add %[[VAL_5]], %[[VAL_1]] : (i32, i32) -> i32
-// CHECK:               %[[VAL_7:.*]] = cmp lt, %[[VAL_6]], %[[VAL_2]] : (i32, i32) -> i1
-// CHECK:               yield %[[VAL_7]] : i1
-// CHECK:             }
-// CHECK:             yield %[[VAL_4]] : i1
-// CHECK:           } do {
-// CHECK:             %[[VAL_8:.*]] = load %[[VAL_3]] : <i32>
-// CHECK:             %[[VAL_9:.*]] = call @payload_while(%[[VAL_8]]) : (i32) -> i32
-// CHECK:             assign %[[VAL_9]] : i32 to %[[VAL_3]] : <i32>
-// CHECK:           }
-// CHECK:           %[[VAL_10:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
-// CHECK:           %[[VAL_11:.*]] = emitc.load %[[VAL_3]] : <i32>
-// CHECK:           emitc.assign %[[VAL_11]] : i32 to %[[VAL_10]] : <i32>
-// CHECK:           %[[VAL_12:.*]] = emitc.load %[[VAL_10]] : <i32>
-// CHECK:           return %[[VAL_12]] : i32
-// CHECK:         }
-
-emitc.func @payload_do(%arg: i32) -> i32 {
-  %result = add %arg, %arg : (i32, i32) -> i32
-  return %result : i32
-}
-
-func.func @do() -> i32 {
+func.func @one_result() -> i32 {
   %init = emitc.literal "1.0" : i32
   %var  = emitc.literal "1.0" : i32
   %exit = emitc.literal "10.0" : i32
@@ -72,19 +18,19 @@ func.func @do() -> i32 {
     scf.condition(%condition) %next : i32
   } do {
   ^bb0(%arg2: i32):
-    %next_arg1 = emitc.call @payload_do(%arg2) : (i32) -> i32
+    %next_arg1 = emitc.call @payload_one_result(%arg2) : (i32) -> i32
     scf.yield %next_arg1 : i32
   }
   
   return %res : i32
 }
-// CHECK-LABEL:   emitc.func @payload_do(
+// CHECK-LABEL:   emitc.func @payload_one_result(
 // CHECK-SAME:      %[[ARG0:.*]]: i32) -> i32 {
 // CHECK:           %[[VAL_0:.*]] = add %[[ARG0]], %[[ARG0]] : (i32, i32) -> i32
 // CHECK:           return %[[VAL_0]] : i32
 // CHECK:         }
 
-// CHECK-LABEL:   func.func @do() -> i32 {
+// CHECK-LABEL:   func.func @one_result() -> i32 {
 // CHECK:           %[[VAL_0:.*]] = emitc.literal "1.0" : i32
 // CHECK:           %[[VAL_1:.*]] = emitc.literal "1.0" : i32
 // CHECK:           %[[VAL_2:.*]] = emitc.literal "10.0" : i32
@@ -98,7 +44,7 @@ func.func @do() -> i32 {
 // CHECK:             %[[VAL_8:.*]] = add %[[VAL_5]], %[[VAL_5]] : (i32, i32) -> i32
 // CHECK:             assign %[[VAL_7]] : i1 to %[[VAL_4]] : <i1>
 // CHECK:             if %[[VAL_7]] {
-// CHECK:               %[[VAL_9:.*]] = call @payload_do(%[[VAL_8]]) : (i32) -> i32
+// CHECK:               %[[VAL_9:.*]] = call @payload_one_result(%[[VAL_8]]) : (i32) -> i32
 // CHECK:               assign %[[VAL_9]] : i32 to %[[VAL_3]] : <i32>
 // CHECK:             }
 // CHECK:           } while {
@@ -115,58 +61,61 @@ func.func @do() -> i32 {
 // CHECK:           return %[[VAL_14]] : i32
 // CHECK:         }
 
-emitc.func @payload_while_two_results(%arg: i32) -> i32 {
+emitc.func @payload_two_results(%arg: i32) -> i32 {
   %result = add %arg, %arg : (i32, i32) -> i32
   return %result : i32
 }
 
-func.func @while_two_results() -> i32 {
+func.func @two_results() -> i32 {
   %init = emitc.literal "1.0" : i32
   %exit = emitc.literal "10.0" : i32
 
   %res1, %res2 = scf.while (%arg1_1 = %init, %arg1_2 = %init) : (i32, i32) -> (i32, i32) {
     %sum = emitc.add %arg1_1, %arg1_2 : (i32, i32) -> i32
     %condition = emitc.cmp lt, %sum, %exit : (i32, i32) -> i1
-    scf.condition(%condition) %arg1_1, %arg1_2  : i32, i32
+    scf.condition(%condition) %init, %arg1_2  : i32, i32
   } do {
   ^bb0(%arg2_1 : i32, %arg2_2 : i32):
-    %next1 = emitc.call @payload_while_two_results(%arg2_1) : (i32) -> i32
-    %next2 = emitc.call @payload_while_two_results(%arg2_2) : (i32) -> i32
+    %next1 = emitc.call @payload_two_results(%arg2_1) : (i32) -> i32
+    %next2 = emitc.call @payload_two_results(%arg2_2) : (i32) -> i32
     scf.yield %next1, %next2 : i32, i32
   }
   
   return %res1 : i32
 }
 
-// CHECK-LABEL:   emitc.func @payload_while_two_results(
+// CHECK-LABEL:   emitc.func @payload_two_results(
 // CHECK-SAME:      %[[ARG0:.*]]: i32) -> i32 {
 // CHECK:           %[[VAL_0:.*]] = add %[[ARG0]], %[[ARG0]] : (i32, i32) -> i32
 // CHECK:           return %[[VAL_0]] : i32
 // CHECK:         }
 
-// CHECK-LABEL:   func.func @while_two_results() -> i32 {
+// CHECK-LABEL:   func.func @two_results() -> i32 {
 // CHECK:           %[[VAL_0:.*]] = emitc.literal "1.0" : i32
 // CHECK:           %[[VAL_1:.*]] = emitc.literal "10.0" : i32
 // CHECK:           %[[VAL_2:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
 // CHECK:           emitc.assign %[[VAL_0]] : i32 to %[[VAL_2]] : <i32>
 // CHECK:           %[[VAL_3:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
 // CHECK:           emitc.assign %[[VAL_0]] : i32 to %[[VAL_3]] : <i32>
-// CHECK:           emitc.while {
-// CHECK:             %[[VAL_4:.*]] = expression : i1 {
-// CHECK:               %[[VAL_5:.*]] = load %[[VAL_2]] : <i32>
-// CHECK:               %[[VAL_6:.*]] = load %[[VAL_3]] : <i32>
-// CHECK:               %[[VAL_7:.*]] = add %[[VAL_5]], %[[VAL_6]] : (i32, i32) -> i32
-// CHECK:               %[[VAL_8:.*]] = cmp lt, %[[VAL_7]], %[[VAL_1]] : (i32, i32) -> i1
-// CHECK:               yield %[[VAL_8]] : i1
+// CHECK:           %[[VAL_4:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i1>
+// CHECK:           emitc.do {
+// CHECK:             %[[VAL_5:.*]] = load %[[VAL_2]] : <i32>
+// CHECK:             %[[VAL_6:.*]] = load %[[VAL_3]] : <i32>
+// CHECK:             %[[VAL_7:.*]] = add %[[VAL_5]], %[[VAL_6]] : (i32, i32) -> i32
+// CHECK:             %[[VAL_8:.*]] = cmp lt, %[[VAL_7]], %[[VAL_1]] : (i32, i32) -> i1
+// CHECK:             assign %[[VAL_8]] : i1 to %[[VAL_4]] : <i1>
+// CHECK:             if %[[VAL_8]] {
+// CHECK:               %[[VAL_9:.*]] = call @payload_two_results(%[[VAL_0]]) : (i32) -> i32
+// CHECK:               %[[VAL_10:.*]] = call @payload_two_results(%[[VAL_6]]) : (i32) -> i32
+// CHECK:               assign %[[VAL_9]] : i32 to %[[VAL_2]] : <i32>
+// CHECK:               assign %[[VAL_10]] : i32 to %[[VAL_3]] : <i32>
 // CHECK:             }
-// CHECK:             yield %[[VAL_4]] : i1
-// CHECK:           } do {
-// CHECK:             %[[VAL_9:.*]] = load %[[VAL_2]] : <i32>
-// CHECK:             %[[VAL_10:.*]] = load %[[VAL_3]] : <i32>
-// CHECK:             %[[VAL_11:.*]] = call @payload_while_two_results(%[[VAL_9]]) : (i32) -> i32
-// CHECK:             %[[VAL_12:.*]] = call @payload_while_two_results(%[[VAL_10]]) : (i32) -> i32
-// CHECK:             assign %[[VAL_11]] : i32 to %[[VAL_2]] : <i32>
-// CHECK:             assign %[[VAL_12]] : i32 to %[[VAL_3]] : <i32>
+// CHECK:           } while {
+// CHECK:             %[[VAL_11:.*]] = expression : i1 {
+// CHECK:               %[[VAL_12:.*]] = load %[[VAL_4]] : <i1>
+// CHECK:               yield %[[VAL_12]] : i1
+// CHECK:             }
+// CHECK:             yield %[[VAL_11]] : i1
 // CHECK:           }
 // CHECK:           %[[VAL_13:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
 // CHECK:           %[[VAL_14:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
@@ -179,53 +128,66 @@ func.func @while_two_results() -> i32 {
 // CHECK:           return %[[VAL_17]] : i32
 // CHECK:         }
 
-emitc.func @payload_do_two_results(%arg: i32) -> i32 {
+emitc.func @payload_double_use(%arg: i32) -> i32 {
   %result = add %arg, %arg : (i32, i32) -> i32
   return %result : i32
 }
 
-func.func @do_two_results() -> i32 {
+emitc.func @foo_with_side_effect(%arg: i32, %p : !emitc.ptr<i32>) -> i32 {
+  %sum = add %arg, %arg : (i32, i32) -> i32
+  emitc.verbatim "{}[0] = {};" args %p, %sum : !emitc.ptr<i32>, i32
+  return %sum : i32
+}
+
+func.func @double_use(%p : !emitc.ptr<i32>) -> i32 {
   %init = emitc.literal "1.0" : i32
+  %var  = emitc.literal "1.0" : i32
   %exit = emitc.literal "10.0" : i32
-
-  %res1, %res2 = scf.while (%arg1_1 = %init, %arg1_2 = %init) : (i32, i32) -> (i32, i32) {
-    %sum = emitc.add %arg1_1, %arg1_2 : (i32, i32) -> i32
+  %res = scf.while (%arg1 = %init) : (i32) -> i32 {
+    %used_twice = emitc.call @foo_with_side_effect(%arg1, %p) : (i32, !emitc.ptr<i32>) -> i32
+    %prod = emitc.add %used_twice, %used_twice : (i32, i32) -> i32
+    %sum = emitc.add %arg1, %prod : (i32, i32) -> i32
     %condition = emitc.cmp lt, %sum, %exit : (i32, i32) -> i1
-    scf.condition(%condition) %init, %arg1_2  : i32, i32
+    scf.condition(%condition) %arg1 : i32
   } do {
-  ^bb0(%arg2_1 : i32, %arg2_2 : i32):
-    %next1 = emitc.call @payload_do_two_results(%arg2_1) : (i32) -> i32
-    %next2 = emitc.call @payload_do_two_results(%arg2_2) : (i32) -> i32
-    scf.yield %next1, %next2 : i32, i32
+  ^bb0(%arg2: i32):
+    %next_arg1 = emitc.call @payload_double_use(%arg2) : (i32) -> i32
+    scf.yield %next_arg1 : i32
   }
-  
-  return %res1 : i32
+  return %res : i32
 }
 
-// CHECK-LABEL:   emitc.func @payload_do_two_results(
+// CHECK-LABEL:   emitc.func @payload_double_use(
 // CHECK-SAME:      %[[ARG0:.*]]: i32) -> i32 {
 // CHECK:           %[[VAL_0:.*]] = add %[[ARG0]], %[[ARG0]] : (i32, i32) -> i32
 // CHECK:           return %[[VAL_0]] : i32
 // CHECK:         }
 
-// CHECK-LABEL:   func.func @do_two_results() -> i32 {
+// CHECK-LABEL:   emitc.func @foo_with_side_effect(
+// CHECK-SAME:      %[[ARG0:.*]]: i32,
+// CHECK-SAME:      %[[ARG1:.*]]: !emitc.ptr<i32>) -> i32 {
+// CHECK:           %[[VAL_0:.*]] = add %[[ARG0]], %[[ARG0]] : (i32, i32) -> i32
+// CHECK:           verbatim "{}[0] = {};" args %[[ARG1]], %[[VAL_0]] : !emitc.ptr<i32>, i32
+// CHECK:           return %[[VAL_0]] : i32
+// CHECK:         }
+
+// CHECK-LABEL:   func.func @double_use(
+// CHECK-SAME:      %[[ARG0:.*]]: !emitc.ptr<i32>) -> i32 {
 // CHECK:           %[[VAL_0:.*]] = emitc.literal "1.0" : i32
-// CHECK:           %[[VAL_1:.*]] = emitc.literal "10.0" : i32
-// CHECK:           %[[VAL_2:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
-// CHECK:           emitc.assign %[[VAL_0]] : i32 to %[[VAL_2]] : <i32>
+// CHECK:           %[[VAL_1:.*]] = emitc.literal "1.0" : i32
+// CHECK:           %[[VAL_2:.*]] = emitc.literal "10.0" : i32
 // CHECK:           %[[VAL_3:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
 // CHECK:           emitc.assign %[[VAL_0]] : i32 to %[[VAL_3]] : <i32>
 // CHECK:           %[[VAL_4:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i1>
 // CHECK:           emitc.do {
-// CHECK:             %[[VAL_5:.*]] = load %[[VAL_2]] : <i32>
-// CHECK:             %[[VAL_6:.*]] = load %[[VAL_3]] : <i32>
-// CHECK:             %[[VAL_7:.*]] = add %[[VAL_5]], %[[VAL_6]] : (i32, i32) -> i32
-// CHECK:             %[[VAL_8:.*]] = cmp lt, %[[VAL_7]], %[[VAL_1]] : (i32, i32) -> i1
-// CHECK:             assign %[[VAL_8]] : i1 to %[[VAL_4]] : <i1>
-// CHECK:             if %[[VAL_8]] {
-// CHECK:               %[[VAL_9:.*]] = call @payload_do_two_results(%[[VAL_0]]) : (i32) -> i32
-// CHECK:               %[[VAL_10:.*]] = call @payload_do_two_results(%[[VAL_6]]) : (i32) -> i32
-// CHECK:               assign %[[VAL_9]] : i32 to %[[VAL_2]] : <i32>
+// CHECK:             %[[VAL_5:.*]] = load %[[VAL_3]] : <i32>
+// CHECK:             %[[VAL_6:.*]] = call @foo_with_side_effect(%[[VAL_5]], %[[ARG0]]) : (i32, !emitc.ptr<i32>) -> i32
+// CHECK:             %[[VAL_7:.*]] = add %[[VAL_6]], %[[VAL_6]] : (i32, i32) -> i32
+// CHECK:             %[[VAL_8:.*]] = add %[[VAL_5]], %[[VAL_7]] : (i32, i32) -> i32
+// CHECK:             %[[VAL_9:.*]] = cmp lt, %[[VAL_8]], %[[VAL_2]] : (i32, i32) -> i1
+// CHECK:             assign %[[VAL_9]] : i1 to %[[VAL_4]] : <i1>
+// CHECK:             if %[[VAL_9]] {
+// CHECK:               %[[VAL_10:.*]] = call @payload_double_use(%[[VAL_5]]) : (i32) -> i32
 // CHECK:               assign %[[VAL_10]] : i32 to %[[VAL_3]] : <i32>
 // CHECK:             }
 // CHECK:           } while {
@@ -236,12 +198,8 @@ func.func @do_two_results() -> i32 {
 // CHECK:             yield %[[VAL_11]] : i1
 // CHECK:           }
 // CHECK:           %[[VAL_13:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
-// CHECK:           %[[VAL_14:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
-// CHECK:           %[[VAL_15:.*]] = emitc.load %[[VAL_2]] : <i32>
-// CHECK:           emitc.assign %[[VAL_15]] : i32 to %[[VAL_13]] : <i32>
-// CHECK:           %[[VAL_16:.*]] = emitc.load %[[VAL_3]] : <i32>
-// CHECK:           emitc.assign %[[VAL_16]] : i32 to %[[VAL_14]] : <i32>
-// CHECK:           %[[VAL_17:.*]] = emitc.load %[[VAL_13]] : <i32>
-// CHECK:           %[[VAL_18:.*]] = emitc.load %[[VAL_14]] : <i32>
-// CHECK:           return %[[VAL_17]] : i32
+// CHECK:           %[[VAL_14:.*]] = emitc.load %[[VAL_3]] : <i32>
+// CHECK:           emitc.assign %[[VAL_14]] : i32 to %[[VAL_13]] : <i32>
+// CHECK:           %[[VAL_15:.*]] = emitc.load %[[VAL_13]] : <i32>
+// CHECK:           return %[[VAL_15]] : i32
 // CHECK:         }

>From b8d88b955a970e280d7bbe48cc723cdab32638e3 Mon Sep 17 00:00:00 2001
From: Vlad Lazar <lazar_2004 at list.ru>
Date: Wed, 3 Sep 2025 20:23:21 +0300
Subject: [PATCH 5/6] [mlir][emitc] Changes after updating ExpressionOp

---
 mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp | 53 +++----------------
 mlir/test/Conversion/SCFToEmitC/while.mlir    |  6 +--
 mlir/test/Dialect/EmitC/invalid_ops.mlir      | 32 +++++------
 mlir/test/Dialect/EmitC/ops.mlir              |  4 +-
 mlir/test/Target/Cpp/do.mlir                  | 10 ++--
 mlir/test/Target/Cpp/while.mlir               | 10 ++--
 6 files changed, 37 insertions(+), 78 deletions(-)

diff --git a/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp b/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp
index 5661d19452322..07df270f8221f 100644
--- a/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp
+++ b/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp
@@ -453,51 +453,6 @@ struct WhileLowering : public OpConversionPattern<WhileOp> {
     return finalResults;
   }
 
-  // Direct lowering to emitc.while when condition arguments match region
-  // inputs.
-  LogicalResult lowerWhile(WhileOp whileOp, ArrayRef<Value> vars,
-                           MLIRContext *context,
-                           ConversionPatternRewriter &rewriter,
-                           Location loc) const {
-    auto loweredWhile = rewriter.create<emitc::WhileOp>(loc);
-
-    // Lower before region to condition region.
-    Region &condRegion = loweredWhile.getConditionRegion();
-    Block *condBlock = rewriter.createBlock(&condRegion);
-    rewriter.setInsertionPointToStart(condBlock);
-
-    Type i1Type = IntegerType::get(context, 1);
-    auto exprOp = rewriter.create<emitc::ExpressionOp>(loc, TypeRange{i1Type});
-    Region &exprRegion = exprOp.getBodyRegion();
-
-    rewriter.inlineRegionBefore(whileOp.getBefore(), exprRegion,
-                                exprRegion.begin());
-
-    Block *exprBlock = &exprRegion.front();
-    replaceBlockArgsWithVarLoads(exprBlock, vars, rewriter, loc);
-
-    auto condOp = cast<scf::ConditionOp>(exprBlock->getTerminator());
-    Value condition = rewriter.getRemappedValue(condOp.getCondition());
-    rewriter.setInsertionPointAfter(condOp);
-    rewriter.replaceOpWithNewOp<emitc::YieldOp>(condOp, condition);
-
-    rewriter.setInsertionPointToEnd(condBlock);
-    rewriter.create<emitc::YieldOp>(loc, exprOp);
-
-    // Lower after region to body region.
-    Region &bodyRegion = loweredWhile.getBodyRegion();
-    rewriter.inlineRegionBefore(whileOp.getAfter(), bodyRegion,
-                                bodyRegion.end());
-
-    Block *bodyBlock = &bodyRegion.front();
-    replaceBlockArgsWithVarLoads(bodyBlock, vars, rewriter, loc);
-
-    // Convert scf.yield to variable assignments for state updates.
-    processYieldTerminator(bodyBlock->getTerminator(), vars, rewriter, loc);
-
-    return success();
-  }
-
   // Lower to emitc.do when condition arguments differ from region inputs.
   LogicalResult lowerDoWhile(WhileOp whileOp, ArrayRef<Value> vars,
                              MLIRContext *context,
@@ -555,11 +510,15 @@ struct WhileLowering : public OpConversionPattern<WhileOp> {
     Block *condBlock = rewriter.createBlock(&condRegion);
     rewriter.setInsertionPointToStart(condBlock);
 
-    auto exprOp = rewriter.create<emitc::ExpressionOp>(loc, TypeRange{i1Type});
+    auto exprOp = rewriter.create<emitc::ExpressionOp>(
+        loc, i1Type, conditionVal, /*do_not_inline=*/false);
     Block *exprBlock = rewriter.createBlock(&exprOp.getBodyRegion());
+
+    exprBlock->addArgument(conditionVal.getType(), loc);
     rewriter.setInsertionPointToStart(exprBlock);
 
-    Value cond = rewriter.create<emitc::LoadOp>(loc, i1Type, conditionVal);
+    Value cond =
+        rewriter.create<emitc::LoadOp>(loc, i1Type, exprBlock->getArgument(0));
     rewriter.create<emitc::YieldOp>(loc, cond);
 
     rewriter.setInsertionPointToEnd(condBlock);
diff --git a/mlir/test/Conversion/SCFToEmitC/while.mlir b/mlir/test/Conversion/SCFToEmitC/while.mlir
index 59a4c20387fba..c2a446e4f8017 100644
--- a/mlir/test/Conversion/SCFToEmitC/while.mlir
+++ b/mlir/test/Conversion/SCFToEmitC/while.mlir
@@ -48,7 +48,7 @@ func.func @one_result() -> i32 {
 // CHECK:               assign %[[VAL_9]] : i32 to %[[VAL_3]] : <i32>
 // CHECK:             }
 // CHECK:           } while {
-// CHECK:             %[[VAL_10:.*]] = expression : i1 {
+// CHECK:             %[[VAL_10:.*]] = expression %[[VAL_4]] : (!emitc.lvalue<i1>) -> i1 {
 // CHECK:               %[[VAL_11:.*]] = load %[[VAL_4]] : <i1>
 // CHECK:               yield %[[VAL_11]] : i1
 // CHECK:             }
@@ -111,7 +111,7 @@ func.func @two_results() -> i32 {
 // CHECK:               assign %[[VAL_10]] : i32 to %[[VAL_3]] : <i32>
 // CHECK:             }
 // CHECK:           } while {
-// CHECK:             %[[VAL_11:.*]] = expression : i1 {
+// CHECK:             %[[VAL_11:.*]] = expression %[[VAL_4]] : (!emitc.lvalue<i1>) -> i1 {
 // CHECK:               %[[VAL_12:.*]] = load %[[VAL_4]] : <i1>
 // CHECK:               yield %[[VAL_12]] : i1
 // CHECK:             }
@@ -191,7 +191,7 @@ func.func @double_use(%p : !emitc.ptr<i32>) -> i32 {
 // CHECK:               assign %[[VAL_10]] : i32 to %[[VAL_3]] : <i32>
 // CHECK:             }
 // CHECK:           } while {
-// CHECK:             %[[VAL_11:.*]] = expression : i1 {
+// CHECK:             %[[VAL_11:.*]] = expression %[[VAL_4]] : (!emitc.lvalue<i1>) -> i1 {
 // CHECK:               %[[VAL_12:.*]] = load %[[VAL_4]] : <i1>
 // CHECK:               yield %[[VAL_12]] : i1
 // CHECK:             }
diff --git a/mlir/test/Dialect/EmitC/invalid_ops.mlir b/mlir/test/Dialect/EmitC/invalid_ops.mlir
index 68d2a7f0b3545..f897f29b2c068 100644
--- a/mlir/test/Dialect/EmitC/invalid_ops.mlir
+++ b/mlir/test/Dialect/EmitC/invalid_ops.mlir
@@ -728,7 +728,7 @@ func.func @test_while(%arg0 : !emitc.ptr<i32>) {
 
   // expected-error @+1 {{'emitc.while' op condition region must contain exactly two operations: 'emitc.expression' followed by 'emitc.yield', but found 3 operations}}
   emitc.while {
-    %r = emitc.expression : i1 {
+    %r = emitc.expression %1, %2 : (i32, i32) -> i1 {
       %cmp = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
       emitc.yield %cmp : i1
     }
@@ -766,7 +766,7 @@ func.func @test_while(%arg0 : !emitc.ptr<i32>) {
 
   // expected-error @+1 {{'emitc.while' op emitc.expression in condition region must return 'i1', but returns 'i32'}}
   emitc.while {
-    %r = emitc.expression : i32 {
+    %r = emitc.expression %1, %2 : (i32, i32) -> i32 {
       %add = emitc.add %1, %2 : (i32, i32) -> i32
       emitc.yield %add : i32
     }
@@ -787,12 +787,12 @@ func.func @test_while(%arg0 : !emitc.ptr<i32>) {
 
   // expected-error @+1 {{'emitc.while' op expected last op in condition region to be 'emitc.yield', but got emitc.expression}}
   emitc.while {
-    %r1 = emitc.expression : i1 {
+    %r1 = emitc.expression %1, %2 : (i32, i32) -> i1 {
       %cmp = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
       emitc.yield %cmp : i1
     }
 
-    %r2 = emitc.expression : i32 {
+    %r2 = emitc.expression %1, %2 : (i32, i32) -> i32 {
       %add = emitc.add %1, %2 : (i32, i32) -> i32
       emitc.yield %add : i32
     }
@@ -811,7 +811,7 @@ func.func @test_while(%arg0 : !emitc.ptr<i32>) {
 
   // expected-error @+1 {{'emitc.while' op expected condition region to return 1 value, but it returns 0 values}}
   emitc.while {
-    %r = emitc.expression : i1 {
+    %r = emitc.expression %1, %2 : (i32, i32) -> i1 {
       %cmp = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
       emitc.yield %cmp : i1
     }
@@ -834,7 +834,7 @@ func.func @test_while(%arg0 : !emitc.ptr<i32>) {
 
   // expected-error @+1 {{'emitc.yield' must return result of 'emitc.expression' from this condition region}}
   emitc.while {
-    %r = emitc.expression : i1 {
+    %r = emitc.expression %1, %2 : (i32, i32) -> i1 {
       %cmp = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
       emitc.yield %cmp : i1
     }
@@ -855,7 +855,7 @@ func.func @test_while() {
 
   // expected-error @+1 {{'emitc.while' op body region cannot be empty}}
   emitc.while {
-    %r = emitc.expression : i1 {
+    %r = emitc.expression %1, %2 : (i32, i32) -> i1 {
       %cmp = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
       emitc.yield %cmp : i1
     }
@@ -876,7 +876,7 @@ func.func @test_while(%arg0 : !emitc.ptr<i32>) {
 
   // expected-error @+1 {{'emitc.while' op body region must not contain terminator}}
   emitc.while {
-    %r = emitc.expression : i1 {
+    %r = emitc.expression %1, %2 : (i32, i32) -> i1 {
       %cmp = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
       emitc.yield %cmp : i1
     }
@@ -900,7 +900,7 @@ func.func @test_do(%arg0 : !emitc.ptr<i32>) {
   emitc.do {
     emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
   } while {
-    %r = emitc.expression : i1 {
+    %r = emitc.expression %1, %2 : (i32, i32) -> i1 {
       %cmp = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
       emitc.yield %cmp : i1
     }
@@ -936,7 +936,7 @@ func.func @test_do(%arg0 : !emitc.ptr<i32>) {
   emitc.do {
     emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
   } while {
-    %r = emitc.expression : i32 {
+    %r = emitc.expression %1, %2 : (i32, i32) -> i32 {
       %add = emitc.add %1, %2 : (i32, i32) -> i32
       emitc.yield %add : i32
     }
@@ -957,12 +957,12 @@ func.func @test_do(%arg0 : !emitc.ptr<i32>) {
   emitc.do {
     emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
   } while {
-    %r1 = emitc.expression : i1 {
+    %r1 = emitc.expression %1, %2 : (i32, i32) -> i1 {
       %cmp = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
       emitc.yield %cmp : i1
     }
 
-    %r2 = emitc.expression : i32 {
+    %r2 = emitc.expression %1, %2 : (i32, i32) -> i32 {
       %add = emitc.add %1, %2 : (i32, i32) -> i32
       emitc.yield %add : i32
     }
@@ -981,7 +981,7 @@ func.func @test_do(%arg0 : !emitc.ptr<i32>) {
   emitc.do {
     emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
   } while {
-    %r = emitc.expression : i1 {
+    %r = emitc.expression %1, %2 : (i32, i32) -> i1 {
       %cmp = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
       emitc.yield %cmp : i1
     }
@@ -1004,7 +1004,7 @@ func.func @test_do(%arg0 : !emitc.ptr<i32>) {
   emitc.do {
     emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
   } while {
-    %r = emitc.expression : i1 {
+    %r = emitc.expression %1, %2 : (i32, i32) -> i1 {
       %cmp = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
       emitc.yield %cmp : i1
     }
@@ -1026,7 +1026,7 @@ func.func @test_do() {
   emitc.do {
     ^bb0:
   } while {
-    %r = emitc.expression : i1 {
+    %r = emitc.expression %1, %2 : (i32, i32) -> i1 {
       %cmp = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
       emitc.yield %cmp : i1
     }
@@ -1048,7 +1048,7 @@ func.func @test_do(%arg0 : !emitc.ptr<i32>) {
     emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
     emitc.yield
   } while {
-    %r = emitc.expression : i1 {
+    %r = emitc.expression %1, %2 : (i32, i32) -> i1 {
       %cmp = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
       emitc.yield %cmp : i1
     }
diff --git a/mlir/test/Dialect/EmitC/ops.mlir b/mlir/test/Dialect/EmitC/ops.mlir
index 17be4875127ff..ce196247d9043 100644
--- a/mlir/test/Dialect/EmitC/ops.mlir
+++ b/mlir/test/Dialect/EmitC/ops.mlir
@@ -332,7 +332,7 @@ func.func @while(%arg0 : !emitc.ptr<i32>) {
   %3 = emitc.literal "3" : i32
 
   emitc.while {
-    %r = emitc.expression : i1 {
+    %r = emitc.expression %1, %2, %3 : (i32, i32, i32) -> i1 {
       %add = emitc.add %1, %2 : (i32, i32) -> i32
       %cmp = emitc.cmp eq, %add, %3 : (i32, i32) -> i1
       emitc.yield %cmp : i1
@@ -354,7 +354,7 @@ func.func @do(%arg0 : !emitc.ptr<i32>) {
   emitc.do {
     emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
   } while {
-    %r = emitc.expression : i1 {
+    %r = emitc.expression %1, %2, %3 : (i32, i32, i32) -> i1 {
       %add = emitc.add %1, %2 : (i32, i32) -> i32
       %cmp = emitc.cmp eq, %add, %3 : (i32, i32) -> i1
       emitc.yield %cmp : i1
diff --git a/mlir/test/Target/Cpp/do.mlir b/mlir/test/Target/Cpp/do.mlir
index 9ffbb38f25239..a9b8ceddcf3a4 100644
--- a/mlir/test/Target/Cpp/do.mlir
+++ b/mlir/test/Target/Cpp/do.mlir
@@ -23,7 +23,7 @@ emitc.func @emitc_do(%arg0 : !emitc.ptr<i32>) {
     %tmp_add = add %var_load, %1 : (i32, i32) -> i32
     "emitc.assign"(%var, %tmp_add) : (!emitc.lvalue<i32>, i32) -> ()
   } while {
-    %r = expression : i1 {
+    %r = expression %var, %0 : (!emitc.lvalue<i32>, i32) -> i1 {
       %var_load = load %var : <i32>
       %cmp = cmp le, %var_load, %0 : (i32, i32) -> i1
       yield %cmp : i1
@@ -53,7 +53,7 @@ emitc.func @emitc_do_with_expression(%arg0 : !emitc.ptr<i32>) {
   %0 = literal "10" : i32
   %1 = literal "1" : i32
 
-  %add = expression : i32 {
+  %add = expression %0, %1 : (i32, i32) -> i32 {
     %add = add %0, %1 : (i32, i32) -> i32
     yield %add : i32
   }
@@ -64,7 +64,7 @@ emitc.func @emitc_do_with_expression(%arg0 : !emitc.ptr<i32>) {
     %tmp_add = add %var_load, %1 : (i32, i32) -> i32
     "emitc.assign"(%var, %tmp_add) : (!emitc.lvalue<i32>, i32) -> ()
   } while {
-    %r = expression : i1 {
+    %r = expression %var, %add : (!emitc.lvalue<i32>, i32) -> i1 {
       %var_load = load %var : <i32>
       %cmp = cmp le, %var_load, %add : (i32, i32) -> i1
       yield %cmp : i1
@@ -111,7 +111,7 @@ emitc.func @emitc_double_do() {
       %tmp_add = add %var_2_load, %step : (i32, i32) -> i32
       "emitc.assign"(%var_2, %tmp_add) : (!emitc.lvalue<i32>, i32) -> ()
     } while {
-      %r = expression : i1 {
+      %r = expression %var_2, %end_2 : (!emitc.lvalue<i32>, i32) -> i1 {
         %var_2_load = load %var_2 : <i32>
         %cmp = cmp le, %var_2_load, %end_2 : (i32, i32) -> i1
         yield %cmp : i1
@@ -123,7 +123,7 @@ emitc.func @emitc_double_do() {
     %tmp_add = add %var_1_load, %step : (i32, i32) -> i32
     "emitc.assign"(%var_1, %tmp_add) : (!emitc.lvalue<i32>, i32) -> ()
   } while {
-    %r = expression : i1 {
+    %r = expression %var_1, %end_1 : (!emitc.lvalue<i32>, i32) -> i1 {
       %var_1_load = load %var_1 : <i32>
       %cmp = cmp le, %var_1_load, %end_1 : (i32, i32) -> i1
       yield %cmp : i1
diff --git a/mlir/test/Target/Cpp/while.mlir b/mlir/test/Target/Cpp/while.mlir
index 8770550f3ad4d..cff154161ab40 100644
--- a/mlir/test/Target/Cpp/while.mlir
+++ b/mlir/test/Target/Cpp/while.mlir
@@ -18,7 +18,7 @@ emitc.func @emitc_while(%arg0 : !emitc.ptr<i32>) {
   %1 = literal "1" : i32
 
   while {
-    %r = expression : i1 {
+    %r = expression %var, %0 : (!emitc.lvalue<i32>, i32) -> i1 {
       %var_load = load %var : <i32>
       %cmp = cmp le, %var_load, %0 : (i32, i32) -> i1
       yield %cmp : i1
@@ -53,13 +53,13 @@ emitc.func @emitc_while_with_expression(%arg0 : !emitc.ptr<i32>) {
   %0 = literal "10" : i32
   %1 = literal "1" : i32
 
-  %add = expression : i32 {
+  %add = expression %0, %1 : (i32, i32) -> i32 {
     %add = add %0, %1 : (i32, i32) -> i32
     yield %add : i32
   }
 
   while {
-    %r = expression : i1 {
+    %r = expression %var, %add : (!emitc.lvalue<i32>, i32) -> i1 {
       %var_load = load %var : <i32>
       %cmp = cmp le, %var_load, %add : (i32, i32) -> i1
       yield %cmp : i1
@@ -103,7 +103,7 @@ emitc.func @emitc_double_while() {
   %end_2 = literal "5" : i32
 
   while {
-    %r = expression : i1 {
+    %r = expression %var_1, %end_1 : (!emitc.lvalue<i32>, i32) -> i1 {
       %var_1_load = load %var_1 : <i32>
       %cmp = cmp le, %var_1_load, %end_1 : (i32, i32) -> i1
       yield %cmp : i1
@@ -114,7 +114,7 @@ emitc.func @emitc_double_while() {
     %var_1_load = load %var_1 : <i32>
     
     while {
-      %r = expression : i1 {
+      %r = expression %var_2, %end_2 : (!emitc.lvalue<i32>, i32) -> i1 {
         %var_2_load = load %var_2 : <i32>
         %cmp = cmp le, %var_2_load, %end_2 : (i32, i32) -> i1
         yield %cmp : i1

>From de894baec0604015062947b56080318898a2cc45 Mon Sep 17 00:00:00 2001
From: Vlad Lazar <lazar_2004 at list.ru>
Date: Wed, 3 Sep 2025 20:38:48 +0300
Subject: [PATCH 6/6] [mlir][emitc] Update description (NFC)

---
 mlir/include/mlir/Dialect/EmitC/IR/EmitC.td | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
index 48d40c5796618..d6cb78f7cd821 100644
--- a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
+++ b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
@@ -1743,7 +1743,7 @@ def EmitC_WhileOp : EmitC_Op<"while",
     ```mlir
     emitc.while {
       // Condition region (must yield i1)
-      %condition = emitc.expression : i1 {
+      %condition = emitc.expression : () -> i1 {
         // Condition computation...
         %result = ... : i1  // Last operation must produce i1
         emitc.yield %result : i1
@@ -1765,7 +1765,7 @@ def EmitC_WhileOp : EmitC_Op<"while",
       %step = emitc.literal "1" : i32
 
       emitc.while {
-        %condition = emitc.expression : i1 {
+        %condition = emitc.expression %counter, %end : (!emitc.lvalue<i32>, i32) -> i1 {
           %current = emitc.load %counter : !emitc.lvalue<i32>
           %cmp_res = emitc.cmp lt, %current, %end : (i32, i32) -> i1
           emitc.yield %cmp_res : i1
@@ -1848,7 +1848,7 @@ def EmitC_DoOp : EmitC_Op<"do",
       // Loop body operations...
     } while {
       // Condition region (must yield i1)
-      %condition = emitc.expression : i1 {
+      %condition = emitc.expression : () -> i1 {
         // Condition computation...
         %result = ... : i1  // Last operation must produce i1
         emitc.yield %result : i1
@@ -1875,7 +1875,7 @@ def EmitC_DoOp : EmitC_Op<"do",
         %new_val = emitc.add %val, %step : (i32, i32) -> i32
         "emitc.assign"(%counter, %new_val) : (!emitc.lvalue<i32>, i32) -> ()
       } while {
-        %condition = emitc.expression : i1 {
+        %condition = emitc.expression %counter, %end : (!emitc.lvalue<i32>, i32) -> i1 {
           %current = emitc.load %counter : !emitc.lvalue<i32>
           %cmp_res = emitc.cmp lt, %current, %end : (i32, i32) -> i1
           emitc.yield %cmp_res : i1



More information about the Mlir-commits mailing list