Skip to content

Commit 2eda87d

Browse files
committed
[mlir][SCCP] Add support for propagating constants across inter-region control flow.
This is possible by adding two new ControlFlowInterface additions: - A new interface, RegionBranchOpInterface This interface allows for region holding operations to describe how control flows between regions. This interface initially contains two methods: * getSuccessorEntryOperands Returns the operands of this operation used as the entry arguments when entering the region at `index`, which was specified as a successor by `getSuccessorRegions`. when entering. These operands should correspond 1-1 with the successor inputs specified in `getSuccessorRegions`, and may be a subset of the entry arguments for that region. * getSuccessorRegions Returns the viable successors of a region, or the possible successor when branching from the parent op. This allows for describing which regions may be executed when entering an operation, and which regions are executed after having executed another region of the parent op. For example, a structured loop operation may always enter into the loop body region. The loop body region may branch back to itself, or exit to the operation. - A trait, ReturnLike This trait signals that a terminator exits a region and forwards all of its operands as "exiting" values. These additions allow for performing more general dataflow analysis in the presence of region holding operations. Differential Revision: https://reviews.llvm.org/D78447
1 parent 152d29c commit 2eda87d

File tree

8 files changed

+496
-32
lines changed

8 files changed

+496
-32
lines changed

mlir/include/mlir/Dialect/LoopOps/LoopOps.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "mlir/IR/Builders.h"
1818
#include "mlir/IR/Dialect.h"
1919
#include "mlir/IR/OpDefinition.h"
20+
#include "mlir/Interfaces/ControlFlowInterfaces.h"
2021
#include "mlir/Interfaces/LoopLikeInterface.h"
2122
#include "mlir/Interfaces/SideEffects.h"
2223

mlir/include/mlir/Dialect/LoopOps/LoopOps.td

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#ifndef LOOP_OPS
1414
#define LOOP_OPS
1515

16+
include "mlir/Interfaces/ControlFlowInterfaces.td"
1617
include "mlir/Interfaces/LoopLikeInterface.td"
1718
include "mlir/Interfaces/SideEffects.td"
1819

@@ -37,6 +38,7 @@ class Loop_Op<string mnemonic, list<OpTrait> traits = []> :
3738

3839
def ForOp : Loop_Op<"for",
3940
[DeclareOpInterfaceMethods<LoopLikeOpInterface>,
41+
DeclareOpInterfaceMethods<RegionBranchOpInterface>,
4042
SingleBlockImplicitTerminator<"YieldOp">,
4143
RecursiveSideEffects]> {
4244
let summary = "for operation";
@@ -169,11 +171,18 @@ def ForOp : Loop_Op<"for",
169171
unsigned getNumIterOperands() {
170172
return getOperation()->getNumOperands() - getNumControlOperands();
171173
}
174+
175+
/// Return operands used when entering the region at 'index'. These operands
176+
/// correspond to the loop iterator operands, i.e., those exclusing the
177+
/// induction variable. LoopOp only has one region, so 0 is the only valid
178+
/// value for `index`.
179+
OperandRange getSuccessorEntryOperands(unsigned index);
172180
}];
173181
}
174182

