summaryrefslogtreecommitdiff
path: root/shape.c
diff options
context:
space:
mode:
authorJemma Issroff <jemmaissroff@gmail.com>2022-11-08 15:35:31 -0500
committerPeter Zhu <peter@peterzhu.ca>2022-11-10 10:11:34 -0500
commit5246f4027ec574e77809845e1b1f7822cc2a5cef (patch)
treea29c972df6a589c7ab8c2541ea2eea1f7caf5f70 /shape.c
parent9986697b621e5345177a1c395489dcc9fab8602b (diff)
downloadruby-5246f4027ec574e77809845e1b1f7822cc2a5cef.tar.gz
Transition shape when object's capacity changes
This commit adds a `capacity` field to shapes, and adds shape transitions whenever an object's capacity changes. Objects which are allocated out of a bigger size pool will also make a transition from the root shape to the shape with the correct capacity for their size pool when they are allocated. This commit will allow us to remove numiv from objects completely, and will also mean we can guarantee that if two objects share shapes, their IVs are in the same positions (an embedded and extended object cannot share shapes). This will enable us to implement ivar sets in YJIT using object shapes. Co-Authored-By: Aaron Patterson <tenderlove@ruby-lang.org>
Diffstat (limited to 'shape.c')
-rw-r--r--shape.c203
1 files changed, 158 insertions, 45 deletions
diff --git a/shape.c b/shape.c
index 1de89d3f8f..e19667ae2c 100644
--- a/shape.c
+++ b/shape.c
@@ -1,15 +1,19 @@
#include "vm_core.h"
#include "vm_sync.h"
#include "shape.h"
+#include "gc.h"
#include "internal/class.h"
#include "internal/symbol.h"
#include "internal/variable.h"
#include <stdbool.h>
+static ID id_frozen;
+static ID size_pool_edge_names[SIZE_POOL_COUNT];
+
/*
* Shape getters
*/
-static rb_shape_t*
+rb_shape_t *
rb_shape_get_root_shape(void)
{
return GET_VM()->root_shape;
@@ -21,12 +25,6 @@ rb_shape_id(rb_shape_t * shape)
return (shape_id_t)(shape - GET_VM()->shape_list);
}
-static rb_shape_t*
-rb_shape_get_frozen_root_shape(void)
-{
- return GET_VM()->frozen_root_shape;
-}
-
bool
rb_shape_root_shape_p(rb_shape_t* shape)
{
@@ -68,7 +66,7 @@ shape_id_t
rb_shape_get_shape_id(VALUE obj)
{
if (RB_SPECIAL_CONST_P(obj)) {
- return FROZEN_ROOT_SHAPE_ID;
+ return SPECIAL_CONST_SHAPE_ID;
}
#if SHAPE_IN_BASIC_FLAGS
@@ -113,12 +111,9 @@ rb_shape_lookup_id(rb_shape_t* shape, ID id, enum shape_type shape_type)
}
static rb_shape_t*
-get_next_shape_internal(rb_shape_t* shape, ID id, VALUE obj, enum shape_type shape_type)
+get_next_shape_internal(rb_shape_t * shape, ID id, enum shape_type shape_type)
{
rb_shape_t *res = NULL;
-
- RUBY_ASSERT(SHAPE_FROZEN != (enum shape_type)shape->type || RB_TYPE_P(obj, T_MODULE) || RB_TYPE_P(obj, T_CLASS));
-
RB_VM_LOCK_ENTER();
{
if (rb_shape_lookup_id(shape, id, shape_type)) {
@@ -142,23 +137,18 @@ get_next_shape_internal(rb_shape_t* shape, ID id, VALUE obj, enum shape_type sha
rb_shape_t * new_shape = rb_shape_alloc(id, shape);
new_shape->type = (uint8_t)shape_type;
+ new_shape->capacity = shape->capacity;
switch (shape_type) {
case SHAPE_IVAR:
- new_shape->next_iv_index = rb_shape_get_shape_by_id(new_shape->parent_id)->next_iv_index + 1;
-
- // Check if we should update next_iv_index on the object's class
- if (BUILTIN_TYPE(obj) == T_OBJECT) {
- VALUE klass = rb_obj_class(obj);
- if (new_shape->next_iv_index > RCLASS_EXT(klass)->max_iv_count) {
- RCLASS_EXT(klass)->max_iv_count = new_shape->next_iv_index;
- }
- }
+ new_shape->next_iv_index = shape->next_iv_index + 1;
break;
+ case SHAPE_CAPACITY_CHANGE:
case SHAPE_IVAR_UNDEF:
case SHAPE_FROZEN:
- new_shape->next_iv_index = rb_shape_get_shape_by_id(new_shape->parent_id)->next_iv_index;
+ new_shape->next_iv_index = shape->next_iv_index;
break;
+ case SHAPE_INITIAL_CAPACITY:
case SHAPE_ROOT:
rb_bug("Unreachable");
break;
@@ -183,7 +173,7 @@ rb_shape_frozen_shape_p(rb_shape_t* shape)
void
rb_shape_transition_shape_remove_ivar(VALUE obj, ID id, rb_shape_t *shape)
{
- rb_shape_t* next_shape = get_next_shape_internal(shape, id, obj, SHAPE_IVAR_UNDEF);
+ rb_shape_t * next_shape = get_next_shape_internal(shape, id, SHAPE_IVAR_UNDEF);
if (shape == next_shape) {
return;
@@ -206,16 +196,11 @@ rb_shape_transition_shape_frozen(VALUE obj)
rb_shape_t* next_shape;
if (shape == rb_shape_get_root_shape()) {
- next_shape = rb_shape_get_frozen_root_shape();
+ rb_shape_set_shape_id(obj, SPECIAL_CONST_SHAPE_ID);
+ return;
}
- else {
- static ID id_frozen;
- if (!id_frozen) {
- id_frozen = rb_make_internal_id();
- }
- next_shape = get_next_shape_internal(shape, (ID)id_frozen, obj, SHAPE_FROZEN);
- }
+ next_shape = get_next_shape_internal(shape, (ID)id_frozen, SHAPE_FROZEN);
RUBY_ASSERT(next_shape);
rb_shape_set_shape(obj, next_shape);
@@ -231,10 +216,39 @@ rb_shape_transition_shape(VALUE obj, ID id, rb_shape_t *shape)
rb_shape_set_shape(obj, next_shape);
}
-rb_shape_t*
+/*
+ * This function is used for assertions where we don't want to increment
+ * max_iv_count
+ */
+rb_shape_t *
+rb_shape_get_next_iv_shape(rb_shape_t* shape, ID id)
+{
+ return get_next_shape_internal(shape, id, SHAPE_IVAR);
+}
+
+rb_shape_t *
rb_shape_get_next(rb_shape_t* shape, VALUE obj, ID id)
{
- return get_next_shape_internal(shape, id, obj, SHAPE_IVAR);
+ rb_shape_t * new_shape = rb_shape_get_next_iv_shape(shape, id);
+
+ // Check if we should update max_iv_count on the object's class
+ if (BUILTIN_TYPE(obj) == T_OBJECT) {
+ VALUE klass = rb_obj_class(obj);
+ if (new_shape->next_iv_index > RCLASS_EXT(klass)->max_iv_count) {
+ RCLASS_EXT(klass)->max_iv_count = new_shape->next_iv_index;
+ }
+ }
+
+ return new_shape;
+}
+
+rb_shape_t *
+rb_shape_transition_shape_capa(rb_shape_t* shape, uint32_t new_capacity)
+{
+ ID edge_name = rb_make_temporary_id(new_capacity);
+ rb_shape_t * new_shape = get_next_shape_internal(shape, edge_name, SHAPE_CAPACITY_CHANGE);
+ new_shape->capacity = new_capacity;
+ return new_shape;
}
bool
@@ -250,11 +264,13 @@ rb_shape_get_iv_index(rb_shape_t * shape, ID id, attr_index_t *value)
RUBY_ASSERT(shape->next_iv_index > 0);
*value = shape->next_iv_index - 1;
return true;
+ case SHAPE_CAPACITY_CHANGE:
case SHAPE_IVAR_UNDEF:
case SHAPE_ROOT:
+ case SHAPE_INITIAL_CAPACITY:
return false;
case SHAPE_FROZEN:
- rb_bug("Ivar should not exist on frozen transition\n");
+ rb_bug("Ivar should not exist on transition\n");
}
}
shape = rb_shape_get_shape_by_id(shape->parent_id);
@@ -290,9 +306,18 @@ rb_shape_alloc_with_parent_id(ID edge_name, shape_id_t parent_id)
}
rb_shape_t *
+rb_shape_alloc_with_size_pool_index(ID edge_name, rb_shape_t * parent, uint8_t size_pool_index)
+{
+ rb_shape_t * shape = rb_shape_alloc_with_parent_id(edge_name, rb_shape_id(parent));
+ shape->size_pool_index = size_pool_index;
+ return shape;
+}
+
+
+rb_shape_t *
rb_shape_alloc(ID edge_name, rb_shape_t * parent)
{
- return rb_shape_alloc_with_parent_id(edge_name, rb_shape_id(parent));
+ return rb_shape_alloc_with_size_pool_index(edge_name, parent, parent->size_pool_index);
}
MJIT_FUNC_EXPORTED void
@@ -307,6 +332,39 @@ rb_shape_flags_mask(void)
return SHAPE_FLAG_MASK;
}
+rb_shape_t *
+rb_shape_rebuild_shape(rb_shape_t * initial_shape, rb_shape_t * dest_shape)
+{
+ rb_shape_t * midway_shape;
+
+ if (dest_shape->type != SHAPE_ROOT) {
+ midway_shape = rb_shape_rebuild_shape(initial_shape, rb_shape_get_shape_by_id(dest_shape->parent_id));
+ }
+ else {
+ midway_shape = initial_shape;
+ }
+
+ switch (dest_shape->type) {
+ case SHAPE_IVAR:
+ if (midway_shape->capacity < midway_shape->next_iv_index) {
+ // There isn't enough room to write this IV, so we need to increase the capacity
+ midway_shape = rb_shape_transition_shape_capa(midway_shape, midway_shape->capacity * 2);
+ }
+
+ midway_shape = rb_shape_get_next_iv_shape(midway_shape, dest_shape->edge_name);
+ break;
+ case SHAPE_IVAR_UNDEF:
+ midway_shape = get_next_shape_internal(midway_shape, dest_shape->edge_name, SHAPE_IVAR_UNDEF);
+ break;
+ case SHAPE_ROOT:
+ case SHAPE_FROZEN:
+ case SHAPE_CAPACITY_CHANGE:
+ break;
+ }
+
+ return midway_shape;
+}
+
#if VM_CHECK_MODE > 0
VALUE rb_cShape;
@@ -336,6 +394,14 @@ rb_shape_type(VALUE self)
}
static VALUE
+rb_shape_capacity(VALUE self)
+{
+ rb_shape_t * shape;
+ TypedData_Get_Struct(self, rb_shape_t, &shape_data_type, shape);
+ return INT2NUM(shape->capacity);
+}
+
+static VALUE
rb_shape_parent_id(VALUE self)
{
rb_shape_t * shape;
@@ -398,11 +464,16 @@ rb_shape_edge_name(VALUE self)
rb_shape_t* shape;
TypedData_Get_Struct(self, rb_shape_t, &shape_data_type, shape);
- if (shape->edge_name) {
- return ID2SYM(shape->edge_name);
+ if ((shape->edge_name & (ID_INTERNAL)) == ID_INTERNAL) {
+ return INT2NUM(shape->capacity);
}
else {
- return Qnil;
+ if (shape->edge_name) {
+ return ID2SYM(shape->edge_name);
+ }
+ else {
+ return Qnil;
+ }
}
}
@@ -416,6 +487,15 @@ rb_shape_next_iv_index(VALUE self)
}
static VALUE
+rb_shape_size_pool_index(VALUE self)
+{
+ rb_shape_t * shape;
+ TypedData_Get_Struct(self, rb_shape_t, &shape_data_type, shape);
+
+ return INT2NUM(shape->size_pool_index);
+}
+
+static VALUE
rb_shape_export_depth(VALUE self)
{
rb_shape_t* shape;
@@ -454,12 +534,6 @@ rb_shape_root_shape(VALUE self)
return rb_shape_t_to_rb_cShape(rb_shape_get_root_shape());
}
-static VALUE
-rb_shape_frozen_root_shape(VALUE self)
-{
- return rb_shape_t_to_rb_cShape(rb_shape_get_frozen_root_shape());
-}
-
VALUE rb_obj_shape(rb_shape_t* shape);
static enum rb_id_table_iterator_result collect_keys_and_values(ID key, VALUE value, void *ref)
@@ -519,6 +593,43 @@ rb_shape_find_by_id(VALUE mod, VALUE id)
#endif
void
+Init_default_shapes(void)
+{
+ id_frozen = rb_make_internal_id();
+
+ // Shapes by size pool
+ for (int i = 0; i < SIZE_POOL_COUNT; i++) {
+ size_pool_edge_names[i] = rb_make_internal_id();
+ }
+
+ // Root shape
+ rb_shape_t * root = rb_shape_alloc_with_parent_id(0, INVALID_SHAPE_ID);
+ root->capacity = (uint32_t)((rb_size_pool_slot_size(0) - offsetof(struct RObject, as.ary)) / sizeof(VALUE));
+ root->type = SHAPE_ROOT;
+ root->size_pool_index = 0;
+ GET_VM()->root_shape = root;
+ RUBY_ASSERT(rb_shape_id(GET_VM()->root_shape) == ROOT_SHAPE_ID);
+
+ // Shapes by size pool
+ for (int i = 1; i < SIZE_POOL_COUNT; i++) {
+ uint32_t capa = (uint32_t)((rb_size_pool_slot_size(i) - offsetof(struct RObject, as.ary)) / sizeof(VALUE));
+ rb_shape_t * new_shape = rb_shape_transition_shape_capa(root, capa);
+ new_shape->type = SHAPE_INITIAL_CAPACITY;
+ new_shape->size_pool_index = i;
+ RUBY_ASSERT(rb_shape_id(new_shape) == (shape_id_t)i);
+ }
+
+ // Special const shape
+#if RUBY_DEBUG
+ rb_shape_t * special_const_shape =
+#endif
+ get_next_shape_internal(root, (ID)id_frozen, SHAPE_FROZEN);
+ RUBY_ASSERT(rb_shape_id(special_const_shape) == SPECIAL_CONST_SHAPE_ID);
+ RUBY_ASSERT(SPECIAL_CONST_SHAPE_ID == (GET_VM()->next_shape_id - 1));
+ RUBY_ASSERT(rb_shape_frozen_shape_p(special_const_shape));
+}
+
+void
Init_shape(void)
{
#if VM_CHECK_MODE > 0
@@ -530,21 +641,23 @@ Init_shape(void)
rb_define_method(rb_cShape, "edges", rb_shape_edges, 0);
rb_define_method(rb_cShape, "edge_name", rb_shape_edge_name, 0);
rb_define_method(rb_cShape, "next_iv_index", rb_shape_next_iv_index, 0);
+ rb_define_method(rb_cShape, "size_pool_index", rb_shape_size_pool_index, 0);
rb_define_method(rb_cShape, "depth", rb_shape_export_depth, 0);
rb_define_method(rb_cShape, "id", rb_wrapped_shape_id, 0);
rb_define_method(rb_cShape, "type", rb_shape_type, 0);
+ rb_define_method(rb_cShape, "capacity", rb_shape_capacity, 0);
rb_define_const(rb_cShape, "SHAPE_ROOT", INT2NUM(SHAPE_ROOT));
rb_define_const(rb_cShape, "SHAPE_IVAR", INT2NUM(SHAPE_IVAR));
rb_define_const(rb_cShape, "SHAPE_IVAR_UNDEF", INT2NUM(SHAPE_IVAR_UNDEF));
rb_define_const(rb_cShape, "SHAPE_FROZEN", INT2NUM(SHAPE_FROZEN));
rb_define_const(rb_cShape, "SHAPE_BITS", INT2NUM(SHAPE_BITS));
rb_define_const(rb_cShape, "SHAPE_FLAG_SHIFT", INT2NUM(SHAPE_FLAG_SHIFT));
+ rb_define_const(rb_cShape, "SPECIAL_CONST_SHAPE_ID", INT2NUM(SPECIAL_CONST_SHAPE_ID));
rb_define_singleton_method(rb_cShape, "transition_tree", shape_transition_tree, 0);
rb_define_singleton_method(rb_cShape, "find_by_id", rb_shape_find_by_id, 1);
rb_define_singleton_method(rb_cShape, "next_shape_id", next_shape_id, 0);
rb_define_singleton_method(rb_cShape, "of", rb_shape_debug_shape, 1);
rb_define_singleton_method(rb_cShape, "root_shape", rb_shape_root_shape, 0);
- rb_define_singleton_method(rb_cShape, "frozen_root_shape", rb_shape_frozen_root_shape, 0);
#endif
}