YARV是面向对象脚本语言Ruby的一个实现。和普通的Ruby不一样,它的特点是把脚本转换成虚拟机上的bytecode,能高速执行。
最近,Parrot,CLR,JavaVM等基于虚拟机的编程语言好像比较热门。想着能深入了解某一种就好了,于是选择了YARV。另外,我连10行的Ruby都没编过,在学习Yarv的同时,也能顺便学习一下Ruby。

1.资料
现在时点(2006/11/)Yarv的最新版本是0.4.1。为什么没有从cvs下来最新的代码呢?因为手里没有bison(GNU版的YACC),所以就偷点懒了。
可以参考的网站

YARV: Yet Another Ruby VM
Yarv的老家。源代码的下载,和Yarv的架构设计文档等。
YARV Maniacs
Rubyist Magazie的连载文章,介绍Yarv的。Yarv的作者写的,介绍了在源代码上如何实装等的一系列文章。
Ruby源代码完全解说
青木峰郎写的Ruby源代码解读的书。除了Yarv核心以外,其它的Ruby等内容,可以参考这本书

main @ main.c
main函数中初始化完了后,就可是执行代码了。这里看一下这之前的一些流程。先来看一下main函数。Ruby/Yarv就是从这里开始执行的。去掉依存于环境的#ifdef等先不管,简单的main函数就是这个样子:

  1. int
  2. main(int argc, char **argv, char **envp)
  3. {
  4.     {
  5.         RUBY_INIT_STACK ruby_init();
  6.         ruby_options(argc, argv);
  7.         ruby_run();
  8.     }
  9.     return 0;
  10. }

最初的RUBY_INIT_STACK是GC所需要的,记住stack开始位置的宏。所做的事情主要的就是把stack的开始地址赋给变量rb_gc_stack_start(IA64系统好像是别的变量)。宏定义如下:

  1. #define RUBY_INIT_STACK \
  2.     VALUE variable_in_this_stack_frame; \
  3.     ruby_init_stack(&variable_in_this_stack_frame);

这样,在stack声明了一个变量variable_in_this_stack_frame,并把把这个变量的地址传给了ruby_init_stack 。 ruby_init_stack中包括对stack的增长方向检查等稍稍复杂的工作,抛开这些和当前讨论有关的内容简单来说如下面这样:

  1. void ruby_init_stack(VALUE *addr)
  2. {
  3.   ... 略 ...
  4.     rb_gc_stack_start = addr;
  5.   ... 略 ...
  6. }

像上面那样。这段代码(变量),可能在之后会被GC不停的访问,今天就不深入研究了。

