User:Mscuttari/sandbox

MLIR is a framework for compiler development. The project belongs to the LLVM ecosystem and aims to abstract intermediate representations through the concept of dialects. A dialect is a set of operations, types, and attributes, whose semantics can be modeled to represent domain-specific languages. The name of the project stands for Multi-Level Intermediate Representation, in which the multi-level keyword refers to the possibility of defining multiple dialects and progressive conversions towards machine code. This capability enables to retain information at a higher level of abstraction and perform more accurate analyses and transformations, which otherwise would have to deal with lower level representations.

Dialects
Operations represent the core element around which dialects are built. They are identified by a name – that must be unique within the dialect they belong to – and have optional operands, results, attributes and regions. Operands and results adhere to the Static Single Assignment form. Each result also has an associated type. Attributes represent compile-time knowledge (e.g., constant values). Regions consist in a list of blocks, each of which may have input arguments and contain a list of operations. Despite the dialects being designed around the SSA form, PHI nodes are not part of this design and are instead replaced by the input arguments of blocks, in combination with operands of control-flow operations.

The general syntax for an operation is the following: The example shows an operation that is named morph, belongs to the mydialect dialect, takes one input operand and produces two results. The input argument has an associated type named custom_type and the results both have type other_type, with both the types belonging again to the mydialect dialect. The operation also has two associated attributes – named some.attribute and other_attribute – and a region containing one block. Finally, locations are attached for debugging purposes.

The syntax of operations, types and attributes can also be customized according to the user preferences by implementing proper parsing and printing functions within the operation definition.

Core dialects
The MLIR dialects ecosystem is open and extensible, meaning that end-users are free to create new dialects capturing the semantics they need. Still, the codebase of MLIR already makes various kinds of dialects available to end-users. Each of them aims to address a specific aspect that often manifests within intermediate representations, but does it in a self-contained way. For example, the arith dialect holds simple mathematical operations on integer and floating point values, while the memref dialect holds the operations for memory management.

The following code defines a function that takes two floating point matrices and performs the sum between the values at the same positions: Different dialects may be used to achieve the same result, and each of them may imply different levels of abstraction. In this example, the affine dialect has been chosen to reuse existing analyses and optimizations for polyhedral compilation.

One relevant core dialect is the LLVM one. Its purpose is to provide a one-to-one map of LLVM-IR – the intermediate representation used by LLVM – in order to enable the reuse all of its middle-end and backend transformations, including machine code generation.

Operation definition specification
The operations of a dialect can be defined using the C++ language, but also in a more convenient and robust way by using the Operation definition specification (ODS). By using TableGen, the C++ code for declarations and definitions can be then automatically generated.

The autogenerated code includes the parsing and printing methods – which are based on a simple string mapping the structure of desired textual representation – together with all the boilerplate code for accessing fields and perform common actions such verification, canonicalization or folding.

The same declaration mechanism can be used also for types and attributes, which are the other two categories of elements constituting a dialect.

The following example illustrates how to specify the assembly format of an operation expecting a variadic number of operands and producing zero results. The textual representation consists in the optional list of attributes, followed by the optional list of operands, a colon, and types of the operands.

Transformations
Transformations can always be performed directly on the IR, without having to rely on built-in coordination mechanisms. However, in order to ease both implementation and maintenance, MLIR provides an infrastructure for IR rewriting that is composed by different rewrite drivers. Each driver receives a set of objects named patterns, each of which has its own internal logic to match operations with certain properties. When an operation is matched, the rewrite process is performed and the IR is modified according to the logic within the pattern.

Dialect Conversion Driver
This driver operates according to the legality of existing operations, meaning that the driver receives a set of rules determining which operations have to be considered illegal and expects the patterns to match and convert them into legal ones. The logic behind those rules can be arbitrarly complex: it may be based just on the dialect to which the operations belong, but can also inspect more specific properties such as attributes or nested operations.

As the names suggests, this driver is tipically used for converting the operations of a dialect in operations belonging to a different one. In this scenario, the whole source dialect would be marked as illegal, the destination one as legal, and patterns for the source dialect operations would be provided. The dialect conversion framework also provides support for type conversion, which has to be performed on operands and results to convert them to the type system of the destination dialect.

MLIR allows for multiple conversion paths to be taken. Considering the example about the sum of matrices, a possible lowering strategy may be to generate to generate for-loops belonging to the scf dialect, obtaining code to be executed on CPUs: Another possible strategy, however, could have been to use the gpu dialect to generate code for GPUs:

Greedy Pattern Rewrite Driver
The driver greedily applies the provided patterns according to their benefit, until a fixed point is reached or the maximum number of iterations is reached. The benefit of a pattern is given by the pattern itself, with the relative order within the patterns list being used in case of equalities.

Traits and Interfaces
MLIR allows to apply existing optimizations (e.g., common subexpression elimination, loop-invariant code motion) on custom dialects by means of traits and interfaces. These two mechanisms enable transformation passes to operate on operations without knowing their actual implementation, relying only on some properties that traits or interfaces provide.

Traits are meant to be attached to operations without requiring any additional implementation. Their purpose is to indicate that the operation satisfies certain properties (e.g. having exactly two operands). Interfaces, instead, represent a more powerful tool through which the operation can be queried about some specific aspect, whose value may change between instances of the same kind of operation. An example of interface is the representation of memory effects: each operation that operates on memory may have such interface attached, but the actual effects may depend on the actual operands (e.g., a function call with arguments possibly being constants or references to memory).

Applications
The freedom in modeling intermediate representations enables MLIR to be used in a wide range of scenarios. This includes traditional languages, but also high-level synthesis , quantum computing and encryption. Machine learning applications also take advantage of built-in polyhedral compilation techniques, together with dialects targeting parallel architectures.