生成跳转指令时并不确定最后将跳转到哪里去(因为往往还未解析到目标代码段)。因此这里会用到编译原理中一个叫“回填”的技术。相关的指令:
typedef enum { /*---------------------------------------------------------------------- name args description ------------------------------------------------------------------------*/ // ...... OP_LOADBOOL,/* A B C R(A) := (Bool)B; if (C) pc++ */ // ...... OP_JMP, /* sBx pc+=sBx */ OP_EQ, /* A B C if ((RK(B) == RK(C)) ~= A) then pc++ */ OP_LT, /* A B C if ((RK(B) < RK(C)) ~= A) then pc++ */ OP_LE, /* A B C if ((RK(B) <= RK(C)) ~= A) then pc++ */ OP_TEST, /* A C if not (R(A) <=> C) then pc++ */ OP_TESTSET, /* A B C if (R(B) <=> C) then R(A) := R(B) else pc++ */ // ...... } OpCode;
所有的逻辑跳转类指令无非是这样的形式:
if (cond) goto label1; label2: func2(); goto label_end; label1: func1(); label_end: func _end();
同 一个表达式只有两个跳转链表,一般称为truelist和 falselist 。 在具体的实现中,就是expdesc 结构体中的成员 t和f
if (cond) //生成一个跳转语句,此时 label1 位置未知,因此生成跳转语句的跳转点加入 cond 的 truelist label2: func2(); //生成一个跳转语句,此时 label_end 位置未知,因此生成跳转语句的跳转点加入 cond 的 falselist label1: func1(); label_end: func _end();
回填技术涉及如下两个操作:
1. 将当前生成的未知其目的地址的跳转语句加入到某个空悬链表中
2. 以某个位置的数据,回填上面生成的空悬链表的悬空地址
OP_JMP指令中,sBx:跳转目的地址的偏移量。
A,B,C都是OP_JMP指令,并跳转到同一个目的地址。
A的跳转地址:B的偏移量
B的跳转地址:C的偏移量
C的跳转地址:NO_JUMP(-1)
typedef enum { // ...... OP_JMP, /* sBx pc+=sBx */ // ...... } OpCode;
将一个新的跳转位置加入空悬跳转链表的操作在函数 luaK concat 中:
// l1 : 空悬链表的第一个指令位置 // l2 : 待加入该链表的指令位置 void luaK_concat (FuncState *fs, int *l1, int l2) { if (l2 == NO_JUMP) return; // l2 存储的指令不是一个跳转指令 else if (*l1 == NO_JUMP) // 跳转链表为空,没有空悬的跳转指令在该链表中 *l1 = l2; else { int list = *l1; int next; while ((next = getjump(fs, list)) != NO_JUMP) /* find last element */ list = next; //将最后一个元素的跳转位置设置为 12 fixjump(fs, list, l2); } }