探索 Lua5.2 内部实现:编译系统(2) 跳转的处理

如题所述

跳转是程序指令流程控制的关键手段。在 Lua5.2 中,利用 OP_JMP 指令实现跳转,具体细节在《虚拟机指令》中有详细介绍。跳转主要分为条件跳转和非条件跳转两种类型,后者相对简单,我们先从这里着手。

在 Lua5.2 环境中,使用 goto 和 label 语句执行非条件跳转,这些语句在 lparser.c 中的 gotostat 和 labelstat 函数中进行解析。全局数据 Dyndata 中包含 goto 列表和 label 列表,使用相同的数据结构 Labeldesc 表示。其中,name 标识 label 名称,pc 表示 label 对应的当前函数指令集合位置,nactvar 代表函数有效局部变量数量,用于跳转时关闭 upvalue。

处理 goto 时,通过 gotostat 接受预生成的 OP_JMP 指令位置,首先在 ls->dyd->gt 中创建 Labeldesc 标记未处理的 goto,然后调用 findlabel 查找已定义的 label。若找到,则通过 closegoto 设置跳转位置,并决定关闭局部变量对应的 upvalue;若未找到,跳转位置为 NO_JUMP,等待后续处理。

labelstat 处理 label 时,同样查找已定义的 goto,若找到则修改跳转位置。整个语法分析代码逻辑清晰,易于理解。然而,Lua 对 OP_JMP 指令处理方法较为复杂,包括指令回填机制。在真正生成指令前,无法确定跳转位置,Lua 将指令先生成到函数 proto 的指令列表中,记录位置,待明确时再进行修改。

对于每个 OP_JMP 指令,有 A 和 sBx 两个参数需要回填。在 luaK_jump 函数中,A 初始化为 0,sBx 初始化为 NO_JUMP,并返回指令位置。通过保存此值,可以找到并修改指令。当在关系和逻辑运算过程中生成一系列 OP_JMP 指令时,Lua 使用链表存储这些指令集合,每个节点为 OP_JMP 指令,使用 sBx 指向下一个指令。遍历链表即可完成回填。

回填跳转地址分为两类:已生成指令位置直接修改;当前位置则将待修改指令串接到 FuncState 中的 jpc 链表。在生成下一条指令时,调用 dischargejpc 函数修改链表上的跳转指令,实现延迟回填。这种处理方式优化了跳转操作,避免了重复跳转,简化了指令流程。

跳转处理与逻辑和关系表达式紧密相关,将在后续的表达式部分深入探讨。
温馨提示:答案为网友推荐,仅供参考
相似回答
大家正在搜