summaryrefslogtreecommitdiff
path: root/shape.c
diff options
context:
space:
mode:
Diffstat (limited to 'shape.c')
-rw-r--r--shape.c798
1 files changed, 614 insertions, 184 deletions
diff --git a/shape.c b/shape.c
index dcdb9d28aa..0f9e3d54fa 100644
--- a/shape.c
+++ b/shape.c
@@ -4,26 +4,296 @@
#include "symbol.h"
#include "id_table.h"
#include "internal/class.h"
+#include "internal/error.h"
#include "internal/gc.h"
+#include "internal/object.h"
#include "internal/symbol.h"
#include "internal/variable.h"
-#include "internal/error.h"
#include "variable.h"
#include <stdbool.h>
+#ifndef _WIN32
+#include <sys/mman.h>
+#endif
+
#ifndef SHAPE_DEBUG
#define SHAPE_DEBUG (VM_CHECK_MODE > 0)
#endif
+#if SIZEOF_SHAPE_T == 4
+#if RUBY_DEBUG
+#define SHAPE_BUFFER_SIZE 0x8000
+#else
+#define SHAPE_BUFFER_SIZE 0x80000
+#endif
+#else
+#define SHAPE_BUFFER_SIZE 0x8000
+#endif
+
+#define REDBLACK_CACHE_SIZE (SHAPE_BUFFER_SIZE * 32)
+
#define SINGLE_CHILD_TAG 0x1
#define TAG_SINGLE_CHILD(x) (struct rb_id_table *)((uintptr_t)x | SINGLE_CHILD_TAG)
#define SINGLE_CHILD_MASK (~((uintptr_t)SINGLE_CHILD_TAG))
#define SINGLE_CHILD_P(x) (((uintptr_t)x) & SINGLE_CHILD_TAG)
#define SINGLE_CHILD(x) (rb_shape_t *)((uintptr_t)x & SINGLE_CHILD_MASK)
+#define ANCESTOR_CACHE_THRESHOLD 10
+#define MAX_SHAPE_ID (SHAPE_BUFFER_SIZE - 1)
+#define ANCESTOR_SEARCH_MAX_DEPTH 2
static ID id_frozen;
static ID id_t_object;
-static ID size_pool_edge_names[SIZE_POOL_COUNT];
+
+#define LEAF 0
+#define BLACK 0x0
+#define RED 0x1
+
+static redblack_node_t *
+redblack_left(redblack_node_t * node)
+{
+ if (node->l == LEAF) {
+ return LEAF;
+ }
+ else {
+ RUBY_ASSERT(node->l < GET_SHAPE_TREE()->cache_size);
+ redblack_node_t * left = &GET_SHAPE_TREE()->shape_cache[node->l - 1];
+ return left;
+ }
+}
+
+static redblack_node_t *
+redblack_right(redblack_node_t * node)
+{
+ if (node->r == LEAF) {
+ return LEAF;
+ }
+ else {
+ RUBY_ASSERT(node->r < GET_SHAPE_TREE()->cache_size);
+ redblack_node_t * right = &GET_SHAPE_TREE()->shape_cache[node->r - 1];
+ return right;
+ }
+}
+
+static redblack_node_t *
+redblack_find(redblack_node_t * tree, ID key)
+{
+ if (tree == LEAF) {
+ return LEAF;
+ }
+ else {
+ RUBY_ASSERT(redblack_left(tree) == LEAF || redblack_left(tree)->key < tree->key);
+ RUBY_ASSERT(redblack_right(tree) == LEAF || redblack_right(tree)->key > tree->key);
+
+ if (tree->key == key) {
+ return tree;
+ }
+ else {
+ if (key < tree->key) {
+ return redblack_find(redblack_left(tree), key);
+ }
+ else {
+ return redblack_find(redblack_right(tree), key);
+ }
+ }
+ }
+}
+
+static inline char
+redblack_color(redblack_node_t * node)
+{
+ return node && ((uintptr_t)node->value & RED);
+}
+
+static inline bool
+redblack_red_p(redblack_node_t * node)
+{
+ return redblack_color(node) == RED;
+}
+
+static inline rb_shape_t *
+redblack_value(redblack_node_t * node)
+{
+ // Color is stored in the bottom bit of the shape pointer
+ // Mask away the bit so we get the actual pointer back
+ return (rb_shape_t *)((uintptr_t)node->value & (((uintptr_t)-1) - 1));
+}
+
+#ifdef HAVE_MMAP
+static redblack_id_t
+redblack_id_for(redblack_node_t * node)
+{
+ RUBY_ASSERT(node || node == LEAF);
+ if (node == LEAF) {
+ return 0;
+ }
+ else {
+ redblack_node_t * redblack_nodes = GET_SHAPE_TREE()->shape_cache;
+ redblack_id_t id = (redblack_id_t)(node - redblack_nodes);
+ return id + 1;
+ }
+}
+
+static redblack_node_t *
+redblack_new(char color, ID key, rb_shape_t * value, redblack_node_t * left, redblack_node_t * right)
+{
+ if (GET_SHAPE_TREE()->cache_size + 1 >= REDBLACK_CACHE_SIZE) {
+ // We're out of cache, just quit
+ return LEAF;
+ }
+
+ RUBY_ASSERT(left == LEAF || left->key < key);
+ RUBY_ASSERT(right == LEAF || right->key > key);
+
+ redblack_node_t * redblack_nodes = GET_SHAPE_TREE()->shape_cache;
+ redblack_node_t * node = &redblack_nodes[(GET_SHAPE_TREE()->cache_size)++];
+ node->key = key;
+ node->value = (rb_shape_t *)((uintptr_t)value | color);
+ node->l = redblack_id_for(left);
+ node->r = redblack_id_for(right);
+ return node;
+}
+
+static redblack_node_t *
+redblack_balance(char color, ID key, rb_shape_t * value, redblack_node_t * left, redblack_node_t * right)
+{
+ if (color == BLACK) {
+ ID new_key, new_left_key, new_right_key;
+ rb_shape_t *new_value, *new_left_value, *new_right_value;
+ redblack_node_t *new_left_left, *new_left_right, *new_right_left, *new_right_right;
+
+ if (redblack_red_p(left) && redblack_red_p(redblack_left(left))) {
+ new_right_key = key;
+ new_right_value = value;
+ new_right_right = right;
+
+ new_key = left->key;
+ new_value = redblack_value(left);
+ new_right_left = redblack_right(left);
+
+ new_left_key = redblack_left(left)->key;
+ new_left_value = redblack_value(redblack_left(left));
+
+ new_left_left = redblack_left(redblack_left(left));
+ new_left_right = redblack_right(redblack_left(left));
+ }
+ else if (redblack_red_p(left) && redblack_red_p(redblack_right(left))) {
+ new_right_key = key;
+ new_right_value = value;
+ new_right_right = right;
+
+ new_left_key = left->key;
+ new_left_value = redblack_value(left);
+ new_left_left = redblack_left(left);
+
+ new_key = redblack_right(left)->key;
+ new_value = redblack_value(redblack_right(left));
+ new_left_right = redblack_left(redblack_right(left));
+ new_right_left = redblack_right(redblack_right(left));
+ }
+ else if (redblack_red_p(right) && redblack_red_p(redblack_left(right))) {
+ new_left_key = key;
+ new_left_value = value;
+ new_left_left = left;
+
+ new_right_key = right->key;
+ new_right_value = redblack_value(right);
+ new_right_right = redblack_right(right);
+
+ new_key = redblack_left(right)->key;
+ new_value = redblack_value(redblack_left(right));
+ new_left_right = redblack_left(redblack_left(right));
+ new_right_left = redblack_right(redblack_left(right));
+ }
+ else if (redblack_red_p(right) && redblack_red_p(redblack_right(right))) {
+ new_left_key = key;
+ new_left_value = value;
+ new_left_left = left;
+
+ new_key = right->key;
+ new_value = redblack_value(right);
+ new_left_right = redblack_left(right);
+
+ new_right_key = redblack_right(right)->key;
+ new_right_value = redblack_value(redblack_right(right));
+ new_right_left = redblack_left(redblack_right(right));
+ new_right_right = redblack_right(redblack_right(right));
+ }
+ else {
+ return redblack_new(color, key, value, left, right);
+ }
+
+ RUBY_ASSERT(new_left_key < new_key);
+ RUBY_ASSERT(new_right_key > new_key);
+ RUBY_ASSERT(new_left_left == LEAF || new_left_left->key < new_left_key);
+ RUBY_ASSERT(new_left_right == LEAF || new_left_right->key > new_left_key);
+ RUBY_ASSERT(new_left_right == LEAF || new_left_right->key < new_key);
+ RUBY_ASSERT(new_right_left == LEAF || new_right_left->key < new_right_key);
+ RUBY_ASSERT(new_right_left == LEAF || new_right_left->key > new_key);
+ RUBY_ASSERT(new_right_right == LEAF || new_right_right->key > new_right_key);
+
+ return redblack_new(
+ RED, new_key, new_value,
+ redblack_new(BLACK, new_left_key, new_left_value, new_left_left, new_left_right),
+ redblack_new(BLACK, new_right_key, new_right_value, new_right_left, new_right_right));
+ }
+
+ return redblack_new(color, key, value, left, right);
+}
+
+static redblack_node_t *
+redblack_insert_aux(redblack_node_t * tree, ID key, rb_shape_t * value)
+{
+ if (tree == LEAF) {
+ return redblack_new(RED, key, value, LEAF, LEAF);
+ }
+ else {
+ redblack_node_t *left, *right;
+ if (key < tree->key) {
+ left = redblack_insert_aux(redblack_left(tree), key, value);
+ RUBY_ASSERT(left != LEAF);
+ right = redblack_right(tree);
+ RUBY_ASSERT(right == LEAF || right->key > tree->key);
+ }
+ else if (key > tree->key) {
+ left = redblack_left(tree);
+ RUBY_ASSERT(left == LEAF || left->key < tree->key);
+ right = redblack_insert_aux(redblack_right(tree), key, value);
+ RUBY_ASSERT(right != LEAF);
+ }
+ else {
+ return tree;
+ }
+
+ return redblack_balance(
+ redblack_color(tree),
+ tree->key,
+ redblack_value(tree),
+ left,
+ right
+ );
+ }
+}
+
+static redblack_node_t *
+redblack_force_black(redblack_node_t * node)
+{
+ node->value = redblack_value(node);
+ return node;
+}
+
+static redblack_node_t *
+redblack_insert(redblack_node_t * tree, ID key, rb_shape_t * value)
+{
+ redblack_node_t * root = redblack_insert_aux(tree, key, value);
+
+ if (redblack_red_p(root)) {
+ return redblack_force_black(root);
+ }
+ else {
+ return root;
+ }
+}
+#endif
rb_shape_tree_t *rb_shape_tree_ptr = NULL;
@@ -53,7 +323,7 @@ rb_shape_each_shape(each_shape_callback callback, void *data)
}
}
-RUBY_FUNC_EXPORTED rb_shape_t*
+RUBY_FUNC_EXPORTED rb_shape_t *
rb_shape_get_shape_by_id(shape_id_t shape_id)
{
RUBY_ASSERT(shape_id != INVALID_SHAPE_ID);
@@ -120,7 +390,7 @@ shape_alloc(void)
shape_id_t shape_id = GET_SHAPE_TREE()->next_shape_id;
GET_SHAPE_TREE()->next_shape_id++;
- if (shape_id == MAX_SHAPE_ID) {
+ if (shape_id == (MAX_SHAPE_ID + 1)) {
// TODO: Make an OutOfShapesError ??
rb_bug("Out of shapes");
}
@@ -152,6 +422,41 @@ rb_shape_alloc(ID edge_name, rb_shape_t * parent, enum shape_type type)
return shape;
}
+#ifdef HAVE_MMAP
+static redblack_node_t *
+redblack_cache_ancestors(rb_shape_t * shape)
+{
+ if (!(shape->ancestor_index || shape->parent_id == INVALID_SHAPE_ID)) {
+ redblack_node_t * parent_index;
+
+ parent_index = redblack_cache_ancestors(rb_shape_get_parent(shape));
+
+ if (shape->type == SHAPE_IVAR) {
+ shape->ancestor_index = redblack_insert(parent_index, shape->edge_name, shape);
+
+#if RUBY_DEBUG
+ if (shape->ancestor_index) {
+ redblack_node_t *inserted_node = redblack_find(shape->ancestor_index, shape->edge_name);
+ RUBY_ASSERT(inserted_node);
+ RUBY_ASSERT(redblack_value(inserted_node) == shape);
+ }
+#endif
+ }
+ else {
+ shape->ancestor_index = parent_index;
+ }
+ }
+
+ return shape->ancestor_index;
+}
+#else
+static redblack_node_t *
+redblack_cache_ancestors(rb_shape_t * shape)
+{
+ return LEAF;
+}
+#endif
+
static rb_shape_t *
rb_shape_alloc_new_child(ID id, rb_shape_t * shape, enum shape_type shape_type)
{
@@ -159,16 +464,22 @@ rb_shape_alloc_new_child(ID id, rb_shape_t * shape, enum shape_type shape_type)
switch (shape_type) {
case SHAPE_IVAR:
+ if (UNLIKELY(shape->next_iv_index >= shape->capacity)) {
+ RUBY_ASSERT(shape->next_iv_index == shape->capacity);
+ new_shape->capacity = (uint32_t)rb_malloc_grow_capa(shape->capacity, sizeof(VALUE));
+ }
+ RUBY_ASSERT(new_shape->capacity > shape->next_iv_index);
new_shape->next_iv_index = shape->next_iv_index + 1;
+ if (new_shape->next_iv_index > ANCESTOR_CACHE_THRESHOLD) {
+ redblack_cache_ancestors(new_shape);
+ }
break;
- case SHAPE_CAPACITY_CHANGE:
case SHAPE_FROZEN:
- case SHAPE_T_OBJECT:
new_shape->next_iv_index = shape->next_iv_index;
break;
case SHAPE_OBJ_TOO_COMPLEX:
- case SHAPE_INITIAL_CAPACITY:
case SHAPE_ROOT:
+ case SHAPE_T_OBJECT:
rb_bug("Unreachable");
break;
}
@@ -177,7 +488,7 @@ rb_shape_alloc_new_child(ID id, rb_shape_t * shape, enum shape_type shape_type)
}
static rb_shape_t*
-get_next_shape_internal(rb_shape_t * shape, ID id, enum shape_type shape_type, bool * variation_created, bool new_shapes_allowed, bool new_shape_necessary)
+get_next_shape_internal(rb_shape_t * shape, ID id, enum shape_type shape_type, bool * variation_created, bool new_variations_allowed)
{
rb_shape_t *res = NULL;
@@ -186,56 +497,60 @@ get_next_shape_internal(rb_shape_t * shape, ID id, enum shape_type shape_type, b
*variation_created = false;
- if (new_shape_necessary || (new_shapes_allowed && (shape->next_iv_index < SHAPE_MAX_NUM_IVS))) {
- RB_VM_LOCK_ENTER();
- {
- // If the current shape has children
- if (shape->edges) {
- // Check if it only has one child
- if (SINGLE_CHILD_P(shape->edges)) {
- rb_shape_t * child = SINGLE_CHILD(shape->edges);
- // If the one child has a matching edge name, then great,
- // we found what we want.
- if (child->edge_name == id) {
- res = child;
- }
- else {
- // Otherwise we're going to have to create a new shape
- // and insert it as a child node, so create an id
- // table and insert the existing child
- shape->edges = rb_id_table_create(2);
- rb_id_table_insert(shape->edges, child->edge_name, (VALUE)child);
- }
+ RB_VM_LOCK_ENTER();
+ {
+ // If the current shape has children
+ if (shape->edges) {
+ // Check if it only has one child
+ if (SINGLE_CHILD_P(shape->edges)) {
+ rb_shape_t * child = SINGLE_CHILD(shape->edges);
+ // If the one child has a matching edge name, then great,
+ // we found what we want.
+ if (child->edge_name == id) {
+ res = child;
}
- else {
- // If it has more than one child, do a hash lookup to find it.
- VALUE lookup_result;
- if (rb_id_table_lookup(shape->edges, id, &lookup_result)) {
- res = (rb_shape_t *)lookup_result;
- }
+ }
+ else {
+ // If it has more than one child, do a hash lookup to find it.
+ VALUE lookup_result;
+ if (rb_id_table_lookup(shape->edges, id, &lookup_result)) {
+ res = (rb_shape_t *)lookup_result;
}
+ }
+ }
- // If the shape we were looking for above was found,
- // then `res` will be set to the child. If it isn't set, then
- // we know we need a new child shape, and that we must insert
- // it in to the table.
- if (!res) {
- *variation_created = true;
- rb_shape_t * new_shape = rb_shape_alloc_new_child(id, shape, shape_type);
- rb_id_table_insert(shape->edges, id, (VALUE)new_shape);
- res = new_shape;
- }
+ // If we didn't find the shape we're looking for we create it.
+ if (!res) {
+ // If we're not allowed to create a new variation, of if we're out of shapes
+ // we return TOO_COMPLEX_SHAPE.
+ if (!new_variations_allowed || GET_SHAPE_TREE()->next_shape_id > MAX_SHAPE_ID) {
+ res = rb_shape_get_shape_by_id(OBJ_TOO_COMPLEX_SHAPE_ID);
}
else {
- // If the shape didn't have any outgoing edges, then create
- // the new outgoing edge and tag the pointer.
rb_shape_t * new_shape = rb_shape_alloc_new_child(id, shape, shape_type);
- shape->edges = TAG_SINGLE_CHILD(new_shape);
+
+ if (!shape->edges) {
+ // If the shape had no edge yet, we can directly set the new child
+ shape->edges = TAG_SINGLE_CHILD(new_shape);
+ }
+ else {
+ // If the edge was single child we need to allocate a table.
+ if (SINGLE_CHILD_P(shape->edges)) {
+ rb_shape_t * old_child = SINGLE_CHILD(shape->edges);
+ shape->edges = rb_id_table_create(2);
+ rb_id_table_insert(shape->edges, old_child->edge_name, (VALUE)old_child);
+ }
+
+ rb_id_table_insert(shape->edges, new_shape->edge_name, (VALUE)new_shape);
+ *variation_created = true;
+ }
+
res = new_shape;
}
}
- RB_VM_LOCK_LEAVE();
}
+ RB_VM_LOCK_LEAVE();
+
return res;
}
@@ -245,29 +560,8 @@ rb_shape_frozen_shape_p(rb_shape_t* shape)
return SHAPE_FROZEN == (enum shape_type)shape->type;
}
-static void
-move_iv(VALUE obj, ID id, attr_index_t from, attr_index_t to)
-{
- switch(BUILTIN_TYPE(obj)) {
- case T_CLASS:
- case T_MODULE:
- RCLASS_IVPTR(obj)[to] = RCLASS_IVPTR(obj)[from];
- break;
- case T_OBJECT:
- RUBY_ASSERT(!rb_shape_obj_too_complex(obj));
- ROBJECT_IVPTR(obj)[to] = ROBJECT_IVPTR(obj)[from];
- break;
- default: {
- struct gen_ivtbl *ivtbl;
- rb_gen_ivtbl_get(obj, id, &ivtbl);
- ivtbl->ivptr[to] = ivtbl->ivptr[from];
- break;
- }
- }
-}
-
static rb_shape_t *
-remove_shape_recursive(VALUE obj, ID id, rb_shape_t * shape, VALUE * removed)
+remove_shape_recursive(rb_shape_t *shape, ID id, rb_shape_t **removed_shape)
{
if (shape->parent_id == INVALID_SHAPE_ID) {
// We've hit the top of the shape tree and couldn't find the
@@ -276,43 +570,29 @@ remove_shape_recursive(VALUE obj, ID id, rb_shape_t * shape, VALUE * removed)
}
else {
if (shape->type == SHAPE_IVAR && shape->edge_name == id) {
- // We've hit the edge we wanted to remove, return it's _parent_
- // as the new parent while we go back down the stack.
- attr_index_t index = shape->next_iv_index - 1;
-
- switch(BUILTIN_TYPE(obj)) {
- case T_CLASS:
- case T_MODULE:
- *removed = RCLASS_IVPTR(obj)[index];
- break;
- case T_OBJECT:
- *removed = ROBJECT_IVPTR(obj)[index];
- break;
- default: {
- struct gen_ivtbl *ivtbl;
- rb_gen_ivtbl_get(obj, id, &ivtbl);
- *removed = ivtbl->ivptr[index];
- break;
- }
- }
+ *removed_shape = shape;
+
return rb_shape_get_parent(shape);
}
else {
// This isn't the IV we want to remove, keep walking up.
- rb_shape_t * new_parent = remove_shape_recursive(obj, id, rb_shape_get_parent(shape), removed);
+ rb_shape_t *new_parent = remove_shape_recursive(rb_shape_get_parent(shape), id, removed_shape);
// We found a new parent. Create a child of the new parent that
// has the same attributes as this shape.
if (new_parent) {
+ if (UNLIKELY(new_parent->type == SHAPE_OBJ_TOO_COMPLEX)) {
+ return new_parent;
+ }
+
bool dont_care;
- enum ruby_value_type type = BUILTIN_TYPE(obj);
- bool new_shape_necessary = type != T_OBJECT;
- rb_shape_t * new_child = get_next_shape_internal(new_parent, shape->edge_name, shape->type, &dont_care, true, new_shape_necessary);
- new_child->capacity = shape->capacity;
- if (new_child->type == SHAPE_IVAR) {
- move_iv(obj, id, shape->next_iv_index - 1, new_child->next_iv_index - 1);
+ rb_shape_t *new_child = get_next_shape_internal(new_parent, shape->edge_name, shape->type, &dont_care, true);
+ if (UNLIKELY(new_child->type == SHAPE_OBJ_TOO_COMPLEX)) {
+ return new_child;
}
+ RUBY_ASSERT(new_child->capacity <= shape->capacity);
+
return new_child;
}
else {
@@ -324,16 +604,63 @@ remove_shape_recursive(VALUE obj, ID id, rb_shape_t * shape, VALUE * removed)
}
}
-void
-rb_shape_transition_shape_remove_ivar(VALUE obj, ID id, rb_shape_t *shape, VALUE * removed)
+bool
+rb_shape_transition_shape_remove_ivar(VALUE obj, ID id, rb_shape_t *shape, VALUE *removed)
{
- rb_shape_t * new_shape = remove_shape_recursive(obj, id, shape, removed);
+ if (UNLIKELY(shape->type == SHAPE_OBJ_TOO_COMPLEX)) {
+ return false;
+ }
+
+ rb_shape_t *removed_shape = NULL;
+ rb_shape_t *new_shape = remove_shape_recursive(shape, id, &removed_shape);
if (new_shape) {
+ RUBY_ASSERT(removed_shape != NULL);
+
+ if (UNLIKELY(new_shape->type == SHAPE_OBJ_TOO_COMPLEX)) {
+ return false;
+ }
+
+ RUBY_ASSERT(new_shape->next_iv_index == shape->next_iv_index - 1);
+
+ VALUE *ivptr;
+ switch(BUILTIN_TYPE(obj)) {
+ case T_CLASS:
+ case T_MODULE:
+ ivptr = RCLASS_IVPTR(obj);
+ break;
+ case T_OBJECT:
+ ivptr = ROBJECT_IVPTR(obj);
+ break;
+ default: {
+ struct gen_ivtbl *ivtbl;
+ rb_gen_ivtbl_get(obj, id, &ivtbl);
+ ivptr = ivtbl->as.shape.ivptr;
+ break;
+ }
+ }
+
+ *removed = ivptr[removed_shape->next_iv_index - 1];
+
+ memmove(&ivptr[removed_shape->next_iv_index - 1], &ivptr[removed_shape->next_iv_index],
+ ((new_shape->next_iv_index + 1) - removed_shape->next_iv_index) * sizeof(VALUE));
+
+ // Re-embed objects when instances become small enough
+ // This is necessary because YJIT assumes that objects with the same shape
+ // have the same embeddedness for efficiency (avoid extra checks)
+ if (BUILTIN_TYPE(obj) == T_OBJECT &&
+ !RB_FL_TEST_RAW(obj, ROBJECT_EMBED) &&
+ rb_obj_embedded_size(new_shape->next_iv_index) <= rb_gc_obj_slot_size(obj)) {
+ RB_FL_SET_RAW(obj, ROBJECT_EMBED);
+ memcpy(ROBJECT_IVPTR(obj), ivptr, new_shape->next_iv_index * sizeof(VALUE));
+ xfree(ivptr);
+ }
+
rb_shape_set_shape(obj, new_shape);
}
+ return true;
}
-void
+rb_shape_t *
rb_shape_transition_shape_frozen(VALUE obj)
{
rb_shape_t* shape = rb_shape_get_shape(obj);
@@ -341,21 +668,20 @@ rb_shape_transition_shape_frozen(VALUE obj)
RUBY_ASSERT(RB_OBJ_FROZEN(obj));
if (rb_shape_frozen_shape_p(shape) || rb_shape_obj_too_complex(obj)) {
- return;
+ return shape;
}
rb_shape_t* next_shape;
if (shape == rb_shape_get_root_shape()) {
- rb_shape_set_shape_id(obj, SPECIAL_CONST_SHAPE_ID);
- return;
+ return rb_shape_get_shape_by_id(SPECIAL_CONST_SHAPE_ID);
}
bool dont_care;
- next_shape = get_next_shape_internal(shape, (ID)id_frozen, SHAPE_FROZEN, &dont_care, true, false);
+ next_shape = get_next_shape_internal(shape, (ID)id_frozen, SHAPE_FROZEN, &dont_care, true);
RUBY_ASSERT(next_shape);
- rb_shape_set_shape(obj, next_shape);
+ return next_shape;
}
/*
@@ -367,13 +693,23 @@ rb_shape_get_next_iv_shape(rb_shape_t* shape, ID id)
{
RUBY_ASSERT(!is_instance_id(id) || RTEST(rb_sym2str(ID2SYM(id))));
bool dont_care;
- return get_next_shape_internal(shape, id, SHAPE_IVAR, &dont_care, true, false);
+ return get_next_shape_internal(shape, id, SHAPE_IVAR, &dont_care, true);
}
rb_shape_t *
-rb_shape_get_next(rb_shape_t* shape, VALUE obj, ID id)
+rb_shape_get_next(rb_shape_t *shape, VALUE obj, ID id)
{
RUBY_ASSERT(!is_instance_id(id) || RTEST(rb_sym2str(ID2SYM(id))));
+ if (UNLIKELY(shape->type == SHAPE_OBJ_TOO_COMPLEX)) {
+ return shape;
+ }
+
+#if RUBY_DEBUG
+ attr_index_t index;
+ if (rb_shape_get_iv_index(shape, id, &index)) {
+ rb_bug("rb_shape_get_next: trying to create ivar that already exists at index %u", index);
+ }
+#endif
bool allow_new_shape = true;
@@ -383,14 +719,7 @@ rb_shape_get_next(rb_shape_t* shape, VALUE obj, ID id)
}
bool variation_created = false;
- // For non T_OBJECTS, force a new shape
- bool new_shape_necessary = BUILTIN_TYPE(obj) != T_OBJECT;
- rb_shape_t * new_shape = get_next_shape_internal(shape, id, SHAPE_IVAR, &variation_created, allow_new_shape, new_shape_necessary);
-
- if (!new_shape) {
- RUBY_ASSERT(BUILTIN_TYPE(obj) == T_OBJECT);
- new_shape = rb_shape_get_shape_by_id(OBJ_TOO_COMPLEX_SHAPE_ID);
- }
+ rb_shape_t *new_shape = get_next_shape_internal(shape, id, SHAPE_IVAR, &variation_created, allow_new_shape);
// Check if we should update max_iv_count on the object's class
if (BUILTIN_TYPE(obj) == T_OBJECT) {
@@ -405,9 +734,10 @@ rb_shape_get_next(rb_shape_t* shape, VALUE obj, ID id)
if (RCLASS_EXT(klass)->variation_count >= SHAPE_MAX_VARIATIONS) {
rb_category_warn(
RB_WARN_CATEGORY_PERFORMANCE,
- "Maximum shapes variations (%d) reached by %"PRIsVALUE", instance variables accesses will be slower.",
- SHAPE_MAX_VARIATIONS,
- rb_class_path(klass)
+ "The class %"PRIsVALUE" reached %d shape variations, instance variables accesses will be slower and memory usage increased.\n"
+ "It is recommended to define instance variables in a consistent order, for instance by eagerly defining them all in the #initialize method.",
+ rb_class_path(klass),
+ SHAPE_MAX_VARIATIONS
);
}
}
@@ -417,23 +747,65 @@ rb_shape_get_next(rb_shape_t* shape, VALUE obj, ID id)
return new_shape;
}
-rb_shape_t *
-rb_shape_transition_shape_capa(rb_shape_t* shape, uint32_t new_capacity)
+// Same as rb_shape_get_iv_index, but uses a provided valid shape id and index
+// to return a result faster if branches of the shape tree are closely related.
+bool
+rb_shape_get_iv_index_with_hint(shape_id_t shape_id, ID id, attr_index_t *value, shape_id_t *shape_id_hint)
{
- ID edge_name = rb_make_temporary_id(new_capacity);
- bool dont_care;
- rb_shape_t * new_shape = get_next_shape_internal(shape, edge_name, SHAPE_CAPACITY_CHANGE, &dont_care, true, false);
- new_shape->capacity = new_capacity;
- return new_shape;
+ attr_index_t index_hint = *value;
+ rb_shape_t *shape = rb_shape_get_shape_by_id(shape_id);
+ rb_shape_t *initial_shape = shape;
+
+ if (*shape_id_hint == INVALID_SHAPE_ID) {
+ *shape_id_hint = shape_id;
+ return rb_shape_get_iv_index(shape, id, value);
+ }
+
+ rb_shape_t * shape_hint = rb_shape_get_shape_by_id(*shape_id_hint);
+
+ // We assume it's likely shape_id_hint and shape_id have a close common
+ // ancestor, so we check up to ANCESTOR_SEARCH_MAX_DEPTH ancestors before
+ // eventually using the index, as in case of a match it will be faster.
+ // However if the shape doesn't have an index, we walk the entire tree.
+ int depth = INT_MAX;
+ if (shape->ancestor_index && shape->next_iv_index >= ANCESTOR_CACHE_THRESHOLD) {
+ depth = ANCESTOR_SEARCH_MAX_DEPTH;
+ }
+
+ while (depth > 0 && shape->next_iv_index > index_hint) {
+ while (shape_hint->next_iv_index > shape->next_iv_index) {
+ shape_hint = rb_shape_get_parent(shape_hint);
+ }
+
+ if (shape_hint == shape) {
+ // We've found a common ancestor so use the index hint
+ *value = index_hint;
+ *shape_id_hint = rb_shape_id(shape);
+ return true;
+ }
+ if (shape->edge_name == id) {
+ // We found the matching id before a common ancestor
+ *value = shape->next_iv_index - 1;
+ *shape_id_hint = rb_shape_id(shape);
+ return true;
+ }
+
+ shape = rb_shape_get_parent(shape);
+ depth--;
+ }
+
+ // If the original shape had an index but its ancestor doesn't
+ // we switch back to the original one as it will be faster.
+ if (!shape->ancestor_index && initial_shape->ancestor_index) {
+ shape = initial_shape;
+ }
+ *shape_id_hint = shape_id;
+ return rb_shape_get_iv_index(shape, id, value);
}
-bool
-rb_shape_get_iv_index(rb_shape_t * shape, ID id, attr_index_t *value)
+static bool
+shape_get_iv_index(rb_shape_t *shape, ID id, attr_index_t *value)
{
- // It doesn't make sense to ask for the index of an IV that's stored
- // on an object that is "too complex" as it uses a hash for storing IVs
- RUBY_ASSERT(rb_shape_id(shape) != OBJ_TOO_COMPLEX_SHAPE_ID);
-
while (shape->parent_id != INVALID_SHAPE_ID) {
if (shape->edge_name == id) {
enum shape_type shape_type;
@@ -444,9 +816,7 @@ 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_ROOT:
- case SHAPE_INITIAL_CAPACITY:
case SHAPE_T_OBJECT:
return false;
case SHAPE_OBJ_TOO_COMPLEX:
@@ -454,11 +824,59 @@ rb_shape_get_iv_index(rb_shape_t * shape, ID id, attr_index_t *value)
rb_bug("Ivar should not exist on transition");
}
}
+
shape = rb_shape_get_parent(shape);
}
+
return false;
}
+static bool
+shape_cache_get_iv_index(rb_shape_t *shape, ID id, attr_index_t *value)
+{
+ if (shape->ancestor_index && shape->next_iv_index >= ANCESTOR_CACHE_THRESHOLD) {
+ redblack_node_t *node = redblack_find(shape->ancestor_index, id);
+ if (node) {
+ rb_shape_t *shape = redblack_value(node);
+ *value = shape->next_iv_index - 1;
+
+#if RUBY_DEBUG
+ attr_index_t shape_tree_index;
+ RUBY_ASSERT(shape_get_iv_index(shape, id, &shape_tree_index));
+ RUBY_ASSERT(shape_tree_index == *value);
+#endif
+
+ return true;
+ }
+
+ /* Verify the cache is correct by checking that this instance variable
+ * does not exist in the shape tree either. */
+ RUBY_ASSERT(!shape_get_iv_index(shape, id, value));
+ }
+
+ return false;
+}
+
+bool
+rb_shape_get_iv_index(rb_shape_t *shape, ID id, attr_index_t *value)
+{
+ // It doesn't make sense to ask for the index of an IV that's stored
+ // on an object that is "too complex" as it uses a hash for storing IVs
+ RUBY_ASSERT(rb_shape_id(shape) != OBJ_TOO_COMPLEX_SHAPE_ID);
+
+ if (!shape_cache_get_iv_index(shape, id, value)) {
+ // If it wasn't in the ancestor cache, then don't do a linear search
+ if (shape->ancestor_index && shape->next_iv_index >= ANCESTOR_CACHE_THRESHOLD) {
+ return false;
+ }
+ else {
+ return shape_get_iv_index(shape, id, value);
+ }
+ }
+
+ return true;
+}
+
void
rb_shape_set_shape(VALUE obj, rb_shape_t* shape)
{
@@ -511,8 +929,6 @@ rb_shape_traverse_from_new_root(rb_shape_t *initial_shape, rb_shape_t *dest_shap
}
break;
case SHAPE_ROOT:
- case SHAPE_CAPACITY_CHANGE:
- case SHAPE_INITIAL_CAPACITY:
case SHAPE_T_OBJECT:
break;
case SHAPE_OBJ_TOO_COMPLEX:
@@ -526,12 +942,18 @@ rb_shape_traverse_from_new_root(rb_shape_t *initial_shape, rb_shape_t *dest_shap
rb_shape_t *
rb_shape_rebuild_shape(rb_shape_t * initial_shape, rb_shape_t * dest_shape)
{
+ RUBY_ASSERT(rb_shape_id(initial_shape) != OBJ_TOO_COMPLEX_SHAPE_ID);
+ RUBY_ASSERT(rb_shape_id(dest_shape) != OBJ_TOO_COMPLEX_SHAPE_ID);
+
rb_shape_t * midway_shape;
RUBY_ASSERT(initial_shape->type == SHAPE_T_OBJECT);
if (dest_shape->type != initial_shape->type) {
midway_shape = rb_shape_rebuild_shape(initial_shape, rb_shape_get_parent(dest_shape));
+ if (UNLIKELY(rb_shape_id(midway_shape) == OBJ_TOO_COMPLEX_SHAPE_ID)) {
+ return midway_shape;
+ }
}
else {
midway_shape = initial_shape;
@@ -539,17 +961,10 @@ rb_shape_rebuild_shape(rb_shape_t * initial_shape, rb_shape_t * dest_shape)
switch ((enum shape_type)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_ROOT:
case SHAPE_FROZEN:
- case SHAPE_CAPACITY_CHANGE:
- case SHAPE_INITIAL_CAPACITY:
case SHAPE_T_OBJECT:
break;
case SHAPE_OBJ_TOO_COMPLEX:
@@ -566,14 +981,6 @@ rb_shape_obj_too_complex(VALUE obj)
return rb_shape_get_shape_id(obj) == OBJ_TOO_COMPLEX_SHAPE_ID;
}
-void
-rb_shape_set_too_complex(VALUE obj)
-{
- RUBY_ASSERT(BUILTIN_TYPE(obj) == T_OBJECT);
- RUBY_ASSERT(!rb_shape_obj_too_complex(obj));
- rb_shape_set_shape_id(obj, OBJ_TOO_COMPLEX_SHAPE_ID);
-}
-
size_t
rb_shape_edges_count(rb_shape_t *shape)
{
@@ -724,6 +1131,23 @@ rb_shape_root_shape(VALUE self)
return rb_shape_t_to_rb_cShape(rb_shape_get_root_shape());
}
+/* :nodoc: */
+static VALUE
+rb_shape_shapes_available(VALUE self)
+{
+ return INT2NUM(MAX_SHAPE_ID - (GET_SHAPE_TREE()->next_shape_id - 1));
+}
+
+/* :nodoc: */
+static VALUE
+rb_shape_exhaust(int argc, VALUE *argv, VALUE self)
+{
+ rb_check_arity(argc, 0, 1);
+ int offset = argc == 1 ? NUM2INT(argv[0]) : 0;
+ GET_SHAPE_TREE()->next_shape_id = MAX_SHAPE_ID - offset + 1;
+ return Qnil;
+}
+
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)
@@ -791,9 +1215,7 @@ rb_shape_find_by_id(VALUE mod, VALUE id)
void
Init_default_shapes(void)
{
- rb_shape_tree_t *st = ruby_mimmalloc(sizeof(rb_shape_tree_t));
- memset(st, 0, sizeof(rb_shape_tree_t));
- rb_shape_tree_ptr = st;
+ rb_shape_tree_ptr = xcalloc(1, sizeof(rb_shape_tree_t));
#ifdef HAVE_MMAP
rb_shape_tree_ptr->shape_list = (rb_shape_t *)mmap(NULL, rb_size_mul_or_raise(SHAPE_BUFFER_SIZE, sizeof(rb_shape_t), rb_eRuntimeError),
@@ -812,53 +1234,55 @@ Init_default_shapes(void)
id_frozen = rb_make_internal_id();
id_t_object = 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();
+#ifdef HAVE_MMAP
+ rb_shape_tree_ptr->shape_cache = (redblack_node_t *)mmap(NULL, rb_size_mul_or_raise(REDBLACK_CACHE_SIZE, sizeof(redblack_node_t), rb_eRuntimeError),
+ PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ rb_shape_tree_ptr->cache_size = 0;
+
+ // If mmap fails, then give up on the redblack tree cache.
+ // We set the cache size such that the redblack node allocators think
+ // the cache is full.
+ if (GET_SHAPE_TREE()->shape_cache == MAP_FAILED) {
+ GET_SHAPE_TREE()->shape_cache = 0;
+ GET_SHAPE_TREE()->cache_size = REDBLACK_CACHE_SIZE;
}
+#endif
// 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));
+ rb_shape_t *root = rb_shape_alloc_with_parent_id(0, INVALID_SHAPE_ID);
+ root->capacity = 0;
root->type = SHAPE_ROOT;
root->size_pool_index = 0;
GET_SHAPE_TREE()->root_shape = root;
RUBY_ASSERT(rb_shape_id(GET_SHAPE_TREE()->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);
- }
-
- // Make shapes for T_OBJECT
- for (int i = 0; i < SIZE_POOL_COUNT; i++) {
- rb_shape_t * shape = rb_shape_get_shape_by_id(i);
- bool dont_care;
- rb_shape_t * t_object_shape =
- get_next_shape_internal(shape, id_t_object, SHAPE_T_OBJECT, &dont_care, true, false);
- t_object_shape->edges = rb_id_table_create(0);
- RUBY_ASSERT(rb_shape_id(t_object_shape) == (shape_id_t)(i + SIZE_POOL_COUNT));
- }
-
bool dont_care;
// Special const shape
#if RUBY_DEBUG
- rb_shape_t * special_const_shape =
+ rb_shape_t *special_const_shape =
#endif
- get_next_shape_internal(root, (ID)id_frozen, SHAPE_FROZEN, &dont_care, true, false);
+ get_next_shape_internal(root, (ID)id_frozen, SHAPE_FROZEN, &dont_care, true);
RUBY_ASSERT(rb_shape_id(special_const_shape) == SPECIAL_CONST_SHAPE_ID);
RUBY_ASSERT(SPECIAL_CONST_SHAPE_ID == (GET_SHAPE_TREE()->next_shape_id - 1));
RUBY_ASSERT(rb_shape_frozen_shape_p(special_const_shape));
- rb_shape_t * hash_fallback_shape = rb_shape_alloc_with_parent_id(0, ROOT_SHAPE_ID);
- hash_fallback_shape->type = SHAPE_OBJ_TOO_COMPLEX;
- hash_fallback_shape->size_pool_index = 0;
+ rb_shape_t *too_complex_shape = rb_shape_alloc_with_parent_id(0, ROOT_SHAPE_ID);
+ too_complex_shape->type = SHAPE_OBJ_TOO_COMPLEX;
+ too_complex_shape->size_pool_index = 0;
RUBY_ASSERT(OBJ_TOO_COMPLEX_SHAPE_ID == (GET_SHAPE_TREE()->next_shape_id - 1));
- RUBY_ASSERT(rb_shape_id(hash_fallback_shape) == OBJ_TOO_COMPLEX_SHAPE_ID);
+ RUBY_ASSERT(rb_shape_id(too_complex_shape) == OBJ_TOO_COMPLEX_SHAPE_ID);
+
+ // Make shapes for T_OBJECT
+ size_t *sizes = rb_gc_size_pool_sizes();
+ for (int i = 0; sizes[i] > 0; i++) {
+ rb_shape_t *t_object_shape = rb_shape_alloc_with_parent_id(0, INVALID_SHAPE_ID);
+ t_object_shape->type = SHAPE_T_OBJECT;
+ t_object_shape->size_pool_index = i;
+ t_object_shape->capacity = (uint32_t)((sizes[i] - offsetof(struct RObject, as.ary)) / sizeof(VALUE));
+ t_object_shape->edges = rb_id_table_create(0);
+ t_object_shape->ancestor_index = LEAF;
+ RUBY_ASSERT(rb_shape_id(t_object_shape) == (shape_id_t)(i + FIRST_T_OBJECT_SHAPE_ID));
+ }
}
void
@@ -887,12 +1311,18 @@ Init_shape(void)
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_const(rb_cShape, "OBJ_TOO_COMPLEX_SHAPE_ID", INT2NUM(OBJ_TOO_COMPLEX_SHAPE_ID));
+ rb_define_const(rb_cShape, "FIRST_T_OBJECT_SHAPE_ID", INT2NUM(FIRST_T_OBJECT_SHAPE_ID));
rb_define_const(rb_cShape, "SHAPE_MAX_VARIATIONS", INT2NUM(SHAPE_MAX_VARIATIONS));
- rb_define_const(rb_cShape, "SHAPE_MAX_NUM_IVS", INT2NUM(SHAPE_MAX_NUM_IVS));
+ rb_define_const(rb_cShape, "SIZEOF_RB_SHAPE_T", INT2NUM(sizeof(rb_shape_t)));
+ rb_define_const(rb_cShape, "SIZEOF_REDBLACK_NODE_T", INT2NUM(sizeof(redblack_node_t)));
+ rb_define_const(rb_cShape, "SHAPE_BUFFER_SIZE", INT2NUM(sizeof(rb_shape_t) * SHAPE_BUFFER_SIZE));
+ rb_define_const(rb_cShape, "REDBLACK_CACHE_SIZE", INT2NUM(sizeof(redblack_node_t) * REDBLACK_CACHE_SIZE));
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, "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, "shapes_available", rb_shape_shapes_available, 0);
+ rb_define_singleton_method(rb_cShape, "exhaust_shapes", rb_shape_exhaust, -1);
#endif
}