ruby-changes:30743
From: charliesome <ko1@a...>
Date: Wed, 4 Sep 2013 14:25:20 +0900 (JST)
Subject: [ruby-changes:30743] charliesome:r42822 (trunk): * class.c, compile.c, eval.c, gc.h, insns.def, internal.h, method.h,
charliesome 2013-09-04 14:25:06 +0900 (Wed, 04 Sep 2013) New Revision: 42822 http://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=rev&revision=42822 Log: * class.c, compile.c, eval.c, gc.h, insns.def, internal.h, method.h, variable.c, vm.c, vm_core.c, vm_insnhelper.c, vm_insnhelper.h, vm_method.c: Implement class hierarchy method cache invalidation. [ruby-core:55053] [Feature #8426] [GH-387] Modified files: trunk/ChangeLog trunk/class.c trunk/compile.c trunk/eval.c trunk/gc.c trunk/insns.def trunk/internal.h trunk/method.h trunk/variable.c trunk/vm.c trunk/vm_core.h trunk/vm_insnhelper.c trunk/vm_insnhelper.h trunk/vm_method.c Index: method.h =================================================================== --- method.h (revision 42821) +++ method.h (revision 42822) @@ -11,6 +11,8 @@ https://github.com/ruby/ruby/blob/trunk/method.h#L11 #ifndef METHOD_H #define METHOD_H +#include "internal.h" + #ifndef END_OF_ENUMERATION # ifdef __GNUC__ # define END_OF_ENUMERATION(key) @@ -120,7 +122,7 @@ rb_method_entry_t *rb_method_entry_with_ https://github.com/ruby/ruby/blob/trunk/method.h#L122 rb_method_entry_t *rb_method_entry_without_refinements(VALUE klass, ID id, VALUE *defined_class_ptr); -rb_method_entry_t *rb_method_entry_get_without_cache(VALUE klass, ID id, VALUE *define_class_ptr); +rb_method_entry_t *rb_method_entry_get_without_cache(VALUE klass, ID id, VALUE *define_class_ptr, method_cache_entry_t *ent); rb_method_entry_t *rb_method_entry_set(VALUE klass, ID mid, const rb_method_entry_t *, rb_method_flag_t noex); int rb_method_entry_arity(const rb_method_entry_t *me); Index: ChangeLog =================================================================== --- ChangeLog (revision 42821) +++ ChangeLog (revision 42822) @@ -1,3 +1,11 @@ https://github.com/ruby/ruby/blob/trunk/ChangeLog#L1 +Wed Sep 4 14:08:00 2013 Charlie Somerville <charliesome@r...> + + * class.c, compile.c, eval.c, gc.h, insns.def, internal.h, method.h, + variable.c, vm.c, vm_core.c, vm_insnhelper.c, vm_insnhelper.h, + vm_method.c: Implement class hierarchy method cache invalidation. + + [ruby-core:55053] [Feature #8426] [GH-387] + Wed Sep 4 11:13:40 2013 Nobuyoshi Nakada <nobu@r...> * string.c (str_gsub): use BEG(0) for whole matched position not Index: insns.def =================================================================== --- insns.def (revision 42821) +++ insns.def (revision 42822) @@ -218,7 +218,7 @@ setconstant https://github.com/ruby/ruby/blob/trunk/insns.def#L218 { 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 https://github.com/ruby/ruby/blob/trunk/insns.def#L975 class_iseq->local_size, 0, class_iseq->stack_max); RESTORE_REGS(); - INC_VM_STATE_VERSION(); + rb_clear_cache_by_class(klass); NEXT_INSN(); } Index: variable.c =================================================================== --- variable.c (revision 42821) +++ variable.c (revision 42822) @@ -1939,7 +1939,7 @@ rb_const_remove(VALUE mod, ID id) https://github.com/ruby/ruby/blob/trunk/variable.c#L1939 rb_class_name(mod), QUOTE_ID(id)); } - rb_vm_change_state(); + rb_clear_cache(); val = ((rb_const_entry_t*)v)->value; if (val == Qundef) { @@ -2149,7 +2149,8 @@ rb_const_set(VALUE klass, ID id, VALUE v https://github.com/ruby/ruby/blob/trunk/variable.c#L2149 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; /* autoload_i is shady */ return; } @@ -2172,7 +2173,8 @@ rb_const_set(VALUE klass, ID id, VALUE v https://github.com/ruby/ruby/blob/trunk/variable.c#L2173 } } - rb_vm_change_state(); + rb_clear_cache(); + ce = ALLOC(rb_const_entry_t); MEMZERO(ce, rb_const_entry_t, 1); @@ -2217,8 +2219,10 @@ set_const_visibility(VALUE mod, int argc https://github.com/ruby/ruby/blob/trunk/variable.c#L2219 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)); } @@ -2227,13 +2231,14 @@ set_const_visibility(VALUE mod, int argc https://github.com/ruby/ruby/blob/trunk/variable.c#L2231 ((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(); } /* Index: vm_core.h =================================================================== --- vm_core.h (revision 42821) +++ vm_core.h (revision 42822) @@ -127,7 +127,8 @@ typedef struct rb_compile_option_struct https://github.com/ruby/ruby/blob/trunk/vm_core.h#L127 struct iseq_inline_cache_entry { - VALUE ic_vmstat; + vm_state_version_t ic_vmstat; + vm_state_version_t ic_seq; VALUE ic_class; union { size_t index; @@ -157,7 +158,8 @@ typedef struct rb_call_info_struct { https://github.com/ruby/ruby/blob/trunk/vm_core.h#L158 rb_iseq_t *blockiseq; /* inline cache: keys */ - VALUE vmstat; + vm_state_version_t vmstat; + vm_state_version_t seq; VALUE klass; /* inline cache: values */ Index: compile.c =================================================================== --- compile.c (revision 42821) +++ compile.c (revision 42822) @@ -962,6 +962,7 @@ new_callinfo(rb_iseq_t *iseq, ID mid, in https://github.com/ruby/ruby/blob/trunk/compile.c#L962 } } ci->vmstat = 0; + ci->seq = 0; ci->blockptr = 0; ci->recv = Qundef; ci->call = 0; /* TODO: should set default function? */ Index: vm_method.c =================================================================== --- vm_method.c (revision 42821) +++ vm_method.c (revision 42822) @@ -2,9 +2,7 @@ https://github.com/ruby/ruby/blob/trunk/vm_method.c#L2 * This file is included by vm.c */ -#define CACHE_SIZE 0x800 -#define CACHE_MASK 0x7ff -#define EXPR1(c,m) ((((c)>>3)^(m))&CACHE_MASK) +#include "method.h" #define NOEX_NOREDEF 0 #ifndef NOEX_NOREDEF @@ -22,53 +20,32 @@ static void rb_vm_check_redefinition_opt https://github.com/ruby/ruby/blob/trunk/vm_method.c#L20 #define singleton_undefined idSingleton_method_undefined #define attached id__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; - VALUE defined_class; -}; - -static struct cache_entry cache[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 @@ -310,7 +287,7 @@ rb_method_entry_make(VALUE klass, ID mid https://github.com/ruby/ruby/blob/trunk/vm_method.c#L287 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; @@ -472,6 +449,7 @@ rb_add_method(VALUE klass, ID mid, rb_me https://github.com/ruby/ruby/blob/trunk/vm_method.c#L449 if (type != VM_METHOD_TYPE_UNDEF && type != VM_METHOD_TYPE_REFINED) { method_added(klass, mid); } + rb_clear_cache_by_class(klass); return me; } @@ -540,26 +518,26 @@ rb_method_entry_at(VALUE klass, ID id) https://github.com/ruby/ruby/blob/trunk/vm_method.c#L518 */ rb_method_entry_t * rb_method_entry_get_without_cache(VALUE klass, ID id, - VALUE *defined_class_ptr) + VALUE *defined_class_ptr, + method_cache_entry_t *ent) { VALUE defined_class; rb_method_entry_t *me = search_method(klass, id, &defined_class); if (ruby_running) { - struct cache_entry *ent; - ent = cache + EXPR1(klass, id); - ent->filled_version = GET_VM_STATE_VERSION(); - ent->klass = klass; - ent->defined_class = defined_class; + ent->seq = RCLASS_EXT(klass)->seq; + ent->vm_state = GET_VM_STATE_VERSION(); if (UNDEFINED_METHOD_ENTRY_P(me)) { ent->mid = id; ent->me = 0; + ent->defined_class = defined_class; me = 0; } else { ent->mid = id; ent->me = me; + ent->defined_class = defined_class; } } @@ -568,22 +546,52 @@ rb_method_entry_get_without_cache(VALUE https://github.com/ruby/ruby/blob/trunk/vm_method.c#L546 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; + method_cache_entry_t ent; + rb_method_entry_t *actual_me = + rb_method_entry_get_without_cache(klass, id, &actual_defined_class, &ent); + + 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; + method_cache_entry_t *ent; + + if (RCLASS_EXT(klass)->mc_tbl == NULL) { + RCLASS_EXT(klass)->mc_tbl = st_init_numtable(); + } - ent = cache + EXPR1(klass, id); - if (ent->filled_version == GET_VM_STATE_VERSION() && - ent->mid == id && ent->klass == klass) { + if (!st_lookup(RCLASS_EXT(klass)->mc_tbl, (st_index_t)id, (st_data_t *)&ent)) { + ent = calloc(1, sizeof(*ent)); + st_insert(RCLASS_EXT(klass)->mc_tbl, (st_index_t)id, (st_data_t)ent); + } + + if (ent->seq == RCLASS_EXT(klass)->seq && + ent->vm_state == GET_VM_STATE_VERSION() && + 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; } +#else + method_cache_entry_t ent_; + method_cache_entry_t* ent = &ent_; #endif - return rb_method_entry_get_without_cache(klass, id, defined_class_ptr); + return rb_method_entry_get_without_cache(klass, id, defined_class_ptr, ent); } static rb_method_entry_t * @@ -687,7 +695,7 @@ remove_method(VALUE klass, ID mid) https://github.com/ruby/ruby/blob/trunk/vm_method.c#L695 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); @@ -1220,6 +1228,7 @@ rb_alias(VALUE klass, ID name, ID def) https://github.com/ruby/ruby/blob/trunk/vm_method.c#L1228 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); } /* Index: eval.c =================================================================== --- eval.c (revision 42821) +++ eval.c (revision 42822) @@ -1262,7 +1262,7 @@ mod_using(VALUE self, VALUE module) https://github.com/ruby/ruby/blob/trunk/eval.c#L1262 } Check_Type(module, T_MODULE); rb_using_module(cref, module); - rb_clear_cache(); + rb_clear_cache_by_class(rb_cObject); return self; } @@ -1398,7 +1398,7 @@ top_using(VALUE self, VALUE module) https://github.com/ruby/ruby/blob/trunk/eval.c#L1398 } Check_Type(module, T_MODULE); rb_using_module(cref, module); - rb_clear_cache(); + rb_clear_cache_by_class(rb_cObject); return self; } Index: gc.c =================================================================== --- gc.c (revision 42821) +++ gc.c (revision 42822) @@ -1122,6 +1122,20 @@ rb_free_m_table(st_table *tbl) https://github.com/ruby/ruby/blob/trunk/gc.c#L1122 } static int +free_method_cache_entry_i(ID key, method_cache_entry_t *entry, st_data_t data) +{ + free(entry); + return ST_CONTINUE; +} + +void +rb_free_mc_table(st_table *tbl) +{ + st_foreach(tbl, free_method_cache_entry_i, 0); + st_free_table(tbl); +} + +static int free_const_entry_i(ID key, rb_const_entry_t *ce, st_data_t data) { xfree(ce); @@ -1226,7 +1240,6 @@ obj_free(rb_objspace_t *objspace, VALUE https://github.com/ruby/ruby/blob/trunk/gc.c#L1240 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)); } @@ -1239,7 +1252,23 @@ obj_free(rb_objspace_t *objspace, VALUE https://github.com/ruby/ruby/blob/trunk/gc.c#L1252 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; + } + if (RCLASS_EXT(obj)->mc_tbl) { + rb_free_mc_table(RCLASS_EXT(obj)->mc_tbl); + RCLASS_EXT(obj)->mc_tbl = 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); @@ -1291,7 +1320,14 @@ obj_free(rb_objspace_t *objspace, VALUE https://github.com/ruby/ruby/blob/trunk/gc.c#L1320 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: Index: class.c =================================================================== --- class.c (revision 42821) +++ class.c (revision 42822) @@ -34,6 +34,109 @@ https://github.com/ruby/ruby/blob/trunk/class.c#L34 extern st_table *rb_class_tbl; #define id_attached 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. * @@ -57,6 +160,13 @@ class_alloc(VALUE flags, VALUE klass) https://github.com/ruby/ruby/blob/trunk/class.c#L160 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_EXT(obj)->mc_tbl = NULL; + RCLASS_REFINED_CLASS(obj) = Qnil; RCLASS_EXT(obj)->allocator = 0; return (VALUE)obj; @@ -723,7 +833,6 @@ rb_include_module(VALUE klass, VALUE mod https://github.com/ruby/ruby/blob/trunk/class.c#L833 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 @@ -736,8 +845,8 @@ add_refined_method_entry_i(st_data_t key https://github.com/ruby/ruby/blob/trunk/class.c#L845 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) { @@ -763,7 +872,15 @@ include_modules_at(const VALUE klass, VA https://github.com/ruby/ruby/blob/trunk/class.c#L872 break; } } - c = RCLASS_SET_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); @@ -773,14 +890,17 @@ include_modules_at(const VALUE klass, VA https://github.com/ruby/ruby/blob/trunk/class.c#L890 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 @@ -839,7 +959,6 @@ rb_prepend_module(VALUE klass, VALUE mod https://github.com/ruby/ruby/blob/trunk/class.c#L959 if (changed < 0) rb_raise(rb_eArgError, "cyclic prepend detected"); if (changed) { - rb_clear_cache(); rb_vm_c (... truncated) -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/