Structure
🪙

Structure

AI keywords
Created
Jul 16, 2024 11:57 AM
MLIR
 

High-Level-Structure

MLIR(多级中间表示)的核心结构是一个类图的(graph-like)数据结构,其中节点被称为操作(Operations),边被称为值(Values)。每个值都是由一个操作或块参数(Block Argument)生成,并且具有由类型系统定义的值类型(Value Type)操作被包含在块(Blocks)中,块被包含在区域(Regions)中操作在所属块中是有序的,块在所属区域中也是有序的,尽管在某些区域中这种顺序可能没有语义上的重要性。此外,操作本身也可以包含区域,从而能够表示层次化结构。
操作可以表示多种不同的概念,从较高级的功能(如函数定义、函数调用、缓冲区分配、缓冲区的视图或切片、进程创建)到较低级的概念(如与平台无关的算术运算、平台特定的指令、配置寄存器和逻辑门)。这些不同的概念由MLIR中的不同操作来表示,并且可以任意扩展可用的操作集。
MLIR还提供了一个可扩展的框架,用于对操作进行转换,基于熟悉的编译器“传递(Passes)”概念。对任意操作集应用任意传递带来了显著的规模挑战,因为每个转换都可能需要考虑任何操作的语义。MLIR通过允许用特性(Traits)接口(Interfaces)抽象地描述操作语义,来应对这种复杂性,从而使转换能够更普遍地作用于操作。特性通常描述对有效IR(中间表示)的验证约束,从而能够捕获并检查复杂的不变量。
MLIR的一个明显应用是表示基于SSA(单一静态赋值)的中间表示,例如LLVM的核心IR,通过选择适当的操作类型来定义模块、函数、分支、内存分配,并且可以通过验证约束来确保SSA支配性(SSA Dominance)属性。MLIR中包含一系列方言(dialects),定义了这些结构。然而,MLIR的设计足够通用,可以表示其他类似编译器的数据结构,例如语言前端的抽象语法树(AST)、目标特定后端生成的指令,或高级综合工具中的电路。

Structure

IR的嵌套结构

