diff --git a/class.c b/class.c index 25e732ac2ed39f..ccc82726efa14e 100644 --- a/class.c +++ b/class.c @@ -34,6 +34,109 @@ extern st_table *rb_class_tbl; static ID id_attached; +void +rb_class_subclass_add(VALUE super, VALUE klass) +{ + rb_subclass_entry_t *entry, *head; + + if (super && super != Qundef) { + entry = malloc(sizeof(*entry)); + entry->klass = klass; + entry->next = NULL; + + head = RCLASS_EXT(super)->subclasses; + if (head) { + entry->next = head; + RCLASS_EXT(head->klass)->parent_subclasses = &entry->next; + } + + RCLASS_EXT(super)->subclasses = entry; + RCLASS_EXT(klass)->parent_subclasses = &RCLASS_EXT(super)->subclasses; + } +} + +static void +rb_module_add_to_subclasses_list(VALUE module, VALUE iclass) +{ + rb_subclass_entry_t *entry, *head; + + entry = malloc(sizeof(*entry)); + entry->klass = iclass; + entry->next = NULL; + + head = RCLASS_EXT(module)->subclasses; + if (head) { + entry->next = head; + RCLASS_EXT(head->klass)->module_subclasses = &entry->next; + } + + RCLASS_EXT(module)->subclasses = entry; + RCLASS_EXT(iclass)->module_subclasses = &RCLASS_EXT(module)->subclasses; +} + +void +rb_class_remove_from_super_subclasses(VALUE klass) +{ + rb_subclass_entry_t *entry; + + if (RCLASS_EXT(klass)->parent_subclasses) { + entry = *RCLASS_EXT(klass)->parent_subclasses; + + *RCLASS_EXT(klass)->parent_subclasses = entry->next; + if (entry->next) { + RCLASS_EXT(entry->next->klass)->parent_subclasses = RCLASS_EXT(klass)->parent_subclasses; + } + free(entry); + } + + RCLASS_EXT(klass)->parent_subclasses = NULL; +} + +void +rb_class_remove_from_module_subclasses(VALUE klass) +{ + rb_subclass_entry_t *entry; + + if (RCLASS_EXT(klass)->module_subclasses) { + entry = *RCLASS_EXT(klass)->module_subclasses; + *RCLASS_EXT(klass)->module_subclasses = entry->next; + + if (entry->next) { + RCLASS_EXT(entry->next->klass)->module_subclasses = RCLASS_EXT(klass)->module_subclasses; + } + + free(entry); + } + + RCLASS_EXT(klass)->module_subclasses = NULL; +} + +void +rb_class_foreach_subclass(VALUE klass, void(*f)(VALUE)) +{ + rb_subclass_entry_t *cur = RCLASS_EXT(klass)->subclasses; + + /* do not be tempted to simplify this loop into a for loop, the order of + operations is important here if `f` modifies the linked list */ + while (cur) { + VALUE curklass = cur->klass; + cur = cur->next; + f(curklass); + } +} + +void +rb_class_detach_subclasses(VALUE klass) +{ + rb_class_foreach_subclass(klass, rb_class_remove_from_super_subclasses); +} + +void +rb_class_detach_module_subclasses(VALUE klass) +{ + rb_class_foreach_subclass(klass, rb_class_remove_from_module_subclasses); +} + /** * Allocates a struct RClass for a new class. * @@ -54,9 +157,15 @@ class_alloc(VALUE flags, VALUE klass) RCLASS_IV_TBL(obj) = 0; RCLASS_CONST_TBL(obj) = 0; RCLASS_M_TBL(obj) = 0; - RCLASS_SUPER(obj) = 0; + RCLASS_SET_SUPER((VALUE)obj, 0); RCLASS_ORIGIN(obj) = (VALUE)obj; RCLASS_IV_INDEX_TBL(obj) = 0; + + RCLASS_EXT(obj)->subclasses = NULL; + RCLASS_EXT(obj)->parent_subclasses = NULL; + RCLASS_EXT(obj)->module_subclasses = NULL; + RCLASS_EXT(obj)->seq = rb_next_class_sequence(); + RCLASS_REFINED_CLASS(obj) = Qnil; RCLASS_EXT(obj)->allocator = 0; return (VALUE)obj; @@ -77,7 +186,7 @@ rb_class_boot(VALUE super) { VALUE klass = class_alloc(T_CLASS, rb_cClass); - RCLASS_SUPER(klass) = super; + RCLASS_SET_SUPER(klass, super); RCLASS_M_TBL(klass) = st_init_numtable(); OBJ_INFECT(klass, super); @@ -203,7 +312,7 @@ rb_mod_init_copy(VALUE clone, VALUE orig) RBASIC(clone)->klass = rb_singleton_class_clone(orig); rb_singleton_class_attached(RBASIC(clone)->klass, (VALUE)clone); } - RCLASS_SUPER(clone) = RCLASS_SUPER(orig); + RCLASS_SET_SUPER(clone, RCLASS_SUPER(orig)); RCLASS_EXT(clone)->allocator = RCLASS_EXT(orig)->allocator; if (RCLASS_IV_TBL(orig)) { st_data_t id; @@ -261,7 +370,7 @@ rb_singleton_class_clone_and_attach(VALUE obj, VALUE attach) RBASIC(clone)->klass = rb_singleton_class_clone(klass); } - RCLASS_SUPER(clone) = RCLASS_SUPER(klass); + RCLASS_SET_SUPER(clone, RCLASS_SUPER(klass)); RCLASS_EXT(clone)->allocator = RCLASS_EXT(klass)->allocator; if (RCLASS_IV_TBL(klass)) { RCLASS_IV_TBL(clone) = st_copy(RCLASS_IV_TBL(klass)); @@ -356,7 +465,7 @@ make_metaclass(VALUE klass) super = RCLASS_SUPER(klass); while (RB_TYPE_P(super, T_ICLASS)) super = RCLASS_SUPER(super); - RCLASS_SUPER(metaclass) = super ? ENSURE_EIGENCLASS(super) : rb_cClass; + RCLASS_SET_SUPER(metaclass, super ? ENSURE_EIGENCLASS(super) : rb_cClass); OBJ_INFECT(metaclass, RCLASS_SUPER(metaclass)); @@ -676,7 +785,7 @@ rb_include_class_new(VALUE module, VALUE super) RCLASS_IV_TBL(klass) = RCLASS_IV_TBL(module); RCLASS_CONST_TBL(klass) = RCLASS_CONST_TBL(module); RCLASS_M_TBL(klass) = RCLASS_M_TBL(RCLASS_ORIGIN(module)); - RCLASS_SUPER(klass) = super; + RCLASS_SET_SUPER(klass, super); if (RB_TYPE_P(module, T_ICLASS)) { RBASIC(klass)->klass = RBASIC(module)->klass; } @@ -710,7 +819,6 @@ rb_include_module(VALUE klass, VALUE module) changed = include_modules_at(klass, RCLASS_ORIGIN(klass), module); if (changed < 0) rb_raise(rb_eArgError, "cyclic include detected"); - if (changed) rb_clear_cache(); } static int @@ -723,8 +831,8 @@ add_refined_method_entry_i(st_data_t key, st_data_t value, st_data_t data) static int include_modules_at(const VALUE klass, VALUE c, VALUE module) { - VALUE p; - int changed = 0; + VALUE p, iclass; + int method_changed = 0, constant_changed = 0; const st_table *const klass_m_tbl = RCLASS_M_TBL(RCLASS_ORIGIN(klass)); while (module) { @@ -750,7 +858,15 @@ include_modules_at(const VALUE klass, VALUE c, VALUE module) break; } } - c = RCLASS_SUPER(c) = rb_include_class_new(module, RCLASS_SUPER(c)); + iclass = rb_include_class_new(module, RCLASS_SUPER(c)); + c = RCLASS_SET_SUPER(c, iclass); + + if (BUILTIN_TYPE(module) == T_ICLASS) { + rb_module_add_to_subclasses_list(RBASIC(module)->klass, iclass); + } else { + rb_module_add_to_subclasses_list(module, iclass); + } + if (FL_TEST(klass, RMODULE_IS_REFINEMENT)) { VALUE refined_class = rb_refinement_module_get_refined_class(klass); @@ -760,14 +876,17 @@ include_modules_at(const VALUE klass, VALUE c, VALUE module) FL_SET(c, RMODULE_INCLUDED_INTO_REFINEMENT); } if (RMODULE_M_TBL(module) && RMODULE_M_TBL(module)->num_entries) - changed = 1; + method_changed = 1; if (RMODULE_CONST_TBL(module) && RMODULE_CONST_TBL(module)->num_entries) - changed = 1; + constant_changed = 1; skip: module = RCLASS_SUPER(module); } - return changed; + if (method_changed) rb_clear_cache_by_class(klass); + if (constant_changed) rb_clear_cache(); + + return method_changed; } static int @@ -816,8 +935,8 @@ rb_prepend_module(VALUE klass, VALUE module) origin = RCLASS_ORIGIN(klass); if (origin == klass) { origin = class_alloc(T_ICLASS, klass); - RCLASS_SUPER(origin) = RCLASS_SUPER(klass); - RCLASS_SUPER(klass) = origin; + RCLASS_SET_SUPER(origin, RCLASS_SUPER(klass)); + RCLASS_SET_SUPER(klass, origin); RCLASS_ORIGIN(klass) = origin; RCLASS_M_TBL(origin) = RCLASS_M_TBL(klass); RCLASS_M_TBL(klass) = st_init_numtable(); @@ -828,7 +947,6 @@ rb_prepend_module(VALUE klass, VALUE module) if (changed < 0) rb_raise(rb_eArgError, "cyclic prepend detected"); if (changed) { - rb_clear_cache(); rb_vm_check_redefinition_by_prepend(klass); } } diff --git a/compile.c b/compile.c index 6c0dfff4c10c61..f2e112c2c4d804 100644 --- a/compile.c +++ b/compile.c @@ -959,6 +959,7 @@ new_callinfo(rb_iseq_t *iseq, ID mid, int argc, VALUE block, unsigned long flag) } } ci->vmstat = 0; + ci->seq = 0; ci->blockptr = 0; ci->recv = Qundef; ci->call = 0; /* TODO: should set default function? */ diff --git a/eval.c b/eval.c index 958daa8752d1d6..2952a4e69f1c38 100644 --- a/eval.c +++ b/eval.c @@ -1097,7 +1097,7 @@ rb_using_refinement(NODE *cref, VALUE klass, VALUE module) module = RCLASS_SUPER(module); while (module && module != klass) { FL_SET(module, RMODULE_IS_OVERLAID); - c = RCLASS_SUPER(c) = rb_include_class_new(module, RCLASS_SUPER(c)); + c = RCLASS_SET_SUPER(c, rb_include_class_new(module, RCLASS_SUPER(c))); RCLASS_REFINED_CLASS(c) = klass; module = RCLASS_SUPER(module); } @@ -1156,8 +1156,8 @@ add_activated_refinement(VALUE activated_refinements, refinement = RCLASS_SUPER(refinement); while (refinement) { FL_SET(refinement, RMODULE_IS_OVERLAID); - c = RCLASS_SUPER(c) = - rb_include_class_new(refinement, RCLASS_SUPER(c)); + c = RCLASS_SET_SUPER(c, + rb_include_class_new(refinement, RCLASS_SUPER(c))); RCLASS_REFINED_CLASS(c) = klass; refinement = RCLASS_SUPER(refinement); } @@ -1210,7 +1210,7 @@ rb_mod_refine(VALUE module, VALUE klass) refinement = rb_hash_lookup(refinements, klass); if (NIL_P(refinement)) { refinement = rb_module_new(); - RCLASS_SUPER(refinement) = klass; + RCLASS_SET_SUPER(refinement, klass); FL_SET(refinement, RMODULE_IS_REFINEMENT); CONST_ID(id_refined_class, "__refined_class__"); rb_ivar_set(refinement, id_refined_class, klass); @@ -1356,7 +1356,7 @@ top_using(VALUE self, VALUE module) } Check_Type(module, T_MODULE); rb_using_module(cref, module); - rb_clear_cache(); + rb_clear_cache_by_class(rb_cObject); return self; } diff --git a/gc.c b/gc.c index 2903beeb2fbb87..a6ebb09b00b9a4 100644 --- a/gc.c +++ b/gc.c @@ -930,7 +930,6 @@ obj_free(rb_objspace_t *objspace, VALUE obj) break; case T_MODULE: case T_CLASS: - rb_clear_cache_by_class((VALUE)obj); if (RCLASS_M_TBL(obj)) { rb_free_m_table(RCLASS_M_TBL(obj)); } @@ -943,7 +942,19 @@ obj_free(rb_objspace_t *objspace, VALUE obj) if (RCLASS_IV_INDEX_TBL(obj)) { st_free_table(RCLASS_IV_INDEX_TBL(obj)); } - xfree(RANY(obj)->as.klass.ptr); + if (RCLASS_EXT(obj)->subclasses) { + if (BUILTIN_TYPE(obj) == T_MODULE) { + rb_class_detach_module_subclasses(obj); + } else { + rb_class_detach_subclasses(obj); + } + RCLASS_EXT(obj)->subclasses = NULL; + } + rb_class_remove_from_module_subclasses(obj); + rb_class_remove_from_super_subclasses(obj); + if (RANY(obj)->as.klass.ptr) + xfree(RANY(obj)->as.klass.ptr); + RANY(obj)->as.klass.ptr = NULL; break; case T_STRING: rb_str_free(obj); @@ -995,7 +1006,14 @@ obj_free(rb_objspace_t *objspace, VALUE obj) break; case T_ICLASS: /* iClass shares table with the module */ + if (RCLASS_EXT(obj)->subclasses) { + rb_class_detach_subclasses(obj); + RCLASS_EXT(obj)->subclasses = NULL; + } + rb_class_remove_from_module_subclasses(obj); + rb_class_remove_from_super_subclasses(obj); xfree(RANY(obj)->as.klass.ptr); + RANY(obj)->as.klass.ptr = NULL; break; case T_FLOAT: @@ -2792,7 +2810,7 @@ gc_mark_children(rb_objspace_t *objspace, VALUE ptr) if (!RCLASS_EXT(obj)) break; mark_tbl(objspace, RCLASS_IV_TBL(obj)); mark_const_tbl(objspace, RCLASS_CONST_TBL(obj)); - ptr = RCLASS_SUPER(obj); + ptr = RCLASS_SUPER((VALUE)obj); goto again; case T_ARRAY: diff --git a/insns.def b/insns.def index 479d0daefe51c6..b88dd6567a990b 100644 --- a/insns.def +++ b/insns.def @@ -218,7 +218,7 @@ setconstant { vm_check_if_namespace(cbase); rb_const_set(cbase, id, val); - INC_VM_STATE_VERSION(); + rb_clear_cache_by_class(cbase); } /** @@ -975,7 +975,7 @@ defineclass class_iseq->local_size, 0); RESTORE_REGS(); - INC_VM_STATE_VERSION(); + rb_clear_cache_by_class(klass); NEXT_INSN(); } diff --git a/internal.h b/internal.h index 6c80427dce31cf..d59bb3602a6e92 100644 --- a/internal.h +++ b/internal.h @@ -32,18 +32,43 @@ struct rb_deprecated_classext_struct { char conflict[sizeof(VALUE) * 3]; }; +struct rb_subclass_entry; +typedef struct rb_subclass_entry rb_subclass_entry_t; + +struct rb_subclass_entry { + VALUE klass; + rb_subclass_entry_t *next; +}; + +#if HAVE_UINT64_T + typedef uint64_t vm_state_version_t; +#else + typedef unsigned long long vm_state_version_t; +#endif + struct rb_classext_struct { VALUE super; struct st_table *iv_tbl; struct st_table *const_tbl; + rb_subclass_entry_t *subclasses; + rb_subclass_entry_t **parent_subclasses; + /** + * In the case that this is an `ICLASS`, `module_subclasses` points to the link + * in the module's `subclasses` list that indicates that the klass has been + * included. Hopefully that makes sense. + */ + rb_subclass_entry_t **module_subclasses; + vm_state_version_t seq; VALUE origin; VALUE refined_class; rb_alloc_func_t allocator; }; -#undef RCLASS_SUPER +/* class.c */ +void rb_class_subclass_add(VALUE super, VALUE klass); +void rb_class_remove_from_super_subclasses(VALUE); + #define RCLASS_EXT(c) (RCLASS(c)->ptr) -#define RCLASS_SUPER(c) (RCLASS_EXT(c)->super) #define RCLASS_IV_TBL(c) (RCLASS_EXT(c)->iv_tbl) #define RCLASS_CONST_TBL(c) (RCLASS_EXT(c)->const_tbl) #define RCLASS_M_TBL(c) (RCLASS(c)->m_tbl) @@ -51,6 +76,25 @@ struct rb_classext_struct { #define RCLASS_ORIGIN(c) (RCLASS_EXT(c)->origin) #define RCLASS_REFINED_CLASS(c) (RCLASS_EXT(c)->refined_class) +#undef RCLASS_SUPER + +static inline VALUE +RCLASS_SUPER(VALUE klass) +{ + return RCLASS_EXT(klass)->super; +} + +static inline VALUE +RCLASS_SET_SUPER(VALUE klass, VALUE super) +{ + if (super) { + rb_class_remove_from_super_subclasses(klass); + rb_class_subclass_add(super, klass); + } + RCLASS_EXT(klass)->super = super; + return super; +} + struct vtm; /* defined by timev.h */ /* array.c */ @@ -66,6 +110,10 @@ VALUE rb_integer_float_cmp(VALUE x, VALUE y); VALUE rb_integer_float_eq(VALUE x, VALUE y); /* class.c */ +void rb_class_foreach_subclass(VALUE klass, void(*f)(VALUE)); +void rb_class_detach_subclasses(VALUE); +void rb_class_detach_module_subclasses(VALUE); +void rb_class_remove_from_module_subclasses(VALUE); VALUE rb_obj_methods(int argc, VALUE *argv, VALUE obj); VALUE rb_obj_protected_methods(int argc, VALUE *argv, VALUE obj); VALUE rb_obj_private_methods(int argc, VALUE *argv, VALUE obj); @@ -306,6 +354,9 @@ VALUE rb_mutex_owned_p(VALUE self); /* thread_pthread.c, thread_win32.c */ void Init_native_thread(void); +/* vm_insnhelper.h */ +vm_state_version_t rb_next_class_sequence(); + /* vm.c */ VALUE rb_obj_is_thread(VALUE obj); void rb_vm_mark(void *ptr); diff --git a/method.h b/method.h index b99674a913c83c..505d2412929a98 100644 --- a/method.h +++ b/method.h @@ -11,6 +11,8 @@ #ifndef METHOD_H #define METHOD_H +#include "internal.h" + #ifndef END_OF_ENUMERATION # ifdef __GNUC__ # define END_OF_ENUMERATION(key) diff --git a/object.c b/object.c index 30a3d936f64c98..54fe8c380148d6 100644 --- a/object.c +++ b/object.c @@ -1668,7 +1668,7 @@ rb_class_initialize(int argc, VALUE *argv, VALUE klass) rb_raise(rb_eTypeError, "can't inherit uninitialized class"); } } - RCLASS_SUPER(klass) = super; + RCLASS_SET_SUPER(klass, super); rb_make_metaclass(klass, RBASIC(super)->klass); rb_class_inherited(super, klass); rb_mod_initialize(klass); diff --git a/variable.c b/variable.c index 1be62dc1dbefdc..b2fd8e8ec4d152 100644 --- a/variable.c +++ b/variable.c @@ -1947,7 +1947,7 @@ rb_const_remove(VALUE mod, ID id) rb_class_name(mod), QUOTE_ID(id)); } - rb_vm_change_state(); + rb_clear_cache(); val = ((rb_const_entry_t*)v)->value; if (val == Qundef) { @@ -2159,7 +2159,7 @@ rb_const_set(VALUE klass, ID id, VALUE val) load = autoload_data(klass, id); /* for autoloading thread, keep the defined value to autoloading storage */ if (load && (ele = check_autoload_data(load)) && (ele->thread == rb_thread_current())) { - rb_vm_change_state(); + rb_clear_cache(); ele->value = val; return; } @@ -2182,7 +2182,8 @@ rb_const_set(VALUE klass, ID id, VALUE val) } } - rb_vm_change_state(); + rb_clear_cache(); + ce = ALLOC(rb_const_entry_t); ce->flag = (rb_const_flag_t)visibility; @@ -2234,8 +2235,10 @@ set_const_visibility(VALUE mod, int argc, VALUE *argv, rb_const_flag_t flag) VALUE val = argv[i]; id = rb_check_id(&val); if (!id) { - if (i > 0) - rb_clear_cache_by_class(mod); + if (i > 0) { + rb_clear_cache(); + } + rb_name_error_str(val, "constant %"PRIsVALUE"::%"PRIsVALUE" not defined", rb_class_name(mod), QUOTE(val)); } @@ -2244,13 +2247,14 @@ set_const_visibility(VALUE mod, int argc, VALUE *argv, rb_const_flag_t flag) ((rb_const_entry_t*)v)->flag = flag; } else { - if (i > 0) - rb_clear_cache_by_class(mod); + if (i > 0) { + rb_clear_cache(); + } rb_name_error(id, "constant %"PRIsVALUE"::%"PRIsVALUE" not defined", rb_class_name(mod), QUOTE_ID(id)); } } - rb_clear_cache_by_class(mod); + rb_clear_cache(); } /* diff --git a/vm.c b/vm.c index b6c40e7a7a306f..45801a6dcc2f04 100644 --- a/vm.c +++ b/vm.c @@ -73,6 +73,9 @@ static VALUE vm_invoke_proc(rb_thread_t *th, rb_proc_t *proc, VALUE self, VALUE defined_class, int argc, const VALUE *argv, const rb_block_t *blockptr); +static vm_state_version_t ruby_vm_global_state_version = 1; +static vm_state_version_t ruby_vm_sequence = 1; + #include "vm_insnhelper.h" #include "vm_insnhelper.c" #include "vm_exec.h" @@ -86,6 +89,12 @@ vm_invoke_proc(rb_thread_t *th, rb_proc_t *proc, VALUE self, VALUE defined_class #define BUFSIZE 0x100 #define PROCDEBUG 0 +vm_state_version_t +rb_next_class_sequence() +{ + return NEXT_CLASS_SEQUENCE(); +} + VALUE rb_cRubyVM; VALUE rb_cThread; VALUE rb_cEnv; @@ -99,14 +108,6 @@ rb_event_flag_t ruby_vm_event_flags; static void thread_free(void *ptr); -void -rb_vm_change_state(void) -{ - INC_VM_STATE_VERSION(); -} - -static void vm_clear_global_method_cache(void); - static void vm_clear_all_inline_method_cache(void) { @@ -119,7 +120,6 @@ vm_clear_all_inline_method_cache(void) static void vm_clear_all_cache() { - vm_clear_global_method_cache(); vm_clear_all_inline_method_cache(); ruby_vm_global_state_version = 1; } @@ -2035,11 +2035,13 @@ vm_define_method(rb_thread_t *th, VALUE obj, ID id, VALUE iseqval, miseq->klass = klass; miseq->defined_method_id = id; rb_add_method(klass, id, VM_METHOD_TYPE_ISEQ, miseq, noex); + rb_clear_cache_by_class(klass); if (!is_singleton && noex == NOEX_MODFUNC) { - rb_add_method(rb_singleton_class(klass), id, VM_METHOD_TYPE_ISEQ, miseq, NOEX_PUBLIC); + klass = rb_singleton_class(klass); + rb_add_method(klass, id, VM_METHOD_TYPE_ISEQ, miseq, NOEX_PUBLIC); + rb_clear_cache_by_class(klass); } - INC_VM_STATE_VERSION(); } #define REWIND_CFP(expr) do { \ @@ -2088,7 +2090,8 @@ m_core_undef_method(VALUE self, VALUE cbase, VALUE sym) { REWIND_CFP({ rb_undef(cbase, SYM2ID(sym)); - INC_VM_STATE_VERSION(); + rb_clear_cache_by_class(cbase); + rb_clear_cache_by_class(self); }); return Qnil; } diff --git a/vm_core.h b/vm_core.h index 0bffdd3daa0d0c..051e9e6aca0b31 100644 --- a/vm_core.h +++ b/vm_core.h @@ -130,7 +130,8 @@ struct iseq_compile_data_ensure_node_stack; typedef struct rb_compile_option_struct rb_compile_option_t; struct iseq_inline_cache_entry { - VALUE ic_vmstat; + vm_state_version_t ic_vmstat; + vm_state_version_t ic_seq; VALUE ic_class; union { VALUE value; @@ -151,7 +152,8 @@ typedef struct rb_call_info_struct { rb_iseq_t *blockiseq; /* inline cache: keys */ - VALUE vmstat; + vm_state_version_t vmstat; + vm_state_version_t seq; VALUE klass; /* inline cache: values */ diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 0a40a1e0dc23f2..8ef8fe5f10f3c1 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -504,7 +504,7 @@ vm_getivar(VALUE obj, ID id, IC ic, rb_call_info_t *ci, int is_attr) VALUE val = Qundef; VALUE klass = RBASIC(obj)->klass; - if (LIKELY((!is_attr && (ic->ic_class == klass && ic->ic_vmstat == GET_VM_STATE_VERSION())) || + if (LIKELY((!is_attr && ic->ic_seq == RCLASS_EXT(klass)->seq) || (is_attr && ci->aux.index > 0))) { long index = !is_attr ? ic->ic_value.index : ci->aux.index - 1; long len = ROBJECT_NUMIV(obj); @@ -526,9 +526,8 @@ vm_getivar(VALUE obj, ID id, IC ic, rb_call_info_t *ci, int is_attr) val = ptr[index]; } if (!is_attr) { - ic->ic_class = klass; ic->ic_value.index = index; - ic->ic_vmstat = GET_VM_STATE_VERSION(); + ic->ic_seq = RCLASS_EXT(klass)->seq; } else { /* call_info */ ci->aux.index = index + 1; @@ -564,7 +563,7 @@ vm_setivar(VALUE obj, ID id, VALUE val, IC ic, rb_call_info_t *ci, int is_attr) st_data_t index; if (LIKELY( - (!is_attr && ic->ic_class == klass && ic->ic_vmstat == GET_VM_STATE_VERSION()) || + (!is_attr && ic->ic_seq == RCLASS_EXT(klass)->seq) || (is_attr && ci->aux.index > 0))) { long index = !is_attr ? ic->ic_value.index : ci->aux.index-1; long len = ROBJECT_NUMIV(obj); @@ -580,9 +579,8 @@ vm_setivar(VALUE obj, ID id, VALUE val, IC ic, rb_call_info_t *ci, int is_attr) if (iv_index_tbl && st_lookup(iv_index_tbl, (st_data_t)id, &index)) { if (!is_attr) { - ic->ic_class = klass; ic->ic_value.index = index; - ic->ic_vmstat = GET_VM_STATE_VERSION(); + ic->ic_seq = RCLASS_EXT(klass)->seq; } else { ci->aux.index = index + 1; @@ -845,19 +843,18 @@ vm_search_method(rb_call_info_t *ci, VALUE recv) VALUE klass = CLASS_OF(recv); #if OPT_INLINE_METHOD_CACHE - if (LIKELY(GET_VM_STATE_VERSION() == ci->vmstat && klass == ci->klass)) { + if (LIKELY(GET_VM_STATE_VERSION() == ci->vmstat && RCLASS_EXT(klass)->seq == ci->seq)) { /* cache hit! */ + return; } - else { - ci->me = rb_method_entry(klass, ci->mid, &ci->defined_class); - ci->klass = klass; - ci->vmstat = GET_VM_STATE_VERSION(); - ci->call = vm_call_general; - } -#else +#endif + ci->me = rb_method_entry(klass, ci->mid, &ci->defined_class); - ci->call = vm_call_general; ci->klass = klass; + ci->call = vm_call_general; +#if OPT_INLINE_METHOD_CACHE + ci->vmstat = GET_VM_STATE_VERSION(); + ci->seq = RCLASS_EXT(klass)->seq; #endif } diff --git a/vm_insnhelper.h b/vm_insnhelper.h index 62d9781bc96531..2ae989b243d94d 100644 --- a/vm_insnhelper.h +++ b/vm_insnhelper.h @@ -258,8 +258,7 @@ enum vm_regan_acttype { CALL_METHOD(ci); \ } while (0) -static VALUE ruby_vm_global_state_version = 1; - +#define NEXT_CLASS_SEQUENCE() (++ruby_vm_sequence) #define GET_VM_STATE_VERSION() (ruby_vm_global_state_version) #define INC_VM_STATE_VERSION() do { \ ruby_vm_global_state_version = (ruby_vm_global_state_version + 1); \ diff --git a/vm_method.c b/vm_method.c index e00c282ca9a3a0..942f9ed719b254 100644 --- a/vm_method.c +++ b/vm_method.c @@ -2,9 +2,11 @@ * This file is included by vm.c */ -#define CACHE_SIZE 0x800 -#define CACHE_MASK 0x7ff -#define EXPR1(c,m) ((((c)>>3)^(m))&CACHE_MASK) +#define GLOBAL_METHOD_CACHE_SIZE 0x800 +#define GLOBAL_METHOD_CACHE_MASK 0x7ff +#define GLOBAL_METHOD_CACHE_KEY(c,m) ((((c)>>3)^(m))&GLOBAL_METHOD_CACHE_MASK) +#define GLOBAL_METHOD_CACHE(c,m) (global_method_cache + GLOBAL_METHOD_CACHE_KEY(c,m)) +#include "method.h" #define NOEX_NOREDEF 0 #ifndef NOEX_NOREDEF @@ -17,53 +19,41 @@ static ID object_id; static ID removed, singleton_removed, undefined, singleton_undefined; static ID added, singleton_added, attached; -struct cache_entry { /* method hash table. */ - VALUE filled_version; /* filled state version */ - ID mid; /* method's id */ - VALUE klass; /* receiver's class */ - rb_method_entry_t *me; +struct cache_entry { + vm_state_version_t vm_state; + vm_state_version_t seq; + ID mid; + rb_method_entry_t* me; VALUE defined_class; }; -static struct cache_entry cache[CACHE_SIZE]; +static struct cache_entry global_method_cache[GLOBAL_METHOD_CACHE_SIZE]; #define ruby_running (GET_VM()->running) /* int ruby_running = 0; */ static void -vm_clear_global_method_cache(void) +rb_class_clear_method_cache(VALUE klass) { - struct cache_entry *ent, *end; - - ent = cache; - end = ent + CACHE_SIZE; - while (ent < end) { - ent->filled_version = 0; - ent++; - } + RCLASS_EXT(klass)->seq = rb_next_class_sequence(); + rb_class_foreach_subclass(klass, rb_class_clear_method_cache); } void rb_clear_cache(void) { - rb_vm_change_state(); -} - -static void -rb_clear_cache_for_undef(VALUE klass, ID id) -{ - rb_vm_change_state(); -} - -static void -rb_clear_cache_by_id(ID id) -{ - rb_vm_change_state(); + INC_VM_STATE_VERSION(); } void rb_clear_cache_by_class(VALUE klass) { - rb_vm_change_state(); + if (klass && klass != Qundef) { + if (klass == rb_cBasicObject || klass == rb_cObject || klass == rb_mKernel) { + INC_VM_STATE_VERSION(); + } else { + rb_class_clear_method_cache(klass); + } + } } VALUE @@ -315,7 +305,7 @@ rb_method_entry_make(VALUE klass, ID mid, rb_method_type_t type, me = ALLOC(rb_method_entry_t); - rb_clear_cache_by_id(mid); + rb_clear_cache_by_class(klass); me->flag = NOEX_WITH_SAFE(noex); me->mark = 0; @@ -457,6 +447,7 @@ rb_add_method(VALUE klass, ID mid, rb_method_type_t type, void *opts, rb_method_ if (type != VM_METHOD_TYPE_UNDEF && type != VM_METHOD_TYPE_REFINED) { method_added(klass, mid); } + rb_clear_cache_by_class(klass); return me; } @@ -526,18 +517,17 @@ rb_method_entry_get_without_cache(VALUE klass, ID id, if (ruby_running) { struct cache_entry *ent; - ent = cache + EXPR1(klass, id); - ent->filled_version = GET_VM_STATE_VERSION(); - ent->klass = klass; + ent = GLOBAL_METHOD_CACHE(klass, id); + ent->seq = RCLASS_EXT(klass)->seq; + ent->vm_state = GET_VM_STATE_VERSION(); ent->defined_class = defined_class; + ent->mid = id; if (UNDEFINED_METHOD_ENTRY_P(me)) { - ent->mid = id; ent->me = 0; me = 0; } else { - ent->mid = id; ent->me = me; } } @@ -547,17 +537,34 @@ rb_method_entry_get_without_cache(VALUE klass, ID id, return me; } +#if VM_DEBUG_VERIFY_METHOD_CACHE +static void +verify_method_cache(VALUE klass, ID id, VALUE defined_class, rb_method_entry_t *me) +{ + VALUE actual_defined_class; + rb_method_entry_t *actual_me = + rb_method_entry_get_without_cache(klass, id, &actual_defined_class); + + if (me != actual_me || defined_class != actual_defined_class) { + rb_bug("method cache verification failed"); + } +} +#endif + rb_method_entry_t * rb_method_entry(VALUE klass, ID id, VALUE *defined_class_ptr) { #if OPT_GLOBAL_METHOD_CACHE struct cache_entry *ent; - - ent = cache + EXPR1(klass, id); - if (ent->filled_version == GET_VM_STATE_VERSION() && - ent->mid == id && ent->klass == klass) { + ent = GLOBAL_METHOD_CACHE(klass, id); + if (ent->vm_state == GET_VM_STATE_VERSION() && + ent->seq == RCLASS_EXT(klass)->seq && + ent->mid == id) { if (defined_class_ptr) *defined_class_ptr = ent->defined_class; +#if VM_DEBUG_VERIFY_METHOD_CACHE + verify_method_cache(klass, id, ent->defined_class, ent->me); +#endif return ent->me; } #endif @@ -672,7 +679,7 @@ remove_method(VALUE klass, ID mid) st_delete(RCLASS_M_TBL(klass), &key, &data); rb_vm_check_redefinition_opt_method(me, klass); - rb_clear_cache_for_undef(klass, mid); + rb_clear_cache_by_class(klass); rb_unlink_method_entry(me); CALL_METHOD_HOOK(self, removed, mid); @@ -1206,6 +1213,7 @@ rb_alias(VALUE klass, ID name, ID def) if (flag == NOEX_UNDEF) flag = orig_me->flag; rb_method_entry_set(target_klass, name, orig_me, flag); + rb_clear_cache_by_class(target_klass); } /*