当前状态:开发中,默认未启用
构建
前置依赖
CMake变量
推荐开发实践
设计
使用场景
可组合模块
子模块
加载器
基于C-API实现
核心IR的所有权管理
核心IR的可选参数与参数顺序
用户级API
上下文管理
查看IR对象
创建IR对象
编码风格
属性 vs get*()方法
__repr__方法
驼峰式(CamelCase)vs 蛇形(snake_case)
优先使用伪容器
为通用操作提供一站式辅助函数
测试
示例FileCheck测试
与ODS集成
生成_{方言命名空间}_ops_gen.py包装模块
扩展包装模块的搜索路径
包装模块代码组织
为方言(Dialect)提供Python绑定
操作(Operation)
属性(Attribute)与类型(Type)
Pass(Pass)
其他功能
在Python中扩展MLIR
方言(Dialect)
Pass(Pass)
重写模式(Rewrite Pattern)
无全局解释器锁(无GIL/Free-threading)支持
较新版本的Python3
安装mlir/python/requirements.txt中指定的Python依赖
MLIR_ENABLE_BINDINGS_PYTHON:布尔型(BOOL)
启用Python绑定的编译,默认关闭(OFF)。
Python3_EXECUTABLE:字符串型(STRING)
指定用于LLVM编译的Python可执行文件,包括为Python绑定确定头文件/链接标志。在存在多个Python版本的系统上,强烈建议显式设置为首选的python3可执行文件。
建议使用Python虚拟环境。创建虚拟环境的方式有多种,推荐以下两种:
# 确认当前使用的python为预期版本。多Python版本系统中可能带版本后缀;# 在Linux和macOS同时存在python2/python3的系统中,建议使用python3which pythonpython -m venv ~/.venv/mlirdevsource ~/.venv/mlirdev/bin/activate若已安装uv,可通过以下命令创建虚拟环境(示例指定Python 3.12):
uv venv ~/.venv/mlirdev --seed -p 3.12source ~/.venv/mlirdev/bin/activate可按需修改Python版本(-p参数)——若请求的Python解释器未安装,uv会尝试下载,除非指定--no-python-downloads。uv安装方式参考官方文档:https://docs.astral.sh/uv/getting-started/installation/
# 许多LTS发行版自带的pip版本过旧,无法下载部分平台最新二进制包# 可通过python -m pip --version查看pip版本;Linux环境需核对最低版本要求:# https://github.com/pypa/manylinux# 建议升级pippython -m pip install --upgrade pip# 此时python命令指向虚拟环境,包将安装到该环境python -m pip install -r mlir/python/requirements.txt# uv创建的虚拟环境可执行:uv pip install -r mlir/python/requirements.txt# 执行cmake、ninja等编译命令# 运行MLIR测试,例如仅通过ninja运行Python绑定测试:ninja check-mlir-python交互式使用时,只需将编译目录下的tools/mlir/python_packages/mlir_core/加入PYTHONPATH,典型配置:
export PYTHONPATH=$(cd build && pwd)/tools/mlir/python_packages/mlir_core若已执行安装(如ninja install),所有启用项目的Python包会位于安装目录的python_packages/下(如python_packages/mlir_core)。官方发行版使用更专用的构建配置。
MLIR Python绑定主要有两大核心场景:
支持用户期望安装LLVM/MLIR后,可直接import mlir并开箱即用纯Python API。
下游集成方希望将部分API纳入私有命名空间或定制库,通常与其他Python原生组件混合使用。
为支持场景2,Python绑定被组织为可组合模块,下游集成方可按需引入并重新导出到自身命名空间。这要求遵循以下设计原则:
将nb::module的构造/填充与NB_MODULE全局构造函数分离。
为仅C++的包装类引入头文件,便于其他相关C++模块交互。
将依赖可选组件的初始化逻辑分离到独立模块/依赖项(当前registerAllDialects等属于此类)。
共享库链接、分发等关联问题均受此影响。将代码拆分为可组合模块(而非单一cpp文件),可灵活应对后续各类需求。此外,pybind的模板元编程编译耗时随翻译单元内定义数量增加,拆分多个翻译单元可显著降低大表面积API的编译时间。
C++代码库中绝大多数对象归属于mlir命名空间。为实现模块化并提升Python绑定可读性,定义了与MLIR功能单元目录结构大致对应的子包,例如:
mlir.ir
mlir.passes(pass是Python保留字)
mlir.dialect
mlir.execution_engine(除命名空间隔离外,此类重量级/可选组件需独立封装)
此外,隐含可选依赖的初始化函数应放在带下划线的(名义上私有)模块(如_init)中并单独链接。这让下游集成方可完全定制默认包含的组件,覆盖方言注册、Pass注册等场景。
LLVM/MLIR是复杂的Python原生项目,可能与其他复杂原生扩展共存。因此,原生扩展(.so/.pyd/.dylib)以私有顶层符号(_mlir)导出,同时在mlir/_cext_loader.py及同级文件中提供少量Python代码,用于加载并重新导出。
这种拆分提供了在共享库加载到Python运行时前准备环境的代码入口,也为一次性初始化代码提供了独立于模块构造函数的执行位置。
建议尽量避免使用__init__.py,直到代表独立组件的叶子包。核心规则:__init__.py的存在会导致无法将该层级及以下命名空间拆分到不同目录、部署包、wheel包等。
更多说明参考:https://packaging.python.org/guides/packaging-namespace-packages/
Python API应尽可能基于C-API封装,尤其是核心、与方言无关的部分。这种绑定方式可规避跨C++ ABI边界的分发难题,同时解决基于RTTI的模块(pybind派生对象)与非RTTI多态C++代码(LLVM默认编译模式)混合使用的棘手问题。
核心IR中有若干顶层类型由Python侧引用强持有:
PyContext(mlir.ir.Context)
PyModule(mlir.ir.Module)
PyOperation(mlir.ir.Operation)——有特殊说明
所有其他对象均为依赖对象,会保留对最近顶层容器对象的反向引用(保活)。依赖对象分为两类:a) 唯一化对象(生命周期与上下文一致);b) 可变对象。可变对象需要额外机制跟踪其背后C++实例是否失效(通常因IR修改、删除、批量操作导致)。
以下类型支持作为上下文管理器绑定到当前线程:
PyLocation(loc: mlir.ir.Location = None)
PyInsertionPoint(ip: mlir.ir.InsertionPoint = None)
PyMlirContext(context: mlir.ir.Context = None)
为支持函数参数的可组合性,这些类型作为参数时必须置于末尾,且按上述顺序、使用指定名称(通常对应特殊场景下显式传参的优先级),默认值为py::none(),通过手动/自动转换解析显式传参或线程上下文管理器的值(如DefaultingPyMlirContext、DefaultingPyLocation)。
设计依据:Python中右侧尾部关键字参数可组合性最强,支持参数透传、默认值等多种用法。保持函数签名可组合,能更轻松构建领域特定语言(DSL)与高层API,减少冗余样板代码。
统一遵循该规则可实现极简IR构造风格:极少需要显式指定上下文、位置(Location)、插入点(InsertionPoint),需要精细控制时可自由指定。
PyOperation特殊之处在于可处于顶层或依赖状态,生命周期单向:操作可创建为分离状态(顶层),加入另一操作后则终身为依赖状态。若操作被加入仍处于分离状态的传递父节点,需在状态转换时额外处理(所有新增子节点初始归属最外层分离操作,外层操作加入已附着操作后,子节点重新归属到容器模块)。
基于有效性与父节点管理需求,PyOperation持有区域(Region)与块(Block),且仅操作允许处于分离状态。
注意:多个PyOperation对象(Python层对象)可别名指向同一mlir::Operation。例如py_op1与py_op2包装同一mlir::Operation op,对op执行Pass后,通过任一Python对象遍历MLIR抽象语法树(AST)均会看到相同结果,此用法安全且受支持。不支持的场景:存在多个Python包装对象时使操作失效,随后操作这些包装对象。
例如py_op1/py_op2包装根节点py_op3下的同一操作,py_op3被修改导致该操作被擦除,py_op1/py_op2即变为“未定义”状态,任何操作均被严格禁止。符号表(SymbolTable)修改同理,视为对根符号表操作的修改。
最佳实践建议按以下结构编码:
先查询/操作各类Python包装对象(py_op1、py_op2、py_op3等);
再通过单一根对象执行AST修改/操作擦除等;
使所有查询节点失效(如op._set_invalid())。
理想情况下在函数体内完成,使步骤3对应函数结束,避免Python包装对象泄漏/过度存活。简言之:按嵌套层级修改,先修改叶子节点再向上层操作,极少情况下在修改父节点后再查询嵌套操作。
C/C++ API允许Region/Block分离,Python API消除该可能性以简化所有权模型,使Region/Block完全依赖所属操作管理。Python的Region/Block实例与底层MlirRegion/MlirBlock的别名无害,且这些对象不会像操作一样在上下文内驻留。
若后续需重新引入分离的Region/Block,可新增DetachedRegion等类实现,避免复杂度。当前设计无需为Region/Block维护全局存活列表,未来若需操作本地列表可按需扩展。
绑定依赖Python上下文管理器(with语句)简化IR对象创建与处理,省略重复传参(如MLIR上下文、操作插入点、位置)。上下文管理器为同线程内后续所有绑定调用设置默认对象,特定调用可通过专用关键字参数覆盖默认值。
MLIR上下文是持有属性与类型的顶层实体,几乎所有IR结构都会引用它,同时在C++层提供线程安全保障。Python绑定中,MLIR上下文也是Python上下文管理器,示例:
from mlir.ir import Context, Modulewith Context() as ctx:# 使用ctx作为上下文构造IR# 从字符串解析MLIR模块需要上下文 Module.parse("builtin.module {}")引用上下文的IR对象通常通过.context属性访问上下文。多数IR构造函数要求以某种方式传入上下文:属性与类型可从包含的属性/类型提取上下文;操作统一从位置(Location)提取上下文;无法从参数提取时,API要求传入context关键字参数,未提供或为None(默认)时,从绑定维护的当前线程隐式上下文栈查找,无上下文则抛出错误。
上下文管理器内外均可手动指定MLIR上下文:
from mlir.ir import Context, Modulestandalone_ctx = Context()with Context() as managed_ctx:# 在managed_ctx中解析模块 Module.parse("...")# 在standalone_ctx中解析模块(覆盖上下文管理器) Module.parse("...", context=standalone_ctx)# 不使用上下文管理器解析模块Module.parse("...", context=standalone_ctx)只要存在引用上下文的IR对象,上下文对象就会保持存活。
构造MLIR操作需要两个关键信息:
插入点:指定操作创建在IR的区域/块/操作结构中的位置(通常在另一操作前后或块末尾);可省略,此时操作创建为分离状态。
位置:包含操作来源的用户可读信息(如文件/行/列),必须提供,且持有MLIR上下文引用。
两者均可通过上下文管理器或操作构造函数的关键字参数(ip/loc)显式传入,上下文管理器内外均生效。
from mlir.ir import Context, InsertionPoint, Location, Module, Operationwith Context() as ctx: module = Module.create()# 准备向模块体插入操作,指定操作来源为f.mlir文件42行1列with InsertionPoint(module.body), Location.file("f.mlir", line=42, col=1):# 操作插入到模块体末尾,使用上下文管理器设置的位置 Operation(<...>)# 操作插入到模块体末尾(前一操作之后),使用关键字参数指定位置 Operation(<...>, loc=Location.file("g.mlir", line=1, col=10))# 操作插入到块开头而非末尾 Operation(<...>, ip=InsertionPoint.at_block_begin(module.body))构造位置(Location)需要MLIR上下文,可从当前线程上下文管理器获取,或显式传入:
from mlir.ir import Context, Location# 同一with语句创建上下文与对应位置with Context() as ctx, Location.file("f.mlir", line=42, col=1, context=ctx):pass位置由上下文持有,只要被Python代码(传递)引用就会存活。
与位置不同,构造操作时可省略插入点(或设为None/False),此时操作创建为分离状态——未加入其他操作的区域,由调用者持有。顶层IR容器(如模块)通常为此状态。操作包含的区域、块、值会反向引用并保活该操作。
查看IR是Python绑定的核心功能,可遍历IR的操作/区域/块结构,检查操作属性、值类型等特征。
操作有两种表示形式:
通用Operation类:适合未注册操作的通用处理;
OpView特定子类:提供更语义化的操作属性访问器。
OpView子类可通过.operation属性获取Operation;Operation可通过.opview属性获取对应OpView(前提是加载对应方言的Python模块)。默认遍历IR树时返回OpView形式。
可通过Pythonisinstance判断操作类型:
operation = <...>opview = <...>ifisinstance(operation.opview, mydialect.MyOp):passifisinstance(opview, mydialect.MyOp):pass可通过属性检查操作组成部分:
attributes:操作属性集合,支持字典与序列下标访问(如operation.attributes["value"]/operation.attributes[0]),按序列遍历属性时不保证顺序。
operands:操作操作数序列集合。
results:操作结果序列集合。
regions:操作附加区域序列集合。
操作数与结果对象拥有.types属性,存储对应值的类型序列。
from mlir.ir import Operationoperation1 = <...>operation2 = <...>if operation1.results.types == operation2.operand.types:pass特定操作的OpView子类提供更简洁的属性访问器,例如命名属性、操作数、结果可直接作为子类属性访问(operation.const_value替代operation.attributes["const_value"])。若名称为Python保留字,添加下划线后缀。
操作本身可迭代,按顺序访问附加区域:
from mlir.ir import Operationoperation = <...>for region in operation: do_something_with_region(region)区域在概念上是块的序列,Region对象可迭代访问块,也可使用.blocks属性。
# 区域可直接迭代访问块for block1, block2 inzip(operation.regions[0], operation.regions[0].blocks)assert block1 == block2块包含操作序列,另有若干附加属性。Block对象可迭代访问内部操作,.operations属性效果相同。块的参数列表可通过.arguments序列集合访问。
Python绑定中,块与区域归属于父操作并保活该操作,可通过.owner属性访问所属操作。
属性与类型大多是上下文持有的不可变对象,有两种表示形式:
不透明Attribute/Type对象:支持打印与比较;
具体子类:可访问属性/类型的内部特征。
Attribute/Type对象可通过子类构造函数转为具体子类,类型不匹配时抛出ValueError:
from mlir.ir import Attribute, Typefrom mlir.<dialect> import ConcreteAttr, ConcreteTypeattribute = <...>type = <...>try: concrete_attr = ConcreteAttr(attribute) concrete_type = ConcreteType(type)except ValueError as e:# 处理类型不匹配具体属性/类型类提供静态isinstance方法,判断不透明对象能否向下转型:
from mlir.ir import Attribute, Typefrom mlir.<dialect> import ConcreteAttr, ConcreteTypeattribute = <...>type = <...># 无需处理错误if ConcreteAttr.isinstance(attribute): concrete_attr = ConcreteAttr(attribute)if ConcreteType.isinstance(type): concrete_type = ConcreteType(type)与操作不同,遍历IR时默认返回不透明Attribute/Type,需要手动向下转型。
具体属性/类型类通常将内部特征暴露为Python只读属性,例如张量类型的元素类型可通过.element_type访问。
MLIR值分为两类:块参数(BlockArgument)与操作结果(OpResult)。值的处理与属性/类型类似,有两种表示:
通用Value对象;
具体BlockArgument/OpResult对象。
通用Value提供比较、类型访问、打印等基础功能;具体子类可访问定义块/操作及值在其中的位置。遍历IR时默认返回通用Value,向下转型方式与属性/类型一致:
from mlir.ir import BlockArgument, OpResult, Valuevalue = ...# 转为具体值子类try: concrete = BlockArgument(value)except ValueError:# 值必为块参数或操作结果,此处不会再抛出ValueError concrete = OpResult(value)MLIR接口是无需知晓操作具体类型、仅依赖部分特征即可与IR交互的机制。操作接口在Python中以与C++同名的类提供,可从以下对象构造:
Operation/OpView子类对象:可调用所有接口方法;
OpView子类+上下文:仅可调用静态接口方法(无关联操作)。
构造时若操作类在指定上下文未实现该接口,抛出ValueError。MLIR上下文可通过外层上下文管理器设置。
from mlir.ir import Context, InferTypeOpInterfacewith Context(): op = <...># 尝试将操作转为接口try: iface = InferTypeOpInterface(op)except ValueError:print("Operation does not implement InferTypeOpInterface.")raise# 从Operation/OpView构造的接口对象可调用所有方法 iface.someInstanceMethod()# 也可从OpView子类构造接口,需指定上下文(显式/上下文管理器)try: iface = InferTypeOpInterface(some_dialect.SomeOp)except ValueError:print("SomeOp does not implement the interface.")raise# 从类构造的接口对象调用实例方法会抛出TypeErrortry: iface.someInstanceMethod()except TypeError:pass# 仍可调用静态接口方法 iface.inferOpReturnTypes(<...>)若接口对象从Operation/OpView构造,可通过.operation/.opview属性访问原对象。
当前Python绑定仅提供部分操作接口,属性与类型接口暂未支持。
Python绑定支持IR的创建与修改。
创建操作需要位置(Location)与可选插入点(InsertionPoint)。批量创建操作时,使用上下文管理器指定位置与插入点更便捷。
具体操作可通过对应OpView子类构造函数创建,默认构造函数参数:
操作结果类型序列(可选,results);
操作操作数值序列/产生该值的另一操作(可选,operands);
操作属性字典(可选,attributes);
后继块序列(可选,successors);
附加区域数量(默认0,regions);
关键字参数loc:操作位置,未指定则使用最近上下文管理器的位置,无则抛异常;
关键字参数ip:插入点,未指定则使用最近上下文管理器的插入点,无则创建为分离状态。
多数操作会自定义构造函数,仅保留相关参数。例如无结果操作可省略results,结果类型可从操作数类型唯一推导的操作也可省略。示例:内置函数操作可通过函数名、参数类型、结果类型元组构造。
from mlir.ir import Context, Modulefrom mlir.dialects import builtinwith Context(): module = Module.create()with InsertionPoint(module.body), Location.unknown(): func = func.FuncOp("main", ([], []))也可通过通用Operation.create基于操作标准字符串名构造,参数与OpView默认构造函数一致。不推荐此方式,仅用于通用操作处理。
from mlir.ir import Context, Modulefrom mlir.dialects import builtinwith Context(): module = Module.create()with InsertionPoint(module.body), Location.unknown():# 通用方式创建操作 func = Operation.create("func.func", results=[], operands=[], attributes={"function_type":TypeAttr.get(FunctionType.get([], []))}, successors=None, regions=1)# 若可用,结果会向下转型为具体OpView子类assertisinstance(func, func.FuncOp)区域在C++侧构造操作时创建,Python中不可直接构造,且不允许脱离操作存在(C++支持分离区域)。
块可在指定区域内创建,并插入到同区域另一块之前/之后,使用Block类的create_before()/create_after()方法,或静态方法create_at_start()。块不允许脱离区域存在(C++支持分离块)。
from mlir.ir import Block, Context, Operationwith Context(): op = Operation.create("generic.op", regions=1)# 在区域创建第一个块 entry_block = Block.create_at_start(op.regions[0])# 创建后续块 other_block = entry_block.create_after()块可用于创建插入点(InsertionPoint),指向块开头、末尾或终止符之前。OpView子类通常提供.body属性用于构造插入点,例如内置Module与FuncOp提供.body与.add_entry_blocK()。
属性与类型可通过上下文(Context)或已持有上下文的其他属性/类型对象创建。为标识其由上下文持有,通过具体属性/类型类的静态get方法获取,参数为构造所需数据,无法从其他参数推导上下文时需显式传入context关键字参数。
from mlir.ir import Context, F32Type, FloatAttr# 属性与类型需要MLIR上下文,直接传入或通过其他上下文持有对象获取ctx = Context()f32 = F32Type.get(context=ctx)pi = FloatAttr.get(f32, 3.14)# 可使用外层上下文管理器设置的上下文with Context(): f32 = F32Type.get() pi = FloatAttr.get(f32, 3.14)部分属性提供额外构造方法以提升可读性:
from mlir.ir import Context, IntegerAttr, IntegerTypewith Context(): i8 = IntegerType.get_signless(8) IntegerAttr.get(i8, 42)内置属性常可从结构相似的Python类型直接构造,例如ArrayAttr从属性序列构造,DictAttr从字典构造:
from mlir.ir import ArrayAttr, Context, DictAttr, UnitAttrwith Context(): array = ArrayAttr.get([UnitAttr.get(), UnitAttr.get()]) dictionary = DictAttr.get({"array": array, "unit": UnitAttr.get()})可通过register_attribute_builder注册操作创建时使用的自定义属性构造器,例如I32Attr:
@register_attribute_builder("I32Attr")def_i32Attr(x: int, context: Context):return IntegerAttr.get( IntegerType.get_signless(32, context=context), x)注册后,创建带I32Attr的操作可直接写:
foo.Op(30)替代原写法:
foo.Op(IntegerAttr.get(IndexType.get_signless(32, context=context), 30))注册基于ODS名称,纯Python实现。每个ODS属性类型仅允许注册一个自定义构造器(如I32Attr对应一个,可映射多个底层IntegerAttr类型)。
MLIR核心部分的Python绑定应与底层C++结构基本同构,同时兼顾实用性与Python风格。
优先将getContext()、getName()、isEntryBlock()等简单方法转为Python只读属性(如context)。绑定代码中只需使用def_prop_ro而非def,显著提升Python侧使用体验。
为对象实现友好的打印表示效果极佳,合理的打印形式绑定到__repr__方法(并通过doctest验证)可大幅提升开发效率。
函数/方法/属性使用蛇形(snake_case),类使用驼峰式(CamelCase)。遵循PEP 8规范,让API更贴合Python生态。
许多核心IR结构直接在实例上提供计数、迭代器方法,优先将其提升为专用伪容器。
例如区域内的块,不推荐:
region = ...for block in region:pass推荐:
region = ...for block in region.blocks:passprint(len(region.blocks))print(region.blocks[0])print(region.blocks[-1])避免暴露STL风格标识符(front、back等),在绑定中转为合适的魔法方法与迭代器包装。
注意适度使用,若遇到语义复杂的场景(如块参数的查找与修改难以合理建模),直接镜像C/C++ API即可。
鼓励封装跨多个底层实体的一站式辅助函数,例如为Context添加parse_asm方法,避免显式构造SourceMgr。一站式辅助函数可与更完整的底层映射共存。
测试代码放在mlir/test/python目录,通常为带lit运行指令的.py文件。
使用基于lit与FileCheck的测试方式:
生成式测试(产生IR):定义Python模块构造/打印IR,通过管道传给FileCheck。
解析测试:使用原始常量与合适的parse_asm调用,保持模块自包含。
文件I/O代码通过临时文件处理,不依赖测试模块外的文件产物/路径。
方便起见,非生成式API交互也用相同机制,按需打印并CHECK验证。
# RUN: %PYTHON %s | mlir-opt -split-input-file | FileCheck# TODO: 后续移入测试工具类defprint_module(f): m = f()print("// -----")print("// TEST_FUNCTION:", f.__name__)print(m.to_asm())return f# CHECK-LABEL: TEST_FUNCTION: create_my_op@print_moduledefcreate_my_op(): m = mlir.ir.Module() builder = m.new_op_builder()# CHECK: mydialect.my_operation ... builder.my_op()return mMLIR Python绑定与基于TableGen的ODS系统集成,为MLIR方言与操作提供易用的包装层,集成要点如下(细节略):主仓库中mlir.dialects下的构建规则与Python代码为标准使用方式。
用户需提供{方言命名空间}.py(或带__init__.py的同级目录)作为入口。
每个映射到Python的方言需要生成对应的_{方言命名空间}_ops_gen.py包装模块,通过mlir-tblgen处理Python绑定专用TableGen包装文件实现,该文件包含样板代码与方言专属.td文件。以Func方言(命名空间特例为func)为例:
#ifndef PYTHON_BINDINGS_FUNC_OPS#define PYTHON_BINDINGS_FUNC_OPSinclude "mlir/Dialect/Func/IR/FuncOps.td"#endif // PYTHON_BINDINGS_FUNC_OPS主仓库中通过CMake函数declare_mlir_dialect_python_bindings编译包装层,执行:
mlir-tblgen -gen-python-op-bindings -bind-dialect={方言命名空间} \ {PYTHON_BINDING_TD_FILE}生成的操作类需像C++生成头文件一样,在{方言命名空间}.py中导入:
from ._my_dialect_ops_gen import *Python绑定查找包装模块时会查询dialect_search_path。主仓库中该路径硬编码包含mlir.dialects模块(上述构建规则输出包装层的位置)。外部方言可通过以下代码将模块加入搜索路径:
from mlir.dialects._ods_common import _cext_cext.globals.append_dialect_search_prefix("myproject.mlir.dialects")包装模块TableGen生成器输出:
_Dialect类(继承mlir.ir.Dialect),带DIALECT_NAMESPACE属性;
每个操作的{操作名}类(继承mlir.ir.OpView);
上述类的注册装饰器。
为避免命名冲突,包装模块的所有内部名称以_ods_为前缀。
每个具体OpView子类定义若干公开属性:
OPERATION_NAME:字符串类型,完整操作名(如math.absf);
__init__方法:若操作定义/推导了默认构造器则实现;
每个操作数/结果的@property getter(无名对象自动生成名称);
每个声明属性的@property getter/setter/deleter。
同时输出供子类化与自定义的私有属性(默认情况使用OpView默认值,省略这些属性):
_ODS_REGIONS:区域数量与类型说明,当前为(最小区域数,无可变参数区域)元组。API仅做轻量校验,核心用于记录默认构造与区域访问器生成所需信息。
_ODS_OPERAND_SEGMENTS/_ODS_RESULT_SEGMENTS:黑盒值,标记操作数/结果的可变参数结构,用于OpView._ods_build_default解析含可变参数的操作数/结果列表。
当前仅将单一默认构造器映射到__init__方法,意图是该__init__对应C++生成的最具体构造器,当前仅实现通用形式:
每个声明结果对应一个参数:
单值结果:接受mlir.ir.Type;
可变结果:接受List[mlir.ir.Type]。
每个声明操作数/属性对应一个参数:
单值操作数:接受mlir.ir.Value;
可变操作数:接受List[mlir.ir.Value];
属性:接受mlir.ir.Attribute。
尾部专用可选关键字参数:
loc:显式位置,默认使用线程绑定位置,无则抛错;
ip:显式插入点,默认使用线程绑定插入点。
每个OpView继承build_generic方法,支持通过(可变参数嵌套)results/operands序列构造,用于Python暂不支持的操作的默认构造,代价是签名通用化。
构建系统为每个带Python绑定的方言生成_{方言命名空间}_ops_gen.py,常需以生成类为基础自定义,绑定提供便捷扩展机制:常规继承+OpView注册。例如arith.constant的默认构造器:
classConstantOp(_ods_ir.OpView): OPERATION_NAME = "arith.constant" _ODS_REGIONS = (0, True)def__init__(self, value, *, loc=None, ip=None): ...要求value为TypedAttr(如IntegerAttr/FloatAttr),自然扩展是支持传入MLIR类型与Python值,实例化对应TypedAttr:
from typing importUnionfrom mlir.ir importType, IntegerAttr, FloatAttrfrom mlir.dialects._arith_ops_gen import _Dialect, ConstantOpfrom mlir.dialects._ods_common import _cext@_cext.register_operation(_Dialect, replace=True)classConstantOpExt(ConstantOp):def__init__( self, result: Type, value: Union[int, float], *, loc=None, ip=None):ifisinstance(value, int):super().__init__(IntegerAttr.get(result, value), loc=loc, ip=ip)elifisinstance(value, float):super().__init__(FloatAttr.get(result, value), loc=loc, ip=ip)else:raise NotImplementedError(f"Building `arith.constant` not supported for {result=}{value=}")扩展后可直接构造arith.constant:
from mlir.ir import F32Typea = ConstantOpExt(F32Type.get(), 42.42)b = ConstantOpExt(IntegerType.get_signless(32), 42)扩展机制三大要点:
ConstantOpExt直接继承生成的ConstantOp;
最简场景只需调用父类初始化方法super().__init__(...);
用@_cext.register_operation(_Dialect, replace=True)装饰类,将ConstantOpExt注册为mlir.ir.Operation.opview返回的首选OpView,必须指定replace=True。
复杂场景需通过OpView.build_generic显式构造(同生成构造器逻辑),即调用OpView.build_generic并将结果传入OpView.__init__,需调用祖父类方法,示例:
from mlir.dialects._scf_ops_gen import _Dialect, ForOpfrom mlir.dialects._ods_common import _cext@_cext.register_operation(_Dialect, replace=True)classForOpExt(ForOp):def__init__(self, lower_bound, upper_bound, step, iter_args, *, loc=None, ip=None): ...super(ForOp, self).__init__(self.build_generic(...))此处通过super(ForOp, self).__init__调用OpView.__init__,也可直接写OpView.__init__。
Python绑定设计支持MLIR开放方言生态,方言可作为mlir.dialects子模块暴露给Python,与其他绑定互通。
仅含操作的方言只需提供操作的Python API,大部分样板代码可从ODS生成。
含属性与类型的方言需通过C API对接,无通用创建机制。
Pass需在上下文注册,才能在文本格式的Pass管理器中使用,可在Python模块加载时完成。
其他功能可参考属性/类型,暴露对应C API并封装Python API。
方言操作通过为通用mlir.ir.Operation包装操作专属构造函数与属性提供Python支持,无需单独实现C API。
ODS定义的操作通过mlir-tblgen -gen-python-op-bindings -bind-dialect=<方言命名空间>从声明式描述生成Python API。只需新建.td文件包含原ODS定义,作为mlir-tblgen输入源,此类文件放在python/mlir/dialects/。
mlir-tblgen输出按约定命名为_<方言命名空间>_ops_gen.py。生成的操作类可按上述方式扩展。MLIR提供CMake函数自动化生成。
最后创建python/mlir/dialects/<方言命名空间>.py或python/mlir/dialects/<方言命名空间>/__init__.py,导入生成文件,支持import mlir.dialects.<方言命名空间>。
方言属性与类型分别作为mlir.ir.Attribute与mlir.ir.Type的子类提供Python支持。属性/类型的Python API必须对接对应的C API(需先实现)。
Attribute/Type子类的绑定可通过include/mlir/Bindings/Python/PybindAdaptors.h或include/mlir/Bindings/Python/NanobindAdaptors.h工具定义,模仿pybind11/nanobind API。
绑定放在独立模块,工具自动实现C API句柄MlirAttribute/MlirType与Python对象的转换,绑定实现可直接使用C API句柄。
绑定的方法与属性遵循上述风格原则。
方言的属性/类型绑定可放在lib/Bindings/Python/Dialect<名称>.cpp,编译为独立“Python扩展”库,放到python/mlir/_mlir_libs,Python运行时加载。MLIR提供CMake函数自动化生成。
该库需在方言主文件(python/mlir/dialects/<方言命名空间>.py/__init__.py)中import,确保从Python加载方言时类型可用。
方言专属Pass需在上下文注册,才能在Python的Pass管理器中使用,依赖从文本解析Pass管道的API。
实现方式:新建nanobind模块(lib/Bindings/Python/<方言>Passes.cpp),调用C API注册(需先实现)。
TableGen声明式定义的Pass,通过mlir-tblgen -gen-pass-capi-header与-mlir-tblgen -gen-pass-capi-impl自动生成C API。
nanobind模块编译为独立“Python扩展”库,可在方言主文件导入,或放到独立passes子模块(python/mlir/dialects/<方言命名空间>/passes.py),避免随方言默认加载。
方言的非IR对象/Pass功能(如辅助函数)可参考属性/类型暴露给Python。需先实现对应C API,再通过pybind11+PybindAdaptors.h或nanobind+NanobindAdaptors.h封装,对接Python API。
绑定可放在独立模块或与属性/类型同模块,随方言加载。
MLIR Python绑定支持在Python中定义自定义组件,主要包括方言、Pass、重写模式,各部分实现方式如下。
可通过Python的IRDL方言绑定定义方言,IRDL绑定提供load_dialects函数,将包含irdl.dialect操作的MLIR模块转为MLIR方言。详情参考IRDL方言文档。
可通过PassManager.add API将Python可调用对象定义为Pass,内部包装为mlir::Pass,PassManager.run时作为Pass管道执行。
可调用对象的op参数为当前待转换操作,pass_参数提供当前Pass对象的访问,支持signalPassFailure()等操作。
可调用对象的生命周期至少延长到PassManager销毁。示例:
defdemo_pass(op, pass_):# 处理传入的oppasspm = PassManager('any')pm.add(demo_pass)pm.add('some-cpp-defined-passes')...pm.run(some_op)可通过mlir.rewrite.RewritePatternSet的add方法注册重写模式,参数为待重写的操作类型与定义“匹配-重写”逻辑的Python可调用对象。
Python可调用对象需保证:匹配成功才执行重写,对应返回值可转为False。
RewritePatternSet可通过freeze方法转为FrozenRewritePatternSet,通过贪心模式驱动应用到操作:apply_patterns_and_fold_greedily。典型用法:
defto_muli(op, rewriter):with rewriter.ip: new_op = arith.muli(op.lhs, op.rhs, loc=op.location) rewriter.replace_op(op, new_op)patterns = RewritePatternSet()patterns.add(arith.AddIOp, to_muli) # 将arith.addi重写为arith.mulipatterns.add(...)frozen = patterns.freeze()module = ...apply_patterns_and_fold_greedily(module, frozen)PDL方言绑定支持在Python中定义生成重写模式,mlir.rewrite.PDLModule接收包含pdl.pattern操作的模块,通过freeze方法转为FrozenRewritePatternSet,再通过贪心重写驱动应用到操作。详情参考PDL方言文档。
无GIL支持指CPython解释器(≥3.13)关闭全局解释器锁,详情参考PEP-703与Python无GIL指南。
MLIR Python绑定兼容无GIL(存在例外),安全场景:独立上下文的多线程操作。安全用法示例:
# python3.13t example.pyimport concurrent.futuresimport mlir.dialects.arith as arithfrom mlir.ir import Context, Location, Module, IntegerType, InsertionPointdeffunc(py_value):with Context() as ctx: module = Module.create(loc=Location.file("foo.txt", 0, 0)) dtype = IntegerType.get_signless(64)with InsertionPoint(module.body), Location.name("a"): arith.constant(dtype, py_value)return modulenum_workers = 8with concurrent.futures.ThreadPoolExecutor(max_workers=num_workers) as executor: futures = []for i inrange(num_workers): futures.append(executor.submit(func, i))assertlen(list(f.result() for f in futures)) == num_workers无GIL兼容例外(线程不安全):
IR打印(如PassManager启用enable_ir_printing(),调用线程不安全的llvm::raw_ostream);
Location.emit_error使用;
Module.dump使用;
mlir.dialects.transform.interpreter使用;
mlir.dialects.gpu与gpu-module-to-binary使用。