[mlir][emitc] Add a structured for operation (#68206)

Add an emitc.for op to the EmitC dialect as a lowering target for
scf.for, replacing its current direct translation to C; The translator
now handles emitc.for instead.
This commit is contained in:
Gil Rapaport 2023-10-26 16:40:18 +03:00 committed by GitHub
parent 585da2651f
commit 2633d94f28
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 426 additions and 122 deletions

View File

@ -31,8 +31,5 @@ translating the following operations:
* `func.constant`
* `func.func`
* `func.return`
* 'scf' Dialect
* `scf.for`
* `scf.yield`
* 'arith' Dialect
* `arith.constant`

View File

@ -246,6 +246,67 @@ def EmitC_DivOp : EmitC_BinaryOp<"div", []> {
let results = (outs FloatIntegerIndexOrOpaqueType);
}
def EmitC_ForOp : EmitC_Op<"for",
[AllTypesMatch<["lowerBound", "upperBound", "step"]>,
SingleBlockImplicitTerminator<"emitc::YieldOp">,
RecursiveMemoryEffects]> {
let summary = "for operation";
let description = [{
The `emitc.for` operation represents a C loop of the following form:
```c++
for (T i = lb; i < ub; i += step) { /* ... */ } // where T is typeof(lb)
```
The operation takes 3 SSA values as operands that represent the lower bound,
upper bound and step respectively, and defines an SSA value for its
induction variable. It has one region capturing the loop body. The induction
variable is represented as an argument of this region. This SSA value is a
signless integer or index. The step is a value of same type.
This operation has no result. The body region must contain exactly one block
that terminates with `emitc.yield`. Calling ForOp::build will create such a
region and insert the terminator implicitly if none is defined, so will the
parsing even in cases when it is absent from the custom format. For example:
```mlir
// Index case.
emitc.for %iv = %lb to %ub step %step {
... // body
}
...
// Integer case.
emitc.for %iv_32 = %lb_32 to %ub_32 step %step_32 : i32 {
... // body
}
```
}];
let arguments = (ins IntegerIndexOrOpaqueType:$lowerBound,
IntegerIndexOrOpaqueType:$upperBound,
IntegerIndexOrOpaqueType:$step);
let results = (outs);
let regions = (region SizedRegion<1>:$region);
let skipDefaultBuilders = 1;
let builders = [
OpBuilder<(ins "Value":$lowerBound, "Value":$upperBound, "Value":$step,
CArg<"function_ref<void(OpBuilder &, Location, Value)>", "nullptr">)>
];
let extraClassDeclaration = [{
using BodyBuilderFn =
function_ref<void(OpBuilder &, Location, Value)>;
Value getInductionVar() { return getBody()->getArgument(0); }
void setLowerBound(Value bound) { getOperation()->setOperand(0, bound); }
void setUpperBound(Value bound) { getOperation()->setOperand(1, bound); }
void setStep(Value step) { getOperation()->setOperand(2, step); }
}];
let hasCanonicalizer = 1;
let hasCustomAssemblyFormat = 1;
let hasRegionVerifier = 1;
}
def EmitC_IncludeOp
: EmitC_Op<"include", [HasParent<"ModuleOp">]> {
let summary = "Include operation";
@ -430,7 +491,8 @@ def EmitC_AssignOp : EmitC_Op<"assign", []> {
let assemblyFormat = "$value `:` type($value) `to` $var `:` type($var) attr-dict";
}
def EmitC_YieldOp : EmitC_Op<"yield", [Pure, Terminator, ParentOneOf<["IfOp"]>]> {
def EmitC_YieldOp : EmitC_Op<"yield",
[Pure, Terminator, ParentOneOf<["IfOp", "ForOp"]>]> {
let summary = "block termination operation";
let description = [{
"yield" terminates blocks within EmitC control-flow operations. Since

View File

@ -37,7 +37,100 @@ struct SCFToEmitCPass : public impl::SCFToEmitCBase<SCFToEmitCPass> {
void runOnOperation() override;
};
// Lower scf::if to emitc::if, implementing return values as emitc::variable's
// Lower scf::for to emitc::for, implementing result values using
// emitc::variable's updated within the loop body.
struct ForLowering : public OpRewritePattern<ForOp> {
using OpRewritePattern<ForOp>::OpRewritePattern;
LogicalResult matchAndRewrite(ForOp forOp,
PatternRewriter &rewriter) const override;
};
// Create an uninitialized emitc::variable op for each result of the given op.
template <typename T>
static SmallVector<Value> createVariablesForResults(T op,
PatternRewriter &rewriter) {
SmallVector<Value> resultVariables;
if (!op.getNumResults())
return resultVariables;
Location loc = op->getLoc();
MLIRContext *context = op.getContext();
OpBuilder::InsertionGuard guard(rewriter);
rewriter.setInsertionPoint(op);
for (OpResult result : op.getResults()) {
Type resultType = result.getType();
emitc::OpaqueAttr noInit = emitc::OpaqueAttr::get(context, "");
emitc::VariableOp var =
rewriter.create<emitc::VariableOp>(loc, resultType, noInit);
resultVariables.push_back(var);
}
return resultVariables;
}
// Create a series of assign ops assigning given values to given variables at
// the current insertion point of given rewriter.
static void assignValues(ValueRange values, SmallVector<Value> &variables,
PatternRewriter &rewriter, Location loc) {
for (auto [value, var] : llvm::zip(values, variables))
rewriter.create<emitc::AssignOp>(loc, var, value);
}
static void lowerYield(SmallVector<Value> &resultVariables,
PatternRewriter &rewriter, scf::YieldOp yield) {
Location loc = yield.getLoc();
ValueRange operands = yield.getOperands();
OpBuilder::InsertionGuard guard(rewriter);
rewriter.setInsertionPoint(yield);
assignValues(operands, resultVariables, rewriter, loc);
rewriter.create<emitc::YieldOp>(loc);
rewriter.eraseOp(yield);
}
LogicalResult ForLowering::matchAndRewrite(ForOp forOp,
PatternRewriter &rewriter) const {
Location loc = forOp.getLoc();
// Create an emitc::variable op for each result. These variables will be
// assigned to by emitc::assign ops within the loop body.
SmallVector<Value> resultVariables =
createVariablesForResults(forOp, rewriter);
SmallVector<Value> iterArgsVariables =
createVariablesForResults(forOp, rewriter);
assignValues(forOp.getInits(), iterArgsVariables, rewriter, loc);
emitc::ForOp loweredFor = rewriter.create<emitc::ForOp>(
loc, forOp.getLowerBound(), forOp.getUpperBound(), forOp.getStep());
Block *loweredBody = loweredFor.getBody();
// Erase the auto-generated terminator for the lowered for op.
rewriter.eraseOp(loweredBody->getTerminator());
SmallVector<Value> replacingValues;
replacingValues.push_back(loweredFor.getInductionVar());
replacingValues.append(iterArgsVariables.begin(), iterArgsVariables.end());
rewriter.mergeBlocks(forOp.getBody(), loweredBody, replacingValues);
lowerYield(iterArgsVariables, rewriter,
cast<scf::YieldOp>(loweredBody->getTerminator()));
// Copy iterArgs into results after the for loop.
assignValues(iterArgsVariables, resultVariables, rewriter, loc);
rewriter.replaceOp(forOp, resultVariables);
return success();
}
// Lower scf::if to emitc::if, implementing result values as emitc::variable's
// updated within the then and else regions.
struct IfLowering : public OpRewritePattern<IfOp> {
using OpRewritePattern<IfOp>::OpRewritePattern;
@ -52,20 +145,10 @@ LogicalResult IfLowering::matchAndRewrite(IfOp ifOp,
PatternRewriter &rewriter) const {
Location loc = ifOp.getLoc();
SmallVector<Value> resultVariables;
// Create an emitc::variable op for each result. These variables will be
// assigned to by emitc::assign ops within the then & else regions.
if (ifOp.getNumResults()) {
MLIRContext *context = ifOp.getContext();
rewriter.setInsertionPoint(ifOp);
for (OpResult result : ifOp.getResults()) {
Type resultType = result.getType();
auto noInit = emitc::OpaqueAttr::get(context, "");
auto var = rewriter.create<emitc::VariableOp>(loc, resultType, noInit);
resultVariables.push_back(var);
}
}
SmallVector<Value> resultVariables =
createVariablesForResults(ifOp, rewriter);
// Utility function to lower the contents of an scf::if region to an emitc::if
// region. The contents of the scf::if regions is moved into the respective
@ -76,16 +159,7 @@ LogicalResult IfLowering::matchAndRewrite(IfOp ifOp,
Region &loweredRegion) {
rewriter.inlineRegionBefore(region, loweredRegion, loweredRegion.end());
Operation *terminator = loweredRegion.back().getTerminator();
Location terminatorLoc = terminator->getLoc();
ValueRange terminatorOperands = terminator->getOperands();
rewriter.setInsertionPointToEnd(&loweredRegion.back());
for (auto value2Var : llvm::zip(terminatorOperands, resultVariables)) {
Value resultValue = std::get<0>(value2Var);
Value resultVar = std::get<1>(value2Var);
rewriter.create<emitc::AssignOp>(terminatorLoc, resultVar, resultValue);
}
rewriter.create<emitc::YieldOp>(terminatorLoc);
rewriter.eraseOp(terminator);
lowerYield(resultVariables, rewriter, cast<scf::YieldOp>(terminator));
};
Region &thenRegion = ifOp.getThenRegion();
@ -109,6 +183,7 @@ LogicalResult IfLowering::matchAndRewrite(IfOp ifOp,
}
void mlir::populateSCFToEmitCConversionPatterns(RewritePatternSet &patterns) {
patterns.add<ForLowering>(patterns.getContext());
patterns.add<IfLowering>(patterns.getContext());
}
@ -118,7 +193,7 @@ void SCFToEmitCPass::runOnOperation() {
// Configure conversion to lower out SCF operations.
ConversionTarget target(getContext());
target.addIllegalOp<scf::IfOp>();
target.addIllegalOp<scf::ForOp, scf::IfOp>();
target.markUnknownOpDynamicallyLegal([](Operation *) { return true; });
if (failed(
applyPartialConversion(getOperation(), target, std::move(patterns))))

View File

@ -189,6 +189,101 @@ LogicalResult emitc::ConstantOp::verify() {
OpFoldResult emitc::ConstantOp::fold(FoldAdaptor adaptor) { return getValue(); }
//===----------------------------------------------------------------------===//
// ForOp
//===----------------------------------------------------------------------===//
void ForOp::build(OpBuilder &builder, OperationState &result, Value lb,
Value ub, Value step, BodyBuilderFn bodyBuilder) {
result.addOperands({lb, ub, step});
Type t = lb.getType();
Region *bodyRegion = result.addRegion();
bodyRegion->push_back(new Block);
Block &bodyBlock = bodyRegion->front();
bodyBlock.addArgument(t, result.location);
// Create the default terminator if the builder is not provided.
if (!bodyBuilder) {
ForOp::ensureTerminator(*bodyRegion, builder, result.location);
} else {
OpBuilder::InsertionGuard guard(builder);
builder.setInsertionPointToStart(&bodyBlock);
bodyBuilder(builder, result.location, bodyBlock.getArgument(0));
}
}
void ForOp::getCanonicalizationPatterns(RewritePatternSet &, MLIRContext *) {}
ParseResult ForOp::parse(OpAsmParser &parser, OperationState &result) {
Builder &builder = parser.getBuilder();
Type type;
OpAsmParser::Argument inductionVariable;
OpAsmParser::UnresolvedOperand lb, ub, step;
// Parse the induction variable followed by '='.
if (parser.parseOperand(inductionVariable.ssaName) || parser.parseEqual() ||
// Parse loop bounds.
parser.parseOperand(lb) || parser.parseKeyword("to") ||
parser.parseOperand(ub) || parser.parseKeyword("step") ||
parser.parseOperand(step))
return failure();
// Parse the optional initial iteration arguments.
SmallVector<OpAsmParser::Argument, 4> regionArgs;
SmallVector<OpAsmParser::UnresolvedOperand, 4> operands;
regionArgs.push_back(inductionVariable);
// Parse optional type, else assume Index.
if (parser.parseOptionalColon())
type = builder.getIndexType();
else if (parser.parseType(type))
return failure();
// Resolve input operands.
regionArgs.front().type = type;
if (parser.resolveOperand(lb, type, result.operands) ||
parser.resolveOperand(ub, type, result.operands) ||
parser.resolveOperand(step, type, result.operands))
return failure();
// Parse the body region.
Region *body = result.addRegion();
if (parser.parseRegion(*body, regionArgs))
return failure();
ForOp::ensureTerminator(*body, builder, result.location);
// Parse the optional attribute list.
if (parser.parseOptionalAttrDict(result.attributes))
return failure();
return success();
}
void ForOp::print(OpAsmPrinter &p) {
p << " " << getInductionVar() << " = " << getLowerBound() << " to "
<< getUpperBound() << " step " << getStep();
p << ' ';
if (Type t = getInductionVar().getType(); !t.isIndex())
p << " : " << t << ' ';
p.printRegion(getRegion(),
/*printEntryBlockArgs=*/false,
/*printBlockTerminators=*/false);
p.printOptionalAttrDict((*this)->getAttrs());
}
LogicalResult ForOp::verifyRegions() {
// Check that the body defines as single block argument for the induction
// variable.
if (getInductionVar().getType() != getLowerBound().getType())
return emitOpError(
"expected induction variable to be same type as bounds and step");
return success();
}
//===----------------------------------------------------------------------===//
// IfOp
//===----------------------------------------------------------------------===//

View File

@ -10,7 +10,6 @@
#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
#include "mlir/Dialect/EmitC/IR/EmitC.h"
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/Dialect/SCF/IR/SCF.h"
#include "mlir/IR/BuiltinOps.h"
#include "mlir/IR/BuiltinTypes.h"
#include "mlir/IR/Dialect.h"
@ -502,30 +501,10 @@ static LogicalResult printOperation(CppEmitter &emitter,
return success();
}
static LogicalResult printOperation(CppEmitter &emitter, scf::ForOp forOp) {
static LogicalResult printOperation(CppEmitter &emitter, emitc::ForOp forOp) {
raw_indented_ostream &os = emitter.ostream();
OperandRange operands = forOp.getInitArgs();
Block::BlockArgListType iterArgs = forOp.getRegionIterArgs();
Operation::result_range results = forOp.getResults();
if (!emitter.shouldDeclareVariablesAtTop()) {
for (OpResult result : results) {
if (failed(emitter.emitVariableDeclaration(result,
/*trailingSemicolon=*/true)))
return failure();
}
}
for (auto pair : llvm::zip(iterArgs, operands)) {
if (failed(emitter.emitType(forOp.getLoc(), std::get<0>(pair).getType())))
return failure();
os << " " << emitter.getOrCreateName(std::get<0>(pair)) << " = ";
os << emitter.getOrCreateName(std::get<1>(pair)) << ";";
os << "\n";
}
os << "for (";
if (failed(
emitter.emitType(forOp.getLoc(), forOp.getInductionVar().getType())))
@ -548,35 +527,14 @@ static LogicalResult printOperation(CppEmitter &emitter, scf::ForOp forOp) {
Region &forRegion = forOp.getRegion();
auto regionOps = forRegion.getOps();
// We skip the trailing yield op because this updates the result variables
// of the for op in the generated code. Instead we update the iterArgs at
// the end of a loop iteration and set the result variables after the for
// loop.
// We skip the trailing yield op.
for (auto it = regionOps.begin(); std::next(it) != regionOps.end(); ++it) {
if (failed(emitter.emitOperation(*it, /*trailingSemicolon=*/true)))
return failure();
}
Operation *yieldOp = forRegion.getBlocks().front().getTerminator();
// Copy yield operands into iterArgs at the end of a loop iteration.
for (auto pair : llvm::zip(iterArgs, yieldOp->getOperands())) {
BlockArgument iterArg = std::get<0>(pair);
Value operand = std::get<1>(pair);
os << emitter.getOrCreateName(iterArg) << " = "
<< emitter.getOrCreateName(operand) << ";\n";
}
os.unindent() << "}";
// Copy iterArgs into results after the for loop.
for (auto pair : llvm::zip(results, iterArgs)) {
OpResult result = std::get<0>(pair);
BlockArgument iterArg = std::get<1>(pair);
os << "\n"
<< emitter.getOrCreateName(result) << " = "
<< emitter.getOrCreateName(iterArg) << ";";
}
return success();
}
@ -617,33 +575,6 @@ static LogicalResult printOperation(CppEmitter &emitter, emitc::IfOp ifOp) {
return success();
}
static LogicalResult printOperation(CppEmitter &emitter, scf::YieldOp yieldOp) {
raw_ostream &os = emitter.ostream();
Operation &parentOp = *yieldOp.getOperation()->getParentOp();
if (yieldOp.getNumOperands() != parentOp.getNumResults()) {
return yieldOp.emitError("number of operands does not to match the number "
"of the parent op's results");
}
if (failed(interleaveWithError(
llvm::zip(parentOp.getResults(), yieldOp.getOperands()),
[&](auto pair) -> LogicalResult {
auto result = std::get<0>(pair);
auto operand = std::get<1>(pair);
os << emitter.getOrCreateName(result) << " = ";
if (!emitter.hasValueInScope(operand))
return yieldOp.emitError("operand value not in scope");
os << emitter.getOrCreateName(operand);
return success();
},
[&]() { os << ";\n"; })))
return failure();
return success();
}
static LogicalResult printOperation(CppEmitter &emitter,
func::ReturnOp returnOp) {
raw_ostream &os = emitter.ostream();
@ -748,10 +679,11 @@ static LogicalResult printOperation(CppEmitter &emitter,
for (Operation &op : block.getOperations()) {
// When generating code for an emitc.if or cf.cond_br op no semicolon
// needs to be printed after the closing brace.
// When generating code for an scf.for op, printing a trailing semicolon
// When generating code for an emitc.for op, printing a trailing semicolon
// is handled within the printOperation function.
bool trailingSemicolon =
!isa<cf::CondBranchOp, emitc::LiteralOp, emitc::IfOp, scf::ForOp>(op);
!isa<cf::CondBranchOp, emitc::ForOp, emitc::IfOp, emitc::LiteralOp>(
op);
if (failed(emitter.emitOperation(
op, /*trailingSemicolon=*/trailingSemicolon)))
@ -1015,15 +947,12 @@ LogicalResult CppEmitter::emitOperation(Operation &op, bool trailingSemicolon) {
// EmitC ops.
.Case<emitc::AddOp, emitc::ApplyOp, emitc::AssignOp, emitc::CallOp,
emitc::CastOp, emitc::CmpOp, emitc::ConstantOp, emitc::DivOp,
emitc::IfOp, emitc::IncludeOp, emitc::MulOp, emitc::RemOp,
emitc::SubOp, emitc::VariableOp>(
emitc::ForOp, emitc::IfOp, emitc::IncludeOp, emitc::MulOp,
emitc::RemOp, emitc::SubOp, emitc::VariableOp>(
[&](auto op) { return printOperation(*this, op); })
// Func ops.
.Case<func::CallOp, func::ConstantOp, func::FuncOp, func::ReturnOp>(
[&](auto op) { return printOperation(*this, op); })
// SCF ops.
.Case<scf::ForOp, scf::YieldOp>(
[&](auto op) { return printOperation(*this, op); })
// Arithmetic ops.
.Case<arith::ConstantOp>(
[&](auto op) { return printOperation(*this, op); })

View File

@ -0,0 +1,96 @@
// RUN: mlir-opt -allow-unregistered-dialect -convert-scf-to-emitc %s | FileCheck %s
func.func @simple_std_for_loop(%arg0 : index, %arg1 : index, %arg2 : index) {
scf.for %i0 = %arg0 to %arg1 step %arg2 {
%c1 = arith.constant 1 : index
}
return
}
// CHECK-LABEL: func.func @simple_std_for_loop(
// CHECK-SAME: %[[VAL_0:.*]]: index, %[[VAL_1:.*]]: index, %[[VAL_2:.*]]: index) {
// CHECK-NEXT: emitc.for %[[VAL_3:.*]] = %[[VAL_0]] to %[[VAL_1]] step %[[VAL_2]] {
// CHECK-NEXT: %[[VAL_4:.*]] = arith.constant 1 : index
// CHECK-NEXT: }
// CHECK-NEXT: return
// CHECK-NEXT: }
func.func @simple_std_2_for_loops(%arg0 : index, %arg1 : index, %arg2 : index) {
scf.for %i0 = %arg0 to %arg1 step %arg2 {
%c1 = arith.constant 1 : index
scf.for %i1 = %arg0 to %arg1 step %arg2 {
%c1_0 = arith.constant 1 : index
}
}
return
}
// CHECK-LABEL: func.func @simple_std_2_for_loops(
// CHECK-SAME: %[[VAL_0:.*]]: index, %[[VAL_1:.*]]: index, %[[VAL_2:.*]]: index) {
// CHECK-NEXT: emitc.for %[[VAL_3:.*]] = %[[VAL_0]] to %[[VAL_1]] step %[[VAL_2]] {
// CHECK-NEXT: %[[VAL_4:.*]] = arith.constant 1 : index
// CHECK-NEXT: emitc.for %[[VAL_5:.*]] = %[[VAL_0]] to %[[VAL_1]] step %[[VAL_2]] {
// CHECK-NEXT: %[[VAL_6:.*]] = arith.constant 1 : index
// CHECK-NEXT: }
// CHECK-NEXT: }
// CHECK-NEXT: return
// CHECK-NEXT: }
func.func @for_yield(%arg0 : index, %arg1 : index, %arg2 : index) -> (f32, f32) {
%s0 = arith.constant 0.0 : f32
%s1 = arith.constant 1.0 : f32
%result:2 = scf.for %i0 = %arg0 to %arg1 step %arg2 iter_args(%si = %s0, %sj = %s1) -> (f32, f32) {
%sn = arith.addf %si, %sj : f32
scf.yield %sn, %sn : f32, f32
}
return %result#0, %result#1 : f32, f32
}
// CHECK-LABEL: func.func @for_yield(
// CHECK-SAME: %[[VAL_0:.*]]: index, %[[VAL_1:.*]]: index, %[[VAL_2:.*]]: index) -> (f32, f32) {
// CHECK-NEXT: %[[VAL_3:.*]] = arith.constant 0.000000e+00 : f32
// CHECK-NEXT: %[[VAL_4:.*]] = arith.constant 1.000000e+00 : f32
// CHECK-NEXT: %[[VAL_5:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> f32
// CHECK-NEXT: %[[VAL_6:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> f32
// CHECK-NEXT: %[[VAL_7:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> f32
// CHECK-NEXT: %[[VAL_8:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> f32
// CHECK-NEXT: emitc.assign %[[VAL_3]] : f32 to %[[VAL_7]] : f32
// CHECK-NEXT: emitc.assign %[[VAL_4]] : f32 to %[[VAL_8]] : f32
// CHECK-NEXT: emitc.for %[[VAL_9:.*]] = %[[VAL_0]] to %[[VAL_1]] step %[[VAL_2]] {
// CHECK-NEXT: %[[VAL_10:.*]] = arith.addf %[[VAL_7]], %[[VAL_8]] : f32
// CHECK-NEXT: emitc.assign %[[VAL_10]] : f32 to %[[VAL_7]] : f32
// CHECK-NEXT: emitc.assign %[[VAL_10]] : f32 to %[[VAL_8]] : f32
// CHECK-NEXT: }
// CHECK-NEXT: emitc.assign %[[VAL_7]] : f32 to %[[VAL_5]] : f32
// CHECK-NEXT: emitc.assign %[[VAL_8]] : f32 to %[[VAL_6]] : f32
// CHECK-NEXT: return %[[VAL_5]], %[[VAL_6]] : f32, f32
// CHECK-NEXT: }
func.func @nested_for_yield(%arg0 : index, %arg1 : index, %arg2 : index) -> f32 {
%s0 = arith.constant 1.0 : f32
%r = scf.for %i0 = %arg0 to %arg1 step %arg2 iter_args(%iter = %s0) -> (f32) {
%result = scf.for %i1 = %arg0 to %arg1 step %arg2 iter_args(%si = %iter) -> (f32) {
%sn = arith.addf %si, %si : f32
scf.yield %sn : f32
}
scf.yield %result : f32
}
return %r : f32
}
// CHECK-LABEL: func.func @nested_for_yield(
// CHECK-SAME: %[[VAL_0:.*]]: index, %[[VAL_1:.*]]: index, %[[VAL_2:.*]]: index) -> f32 {
// CHECK-NEXT: %[[VAL_3:.*]] = arith.constant 1.000000e+00 : f32
// CHECK-NEXT: %[[VAL_4:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> f32
// CHECK-NEXT: %[[VAL_5:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> f32
// CHECK-NEXT: emitc.assign %[[VAL_3]] : f32 to %[[VAL_5]] : f32
// CHECK-NEXT: emitc.for %[[VAL_6:.*]] = %[[VAL_0]] to %[[VAL_1]] step %[[VAL_2]] {
// CHECK-NEXT: %[[VAL_7:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> f32
// CHECK-NEXT: %[[VAL_8:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> f32
// CHECK-NEXT: emitc.assign %[[VAL_5]] : f32 to %[[VAL_8]] : f32
// CHECK-NEXT: emitc.for %[[VAL_9:.*]] = %[[VAL_0]] to %[[VAL_1]] step %[[VAL_2]] {
// CHECK-NEXT: %[[VAL_10:.*]] = arith.addf %[[VAL_8]], %[[VAL_8]] : f32
// CHECK-NEXT: emitc.assign %[[VAL_10]] : f32 to %[[VAL_8]] : f32
// CHECK-NEXT: }
// CHECK-NEXT: emitc.assign %[[VAL_8]] : f32 to %[[VAL_7]] : f32
// CHECK-NEXT: emitc.assign %[[VAL_7]] : f32 to %[[VAL_5]] : f32
// CHECK-NEXT: }
// CHECK-NEXT: emitc.assign %[[VAL_5]] : f32 to %[[VAL_4]] : f32
// CHECK-NEXT: return %[[VAL_4]] : f32
// CHECK-NEXT: }

View File

@ -203,7 +203,7 @@ func.func @sub_pointer_pointer(%arg0: !emitc.ptr<f32>, %arg1: !emitc.ptr<f32>) {
// -----
func.func @test_misplaced_yield() {
// expected-error @+1 {{'emitc.yield' op expects parent op 'emitc.if'}}
// expected-error @+1 {{'emitc.yield' op expects parent op to be one of 'emitc.if, emitc.for'}}
emitc.yield
return
}

View File

@ -105,7 +105,7 @@ func.func @test_if(%arg0: i1, %arg1: f32) {
return
}
func.func @test_explicit_yield(%arg0: i1, %arg1: f32) {
func.func @test_if_explicit_yield(%arg0: i1, %arg1: f32) {
emitc.if %arg0 {
%0 = emitc.call "func_const"(%arg1) : (f32) -> i32
emitc.yield
@ -127,3 +127,25 @@ func.func @test_assign(%arg1: f32) {
emitc.assign %arg1 : f32 to %v : f32
return
}
func.func @test_for(%arg0 : index, %arg1 : index, %arg2 : index) {
emitc.for %i0 = %arg0 to %arg1 step %arg2 {
%0 = emitc.call "func_const"(%i0) : (index) -> i32
}
return
}
func.func @test_for_explicit_yield(%arg0 : index, %arg1 : index, %arg2 : index) {
emitc.for %i0 = %arg0 to %arg1 step %arg2 {
%0 = emitc.call "func_const"(%i0) : (index) -> i32
emitc.yield
}
return
}
func.func @test_for_not_index_induction(%arg0 : i16, %arg1 : i16, %arg2 : i16) {
emitc.for %i0 = %arg0 to %arg1 step %arg2 : i16 {
%0 = emitc.call "func_const"(%i0) : (i16) -> i32
}
return
}

View File

@ -2,7 +2,7 @@
// RUN: mlir-translate -mlir-to-cpp -declare-variables-at-top %s | FileCheck %s -check-prefix=CPP-DECLTOP
func.func @test_for(%arg0 : index, %arg1 : index, %arg2 : index) {
scf.for %i0 = %arg0 to %arg1 step %arg2 {
emitc.for %i0 = %arg0 to %arg1 step %arg2 {
%0 = emitc.call "f"() : () -> i32
}
return
@ -28,11 +28,21 @@ func.func @test_for_yield() {
%s0 = arith.constant 0 : i32
%p0 = arith.constant 1.0 : f32
%result:2 = scf.for %iter = %start to %stop step %step iter_args(%si = %s0, %pi = %p0) -> (i32, f32) {
%sn = emitc.call "add"(%si, %iter) : (i32, index) -> i32
%pn = emitc.call "mul"(%pi, %iter) : (f32, index) -> f32
scf.yield %sn, %pn : i32, f32
%0 = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> i32
%1 = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> f32
%2 = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> i32
%3 = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> f32
emitc.assign %s0 : i32 to %2 : i32
emitc.assign %p0 : f32 to %3 : f32
emitc.for %iter = %start to %stop step %step {
%sn = emitc.call "add"(%2, %iter) : (i32, index) -> i32
%pn = emitc.call "mul"(%3, %iter) : (f32, index) -> f32
emitc.assign %sn : i32 to %2 : i32
emitc.assign %pn : f32 to %3 : f32
emitc.yield
}
emitc.assign %2 : i32 to %0 : i32
emitc.assign %3 : f32 to %1 : f32
return
}
@ -44,8 +54,10 @@ func.func @test_for_yield() {
// CPP-DEFAULT-NEXT: float [[P0:[^ ]*]] = (float)1.000000000e+00;
// CPP-DEFAULT-NEXT: int32_t [[SE:[^ ]*]];
// CPP-DEFAULT-NEXT: float [[PE:[^ ]*]];
// CPP-DEFAULT-NEXT: int32_t [[SI:[^ ]*]] = [[S0]];
// CPP-DEFAULT-NEXT: float [[PI:[^ ]*]] = [[P0]];
// CPP-DEFAULT-NEXT: int32_t [[SI:[^ ]*]];
// CPP-DEFAULT-NEXT: float [[PI:[^ ]*]];
// CPP-DEFAULT-NEXT: [[SI:[^ ]*]] = [[S0]];
// CPP-DEFAULT-NEXT: [[PI:[^ ]*]] = [[P0]];
// CPP-DEFAULT-NEXT: for (size_t [[ITER:[^ ]*]] = [[START]]; [[ITER]] < [[STOP]]; [[ITER]] += [[STEP]]) {
// CPP-DEFAULT-NEXT: int32_t [[SN:[^ ]*]] = add([[SI]], [[ITER]]);
// CPP-DEFAULT-NEXT: float [[PN:[^ ]*]] = mul([[PI]], [[ITER]]);
@ -64,6 +76,8 @@ func.func @test_for_yield() {
// CPP-DECLTOP-NEXT: float [[P0:[^ ]*]];
// CPP-DECLTOP-NEXT: int32_t [[SE:[^ ]*]];
// CPP-DECLTOP-NEXT: float [[PE:[^ ]*]];
// CPP-DECLTOP-NEXT: int32_t [[SI:[^ ]*]];
// CPP-DECLTOP-NEXT: float [[PI:[^ ]*]];
// CPP-DECLTOP-NEXT: int32_t [[SN:[^ ]*]];
// CPP-DECLTOP-NEXT: float [[PN:[^ ]*]];
// CPP-DECLTOP-NEXT: [[START]] = 0;
@ -71,8 +85,12 @@ func.func @test_for_yield() {
// CPP-DECLTOP-NEXT: [[STEP]] = 1;
// CPP-DECLTOP-NEXT: [[S0]] = 0;
// CPP-DECLTOP-NEXT: [[P0]] = (float)1.000000000e+00;
// CPP-DECLTOP-NEXT: int32_t [[SI:[^ ]*]] = [[S0]];
// CPP-DECLTOP-NEXT: float [[PI:[^ ]*]] = [[P0]];
// CPP-DECLTOP-NEXT: ;
// CPP-DECLTOP-NEXT: ;
// CPP-DECLTOP-NEXT: ;
// CPP-DECLTOP-NEXT: ;
// CPP-DECLTOP-NEXT: [[SI:[^ ]*]] = [[S0]];
// CPP-DECLTOP-NEXT: [[PI:[^ ]*]] = [[P0]];
// CPP-DECLTOP-NEXT: for (size_t [[ITER:[^ ]*]] = [[START]]; [[ITER]] < [[STOP]]; [[ITER]] += [[STEP]]) {
// CPP-DECLTOP-NEXT: [[SN]] = add([[SI]], [[ITER]]);
// CPP-DECLTOP-NEXT: [[PN]] = mul([[PI]], [[ITER]]);
@ -91,14 +109,24 @@ func.func @test_for_yield_2() {
%s0 = emitc.literal "0" : i32
%p0 = emitc.literal "M_PI" : f32
%result:2 = scf.for %iter = %start to %stop step %step iter_args(%si = %s0, %pi = %p0) -> (i32, f32) {
%sn = emitc.call "add"(%si, %iter) : (i32, index) -> i32
%pn = emitc.call "mul"(%pi, %iter) : (f32, index) -> f32
scf.yield %sn, %pn : i32, f32
%0 = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> i32
%1 = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> f32
%2 = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> i32
%3 = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> f32
emitc.assign %s0 : i32 to %2 : i32
emitc.assign %p0 : f32 to %3 : f32
emitc.for %iter = %start to %stop step %step {
%sn = emitc.call "add"(%2, %iter) : (i32, index) -> i32
%pn = emitc.call "mul"(%3, %iter) : (f32, index) -> f32
emitc.assign %sn : i32 to %2 : i32
emitc.assign %pn : f32 to %3 : f32
emitc.yield
}
emitc.assign %2 : i32 to %0 : i32
emitc.assign %3 : f32 to %1 : f32
return
}
// CPP-DEFAULT: void test_for_yield_2() {
// CPP-DEFAULT: float{{.*}}= M_PI
// CPP-DEFAULT: {{.*}}= M_PI
// CPP-DEFAULT: for (size_t [[IN:.*]] = 0; [[IN]] < 10; [[IN]] += 1) {