YARV源码读解(2)
原文:http://d.hatena.ne.jp/hzkr/20061103
第二回了,上回看了ruby命令启动到yarv的评价器入口:
- VALUE iseq = th_compile_from_node(GET_THREAD(), node, file);
- return yarvcore_eval_iseq(iseq);
这次,来看一下这个函数的前半部分,进入到th_compile_from_node里面去。在这之前,先看看大体的流程和一些数据的构造。
编译处理流程
iseq = th_compile_from_node(thread, node, file) @ yarvcore.c
这个函数的作用是把传过来的解析好的node转换成YARV的指令序列
编译Top level的代码的时候,和编译eval执行的代码时候参数有少许不同。
但是基本上都是整个扔到yarv_iseq_new函数中直接进行处理。实际上eval之外进行编译时,可以通过查看执行path来查看到这个情况
- VALUE iseq;
- iseq = yarv_iseq_new(node, rb_str_new2("<main>"), file,
- Qfalse, ISEQ_TYPE_TOP);
- return iseq;
yarv_iseq_new(node, name, file, parent, type) @ iseq.c
这个函数主要是调用yarv_iseq_new_with_opt ,调用时,有一个表示各种优化标志的参数,这时候会用默认值(COMPILE_OPTION_DEFAULT)传过去。关于这些参数(flag)在后面读到优化部分的时候再看。
- return yarv_iseq_new_with_opt(node, name, file_name,
- parent, type, Qfalse,
- &COMPILE_OPTION_DEFAULT);
o yarv_iseq_new_with_opt @ iseq.c 这个函数顺次进行如下工作:
做成表示命令列的Ruby的object(YARVCore::InstructionSequence)
把要执行的文件名等情报存到刚做成的对象中,然后分配编译过程中必要的内存。(prepare_iseq_build)
主要的编译执行部分 (iseq_compile)
释放使用后的内存(cleanup_iseq_build)
- VALUE
- yarv_iseq_new_with_opt(NODE*node, VALUE name, VALUE file_name,
- VALUE parent, VALUE type,
- VALUE block_opt,
- const yarv_compile_option_t *option)
- {
- yarv_iseq_t *iseq;
- VALUE self = iseq_alloc(cYarvISeq);
- GetISeqPtr(self, iseq);
- iseq->self = self;
- prepare_iseq_build(iseq, name, file_name, parent, type,
- block_opt, option);
- iseq_compile(self, node);
- cleanup_iseq_build(iseq);
- return self;
- }
一点点的深入,下面来看看iseq_compile函数。
o iseq_compile(self, node) @ compile.c
这个函数比较长,适当的裁减了一下,并在代码中加入说明。
- VALUE
- iseq_compile(VALUE self, NODE *narg)
- {
- DECL_ANCHOR(list_anchor);
参数self是刚才做成的对象。这个函数的功能是把语法树narg编译之后放到这个self变量里去。这里不是一下子就把命令列就放到self里,而是会先临时放到一个list里管理。DECL_ANCHOR的作用就是声明了这么一个list的构造体。
debugs(“[compile step 1 (traverse each node)]\n”);
从这里开始,每步编译过程都加入了debug用的message,很容易理解。第一步就是单纯的遍历文法树,把文法变为YARV的命令列。根据node的不同,可能会有复杂的判断处理,但是最终这一步的处理都是通过COMPILE这个宏来完成的。 COMPILE(list_anchor, “top level node”, node);
宏COMPILE的定义如下:
- #define COMPILE(anchor, desc, node) \
- (debug_compile("== " desc "\n", \
- iseq_compile_each(iseq, anchor, node, 0)))
一边记录debug消息,同时调用了函数iseq_compile_each。这个函数遍历文法树,长度有2000多行,几乎都是switch语句。具体的【文法树-》命令列】变换的规则今天先不说,先看后面的内容。
- return iseq_setup(iseq, list_anchor);
- }
下面接着看iseq_setup这个函数。
o iseq_setup(iseq, anchor) @
为了容易理解,只把debug消息抽出来。
- //debugs("[compile step 2] (iseq_array_to_linkedlist)\n");
- debugs("[compile step 3.1 (iseq_optimize)]\n");
- debugs("[compile step 3.2 (iseq_insns_unification)]\n");
- debugs("[compile step 3.3 (set_sequence_stackcaching)]\n");
- debugs("[compile step 4.1 (set_sequence)]\n");
- debugs("[compile step 4.2 (set_exception_table)]\n");
- debugs("[compile step 4.3 (set_optargs_table)] \n");
- debugs("[compile step 5 (iseq_translate_direct_threaded_code)] \n");
- debugs("[compile step: finish]\n");
这里的几个步骤,的if语句根据option的不同,有的被执行,有的不被执行。第二步被注释掉了。从iseq_array_to_linkedlist这个字面来看,是把第一步做成的数组转换成list的意思了。
编译处理流程的总结
总之目前流程就是如上所述,下面总结一下。
th_compile_from_node→yarv_iseq_new→yarv_iseq_new_with_opt
YARVCore::InstructionSequence对象的做成
prepare_iseq_build
iseq_compile
[compile step 1 (traverse each node)]
COMPILE
iseq_setup
[compile step 3.1 (iseq_optimize)]
[compile step 3.2 (iseq_insns_unification)]
[compile step 3.3 (set_sequence_stackcaching)]
[compile step 4.1 (set_sequence)]
[compile step 4.2 (set_exception_table)]
[compile step 4.3 (set_optargs_table)]
[compile step 5 (iseq_translate_direct_threaded_code)]
[compile step: finish]
cleanup_iseq_build
数据结构
编译指的是把语法树结构换成YARV命令列。而且,刚才看到的连接链表(list_anchor)也会在变换中被用到。
在这里数据结构,操作这些结构的函数会被经常用到,所以先来总结一下这些东西。
数据结构:文法树的node
Ruby程序一时的被转换为了文法树。YARV的核心处理的不是Ruby的源程序,而是Ruby的文法树。文法树的节点的构造体的定义如下:
- typedef struct RNode {
- unsigned long flags;
- char *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 global_entry *entry;
- long cnt;
- VALUE value;
- } u3;
- } NODE;
真是比较长啊,不过还好RHG的第12章有详细的解说。
flags表示node的种类,比如NODE_METHOD, NODE_IF, NODE_STR,等。
宏定义nd_type(node)用来取得node的类型。switch(nd_type(node)) 这样的处理几乎已经成为定型了。
根据node种类的不同,最大可以有三个子node:u1, u2, u3。
比如if语句有条件,then分支和else分支。
u1这样的名字不太好读,所以提供了类似nd_cond或nd_else等的宏。然后就可以方便的用node.nd_else来访问这些字段了。
看到node.nd_*** 这样的code的话意思就是要访问某一子node了。
记住了这些大概就能继续往后面阅读了。
数据结构:list
文法树还会被变换到YARV的命令列表里。在那里再进行优化处理,最后,插入到YARV的命令列数组中去。也许在链表里进行命令的插入,删除,排序等要比全部在数组里进行效率要高很多吧。
List的构造体如下:
- typedef struct iseq_link_element {
- int type;
- struct iseq_link_element *next;
- struct iseq_link_element *prev;
- } LINK_ELEMENT;
看到next和prev这样的指针,就知道是典型的链表结构了。想要加入到list的数据,要把这个LINK_ELEMENT类型的数据放到内存的最前边。然后在type成员中指定数据类型。比如INSN这个构造体(它的结构一会解说),在内存中结构就是这样的,LINK_ELEMENT在最前边。
+——————-+
| LINK | link.type |
| _ELE | link.next |
| MENT | link.prev |
+——+————+
| insn_id |
| line_no |
| … |
+——————-+
因为这样的内存结构,想对list进行处理的话也可以把INSN* 当LINK_ELEMENT*进行操作了。要想作为INSN进行操作,可以先判断type是不是ISEQ_ELEMENT_INSN,如果是的话再可以转换为INSN*。看起来像是INSN类继承了LINK_ELEMENT类似的(当然他们只是构造体,至少在c语言里。)
List里能放两种类型的数据,一个是YARV命令。
- #define ISEQ_ELEMENT_INSN INT2FIX(0x02)
- typedef struct iseq_insn_data {
- LINK_ELEMENT link;
- int insn_id;
- int line_no;
- int operand_size;
- int sc_state;
- VALUE *operands;
- } INSN;
另一个是表示跳转目的的label
- #define ISEQ_ELEMENT_LABEL INT2FIX(0x01)
- typedef struct iseq_label_data {
- LINK_ELEMENT link;
- int label_no;
- int position;
- int sc_state;
- int set;
- int sp;
- } LABEL;
标号(label)和命令混在list里,作为YARV的中间数据使用。
在源代码里还看到了这么一行
#define ISEQ_ELEMENT_SEQ INT2FIX(0×03)
这个类型虽然声明了,但是没有在哪里被用到过。
LINK_ELEMENT元素指的是list中的单个的元素而已,指向list本身的类型是LINK_ANCHOR结构
- typedef struct iseq_link_anchor {
- LINK_ELEMENT anchor;
- LINK_ELEMENT *last;
- } LINK_ANCHOR;
成员last指的是list的最后一个元素。这样在列表后面添加元素就简单了。ADD_ELEM正是往列表里添加元素的函数。
- static void
- ADD_ELEM(LINK_ANCHOR *anchor, LINK_ELEMENT *elem)
- {
- elem->prev = anchor->last;
- anchor->last->next = elem;
- anchor->last = elem;
- verify_list("add", anchor);
- }
在anchor->last后面接上一个elem,然后再把elem赋给anchor->last。这个参数elem应该是新做成的,还没有连到任何列表的ELEMENT
LINK_ANCHOR的另一个参数anchor是干什么用的呢?这个成员即是list的头元素,也是last的初期值。什么意思呢,比如新的空的list做成时候,这样来进行声明。
LINK_ANCHOR newList = {{0,0,0}, &newList.anchor};
// {0,0,0}:没有联接到任何链表的空的newList.anchor
// last也被赋值为newList.anchor所指向的地址。
尽管可以用 last==NULL 来判断list是不是空的,但是,如果使用dummy的anchor这个属性,在ADD_ELEM函数里不用判断是不是NULL,直接就可以使用anchor->last->next。
而且,list的头元素可以用anchor.next很方便的取得。
另外,空list的声明也是用宏来实现的。
#define DECL_ANCHOR(name) \
LINK_ANCHOR name##_body__ = {{0,}, &name##_body__.anchor}; \
LINK_ANCHOR *name = & name##_body__
除了这些,还定义了别的一系列的列表操作函数。实现起来比较简单,这里只列一下名字。
//把 LINK_ELEMENT作为参数的函数
- INSERT_ELEM_PREV(elem1, elem2) /* elemX, elem1 =>
- elemX, elem2, elem1 */
- REPLACE_ELEM(elem1, elem2) /* elemX, elem1, elemY =>
- elemX, elem2, elemY */
- REMOVE_ELEM(elem) /* elemX, elem, elemY =>
- elemX, elemY */
//把 LINK_ANCHOR作为参数的函数
FIRST_ELEMENT
POP_ELEMENT
SHIFT_ELEMENT
LIST_SIZE
LIST_SIZE_ZERO
APPEND_LIST
数据结构-命令列
最终生成的命令列都以类YARVCore::InstructionSequence的对象的形式返回,这个类,在c语言里是yarv_iseq_struct构造体。
- struct yarv_iseq_struct {
- /* instruction sequence type */
- VALUE type;
- VALUE self;
- VALUE name; /* String: iseq name */
- VALUE *iseq; /* iseq */
- ...
这个构造体里还不明白的地方今天就先略过不看了,一边看实际的使用的代码,一边来理解吧。
原始的YARV命令列,是放到了一个VALUE型的的数组里。VALUE在RHG第二章里提到是表示指针的整形,32bit的机器里的话是32bit的unsinged int
总结
本周进行了YARV源代码的学习,下周继续。
最新评论