LINK_ELEMENT
LINK_ELEMENT 是一個雙向連結串列的”頭”節點,包含指向前一個節點和後一個節點的指標,其它結構體通過將 LINK_ELEMENT 放在結構體的頭部來將結構體組織成雙向連結串列
// 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 欄位將所有的指令連結成一個雙向連結串列
// 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_ANCHOR 結構體用來管理由 LINK_ELEMENT 組成的雙向連結串列,包含表頭 anchor 和一個指向雙向連結串列最後一個節點的指標 last,注意 LINK_ANCHOR 本身不包含資料,anchor.next 指向雙向連結串列第一個元素
// compile.c
typedef struct iseq_link_anchor {
LINK_ELEMENT anchor;
LINK_ELEMENT *last;
} LINK_ANCHOR;
新增 LINK_ELEMENT
下文講到 建立 INSN 結構體的時候會用到 ADD_ELEM 函式,這裡簡單介紹一下:
/*
* 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
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 結構體的 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 type
node.h 中給出了完整的 node_type 列表,緊跟每個 node_type 後面的 巨集定義是為了方便訪問 not_type
// node.h
enum node_type {
NODE_SCOPE,
#define NODE_SCOPE NODE_SCOPE
NODE_BLOCK,
#define NODE_BLOCK NODE_BLOCK
...
}
訪問 NODE 欄位
如果直接以 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
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,運算元陣列
建立 INSN
compile.c 定義了一些巨集和函式用於建立 INSN,我們來看幾個關鍵的巨集定義
ADD_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
", 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
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
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_t
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);
}
iseq_imemo_alloc
該函式用於分配 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
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
在經過了前面的準備工作之後,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
程式碼塊1 對應 AST 是一棵空樹的情況,即沒有 ruby 指令碼需要轉譯,COMPILE 是一個巨集定義,下文再詳細介紹
if (node == 0) {
COMPILE(ret, "nil", node);
iseq_set_local_table(iseq, 0);
}
程式碼塊2
程式碼端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: {
}
}
}