はじめに

Rubyでは$で始まる変数はグローバル変数です。しかし、リファレンスマニュアルを見てみると

$&
ローカルスコープ
$SAFE
スレッドローカルスコープ

と書かれています。今回の読解対象はこれがどのようなカラクリになっているかです。ちなみにYARVのことがわかっていることを前提に話を進めますのでわかってない人はこのページなどを見てYARVの実行モデルを理解しておいてください。

なお、今回対象としたバージョンは

ruby 1.9.0 (2008-06-14 revision 16363)

です。

グローバル変数の実装

グローバル変数実装の概要はRHGを参照してください。要約すると、

  • グローバル変数にはglobal_variable構造体が対応している
  • グローバル変数が参照されるときはglobal_variable構造体のgetterに設定されている関数が呼ばれる

ということになります。

Rubyではこの仕組みを利用して実体のないグローバル変数を定義することができます。例えば、

ruby.c

ruby_prog_init(void)
{
    rb_define_virtual_variable("$-W", opt_W_getter, 0);

opt_W_getter(VALUE val, ID id)
{
    if (ruby_verbose == Qnil)
        return INT2FIX(0);
    if (ruby_verbose == Qfalse)
        return INT2FIX(1);
    if (ruby_verbose == Qtrue)
        return INT2FIX(2);
    return Qnil;                /* not reached */
}

というようなことが可能です。*1

青木さんはグローバル変数のところを投げ遣り度85%で進行してますがこの仕組みはとてもおもしろいと思うのですけどね、まあ黒魔術なわけですが。

$で始まるスレッドローカル変数

スレッドローカルなグローバル変数の例として$SAFEを見てみましょう。まず定義されているところです。

eval.c

Init_eval(void)
{
    rb_define_virtual_variable("$SAFE", safe_getter, safe_setter);

eval_safe.c

safe_getter(void)
{
    return INT2NUM(rb_safe_level());
}

rb_safe_level(void)
{
    return GET_THREAD()->safe_level;
}

というわけでgetterの関数が呼ばれた場合、現在実行しているスレッドの構造体のsafe_levelが返されています。

余談ですがスレッド構造体なんてなかったRuby1.8まではsafe_levelはCレベルではグローバル変数でRubyのスレッド切り替えの時にスレッドコンテキストに保存されているsafe_levelが設定されるという仕組みになっていました。

$で始まるローカル変数

ローカル変数の方はもう少し複雑です。例として$&を使います。

re.c

Init_Regexp(void)
{
    rb_define_virtual_variable("$&", last_match_getter, 0);

last_match_getter(void)
{
    return rb_reg_last_match(rb_backref_get());
}

vm.c

rb_backref_get(void)
{
    return vm_svar_get(1);
}

svarという単語が出てきました。svar用の領域はスレッドスタックにローカル変数と一緒に存在しています。

         |Qnil # for n
         |Qnil # for pi
         |Qnil # for svar
dfp,lfp→|GC_GUARDED_PTR(0)

では先に進みます。

vm.c

vm_svar_get(VALUE key)
{
    rb_thread_t *th = GET_THREAD();
    return vm_cfp_svar_get(th, th->cfp, key);
}

vm_cfp_svar_get(rb_thread_t *th, rb_control_frame_t *cfp, VALUE key)
{
    cfp = vm_normal_frame(th, cfp);
    return lfp_svar_get(th, cfp ? cfp->lfp : 0, key);
}

vm_normal_frame(rb_thread_t *th, rb_control_frame_t *cfp)
{
    while (cfp->pc == 0) {
        cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp);
        if (RUBY_VM_CONTROL_FRAME_STACK_OVERFLOW_P(th, cfp)) {
            return 0;
        }
    }
    return cfp;
}

vm_insnhelper.c

lfp_svar_get(rb_thread_t *th, VALUE *lfp, VALUE key)
{
    NODE *svar = lfp_svar_place(th, lfp);

    switch (key) {
      case 0:
        return svar->u1.value;
      case 1:
        return svar->u2.value;
      default: {
        const VALUE hash = svar->u3.value;

        if (hash == Qnil) {
            return Qnil;
        }
        else {
            return rb_hash_lookup(hash, key);
        }
      }
    }
}

lfp_svar_place(rb_thread_t *th, VALUE *lfp)
{
    VALUE *svar;

    if (lfp && th->local_lfp != lfp) {
        svar = &lfp[-1];
    }
    else {
        svar = &th->local_svar;
    }
    if (NIL_P(*svar)) {
        *svar = (VALUE)NEW_IF(Qnil, Qnil, Qnil);
    }
    return (NODE *)*svar;
}

場合分けされてますが先ほどのsvar用に確保された領域を使うと思って問題ないでしょう。というわけで$&が参照された場合、ローカル変数領域のsvarが指すNODEのu2.valueが返されるようです。ちなみに、u1.valueは$_です。

とこんな感じなのですが実は$&参照時には上記のパスは通りません。YARVコードへのコンパイルでは$&が現れるとgetspecial命令が生成されます。

insns.def

DEFINE_INSN
getspecial
(VALUE key, rb_num_t type)
()
(VALUE val)
{
    val = vm_getspecial(th, GET_LFP(), key, type);
}

vm_insnhelper.c

vm_getspecial(rb_thread_t *th, VALUE *lfp, VALUE key, rb_num_t type)
{
    VALUE val;

    if (type == 0) {
        VALUE k = key;
        if (FIXNUM_P(key)) {
            k = FIX2INT(key);
        }
        val = lfp_svar_get(th, lfp, k);
    }
    else {
        VALUE backref = lfp_svar_get(th, lfp, 1);

と、lfp_svar_get関数まで処理がバイパスされています。

おわりに

今回はグローバル変数に見えるローカル変数の実装を見てきました。実装方法は以下のようになっています。

  • グローバル変数はgetterを呼び出して値を取得するいう仕組みでありgetterが頑張ってる
  • ローカル変数領域に特殊変数領域がありそこに格納されている

それではみなさんもよいコードリーディングを。


*1 書いた数日後に$-Wはvirtual variableじゃなくなりました:-<

トップ   編集 凍結解除 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2008-06-25 (水) 18:45:51 (4191d)