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/