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.

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 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).

Used to implement and and or logical operators, or for testing a single register in a conditional statement.

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 0000020 F274CF1A0 ) 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 0000020 F274CF1A0 : locals ( 3 ) for 0000020 F274CF1A0 : 0 a 2 6 1 b 2 6 2 c 2 6 upvalues ( 1 ) for 0000020 F274CF1A0 : 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 0000020 F274D0A70 ) 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 0000020 F274D0A70 : locals ( 2 ) for 0000020 F274D0A70 : 0 a 2 6 1 b 2 6 upvalues ( 1 ) for 0000020 F274D0A70 : 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 0000020 F274D1AB0 ) 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 0000020 F274D1AB0 : locals ( 3 ) for 0000020 F274D1AB0 : 0 a 2 6 1 b 2 6 2 c 2 6 upvalues ( 1 ) for 0000020 F274D1AB0 : 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 0000020 F274D1010 ) 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 0000020 F274D1010 : locals ( 2 ) for 0000020 F274D1010 : 0 a 2 6 1 b 2 6 upvalues ( 1 ) for 0000020 F274D1010 : 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 0000020 F274D1150 ) 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 0000020 F274D1150 : locals ( 3 ) for 0000020 F274D1150 : 0 a 2 8 1 b 2 8 2 c 2 8 upvalues ( 1 ) for 0000020 F274D1150 : 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 0000020 F274D13D0 ) 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 0000020 F274D13D0 : 1 "Done" locals ( 0 ) for 0000020 F274D13D0 : upvalues ( 1 ) for 0000020 F274D13D0 : 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 0000020 F274D1C90 ) 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 0000020 F274D1C90 : 1 "Found" 2 "Match" locals ( 0 ) for 0000020 F274D1C90 : upvalues ( 1 ) for 0000020 F274D1C90 : 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 0000020 F274D1A10 ) 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 0000020 F274D1A10 : locals ( 3 ) for 0000020 F274D1A10 : 0 a 2 8 1 b 2 8 2 c 2 8 upvalues ( 1 ) for 0000020 F274D1A10 : 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: