1# PDLL - PDL Language 2 3This document details the PDL Language (PDLL), a custom frontend language for 4writing pattern rewrites targeting MLIR. 5 6Note: This document assumes a familiarity with MLIR concepts; more specifically 7the concepts detailed within the 8[MLIR Pattern Rewriting](https://mlir.llvm.org/docs/PatternRewriter/) and 9[Operation Definition Specification (ODS)](https://mlir.llvm.org/docs/OpDefinitions/) 10documentation. 11 12[TOC] 13 14## Introduction 15 16Pattern matching is an extremely important component within MLIR, as it 17encompasses many different facets of the compiler. From canonicalization, to 18optimization, to conversion; every MLIR based compiler will heavily rely on the 19pattern matching infrastructure in some capacity. 20 21The PDL Language (PDLL) provides a declarative pattern language designed from 22the ground up for representing MLIR pattern rewrites. PDLL is designed to 23natively support writing matchers on all of MLIRs constructs via an intuitive 24interface that may be used for both ahead-of-time (AOT) and just-in-time (JIT) 25pattern compilation. 26 27## Rationale 28 29This section provides details on various design decisions, their rationale, and 30alternatives considered when designing PDLL. Given the nature of software 31development, this section may include references to areas of the MLIR compiler 32that no longer exist. 33 34### Why build a new language instead of improving TableGen DRR? 35 36Note: This section assumes familiarity with 37[TDRR](https://mlir.llvm.org/docs/DeclarativeRewrites/), please refer the 38relevant documentation before continuing. 39 40Tablegen DRR (TDRR), i.e. 41[Table-driven Declarative Rewrite Rules](https://mlir.llvm.org/docs/DeclarativeRewrites/), 42is a declarative DSL for defining MLIR pattern rewrites within the 43[TableGen](https://llvm.org/docs/TableGen/index.html) language. This 44infrastructure is currently the main way in which patterns may be defined 45declaratively within MLIR. TDRR utilizes TableGen's `dag` support to enable 46defining MLIR patterns that fit nicely within a DAG structure; in a similar way 47in which tablegen has been used to defined patterns for LLVM's backend 48infrastructure (SelectionDAG/Global Isel/etc.). Unfortunately however, the 49TableGen language is not as amenable to the structure of MLIR patterns as it has 50been for LLVM. 51 52The issues with TDRR largely stem from the use of TableGen as the host language 53for the DSL. These issues have risen from a mismatch in the structure of 54TableGen compared to the structure of MLIR, and from TableGen having different 55motivational goals than MLIR. A majority (or all depending on how stubborn you 56are) of the issues that we've come across with TDRR have been addressable in 57some form; the sticking point here is that the solutions to these problems have 58often been more "creative" than we'd like. This is a problem, and why we decided 59not to invest a larger effort into improving TDRR; users generally don't want 60"creative" APIs, they want something that is intuitive to read/write. 61 62To highlight some of these issues, below we will take a tour through some of the 63problems that have arisen, and how we "fixed" them. 64 65#### Multi-result operations 66 67MLIR natively supports a variable number of operation results. For the DAG based 68structure of TDRR, any form of multiple results (operations in this instance) 69creates a problem. This is because the DAG wants a single root node, and does 70not have nice facilities for indexing or naming the multiple results. Let's take 71a look at a quick example to see how this manifests: 72 73```tablegen 74// Suppose we have a three result operation, defined as seen below. 75def ThreeResultOp : Op<"three_result_op"> { 76 let arguments = (ins ...); 77 78 let results = (outs 79 AnyTensor:$output1, 80 AnyTensor:$output2, 81 AnyTensor:$output3 82 ); 83} 84 85// To bind the results of `ThreeResultOp` in a TDRR pattern, we bind all results 86// to a single name and use a special naming convention: `__N`, where `N` is the 87// N-th result. 88def : Pattern<(ThreeResultOp:$results ...), 89 [(... $results__0), ..., (... $results__2), ...]>; 90``` 91 92In TDRR, we "solved" the problem of accessing multiple results, but this isn't a 93very intuitive interface for users. Magical naming conventions obfuscate the 94code and can easily introduce bugs and other errors. There are various things 95that we could try to improve this situation, but there is a fundamental limit to 96what we can do given the limits of the TableGen dag structure. In PDLL, however, 97we have the freedom and flexibility to provide a proper interface into 98operations, regardless of their structure: 99 100```pdll 101// Import our definition of `ThreeResultOp`. 102#include "ops.td" 103 104Pattern { 105 ... 106 107 // In PDLL, we can directly reference the results of an operation variable. 108 // This provides a closer mental model to what the user expects. 109 let threeResultOp = op<my_dialect.three_result_op>; 110 let userOp = op<my_dialect.user_op>(threeResultOp.output1, ..., threeResultOp.output3); 111 112 ... 113} 114``` 115 116#### Constraints 117 118In TDRR, the match dag defines the general structure of the input IR to match. 119Any non-structural/non-type constraints on the input are generally relegated to 120a list of constraints specified after the rewrite dag. For very simple patterns 121this may suffice, but with larger patterns it becomes quite problematic as it 122separates the constraint from the entity it constrains and negatively impacts 123the readability of the pattern. As an example, let's look at a simple pattern 124that adds additional constraints to its inputs: 125 126```tablegen 127// Suppose we have a two result operation, defined as seen below. 128def TwoResultOp : Op<"two_result_op"> { 129 let arguments = (ins ...); 130 131 let results = (outs 132 AnyTensor:$output1, 133 AnyTensor:$output2 134 ); 135} 136 137// A simple constraint to check if a value is use_empty. 138def HasNoUseOf: Constraint<CPred<"$_self.use_empty()">, "has no use">; 139 140// Check if two values have a ShapedType with the same element type. 141def HasSameElementType : Constraint< 142 CPred<"$0.getType().cast<ShapedType>().getElementType() == " 143 "$1.getType().cast<ShapedType>().getElementType()">, 144 "values have same element type">; 145 146def : Pattern<(TwoResultOp:$results $input), 147 [(...), (...)], 148 [(HasNoUseOf:$results__1), 149 (HasSameElementType $results__0, $input)]>; 150``` 151 152Above, when observing the constraints we need to search through the input dag 153for the inputs (also keeping in mind the magic naming convention for multiple 154results). For this simple pattern it may be just a few lines above, but complex 155patterns often grow to 10s of lines long. In PDLL, these constraints can be 156applied directly on or next to the entities they apply to: 157 158```pdll 159// The same constraints that we defined above: 160Constraint HasNoUseOf(value: Value) [{ 161 return success(value.use_empty()); 162}]; 163Constraint HasSameElementType(value1: Value, value2: Value) [{ 164 return success(value1.getType().cast<ShapedType>().getElementType() == 165 value2.getType().cast<ShapedType>().getElementType()); 166}]; 167 168Pattern { 169 // In PDLL, we can apply the constraint as early (or as late) as we want. This 170 // enables better structuring of the matcher code, and improves the 171 // readability/maintainability of the pattern. 172 let op = op<my_dialect.two_result_op>(input: Value); 173 HasNoUseOf(op.output2); 174 HasSameElementType(input, op.output2); 175 176 // ... 177} 178``` 179 180#### Replacing Multiple Operations 181 182Often times a pattern will transform N number of input operations into N number 183of result operations. In PDLL, replacing multiple operations is as simple as 184adding two [`replace` statements](#replace-statement). In TDRR, the situation is 185a bit more nuanced. Given the single root structure of the TableGen dag, 186replacing a non-root operation is not nicely supported. It currently isn't 187natively possible, and instead requires using multiple patterns. We could 188potentially add another special rewrite directive, or extend `replaceWithValue`, 189but this simply highlights how even a basic IR transformation is muddled by the 190complexity of the host language. 191 192### Why not build a DSL in "X"? 193 194Yes! Well yes and no. To understand why, we have to consider what types of users 195we are trying to serve and what constraints we enforce upon them. The goal of 196PDLL is to provide a default and effective pattern language for MLIR that all 197users of MLIR can interact with immediately, regardless of their host 198environment. This language is available with no extra dependencies and comes 199"free" along with MLIR. If we were to use an existing host language to build our 200new DSL, we would need to make compromises along with it depending on the 201language. For some, there are questions of how to enforce matching environments 202(python2 or python3?, which version?), performance considerations, integration, 203etc. As an LLVM project, this could also mean enforcing a new language 204dependency on the users of MLIR (many of which may not want/need such a 205dependency otherwise). Another issue that comes along with any DSL that is 206embeded in another language: mitigating the user impedance mismatch between what 207the user expects from the host language and what our "backend" supports. For 208example, the PDL IR abstraction only contains limited support for control flow. 209If we were to build a DSL in python, we would need to ensure that complex 210control flow is either handled completely or effectively errors out. Even with 211ideal error handling, not having the expected features available creates user 212frustration. In addition to the environment constraints, there is also the issue 213of language tooling. With PDLL we intend to build a very robust and modern 214toolset that is designed to cater the needs of pattern developers, including 215code completion, signature help, and many more features that are specific to the 216problem we are solving. Integrating custom language tooling into existing 217languages can be difficult, and in some cases impossible (as our DSL would 218merely be a small subset of the existing language). 219 220These various points have led us to the initial conclusion that the most 221effective tool we can provide for our users is a custom tool designed for the 222problem at hand. With all of that being said, we understand that not all users 223have the same constraints that we have placed upon ourselves. We absolutely 224encourage and support the existence of various PDL frontends defined in 225different languages. This is one of the original motivating factors around 226building the PDL IR abstraction in the first place; to enable innovation and 227flexibility for our users (and in turn their users). For some, such as those in 228research and the Machine Learning space, they may already have a certain 229language (such as Python) heavily integrated into their workflow. For these 230users, a PDL DSL in their language may be ideal and we will remain committed to 231supporting and endorsing that from an infrastructure point-of-view. 232 233## Language Specification 234 235Note: PDLL is still under active development, and the designs discussed below 236are not necessarily final and may be subject to change. 237 238The design of PDLL is heavily influenced and centered around the 239[PDL IR abstraction](https://mlir.llvm.org/docs/Dialects/PDLOps/), which in turn 240is designed as an abstract model of the core MLIR structures. This leads to a 241design and structure that feels very similar to if you were directly writing the 242IR you want to match. 243 244### Includes 245 246PDLL supports an `include` directive to import content defined within other 247source files. There are two types of files that may be included: `.pdll` and 248`.td` files. 249 250#### `.pdll` includes 251 252When including a `.pdll` file, the contents of that file are copied directly into 253the current file being processed. This means that any patterns, constraints, 254rewrites, etc., defined within that file are processed along with those within 255the current file. 256 257#### `.td` includes 258 259When including a `.td` file, PDLL will automatically import any pertinent 260[ODS](https://mlir.llvm.org/docs/OpDefinitions/) information within that file. 261This includes any defined operations, constraints, interfaces, and more, making 262them implicitly accessible within PDLL. This is important, as ODS information 263allows for certain PDLL constructs, such as the 264[`operation` expression](#operation), to become much more powerful. 265 266### Patterns 267 268In any pattern descriptor language, pattern definition is at the core. In PDLL, 269patterns start with `Pattern` optionally followed by a name and a set of pattern 270metadata, and finally terminated by a pattern body. A few simple examples are 271shown below: 272 273```pdll 274// Here we have defined an anonymous pattern: 275Pattern { 276 // Pattern bodies are separated into two components: 277 // * Match Section 278 // - Describes the input IR. 279 let root = op<toy.reshape>(op<toy.reshape>(arg: Value)); 280 281 // * Rewrite Section 282 // - Describes how to transform the IR. 283 // - Last statement starts the rewrite. 284 replace root with op<toy.reshape>(arg); 285} 286 287// Here we have defined a pattern named `ReshapeReshapeOptPattern` with a 288// benefit of 10: 289Pattern ReshapeReshapeOptPattern with benefit(10) { 290 replace op<toy.reshape>(op<toy.reshape>(arg: Value)) 291 with op<toy.reshape>(arg); 292} 293``` 294 295After the definition of the pattern metadata, we specify the pattern body. The 296structure of a pattern body is comprised of two main sections, the `match` 297section and the `rewrite` section. The `match` section of a pattern describes 298the expected input IR, whereas the `rewrite` section describes how to transform 299that IR. This distinction is an important one to make, as PDLL handles certain 300variables and expressions differently within the different sections. When 301relevant in each of the sections below, we shall explicitly call out any 302behavioral differences. 303 304The general layout of the `match` and `rewrite` section is as follows: the 305*last* statement of the pattern body is required to be a 306[`operation rewrite statement`](#operation-rewrite-statements), and denotes the 307`rewrite` section; every statement before denotes the `match` section. 308 309#### Pattern metadata 310 311Rewrite patterns in MLIR have a set of metadata that allow for controlling 312certain behaviors, and providing information to the rewrite driver applying the 313pattern. In PDLL, a pattern can provide a non-default value for this metadata 314after the pattern name. Below, examples are shown for the different types of 315metadata supported: 316 317##### Benefit 318 319The benefit of a Pattern is an integer value that represents the "benefit" of 320matching that pattern. It is used by pattern drivers to determine the relative 321priorities of patterns during application; a pattern with a higher benefit is 322generally applied before one with a lower benefit. 323 324In PDLL, a pattern has a default benefit set to the number of input operations, 325i.e. the number of distinct `Op` expressions/variables, in the match section. This 326rule is driven by an observation that larger matches are more beneficial than smaller 327ones, and if a smaller one is applied first the larger one may not apply anymore. 328Patterns can override this behavior by specifying the benefit in the metadata section 329of the pattern: 330 331```pdll 332// Here we specify that this pattern has a benefit of `10`, overriding the 333// default behavior. 334Pattern with benefit(10) { 335 ... 336} 337``` 338 339##### Bounded Rewrite Recursion 340 341During pattern application, there are situations in which a pattern may be 342applicable to the result of a previous application of that same pattern. If the 343pattern does not properly handle this recusive application, the pattern driver 344could become stuck in an infinite loop of application. To prevent this, patterns 345by-default are assumed to not have proper recursive bounding and will not be 346recursively applied. A pattern can signal that it does have proper handling for 347recursion by specifying the `recusion` flag in the pattern metadata section: 348 349```pdll 350// Here we signal that this pattern properly bounds recursive application. 351Pattern with recusion { 352 ... 353} 354``` 355 356#### Single Line "Lambda" Body 357 358Patterns generally define their body using a compound block of statements, as 359shown below: 360 361```pdll 362Pattern { 363 replace op<my_dialect.foo>(operands: ValueRange) with operands; 364} 365``` 366 367Patterns also support a lambda-like syntax for specifying simple single line 368bodies. The lambda body of a Pattern expects a single 369[operation rewrite statement](#operation-rewrite-statements): 370 371```pdll 372Pattern => replace op<my_dialect.foo>(operands: ValueRange) with operands; 373``` 374 375### Variables 376 377Variables in PDLL represent specific instances of IR entities, such as `Value`s, 378`Operation`s, `Type`s, etc. Consider the simple pattern below: 379 380```pdll 381Pattern { 382 let value: Value; 383 let root = op<mydialect.foo>(value); 384 385 replace root with value; 386} 387``` 388 389In this pattern we define two variables, `value` and `root`, using the `let` 390statement. The `let` statement allows for defining variables and constraining 391them. Every variable in PDLL is of a certain type, which defines the type of IR 392entity the variable represents. The type of a variable may be determined via 393either a constraint, or an initializer expression. 394 395#### Variable "Binding" 396 397In addition to having a type, variables must also be "bound", either via an initializer 398expression or to a non-native constraint or rewrite use within the `match` section of the 399pattern. "Binding" a variable contextually identifies that variable within either the 400input (i.e. `match` section) or output (i.e. `rewrite` section) IR. In the `match` section, 401this allows for building the match tree from the pattern's root operation, which must be 402"bound" to the [operation rewrite statement](#operation-rewrite-statements) that denotes the 403`rewrite` section of the pattern. All non-root variables within the `match` 404section must be bound in some way to the "root" operation. To help illustrate 405the concept, let's take a look at a quick example. Consider the `.mlir` snippet 406below: 407 408```mlir 409func.func @baz(%arg: i32) { 410 %result = my_dialect.foo %arg, %arg -> i32 411} 412``` 413 414Say that we want to write a pattern that matches `my_dialect.foo` and replaces 415it with its unique input argument. A naive way to write this pattern in PDLL is 416shown below: 417 418```pdll 419Pattern { 420 // ** match section ** // 421 let arg: Value; 422 let root = op<my_dialect.foo>(arg, arg); 423 424 // ** rewrite section ** // 425 replace root with arg; 426} 427``` 428 429In the above pattern, the `arg` variable is "bound" to the first and second operands 430of the `root` operation. Every use of `arg` is constrained to be the same `Value`, i.e. 431the first and second operands of `root` will be constrained to refer to the same input 432Value. The same is true for the `root` operation, it is bound to the "root" operation of the 433pattern as it is used in input of the top-level [`replace` statement](#replace-statement) 434of the `rewrite` section of the pattern. Writing this pattern using the C++ API, the concept 435of "binding" becomes more clear: 436 437```c++ 438struct Pattern : public OpRewritePattern<my_dialect::FooOp> { 439 LogicalResult matchAndRewrite(my_dialect::FooOp root, PatternRewriter &rewriter) { 440 Value arg = root->getOperand(0); 441 if (arg != root->getOperand(1)) 442 return failure(); 443 444 rewriter.replaceOp(root, arg); 445 return success(); 446 } 447}; 448``` 449 450If a variable is not "bound" properly, PDLL won't be able to identify what value 451it would correspond to in the IR. As a final example, let's consider a variable 452that hasn't been bound: 453 454```pdll 455Pattern { 456 // ** match section ** // 457 let arg: Value; 458 let root = op<my_dialect.foo> 459 460 // ** rewrite section ** // 461 replace root with arg; 462} 463``` 464 465If we were to write this exact pattern in C++, we would end up with: 466 467```c++ 468struct Pattern : public OpRewritePattern<my_dialect::FooOp> { 469 LogicalResult matchAndRewrite(my_dialect::FooOp root, PatternRewriter &rewriter) { 470 // `arg` was never bound, so we don't know what input Value it was meant to 471 // correspond to. 472 Value arg; 473 474 rewriter.replaceOp(root, arg); 475 return success(); 476 } 477}; 478``` 479 480#### Variable Constraints 481 482```pdll 483// This statement defines a variable `value` that is constrained to be a `Value`. 484let value: Value; 485 486// This statement defines a variable `value` that is constrained to be a `Value` 487// *and* constrained to have a single use. 488let value: [Value, HasOneUse]; 489``` 490 491Any number of single entity constraints may be attached directly to a variable 492upon declaration. Within the `matcher` section, these constraints may add 493additional checks on the input IR. Within the `rewriter` section, constraints 494are *only* used to define the type of the variable. There are a number of 495builtin constraints that correlate to the core MLIR constructs: `Attr`, `Op`, 496`Type`, `TypeRange`, `Value`, `ValueRange`. Along with these, users may define 497custom constraints that are implemented within PDLL, or natively (i.e. outside 498of PDLL). See the general [Constraints](#constraints) section for more detailed 499information. 500 501#### Inline Variable Definition 502 503Along with the `let` statement, variables may also be defined inline by 504specifying the constraint list along with the desired variable name in the first 505place that the variable would be used. After definition, the variable is visible 506from all points forward. See below for an example: 507 508```pdll 509// `value` is used as an operand to the operation `root`: 510let value: Value; 511let root = op<my_dialect.foo>(value); 512replace root with value; 513 514// `value` could also be defined "inline": 515let root = op<my_dialect.foo>(value: Value); 516replace root with value; 517``` 518 519Note that the point of definition of an inline variable is the point of reference, 520meaning that an inline variable can be used immediately in the same parent 521expression within which it was defined: 522 523```pdll 524let root = op<my_dialect.foo>(value: Value, _: Value, value); 525replace root with value; 526``` 527 528##### Wildcard Variable Definition 529 530Often times when defining a variable inline, the variable isn't intended to be 531used anywhere else in the pattern. For example, this may happen if you want to 532attach constraints to a variable but have no other use for it. In these 533situations, the "wildcard" variable can be used to remove the need to provide a 534name, as "wildcard" variables are not visible outside of the point of 535definition. An example is shown below: 536 537```pdll 538Pattern { 539 let root = op<my_dialect.foo>(arg: Value, _: Value, _: [Value, I64Value], arg); 540 replace root with arg; 541} 542``` 543 544In the above example, the second operand isn't needed for the pattern but we 545need to provide it to signal that a second operand does exist (we just don't 546care what it is in this pattern). 547 548### Operation Expression 549 550An operation expression in PDLL represents an MLIR operation. In the `match` 551section of the pattern, this expression models one of the input operations to 552the pattern. In the `rewrite` section of the pattern, this expression models one 553of the operations to create. The general structure of the operation expression 554is very similar to that of the "generic form" of textual MLIR assembly: 555 556```pdll 557let root = op<my_dialect.foo>(operands: ValueRange) {attr = attr: Attr} -> (resultTypes: TypeRange); 558``` 559 560Let's walk through each of the different components of the expression: 561 562#### Operation name 563 564The operation name signifies which type of MLIR Op this operation corresponds 565to. In the `match` section of the pattern, the name may be elided. This would 566cause this pattern to match *any* operation type that satifies the rest of the 567constraints of the operation. In the `rewrite` section, the name is required. 568 569```pdll 570// `root` corresponds to an instance of a `my_dialect.foo` operation. 571let root = op<my_dialect.foo>; 572 573// `root` could be an instance of any operation type. 574let root = op<>; 575``` 576 577#### Operands 578 579The operands section corresponds to the operands of the operation. This section 580of an operation expression may be elided, which within a `match` section means 581that the operands are not constrained in any way. If elided within a `rewrite` 582section, the operation is treated as having no operands. When present, the 583operands of an operation expression are interpreted in the following ways: 584 5851) A single instance of type `ValueRange`: 586 587In this case, the single range is treated as all of the operands of the 588operation: 589 590```pdll 591// Define an instance with single range of operands. 592let root = op<my_dialect.foo>(allOperands: ValueRange); 593``` 594 5952) A variadic number of either `Value` or `ValueRange`: 596 597In this case, the inputs are expected to correspond with the operand groups as 598defined on the operation in ODS. 599 600Given the following operation definition in ODS: 601 602```tablegen 603def MyIndirectCallOp { 604 let arguments = (ins FunctionType:$call, Variadic<AnyType>:$args); 605} 606``` 607 608We can match the operands as so: 609 610```pdll 611let root = op<my_dialect.indirect_call>(call: Value, args: ValueRange); 612``` 613 614#### Results 615 616The results section corresponds to the result types of the operation. This section 617of an operation expression may be elided, which within a `match` section means 618that the result types are not constrained in any way. If elided within a `rewrite` 619section, the results of the operation are [inferred](#inferred-results). When present, 620the result types of an operation expression are interpreted in the following ways: 621 6221) A single instance of type `TypeRange`: 623 624In this case, the single range is treated as all of the result types of the 625operation: 626 627```pdll 628// Define an instance with single range of types. 629let root = op<my_dialect.foo> -> (allResultTypes: TypeRange); 630``` 631 6322) A variadic number of either `Type` or `TypeRange`: 633 634In this case, the inputs are expected to correspond with the result groups as 635defined on the operation in ODS. 636 637Given the following operation definition in ODS: 638 639```tablegen 640def MyOp { 641 let results = (outs SomeType:$result, Variadic<SomeType>:$otherResults); 642} 643``` 644 645We can match the result types as so: 646 647```pdll 648let root = op<my_dialect.op> -> (result: Type, otherResults: TypeRange); 649``` 650 651#### Inferred Results 652 653Within the `rewrite` section of a pattern, the result types of an 654operation are inferred if they are elided or otherwise not 655previously bound. The ["variable binding"](#variable-binding) section above 656discusses the concept of "binding" in more detail. Below are various examples 657that build upon this to help showcase how a result type may be "bound": 658 659* Binding to a [constant](#type-expression): 660 661```pdll 662op<my_dialect.op> -> (type<"i32">); 663``` 664 665* Binding to types within the `match` section: 666 667```pdll 668Pattern { 669 replace op<dialect.inputOp> -> (resultTypes: TypeRange) 670 with op<dialect.outputOp> -> (resultTypes); 671} 672``` 673 674* Binding to previously inferred types: 675 676```pdll 677Pattern { 678 rewrite root: Op with { 679 // `resultTypes` here is *not* yet bound, and will be inferred when 680 // creating `dialect.op`. Any uses of `resultTypes` after this expression, 681 // will use the types inferred when creating this operation. 682 op<dialect.op> -> (resultTypes: TypeRange); 683 684 // `resultTypes` here is bound to the types inferred when creating `dialect.op`. 685 op<dialect.bar> -> (resultTypes); 686 }; 687} 688``` 689 690* Binding to a [`Native Rewrite`](#native-rewriters) method result: 691 692```pdll 693Rewrite BuildTypes() -> TypeRange; 694 695Pattern { 696 rewrite root: Op with { 697 op<dialect.op> -> (BuildTypes()); 698 }; 699} 700``` 701 702Below are the set of contexts in which result type inferrence is supported: 703 704##### Inferred Results of Replacement Operation 705 706Replacements have the invariant that the types of the replacement values must 707match the result types of the input operation. This means that when replacing 708one operation with another, the result types of the replacement operation may 709be inferred from the result types of the operation being replaced. For example, 710consider the following pattern: 711 712```pdll 713Pattern => replace op<dialect.inputOp> with op<dialect.outputOp>; 714``` 715 716This pattern could be written in a more explicit way as: 717 718```pdll 719Pattern { 720 replace op<dialect.inputOp> -> (resultTypes: TypeRange) 721 with op<dialect.outputOp> -> (resultTypes); 722} 723``` 724 725##### Inferred Results with InferTypeOpInterface 726 727`InferTypeOpInterface` is an interface that enables operations to infer its result 728types from its input attributes, operands, regions, etc. When the result types of 729an operation cannot be inferred from any other context, this interface is invoked 730to infer the result types of the operation. 731 732#### Attributes 733 734The attributes section of the operation expression corresponds to the attribute 735dictionary of the operation. This section of an operation expression may be 736elided, in which case the attributes are not constrained in any way. The 737composition of this component maps exactly to how attribute dictionaries are 738structured in the MLIR textual assembly format: 739 740```pdll 741let root = op<my_dialect.foo> {attr1 = attrValue: Attr, attr2 = attrValue2: Attr}; 742``` 743 744Within the `{}` attribute entries are specified by an identifier or string name, 745corresponding to the attribute name, followed by an assignment to the attribute 746value. If the attribute value is elided, the value of the attribute is 747implicitly defined as a 748[`UnitAttr`](https://mlir.llvm.org/docs/Dialects/Builtin/#unitattr). 749 750```pdll 751let unitConstant = op<my_dialect.constant> {value}; 752``` 753 754##### Accessing Operation Results 755 756In multi-operation patterns, the result of one operation often feeds as an input 757into another. The result groups of an operation may be accessed by name or by 758index via the `.` operator: 759 760Note: Remember to import the definition of your operation via 761[include](#`.td`_includes) to ensure it is visible to PDLL. 762 763Given the following operation definition in ODS: 764 765```tablegen 766def MyResultOp { 767 let results = (outs SomeType:$result); 768} 769def MyInputOp { 770 let arguments = (ins SomeType:$input, SomeType:$input); 771} 772``` 773 774We can write a pattern where `MyResultOp` feeds into `MyInputOp` as so: 775 776```pdll 777// In this example, we use both `result`(the name) and `0`(the index) to refer to 778// the first result group of `resultOp`. 779// Note: If we elide the result types section within the match section, it means 780// they aren't constrained, not that the operation has no results. 781let resultOp = op<my_dialect.result_op>; 782let inputOp = op<my_dialect.input_op>(resultOp.result, resultOp.0); 783``` 784 785Along with result name access, variables of `Op` type may implicitly convert to 786`Value` or `ValueRange`. If these variables are registered (has ODS entry), they 787are converted to `Value` when they are known to only have one result, otherwise 788they will be converted to `ValueRange`: 789 790```pdll 791// `resultOp` may also convert implicitly to a Value for use in `inputOp`: 792let resultOp = op<my_dialect.result_op>; 793let inputOp = op<my_dialect.input_op>(resultOp); 794 795// We could also inline `resultOp` directly: 796let inputOp = op<my_dialect.input_op>(op<my_dialect.result_op>); 797``` 798 799#### Unregistered Operations 800 801A variable of unregistered op is still available for numeric result indexing. 802Given that we don't have knowledge of its result groups, numeric indexing 803returns a Value corresponding to the individual result at the given index. 804 805```pdll 806// Use the index `0` to refer to the first result value of the unregistered op. 807let inputOp = op<my_dialect.input_op>(op<my_dialect.unregistered_op>.0); 808``` 809 810### Attribute Expression 811 812An attribute expression represents a literal MLIR attribute. It allows for 813statically specifying an MLIR attribute to use, by specifying the textual form 814of that attribute. 815 816```pdll 817let trueConstant = op<arith.constant> {value = attr<"true">}; 818 819let applyResult = op<affine.apply>(args: ValueRange) {map = attr<"affine_map<(d0, d1) -> (d1 - 3)>">} 820``` 821 822### Type Expression 823 824A type expression represents a literal MLIR type. It allows for statically 825specifying an MLIR type to use, by specifying the textual form of that type. 826 827```pdll 828let i32Constant = op<arith.constant> -> (type<"i32">); 829``` 830 831### Tuples 832 833PDLL provides native support for tuples, which are used to group multiple 834elements into a single compound value. The values in a tuple can be of any type, 835and do not need to be of the same type. There is also no limit to the number of 836elements held by a tuple. The elements of a tuple can be accessed by index: 837 838```pdll 839let tupleValue = (op<my_dialect.foo>, attr<"10 : i32">, type<"i32">); 840 841let opValue = tupleValue.0; 842let attrValue = tupleValue.1; 843let typeValue = tupleValue.2; 844``` 845 846You can also name the elements of a tuple and use those names to refer to the 847values of the individual elements. An element name consists of an identifier 848followed immediately by an equal (=). 849 850```pdll 851let tupleValue = ( 852 opValue = op<my_dialect.foo>, 853 attr<"10 : i32">, 854 typeValue = type<"i32"> 855); 856 857let opValue = tupleValue.opValue; 858let attrValue = tupleValue.1; 859let typeValue = tupleValue.typeValue; 860``` 861 862Tuples are used to represent multiple results from a 863[constraint](#constraints-with-multiple-results) or 864[rewrite](#rewrites-with-multiple-results). 865 866### Constraints 867 868Constraints provide the ability to inject additional checks on the input IR 869within the `match` section of a pattern. Constraints can be applied anywhere 870within the `match` section, and depending on the type can either be applied via 871the constraint list of a [variable](#variables) or via the call operator (e.g. 872`MyConstraint(...)`). There are three main categories of constraints: 873 874#### Core Constraints 875 876PDLL defines a number of core constraints that constrain the type of the IR 877entity. These constraints can only be applied via the 878[constraint list](#variable-constraints) of a variable. 879 880* `Attr` (`<` type `>`)? 881 882A single entity constraint that corresponds to an `mlir::Attribute`. This 883constraint optionally takes a type component that constrains the result type of 884the attribute. 885 886```pdll 887// Define a simple variable using the `Attr` constraint. 888let attr: Attr; 889let constant = op<arith.constant> {value = attr}; 890 891// Define a simple variable using the `Attr` constraint, that has its type 892// constrained as well. 893let attrType: Type; 894let attr: Attr<attrType>; 895let constant = op<arith.constant> {value = attr}; 896``` 897 898* `Op` (`<` op-name `>`)? 899 900A single entity constraint that corresponds to an `mlir::Operation *`. 901 902```pdll 903// Match only when the input is from another operation. 904let inputOp: Op; 905let root = op<my_dialect.foo>(inputOp); 906 907// Match only when the input is from another `my_dialect.foo` operation. 908let inputOp: Op<my_dialect.foo>; 909let root = op<my_dialect.foo>(inputOp); 910``` 911 912* `Type` 913 914A single entity constraint that corresponds to an `mlir::Type`. 915 916```pdll 917// Define a simple variable using the `Type` constraint. 918let resultType: Type; 919let root = op<my_dialect.foo> -> (resultType); 920``` 921 922* `TypeRange` 923 924A single entity constraint that corresponds to a `mlir::TypeRange`. 925 926```pdll 927// Define a simple variable using the `TypeRange` constraint. 928let resultTypes: TypeRange; 929let root = op<my_dialect.foo> -> (resultTypes); 930``` 931 932* `Value` (`<` type-expr `>`)? 933 934A single entity constraint that corresponds to an `mlir::Value`. This constraint 935optionally takes a type component that constrains the result type of the value. 936 937```pdll 938// Define a simple variable using the `Value` constraint. 939let value: Value; 940let root = op<my_dialect.foo>(value); 941 942// Define a variable using the `Value` constraint, that has its type constrained 943// to be same as the result type of the `root` op. 944let valueType: Type; 945let input: Value<valueType>; 946let root = op<my_dialect.foo>(input) -> (valueType); 947``` 948 949* `ValueRange` (`<` type-expr `>`)? 950 951A single entity constraint that corresponds to a `mlir::ValueRange`. This 952constraint optionally takes a type component that constrains the result types of 953the value range. 954 955```pdll 956// Define a simple variable using the `ValueRange` constraint. 957let inputs: ValueRange; 958let root = op<my_dialect.foo>(inputs); 959 960// Define a variable using the `ValueRange` constraint, that has its types 961// constrained to be same as the result types of the `root` op. 962let valueTypes: TypeRange; 963let inputs: ValueRange<valueTypes>; 964let root = op<my_dialect.foo>(inputs) -> (valueTypes); 965``` 966 967#### Defining Constraints in PDLL 968 969Aside from the core constraints, additional constraints can also be defined 970within PDLL. This allows for building matcher fragments that can be composed 971across many different patterns. A constraint in PDLL is defined similarly to a 972function in traditional programming languages; it contains a name, a set of 973input arguments, a set of result types, and a body. Results of a constraint are 974returned via a `return` statement. A few examples are shown below: 975 976```pdll 977/// A constraint that takes an input and constrains the use to an operation of 978/// a given type. 979Constraint UsedByFooOp(value: Value) { 980 op<my_dialect.foo>(value); 981} 982 983/// A constraint that returns a result of an existing operation. 984Constraint ExtractResult(op: Op<my_dialect.foo>) -> Value { 985 return op.result; 986} 987 988Pattern { 989 let value = ExtractResult(op<my_dialect.foo>); 990 UsedByFooOp(value); 991} 992``` 993 994##### Constraints with multiple results 995 996Constraints can return multiple results by returning a tuple of values. When 997returning multiple results, each result can also be assigned a name to use when 998indexing that tuple element. Tuple elements can be referenced by their index 999number, or by name if they were assigned one. 1000 1001```pdll 1002// A constraint that returns multiple results, with some of the results assigned 1003// a more readable name. 1004Constraint ExtractMultipleResults(op: Op<my_dialect.foo>) -> (Value, result1: Value) { 1005 return (op.result1, op.result2); 1006} 1007 1008Pattern { 1009 // Return a tuple of values. 1010 let result = ExtractMultipleResults(op: op<my_dialect.foo>); 1011 1012 // Index the tuple elements by index, or by name. 1013 replace op<my_dialect.foo> with (result.0, result.1, result.result1); 1014} 1015``` 1016 1017##### Constraint result type inference 1018 1019In addition to explicitly specifying the results of the constraint via the 1020constraint signature, PDLL defined constraints also support inferring the result 1021type from the return statement. Result type inference is active whenever the 1022constraint is defined with no result constraints: 1023 1024```pdll 1025// This constraint returns a derived operation. 1026Constraint ReturnSelf(op: Op<my_dialect.foo>) { 1027 return op; 1028} 1029// This constraint returns a tuple of two Values. 1030Constraint ExtractMultipleResults(op: Op<my_dialect.foo>) { 1031 return (result1 = op.result1, result2 = op.result2); 1032} 1033 1034Pattern { 1035 let values = ExtractMultipleResults(op<my_dialect.foo>); 1036 replace op<my_dialect.foo> with (values.result1, values.result2); 1037} 1038``` 1039 1040##### Single Line "Lambda" Body 1041 1042Constraints generally define their body using a compound block of statements, as 1043shown below: 1044 1045```pdll 1046Constraint ReturnSelf(op: Op<my_dialect.foo>) { 1047 return op; 1048} 1049Constraint ExtractMultipleResults(op: Op<my_dialect.foo>) { 1050 return (result1 = op.result1, result2 = op.result2); 1051} 1052``` 1053 1054Constraints also support a lambda-like syntax for specifying simple single line 1055bodies. The lambda body of a Constraint expects a single expression, which is 1056implicitly returned: 1057 1058```pdll 1059Constraint ReturnSelf(op: Op<my_dialect.foo>) => op; 1060 1061Constraint ExtractMultipleResults(op: Op<my_dialect.foo>) 1062 => (result1 = op.result1, result2 = op.result2); 1063``` 1064 1065#### Native Constraints 1066 1067Constraints may also be defined outside of PDLL, and registered natively within 1068the C++ API. 1069 1070##### Importing existing Native Constraints 1071 1072Constraints defined externally can be imported into PDLL by specifying a 1073constraint "declaration". This is similar to the PDLL form of defining a 1074constraint but omits the body. Importing the declaration in this form allows for 1075PDLL to statically know the expected input and output types. 1076 1077```pdll 1078// Import a single entity value native constraint that checks if the value has a 1079// single use. This constraint must be registered by the consumer of the 1080// compiled PDL. 1081Constraint HasOneUse(value: Value); 1082 1083// Import a multi-entity type constraint that checks if two values have the same 1084// element type. 1085Constraint HasSameElementType(value1: Value, value2: Value); 1086 1087Pattern { 1088 // A single entity constraint can be applied via the variable argument list. 1089 let value: HasOneUse; 1090 1091 // Otherwise, constraints can be applied via the call operator: 1092 let value: Value = ...; 1093 let value2: Value = ...; 1094 HasOneUse(value); 1095 HasSameElementType(value, value2); 1096} 1097``` 1098 1099External constraints are those registered explicitly with the `RewritePatternSet` via 1100the C++ PDL API. For example, the constraints above may be registered as: 1101 1102```c++ 1103static LogicalResult hasOneUseImpl(PatternRewriter &rewriter, Value value) { 1104 return success(value.hasOneUse()); 1105} 1106static LogicalResult hasSameElementTypeImpl(PatternRewriter &rewriter, 1107 Value value1, Value Value2) { 1108 return success(value1.getType().cast<ShapedType>().getElementType() == 1109 value2.getType().cast<ShapedType>().getElementType()); 1110} 1111 1112void registerNativeConstraints(RewritePatternSet &patterns) { 1113 patternList.getPDLPatterns().registerConstraintFunction( 1114 "HasOneUse", hasOneUseImpl); 1115 patternList.getPDLPatterns().registerConstraintFunction( 1116 "HasSameElementType", hasSameElementTypeImpl); 1117} 1118``` 1119 1120##### Defining Native Constraints in PDLL 1121 1122In addition to importing native constraints, PDLL also supports defining native 1123constraints directly when compiling ahead-of-time (AOT) for C++. These 1124constraints can be defined by specifying a string code block after the 1125constraint declaration: 1126 1127```pdll 1128Constraint HasOneUse(value: Value) [{ 1129 return success(value.hasOneUse()); 1130}]; 1131Constraint HasSameElementType(value1: Value, value2: Value) [{ 1132 return success(value1.getType().cast<ShapedType>().getElementType() == 1133 value2.getType().cast<ShapedType>().getElementType()); 1134}]; 1135 1136Pattern { 1137 // A single entity constraint can be applied via the variable argument list. 1138 let value: HasOneUse; 1139 1140 // Otherwise, constraints can be applied via the call operator: 1141 let value: Value = ...; 1142 let value2: Value = ...; 1143 HasOneUse(value); 1144 HasSameElementType(value, value2); 1145} 1146``` 1147 1148The arguments of the constraint are accessible within the code block via the 1149same name. See the ["type translation"](#native-constraint-type-translations) below for 1150detailed information on how PDLL types are converted to native types. In addition to the 1151PDLL arguments, the code block may also access the current `PatternRewriter` using 1152`rewriter`. The result type of the native constraint function is implicitly defined 1153as a `::mlir::LogicalResult`. 1154 1155Taking the constraints defined above as an example, these function would roughly be 1156translated into: 1157 1158```c++ 1159LogicalResult HasOneUse(PatternRewriter &rewriter, Value value) { 1160 return success(value.hasOneUse()); 1161} 1162LogicalResult HasSameElementType(Value value1, Value value2) { 1163 return success(value1.getType().cast<ShapedType>().getElementType() == 1164 value2.getType().cast<ShapedType>().getElementType()); 1165} 1166``` 1167 1168TODO: Native constraints should also be allowed to return values in certain cases. 1169 1170###### Native Constraint Type Translations 1171 1172The types of argument and result variables are generally mapped to the corresponding 1173MLIR type of the [constraint](#constraints) used. Below is a detailed description 1174of how the mapped type of a variable is determined for the various different types of 1175constraints. 1176 1177* Attr, Op, Type, TypeRange, Value, ValueRange: 1178 1179These are all core constraints, and are mapped directly to the MLIR equivalent 1180(that their names suggest), namely: 1181 1182 * `Attr` -> "::mlir::Attribute" 1183 * `Op` -> "::mlir::Operation *" 1184 * `Type` -> "::mlir::Type" 1185 * `TypeRange` -> "::mlir::TypeRange" 1186 * `Value` -> "::mlir::Value" 1187 * `ValueRange` -> "::mlir::ValueRange" 1188 1189* Op<dialect.name> 1190 1191A named operation constraint has a unique translation. If the ODS registration of the 1192referenced operation has been included, the qualified C++ is used. If the ODS information 1193is not available, this constraint maps to "::mlir::Operation *", similarly to the unnamed 1194variant. For example, given the following: 1195 1196```pdll 1197// `my_ops.td` provides the ODS definition of the `my_dialect` operations, such as 1198// `my_dialect.bar` used below. 1199#include "my_ops.td" 1200 1201Constraint Cst(op: Op<my_dialect.bar>) [{ 1202 return success(op ... ); 1203}]; 1204``` 1205 1206The native type used for `op` may be of the form `my_dialect::BarOp`, as opposed to the 1207default `::mlir::Operation *`. Below is a sample translation of the above constraint: 1208 1209```c++ 1210LogicalResult Cst(my_dialect::BarOp op) { 1211 return success(op ... ); 1212} 1213``` 1214 1215* Imported ODS Constraints 1216 1217Aside from the core constraints, certain constraints imported from ODS may use a unique 1218native type. How to enable this unique type depends on the ODS constraint construct that 1219was imported: 1220 1221 * `Attr` constraints 1222 - Imported `Attr` constraints utilize the `storageType` field for native type translation. 1223 1224 * `Type` constraints 1225 - Imported `Type` constraints utilize the `cppClassName` field for native type translation. 1226 1227 * `AttrInterface`/`OpInterface`/`TypeInterface` constraints 1228 - Imported interfaces utilize the `cppInterfaceName` field for native type translation. 1229 1230#### Defining Constraints Inline 1231 1232In addition to global scope, PDLL Constraints and Native Constraints defined in 1233PDLL may be specified *inline* at any level of nesting. This means that they may 1234be defined in Patterns, other Constraints, Rewrites, etc: 1235 1236```pdll 1237Constraint GlobalConstraint() { 1238 Constraint LocalConstraint(value: Value) { 1239 ... 1240 }; 1241 Constraint LocalNativeConstraint(value: Value) [{ 1242 ... 1243 }]; 1244 let someValue: [LocalConstraint, LocalNativeConstraint] = ...; 1245} 1246``` 1247 1248Constraints that are defined inline may also elide the name when used directly: 1249 1250```pdll 1251Constraint GlobalConstraint(inputValue: Value) { 1252 Constraint(value: Value) { ... }(inputValue); 1253 Constraint(value: Value) [{ ... }](inputValue); 1254} 1255``` 1256 1257When defined inline, PDLL constraints may reference any previously defined 1258variable: 1259 1260```pdll 1261Constraint GlobalConstraint(op: Op<my_dialect.foo>) { 1262 Constraint LocalConstraint() { 1263 let results = op.results; 1264 }; 1265} 1266``` 1267 1268### Rewriters 1269 1270Rewriters define the set of transformations to be performed within the `rewrite` 1271section of a pattern, and, more specifically, how to transform the input IR 1272after a successful pattern match. All PDLL rewrites must be defined within the 1273`rewrite` section of the pattern. The `rewrite` section is denoted by the last 1274statement within the body of the `Pattern`, which is required to be an 1275[operation rewrite statement](#operation-rewrite-statements). There are two main 1276categories of rewrites in PDLL: operation rewrite statements, and user defined 1277rewrites. 1278 1279#### Operation Rewrite statements 1280 1281Operation rewrite statements are builtin PDLL statements that perform an IR 1282transformation given a root operation. These statements are the only ones able 1283to start the `rewrite` section of a pattern, as they allow for properly 1284["binding"](#variable-binding) the root operation of the pattern. 1285 1286##### `erase` statement 1287 1288```pdll 1289// A pattern that erases all `my_dialect.foo` operations. 1290Pattern => erase op<my_dialect.foo>; 1291``` 1292 1293The `erase` statement erases a given operation. 1294 1295##### `replace` statement 1296 1297```pdll 1298// A pattern that replaces the root operation with its input value. 1299Pattern { 1300 let root = op<my_dialect.foo>(input: Value); 1301 replace root with input; 1302} 1303 1304// A pattern that replaces the root operation with multiple input values. 1305Pattern { 1306 let root = op<my_dialect.foo>(input: Value, _: Value, input2: Value); 1307 replace root with (input, input2); 1308} 1309 1310// A pattern that replaces the root operation with another operation. 1311// Note that when an operation is used as the replacement, we can infer its 1312// result types from the input operation. In these cases, the result 1313// types of replacement operation may be elided. 1314Pattern { 1315 // Note: In this pattern we also inlined the `root` expression. 1316 replace op<my_dialect.foo> with op<my_dialect.bar>; 1317} 1318``` 1319 1320The `replace` statement allows for replacing a given root operation with either 1321another operation, or a set of input `Value` and `ValueRange` values. When an operation 1322is used as the replacement, we allow infering the result types from the input operation. 1323In these cases, the result types of replacement operation may be elided. Note that no 1324other components aside from the result types will be inferred from the input operation 1325during the replacement. 1326 1327##### `rewrite` statement 1328 1329```pdll 1330// A simple pattern that replaces the root operation with its input value. 1331Pattern { 1332 let root = op<my_dialect.foo>(input: Value); 1333 rewrite root with { 1334 ... 1335 1336 replace root with input; 1337 }; 1338} 1339``` 1340 1341The `rewrite` statement allows for rewriting a given root operation with a block 1342of nested rewriters. The root operation is not implicitly erased or replaced, 1343and any transformations to it must be expressed within the nested rewrite block. 1344The inner body may contain any number of other rewrite statements, variables, or 1345expressions. 1346 1347#### Defining Rewriters in PDLL 1348 1349Additional rewrites can also be defined within PDLL, which allows for building 1350rewrite fragments that can be composed across many different patterns. A 1351rewriter in PDLL is defined similarly to a function in traditional programming 1352languages; it contains a name, a set of input arguments, a set of result types, 1353and a body. Results of a rewrite are returned via a `return` statement. A few 1354examples are shown below: 1355 1356```pdll 1357// A rewrite that constructs and returns a new operation, given an input value. 1358Rewrite BuildFooOp(value: Value) -> Op { 1359 return op<my_dialect.foo>(value); 1360} 1361 1362Pattern { 1363 // We invoke the rewrite in the same way as functions in traditional 1364 // languages. 1365 replace op<my_dialect.old_op>(input: Value) with BuildFooOp(input); 1366} 1367``` 1368 1369##### Rewrites with multiple results 1370 1371Rewrites can return multiple results by returning a tuple of values. When 1372returning multiple results, each result can also be assigned a name to use when 1373indexing that tuple element. Tuple elements can be referenced by their index 1374number, or by name if they were assigned one. 1375 1376```pdll 1377// A rewrite that returns multiple results, with some of the results assigned 1378// a more readable name. 1379Rewrite CreateRewriteOps() -> (Op, result1: ValueRange) { 1380 return (op<my_dialect.bar>, op<my_dialect.foo>); 1381} 1382 1383Pattern { 1384 rewrite root: Op<my_dialect.foo> with { 1385 // Invoke the rewrite, which returns a tuple of values. 1386 let result = CreateRewriteOps(); 1387 1388 // Index the tuple elements by index, or by name. 1389 replace root with (result.0, result.1, result.result1); 1390 } 1391} 1392``` 1393 1394##### Rewrite result type inference 1395 1396In addition to explicitly specifying the results of the rewrite via the rewrite 1397signature, PDLL defined rewrites also support inferring the result type from the 1398return statement. Result type inference is active whenever the rewrite is 1399defined with no result constraints: 1400 1401```pdll 1402// This rewrite returns a derived operation. 1403Rewrite ReturnSelf(op: Op<my_dialect.foo>) => op; 1404// This rewrite returns a tuple of two Values. 1405Rewrite ExtractMultipleResults(op: Op<my_dialect.foo>) { 1406 return (result1 = op.result1, result2 = op.result2); 1407} 1408 1409Pattern { 1410 rewrite root: Op<my_dialect.foo> with { 1411 let values = ExtractMultipleResults(op<my_dialect.foo>); 1412 replace root with (values.result1, values.result2); 1413 } 1414} 1415``` 1416 1417##### Single Line "Lambda" Body 1418 1419Rewrites generally define their body using a compound block of statements, as 1420shown below: 1421 1422```pdll 1423Rewrite ReturnSelf(op: Op<my_dialect.foo>) { 1424 return op; 1425} 1426Rewrite EraseOp(op: Op) { 1427 erase op; 1428} 1429``` 1430 1431Rewrites also support a lambda-like syntax for specifying simple single line 1432bodies. The lambda body of a Rewrite expects a single expression, which is 1433implicitly returned, or a single 1434[operation rewrite statement](#operation-rewrite-statements): 1435 1436```pdll 1437Rewrite ReturnSelf(op: Op<my_dialect.foo>) => op; 1438Rewrite EraseOp(op: Op) => erase op; 1439``` 1440 1441#### Native Rewriters 1442 1443Rewriters may also be defined outside of PDLL, and registered natively within 1444the C++ API. 1445 1446##### Importing existing Native Rewrites 1447 1448Rewrites defined externally can be imported into PDLL by specifying a 1449rewrite "declaration". This is similar to the PDLL form of defining a 1450rewrite but omits the body. Importing the declaration in this form allows for 1451PDLL to statically know the expected input and output types. 1452 1453```pdll 1454// Import a single input native rewrite that returns a new operation. This 1455// rewrite must be registered by the consumer of the compiled PDL. 1456Rewrite BuildOp(value: Value) -> Op; 1457 1458Pattern { 1459 replace op<my_dialect.old_op>(input: Value) with BuildOp(input); 1460} 1461``` 1462 1463External rewrites are those registered explicitly with the `RewritePatternSet` via 1464the C++ PDL API. For example, the rewrite above may be registered as: 1465 1466```c++ 1467static Operation *buildOpImpl(PDLResultList &results, Value value) { 1468 // insert special rewrite logic here. 1469 Operation *resultOp = ...; 1470 return resultOp; 1471} 1472 1473void registerNativeRewrite(RewritePatternSet &patterns) { 1474 patterns.getPDLPatterns().registerRewriteFunction("BuildOp", buildOpImpl); 1475} 1476``` 1477 1478##### Defining Native Rewrites in PDLL 1479 1480In addition to importing native rewrites, PDLL also supports defining native 1481rewrites directly when compiling ahead-of-time (AOT) for C++. These rewrites can 1482be defined by specifying a string code block after the rewrite declaration: 1483 1484```pdll 1485Rewrite BuildOp(value: Value) -> (foo: Op<my_dialect.foo>, bar: Op<my_dialect.bar>) [{ 1486 return {rewriter.create<my_dialect::FooOp>(value), rewriter.create<my_dialect::BarOp>()}; 1487}]; 1488 1489Pattern { 1490 let root = op<my_dialect.foo>(input: Value); 1491 rewrite root with { 1492 // Invoke the native rewrite and use the results when replacing the root. 1493 let results = BuildOp(input); 1494 replace root with (results.foo, results.bar); 1495 } 1496} 1497``` 1498 1499The arguments of the rewrite are accessible within the code block via the 1500same name. See the ["type translation"](#native-rewrite-type-translations) below for 1501detailed information on how PDLL types are converted to native types. In addition to the 1502PDLL arguments, the code block may also access the current `PatternRewriter` using 1503`rewriter`. See the ["result translation"](#native-rewrite-result-translation) section 1504for detailed information on how the result type of the native function is determined. 1505 1506Taking the rewrite defined above as an example, this function would roughly be 1507translated into: 1508 1509```c++ 1510std::tuple<my_dialect::FooOp, my_dialect::BarOp> BuildOp(Value value) { 1511 return {rewriter.create<my_dialect::FooOp>(value), rewriter.create<my_dialect::BarOp>()}; 1512} 1513``` 1514 1515###### Native Rewrite Type Translations 1516 1517The types of argument and result variables are generally mapped to the corresponding 1518MLIR type of the [constraint](#constraints) used. The rules of native `Rewrite` type translation 1519are identical to those of native `Constraint`s, please view the corresponding 1520[native `Constraint` type translation](#native-constraint-type-translations) section for a 1521detailed description of how the mapped type of a variable is determined. 1522 1523###### Native Rewrite Result Translation 1524 1525The results of a native rewrite are directly translated to the results of the native function, 1526using the type translation rules [described above](#native-rewrite-type-translations). The section 1527below describes the various result translation scenarios: 1528 1529* Zero Result 1530 1531```pdll 1532Rewrite createOp() [{ 1533 rewriter.create<my_dialect::FooOp>(); 1534}]; 1535``` 1536 1537In the case where a native `Rewrite` has no results, the native function returns `void`: 1538 1539```c++ 1540void createOp(PatternRewriter &rewriter) { 1541 rewriter.create<my_dialect::FooOp>(); 1542} 1543``` 1544 1545* Single Result 1546 1547```pdll 1548Rewrite createOp() -> Op<my_dialect.foo> [{ 1549 return rewriter.create<my_dialect::FooOp>(); 1550}]; 1551``` 1552 1553In the case where a native `Rewrite` has a single result, the native function returns the corresponding 1554native type for that single result: 1555 1556```c++ 1557my_dialect::FooOp createOp(PatternRewriter &rewriter) { 1558 return rewriter.create<my_dialect::FooOp>(); 1559} 1560``` 1561 1562* Multi Result 1563 1564```pdll 1565Rewrite complexRewrite(value: Value) -> (Op<my_dialect.foo>, FunctionOpInterface) [{ 1566 ... 1567}]; 1568``` 1569 1570In the case where a native `Rewrite` has multiple results, the native function returns a `std::tuple<...>` 1571containing the corresponding native types for each of the results: 1572 1573```c++ 1574std::tuple<my_dialect::FooOp, FunctionOpInterface> 1575complexRewrite(PatternRewriter &rewriter, Value value) { 1576 ... 1577} 1578``` 1579 1580#### Defining Rewrites Inline 1581 1582In addition to global scope, PDLL Rewrites and Native Rewrites defined in PDLL 1583may be specified *inline* at any level of nesting. This means that they may be 1584defined in Patterns, other Rewrites, etc: 1585 1586```pdll 1587Rewrite GlobalRewrite(inputValue: Value) { 1588 Rewrite localRewrite(value: Value) { 1589 ... 1590 }; 1591 Rewrite localNativeRewrite(value: Value) [{ 1592 ... 1593 }]; 1594 localRewrite(inputValue); 1595 localNativeRewrite(inputValue); 1596} 1597``` 1598 1599Rewrites that are defined inline may also elide the name when used directly: 1600 1601```pdll 1602Rewrite GlobalRewrite(inputValue: Value) { 1603 Rewrite(value: Value) { ... }(inputValue); 1604 Rewrite(value: Value) [{ ... }](inputValue); 1605} 1606``` 1607 1608When defined inline, PDLL rewrites may reference any previously defined 1609variable: 1610 1611```pdll 1612Rewrite GlobalRewrite(op: Op<my_dialect.foo>) { 1613 Rewrite localRewrite() { 1614 let results = op.results; 1615 }; 1616} 1617``` 1618