| //===- SCFOps.td - Structured Control Flow operations ------*- tablegen -*-===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // Defines MLIR structured control flow operations. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #ifndef MLIR_DIALECT_SCF_SCFOPS |
| #define MLIR_DIALECT_SCF_SCFOPS |
| |
| include "mlir/Interfaces/ControlFlowInterfaces.td" |
| include "mlir/Interfaces/LoopLikeInterface.td" |
| include "mlir/Interfaces/SideEffectInterfaces.td" |
| |
| def SCF_Dialect : Dialect { |
| let name = "scf"; |
| let cppNamespace = "::mlir::scf"; |
| let dependentDialects = ["arith::ArithmeticDialect"]; |
| } |
| |
| // Base class for SCF dialect ops. |
| class SCF_Op<string mnemonic, list<OpTrait> traits = []> : |
| Op<SCF_Dialect, mnemonic, traits> { |
| // For every standard op, there needs to be a: |
| // * void print(OpAsmPrinter &p, ${C++ class of Op} op) |
| // * LogicalResult verify(${C++ class of Op} op) |
| // * ParseResult parse${C++ class of Op}(OpAsmParser &parser, |
| // OperationState &result) |
| // functions. |
| let printer = [{ return ::print(p, *this); }]; |
| let verifier = [{ return ::verify(*this); }]; |
| let parser = [{ return ::parse$cppClass(parser, result); }]; |
| } |
| |
| def ConditionOp : SCF_Op<"condition", [ |
| HasParent<"WhileOp">, |
| DeclareOpInterfaceMethods<RegionBranchTerminatorOpInterface>, |
| NoSideEffect, |
| Terminator |
| ]> { |
| let summary = "loop continuation condition"; |
| let description = [{ |
| This operation accepts the continuation (i.e., inverse of exit) condition |
| of the `scf.while` construct. If its first argument is true, the "after" |
| region of `scf.while` is executed, with the remaining arguments forwarded |
| to the entry block of the region. Otherwise, the loop terminates. |
| }]; |
| |
| let arguments = (ins I1:$condition, Variadic<AnyType>:$args); |
| |
| let assemblyFormat = |
| [{ `(` $condition `)` attr-dict ($args^ `:` type($args))? }]; |
| |
| // Override the default verifier, everything is checked by traits. |
| let verifier = ?; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // ExecuteRegionOp |
| //===----------------------------------------------------------------------===// |
| |
| def ExecuteRegionOp : SCF_Op<"execute_region"> { |
| let summary = "operation that executes its region exactly once"; |
| let description = [{ |
| The `execute_region` operation is used to allow multiple blocks within SCF |
| and other operations which can hold only one block. The `execute_region` |
| operation executes the region held exactly once and cannot have any operands. |
| As such, its region has no arguments. All SSA values that dominate the op can |
| be accessed inside the op. The op's region can have multiple blocks and the |
| blocks can have multiple distinct terminators. Values returned from this op's |
| region define the op's results. |
| |
| Example: |
| |
| ```mlir |
| scf.for %i = 0 to 128 step %c1 { |
| %y = scf.execute_region -> i32 { |
| %x = load %A[%i] : memref<128xi32> |
| scf.yield %x : i32 |
| } |
| } |
| |
| affine.for %i = 0 to 100 { |
| "foo"() : () -> () |
| %v = scf.execute_region -> i64 { |
| cond_br %cond, ^bb1, ^bb2 |
| |
| ^bb1: |
| %c1 = arith.constant 1 : i64 |
| br ^bb3(%c1 : i64) |
| |
| ^bb2: |
| %c2 = arith.constant 2 : i64 |
| br ^bb3(%c2 : i64) |
| |
| ^bb3(%x : i64): |
| scf.yield %x : i64 |
| } |
| "bar"(%v) : (i64) -> () |
| } |
| ``` |
| }]; |
| |
| let results = (outs Variadic<AnyType>); |
| |
| let regions = (region AnyRegion:$region); |
| |
| let hasCanonicalizer = 1; |
| |
| let hasFolder = 0; |
| } |
| |
| def ForOp : SCF_Op<"for", |
| [DeclareOpInterfaceMethods<LoopLikeOpInterface>, |
| DeclareOpInterfaceMethods<RegionBranchOpInterface>, |
| SingleBlockImplicitTerminator<"scf::YieldOp">, |
| RecursiveSideEffects]> { |
| let summary = "for operation"; |
| let description = [{ |
| The "scf.for" operation represents a loop taking 3 SSA value as operands |
| that represent the lower bound, upper bound and step respectively. The |
| operation 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 always has type index, which is the |
| size of the machine word. The step is a value of type index, required to be |
| positive. |
| The lower and upper bounds specify a half-open range: the range includes |
| the lower bound but does not include the upper bound. |
| |
| The body region must contain exactly one block that terminates with |
| "scf.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 |
| scf.for %iv = %lb to %ub step %step { |
| ... // body |
| } |
| ``` |
| |
| `scf.for` can also operate on loop-carried variables and returns the final |
| values after loop termination. The initial values of the variables are |
| passed as additional SSA operands to the "scf.for" following the 3 loop |
| control SSA values mentioned above (lower bound, upper bound and step). The |
| operation region has an argument for the induction variable, followed by |
| one argument for each loop-carried variable, representing the value of the |
| variable at the current iteration. |
| |
| The region must terminate with a "scf.yield" that passes the current |
| values of all loop-carried variables to the next iteration, or to the |
| "scf.for" result, if at the last iteration. The static type of a |
| loop-carried variable may not change with iterations; its runtime type is |
| allowed to change. Note, that when the loop-carried variables are present, |
| calling ForOp::build will not insert the terminator implicitly. The caller |
| must insert "scf.yield" in that case. |
| |
| "scf.for" results hold the final values after the last iteration. |
| For example, to sum-reduce a memref: |
| |
| ```mlir |
| func @reduce(%buffer: memref<1024xf32>, %lb: index, |
| %ub: index, %step: index) -> (f32) { |
| // Initial sum set to 0. |
| %sum_0 = arith.constant 0.0 : f32 |
| // iter_args binds initial values to the loop's region arguments. |
| %sum = scf.for %iv = %lb to %ub step %step |
| iter_args(%sum_iter = %sum_0) -> (f32) { |
| %t = load %buffer[%iv] : memref<1024xf32> |
| %sum_next = arith.addf %sum_iter, %t : f32 |
| // Yield current iteration sum to next iteration %sum_iter or to %sum |
| // if final iteration. |
| scf.yield %sum_next : f32 |
| } |
| return %sum : f32 |
| } |
| ``` |
| |
| If the "scf.for" defines any values, a yield must be explicitly present. |
| The number and types of the "scf.for" results must match the initial |
| values in the "iter_args" binding and the yield operands. |
| |
| Another example with a nested "scf.if" (see "scf.if" for details) to |
| perform conditional reduction: |
| |
| ```mlir |
| func @conditional_reduce(%buffer: memref<1024xf32>, %lb: index, |
| %ub: index, %step: index) -> (f32) { |
| %sum_0 = arith.constant 0.0 : f32 |
| %c0 = arith.constant 0.0 : f32 |
| %sum = scf.for %iv = %lb to %ub step %step |
| iter_args(%sum_iter = %sum_0) -> (f32) { |
| %t = load %buffer[%iv] : memref<1024xf32> |
| %cond = arith.cmpf "ugt", %t, %c0 : f32 |
| %sum_next = scf.if %cond -> (f32) { |
| %new_sum = arith.addf %sum_iter, %t : f32 |
| scf.yield %new_sum : f32 |
| } else { |
| scf.yield %sum_iter : f32 |
| } |
| scf.yield %sum_next : f32 |
| } |
| return %sum : f32 |
| } |
| ``` |
| }]; |
| let arguments = (ins Index:$lowerBound, |
| Index:$upperBound, |
| Index:$step, |
| Variadic<AnyType>:$initArgs); |
| let results = (outs Variadic<AnyType>:$results); |
| let regions = (region SizedRegion<1>:$region); |
| |
| let skipDefaultBuilders = 1; |
| let builders = [ |
| OpBuilder<(ins "Value":$lowerBound, "Value":$upperBound, "Value":$step, |
| CArg<"ValueRange", "llvm::None">:$iterArgs, |
| CArg<"function_ref<void(OpBuilder &, Location, Value, ValueRange)>", |
| "nullptr">)> |
| ]; |
| |
| let extraClassDeclaration = [{ |
| using BodyBuilderFn = |
| function_ref<void(OpBuilder &, Location, Value, ValueRange)>; |
| |
| Value getInductionVar() { return getBody()->getArgument(0); } |
| Block::BlockArgListType getRegionIterArgs() { |
| return getBody()->getArguments().drop_front(getNumInductionVars()); |
| } |
| Operation::operand_range getIterOperands() { |
| return getOperands().drop_front(getNumControlOperands()); |
| } |
| MutableArrayRef<OpOperand> getIterOpOperands() { |
| return |
| getOperation()->getOpOperands().drop_front(getNumControlOperands()); |
| } |
| |
| void setLowerBound(Value bound) { getOperation()->setOperand(0, bound); } |
| void setUpperBound(Value bound) { getOperation()->setOperand(1, bound); } |
| void setStep(Value step) { getOperation()->setOperand(2, step); } |
| |
| /// Number of induction variables, always 1 for scf::ForOp. |
| unsigned getNumInductionVars() { return 1; } |
| /// Number of region arguments for loop-carried values |
| unsigned getNumRegionIterArgs() { |
| return getBody()->getNumArguments() - getNumInductionVars(); |
| } |
| /// Number of operands controlling the loop: lb, ub, step |
| unsigned getNumControlOperands() { return 3; } |
| /// Does the operation hold operands for loop-carried values |
| bool hasIterOperands() { |
| return getOperation()->getNumOperands() > getNumControlOperands(); |
| } |
| /// Get Number of loop-carried values |
| unsigned getNumIterOperands() { |
| return getOperation()->getNumOperands() - getNumControlOperands(); |
| } |
| /// Get the region iter arg that corresponds to an OpOperand. |
| /// This helper prevents internal op implementation detail leakage to |
| /// clients by hiding the operand / block argument mapping. |
| BlockArgument getRegionIterArgForOpOperand(OpOperand &opOperand) { |
| assert(opOperand.getOperandNumber() >= getNumControlOperands() && |
| "expected an iter args operand"); |
| assert(opOperand.getOwner() == getOperation() && |
| "opOperand does not belong to this scf::ForOp operation"); |
| return getRegionIterArgs()[ |
| opOperand.getOperandNumber() - getNumControlOperands()]; |
| } |
| /// Get the OpOperand& that corresponds to a region iter arg. |
| /// This helper prevents internal op implementation detail leakage to |
| /// clients by hiding the operand / block argument mapping. |
| OpOperand &getOpOperandForRegionIterArg(BlockArgument bbArg) { |
| assert(bbArg.getArgNumber() >= getNumInductionVars() && |
| "expected a bbArg that is not an induction variable"); |
| assert(bbArg.getOwner()->getParentOp() == getOperation() && |
| "bbArg does not belong to the scf::ForOp body"); |
| return getOperation()->getOpOperand( |
| getNumControlOperands() + bbArg.getArgNumber() - getNumInductionVars()); |
| } |
| /// Get the OpResult that corresponds to an OpOperand. |
| /// Assert that opOperand is an iterArg. |
| /// This helper prevents internal op implementation detail leakage to |
| /// clients by hiding the operand / block argument mapping. |
| OpResult getResultForOpOperand(OpOperand &opOperand) { |
| assert(opOperand.getOperandNumber() >= getNumControlOperands() && |
| "expected an iter args operand"); |
| assert(opOperand.getOwner() == getOperation() && |
| "opOperand does not belong to this scf::ForOp operation"); |
| return getOperation()->getResult( |
| opOperand.getOperandNumber() - getNumControlOperands()); |
| } |
| /// Get the OpOperand& that corresponds to an OpResultOpOperand. |
| /// This helper prevents internal op implementation detail leakage to |
| /// clients by hiding the operand / block argument mapping. |
| OpOperand &getOpOperandForResult(OpResult opResult) { |
| assert(opResult.getDefiningOp() == getOperation() && |
| "opResult does not belong to the scf::ForOp operation"); |
| return getOperation()->getOpOperand( |
| getNumControlOperands() + opResult.getResultNumber()); |
| } |
| |
| /// Return operands used when entering the region at 'index'. These operands |
| /// correspond to the loop iterator operands, i.e., those exclusing the |
| /// induction variable. LoopOp only has one region, so 0 is the only valid |
| /// value for `index`. |
| OperandRange getSuccessorEntryOperands(unsigned index); |
| |
| /// Returns the number of invocations of the body block if the loop bounds |
| /// are constants. Returns `kUnknownNumRegionInvocations` otherwise. |
| void getNumRegionInvocations(ArrayRef<Attribute> operands, |
| SmallVectorImpl<int64_t> &countPerRegion); |
| }]; |
| |
| let hasCanonicalizer = 1; |
| } |
| |
| def IfOp : SCF_Op<"if", |
| [DeclareOpInterfaceMethods<RegionBranchOpInterface>, |
| SingleBlockImplicitTerminator<"scf::YieldOp">, RecursiveSideEffects, |
| NoRegionArguments]> { |
| let summary = "if-then-else operation"; |
| let description = [{ |
| The `scf.if` operation represents an if-then-else construct for |
| conditionally executing two regions of code. The operand to an if operation |
| is a boolean value. For example: |
| |
| ```mlir |
| scf.if %b { |
| ... |
| } else { |
| ... |
| } |
| ``` |
| |
| `scf.if` may also return results that are defined in its regions. The |
| values defined are determined by which execution path is taken. |
| |
| Example: |
| |
| ```mlir |
| %x, %y = scf.if %b -> (f32, f32) { |
| %x_true = ... |
| %y_true = ... |
| scf.yield %x_true, %y_true : f32, f32 |
| } else { |
| %x_false = ... |
| %y_false = ... |
| scf.yield %x_false, %y_false : f32, f32 |
| } |
| ``` |
| |
| `scf.if` regions are always terminated with "scf.yield". If "scf.if" |
| defines no values, the "scf.yield" can be left out, and will be inserted |
| implicitly. Otherwise, it must be explicit. |
| Also, if "scf.if" defines one or more values, the 'else' block cannot be |
| omitted. |
| |
| Example: |
| |
| ```mlir |
| scf.if %b { |
| ... |
| } |
| ``` |
| }]; |
| let arguments = (ins I1:$condition); |
| let results = (outs Variadic<AnyType>:$results); |
| let regions = (region SizedRegion<1>:$thenRegion, AnyRegion:$elseRegion); |
| |
| let skipDefaultBuilders = 1; |
| let builders = [ |
| OpBuilder<(ins "Value":$cond, "bool":$withElseRegion)>, |
| OpBuilder<(ins "TypeRange":$resultTypes, "Value":$cond, |
| "bool":$withElseRegion)>, |
| OpBuilder<(ins "TypeRange":$resultTypes, "Value":$cond, |
| CArg<"function_ref<void(OpBuilder &, Location)>", |
| "buildTerminatedBody">:$thenBuilder, |
| CArg<"function_ref<void(OpBuilder &, Location)>", |
| "nullptr">:$elseBuilder)>, |
| OpBuilder<(ins "Value":$cond, |
| CArg<"function_ref<void(OpBuilder &, Location)>", |
| "buildTerminatedBody">:$thenBuilder, |
| CArg<"function_ref<void(OpBuilder &, Location)>", |
| "nullptr">:$elseBuilder)> |
| ]; |
| |
| let extraClassDeclaration = [{ |
| OpBuilder getThenBodyBuilder(OpBuilder::Listener *listener = nullptr) { |
| Block* body = getBody(0); |
| return results().empty() ? OpBuilder::atBlockTerminator(body, listener) |
| : OpBuilder::atBlockEnd(body, listener); |
| } |
| OpBuilder getElseBodyBuilder(OpBuilder::Listener *listener = nullptr) { |
| Block* body = getBody(1); |
| return results().empty() ? OpBuilder::atBlockTerminator(body, listener) |
| : OpBuilder::atBlockEnd(body, listener); |
| } |
| Block* thenBlock(); |
| YieldOp thenYield(); |
| Block* elseBlock(); |
| YieldOp elseYield(); |
| }]; |
| |
| let hasCanonicalizer = 1; |
| } |
| |
| def ParallelOp : SCF_Op<"parallel", |
| [AttrSizedOperandSegments, |
| DeclareOpInterfaceMethods<LoopLikeOpInterface>, |
| RecursiveSideEffects, |
| SingleBlockImplicitTerminator<"scf::YieldOp">]> { |
| let summary = "parallel for operation"; |
| let description = [{ |
| The "scf.parallel" operation represents a loop nest taking 4 groups of SSA |
| values as operands that represent the lower bounds, upper bounds, steps and |
| initial values, respectively. The operation defines a variadic number of |
| SSA values for its induction variables. It has one region capturing the |
| loop body. The induction variables are represented as an argument of this |
| region. These SSA values always have type index, which is the size of the |
| machine word. The steps are values of type index, required to be positive. |
| The lower and upper bounds specify a half-open range: the range includes |
| the lower bound but does not include the upper bound. The initial values |
| have the same types as results of "scf.parallel". If there are no results, |
| the keyword `init` can be omitted. |
| |
| Semantically we require that the iteration space can be iterated in any |
| order, and the loop body can be executed in parallel. If there are data |
| races, the behavior is undefined. |
| |
| The parallel loop operation supports reduction of values produced by |
| individual iterations into a single result. This is modeled using the |
| scf.reduce operation (see scf.reduce for details). Each result of a |
| scf.parallel operation is associated with an initial value operand and |
| reduce operation that is an immediate child. Reductions are matched to |
| result and initial values in order of their appearance in the body. |
| Consequently, we require that the body region has the same number of |
| results and initial values as it has reduce operations. |
| |
| The body region must contain exactly one block that terminates with |
| "scf.yield" without operands. Parsing ParallelOp will create such a region |
| and insert the terminator when it is absent from the custom format. |
| |
| Example: |
| |
| ```mlir |
| %init = arith.constant 0.0 : f32 |
| scf.parallel (%iv) = (%lb) to (%ub) step (%step) init (%init) -> f32 { |
| %elem_to_reduce = load %buffer[%iv] : memref<100xf32> |
| scf.reduce(%elem_to_reduce) : f32 { |
| ^bb0(%lhs : f32, %rhs: f32): |
| %res = arith.addf %lhs, %rhs : f32 |
| scf.reduce.return %res : f32 |
| } |
| } |
| ``` |
| }]; |
| |
| let arguments = (ins Variadic<Index>:$lowerBound, |
| Variadic<Index>:$upperBound, |
| Variadic<Index>:$step, |
| Variadic<AnyType>:$initVals); |
| let results = (outs Variadic<AnyType>:$results); |
| let regions = (region SizedRegion<1>:$region); |
| |
| let skipDefaultBuilders = 1; |
| let builders = [ |
| OpBuilder<(ins "ValueRange":$lowerBounds, "ValueRange":$upperBounds, |
| "ValueRange":$steps, "ValueRange":$initVals, |
| CArg<"function_ref<void (OpBuilder &, Location, ValueRange, ValueRange)>", |
| "nullptr">:$bodyBuilderFn)>, |
| OpBuilder<(ins "ValueRange":$lowerBounds, "ValueRange":$upperBounds, |
| "ValueRange":$steps, |
| CArg<"function_ref<void (OpBuilder &, Location, ValueRange)>", |
| "nullptr">:$bodyBuilderFn)>, |
| ]; |
| |
| let extraClassDeclaration = [{ |
| ValueRange getInductionVars() { |
| return getBody()->getArguments(); |
| } |
| unsigned getNumLoops() { return step().size(); } |
| unsigned getNumReductions() { return initVals().size(); } |
| }]; |
| |
| let hasCanonicalizer = 1; |
| } |
| |
| def ReduceOp : SCF_Op<"reduce", [HasParent<"ParallelOp">]> { |
| let summary = "reduce operation for parallel for"; |
| let description = [{ |
| "scf.reduce" is an operation occurring inside "scf.parallel" operations. |
| It consists of one block with two arguments which have the same type as the |
| operand of "scf.reduce". |
| |
| "scf.reduce" is used to model the value for reduction computations of a |
| "scf.parallel" operation. It has to appear as an immediate child of a |
| "scf.parallel" and is associated with a result value of its parent |
| operation. |
| |
| Association is in the order of appearance in the body where the first |
| result of a parallel loop operation corresponds to the first "scf.reduce" |
| in the operation's body region. The reduce operation takes a single |
| operand, which is the value to be used in the reduction. |
| |
| The reduce operation contains a region whose entry block expects two |
| arguments of the same type as the operand. As the iteration order of the |
| parallel loop and hence reduction order is unspecified, the result of |
| reduction may be non-deterministic unless the operation is associative and |
| commutative. |
| |
| The result of the reduce operation's body must have the same type as the |
| operands and associated result value of the parallel loop operation. |
| Example: |
| |
| ```mlir |
| %operand = arith.constant 1.0 : f32 |
| scf.reduce(%operand) : f32 { |
| ^bb0(%lhs : f32, %rhs: f32): |
| %res = arith.addf %lhs, %rhs : f32 |
| scf.reduce.return %res : f32 |
| } |
| ``` |
| }]; |
| |
| let skipDefaultBuilders = 1; |
| let builders = [ |
| OpBuilder<(ins "Value":$operand, |
| CArg<"function_ref<void (OpBuilder &, Location, Value, Value)>", |
| "nullptr">:$bodyBuilderFn)> |
| ]; |
| |
| let arguments = (ins AnyType:$operand); |
| let regions = (region SizedRegion<1>:$reductionOperator); |
| } |
| |
| def ReduceReturnOp : |
| SCF_Op<"reduce.return", [HasParent<"ReduceOp">, NoSideEffect, |
| Terminator]> { |
| let summary = "terminator for reduce operation"; |
| let description = [{ |
| "scf.reduce.return" is a special terminator operation for the block inside |
| "scf.reduce". It terminates the region. It should have the same type as |
| the operand of "scf.reduce". Example for the custom format: |
| |
| ```mlir |
| scf.reduce.return %res : f32 |
| ``` |
| }]; |
| |
| let arguments = (ins AnyType:$result); |
| let assemblyFormat = "$result attr-dict `:` type($result)"; |
| } |
| |
| def WhileOp : SCF_Op<"while", |
| [DeclareOpInterfaceMethods<RegionBranchOpInterface>, |
| RecursiveSideEffects]> { |
| let summary = "a generic 'while' loop"; |
| let description = [{ |
| This operation represents a generic "while"/"do-while" loop that keeps |
| iterating as long as a condition is satisfied. There is no restriction on |
| the complexity of the condition. It consists of two regions (with single |
| block each): "before" region and "after" region. The names of regions |
| indicates whether they execute before or after the condition check. |
| Therefore, if the main loop payload is located in the "before" region, the |
| operation is a "do-while" loop. Otherwise, it is a "while" loop. |
| |
| The "before" region terminates with a special operation, `scf.condition`, |
| that accepts as its first operand an `i1` value indicating whether to |
| proceed to the "after" region (value is `true`) or not. The two regions |
| communicate by means of region arguments. Initially, the "before" region |
| accepts as arguments the operands of the `scf.while` operation and uses them |
| to evaluate the condition. It forwards the trailing, non-condition operands |
| of the `scf.condition` terminator either to the "after" region if the |
| control flow is transferred there or to results of the `scf.while` operation |
| otherwise. The "after" region takes as arguments the values produced by the |
| "before" region and uses `scf.yield` to supply new arguments for the |
| "before" region, into which it transfers the control flow unconditionally. |
| |
| A simple "while" loop can be represented as follows. |
| |
| ```mlir |
| %res = scf.while (%arg1 = %init1) : (f32) -> f32 { |
| /* "Before" region. |
| * In a "while" loop, this region computes the condition. */ |
| %condition = call @evaluate_condition(%arg1) : (f32) -> i1 |
| |
| /* Forward the argument (as result or "after" region argument). */ |
| scf.condition(%condition) %arg1 : f32 |
| |
| } do { |
| ^bb0(%arg2: f32): |
| /* "After region. |
| * In a "while" loop, this region is the loop body. */ |
| %next = call @payload(%arg2) : (f32) -> f32 |
| |
| /* Forward the new value to the "before" region. |
| * The operand types must match the types of the `scf.while` operands. */ |
| scf.yield %next : f32 |
| } |
| ``` |
| |
| A simple "do-while" loop can be represented by reducing the "after" block |
| to a simple forwarder. |
| |
| ```mlir |
| %res = scf.while (%arg1 = %init1) : (f32) -> f32 { |
| /* "Before" region. |
| * In a "do-while" loop, this region contains the loop body. */ |
| %next = call @payload(%arg1) : (f32) -> f32 |
| |
| /* And also evaluates the condition. */ |
| %condition = call @evaluate_condition(%arg1) : (f32) -> i1 |
| |
| /* Loop through the "after" region. */ |
| scf.condition(%condition) %next : f32 |
| |
| } do { |
| ^bb0(%arg2: f32): |
| /* "After" region. |
| * Forwards the values back to "before" region unmodified. */ |
| scf.yield %arg2 : f32 |
| } |
| ``` |
| |
| Note that the types of region arguments need not to match with each other. |
| The op expects the operand types to match with argument types of the |
| "before" region"; the result types to match with the trailing operand types |
| of the terminator of the "before" region, and with the argument types of the |
| "after" region. The following scheme can be used to share the results of |
| some operations executed in the "before" region with the "after" region, |
| avoiding the need to recompute them. |
| |
| ```mlir |
| %res = scf.while (%arg1 = %init1) : (f32) -> i64 { |
| /* One can perform some computations, e.g., necessary to evaluate the |
| * condition, in the "before" region and forward their results to the |
| * "after" region. */ |
| %shared = call @shared_compute(%arg1) : (f32) -> i64 |
| |
| /* Evaluate the condition. */ |
| %condition = call @evaluate_condition(%arg1, %shared) : (f32, i64) -> i1 |
| |
| /* Forward the result of the shared computation to the "after" region. |
| * The types must match the arguments of the "after" region as well as |
| * those of the `scf.while` results. */ |
| scf.condition(%condition) %shared : i64 |
| |
| } do { |
| ^bb0(%arg2: i64) { |
| /* Use the partial result to compute the rest of the payload in the |
| * "after" region. */ |
| %res = call @payload(%arg2) : (i64) -> f32 |
| |
| /* Forward the new value to the "before" region. |
| * The operand types must match the types of the `scf.while` operands. */ |
| scf.yield %res : f32 |
| } |
| ``` |
| |
| The custom syntax for this operation is as follows. |
| |
| ``` |
| op ::= `scf.while` assignments `:` function-type region `do` region |
| `attributes` attribute-dict |
| initializer ::= /* empty */ | `(` assignment-list `)` |
| assignment-list ::= assignment | assignment `,` assignment-list |
| assignment ::= ssa-value `=` ssa-value |
| ``` |
| }]; |
| |
| let arguments = (ins Variadic<AnyType>:$inits); |
| let results = (outs Variadic<AnyType>:$results); |
| let regions = (region SizedRegion<1>:$before, SizedRegion<1>:$after); |
| |
| let extraClassDeclaration = [{ |
| OperandRange getSuccessorEntryOperands(unsigned index); |
| ConditionOp getConditionOp(); |
| Block::BlockArgListType getAfterArguments(); |
| }]; |
| |
| let hasCanonicalizer = 1; |
| } |
| |
| def YieldOp : SCF_Op<"yield", [NoSideEffect, ReturnLike, Terminator, |
| ParentOneOf<["ExecuteRegionOp, ForOp", |
| "IfOp, ParallelOp, WhileOp"]>]> { |
| let summary = "loop yield and termination operation"; |
| let description = [{ |
| "scf.yield" yields an SSA value from the SCF dialect op region and |
| terminates the regions. The semantics of how the values are yielded is |
| defined by the parent operation. |
| If "scf.yield" has any operands, the operands must match the parent |
| operation's results. |
| If the parent operation defines no values, then the "scf.yield" may be |
| left out in the custom syntax and the builders will insert one implicitly. |
| Otherwise, it has to be present in the syntax to indicate which values are |
| yielded. |
| }]; |
| |
| let arguments = (ins Variadic<AnyType>:$results); |
| let builders = [OpBuilder<(ins), [{ /* nothing to do */ }]>]; |
| |
| let assemblyFormat = |
| [{ attr-dict ($results^ `:` type($results))? }]; |
| |
| // Override default verifier (defined in SCF_Op), no custom verification |
| // needed. |
| let verifier = ?; |
| } |
| |
| #endif // MLIR_DIALECT_SCF_SCFOPS |