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

ruby-changes:68847

From: Alan <ko1@a...>
Date: Thu, 21 Oct 2021 08:14:16 +0900 (JST)
Subject: [ruby-changes:68847] 0cd9120f17 (master): YJIT: hash specialization for opt_aref

https://git.ruby-lang.org/ruby.git/commit/?id=0cd9120f17

From 0cd9120f177b153126a093e4beabb5784cd0ab99 Mon Sep 17 00:00:00 2001
From: Alan Wu <XrXr@u...>
Date: Thu, 11 Mar 2021 14:46:19 -0500
Subject: YJIT: hash specialization for opt_aref

Make it lazy and add a hash specialization in addition to the array
specialization.
---
 bootstraptest/test_yjit.rb |  87 +++++++++++++++++++
 yjit.rb                    |   2 +-
 yjit_codegen.c             | 203 +++++++++++++++++++++++++++++++++------------
 yjit_iface.h               |   2 -
 4 files changed, 238 insertions(+), 56 deletions(-)

diff --git a/bootstraptest/test_yjit.rb b/bootstraptest/test_yjit.rb
index 4750ac5fe8..44817313b4 100644
--- a/bootstraptest/test_yjit.rb
+++ b/bootstraptest/test_yjit.rb
@@ -361,3 +361,90 @@ assert_equal 'ok', %q{ https://github.com/ruby/ruby/blob/trunk/bootstraptest/test_yjit.rb#L361
   subclasses.each { _1.new.get }
   parent.new.get
 }
+
+# Test polymorphic opt_aref. array -> hash
+assert_equal '[42, :key]', %q{
+  def index(obj, idx)
+    obj[idx]
+  end
+
+  index([], 0) # get over compilation threshold
+
+  [
+    index([42], 0),
+    index({0=>:key}, 0),
+  ]
+}
+
+# Test polymorphic opt_aref. hash -> array -> custom class
+assert_equal '[nil, nil, :custom]', %q{
+  def index(obj, idx)
+    obj[idx]
+  end
+
+  custom = Object.new
+  def custom.[](_idx)
+    :custom
+  end
+
+  index({}, 0) # get over compilation threshold
+
+  [
+    index({}, 0),
+    index([], 0),
+    index(custom, 0)
+  ]
+}
+
+# Test polymorphic opt_aref. array -> custom class
+assert_equal '[42, :custom]', %q{
+  def index(obj, idx)
+    obj[idx]
+  end
+
+  custom = Object.new
+  def custom.[](_idx)
+    :custom
+  end
+
+  index([], 0) # get over compilation threshold
+
+  [
+    index([42], 0),
+    index(custom, 0)
+  ]
+}
+
+# Test custom hash method with opt_aref
+assert_equal '[nil, :ok]', %q{
+  def index(obj, idx)
+    obj[idx]
+  end
+
+  custom = Object.new
+  def custom.hash
+    42
+  end
+
+  h = {custom => :ok}
+
+  [
+    index(h, 0),
+    index(h, custom)
+  ]
+}
+
+# Test default value block for Hash with opt_aref
+assert_equal '[42, :default]', %q{
+  def index(obj, idx)
+    obj[idx]
+  end
+
+  h = Hash.new { :default }
+  h[0] = 42
+
+  [
+    index(h, 0),
+    index(h, 1)
+  ]
+}
diff --git a/yjit.rb b/yjit.rb
index 5daee855e1..a05c2b2d24 100644
--- a/yjit.rb
+++ b/yjit.rb
@@ -14,7 +14,7 @@ module YJIT https://github.com/ruby/ruby/blob/trunk/yjit.rb#L14
 
     # Sort the blocks by increasing addresses
     blocks.sort_by(&:address).each_with_index do |block, i|
-      str << "== BLOCK #{i+1}/#{blocks.length}: #{block.code.length} BYTES, ISEQ RANGE [#{block.iseq_start_index},#{block.iseq_end_index}] ".ljust(80, "=")
+      str << "== BLOCK #{i+1}/#{blocks.length}: #{block.code.length} BYTES, ISEQ RANGE [#{block.iseq_start_index},#{block.iseq_end_index}) ".ljust(80, "=")
       str << "\n"
 
       cs.disasm(block.code, block.address).each do |i|
diff --git a/yjit_codegen.c b/yjit_codegen.c
index 7d591a9147..e1d27c5b45 100644
--- a/yjit_codegen.c
+++ b/yjit_codegen.c
@@ -89,15 +89,16 @@ jit_at_current_insn(jitstate_t* jit) https://github.com/ruby/ruby/blob/trunk/yjit_codegen.c#L89
     return (ec_pc == jit->pc);
 }
 
