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, in which case the operands are not 581constrained in any way. When present, the operands of an operation expression 582are interpreted in the following ways: 583 5841) A single instance of type `ValueRange`: 585 586In this case, the single range is treated as all of the operands of the 587operation: 588 589```pdll 590// Define an instance with single range of operands. 591let root = op<my_dialect.foo>(allOperands: ValueRange); 592``` 593 5942) A variadic number of either `Value` or `ValueRange`: 595 596In this case, the inputs are expected to correspond with the operand groups as 597defined on the operation in ODS. 598 599Given the following operation definition in ODS: 600 601```tablegen 602def MyIndirectCallOp { 603 let arguments = (ins FunctionType:$call, Variadic<AnyType>:$args); 604} 605``` 606 607We can match the operands as so: 608 609```pdll 610let root = op<my_dialect.indirect_call>(call: Value, args: ValueRange); 611``` 612 613#### Results 614 615The results section corresponds to the result types of the operation. This 616section of an operation expression may be elided, in which case the result types 617are not constrained in any way. When present, the result types of an operation 618expression are interpreted in the following ways: 619 6201) A single instance of type `TypeRange`: 621 622In this case, the single range is treated as all of the result types of the 623operation: 624 625```pdll 626// Define an instance with single range of types. 627let root = op<my_dialect.foo> -> (allResultTypes: TypeRange); 628``` 629 6302) A variadic number of either `Type` or `TypeRange`: 631 632In this case, the inputs are expected to correspond with the result groups as 633defined on the operation in ODS. 634 635Given the following operation definition in ODS: 636 637```tablegen 638def MyOp { 639 let results = (outs SomeType:$result, Variadic<SomeType>:$otherResults); 640} 641``` 642 643We can match the result types as so: 644 645```pdll 646let root = op<my_dialect.op> -> (result: Type, otherResults: TypeRange); 647``` 648 649#### Attributes 650 651The attributes section of the operation expression corresponds to the attribute 652dictionary of the operation. This section of an operation expression may be 653elided, in which case the attributes are not constrained in any way. The 654composition of this component maps exactly to how attribute dictionaries are 655structured in the MLIR textual assembly format: 656 657```pdll 658let root = op<my_dialect.foo> {attr1 = attrValue: Attr, attr2 = attrValue2: Attr}; 659``` 660 661Within the `{}` attribute entries are specified by an identifier or string name, 662corresponding to the attribute name, followed by an assignment to the attribute 663value. If the attribute value is elided, the value of the attribute is 664implicitly defined as a 665[`UnitAttr`](https://mlir.llvm.org/docs/Dialects/Builtin/#unitattr). 666 667```pdll 668let unitConstant = op<my_dialect.constant> {value}; 669``` 670 671##### Accessing Operation Results 672 673In multi-operation patterns, the result of one operation often feeds as an input 674into another. The result groups of an operation may be accessed by name or by 675index via the `.` operator: 676 677Note: Remember to import the definition of your operation via 678[include](#`.td`_includes) to ensure it is visible to PDLL. 679 680Given the following operation definition in ODS: 681 682```tablegen 683def MyResultOp { 684 let results = (outs SomeType:$result); 685} 686def MyInputOp { 687 let arguments = (ins SomeType:$input, SomeType:$input); 688} 689``` 690 691We can write a pattern where `MyResultOp` feeds into `MyInputOp` as so: 692 693```pdll 694// In this example, we use both `result`(the name) and `0`(the index) to refer to 695// the first result group of `resultOp`. 696// Note: If we elide the result types section within the match section, it means 697// they aren't constrained, not that the operation has no results. 698let resultOp = op<my_dialect.result_op>; 699let inputOp = op<my_dialect.input_op>(resultOp.result, resultOp.0); 700``` 701 702Along with result name access, variables of `Op` type may implicitly convert to 703`Value` or `ValueRange`. If these variables are registered (has ODS entry), they 704are converted to `Value` when they are known to only have one result, otherwise 705they will be converted to `ValueRange`: 706 707```pdll 708// `resultOp` may also convert implicitly to a Value for use in `inputOp`: 709let resultOp = op<my_dialect.result_op>; 710let inputOp = op<my_dialect.input_op>(resultOp); 711 712// We could also inline `resultOp` directly: 713let inputOp = op<my_dialect.input_op>(op<my_dialect.result_op>); 714``` 715 716#### Unregistered Operations 717 718A variable of unregistered op is still available for numeric result indexing. 719Given that we don't have knowledge of its result groups, numeric indexing 720returns a Value corresponding to the individual result at the given index. 721 722```pdll 723// Use the index `0` to refer to the first result value of the unregistered op. 724let inputOp = op<my_dialect.input_op>(op<my_dialect.unregistered_op>.0); 725``` 726 727### Attribute Expression 728 729An attribute expression represents a literal MLIR attribute. It allows for 730statically specifying an MLIR attribute to use, by specifying the textual form 731of that attribute. 732 733```pdll 734let trueConstant = op<arith.constant> {value = attr<"true">}; 735 736let applyResult = op<affine.apply>(args: ValueRange) {map = attr<"affine_map<(d0, d1) -> (d1 - 3)>">} 737``` 738 739### Type Expression 740 741A type expression represents a literal MLIR type. It allows for statically 742specifying an MLIR type to use, by specifying the textual form of that type. 743 744```pdll 745let i32Constant = op<arith.constant> -> (type<"i32">); 746``` 747 748### Tuples 749 750PDLL provides native support for tuples, which are used to group multiple 751elements into a single compound value. The values in a tuple can be of any type, 752and do not need to be of the same type. There is also no limit to the number of 753elements held by a tuple. The elements of a tuple can be accessed by index: 754 755```pdll 756let tupleValue = (op<my_dialect.foo>, attr<"10 : i32">, type<"i32">); 757 758let opValue = tupleValue.0; 759let attrValue = tupleValue.1; 760let typeValue = tupleValue.2; 761``` 762 763You can also name the elements of a tuple and use those names to refer to the 764values of the individual elements. An element name consists of an identifier 765followed immediately by an equal (=). 766 767```pdll 768let tupleValue = ( 769 opValue = op<my_dialect.foo>, 770 attr<"10 : i32">, 771 typeValue = type<"i32"> 772); 773 774let opValue = tupleValue.opValue; 775let attrValue = tupleValue.1; 776let typeValue = tupleValue.typeValue; 777``` 778 779Tuples are used to represent multiple results from a 780[constraint](#constraints-with-multiple-results) or 781[rewrite](#rewrites-with-multiple-results). 782 783### Constraints 784 785Constraints provide the ability to inject additional checks on the input IR 786within the `match` section of a pattern. Constraints can be applied anywhere 787within the `match` section, and depending on the type can either be applied via 788the constraint list of a [variable](#variables) or via the call operator (e.g. 789`MyConstraint(...)`). There are three main categories of constraints: 790 791#### Core Constraints 792 793PDLL defines a number of core constraints that constrain the type of the IR 794entity. These constraints can only be applied via the 795[constraint list](#variable-constraints) of a variable. 796 797* `Attr` (`<` type `>`)? 798 799A single entity constraint that corresponds to an `mlir::Attribute`. This 800constraint optionally takes a type component that constrains the result type of 801the attribute. 802 803```pdll 804// Define a simple variable using the `Attr` constraint. 805let attr: Attr; 806let constant = op<arith.constant> {value = attr}; 807 808// Define a simple variable using the `Attr` constraint, that has its type 809// constrained as well. 810let attrType: Type; 811let attr: Attr<attrType>; 812let constant = op<arith.constant> {value = attr}; 813``` 814 815* `Op` (`<` op-name `>`)? 816 817A single entity constraint that corresponds to an `mlir::Operation *`. 818 819```pdll 820// Match only when the input is from another operation. 821let inputOp: Op; 822let root = op<my_dialect.foo>(inputOp); 823 824// Match only when the input is from another `my_dialect.foo` operation. 825let inputOp: Op<my_dialect.foo>; 826let root = op<my_dialect.foo>(inputOp); 827``` 828 829* `Type` 830 831A single entity constraint that corresponds to an `mlir::Type`. 832 833```pdll 834// Define a simple variable using the `Type` constraint. 835let resultType: Type; 836let root = op<my_dialect.foo> -> (resultType); 837``` 838 839* `TypeRange` 840 841A single entity constraint that corresponds to a `mlir::TypeRange`. 842 843```pdll 844// Define a simple variable using the `TypeRange` constraint. 845let resultTypes: TypeRange; 846let root = op<my_dialect.foo> -> (resultTypes); 847``` 848 849* `Value` (`<` type-expr `>`)? 850 851A single entity constraint that corresponds to an `mlir::Value`. This constraint 852optionally takes a type component that constrains the result type of the value. 853 854```pdll 855// Define a simple variable using the `Value` constraint. 856let value: Value; 857let root = op<my_dialect.foo>(value); 858 859// Define a variable using the `Value` constraint, that has its type constrained 860// to be same as the result type of the `root` op. 861let valueType: Type; 862let input: Value<valueType>; 863let root = op<my_dialect.foo>(input) -> (valueType); 864``` 865 866* `ValueRange` (`<` type-expr `>`)? 867 868A single entity constraint that corresponds to a `mlir::ValueRange`. This 869constraint optionally takes a type component that constrains the result types of 870the value range. 871 872```pdll 873// Define a simple variable using the `ValueRange` constraint. 874let inputs: ValueRange; 875let root = op<my_dialect.foo>(inputs); 876 877// Define a variable using the `ValueRange` constraint, that has its types 878// constrained to be same as the result types of the `root` op. 879let valueTypes: TypeRange; 880let inputs: ValueRange<valueTypes>; 881let root = op<my_dialect.foo>(inputs) -> (valueTypes); 882``` 883 884#### Defining Constraints in PDLL 885 886Aside from the core constraints, additional constraints can also be defined 887within PDLL. This allows for building matcher fragments that can be composed 888across many different patterns. A constraint in PDLL is defined similarly to a 889function in traditional programming languages; it contains a name, a set of 890input arguments, a set of result types, and a body. Results of a constraint are 891returned via a `return` statement. A few examples are shown below: 892 893```pdll 894/// A constraint that takes an input and constrains the use to an operation of 895/// a given type. 896Constraint UsedByFooOp(value: Value) { 897 op<my_dialect.foo>(value); 898} 899 900/// A constraint that returns a result of an existing operation. 901Constraint ExtractResult(op: Op<my_dialect.foo>) -> Value { 902 return op.result; 903} 904 905Pattern { 906 let value = ExtractResult(op<my_dialect.foo>); 907 UsedByFooOp(value); 908} 909``` 910 911##### Constraints with multiple results 912 913Constraints can return multiple results by returning a tuple of values. When 914returning multiple results, each result can also be assigned a name to use when 915indexing that tuple element. Tuple elements can be referenced by their index 916number, or by name if they were assigned one. 917 918```pdll 919// A constraint that returns multiple results, with some of the results assigned 920// a more readable name. 921Constraint ExtractMultipleResults(op: Op<my_dialect.foo>) -> (Value, result1: Value) { 922 return (op.result1, op.result2); 923} 924 925Pattern { 926 // Return a tuple of values. 927 let result = ExtractMultipleResults(op: op<my_dialect.foo>); 928 929 // Index the tuple elements by index, or by name. 930 replace op<my_dialect.foo> with (result.0, result.1, result.result1); 931} 932``` 933 934##### Constraint result type inference 935 936In addition to explicitly specifying the results of the constraint via the 937constraint signature, PDLL defined constraints also support inferring the result 938type from the return statement. Result type inference is active whenever the 939constraint is defined with no result constraints: 940 941```pdll 942// This constraint returns a derived operation. 943Constraint ReturnSelf(op: Op<my_dialect.foo>) { 944 return op; 945} 946// This constraint returns a tuple of two Values. 947Constraint ExtractMultipleResults(op: Op<my_dialect.foo>) { 948 return (result1 = op.result1, result2 = op.result2); 949} 950 951Pattern { 952 let values = ExtractMultipleResults(op<my_dialect.foo>); 953 replace op<my_dialect.foo> with (values.result1, values.result2); 954} 955``` 956 957##### Single Line "Lambda" Body 958 959Constraints generally define their body using a compound block of statements, as 960shown below: 961 962```pdll 963Constraint ReturnSelf(op: Op<my_dialect.foo>) { 964 return op; 965} 966Constraint ExtractMultipleResults(op: Op<my_dialect.foo>) { 967 return (result1 = op.result1, result2 = op.result2); 968} 969``` 970 971Constraints also support a lambda-like syntax for specifying simple single line 972bodies. The lambda body of a Constraint expects a single expression, which is 973implicitly returned: 974 975```pdll 976Constraint ReturnSelf(op: Op<my_dialect.foo>) => op; 977 978Constraint ExtractMultipleResults(op: Op<my_dialect.foo>) 979 => (result1 = op.result1, result2 = op.result2); 980``` 981 982#### Native Constraints 983 984Constraints may also be defined outside of PDLL, and registered natively within 985the C++ API. 986 987##### Importing existing Native Constraints 988 989Constraints defined externally can be imported into PDLL by specifying a 990constraint "declaration". This is similar to the PDLL form of defining a 991constraint but omits the body. Importing the declaration in this form allows for 992PDLL to statically know the expected input and output types. 993 994```pdll 995// Import a single entity value native constraint that checks if the value has a 996// single use. This constraint must be registered by the consumer of the 997// compiled PDL. 998Constraint HasOneUse(value: Value); 999 1000// Import a multi-entity type constraint that checks if two values have the same 1001// element type. 1002Constraint HasSameElementType(value1: Value, value2: Value); 1003 1004Pattern { 1005 // A single entity constraint can be applied via the variable argument list. 1006 let value: HasOneUse; 1007 1008 // Otherwise, constraints can be applied via the call operator: 1009 let value: Value = ...; 1010 let value2: Value = ...; 1011 HasOneUse(value); 1012 HasSameElementType(value, value2); 1013} 1014``` 1015 1016External constraints are those registered explicitly with the `RewritePatternSet` via 1017the C++ PDL API. For example, the constraints above may be registered as: 1018 1019```c++ 1020static LogicalResult hasOneUseImpl(PatternRewriter &rewriter, Value value) { 1021 return success(value.hasOneUse()); 1022} 1023static LogicalResult hasSameElementTypeImpl(PatternRewriter &rewriter, 1024 Value value1, Value Value2) { 1025 return success(value1.getType().cast<ShapedType>().getElementType() == 1026 value2.getType().cast<ShapedType>().getElementType()); 1027} 1028 1029void registerNativeConstraints(RewritePatternSet &patterns) { 1030 patternList.getPDLPatterns().registerConstraintFunction( 1031 "HasOneUse", hasOneUseImpl); 1032 patternList.getPDLPatterns().registerConstraintFunction( 1033 "HasSameElementType", hasSameElementTypeImpl); 1034} 1035``` 1036 1037##### Defining Native Constraints in PDLL 1038 1039In addition to importing native constraints, PDLL also supports defining native 1040constraints directly when compiling ahead-of-time (AOT) for C++. These 1041constraints can be defined by specifying a string code block after the 1042constraint declaration: 1043 1044```pdll 1045Constraint HasOneUse(value: Value) [{ 1046 return success(value.hasOneUse()); 1047}]; 1048Constraint HasSameElementType(value1: Value, value2: Value) [{ 1049 return success(value1.getType().cast<ShapedType>().getElementType() == 1050 value2.getType().cast<ShapedType>().getElementType()); 1051}]; 1052 1053Pattern { 1054 // A single entity constraint can be applied via the variable argument list. 1055 let value: HasOneUse; 1056 1057 // Otherwise, constraints can be applied via the call operator: 1058 let value: Value = ...; 1059 let value2: Value = ...; 1060 HasOneUse(value); 1061 HasSameElementType(value, value2); 1062} 1063``` 1064 1065The arguments of the constraint are accessible within the code block via the 1066same name. The type of these native variables are mapped directly to the 1067corresponding MLIR type of the [core constraint](#core-constraints) used. For 1068example, an `Op` corresponds to a variable of type `Operation *`. 1069 1070The results of the constraint can be populated using the provided `results` 1071variable. This variable is a `PDLResultList`, and expects results to be 1072populated in the order that they are defined within the result list of the 1073constraint declaration. 1074 1075In addition to the above, the code block may also access the current 1076`PatternRewriter` using `rewriter`. 1077 1078#### Defining Constraints Inline 1079 1080In addition to global scope, PDLL Constraints and Native Constraints defined in 1081PDLL may be specified *inline* at any level of nesting. This means that they may 1082be defined in Patterns, other Constraints, Rewrites, etc: 1083 1084```pdll 1085Constraint GlobalConstraint() { 1086 Constraint LocalConstraint(value: Value) { 1087 ... 1088 }; 1089 Constraint LocalNativeConstraint(value: Value) [{ 1090 ... 1091 }]; 1092 let someValue: [LocalConstraint, LocalNativeConstraint] = ...; 1093} 1094``` 1095 1096Constraints that are defined inline may also elide the name when used directly: 1097 1098```pdll 1099Constraint GlobalConstraint(inputValue: Value) { 1100 Constraint(value: Value) { ... }(inputValue); 1101 Constraint(value: Value) [{ ... }](inputValue); 1102} 1103``` 1104 1105When defined inline, PDLL constraints may reference any previously defined 1106variable: 1107 1108```pdll 1109Constraint GlobalConstraint(op: Op<my_dialect.foo>) { 1110 Constraint LocalConstraint() { 1111 let results = op.results; 1112 }; 1113} 1114``` 1115 1116### Rewriters 1117 1118Rewriters define the set of transformations to be performed within the `rewrite` 1119section of a pattern, and, more specifically, how to transform the input IR 1120after a successful pattern match. All PDLL rewrites must be defined within the 1121`rewrite` section of the pattern. The `rewrite` section is denoted by the last 1122statement within the body of the `Pattern`, which is required to be an 1123[operation rewrite statement](#operation-rewrite-statements). There are two main 1124categories of rewrites in PDLL: operation rewrite statements, and user defined 1125rewrites. 1126 1127#### Operation Rewrite statements 1128 1129Operation rewrite statements are builtin PDLL statements that perform an IR 1130transformation given a root operation. These statements are the only ones able 1131to start the `rewrite` section of a pattern, as they allow for properly 1132["binding"](#variable-binding) the root operation of the pattern. 1133 1134##### `erase` statement 1135 1136```pdll 1137// A pattern that erases all `my_dialect.foo` operations. 1138Pattern => erase op<my_dialect.foo>; 1139``` 1140 1141The `erase` statement erases a given operation. 1142 1143##### `replace` statement 1144 1145```pdll 1146// A pattern that replaces the root operation with its input value. 1147Pattern { 1148 let root = op<my_dialect.foo>(input: Value); 1149 replace root with input; 1150} 1151 1152// A pattern that replaces the root operation with multiple input values. 1153Pattern { 1154 let root = op<my_dialect.foo>(input: Value, _: Value, input2: Value); 1155 replace root with (input, input2); 1156} 1157 1158// A pattern that replaces the root operation with another operation. 1159// Note that when an operation is used as the replacement, we can infer its 1160// result types from the input operation. In these cases, the result 1161// types of replacement operation may be elided. 1162Pattern { 1163 // Note: In this pattern we also inlined the `root` expression. 1164 replace op<my_dialect.foo> with op<my_dialect.bar>; 1165} 1166``` 1167 1168The `replace` statement allows for replacing a given root operation with either 1169another operation, or a set of input `Value` and `ValueRange` values. When an operation 1170is used as the replacement, we allow infering the result types from the input operation. 1171In these cases, the result types of replacement operation may be elided. Note that no 1172other components aside from the result types will be inferred from the input operation 1173during the replacement. 1174 1175##### `rewrite` statement 1176 1177```pdll 1178// A simple pattern that replaces the root operation with its input value. 1179Pattern { 1180 let root = op<my_dialect.foo>(input: Value); 1181 rewrite root with { 1182 ... 1183 1184 replace root with input; 1185 }; 1186} 1187``` 1188 1189The `rewrite` statement allows for rewriting a given root operation with a block 1190of nested rewriters. The root operation is not implicitly erased or replaced, 1191and any transformations to it must be expressed within the nested rewrite block. 1192The inner body may contain any number of other rewrite statements, variables, or 1193expressions. 1194 1195#### Defining Rewriters in PDLL 1196 1197Additional rewrites can also be defined within PDLL, which allows for building 1198rewrite fragments that can be composed across many different patterns. A 1199rewriter in PDLL is defined similarly to a function in traditional programming 1200languages; it contains a name, a set of input arguments, a set of result types, 1201and a body. Results of a rewrite are returned via a `return` statement. A few 1202examples are shown below: 1203 1204```pdll 1205// A rewrite that constructs and returns a new operation, given an input value. 1206Rewrite BuildFooOp(value: Value) -> Op { 1207 return op<my_dialect.foo>(value); 1208} 1209 1210Pattern { 1211 // We invoke the rewrite in the same way as functions in traditional 1212 // languages. 1213 replace op<my_dialect.old_op>(input: Value) with BuildFooOp(input); 1214} 1215``` 1216 1217##### Rewrites with multiple results 1218 1219Rewrites can return multiple results by returning a tuple of values. When 1220returning multiple results, each result can also be assigned a name to use when 1221indexing that tuple element. Tuple elements can be referenced by their index 1222number, or by name if they were assigned one. 1223 1224```pdll 1225// A rewrite that returns multiple results, with some of the results assigned 1226// a more readable name. 1227Rewrite CreateRewriteOps() -> (Op, result1: ValueRange) { 1228 return (op<my_dialect.bar>, op<my_dialect.foo>); 1229} 1230 1231Pattern { 1232 rewrite root: Op<my_dialect.foo> with { 1233 // Invoke the rewrite, which returns a tuple of values. 1234 let result = CreateRewriteOps(); 1235 1236 // Index the tuple elements by index, or by name. 1237 replace root with (result.0, result.1, result.result1); 1238 } 1239} 1240``` 1241 1242##### Rewrite result type inference 1243 1244In addition to explicitly specifying the results of the rewrite via the rewrite 1245signature, PDLL defined rewrites also support inferring the result type from the 1246return statement. Result type inference is active whenever the rewrite is 1247defined with no result constraints: 1248 1249```pdll 1250// This rewrite returns a derived operation. 1251Rewrite ReturnSelf(op: Op<my_dialect.foo>) => op; 1252// This rewrite returns a tuple of two Values. 1253Rewrite ExtractMultipleResults(op: Op<my_dialect.foo>) { 1254 return (result1 = op.result1, result2 = op.result2); 1255} 1256 1257Pattern { 1258 rewrite root: Op<my_dialect.foo> with { 1259 let values = ExtractMultipleResults(op<my_dialect.foo>); 1260 replace root with (values.result1, values.result2); 1261 } 1262} 1263``` 1264 1265##### Single Line "Lambda" Body 1266 1267Rewrites generally define their body using a compound block of statements, as 1268shown below: 1269 1270```pdll 1271Rewrite ReturnSelf(op: Op<my_dialect.foo>) { 1272 return op; 1273} 1274Rewrite EraseOp(op: Op) { 1275 erase op; 1276} 1277``` 1278 1279Rewrites also support a lambda-like syntax for specifying simple single line 1280bodies. The lambda body of a Rewrite expects a single expression, which is 1281implicitly returned, or a single 1282[operation rewrite statement](#operation-rewrite-statements): 1283 1284```pdll 1285Rewrite ReturnSelf(op: Op<my_dialect.foo>) => op; 1286Rewrite EraseOp(op: Op) => erase op; 1287``` 1288 1289#### Native Rewriters 1290 1291Rewriters may also be defined outside of PDLL, and registered natively within 1292the C++ API. 1293 1294##### Importing existing Native Rewrites 1295 1296Rewrites defined externally can be imported into PDLL by specifying a 1297rewrite "declaration". This is similar to the PDLL form of defining a 1298rewrite but omits the body. Importing the declaration in this form allows for 1299PDLL to statically know the expected input and output types. 1300 1301```pdll 1302// Import a single input native rewrite that returns a new operation. This 1303// rewrite must be registered by the consumer of the compiled PDL. 1304Rewrite BuildOp(value: Value) -> Op; 1305 1306Pattern { 1307 replace op<my_dialect.old_op>(input: Value) with BuildOp(input); 1308} 1309``` 1310 1311External rewrites are those registered explicitly with the `RewritePatternSet` via 1312the C++ PDL API. For example, the rewrite above may be registered as: 1313 1314```c++ 1315static Operation *buildOpImpl(PDLResultList &results, Value value) { 1316 // insert special rewrite logic here. 1317 Operation *resultOp = ...; 1318 return resultOp; 1319} 1320 1321void registerNativeRewrite(RewritePatternSet &patterns) { 1322 patterns.getPDLPatterns().registerRewriteFunction("BuildOp", buildOpImpl); 1323} 1324``` 1325 1326##### Defining Native Rewrites in PDLL 1327 1328In addition to importing native rewrites, PDLL also supports defining native 1329rewrites directly when compiling ahead-of-time (AOT) for C++. These rewrites can 1330be defined by specifying a string code block after the rewrite declaration: 1331 1332```pdll 1333Rewrite BuildOp(value: Value) -> (foo: Op<my_dialect.foo>, bar: Op<my_dialect.bar>) [{ 1334 // We push back the results into the `results` variable in the order defined 1335 // by the result list of the rewrite declaration. 1336 results.push_back(rewriter.create<my_dialect::FooOp>(value)); 1337 results.push_back(rewriter.create<my_dialect::BarOp>()); 1338}]; 1339 1340Pattern { 1341 let root = op<my_dialect.foo>(input: Value); 1342 rewrite root with { 1343 // Invoke the native rewrite and use the results when replacing the root. 1344 let results = BuildOp(input); 1345 replace root with (results.foo, results.bar); 1346 } 1347} 1348``` 1349 1350The arguments of the rewrite are accessible within the code block via the 1351same name. The type of these native variables are mapped directly to the 1352corresponding MLIR type of the [core constraint](#core-constraints) used. For 1353example, an `Op` corresponds to a variable of type `Operation *`. 1354 1355The results of the rewrite can be populated using the provided `results` 1356variable. This variable is a `PDLResultList`, and expects results to be 1357populated in the order that they are defined within the result list of the 1358rewrite declaration. 1359 1360In addition to the above, the code block may also access the current 1361`PatternRewriter` using `rewriter`. 1362 1363#### Defining Rewrites Inline 1364 1365In addition to global scope, PDLL Rewrites and Native Rewrites defined in PDLL 1366may be specified *inline* at any level of nesting. This means that they may be 1367defined in Patterns, other Rewrites, etc: 1368 1369```pdll 1370Rewrite GlobalRewrite(inputValue: Value) { 1371 Rewrite localRewrite(value: Value) { 1372 ... 1373 }; 1374 Rewrite localNativeRewrite(value: Value) [{ 1375 ... 1376 }]; 1377 localRewrite(inputValue); 1378 localNativeRewrite(inputValue); 1379} 1380``` 1381 1382Rewrites that are defined inline may also elide the name when used directly: 1383 1384```pdll 1385Rewrite GlobalRewrite(inputValue: Value) { 1386 Rewrite(value: Value) { ... }(inputValue); 1387 Rewrite(value: Value) [{ ... }](inputValue); 1388} 1389``` 1390 1391When defined inline, PDLL rewrites may reference any previously defined 1392variable: 1393 1394```pdll 1395Rewrite GlobalRewrite(op: Op<my_dialect.foo>) { 1396 Rewrite localRewrite() { 1397 let results = op.results; 1398 }; 1399} 1400``` 1401