Skip to content

Udon's execution model

Udon has 9 kinds of instructions. The slot an instruction points to is where a value lives (Where values live).

InstructionWhat it does
NOPNo-op
PUSHPushes a slot number onto the stack
POPDiscards the top of the stack
JUMP_IF_FALSEJumps to the given position if the slot at the pushed number is false
JUMPJumps to the given position
EXTERNCalls one extern function
ANNOTATIONNo-op (takes one argument)
JUMP_INDIRECTJumps to the value held in a slot
COPYCopies 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 compileReasonDiagnostic
Forms that hold a function as a valueThe call target is written into the instruction itself and can’t be swapped at runtimeDelegates and lambdas get TUKI0108
Forms that suspend and resumeExecution runs straight through from the entry point to the endyield and await get TUKI0001
Forms that look up a member by name at runtimeThe name of the call target is decided at compile timedynamic gets TUKI0102

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).

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() { }
}

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).

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 itHow it’s counted
FieldUses 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 variablesThe runtime has no local-variable frame, so variables declared inside a method live here too
Constants and intermediate valuesValues not written in the source still need a place during compilation
Extern function signature stringsUses 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.

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).

OperationWhat happensWhere to find an example
Integer division by zeroExecution halts. Dividing a float by 0 produces Infinity.Numerics
A numeric conversion that goes out of rangeExecution halts. The extern function that does the conversion checks the range.Numerics
Calling a member on a null referenceExecution 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.