From c7f754e8aca15d3612922d0dbe8cb2343b2533a9 Mon Sep 17 00:00:00 2001 From: Vlad Lazar Date: Thu, 5 Jun 2025 19:41:52 +0300 Subject: [PATCH 01/15] [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 | 119 ++++++++- 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, 1210 insertions(+), 16 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 721f9f6b320ad..133aefccd59ad 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", "WhileOp"]>]> { let summary = "Block termination operation"; let description = [{ The `emitc.yield` terminates its parent EmitC op's region, optionally yielding @@ -1727,4 +1727,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) { + %var = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue + %0 = emitc.literal "10" : i32 + %1 = emitc.literal "1" : i32 + + emitc.while { + %var_load = load %var : + %res = emitc.cmp le, %var_load, %0 : (i32, i32) -> i1 + emitc.yield %res : i1 + } do { + emitc.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) -> () + } + + 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) { + %var = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue + %0 = emitc.literal "10" : i32 + %1 = emitc.literal "1" : i32 + + emitc.do { + emitc.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 { + %var_load = load %var : + %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 { + 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 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(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 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 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 &outVars, + Location loc, MLIRContext *context) { + emitc::OpaqueAttr noInit = emitc::OpaqueAttr::get(context, ""); + + for (Value init : whileOp.getInits()) { + emitc::VariableOp var = rewriter.create( + loc, emitc::LValueType::get(init.getType()), noInit); + rewriter.create(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 vars, + ConversionPatternRewriter &rewriter, + Location loc) const { + rewriter.setInsertionPointToStart(block); + + for (auto [arg, var] : llvm::zip(block->getArguments(), vars)) { + Type loadedType = cast(var.getType()).getValueType(); + Value load = rewriter.create(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 vars, + ConversionPatternRewriter &rewriter, + Location loc) const { + auto yieldOp = cast(terminator); + SmallVector yields(yieldOp.getOperands()); + rewriter.eraseOp(yieldOp); + + rewriter.setInsertionPointToEnd(yieldOp->getBlock()); + for (auto [var, val] : llvm::zip(vars, yields)) + rewriter.create(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 + finalizeLoopResults(ArrayRef resultVariables, + ArrayRef 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(var.getType()).getValueType(); + Value load = rewriter.create(loc, loadedType, var); + rewriter.create(loc, resultVar, load); + } + + // Replace op with loaded values to integrate with converted SSA graph. + SmallVector finalResults; + for (Value resultVar : resultVariables) { + Type loadedType = + cast(resultVar.getType()).getValueType(); + finalResults.push_back( + rewriter.create(loc, loadedType, resultVar)); + } + + return finalResults; + } + + // Direct lowering to emitc.while when condition arguments match region + // inputs. + LogicalResult lowerWhile(WhileOp whileOp, ArrayRef vars, + MLIRContext *context, + ConversionPatternRewriter &rewriter, + Location loc) const { + auto loweredWhile = rewriter.create(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(condTerminator); + rewriter.setInsertionPoint(condOp); + Value condition = rewriter.getRemappedValue(condOp.getCondition()); + rewriter.create(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 vars, + MLIRContext *context, + ConversionPatternRewriter &rewriter, + Location loc) const { + 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); + + // 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(condTerminator); + rewriter.setInsertionPoint(condOp); + Value condition = rewriter.getRemappedValue(condOp.getCondition()); + rewriter.create(loc, conditionVal, condition); + + // Wrap body region in conditional to preserve scf semantics. + auto ifOp = rewriter.create(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(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(loc, i1Type, conditionVal); + rewriter.create(loc, cond); + + 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 +590,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..9b3ea203cad07 100644 --- a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp +++ b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp @@ -974,10 +974,12 @@ LogicalResult emitc::YieldOp::verify() { Value result = getResult(); Operation *containingOp = getOperation()->getParentOp(); - if (result && containingOp->getNumResults() != 1) + if (result && containingOp->getNumResults() != 1 && + !isa(containingOp)) return emitOpError() << "yields a value not returned by parent"; - if (!result && containingOp->getNumResults() != 0) + if (!result && containingOp->getNumResults() != 0 && + !isa(containingOp)) return emitOpError() << "does not yield a value to be returned by parent"; return success(); @@ -1561,6 +1563,116 @@ LogicalResult GetFieldOp::verifySymbolUses(SymbolTableCollection &symbolTable) { return success(); } +//===----------------------------------------------------------------------===// +// Common functions for WhileOp and DoOp +//===----------------------------------------------------------------------===// + +static Operation *getRootOpFromLoopCondition(Region &condRegion) { + auto yieldOp = cast(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()) + return op.emitOpError( + "expected all operations in condition region must implement " + "CExpression trait, but ") + << inner.getName() << " does not"; + } + + auto condYield = dyn_cast(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(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 a5bd80e9d6b8b..dcc2bd6e3f07b 100644 --- a/mlir/lib/Target/Cpp/TranslateToCpp.cpp +++ b/mlir/lib/Target/Cpp/TranslateToCpp.cpp @@ -179,6 +179,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); @@ -201,6 +207,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); @@ -258,7 +266,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. @@ -322,7 +330,7 @@ struct CppEmitter { unsigned int valueCount{0}; /// State of the current expression being emitted. - ExpressionOp emittedExpression; + Operation *emittedExpression = nullptr; SmallVector emittedExpressionPrecedence; void pushExpressionPrecedence(int precedence) { @@ -557,6 +565,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(); @@ -1514,6 +1532,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 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(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(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 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(); @@ -1711,13 +1807,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. @@ -1765,9 +1862,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..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 +// CHECK: emitc.assign %[[VAL_0]] : i32 to %[[VAL_3]] : +// CHECK: emitc.while { +// CHECK: %[[VAL_4:.*]] = load %[[VAL_3]] : +// 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]] : +// CHECK: %[[VAL_8:.*]] = call @payload_whileOp(%[[VAL_7]]) : (i32) -> i32 +// CHECK: assign %[[VAL_8]] : i32 to %[[VAL_3]] : +// CHECK: } +// CHECK: %[[VAL_9:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue +// CHECK: %[[VAL_10:.*]] = emitc.load %[[VAL_3]] : +// CHECK: emitc.assign %[[VAL_10]] : i32 to %[[VAL_9]] : +// CHECK: %[[VAL_11:.*]] = emitc.load %[[VAL_9]] : +// 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 +// CHECK: emitc.assign %[[VAL_0]] : i32 to %[[VAL_3]] : +// CHECK: %[[VAL_4:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue +// CHECK: emitc.do { +// CHECK: %[[VAL_5:.*]] = load %[[VAL_3]] : +// 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]] : +// CHECK: if %[[VAL_7]] { +// CHECK: %[[VAL_9:.*]] = call @payload_doOp(%[[VAL_8]]) : (i32) -> i32 +// CHECK: assign %[[VAL_9]] : i32 to %[[VAL_3]] : +// CHECK: } +// CHECK: } while { +// CHECK: %[[VAL_10:.*]] = load %[[VAL_4]] : +// CHECK: yield %[[VAL_10]] : i1 +// CHECK: } +// CHECK: %[[VAL_11:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue +// CHECK: %[[VAL_12:.*]] = emitc.load %[[VAL_3]] : +// CHECK: emitc.assign %[[VAL_12]] : i32 to %[[VAL_11]] : +// CHECK: %[[VAL_13:.*]] = emitc.load %[[VAL_11]] : +// 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 +// CHECK: emitc.assign %[[VAL_0]] : i32 to %[[VAL_2]] : +// CHECK: %[[VAL_3:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue +// CHECK: emitc.assign %[[VAL_0]] : i32 to %[[VAL_3]] : +// CHECK: emitc.while { +// CHECK: %[[VAL_4:.*]] = load %[[VAL_2]] : +// CHECK: %[[VAL_5:.*]] = load %[[VAL_3]] : +// 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]] : +// CHECK: %[[VAL_9:.*]] = load %[[VAL_3]] : +// 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]] : +// CHECK: assign %[[VAL_11]] : i32 to %[[VAL_3]] : +// CHECK: } +// CHECK: %[[VAL_12:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue +// CHECK: %[[VAL_13:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue +// CHECK: %[[VAL_14:.*]] = emitc.load %[[VAL_2]] : +// CHECK: emitc.assign %[[VAL_14]] : i32 to %[[VAL_12]] : +// CHECK: %[[VAL_15:.*]] = emitc.load %[[VAL_3]] : +// CHECK: emitc.assign %[[VAL_15]] : i32 to %[[VAL_13]] : +// CHECK: %[[VAL_16:.*]] = emitc.load %[[VAL_12]] : +// CHECK: %[[VAL_17:.*]] = emitc.load %[[VAL_13]] : +// 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 +// CHECK: emitc.assign %[[VAL_0]] : i32 to %[[VAL_2]] : +// CHECK: %[[VAL_3:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue +// CHECK: emitc.assign %[[VAL_0]] : i32 to %[[VAL_3]] : +// CHECK: %[[VAL_4:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue +// CHECK: emitc.do { +// CHECK: %[[VAL_5:.*]] = load %[[VAL_2]] : +// CHECK: %[[VAL_6:.*]] = load %[[VAL_3]] : +// 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]] : +// 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]] : +// CHECK: assign %[[VAL_10]] : i32 to %[[VAL_3]] : +// CHECK: } +// CHECK: } while { +// CHECK: %[[VAL_11:.*]] = load %[[VAL_4]] : +// CHECK: yield %[[VAL_11]] : i1 +// CHECK: } +// CHECK: %[[VAL_12:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue +// CHECK: %[[VAL_13:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue +// CHECK: %[[VAL_14:.*]] = emitc.load %[[VAL_2]] : +// CHECK: emitc.assign %[[VAL_14]] : i32 to %[[VAL_12]] : +// CHECK: %[[VAL_15:.*]] = emitc.load %[[VAL_3]] : +// CHECK: emitc.assign %[[VAL_15]] : i32 to %[[VAL_13]] : +// CHECK: %[[VAL_16:.*]] = emitc.load %[[VAL_12]] : +// CHECK: %[[VAL_17:.*]] = emitc.load %[[VAL_13]] : +// 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 f4c15f50053a8..4644e71a5101e 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.while'}} emitc.yield return } @@ -729,3 +729,197 @@ emitc.class @testClass { return } } + +// ----- + +func.func @test_while(%arg0 : !emitc.ptr) { + // expected-error @+1 {{'emitc.while' op condition region cannot be empty}} + emitc.while { + } do { + emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr + } + + return +} + +// ----- + +emitc.func @test_while(%arg0 : !emitc.ptr) { + %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 + } + + return +} + +// ----- + +func.func @test_while(%arg0 : !emitc.ptr) { + %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 + } + + return +} + +// ----- + +func.func @test_while(%arg0 : !emitc.ptr) { + %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 + } + + 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) { + %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 + %4 = emitc.add %1, %2 : (i32, i32) -> i32 + emitc.yield %4 : i32 + } + + return +} + +// ----- + +func.func @test_do_while(%arg0 : !emitc.ptr) { + // expected-error @+1 {{'emitc.do' op condition region cannot be empty}} + emitc.do { + emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr + } while { + } + + return +} + +// ----- + +emitc.func @test_do_while(%arg0 : !emitc.ptr) { + %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 + } 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) { + %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 + } while { + %3 = emitc.cmp eq, %1, %2 : (i32, i32) -> i1 + } + + return +} + +// ----- + +func.func @test_do_while(%arg0 : !emitc.ptr) { + %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 + } 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) { + %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 + %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 84c9b65d775d2..6a41c839399f4 100644 --- a/mlir/test/Dialect/EmitC/ops.mlir +++ b/mlir/test/Dialect/EmitC/ops.mlir @@ -335,3 +335,35 @@ emitc.class final @finalClass { return } } + +func.func @while(%arg0 : !emitc.ptr) { + %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 + } + + 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 { + %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) { + %var = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue + %0 = emitc.literal "10" : i32 + %1 = emitc.literal "1" : i32 + + emitc.do { + emitc.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 { + %var_load = load %var : + %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) { + %var = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue + %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 + %var_load = load %var : + %tmp_add = add %var_load, %1 : (i32, i32) -> i32 + "emitc.assign"(%var, %tmp_add) : (!emitc.lvalue, i32) -> () + } while { + %var_load = load %var : + %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) { + %var = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue + %0 = emitc.literal "10" : i32 + %1 = emitc.literal "1" : i32 + + emitc.while { + %var_load = load %var : + %res = emitc.cmp le, %var_load, %0 : (i32, i32) -> i1 + emitc.yield %res : i1 + } do { + emitc.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) -> () + } + + 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) { + %var = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue + %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 : + %res = emitc.cmp le, %var_load, %add : (i32, i32) -> i1 + emitc.yield %res : i1 + } do { + emitc.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) -> () + } + + return +} From 78712dbdef1c01a4687e6690c9b947acc23f13b1 Mon Sep 17 00:00:00 2001 From: Vlad Lazar Date: Wed, 23 Jul 2025 15:58:05 +0300 Subject: [PATCH 02/15] [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 133aefccd59ad..8f58d671e20bf 100644 --- a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td +++ b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td @@ -1728,63 +1728,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) { - %var = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue - %0 = emitc.literal "10" : i32 - %1 = emitc.literal "1" : i32 + emitc.func @while_example() { + %counter = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue + %end = emitc.literal "10" : i32 + %step = emitc.literal "1" : i32 emitc.while { - %var_load = load %var : - %res = emitc.cmp le, %var_load, %0 : (i32, i32) -> i1 - emitc.yield %res : i1 + %condition = emitc.expression : 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 } do { - emitc.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) -> () - } + // 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) -> () + } 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; @@ -1804,54 +1829,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) { - %var = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue - %0 = emitc.literal "10" : i32 - %1 = emitc.literal "1" : i32 + emitc.func @do_example() { + %counter = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue + %end = emitc.literal "10" : i32 + %step = emitc.literal "1" : i32 emitc.do { - emitc.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) -> () + // 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 { - %var_load = load %var : - %res = emitc.cmp le, %var_load, %0 : (i32, i32) -> i1 - emitc.yield %res : i1 + %condition = emitc.expression : 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 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; } ``` @@ -1859,8 +1911,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 { auto loweredWhile = rewriter.create(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(loc, TypeRange{i1Type}); + Region &exprRegion = exprOp.getBodyRegion(); - Operation *condTerminator = - loweredWhile.getConditionRegion().back().getTerminator(); - auto condOp = cast(condTerminator); - rewriter.setInsertionPoint(condOp); + rewriter.inlineRegionBefore(whileOp.getBefore(), exprRegion, + exprRegion.begin()); + + Block *exprBlock = &exprRegion.front(); + replaceBlockArgsWithVarLoads(exprBlock, vars, rewriter, loc); + + auto condOp = cast(exprBlock->getTerminator()); Value condition = rewriter.getRemappedValue(condOp.getCondition()); - rewriter.create(condOp.getLoc(), condition); - rewriter.eraseOp(condOp); + rewriter.setInsertionPointAfter(condOp); + rewriter.replaceOpWithNewOp(condOp, condition); + + rewriter.setInsertionPointToEnd(condBlock); + rewriter.create(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 { 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(loc, TypeRange{i1Type}); + Block *exprBlock = rewriter.createBlock(&exprOp.getBodyRegion()); + rewriter.setInsertionPointToStart(exprBlock); + Value cond = rewriter.create(loc, i1Type, conditionVal); rewriter.create(loc, cond); + rewriter.setInsertionPointToEnd(condBlock); + rewriter.create(loc, exprOp); + return success(); } }; diff --git a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp index 9b3ea203cad07..f23d076752d5d 100644 --- a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp +++ b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp @@ -1574,37 +1574,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()) - return op.emitOpError( - "expected all operations in condition region must implement " - "CExpression trait, but ") - << inner.getName() << " does not"; - } - auto condYield = dyn_cast(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(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(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(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 dcc2bd6e3f07b..787997191bc77 100644 --- a/mlir/lib/Target/Cpp/TranslateToCpp.cpp +++ b/mlir/lib/Target/Cpp/TranslateToCpp.cpp @@ -179,12 +179,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); @@ -567,12 +561,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(condBlock.back()); + if (failed(emitter.emitExpression( + cast(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(condBlock.back()); + if (failed(emitter.emitExpression( + cast(condYield.getOperand(0).getDefiningOp())))) + return failure(); + + os << ");"; + return success(); } static LogicalResult printOperation(CppEmitter &emitter, emitc::CmpOp cmpOp) { @@ -1532,84 +1565,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 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(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(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 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(); @@ -1807,14 +1762,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 // CHECK: emitc.assign %[[VAL_0]] : i32 to %[[VAL_3]] : // CHECK: emitc.while { -// CHECK: %[[VAL_4:.*]] = load %[[VAL_3]] : -// 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]] : +// 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]] : -// CHECK: %[[VAL_8:.*]] = call @payload_whileOp(%[[VAL_7]]) : (i32) -> i32 -// CHECK: assign %[[VAL_8]] : i32 to %[[VAL_3]] : +// CHECK: %[[VAL_8:.*]] = load %[[VAL_3]] : +// CHECK: %[[VAL_9:.*]] = call @payload_while(%[[VAL_8]]) : (i32) -> i32 +// CHECK: assign %[[VAL_9]] : i32 to %[[VAL_3]] : // CHECK: } -// CHECK: %[[VAL_9:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue -// CHECK: %[[VAL_10:.*]] = emitc.load %[[VAL_3]] : -// CHECK: emitc.assign %[[VAL_10]] : i32 to %[[VAL_9]] : -// CHECK: %[[VAL_11:.*]] = emitc.load %[[VAL_9]] : -// CHECK: return %[[VAL_11]] : i32 +// CHECK: %[[VAL_10:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue +// CHECK: %[[VAL_11:.*]] = emitc.load %[[VAL_3]] : +// CHECK: emitc.assign %[[VAL_11]] : i32 to %[[VAL_10]] : +// CHECK: %[[VAL_12:.*]] = emitc.load %[[VAL_10]] : +// 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]] : // 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]] : // CHECK: } // CHECK: } while { -// CHECK: %[[VAL_10:.*]] = load %[[VAL_4]] : +// CHECK: %[[VAL_10:.*]] = expression : i1 { +// CHECK: %[[VAL_11:.*]] = load %[[VAL_4]] : +// CHECK: yield %[[VAL_11]] : i1 +// CHECK: } // CHECK: yield %[[VAL_10]] : i1 // CHECK: } -// CHECK: %[[VAL_11:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue -// CHECK: %[[VAL_12:.*]] = emitc.load %[[VAL_3]] : -// CHECK: emitc.assign %[[VAL_12]] : i32 to %[[VAL_11]] : -// CHECK: %[[VAL_13:.*]] = emitc.load %[[VAL_11]] : -// CHECK: return %[[VAL_13]] : i32 +// CHECK: %[[VAL_12:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue +// CHECK: %[[VAL_13:.*]] = emitc.load %[[VAL_3]] : +// CHECK: emitc.assign %[[VAL_13]] : i32 to %[[VAL_12]] : +// CHECK: %[[VAL_14:.*]] = emitc.load %[[VAL_12]] : +// 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 @@ -146,36 +152,39 @@ func.func @whileOp_two_results() -> i32 { // CHECK: %[[VAL_3:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue // CHECK: emitc.assign %[[VAL_0]] : i32 to %[[VAL_3]] : // CHECK: emitc.while { -// CHECK: %[[VAL_4:.*]] = load %[[VAL_2]] : -// CHECK: %[[VAL_5:.*]] = load %[[VAL_3]] : -// 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]] : +// CHECK: %[[VAL_6:.*]] = load %[[VAL_3]] : +// 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]] : -// CHECK: %[[VAL_9:.*]] = load %[[VAL_3]] : -// 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]] : -// CHECK: assign %[[VAL_11]] : i32 to %[[VAL_3]] : +// CHECK: %[[VAL_9:.*]] = load %[[VAL_2]] : +// CHECK: %[[VAL_10:.*]] = load %[[VAL_3]] : +// 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]] : +// CHECK: assign %[[VAL_12]] : i32 to %[[VAL_3]] : // CHECK: } -// CHECK: %[[VAL_12:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue // CHECK: %[[VAL_13:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue -// CHECK: %[[VAL_14:.*]] = emitc.load %[[VAL_2]] : -// CHECK: emitc.assign %[[VAL_14]] : i32 to %[[VAL_12]] : -// CHECK: %[[VAL_15:.*]] = emitc.load %[[VAL_3]] : +// CHECK: %[[VAL_14:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue +// CHECK: %[[VAL_15:.*]] = emitc.load %[[VAL_2]] : // CHECK: emitc.assign %[[VAL_15]] : i32 to %[[VAL_13]] : -// CHECK: %[[VAL_16:.*]] = emitc.load %[[VAL_12]] : +// CHECK: %[[VAL_16:.*]] = emitc.load %[[VAL_3]] : +// CHECK: emitc.assign %[[VAL_16]] : i32 to %[[VAL_14]] : // CHECK: %[[VAL_17:.*]] = emitc.load %[[VAL_13]] : -// CHECK: return %[[VAL_16]] : i32 +// CHECK: %[[VAL_18:.*]] = emitc.load %[[VAL_14]] : +// 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 @@ -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]] : // 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]] : // CHECK: assign %[[VAL_10]] : i32 to %[[VAL_3]] : // CHECK: } // CHECK: } while { -// CHECK: %[[VAL_11:.*]] = load %[[VAL_4]] : +// CHECK: %[[VAL_11:.*]] = expression : i1 { +// CHECK: %[[VAL_12:.*]] = load %[[VAL_4]] : +// CHECK: yield %[[VAL_12]] : i1 +// CHECK: } // CHECK: yield %[[VAL_11]] : i1 // CHECK: } -// CHECK: %[[VAL_12:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue // CHECK: %[[VAL_13:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue -// CHECK: %[[VAL_14:.*]] = emitc.load %[[VAL_2]] : -// CHECK: emitc.assign %[[VAL_14]] : i32 to %[[VAL_12]] : -// CHECK: %[[VAL_15:.*]] = emitc.load %[[VAL_3]] : +// CHECK: %[[VAL_14:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue +// CHECK: %[[VAL_15:.*]] = emitc.load %[[VAL_2]] : // CHECK: emitc.assign %[[VAL_15]] : i32 to %[[VAL_13]] : -// CHECK: %[[VAL_16:.*]] = emitc.load %[[VAL_12]] : +// CHECK: %[[VAL_16:.*]] = emitc.load %[[VAL_3]] : +// CHECK: emitc.assign %[[VAL_16]] : i32 to %[[VAL_14]] : // CHECK: %[[VAL_17:.*]] = emitc.load %[[VAL_13]] : -// CHECK: return %[[VAL_16]] : i32 +// CHECK: %[[VAL_18:.*]] = emitc.load %[[VAL_14]] : +// 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 4644e71a5101e..b8854271feb19 100644 --- a/mlir/test/Dialect/EmitC/invalid_ops.mlir +++ b/mlir/test/Dialect/EmitC/invalid_ops.mlir @@ -733,8 +733,55 @@ emitc.class @testClass { // ----- func.func @test_while(%arg0 : !emitc.ptr) { - // 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 + } + + return +} + +// ----- + +func.func @test_while(%arg0 : !emitc.ptr) { + // 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 + } + + return +} + +// ----- + +func.func @test_while(%arg0 : !emitc.ptr) { + %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 } @@ -744,14 +791,21 @@ func.func @test_while(%arg0 : !emitc.ptr) { // ----- -emitc.func @test_while(%arg0 : !emitc.ptr) { +func.func @test_while(%arg0 : !emitc.ptr) { %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 } @@ -765,9 +819,14 @@ func.func @test_while(%arg0 : !emitc.ptr) { %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 } @@ -781,10 +840,16 @@ func.func @test_while(%arg0 : !emitc.ptr) { %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 } @@ -800,9 +865,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 @@ -810,18 +880,43 @@ func.func @test_while() { // ----- -emitc.func @test_while(%arg0 : !emitc.ptr) { +func.func @test_while(%arg0 : !emitc.ptr) { %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 - %4 = emitc.add %1, %2 : (i32, i32) -> i32 - emitc.yield %4 : i32 + emitc.yield + } + + 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 : i1 { + %cmp = emitc.cmp eq, %1, %2 : (i32, i32) -> i1 + emitc.yield %cmp : i1 + } + + %3 = emitc.literal "3" : i32 + emitc.yield %r : i1 } return @@ -829,11 +924,13 @@ emitc.func @test_while(%arg0 : !emitc.ptr) { // ----- -func.func @test_do_while(%arg0 : !emitc.ptr) { - // expected-error @+1 {{'emitc.do' op condition region cannot be empty}} +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 @@ -841,16 +938,20 @@ func.func @test_do_while(%arg0 : !emitc.ptr) { // ----- -emitc.func @test_do_while(%arg0 : !emitc.ptr) { +func.func @test_do(%arg0 : !emitc.ptr) { %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 } 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 @@ -858,15 +959,23 @@ emitc.func @test_do_while(%arg0 : !emitc.ptr) { // ----- -func.func @test_do_while(%arg0 : !emitc.ptr) { +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 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 } 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 @@ -874,16 +983,20 @@ func.func @test_do_while(%arg0 : !emitc.ptr) { // ----- -func.func @test_do_while(%arg0 : !emitc.ptr) { +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 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 } 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 @@ -891,15 +1004,44 @@ func.func @test_do_while(%arg0 : !emitc.ptr) { // ----- -func.func @test_do_while() { +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 : 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 @@ -907,18 +1049,21 @@ func.func @test_do_while() { // ----- -emitc.func @test_do_while(%arg0 : !emitc.ptr) { +func.func @test_do(%arg0 : !emitc.ptr) { %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 - %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 6a41c839399f4..90058a41f62d1 100644 --- a/mlir/test/Dialect/EmitC/ops.mlir +++ b/mlir/test/Dialect/EmitC/ops.mlir @@ -342,9 +342,13 @@ func.func @while(%arg0 : !emitc.ptr) { %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 } @@ -360,9 +364,13 @@ func.func @do(%arg0 : !emitc.ptr) { emitc.do { emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr } 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) { %var = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue - %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 + 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 { - %var_load = load %var : - %res = emitc.cmp le, %var_load, %0 : (i32, i32) -> i1 - emitc.yield %res : i1 + %r = expression : i1 { + %var_load = load %var : + %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) { emitc.func @emitc_do_with_expression(%arg0 : !emitc.ptr) { %var = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue - %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 + 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 { - %var_load = load %var : - %res = emitc.cmp le, %var_load, %add : (i32, i32) -> i1 - emitc.yield %res : i1 + %r = expression : 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 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 + %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 : 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 : 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 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) { %var = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue - %0 = emitc.literal "10" : i32 - %1 = emitc.literal "1" : i32 + %0 = literal "10" : i32 + %1 = literal "1" : i32 - emitc.while { - %var_load = load %var : - %res = emitc.cmp le, %var_load, %0 : (i32, i32) -> i1 - emitc.yield %res : i1 + while { + %r = expression : i1 { + %var_load = load %var : + %cmp = cmp le, %var_load, %0 : (i32, i32) -> i1 + yield %cmp : i1 + } + + yield %r : i1 } do { - emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr + 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) -> () @@ -46,20 +50,24 @@ emitc.func @emitc_while(%arg0 : !emitc.ptr) { emitc.func @emitc_while_with_expression(%arg0 : !emitc.ptr) { %var = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue - %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 : - %res = emitc.cmp le, %var_load, %add : (i32, i32) -> i1 - emitc.yield %res : i1 + while { + %r = expression : i1 { + %var_load = load %var : + %cmp = cmp le, %var_load, %add : (i32, i32) -> i1 + yield %cmp : i1 + } + + yield %r : i1 } do { - emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr + 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) -> () @@ -67,3 +75,62 @@ emitc.func @emitc_while_with_expression(%arg0 : !emitc.ptr) { 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 + %var_2 = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue + + %step = literal "1" : i32 + %end_1 = literal "3" : i32 + %end_2 = literal "5" : i32 + + while { + %r = expression : i1 { + %var_1_load = load %var_1 : + %cmp = cmp le, %var_1_load, %end_1 : (i32, i32) -> i1 + yield %cmp : i1 + } + + yield %r : i1 + } do { + %var_1_load = load %var_1 : + + while { + %r = expression : i1 { + %var_2_load = load %var_2 : + %cmp = cmp le, %var_2_load, %end_2 : (i32, i32) -> i1 + yield %cmp : i1 + } + + yield %r : i1 + } 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) -> () + } + + %tmp_add = add %var_1_load, %step : (i32, i32) -> i32 + "emitc.assign"(%var_1, %tmp_add) : (!emitc.lvalue, i32) -> () + } + + return +} From 046b038d4385eb050709eb63176a6f1c470296c2 Mon Sep 17 00:00:00 2001 From: Vlad Lazar Date: Tue, 19 Aug 2025 16:30:28 +0300 Subject: [PATCH 03/15] [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 f23d076752d5d..aa7f64b923715 100644 --- a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp +++ b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp @@ -1595,7 +1595,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(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 787997191bc77..8c80331d03be9 100644 --- a/mlir/lib/Target/Cpp/TranslateToCpp.cpp +++ b/mlir/lib/Target/Cpp/TranslateToCpp.cpp @@ -260,7 +260,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. @@ -324,7 +324,7 @@ struct CppEmitter { unsigned int valueCount{0}; /// State of the current expression being emitted. - Operation *emittedExpression = nullptr; + ExpressionOp emittedExpression; SmallVector emittedExpressionPrecedence; void pushExpressionPrecedence(int precedence) { @@ -1764,12 +1764,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 248500d7327a94e50fb6ea1f7f7539c3802ef908 Mon Sep 17 00:00:00 2001 From: Vlad Lazar Date: Tue, 26 Aug 2025 20:03:53 +0300 Subject: [PATCH 04/15] [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 { // Create variable storage for loop-carried values to enable imperative // updates while maintaining SSA semantics at conversion boundaries. SmallVector 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(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 aa7f64b923715..dc1326a1cdbc3 100644 --- a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp +++ b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp @@ -1602,11 +1602,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 " @@ -1636,9 +1635,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 -// CHECK: emitc.assign %[[VAL_0]] : i32 to %[[VAL_3]] : -// CHECK: emitc.while { -// CHECK: %[[VAL_4:.*]] = expression : i1 { -// CHECK: %[[VAL_5:.*]] = load %[[VAL_3]] : -// 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]] : -// CHECK: %[[VAL_9:.*]] = call @payload_while(%[[VAL_8]]) : (i32) -> i32 -// CHECK: assign %[[VAL_9]] : i32 to %[[VAL_3]] : -// CHECK: } -// CHECK: %[[VAL_10:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue -// CHECK: %[[VAL_11:.*]] = emitc.load %[[VAL_3]] : -// CHECK: emitc.assign %[[VAL_11]] : i32 to %[[VAL_10]] : -// CHECK: %[[VAL_12:.*]] = emitc.load %[[VAL_10]] : -// 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]] : // 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]] : // 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 // CHECK: emitc.assign %[[VAL_0]] : i32 to %[[VAL_2]] : // CHECK: %[[VAL_3:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue // CHECK: emitc.assign %[[VAL_0]] : i32 to %[[VAL_3]] : -// CHECK: emitc.while { -// CHECK: %[[VAL_4:.*]] = expression : i1 { -// CHECK: %[[VAL_5:.*]] = load %[[VAL_2]] : -// CHECK: %[[VAL_6:.*]] = load %[[VAL_3]] : -// 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 +// CHECK: emitc.do { +// CHECK: %[[VAL_5:.*]] = load %[[VAL_2]] : +// CHECK: %[[VAL_6:.*]] = load %[[VAL_3]] : +// 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]] : +// 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]] : +// CHECK: assign %[[VAL_10]] : i32 to %[[VAL_3]] : // CHECK: } -// CHECK: yield %[[VAL_4]] : i1 -// CHECK: } do { -// CHECK: %[[VAL_9:.*]] = load %[[VAL_2]] : -// CHECK: %[[VAL_10:.*]] = load %[[VAL_3]] : -// 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]] : -// CHECK: assign %[[VAL_12]] : i32 to %[[VAL_3]] : +// CHECK: } while { +// CHECK: %[[VAL_11:.*]] = expression : i1 { +// CHECK: %[[VAL_12:.*]] = load %[[VAL_4]] : +// CHECK: yield %[[VAL_12]] : i1 +// CHECK: } +// CHECK: yield %[[VAL_11]] : i1 // CHECK: } // CHECK: %[[VAL_13:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue // CHECK: %[[VAL_14:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue @@ -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 { + %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 - - %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 + %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 { +// 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 "10.0" : i32 -// CHECK: %[[VAL_2:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue -// CHECK: emitc.assign %[[VAL_0]] : i32 to %[[VAL_2]] : +// 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: emitc.assign %[[VAL_0]] : i32 to %[[VAL_3]] : // CHECK: %[[VAL_4:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue // CHECK: emitc.do { -// CHECK: %[[VAL_5:.*]] = load %[[VAL_2]] : -// CHECK: %[[VAL_6:.*]] = load %[[VAL_3]] : -// 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]] : -// 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]] : +// CHECK: %[[VAL_5:.*]] = load %[[VAL_3]] : +// CHECK: %[[VAL_6:.*]] = call @foo_with_side_effect(%[[VAL_5]], %[[ARG0]]) : (i32, !emitc.ptr) -> 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]] : +// CHECK: if %[[VAL_9]] { +// CHECK: %[[VAL_10:.*]] = call @payload_double_use(%[[VAL_5]]) : (i32) -> i32 // CHECK: assign %[[VAL_10]] : i32 to %[[VAL_3]] : // 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 -// CHECK: %[[VAL_14:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue -// CHECK: %[[VAL_15:.*]] = emitc.load %[[VAL_2]] : -// CHECK: emitc.assign %[[VAL_15]] : i32 to %[[VAL_13]] : -// CHECK: %[[VAL_16:.*]] = emitc.load %[[VAL_3]] : -// CHECK: emitc.assign %[[VAL_16]] : i32 to %[[VAL_14]] : -// CHECK: %[[VAL_17:.*]] = emitc.load %[[VAL_13]] : -// CHECK: %[[VAL_18:.*]] = emitc.load %[[VAL_14]] : -// CHECK: return %[[VAL_17]] : i32 +// CHECK: %[[VAL_14:.*]] = emitc.load %[[VAL_3]] : +// CHECK: emitc.assign %[[VAL_14]] : i32 to %[[VAL_13]] : +// CHECK: %[[VAL_15:.*]] = emitc.load %[[VAL_13]] : +// CHECK: return %[[VAL_15]] : i32 // CHECK: } From 8b96e3258ad89d1f9285e28fea4b9ac51b50ebc1 Mon Sep 17 00:00:00 2001 From: Vlad Lazar Date: Wed, 3 Sep 2025 20:23:21 +0300 Subject: [PATCH 05/15] [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 { return finalResults; } - // Direct lowering to emitc.while when condition arguments match region - // inputs. - LogicalResult lowerWhile(WhileOp whileOp, ArrayRef vars, - MLIRContext *context, - ConversionPatternRewriter &rewriter, - Location loc) const { - auto loweredWhile = rewriter.create(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(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(exprBlock->getTerminator()); - Value condition = rewriter.getRemappedValue(condOp.getCondition()); - rewriter.setInsertionPointAfter(condOp); - rewriter.replaceOpWithNewOp(condOp, condition); - - rewriter.setInsertionPointToEnd(condBlock); - rewriter.create(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 vars, MLIRContext *context, @@ -555,11 +510,15 @@ struct WhileLowering : public OpConversionPattern { Block *condBlock = rewriter.createBlock(&condRegion); rewriter.setInsertionPointToStart(condBlock); - auto exprOp = rewriter.create(loc, TypeRange{i1Type}); + auto exprOp = rewriter.create( + 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(loc, i1Type, conditionVal); + Value cond = + rewriter.create(loc, i1Type, exprBlock->getArgument(0)); rewriter.create(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]] : // CHECK: } // CHECK: } while { -// CHECK: %[[VAL_10:.*]] = expression : i1 { +// CHECK: %[[VAL_10:.*]] = expression %[[VAL_4]] : (!emitc.lvalue) -> i1 { // CHECK: %[[VAL_11:.*]] = load %[[VAL_4]] : // CHECK: yield %[[VAL_11]] : i1 // CHECK: } @@ -111,7 +111,7 @@ func.func @two_results() -> i32 { // CHECK: assign %[[VAL_10]] : i32 to %[[VAL_3]] : // CHECK: } // CHECK: } while { -// CHECK: %[[VAL_11:.*]] = expression : i1 { +// CHECK: %[[VAL_11:.*]] = expression %[[VAL_4]] : (!emitc.lvalue) -> i1 { // CHECK: %[[VAL_12:.*]] = load %[[VAL_4]] : // CHECK: yield %[[VAL_12]] : i1 // CHECK: } @@ -191,7 +191,7 @@ func.func @double_use(%p : !emitc.ptr) -> i32 { // CHECK: assign %[[VAL_10]] : i32 to %[[VAL_3]] : // CHECK: } // CHECK: } while { -// CHECK: %[[VAL_11:.*]] = expression : i1 { +// CHECK: %[[VAL_11:.*]] = expression %[[VAL_4]] : (!emitc.lvalue) -> i1 { // CHECK: %[[VAL_12:.*]] = load %[[VAL_4]] : // 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 b8854271feb19..dc7eb343aa2d7 100644 --- a/mlir/test/Dialect/EmitC/invalid_ops.mlir +++ b/mlir/test/Dialect/EmitC/invalid_ops.mlir @@ -738,7 +738,7 @@ func.func @test_while(%arg0 : !emitc.ptr) { // 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 } @@ -776,7 +776,7 @@ func.func @test_while(%arg0 : !emitc.ptr) { // 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 } @@ -797,12 +797,12 @@ func.func @test_while(%arg0 : !emitc.ptr) { // 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 } @@ -821,7 +821,7 @@ func.func @test_while(%arg0 : !emitc.ptr) { // 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 } @@ -844,7 +844,7 @@ func.func @test_while(%arg0 : !emitc.ptr) { // 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 } @@ -865,7 +865,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 } @@ -886,7 +886,7 @@ func.func @test_while(%arg0 : !emitc.ptr) { // 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 } @@ -910,7 +910,7 @@ func.func @test_do(%arg0 : !emitc.ptr) { emitc.do { emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr } 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 } @@ -946,7 +946,7 @@ func.func @test_do(%arg0 : !emitc.ptr) { emitc.do { emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr } 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 } @@ -967,12 +967,12 @@ func.func @test_do(%arg0 : !emitc.ptr) { emitc.do { emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr } 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 } @@ -991,7 +991,7 @@ func.func @test_do(%arg0 : !emitc.ptr) { emitc.do { emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr } 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 } @@ -1014,7 +1014,7 @@ func.func @test_do(%arg0 : !emitc.ptr) { emitc.do { emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr } 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 } @@ -1036,7 +1036,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 } @@ -1058,7 +1058,7 @@ func.func @test_do(%arg0 : !emitc.ptr) { emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr 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 90058a41f62d1..d85d725f7b20b 100644 --- a/mlir/test/Dialect/EmitC/ops.mlir +++ b/mlir/test/Dialect/EmitC/ops.mlir @@ -342,7 +342,7 @@ func.func @while(%arg0 : !emitc.ptr) { %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 @@ -364,7 +364,7 @@ func.func @do(%arg0 : !emitc.ptr) { emitc.do { emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr } 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) { %tmp_add = add %var_load, %1 : (i32, i32) -> i32 "emitc.assign"(%var, %tmp_add) : (!emitc.lvalue, i32) -> () } while { - %r = expression : i1 { + %r = expression %var, %0 : (!emitc.lvalue, i32) -> i1 { %var_load = load %var : %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) { %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) { %tmp_add = add %var_load, %1 : (i32, i32) -> i32 "emitc.assign"(%var, %tmp_add) : (!emitc.lvalue, i32) -> () } while { - %r = expression : i1 { + %r = expression %var, %add : (!emitc.lvalue, i32) -> i1 { %var_load = load %var : %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) -> () } while { - %r = expression : i1 { + %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 @@ -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) -> () } while { - %r = expression : i1 { + %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 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) { %1 = literal "1" : i32 while { - %r = expression : i1 { + %r = expression %var, %0 : (!emitc.lvalue, i32) -> i1 { %var_load = load %var : %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) { %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) -> i1 { %var_load = load %var : %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) -> i1 { %var_1_load = load %var_1 : %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 : while { - %r = expression : i1 { + %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 From 531a8d87d19410829a9ce2c604ddd728c09108fe Mon Sep 17 00:00:00 2001 From: Vlad Lazar Date: Wed, 3 Sep 2025 20:38:48 +0300 Subject: [PATCH 06/15] [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 8f58d671e20bf..7d3beb23babb5 100644 --- a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td +++ b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td @@ -1752,7 +1752,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 @@ -1774,7 +1774,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) -> i1 { %current = emitc.load %counter : !emitc.lvalue %cmp_res = emitc.cmp lt, %current, %end : (i32, i32) -> i1 emitc.yield %cmp_res : i1 @@ -1857,7 +1857,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 @@ -1884,7 +1884,7 @@ def EmitC_DoOp : EmitC_Op<"do", %new_val = emitc.add %val, %step : (i32, i32) -> i32 "emitc.assign"(%counter, %new_val) : (!emitc.lvalue, i32) -> () } while { - %condition = emitc.expression : i1 { + %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 From 5af06b8340cf8dbc8a05d0ad88f7173405d92e10 Mon Sep 17 00:00:00 2001 From: Vlad Lazar Date: Sat, 6 Sep 2025 02:00:33 +0300 Subject: [PATCH 07/15] [mlir][emitc] Remove unused WhileOp implementation --- mlir/include/mlir/Dialect/EmitC/IR/EmitC.td | 107 +---------- mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp | 7 +- mlir/lib/Dialect/EmitC/IR/EmitC.cpp | 119 ++++-------- mlir/lib/Target/Cpp/TranslateToCpp.cpp | 29 +-- mlir/test/Dialect/EmitC/invalid_ops.mlir | 173 +----------------- mlir/test/Dialect/EmitC/ops.mlir | 20 -- mlir/test/Target/Cpp/while.mlir | 136 -------------- 7 files changed, 46 insertions(+), 545 deletions(-) delete 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 7d3beb23babb5..3fc2bd4bb1841 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<["DoOp", "ExpressionOp", "ForOp", "IfOp", "SwitchOp", "WhileOp"]>]> { + [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,107 +1727,6 @@ def EmitC_GetFieldOp let hasVerifier = 1; } -def EmitC_WhileOp : EmitC_Op<"while", - [NoTerminator, OpAsmOpInterface, RecursiveMemoryEffects]> { - 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 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 @while_example() { - %counter = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue - %end = emitc.literal "10" : i32 - %step = emitc.literal "1" : i32 - - emitc.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 - } 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) -> () - } - return - } - ``` - ```c++ - // Code emitted for the operation above. - 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 results = (outs); - let regions = (region SizedRegion<1>:$conditionRegion, - SizedRegion<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", [NoTerminator, OpAsmOpInterface, RecursiveMemoryEffects]> { let summary = "Do-while operation"; @@ -1845,8 +1744,8 @@ def EmitC_DoOp : EmitC_Op<"do", 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. Thus, there is a guarantee that the loop will be executed + 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: diff --git a/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp b/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp index 07df270f8221f..698325e73f3ef 100644 --- a/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp +++ b/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp @@ -336,8 +336,9 @@ 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. +// 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; @@ -453,7 +454,7 @@ struct WhileLowering : public OpConversionPattern { return finalResults; } - // Lower to emitc.do when condition arguments differ from region inputs. + // Lower scf.while to emitc.do. LogicalResult lowerDoWhile(WhileOp whileOp, ArrayRef vars, MLIRContext *context, ConversionPatternRewriter &rewriter, diff --git a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp index dc1326a1cdbc3..1ad621ae14b69 100644 --- a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp +++ b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp @@ -974,12 +974,10 @@ LogicalResult emitc::YieldOp::verify() { Value result = getResult(); Operation *containingOp = getOperation()->getParentOp(); - if (result && containingOp->getNumResults() != 1 && - !isa(containingOp)) + if (result && containingOp->getNumResults() != 1 && !isa(containingOp)) return emitOpError() << "yields a value not returned by parent"; - if (!result && containingOp->getNumResults() != 0 && - !isa(containingOp)) + if (!result && containingOp->getNumResults() != 0 && !isa(containingOp)) return emitOpError() << "does not yield a value to be returned by parent"; return success(); @@ -1564,20 +1562,28 @@ LogicalResult GetFieldOp::verifySymbolUses(SymbolTableCollection &symbolTable) { } //===----------------------------------------------------------------------===// -// Common functions for WhileOp and DoOp +// DoOp //===----------------------------------------------------------------------===// -static Operation *getRootOpFromLoopCondition(Region &condRegion) { - auto yieldOp = cast(condRegion.front().getTerminator()); +Operation *DoOp::getRootOp() { + auto yieldOp = + cast(getConditionRegion().front().getTerminator()); return yieldOp.getResult().getDefiningOp(); } -static LogicalResult verifyLoopRegions(Operation &op, Region &condition, - Region &body) { - Block &condBlock = condition.front(); +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 op.emitOpError( + return emitOpError( "condition region must contain exactly two operations: " "'emitc.expression' followed by 'emitc.yield', but found ") << condBlock.getOperations().size() << " operations"; @@ -1585,103 +1591,50 @@ static LogicalResult verifyLoopRegions(Operation &op, Region &condition, Operation &first = condBlock.front(); auto exprOp = dyn_cast(first); if (!exprOp) - return op.emitOpError("expected first op in condition region to be " - "'emitc.expression', " - "but got ") + return 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 ") + 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 op.emitOpError("expected last op in condition region to be " - "'emitc.yield', but got ") + return 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 ") + return 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 " - "'emitc.expression' from this condition region"); + return emitError("'emitc.yield' must return result of " + "'emitc.expression' from this condition region"); - Block &bodyBlock = body.front(); + Block &bodyBlock = getBodyRegion().front(); if (bodyBlock.empty()) - return op.emitOpError("body region cannot be empty"); + return emitOpError("body region cannot be empty"); if (bodyBlock.mightHaveTerminator()) - return op.emitOpError("body region must not contain terminator"); + return emitOpError("body region must not contain terminator"); 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(); +ParseResult DoOp::parse(OpAsmParser &parser, OperationState &result) { + Region *bodyRegion = result.addRegion(); + Region *condRegion = result.addRegion(); - if (parser.parseRegion(*firstRegion) || parser.parseKeyword(midKeyword) || - parser.parseRegion(*secondRegion)) + if (parser.parseRegion(*bodyRegion) || parser.parseKeyword("while") || + parser.parseRegion(*condRegion)) 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"); + return parser.parseOptionalAttrDictWithKeyword(result.attributes); } //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Target/Cpp/TranslateToCpp.cpp b/mlir/lib/Target/Cpp/TranslateToCpp.cpp index 8c80331d03be9..5fe5f4181b71d 100644 --- a/mlir/lib/Target/Cpp/TranslateToCpp.cpp +++ b/mlir/lib/Target/Cpp/TranslateToCpp.cpp @@ -559,31 +559,6 @@ static LogicalResult printOperation(CppEmitter &emitter, return success(); } -static LogicalResult printOperation(CppEmitter &emitter, - emitc::WhileOp whileOp) { - raw_indented_ostream &os = emitter.ostream(); - - os << "while ("; - - Block &condBlock = whileOp.getConditionRegion().front(); - auto condYield = cast(condBlock.back()); - if (failed(emitter.emitExpression( - cast(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 doOp) { raw_indented_ostream &os = emitter.ostream(); @@ -1769,7 +1744,7 @@ LogicalResult CppEmitter::emitOperation(Operation &op, bool trailingSemicolon) { 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::VerbatimOp>( [&](auto op) { return printOperation(*this, op); }) // Func ops. @@ -1819,7 +1794,7 @@ LogicalResult CppEmitter::emitOperation(Operation &op, bool trailingSemicolon) { trailingSemicolon &= !isa(op); + emitc::VerbatimOp>(op); os << (trailingSemicolon ? ";\n" : "\n"); diff --git a/mlir/test/Dialect/EmitC/invalid_ops.mlir b/mlir/test/Dialect/EmitC/invalid_ops.mlir index dc7eb343aa2d7..9a8aabceec5cb 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.do, emitc.expression, emitc.for, emitc.if, emitc.switch, emitc.while'}} + // 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 } @@ -732,176 +732,6 @@ emitc.class @testClass { // ----- -func.func @test_while(%arg0 : !emitc.ptr) { - %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 %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 - } do { - emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr - } - - return -} - -// ----- - -func.func @test_while(%arg0 : !emitc.ptr) { - // 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 - } - - return -} - -// ----- - -func.func @test_while(%arg0 : !emitc.ptr) { - %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 %1, %2 : (i32, i32) -> 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 - } - - return -} - -// ----- - -func.func @test_while(%arg0 : !emitc.ptr) { - %1 = emitc.literal "1" : i32 - %2 = emitc.literal "2" : 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 %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 - } - } do { - emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr - } - - return -} - -// ----- - -func.func @test_while(%arg0 : !emitc.ptr) { - %1 = emitc.literal "1" : i32 - %2 = emitc.literal "2" : i32 - - // expected-error @+1 {{'emitc.while' op expected condition region to return 1 value, but it returns 0 values}} - emitc.while { - %r = emitc.expression %1, %2 : (i32, i32) -> 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 - } - - return -} - -// ----- - -func.func @test_while(%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.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 - } do { - emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr - } - - 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 { - %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 - } do { - ^bb0: - } - - return -} - -// ----- - -func.func @test_while(%arg0 : !emitc.ptr) { - %1 = emitc.literal "1" : i32 - %2 = emitc.literal "2" : i32 - - // expected-error @+1 {{'emitc.while' op body region must not contain terminator}} - emitc.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 - } do { - emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr - emitc.yield - } - - return -} - -// ----- - func.func @test_do(%arg0 : !emitc.ptr) { %1 = emitc.literal "1" : i32 %2 = emitc.literal "2" : i32 @@ -1027,7 +857,6 @@ func.func @test_do(%arg0 : !emitc.ptr) { // ----- - func.func @test_do() { %1 = emitc.literal "1" : i32 %2 = emitc.literal "2" : i32 diff --git a/mlir/test/Dialect/EmitC/ops.mlir b/mlir/test/Dialect/EmitC/ops.mlir index d85d725f7b20b..1259748dfce84 100644 --- a/mlir/test/Dialect/EmitC/ops.mlir +++ b/mlir/test/Dialect/EmitC/ops.mlir @@ -336,26 +336,6 @@ emitc.class final @finalClass { } } -func.func @while(%arg0 : !emitc.ptr) { - %1 = emitc.literal "1" : i32 - %2 = emitc.literal "2" : i32 - %3 = emitc.literal "3" : i32 - - emitc.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 - } do { - emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr - } - - return -} - func.func @do(%arg0 : !emitc.ptr) { %1 = emitc.literal "1" : i32 %2 = emitc.literal "2" : i32 diff --git a/mlir/test/Target/Cpp/while.mlir b/mlir/test/Target/Cpp/while.mlir deleted file mode 100644 index cff154161ab40..0000000000000 --- a/mlir/test/Target/Cpp/while.mlir +++ /dev/null @@ -1,136 +0,0 @@ -// 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) { - %var = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue - %0 = literal "10" : i32 - %1 = literal "1" : 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 - } 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) -> () - } - - 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) { - %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 - } - - 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 - } 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) -> () - } - - 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 - %var_2 = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue - - %step = literal "1" : i32 - %end_1 = literal "3" : i32 - %end_2 = literal "5" : 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 - } do { - %var_1_load = load %var_1 : - - 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 - } 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) -> () - } - - %tmp_add = add %var_1_load, %step : (i32, i32) -> i32 - "emitc.assign"(%var_1, %tmp_add) : (!emitc.lvalue, i32) -> () - } - - return -} From 6df8ceb0696a6b88b85318a34f814a340beb3773 Mon Sep 17 00:00:00 2001 From: Vlad Lazar Date: Mon, 8 Sep 2025 11:54:59 +0300 Subject: [PATCH 08/15] [mlir][emitc] Changes after review --- mlir/lib/Dialect/EmitC/IR/EmitC.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp index 1ad621ae14b69..7100ed46294a0 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 && !isa(containingOp)) + if (!isa(containingOp) && result && containingOp->getNumResults() != 1) return emitOpError() << "yields a value not returned by parent"; - if (!result && containingOp->getNumResults() != 0 && !isa(containingOp)) + if (!isa(containingOp) && !result && containingOp->getNumResults() != 0) return emitOpError() << "does not yield a value to be returned by parent"; return success(); From dd244fe95ccdcae19b3b37a630f0029d11df9d73 Mon Sep 17 00:00:00 2001 From: Vlad Lazar Date: Tue, 16 Sep 2025 15:43:25 +0300 Subject: [PATCH 09/15] [mlir][emitc] Changes after review --- mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp b/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp index 698325e73f3ef..db022002ea0fd 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 @@ -415,16 +416,22 @@ struct WhileLowering : public OpConversionPattern { // Convert SCF yield terminators to imperative assignments to update loop // variables, maintaining loop semantics while transitioning to emitc model. - void processYieldTerminator(Operation *terminator, ArrayRef vars, - ConversionPatternRewriter &rewriter, - Location loc) const { + LogicalResult processYieldTerminator(Operation *terminator, + ArrayRef vars, + ConversionPatternRewriter &rewriter, + Location loc) const { auto yieldOp = cast(terminator); - SmallVector yields(yieldOp.getOperands()); + SmallVector yields; + if (failed(rewriter.getRemappedValues(yieldOp.getOperands(), yields))) + return rewriter.notifyMatchFailure(yieldOp, + "failed to lower yield operands"); rewriter.eraseOp(yieldOp); rewriter.setInsertionPointToEnd(yieldOp->getBlock()); for (auto [var, val] : llvm::zip(vars, yields)) rewriter.create(loc, var, val); + + return success(); } // Transfers final loop state from mutable variables to result variables, @@ -500,7 +507,10 @@ struct WhileLowering : public OpConversionPattern { ifBlock->eraseArguments(0, ifBlock->getNumArguments()); // Convert scf.yield to variable assignments for state updates. - processYieldTerminator(ifBlock->getTerminator(), vars, rewriter, loc); + if (failed(processYieldTerminator(ifBlock->getTerminator(), vars, + rewriter, loc))) + return failure(); + rewriter.create(loc); } From 96962f68ae8865adc0e4a8c1ac70b26fa3607913 Mon Sep 17 00:00:00 2001 From: Vlad Lazar Date: Wed, 17 Sep 2025 16:02:14 +0300 Subject: [PATCH 10/15] [mlir][emitc] Do not create an ifOp if the after-region is empty --- mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp | 15 ++++---- mlir/test/Conversion/SCFToEmitC/while.mlir | 36 +++++++++++++++++++ 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp b/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp index db022002ea0fd..2da287b4e7156 100644 --- a/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp +++ b/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp @@ -474,7 +474,7 @@ struct WhileLowering : public OpConversionPattern { auto loweredDo = rewriter.create(loc); - // Lower before region as body. + // Lower before-region as body. rewriter.inlineRegionBefore(whileOp.getBefore(), loweredDo.getBodyRegion(), loweredDo.getBodyRegion().end()); @@ -489,14 +489,13 @@ struct WhileLowering : public OpConversionPattern { Value condition = rewriter.getRemappedValue(condOp.getCondition()); rewriter.create(loc, conditionVal, condition); - // Wrap body region in conditional to preserve scf semantics. - auto ifOp = rewriter.create(loc, condition, false, false); + // 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); + rewriter.inlineRegionBefore(whileOp.getAfter(), ifOp.getBodyRegion(), + ifOp.getBodyRegion().begin()); - // 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. diff --git a/mlir/test/Conversion/SCFToEmitC/while.mlir b/mlir/test/Conversion/SCFToEmitC/while.mlir index c2a446e4f8017..3e958630f5e27 100644 --- a/mlir/test/Conversion/SCFToEmitC/while.mlir +++ b/mlir/test/Conversion/SCFToEmitC/while.mlir @@ -203,3 +203,39 @@ func.func @double_use(%p : !emitc.ptr) -> i32 { // CHECK: %[[VAL_15:.*]] = emitc.load %[[VAL_13]] : // CHECK: return %[[VAL_15]] : 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: } From ba3f5c183a5d26edf2fb997787191ae0263034f3 Mon Sep 17 00:00:00 2001 From: Vlad Lazar Date: Wed, 17 Sep 2025 19:03:47 +0300 Subject: [PATCH 11/15] [mlir][emitc] Duplicate of the 'lowerYield' function has been removed --- mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp | 32 +++---------------- mlir/test/Conversion/SCFToEmitC/while.mlir | 3 -- 2 files changed, 4 insertions(+), 31 deletions(-) diff --git a/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp b/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp index 2da287b4e7156..38a881bca3f91 100644 --- a/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp +++ b/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp @@ -117,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); @@ -414,26 +413,6 @@ struct WhileLowering : public OpConversionPattern { block->eraseArguments(0, block->getNumArguments()); } - // Convert SCF yield terminators to imperative assignments to update loop - // variables, maintaining loop semantics while transitioning to emitc model. - LogicalResult processYieldTerminator(Operation *terminator, - ArrayRef vars, - ConversionPatternRewriter &rewriter, - Location loc) const { - auto yieldOp = cast(terminator); - SmallVector yields; - if (failed(rewriter.getRemappedValues(yieldOp.getOperands(), yields))) - return rewriter.notifyMatchFailure(yieldOp, - "failed to lower yield operands"); - rewriter.eraseOp(yieldOp); - - rewriter.setInsertionPointToEnd(yieldOp->getBlock()); - for (auto [var, val] : llvm::zip(vars, yields)) - rewriter.create(loc, var, val); - - return success(); - } - // Transfers final loop state from mutable variables to result variables, // then returns the final SSA values to replace the original scf::while // results. @@ -505,12 +484,9 @@ struct WhileLowering : public OpConversionPattern { ifBlock->eraseArguments(0, ifBlock->getNumArguments()); - // Convert scf.yield to variable assignments for state updates. - if (failed(processYieldTerminator(ifBlock->getTerminator(), vars, - rewriter, loc))) + if (failed(lowerYield(whileOp, vars, rewriter, + cast(ifBlock->getTerminator())))) return failure(); - - rewriter.create(loc); } rewriter.eraseOp(condOp); diff --git a/mlir/test/Conversion/SCFToEmitC/while.mlir b/mlir/test/Conversion/SCFToEmitC/while.mlir index 3e958630f5e27..a791ed59521e2 100644 --- a/mlir/test/Conversion/SCFToEmitC/while.mlir +++ b/mlir/test/Conversion/SCFToEmitC/while.mlir @@ -83,7 +83,6 @@ func.func @two_results() -> 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 @@ -156,7 +155,6 @@ func.func @double_use(%p : !emitc.ptr) -> 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 @@ -219,7 +217,6 @@ func.func @empty_after_region() { } return } - // CHECK-LABEL: emitc.func @payload_empty_after_region() -> i1 { // CHECK: %[[VAL_0:.*]] = literal "true" : i1 // CHECK: return %[[VAL_0]] : i1 From d2fb44cff73c5e7e01d8b41296de9228e227d9b3 Mon Sep 17 00:00:00 2001 From: Vlad Lazar Date: Mon, 22 Sep 2025 18:18:51 +0300 Subject: [PATCH 12/15] [mlir][emitc] Changes after review --- mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp | 81 ++++++++++--------- 1 file changed, 42 insertions(+), 39 deletions(-) diff --git a/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp b/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp index 38a881bca3f91..8e8c75968db55 100644 --- a/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp +++ b/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp @@ -107,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(); @@ -351,7 +351,8 @@ struct WhileLowering : public OpConversionPattern { // Create variable storage for loop-carried values to enable imperative // updates while maintaining SSA semantics at conversion boundaries. SmallVector variables; - if (failed(createInitVariables(whileOp, rewriter, variables, loc, context))) + if (failed(createVariablesForLoopCarriedValues(whileOp, rewriter, variables, + loc, context))) return failure(); if (failed(lowerDoWhile(whileOp, variables, context, rewriter, loc))) @@ -379,15 +380,19 @@ struct WhileLowering : public OpConversionPattern { 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 &outVars, - Location loc, MLIRContext *context) { + LogicalResult createVariablesForLoopCarriedValues( + WhileOp whileOp, ConversionPatternRewriter &rewriter, + SmallVectorImpl &outVars, Location loc, + MLIRContext *context) const { 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(init.getType()), noInit); + loc, emitc::LValueType::get(convertedType), noInit); rewriter.create(loc, var.getResult(), init); outVars.push_back(var.getResult()); } @@ -395,24 +400,6 @@ struct WhileLowering : public OpConversionPattern { 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 vars, - ConversionPatternRewriter &rewriter, - Location loc) const { - rewriter.setInsertionPointToStart(block); - - for (auto [arg, var] : llvm::zip(block->getArguments(), vars)) { - Type loadedType = cast(var.getType()).getValueType(); - Value load = rewriter.create(loc, loadedType, var); - arg.replaceAllUsesWith(load); - } - - // Remove arguments after replacement to simplify block structure. - block->eraseArguments(0, block->getNumArguments()); - } - // Transfers final loop state from mutable variables to result variables, // then returns the final SSA values to replace the original scf::while // results. @@ -445,6 +432,7 @@ struct WhileLowering : public OpConversionPattern { 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), @@ -453,12 +441,24 @@ struct WhileLowering : public OpConversionPattern { auto loweredDo = rewriter.create(loc); - // Lower before-region as body. - rewriter.inlineRegionBefore(whileOp.getBefore(), loweredDo.getBodyRegion(), - loweredDo.getBodyRegion().end()); + // 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); - Block *bodyBlock = &loweredDo.getBodyRegion().front(); - replaceBlockArgsWithVarLoads(bodyBlock, vars, rewriter, loc); + // Load current variable values to use as initial arguments for the + // condition block. + SmallVector replacingValues = loadValues(vars, rewriter, loc); + rewriter.mergeBlocks(beforeBlock, bodyBlock, replacingValues); // Convert scf.condition to condition variable assignment. Operation *condTerminator = @@ -472,20 +472,20 @@ struct WhileLowering : public OpConversionPattern { // ifOp if after-region is non-empty. if (whileOp.getAfterBody()->getOperations().size() > 1) { auto ifOp = rewriter.create(loc, condition, false, false); - rewriter.inlineRegionBefore(whileOp.getAfter(), ifOp.getBodyRegion(), - ifOp.getBodyRegion().begin()); - Block *ifBlock = &ifOp.getBodyRegion().front(); + // Prepare the after region (loop body) for merging. + Block *afterBlock = &whileOp.getAfter().front(); + Block *ifBodyBlock = rewriter.createBlock(&ifOp.getBodyRegion()); - // 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)); + // Replacement values for after block using condition op arguments. + SmallVector afterReplacingValues; + for (Value arg : condOp.getArgs()) + afterReplacingValues.push_back(rewriter.getRemappedValue(arg)); - ifBlock->eraseArguments(0, ifBlock->getNumArguments()); + rewriter.mergeBlocks(afterBlock, ifBodyBlock, afterReplacingValues); if (failed(lowerYield(whileOp, vars, rewriter, - cast(ifBlock->getTerminator())))) + cast(ifBodyBlock->getTerminator())))) return failure(); } @@ -500,13 +500,16 @@ struct WhileLowering : public OpConversionPattern { 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); From cff30128e94f271555b75ea49279235ceee3e614 Mon Sep 17 00:00:00 2001 From: Vlad Lazar Date: Fri, 26 Sep 2025 14:56:49 +0300 Subject: [PATCH 13/15] [mlir][emitc] Fixed a bug with return values in the lowering --- mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp | 79 +++++------- mlir/test/Conversion/SCFToEmitC/while.mlir | 118 +++++++++--------- 2 files changed, 87 insertions(+), 110 deletions(-) diff --git a/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp b/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp index 8e8c75968db55..939cfca806660 100644 --- a/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp +++ b/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp @@ -348,32 +348,32 @@ struct WhileLowering : public OpConversionPattern { 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 variables; - if (failed(createVariablesForLoopCarriedValues(whileOp, rewriter, variables, - loc, context))) - return failure(); - - if (failed(lowerDoWhile(whileOp, variables, context, rewriter, loc))) - 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 resultVariables; if (failed(createVariablesForResults(whileOp, getTypeConverter(), rewriter, - resultVariables))) { + 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); - // Transfer final loop state to result variables and get final SSA results. + // Load the final result values from result variables. SmallVector finalResults = - finalizeLoopResults(resultVariables, variables, rewriter, loc); - + loadValues(resultVariables, rewriter, loc); rewriter.replaceOp(whileOp, finalResults); + return success(); } @@ -382,7 +382,7 @@ struct WhileLowering : public OpConversionPattern { // across iterations without SSA argument passing. LogicalResult createVariablesForLoopCarriedValues( WhileOp whileOp, ConversionPatternRewriter &rewriter, - SmallVectorImpl &outVars, Location loc, + SmallVectorImpl &loopVars, Location loc, MLIRContext *context) const { emitc::OpaqueAttr noInit = emitc::OpaqueAttr::get(context, ""); @@ -394,42 +394,15 @@ struct WhileLowering : public OpConversionPattern { emitc::VariableOp var = rewriter.create( loc, emitc::LValueType::get(convertedType), noInit); rewriter.create(loc, var.getResult(), init); - outVars.push_back(var.getResult()); + loopVars.push_back(var.getResult()); } return success(); } - // 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 - finalizeLoopResults(ArrayRef resultVariables, - ArrayRef 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(var.getType()).getValueType(); - Value load = rewriter.create(loc, loadedType, var); - rewriter.create(loc, resultVar, load); - } - - // Replace op with loaded values to integrate with converted SSA graph. - SmallVector finalResults; - for (Value resultVar : resultVariables) { - Type loadedType = - cast(resultVar.getType()).getValueType(); - finalResults.push_back( - rewriter.create(loc, loadedType, resultVar)); - } - - return finalResults; - } - // Lower scf.while to emitc.do. - LogicalResult lowerDoWhile(WhileOp whileOp, ArrayRef vars, - MLIRContext *context, + 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. @@ -457,14 +430,22 @@ struct WhileLowering : public OpConversionPattern { // Load current variable values to use as initial arguments for the // condition block. - SmallVector replacingValues = loadValues(vars, rewriter, loc); + SmallVector replacingValues = loadValues(loopVars, rewriter, loc); rewriter.mergeBlocks(beforeBlock, bodyBlock, replacingValues); - // Convert scf.condition to condition variable assignment. 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); @@ -484,7 +465,7 @@ struct WhileLowering : public OpConversionPattern { rewriter.mergeBlocks(afterBlock, ifBodyBlock, afterReplacingValues); - if (failed(lowerYield(whileOp, vars, rewriter, + if (failed(lowerYield(whileOp, loopVars, rewriter, cast(ifBodyBlock->getTerminator())))) return failure(); } diff --git a/mlir/test/Conversion/SCFToEmitC/while.mlir b/mlir/test/Conversion/SCFToEmitC/while.mlir index a791ed59521e2..f6ea41f2ebb8f 100644 --- a/mlir/test/Conversion/SCFToEmitC/while.mlir +++ b/mlir/test/Conversion/SCFToEmitC/while.mlir @@ -35,30 +35,29 @@ func.func @one_result() -> 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: emitc.assign %[[VAL_0]] : i32 to %[[VAL_3]] : -// CHECK: %[[VAL_4:.*]] = "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_5:.*]] = load %[[VAL_3]] : -// 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]] : -// CHECK: if %[[VAL_7]] { -// CHECK: %[[VAL_9:.*]] = call @payload_one_result(%[[VAL_8]]) : (i32) -> i32 -// CHECK: assign %[[VAL_9]] : i32 to %[[VAL_3]] : +// 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_10:.*]] = expression %[[VAL_4]] : (!emitc.lvalue) -> i1 { -// CHECK: %[[VAL_11:.*]] = load %[[VAL_4]] : -// CHECK: yield %[[VAL_11]] : i1 +// CHECK: %[[VAL_11:.*]] = expression %[[VAL_5]] : (!emitc.lvalue) -> i1 { +// CHECK: %[[VAL_12:.*]] = load %[[VAL_5]] : +// CHECK: yield %[[VAL_12]] : i1 // CHECK: } -// CHECK: yield %[[VAL_10]] : i1 +// CHECK: yield %[[VAL_11]] : i1 // CHECK: } -// CHECK: %[[VAL_12:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue // CHECK: %[[VAL_13:.*]] = emitc.load %[[VAL_3]] : -// CHECK: emitc.assign %[[VAL_13]] : i32 to %[[VAL_12]] : -// CHECK: %[[VAL_14:.*]] = emitc.load %[[VAL_12]] : -// CHECK: return %[[VAL_14]] : i32 +// CHECK: return %[[VAL_13]] : i32 // CHECK: } emitc.func @payload_two_results(%arg: i32) -> i32 { @@ -93,38 +92,36 @@ 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: emitc.assign %[[VAL_0]] : i32 to %[[VAL_2]] : // CHECK: %[[VAL_3:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue -// CHECK: emitc.assign %[[VAL_0]] : i32 to %[[VAL_3]] : -// CHECK: %[[VAL_4:.*]] = "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_5:.*]] = load %[[VAL_2]] : -// CHECK: %[[VAL_6:.*]] = load %[[VAL_3]] : -// 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]] : -// 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]] : -// CHECK: assign %[[VAL_10]] : i32 to %[[VAL_3]] : +// 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_11:.*]] = expression %[[VAL_4]] : (!emitc.lvalue) -> i1 { -// CHECK: %[[VAL_12:.*]] = load %[[VAL_4]] : -// CHECK: yield %[[VAL_12]] : i1 +// CHECK: %[[VAL_13:.*]] = expression %[[VAL_6]] : (!emitc.lvalue) -> i1 { +// CHECK: %[[VAL_14:.*]] = load %[[VAL_6]] : +// CHECK: yield %[[VAL_14]] : i1 // CHECK: } -// CHECK: yield %[[VAL_11]] : i1 +// CHECK: yield %[[VAL_13]] : i1 // CHECK: } -// CHECK: %[[VAL_13:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue -// CHECK: %[[VAL_14:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue // CHECK: %[[VAL_15:.*]] = emitc.load %[[VAL_2]] : -// CHECK: emitc.assign %[[VAL_15]] : i32 to %[[VAL_13]] : // CHECK: %[[VAL_16:.*]] = emitc.load %[[VAL_3]] : -// CHECK: emitc.assign %[[VAL_16]] : i32 to %[[VAL_14]] : -// CHECK: %[[VAL_17:.*]] = emitc.load %[[VAL_13]] : -// CHECK: %[[VAL_18:.*]] = emitc.load %[[VAL_14]] : -// CHECK: return %[[VAL_17]] : i32 +// CHECK: return %[[VAL_15]] : i32 // CHECK: } emitc.func @payload_double_use(%arg: i32) -> i32 { @@ -175,31 +172,30 @@ func.func @double_use(%p : !emitc.ptr) -> 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: emitc.assign %[[VAL_0]] : i32 to %[[VAL_3]] : -// CHECK: %[[VAL_4:.*]] = "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_5:.*]] = load %[[VAL_3]] : -// CHECK: %[[VAL_6:.*]] = call @foo_with_side_effect(%[[VAL_5]], %[[ARG0]]) : (i32, !emitc.ptr) -> 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]] : -// CHECK: if %[[VAL_9]] { -// CHECK: %[[VAL_10:.*]] = call @payload_double_use(%[[VAL_5]]) : (i32) -> i32 -// CHECK: assign %[[VAL_10]] : i32 to %[[VAL_3]] : +// 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_11:.*]] = expression %[[VAL_4]] : (!emitc.lvalue) -> i1 { -// CHECK: %[[VAL_12:.*]] = load %[[VAL_4]] : -// CHECK: yield %[[VAL_12]] : i1 +// CHECK: %[[VAL_12:.*]] = expression %[[VAL_5]] : (!emitc.lvalue) -> i1 { +// CHECK: %[[VAL_13:.*]] = load %[[VAL_5]] : +// CHECK: yield %[[VAL_13]] : i1 // CHECK: } -// CHECK: yield %[[VAL_11]] : i1 +// CHECK: yield %[[VAL_12]] : i1 // CHECK: } -// CHECK: %[[VAL_13:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue // CHECK: %[[VAL_14:.*]] = emitc.load %[[VAL_3]] : -// CHECK: emitc.assign %[[VAL_14]] : i32 to %[[VAL_13]] : -// CHECK: %[[VAL_15:.*]] = emitc.load %[[VAL_13]] : -// CHECK: return %[[VAL_15]] : i32 +// CHECK: return %[[VAL_14]] : i32 // CHECK: } emitc.func @payload_empty_after_region() -> i1 { From fd03998012a745a6c61eee16e7a32d0927b70094 Mon Sep 17 00:00:00 2001 From: Vlad Lazar Date: Mon, 29 Sep 2025 16:31:46 +0300 Subject: [PATCH 14/15] [mlir][emitc] Added the ability to do empty bodyBlock --- mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp | 5 +- mlir/lib/Dialect/EmitC/IR/EmitC.cpp | 6 +- mlir/test/Conversion/SCFToEmitC/while.mlir | 59 +++++++++++++++++++ mlir/test/Dialect/EmitC/invalid_ops.mlir | 21 ------- mlir/test/Target/Cpp/do.mlir | 28 +++++++++ 5 files changed, 94 insertions(+), 25 deletions(-) diff --git a/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp b/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp index 939cfca806660..519d9c8f835f2 100644 --- a/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp +++ b/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp @@ -384,6 +384,9 @@ struct WhileLowering : public OpConversionPattern { 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()) { @@ -394,7 +397,7 @@ struct WhileLowering : public OpConversionPattern { emitc::VariableOp var = rewriter.create( loc, emitc::LValueType::get(convertedType), noInit); rewriter.create(loc, var.getResult(), init); - loopVars.push_back(var.getResult()); + loopVars.push_back(var); } return success(); diff --git a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp index 7100ed46294a0..fccbe46b438d4 100644 --- a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp +++ b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp @@ -1617,9 +1617,6 @@ LogicalResult emitc::DoOp::verify() { "'emitc.expression' from this condition region"); Block &bodyBlock = getBodyRegion().front(); - if (bodyBlock.empty()) - return emitOpError("body region cannot be empty"); - if (bodyBlock.mightHaveTerminator()) return emitOpError("body region must not contain terminator"); @@ -1634,6 +1631,9 @@ ParseResult DoOp::parse(OpAsmParser &parser, OperationState &result) { parser.parseRegion(*condRegion)) return failure(); + if (bodyRegion->empty()) + bodyRegion->emplaceBlock(); + return parser.parseOptionalAttrDictWithKeyword(result.attributes); } diff --git a/mlir/test/Conversion/SCFToEmitC/while.mlir b/mlir/test/Conversion/SCFToEmitC/while.mlir index f6ea41f2ebb8f..28524a01f5e51 100644 --- a/mlir/test/Conversion/SCFToEmitC/while.mlir +++ b/mlir/test/Conversion/SCFToEmitC/while.mlir @@ -232,3 +232,62 @@ func.func @empty_after_region() { // 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 9a8aabceec5cb..5f594fb08c43f 100644 --- a/mlir/test/Dialect/EmitC/invalid_ops.mlir +++ b/mlir/test/Dialect/EmitC/invalid_ops.mlir @@ -857,27 +857,6 @@ func.func @test_do(%arg0 : !emitc.ptr) { // ----- -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 { - %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 -} - -// ----- - func.func @test_do(%arg0 : !emitc.ptr) { %1 = emitc.literal "1" : i32 %2 = emitc.literal "2" : i32 diff --git a/mlir/test/Target/Cpp/do.mlir b/mlir/test/Target/Cpp/do.mlir index a9b8ceddcf3a4..b9cfb42572112 100644 --- a/mlir/test/Target/Cpp/do.mlir +++ b/mlir/test/Target/Cpp/do.mlir @@ -134,3 +134,31 @@ emitc.func @emitc_double_do() { return } + + +// CPP-DEFAULT-LABEL: bool payload_do_with_empty_body(int32_t v1, int32_t v2) { +// CPP-DEFAULT: bool v3 = v1 < v2; +// CPP-DEFAULT: return v3; +// CPP-DEFAULT: } +// CPP-DEFAULT: void emitc_do_with_empty_body(int32_t v1, int32_t v2) { +// CPP-DEFAULT: do { +// CPP-DEFAULT: } while (payload_do_with_empty_body(v1, v2)); +// 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 +} From a96120d29867479333d34a35cb1678961bdcc869 Mon Sep 17 00:00:00 2001 From: Vlad Lazar Date: Wed, 1 Oct 2025 14:54:14 +0300 Subject: [PATCH 15/15] [mlir][emitc] Changes after review Deleted unused function DoOp::getRootOp(). The tests have been rewritten with symbolic variable names. --- mlir/include/mlir/Dialect/EmitC/IR/EmitC.td | 2 - mlir/lib/Dialect/EmitC/IR/EmitC.cpp | 6 -- mlir/test/Target/Cpp/do.mlir | 70 +++++++++++---------- 3 files changed, 37 insertions(+), 41 deletions(-) diff --git a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td index 3fc2bd4bb1841..5971c31876689 100644 --- a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td +++ b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td @@ -1817,8 +1817,6 @@ def EmitC_DoOp : EmitC_Op<"do", let hasVerifier = 1; let extraClassDeclaration = [{ - Operation *getRootOp(); - //===------------------------------------------------------------------===// // OpAsmOpInterface Methods //===------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp index fccbe46b438d4..4754f0bfe895e 100644 --- a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp +++ b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp @@ -1565,12 +1565,6 @@ LogicalResult GetFieldOp::verifySymbolUses(SymbolTableCollection &symbolTable) { // DoOp //===----------------------------------------------------------------------===// -Operation *DoOp::getRootOp() { - auto yieldOp = - cast(getConditionRegion().front().getTerminator()); - return yieldOp.getResult().getDefiningOp(); -} - void DoOp::print(OpAsmPrinter &p) { p << ' '; p.printRegion(getBodyRegion(), /*printEntryBlockArgs=*/false); diff --git a/mlir/test/Target/Cpp/do.mlir b/mlir/test/Target/Cpp/do.mlir index b9cfb42572112..38cbc8110a70c 100644 --- a/mlir/test/Target/Cpp/do.mlir +++ b/mlir/test/Target/Cpp/do.mlir @@ -1,14 +1,15 @@ -// RUN: mlir-translate -mlir-to-cpp %s | FileCheck --match-full-lines %s -check-prefix=CPP-DEFAULT +// RUN: mlir-translate -mlir-to-cpp %s | FileCheck %s -check-prefix=CPP-DEFAULT -// CPP-DEFAULT-LABEL: void emitc_do(int32_t* v1) { -// CPP-DEFAULT: int32_t v2 = 0; +// 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", *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: 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: } @@ -36,15 +37,16 @@ emitc.func @emitc_do(%arg0 : !emitc.ptr) { } -// 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-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", *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: 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: } @@ -77,20 +79,20 @@ emitc.func @emitc_do_with_expression(%arg0 : !emitc.ptr) { } -// CPP-DEFAULT-LABEL: void emitc_double_do() { -// CPP-DEFAULT: int32_t v1 = 0; -// CPP-DEFAULT: int32_t v2 = 0; +// 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 v3 = v1; +// CPP-DEFAULT: int32_t [[VAL_3:v[0-9]+]] = [[VAL_1]]; // 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: 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: } @@ -136,13 +138,15 @@ emitc.func @emitc_double_do() { } -// CPP-DEFAULT-LABEL: bool payload_do_with_empty_body(int32_t v1, int32_t v2) { -// CPP-DEFAULT: bool v3 = v1 < v2; -// CPP-DEFAULT: return v3; +// 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(int32_t v1, int32_t v2) { +// 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(v1, v2)); +// CPP-DEFAULT: } while (payload_do_with_empty_body([[VAL_1]], [[VAL_2]])); // CPP-DEFAULT: return; // CPP-DEFAULT: }