【翻译】YARV源码读解(2)
发表于:2007年06月15日  分类:Ruby  添加评论  1,937 次浏览 

YARV源码读解(2)
原文:http://d.hatena.ne.jp/hzkr/20061103
第二回了,上回看了ruby命令启动到yarv的评价器入口:

  1. VALUE iseq = th_compile_from_node(GET_THREAD(), node, file);
  2. 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来查看到这个情况

  1. VALUE iseq;
  2.     iseq = yarv_iseq_new(node, rb_str_new2("<main>"), file,
  3.                          Qfalse, ISEQ_TYPE_TOP);
  4.     return iseq;

yarv_iseq_new(node, name, file, parent, type) @ iseq.c
这个函数主要是调用yarv_iseq_new_with_opt ,调用时,有一个表示各种优化标志的参数,这时候会用默认值(COMPILE_OPTION_DEFAULT)传过去。关于这些参数(flag)在后面读到优化部分的时候再看。

  1. return yarv_iseq_new_with_opt(node, name, file_name,
  2.                                   parent, type, Qfalse,
  3.                                   &COMPILE_OPTION_DEFAULT);

o yarv_iseq_new_with_opt @ iseq.c 这个函数顺次进行如下工作:
做成表示命令列的Ruby的object(YARVCore::InstructionSequence)
把要执行的文件名等情报存到刚做成的对象中,然后分配编译过程中必要的内存。(prepare_iseq_build)
主要的编译执行部分 (iseq_compile)
释放使用后的内存(cleanup_iseq_build)

  1. VALUE
  2. yarv_iseq_new_with_opt(NODE*node, VALUE name, VALUE file_name,
  3.      VALUE parent, VALUE type,
  4.      VALUE block_opt,
  5.                        const yarv_compile_option_t *option)
  6. {
  7.     yarv_iseq_t *iseq;
  8.     VALUE self = iseq_alloc(cYarvISeq);
  9.     
  10.     GetISeqPtr(self, iseq);
  11.     iseq->self = self;
  12.  
  13.     prepare_iseq_build(iseq, name, file_name, parent, type,
  14.                        block_opt, option);
  15.     iseq_compile(self, node);
  16.     cleanup_iseq_build(iseq);
  17.     return self;
  18. }

一点点的深入,下面来看看iseq_compile函数。
o iseq_compile(self, node) @ compile.c
这个函数比较长,适当的裁减了一下,并在代码中加入说明。

  1. VALUE
  2. iseq_compile(VALUE self, NODE *narg)
  3. {
  4.     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的定义如下:

  1. #define COMPILE(anchor, desc, node) \
  2.   (debug_compile("== " desc "\n", \
  3.                  iseq_compile_each(iseq, anchor, node, 0)))

一边记录debug消息,同时调用了函数iseq_compile_each。这个函数遍历文法树,长度有2000多行,几乎都是switch语句。具体的【文法树-》命令列】变换的规则今天先不说,先看后面的内容。

  1. return iseq_setup(iseq, list_anchor);
  2. }

下面接着看iseq_setup这个函数。
o iseq_setup(iseq, anchor) @
为了容易理解,只把debug消息抽出来。

  1. //debugs("[compile step 2] (iseq_array_to_linkedlist)\n");
  2.   debugs("[compile step 3.1 (iseq_optimize)]\n");
  3.   debugs("[compile step 3.2 (iseq_insns_unification)]\n");
  4.   debugs("[compile step 3.3 (set_sequence_stackcaching)]\n");
  5.   debugs("[compile step 4.1 (set_sequence)]\n");
  6.   debugs("[compile step 4.2 (set_exception_table)]\n");
  7.   debugs("[compile step 4.3 (set_optargs_table)] \n");
  8.   debugs("[compile step 5 (iseq_translate_direct_threaded_code)] \n");
  9.   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的文法树。文法树的节点的构造体的定义如下:

  1. typedef struct RNode {
  2.     unsigned long flags;
  3.     char *nd_file;
  4.     union {
  5. struct RNode *node;
  6. ID id;
  7. VALUE value;
  8. VALUE (*cfunc)(ANYARGS);
  9. ID *tbl;
  10.     } u1;
  11.     union {
  12. struct RNode *node;
  13. ID id;
  14. long argc;
  15. VALUE value;
  16.     } u2;
  17.     union {
  18. struct RNode *node;
  19. ID id;
  20. long state;
  21. struct global_entry *entry;
  22. long cnt;
  23. VALUE value;
  24.     } u3;
  25. } 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的构造体如下:

  1. typedef struct iseq_link_element {
  2.     int type;
  3.     struct iseq_link_element *next;
  4.     struct iseq_link_element *prev;
  5. } 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命令。

  1. #define ISEQ_ELEMENT_INSN  INT2FIX(0x02)
  2. typedef struct iseq_insn_data {
  3.     LINK_ELEMENT link;
  4.     int insn_id;
  5.     int line_no;
  6.     int operand_size;
  7.     int sc_state;
  8.     VALUE *operands;
  9. } INSN;

