Rubyのオブジェクトについて

前回は院試終わって,疲れきった中での更新でした.しかも夜中の更新でした. 文章読んでも,何書いてあるかわからなかった.

とりあえず,今回の題材はrb_object

なぜコレをピックアップするかというと,gc.cのオブジェクトを辿って行くとコレに着く(たしか). 以下に構造体の中身をなくした状態のオブジェクトの構造体を載せる. ホントは絵とか書いて,説明したいけどスキャンして貼り付けまではしたくないため割愛.

あたりまえの話だけど,オープンソースのコードは共同で開発しているので,大体わかりやすい名前付けになっている. Rubyとかだと担当場所が決められてるらしいんで,他のコミッタが読めない名前が付けられてるときもあるらしい.

typedef struct rb_objspace {
    struct {
        ・・・
    } malloc_params;

    struct {
        ・・・
    } flags;

    rb_event_flag_t hook_events;
    size_t total_allocated_objects;

    rb_heap_t eden_heap;
    rb_heap_t tomb_heap; /* heap for zombies and ghosts */

    struct {
    rb_atomic_t finalizing;
    } atomic_flags;

    struct mark_func_data_struct {
        ・・・
    } *mark_func_data;

    mark_stack_t mark_stack;
    size_t marked_slots;

    struct {
        ・・・
    } heap_pages;

    st_table *finalizer_table;

    struct {
        ・・・
    } profile;
    struct gc_list *global_list;

    VALUE gc_stress_mode;

#if USE_RGENGC
    struct {
        ・・・
    } rgengc;
#if GC_ENABLE_INCREMENTAL_MARK
    struct {
        ・・・
    } rincgc;
#endif
#endif /* USE_RGENGC */
} rb_objspace_t;

で実際,みると名前だけでも大体わかる. 下にまとめる.

  • malloc_params: Rubyオブジェクトのためのmalloc()の設定
  • flags:GCの実施,マイナーGCを実施するか等をフラグにて表現(コロン以下はビット数)
  • eden_heap: rgengcのナーサリ領域(もしかしたら違うかも)
  • tomp_heap: rgengcのマチュア領域(もしかしたら違うかも)
  • heap_pages: オブジェクトを割り付けるところ(ヒープ)
  • profile :プロファイルしたいときに使うパラメータ群
  • rgengc: RgenGCの設定 世代別GC
  • rincgc: incrementalつまり,リアルタイム性を重視したGC

といった形で読める.

mark_func_dataとかよくわからない言葉もちらほら…,わからないものに関しては出てきたらなんとかしようと思います.

なんかダレてしまっている気がするため,今度はkobitoとかにmちゃんと書き写したやつをコピペするようにシます

RubyのGCについて

最近はGC周りのことについての知識欲求が強い. ちょこっとCRuby(MRI)の内部を触ったので覚え書き程度に書き残す. 何で検索にひっかかってるのかよく見ていないが,ちょこちょこアクセスがあったのが不思議に感じた(記事を書くキッカケ).

ソースを触る環境について

MRIをソースからビルドする場合,macbrewを使っているならばパスの通り方を気をつけた方がよい. brewでlibtoolとかが入ってるとコンパイルが通らないため,注意. ソースからビルドに関しては上記以外は特に気にする必要はないが,make minirubyとかで良い.

ヒープについて

MRIのヒープは昔の記事で色々見かけるが,特に大きな変更はされていない. つまり,heapsと呼ばれるheapのリストの集まりが存在し,さらにheapの中にslotと呼ばれるobjectのリスト集合が存在する. 言っている意味がわからない場合はAoki Mineroさんのwebページを参考にすると良い.

イメージは上で良いが,実際にはheaps変数は存在しない. 実際は*objspaceでヒープヘッダを押さえていて,内部にはヒープのエントリやオプションが記載されている.

profile部分の情報をカットした構造体定義が以下になる. さらっと見るだけでもRGenGCやインクリメンタルGCの表記が見え不穏さを感じる…… さてRubyは保守的なGCとどうやって接しているのか…

