Lua 5.3 Bytecode Reference¶
This is my attempt to bring up to date the Lua bytecode reference. Note that this is work in progress. Following copyrights are acknowledged:
A No-Frills Introduction to Lua 5.1 VM Instructions
by Kein-Hong Man, esq. <khman AT users.sf.net>
Version 0.1, 2006-03-13
A No-Frills Introduction to Lua 5.1 VM Instructions is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike License 2.0. You are free to copy, distribute and display the work, and make derivative works as long as you give the original author credit, you do not use this work for commercial purposes, and if you alter, transform, or build upon this work, you distribute the resulting work only under a license identical to this one. See the following URLs for more information:
http://creativecommons.org/licenses/by-nc-sa/2.0/
http://creativecommons.org/licenses/by-nc-sa/2.0/legalcode
Lua Stack and Registers¶
Lua employs two stacks.
The Callinfo
stack tracks activation frames.
There is the secondary stack L->stack
that is an array of TValue
objects.
The Callinfo
objects index into this array. Registers are basically slots in
the L->stack
array.
When a function is called - the stack is setup as follows:
stack
| function reference
| var arg 1
| ...
| var arg n
| base-> fixed arg 1
| ...
| fixed arg n
| local 1
| ...
| local n
| temporaries
| ...
| top->
|
V
So top
is just past the registers needed by the function.
The number of registers is determined based on parameters, locals and temporaries.
For each Lua function, the base
of the stack is set to the first fixed parameter or local.
All register addressing is done as offset from base
- so R(0)
is at base+0
on the stack.

