Control Flow Opcodes
This section documents the control flow instructions available in CandleVM.
For operand encodings, see the executable format reference.
Control flow and calls
| Opcode byte | Opcode | Operand bytes | Behavior |
|---|---|---|---|
| 0 | Nop | — | Does nothing. |
| 1 | Exit | code: register | Converts the register to an unsigned byte and stops the VM with that exit code. Accepts a Byte or an integral finite Number in 0..255; otherwise raises an error. |
| 2 | Call | return: register, function/closure: register, parameter_count: u8, then parameter_count register bytes | Accepts a FunctionRef or an ObjectRef to a Closure. Reads the explicit argument count (0–8) and those register indices. Missing regular parameters are filled with Null; extra arguments are consumed and ignored. For a Closure, its captured values are appended after the regular parameters. The return register receives the native result or the value returned by the callee's Ret instruction. CandleVM errors propagate through normal VM unwinding. |
| 3 | Pcall | return: register, error: register, function/closure: register, parameter_count: u8, then parameter_count register bytes | Like Call, but if an error unwinds to it, the Error is written to error; the return register is set to Null, execution resumes after all operands of this instruction. |
| 4 | Ret | value: register | Pops a call frame, truncates registers to its saved base, writes the selected value to the caller's return register and resumes at the saved return PC. Without a frame, raises CallstackUnderflow. |
| 5 | Throw | error: register | Requires an ObjectRef to an Error and raises the stored CandleVM error. |
| 6 | Jump | offset: i32 | Adds the signed offset to the PC after the operand. The result must be in `[0, bytecode length); otherwise raises InvalidJump. |
| 7 | JumpIf | offset: i32, condition: register | Requires a Bool; if true, applies the same relative jump as Jump. If false, continues after the operands. |
| 8 | JumpIfNot | offset: i32, condition: register | Requires a Bool; if false, applies the same relative jump as Jump. If true, continues after the operands. |
Calls have a maximum stack depth of 1024. The loader sets each bytecode function's entry address. When the function is called, its parameters are written to registers starting at 1 and execution begins at that address.