diff options
author | John Hawthorn <[email protected]> | 2022-08-10 10:35:48 -0700 |
---|---|---|
committer | John Hawthorn <[email protected]> | 2022-09-01 15:20:49 -0700 |
commit | 679ef34586e7a43151865cb7f33a3253d815f7cf (patch) | |
tree | 1f46e901c2c77438e050585e9e9708492cc985a6 /vm_insnhelper.c | |
parent | 7064d259bc20050d467874e5622082c29529a2d3 (diff) |
New constant caching insn: opt_getconstant_path
Previously YARV bytecode implemented constant caching by having a pair
of instructions, opt_getinlinecache and opt_setinlinecache, wrapping a
series of getconstant calls (with putobject providing supporting
arguments).
This commit replaces that pattern with a new instruction,
opt_getconstant_path, handling both getting/setting the inline cache and
fetching the constant on a cache miss.
This is implemented by storing the full constant path as a
null-terminated array of IDs inside of the IC structure. idNULL is used
to signal an absolute constant reference.
$ ./miniruby --dump=insns -e '::Foo::Bar::Baz'
== disasm: #<ISeq:<main>@-e:1 (1,0)-(1,13)> (catch: FALSE)
0000 opt_getconstant_path <ic:0 ::Foo::Bar::Baz> ( 1)[Li]
0002 leave
The motivation for this is that we had increasingly found the need to
disassemble the instructions between the opt_getinlinecache and
opt_setinlinecache in order to determine the constant we are fetching,
or otherwise store metadata.
This disassembly was done:
* In opt_setinlinecache, to register the IC against the constant names
it is using for granular invalidation.
* In rb_iseq_free, to unregister the IC from the invalidation table.
* In YJIT to find the position of a opt_getinlinecache instruction to
invalidate it when the cache is populated
* In YJIT to register the constant names being used for invalidation.
With this change we no longe need disassemly for these (in fact
rb_iseq_each is now unused), as the list of constant names being
referenced is held in the IC. This should also make it possible to make
more optimizations in the future.
This may also reduce the size of iseqs, as previously each segment
required 32 bytes (on 64-bit platforms) for each constant segment. This
implementation only stores one ID per-segment.
There should be no significant performance change between this and the
previous implementation. Previously opt_getinlinecache was a "leaf"
instruction, but it included a jump (almost always to a separate cache
line). Now opt_getconstant_path is a non-leaf (it may
raise/autoload/call const_missing) but it does not jump. These seem to
even out.
Notes
Notes:
Merged: https://github.com/ruby/ruby/pull/6187
Diffstat (limited to 'vm_insnhelper.c')
-rw-r--r-- | vm_insnhelper.c | 82 |
1 files changed, 43 insertions, 39 deletions
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 } |