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