python对象模型 ruby_Ruby 2.x 源代码学习:对象模型

前言

本文参考了《Ruby原理剖析》并结合 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 所属的类

对象属性

Ruby 对属性访问做了优化,对象属性存储在 RObject 的 as 联合体内。如果属性个数小于 ROBJECT_EMBED_LEN_MAX 属性值将直接存储在 ary 数组内,属性索引存储在 RClass 中(见下文);否则属性值和索引都存储在 heap 结构体中,访问属性的过程如下:根据属性名在 iv_index_tbl 表中查询属性在 ivptr 中的索引,使用该索引在 ivptr 中获取属性,numiv 保存了属性个数

获取对象属性

我们来看一下获取对象属性的虚拟机指令

// 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 文件中

// 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 虚拟机内部表示

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=<

class MyClass

def my_method

a + b

end

end

EOF

irb> puts RubyVM::InstructionSequence.compile(code).disasm

compile 方法用于编译 code 并生成二进制指令序列,disasm 方法用于将"反汇编"指令序列生成 程序员 可读的格式

== disasm: #@>================================

0000 trace 1 ( 1)

0002 putspecialobject 3

0004 putnil

0005 defineclass :MyClass, , 0

0009 leave

== disasm: #@>===========================

0000 trace 2 ( 1)

0002 trace 1 ( 2)

0004 putspecialobject 1

0006 putobject :my_method

0008 putiseq my_method

0010 opt_send_without_block ,

0013 trace 4 ( 5)

0015 leave ( 2)

== disasm: #>=================================

0000 trace 8 ( 2)

0002 trace 1 ( 3)

0004 putself

0005 opt_send_without_block ,

0008 putself

0009 opt_send_without_block ,

0012 opt_plus ,

0015 trace 16 ( 4)

0017 leave ( 3)

输出结果显示有 3 个指令序列,代码片段间使用 == disasm 头来分割,这 3 个代码片段从上到下依次为:

主指令序列

MyClass 类内部指令序列,用于定义字段和方法 .etc

my_method 内部指令序列

主指令序列中的 defineclass 指令用于定义一个类,Ruby 每次遇到一个类定义时都会生成一条 defineclass 指令;

MyClass 类内部指令序列虽然没有类似的 definemethod 指令,但是有个 opt_send_without_block 指令,这个指令是 send 指令的优化版,它用于进行方法调用,后面的参数 表明要调用 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 ,

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

}

类方法

继承

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值