Skip to content

LIR Reference

LIR is GDCC’s lower-level function IR. It is designed around explicit GDExtension-facing boundaries: types, variable slots, temporary values, control flow, object lifecycle, and Variant transitions are represented directly in the IR.

This reference describes the public LIR surface and the intent of each instruction. It does not document GDCC’s concrete backend implementation strategy.

An LIR unit is organized around class definitions. A class contains signals, properties, and functions; functions contain the executable body.

  • Module: contains one or more class definitions emitted by a compile unit.
  • Class definition: contains the class name, superclass, annotations, signals, properties, and functions that make up the Godot-visible class surface.
  • Signal: contains a name and parameter list for a typed signal declaration.
  • Property: contains a name, type, static flag, initialization helper, and accessor helpers for field/property surface.
  • Function: contains the signature, parameters, return type, variable table, and basic blocks for executable logic.
  • Variables: record parameters, locals, constants, and stack slots addressed by $id.
  • Basic block: contains one entry, ordinary instructions, and one terminator instruction.
  • Instruction: contains an opcode, optional result, and operands for a computation, call, read/write, or jump.

LIR is not SSA. Values flow through explicit variable slots, assignments, loads, stores, and returns. That shape keeps the IR close to GDExtension C generation without making the generated C details part of this reference.

The text form is:

($<result_id> = )?<opcode> <operand>...

Variable operands use $id, strings and operator names use quotes, and block labels are written as bare labels.

An LIR file is XML: a top-level class_def contains signals, properties, and functions. Besides instructions, class definitions and functions carry metadata for both the Godot-visible surface and compiler-internal needs.

<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 optional for anonymous classes.
  • is_abstract and is_tool are optional and default to false.
  • is_tool is script-level state: when a @tool script compiles to multiple classes (top-level plus inner classes), every class_def carries is_tool="true", but only the top-level class carries the tool annotation.

A property carries a name, type, static flag, init_func, getter_func, setter_func, and annotations.

  • init_func is optional; its return value initializes the property. For a non-static property, the initializer’s first parameter must be an Object type representing self.
  • getter_func / setter_func are optional: the getter receives self and returns the property’s type, while the setter receives self and a value of that type. A default accessor is generated when they are omitted.
  • For the @export* family, the annotation value encodes the Godot hint_string verbatim: no quotes and no annotation-name prefix. For example, @export_range(0, 20, 0.5) becomes <annotation key="export_range" value="0,20,0.5"/>; an empty value means an empty 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 marks a stackful-coroutine function. await is only legal inside a function where it is true.
  • is_lambda marks a lambda function. Lambdas must carry meta; plain functions never do.
  • meta is the lambda hot-reload identity metadata: source_identity_key uses the <Class>::<enclosingFunc>#<ordinal> form, and call_site_context is an optional additional rebind gate.
  • is_hidden marks compiler-generated helper functions, such as the default-argument helper _default_get_pitch$to_radius and the field initializer _field_init_pitch_degree; they are not part of the source-callable method surface.
  • captures belongs only to lambdas and records capture names and types.
  • A parameter’s default_value_func is optional. When the caller omits the argument, the compiler calls that function and uses its return value; omitting the argument without a default_value_func is an error.

Instructions can require a result, allow an optional result, or produce no result.

  • Required result: the instruction produces a new value, written as $result = opcode ....
  • Optional result: call instructions may keep the return value or omit it when only side effects matter.
  • No result: the instruction only produces side effects, control flow, or debug metadata.

Common operand forms:

  • Variable: $value, referring to a variable id or temporary id.
  • String: "name", used for class names, function names, property names, and string literals.
  • Number or bool: 1, 1.5, true, used for immediate literal operands.
  • Label: entry, used for basic block ids.
  • Operator: "ADD", used for Godot operator enum names.

These instructions turn constants or existing variables into LIR values. They are usually expression-lowering leaves.

Creates a bool constant.

$flag = literal_bool true

Creates an integer constant.

$count = literal_int 42

Creates a floating-point constant.

$ratio = literal_float 0.5

Creates a UTF-8 String constant.

$text = literal_string "hello"

Creates a StringName constant for identity-like names such as methods, properties, and signals.

