類別 RubyVM::InstructionSequence
InstructionSequence
類別表示 MRI 中使用的虛擬機器編譯的指令序列。並非所有 Ruby 實作都會實作這個類別,而對於實作它的實作,方法的定義和方法的行為可能會在任何版本中變更。
有了它,您可以取得組成方法或程序的指令的控制權,將 Ruby 程式碼字串編譯成 VM 指令,以及將指令序列反組譯成字串以方便檢查。如果您想了解 YARV 的運作方式,它非常有用,但它也允許您控制 Ruby iseq 編譯器的各種設定。
您可以在 Ruby 原始碼中的 insns.def
中找到 VM 指令的原始碼。
指令序列結果幾乎肯定會隨著 Ruby 的變更而變更,因此此文件中的範例輸出可能與您看到的不同。
當然,這個類別是 MRI 特有的。
公開類別方法
採用 source
,它可以是 Ruby 程式碼字串,或包含 Ruby 原始碼的開啟 File
物件。
選擇性地採用 file
、path
和 line
,它們描述 source
中 Ruby 程式碼的檔案路徑、真實路徑和第一行號碼,這些是附加到傳回 iseq
的元資料。
file
用於「__FILE__」和例外回溯。path
用於 require_relative
基底。建議這些應該是相同的完整路徑。
options
,可以是 true
、false
或 Hash
,用於修改 Ruby iseq 編譯器的預設行為。
有關有效編譯選項的詳細資訊,請參閱 ::compile_option=
。
RubyVM::InstructionSequence.compile("a = 1 + 2") #=> <RubyVM::InstructionSequence:<compiled>@<compiled>> path = "test.rb" RubyVM::InstructionSequence.compile(File.read(path), path, File.expand_path(path)) #=> <RubyVM::InstructionSequence:<compiled>@test.rb:1> file = File.open("test.rb") RubyVM::InstructionSequence.compile(file) #=> <RubyVM::InstructionSequence:<compiled>@<compiled>:1> path = File.expand_path("test.rb") RubyVM::InstructionSequence.compile(File.read(path), path, path) #=> <RubyVM::InstructionSequence:<compiled>@/absolute/path/to/test.rb:1>
static VALUE iseqw_s_compile(int argc, VALUE *argv, VALUE self) { VALUE src, file = Qnil, path = Qnil, line = Qnil, opt = Qnil; int i; i = rb_scan_args(argc, argv, "1*:", &src, NULL, &opt); if (i > 4+NIL_P(opt)) rb_error_arity(argc, 1, 5); switch (i) { case 5: opt = argv[--i]; case 4: line = argv[--i]; case 3: path = argv[--i]; case 2: file = argv[--i]; } if (NIL_P(file)) file = rb_fstring_lit("<compiled>"); if (NIL_P(path)) path = file; if (NIL_P(line)) line = INT2FIX(1); Check_Type(path, T_STRING); Check_Type(file, T_STRING); return iseqw_new(rb_iseq_compile_with_option(src, file, path, line, opt)); }
採用 file
,一個包含 Ruby 來源檔案位置的 String
,讀取、剖析和編譯檔案,並傳回已編譯的 InstructionSequence
,其中設定了來源位置的元資料。
選擇性採用 options
,可以是 true
、false
或 Hash
,以修改 Ruby iseq 編譯器的預設行為。
有關有效編譯選項的詳細資訊,請參閱 ::compile_option=
。
# /tmp/hello.rb puts "Hello, world!" # elsewhere RubyVM::InstructionSequence.compile_file("/tmp/hello.rb") #=> <RubyVM::InstructionSequence:<main>@/tmp/hello.rb>
static VALUE iseqw_s_compile_file(int argc, VALUE *argv, VALUE self) { VALUE file, opt = Qnil; VALUE parser, f, exc = Qnil, ret; rb_ast_t *ast; rb_compile_option_t option; int i; i = rb_scan_args(argc, argv, "1*:", &file, NULL, &opt); if (i > 1+NIL_P(opt)) rb_error_arity(argc, 1, 2); switch (i) { case 2: opt = argv[--i]; } FilePathValue(file); file = rb_fstring(file); /* rb_io_t->pathv gets frozen anyways */ f = rb_file_open_str(file, "r"); rb_execution_context_t *ec = GET_EC(); VALUE v = rb_vm_push_frame_fname(ec, file); parser = rb_parser_new(); rb_parser_set_context(parser, NULL, FALSE); ast = (rb_ast_t *)rb_parser_load_file(parser, file); if (!ast->body.root) exc = GET_EC()->errinfo; rb_io_close(f); if (!ast->body.root) { rb_ast_dispose(ast); rb_exc_raise(exc); } make_compile_option(&option, opt); ret = iseqw_new(rb_iseq_new_with_opt(&ast->body, rb_fstring_lit("<main>"), file, rb_realpath_internal(Qnil, file, 1), 1, NULL, 0, ISEQ_TYPE_TOP, &option)); rb_ast_dispose(ast); rb_vm_pop_frame(ec); RB_GC_GUARD(v); return ret; }
static VALUE iseqw_s_compile_file_prism(int argc, VALUE *argv, VALUE self) { VALUE file = Qnil, opt = Qnil; int i; i = rb_scan_args(argc, argv, "1*:", &file, NULL, &opt); if (i > 1+NIL_P(opt)) rb_error_arity(argc, 1, 5); switch (i) { case 2: opt = argv[--i]; } FilePathValue(file); file = rb_fstring(file); /* rb_io_t->pathv gets frozen anyways */ pm_string_t input; pm_string_mapped_init(&input, RSTRING_PTR(file)); pm_options_t options = { 0 }; pm_options_filepath_set(&options, RSTRING_PTR(file)); pm_parser_t parser; pm_parser_init(&parser, pm_string_source(&input), pm_string_length(&input), &options); rb_iseq_t *iseq = iseq_alloc(); iseqw_s_compile_prism_compile(&parser, opt, iseq, file, rb_realpath_internal(Qnil, file, 1), 1); pm_parser_free(&parser); pm_string_free(&input); pm_options_free(&options); return iseqw_new(iseq); }
傳回 Ruby iseq 編譯器所使用的預設選項雜湊。
有關詳細資訊,請參閱 InstructionSequence.compile_option=
。
static VALUE iseqw_s_compile_option_get(VALUE self) { return make_compile_option_value(&COMPILE_OPTION_DEFAULT); }
設定 Ruby iseq 編譯器中各種最佳化的預設值。
options
的可能值包括 true
(啟用所有選項)、false
(停用所有選項)和 nil
(不變更任何選項)。
您也可以傳遞一個 options
的 Hash
,其中包含您想要變更的選項,雜湊中不存在的任何選項都將保持不變。
可以設定為 true
或 false
的可能選項名稱(options
中的鍵)包括
-
:inline_const_cache
-
:instructions_unification
-
:operands_unification
-
:peephole_optimization
-
:specialized_instruction
-
:tailcall_optimization
此外,:debug_level
可以設定為整數。
這些預設選項可以透過將上述任何值傳遞為 options
參數給 ::new
、::compile
和 ::compile_file
,來覆寫 iseq 編譯器的單一執行。
static VALUE iseqw_s_compile_option_set(VALUE self, VALUE opt) { rb_compile_option_t option; make_compile_option(&option, opt); COMPILE_OPTION_DEFAULT = option; return opt; }
static VALUE iseqw_s_compile_prism(int argc, VALUE *argv, VALUE self) { VALUE src, file = Qnil, path = Qnil, line = Qnil, opt = Qnil; int i; i = rb_scan_args(argc, argv, "1*:", &src, NULL, &opt); if (i > 4+NIL_P(opt)) rb_error_arity(argc, 1, 5); switch (i) { case 5: opt = argv[--i]; case 4: line = argv[--i]; case 3: path = argv[--i]; case 2: file = argv[--i]; } if (NIL_P(file)) file = rb_fstring_lit("<compiled>"); if (NIL_P(path)) path = file; if (NIL_P(line)) line = INT2FIX(1); Check_Type(path, T_STRING); Check_Type(file, T_STRING); pm_options_t options = { 0 }; pm_options_filepath_set(&options, RSTRING_PTR(file)); int start_line = NUM2INT(line); pm_options_line_set(&options, start_line); pm_parser_t parser; if (RB_TYPE_P(src, T_FILE)) { FilePathValue(src); file = rb_fstring(src); /* rb_io_t->pathv gets frozen anyways */ pm_string_t input; pm_string_mapped_init(&input, RSTRING_PTR(file)); pm_parser_init(&parser, pm_string_source(&input), pm_string_length(&input), &options); } else { pm_parser_init(&parser, (const uint8_t *) RSTRING_PTR(src), RSTRING_LEN(src), &options); } rb_iseq_t *iseq = iseq_alloc(); iseqw_s_compile_prism_compile(&parser, opt, iseq, file, path, start_line); pm_parser_free(&parser); pm_options_free(&options); return iseqw_new(iseq); }
接收一個 Method
或 Proc
物件的 body
,並回傳一個包含 body
的人類可讀指令的 String
。
對於 Method
物件
# /tmp/method.rb def hello puts "hello, world" end puts RubyVM::InstructionSequence.disasm(method(:hello))
產生
== disasm: <RubyVM::InstructionSequence:hello@/tmp/method.rb>============ 0000 trace 8 ( 1) 0002 trace 1 ( 2) 0004 putself 0005 putstring "hello, world" 0007 send :puts, 1, nil, 8, <ic:0> 0013 trace 16 ( 3) 0015 leave ( 2)
對於 Proc
# /tmp/proc.rb p = proc { num = 1 + 2 } puts RubyVM::InstructionSequence.disasm(p)
產生
== disasm: <RubyVM::InstructionSequence:block in <main>@/tmp/proc.rb>=== == catch table | catch type: redo st: 0000 ed: 0012 sp: 0000 cont: 0000 | catch type: next st: 0000 ed: 0012 sp: 0000 cont: 0012 |------------------------------------------------------------------------ local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1] s1) [ 2] num 0000 trace 1 ( 1) 0002 putobject 1 0004 putobject 2 0006 opt_plus <ic:1> 0008 dup 0009 setlocal num, 0 0012 leave
static VALUE iseqw_s_disasm(VALUE klass, VALUE body) { VALUE iseqw = iseqw_s_of(klass, body); return NIL_P(iseqw) ? Qnil : rb_iseq_disasm(iseqw_check(iseqw)); }
接收一個 Method
或 Proc
物件的 body
,並回傳一個包含 body
的人類可讀指令的 String
。
對於 Method
物件
# /tmp/method.rb def hello puts "hello, world" end puts RubyVM::InstructionSequence.disasm(method(:hello))
產生
== disasm: <RubyVM::InstructionSequence:hello@/tmp/method.rb>============ 0000 trace 8 ( 1) 0002 trace 1 ( 2) 0004 putself 0005 putstring "hello, world" 0007 send :puts, 1, nil, 8, <ic:0> 0013 trace 16 ( 3) 0015 leave ( 2)
對於 Proc
# /tmp/proc.rb p = proc { num = 1 + 2 } puts RubyVM::InstructionSequence.disasm(p)
產生
== disasm: <RubyVM::InstructionSequence:block in <main>@/tmp/proc.rb>=== == catch table | catch type: redo st: 0000 ed: 0012 sp: 0000 cont: 0000 | catch type: next st: 0000 ed: 0012 sp: 0000 cont: 0012 |------------------------------------------------------------------------ local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1] s1) [ 2] num 0000 trace 1 ( 1) 0002 putobject 1 0004 putobject 2 0006 opt_plus <ic:1> 0008 dup 0009 setlocal num, 0 0012 leave
static VALUE iseqw_s_disasm(VALUE klass, VALUE body) { VALUE iseqw = iseqw_s_of(klass, body); return NIL_P(iseqw) ? Qnil : rb_iseq_disasm(iseqw_check(iseqw)); }
從 RubyVM::InstructionSequence.to_binary
建立的二進位格式 String
物件載入一個 iseq 物件。
這個載入器沒有驗證器,因此載入有問題或已修改的二進位會造成嚴重的問題。
您不應該載入其他使用者提供的二進位資料。您應該使用自己轉譯的二進位資料。
static VALUE iseqw_s_load_from_binary(VALUE self, VALUE str) { return iseqw_new(rb_iseq_ibf_load(str)); }
載入嵌入在二進位格式 String
物件中的額外資料。
static VALUE iseqw_s_load_from_binary_extra_data(VALUE self, VALUE str) { return rb_iseq_ibf_load_extra_data(str); }
採用 source
,它可以是 Ruby 程式碼字串,或包含 Ruby 原始碼的開啟 File
物件。
選擇性地採用 file
、path
和 line
,它們描述 source
中 Ruby 程式碼的檔案路徑、真實路徑和第一行號碼,這些是附加到傳回 iseq
的元資料。
file
用於「__FILE__」和例外回溯。path
用於 require_relative
基底。建議這些應該是相同的完整路徑。
options
,可以是 true
、false
或 Hash
,用於修改 Ruby iseq 編譯器的預設行為。
有關有效編譯選項的詳細資訊,請參閱 ::compile_option=
。
RubyVM::InstructionSequence.compile("a = 1 + 2") #=> <RubyVM::InstructionSequence:<compiled>@<compiled>> path = "test.rb" RubyVM::InstructionSequence.compile(File.read(path), path, File.expand_path(path)) #=> <RubyVM::InstructionSequence:<compiled>@test.rb:1> file = File.open("test.rb") RubyVM::InstructionSequence.compile(file) #=> <RubyVM::InstructionSequence:<compiled>@<compiled>:1> path = File.expand_path("test.rb") RubyVM::InstructionSequence.compile(File.read(path), path, path) #=> <RubyVM::InstructionSequence:<compiled>@/absolute/path/to/test.rb:1>
static VALUE iseqw_s_compile(int argc, VALUE *argv, VALUE self) { VALUE src, file = Qnil, path = Qnil, line = Qnil, opt = Qnil; int i; i = rb_scan_args(argc, argv, "1*:", &src, NULL, &opt); if (i > 4+NIL_P(opt)) rb_error_arity(argc, 1, 5); switch (i) { case 5: opt = argv[--i]; case 4: line = argv[--i]; case 3: path = argv[--i]; case 2: file = argv[--i]; } if (NIL_P(file)) file = rb_fstring_lit("<compiled>"); if (NIL_P(path)) path = file; if (NIL_P(line)) line = INT2FIX(1); Check_Type(path, T_STRING); Check_Type(file, T_STRING); return iseqw_new(rb_iseq_compile_with_option(src, file, path, line, opt)); }
傳回包含給定程序或方法的指令序列。
例如,使用 irb
# a proc > p = proc { num = 1 + 2 } > RubyVM::InstructionSequence.of(p) > #=> <RubyVM::InstructionSequence:block in irb_binding@(irb)> # for a method > def foo(bar); puts bar; end > RubyVM::InstructionSequence.of(method(:foo)) > #=> <RubyVM::InstructionSequence:foo@(irb)>
# /tmp/iseq_of.rb def hello puts "hello, world" end $a_global_proc = proc { str = 'a' + 'b' } # in irb > require '/tmp/iseq_of.rb' # first the method hello > RubyVM::InstructionSequence.of(method(:hello)) > #=> #<RubyVM::InstructionSequence:0x007fb73d7cb1d0> # then the global proc > RubyVM::InstructionSequence.of($a_global_proc) > #=> #<RubyVM::InstructionSequence:0x007fb73d7caf78>
static VALUE iseqw_s_of(VALUE klass, VALUE body) { const rb_iseq_t *iseq = NULL; if (rb_obj_is_proc(body)) { iseq = vm_proc_iseq(body); if (!rb_obj_is_iseq((VALUE)iseq)) { iseq = NULL; } } else if (rb_obj_is_method(body)) { iseq = rb_method_iseq(body); } else if (rb_typeddata_is_instance_of(body, &iseqw_data_type)) { return body; } return iseq ? iseqw_new(iseq) : Qnil; }
公開實例方法
傳回這個指令序列的絕對路徑。
如果 iseq 是從字串評估的,則傳回 nil
。
例如,使用 ::compile_file
# /tmp/method.rb def hello puts "hello, world" end # in irb > iseq = RubyVM::InstructionSequence.compile_file('/tmp/method.rb') > iseq.absolute_path #=> /tmp/method.rb
static VALUE iseqw_absolute_path(VALUE self) { return rb_iseq_realpath(iseqw_check(self)); }
傳回這個指令序列的基礎標籤。
例如,使用 irb
iseq = RubyVM::InstructionSequence.compile('num = 1 + 2') #=> <RubyVM::InstructionSequence:<compiled>@<compiled>> iseq.base_label #=> "<compiled>"
# /tmp/method.rb def hello puts "hello, world" end # in irb > iseq = RubyVM::InstructionSequence.compile_file('/tmp/method.rb') > iseq.base_label #=> <main>
static VALUE iseqw_base_label(VALUE self) { return rb_iseq_base_label(iseqw_check(self)); }
傳回指令序列作為人類可讀形式的 String
。
puts RubyVM::InstructionSequence.compile('1 + 2').disasm
產生
== disasm: <RubyVM::InstructionSequence:<compiled>@<compiled>>========== 0000 trace 1 ( 1) 0002 putobject 1 0004 putobject 2 0006 opt_plus <ic:1> 0008 leave
static VALUE iseqw_disasm(VALUE self) { return rb_iseq_disasm(iseqw_check(self)); }
傳回指令序列作為人類可讀形式的 String
。
puts RubyVM::InstructionSequence.compile('1 + 2').disasm
產生
== disasm: <RubyVM::InstructionSequence:<compiled>@<compiled>>========== 0000 trace 1 ( 1) 0002 putobject 1 0004 putobject 2 0006 opt_plus <ic:1> 0008 leave
反覆處理所有直接子指令序列。反覆處理順序由實作/版本定義,因此人們不應該依賴順序。
static VALUE iseqw_each_child(VALUE self) { const rb_iseq_t *iseq = iseqw_check(self); iseq_iterate_children(iseq, yield_each_children, NULL); return self; }
評估指令序列並傳回結果。
RubyVM::InstructionSequence.compile("1 + 2").eval #=> 3
static VALUE iseqw_eval(VALUE self) { return rb_iseq_eval(iseqw_check(self)); }
傳回載入指令序列的第一個來源行號。
例如,使用 irb
iseq = RubyVM::InstructionSequence.compile('num = 1 + 2') #=> <RubyVM::InstructionSequence:<compiled>@<compiled>> iseq.first_lineno #=> 1
static VALUE iseqw_first_lineno(VALUE self) { return rb_iseq_first_lineno(iseqw_check(self)); }
static VALUE iseqw_inspect(VALUE self) { const rb_iseq_t *iseq = iseqw_check(self); const struct rb_iseq_constant_body *const body = ISEQ_BODY(iseq); VALUE klass = rb_class_name(rb_obj_class(self)); if (!body->location.label) { return rb_sprintf("#<%"PRIsVALUE": uninitialized>", klass); } else { return rb_sprintf("<%"PRIsVALUE":%"PRIsVALUE"@%"PRIsVALUE":%d>", klass, body->location.label, rb_iseq_path(iseq), FIX2INT(rb_iseq_first_lineno(iseq))); } }
傳回此指令序列的標籤。
<main>
如果在頂層,<compiled>
如果從字串評估。
例如,使用 irb
iseq = RubyVM::InstructionSequence.compile('num = 1 + 2') #=> <RubyVM::InstructionSequence:<compiled>@<compiled>> iseq.label #=> "<compiled>"
# /tmp/method.rb def hello puts "hello, world" end # in irb > iseq = RubyVM::InstructionSequence.compile_file('/tmp/method.rb') > iseq.label #=> <main>
static VALUE iseqw_label(VALUE self) { return rb_iseq_label(iseqw_check(self)); }
傳回此指令序列的路徑。
<compiled>
如果 iseq 從字串評估。
例如,使用 irb
iseq = RubyVM::InstructionSequence.compile('num = 1 + 2') #=> <RubyVM::InstructionSequence:<compiled>@<compiled>> iseq.path #=> "<compiled>"
# /tmp/method.rb def hello puts "hello, world" end # in irb > iseq = RubyVM::InstructionSequence.compile_file('/tmp/method.rb') > iseq.path #=> /tmp/method.rb
static VALUE iseqw_path(VALUE self) { return rb_iseq_path(iseqw_check(self)); }
如果可用,它會傳回已記錄的腳本行。腳本行不限於 iseq 範圍,而是來源檔案的整行。
請注意,這是 Ruby 內部使用、偵錯和研究的 API。請勿將其用於任何其他目的。相容性無法保證。
static VALUE iseqw_script_lines(VALUE self) { const rb_iseq_t *iseq = iseqw_check(self); return ISEQ_BODY(iseq)->variable.script_lines; }
傳回 陣列
,其中包含 14 個元素,代表具有下列資料的指令序列
- magic
-
識別資料格式的字串。永遠是
YARVInstructionSequence/SimpleDataFormat
。 - major_version
-
指令序列的主要版本。
- minor_version
-
指令序列的次要版本。
- format_type
-
識別資料格式的數字。永遠是 1。
- misc
-
包含下列雜湊
:arg_size
-
方法或區塊所採取的總引數數目(如果 iseq 不代表方法或區塊,則為 0)
:local_size
-
區域變數數目 + 1
:stack_max
-
用於計算會擲出
SystemStackError
的堆疊深度。
label
-
此指令序列所屬的內容(區塊、方法、類別、模組等)的名稱。
<main>
如果在頂層,<compiled>
如果從字串評估。 path
-
載入指令序列的 Ruby 檔案的相對路徑。
<compiled>
如果 iseq 從字串評估。 absolute_path
-
載入指令序列的 Ruby 檔案的絕對路徑。
如果 iseq 是從字串評估的,則傳回
nil
。 first_lineno
-
載入指令序列的第一個來源行號。
- type
-
指令序列的類型。
有效值為
:top
、:method
、:block
、:class
、:rescue
、:ensure
、:eval
、:main
和plain
。 - locals
-
包含所有參數和局部變數名稱的陣列,以符號表示。
- params
-
包含參數資訊的
Hash
物件。可以在
vm_core.h
中找到這些值的更多資訊。 - catch_table
-
例外和控制流程運算子(rescue、next、redo、break 等)的清單。
- bytecode
-
包含組成指令序列主體的指令名稱和運算元的陣列陣列。
請注意,此格式是 MRI 特有的,且依版本而定。
static VALUE iseqw_to_a(VALUE self) { const rb_iseq_t *iseq = iseqw_check(self); return iseq_data_to_ary(iseq); }
以 String
物件傳回序列化的 iseq 二進位格式資料。對應的 iseq 物件是由 RubyVM::InstructionSequence.load_from_binary()
方法建立的。
String
extra_data 會儲存在二進位資料中。您可以使用 RubyVM::InstructionSequence.load_from_binary_extra_data(binary)
存取此資料。
請注意,轉譯的二進位資料不可攜帶。您無法將此二進位資料移至另一部電腦。您無法使用由 Ruby 的其他版本/其他架構建立的二進位資料。
static VALUE iseqw_to_binary(int argc, VALUE *argv, VALUE self) { VALUE opt = !rb_check_arity(argc, 0, 1) ? Qnil : argv[0]; return rb_iseq_ibf_dump(iseqw_check(self), opt); }
傳回指令序列中的追蹤點。傳回 [line, event_symbol] 配對的陣列。
static VALUE iseqw_trace_points(VALUE self) { const rb_iseq_t *iseq = iseqw_check(self); const struct rb_iseq_constant_body *const body = ISEQ_BODY(iseq); unsigned int i; VALUE ary = rb_ary_new(); for (i=0; i<body->insns_info.size; i++) { const struct iseq_insn_info_entry *entry = &body->insns_info.body[i]; if (entry->events) { push_event_info(iseq, entry->events, entry->line_no, ary); } } return ary; }