From c15a577eda78f1944ade1e9ae1bdadeaeee3c8d7 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 9 Mar 2021 11:01:16 -0800 Subject: [PATCH] Make Blocks depend on BOPS When a BOP is redefined, the BOP redefinition callback will invalidate any blocks that depend on BOPS. This allows us to eliminate runtime checks for BOP redefinition. --- bootstraptest/test_yjit.rb | 35 ++++++++++++++++++++++ yjit_codegen.c | 60 ++++++++++---------------------------- yjit_iface.c | 36 ++++++++++++++++++----- yjit_iface.h | 1 + 4 files changed, 80 insertions(+), 52 deletions(-) diff --git a/bootstraptest/test_yjit.rb b/bootstraptest/test_yjit.rb index cf79ccd101..f7e1fe4a7e 100644 --- a/bootstraptest/test_yjit.rb +++ b/bootstraptest/test_yjit.rb @@ -1,3 +1,38 @@ +# BOP redefined methods work when JIT compiled +assert_equal 'false', %q{ + def less_than x + x < 10 + end + + class Integer + def < x + false + end + end + + less_than 2 + less_than 2 + less_than 2 +} + +# BOP redefinition works on Integer#< +assert_equal 'false', %q{ + def less_than x + x < 10 + end + + less_than 2 + less_than 2 + + class Integer + def < x + false + end + end + + less_than 2 +} + # Putobject, less-than operator, fixnums assert_equal '2', %q{ def check_index(index) diff --git a/yjit_codegen.c b/yjit_codegen.c index ab16ccbf42..0877517850 100644 --- a/yjit_codegen.c +++ b/yjit_codegen.c @@ -740,15 +740,9 @@ gen_fixnum_cmp(jitstate_t* jit, ctx_t* ctx, cmov_fn cmov_op) // Note: we generate the side-exit before popping operands from the stack uint8_t* side_exit = yjit_side_exit(jit, ctx); - // TODO: make a helper function for guarding on op-not-redefined - // Make sure that minus isn't redefined for integers - mov(cb, RAX, const_ptr_opnd(ruby_current_vm_ptr)); - test( - cb, - member_opnd_idx(RAX, rb_vm_t, redefined_flag, BOP_LT), - imm_opnd(INTEGER_REDEFINED_OP_FLAG) - ); - jnz_ptr(cb, side_exit); + if (!assume_bop_not_redefined(jit->block, INTEGER_REDEFINED_OP_FLAG, BOP_LT)) { + return YJIT_CANT_COMPILE; + } // Get the operands and destination from the stack int arg1_type = ctx_get_top_type(ctx); @@ -821,15 +815,9 @@ gen_opt_aref(jitstate_t* jit, ctx_t* ctx) // Create a size-exit to fall back to the interpreter uint8_t* side_exit = yjit_side_exit(jit, ctx); - // TODO: make a helper function for guarding on op-not-redefined - // Make sure that aref isn't redefined for arrays. - mov(cb, RAX, const_ptr_opnd(ruby_current_vm_ptr)); - test( - cb, - member_opnd_idx(RAX, rb_vm_t, redefined_flag, BOP_AREF), - imm_opnd(ARRAY_REDEFINED_OP_FLAG) - ); - jnz_ptr(cb, side_exit); + 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); @@ -881,15 +869,9 @@ gen_opt_and(jitstate_t* jit, ctx_t* ctx) // Note: we generate the side-exit before popping operands from the stack uint8_t* side_exit = yjit_side_exit(jit, ctx); - // TODO: make a helper function for guarding on op-not-redefined - // Make sure that plus isn't redefined for integers - mov(cb, RAX, const_ptr_opnd(ruby_current_vm_ptr)); - test( - cb, - member_opnd_idx(RAX, rb_vm_t, redefined_flag, BOP_AND), - imm_opnd(INTEGER_REDEFINED_OP_FLAG) - ); - jnz_ptr(cb, side_exit); + if (!assume_bop_not_redefined(jit->block, INTEGER_REDEFINED_OP_FLAG, BOP_AND)) { + return YJIT_CANT_COMPILE; + } // Get the operands and destination from the stack int arg1_type = ctx_get_top_type(ctx); @@ -925,15 +907,9 @@ gen_opt_minus(jitstate_t* jit, ctx_t* ctx) // Note: we generate the side-exit before popping operands from the stack uint8_t* side_exit = yjit_side_exit(jit, ctx); - // TODO: make a helper function for guarding on op-not-redefined - // Make sure that minus isn't redefined for integers - mov(cb, RAX, const_ptr_opnd(ruby_current_vm_ptr)); - test( - cb, - member_opnd_idx(RAX, rb_vm_t, redefined_flag, BOP_MINUS), - imm_opnd(INTEGER_REDEFINED_OP_FLAG) - ); - jnz_ptr(cb, side_exit); + if (!assume_bop_not_redefined(jit->block, INTEGER_REDEFINED_OP_FLAG, BOP_MINUS)) { + return YJIT_CANT_COMPILE; + } // Get the operands and destination from the stack x86opnd_t arg1 = ctx_stack_pop(ctx, 1); @@ -965,15 +941,9 @@ gen_opt_plus(jitstate_t* jit, ctx_t* ctx) // Note: we generate the side-exit before popping operands from the stack uint8_t* side_exit = yjit_side_exit(jit, ctx); - // TODO: make a helper function for guarding on op-not-redefined - // Make sure that plus isn't redefined for integers - mov(cb, RAX, const_ptr_opnd(ruby_current_vm_ptr)); - test( - cb, - member_opnd_idx(RAX, rb_vm_t, redefined_flag, BOP_PLUS), - imm_opnd(INTEGER_REDEFINED_OP_FLAG) - ); - jnz_ptr(cb, side_exit); + if (!assume_bop_not_redefined(jit->block, INTEGER_REDEFINED_OP_FLAG, BOP_PLUS)) { + return YJIT_CANT_COMPILE; + } // Get the operands and destination from the stack int arg1_type = ctx_get_top_type(ctx); diff --git a/yjit_iface.c b/yjit_iface.c index 6c43a53728..6424d34938 100644 --- a/yjit_iface.c +++ b/yjit_iface.c @@ -174,6 +174,23 @@ add_lookup_dependency_i(st_data_t *key, st_data_t *value, st_data_t data, int ex return ST_CONTINUE; } +// Hash table of BOP blocks +static st_table *blocks_assuming_bops; + +bool +assume_bop_not_redefined(block_t *block, int redefined_flag, enum ruby_basic_operators bop) +{ + if (BASIC_OP_UNREDEFINED_P(bop, redefined_flag)) { + if (blocks_assuming_bops) { + st_insert(blocks_assuming_bops, (st_data_t)block, 0); + } + return true; + } + else { + return false; + } +} + // Remember that the currently compiling block is only valid while cme and cc are valid void assume_method_lookup_stable(const struct rb_callcache *cc, const rb_callable_method_entry_t *cme, block_t *block) @@ -341,6 +358,10 @@ yjit_block_assumptions_free(block_t *block) if (blocks_assuming_single_ractor_mode) { st_delete(blocks_assuming_single_ractor_mode, &as_st_data, NULL); } + + if (blocks_assuming_bops) { + st_delete(blocks_assuming_bops, &as_st_data, NULL); + } } void @@ -440,13 +461,6 @@ iseq_end_index(VALUE self) return INT2NUM(block->end_idx); } -/* Called when a basic operation is redefined */ -void -rb_yjit_bop_redefined(VALUE klass, const rb_method_entry_t *me, enum ruby_basic_operators bop) -{ - //fprintf(stderr, "bop redefined\n"); -} - static int block_invalidation_iterator(st_data_t key, st_data_t value, st_data_t data) { block_t *block = (block_t *)key; @@ -454,6 +468,13 @@ block_invalidation_iterator(st_data_t key, st_data_t value, st_data_t data) { return ST_CONTINUE; } +/* Called when a basic operation is redefined */ +void +rb_yjit_bop_redefined(VALUE klass, const rb_method_entry_t *me, enum ruby_basic_operators bop) +{ + st_foreach(blocks_assuming_bops, block_invalidation_iterator, 0); +} + /* Called when the constant state changes */ void rb_yjit_constant_state_changed(void) @@ -782,6 +803,7 @@ rb_yjit_init(struct rb_yjit_options *options) blocks_assuming_stable_global_constant_state = st_init_numtable(); blocks_assuming_single_ractor_mode = st_init_numtable(); + blocks_assuming_bops = st_init_numtable(); yjit_init_core(); yjit_init_codegen(); diff --git a/yjit_iface.h b/yjit_iface.h index 600fa47a34..d525337d24 100644 --- a/yjit_iface.h +++ b/yjit_iface.h @@ -78,6 +78,7 @@ int opcode_at_pc(const rb_iseq_t *iseq, const VALUE *pc); void check_cfunc_dispatch(VALUE receiver, struct rb_call_data *cd, void *callee, rb_callable_method_entry_t *compile_time_cme); bool cfunc_needs_frame(const rb_method_cfunc_t *cfunc); +RBIMPL_ATTR_NODISCARD() bool assume_bop_not_redefined(block_t *block, int redefined_flag, enum ruby_basic_operators bop); void assume_method_lookup_stable(const struct rb_callcache *cc, const rb_callable_method_entry_t *cme, block_t* block); RBIMPL_ATTR_NODISCARD() bool assume_single_ractor_mode(block_t *block); RBIMPL_ATTR_NODISCARD() bool assume_stable_global_constant_state(block_t *block);