mirror of
https://github.com/ruby/ruby.git
synced 2026-07-31 09:25:15 +08:00
If we allocate objects larger than 1024 bytes, the capacity in the shape ID will overflow. This commit fixes it set the capacity to SHAPE_ID_CAPACITY_MAX in that case and falls back to rb_gc_obj_slot_size instead.
732 lines
22 KiB
C
732 lines
22 KiB
C
#ifndef RUBY_SHAPE_H
|
|
#define RUBY_SHAPE_H
|
|
|
|
#include "internal/gc.h"
|
|
|
|
typedef uint8_t attr_index_t;
|
|
typedef uint32_t shape_id_t;
|
|
#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_CAPACITY_BITS 7
|
|
#define SHAPE_ID_CAPACITY_MAX ((1U << SHAPE_ID_CAPACITY_BITS) - 1)
|
|
|
|
#define SHAPE_ID_CAPACITY_OFFSET SHAPE_ID_OFFSET_NUM_BITS
|
|
#define SHAPE_ID_FL_USHIFT (SHAPE_ID_OFFSET_NUM_BITS + SHAPE_ID_CAPACITY_BITS)
|
|
|
|
// shape_id_t bits:
|
|
// 0-18 SHAPE_ID_OFFSET_MASK
|
|
// index in rb_shape_tree.shape_list. Allow to access `rb_shape_t *`.
|
|
// This is the part that describe how fields are laid out in memory.
|
|
// 19-25 SHAPE_ID_CAPACITY_MASK
|
|
// Embedded field capacity for T_OBJECT objects.
|
|
// 26 SHAPE_ID_FL_COMPLEX
|
|
// The object is backed by a `st_table`.
|
|
// 27 SHAPE_ID_FL_FROZEN
|
|
// Whether the object is frozen or not.
|
|
// 28 SHAPE_ID_FL_HAS_OBJECT_ID
|
|
// Whether the object has an `SHAPE_OBJ_ID` transition.
|
|
// 29-30 SHAPE_ID_LAYOUT_MASK
|
|
// The object's physical field layout.
|
|
|
|
STATIC_ASSERT(robject_rdata_fields_offset, offsetof(struct RObject, as.extended) == offsetof(struct RTypedData, fields_obj));
|
|
|
|
enum shape_id_fl_type {
|
|
#define RBIMPL_SHAPE_ID_FL(n) (1<<(SHAPE_ID_FL_USHIFT+n))
|
|
|
|
SHAPE_ID_CAPACITY_MASK = ((1 << SHAPE_ID_CAPACITY_BITS) - 1) << SHAPE_ID_CAPACITY_OFFSET,
|
|
|
|
SHAPE_ID_FL_COMPLEX = RBIMPL_SHAPE_ID_FL(0),
|
|
SHAPE_ID_FL_FROZEN = RBIMPL_SHAPE_ID_FL(1),
|
|
SHAPE_ID_FL_HAS_OBJECT_ID = RBIMPL_SHAPE_ID_FL(2),
|
|
|
|
// Means IVs are found at an offset from the object's addr, or in a
|
|
// malloc allocated side table
|
|
SHAPE_ID_LAYOUT_ROBJECT = 0,
|
|
|
|
// Means this object is a class/module that is NOT RCLASS_BOXABLE, and IV's
|
|
// are found in the fields_obj found on the rclass struct
|
|
SHAPE_ID_LAYOUT_RCLASS = RBIMPL_SHAPE_ID_FL(3),
|
|
|
|
// Means this object is an extened RObject or a RTypedData and IVs are found in the
|
|
// fields_obj found on the RObject/RTypedData struct at offset `sizeof(VALUE) * 2`.
|
|
SHAPE_ID_LAYOUT_EXTENDED = RBIMPL_SHAPE_ID_FL(4),
|
|
SHAPE_ID_LAYOUT_RDATA = SHAPE_ID_LAYOUT_EXTENDED,
|
|
|
|
// Means this is a complicated object: boxable classes, structs, objects
|
|
// that store IVs on the geniv table
|
|
SHAPE_ID_LAYOUT_OTHER = SHAPE_ID_LAYOUT_RCLASS | SHAPE_ID_LAYOUT_EXTENDED,
|
|
|
|
SHAPE_ID_LAYOUT_MASK = SHAPE_ID_LAYOUT_OTHER,
|
|
|
|
SHAPE_ID_FL_NON_CANONICAL_MASK = SHAPE_ID_FL_FROZEN | SHAPE_ID_FL_HAS_OBJECT_ID,
|
|
SHAPE_ID_FLAGS_MASK = SHAPE_ID_CAPACITY_MASK | SHAPE_ID_FL_NON_CANONICAL_MASK | SHAPE_ID_FL_COMPLEX | SHAPE_ID_LAYOUT_MASK,
|
|
|
|
// These parts of the shape id are specific to the object.
|
|
// Typically, when replicating a shape transition from an object to
|
|
// its IMEMO/fields, these bits should be stripped.
|
|
// All other bits are shared between an IMEMO/fields and its owner.
|
|
SHAPE_ID_FL_PRIVATE_MASK = SHAPE_ID_LAYOUT_MASK|SHAPE_ID_CAPACITY_MASK,
|
|
#undef RBIMPL_SHAPE_ID_FL
|
|
};
|
|
|
|
// This mask allows to check if a shape_id contains any ivar.
|
|
// It relies on ROOT_SHAPE_WITH_OBJ_ID==1.
|
|
enum shape_id_mask {
|
|
SHAPE_ID_HAS_IVAR_MASK = SHAPE_ID_FL_COMPLEX | (SHAPE_ID_OFFSET_MASK - 1),
|
|
};
|
|
|
|
// The interpreter doesn't care about frozen status, embedded capacity, or object id, and
|
|
// has its own checks for physical field layout when reading ivars.
|
|
// So we normalize shape_id by clearing these bits to improve cache hits.
|
|
// JITs however might care about some of it.
|
|
#define SHAPE_ID_READ_ONLY_MASK (~(SHAPE_ID_FL_FROZEN | SHAPE_ID_CAPACITY_MASK | SHAPE_ID_FL_HAS_OBJECT_ID | SHAPE_ID_LAYOUT_MASK))
|
|
// For write it's the same idea, but here we do care about frozen status.
|
|
#define SHAPE_ID_WRITE_MASK (~(SHAPE_ID_CAPACITY_MASK | SHAPE_ID_FL_HAS_OBJECT_ID | SHAPE_ID_LAYOUT_MASK))
|
|
|
|
typedef uint32_t redblack_id_t;
|
|
|
|
#define SHAPE_FLAG_SHIFT ((SIZEOF_VALUE * CHAR_BIT) - SHAPE_ID_NUM_BITS)
|
|
#define SHAPE_FLAG_MASK (((VALUE)-1) >> SHAPE_ID_NUM_BITS)
|
|
|
|
#define SHAPE_MAX_VARIATIONS 8
|
|
|
|
#define INVALID_SHAPE_ID (SHAPE_BUFFER_SIZE - 1)
|
|
#define ATTR_INDEX_NOT_SET ((attr_index_t)-1)
|
|
|
|
#define ROOT_SHAPE_ID 0x0
|
|
#define ROOT_SHAPE_WITH_OBJ_ID 0x1
|
|
#define ROOT_COMPLEX_SHAPE_ID (ROOT_SHAPE_ID | SHAPE_ID_FL_COMPLEX)
|
|
#define ROOT_COMPLEX_WITH_OBJ_ID (ROOT_SHAPE_WITH_OBJ_ID | SHAPE_ID_FL_COMPLEX | SHAPE_ID_FL_HAS_OBJECT_ID)
|
|
|
|
enum shape_type {
|
|
SHAPE_ROOT,
|
|
SHAPE_IVAR,
|
|
SHAPE_OBJ_ID
|
|
};
|
|
|
|
struct rb_shape {
|
|
VALUE edges; // id_table from ID (ivar) to next shape
|
|
ID edge_name; // ID (ivar) for transition from parent to rb_shape
|
|
redblack_id_t ancestor_index;
|
|
shape_id_t parent_offset;
|
|
attr_index_t next_field_index; // Fields are either ivars or internal properties like `object_id`
|
|
attr_index_t capacity; // Total capacity of the object with this shape
|
|
enum shape_type type : 8;
|
|
};
|
|
|
|
typedef struct rb_shape rb_shape_t;
|
|
|
|
enum shape_flags {
|
|
SHAPE_FL_FROZEN = 1 << 0,
|
|
SHAPE_FL_HAS_OBJECT_ID = 1 << 1,
|
|
SHAPE_FL_COMPLEX = 1 << 2,
|
|
|
|
SHAPE_FL_NON_CANONICAL_MASK = SHAPE_FL_FROZEN | SHAPE_FL_HAS_OBJECT_ID,
|
|
};
|
|
|
|
typedef struct {
|
|
rb_shape_t *shape_list;
|
|
attr_index_t max_capacity;
|
|
ID id_object_id;
|
|
} rb_shape_tree_t;
|
|
|
|
RUBY_SYMBOL_EXPORT_BEGIN
|
|
RUBY_EXTERN rb_shape_tree_t rb_shape_tree;
|
|
RUBY_SYMBOL_EXPORT_END
|
|
|
|
size_t rb_shapes_cache_size(void);
|
|
size_t rb_shapes_count(void);
|
|
|
|
static inline attr_index_t
|
|
rb_shape_max_capacity(void)
|
|
{
|
|
return rb_shape_tree.max_capacity;
|
|
}
|
|
|
|
static inline shape_id_t
|
|
RBASIC_SHAPE_ID(VALUE obj)
|
|
{
|
|
RUBY_ASSERT(!RB_SPECIAL_CONST_P(obj));
|
|
RUBY_ASSERT(!RB_TYPE_P(obj, T_IMEMO) || IMEMO_TYPE_P(obj, imemo_fields));
|
|
#if RBASIC_SHAPE_ID_FIELD
|
|
return (shape_id_t)((RBASIC(obj)->shape_id));
|
|
#else
|
|
return (shape_id_t)((RBASIC(obj)->flags) >> SHAPE_FLAG_SHIFT);
|
|
#endif
|
|
}
|
|
|
|
// Same as RBASIC_SHAPE_ID but with flags that have no impact
|
|
// on reads removed. e.g. Remove FL_FROZEN.
|
|
static inline shape_id_t
|
|
RBASIC_SHAPE_ID_FOR_READ(VALUE obj)
|
|
{
|
|
return RBASIC_SHAPE_ID(obj) & SHAPE_ID_READ_ONLY_MASK;
|
|
}
|
|
|
|
#if RUBY_DEBUG
|
|
bool rb_shape_verify_consistency(VALUE obj, shape_id_t shape_id);
|
|
#endif
|
|
|
|
static inline void
|
|
RBASIC_SET_FULL_SHAPE_ID_NO_CHECKS(VALUE obj, shape_id_t shape_id)
|
|
{
|
|
#if RBASIC_SHAPE_ID_FIELD
|
|
RBASIC(obj)->shape_id = (VALUE)shape_id;
|
|
#else
|
|
// Object shapes are occupying top bits
|
|
RBASIC(obj)->flags &= SHAPE_FLAG_MASK;
|
|
RBASIC(obj)->flags |= ((VALUE)(shape_id) << SHAPE_FLAG_SHIFT);
|
|
#endif
|
|
}
|
|
|
|
static inline shape_id_t
|
|
rb_shape_layout(shape_id_t shape_id)
|
|
{
|
|
return shape_id & SHAPE_ID_LAYOUT_MASK;
|
|
}
|
|
|
|
static inline bool
|
|
rb_shape_embedded_p(shape_id_t shape_id)
|
|
{
|
|
return rb_shape_layout(shape_id) == SHAPE_ID_LAYOUT_ROBJECT;
|
|
}
|
|
|
|
static inline bool
|
|
rb_shape_extended_p(shape_id_t shape_id)
|
|
{
|
|
return rb_shape_layout(shape_id) == SHAPE_ID_LAYOUT_EXTENDED;
|
|
}
|
|
|
|
static inline bool
|
|
rb_obj_shape_embedded_p(VALUE obj)
|
|
{
|
|
return rb_shape_embedded_p(RBASIC_SHAPE_ID(obj));
|
|
}
|
|
|
|
static inline bool
|
|
rb_obj_shape_extended_p(VALUE obj)
|
|
{
|
|
return rb_shape_extended_p(RBASIC_SHAPE_ID(obj));
|
|
}
|
|
|
|
// Assigns the entire shape_id.
|
|
// shape_id_t is composed of two parts:
|
|
// - The layout and capacity part, which never changes except on GC compaction.
|
|
// - All the other bits that regularly change.
|
|
// In the overwhelming majority of cases, you want to use RBASIC_SET_SHAPE_ID
|
|
// which preserves the object's layout and capacity bits.
|
|
// In rare cases you may want to set all bits.
|
|
static inline void
|
|
RBASIC_SET_FULL_SHAPE_ID(VALUE obj, shape_id_t shape_id)
|
|
{
|
|
RUBY_ASSERT(!RB_SPECIAL_CONST_P(obj));
|
|
RUBY_ASSERT(!RB_TYPE_P(obj, T_IMEMO) || IMEMO_TYPE_P(obj, imemo_fields));
|
|
|
|
RBASIC_SET_FULL_SHAPE_ID_NO_CHECKS(obj, shape_id);
|
|
|
|
RUBY_ASSERT(rb_shape_verify_consistency(obj, shape_id));
|
|
}
|
|
|
|
static inline shape_id_t rb_shape_transition_layout(shape_id_t, shape_id_t);
|
|
|
|
static inline void
|
|
RBASIC_SET_SHAPE_ID_WITH_LAYOUT(VALUE obj, shape_id_t target_shape_id, shape_id_t layout)
|
|
{
|
|
RUBY_ASSERT((layout & SHAPE_ID_LAYOUT_MASK) == layout);
|
|
shape_id_t current_shape_id = RBASIC_SHAPE_ID(obj);
|
|
current_shape_id = rb_shape_transition_layout(current_shape_id, layout);
|
|
current_shape_id = (current_shape_id & SHAPE_ID_FL_PRIVATE_MASK) | (target_shape_id & ~SHAPE_ID_FL_PRIVATE_MASK);
|
|
RBASIC_SET_FULL_SHAPE_ID(obj, current_shape_id);
|
|
}
|
|
|
|
static inline void
|
|
RBASIC_SET_SHAPE_ID(VALUE obj, shape_id_t shape_id)
|
|
{
|
|
RUBY_ASSERT(!RB_SPECIAL_CONST_P(obj));
|
|
|
|
RBASIC_SET_FULL_SHAPE_ID(obj, (
|
|
(shape_id & ~SHAPE_ID_FL_PRIVATE_MASK) |
|
|
(RBASIC_SHAPE_ID(obj) & SHAPE_ID_FL_PRIVATE_MASK)
|
|
));
|
|
}
|
|
|
|
static inline shape_id_t
|
|
RSHAPE_FLAGS(shape_id_t shape_id)
|
|
{
|
|
return shape_id & SHAPE_ID_FLAGS_MASK;
|
|
}
|
|
|
|
static inline shape_id_t
|
|
RSHAPE_OFFSET(shape_id_t shape_id)
|
|
{
|
|
return shape_id & SHAPE_ID_OFFSET_MASK;
|
|
}
|
|
|
|
static inline rb_shape_t *
|
|
RSHAPE(shape_id_t shape_id)
|
|
{
|
|
shape_id_t offset = RSHAPE_OFFSET(shape_id);
|
|
RUBY_ASSERT(offset != INVALID_SHAPE_ID);
|
|
return &rb_shape_tree.shape_list[offset];
|
|
}
|
|
|
|
int32_t rb_shape_id_offset(void);
|
|
|
|
RUBY_FUNC_EXPORTED shape_id_t rb_obj_shape_id(VALUE obj);
|
|
bool rb_shape_get_iv_index(shape_id_t shape_id, ID id, attr_index_t *value);
|
|
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);
|
|
bool rb_shape_find_ivar(shape_id_t shape_id, ID id, shape_id_t *ivar_shape);
|
|
|
|
typedef int rb_shape_foreach_transition_callback(shape_id_t shape_id, void *data);
|
|
bool rb_shape_foreach_field(shape_id_t shape_id, rb_shape_foreach_transition_callback func, void *data);
|
|
|
|
shape_id_t rb_shape_transition_add_ivar_no_warnings(shape_id_t shape_id, ID id, VALUE klass);
|
|
|
|
shape_id_t rb_shape_object_id(shape_id_t original_shape_id);
|
|
shape_id_t rb_shape_rebuild(shape_id_t initial_shape_id, shape_id_t dest_shape_id);
|
|
void rb_shape_copy_fields(VALUE dest, VALUE *dest_buf, shape_id_t dest_shape_id, VALUE *src_buf, shape_id_t src_shape_id);
|
|
|
|
static inline bool
|
|
rb_shape_frozen_p(shape_id_t shape_id)
|
|
{
|
|
return shape_id & SHAPE_ID_FL_FROZEN;
|
|
}
|
|
|
|
static inline bool
|
|
rb_shape_complex_p(shape_id_t shape_id)
|
|
{
|
|
return shape_id & SHAPE_ID_FL_COMPLEX;
|
|
}
|
|
|
|
static inline bool
|
|
rb_obj_shape_complex_p(VALUE obj)
|
|
{
|
|
return !RB_SPECIAL_CONST_P(obj) && rb_shape_complex_p(RBASIC_SHAPE_ID(obj));
|
|
}
|
|
|
|
static inline bool
|
|
rb_shape_has_object_id(shape_id_t shape_id)
|
|
{
|
|
return shape_id & SHAPE_ID_FL_HAS_OBJECT_ID;
|
|
}
|
|
|
|
static inline bool
|
|
rb_shape_canonical_p(shape_id_t shape_id)
|
|
{
|
|
return !(shape_id & SHAPE_ID_FL_NON_CANONICAL_MASK);
|
|
}
|
|
|
|
static inline attr_index_t
|
|
rb_shape_embedded_capacity(shape_id_t shape_id)
|
|
{
|
|
return (attr_index_t)((shape_id & SHAPE_ID_CAPACITY_MASK) >> SHAPE_ID_CAPACITY_OFFSET);
|
|
}
|
|
|
|
static inline size_t
|
|
rb_shape_slot_size(shape_id_t shape_id)
|
|
{
|
|
return sizeof(struct RBasic) + (rb_shape_embedded_capacity(shape_id) * sizeof(VALUE));
|
|
}
|
|
|
|
static inline size_t
|
|
rb_obj_shape_slot_size(VALUE obj)
|
|
{
|
|
RUBY_ASSERT(!RB_TYPE_P(obj, T_IMEMO) || IMEMO_TYPE_P(obj, imemo_fields));
|
|
|
|
shape_id_t shape_id = RBASIC_SHAPE_ID(obj);
|
|
size_t slot_size = rb_shape_slot_size(shape_id);
|
|
|
|
if (rb_shape_embedded_capacity(shape_id) == SHAPE_ID_CAPACITY_MAX) {
|
|
size_t gc_slot_size = rb_gc_obj_slot_size(obj);
|
|
RUBY_ASSERT(gc_slot_size >= slot_size);
|
|
return gc_slot_size;
|
|
}
|
|
|
|
return slot_size;
|
|
}
|
|
|
|
static inline attr_index_t
|
|
rb_shape_capacity_for_slot_size(size_t slot_size)
|
|
{
|
|
size_t capacity = (slot_size - sizeof(struct RBasic)) / sizeof(VALUE);
|
|
if (capacity > SHAPE_ID_CAPACITY_MAX) {
|
|
capacity = SHAPE_ID_CAPACITY_MAX;
|
|
}
|
|
return (attr_index_t)capacity;
|
|
}
|
|
|
|
static inline shape_id_t
|
|
RSHAPE_PARENT_OFFSET(shape_id_t shape_id)
|
|
{
|
|
return RSHAPE(shape_id)->parent_offset;
|
|
}
|
|
|
|
static inline bool
|
|
RSHAPE_DIRECT_CHILD_P(shape_id_t parent_offset, shape_id_t child_id)
|
|
{
|
|
return RSHAPE_PARENT_OFFSET(child_id) == RSHAPE_OFFSET(parent_offset);
|
|
}
|
|
|
|
static inline enum shape_type
|
|
RSHAPE_TYPE(shape_id_t shape_id)
|
|
{
|
|
return RSHAPE(shape_id)->type;
|
|
}
|
|
|
|
static inline bool
|
|
RSHAPE_TYPE_P(shape_id_t shape_id, enum shape_type type)
|
|
{
|
|
return RSHAPE_TYPE(shape_id) == type;
|
|
}
|
|
|
|
static inline attr_index_t
|
|
RSHAPE_CAPACITY(shape_id_t shape_id)
|
|
{
|
|
attr_index_t embedded_capacity = rb_shape_embedded_capacity(shape_id);
|
|
|
|
if (embedded_capacity > RSHAPE(shape_id)->capacity) {
|
|
return embedded_capacity;
|
|
}
|
|
else {
|
|
return RSHAPE(shape_id)->capacity;
|
|
}
|
|
}
|
|
|
|
static inline attr_index_t
|
|
RSHAPE_LEN(shape_id_t shape_id)
|
|
{
|
|
return RSHAPE(shape_id)->next_field_index;
|
|
}
|
|
|
|
static inline attr_index_t
|
|
RSHAPE_INDEX(shape_id_t shape_id)
|
|
{
|
|
RUBY_ASSERT(RSHAPE_LEN(shape_id) > 0);
|
|
return RSHAPE_LEN(shape_id) - 1;
|
|
}
|
|
|
|
static inline ID
|
|
RSHAPE_EDGE_NAME(shape_id_t shape_id)
|
|
{
|
|
return RSHAPE(shape_id)->edge_name;
|
|
}
|
|
|
|
static inline VALUE *
|
|
rb_imemo_fields_ptr(VALUE fields_obj)
|
|
{
|
|
if (!fields_obj) {
|
|
return NULL;
|
|
}
|
|
|
|
RUBY_ASSERT(rb_obj_shape_embedded_p(fields_obj));
|
|
return IMEMO_OBJ_FIELDS(fields_obj)->as.embed.fields;
|
|
}
|
|
|
|
static inline uint32_t
|
|
RBASIC_FIELDS_COUNT(VALUE obj)
|
|
{
|
|
return RSHAPE(RBASIC_SHAPE_ID(obj))->next_field_index;
|
|
}
|
|
|
|
static inline bool
|
|
rb_obj_shape_has_id(VALUE obj)
|
|
{
|
|
return rb_shape_has_object_id(RBASIC_SHAPE_ID(obj));
|
|
}
|
|
|
|
static inline bool
|
|
rb_shape_has_ivars(shape_id_t shape_id)
|
|
{
|
|
return shape_id & SHAPE_ID_HAS_IVAR_MASK;
|
|
}
|
|
|
|
static inline bool
|
|
rb_obj_shape_has_ivars(VALUE obj)
|
|
{
|
|
return rb_shape_has_ivars(RBASIC_SHAPE_ID(obj));
|
|
}
|
|
|
|
static inline bool
|
|
rb_shape_has_fields(shape_id_t shape_id)
|
|
{
|
|
return shape_id & (SHAPE_ID_OFFSET_MASK | SHAPE_ID_FL_COMPLEX);
|
|
}
|
|
|
|
static inline bool
|
|
rb_obj_shape_has_fields(VALUE obj)
|
|
{
|
|
return rb_shape_has_fields(RBASIC_SHAPE_ID(obj));
|
|
}
|
|
|
|
static inline bool
|
|
rb_obj_gen_fields_p(VALUE obj)
|
|
{
|
|
switch (TYPE(obj)) {
|
|
case T_NONE:
|
|
case T_OBJECT:
|
|
case T_CLASS:
|
|
case T_MODULE:
|
|
case T_IMEMO:
|
|
return false;
|
|
default:
|
|
break;
|
|
}
|
|
return rb_obj_shape_has_fields(obj);
|
|
}
|
|
|
|
static inline shape_id_t
|
|
rb_shape_transition_layout(shape_id_t shape_id, shape_id_t layout)
|
|
{
|
|
return (shape_id & (~SHAPE_ID_LAYOUT_MASK)) | layout;
|
|
}
|
|
|
|
static inline shape_id_t
|
|
rb_shape_transition_robject(shape_id_t shape_id)
|
|
{
|
|
return rb_shape_transition_layout(shape_id, SHAPE_ID_LAYOUT_ROBJECT);
|
|
}
|
|
|
|
static inline shape_id_t
|
|
rb_shape_transition_extended(shape_id_t shape_id)
|
|
{
|
|
return rb_shape_transition_layout(shape_id, SHAPE_ID_LAYOUT_EXTENDED);
|
|
}
|
|
|
|
static inline shape_id_t
|
|
rb_shape_transition_frozen(shape_id_t shape_id)
|
|
{
|
|
return shape_id | SHAPE_ID_FL_FROZEN;
|
|
}
|
|
|
|
static inline shape_id_t
|
|
rb_shape_transition_complex(shape_id_t shape_id)
|
|
{
|
|
shape_id_t next_shape_id = rb_shape_layout(shape_id) | ROOT_COMPLEX_SHAPE_ID;
|
|
|
|
if (rb_shape_has_object_id(shape_id)) {
|
|
next_shape_id = rb_shape_layout(shape_id) | ROOT_COMPLEX_WITH_OBJ_ID;
|
|
}
|
|
|
|
next_shape_id |= shape_id & SHAPE_ID_CAPACITY_MASK;
|
|
|
|
RUBY_ASSERT(rb_shape_has_object_id(shape_id) == rb_shape_has_object_id(next_shape_id));
|
|
|
|
return next_shape_id;
|
|
}
|
|
|
|
static inline shape_id_t
|
|
rb_shape_transition_offset(shape_id_t shape_id, shape_id_t offset)
|
|
{
|
|
offset = RSHAPE_OFFSET(offset);
|
|
RUBY_ASSERT(RSHAPE_OFFSET(shape_id) == offset || RSHAPE_DIRECT_CHILD_P(shape_id, offset));
|
|
return RSHAPE_FLAGS(shape_id) | offset;
|
|
}
|
|
|
|
static inline shape_id_t
|
|
rb_shape_transition_capacity(shape_id_t shape_id, size_t capacity)
|
|
{
|
|
RUBY_ASSERT(capacity <= SHAPE_ID_CAPACITY_MAX);
|
|
|
|
shape_id_t capacity_flags = (shape_id_t)capacity << SHAPE_ID_CAPACITY_OFFSET;
|
|
return (shape_id & (~SHAPE_ID_CAPACITY_MASK)) | capacity_flags;
|
|
}
|
|
|
|
static inline shape_id_t
|
|
rb_shape_transition_slot_size(shape_id_t shape_id, size_t slot_size)
|
|
{
|
|
return rb_shape_transition_capacity(shape_id, rb_shape_capacity_for_slot_size(slot_size));
|
|
}
|
|
|
|
shape_id_t rb_shape_transition_object_id(shape_id_t shape_id);
|
|
|
|
static inline shape_id_t
|
|
rb_obj_shape_transition_frozen(VALUE obj)
|
|
{
|
|
RUBY_ASSERT(RB_OBJ_FROZEN(obj));
|
|
return rb_shape_transition_frozen(RBASIC_SHAPE_ID(obj));
|
|
}
|
|
|
|
static inline shape_id_t
|
|
rb_obj_shape_transition_complex(VALUE obj)
|
|
{
|
|
return rb_shape_transition_complex(RBASIC_SHAPE_ID(obj));
|
|
}
|
|
|
|
static inline shape_id_t
|
|
rb_obj_shape_transition_capacity(VALUE obj, size_t capacity)
|
|
{
|
|
return rb_shape_transition_capacity(RBASIC_SHAPE_ID(obj), capacity);
|
|
}
|
|
|
|
static inline shape_id_t
|
|
rb_obj_shape_transition_slot_size(VALUE obj, size_t slot_size)
|
|
{
|
|
return rb_shape_transition_slot_size(RBASIC_SHAPE_ID(obj), slot_size);
|
|
}
|
|
|
|
static inline shape_id_t
|
|
rb_obj_shape_transition_object_id(VALUE obj)
|
|
{
|
|
return rb_shape_transition_object_id(RBASIC_SHAPE_ID(obj));
|
|
}
|
|
|
|
shape_id_t rb_obj_shape_transition_remove_ivar(VALUE obj, ID id, shape_id_t *removed_shape_id);
|
|
shape_id_t rb_obj_shape_transition_add_ivar(VALUE obj, ID id);
|
|
|
|
// For ext/objspace
|
|
RUBY_SYMBOL_EXPORT_BEGIN
|
|
typedef void each_shape_callback(shape_id_t shape_id, void *data);
|
|
void rb_shape_each_shape_id(each_shape_callback callback, void *data);
|
|
size_t rb_shape_memsize(shape_id_t shape);
|
|
size_t rb_shape_edges_count(shape_id_t shape_id);
|
|
size_t rb_shape_depth(shape_id_t shape_id);
|
|
RUBY_SYMBOL_EXPORT_END
|
|
|
|
// Inline cache helpers
|
|
|
|
typedef struct {
|
|
attr_index_t index;
|
|
shape_id_t shape_offset;
|
|
} rb_getivar_cache;
|
|
|
|
union rb_getivar_cache {
|
|
uint64_t pack;
|
|
rb_getivar_cache unpack;
|
|
};
|
|
STATIC_ASSERT(rb_getivar_cache_size, sizeof(union rb_getivar_cache) <= sizeof(uint64_t));
|
|
|
|
#define IVAR_CACHE_INIT ((uint64_t)-1)
|
|
#define ATTR_INDEX_T_NUM_BITS (sizeof(attr_index_t) * CHAR_BIT)
|
|
|
|
static inline rb_getivar_cache
|
|
rb_getivar_cache_unpack(uint64_t packed)
|
|
{
|
|
union rb_getivar_cache cache = {
|
|
.pack = packed,
|
|
};
|
|
|
|
// Because caches may initialized with all bits set (IVAR_CACHE_INIT), and `shape_offset` if 32bits,
|
|
// we need to remove any potential extra bits set in the "padding".
|
|
cache.unpack.shape_offset &= SHAPE_ID_OFFSET_MASK;
|
|
return cache.unpack;
|
|
}
|
|
|
|
static inline uint64_t
|
|
rb_getivar_cache_pack(shape_id_t shape_offset, attr_index_t index)
|
|
{
|
|
RUBY_ASSERT(shape_offset == RSHAPE_OFFSET(shape_offset));
|
|
RUBY_ASSERT(shape_offset != INVALID_SHAPE_ID);
|
|
|
|
union rb_getivar_cache cache = {
|
|
.unpack = {
|
|
.shape_offset = shape_offset,
|
|
.index = index,
|
|
},
|
|
};
|
|
return cache.pack;
|
|
}
|
|
|
|
typedef struct {
|
|
attr_index_t index;
|
|
shape_id_t source_shape_offset;
|
|
shape_id_t dest_shape_offset;
|
|
} rb_setivar_cache;
|
|
|
|
static inline rb_setivar_cache
|
|
rb_setivar_cache_unpack(uint64_t packed)
|
|
{
|
|
rb_setivar_cache cache = {
|
|
.index = (attr_index_t)packed,
|
|
.source_shape_offset = RSHAPE_OFFSET((shape_id_t)(packed >> ATTR_INDEX_T_NUM_BITS)),
|
|
.dest_shape_offset = RSHAPE_OFFSET((shape_id_t)(packed >> (ATTR_INDEX_T_NUM_BITS + SHAPE_ID_OFFSET_NUM_BITS))),
|
|
};
|
|
return cache;
|
|
}
|
|
|
|
static inline uint64_t
|
|
rb_setivar_cache_pack(shape_id_t shape_offset, shape_id_t dest_shape_offset, attr_index_t index)
|
|
{
|
|
RUBY_ASSERT(shape_offset == RSHAPE_OFFSET(shape_offset));
|
|
RUBY_ASSERT(dest_shape_offset == RSHAPE_OFFSET(dest_shape_offset));
|
|
RUBY_ASSERT(shape_offset == dest_shape_offset || RSHAPE_DIRECT_CHILD_P(shape_offset, dest_shape_offset));
|
|
|
|
uint64_t packed_cache = (uint64_t)dest_shape_offset << (ATTR_INDEX_T_NUM_BITS + SHAPE_ID_OFFSET_NUM_BITS);
|
|
packed_cache |= (uint64_t)shape_offset << ATTR_INDEX_T_NUM_BITS;
|
|
packed_cache |= (uint64_t)index;
|
|
return packed_cache;
|
|
}
|
|
|
|
ALWAYS_INLINE(static shape_id_t rb_setivar_cache_revalidate(shape_id_t shape_id, shape_id_t fields_shape_id, rb_setivar_cache cache));
|
|
static shape_id_t
|
|
rb_setivar_cache_revalidate(shape_id_t shape_id, shape_id_t fields_shape_id, rb_setivar_cache cache)
|
|
{
|
|
RUBY_ASSERT(shape_id != INVALID_SHAPE_ID);
|
|
RUBY_ASSERT(cache.dest_shape_offset == INVALID_SHAPE_ID || cache.dest_shape_offset == RSHAPE_OFFSET(cache.dest_shape_offset));
|
|
|
|
shape_id_t normalized_shape_id = shape_id & SHAPE_ID_WRITE_MASK;
|
|
if (UNLIKELY(normalized_shape_id != cache.source_shape_offset)) {
|
|
return INVALID_SHAPE_ID;
|
|
}
|
|
|
|
if (UNLIKELY(cache.index >= RSHAPE_CAPACITY(fields_shape_id))) {
|
|
// That's still a hit in term of layout, but the object will need to be resized,
|
|
// so unfortunately we'll have to go through the slow path regardless...
|
|
return INVALID_SHAPE_ID;
|
|
}
|
|
|
|
// Cache hit case
|
|
RUBY_ASSERT(cache.source_shape_offset == cache.dest_shape_offset || RSHAPE_DIRECT_CHILD_P(shape_id, cache.dest_shape_offset));
|
|
RUBY_ASSERT(cache.index < RSHAPE_CAPACITY(shape_id));
|
|
RUBY_ASSERT(!rb_shape_frozen_p(shape_id));
|
|
RUBY_ASSERT(!rb_shape_complex_p(shape_id));
|
|
|
|
// We use the cached offset, but combined with the current shape flags.
|
|
return rb_shape_transition_offset(shape_id, cache.dest_shape_offset);
|
|
}
|
|
|
|
static inline st_table *
|
|
rb_imemo_fields_complex_tbl(VALUE fields_obj)
|
|
{
|
|
if (!fields_obj) {
|
|
return NULL;
|
|
}
|
|
|
|
RUBY_ASSERT(IMEMO_TYPE_P(fields_obj, imemo_fields));
|
|
|
|
// Some codepaths unconditionally access the fields_ptr, and assume it can be used as st_table if the
|
|
// shape is complex.
|
|
RUBY_ASSERT((st_table *)rb_imemo_fields_ptr(fields_obj) == &IMEMO_OBJ_FIELDS(fields_obj)->as.complex.table);
|
|
|
|
return &IMEMO_OBJ_FIELDS(fields_obj)->as.complex.table;
|
|
}
|
|
|
|
static inline VALUE
|
|
ROBJECT_FIELDS_OBJ(VALUE obj)
|
|
{
|
|
RBIMPL_ASSERT_TYPE(obj, RUBY_T_OBJECT);
|
|
|
|
return rb_obj_shape_embedded_p(obj) ? obj : ROBJECT(obj)->as.extended;
|
|
}
|
|
|
|
static inline VALUE *
|
|
ROBJECT_EMBEDDED_FIELDS(VALUE obj)
|
|
{
|
|
return ROBJECT(obj)->as.ary;
|
|
}
|
|
|
|
static inline VALUE *
|
|
ROBJECT_FIELDS(VALUE obj)
|
|
{
|
|
RBIMPL_ASSERT_TYPE(obj, RUBY_T_OBJECT);
|
|
|
|
return ROBJECT_EMBEDDED_FIELDS(ROBJECT_FIELDS_OBJ(obj));
|
|
}
|
|
|
|
#endif
|