[Mlir-commits] [mlir] c3aa158 - [mlir][emitc] Add emitc.do op to the dialect (#143008)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Wed Oct 8 06:02:57 PDT 2025


Author: Vlad Lazar
Date: 2025-10-08T16:02:53+03:00
New Revision: c3aa1584e0981d5bbd26c076c3a2298ccf34e3ca

URL: https://github.com/llvm/llvm-project/commit/c3aa1584e0981d5bbd26c076c3a2298ccf34e3ca
DIFF: https://github.com/llvm/llvm-project/commit/c3aa1584e0981d5bbd26c076c3a2298ccf34e3ca.diff

LOG: [mlir][emitc] Add emitc.do op to the dialect (#143008)

This patch adds:
- Emission of the corresponding ops in the CppEmitter
- Conversion from the SCF dialect to the EmitC dialect for the ops
- Corresponding tests

Added: 
    mlir/test/Conversion/SCFToEmitC/while.mlir
    mlir/test/Target/Cpp/do.mlir

Modified: 
    mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
    mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp
    mlir/lib/Dialect/EmitC/IR/EmitC.cpp
    mlir/lib/Target/Cpp/TranslateToCpp.cpp
    mlir/test/Dialect/EmitC/invalid_ops.mlir
    mlir/test/Dialect/EmitC/ops.mlir

Removed: 
    


################################################################################
diff  --git a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
index f52eb7b91dc4c..5754db6008195 100644
--- a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
+++ b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
@@ -1393,7 +1393,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"]>]> {
   let summary = "Block termination operation";
   let description = [{
     The `emitc.yield` terminates its parent EmitC op's region, optionally yielding
@@ -1725,4 +1725,105 @@ def EmitC_GetFieldOp
   let hasVerifier = 1;
 }
 
+def EmitC_DoOp : EmitC_Op<"do",
+      [NoTerminator, OpAsmOpInterface, RecursiveMemoryEffects]> {
+  let summary = "Do-while operation";
+  let description = [{
+    The `emitc.do` operation represents a C/C++ do-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 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
+
+    The body region is executed before the first evaluation 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 @do_example() {
+      %counter = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue<i32>
+      %end = emitc.literal "10" : i32
+      %step = emitc.literal "1" : i32
+
+      emitc.do {
+        // 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 {
+        %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
+        }
+        emitc.yield %condition : i1
+      }
+      return
+    }
+    ```
+    ```c++
+    // Code emitted for the operation above.
+    void do_example() {
+      int32_t v1 = 0;
+      do {
+        int32_t v2 = v1;
+        printf("%d\n", v2);
+        int32_t v3 = v2 + 1;
+        v1 = v3;
+      } while (v1 < 10);
+      return;
+    }
+    ```
+  }];
+
+  let arguments = (ins);
+  let results = (outs); 
+  let regions = (region SizedRegion<1>:$bodyRegion,
+                        SizedRegion<1>:$conditionRegion);
+
+  let hasCustomAssemblyFormat = 1;
+  let hasVerifier = 1;
+
+  let extraClassDeclaration = [{
+    //===------------------------------------------------------------------===//
+    // 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..519d9c8f835f2 100644
--- a/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp
+++ b/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp
@@ -21,6 +21,7 @@
 #include "mlir/IR/PatternMatch.h"
 #include "mlir/Transforms/DialectConversion.h"
 #include "mlir/Transforms/Passes.h"
+#include "llvm/Support/LogicalResult.h"
 
 namespace mlir {
 #define GEN_PASS_DEF_SCFTOEMITC
@@ -106,7 +107,7 @@ static void assignValues(ValueRange values, ValueRange variables,
     emitc::AssignOp::create(rewriter, loc, var, value);
 }
 
-SmallVector<Value> loadValues(const SmallVector<Value> &variables,
+SmallVector<Value> loadValues(ArrayRef<Value> variables,
                               PatternRewriter &rewriter, Location loc) {
   return llvm::map_to_vector<>(variables, [&](Value var) {
     Type type = cast<emitc::LValueType>(var.getType()).getValueType();
@@ -116,16 +117,15 @@ SmallVector<Value> loadValues(const SmallVector<Value> &variables,
 
 static LogicalResult lowerYield(Operation *op, ValueRange resultVariables,
                                 ConversionPatternRewriter &rewriter,
-                                scf::YieldOp yield) {
+                                scf::YieldOp yield, bool createYield = true) {
   Location loc = yield.getLoc();
 
   OpBuilder::InsertionGuard guard(rewriter);
   rewriter.setInsertionPoint(yield);
 
   SmallVector<Value> yieldOperands;
-  if (failed(rewriter.getRemappedValues(yield.getOperands(), yieldOperands))) {
+  if (failed(rewriter.getRemappedValues(yield.getOperands(), yieldOperands)))
     return rewriter.notifyMatchFailure(op, "failed to lower yield operands");
-  }
 
   assignValues(yieldOperands, resultVariables, rewriter, loc);
 
@@ -336,11 +336,177 @@ LogicalResult IndexSwitchOpLowering::matchAndRewrite(
   return success();
 }
 
+// Lower scf::while to emitc::do using mutable variables to maintain loop state
+// across iterations. The do-while structure ensures the condition is evaluated
+// after each iteration, matching SCF while semantics.
+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 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");
+
+    // Create variable storage for loop-carried values to enable imperative
+    // updates while maintaining SSA semantics at conversion boundaries.
+    SmallVector<Value> loopVariables;
+    if (failed(createVariablesForLoopCarriedValues(
+            whileOp, rewriter, loopVariables, loc, context)))
+      return failure();
+
+    if (failed(lowerDoWhile(whileOp, loopVariables, resultVariables, context,
+                            rewriter, loc)))
+      return failure();
+
+    rewriter.setInsertionPointAfter(whileOp);
+
+    // Load the final result values from result variables.
+    SmallVector<Value> finalResults =
+        loadValues(resultVariables, 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.
+  LogicalResult createVariablesForLoopCarriedValues(
+      WhileOp whileOp, ConversionPatternRewriter &rewriter,
+      SmallVectorImpl<Value> &loopVars, Location loc,
+      MLIRContext *context) const {
+    OpBuilder::InsertionGuard guard(rewriter);
+    rewriter.setInsertionPoint(whileOp);
+
+    emitc::OpaqueAttr noInit = emitc::OpaqueAttr::get(context, "");
+
+    for (Value init : whileOp.getInits()) {
+      Type convertedType = getTypeConverter()->convertType(init.getType());
+      if (!convertedType)
+        return rewriter.notifyMatchFailure(whileOp, "type conversion failed");
+
+      emitc::VariableOp var = rewriter.create<emitc::VariableOp>(
+          loc, emitc::LValueType::get(convertedType), noInit);
+      rewriter.create<emitc::AssignOp>(loc, var.getResult(), init);
+      loopVars.push_back(var);
+    }
+
+    return success();
+  }
+
+  // Lower scf.while to emitc.do.
+  LogicalResult lowerDoWhile(WhileOp whileOp, ArrayRef<Value> loopVars,
+                             ArrayRef<Value> resultVars, MLIRContext *context,
+                             ConversionPatternRewriter &rewriter,
+                             Location loc) const {
+    // Create a global boolean variable to store the loop condition state.
+    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);
+
+    // Convert region types to match the target dialect type system.
+    if (failed(rewriter.convertRegionTypes(&whileOp.getBefore(),
+                                           *getTypeConverter(), nullptr)) ||
+        failed(rewriter.convertRegionTypes(&whileOp.getAfter(),
+                                           *getTypeConverter(), nullptr))) {
+      return rewriter.notifyMatchFailure(whileOp,
+                                         "region types conversion failed");
+    }
+
+    // Prepare the before region (condition evaluation) for merging.
+    Block *beforeBlock = &whileOp.getBefore().front();
+    Block *bodyBlock = rewriter.createBlock(&loweredDo.getBodyRegion());
+    rewriter.setInsertionPointToStart(bodyBlock);
+
+    // Load current variable values to use as initial arguments for the
+    // condition block.
+    SmallVector<Value> replacingValues = loadValues(loopVars, rewriter, loc);
+    rewriter.mergeBlocks(beforeBlock, bodyBlock, replacingValues);
+
+    Operation *condTerminator =
+        loweredDo.getBodyRegion().back().getTerminator();
+    scf::ConditionOp condOp = cast<scf::ConditionOp>(condTerminator);
+    rewriter.setInsertionPoint(condOp);
+
+    // Update result variables with values from scf::condition.
+    SmallVector<Value> conditionArgs;
+    for (Value arg : condOp.getArgs()) {
+      conditionArgs.push_back(rewriter.getRemappedValue(arg));
+    }
+    assignValues(conditionArgs, resultVars, rewriter, loc);
+
+    // Convert scf.condition to condition variable assignment.
+    Value condition = rewriter.getRemappedValue(condOp.getCondition());
+    rewriter.create<emitc::AssignOp>(loc, conditionVal, condition);
+
+    // Wrap body region in conditional to preserve scf semantics. Only create
+    // ifOp if after-region is non-empty.
+    if (whileOp.getAfterBody()->getOperations().size() > 1) {
+      auto ifOp = rewriter.create<emitc::IfOp>(loc, condition, false, false);
+
+      // Prepare the after region (loop body) for merging.
+      Block *afterBlock = &whileOp.getAfter().front();
+      Block *ifBodyBlock = rewriter.createBlock(&ifOp.getBodyRegion());
+
+      // Replacement values for after block using condition op arguments.
+      SmallVector<Value> afterReplacingValues;
+      for (Value arg : condOp.getArgs())
+        afterReplacingValues.push_back(rewriter.getRemappedValue(arg));
+
+      rewriter.mergeBlocks(afterBlock, ifBodyBlock, afterReplacingValues);
+
+      if (failed(lowerYield(whileOp, loopVars, rewriter,
+                            cast<scf::YieldOp>(ifBodyBlock->getTerminator()))))
+        return failure();
+    }
+
+    rewriter.eraseOp(condOp);
+
+    // Create condition region that loads from the flag variable.
+    Region &condRegion = loweredDo.getConditionRegion();
+    Block *condBlock = rewriter.createBlock(&condRegion);
+    rewriter.setInsertionPointToStart(condBlock);
+
+    auto exprOp = rewriter.create<emitc::ExpressionOp>(
+        loc, i1Type, conditionVal, /*do_not_inline=*/false);
+    Block *exprBlock = rewriter.createBlock(&exprOp.getBodyRegion());
+
+    // Set up the expression block to load the condition variable.
+    exprBlock->addArgument(conditionVal.getType(), loc);
+    rewriter.setInsertionPointToStart(exprBlock);
+
+    // Load the condition value and yield it as the expression result.
+    Value cond =
+        rewriter.create<emitc::LoadOp>(loc, i1Type, exprBlock->getArgument(0));
+    rewriter.create<emitc::YieldOp>(loc, cond);
+
+    // Yield the expression as the condition region result.
+    rewriter.setInsertionPointToEnd(condBlock);
+    rewriter.create<emitc::YieldOp>(loc, exprOp);
+
+    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 +523,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 5c8564bca6f86..4754f0bfe895e 100644
--- a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
+++ b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
@@ -974,10 +974,10 @@ LogicalResult emitc::YieldOp::verify() {
   Value result = getResult();
   Operation *containingOp = getOperation()->getParentOp();
 
-  if (result && containingOp->getNumResults() != 1)
+  if (!isa<DoOp>(containingOp) && result && containingOp->getNumResults() != 1)
     return emitOpError() << "yields a value not returned by parent";
 
-  if (!result && containingOp->getNumResults() != 0)
+  if (!isa<DoOp>(containingOp) && !result && containingOp->getNumResults() != 0)
     return emitOpError() << "does not yield a value to be returned by parent";
 
   return success();
@@ -1561,6 +1561,76 @@ LogicalResult GetFieldOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
   return success();
 }
 
+//===----------------------------------------------------------------------===//
+// DoOp
+//===----------------------------------------------------------------------===//
+
+void DoOp::print(OpAsmPrinter &p) {
+  p << ' ';
+  p.printRegion(getBodyRegion(), /*printEntryBlockArgs=*/false);
+  p << " while ";
+  p.printRegion(getConditionRegion());
+  p.printOptionalAttrDictWithKeyword(getOperation()->getAttrs());
+}
+
+LogicalResult emitc::DoOp::verify() {
+  Block &condBlock = getConditionRegion().front();
+
+  if (condBlock.getOperations().size() != 2)
+    return emitOpError(
+               "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 emitOpError("expected first op in condition region to be "
+                       "'emitc.expression', but got ")
+           << first.getName();
+
+  if (!exprOp.getResult().getType().isInteger(1))
+    return emitOpError("emitc.expression in condition region must return "
+                       "'i1', but returns ")
+           << exprOp.getResult().getType();
+
+  Operation &last = condBlock.back();
+  auto condYield = dyn_cast<emitc::YieldOp>(last);
+  if (!condYield)
+    return emitOpError("expected last op in condition region to be "
+                       "'emitc.yield', but got ")
+           << last.getName();
+
+  if (condYield.getNumOperands() != 1)
+    return emitOpError("expected condition region to return 1 value, but "
+                       "it returns ")
+           << condYield.getNumOperands() << " values";
+
+  if (condYield.getOperand(0) != exprOp.getResult())
+    return emitError("'emitc.yield' must return result of "
+                     "'emitc.expression' from this condition region");
+
+  Block &bodyBlock = getBodyRegion().front();
+  if (bodyBlock.mightHaveTerminator())
+    return emitOpError("body region must not contain terminator");
+
+  return success();
+}
+
+ParseResult DoOp::parse(OpAsmParser &parser, OperationState &result) {
+  Region *bodyRegion = result.addRegion();
+  Region *condRegion = result.addRegion();
+
+  if (parser.parseRegion(*bodyRegion) || parser.parseKeyword("while") ||
+      parser.parseRegion(*condRegion))
+    return failure();
+
+  if (bodyRegion->empty())
+    bodyRegion->emplaceBlock();
+
+  return parser.parseOptionalAttrDictWithKeyword(result.attributes);
+}
+
 //===----------------------------------------------------------------------===//
 // TableGen'd op method definitions
 //===----------------------------------------------------------------------===//

diff  --git a/mlir/lib/Target/Cpp/TranslateToCpp.cpp b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
index a5bd80e9d6b8b..5fe5f4181b71d 100644
--- a/mlir/lib/Target/Cpp/TranslateToCpp.cpp
+++ b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
@@ -201,6 +201,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);
 
@@ -557,6 +559,30 @@ static LogicalResult printOperation(CppEmitter &emitter,
   return success();
 }
 
+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) {
   Operation *operation = cmpOp.getOperation();
 
@@ -1711,13 +1737,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::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::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>(
 
               [&](auto op) { return printOperation(*this, op); })
           // Func ops.
@@ -1765,9 +1792,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>(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..28524a01f5e51
--- /dev/null
+++ b/mlir/test/Conversion/SCFToEmitC/while.mlir
@@ -0,0 +1,293 @@
+// 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_one_result(%arg: i32) -> i32 {
+  %result = add %arg, %arg : (i32, i32) -> i32
+  return %result : i32
+}
+
+func.func @one_result() -> 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_one_result(%arg2) : (i32) -> i32
+    scf.yield %next_arg1 : i32
+  }
+  
+  return %res : i32
+}
+// 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 @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
+// CHECK:           %[[VAL_3:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
+// CHECK:           %[[VAL_4:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
+// CHECK:           emitc.assign %[[VAL_0]] : i32 to %[[VAL_4]] : <i32>
+// CHECK:           %[[VAL_5:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i1>
+// CHECK:           emitc.do {
+// CHECK:             %[[VAL_6:.*]] = load %[[VAL_4]] : <i32>
+// CHECK:             %[[VAL_7:.*]] = add %[[VAL_6]], %[[VAL_1]] : (i32, i32) -> i32
+// CHECK:             %[[VAL_8:.*]] = cmp lt, %[[VAL_7]], %[[VAL_2]] : (i32, i32) -> i1
+// CHECK:             %[[VAL_9:.*]] = add %[[VAL_6]], %[[VAL_6]] : (i32, i32) -> i32
+// CHECK:             assign %[[VAL_9]] : i32 to %[[VAL_3]] : <i32>
+// CHECK:             assign %[[VAL_8]] : i1 to %[[VAL_5]] : <i1>
+// CHECK:             if %[[VAL_8]] {
+// CHECK:               %[[VAL_10:.*]] = call @payload_one_result(%[[VAL_9]]) : (i32) -> i32
+// CHECK:               assign %[[VAL_10]] : i32 to %[[VAL_4]] : <i32>
+// CHECK:             }
+// CHECK:           } while {
+// CHECK:             %[[VAL_11:.*]] = expression %[[VAL_5]] : (!emitc.lvalue<i1>) -> i1 {
+// CHECK:               %[[VAL_12:.*]] = load %[[VAL_5]] : <i1>
+// CHECK:               yield %[[VAL_12]] : i1
+// CHECK:             }
+// CHECK:             yield %[[VAL_11]] : i1
+// CHECK:           }
+// CHECK:           %[[VAL_13:.*]] = emitc.load %[[VAL_3]] : <i32>
+// CHECK:           return %[[VAL_13]] : i32
+// CHECK:         }
+
+emitc.func @payload_two_results(%arg: i32) -> i32 {
+  %result = add %arg, %arg : (i32, i32) -> i32
+  return %result : 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) %init, %arg1_2  : i32, i32
+  } do {
+  ^bb0(%arg2_1 : i32, %arg2_2 : 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_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 @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:           %[[VAL_3:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
+// CHECK:           %[[VAL_4:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
+// CHECK:           emitc.assign %[[VAL_0]] : i32 to %[[VAL_4]] : <i32>
+// CHECK:           %[[VAL_5:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
+// CHECK:           emitc.assign %[[VAL_0]] : i32 to %[[VAL_5]] : <i32>
+// CHECK:           %[[VAL_6:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i1>
+// CHECK:           emitc.do {
+// CHECK:             %[[VAL_7:.*]] = load %[[VAL_4]] : <i32>
+// CHECK:             %[[VAL_8:.*]] = load %[[VAL_5]] : <i32>
+// CHECK:             %[[VAL_9:.*]] = add %[[VAL_7]], %[[VAL_8]] : (i32, i32) -> i32
+// CHECK:             %[[VAL_10:.*]] = cmp lt, %[[VAL_9]], %[[VAL_1]] : (i32, i32) -> i1
+// CHECK:             assign %[[VAL_0]] : i32 to %[[VAL_2]] : <i32>
+// CHECK:             assign %[[VAL_8]] : i32 to %[[VAL_3]] : <i32>
+// CHECK:             assign %[[VAL_10]] : i1 to %[[VAL_6]] : <i1>
+// CHECK:             if %[[VAL_10]] {
+// CHECK:               %[[VAL_11:.*]] = call @payload_two_results(%[[VAL_0]]) : (i32) -> i32
+// CHECK:               %[[VAL_12:.*]] = call @payload_two_results(%[[VAL_8]]) : (i32) -> i32
+// CHECK:               assign %[[VAL_11]] : i32 to %[[VAL_4]] : <i32>
+// CHECK:               assign %[[VAL_12]] : i32 to %[[VAL_5]] : <i32>
+// CHECK:             }
+// CHECK:           } while {
+// CHECK:             %[[VAL_13:.*]] = expression %[[VAL_6]] : (!emitc.lvalue<i1>) -> i1 {
+// CHECK:               %[[VAL_14:.*]] = load %[[VAL_6]] : <i1>
+// CHECK:               yield %[[VAL_14]] : i1
+// CHECK:             }
+// CHECK:             yield %[[VAL_13]] : i1
+// CHECK:           }
+// CHECK:           %[[VAL_15:.*]] = emitc.load %[[VAL_2]] : <i32>
+// CHECK:           %[[VAL_16:.*]] = emitc.load %[[VAL_3]] : <i32>
+// CHECK:           return %[[VAL_15]] : i32
+// CHECK:         }
+
+emitc.func @payload_double_use(%arg: i32) -> i32 {
+  %result = add %arg, %arg : (i32, i32) -> i32
+  return %result : 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
+  %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) %arg1 : i32
+  } do {
+  ^bb0(%arg2: i32):
+    %next_arg1 = emitc.call @payload_double_use(%arg2) : (i32) -> i32
+    scf.yield %next_arg1 : i32
+  }
+  return %res : i32
+}
+// 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:   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 "1.0" : i32
+// CHECK:           %[[VAL_2:.*]] = emitc.literal "10.0" : i32
+// CHECK:           %[[VAL_3:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
+// CHECK:           %[[VAL_4:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
+// CHECK:           emitc.assign %[[VAL_0]] : i32 to %[[VAL_4]] : <i32>
+// CHECK:           %[[VAL_5:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i1>
+// CHECK:           emitc.do {
+// CHECK:             %[[VAL_6:.*]] = load %[[VAL_4]] : <i32>
+// CHECK:             %[[VAL_7:.*]] = call @foo_with_side_effect(%[[VAL_6]], %[[ARG0]]) : (i32, !emitc.ptr<i32>) -> i32
+// CHECK:             %[[VAL_8:.*]] = add %[[VAL_7]], %[[VAL_7]] : (i32, i32) -> i32
+// CHECK:             %[[VAL_9:.*]] = add %[[VAL_6]], %[[VAL_8]] : (i32, i32) -> i32
+// CHECK:             %[[VAL_10:.*]] = cmp lt, %[[VAL_9]], %[[VAL_2]] : (i32, i32) -> i1
+// CHECK:             assign %[[VAL_6]] : i32 to %[[VAL_3]] : <i32>
+// CHECK:             assign %[[VAL_10]] : i1 to %[[VAL_5]] : <i1>
+// CHECK:             if %[[VAL_10]] {
+// CHECK:               %[[VAL_11:.*]] = call @payload_double_use(%[[VAL_6]]) : (i32) -> i32
+// CHECK:               assign %[[VAL_11]] : i32 to %[[VAL_4]] : <i32>
+// CHECK:             }
+// CHECK:           } while {
+// CHECK:             %[[VAL_12:.*]] = expression %[[VAL_5]] : (!emitc.lvalue<i1>) -> i1 {
+// CHECK:               %[[VAL_13:.*]] = load %[[VAL_5]] : <i1>
+// CHECK:               yield %[[VAL_13]] : i1
+// CHECK:             }
+// CHECK:             yield %[[VAL_12]] : i1
+// CHECK:           }
+// CHECK:           %[[VAL_14:.*]] = emitc.load %[[VAL_3]] : <i32>
+// CHECK:           return %[[VAL_14]] : i32
+// CHECK:         }
+
+emitc.func @payload_empty_after_region() -> i1 {
+  %true = emitc.literal "true" : i1
+  return %true : i1
+}
+
+func.func @empty_after_region() {
+  scf.while () : () -> () {
+    %condition = emitc.call @payload_empty_after_region() : () -> i1
+    scf.condition(%condition)
+  } do {
+  ^bb0():
+    scf.yield
+  }
+  return
+}
+// CHECK-LABEL:   emitc.func @payload_empty_after_region() -> i1 {
+// CHECK:           %[[VAL_0:.*]] = literal "true" : i1
+// CHECK:           return %[[VAL_0]] : i1
+// CHECK:         }
+
+// CHECK-LABEL:   func.func @empty_after_region() {
+// CHECK:           %[[VAL_0:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i1>
+// CHECK:           emitc.do {
+// CHECK:             %[[VAL_1:.*]] = call @payload_empty_after_region() : () -> i1
+// CHECK:             assign %[[VAL_1]] : i1 to %[[VAL_0]] : <i1>
+// CHECK:           } while {
+// CHECK:             %[[VAL_2:.*]] = expression %[[VAL_0]] : (!emitc.lvalue<i1>) -> i1 {
+// CHECK:               %[[VAL_3:.*]] = load %[[VAL_0]] : <i1>
+// CHECK:               yield %[[VAL_3]] : i1
+// CHECK:             }
+// CHECK:             yield %[[VAL_2]] : i1
+// CHECK:           }
+// CHECK:           return
+// CHECK:         }
+
+emitc.func @payload_
diff erent_number_of_vars(%arg0: i32) -> i32 {
+  %0 = add %arg0, %arg0 : (i32, i32) -> i32
+  return %0 : i32
+}
+func.func @
diff erent_number_of_vars() -> (i32, i32) {
+  %init = emitc.literal "1.0" : i32
+  %var  = emitc.literal "7.0" : i32
+  %exit = emitc.literal "10.0" : i32
+  %res, %res2 = scf.while (%arg1 = %init) : (i32) -> (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, %sum : i32, i32
+  } do {
+  ^bb0(%arg2: i32, %arg3 : i32):
+    %next_arg1 = emitc.call @payload_
diff erent_number_of_vars(%arg2) : (i32) -> i32
+    scf.yield %next_arg1 : i32
+  }
+  return %res, %res2 : i32, i32
+}
+// CHECK-LABEL:   emitc.func @payload_
diff erent_number_of_vars(
+// CHECK-SAME:      %[[ARG0:.*]]: i32) -> i32 {
+// CHECK:           %[[VAL_0:.*]] = add %[[ARG0]], %[[ARG0]] : (i32, i32) -> i32
+// CHECK:           return %[[VAL_0]] : i32
+// CHECK:         }
+
+// CHECK-LABEL:   func.func @
diff erent_number_of_vars() -> (i32, i32) {
+// CHECK:           %[[VAL_0:.*]] = emitc.literal "1.0" : i32
+// CHECK:           %[[VAL_1:.*]] = emitc.literal "7.0" : i32
+// CHECK:           %[[VAL_2:.*]] = emitc.literal "10.0" : i32
+// CHECK:           %[[VAL_3:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
+// CHECK:           %[[VAL_4:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
+// CHECK:           %[[VAL_5:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
+// CHECK:           emitc.assign %[[VAL_0]] : i32 to %[[VAL_5]] : <i32>
+// CHECK:           %[[VAL_6:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i1>
+// CHECK:           emitc.do {
+// CHECK:             %[[VAL_7:.*]] = load %[[VAL_5]] : <i32>
+// CHECK:             %[[VAL_8:.*]] = add %[[VAL_7]], %[[VAL_1]] : (i32, i32) -> i32
+// CHECK:             %[[VAL_9:.*]] = cmp lt, %[[VAL_8]], %[[VAL_2]] : (i32, i32) -> i1
+// CHECK:             %[[VAL_10:.*]] = add %[[VAL_7]], %[[VAL_7]] : (i32, i32) -> i32
+// CHECK:             assign %[[VAL_10]] : i32 to %[[VAL_3]] : <i32>
+// CHECK:             assign %[[VAL_8]] : i32 to %[[VAL_4]] : <i32>
+// CHECK:             assign %[[VAL_9]] : i1 to %[[VAL_6]] : <i1>
+// CHECK:             if %[[VAL_9]] {
+// CHECK:               %[[VAL_11:.*]] = call @payload_
diff erent_number_of_vars(%[[VAL_10]]) : (i32) -> i32
+// CHECK:               assign %[[VAL_11]] : i32 to %[[VAL_5]] : <i32>
+// CHECK:             }
+// CHECK:           } while {
+// CHECK:             %[[VAL_12:.*]] = expression %[[VAL_6]] : (!emitc.lvalue<i1>) -> i1 {
+// CHECK:               %[[VAL_13:.*]] = load %[[VAL_6]] : <i1>
+// CHECK:               yield %[[VAL_13]] : i1
+// CHECK:             }
+// CHECK:             yield %[[VAL_12]] : i1
+// CHECK:           }
+// CHECK:           %[[VAL_14:.*]] = emitc.load %[[VAL_3]] : <i32>
+// CHECK:           %[[VAL_15:.*]] = emitc.load %[[VAL_4]] : <i32>
+// CHECK:           return %[[VAL_14]], %[[VAL_15]] : i32, i32
+// CHECK:         }

diff  --git a/mlir/test/Dialect/EmitC/invalid_ops.mlir b/mlir/test/Dialect/EmitC/invalid_ops.mlir
index f4c15f50053a8..5f594fb08c43f 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.yield
   return
 }
@@ -729,3 +729,150 @@ emitc.class @testClass {
     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 %1, %2 : (i32, i32) -> i1 {
+      %cmp = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
+      emitc.yield %cmp : i1
+    }
+
+    %3 = emitc.literal "3" : i32
+    emitc.yield %r : i1
+  }
+
+  return
+}
+
+// -----
+
+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
+}
+
+// -----
+
+func.func @test_do(%arg0 : !emitc.ptr<i32>) {
+  %1 = emitc.literal "1" : i32
+  %2 = emitc.literal "2" : i32
+
+  // 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 {
+    %r = emitc.expression %1, %2 : (i32, i32) -> i32 {
+      %add = emitc.add %1, %2 : (i32, i32) -> i32
+      emitc.yield %add : i32
+    }
+
+    emitc.yield %r : i32
+  }
+
+  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 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 {
+    %r1 = emitc.expression %1, %2 : (i32, i32) -> i1 {
+      %cmp = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
+      emitc.yield %cmp : i1
+    }
+
+    %r2 = emitc.expression %1, %2 : (i32, i32) -> i32 {
+      %add = emitc.add %1, %2 : (i32, i32) -> i32
+      emitc.yield %add : i32
+    }
+  }
+
+  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 expected condition region to return 1 value, but it returns 0 values}}
+  emitc.do {
+    emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
+  } while {
+    %r = emitc.expression %1, %2 : (i32, i32) -> i1 {
+      %cmp = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
+      emitc.yield %cmp : i1
+    }
+
+    emitc.yield
+  }
+
+  return
+}
+
+// -----
+
+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 %1, %2 : (i32, i32) -> i1 {
+      %cmp = emitc.cmp eq, %1, %2 : (i32, i32) -> i1
+      emitc.yield %cmp : i1
+    }
+
+    emitc.yield %true: i1
+  }
+
+  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 body region must not contain terminator}}
+  emitc.do {
+    emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32>
+    emitc.yield
+  } while {
+    %r = emitc.expression %1, %2 : (i32, i32) -> 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 84c9b65d775d2..1259748dfce84 100644
--- a/mlir/test/Dialect/EmitC/ops.mlir
+++ b/mlir/test/Dialect/EmitC/ops.mlir
@@ -335,3 +335,23 @@ emitc.class final @finalClass {
     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 {
+    %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
+    }
+    
+    emitc.yield %r : i1
+  }
+
+  return
+}

diff  --git a/mlir/test/Target/Cpp/do.mlir b/mlir/test/Target/Cpp/do.mlir
new file mode 100644
index 0000000000000..38cbc8110a70c
--- /dev/null
+++ b/mlir/test/Target/Cpp/do.mlir
@@ -0,0 +1,168 @@
+// RUN: mlir-translate -mlir-to-cpp %s | FileCheck %s -check-prefix=CPP-DEFAULT
+
+
+// CPP-DEFAULT-LABEL: void emitc_do(
+// CPP-DEFAULT:         int32_t* [[VAL_1:v[0-9]+]]) {
+// CPP-DEFAULT:         int32_t [[VAL_2:v[0-9]+]] = 0;
+// CPP-DEFAULT:         do {
+// CPP-DEFAULT:           printf("%d", *[[VAL_1]]);
+// CPP-DEFAULT:           int32_t [[VAL_3:v[0-9]+]] = [[VAL_2]];
+// CPP-DEFAULT:           int32_t [[VAL_4:v[0-9]+]] = [[VAL_3]] + 1;
+// CPP-DEFAULT:           [[VAL_2]] = [[VAL_4]];
+// CPP-DEFAULT:         } while ([[VAL_2]] <= 10);
+// CPP-DEFAULT:         return;
+// CPP-DEFAULT:       }
+
+emitc.func @emitc_do(%arg0 : !emitc.ptr<i32>) {
+  %var = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue<i32>
+  %0 = literal "10" : i32
+  %1 = literal "1" : 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 {
+    %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
+    }
+    
+    yield %r : i1
+  }
+
+  return
+}
+
+
+// CPP-DEFAULT-LABEL: void emitc_do_with_expression(
+// CPP-DEFAULT:         int32_t* [[VAL_1:v[0-9]+]]) {
+// CPP-DEFAULT:         int32_t [[VAL_2:v[0-9]+]] = 0;
+// CPP-DEFAULT:         int32_t [[VAL_3:v[0-9]+]] = 10 + 1;
+// CPP-DEFAULT:         do {
+// CPP-DEFAULT:           printf("%d", *[[VAL_1]]);
+// CPP-DEFAULT:           int32_t [[VAL_4:v[0-9]+]] = [[VAL_2]];
+// CPP-DEFAULT:           int32_t [[VAL_5:v[0-9]+]] = [[VAL_4]] + 1;
+// CPP-DEFAULT:           [[VAL_2]] = [[VAL_5]];
+// CPP-DEFAULT:         } while ([[VAL_2]] <= [[VAL_3]]);
+// 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 = literal "10" : i32
+  %1 = literal "1" : i32
+
+  %add = expression %0, %1 : (i32, i32) -> i32 {
+    %add = add %0, %1 : (i32, i32) -> i32
+    yield %add : 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 {
+    %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
+    }
+
+    yield %r : i1
+  }
+
+  return
+}
+
+
+// CPP-DEFAULT-LABEL: void emitc_double_do()
+// CPP-DEFAULT:         int32_t [[VAL_1:v[0-9]+]] = 0;
+// CPP-DEFAULT:         int32_t [[VAL_2:v[0-9]+]] = 0;
+// CPP-DEFAULT:         do {
+// CPP-DEFAULT:           int32_t [[VAL_3:v[0-9]+]] = [[VAL_1]];
+// CPP-DEFAULT:           do {
+// CPP-DEFAULT:             int32_t [[VAL_4:v[0-9]+]] = [[VAL_2]];
+// CPP-DEFAULT:             printf("i = %d, j = %d", [[VAL_3]], [[VAL_4]]);
+// CPP-DEFAULT:             int32_t [[VAL_5:v[0-9]+]] = [[VAL_4]] + 1;
+// CPP-DEFAULT:             [[VAL_2]] = [[VAL_5]];
+// CPP-DEFAULT:           } while ([[VAL_2]] <= 5);
+// CPP-DEFAULT:           int32_t [[VAL_6:v[0-9]+]] = [[VAL_3]] + 1;
+// CPP-DEFAULT:           [[VAL_1]] = [[VAL_6]];
+// CPP-DEFAULT:         } while ([[VAL_1]] <= 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 %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
+      }
+      
+      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 %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
+    }
+    
+    yield %r : i1
+  }
+
+  return
+}
+
+
+// CPP-DEFAULT-LABEL: bool payload_do_with_empty_body(
+// CPP-DEFAULT:         int32_t [[VAL_1:v[0-9]+]], int32_t [[VAL_2:v[0-9]+]]) {
+// CPP-DEFAULT:         bool [[VAL_3:v[0-9]+]] = [[VAL_1]] < [[VAL_2]];
+// CPP-DEFAULT:         return [[VAL_3]];
+// CPP-DEFAULT:       }
+// CPP-DEFAULT:       void emitc_do_with_empty_body(
+// CPP-DEFAULT:         int32_t [[VAL_1:v[0-9]+]], int32_t [[VAL_2:v[0-9]+]]) {
+// CPP-DEFAULT:         do {
+// CPP-DEFAULT:         } while (payload_do_with_empty_body([[VAL_1]], [[VAL_2]]));
+// CPP-DEFAULT:         return;
+// CPP-DEFAULT:       }
+
+emitc.func @payload_do_with_empty_body(%1 : i32, %2 : i32) -> i1 {
+  %cmp = emitc.cmp lt, %1, %2 : (i32, i32) -> i1
+  return %cmp : i1
+}
+func.func @emitc_do_with_empty_body(%arg1 : i32, %arg2 : i32) {
+  emitc.do {
+  } while {
+    %r = emitc.expression %arg1, %arg2 : (i32, i32) -> i1 {
+      %call = emitc.call @payload_do_with_empty_body(%arg1, %arg2) : (i32, i32) -> i1
+      emitc.yield %call : i1
+    }
+    emitc.yield %r: i1
+  }
+
+  return
+}


        


More information about the Mlir-commits mailing list