本文參考了《Ruby原理剖析》並結合 Ruby 源代碼進行分析程序員
Ruby 中的對象在虛擬機中以 RObject 結構體的形式存在:算法
// ruby.h struct RObject { struct RBasic basic; union { struct { uint32_t numiv; VALUE *ivptr; void *iv_index_tbl; /* shortcut for RCLASS_IV_INDEX_TBL(rb_obj_class(obj)) */ } heap; VALUE ary[ROBJECT_EMBED_LEN_MAX]; } as; }; struct RBasic { VALUE flags; const VALUE klass; }
RBasic 結構體的 klass 字段指向 RObject 所屬的類bootstrap
Ruby 對屬性訪問作了優化,對象屬性存儲在 RObject 的 as 聯合體內。若是屬性個數小於 ROBJECT_EMBED_LEN_MAX 屬性值將直接存儲在 ary 數組內,屬性索引存儲在 RClass 中(見下文);不然屬性值和索引都存儲在 heap 結構體中,訪問屬性的過程以下:根據屬性名在 iv_index_tbl 表中查詢屬性在 ivptr 中的索引,使用該索引在 ivptr 中獲取屬性,numiv 保存了屬性個數segmentfault
咱們來看一下獲取對象屬性的虛擬機指令數組
// insns.def /** @c variable @e Get value of instance variable id of self. If is_local is not 0, get value of class local variable. @j self のインスタンス変數 id の値を得る。 */ DEFINE_INSN getinstancevariable (ID id, IC ic) () (VALUE val) { val = vm_getinstancevariable(GET_SELF(), id, ic); }
vm_getinstancevariable 函數定義在 vm_insnhelper.c 文件中ruby
// vm_insnhelper.c static inline VALUE vm_getivar(VALUE obj, ID id, IC ic, struct rb_call_cache *cc, int is_attr) { #if USE_IC_FOR_IVAR ... #endif /* USE_IC_FOR_IVAR */ if (is_attr) return rb_attr_get(obj, id); return rb_ivar_get(obj, id); }
USE_IC_FOR_IVAR 裏面的代碼使用了優化算法來加快對象屬性的獲取,咱們先跳過。這樣在函數的底部判斷要獲取的是不是 attr,若是是調用 rb_attr_get 函數,不然調用 rb_ivar_get 函數函數
// variable.c VALUE rb_ivar_lookup(VALUE obj, ID id, VALUE undef) { VALUE val, *ptr; struct st_table *iv_index_tbl; uint32_t len; st_data_t index; if (SPECIAL_CONST_P(obj)) return undef; switch (BUILTIN_TYPE(obj)) { case T_OBJECT: len = ROBJECT_NUMIV(obj); ptr = ROBJECT_IVPTR(obj); iv_index_tbl = ROBJECT_IV_INDEX_TBL(obj); if (!iv_index_tbl) break; if (!st_lookup(iv_index_tbl, (st_data_t)id, &index)) break; if (len <= index) break; val = ptr[index]; if (val != Qundef) return val; break; case T_CLASS: case T_MODULE: if (RCLASS_IV_TBL(obj) && st_lookup(RCLASS_IV_TBL(obj), (st_data_t)id, &index)) return (VALUE)index; break; default: if (FL_TEST(obj, FL_EXIVAR)) return generic_ivar_get(obj, id, undef); break; } return undef; }
switch 語句根據 obj 類別分別處理,這裏的類別包括:佈局
T_OBJECT,對象學習
T_CLASS,類優化
T_MODULE,模塊
上文提到若是對象屬性個數小於 ROBJECT_EMBED_LEN_MAX,屬性索引會被存儲在 RClass 結構體中,屬性值被存儲在 RObject as 聯合體的 ary 中,ROBJECT_NUMIV,ROBJECT_IVPTR 和 ROBJECT_IV_INDEX_TBL 宏對這兩種屬性訪問進行了封裝:
// ruby.h #define ROBJECT_NUMIV(o) \ ((RBASIC(o)->flags & ROBJECT_EMBED) ? \ ROBJECT_EMBED_LEN_MAX : \ ROBJECT(o)->as.heap.numiv) #define ROBJECT_IVPTR(o) \ ((RBASIC(o)->flags & ROBJECT_EMBED) ? \ ROBJECT(o)->as.ary : \ ROBJECT(o)->as.heap.ivptr) #define ROBJECT_IV_INDEX_TBL(o) \ ((RBASIC(o)->flags & ROBJECT_EMBED) ? \ RCLASS_IV_INDEX_TBL(rb_obj_class(o)) : \ ROBJECT(o)->as.heap.iv_index_tbl)
Ruby 中的類在虛擬機中以 RClass 結構體的形式存在:
// internal.h struct RClass { struct RBasic basic; VALUE super; rb_classext_t *ptr; struct rb_id_table *m_tbl; };
結構體中第一個字段爲 basic,代表 Ruby 中的類(Class)也是一個對象(Object,也有所屬的類型)
super 字段指向父類,m_tal 爲類的方法表,ptr 指向類在虛擬機內部的私有信息(不但願做爲 API 對外公開)
爲了分析 Ruby 類變量的實現,咱們仍是從 虛擬機指令 入手:
// insns.def /** @c variable @e Get value of class variable id of klass as val. @j 現在のスコープのクラス変數 id の値を得る。 */ DEFINE_INSN getclassvariable (ID id) () (VALUE val) { val = rb_cvar_get(vm_get_cvar_base(rb_vm_get_cref(GET_EP()), GET_CFP()), id); }
代碼中的 GET_CFP,GET_EP,rb_vm_get_cref 等函數(宏)涉及到 Ruby 虛擬機運行時環境(棧幀),先略過,先看 rb_cvar_get 函數
rb_cvar_get(VALUE klass, ID id) { VALUE tmp, front = 0, target = 0; st_data_t value; tmp = klass; CVAR_LOOKUP(&value, {if (!front) front = klass; target = klass;}); ... return (VALUE)value; }
rb_ccar_get 函數的輸入參數爲類 kclass 以及屬性 id,咱們先忽略一些條件判斷和錯誤處理,核心邏輯在 CVAR_LOOKUP 宏定義裏:
// variable.c #define CVAR_FOREACH_ANCESTORS(klass, v, r) \ for (klass = cvar_front_klass(klass); klass; klass = RCLASS_SUPER(klass)) { \ if (cvar_lookup_at(klass, id, (v))) { \ r; \ } \ } #define CVAR_LOOKUP(v,r) do {\ if (cvar_lookup_at(klass, id, (v))) {r;}\ CVAR_FOREACH_ANCESTORS(klass, v, r);\ } while(0)
兩個宏定義都涉及到 cvar_lookup_at 函數,從函數命名能夠猜想該函數用於在 kclass 中查找 類屬性:
// variable.c static int cvar_lookup_at(VALUE klass, ID id, st_data_t *v) { if (!RCLASS_IV_TBL(klass)) return 0; return st_lookup(RCLASS_IV_TBL(klass), (st_data_t)id, v); }
st_lookup 是 Ruby hash map 查詢函數,RCLASS_IV_TBL 宏定義:
// internal.h #define RCLASS_EXT(c) (RCLASS(c)->ptr) #define RCLASS_IV_TBL(c) (RCLASS_EXT(c)->iv_tbl)
能夠看出類屬性存儲在 RClass 中 ptr 指向的 rb_classext_struct 結構體(iv_tbl字段)內
上文分析了對象屬性的存儲以及 Ruby 是如何獲取和設置對象屬性的。如今咱們換一種思路來分析對象方法,
咱們將跟蹤 對象方法 的生命週期,從定義,編譯成虛擬機指令到它被添加到 類結構(RClass)中
在這個過程當中咱們將使用如下 Ruby 代碼片斷:
class MyClass def my_method a + b end end
爲了查看最終生成的虛擬機指令,咱們使用 Ruby 提供的 RubyVM::InstructionSequence 類來"編譯"和"反彙編"這段類定義代碼
啓動 irb 交互式執行環境,並輸入以下代碼
irb> code=<<EOF class MyClass def my_method a + b end end EOF irb> puts RubyVM::InstructionSequence.compile(code).disasm
compile 方法用於編譯 code 並生成二進制指令序列,disasm 方法用於將"反彙編"指令序列生成 程序員 可讀的格式
== disasm: #<ISeq:<compiled>@<compiled>>================================ 0000 trace 1 ( 1) 0002 putspecialobject 3 0004 putnil 0005 defineclass :MyClass, <class:MyClass>, 0 0009 leave == disasm: #<ISeq:<class:MyClass>@<compiled>>=========================== 0000 trace 2 ( 1) 0002 trace 1 ( 2) 0004 putspecialobject 1 0006 putobject :my_method 0008 putiseq my_method 0010 opt_send_without_block <callinfo!mid:core#define_method, argc:2, ARGS_SIMPLE>, <callcache> 0013 trace 4 ( 5) 0015 leave ( 2) == disasm: #<ISeq:my_method@<compiled>>================================= 0000 trace 8 ( 2) 0002 trace 1 ( 3) 0004 putself 0005 opt_send_without_block <callinfo!mid:a, argc:0, FCALL|VCALL|ARGS_SIMPLE>, <callcache> 0008 putself 0009 opt_send_without_block <callinfo!mid:b, argc:0, FCALL|VCALL|ARGS_SIMPLE>, <callcache> 0012 opt_plus <callinfo!mid:+, argc:1, ARGS_SIMPLE>, <callcache> 0015 trace 16 ( 4) 0017 leave ( 3)
輸出結果顯示有 3 個指令序列,代碼片斷間使用 == disasm 頭來分割,這 3 個代碼片斷從上到下依次爲:
主指令序列
MyClass 類內部指令序列,用於定義字段和方法 .etc
my_method 內部指令序列
主指令序列中的 defineclass 指令用於定義一個類,Ruby 每次遇到一個類定義時都會生成一條 defineclass 指令;
MyClass 類內部指令序列雖然沒有相似的 definemethod 指令,可是有個 opt_send_without_block 指令,這個指令是 send 指令的優化版,它用於進行方法調用,後面的參數<callinfo!mid:core#define_method> 代表要調用 core#define_method 方法,你們應該可以猜到該方法就是用來添加方法到類結構裏的,在方法調用以前有 3 條 put 指令將參數壓入操做數棧:
putspecialobject,將接收 define_method 的對象壓入堆棧(相似於 this)
putobject,將操做數壓入堆棧
putiseq,將方法對應的 指令序列 壓入堆棧
如今問題來了,define_method native 方法在哪定義的的?回顧以前的文章Ruby 2.x 源代碼學習:bootstrap,Ruby 在啓動的時候會預先定義一些 C 語言實現的內置類和方法
// vm.c void Init_VM(void) { ... rb_define_method_id(klass, id_core_define_method, m_core_define_method, 2); }
繼續跟蹤 m_core_define_method 函數, 它調用 vm_define_method 最終將方法"附着"在 類結構上
// vm.c static VALUE m_core_define_method(VALUE self, VALUE sym, VALUE iseqval) { REWIND_CFP({ vm_define_method(GET_THREAD(), Qnil, SYM2ID(sym), iseqval, FALSE); }); return sym; }
咱們以一樣的方式解析一段 Ruby 代碼來分析當調用對象的方法時虛擬機內部發生了什麼:
// ruby code class MyClass def my_method a + b end end mc = MyClass.new mc.my_method
咱們忽略上文已經分析過的類及方法定義指令,直接列出方法調用的指令:
0024 trace 1 ( 7) 0026 getlocal_OP__WC__0 2 0028 opt_send_without_block <callinfo!mid:method, argc:0, ARGS_SIMPLE>, <callcache> 0031 leave
getlocal_OP__WC__0 指令是 getlocal 指令的優化指令,它將局部變量 mc (做爲 this)壓入堆棧
opt_send_without_block 指令調用 mc 的 method 方法,經過查看 vm.inc(參考Ruby 2.x 源代碼學習:YARV 虛擬機指令)來看看 send 指令都幹了些啥:
// vm.inc INSN_ENTRY(opt_send_without_block){ { VALUE val; // 獲取第 2 個指令操做數,call cache,加速方法調用的結構體,後面再仔細分析 CALL_CACHE cc = (CALL_CACHE)GET_OPERAND(2); // 獲取第 1 個指令操做數,call info,後面再仔細分析 CALL_INFO ci = (CALL_INFO)GET_OPERAND(1); // 遞增指令指針,1 個操做碼 + 2 個操做數 ADD_PC(1+2); // 編譯器 hack,起到相似指令預取的做用 PREFETCH(GET_PC()); { #line 1063 "insns.def" struct rb_calling_info calling; // 上面提到過這個版本的 send 是不帶 block 的,因此直接設置 bolcok_handler 爲 NONE calling.block_handler = VM_BLOCK_HANDLER_NONE; // 主角登場,調用該方法進行方法查找,calling.recv 很是重要!!! vm_search_method(ci, cc, calling.recv = TOPN(calling.argc = ci->orig_argc)); // 方法調用 CALL_METHOD(&calling, ci, cc); #line 1579 "vm.inc" PUSH(val); END_INSN(opt_send_without_block);}}}
爲了便於分析,特地去掉了一些宏定義。熟悉面向對象的同窗應該能猜的出來 calling.recv 至關於 Java/C++ 中的 this 引用 or 指針,因此 TOPN 宏就是爲了取得這個指針:
// vm_insnhelper.h #define TOPN(n) (*(GET_SP() - (n) - 1))
對於本例,my_method 方法參數個數爲 0,即 ci->orig_argc = 0,因此 TOPN(0) = *(GET_SP() - 1),因此 calling.recv(this) 指向棧頂第一個元素,這也就是爲何在 send 指令以前有一條 getlocal 指令:
0026 getlocal_OP__WC__0 2
最後咱們來看看 vm_search_method 方法,CLASS_OF 宏用於經過 對象(RObject)獲取對應的類(RClass),若是忘了的話能夠回到頂部看看對象在 Ruby 額你不的佈局,獲取到 klass 後調用 rb_callable_method_entry 方法查找 method id 爲 mid 的方法,這裏暫不展開 rb_callable_method_entry 方法
// vm_insnhelper.c static void vm_search_method(const struct rb_call_info *ci, struct rb_call_cache *cc, VALUE recv) { VALUE klass = CLASS_OF(recv); #if OPT_INLINE_METHOD_CACHE ... #endif cc->me = rb_callable_method_entry(klass, ci->mid); VM_ASSERT(callable_method_entry_p(cc->me)); cc->call = vm_call_general; #if OPT_INLINE_METHOD_CACHE ... #endif }