Skip to content

Commit 973ddb7

Browse files
committed
Define a NoTerminator traits that allows operations with a single block region to not provide a terminator
In particular for Graph Regions, the terminator needs is just a historical artifact of the generalization of MLIR from CFG region. Operations like Module don't need a terminator, and before Module migrated to be an operation with region there wasn't any needed. To validate the feature, the ModuleOp is migrated to use this trait and the ModuleTerminator operation is deleted. This patch is likely to break clients, if you're in this case: - you may iterate on a ModuleOp with `getBody()->without_terminator()`, the solution is simple: just remove the ->without_terminator! - you created a builder with `Builder::atBlockTerminator(module_body)`, just use `Builder::atBlockEnd(module_body)` instead. - you were handling ModuleTerminator: it isn't needed anymore. - for generic code, a `Block::mayNotHaveTerminator()` may be used. Differential Revision: https://reviews.llvm.org/D98468
1 parent 0f99c6c commit 973ddb7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+388
-194
lines changed

mlir/docs/LangRef.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -351,13 +351,18 @@ value-id-and-type-list ::= value-id-and-type (`,` value-id-and-type)*
351351
block-arg-list ::= `(` value-id-and-type-list? `)`
352352
```
353353

354-
A *Block* is an ordered list of operations, concluding with a single
355-
[terminator operation](#terminator-operations). In [SSACFG
354+
A *Block* is a list of operations. In [SSACFG
356355
regions](#control-flow-and-ssacfg-regions), each block represents a compiler
357356
[basic block](https://en.wikipedia.org/wiki/Basic_block) where instructions
358357
inside the block are executed in order and terminator operations implement
359358
control flow branches between basic blocks.
360359

360+
A region with a single block may not include a [terminator
361+
operation](#terminator-operations). The enclosing op can opt-out of this
362+
requirement with the `NoTerminator` trait. The top-level `ModuleOp` is an
363+
example of such operation which defined this trait and whose block body does
364+
not have a terminator.
365+
361366
Blocks in MLIR take a list of block arguments, notated in a function-like
362367
way. Block arguments are bound to values specified by the semantics of
363368
individual operations. Block arguments of the entry block of a region are also

mlir/docs/Traits.md

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -323,13 +323,20 @@ index expression that can express the equivalent of the memory-layout
323323
specification of the MemRef type. See [the -normalize-memrefs pass].
324324
(https://mlir.llvm.org/docs/Passes/#-normalize-memrefs-normalize-memrefs)
325325

326+
### Single Block Region
327+
328+
* `OpTrait::SingleBlock` -- `SingleBlock`
329+
330+
This trait provides APIs and verifiers for operations with regions that have a
331+
single block.
332+
326333
### Single Block with Implicit Terminator
327334

328-
* `OpTrait::SingleBlockImplicitTerminator<typename TerminatorOpType>` :
335+
* `OpTrait::SingleBlockImplicitTerminator<typename TerminatorOpType>` --
329336
`SingleBlockImplicitTerminator<string op>`
330337

331-
This trait provides APIs and verifiers for operations with regions that have a
332-
single block that must terminate with `TerminatorOpType`.
338+
This trait implies the `SingleBlock` above, but adds the additional requirement
339+
that the single block must terminate with `TerminatorOpType`.
333340

334341
### SymbolTable
335342

@@ -344,3 +351,10 @@ This trait is used for operations that define a
344351

345352
This trait provides verification and functionality for operations that are known
346353
to be [terminators](LangRef.md#terminator-operations).
354+
355+
* `OpTrait::NoTerminator` -- `NoTerminator`
356+
357+
This trait removes the requirement on regions held by an operation to have
358+
[terminator operations](LangRef.md#terminator-operations) at the end of a block.
359+
This requires that these regions have a single block. An example of operation
360+
using this trait is the top-level `ModuleOp`.

mlir/docs/Tutorials/Toy/Ch-6.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ everything to the LLVM dialect.
6363
```c++
6464
mlir::ConversionTarget target(getContext());
6565
target.addLegalDialect<mlir::LLVMDialect>();
66-
target.addLegalOp<mlir::ModuleOp, mlir::ModuleTerminatorOp>();
66+
target.addLegalOp<mlir::ModuleOp>();
6767
```
6868

6969
### Type Converter

mlir/docs/Tutorials/UnderstandingTheIRStructure.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,6 @@ llvm-project/mlir/test/IR/print-ir-nesting.mlir`:
110110
"dialect.innerop6"() : () -> ()
111111
"dialect.innerop7"() : () -> ()
112112
}) {"other attribute" = 42 : i64} : () -> ()
113-
"module_terminator"() : () -> ()
114113
}) : () -> ()
115114
```
116115

@@ -147,7 +146,6 @@ visiting op: 'module' with 0 operands and 0 results
147146
0 nested regions:
148147
visiting op: 'dialect.innerop7' with 0 operands and 0 results
149148
0 nested regions:
150-
visiting op: 'module_terminator' with 0 operands and 0 results
151149
0 nested regions:
152150
```
153151

