Fix cache incoherency for ME resolved through VM_METHOD_TYPE_REFINED

Previously, we didn't invalidate the method entry wrapped by
VM_METHOD_TYPE_REFINED method entries which could cause calls to
land in the wrong method like it did in the included test.

Do the invalidation, and adjust rb_method_entry_clone() to accommodate
this new invalidation vector.

Fix: cfd7729ce7
See-also: e201b81f79
This commit is contained in:
Alan Wu 2023-11-27 19:19:41 -05:00
Родитель 0704f40787
Коммит cd4207869f
3 изменённых файлов: 55 добавлений и 0 удалений

Просмотреть файл

@ -2650,6 +2650,28 @@ class TestRefinement < Test::Unit::TestCase
end;
end
def test_inline_cache_invalidation
klass = Class.new do
def cached_foo_callsite = foo
def foo = :v1
host = self
@refinement = Module.new do
refine(host) do
def foo = :unused
end
end
end
obj = klass.new
obj.cached_foo_callsite # prime cache
klass.class_eval do
def foo = :v2 # invalidate
end
assert_equal(:v2, obj.cached_foo_callsite)
end
private
def eval_using(mod, s)

Просмотреть файл

@ -4379,6 +4379,8 @@ vm_call_method_each_type(rb_execution_context_t *ec, rb_control_frame_t *cfp, st
const rb_callable_method_entry_t *cme = vm_cc_cme(cc);
VALUE v;
VM_ASSERT(! METHOD_ENTRY_INVALIDATED(cme));
switch (cme->def->type) {
case VM_METHOD_TYPE_ISEQ:
CC_SET_FASTPATH(cc, vm_call_iseq_setup, TRUE);

Просмотреть файл

@ -240,6 +240,13 @@ clear_method_cache_by_id_in_class(VALUE klass, ID mid)
vm_cme_invalidate((rb_callable_method_entry_t *)cme);
RB_DEBUG_COUNTER_INC(cc_invalidate_tree_cme);
// In case of refinement ME, also invalidate the wrapped ME that
// could be cached at some callsite and is unreachable from any
// RCLASS_CC_TBL.
if (cme->def->type == VM_METHOD_TYPE_REFINED && cme->def->body.refined.orig_me) {
vm_cme_invalidate((rb_callable_method_entry_t *)cme->def->body.refined.orig_me);
}
if (cme->def->iseq_overload) {
rb_callable_method_entry_t *monly_cme = (rb_callable_method_entry_t *)lookup_overloaded_cme(cme);
if (monly_cme) {
@ -676,12 +683,36 @@ rb_method_entry_create(ID called_id, VALUE klass, rb_method_visibility_t visi, r
return me;
}
// Return a cloned ME that's not invalidated (MEs are disposable for caching).
const rb_method_entry_t *
rb_method_entry_clone(const rb_method_entry_t *src_me)
{
rb_method_entry_t *me = rb_method_entry_alloc(src_me->called_id, src_me->owner, src_me->defined_class, src_me->def, METHOD_ENTRY_COMPLEMENTED(src_me));
METHOD_ENTRY_FLAGS_COPY(me, src_me);
// Also clone inner ME in case of refinement ME
if (src_me->def &&
src_me->def->type == VM_METHOD_TYPE_REFINED &&
src_me->def->body.refined.orig_me) {
const rb_method_entry_t *orig_me = src_me->def->body.refined.orig_me;
VM_ASSERT(orig_me->def->type != VM_METHOD_TYPE_REFINED);
rb_method_entry_t *orig_clone = rb_method_entry_alloc(orig_me->called_id,
orig_me->owner, orig_me->defined_class, orig_me->def, METHOD_ENTRY_COMPLEMENTED(orig_me));
METHOD_ENTRY_FLAGS_COPY(orig_clone, orig_me);
// Clone definition, since writing a VALUE to a shared definition
// can create reference edges we can't run WBs for.
rb_method_definition_t *clone_def =
rb_method_definition_create(VM_METHOD_TYPE_REFINED, src_me->called_id);
rb_method_refined_t refined = {
.owner = src_me->def->body.refined.owner,
.orig_me = orig_clone,
};
rb_method_definition_set(me, clone_def, &refined);
}
return me;
}