Introduce a specialize instruction for Array#pack

Instructions for this code:

```ruby
  # frozen_string_literal: true

[a].pack("C")
```

Before this commit:

```
== disasm: #<ISeq:<main>@test.rb:1 (1,0)-(3,13)>
0000 putself                                                          (   3)[Li]
0001 opt_send_without_block                 <calldata!mid:a, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0003 newarray                               1
0005 putobject                              "C"
0007 opt_send_without_block                 <calldata!mid:pack, argc:1, ARGS_SIMPLE>
0009 leave
```

After this commit:

```
== disasm: #<ISeq:<main>@test.rb:1 (1,0)-(3,13)>
0000 putself                                                          (   3)[Li]
0001 opt_send_without_block                 <calldata!mid:a, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0003 putobject                              "C"
0005 opt_newarray_send                      2, :pack
0008 leave
```

Co-authored-by: Maxime Chevalier-Boisvert <maxime.chevalierboisvert@shopify.com>
Co-authored-by: Aaron Patterson <tenderlove@ruby-lang.org>
This commit is contained in:
Nobuyoshi Nakada 2024-05-23 11:23:26 -07:00 коммит произвёл Aaron Patterson
Родитель e5e079e70f
Коммит 49fcd33e13
15 изменённых файлов: 132 добавлений и 5 удалений

13
array.c
Просмотреть файл

