@
Kaigi The Future Shape of Ruby Objects 202:1
name: ‘Chris’
age: 36
company: ‘Shopify’
home: ‘Cheshire’
struct RObject {
VALUE klass;
int numiv;
VALUE[] ivptr;
};
struct RClass {
struct st_table *iv_index_tbl; // Hash of Symbol -> index
};
VALUE vm_getivar(VALUE obj, IVC inline_cache) {
if (inline_cache && inline_cache->class_serial == obj->klass->class_serial) {
int index = inline_cache->index;
if (obj->type == T_OBJECT) && index < obj->numiv) {
return obj->ivptr[index];
} else {
// slow path
}
} else {
// slow path
}
}
VALUE vm_setivar(VALUE obj, VALUE val, IVC inline_cache) {
if (inline_cache && inline_cache->class_serial == obj->klass->class_serial) {
int index = inline_cache->index;
if (obj->type == T_OBJECT) && index < obj->numiv) {
obj->ivptr[index] = val;
} else {
// slow path
} } else {
// slow path
}
}
def vm_getivar(cache, obj)
if cache.serial == obj.klass.serial && cache.index < obj.numiv
obj.ivptr[cache.index]
else
# slow path
end
end
def vm_setivar(cache, obj, val)
if cache.serial == obj.klass.serial && cache.index < obj.numiv
obj.ivptr[cache.index] = val
else
# slow path
end
end
class TwoVariablesObject {
Object var0;
Object var1;
}
def vm_getivar(cache, obj)
if cache.id == obj.getMetaClass().getRealClass().id
obj[cache.index]
else
# slow path
end
end
name: 0
home: 1
company: 2
age: 3
klass
shape
‘Chris’
‘Cheshire’
‘Shopify’
36
index = obj.shape[:name]
obj[index]
index = obj.shape[:name]
obj[index] = value
name: 0
home: 1
company: 2
age: 3
klass
shape
‘Chris’
‘Cheshire’
‘Shopify’
36
181 name: 0
home: 1
company: 2
age: 3
height: 4
+ height
new_shape = obj.shape.transitions[:add_height]
index = new_shape[:name]
obj.shape = new_shape
obj.resize
obj[index] = value
slow_path unless obj.shape == 0x12345678
obj[4]
def getter
@ivar
End
0x1242ec600: cmp dword ptr [rax*8 + 0xc], expected_shape
0x1242ec60b: jne slow_path
0x1242ec611: mov r10d, dword ptr [rax*8 + index]
def setter(value)
@ivar = value
end
0x11fd28a00: cmp dword ptr [rsi*8 + 0xc], expected_shape
0x11fd28a0b: jne slow_path
0x11fd28a1f: mov qword ptr [rsi*8 + 0x20], r10
def add
@a + @b
end
0x12258d380: cmp dword ptr [rax*8 + 0xc], expected_shape
0x12258d38b: jne slow_path
0x12258d391: mov esi, dword ptr [rax*8 + index_a]
0x12258d398: mov eax, dword ptr [rax*8 + index_b]
0x12258d39f: mov r10d, eax
0x12258d3a2: add r10d, esi
0x12258d3a5: jo overflow
name: 0
home: 1
company: 2
age: 3
klass
shape
‘Chris’
‘Cheshire’
‘Shopify’
36
181 name: 0
home: 1
company: 2
age: 3
(frozen)
freeze
0x11fd28a00: cmp dword ptr [rsi*8 + 0xc], expected_shape
0x11fd28a0b: jne slow_path
0x11fd28a11: mov eax, dword ptr [rdx + index]
@chrisgseaton

The Future Shape of Ruby Objects

  • 1.
    @ Kaigi The FutureShape of Ruby Objects 202:1
  • 2.
    name: ‘Chris’ age: 36 company:‘Shopify’ home: ‘Cheshire’
  • 3.
    struct RObject { VALUEklass; int numiv; VALUE[] ivptr; }; struct RClass { struct st_table *iv_index_tbl; // Hash of Symbol -> index };
  • 4.
    VALUE vm_getivar(VALUE obj,IVC inline_cache) { if (inline_cache && inline_cache->class_serial == obj->klass->class_serial) { int index = inline_cache->index; if (obj->type == T_OBJECT) && index < obj->numiv) { return obj->ivptr[index]; } else { // slow path } } else { // slow path } } VALUE vm_setivar(VALUE obj, VALUE val, IVC inline_cache) { if (inline_cache && inline_cache->class_serial == obj->klass->class_serial) { int index = inline_cache->index; if (obj->type == T_OBJECT) && index < obj->numiv) { obj->ivptr[index] = val; } else { // slow path } } else { // slow path } }
  • 5.
    def vm_getivar(cache, obj) ifcache.serial == obj.klass.serial && cache.index < obj.numiv obj.ivptr[cache.index] else # slow path end end def vm_setivar(cache, obj, val) if cache.serial == obj.klass.serial && cache.index < obj.numiv obj.ivptr[cache.index] = val else # slow path end end
  • 6.
    class TwoVariablesObject { Objectvar0; Object var1; } def vm_getivar(cache, obj) if cache.id == obj.getMetaClass().getRealClass().id obj[cache.index] else # slow path end end
  • 12.
    name: 0 home: 1 company:2 age: 3 klass shape ‘Chris’ ‘Cheshire’ ‘Shopify’ 36
  • 13.
  • 14.
  • 15.
    name: 0 home: 1 company:2 age: 3 klass shape ‘Chris’ ‘Cheshire’ ‘Shopify’ 36 181 name: 0 home: 1 company: 2 age: 3 height: 4 + height
  • 16.
    new_shape = obj.shape.transitions[:add_height] index= new_shape[:name] obj.shape = new_shape obj.resize obj[index] = value
  • 17.
    slow_path unless obj.shape== 0x12345678 obj[4]
  • 19.
    def getter @ivar End 0x1242ec600: cmpdword ptr [rax*8 + 0xc], expected_shape 0x1242ec60b: jne slow_path 0x1242ec611: mov r10d, dword ptr [rax*8 + index]
  • 20.
    def setter(value) @ivar =value end 0x11fd28a00: cmp dword ptr [rsi*8 + 0xc], expected_shape 0x11fd28a0b: jne slow_path 0x11fd28a1f: mov qword ptr [rsi*8 + 0x20], r10
  • 22.
    def add @a +@b end 0x12258d380: cmp dword ptr [rax*8 + 0xc], expected_shape 0x12258d38b: jne slow_path 0x12258d391: mov esi, dword ptr [rax*8 + index_a] 0x12258d398: mov eax, dword ptr [rax*8 + index_b] 0x12258d39f: mov r10d, eax 0x12258d3a2: add r10d, esi 0x12258d3a5: jo overflow
  • 23.
    name: 0 home: 1 company:2 age: 3 klass shape ‘Chris’ ‘Cheshire’ ‘Shopify’ 36 181 name: 0 home: 1 company: 2 age: 3 (frozen) freeze
  • 24.
    0x11fd28a00: cmp dwordptr [rsi*8 + 0xc], expected_shape 0x11fd28a0b: jne slow_path 0x11fd28a11: mov eax, dword ptr [rdx + index]
  • 25.