$name = literal_string_name "position"

Creates a NodePath constant. A source ^"..." literal becomes an ordinary string operand; the operand is the path text with the ^ prefix and outer quotes removed.

$path = literal_node_path "Camera3D/Pivot"

Creates a Variant Nil value.

$v = literal_nil

Creates a null object reference.

$obj = literal_null

Moves a source value into a target-typed result slot.

$target = assign $source

assign represents a typed boundary. The source must be assignable to the result variable’s type, such as same-type assignment, object upcast, Variant boundary, supported container covariance, or a value already materialized by a supported implicit conversion rule.

These instructions create complex values, maintain lifecycle boundaries, and execute Godot operators.

Constructs a builtin value using the result variable’s type.

$vec = construct_builtin $x $y $z

Constructs Array or Packed*Array. A normal Array may carry an element type hint; a packed array is selected from the result variable type.

$items = construct_array "Node"
$packed = construct_array

Constructs a typed or generic Dictionary.

$map = construct_dictionary "StringName" "Node"

Constructs and fills a fresh Array or Dictionary from already-materialized operand slots. The container family is decided solely by the result variable type:

  • The result type must be a non-ref ordinary Array or Dictionary; never Packed*Array or another builtin.
  • For Array, operands are element0..elementN in source order; empty operands are legal.
  • For Dictionary, operands are key0/value0/key1/value1… and the count must be even.
  • Operand-to-element/key/value boundary conversions (such as int -> float, String -> StringName, Variant pack/unpack) are emitted by the frontend before this instruction.

This instruction does not replace the empty-container construct_array / construct_dictionary, which remain the contract for __prepare__, property default initialization, and explicit empty construction.

$arr = construct_container_literal $a $b $c
$dict = construct_container_literal $k0 $v0 $k1 $v1

Constructs an object of a named class.

$node = construct_object "Node"

Constructs a Signal value from a live object receiver and a compile-time signal name. The result is a destroyable builtin value and does not keep the receiver alive.

$sig = construct_signal $self "timeout"

Creates a Callable from an instance receiver and a compile-time method name. The operands are exactly (receiver, method_name); the old one-operand form is illegal.

$cb = construct_callable $self "_on_timeout"

The receiver’s static type selects the dispatch path:

  • Object type: bind via godot_new_Callable_with_Object_StringName.
  • Non-Object builtin: pack a temporary Variant, then create a Callable bound to that value (the static wrapper takes no self; its internal ptrcall base is NULL).
  • Variant, static method, or utility function: illegal as a receiver.

The result is a destroyable builtin value and does not keep the receiver alive. Static or global functions should use construct_standalone_callable.

Creates a Callable for a compile-time known function that has no instance receiver. kind is one of utility, static_gdcc, or static_engine:

  • utility: a utility function; the owner must be the empty string.
  • static_gdcc / static_engine: a static function; the owner must be a class name.
$cb = construct_standalone_callable "utility" "" "print"
$cb2 = construct_standalone_callable "static_gdcc" "MathUtil" "clamp_i"

Creates a Callable from a lambda and capture variables. The first operand is the lambda function name; the remaining operands are captures.

$cb = construct_lambda "lambda_1" $self $value

Destroys or releases a resource held by a variable, with provenance.

destruct $value "AUTO_GENERATED"

Tries to take ownership of an object reference. For non-reference-counted objects, this may be a semantic no-op.

try_own_object $obj "INTERNAL"

Tries to release ownership of an object reference. For non-reference-counted objects, this may be a semantic no-op.

try_release_object $obj "USER_EXPLICIT"

Lifecycle provenance may be AUTO_GENERATED, INTERNAL, USER_EXPLICIT, or UNKNOWN. New LIR should prefer explicit provenance.

Executes a unary Godot operator.

$out = unary_op "NEGATE" $value

Available unary operators:

OperatorCommon source spelling
NEGATEunary -
POSITIVEunary +
BIT_NOT~
NOTnot, !

Executes a binary Godot operator.

$sum = binary_op "ADD" $left $right

Available binary operators:

