1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

RubyVM.keep_script_lines

`RubyVM.keep_script_lines` enables to keep script lines
for each ISeq and AST. This feature is for debugger/REPL
support.

```ruby
RubyVM.keep_script_lines = true
RubyVM::keep_script_lines = true

eval("def foo = nil\ndef bar = nil")
pp RubyVM::InstructionSequence.of(method(:foo)).script_lines
```
This commit is contained in:
Koichi Sasada 2021-09-30 16:58:46 +09:00
parent 3b16d07e45
commit c7550537f1
Notes: git 2021-10-21 16:18:11 +09:00
8 changed files with 137 additions and 6 deletions

View file

@ -7194,6 +7194,7 @@ iseq.$(OBJEXT): {$(VPATH)}node.h
iseq.$(OBJEXT): {$(VPATH)}node_name.inc iseq.$(OBJEXT): {$(VPATH)}node_name.inc
iseq.$(OBJEXT): {$(VPATH)}onigmo.h iseq.$(OBJEXT): {$(VPATH)}onigmo.h
iseq.$(OBJEXT): {$(VPATH)}oniguruma.h iseq.$(OBJEXT): {$(VPATH)}oniguruma.h
iseq.$(OBJEXT): {$(VPATH)}ractor.h
iseq.$(OBJEXT): {$(VPATH)}ruby_assert.h iseq.$(OBJEXT): {$(VPATH)}ruby_assert.h
iseq.$(OBJEXT): {$(VPATH)}ruby_atomic.h iseq.$(OBJEXT): {$(VPATH)}ruby_atomic.h
iseq.$(OBJEXT): {$(VPATH)}st.h iseq.$(OBJEXT): {$(VPATH)}st.h

View file

