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

ruby-changes:68924

From: Alan <ko1@a...>
Date: Thu, 21 Oct 2021 08:16:36 +0900 (JST)
Subject: [ruby-changes:68924] 936ee55562 (master): Improve opt_not by expanding cfunc codegen

https://git.ruby-lang.org/ruby.git/commit/?id=936ee55562

From 936ee5556280162da3016bf62ebe74ef07caf882 Mon Sep 17 00:00:00 2001
From: Alan Wu <XrXr@u...>
Date: Wed, 16 Jun 2021 18:06:06 -0400
Subject: Improve opt_not by expanding cfunc codegen

This commit improves opt_not by making it correct when TrueClass#!
and/or FalseClass#! is defined and genearting better code when the
receiver is a heap object.

guard_known_class() can now handle true, false, and nil, and we
introduce a codegen function reimplementing rb_obj_not(), used when we
know we are calling into rb_obj_not().

Co-authored-by: Maxime Chevalier-Boisvert <maximechevalierb@g...>
Co-authored-by: Noah Gibbs <the.codefolio.guy@g...>
---
 bootstraptest/test_yjit.rb |  70 +++++++++++++++++
 yjit_codegen.c             | 187 +++++++++++++++++++++++++++------------------
 2 files changed, 183 insertions(+), 74 deletions(-)

diff --git a/bootstraptest/test_yjit.rb b/bootstraptest/test_yjit.rb
index de20b3565e..129903bf9c 100644
--- a/bootstraptest/test_yjit.rb
+++ b/bootstraptest/test_yjit.rb
@@ -1070,6 +1070,76 @@ assert_normal_exit %q{ https://github.com/ruby/ruby/blob/trunk/bootstraptest/test_yjit.rb#L1070
   foo(Object.new)
 }
 
+# defining TrueClass#!
+assert_equal '[false, false, :ok]', %q{
+  def foo(obj)
+    !obj
+  end
+
+  x = foo(true)
+  y = foo(true)
+
+  class TrueClass
+    def !
+      :ok
+    end
+  end
+
+  z = foo(true)
+
+  [x, y, z]
+}
+
+# defining FalseClass#!
+assert_equal '[true, true, :ok]', %q{
+  def foo(obj)
+    !obj
+  end
+
+  x = foo(false)
+  y = foo(false)
+
+  class FalseClass
+    def !
+      :ok
+    end
+  end
+
+  z = foo(false)
+
+  [x, y, z]
+}
+
+# defining NilClass#!
+assert_equal '[true, true, :ok]', %q{
+  def foo(obj)
+    !obj
+  end
+
+  x = foo(nil)
+  y = foo(nil)
+
+  class NilClass
+    def !
+      :ok
+    end
+  end
+
+  z = foo(nil)
+
+  [x, y, z]
+}
+
+# polymorphic opt_not
+assert_equal '[true, true, false, false, false, false, false]', %q{
+  def foo(obj)
+    !obj
+  end
+
+  foo(0)
+  [foo(nil), foo(false), foo(true), foo([]), foo(0), foo(4.2), foo(:sym)]
+}
+
 # getlocal with 2 levels
 assert_equal '7', %q{
   def foo(foo, bar)
diff --git a/yjit_codegen.c b/yjit_codegen.c
index e36ee93774..b0973cb2a7 100644
--- a/yjit_codegen.c
+++ b/yjit_codegen.c
@@ -1964,51 +1964,8 @@ gen_opt_str_uminus(jitstate_t* jit, ctx_t* ctx) https://github.com/ruby/ruby/blob/trunk/yjit_codegen.c#L1964
 }
 
 static codegen_status_t
