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:
Koichi SasadaandClaude Opus 5 committed 2026-08-07 16:27:53 +09:00
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

+11 -5
View File
@@ -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)
{
+7 -6
View File
@@ -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
-14
View File
@@ -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;
};
+3
View File
@@ -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
View File
@@ -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)
{
+1
View File
@@ -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);
-1
View File
@@ -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 */
-43
View File
@@ -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
View File
@@ -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;
}
-6
View File
@@ -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;
-3
View File
@@ -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);