1# Dialect Conversion 2 3This document describes a framework in MLIR in which to perform operation 4conversions between, and within dialects. This framework allows for transforming 5illegal operations to those supported by a provided conversion target, via a set 6of pattern-based operation rewriting patterns. 7 8The dialect conversion framework consists of the following components: 9 10* A [Conversion Target](#conversion-target) 11* A set of [Rewrite Patterns](#rewrite-pattern-specification) 12* A [Type Converter](#type-conversion) (Optional) 13 14[TOC] 15 16## Modes of Conversion 17 18When applying a conversion to a set of operations, there are several different 19conversion modes that may be selected from: 20 21* Partial Conversion 22 23 - A partial conversion will legalize as many operations to the target as 24 possible, but will allow pre-existing operations that were not 25 explicitly marked as "illegal" to remain unconverted. This allows for 26 partially lowering parts of the input in the presence of unknown 27 operations. 28 - A partial conversion can be applied via `applyPartialConversion`. 29 30* Full Conversion 31 32 - A full conversion legalizes all input operations, and is only successful 33 if all operations are properly legalized to the given conversion target. 34 This ensures that only known operations will exist after the conversion 35 process. 36 - A full conversion can be applied via `applyFullConversion`. 37 38* Analysis Conversion 39 40 - An analysis conversion will analyze which operations are legalizable to 41 the given conversion target if a conversion were to be applied. This is 42 done by performing a 'partial' conversion and recording which operations 43 would have been successfully converted if successful. Note that no 44 rewrites, or transformations, are actually applied to the input 45 operations. 46 - An analysis conversion can be applied via `applyAnalysisConversion`. 47 48In all cases, the framework walks the operations in preorder, examining an op 49before the ops in any regions it has. 50 51## Conversion Target 52 53The conversion target is a formal definition of what is considered to be legal 54during the conversion process. The final operations generated by the conversion 55framework must be marked as legal on the `ConversionTarget` for the rewrite to 56be a success. Depending on the conversion mode, existing operations need not 57always be legal. Operations and dialects may be marked with any of the provided 58legality actions below: 59 60* Legal 61 62 - This action signals that every instance of a given operation is legal, 63 i.e. any combination of attributes, operands, types, etc. are valid. 64 65* Dynamic 66 67 - This action signals that only some instances of a given operation are 68 legal. This allows for defining fine-tune constraints, e.g. saying that 69 `arith.addi` is only legal when operating on 32-bit integers. 70 71* Illegal 72 73 - This action signals that no instance of a given operation is legal. 74 Operations marked as "illegal" must always be converted for the 75 conversion to be successful. This action also allows for selectively 76 marking specific operations as illegal in an otherwise legal dialect. 77 78Operations and dialects that are neither explicitly marked legal nor illegal are 79separate from the above ("unknown" operations) and are treated differently, for 80example, for the purposes of partial conversion as mentioned above. 81 82An example conversion target is shown below: 83 84```c++ 85struct MyTarget : public ConversionTarget { 86 MyTarget(MLIRContext &ctx) : ConversionTarget(ctx) { 87 //-------------------------------------------------------------------------- 88 // Marking an operation as Legal: 89 90 /// Mark all operations within the LLVM dialect are legal. 91 addLegalDialect<LLVMDialect>(); 92 93 /// Mark `arith.constant` op is always legal on this target. 94 addLegalOp<arith::ConstantOp>(); 95 96 //-------------------------------------------------------------------------- 97 // Marking an operation as dynamically legal. 98 99 /// Mark all operations within Affine dialect have dynamic legality 100 /// constraints. 101 addDynamicallyLegalDialect<affine::AffineDialect>( 102 [](Operation *op) { ... }); 103 104 /// Mark `func.return` as dynamically legal, but provide a specific legality 105 /// callback. 106 addDynamicallyLegalOp<func::ReturnOp>([](func::ReturnOp op) { ... }); 107 108 /// Treat unknown operations, i.e. those without a legalization action 109 /// directly set, as dynamically legal. 110 markUnknownOpDynamicallyLegal([](Operation *op) { ... }); 111 112 //-------------------------------------------------------------------------- 113 // Marking an operation as illegal. 114 115 /// All operations within the GPU dialect are illegal. 116 addIllegalDialect<GPUDialect>(); 117 118 /// Mark `cf.br` and `cf.cond_br` as illegal. 119 addIllegalOp<cf::BranchOp, cf::CondBranchOp>(); 120 } 121 122 /// Implement the default legalization handler to handle operations marked as 123 /// dynamically legal that were not provided with an explicit handler. 124 bool isDynamicallyLegal(Operation *op) override { ... } 125}; 126``` 127 128### Recursive Legality 129 130In some cases, it may be desirable to mark entire regions as legal. This 131provides an additional granularity of context to the concept of "legal". If an 132operation is marked recursively legal, either statically or dynamically, then 133all of the operations nested within are also considered legal even if they would 134otherwise be considered "illegal". An operation can be marked via 135`markOpRecursivelyLegal<>`: 136 137```c++ 138ConversionTarget &target = ...; 139 140/// The operation must first be marked as `Legal` or `Dynamic`. 141target.addLegalOp<MyOp>(...); 142target.addDynamicallyLegalOp<MySecondOp>(...); 143 144/// Mark the operation as always recursively legal. 145target.markOpRecursivelyLegal<MyOp>(); 146/// Mark optionally with a callback to allow selective marking. 147target.markOpRecursivelyLegal<MyOp, MySecondOp>([](Operation *op) { ... }); 148/// Mark optionally with a callback to allow selective marking. 149target.markOpRecursivelyLegal<MyOp>([](MyOp op) { ... }); 150``` 151 152## Rewrite Pattern Specification 153 154After the conversion target has been defined, a set of legalization patterns 155must be provided to transform illegal operations into legal ones. The patterns 156supplied here have the same structure and restrictions as those described in the 157main [Pattern](PatternRewriter.md) documentation. The patterns provided do not 158need to generate operations that are directly legal on the target. The framework 159will automatically build a graph of conversions to convert non-legal operations 160into a set of legal ones. 161 162As an example, say you define a target that supports one operation: `foo.add`. 163When providing the following patterns: [`bar.add` -> `baz.add`, `baz.add` -> 164`foo.add`], the framework will automatically detect that it can legalize 165`bar.add` -> `foo.add` even though a direct conversion does not exist. This 166means that you don’t have to define a direct legalization pattern for `bar.add` 167-> `foo.add`. 168 169### Conversion Patterns 170 171Along with the general `RewritePattern` classes, the conversion framework 172provides a special type of rewrite pattern that can be used when a pattern 173relies on interacting with constructs specific to the conversion process, the 174`ConversionPattern`. For example, the conversion process does not necessarily 175update operations in-place and instead creates a mapping of events such as 176replacements and erasures, and only applies them when the entire conversion 177process is successful. Certain classes of patterns rely on using the 178updated/remapped operands of an operation, such as when the types of results 179defined by an operation have changed. The general Rewrite Patterns can no longer 180be used in these situations, as the types of the operands of the operation being 181matched will not correspond with those expected by the user. This pattern 182provides, as an additional argument to the `matchAndRewrite` and `rewrite` 183methods, the list of operands that the operation should use after conversion. If 184an operand was the result of a non-converted operation, for example if it was 185already legal, the original operand is used. This means that the operands 186provided always have a 1-1 non-null correspondence with the operands on the 187operation. The original operands of the operation are still intact and may be 188inspected as normal. These patterns also utilize a special `PatternRewriter`, 189`ConversionPatternRewriter`, that provides special hooks for use with the 190conversion infrastructure. 191 192```c++ 193struct MyConversionPattern : public ConversionPattern { 194 /// The `matchAndRewrite` hooks on ConversionPatterns take an additional 195 /// `operands` parameter, containing the remapped operands of the original 196 /// operation. 197 virtual LogicalResult 198 matchAndRewrite(Operation *op, ArrayRef<Value> operands, 199 ConversionPatternRewriter &rewriter) const; 200}; 201``` 202 203#### Type Safety 204 205The types of the remapped operands provided to a conversion pattern must be of a 206type expected by the pattern. The expected types of a pattern are determined by 207a provided [TypeConverter](#type-converter). If no type converter is provided, 208the types of the remapped operands are expected to match the types of the 209original operands. If a type converter is provided, the types of the remapped 210operands are expected to be legal as determined by the converter. If the 211remapped operand types are not of an expected type, and a materialization to the 212expected type could not be performed, the pattern fails application before the 213`matchAndRewrite` hook is invoked. This ensures that patterns do not have to 214explicitly ensure type safety, or sanitize the types of the incoming remapped 215operands. More information on type conversion is detailed in the 216[dedicated section](#type-conversion) below. 217 218## Type Conversion 219 220It is sometimes necessary as part of a conversion to convert the set types of 221being operated on. In these cases, a `TypeConverter` object may be defined that 222details how types should be converted when interfacing with a pattern. A 223`TypeConverter` may be used to convert the signatures of block arguments and 224regions, to define the expected inputs types of the pattern, and to reconcile 225type differences in general. 226 227### Type Converter 228 229The `TypeConverter` contains several hooks for detailing how to convert types, 230and how to materialize conversions between types in various situations. The two 231main aspects of the `TypeConverter` are conversion and materialization. 232 233A `conversion` describes how a given source `Type` should be converted to N 234target types. If the source type is converted to itself, we say it is a "legal" 235type. Type conversions are specified via the `addConversion` method described 236below. 237 238A `materialization` describes how a list of values should be converted to a 239list of values with specific types. An important distinction from a 240`conversion` is that a `materialization` can produce IR, whereas a `conversion` 241cannot. These materializations are used by the conversion framework to ensure 242type safety during the conversion process. There are several types of 243materializations depending on the situation. 244 245* Source Materialization 246 247 - A source materialization is used when a value was replaced with a value 248 of a different type, but there are still users that expects the original 249 ("source") type at the end of the conversion process. A source 250 materialization converts the replacement value back to the source type. 251 - This materialization is used in the following situations: 252 * When a block argument has been converted to a different type, but 253 the original argument still has users that will remain live after 254 the conversion process has finished. 255 * When a block argument has been dropped, but the argument still has 256 users that will remain live after the conversion process has 257 finished. 258 * When the result type of an operation has been converted to a 259 different type, but the original result still has users that will 260 remain live after the conversion process is finished. 261 262* Target Materialization 263 264 - A target materialization converts a value to the type that is expected 265 by a conversion pattern according to its type converter. 266 - A target materialization is used when a pattern expects the remapped 267 operands to be of a certain set of types, but the original input 268 operands have either not been replaced or been replaced with values of 269 a different type. 270 271If a converted value is used by an operation that isn't converted, it needs a 272conversion back to the `source` type, hence source materialization; if an 273unconverted value is used by an operation that is being converted, it needs 274conversion to the `target` type, hence target materialization. 275 276As noted above, the conversion process guarantees that the type contract of the 277IR is preserved during the conversion. This means that the types of value uses 278will not implicitly change during the conversion process. When the type of a 279value definition, either block argument or operation result, is being changed, 280the users of that definition must also be updated during the conversion process. 281If they aren't, a type conversion must be materialized to ensure that a value of 282the expected type is still present within the IR. If a materialization is 283required, but cannot be performed, the entire conversion process fails. 284 285Several of the available hooks are detailed below: 286 287```c++ 288class TypeConverter { 289 public: 290 /// Register a conversion function. A conversion function defines how a given 291 /// source type should be converted. A conversion function must be convertible 292 /// to any of the following forms(where `T` is a class derived from `Type`: 293 /// * Optional<Type>(T) 294 /// - This form represents a 1-1 type conversion. It should return nullptr 295 /// or `std::nullopt` to signify failure. If `std::nullopt` is returned, the 296 /// converter is allowed to try another conversion function to perform 297 /// the conversion. 298 /// * Optional<LogicalResult>(T, SmallVectorImpl<Type> &) 299 /// - This form represents a 1-N type conversion. It should return 300 /// `failure` or `std::nullopt` to signify a failed conversion. If the new 301 /// set of types is empty, the type is removed and any usages of the 302 /// existing value are expected to be removed during conversion. If 303 /// `std::nullopt` is returned, the converter is allowed to try another 304 /// conversion function to perform the conversion. 305 /// * Optional<LogicalResult>(T, SmallVectorImpl<Type> &, ArrayRef<Type>) 306 /// - This form represents a 1-N type conversion supporting recursive 307 /// types. The first two arguments and the return value are the same as 308 /// for the regular 1-N form. The third argument is contains is the 309 /// "call stack" of the recursive conversion: it contains the list of 310 /// types currently being converted, with the current type being the 311 /// last one. If it is present more than once in the list, the 312 /// conversion concerns a recursive type. 313 /// Note: When attempting to convert a type, e.g. via 'convertType', the 314 /// mostly recently added conversions will be invoked first. 315 template <typename FnT, 316 typename T = typename llvm::function_traits<FnT>::template arg_t<0>> 317 void addConversion(FnT &&callback) { 318 registerConversion(wrapCallback<T>(std::forward<FnT>(callback))); 319 } 320 321 /// All of the following materializations require function objects that are 322 /// convertible to the following form: 323 /// `std::optional<Value>(OpBuilder &, T, ValueRange, Location)`, 324 /// where `T` is any subclass of `Type`. This function is responsible for 325 /// creating an operation, using the OpBuilder and Location provided, that 326 /// "casts" a range of values into a single value of the given type `T`. It 327 /// must return a Value of the converted type on success, an `std::nullopt` if 328 /// it failed but other materialization can be attempted, and `nullptr` on 329 /// unrecoverable failure. It will only be called for (sub)types of `T`. 330 /// Materialization functions must be provided when a type conversion may 331 /// persist after the conversion has finished. 332 333 /// This method registers a materialization that will be called when 334 /// converting a replacement value back to its original source type. 335 /// This is used when some uses of the original value persist beyond the main 336 /// conversion. 337 template <typename FnT, 338 typename T = typename llvm::function_traits<FnT>::template arg_t<1>> 339 void addSourceMaterialization(FnT &&callback) { 340 sourceMaterializations.emplace_back( 341 wrapMaterialization<T>(std::forward<FnT>(callback))); 342 } 343 344 /// This method registers a materialization that will be called when 345 /// converting a value to a target type according to a pattern's type 346 /// converter. 347 /// 348 /// Note: Target materializations can optionally inspect the "original" 349 /// type. This type may be different from the type of the input value. 350 /// For example, let's assume that a conversion pattern "P1" replaced an SSA 351 /// value "v1" (type "t1") with "v2" (type "t2"). Then a different conversion 352 /// pattern "P2" matches an op that has "v1" as an operand. Let's furthermore 353 /// assume that "P2" determines that the converted target type of "t1" is 354 /// "t3", which may be different from "t2". In this example, the target 355 /// materialization will be invoked with: outputType = "t3", inputs = "v2", 356 /// originalType = "t1". Note that the original type "t1" cannot be recovered 357 /// from just "t3" and "v2"; that's why the originalType parameter exists. 358 /// 359 /// Note: During a 1:N conversion, the result types can be a TypeRange. In 360 /// that case the materialization produces a SmallVector<Value>. 361 template <typename FnT, 362 typename T = typename llvm::function_traits<FnT>::template arg_t<1>> 363 void addTargetMaterialization(FnT &&callback) { 364 targetMaterializations.emplace_back( 365 wrapMaterialization<T>(std::forward<FnT>(callback))); 366 } 367}; 368``` 369 370Materializations through the type converter are optional. If the 371`ConversionConfig::buildMaterializations` flag is set to "false", the dialect 372conversion driver builds an `unrealized_conversion_cast` op instead of calling 373the respective type converter callback whenever a materialization is required. 374 375### Region Signature Conversion 376 377From the perspective of type conversion, the types of block arguments are a bit 378special. Throughout the conversion process, blocks may move between regions of 379different operations. Given this, the conversion of the types for blocks must be 380done explicitly via a conversion pattern. 381 382To convert the types of block arguments within a Region, a custom hook on the 383`ConversionPatternRewriter` must be invoked; `convertRegionTypes`. This hook 384uses a provided type converter to apply type conversions to all blocks of a 385given region. This hook also takes an optional 386`TypeConverter::SignatureConversion` parameter that applies a custom conversion 387to the entry block of the region. The types of the entry block arguments are 388often tied semantically to the operation, e.g., `func::FuncOp`, `AffineForOp`, 389etc. 390 391To convert the signature of just one given block, the 392`applySignatureConversion` hook can be used. 393 394A signature conversion, `TypeConverter::SignatureConversion`, can be built 395programmatically: 396 397```c++ 398class SignatureConversion { 399public: 400 /// Remap an input of the original signature with a new set of types. The 401 /// new types are appended to the new signature conversion. 402 void addInputs(unsigned origInputNo, ArrayRef<Type> types); 403 404 /// Append new input types to the signature conversion, this should only be 405 /// used if the new types are not intended to remap an existing input. 406 void addInputs(ArrayRef<Type> types); 407 408 /// Remap an input of the original signature with a range of types in the 409 /// new signature. 410 void remapInput(unsigned origInputNo, unsigned newInputNo, 411 unsigned newInputCount = 1); 412 413 /// Remap an input of the original signature to another `replacement` 414 /// value. This drops the original argument. 415 void remapInput(unsigned origInputNo, Value replacement); 416}; 417``` 418 419The `TypeConverter` provides several default utilities for signature conversion 420and legality checking: 421`convertSignatureArgs`/`convertBlockSignature`/`isLegal(Region *|Type)`. 422 423## Debugging 424 425To debug the execution of the dialect conversion framework, 426`-debug-only=dialect-conversion` may be used. This command line flag activates 427LLVM's debug logging infrastructure solely for the conversion framework. The 428output is formatted as a tree structure, mirroring the structure of the 429conversion process. This output contains all of the actions performed by the 430rewriter, how generated operations get legalized, and why they fail. 431 432Example output is shown below: 433 434``` 435//===-------------------------------------------===// 436Legalizing operation : 'func.return'(0x608000002e20) { 437 "func.return"() : () -> () 438 439 * Fold { 440 } -> FAILURE : unable to fold 441 442 * Pattern : 'func.return -> ()' { 443 ** Insert : 'spirv.Return'(0x6070000453e0) 444 ** Replace : 'func.return'(0x608000002e20) 445 446 //===-------------------------------------------===// 447 Legalizing operation : 'spirv.Return'(0x6070000453e0) { 448 "spirv.Return"() : () -> () 449 450 } -> SUCCESS : operation marked legal by the target 451 //===-------------------------------------------===// 452 } -> SUCCESS : pattern applied successfully 453} -> SUCCESS 454//===-------------------------------------------===// 455``` 456 457This output is describing the legalization of an `func.return` operation. We 458first try to legalize by folding the operation, but that is unsuccessful for 459`func.return`. From there, a pattern is applied that replaces the `func.return` 460with a `spirv.Return`. The newly generated `spirv.Return` is then processed for 461legalization, but is found to already legal as per the target. 462