Skip to content

Commit 12e3b07

Browse files
committed
Re-embed when removing Object instance variables
Objects with the same shape must always have the same "embeddedness" (either embedded or heap allocated) because YJIT assumes so. However, using remove_instance_variable, it's possible that some objects are embedded and some are heap allocated because it does not re-embed heap allocated objects. This commit changes remove_instance_variable to re-embed Object instance variables when it becomes small enough.
1 parent f80262b commit 12e3b07

File tree

6 files changed

+55
-6
lines changed

6 files changed

+55
-6
lines changed

common.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15849,6 +15849,7 @@ shape.$(OBJEXT): $(top_srcdir)/internal/compilers.h
1584915849
shape.$(OBJEXT): $(top_srcdir)/internal/error.h
1585015850
shape.$(OBJEXT): $(top_srcdir)/internal/gc.h
1585115851
shape.$(OBJEXT): $(top_srcdir)/internal/imemo.h
15852+
shape.$(OBJEXT): $(top_srcdir)/internal/object.h
1585215853
shape.$(OBJEXT): $(top_srcdir)/internal/serial.h
1585315854
shape.$(OBJEXT): $(top_srcdir)/internal/static_assert.h
1585415855
shape.$(OBJEXT): $(top_srcdir)/internal/string.h

gc.c

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2960,12 +2960,6 @@ rb_newobj(void)
29602960
return newobj_of(GET_RACTOR(), 0, T_NONE, 0, 0, 0, FALSE, RVALUE_SIZE);
29612961
}
29622962

2963-
static size_t
2964-
rb_obj_embedded_size(uint32_t numiv)
2965-
{
2966-
return offsetof(struct RObject, as.ary) + (sizeof(VALUE) * numiv);
2967-
}
2968-
29692963
static VALUE
29702964
rb_class_instance_allocate_internal(VALUE klass, VALUE flags, bool wb_protected)
29712965
{

internal/object.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "ruby/ruby.h" /* for VALUE */
1212

1313
/* object.c */
14+
size_t rb_obj_embedded_size(uint32_t numiv);
1415
VALUE rb_class_search_ancestor(VALUE klass, VALUE super);
1516
NORETURN(void rb_undefined_alloc(VALUE klass));
1617
double rb_num_to_dbl(VALUE val);

object.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,12 @@ static VALUE rb_cFalseClass_to_s;
9292

9393
/*! \endcond */
9494

95+
size_t
96+
rb_obj_embedded_size(uint32_t numiv)
97+
{
98+
return offsetof(struct RObject, as.ary) + (sizeof(VALUE) * numiv);
99+
}
100+
95101
VALUE
96102
rb_obj_hide(VALUE obj)
97103
{

shape.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "internal/class.h"
77
#include "internal/error.h"
88
#include "internal/gc.h"
9+
#include "internal/object.h"
910
#include "internal/symbol.h"
1011
#include "internal/variable.h"
1112
#include "variable.h"
@@ -642,6 +643,17 @@ rb_shape_transition_shape_remove_ivar(VALUE obj, ID id, rb_shape_t *shape, VALUE
642643
memmove(&ivptr[removed_shape->next_iv_index - 1], &ivptr[removed_shape->next_iv_index],
643644
((new_shape->next_iv_index + 1) - removed_shape->next_iv_index) * sizeof(VALUE));
644645

646+
// Re-embed objects when instances become small enough
647+
// This is necessary because YJIT assumes that objects with the same shape
648+
// have the same embeddedness for efficiency (avoid extra checks)
649+
if (BUILTIN_TYPE(obj) == T_OBJECT &&
650+
!RB_FL_TEST_RAW(obj, ROBJECT_EMBED) &&
651+
rb_obj_embedded_size(new_shape->next_iv_index) <= rb_gc_obj_slot_size(obj)) {
652+
RB_FL_SET_RAW(obj, ROBJECT_EMBED);
653+
memcpy(ROBJECT_IVPTR(obj), ivptr, new_shape->next_iv_index * sizeof(VALUE));
654+
xfree(ivptr);
655+
}
656+
645657
rb_shape_set_shape(obj, new_shape);
646658
}
647659
return true;

test/ruby/test_object.rb

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,41 @@ def test_remove_instance_variable
355355
end
356356
end
357357

358+
def test_remove_instance_variable_re_embed
359+
require "objspace"
360+
361+
c = Class.new do
362+
def a = @a
363+
364+
def b = @b
365+
366+
def c = @c
367+
end
368+
369+
o1 = c.new
370+
o2 = c.new
371+
372+
o1.instance_variable_set(:@foo, 5)
373+
o1.instance_variable_set(:@a, 0)
374+
o1.instance_variable_set(:@b, 1)
375+
o1.instance_variable_set(:@c, 2)
376+
refute_includes ObjectSpace.dump(o1), '"embedded":true'
377+
o1.remove_instance_variable(:@foo)
378+
assert_includes ObjectSpace.dump(o1), '"embedded":true'
379+
380+
o2.instance_variable_set(:@a, 0)
381+
o2.instance_variable_set(:@b, 1)
382+
o2.instance_variable_set(:@c, 2)
383+
assert_includes ObjectSpace.dump(o2), '"embedded":true'
384+
385+
assert_equal(0, o1.a)
386+
assert_equal(1, o1.b)
387+
assert_equal(2, o1.c)
388+
assert_equal(0, o2.a)
389+
assert_equal(1, o2.b)
390+
assert_equal(2, o2.c)
391+
end
392+
358393
def test_convert_string
359394
o = Object.new
360395
def o.to_s; 1; end

0 commit comments

Comments
 (0)