Operations->Regions->Blocks->Operations->...
%results : 2 = "d.operation" (%arg0, %arg1) ({ // Regions belong to Ops and can have multiple blocks. Region ^block(%argument: !d.type): Block %value = "nested.operation" ( ) ({ // Ops can contain nested regions. Region "d.op"() : () -> () }) : () -> (!d.other_type) "consume.value" (%value) : (!d.other_type) -> () ^other_block: "d.terminator"() [^block(%argument : !d.type)] : () -> () → () }) : () -> (!d.type, !d.other_type)

IR(中间表示)是递归嵌套的。一个操作(Operation)可以包含一个或多个嵌套的区域(Regions),而每个区域实际上是一个块(Blocks)的列表,每个块又包含一个操作列表。
  • Operation: 包含若干个嵌套区域。
  • Region: 是一个块的列表。
  • Block: 包含一系列操作。

Value

  • Values: 每个value要么是一个BlockArgument,要么是一个Operation的结果
  • Operand: 每个Operation的输入Operand是对值的引用
notion image
  • 每个Value都与其User链接在了一起,是一个双链表的结构,可以快速的通过value拿到users或者通过user拿到operand
    • notion image

Operation

操作可以表示多种不同的概念,从较高级的功能(如函数定义、函数调用、缓冲区分配、缓冲区的视图或切片、进程创建)到较低级的概念(如与平台无关的算术运算、平台特定的指令、配置寄存器和逻辑门)
  • 语法
    • operation ::= op-result-list? (generic-operation | custom-operation) trailing-location? generic-operation ::= string-literal `(` value-use-list? `)` successor-list? dictionary-properties? region-list? dictionary-attribute? `:` function-type custom-operation ::= bare-id custom-operation-format op-result-list ::= op-result (`,` op-result)* `=` op-result ::= value-id (`:` integer-literal)? successor-list ::= `[` successor (`,` successor)* `]` successor ::= caret-id (`:` block-arg-list)? dictionary-properties ::= `<` dictionary-attribute `>` region-list ::= `(` region (`,` region)* `)` dictionary-attribute ::= `{` (attribute-entry (`,` attribute-entry)*)? `}` trailing-location ::= `loc` `(` location `)`
一个操作的内部表示相对简单:
  • 标识符:操作由一个唯一的字符串标识符表示(如 "dim""tf.Conv2d""x86.repmovsb" 等)。
  • 结果:操作可以返回零个或多个结果,每个结果都是一个独立的值。
  • 操作数:操作可以接受零个或多个操作数。
  • 属性:操作可以存储键值对形式的属性,称为“属性字典”。
  • 后继:操作可以有零个或多个后继块。
  • 区域:操作可以包含零个或多个嵌套的区域。

Blocks

块是操作的列表, 在具有静态单赋值控制流图(SSA-CFG)的区域中,每个块表示一个编译器的基本块,块内的指令按顺序执行,最后一个操作是终结符(terminator),它实现块之间的控制流跳转
block ::= block-label operation+ block-label ::= block-id block-arg-list? `:` block-id ::= caret-id caret-id ::= `^` suffix-id value-id-and-type ::= value-id `:` type value-id-and-type-list ::= value-id-and-type (`,` value-id-and-type)* block-arg-list ::= `(` value-id-and-type-list? `)`
 
  1. 块(Block):一个块由多个操作组成。在 SSA 控制流图区域中,每个块表示一个基本块,其中的操作顺序执行。块的最后一个操作必须是终结符(terminator),它负责控制流的转移。
  1. 块标签(Block Label):每个块都有一个块标签,用 ^ 后接一个标识符表示,如 ^bb0
  1. 块参数(Block Arguments):块可以带有参数,类似于函数参数。块参数由块的前驱块通过控制流传递。在 MLIR 中,这种块参数机制避免了传统 SSA 表示中的 PHI 节点复杂性,允许更直接地表示控制流依赖的值。
  1. 块的终结符(Terminator Operations):每个块的最后一个操作必须是一个终结符,它负责实现控制流跳转,如分支(Branch)或返回(Return)。对于只有一个块的区域,如果操作定义了 NoTerminator 属性,则该块可以不需要终结符。

Regions

region ::= `{` entry-block? block* `}` entry-block ::= operation+
在 MLIR 中,区域(Region)是一个有序的 MLIR 块(Block)列表。区域内部的语义不是由 IR 强制定义的,而是由包含该区域的操作(Operation)定义。当前,MLIR 定义了两种类型的区域:SSA, CFG 区域,用于描述块之间的控制流,以及图区域,不需要块之间的控制流。区域的种类通过 RegionKindInterface 描述
  • 无名称或地址:区域本身没有名称或地址,仅其包含的块具有这些特征。
  • 必须包含在操作中:区域必须嵌套在操作内部,没有类型或属性。
  • 入口块:区域的第一个块被称为“入口块”(entry block),其参数也是区域的参数。入口块不能作为任何其他块的后继。

Value Scoping

值的作用域,类似于C++
 区域同样提供了自然的作用域管理,使得在区域中定义的值无法逃逸到其外部的封闭区域。默认情况下,区域内部的操作可以引用定义在外部区域的值,只要在包含操作的上下文中合法即可。然而,这种行为可以通过特性(Traits)如 OpTrait::IsolatedFromAbove 或自定义验证器来限制

Arguments and Results

  • 参数:区域的第一个块的参数被视为该区域的参数。这些参数的来源由父操作的语义决定,通常对应于操作自身使用的一些值。
  • 结果:区域可以产生一个(可能为空的)值列表。区域的结果与操作的结果之间的关系也是由操作的语义来定义的。换句话说,区域的输出如何映射到操作的输出取决于操作本身的定义。