LIR 参考
LIR 是 GDCC 在较低层使用的函数级中间表示。它面向 GDExtension 这类显式调用边界设计:类型、变量、临时值、控制流、对象生命周期和 Variant 动态边界都需要在 IR 中被明确表达。
这份参考描述 LIR 的公开结构和每条指令的语义意图,不描述 GDCC 内部如何把这些指令落到具体实现。阅读时可以把 LIR 看成一个“接近目标运行时,但仍保留编译器语义”的线性表示。
LIR 组成架构
Section titled “LIR 组成架构”一个 LIR 单元由类定义组成。类定义之下包含信号、属性和函数;函数体是 LIR 的核心执行单元。
- 模块:包含一个或多个类定义,表示一次编译输出中的脚本类集合。
- 类定义:包含类名、父类、注解、信号、属性和函数,描述 Godot 可见的脚本类表面。
- 信号:包含名称和参数列表,描述类发出的 typed signal。
- 属性:包含名称、类型、静态性、初始化 helper 和访问 helper,描述类字段与 Godot 属性表面。
- 函数:包含签名、参数、返回类型、变量表和基本块,表示可执行逻辑。
- 变量表:记录参数、局部变量、常量或栈存储,给指令中的
$id提供类型和存储语义。 - 基本块:包含一个入口、一串普通指令和一个终结指令,表示线性代码片段和控制流节点。
- 指令:包含 opcode、可选结果和操作数,表示具体计算、调用、读写或跳转。
LIR 不采用 SSA。一个值可以通过变量槽、显式赋值、加载和存储继续流动。这样做的设计目标是让 IR 更接近 GDExtension C 代码的生成模型:变量槽、属性槽、返回槽和生命周期边界都可以被直接表达。
基本指令文本形态如下:
($<result_id> = )?<opcode> <operand>...结果值写在左侧,变量操作数使用 $id,字符串和操作符名称使用引号,基本块标签直接写标签名。没有结果的指令只写 opcode 和操作数。
XML 结构和元数据
Section titled “XML 结构和元数据”LIR 文件是 XML 结构:顶层 class_def 之下包含 signals、properties 和 functions。类定义和函数除了承载指令,还携带 Godot 表面和编译器内部所需的元数据。
<class_def name="RotatingCamera" super="Camera3D" is_abstract="false" is_tool="false"> <annotation key="tool" value=""/> <signals>...</signals> <properties>...</properties> <functions>...</functions></class_def>name对匿名类是可选。is_abstract、is_tool可选,默认false。is_tool是脚本级状态:一个@tool脚本编译成多个类(顶层类与内部类)时,每个class_def都写is_tool="true",但只有顶层类带toolannotation。
属性包含名称、类型、静态性、init_func、getter_func、setter_func 和注解。
init_func可选,其返回值用于初始化属性;非静态属性的初始化函数第一个参数必须是表示self的Object类型。getter_func/setter_func可选:getter 接收self并返回属性同类型值,setter 接收self与同类型值;省略时生成默认访问器。@export*家族的 annotationvalue原样编码 Godot 的hint_string:不带引号、不带注解名前缀。例如@export_range(0, 20, 0.5)写成<annotation key="export_range" value="0,20,0.5"/>;空值表示空hint_string。
<function name="get_pitch" is_static="false" is_abstract="false" is_lambda="false" is_vararg="false" is_hidden="false" is_coroutine="false"> <meta call_site_context="<call_site_context>" source_identity_key="<Class>::<enclosingFunc>#<ordinal>"/> <parameters> <parameter name="to_radius" type="bool" default_value_func="_default_get_pitch$to_radius"/> </parameters> <captures> <capture name="value" type="int"/> </captures> <return_type type="float"/> <variables>...</variables> <basic_blocks entry="entry">...</basic_blocks></function>is_coroutine标记栈式协程函数。只有它为true时,函数体内才允许出现await。is_lambda标记 lambda 函数。lambda 必须携带meta,普通函数不携带。meta是 lambda 的热重载身份元数据:source_identity_key采用<Class>::<enclosingFunc>#<ordinal>形式,call_site_context是可选的附加重绑定条件。is_hidden标记编译器生成的辅助函数,例如默认参数 helper_default_get_pitch$to_radius和属性初始化 helper_field_init_pitch_degree;它们不对应源码中可直接调用的方法表面。captures只属于 lambda,记录捕获变量的名称和类型。- 参数的
default_value_func可选。调用方省略该参数时,编译器调用该函数并用其返回值作为实参;没有default_value_func又省略参数则报错。
指令按返回值形态分为三类:
- 必须有结果:指令会产生新值,写作
$result = opcode ...。 - 可选结果:调用类指令可保留返回值,也可在只关心副作用时省略结果。
- 无结果:指令只产生副作用、控制流或调试标记。
操作数常见形态:
- 变量:写作
$value,引用变量表或临时值中的变量 id。 - 字符串:写作
"name",表示类名、函数名、属性名、字符串字面量等。 - 整数、浮点和布尔:写作
1、1.5、true,表示立即值。 - 标签:写作
entry、exit,表示基本块 id。 - 操作符:写作
"ADD"、"LESS",表示 Godot 操作符枚举名称。
数据字面量和赋值
Section titled “数据字面量和赋值”这些指令把常量或已有变量变成 LIR 值。它们通常是表达式 lowering 的叶子节点。
literal_bool
Section titled “literal_bool”创建 bool 常量。适合条件、比较结果替代值、布尔字面量。
$flag = literal_bool trueliteral_int
Section titled “literal_int”创建整数常量。LIR 表面按整数值表达,目标变量类型决定后续边界。
$count = literal_int 42literal_float
Section titled “literal_float”创建浮点常量。用于 float 字面量或需要浮点临时值的位置。
$ratio = literal_float 0.5literal_string
Section titled “literal_string”创建 String 常量,字符串内容按 UTF-8 文本表达。
$text = literal_string "hello"literal_string_name
Section titled “literal_string_name”创建 StringName 常量。适合方法名、属性名、信号名等身份化名称边界。
$name = literal_string_name "position"literal_node_path
Section titled “literal_node_path”创建 NodePath 常量。源码 ^"..." 字面量在 LIR 中写成普通字符串操作数,操作数是去掉 ^ 前缀和外侧引号后的路径文本。
$path = literal_node_path "Camera3D/Pivot"literal_nil
Section titled “literal_nil”创建 Variant 的 Nil 值。用于动态空值或 Variant 空边界。
$v = literal_nilliteral_null
Section titled “literal_null”创建对象空引用。用于对象类型的 null 值。
$obj = literal_nullassign
Section titled “assign”把一个变量的值写入另一个结果变量,显式表达类型兼容边界。
$target = assign $sourceassign 不只是文本拷贝。它表达“把 source 放入 result 所属类型槽”的语义,因此需要满足同类型、对象上行转换、Variant 边界、有限容器协变,或已经按受支持的隐式转换规则物化后的值。
构造、销毁和运算
Section titled “构造、销毁和运算”这些指令负责创建复杂值、处理生命周期,以及执行 Godot 操作符。
construct_builtin
Section titled “construct_builtin”按结果变量类型构造内建值,例如向量、颜色、变换等。参数列表对应构造输入。
$vec = construct_builtin $x $y $zconstruct_array
Section titled “construct_array”构造 Array 或 Packed*Array。普通 Array 可带元素类型提示;packed array 由结果变量类型决定。
$items = construct_array "Node"$packed = construct_arrayconstruct_dictionary
Section titled “construct_dictionary”构造 Dictionary,可选择提供 key/value 类型名。省略类型名表示动态字典。
$map = construct_dictionary "StringName" "Node"construct_container_literal
Section titled “construct_container_literal”从已经物化的操作数槽构造并填充一个新的 Array 或 Dictionary。容器族只由结果变量类型决定:
- 结果类型必须是普通(非 ref)
Array或Dictionary,不能是Packed*Array或其它内建类型。 Array的操作数按源码顺序作为 element0..elementN;空操作数合法。Dictionary的操作数按 key0/value0/key1/value1… 成对排列,数量必须为偶数。- 操作数到元素/键/值类型的边界转换(如
int -> float、String -> StringName、Variant 打包/解包)由前端在发射该指令前完成。
该指令不替代构造空容器的 construct_array / construct_dictionary;__prepare__、属性默认初始化和显式空容器构造仍使用后者。
$arr = construct_container_literal $a $b $c$dict = construct_container_literal $k0 $v0 $k1 $v1construct_object
Section titled “construct_object”构造某个类的对象实例。结果变量类型应能承载该类实例。
$node = construct_object "Node"construct_signal
Section titled “construct_signal”从存活的对象 receiver 和编译期信号名构造 Signal 值。结果是一个可销毁的内建值,不会延长 receiver 的生命周期。
$sig = construct_signal $self "timeout"construct_callable
Section titled “construct_callable”从实例 receiver 和编译期方法名构造 Callable。操作数固定为 (receiver, method_name) 两个;旧的一操作数形式非法。
$cb = construct_callable $self "_on_timeout"receiver 的静态类型决定分派方式:
Object类型:通过godot_new_Callable_with_Object_StringName绑定到该对象实例。- 非对象内建类型:先打包成临时
Variant,再创建绑定到该值的Callable(静态包装不接收self,内部 ptrcall base 为NULL)。 Variant、静态方法或 utility 函数:非法,不能作为 receiver。
结果同样是可销毁的内建值,不会延长 receiver 的生命周期。静态或全局函数应使用 construct_standalone_callable。
construct_standalone_callable
Section titled “construct_standalone_callable”为没有实例 receiver 的编译期已知函数构造 Callable。kind 取 utility、static_gdcc 或 static_engine:
utility:utility 函数,owner 必须为空字符串。static_gdcc/static_engine:静态函数,owner 必须是类名。
$cb = construct_standalone_callable "utility" "" "print"$cb2 = construct_standalone_callable "static_gdcc" "MathUtil" "clamp_i"construct_lambda
Section titled “construct_lambda”从 lambda 函数和捕获列表创建 Callable。第一个操作数是 lambda 函数名,之后是捕获变量。
$cb = construct_lambda "lambda_1" $self $valuedestruct
Section titled “destruct”销毁或释放变量持有的可销毁资源。只应出现在有明确来源的生命周期路径中。
destruct $value "AUTO_GENERATED"try_own_object
Section titled “try_own_object”尝试为对象取得引用所有权;非引用计数对象上语义上可为空操作。
try_own_object $obj "INTERNAL"try_release_object
Section titled “try_release_object”尝试释放对象引用所有权;非引用计数对象上语义上可为空操作。
try_release_object $obj "USER_EXPLICIT"生命周期指令可以带来源字符串:AUTO_GENERATED、INTERNAL、USER_EXPLICIT 或 UNKNOWN。省略时等价于兼容旧格式的未知来源。新的 LIR 应尽量显式写出来源,避免无法判断这条生命周期操作由谁负责。
unary_op
Section titled “unary_op”执行一元 Godot 操作符,例如取负或逻辑非。
$out = unary_op "NEGATE" $value可用的一元操作符:
| 操作符 | 常见源码写法 |
|---|---|
NEGATE | unary - |
POSITIVE | unary + |
BIT_NOT | ~ |
NOT | not、! |
binary_op
Section titled “binary_op”执行二元 Godot 操作符,例如加法、比较、位运算。
$sum = binary_op "ADD" $left $right可用的二元操作符:
| 操作符 | 常见源码写法 |
|---|---|
EQUAL | == |
NOT_EQUAL | != |
LESS | < |
LESS_EQUAL | <= |
GREATER | > |
GREATER_EQUAL | >= |
ADD | binary + |
SUBTRACT | binary - |
MULTIPLY | * |
DIVIDE | / |
MODULE | % |
POWER | ** |
SHIFT_LEFT | << |
SHIFT_RIGHT | >> |
BIT_AND | & |
BIT_OR | | |
BIT_XOR | ^ |
AND | and、&& |
OR | or、|| |
XOR | xor |
IN | in |
LIR 文本中的操作符操作数写枚举名,例如 "ADD"、"NEGATE",不是写源码符号 + 或 -。
补充约定:
MODULE同时承载数值取模和字符串格式化:左操作数为String时,%按 Godot 的格式化语义执行。not in按复合规则支持,等价于对IN的结果取NOT。- 对象值在 LIR 中携带类型身份,因此对象之间的
EQUAL/NOT_EQUAL按 Godot 的实例 identity 语义比较;它不要求两侧静态类相关,也不等同于is的继承判断。
索引和动态访问
Section titled “索引和动态访问”索引指令面向 Variant、字典、数组、对象和动态 keyed/named/indexed 访问。它们把“通过 key/name/index 访问一个动态承载值”的语义显式拆出来。
variant_get
Section titled “variant_get”通过另一个 Variant key 从 Variant 中读取值。适合最通用的动态 get。
$out = variant_get $variant $keyvariant_get_keyed
Section titled “variant_get_keyed”通过 key 从 keyed Variant 读取值,通常用于字典类访问。
$out = variant_get_keyed $dict_like $keyvariant_get_named
Section titled “variant_get_named”通过 StringName 从 named Variant 读取值,通常用于对象属性或命名成员。
$out = variant_get_named $object_like $namevariant_get_indexed
Section titled “variant_get_indexed”通过整数 index 从 Variant 读取值,通常用于数组或 packed array。
$out = variant_get_indexed $array_like $indexvariant_set
Section titled “variant_set”通过 Variant key 向 Variant 写入值。
variant_set $variant $key $valuevariant_set_keyed
Section titled “variant_set_keyed”通过 key 向 keyed Variant 写入值。
variant_set_keyed $dict_like $key $valuevariant_set_named
Section titled “variant_set_named”通过 StringName 向 named Variant 写入值。
variant_set_named $object_like $name $valuevariant_set_indexed
Section titled “variant_set_indexed”通过整数 index 向 Variant 写入值。
variant_set_indexed $array_like $index $value这些指令的结果通常仍是 Variant 或后续要进入 typed boundary 的值。若后续需要稳定类型,应配合 unpack_variant、assign 或显式构造边界。
类型和 Variant 边界
Section titled “类型和 Variant 边界”类型指令用于运行时类型查询、对象判断、对象转换,以及稳定类型和 Variant 之间的打包/解包。
get_variant_type
Section titled “get_variant_type”读取 Variant 当前承载值的 Godot 类型 id。
$type_id = get_variant_type $variantget_class_name
Section titled “get_class_name”取得变量的类型名或对象类名,结果是 String。
$name = get_class_name $valueobject_cast
Section titled “object_cast”当 as 的目标是对象类时执行运行时类转换。源可以是 Object、Variant 或 Nil。
$node = object_cast "Node" $value成功时返回目标类型的同实例对象(携带目标类型身份),不改变所有权类别;失败(空引用、已释放、非对象 Variant 或类不匹配)时产生 null。结果可以省略,此时转换在表面上只做校验。
builtin_cast
Section titled “builtin_cast”当 as 的目标是非对象、非 Variant、非 Nil 的内建类型时执行运行时转换,包括参数化的 Array[T] / Dictionary[K, V]。语义对齐 Godot 的 Variant::construct / can_convert。
$v = builtin_cast "Vector2" $value$ints = builtin_cast "PackedInt32Array" $value- 结果必须存在;参数化容器保留完整声明类型文本。
- 完全同类型和
as Variant使用assign/pack_variant,不走这里。 - 例外:同族
Packed*Array的as不是保持身份的assign,而会产生一个 COW 新身份副本,与 Godot 4.5 解释器一致。Packed 的is测试则是精确的Variant.get_type()族匹配。
is_instance_of
Section titled “is_instance_of”判断值是否为编译期指定类型的实例(源码 is)。$value 可以是任意普通有类型的值,包括 Variant、内建类型和参数化容器,不再只限对象。
$ok = is_instance_of "Node" $value$is_int_array = is_instance_of "Array[int]" $value- 结果始终是
bool;对于非Variant目标,null/ nil 对象得到false。 - 类名使用 canonical / Godot 面向的名称;参数化容器使用完整类型文本。
Variant是顶层类型:x is Variant会被折叠为true,x is not Variant折叠为false,稳定的 LIR 不应携带is_instance_of "Variant"。- 裸
Array/Dictionary针对参数化目标(如"Array[T]")仍然执行运行时类型元数据检查,不能只依据静态裸槽类型折叠。
pack_variant
Section titled “pack_variant”把稳定类型值装入 Variant。用于进入动态边界。
$variant = pack_variant $valueunpack_variant
Section titled “unpack_variant”从 Variant 解出结果变量所需类型。用于离开动态边界。
$value = unpack_variant $variantpack_variant 和 unpack_variant 是 LIR 中最重要的动态边界标记。它们让 Variant 的进入和离开变成显式动作,而不是由调用或赋值隐式猜测。
variant_is_nil
Section titled “variant_is_nil”判断 Variant 是否为 Nil。
$is_nil = variant_is_nil $variantobject_is_null
Section titled “object_is_null”判断对象引用是否为空。
$is_null = object_is_null $obj控制流指令只能作为基本块终结指令使用。每个基本块应以一个终结指令结束,从而让控制流图清晰可验证。
无条件跳转到目标基本块。
goto exit根据 bool 条件跳转到 true/false 两个基本块。
go_if $cond then_block else_blockgo_if 的条件应是布尔值。来自 Variant 或其它稳定类型的条件通常需要先经过显式归一化,再进入 go_if。
return
Section titled “return”从当前函数返回,可带返回值,也可无返回值。
returnreturn $value调用指令都允许结果可选:如果调用返回值被使用,就写 $result = ...;如果只需要副作用,可以省略结果。
call_global
Section titled “call_global”调用全局函数,例如全局工具函数或 Godot 全局函数。
$out = call_global "print" $messagecall_method
Section titled “call_method”调用对象实例方法。第一个变量操作数是 receiver,之后是参数。
$out = call_method "get_child" $node $indexcall_super_method
Section titled “call_super_method”调用父类方法。用于源码中的 super 调用语义。
call_super_method "_ready" $selfcall_static_method
Section titled “call_static_method”调用类上的静态方法。
$out = call_static_method "MathUtil" "clamp_i" $value $min $maxcall_intrinsic
Section titled “call_intrinsic”调用后端按名称注册的 intrinsic。需要显式物化的隐式转换会使用这种形态,例如 c_int_to_float 和 c_vector3i_to_vector3。
$out = call_intrinsic "intrinsic_name" $arg调用参数按顺序列出。返回值是否保留应由源码上下文决定:表达式位置通常需要结果,statement 位置的 void 或纯副作用调用通常不需要结果。
挂起当前协程函数,直到被等待的操作数产生结果,然后把该结果作为指令值发布。挂起在运行时是栈式协程帧上的栈切换;在 LIR 表面上 await 仍然像一条同步指令,不拆分基本块。
$result = await $operand- 只能在
is_coroutine="true"的函数中。 - 操作数的静态类型决定分派路径:
Signal:一次性等待信号。compiler::GdccCoroState:等待一个静态协程调用,即call_method、call_super_method或call_static_method命中协程函数时产生的结果;这类调用的结果变量必须声明为该类型。Variant:运行时动态分派。
- 其它静态类型非法。分派完全由静态类型驱动,不做名字推导或跨指令配对。
- 结果类型由前端为结果变量发布:0 参信号为
Variant;1 参信号为声明的参数类型;多参信号为Array[Variant];协程调用为被调函数的声明返回类型(void被调函数得到承载 nil 的Variant);动态等待为Variant。 await是产出值的普通指令,不是基本块终结指令,因此不改变入口块和终结指令规则。compiler::GdccCoroState的单消费者所有权规则见编译器专用类型。
属性和静态存取
Section titled “属性和静态存取”这组指令表达对象属性和静态成员的直接读写。它们与 variant_get_named / variant_set_named 的区别是:这里的属性名和类名是 LIR 表面上的稳定字符串,而不是动态 Variant 路径。
load_property
Section titled “load_property”从对象读取命名属性。
$value = load_property "position" $nodestore_property
Section titled “store_property”向对象写入命名属性。
store_property "position" $node $valueload_static
Section titled “load_static”读取类上的静态变量或静态属性。
$value = load_static "GameState" "score"store_static
Section titled “store_static”写入类上的静态变量或静态属性。
store_static "GameState" "score" $value属性读写应由类型和可见性分析保证目标存在、可读或可写。LIR 本身通过指令形态表达“这是一个稳定属性路径”,而不是重新描述源码解析规则。
空操作。用于占位、测试或保持指令序列结构。
nopline_number
Section titled “line_number”标记后续指令对应的源码行号,服务诊断或调试信息。line_number 不改变程序语义。
line_number 27assert
Section titled “assert”用户级条件断言。条件为 false 时,当前函数进入稳定的运行时错误/默认返回清理路径(goto __finally__)。
assert $condassert $cond $message$cond 必须已经是 bool;真值归一化属于前端 lowering,不由该指令处理。可选 $message 必须可赋给 String。该指令无结果、不改变对象状态、不需要生命周期来源,也不是终结指令。
assert_object_live
Section titled “assert_object_live”对象存活解引用保护。在解引用对象引用前断言其仍存活;对象为空或已释放时进入与 assert 相同的清理路径。
assert_object_live $node该指令只用于方法 receiver、属性 receiver、_super 链访问和直接访问 GDCC 字段/方法所有者,不用于用户条件检查、相等比较或所有权操作。self 在方法执行期间保证存活,因此豁免;RefCounted 对象只要持有引用就保证存活,同样豁免。
- 数据:
literal_bool、literal_int、literal_float、literal_string、literal_string_name、literal_node_path、literal_nil、literal_null、assign。 - 构造、生命周期和运算:
construct_builtin、construct_array、construct_dictionary、construct_container_literal、construct_object、construct_signal、construct_callable、construct_standalone_callable、construct_lambda、destruct、try_own_object、try_release_object、unary_op、binary_op。 - 索引和动态访问:
variant_get、variant_get_keyed、variant_get_named、variant_get_indexed、variant_set、variant_set_keyed、variant_set_named、variant_set_indexed。 - 类型:
get_variant_type、get_class_name、object_cast、builtin_cast、is_instance_of、pack_variant、unpack_variant、variant_is_nil、object_is_null。 - 控制流:
goto、go_if、return。 - 调用:
call_global、call_method、call_super_method、call_static_method、call_intrinsic。 - 协程:
await。 - 存取:
load_property、store_property、load_static、store_static。 - 其它:
nop、line_number、assert、assert_object_live。