mlir/examples/toy/Ch6/mlir/LowerToLLVM.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ void ToyToLLVMLoweringPass::runOnOperation() {
174174
// final target for this lowering. For this lowering, we are only targeting
175175
// the LLVM dialect.
176176
LLVMConversionTarget target(getContext());
177-
target.addLegalOp<ModuleOp, ModuleTerminatorOp>();
177+
target.addLegalOp<ModuleOp>();
178178

179179
// During this lowering, we will also be lowering the MemRef types, that are
180180
// currently being operated on, to a representation in LLVM. To perform this

mlir/examples/toy/Ch7/mlir/LowerToLLVM.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ void ToyToLLVMLoweringPass::runOnOperation() {
174174
// final target for this lowering. For this lowering, we are only targeting
175175
// the LLVM dialect.
176176
LLVMConversionTarget target(getContext());
177-
target.addLegalOp<ModuleOp, ModuleTerminatorOp>();
177+
target.addLegalOp<ModuleOp>();
178178

179179
// During this lowering, we will also be lowering the MemRef types, that are
180180
// currently being operated on, to a representation in LLVM. To perform this

mlir/include/mlir/IR/BuiltinOps.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
#include "mlir/IR/FunctionSupport.h"
1717
#include "mlir/IR/OwningOpRef.h"
18+
#include "mlir/IR/RegionKindInterface.h"
1819
#include "mlir/IR/SymbolTable.h"
1920
#include "mlir/Interfaces/CallInterfaces.h"
2021
#include "mlir/Interfaces/CastInterfaces.h"

mlir/include/mlir/IR/BuiltinOps.td

Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#define BUILTIN_OPS
1616

1717
include "mlir/IR/BuiltinDialect.td"
18+
include "mlir/IR/RegionKindInterface.td"
1819
include "mlir/IR/SymbolInterfaces.td"
1920
include "mlir/Interfaces/CallInterfaces.td"
2021
include "mlir/Interfaces/CastInterfaces.td"
@@ -159,17 +160,17 @@ def FuncOp : Builtin_Op<"func", [
159160
//===----------------------------------------------------------------------===//
160161

161162
def ModuleOp : Builtin_Op<"module", [
162-
AffineScope, IsolatedFromAbove, NoRegionArguments, SymbolTable, Symbol,
163-
SingleBlockImplicitTerminator<"ModuleTerminatorOp">
164-
]> {
163+
AffineScope, IsolatedFromAbove, NoRegionArguments, SymbolTable, Symbol]
164+
# GraphRegionNoTerminator.traits> {
165165
let summary = "A top level container operation";
166166
let description = [{
167167
A `module` represents a top-level container operation. It contains a single
168-
SSACFG region containing a single block which can contain any
169-
operations. Operations within this region cannot implicitly capture values
170-
defined outside the module, i.e. Modules are `IsolatedFromAbove`. Modules
171-
have an optional symbol name which can be used to refer to them in
172-
operations.
168+
[graph region](#control-flow-and-ssacfg-regions) containing a single block
169+
which can contain any operations and does not have a terminator. Operations
170+
within this region cannot implicitly capture values defined outside the module,
171+
i.e. Modules are [IsolatedFromAbove](Traits.md#isolatedfromabove). Modules have
172+
an optional [symbol name](SymbolsAndSymbolTables.md) which can be used to refer
173+
to them in operations.
173174

174175
Example:
175176

@@ -213,22 +214,6 @@ def ModuleOp : Builtin_Op<"module", [
213214
let skipDefaultBuilders = 1;
214215
}
215216

216-
//===----------------------------------------------------------------------===//
217-
// ModuleTerminatorOp
218-
//===----------------------------------------------------------------------===//
219-
220-
def ModuleTerminatorOp : Builtin_Op<"module_terminator", [
221-
Terminator, HasParent<"ModuleOp">
222-
]> {
223-
let summary = "A pseudo op that marks the end of a module";
224-
let description = [{
225-
`module_terminator` is a special terminator operation for the body of a
226-
`module`, it has no semantic meaning beyond keeping the body of a `module`
227-
well-formed.
228-
}];
229-
let assemblyFormat = "attr-dict";
230-
}
231-
232217
//===----------------------------------------------------------------------===//
233218
// UnrealizedConversionCastOp
234219
//===----------------------------------------------------------------------===//

mlir/include/mlir/IR/OpBase.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1827,10 +1827,16 @@ def ElementwiseMappable {
18271827
];
18281828
}
18291829

1830+
// Op's regions have a single block.
1831+
def SingleBlock : NativeOpTrait<"SingleBlock">;
1832+
18301833
// Op's regions have a single block with the specified terminator.
18311834
class SingleBlockImplicitTerminator<string op>
18321835
: ParamNativeOpTrait<"SingleBlockImplicitTerminator", op>;
18331836

1837+
// Op's regions don't have terminator.
1838+
def NoTerminator : NativeOpTrait<"NoTerminator">;
1839+
18341840
// Op's parent operation is the provided one.
18351841
class HasParent<string op>
18361842
: ParamNativeOpTrait<"HasParent", op>;

mlir/include/mlir/IR/OpDefinition.h

Lines changed: 113 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,11 @@ class VariadicResults
654654
//===----------------------------------------------------------------------===//
655655
// Terminator Traits
656656

657+
/// This class indicates that the regions associated with this op don't have
658+
/// terminators.
659+
template <typename ConcreteType>
660+
class NoTerminator : public TraitBase<ConcreteType, NoTerminator> {};
661+
657662
/// This class provides the API for ops that are known to be terminators.
658663
template <typename ConcreteType>
659664
class IsTerminator : public TraitBase<ConcreteType, IsTerminator> {
@@ -757,6 +762,87 @@ class VariadicSuccessors
757762
: public detail::MultiSuccessorTraitBase<ConcreteType, VariadicSuccessors> {
758763
};
759764

765+
//===----------------------------------------------------------------------===//
766+
// SingleBlock
767+
768+
/// This class provides APIs and verifiers for ops with regions having a single
769+
/// block.
770+
template <typename ConcreteType>
771+
struct SingleBlock : public TraitBase<ConcreteType, SingleBlock> {
772+
public:
773+
static LogicalResult verifyTrait(Operation *op) {
774+
for (unsigned i = 0, e = op->getNumRegions(); i < e; ++i) {
775+
Region &region = op->getRegion(i);
776+
777+
// Empty regions are fine.
778+
if (region.empty())
779+
continue;
780+
781+
// Non-empty regions must contain a single basic block.
782+
if (!llvm::hasSingleElement(region))
783+
return op->emitOpError("expects region #")
784+
<< i << " to have 0 or 1 blocks";
785+
786+
if (!ConcreteType::template hasTrait<NoTerminator>()) {
787+
Block &block = region.front();
788+
if (block.empty())
789+
return op->emitOpError() << "expects a non-empty block";
790+
}
791+
}
792+
return success();
793+
}
794+
795+
Block *getBody(unsigned idx = 0) {
796+
Region &region = this->getOperation()->getRegion(idx);
797+
assert(!region.empty() && "unexpected empty region");
798+
return &region.front();
799+
}
800+
Region &getBodyRegion(unsigned idx = 0) {
801+
return this->getOperation()->getRegion(idx);
802+
}
803+
804+
//===------------------------------------------------------------------===//
805+
// Single Region Utilities
806+
//===------------------------------------------------------------------===//
807+
808+
/// The following are a set of methods only enabled when the parent
809+
/// operation has a single region. Each of these methods take an additional
810+
/// template parameter that represents the concrete operation so that we
811+
/// can use SFINAE to disable the methods for non-single region operations.
812+
template <typename OpT, typename T = void>
813+
using enable_if_single_region =
814+
typename std::enable_if_t<OpT::template hasTrait<OneRegion>(), T>;
815+
816+
template <typename OpT = ConcreteType>
817+
enable_if_single_region<OpT, Block::iterator> begin() {
818+
return getBody()->begin();
819+
}
820+
template <typename OpT = ConcreteType>
821+
enable_if_single_region<OpT, Block::iterator> end() {
822+
return getBody()->end();
823+
}
824+
template <typename OpT = ConcreteType>
825+
enable_if_single_region<OpT, Operation &> front() {
826+
return *begin();
827+
}
828+
829+
/// Insert the operation into the back of the body.
830+
template <typename OpT = ConcreteType>
831+
enable_if_single_region<OpT> push_back(Operation *op) {
832+
insert(Block::iterator(getBody()->end()), op);
833+
}
834+
835+
/// Insert the operation at the given insertion point.
836+
template <typename OpT = ConcreteType>
837+
enable_if_single_region<OpT> insert(Operation *insertPt, Operation *op) {
838+
insert(Block::iterator(insertPt), op);
839+
}
840+
template <typename OpT = ConcreteType>
841+
enable_if_single_region<OpT> insert(Block::iterator insertPt, Operation *op) {
842+
getBody()->getOperations().insert(insertPt, op);
843+
}
844+
};
845+
760846
//===----------------------------------------------------------------------===//
761847
// SingleBlockImplicitTerminator
762848

@@ -765,8 +851,9 @@ class VariadicSuccessors
765851
template <typename TerminatorOpType>
766852
struct SingleBlockImplicitTerminator {
767853
template <typename ConcreteType>
768-
class Impl : public TraitBase<ConcreteType, Impl> {
854+
class Impl : public SingleBlock<ConcreteType> {
769855
private:
856+
using Base = SingleBlock<ConcreteType>;
770857
/// Builds a terminator operation without relying on OpBuilder APIs to avoid
771858
/// cyclic header inclusion.
772859
static Operation *buildTerminator(OpBuilder &builder, Location loc) {
@@ -780,22 +867,14 @@ struct SingleBlockImplicitTerminator {
780867
using ImplicitTerminatorOpT = TerminatorOpType;
781868

782869
static LogicalResult verifyTrait(Operation *op) {
870+
if (failed(Base::verifyTrait(op)))
871+
return failure();
783872
for (unsigned i = 0, e = op->getNumRegions(); i < e; ++i) {
784873
Region &region = op->getRegion(i);
785-
786874
// Empty regions are fine.
787875
if (region.empty())
788876
continue;
789-
790-
// Non-empty regions must contain a single basic block.
791-
if (std::next(region.begin()) != region.end())
792-
return op->emitOpError("expects region #")
793-
<< i << " to have 0 or 1 blocks";
794-
795-
Block &block = region.front();
796-
if (block.empty())
797-
return op->emitOpError() << "expects a non-empty block";
798-
Operation &terminator = block.back();
877+
Operation &terminator = region.front().back();
799878
if (isa<TerminatorOpType>(terminator))
800879
continue;
801880

@@ -828,40 +907,15 @@ struct SingleBlockImplicitTerminator {
828907
buildTerminator);
829908
}
830909

831-
Block *getBody(unsigned idx = 0) {
832-
Region &region = this->getOperation()->getRegion(idx);
833-
assert(!region.empty() && "unexpected empty region");
834-
return &region.front();
835-
}
836-
Region &getBodyRegion(unsigned idx = 0) {
837-
return this->getOperation()->getRegion(idx);
838-
}
839-
840910
//===------------------------------------------------------------------===//
841911
// Single Region Utilities
842912
//===------------------------------------------------------------------===//
913+
using Base::getBody;
843914

844-
/// The following are a set of methods only enabled when the parent
845-
/// operation has a single region. Each of these methods take an additional
846-
/// template parameter that represents the concrete operation so that we
847-
/// can use SFINAE to disable the methods for non-single region operations.
848915
template <typename OpT, typename T = void>
849916
using enable_if_single_region =
850917
typename std::enable_if_t<OpT::template hasTrait<OneRegion>(), T>;
851918

852-
template <typename OpT = ConcreteType>
853-
enable_if_single_region<OpT, Block::iterator> begin() {
854-
return getBody()->begin();
855-
}
856-
template <typename OpT = ConcreteType>
857-
enable_if_single_region<OpT, Block::iterator> end() {
858-
return getBody()->end();
859-
}
860-
template <typename OpT = ConcreteType>
861-
enable_if_single_region<OpT, Operation &> front() {
862-
return *begin();
863-
}
864-
865919
/// Insert the operation into the back of the body, before the terminator.
866920
template <typename OpT = ConcreteType>
867921
enable_if_single_region<OpT> push_back(Operation *op) {
@@ -886,6 +940,27 @@ struct SingleBlockImplicitTerminator {
886940
};
887941
};
888942

943+
/// Check is an op defines the `ImplicitTerminatorOpT` member. This is intended
944+
/// to be used with `llvm::is_detected`.
945+
template <class T>
946+
using has_implicit_terminator_t = typename T::ImplicitTerminatorOpT;
947+
948+
/// Support to check if an operation has the SingleBlockImplicitTerminator
949+
/// trait. We can't just use `hasTrait` because this class is templated on a
950+
/// specific terminator op.
951+
template <class Op, bool hasTerminator =
952+
llvm::is_detected<has_implicit_terminator_t, Op>::value>
953+
struct hasSingleBlockImplicitTerminator {
954+
static constexpr bool value = std::is_base_of<
955+
typename OpTrait::SingleBlockImplicitTerminator<
956+
typename Op::ImplicitTerminatorOpT>::template Impl<Op>,
957+
Op>::value;
958+
};
959+
template <class Op>
960+
struct hasSingleBlockImplicitTerminator<Op, false> {
961+
static constexpr bool value = false;
962+
};
963+
889964
//===----------------------------------------------------------------------===//
890965
// Misc Traits
891966

0 commit comments

Comments
 (0)