diff options
-rwxr-xr-x | benchmark/bm_vm1_blockparam.rb | 9 | ||||
-rwxr-xr-x | benchmark/bm_vm1_blockparam_call.rb | 9 | ||||
-rwxr-xr-x | benchmark/bm_vm1_blockparam_pass.rb | 13 | ||||
-rwxr-xr-x | benchmark/bm_vm1_blockparam_yield.rb | 9 | ||||
-rw-r--r-- | compile.c | 75 | ||||
-rw-r--r-- | insns.def | 47 | ||||
-rw-r--r-- | proc.c | 12 | ||||
-rw-r--r-- | safe.c | 16 | ||||
-rw-r--r-- | test/ruby/test_optimization.rb | 65 | ||||
-rw-r--r-- | vm_args.c | 62 | ||||
-rw-r--r-- | vm_core.h | 3 | ||||
-rw-r--r-- | vm_insnhelper.c | 20 |
12 files changed, 292 insertions, 48 deletions
diff --git a/benchmark/bm_vm1_blockparam.rb b/benchmark/bm_vm1_blockparam.rb new file mode 100755 index 0000000000..11680a2e61 --- /dev/null +++ b/benchmark/bm_vm1_blockparam.rb @@ -0,0 +1,9 @@ +def m &b +end + +i = 0 +while i<30_000_000 # while loop 1 + i += 1 + m{} +end + diff --git a/benchmark/bm_vm1_blockparam_call.rb b/benchmark/bm_vm1_blockparam_call.rb new file mode 100755 index 0000000000..f6102a2b5a --- /dev/null +++ b/benchmark/bm_vm1_blockparam_call.rb @@ -0,0 +1,9 @@ +def m &b + b.call +end + +i = 0 +while i<30_000_000 # while loop 1 + i += 1 + m{} +end diff --git a/benchmark/bm_vm1_blockparam_pass.rb b/benchmark/bm_vm1_blockparam_pass.rb new file mode 100755 index 0000000000..10029a257a --- /dev/null +++ b/benchmark/bm_vm1_blockparam_pass.rb @@ -0,0 +1,13 @@ +def bp_yield + yield +end + +def bp_pass &b + bp_yield &b +end + +i = 0 +while i<30_000_000 # while loop 1 + i += 1 + bp_pass{} +end diff --git a/benchmark/bm_vm1_blockparam_yield.rb b/benchmark/bm_vm1_blockparam_yield.rb new file mode 100755 index 0000000000..6dc01ced7c --- /dev/null +++ b/benchmark/bm_vm1_blockparam_yield.rb @@ -0,0 +1,9 @@ +def bp_yield &b + yield +end + +i = 0 +while i<30_000_000 # while loop 1 + i += 1 + bp_yield{} +end @@ -300,15 +300,11 @@ struct iseq_compile_data_ensure_node_stack { } \ } while (0) -#define ADD_GETLOCAL(seq, line, idx, level) \ - do { \ - ADD_INSN2((seq), (line), getlocal, INT2FIX((idx) + VM_ENV_DATA_SIZE - 1), INT2FIX(level)); \ - } while (0) +static void iseq_add_getlocal(rb_iseq_t *iseq, LINK_ANCHOR *const seq, int line, int idx, int level); +static void iseq_add_setlocal(rb_iseq_t *iseq, LINK_ANCHOR *const seq, int line, int idx, int level); -#define ADD_SETLOCAL(seq, line, idx, level) \ - do { \ - ADD_INSN2((seq), (line), setlocal, INT2FIX((idx) + VM_ENV_DATA_SIZE - 1), INT2FIX(level)); \ - } while (0) +#define ADD_GETLOCAL(seq, line, idx, level) iseq_add_getlocal(iseq, (seq), (line), (idx), (level)) +#define ADD_SETLOCAL(seq, line, idx, level) iseq_add_setlocal(iseq, (seq), (line), (idx), (level)) /* add label */ #define ADD_LABEL(seq, label) \ @@ -976,6 +972,18 @@ LIST_SIZE_ZERO(LINK_ANCHOR *const anchor) } } +static int +LIST_SIZE_ONE(const LINK_ANCHOR *const anchor) +{ + if (anchor->anchor.next != NULL && + anchor->anchor.next->next == NULL) { + return 1; + } + else { + return 0; + } +} + /* * anc1: e1, e2, e3 * anc2: e4, e5 @@ -1298,6 +1306,47 @@ get_dyna_var_idx(const rb_iseq_t *iseq, ID id, int *level, int *ls) return idx; } +static int +iseq_local_block_param_p(const rb_iseq_t *iseq, unsigned int idx, unsigned int level) +{ + while (level > 0) { + iseq = iseq->body->parent_iseq; + level--; + } + if (iseq->body->local_iseq == iseq && /* local variables */ + iseq->body->param.flags.has_block && + iseq->body->local_table_size - iseq->body->param.block_start == idx) { + return TRUE; + } + else { + return FALSE; + } +} + +static void +iseq_add_getlocal(rb_iseq_t *iseq, LINK_ANCHOR *const seq, int line, int idx, int level) +{ + if (iseq_local_block_param_p(iseq, idx, level)) { + ADD_INSN2(seq, line, getblockparam, INT2FIX((idx) + VM_ENV_DATA_SIZE - 1), INT2FIX(level)); + } + else { + ADD_INSN2(seq, line, getlocal, INT2FIX((idx) + VM_ENV_DATA_SIZE - 1), INT2FIX(level)); + } +} + +static void +iseq_add_setlocal(rb_iseq_t *iseq, LINK_ANCHOR *const seq, int line, int idx, int level) +{ + if (iseq_local_block_param_p(iseq, idx, level)) { + ADD_INSN2(seq, line, setblockparam, INT2FIX((idx) + VM_ENV_DATA_SIZE - 1), INT2FIX(level)); + } + else { + ADD_INSN2(seq, line, setlocal, INT2FIX((idx) + VM_ENV_DATA_SIZE - 1), INT2FIX(level)); + } +} + + + static void iseq_calc_param_size(rb_iseq_t *iseq) { @@ -4173,6 +4222,16 @@ setup_args(rb_iseq_t *iseq, LINK_ANCHOR *const args, NODE *argn, } if (*flag & VM_CALL_ARGS_BLOCKARG) { + if (LIST_SIZE_ONE(arg_block)) { + LINK_ELEMENT *elem = FIRST_ELEMENT(arg_block); + if (elem->type == ISEQ_ELEMENT_INSN) { + INSN *iobj = (INSN *)elem; + if (iobj->insn_id == BIN(getblockparam)) { + iobj->insn_id = BIN(getlocal); + *flag |= VM_CALL_ARGS_BLOCKARG_BLOCKPARAM; + } + } + } ADD_SEQ(args, arg_block); } return argc; @@ -82,6 +82,53 @@ setlocal /** @c variable + @e Get a block parameter. + @j ブロックパラメータを取得する。 + */ +DEFINE_INSN +getblockparam +(lindex_t idx, rb_num_t level) +() +(VALUE val) +{ + const VALUE *ep = vm_get_ep(GET_EP(), level); + VM_ASSERT(VM_ENV_LOCAL_P(ep)); + + if (!VM_ENV_FLAGS(ep, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM)) { + val = rb_vm_bh_to_procval(th, VM_ENV_BLOCK_HANDLER(ep)); + vm_env_write(ep, -(int)idx, val); + VM_ENV_FLAGS_SET(ep, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM); + } + else { + val = *(ep - idx); + RB_DEBUG_COUNTER_INC(lvar_get); + (void)RB_DEBUG_COUNTER_INC_IF(lvar_get_dynamic, level > 0); + } +} + +/** + @c variable + @e Set block parameter. + @j ブロックパラメータを設定する。 + */ +DEFINE_INSN +setblockparam +(lindex_t idx, rb_num_t level) +(VALUE val) +() +{ + const VALUE *ep = vm_get_ep(GET_EP(), level); + VM_ASSERT(VM_ENV_LOCAL_P(ep)); + + vm_env_write(ep, -(int)idx, val); + RB_DEBUG_COUNTER_INC(lvar_set); + (void)RB_DEBUG_COUNTER_INC_IF(lvar_set_dynamic, level > 0); + + VM_ENV_FLAGS_SET(ep, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM); +} + +/** + @c variable @e Get value of special local variable ($~, $_, ..). @j 特殊なローカル変数($~, $_, ...)の値を得る。 */ @@ -384,6 +384,8 @@ bind_eval(int argc, VALUE *argv, VALUE bindval) return rb_f_eval(argc+1, args, Qnil /* self will be searched in eval */); } +VALUE rb_vm_bh_to_procval(rb_thread_t *th, VALUE block_handler); + static const VALUE * get_local_variable_ptr(const rb_env_t **envp, ID lid) { @@ -397,6 +399,16 @@ get_local_variable_ptr(const rb_env_t **envp, ID lid) for (i=0; i<iseq->body->local_table_size; i++) { if (iseq->body->local_table[i] == lid) { + if (iseq->body->local_iseq == iseq && + iseq->body->param.flags.has_block && + (unsigned int)iseq->body->param.block_start == i) { + const VALUE *ep = env->ep; + if (!VM_ENV_FLAGS(ep, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM)) { + RB_OBJ_WRITE(env, &env->env[i], rb_vm_bh_to_procval(GET_THREAD(), VM_ENV_BLOCK_HANDLER(ep))); + VM_ENV_FLAGS_SET(ep, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM); + } + } + *envp = env; return &env->env[i]; } @@ -65,17 +65,25 @@ safe_getter(void) static void safe_setter(VALUE val) { - int level = NUM2INT(val); rb_thread_t *th = GET_THREAD(); + int current_level = th->ec.safe_level; + int level = NUM2INT(val); - if (level < th->ec.safe_level) { + if (level == current_level) { + return; + } + else if (level < current_level) { rb_raise(rb_eSecurityError, "tried to downgrade safe level from %d to %d", - th->ec.safe_level, level); + current_level, level); } - if (level > SAFE_LEVEL_MAX) { + else if (level > SAFE_LEVEL_MAX) { rb_raise(rb_eArgError, "$SAFE=2 to 4 are obsolete"); } + + /* block parameters */ + rb_vm_stack_to_heap(th); + th->ec.safe_level = level; } diff --git a/test/ruby/test_optimization.rb b/test/ruby/test_optimization.rb index 2d0eec02fd..ed16ad9fe2 100644 --- a/test/ruby/test_optimization.rb +++ b/test/ruby/test_optimization.rb @@ -575,4 +575,69 @@ class TestRubyOptimization < Test::Unit::TestCase def t; if false; case 42; when s {}; end; end; end end; end + + def bptest_yield &b + yield + end + + def bptest_yield_pass &b + bptest_yield(&b) + end + + def bptest_bp_value &b + b + end + + def bptest_bp_pass_bp_value &b + bptest_bp_value(&b) + end + + def bptest_binding &b + binding + end + + def bptest_set &b + b = Proc.new{2} + end + + def test_block_parameter + assert_equal(1, bptest_yield{1}) + assert_equal(1, bptest_yield_pass{1}) + assert_equal(1, send(:bptest_yield){1}) + + assert_equal(Proc, bptest_bp_value{}.class) + assert_equal nil, bptest_bp_value + assert_equal(Proc, bptest_bp_pass_bp_value{}.class) + assert_equal nil, bptest_bp_pass_bp_value + + assert_equal Proc, bptest_binding{}.local_variable_get(:b).class + + assert_equal 2, bptest_set{1}.call + end + + def test_block_parameter_should_not_create_objects + assert_separately [], <<-END + # + def foo &b + end + h1 = {}; h2 = {} + ObjectSpace.count_objects(h1) # reharsal + ObjectSpace.count_objects(h1) + foo{} + ObjectSpace.count_objects(h2) + + assert_equal 0, h2[:TOTAL] - h1[:TOTAL] + END + end + + def test_block_parameter_should_restore_safe_level + assert_separately [], <<-END + # + def foo &b + $SAFE = 1 + b.call + end + assert_equal 0, foo{$SAFE} + END + end end @@ -477,24 +477,7 @@ static inline void args_setup_block_parameter(rb_thread_t *th, struct rb_calling_info *calling, VALUE *locals) { VALUE block_handler = calling->block_handler; - VALUE blockval = Qnil; - - if (block_handler != VM_BLOCK_HANDLER_NONE) { - - switch (vm_block_handler_type(block_handler)) { - case block_handler_type_iseq: - case block_handler_type_ifunc: - blockval = rb_vm_make_proc(th, VM_BH_TO_CAPT_BLOCK(block_handler), rb_cProc); - break; - case block_handler_type_symbol: - blockval = rb_sym_to_proc(VM_BH_TO_SYMBOL(block_handler)); - break; - case block_handler_type_proc: - blockval = VM_BH_TO_PROC(block_handler); - break; - } - } - *locals = blockval; + *locals = rb_vm_bh_to_procval(th, block_handler); } struct fill_values_arg { @@ -683,7 +666,12 @@ setup_parameters_complex(rb_thread_t * const th, const rb_iseq_t * const iseq, } if (iseq->body->param.flags.has_block) { - args_setup_block_parameter(th, calling, locals + iseq->body->param.block_start); + if (iseq->body->local_iseq == iseq) { + /* Do nothing */ + } + else { + args_setup_block_parameter(th, calling, locals + iseq->body->param.block_start); + } } #if 0 @@ -843,27 +831,29 @@ vm_caller_setup_arg_block(const rb_thread_t *th, rb_control_frame_t *reg_cfp, if (ci->flag & VM_CALL_ARGS_BLOCKARG) { VALUE block_code = *(--reg_cfp->sp); - if (NIL_P(block_code)) { + if ((ci->flag & VM_CALL_ARGS_BLOCKARG_BLOCKPARAM) && + !VM_ENV_FLAGS(VM_CF_LEP(reg_cfp), VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM)) { + calling->block_handler = VM_CF_BLOCK_HANDLER(reg_cfp); + } + else if (NIL_P(block_code)) { calling->block_handler = VM_BLOCK_HANDLER_NONE; } - else { - if (SYMBOL_P(block_code) && rb_method_basic_definition_p(rb_cSymbol, idTo_proc)) { - const rb_cref_t *cref = vm_env_cref(reg_cfp->ep); - if (cref && !NIL_P(cref->refinements)) { - VALUE ref = cref->refinements; - VALUE func = rb_hash_lookup(ref, block_code); - if (NIL_P(func)) { - /* TODO: limit cached funcs */ - func = rb_func_proc_new(refine_sym_proc_call, block_code); - rb_hash_aset(ref, block_code, func); - } - block_code = func; + else if (SYMBOL_P(block_code) && rb_method_basic_definition_p(rb_cSymbol, idTo_proc)) { + const rb_cref_t *cref = vm_env_cref(reg_cfp->ep); + if (cref && !NIL_P(cref->refinements)) { + VALUE ref = cref->refinements; + VALUE func = rb_hash_lookup(ref, block_code); + if (NIL_P(func)) { + /* TODO: limit cached funcs */ + func = rb_func_proc_new(refine_sym_proc_call, block_code); + rb_hash_aset(ref, block_code, func); } - calling->block_handler = block_code; - } - else { - calling->block_handler = vm_to_proc(block_code); + block_code = func; } + calling->block_handler = block_code; + } + else { + calling->block_handler = vm_to_proc(block_code); } } else if (blockiseq != NULL) { /* likely */ @@ -943,6 +943,7 @@ enum vm_check_match_type { enum vm_call_flag_bits { VM_CALL_ARGS_SPLAT_bit, /* m(*args) */ VM_CALL_ARGS_BLOCKARG_bit, /* m(&block) */ + VM_CALL_ARGS_BLOCKARG_BLOCKPARAM_bit, /* m(&block) and block is a passed block parameter */ VM_CALL_FCALL_bit, /* m(...) */ VM_CALL_VCALL_bit, /* m */ VM_CALL_ARGS_SIMPLE_bit, /* (ci->flag & (SPLAT|BLOCKARG)) && blockiseq == NULL && ci->kw_arg == NULL */ @@ -957,6 +958,7 @@ enum vm_call_flag_bits { #define VM_CALL_ARGS_SPLAT (0x01 << VM_CALL_ARGS_SPLAT_bit) #define VM_CALL_ARGS_BLOCKARG (0x01 << VM_CALL_ARGS_BLOCKARG_bit) +#define VM_CALL_ARGS_BLOCKARG_BLOCKPARAM (0x01 << VM_CALL_ARGS_BLOCKARG_BLOCKPARAM_bit) #define VM_CALL_FCALL (0x01 << VM_CALL_FCALL_bit) #define VM_CALL_VCALL (0x01 << VM_CALL_VCALL_bit) #define VM_CALL_ARGS_SIMPLE (0x01 << VM_CALL_ARGS_SIMPLE_bit) @@ -1033,6 +1035,7 @@ enum { VM_FRAME_FLAG_BMETHOD = 0x0040, VM_FRAME_FLAG_CFRAME = 0x0080, VM_FRAME_FLAG_LAMBDA = 0x0100, + VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM = 0x0200, /* env flag */ VM_ENV_FLAG_LOCAL = 0x0002, diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 0d23359f5a..3e3e7d5d6d 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -359,6 +359,26 @@ rb_vm_env_write(const VALUE *ep, int index, VALUE v) vm_env_write(ep, index, v); } +VALUE +rb_vm_bh_to_procval(rb_thread_t *th, VALUE block_handler) +{ + if (block_handler == VM_BLOCK_HANDLER_NONE) { + return Qnil; + } + else { + switch (vm_block_handler_type(block_handler)) { + case block_handler_type_iseq: + case block_handler_type_ifunc: + return rb_vm_make_proc(th, VM_BH_TO_CAPT_BLOCK(block_handler), rb_cProc); + case block_handler_type_symbol: + return rb_sym_to_proc(VM_BH_TO_SYMBOL(block_handler)); + case block_handler_type_proc: + return VM_BH_TO_PROC(block_handler); + default: + VM_UNREACHABLE(rb_vm_bh_to_procval); + } + } +} /* svar */ |