Skip to content

Commit 5ee9513

Browse files
committed
Lazy Proc allocation for block parameters
[Feature #14045] * insns.def (getblockparam, setblockparam): add special access instructions for block parameters. getblockparam checks VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM and if it is not set this instruction creates a Proc object from a given blcok and set VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM. setblockparam is similar to setlocal, but set VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM. * compile.c: use get/setblockparm instead get/setlocal instructions. Note that they are used for method local block parameters (def m(&b)), not for block local method parameters (iter{|&b|). * proc.c (get_local_variable_ptr): creates Proc object for Binding#local_variable_get/set. * safe.c (safe_setter): we need to create Proc objects for postponed block parameters when $SAFE is changed. * vm_args.c (args_setup_block_parameter): used only for block local blcok parameters. * vm_args.c (vm_caller_setup_arg_block): if called with VM_CALL_ARGS_BLOCKARG_BLOCKPARAM flag then passed block values should be a block handler. * test/ruby/test_optimization.rb: add tests. * benchmark/bm_vm1_blockparam*: added. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@60397 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
1 parent d0d32ba commit 5ee9513

12 files changed

+292
-48
lines changed

benchmark/bm_vm1_blockparam.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
def m &b
2+
end
3+
4+
i = 0
5+
while i<30_000_000 # while loop 1
6+
i += 1
7+
m{}
8+
end
9+

benchmark/bm_vm1_blockparam_call.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
def m &b
2+
b.call
3+
end
4+
5+
i = 0
6+
while i<30_000_000 # while loop 1
7+
i += 1
8+
m{}
9+
end

benchmark/bm_vm1_blockparam_pass.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
def bp_yield
2+
yield
3+
end
4+
5+
def bp_pass &b
6+
bp_yield &b
7+
end
8+
9+
i = 0
10+
while i<30_000_000 # while loop 1
11+
i += 1
12+
bp_pass{}
13+
end

benchmark/bm_vm1_blockparam_yield.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
def bp_yield &b
2+
yield
3+
end
4+
5+
i = 0
6+
while i<30_000_000 # while loop 1
7+
i += 1
8+
bp_yield{}
9+
end

compile.c

Lines changed: 67 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -300,15 +300,11 @@ struct iseq_compile_data_ensure_node_stack {
300300
} \
301301
} while (0)
302302

303-
#define ADD_GETLOCAL(seq, line, idx, level) \
304-
do { \
305-
ADD_INSN2((seq), (line), getlocal, INT2FIX((idx) + VM_ENV_DATA_SIZE - 1), INT2FIX(level)); \
306-
} while (0)
303+
static void iseq_add_getlocal(rb_iseq_t *iseq, LINK_ANCHOR *const seq, int line, int idx, int level);
304+
static void iseq_add_setlocal(rb_iseq_t *iseq, LINK_ANCHOR *const seq, int line, int idx, int level);
307305

308-
#define ADD_SETLOCAL(seq, line, idx, level) \
309-
do { \
310-
ADD_INSN2((seq), (line), setlocal, INT2FIX((idx) + VM_ENV_DATA_SIZE - 1), INT2FIX(level)); \
311-
} while (0)
306+
#define ADD_GETLOCAL(seq, line, idx, level) iseq_add_getlocal(iseq, (seq), (line), (idx), (level))
307+
#define ADD_SETLOCAL(seq, line, idx, level) iseq_add_setlocal(iseq, (seq), (line), (idx), (level))
312308

313309
/* add label */
314310
#define ADD_LABEL(seq, label) \
@@ -976,6 +972,18 @@ LIST_SIZE_ZERO(LINK_ANCHOR *const anchor)
976972
}
977973
}
978974

