Skip to content

Commit

Permalink
RubyVM.keep_script_lines
Browse files Browse the repository at this point in the history
`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
```
  • Loading branch information
ko1 committed Oct 21, 2021
1 parent 3b16d07 commit c755053
Show file tree
Hide file tree
Showing 8 changed files with 137 additions and 6 deletions.
1 change: 1 addition & 0 deletions common.mk
Expand Up @@ -7194,6 +7194,7 @@ iseq.$(OBJEXT): {$(VPATH)}node.h
iseq.$(OBJEXT): {$(VPATH)}node_name.inc
iseq.$(OBJEXT): {$(VPATH)}onigmo.h
iseq.$(OBJEXT): {$(VPATH)}oniguruma.h
iseq.$(OBJEXT): {$(VPATH)}ractor.h
iseq.$(OBJEXT): {$(VPATH)}ruby_assert.h
iseq.$(OBJEXT): {$(VPATH)}ruby_atomic.h
iseq.$(OBJEXT): {$(VPATH)}st.h
Expand Down
2 changes: 1 addition & 1 deletion compile.c
Expand Up @@ -1328,7 +1328,7 @@ new_child_iseq(rb_iseq_t *iseq, const NODE *const node,

ast.root = node;
ast.compile_option = 0;
ast.script_lines = INT2FIX(-1);
ast.script_lines = iseq->body->variable.script_lines;

debugs("[new_child_iseq]> ---------------------------------------\n");
int isolated_depth = ISEQ_COMPILE_DATA(iseq)->isolated_depth;
Expand Down
1 change: 1 addition & 0 deletions internal/compile.h
Expand Up @@ -25,6 +25,7 @@ st_index_t rb_iseq_cdhash_hash(VALUE a);
/* iseq.c */
int rb_vm_insn_addr2insn(const void *);
int rb_vm_insn_decode(const VALUE encoded);
extern bool ruby_vm_keep_script_lines;

MJIT_SYMBOL_EXPORT_BEGIN
/* iseq.c (export) */
Expand Down
44 changes: 40 additions & 4 deletions iseq.c
Expand Up @@ -595,7 +595,8 @@ new_arena(void)
static VALUE
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,
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 err_info = Qnil;
Expand All @@ -616,6 +617,8 @@ prepare_iseq_build(rb_iseq_t *iseq,
ISEQ_ORIGINAL_ISEQ_CLEAR(iseq);
body->variable.flip_count = 0;

RB_OBJ_WRITE(iseq, &body->variable.script_lines, script_lines);

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)->catch_table_ary, Qnil);
Expand Down Expand Up @@ -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);

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);
finish_iseq_build(iseq);
Expand All @@ -913,7 +926,7 @@ rb_iseq_new_with_callback(
rb_iseq_t *iseq = iseq_alloc();

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);
finish_iseq_build(iseq);
Expand Down Expand Up @@ -1026,7 +1039,7 @@ iseq_load(VALUE data, const rb_iseq_t *parent, VALUE opt)
make_compile_option(&option, opt);
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),
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);

Expand Down Expand Up @@ -3680,6 +3693,26 @@ succ_index_lookup(const struct succ_index_table *sd, int x)
}
#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
*
Expand Down Expand Up @@ -3747,6 +3780,9 @@ Init_ISeq(void)
rb_define_singleton_method(rb_cISeq, "disassemble", iseqw_s_disasm, 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), "load_iseq");
}
3 changes: 2 additions & 1 deletion parse.y
Expand Up @@ -6306,7 +6306,8 @@ yycompile0(VALUE arg)
cov = Qtrue;
}
}
if (p->keep_script_lines) {

if (p->keep_script_lines || ruby_vm_keep_script_lines) {
if (!p->debug_lines) {
p->debug_lines = rb_ary_new();
}
Expand Down
52 changes: 52 additions & 0 deletions test/ruby/test_rubyvm.rb
Expand Up @@ -15,4 +15,56 @@ def test_stat_unknown
assert_raise(ArgumentError){ RubyVM.stat(:unknown) }
assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) {RubyVM.stat(:"\u{30eb 30d3 30fc}")}
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
39 changes: 39 additions & 0 deletions vm.c
Expand Up @@ -380,6 +380,7 @@ VALUE rb_block_param_proxy;
VALUE ruby_vm_const_missing_count = 0;
rb_vm_t *ruby_current_vm_ptr = NULL;
rb_ractor_t *ruby_single_main_ractor;
bool ruby_vm_keep_script_lines;

#ifdef RB_THREAD_LOCAL_SPECIFIER
RB_THREAD_LOCAL_SPECIFIER rb_execution_context_t *ruby_current_ec;
Expand Down Expand Up @@ -3338,6 +3339,41 @@ vm_mtbl2(VALUE self, VALUE obj, VALUE sym)
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
Init_VM(void)
{
Expand All @@ -3361,6 +3397,9 @@ Init_VM(void)
rb_undef_alloc_func(rb_cRubyVM);
rb_undef_method(CLASS_OF(rb_cRubyVM), "new");
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
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);
Expand Down
1 change: 1 addition & 0 deletions vm_core.h
Expand Up @@ -433,6 +433,7 @@ struct rb_iseq_constant_body {

struct {
rb_snum_t flip_count;
VALUE script_lines;
VALUE coverage;
VALUE pc2branchindex;
VALUE *original_iseq;
Expand Down

0 comments on commit c755053

Please sign in to comment.