みたいな形で記事は次へ(不定期)

少し脇道に逸れるが,ソース内の命名方法が特殊で変数っぽい小文字の場合でもマクロだったりするときがある. つまりソースを読んでいるときに急に変な名前が飛び出すことがある. 個人的にはマクロは大文字が良いなあと思ってしまう.あとは変換すれば良い話だがインデントが水平タブとスペースが混在しているのが気になる… インデントに関してはもしかして自分の環境だけかもしれない.

typedef struct rb_objspace {
    struct {
    size_t limit;
    size_t increase;
#if MALLOC_ALLOCATED_SIZE
    size_t allocated_size;
    size_t allocations;
#endif
    } malloc_params;

    struct {
    unsigned int mode : 2;
    unsigned int immediate_sweep : 1;
    unsigned int dont_gc : 1;
    unsigned int dont_incremental : 1;
    unsigned int during_gc : 1;
    unsigned int gc_stressful: 1;
    unsigned int has_hook: 1;
#if USE_RGENGC
    unsigned int during_minor_gc : 1;
#endif
#if GC_ENABLE_INCREMENTAL_MARK
    unsigned int during_incremental_marking : 1;
#endif
    } flags;

    rb_event_flag_t hook_events;
    size_t total_allocated_objects;

    rb_heap_t eden_heap;
    rb_heap_t tomb_heap; /* heap for zombies and ghosts */

    struct {
    rb_atomic_t finalizing;
    } atomic_flags;

    struct mark_func_data_struct {
    void *data;
    void (*mark_func)(VALUE v, void *data);
    } *mark_func_data;

    mark_stack_t mark_stack;
    size_t marked_slots;

    struct {
    struct heap_page **sorted;
    size_t allocated_pages;
    size_t allocatable_pages;
    size_t sorted_length;
    RVALUE *range[2];
    size_t freeable_pages;

    /* final */
    size_t final_slots;
    VALUE deferred_final;
    } heap_pages;

    st_table *finalizer_table;

    struct {
    int run;
    int latest_gc_info;
    gc_profile_record *records;
    gc_profile_record *current_record;
    size_t next_index;
    size_t size;
    double invoke_time;

#if USE_RGENGC
    size_t minor_gc_count;
    size_t major_gc_count;

#endif /* USE_RGENGC */

    /* temporary profiling space */
    double gc_sweep_start_time;
    size_t total_allocated_objects_at_gc_start;
    size_t heap_used_at_gc_start;

    /* basic statistics */
    size_t count;
    size_t total_freed_objects;
    size_t total_allocated_pages;
    size_t total_freed_pages;
    } profile;
    struct gc_list *global_list;

    VALUE gc_stress_mode;

#if USE_RGENGC
    struct {
    VALUE parent_object;
    int need_major_gc;
    size_t last_major_gc;
    size_t uncollectible_wb_unprotected_objects;
    size_t uncollectible_wb_unprotected_objects_limit;
    size_t old_objects;
    size_t old_objects_limit;

#if RGENGC_ESTIMATE_OLDMALLOC
    size_t oldmalloc_increase;
    size_t oldmalloc_increase_limit;
#endif

    } rgengc;
#if GC_ENABLE_INCREMENTAL_MARK
    struct {
    size_t pooled_slots;
    size_t step_slots;
    } rincgc;
#endif
#endif /* USE_RGENGC */
} rb_objspace_t;

現状把握(2)

現状把握のために書き記す.
最初のこのブログのモチベとして(1)グループ開発の知識, (2)LLVMのPASSの作成 この2つを主に考えていた.

現状(2)は簡単なPASSではあるが, ノルマ的な意味では達成していると考えている.

(1)は やはり, Git, GitHubの分散ファイル管理システムが主流のようだ(※身内調査).

