diff --git a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td index 721f9f6b320ad..5971c31876689 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 @@ -1727,4 +1727,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 + %end = emitc.literal "10" : i32 + %step = emitc.literal "1" : i32 + + emitc.do { + // Print current value + %val = emitc.load %counter : !emitc.lvalue + 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) -> () + } while { + %condition = emitc.expression %counter, %end : (!emitc.lvalue, i32) -> i1 { + %current = emitc.load %counter : !emitc.lvalue + %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 loadValues(const SmallVector &variables, +SmallVector loadValues(ArrayRef variables, PatternRewriter &rewriter, Location loc) { return llvm::map_to_vector<>(variables, [&](Value var) { Type type = cast(var.getType()).getValueType(); @@ -116,16 +117,15 @@ SmallVector loadValues(const SmallVector &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 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 { + 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 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 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 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 &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( + loc, emitc::LValueType::get(convertedType), noInit); + rewriter.create(loc, var.getResult(), init); + loopVars.push_back(var); + } + + return success(); + } + + // Lower scf.while to emitc.do. + LogicalResult lowerDoWhile(WhileOp whileOp, ArrayRef loopVars, + ArrayRef 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(loc, emitc::LValueType::get(i1Type), + emitc::OpaqueAttr::get(context, "")); + Value conditionVal = globalCondition.getResult(); + + auto loweredDo = rewriter.create(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 replacingValues = loadValues(loopVars, rewriter, loc); + rewriter.mergeBlocks(beforeBlock, bodyBlock, replacingValues); + + Operation *condTerminator = + loweredDo.getBodyRegion().back().getTerminator(); + scf::ConditionOp condOp = cast(condTerminator); + rewriter.setInsertionPoint(condOp); + + // Update result variables with values from scf::condition. + SmallVector 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(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(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 afterReplacingValues; + for (Value arg : condOp.getArgs()) + afterReplacingValues.push_back(rewriter.getRemappedValue(arg)); + + rewriter.mergeBlocks(afterBlock, ifBodyBlock, afterReplacingValues); + + if (failed(lowerYield(whileOp, loopVars, rewriter, + cast(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( + 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(loc, i1Type, exprBlock->getArgument(0)); + rewriter.create(loc, cond); + + // Yield the expression as the condition region result. + rewriter.setInsertionPointToEnd(condBlock); + rewriter.create(loc, exprOp); + + return success(); + } +}; + void mlir::populateSCFToEmitCConversionPatterns(RewritePatternSet &patterns, TypeConverter &typeConverter) { patterns.add(typeConverter, patterns.getContext()); patterns.add(typeConverter, patterns.getContext()); patterns.add(typeConverter, patterns.getContext()); + patterns.add(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(); + target + .addIllegalOp(); 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(containingOp) && result && containingOp->getNumResults() != 1) return emitOpError() << "yields a value not returned by parent"; - if (!result && containingOp->getNumResults() != 0) + if (!isa(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(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(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(condBlock.back()); + if (failed(emitter.emitExpression( + cast(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( - op); + !isa(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 +// CHECK: %[[VAL_4:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue +// CHECK: emitc.assign %[[VAL_0]] : i32 to %[[VAL_4]] : +// CHECK: %[[VAL_5:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue +// CHECK: emitc.do { +// CHECK: %[[VAL_6:.*]] = load %[[VAL_4]] : +// 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]] : +// CHECK: assign %[[VAL_8]] : i1 to %[[VAL_5]] : +// CHECK: if %[[VAL_8]] { +// CHECK: %[[VAL_10:.*]] = call @payload_one_result(%[[VAL_9]]) : (i32) -> i32 +// CHECK: assign %[[VAL_10]] : i32 to %[[VAL_4]] : +// CHECK: } +// CHECK: } while { +// CHECK: %[[VAL_11:.*]] = expression %[[VAL_5]] : (!emitc.lvalue) -> i1 { +// CHECK: %[[VAL_12:.*]] = load %[[VAL_5]] : +// CHECK: yield %[[VAL_12]] : i1 +// CHECK: } +// CHECK: yield %[[VAL_11]] : i1 +// CHECK: } +// CHECK: %[[VAL_13:.*]] = emitc.load %[[VAL_3]] : +// 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 +// CHECK: %[[VAL_3:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue +// CHECK: %[[VAL_4:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue +// CHECK: emitc.assign %[[VAL_0]] : i32 to %[[VAL_4]] : +// CHECK: %[[VAL_5:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue +// CHECK: emitc.assign %[[VAL_0]] : i32 to %[[VAL_5]] : +// CHECK: %[[VAL_6:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue +// CHECK: emitc.do { +// CHECK: %[[VAL_7:.*]] = load %[[VAL_4]] : +// CHECK: %[[VAL_8:.*]] = load %[[VAL_5]] : +// 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]] : +// CHECK: assign %[[VAL_8]] : i32 to %[[VAL_3]] : +// CHECK: assign %[[VAL_10]] : i1 to %[[VAL_6]] : +// 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]] : +// CHECK: assign %[[VAL_12]] : i32 to %[[VAL_5]] : +// CHECK: } +// CHECK: } while { +// CHECK: %[[VAL_13:.*]] = expression %[[VAL_6]] : (!emitc.lvalue) -> i1 { +// CHECK: %[[VAL_14:.*]] = load %[[VAL_6]] : +// CHECK: yield %[[VAL_14]] : i1 +// CHECK: } +// CHECK: yield %[[VAL_13]] : i1 +// CHECK: } +// CHECK: %[[VAL_15:.*]] = emitc.load %[[VAL_2]] : +// CHECK: %[[VAL_16:.*]] = emitc.load %[[VAL_3]] : +// 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 { + %sum = add %arg, %arg : (i32, i32) -> i32 + emitc.verbatim "{}[0] = {};" args %p, %sum : !emitc.ptr, i32 + return %sum : i32 +} + +func.func @double_use(%p : !emitc.ptr) -> 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 + %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 { +// CHECK: %[[VAL_0:.*]] = add %[[ARG0]], %[[ARG0]] : (i32, i32) -> i32 +// CHECK: verbatim "{}[0] = {};" args %[[ARG1]], %[[VAL_0]] : !emitc.ptr, i32 +// CHECK: return %[[VAL_0]] : i32 +// CHECK: } + +// CHECK-LABEL: func.func @double_use( +// CHECK-SAME: %[[ARG0:.*]]: !emitc.ptr) -> 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 +// CHECK: %[[VAL_4:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue +// CHECK: emitc.assign %[[VAL_0]] : i32 to %[[VAL_4]] : +// CHECK: %[[VAL_5:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue +// CHECK: emitc.do { +// CHECK: %[[VAL_6:.*]] = load %[[VAL_4]] : +// CHECK: %[[VAL_7:.*]] = call @foo_with_side_effect(%[[VAL_6]], %[[ARG0]]) : (i32, !emitc.ptr) -> 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]] : +// CHECK: assign %[[VAL_10]] : i1 to %[[VAL_5]] : +// CHECK: if %[[VAL_10]] { +// CHECK: %[[VAL_11:.*]] = call @payload_double_use(%[[VAL_6]]) : (i32) -> i32 +// CHECK: assign %[[VAL_11]] : i32 to %[[VAL_4]] : +// CHECK: } +// CHECK: } while { +// CHECK: %[[VAL_12:.*]] = expression %[[VAL_5]] : (!emitc.lvalue) -> i1 { +// CHECK: %[[VAL_13:.*]] = load %[[VAL_5]] : +// CHECK: yield %[[VAL_13]] : i1 +// CHECK: } +// CHECK: yield %[[VAL_12]] : i1 +// CHECK: } +// CHECK: %[[VAL_14:.*]] = emitc.load %[[VAL_3]] : +// 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 +// CHECK: emitc.do { +// CHECK: %[[VAL_1:.*]] = call @payload_empty_after_region() : () -> i1 +// CHECK: assign %[[VAL_1]] : i1 to %[[VAL_0]] : +// CHECK: } while { +// CHECK: %[[VAL_2:.*]] = expression %[[VAL_0]] : (!emitc.lvalue) -> i1 { +// CHECK: %[[VAL_3:.*]] = load %[[VAL_0]] : +// CHECK: yield %[[VAL_3]] : i1 +// CHECK: } +// CHECK: yield %[[VAL_2]] : i1 +// CHECK: } +// CHECK: return +// CHECK: } + +emitc.func @payload_different_number_of_vars(%arg0: i32) -> i32 { + %0 = add %arg0, %arg0 : (i32, i32) -> i32 + return %0 : i32 +} +func.func @different_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_different_number_of_vars(%arg2) : (i32) -> i32 + scf.yield %next_arg1 : i32 + } + return %res, %res2 : i32, i32 +} +// CHECK-LABEL: emitc.func @payload_different_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 @different_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 +// CHECK: %[[VAL_4:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue +// CHECK: %[[VAL_5:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue +// CHECK: emitc.assign %[[VAL_0]] : i32 to %[[VAL_5]] : +// CHECK: %[[VAL_6:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue +// CHECK: emitc.do { +// CHECK: %[[VAL_7:.*]] = load %[[VAL_5]] : +// 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]] : +// CHECK: assign %[[VAL_8]] : i32 to %[[VAL_4]] : +// CHECK: assign %[[VAL_9]] : i1 to %[[VAL_6]] : +// CHECK: if %[[VAL_9]] { +// CHECK: %[[VAL_11:.*]] = call @payload_different_number_of_vars(%[[VAL_10]]) : (i32) -> i32 +// CHECK: assign %[[VAL_11]] : i32 to %[[VAL_5]] : +// CHECK: } +// CHECK: } while { +// CHECK: %[[VAL_12:.*]] = expression %[[VAL_6]] : (!emitc.lvalue) -> i1 { +// CHECK: %[[VAL_13:.*]] = load %[[VAL_6]] : +// CHECK: yield %[[VAL_13]] : i1 +// CHECK: } +// CHECK: yield %[[VAL_12]] : i1 +// CHECK: } +// CHECK: %[[VAL_14:.*]] = emitc.load %[[VAL_3]] : +// CHECK: %[[VAL_15:.*]] = emitc.load %[[VAL_4]] : +// 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, %arg1: !emitc.ptr) { // ----- 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) { + %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 + } 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) { + // 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 + } while { + %true = emitc.literal "true" : i1 + emitc.yield %true : i1 + } + + return +} + +// ----- + +func.func @test_do(%arg0 : !emitc.ptr) { + %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 + } 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) { + %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 + } 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) { + %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 + } 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) { + %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 + } 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) { + %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 + 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) { + %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 + } 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) { + %var = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue + %0 = literal "10" : i32 + %1 = literal "1" : i32 + + do { + verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr + %var_load = load %var : + %tmp_add = add %var_load, %1 : (i32, i32) -> i32 + "emitc.assign"(%var, %tmp_add) : (!emitc.lvalue, i32) -> () + } while { + %r = expression %var, %0 : (!emitc.lvalue, i32) -> i1 { + %var_load = load %var : + %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) { + %var = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue + %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 + %var_load = load %var : + %tmp_add = add %var_load, %1 : (i32, i32) -> i32 + "emitc.assign"(%var, %tmp_add) : (!emitc.lvalue, i32) -> () + } while { + %r = expression %var, %add : (!emitc.lvalue, i32) -> i1 { + %var_load = load %var : + %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 + %var_2 = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue + + %step = literal "1" : i32 + %end_1 = literal "3" : i32 + %end_2 = literal "5" : i32 + + do { + %var_1_load = load %var_1 : + + do { + %var_2_load = load %var_2 : + 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) -> () + } while { + %r = expression %var_2, %end_2 : (!emitc.lvalue, i32) -> i1 { + %var_2_load = load %var_2 : + %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) -> () + } while { + %r = expression %var_1, %end_1 : (!emitc.lvalue, i32) -> i1 { + %var_1_load = load %var_1 : + %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 +}