1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

YJIT: implement calls to ivar getter methods

This commit is contained in:
Alan Wu 2021-04-01 14:31:57 -04:00
parent 53ff2d791d
commit 4ea2e753f6
3 changed files with 146 additions and 40 deletions

View file

@ -505,3 +505,76 @@ assert_equal '[Iseq, Cfunc]', %q{
[Iseq.call_itself, Cfunc.call_itself]
}
# attr_reader method
assert_equal '[100, 299]', %q{
class A
attr_reader :foo
def initialize
@foo = 100
end
# Make it extended
def fill!
@bar = @jojo = @as = @sdfsdf = @foo = 299
end
end
def bar(ins)
ins.foo
end
ins = A.new
oth = A.new
oth.fill!
bar(ins)
bar(oth)
[bar(ins), bar(oth)]
}
# get ivar on String
assert_equal '[nil, nil, 42, 42]', %q{
# @foo to exercise the getinstancevariable instruction
public def get_foo
@foo
end
get_foo
get_foo # compile it for the top level object
class String
attr_reader :foo
end
def run
str = String.new
getter = str.foo
insn = str.get_foo
str.instance_variable_set(:@foo, 42)
[getter, insn, str.foo, str.get_foo]
end
run
run
}
# splatting an empty array on a getter
assert_equal '42', %q{
@foo = 42
module Kernel
attr_reader :foo
end
def run
foo(*[])
end
run
run
}

View file

