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.
This commit is contained in:
John Hawthorn 2022-08-10 10:35:48 -07:00
parent 7064d259bc
commit 679ef34586
Notes: git 2022-09-02 07:21:29 +09:00
16 changed files with 482 additions and 450 deletions

View file

@ -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