summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xbenchmark/bm_vm1_blockparam.rb9
-rwxr-xr-xbenchmark/bm_vm1_blockparam_call.rb9
-rwxr-xr-xbenchmark/bm_vm1_blockparam_pass.rb13
-rwxr-xr-xbenchmark/bm_vm1_blockparam_yield.rb9
-rw-r--r--compile.c75
-rw-r--r--insns.def47
-rw-r--r--proc.c12
-rw-r--r--safe.c16
-rw-r--r--test/ruby/test_optimization.rb65
-rw-r--r--vm_args.c62
-rw-r--r--vm_core.h3
-rw-r--r--vm_insnhelper.c20
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
diff --git a/compile.c b/compile.c
index 3d59b6383f..767da7a3da 100644
--- a/compile.c
+++ b/compile.c
@@ -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;
diff --git a/insns.def b/insns.def
index bd47c2c0b0..ef229ddef4 100644
--- a/insns.def
+++ b/insns.def
@@ -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 特殊なローカル変数($~, $_, ...)の値を得る。
*/
diff --git a/proc.c b/proc.c
index 021442ad93..0eba245dee 100644
--- a/proc.c
+++ b/proc.c
@@ -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];
}
diff --git a/safe.c b/safe.c
index d938082d64..2e1ac8b529 100644
--- a/safe.c
+++ b/safe.c
@@ -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
diff --git a/vm_args.c b/vm_args.c
index 2a09eb262a..c15ce788bd 100644
--- a/vm_args.c
+++ b/vm_args.c
@@ -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 */
diff --git a/vm_core.h b/vm_core.h
index d434d01acd..b82fb2f79f 100644
--- a/vm_core.h
+++ b/vm_core.h
@@ -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 */