mirror of
https://github.com/ruby/ruby.git
synced 2026-10-28 16:36:20 +08:00
Ractor: derive object belonging from the owning objspace
RACTOR_CHECK_MODE stamped every object with the id of the Ractor that allocated it (a 4 byte suffix on every slot), and re-stamped whole object graphs whenever a value crossed a Ractor boundary. Per-Ractor objspaces already record the owner: an object's page knows its objspace, so rb_gc_obj_foreign_p() answers "is this another Ractor's object" with one load, and the id, its suffix and the re-stamping traversals all go away. Deriving it is also more accurate. The stamped id was never rewritten when a zombie objspace was absorbed, which is what ractor_reset_belonging() had to paper over by re-stamping received and inherited values. Page ownership moves with the absorb, so nothing is left stale. thread.c stamped the child's main Thread wrapper with the child's id because upstream allocates that wrapper in the parent's objspace. This branch already allocates it in the child's objspace, so the site goes too. With ractor_reset_belonging() gone, no rb_obj_traverse() caller has an enter function that descends into arbitrary types: make_shareable raises on T_FILE and shareable_p stops there, so obj_traverse_i() can no longer reach T_FILE and its case goes as well. GC impls that keep a single objspace (mmtk) cannot tell owners apart and report every object as their own, so the check degrades to a no-op there; in exchange, dropping the suffix re-enables mmtk's ZJIT allocation fastpath. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
78ec25efa2
commit
09aab7899f
11 files changed
+44
-122
Notes:
git
2026-08-07 07:28:56 +00:00
No files matched your search
@@ -639,6 +639,7 @@ typedef struct gc_function_map {
|
||||
bool (*user_gc_disabled_p)(void *objspace_ptr);
|
||||
bool (*multi_objspace_p)(void);
|
||||
bool (*during_global_gc_p)(void *objspace_ptr);
|
||||
bool (*obj_foreign_p)(void *objspace_ptr, VALUE obj);
|
||||
bool (*shref_marked_p)(void *objspace_ptr, VALUE obj);
|
||||
size_t (*heap_page_count)(void *objspace_ptr);
|
||||
void (*objspace_absorb)(void *dst_ptr, void *src_ptr);
|
||||
@@ -832,6 +833,7 @@ ruby_modular_gc_init(void)
|
||||
load_modular_gc_func(user_gc_disabled_p);
|
||||
load_modular_gc_func(multi_objspace_p);
|
||||
load_modular_gc_func(during_global_gc_p);
|
||||
load_modular_gc_func(obj_foreign_p);
|
||||
load_modular_gc_func(shref_marked_p);
|
||||
load_modular_gc_func(heap_page_count);
|
||||
load_modular_gc_func(objspace_absorb);
|
||||
@@ -934,6 +936,7 @@ ruby_modular_gc_init(void)
|
||||
# define rb_gc_impl_user_gc_disabled_p rb_gc_functions.user_gc_disabled_p
|
||||
# define rb_gc_impl_multi_objspace_p rb_gc_functions.multi_objspace_p
|
||||
# define rb_gc_impl_during_global_gc_p rb_gc_functions.during_global_gc_p
|
||||
# define rb_gc_impl_obj_foreign_p rb_gc_functions.obj_foreign_p
|
||||
# define rb_gc_impl_shref_marked_p rb_gc_functions.shref_marked_p
|
||||
# define rb_gc_impl_heap_page_count rb_gc_functions.heap_page_count
|
||||
# define rb_gc_impl_objspace_absorb rb_gc_functions.objspace_absorb
|
||||
@@ -1131,11 +1134,6 @@ rb_newobj(rb_execution_context_t *ec, VALUE klass, VALUE flags, shape_id_t shape
|
||||
GC_ASSERT(actual_alloc_size >= size);
|
||||
shape_id = rb_shape_transition_slot_size(shape_id, actual_alloc_size);
|
||||
|
||||
#if RACTOR_CHECK_MODE
|
||||
void rb_ractor_setup_belonging(VALUE obj);
|
||||
rb_ractor_setup_belonging(obj);
|
||||
#endif
|
||||
|
||||
RBASIC_SET_FULL_SHAPE_ID_NO_CHECKS(obj, shape_id);
|
||||
|
||||
gc_validate_pc(obj);
|
||||
@@ -4151,6 +4149,14 @@ rb_gc_multi_objspace_p(void)
|
||||
return rb_gc_impl_multi_objspace_p();
|
||||
}
|
||||
|
||||
/* Does obj belong to another Ractor's objspace rather than the current one? Always
|
||||
* false for a single-objspace impl, which cannot tell owners apart. */
|
||||
bool
|
||||
rb_gc_obj_foreign_p(VALUE obj)
|
||||
{
|
||||
return rb_gc_impl_obj_foreign_p(rb_gc_get_objspace(), obj);
|
||||
}
|
||||
|
||||
bool
|
||||
rb_gc_single_objspace_p(void)
|
||||
{
|
||||
|
||||
@@ -858,15 +858,10 @@ global_objspace_init(void)
|
||||
#define HEAP_PAGE_ALIGN_LOG 16
|
||||
#endif
|
||||
|
||||
#if RB_GC_OBJ_HAS_SUFFIX || GC_DEBUG
|
||||
#if GC_DEBUG
|
||||
struct rvalue_overhead {
|
||||
# if RB_GC_OBJ_HAS_SUFFIX
|
||||
struct rb_gc_obj_suffix suffix;
|
||||
# endif
|
||||
# if GC_DEBUG
|
||||
const char *file;
|
||||
int line;
|
||||
# endif
|
||||
};
|
||||
|
||||
// Make sure that RVALUE_OVERHEAD aligns to sizeof(VALUE)
|
||||
@@ -8519,6 +8514,12 @@ rb_gc_impl_during_global_gc_p(void *objspace_ptr)
|
||||
return objspace->flags.during_global_gc != 0;
|
||||
}
|
||||
|
||||
bool
|
||||
rb_gc_impl_obj_foreign_p(void *objspace_ptr, VALUE obj)
|
||||
{
|
||||
return gc_foreign_object_p(objspace_ptr, obj);
|
||||
}
|
||||
|
||||
|
||||
/* Whether obj is recorded as an unshareable object referenced from a shareable one. For
|
||||
* the verifier: a shareable -> unshareable edge is only accepted if the write barrier
|
||||
|
||||
@@ -24,20 +24,6 @@
|
||||
# define RACTOR_CHECK_MODE (VM_CHECK_MODE || RUBY_DEBUG) && (SIZEOF_UINT64_T == SIZEOF_VALUE)
|
||||
#endif
|
||||
|
||||
#if RACTOR_CHECK_MODE
|
||||
void rb_ractor_setup_belonging(VALUE obj);
|
||||
|
||||
struct rb_gc_obj_suffix {
|
||||
uint32_t _ractor_belonging_id;
|
||||
};
|
||||
|
||||
# define RB_GC_OBJ_HAS_SUFFIX 1
|
||||
# define RB_GC_OBJ_SUFFIX_SIZE (sizeof(struct rb_gc_obj_suffix))
|
||||
#else
|
||||
# define RB_GC_OBJ_HAS_SUFFIX 0
|
||||
# define RB_GC_OBJ_SUFFIX_SIZE 0
|
||||
#endif
|
||||
|
||||
struct rb_gc_vm_context {
|
||||
struct rb_execution_context_struct *ec;
|
||||
};
|
||||
|
||||
@@ -137,6 +137,9 @@ GC_IMPL_FN void rb_gc_impl_each_objects_foreign(void *objspace_ptr, int (*callba
|
||||
* objspace and passes the per-Ractor objspace machinery (retire, absorb, ...) straight through. */
|
||||
GC_IMPL_FN bool rb_gc_impl_multi_objspace_p(void);
|
||||
GC_IMPL_FN bool rb_gc_impl_during_global_gc_p(void *objspace_ptr);
|
||||
/* Whether obj is owned by an objspace other than objspace_ptr. Always false for a single
|
||||
* objspace impl. */
|
||||
GC_IMPL_FN bool rb_gc_impl_obj_foreign_p(void *objspace_ptr, VALUE obj);
|
||||
GC_IMPL_FN bool rb_gc_impl_shref_marked_p(void *objspace_ptr, VALUE obj);
|
||||
GC_IMPL_FN size_t rb_gc_impl_heap_page_count(void *objspace_ptr);
|
||||
GC_IMPL_FN void rb_gc_impl_objspace_absorb(void *dst_ptr, void *src_ptr);
|
||||
|
||||
+15
-8
@@ -584,7 +584,7 @@ rb_gc_impl_objspace_alloc(void)
|
||||
if (the_objspace == NULL) {
|
||||
MMTk_Builder *builder = rb_mmtk_builder_init();
|
||||
MMTk_RubyBindingOptions binding_options = {
|
||||
.suffix_size = RB_GC_OBJ_SUFFIX_SIZE,
|
||||
.suffix_size = 0,
|
||||
};
|
||||
mmtk_init_binding(builder, &binding_options, &ruby_upcalls);
|
||||
the_objspace = calloc(1, sizeof(struct objspace));
|
||||
@@ -708,11 +708,11 @@ bool
|
||||
rb_gc_impl_zjit_new_obj_fastpath(void *objspace_ptr, size_t alloc_size, VALUE flags, VALUE klass,
|
||||
struct rb_gc_zjit_fastpath *fastpath)
|
||||
{
|
||||
#if USE_ZJIT && RB_GC_OBJ_SUFFIX_SIZE == 0
|
||||
#if USE_ZJIT
|
||||
struct objspace *objspace = objspace_ptr;
|
||||
|
||||
size_t total_size = rb_mmtk_align_obj_size(alloc_size + sizeof(VALUE) + RB_GC_OBJ_SUFFIX_SIZE);
|
||||
size_t object_size = total_size - sizeof(VALUE) - RB_GC_OBJ_SUFFIX_SIZE;
|
||||
size_t total_size = rb_mmtk_align_obj_size(alloc_size + sizeof(VALUE));
|
||||
size_t object_size = total_size - sizeof(VALUE);
|
||||
size_t value_size_shift = sizeof(VALUE) == 8 ? 3 : 2;
|
||||
|
||||
if (total_size > objspace->max_non_los_default_alloc_bytes) return false;
|
||||
@@ -1019,9 +1019,9 @@ rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags
|
||||
rb_bug("rb_gc_impl_new_obj: allocation size out of range (size=%"PRIuSIZE")", alloc_size);
|
||||
}
|
||||
|
||||
// Layout: [hidden size header (sizeof(VALUE))][payload (alloc_size)][suffix (RB_GC_OBJ_SUFFIX_SIZE)]
|
||||
size_t total_size = rb_mmtk_align_obj_size(alloc_size + sizeof(VALUE) + RB_GC_OBJ_SUFFIX_SIZE);
|
||||
size_t object_size = total_size - sizeof(VALUE) - RB_GC_OBJ_SUFFIX_SIZE;
|
||||
// Layout: [hidden size header (sizeof(VALUE))][payload (alloc_size)]
|
||||
size_t total_size = rb_mmtk_align_obj_size(alloc_size + sizeof(VALUE));
|
||||
size_t object_size = total_size - sizeof(VALUE);
|
||||
MMTk_AllocationSemantics semantics = total_size > objspace->max_non_los_default_alloc_bytes
|
||||
? MMTK_ALLOCATION_SEMANTICS_LOS
|
||||
: MMTK_ALLOCATION_SEMANTICS_DEFAULT;
|
||||
@@ -1078,7 +1078,7 @@ rb_gc_impl_size_slot_size(void *objspace_ptr, size_t size)
|
||||
rb_bug("rb_gc_impl_size_slot_size: size too large (size=%"PRIuSIZE")", size);
|
||||
}
|
||||
|
||||
return rb_mmtk_align_obj_size(size + sizeof(VALUE) + RB_GC_OBJ_SUFFIX_SIZE) - sizeof(VALUE) - RB_GC_OBJ_SUFFIX_SIZE;
|
||||
return rb_mmtk_align_obj_size(size + sizeof(VALUE)) - sizeof(VALUE);
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -1809,6 +1809,13 @@ rb_gc_impl_during_global_gc_p(void *objspace_ptr)
|
||||
return objspace->world_stopped;
|
||||
}
|
||||
|
||||
bool
|
||||
rb_gc_impl_obj_foreign_p(void *objspace_ptr, VALUE obj)
|
||||
{
|
||||
/* With a single objspace every object is our own. */
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
rb_gc_impl_shref_marked_p(void *objspace_ptr, VALUE obj)
|
||||
{
|
||||
|
||||
@@ -300,6 +300,7 @@ void rb_gc_writebarrier_remember(VALUE obj);
|
||||
void rb_gc_obj_became_shareable(VALUE obj);
|
||||
void rb_gc_pin_in_flight_message(VALUE obj);
|
||||
bool rb_gc_multi_objspace_p(void);
|
||||
bool rb_gc_obj_foreign_p(VALUE obj);
|
||||
void *rb_gc_objspace_alloc(void);
|
||||
void rb_gc_objspace_retire_gc(void);
|
||||
void rb_gc_objspace_retire(void **objspace_slot);
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
void rb_ractor_ensure_main_ractor(const char *msg);
|
||||
|
||||
RUBY_SYMBOL_EXPORT_BEGIN
|
||||
void rb_ractor_setup_belonging(VALUE obj);
|
||||
RUBY_SYMBOL_EXPORT_END
|
||||
|
||||
#endif /* INTERNAL_RACTOR_H */
|
||||
@@ -489,19 +489,6 @@ RACTOR_PTR(VALUE self)
|
||||
#define MAIN_RACTOR_ID 1
|
||||
static rb_atomic_t ractor_last_id = MAIN_RACTOR_ID;
|
||||
|
||||
#if RACTOR_CHECK_MODE > 0
|
||||
uint32_t
|
||||
rb_ractor_current_id(void)
|
||||
{
|
||||
if (GET_THREAD()->ractor == NULL) {
|
||||
return 1; // main ractor
|
||||
}
|
||||
else {
|
||||
return rb_ractor_id(GET_RACTOR());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#include "ractor_sync.c"
|
||||
|
||||
// creation/termination
|
||||
@@ -1602,7 +1589,6 @@ obj_traverse_i(VALUE obj, struct obj_traverse_data *data)
|
||||
case T_FLOAT:
|
||||
case T_BIGNUM:
|
||||
case T_REGEXP:
|
||||
case T_FILE:
|
||||
case T_SYMBOL:
|
||||
break;
|
||||
|
||||
@@ -1947,41 +1933,12 @@ rb_ractor_shareable_p_continue(VALUE obj)
|
||||
}
|
||||
}
|
||||
|
||||
#if RACTOR_CHECK_MODE > 0
|
||||
void
|
||||
rb_ractor_setup_belonging(VALUE obj)
|
||||
{
|
||||
rb_ractor_setup_belonging_to(obj, rb_ractor_current_id());
|
||||
}
|
||||
|
||||
static enum obj_traverse_iterator_result
|
||||
reset_belonging_enter(VALUE obj)
|
||||
{
|
||||
if (rb_ractor_shareable_p(obj)) {
|
||||
return traverse_skip;
|
||||
}
|
||||
else {
|
||||
rb_ractor_setup_belonging(obj);
|
||||
return traverse_cont;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static enum obj_traverse_iterator_result
|
||||
null_leave(VALUE obj)
|
||||
{
|
||||
return traverse_cont;
|
||||
}
|
||||
|
||||
static VALUE
|
||||
ractor_reset_belonging(VALUE obj)
|
||||
{
|
||||
#if RACTOR_CHECK_MODE > 0
|
||||
rb_obj_traverse(obj, reset_belonging_enter, null_leave, NULL);
|
||||
#endif
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
/// traverse and replace function
|
||||
|
||||
|
||||
+7
-36
@@ -362,50 +362,21 @@ rb_ractor_targeted_hooks_cnt(rb_ractor_t *cr)
|
||||
}
|
||||
|
||||
#if RACTOR_CHECK_MODE > 0
|
||||
# define RACTOR_BELONGING_ID(obj) (*(uint32_t *)(((uintptr_t)(obj)) + rb_gc_obj_slot_size(obj)))
|
||||
|
||||
uint32_t rb_ractor_current_id(void);
|
||||
|
||||
static inline void
|
||||
rb_ractor_setup_belonging_to(VALUE obj, uint32_t rid)
|
||||
{
|
||||
RACTOR_BELONGING_ID(obj) = rid;
|
||||
}
|
||||
|
||||
static inline uint32_t
|
||||
rb_ractor_belonging(VALUE obj)
|
||||
{
|
||||
if (SPECIAL_CONST_P(obj) || RB_OBJ_SHAREABLE_P(obj)) {
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
return RACTOR_BELONGING_ID(obj);
|
||||
}
|
||||
}
|
||||
|
||||
extern bool rb_ractor_ignore_belonging_flag;
|
||||
|
||||
/* An object's owning Ractor is decided by the objspace its page belongs to
|
||||
* (rb_gc_obj_foreign_p). Putting an unshareable object on the VM stack of anyone
|
||||
* but its owner is a containment violation. */
|
||||
static inline VALUE
|
||||
rb_ractor_confirm_belonging(VALUE obj)
|
||||
{
|
||||
if (rb_ractor_ignore_belonging_flag) return obj;
|
||||
if (SPECIAL_CONST_P(obj) || RB_OBJ_SHAREABLE_P(obj)) return obj;
|
||||
|
||||
uint32_t id = rb_ractor_belonging(obj);
|
||||
|
||||
if (id == 0) {
|
||||
if (UNLIKELY(!rb_ractor_shareable_p(obj))) {
|
||||
rp(obj);
|
||||
rb_bug("id == 0 but not shareable");
|
||||
}
|
||||
}
|
||||
else if (UNLIKELY(id != rb_ractor_current_id())) {
|
||||
if (rb_ractor_shareable_p(obj)) {
|
||||
// ok
|
||||
}
|
||||
else {
|
||||
rp(obj);
|
||||
rb_bug("rb_ractor_confirm_belonging object-ractor id:%u, current-ractor id:%u", id, rb_ractor_current_id());
|
||||
}
|
||||
if (UNLIKELY(rb_gc_obj_foreign_p(obj))) {
|
||||
rp(obj);
|
||||
rb_bug("rb_ractor_confirm_belonging: unshareable object of another Ractor's objspace");
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
@@ -909,8 +909,6 @@ ractor_set_successor_once(rb_ractor_t *r, rb_ractor_t *cr)
|
||||
return r->sync.successor;
|
||||
}
|
||||
|
||||
static VALUE ractor_reset_belonging(VALUE obj);
|
||||
|
||||
static VALUE
|
||||
ractor_make_remote_exception(VALUE cause, VALUE sender)
|
||||
{
|
||||
@@ -965,8 +963,6 @@ ractor_value(rb_execution_context_t *ec, VALUE self)
|
||||
r->sync.legacy_taken = true;
|
||||
RB_GC_GUARD(legacy_keep);
|
||||
|
||||
ractor_reset_belonging(legacy);
|
||||
|
||||
if (r->sync.legacy_exc) {
|
||||
rb_exc_raise(ractor_make_remote_exception(legacy, self));
|
||||
}
|
||||
@@ -1158,7 +1154,6 @@ ractor_basket_value(struct ractor_basket *b)
|
||||
EC_JUMP_TAG(ec, state);
|
||||
}
|
||||
/* keep rooting result from the stack after the frame is popped */
|
||||
ractor_reset_belonging(result);
|
||||
b->p.v = result;
|
||||
RB_GC_GUARD(result);
|
||||
break;
|
||||
@@ -1194,7 +1189,6 @@ ractor_basket_value(struct ractor_basket *b)
|
||||
}
|
||||
rb_ractor_move_courier_free(courier);
|
||||
b->p.move_courier = NULL;
|
||||
ractor_reset_belonging(result);
|
||||
b->p.v = result;
|
||||
RB_GC_GUARD(result);
|
||||
break;
|
||||
|
||||
@@ -898,9 +898,6 @@ thread_create_core(VALUE thval, struct thread_create_params *params)
|
||||
break;
|
||||
|
||||
case thread_invoke_type_ractor_proc:
|
||||
#if RACTOR_CHECK_MODE > 0
|
||||
rb_ractor_setup_belonging_to(thval, rb_ractor_id(params->g));
|
||||
#endif
|
||||
th->invoke_type = thread_invoke_type_ractor_proc;
|
||||
th->ractor = params->g;
|
||||
th->ec->ractor_id = rb_ractor_id(th->ractor);
|
||||
|
||||
Reference in new issue
Block a user