Get rid of frozen shapes.
Instead `shape_id_t` higher bits contain flags, and the first one tells whether the shape is frozen. This has multiple benefits: - Can check if a shape is frozen with a single bit check instead of dereferencing a pointer. - Guarantees it is always possible to transition to frozen. - This allow reclaiming `FL_FREEZE` (not done yet). The downside is you have to be careful to preserve these flags when transitioning.
This commit is contained in:
parent
6b7e3395a4
commit
625d6a9cbb
Notes:
git
2025-06-04 05:59:36 +00:00
@ -817,9 +817,6 @@ shape_id_i(shape_id_t shape_id, void *data)
|
|||||||
dump_append(dc, ",\"edge_name\":");
|
dump_append(dc, ",\"edge_name\":");
|
||||||
dump_append_id(dc, shape->edge_name);
|
dump_append_id(dc, shape->edge_name);
|
||||||
|
|
||||||
break;
|
|
||||||
case SHAPE_FROZEN:
|
|
||||||
dump_append(dc, ", \"shape_type\":\"FROZEN\"");
|
|
||||||
break;
|
break;
|
||||||
case SHAPE_T_OBJECT:
|
case SHAPE_T_OBJECT:
|
||||||
dump_append(dc, ", \"shape_type\":\"T_OBJECT\"");
|
dump_append(dc, ", \"shape_type\":\"T_OBJECT\"");
|
||||||
|
@ -297,7 +297,6 @@ static inline void RCLASS_WRITE_CLASSPATH(VALUE klass, VALUE classpath, bool per
|
|||||||
#define RCLASS_PRIME_CLASSEXT_WRITABLE FL_USER2
|
#define RCLASS_PRIME_CLASSEXT_WRITABLE FL_USER2
|
||||||
#define RCLASS_IS_INITIALIZED FL_USER3
|
#define RCLASS_IS_INITIALIZED FL_USER3
|
||||||
// 3 is RMODULE_IS_REFINEMENT for RMODULE
|
// 3 is RMODULE_IS_REFINEMENT for RMODULE
|
||||||
// 4-19: SHAPE_FLAG_MASK
|
|
||||||
|
|
||||||
/* class.c */
|
/* class.c */
|
||||||
rb_classext_t * rb_class_duplicate_classext(rb_classext_t *orig, VALUE obj, const rb_namespace_t *ns);
|
rb_classext_t * rb_class_duplicate_classext(rb_classext_t *orig, VALUE obj, const rb_namespace_t *ns);
|
||||||
|
16
object.c
16
object.c
@ -496,12 +496,7 @@ rb_obj_clone_setup(VALUE obj, VALUE clone, VALUE kwfreeze)
|
|||||||
|
|
||||||
if (RB_OBJ_FROZEN(obj)) {
|
if (RB_OBJ_FROZEN(obj)) {
|
||||||
shape_id_t next_shape_id = rb_shape_transition_frozen(clone);
|
shape_id_t next_shape_id = rb_shape_transition_frozen(clone);
|
||||||
if (!rb_shape_obj_too_complex_p(clone) && rb_shape_too_complex_p(next_shape_id)) {
|
rb_obj_set_shape_id(clone, next_shape_id);
|
||||||
rb_evict_ivars_to_hash(clone);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
rb_obj_set_shape_id(clone, next_shape_id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Qtrue: {
|
case Qtrue: {
|
||||||
@ -518,14 +513,7 @@ rb_obj_clone_setup(VALUE obj, VALUE clone, VALUE kwfreeze)
|
|||||||
rb_funcallv_kw(clone, id_init_clone, 2, argv, RB_PASS_KEYWORDS);
|
rb_funcallv_kw(clone, id_init_clone, 2, argv, RB_PASS_KEYWORDS);
|
||||||
RBASIC(clone)->flags |= FL_FREEZE;
|
RBASIC(clone)->flags |= FL_FREEZE;
|
||||||
shape_id_t next_shape_id = rb_shape_transition_frozen(clone);
|
shape_id_t next_shape_id = rb_shape_transition_frozen(clone);
|
||||||
// If we're out of shapes, but we want to freeze, then we need to
|
rb_obj_set_shape_id(clone, next_shape_id);
|
||||||
// evacuate this clone to a hash
|
|
||||||
if (!rb_shape_obj_too_complex_p(clone) && rb_shape_too_complex_p(next_shape_id)) {
|
|
||||||
rb_evict_ivars_to_hash(clone);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
rb_obj_set_shape_id(clone, next_shape_id);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Qfalse: {
|
case Qfalse: {
|
||||||
|
175
shape.c
175
shape.c
@ -20,17 +20,7 @@
|
|||||||
#define SHAPE_DEBUG (VM_CHECK_MODE > 0)
|
#define SHAPE_DEBUG (VM_CHECK_MODE > 0)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if SIZEOF_SHAPE_T == 4
|
#define ROOT_TOO_COMPLEX_SHAPE_ID 0x1
|
||||||
#if RUBY_DEBUG
|
|
||||||
#define SHAPE_BUFFER_SIZE 0x8000
|
|
||||||
#else
|
|
||||||
#define SHAPE_BUFFER_SIZE 0x80000
|
|
||||||
#endif
|
|
||||||
#else
|
|
||||||
#define SHAPE_BUFFER_SIZE 0x8000
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define ROOT_TOO_COMPLEX_SHAPE_ID 0x2
|
|
||||||
|
|
||||||
#define REDBLACK_CACHE_SIZE (SHAPE_BUFFER_SIZE * 32)
|
#define REDBLACK_CACHE_SIZE (SHAPE_BUFFER_SIZE * 32)
|
||||||
|
|
||||||
@ -53,14 +43,6 @@ ID ruby_internal_object_id; // extern
|
|||||||
#define BLACK 0x0
|
#define BLACK 0x0
|
||||||
#define RED 0x1
|
#define RED 0x1
|
||||||
|
|
||||||
enum shape_flags {
|
|
||||||
SHAPE_FL_FROZEN = 1 << 0,
|
|
||||||
SHAPE_FL_HAS_OBJECT_ID = 1 << 1,
|
|
||||||
SHAPE_FL_TOO_COMPLEX = 1 << 2,
|
|
||||||
|
|
||||||
SHAPE_FL_NON_CANONICAL_MASK = SHAPE_FL_FROZEN | SHAPE_FL_HAS_OBJECT_ID,
|
|
||||||
};
|
|
||||||
|
|
||||||
static redblack_node_t *
|
static redblack_node_t *
|
||||||
redblack_left(redblack_node_t *node)
|
redblack_left(redblack_node_t *node)
|
||||||
{
|
{
|
||||||
@ -373,7 +355,7 @@ static const rb_data_type_t shape_tree_type = {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
static inline shape_id_t
|
static inline shape_id_t
|
||||||
rb_shape_id(rb_shape_t *shape)
|
raw_shape_id(rb_shape_t *shape)
|
||||||
{
|
{
|
||||||
if (shape == NULL) {
|
if (shape == NULL) {
|
||||||
return INVALID_SHAPE_ID;
|
return INVALID_SHAPE_ID;
|
||||||
@ -381,6 +363,24 @@ rb_shape_id(rb_shape_t *shape)
|
|||||||
return (shape_id_t)(shape - GET_SHAPE_TREE()->shape_list);
|
return (shape_id_t)(shape - GET_SHAPE_TREE()->shape_list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline shape_id_t
|
||||||
|
shape_id(rb_shape_t *shape, shape_id_t previous_shape_id)
|
||||||
|
{
|
||||||
|
if (shape == NULL) {
|
||||||
|
return INVALID_SHAPE_ID;
|
||||||
|
}
|
||||||
|
shape_id_t raw_id = (shape_id_t)(shape - GET_SHAPE_TREE()->shape_list);
|
||||||
|
return raw_id | (previous_shape_id & SHAPE_ID_FLAGS_MASK);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if RUBY_DEBUG
|
||||||
|
static inline bool
|
||||||
|
shape_frozen_p(shape_id_t shape_id)
|
||||||
|
{
|
||||||
|
return shape_id & SHAPE_ID_FL_FROZEN;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
static inline bool
|
static inline bool
|
||||||
shape_too_complex_p(rb_shape_t *shape)
|
shape_too_complex_p(rb_shape_t *shape)
|
||||||
{
|
{
|
||||||
@ -402,9 +402,10 @@ rb_shape_each_shape_id(each_shape_callback callback, void *data)
|
|||||||
RUBY_FUNC_EXPORTED rb_shape_t *
|
RUBY_FUNC_EXPORTED rb_shape_t *
|
||||||
rb_shape_lookup(shape_id_t shape_id)
|
rb_shape_lookup(shape_id_t shape_id)
|
||||||
{
|
{
|
||||||
RUBY_ASSERT(shape_id != INVALID_SHAPE_ID);
|
uint32_t offset = (shape_id & SHAPE_ID_OFFSET_MASK);
|
||||||
|
RUBY_ASSERT(offset != INVALID_SHAPE_ID);
|
||||||
|
|
||||||
return &GET_SHAPE_TREE()->shape_list[shape_id];
|
return &GET_SHAPE_TREE()->shape_list[offset];
|
||||||
}
|
}
|
||||||
|
|
||||||
RUBY_FUNC_EXPORTED shape_id_t
|
RUBY_FUNC_EXPORTED shape_id_t
|
||||||
@ -466,7 +467,7 @@ rb_shape_alloc_with_parent_id(ID edge_name, shape_id_t parent_id)
|
|||||||
static rb_shape_t *
|
static rb_shape_t *
|
||||||
rb_shape_alloc(ID edge_name, rb_shape_t *parent, enum shape_type type)
|
rb_shape_alloc(ID edge_name, rb_shape_t *parent, enum shape_type type)
|
||||||
{
|
{
|
||||||
rb_shape_t *shape = rb_shape_alloc_with_parent_id(edge_name, rb_shape_id(parent));
|
rb_shape_t *shape = rb_shape_alloc_with_parent_id(edge_name, raw_shape_id(parent));
|
||||||
shape->type = (uint8_t)type;
|
shape->type = (uint8_t)type;
|
||||||
shape->flags = parent->flags;
|
shape->flags = parent->flags;
|
||||||
shape->heap_index = parent->heap_index;
|
shape->heap_index = parent->heap_index;
|
||||||
@ -530,10 +531,6 @@ rb_shape_alloc_new_child(ID id, rb_shape_t *shape, enum shape_type shape_type)
|
|||||||
redblack_cache_ancestors(new_shape);
|
redblack_cache_ancestors(new_shape);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SHAPE_FROZEN:
|
|
||||||
new_shape->next_field_index = shape->next_field_index;
|
|
||||||
new_shape->flags |= SHAPE_FL_FROZEN;
|
|
||||||
break;
|
|
||||||
case SHAPE_OBJ_TOO_COMPLEX:
|
case SHAPE_OBJ_TOO_COMPLEX:
|
||||||
case SHAPE_ROOT:
|
case SHAPE_ROOT:
|
||||||
case SHAPE_T_OBJECT:
|
case SHAPE_T_OBJECT:
|
||||||
@ -626,8 +623,8 @@ retry:
|
|||||||
static rb_shape_t *
|
static rb_shape_t *
|
||||||
get_next_shape_internal(rb_shape_t *shape, ID id, enum shape_type shape_type, bool *variation_created, bool new_variations_allowed)
|
get_next_shape_internal(rb_shape_t *shape, ID id, enum shape_type shape_type, bool *variation_created, bool new_variations_allowed)
|
||||||
{
|
{
|
||||||
// There should never be outgoing edges from "too complex", except for SHAPE_FROZEN and SHAPE_OBJ_ID
|
// There should never be outgoing edges from "too complex", except for SHAPE_OBJ_ID
|
||||||
RUBY_ASSERT(!shape_too_complex_p(shape) || shape_type == SHAPE_FROZEN || shape_type == SHAPE_OBJ_ID);
|
RUBY_ASSERT(!shape_too_complex_p(shape) || shape_type == SHAPE_OBJ_ID);
|
||||||
|
|
||||||
if (rb_multi_ractor_p()) {
|
if (rb_multi_ractor_p()) {
|
||||||
return get_next_shape_internal_atomic(shape, id, shape_type, variation_created, new_variations_allowed);
|
return get_next_shape_internal_atomic(shape, id, shape_type, variation_created, new_variations_allowed);
|
||||||
@ -692,12 +689,6 @@ get_next_shape_internal(rb_shape_t *shape, ID id, enum shape_type shape_type, bo
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool
|
|
||||||
shape_frozen_p(rb_shape_t *shape)
|
|
||||||
{
|
|
||||||
return SHAPE_FL_FROZEN & shape->flags;
|
|
||||||
}
|
|
||||||
|
|
||||||
static rb_shape_t *
|
static rb_shape_t *
|
||||||
remove_shape_recursive(rb_shape_t *shape, ID id, rb_shape_t **removed_shape)
|
remove_shape_recursive(rb_shape_t *shape, ID id, rb_shape_t **removed_shape)
|
||||||
{
|
{
|
||||||
@ -749,12 +740,13 @@ rb_shape_transition_remove_ivar(VALUE obj, ID id, shape_id_t *removed_shape_id)
|
|||||||
rb_shape_t *shape = RSHAPE(shape_id);
|
rb_shape_t *shape = RSHAPE(shape_id);
|
||||||
|
|
||||||
RUBY_ASSERT(!shape_too_complex_p(shape));
|
RUBY_ASSERT(!shape_too_complex_p(shape));
|
||||||
|
RUBY_ASSERT(!shape_frozen_p(shape_id));
|
||||||
|
|
||||||
rb_shape_t *removed_shape = NULL;
|
rb_shape_t *removed_shape = NULL;
|
||||||
rb_shape_t *new_shape = remove_shape_recursive(shape, id, &removed_shape);
|
rb_shape_t *new_shape = remove_shape_recursive(shape, id, &removed_shape);
|
||||||
if (new_shape) {
|
if (new_shape) {
|
||||||
*removed_shape_id = rb_shape_id(removed_shape);
|
*removed_shape_id = raw_shape_id(removed_shape);
|
||||||
return rb_shape_id(new_shape);
|
return raw_shape_id(new_shape);
|
||||||
}
|
}
|
||||||
return shape_id;
|
return shape_id;
|
||||||
}
|
}
|
||||||
@ -765,22 +757,7 @@ rb_shape_transition_frozen(VALUE obj)
|
|||||||
RUBY_ASSERT(RB_OBJ_FROZEN(obj));
|
RUBY_ASSERT(RB_OBJ_FROZEN(obj));
|
||||||
|
|
||||||
shape_id_t shape_id = rb_obj_shape_id(obj);
|
shape_id_t shape_id = rb_obj_shape_id(obj);
|
||||||
if (shape_id == ROOT_SHAPE_ID) {
|
return shape_id | SHAPE_ID_FL_FROZEN;
|
||||||
return SPECIAL_CONST_SHAPE_ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
rb_shape_t *shape = RSHAPE(shape_id);
|
|
||||||
RUBY_ASSERT(shape);
|
|
||||||
|
|
||||||
if (shape_frozen_p(shape)) {
|
|
||||||
return shape_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool dont_care;
|
|
||||||
rb_shape_t *next_shape = get_next_shape_internal(shape, id_frozen, SHAPE_FROZEN, &dont_care, true);
|
|
||||||
|
|
||||||
RUBY_ASSERT(next_shape);
|
|
||||||
return rb_shape_id(next_shape);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static rb_shape_t *
|
static rb_shape_t *
|
||||||
@ -788,11 +765,6 @@ shape_transition_too_complex(rb_shape_t *original_shape)
|
|||||||
{
|
{
|
||||||
rb_shape_t *next_shape = RSHAPE(ROOT_TOO_COMPLEX_SHAPE_ID);
|
rb_shape_t *next_shape = RSHAPE(ROOT_TOO_COMPLEX_SHAPE_ID);
|
||||||
|
|
||||||
if (original_shape->flags & SHAPE_FL_FROZEN) {
|
|
||||||
bool dont_care;
|
|
||||||
next_shape = get_next_shape_internal(next_shape, id_frozen, SHAPE_FROZEN, &dont_care, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (original_shape->flags & SHAPE_FL_HAS_OBJECT_ID) {
|
if (original_shape->flags & SHAPE_FL_HAS_OBJECT_ID) {
|
||||||
bool dont_care;
|
bool dont_care;
|
||||||
next_shape = get_next_shape_internal(next_shape, ruby_internal_object_id, SHAPE_OBJ_ID, &dont_care, false);
|
next_shape = get_next_shape_internal(next_shape, ruby_internal_object_id, SHAPE_OBJ_ID, &dont_care, false);
|
||||||
@ -804,8 +776,8 @@ shape_transition_too_complex(rb_shape_t *original_shape)
|
|||||||
shape_id_t
|
shape_id_t
|
||||||
rb_shape_transition_complex(VALUE obj)
|
rb_shape_transition_complex(VALUE obj)
|
||||||
{
|
{
|
||||||
rb_shape_t *original_shape = obj_shape(obj);
|
shape_id_t original_shape_id = RBASIC_SHAPE_ID(obj);
|
||||||
return rb_shape_id(shape_transition_too_complex(original_shape));
|
return shape_id(shape_transition_too_complex(RSHAPE(original_shape_id)), original_shape_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool
|
static inline bool
|
||||||
@ -823,7 +795,9 @@ rb_shape_has_object_id(shape_id_t shape_id)
|
|||||||
shape_id_t
|
shape_id_t
|
||||||
rb_shape_transition_object_id(VALUE obj)
|
rb_shape_transition_object_id(VALUE obj)
|
||||||
{
|
{
|
||||||
rb_shape_t* shape = obj_shape(obj);
|
shape_id_t original_shape_id = RBASIC_SHAPE_ID(obj);
|
||||||
|
|
||||||
|
rb_shape_t* shape = RSHAPE(original_shape_id);
|
||||||
RUBY_ASSERT(shape);
|
RUBY_ASSERT(shape);
|
||||||
|
|
||||||
if (shape->flags & SHAPE_FL_HAS_OBJECT_ID) {
|
if (shape->flags & SHAPE_FL_HAS_OBJECT_ID) {
|
||||||
@ -836,7 +810,7 @@ rb_shape_transition_object_id(VALUE obj)
|
|||||||
shape = get_next_shape_internal(shape, ruby_internal_object_id, SHAPE_OBJ_ID, &dont_care, true);
|
shape = get_next_shape_internal(shape, ruby_internal_object_id, SHAPE_OBJ_ID, &dont_care, true);
|
||||||
}
|
}
|
||||||
RUBY_ASSERT(shape);
|
RUBY_ASSERT(shape);
|
||||||
return rb_shape_id(shape);
|
return shape_id(shape, original_shape_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -856,7 +830,7 @@ rb_shape_get_next_iv_shape(shape_id_t shape_id, ID id)
|
|||||||
{
|
{
|
||||||
rb_shape_t *shape = RSHAPE(shape_id);
|
rb_shape_t *shape = RSHAPE(shape_id);
|
||||||
rb_shape_t *next_shape = shape_get_next_iv_shape(shape, id);
|
rb_shape_t *next_shape = shape_get_next_iv_shape(shape, id);
|
||||||
return rb_shape_id(next_shape);
|
return raw_shape_id(next_shape);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
@ -877,7 +851,6 @@ shape_get_iv_index(rb_shape_t *shape, ID id, attr_index_t *value)
|
|||||||
return false;
|
return false;
|
||||||
case SHAPE_OBJ_TOO_COMPLEX:
|
case SHAPE_OBJ_TOO_COMPLEX:
|
||||||
case SHAPE_OBJ_ID:
|
case SHAPE_OBJ_ID:
|
||||||
case SHAPE_FROZEN:
|
|
||||||
rb_bug("Ivar should not exist on transition");
|
rb_bug("Ivar should not exist on transition");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -945,13 +918,17 @@ shape_get_next(rb_shape_t *shape, VALUE obj, ID id, bool emit_warnings)
|
|||||||
shape_id_t
|
shape_id_t
|
||||||
rb_shape_transition_add_ivar(VALUE obj, ID id)
|
rb_shape_transition_add_ivar(VALUE obj, ID id)
|
||||||
{
|
{
|
||||||
return rb_shape_id(shape_get_next(obj_shape(obj), obj, id, true));
|
RUBY_ASSERT(!shape_frozen_p(RBASIC_SHAPE_ID(obj)));
|
||||||
|
|
||||||
|
return raw_shape_id(shape_get_next(obj_shape(obj), obj, id, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
shape_id_t
|
shape_id_t
|
||||||
rb_shape_transition_add_ivar_no_warnings(VALUE obj, ID id)
|
rb_shape_transition_add_ivar_no_warnings(VALUE obj, ID id)
|
||||||
{
|
{
|
||||||
return rb_shape_id(shape_get_next(obj_shape(obj), obj, id, false));
|
RUBY_ASSERT(!shape_frozen_p(RBASIC_SHAPE_ID(obj)));
|
||||||
|
|
||||||
|
return raw_shape_id(shape_get_next(obj_shape(obj), obj, id, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Same as rb_shape_get_iv_index, but uses a provided valid shape id and index
|
// Same as rb_shape_get_iv_index, but uses a provided valid shape id and index
|
||||||
@ -987,13 +964,13 @@ rb_shape_get_iv_index_with_hint(shape_id_t shape_id, ID id, attr_index_t *value,
|
|||||||
if (shape_hint == shape) {
|
if (shape_hint == shape) {
|
||||||
// We've found a common ancestor so use the index hint
|
// We've found a common ancestor so use the index hint
|
||||||
*value = index_hint;
|
*value = index_hint;
|
||||||
*shape_id_hint = rb_shape_id(shape);
|
*shape_id_hint = raw_shape_id(shape);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (shape->edge_name == id) {
|
if (shape->edge_name == id) {
|
||||||
// We found the matching id before a common ancestor
|
// We found the matching id before a common ancestor
|
||||||
*value = shape->next_field_index - 1;
|
*value = shape->next_field_index - 1;
|
||||||
*shape_id_hint = rb_shape_id(shape);
|
*shape_id_hint = raw_shape_id(shape);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1080,7 +1057,6 @@ shape_traverse_from_new_root(rb_shape_t *initial_shape, rb_shape_t *dest_shape)
|
|||||||
switch ((enum shape_type)dest_shape->type) {
|
switch ((enum shape_type)dest_shape->type) {
|
||||||
case SHAPE_IVAR:
|
case SHAPE_IVAR:
|
||||||
case SHAPE_OBJ_ID:
|
case SHAPE_OBJ_ID:
|
||||||
case SHAPE_FROZEN:
|
|
||||||
if (!next_shape->edges) {
|
if (!next_shape->edges) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@ -1120,17 +1096,17 @@ rb_shape_traverse_from_new_root(shape_id_t initial_shape_id, shape_id_t dest_sha
|
|||||||
{
|
{
|
||||||
rb_shape_t *initial_shape = RSHAPE(initial_shape_id);
|
rb_shape_t *initial_shape = RSHAPE(initial_shape_id);
|
||||||
rb_shape_t *dest_shape = RSHAPE(dest_shape_id);
|
rb_shape_t *dest_shape = RSHAPE(dest_shape_id);
|
||||||
return rb_shape_id(shape_traverse_from_new_root(initial_shape, dest_shape));
|
return shape_id(shape_traverse_from_new_root(initial_shape, dest_shape), dest_shape_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rebuild a similar shape with the same ivars but starting from
|
// Rebuild a similar shape with the same ivars but starting from
|
||||||
// a different SHAPE_T_OBJECT, and don't cary over non-canonical transitions
|
// a different SHAPE_T_OBJECT, and don't cary over non-canonical transitions
|
||||||
// such as SHAPE_FROZEN or SHAPE_OBJ_ID.
|
// such as SHAPE_OBJ_ID.
|
||||||
rb_shape_t *
|
rb_shape_t *
|
||||||
rb_shape_rebuild_shape(rb_shape_t *initial_shape, rb_shape_t *dest_shape)
|
rb_shape_rebuild_shape(rb_shape_t *initial_shape, rb_shape_t *dest_shape)
|
||||||
{
|
{
|
||||||
RUBY_ASSERT(rb_shape_id(initial_shape) != ROOT_TOO_COMPLEX_SHAPE_ID);
|
RUBY_ASSERT(raw_shape_id(initial_shape) != ROOT_TOO_COMPLEX_SHAPE_ID);
|
||||||
RUBY_ASSERT(rb_shape_id(dest_shape) != ROOT_TOO_COMPLEX_SHAPE_ID);
|
RUBY_ASSERT(raw_shape_id(dest_shape) != ROOT_TOO_COMPLEX_SHAPE_ID);
|
||||||
|
|
||||||
rb_shape_t *midway_shape;
|
rb_shape_t *midway_shape;
|
||||||
|
|
||||||
@ -1138,7 +1114,7 @@ rb_shape_rebuild_shape(rb_shape_t *initial_shape, rb_shape_t *dest_shape)
|
|||||||
|
|
||||||
if (dest_shape->type != initial_shape->type) {
|
if (dest_shape->type != initial_shape->type) {
|
||||||
midway_shape = rb_shape_rebuild_shape(initial_shape, RSHAPE(dest_shape->parent_id));
|
midway_shape = rb_shape_rebuild_shape(initial_shape, RSHAPE(dest_shape->parent_id));
|
||||||
if (UNLIKELY(rb_shape_id(midway_shape) == ROOT_TOO_COMPLEX_SHAPE_ID)) {
|
if (UNLIKELY(raw_shape_id(midway_shape) == ROOT_TOO_COMPLEX_SHAPE_ID)) {
|
||||||
return midway_shape;
|
return midway_shape;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1152,7 +1128,6 @@ rb_shape_rebuild_shape(rb_shape_t *initial_shape, rb_shape_t *dest_shape)
|
|||||||
break;
|
break;
|
||||||
case SHAPE_OBJ_ID:
|
case SHAPE_OBJ_ID:
|
||||||
case SHAPE_ROOT:
|
case SHAPE_ROOT:
|
||||||
case SHAPE_FROZEN:
|
|
||||||
case SHAPE_T_OBJECT:
|
case SHAPE_T_OBJECT:
|
||||||
break;
|
break;
|
||||||
case SHAPE_OBJ_TOO_COMPLEX:
|
case SHAPE_OBJ_TOO_COMPLEX:
|
||||||
@ -1166,7 +1141,7 @@ rb_shape_rebuild_shape(rb_shape_t *initial_shape, rb_shape_t *dest_shape)
|
|||||||
shape_id_t
|
shape_id_t
|
||||||
rb_shape_rebuild(shape_id_t initial_shape_id, shape_id_t dest_shape_id)
|
rb_shape_rebuild(shape_id_t initial_shape_id, shape_id_t dest_shape_id)
|
||||||
{
|
{
|
||||||
return rb_shape_id(rb_shape_rebuild_shape(RSHAPE(initial_shape_id), RSHAPE(dest_shape_id)));
|
return raw_shape_id(rb_shape_rebuild_shape(RSHAPE(initial_shape_id), RSHAPE(dest_shape_id)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@ -1266,8 +1241,7 @@ static VALUE
|
|||||||
shape_frozen(VALUE self)
|
shape_frozen(VALUE self)
|
||||||
{
|
{
|
||||||
shape_id_t shape_id = NUM2INT(rb_struct_getmember(self, rb_intern("id")));
|
shape_id_t shape_id = NUM2INT(rb_struct_getmember(self, rb_intern("id")));
|
||||||
rb_shape_t *shape = RSHAPE(shape_id);
|
return RBOOL(shape_id & SHAPE_ID_FL_FROZEN);
|
||||||
return RBOOL(shape_frozen_p(shape));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static VALUE
|
static VALUE
|
||||||
@ -1290,12 +1264,13 @@ parse_key(ID key)
|
|||||||
static VALUE rb_shape_edge_name(rb_shape_t *shape);
|
static VALUE rb_shape_edge_name(rb_shape_t *shape);
|
||||||
|
|
||||||
static VALUE
|
static VALUE
|
||||||
rb_shape_t_to_rb_cShape(rb_shape_t *shape)
|
shape_id_t_to_rb_cShape(shape_id_t shape_id)
|
||||||
{
|
{
|
||||||
VALUE rb_cShape = rb_const_get(rb_cRubyVM, rb_intern("Shape"));
|
VALUE rb_cShape = rb_const_get(rb_cRubyVM, rb_intern("Shape"));
|
||||||
|
rb_shape_t *shape = RSHAPE(shape_id);
|
||||||
|
|
||||||
VALUE obj = rb_struct_new(rb_cShape,
|
VALUE obj = rb_struct_new(rb_cShape,
|
||||||
INT2NUM(rb_shape_id(shape)),
|
INT2NUM(shape_id),
|
||||||
INT2NUM(shape->parent_id),
|
INT2NUM(shape->parent_id),
|
||||||
rb_shape_edge_name(shape),
|
rb_shape_edge_name(shape),
|
||||||
INT2NUM(shape->next_field_index),
|
INT2NUM(shape->next_field_index),
|
||||||
@ -1309,7 +1284,7 @@ rb_shape_t_to_rb_cShape(rb_shape_t *shape)
|
|||||||
static enum rb_id_table_iterator_result
|
static enum rb_id_table_iterator_result
|
||||||
rb_edges_to_hash(ID key, VALUE value, void *ref)
|
rb_edges_to_hash(ID key, VALUE value, void *ref)
|
||||||
{
|
{
|
||||||
rb_hash_aset(*(VALUE *)ref, parse_key(key), rb_shape_t_to_rb_cShape((rb_shape_t *)value));
|
rb_hash_aset(*(VALUE *)ref, parse_key(key), shape_id_t_to_rb_cShape(raw_shape_id((rb_shape_t *)value)));
|
||||||
return ID_TABLE_CONTINUE;
|
return ID_TABLE_CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1360,7 +1335,7 @@ rb_shape_parent(VALUE self)
|
|||||||
rb_shape_t *shape;
|
rb_shape_t *shape;
|
||||||
shape = RSHAPE(NUM2INT(rb_struct_getmember(self, rb_intern("id"))));
|
shape = RSHAPE(NUM2INT(rb_struct_getmember(self, rb_intern("id"))));
|
||||||
if (shape->parent_id != INVALID_SHAPE_ID) {
|
if (shape->parent_id != INVALID_SHAPE_ID) {
|
||||||
return rb_shape_t_to_rb_cShape(RSHAPE(shape->parent_id));
|
return shape_id_t_to_rb_cShape(shape->parent_id);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return Qnil;
|
return Qnil;
|
||||||
@ -1370,13 +1345,13 @@ rb_shape_parent(VALUE self)
|
|||||||
static VALUE
|
static VALUE
|
||||||
rb_shape_debug_shape(VALUE self, VALUE obj)
|
rb_shape_debug_shape(VALUE self, VALUE obj)
|
||||||
{
|
{
|
||||||
return rb_shape_t_to_rb_cShape(obj_shape(obj));
|
return shape_id_t_to_rb_cShape(rb_obj_shape_id(obj));
|
||||||
}
|
}
|
||||||
|
|
||||||
static VALUE
|
static VALUE
|
||||||
rb_shape_root_shape(VALUE self)
|
rb_shape_root_shape(VALUE self)
|
||||||
{
|
{
|
||||||
return rb_shape_t_to_rb_cShape(rb_shape_get_root_shape());
|
return shape_id_t_to_rb_cShape(ROOT_SHAPE_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
static VALUE
|
static VALUE
|
||||||
@ -1420,10 +1395,8 @@ shape_to_h(rb_shape_t *shape)
|
|||||||
{
|
{
|
||||||
VALUE rb_shape = rb_hash_new();
|
VALUE rb_shape = rb_hash_new();
|
||||||
|
|
||||||
rb_hash_aset(rb_shape, ID2SYM(rb_intern("id")), INT2NUM(rb_shape_id(shape)));
|
rb_hash_aset(rb_shape, ID2SYM(rb_intern("id")), INT2NUM(raw_shape_id(shape)));
|
||||||
VALUE shape_edges = shape->edges;
|
rb_hash_aset(rb_shape, ID2SYM(rb_intern("edges")), edges(shape->edges));
|
||||||
rb_hash_aset(rb_shape, ID2SYM(rb_intern("edges")), edges(shape_edges));
|
|
||||||
RB_GC_GUARD(shape_edges);
|
|
||||||
|
|
||||||
if (shape == rb_shape_get_root_shape()) {
|
if (shape == rb_shape_get_root_shape()) {
|
||||||
rb_hash_aset(rb_shape, ID2SYM(rb_intern("parent_id")), INT2NUM(ROOT_SHAPE_ID));
|
rb_hash_aset(rb_shape, ID2SYM(rb_intern("parent_id")), INT2NUM(ROOT_SHAPE_ID));
|
||||||
@ -1449,7 +1422,7 @@ rb_shape_find_by_id(VALUE mod, VALUE id)
|
|||||||
if (shape_id >= GET_SHAPE_TREE()->next_shape_id) {
|
if (shape_id >= GET_SHAPE_TREE()->next_shape_id) {
|
||||||
rb_raise(rb_eArgError, "Shape ID %d is out of bounds\n", shape_id);
|
rb_raise(rb_eArgError, "Shape ID %d is out of bounds\n", shape_id);
|
||||||
}
|
}
|
||||||
return rb_shape_t_to_rb_cShape(RSHAPE(shape_id));
|
return shape_id_t_to_rb_cShape(shape_id);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -1511,24 +1484,14 @@ Init_default_shapes(void)
|
|||||||
root->type = SHAPE_ROOT;
|
root->type = SHAPE_ROOT;
|
||||||
root->heap_index = 0;
|
root->heap_index = 0;
|
||||||
GET_SHAPE_TREE()->root_shape = root;
|
GET_SHAPE_TREE()->root_shape = root;
|
||||||
RUBY_ASSERT(rb_shape_id(GET_SHAPE_TREE()->root_shape) == ROOT_SHAPE_ID);
|
RUBY_ASSERT(raw_shape_id(GET_SHAPE_TREE()->root_shape) == ROOT_SHAPE_ID);
|
||||||
|
|
||||||
bool dont_care;
|
bool dont_care;
|
||||||
// Special const shape
|
|
||||||
#if RUBY_DEBUG
|
|
||||||
rb_shape_t *special_const_shape =
|
|
||||||
#endif
|
|
||||||
get_next_shape_internal(root, 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(shape_frozen_p(special_const_shape));
|
|
||||||
|
|
||||||
rb_shape_t *too_complex_shape = rb_shape_alloc_with_parent_id(0, ROOT_SHAPE_ID);
|
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->type = SHAPE_OBJ_TOO_COMPLEX;
|
||||||
too_complex_shape->flags |= SHAPE_FL_TOO_COMPLEX;
|
too_complex_shape->flags |= SHAPE_FL_TOO_COMPLEX;
|
||||||
too_complex_shape->heap_index = 0;
|
too_complex_shape->heap_index = 0;
|
||||||
RUBY_ASSERT(ROOT_TOO_COMPLEX_SHAPE_ID == (GET_SHAPE_TREE()->next_shape_id - 1));
|
RUBY_ASSERT(too_complex_shape == RSHAPE(ROOT_TOO_COMPLEX_SHAPE_ID));
|
||||||
RUBY_ASSERT(rb_shape_id(too_complex_shape) == ROOT_TOO_COMPLEX_SHAPE_ID);
|
|
||||||
|
|
||||||
// Make shapes for T_OBJECT
|
// Make shapes for T_OBJECT
|
||||||
size_t *sizes = rb_gc_heap_sizes();
|
size_t *sizes = rb_gc_heap_sizes();
|
||||||
@ -1539,17 +1502,12 @@ Init_default_shapes(void)
|
|||||||
t_object_shape->capacity = (uint32_t)((sizes[i] - offsetof(struct RObject, as.ary)) / sizeof(VALUE));
|
t_object_shape->capacity = (uint32_t)((sizes[i] - offsetof(struct RObject, as.ary)) / sizeof(VALUE));
|
||||||
t_object_shape->edges = rb_managed_id_table_new(256);
|
t_object_shape->edges = rb_managed_id_table_new(256);
|
||||||
t_object_shape->ancestor_index = LEAF;
|
t_object_shape->ancestor_index = LEAF;
|
||||||
RUBY_ASSERT(rb_shape_id(t_object_shape) == rb_shape_root(i));
|
RUBY_ASSERT(t_object_shape == RSHAPE(rb_shape_root(i)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prebuild TOO_COMPLEX variations so that they already exist if we ever need them after we
|
// Prebuild TOO_COMPLEX variations so that they already exist if we ever need them after we
|
||||||
// ran out of shapes.
|
// ran out of shapes.
|
||||||
rb_shape_t *shape;
|
get_next_shape_internal(too_complex_shape, ruby_internal_object_id, SHAPE_OBJ_ID, &dont_care, true);
|
||||||
shape = get_next_shape_internal(too_complex_shape, id_frozen, SHAPE_FROZEN, &dont_care, true);
|
|
||||||
get_next_shape_internal(shape, ruby_internal_object_id, SHAPE_OBJ_ID, &dont_care, true);
|
|
||||||
|
|
||||||
shape = get_next_shape_internal(too_complex_shape, ruby_internal_object_id, SHAPE_OBJ_ID, &dont_care, true);
|
|
||||||
get_next_shape_internal(shape, id_frozen, SHAPE_FROZEN, &dont_care, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@ -1584,7 +1542,6 @@ Init_shape(void)
|
|||||||
rb_define_const(rb_cShape, "SHAPE_ROOT", INT2NUM(SHAPE_ROOT));
|
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", INT2NUM(SHAPE_IVAR));
|
||||||
rb_define_const(rb_cShape, "SHAPE_T_OBJECT", INT2NUM(SHAPE_T_OBJECT));
|
rb_define_const(rb_cShape, "SHAPE_T_OBJECT", INT2NUM(SHAPE_T_OBJECT));
|
||||||
rb_define_const(rb_cShape, "SHAPE_FROZEN", INT2NUM(SHAPE_FROZEN));
|
|
||||||
rb_define_const(rb_cShape, "SHAPE_ID_NUM_BITS", INT2NUM(SHAPE_ID_NUM_BITS));
|
rb_define_const(rb_cShape, "SHAPE_ID_NUM_BITS", INT2NUM(SHAPE_ID_NUM_BITS));
|
||||||
rb_define_const(rb_cShape, "SHAPE_FLAG_SHIFT", INT2NUM(SHAPE_FLAG_SHIFT));
|
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, "SPECIAL_CONST_SHAPE_ID", INT2NUM(SPECIAL_CONST_SHAPE_ID));
|
||||||
|
30
shape.h
30
shape.h
@ -3,17 +3,23 @@
|
|||||||
|
|
||||||
#include "internal/gc.h"
|
#include "internal/gc.h"
|
||||||
|
|
||||||
#define SIZEOF_SHAPE_T 4
|
|
||||||
typedef uint16_t attr_index_t;
|
typedef uint16_t attr_index_t;
|
||||||
typedef uint32_t shape_id_t;
|
typedef uint32_t shape_id_t;
|
||||||
#define SHAPE_ID_NUM_BITS 32
|
#define SHAPE_ID_NUM_BITS 32
|
||||||
|
#define SHAPE_ID_OFFSET_NUM_BITS 19
|
||||||
|
|
||||||
|
STATIC_ASSERT(shape_id_num_bits, SHAPE_ID_NUM_BITS == sizeof(shape_id_t) * CHAR_BIT);
|
||||||
|
|
||||||
|
#define SHAPE_BUFFER_SIZE (1 << SHAPE_ID_OFFSET_NUM_BITS)
|
||||||
|
#define SHAPE_ID_OFFSET_MASK (SHAPE_BUFFER_SIZE - 1)
|
||||||
|
#define SHAPE_ID_FLAGS_MASK (shape_id_t)(((1 << (SHAPE_ID_NUM_BITS - SHAPE_ID_OFFSET_NUM_BITS)) - 1) << SHAPE_ID_OFFSET_NUM_BITS)
|
||||||
|
#define SHAPE_ID_FL_FROZEN (SHAPE_FL_FROZEN << SHAPE_ID_OFFSET_NUM_BITS)
|
||||||
|
|
||||||
typedef uint32_t redblack_id_t;
|
typedef uint32_t redblack_id_t;
|
||||||
|
|
||||||
#define SHAPE_MAX_FIELDS (attr_index_t)(-1)
|
#define SHAPE_MAX_FIELDS (attr_index_t)(-1)
|
||||||
|
#define SHAPE_FLAG_SHIFT ((SIZEOF_VALUE * CHAR_BIT) - SHAPE_ID_NUM_BITS)
|
||||||
#define SHAPE_FLAG_MASK (((VALUE)-1) >> SHAPE_ID_NUM_BITS)
|
#define SHAPE_FLAG_MASK (((VALUE)-1) >> SHAPE_ID_NUM_BITS)
|
||||||
#define SHAPE_FLAG_SHIFT ((SIZEOF_VALUE * 8) - SHAPE_ID_NUM_BITS)
|
|
||||||
|
|
||||||
#define SHAPE_MAX_VARIATIONS 8
|
#define SHAPE_MAX_VARIATIONS 8
|
||||||
|
|
||||||
@ -21,9 +27,9 @@ typedef uint32_t redblack_id_t;
|
|||||||
#define ATTR_INDEX_NOT_SET ((attr_index_t)-1)
|
#define ATTR_INDEX_NOT_SET ((attr_index_t)-1)
|
||||||
|
|
||||||
#define ROOT_SHAPE_ID 0x0
|
#define ROOT_SHAPE_ID 0x0
|
||||||
#define SPECIAL_CONST_SHAPE_ID 0x1
|
// ROOT_TOO_COMPLEX_SHAPE_ID 0x1
|
||||||
// ROOT_TOO_COMPLEX_SHAPE_ID 0x2
|
#define SPECIAL_CONST_SHAPE_ID (ROOT_SHAPE_ID | SHAPE_ID_FL_FROZEN)
|
||||||
#define FIRST_T_OBJECT_SHAPE_ID 0x3
|
#define FIRST_T_OBJECT_SHAPE_ID 0x2
|
||||||
|
|
||||||
extern ID ruby_internal_object_id;
|
extern ID ruby_internal_object_id;
|
||||||
|
|
||||||
@ -54,11 +60,18 @@ enum shape_type {
|
|||||||
SHAPE_ROOT,
|
SHAPE_ROOT,
|
||||||
SHAPE_IVAR,
|
SHAPE_IVAR,
|
||||||
SHAPE_OBJ_ID,
|
SHAPE_OBJ_ID,
|
||||||
SHAPE_FROZEN,
|
|
||||||
SHAPE_T_OBJECT,
|
SHAPE_T_OBJECT,
|
||||||
SHAPE_OBJ_TOO_COMPLEX,
|
SHAPE_OBJ_TOO_COMPLEX,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum shape_flags {
|
||||||
|
SHAPE_FL_FROZEN = 1 << 0,
|
||||||
|
SHAPE_FL_HAS_OBJECT_ID = 1 << 1,
|
||||||
|
SHAPE_FL_TOO_COMPLEX = 1 << 2,
|
||||||
|
|
||||||
|
SHAPE_FL_NON_CANONICAL_MASK = SHAPE_FL_FROZEN | SHAPE_FL_HAS_OBJECT_ID,
|
||||||
|
};
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
/* object shapes */
|
/* object shapes */
|
||||||
rb_shape_t *shape_list;
|
rb_shape_t *shape_list;
|
||||||
@ -105,7 +118,6 @@ RBASIC_SET_SHAPE_ID(VALUE obj, shape_id_t shape_id)
|
|||||||
#if RBASIC_SHAPE_ID_FIELD
|
#if RBASIC_SHAPE_ID_FIELD
|
||||||
RBASIC(obj)->shape_id = (VALUE)shape_id;
|
RBASIC(obj)->shape_id = (VALUE)shape_id;
|
||||||
#else
|
#else
|
||||||
// Ractors are occupying the upper 32 bits of flags, but only in debug mode
|
|
||||||
// Object shapes are occupying top bits
|
// Object shapes are occupying top bits
|
||||||
RBASIC(obj)->flags &= SHAPE_FLAG_MASK;
|
RBASIC(obj)->flags &= SHAPE_FLAG_MASK;
|
||||||
RBASIC(obj)->flags |= ((VALUE)(shape_id) << SHAPE_FLAG_SHIFT);
|
RBASIC(obj)->flags |= ((VALUE)(shape_id) << SHAPE_FLAG_SHIFT);
|
||||||
@ -141,7 +153,7 @@ void rb_shape_copy_complex_ivars(VALUE dest, VALUE obj, shape_id_t src_shape_id,
|
|||||||
static inline bool
|
static inline bool
|
||||||
rb_shape_canonical_p(shape_id_t shape_id)
|
rb_shape_canonical_p(shape_id_t shape_id)
|
||||||
{
|
{
|
||||||
return !RSHAPE(shape_id)->flags;
|
return !(shape_id & SHAPE_ID_FLAGS_MASK) && !RSHAPE(shape_id)->flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline shape_id_t
|
static inline shape_id_t
|
||||||
|
@ -1055,11 +1055,12 @@ class TestShapes < Test::Unit::TestCase
|
|||||||
|
|
||||||
def test_freezing_and_duplicating_object
|
def test_freezing_and_duplicating_object
|
||||||
obj = Object.new.freeze
|
obj = Object.new.freeze
|
||||||
|
assert_predicate(RubyVM::Shape.of(obj), :shape_frozen?)
|
||||||
|
|
||||||
|
# dup'd objects shouldn't be frozen
|
||||||
obj2 = obj.dup
|
obj2 = obj.dup
|
||||||
refute_predicate(obj2, :frozen?)
|
refute_predicate(obj2, :frozen?)
|
||||||
# dup'd objects shouldn't be frozen, and the shape should be the
|
refute_predicate(RubyVM::Shape.of(obj2), :shape_frozen?)
|
||||||
# parent shape of the copied object
|
|
||||||
assert_equal(RubyVM::Shape.of(obj).parent.id, RubyVM::Shape.of(obj2).id)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_freezing_and_duplicating_object_with_ivars
|
def test_freezing_and_duplicating_object_with_ivars
|
||||||
@ -1076,6 +1077,7 @@ class TestShapes < Test::Unit::TestCase
|
|||||||
str.freeze
|
str.freeze
|
||||||
str2 = str.dup
|
str2 = str.dup
|
||||||
refute_predicate(str2, :frozen?)
|
refute_predicate(str2, :frozen?)
|
||||||
|
|
||||||
refute_equal(RubyVM::Shape.of(str).id, RubyVM::Shape.of(str2).id)
|
refute_equal(RubyVM::Shape.of(str).id, RubyVM::Shape.of(str2).id)
|
||||||
assert_equal(str2.instance_variable_get(:@a), 1)
|
assert_equal(str2.instance_variable_get(:@a), 1)
|
||||||
end
|
end
|
||||||
@ -1092,8 +1094,7 @@ class TestShapes < Test::Unit::TestCase
|
|||||||
obj2 = obj.clone(freeze: true)
|
obj2 = obj.clone(freeze: true)
|
||||||
assert_predicate(obj2, :frozen?)
|
assert_predicate(obj2, :frozen?)
|
||||||
refute_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2))
|
refute_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2))
|
||||||
assert_equal(RubyVM::Shape::SHAPE_FROZEN, RubyVM::Shape.of(obj2).type)
|
assert_predicate(RubyVM::Shape.of(obj2), :shape_frozen?)
|
||||||
assert_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2).parent)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_freezing_and_cloning_object_with_ivars
|
def test_freezing_and_cloning_object_with_ivars
|
||||||
|
@ -2057,12 +2057,6 @@ void rb_obj_freeze_inline(VALUE x)
|
|||||||
}
|
}
|
||||||
|
|
||||||
shape_id_t next_shape_id = rb_shape_transition_frozen(x);
|
shape_id_t next_shape_id = rb_shape_transition_frozen(x);
|
||||||
|
|
||||||
// If we're transitioning from "not complex" to "too complex"
|
|
||||||
// then evict ivars. This can happen if we run out of shapes
|
|
||||||
if (rb_shape_too_complex_p(next_shape_id) && !rb_shape_obj_too_complex_p(x)) {
|
|
||||||
rb_evict_fields_to_hash(x);
|
|
||||||
}
|
|
||||||
rb_obj_set_shape_id(x, next_shape_id);
|
rb_obj_set_shape_id(x, next_shape_id);
|
||||||
|
|
||||||
if (RBASIC_CLASS(x)) {
|
if (RBASIC_CLASS(x)) {
|
||||||
@ -2227,8 +2221,6 @@ iterate_over_shapes_with_callback(rb_shape_t *shape, rb_ivar_foreach_callback_fu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
case SHAPE_FROZEN:
|
|
||||||
return iterate_over_shapes_with_callback(RSHAPE(shape->parent_id), callback, itr_data);
|
|
||||||
case SHAPE_OBJ_TOO_COMPLEX:
|
case SHAPE_OBJ_TOO_COMPLEX:
|
||||||
default:
|
default:
|
||||||
rb_bug("Unreachable");
|
rb_bug("Unreachable");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user