975+
static int
976+
LIST_SIZE_ONE(const LINK_ANCHOR *const anchor)
977+
{
978+
if (anchor->anchor.next != NULL &&
979+
anchor->anchor.next->next == NULL) {
980+
return 1;
981+
}
982+
else {
983+
return 0;
984+
}
985+
}
986+
979987
/*
980988
* anc1: e1, e2, e3
981989
* anc2: e4, e5
@@ -1298,6 +1306,47 @@ get_dyna_var_idx(const rb_iseq_t *iseq, ID id, int *level, int *ls)
12981306
return idx;
12991307
}
13001308

1309+
static int
1310+
iseq_local_block_param_p(const rb_iseq_t *iseq, unsigned int idx, unsigned int level)
1311+
{
1312+
while (level > 0) {
1313+
iseq = iseq->body->parent_iseq;
1314+
level--;
1315+
}
1316+
if (iseq->body->local_iseq == iseq && /* local variables */
1317+
iseq->body->param.flags.has_block &&
1318+
iseq->body->local_table_size - iseq->body->param.block_start == idx) {
1319+
return TRUE;
1320+
}
1321+
else {
1322+
return FALSE;
1323+
}
1324+
}
1325+
1326+
static void
1327+
iseq_add_getlocal(rb_iseq_t *iseq, LINK_ANCHOR *const seq, int line, int idx, int level)
1328+
{
1329+
if (iseq_local_block_param_p(iseq, idx, level)) {
1330+
ADD_INSN2(seq, line, getblockparam, INT2FIX((idx) + VM_ENV_DATA_SIZE - 1), INT2FIX(level));
1331+
}
1332+
else {
1333+
ADD_INSN2(seq, line, getlocal, INT2FIX((idx) + VM_ENV_DATA_SIZE - 1), INT2FIX(level));
1334+
}
1335+
}
1336+
1337+
static void
1338+
iseq_add_setlocal(rb_iseq_t *iseq, LINK_ANCHOR *const seq, int line, int idx, int level)
1339+
{
1340+
if (iseq_local_block_param_p(iseq, idx, level)) {
1341+
ADD_INSN2(seq, line, setblockparam, INT2FIX((idx) + VM_ENV_DATA_SIZE - 1), INT2FIX(level));
1342+
}
1343+
else {
1344+
ADD_INSN2(seq, line, setlocal, INT2FIX((idx) + VM_ENV_DATA_SIZE - 1), INT2FIX(level));
1345+
}
1346+
}
1347+
1348+
1349+
13011350
static void
13021351
iseq_calc_param_size(rb_iseq_t *iseq)
13031352
{
@@ -4173,6 +4222,16 @@ setup_args(rb_iseq_t *iseq, LINK_ANCHOR *const args, NODE *argn,
41734222
}
41744223

41754224
if (*flag & VM_CALL_ARGS_BLOCKARG) {
4225+
if (LIST_SIZE_ONE(arg_block)) {
4226+
LINK_ELEMENT *elem = FIRST_ELEMENT(arg_block);
4227+
if (elem->type == ISEQ_ELEMENT_INSN) {
4228+
INSN *iobj = (INSN *)elem;
4229+
if (iobj->insn_id == BIN(getblockparam)) {
4230+
iobj->insn_id = BIN(getlocal);
4231+
*flag |= VM_CALL_ARGS_BLOCKARG_BLOCKPARAM;
4232+
}
4233+
}
4234+
}
41764235
ADD_SEQ(args, arg_block);
41774236
}
41784237
return argc;

insns.def

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,53 @@ setlocal
8080
(void)RB_DEBUG_COUNTER_INC_IF(lvar_set_dynamic, level > 0);
8181
}
8282

83+
/**
84+
@c variable
85+
@e Get a block parameter.
86+
@j ブロックパラメータを取得する。
87+
*/
88+
DEFINE_INSN
89+
getblockparam
90+
(lindex_t idx, rb_num_t level)
91+
()
92+
(VALUE val)
93+
{
94+
const VALUE *ep = vm_get_ep(GET_EP(), level);
95+
VM_ASSERT(VM_ENV_LOCAL_P(ep));
96+
97+
if (!VM_ENV_FLAGS(ep, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM)) {
98+
val = rb_vm_bh_to_procval(th, VM_ENV_BLOCK_HANDLER(ep));
99+
vm_env_write(ep, -(int)idx, val);
100+
VM_ENV_FLAGS_SET(ep, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM);
101+
}
102+
else {
103+
val = *(ep - idx);
104+
RB_DEBUG_COUNTER_INC(lvar_get);
105+
(void)RB_DEBUG_COUNTER_INC_IF(lvar_get_dynamic, level > 0);
106+
}
107+
}
108+
109+
/**
110+
@c variable
111+
@e Set block parameter.
112+
@j ブロックパラメータを設定する。
113+
*/
114+
DEFINE_INSN
115+
setblockparam
116+
(lindex_t idx, rb_num_t level)
117+
(VALUE val)
118+
()
119+
{
120+
const VALUE *ep = vm_get_ep(GET_EP(), level);
121+
VM_ASSERT(VM_ENV_LOCAL_P(ep));
122+
123+
vm_env_write(ep, -(int)idx, val);
124+
RB_DEBUG_COUNTER_INC(lvar_set);
125+
(void)RB_DEBUG_COUNTER_INC_IF(lvar_set_dynamic, level > 0);
126+
127+
VM_ENV_FLAGS_SET(ep, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM);
128+
}
129+
83130
/**
84131
@c variable
85132
@e Get value of special local variable ($~, $_, ..).

proc.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,8 @@ bind_eval(int argc, VALUE *argv, VALUE bindval)
384384
return rb_f_eval(argc+1, args, Qnil /* self will be searched in eval */);
385385
}
386386

