YARV源码读解(2)
原文:http://d.hatena.ne.jp/hzkr/20061103
第二回了,上回看了ruby命令启动到yarv的评价器入口:
这次,来看一下这个函数的前半部分,进入到th_compile_from_node里面去。在这之前,先看看大体的流程和一些数据的构造。
编译处理流程
iseq = th_compile_from_node(thread, node, file) @ yarvcore.c
这个函数的作用是把传过来的解析好的node转换成YARV的指令序列
编译Top level的代码的时候,和编译eval执行的代码时候参数有少许不同。
但是基本上都是整个扔到yarv_iseq_new函数中直接进行处理。实际上eval之外进行编译时,可以通过查看执行path来查看到这个情况
yarv_iseq_new(node, name, file, parent, type) @ iseq.c
这个函数主要是调用yarv_iseq_new_with_opt ,调用时,有一个表示各种优化标志的参数,这时候会用默认值(COMPILE_OPTION_DEFAULT)传过去。关于这些参数(flag)在后面读到优化部分的时候再看。
o yarv_iseq_new_with_opt @ iseq.c 这个函数顺次进行如下工作:
做成表示命令列的Ruby的object(YARVCore::InstructionSequence)
把要执行的文件名等情报存到刚做成的对象中,然后分配编译过程中必要的内存。(prepare_iseq_build)
主要的编译执行部分 (iseq_compile)
释放使用后的内存(cleanup_iseq_build)
一点点的深入,下面来看看iseq_compile函数。
o iseq_compile(self, node) @ compile.c
这个函数比较长,适当的裁减了一下,并在代码中加入说明。
参数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的定义如下:
一边记录debug消息,同时调用了函数iseq_compile_each。这个函数遍历文法树,长度有2000多行,几乎都是switch语句。具体的【文法树-》命令列】变换的规则今天先不说,先看后面的内容。
下面接着看iseq_setup这个函数。
o iseq_setup(iseq, anchor) @
为了容易理解,只把debug消息抽出来。
这里的几个步骤,的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的文法树。文法树的节点的构造体的定义如下:
真是比较长啊,不过还好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的构造体如下:
看到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命令。
另一个是表示跳转目的的label
标号(label)和命令混在list里,作为YARV的中间数据使用。
在源代码里还看到了这么一行
#define ISEQ_ELEMENT_SEQ INT2FIX(0×03)
这个类型虽然声明了,但是没有在哪里被用到过。
LINK_ELEMENT元素指的是list中的单个的元素而已,指向list本身的类型是LINK_ANCHOR结构
成员last指的是list的最后一个元素。这样在列表后面添加元素就简单了。ADD_ELEM正是往列表里添加元素的函数。
在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作为参数的函数
//把 LINK_ANCHOR作为参数的函数
FIRST_ELEMENT
POP_ELEMENT
SHIFT_ELEMENT
LIST_SIZE
LIST_SIZE_ZERO
APPEND_LIST
数据结构-命令列
最终生成的命令列都以类YARVCore::InstructionSequence的对象的形式返回,这个类,在c语言里是yarv_iseq_struct构造体。
这个构造体里还不明白的地方今天就先略过不看了,一边看实际的使用的代码,一边来理解吧。
原始的YARV命令列,是放到了一个VALUE型的的数组里。VALUE在RHG第二章里提到是表示指针的整形,32bit的机器里的话是32bit的unsinged int
总结
本周进行了YARV源代码的学习,下周继续。
RSS feed for comments on this post · TrackBack URI
Leave a reply