githubはcloneなどで使っていたためアカウントは持っているがいまいちコマンドとか用語とかがわからない。。。。。
そのうち, このブログでも取り上げるかもしれないけど, 他のブログの人や本の丸パクリはやっぱりしたくない....



そんな話はとりあえずおいておく.

今後の目標としては, PASSで使われるC++の知識を簡単にだが忘備録として残していこうと思っている.

個人的な考えは"気楽に"ということを目指しているので, とくにやらないかもしれない.

そしてこのブログはだんだん更新が遅くなるだろう.........(笑)

PassのMakefile

とりあえずのMakefile

#Makefile
#OPTOBJS = $(wildcard ./*.ll)
OPTOBJS = mem2.ll
BASE = $(basename $(wildcard *.cpp))
OPTION = $(join -, $(BASE))
TARGET = $(join ./, $(BASE).so)
CXX = g++
CXXFLAGS = -I/usr/local/llvm/include -std=c++11 `llvm-config --cflags --ldflags --libs` -fno-rtti -c -o
LLFILE = $(basename $(wildcard *.c))

.SUFFIXES:.cpp .o .so .c .ll

#
all:$(TARGET)
.o.so:
	$(CXX) -g -shared $< -o $@
.cpp.o:
	$(CXX) -g $< $(CXXFLAGS) $@

#
prod:mem2.ll
mem2.ll:$(LLFILE).ll
	opt -S -mem2reg $(LLFILE).ll -o mem2.ll 
.c.ll:
	clang -emit-llvm -S -o $(LLFILE).ll $(LLFILE).c


run: $(TARGET)
	opt -load $(TARGET) $(OPTION)  mem2.ll -S > /dev/null 

clean:
	rm -rf $(TARGET) *.ll

あんまMakefileを作ってきてないので関数とか, 内部マクロとかもっと使う方がスマートにかけるのかもしれないが,
いまは, そっちにばっか気を使うわけにも行かないので, 妥協する.



ちなみに使い方は

  • make --------- cppファイルを探してきて, .soの共有ライブラリになる.
  • make prod ----- Passに食わせたい, cファイルを.ll形式にしてくれる.
  • make run ------ make prodで作ったファイルをPassに食わせる.
  • make clean ------ 中間ファイルの削除としている.

これを書いているときに/dev/nullに気になって調べたら, 出力ストリームを捨てるときの常套手段らしい.
まだまだ, この世界には慣れてないことが多くて, こういう開発者のよく使う手段は覚えていきたい.

Passを作った時のコマンドメモ(2)

つぎに任意のフォルダでつぎのhello.cppを作成する.

#include "llvm/Pass.h"
#include "llvm/IR/Function.h"
#include "llvm/Support/raw_ostream.h"

using namespace llvm;

namespace {
  struct Hello : public FunctionPass {
    static char ID;
    Hello() : FunctionPass(ID) {}

    bool runOnFunction(Function &F) override {
      errs() << "Hello: ";
      errs().write_escaped(F.getName()) << '\n';
      return false;
    }
  };
}

char Hello::ID = 0;
static RegisterPass<Hello> X("hello", "Hello World Pass", false, false);

つぎのコマンドを使う.

g++ -g ./hello.cpp -I/usr/local/llvm/include -std=c++11 -D__STDC_CONSTANT_MACTOS -D__STDC_LIMIT_MACTOS `llm-config --cflags --ldflags -libs` -fno-rtti -c -o hello.o

つぎに共有ライブラリの作成 

g++ -shared -fpic -o ./hello.so ./hello.o

ここで, test.cというファイルを作成しておく.

#include <stdio.h>
int main(void){
 printf("Hello\n");
 return 0;
}

test.cをコマンドでtest.bcをつくる

clang -O3 -emit-llvm test.c -c -o test.bc

これを食わせる,

opt -load ./hello.so -hello < test.bc > /dev/null

これでつぎの出力が得られるはず

Hello: main

