Files
Viper_VRTE/mlirlite
TermiNexus db3be6968f fix(memhub,gzip): 内存池 OOM 归属画像与 free 拒绝现场;gzip 注解 *= 词法粘连
memhub: alloc 失败打印归属 site/ctx 与存活块 order 分布(新增 dump_alive_orders);free 被 _is_valid_ptr 拒绝时首次打印现场;接通 big_alloc_count

gzip: ub: uchar*= buf 补空格(*= 被词法合并为单 OP,注解止于 uchar)

新增 traceback 模块;删除 dbgstack.vp;其余模块随新语法与尺寸口径同步
2026-09-26 19:27:24 +08:00
..

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 导入清单逐名一致(208 个名字),代码逐行一致,仅注释不同
__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 内:
    1. 常量物化(MLIR 操作数不能是字面量);
    2. phi → 块参数(MLIR 块参数取代 phi 指令);
    3. 不透明指针(所有指针渲染 !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[%c0, %i] : (!llvm.ptr, i32, 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[%c0, %c1] : (!llvm.ptr, i32, i32) -> !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 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
f64 0x40091EB851EB851F %c0 = llvm.mlir.constant(3.140000e+00 : f64) : f64(位模式 → 十进制科学计数法)
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 也参与扫描,只是发射阶段不输出)。

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
指针型初值 i8* @s0 / i8* null region(顶层叶子:无 undef/insertvalue,直接 llvm.return)

初值两条路径(判定见 __module.vp:_MlirGlobalInitNeedsRegion): c"…" / "…" / 标量字面量 / 纯标量数组走属性路径; 根级裸 null / zeroinitializer、结构体 {…}、含符号引用(@)或结构体的数组、 顶层 @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 实测确立的硬约束(违反即解析/校验失败)

  1. 符号名判据与 LLVM 不同:MLIR 裸标识符首字符必须是字母或 _,且 不允许 -(LLVM 允许)。⇒ TPV 的 {sha1}.{name}(sha1 以数字开头)与 @.str(以 . 开头)一律需要 @"…" 引号形式。判据见 __function.vp:_ll_name_needs_quote,输出统一走 _MlirSymRefAppend。
  2. 字符串属性长度必须等于 i8 数组长度:TPV 的字符串常量类型含尾随 NUL ([N+1 x i8])⇒ 属性里必须保留 \00。绝不能为了对齐长度去剥掉 类型里的 NUL —— 那会丢掉 C 字符串终止符、破坏 ABI。
  3. dense<[…] : !llvm.array<…>> 非法(dense 只接受 builtin shaped 类型 tensor/vector);数组属性必须是 值 : 类型 的元素列表形态,且元素类型与 全局类型一致(元素不注解时按 i64 解释)。
  4. 属性字典必须在类型之前(紧跟值括号):正确形态 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。
  5. 值括号必须存在:stub 的 external 声明没有初值,但仍要写空括号 —— llvm.mlir.global external @x() : i32 exit 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 完全同形)。

④ 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):

  1. 无独立类型定义语法 ⇒ 定义行整体丢弃(PrintStructDefinition 输出空串, PrintModule 完全不遍历 NamedTypeHead 输出文本);
  2. 不允许无体前向声明 ⇒ !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(不透明指针,既有路径不变)

定义行由 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 同形)。

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 位模式);转换发生在打印阶段(浮点位模式→十进制科学计数法)
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)按名称/结构匹配,语义不受影响

5. 待确认(不做静默假设,逐条标注)

  1. switch 的方言形态按设计规范输出 llvm.switch %v, ^default [0: ^case0, …] —— 已由 mlir-opt 22.1.8 实测纠正:规范里的形态被拒(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),三者均以实测形态为准。
  2. alloca / load / store 的 volatile、atomicrmw 的 ordering 关键字按 LLVM 词法直译为 MLIR 词法。其中 acq_rel / monotonic 已随本轮的 cmpxchg 用例一并由 mlir-opt 实测(§2.9);volatile 仍未单独校验。
  3. 全局 {section = "…", alignment = N : i64} 属性字典的位置(值之后、类型之前) 未用 mlir-opt 校验 —— 已由 mlir-opt 22.1.8 实测确立:字典必须在类型之前;放到类型之后会被当成 新 op(Dialect `' not found for custom op 'section'),unnamed_addr 只能作 关键字(进字典报 occurs more than once);stub 的 external 声明必须带空括号 ()。详见 §2.5 的硬约束 4/5。
  4. c"hello\00" → "hello":按设计规范去掉尾随 \00 —— 与实测冲突,已按实测推翻:MLIR 要求字符串属性长度等于 i8 数组长度, 而 TPV 的类型含尾随 NUL([N+1 x i8])⇒ 属性里必须保留 \00 (_MlirCStringToMlir 只剥 c 前缀,见 §2.5 硬约束 2)。
  5. Func(Void, …) 的类型文本渲染为 !llvm.func<none (…)>(由 Void → none 规则 组合而来);函数签名位置的 void 返回值则是省略 -> ty(不影响)。
  6. 渲染期的常量表 / 别名表 / 解析 scratch 都是模块级全局(每次 FunctionPrint 重新初始化),因此本库的打印阶段不可重入:同一进程内不支持两个线程同时打印 (与 llvmlite 的 _g_last_dst 等全局缓存同一假设)。
  7. 每次 FunctionPrint 会从内存池分配约 40KB 的常量表(512 项)与解析 scratch (20 个缓冲),模块打印另需一个 64KB 临时缓冲。函数数量很大的编译单元需要相应 放大编译器内存池;后续若要降内存,可改为"同一池内一次性分配并复用"。
  8. 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)。
  9. 具名结构体登记表(§2.10)是打印期设施:只在 PrintModule 期间有效 (先数 NamedTypeHead 实际长度 → MlirNamedTypesBegin(pool, cnt) 精确分配 cnt * 16 字节,每槽 { LLVMType*, IsPacked } → 逐项 MlirNamedTypeRegister(ty, is_packed) → 打印结束 MlirNamedTypesClear)。 无具名类型的模块零分配;真实模块的具名类型可达数千个(实测单文件最多 7837 个), 故不设固定容量上限。独立调用 FunctionPrint 时表未发布 ⇒ 中间文本里的 %"name" 会响亮报错(builders 不会产生该形态,故正常路径不受影响)。
  10. _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)。