R14:rawptr[X] -> X*(41 文件 / 2037 行改动),X 含模块限定名与嵌套下标(rawptr[GSList[NameEntry]] -> GSList[NameEntry]*)。保留 6 处 rawptr[t.CPtr](规范例外:存纯裸指针);# 注释与字符串里的 rawptr[...] 属元语法描述,未动。 R16(保守子集):t.CConst | str/bytes -> const str/bytes,t.CConst | char | t.CPtr -> const str;typedef 右值位置豁免(const str 在该处无法注册,与 R1 同源)。 验证:TPC 自举三重重编 SUCCESS;TransPyV.exe 全量 77/77 顺序回归通过。
mlirlite 设计方案
用 TransPyC 自身实现的 MLIR(llvm 方言)文本发射库。它与 includes/llvmlite
逐名逐签名同形:内部模型(类型/值/函数/基本块/构建器/模块)与公开 API 完全一致,
唯一区别是"打印"阶段输出 MLIR 文本而不是 LLVM IR 文本。
目的:让 TransPyV 侧只需把 llvmlite. 前缀重命名为 mlirlite. 即可编译,
从而在不改内部模型、不改 builders 的前提下切换到 MLIR 后端。
实际切换方式(2026-09):不动 TransPyV 侧任何
llvmlite.调用点,也不动includes/llvmlite/**——工程文件project.vpj的options.target = "mlir"会让 模块名llvmlite在名称→SHA1 解析处改写为mlirlite(唯一改动点是StubMergerIR._FindSha1ByModName)。开关、回退与缓存标签要求见 Debugger/BackendSwitch.md。
术语约定:文中"中间文本"指 builders 写进
Line的指令文本(仍是 LLVM 语法, 如%0 = add i32 %a, %b);"MLIR 文本"指FunctionPrint/LLVMModulePrint最终输出、可喂给mlir-translate/mlir-opt的文本。
1. 同形性边界
1.1 逐名同形(保证 llvmlite. → mlirlite. 重命名可编译)
| 文件 | 内容 | 与 llvmlite 的关系 |
|---|---|---|
__init__.vp |
公共导出 + 便捷工厂(create_function/const_int*/create_global_* 等)+ LLVMTypeCore/LLVMModuleCore |
导入清单逐名一致(215 个名字,含 Debug Info 全套),代码逐行一致,仅注释不同 |
__types.vp |
LLVMType(REnum) / ParamNode / 类型工厂 / TypePrint / DeepCopyType |
模型与工厂逐名同形;TypePrint 改为 MLIR 语义(具名结构体内联为 !llvm.struct<"name", (…)>),PrintStructDefinition 变为空操作,新增打印期命名结构体登记表(§2.10) |
__values.vp |
Value / ConstInt/ConstNull/ConstZero/ConstFloat/SSAValue / ValuePrint |
模型与工厂同形;仅 ValuePrint 改为 MLIR 语义 |
__function.vp |
Line/BasicBlock/Param/Function + 工厂 + RenumberSSA + 窥孔 + FunctionPrint |
模型、RenumberSSA、本地窥孔逐行一致;FunctionPrint 重写为 MLIR 渲染 |
__builder.vp |
IRBuilder + 全部 build_* |
逐行一致(仅 _type_str 的 fail-fast 校验字符集适配 MLIR 类型首字符) |
__module.vp |
LLVMModule/GlobalVariable/NamedTypeNode + LLVMModulePrint |
模型同形;PrintModule 与全局渲染改为 MLIR(定义行丢弃 + 打印期登记表发布,§2.10) |
__verify.vp |
VerifyResult/verify_function/verify_module |
逐行一致(校验的是内部模型 + 中间文本,与 MLIR 文本无关) |
1.2 差异只在打印阶段(设计红线)
- builders 一行不改:仍按 LLVM 语法把指令文本写入
Line。 - MLIR 特有的三类差异全部集中在
FunctionPrint/PrintModule内:- 常量物化(MLIR 操作数不能是字面量);
- phi → 块参数(MLIR 块参数取代 phi 指令);
- 不透明指针(所有指针渲染
!llvm.ptr)。
- 因此"纯重命名可用"成立:模型层与 API 层不变,语义差异只体现在文本。
唯一的 builders 例外:IRBuilder._type_str 的 fail-fast 护栏原本只接受 LLVM 类型
首字符(i/f/d/h/l/m/v/%/[/{/.),MLIR 类型首字符是 !(!llvm.ptr)或 n(none),
故把合法集合扩为 i/f/!/n/[/{/%/"。该改动只放宽校验、不改文本生成。
1.3 关键推论:中间文本里的类型已经是 MLIR 文本
builders 未改,而它们内嵌的类型一律来自 IRBuilder._type_str → TypePrint,
所以中间指令文本里的类型已经是 MLIR 形态。例如 build_load 生成的中间行是
%1 = load i32, !llvm.ptr %p
(llvmlite 版同位置是 %1 = load i32, i32* %p)。由此:
MlirTypeText(中间类型文本 → MLIR 类型文本)以!llvm.…/none直通为主路径, 同时保留 LLVM 语法分支(i8*、[N x x]、{…})以兼容手工构造的中间文本;- 结构体类型在中间文本里就已是完整内联形态(匿名
!llvm.struct<(…)>、 具名!llvm.struct<"name", (…)>,见 §2.10),因此不需要在中间文本里 出现%"name"这种"只有名字"的形态;若出现(手工构造 / TPV 注入),MlirTypeText按模块打印期发布的登记表回查完整定义后展开为具名内联形态, 查不到 / 仍是 opaque ⇒ 响亮报错; _mlir_read_type的类型读取按[] {} () <>统一做括号平衡, 以正确读出含空格/逗号的!llvm.array<…>/!llvm.struct<(…)>。
2. MLIR 渲染规则表
2.1 类型(TypePrint)
| 内部模型 | MLIR 文本 |
|---|---|
Int1/8/16/32/64 |
i1/i8/i16/i32/i64 |
Half/Float/Double/FP128 |
f16/f32/f64/f128 |
Ptr(_) |
!llvm.ptr(不透明指针;pointee 只保留在内部模型里) |
Array(elem,N) |
!llvm.array<N x elem>(嵌套:!llvm.array<2 x !llvm.array<3 x i8>>) |
Struct(fields,N,name) |
!llvm.struct<(f0, f1, …)>;空 → !llvm.struct<()>。具名(name 非 None)→ !llvm.struct<"name", (f0, f1, …)>(内联具名体,见 §2.10) |
Func(ret,params) |
!llvm.func<ret (p0, p1)> |
Void |
none(仅必须打印时;函数返回值位置省略) |
Label / Metadata |
v1 不支持 → 响亮报错 |
中间文本 → MLIR 类型文本的转换(MlirTypeText,用于 op 行里的类型标注):
| 中间文本 | MLIR 文本 |
|---|---|
!llvm.ptr / !llvm.array<…> / !llvm.struct<(…)> / !llvm.func<…> |
直通(builders 内嵌类型的实际形态) |
none |
none(builders 对 Void 的类型文本) |
void / half / float / double / fp128 |
none / f16 / f32 / f64 / f128 |
iN |
iN |
任意以 * 结尾(i8*、i8**) |
!llvm.ptr |
[N x T] |
!llvm.array<N x T'> |
{T1, T2} |
!llvm.struct<(T1', T2')> |
%name(只有名字的结构体类型) |
按打印期登记表回查完整定义 → !llvm.struct<"name", (f0, …)>;未登记 / 仍是 opaque → 响亮报错(见 §2.10) |
T (P0, P1) |
!llvm.func<T' (P0', P1')> |
2.2 函数 / 块 / 指令
llvm.func @add(%a: i32, %b: i32) -> i32 {
%0 = llvm.add %a, %b : i32
llvm.return %0 : i32
}
- void 返回:
llvm.func @noop() { … llvm.return };declare(无体):llvm.func @f(%x: i32) -> i32 - 变参函数(
func.IsVarArg == 1):参数表尾追加...——llvm.func @printf(%p: !llvm.ptr, ...) -> i32;无固定参数时是llvm.func @f(...) -> i32 - entry 块不带标签;后续块
^<name>:(块名去掉前导%) - 块参数:
^merge(%7: i32):(块首连续 phi 变成块参数,phi 行不再输出)
| 中间文本 | MLIR 文本 |
|---|---|
%N = add i32 %a, %b |
%N = llvm.add %a, %b : i32(sub/mul/sdiv/udiv/srem/urem/and/or/xor/shl/lshr/ashr 同) |
%N = fadd f64 %a, %b |
%N = llvm.fadd %a, %b : f64(fsub/fmul/fdiv/frem 同) |
%N = fneg f64 %a |
%N = llvm.fneg %a : f64 |
%N = icmp sgt i32 %a, %b |
%N = llvm.icmp "sgt" %a, %b : i32 |
%N = fcmp oeq f64 %a, %b |
%N = llvm.fcmp "oeq" %a, %b : f64 |
%N = alloca i32 |
%N = llvm.alloca %c0 x i32 : (i32) -> !llvm.ptr(个数按常量 op 物化) |
%N = alloca i32, align N |
%N = llvm.alloca %c0 x i32 {alignment = N : i64} : (i32) -> !llvm.ptr(对齐必须是 2 的幂常量,见 §2.7) |
%N = load i32, !llvm.ptr %p |
%N = llvm.load %p : !llvm.ptr -> i32 |
store i32 %v, !llvm.ptr %p |
llvm.store %v, %p : i32, !llvm.ptr |
%N = getelementptr i32, !llvm.ptr %p, i64 %i |
%N = llvm.getelementptr %p[%i] : (!llvm.ptr, i64) -> !llvm.ptr, i32 |
%N = getelementptr !llvm.array<4 x i32>, !llvm.ptr %p, i32 0, i64 %i |
%N = llvm.getelementptr %p[0, %i] : (!llvm.ptr, i64) -> !llvm.ptr, !llvm.array<4 x i32> |
%N = getelementptr !llvm.struct<(i32, !llvm.ptr)>, !llvm.ptr %p, i32 0, i32 1 |
%N = llvm.getelementptr %p[0, 1] : (!llvm.ptr) -> !llvm.ptr, !llvm.struct<(i32, !llvm.ptr)> |
%N = bitcast !llvm.ptr %p to !llvm.ptr |
no-op:不输出指令,%N 登记为 %p 的别名(后续引用自动改写) |
%N = bitcast i32 %v to f32 |
%N = llvm.bitcast %v : i32 to f32 |
%N = trunc i64 %v to i32 |
%N = llvm.trunc %v : i64 to i32(zext/sext/ptrtoint/inttoptr/fpext/fptrunc/fptosi/sitofp/fptoui/uitofp 同构) |
%N = call i32 @f(i32 %a) |
%N = llvm.call @f(%a) : (i32) -> i32 |
call void @llvm.va_start(i8** %ap) |
llvm.call_intrinsic "llvm.va_start"(%ap) : (!llvm.ptr) -> ()(intrinsic 专用:名字由 mlir-translate 按类型自动 mangle;同名 declare 不输出,见 §2.9 ③′) |
call none @f(i32 %a) |
llvm.call @f(%a) : (i32) -> ()(Void 的中间类型文本是 none) |
%N = call i32 %fp(i32 %a) |
%N = llvm.call %fp(%a) : !llvm.ptr, (i32) -> i32 |
call none asm sideeffect "nop", ""() |
llvm.inline_asm has_side_effects "nop", "" : () -> ()(见 §2.8) |
%N = call i32 asm sideeffect inteldialect "…", "=r,r"(i32 %v) |
%N = llvm.inline_asm has_side_effects asm_dialect = intel "…", "=r,r" %v : (i32) -> i32(见 §2.8) |
br label %blk |
llvm.br ^blk(目标有块参数时 llvm.br ^merge(%c0 : i32)) |
br i1 %c, label %t, label %e |
llvm.cond_br %c, ^t, ^e |
ret i32 %v / ret void |
llvm.return %v : i32 / llvm.return |
unreachable |
llvm.unreachable |
%N = select i1 %c, i32 %a, i32 %b |
%N = llvm.select %c, %a, %b : i1, i32 |
switch i32 %v, label %d [i32 0, label %c0] |
llvm.switch %v : i32, ^d [0: ^c0](值类型紧随值;case 值是属性,不物化) |
| 目标块带 phi 的 switch | llvm.switch %v : i32, ^d(%c1 : i32) [0: ^c0(%c0 : i32)](与 br 同一块实参设施,见 §2.4) |
%N = atomicrmw add !llvm.ptr %p, i32 %v monotonic |
%N = llvm.atomicrmw add %p, %v monotonic : !llvm.ptr, i32 |
%N = va_arg !llvm.ptr %ap, i32 |
%N = "llvm.va_arg"(%ap) : (!llvm.ptr) -> i32(generic 形态,见 §2.9) |
%N = cmpxchg !llvm.ptr %p, i32 %c, i32 %n acq_rel monotonic |
%N = llvm.cmpxchg %p, %c, %n acq_rel monotonic : !llvm.ptr, i32 + 两条 llvm.extractvalue(见 §2.9) |
%r = extractvalue {i32, i32} %v, 0 |
%r = llvm.extractvalue %v[0] : !llvm.struct<(i32, i32)>(索引写在 [] 内;多级索引 %v[1, 0] 同理,见 §2.8) |
变参调用 %N = call i32 (i8*, i32, ...) @printf(i8* %p, i32 %n) |
%N = llvm.call @printf(%p, %n) vararg(!llvm.func<i32 (!llvm.ptr, i32, ...)>) : (!llvm.ptr, i32) -> i32(见 §2.9) |
2.3 常量必须物化为 op(关键)
MLIR 的操作数不能是字面量。扫描阶段(pass 1)遍历函数所有指令行,凡非 SSA
操作数(整数/浮点字面量、null、zeroinitializer、@符号引用)都在常量表
登记 (MLIR 类型, 常量文本);发射阶段(pass 2)在 entry 块开头按登记顺序
生成物化 op,并在原位置改用 %cN:
| 常量 | 物化 op |
|---|---|
i32 42 |
%c0 = llvm.mlir.constant(42 : i32) : i32 |
i1 true / i1 false |
%c0 = llvm.mlir.constant(true) : i1(true/false 是 MLIR 的裸布尔关键字,不带 : i1;写成 true : i1 报 expected ')') |
f64 0x40091EB851EB851F |
%c0 = llvm.mlir.constant(0x40091EB851EB851F : f64) : f64(位模式原样直通:MLIR FloatAttr 支持十六进制位模式,按底层二进制位解释,无精度损失;宽度按类型取 f16/f32/f64/f128 ↔ 4/8/16/32 位十六进制,不符则响亮报错) |
null / zeroinitializer |
%c0 = llvm.mlir.zero : !llvm.ptr(或 : i32 等) |
@f / @"sha1.x" |
%c0 = llvm.mlir.addressof @f : !llvm.ptr |
- 同函数内相同
(类型, 文本)只物化一次; - 常量统一放 entry 块开头 ⇒ entry 支配全函数,常量在所有块内可见, 且不破坏 phi 的"块首"约束(phi 只在非 entry 块,块首仍由 phi 独占);
phi入边里的常量同样会被物化(phi行在 pass 1 也参与扫描,只是发射阶段不输出)。- 例外 ——
getelementptr的索引不是"操作数"而是索引属性:常量索引必须写成 整数字面量([0, 1]),不物化为%cN(否则 MLIR verifier 报expected index N indexing a struct to be constant);只有 SSA 索引才物化并 占类型位,故(…)类型列表只列 SSA 索引的类型(见 §2.1 表)。
2.4 phi → 块参数
- 块首连续 phi ⇒ 建立块参数:
^merge(%7: i32):,phi 行不再输出; - 跳转到该块的
br/cond_br按源块匹配入边追加实参:llvm.br ^merge(%c1 : i32); - 找不到匹配入边 ⇒ 响亮报错(绝不静默丢实参);
- phi 不在块首(非连续)⇒ 响亮报错;
- entry 块出现 phi ⇒ 响亮报错(MLIR entry 块不接受块参数)。
2.5 全局变量
| 内部模型 | MLIR 文本 |
|---|---|
字符串常量(c"hello\00",[6 x i8]) |
llvm.mlir.global private unnamed_addr constant @".str"("hello\00") : !llvm.array<6 x i8> |
整数全局(0,i32) |
llvm.mlir.global @counter(0 : i32) : i32 |
| stub 模式(无初值) | llvm.mlir.global external @x() : i32(值括号必须保留,实测见下) |
| linkage / constant / unnamed_addr | 对应关键字(private/internal/linkonce_odr、constant、unnamed_addr) |
| section / align | 属性字典 {section = "…", alignment = N : i64},位置在值括号之后、类型之前(已实测,见下) |
整型/浮点数组 [i8 1, i8 2] |
[1 : i8, 2 : i8](元素带类型注解;dense<…> 对 !llvm.array 非法) |
根级裸 null / zeroinitializer |
initializer region:@p() : !llvm.ptr + %v0 = llvm.mlir.zero : <顶层类型> + llvm.return(属性形态 (zeroinitializer) 被 mlir-opt 拒绝:expected attribute value,实测 22.1.8) |
结构体 / vtable { i8* null, i8* bitcast (… @m0 …) } |
initializer region:@vt() + : !llvm.struct<(…)> + { <叶子 op>; undef; insertvalue 链; llvm.return } |
指针数组 + GEP [i8* getelementptr … @s0 …] |
region;GEP/bitcast 常量表达式整体等价 llvm.mlir.addressof @s0 : !llvm.ptr |
嵌套数组 [ !llvm.array<16 x i8>[i8 0, …], … ] |
region(内层类型前缀按"类型记号后紧跟 [ / { 即剥离"处理;元素 MLIR 类型仍取全局类型树,子 zeroinitializer 亦由 region 表达) |
指针型初值 i8* @s0 / i8* null |
region(顶层叶子:无 undef/insertvalue,直接 llvm.return) |
初值两条路径(判定见 __module.vp:_MlirGlobalInitNeedsRegion):
c"…" / "…" / 标量字面量 / 纯标量数组走属性路径;
根级裸 null / zeroinitializer、结构体 {…}、含符号引用(@)/结构体({…})
/MLIR 类型前缀(!llvm.…,如嵌套数组子数组前缀)的数组、
顶层 @name、指针/聚合类型前缀开头的初值
(i8* null、i8* bitcast (…)、i8* getelementptr …)走 region 路径。
region 内元素 MLIR 类型并行取自全局类型树(Struct 字段链 / Array 元素类型),
下标为多下标路径(嵌套聚合如 [0, 1]),insertvalue/llvm.return 的类型后缀始终是顶层聚合类型。
符号名一律走 _MlirSymRefAppend(先剥 LLVM 口径引号,再按 MLIR 口径重加)。
两条路径都覆盖不到的叶子形态一律 MlirFail(带原始文本)。
module_set_target/module_set_datalayout:MLIR 模块不携带 triple/datalayout ⇒ 记录但不输出,模块输出里给一行注释标明"由 mlir-translate / llc 阶段提供"。
⚠ 五条由 mlir-opt 22.1.8 实测确立的硬约束(违反即解析/校验失败)
- 符号名判据与 LLVM 不同:MLIR 裸标识符首字符必须是字母或
_,且 不允许-(LLVM 允许)。⇒ TPV 的{sha1}.{name}(sha1 以数字开头)与@.str(以.开头)一律需要@"…"引号形式。判据见__function.vp:_ll_name_needs_quote,输出统一走_MlirSymRefAppend。 - 字符串属性长度必须等于 i8 数组长度:TPV 的字符串常量类型含尾随 NUL
(
[N+1 x i8])⇒ 属性里必须保留\00。绝不能为了对齐长度去剥掉 类型里的 NUL —— 那会丢掉 C 字符串终止符、破坏 ABI。 dense<[…] : !llvm.array<…>>非法(dense只接受 builtin shaped 类型 tensor/vector);数组属性必须是值 : 类型的元素列表形态,且元素类型与 全局类型一致(元素不注解时按 i64 解释)。- 属性字典必须在类型之前(紧跟值括号):正确形态
llvm.mlir.global internal constant @s("ab\00") {section = "data", alignment = 16 : i64} : !llvm.array<3 x i8>exit 0;把字典放到类型之后(… : !llvm.array<3 x i8> {section = "data"}) 会把section当成新 op,报Dialect `' not found for custom op 'section'。 同理unnamed_addr是关键字(输出在名字之前),不能放进字典 —— 放进字典报attribute 'unnamed_addr' occurs more than once。 - 值括号必须存在:stub 的
external声明没有初值,但仍要写空括号 ——llvm.mlir.global external @x() : i32exit 0;llvm.mlir.global external @x : i32报expected '('。
⚠ 验收口径:类型文本、值文本、裸函数都不是合法的 MLIR 顶层单元
(类型/值不是顶层 op;裸函数父级不是 SymbolTable,其外部符号引用无法解析)
⇒ 一律以整模块(module {})作为验收单元。
2.6 模块输出
module {
// MLIR-TEXT: 仅带体函数与含初值全局 ← 模式注释(MLIR 无 stub/text 二分)
llvm.mlir.global …
llvm.func @f(…) -> i32 { … }
}
| 模式 | MLIR 输出 |
|---|---|
OUTPUT_FULL |
全量(全局 + 全部函数) |
OUTPUT_STUB |
只输出全局 external 声明 + 函数 declare(无体) |
OUTPUT_TEXT |
只输出带体函数 + 含初值的全局 |
ValuePrint:SSA → %0;常量 → 42 : i32(仅供诊断)。
指令渲染不依赖 ValuePrint,FunctionPrint 用自己的操作数解析。
2.7 alloca 对齐(build_alloca_aligned)
中间文本 %N = alloca <ty>, align N → %N = llvm.alloca %c0 x i32 {alignment = N : i64} : (i32) -> !llvm.ptr
- 属性形态由 mlir-opt 22.1.8 实测确定(不得凭记忆):
{alignment = 16 : i64}exit 0;align(16)/, align 16(LLVM 词法)报expected ':';{alignment = 16 : i32}报attribute 'alignment' failed to satisfy constraint: 64-bit signless integer attribute(必须是 i64)。 - 对齐值只接受常量十进制 + 2 的幂:非常量(SSA)/非幂/超范围(> 2^30)一律
MlirFail—— mlir-opt 自身不校验幂次(实测{alignment = 3 : i64}exit 0),故由本层把关。
2.8 内联汇编(build_inline_asm / TPV 注入文本)
两条来源的中间文本 opcode 都是 call,由 _MlirCall 转交 _MlirAsm:
| 中间文本(LLVM 词法) | MLIR 文本 |
|---|---|
call none asm sideeffect "nop", ""() |
llvm.inline_asm has_side_effects "nop", "" : () -> () |
%N = call i32 asm sideeffect "…", "=r,r"() |
%N = llvm.inline_asm has_side_effects "…", "=r,r" : () -> i32 |
%N = call i32 asm sideeffect inteldialect "…", "=r,r"(i32 %v) |
%N = llvm.inline_asm has_side_effects asm_dialect = intel "…", "=r,r" %v : (i32) -> i32 |
%N = call {i32, i32} asm sideeffect "…", "=r,r"() |
%N = llvm.inline_asm has_side_effects "…", "=r,r" : () -> !llvm.struct<(i32, i32)>(多结果 = 单结果 + 结构体类型) |
- 方言关键字:LLVM 的
inteldialect→ MLIR 的asm_dialect = intel(裸inteldialect实测报expected attribute value;asm_dialect = inteldialect报expected … enum values … [att, intel])。 - 输入操作数在 MLIR 里是逗号分隔的裸操作数、不带括号(LLVM 词法的
"…"(%v)实测报expected ':');操作数类型串位于: (<操作数类型…>) -> <返回类型|()>。 - 汇编/约束串按引号串整体透传:TPV 的
\22/\5C/\0A转义对 MLIR 词法同样合法(实测 exit 0)。 - 只认
sideeffect/inteldialect两个关键字;其它记号(含 LLVM 的attdialect/alignstack/unwind)、缺约束串、实参不成对、有返回值却无结果名 ⇒MlirFail响亮报错。 (attdialect/alignstack在 1024 份真实.ll语料中 0 命中 ⇒ 不列入支持计划, 此处的MlirFail只是兜底,不是待补齐缺口。)
多结果内联汇编 + extractvalue(本轮补齐;TPV 全量产物 4163 份 .ll 的
"指令位置"扫描中 extractvalue 70 处,且是唯一未支持却真实使用的 opcode)
真实形态(Test/DefaultArgTest/output/*.ll 等,多结果内联汇编是 extractvalue
的唯一来源——rdrand/rdseed 类 builtin):
%asm_result = call {i32, i32} asm inteldialect "rdrand $1", "=r,=r,~{cc}"()
%asm_out_0 = extractvalue {i32, i32} %asm_result, 0
%asm_out_1 = extractvalue {i32, i32} %asm_result, 1
(语料实测结构体结果有 {i32, i32} / {i64, i64} / {i32, i32, i32, i32} /
{i64, i32, i64, i32},索引一律单级。)
| 中间文本(LLVM 词法) | MLIR 文本 |
|---|---|
%N = call {i32, i32} asm sideeffect "…", "=r,r"() |
%N = llvm.inline_asm has_side_effects "…", "=r,r" : () -> !llvm.struct<(i32, i32)> |
%r = extractvalue {i32, i32} %N, 0 |
%r = llvm.extractvalue %N[0] : !llvm.struct<(i32, i32)> |
%r = extractvalue {i32, {i32, i32}} %N, 1, 0 |
%r = llvm.extractvalue %N[1, 0] : !llvm.struct<(i32, !llvm.struct<(i32, i32)>)> |
- 多结果 = "单结果 + 结构体返回类型":
_MlirAsm无需特殊分支(返回类型 文本经MlirTypeText即{…}→!llvm.struct<(...)>,嵌套结构体亦同)。 extractvalue由_MlirExtractValue渲染(_MlirRenderLine分派):聚合类型 文本在值之前(LLVM 词法)→MlirTypeText转 MLIR 类型;索引必须从 LLVM 的<聚合>, i0, i1转写为 MLIR 的[i0, i1](MLIR 实测拒绝 LLVM 词法:把<聚合>当结果类型、%v,当多余记号)。- 边界:SSA 索引 / 缺索引 / 索引后有多余记号 ⇒
MlirFail响亮报错。 多级索引(%v[1, 0])经 mlir-opt 22.1.8 实测合法 ⇒ 一并支持。 - 实测(mlir-opt / mlir-translate 22.1.8):
%v[0]/%v[1, 0]/ 具名结构体!llvm.struct<"n", (i32, i32)>/ 数组!llvm.array<2 x i32>作聚合类型均 exit 0; 降级得call { i32, i32 } asm sideeffect "…", "=r,r"()+extractvalue { i32, i32 } %1, 0/, 1(与 TPV 产物逐字同形)。 insertvalue/invoke/landingpad/resume/freeze/fence/shufflevector/insertelement/extractelement/callbr在 4163 份真实产物中 全部 0 命中 ⇒ 不实现,仍走"未知指令 opcode"响亮报错。
2.9 变参(...)/ va_arg / cmpxchg
三者形态均由 mlir-opt 22.1.8 实测 exit 0 确立(候选与 exit 码见
__function.vp 的 _MlirFuncSig / _MlirCall / _MlirVaArg / _MlirCmpXchg);
选做依据是真实 .ll 语料频次(变参 call 8831 行/627 文件、va_arg 1994/397、
cmpxchg 547/371)。
① 变参函数声明(func.IsVarArg == 1):参数表尾追加 ...
(有固定参数 ⇒ , ...,否则裸 ...)。
② 变参调用:中间文本 call <ret> (<实参类型…>, ...) @f(<实参>)
(builders 的 build_call(is_variadic=1))在操作数括号后追加
vararg(<被调函数的完整 MLIR 函数类型>):
%0 = llvm.call @printf(%p, %n) vararg(!llvm.func<i32 (!llvm.ptr, i32, ...)>) : (!llvm.ptr, i32) -> i32
vararg(...)内是完整函数类型(返回类型 + 中间文本括号里的参数类型串,含...);- void 返回时该处返回类型写
void—— 写none实测报expected var_callee_type to return void(普通-> ()位置仍用()); - 降级验证:
mlir-translate --mlir-to-llvmir得%3 = call i32 (ptr, ...) @printf(ptr %0, i32 %1)✓。
③ va_arg —— 用 generic(通用 op)形态是刻意选择:
%0 = "llvm.va_arg"(%ap) : (!llvm.ptr) -> i32
- 自定义汇编语法
llvm.va_arg %ap, i32要求显式给出 va_list 的类型(LLVM 里的ptr),而 22.x 不透明指针下该类型无法表达 ⇒ mlir-opt 报expected builtin.function。故刻意改用通用 op 语法,只用操作数/结果类型列表描述。 - 降级验证:
mlir-translate --mlir-to-llvmir得%2 = va_arg ptr %0, i32✓ (与 LLVM 的 va_arg 完全同形)。
③′ LLVM intrinsic(llvm.va_start / llvm.va_end 等)—— 必须用 llvm.call_intrinsic:
- 中间文本
call void @llvm.va_start(i8** %ap)渲染为llvm.call_intrinsic "llvm.va_start"(%ap) : (!llvm.ptr) -> (); 同名declare声明不输出(FunctionPrint跳过llvm.*声明)。 - 原因:mlir-translate 是程序化构造 LLVM IR,不会像 LLVM 的 IR 解析器
那样把旧式 intrinsic 名 upgrade 成带类型后缀的形式;若走普通
llvm.call(或输出llvm.func @llvm.va_start),降级得到未 mangle 的@llvm.va_start, LLVM 校验报Intrinsic name not mangled correctly for type arguments! Should be: llvm.va_start.p0。 llvm.call_intrinsic由 mlir-translate 按操作数类型自动 mangle 名字 (llvm.va_start→llvm.va_start.p0),故此处只给未 mangle 的 intrinsic 名。- 判定:裸符号名以
llvm.开头且非llvm.mlir.(后者是 MLIR 内部 op 名, 不会作为函数名出现)。
④ cmpxchg —— MLIR 里是单结果 struct{T, i1},不是两个 SSA 值:
中间文本两态(builder 的 %N = cmpxchg … 与 %a, %b = cmpxchg …)都渲染为
一条 op + 两条 extractvalue:
%N = llvm.cmpxchg %p, %c, %n acq_rel monotonic : !llvm.ptr, i32
%_N_v = llvm.extractvalue %N[0] : !llvm.struct<(i32, i1)> ; [0] = 旧值(比较值)
%_N_ok = llvm.extractvalue %N[1] : !llvm.struct<(i32, i1)> ; [1] = i1 成功位
- 结果名绑定:
%N(单结果)/%a(双结果首名)→ 旧值[0];双结果次名%b→ 成功位[1]。 绑定复用 bitcast no-op 的别名设施(_MlirAliasAdd),后续引用经_MlirResolve自动改写为上述两条extractvalue的结果名 ⇒ builder 端build_cmpxchg返回的 「T 类型 Value」(旧值)语义保持。 - 两条
extractvalue结果名由 struct 结果名确定性派生(_MlirCxResName):%0→%_0_v(旧值)/%_0_ok(成功位),%a→%_a_v/%_a_ok。_前缀是必需的——MLIR 裸 SSA 名首字符须为字母或_,%0_v被 mlir-opt 22.1.8 拒绝(expected '=' after SSA name)。名字与结果名一一对应、不含计数器 ⇒ pass1(登记别名)与 pass2(发射)必然同名,引用不会悬空。 取[0](旧值)在前、[1](成功位)在后。 - 降级验证:
mlir-translate --mlir-to-llvmir得cmpxchg ptr %0, i32 %1, i32 %2 acq_rel monotonic, align 4+extractvalue { i32, i1 } %4, 0/, 1✓。
2.10 具名结构体(%"name" = type {…})
背景(实测语料):TPV 对每个类都调 module_add_named_type(18 处调用点:
ClassGenDef.vp:522、ClassGenEnum.vp:720/1000、StructGenMixin.vp:1802、
AsyncGenMixin.vp:733、VpiReader.vp:2705/4059 等),llvmlite 侧由
PrintStructDefinition 输出 %"sha1.Cls" = type {…} 定义行
(.tpv_build/**/*.ll 里 ^%"[^"]+" = type 命中 78319 行 / 1815 文件)。
MLIR 侧两条硬约束(实测 mlir-opt 22.1.8):
- 无独立类型定义语法 ⇒ 定义行整体丢弃(
PrintStructDefinition输出空串,PrintModule完全不遍历NamedTypeHead输出文本); - 不允许无体前向声明 ⇒
!llvm.struct<"x">(只有名字、无字段表)报struct without a body only allowed in a recursive struct;opaque (fcount < 0哨兵)无法表达。
唯一可表达形态 = 使用处的内联具名体(四种均实测 exit 0):
| 场景 | MLIR 文本 |
|---|---|
| 按值(形参 / 返回 / 全局 / 字段 / alloca 元素) | !llvm.struct<"aa11bb22cc33dd44.Cls", (i32, !llvm.ptr)> |
名字含数字 / . / [ / ](泛型特化名) |
!llvm.struct<"01c70390f01048df.Lock[0]", (i32)>(直接作字符串字面量,不额外转义/加引号) |
| 嵌套具名(字段是另一个具名结构体) | !llvm.struct<"A.Lock", (!llvm.struct<"A.CRIT", (i32)>)> |
| 空体(无字段的类) | !llvm.struct<"x.Empty", ()> |
packed(@c.Attribute(t.attr.packed)) |
!llvm.struct<"a.P", packed (i32, i8)>(关键字在字段表前;降级 %a.P = type <{ i32, i8 }>;空体 packed () 亦 exit 0) |
Ptr(具名结构体)(%"name"*) |
!llvm.ptr(不透明指针,既有路径不变) |
同名同体(硬性):identified type 的体在整个模块内必须完全一致,否则
mlir-opt 报 identified type already used with a different body。故凡具名结构体
一律以模块登记表(NamedTypeHead 发布)的定义为准渲染,见
_MlirNamedBodyFromTable:文本路径(_MlirTypeAppend 里
!llvm.struct<"name", …> 的直通)与类型对象路径(TypePrint)都先回查登记表;
只有未登记(独立 TypePrint / 外在类型)才按自身字段内联。这样可避免跨模块 stub
构造的0 字段占位类型对象生成 () 体、与别处完整体冲突(现场:
0ba5c56e78e3ff23.ParamNode 在两处渲染成 ()、别处是 (!llvm.ptr, !llvm.ptr))。
定义行由 MLIR 自动生成(实测降级证据):mlir-translate --mlir-to-llvmir
会把使用处的内联形态合成为定义行,故模块里不需要(也不能)手写定义行:
%aa11bb22cc33dd44.Cls = type { i32, ptr } ; ← 模块文本里没有这一行
%x.Empty = type {}
%"01c70390f01048df.Lock[0]" = type { i32 }
@per_cpu = internal global %aa11bb22cc33dd44.Cls { i32 undef, ptr null }
实现位点:
__types.vp:_MlirNamedStructAppend—— 具名内联体渲染(TypePrint/_MlirTypePrintDepth的具名分支;匿名分支_MlirStructFields不变);packed非 0 时在字段表前多输出packed;__types.vp:_MlirNamedStructText—— 中间文本%"name"→ 查登记表展开 (packed 标记与类型同槽取出,故文本路径同样输出packed (…));__module.vp:_MlirPublishNamedTypes——PrintModule开始时按NamedTypeHead发布登记表(MlirNamedTypesBegin(pool, cnt)+MlirNamedTypeRegister(ty, is_packed),cnt取自链表实际长度,is_packed取自NamedTypeNode.IsPacked),打印结束时MlirNamedTypesClear(表内类型 指针只在当次打印期间有效)。
响亮报错(绝不静默输出错误布局):
| 情形 | 报文 |
|---|---|
打印时该具名结构体仍是 opaque(fcount < 0,跨模块前向声明未被完整定义替换) |
命名结构体在打印时仍为 opaque(无字段体)——MLIR 不允许无体前向声明(类型名 '…') |
具名但字段链缺失(FCount 非 0 而 Fields == None) |
命名结构体字段链缺失(FCount 非 0 但无字段) |
名字含 " / \ / 控制字符 |
命名结构体名字含 MLIR 字符串字面量非法字符 |
| 名字为空 / 字段链成环(> 4096) | 命名结构体名字为空 / 命名结构体字段过多或字段链成环 |
中间文本 %"name" 未登记(含"表未发布"的独立 FunctionPrint) |
命名结构体类型文本未在模块中登记完整定义(MLIR 不允许无体前向声明…) |
opaque 风险查证(结论:按值使用处不会出现 opaque):TPV 的 opaque 只由
_scan_type_for_cross_module_refs(Ptr(Struct(name)) 分支)与 REPL 专用的
_repl_opaque_class_ty 产生,两者都服务于指针引用;按值位置(C ABI 值形参)
另有显式判据 _AggregateParamTy:命中注册表且非 opaque 才按值、否则降级 i64;
值类型的全局 / 返回类型先经 _ensure_full_value_types 注册完整定义。故打印时
按值出现的具名结构体字段都是完整的;一旦不完整即按上表响亮报错(旧行为是静默
输出空体 !llvm.struct<()> ⇒ 错误布局)。
packed 结构体(已接入):@c.Attribute(t.attr.packed) →
module_set_named_type_packed → NamedTypeNode.IsPacked(唯一设置点)。
packed 标记只挂在具名节点上(LLVMType.Struct 变体没有该字段;
module_set_named_type_packed 对 Name == None 直接返回),故匿名结构体
({…} → !llvm.struct<(...)>)结构上无法携带 packed —— 不存在"无名却要求
packed"的组合,无需(也无处)为其单独报错。登记表每槽 16 字节
({ LLVMType*, IsPacked }),打印期由 _MlirPublishNamedTypes 从节点读出;
渲染侧(TypePrint 具名分支经 _MlirNamedPackedOf 回查;文本路径
_MlirNamedStructText 与类型同槽取出)据标记在字段表前输出 packed 。
实测 mlir-opt / mlir-translate 22.1.8 exit 0:
!llvm.struct<"a.P", packed (i32, i8)> → %a.P = type <{ i32, i8 }>;
空体 !llvm.struct<"x.Empty", packed ()> → %x.Empty = type <{}>。
3. v1 支持 / 不支持清单
3.1 支持(已实现)
类型:iN 全整数、f16/f32/f64/f128、不透明指针、数组、结构体(含具名结构体
!llvm.struct<"name", (…)> 与 packed 具名结构体 !llvm.struct<"name", packed (…)>,
见 §2.10)、函数类型、Void。
指令:add/sub/mul/sdiv/udiv/srem/urem/and/or/xor/shl/lshr/ashr、
fadd/fsub/fmul/fdiv/frem/fneg、icmp/fcmp、alloca(含 , align N 对齐属性,见 §2.7)、
load/store(含 volatile)、
getelementptr(单索引 / 数组 / 结构体)、bitcast(ptr→ptr 折叠为 no-op)、
sext/trunc/zext/ptrtoint/inttoptr/fpext/fptrunc/fptosi/sitofp/fptoui/uitofp、
call(直接 / 间接 / 变参)、inline_asm(sideeffect / inteldialect,含多结果,见 §2.8)、
br/cond_br(含块实参)、ret/ret void、unreachable、
select、phi(→ 块参数)、switch(含目标块带 phi 的块实参)、atomicrmw、
va_arg、cmpxchg(三者见 §2.9)、extractvalue(多结果内联汇编的结果抽取,
含多级索引,见 §2.8);变参函数声明(llvm.func @f(…, ...))。
全局初值:整数、浮点、cstring、整型数组(含嵌套数组)走属性路径;
根级裸 null / zeroinitializer、结构体/vtable、含符号引用的聚合、指针型初值
走 initializer region(叶子 op + undef + insertvalue 链 + return;见 §2.5)。
3.2 不支持 —— 一律响亮报错(MlirFail:打印定位信息后 exit(1))
| 形态 | 报错位点(函数) | 报文 |
|---|---|---|
Label 类型 |
TypePrint / _MlirTypePrintDepth |
TypePrint(Label): MLIR 无 label 类型,v1 不支持 |
Metadata 类型 |
TypePrint / _MlirTypePrintDepth |
TypePrint(Metadata): MLIR llvm 方言 v1 不支持 metadata 类型 |
alloca 对齐值非常量/非 2 的幂/超范围(> 2^30) |
_MlirAlloca |
alloca 对齐值必须是常量(常量十进制,非 SSA) / alloca 对齐值必须是 2 的幂 / alloca 对齐值超范围(须为 2 的幂且 <= 2^30) |
alloca 的 , <非 align 属性> |
_MlirAlloca |
alloca 只支持 align 属性(未知属性) |
inline_asm 的未知关键字(含 LLVM 的 attdialect/alignstack/unwind)或非字符串汇编文本 |
_MlirAsm |
inline_asm 缺少汇编文本(或含未知关键字) |
inline_asm 缺约束串 / 约束串非字符串 / 缺实参列表 / 实参后有多余记号 |
_MlirAsm |
inline_asm 缺少约束串(形态须为 "asm", "constraints") 等 |
inline_asm 有返回值却无结果名(中间文本缺 %N = ) |
_MlirAsm |
inline_asm 有返回值但缺少结果名(%N = call …) |
双结果前缀 %a, %b = 用于非 cmpxchg,或缺 = |
_MlirRenderLine |
双结果前缀(%a, %b =)只支持 cmpxchg / 双结果前缀(%a, %b =)缺少 '=' |
cmpxchg 缺结果名 / extractvalue 结果名分配失败 |
_MlirCmpXchg / _MlirCxResName |
cmpxchg 缺少结果名 / cmpxchg 结果名分配失败 |
va_arg 缺结果名 |
_MlirVaArg |
va_arg 缺少结果名 |
extractvalue 缺索引 / SSA 索引 / 索引后有多余记号 / 索引过多(> 32) |
_MlirExtractValue |
extractvalue 缺少索引 / extractvalue 索引必须是常量(SSA 索引不支持) / extractvalue 索引之后出现多余记号 / extractvalue 索引过多 |
变参 call 的函数类型参数过多(> 256) |
_MlirCall |
变参 call 的函数类型参数过多 |
全局初值叶子形态非 符号引用/null/zeroinitializer/标量字面量(如聚合里的字符串常量) |
_MlirGvWalkLeaf |
全局初值叶子形态 v1 不支持:'<原始文本>' |
| 初值树与全局类型树不一致(初值为结构体/数组但全局类型不匹配) | _MlirGvWalkStruct / _MlirGvWalkArray |
初值为结构体,但全局类型不是结构体 / 初值为数组,但全局类型不是数组 |
| 初值嵌套超过 64 层 | _MlirGvWalk |
全局初值嵌套过深(> 64 层) |
| phi 不在块首(非连续) | _MlirRenderLine |
phi 不在块首(v1 只支持块首连续 phi → 块参数) |
| entry 块出现 phi | _MlirCheckEntryNoPhi |
entry 块不允许 phi(MLIR entry 块不接受块参数) |
跳转目标(含 switch 的 case/default 目标)带 phi 但缺匹配入边 |
_MlirBlockOperands |
跳转目标块带 phi,但找不到与源块匹配的入边 |
switch case 值是 SSA |
_MlirSwitch |
switch case 值必须是常量(SSA 值不支持) |
具名结构体在打印时仍为 opaque / 字段链缺失;名字含 "、\、控制字符;字段链成环 |
_MlirNamedStructAppend / _MlirNamedCheckName |
见 §2.10 报错表 |
中间文本 %"name" 未登记完整定义(含独立 FunctionPrint 时登记表未发布) |
_MlirNamedStructText |
命名结构体类型文本未在模块中登记完整定义(MLIR 不允许无体前向声明…) |
| 中间文本里出现未知 opcode | _MlirRenderLine |
未知指令 opcode |
| 无法转换的类型文本 | _MlirTypeAppend |
无法转换为 MLIR 类型文本(v1 未覆盖该形态) |
| 常量操作数形态非 整数/浮点/null/符号 | _MlirEmitOneConst |
不支持的常量操作数形态 |
| 常量表 / 别名表溢出 | _MlirConstGet / _MlirAliasAdd |
常量表溢出 / bitcast 别名表溢出 |
3.3 记录但不输出(信息不丢,但不在 MLIR 文本里)
module_set_target/module_set_datalayout:MLIR 模块不携带,由mlir-translate/llc阶段提供(模块输出里有注释标明)。- 函数属性
Attrs/AttrGroup/AttrGroupId:MLIR 的属性表达方式差异较大, v1 记录但不渲染(模型字段保留,function_set_attrs/function_set_attr_group同名可用)。 Param.Attrs(noundef/nocapture等):同上。NamedTypeHead类型定义链表:MLIR 无独立类型定义语法,结构体在使用处内联为!llvm.struct<"name", (…)>(具名)/!llvm.struct<(…)>(匿名),packed 具名 再加关键字(!llvm.struct<"name", packed (…)>);不输出任何 类型定义行。module_add_named_type保留模型语义(去重 / opaque 就地替换)并作为 打印期登记表的来源(_MlirPublishNamedTypes,见 §2.10),PrintStructDefinition输出空串(保留 API 同形)。- Debug Info(
module_add_di_node/module_add_named_metadata/function_set_dbg/builder_set_(clear_)debug_location、DINode/DINamedMeta、LLVMModule.DIMeta*/DINamed*、Function.DbgRef、IRBuilder.DbgLoc):为保持与 llvmlite 逐名同形(并让 TransPyV 的DebugInfoGen在llvmlite.→mlirlite.改名后可编译),本库完整保留这些名字 与模型字段/布局(LLVMModule144B、Function88B、IRBuilder40B, 与 llvmlite 同布局);但 MLIR 语法没有!N = …元数据与, !dbg !N后缀, 故全部只记录、不输出(LLVMModulePrint不打印 DI 节点,_emit不读DbgLoc)。
4. 与 llvmlite 的语义差异(同形但语义必然不同)
| # | 同形点 | 语义差异 |
|---|---|---|
| 1 | TypePrint(buf,size,ty,pool) |
输出 MLIR 类型而非 LLVM 类型;Ptr(_) 恒为 !llvm.ptr(丢失 pointee)、Struct(…,name) 内联为 !llvm.struct<"name", (…)>(丢失"定义行"这一概念,见 §2.10),Void 为 none,Label 报错 |
| 2 | ValuePrint(buf,size,val,pool) |
SSA 只输出 %0(不带类型);常量输出 42 : i32;llvmlite 版是 i32 42 |
| 3 | FunctionPrint(buf,size,func,pool) |
输出 llvm.func/llvm.* op;入口常量物化、phi→块参数、no-op bitcast 折叠 |
| 4 | LLVMModulePrint(buf,size,mod,pool,mode) |
输出 module { … };OUTPUT_STUB/TEXT 只能用注释标示(MLIR 无此二分);triple/datalayout 不输出 |
| 5 | IRBuilder.build_bitcast(val,to_ty) |
返回的 Value 名字仍会出现在中间文本里,但打印时若两端都是指针则整个指令消失、名字被别名替换(Value 对象本身不变) |
| 6 | IRBuilder.build_phi / build_br |
builder 语义不变(仍写 phi 行);MLIR 打印时 phi 行消失、br 追加实参 |
| 7 | IRBuilder.build_alloca |
打印时个数按常量 1(i32)物化;build_alloca_aligned 的 , align N 转写为 {alignment = N : i64} 属性(N 必须是 2 的幂常量,否则响亮报错) |
| 8 | const_int / ConstFloat |
Name 文本仍是 llvmlite 形态(十进制 / 0x 位模式);转换发生在打印阶段(浮点 0x 位模式原样直通,MLIR 按底层位解释,宽度按类型 f16/f32/f64/f128 ↔ 4/8/16/32 位十六进制) |
| 9 | create_global_string |
Initializer 仍是 c"…\00";打印时转成 "…"(去掉尾随 \00,见 §5 待确认) |
| 10 | verify_function / verify_module |
校验内部模型 + 中间文本(与 llvmlite 完全一致);不校验 MLIR 文本合法性(交给 mlir-opt) |
| 11 | PrintStructDefinition |
输出空串(MLIR 无类型定义行),llvmlite 版输出 %"name" = type {…};具名结构体只在使用处内联出现(§2.10) |
| 12 | G_PeepEnable |
语义不变:窥孔作用在中间文本(ret zeroinitializer→0/null、入口槽 store→load 转发、no-op bitcast 消除),与 MLIR 渲染正交(T3 与 MLIR 的 no-op 折叠叠加,结果一致) |
| 13 | module_set_named_type_packed |
语义不变(记录 NamedTypeNode.IsPacked);MLIR 侧已参与渲染:具名结构体在使用处内联为 !llvm.struct<"name", packed (…)>(见 §2.10) |
| 14 | 中间指令文本(Line.Buf) |
内容与 llvmlite 不同:builders 内嵌类型经 _type_str → TypePrint 已是 MLIR 形态(load i32, !llvm.ptr %p 而非 load i32, i32* %p)。文本层使用者(窥孔、verify)按名称/结构匹配,语义不受影响 |
| 15 | Debug Info 全套(module_add_di_node / module_add_named_metadata / function_set_dbg / builder_set_(clear_)debug_location、DINode/DINamedMeta、DIMeta*/DINamed*/DbgRef/DbgLoc) |
逐名同形、布局同形(LLVMModule 144B / Function 88B / IRBuilder 40B),但 MLIR 无 !N = … 元数据与 , !dbg !N 后缀 ⇒ 只记录、不输出(见 §3.3) |
5. 待确认(不做静默假设,逐条标注)
—— 已由 mlir-opt 22.1.8 实测纠正:规范里的形态被拒(switch的方言形态按设计规范输出llvm.switch %v, ^default [0: ^case0, …]expected ':'), 实际采用llvm.switch %v : i32, ^default [0: ^case0](值类型紧随值), 目标块实参写作^d(%c1 : i32)/[0: ^c0(%c0 : i32)](见 §2.2)。 本轮同样实测了alloca {alignment = N : i64}(§2.7)与inline_asm … asm_dialect = intel …(§2.8),三者均以实测形态为准。alloca/load/store的volatile、atomicrmw的 ordering 关键字按 LLVM 词法直译为 MLIR 词法。其中acq_rel/monotonic已随本轮的cmpxchg用例一并由mlir-opt实测(§2.9);volatile仍未单独校验。全局—— 已由 mlir-opt 22.1.8 实测确立:字典必须在类型之前;放到类型之后会被当成 新 op({section = "…", alignment = N : i64}属性字典的位置(值之后、类型之前) 未用mlir-opt校验Dialect `' not found for custom op 'section'),unnamed_addr只能作 关键字(进字典报occurs more than once);stub 的external声明必须带空括号()。详见 §2.5 的硬约束 4/5。—— 与实测冲突,已按实测推翻:MLIR 要求字符串属性长度等于 i8 数组长度, 而 TPV 的类型含尾随 NUL(c"hello\00"→"hello":按设计规范去掉尾随\00[N+1 x i8])⇒ 属性里必须保留\00(_MlirCStringToMlir只剥c前缀,见 §2.5 硬约束 2)。Func(Void, …)的类型文本渲染为!llvm.func<none (…)>(由Void → none规则 组合而来);函数签名位置的 void 返回值则是省略-> ty(不影响)。- 渲染期的常量表 / 别名表 / 解析 scratch 都是模块级全局(每次
FunctionPrint重新初始化),因此本库的打印阶段不可重入:同一进程内不支持两个线程同时打印 (与 llvmlite 的_g_last_dst等全局缓存同一假设)。 - 每次
FunctionPrint会从内存池分配约 40KB 的常量表(512 项)与解析 scratch (20 个缓冲),模块打印另需一个 64KB 临时缓冲。函数数量很大的编译单元需要相应 放大编译器内存池;后续若要降内存,可改为"同一池内一次性分配并复用"。 MlirFail采用stdio.printf+stdlib.exit(1)的 fail-fast 风格(对齐 llvmlite 的[IR-DUMP]/[IR-FAIL]):不支持形态会终止进程而非返回错误码。 拿不到调用栈,故"类型文本转换失败"这条报文会追加当前正在渲染的中间文本行 (… :'<类型片断>' [行: %1 = call …]);行上下文由__function.vp的_MlirRenderLine入口经__types.vp的MlirSetContextLine寄存 (FunctionPrint/PrintModule入口清空,避免带上无关旧行)。 具名结构体的报错同样带类型名与行上下文(_MlirFailNamed,见 §2.10)。- 具名结构体登记表(§2.10)是打印期设施:只在
PrintModule期间有效 (先数NamedTypeHead实际长度 →MlirNamedTypesBegin(pool, cnt)精确分配cnt * 16字节,每槽{ LLVMType*, IsPacked }→ 逐项MlirNamedTypeRegister(ty, is_packed)→ 打印结束MlirNamedTypesClear)。 无具名类型的模块零分配;真实模块的具名类型可达数千个(实测单文件最多 7837 个), 故不设固定容量上限。独立调用FunctionPrint时表未发布 ⇒ 中间文本里的%"name"会响亮报错(builders 不会产生该形态,故正常路径不受影响)。 _mlir_read_type按[] {} () <>做字符级括号平衡来读!llvm.struct<…>; 具名结构的名字是带引号字符串,含[/](泛型特化名如100000000000000a.GSList[100000000000000b.BasicBlock])时不影响平衡(成对出现)。 实测语料(全仓 1462 个.ll/.vpi,26476 个唯一具名类型):TPV 名字全部成对、 无空格、无"/\;仅 19 个非 TPV 名(手工 IR 里的<<.1/>>.10类移位临时名) 含不配对<>—— 它们不经 mlirlite(本库是生成器,只打印 TPV 模型)。 MLIR 侧对这些名字的承载已实测:!llvm.struct<">>.1", (i32)>exit 0, 降级得%">>.1" = type { i32 }。
6. 文件结构
includes/mlirlite/
├── __init__.vp # 公共导出(与 llvmlite 逐名一致)+ 便捷工厂 + *Core 类
├── __types.vp # LLVMType(REnum) + 类型工厂 + MLIR TypePrint + 命名类型登记
├── __values.vp # Value/常量/SSA + MLIR ValuePrint
├── __function.vp # Function/BasicBlock/Line + RenumberSSA + 窥孔 + MLIR FunctionPrint
├── __module.vp # LLVMModule/GlobalVariable + MLIR 模块与全局渲染
├── __builder.vp # IRBuilder(与 llvmlite 逐行一致)
├── __verify.vp # SSA 验证器(与 llvmlite 逐行一致)
└── README.md # 本文件
测试:Test/MLIRLiteTest/(App/main.vp + project.json),用 testcheck
断言各渲染规则(类型 / 函数 / 模块 / 常量物化 / phi→块参数 / 内存·调用·转换 / 全局 / verify /
alloca 对齐·switch·inline_asm / 变参·va_arg·cmpxchg·全局属性字典(T14)/
具名结构体:按值·指针·文本 %"name" 展开·定义行丢弃(T15,见 §2.10)/
多结果内联汇编·extractvalue(含多级索引,T16,见 §2.8))。
其中 output/*.mlir 是 mlir-opt 的离线校验集(裸函数/类型/值文本落 .txt)。