-// Peek at the topmost value on the Ruby stack
+// Peek at the nth topmost value on the Ruby stack.
+// Returns the topmost value when n == 0.
 static VALUE
-jit_peek_at_stack(jitstate_t* jit, ctx_t* ctx)
+jit_peek_at_stack(jitstate_t* jit, ctx_t* ctx, int n)
 {
     RUBY_ASSERT(jit_at_current_insn(jit));
 
-    VALUE* sp = jit->ec->cfp->sp + ctx->sp_offset;
+    VALUE *sp = jit->ec->cfp->sp + ctx->sp_offset;
 
-    return *(sp - 1);
+    return *(sp - 1 - n);
 }
 
 static VALUE
@@ -621,7 +622,7 @@ enum jcc_kinds { https://github.com/ruby/ruby/blob/trunk/yjit_codegen.c#L622
 // Generate a jump to a stub that recompiles the current YARV instruction on failure.
 // When depth_limitk is exceeded, generate a jump to a side exit.
 static void
-jit_chain_guard(enum jcc_kinds jcc, jitstate_t *jit, ctx_t *ctx, uint8_t depth_limit, uint8_t *side_exit)
+jit_chain_guard(enum jcc_kinds jcc, jitstate_t *jit, const ctx_t *ctx, uint8_t depth_limit, uint8_t *side_exit)
 {
     branchgen_fn target0_gen_fn;
 
@@ -661,7 +662,8 @@ jit_chain_guard(enum jcc_kinds jcc, jitstate_t *jit, ctx_t *ctx, uint8_t depth_l https://github.com/ruby/ruby/blob/trunk/yjit_codegen.c#L662
 bool rb_iv_index_tbl_lookup(struct st_table *iv_index_tbl, ID id, struct rb_iv_index_tbl_entry **ent); // vm_insnhelper.c
 
 enum {
-    GETIVAR_MAX_DEPTH = 10 // up to 5 different classes, and embedded or not for each
+    GETIVAR_MAX_DEPTH = 10,       // up to 5 different classes, and embedded or not for each
+    OPT_AREF_MAX_CHAIN_DEPTH = 2, // hashes and arrays
 };
 
 static codegen_status_t
@@ -917,7 +919,7 @@ gen_opt_ge(jitstate_t* jit, ctx_t* ctx) https://github.com/ruby/ruby/blob/trunk/yjit_codegen.c#L919
 }
 
 static codegen_status_t
-gen_opt_aref(jitstate_t* jit, ctx_t* ctx)
+gen_opt_aref(jitstate_t *jit, ctx_t *ctx)
 {
     struct rb_call_data * cd = (struct rb_call_data *)jit_get_arg(jit, 0);
     int32_t argc = (int32_t)vm_ci_argc(cd->ci);
@@ -928,64 +930,159 @@ gen_opt_aref(jitstate_t* jit, ctx_t* ctx) https://github.com/ruby/ruby/blob/trunk/yjit_codegen.c#L930
         return YJIT_CANT_COMPILE;
     }
 
-    const rb_callable_method_entry_t *cme = vm_cc_cme(cd->cc);
-
-    // Bail if the inline cache has been filled.  Currently, certain types
-    // (including arrays) don't use the inline cache, so if the inline cache
-    // has an entry, then this must be used by some other type.
-    if (cme) {
-        GEN_COUNTER_INC(cb, oaref_filled_cc);
-        return YJIT_CANT_COMPILE;
+    // Defer compilation so we can specialize base on a runtime receiver
+    if (!jit_at_current_insn(jit)) {
+        defer_compilation(jit->block, jit->insn_idx, ctx);
+        return YJIT_END_BLOCK;
     }
 
+    // Remember the context on entry for adding guard chains
+    const ctx_t starting_context = *ctx;
+
+    // Specialize base on compile time values
+    VALUE comptime_idx = jit_peek_at_stack(jit, ctx, 0);
+    VALUE comptime_recv = jit_peek_at_stack(jit, ctx, 1);
+
     // Create a size-exit to fall back to the interpreter
-    uint8_t* side_exit = yjit_side_exit(jit, ctx);
+    uint8_t *side_exit = yjit_side_exit(jit, ctx);
 
-    if (!assume_bop_not_redefined(jit->block, ARRAY_REDEFINED_OP_FLAG, BOP_AREF)) {
-        return YJIT_CANT_COMPILE;
+    if (CLASS_OF(comptime_recv) == rb_cArray && RB_FIXNUM_P(comptime_idx)) {
+        if (!assume_bop_not_redefined(jit->block, ARRAY_REDEFINED_OP_FLAG, BOP_AREF)) {
+            return YJIT_CANT_COMPILE;
+        }
+
+        // Pop the stack operands
+        x86opnd_t idx_opnd = ctx_stack_pop(ctx, 1);
+        x86opnd_t recv_opnd = ctx_stack_pop(ctx, 1);
+        mov(cb, REG0, recv_opnd);
+
+        // if (SPECIAL_CONST_P(recv)) {
+        // Bail if receiver is not a heap object
+        test(cb, REG0, imm_opnd(RUBY_IMMEDIATE_MASK));
+        jnz_ptr(cb, side_exit);
+        cmp(cb, REG0, imm_opnd(Qfalse));
+        je_ptr(cb, side_exit);
+        cmp(cb, REG0, imm_opnd(Qnil));
+        je_ptr(cb, side_exit);
+
+        // Bail if recv has a class other than ::Array.
+        // BOP_AREF check above is only good for ::Array.
+        mov(cb, REG1, mem_opnd(64, REG0, offsetof(struct RBasic, klass)));
+        mov(cb, REG0, const_ptr_opnd((void *)rb_cArray));
+        cmp(cb, REG0, REG1);
+        jit_chain_guard(JCC_JNE, jit, &starting_context, OPT_AREF_MAX_CHAIN_DEPTH, side_exit);
+
+        // Bail if idx is not a FIXNUM
+        mov(cb, REG1, idx_opnd);
+        test(cb, REG1, imm_opnd(RUBY_FIXNUM_FLAG));
+        jz_ptr(cb, COUNTED_EXIT(side_exit, oaref_arg_not_fixnum));
+
+        // Call VALUE rb_ary_entry_internal(VALUE ary, long offset).
+        // It never raises or allocates, so we don't need to write to cfp->pc.
+        {
+            yjit_save_regs(cb);
+
+            mov(cb, RDI, recv_opnd);
+            sar(cb, REG1, imm_opnd(1)); // Convert fixnum to int
+            mov(cb, RSI, REG1);
+            call_ptr(cb, REG0, (void *)rb_ary_entry_internal);
+
+            yjit_load_regs(cb);
+
+            // Push the return value onto the stack
+            x86opnd_t stack_ret = ctx_stack_push(ctx, T_NONE);
+            mov(cb, stack_ret, RAX);
+        }
+
+        // Jump to next instruction. This allows guard chains to share the same successor.
+        {
+            ctx_t reset_depth = *ctx;
+            reset_depth.chain_depth = 0;
+
+            blockid_t jump_block = { jit->iseq, jit_next_insn_idx(jit) };
+
+            // Generate the jump instruction
+            gen_direct_jump(
+                &reset_depth,
+                jump_block
+            );
+        }
+        return YJIT_END_BLOCK;
     }
+    else if (CLASS_OF(comptime_recv) == rb_cHash) {
+        if (!assume_bop_not_redefined(jit->block, HASH_REDEFINED_OP_FLAG, BOP_AREF)) {
+            return YJIT_CANT_COMPILE;
+        }
 
-    // Pop the stack operands
-    x86opnd_t idx_opnd = ctx_stack_pop(ctx, 1);
-    x86opnd_t recv_opnd = ctx_stack_pop(ctx, 1);
-    mov(cb, REG0, recv_opnd);
+        // Pop the stack operands
+        x86opnd_t idx_opnd = ctx_stack_pop(ctx, 1);
+        x86opnd_t recv_opnd = ctx_stack_pop(ctx, 1);
+        mov(cb, REG0, recv_opnd);
 
-    // if (SPECIAL_CONST_P(recv)) {
-    // Bail if it's not a heap object
-    test(cb, REG0, imm_opnd(RUBY_IMMEDIATE_MASK));
-    jnz_ptr(cb, side_exit);
-    cmp(cb, REG0, imm_opnd(Qfalse));
-    je_ptr(cb, side_exit);
-    cmp(cb, REG0, imm_opnd(Qnil));
-    je_ptr(cb, side_exit);
-
-    // Bail if recv has a class other than ::Array.
-    // BOP_AREF check above is only good for ::Array.
-    mov(cb, REG1, mem_opnd(64, REG0, offsetof(struct RBasic, klass)));
-    mov(cb, REG0, const_ptr_opnd((void *)rb_cArray));
-    cmp(cb, REG0, REG1);
-    jne_ptr(cb, COUNTED_EXIT(side_exit, oaref_not_array));
-
-    // Bai (... truncated)

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

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