资讯动态

Mojo 编译器内部术语全解:从运行时值、参数到元类型(Metatype)

发布时间:2026/9/10 20:37:24 来源:尧图企业网站定制
Mojo 编译器内部术语全解从运行时值、参数到元类型Metatype【免费下载链接】mojoThe Modular Platform (includes MAX Mojo)项目地址: https://gitcode.com/GitHub_Trending/mo/mojo导读本篇文章基于 Mojo 编译器文档《Terminology》整理并深化围绕一段简短的 Mojo 代码系统拆解 Modular 团队在 Mojo 编译器内部使用的术语体系运行时值runtime value与类型type、参数parameter与实参argument、参数声明/参数引用/参数表达式/参数值之间的精确区别以及贯穿 LIT 与 KGEN 两个 MLIR dialect 的元类型metatype概念。阅读本文后你将能够准确读懂 Mojo 编译器的 MLIR 输出如lit.fn、kgen.param.constant、!lit.metaInt、!kgen.type看懂编译器中关于参数与类型的讨论并理解type与metatype在 Mojo 中为何是同一回事。为什么需要一套内部术语Mojo 是一门同时具备运行时值与编译期参数两个世界的语言而它的编译器构建在 MLIR 之上。当开发者在同一句话里说 value 时可能指的是运行时的 SSA 值也可能指的是编译期的参数值同一个单词 parameter 在 Mojo 内部也至少有三层含义。为避免交流歧义Mojo 编译器团队约定了一套相对严格的内部术语其中一部分用 ⚠️ 标注与语言社区的常见叫法不同。掌握这套术语是阅读 Mojo 编译器源码、MLIR dump 输出以及参与 Modular 社区讨论的前提。核心术语地图一段代码看懂全部概念文档用下面这段代码作为贯穿全文的解剖样本struct Spaceship[N: Int]: var engine: Engine[N] def fooT: Stringable: var i: Int 42围绕这几行代码编译器内部会区分出以下概念注意下面的条目并不按代码顺序排列运行时值runtime value与类型type在var x: Int 42中42被称为运行时值runtime value。文档特别强调尽量不要只叫它 value因为 Mojo 里还有编译期compile-time的值存在两者共用同一个词会造成混淆。同一行里的Int被称为类型type。Int这个类型不是一个 value——一个类型只有出现在参数parameter中时才算值因为只有参数才拥有值only parameters have values。x本身也不是一个 value尽管 LLVM 层用LLVMValueRef来处理它。它是运行时变量不是值。从 PassesAndIR.md 的 MLIR 输出可以看出这种区分在 IR 层的体现var x 42会被解析成%x lit.var.decl x var : !lit.ref!Int, mut *x%x是 MLIR SSA 值对应运行时值而常量42则由kgen.param.constant: !Int {42} 这个 operation 携带一个属性attribute来表示。参数parameter与实参argument在def fooT: Stringable:中x是实参argument它不是参数parameter。⚠️ 这是 Mojo 内部术语与外界习惯最大的差异点之一外部社区常说的 generic arguments 或 generic parameters在 Mojo 内部统一只叫parameters。T是参数parameter。Mojo 刻意把函数形参/实参统一称为 argument对应 C 里的 function parameter而把泛型层面的占位符统一称为 parameter避免混淆。这一点在 PassesAndIR.md 的 Terminologies (from Mojos perspective) 一节有更明确的阐述Mojo 的 parameter 类似于 C 模板参数可以被具体化为编译期常量而函数参数则统一叫 argument包括函数定义中的命名变量和调用时传入的值。参数声明parameter decl与参数引用parameter reference同一个T在不同位置有不同身份出现在[T: Stringable]里的第一个T是参数声明parameter decl它规定了参数的名字、类型约束与默认值等。出现在(x: T)里的第二个T是参数引用parameter reference简称param ref它指回某个参数声明。同样地struct Spaceship[N: Int]:中的N也是参数声明。而在var engine: Engine[N]中N是一个参数引用。参数表达式parameter expression与参数值parameter value继续看var engine: Engine[N]N、Engine[N]以及一个假设存在的N 1全部都是参数表达式parameter expression因为它们发生在编译期。N的值被称为参数值parameter value。类型也可以是参数值——严格来说并不完全是这样但只需把一个类型包进TypeParamAttr即可。N的值和Engine这个类型两者都是参数值。一条实用规则任何出现在冒号:右侧的东西都是一个由参数值组成的参数表达式。⚠️ 但参数值并不总是类型。例如N是一个值但它不是类型。用 PassesAndIR.md 的话说参数在 Mojo 里可以指三样东西参数声明param decl如T: Stringable、参数引用param ref如field: T中的T、以及参数值param value如实际传入的Int。三者的关系可以这样串起来参数值Int被喂给Foo的参数声明T: Stringable最终抵达参数引用T出现在field: T中。类型作为参数值TypeParamAttr 包装文档指出把类型转换为参数值的手段是TypeParamAttr。在 KGENAttrs.td 中定义了KGEN_TypeParamAttrassembly 形式为#kgen.type其官方 summary 就是 Convert an MLIR type to a parameter expression。它类似 MLIR 内置的TypeAttr区别在于它同时是一个TypedAttr因此可以作为参数表达式参与编译期求值并且带有元类型metatype。其typeValue字段编码类型的值表示mlirType字段编码类型的类型表示例如#kgen.type!myTypeValue : !kgen.type。与之配套的还有KGEN_ParamTypeassembly 形式为!kgen.param作用相反把参数值/类型参数还原回一个 MLIR 类型。这在 KGENTypes.td 中有定义它是由参数表达式类型域指定的符号化类型一旦参数被替换为类型常量ParamType就会被折叠掉并返回内部的 MLIR 类型。从 PassesAndIR.md 可以看到它的实际用法lit.fn my_func[...]T: !MyTrait(%x: !kgen.param:!MyTrait T)——因为实参必须是类型而不是参数值所以要用!kgen.param把T这个参数值还原成类型。LIT 与 KGEN 对参数的处理差异值得一提的补充在参数是否带类型这件事上两个 dialect 的行为不同见 PassesAndIR.mdLIT记得参数的类型lit.fn zork[::Int]()N: !Int()中的N: !Int表明参数声明N的类型是Int调用点lit.call ... :!Int {42}表明参数值是类型为Int的常量42。KGEN的参数不带类型同一函数在 KGEN 里变成kgen.generator main::zork[::Int]()N()调用点变成kgen.call ... 42()类型信息被抹掉了。此外与 C 只能接受int、bool等基本类型作为模板参数不同Mojo 的参数几乎可以是任何东西甚至可以是一个完整的List[Int]之类的内存类型。MLIR 层的基础术语operation、attribute、type、symbol文档给出了一条高度概括的规则MLIR 里的一切要么是 operation要么是 attribute。operation一般描述计算比如一个函数或一个表达式有意思的是在 Mojo 的 IR 中struct 和 trait 也是以 operation 的形式存在的。attribute是元数据、值和标志。精化前pre-elaborationattribute 通常是参数表达式parameter expression。精化后post-elaborationattribute 通常是参数值parameter value。结合 PassesAndIR.md 的 MLIR Guide 一节可以给这四个基本符号建立完整的对照表符号含义说明%nameMLIR SSA 值一个 Mojo 运行时值某个 MLIR operation 的结果nameSymbolRefAttr对别处定义实体的引用!typeMLIR 类型如!kgen.none、!lit.ref...#expr/{expr}MLIR 属性attributeMojo 参数在 IR 中表示成 MLIR 属性attribute 也可表示其他编译期值文档特别提醒!kgen.none类型和#kgen.none属性是完全不同的东西两者不能混用。MLIR 的 attribute 是只存在于编译期的数据它不能是变量这也是 Mojo 泛型参数在 IR 中得以表示的基础。例如调用my_func73时lit.call行会出现:!Int {73}其中{73}就是类型为!Int的属性。注原文档提到的预精化/后精化elaboration是指编译器用具体参数值去实例化泛型如把def add[x: Int]实例化成add[3]、add[42]的阶段。LIT 主要存在于精化前KGEN 则能同时描述参数化与具体化的 IR。Bindings绑定与签名SignatureBindings绑定调用方提供的实参或参数与被调方实参或参数声明之间的映射。例如以foo[42, True]调用foo[a: Int, b: Bool]()时绑定就是a42和bTrue。绑定这一概念在编译器实现中直接对应ParamBindings——在 MojoIRCPPCorrespondence.md 生成函数调用的 C 代码示例中可以看到ParamBindings(emitter.getDeclScope())的用法。Signature签名struct 和函数都可以有签名。签名包含名称、参数类型以及如果是函数的话实参类型。POC 与 POGPOC—— Parameter Operator Code参数算子代码。POG—— Parameter Or arGument参数或实参的缩写。这个叫法最初是 Jeff 开的一个玩笑pog 是游戏圈用语但现在已经成了团队内部的常用词。从源码结构看POG 这一概念已深度嵌入 IR 表示在 KGENTypes.td 中GeneratorType携带PogListAttr参数元信息列表FuncType也带有PogListAttr:$argListAttrs实参元信息列表并参与函数类型的唯一性判定。MLIR 打印形式为#kgen.pog_list[T], [pos]参数声明列表及其传递方式。Metatype元类型深度解析这是文档着墨最多、也最值得深入理解的概念。一个有用但不精确的心智模型当讨论 trait或一组必需方法时团队有时会称之为 meta type。一个有用但可能不精确的心智模型是metatype 就是一个具体的 trait比如Copyable或者是那个描述所有 trait 的类型——_AnyTypeMetaType。注意trait 的 supertrait 不是 trait 的 metatype。如果你有trait Spaceship(Launchable): ...那么Launchable不是Spaceship的 metatype而是它的 supertrait。真正的定义metatype 是一组需求set of requirements如果你要超越上面那个心智模型文档建议非必要不要深究那么请记住metatype 实际上并不是一个 trait。之所以容易混淆是因为两者确有相似之处metatype 和 trait 都是一组需求requirements的集合trait 之间存在子类型关系两者都可以用在冒号:的右侧参见VariadicPack的_AnyTypeMetaType用法。但 metatype 的本质是一组需求。例如对于这个 structstruct Spaceship: var hp: Int def launch(mut self): ...它的 metatype 描述的是一个值要成为Spaceship需要满足什么。在这个例子里需求就是它必须被显式声明为Spaceship例如var s: Spaceship。而对于这个 traittrait Launchable: def launch(mut self): ...它的 metatype 要复杂得多。要成为一个Launchable一个值可以是任何带有类似launch函数的 struct在移除隐式 conform 之前都是这样任何显式声明 conform 到Launchable的 struct例如struct Enterprise(Launchable): ...任何间接 conform 到Launchable的 struct例如struct Enterprise(Constitution): ...配合trait Constitution(Launchable): ...。trait 是共享需求的 metatype 的集合一个 trait如Launchable是一组拥有共享需求的 metatype 的集合。以下这些都是该 trait 的合法 metatype 示例该 metatype 的需求集合def launch(mut self)该 metatype 的需求集合def launch(mut self)def land(mut self)该 metatype 的需求集合def launch(mut self)def fire(mut self, num_missiles: Int)任何 conform 到其中任意一个 metatype 的类型都可以满足该 trait。换个角度说trait 是所有至少拥有这些需求的 metatype的总称。_AnyTypeMetaType的实际形态可以在 Variadics.md 中看到它是VariadicPack的默认元素 traitalias _AnyTypeMetaType type_of(AnyType) struct VariadicPack[ elt_is_mutable: Bool, //, origin: Origin[elt_is_mutable], element_trait: _AnyTypeMetaType, *element_types: element_trait, ](RegisterPassable): ..._AnyTypeMetaType type_of(AnyType)意味着element_trait可以是任意 trait——但不能是 struct 或 int 之类的具体类型。LIT 中的元类型lit.meta 与 AnyStruct!lit.anystruct即LIT::AnyStructType是某个 struct 的!lit.meta。!lit.meta是一个trait姑且这么说只有一个特定类型 conform 到它并且它拥有那个类型的所有方法和字段。例如!lit.metaInt就是一个只有Int才 conform 的 trait姑且这么说并且它拥有Int的全部方法和字段。在 LITTypes.td 中LIT_MetaTypeassembly 形式!lit.meta的官方定义是 The singleton metatype of a type——某个类型的单例元类型是一个类型能拥有的最紧tightest的 metatype。打印示例为!lit.metaInt。同一文件中的LIT_TraitType!lit.trait则被定义为 Type of a trait value并且其isMetaType()方法直接返回true——这就是文档所说trait 的 metatype 心智模型在代码层面的落点。需要强调的是!lit.meta、!lit.anystruct这些东西只存在于 LIT 中。它们以及所有 trait最终都会 lower 到 KGEN 的kgen.type也就是 type type代码中的KGEN_TypeType定义见 KGENTypes.td。KGEN 中的元类型kgen.typeTypeTypeKGEN 中只有一个 trait/metatype!kgen.type即 TypeType。在我们的 MLIR 里它经常被简写为type。每一个 Mojo trait 都会 lower 成!kgen.type。于是就有了文档里那句经典的结论所有 trait 都是 metatype所有 trait 都会 lower 成type因此type metatype。文档随后补充了一句Try not to think about it——即不要试图在 Mojo 层面把两者区分开它们在 KGEN 里就是同一个东西。在 KGENTypes.td 中KGEN_TypeType的官方描述是 Type of all types所有类型的类型它表示一个任意类型用于参数化类型泛型的函数和 struct它无法被具体化为 SSA 值因为它在运行时没有表示It cannot be materialized into an SSA value, because it has no runtime representation。这正好呼应了前文类型只有在参数中才算值的说法。与 KGEN 的!kgen.type相关的还有一个!kgen.non_struct_typeKGEN_NonStructTypeType同样定义于 KGENTypes.td它表示所有非 lit-struct 类型即不是 Mojo struct 也不是元类型表达式主要用来归类那些需要被__MLIRType包装的类型例如!kgen.i1这类直接来自 MLIR 的类型。与 C 模板的对应在 KGEN 中x: type与 C 的templatetypename T完全等价。和 C 一样KGEN 的模板是鸭子类型duck-typed的参数只要满足所需的方法/属性即可编译器不会预先做名义上的类型检查。不过两者的约束方式不同见 PassesAndIR.mdC 用typename T声明任意类型参数而 MojoLIT 层要求给参数指定大致形状即一个 trait。例如 C 的templateint N, typename T class Vec在 Mojo 中等价于struct Vec[N: Int, T: Copyable]: ...——其中N的类型是IntT的类型是Copyable。类型是一个相对概念N的类型是IntInt的类型又是别的什么如此层层向上一切皆有类型。总结与延伸阅读Mojo 编译器内部术语的核心可以浓缩为几条规则argument 与 parameter 严格分开argument 是函数层面的实参parameter 是泛型层面的占位符外界常说的 generic parameter 在 Mojo 内部就叫 parameter。参数有声明decl、引用ref、表达式expression、值value四个层次冒号右侧的东西都是参数表达式由参数值构成。只有参数才有值一个类型只有作为参数值用#kgen.type/TypeParamAttr包装时才是值运行时变量与运行时值则对应 MLIR 的 SSA 值与 operation。MLIR 中一切非 operation 即 attribute参数在精化前以参数表达式attribute形式存在精化后以参数值形式存在。metatype 本质是一组需求trait 是共享需求的 metatype 集合supertrait 不是 metatypeLIT 中的!lit.metaInt是单例元类型而 KGEN 中一切 trait 最终统一为!kgen.type。想继续深入推荐在仓库内阅读以下资源PassesAndIR.mdLIT、KGEN 等各 dialect 的职责与编译 pipeline以及大量 Mojo→MLIR 对照示例MojoIRCPPCorrespondence.md同一语义在 Mojo 源码、MLIR 与编译器 C 代码三个域中的对应关系Variadics.mdVariadicPack、_AnyTypeMetaType等变长参数机制的深入讲解LITTypes.td!lit.meta、!lit.trait、!lit.anystruct等 LIT 类型的权威定义KGENTypes.td!kgen.type、!kgen.param、!kgen.generator等 KGEN 类型的权威定义KGENAttrs.td#kgen.typeTypeParamAttr等参数属性attribute的权威定义。【免费下载链接】mojoThe Modular Platform (includes MAX Mojo)项目地址: https://gitcode.com/GitHub_Trending/mo/mojo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价