本文做爲這一系列的開篇,簡單介紹了 Ruby 解釋器運行軌跡,爲後續詳細分析 Ruby 源代碼提供一個綱領。
之因此選擇學習 Ruby 源代碼主要是出於:node
Ruby 源代碼主要使用 C 語言編寫,不像 JVM 使用 C++,要看懂 JVM 首先得有至關的 C++ 功底bootstrap
Ruby 解釋器(CRuby)目前尚未實現 JIT,代碼流程比較簡單,清晰,利於學習一門語言以及虛擬機實現ubuntu
相比於其它的動態腳本語言(Python),Ruby 的面向對象實現應該是比較純正的,比較符合 Me 的口味segmentfault
本系列的靈感來源於《Ruby原理剖析》這本著做,讓一個 Java 技術棧的~從業者~有興趣和勇氣向你們分享這一系列^_-數組
Ruby 2.x 源代碼學習:ubuntu 環境 下載,編譯,調試 ruby 源代碼
全部的偉大,源於一個勇敢的開始,把源代碼下載下來,編譯,調試/運行!ruby
代碼片斷省略掉 (萬能的!!!)宏定義 和一些無關主幹的細節(下同)
# 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)