diff --git a/ChangeLog b/ChangeLog index 7830b4cfd21d30..3dd8e9a9bd558d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,54 @@ +Thu Dec 6 18:23:05 2012 Shugo Maeda + + * revised r37993 to avoid SEGV/ILL in tests. In r37993, a method + entry with VM_METHOD_TYPE_REFINED holds only the original method + definition, so ci->me is set to a method entry allocated in the + stack, and it causes SEGV/ILL. In this commit, a method entry + with VM_METHOD_TYPE_REFINED holds the whole original method entry. + Furthermore, rb_thread_mark() is changed to mark cfp->klass to + avoid GC for iclasses created by copy_refinement_iclass(). + + * vm_method.c (rb_method_entry_make): add a method entry with + VM_METHOD_TYPE_REFINED to the class refined by the refinement if + the target module is a refinement. When a method entry with + VM_METHOD_TYPE_UNDEF is invoked by vm_call_method(), a method with + the same name is searched in refinements. If such a method is + found, the method is invoked. Otherwise, the original method in + the refined class (rb_method_definition_t::body.orig_me) is + invoked. This change is made to simplify the normal method lookup + and to improve the performance of normal method calls. + + * vm_method.c (EXPR1, search_method, rb_method_entry), + vm_eval.c (rb_call0, rb_search_method_entry): do not use + refinements for method lookup. + + * vm_insnhelper.c (vm_call_method): search methods in refinements if + ci->me is VM_METHOD_TYPE_REFINED. If the method is called by + super (i.e., ci->call == vm_call_super_method), skip the same + method entry as the current method to avoid infinite call of the + same method. + + * class.c (include_modules_at): add a refined method entry for each + method defined in a module included in a refinement. + + * class.c (rb_prepend_module): set an empty table to + RCLASS_M_TBL(klass) to add refined method entries, because + refinements should have priority over prepended modules. + + * proc.c (mnew): use rb_method_entry_with_refinements() to get + a refined method. + + * vm.c (rb_thread_mark): mark cfp->klass for iclasses created by + copy_refinement_iclass(). + + * vm.c (Init_VM), cont.c (fiber_init): initialize th->cfp->klass. + + * test/ruby/test_refinement.rb (test_inline_method_cache): do not skip + the test because it should pass successfully. + + * test/ruby/test_refinement.rb (test_redefine_refined_method): new + test for the case a refined method is redefined. + Thu Dec 6 17:29:03 2012 Nobuyoshi Nakada * parse.y (parser_here_document): flush string content between new diff --git a/class.c b/class.c index e083bf633dfde0..0aa7561aa637c8 100644 --- a/class.c +++ b/class.c @@ -677,6 +677,13 @@ rb_include_module(VALUE klass, VALUE module) if (changed) rb_clear_cache(); } +static int +add_refined_method_entry_i(st_data_t key, st_data_t value, st_data_t data) +{ + rb_add_refined_method_entry((VALUE) data, (ID) key); + return ST_CONTINUE; +} + static int include_modules_at(VALUE klass, VALUE c, VALUE module) { @@ -707,6 +714,13 @@ include_modules_at(VALUE klass, VALUE c, VALUE module) } } c = RCLASS_SUPER(c) = rb_include_class_new(module, RCLASS_SUPER(c)); + if (FL_TEST(klass, RMODULE_IS_REFINEMENT)) { + VALUE refined_class = + rb_refinement_module_get_refined_class(klass); + + st_foreach(RMODULE_M_TBL(module), add_refined_method_entry_i, + (st_data_t) refined_class); + } if (RMODULE_M_TBL(module) && RMODULE_M_TBL(module)->num_entries) changed = 1; skip: @@ -738,7 +752,7 @@ rb_prepend_module(VALUE klass, VALUE module) RCLASS_SUPER(klass) = origin; RCLASS_ORIGIN(klass) = origin; RCLASS_M_TBL(origin) = RCLASS_M_TBL(klass); - RCLASS_M_TBL(klass) = 0; + RCLASS_M_TBL(klass) = st_init_numtable(); } changed = include_modules_at(klass, klass, module); if (changed < 0) diff --git a/cont.c b/cont.c index 084ea3edc63104..01800a60165eaa 100644 --- a/cont.c +++ b/cont.c @@ -1067,6 +1067,7 @@ fiber_init(VALUE fibval, VALUE proc) th->cfp->ep = th->stack; *th->cfp->ep = VM_ENVVAL_BLOCK_PTR(0); th->cfp->self = Qnil; + th->cfp->klass = Qnil; th->cfp->flag = 0; th->cfp->iseq = 0; th->cfp->proc = 0; diff --git a/eval.c b/eval.c index bcc461e3e46254..9d9821a20e678f 100644 --- a/eval.c +++ b/eval.c @@ -1187,6 +1187,14 @@ rb_mod_using(VALUE self, VALUE module) return self; } +VALUE rb_refinement_module_get_refined_class(VALUE module) +{ + ID id_refined_class; + + CONST_ID(id_refined_class, "__refined_class__"); + return rb_attr_get(module, id_refined_class); +} + static VALUE refinement_module_include(int argc, VALUE *argv, VALUE module) { @@ -1195,11 +1203,9 @@ refinement_module_include(int argc, VALUE *argv, VALUE module) rb_control_frame_t *end_cfp = RUBY_VM_END_CONTROL_FRAME(th); VALUE result = rb_mod_include(argc, argv, module); NODE *cref; - ID id_refined_class; VALUE klass, c; - CONST_ID(id_refined_class, "__refined_class__"); - klass = rb_attr_get(module, id_refined_class); + klass = rb_refinement_module_get_refined_class(module); while (RUBY_VM_VALID_CONTROL_FRAME_P(cfp, end_cfp)) { if (RUBY_VM_NORMAL_ISEQ_P(cfp->iseq) && (cref = rb_vm_get_cref(cfp->iseq, cfp->ep)) && diff --git a/gc.c b/gc.c index beaadddb543317..4c5e2b48a8e91e 100644 --- a/gc.c +++ b/gc.c @@ -2410,6 +2410,7 @@ mark_method_entry(rb_objspace_t *objspace, const rb_method_entry_t *me) const rb_method_definition_t *def = me->def; gc_mark(objspace, me->klass); + again: if (!def) return; switch (def->type) { case VM_METHOD_TYPE_ISEQ: @@ -2422,6 +2423,12 @@ mark_method_entry(rb_objspace_t *objspace, const rb_method_entry_t *me) case VM_METHOD_TYPE_IVAR: gc_mark(objspace, def->body.attr.location); break; + case VM_METHOD_TYPE_REFINED: + if (def->body.orig_me) { + def = def->body.orig_me->def; + goto again; + } + break; default: break; /* ignore */ } diff --git a/internal.h b/internal.h index 8165545813b90a..ced8aead828fd8 100644 --- a/internal.h +++ b/internal.h @@ -99,6 +99,9 @@ NORETURN(void rb_async_bug_errno(const char *,int)); const char *rb_builtin_type_name(int t); const char *rb_builtin_class_name(VALUE x); +/* eval.c */ +VALUE rb_refinement_module_get_refined_class(VALUE module); + /* eval_error.c */ void ruby_error_print(void); VALUE rb_get_backtrace(VALUE info); diff --git a/method.h b/method.h index a87fc5c1ba52be..b6d20916fb3de1 100644 --- a/method.h +++ b/method.h @@ -42,7 +42,8 @@ typedef enum { VM_METHOD_TYPE_NOTIMPLEMENTED, VM_METHOD_TYPE_OPTIMIZED, /* Kernel#send, Proc#call, etc */ VM_METHOD_TYPE_MISSING, /* wrapper for method_missing(id) */ - VM_METHOD_TYPE_CFUNC_FRAMELESS + VM_METHOD_TYPE_CFUNC_FRAMELESS, + VM_METHOD_TYPE_REFINED, } rb_method_type_t; struct rb_call_info_struct; @@ -72,6 +73,7 @@ typedef struct rb_method_definition_struct { OPTIMIZED_METHOD_TYPE_SEND, OPTIMIZED_METHOD_TYPE_CALL } optimize_type; + struct rb_method_entry_struct *orig_me; } body; int alias_count; } rb_method_definition_t; @@ -94,9 +96,14 @@ struct unlinked_method_entry_list_entry { void rb_add_method_cfunc(VALUE klass, ID mid, VALUE (*func)(ANYARGS), int argc, rb_method_flag_t noex); rb_method_entry_t *rb_add_method(VALUE klass, ID mid, rb_method_type_t type, void *option, rb_method_flag_t noex); rb_method_entry_t *rb_method_entry(VALUE klass, ID id, VALUE *define_class_ptr); - -rb_method_entry_t *rb_method_entry_get_with_refinements(VALUE refinements, VALUE klass, ID id, VALUE *define_class_ptr); -rb_method_entry_t *rb_method_entry_get_without_cache(VALUE klass, VALUE refinements, ID id, VALUE *define_class_ptr); +void rb_add_refined_method_entry(VALUE refined_class, ID mid); +rb_method_entry_t *rb_resolve_refined_method(VALUE refinements, + rb_method_entry_t *me, + VALUE *defined_class_ptr); +rb_method_entry_t *rb_method_entry_with_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_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); diff --git a/object.c b/object.c index 02bbdb086bda18..7fdc0e2d0276af 100644 --- a/object.c +++ b/object.c @@ -1338,7 +1338,7 @@ rb_obj_cmp(VALUE obj1, VALUE obj2) static VALUE rb_mod_to_s(VALUE klass) { - ID id_refined_class, id_defined_at; + ID id_defined_at; VALUE refined_class, defined_at; if (FL_TEST(klass, FL_SINGLETON)) { @@ -1358,8 +1358,7 @@ rb_mod_to_s(VALUE klass) return s; } - CONST_ID(id_refined_class, "__refined_class__"); - refined_class = rb_attr_get(klass, id_refined_class); + refined_class = rb_refinement_module_get_refined_class(klass); if (!NIL_P(refined_class)) { VALUE s = rb_usascii_str_new2("#type); diff --git a/test/ruby/test_refinement.rb b/test/ruby/test_refinement.rb index 3ca00ed9124707..380119fb9bc44b 100644 --- a/test/ruby/test_refinement.rb +++ b/test/ruby/test_refinement.rb @@ -379,10 +379,15 @@ def foo def test_refine_module_with_overriding m1 = Module.new { def foo - [:m1] + super << :m1 end } - c = Class.new { + c0 = Class.new { + def foo + [:c0] + end + } + c = Class.new(c0) { include m1 } m2 = Module.new { @@ -393,7 +398,7 @@ def foo end } obj = c.new - assert_equal([:m1, :m2], m2.module_eval { obj.foo }) + assert_equal([:c0, :m1, :m2], m2.module_eval { obj.foo }) end def test_refine_module_with_double_overriding @@ -726,7 +731,6 @@ def foo end def test_inline_method_cache - skip "can't implement efficiently with the current implementation of refinements" c = InlineMethodCache::C.new f = Proc.new { c.foo } assert_equal("original", f.call) @@ -822,4 +826,31 @@ def test_circular_using_is_not_allowed end end end + + module RedifineRefinedMethod + class C + def foo + "original" + end + end + + module M + refine C do + def foo + "refined" + end + end + end + + class C + def foo + "redefined" + end + end + end + + def test_redefine_refined_method + c = RedifineRefinedMethod::C.new + assert_equal("refined", RedifineRefinedMethod::M.module_eval { c.foo }) + end end diff --git a/vm.c b/vm.c index b59044d5e467c3..52f34c332f26f9 100644 --- a/vm.c +++ b/vm.c @@ -1686,6 +1686,7 @@ rb_thread_mark(void *ptr) rb_iseq_t *iseq = cfp->iseq; rb_gc_mark(cfp->proc); rb_gc_mark(cfp->self); + rb_gc_mark(cfp->klass); if (iseq) { rb_gc_mark(RUBY_VM_NORMAL_ISEQ_P(iseq) ? iseq->self : (VALUE)iseq); } @@ -2223,6 +2224,7 @@ Init_VM(void) th->cfp->iseq = iseq; th->cfp->pc = iseq->iseq_encoded; th->cfp->self = th->top_self; + th->cfp->klass = Qnil; /* * The Binding of the top level scope diff --git a/vm_eval.c b/vm_eval.c index 50b44b66ca60b0..ea2455c691cb83 100644 --- a/vm_eval.c +++ b/vm_eval.c @@ -179,7 +179,14 @@ vm_call0_body(rb_thread_t* th, rb_call_info_t *ci, const VALUE *argv) case VM_METHOD_TYPE_BMETHOD: return vm_call_bmethod_body(th, ci, argv); case VM_METHOD_TYPE_ZSUPER: + case VM_METHOD_TYPE_REFINED: { + if (ci->me->def->type == VM_METHOD_TYPE_REFINED && + ci->me->def->body.orig_me) { + ci->me = ci->me->def->body.orig_me; + goto again; + } + ci->defined_class = RCLASS_SUPER(ci->defined_class); if (!ci->defined_class || !(ci->me = rb_method_entry(ci->defined_class, ci->mid, &ci->defined_class))) { @@ -274,8 +281,7 @@ stack_check(void) } static inline rb_method_entry_t * - rb_search_method_entry(VALUE refinements, VALUE recv, ID mid, - VALUE *defined_class_ptr); + rb_search_method_entry(VALUE recv, ID mid, VALUE *defined_class_ptr); static inline int rb_method_call_status(rb_thread_t *th, const rb_method_entry_t *me, call_type scope, VALUE self); #define NOEX_OK NOEX_NOSUPER @@ -296,11 +302,11 @@ static inline int rb_method_call_status(rb_thread_t *th, const rb_method_entry_t */ static inline VALUE rb_call0(VALUE recv, ID mid, int argc, const VALUE *argv, - call_type scope, VALUE self, VALUE refinements) + call_type scope, VALUE self) { VALUE defined_class; rb_method_entry_t *me = - rb_search_method_entry(refinements, recv, mid, &defined_class); + rb_search_method_entry(recv, mid, &defined_class); rb_thread_t *th = GET_THREAD(); int call_status = rb_method_call_status(th, me, scope, self); @@ -364,7 +370,7 @@ check_funcall(VALUE recv, ID mid, int argc, VALUE *argv) } } - me = rb_search_method_entry(Qnil, recv, mid, &defined_class); + me = rb_search_method_entry(recv, mid, &defined_class); call_status = rb_method_call_status(th, me, CALL_FCALL, th->cfp->self); if (call_status != NOEX_OK) { if (rb_method_basic_definition_p(klass, idMethodMissing)) { @@ -429,8 +435,7 @@ rb_type_str(enum ruby_value_type type) } static inline rb_method_entry_t * -rb_search_method_entry(VALUE refinements, VALUE recv, ID mid, - VALUE *defined_class_ptr) +rb_search_method_entry(VALUE recv, ID mid, VALUE *defined_class_ptr) { VALUE klass = CLASS_OF(recv); @@ -469,8 +474,7 @@ rb_search_method_entry(VALUE refinements, VALUE recv, ID mid, rb_id2name(mid), type, (void *)recv, flags, klass); } } - return rb_method_entry_get_with_refinements(refinements, klass, mid, - defined_class_ptr); + return rb_method_entry(klass, mid, defined_class_ptr); } static inline int @@ -532,7 +536,7 @@ static inline VALUE rb_call(VALUE recv, ID mid, int argc, const VALUE *argv, call_type scope) { rb_thread_t *th = GET_THREAD(); - return rb_call0(recv, mid, argc, argv, scope, th->cfp->self, Qnil); + return rb_call0(recv, mid, argc, argv, scope, th->cfp->self); } NORETURN(static void raise_method_missing(rb_thread_t *th, int argc, const VALUE *argv, @@ -785,11 +789,23 @@ rb_funcall_passing_block_with_refinements(VALUE recv, ID mid, int argc, const VALUE *argv, VALUE refinements) { - rb_thread_t *th = GET_THREAD(); + VALUE defined_class; + rb_method_entry_t *me = + rb_search_method_entry(recv, mid, &defined_class); + rb_thread_t *th; + int call_status; - PASS_PASSED_BLOCK_TH(th); - return rb_call0(recv, mid, argc, argv, CALL_PUBLIC, th->cfp->self, - refinements); + if (me && me->def->type == VM_METHOD_TYPE_REFINED) { + me = rb_resolve_refined_method(refinements, me, &defined_class); + } + PASS_PASSED_BLOCK_TH(GET_THREAD()); + th = GET_THREAD(); + call_status = rb_method_call_status(th, me, CALL_PUBLIC, th->cfp->self); + if (call_status != NOEX_OK) { + return method_missing(recv, mid, argc, argv, call_status); + } + stack_check(); + return vm_call0(th, recv, mid, argc, argv, me, defined_class); } static VALUE @@ -823,7 +839,7 @@ send_internal(int argc, const VALUE *argv, VALUE recv, call_type scope) id = rb_to_id(vid); } PASS_PASSED_BLOCK_TH(th); - return rb_call0(recv, id, argc, argv, scope, self, Qnil); + return rb_call0(recv, id, argc, argv, scope, self); } /* diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 75da83e525153a..7d57b792b94461 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -1694,6 +1694,47 @@ vm_call_method_missing(rb_thread_t *th, rb_control_frame_t *reg_cfp, rb_call_inf return vm_call_method(th, reg_cfp, &ci_entry); } +static VALUE +copy_refinement_iclass(VALUE iclass, VALUE superclass) +{ + VALUE result, c; + + Check_Type(iclass, T_ICLASS); + c = result = rb_include_class_new(RBASIC(iclass)->klass, superclass); + RCLASS_REFINED_CLASS(c) = RCLASS_REFINED_CLASS(iclass); + iclass = RCLASS_SUPER(iclass); + while (iclass && BUILTIN_TYPE(iclass) == T_ICLASS) { + c = RCLASS_SUPER(c) = rb_include_class_new(RBASIC(iclass)->klass, + RCLASS_SUPER(c)); + RCLASS_REFINED_CLASS(c) = RCLASS_REFINED_CLASS(iclass); + iclass = RCLASS_SUPER(iclass); + } + return result; +} + +static VALUE +find_refinement(VALUE refinements, VALUE klass) +{ + VALUE refinement; + + if (NIL_P(refinements)) { + return Qnil; + } + refinement = rb_hash_lookup(refinements, klass); + if (NIL_P(refinement) && + BUILTIN_TYPE(klass) == T_ICLASS) { + refinement = rb_hash_lookup(refinements, + RBASIC(klass)->klass); + if (!NIL_P(refinement)) { + refinement = copy_refinement_iclass(refinement, klass); + } + } + return refinement; +} + +static int rb_method_definition_eq(const rb_method_definition_t *d1, const rb_method_definition_t *d2); +static VALUE vm_call_super_method(rb_thread_t *th, rb_control_frame_t *reg_cfp, rb_call_info_t *ci); + static inline VALUE vm_call_method(rb_thread_t *th, rb_control_frame_t *cfp, rb_call_info_t *ci) { @@ -1735,7 +1776,10 @@ vm_call_method(rb_thread_t *th, rb_control_frame_t *cfp, rb_call_info_t *ci) return vm_call_bmethod(th, cfp, ci); } case VM_METHOD_TYPE_ZSUPER:{ - VALUE klass = RCLASS_SUPER(ci->me->klass); + VALUE klass; + + zsuper_method_dispatch: + klass = RCLASS_SUPER(ci->me->klass); ci_temp = *ci; ci = &ci_temp; @@ -1778,6 +1822,42 @@ vm_call_method(rb_thread_t *th, rb_control_frame_t *cfp, rb_call_info_t *ci) break; case VM_METHOD_TYPE_UNDEF: break; + case VM_METHOD_TYPE_REFINED:{ + NODE *cref = rb_vm_get_cref(cfp->iseq, cfp->ep); + VALUE refinements = cref ? cref->nd_refinements : Qnil; + VALUE refinement, defined_class; + rb_method_entry_t *me; + ci_temp = *ci; + ci = &ci_temp; + + refinement = find_refinement(refinements, + ci->defined_class); + if (NIL_P(refinement)) { + goto no_refinement_dispatch; + } + me = rb_method_entry(refinement, ci->mid, &defined_class); + if (me) { + if (ci->call == vm_call_super_method && + cfp->me && + rb_method_definition_eq(me->def, cfp->me->def)) { + goto no_refinement_dispatch; + } + ci->me = me; + ci->defined_class = defined_class; + if (me->def->type != VM_METHOD_TYPE_REFINED) { + goto normal_method_dispatch; + } + } + + no_refinement_dispatch: + if (ci->me->def->body.orig_me) { + ci->me = ci->me->def->body.orig_me; + goto normal_method_dispatch; + } + else { + goto zsuper_method_dispatch; + } + } } rb_bug("vm_call_method: unsupported method type (%d)", ci->me->def->type); } @@ -1841,6 +1921,12 @@ vm_call_general(rb_thread_t *th, rb_control_frame_t *reg_cfp, rb_call_info_t *ci return vm_call_method(th, reg_cfp, ci); } +static VALUE +vm_call_super_method(rb_thread_t *th, rb_control_frame_t *reg_cfp, rb_call_info_t *ci) +{ + return vm_call_method(th, reg_cfp, ci); +} + /* super */ static inline VALUE @@ -1929,7 +2015,7 @@ vm_search_super_method(rb_thread_t *th, rb_control_frame_t *reg_cfp, rb_call_inf /* TODO: use inline cache */ ci->me = rb_method_entry(ci->klass, ci->mid, &ci->defined_class); - ci->call = vm_call_general; + ci->call = vm_call_super_method; while (iseq && !iseq->klass) { iseq = iseq->parent_iseq; @@ -1937,7 +2023,7 @@ vm_search_super_method(rb_thread_t *th, rb_control_frame_t *reg_cfp, rb_call_inf if (ci->me && ci->me->def->type == VM_METHOD_TYPE_ISEQ && ci->me->def->body.iseq == iseq) { ci->klass = RCLASS_SUPER(ci->defined_class); - ci->me = rb_method_entry_get_with_refinements(Qnil, ci->klass, ci->mid, &ci->defined_class); + ci->me = rb_method_entry(ci->klass, ci->mid, &ci->defined_class); } } diff --git a/vm_method.c b/vm_method.c index 4b4be139a7f03e..2db6000691810b 100644 --- a/vm_method.c +++ b/vm_method.c @@ -4,7 +4,7 @@ #define CACHE_SIZE 0x800 #define CACHE_MASK 0x7ff -#define EXPR1(c,o,m) ((((c)>>3)^((o)>>3)^(m))&CACHE_MASK) +#define EXPR1(c,m) ((((c)>>3)^(m))&CACHE_MASK) #define NOEX_NOREDEF 0 #ifndef NOEX_NOREDEF @@ -21,7 +21,6 @@ struct cache_entry { /* method hash table. */ VALUE filled_version; /* filled state version */ ID mid; /* method's id */ VALUE klass; /* receiver's class */ - VALUE refinements; /* refinements */ rb_method_entry_t *me; VALUE defined_class; }; @@ -148,26 +147,78 @@ rb_sweep_method_entry(void *pvm) } } -void -rb_free_method_entry(rb_method_entry_t *me) +static void +release_method_definition(rb_method_definition_t *def) { - rb_method_definition_t *def = me->def; - - if (def) { - if (def->alias_count == 0) { - xfree(def); - } - else if (def->alias_count > 0) { - def->alias_count--; + if (def == 0) + return; + if (def->alias_count == 0) { + if (def->type == VM_METHOD_TYPE_REFINED && + def->body.orig_me) { + release_method_definition(def->body.orig_me->def); + xfree(def->body.orig_me); } - me->def = 0; + xfree(def); } + else if (def->alias_count > 0) { + def->alias_count--; + } +} + +void +rb_free_method_entry(rb_method_entry_t *me) +{ + release_method_definition(me->def); xfree(me); } static int rb_method_definition_eq(const rb_method_definition_t *d1, const rb_method_definition_t *d2); -void rb_redefine_opt_method(VALUE, ID); +static inline rb_method_entry_t * +lookup_method_table(VALUE klass, ID id) +{ + st_data_t body; + st_table *m_tbl = RCLASS_M_TBL(klass); + if (st_lookup(m_tbl, id, &body)) { + return (rb_method_entry_t *) body; + } + else { + return 0; + } +} + +static void +make_method_entry_refined(rb_method_entry_t *me) +{ + rb_method_definition_t *new_def; + + if (me->def && me->def->type == VM_METHOD_TYPE_REFINED) + return; + + new_def = ALLOC(rb_method_definition_t); + new_def->type = VM_METHOD_TYPE_REFINED; + new_def->original_id = me->called_id; + new_def->alias_count = 0; + new_def->body.orig_me = ALLOC(rb_method_entry_t); + *new_def->body.orig_me = *me; + rb_vm_check_redefinition_opt_method(me, me->klass); + if (me->def) me->def->alias_count++; + me->def = new_def; +} + +void +rb_add_refined_method_entry(VALUE refined_class, ID mid) +{ + rb_method_entry_t *me = lookup_method_table(refined_class, mid); + + if (me) { + make_method_entry_refined(me); + } + else { + rb_add_method(refined_class, mid, VM_METHOD_TYPE_REFINED, 0, + NOEX_PUBLIC); + } +} static rb_method_entry_t * rb_method_entry_make(VALUE klass, ID mid, rb_method_type_t type, @@ -179,6 +230,7 @@ rb_method_entry_make(VALUE klass, ID mid, rb_method_type_t type, #endif st_table *mtbl; st_data_t data; + int make_refined = 0; if (NIL_P(klass)) { klass = rb_cObject; @@ -201,14 +253,19 @@ rb_method_entry_make(VALUE klass, ID mid, rb_method_type_t type, rklass = klass; #endif if (FL_TEST(klass, RMODULE_IS_REFINEMENT)) { - ID id_refined_class; - VALUE refined_class; + VALUE refined_class = + rb_refinement_module_get_refined_class(klass); - CONST_ID(id_refined_class, "__refined_class__"); - refined_class = rb_ivar_get(klass, id_refined_class); - rb_redefine_opt_method(refined_class, mid); + rb_add_refined_method_entry(refined_class, mid); + } + if (type == VM_METHOD_TYPE_REFINED) { + rb_method_entry_t *old_me = + lookup_method_table(RCLASS_ORIGIN(klass), mid); + if (old_me) rb_vm_check_redefinition_opt_method(old_me, klass); + } + else { + klass = RCLASS_ORIGIN(klass); } - klass = RCLASS_ORIGIN(klass); mtbl = RCLASS_M_TBL(klass); /* check re-definition */ @@ -224,6 +281,8 @@ rb_method_entry_make(VALUE klass, ID mid, rb_method_type_t type, } #endif rb_vm_check_redefinition_opt_method(old_me, klass); + if (old_def->type == VM_METHOD_TYPE_REFINED) + make_refined = 1; if (RTEST(ruby_verbose) && type != VM_METHOD_TYPE_UNDEF && @@ -276,6 +335,10 @@ rb_method_entry_make(VALUE klass, ID mid, rb_method_type_t type, } } + if (make_refined) { + make_method_entry_refined(me); + } + st_insert(mtbl, mid, (st_data_t) me); return me; @@ -343,7 +406,12 @@ rb_add_method(VALUE klass, ID mid, rb_method_type_t type, void *opts, rb_method_ int line; rb_method_entry_t *me = rb_method_entry_make(klass, mid, type, 0, noex); rb_method_definition_t *def = ALLOC(rb_method_definition_t); - me->def = def; + if (me->def && me->def->type == VM_METHOD_TYPE_REFINED) { + me->def->body.orig_me->def = def; + } + else { + me->def = def; + } def->type = type; def->original_id = mid; def->alias_count = 0; @@ -381,10 +449,13 @@ rb_add_method(VALUE klass, ID mid, rb_method_type_t type, void *opts, rb_method_ case VM_METHOD_TYPE_ZSUPER: case VM_METHOD_TYPE_UNDEF: break; + case VM_METHOD_TYPE_REFINED: + def->body.orig_me = (rb_method_entry_t *) opts; + break; default: rb_bug("rb_add_method: unsupported method type (%d)\n", type); } - if (type != VM_METHOD_TYPE_UNDEF) { + if (type != VM_METHOD_TYPE_UNDEF && type != VM_METHOD_TYPE_REFINED) { method_added(klass, mid); } return me; @@ -427,86 +498,18 @@ rb_get_alloc_func(VALUE klass) return 0; } -static VALUE -copy_refinement_iclass(VALUE iclass, VALUE superclass) -{ - VALUE result, c; - - Check_Type(iclass, T_ICLASS); - c = result = rb_include_class_new(RBASIC(iclass)->klass, superclass); - RCLASS_REFINED_CLASS(c) = RCLASS_REFINED_CLASS(iclass); - iclass = RCLASS_SUPER(iclass); - while (iclass && BUILTIN_TYPE(iclass) == T_ICLASS) { - c = RCLASS_SUPER(c) = rb_include_class_new(RBASIC(iclass)->klass, - RCLASS_SUPER(c)); - RCLASS_REFINED_CLASS(c) = RCLASS_REFINED_CLASS(iclass); - iclass = RCLASS_SUPER(iclass); - } - return result; -} - -static inline int -lookup_method_table(VALUE klass, ID id, st_data_t *body) -{ - st_table *m_tbl = RCLASS_M_TBL(klass); - if (!m_tbl) { - m_tbl = RCLASS_M_TBL(RCLASS_ORIGIN(RBASIC(klass)->klass)); - } - return st_lookup(m_tbl, id, body); -} - static inline rb_method_entry_t* -search_method_with_refinements(VALUE klass, ID id, VALUE refinements, - VALUE *defined_class_ptr) +search_method(VALUE klass, ID id, VALUE *defined_class_ptr) { - st_data_t body; - VALUE iclass, skipped_class = Qnil; - - for (body = 0; klass; klass = RCLASS_SUPER(klass)) { - if (klass != skipped_class) { - iclass = rb_hash_lookup(refinements, klass); - if (NIL_P(iclass) && BUILTIN_TYPE(klass) == T_ICLASS) { - iclass = rb_hash_lookup(refinements, RBASIC(klass)->klass); - if (!NIL_P(iclass)) - iclass = copy_refinement_iclass(iclass, klass); - } - if (!NIL_P(iclass)) { - skipped_class = klass; - klass = iclass; - } - } - if (lookup_method_table(klass, id, &body)) break; - } - - if (defined_class_ptr) - *defined_class_ptr = klass; - return (rb_method_entry_t *)body; -} - -static inline rb_method_entry_t* -search_method_without_refinements(VALUE klass, ID id, VALUE *defined_class_ptr) -{ - st_data_t body; + rb_method_entry_t *me; - for (body = 0; klass; klass = RCLASS_SUPER(klass)) { - if (lookup_method_table(klass, id, &body)) break; + for (me = 0; klass; klass = RCLASS_SUPER(klass)) { + if ((me = lookup_method_table(klass, id)) != 0) break; } if (defined_class_ptr) *defined_class_ptr = klass; - return (rb_method_entry_t *)body; -} - -static rb_method_entry_t* -search_method(VALUE klass, ID id, VALUE refinements, VALUE *defined_class_ptr) -{ - if (NIL_P(refinements)) { - return search_method_without_refinements(klass, id, defined_class_ptr); - } - else { - return search_method_with_refinements(klass, id, refinements, - defined_class_ptr); - } + return me; } /* @@ -516,19 +519,17 @@ search_method(VALUE klass, ID id, VALUE refinements, VALUE *defined_class_ptr) * rb_method_entry() simply. */ rb_method_entry_t * -rb_method_entry_get_without_cache(VALUE klass, VALUE refinements, ID id, +rb_method_entry_get_without_cache(VALUE klass, ID id, VALUE *defined_class_ptr) { VALUE defined_class; - rb_method_entry_t *me = search_method(klass, id, refinements, - &defined_class); + rb_method_entry_t *me = search_method(klass, id, &defined_class); if (ruby_running) { struct cache_entry *ent; - ent = cache + EXPR1(klass, refinements, id); + ent = cache + EXPR1(klass, id); ent->filled_version = GET_VM_STATE_VERSION(); ent->klass = klass; - ent->refinements = refinements; ent->defined_class = defined_class; if (UNDEFINED_METHOD_ENTRY_P(me)) { @@ -548,37 +549,84 @@ rb_method_entry_get_without_cache(VALUE klass, VALUE refinements, ID id, } rb_method_entry_t * -rb_method_entry_get_with_refinements(VALUE refinements, VALUE klass, ID id, - VALUE *defined_class_ptr) +rb_method_entry(VALUE klass, ID id, VALUE *defined_class_ptr) { #if OPT_GLOBAL_METHOD_CACHE struct cache_entry *ent; - ent = cache + EXPR1(klass, refinements, id); + ent = cache + EXPR1(klass, id); if (ent->filled_version == GET_VM_STATE_VERSION() && - ent->mid == id && ent->klass == klass && - ent->refinements == refinements) { + ent->mid == id && ent->klass == klass) { if (defined_class_ptr) *defined_class_ptr = ent->defined_class; return ent->me; } #endif - return rb_method_entry_get_without_cache(klass, refinements, id, + return rb_method_entry_get_without_cache(klass, id, defined_class_ptr); +} + +static rb_method_entry_t * +get_original_method_entry(VALUE refinements, + rb_method_entry_t *me, + VALUE *defined_class_ptr) +{ + if (me->def->body.orig_me) { + return me->def->body.orig_me; + } + else { + rb_method_entry_t *tmp_me; + tmp_me = rb_method_entry(RCLASS_SUPER(me->klass), me->called_id, + defined_class_ptr); + return rb_resolve_refined_method(refinements, tmp_me, + defined_class_ptr); + } +} + +rb_method_entry_t * +rb_resolve_refined_method(VALUE refinements, rb_method_entry_t *me, + VALUE *defined_class_ptr) +{ + if (me && me->def->type == VM_METHOD_TYPE_REFINED) { + VALUE refinement; + rb_method_entry_t *tmp_me; + + refinement = find_refinement(refinements, me->klass); + if (NIL_P(refinement)) { + return get_original_method_entry(refinements, me, + defined_class_ptr); + } + tmp_me = rb_method_entry(refinement, me->called_id, + defined_class_ptr); + if (tmp_me && tmp_me->def->type != VM_METHOD_TYPE_REFINED) { + return tmp_me; + } + else { + return get_original_method_entry(refinements, me, defined_class_ptr); + } + } + else { + return me; + } } rb_method_entry_t * -rb_method_entry(VALUE klass, ID id, VALUE *defined_class_ptr) +rb_method_entry_with_refinements(VALUE klass, ID id, + VALUE *defined_class_ptr) { - NODE *cref = rb_vm_cref(); - VALUE refinements = Qnil; + VALUE defined_class; + rb_method_entry_t *me = rb_method_entry(klass, id, &defined_class); + + if (me && me->def->type == VM_METHOD_TYPE_REFINED) { + NODE *cref = rb_vm_cref(); + VALUE refinements = cref ? cref->nd_refinements : Qnil; - if (cref && !NIL_P(cref->nd_refinements)) { - refinements = cref->nd_refinements; + me = rb_resolve_refined_method(refinements, me, &defined_class); } - return rb_method_entry_get_with_refinements(refinements, klass, id, - defined_class_ptr); + if (defined_class_ptr) + *defined_class_ptr = defined_class; + return me; } static void @@ -677,9 +725,9 @@ rb_export_method(VALUE klass, ID name, rb_method_flag_t noex) rb_secure(4); } - me = search_method(klass, name, Qnil, &defined_class); + me = search_method(klass, name, &defined_class); if (!me && RB_TYPE_P(klass, T_MODULE)) { - me = search_method(rb_cObject, name, Qnil, &defined_class); + me = search_method(rb_cObject, name, &defined_class); } if (UNDEFINED_METHOD_ENTRY_P(me)) { @@ -691,6 +739,9 @@ rb_export_method(VALUE klass, ID name, rb_method_flag_t noex) if (klass == defined_class) { me->flag = noex; + if (me->def->type == VM_METHOD_TYPE_REFINED) { + me->def->body.orig_me->flag = noex; + } } else { rb_add_method(klass, name, VM_METHOD_TYPE_ZSUPER, 0, noex); @@ -701,7 +752,8 @@ rb_export_method(VALUE klass, ID name, rb_method_flag_t noex) int rb_method_boundp(VALUE klass, ID id, int ex) { - rb_method_entry_t *me = rb_method_entry(klass, id, 0); + rb_method_entry_t *me = + rb_method_entry_with_refinements(klass, id, 0); if (me != 0) { if ((ex & ~NOEX_RESPONDS) && @@ -767,9 +819,6 @@ void rb_undef(VALUE klass, ID id) { rb_method_entry_t *me; - NODE *cref = rb_vm_cref(); - VALUE refinements = Qnil; - void rb_using_refinement(NODE *cref, VALUE klass, VALUE module); if (NIL_P(klass)) { rb_raise(rb_eTypeError, "no class to undef method"); @@ -785,10 +834,7 @@ rb_undef(VALUE klass, ID id) rb_warn("undefining `%s' may cause serious problems", rb_id2name(id)); } - if (cref && !NIL_P(cref->nd_refinements)) { - refinements = cref->nd_refinements; - } - me = search_method(klass, id, refinements, 0); + me = search_method(klass, id, 0); if (UNDEFINED_METHOD_ENTRY_P(me)) { const char *s0 = " class"; @@ -809,11 +855,6 @@ rb_undef(VALUE klass, ID id) rb_id2name(id), s0, rb_class2name(c)); } - if (!RTEST(rb_class_inherited_p(klass, me->klass))) { - VALUE mod = rb_module_new(); - rb_using_refinement(cref, klass, mod); - klass = mod; - } rb_add_method(klass, id, VM_METHOD_TYPE_UNDEF, 0, NOEX_PUBLIC); CALL_METHOD_HOOK(klass, undefined, id); @@ -1035,6 +1076,10 @@ rb_method_entry_eq(const rb_method_entry_t *m1, const rb_method_entry_t *m2) static int rb_method_definition_eq(const rb_method_definition_t *d1, const rb_method_definition_t *d2) { + if (d1 && d1->type == VM_METHOD_TYPE_REFINED) + d1 = d1->body.orig_me->def; + if (d2 && d2->type == VM_METHOD_TYPE_REFINED) + d2 = d2->body.orig_me->def; if (d1 == d2) return 1; if (!d1 || !d2) return 0; if (d1->type != d2->type) { @@ -1069,6 +1114,7 @@ rb_method_definition_eq(const rb_method_definition_t *d1, const rb_method_defini static st_index_t rb_hash_method_definition(st_index_t hash, const rb_method_definition_t *def) { + again: hash = rb_hash_uint(hash, def->type); switch (def->type) { case VM_METHOD_TYPE_ISEQ: @@ -1089,6 +1135,14 @@ rb_hash_method_definition(st_index_t hash, const rb_method_definition_t *def) return hash; case VM_METHOD_TYPE_OPTIMIZED: return rb_hash_uint(hash, def->body.optimize_type); + case VM_METHOD_TYPE_REFINED: + if (def->body.orig_me) { + def = def->body.orig_me->def; + goto again; + } + else { + return hash; + } default: rb_bug("rb_hash_method_definition: unsupported method type (%d)\n", def->type); } @@ -1118,11 +1172,11 @@ rb_alias(VALUE klass, ID name, ID def) } again: - orig_me = search_method(klass, def, Qnil, 0); + orig_me = search_method(klass, def, 0); if (UNDEFINED_METHOD_ENTRY_P(orig_me)) { if ((!RB_TYPE_P(klass, T_MODULE)) || - (orig_me = search_method(rb_cObject, def, Qnil, 0), + (orig_me = search_method(rb_cObject, def, 0), UNDEFINED_METHOD_ENTRY_P(orig_me))) { rb_print_undef(klass, def, 0); } @@ -1398,9 +1452,9 @@ rb_mod_modfunc(int argc, VALUE *argv, VALUE module) id = rb_to_id(argv[i]); for (;;) { - me = search_method(m, id, Qnil, 0); + me = search_method(m, id, 0); if (me == 0) { - me = search_method(rb_cObject, id, Qnil, 0); + me = search_method(rb_cObject, id, 0); } if (UNDEFINED_METHOD_ENTRY_P(me)) { rb_print_undef(module, id, 0); @@ -1518,21 +1572,6 @@ obj_respond_to_missing(VALUE obj, VALUE mid, VALUE priv) return Qfalse; } -void -rb_redefine_opt_method(VALUE klass, ID mid) -{ - st_data_t data; - rb_method_entry_t *me = 0; - VALUE origin = RCLASS_ORIGIN(klass); - - if (!st_lookup(RCLASS_M_TBL(origin), mid, &data) || - !(me = (rb_method_entry_t *)data) || - (!me->def || me->def->type == VM_METHOD_TYPE_UNDEF)) { - return; - } - rb_vm_check_redefinition_opt_method(me, origin); -} - void Init_eval_method(void) {