Emit special instruction for array literal + .(hash|min|max)

This commit introduces a new instruction `opt_newarray_send` which is
used when there is an array literal followed by either the `hash`,
`min`, or `max` method.

```
[a, b, c].hash
```

Will emit an `opt_newarray_send` instruction.  This instruction falls
back to a method call if the "interested" method has been monkey
patched.

Here are some examples of the instructions generated:

```
$ ./miniruby --dump=insns -e '[@a, @b].max'
== disasm: #<ISeq:<main>@-e:1 (1,0)-(1,12)> (catch: FALSE)
0000 getinstancevariable                    :@a, <is:0>               (   1)[Li]
0003 getinstancevariable                    :@b, <is:1>
0006 opt_newarray_send                      2, :max
0009 leave
$ ./miniruby --dump=insns -e '[@a, @b].min'
== disasm: #<ISeq:<main>@-e:1 (1,0)-(1,12)> (catch: FALSE)
0000 getinstancevariable                    :@a, <is:0>               (   1)[Li]
0003 getinstancevariable                    :@b, <is:1>
0006 opt_newarray_send                      2, :min
0009 leave
$ ./miniruby --dump=insns -e '[@a, @b].hash'
== disasm: #<ISeq:<main>@-e:1 (1,0)-(1,13)> (catch: FALSE)
0000 getinstancevariable                    :@a, <is:0>               (   1)[Li]
0003 getinstancevariable                    :@b, <is:1>
0006 opt_newarray_send                      2, :hash
0009 leave
```

[Feature #18897] [ruby-core:109147]

Co-authored-by: John Hawthorn <jhawthorn@github.com>
This commit is contained in:
Aaron Patterson 2022-06-06 17:27:56 -07:00 committed by Aaron Patterson
parent 3016f30c95
commit c5fc1ce975
Notes: git 2023-04-19 00:16:56 +00:00
9 changed files with 75 additions and 45 deletions

30
array.c
View File

@ -5350,6 +5350,23 @@ rb_ary_eql(VALUE ary1, VALUE ary2)
return rb_exec_recursive_paired(recursive_eql, ary1, ary2, ary2); return rb_exec_recursive_paired(recursive_eql, ary1, ary2, ary2);
} }
VALUE
rb_ary_hash_values(long len, const VALUE *elements)
{
long i;
st_index_t h;
VALUE n;
h = rb_hash_start(len);
h = rb_hash_uint(h, (st_index_t)rb_ary_hash_values);
for (i=0; i<len; i++) {
n = rb_hash(elements[i]);
h = rb_hash_uint(h, NUM2LONG(n));
}
h = rb_hash_end(h);
return ST2FIX(h);
}
/* /*
* call-seq: * call-seq:
* array.hash -> integer * array.hash -> integer
@ -5366,18 +5383,7 @@ rb_ary_eql(VALUE ary1, VALUE ary2)
static VALUE static VALUE
rb_ary_hash(VALUE ary) rb_ary_hash(VALUE ary)
{ {
long i; return rb_ary_hash_values(RARRAY_LEN(ary), RARRAY_CONST_PTR(ary));
st_index_t h;
VALUE n;
h = rb_hash_start(RARRAY_LEN(ary));
h = rb_hash_uint(h, (st_index_t)rb_ary_hash);
for (i=0; i<RARRAY_LEN(ary); i++) {
n = rb_hash(RARRAY_AREF(ary, i));
h = rb_hash_uint(h, NUM2LONG(n));
}
h = rb_hash_end(h);
return ST2FIX(h);
} }
/* /*

View File

@ -214,9 +214,11 @@ tests = [
'true'.freeze 'true'.freeze
}, },
[ 'opt_newarray_max', %q{ [ ].max.nil? }, ], [ 'opt_newarray_send', %q{ ![ ].hash.nil? }, ],
[ 'opt_newarray_max', %q{ [1, x = 2, 3].max == 3 }, ],
[ 'opt_newarray_max', <<-'},', ], # { [ 'opt_newarray_send', %q{ [ ].max.nil? }, ],
[ 'opt_newarray_send', %q{ [1, x = 2, 3].max == 3 }, ],
[ 'opt_newarray_send', <<-'},', ], # {
class Array class Array
def max def max
true true
@ -224,9 +226,9 @@ tests = [
end end
[1, x = 2, 3].max [1, x = 2, 3].max
}, },
[ 'opt_newarray_min', %q{ [ ].min.nil? }, ], [ 'opt_newarray_send', %q{ [ ].min.nil? }, ],
[ 'opt_newarray_min', %q{ [3, x = 2, 1].min == 1 }, ], [ 'opt_newarray_send', %q{ [3, x = 2, 1].min == 1 }, ],
[ 'opt_newarray_min', <<-'},', ], # { [ 'opt_newarray_send', <<-'},', ], # {
class Array class Array
def min def min
true true

View File

@ -3708,17 +3708,22 @@ iseq_specialized_instruction(rb_iseq_t *iseq, INSN *iobj)
if ((vm_ci_flag(ci) & VM_CALL_ARGS_SIMPLE) && vm_ci_argc(ci) == 0) { if ((vm_ci_flag(ci) & VM_CALL_ARGS_SIMPLE) && vm_ci_argc(ci) == 0) {
switch (vm_ci_mid(ci)) { switch (vm_ci_mid(ci)) {
case idMax: case idMax:
iobj->insn_id = BIN(opt_newarray_max);
ELEM_REMOVE(&niobj->link);
return COMPILE_OK;
case idMin: case idMin:
iobj->insn_id = BIN(opt_newarray_min); case idHash:
{
rb_num_t num = (rb_num_t)iobj->operands[0];
iobj->insn_id = BIN(opt_newarray_send);
iobj->operands = compile_data_calloc2(iseq, insn_len(iobj->insn_id) - 1, sizeof(VALUE));
iobj->operands[0] = (VALUE)num;
iobj->operands[1] = (VALUE)rb_id2sym(vm_ci_mid(ci));
iobj->operand_size = insn_len(iobj->insn_id) - 1;
ELEM_REMOVE(&niobj->link); ELEM_REMOVE(&niobj->link);
return COMPILE_OK; return COMPILE_OK;
} }
} }
} }
} }
}
if (IS_INSN_ID(iobj, send)) { if (IS_INSN_ID(iobj, send)) {
const struct rb_callinfo *ci = (struct rb_callinfo *)OPERAND_AT(iobj, 0); const struct rb_callinfo *ci = (struct rb_callinfo *)OPERAND_AT(iobj, 0);

View File

@ -2,6 +2,7 @@
firstline, predefined = __LINE__+1, %[\ firstline, predefined = __LINE__+1, %[\
max max
min min
hash
freeze freeze
nil? nil?
inspect inspect

View File

@ -899,8 +899,8 @@ opt_str_uminus
} }
DEFINE_INSN DEFINE_INSN
opt_newarray_max opt_newarray_send
(rb_num_t num) (rb_num_t num, ID method)
(...) (...)
(VALUE val) (VALUE val)
/* This instruction typically has no funcalls. But it compares array /* This instruction typically has no funcalls. But it compares array
@ -909,20 +909,21 @@ opt_newarray_max
* cannot but mark it being not leaf. */ * cannot but mark it being not leaf. */
// attr bool leaf = false; /* has rb_funcall() */ // attr bool leaf = false; /* has rb_funcall() */
// attr rb_snum_t sp_inc = 1 - (rb_snum_t)num; // attr rb_snum_t sp_inc = 1 - (rb_snum_t)num;
// attr rb_snum_t comptime_sp_inc = 1 - (rb_snum_t)num;
{ {
val = vm_opt_newarray_max(ec, num, STACK_ADDR_FROM_TOP(num)); switch(method) {
} case idHash:
val = vm_opt_newarray_hash(ec, num, STACK_ADDR_FROM_TOP(num));
DEFINE_INSN break;
opt_newarray_min case idMin:
(rb_num_t num)
(...)
(VALUE val)
/* Same discussion as opt_newarray_max. */
// attr bool leaf = false; /* has rb_funcall() */
// attr rb_snum_t sp_inc = 1 - (rb_snum_t)num;
{
val = vm_opt_newarray_min(ec, num, STACK_ADDR_FROM_TOP(num)); val = vm_opt_newarray_min(ec, num, STACK_ADDR_FROM_TOP(num));
break;
case idMax:
val = vm_opt_newarray_max(ec, num, STACK_ADDR_FROM_TOP(num));
break;
default:
rb_bug("unreachable");
}
} }
/* super(args) # args.size => num */ /* super(args) # args.size => num */

