RubyVM::InstructionSequence.load_from_binaryから実行までの流れ

# はじめに

iseqがバイナリーデータからどのように
実行サれるかを見ていきます。

# 入力データ

puts "hello world"をInstructionSequenceでバイナリーデータにします。

~~~ ruby
iseq = RubyVM::InstructionSequence.compile(<<~SRC)
  puts "Hello world"
SRC

bin = iseq.to_binary.bytes.each_slice(32).map {|a|
 a.map{|b| b}.join(',')
}.join(",")

p bin

~~~
この結果がこちら
~~~
"89,65,82,66,3,0,0,0,2,0,0,0,212,0,0,0,0,0,0,0,1,0,0,0,4,0,0,0,148,0,0,0,196,0,0,0,120,
56,54,95,54,52,45,108,105,110,117,120,0,37,43,5,103,121,3,1,3,3,3,1,3,1,1,3,0,255,255,
255,255,255,255,255,255,1,1,3,5,5,0,0,7,41,3,1,1,1,13,73,11,1,1,1,1,1,1,1,1,63,1,3,3,3,
3,7,3,1,3,39,63,23,9,11,1,11,0,255,255,255,255,255,255,255,255,1,0,255,255,255,255,255,
255,255,255,11,3,1,1,1,1,1,1,3,5,1,1,0,0,0,85,0,0,0,241,9,0,0,69,5,21,60,99,111,109,112,
105,108,101,100,62,0,0,0,69,3,23,72,101,108,108,111,32,119,111,114,108,100,0,0,20,5,9,
112,117,116,115,0,152,0,0,0,156,0,0,0,172,0,0,0,188,0,0,0"

~~~

このデータを今度はload_from_binaryに入れて実行するコードがこちら
~~~ ruby
bin_arr = [89,65,82,66,3,0,0,0,2,0,0,0,212,0,0,0,0,0,0,0,1,0,0,0,4,0,0,0,148,0,0,0,196,0,0,0,120,56,54,95,54,52,45,108,105,110,117,120,0,37,43,5,103,121,3,1,3,3,3,1,3,1,1,3,0,255,255,255,255,255,255,255,255,1,1,3,5,5,0,0,7,41,3,1,1,1,13,73,11,1,1,1,1,1,1,1,1,63,1,3,3,3,3,7,3,1,3,39,63,23,9,11,1,11,0,255,255,255,255,255,255,255,255,1,0,255,255,255,255,255,255,255,255,11,3,1,1,1,1,1,1,3,5,1,1,0,0,0,85,0,0,0,241,9,0,0,69,5,21,60,99,111,109,112,105,108,101,100,62,0,0,0,69,3,23,72,101,108,108,111,32,119,111,114,108,100,0,0,20,5,9,112,117,116,115,0,152,0,0,0,156,0,0,0,172,0,0,0,188,0,0,0]

bin = bin_arr.pack("c*")

iseq2 = RubyVM::InstructionSequence.load_from_binary(bin)

iseq2.eval
~~~
これを実行すれば
Hello Worldと表示されます。
ではさっそくload_from_binaryから見ていきましょう。

# load_from_binaryからの流れ

次の関数でbinaryデータからiseqのデータが生成されます。

iseqw_s_load_from_binary
rb_iseq_ibf_load
    ibf_load_setup
    　　ibf_load_setup_bytes
    ibf_load_iseq
    　　rb_ibf_load_iseq_complete
    　　　　ibf_load_iseq_each
    　　　　　　ibf_load_object
    　　　　　　ibf_load_code








