diff options
-rw-r--r-- | benchmark/vm_const.yml | 6 | ||||
-rw-r--r-- | common.mk | 2 | ||||
-rw-r--r-- | compile.c | 205 | ||||
-rw-r--r-- | insns.def | 63 | ||||
-rw-r--r-- | iseq.c | 140 | ||||
-rw-r--r-- | iseq.h | 1 | ||||
-rw-r--r-- | test/ruby/test_mjit.rb | 6 | ||||
-rw-r--r-- | test/ruby/test_yjit.rb | 8 | ||||
-rw-r--r-- | tool/ruby_vm/views/_mjit_compile_getconstant_path.erb (renamed from tool/ruby_vm/views/_mjit_compile_getinlinecache.erb) | 1 | ||||
-rw-r--r-- | tool/ruby_vm/views/mjit_compile.inc.erb | 4 | ||||
-rw-r--r-- | vm_core.h | 19 | ||||
-rw-r--r-- | vm_insnhelper.c | 82 | ||||
-rw-r--r-- | yjit.h | 4 | ||||
-rw-r--r-- | yjit/src/codegen.rs | 29 | ||||
-rw-r--r-- | yjit/src/cruby_bindings.inc.rs | 280 | ||||
-rw-r--r-- | yjit/src/invariants.rs | 82 |
16 files changed, 482 insertions, 450 deletions
diff --git a/benchmark/vm_const.yml b/benchmark/vm_const.yml index 6064d4eed0..8939ca0cd3 100644 --- a/benchmark/vm_const.yml +++ b/benchmark/vm_const.yml @@ -1,7 +1,13 @@ prelude: | Const = 1 + A = B = C = D = E = F = G = H = I = J = K = L = M = N = O = P = Q = R = S = T = U = V = W = X = Y = Z = 1 + def foo + A; B; C; D; E; F; G; H; I; J; K; L; M; N; O; P; Q; R; S; T; U; V; W; X; Y; Z + end benchmark: vm_const: | j = Const k = Const + vm_const_many: | + foo loop_count: 30000000 @@ -1045,7 +1045,7 @@ $(srcs_vpath)mjit_compile.inc: $(tooldir)/ruby_vm/views/mjit_compile.inc.erb $(i $(tooldir)/ruby_vm/views/_mjit_compile_insn.erb $(tooldir)/ruby_vm/views/_mjit_compile_send.erb \ $(tooldir)/ruby_vm/views/_mjit_compile_ivar.erb \ $(tooldir)/ruby_vm/views/_mjit_compile_insn_body.erb $(tooldir)/ruby_vm/views/_mjit_compile_pc_and_sp.erb \ - $(tooldir)/ruby_vm/views/_mjit_compile_invokebuiltin.erb $(tooldir)/ruby_vm/views/_mjit_compile_getinlinecache.erb + $(tooldir)/ruby_vm/views/_mjit_compile_invokebuiltin.erb $(tooldir)/ruby_vm/views/_mjit_compile_getconstant_path.erb BUILTIN_RB_SRCS = \ $(srcdir)/ast.rb \ @@ -2251,6 +2251,30 @@ add_adjust_info(struct iseq_insn_info_entry *insns_info, unsigned int *positions return TRUE; } +static ID * +array_to_idlist(VALUE arr) +{ + RUBY_ASSERT(RB_TYPE_P(arr, T_ARRAY)); + long size = RARRAY_LEN(arr); + ID *ids = (ID *)ALLOC_N(ID, size + 1); + for (int i = 0; i < size; i++) { + VALUE sym = RARRAY_AREF(arr, i); + ids[i] = SYM2ID(sym); + } + ids[size] = 0; + return ids; +} + +static VALUE +idlist_to_array(const ID *ids) +{ + VALUE arr = rb_ary_new(); + while (*ids) { + rb_ary_push(arr, ID2SYM(*ids++)); + } + return arr; +} + /** ruby insn object list -> raw instruction sequence */ @@ -2433,6 +2457,21 @@ iseq_set_sequence(rb_iseq_t *iseq, LINK_ANCHOR *const anchor) } /* [ TS_IVC | TS_ICVARC | TS_ISE | TS_IC ] */ case TS_IC: /* inline cache: constants */ + { + unsigned int ic_index = ISEQ_COMPILE_DATA(iseq)->ic_index++; + IC ic = &ISEQ_IS_ENTRY_START(body, type)[ic_index].ic_cache; + if (UNLIKELY(ic_index >= body->ic_size)) { + BADINSN_DUMP(anchor, &iobj->link, 0); + COMPILE_ERROR(iseq, iobj->insn_info.line_no, + "iseq_set_sequence: ic_index overflow: index: %d, size: %d", + ic_index, ISEQ_IS_SIZE(body)); + } + + ic->segments = array_to_idlist(operands[j]); + + generated_iseq[code_index + 1 + j] = (VALUE)ic; + } + break; case TS_ISE: /* inline storage entry: `once` insn */ case TS_ICVARC: /* inline cvar cache */ case TS_IVC: /* inline ivar cache */ @@ -2447,11 +2486,6 @@ iseq_set_sequence(rb_iseq_t *iseq, LINK_ANCHOR *const anchor) } generated_iseq[code_index + 1 + j] = (VALUE)ic; - if (insn == BIN(opt_getinlinecache) && type == TS_IC) { - // Store the instruction index for opt_getinlinecache on the IC for - // YJIT to invalidate code when opt_setinlinecache runs. - ic->get_insn_idx = (unsigned int)code_index; - } break; } case TS_CALLDATA: @@ -5233,6 +5267,30 @@ compile_massign(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, return COMPILE_OK; } +static VALUE +collect_const_segments(rb_iseq_t *iseq, const NODE *node) +{ + VALUE arr = rb_ary_new(); + for (;;) + { + switch (nd_type(node)) { + case NODE_CONST: + rb_ary_unshift(arr, ID2SYM(node->nd_vid)); + return arr; + case NODE_COLON3: + rb_ary_unshift(arr, ID2SYM(node->nd_mid)); + rb_ary_unshift(arr, ID2SYM(idNULL)); + return arr; + case NODE_COLON2: + rb_ary_unshift(arr, ID2SYM(node->nd_mid)); + node = node->nd_head; + break; + default: + return Qfalse; + } + } +} + static int compile_const_prefix(rb_iseq_t *iseq, const NODE *const node, LINK_ANCHOR *const pref, LINK_ANCHOR *const body) @@ -8970,37 +9028,31 @@ compile_match(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, i static int compile_colon2(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, int popped) { - const int line = nd_line(node); if (rb_is_const_id(node->nd_mid)) { /* constant */ - LABEL *lend = NEW_LABEL(line); - int ic_index = ISEQ_BODY(iseq)->ic_size++; - - DECL_ANCHOR(pref); - DECL_ANCHOR(body); - - INIT_ANCHOR(pref); - INIT_ANCHOR(body); - CHECK(compile_const_prefix(iseq, node, pref, body)); - if (LIST_INSN_SIZE_ZERO(pref)) { - if (ISEQ_COMPILE_DATA(iseq)->option->inline_const_cache) { - ADD_INSN2(ret, node, opt_getinlinecache, lend, INT2FIX(ic_index)); - } - else { + VALUE segments; + if (ISEQ_COMPILE_DATA(iseq)->option->inline_const_cache && + (segments = collect_const_segments(iseq, node))) { + ISEQ_BODY(iseq)->ic_size++; + ADD_INSN1(ret, node, opt_getconstant_path, segments); + RB_OBJ_WRITTEN(iseq, Qundef, segments); + } else { + /* constant */ + DECL_ANCHOR(pref); + DECL_ANCHOR(body); + + INIT_ANCHOR(pref); + INIT_ANCHOR(body); + CHECK(compile_const_prefix(iseq, node, pref, body)); + if (LIST_INSN_SIZE_ZERO(pref)) { ADD_INSN(ret, node, putnil); + ADD_SEQ(ret, body); } - - ADD_SEQ(ret, body); - - if (ISEQ_COMPILE_DATA(iseq)->option->inline_const_cache) { - ADD_INSN1(ret, node, opt_setinlinecache, INT2FIX(ic_index)); - ADD_LABEL(ret, lend); + else { + ADD_SEQ(ret, pref); + ADD_SEQ(ret, body); } } - else { - ADD_SEQ(ret, pref); - ADD_SEQ(ret, body); - } } else { /* function call */ @@ -9017,25 +9069,18 @@ compile_colon2(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, static int compile_colon3(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, int popped) { - const int line = nd_line(node); - LABEL *lend = NEW_LABEL(line); - int ic_index = ISEQ_BODY(iseq)->ic_size++; - debugi("colon3#nd_mid", node->nd_mid); /* add cache insn */ if (ISEQ_COMPILE_DATA(iseq)->option->inline_const_cache) { - ADD_INSN2(ret, node, opt_getinlinecache, lend, INT2FIX(ic_index)); - ADD_INSN(ret, node, pop); - } - - ADD_INSN1(ret, node, putobject, rb_cObject); - ADD_INSN1(ret, node, putobject, Qtrue); - ADD_INSN1(ret, node, getconstant, ID2SYM(node->nd_mid)); - - if (ISEQ_COMPILE_DATA(iseq)->option->inline_const_cache) { - ADD_INSN1(ret, node, opt_setinlinecache, INT2FIX(ic_index)); - ADD_LABEL(ret, lend); + ISEQ_BODY(iseq)->ic_size++; + VALUE segments = rb_ary_new_from_args(2, ID2SYM(idNULL), ID2SYM(node->nd_mid)); + ADD_INSN1(ret, node, opt_getconstant_path, segments); + RB_OBJ_WRITTEN(iseq, Qundef, segments); + } else { + ADD_INSN1(ret, node, putobject, rb_cObject); + ADD_INSN1(ret, node, putobject, Qtrue); + ADD_INSN1(ret, node, getconstant, ID2SYM(node->nd_mid)); } if (popped) { @@ -9536,18 +9581,14 @@ iseq_compile_each0(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const no case NODE_CONST:{ debugi("nd_vid", node->nd_vid); - if (ISEQ_COMPILE_DATA(iseq)->option->inline_const_cache) { - LABEL *lend = NEW_LABEL(line); - int ic_index = body->ic_size++; - - ADD_INSN2(ret, node, opt_getinlinecache, lend, INT2FIX(ic_index)); - ADD_INSN1(ret, node, putobject, Qtrue); - ADD_INSN1(ret, node, getconstant, ID2SYM(node->nd_vid)); - ADD_INSN1(ret, node, opt_setinlinecache, INT2FIX(ic_index)); - ADD_LABEL(ret, lend); - } - else { - ADD_INSN(ret, node, putnil); + if (ISEQ_COMPILE_DATA(iseq)->option->inline_const_cache) { + body->ic_size++; + VALUE segments = rb_ary_new_from_args(1, ID2SYM(node->nd_vid)); + ADD_INSN1(ret, node, opt_getconstant_path, segments); + RB_OBJ_WRITTEN(iseq, Qundef, segments); + } + else { + ADD_INSN(ret, node, putnil); ADD_INSN1(ret, node, putobject, Qtrue); ADD_INSN1(ret, node, getconstant, ID2SYM(node->nd_vid)); } @@ -10032,10 +10073,16 @@ insn_data_to_s_detail(INSN *iobj) rb_str_concat(str, opobj_inspect(OPERAND_AT(iobj, j))); break; case TS_IC: /* inline cache */ + rb_str_concat(str, opobj_inspect(OPERAND_AT(iobj, j))); + break; case TS_IVC: /* inline ivar cache */ + rb_str_catf(str, "<ivc:%d>", FIX2INT(OPERAND_AT(iobj, j))); + break; case TS_ICVARC: /* inline cvar cache */ + rb_str_catf(str, "<icvarc:%d>", FIX2INT(OPERAND_AT(iobj, j))); + break; case TS_ISE: /* inline storage entry */ - rb_str_catf(str, "<ic:%d>", FIX2INT(OPERAND_AT(iobj, j))); + rb_str_catf(str, "<ise:%d>", FIX2INT(OPERAND_AT(iobj, j))); break; case TS_CALLDATA: /* we store these as call infos at compile time */ { @@ -10431,9 +10478,20 @@ iseq_build_from_ary_body(rb_iseq_t *iseq, LINK_ANCHOR *const anchor, } break; case TS_IC: - argv[j] = op; - if (NUM2UINT(op) >= ISEQ_BODY(iseq)->ic_size) { - ISEQ_BODY(iseq)->ic_size = NUM2INT(op) + 1; + { + VALUE segments = rb_ary_new(); + op = rb_to_array_type(op); + + for (int i = 0; i < RARRAY_LEN(op); i++) { + VALUE sym = RARRAY_AREF(op, i); + sym = rb_to_symbol_type(sym); + rb_ary_push(segments, sym); + } + + RB_GC_GUARD(op); + argv[j] = segments; + RB_OBJ_WRITTEN(iseq, Qundef, segments); + ISEQ_BODY(iseq)->ic_size++; } break; case TS_IVC: /* inline ivar cache */ @@ -10627,6 +10685,7 @@ rb_iseq_mark_insn_storage(struct iseq_compile_data_storage *storage) case TS_CDHASH: case TS_ISEQ: case TS_VALUE: + case TS_IC: // constant path array case TS_CALLDATA: // ci is stored. { VALUE op = OPERAND_AT(iobj, j); @@ -11255,6 +11314,12 @@ ibf_dump_code(struct ibf_dump *dump, const rb_iseq_t *iseq) wv = (VALUE)ibf_dump_iseq(dump, (const rb_iseq_t *)op); break; case TS_IC: + { + IC ic = (IC)op; + VALUE arr = idlist_to_array(ic->segments); + wv = ibf_dump_object(dump, arr); + } + break; case TS_ISE: case TS_IVC: case TS_ICVARC: @@ -11299,6 +11364,7 @@ ibf_load_code(const struct ibf_load *load, rb_iseq_t *iseq, ibf_offset_t bytecod struct rb_iseq_constant_body *load_body = ISEQ_BODY(iseq); struct rb_call_data *cd_entries = load_body->call_data; + int ic_index = 0; iseq_bits_t * mark_offset_bits; @@ -11315,7 +11381,6 @@ ibf_load_code(const struct ibf_load *load, rb_iseq_t *iseq, ibf_offset_t bytecod for (code_index=0; code_index<iseq_size;) { /* opcode */ const VALUE insn = code[code_index] = ibf_load_small_value(load, &reading_pos); - const unsigned int insn_index = code_index; const char *types = insn_op_types(insn); int op_index; @@ -11370,6 +11435,16 @@ ibf_load_code(const struct ibf_load *load, rb_iseq_t *iseq, ibf_offset_t bytecod break; } case TS_IC: + { + VALUE op = ibf_load_small_value(load, &reading_pos); + VALUE arr = ibf_load_object(load, op); + + IC ic = &ISEQ_IS_IC_ENTRY(load_body, ic_index++); + ic->segments = array_to_idlist(arr); + + code[code_index] = (VALUE)ic; + } + break; case TS_ISE: case TS_ICVARC: case TS_IVC: @@ -11378,12 +11453,6 @@ ibf_load_code(const struct ibf_load *load, rb_iseq_t *iseq, ibf_offset_t bytecod ISE ic = ISEQ_IS_ENTRY_START(load_body, operand_type) + op; code[code_index] = (VALUE)ic; - - if (insn == BIN(opt_getinlinecache) && operand_type == TS_IC) { - // Store the instruction index for opt_getinlinecache on the IC for - // YJIT to invalidate code when opt_setinlinecache runs. - ic->ic_cache.get_insn_idx = insn_index; - } } break; case TS_CALLDATA: @@ -253,6 +253,29 @@ setclassvariable vm_setclassvariable(GET_ISEQ(), GET_CFP(), id, val, ic); } +DEFINE_INSN +opt_getconstant_path +(IC ic) +() +(VALUE val) +// attr bool leaf = false; /* may autoload or raise */ +{ + const ID *segments = ic->segments; + struct iseq_inline_constant_cache_entry *ice = ic->entry; + if (ice && vm_ic_hit_p(ice, GET_EP())) { + val = ice->value; + + VM_ASSERT(val == vm_get_ev_const_chain(ec, segments)); + } else { + ruby_vm_constant_cache_misses++; + val = vm_get_ev_const_chain(ec, segments); + vm_ic_track_const_chain(GET_CFP(), ic, segments); + // Because leaf=false, we need to undo the PC increment to get the address to this instruction + // INSN_ATTR(width) == 2 + vm_ic_update(GET_ISEQ(), ic, val, GET_EP(), GET_PC() - 2); + } +} + /* Get constant variable id. If klass is Qnil and allow_nil is Qtrue, constants are searched in the current scope. Otherwise, get constant under klass class or module. @@ -1039,46 +1062,6 @@ branchnil /* for optimize */ /**********************************************************/ -/* push inline-cached value and go to dst if it is valid */ -DEFINE_INSN -opt_getinlinecache -(OFFSET dst, IC ic) -() -(VALUE val) -{ - struct iseq_inline_constant_cache_entry *ice = ic->entry; - - // If there isn't an entry, then we're going to walk through the ISEQ - // starting at this instruction until we get to the associated - // opt_setinlinecache and associate this inline cache with every getconstant - // listed in between. We're doing this here instead of when the instructions - // are first compiled because it's possible to turn off inline caches and we - // want this to work in either case. - if (!ice) { - vm_ic_compile(GET_CFP(), ic); - } - - if (ice && vm_ic_hit_p(ice, GET_EP())) { - val = ice->value; - JUMP(dst); - } - else { - ruby_vm_constant_cache_misses++; - val = Qnil; - } -} - -/* set inline cache */ -DEFINE_INSN -opt_setinlinecache -(IC ic) -(VALUE val) -(VALUE val) -// attr bool leaf = false; -{ - vm_ic_update(GET_ISEQ(), ic, val, GET_EP()); -} - /* run iseq only once */ DEFINE_INSN once @@ -102,68 +102,49 @@ compile_data_free(struct iseq_compile_data *compile_data) } } -struct iseq_clear_ic_references_data { - IC ic; -}; - -// This iterator is used to walk through the instructions and clean any -// references to ICs that are contained within this ISEQ out of the VM's -// constant cache table. It passes around a struct that holds the current IC -// we're looking for, which can be NULL (if we haven't hit an opt_getinlinecache -// instruction yet) or set to an IC (if we've hit an opt_getinlinecache and -// haven't yet hit the associated opt_setinlinecache). -static bool -iseq_clear_ic_references_i(VALUE *code, VALUE insn, size_t index, void *data) -{ - struct iseq_clear_ic_references_data *ic_data = (struct iseq_clear_ic_references_data *) data; +static void +remove_from_constant_cache(ID id, IC ic) { + rb_vm_t *vm = GET_VM(); + VALUE lookup_result; + st_data_t ic_data = (st_data_t)ic; - switch (insn) { - case BIN(opt_getinlinecache): { - RUBY_ASSERT_ALWAYS(ic_data->ic == NULL); + if (rb_id_table_lookup(vm->constant_cache, id, &lookup_result)) { + st_table *ics = (st_table *)lookup_result; + st_delete(ics, &ic_data, NULL); - ic_data->ic = (IC) code[index + 2]; - return true; - } - case BIN(getconstant): { - if (ic_data->ic != NULL) { - ID id = (ID) code[index + 1]; - rb_vm_t *vm = GET_VM(); - VALUE lookup_result; - - if (rb_id_table_lookup(vm->constant_cache, id, &lookup_result)) { - st_table *ics = (st_table *)lookup_result; - st_data_t ic = (st_data_t)ic_data->ic; - st_delete(ics, &ic, NULL); - - if (ics->num_entries == 0) { - rb_id_table_delete(vm->constant_cache, id); - st_free_table(ics); - } - } + if (ics->num_entries == 0) { + rb_id_table_delete(vm->constant_cache, id); + st_free_table(ics); } - - return true; - } - case BIN(opt_setinlinecache): { - RUBY_ASSERT_ALWAYS(ic_data->ic != NULL); - - ic_data->ic = NULL; - return true; - } - default: - return true; } } // When an ISEQ is being freed, all of its associated ICs are going to go away -// as well. Because of this, we need to walk through the ISEQ, find any -// opt_getinlinecache calls, and clear out the VM's constant cache of associated -// ICs. +// as well. Because of this, we need to iterate over the ICs, and clear them +// from the VM's constant cache. static void iseq_clear_ic_references(const rb_iseq_t *iseq) { - struct iseq_clear_ic_references_data data = { .ic = NULL }; - rb_iseq_each(iseq, 0, iseq_clear_ic_references_i, (void *) &data); + for (unsigned int ic_idx = 0; ic_idx < ISEQ_BODY(iseq)->ic_size; ic_idx++) { + IC ic = &ISEQ_IS_IC_ENTRY(ISEQ_BODY(iseq), ic_idx); + + // Iterate over the IC's constant path's segments and clean any references to + // the ICs out of the VM's constant cache table. + const ID *segments = ic->segments; + + // It's possible that segments is NULL if we overallocated an IC but + // optimizations removed the instruction using it + if (segments == NULL) + continue; + + for (int i = 0; segments[i]; i++) { + ID id = segments[i]; + if (id == idNULL) continue; + remove_from_constant_cache(id, ic); + } + + ruby_xfree((void *)segments); + } } void @@ -213,32 +194,7 @@ rb_iseq_free(const rb_iseq_t *iseq) RUBY_FREE_LEAVE("iseq"); } -#if OPT_DIRECT_THREADED_CODE || OPT_CALL_THREADED_CODE -static VALUE -rb_vm_insn_addr2insn2(const void *addr) -{ - return (VALUE)rb_vm_insn_addr2insn(addr); -} -#endif - -// The translator for OPT_DIRECT_THREADED_CODE and OPT_CALL_THREADED_CODE does -// some normalization to always return the non-trace version of instructions. To -// mirror that behavior in token-threaded environments, we normalize in this -// translator by also returning non-trace opcodes. -static VALUE -rb_vm_insn_normalizing_translator(const void *addr) -{ - VALUE opcode = (VALUE)addr; - VALUE trace_opcode_threshold = (VM_INSTRUCTION_SIZE / 2); - - if (opcode >= trace_opcode_threshold) { - return opcode - trace_opcode_threshold; - } - return opcode; -} - typedef VALUE iseq_value_itr_t(void *ctx, VALUE obj); -typedef VALUE rb_vm_insns_translator_t(const void *addr); static inline void iseq_scan_bits(unsigned int page, iseq_bits_t bits, VALUE *code, iseq_value_itr_t *func, void *data) @@ -593,6 +549,17 @@ rb_iseq_memsize(const rb_iseq_t *iseq) /* body->is_entries */ size += ISEQ_IS_SIZE(body) * sizeof(union iseq_inline_storage_entry); + /* IC entries constant segments */ + for (unsigned int ic_idx = 0; ic_idx < body->ic_size; ic_idx++) { + IC ic = &ISEQ_IS_IC_ENTRY(body, ic_idx); + const ID *ids = ic->segments; + if (!ids) continue; + while (*ids++) { + size += sizeof(ID); + } + size += sizeof(ID); // null terminator + } + /* body->call_data */ size += body->ci_size * sizeof(struct rb_call_data); // TODO: should we count imemo_callinfo? @@ -2175,6 +2142,16 @@ rb_insn_operand_intern(const rb_iseq_t *iseq, } case TS_IC: + { + ret = rb_sprintf("<ic:%"PRIdPTRDIFF" ", (union iseq_inline_storage_entry *)op - ISEQ_BODY(iseq)->is_entries); + const ID *segments = ((IC)op)->segments; + rb_str_cat2(ret, rb_id2name(*segments++)); + while (*segments) { + rb_str_catf(ret, "::%s", rb_id2name(*segments++)); + } + rb_str_cat2(ret, ">"); + } + break; case TS_IVC: case TS_ICVARC: case TS_ISE: @@ -3011,6 +2988,15 @@ iseq_data_to_ary(const rb_iseq_t *iseq) } break; case TS_IC: + { + VALUE list = rb_ary_new(); + const ID *ids = ((IC)*seq)->segments; + while (*ids) { + rb_ary_push(list, ID2SYM(*ids++)); + } + rb_ary_push(ary, list); + } + break; case TS_IVC: case TS_ICVARC: case TS_ISE: @@ -119,6 +119,7 @@ struct iseq_compile_data { int node_level; int isolated_depth; unsigned int ci_index; + unsigned int ic_index; const rb_compile_option_t *option; struct rb_id_table *ivar_cache_table; const struct rb_builtin_function *builtin_function_table; diff --git a/test/ruby/test_mjit.rb b/test/ruby/test_mjit.rb index 9cd93855bd..e49195f763 100644 --- a/test/ruby/test_mjit.rb +++ b/test/ruby/test_mjit.rb @@ -179,7 +179,7 @@ class TestMJIT < Test::Unit::TestCase end def test_compile_insn_constant - assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[getconstant setconstant]) + assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[opt_getconstant_path setconstant]) begin; FOO = 1 FOO @@ -490,8 +490,8 @@ class TestMJIT < Test::Unit::TestCase end; end - def test_compile_insn_inlinecache - assert_compile_once('Struct', result_inspect: 'Struct', insns: %i[opt_getinlinecache opt_setinlinecache]) + def test_compile_insn_getconstant_path + assert_compile_once('Struct', result_inspect: 'Struct', insns: %i[opt_getconstant_path]) end def test_compile_insn_once diff --git a/test/ruby/test_yjit.rb b/test/ruby/test_yjit.rb index 37e72dcafa..6cafb21698 100644 --- a/test/ruby/test_yjit.rb +++ b/test/ruby/test_yjit.rb @@ -389,8 +389,8 @@ class TestYJIT < Test::Unit::TestCase assert_compiles("'foo' =~ /(o)./; $2", insns: %i[getspecial], result: nil) end - def test_compile_opt_getinlinecache - assert_compiles(<<~RUBY, insns: %i[opt_getinlinecache], result: 123, call_threshold: 2) + def test_compile_opt_getconstant_path + assert_compiles(<<~RUBY, insns: %i[opt_getconstant_path], result: 123, call_threshold: 2) def get_foo FOO end @@ -402,8 +402,8 @@ class TestYJIT < Test::Unit::TestCase RUBY end - def test_opt_getinlinecache_slowpath - assert_compiles(<<~RUBY, exits: { opt_getinlinecache: 1 }, result: [42, 42, 1, 1], call_threshold: 2) + def test_opt_getconstant_path_slowpath + assert_compiles(<<~RUBY, exits: { opt_getconstant_path: 1 }, result: [42, 42, 1, 1], call_threshold: 2) class A FOO = 42 class << self diff --git a/tool/ruby_vm/views/_mjit_compile_getinlinecache.erb b/tool/ruby_vm/views/_mjit_compile_getconstant_path.erb index fa38af4045..c321da9a52 100644 --- a/tool/ruby_vm/views/_mjit_compile_getinlinecache.erb +++ b/tool/ruby_vm/views/_mjit_compile_getconstant_path.erb @@ -17,7 +17,6 @@ % # JIT: Inline everything in IC, and cancel the slow path fprintf(f, " if (vm_inlined_ic_hit_p(0x%"PRIxVALUE", 0x%"PRIxVALUE", (const rb_cref_t *)0x%"PRIxVALUE", reg_cfp->ep)) {", ice->flags, ice->value, (VALUE)ice->ic_cref); fprintf(f, " stack[%d] = 0x%"PRIxVALUE";\n", b->stack_size, ice->value); - fprintf(f, " goto label_%d;\n", pos + insn_len(insn) + (int)dst); fprintf(f, " }"); fprintf(f, " else {"); fprintf(f, " reg_cfp->sp = vm_base_ptr(reg_cfp) + %d;\n", b->stack_size); diff --git a/tool/ruby_vm/views/mjit_compile.inc.erb b/tool/ruby_vm/views/mjit_compile.inc.erb index 0e66f78007..00808b21ff 100644 --- a/tool/ruby_vm/views/mjit_compile.inc.erb +++ b/tool/ruby_vm/views/mjit_compile.inc.erb @@ -65,8 +65,8 @@ switch (insn) { <%= render 'mjit_compile_ivar', locals: { insn: insn } -%> % when 'invokebuiltin', 'opt_invokebuiltin_delegate' <%= render 'mjit_compile_invokebuiltin', locals: { insn: insn } -%> -% when 'opt_getinlinecache' -<%= render 'mjit_compile_getinlinecache', locals: { insn: insn } -%> +% when 'opt_getconstant_path' +<%= render 'mjit_compile_getconstant_path', locals: { insn: insn } -%> % when 'leave', 'opt_invokebuiltin_delegate_leave' % # opt_invokebuiltin_delegate_leave also implements leave insn. We need to handle it here for inlining. % if insn.name == 'opt_invokebuiltin_delegate_leave' @@ -256,9 +256,19 @@ STATIC_ASSERT(sizeof_iseq_inline_constant_cache_entry, struct iseq_inline_constant_cache { struct iseq_inline_constant_cache_entry *entry; - // For YJIT: the index to the opt_getinlinecache instruction in the same iseq. - // It's set during compile time and constant once set. - unsigned get_insn_idx; + + /** + * A null-terminated list of ids, used to represent a constant's path + * idNULL is used to represent the :: prefix, and 0 is used to donate the end + * of the list. + * + * For example + * FOO {rb_intern("FOO"), 0} + * FOO::BAR {rb_intern("FOO"), rb_intern("BAR"), 0} + * ::FOO {idNULL, rb_intern("FOO"), 0} + * ::FOO::BAR {idNULL, rb_intern("FOO"), rb_intern("BAR"), 0} + */ + const ID *segments; }; struct iseq_inline_iv_cache_entry { @@ -339,6 +349,9 @@ typedef uintptr_t iseq_bits_t; #define ISEQ_IS_SIZE(body) (body->ic_size + body->ivc_size + body->ise_size + body->icvarc_size) +/* [ TS_IVC | TS_ICVARC | TS_ISE | TS_IC ] */ +#define ISEQ_IS_IC_ENTRY(body, idx) (body->is_entries[(idx) + body->ise_size + body->icvarc_size + body->ivc_size].ic_cache); + /* instruction sequence type */ enum rb_iseq_type { ISEQ_TYPE_TOP, diff --git a/vm_insnhelper.c b/vm_insnhelper.c index ab1394c7ca..9ccfdff4a0 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -1040,6 +1040,26 @@ vm_get_ev_const(rb_execution_context_t *ec, VALUE orig_klass, ID id, bool allow_ } static inline VALUE +vm_get_ev_const_chain(rb_execution_context_t *ec, const ID *segments) +{ + VALUE val = Qnil; + int idx = 0; + int allow_nil = TRUE; + if (segments[0] == idNULL) { + val = rb_cObject; + idx++; + allow_nil = FALSE; + } + while (segments[idx]) { + ID id = segments[idx++]; + val = vm_get_ev_const(ec, val, id, allow_nil, 0); + allow_nil = FALSE; + } + return val; +} + + +static inline VALUE vm_get_cvar_base(const rb_cref_t *cref, const rb_control_frame_t *cfp, int top_level_raise) { VALUE klass; @@ -4949,53 +4969,35 @@ vm_opt_newarray_min(rb_execution_context_t *ec, rb_num_t num, const VALUE *ptr) #define IMEMO_CONST_CACHE_SHAREABLE IMEMO_FL_USER0 -// This is the iterator used by vm_ic_compile for rb_iseq_each. It is used as a -// callback for each instruction within the ISEQ, and is meant to return a -// boolean indicating whether or not to keep iterating. -// -// This is used to walk through the ISEQ and find all getconstant instructions -// between the starting opt_getinlinecache and the ending opt_setinlinecache and -// associating the inline cache with the constant name components on the VM. -static bool -vm_ic_compile_i(VALUE *code, VALUE insn, size_t index, void *ic) +static void +vm_track_constant_cache(ID id, void *ic) { - if (insn == BIN(opt_setinlinecache)) { - return false; - } + struct rb_id_table *const_cache = GET_VM()->constant_cache; + VALUE lookup_result; + st_table *ics; - if (insn == BIN(getconstant)) { - ID id = code[index + 1]; - struct rb_id_table *const_cache = GET_VM()->constant_cache; - VALUE lookup_result; - st_table *ics; - - if (rb_id_table_lookup(const_cache, id, &lookup_result)) { - ics = (st_table *)lookup_result; - } - else { - ics = st_init_numtable(); - rb_id_table_insert(const_cache, id, (VALUE)ics); - } - - st_insert(ics, (st_data_t) ic, (st_data_t) Qtrue); + if (rb_id_table_lookup(const_cache, id, &lookup_result)) { + ics = (st_table *)lookup_result; + } + else { + ics = st_init_numtable(); + rb_id_table_insert(const_cache, id, (VALUE)ics); } - return true; + st_insert(ics, (st_data_t) ic, (st_data_t) Qtrue); } -// Loop through the instruction sequences starting at the opt_getinlinecache -// call and gather up every getconstant's ID. Associate that with the VM's -// constant cache so that whenever one of the constants changes the inline cache -// will get busted. static void -vm_ic_compile(rb_control_frame_t *cfp, IC ic) +vm_ic_track_const_chain(rb_control_frame_t *cfp, IC ic, const ID *segments) { - const rb_iseq_t *iseq = cfp->iseq; - RB_VM_LOCK_ENTER(); - { - rb_iseq_each(iseq, cfp->pc - ISEQ_BODY(iseq)->iseq_encoded, vm_ic_compile_i, (void *) ic); + + for (int i = 0; segments[i]; i++) { + ID id = segments[i]; + if (id == idNULL) continue; + vm_track_constant_cache(id, ic); } + RB_VM_LOCK_LEAVE(); } @@ -5027,7 +5029,7 @@ rb_vm_ic_hit_p(IC ic, const VALUE *reg_ep) } static void -vm_ic_update(const rb_iseq_t *iseq, IC ic, VALUE val, const VALUE *reg_ep) +vm_ic_update(const rb_iseq_t *iseq, IC ic, VALUE val, const VALUE *reg_ep, const VALUE *pc) { if (ruby_vm_const_missing_count > 0) { ruby_vm_const_missing_count = 0; @@ -5043,7 +5045,9 @@ vm_ic_update(const rb_iseq_t *iseq, IC ic, VALUE val, const VALUE *reg_ep) #ifndef MJIT_HEADER // MJIT and YJIT can't be on at the same time, so there is no need to // notify YJIT about changes to the IC when running inside MJIT code. - rb_yjit_constant_ic_update(iseq, ic); + RUBY_ASSERT(pc >= ISEQ_BODY(iseq)->iseq_encoded); + unsigned pos = (unsigned)(pc - ISEQ_BODY(iseq)->iseq_encoded); + rb_yjit_constant_ic_update(iseq, ic, pos); #endif } @@ -41,7 +41,7 @@ void rb_yjit_iseq_mark(void *payload); void rb_yjit_iseq_update_references(void *payload); void rb_yjit_iseq_free(void *payload); void rb_yjit_before_ractor_spawn(void); -void rb_yjit_constant_ic_update(const rb_iseq_t *const iseq, IC ic); +void rb_yjit_constant_ic_update(const rb_iseq_t *const iseq, IC ic, unsigned insn_idx); void rb_yjit_tracing_invalidate_all(void); #else @@ -64,7 +64,7 @@ static inline void rb_yjit_iseq_mark(void *payload) {} static inline void rb_yjit_iseq_update_references(void *payload) {} static inline void rb_yjit_iseq_free(void *payload) {} static inline void rb_yjit_before_ractor_spawn(void) {} -static inline void rb_yjit_constant_ic_update(const rb_iseq_t *const iseq, IC ic) {} +static inline void rb_yjit_constant_ic_update(const rb_iseq_t *const iseq, IC ic, unsigned insn_idx) {} static inline void rb_yjit_tracing_invalidate_all(void) {} #endif // #if USE_YJIT diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index ac42b4a1e2..58b30d5c85 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -769,9 +769,10 @@ pub fn gen_single_block( .try_into() .unwrap(); - // opt_getinlinecache wants to be in a block all on its own. Cut the block short - // if we run into it. See gen_opt_getinlinecache() for details. - if opcode == YARVINSN_opt_getinlinecache.as_usize() && insn_idx > starting_insn_idx { + // We need opt_getconstant_path to be in a block all on its own. Cut the block short + // if we run into it. This is necessary because we want to invalidate based on the + // instruction's index. + if opcode == YARVINSN_opt_getconstant_path.as_usize() && insn_idx > starting_insn_idx { jump_to_next_insn(&mut jit, &ctx, &mut asm, ocb); break; } @@ -5508,15 +5509,15 @@ fn gen_setclassvariable( KeepCompiling } -fn gen_opt_getinlinecache( +fn gen_opt_getconstant_path( jit: &mut JITState, ctx: &mut Context, asm: &mut Assembler, ocb: &mut OutlinedCb, ) -> CodegenStatus { - let jump_offset = jit_get_arg(jit, 0); - let const_cache_as_value = jit_get_arg(jit, 1); + let const_cache_as_value = jit_get_arg(jit, 0); let ic: *const iseq_inline_constant_cache = const_cache_as_value.as_ptr(); + let idlist: *const ID = unsafe { (*ic).segments }; // See vm_ic_hit_p(). The same conditions are checked in yjit_constant_ic_update(). let ice = unsafe { (*ic).entry }; @@ -5573,22 +5574,12 @@ fn gen_opt_getinlinecache( // Invalidate output code on any constant writes associated with // constants referenced within the current block. - assume_stable_constant_names(jit, ocb); + assume_stable_constant_names(jit, ocb, idlist); jit_putobject(jit, ctx, asm, unsafe { (*ice).value }); } - // Jump over the code for filling the cache - let jump_idx = jit_next_insn_idx(jit) + jump_offset.as_u32(); - gen_direct_jump( - jit, - ctx, - BlockId { - iseq: jit.iseq, - idx: jump_idx, - }, - asm, - ); + jump_to_next_insn(jit, ctx, asm, ocb); EndBlock } @@ -5920,7 +5911,7 @@ fn get_gen_fn(opcode: VALUE) -> Option<InsnGenFn> { YARVINSN_opt_size => Some(gen_opt_size), YARVINSN_opt_length => Some(gen_opt_length), YARVINSN_opt_regexpmatch2 => Some(gen_opt_regexpmatch2), - YARVINSN_opt_getinlinecache => Some(gen_opt_getinlinecache), + YARVINSN_opt_getconstant_path => Some(gen_opt_getconstant_path), YARVINSN_invokebuiltin => Some(gen_invokebuiltin), YARVINSN_opt_invokebuiltin_delegate => Some(gen_opt_invokebuiltin_delegate), YARVINSN_opt_invokebuiltin_delegate_leave => Some(gen_opt_invokebuiltin_delegate), diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index 591408e1da..cedc3216b8 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -588,7 +588,7 @@ pub struct iseq_inline_constant_cache_entry { #[derive(Debug, Copy, Clone)] pub struct iseq_inline_constant_cache { pub entry: *mut iseq_inline_constant_cache_entry, - pub get_insn_idx: ::std::os::raw::c_uint, + pub segments: *const ID, } #[repr(C)] #[derive(Debug, Copy, Clone)] @@ -784,108 +784,108 @@ pub const YARVINSN_getinstancevariable: ruby_vminsn_type = 8; pub const YARVINSN_setinstancevariable: ruby_vminsn_type = 9; pub const YARVINSN_getclassvariable: ruby_vminsn_type = 10; pub const YARVINSN_setclassvariable: ruby_vminsn_type = 11; -pub const YARVINSN_getconstant: ruby_vminsn_type = 12; -pub const YARVINSN_setconstant: ruby_vminsn_type = 13; -pub const YARVINSN_getglobal: ruby_vminsn_type = 14; -pub const YARVINSN_setglobal: ruby_vminsn_type = 15; -pub const YARVINSN_putnil: ruby_vminsn_type = 16; -pub const YARVINSN_putself: ruby_vminsn_type = 17; -pub const YARVINSN_putobject: ruby_vminsn_type = 18; -pub const YARVINSN_putspecialobject: ruby_vminsn_type = 19; -pub const YARVINSN_putstring: ruby_vminsn_type = 20; -pub const YARVINSN_concatstrings: ruby_vminsn_type = 21; -pub const YARVINSN_anytostring: ruby_vminsn_type = 22; -pub const YARVINSN_toregexp: ruby_vminsn_type = 23; -pub const YARVINSN_intern: ruby_vminsn_type = 24; -pub const YARVINSN_newarray: ruby_vminsn_type = 25; -pub const YARVINSN_newarraykwsplat: ruby_vminsn_type = 26; -pub const YARVINSN_duparray: ruby_vminsn_type = 27; -pub const YARVINSN_duphash: ruby_vminsn_type = 28; -pub const YARVINSN_expandarray: ruby_vminsn_type = 29; -pub const YARVINSN_concatarray: ruby_vminsn_type = 30; -pub const YARVINSN_splatarray: ruby_vminsn_type = 31; -pub const YARVINSN_newhash: ruby_vminsn_type = 32; -pub const YARVINSN_newrange: ruby_vminsn_type = 33; -pub const YARVINSN_pop: ruby_vminsn_type = 34; -pub const YARVINSN_dup: ruby_vminsn_type = 35; -pub const YARVINSN_dupn: ruby_vminsn_type = 36; -pub const YARVINSN_swap: ruby_vminsn_type = 37; -pub const YARVINSN_opt_reverse: ruby_vminsn_type = 38; -pub const YARVINSN_topn: ruby_vminsn_type = 39; -pub const YARVINSN_setn: ruby_vminsn_type = 40; -pub const YARVINSN_adjuststack: ruby_vminsn_type = 41; -pub const YARVINSN_defined: ruby_vminsn_type = 42; -pub const YARVINSN_checkmatch: ruby_vminsn_type = 43; -pub const YARVINSN_checkkeyword: ruby_vminsn_type = 44; -pub const YARVINSN_checktype: ruby_vminsn_type = 45; -pub const YARVINSN_defineclass: ruby_vminsn_type = 46; -pub const YARVINSN_definemethod: ruby_vminsn_type = 47; -pub const YARVINSN_definesmethod: ruby_vminsn_type = 48; -pub const YARVINSN_send: ruby_vminsn_type = 49; -pub const YARVINSN_opt_send_without_block: ruby_vminsn_type = 50; -pub const YARVINSN_objtostring: ruby_vminsn_type = 51; -pub const YARVINSN_opt_str_freeze: ruby_vminsn_type = 52; -pub const YARVINSN_opt_nil_p: ruby_vminsn_type = 53; -pub const YARVINSN_opt_str_uminus: ruby_vminsn_type = 54; -pub const YARVINSN_opt_newarray_max: ruby_vminsn_type = 55; -pub const YARVINSN_opt_newarray_min: ruby_vminsn_type = 56; -pub const YARVINSN_invokesuper: ruby_vminsn_type = 57; -pub const YARVINSN_invokeblock: ruby_vminsn_type = 58; -pub const YARVINSN_leave: ruby_vminsn_type = 59; -pub const YARVINSN_throw: ruby_vminsn_type = 60; -pub const YARVINSN_jump: ruby_vminsn_type = 61; -pub const YARVINSN_branchif: ruby_vminsn_type = 62; -pub const YARVINSN_branchunless: ruby_vminsn_type = 63; -pub const YARVINSN_branchnil: ruby_vminsn_type = 64; -pub const YARVINSN_opt_getinlinecache: ruby_vminsn_type = 65; -pub const YARVINSN_opt_setinlinecache: ruby_vminsn_type = 66; -pub const YARVINSN_once: ruby_vminsn_type = 67; -pub const YARVINSN_opt_case_dispatch: ruby_vminsn_type = 68; -pub const YARVINSN_opt_plus: ruby_vminsn_type = 69; -pub const YARVINSN_opt_minus: ruby_vminsn_type = 70; -pub const YARVINSN_opt_mult: ruby_vminsn_type = 71; -pub const YARVINSN_opt_div: ruby_vminsn_type = 72; -pub const YARVINSN_opt_mod: ruby_vminsn_type = 73; -pub const YARVINSN_opt_eq: ruby_vminsn_type = 74; -pub const YARVINSN_opt_neq: ruby_vminsn_type = 75; -pub const YARVINSN_opt_lt: ruby_vminsn_type = 76; -pub const YARVINSN_opt_le: ruby_vminsn_type = 77; -pub const YARVINSN_opt_gt: ruby_vminsn_type = 78; -pub const YARVINSN_opt_ge: ruby_vminsn_type = 79; -pub const YARVINSN_opt_ltlt: ruby_vminsn_type = 80; -pub const YARVINSN_opt_and: ruby_vminsn_type = 81; -pub const YARVINSN_opt_or: ruby_vminsn_type = 82; -pub const YARVINSN_opt_aref: ruby_vminsn_type = 83; -pub const YARVINSN_opt_aset: ruby_vminsn_type = 84; -pub const YARVINSN_opt_aset_with: ruby_vminsn_type = 85; -pub const YARVINSN_opt_aref_with: ruby_vminsn_type = 86; -pub const YARVINSN_opt_length: ruby_vminsn_type = 87; -pub const YARVINSN_opt_size: ruby_vminsn_type = 88; -pub const YARVINSN_opt_empty_p: ruby_vminsn_type = 89; -pub const YARVINSN_opt_succ: ruby_vminsn_type = 90; -pub const YARVINSN_opt_not: ruby_vminsn_type = 91; -pub const YARVINSN_opt_regexpmatch2: ruby_vminsn_type = 92; -pub const YARVINSN_invokebuiltin: ruby_vminsn_type = 93; -pub const YARVINSN_opt_invokebuiltin_delegate: ruby_vminsn_type = 94; -pub const YARVINSN_opt_invokebuiltin_delegate_leave: ruby_vminsn_type = 95; -pub const YARVINSN_getlocal_WC_0: ruby_vminsn_type = 96; -pub const YARVINSN_getlocal_WC_1: ruby_vminsn_type = 97; -pub const YARVINSN_setlocal_WC_0: ruby_vminsn_type = 98; -pub const YARVINSN_setlocal_WC_1: ruby_vminsn_type = 99; -pub const YARVINSN_putobject_INT2FIX_0_: ruby_vminsn_type = 100; -pub const YARVINSN_putobject_INT2FIX_1_: ruby_vminsn_type = 101; -pub const YARVINSN_trace_nop: ruby_vminsn_type = 102; -pub const YARVINSN_trace_getlocal: ruby_vminsn_type = 103; -pub const YARVINSN_trace_setlocal: ruby_vminsn_type = 104; -pub const YARVINSN_trace_getblockparam: ruby_vminsn_type = 105; -pub const YARVINSN_trace_setblockparam: ruby_vminsn_type = 106; -pub const YARVINSN_trace_getblockparamproxy: ruby_vminsn_type = 107; -pub const YARVINSN_trace_getspecial: ruby_vminsn_type = 108; -pub const YARVINSN_trace_setspecial: ruby_vminsn_type = 109; -pub const YARVINSN_trace_getinstancevariable: ruby_vminsn_type = 110; -pub const YARVINSN_trace_setinstancevariable: ruby_vminsn_type = 111; -pub const YARVINSN_trace_getclassvariable: ruby_vminsn_type = 112; -pub const YARVINSN_trace_setclassvariable: ruby_vminsn_type = 113; +pub const YARVINSN_opt_getconstant_path: ruby_vminsn_type = 12; +pub const YARVINSN_getconstant: ruby_vminsn_type = 13; +pub const YARVINSN_setconstant: ruby_vminsn_type = 14; +pub const YARVINSN_getglobal: ruby_vminsn_type = 15; +pub const YARVINSN_setglobal: ruby_vminsn_type = 16; +pub const YARVINSN_putnil: ruby_vminsn_type = 17; +pub const YARVINSN_putself: ruby_vminsn_type = 18; +pub const YARVINSN_putobject: ruby_vminsn_type = 19; +pub const YARVINSN_putspecialobject: ruby_vminsn_type = 20; +pub const YARVINSN_putstring: ruby_vminsn_type = 21; +pub const YARVINSN_concatstrings: ruby_vminsn_type = 22; +pub const YARVINSN_anytostring: ruby_vminsn_type = 23; +pub const YARVINSN_toregexp: ruby_vminsn_type = 24; +pub const YARVINSN_intern: ruby_vminsn_type = 25; +pub const YARVINSN_newarray: ruby_vminsn_type = 26; +pub const YARVINSN_newarraykwsplat: ruby_vminsn_type = 27; +pub const YARVINSN_duparray: ruby_vminsn_type = 28; +pub const YARVINSN_duphash: ruby_vminsn_type = 29; +pub const YARVINSN_expandarray: ruby_vminsn_type = 30; +pub const YARVINSN_concatarray: ruby_vminsn_type = 31; +pub const YARVINSN_splatarray: ruby_vminsn_type = 32; +pub const YARVINSN_newhash: ruby_vminsn_type = 33; +pub const YARVINSN_newrange: ruby_vminsn_type = 34; +pub const YARVINSN_pop: ruby_vminsn_type = 35; +pub const YARVINSN_dup: ruby_vminsn_type = 36; +pub const YARVINSN_dupn: ruby_vminsn_type = 37; +pub const YARVINSN_swap: ruby_vminsn_type = 38; +pub const YARVINSN_opt_reverse: ruby_vminsn_type = 39; +pub const YARVINSN_topn: ruby_vminsn_type = 40; +pub const YARVINSN_setn: ruby_vminsn_type = 41; +pub const YARVINSN_adjuststack: ruby_vminsn_type = 42; +pub const YARVINSN_defined: ruby_vminsn_type = 43; +pub const YARVINSN_checkmatch: ruby_vminsn_type = 44; +pub const YARVINSN_checkkeyword: ruby_vminsn_type = 45; +pub const YARVINSN_checktype: ruby_vminsn_type = 46; +pub const YARVINSN_defineclass: ruby_vminsn_type = 47; +pub const YARVINSN_definemethod: ruby_vminsn_type = 48; +pub const YARVINSN_definesmethod: ruby_vminsn_type = 49; +pub const YARVINSN_send: ruby_vminsn_type = 50; +pub const YARVINSN_opt_send_without_block: ruby_vminsn_type = 51; +pub const YARVINSN_objtostring: ruby_vminsn_type = 52; +pub const YARVINSN_opt_str_freeze: ruby_vminsn_type = 53; +pub const YARVINSN_opt_nil_p: ruby_vminsn_type = 54; +pub const YARVINSN_opt_str_uminus: ruby_vminsn_type = 55; +pub const YARVINSN_opt_newarray_max: ruby_vminsn_type = 56; +pub const YARVINSN_opt_newarray_min: ruby_vminsn_type = 57; +pub const YARVINSN_invokesuper: ruby_vminsn_type = 58; +pub const YARVINSN_invokeblock: ruby_vminsn_type = 59; +pub const YARVINSN_leave: ruby_vminsn_type = 60; +pub const YARVINSN_throw: ruby_vminsn_type = 61; +pub const YARVINSN_jump: ruby_vminsn_type = 62; +pub const YARVINSN_branchif: ruby_vminsn_type = 63; +pub const YARVINSN_branchunless: ruby_vminsn_type = 64; +pub const YARVINSN_branchnil: ruby_vminsn_type = 65; +pub const YARVINSN_once: ruby_vminsn_type = 66; +pub const YARVINSN_opt_case_dispatch: ruby_vminsn_type = 67; +pub const YARVINSN_opt_plus: ruby_vminsn_type = 68; +pub const YARVINSN_opt_minus: ruby_vminsn_type = 69; +pub const YARVINSN_opt_mult: ruby_vminsn_type = 70; +pub const YARVINSN_opt_div: ruby_vminsn_type = 71; +pub const YARVINSN_opt_mod: ruby_vminsn_type = 72; +pub const YARVINSN_opt_eq: ruby_vminsn_type = 73; +pub const YARVINSN_opt_neq: ruby_vminsn_type = 74; +pub const YARVINSN_opt_lt: ruby_vminsn_type = 75; +pub const YARVINSN_opt_le: ruby_vminsn_type = 76; +pub const YARVINSN_opt_gt: ruby_vminsn_type = 77; +pub const YARVINSN_opt_ge: ruby_vminsn_type = 78; +pub const YARVINSN_opt_ltlt: ruby_vminsn_type = 79; +pub const YARVINSN_opt_and: ruby_vminsn_type = 80; +pub const YARVINSN_opt_or: ruby_vminsn_type = 81; +pub const YARVINSN_opt_aref: ruby_vminsn_type = 82; +pub const YARVINSN_opt_aset: ruby_vminsn_type = 83; +pub const YARVINSN_opt_aset_with: ruby_vminsn_type = 84; +pub const YARVINSN_opt_aref_with: ruby_vminsn_type = 85; +pub const YARVINSN_opt_length: ruby_vminsn_type = 86; +pub const YARVINSN_opt_size: ruby_vminsn_type = 87; +pub const YARVINSN_opt_empty_p: ruby_vminsn_type = 88; +pub const YARVINSN_opt_succ: ruby_vminsn_type = 89; +pub const YARVINSN_opt_not: ruby_vminsn_type = 90; +pub const YARVINSN_opt_regexpmatch2: ruby_vminsn_type = 91; +pub const YARVINSN_invokebuiltin: ruby_vminsn_type = 92; +pub const YARVINSN_opt_invokebuiltin_delegate: ruby_vminsn_type = 93; +pub const YARVINSN_opt_invokebuiltin_delegate_leave: ruby_vminsn_type = 94; +pub const YARVINSN_getlocal_WC_0: ruby_vminsn_type = 95; +pub const YARVINSN_getlocal_WC_1: ruby_vminsn_type = 96; +pub const YARVINSN_setlocal_WC_0: ruby_vminsn_type = 97; +pub const YARVINSN_setlocal_WC_1: ruby_vminsn_type = 98; +pub const YARVINSN_putobject_INT2FIX_0_: ruby_vminsn_type = 99; +pub const YARVINSN_putobject_INT2FIX_1_: ruby_vminsn_type = 100; +pub const YARVINSN_trace_nop: ruby_vminsn_type = 101; +pub const YARVINSN_trace_getlocal: ruby_vminsn_type = 102; +pub const YARVINSN_trace_setlocal: ruby_vminsn_type = 103; +pub const YARVINSN_trace_getblockparam: ruby_vminsn_type = 104; +pub const YARVINSN_trace_setblockparam: ruby_vminsn_type = 105; +pub const YARVINSN_trace_getblockparamproxy: ruby_vminsn_type = 106; +pub const YARVINSN_trace_getspecial: ruby_vminsn_type = 107; +pub const YARVINSN_trace_setspecial: ruby_vminsn_type = 108; +pub const YARVINSN_trace_getinstancevariable: ruby_vminsn_type = 109; +pub const YARVINSN_trace_setinstancevariable: ruby_vminsn_type = 110; +pub const YARVINSN_trace_getclassvariable: ruby_vminsn_type = 111; +pub const YARVINSN_trace_setclassvariable: ruby_vminsn_type = 112; +pub const YARVINSN_trace_opt_getconstant_path: ruby_vminsn_type = 113; pub const YARVINSN_trace_getconstant: ruby_vminsn_type = 114; pub const YARVINSN_trace_setconstant: ruby_vminsn_type = 115; pub const YARVINSN_trace_getglobal: ruby_vminsn_type = 116; @@ -939,44 +939,42 @@ pub const YARVINSN_trace_jump: ruby_vminsn_type = 163; pub const YARVINSN_trace_branchif: ruby_vminsn_type = 164; pub const YARVINSN_trace_branchunless: ruby_vminsn_type = 165; pub const YARVINSN_trace_branchnil: ruby_vminsn_type = 166; -pub const YARVINSN_trace_opt_getinlinecache: ruby_vminsn_type = 167; -pub const YARVINSN_trace_opt_setinlinecache: ruby_vminsn_type = 168; -pub const YARVINSN_trace_once: ruby_vminsn_type = 169; -pub const YARVINSN_trace_opt_case_dispatch: ruby_vminsn_type = 170; -pub const YARVINSN_trace_opt_plus: ruby_vminsn_type = 171; -pub const YARVINSN_trace_opt_minus: ruby_vminsn_type = 172; -pub const YARVINSN_trace_opt_mult: ruby_vminsn_type = 173; -pub const YARVINSN_trace_opt_div: ruby_vminsn_type = 174; -pub const YARVINSN_trace_opt_mod: ruby_vminsn_type = 175; -pub const YARVINSN_trace_opt_eq: ruby_vminsn_type = 176; -pub const YARVINSN_trace_opt_neq: ruby_vminsn_type = 177; -pub const YARVINSN_trace_opt_lt: ruby_vminsn_type = 178; -pub const YARVINSN_trace_opt_le: ruby_vminsn_type = 179; -pub const YARVINSN_trace_opt_gt: ruby_vminsn_type = 180; -pub const YARVINSN_trace_opt_ge: ruby_vminsn_type = 181; -pub const YARVINSN_trace_opt_ltlt: ruby_vminsn_type = 182; -pub const YARVINSN_trace_opt_and: ruby_vminsn_type = 183; -pub const YARVINSN_trace_opt_or: ruby_vminsn_type = 184; -pub const YARVINSN_trace_opt_aref: ruby_vminsn_type = 185; -pub const YARVINSN_trace_opt_aset: ruby_vminsn_type = 186; -pub const YARVINSN_trace_opt_aset_with: ruby_vminsn_type = 187; -pub const YARVINSN_trace_opt_aref_with: ruby_vminsn_type = 188; -pub const YARVINSN_trace_opt_length: ruby_vminsn_type = 189; -pub const YARVINSN_trace_opt_size: ruby_vminsn_type = 190; -pub const YARVINSN_trace_opt_empty_p: ruby_vminsn_type = 191; -pub const YARVINSN_trace_opt_succ: ruby_vminsn_type = 192; -pub const YARVINSN_trace_opt_not: ruby_vminsn_type = 193; -pub const YARVINSN_trace_opt_regexpmatch2: ruby_vminsn_type = 194; -pub const YARVINSN_trace_invokebuiltin: ruby_vminsn_type = 195; -pub const YARVINSN_trace_opt_invokebuiltin_delegate: ruby_vminsn_type = 196; -pub const YARVINSN_trace_opt_invokebuiltin_delegate_leave: ruby_vminsn_type = 197; -pub const YARVINSN_trace_getlocal_WC_0: ruby_vminsn_type = 198; -pub const YARVINSN_trace_getlocal_WC_1: ruby_vminsn_type = 199; -pub const YARVINSN_trace_setlocal_WC_0: ruby_vminsn_type = 200; -pub const YARVINSN_trace_setlocal_WC_1: ruby_vminsn_type = 201; -pub const YARVINSN_trace_putobject_INT2FIX_0_: ruby_vminsn_type = 202; -pub const YARVINSN_trace_putobject_INT2FIX_1_: ruby_vminsn_type = 203; -pub const VM_INSTRUCTION_SIZE: ruby_vminsn_type = 204; +pub const YARVINSN_trace_once: ruby_vminsn_type = 167; +pub const YARVINSN_trace_opt_case_dispatch: ruby_vminsn_type = 168; +pub const YARVINSN_trace_opt_plus: ruby_vminsn_type = 169; +pub const YARVINSN_trace_opt_minus: ruby_vminsn_type = 170; +pub const YARVINSN_trace_opt_mult: ruby_vminsn_type = 171; +pub const YARVINSN_trace_opt_div: ruby_vminsn_type = 172; +pub const YARVINSN_trace_opt_mod: ruby_vminsn_type = 173; +pub const YARVINSN_trace_opt_eq: ruby_vminsn_type = 174; +pub const YARVINSN_trace_opt_neq: ruby_vminsn_type = 175; +pub const YARVINSN_trace_opt_lt: ruby_vminsn_type = 176; +pub const YARVINSN_trace_opt_le: ruby_vminsn_type = 177; +pub const YARVINSN_trace_opt_gt: ruby_vminsn_type = 178; +pub const YARVINSN_trace_opt_ge: ruby_vminsn_type = 179; +pub const YARVINSN_trace_opt_ltlt: ruby_vminsn_type = 180; +pub const YARVINSN_trace_opt_and: ruby_vminsn_type = 181; +pub const YARVINSN_trace_opt_or: ruby_vminsn_type = 182; +pub const YARVINSN_trace_opt_aref: ruby_vminsn_type = 183; +pub const YARVINSN_trace_opt_aset: ruby_vminsn_type = 184; +pub const YARVINSN_trace_opt_aset_with: ruby_vminsn_type = 185; +pub const YARVINSN_trace_opt_aref_with: ruby_vminsn_type = 186; +pub const YARVINSN_trace_opt_length: ruby_vminsn_type = 187; +pub const YARVINSN_trace_opt_size: ruby_vminsn_type = 188; +pub const YARVINSN_trace_opt_empty_p: ruby_vminsn_type = 189; +pub const YARVINSN_trace_opt_succ: ruby_vminsn_type = 190; +pub const YARVINSN_trace_opt_not: ruby_vminsn_type = 191; +pub const YARVINSN_trace_opt_regexpmatch2: ruby_vminsn_type = 192; +pub const YARVINSN_trace_invokebuiltin: ruby_vminsn_type = 193; +pub const YARVINSN_trace_opt_invokebuiltin_delegate: ruby_vminsn_type = 194; +pub const YARVINSN_trace_opt_invokebuiltin_delegate_leave: ruby_vminsn_type = 195; +pub const YARVINSN_trace_getlocal_WC_0: ruby_vminsn_type = 196; +pub const YARVINSN_trace_getlocal_WC_1: ruby_vminsn_type = 197; +pub const YARVINSN_trace_setlocal_WC_0: ruby_vminsn_type = 198; +pub const YARVINSN_trace_setlocal_WC_1: ruby_vminsn_type = 199; +pub const YARVINSN_trace_putobject_INT2FIX_0_: ruby_vminsn_type = 200; +pub const YARVINSN_trace_putobject_INT2FIX_1_: ruby_vminsn_type = 201; +pub const VM_INSTRUCTION_SIZE: ruby_vminsn_type = 202; pub type ruby_vminsn_type = u32; extern "C" { pub fn rb_vm_insn_addr2opcode(addr: *const ::std::os::raw::c_void) -> ::std::os::raw::c_int; diff --git a/yjit/src/invariants.rs b/yjit/src/invariants.rs index 9cdef0d8bb..4ed21118cc 100644 --- a/yjit/src/invariants.rs +++ b/yjit/src/invariants.rs @@ -12,7 +12,6 @@ use crate::yjit::yjit_enabled_p; use std::collections::{HashMap, HashSet}; use std::mem; -use std::os::raw::c_void; // Invariants to track: // assume_bop_not_redefined(jit, INTEGER_REDEFINED_OP_FLAG, BOP_PLUS) @@ -173,57 +172,41 @@ pub fn assume_single_ractor_mode(jit: &mut JITState, ocb: &mut OutlinedCb) -> bo /// subsequent opt_setinlinecache and find all of the name components that are /// associated with this constant (which correspond to the getconstant /// arguments). -pub fn assume_stable_constant_names(jit: &mut JITState, ocb: &mut OutlinedCb) { +pub fn assume_stable_constant_names(jit: &mut JITState, ocb: &mut OutlinedCb, idlist: *const ID) { /// Tracks that a block is assuming that the name component of a constant /// has not changed since the last call to this function. - unsafe extern "C" fn assume_stable_constant_name( - code: *mut VALUE, - insn: VALUE, - index: u64, - data: *mut c_void, - ) -> bool { - if insn.as_u32() == YARVINSN_opt_setinlinecache { - return false; + fn assume_stable_constant_name( + jit: &mut JITState, + id: ID, + ) { + if id == idNULL as u64 { + // Used for :: prefix + return; } - if insn.as_u32() == YARVINSN_getconstant { - let jit = &mut *(data as *mut JITState); + let invariants = Invariants::get_instance(); + invariants + .constant_state_blocks + .entry(id) + .or_default() + .insert(jit.get_block()); + invariants + .block_constant_states + .entry(jit.get_block()) + .or_default() + .insert(id); + } - // The first operand to GETCONSTANT is always the ID associated with - // the constant lookup. We are grabbing this out in order to - // associate this block with the stability of this constant name. - let id = code.add(index.as_usize() + 1).read().as_u64() as ID; - let invariants = Invariants::get_instance(); - invariants - .constant_state_blocks - .entry(id) - .or_default() - .insert(jit.get_block()); - invariants - .block_constant_states - .entry(jit.get_block()) - .or_default() - .insert(id); + for i in 0.. { + match unsafe { *idlist.offset(i) } { + 0 => break, // End of NULL terminated list + id => assume_stable_constant_name(jit, id), } - - true } jit_ensure_block_entry_exit(jit, ocb); - unsafe { - let iseq = jit.get_iseq(); - let encoded = get_iseq_body_iseq_encoded(iseq); - let start_index = jit.get_pc().offset_from(encoded); - - rb_iseq_each( - iseq, - start_index.try_into().unwrap(), - Some(assume_stable_constant_name), - jit as *mut _ as *mut c_void, - ); - }; } /// Called when a basic operator is redefined. Note that all the blocks assuming @@ -450,7 +433,7 @@ pub fn block_assumptions_free(blockref: &BlockRef) { /// Invalidate the block for the matching opt_getinlinecache so it could regenerate code /// using the new value in the constant cache. #[no_mangle] -pub extern "C" fn rb_yjit_constant_ic_update(iseq: *const rb_iseq_t, ic: IC) { +pub extern "C" fn rb_yjit_constant_ic_update(iseq: *const rb_iseq_t, ic: IC, insn_idx: u32) { // If YJIT isn't enabled, do nothing if !yjit_enabled_p() { return; @@ -464,34 +447,33 @@ pub extern "C" fn rb_yjit_constant_ic_update(iseq: *const rb_iseq_t, ic: IC) { with_vm_lock(src_loc!(), || { let code = unsafe { get_iseq_body_iseq_encoded(iseq) }; - let get_insn_idx = unsafe { (*ic).get_insn_idx }; // This should come from a running iseq, so direct threading translation // should have been done assert!(unsafe { FL_TEST(iseq.into(), VALUE(ISEQ_TRANSLATED as usize)) } != VALUE(0)); - assert!(get_insn_idx < unsafe { get_iseq_encoded_size(iseq) }); + assert!(insn_idx < unsafe { get_iseq_encoded_size(iseq) }); - // Ensure that the instruction the get_insn_idx is pointing to is in - // fact a opt_getinlinecache instruction. + // Ensure that the instruction the insn_idx is pointing to is in + // fact a opt_getconstant_path instruction. assert_eq!( unsafe { - let opcode_pc = code.add(get_insn_idx.as_usize()); + let opcode_pc = code.add(insn_idx.as_usize()); let translated_opcode: VALUE = opcode_pc.read(); rb_vm_insn_decode(translated_opcode) }, - YARVINSN_opt_getinlinecache.try_into().unwrap() + YARVINSN_opt_getconstant_path.try_into().unwrap() ); // Find the matching opt_getinlinecache and invalidate all the blocks there // RUBY_ASSERT(insn_op_type(BIN(opt_getinlinecache), 1) == TS_IC); - let ic_pc = unsafe { code.add(get_insn_idx.as_usize() + 2) }; + let ic_pc = unsafe { code.add(insn_idx.as_usize() + 1) }; let ic_operand: IC = unsafe { ic_pc.read() }.as_mut_ptr(); if ic == ic_operand { for block in take_version_list(BlockId { iseq, - idx: get_insn_idx, + idx: insn_idx, }) { invalidate_block_version(&block); incr_counter!(invalidate_constant_ic_fill); |