1 //===- TestPatterns.cpp - Test dialect pattern driver ---------------------===// 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 "mlir/Conversion/StandardToStandard/StandardToStandard.h" 11 #include "mlir/IR/PatternMatch.h" 12 #include "mlir/Pass/Pass.h" 13 #include "mlir/Transforms/DialectConversion.h" 14 using namespace mlir; 15 16 // Native function for testing NativeCodeCall 17 static Value chooseOperand(Value input1, Value input2, BoolAttr choice) { 18 return choice.getValue() ? input1 : input2; 19 } 20 21 static void createOpI(PatternRewriter &rewriter, Value input) { 22 rewriter.create<OpI>(rewriter.getUnknownLoc(), input); 23 } 24 25 static void handleNoResultOp(PatternRewriter &rewriter, 26 OpSymbolBindingNoResult op) { 27 // Turn the no result op to a one-result op. 28 rewriter.create<OpSymbolBindingB>(op.getLoc(), op.operand().getType(), 29 op.operand()); 30 } 31 32 namespace { 33 #include "TestPatterns.inc" 34 } // end anonymous namespace 35 36 //===----------------------------------------------------------------------===// 37 // Canonicalizer Driver. 38 //===----------------------------------------------------------------------===// 39 40 namespace { 41 struct TestPatternDriver : public FunctionPass<TestPatternDriver> { 42 void runOnFunction() override { 43 mlir::OwningRewritePatternList patterns; 44 populateWithGenerated(&getContext(), &patterns); 45 46 // Verify named pattern is generated with expected name. 47 patterns.insert<TestNamedPatternRule>(&getContext()); 48 49 applyPatternsGreedily(getFunction(), patterns); 50 } 51 }; 52 } // end anonymous namespace 53 54 //===----------------------------------------------------------------------===// 55 // ReturnType Driver. 56 //===----------------------------------------------------------------------===// 57 58 namespace { 59 // Generate ops for each instance where the type can be successfully inferred. 60 template <typename OpTy> 61 static void invokeCreateWithInferredReturnType(Operation *op) { 62 auto *context = op->getContext(); 63 auto fop = op->getParentOfType<FuncOp>(); 64 auto location = UnknownLoc::get(context); 65 OpBuilder b(op); 66 b.setInsertionPointAfter(op); 67 68 // Use permutations of 2 args as operands. 69 assert(fop.getNumArguments() >= 2); 70 for (int i = 0, e = fop.getNumArguments(); i < e; ++i) { 71 for (int j = 0; j < e; ++j) { 72 std::array<Value, 2> values = {{fop.getArgument(i), fop.getArgument(j)}}; 73 SmallVector<Type, 2> inferredReturnTypes; 74 if (succeeded(OpTy::inferReturnTypes(context, llvm::None, values, 75 op->getAttrs(), op->getRegions(), 76 inferredReturnTypes))) { 77 OperationState state(location, OpTy::getOperationName()); 78 // TODO(jpienaar): Expand to regions. 79 OpTy::build(&b, state, values, op->getAttrs()); 80 (void)b.createOperation(state); 81 } 82 } 83 } 84 } 85 86 static void reifyReturnShape(Operation *op) { 87 OpBuilder b(op); 88 89 // Use permutations of 2 args as operands. 90 auto shapedOp = cast<OpWithShapedTypeInferTypeInterfaceOp>(op); 91 SmallVector<Value, 2> shapes; 92 if (failed(shapedOp.reifyReturnTypeShapes(b, shapes))) 93 return; 94 for (auto it : llvm::enumerate(shapes)) 95 op->emitRemark() << "value " << it.index() << ": " 96 << it.value().getDefiningOp(); 97 } 98 99 struct TestReturnTypeDriver : public FunctionPass<TestReturnTypeDriver> { 100 void runOnFunction() override { 101 if (getFunction().getName() == "testCreateFunctions") { 102 std::vector<Operation *> ops; 103 // Collect ops to avoid triggering on inserted ops. 104 for (auto &op : getFunction().getBody().front()) 105 ops.push_back(&op); 106 // Generate test patterns for each, but skip terminator. 107 for (auto *op : llvm::makeArrayRef(ops).drop_back()) { 108 // Test create method of each of the Op classes below. The resultant 109 // output would be in reverse order underneath `op` from which 110 // the attributes and regions are used. 111 invokeCreateWithInferredReturnType<OpWithInferTypeInterfaceOp>(op); 112 invokeCreateWithInferredReturnType< 113 OpWithShapedTypeInferTypeInterfaceOp>(op); 114 }; 115 return; 116 } 117 if (getFunction().getName() == "testReifyFunctions") { 118 std::vector<Operation *> ops; 119 // Collect ops to avoid triggering on inserted ops. 120 for (auto &op : getFunction().getBody().front()) 121 if (isa<OpWithShapedTypeInferTypeInterfaceOp>(op)) 122 ops.push_back(&op); 123 // Generate test patterns for each, but skip terminator. 124 for (auto *op : ops) 125 reifyReturnShape(op); 126 } 127 } 128 }; 129 } // end anonymous namespace 130 131 //===----------------------------------------------------------------------===// 132 // Legalization Driver. 133 //===----------------------------------------------------------------------===// 134 135 namespace { 136 //===----------------------------------------------------------------------===// 137 // Region-Block Rewrite Testing 138 139 /// This pattern is a simple pattern that inlines the first region of a given 140 /// operation into the parent region. 141 struct TestRegionRewriteBlockMovement : public ConversionPattern { 142 TestRegionRewriteBlockMovement(MLIRContext *ctx) 143 : ConversionPattern("test.region", 1, ctx) {} 144 145 LogicalResult 146 matchAndRewrite(Operation *op, ArrayRef<Value> operands, 147 ConversionPatternRewriter &rewriter) const final { 148 // Inline this region into the parent region. 149 auto &parentRegion = *op->getParentRegion(); 150 if (op->getAttr("legalizer.should_clone")) 151 rewriter.cloneRegionBefore(op->getRegion(0), parentRegion, 152 parentRegion.end()); 153 else 154 rewriter.inlineRegionBefore(op->getRegion(0), parentRegion, 155 parentRegion.end()); 156 157 // Drop this operation. 158 rewriter.eraseOp(op); 159 return success(); 160 } 161 }; 162 /// This pattern is a simple pattern that generates a region containing an 163 /// illegal operation. 164 struct TestRegionRewriteUndo : public RewritePattern { 165 TestRegionRewriteUndo(MLIRContext *ctx) 166 : RewritePattern("test.region_builder", 1, ctx) {} 167 168 LogicalResult matchAndRewrite(Operation *op, 169 PatternRewriter &rewriter) const final { 170 // Create the region operation with an entry block containing arguments. 171 OperationState newRegion(op->getLoc(), "test.region"); 172 newRegion.addRegion(); 173 auto *regionOp = rewriter.createOperation(newRegion); 174 auto *entryBlock = rewriter.createBlock(®ionOp->getRegion(0)); 175 entryBlock->addArgument(rewriter.getIntegerType(64)); 176 177 // Add an explicitly illegal operation to ensure the conversion fails. 178 rewriter.create<ILLegalOpF>(op->getLoc(), rewriter.getIntegerType(32)); 179 rewriter.create<TestValidOp>(op->getLoc(), ArrayRef<Value>()); 180 181 // Drop this operation. 182 rewriter.eraseOp(op); 183 return success(); 184 } 185 }; 186 /// A simple pattern that creates a block at the end of the parent region of the 187 /// matched operation. 188 struct TestCreateBlock : public RewritePattern { 189 TestCreateBlock(MLIRContext *ctx) 190 : RewritePattern("test.create_block", /*benefit=*/1, ctx) {} 191 192 LogicalResult matchAndRewrite(Operation *op, 193 PatternRewriter &rewriter) const final { 194 Region ®ion = *op->getParentRegion(); 195 Type i32Type = rewriter.getIntegerType(32); 196 rewriter.createBlock(®ion, region.end(), {i32Type, i32Type}); 197 rewriter.create<TerminatorOp>(op->getLoc()); 198 rewriter.replaceOp(op, {}); 199 return success(); 200 } 201 }; 202 203 /// A simple pattern that creates a block containing an invalid operaiton in 204 /// order to trigger the block creation undo mechanism. 205 struct TestCreateIllegalBlock : public RewritePattern { 206 TestCreateIllegalBlock(MLIRContext *ctx) 207 : RewritePattern("test.create_illegal_block", /*benefit=*/1, ctx) {} 208 209 LogicalResult matchAndRewrite(Operation *op, 210 PatternRewriter &rewriter) const final { 211 Region ®ion = *op->getParentRegion(); 212 Type i32Type = rewriter.getIntegerType(32); 213 rewriter.createBlock(®ion, region.end(), {i32Type, i32Type}); 214 // Create an illegal op to ensure the conversion fails. 215 rewriter.create<ILLegalOpF>(op->getLoc(), i32Type); 216 rewriter.create<TerminatorOp>(op->getLoc()); 217 rewriter.replaceOp(op, {}); 218 return success(); 219 } 220 }; 221 222 //===----------------------------------------------------------------------===// 223 // Type-Conversion Rewrite Testing 224 225 /// This patterns erases a region operation that has had a type conversion. 226 struct TestDropOpSignatureConversion : public ConversionPattern { 227 TestDropOpSignatureConversion(MLIRContext *ctx, TypeConverter &converter) 228 : ConversionPattern("test.drop_region_op", 1, ctx), converter(converter) { 229 } 230 LogicalResult 231 matchAndRewrite(Operation *op, ArrayRef<Value> operands, 232 ConversionPatternRewriter &rewriter) const override { 233 Region ®ion = op->getRegion(0); 234 Block *entry = ®ion.front(); 235 236 // Convert the original entry arguments. 237 TypeConverter::SignatureConversion result(entry->getNumArguments()); 238 for (unsigned i = 0, e = entry->getNumArguments(); i != e; ++i) 239 if (failed(converter.convertSignatureArg( 240 i, entry->getArgument(i).getType(), result))) 241 return failure(); 242 243 // Convert the region signature and just drop the operation. 244 rewriter.applySignatureConversion(®ion, result); 245 rewriter.eraseOp(op); 246 return success(); 247 } 248 249 /// The type converter to use when rewriting the signature. 250 TypeConverter &converter; 251 }; 252 /// This pattern simply updates the operands of the given operation. 253 struct TestPassthroughInvalidOp : public ConversionPattern { 254 TestPassthroughInvalidOp(MLIRContext *ctx) 255 : ConversionPattern("test.invalid", 1, ctx) {} 256 LogicalResult 257 matchAndRewrite(Operation *op, ArrayRef<Value> operands, 258 ConversionPatternRewriter &rewriter) const final { 259 rewriter.replaceOpWithNewOp<TestValidOp>(op, llvm::None, operands, 260 llvm::None); 261 return success(); 262 } 263 }; 264 /// This pattern handles the case of a split return value. 265 struct TestSplitReturnType : public ConversionPattern { 266 TestSplitReturnType(MLIRContext *ctx) 267 : ConversionPattern("test.return", 1, ctx) {} 268 LogicalResult 269 matchAndRewrite(Operation *op, ArrayRef<Value> operands, 270 ConversionPatternRewriter &rewriter) const final { 271 // Check for a return of F32. 272 if (op->getNumOperands() != 1 || !op->getOperand(0).getType().isF32()) 273 return failure(); 274 275 // Check if the first operation is a cast operation, if it is we use the 276 // results directly. 277 auto *defOp = operands[0].getDefiningOp(); 278 if (auto packerOp = llvm::dyn_cast_or_null<TestCastOp>(defOp)) { 279 rewriter.replaceOpWithNewOp<TestReturnOp>(op, packerOp.getOperands()); 280 return success(); 281 } 282 283 // Otherwise, fail to match. 284 return failure(); 285 } 286 }; 287 288 //===----------------------------------------------------------------------===// 289 // Multi-Level Type-Conversion Rewrite Testing 290 struct TestChangeProducerTypeI32ToF32 : public ConversionPattern { 291 TestChangeProducerTypeI32ToF32(MLIRContext *ctx) 292 : ConversionPattern("test.type_producer", 1, ctx) {} 293 LogicalResult 294 matchAndRewrite(Operation *op, ArrayRef<Value> operands, 295 ConversionPatternRewriter &rewriter) const final { 296 // If the type is I32, change the type to F32. 297 if (!Type(*op->result_type_begin()).isSignlessInteger(32)) 298 return failure(); 299 rewriter.replaceOpWithNewOp<TestTypeProducerOp>(op, rewriter.getF32Type()); 300 return success(); 301 } 302 }; 303 struct TestChangeProducerTypeF32ToF64 : public ConversionPattern { 304 TestChangeProducerTypeF32ToF64(MLIRContext *ctx) 305 : ConversionPattern("test.type_producer", 1, ctx) {} 306 LogicalResult 307 matchAndRewrite(Operation *op, ArrayRef<Value> operands, 308 ConversionPatternRewriter &rewriter) const final { 309 // If the type is F32, change the type to F64. 310 if (!Type(*op->result_type_begin()).isF32()) 311 return rewriter.notifyMatchFailure(op, "expected single f32 operand"); 312 rewriter.replaceOpWithNewOp<TestTypeProducerOp>(op, rewriter.getF64Type()); 313 return success(); 314 } 315 }; 316 struct TestChangeProducerTypeF32ToInvalid : public ConversionPattern { 317 TestChangeProducerTypeF32ToInvalid(MLIRContext *ctx) 318 : ConversionPattern("test.type_producer", 10, ctx) {} 319 LogicalResult 320 matchAndRewrite(Operation *op, ArrayRef<Value> operands, 321 ConversionPatternRewriter &rewriter) const final { 322 // Always convert to B16, even though it is not a legal type. This tests 323 // that values are unmapped correctly. 324 rewriter.replaceOpWithNewOp<TestTypeProducerOp>(op, rewriter.getBF16Type()); 325 return success(); 326 } 327 }; 328 struct TestUpdateConsumerType : public ConversionPattern { 329 TestUpdateConsumerType(MLIRContext *ctx) 330 : ConversionPattern("test.type_consumer", 1, ctx) {} 331 LogicalResult 332 matchAndRewrite(Operation *op, ArrayRef<Value> operands, 333 ConversionPatternRewriter &rewriter) const final { 334 // Verify that the incoming operand has been successfully remapped to F64. 335 if (!operands[0].getType().isF64()) 336 return failure(); 337 rewriter.replaceOpWithNewOp<TestTypeConsumerOp>(op, operands[0]); 338 return success(); 339 } 340 }; 341 342 //===----------------------------------------------------------------------===// 343 // Non-Root Replacement Rewrite Testing 344 /// This pattern generates an invalid operation, but replaces it before the 345 /// pattern is finished. This checks that we don't need to legalize the 346 /// temporary op. 347 struct TestNonRootReplacement : public RewritePattern { 348 TestNonRootReplacement(MLIRContext *ctx) 349 : RewritePattern("test.replace_non_root", 1, ctx) {} 350 351 LogicalResult matchAndRewrite(Operation *op, 352 PatternRewriter &rewriter) const final { 353 auto resultType = *op->result_type_begin(); 354 auto illegalOp = rewriter.create<ILLegalOpF>(op->getLoc(), resultType); 355 auto legalOp = rewriter.create<LegalOpB>(op->getLoc(), resultType); 356 357 rewriter.replaceOp(illegalOp, {legalOp}); 358 rewriter.replaceOp(op, {illegalOp}); 359 return success(); 360 } 361 }; 362 } // namespace 363 364 namespace { 365 struct TestTypeConverter : public TypeConverter { 366 using TypeConverter::TypeConverter; 367 TestTypeConverter() { addConversion(convertType); } 368 369 static LogicalResult convertType(Type t, SmallVectorImpl<Type> &results) { 370 // Drop I16 types. 371 if (t.isSignlessInteger(16)) 372 return success(); 373 374 // Convert I64 to F64. 375 if (t.isSignlessInteger(64)) { 376 results.push_back(FloatType::getF64(t.getContext())); 377 return success(); 378 } 379 380 // Split F32 into F16,F16. 381 if (t.isF32()) { 382 results.assign(2, FloatType::getF16(t.getContext())); 383 return success(); 384 } 385 386 // Otherwise, convert the type directly. 387 results.push_back(t); 388 return success(); 389 } 390 391 /// Override the hook to materialize a conversion. This is necessary because 392 /// we generate 1->N type mappings. 393 Operation *materializeConversion(PatternRewriter &rewriter, Type resultType, 394 ArrayRef<Value> inputs, 395 Location loc) override { 396 return rewriter.create<TestCastOp>(loc, resultType, inputs); 397 } 398 }; 399 400 struct TestLegalizePatternDriver 401 : public ModulePass<TestLegalizePatternDriver> { 402 /// The mode of conversion to use with the driver. 403 enum class ConversionMode { Analysis, Full, Partial }; 404 405 TestLegalizePatternDriver(ConversionMode mode) : mode(mode) {} 406 407 void runOnModule() override { 408 TestTypeConverter converter; 409 mlir::OwningRewritePatternList patterns; 410 populateWithGenerated(&getContext(), &patterns); 411 patterns.insert< 412 TestRegionRewriteBlockMovement, TestRegionRewriteUndo, TestCreateBlock, 413 TestCreateIllegalBlock, TestPassthroughInvalidOp, TestSplitReturnType, 414 TestChangeProducerTypeI32ToF32, TestChangeProducerTypeF32ToF64, 415 TestChangeProducerTypeF32ToInvalid, TestUpdateConsumerType, 416 TestNonRootReplacement>(&getContext()); 417 patterns.insert<TestDropOpSignatureConversion>(&getContext(), converter); 418 mlir::populateFuncOpTypeConversionPattern(patterns, &getContext(), 419 converter); 420 mlir::populateCallOpTypeConversionPattern(patterns, &getContext(), 421 converter); 422 423 // Define the conversion target used for the test. 424 ConversionTarget target(getContext()); 425 target.addLegalOp<ModuleOp, ModuleTerminatorOp>(); 426 target.addLegalOp<LegalOpA, LegalOpB, TestCastOp, TestValidOp, 427 TerminatorOp>(); 428 target 429 .addIllegalOp<ILLegalOpF, TestRegionBuilderOp, TestOpWithRegionFold>(); 430 target.addDynamicallyLegalOp<TestReturnOp>([](TestReturnOp op) { 431 // Don't allow F32 operands. 432 return llvm::none_of(op.getOperandTypes(), 433 [](Type type) { return type.isF32(); }); 434 }); 435 target.addDynamicallyLegalOp<FuncOp>( 436 [&](FuncOp op) { return converter.isSignatureLegal(op.getType()); }); 437 438 // Expect the type_producer/type_consumer operations to only operate on f64. 439 target.addDynamicallyLegalOp<TestTypeProducerOp>( 440 [](TestTypeProducerOp op) { return op.getType().isF64(); }); 441 target.addDynamicallyLegalOp<TestTypeConsumerOp>([](TestTypeConsumerOp op) { 442 return op.getOperand().getType().isF64(); 443 }); 444 445 // Check support for marking certain operations as recursively legal. 446 target.markOpRecursivelyLegal<FuncOp, ModuleOp>([](Operation *op) { 447 return static_cast<bool>( 448 op->getAttrOfType<UnitAttr>("test.recursively_legal")); 449 }); 450 451 // Handle a partial conversion. 452 if (mode == ConversionMode::Partial) { 453 (void)applyPartialConversion(getModule(), target, patterns, &converter); 454 return; 455 } 456 457 // Handle a full conversion. 458 if (mode == ConversionMode::Full) { 459 // Check support for marking unknown operations as dynamically legal. 460 target.markUnknownOpDynamicallyLegal([](Operation *op) { 461 return (bool)op->getAttrOfType<UnitAttr>("test.dynamically_legal"); 462 }); 463 464 (void)applyFullConversion(getModule(), target, patterns, &converter); 465 return; 466 } 467 468 // Otherwise, handle an analysis conversion. 469 assert(mode == ConversionMode::Analysis); 470 471 // Analyze the convertible operations. 472 DenseSet<Operation *> legalizedOps; 473 if (failed(applyAnalysisConversion(getModule(), target, patterns, 474 legalizedOps, &converter))) 475 return signalPassFailure(); 476 477 // Emit remarks for each legalizable operation. 478 for (auto *op : legalizedOps) 479 op->emitRemark() << "op '" << op->getName() << "' is legalizable"; 480 } 481 482 /// The mode of conversion to use. 483 ConversionMode mode; 484 }; 485 } // end anonymous namespace 486 487 static llvm::cl::opt<TestLegalizePatternDriver::ConversionMode> 488 legalizerConversionMode( 489 "test-legalize-mode", 490 llvm::cl::desc("The legalization mode to use with the test driver"), 491 llvm::cl::init(TestLegalizePatternDriver::ConversionMode::Partial), 492 llvm::cl::values( 493 clEnumValN(TestLegalizePatternDriver::ConversionMode::Analysis, 494 "analysis", "Perform an analysis conversion"), 495 clEnumValN(TestLegalizePatternDriver::ConversionMode::Full, "full", 496 "Perform a full conversion"), 497 clEnumValN(TestLegalizePatternDriver::ConversionMode::Partial, 498 "partial", "Perform a partial conversion"))); 499 500 //===----------------------------------------------------------------------===// 501 // ConversionPatternRewriter::getRemappedValue testing. This method is used 502 // to get the remapped value of a original value that was replaced using 503 // ConversionPatternRewriter. 504 namespace { 505 /// Converter that replaces a one-result one-operand OneVResOneVOperandOp1 with 506 /// a one-operand two-result OneVResOneVOperandOp1 by replicating its original 507 /// operand twice. 508 /// 509 /// Example: 510 /// %1 = test.one_variadic_out_one_variadic_in1"(%0) 511 /// is replaced with: 512 /// %1 = test.one_variadic_out_one_variadic_in1"(%0, %0) 513 struct OneVResOneVOperandOp1Converter 514 : public OpConversionPattern<OneVResOneVOperandOp1> { 515 using OpConversionPattern<OneVResOneVOperandOp1>::OpConversionPattern; 516 517 LogicalResult 518 matchAndRewrite(OneVResOneVOperandOp1 op, ArrayRef<Value> operands, 519 ConversionPatternRewriter &rewriter) const override { 520 auto origOps = op.getOperands(); 521 assert(std::distance(origOps.begin(), origOps.end()) == 1 && 522 "One operand expected"); 523 Value origOp = *origOps.begin(); 524 SmallVector<Value, 2> remappedOperands; 525 // Replicate the remapped original operand twice. Note that we don't used 526 // the remapped 'operand' since the goal is testing 'getRemappedValue'. 527 remappedOperands.push_back(rewriter.getRemappedValue(origOp)); 528 remappedOperands.push_back(rewriter.getRemappedValue(origOp)); 529 530 rewriter.replaceOpWithNewOp<OneVResOneVOperandOp1>(op, op.getResultTypes(), 531 remappedOperands); 532 return success(); 533 } 534 }; 535 536 struct TestRemappedValue : public mlir::FunctionPass<TestRemappedValue> { 537 void runOnFunction() override { 538 mlir::OwningRewritePatternList patterns; 539 patterns.insert<OneVResOneVOperandOp1Converter>(&getContext()); 540 541 mlir::ConversionTarget target(getContext()); 542 target.addLegalOp<ModuleOp, ModuleTerminatorOp, FuncOp, TestReturnOp>(); 543 // We make OneVResOneVOperandOp1 legal only when it has more that one 544 // operand. This will trigger the conversion that will replace one-operand 545 // OneVResOneVOperandOp1 with two-operand OneVResOneVOperandOp1. 546 target.addDynamicallyLegalOp<OneVResOneVOperandOp1>( 547 [](Operation *op) -> bool { 548 return std::distance(op->operand_begin(), op->operand_end()) > 1; 549 }); 550 551 if (failed(mlir::applyFullConversion(getFunction(), target, patterns))) { 552 signalPassFailure(); 553 } 554 } 555 }; 556 } // end anonymous namespace 557 558 namespace mlir { 559 void registerPatternsTestPass() { 560 mlir::PassRegistration<TestReturnTypeDriver>("test-return-type", 561 "Run return type functions"); 562 563 mlir::PassRegistration<TestPatternDriver>("test-patterns", 564 "Run test dialect patterns"); 565 566 mlir::PassRegistration<TestLegalizePatternDriver>( 567 "test-legalize-patterns", "Run test dialect legalization patterns", [] { 568 return std::make_unique<TestLegalizePatternDriver>( 569 legalizerConversionMode); 570 }); 571 572 PassRegistration<TestRemappedValue>( 573 "test-remapped-value", 574 "Test public remapped value mechanism in ConversionPatternRewriter"); 575 } 576 } // namespace mlir 577