@ -111,6 +111,8 @@ jit_peek_at_self(jitstate_t *jit, ctx_t *ctx)
return jit->ec->cfp->self;
}
static bool jit_guard_known_klass(jitstate_t *jit, ctx_t* ctx, VALUE known_klass, insn_opnd_t insn_opnd, const int max_chain_depth, uint8_t *side_exit);
// Save YJIT registers prior to a C call
static void
yjit_save_regs(codeblock_t* cb)
@ -371,6 +373,9 @@ yjit_gen_block(ctx_t *ctx, block_t *block, rb_execution_context_t *ec)
// If we can't compile this instruction
// exit to the interpreter and stop compiling
if (status == YJIT_CANT_COMPILE) {
// TODO: if the codegen funcion makes changes to ctx and then return YJIT_CANT_COMPILE,
// the exit this generates would be wrong. We could save a copy of the entry context
// and assert that ctx is the same here.
yjit_gen_exit(&jit, ctx, cb);
break;
}
@ -717,53 +722,42 @@ enum {
OSWB_MAX_DEPTH = 5, // up to 5 different classes
};
// Codegen for getting an instance variable.
// Preconditions:
// - receiver is in REG0
// - receiver has the same class as CLASS_OF(comptime_receiver)
// - ctx has not been modified since entry to the codegen of the instruction being compiled
static codegen_status_t
gen_getinstancevariable(jitstate_t* jit, ctx_t* ctx)
gen_get_ivar(jitstate_t *jit, ctx_t *ctx, const int max_chain_depth, VALUE comptime_receiver, ID ivar_name, bool pop_receiver, uint8_t *side_exit)
{
// Defer compilation so we can specialize a runtime `self`
if (!jit_at_current_insn(jit)) {
defer_compilation(jit->block, jit->insn_idx, ctx);
return YJIT_END_BLOCK;
}
// Specialize base on the compile time self
VALUE self_val = jit_peek_at_self(jit, ctx);
VALUE self_klass = rb_class_of(self_val);
// Create a size-exit to fall back to the interpreter
uint8_t *side_exit = yjit_side_exit(jit, ctx);
VALUE comptime_val_klass = CLASS_OF(comptime_receiver);
const ctx_t starting_context = *ctx; // make a copy for use with jit_chain_guard
// If the class uses the default allocator, instances should all be T_OBJECT
// NOTE: This assumes nobody changes the allocator of the class after allocation.
// Eventually, we can encode whether an object is T_OBJECT or not
// inside object shapes.
if (rb_get_alloc_func(self_klass) != rb_class_allocate_instance) {
if (rb_get_alloc_func(comptime_val_klass) != rb_class_allocate_instance) {
GEN_COUNTER_INC(cb, getivar_not_object);
return YJIT_CANT_COMPILE;
}
RUBY_ASSERT(BUILTIN_TYPE(self_val) == T_OBJECT); // because we checked the allocator
RUBY_ASSERT(BUILTIN_TYPE(comptime_receiver) == T_OBJECT); // because we checked the allocator
ID id = (ID)jit_get_arg(jit, 0);
// ID for the name of the ivar
ID id = ivar_name;
struct rb_iv_index_tbl_entry *ent;
struct st_table *iv_index_tbl = ROBJECT_IV_INDEX_TBL(self_val);
struct st_table *iv_index_tbl = ROBJECT_IV_INDEX_TBL(comptime_receiver);
// Lookup index for the ivar the instruction loads
if (iv_index_tbl && rb_iv_index_tbl_lookup(iv_index_tbl, id, &ent)) {
uint32_t ivar_index = ent->index;
// Load self from CFP
mov(cb, REG0, member_opnd(REG_CFP, rb_control_frame_t, self));
if (pop_receiver) {
(void)ctx_stack_pop(ctx, 1);
}
guard_self_is_heap(cb, REG0, COUNTED_EXIT(side_exit, getivar_se_self_not_heap), ctx);
// Guard that self has a known class
x86opnd_t klass_opnd = mem_opnd(64, REG0, offsetof(struct RBasic, klass));
mov(cb, REG1, klass_opnd);
x86opnd_t serial_opnd = mem_opnd(64, REG1, offsetof(struct RClass, class_serial));
cmp(cb, serial_opnd, imm_opnd(RCLASS_SERIAL(self_klass)));
jit_chain_guard(JCC_JNE, jit, ctx, GETIVAR_MAX_DEPTH, side_exit);
// Compile time self is embedded and the ivar index is within the object
if (RB_FL_TEST_RAW(self_val, ROBJECT_EMBED) && ivar_index < ROBJECT_EMBED_LEN_MAX) {
// Compile time self is embedded and the ivar index lands within the object
if (RB_FL_TEST_RAW(comptime_receiver, ROBJECT_EMBED) && ivar_index < ROBJECT_EMBED_LEN_MAX) {
// See ROBJECT_IVPTR() from include/ruby/internal/core/robject.h
// Guard that self is embedded
@ -771,13 +765,14 @@ gen_getinstancevariable(jitstate_t* jit, ctx_t* ctx)
ADD_COMMENT(cb, "guard embedded getivar");
x86opnd_t flags_opnd = member_opnd(REG0, struct RBasic, flags);
test(cb, flags_opnd, imm_opnd(ROBJECT_EMBED));
jit_chain_guard(JCC_JZ, jit, ctx, GETIVAR_MAX_DEPTH, side_exit);
jit_chain_guard(JCC_JZ, jit, &starting_context, max_chain_depth, side_exit);
// Load the variable
x86opnd_t ivar_opnd = mem_opnd(64, REG0, offsetof(struct RObject, as.ary) + ivar_index * SIZEOF_VALUE);
mov(cb, REG1, ivar_opnd);
// Guard that the variable is not Qundef
// TODO: use cmov to push Qnil in this case
cmp(cb, REG1, imm_opnd(Qundef));
je_ptr(cb, COUNTED_EXIT(side_exit, getivar_undef));
@ -786,14 +781,14 @@ gen_getinstancevariable(jitstate_t* jit, ctx_t* ctx)
mov(cb, out_opnd, REG1);
}
else {
// Compile time self is *not* embeded.
// Compile value is *not* embeded.
// Guard that self is *not* embedded
// Guard that value is *not* embedded
// See ROBJECT_IVPTR() from include/ruby/internal/core/robject.h
ADD_COMMENT(cb, "guard extended getivar");
x86opnd_t flags_opnd = member_opnd(REG0, struct RBasic, flags);
test(cb, flags_opnd, imm_opnd(ROBJECT_EMBED));
jit_chain_guard(JCC_JNZ, jit, ctx, GETIVAR_MAX_DEPTH, side_exit);
jit_chain_guard(JCC_JNZ, jit, &starting_context, max_chain_depth, side_exit);
// check that the extended table is big enough
if (ivar_index >= ROBJECT_EMBED_LEN_MAX + 1) {
@ -825,9 +820,36 @@ gen_getinstancevariable(jitstate_t* jit, ctx_t* ctx)
return YJIT_END_BLOCK;
}
GEN_COUNTER_INC(cb, getivar_name_not_mapped);
return YJIT_CANT_COMPILE;
}
static codegen_status_t
gen_getinstancevariable(jitstate_t *jit, ctx_t *ctx)
{
// Defer compilation so we can specialize on a runtime `self`
if (!jit_at_current_insn(jit)) {
defer_compilation(jit->block, jit->insn_idx, ctx);
return YJIT_END_BLOCK;
}
ID ivar_name = (ID)jit_get_arg(jit, 0);
VALUE comptime_val = jit_peek_at_self(jit, ctx);
VALUE comptime_val_klass = CLASS_OF(comptime_val);
// Generate a side exit
uint8_t *side_exit = yjit_side_exit(jit, ctx);
// Guard that the receiver has the same class as the one from compile time.
mov(cb, REG0, member_opnd(REG_CFP, rb_control_frame_t, self));
guard_self_is_heap(cb, REG0, side_exit, ctx);
jit_guard_known_klass(jit, ctx, comptime_val_klass, OPND_SELF, GETIVAR_MAX_DEPTH, side_exit);
return gen_get_ivar(jit, ctx, GETIVAR_MAX_DEPTH, comptime_val, ivar_name, false, side_exit);
}
static codegen_status_t
gen_setinstancevariable(jitstate_t* jit, ctx_t* ctx)
{
@ -1413,8 +1435,8 @@ jit_guard_known_klass(jitstate_t *jit, ctx_t* ctx, VALUE known_klass, insn_opnd_
// Pointer to the klass field of the receiver &(recv->klass)
x86opnd_t klass_opnd = mem_opnd(64, REG0, offsetof(struct RBasic, klass));
// Bail if receiver class is different from compile-time call cache class
ADD_COMMENT(cb, "guard known class");
// Bail if receiver class is different from known_klass
// TODO: jit_mov_gc_ptr keeps a strong reference, which leaks the class.
jit_mov_gc_ptr(jit, cb, REG1, known_klass);
cmp(cb, klass_opnd, REG1);
jit_chain_guard(JCC_JNE, jit, ctx, max_chain_depth, side_exit);
@ -1861,15 +1883,24 @@ gen_opt_send_without_block(jitstate_t* jit, ctx_t* ctx)
return gen_oswb_iseq(jit, ctx, ci, cme, argc);
case VM_METHOD_TYPE_CFUNC:
return gen_oswb_cfunc(jit, ctx, ci, cme, argc);
case VM_METHOD_TYPE_IVAR:
if (argc != 0) {
// Argument count mismatch. Getters take no arguments.
GEN_COUNTER_INC(cb, oswb_getter_arity);
return YJIT_CANT_COMPILE;
}
else {
mov(cb, REG0, recv);
ID ivar_name = cme->def->body.attr.id;
return gen_get_ivar(jit, ctx, OSWB_MAX_DEPTH, comptime_recv, ivar_name, true, side_exit);
}
case VM_METHOD_TYPE_ATTRSET:
GEN_COUNTER_INC(cb, oswb_ivar_set_method);
return YJIT_CANT_COMPILE;
case VM_METHOD_TYPE_BMETHOD:
GEN_COUNTER_INC(cb, oswb_bmethod);
return YJIT_CANT_COMPILE;
case VM_METHOD_TYPE_IVAR:
GEN_COUNTER_INC(cb, oswb_ivar_get_method);
return YJIT_CANT_COMPILE;
case VM_METHOD_TYPE_ZSUPER:
GEN_COUNTER_INC(cb, oswb_zsuper_method);
return YJIT_CANT_COMPILE;

View file

@ -26,7 +26,6 @@ YJIT_DECLARE_COUNTERS(
oswb_ic_empty,
oswb_invalid_cme,
oswb_ivar_set_method,
oswb_ivar_get_method,
oswb_zsuper_method,
oswb_alias_method,
oswb_undef_method,
@ -42,6 +41,7 @@ YJIT_DECLARE_COUNTERS(
oswb_iseq_argc_mismatch,
oswb_iseq_not_simple,
oswb_not_implemented_method,
oswb_getter_arity,
oswb_se_receiver_not_heap,
oswb_se_cf_overflow,
oswb_se_cc_klass_differ,
@ -54,6 +54,8 @@ YJIT_DECLARE_COUNTERS(
getivar_se_self_not_heap,
getivar_idx_out_of_range,
getivar_undef,
getivar_name_not_mapped,
getivar_not_object,
oaref_argc_not_one,
oaref_arg_not_fixnum,