OperatorCommon source spelling
EQUAL==
NOT_EQUAL!=
LESS<
LESS_EQUAL<=
GREATER>
GREATER_EQUAL>=
ADDbinary +
SUBTRACTbinary -
MULTIPLY*
DIVIDE/
MODULE%
POWER**
SHIFT_LEFT<<
SHIFT_RIGHT>>
BIT_AND&
BIT_OR|
BIT_XOR^
ANDand, &&
ORor, ||
XORxor
INin

The LIR text operand uses the enum name, such as "ADD" or "NEGATE", not the source symbol + or -.

Additional conventions:

  • MODULE carries both numeric modulo and string formatting: when the left operand is a String, % follows Godot’s formatting semantics.
  • not in is supported as a composite rule, equivalent to applying NOT to the result of IN.
  • Object values carry type identity in LIR, so EQUAL / NOT_EQUAL between objects compares instance identity as in Godot; it does not require the static classes to be related, and it is not an inheritance test like is.

These instructions are the dynamic access surface for Variant, dictionary-like, array-like, object-like, keyed, named, and indexed access.

Reads from a Variant with a Variant key.

$out = variant_get $variant $key

Reads from a keyed Variant, usually dictionary-like.

$out = variant_get_keyed $dict_like $key

Reads from a named Variant using StringName.

$out = variant_get_named $object_like $name

Reads from a Variant using an integer index.

$out = variant_get_indexed $array_like $index

Writes to a Variant with a Variant key.

variant_set $variant $key $value

Writes to a keyed Variant.

variant_set_keyed $dict_like $key $value

Writes to a named Variant.

variant_set_named $object_like $name $value

Writes to a Variant using an integer index.

variant_set_indexed $array_like $index $value

Results often remain Variant until a later unpack_variant, assign, or explicit typed boundary.

Type instructions handle runtime type queries, object tests, object casts, and stable-type to Variant transitions.

Reads the Godot type id held by a Variant.

$type_id = get_variant_type $variant

Gets a static type name or runtime object class name as String.

$name = get_class_name $value

Performs a runtime class cast when the as target is an object class. The source may be Object, Variant, or Nil.

$node = object_cast "Node" $value

On success it returns the same instance represented as the target type (carrying the target type identity) without changing ownership category; on failure (null, freed, non-object Variant payload, or class mismatch) it produces null. The result is optional, in which case the cast is validated but not materialized.

Performs a runtime conversion when the as target is a non-Object, non-Variant, non-Nil builtin, including parameterized Array[T] / Dictionary[K, V]. Semantics align with Godot’s Variant::construct / can_convert.

$v = builtin_cast "Vector2" $value
$ints = builtin_cast "PackedInt32Array" $value
  • The result is required; parameterized containers keep their full declared type text.
  • Exact same-type and as Variant use assign / pack_variant instead.
  • Exception: a same-family Packed*Array as is not an identity-preserving assign; it produces a COW copy with a fresh identity, matching the Godot 4.5 interpreter. Packed is tests are exact Variant.get_type() family matches.

Tests whether a value is an instance of the compile-time type (source is). $value may be any ordinary typed value, including Variant, builtins, and parameterized containers; it is no longer limited to objects.

$ok = is_instance_of "Node" $value
$is_int_array = is_instance_of "Array[int]" $value
  • The result is always bool; for non-Variant targets, null / a nil object yields false.
  • Class names use canonical / Godot-facing names; parameterized containers use full type text.
  • Variant is the top type: x is Variant is folded to true and x is not Variant to false, so stable LIR should not carry is_instance_of "Variant".
  • A bare Array / Dictionary tested against a parameterized target (such as "Array[T]") still performs a runtime typed-metadata check and must not be folded away based only on the static bare slot type.

Packs a stable value into Variant.

$variant = pack_variant $value

Unpacks a Variant into the result variable’s type.

$value = unpack_variant $variant

pack_variant and unpack_variant are the explicit markers for entering and leaving dynamic value space.

Tests whether a Variant is Nil.

$is_nil = variant_is_nil $variant

Tests whether an object reference is null.

$is_null = object_is_null $obj

Control-flow instructions terminate basic blocks.

Unconditionally branches to a basic block.

goto exit

Branches to true/false blocks based on a bool.

go_if $cond then_block else_block

