Udon's execution model
Instruction set
Section titled “Instruction set”Udon has 9 instructions, and only EXTERN computes values (even a + b calls the addition function by name).
| Instruction | What it does |
|---|---|
NOP | No-op |
PUSH | Pushes a heap address onto the stack |
POP | Discards the top of the stack |
JUMP_IF_FALSE | Jumps to the given position if the value at the pushed address 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 at the pushed address |
COPY | Copies a value between the two addresses the stack points to |
Which functions can be called
Section titled “Which functions can be called”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).
Constraints from the runtime
Section titled “Constraints from the runtime”| Syntax | 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 |
| Constructs that return values one at a time | A single run proceeds straight through from the entry point to the end | yield gets 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”| Where values are stored | A single array called the heap, and nothing else |
| Local-variable frame | None. Every value lives in a heap variable |
| Type and count of heap variables | Each one has a type, and the count is fixed at compile time. There is no way to add more while running |
| Arrays | An 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) |
Program units
Section titled “Program units”| Unit | One 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 starts | There is no main. Execution starts at the events the runtime calls, and at methods called by name (Events) |
A method named Main | You 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() { }}Code unreachable from the entry point
Section titled “Code unreachable from the entry point”| What becomes instructions | Only the methods reached by following calls from an entry point |
| Entry points | Events, and methods marked public |
| What does not become instructions | A method without public that nothing calls. It doesn’t use a heap variable, either |
| Checks that still apply | Whether a form is supported is still checked. An unsupported form inside a method you forgot to call is an error there |
Heap limit
Section titled “Heap limit”There are three limits, and all of them are compile-time errors.
| Limit | What is counted | Diagnostic |
|---|---|---|
| 1,048,576 heap variables | Fields with structs expanded, plus each method’s parameters and local variables (counted only for programs that use a struct) | TUKI0104 |
| 256 instantiated combinations | The total number of instantiated generic combinations | TUKI0105 |
| 8 levels of nesting | The nesting depth of type arguments | TUKI0105 |
The two TUKI0105 checks run before TUKI0104.
Where the limits come from
Section titled “Where the limits come from”| Limit | What decides it |
|---|---|
| 1,048,576 heap variables | The maximum number the runtime allows for the heap |
| 512 heap variables when no size is given | The 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 numbers | Numbers the compiler enforces to prevent runaway growth. Not a restriction from the runtime |
What uses heap variables
Section titled “What uses heap variables”| What consumes them | How it is 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 somewhere to be stored during compilation |
| Extern function signature strings | One per distinct function called. Calling the same function repeatedly does not add more, but each additional distinct function leaves fewer slots for variables |
Difference from actual usage
Section titled “Difference from actual usage”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.
Conditions that halt execution
Section titled “Conditions that halt execution”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).
Operations that halt at runtime
Section titled “Operations that halt at runtime”| Control | 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 |
What happens after a halt
Section titled “What happens after a halt”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).