求值中的关系运算

上一节介绍了关系运算在 条件判断 中的应用。这一节介绍另外一个应用场景,即在 求值 时的处理。

跟逻辑运算类似,处理求值中的关系判断,也只需要把上一节中解析得到的ExpDesc::Compare discharge到栈上。如下图所示,上一节完成了(a)和(b)部分,本节在(a)的基础上,实现(c)部分。

                                             +------------+
                                        /--->| (b)条件判断 |
+---------------+   ExpDesc::Compare   |     +------------+
| (a)处理关系运算 |--------------------->+
+---------------+                      |     +---------+
                                        \--->| (c)求值  |
                                             +---------+

逻辑运算的求值,是把两条跳转链表中的TestAndJumpTestOrJump字节码,分别替换为TestAndSetJumpTestOrSetJump。对于关系运算,虽然也可以这么做,但是把18条字节码都增加个Set版本就太啰嗦了。我们这里参考Lua官方实现的方式。对于如下Lua代码:

print (123 == 456)

编译可得字节码序列:

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

其中第4、5条字节码为比较运算。关键就在于后面紧跟的两条字节码:

  • 第6条字节码LFALSESKIP,专门用于对关系运算的求值,功能是向目标地址设置False,并跳过下一条语句;
  • 第7条字节码LOADTRUE,功能是加载True到目标地址。

这两条字节码再配合上面第4、5条字节码,就能实现求布尔值的功能:

  • 假如第4条字节码比较结果为真,则执行第5条的JMP,跳过下一条语句,执行第7条语句,设置True;
  • 假如第4条字节码比较结果为假,则跳过第5条,而执行第6条的LFALSESKIP,设置False并跳过下一条。

很巧妙,也很啰嗦。如果按照之前二元数值运算的方式,上面的功能只需要一条字节码:EQ $dst $a $b。之所以现在搞的这么复杂,就是为了对关系运算在 条件判断 场景下进行优化,而牺牲了在 求值 场景下的性能,毕竟后者出现的太少了。

语法分析

求值过程,就是把ExpDesc::Compare discharge到栈上,

    fn discharge(&mut self, dst: usize, desc: ExpDesc) {
        let code = match desc {
            // 省略其他类型的处理

            // 之前介绍的逻辑运算的求值
            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;
            }

            // 关系运算的求值
            ExpDesc::Compare(op, left, right, true_list, false_list) => {
                // 生成关系运算的2条字节码
                self.byte_codes.push(op(left as u8, right as u8, false));
                self.byte_codes.push(ByteCode::Jump(1));

                // 终结False跳转列表,到`SetFalseSkip`字节码,求值False
                self.fix_test_list(false_list);
                self.byte_codes.push(ByteCode::SetFalseSkip(dst as u8));

                // 终结True跳转列表,到`LoadBool(true)`字节码,求值True
                self.fix_test_list(true_list);
                ByteCode::LoadBool(dst as u8, true)
            }
        };
        self.byte_codes.push(code);

相比起来,逻辑运算ExpDesc::Test的求值都显得简单了。