Ruby 2.x 源代碼學習:語法分析 & 中間代碼生成 之 指令鏈表序列化

前言

回顧一下 Ruby 解釋器處理流程node

  • 詞法分析數組

  • 語法分析ruby

  • AST 生成數據結構

  • 虛擬機指令生成框架

虛擬機指令有兩種存儲結構:ide

  • 鏈式結構,經過 LINK_ELEMENT 鏈表節點函數

  • 數組(線性)結構優化

解釋器會先遍歷 AST 生成鏈式結構存儲的指令列表,可是虛擬機並不會直接解釋執行該列表,而是進一步將指令列表「序列化」成字節數組,PC(指令指針)寄存器能夠索引指令數組(取指,分支跳轉)idea

本文簡要介紹 Ruby 2.x 源代碼中將鏈式結構轉化成數組結構的源代碼,從中能夠進一步瞭解 Ruby 虛擬機相關數據結構的設計與實現debug

由 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);
...

總結

相關文章
相關標籤/搜索