前言
回顾一下 Ruby 解释器处理流程
词法分析
语法分析
AST 生成
虚拟机指令生成
虚拟机指令有两种存储结构:
链式结构,通过 LINK_ELEMENT 链表节点
数组(线性)结构
解释器会先遍历 AST 生成链式结构存储的指令列表,但是虚拟机并不会直接解释执行该列表,而是进一步将指令列表“序列化”成字节数组,PC(指令指针)寄存器可以索引指令数组(取指,分支跳转)
本文简要介绍 Ruby 2.x 源代码中将链式结构转化成数组结构的源代码,从中可以进一步了解 Ruby 虚拟机相关数据结构的设计与实现
由 AST 生成虚拟机指令(简要回顾)
由此前的系列文章可以,rb_iseq_compile_node 函数负责遍历 AST 生成虚拟机指令:
// compile.c
VALUE rb_iseq_compile_node(rb_iseq_t *iseq, NODE *node) {
DECL_ANCHOR(ret);
INIT_ANCHOR(ret);
...
return iseq_setup(iseq, ret);
}
DECL_ANCHOR 和 INIT_ANCHOR 声明了一个双向链表用于收集在遍历 AST 根节点 node 过程中生成的虚拟机指令,到达函数末尾时,ret 指向指令链表的头部,然后 iseq_setup 函数登场,对指令链表进行优化,序列化等操作
iseq_setup
为了便于分析,去掉了 iseq_setup 函数中 compile_debug 开关控制的调试输出以及一些编译选项(比如 stack caching, instructions unification .etc)
// compile.c
static int iseq_setup(rb_iseq_t *iseq, LINK_ANCHOR *const anchor) {
iseq_optimize(idea, anchor);
...
if (!iseq_set_sequence(iseq, anchor)) {
return COMPILE_NG;
}
...
return COMPILE_OK;
}
iseq_set_sequence 将 anchor 转化为 instruction sequence 存储在 iseq 中
iseq_set_sequence
iseq_set_sequence 函数有一段注释表明了该函数的用途
// compile.c
/**
ruby insn object list -> raw instruction sequence
**/
static int iseq_set_sequence(rb_iseq_t *iseq, LINK_ANCHOR *const anchor) {
struct iseq_line_info_entry *line_info_table;
unsigned int last_line = 0;
LINK_ELEMENT *list;
VALUE *generated_iseq;
int insn_num, code_index, line_info_index, sp, stack_max = 0, line = 0;
}
函数开头的局部变量定义,信息量还是很大的
line_info_table,行号表
last_line
list,遍历 anchor 的中间变量
generated_iseq,VALUE 数组,用于存放序列化后的指令
insn_num,anchor 中包含的指令个数
code_index
line_info_index
sp
stack_max,执行指令过程中堆栈的最大值
line
iseq_set_sequence 会遍历两次 anchor(指令链表),第一次用于搜集指令个数 insn_num,序列后的指令数组的大小 generated_iseq 以及确定 label(标签)的跳转位置。第二次遍历用于生成指令序列
第一次遍历指令链表
第一次遍历指令链表的主体代码框架如下:
// compile.c
static int iseq_set_sequence(rb_iseq_t *iseq, LINK_ANCHOR *const anchor) {
...
/* fix label position */
list = FIRST_ELEMENT(anchor);
insn_num = code_index = 0;
while (list) {
switch(list->type) {
case ISEQ_ELEMENT_INSN:
...
break;
case ISEQ_ELEMENT_LABEL:
...
break;
case ISEQ_ELEMENT_ADJUST:
...
break;
default:
...
return COMPILE_NG
}
list = list->next;
}
}
ISEQ_ELEMENT_INSN
// iseq_set_sequence @ compile.c
case ISEQ_ELEMENT_INSN: {
INSN *iobj = (INSN*) list;
line = iobj->line_no;
// 调用 insn_data_length 获取指令占用的字节数并累加到 code_index
code_index += insn_data_length(jobs);
// 指令个数加 1
insn_num++;
break;
}
ISEQ_ELEMENT_LABEL
ISEQ_ELEMENT_LABEL 类型的指令是一条伪指令,并不生成具体的指令序列,只是为了确定分支跳转的指令偏移量。在创建 label 的时候 position,set 属性并未赋值,要等到此处才能确定它们的真实值
// iseq_set_sequence @ compile.c
case ISEQ_ELEMENT_LABEL: {
LABEL *lobj = (LABEL*) list;
lobj->position = code_index;
lobj->set = TRUE;
break;
}
ISEQ_ELEMENT_NONE
ISEQ_ELEMENT_ADJUST
第二次遍历指令链表
准备工作
使用第一次遍历得到的 code_index 和 insn_num 为 generated_iseq 和 line_info_table 分配空间
// iseq_set_sequence @ compile.c
generated_iseq = ALLOC_N(VALUE, code_index);
line_info_table = ALLOC_N(struct iseq_line_info_entry, insn_num);
...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。