variable.c: Refactor generic_field_set / generic_ivar_set

These two functions are very similar, they can share most of their
logic.
This commit is contained in:
Jean Boussier 2025-06-25 13:05:45 +02:00
parent a4948c30fd
commit 242343ff80
5 changed files with 122 additions and 141 deletions

2
gc.c
View file

@ -1906,7 +1906,7 @@ object_id0(VALUE obj)
shape_id_t object_id_shape_id = rb_shape_transition_object_id(obj);
id = generate_next_object_id();
rb_obj_field_set(obj, object_id_shape_id, id);
rb_obj_field_set(obj, object_id_shape_id, 0, id);
RUBY_ASSERT(RBASIC_SHAPE_ID(obj) == object_id_shape_id);
RUBY_ASSERT(rb_shape_obj_has_id(obj));

View file

@ -53,13 +53,13 @@ void rb_evict_ivars_to_hash(VALUE obj);
shape_id_t rb_evict_fields_to_hash(VALUE obj);
VALUE rb_obj_field_get(VALUE obj, shape_id_t target_shape_id);
void rb_ivar_set_internal(VALUE obj, ID id, VALUE val);
void rb_obj_field_set(VALUE obj, shape_id_t target_shape_id, VALUE val);
void rb_obj_field_set(VALUE obj, shape_id_t target_shape_id, ID field_name, VALUE val);
RUBY_SYMBOL_EXPORT_BEGIN
/* variable.c (export) */
void rb_mark_generic_ivar(VALUE obj);
VALUE rb_const_missing(VALUE klass, VALUE name);
int rb_class_ivar_set(VALUE klass, ID vid, VALUE value);
bool rb_class_ivar_set(VALUE klass, ID vid, VALUE value);
void rb_fields_tbl_copy(VALUE dst, VALUE src);
RUBY_SYMBOL_EXPORT_END

73
shape.c
View file

@ -1010,31 +1010,61 @@ rb_shape_get_iv_index_with_hint(shape_id_t shape_id, ID id, attr_index_t *value,
}
static bool
shape_cache_get_iv_index(rb_shape_t *shape, ID id, attr_index_t *value)
shape_cache_find_ivar(rb_shape_t *shape, ID id, rb_shape_t **ivar_shape)
{
if (shape->ancestor_index && shape->next_field_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_field_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
*ivar_shape = redblack_value(node);
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;
}
static bool
shape_find_ivar(rb_shape_t *shape, ID id, rb_shape_t **ivar_shape)
{
while (shape->parent_id != INVALID_SHAPE_ID) {
if (shape->edge_name == id) {
RUBY_ASSERT(shape->type == SHAPE_IVAR);
*ivar_shape = shape;
return true;
}
shape = RSHAPE(shape->parent_id);
}
return false;
}
bool
rb_shape_find_ivar(shape_id_t current_shape_id, ID id, shape_id_t *ivar_shape_id)
{
RUBY_ASSERT(!rb_shape_too_complex_p(current_shape_id));
rb_shape_t *shape = RSHAPE(current_shape_id);
rb_shape_t *ivar_shape;
if (!shape_cache_find_ivar(shape, id, &ivar_shape)) {
// If it wasn't in the ancestor cache, then don't do a linear search
if (shape->ancestor_index && shape->next_field_index >= ANCESTOR_CACHE_THRESHOLD) {
return false;
}
else {
if (!shape_find_ivar(shape, id, &ivar_shape)) {
return false;
}
}
}
*ivar_shape_id = shape_id(ivar_shape, current_shape_id);
return true;
}
bool
rb_shape_get_iv_index(shape_id_t shape_id, ID id, attr_index_t *value)
{
@ -1042,19 +1072,12 @@ rb_shape_get_iv_index(shape_id_t shape_id, ID id, attr_index_t *value)
// on an object that is "too complex" as it uses a hash for storing IVs
RUBY_ASSERT(!rb_shape_too_complex_p(shape_id));
rb_shape_t *shape = RSHAPE(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_field_index >= ANCESTOR_CACHE_THRESHOLD) {
return false;
}
else {
return shape_get_iv_index(shape, id, value);
}
shape_id_t ivar_shape_id;
if (rb_shape_find_ivar(shape_id, id, &ivar_shape_id)) {
*value = RSHAPE_INDEX(ivar_shape_id);
return true;
}
return true;
return false;
}
int32_t

View file

@ -200,6 +200,7 @@ RUBY_FUNC_EXPORTED shape_id_t rb_obj_shape_id(VALUE obj);
shape_id_t rb_shape_get_next_iv_shape(shape_id_t shape_id, ID id);
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);

