[Feature #19406] Allow declarative definition of references
When using rb_data_type_struct to wrap a C struct, that C struct can contain VALUE references to other Ruby objects. If this is the case then one must also define dmark and optionally dcompact callbacks in order to allow these objects to be correctly handled by the GC. This is suboptimal as it requires GC related logic to be implemented by extension developers. This can be a cause of subtle bugs when references are not marked of updated correctly inside these callbacks. This commit provides an alternative approach, useful in the simple case where the C struct contains VALUE members (ie. there isn't any conditional logic, or data structure manipulation required to traverse these references). In this case references can be defined using a declarative syntax as a list of edges (or, pointers to references). A flag can be set on the rb_data_type_struct to notify the GC that declarative references are being used, and a list of those references can be assigned to the dmark pointer instead of a function callback, on the rb_data_type_struct. Macros are also provided for simple declaration of the reference list, and building edges. To avoid having to also find space in the struct to define a length for the references list, I've chosed to always terminate the references list with RUBY_REF_END - defined as UINTPTR_MAX. My assumption is that no single struct will ever be large enough that UINTPTR_MAX is actually a valid reference.
This commit is contained in:
parent
9fd94d6a0c
commit
7142328a94
Notes:
git
2023-03-17 19:21:14 +00:00
51
gc.c
51
gc.c
@ -7241,6 +7241,39 @@ gc_mark_imemo(rb_objspace_t *objspace, VALUE obj)
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
gc_declarative_marking_p(const rb_data_type_t *type)
|
||||
{
|
||||
return (type->flags & RUBY_TYPED_DECL_MARKING) != 0;
|
||||
}
|
||||
|
||||
#define EDGE (VALUE *)((char *)data_struct + offset)
|
||||
|
||||
static inline void
|
||||
gc_mark_from_offset(rb_objspace_t *objspace, VALUE obj)
|
||||
{
|
||||
// we are overloading the dmark callback to contain a list of offsets
|
||||
size_t *offset_list = (size_t *)RANY(obj)->as.typeddata.type->function.dmark;
|
||||
void *data_struct = RANY(obj)->as.typeddata.data;
|
||||
|
||||
for (size_t offset = *offset_list; *offset_list != RUBY_REF_END; offset = *offset_list++) {
|
||||
rb_gc_mark_movable(*EDGE);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
gc_ref_update_from_offset(rb_objspace_t *objspace, VALUE obj)
|
||||
{
|
||||
// we are overloading the dmark callback to contain a list of offsets
|
||||
size_t *offset_list = (size_t *)RANY(obj)->as.typeddata.type->function.dmark;
|
||||
void *data_struct = RANY(obj)->as.typeddata.data;
|
||||
|
||||
for (size_t offset = *offset_list; *offset_list != RUBY_REF_END; offset = *offset_list++) {
|
||||
if (SPECIAL_CONST_P(*EDGE)) continue;
|
||||
*EDGE = rb_gc_location(*EDGE);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gc_mark_children(rb_objspace_t *objspace, VALUE obj)
|
||||
{
|
||||
@ -7350,10 +7383,15 @@ gc_mark_children(rb_objspace_t *objspace, VALUE obj)
|
||||
{
|
||||
void *const ptr = DATA_PTR(obj);
|
||||
if (ptr) {
|
||||
RUBY_DATA_FUNC mark_func = RTYPEDDATA_P(obj) ?
|
||||
any->as.typeddata.type->function.dmark :
|
||||
any->as.data.dmark;
|
||||
if (mark_func) (*mark_func)(ptr);
|
||||
if (RTYPEDDATA_P(obj) && gc_declarative_marking_p(any->as.typeddata.type)) {
|
||||
gc_mark_from_offset(objspace, obj);
|
||||
}
|
||||
else {
|
||||
RUBY_DATA_FUNC mark_func = RTYPEDDATA_P(obj) ?
|
||||
any->as.typeddata.type->function.dmark :
|
||||
any->as.data.dmark;
|
||||
if (mark_func) (*mark_func)(ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -10606,7 +10644,10 @@ gc_update_object_references(rb_objspace_t *objspace, VALUE obj)
|
||||
{
|
||||
void *const ptr = DATA_PTR(obj);
|
||||
if (ptr) {
|
||||
if (RTYPEDDATA_P(obj)) {
|
||||
if (RTYPEDDATA_P(obj) && gc_declarative_marking_p(any->as.typeddata.type)) {
|
||||
gc_ref_update_from_offset(objspace, obj);
|
||||
}
|
||||
else if (RTYPEDDATA_P(obj)) {
|
||||
RUBY_DATA_FUNC compact_func = any->as.typeddata.type->function.dcompact;
|
||||
if (compact_func) (*compact_func)(ptr);
|
||||
}
|
||||
|
@ -176,7 +176,14 @@ rbimpl_typeddata_flags {
|
||||
* This flag is mysterious. It seems nobody is currently using it. The
|
||||
* intention of this flag is also unclear. We need further investigations.
|
||||
*/
|
||||
RUBY_TYPED_PROMOTED1 = RUBY_FL_PROMOTED1 /* THIS FLAG DEPENDS ON Ruby version */
|
||||
RUBY_TYPED_PROMOTED1 = RUBY_FL_PROMOTED1, /* THIS FLAG DEPENDS ON Ruby version */
|
||||
|
||||
/**
|
||||
* This flag determines whether marking and compaction should be carried out
|
||||
* using the dmark/dcompact callback functions or whether we should mark
|
||||
* declaratively using a list of references defined inside the data struct we're wrapping
|
||||
*/
|
||||
RUBY_TYPED_DECL_MARKING = RUBY_FL_USER2
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -44,6 +44,12 @@
|
||||
|
||||
RBIMPL_SYMBOL_EXPORT_BEGIN()
|
||||
|
||||
#define REF_EDGE(s, p) (offsetof(struct s, p))
|
||||
#define REFS_LIST_PTR(l) ((RUBY_DATA_FUNC)l)
|
||||
#define RUBY_REF_END SIZE_MAX
|
||||
#define RUBY_REFERENCES_START(t) static size_t t[] = {
|
||||
#define RUBY_REFERENCES_END RUBY_REF_END, };
|
||||
|
||||
/* gc.c */
|
||||
|
||||
RBIMPL_ATTR_COLD()
|
||||
|
Loading…
x
Reference in New Issue
Block a user