回顧一下 Ruby 解釋器處理流程node
詞法分析數組
語法分析ruby
AST 生成數據結構
虛擬機指令生成框架
虛擬機指令有兩種存儲結構:ide
鏈式結構,經過 LINK_ELEMENT 鏈表節點函數
數組(線性)結構優化
解釋器會先遍歷 AST 生成鏈式結構存儲的指令列表,可是虛擬機並不會直接解釋執行該列表,而是進一步將指令列表「序列化」成字節數組,PC(指令指針)寄存器能夠索引指令數組(取指,分支跳轉)idea
本文簡要介紹 Ruby 2.x 源代碼中將鏈式結構轉化成數組結構的源代碼,從中能夠進一步瞭解 Ruby 虛擬機相關數據結構的設計與實現debug
由此前的系列文章能夠,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 函數中 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 函數有一段註釋代表了該函數的用途
// 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_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 類型的指令是一條僞指令,並不生成具體的指令序列,只是爲了肯定分支跳轉的指令偏移量。在建立 label 的時候 position,set 屬性並未賦值,要等到此處才能肯定它們的真實值
// iseq_set_sequence @ compile.c case ISEQ_ELEMENT_LABEL: { LABEL *lobj = (LABEL*) list; lobj->position = code_index; lobj->set = TRUE; break; }
使用第一次遍歷獲得的 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); ...