Ruby 2.x 源代码学习:解释器概述
前言
本文作为这一系列的开篇,简单介绍了 Ruby 解释器运行轨迹,为后续详细分析 Ruby 源代码提供一个纲领。
之所以选择学习 Ruby 源代码主要是出于:
Ruby 源代码主要使用 C 语言编写,不像 JVM 使用 C++,要看懂 JVM 首先得有相当的 C++ 功底
Ruby 解释器(CRuby)目前还没有实现 JIT,代码流程比较简单,清晰,利于学习一门语言以及虚拟机实现
相比于其它的动态脚本语言(Python),Ruby 的面向对象实现应该是比较纯正的,比较符合 Me 的口味
本系列的灵感来源于《Ruby原理剖析》这本著作,让一个 Java 技术栈的~从业者~有兴趣和勇气向大家分享这一系列^_-
Ruby 2.x 源代码学习:ubuntu 环境 下载,编译,调试 ruby 源代码
所有的伟大,源于一个勇敢的开始,把源代码下载下来,编译,调试/运行!Ruby 2.x 源代码学习:解释器概述
Ruby 2.x 源代码学习:bootstrap
Ruby 2.x 源代码学习:词法分析
Ruby 2.x 源代码学习:语法分析 & 中间代码生成 之 概述
Ruby 2.x 源代码学习:语法分析 & 中间代码生成 之 数据结构
Ruby 2.x 源代码学习:语法分析 & 中间代码生成 之 方法解析
Ruby 2.x 源代码学习:语法分析 & 中间代码生成 之 指令链表序列化
Ruby 2.x 源代码学习:YARV 虚拟机 指令
Ruby 2.x 源代码学习:对象模型
Ruby 2.x 原地吗学习:内存管理 & GC
Ruby 2.x 源代码学习:线程
Ruby 2.x 源代码学习:RubyVM & InstructionSequence
Ruby 2.x 源代码学习:扩展 概述
解释器入口
代码片段省略掉 (万能的!!!)宏定义 和一些无关主干的细节(下同)
# main.c int main(int argc, char **argv) { ruby_init(); ruby_run_node(ruby_options(argc, argv)); }
main 方法很干净简洁,不绕来绕去
初始化(ruby_init)
处理命令行参数,词法分析,语法分析(生成 AST),字节码生成(ruby_options)
解释执行(ruby_run_node)
虚拟机初始化
ruby_init 函数调用栈
ruby_init ruby_setup ruby_init_stack Init_BareVM Init_heap Init_vm_objects rb_call_inits ruby_prog_init GET_VM()->running = 1
从名字能够大概猜出各个函数的功能,这里比较有意思的是 rb_call_inits 函数
它调用 Init_XXX 函数初始化 Ruby 内置的 Class(Array, Hash .etc)
#define CALL(n) {void Init_##n(void); Init_##n();} void rb_call_inits(void) { CALL(Method); // other calls CALL(Array); // other calls }
词法分析,语法分析,中间代码生成
ruby_options 函数调用栈
ruby_options ruby_process_options process_options rb_iseq_new_main rb_iseq_new_with_opt iseq_alloc prepare_iseq_build rb_iseq_compile_node iseq_setup iseq_translate iseq_optimize iseq_insns_unification iseq_set_sequence_stackcaching iseq_set_sequence
以 iseq(instruction sequence)开头的函数基本上都是和 字节码处理相关的,几个重要的函数:
rb_iseq_new_main: 将 ruby 脚本加工成 rb_iseq_struct
iseq_optimize: 字节码优化
iseq_set_sequence: 将双向链表链接的字节码指令结构体编码为数组形式线性存储,方便虚拟机取指令
字节码解释执行
字节码定义
Ruby 源代码包下有个 insns.def 的 文件,在编译 Ruby 的时候会将该文件转化成 vm.inc 文件
下面是 insns.def 文件的一个片段,定义了 getlocal 指令,该指令用于从本地变量表中获取一个本地变量
/** @c variable @e Get local variable (pointed by `idx' and `level'). 'level' indicates the nesting depth from the current block. @j level, idx で指定されたローカル変数の値をスタックに置く。 level はブロックのネストレベルで、何段上かを示す。 */ DEFINE_INSN getlocal (lindex_t idx, rb_num_t level) () (VALUE val) { int i, lev = (int)level; const VALUE *ep = GET_EP(); /* optimized insns generated for level == (0|1) in defs/opt_operand.def */ for (i = 0; i < lev; i++) { ep = GET_PREV_EP(ep); } val = *(ep - idx); }
解释执行
编译 Ruby 的时候可以在 vm_opts.h 头文件中打开 OPT_CALL_THREADED_CODE 开关
ruby_run_node 调用栈
ruby_run_node ruby_exec_node ruby_exec_internal rb_iseq_eval_main vm_exec vm_exec_core
我们直接来看 vm_exec_core 函数,和想象中的差不多,一个 while 循环
vm_exec_core(rb_thread_t *th, VALUE initial) { register rb_control_frame_t *reg_cfp = th->cfp; while (1) { reg_cfp = ((rb_insn_func_t) (*GET_PC()))(th, reg_cfp); if (UNLIKELY(reg_cfp == 0)) { break; } } if (th->retval != Qundef) { VALUE ret = th->retval; th->retval = Qundef; return ret; } else { VALUE err = th->errinfo; th->errinfo = Qnil; return err; } }
rb_control_frame_t 是对函数调用栈的抽象
rb_thread_t 是对线程的抽象
rb_insn_func_t 是 ruby 字节码指令处理函数,每条指令都对应一个处理函数
GET_PC 方法用于获取当前指令指针
字节码处理函数
vm_exec.h 中定义了几个用于申明字节码处理函数的 宏定义
#define LABEL(x) insn_func_##x #define INSN_ENTRY(insn) \ static rb_control_frame_t * \ FUNC_FASTCALL(LABEL(insn))(rb_thread_t *th, rb_control_frame_t *reg_cfp) { #define END_INSN(insn) return reg_cfp;}
结合上文提到的 vm.inc,下面的函数声明
INSN_ENTRY(getlocal)
会被展开成
static rb_control_frame_t* insn_func_getlocal(rb_thread_t *th, rb_control_frame_t *reg_cfp)