175183
def IfOp : Loop_Op<"if",
176-
[SingleBlockImplicitTerminator<"YieldOp">, RecursiveSideEffects]> {
184+
[DeclareOpInterfaceMethods<RegionBranchOpInterface>,
185+
SingleBlockImplicitTerminator<"YieldOp">, RecursiveSideEffects]> {
177186
let summary = "if-then-else operation";
178187
let description = [{
179188
The `loop.if` operation represents an if-then-else construct for
@@ -385,7 +394,7 @@ def ReduceReturnOp :
385394
let assemblyFormat = "$result attr-dict `:` type($result)";
386395
}
387396

388-
def YieldOp : Loop_Op<"yield", [NoSideEffect, Terminator]> {
397+
def YieldOp : Loop_Op<"yield", [NoSideEffect, ReturnLike, Terminator]> {
389398
let summary = "loop yield and termination operation";
390399
let description = [{
391400
"loop.yield" yields an SSA value from a loop dialect op region and

mlir/include/mlir/Dialect/StandardOps/IR/Ops.td

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1865,7 +1865,7 @@ def RemFOp : FloatArithmeticOp<"remf"> {
18651865
// ReturnOp
18661866
//===----------------------------------------------------------------------===//
18671867

1868-
def ReturnOp : Std_Op<"return", [NoSideEffect, HasParent<"FuncOp">,
1868+
def ReturnOp : Std_Op<"return", [NoSideEffect, HasParent<"FuncOp">, ReturnLike,
18691869
Terminator]> {
18701870
let summary = "return operation";
18711871
let description = [{

mlir/include/mlir/Interfaces/ControlFlowInterfaces.h

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
namespace mlir {
2020
class BranchOpInterface;
2121

22+
//===----------------------------------------------------------------------===//
23+
// BranchOpInterface
24+
//===----------------------------------------------------------------------===//
25+
2226
namespace detail {
2327
/// Erase an operand from a branch operation that is used as a successor
2428
/// operand. `operandIndex` is the operand within `operands` to be erased.
@@ -37,7 +41,69 @@ LogicalResult verifyBranchSuccessorOperands(Operation *op, unsigned succNo,
3741
Optional<OperandRange> operands);
3842
} // namespace detail
3943

44+
//===----------------------------------------------------------------------===//
45+
// RegionBranchOpInterface
46+
//===----------------------------------------------------------------------===//
47+
48+
/// This class represents a successor of a region. A region successor can either
49+
/// be another region, or the parent operation. If the successor is a region,
50+
/// this class accepts the destination region, as well as a set of arguments
51+
/// from that region that will be populated by values from the current region.
52+
/// If the successor is the parent operation, this class accepts an optional set
53+
/// of results that will be populated by values from the current region.
54+
class RegionSuccessor {
55+
public:
56+
/// Initialize a successor that branches to another region of the parent
57+
/// operation.
58+
RegionSuccessor(Region *region, Block::BlockArgListType regionInputs = {})
59+
: region(region), inputs(regionInputs) {}
60+
/// Initialize a successor that branches back to/out of the parent operation.
61+
RegionSuccessor(Optional<Operation::result_range> results = {})
62+
: region(nullptr), inputs(results ? ValueRange(*results) : ValueRange()) {
63+
}
64+
65+
/// Return the given region successor. Returns nullptr if the successor is the
66+
/// parent operation.
67+
Region *getSuccessor() const { return region; }
68+
69+
/// Return the inputs to the successor that are remapped by the exit values of
70+
/// the current region.
71+
ValueRange getSuccessorInputs() const { return inputs; }
72+
73+
private:
74+
Region *region;
75+
ValueRange inputs;
76+
};
77+
78+
//===----------------------------------------------------------------------===//
79+
// ControlFlow Interfaces
80+
//===----------------------------------------------------------------------===//
81+
4082
#include "mlir/Interfaces/ControlFlowInterfaces.h.inc"
83+
84+
//===----------------------------------------------------------------------===//
85+
// ControlFlow Traits
86+
//===----------------------------------------------------------------------===//
87+
88+
namespace OpTrait {
89+
/// This trait indicates that a terminator operation is "return-like". This
90+
/// means that it exits its current region and forwards its operands as "exit"
91+
/// values to the parent region. Operations with this trait are not permitted to
92+
/// contain successors or produce results.
93+
template <typename ConcreteType>
94+
struct ReturnLike : public TraitBase<ConcreteType, ReturnLike> {
95+
static LogicalResult verifyTrait(Operation *op) {
96+
static_assert(ConcreteType::template hasTrait<IsTerminator>(),
97+
"expected operation to be a terminator");
98+
static_assert(ConcreteType::template hasTrait<ZeroResult>(),
99+
"expected operation to have zero results");
100+
static_assert(ConcreteType::template hasTrait<ZeroSuccessor>(),
101+
"expected operation to have zero successors");
102+
return success();
103+
}
104+
};
105+
} // namespace OpTrait
106+
41107
} // end namespace mlir
42108

43109
#endif // MLIR_INTERFACES_CONTROLFLOWINTERFACES_H

mlir/include/mlir/Interfaces/ControlFlowInterfaces.td

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,55 @@ def BranchOpInterface : OpInterface<"BranchOpInterface"> {
9090
}];
9191
}
9292

93+
//===----------------------------------------------------------------------===//
94+
// RegionBranchOpInterface
95+
//===----------------------------------------------------------------------===//
96+
97+
def RegionBranchOpInterface : OpInterface<"RegionBranchOpInterface"> {
98+
let description = [{
99+
This interface provides information for region operations that contain
100+
branching behavior between held regions, i.e. this interface allows for
101+
expressing control flow information for region holding operations.
102+
}];
103+
let methods = [
104+
InterfaceMethod<[{
105+
Returns the operands of this operation used as the entry arguments when
106+
entering the region at `index`, which was specified as a successor by
107+
`getSuccessorRegions`. These operands should correspond 1-1 with the
108+
successor inputs specified in `getSuccessorRegions`, and may corre
109+
}],
110+
"OperandRange", "getSuccessorEntryOperands",
111+
(ins "unsigned":$index), [{}], /*defaultImplementation=*/[{
112+
auto operandEnd = this->getOperation()->operand_end();
113+
return OperandRange({operandEnd, operandEnd});
114+
}]
115+
>,
116+
InterfaceMethod<[{
117+
Returns the viable successors of a region at `index`, or the possible
118+
successors when branching from the parent op if `index` is None. These
119+
are the regions that may be selected during the flow of control. If
120+
`index` is None, `operands` is a set of optional attributes that
121+
either correspond to a constant value for each operand of this
122+
operation, or null if that operand is not a constant. If `index` is
123+
valid, `operands` corresponds to the exit values of the region at
124+
`index`. Only a region, i.e. a valid `index`, may use the parent
125+
operation as a successor. This method allows for describing which
126+
regions may be executed when entering an operation, and which regions
127+
are executed after having executed another region of the parent op. The
128+
successor region must be non-empty.
129+
}],
130+
"void", "getSuccessorRegions",
131+
(ins "Optional<unsigned>":$index, "ArrayRef<Attribute>":$operands,
132+
"SmallVectorImpl<RegionSuccessor> &":$regions)
133+
>
134+
];
135+
}
136+
137+
//===----------------------------------------------------------------------===//
138+
// ControlFlow Traits
139+
//===----------------------------------------------------------------------===//
140+
141+
// Op is "return-like".
142+
def ReturnLike : NativeOpTrait<"ReturnLike">;
143+
93144
#endif // MLIR_INTERFACES_CONTROLFLOWINTERFACES

mlir/lib/Dialect/LoopOps/LoopOps.cpp

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,39 @@ ForOp mlir::loop::getForInductionVarOwner(Value val) {
196196
return dyn_cast_or_null<ForOp>(containingOp);
197197
}
198198

199+
/// Return operands used when entering the region at 'index'. These operands
200+
/// correspond to the loop iterator operands, i.e., those exclusing the
201+
/// induction variable. LoopOp only has one region, so 0 is the only valid value
202+
/// for `index`.
203+
OperandRange ForOp::getSuccessorEntryOperands(unsigned index) {
204+
assert(index == 0 && "invalid region index");
205+
206+
// The initial operands map to the loop arguments after the induction
207+
// variable.
208+
return initArgs();
209+
}
210+
211+
/// Given the region at `index`, or the parent operation if `index` is None,
212+
/// return the successor regions. These are the regions that may be selected
213+
/// during the flow of control. `operands` is a set of optional attributes that
214+
/// correspond to a constant value for each operand, or null if that operand is
215+
/// not a constant.
216+
void ForOp::getSuccessorRegions(Optional<unsigned> index,
217+
ArrayRef<Attribute> operands,
218+
SmallVectorImpl<RegionSuccessor> &regions) {
219+
// If the predecessor is the ForOp, branch into the body using the iterator
220+
// arguments.
221+
if (!index.hasValue()) {
222+
regions.push_back(RegionSuccessor(&getLoopBody(), getRegionIterArgs()));
223+
return;
224+
}
225+
226+
// Otherwise, the loop may branch back to itself or the parent operation.
227+
assert(index.getValue() == 0 && "expected loop region");
228+
regions.push_back(RegionSuccessor(&getLoopBody(), getRegionIterArgs()));
229+
regions.push_back(RegionSuccessor(getResults()));
230+
}
231+
199232
//===----------------------------------------------------------------------===//
200233
// IfOp
201234
//===----------------------------------------------------------------------===//
@@ -298,6 +331,42 @@ static void print(OpAsmPrinter &p, IfOp op) {
298331
p.printOptionalAttrDict(op.getAttrs());
299332
}
300333

334+
/// Given the region at `index`, or the parent operation if `index` is None,
335+
/// return the successor regions. These are the regions that may be selected
336+
/// during the flow of control. `operands` is a set of optional attributes that
337+
/// correspond to a constant value for each operand, or null if that operand is
338+
/// not a constant.
339+
void IfOp::getSuccessorRegions(Optional<unsigned> index,
340+
ArrayRef<Attribute> operands,
341+
SmallVectorImpl<RegionSuccessor> &regions) {
342+
// The `then` and the `else` region branch back to the parent operation.
343+
if (index.hasValue()) {
344+
regions.push_back(RegionSuccessor(getResults()));
345+
return;
346+
}
347+
348+
// Don't consider the else region if it is empty.
349+
Region *elseRegion = &this->elseRegion();
350+
if (elseRegion->empty())
351+
elseRegion = nullptr;
352+
353+
// Otherwise, the successor is dependent on the condition.
354+
bool condition;
355+
if (auto condAttr = operands.front().dyn_cast_or_null<IntegerAttr>()) {
356+
condition = condAttr.getValue().isOneValue();
357+
} else if (auto condAttr = operands.front().dyn_cast_or_null<BoolAttr>()) {
358+
condition = condAttr.getValue();
359+
} else {
360+
// If the condition isn't constant, both regions may be executed.
361+
regions.push_back(RegionSuccessor(&thenRegion()));
362+
regions.push_back(RegionSuccessor(elseRegion));
363+
return;
364+
}
365+
366+
// Add the successor regions using the condition.
367+
regions.push_back(RegionSuccessor(condition ? &thenRegion() : elseRegion));
368+
}
369+
301370
//===----------------------------------------------------------------------===//
302371
// ParallelOp
303372
//===----------------------------------------------------------------------===//

0 commit comments

Comments
 (0)