From 83cb2913133a9228ea0c5a0f8bfb58def783e897 Mon Sep 17 00:00:00 2001 From: Matt Valentine-House Date: Thu, 7 May 2026 14:24:58 +0100 Subject: [PATCH 1/3] Introduce support for ractor_belonging. This is a debug mode in Ruby where an extra word is used after each object to store the address of the Ractor that owns the object, used for debug purposes only. While we're working on Ractors, we also need to be able to test with MMTk enabled, so we should introduce support for this to the MMTk binding as well. As implemented we'll default the binding options to have everything disabled and hardcoded to 0, as was always the case, but if RACTOR_CHECK_MODE is enabled, we'll build and pass a valid RubyBinding object to MMTk. --- gc/mmtk/mmtk.c | 31 ++++++++++++++++++++++++++++--- gc/mmtk/mmtk.h | 2 +- gc/mmtk/src/api.rs | 12 ++++++++---- 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index e4cd719..9b1aed4 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -16,6 +16,22 @@ #include #endif +#ifndef VM_CHECK_MODE +# define VM_CHECK_MODE RUBY_DEBUG +#endif + +// From ractor_core.h +#ifndef RACTOR_CHECK_MODE +# define RACTOR_CHECK_MODE (VM_CHECK_MODE || RUBY_DEBUG) && (SIZEOF_UINT64_T == SIZEOF_VALUE) +#endif + +#if RACTOR_CHECK_MODE +# define RVALUE_SUFFIX_SIZE sizeof(VALUE) +void rb_ractor_setup_belonging(VALUE obj); +#else +# define RVALUE_SUFFIX_SIZE 0 +#endif + struct objspace { bool measure_gc_time; bool gc_stress; @@ -557,7 +573,11 @@ void * rb_gc_impl_objspace_alloc(void) { MMTk_Builder *builder = rb_mmtk_builder_init(); - mmtk_init_binding(builder, NULL, &ruby_upcalls); + MMTk_RubyBindingOptions binding_options = { + .ractor_check_mode = RACTOR_CHECK_MODE != 0, + .suffix_size = RVALUE_SUFFIX_SIZE, + }; + mmtk_init_binding(builder, &binding_options, &ruby_upcalls); return calloc(1, sizeof(struct objspace)); } @@ -885,7 +905,8 @@ rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags mmtk_handle_user_collection_request(ractor_cache, false, false); } - alloc_size += sizeof(VALUE); + // Layout: [hidden size header (sizeof(VALUE))][payload (alloc_size)][suffix (RVALUE_SUFFIX_SIZE)] + alloc_size += sizeof(VALUE) + RVALUE_SUFFIX_SIZE; VALUE *alloc_obj = (VALUE *)rb_mmtk_alloc_fast_path(objspace, ractor_cache, alloc_size); if (!alloc_obj) { @@ -893,7 +914,7 @@ rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags } alloc_obj++; - alloc_obj[-1] = alloc_size - sizeof(VALUE); + alloc_obj[-1] = alloc_size - sizeof(VALUE) - RVALUE_SUFFIX_SIZE; alloc_obj[0] = flags; alloc_obj[1] = klass; @@ -905,6 +926,10 @@ rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags objspace->total_allocated_objects++; +#if RACTOR_CHECK_MODE + rb_ractor_setup_belonging((VALUE)alloc_obj); +#endif + return (VALUE)alloc_obj; } diff --git a/gc/mmtk/mmtk.h b/gc/mmtk/mmtk.h index ee338c8..e8f9592 100644 --- a/gc/mmtk/mmtk.h +++ b/gc/mmtk/mmtk.h @@ -95,7 +95,7 @@ bool mmtk_is_reachable(MMTk_ObjectReference object); MMTk_Builder *mmtk_builder_default(void); void mmtk_init_binding(MMTk_Builder *builder, - const struct MMTk_RubyBindingOptions *_binding_options, + const struct MMTk_RubyBindingOptions *binding_options, const struct MMTk_RubyUpcalls *upcalls); void mmtk_initialize_collection(MMTk_VMThread tls); diff --git a/gc/mmtk/src/api.rs b/gc/mmtk/src/api.rs index b9797f6..0c73cd7 100644 --- a/gc/mmtk/src/api.rs +++ b/gc/mmtk/src/api.rs @@ -181,7 +181,7 @@ pub extern "C" fn mmtk_builder_default() -> *mut MMTKBuilder { #[no_mangle] pub unsafe extern "C" fn mmtk_init_binding( builder: *mut MMTKBuilder, - _binding_options: *const RubyBindingOptions, + binding_options: *const RubyBindingOptions, upcalls: *const RubyUpcalls, ) { crate::MUTATOR_THREAD_PANIC_HANDLER @@ -191,9 +191,13 @@ pub unsafe extern "C" fn mmtk_init_binding( crate::set_panic_hook(); let builder: Box = unsafe { Box::from_raw(builder) }; - let binding_options = RubyBindingOptions { - ractor_check_mode: false, - suffix_size: 0, + let binding_options = if binding_options.is_null() { + RubyBindingOptions { + ractor_check_mode: false, + suffix_size: 0, + } + } else { + unsafe { (*binding_options).clone() } }; let mmtk_boxed = mmtk_init(&builder); let mmtk_static = Box::leak(Box::new(mmtk_boxed)); From 76ba5cc0f7b5ff98e0aa2714f6554bf33a545726 Mon Sep 17 00:00:00 2001 From: Matt Valentine-House Date: Thu, 7 May 2026 14:40:56 +0100 Subject: [PATCH 2/3] Update vendored headers --- gc/gc.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/gc/gc.h b/gc/gc.h index d38a129..31ce736 100644 --- a/gc/gc.h +++ b/gc/gc.h @@ -86,9 +86,7 @@ MODULAR_GC_FN void rb_gc_mark_roots(void *objspace, const char **categoryp); MODULAR_GC_FN void rb_gc_ractor_newobj_cache_foreach(void (*func)(void *cache, void *data), void *data); MODULAR_GC_FN bool rb_gc_multi_ractor_p(void); MODULAR_GC_FN bool rb_gc_shutdown_call_finalizer_p(VALUE obj); -MODULAR_GC_FN uint32_t rb_gc_get_shape(VALUE obj); -MODULAR_GC_FN void rb_gc_set_shape(VALUE obj, uint32_t shape_id); -MODULAR_GC_FN uint32_t rb_gc_rebuild_shape(VALUE obj, size_t heap_id); +MODULAR_GC_FN void rb_gc_obj_changed_pool(VALUE obj, size_t heap_id); MODULAR_GC_FN void rb_gc_prepare_heap_process_object(VALUE obj); MODULAR_GC_FN bool rb_memerror_reentered(void); MODULAR_GC_FN bool rb_obj_id_p(VALUE); From d832004e8970827c86a069379b32601bcbede633 Mon Sep 17 00:00:00 2001 From: Matt Valentine-House Date: Thu, 7 May 2026 15:19:34 +0100 Subject: [PATCH 3/3] Remove unnecessary null check. the only caller of this unconditionally constructs a binding options object now, So actually this is dead code --- gc/mmtk/src/api.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/gc/mmtk/src/api.rs b/gc/mmtk/src/api.rs index 0c73cd7..1519d2b 100644 --- a/gc/mmtk/src/api.rs +++ b/gc/mmtk/src/api.rs @@ -191,14 +191,7 @@ pub unsafe extern "C" fn mmtk_init_binding( crate::set_panic_hook(); let builder: Box = unsafe { Box::from_raw(builder) }; - let binding_options = if binding_options.is_null() { - RubyBindingOptions { - ractor_check_mode: false, - suffix_size: 0, - } - } else { - unsafe { (*binding_options).clone() } - }; + let binding_options = unsafe { (*binding_options).clone() }; let mmtk_boxed = mmtk_init(&builder); let mmtk_static = Box::leak(Box::new(mmtk_boxed));