文章目录
- [Build System (Getting Started)](#Build System (Getting Started))
- [Running and Testing a Lowering](#Running and Testing a Lowering)
-
- [Two example programs](#Two example programs)
- [Lowerings and the math-to-funcs pass](#Lowerings and the math-to-funcs pass)
- 参考
Build System (Getting Started)
采用cmake,而不采用bazel
MLIR 自 LLVM 14 (2022) 起正式进入 LLVM 稳定发布系列,所有稳定版中 Clang 与 MLIR 完全配套、ABI 稳定.
| LLVM/Clang 版本 | 发布时间 | MLIR 状态 | 推荐度 |
|---|---|---|---|
| LLVM 19.x | 2026 最新 | MLIR 功能完整、稳定、bug 少 | ★★★★★ |
| LLVM 18.x | 2025 稳定 | 工业级稳定,Ubuntu 22.04 首选 | ★★★★★ |
| LLVM 17.x | 2024 稳定 | 成熟稳定 | ★★★★☆ |
| LLVM 16.x | 2023 稳定 | MLIR 核心框架成熟、API 稳定 | ★★★★☆ |
| LLVM 15.x | 2022 稳定 | 可用、API 基本稳定 | ★★★☆☆ |
| LLVM 14.x | 2022 稳定 | 首个官方稳定版(基础可用) | ★★★☆☆ |
| LLVM ≤ 13 | 更早 | MLIR 未正式进入稳定版 | ❌ 不推荐 |
LLVM 官方 APT 源
-
测试系统:ubuntu22.04
-
故采用llvm-18
安装密钥与源
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
sudo add-apt-repository "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-18 main"
sudo apt update安装 Clang + MLIR 开发包(完整工具链)
sudo apt install -y
clang-18 clang-tools-18 libclang-18-dev
llvm-18 llvm-18-dev llvm-18-tools
mlir-18-tools libmlir-18-dev
Running and Testing a Lowering
核心机制:传统编译器仅依靠单一中间表示(IR)完成程序转换、优化与机器码生成;而 MLIR 依托多类高低级方言与逐步降级的设计,拆分编译流程,分步完成代码下沉。
流程特点:MLIR 通过专属降级步骤逐层将高级方言转为低级方言,过程中穿插优化;且降级与优化流程无严格界限,统一为 IR 重写模块。
设计初衷:MLIR 诞生的重要原因是适配多面体优化、线性代数等专项优化需求;区别于传统编译器丢失循环高层结构、重构难度大的痛点,MLIR 在高级方言中保留程序原始结构以便高效优化,再于降级过程中舍弃结构。
Two example programs
例子:用32 位整数前导零计数的两段 MLIR 代码作为实例,入门理解 MLIR 基础语法与核心概念。
func.func @main(%arg0: i32) -> i32 {
%0 = math.ctlz %arg0 : i32
func.return %0 : i32
}
代码直接调用math方言的ctlz原生算子,一行完成前导零计算,写法简洁高层。
MLIR 基础语法规则
- 变量前缀%、函数前缀@;值后跟: 类型显式标注类型。
- 算子格式固定:方言.算子(如math.ctlz),不同方言自定义自身语法解析规则。
关键核心概念
- func 也是独立方言,函数定义func.func属于标准 MLIR 算子。
- Region(区域):大括号包裹的代码范围;Basic Block(基本块):单入口、单出口的指令块,对标编译器经典基础块,用于控制流、跳转逻辑。
在 MLIR 中,随着程序逐渐降低到某个最终后端目标,多个方言通常会在同一程序中共存
例子:该程序的第二个版本实现了 ctlz 函数的软件版本并调用了它
func.func @main(%arg0: i32) -> i32 {
%0 = func.call @my_ctlz(%arg0) : (i32) -> i32
func.return %0 : i32
}
func.func @my_ctlz(%arg0: i32) -> i32 {
%c32_i32 = arith.constant 32 : i32
%c0_i32 = arith.constant 0 : i32
%0 = arith.cmpi eq, %arg0, %c0_i32 : i32
%1 = scf.if %0 -> (i32) {
scf.yield %c32_i32 : i32
} else {
%c1 = arith.constant 1 : index
%c1_i32 = arith.constant 1 : i32
%c32 = arith.constant 32 : index
%c0_i32_0 = arith.constant 0 : i32
%2:2 = scf.for %arg1 = %c1 to %c32 step %c1 iter_args(%arg2 = %arg0, %arg3 = %c0_i32_0) -> (i32, i32) {
%3 = arith.cmpi slt, %arg2, %c0_i32 : i32
%4:2 = scf.if %3 -> (i32, i32) {
scf.yield %arg2, %arg3 : i32, i32
} else {
%5 = arith.addi %arg3, %c1_i32 : i32
%6 = arith.shli %arg2, %c1_i32 : i32
scf.yield %6, %5 : i32, i32
}
scf.yield %4#0, %4#1 : i32, i32
}
scf.yield %2#1 : i32
}
func.return %1 : i32
}
MLIR 代码拆分出自定义 my_ctlz 函数,主函数 main 调用它,纯软件逻辑实现 32 位整数前导零计数,不再直接使用高阶 math.ctlz 算子,是 MLIR 高阶算子向底层方言降级的示例。
核心算法
- 特殊判定:输入为 0 时直接返回 32(32 位全零),避免死循环;
- 核心逻辑:循环逐位左移输入数,判断是否变为负数(有符号数最高位为 1),累计移位次数,即为前导零个数
用到的 MLIR 方言
- arith:底层算术、常量定义、数值比较、加减、左移等基础运算;
- scf:结构化控制流,实现 if 分支、for 循环,通过 scf.yield 传出代码块 / 循环的返回值。
关键语法特性
- 多元组:%2:2 绑定双值结果,%4#1 读取元组第二个分量;
- 类型区分:i32 固定位宽整型,index 平台相关整型,专用于循环计数、边界、数组索引。
Lowerings and the math-to-funcs pass
例子:tests/ctlz.mlir
func.func @main(%arg0: i32) {
%0 = math.ctlz %arg0 : i32
func.return
}
mlir-opt 工具对任何 MLIR 代码进行无参数运行,它会解析代码,验证其格式是否正确,并进行一些轻微的规范化处理后将其输出。在本例中,它会将代码封装在一个 module 中,这是一种命名空间隔离机制。
➜ externals git:(main) mlir-opt-18 /home/wangji/code/mlir-tutorial/tests/ctlz.mlir
module {
func.func @main(%arg0: i32) {
%0 = math.ctlz %arg0 : i32
return
}
}
--convert-math-to-funcs=convert-ctlz:作用是 将 Math dialect 中的 ctlz 操作转换为具体的函数实现。
-
使用 arith 和 scf dialect 实现了 count leading zeros 的完整逻辑
➜ externals git:(main) mlir-opt-18 --convert-math-to-funcs=convert-ctlz /home/wangji/code/mlir-tutorial/tests/ctlz.mlir
module {
func.func @main(%arg0: i32) {
%0 = call @__mlir_math_ctlz_i32(%arg0) : (i32) -> i32
return
}
func.func private @__mlir_math_ctlz_i32(%arg0: i32) -> i32 attributes {llvm.linkage = #llvm.linkage<linkonce_odr>} {
%c32_i32 = arith.constant 32 : i32
%c0_i32 = arith.constant 0 : i32
%0 = arith.cmpi eq, %arg0, %c0_i32 : i32
%1 = scf.if %0 -> (i32) {
scf.yield %c32_i32 : i32
} else {
%c1 = arith.constant 1 : index
%c1_i32 = arith.constant 1 : i32
%c32 = arith.constant 32 : index
%c0_i32_0 = arith.constant 0 : i32
%2:2 = scf.for %arg1 = %c1 to %c32 step %c1 iter_args(%arg2 = %arg0, %arg3 = %c0_i32_0) -> (i32, i32) {
%3 = arith.cmpi slt, %arg2, %c0_i32 : i32
%4:2 = scf.if %3 -> (i32, i32) {
scf.yield %arg2, %arg3 : i32, i32
} else {
%5 = arith.addi %arg3, %c1_i32 : i32
%6 = arith.shli %arg2, %c1_i32 : i32
scf.yield %6, %5 : i32, i32
}
scf.yield %4#0, %4#1 : i32, i32
}
scf.yield %2#1 : i32
}
return %1 : i32
}
}
MLIR 降级逻辑
同一 ctlz(前导零计数)程序有高低两个版本:硬件支持ctlz指令时,直接将高层math.ctlz映射为机器指令;无硬件指令时,降级(Lowering) 会把该高阶运算,拆解翻译为func/arith/scf这类底层方言与基础操作,用软件逻辑实现。
mlir-opt 工具作用
MLIR 核心命令行工具,是执行各类编译 Pass(转换 / 降级)的入口:
无参数运行:解析、校验 MLIR 代码,格式化并包裹module做命名空间隔离;
携带参数(如--convert-math-to-funcs):执行内置降级 Pass,自动把高阶算子转为底层代码实现。
Pass 调度方式
每个 MLIR 转换 Pass 都有独立命令行参数,支持单独调用,也可通过--pass-pipeline串联多个 Pass、按顺序流水线执行。