MLIR入门

文章目录

  • [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、按顺序流水线执行。

参考

相关推荐
jjjava2.02 小时前
数据库事务:ACID特性与实战应用
java·开发语言·数据库
东北甜妹2 小时前
Redis Cluster 集群
数据库
(Charon)2 小时前
【kv存储】基于 C 的 KV 存储项目:主从单向同步是怎么实现的
数据库
Jul1en_2 小时前
【Redis】String 类型命令、编码方式与应用场景
数据库·redis·缓存
lifallen2 小时前
一篇文章讲透 Flink State
大数据·数据库·python·flink
赵渝强老师2 小时前
【赵渝强老师】MySQL数据库的分库与分表
数据库·mysql
XDHCOM2 小时前
利用MSSQL解析优化数据库性能,提升效率,驱动业务创新与稳定发展
数据库·sqlserver
·云扬·3 小时前
MySQL分区实战指南:从原理到落地的完整攻略
数据库·mysql
雨墨✘3 小时前
PHP怎么执行Shell命令_exec与shell_exec区别说明【说明】
jvm·数据库·python