Relational Operations in Evaluation

The previous section introduced the relational operations in conditional judgment. This section introduces another scenario, that is, the processing during evaluation.

Similar to logical operations, to process the relationship judgment in the evaluation, we only need to discharge the ExpDesc::Compare parsed in the previous section to the stack. As shown in the figure below, the previous section completed parts (a) and (b), and this section implements part (c) on the basis of (a).

                                                  +------------------------+
+-----------------------+                    /--->| (b) Condition judgment |
| (a) Process           |   ExpDesc::Test   |     +------------------------+
| relational operations |------------------>+
+-----------------------+                   |     +-----------------+
                                             \--->| (c) Evaluation  |
                                                  +-----------------+                                              

The evaluation of the logical operation is to replace the TestAndJump and TestOrJump bytecodes in the two jump lists with TestAndSetJump and TestOrSetJump respectively. For relational operations, although we can also do it like this, it would be too verbose to add a Set version to all 18 bytecodes. Here we refer to the official implementation of Lua. For the following Lua code:

print(123 == 456)

Compile the available bytecode sequence:

luac  -l tt.lua

main <tt.lua:0,0> (9 instructions at 0x6000037fc080)
0+ params, 2 slots, 1 upvalue, 0 locals, 1 constant, 0 functions
    1	[1]	VARARGPREP	0
    2	[1]	GETTABUP 	0 0 0	; _ENV "print"
    3	[1]	LOADI    	1 456
    4	[1]	EQI      	1 123 1
    5	[1]	JMP      	1	; to 7
    6	[1]	LFALSESKIP	1
    7	[1]	LOADTRUE 	1
    8	[1]	CALL     	0 2 1	; 1 in 0 out
    9	[1]	RETURN   	0 1 1	; 0 out

Among them, the 4th and 5th bytecodes are comparison operations. The key lies in the following two bytecodes:

  • The sixth bytecode LFALSESKIP is specially used for the evaluation of relational operations. The function is to set False to the target address and skip the next statement;
  • The seventh bytecode LOADTRUE, the function is to load True to the target address.

These two bytecodes, together with the 4th and 5th bytecodes above, can realize the function of finding Boolean values:

  • If the fourth bytecode comparison result is true, execute the JMP of the fifth, skip the next statement, execute the seventh statement, and set True;
  • If the comparison result of the fourth bytecode is false, then skip the fifth article, and execute the LFALSESKIP of the sixth article, set False and skip the next article.

This is very clever, but also very long-winded. If you follow the previous method of Binary Arithmetic Operation, the above function only needs one bytecode: EQ $dst $a $b. The reason why it is so complicated now is to optimize for relational operations in conditional judgment scenarios, thus hurting performance in evaluation scenarios, after all, the latter appears too little.

Syntax Analysis

The evaluation process is to discharge ExpDesc::Compare onto the stack,

     fn discharge(&mut self, dst: usize, desc: ExpDesc) {
         let code = match desc {
             // omit other types of processing

             // Evaluation of the logical operations introduced earlier
             ExpDesc::Test(condition, true_list, false_list) => {
                 self.discharge(dst, *condition);
                 self.fix_test_set_list(true_list, dst);
                 self.fix_test_set_list(false_list, dst);
                 return;
             }

             // evaluation of relational operations
             ExpDesc::Compare(op, left, right, true_list, false_list) => {
                 // Generate 2 bytecodes for relational operations
                 self.byte_codes.push(op(left as u8, right as u8, false));
                 self.byte_codes.push(ByteCode::Jump(1));

                 // Terminate False jump list, go to `SetFalseSkip` bytecode, evaluate False
                 self.fix_test_list(false_list);
                 self.byte_codes.push(ByteCode::SetFalseSkip(dst as u8));

                 // Terminate True jump list, go to `LoadBool(true)` bytecode, evaluate True
                 self.fix_test_list(true_list);
                 ByteCode::LoadBool(dst as u8, true)
             }
         };
         self.byte_codes.push(code);

In comparison, the evaluation of the logical operation ExpDesc::Test is simple.