@ -1328,7 +1328,7 @@ new_child_iseq(rb_iseq_t *iseq, const NODE *const node,
ast.root = node; ast.root = node;
ast.compile_option = 0; ast.compile_option = 0;
ast.script_lines = INT2FIX(-1); ast.script_lines = iseq->body->variable.script_lines;
debugs("[new_child_iseq]> ---------------------------------------\n"); debugs("[new_child_iseq]> ---------------------------------------\n");
int isolated_depth = ISEQ_COMPILE_DATA(iseq)->isolated_depth; int isolated_depth = ISEQ_COMPILE_DATA(iseq)->isolated_depth;

View file

@ -25,6 +25,7 @@ st_index_t rb_iseq_cdhash_hash(VALUE a);
/* iseq.c */ /* iseq.c */
int rb_vm_insn_addr2insn(const void *); int rb_vm_insn_addr2insn(const void *);
int rb_vm_insn_decode(const VALUE encoded); int rb_vm_insn_decode(const VALUE encoded);
extern bool ruby_vm_keep_script_lines;
MJIT_SYMBOL_EXPORT_BEGIN MJIT_SYMBOL_EXPORT_BEGIN
/* iseq.c (export) */ /* iseq.c (export) */

44
iseq.c
View file

@ -595,7 +595,8 @@ new_arena(void)
static VALUE static VALUE
prepare_iseq_build(rb_iseq_t *iseq, prepare_iseq_build(rb_iseq_t *iseq,
VALUE name, VALUE path, VALUE realpath, VALUE first_lineno, const rb_code_location_t *code_location, const int node_id, VALUE name, VALUE path, VALUE realpath, VALUE first_lineno, const rb_code_location_t *code_location, const int node_id,
const rb_iseq_t *parent, int isolated_depth, enum iseq_type type, const rb_compile_option_t *option) const rb_iseq_t *parent, int isolated_depth, enum iseq_type type,
VALUE script_lines, const rb_compile_option_t *option)
{ {
VALUE coverage = Qfalse; VALUE coverage = Qfalse;
VALUE err_info = Qnil; VALUE err_info = Qnil;
@ -616,6 +617,8 @@ prepare_iseq_build(rb_iseq_t *iseq,
ISEQ_ORIGINAL_ISEQ_CLEAR(iseq); ISEQ_ORIGINAL_ISEQ_CLEAR(iseq);
body->variable.flip_count = 0; body->variable.flip_count = 0;
RB_OBJ_WRITE(iseq, &body->variable.script_lines, script_lines);
ISEQ_COMPILE_DATA_ALLOC(iseq); ISEQ_COMPILE_DATA_ALLOC(iseq);
RB_OBJ_WRITE(iseq, &ISEQ_COMPILE_DATA(iseq)->err_info, err_info); RB_OBJ_WRITE(iseq, &ISEQ_COMPILE_DATA(iseq)->err_info, err_info);
RB_OBJ_WRITE(iseq, &ISEQ_COMPILE_DATA(iseq)->catch_table_ary, Qnil); RB_OBJ_WRITE(iseq, &ISEQ_COMPILE_DATA(iseq)->catch_table_ary, Qnil);
@ -894,7 +897,17 @@ rb_iseq_new_with_opt(const rb_ast_body_t *ast, VALUE name, VALUE path, VALUE rea
} }
if (ast && ast->compile_option) rb_iseq_make_compile_option(&new_opt, ast->compile_option); if (ast && ast->compile_option) rb_iseq_make_compile_option(&new_opt, ast->compile_option);
prepare_iseq_build(iseq, name, path, realpath, first_lineno, node ? &node->nd_loc : NULL, node ? nd_node_id(node) : -1, parent, isolated_depth, type, &new_opt); VALUE script_lines = Qnil;
if (ast && !FIXNUM_P(ast->script_lines) && ast->script_lines) {
script_lines = ast->script_lines;
}
else if (parent) {
script_lines = parent->body->variable.script_lines;
}
prepare_iseq_build(iseq, name, path, realpath, first_lineno, node ? &node->nd_loc : NULL, node ? nd_node_id(node) : -1,
parent, isolated_depth, type, script_lines, &new_opt);
rb_iseq_compile_node(iseq, node); rb_iseq_compile_node(iseq, node);
finish_iseq_build(iseq); finish_iseq_build(iseq);
@ -913,7 +926,7 @@ rb_iseq_new_with_callback(
rb_iseq_t *iseq = iseq_alloc(); rb_iseq_t *iseq = iseq_alloc();
if (!option) option = &COMPILE_OPTION_DEFAULT; if (!option) option = &COMPILE_OPTION_DEFAULT;
prepare_iseq_build(iseq, name, path, realpath, first_lineno, NULL, -1, parent, 0, type, option); prepare_iseq_build(iseq, name, path, realpath, first_lineno, NULL, -1, parent, 0, type, Qnil, option);
rb_iseq_compile_callback(iseq, ifunc); rb_iseq_compile_callback(iseq, ifunc);
finish_iseq_build(iseq); finish_iseq_build(iseq);
@ -1026,7 +1039,7 @@ iseq_load(VALUE data, const rb_iseq_t *parent, VALUE opt)
make_compile_option(&option, opt); make_compile_option(&option, opt);
option.peephole_optimization = FALSE; /* because peephole optimization can modify original iseq */ option.peephole_optimization = FALSE; /* because peephole optimization can modify original iseq */
prepare_iseq_build(iseq, name, path, realpath, first_lineno, &tmp_loc, NUM2INT(node_id), prepare_iseq_build(iseq, name, path, realpath, first_lineno, &tmp_loc, NUM2INT(node_id),
parent, 0, (enum iseq_type)iseq_type, &option); parent, 0, (enum iseq_type)iseq_type, Qnil, &option);
rb_iseq_build_from_ary(iseq, misc, locals, params, exception, body); rb_iseq_build_from_ary(iseq, misc, locals, params, exception, body);
@ -3680,6 +3693,26 @@ succ_index_lookup(const struct succ_index_table *sd, int x)
} }
#endif #endif
/*
* call-seq:
* iseq.script_lines -> array or nil
*
* It returns recorded script lines if it is availalble.
* The script lines are not limited to the iseq range, but
* are entire lines of the source file.
*
* Note that this is an API for ruby internal use, debugging,
* and research. Do not use this for any other purpose.
* The compatibility is not guaranteed.
*/
static VALUE
iseqw_script_lines(VALUE self)
{
const rb_iseq_t *iseq = iseqw_check(self);
return iseq->body->variable.script_lines;
}
/* /*
* Document-class: RubyVM::InstructionSequence * Document-class: RubyVM::InstructionSequence
* *
@ -3747,6 +3780,9 @@ Init_ISeq(void)
rb_define_singleton_method(rb_cISeq, "disassemble", iseqw_s_disasm, 1); rb_define_singleton_method(rb_cISeq, "disassemble", iseqw_s_disasm, 1);
rb_define_singleton_method(rb_cISeq, "of", iseqw_s_of, 1); rb_define_singleton_method(rb_cISeq, "of", iseqw_s_of, 1);
// script lines
rb_define_method(rb_cISeq, "script_lines", iseqw_script_lines, 0);
rb_undef_method(CLASS_OF(rb_cISeq), "translate"); rb_undef_method(CLASS_OF(rb_cISeq), "translate");
rb_undef_method(CLASS_OF(rb_cISeq), "load_iseq"); rb_undef_method(CLASS_OF(rb_cISeq), "load_iseq");
} }

View file

@ -6306,7 +6306,8 @@ yycompile0(VALUE arg)
cov = Qtrue; cov = Qtrue;
} }
} }
if (p->keep_script_lines) {
if (p->keep_script_lines || ruby_vm_keep_script_lines) {
if (!p->debug_lines) { if (!p->debug_lines) {
p->debug_lines = rb_ary_new(); p->debug_lines = rb_ary_new();
} }

View file

@ -15,4 +15,56 @@ class TestRubyVM < Test::Unit::TestCase
assert_raise(ArgumentError){ RubyVM.stat(:unknown) } assert_raise(ArgumentError){ RubyVM.stat(:unknown) }
assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) {RubyVM.stat(:"\u{30eb 30d3 30fc}")} assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) {RubyVM.stat(:"\u{30eb 30d3 30fc}")}
end end
def parse_and_compile
script = <<~RUBY
a = 1
def foo
b = 2
end
1.times{
c = 3
}
RUBY
ast = RubyVM::AbstractSyntaxTree.parse(script)
iseq = RubyVM::InstructionSequence.compile(script)
[ast, iseq]
end
def test_keep_script_lines
prev_conf = RubyVM.keep_script_lines
# keep
RubyVM.keep_script_lines = true
ast, iseq = *parse_and_compile
lines = ast.script_lines
assert_equal Array, lines.class
lines = iseq.script_lines
assert_equal Array, lines.class
iseq.each_child{|child|
assert_equal lines, child.script_lines
}
# don't keep
RubyVM.keep_script_lines = false
ast, iseq = *parse_and_compile
lines = ast.script_lines
assert_equal nil, lines
lines = iseq.script_lines
assert_equal nil, lines
iseq.each_child{|child|
assert_equal lines, child.script_lines
}
ensure
RubyVM.keep_script_lines = prev_conf
end
end end

39
vm.c
View file

@ -380,6 +380,7 @@ VALUE rb_block_param_proxy;
VALUE ruby_vm_const_missing_count = 0; VALUE ruby_vm_const_missing_count = 0;
rb_vm_t *ruby_current_vm_ptr = NULL; rb_vm_t *ruby_current_vm_ptr = NULL;
rb_ractor_t *ruby_single_main_ractor; rb_ractor_t *ruby_single_main_ractor;
bool ruby_vm_keep_script_lines;
#ifdef RB_THREAD_LOCAL_SPECIFIER #ifdef RB_THREAD_LOCAL_SPECIFIER
RB_THREAD_LOCAL_SPECIFIER rb_execution_context_t *ruby_current_ec; RB_THREAD_LOCAL_SPECIFIER rb_execution_context_t *ruby_current_ec;
@ -3338,6 +3339,41 @@ vm_mtbl2(VALUE self, VALUE obj, VALUE sym)
return Qnil; return Qnil;
} }
/*
* call-seq:
* RubyVM.keep_script_lines -> true or false
*
* Return current +keep_script_lines+ status. Now it only returns
* +true+ of +false+, but it can return other objects in future.
*
* Note that this is an API for ruby internal use, debugging,
* and research. Do not use this for any other purpose.
* The compatibility is not guaranteed.
*/
static VALUE
vm_keep_script_lines(VALUE self)
{
return RBOOL(ruby_vm_keep_script_lines);
}
/*
* call-seq:
* RubyVM.keep_script_lines = true / false
*
* It set +keep_script_lines+ flag. If the flag is set, all
* loaded scripts are recorded in a interpreter process.
*
* Note that this is an API for ruby internal use, debugging,
* and research. Do not use this for any other purpose.
* The compatibility is not guaranteed.
*/
static VALUE
vm_keep_script_lines_set(VALUE self, VALUE flags)
{
ruby_vm_keep_script_lines = RTEST(flags);
return flags;
}
void void
Init_VM(void) Init_VM(void)
{ {
@ -3361,6 +3397,9 @@ Init_VM(void)
rb_undef_alloc_func(rb_cRubyVM); rb_undef_alloc_func(rb_cRubyVM);
rb_undef_method(CLASS_OF(rb_cRubyVM), "new"); rb_undef_method(CLASS_OF(rb_cRubyVM), "new");
rb_define_singleton_method(rb_cRubyVM, "stat", vm_stat, -1); rb_define_singleton_method(rb_cRubyVM, "stat", vm_stat, -1);
rb_define_singleton_method(rb_cRubyVM, "keep_script_lines", vm_keep_script_lines, 0);
rb_define_singleton_method(rb_cRubyVM, "keep_script_lines=", vm_keep_script_lines_set, 1);
#if USE_DEBUG_COUNTER #if USE_DEBUG_COUNTER
rb_define_singleton_method(rb_cRubyVM, "reset_debug_counters", rb_debug_counter_reset, 0); rb_define_singleton_method(rb_cRubyVM, "reset_debug_counters", rb_debug_counter_reset, 0);
rb_define_singleton_method(rb_cRubyVM, "show_debug_counters", rb_debug_counter_show, 0); rb_define_singleton_method(rb_cRubyVM, "show_debug_counters", rb_debug_counter_show, 0);

View file

@ -433,6 +433,7 @@ struct rb_iseq_constant_body {
struct { struct {
rb_snum_t flip_count; rb_snum_t flip_count;
VALUE script_lines;
VALUE coverage; VALUE coverage;
VALUE pc2branchindex; VALUE pc2branchindex;
VALUE *original_iseq; VALUE *original_iseq;