go_if expects a boolean condition. Dynamic or non-boolean source conditions should be normalized before the branch.

Returns from the current function, with or without a value.

return
return $value

Call instructions allow optional results. Keep the result when the surrounding expression needs it; omit it for side-effect-only calls.

Calls a global function.

$out = call_global "print" $message

Calls an instance method. The first variable operand is the receiver.

$out = call_method "get_child" $node $index

Calls a superclass method.

call_super_method "_ready" $self

Calls a static method on a class.

$out = call_static_method "MathUtil" "clamp_i" $value $min $max

Calls a backend-owned intrinsic registered by name. Implicit conversions that need materialization use this shape, such as c_int_to_float and c_vector3i_to_vector3.

$out = call_intrinsic "intrinsic_name" $arg

Call arguments are listed in source order. Retain the result only when the surrounding expression needs it.

Suspends the enclosing coroutine function until the awaited operand produces a result, then publishes that result as the instruction value. At runtime, suspension is a stack switch on the stackful coroutine frame; at the LIR surface await still looks synchronous and does not split basic blocks.

$result = await $operand
  • It is legal only inside a function whose is_coroutine attribute is true; backend validation fails fast when it appears in a plain function.
  • The operand’s static type selects the dispatch path:
    • Signal: a one-shot signal wait.
    • compiler::GdccCoroState: a static coroutine-call wait, i.e. the result produced when call_method, call_super_method, or call_static_method targets a coroutine; such a call must declare its result variable with this type.
    • Variant: runtime dynamic dispatch.
  • Any other static type is illegal. Dispatch is purely static-type-driven; there is no name-derived pairing or cross-instruction bookkeeping.
  • The frontend publishes the result type: 0-arg signal → Variant; 1-arg signal → the declared argument type; multi-arg signal → Array[Variant]; coroutine call → the callee’s declared return type (a void callee yields a Variant holding nil); dynamic wait → Variant.
  • await is a value-producing ordinary instruction, not a block terminator, so entry-block and terminator rules are unaffected.
  • The single-consumer ownership rules for compiler::GdccCoroState are described in Compiler-Only Types.

These instructions represent stable property/static paths, unlike variant_get_named and variant_set_named, which operate through dynamic Variant access.

Reads a named property from an object.

$value = load_property "position" $node

Writes a named property on an object.

store_property "position" $node $value

Reads a static variable or property.

$value = load_static "GameState" "score"

Writes a static variable or property.

store_static "GameState" "score" $value

Does nothing; useful as a placeholder or test instruction.

nop

Marks the current source line for diagnostics or debug data.

line_number 27

User-level condition assertion. When the condition is false, the current function enters its stable runtime-error/default-return cleanup path (goto __finally__).

assert $cond
assert $cond $message

$cond must already be bool; truthiness normalization is the frontend lowering’s responsibility. The optional $message must be assignable to String. The instruction has no result, does not mutate object state, requires no lifecycle provenance, and is not a terminator.

Hard-fail dereference guard. It asserts that an object reference is still live before use; if the object is null or freed, the function enters the same cleanup path as assert.

assert_object_live $node

It is used only for method receivers, property receivers, _super chain access, and direct GDCC field/method owners; not for user conditional checks, equality, or ownership operations. self is guaranteed live during method execution and is exempt; RefCounted objects are also exempt because holding a reference guarantees liveness.

  • Data: literal_bool, literal_int, literal_float, literal_string, literal_string_name, literal_node_path, literal_nil, literal_null, assign.
  • Construction, lifecycle, and operators: 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.
  • Indexing and dynamic access: variant_get, variant_get_keyed, variant_get_named, variant_get_indexed, variant_set, variant_set_keyed, variant_set_named, variant_set_indexed.
  • Type: get_variant_type, get_class_name, object_cast, builtin_cast, is_instance_of, pack_variant, unpack_variant, variant_is_nil, object_is_null.
  • Control flow: goto, go_if, return.
  • Calls: call_global, call_method, call_super_method, call_static_method, call_intrinsic.
  • Coroutines: await.
  • Access: load_property, store_property, load_static, store_static.
  • Misc: nop, line_number, assert, assert_object_live.