[前][次][番号順一覧][スレッド一覧]

ruby-changes:69142

From: Alan <ko1@a...>
Date: Thu, 21 Oct 2021 08:20:59 +0900 (JST)
Subject: [ruby-changes:69142] 78b5e95e41 (master): Add a slowpath for opt_getinlinecache

https://git.ruby-lang.org/ruby.git/commit/?id=78b5e95e41

From 78b5e95e41cefd36702c37293bab95fdede38369 Mon Sep 17 00:00:00 2001
From: Alan Wu <XrXr@u...>
Date: Tue, 21 Sep 2021 18:16:23 -0400
Subject: 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.
---
 test/ruby/test_yjit.rb | 28 ++++++++++++++++++++++++++++
 vm_insnhelper.c        |  6 ++++++
 yjit.rb                |  1 +
 yjit_codegen.c         | 46 ++++++++++++++++++++++++++++++++++------------
 yjit_iface.h           |  2 ++
 5 files changed, 71 insertions(+), 12 deletions(-)

diff --git a/test/ruby/test_yjit.rb b/test/ruby/test_yjit.rb
index e6325a4d1c..e00f15ecf5 100644
--- a/test/ruby/test_yjit.rb
+++ b/test/ruby/test_yjit.rb
@@ -289,6 +289,34 @@ class TestYJIT < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_yjit.rb#L289
     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)
diff --git a/vm_insnhelper.c b/vm_insnhelper.c
index 6c2458f32e..a33f6b5ea3 100644
--- a/vm_insnhelper.c
+++ b/vm_insnhelper.c
@@ -4778,6 +4778,12 @@ vm_ic_hit_p(const struct iseq_inline_constant_cache_entry *ice, const VALUE *reg https://github.com/ruby/ruby/blob/trunk/vm_insnhelper.c#L4778
     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)
 {
diff --git a/yjit.rb b/yjit.rb
index 3c447b4b11..f7e283dc05 100644
--- a/yjit.rb
+++ b/yjit.rb
@@ -159,6 +159,7 @@ module YJIT https://github.com/ruby/ruby/blob/trunk/yjit.rb#L159
       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]
diff --git a/yjit_codegen.c b/yjit_codegen.c
index ce5fb4e344..2ee00cd464 100644
--- a/yjit_codegen.c
+++ b/yjit_codegen.c
@@ -4047,25 +4047,47 @@ gen_opt_getinlinecache(jitstate_t* jit, ctx_t* ctx, codeblock_t* cb) https://github.com/ruby/ruby/blob/trunk/yjit_codegen.c#L4047
     // 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;
diff --git a/yjit_iface.h b/yjit_iface.h
index 452d27fea8..946ab5bb85 100644
--- a/yjit_iface.h
+++ b/yjit_iface.h
@@ -82,6 +82,8 @@ YJIT_DECLARE_COUNTERS( https://github.com/ruby/ruby/blob/trunk/yjit_iface.h#L82
     oaref_argc_not_one,
     oaref_arg_not_fixnum,
 
+    opt_getinlinecache_miss,
+
     binding_allocations,
     binding_set,
 
-- 
cgit v1.2.1


--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]