## headerを読み込む
ibf_load_setup_bytesでbufferをheaderにデータを読み込みます。
~~~ c
13180 ibf_load_setup_bytes(struct ibf_load *load, VALUE loader_obj, const char *bytes, size_t size)
13181 {
13182     load->loader_obj = loader_obj;
13183     load->global_buffer.buff = bytes;
13184     load->header = (struct ibf_header *)load->global_buffer.buff;
~~~
## iseq型のデータを読み込む

ibf_load_iseq_eachでiseqのbodyデータが読み込まれて
実行されるデータがibf_load_codeで読み込まれます。

~~~ c
11409 static VALUE *
11410 ibf_load_code(const struct ibf_load *load, rb_iseq_t *iseq, ibf_offset_t bytecode_offset, ibf_offset_t bytecode_size, unsigned int iseq_size)
11411 {
...
11432 
11433     for (code_index=0; code_index<iseq_size;) {
11434         /* opcode */
11435         const VALUE insn = code[code_index] = ibf_load_small_value(load, &reading_pos);
11436         const char *types = insn_op_types(insn);
11437         int op_index;
11438 
11439         code_index++;
11440 
11441         /* operands */
11442         for (op_index=0; types[op_index]; op_index++, code_index++) {
11443             const char operand_type = types[op_index];
11444             switch (operand_type) {
11445               case TS_VALUE:
11446                 {
11447                     VALUE op = ibf_load_small_value(load, &reading_pos);
11448                     VALUE v = ibf_load_object(load, op);
11449                     code[code_index] = v;
11450                     if (!SPECIAL_CONST_P(v)) {
11451                         RB_OBJ_WRITTEN(iseqv, Qundef, v);
11452                         ISEQ_MBITS_SET(mark_offset_bits, code_index);
11453                         needs_bitmap = true;
11454                     }
11455                     break;
11456                 }
11457            
~~~

一回目はココに来てstringをloadする関数ポインター(ibf_load_object_string)が呼ばれます。
~~~ c

11515               case TS_CALLDATA:
11516                 {
11517                     code[code_index] = (VALUE)cd_entries++;
11518                 }
11519                 break;
...
11536         }
11537         if (insn_len(insn) != op_index+1) {
11538             rb_raise(rb_eRuntimeError, "operand size mismatch");
11539         }
11540     }
11541 
~~~
二回目はここでrb_call_dataがcodeに入ります。

~~~ c


11542     load_body->iseq_encoded = code;
...
11560     return code;
11561 }
~~~
ここでcodeがbodyのiseq_encodedに入れられます。
これがevalでPCポインターに入り実行されていきます。




# evalからの流れ
~~~ c
3891     rb_define_method(rb_cISeq, "eval", iseqw_eval, 0);
(iseq.c)
~~~

~~~ c
1548 static VALUE
1549 iseqw_eval(VALUE self)
1550 {
1551     return rb_iseq_eval(iseqw_check(self));
1552 }
(iseq.c)
~~~

~~~ c
2621 rb_iseq_eval(const rb_iseq_t *iseq)
2622 {
2623     rb_execution_context_t *ec = GET_EC();
2624     VALUE val;
2625     vm_set_top_stack(ec, iseq);
2626     val = vm_exec(ec, true);
2627     return val;
2628 }
(vm.c)
~~~
rubyのevalからrb_iseq_evalが呼ばれます。

## iseqのencodedがPCポインターへ
~~~ c
661 static void
662 vm_set_top_stack(rb_execution_context_t *ec, const rb_iseq_t *iseq)
663 {
664     if (ISEQ_BODY(iseq)->type != ISEQ_TYPE_TOP) {
665         rb_raise(rb_eTypeError, "Not a toplevel InstructionSequence");
666     }
667 
668     /* for return */
669     vm_push_frame(ec, iseq, VM_FRAME_MAGIC_TOP | VM_ENV_FLAG_LOCAL | VM_FRAME_FLAG_FINISH, rb_ec_thread_ptr(ec)->top_self,
670                   VM_BLOCK_HANDLER_NONE,
671                   (VALUE)vm_cref_new_toplevel(ec), /* cref or me */
672                   ISEQ_BODY(iseq)->iseq_encoded, ec->cfp->sp,
673                   ISEQ_BODY(iseq)->local_table_size, ISEQ_BODY(iseq)->stack_max);
674 }

(vm.c)
~~~

vm_set_top_stackでiseqのbodyのiseq_encodedがpcポインターにセットされます

## iseqの配列をループする

~~~ c
70 static VALUE
71 vm_exec_core(rb_execution_context_t *ec, VALUE initial)
72 {
73 
...
142 
143   first:
144     INSN_DISPATCH();
145 /*****************/
146  #include "vm.inc"
147 /*****************/
148     END_INSNS_DISPATCH();
149 
150     /* unreachable */
151     rb_bug("vm_eval: unreachable");
152     goto first;
153 }
(vm_exec.c)
~~~
そのpcポインターがvm.incの中の
INSN_DISPATCH();でswitch文になり

~~~ c
146 #define INSN_DISPATCH()         \
147   while (1) {                   \
148     switch (GET_CURRENT_INSN()) {

...

150 #define END_INSNS_DISPATCH()    \
151 default:                        \
152   SDR(); \
153       rb_bug("unknown insn: %ld", GET_CURRENT_INSN());   \
154     } /* end of switch */       \
155   }   /* end of while loop */   \
156 
(vm_exec.h)
~~~

それぞれのINSN_ENTRYの中でADD_PCを読んでpcポインターを進めながら
ループして行きます。

## stringをSPポインターへ

~~~ c
/* insn putstring(str)()(val) */
INSN_ENTRY(putstring)
{
	/* ###  Declare that we have just entered into an instruction. ### */
	START_OF_ORIGINAL_INSN(putstring);
	DEBUG_ENTER_INSN("putstring");

	/* ###  Declare and assign variables. ### */
	VALUE str = (VALUE)GET_OPERAND(1);
#   define INSN_ATTR(x) attr_ ## x ## _putstring(str)
	const bool leaf = INSN_ATTR(leaf);
	VALUE val;

	/* ### Instruction preambles. ### */
	if (! leaf) ADD_PC(INSN_ATTR(width));
	SETUP_CANARY(leaf);
	COLLECT_USAGE_INSN(INSN_ATTR(bin));
	COLLECT_USAGE_OPERAND(INSN_ATTR(bin), 0, str);

	/* ### Here we do the instruction body. ### */
#   define NAME_OF_CURRENT_INSN putstring
#   line 390 "insns.def"
	{
		val = rb_ec_str_resurrect(ec, str);
	}
#   line 994 "vm.inc"
#   undef NAME_OF_CURRENT_INSN

	/* ### Instruction trailers. ### */
	CHECK_VM_STACK_OVERFLOW_FOR_INSN(VM_REG_CFP, INSN_ATTR(retn));
	CHECK_CANARY(leaf, INSN_ATTR(bin));
	INC_SP(INSN_ATTR(sp_inc));
	TOPN(0) = val;
	VM_ASSERT(!RB_TYPE_P(TOPN(0), T_NONE));
	VM_ASSERT(!RB_TYPE_P(TOPN(0), T_MOVED));
	if (leaf) ADD_PC(INSN_ATTR(width));
#   undef INSN_ATTR

	/* ### Leave the instruction. ### */
	END_INSN(putstring);
}

(vm.inc)
~~~
rb_ec_str_resurrectでstringを取り
TOPN(0) = valでそのstringがspポインターに入れられます。

## putsを呼ぶ

~~~ c
/* insn opt_send_without_block(cd)(...)(val) */
INSN_ENTRY(opt_send_without_block)
{
	/* ###  Declare that we have just entered into an instruction. ### */
	START_OF_ORIGINAL_INSN(opt_send_without_block);
	DEBUG_ENTER_INSN("opt_send_without_block");

	/* ###  Declare and assign variables. ### */
	CALL_DATA cd = (CALL_DATA)GET_OPERAND(1);
#   define INSN_ATTR(x) attr_ ## x ## _opt_send_without_block(cd)
	const bool leaf = INSN_ATTR(leaf);
	VALUE val;

	/* ### Instruction preambles. ### */
	if (! leaf) ADD_PC(INSN_ATTR(width));
	POPN(INSN_ATTR(popn));
	SETUP_CANARY(leaf);
	COLLECT_USAGE_INSN(INSN_ATTR(bin));
	COLLECT_USAGE_OPERAND(INSN_ATTR(bin), 0, cd);

	/* ### Here we do the instruction body. ### */
#   define NAME_OF_CURRENT_INSN opt_send_without_block
#   line 818 "insns.def"
	{
		VALUE bh = VM_BLOCK_HANDLER_NONE;
		val = vm_sendish(ec, GET_CFP(), cd, bh, mexp_search_method);

		if (val == Qundef) {
			RESTORE_REGS();
			NEXT_INSN();
		}
	}
#   line 2284 "vm.inc"
#   undef NAME_OF_CURRENT_INSN

	/* ### Instruction trailers. ### */
	CHECK_VM_STACK_OVERFLOW_FOR_INSN(VM_REG_CFP, INSN_ATTR(retn));
	CHECK_CANARY(leaf, INSN_ATTR(bin));
	PUSH(val);
	if (leaf) ADD_PC(INSN_ATTR(width));
#   undef INSN_ATTR

	/* ### Leave the instruction. ### */
	END_INSN(opt_send_without_block);
}
(vm.inc)
~~~
opt_send_without_block内で
POPN(INSN_ATTR(popn))　でSPを減らしてstring
を引数にしてvm_sendishを呼びrb_call_data
に設定された関数ポインターを呼びます。
これでrb_io_putsが呼ばれます。

# おわりに
ものすごくざっとload_from_binaryからevalでどのように実行されるか
を見てみました。もし間違いなどありましたらコメントで指摘して頂けると
ありがたいです。
ありがとうございました。