387+
VALUE rb_vm_bh_to_procval(rb_thread_t *th, VALUE block_handler);
388+
387389
static const VALUE *
388390
get_local_variable_ptr(const rb_env_t **envp, ID lid)
389391
{
@@ -397,6 +399,16 @@ get_local_variable_ptr(const rb_env_t **envp, ID lid)
397399

398400
for (i=0; i<iseq->body->local_table_size; i++) {
399401
if (iseq->body->local_table[i] == lid) {
402+
if (iseq->body->local_iseq == iseq &&
403+
iseq->body->param.flags.has_block &&
404+
(unsigned int)iseq->body->param.block_start == i) {
405+
const VALUE *ep = env->ep;
406+
if (!VM_ENV_FLAGS(ep, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM)) {
407+
RB_OBJ_WRITE(env, &env->env[i], rb_vm_bh_to_procval(GET_THREAD(), VM_ENV_BLOCK_HANDLER(ep)));
408+
VM_ENV_FLAGS_SET(ep, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM);
409+
}
410+
}
411+
400412
*envp = env;
401413
return &env->env[i];
402414
}

safe.c

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,17 +65,25 @@ safe_getter(void)
6565
static void
6666
safe_setter(VALUE val)
6767
{
68-
int level = NUM2INT(val);
6968
rb_thread_t *th = GET_THREAD();
69+
int current_level = th->ec.safe_level;
70+
int level = NUM2INT(val);
7071

71-
if (level < th->ec.safe_level) {
72+
if (level == current_level) {
73+
return;
74+
}
75+
else if (level < current_level) {
7276
rb_raise(rb_eSecurityError,
7377
"tried to downgrade safe level from %d to %d",
74-
th->ec.safe_level, level);
78+
current_level, level);
7579
}
76-
if (level > SAFE_LEVEL_MAX) {
80+
else if (level > SAFE_LEVEL_MAX) {
7781
rb_raise(rb_eArgError, "$SAFE=2 to 4 are obsolete");
7882
}
83+
84+
/* block parameters */
85+
rb_vm_stack_to_heap(th);
86+
7987
th->ec.safe_level = level;
8088
}
8189

test/ruby/test_optimization.rb

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,4 +575,69 @@ def test_retry_label_in_unreachable_chunk
575575
def t; if false; case 42; when s {}; end; end; end
576576
end;
577577
end
578+
579+
def bptest_yield &b
580+
yield
581+
end
582+
583+
def bptest_yield_pass &b
584+
bptest_yield(&b)
585+
end
586+
587+
def bptest_bp_value &b
588+
b
589+
end
590+
591+
def bptest_bp_pass_bp_value &b
592+
bptest_bp_value(&b)
593+
end
594+
595+
def bptest_binding &b
596+
binding
597+
end
598+
599+
def bptest_set &b
600+
b = Proc.new{2}
601+
end
602+
603+
def test_block_parameter
604+
assert_equal(1, bptest_yield{1})
605+
assert_equal(1, bptest_yield_pass{1})
606+
assert_equal(1, send(:bptest_yield){1})
607+
608+
assert_equal(Proc, bptest_bp_value{}.class)
609+
assert_equal nil, bptest_bp_value
610+
assert_equal(Proc, bptest_bp_pass_bp_value{}.class)
611+
assert_equal nil, bptest_bp_pass_bp_value
612+
613+
assert_equal Proc, bptest_binding{}.local_variable_get(:b).class
614+
615+
assert_equal 2, bptest_set{1}.call
616+
end
617+
618+
def test_block_parameter_should_not_create_objects
619+
assert_separately [], <<-END
620+
#
621+
def foo &b
622+
end
623+
h1 = {}; h2 = {}
624+
ObjectSpace.count_objects(h1) # reharsal
625+
ObjectSpace.count_objects(h1)
626+
foo{}
627+
ObjectSpace.count_objects(h2)
628+
629+
assert_equal 0, h2[:TOTAL] - h1[:TOTAL]
630+
END
631+
end
632+
633+
def test_block_parameter_should_restore_safe_level
634+
assert_separately [], <<-END
635+
#
636+
def foo &b
637+
$SAFE = 1
638+
b.call
639+
end
640+
assert_equal 0, foo{$SAFE}
641+
END
642+
end
578643
end

0 commit comments

Comments
 (0)