Skip to content

Udon's execution model

Udon has 9 instructions, and only EXTERN computes values (even a + b calls the addition function by name).

InstructionWhat it does
NOPNo-op
PUSHPushes a heap address onto the stack
POPDiscards the top of the stack
JUMP_IF_FALSEJumps to the given position if the value at the pushed address is false
JUMPJumps to the given position
EXTERNCalls one extern function
ANNOTATIONNo-op (takes one argument)
JUMP_INDIRECTJumps to the value at the pushed address
COPYCopies a value between the two addresses the stack points to

Only what appears in the list Udon exposes can be called. Even a method that exists in C# gets TUKI0101 if it isn’t in the list, and TUKI0102 for a type (Error).

How call targets and jump targets are decided

Section titled “How call targets and jump targets are decided”

The name of the function being called is written into the instruction stream as a signature string, and the only instruction whose jump target can be decided at runtime is JUMP_INDIRECT (the compiler uses it for returning from a method).

SyntaxReasonDiagnostic
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
Constructs that return values one at a timeA single run proceeds straight through from the entry point to the endyield gets TUKI0001
Forms that look up a member by name at runtimeThe name of the call target is decided at compile timedynamic gets TUKI0102
Where values are storedA single array called the heap, and nothing else
Local-variable frameNone. Every value lives in a heap variable
Type and count of heap variablesEach one has a type, and the count is fixed at compile time. There is no way to add more while running
ArraysAn array lives in a single heap variable no matter how many elements it holds. Its length can be a value decided at runtime, but there is no way to change it afterwards (Arrays)
UnitOne file is one program, and the concrete Behaviour inside it is the body (a second one in the same file gets TUKI0109; File layout and namespaces)
Where execution startsThere is no main. Execution starts at the events the runtime calls, and at methods called by name (Events)
A method named MainYou can write it, but the runtime never calls it
using UnityEngine;
using Tsukimi;
public class BcMain : TsukimiBehaviour
{
void Start()
{
Debug.Log(1); // => 1
}
public static void Main() { }
}
What becomes instructionsOnly the methods reached by following calls from an entry point
Entry pointsEvents, and methods marked public
What does not become instructionsA method without public that nothing calls. It doesn’t use a heap variable, either
Checks that still applyWhether a form is supported is still checked. An unsupported form inside a method you forgot to call is an error there

There are three limits, and all of them are compile-time errors.

LimitWhat is countedDiagnostic
1,048,576 heap variablesFields with structs expanded, plus each method’s parameters and local variables (counted only for programs that use a struct)TUKI0104
256 instantiated combinationsThe total number of instantiated generic combinationsTUKI0105
8 levels of nestingThe nesting depth of type argumentsTUKI0105

The two TUKI0105 checks run before TUKI0104.

LimitWhat decides it
1,048,576 heap variablesThe maximum number the runtime allows for the heap
512 heap variables when no size is givenThe number the assembler uses when it builds without being given a size. It isn’t a limit: programs in this language are built with the size they need, so a program past 512 runs as-is
The two generic numbersNumbers the compiler enforces to prevent runaway growth. Not a restriction from the runtime
What consumes themHow it is 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 somewhere to be stored during compilation
Extern function signature stringsOne per distinct function called. Calling the same function repeatedly does not add more, but each additional distinct function leaves fewer slots for variables

TUKI0104 counts only fields, parameters, and local variables, while what is actually needed at assembly time also includes constants, intermediate values, and signature strings. This count is an estimate.

The runtime has no exception mechanism, so throw and try / catch are both errors (Statements and control flow).

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 (checked not compiling is for the same reason).

ControlWhat happensWhere to find an example
Integer division by zeroExecution halts. Dividing a float by 0 produces InfinityNumerics
A numeric conversion that goes out of rangeExecution halts. The extern function that does the conversion checks the rangeNumerics
Calling a member on a null referenceExecution halts, and the runtime raises an errornull and references

When it halts, the rest of that event does not 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, there is no way to recover (you rebuild the program or replace it).