これでひとまず, 終.
一応注意としては, -fno-rttiや-std=c++11とかのオプションをちゃんと付ける.
そしてちゃんとPATHを通したり, -Iオプションでincludeのヘッダー場所をしているするのがポイントというか自分がハマったポイントでした.

Passを作った時のコマンドメモ(1)

いちおう完成したときのコマンドをめもりました.
OS: Ubuntu 15.04
LLVM clang : 3.6.2
GCC version : 4.9.2

まずは,LLVMのビルド方法から今回はウェブページから必要なファイル群をダウンロードします.

LLVM Download Page

ここから

  • Clang source code
  • LLVM source code
  • Compiler RT source code

をクリックしてダウンロード.

そうすると3つのtar.xzが手に入る.
その3つを展開する.

tar Jxvf [ファイル名]

その後, つぎのような階層に入れる.

mv llvm-3.6.2.src ../
mv cfe-3.6.2.src ../llm-3.6.2.src/tools/clang
mv compiler-rt-3.6.2.src ../llm-3.6.2.src/projects/compiler-rt

その後任意のディレクトにビルド用ディレクトリを作る.

mkdir build 
cd build

makeを使って展開, installをしてゆく

../llvm-3.6.2.src/configure --prefix=/usr/local/llvm --enable-optimized
make -j2 // j2で2つの並列で動作する.
sudo make install // make check で上手く出来たか試してみるのもあり.

つぎが一番重要です.(自分がよく忘れる)

zshbashどっちでもいいが, 自分はzshを使っているので.zshrcにPATHを書き込む.

PATH=$PATH:/usr/local/llvm/bin

このあと,

source ~/.zshrc

でPATHを適用させる.

とりあえずの状況

環境

VisualBox上だが,

  • Ubuntu 14.04
  • LLVM 3.6.2 (あんま調べていないたぶん最新版)
  • gccとか必要なソフトウェア類は最新版にしている.

この状況で
Writing an LLVM Pass — LLVM 3.8 documentation
のサンプルコードのPassをコンパイルした.

#include "llvm/Pass.h"
#include "llvm/IR/Function.h"
#include "llvm/Support/raw_ostream.h"

using namespace llvm;

namespace {
  struct Hello : public FunctionPass {
    static char ID;
    Hello() : FunctionPass(ID) {}

    bool runOnFunction(Function &F) override {
      errs() << "Hello: ";
      errs().write_escaped(F.getName()) << '\n';
      return false;
    }
  };
}

char Hello::ID = 0;
static RegisterPass<Hello> X("hello", "Hello World Pass", false, false);

コンパイルのコマンドを入力

g++ -g ./hello.cpp -c - o hello.o

この時, ヘッダファイルが見つからないというエラーが出た.(いまエラーファイルがないので, あとから付け足す予定)

回避方法として -IオプションをつけてLLVMをbuildしたときにできたヘッダーディレクトリ郡の絶対パスを付け足してコンパイルした.
そうしたところ, さらになんかでた.(これも後で付け足します.)

それにも, なんかオプションつけた(あとで(略)) 

その次に

g++ -g shared -o ./hello.so hello.o

で動的リンクをして共有ライブラリを作成した.

で, optコマンドを使って先ほどの共有ライブラリをロードしようと試みたが, "このファイルは動的リンクされていません"的なエラーが出力.
動的リンクができてないらしい.
fileコマンドをつかってしらべてみると, 確かにその旨を伝える部分が存在しない.

なんかいろいろ調べていたら, -fno-rttiをオプションとして付けなければならないらしい.
これをつけて実行したら動的リンクに成功.

最終的にoptコマンドでhello.cpp Passを動かすことができた.


一応今の現状はこんな感じです. 忘備録をつくっているのに

エラーとかコマンドとか

をメモる癖がないので, 非常にわかりにくいものとなってしまった.
なるべく次からは気を付けたいと思う.

それと, 毎日更新予定でいたが, あんまり進捗も産めなさそうなのでどうしようか悩み中.

気ままにLLVM系の英語のニュースでも訳そうかなって思っている.