This document describes the instruction set of the virtual machine. The virtual
machine is a stack based machine, which means that almost all instructions
operate on the stack. The stack is a LIFO (Last In First Out) data structure
that stores values (instances of LuaValue). Any LuaValue is assumed to be
safe to clone(). Any value that should be passed by reference is a
LuaObject, and is passed around on the stack as a LuaValue::Object.
The Marker value is a special value that is used to mark the beginning of a
sequence of values. It is used to separate the arguments to a function from the
local variables of the function. It is also used in assignment statements, to
ensure an equal number of values to the number of variables.
Local variables are stored on the stack as values. The index of a local variable is relative to the current stack frame. The frame pointer is the index of the first argument in the stack. The local variables are registered at compile time, and are accessed purely by index, meaning no lookup is required at runtime.
Global variables are stored in a table in the virtual machine's global state. The index of a global variable corresponds to a key in the constants table, where the value is the name of the global variable. The global variables are looked up at runtime by index, but their name may be looked up at runtime if desired (e.g. for debugging purposes).
When a function is called, a marker is pushed onto the stack followed by the arguments to the function. This allows determining the number of arguments at runtime, when passing a variadic number of arguments to a function.
The marker is discarded (all values above it are shifted down) when the function is called, and the frame pointer is set to the index of where the arguments start. This allows the function to access its arguments by indexing the stack from the frame pointer.
In the case of a variadic function, the variadic arguments are stored in a
sequence table, which is pushed onto the stack as a local variable. The
AlignVararg instruction is used to collect the variadic arguments into a
sequence table.
For example, a function foo(bar, baz, ...) when called with foo(1, 2, 3, 4),
would have the following local variables:
[1, 2, {3, 4}]
Closures are functions that can capture variables from their enclosing scope. At
compilation time, the compiler determines which variables are captured by the
closure, and embeds information about this in the LoadClosure instruction. At
runtime, this instruction converts the local value on the stack into an upvalue,
a reference to the captured variable. The closure object iself also keeps a list
of upvalues, which are used to access the captured variables.
To access an upvalue, the closure uses the GetUpval and SetUpval
instructions, both of which take an index as an argument and operate on the
upvalue list of the closure, which is saved in the call frame.
Reads a constant index from the bytes directly following the instruction and looks up the value of the constant from the virtual machine's constant table.
The LuaConst value is then pushed onto the stack.
Reads a function index from the bytes directly following the instruction and looks up the function from the virtual machine's function table. The function contains information about the number of upvalues it captures.
For each captured upvalue, the instruction reads two bytes. The first indicates
whether this upvalue refers to a local in the parent scope (as opposed to an
already captured upvalue), and the second is the index of the local variable or
upvalue.
In the case of a local, the virtual machine will convert the local variable on the stack into an upvalue, and replace the local variable with the upvalue. In the case of an upvalue, the virtual machine will simply fetch the upvalue from the current call frame. In both cases, the upvalue is then stored in the closure object.
The closure object is pushed onto the stack.
Pops the top value from the stack, discarding it.
Keeps popping values from the stack until a Marker is found, discarding all of
them, including the marker.
Reads an offset from the byte directly following the instruction and swaps the top value with the value at the offset from the top of the stack.
For example, if the stack is [1, 2, 3, 4] and the instruction is Swap 2,
the stack will become [1, 3, 2, 4].
Reads a number from the byte directly following the instruction, and manipulates
the stack so that there are exactly that many values from the position of the
latest LuaValue::Marker to the top of the stack. The Align operation
discards both any values above the marker and the marker itself. If there are
fewer than n values above the marker, the operation will pad the stack with
nil values.
For example, if the stack is [1, 2, Marker, 3, 4] and the instruction is
Align 3, the stack will become [1, 2, 3, 4, nil]. Conversely, if the stack
is [1, 2, Marker, 3, 4] and the instruction is Align 1, the stack will
become [1, 2, 3].
This operation is similar to Align, but any extraneous values beyond n are
collected into a sequence table, which is then pushed onto the stack. This is
used to collect variadic arguments into a single table.
For example, if the stack is [1, 2, Marker, 3, 4, 5] and the instruction is
AlignVararg 1, the stack will become [1, 2, 3, {4, 5}], where in reality
{4, 5} is a pointer to a table on the heap. The table containing the variadic
arguments are stored as a "local" (see GetLocal and LoadVararg below).
Reads an offset from the byte directly following the instruction, and duplicates
the value at the offset from the latest Marker on the stack. The duplicated
value is pushed onto the stack.
For example, if the stack is [1, 2, Marker, 3, 4, 5] and the instruction is
DupFromMarker 1, the stack will become [1, 2, Marker, 3, 4, 5, 3].
The arithmetic operations are Add, Sub, Mul, Div, Mod, Pow, IDiv,
BAnd, BOr, BXor, Shl, and Shr. All of these operations pop two values
from the stack, perform the operation, and push the result back onto the stack.
The logical operations are And and Or. Both of these operations pop two
values off the stack. In the case of And, if the first value is falsy, that
first value is pushed back onto the stack. Otherwise, the second value is pushed
back onto the stack. Or behaves the same way, but pushes the first value back
if its truthy, rather than when it's falsy.
The comparison operations are Eq, Ne, Lt, Le, Gt, and Ge. All of
these operations pop two values from the stack, perform the comparison, and push
the result back onto the stack.
The concatenation operation is Concat. It pops two values from the stack,
concatenates them, and pushes the result back onto the stack.
Pops a value from the stack, negates it, and pushes the result back onto the
stack. For example, if the stack is [1], the stack will become [-1].
Pops a value from the stack, negates it, and pushes the result back onto the
stack. For example, if the stack is [true], the stack will become [false].
Pops a value from the stack, gets its length, and pushes the result back onto
the stack. For example, if the stack is ["abc"], the stack will become
[3].
Pops a value from the stack, bitwise negates it, and pushes the result back onto
the stack. For example, if the stack is [5], the stack will become [-6].
Reads an index from the bytes directly following the instruction. It pops the top value from the stack and sets the global variable at the index to the value.
Reads an index from the bytes directly following the instruction. It looks up
the global variable at the index and pushes the value onto the stack. If the
variable does not exist, it pushes nil.
Reads a stack index from the bytes directly following the instruction. It pops the top value from the stack and sets the local variable at the index to the value. The index here is relative to the current call frame pointer.
If the value on the stack refers to an upvalue, the upvalue is updated instead.
Reads a stack index from the bytes directly following the instruction. It looks up the local variable at the index and pushes the value onto the stack. The index here is relative to the current call frame pointer.
If the local variable refers to an upvalue, the inner value of the upvalue is extracted and pushed onto the stack.
Reads a stack index from the bytes directly following the instruction, as well as an attribute byte after that. Sets that attribute in the local indicated by the stack index bytes, relative to the current frame pointer.
Reads an upvalue index from the bytes directly following the instruction. It pops the top value from the stack and sets the upvalue at the index in the current call frame to the popped value.
Reads an upvalue index from the bytes directly following the instruction. It looks up the upvalue at the index in the current call frame and pushes the value onto the stack.
This is similar to GetLocal, but it expects the local variable to be a
sequence table containing variadic arguments. It pushes each of the variadic
arguments onto the stack.
Creates a new table and pushes it onto the stack.
Pops a key and a value from the stack, and sets the value of the key in the table at the top of the stack to the value. The table is kept on the stack.
For example, if the stack is [table, key, value], the table will become
[table] with the key-value pair set.
Pops a key and a table from the stack, and looks up the value of the key in the
table. The value is pushed onto the stack. If the key does not exist in the
table, nil is pushed.
For example, if the stack is [table, key], the stack will become [value].
Appends all the values on the stack to the table, which is assumed to be the value before the marker. The values and the marker are popped from the stack.
Pops a function from the stack, and then determines the number of arguments to
pass to the function by checking up the stack for a Marker. The marker is then
discarded.
The operation pushes a new call frame onto the dedicated call frames stack,
storing the name of the function (if any, for the purposes of a stack trace in
the case of an error), the frame pointer (the index of the first argument in the
stack), and the return address (the instruction pointer after the Call
instruction).
For example, if the stack is [1, 2, marker, 3, function], and the Call
instruction is at instruction pointer 10, the call frame stack will become
[CallFrame("function", 2, 11)], and the stack will become [1, 2, 3]. This
way, the function can access its arguments by indexing the stack from the frame
pointer.
Gathers the return values from the top of the stack to the latest Marker, and
pops the call frame from the call frame stack. The stack pointer is then reset
to the frame pointer of the popped call frame, before pushing the return values
back onto the stack.
For example, if the stack is [1, 2, 3, Marker, 4, 5], and the call frame stack
is [CallFrame("function", 2, 11)], the stack will become [1, 2, 4, 5].
Reads an instruction pointer from the bytes directly following the instruction, and sets the instruction pointer to that value. This is used for unconditional jumps.
Reads an instruction pointer from the bytes directly following the instruction, and pops a value from the stack. If the value is true, the instruction pointer is set to the value. Otherwise, the instruction pointer is incremented by the size of the instruction.
The same as JmpTrue, but the jump is taken if the value is false.
Triggers a runtime error, with the error code being the byte directly following the instruction.