View File

@ -23,6 +23,7 @@
#define RARRAY_PTR_IN_USE_FLAG FL_USER14 #define RARRAY_PTR_IN_USE_FLAG FL_USER14
/* array.c */ /* array.c */
VALUE rb_ary_hash_values(long len, const VALUE *elements);
VALUE rb_ary_last(int, const VALUE *, VALUE); VALUE rb_ary_last(int, const VALUE *, VALUE);
void rb_ary_set_len(VALUE, long); void rb_ary_set_len(VALUE, long);
void rb_ary_delete_same(VALUE, VALUE); void rb_ary_delete_same(VALUE, VALUE);

View File

@ -31,6 +31,7 @@ enum ruby_basic_operators {
BOP_UMINUS, BOP_UMINUS,
BOP_MAX, BOP_MAX,
BOP_MIN, BOP_MIN,
BOP_HASH,
BOP_CALL, BOP_CALL,
BOP_AND, BOP_AND,
BOP_OR, BOP_OR,

1
vm.c
View File

@ -2051,6 +2051,7 @@ vm_init_redefined_flag(void)
OP(UMinus, UMINUS), (C(String)); OP(UMinus, UMINUS), (C(String));
OP(Max, MAX), (C(Array)); OP(Max, MAX), (C(Array));
OP(Min, MIN), (C(Array)); OP(Min, MIN), (C(Array));
OP(Hash, HASH), (C(Array));
OP(Call, CALL), (C(Proc)); OP(Call, CALL), (C(Proc));
OP(And, AND), (C(Integer)); OP(And, AND), (C(Integer));
OP(Or, OR), (C(Integer)); OP(Or, OR), (C(Integer));

View File

@ -5377,6 +5377,18 @@ rb_vm_opt_newarray_min(rb_execution_context_t *ec, rb_num_t num, const VALUE *pt
return vm_opt_newarray_min(ec, num, ptr); return vm_opt_newarray_min(ec, num, ptr);
} }
static VALUE
vm_opt_newarray_hash(rb_execution_context_t *ec, rb_num_t num, const VALUE *ptr)
{
// If Array#hash is _not_ monkeypatched, use the optimized call
if (BASIC_OP_UNREDEFINED_P(BOP_HASH, ARRAY_REDEFINED_OP_FLAG)) {
return rb_ary_hash_values(num, ptr);
}
else {
return rb_vm_call_with_refinements(ec, rb_ary_new4(num, ptr), idHash, 0, NULL, RB_NO_KEYWORDS);
}
}
#undef id_cmp #undef id_cmp
#define IMEMO_CONST_CACHE_SHAREABLE IMEMO_FL_USER0 #define IMEMO_CONST_CACHE_SHAREABLE IMEMO_FL_USER0