The figure above shows how the stack is related to other Lua objects.¶
When the function returns the return values are copied to location starting at the function reference.
Instruction Notation¶
- R(A)
Register A (specified in instruction field A)
- R(B)
Register B (specified in instruction field B)
- R(C)
Register C (specified in instruction field C)
- PC
Program Counter
- Kst(n)
Element n in the constant list
- Upvalue[n]
Name of upvalue with index n
- Gbl[sym]
Global variable indexed by symbol sym
- RK(B)
Register B or a constant index
- RK(C)
Register C or a constant index
- sBx
Signed displacement (in field sBx) for all kinds of jumps
Instruction Summary¶
Lua bytecode instructions are 32-bits in size. All instructions have an opcode in the first 6 bits. Instructions can have the following fields:
'A' : 8 bits
'B' : 9 bits
'C' : 9 bits
'Ax' : 26 bits ('A', 'B', and 'C' together)
'Bx' : 18 bits ('B' and 'C' together)
'sBx' : signed Bx
A signed argument is represented in excess K; that is, the number value is the unsigned value minus K. K is exactly the maximum value for that argument (so that -max is represented by 0, and +max is represented by 2*max), which is half the maximum for the corresponding unsigned argument.
Note that B and C operands need to have an extra bit compared to A. This is because B and C can reference registers or constants, and the extra bit is used to decide which one. But A always references registers so it doesn’t need the extra bit.
Opcode |
Description |
---|---|
MOVE |
Copy a value between registers |
LOADK |
Load a constant into a register |
LOADKX |
Load a constant into a register |
LOADBOOL |
Load a boolean into a register |
LOADNIL |
Load nil values into a range of registers |
GETUPVAL |
Read an upvalue into a register |
GETTABUP |
Read a value from table in up-value into a register |
GETTABLE |
Read a table element into a register |
SETTABUP |
Write a register value into table in up-value |
SETUPVAL |
Write a register value into an upvalue |
SETTABLE |
Write a register value into a table element |
NEWTABLE |
Create a new table |
SELF |
Prepare an object method for calling |
ADD |
Addition operator |
SUB |
Subtraction operator |
MUL |
Multiplication operator |
MOD |
Modulus (remainder) operator |
POW |
Exponentation operator |
DIV |
Division operator |
IDIV |
Integer division operator |
BAND |
Bit-wise AND operator |
BOR |
Bit-wise OR operator |
BXOR |
Bit-wise Exclusive OR operator |
SHL |
Shift bits left |
SHR |
Shift bits right |
UNM |
Unary minus |
BNOT |
Bit-wise NOT operator |
NOT |
Logical NOT operator |
LEN |
Length operator |
CONCAT |
Concatenate a range of registers |
JMP |
Unconditional jump |
EQ |
Equality test, with conditional jump |
LT |
Less than test, with conditional jump |
LE |
Less than or equal to test, with conditional jump |
TEST |
Boolean test, with conditional jump |
TESTSET |
Boolean test, with conditional jump and assignment |
CALL |
Call a closure |
TAILCALL |
Perform a tail call |
RETURN |
Return from function call |
FORLOOP |
Iterate a numeric for loop |
FORPREP |
Initialization for a numeric for loop |
TFORLOOP |
Iterate a generic for loop |
TFORCALL |
Initialization for a generic for loop |
SETLIST |
Set a range of array elements for a table |
CLOSURE |
Create a closure of a function prototype |
VARARG |
Assign vararg function arguments to registers |
OP_CALL instruction¶
Syntax¶
CALL A B C R(A), ... ,R(A+C-2) := R(A)(R(A+1), ... ,R(A+B-1))
Description¶
Performs a function call, with register R(A) holding the reference to the function object to be called.
Parameters to the function are placed in the registers following R(A). If B is 1, the function has no
parameters. If B is 2 or more, there are (B-1) parameters. If B >= 2, then upon entry to the called
function, R(A+1) will become the base
.
If B is 0, then B = ‘top’, i.e., the function parameters range from R(A+1) to the top of the stack.
This form is used when the number of parameters to pass is set by the previous VM instruction,
which has to be one of OP_CALL
or OP_VARARG
.
If C is 1, no return results are saved. If C is 2 or more, (C-1) return values are saved.
If C == 0, then ‘top’ is set to last_result+1, so that the next open instruction
(OP_CALL
, OP_RETURN
, OP_SETLIST
) can use ‘top’.
Examples¶
Example of OP_VARARG
followed by OP_CALL
:
function y(...) print(...) end
1 [1] GETTABUP 0 0 -1 ; _ENV "print"
2 [1] VARARG 1 0 ; VARARG will set L->top
3 [1] CALL 0 0 1 ; B=0 so L->top set by previous instruction
4 [1] RETURN 0 1
Example of OP_CALL
followed by OP_CALL
:
function z1() y(x()) end
1 [1] GETTABUP 0 0 -1 ; _ENV "y"
2 [1] GETTABUP 1 0 -2 ; _ENV "x"
3 [1] CALL 1 1 0 ; C=0 so return values indicated by L->top
4 [1] CALL 0 0 1 ; B=0 so L->top set by previous instruction
5 [1] RETURN 0 1
Thus upon entry to a function base
is always the location of the first fixed parameter if any or else local
if any. The three possibilities are shown below.
Two variable args and 1 Two variable args and no
Caller One fixed arg fixed arg fixed args
R(A) CI->func [ function ] CI->func [ function ] CI->func [ function ]
R(A+1) CI->base [ fixed arg 1 ] [ var arg 1 ] [ var arg 1 ]
R(A+2) [ local 1 ] [ var arg 2 ] [ var arg 2 ]
R(A+3) CI->base [ fixed arg 1 ] CI->base [ local 1 ]
R(A+4) [ local 1 ]
Results returned by the function call are placed in a range of registers starting from CI->func
.
If C
is 1
, no return results are saved. If C
is 2 or more, (C-1)
return values are saved.
If C
is 0
, then multiple return results are saved. In this case the number of values to save is
determined by one of following ways:
A C function returns an integer value indicating number of results returned so for C function calls this is used (see the value of
n
passed to luaD_poscall() in luaD_precall())For Lua functions, the results are saved by the called function’s
OP_RETURN
instruction.
More examples¶
x=function() y() end
Produces:
function <stdin:1,1> (3 instructions at 000000CECB2BE040)
0 params, 2 slots, 1 upvalue, 0 locals, 1 constant, 0 functions
1 [1] GETTABUP 0 0 -1 ; _ENV "y"
2 [1] CALL 0 1 1
3 [1] RETURN 0 1
constants (1) for 000000CECB2BE040:
1 "y"
locals (0) for 000000CECB2BE040:
upvalues (1) for 000000CECB2BE040:
0 _ENV 0 0
In line [2], the call has zero parameters (field B is 1), zero results are retained (field C is 1), while register 0 temporarily holds the reference to the function object from global y. Next we see a function call with multiple parameters or arguments:
x=function() z(1,2,3) end
Generates:
function <stdin:1,1> (6 instructions at 000000CECB2D7BC0)
0 params, 4 slots, 1 upvalue, 0 locals, 4 constants, 0 functions
1 [1] GETTABUP 0 0 -1 ; _ENV "z"
2 [1] LOADK 1 -2 ; 1
3 [1] LOADK 2 -3 ; 2
4 [1] LOADK 3 -4 ; 3
5 [1] CALL 0 4 1
6 [1] RETURN 0 1
constants (4) for 000000CECB2D7BC0:
1 "z"
2 1
3 2
4 3
locals (0) for 000000CECB2D7BC0:
upvalues (1) for 000000CECB2D7BC0:
0 _ENV 0 0
Lines [1] to [4] loads the function reference and the arguments in order, then line [5] makes the call with an operand B value of 4, which means there are 3 parameters. Since the call statement is not assigned to anything, no return results need to be retained, hence field C is 1. Here is an example that uses multiple parameters and multiple return values:
x=function() local p,q,r,s = z(y()) end
Produces:
function <stdin:1,1> (5 instructions at 000000CECB2D6CC0)
0 params, 4 slots, 1 upvalue, 4 locals, 2 constants, 0 functions
1 [1] GETTABUP 0 0 -1 ; _ENV "z"
2 [1] GETTABUP 1 0 -2 ; _ENV "y"
3 [1] CALL 1 1 0
4 [1] CALL 0 0 5
5 [1] RETURN 0 1
constants (2) for 000000CECB2D6CC0:
1 "z"
2 "y"
locals (4) for 000000CECB2D6CC0:
0 p 5 6
1 q 5 6
2 r 5 6
3 s 5 6
upvalues (1) for 000000CECB2D6CC0:
0 _ENV 0 0
First, the function references are retrieved (lines [1] and [2]), then function y is called first (temporary register 1). The CALL has a field C of 0, meaning multiple return values are accepted. These return values become the parameters to function z, and so in line [4], field B of the CALL instruction is 0, signifying multiple parameters. After the call to function z, 4 results are retained, so field C in line [4] is 5. Finally, here is an example with calls to standard library functions:
x=function() print(string.char(64)) end
Leads to:
function <stdin:1,1> (7 instructions at 000000CECB2D6220)
0 params, 3 slots, 1 upvalue, 0 locals, 4 constants, 0 functions
1 [1] GETTABUP 0 0 -1 ; _ENV "print"
2 [1] GETTABUP 1 0 -2 ; _ENV "string"
3 [1] GETTABLE 1 1 -3 ; "char"
4 [1] LOADK 2 -4 ; 64
5 [1] CALL 1 2 0
6 [1] CALL 0 0 1
7 [1] RETURN 0 1
constants (4) for 000000CECB2D6220:
1 "print"
2 "string"
3 "char"
4 64
locals (0) for 000000CECB2D6220:
upvalues (1) for 000000CECB2D6220:
0 _ENV 0 0
When a function call is the last parameter to another function call, the former can pass multiple return values, while the latter can accept multiple parameters.
OP_TAILCALL instruction¶
Syntax¶
TAILCALL A B C return R(A)(R(A+1), ... ,R(A+B-1))
Description¶
Performs a tail call, which happens when a return statement has a single function call as the expression, e.g. return foo(bar). A tail call results in the function being interpreted within the same call frame as the caller - the stack is replaced and then a ‘goto’ executed to start at the entry point in the VM. Only Lua functions can be tailcalled. Tailcalls allow infinite recursion without growing the stack.
Like OP_CALL
, register R(A) holds the reference to the function object to be called. B encodes the number of parameters in the same manner as a OP_CALL
instruction.
C isn’t used by TAILCALL, since all return results are significant. In any case, Lua always generates a 0 for C, to denote multiple return results.
Examples¶
An OP_TAILCALL
is used only for one specific return style, described above. Multiple return results are always produced by a tail call. Here is an example:
function y() return x('foo', 'bar') end
Generates:
function <stdin:1,1> (6 instructions at 000000C3C24DE4A0)
0 params, 3 slots, 1 upvalue, 0 locals, 3 constants, 0 functions
1 [1] GETTABUP 0 0 -1 ; _ENV "x"
2 [1] LOADK 1 -2 ; "foo"
3 [1] LOADK 2 -3 ; "bar"
4 [1] TAILCALL 0 3 0
5 [1] RETURN 0 0
6 [1] RETURN 0 1
constants (3) for 000000C3C24DE4A0:
1 "x"
2 "foo"
3 "bar"
locals (0) for 000000C3C24DE4A0:
upvalues (1) for 000000C3C24DE4A0:
0 _ENV 0 0
Arguments for a tail call are handled in exactly the same way as arguments for a normal call, so in line [4], the tail call has a field B value of 3, signifying 2 parameters. Field C is 0, for multiple returns; this due to the constant LUA_MULTRET in lua.h. In practice, field C is not used by the virtual machine (except as an assert) since the syntax guarantees multiple return results.
Line [5] is a OP_RETURN
instruction specifying multiple return results. This is required when the function called by OP_TAILCALL
is a C function. In the case of a C function, execution continues to line [5] upon return, thus the RETURN is necessary. Line [6] is redundant. When Lua functions are tailcalled, the virtual machine does not return to line [5] at all.
OP_RETURN instruction¶
Syntax¶
RETURN A B return R(A), ... ,R(A+B-2)
Description¶
Returns to the calling function, with optional return values.
First OP_RETURN
closes any open upvalues by calling luaF_close().
If B is 1, there are no return values. If B is 2 or more, there are (B-1) return values, located in consecutive registers from R(A) onwards. If B is 0, the set of values range from R(A) to the top of the stack.
It is assumed that if the VM is returning to a Lua function then it is within the same invocation of the luaV_execute()
. Else it is assumed that luaV_execute()
is being invoked from a C function.
If B is 0 then the previous instruction (which must be either OP_CALL
or OP_VARARG
) would have set L->top
to indicate how many values to return. The number of values to be returned in this case is R(A) to L->top.
If B > 0 then the number of values to be returned is simply B-1.
OP_RETURN
calls luaD_poscall() which is responsible for copying return values to the caller - the first result is placed at the current closure
’s address. luaD_poscall()
leaves L->top
just past the last result that was copied.
If OP_RETURN
is returning to a Lua function and if the number of return values expected was indeterminate - i.e. OP_CALL
had operand C = 0, then L->top
is left where luaD_poscall()
placed it - just beyond the top of the result list. This allows the OP_CALL
instruction to figure out how many results were returned. If however OP_CALL
had invoked with a value of C > 0 then the expected number of results is known, and in that case, L->top
is reset to the calling function’s C->top
.
If luaV_execute()
was called externally then OP_RETURN
leaves L->top
unchanged - so it will continue to be just past the top of the results list. This is because luaV_execute() does not have a way of informing callers how many values were returned; so the caller can determine the number of results by inspecting L->top
.
Examples¶
Example of OP_VARARG
followed by OP_RETURN
:
function x(...) return ... end
1 [1] VARARG 0 0
2 [1] RETURN 0 0
Suppose we call x(1,2,3)
; then, observe the setting of L->top
when OP_RETURN
executes:
(LOADK A=1 Bx=-2) L->top = 4, ci->top = 4
(LOADK A=2 Bx=-3) L->top = 4, ci->top = 4
(LOADK A=3 Bx=-4) L->top = 4, ci->top = 4
(TAILCALL A=0 B=4 C=0) L->top = 4, ci->top = 4
(VARARG A=0 B=0) L->top = 2, ci->top = 2 ; we are in x()
(RETURN A=0 B=0) L->top = 3, ci->top = 2
Observe that OP_VARARG
set L->top
to base+3
.
But if we call x(1)
instead:
(LOADK A=1 Bx=-2) L->top = 4, ci->top = 4
(LOADK A=2 Bx=-3) L->top = 4, ci->top = 4
(LOADK A=3 Bx=-4) L->top = 4, ci->top = 4
(TAILCALL A=0 B=4 C=0) L->top = 4, ci->top = 4
(VARARG A=0 B=0) L->top = 2, ci->top = 2 ; we are in x()
(RETURN A=0 B=0) L->top = 1, ci->top = 2
Notice that this time OP_VARARG
set L->top
to base+1
.
OP_JMP instruction¶
Syntax¶
JMP A sBx pc+=sBx; if (A) close all upvalues >= R(A - 1)
Description¶
Performs an unconditional jump, with sBx as a signed displacement. sBx is added to the program counter (PC), which points to the next instruction to be executed. If sBx is 0, the VM will proceed to the next instruction.
If R(A) is not 0 then all upvalues >= R(A-1) will be closed by calling luaF_close().
OP_JMP
is used in loops, conditional statements, and in expressions when a boolean true/false need to be generated.
Examples¶
For example, since a relational test instruction makes conditional jumps rather than generate a boolean result, a JMP is used in the code sequence for loading either a true or a false:
function x() local m, n; return m >= n end
Generates:
function <stdin:1,1> (7 instructions at 00000034D2ABE340)
0 params, 3 slots, 0 upvalues, 2 locals, 0 constants, 0 functions
1 [1] LOADNIL 0 1
2 [1] LE 1 1 0 ; to 4 if false (n <= m)
3 [1] JMP 0 1 ; to 5
4 [1] LOADBOOL 2 0 1
5 [1] LOADBOOL 2 1 0
6 [1] RETURN 2 2
7 [1] RETURN 0 1
constants (0) for 00000034D2ABE340:
locals (2) for 00000034D2ABE340:
0 m 2 8
1 n 2 8
upvalues (0) for 00000034D2ABE340:
Line[2] performs the relational test. In line [3], the JMP skips over the false path (line [4]) to the true path (line [5]). The result is placed into temporary local 2, and returned to the caller by RETURN in line [6].
OP_VARARG instruction¶
Syntax¶
VARARG A B R(A), R(A+1), ..., R(A+B-1) = vararg
Description¶
VARARG
implements the vararg operator ...
in expressions. VARARG
copies B-1 parameters into a number of registers starting from R(A), padding with nils if there aren’t enough values. If B is 0, VARARG
copies as many values as it can based on the number of parameters passed. If a fixed number of values is required, B is a value greater than 1. If any number of values is required, B is 0.
Examples¶
The use of VARARG will become clear with the help of a few examples:
local a,b,c = ...
Generates:
main <(string):0,0> (2 instructions at 00000029D9FA8310)
0+ params, 3 slots, 1 upvalue, 3 locals, 0 constants, 0 functions
1 [1] VARARG 0 4
2 [1] RETURN 0 1
constants (0) for 00000029D9FA8310:
locals (3) for 00000029D9FA8310:
0 a 2 3
1 b 2 3
2 c 2 3
upvalues (1) for 00000029D9FA8310:
0 _ENV 1 0
Note that the main or top-level chunk is a vararg function. In this example, the left hand side of the assignment statement needs three values (or objects.) So in instruction [1], the operand B of the VARARG
instruction is (3+1), or 4. VARARG
will copy three values into a, b and c. If there are less than three values available, nils will be used to fill up the empty places.
local a = function(...) local a,b,c = ... end
This gives:
main <(string):0,0> (2 instructions at 00000029D9FA72D0)
0+ params, 2 slots, 1 upvalue, 1 local, 0 constants, 1 function
1 [1] CLOSURE 0 0 ; 00000029D9FA86D0
2 [1] RETURN 0 1
constants (0) for 00000029D9FA72D0:
locals (1) for 00000029D9FA72D0:
0 a 2 3
upvalues (1) for 00000029D9FA72D0:
0 _ENV 1 0
function <(string):1,1> (2 instructions at 00000029D9FA86D0)
0+ params, 3 slots, 0 upvalues, 3 locals, 0 constants, 0 functions
1 [1] VARARG 0 4
2 [1] RETURN 0 1
constants (0) for 00000029D9FA86D0:
locals (3) for 00000029D9FA86D0:
0 a 2 3
1 b 2 3
2 c 2 3
upvalues (0) for 00000029D9FA86D0:
Here is an alternate version where a function is instantiated and assigned to local a. The old-style arg is retained for compatibility purposes, but is unused in the above example.
local a; a(...)
Leads to:
main <(string):0,0> (5 instructions at 00000029D9FA6D30)
0+ params, 3 slots, 1 upvalue, 1 local, 0 constants, 0 functions
1 [1] LOADNIL 0 0
2 [1] MOVE 1 0
3 [1] VARARG 2 0
4 [1] CALL 1 0 1
5 [1] RETURN 0 1
constants (0) for 00000029D9FA6D30:
locals (1) for 00000029D9FA6D30:
0 a 2 6
upvalues (1) for 00000029D9FA6D30:
0 _ENV 1 0
When a function is called with ...
as the argument, the function will accept a variable number of parameters or arguments. On instruction [3], a VARARG
with a B field of 0 is used. The VARARG
will copy all the parameters passed on to the main chunk to register 2 onwards, so that the CALL
in the next line can utilize them as parameters of function a
. The function call is set to accept a multiple number of parameters and returns zero results.
local a = {...}
Produces:
main <(string):0,0> (4 instructions at 00000029D9FA8130)
0+ params, 2 slots, 1 upvalue, 1 local, 0 constants, 0 functions
1 [1] NEWTABLE 0 0 0
2 [1] VARARG 1 0
3 [1] SETLIST 0 0 1 ; 1
4 [1] RETURN 0 1
constants (0) for 00000029D9FA8130:
locals (1) for 00000029D9FA8130:
0 a 4 5
upvalues (1) for 00000029D9FA8130:
0 _ENV 1 0
And:
return ...
Produces:
main <(string):0,0> (3 instructions at 00000029D9FA8270)
0+ params, 2 slots, 1 upvalue, 0 locals, 0 constants, 0 functions
1 [1] VARARG 0 0
2 [1] RETURN 0 0
3 [1] RETURN 0 1
constants (0) for 00000029D9FA8270:
locals (0) for 00000029D9FA8270:
upvalues (1) for 00000029D9FA8270:
0 _ENV 1 0
Above are two other cases where VARARG
needs to copy all passed parameters
over to a set of registers in order for the next operation to proceed. Both the above forms of
table creation and return accepts a variable number of values or objects.
OP_LOADBOOL instruction¶
Syntax¶
LOADBOOL A B C R(A) := (Bool)B; if (C) pc++
Description¶
Loads a boolean value (true or false) into register R(A). true is usually encoded as an integer 1, false is always 0. If C is non-zero, then the next instruction is skipped (this is used when you have an assignment statement where the expression uses relational operators, e.g. M = K>5.) You can use any non-zero value for the boolean true in field B, but since you cannot use booleans as numbers in Lua, it’s best to stick to 1 for true.
LOADBOOL
is used for loading a boolean value into a register. It’s also used where a boolean result is supposed to be generated, because relational test instructions, for example, do not generate boolean results – they perform conditional jumps instead. The operand C is used to optionally skip the next instruction (by incrementing PC by 1) in order to support such code. For simple assignments of boolean values, C is always 0.
Examples¶
The following line of code:
f=load('local a,b = true,false')
generates:
main <(string):0,0> (3 instructions at 0000020F274C2610)
0+ params, 2 slots, 1 upvalue, 2 locals, 0 constants, 0 functions
1 [1] LOADBOOL 0 1 0
2 [1] LOADBOOL 1 0 0
3 [1] RETURN 0 1
constants (0) for 0000020F274C2610:
locals (2) for 0000020F274C2610:
0 a 3 4
1 b 3 4
upvalues (1) for 0000020F274C2610:
0 _ENV 1 0
This example is straightforward: Line [1] assigns true to local a (register 0) while line [2] assigns false to local b (register 1). In both cases, field C is 0, so PC is not incremented and the next instruction is not skipped.
Next, look at this line:
f=load('local a = 5 > 2')
This leads to following bytecode:
main <(string):0,0> (5 instructions at 0000020F274BAE00)
0+ params, 2 slots, 1 upvalue, 1 local, 2 constants, 0 functions
1 [1] LT 1 -2 -1 ; 2 5
2 [1] JMP 0 1 ; to 4
3 [1] LOADBOOL 0 0 1
4 [1] LOADBOOL 0 1 0
5 [1] RETURN 0 1
constants (2) for 0000020F274BAE00:
1 5
2 2
locals (1) for 0000020F274BAE00:
0 a 5 6
upvalues (1) for 0000020F274BAE00:
0 _ENV 1 0
This is an example of an expression that gives a boolean result and is assigned to a variable. Notice that Lua does not optimize the expression into a true value; Lua does not perform compile-time constant evaluation for relational operations, but it can perform simple constant evaluation for arithmetic operations.
Since the relational operator LT
does not give a boolean result but performs a conditional jump, LOADBOOL
uses its C operand to perform an unconditional jump in line [3] – this saves one instruction and makes things a little tidier. The reason for all this is that the instruction set is simply optimized for if…then blocks. Essentially, local a = 5 > 2
is executed in the following way:
local a
if 2 < 5 then
a = true
else
a = false
end
In the disassembly listing, when LT
tests 2 < 5, it evaluates to true and doesn’t perform a conditional jump. Line [2] jumps over the false result path, and in line [4], the local a (register 0) is assigned the boolean true by the instruction LOADBOOL
. If 2 and 5 were reversed, line [3] will be followed instead, setting a false, and then the true result path (line [4]) will be skipped, since LOADBOOL
has its field C set to non-zero.
So the true result path goes like this (additional comments in parentheses):
1 [1] LT 1 -2 -1 ; 2 5 (if 2 < 5)
2 [1] JMP 0 1 ; to 4
4 [1] LOADBOOL 0 1 0 ; (a = true)
5 [1] RETURN 0 1
and the false result path (which never executes in this example) goes like this:
1 [1] LT 1 -2 -1 ; 2 5 (if 2 < 5)
3 [1] LOADBOOL 0 0 1 (a = false)
5 [1] RETURN 0 1
The true result path looks longer, but it isn’t, due to the way the virtual machine is implemented. This will be discussed further in the section on relational and logic instructions.
OP_EQ, OP_LT and OP_LE Instructions¶
Relational and logic instructions are used in conjunction with other instructions to implement control structures or expressions. Instead of generating boolean results, these instructions conditionally perform a jump over the next instruction; the emphasis is on implementing control blocks. Instructions are arranged so that there are two paths to follow based on the relational test.
EQ A B C if ((RK(B) == RK(C)) ~= A) then PC++
LT A B C if ((RK(B) < RK(C)) ~= A) then PC++
LE A B C if ((RK(B) <= RK(C)) ~= A) then PC++
Description¶
Compares RK(B) and RK(C), which may be registers or constants. If the boolean result is not A, then skip the next instruction. Conversely, if the boolean result equals A, continue with the next instruction.
EQ
is for equality. LT
is for “less than” comparison. LE
is for “less than or equal to”
comparison. The boolean A field allows the full set of relational comparison operations to be
synthesized from these three instructions. The Lua code generator produces either 0 or 1 for the boolean A.
For the fall-through case, a OP_JMP instruction is always expected, in order to optimize execution in the
virtual machine. In effect, EQ
, LT
and LE
must always be paired with a following JMP
instruction.
Examples¶
By comparing the result of the relational operation with A, the sense of the comparison can
be reversed. Obviously the alternative is to reverse the paths taken by the instruction, but that
will probably complicate code generation some more. The conditional jump is performed if the comparison
result is not A, whereas execution continues normally if the comparison result matches A.
Due to the way code is generated and the way the virtual machine works, a JMP
instruction is
always expected to follow an EQ
, LT
or LE
. The following JMP
is optimized by
executing it in conjunction with EQ
, LT
or LE
.
local x,y; return x ~= y
Generates:
main <(string):0,0> (7 instructions at 0000001BC48FD390)
0+ params, 3 slots, 1 upvalue, 2 locals, 0 constants, 0 functions
1 [1] LOADNIL 0 1
2 [1] EQ 0 0 1
3 [1] JMP 0 1 ; to 5
4 [1] LOADBOOL 2 0 1
5 [1] LOADBOOL 2 1 0
6 [1] RETURN 2 2
7 [1] RETURN 0 1
constants (0) for 0000001BC48FD390:
locals (2) for 0000001BC48FD390:
0 x 2 8
1 y 2 8
upvalues (1) for 0000001BC48FD390:
0 _ENV 1 0
In the above example, the equality test is performed in instruction [2]. However, since the
comparison need to be returned as a result, LOADBOOL
instructions are used to set a
register with the correct boolean value. This is the usual code pattern generated if the expression
requires a boolean value to be generated and stored in a register as an intermediate value or
a final result.
It is easier to visualize the disassembled code as:
if x ~= y then
return true
else
return false
end
The true result path (when the comparison result matches A) goes like this:
1 [1] LOADNIL 0 1
2 [1] EQ 0 0 1 ; to 4 if true (x ~= y)
3 [1] JMP 1 ; to 5
5 [1] LOADBOOL 2 1 0 ; true (true path)
6 [1] RETURN 2 2
While the false result path (when the comparison result does not match A) goes like this:
1 [1] LOADNIL 0 1
2 [1] EQ 0 0 1 ; to 4 if true (x ~= y)
4 [1] LOADBOOL 2 0 1 ; false, to 6 (false path)
6 [1] RETURN 2 2
Comments following the EQ
in line [2] lets the user know when the conditional jump
is taken. The jump is taken when “the value in register 0 equals to the value in register 1”
(the comparison) is not false (the value of operand A). If the comparison is x == y,
everything will be the same except that the A operand in the EQ
instruction will be 1,
thus reversing the sense of the comparison. Anyway, these are just the Lua code generator’s
conventions; there are other ways to code x ~= y in terms of Lua virtual machine instructions.
For conditional statements, there is no need to set boolean results. Lua is optimized for coding the more common conditional statements rather than conditional expressions.
local x,y; if x ~= y then return "foo" else return "bar" end
Results in:
main <(string):0,0> (9 instructions at 0000001BC4914D50)
0+ params, 3 slots, 1 upvalue, 2 locals, 2 constants, 0 functions
1 [1] LOADNIL 0 1
2 [1] EQ 1 0 1 ; to 4 if false (x ~= y)
3 [1] JMP 0 3 ; to 7
4 [1] LOADK 2 -1 ; "foo" (true block)
5 [1] RETURN 2 2
6 [1] JMP 0 2 ; to 9
7 [1] LOADK 2 -2 ; "bar" (false block)
8 [1] RETURN 2 2
9 [1] RETURN 0 1
constants (2) for 0000001BC4914D50:
1 "foo"
2 "bar"
locals (2) for 0000001BC4914D50:
0 x 2 10
1 y 2 10
upvalues (1) for 0000001BC4914D50:
0 _ENV 1 0
In the above conditional statement, the same inequality operator is used in the source,
but the sense of the EQ
instruction in line [2] is now reversed. Since the EQ
conditional jump can only skip the next instruction, additional JMP
instructions
are needed to allow large blocks of code to be placed in both true and false paths.
In contrast, in the previous example, only a single instruction is needed to set a
boolean value. For if
statements, the true block comes first followed by the false
block in code generated by the code generator. To reverse the positions of the true and
false paths, the value of operand A is changed.
The true path (when x ~= y
is true) goes from [2] to [4]–[6] and on to [9]. Since
there is a RETURN
in line [5], the JMP
in line [6] and the RETURN
in [9]
are never executed at all; they are redundant but does not adversely affect performance
in any way. The false path is from [2] to [3] to [7]–[9] onwards. So in a disassembly
listing, you should see the true and false code blocks in the same order as in the
Lua source.
The following is another example, this time with an elseif
:
if 8 > 9 then return 8 elseif 5 >= 4 then return 5 else return 9 end
Generates:
main <(string):0,0> (13 instructions at 0000001BC4913770)
0+ params, 2 slots, 1 upvalue, 0 locals, 4 constants, 0 functions
1 [1] LT 0 -2 -1 ; 9 8
2 [1] JMP 0 3 ; to 6
3 [1] LOADK 0 -1 ; 8
4 [1] RETURN 0 2
5 [1] JMP 0 7 ; to 13
6 [1] LE 0 -4 -3 ; 4 5
7 [1] JMP 0 3 ; to 11
8 [1] LOADK 0 -3 ; 5
9 [1] RETURN 0 2
10 [1] JMP 0 2 ; to 13
11 [1] LOADK 0 -2 ; 9
12 [1] RETURN 0 2
13 [1] RETURN 0 1
constants (4) for 0000001BC4913770:
1 8
2 9
3 5
4 4
locals (0) for 0000001BC4913770:
upvalues (1) for 0000001BC4913770:
0 _ENV 1 0
This example is a little more complex, but the blocks are structured in the same order as the Lua source, so interpreting the disassembled code should not be too hard.
OP_TEST and OP_TESTSET instructions¶
Syntax¶
TEST A C if (boolean(R(A)) != C) then PC++
TESTSET A B C if (boolean(R(B)) != C) then PC++ else R(A) := R(B)
where boolean(x) => ((x == nil || x == false) ? 0 : 1)
Description¶
These two instructions used for performing boolean tests and implementing Lua’s logical operators.
Used to implement and
and or
logical operators, or for testing a single register in a conditional statement.
For TESTSET
, register R(B)
is coerced into a boolean (i.e. false
and nil
evaluate to 0
and any other value to 1
) and
compared to the boolean field C
(0
or 1
). If boolean(R(B))
does not match C
, the next instruction is skipped,
otherwise R(B)
is assigned to R(A)
and the VM continues with the next instruction. The and
operator uses a C
of 0
(false) while
or
uses a C value of 1
(true).
TEST
is a more primitive version of TESTSET
. TEST
is used when the assignment operation is not needed, otherwise it is the same as TESTSET
except that the operand slots are different.
For the fall-through case, a JMP
is always expected, in order to optimize execution in the virtual machine. In effect, TEST
and TESTSET
must always be paired with a following JMP
instruction.
Examples¶
TEST
and TESTSET
are used in conjunction with a following JMP
instruction, while TESTSET
has an addditional conditional assignment. Like EQ
, LT
and LE
, the following JMP
instruction is compulsory, as the virtual machine will execute the JMP
together with TEST
or TESTSET
. The two instructions are used to implement short-circuit LISP-style logical operators that retains and propagates operand values instead of booleans. First, we’ll look at how and and or behaves:
f=load('local a,b,c; c = a and b')
Generates:
main <(string):0,0> (5 instructions at 0000020F274CF1A0)
0+ params, 3 slots, 1 upvalue, 3 locals, 0 constants, 0 functions
1 [1] LOADNIL 0 2
2 [1] TESTSET 2 0 0 ; to 4 if true
3 [1] JMP 0 1 ; to 5
4 [1] MOVE 2 1
5 [1] RETURN 0 1
constants (0) for 0000020F274CF1A0:
locals (3) for 0000020F274CF1A0:
0 a 2 6
1 b 2 6
2 c 2 6
upvalues (1) for 0000020F274CF1A0:
0 _ENV 1 0
An and
sequence exits on false
operands (which can be false
or nil
) because any false
operands in a string of and
operations
will make the whole boolean expression false
. If operands evaluates to true
, evaluation continues. When a string of and
operations
evaluates to true
, the result is the last operand value.
In line [2], C
is 0
. Since B
is 0
, therefore R(B)
refers to the local a
. Since R(B)
is nil
then boolean(R(B))
evaluates to 0
.
Thus C
matches boolean(R(B))
. Therefore the value of a
is assigned to c
and the next instruction which is a JMP
is executed. This is equivalent to:
if a then
c = b -- executed by MOVE on line [4]
else
c = a -- executed by TESTSET on line [2]
end
The c = a
portion is done by TESTSET
itself, while MOVE
performs c = b
. Now, if the result is already set with one of the possible values,
a TEST
instruction is used instead:
f=load('local a,b; a = a and b')
Generates:
main <(string):0,0> (5 instructions at 0000020F274D0A70)
0+ params, 2 slots, 1 upvalue, 2 locals, 0 constants, 0 functions
1 [1] LOADNIL 0 1
2 [1] TEST 0 0 ; to 4 if true
3 [1] JMP 0 1 ; to 5
4 [1] MOVE 0 1
5 [1] RETURN 0 1
constants (0) for 0000020F274D0A70:
locals (2) for 0000020F274D0A70:
0 a 2 6
1 b 2 6
upvalues (1) for 0000020F274D0A70:
0 _ENV 1 0
Here C
is 0
, and boolean(R(A))
is 0
too, so that the TEST
instruction on line [2] does not skip the next instruction which is a JMP
.
The TEST
instruction does not perform an assignment operation, since a = a
is redundant. This makes TEST
a little faster. This is equivalent to:
if a then
a = b
end
Next, we will look at the or operator:
f=load('local a,b,c; c = a or b')
Generates:
main <(string):0,0> (5 instructions at 0000020F274D1AB0)
0+ params, 3 slots, 1 upvalue, 3 locals, 0 constants, 0 functions
1 [1] LOADNIL 0 2
2 [1] TESTSET 2 0 1 ; to 4 if false
3 [1] JMP 0 1 ; to 5
4 [1] MOVE 2 1
5 [1] RETURN 0 1
constants (0) for 0000020F274D1AB0:
locals (3) for 0000020F274D1AB0:
0 a 2 6
1 b 2 6
2 c 2 6
upvalues (1) for 0000020F274D1AB0:
0 _ENV 1 0
An or
sequence exits on true
operands, because any operands evaluating to true
in a string of or operations will make the whole boolean
expression true
. If operands evaluates to false
, evaluation continues. When a string of or operations evaluates to false
,
all operands must have evaluated to false
.
In line [2], C
is 1
. Since B
is 0
, therefore R(B)
refers to the local a
. Since R(B)
is nil
then boolean(R(B))
evaluates to 0
.
Thus C
does not match boolean(R(B))
. Therefore, the next instruction which is a JMP
is skipped and execution continues on line [4]. This is equivalent to:
if a then
c = a -- executed by TESTSET on line [2]
else
c = b -- executed by MOVE on line [4]
end
Like the case of and
, TEST
is used when the result already has one of the possible values, saving an assignment operation:
f=load('local a,b; a = a or b')
Generates:
main <(string):0,0> (5 instructions at 0000020F274D1010)
0+ params, 2 slots, 1 upvalue, 2 locals, 0 constants, 0 functions
1 [1] LOADNIL 0 1
2 [1] TEST 0 1 ; to 4 if false
3 [1] JMP 0 1 ; to 5
4 [1] MOVE 0 1
5 [1] RETURN 0 1
constants (0) for 0000020F274D1010:
locals (2) for 0000020F274D1010:
0 a 2 6
1 b 2 6
upvalues (1) for 0000020F274D1010:
0 _ENV 1 0
Short-circuit logical operators also means that the following Lua code does not require the use of a boolean operation:
f=load('local a,b,c; if a > b and a > c then return a end')
Leads to:
main <(string):0,0> (7 instructions at 0000020F274D1150)
0+ params, 3 slots, 1 upvalue, 3 locals, 0 constants, 0 functions
1 [1] LOADNIL 0 2
2 [1] LT 0 1 0 ; to 4 if true
3 [1] JMP 0 3 ; to 7
4 [1] LT 0 2 0 ; to 6 if true
5 [1] JMP 0 1 ; to 7
6 [1] RETURN 0 2
7 [1] RETURN 0 1
constants (0) for 0000020F274D1150:
locals (3) for 0000020F274D1150:
0 a 2 8
1 b 2 8
2 c 2 8
upvalues (1) for 0000020F274D1150:
0 _ENV 1 0
With short-circuit evaluation, a > c
is never executed if a > b
is false, so the logic of the Lua statement can be readily implemented using the normal conditional structure. If both a > b
and a > c
are true, the path followed is [2] (the a > b
test) to [4] (the a > c
test) and finally to [6], returning the value of a
. A TEST
instruction is not required. This is equivalent to:
if a > b then
if a > c then
return a
end
end
For a single variable used in the expression part of a conditional statement, TEST
is used to boolean-test the variable:
f=load('if Done then return end')
Generates:
main <(string):0,0> (5 instructions at 0000020F274D13D0)
0+ params, 2 slots, 1 upvalue, 0 locals, 1 constant, 0 functions
1 [1] GETTABUP 0 0 -1 ; _ENV "Done"
2 [1] TEST 0 0 ; to 4 if true
3 [1] JMP 0 1 ; to 5
4 [1] RETURN 0 1
5 [1] RETURN 0 1
constants (1) for 0000020F274D13D0:
1 "Done"
locals (0) for 0000020F274D13D0:
upvalues (1) for 0000020F274D13D0:
0 _ENV 1 0
In line [2], the TEST
instruction jumps to the true
block if the value in temporary register 0 (from the global Done
) is true
. The JMP
at line [3] jumps over the true
block, which is the code inside the if block (line [4]).
If the test expression of a conditional statement consist of purely boolean operators, then a number of TEST instructions will be used in the usual short-circuit evaluation style:
f=load('if Found and Match then return end')
Generates:
main <(string):0,0> (8 instructions at 0000020F274D1C90)
0+ params, 2 slots, 1 upvalue, 0 locals, 2 constants, 0 functions
1 [1] GETTABUP 0 0 -1 ; _ENV "Found"
2 [1] TEST 0 0 ; to 4 if true
3 [1] JMP 0 4 ; to 8
4 [1] GETTABUP 0 0 -2 ; _ENV "Match"
5 [1] TEST 0 0 ; to 7 if true
6 [1] JMP 0 1 ; to 8
7 [1] RETURN 0 1
8 [1] RETURN 0 1
constants (2) for 0000020F274D1C90:
1 "Found"
2 "Match"
locals (0) for 0000020F274D1C90:
upvalues (1) for 0000020F274D1C90:
0 _ENV 1 0
In the last example, the true block of the conditional statement is executed only if both Found
and Match
evaluate to true
. The path is from [2] (test for Found
) to [4] to [5] (test for Match
) to [7] (the true block, which is an explicit return
statement.)
If the statement has an else
section, then the JMP
on line [6] will jump to the false block (the else
block) while an additional JMP
will be added to the true block to jump over this new block of code. If or
is used instead of and
, the appropriate C operand will be adjusted accordingly.
Finally, here is how Lua’s ternary operator (:? in C) equivalent works:
f=load('local a,b,c; a = a and b or c')
Generates:
main <(string):0,0> (7 instructions at 0000020F274D1A10)
0+ params, 3 slots, 1 upvalue, 3 locals, 0 constants, 0 functions
1 [1] LOADNIL 0 2
2 [1] TEST 0 0 ; to 4 if true
3 [1] JMP 0 2 ; to 6
4 [1] TESTSET 0 1 1 ; to 6 if false
5 [1] JMP 0 1 ; to 7
6 [1] MOVE 0 2
7 [1] RETURN 0 1
constants (0) for 0000020F274D1A10:
locals (3) for 0000020F274D1A10:
0 a 2 8
1 b 2 8
2 c 2 8
upvalues (1) for 0000020F274D1A10:
0 _ENV 1 0
The TEST
in line [2] is for the and
operator. First, local a
is tested in line [2]. If it is false, then execution continues in [3], jumping to line [6]. Line [6] assigns local c
to the end result because since if a
is false, then a and b
is false
, and false or c
is c
.
If local a
is true
in line [2], the TEST
instruction makes a jump to line [4], where there is a TESTSET
, for the or
operator. If b
evaluates to true
, then the end result is assigned the value of b
, because b or c
is b
if b
is not false
. If b
is also false
, the end result will be c
.
For the instructions in line [2], [4] and [6], the target (in field A) is register 0, or the local a
, which is the location where the result of the boolean expression is assigned. The equivalent Lua code is:
if a then
if b then
a = b
else
a = c
end
else
a = c
end
The two a = c
assignments are actually the same piece of code, but are repeated here to avoid using a goto
and a label. Normally, if we assume b
is not false
and not nil
, we end up with the more recognizable form:
if a then
a = b -- assuming b ~= false
else
a = c
end
OP_FORPREP and OP_FORLOOP instructions¶
Syntax¶
FORPREP A sBx R(A)-=R(A+2); pc+=sBx
FORLOOP A sBx R(A)+=R(A+2);
if R(A) <?= R(A+1) then { pc+=sBx; R(A+3)=R(A) }
Description¶
Lua has dedicated instructions to implement the two types of for
loops, while the other two types of loops uses traditional test-and-jump.
FORPREP
initializes a numeric for loop, while FORLOOP
performs an iteration of a numeric for loop.
A numeric for loop requires 4 registers on the stack, and each register must be a number. R(A) holds the initial value and doubles as the internal loop variable (the internal index); R(A+1) is the limit; R(A+2) is the stepping value; R(A+3) is the actual loop variable (the external index) that is local to the for block.
FORPREP
sets up a for loop. Since FORLOOP
is used for initial testing of the loop condition as well as conditional testing during the loop itself, FORPREP
performs a negative step and jumps unconditionally to FORLOOP
so that FORLOOP
is able to correctly make the initial loop test. After this initial test, FORLOOP
performs a loop step as usual, restoring the initial value of the loop index so that the first iteration can start.
In FORLOOP
, a jump is made back to the start of the loop body if the limit has not been reached or exceeded. The sense of the comparison depends on whether the stepping is negative or positive, hence the “<?=” operator. Jumps for both instructions are encoded as signed displacements in the sBx
field. An empty loop has a FORLOOP
sBx
value of -1.
FORLOOP
also sets R(A+3), the external loop index that is local to the loop block. This is significant if the loop index is used as an upvalue (see below.) R(A), R(A+1) and R(A+2) are not visible to the programmer.
The loop variable ends with the last value before the limit is reached (unlike C) because it is not updated unless the jump is made. However, since loop variables are local to the loop itself, you should not be able to use it unless you cook up an implementation-specific hack.
Examples¶
For the sake of efficiency, FORLOOP
contains a lot of functionality, so when a loop iterates, only one instruction, FORLOOP
, is needed. Here is a simple example:
f=load('local a = 0; for i = 1,100,5 do a = a + i end')
Generates:
main <(string):0,0> (8 instructions at 000001E9F0DF52F0)
0+ params, 5 slots, 1 upvalue, 5 locals, 4 constants, 0 functions
1 [1] LOADK 0 -1 ; 0
2 [1] LOADK 1 -2 ; 1
3 [1] LOADK 2 -3 ; 100
4 [1] LOADK 3 -4 ; 5
5 [1] FORPREP 1 1 ; to 7
6 [1] ADD 0 0 4
7 [1] FORLOOP 1 -2 ; to 6
8 [1] RETURN 0 1
constants (4) for 000001E9F0DF52F0:
1 0
2 1
3 100
4 5
locals (5) for 000001E9F0DF52F0:
0 a 2 9
1 (for index) 5 8
2 (for limit) 5 8
3 (for step) 5 8
4 i 6 7
upvalues (1) for 000001E9F0DF52F0:
0 _ENV 1 0
In the above example, notice that the for
loop causes three additional local pseudo-variables (or internal variables) to be defined, apart from the external loop index, i
. The three pseudovariables, named (for index)
, (for limit)
and (for step)
are required to completely specify the state of the loop, and are not visible to Lua source code. They are arranged in consecutive registers, with the external loop index given by R(A+3) or register 4 in the example.
The loop body is in line [6] while line [7] is the FORLOOP
instruction that steps through the loop state. The sBx
field of FORLOOP
is negative, as it always jumps back to the beginning of the loop body.
Lines [2]–[4] initialize the three register locations where the loop state will be stored. If the loop step is not specified in the Lua source, a constant 1 is added to the constant pool and a LOADK
instruction is used to initialize the pseudo-variable (for step)
with the loop step.
FORPREP
in lines [5] makes a negative loop step and jumps to line [7] for the initial test. In the example, at line [5], the internal loop index (at register 1) will be (1-5) or -4. When the virtual machine arrives at the FORLOOP
in line [7] for the first time, one loop step is made prior to the first test, so the initial value that is actually tested against the limit is (-4+5) or 1. Since 1 < 100, an iteration will be performed. The external loop index i
is then set to 1 and a jump is made to line [6], thus starting the first iteration of the loop.
The loop at line [6]–[7] repeats until the internal loop index exceeds the loop limit of 100. The conditional jump is not taken when that occurs and the loop ends. Beyond the scope of the loop body, the loop state ((for index)
, (for limit)
, (for step)
and i
) is not valid. This is determined by the parser and code generator. The range of PC values for which the loop state variables are valid is located in the locals list.
Here is another example:
f=load('for i = 10,1,-1 do if i == 5 then break end end')
This leads to:
main <(string):0,0> (8 instructions at 000001E9F0DEC110)
0+ params, 4 slots, 1 upvalue, 4 locals, 4 constants, 0 functions
1 [1] LOADK 0 -1 ; 10
2 [1] LOADK 1 -2 ; 1
3 [1] LOADK 2 -3 ; -1
4 [1] FORPREP 0 2 ; to 7
5 [1] EQ 1 3 -4 ; - 5
6 [1] JMP 0 1 ; to 8
7 [1] FORLOOP 0 -3 ; to 5
8 [1] RETURN 0 1
constants (4) for 000001E9F0DEC110:
1 10
2 1
3 -1
4 5
locals (4) for 000001E9F0DEC110:
0 (for index) 4 8
1 (for limit) 4 8
2 (for step) 4 8
3 i 5 7
upvalues (1) for 000001E9F0DEC110:
0 _ENV 1 0
In the second loop example above, except for a negative loop step size, the structure of the loop is identical. The body of the loop is from line [5] to line [7]. Since no additional stacks or states are used, a break translates simply to a JMP
instruction (line [6]). There is nothing to clean up after a FORLOOP
ends or after a JMP
to exit a loop.
OP_TFORCALL and OP_TFORLOOP instructions¶
Syntax¶
TFORCALL A C R(A+3), ... ,R(A+2+C) := R(A)(R(A+1), R(A+2))
TFORLOOP A sBx if R(A+1) ~= nil then { R(A)=R(A+1); pc += sBx }
Description¶
Apart from a numeric for
loop (implemented by FORPREP
and FORLOOP
), Lua has a generic for
loop, implemented by TFORCALL
and TFORLOOP
.
The generic for
loop keeps 3 items in consecutive register locations to keep track of things. R(A) is the iterator function, which is called once per loop. R(A+1) is the state, and R(A+2) is the control variable. At the start, R(A+2) has an initial value. R(A), R(A+1) and R(A+2) are internal to the loop and cannot be accessed by the programmer.
In addition to these internal loop variables, the programmer specifies one or more loop variables that are external and visible to the programmer. These loop variables reside at locations R(A+3) onwards, and their count is specified in operand C. Operand C must be at least 1. They are also local to the loop body, like the external loop index in a numerical for loop.
Each time TFORCALL
executes, the iterator function referenced by R(A) is called with two arguments: the state and the control variable (R(A+1) and R(A+2)). The results are returned in the local loop variables, from R(A+3) onwards, up to R(A+2+C).
Next, the TFORLOOP
instruction tests the first return value, R(A+3). If it is nil, the iterator loop is at an end, and the for
loop block ends by simply moving to the next instruction.
If R(A+3) is not nil, there is another iteration, and R(A+3) is assigned as the new value of the control variable, R(A+2). Then the TFORLOOP
instruction sends execution back to the beginning of the loop (the sBx
operand specifies how many instructions to move to get to the start of the loop body).
Examples¶
This example has a loop with one additional result (v
) in addition to the loop enumerator (i
):
f=load('for i,v in pairs(t) do print(i,v) end')
This produces:
main <(string):0,0> (11 instructions at 0000014DB7FD2610)
0+ params, 8 slots, 1 upvalue, 5 locals, 3 constants, 0 functions
1 [1] GETTABUP 0 0 -1 ; _ENV "pairs"
2 [1] GETTABUP 1 0 -2 ; _ENV "t"
3 [1] CALL 0 2 4
4 [1] JMP 0 4 ; to 9
5 [1] GETTABUP 5 0 -3 ; _ENV "print"
6 [1] MOVE 6 3
7 [1] MOVE 7 4
8 [1] CALL 5 3 1
9 [1] TFORCALL 0 2
10 [1] TFORLOOP 2 -6 ; to 5
11 [1] RETURN 0 1
constants (3) for 0000014DB7FD2610:
1 "pairs"
2 "t"
3 "print"
locals (5) for 0000014DB7FD2610:
0 (for generator) 4 11
1 (for state) 4 11
2 (for control) 4 11
3 i 5 9
4 v 5 9
upvalues (1) for 0000014DB7FD2610:
0 _ENV 1 0
The iterator function is located in register 0, and is named (for generator)
for debugging purposes. The state is in register 1, and has the name (for state)
. The control variable, (for control)
, is contained in register 2. These correspond to locals R(A), R(A+1) and R(A+2) in the TFORCALL
description. Results from the iterator function call is placed into register 3 and 4, which are locals i
and v
, respectively. On line [9], the operand C of TFORCALL
is 2, corresponding to two iterator variables (i
and v
).
Lines [1]–[3] prepares the iterator state. Note that the call to the pairs()
standard library function has 1 parameter and 3 results. After the call in line [3], register 0 is the iterator function (which by default is the Lua function next()
unless __pairs
meta method has been overriden), register 1 is the loop state, register 2 is the initial value of the control variable (which is nil
in the default case). The iterator variables i
and v
are both invalid at the moment, because we have not entered the loop yet.
Line [4] is a JMP
to TFORCALL
on line [9]. The TFORCALL
instruction calls the iterator function, generating the first set of enumeration results in locals i
and v
.
The TFORLOOP
insruction executes and checks whether i
is nil
. If it is not nil
, then the internal control variable (register 2) is set to the value in i
and control goes back to to the start of the loop body (lines [5]–[8]).
The body of the generic for
loop executes (print(i,v)
) and then TFORCALL
is encountered again, calling the iterator function to get the next iteration state. Finally, when the TFORLOOP
finds that the first result from the iterator is nil
, the loop ends, and execution continues on line [11].
OP_CLOSURE instruction¶
Syntax¶
CLOSURE A Bx R(A) := closure(KPROTO[Bx])
Description¶
Creates an instance (or closure) of a function prototype. The Bx
parameter
identifies the entry in the parent function’s table of closure prototypes (the field p
in the struct Proto
). The indices start from 0, i.e., a parameter of Bx = 0 references the first
closure prototype in the table.
The OP_CLOSURE
instruction also sets up the upvalues
for the closure being defined. This
is an involved process that is worthy of detailed discussion, and will be described through examples.
Examples¶
Let’s start with a simple example of a Lua function:
f=load('function x() end; function y() end')
Here we are creating two Lua functions/closures within the main chunk. The bytecodes for the chunk look this:
main <(string):0,0> (5 instructions at 0000020E8A352930)
0+ params, 2 slots, 1 upvalue, 0 locals, 2 constants, 2 functions
1 [1] CLOSURE 0 0 ; 0000020E8A352A70
2 [1] SETTABUP 0 -1 0 ; _ENV "x"
3 [1] CLOSURE 0 1 ; 0000020E8A3536A0
4 [1] SETTABUP 0 -2 0 ; _ENV "y"
5 [1] RETURN 0 1
constants (2) for 0000020E8A352930:
1 "x"
2 "y"
locals (0) for 0000020E8A352930:
upvalues (1) for 0000020E8A352930:
0 _ENV 1 0
function <(string):1,1> (1 instruction at 0000020E8A352A70)
0 params, 2 slots, 0 upvalues, 0 locals, 0 constants, 0 functions
1 [1] RETURN 0 1
constants (0) for 0000020E8A352A70:
locals (0) for 0000020E8A352A70:
upvalues (0) for 0000020E8A352A70:
function <(string):1,1> (1 instruction at 0000020E8A3536A0)
0 params, 2 slots, 0 upvalues, 0 locals, 0 constants, 0 functions
1 [1] RETURN 0 1
constants (0) for 0000020E8A3536A0:
locals (0) for 0000020E8A3536A0:
upvalues (0) for 0000020E8A3536A0:
What we observe is that the first CLOSURE
instruction has parameter
Bx
set to 0, and this is the reference to the closure 0000020E8A352A70
which appears at position 0 in the table of closures within the main chunk’s
Proto
structure.
Similarly the second CLOSURE
instruction has parameter Bx
set to 1,
and this references the closure at position 1 in the table, which is
0000020E8A3536A0.
Other things to notice is that the main chunk got an automatic upvalue
named _ENV
:
upvalues (1) for 0000020E8A352930:
0 _ENV 1 0
The first 0
is the index of the upvalue in the main chunk. The 1
following the name is a boolean indicating that the upvalue is located on the
stack, and the last 0
is identifies the register location on the stack.
So the Lua Parser has setup the upvalue
reference for _ENV
. However
note that there is no actual local in this case; the _ENV
upvalue is
special and is setup by the Lua lua_load()
API function.
Now let’s look at an example that creates a local up-value:
f=load('local u,v; function p() return v end')
We get following bytecodes:
main <(string):0,0> (4 instructions at 0000022149BBA3B0)
0+ params, 3 slots, 1 upvalue, 2 locals, 1 constant, 1 function
1 [1] LOADNIL 0 1
2 [1] CLOSURE 2 0 ; 0000022149BBB7B0
3 [1] SETTABUP 0 -1 2 ; _ENV "p"
4 [1] RETURN 0 1
constants (1) for 0000022149BBA3B0:
1 "p"
locals (2) for 0000022149BBA3B0:
0 u 2 5
1 v 2 5
upvalues (1) for 0000022149BBA3B0:
0 _ENV 1 0
function <(string):1,1> (3 instructions at 0000022149BBB7B0)
0 params, 2 slots, 1 upvalue, 0 locals, 0 constants, 0 functions
1 [1] GETUPVAL 0 0 ; v
2 [1] RETURN 0 2
3 [1] RETURN 0 1
constants (0) for 0000022149BBB7B0:
locals (0) for 0000022149BBB7B0:
upvalues (1) for 0000022149BBB7B0:
0 v 1 1
In the function ‘p’ the upvalue list contains:
upvalues (1) for 0000022149BBB7B0:
0 v 1 1
This says that the up-value is in the stack (first ‘1’) and is located at register ‘1’ of
the parent function. Access to this upvalue is indirectly obtained via the GETUPVAL
instruction on line 1.
Now, lets look at what happens when the upvalue is not directly within the parent function:
f=load('local u,v; function p() u=1; local function q() return v end end')
In this example, we have 1 upvalue reference in function ‘p’, which is ‘u’. Function ‘q’ has one upvalue reference ‘v’ but this is not a variable in ‘p’, but is in the grand-parent. Here are the resulting bytecodes:
main <(string):0,0> (4 instructions at 0000022149BBFE40)
0+ params, 3 slots, 1 upvalue, 2 locals, 1 constant, 1 function
1 [1] LOADNIL 0 1
2 [1] CLOSURE 2 0 ; 0000022149BBFC60
3 [1] SETTABUP 0 -1 2 ; _ENV "p"
4 [1] RETURN 0 1
constants (1) for 0000022149BBFE40:
1 "p"
locals (2) for 0000022149BBFE40:
0 u 2 5
1 v 2 5
upvalues (1) for 0000022149BBFE40:
0 _ENV 1 0
function <(string):1,1> (4 instructions at 0000022149BBFC60)
0 params, 2 slots, 2 upvalues, 1 local, 1 constant, 1 function
1 [1] LOADK 0 -1 ; 1
2 [1] SETUPVAL 0 0 ; u
3 [1] CLOSURE 0 0 ; 0000022149BC06B0
4 [1] RETURN 0 1
constants (1) for 0000022149BBFC60:
1 1
locals (1) for 0000022149BBFC60:
0 q 4 5
upvalues (2) for 0000022149BBFC60:
0 u 1 0
1 v 1 1
function <(string):1,1> (3 instructions at 0000022149BC06B0)
0 params, 2 slots, 1 upvalue, 0 locals, 0 constants, 0 functions
1 [1] GETUPVAL 0 0 ; v
2 [1] RETURN 0 2
3 [1] RETURN 0 1
constants (0) for 0000022149BC06B0:
locals (0) for 0000022149BC06B0:
upvalues (1) for 0000022149BC06B0:
0 v 0 1
We see that ‘p’ got the upvalue ‘u’ as expected, but it also got the upvalue ‘v’, and both are marked as ‘instack’ of the parent function:
upvalues (2) for 0000022149BBFC60:
0 u 1 0
1 v 1 1
The reason for this is that any upvalue references in the inmost nested function will also appear in the parent functions up the chain until the function whose stack contains the variable being referenced. So although the function ‘p’ does not directly reference ‘v’, but because its child function ‘q’ references ‘v’, ‘p’ gets the upvalue reference to ‘v’ as well.
Observe the upvalue list of ‘q’ now:
upvalues (1) for 0000022149BC06B0:
0 v 0 1
‘q’ has one upvalue reference as expected, but this time the upvalue is not marked ‘instack’, which means that the reference is to an upvalue and not a local in the parent function (in this case ‘p’) and the upvalue index is ‘1’ (i.e. the second upvalue in ‘p’).
Upvalue setup by OP_CLOSURE¶
When the CLOSURE
instruction is executed, the up-values referenced by the
prototype are resolved. So that means the actual resolution if upvalues occurs at
runtime. This is done in the function pushclosure().
Caching of closures¶
The Lua VM maintains a cache of closures within each function prototype at runtime. If a closure is required that has the same set of upvalues as referenced by an existing closure then the VM reuses the existing closure rather than creating a new one. This is illustrated in this contrived example:
f=load('local v; local function q() return function() return v end end; return q(), q()')
When the statement return q(), q()
is executed it will end up returning two closures that
are really the same instance, as shown by the result of executing this code:
> f()
function: 000001E1E2F007E0 function: 000001E1E2F007E0
OP_GETUPVAL and OP_SETUPVAL instructions¶
Syntax¶
GETUPVAL A B R(A) := UpValue[B]
SETUPVAL A B UpValue[B] := R(A)
Description¶
GETUPVAL
copies the value in upvalue number B
into register R(A)
. Each Lua function may have its own upvalue list. This upvalue list is internal to the virtual machine; the list of upvalue name strings in a prototype is not mandatory.
SETUPVAL
copies the value from register R(A)
into the upvalue number B
in the upvalue list for that function.
Examples¶
GETUPVAL
and SETUPVAL
instructions use internally-managed upvalue lists. The list of upvalue name strings that are found in a function prototype is for debugging purposes; it is not used by the Lua virtual machine and can be stripped by luac
.
During execution, upvalues are set up by a CLOSURE
, and maintained by the Lua virtual machine. In the following example, function b
is declared inside the main chunk, and is shown in the disassembly as a function prototype within a function prototype. The indentation, which is not in the original output, helps to visually separate the two functions.
f=load('local a; function b() a = 1 return a end')
Leads to:
main <(string):0,0> (4 instructions at 000002853D5177F0)
0+ params, 2 slots, 1 upvalue, 1 local, 1 constant, 1 function
1 [1] LOADNIL 0 0
2 [1] CLOSURE 1 0 ; 000002853D517920
3 [1] SETTABUP 0 -1 1 ; _ENV "b"
4 [1] RETURN 0 1
constants (1) for 000002853D5177F0:
1 "b"
locals (1) for 000002853D5177F0:
0 a 2 5
upvalues (1) for 000002853D5177F0:
0 _ENV 1 0
function <(string):1,1> (5 instructions at 000002853D517920)
0 params, 2 slots, 1 upvalue, 0 locals, 1 constant, 0 functions
1 [1] LOADK 0 -1 ; 1
2 [1] SETUPVAL 0 0 ; a
3 [1] GETUPVAL 0 0 ; a
4 [1] RETURN 0 2
5 [1] RETURN 0 1
constants (1) for 000002853D517920:
1 1
locals (0) for 000002853D517920:
upvalues (1) for 000002853D517920:
0 a 1 0
In the main chunk, the local a
starts as a nil
. The CLOSURE
instruction in line [2] then instantiates a function closure with a single upvalue, a
. In line [3] the closure is assigned to global b
via the SETTABUP
instruction.
In function b
, there is a single upvalue, a. In Pascal, a variable in an outer scope is found by traversing stack frames. However, instantiations of Lua functions are first-class values, and they may be assigned to a variable and referenced elsewhere. Moreover, a single prototype may have multiple instantiations. Managing upvalues thus becomes a little more tricky than traversing stack frames in Pascal. The Lua virtual machine solution is to provide a clean interface to access upvalues via GETUPVAL
and SETUPVAL
, while the management of upvalues is handled by the virtual machine itself.
Line [2] in function b
sets upvalue a (upvalue number 0 in the upvalue table) to a number value of 1
(held in temporary register 0
.) In line [3], the value in upvalue a
is retrieved and placed into register 0
, where the following RETURN
instruction will use it as a return value. The RETURN
in line [5] is unused.
OP_NEWTABLE instruction¶
Syntax¶
NEWTABLE A B C R(A) := {} (size = B,C)
Description¶
Creates a new empty table at register R(A). B and C are the encoded size information for the array part and the hash part of the table, respectively. Appropriate values for B and C are set in order to avoid rehashing when initially populating the table with array values or hash key-value pairs.
Operand B and C are both encoded as a ‘floating point byte’ (so named in lobject.c)
which is eeeeexxx
in binary, where x is the mantissa and e is the exponent.
The actual value is calculated as 1xxx*2^(eeeee-1)
if eeeee
is greater than 0
(a range of 8
to 15*2^30
). If eeeee
is 0
, the actual value is xxx
(a range of 0
to 7
.)
If an empty table is created, both sizes are zero. If a table is created with a number of objects, the code generator counts the number of array elements and the number of hash elements. Then, each size value is rounded up and encoded in B and C using the floating point byte format.
Examples¶
Creating an empty table forces both array and hash sizes to be zero:
f=load('local q = {}')
Leads to:
main <(string):0,0> (2 instructions at 0000022C1877A220)
0+ params, 2 slots, 1 upvalue, 1 local, 0 constants, 0 functions
1 [1] NEWTABLE 0 0 0
2 [1] RETURN 0 1
constants (0) for 0000022C1877A220:
locals (1) for 0000022C1877A220:
0 q 2 3
upvalues (1) for 0000022C1877A220:
0 _ENV 1 0
More examples are provided in the description of OP_SETLIST
instruction.
OP_SETLIST instruction¶
Syntax¶
SETLIST A B C R(A)[(C-1)*FPF+i] := R(A+i), 1 <= i <= B
Description¶
Sets the values for a range of array elements in a table referenced by R(A). Field B is the number of elements to set. Field C encodes the block number of the table to be initialized. The values used to initialize the table are located in registers R(A+1), R(A+2), and so on.
The block size is denoted by FPF. FPF is ‘fields per flush’, defined as LFIELDS_PER_FLUSH
in the source file lopcodes.h, with a value of 50. For example, for array locations 1 to 20,
C will be 1 and B will be 20.
If B is 0, the table is set with a variable number of array elements, from register R(A+1) up to the top of the stack. This happens when the last element in the table constructor is a function call or a vararg operator.
If C is 0, the next instruction is cast as an integer, and used as the C value. This happens only when operand C is unable to encode the block number, i.e. when C > 511, equivalent to an array index greater than 25550.
Examples¶
We’ll start with a simple example:
f=load('local q = {1,2,3,4,5,}')
This generates:
main <(string):0,0> (8 instructions at 0000022C18756E50)
0+ params, 6 slots, 1 upvalue, 1 local, 5 constants, 0 functions
1 [1] NEWTABLE 0 5 0
2 [1] LOADK 1 -1 ; 1
3 [1] LOADK 2 -2 ; 2
4 [1] LOADK 3 -3 ; 3
5 [1] LOADK 4 -4 ; 4
6 [1] LOADK 5 -5 ; 5
7 [1] SETLIST 0 5 1 ; 1
8 [1] RETURN 0 1
constants (5) for 0000022C18756E50:
1 1
2 2
3 3
4 4
5 5
locals (1) for 0000022C18756E50:
0 q 8 9
upvalues (1) for 0000022C18756E50:
0 _ENV 1 0
A table with the reference in register 0 is created in line [1] by NEWTABLE. Since we are creating a table with no hash elements, the array part of the table has a size of 5, while the hash part has a size of 0.
Constants are then loaded into temporary registers 1 to 5 (lines [2] to [6]) before the SETLIST instruction in line [7] assigns each value to consecutive table elements. The start of the block is encoded as 1 in operand C. The starting index is calculated as (1-1)*50+1 or 1. Since B is 5, the range of the array elements to be set becomes 1 to 5, while the objects used to set the array elements will be R(1) through R(5).
Next is a larger table with 55 array elements. This will require two blocks to initialize. Some lines have been removed and ellipsis (…) added to save space:
> f=load('local q = {1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0, \
>> 1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0, \
>> 1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,}')
The generated code is:
main <(string):0,0> (59 instructions at 0000022C187833C0)
0+ params, 51 slots, 1 upvalue, 1 local, 10 constants, 0 functions
1 [1] NEWTABLE 0 30 0
2 [1] LOADK 1 -1 ; 1
3 [1] LOADK 2 -2 ; 2
4 [1] LOADK 3 -3 ; 3
...
51 [3] LOADK 50 -10 ; 0
52 [3] SETLIST 0 50 1 ; 1
53 [3] LOADK 1 -1 ; 1
54 [3] LOADK 2 -2 ; 2
55 [3] LOADK 3 -3 ; 3
56 [3] LOADK 4 -4 ; 4
57 [3] LOADK 5 -5 ; 5
58 [3] SETLIST 0 5 2 ; 2
59 [3] RETURN 0 1
constants (10) for 0000022C187833C0:
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
10 0
locals (1) for 0000022C187833C0:
0 q 59 60
upvalues (1) for 0000022C187833C0:
0 _ENV 1 0
Since FPF is 50, the array will be initialized in two blocks. The first block is
for index 1 to 50, while the second block is for index 51 to 55. Each array block to be
initialized requires one SETLIST
instruction. On line [1], NEWTABLE
has a field
B value of 30, or 00011110 in binary. From the description of NEWTABLE
, xxx
is 1102
,
while eeeee
is 112
. Thus, the size of the array portion of the table is (1110)*2^(11-1)
or (14*2^2)
or 56
.
Lines [2] to [51] sets the values used to initialize the first block. On line [52],
SETLIST
has a B value of 50 and a C value of 1. So the block is from 1 to 50.
Source registers are from R(1) to R(50).
Lines [53] to [57] sets the values used to initialize the second block. On line [58],
SETLIST
has a B value of 5 and a C value of 2. So the block is from 51 to 55.
The start of the block is calculated as (2-1)*50+1
or 51
. Source registers are
from R(1) to R(5).
Here is a table with hashed elements:
> f=load('local q = {a=1,b=2,c=3,d=4,e=5,f=6,g=7,h=8,}')
This results in:
main <(string):0,0> (10 instructions at 0000022C18783D20)
0+ params, 2 slots, 1 upvalue, 1 local, 16 constants, 0 functions
1 [1] NEWTABLE 0 0 8
2 [1] SETTABLE 0 -1 -2 ; "a" 1
3 [1] SETTABLE 0 -3 -4 ; "b" 2
4 [1] SETTABLE 0 -5 -6 ; "c" 3
5 [1] SETTABLE 0 -7 -8 ; "d" 4
6 [1] SETTABLE 0 -9 -10 ; "e" 5
7 [1] SETTABLE 0 -11 -12 ; "f" 6
8 [1] SETTABLE 0 -13 -14 ; "g" 7
9 [1] SETTABLE 0 -15 -16 ; "h" 8
10 [1] RETURN 0 1
constants (16) for 0000022C18783D20:
1 "a"
2 1
3 "b"
4 2
5 "c"
6 3
7 "d"
8 4
9 "e"
10 5
11 "f"
12 6
13 "g"
14 7
15 "h"
16 8
locals (1) for 0000022C18783D20:
0 q 10 11
upvalues (1) for 0000022C18783D20:
0 _ENV 1 0
In line [1], NEWTABLE
is executed with an array part size of 0 and a hash part size of 8.
On lines [2] to line [9], key-value pairs are set using SETTABLE
. The SETLIST
instruction
is only for initializing array elements. Using SETTABLE
to initialize the key-value pairs of
a table in the above example is quite efficient as it can reference the constant pool directly.
If there are both array elements and hash elements in a table constructor, both SETTABLE
and SETLIST
will be used to initialize the table after the initial NEWTABLE
. In addition,
if the last element of the table constructor is a function call or a vararg operator, then the
B operand of SETLIST
will be 0, to allow objects from R(A+1) up to the top of the stack
to be initialized as array elements of the table.
> f=load('return {1,2,3,a=1,b=2,c=3,foo()}')
Leads to:
main <(string):0,0> (12 instructions at 0000022C18788430)
0+ params, 5 slots, 1 upvalue, 0 locals, 7 constants, 0 functions
1 [1] NEWTABLE 0 3 3
2 [1] LOADK 1 -1 ; 1
3 [1] LOADK 2 -2 ; 2
4 [1] LOADK 3 -3 ; 3
5 [1] SETTABLE 0 -4 -1 ; "a" 1
6 [1] SETTABLE 0 -5 -2 ; "b" 2
7 [1] SETTABLE 0 -6 -3 ; "c" 3
8 [1] GETTABUP 4 0 -7 ; _ENV "foo"
9 [1] CALL 4 1 0
10 [1] SETLIST 0 0 1 ; 1
11 [1] RETURN 0 2
12 [1] RETURN 0 1
constants (7) for 0000022C18788430:
1 1
2 2
3 3
4 "a"
5 "b"
6 "c"
7 "foo"
locals (0) for 0000022C18788430:
upvalues (1) for 0000022C18788430:
0 _ENV 1 0
In the above example, the table is first created in line [1] with its reference in register 0, and it has both array and hash elements to be set. The size of the array part is 3 while the size of the hash part is also 3.
Lines [2]–[4] loads the values for the first 3 array elements. Lines [5]–[7] set
the 3 key-value pairs for the hash part of the table. In lines [8] and [9],
the call to function foo
is made, and then in line [10], the SETLIST
instruction sets
the first 3 array elements (in registers 1 to 3) plus whatever additional results
returned by the foo
function call (from register 4 onwards). This is accomplished by
setting operand B in SETLIST
to 0. For the first block, operand C is 1 as usual.
If no results are returned by the function, the top of stack is at register 3
and only the 3 constant array elements in the table are set.
Finally:
> f=load('local a; return {a(), a(), a()}')
This gives:
main <(string):0,0> (11 instructions at 0000022C18787AD0)
0+ params, 5 slots, 1 upvalue, 1 local, 0 constants, 0 functions
1 [1] LOADNIL 0 0
2 [1] NEWTABLE 1 2 0
3 [1] MOVE 2 0
4 [1] CALL 2 1 2
5 [1] MOVE 3 0
6 [1] CALL 3 1 2
7 [1] MOVE 4 0
8 [1] CALL 4 1 0
9 [1] SETLIST 1 0 1 ; 1
10 [1] RETURN 1 2
11 [1] RETURN 0 1
constants (0) for 0000022C18787AD0:
locals (1) for 0000022C18787AD0:
0 a 2 12
upvalues (1) for 0000022C18787AD0:
0 _ENV 1 0
Note that only the last function call in a table constructor retains all results.
Other function calls in the table constructor keep only one result. This is shown in the
above example. For vararg operators in table constructors, please see the discussion for the
VARARG
instruction for an example.
OP_GETTABLE and OP_SETTABLE instructions¶
Syntax¶
GETTABLE A B C R(A) := R(B)[RK(C)]
SETTABLE A B C R(A)[RK(B)] := RK(C)
Description¶
OP_GETTABLE
copies the value from a table element into register R(A).
The table is referenced by register R(B), while the index to the table is given
by RK(C), which may be the value of register R(C) or a constant number.
OP_SETTABLE
copies the value from register R(C) or a constant into a table element.
The table is referenced by register R(A), while the index to the table is given by
RK(B), which may be the value of register R(B) or a constant number.
All 3 operand fields are used, and some of the operands can be constants. A constant is specified by setting the MSB of the operand to 1. If RK(C) need to refer to constant 1, the encoded value will be (256 | 1) or 257, where 256 is the value of bit 8 of the operand. Allowing constants to be used directly reduces considerably the need for temporary registers.
Examples¶
f=load('local p = {}; p[1] = "foo"; return p["bar"]')
This compiles to:
main <(string):0,0> (5 instructions at 000001FA06FCC3F0)
0+ params, 2 slots, 1 upvalue, 1 local, 3 constants, 0 functions
1 [1] NEWTABLE 0 0 0
2 [1] SETTABLE 0 -1 -2 ; 1 "foo"
3 [1] GETTABLE 1 0 -3 ; "bar"
4 [1] RETURN 1 2
5 [1] RETURN 0 1
constants (3) for 000001FA06FCC3F0:
1 1
2 "foo"
3 "bar"
locals (1) for 000001FA06FCC3F0:
0 p 2 6
upvalues (1) for 000001FA06FCC3F0:
0 _ENV 1 0
In line [1], a new empty table is created and the reference placed in local p (register 0).
Creating and populating new tables is discussed in detail elsewhere.
Table index 1 is set to ‘foo’ in line [2] by the SETTABLE
instruction.
The R(A) value of 0 points to the new table that was defined in line [1]. In line [3], the value of the table element indexed by the string ‘bar’ is copied into temporary register 1, which is then used by RETURN as a return value.
OP_SELF instruction¶
Syntax¶
SELF A B C R(A+1) := R(B); R(A) := R(B)[RK(C)]
Description¶
For object-oriented programming using tables. Retrieves a function reference from a table element and places it in register R(A), then a reference to the table itself is placed in the next register, R(A+1). This instruction saves some messy manipulation when setting up a method call.
R(B) is the register holding the reference to the table with the method. The method function itself is found using the table index RK(C), which may be the value of register R(C) or a constant number.
Examples¶
A SELF
instruction saves an extra instruction and speeds up the calling of
methods in object oriented programming. It is only generated for method calls
that use the colon syntax. In the following example:
f=load('foo:bar("baz")')
We can see SELF
being generated:
main <(string):0,0> (5 instructions at 000001FA06FA7830)
0+ params, 3 slots, 1 upvalue, 0 locals, 3 constants, 0 functions
1 [1] GETTABUP 0 0 -1 ; _ENV "foo"
2 [1] SELF 0 0 -2 ; "bar"
3 [1] LOADK 2 -3 ; "baz"
4 [1] CALL 0 3 1
5 [1] RETURN 0 1
constants (3) for 000001FA06FA7830:
1 "foo"
2 "bar"
3 "baz"
locals (0) for 000001FA06FA7830:
upvalues (1) for 000001FA06FA7830:
0 _ENV 1 0
The method call is equivalent to: foo.bar(foo, "baz")
, except that the global foo
is only looked up once. This is significant if metamethods have been set. The SELF
in
line [2] is equivalent to a GETTABLE
lookup (the table is in register 0 and the
index is constant 1) and a MOVE
(copying the table reference from register 0 to
register 1.)
Without SELF
, a GETTABLE
will write its lookup result to register 0 (which the
code generator will normally do) and the table reference will be overwritten before a MOVE
can be done. Using SELF
saves roughly one instruction and one temporary register slot.
After setting up the method call using SELF
, the call is made with the usual CALL
instruction in line [4], with two parameters. The equivalent code for a method lookup is
compiled in the following manner:
f=load('foo.bar(foo, "baz")')
And generated code:
main <(string):0,0> (6 instructions at 000001FA06FA6960)
0+ params, 3 slots, 1 upvalue, 0 locals, 3 constants, 0 functions
1 [1] GETTABUP 0 0 -1 ; _ENV "foo"
2 [1] GETTABLE 0 0 -2 ; "bar"
3 [1] GETTABUP 1 0 -1 ; _ENV "foo"
4 [1] LOADK 2 -3 ; "baz"
5 [1] CALL 0 3 1
6 [1] RETURN 0 1
constants (3) for 000001FA06FA6960:
1 "foo"
2 "bar"
3 "baz"
locals (0) for 000001FA06FA6960:
upvalues (1) for 000001FA06FA6960:
0 _ENV 1 0
The alternative form of a method call is one instruction longer, and the user
must take note of any metamethods that may affect the call. The SELF
in the previous example
replaces the GETTABLE
on line [2] and the GETTABUP
on line [3]. If foo
is a local variable,
then the equivalent code is a GETTABLE
and a MOVE
.
OP_GETTABUP and OP_SETTABUP instructions¶
Syntax¶
GETTABUP A B C R(A) := UpValue[B][RK(C)]
SETTABUP A B C UpValue[A][RK(B)] := RK(C)
Description¶
OP_GETTABUP
and OP_SETTABUP
instructions are similar to the
OP_GETTABLE
and OP_SETTABLE
instructions except that the table
is referenced as an upvalue. These instructions are used to access global
variables, which since Lua 5.2 are accessed via the upvalue named _ENV
.
Examples¶
f=load('a = 40; local b = a')
Results in:
main <(string):0,0> (3 instructions at 0000028D955FEBF0)
0+ params, 2 slots, 1 upvalue, 1 local, 2 constants, 0 functions
1 [1] SETTABUP 0 -1 -2 ; _ENV "a" 40
2 [1] GETTABUP 0 0 -1 ; _ENV "a"
3 [1] RETURN 0 1
constants (2) for 0000028D955FEBF0:
1 "a"
2 40
locals (1) for 0000028D955FEBF0:
0 b 3 4
upvalues (1) for 0000028D955FEBF0:
0 _ENV 1 0
From the example, we can see that ‘b’ is the name of the local variable while ‘a’ is the name of the global variable.
Line [1] assigns the number 40 to global ‘a’. Line [2] assigns the value in global ‘a’ to the register 0 which is the local ‘b’.
OP_CONCAT instruction¶
Syntax¶
CONCAT A B C R(A) := R(B).. ... ..R(C)
Description¶
Performs concatenation of two or more strings. In a Lua source, this is equivalent to one or more concatenation operators (‘..’) between two or more expressions. The source registers must be consecutive, and C must always be greater than B. The result is placed in R(A).
Examples¶
CONCAT
accepts a range of registers. Doing more than one string concatenation
at a time is faster and more efficient than doing them separately:
f=load('local x,y = "foo","bar"; return x..y..x..y')
Generates:
main <(string):0,0> (9 instructions at 0000028D9560B290)
0+ params, 6 slots, 1 upvalue, 2 locals, 2 constants, 0 functions
1 [1] LOADK 0 -1 ; "foo"
2 [1] LOADK 1 -2 ; "bar"
3 [1] MOVE 2 0
4 [1] MOVE 3 1
5 [1] MOVE 4 0
6 [1] MOVE 5 1
7 [1] CONCAT 2 2 5
8 [1] RETURN 2 2
9 [1] RETURN 0 1
constants (2) for 0000028D9560B290:
1 "foo"
2 "bar"
locals (2) for 0000028D9560B290:
0 x 3 10
1 y 3 10
upvalues (1) for 0000028D9560B290:
0 _ENV 1 0
In this example, strings are moved into place first (lines [3] to [6]) in
the concatenation order before a single CONCAT
instruction is executed
in line [7]. The result is left in temporary local 2, which is then used as
a return value by the RETURN
instruction on line [8].
f=load('local a = "foo".."bar".."baz"')
Compiles to:
main <(string):0,0> (5 instructions at 0000028D9560EE40)
0+ params, 3 slots, 1 upvalue, 1 local, 3 constants, 0 functions
1 [1] LOADK 0 -1 ; "foo"
2 [1] LOADK 1 -2 ; "bar"
3 [1] LOADK 2 -3 ; "baz"
4 [1] CONCAT 0 0 2
5 [1] RETURN 0 1
constants (3) for 0000028D9560EE40:
1 "foo"
2 "bar"
3 "baz"
locals (1) for 0000028D9560EE40:
0 a 5 6
upvalues (1) for 0000028D9560EE40:
0 _ENV 1 0
In the second example, three strings are concatenated together. Note that
there is no string constant folding. Lines [1] through [3] loads the three
constants in the correct order for concatenation; the CONCAT
on line [4]
performs the concatenation itself and assigns the result to local ‘a’.
OP_LEN instruction¶
Syntax¶
LEN A B R(A) := length of R(B)
Description¶
Returns the length of the object in R(B). For strings, the string length is returned, while for tables, the table size (as defined in Lua) is returned. For other objects, the metamethod is called. The result, which is a number, is placed in R(A).
Examples¶
The LEN
operation implements the # operator. If # operates on a constant,
then the constant is loaded in advance using LOADK
. The LEN
instruction
is currently not optimized away using compile time evaluation, even if it is
operating on a constant string or table:
f=load('local a,b; a = #b; a= #"foo"')
Results in:
main <(string):0,0> (5 instructions at 000001DC21778C60)
0+ params, 3 slots, 1 upvalue, 2 locals, 1 constant, 0 functions
1 [1] LOADNIL 0 1
2 [1] LEN 0 1
3 [1] LOADK 2 -1 ; "foo"
4 [1] LEN 0 2
5 [1] RETURN 0 1
constants (1) for 000001DC21778C60:
1 "foo"
locals (2) for 000001DC21778C60:
0 a 2 6
1 b 2 6
upvalues (1) for 000001DC21778C60:
0 _ENV 1 0
In the above example, LEN
operates on local b in line [2], leaving the result in
local a. Since LEN
cannot operate directly on constants, line [3] first loads the
constant “foo” into a temporary local, and only then LEN
is executed.
OP_MOVE instruction¶
Syntax¶
MOVE A B R(A) := R(B)
Description¶
Copies the value of register R(B) into register R(A). If R(B) holds a table,
function or userdata, then the reference to that object is copied. MOVE
is often used
for moving values into place for the next operation.
Examples¶
The most straightforward use of MOVE is for assigning a local to another local:
f=load('local a,b = 10; b = a')
Produces:
main <(string):0,0> (4 instructions at 000001DC217566D0)
0+ params, 2 slots, 1 upvalue, 2 locals, 1 constant, 0 functions
1 [1] LOADK 0 -1 ; 10
2 [1] LOADNIL 1 0
3 [1] MOVE 1 0
4 [1] RETURN 0 1
constants (1) for 000001DC217566D0:
1 10
locals (2) for 000001DC217566D0:
0 a 3 5
1 b 3 5
upvalues (1) for 000001DC217566D0:
0 _ENV 1 0
You won’t see MOVE
instructions used in arithmetic expressions
because they are not needed by arithmetic operators. All arithmetic operators are
in 2- or 3-operand style: the entire local stack frame is already visible
to operands R(A), R(B) and R(C) so there is no need for any extra
MOVE
instructions.
Other places where you will see MOVE
are:
When moving parameters into place for a function call.
When moving values into place for certain instructions where stack order is important, e.g.
GETTABLE
,SETTABLE
andCONCAT
.When copying return values into locals after a function call.
OP_LOADNIL instruction¶
Syntax¶
LOADNIL A B R(A), R(A+1), ..., R(A+B) := nil
Description¶
Sets a range of registers from R(A) to R(B) to nil. If a single register is to
be assigned to, then R(A) = R(B). When two or more consecutive locals need to
be assigned nil values, only a single LOADNIL
is needed.
Examples¶
LOADNIL
uses the operands A and B to mean a range of register locations.
The example for MOVE
earlier shows LOADNIL
used to set a single register to nil
.
f=load('local a,b,c,d,e = nil,nil,0')
Generates:
main <(string):0,0> (4 instructions at 000001DC21780390)
0+ params, 5 slots, 1 upvalue, 5 locals, 1 constant, 0 functions
1 [1] LOADNIL 0 1
2 [1] LOADK 2 -1 ; 0
3 [1] LOADNIL 3 1
4 [1] RETURN 0 1
constants (1) for 000001DC21780390:
1 0
locals (5) for 000001DC21780390:
0 a 4 5
1 b 4 5
2 c 4 5
3 d 4 5
4 e 4 5
upvalues (1) for 000001DC21780390:
0 _ENV 1 0
Line [1] nils locals a and b. Local c is explicitly initialized with the value 0. Line [3] nils d and e.
OP_LOADK instruction¶
Syntax¶
LOADK A Bx R(A) := Kst(Bx)
Description¶
Loads constant number Bx into register R(A). Constants are usually numbers or strings. Each function prototype has its own constant list, or pool.
Examples¶
LOADK
loads a constant from the constant list into a register or local.
Constants are indexed starting from 0. Some instructions, such as arithmetic
instructions, can use the constant list without needing a LOADK
.
Constants are pooled in the list, duplicates are eliminated. The list can
hold nils, booleans, numbers or strings.
f=load('local a,b,c,d = 3,"foo",3,"foo"')
Leads to:
main <(string):0,0> (5 instructions at 000001DC21780B50)
0+ params, 4 slots, 1 upvalue, 4 locals, 2 constants, 0 functions
1 [1] LOADK 0 -1 ; 3
2 [1] LOADK 1 -2 ; "foo"
3 [1] LOADK 2 -1 ; 3
4 [1] LOADK 3 -2 ; "foo"
5 [1] RETURN 0 1
constants (2) for 000001DC21780B50:
1 3
2 "foo"
locals (4) for 000001DC21780B50:
0 a 5 6
1 b 5 6
2 c 5 6
3 d 5 6
upvalues (1) for 000001DC21780B50:
0 _ENV 1 0
The constant 3 and the constant “foo” are both written twice in the source snippet, but in the constant list, each constant has a single location.
Binary operators¶
Lua 5.3 implements a bunch of binary operators for arithmetic and bitwise manipulation of variables. These insructions have a common form.
Syntax¶
ADD A B C R(A) := RK(B) + RK(C)
SUB A B C R(A) := RK(B) - RK(C)
MUL A B C R(A) := RK(B) * RK(C)
MOD A B C R(A) := RK(B) % RK(C)
POW A B C R(A) := RK(B) ^ RK(C)
DIV A B C R(A) := RK(B) / RK(C)
IDIV A B C R(A) := RK(B) // RK(C)
BAND A B C R(A) := RK(B) & RK(C)
BOR A B C R(A) := RK(B) | RK(C)
BXOR A B C R(A) := RK(B) ~ RK(C)
SHL A B C R(A) := RK(B) << RK(C)
SHR A B C R(A) := RK(B) >> RK(C)
Description¶
Binary operators (arithmetic operators and bitwise operators with two inputs.) The result of the operation between RK(B) and RK(C) is placed into R(A). These instructions are in the classic 3-register style.
RK(B) and RK(C) may be either registers or constants in the constant pool.
Opcode |
Description |
---|---|
ADD |
Addition operator |
SUB |
Subtraction operator |
MUL |
Multiplication operator |
MOD |
Modulus (remainder) operator |
POW |
Exponentation operator |
DIV |
Division operator |
IDIV |
Integer division operator |
BAND |
Bit-wise AND operator |
BOR |
Bit-wise OR operator |
BXOR |
Bit-wise Exclusive OR operator |
SHL |
Shift bits left |
SHR |
Shift bits right |
The source operands, RK(B) and RK(C), may be constants. If a constant is out of range of field B or field C, then the constant will be loaded into a temporary register in advance.
Examples¶
f=load('local a,b = 2,4; a = a + 4 * b - a / 2 ^ b % 3')
Generates:
main <(string):0,0> (9 instructions at 000001DC21781DD0)
0+ params, 4 slots, 1 upvalue, 2 locals, 3 constants, 0 functions
1 [1] LOADK 0 -1 ; 2
2 [1] LOADK 1 -2 ; 4
3 [1] MUL 2 -2 1 ; 4 - (loc2 = 4 * b)
4 [1] ADD 2 0 2 (loc2 = A + loc2)
5 [1] POW 3 -1 1 ; 2 - (loc3 = 2 ^ b)
6 [1] DIV 3 0 3 (loc3 = a / loc3)
7 [1] MOD 3 3 -3 (loc3 = loc3 % 3)
8 [1] SUB 0 2 3 (a = loc2 – loc3)
9 [1] RETURN 0 1
constants (3) for 000001DC21781DD0:
1 2
2 4
3 3
locals (2) for 000001DC21781DD0:
0 a 3 10
1 b 3 10
upvalues (1) for 000001DC21781DD0:
0 _ENV 1 0
In the disassembly shown above, parts of the expression is shown as additional
comments in parentheses. Each arithmetic operator translates into a single instruction.
This also means that while the statement count = count + 1
is verbose, it translates
into a single instruction if count is a local. If count is a global, then two
extra instructions are required to read and write to the global (GETTABUP
and SETTABUP
),
since arithmetic operations can only be done on registers (locals) only.
The Lua parser and code generator can perform limited constant expression folding
or evaluation. Constant folding only works for binary arithmetic operators and the unary
minus operator (UNM
, which will be covered next.) There is no equivalent
optimization for relational, boolean or string operators.
The optimization rule is simple: If both terms of a subexpression are numbers, the subexpression will be evaluated at compile time. However, there are exceptions. One, the code generator will not attempt to divide a number by 0 for DIV and MOD, and two, if the result is evaluated as a NaN (Not a Number) then the optimization will not be performed.
Also, constant folding is not done if one term is in the form of a string that need to be coerced. In addition, expression terms are not rearranged, so not all optimization opportunities can be recognized by the code generator. This is intentional; the Lua code generator is not meant to perform heavy duty optimizations, as Lua is a lightweight language. Here are a few examples to illustrate how it works (additional comments in parentheses):
f=load('local a = 4 + 7 + b; a = b + 4 * 7; a = b + 4 + 7')
Generates:
main <(string):0,0> (8 instructions at 000001DC21781650)
0+ params, 2 slots, 1 upvalue, 1 local, 5 constants, 0 functions
1 [1] GETTABUP 0 0 -1 ; _ENV "b"
2 [1] ADD 0 -2 0 ; 11 - (a = 11 + b)
3 [1] GETTABUP 1 0 -1 ; _ENV "b"
4 [1] ADD 0 1 -3 ; - 28 (a = b + 28)
5 [1] GETTABUP 1 0 -1 ; _ENV "b"
6 [1] ADD 1 1 -4 ; - 4 (loc1 = b + 4)
7 [1] ADD 0 1 -5 ; - 7 (a = loc1 + 7)
8 [1] RETURN 0 1
constants (5) for 000001DC21781650:
1 "b"
2 11
3 28
4 4
5 7
locals (1) for 000001DC21781650:
0 a 3 9
upvalues (1) for 000001DC21781650:
0 _ENV 1 0
For the first assignment statement, 4+7
is evaluated, thus 11 is added to b in line [2].
Next, in line [3] and [4], b
and 28
are added together and assigned to a because multiplication
has a higher precedence and 4*7
is evaluated first. Finally, on lines [5] to [7],
there are two addition operations. Since addition is left-associative, code is generated for
b+4
first, and only after that, 7
is added. So in the third example,
Lua performs no optimization. This can be fixed using parentheses to explicitly change the precedence
of a subexpression:
f=load('local a = b + (4 + 7)')
And this leads to:
main <(string):0,0> (3 instructions at 000001DC21781EC0)
0+ params, 2 slots, 1 upvalue, 1 local, 2 constants, 0 functions
1 [1] GETTABUP 0 0 -1 ; _ENV "b"
2 [1] ADD 0 0 -2 ; - 11
3 [1] RETURN 0 1
constants (2) for 000001DC21781EC0:
1 "b"
2 11
locals (1) for 000001DC21781EC0:
0 a 3 4
upvalues (1) for 000001DC21781EC0:
0 _ENV 1 0
Now, the 4+7
subexpression can be evaluated at compile time. If the
statement is written as:
local a = 7 + (4 + 7)
the code generator will generate a single LOADK
instruction; Lua first evaluates
4+7
, then 7
is added, giving a total of 18
. The arithmetic expression is completely
evaluated in this case, thus no arithmetic instructions are generated.
In order to make full use of constant folding in Lua, the user just need to remember the usual order of evaluation of an expression’s elements and apply parentheses where necessary. The following are two expressions which will not be evaluated at compile time:
f=load('local a = 1 / 0; local b = 1 + "1"')
This produces:
main <(string):0,0> (3 instructions at 000001DC21781380)
0+ params, 2 slots, 1 upvalue, 2 locals, 3 constants, 0 functions
1 [1] DIV 0 -2 -1 ; 1 0
2 [1] ADD 1 -2 -3 ; 1 "1"
3 [1] RETURN 0 1
constants (3) for 000001DC21781380:
1 0
2 1
3 "1"
locals (2) for 000001DC21781380:
0 a 2 4
1 b 3 4
upvalues (1) for 000001DC21781380:
0 _ENV 1 0
The first is due to a divide-by-0, while the second is due to a string constant that needs to be coerced into a number. In both cases, constant folding is not performed, so the arithmetic instructions needed to perform the operations at run time are generated instead.
TODO - examples of bitwise operators.
Unary operators¶
Lua 5.3 implements following unary operators in addition to OP_LEN
.
Syntax¶
UNM A B R(A) := -R(B)
BNOT A B R(A) := ~R(B)
NOT A B R(A) := not R(B)
Description¶
The unary operators perform an operation on R(B) and store the result in R(A).
Opcode |
Description |
---|---|
UNM |
Unary minus |
BNOT |
Bit-wise NOT operator |
NOT |
Logical NOT operator |
Examples¶
f=load('local p,q = 10,false; q,p = -p,not q')
Results in:
main <(string):0,0> (6 instructions at 000001DC21781290)
0+ params, 3 slots, 1 upvalue, 2 locals, 1 constant, 0 functions
1 [1] LOADK 0 -1 ; 10
2 [1] LOADBOOL 1 0 0
3 [1] UNM 2 0
4 [1] NOT 0 1
5 [1] MOVE 1 2
6 [1] RETURN 0 1
constants (1) for 000001DC21781290:
1 10
locals (2) for 000001DC21781290:
0 p 3 7
1 q 3 7
upvalues (1) for 000001DC21781290:
0 _ENV 1 0
As UNM
and NOT
do not accept a constant as a source operand, making the
LOADK
on line [1] and the LOADBOOL
on line [2] necessary. When an unary minus
is applied to a constant number, the unary minus is optimized away. Similarly, when a
not is applied to true or false, the logical operation is optimized away.
In addition to this, constant folding is performed for unary minus, if the term is a number. So, the expression in the following is completely evaluated at compile time:
f=load('local a = - (7 / 4)')
Results in:
main <(string):0,0> (2 instructions at 000001DC217810B0)
0+ params, 2 slots, 1 upvalue, 1 local, 1 constant, 0 functions
1 [1] LOADK 0 -1 ; -1.75
2 [1] RETURN 0 1
constants (1) for 000001DC217810B0:
1 -1.75
locals (1) for 000001DC217810B0:
0 a 2 3
upvalues (1) for 000001DC217810B0:
0 _ENV 1 0
Constant folding is performed on 7/4
first. Then, since the unary minus
operator is applied to the constant 1.75
, constant folding can be performed
again, and the code generated becomes a simple LOADK
(on line [1]).
TODO - example of BNOT
.