@ -880,6 +880,19 @@ rb_ary_free(VALUE ary)
}
}
VALUE
rb_setup_fake_ary(struct RArray *fake_ary, const VALUE *list, long len, bool freeze)
{
fake_ary->basic.flags = T_ARRAY;
VALUE ary = (VALUE)fake_ary;
RBASIC_CLEAR_CLASS(ary);
ARY_SET_PTR(ary, list);
ARY_SET_HEAP_LEN(ary, len);
ARY_SET_CAPA(ary, len);
if (freeze) OBJ_FREEZE(ary);
return ary;
}
size_t
rb_ary_memsize(VALUE ary)
{

Просмотреть файл

@ -4048,6 +4048,8 @@ insn_set_specialized_instruction(rb_iseq_t *iseq, INSN *iobj, int insn_id)
return COMPILE_OK;
}
#define vm_ci_simple(ci) (vm_ci_flag(ci) & VM_CALL_ARGS_SIMPLE)
static int
iseq_specialized_instruction(rb_iseq_t *iseq, INSN *iobj)
{
@ -4059,24 +4061,43 @@ iseq_specialized_instruction(rb_iseq_t *iseq, INSN *iobj)
INSN *niobj = (INSN *)iobj->link.next;
if (IS_INSN_ID(niobj, send)) {
const struct rb_callinfo *ci = (struct rb_callinfo *)OPERAND_AT(niobj, 0);
if ((vm_ci_flag(ci) & VM_CALL_ARGS_SIMPLE) && vm_ci_argc(ci) == 0) {
if (vm_ci_simple(ci) && vm_ci_argc(ci) == 0) {
switch (vm_ci_mid(ci)) {
case idMax:
case idMin:
case idHash:
{
VALUE num = iobj->operands[0];
int operand_len = insn_len(BIN(opt_newarray_send)) - 1;
iobj->insn_id = BIN(opt_newarray_send);
iobj->operands = compile_data_calloc2(iseq, insn_len(iobj->insn_id) - 1, sizeof(VALUE));
iobj->operands = compile_data_calloc2(iseq, operand_len, sizeof(VALUE));
iobj->operands[0] = num;
iobj->operands[1] = rb_id2sym(vm_ci_mid(ci));
iobj->operand_size = insn_len(iobj->insn_id) - 1;
iobj->operand_size = operand_len;
ELEM_REMOVE(&niobj->link);
return COMPILE_OK;
}
}
}
}
else if ((IS_INSN_ID(niobj, putstring) ||
(IS_INSN_ID(niobj, putobject) && RB_TYPE_P(OPERAND_AT(niobj, 0), T_STRING))) &&
IS_NEXT_INSN_ID(&niobj->link, send)) {
const struct rb_callinfo *ci = (struct rb_callinfo *)OPERAND_AT((INSN *)niobj->link.next, 0);
if (vm_ci_simple(ci) && vm_ci_argc(ci) == 1 && vm_ci_mid(ci) == idPack) {
VALUE num = iobj->operands[0];
int operand_len = insn_len(BIN(opt_newarray_send)) - 1;
iobj->insn_id = BIN(opt_newarray_send);
iobj->operands = compile_data_calloc2(iseq, operand_len, sizeof(VALUE));
iobj->operands[0] = FIXNUM_INC(num, 1);
iobj->operands[1] = rb_id2sym(vm_ci_mid(ci));
iobj->operand_size = operand_len;
ELEM_REMOVE(&iobj->link);
ELEM_REMOVE(niobj->link.next);
ELEM_INSERT_NEXT(&niobj->link, &iobj->link);
return COMPILE_OK;
}
}
}
if (IS_INSN_ID(iobj, send)) {
@ -4084,7 +4105,7 @@ iseq_specialized_instruction(rb_iseq_t *iseq, INSN *iobj)
const rb_iseq_t *blockiseq = (rb_iseq_t *)OPERAND_AT(iobj, 1);
#define SP_INSN(opt) insn_set_specialized_instruction(iseq, iobj, BIN(opt_##opt))
if (vm_ci_flag(ci) & VM_CALL_ARGS_SIMPLE) {
if (vm_ci_simple(ci)) {
switch (vm_ci_argc(ci)) {
case 0:
switch (vm_ci_mid(ci)) {

Просмотреть файл

@ -59,6 +59,7 @@ firstline, predefined = __LINE__+1, %[\
name
nil
path
pack
_ UScore

Просмотреть файл

@ -977,6 +977,9 @@ opt_newarray_send
case idMax:
val = vm_opt_newarray_max(ec, num, STACK_ADDR_FROM_TOP(num));
break;
case idPack:
val = rb_vm_opt_newarray_pack(ec, (long)num-1, STACK_ADDR_FROM_TOP(num), TOPN(0));
break;
default:
rb_bug("unreachable");
}

Просмотреть файл

@ -37,6 +37,7 @@ enum ruby_basic_operators {
BOP_OR,
BOP_CMP,
BOP_DEFAULT,
BOP_PACK,
BOP_LAST_
};

6
pack.c
Просмотреть файл

@ -782,6 +782,12 @@ pack_pack(rb_execution_context_t *ec, VALUE ary, VALUE fmt, VALUE buffer)
return res;
}
VALUE
rb_ec_pack_ary(rb_execution_context_t *ec, VALUE ary, VALUE fmt, VALUE buffer)
{
return pack_pack(ec, ary, fmt, buffer);
}
static const char uu_table[] =
"`!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_";
static const char b64_table[] =

Просмотреть файл

@ -519,6 +519,7 @@ extern VALUE rb_vm_getclassvariable(const rb_iseq_t *iseq, const rb_control_fram
extern VALUE rb_vm_opt_newarray_min(rb_execution_context_t *ec, rb_num_t num, const VALUE *ptr);
extern VALUE rb_vm_opt_newarray_max(rb_execution_context_t *ec, rb_num_t num, const VALUE *ptr);
extern VALUE rb_vm_opt_newarray_hash(rb_execution_context_t *ec, rb_num_t num, const VALUE *ptr);
extern VALUE rb_vm_opt_newarray_pack(rb_execution_context_t *ec, rb_num_t num, const VALUE *ptr, VALUE fmt);
extern VALUE rb_vm_splat_array(VALUE flag, VALUE array);
extern bool rb_simple_iseq_p(const rb_iseq_t *iseq);
extern bool rb_vm_defined(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, rb_num_t op_type, VALUE obj, VALUE v);

Просмотреть файл

@ -691,6 +691,10 @@ module RubyVM::RJIT # :nodoc: all
Primitive.cexpr! %q{ SIZET2NUM((size_t)rb_vm_opt_newarray_min) }
end
def C.rb_vm_opt_newarray_pack
Primitive.cexpr! %q{ SIZET2NUM((size_t)rb_vm_opt_newarray_pack) }
end
def C.rb_vm_set_ivar_id
Primitive.cexpr! %q{ SIZET2NUM((size_t)rb_vm_set_ivar_id) }
end

Просмотреть файл

@ -895,4 +895,22 @@ EXPECTED
}
assert_equal [nil], "a".unpack("C", offset: 1)
end
def test_monkey_pack
assert_separately([], <<-'end;')
$-w = false
class Array
alias :old_pack :pack
def pack _; "oh no"; end
end
v = [2 ** 15].pack('n')
class Array
alias :pack :old_pack
end
assert_equal "oh no", v
end;
end
end

Просмотреть файл

@ -540,6 +540,7 @@ generator = BindingGenerator.new(
rb_vm_opt_newarray_min
rb_vm_opt_newarray_max
rb_vm_opt_newarray_hash
rb_vm_opt_newarray_pack
rb_vm_setinstancevariable
rb_vm_splat_array
rjit_full_cfunc_return

1
vm.c
Просмотреть файл

@ -2278,6 +2278,7 @@ vm_redefinition_bop_for_id(ID mid)
OP(NilP, NIL_P);
OP(Cmp, CMP);
OP(Default, DEFAULT);
OP(Pack, PACK);
#undef OP
}
return -1;

Просмотреть файл

@ -5932,6 +5932,22 @@ rb_vm_opt_newarray_hash(rb_execution_context_t *ec, rb_num_t num, const VALUE *p
return vm_opt_newarray_hash(ec, num, ptr);
}
VALUE rb_setup_fake_ary(struct RArray *fake_ary, const VALUE *list, long len, bool freeze);
VALUE rb_ec_pack_ary(rb_execution_context_t *ec, VALUE ary, VALUE fmt, VALUE buffer);
VALUE
rb_vm_opt_newarray_pack(rb_execution_context_t *ec, rb_num_t num, const VALUE *ptr, VALUE fmt)
{
if (BASIC_OP_UNREDEFINED_P(BOP_PACK, ARRAY_REDEFINED_OP_FLAG)) {
struct RArray fake_ary;
VALUE ary = rb_setup_fake_ary(&fake_ary, ptr, num, true);
return rb_ec_pack_ary(ec, ary, fmt, Qnil);
}
else {
return rb_vm_call_with_refinements(ec, rb_ary_new4(num, ptr), idPack, 1, &fmt, RB_PASS_CALLED_KEYWORDS);
}
}
#undef id_cmp
#define IMEMO_CONST_CACHE_SHAREABLE IMEMO_FL_USER0

Просмотреть файл

@ -4235,11 +4235,50 @@ fn gen_opt_newarray_send(
gen_opt_newarray_max(jit, asm, _ocb)
} else if method == ID!(hash) {
gen_opt_newarray_hash(jit, asm, _ocb)
} else if method == ID!(pack) {
gen_opt_newarray_pack(jit, asm, _ocb)
} else {
None
}
}
fn gen_opt_newarray_pack(
jit: &mut JITState,
asm: &mut Assembler,
_ocb: &mut OutlinedCb,
) -> Option<CodegenStatus> {
// num == 4 ( for this code )
let num = jit.get_arg(0).as_u32();
// Save the PC and SP because we may call #pack
jit_prepare_non_leaf_call(jit, asm);
extern "C" {
fn rb_vm_opt_newarray_pack(ec: EcPtr, num: u32, elts: *const VALUE, fmt: VALUE) -> VALUE;
}
let values_opnd = asm.ctx.sp_opnd(-(num as i32));
let values_ptr = asm.lea(values_opnd);
let fmt_string = asm.ctx.sp_opnd(-1);
let val_opnd = asm.ccall(
rb_vm_opt_newarray_pack as *const u8,
vec![
EC,
(num - 1).into(),
values_ptr,
fmt_string
],
);
asm.stack_pop(num.as_usize());
let stack_ret = asm.stack_push(Type::CString);
asm.mov(stack_ret, val_opnd);
Some(KeepCompiling)
}
fn gen_opt_newarray_hash(
jit: &mut JITState,
asm: &mut Assembler,

Просмотреть файл

@ -802,6 +802,7 @@ pub(crate) mod ids {
name: min content: b"min"
name: max content: b"max"
name: hash content: b"hash"
name: pack content: b"pack"
name: respond_to_missing content: b"respond_to_missing?"
name: to_ary content: b"to_ary"
name: eq content: b"=="

Просмотреть файл

@ -342,7 +342,8 @@ pub const BOP_AND: ruby_basic_operators = 28;
pub const BOP_OR: ruby_basic_operators = 29;
pub const BOP_CMP: ruby_basic_operators = 30;
pub const BOP_DEFAULT: ruby_basic_operators = 31;
pub const BOP_LAST_: ruby_basic_operators = 32;
pub const BOP_PACK: ruby_basic_operators = 32;
pub const BOP_LAST_: ruby_basic_operators = 33;
pub type ruby_basic_operators = u32;
pub type rb_serial_t = ::std::os::raw::c_ulonglong;
pub const imemo_env: imemo_type = 0;