LINK_ELEMENT 是一個雙向鏈表的"頭"節點,包含指向前一個節點和後一個節點的指針,其它結構體經過將 LINK_ELEMENT 放在結構體的頭部來將結構體組織成雙向鏈表node
// compile.c typedef struct iseq_link_element { enum { ISEQ_ELEMENT_NONE, ISEQ_ELEMENT_LABEL, ISEQ_ELEMENT_INSN, ISEQ_ELEMENT_ADJUST } type; struct iseq_link_element *next; struct iseq_link_element *prev; } LINK_ELEMENT;
以 INSN(表示一條 YNRV 指令)結構體爲例,INSN 結構體經過 link 字段將全部的指令連接成一個雙向鏈表vim
// compile.c typedef struct iseq_insn_data { LINK_ELEMENT link; enum ruby_vminsn_type insn_id; unsigned int line_no; int operand_size; int sc_state; VALUE *operands; } INSN;
LINK_ANCHOR 結構體用來管理由 LINK_ELEMENT 組成的雙向鏈表,包含表頭 anchor 和一個指向雙向鏈表最後一個節點的指針 last,注意 LINK_ANCHOR 自己不包含數據,anchor.next 指向雙向鏈表第一個元素數組
// compile.c typedef struct iseq_link_anchor { LINK_ELEMENT anchor; LINK_ELEMENT *last; } LINK_ANCHOR;
下文講到 建立 INSN 結構體的時候會用到 ADD_ELEM 函數,這裏簡單介紹一下:ruby
/* * elem1, elem2 => elem1, elem2, elem */ static void ADD_ELEM(ISEQ_ARG_DECLARE LINK_ANCHOR *const anchor, LINK_ELEMENT *elem) { elem->prev = anchor->last; anchor->last->next = elem; anchor->last = elem; verify_list("add", anchor); }
NODE 結構體用於封裝 AST(抽象語法樹)的一個節點,NODE 使用了聯合體(union)來複用各個語法樹節點須要的數據,這也是 C 語言慣用優化手法數據結構
// node.h typedef struct RNode { VALUE flags; VALUE nd_reserved; /* ex nd_file */ union { struct RNode *node; ID id; VALUE value; VALUE (*cfunc)(ANYARGS); ID *tbl; } u1; union { struct RNode *node; ID id; long argc; VALUE value; } u2; union { struct RNode *node; ID id; long state; struct rb_global_entry *entry; struct rb_args_info *args; long cnt; VALUE value; } u3; } NODE;
NODE 結構體的 flags 字段包含一系列的比特位,存儲了 NODE 的一些屬性,經過這種壓縮存儲能夠提升 NODE 結構體的空間利用率函數
// node.h /* NODE_FL: * 0..4: T_TYPES, * 5: KEEP_WB, * 6: PROMOTED, * 7: NODE_FL_NEWLINE|NODE_FL_CREF_PUSHED_BY_EVAL, * 8..14: nd_type, * 15..: nd_line */
以 nd_type 爲例,node.h 定義了 nd_type 在 flag 中的偏移量 NODE_TYPE_SHIFT 以及掩碼 NODE_TYPE_MASK,以及獲取和設置 nd_type 的宏定義優化
// node.h #define NODE_TYPESHIFT 8 #define NODE_TYPEMASK (((VALUE)0x7f)<<NODE_TYPESHIFT) #define nd_type(n) ((int) (((RNODE(n))->flags & NODE_TYPEMASK)>>NODE_TYPESHIFT)) #define nd_set_type(n,t) \ RNODE(n)->flags=((RNODE(n)->flags&~NODE_TYPEMASK)|((((unsigned long)(t))<<NODE_TYPESHIFT)&NODE_TYPEMASK))
node.h 中給出了完整的 node_type 列表,緊跟每一個 node_type 後面的 宏定義是爲了方便訪問 not_typeui
// node.h enum node_type { NODE_SCOPE, #define NODE_SCOPE NODE_SCOPE NODE_BLOCK, #define NODE_BLOCK NODE_BLOCK ... }
若是直接以 C 語言聯合體的語法訪問 NODE 中的字段會致使程序的可讀性不好,所以 node.h 中定義了一些宏方便在 具體 AST 節點上下文中訪問,以 if 語句的 NODE 節點爲例,能夠經過如下宏定義訪問 condition(條件語句塊),body(條件爲 true 時語句塊)以及 else(條件爲 false 時語句塊)翻譯
#define nd_cond u1.node #define nd_body u2.node #define nd_else u3.node
node.h 中定義了大量的宏方便新建 NODE,這些宏被被組織成一種層次結構
NEW_NODE 宏定義是核心,它直接被展開成 rb_node_newnode 的方法調用指針
// node.h #define NEW_NODE(t,a0,a1,a2) rb_node_newnode((t),(VALUE)(a0),(VALUE)(a1),(VALUE)(a2))
咱們來看看 rb_node_newnode 的實現:
使用 newobj_of 在 GC 管理的堆空間中分配一個 NODE 節點並初始化
設置節點類型 type
// gc.c NODE* rb_node_newnode(enum node_type type, VALUE a0, VALUE a1, VALUE a2) { /* TODO: node also should be wb protected */ NODE *n = (NODE *)newobj_of(0, T_NODE, a0, a1, a2, FALSE); nd_set_type(n, type); return n; }
其它 NEW_XXX 宏引用 NEW_NODE 宏,以 NEW_IF 宏定義(建立if 語句語法樹節點)爲例:
// node.h #define NEW_IF(c,t,e) NEW_NODE(NODE_IF,c,t,e)
c 表明 condition,即條件語句對應的 NODE 節點
t 表明 then,即條件爲 true 時對應的語句的 NODE 節點
e 表明 else,即條件爲 false 時對應的語句的 NODE 節點
INSN 結構體用於描述一條 YARV 指令:
// compile.c typedef struct iseq_insn_data { LINK_ELEMENT link; enum ruby_vminsn_type insn_id; unsigned int line_no; int operand_size; int sc_state; VALUE *operands; } INSN;
link,用於將指令連接成雙向鏈表
insn_id,指令類型
line_no,行號
operand_size,操做數個數
operands,操做數數組
compile.c 定義了一些宏和函數用於建立 INSN,咱們來看幾個關鍵的宏定義
// compile.c #define ADD_INSN(seq, line, insn) \ ADD_ELEM((seq), (LINK_ELEMENT *) new_insn_body(iseq, (line), BIN(insn), 0)) #define ADD_INSN2(seq, line, insn, op1, op2) \ ADD_ELEM((seq), (LINK_ELEMENT *) \ new_insn_body(iseq, (line), BIN(insn), 2, (VALUE)(op1), (VALUE)(op2))) #define ADD_INSN3(seq, line, insn, op1, op2, op3) \ ADD_ELEM((seq), (LINK_ELEMENT *) \ new_insn_body(iseq, (line), BIN(insn), 3, (VALUE)(op1), (VALUE)(op2), (VALUE)(op3)))
ADD_INSN2 和 ADD_INSN3 是 ADD_INSN 帶操做數版本,咱們先看看簡單一點的 ADD_INSN
seq,LINK_ANCHOR 結構體,新建的 INSN 將被連接到 seq 末尾(last)
line,行號
insn,指令枚舉值
BIN 宏定義在 insns.inc 中,用於將 iseq 轉化爲 ruby_viminsn_type 類型的枚舉值
例如 BIN(nop) 會被展開成 YARVINSN_nop
#define BIN(n) YARVINSN_##n enum ruby_vminsn_type { BIN(nop) = 0, ... }
咱們接着來看一下 new_insn_body 函數
若是指令字節碼包含操做數,即 argc > 0,則調用 compile_data_alloc 在 iseq 中分配空間並初始化 operands
調用 new_insn_core 函數建立具體的 INSN
new_insn_core 函數調用 compile_data_alloc_insn 函數在 iseq 中分配空間並初始化 INSN
static INSN * new_insn_body(rb_iseq_t *iseq, int line_no, enum ruby_vminsn_type insn_id, int argc, ...) { VALUE *operands = 0; va_list argv; if (argc > 0) { int i; va_init_list(argv, argc); operands = (VALUE *)compile_data_alloc(iseq, sizeof(VALUE) * argc); for (i = 0; i < argc; i++) { VALUE v = va_arg(argv, VALUE); operands[i] = v; } va_end(argv); } return new_insn_core(iseq, line_no, insn_id, argc, operands); } static INSN * new_insn_core(rb_iseq_t *iseq, int line_no, int insn_id, int argc, VALUE *argv) { INSN *iobj = compile_data_alloc_insn(iseq); /* printf("insn_id: %d, line: %d\n", insn_id, line_no); */ iobj->link.type = ISEQ_ELEMENT_INSN; iobj->link.next = 0; iobj->insn_id = insn_id; iobj->line_no = line_no; iobj->operands = argv; iobj->operand_size = argc; iobj->sc_state = 0; return iobj; }
ADD_SEND_R 宏定義以下:
// compile.c #define ADD_SEND_R(seq, line, id, argc, block, flag, keywords) \ ADD_ELEM((seq), (LINK_ELEMENT *) new_insn_send(iseq, (line), (id), (VALUE)(argc), (block), (VALUE)(flag), (keywords)))
包含的參數比較多
seq,LINK_ANCHOR,新建的 SEND INSN 將被添加到 seq 末尾
line,行號
id,方法 id
argc,參數個數
block,block 參數
flag,標誌位
keywords,關鍵字參數
rb_iseq_t 是對 Ruby 虛擬機執行的指令序列的抽象,包含運行時信息 rb_iseq_constant_body 的引用,以及編譯時信息 iseq_compile_data 的引用。虛擬機指令的生成和執行都圍繞着 rb_iseq_t 結構體,可見其重要性
// iseq.h #ifndef rb_iseq_t typedef struct rb_iseq_struct rb_iseq_t; #define rb_iseq_t rb_iseq_t #endif
// vm_core.h /* T_IMEMO/iseq */ /* typedef rb_iseq_t is in method.h */ struct rb_iseq_struct { VALUE flags; VALUE reserved1; struct rb_iseq_constant_body *body; union { /* 4, 5 words */ struct iseq_compile_data *compile_data; /* used at compile time */ struct { VALUE obj; int index; } loader; } aux; };
rb_iseq_new_with_opt 函數建立以 參數 node 爲根的抽象語法樹(AST)對應的 rb_iseq_t,或者按照《編譯原理》的說法:根據 AST 生成中間代碼(rb_iseq_t)
parent,父 rb_iseq_t,所以能夠推斷出 rb_iseq_t 被組織成層次結構
type,類型
option,構建選項
// iseq.c rb_iseq_t * rb_iseq_new_with_opt(NODE *node, VALUE name, VALUE path, VALUE absolute_path, VALUE first_lineno, const rb_iseq_t *parent, enum iseq_type type, const rb_compile_option_t *option) { /* TODO: argument check */ rb_iseq_t *iseq = iseq_alloc(); if (!option) option = &COMPILE_OPTION_DEFAULT; prepare_iseq_build(iseq, name, path, absolute_path, first_lineno, parent, type, option); rb_iseq_compile_node(iseq, node); cleanup_iseq_build(iseq); return iseq_translate(iseq); }
該函數用於分配 rb_iseq_t 所需內存,裏面調用的 iseq_imemo_alloc,ZALLOC 等函數涉及到 Ruby 內存管理,暫不展開
// iseq.c static rb_iseq_t * iseq_alloc(void) { rb_iseq_t *iseq = iseq_imemo_alloc(); iseq->body = ZALLOC(struct rb_iseq_constant_body); return iseq; }
prepare_iseq_build 函數爲 翻譯 AST 作一些準備,這裏僅列出和 rb_iseq_t 數據結構相關的一段的代碼
static VALUE prepare_iseq_build(rb_iseq_t *iseq, VALUE name, VALUE path, VALUE absolute_path, VALUE first_lineno, const rb_iseq_t *parent, enum iseq_type type, const rb_compile_option_t *option) { ... iseq->body->type = type; set_relation(iseq, parent); ... }
set_relation 函數設置 iseq 的 parent
static void set_relation(rb_iseq_t *iseq, const rb_iseq_t *piseq) { ... if (piseq) { iseq->body->parent_iseq = piseq; } ... }
在通過了前面的準備工做以後,rb_iseq_compile_node 函數開始將 AST 翻譯成 rb_iseq_t
VALUE rb_iseq_compile_node(rb_iseq_t *iseq, NODE *node) { // 初始化 LINK_ANCHOR,翻譯過程當中全部的指令(ISNS 結構體)都會被添加到 ret 裏面 DECL_ANCHOR(ret); INIT_ANCHOR(ret); if (node == 0) { // 代碼塊 1 } else if (nd_type(node) == NODE_SCOPE) { // 代碼塊 2 } else if (RB_TYPE_P((VALUE) node, T_IMEMO)) { // 代碼塊 3 } else { // 代碼塊 4 } // 優化並將 ret 從鏈式結構轉換成線性(數組)結構存儲在 iseq 中 return iseq_setup(iseq, ret); }
爲了理清思路,對關鍵代碼段進行了註釋,而且標明瞭主要的 4 個分支邏輯
代碼塊1 對應 AST 是一棵空樹的狀況,即沒有 ruby 腳本須要轉譯,COMPILE 是一個宏定義,下文再詳細介紹
if (node == 0) { COMPILE(ret, "nil", node); iseq_set_local_table(iseq, 0); }
代碼端2 是一般會走到的邏輯,即翻譯一個 block, method, class 或 top 做用域
else if (nd_type(node) == NODE_SCOPE) { /* iseq type of top, method, class, block */ iseq_set_local_table(iseq, node->nd_tbl); iseq_set_arguments(iseq, ret, node->nd_args); switch (iseq->body->type) { case ISEQ_TYPE_BLOCK: { break; } case ISEQ_TYPE_CLASS: { break; } case ISEQ_TYPE_METHOD: { break; } default: { } } }