求值中的关系运算
上一节介绍了关系运算在 条件判断 中的应用。这一节介绍另外一个应用场景,即在 求值 时的处理。
跟逻辑运算类似,处理求值中的关系判断,也只需要把上一节中解析得到的ExpDesc::Compare
discharge到栈上。如下图所示,上一节完成了(a)和(b)部分,本节在(a)的基础上,实现(c)部分。
+------------+
/--->| (b)条件判断 |
+---------------+ ExpDesc::Compare | +------------+
| (a)处理关系运算 |--------------------->+
+---------------+ | +---------+
\--->| (c)求值 |
+---------+
逻辑运算的求值,是把两条跳转链表中的TestAndJump
和TestOrJump
字节码,分别替换为TestAndSetJump
和TestOrSetJump
。对于关系运算,虽然也可以这么做,但是把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
的求值都显得简单了。