Udon's execution model
Instruction set
Section titled “Instruction set”Udon has 9 kinds of instructions. The slot an instruction points to is where a value lives (Where values live).
| Instruction | What it does |
|---|---|
NOP | No-op |
PUSH | Pushes a slot number onto the stack |
POP | Discards the top of the stack |
JUMP_IF_FALSE | Jumps to the given position if the slot at the pushed number is false |
JUMP | Jumps to the given position |
EXTERN | Calls one extern function |
ANNOTATION | No-op (takes one argument) |
JUMP_INDIRECT | Jumps to the value held in a slot |
COPY | Copies a value between the two slots the stack points to |
Only EXTERN computes values. Even a + b becomes a call, by name, to the addition function
the runtime has. COPY copies a value between slots; the other instructions only push numbers or jump.
The functions you can call are limited to the set the runtime exposes. Even a method that exists in C#,
if it isn’t in that set, is rejected with TUKI0101. Types work the same way: a type outside the set gets TUKI0102
(Errors).
The name of the function being called is written into the instruction stream as a signature string. The instruction whose jump target can be decided at runtime
is JUMP_INDIRECT alone, and the compiler uses it for returning from a method.
This gives rise to three of the largest entries under “Forms that don’t compile” that appear on other pages.
| Forms that don’t compile | Reason | Diagnostic |
|---|---|---|
| Forms that hold a function as a value | The call target is written into the instruction itself and can’t be swapped at runtime | Delegates and lambdas get TUKI0108 |
| Forms that suspend and resume | Execution runs straight through from the entry point to the end | yield and await get TUKI0001 |
| Forms that look up a member by name at runtime | The name of the call target is decided at compile time | dynamic gets TUKI0102 |
Where values live
Section titled “Where values live”The runtime has no local-variable frame. Every value lives in a slot of a single array called the heap. Each slot has its own type, and the number of slots is fixed at compile time. There’s no way to add more while running.
An array lives in a single slot no matter how many elements it holds. Its length can be a value decided at runtime, but there’s no way to change the length after creating it (Arrays).
Program units
Section titled “Program units”One file becomes one program, and the concrete Behaviour inside it becomes the program’s body.
Writing a second concrete Behaviour in the same file is rejected with TUKI0109
(the shape of a program).
A program has no main. Execution starts at events the runtime calls, and
methods called by name (Events). A method named Main can be
written, but the runtime doesn’t call it.
using UnityEngine;using Tsukimi;
public class BcMain : TsukimiBehaviour{ void Start() { Debug.Log(1); // => 1 }
public static void Main() { }}Code unreachable from the entry point
Section titled “Code unreachable from the entry point”The compiler follows calls from the entry point and turns only the methods it reaches into instructions. Entry points are events and
methods marked public. A method without public that nothing calls
doesn’t become instructions. It doesn’t use a slot, either.
Even a method that doesn’t become instructions is still checked for whether it’s a form that compiles. If a method you forgot to call contains a form that doesn’t compile, it’s rejected there (the boundaries list).
Slot limit
Section titled “Slot limit”There’s a limit on how many slots one program can use. The compiler counts the fields expanded down into the contents of a struct,
plus each method’s parameters and local variables, and stops with TUKI0104 if the total exceeds 512.
Shrinking the struct or reducing variables makes it compile. This count is taken only for programs that use a struct;
programs without a struct aren’t counted.
Slots aren’t used only by variables written in the source.
| What uses it | How it’s counted |
|---|---|
| Field | Uses as many as are declared. A struct is laid out as its members in sequence, so it uses one per member |
| Method parameters and local variables | The runtime has no local-variable frame, so variables declared inside a method live here too |
| Constants and intermediate values | Values not written in the source still need a place during compilation |
| Extern function signature strings | Uses one per distinct function called. Calling the same function repeatedly doesn’t add more |
The more distinct functions are called, the fewer slots are left for variables.
What TUKI0104 counts is fields, plus method parameters and local variables. At assembly time,
what’s actually needed adds constants, intermediate values, and signature strings on top of that.
This count is an estimate, not the actual usage.
512 isn’t a limit imposed by the runtime. It’s just the default size the assembler that builds Udon programs allocates for the heap. You can also assemble with a specified size, and programs with a heap larger than 512 do run in practice.
Generics have two more limits. If the total number of instantiated combinations exceeds 256, or the nesting of type arguments
goes deeper than 8 levels, it stops with TUKI0105. Both are numbers the compiler enforces to prevent runaway growth —
not a restriction from the runtime. These two checks run before TUKI0104.
Conditions that halt execution
Section titled “Conditions that halt execution”The runtime has no exception mechanism. throw, and try / catch, are both rejected
(the boundaries list).
using UnityEngine;using Tsukimi;
public class ExCatchTypeless : TsukimiBehaviour{ void Start() { try { Debug.Log(1); } catch { Debug.Log(2); } }}An operation that can fail can only be written by checking the condition before calling it. Failing first and catching it afterward
doesn’t work in this runtime. checked not catching overflow is for the same reason (Numerics).
| Operation | What happens | Where to find an example |
|---|---|---|
| Integer division by zero | Execution halts. Dividing a float by 0 produces Infinity. | Numerics |
| A numeric conversion that goes out of range | Execution halts. The extern function that does the conversion checks the range. | Numerics |
| Calling a member on a null reference | Execution halts, and the runtime raises an error. | null and references |
When it halts, the rest of that event doesn’t run. The log shows that it halted, and the signature of the extern function that failed. The program can stop responding to any event from then on, and there’s no way to recover once that happens.
There’s no way to restore a program once it has halted. You have to rebuild it or replace it.