1 //===- TestDialect.cpp - MLIR Dialect for Testing -------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "TestDialect.h"
10 #include "TestTypes.h"
11 #include "mlir/Dialect/StandardOps/IR/Ops.h"
12 #include "mlir/IR/DialectImplementation.h"
13 #include "mlir/IR/Function.h"
14 #include "mlir/IR/Module.h"
15 #include "mlir/IR/PatternMatch.h"
16 #include "mlir/IR/TypeUtilities.h"
17 #include "mlir/Transforms/FoldUtils.h"
18 #include "mlir/Transforms/InliningUtils.h"
19 #include "llvm/ADT/StringSwitch.h"
20 
21 using namespace mlir;
22 
23 //===----------------------------------------------------------------------===//
24 // TestDialect Interfaces
25 //===----------------------------------------------------------------------===//
26 
27 namespace {
28 
29 // Test support for interacting with the AsmPrinter.
30 struct TestOpAsmInterface : public OpAsmDialectInterface {
31   using OpAsmDialectInterface::OpAsmDialectInterface;
32 
33   void getAsmResultNames(Operation *op,
34                          OpAsmSetValueNameFn setNameFn) const final {
35     if (auto asmOp = dyn_cast<AsmDialectInterfaceOp>(op))
36       setNameFn(asmOp, "result");
37   }
38 
39   void getAsmBlockArgumentNames(Block *block,
40                                 OpAsmSetValueNameFn setNameFn) const final {
41     auto op = block->getParentOp();
42     auto arrayAttr = op->getAttrOfType<ArrayAttr>("arg_names");
43     if (!arrayAttr)
44       return;
45     auto args = block->getArguments();
46     auto e = std::min(arrayAttr.size(), args.size());
47     for (unsigned i = 0; i < e; ++i) {
48       if (auto strAttr = arrayAttr[i].dyn_cast<StringAttr>())
49         setNameFn(args[i], strAttr.getValue());
50     }
51   }
52 };
53 
54 struct TestOpFolderDialectInterface : public OpFolderDialectInterface {
55   using OpFolderDialectInterface::OpFolderDialectInterface;
56 
57   /// Registered hook to check if the given region, which is attached to an
58   /// operation that is *not* isolated from above, should be used when
59   /// materializing constants.
60   bool shouldMaterializeInto(Region *region) const final {
61     // If this is a one region operation, then insert into it.
62     return isa<OneRegionOp>(region->getParentOp());
63   }
64 };
65 
66 /// This class defines the interface for handling inlining with standard
67 /// operations.
68 struct TestInlinerInterface : public DialectInlinerInterface {
69   using DialectInlinerInterface::DialectInlinerInterface;
70 
71   //===--------------------------------------------------------------------===//
72   // Analysis Hooks
73   //===--------------------------------------------------------------------===//
74 
75   bool isLegalToInline(Region *, Region *, BlockAndValueMapping &) const final {
76     // Inlining into test dialect regions is legal.
77     return true;
78   }
79   bool isLegalToInline(Operation *, Region *,
80                        BlockAndValueMapping &) const final {
81     return true;
82   }
83 
84   bool shouldAnalyzeRecursively(Operation *op) const final {
85     // Analyze recursively if this is not a functional region operation, it
86     // froms a separate functional scope.
87     return !isa<FunctionalRegionOp>(op);
88   }
89 
90   //===--------------------------------------------------------------------===//
91   // Transformation Hooks
92   //===--------------------------------------------------------------------===//
93 
94   /// Handle the given inlined terminator by replacing it with a new operation
95   /// as necessary.
96   void handleTerminator(Operation *op,
97                         ArrayRef<Value> valuesToRepl) const final {
98     // Only handle "test.return" here.
99     auto returnOp = dyn_cast<TestReturnOp>(op);
100     if (!returnOp)
101       return;
102 
103     // Replace the values directly with the return operands.
104     assert(returnOp.getNumOperands() == valuesToRepl.size());
105     for (const auto &it : llvm::enumerate(returnOp.getOperands()))
106       valuesToRepl[it.index()].replaceAllUsesWith(it.value());
107   }
108 
109   /// Attempt to materialize a conversion for a type mismatch between a call
110   /// from this dialect, and a callable region. This method should generate an
111   /// operation that takes 'input' as the only operand, and produces a single
112   /// result of 'resultType'. If a conversion can not be generated, nullptr
113   /// should be returned.
114   Operation *materializeCallConversion(OpBuilder &builder, Value input,
115                                        Type resultType,
116                                        Location conversionLoc) const final {
117     // Only allow conversion for i16/i32 types.
118     if (!(resultType.isSignlessInteger(16) ||
119           resultType.isSignlessInteger(32)) ||
120         !(input.getType().isSignlessInteger(16) ||
121           input.getType().isSignlessInteger(32)))
122       return nullptr;
123     return builder.create<TestCastOp>(conversionLoc, resultType, input);
124   }
125 };
126 } // end anonymous namespace
127 
128 //===----------------------------------------------------------------------===//
129 // TestDialect
130 //===----------------------------------------------------------------------===//
131 
132 TestDialect::TestDialect(MLIRContext *context)
133     : Dialect(getDialectNamespace(), context) {
134   addOperations<
135 #define GET_OP_LIST
136 #include "TestOps.cpp.inc"
137       >();
138   addInterfaces<TestOpAsmInterface, TestOpFolderDialectInterface,
139                 TestInlinerInterface>();
140   addTypes<TestType>();
141   allowUnknownOperations();
142 }
143 
144 Type TestDialect::parseType(DialectAsmParser &parser) const {
145   if (failed(parser.parseKeyword("test_type")))
146     return Type();
147   return TestType::get(getContext());
148 }
149 
150 void TestDialect::printType(Type type, DialectAsmPrinter &printer) const {
151   assert(type.isa<TestType>() && "unexpected type");
152   printer << "test_type";
153 }
154 
155 LogicalResult TestDialect::verifyOperationAttribute(Operation *op,
156                                                     NamedAttribute namedAttr) {
157   if (namedAttr.first == "test.invalid_attr")
158     return op->emitError() << "invalid to use 'test.invalid_attr'";
159   return success();
160 }
161 
162 LogicalResult TestDialect::verifyRegionArgAttribute(Operation *op,
163                                                     unsigned regionIndex,
164                                                     unsigned argIndex,
165                                                     NamedAttribute namedAttr) {
166   if (namedAttr.first == "test.invalid_attr")
167     return op->emitError() << "invalid to use 'test.invalid_attr'";
168   return success();
169 }
170 
171 LogicalResult
172 TestDialect::verifyRegionResultAttribute(Operation *op, unsigned regionIndex,
173                                          unsigned resultIndex,
174                                          NamedAttribute namedAttr) {
175   if (namedAttr.first == "test.invalid_attr")
176     return op->emitError() << "invalid to use 'test.invalid_attr'";
177   return success();
178 }
179 
180 //===----------------------------------------------------------------------===//
181 // TestBranchOp
182 //===----------------------------------------------------------------------===//
183 
184 Optional<MutableOperandRange>
185 TestBranchOp::getMutableSuccessorOperands(unsigned index) {
186   assert(index == 0 && "invalid successor index");
187   return targetOperandsMutable();
188 }
189 
190 //===----------------------------------------------------------------------===//
191 // TestFoldToCallOp
192 //===----------------------------------------------------------------------===//
193 
194 namespace {
195 struct FoldToCallOpPattern : public OpRewritePattern<FoldToCallOp> {
196   using OpRewritePattern<FoldToCallOp>::OpRewritePattern;
197 
198   LogicalResult matchAndRewrite(FoldToCallOp op,
199                                 PatternRewriter &rewriter) const override {
200     rewriter.replaceOpWithNewOp<CallOp>(op, ArrayRef<Type>(), op.calleeAttr(),
201                                         ValueRange());
202     return success();
203   }
204 };
205 } // end anonymous namespace
206 
207 void FoldToCallOp::getCanonicalizationPatterns(
208     OwningRewritePatternList &results, MLIRContext *context) {
209   results.insert<FoldToCallOpPattern>(context);
210 }
211 
212 //===----------------------------------------------------------------------===//
213 // Test IsolatedRegionOp - parse passthrough region arguments.
214 //===----------------------------------------------------------------------===//
215 
216 static ParseResult parseIsolatedRegionOp(OpAsmParser &parser,
217                                          OperationState &result) {
218   OpAsmParser::OperandType argInfo;
219   Type argType = parser.getBuilder().getIndexType();
220 
221   // Parse the input operand.
222   if (parser.parseOperand(argInfo) ||
223       parser.resolveOperand(argInfo, argType, result.operands))
224     return failure();
225 
226   // Parse the body region, and reuse the operand info as the argument info.
227   Region *body = result.addRegion();
228   return parser.parseRegion(*body, argInfo, argType,
229                             /*enableNameShadowing=*/true);
230 }
231 
232 static void print(OpAsmPrinter &p, IsolatedRegionOp op) {
233   p << "test.isolated_region ";
234   p.printOperand(op.getOperand());
235   p.shadowRegionArgs(op.region(), op.getOperand());
236   p.printRegion(op.region(), /*printEntryBlockArgs=*/false);
237 }
238 
239 //===----------------------------------------------------------------------===//
240 // Test SSACFGRegionOp
241 //===----------------------------------------------------------------------===//
242 
243 RegionKind SSACFGRegionOp::getRegionKind(unsigned index) {
244   return RegionKind::SSACFG;
245 }
246 
247 //===----------------------------------------------------------------------===//
248 // Test GraphRegionOp
249 //===----------------------------------------------------------------------===//
250 
251 static ParseResult parseGraphRegionOp(OpAsmParser &parser,
252                                       OperationState &result) {
253   // Parse the body region, and reuse the operand info as the argument info.
254   Region *body = result.addRegion();
255   return parser.parseRegion(*body, /*arguments=*/{}, /*argTypes=*/{});
256 }
257 
258 static void print(OpAsmPrinter &p, GraphRegionOp op) {
259   p << "test.graph_region ";
260   p.printRegion(op.region(), /*printEntryBlockArgs=*/false);
261 }
262 
263 RegionKind GraphRegionOp::getRegionKind(unsigned index) {
264   return RegionKind::Graph;
265 }
266 
267 //===----------------------------------------------------------------------===//
268 // Test AffineScopeOp
269 //===----------------------------------------------------------------------===//
270 
271 static ParseResult parseAffineScopeOp(OpAsmParser &parser,
272                                       OperationState &result) {
273   // Parse the body region, and reuse the operand info as the argument info.
274   Region *body = result.addRegion();
275   return parser.parseRegion(*body, /*arguments=*/{}, /*argTypes=*/{});
276 }
277 
278 static void print(OpAsmPrinter &p, AffineScopeOp op) {
279   p << "test.affine_scope ";
280   p.printRegion(op.region(), /*printEntryBlockArgs=*/false);
281 }
282 
283 //===----------------------------------------------------------------------===//
284 // Test parser.
285 //===----------------------------------------------------------------------===//
286 
287 static ParseResult parseWrappedKeywordOp(OpAsmParser &parser,
288                                          OperationState &result) {
289   StringRef keyword;
290   if (parser.parseKeyword(&keyword))
291     return failure();
292   result.addAttribute("keyword", parser.getBuilder().getStringAttr(keyword));
293   return success();
294 }
295 
296 static void print(OpAsmPrinter &p, WrappedKeywordOp op) {
297   p << WrappedKeywordOp::getOperationName() << " " << op.keyword();
298 }
299 
300 //===----------------------------------------------------------------------===//
301 // Test WrapRegionOp - wrapping op exercising `parseGenericOperation()`.
302 
303 static ParseResult parseWrappingRegionOp(OpAsmParser &parser,
304                                          OperationState &result) {
305   if (parser.parseKeyword("wraps"))
306     return failure();
307 
308   // Parse the wrapped op in a region
309   Region &body = *result.addRegion();
310   body.push_back(new Block);
311   Block &block = body.back();
312   Operation *wrapped_op = parser.parseGenericOperation(&block, block.begin());
313   if (!wrapped_op)
314     return failure();
315 
316   // Create a return terminator in the inner region, pass as operand to the
317   // terminator the returned values from the wrapped operation.
318   SmallVector<Value, 8> return_operands(wrapped_op->getResults());
319   OpBuilder builder(parser.getBuilder().getContext());
320   builder.setInsertionPointToEnd(&block);
321   builder.create<TestReturnOp>(wrapped_op->getLoc(), return_operands);
322 
323   // Get the results type for the wrapping op from the terminator operands.
324   Operation &return_op = body.back().back();
325   result.types.append(return_op.operand_type_begin(),
326                       return_op.operand_type_end());
327 
328   // Use the location of the wrapped op for the "test.wrapping_region" op.
329   result.location = wrapped_op->getLoc();
330 
331   return success();
332 }
333 
334 static void print(OpAsmPrinter &p, WrappingRegionOp op) {
335   p << op.getOperationName() << " wraps ";
336   p.printGenericOp(&op.region().front().front());
337 }
338 
339 //===----------------------------------------------------------------------===//
340 // Test PolyForOp - parse list of region arguments.
341 //===----------------------------------------------------------------------===//
342 
343 static ParseResult parsePolyForOp(OpAsmParser &parser, OperationState &result) {
344   SmallVector<OpAsmParser::OperandType, 4> ivsInfo;
345   // Parse list of region arguments without a delimiter.
346   if (parser.parseRegionArgumentList(ivsInfo))
347     return failure();
348 
349   // Parse the body region.
350   Region *body = result.addRegion();
351   auto &builder = parser.getBuilder();
352   SmallVector<Type, 4> argTypes(ivsInfo.size(), builder.getIndexType());
353   return parser.parseRegion(*body, ivsInfo, argTypes);
354 }
355 
356 //===----------------------------------------------------------------------===//
357 // Test removing op with inner ops.
358 //===----------------------------------------------------------------------===//
359 
360 namespace {
361 struct TestRemoveOpWithInnerOps
362     : public OpRewritePattern<TestOpWithRegionPattern> {
363   using OpRewritePattern<TestOpWithRegionPattern>::OpRewritePattern;
364 
365   LogicalResult matchAndRewrite(TestOpWithRegionPattern op,
366                                 PatternRewriter &rewriter) const override {
367     rewriter.eraseOp(op);
368     return success();
369   }
370 };
371 } // end anonymous namespace
372 
373 void TestOpWithRegionPattern::getCanonicalizationPatterns(
374     OwningRewritePatternList &results, MLIRContext *context) {
375   results.insert<TestRemoveOpWithInnerOps>(context);
376 }
377 
378 OpFoldResult TestOpWithRegionFold::fold(ArrayRef<Attribute> operands) {
379   return operand();
380 }
381 
382 LogicalResult TestOpWithVariadicResultsAndFolder::fold(
383     ArrayRef<Attribute> operands, SmallVectorImpl<OpFoldResult> &results) {
384   for (Value input : this->operands()) {
385     results.push_back(input);
386   }
387   return success();
388 }
389 
390 OpFoldResult TestOpInPlaceFold::fold(ArrayRef<Attribute> operands) {
391   assert(operands.size() == 1);
392   if (operands.front()) {
393     setAttr("attr", operands.front());
394     return getResult();
395   }
396   return {};
397 }
398 
399 LogicalResult OpWithInferTypeInterfaceOp::inferReturnTypes(
400     MLIRContext *, Optional<Location> location, ValueRange operands,
401     DictionaryAttr attributes, RegionRange regions,
402     SmallVectorImpl<Type> &inferredReturnTypes) {
403   if (operands[0].getType() != operands[1].getType()) {
404     return emitOptionalError(location, "operand type mismatch ",
405                              operands[0].getType(), " vs ",
406                              operands[1].getType());
407   }
408   inferredReturnTypes.assign({operands[0].getType()});
409   return success();
410 }
411 
412 LogicalResult OpWithShapedTypeInferTypeInterfaceOp::inferReturnTypeComponents(
413     MLIRContext *context, Optional<Location> location, ValueRange operands,
414     DictionaryAttr attributes, RegionRange regions,
415     SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
416   // Create return type consisting of the last element of the first operand.
417   auto operandType = *operands.getTypes().begin();
418   auto sval = operandType.dyn_cast<ShapedType>();
419   if (!sval) {
420     return emitOptionalError(location, "only shaped type operands allowed");
421   }
422   int64_t dim =
423       sval.hasRank() ? sval.getShape().front() : ShapedType::kDynamicSize;
424   auto type = IntegerType::get(17, context);
425   inferredReturnShapes.push_back(ShapedTypeComponents({dim}, type));
426   return success();
427 }
428 
429 LogicalResult OpWithShapedTypeInferTypeInterfaceOp::reifyReturnTypeShapes(
430     OpBuilder &builder, llvm::SmallVectorImpl<Value> &shapes) {
431   shapes = SmallVector<Value, 1>{
432       builder.createOrFold<DimOp>(getLoc(), getOperand(0), 0)};
433   return success();
434 }
435 
436 //===----------------------------------------------------------------------===//
437 // Test SideEffect interfaces
438 //===----------------------------------------------------------------------===//
439 
440 namespace {
441 /// A test resource for side effects.
442 struct TestResource : public SideEffects::Resource::Base<TestResource> {
443   StringRef getName() final { return "<Test>"; }
444 };
445 } // end anonymous namespace
446 
447 void SideEffectOp::getEffects(
448     SmallVectorImpl<MemoryEffects::EffectInstance> &effects) {
449   // Check for an effects attribute on the op instance.
450   ArrayAttr effectsAttr = getAttrOfType<ArrayAttr>("effects");
451   if (!effectsAttr)
452     return;
453 
454   // If there is one, it is an array of dictionary attributes that hold
455   // information on the effects of this operation.
456   for (Attribute element : effectsAttr) {
457     DictionaryAttr effectElement = element.cast<DictionaryAttr>();
458 
459     // Get the specific memory effect.
460     MemoryEffects::Effect *effect =
461         llvm::StringSwitch<MemoryEffects::Effect *>(
462             effectElement.get("effect").cast<StringAttr>().getValue())
463             .Case("allocate", MemoryEffects::Allocate::get())
464             .Case("free", MemoryEffects::Free::get())
465             .Case("read", MemoryEffects::Read::get())
466             .Case("write", MemoryEffects::Write::get());
467 
468     // Check for a result to affect.
469     Value value;
470     if (effectElement.get("on_result"))
471       value = getResult();
472 
473     // Check for a non-default resource to use.
474     SideEffects::Resource *resource = SideEffects::DefaultResource::get();
475     if (effectElement.get("test_resource"))
476       resource = TestResource::get();
477 
478     effects.emplace_back(effect, value, resource);
479   }
480 }
481 
482 //===----------------------------------------------------------------------===//
483 // StringAttrPrettyNameOp
484 //===----------------------------------------------------------------------===//
485 
486 // This op has fancy handling of its SSA result name.
487 static ParseResult parseStringAttrPrettyNameOp(OpAsmParser &parser,
488                                                OperationState &result) {
489   // Add the result types.
490   for (size_t i = 0, e = parser.getNumResults(); i != e; ++i)
491     result.addTypes(parser.getBuilder().getIntegerType(32));
492 
493   if (parser.parseOptionalAttrDictWithKeyword(result.attributes))
494     return failure();
495 
496   // If the attribute dictionary contains no 'names' attribute, infer it from
497   // the SSA name (if specified).
498   bool hadNames = llvm::any_of(result.attributes, [](NamedAttribute attr) {
499     return attr.first == "names";
500   });
501 
502   // If there was no name specified, check to see if there was a useful name
503   // specified in the asm file.
504   if (hadNames || parser.getNumResults() == 0)
505     return success();
506 
507   SmallVector<StringRef, 4> names;
508   auto *context = result.getContext();
509 
510   for (size_t i = 0, e = parser.getNumResults(); i != e; ++i) {
511     auto resultName = parser.getResultName(i);
512     StringRef nameStr;
513     if (!resultName.first.empty() && !isdigit(resultName.first[0]))
514       nameStr = resultName.first;
515 
516     names.push_back(nameStr);
517   }
518 
519   auto namesAttr = parser.getBuilder().getStrArrayAttr(names);
520   result.attributes.push_back({Identifier::get("names", context), namesAttr});
521   return success();
522 }
523 
524 static void print(OpAsmPrinter &p, StringAttrPrettyNameOp op) {
525   p << "test.string_attr_pretty_name";
526 
527   // Note that we only need to print the "name" attribute if the asmprinter
528   // result name disagrees with it.  This can happen in strange cases, e.g.
529   // when there are conflicts.
530   bool namesDisagree = op.names().size() != op.getNumResults();
531 
532   SmallString<32> resultNameStr;
533   for (size_t i = 0, e = op.getNumResults(); i != e && !namesDisagree; ++i) {
534     resultNameStr.clear();
535     llvm::raw_svector_ostream tmpStream(resultNameStr);
536     p.printOperand(op.getResult(i), tmpStream);
537 
538     auto expectedName = op.names()[i].dyn_cast<StringAttr>();
539     if (!expectedName ||
540         tmpStream.str().drop_front() != expectedName.getValue()) {
541       namesDisagree = true;
542     }
543   }
544 
545   if (namesDisagree)
546     p.printOptionalAttrDictWithKeyword(op.getAttrs());
547   else
548     p.printOptionalAttrDictWithKeyword(op.getAttrs(), {"names"});
549 }
550 
551 // We set the SSA name in the asm syntax to the contents of the name
552 // attribute.
553 void StringAttrPrettyNameOp::getAsmResultNames(
554     function_ref<void(Value, StringRef)> setNameFn) {
555 
556   auto value = names();
557   for (size_t i = 0, e = value.size(); i != e; ++i)
558     if (auto str = value[i].dyn_cast<StringAttr>())
559       if (!str.getValue().empty())
560         setNameFn(getResult(i), str.getValue());
561 }
562 
563 //===----------------------------------------------------------------------===//
564 // RegionIfOp
565 //===----------------------------------------------------------------------===//
566 
567 static void print(OpAsmPrinter &p, RegionIfOp op) {
568   p << RegionIfOp::getOperationName() << " ";
569   p.printOperands(op.getOperands());
570   p << ": " << op.getOperandTypes();
571   p.printArrowTypeList(op.getResultTypes());
572   p << " then";
573   p.printRegion(op.thenRegion(),
574                 /*printEntryBlockArgs=*/true,
575                 /*printBlockTerminators=*/true);
576   p << " else";
577   p.printRegion(op.elseRegion(),
578                 /*printEntryBlockArgs=*/true,
579                 /*printBlockTerminators=*/true);
580   p << " join";
581   p.printRegion(op.joinRegion(),
582                 /*printEntryBlockArgs=*/true,
583                 /*printBlockTerminators=*/true);
584 }
585 
586 static ParseResult parseRegionIfOp(OpAsmParser &parser,
587                                    OperationState &result) {
588   SmallVector<OpAsmParser::OperandType, 2> operandInfos;
589   SmallVector<Type, 2> operandTypes;
590 
591   result.regions.reserve(3);
592   Region *thenRegion = result.addRegion();
593   Region *elseRegion = result.addRegion();
594   Region *joinRegion = result.addRegion();
595 
596   // Parse operand, type and arrow type lists.
597   if (parser.parseOperandList(operandInfos) ||
598       parser.parseColonTypeList(operandTypes) ||
599       parser.parseArrowTypeList(result.types))
600     return failure();
601 
602   // Parse all attached regions.
603   if (parser.parseKeyword("then") || parser.parseRegion(*thenRegion, {}, {}) ||
604       parser.parseKeyword("else") || parser.parseRegion(*elseRegion, {}, {}) ||
605       parser.parseKeyword("join") || parser.parseRegion(*joinRegion, {}, {}))
606     return failure();
607 
608   return parser.resolveOperands(operandInfos, operandTypes,
609                                 parser.getCurrentLocation(), result.operands);
610 }
611 
612 OperandRange RegionIfOp::getSuccessorEntryOperands(unsigned index) {
613   assert(index < 2 && "invalid region index");
614   return getOperands();
615 }
616 
617 void RegionIfOp::getSuccessorRegions(
618     Optional<unsigned> index, ArrayRef<Attribute> operands,
619     SmallVectorImpl<RegionSuccessor> &regions) {
620   // We always branch to the join region.
621   if (index.hasValue()) {
622     if (index.getValue() < 2)
623       regions.push_back(RegionSuccessor(&joinRegion(), getJoinArgs()));
624     else
625       regions.push_back(RegionSuccessor(getResults()));
626     return;
627   }
628 
629   // The then and else regions are the entry regions of this op.
630   regions.push_back(RegionSuccessor(&thenRegion(), getThenArgs()));
631   regions.push_back(RegionSuccessor(&elseRegion(), getElseArgs()));
632 }
633 
634 //===----------------------------------------------------------------------===//
635 // Dialect Registration
636 //===----------------------------------------------------------------------===//
637 
638 // Static initialization for Test dialect registration.
639 static DialectRegistration<TestDialect> testDialect;
640 
641 #include "TestOpEnums.cpp.inc"
642 #include "TestOpStructs.cpp.inc"
643 #include "TestTypeInterfaces.cpp.inc"
644 
645 #define GET_OP_CLASSES
646 #include "TestOps.cpp.inc"
647