This was discussed in a recent ODM, the requirement for every block to have a terminator is an artifact of the evolution of MLIR from a SSA-CFG centric representation to a more generic IR.
This lead to operations like ModuleOp to introduce a companion terminator operation which is never displayed, but has to be explicitly handled. For example the codebase has code like this in many places:
target->addLegalOp<ModuleOp, ModuleTerminatorOp>();
Or even entire patterns:
class ModuleEndConversionPattern
: public SPIRVToLLVMConversion<spirv::ModuleEndOp> {
public:
using SPIRVToLLVMConversion<spirv::ModuleEndOp>::SPIRVToLLVMConversion;
LogicalResult
matchAndRewrite(spirv::ModuleEndOp moduleEndOp, ArrayRef<Value> operands,
ConversionPatternRewriter &rewriter) const override {
rewriter.replaceOpWithNewOp<ModuleTerminatorOp>(moduleEndOp);
return success();
}
};
I have patch that introduce the possibility to make terminator optional: https://reviews.llvm.org/D98468
This supposed that the region has a single block and it is intended to be used for GraphRegion. The patch is fairly large but only because to validate the feature I actually applied it to MLIR ModuleOp
and deleted ModuleTerminatorOp
from the codebase, i.e. ModuleOp
does not define this trait SingleBlockImplicitTerminator<"ModuleTerminatorOp">
anymore and instead append the newly available NoTerminator.traits
(see the diff in BuiltinOps.td.
If/when we land this, we can then update all the other flavors of Module (SPIRV and GPU upstream) to remove their terminator, but also other region operations like the FunctionLibraryOp
in the shape dialect, which has its own ShapeFunctionLibraryTerminatorOp
described as “A pseudo op that marks the end of a shape function library”.