From 5f137d6303b1e95b8c373ec713d2d30c3742496e Mon Sep 17 00:00:00 2001 From: eileencodes Date: Fri, 23 Jun 2023 10:56:03 -0400 Subject: [PATCH] Implement dup on unbound method In Rails `_read_attribute` is defined on the top level ActiveRecord::Base rather than the subclasses. This results in object shapes not being able to use their cache. By implementing `dup` on unbound methods we can give each subclass a uniq iseq and unique inline cache, increasing our shape cache hits. Note: experimental change that must be paired with changes to Active Record to see results. --- method.h | 1 + proc.c | 18 ++++++++++++++++++ vm_method.c | 26 ++++++++++++++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/method.h b/method.h index d33ab5053cbcaa..70af80bac6c95d 100644 --- a/method.h +++ b/method.h @@ -239,6 +239,7 @@ VALUE rb_method_entry_location(const rb_method_entry_t *me); void rb_free_method_entry(const rb_method_entry_t *me); const rb_method_entry_t *rb_method_entry_clone(const rb_method_entry_t *me); +const rb_method_entry_t *rb_method_entry_dup(const rb_method_entry_t *me); const rb_callable_method_entry_t *rb_method_entry_complement_defined_class(const rb_method_entry_t *src_me, ID called_id, VALUE defined_class); void rb_method_entry_copy(rb_method_entry_t *dst, const rb_method_entry_t *src); diff --git a/proc.c b/proc.c index 27df3b1df90431..303de0133bd9fc 100644 --- a/proc.c +++ b/proc.c @@ -2429,6 +2429,23 @@ method_clone(VALUE self) return clone; } +static VALUE +method_dup(VALUE self) +{ + VALUE dup; + struct METHOD *orig, *data; + + TypedData_Get_Struct(self, struct METHOD, &method_data_type, orig); + dup = TypedData_Make_Struct(CLASS_OF(self), struct METHOD, &method_data_type, data); + CLONESETUP(dup, self); + RB_OBJ_WRITE(dup, &data->recv, orig->recv); + RB_OBJ_WRITE(dup, &data->klass, orig->klass); + RB_OBJ_WRITE(dup, &data->iclass, orig->iclass); + RB_OBJ_WRITE(dup, &data->owner, orig->owner); + RB_OBJ_WRITE(dup, &data->me, rb_method_entry_dup(orig->me)); + return dup; +} + /* Document-method: Method#=== * * call-seq: @@ -4354,6 +4371,7 @@ Init_Proc(void) rb_define_method(rb_cUnboundMethod, "eql?", unbound_method_eq, 1); rb_define_method(rb_cUnboundMethod, "hash", method_hash, 0); rb_define_method(rb_cUnboundMethod, "clone", method_clone, 0); + rb_define_method(rb_cUnboundMethod, "dup", method_dup, 0); rb_define_method(rb_cUnboundMethod, "arity", method_arity_m, 0); rb_define_method(rb_cUnboundMethod, "inspect", method_inspect, 0); rb_define_method(rb_cUnboundMethod, "to_s", method_inspect, 0); diff --git a/vm_method.c b/vm_method.c index 30241cc9cd4435..dcba77117b8c0e 100644 --- a/vm_method.c +++ b/vm_method.c @@ -702,6 +702,32 @@ rb_method_entry_clone(const rb_method_entry_t *src_me) return me; } +const rb_method_entry_t * +rb_method_entry_dup(const rb_method_entry_t *src_me) +{ + // Create an empty method definition. + rb_method_definition_t *def = rb_method_definition_create(src_me->def->type, src_me->def->original_id); + + const rb_iseq_t *iseq = src_me->def->body.iseq.iseqptr; + + // Duplicate the instruction sequence + VALUE str = rb_iseq_ibf_dump(iseq, Qnil); + const rb_iseq_t *new_iseq = rb_iseq_ibf_load(str); + + // Assign the copied iseq to the new method definition + def->body.iseq.iseqptr = new_iseq; + + // Create a new method entry + rb_method_entry_t *me = rb_method_entry_alloc(src_me->called_id, src_me->owner, src_me->defined_class, def); + + if (METHOD_ENTRY_COMPLEMENTED(src_me)) { + method_definition_addref_complement(def); + } + + METHOD_ENTRY_FLAGS_COPY(me, src_me); + return me; +} + MJIT_FUNC_EXPORTED const rb_callable_method_entry_t * rb_method_entry_complement_defined_class(const rb_method_entry_t *src_me, ID called_id, VALUE defined_class) {