Ruby 2.x 原始碼學習:直譯器概述

weixin_33840661發表於2017-01-25

前言

本文作為這一系列的開篇,簡單介紹了 Ruby 直譯器執行軌跡,為後續詳細分析 Ruby 原始碼提供一個綱領。
之所以選擇學習 Ruby 原始碼主要是出於:

  • Ruby 原始碼主要使用 C 語言編寫,不像 JVM 使用 C++,要看懂 JVM 首先得有相當的 C++ 功底

  • Ruby 直譯器(CRuby)目前還沒有實現 JIT,程式碼流程比較簡單,清晰,利於學習一門語言以及虛擬機器實現

  • 相比於其它的動態指令碼語言(Python),Ruby 的物件導向實現應該是比較純正的,比較符合 Me 的口味

本系列的靈感來源於《Ruby原理剖析》這本著作,讓一個 Java 技術棧的~從業者~有興趣和勇氣向大家分享這一系列^_-

直譯器入口

程式碼片段省略掉 (萬能的!!!)巨集定義 和一些無關主幹的細節(下同)

# 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)

相關文章