This page covers the back half of the GDScript processing pipeline: the compiler that converts a type-annotated AST into register-based bytecode, and the virtual machine that executes that bytecode at runtime. For the front half of the pipeline (tokenization and parsing into an AST), see GDScript Parser. For semantic analysis and type resolution, see GDScript Analyzer. For script resource lifecycle and caching, see GDScript Runtime & Cache.
After GDScriptAnalyzer has fully resolved types on the AST, GDScriptCompiler walks the tree and emits bytecode through an abstract code-generation interface. The resulting GDScriptFunction objects are stored on the GDScript resource. At runtime, GDScriptFunction::call() is the VM entry point.
GDScript compilation and execution pipeline
Sources: modules/gdscript/gdscript_compiler.cpp1-50 modules/gdscript/gdscript_byte_codegen.cpp1-60 modules/gdscript/gdscript_vm.cpp1-50 modules/gdscript/gdscript.cpp670-686
GDScriptCompiler)Defined in modules/gdscript/gdscript_compiler.h and modules/gdscript/gdscript_compiler.cpp
GDScript::reload() invokes GDScriptCompiler::compile(parser, script, keep_state) after the parser and analyzer have completed. The compiler takes ownership of emitting all bytecode for the script and its inner classes.
The public interface:
GDScriptCompiler::compile(
const GDScriptParser *p_parser,
GDScript *p_script,
bool p_keep_state
) -> Error
Internally it calls _compile_class() recursively for each ClassNode, then _parse_function() for each member function.
Sources: modules/gdscript/gdscript_compiler.cpp1-44 modules/gdscript/gdscript.cpp670-686
GDScriptCompiler uses a private CodeGen struct as a compilation context, threaded through all _parse_* methods:
| Field | Type | Purpose |
|---|---|---|
script | GDScript* | The script being compiled |
class_node | ClassNode* | Current class AST node |
function_node | FunctionNode* | Current function AST node |
function_name | StringName | Name of the current function |
generator | GDScriptCodeGenerator* | Bytecode emitter |
parameters | HashMap<StringName, Address> | Function parameter addresses |
locals | HashMap<StringName, Address> | Local variable addresses |
Sources: modules/gdscript/gdscript_compiler.h1-80
_parse_function() creates a GDScriptByteCodeGenerator, registers all parameters and locals, then calls _parse_block() for the function body. At the end, generator->end_function() finalizes the GDScriptFunction object and the function is registered on the GDScript via member_functions[name].
Special functions compiled for every class:
| Function | AST trigger | Purpose |
|---|---|---|
@implicit_new | Always | Zero-argument init, calls @onready vars |
@implicit_ready | @onready vars exist | Runs @onready assignments in _ready |
@static_initializer | Static var initializers | Initializes static class variables |
_init / user-defined | func _init() | Explicit constructor |
Sources: modules/gdscript/gdscript.h168-176 modules/gdscript/gdscript_compiler.cpp44-210
_parse_expression() is the core method, dispatching on ExpressionNode::type. For identifiers, it searches scope in order:
Identifier lookup order (from innermost to outermost)
Sources: modules/gdscript/gdscript_compiler.cpp254-430
When the compiler can statically verify that all argument types exactly match a native MethodBind's parameters, it emits OPCODE_CALL_METHOD_BIND_VALIDATED_RETURN / OPCODE_CALL_METHOD_BIND_VALIDATED_NO_RETURN instead of the generic OPCODE_CALL_METHOD_BIND. This bypasses runtime argument type coercion.
The check is performed by the file-local function _can_use_validate_call():
_can_use_validate_call(
const MethodBind *p_method,
const Vector<Address> &p_arguments
) -> bool
It returns false if the method is vararg, if argument counts differ (default args), or if any argument type is not an exact match via _is_exact_type().
Sources: modules/gdscript/gdscript_compiler.cpp209-253
GDScriptCodeGeneratorDefined in modules/gdscript/gdscript_codegen.h This abstract class decouples the compiler from the specific bytecode format. All emit calls go through this interface.
GDScriptCodeGenerator::Address is the operand type used throughout:
AddressType | Meaning |
|---|---|
SELF | The script instance (self) |
CLASS | The class object (static context) |
MEMBER | An instance member variable, index into GDScriptInstance::members |
CONSTANT | Index into the function's constants pool |
LOCAL_VARIABLE | A named local on the stack |
TEMPORARY | An anonymous temporary slot |
NIL | The null/nil value |
GLOBAL | GDScript global (autoloads, etc.) |
NAMED_GLOBAL | A global accessed by StringName |
STACK | Raw stack index |
Sources: modules/gdscript/gdscript_codegen.h1-120
GDScriptByteCodeGeneratorDefined in modules/gdscript/gdscript_byte_codegen.h and modules/gdscript/gdscript_byte_codegen.cpp
Stack layout produced by add_local() and add_temporary():
Stack index 0 .. FIXED_ADDRESSES_MAX-1: Reserved (self=0, class=1, nil=2)
Stack index FIXED_ADDRESSES_MAX .. N: Function parameters and locals
Stack index N+1 .. M: Temporaries (recycled via free-list)
FIXED_ADDRESSES_MAX is defined on GDScriptFunction and equals 3.
Key internal tables:
| Member | Purpose |
|---|---|
locals | Vector<StackSlot> — type info per stack slot |
temporaries | Pool of reusable anonymous stack slots |
constant_map | Deduplication map: Variant → int (index into constants array) |
name_map | Deduplication map: StringName → int (index into global_names array) |
output | The flat Vector<int> being built (opcodes + encoded operands) |
patches | Jump addresses to back-patch once the target instruction is known |
add_temporary(type) allocates a slot from the free pool or extends the stack. pop_temporary() returns it to the free pool. All temporaries are freed at each statement boundary.
Sources: modules/gdscript/gdscript_byte_codegen.cpp35-200 modules/gdscript/gdscript_byte_codegen.h1-120
GDScriptFunction — The Compiled ArtifactDefined in modules/gdscript/gdscript_function.h and modules/gdscript/gdscript_function.cpp
Each compiled GDScript function is represented as a GDScriptFunction object. These are heap-allocated and stored on GDScript::member_functions.
Key data members:
| Member | Type | Purpose |
|---|---|---|
code | Vector<int> | Flat bytecode: opcodes and encoded operand addresses |
constants | Vector<Variant> | Constants pool referenced by ADDR_TYPE_CONSTANT |
global_names | Vector<StringName> | Name pool for keyed access and calls |
argument_types | Vector<GDScriptDataType> | Type info per parameter |
return_type | GDScriptDataType | Return type info |
_stack_size | int | Total stack frame size needed |
_argument_count | int | Number of required arguments |
_default_arg_count | int | Number of optional arguments |
lambdas | Vector<GDScriptFunction*> | Nested lambda function objects |
stack_debug | Vector<StackDebug> | Maps line numbers to live local variable names |
name | StringName | Function name |
Operand encoding in code[]: Each operand is stored as a single int. The lower 24 bits (ADDR_BITS) are the address value (stack index, constant index, etc.). The upper bits are the ADDR_TYPE_* discriminant.
Sources: modules/gdscript/gdscript_function.h1-200 modules/gdscript/gdscript_function.cpp37-135
GDScriptFunction::call())The GDScript VM is not a separate class. It is implemented entirely inside GDScriptFunction::call() in modules/gdscript/gdscript_vm.cpp This is a large switch-dispatch loop over the Opcode enum values stored in code[].
VM execution model
Sources: modules/gdscript/gdscript_vm.cpp119-2000
| Category | Representative Opcodes | Notes |
|---|---|---|
| Arithmetic / Logic | OPCODE_OPERATOR, OPCODE_OPERATOR_VALIDATED | Validated variant skips Variant::evaluate() overhead when types are known |
| Type Tests | OPCODE_TYPE_TEST_BUILTIN, OPCODE_TYPE_TEST_NATIVE, OPCODE_TYPE_TEST_SCRIPT | Implements is keyword |
| Assignment | OPCODE_ASSIGN, OPCODE_ASSIGN_TYPED_BUILTIN, OPCODE_ASSIGN_TYPED_NATIVE, OPCODE_ASSIGN_TYPED_SCRIPT | Typed variants enforce type at runtime in debug builds |
| Member Access | OPCODE_GET_MEMBER, OPCODE_SET_MEMBER, OPCODE_GET_NAMED, OPCODE_SET_NAMED | Named accesses use global_names[] for property path |
| Subscript | OPCODE_GET_KEYED, OPCODE_SET_KEYED, OPCODE_GET_KEYED_VALIDATED, OPCODE_SET_INDEXED_VALIDATED | Validated variant used for typed arrays/dicts |
| Construction | OPCODE_CONSTRUCT, OPCODE_CONSTRUCT_ARRAY, OPCODE_CONSTRUCT_TYPED_ARRAY, OPCODE_CONSTRUCT_DICTIONARY | Builds Variant values inline |
| Function Calls | OPCODE_CALL, OPCODE_CALL_RETURN, OPCODE_CALL_METHOD_BIND, OPCODE_CALL_METHOD_BIND_VALIDATED_RETURN, OPCODE_CALL_UTILITY, OPCODE_CALL_SELF_BASE, OPCODE_CALL_GDSCRIPT_UTILITY | Many variants for different call targets and return-value needs |
| Static Calls | OPCODE_CALL_BUILTIN_STATIC, OPCODE_CALL_NATIVE_STATIC, OPCODE_CALL_NATIVE_STATIC_VALIDATED_RETURN | Calls on class objects |
| Return | OPCODE_RETURN, OPCODE_RETURN_TYPED_BUILTIN, OPCODE_RETURN_TYPED_NATIVE, OPCODE_RETURN_TYPED_SCRIPT | Typed returns validate the value against the declared return type |
| Iteration | OPCODE_ITERATE_BEGIN, OPCODE_ITERATE_BEGIN_INT, OPCODE_ITERATE, OPCODE_ITERATE_INT | Implements for loops; integer variant is optimized |
| Async | OPCODE_AWAIT, OPCODE_AWAIT_RESUME | Coroutine suspension and resumption |
| Debug | OPCODE_LINE, OPCODE_BREAKPOINT, OPCODE_ASSERT | Emit line info for debugger; trigger breakpoint; runtime assertion |
| Stack Cleanup | OPCODE_TYPE_ADJUST_* | Coerce a stack slot to a specific builtin type (initializes with default) |
| Globals | OPCODE_STORE_GLOBAL, OPCODE_STORE_NAMED_GLOBAL | Load autoloads/singletons at runtime |
| End | OPCODE_END | Terminal opcode; implicit return |
Sources: modules/gdscript/gdscript_disassembler.cpp1-500 modules/gdscript/gdscript_vm.cpp119-2000
GDScriptLanguage owns a thread-local call stack for debugging:
| Member | Type | Purpose |
|---|---|---|
_call_stack | CallLevel[] | Per-depth frame info |
_call_stack_size | int | Current depth counter |
Each CallLevel holds:
stack — pointer into the current frame's Variant[]function — the GDScriptFunction being executedinstance — the GDScriptInstance (or nullptr for static calls)line — pointer to the VM's current line variable (updated by OPCODE_LINE)ip — instruction pointerGDScriptLanguage::debug_get_stack_level_locals() reconstructs visible locals by calling GDScriptFunction::debug_get_stack_member_state(line, &locals), which replays the stack_debug entries up to the given line.
Sources: modules/gdscript/gdscript_editor.cpp300-386 modules/gdscript/gdscript_function.cpp62-107 modules/gdscript/gdscript.h320-380
await (GDScriptFunctionState)GDScript supports await on signals and coroutine functions. When the VM hits OPCODE_AWAIT:
GDScriptFunctionState (RefCounted).call() returns the GDScriptFunctionState as a Variant to the caller.GDScriptFunctionState::_signal_callback to the awaited signal.GDScriptFunctionState::resume(arg) calls GDScriptFunction::call() again with the saved CallState, restoring the stack and continuing from the stored ip.Await / resume lifecycle
GDScriptFunctionState maintains two SelfList memberships:
scripts_list in GDScript::pending_func_states — invalidated if the script is freedinstances_list in the owning instance — invalidated if the object is freedis_valid(p_extended_check) uses these lists to safely detect dangling references before resuming.
Sources: modules/gdscript/gdscript_function.cpp139-280 modules/gdscript/gdscript_function.h150-300 modules/gdscript/gdscript.h191-192
GDScriptLanguage implements the ScriptLanguage debugger interface. Key debug-only features:
OPCODE_LINE — emitted before each statement; updates *cl->line so the debugger can show the current source line.OPCODE_BREAKPOINT — calls GDScriptLanguage::debug_break("Breakpoint", true), which suspends into EngineDebugger.stack_debug — Vector<StackDebug> on each GDScriptFunction. Each entry records { identifier, stack_pos, line, added }. debug_get_stack_member_state() replays these to reconstruct which names are live at a given line.debug_get_stack_level_locals() — exposes locals to the editor debugger via the CallLevel stack.debug_get_stack_level_members() — exposes instance member values via GDScriptInstance::debug_get_member_by_index().In DEBUG_ENABLED builds, the VM also profiles native calls through _profile_native_call(), tracking time per function call in GDScriptFunction::Profile::native_calls.
Sources: modules/gdscript/gdscript_editor.cpp250-460 modules/gdscript/gdscript_vm.cpp40-115 modules/gdscript/gdscript_function.cpp62-107
GDScriptFunction::disassemble() (in modules/gdscript/gdscript_disassembler.cpp compiled only in DEBUG_ENABLED builds) produces human-readable output of the bytecode. It iterates code[] and formats each instruction with its decoded operand types and values. This is primarily used for debugging and engine development; it is not exposed to user scripts.
Code entity map
Sources: modules/gdscript/gdscript_disassembler.cpp1-500 modules/gdscript/gdscript_compiler.h1-100 modules/gdscript/gdscript_byte_codegen.h1-80 modules/gdscript/gdscript_function.h1-100 modules/gdscript/gdscript_vm.cpp1-120
Refresh this wiki
This wiki was recently refreshed. Please wait 2 days to refresh again.