RTypedData: keep direct reference to IMEMO/fields

Similar to f3206cc79b but for TypedData.

It's quite common for TypedData objects to have a mix of reference in
their struct and some ivars.
Since we do happen to have 8B free in the RtypedData struct, we could
use it to keep a direct reference to the IMEMO/fields saving having
to synchronize the VM and lookup the `gen_fields_tbl` on every ivar
access.

For old school Data classes however, we don't have free space, but
this API is soft-deprecated and no longer very common.
This commit is contained in:
Jean Boussier 2025-08-06 19:47:08 +02:00
parent 85c52079aa
commit 360be94d04
5 changed files with 53 additions and 18 deletions

27
gc.c
View file

@ -1047,7 +1047,7 @@ rb_data_object_wrap(VALUE klass, void *datap, RUBY_DATA_FUNC dmark, RUBY_DATA_FU
{ {
RUBY_ASSERT_ALWAYS(dfree != (RUBY_DATA_FUNC)1); RUBY_ASSERT_ALWAYS(dfree != (RUBY_DATA_FUNC)1);
if (klass) rb_data_object_check(klass); if (klass) rb_data_object_check(klass);
return newobj_of(GET_RACTOR(), klass, T_DATA, (VALUE)dmark, (VALUE)datap, (VALUE)dfree, !dmark, sizeof(struct RTypedData)); return newobj_of(GET_RACTOR(), klass, T_DATA, (VALUE)dmark, (VALUE)dfree, (VALUE)datap, !dmark, sizeof(struct RTypedData));
} }
VALUE VALUE
@ -1064,7 +1064,7 @@ typed_data_alloc(VALUE klass, VALUE typed_flag, void *datap, const rb_data_type_
RBIMPL_NONNULL_ARG(type); RBIMPL_NONNULL_ARG(type);
if (klass) rb_data_object_check(klass); if (klass) rb_data_object_check(klass);
bool wb_protected = (type->flags & RUBY_FL_WB_PROTECTED) || !type->function.dmark; bool wb_protected = (type->flags & RUBY_FL_WB_PROTECTED) || !type->function.dmark;
return newobj_of(GET_RACTOR(), klass, T_DATA, ((VALUE)type) | IS_TYPED_DATA | typed_flag, (VALUE)datap, 0, wb_protected, size); return newobj_of(GET_RACTOR(), klass, T_DATA, 0, ((VALUE)type) | IS_TYPED_DATA | typed_flag, (VALUE)datap, wb_protected, size);
} }
VALUE VALUE
@ -3173,10 +3173,15 @@ rb_gc_mark_children(void *objspace, VALUE obj)
break; break;
case T_DATA: { case T_DATA: {
void *const ptr = RTYPEDDATA_P(obj) ? RTYPEDDATA_GET_DATA(obj) : DATA_PTR(obj); bool typed_data = RTYPEDDATA_P(obj);
void *const ptr = typed_data ? RTYPEDDATA_GET_DATA(obj) : DATA_PTR(obj);
if (typed_data) {
gc_mark_internal(RTYPEDDATA(obj)->fields_obj);
}
if (ptr) { if (ptr) {
if (RTYPEDDATA_P(obj) && gc_declarative_marking_p(RTYPEDDATA_TYPE(obj))) { if (typed_data && gc_declarative_marking_p(RTYPEDDATA_TYPE(obj))) {
size_t *offset_list = TYPED_DATA_REFS_OFFSET_LIST(obj); size_t *offset_list = TYPED_DATA_REFS_OFFSET_LIST(obj);
for (size_t offset = *offset_list; offset != RUBY_REF_END; offset = *offset_list++) { for (size_t offset = *offset_list; offset != RUBY_REF_END; offset = *offset_list++) {
@ -3184,7 +3189,7 @@ rb_gc_mark_children(void *objspace, VALUE obj)
} }
} }
else { else {
RUBY_DATA_FUNC mark_func = RTYPEDDATA_P(obj) ? RUBY_DATA_FUNC mark_func = typed_data ?
RTYPEDDATA_TYPE(obj)->function.dmark : RTYPEDDATA_TYPE(obj)->function.dmark :
RDATA(obj)->dmark; RDATA(obj)->dmark;
if (mark_func) (*mark_func)(ptr); if (mark_func) (*mark_func)(ptr);
@ -4121,9 +4126,15 @@ rb_gc_update_object_references(void *objspace, VALUE obj)
case T_DATA: case T_DATA:
/* Call the compaction callback, if it exists */ /* Call the compaction callback, if it exists */
{ {
void *const ptr = RTYPEDDATA_P(obj) ? RTYPEDDATA_GET_DATA(obj) : DATA_PTR(obj); bool typed_data = RTYPEDDATA_P(obj);
void *const ptr = typed_data ? RTYPEDDATA_GET_DATA(obj) : DATA_PTR(obj);
if (typed_data) {
UPDATE_IF_MOVED(objspace, RTYPEDDATA(obj)->fields_obj);
}
if (ptr) { if (ptr) {
if (RTYPEDDATA_P(obj) && gc_declarative_marking_p(RTYPEDDATA_TYPE(obj))) { if (typed_data && gc_declarative_marking_p(RTYPEDDATA_TYPE(obj))) {
size_t *offset_list = TYPED_DATA_REFS_OFFSET_LIST(obj); size_t *offset_list = TYPED_DATA_REFS_OFFSET_LIST(obj);
for (size_t offset = *offset_list; offset != RUBY_REF_END; offset = *offset_list++) { for (size_t offset = *offset_list; offset != RUBY_REF_END; offset = *offset_list++) {
@ -4131,7 +4142,7 @@ rb_gc_update_object_references(void *objspace, VALUE obj)
*ref = gc_location_internal(objspace, *ref); *ref = gc_location_internal(objspace, *ref);
} }
} }
else if (RTYPEDDATA_P(obj)) { else if (typed_data) {
RUBY_DATA_FUNC compact_func = RTYPEDDATA_TYPE(obj)->function.dcompact; RUBY_DATA_FUNC compact_func = RTYPEDDATA_TYPE(obj)->function.dcompact;
if (compact_func) (*compact_func)(ptr); if (compact_func) (*compact_func)(ptr);
} }

View file

@ -24,7 +24,7 @@
* In released versions of Ruby, this number is not defined since teeny * In released versions of Ruby, this number is not defined since teeny
* versions of Ruby should guarantee ABI compatibility. * versions of Ruby should guarantee ABI compatibility.
*/ */
#define RUBY_ABI_VERSION 2 #define RUBY_ABI_VERSION 3
/* Windows does not support weak symbols so ruby_abi_version will not exist /* Windows does not support weak symbols so ruby_abi_version will not exist
* in the shared library. */ * in the shared library. */

View file

@ -133,12 +133,6 @@ struct RData {
*/ */
RUBY_DATA_FUNC dmark; RUBY_DATA_FUNC dmark;
/** Pointer to the actual C level struct that you want to wrap.
* This is in between dmark and dfree to allow DATA_PTR to continue
* to work for both RData and non-embedded RTypedData.
*/
void *data;
/** /**
* This function is called when the object is no longer used. You need to * This function is called when the object is no longer used. You need to
* do whatever necessary to avoid memory leaks. * do whatever necessary to avoid memory leaks.
@ -147,6 +141,12 @@ struct RData {
* impossible at that moment (that is why GC runs). * impossible at that moment (that is why GC runs).
*/ */
RUBY_DATA_FUNC dfree; RUBY_DATA_FUNC dfree;
/** Pointer to the actual C level struct that you want to wrap.
* This is in between dmark and dfree to allow DATA_PTR to continue
* to work for both RData and non-embedded RTypedData.
*/
void *data;
}; };
RBIMPL_SYMBOL_EXPORT_BEGIN() RBIMPL_SYMBOL_EXPORT_BEGIN()

View file

@ -355,6 +355,9 @@ struct RTypedData {
/** The part that all ruby objects have in common. */ /** The part that all ruby objects have in common. */
struct RBasic basic; struct RBasic basic;
/** Direct reference to the slots that holds instance variables, if any **/
VALUE fields_obj;
/** /**
* This is a `const rb_data_type_t *const` value, with the low bits set: * This is a `const rb_data_type_t *const` value, with the low bits set:
* *

View file

@ -1231,13 +1231,20 @@ rb_obj_fields(VALUE obj, ID field_name)
VALUE fields_obj = 0; VALUE fields_obj = 0;
if (rb_shape_obj_has_fields(obj)) { if (rb_shape_obj_has_fields(obj)) {
switch (BUILTIN_TYPE(obj)) { switch (BUILTIN_TYPE(obj)) {
case T_DATA:
if (LIKELY(RTYPEDDATA_P(obj))) {
fields_obj = RTYPEDDATA(obj)->fields_obj;
break;
}
goto generic_fields;
case T_STRUCT: case T_STRUCT:
if (LIKELY(!FL_TEST_RAW(obj, RSTRUCT_GEN_FIELDS))) { if (LIKELY(!FL_TEST_RAW(obj, RSTRUCT_GEN_FIELDS))) {
fields_obj = RSTRUCT_FIELDS_OBJ(obj); fields_obj = RSTRUCT_FIELDS_OBJ(obj);
break; break;
} }
// fall through goto generic_fields;
default: default:
generic_fields:
RB_VM_LOCKING() { RB_VM_LOCKING() {
if (!st_lookup(generic_fields_tbl_, (st_data_t)obj, (st_data_t *)&fields_obj)) { if (!st_lookup(generic_fields_tbl_, (st_data_t)obj, (st_data_t *)&fields_obj)) {
rb_bug("Object is missing entry in generic_fields_tbl"); rb_bug("Object is missing entry in generic_fields_tbl");
@ -1254,13 +1261,20 @@ rb_free_generic_ivar(VALUE obj)
if (rb_obj_exivar_p(obj)) { if (rb_obj_exivar_p(obj)) {
st_data_t key = (st_data_t)obj, value; st_data_t key = (st_data_t)obj, value;
switch (BUILTIN_TYPE(obj)) { switch (BUILTIN_TYPE(obj)) {
case T_DATA:
if (LIKELY(RTYPEDDATA_P(obj))) {
RB_OBJ_WRITE(obj, &RTYPEDDATA(obj)->fields_obj, 0);
break;
}
goto generic_fields;
case T_STRUCT: case T_STRUCT:
if (LIKELY(!FL_TEST_RAW(obj, RSTRUCT_GEN_FIELDS))) { if (LIKELY(!FL_TEST_RAW(obj, RSTRUCT_GEN_FIELDS))) {
RSTRUCT_SET_FIELDS_OBJ(obj, 0); RSTRUCT_SET_FIELDS_OBJ(obj, 0);
break; break;
} }
// fall through goto generic_fields;
default: default:
generic_fields:
RB_VM_LOCKING() { RB_VM_LOCKING() {
st_delete(generic_fields_tbl_no_ractor_check(), &key, &value); st_delete(generic_fields_tbl_no_ractor_check(), &key, &value);
} }
@ -1279,13 +1293,20 @@ rb_obj_set_fields(VALUE obj, VALUE fields_obj, ID field_name, VALUE original_fie
if (fields_obj != original_fields_obj) { if (fields_obj != original_fields_obj) {
switch (BUILTIN_TYPE(obj)) { switch (BUILTIN_TYPE(obj)) {
case T_DATA:
if (LIKELY(RTYPEDDATA_P(obj))) {
RB_OBJ_WRITE(obj, &RTYPEDDATA(obj)->fields_obj, fields_obj);
break;
}
goto generic_fields;
case T_STRUCT: case T_STRUCT:
if (LIKELY(!FL_TEST_RAW(obj, RSTRUCT_GEN_FIELDS))) { if (LIKELY(!FL_TEST_RAW(obj, RSTRUCT_GEN_FIELDS))) {
RSTRUCT_SET_FIELDS_OBJ(obj, fields_obj); RSTRUCT_SET_FIELDS_OBJ(obj, fields_obj);
break; break;
} }
// fall through goto generic_fields;
default: default:
generic_fields:
RB_VM_LOCKING() { RB_VM_LOCKING() {
st_insert(generic_fields_tbl_, (st_data_t)obj, (st_data_t)fields_obj); st_insert(generic_fields_tbl_, (st_data_t)obj, (st_data_t)fields_obj);
} }