接着,顺序调用下面3个函数

  • ruby_init (处理本体和内置模块的初始化)
  • ruby_options (命令行传过来的参数的解析)
  • ruby_run (脚本的执行)
  • 好了,接着顺次地进行读解下面的内容
    ruby_init @ eval.c
    下面是初始化函数ruby_init 的主要部分的摘要:

    1. void
    2. ruby_init()
    3. {
    4.   ... 略 ...
    5.     Init_yarv();
    6.     Init_stack((void *)&state);
    7.     Init_heap();
    8.  
    9.     PUSH_TAG(PROT_NONE);
    10.     if ((state = EXEC_TAG()) == 0) {
    11.     rb_call_inits();
    12.     ruby_prog_init();
    13.     ALLOW_INTS;
    14.     }
    15.     POP_TAG_INIT();
    16.   ... 略 ...
    17. }

    Init_yarv!终于见到YARV了!
    在继续之前,先看一下那句后面的if语句。这个if语句对第一次看Ruby源代码的人来说应该像迷一样吧。

    1. PUSH_TAG(PROT_NONE);
    2.     if ((state = EXEC_TAG()) == 0) {
    3.       ...
    4.     }
    5.     POP_TAG_INIT();

    嗯,再看看EXEC_TAG的定义。

    1. #define TH_EXEC_TAG() \
    2.   (FLUSH_REGISTER_WINDOWS, ruby_setjmp(_th->tag->buf))
    3.  
    4. #define EXEC_TAG() \
    5.   TH_EXEC_TAG()

    ruby_setjmp是setjmp或_setjmp的#define定义。这里好像是调用了setjmp。Setjmp最开始的时候返回0,里面如果有logjmp的话,则返回非0的值。

    1. if ((state = EXEC_TAG()) == 0) { // ※
    2.       // 最初EXEC_TAG==setjmp返回0,所以代码执行到这里
    3.         // 如果这附近出错的话,里面会调用longjmp来进行跳转
    4.         // 所以在跳向※的时候返回非0值。
    5.       ...
    6.     }
    7.     // 处理将转向到这里

    这里的处理有点像begin~rescue 的处理过程。不过如果考虑的例外的话可能就要费很大精力了,所以先不过if (…) 这句,继续看下面的代码。
    (※稍微看了一下 Ruby Hacking Guide,好像像***_TAG这样的jump tag在Ruby进行评价(eval)的时候用了很多。Yarv怎么样还不知道,如果使用到了,到那时候再调查。)
    接着往下,ruby_init调用的初始化函数中,和yarv有关的只有两个,最初被调用的Init_yarv和在rb_call_init中被调用的Init_yarvcore。
    - Init_yarv
    - rb_call_inits
         - …
         - Init_yarvcore
         - …
    其它的普通的Ruby内部对象的处理等因为和Yarv没什么关系,都可以忽略不看。

    Init_yarv @ yarvcore.c
    第一个初始化函数是 Init_yarv:

    1. void
    2. Init_yarv(void)
    3. {
    4.     /* initialize main thread */
    5.     yarv_vm_t *vm = ALLOC(yarv_vm_t);
    6.     yarv_thread_t *th = ALLOC(yarv_thread_t);
    7.  
    8.     vm_init2(vm);
    9.     theYarvVM = vm;
    10.  
    11.     th_init2(th);
    12.     th->vm = vm;
    13.     yarv_set_current_running_thread_raw(th);
    14. }

    上面的代码先初始化了两个构造体。一个是表示yarv虚拟机构造体(yarv_vm_t),另一个是表示线程的构造体(yarv_thread_t)。vm_init2只是把memroy进行了zeroclear(使用了MEMZERO()函数),th_init2里面应该是frame的stack的做成,但是还没有看到Yarv的执行部分,具体是什么意思也不太清楚。等后面到了线程构造的部分在来检查吧。
    先记着生成了这两个构造体就行了。两个局部标量初始化之后,会把虚拟机变量赋给theYarvVM,这个值可以用宏GET_VM()获得它的值。线程构造体变量th在yarv_set_current_running_thread_raw函数里会赋给变量yarvCurrentThread,这个值可以用宏GET_THREAD()获得。

    Init_yarvcore @ yarvcore.c
    目前的yarv是作为ruby library来工作的(现在的yarv已经合并到Ruby里了),这个函数定义了Ruby的扩展库模块。

    1. mYarvCore = rb_define_module("YARVCore");
    2.     rb_define_const(mYarvCore, "VERSION",
    3.                     rb_str_new2(yarv_version));
    4.     ... 略 ...

    先不管这些,知道是一个Library就行了。途中会看到

    1. // make vm
    2.     /* create main thread */

    这样的注释,开始时候会感到疑惑,难道刚才没有建立吗vm之类的变量吗?保险起见,仔细看了一下make vm以后的内容。

    1. /* create vm object */
    2.     VALUE vmval = vm_alloc(cYarvVM);
    3.     yarv_vm_t *vm;
    4.     yarv_thread_t *th;
    5.     vm = theYarvVM;
    6.  
    7.     xfree(RDATA(vmval)->data);
    8.     RDATA(vmval)->data = vm;
    9.     vm->self = vmval;

    vm_alloc(cYarvVM) 方法创建了YarvVM类的实力(实际上是构造体变量),再把刚才做成的theYarvVM赋给vm,vmval也会赋给vm构造体的属性里。可以看出,theYarvVM是YARV内部使用的虚拟机变量。这里做成的vmval变量,是一个能在ruby脚本中能使用的包装对象(VALUE类型)。
    继续往下,到了注释create main thread 的地方了

    1. /* create main thread */
    2.     vm->main_thread_val = yarv_thread_alloc(cYarvThread);
    3.     GetThreadPtr(vm->main_thread_val, th);
    4.  
    5.     vm->main_thread = th;
    6.     vm->running_thread = th;
    7.     GET_THREAD()->vm = vm;
    8.     thread_free(GET_THREAD());
    9.     th->vm = vm;
    10.     yarv_set_current_running_thread(th);

    跟上面的一样yarv_thread_alloc(cYarvThread)方法里创建了YarvThread的一个实例。
    - yarv_thread_alloc
         - thread_alloc
         - thread_init
             - th_init
    yarv_thread_alloc里面又用th_init建立了一个thread类型的构造体,然后在后面,又用thread_free把刚才做成的thread 变量(GET_THREAD())给释放了,又在set_current_running_thread里面把刚才建立的变量th赋给了yarvCurrentThread。
    为什么这样做呢?因为Init_yarvcore有可能被反复地调用,所以在调用之前就把前面的thread给销毁了。但是这样的话为什么要在Init_yarv要先做成一个thread呢?目前还不明白,留作以后的课题吧。
    ruby_option @ eval.c
    初期化完了之后,就是对命令行参数的解析了,也没有什么特别值得一提的东西,就略过了。
    ruby_run @ eval.c
    最后,在main函数里调用了ruby_run函数。在ruby_run中调用了很多函数,但是主要的流程还是如下面所列的那样。
    - main
        - ruby_run
             - ruby_exec
                 - ruby_exec_internal
                     - yarvcore_eval_parsed @ yarvcore.c
    终于到了yarvcore!

    1. VALUE
    2. yarvcore_eval_parsed(NODE *node, VALUE file)
    3. {
    4.     VALUE iseq = th_compile_from_node(GET_THREAD(), node,
    5.                                       file);
    6.     return yarvcore_eval_iseq(iseq);
    7. }

    参数node是已经解析完的ruby语法书(syntax tree ,日语为构文木),参数file是ruby脚本源文件。这个函数比较容易读,主要是把语法树编译(转换)成iseq(Instruction Sequence,指令序列?),然后交给yarvcore_eval_iseq去真正的执行。

    总结
    下周,读compile和eval哪个比较好呢?还是先看data检查比较好呢?

    接下来将阅读
    PUSH_TAG, EXEC_TAG 等tag jump的实装
    th_init2 : Ruby的做成一个线程的函数的详细内容
    Init_yarv 做成的线程的意义

    *中间有段注释老是乱码(IE有,FireFox没有),不知道是什么问题?乱码部分内容如下:
       if ((state = EXEC_TAG()) == 0) { // ※
         // 最初EXEC_TAG==setjmp返回0,所以代码执行到这里 …
         // 如果这附近出错的话,里面会调用longjmp来进行跳转
         // 所以在跳向※的时候返回非0值。
       …
       }
       // 处理将转向到这里

    Related posts for the current post: