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