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

Add a slowpath for opt_getinlinecache

Before this change, when we encounter a constant cache that is specific
to a lexical scope, we unconditionally exit. This change falls back to
the interpreter's cache in this situation.

This should help constant expressions in `class << self`, which is popular
at Shopify due to the style guide.

This change relies on the cache being warm while compiling to detect the
need for checking the lexical scope for simplicity.
This commit is contained in:
Alan Wu 2021-09-21 18:16:23 -04:00
parent f1eb48cb23
commit 78b5e95e41
5 changed files with 71 additions and 12 deletions

View file

@ -289,6 +289,34 @@ class TestYJIT < Test::Unit::TestCase
RUBY
end
def test_opt_getinlinecache_slowpath
assert_compiles(<<~RUBY, exits: { opt_getinlinecache: 1 }, result: [42, 42, 1, 1], min_calls: 2)
class A
FOO = 42
class << self
def foo
_foo = nil
FOO
end
end
end
result = []
result << A.foo
result << A.foo
class << A
FOO = 1
end
result << A.foo
result << A.foo
result
RUBY
end
def test_string_interpolation
assert_compiles(<<~'RUBY', insns: %i[checktype concatstrings], result: "foobar", min_calls: 2)
def make_str(foo, bar)

View file

@ -4778,6 +4778,12 @@ vm_ic_hit_p(const struct iseq_inline_constant_cache_entry *ice, const VALUE *reg
return vm_inlined_ic_hit_p(ice->flags, ice->value, ice->ic_cref, ice->ic_serial, reg_ep);
}
bool
rb_vm_ic_hit_p(IC ic, const VALUE *reg_ep)
{
return ic->entry && vm_ic_hit_p(ic->entry, reg_ep);
}
static void
vm_ic_update(const rb_iseq_t *iseq, IC ic, VALUE val, const VALUE *reg_ep)
{

View file

@ -159,6 +159,7 @@ module YJIT
print_counters(stats, prefix: 'setivar_', prompt: 'setinstancevariable exit reasons:')
print_counters(stats, prefix: 'oaref_', prompt: 'opt_aref exit reasons: ')
print_counters(stats, prefix: 'expandarray_', prompt: 'expandarray exit reasons: ')
print_counters(stats, prefix: 'opt_getinlinecache_', prompt: 'opt_getinlinecache exit reasons: ')
side_exits = total_exit_count(stats)
total_exits = side_exits + stats[:leave_interp_return]

View file

@ -4047,25 +4047,47 @@ gen_opt_getinlinecache(jitstate_t* jit, ctx_t* ctx, codeblock_t* cb)
// See vm_ic_hit_p(). The same conditions are checked in yjit_constant_ic_update().
struct iseq_inline_constant_cache_entry *ice = ic->entry;
if (!ice || // cache not filled
ice->ic_serial != ruby_vm_global_constant_state || // cache out of date
ice->ic_cref /* cache only valid for certain lexical scopes */) {
ice->ic_serial != ruby_vm_global_constant_state /* cache out of date */) {
// In these cases, leave a block that unconditionally side exits
// for the interpreter to invalidate.
return YJIT_CANT_COMPILE;
}
// Optimize for single ractor mode.
// FIXME: This leaks when st_insert raises NoMemoryError
if (!assume_single_ractor_mode(jit->block)) return YJIT_CANT_COMPILE;
if (ice->ic_cref) {
// Cache is keyed on a certain lexical scope. Use the interpreter's cache.
uint8_t *side_exit = yjit_side_exit(jit, ctx);
// Invalidate output code on any and all constant writes
// FIXME: This leaks when st_insert raises NoMemoryError
assume_stable_global_constant_state(jit->block);
// Call function to verify the cache
bool rb_vm_ic_hit_p(IC ic, const VALUE *reg_ep);
mov(cb, C_ARG_REGS[0], const_ptr_opnd((void *)ic));
mov(cb, C_ARG_REGS[1], member_opnd(REG_CFP, rb_control_frame_t, ep));
call_ptr(cb, REG0, (void *)rb_vm_ic_hit_p);
val_type_t type = yjit_type_of_value(ice->value);
x86opnd_t stack_top = ctx_stack_push(ctx, type);
jit_mov_gc_ptr(jit, cb, REG0, ice->value);
mov(cb, stack_top, REG0);
// Check the result. _Bool is one byte in SysV.
test(cb, AL, AL);
jz_ptr(cb, COUNTED_EXIT(side_exit, opt_getinlinecache_miss));
// Push ic->entry->value
mov(cb, REG0, const_ptr_opnd((void *)ic));
mov(cb, REG0, member_opnd(REG0, struct iseq_inline_constant_cache, entry));
x86opnd_t stack_top = ctx_stack_push(ctx, TYPE_UNKNOWN);
mov(cb, REG0, member_opnd(REG0, struct iseq_inline_constant_cache_entry, value));
mov(cb, stack_top, REG0);
}
else {
// Optimize for single ractor mode.
// FIXME: This leaks when st_insert raises NoMemoryError
if (!assume_single_ractor_mode(jit->block)) return YJIT_CANT_COMPILE;
// Invalidate output code on any and all constant writes
// FIXME: This leaks when st_insert raises NoMemoryError
assume_stable_global_constant_state(jit->block);
val_type_t type = yjit_type_of_value(ice->value);
x86opnd_t stack_top = ctx_stack_push(ctx, type);
jit_mov_gc_ptr(jit, cb, REG0, ice->value);
mov(cb, stack_top, REG0);
}
// Jump over the code for filling the cache
uint32_t jump_idx = jit_next_insn_idx(jit) + (int32_t)jump_offset;

View file

@ -82,6 +82,8 @@ YJIT_DECLARE_COUNTERS(
oaref_argc_not_one,
oaref_arg_not_fixnum,
opt_getinlinecache_miss,
binding_allocations,
binding_set,