另一个是表示跳转目的的label

  1. #define ISEQ_ELEMENT_LABEL INT2FIX(0x01)
  2. typedef struct iseq_label_data {
  3.     LINK_ELEMENT link;
  4.     int label_no;
  5.     int position;
  6.     int sc_state;
  7.     int set;
  8.     int sp;
  9. } LABEL;

标号(label)和命令混在list里,作为YARV的中间数据使用。
在源代码里还看到了这么一行
#define ISEQ_ELEMENT_SEQ INT2FIX(0×03)
这个类型虽然声明了,但是没有在哪里被用到过。
LINK_ELEMENT元素指的是list中的单个的元素而已,指向list本身的类型是LINK_ANCHOR结构

  1. typedef struct iseq_link_anchor {
  2.     LINK_ELEMENT anchor;
  3.     LINK_ELEMENT *last;
  4. } LINK_ANCHOR;

成员last指的是list的最后一个元素。这样在列表后面添加元素就简单了。ADD_ELEM正是往列表里添加元素的函数。

  1. static void
  2. ADD_ELEM(LINK_ANCHOR *anchor, LINK_ELEMENT *elem)
  3. {
  4.   elem->prev = anchor->last;
  5.   anchor->last->next = elem;
  6.   anchor->last = elem;
  7.   verify_list("add", anchor);
  8. }

在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作为参数的函数

  1. INSERT_ELEM_PREV(elem1, elem2) /* elemX, elem1 =>
  2.                                    elemX, elem2, elem1 */
  3. REPLACE_ELEM(elem1, elem2) /* elemX, elem1, elemY =>
  4.                                 elemX, elem2, elemY */
  5. REMOVE_ELEM(elem) /* elemX, elem, elemY =>
  6.                        elemX, elemY */

//把 LINK_ANCHOR作为参数的函数
FIRST_ELEMENT
POP_ELEMENT
SHIFT_ELEMENT
LIST_SIZE
LIST_SIZE_ZERO
APPEND_LIST

数据结构-命令列
最终生成的命令列都以类YARVCore::InstructionSequence的对象的形式返回,这个类,在c语言里是yarv_iseq_struct构造体。

  1. struct yarv_iseq_struct {
  2.     /* instruction sequence type */
  3.     VALUE type;
  4.  
  5.     VALUE self;
  6.     VALUE name;   /* String: iseq name */
  7.     VALUE *iseq;  /* iseq */
  8.     ...

这个构造体里还不明白的地方今天就先略过不看了,一边看实际的使用的代码,一边来理解吧。
原始的YARV命令列,是放到了一个VALUE型的的数组里。VALUE在RHG第二章里提到是表示指针的整形,32bit的机器里的话是32bit的unsinged int
总结
本周进行了YARV源代码的学习,下周继续。

固定链接: http://liubin.nanshapo.com/2007/06/15/%e3%80%90%e7%bf%bb%e8%af%91%e3%80%91yarv%e6%ba%90%e7%a0%81%e8%af%bb%e8%a7%a3%ef%bc%882%ef%bc%89/ | 其实我是一个程序员

报歉!评论已关闭.