-gen_opt_not(jitstate_t* jit, ctx_t* ctx)
+gen_opt_not(jitstate_t *jit, ctx_t *ctx)
 {
-    // Defer compilation so we can specialize type of argument
-    if (!jit_at_current_insn(jit)) {
-        defer_compilation(jit->block, jit->insn_idx, ctx);
-        return YJIT_END_BLOCK;
-    }
-
-    uint8_t* side_exit = yjit_side_exit(jit, ctx);
-
-    VALUE comptime_val = jit_peek_at_stack(jit, ctx, 0);
-
-    // For the true/false case
-    if (comptime_val == Qtrue || comptime_val == Qfalse) {
-
-        // Get the operand from the stack
-        x86opnd_t arg = ctx_stack_pop(ctx, 1);
-
-        uint32_t DONE = cb_new_label(cb, "DONE");
-
-        // Qtrue => Qfalse
-        mov(cb, REG0, imm_opnd(Qfalse));
-        cmp(cb, arg, imm_opnd(Qtrue));
-        je_label(cb, DONE);
-
-        // Qfalse => Qtrue
-        mov(cb, REG0, imm_opnd(Qtrue));
-        cmp(cb, arg, imm_opnd(Qfalse));
-        je_label(cb, DONE);
-
-        // For any other values, we side-exit
-        // This never happens in railsbench
-        jmp_ptr(cb, side_exit);
-
-        cb_write_label(cb, DONE);
-        cb_link_labels(cb);
-
-        // Push the return value onto the stack
-        x86opnd_t stack_ret = ctx_stack_push(ctx, TYPE_IMM);
-        mov(cb, stack_ret, REG0);
-
-        return YJIT_KEEP_COMPILING;
-    }
-
-    // Delegate to send, call the method on the recv
     return gen_opt_send_without_block(jit, ctx);
 }
 
@@ -2107,13 +2064,8 @@ gen_branchunless(jitstate_t* jit, ctx_t* ctx) https://github.com/ruby/ruby/blob/trunk/yjit_codegen.c#L2064
     test(cb, val_opnd, imm_opnd(~Qnil));
 
     // Get the branch target instruction offsets
-<<<<<<< HEAD
-    uint32_t next_idx = jit_next_idx(jit);
-    uint32_t jump_idx = next_idx + (uint32_t)jit_get_arg(jit, 0);
-=======
     uint32_t next_idx = jit_next_insn_idx(jit);
     uint32_t jump_idx = next_idx + jump_offset;