View file

@ -1819,125 +1819,34 @@ generic_update_fields_obj(VALUE obj, VALUE fields_obj, const VALUE original_fiel
}
}
static void
generic_ivar_set(VALUE obj, ID id, VALUE val)
static VALUE
imemo_fields_set(VALUE klass, VALUE fields_obj, shape_id_t target_shape_id, ID field_name, VALUE val, bool concurrent)
{
bool existing = true;
VALUE fields_obj = generic_fields_lookup(obj, id, false);
const VALUE original_fields_obj = fields_obj;
if (!fields_obj) {
fields_obj = rb_imemo_fields_new(rb_obj_class(obj), 1);
}
RUBY_ASSERT(RBASIC_SHAPE_ID(obj) == RBASIC_SHAPE_ID(fields_obj));
shape_id_t current_shape_id = RBASIC_SHAPE_ID(fields_obj);
shape_id_t next_shape_id = current_shape_id;
if (UNLIKELY(rb_shape_too_complex_p(current_shape_id))) {
goto too_complex;
}
attr_index_t index;
if (!rb_shape_get_iv_index(current_shape_id, id, &index)) {
existing = false;
index = RSHAPE_LEN(current_shape_id);
if (index >= SHAPE_MAX_FIELDS) {
rb_raise(rb_eArgError, "too many instance variables");
}
next_shape_id = rb_shape_transition_add_ivar(fields_obj, id);
if (UNLIKELY(rb_shape_too_complex_p(next_shape_id))) {
fields_obj = imemo_fields_complex_from_obj(rb_obj_class(obj), original_fields_obj, next_shape_id);
goto too_complex;
}
attr_index_t next_capacity = RSHAPE_CAPACITY(next_shape_id);
attr_index_t current_capacity = RSHAPE_CAPACITY(current_shape_id);
if (next_capacity != current_capacity) {
RUBY_ASSERT(next_capacity > current_capacity);
fields_obj = rb_imemo_fields_new(rb_obj_class(obj), next_capacity);
if (original_fields_obj) {
attr_index_t fields_count = RSHAPE_LEN(current_shape_id);
VALUE *fields = rb_imemo_fields_ptr(fields_obj);
MEMCPY(fields, rb_imemo_fields_ptr(original_fields_obj), VALUE, fields_count);
for (attr_index_t i = 0; i < fields_count; i++) {
RB_OBJ_WRITTEN(fields_obj, Qundef, fields[i]);
}
}
}
RUBY_ASSERT(RSHAPE(next_shape_id)->type == SHAPE_IVAR);
RUBY_ASSERT(index == (RSHAPE_LEN(next_shape_id) - 1));
}
VALUE *fields = rb_imemo_fields_ptr(fields_obj);
RB_OBJ_WRITE(fields_obj, &fields[index], val);
if (!existing) {
RBASIC_SET_SHAPE_ID(fields_obj, next_shape_id);
}
generic_update_fields_obj(obj, fields_obj, original_fields_obj);
if (!existing) {
RBASIC_SET_SHAPE_ID(obj, next_shape_id);
}
RUBY_ASSERT(RBASIC_SHAPE_ID(obj) == RBASIC_SHAPE_ID(fields_obj));
return;
too_complex:
{
st_table *table = rb_imemo_fields_complex_tbl(fields_obj);
existing = st_insert(table, (st_data_t)id, (st_data_t)val);
RB_OBJ_WRITTEN(fields_obj, Qundef, val);
generic_update_fields_obj(obj, fields_obj, original_fields_obj);
if (!existing) {
RBASIC_SET_SHAPE_ID(obj, next_shape_id);
}
}
RUBY_ASSERT(RBASIC_SHAPE_ID(obj) == RBASIC_SHAPE_ID(fields_obj));
return;
}
static void
generic_field_set(VALUE obj, shape_id_t target_shape_id, VALUE val)
{
bool existing = true;
VALUE fields_obj = generic_fields_lookup(obj, RSHAPE_EDGE_NAME(target_shape_id), false);
const VALUE original_fields_obj = fields_obj;
shape_id_t current_shape_id = fields_obj ? RBASIC_SHAPE_ID(fields_obj) : ROOT_SHAPE_ID;
if (UNLIKELY(rb_shape_too_complex_p(target_shape_id))) {
if (UNLIKELY(!rb_shape_too_complex_p(current_shape_id))) {
fields_obj = imemo_fields_complex_from_obj(rb_obj_class(obj), original_fields_obj, target_shape_id);
if (rb_shape_too_complex_p(current_shape_id)) {
if (concurrent) {
fields_obj = rb_imemo_fields_clone(fields_obj);
}
}
else {
fields_obj = imemo_fields_complex_from_obj(klass, original_fields_obj, target_shape_id);
current_shape_id = target_shape_id;
}
existing = false;
st_table *table = rb_imemo_fields_complex_tbl(fields_obj);
RUBY_ASSERT(RSHAPE_EDGE_NAME(target_shape_id));
st_insert(table, (st_data_t)RSHAPE_EDGE_NAME(target_shape_id), (st_data_t)val);
RUBY_ASSERT(field_name);
st_insert(table, (st_data_t)field_name, (st_data_t)val);
RB_OBJ_WRITTEN(fields_obj, Qundef, val);
RBASIC_SET_SHAPE_ID(fields_obj, target_shape_id);
}
else {
attr_index_t index = RSHAPE_INDEX(target_shape_id);
if (index >= RSHAPE_CAPACITY(current_shape_id)) {
fields_obj = rb_imemo_fields_new(rb_obj_class(obj), RSHAPE_CAPACITY(target_shape_id));
if (concurrent || index >= RSHAPE_CAPACITY(current_shape_id)) {
fields_obj = rb_imemo_fields_new(klass, RSHAPE_CAPACITY(target_shape_id));
if (original_fields_obj) {
attr_index_t fields_count = RSHAPE_LEN(current_shape_id);
VALUE *fields = rb_imemo_fields_ptr(fields_obj);
@ -1952,20 +1861,63 @@ generic_field_set(VALUE obj, shape_id_t target_shape_id, VALUE val)
RB_OBJ_WRITE(fields_obj, &table[index], val);
if (RSHAPE_LEN(target_shape_id) > RSHAPE_LEN(current_shape_id)) {
existing = false;
RBASIC_SET_SHAPE_ID(fields_obj, target_shape_id);
}
}
return fields_obj;
}
static void
generic_field_set(VALUE obj, shape_id_t target_shape_id, ID field_name, VALUE val)
{
if (!field_name) {
field_name = RSHAPE_EDGE_NAME(target_shape_id);
RUBY_ASSERT(field_name);
}
const VALUE original_fields_obj = generic_fields_lookup(obj, field_name, false);
VALUE fields_obj = imemo_fields_set(rb_obj_class(obj), original_fields_obj, target_shape_id, field_name, val, false);
generic_update_fields_obj(obj, fields_obj, original_fields_obj);
if (!existing) {
if (RBASIC_SHAPE_ID(fields_obj) == target_shape_id) {
RBASIC_SET_SHAPE_ID(obj, target_shape_id);
}
RUBY_ASSERT(RBASIC_SHAPE_ID(obj) == RBASIC_SHAPE_ID(fields_obj));
}
static shape_id_t
generic_shape_ivar(VALUE obj, ID id, bool *new_ivar_out)
{
bool new_ivar = false;
shape_id_t current_shape_id = RBASIC_SHAPE_ID(obj);
shape_id_t target_shape_id = current_shape_id;
if (!rb_shape_too_complex_p(current_shape_id)) {
if (!rb_shape_find_ivar(current_shape_id, id, &target_shape_id)) {
if (RSHAPE_LEN(current_shape_id) >= SHAPE_MAX_FIELDS) {
rb_raise(rb_eArgError, "too many instance variables");
}
new_ivar = true;
target_shape_id = rb_shape_transition_add_ivar(obj, id);
}
}
*new_ivar_out = new_ivar;
return target_shape_id;
}
static void
generic_ivar_set(VALUE obj, ID id, VALUE val)
{
bool dontcare;
shape_id_t target_shape_id = generic_shape_ivar(obj, id, &dontcare);
generic_field_set(obj, target_shape_id, id, val);
}
void
rb_ensure_iv_list_size(VALUE obj, uint32_t current_len, uint32_t new_capacity)
{
@ -2141,7 +2093,7 @@ rb_ivar_set_internal(VALUE obj, ID id, VALUE val)
}
void
rb_obj_field_set(VALUE obj, shape_id_t target_shape_id, VALUE val)
rb_obj_field_set(VALUE obj, shape_id_t target_shape_id, ID field_name, VALUE val)
{
switch (BUILTIN_TYPE(obj)) {
case T_OBJECT:
@ -2153,7 +2105,7 @@ rb_obj_field_set(VALUE obj, shape_id_t target_shape_id, VALUE val)
rb_bug("Unreachable");
break;
default:
generic_field_set(obj, target_shape_id, val);
generic_field_set(obj, target_shape_id, field_name, val);
break;
}
}
@ -4429,7 +4381,7 @@ rb_cvar_set(VALUE klass, ID id, VALUE val)
}
check_before_mod_set(target, id, val, "class variable");
int result = rb_class_ivar_set(target, id, val);
bool new_cvar = rb_class_ivar_set(target, id, val);
struct rb_id_table *rb_cvc_tbl = RCLASS_WRITABLE_CVC_TBL(target);
@ -4457,7 +4409,7 @@ rb_cvar_set(VALUE klass, ID id, VALUE val)
// Break the cvar cache if this is a new class variable
// and target is a module or a subclass with the same
// cvar in this lookup.
if (result == 0) {
if (new_cvar) {
if (RB_TYPE_P(target, T_CLASS)) {
if (RCLASS_SUBCLASSES_FIRST(target)) {
rb_class_foreach_subclass(target, check_for_cvar_table, id);
@ -4713,7 +4665,12 @@ class_fields_ivar_set(VALUE klass, VALUE fields_obj, ID id, VALUE val, bool conc
next_shape_id = rb_shape_transition_add_ivar(fields_obj, id);
if (UNLIKELY(rb_shape_too_complex_p(next_shape_id))) {
fields_obj = imemo_fields_complex_from_obj(rb_singleton_class(klass), original_fields_obj, next_shape_id);
attr_index_t current_len = RSHAPE_LEN(current_shape_id);
fields_obj = rb_imemo_fields_new_complex(rb_singleton_class(klass), current_len + 1);
if (current_len) {
rb_obj_copy_fields_to_hash_table(original_fields_obj, rb_imemo_fields_complex_tbl(fields_obj));
RBASIC_SET_SHAPE_ID(fields_obj, next_shape_id);
}
goto too_complex;
}
@ -4765,7 +4722,7 @@ too_complex:
return existing;
}
int
bool
rb_class_ivar_set(VALUE obj, ID id, VALUE val)
{
RUBY_ASSERT(RB_TYPE_P(obj, T_CLASS) || RB_TYPE_P(obj, T_MODULE));
@ -4788,7 +4745,7 @@ rb_class_ivar_set(VALUE obj, ID id, VALUE val)
// Perhaps INVALID_SHAPE_ID?
RBASIC_SET_SHAPE_ID(obj, RBASIC_SHAPE_ID(new_fields_obj));
return existing;
return !existing;
}
static int