->>>>>>> ef0d1ca495 (Avoid interrupt checks for forward branches (#41))
     blockid_t next_block = { jit->iseq, next_idx };
     blockid_t jump_block = { jit->iseq, jump_idx };
 
@@ -2199,11 +2151,7 @@ gen_jump(jitstate_t* jit, ctx_t* ctx) https://github.com/ruby/ruby/blob/trunk/yjit_codegen.c#L2151
     }
 
     // Get the branch target instruction offsets
-<<<<<<< HEAD
-    uint32_t jump_idx = jit_next_idx(jit) + (int32_t)jit_get_arg(jit, 0);
-=======
     uint32_t jump_idx = jit_next_insn_idx(jit) + jump_offset;
->>>>>>> ef0d1ca495 (Avoid interrupt checks for forward branches (#41))
     blockid_t jump_block = { jit->iseq, jump_idx };
 
     // Generate the jump instruction
@@ -2223,31 +2171,67 @@ Recompile as contingency if possible, or take side exit a last resort. https://github.com/ruby/ruby/blob/trunk/yjit_codegen.c#L2171
 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)
 {
-    // Can't guard for for these classes because some of they are sometimes immediate (special const).
-    // Can remove this by adding appropriate dynamic checks.
-    if (known_klass == rb_cInteger ||
-        known_klass == rb_cSymbol ||
-        known_klass == rb_cFloat ||
-        known_klass == rb_cNilClass ||
-        known_klass == rb_cTrueClass ||
-        known_klass == rb_cFalseClass) {
-        return false;
+    val_type_t val_type = ctx_get_opnd_type(ctx, insn_opnd);
+
+    if (known_klass == rb_cNilClass) {
+        if (val_type.type != ETYPE_NIL) {
+            ADD_COMMENT(cb, "guard object is nil");
+            cmp(cb, REG0, imm_opnd(Qnil));
+            jit_chain_guard(JCC_JNE, jit, ctx, max_chain_depth, side_exit);
+
+            ctx_set_opnd_type(ctx, insn_opnd, TYPE_NIL);
+        }
     }
+    else if (known_klass == rb_cTrueClass) {
+        if (val_type.type != ETYPE_TRUE) {
+            ADD_COMMENT(cb, "guard object is true");
+            cmp(cb, REG0, imm_opnd(Qtrue));
+            jit_chain_guard(JCC_JNE, jit, ctx, max_chain_depth, side_exit);
 
-    val_type_t val_type = ctx_get_opnd_type(ctx, insn_opnd);
+            ctx_set_opnd_type(ctx, insn_opnd, TYPE_TRUE);
+        }
 
-    // Check that the receiver is a heap object
-    if (!val_type.is_heap)
-    {
-        // FIXME: use two comparisons instead of 3 here
-        ADD_COMMENT(cb, "guard not immediate");
-        RUBY_ASSERT(Qfalse < Qnil);
-        test(cb, REG0, imm_opnd(RUBY_IMMEDIATE_MASK));
-        jnz_ptr(cb, side_exit);
-        cmp(cb, REG0, imm_opnd(Qnil));
-        jbe_ptr(cb, side_exit);
+    }
+    else if (known_klass == rb_cFalseClass) {
+        if (val_type.type != ETYPE_FALSE) {
+            ADD_COMMENT(cb, "guard object is false");
+            STATIC_ASSERT(qfalse_is_zero, Qfalse == 0);
+            test(cb, REG0, REG0);
+            jit_chain_guard(JCC_JNZ, jit, ctx, max_chain_depth, side_exit);
+
+            ctx_set_opnd_type(ctx, insn_opnd, TYPE_FALSE);
+        }
+    }
+    else {
+        // Can't guard for for these classes because some of they are sometimes immediate (special const).
+        // Can remove this by adding appropriate dynamic checks.
+        if (known_klass == rb_cInteger ||
+            known_klass == rb_cSymbol ||
+            known_klass == rb_cFloat) {
+            return false;
+        }
+
+        // Check that the receiver is a heap object
+        // Note: if we get here, the class doesn't have immediate instances.
+        if (!val_type.is_heap) {
+            ADD_COMMENT(cb, "guard not immediate");
+            RUBY_ASSERT(Qfalse < Qnil);
+            test(cb, REG0, imm_opnd(RUBY_IMMEDIATE_MASK));
+            jnz_ptr(cb, side_exit);
+            cmp(cb, REG0, imm_opnd(Qnil));
+            jbe_ptr(cb, side_exit);
+
+            ctx_set_opnd_type(ctx, insn_opnd, TYPE_HEAP);
+        }
+
+        x86opnd_t klass_opnd = mem_opnd(64, REG0, offsetof(struct RBasic, klass));
 
-        ctx_set_opnd_type(ctx, insn_opnd, TYPE_HEAP);
+        // Bail if receiver class is different from known_klass
+        // TODO: jit_mov_gc_ptr keeps a strong reference, which leaks the class.
+        ADD_COMMENT(cb, "guard known 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);
     }
 
     // Pointer to the klass field of the receiver &(recv->klass)
@@ -2278,6 +2262,48 @@ jit_protected_callee_ancestry_guard(jitstate_t *jit, codeblock_t *cb, const rb_c https://github.com/ruby/ruby/blob/trunk/yjit_codegen.c#L2262
     jz_ptr(cb, COUNTED_EXIT(side_exit, send_se_protected_check_failed));
 }
 
+// Return true when the codegen function generates code.
+typedef bool (*cfunc_codegen_t)(jitstate_t *jit, ctx_t *ctx, const struct rb_callinfo *ci, const rb_callable_method_entry_t *cme, rb_iseq_t *block, const int32_t argc);
+
+// Codegen for rb_obj_not().
+// Note, caller is responsible for generating all the right guards, including
+// arity guards.
+static bool
+jit_rb_obj_not(jitstate_t *jit, ctx_t *ctx, const struct rb_callinfo *ci, const rb_callable_method_entry_t *cme, rb_iseq_t *block, const int32_t argc)
+{
+    const val_type_t recv_opnd = ctx_get_opnd_type(ctx, OPND_STACK(0));
+    x86opnd_t out_opnd = ctx_stack_opnd(ctx, 0);
+
+    if (recv_opnd.type == ETYPE_NIL || recv_opnd.type == ETYPE_FALSE) {
+        ADD_COMMENT(cb, "rb_obj_not(nil_or_false)");
+        mov(cb, out_opnd, imm_opnd(Qtrue));
+        ctx_set_opnd_type(ctx, OPND_STACK(0), TYPE_TRUE);
+    }
+    else if (recv_opnd.is_heap || recv_opnd.type != ETYPE_UNKNOWN) {
+        // Note: recv_op (... truncated)

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

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