Ruby 4.1.0dev (2026-10-21 revision fa2fee5a1d8a80586a0a0ac38e184e0d6622673e)
wbcheck.c
1#include "ruby/ruby.h"
2#include "ruby/assert.h"
3#include "ruby/atomic.h"
4#include "ruby/debug.h"
6#include "ruby/st.h"
7
8#include "ruby/thread.h"
9#include "gc/gc.h"
10#include "gc/gc_impl.h"
11
12#include <stdbool.h>
13#include <stdarg.h>
14
15// Modular GCs can't include internal headers; this mirrors RCLASS_IS_ROOT
16// from internal/class.h.
17#define WBCHECK_RCLASS_IS_ROOT FL_USER0
18
19// Debug output control
20static bool wbcheck_debug_enabled = false;
21
22// Verification after write barrier control
23static bool wbcheck_verify_after_wb_enabled = false;
24
25// Useless write barrier warning control
26static bool wbcheck_warn_useless_wb_enabled = false;
27
28static void
29wbcheck_debug(const char *format, ...)
30{
31 if (!wbcheck_debug_enabled) return;
32
33 va_list args;
34 va_start(args, format);
35 vfprintf(stderr, format, args);
36 va_end(args);
37}
38
39#define WBCHECK_DEBUG(...) do { \
40 if (wbcheck_debug_enabled) { \
41 wbcheck_debug(__VA_ARGS__); \
42 } \
43} while (0)
44
45static void
46wbcheck_debug_obj_info_dump(VALUE obj)
47{
48 if (!wbcheck_debug_enabled) return;
49 char buff[0x100];
50 fprintf(stderr, "%s\n", rb_raw_obj_info(buff, sizeof(buff), obj));
51}
52
53// Forward declaration
54static void lock_and_maybe_gc(void *objspace_ptr);
55static void force_gc(void *objspace_ptr);
56
57// Configure wbcheck from environment variables
58static void
59wbcheck_configure_from_env(void)
60{
61 // Configure debug output based on environment variable
62 const char *debug_env = getenv("WBCHECK_DEBUG");
63 if (debug_env && (strcmp(debug_env, "1") == 0 || strcmp(debug_env, "true") == 0)) {
64 wbcheck_debug_enabled = true;
65 }
66
67 // Configure verification after write barrier based on environment variable
68 const char *verify_after_wb_env = getenv("WBCHECK_VERIFY_AFTER_WB");
69 if (verify_after_wb_env && (strcmp(verify_after_wb_env, "1") == 0 || strcmp(verify_after_wb_env, "true") == 0)) {
70 wbcheck_verify_after_wb_enabled = true;
71 }
72
73 // Configure useless write barrier warnings based on environment variable
74 const char *warn_useless_wb_env = getenv("WBCHECK_WARN_USELESS_WB");
75 if (warn_useless_wb_env && (strcmp(warn_useless_wb_env, "1") == 0 || strcmp(warn_useless_wb_env, "true") == 0)) {
76 wbcheck_warn_useless_wb_enabled = true;
77 }
78}
79
80// Define same heap sizes as the default GC
81static size_t heap_sizes[] = {
82 32,
83 40,
84 48,
85 56,
86 64,
87 72,
88 80,
89 96,
90 128,
91 160,
92 256,
93 512,
94 640,
95 768,
96 1024,
97 0
98};
99
100#define HEAP_COUNT ((int)(sizeof(heap_sizes) / sizeof(heap_sizes[0])) - 1)
101#define MAX_HEAP_SIZE (heap_sizes[(HEAP_COUNT) - 1])
102
103// Object states for verification tracking
104typedef enum {
105 WBCHECK_STATE_CLEAR, // Just allocated or writebarrier_remember, needs reference capture
106 WBCHECK_STATE_MARKED, // Has valid snapshot, ready for normal operation
107 WBCHECK_STATE_DIRTY // Has seen writebarrier since last snapshot, queued for verification
108} wbcheck_object_state_t;
109
110// Tri-color marking colors
111typedef enum {
112 WBCHECK_COLOR_WHITE, // Unmarked - will be swept
113 WBCHECK_COLOR_GRAY, // Marked but children not processed
114 WBCHECK_COLOR_BLACK // Marked and children processed
115} wbcheck_color_t;
116
117// GC phases
118typedef enum {
119 WBCHECK_PHASE_MUTATOR, // Normal execution
120 WBCHECK_PHASE_SNAPSHOT, // Collecting references for verification
121 WBCHECK_PHASE_FULL_GC // Marking objects during full GC
122} wbcheck_phase_t;
123
124// List of objects
125typedef struct {
126 VALUE *items;
127 size_t count;
128 size_t capacity;
130
131// Helper functions for object list
133wbcheck_object_list_init_with_capacity(size_t capacity)
134{
135 wbcheck_object_list_t *list = calloc(1, sizeof(wbcheck_object_list_t));
136 if (!list) rb_bug("wbcheck: failed to allocate object list structure");
137
138 if (capacity < 4) capacity = 4;
139 list->items = malloc(capacity * sizeof(VALUE));
140 if (!list->items) rb_bug("wbcheck: failed to allocate object list array");
141 list->capacity = capacity;
142 list->count = 0;
143 return list;
144}
145
147wbcheck_object_list_init(void)
148{
149 return wbcheck_object_list_init_with_capacity(4);
150}
151
152static void
153wbcheck_object_list_append(wbcheck_object_list_t *list, VALUE obj)
154{
155 if (list->count >= list->capacity) {
156 size_t new_capacity = list->capacity == 0 ? 4 : list->capacity * 2;
157 VALUE *new_items = realloc(list->items, new_capacity * sizeof(VALUE));
158 if (!new_items) rb_bug("wbcheck: failed to reallocate object list array");
159 list->items = new_items;
160 list->capacity = new_capacity;
161 }
162 list->items[list->count++] = obj;
163}
164
165static void
166wbcheck_object_list_free(wbcheck_object_list_t *list)
167{
168 if (!list) return;
169 if (list->items) {
170 free(list->items);
171 }
172 free(list);
173}
174
175static void
176wbcheck_object_list_debug_print(wbcheck_object_list_t *list)
177{
178 if (!wbcheck_debug_enabled) return;
179 for (size_t i = 0; i < list->count; i++) {
180 char buff[0x100];
181 fprintf(stderr, "-> %s\n", rb_raw_obj_info(buff, sizeof(buff), list->items[i]));
182 }
183}
184
185static bool
186wbcheck_object_list_contains(wbcheck_object_list_t *list, VALUE obj)
187{
188 for (size_t i = 0; i < list->count; i++) {
189 if (list->items[i] == obj) {
190 return true;
191 }
192 }
193 return false;
194}
195
196// Information tracked for each object
197typedef struct {
198 size_t alloc_size; // Allocated size (static)
199 bool wb_protected; // Write barrier protection status (static)
200 VALUE finalizers; // Ruby Array of finalizers like [finalizer1, finalizer2, ...]
201 wbcheck_object_list_t *gc_mark_snapshot; // Snapshot of references from last GC mark
202 wbcheck_object_list_t *mark_maybe_snapshot; // Conservative refs reported via mark_maybe; needed for liveness, not verifiable
203 wbcheck_object_list_t *writebarrier_children; // References added via write barriers since last snapshot
204 wbcheck_object_state_t state; // Current state in verification lifecycle
205 wbcheck_color_t color; // Tri-color marking color
207
208// Finalizer job types
210 struct wbcheck_final_job *next;
211 enum {
212 WBCHECK_FINAL_JOB_DFREE,
213 WBCHECK_FINAL_JOB_FINALIZE,
214 } kind;
215 union {
216 struct {
217 void (*func)(void *);
218 void *data;
219 } dfree;
220 struct {
221 VALUE finalizer_array;
222 } finalize;
223 } as;
224};
225
226// wbcheck objspace structure to track all objects
227typedef struct {
228 st_table *object_table; // Hash table to track all allocated objects (VALUE -> rb_wbcheck_object_info_t*)
229 wbcheck_object_list_t *objects_to_capture; // Objects that need initial reference capture
230 wbcheck_object_list_t *objects_to_verify; // Objects that need verification after write barriers
231 wbcheck_object_list_t *current_refs; // Current list for collecting references during marking
232 wbcheck_object_list_t *current_maybe_refs; // Current list for collecting mark_maybe references during marking
233 wbcheck_object_list_t *mark_queue; // Queue of gray objects for tri-color marking
234 wbcheck_object_list_t *weak_references; // Objects holding weak references, found during marking
235 wbcheck_phase_t phase; // Current GC phase
236 bool gc_enabled; // Whether GC is allowed to run
237 bool user_gc_disabled; // Whether GC was disabled by the user (GC.disable)
238 bool gc_stress; // GC stress mode (run GC on every allocation)
239 size_t gc_threshold; // Trigger GC when object count reaches this
240 size_t missed_write_barrier_parents; // Number of parent objects with missed write barriers
241 size_t missed_write_barrier_children; // Total number of missed write barriers detected
242 size_t simulated_gc_count; // Simulated GC count incremented on each GC.start
243 bool measure_total_time; // Whether to accumulate :time in stats
244 struct wbcheck_final_job *finalizer_jobs; // Linked list of finalizer jobs
245 rb_nativethread_lock_t finalizer_lock; // Protects finalizer_jobs list
246 rb_postponed_job_handle_t finalizer_postponed_job; // Postponed job handle for finalizers
247 struct rb_gc_vm_context vm_context;
249
250// Global objspace pointer for accessing from obj_slot_size function
251static rb_wbcheck_objspace_t *wbcheck_global_objspace = NULL;
252
253// Forward declarations
254static void wbcheck_foreach_object(rb_wbcheck_objspace_t *objspace, int (*callback)(VALUE obj, rb_wbcheck_object_info_t *info, void *data), void *data);
255static int wbcheck_verify_all_references_callback(VALUE obj, rb_wbcheck_object_info_t *info, void *data);
256static int wbcheck_update_all_snapshots_callback(VALUE obj, rb_wbcheck_object_info_t *info, void *data);
257static void wbcheck_run_finalizers_for_object(VALUE obj, rb_wbcheck_object_info_t *info);
258static void gc_run_finalizers(void *data);
259static void make_final_job(rb_wbcheck_objspace_t *objspace, VALUE obj, VALUE finalizer_array);
260
261// Helper functions for object tracking
263wbcheck_get_object_info(VALUE obj)
264{
265 // Objspace must be initialized by this point
266 GC_ASSERT(wbcheck_global_objspace);
267
268 st_data_t value;
269 if (st_lookup(wbcheck_global_objspace->object_table, (st_data_t)obj, &value)) {
270 return (rb_wbcheck_object_info_t *)value;
271 }
272
273 fprintf(stderr, "wbcheck: object not found in tracking table\n");
274 char buff[0x100];
275 fprintf(stderr, "%s\n", rb_raw_obj_info(buff, sizeof(buff), obj));
276
277 // Force ASAN crash?
278 ((volatile VALUE *)obj)[0];
279
280 // Object not found in tracking table - this should never happen
281 rb_bug("wbcheck: object not found in tracking table");
282}
283
284static void
285wbcheck_report_error(void *objspace_ptr, VALUE parent_obj, wbcheck_object_list_t *current_refs, wbcheck_object_list_t *gc_mark_snapshot, wbcheck_object_list_t *writebarrier_children, wbcheck_object_list_t *missed_refs)
286{
288
289 rb_wbcheck_object_info_t *parent_info = wbcheck_get_object_info(parent_obj);
290
291 size_t snapshot_count = gc_mark_snapshot ? gc_mark_snapshot->count : 0;
292 size_t wb_count = writebarrier_children ? writebarrier_children->count : 0;
293
294 fprintf(stderr, "WBCHECK ERROR: Missed write barrier detected!\n");
295 fprintf(stderr, " Parent object: %p (wb_protected: %s)\n",
296 (void *)parent_obj, parent_info->wb_protected ? "true" : "false");
297 char buff[0x100];
298 fprintf(stderr, " %s\n", rb_raw_obj_info(buff, sizeof(buff), parent_obj));
299 fprintf(stderr, " Reference counts - snapshot: %zu, writebarrier: %zu, current: %zu, missed: %zu\n",
300 snapshot_count, wb_count, current_refs->count, missed_refs->count);
301
302 for (size_t i = 0; i < missed_refs->count; i++) {
303 VALUE missed_ref = missed_refs->items[i];
304 char buff[0x100];
305 fprintf(stderr, " Missing reference to: %p\n %s\n", (void *)missed_ref, rb_raw_obj_info(buff, sizeof(buff), missed_ref));
306 }
307
308 fprintf(stderr, "\n");
309 objspace->missed_write_barrier_parents++;
310 objspace->missed_write_barrier_children += missed_refs->count;
311}
312
313// Fail if a verification sweep reported any missed write barriers. Called at
314// each sweep boundary so every offender in the sweep is printed before we fail.
315static void
316wbcheck_abort_if_errors(rb_wbcheck_objspace_t *objspace)
317{
318 if (objspace->missed_write_barrier_parents > 0) {
319 fflush(stderr);
320 rb_bug("wbcheck: missed write barrier detected (%zu object(s), %zu reference(s))",
321 objspace->missed_write_barrier_parents,
322 objspace->missed_write_barrier_children);
323 }
324}
325
326static void
327wbcheck_compare_references(void *objspace_ptr, VALUE parent_obj, wbcheck_object_list_t *current_refs, wbcheck_object_list_t *gc_mark_snapshot, wbcheck_object_list_t *writebarrier_children)
328{
330 (void)objspace;
331
332 size_t snapshot_count = gc_mark_snapshot ? gc_mark_snapshot->count : 0;
333 size_t wb_count = writebarrier_children ? writebarrier_children->count : 0;
334
335 WBCHECK_DEBUG("wbcheck: comparing references for object %p\n", (void *)parent_obj);
336 WBCHECK_DEBUG("wbcheck: current refs: %zu, snapshot refs: %zu, wb refs: %zu\n",
337 current_refs->count, snapshot_count, wb_count);
338
339 // Collect missed references (lazily allocated)
340 wbcheck_object_list_t *missed_refs = NULL;
341
342 // Use circular comparison for better performance when lists are mostly similar
343 size_t snapshot_idx = 0;
344
345 // Check each object in current_refs to see if it's in either stored list
346 for (size_t i = 0; i < current_refs->count; i++) {
347 VALUE current_ref = current_refs->items[i];
348
349 // Usually the lists are nearly identical. We take advantage of this by
350 // attempting to loop over both lists in sequence. When the next element
351 // of the snapshot doesn't match the next element of our current_refs,
352 // we'll loop around the list to try to find it and continue from that
353 // match, so any runs of identical items can be matched efficiently.
354 //
355 // Pathologically this is O(N**2), but is O(N * num_changes)
356 bool found_in_snapshot = false;
357 if (gc_mark_snapshot && snapshot_count > 0) {
358 size_t start_idx = snapshot_idx;
359 do {
360 if (gc_mark_snapshot->items[snapshot_idx] == current_ref) {
361 found_in_snapshot = true;
362 snapshot_idx++;
363 if (snapshot_idx >= snapshot_count) snapshot_idx = 0;
364 break;
365 }
366 snapshot_idx++;
367 if (snapshot_idx >= snapshot_count) snapshot_idx = 0;
368 } while (snapshot_idx != start_idx);
369 }
370
371 if (found_in_snapshot) {
372 continue;
373 }
374
375 // Built-in immortal classes can be assigned via RBASIC_SET_CLASS_RAW,
376 // which bypasses the write barrier. They're pinned as VM roots and
377 // can never be collected, so a missing WB to them is harmless.
378 if (RB_TYPE_P(current_ref, T_CLASS) && FL_TEST_RAW(current_ref, WBCHECK_RCLASS_IS_ROOT)) {
379 continue;
380 }
381
382 // Self reference... Weird but okay I guess
383 if (current_ref == parent_obj) {
384 continue;
385 }
386
387
388 // Check if reference exists in writebarrier_children
389 if (writebarrier_children && wbcheck_object_list_contains(writebarrier_children, current_ref)) {
390 continue;
391 }
392
393 // If we get here, the reference wasn't found in either list
394 // Lazily allocate missed_refs list on first miss
395 if (!missed_refs) {
396 missed_refs = wbcheck_object_list_init();
397 }
398 wbcheck_object_list_append(missed_refs, current_ref);
399 }
400
401 // Report any errors found
402 if (missed_refs) {
403 wbcheck_report_error(objspace_ptr, parent_obj, current_refs, gc_mark_snapshot, writebarrier_children, missed_refs);
404 wbcheck_object_list_free(missed_refs);
405 }
406}
407
408static void
409wbcheck_register_object(void *objspace_ptr, VALUE obj, size_t alloc_size, bool wb_protected)
410{
412 GC_ASSERT(objspace);
413
414 // Allocate and initialize object info structure
415 rb_wbcheck_object_info_t *info = calloc(1, sizeof(rb_wbcheck_object_info_t));
416 if (!info) rb_bug("wbcheck_register_object: failed to allocate object info");
417
418 info->alloc_size = alloc_size;
419 info->wb_protected = wb_protected;
420 info->finalizers = 0; /* No finalizers initially */
421 info->gc_mark_snapshot = NULL; /* No snapshot initially */
422 info->mark_maybe_snapshot = NULL; /* No mark_maybe snapshot initially */
423 info->writebarrier_children = NULL; /* No write barrier children initially */
424 info->state = WBCHECK_STATE_CLEAR; /* Start in clear state */
425 info->color = WBCHECK_COLOR_BLACK; /* Start as black to survive current GC */
426
427 // Store object info in hash table (VALUE -> rb_wbcheck_object_info_t*)
428 st_insert(objspace->object_table, (st_data_t)obj, (st_data_t)info);
429}
430
431static void
432wbcheck_unregister_object(void *objspace_ptr, VALUE obj)
433{
436
437 if (st_delete(objspace->object_table, (st_data_t *)&obj, (st_data_t *)&info)) {
438 // Free object lists if they were allocated
439 wbcheck_object_list_free(info->gc_mark_snapshot);
440 wbcheck_object_list_free(info->mark_maybe_snapshot);
441 wbcheck_object_list_free(info->writebarrier_children);
442 free(info);
443 } else {
444 rb_bug("wbcheck_unregister_object: object not found in table");
445 }
446}
447
448// Bootup
449void *
450rb_gc_impl_objspace_alloc(void)
451{
452 wbcheck_configure_from_env();
453
455 if (!objspace) rb_bug("wbcheck: failed to allocate objspace");
456
457 objspace->objects_to_capture = wbcheck_object_list_init(); // Initialize empty list
458 objspace->objects_to_verify = wbcheck_object_list_init(); // Initialize empty list
459 objspace->current_refs = NULL; // No current refs initially
460 objspace->current_maybe_refs = NULL; // No current maybe refs initially
461 objspace->mark_queue = wbcheck_object_list_init(); // Initialize mark queue
462 objspace->weak_references = wbcheck_object_list_init(); // Initialize weak references array
463 objspace->phase = WBCHECK_PHASE_MUTATOR; // Start in mutator phase
464 objspace->gc_enabled = true; // GC enabled by default (like default GC)
465 objspace->gc_stress = false; // GC stress disabled by default
466 objspace->gc_threshold = 1000; // Start with 1000 objects, will adjust after first GC
467 objspace->missed_write_barrier_parents = 0; // No errors found yet
468 objspace->missed_write_barrier_children = 0; // No errors found yet
469 objspace->simulated_gc_count = 0; // Start with GC count of 0
470 objspace->measure_total_time = true; // On by default
471
472 return objspace;
473}
474
475void
476rb_gc_impl_objspace_init(void *objspace_ptr)
477{
478 rb_wbcheck_objspace_t *objspace = objspace_ptr;
479
480 // st_init_numtable allocates through ruby_xmalloc, which needs the objspace
481 // to exist, so the object table can't be created in objspace_alloc.
482 objspace->object_table = st_init_numtable();
483 if (!objspace->object_table) {
484 rb_bug("wbcheck: failed to create object table");
485 }
486
487 // Set up global objspace pointer for obj_slot_size function
488 wbcheck_global_objspace = objspace;
489
490 // Initialize postponed job for finalizers
491 rb_native_mutex_initialize(&objspace->finalizer_lock);
492 objspace->finalizer_postponed_job = rb_postponed_job_preregister(0, gc_run_finalizers, objspace);
493}
494
495void *
496rb_gc_impl_ractor_cache_alloc(void *objspace_ptr, void *ractor)
497{
498 // Stub implementation
499 return NULL;
500}
501
502bool
503rb_gc_impl_zjit_new_obj_fastpath(void *objspace_ptr, size_t alloc_size, VALUE flags, VALUE klass,
504 struct rb_gc_zjit_fastpath *fastpath)
505{
506 return false;
507}
508
509void
510rb_gc_impl_set_params(void *objspace_ptr)
511{
512 // Stub implementation
513}
514
515static VALUE
516gc_verify_internal_consistency(VALUE self)
517{
518 return Qnil;
519}
520
521void
522rb_gc_impl_init(void)
523{
524 VALUE gc_constants = rb_hash_new();
525 //rb_hash_aset(gc_constants, ID2SYM(rb_intern("BASE_SLOT_SIZE")), SIZET2NUM(BASE_SLOT_SIZE));
526 rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVALUE_SIZE")), SIZET2NUM(sizeof(struct RBasic) + sizeof(VALUE[RBIMPL_RVALUE_EMBED_LEN_MAX])));
527 rb_hash_aset(gc_constants, ID2SYM(rb_intern("RBASIC_SIZE")), SIZET2NUM(sizeof(struct RBasic)));
528 rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVALUE_OVERHEAD")), INT2NUM(0));
529 rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVARGC_MAX_ALLOCATE_SIZE")), SIZET2NUM(rb_gc_impl_max_allocation_size()));
530 rb_hash_aset(gc_constants, ID2SYM(rb_intern("HEAP_COUNT")), LONG2FIX(HEAP_COUNT));
531 rb_hash_aset(gc_constants, ID2SYM(rb_intern("SIZE_POOL_COUNT")), LONG2FIX(HEAP_COUNT));
532 rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVALUE_OLD_AGE")), INT2FIX(3));
533 OBJ_FREEZE(gc_constants);
534 rb_define_const(rb_mGC, "INTERNAL_CONSTANTS", gc_constants);
535
536 // no-ops for compatibility
537 rb_define_singleton_method(rb_mGC, "verify_internal_consistency", gc_verify_internal_consistency, 0);
538
542 rb_define_singleton_method(rb_mGC, "latest_compact_info", rb_f_notimplement, 0);
543 rb_define_singleton_method(rb_mGC, "verify_compaction_references", rb_f_notimplement, -1);
544 // Stub implementation
545}
546
547// Shutdown
548void
549rb_gc_impl_shutdown_free_objects(void *objspace_ptr)
550{
551 // Stub implementation
552}
553
554void
555rb_gc_impl_objspace_free(void *objspace_ptr)
556{
557 // This should free everything, but we'll just let it leak
558}
559
560void
561rb_gc_impl_ractor_cache_free(void *objspace_ptr, void *cache)
562{
563 // Stub implementation
564}
565
566// GC
567void
568rb_gc_impl_start(void *objspace_ptr, bool full_mark, bool immediate_mark, bool immediate_sweep, bool compact, bool global)
569{
571 if (objspace) {
572 objspace->simulated_gc_count++;
573 }
574
575 if (!ruby_native_thread_p()) return;
576
577 unsigned int lev = RB_GC_VM_LOCK();
578 rb_gc_vm_barrier();
579 force_gc(objspace_ptr);
580 RB_GC_VM_UNLOCK(lev);
581}
582
583bool
584rb_gc_impl_during_gc_p(void *objspace_ptr)
585{
587 return objspace->phase != WBCHECK_PHASE_MUTATOR;
588}
589
590static void
591wbcheck_prepare_heap_i(VALUE obj, void *data)
592{
593 rb_gc_prepare_heap_process_object(obj);
594}
595
596void
597rb_gc_impl_prepare_heap(void *objspace_ptr)
598{
599 rb_gc_impl_each_object(objspace_ptr, wbcheck_prepare_heap_i, NULL);
600}
601
602void
603rb_gc_impl_gc_enable(void *objspace_ptr)
604{
606 objspace->gc_enabled = true;
607}
608
609void
610rb_gc_impl_gc_disable(void *objspace_ptr, bool finish_current_gc)
611{
613 objspace->gc_enabled = false;
614}
615
616bool
617rb_gc_impl_gc_enabled_p(void *objspace_ptr)
618{
620 return objspace->gc_enabled;
621}
622
623bool
624rb_gc_impl_user_gc_disabled_set(void *objspace_ptr, bool disable)
625{
627 const bool was = objspace->user_gc_disabled;
628 objspace->user_gc_disabled = disable;
629 objspace->gc_enabled = !disable;
630 return was;
631}
632
633bool
634rb_gc_impl_user_gc_disabled_p(void *objspace_ptr)
635{
637 return objspace->user_gc_disabled;
638}
639
640void
641rb_gc_impl_gc_rest(void *objspace_ptr)
642{
643 /* wbcheck GCs are stop-the-world: no incremental state to finish. */
644}
645
646void
647rb_gc_impl_stress_set(void *objspace_ptr, VALUE flag)
648{
650 objspace->gc_stress = RTEST(flag);
651}
652
653VALUE
654rb_gc_impl_stress_get(void *objspace_ptr)
655{
657 return objspace->gc_stress ? Qtrue : Qfalse;
658}
659
660VALUE
661rb_gc_impl_config_get(void *objspace_ptr)
662{
663 return rb_hash_new();
664}
665
666void
667rb_gc_impl_config_set(void *objspace_ptr, VALUE hash)
668{
669}
670
671struct rb_gc_vm_context *
672rb_gc_impl_get_vm_context(void *objspace_ptr)
673{
674 rb_wbcheck_objspace_t *objspace = objspace_ptr;
675
676 return &objspace->vm_context;
677}
678
680wbcheck_collect_references_from_object(VALUE obj, rb_wbcheck_object_info_t *info)
681{
682 rb_wbcheck_objspace_t *objspace = wbcheck_global_objspace;
683
684 // Use combination of writebarrier children and last snapshot as capacity hint
685 size_t snapshot_count = (info->gc_mark_snapshot) ? info->gc_mark_snapshot->count : 0;
686 size_t wb_children_count = (info->writebarrier_children) ? info->writebarrier_children->count : 0;
687 size_t capacity_hint = snapshot_count + wb_children_count;
688 wbcheck_object_list_t *new_list = wbcheck_object_list_init_with_capacity(capacity_hint);
689
690 // Set up objspace state for marking. current_maybe_refs is allocated lazily
691 // by rb_gc_impl_mark_maybe, since most objects have no conservative refs.
692 objspace->current_refs = new_list;
693 objspace->current_maybe_refs = NULL;
694 objspace->phase = WBCHECK_PHASE_SNAPSHOT;
695 rb_gc_initialize_vm_context(&objspace->vm_context);
696
697 // Use the marking infrastructure to collect references
698 rb_gc_mark_children(objspace, obj);
699
700 // Clean up objspace state
701 objspace->phase = WBCHECK_PHASE_MUTATOR;
702 objspace->current_refs = NULL;
703
704 // Update the mark_maybe snapshot in place. These references don't participate
705 // in verification, but we need to keep them so full GC can mark them gray.
706 wbcheck_object_list_free(info->mark_maybe_snapshot);
707 info->mark_maybe_snapshot = objspace->current_maybe_refs;
708 objspace->current_maybe_refs = NULL;
709
710 if (wbcheck_debug_enabled) {
711 WBCHECK_DEBUG("wbcheck: collected %zu references from %p\n", new_list->count, (void *)obj);
712 char buff[0x100];
713 fprintf(stderr, "%s\n", rb_raw_obj_info(buff, sizeof(buff), obj));
714 wbcheck_object_list_debug_print(new_list);
715 }
716
717 return new_list;
718}
719
720static void
721wbcheck_collect_initial_references(void *objspace_ptr, VALUE obj)
722{
723 WBCHECK_DEBUG("wbcheck: collecting initial references from %p:\n", obj);
724 wbcheck_debug_obj_info_dump(obj);
725
726 // Get the object info and set the initial GC mark snapshot
727 rb_wbcheck_object_info_t *info = wbcheck_get_object_info(obj);
728 wbcheck_object_list_t *new_list = wbcheck_collect_references_from_object(obj, info);
729 RUBY_ASSERT(!info->gc_mark_snapshot);
730 RUBY_ASSERT(info->state == WBCHECK_STATE_CLEAR);
731 info->gc_mark_snapshot = new_list; // Set the initial snapshot
732 info->state = WBCHECK_STATE_MARKED; // Transition to marked state
733}
734
735static void
736wbcheck_verify_object_references(void *objspace_ptr, VALUE obj)
737{
738 rb_wbcheck_object_info_t *info = wbcheck_get_object_info(obj);
739
740 // Ignore objects which are not write barrier protected
741 if (!info->wb_protected) {
742 return;
743 }
744
745 // We hadn't captured initial references
746 if (info->state == WBCHECK_STATE_CLEAR) {
747 RUBY_ASSERT(!info->gc_mark_snapshot);
748 return;
749 }
750
751 WBCHECK_DEBUG("wbcheck: verifying references for object:\n");
752 wbcheck_debug_obj_info_dump(obj);
753
754 // Get the current references from the object
755 wbcheck_object_list_t *current_refs = wbcheck_collect_references_from_object(obj, info);
756
757 // Check for useless write barriers before clearing them
758 if (wbcheck_warn_useless_wb_enabled && info->writebarrier_children) {
759 for (size_t i = 0; i < info->writebarrier_children->count; i++) {
760 VALUE wb_ref = info->writebarrier_children->items[i];
761 if (!wbcheck_object_list_contains(current_refs, wb_ref)) {
762 fprintf(stderr, "WBCHECK WARNING: Potentially useless write barrier detected for object %p\n", (void *)obj);
763 fprintf(stderr, " Write barrier was recorded for reference to %p, but object no longer references it\n", (void *)wb_ref);
764 char buff[0x100];
765 fprintf(stderr, " Parent: %s\n", rb_raw_obj_info(buff, sizeof(buff), obj));
766 fprintf(stderr, " Stale reference: %s\n", rb_raw_obj_info(buff, sizeof(buff), wb_ref));
767 }
768 }
769 }
770
771 // Compare current_refs against both stored lists to detect missed write barriers
772 wbcheck_compare_references(objspace_ptr, obj, current_refs, info->gc_mark_snapshot, info->writebarrier_children);
773
774 // Update the snapshot with current references and clear write barrier children
775 wbcheck_object_list_free(info->gc_mark_snapshot);
776 wbcheck_object_list_free(info->writebarrier_children);
777 info->gc_mark_snapshot = current_refs;
778 info->writebarrier_children = NULL;
779 info->state = WBCHECK_STATE_MARKED; // Back to marked state after verification
780}
781
782// Mark object as gray (add to mark queue)
783static void
784wbcheck_mark_gray(rb_wbcheck_objspace_t *objspace, VALUE obj)
785{
786 if (RB_SPECIAL_CONST_P(obj)) return;
787
788 st_data_t value;
789 if (!st_lookup(objspace->object_table, (st_data_t)obj, &value)) {
790 rb_bug("wbcheck: asked to mark object %p not in our object table", (void *)obj);
791 }
792
794 if (info->color != WBCHECK_COLOR_WHITE) {
795 return; // Already marked
796 }
797
798 info->color = WBCHECK_COLOR_GRAY;
799 wbcheck_object_list_append(objspace->mark_queue, obj);
800
802 wbcheck_object_list_append(objspace->weak_references, obj);
803 }
804
805 WBCHECK_DEBUG("wbcheck: marked gray: %p\n", (void *)obj);
806}
807
808// Reset all objects to white
809static int
810st_foreach_reset_white(st_data_t key, st_data_t val, st_data_t arg)
811{
813 info->color = WBCHECK_COLOR_WHITE;
814 return ST_CONTINUE;
815}
816
817// Mark all finalizer arrays to keep them alive during GC
818static int
819st_foreach_mark_finalizers(st_data_t key, st_data_t val, st_data_t arg)
820{
823
824 if (info->finalizers) {
825 wbcheck_mark_gray(objspace, info->finalizers);
826 }
827
828 return ST_CONTINUE;
829}
830
831// Full mark phase using tri-color marking with snapshots
832static void
833wbcheck_mark_phase(rb_wbcheck_objspace_t *objspace)
834{
835 WBCHECK_DEBUG("wbcheck: starting GC mark phase\n");
836
837 objspace->phase = WBCHECK_PHASE_FULL_GC;
838 rb_gc_initialize_vm_context(&objspace->vm_context);
839
840 // Clear mark queue and reset all objects to white
841 objspace->mark_queue->count = 0;
842 st_foreach(objspace->object_table, st_foreach_reset_white, 0);
843
844 // Mark all finalizer arrays first to keep them alive
845 st_foreach(objspace->object_table, st_foreach_mark_finalizers, (st_data_t)objspace);
846
847 // Mark finalizer arrays in pending jobs to keep them alive.
848 // No lock needed: all other threads are stopped during GC.
849 struct wbcheck_final_job *job = objspace->finalizer_jobs;
850 while (job != NULL) {
851 switch (job->kind) {
852 case WBCHECK_FINAL_JOB_DFREE:
853 break;
854 case WBCHECK_FINAL_JOB_FINALIZE:
855 wbcheck_mark_gray(objspace, job->as.finalize.finalizer_array);
856 break;
857 default:
858 rb_bug("wbcheck_mark_phase: unknown final job type %d", job->kind);
859 }
860 job = job->next;
861 }
862
863 // Mark roots gray
864 rb_gc_save_machine_context();
865 rb_gc_mark_roots(objspace, NULL);
866
867 // Process gray queue until empty
868 while (objspace->mark_queue->count > 0) {
869 // Get last object from queue (LIFO)
870 VALUE obj = objspace->mark_queue->items[--objspace->mark_queue->count];
871
872 st_data_t value;
873 if (st_lookup(objspace->object_table, (st_data_t)obj, &value)) {
875 if (info->color == WBCHECK_COLOR_GRAY) {
876 // Mark all children from snapshot gray
877 if (info->gc_mark_snapshot) {
878 for (size_t i = 0; i < info->gc_mark_snapshot->count; i++) {
879 wbcheck_mark_gray(objspace, info->gc_mark_snapshot->items[i]);
880 }
881 }
882
883 // Conservatively-scanned children must also be kept alive
884 if (info->mark_maybe_snapshot) {
885 for (size_t i = 0; i < info->mark_maybe_snapshot->count; i++) {
886 wbcheck_mark_gray(objspace, info->mark_maybe_snapshot->items[i]);
887 }
888 }
889
890 // Mark this object black
891 info->color = WBCHECK_COLOR_BLACK;
892 WBCHECK_DEBUG("wbcheck: marked black: %p\n", (void *)obj);
893 }
894 }
895 }
896
897 objspace->phase = WBCHECK_PHASE_MUTATOR;
898
899 WBCHECK_DEBUG("wbcheck: tri-color mark phase complete\n");
900}
901
902// Sweep phase callback - free white objects
903static int
904wbcheck_sweep_callback(st_data_t key, st_data_t val, st_data_t arg, int error)
905{
906 VALUE obj = (VALUE)key;
909
910 if (info->color == WBCHECK_COLOR_WHITE) {
911 WBCHECK_DEBUG("wbcheck: sweeping unmarked object %p\n", (void *)obj);
912
913 rb_gc_event_hook(obj, RUBY_INTERNAL_EVENT_FREEOBJ);
914
915 // Clear weak references first
916 rb_gc_obj_free_vm_weak_references(obj);
917
918 // Queue finalizers for postponed job if they exist
919 if (info->finalizers) {
920 make_final_job(objspace, obj, info->finalizers);
921 rb_postponed_job_trigger(objspace->finalizer_postponed_job);
922 }
923
924 // Call rb_gc_obj_free which handles finalizers/zombies
925 if (rb_gc_obj_free(objspace, obj)) {
926 // Object was actually freed, clean up our tracking
927 wbcheck_object_list_free(info->gc_mark_snapshot);
928 wbcheck_object_list_free(info->mark_maybe_snapshot);
929 wbcheck_object_list_free(info->writebarrier_children);
930 free(info);
931
932 // Free the actual object memory
933 free((void *)obj);
934
935 return ST_DELETE; // Remove from hash table
936 } else {
937 // Object became a zombie - it will be freed by postponed job
938 // Remove from tracking since we can't safely access it anymore
939 wbcheck_object_list_free(info->gc_mark_snapshot);
940 wbcheck_object_list_free(info->mark_maybe_snapshot);
941 wbcheck_object_list_free(info->writebarrier_children);
942 free(info);
943
944 // Free the actual object memory
945 free((void *)obj);
946
947 return ST_DELETE; // Remove from hash table
948 }
949 }
950
951 return ST_CONTINUE; // Keep marked objects
952}
953
954static void
955wbcheck_sweep_phase(rb_wbcheck_objspace_t *objspace)
956{
957 WBCHECK_DEBUG("wbcheck: starting sweep phase\n");
958
959 size_t objects_before = st_table_size(objspace->object_table);
960
961 // Sweep unmarked objects
962 st_foreach_check(objspace->object_table, wbcheck_sweep_callback, (st_data_t)objspace, 0);
963
964 size_t objects_after = st_table_size(objspace->object_table);
965 size_t freed_objects = objects_before - objects_after;
966
967 // Update GC threshold: 2x the live set after GC
968 objspace->gc_threshold = objects_after * 2;
969
970 WBCHECK_DEBUG("wbcheck: sweep phase complete - freed %zu objects (%zu -> %zu), new threshold: %zu\n",
971 freed_objects, objects_before, objects_after, objspace->gc_threshold);
972}
973
974// Process weak references after marking - call rb_gc_handle_weak_references
975// on each object that was flagged with RUBY_FL_WEAK_REFERENCE and collected
976// during the mark phase.
977static void
978wbcheck_process_weak_references(rb_wbcheck_objspace_t *objspace)
979{
980 WBCHECK_DEBUG("wbcheck: processing %zu weak reference objects\n", objspace->weak_references->count);
981
982 for (size_t i = 0; i < objspace->weak_references->count; i++) {
983 VALUE obj = objspace->weak_references->items[i];
984 rb_gc_handle_weak_references(obj);
985 }
986
987 objspace->weak_references->count = 0;
988}
989
990// Full GC: verify all objects then mark from roots
991static void
992wbcheck_full_gc(rb_wbcheck_objspace_t *objspace)
993{
994 WBCHECK_DEBUG("wbcheck: starting full GC\n");
995
996 rb_gc_event_hook(0, RUBY_INTERNAL_EVENT_GC_ENTER);
997 rb_gc_event_hook(0, RUBY_INTERNAL_EVENT_GC_START);
998
999 // First, update snapshots for all objects (verify wb_protected ones)
1000 WBCHECK_DEBUG("wbcheck: updating snapshots for all objects\n");
1001 wbcheck_foreach_object(objspace, wbcheck_update_all_snapshots_callback, objspace);
1002
1003 // Now start tri-color marking
1004 wbcheck_mark_phase(objspace);
1005
1006 rb_gc_event_hook(0, RUBY_INTERNAL_EVENT_GC_END_MARK);
1007
1008 // Process weak references after marking, before sweeping
1009 wbcheck_process_weak_references(objspace);
1010
1011 // Sweep unmarked objects
1012 wbcheck_sweep_phase(objspace);
1013
1014 rb_gc_event_hook(0, RUBY_INTERNAL_EVENT_GC_END_SWEEP);
1015 rb_gc_event_hook(0, RUBY_INTERNAL_EVENT_GC_EXIT);
1016
1017 WBCHECK_DEBUG("wbcheck: full GC complete\n");
1018}
1019
1020static void
1021gc_step(void *objspace_ptr, bool force)
1022{
1024
1025 // Not initialized yet
1026 if (!objspace) return;
1027
1028 if (!objspace->gc_enabled && !force) return;
1029
1030 // Process all objects that need verification after write barriers (if enabled)
1031 if (wbcheck_verify_after_wb_enabled) {
1032 for (size_t i = 0; i < objspace->objects_to_verify->count; i++) {
1033 VALUE obj = objspace->objects_to_verify->items[i];
1034 wbcheck_verify_object_references(objspace_ptr, obj);
1035 }
1036
1037 // Clear the list after processing
1038 objspace->objects_to_verify->count = 0;
1039
1040 wbcheck_abort_if_errors(objspace);
1041 }
1042
1043 // Process all objects that need initial reference capture
1044 for (size_t i = 0; i < objspace->objects_to_capture->count; i++) {
1045 VALUE obj = objspace->objects_to_capture->items[i];
1046 wbcheck_collect_initial_references(objspace_ptr, obj);
1047 }
1048
1049 // Clear the list after processing
1050 objspace->objects_to_capture->count = 0;
1051
1052 // Run full GC if forced, if we exceed the threshold, or if gc_stress is enabled
1053 if (ruby_native_thread_p() &&
1054 (force ||
1055 (objspace->gc_enabled &&
1056 (objspace->gc_stress || st_table_size(objspace->object_table) >= objspace->gc_threshold)))) {
1057 wbcheck_full_gc(objspace);
1058 wbcheck_abort_if_errors(objspace);
1059 }
1060
1061}
1062
1063static void
1064maybe_gc(void *objspace_ptr)
1065{
1066 gc_step(objspace_ptr, false);
1067}
1068
1069static void
1070force_gc(void *objspace_ptr)
1071{
1072 gc_step(objspace_ptr, true);
1073}
1074
1075int ruby_thread_has_gvl_p(void);
1076
1077static void *
1078lock_and_maybe_gc_gvl(void *objspace_ptr)
1079{
1080 unsigned int lev = RB_GC_VM_LOCK();
1081 rb_gc_vm_barrier();
1082
1083 maybe_gc(objspace_ptr);
1084
1085 RB_GC_VM_UNLOCK(lev);
1086 return NULL;
1087}
1088
1089static void
1090lock_and_maybe_gc(void *objspace_ptr)
1091{
1092 if (!ruby_native_thread_p()) return;
1093
1094 if (!ruby_thread_has_gvl_p()) {
1095 rb_thread_call_with_gvl(lock_and_maybe_gc_gvl, objspace_ptr);
1096 }
1097 else {
1098 lock_and_maybe_gc_gvl(objspace_ptr);
1099 }
1100}
1101
1102VALUE
1103rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags, bool wb_protected, size_t alloc_size, size_t *actual_alloc_size)
1104{
1105 unsigned int lev = RB_GC_VM_LOCK();
1106 rb_gc_vm_barrier();
1107
1108 // Check if we should trigger GC before allocating
1109 maybe_gc(objspace_ptr);
1110
1111 // Ensure minimum allocation size of BASE_SLOT_SIZE
1112 alloc_size = rb_gc_impl_size_slot_size(objspace_ptr, alloc_size);
1113
1114 // Allocate memory for the object
1115 VALUE *mem = malloc(alloc_size);
1116 if (!mem) rb_bug("FIXME: malloc failed");
1117
1118 *actual_alloc_size = alloc_size;
1119
1120 // Initialize the object
1121 VALUE obj = (VALUE)mem;
1122 RBASIC(obj)->flags = flags;
1123 *((VALUE *)&RBASIC(obj)->klass) = klass;
1124
1125 // Register the new object in our tracking table
1126 wbcheck_register_object(objspace_ptr, obj, alloc_size, wb_protected);
1127
1128 // Add this object to the list of objects that need initial reference capture
1130 wbcheck_object_list_append(objspace->objects_to_capture, obj);
1131
1132 RB_GC_VM_UNLOCK(lev);
1133 return obj;
1134}
1135
1136size_t
1137rb_gc_impl_obj_slot_size(VALUE obj)
1138{
1139 unsigned int lev = RB_GC_VM_LOCK();
1140
1141 rb_wbcheck_object_info_t *info = wbcheck_get_object_info(obj);
1142 size_t result = info->alloc_size;
1143
1144 RB_GC_VM_UNLOCK(lev);
1145 return result;
1146}
1147
1148size_t
1149rb_gc_impl_size_slot_size(void *objspace_ptr, size_t size)
1150{
1151 for (int i = 0; i < HEAP_COUNT; i++) {
1152 if (size <= heap_sizes[i]) return heap_sizes[i];
1153 }
1154 rb_bug("size too big");
1155}
1156
1157bool
1158rb_gc_impl_size_allocatable_p(size_t size)
1159{
1160 // Only allow sizes up to the largest heap size
1161 return size <= rb_gc_impl_max_allocation_size();
1162}
1163
1164size_t
1165rb_gc_impl_max_allocation_size(void)
1166{
1167 return MAX_HEAP_SIZE;
1168}
1169
1170// Malloc
1171void *
1172rb_gc_impl_malloc(void *objspace_ptr, size_t size, bool gc_allowed)
1173{
1174 if (gc_allowed) {
1175 lock_and_maybe_gc(objspace_ptr);
1176 }
1177 return malloc(size);
1178}
1179
1180void *
1181rb_gc_impl_calloc(void *objspace_ptr, size_t size, bool gc_allowed)
1182{
1183 if (gc_allowed) {
1184 lock_and_maybe_gc(objspace_ptr);
1185 }
1186 return calloc(1, size);
1187}
1188
1189void *
1190rb_gc_impl_realloc(void *objspace_ptr, void *ptr, size_t new_size, size_t old_size, bool gc_allowed)
1191{
1192 if (gc_allowed) {
1193 lock_and_maybe_gc(objspace_ptr);
1194 }
1195 return realloc(ptr, new_size);
1196}
1197
1198void
1199rb_gc_impl_free(void *objspace_ptr, void *ptr, size_t old_size)
1200{
1201 free(ptr);
1202}
1203
1204void
1205rb_gc_impl_adjust_memory_usage(void *objspace_ptr, ssize_t diff)
1206{
1207 // For wbcheck, we don't track memory usage
1208}
1209
1210// Marking
1211static void
1213{
1214 WBCHECK_DEBUG("wbcheck: gc_mark called\n");
1215 wbcheck_debug_obj_info_dump(obj);
1216
1217 if (RB_SPECIAL_CONST_P(obj)) return;
1218
1219 switch (objspace->phase) {
1220 case WBCHECK_PHASE_SNAPSHOT:
1221 // Collecting references during verification
1222 GC_ASSERT(objspace->current_refs);
1223 wbcheck_object_list_append(objspace->current_refs, obj);
1224 break;
1225 case WBCHECK_PHASE_FULL_GC:
1226 // Marking during full GC
1227 wbcheck_mark_gray(objspace, obj);
1228 break;
1229 case WBCHECK_PHASE_MUTATOR:
1230 // Should not be called during mutator phase
1231 rb_bug("wbcheck: gc_mark called during mutator phase");
1232 break;
1233 }
1234}
1235
1236void
1237rb_gc_impl_mark(void *objspace_ptr, VALUE obj)
1238{
1239 rb_wbcheck_objspace_t *objspace = objspace_ptr;
1240 gc_mark(objspace, obj);
1241}
1242
1243void
1244rb_gc_impl_mark_and_move(void *objspace_ptr, VALUE *ptr)
1245{
1246 rb_wbcheck_objspace_t *objspace = objspace_ptr;
1247 gc_mark(objspace, *ptr);
1248}
1249
1250void
1251rb_gc_impl_mark_and_pin(void *objspace_ptr, VALUE obj)
1252{
1253 rb_wbcheck_objspace_t *objspace = objspace_ptr;
1254 gc_mark(objspace, obj);
1255}
1256
1257void
1258rb_gc_impl_mark_maybe(void *objspace_ptr, VALUE obj)
1259{
1260 rb_wbcheck_objspace_t *objspace = objspace_ptr;
1261
1262 if (!rb_gc_impl_live_object_p(objspace_ptr, (void *)obj)) return;
1263
1264 switch (objspace->phase) {
1265 case WBCHECK_PHASE_SNAPSHOT:
1266 // We don't know if this is actually a reference or just a value
1267 // that looks like one, so we can't expect a write barrier for it.
1268 // Keep it separate from the verifiable refs, but retain it so full
1269 // GC can mark the target gray if it does turn out to be live.
1270 if (!objspace->current_maybe_refs) {
1271 objspace->current_maybe_refs = wbcheck_object_list_init();
1272 }
1273 wbcheck_object_list_append(objspace->current_maybe_refs, obj);
1274 break;
1275 case WBCHECK_PHASE_FULL_GC:
1276 wbcheck_mark_gray(objspace, obj);
1277 break;
1278 case WBCHECK_PHASE_MUTATOR:
1279 rb_bug("wbcheck: rb_gc_impl_mark_maybe called during mutator phase");
1280 break;
1281 }
1282}
1283
1284// Weak references
1285void
1286rb_gc_impl_declare_weak_references(void *objspace_ptr, VALUE obj)
1287{
1289}
1290
1291bool
1292rb_gc_impl_handle_weak_references_alive_p(void *objspace_ptr, VALUE obj)
1293{
1295
1296 st_data_t value;
1297 if (st_lookup(objspace->object_table, (st_data_t)obj, &value)) {
1299 return info->color != WBCHECK_COLOR_WHITE;
1300 }
1301
1302 return false;
1303}
1304
1305// Compaction
1306void
1307rb_gc_impl_register_pinning_obj(void *objspace_ptr, VALUE obj)
1308{
1309 /* no-op */
1310}
1311
1312bool
1313rb_gc_impl_object_moved_p(void *objspace_ptr, VALUE obj)
1314{
1315 // Stub implementation
1316 return false;
1317}
1318
1319bool
1320rb_gc_impl_pinned_p(void *objspace_ptr, VALUE obj)
1321{
1322 // Stub implementation
1323 return false;
1324}
1325
1326VALUE
1327rb_gc_impl_location(void *objspace_ptr, VALUE value)
1328{
1329 // Stub implementation
1330 return Qnil;
1331}
1332
1333// Write barriers
1334void
1335rb_gc_impl_writebarrier(void *objspace_ptr, VALUE a, VALUE b)
1336{
1337 if (RB_SPECIAL_CONST_P(b)) return;
1338
1339 unsigned int lev = RB_GC_VM_LOCK_NO_BARRIER();
1340
1341 rb_wbcheck_objspace_t *objspace = objspace_ptr;
1342
1343 // Get the object info for the parent object (a)
1344 rb_wbcheck_object_info_t *info = wbcheck_get_object_info(a);
1345
1346 // Only record the write barrier if we have a valid snapshot
1347 if (info->state != WBCHECK_STATE_CLEAR) {
1348 RUBY_ASSERT(info->gc_mark_snapshot);
1349
1350 // Initialize writebarrier_children list if it doesn't exist
1351 if (!info->writebarrier_children) {
1352 info->writebarrier_children = wbcheck_object_list_init();
1353 }
1354
1355 // Add the new reference to the write barrier children list
1356 wbcheck_object_list_append(info->writebarrier_children, b);
1357
1358 WBCHECK_DEBUG("wbcheck: write barrier recorded reference from %p to %p\n", (void *)a, (void *)b);
1359
1360 // If verification after write barrier is enabled, queue the object for verification
1361 if (wbcheck_verify_after_wb_enabled && info->state != WBCHECK_STATE_DIRTY) {
1362 WBCHECK_DEBUG("wbcheck: queueing object for verification after write barrier\n");
1363 info->state = WBCHECK_STATE_DIRTY; // Mark as dirty
1364 wbcheck_object_list_append(objspace->objects_to_verify, a);
1365 }
1366 } else {
1367 WBCHECK_DEBUG("wbcheck: write barrier skipped (snapshot not initialized) from %p to %p\n", (void *)a, (void *)b);
1368 }
1369
1370 RB_GC_VM_UNLOCK_NO_BARRIER(lev);
1371}
1372
1373void
1374rb_gc_impl_writebarrier_unprotect(void *objspace_ptr, VALUE obj)
1375{
1376 WBCHECK_DEBUG("wbcheck: writebarrier_unprotect called on object %p\n", (void *)obj);
1377
1378 unsigned int lev = RB_GC_VM_LOCK_NO_BARRIER();
1379
1380 rb_wbcheck_object_info_t *info = wbcheck_get_object_info(obj);
1381 info->wb_protected = false;
1382
1383 RB_GC_VM_UNLOCK_NO_BARRIER(lev);
1384}
1385
1386void
1387rb_gc_impl_writebarrier_remember(void *objspace_ptr, VALUE obj)
1388{
1389 WBCHECK_DEBUG("wbcheck: writebarrier_remember called on object %p\n", (void *)obj);
1390
1391 unsigned int lev = RB_GC_VM_LOCK_NO_BARRIER();
1392
1394 rb_wbcheck_object_info_t *info = wbcheck_get_object_info(obj);
1395
1396 // Clear existing references since they may be stale
1397 if (info->state != WBCHECK_STATE_CLEAR) {
1398 RUBY_ASSERT(info->gc_mark_snapshot);
1399 wbcheck_object_list_free(info->gc_mark_snapshot);
1400 info->gc_mark_snapshot = NULL;
1401
1402 wbcheck_object_list_free(info->mark_maybe_snapshot);
1403 info->mark_maybe_snapshot = NULL;
1404
1405 // Only re-add to objects_to_capture if it had previous snapshot
1406 // (new objects don't need to be re-added since they'll be captured at allocation)
1407 wbcheck_object_list_append(objspace->objects_to_capture, obj);
1408
1409 // Also clear write barrier children
1410 if (info->writebarrier_children) {
1411 wbcheck_object_list_free(info->writebarrier_children);
1412 info->writebarrier_children = NULL;
1413 }
1414
1415 // Reset to clear state
1416 info->state = WBCHECK_STATE_CLEAR;
1417 }
1418 RUBY_ASSERT(!info->gc_mark_snapshot);
1419 RUBY_ASSERT(!info->mark_maybe_snapshot);
1420 RUBY_ASSERT(!info->writebarrier_children);
1421
1422 RB_GC_VM_UNLOCK_NO_BARRIER(lev);
1423}
1424
1425// Heap walking
1427 int (*callback)(VALUE obj, rb_wbcheck_object_info_t *info, void *data);
1428 void *data;
1429};
1430
1431static int
1432wbcheck_foreach_object_i(st_data_t key, st_data_t val, st_data_t arg)
1433{
1434 VALUE obj = (VALUE)key;
1436 struct wbcheck_foreach_data *foreach_data = (struct wbcheck_foreach_data *)arg;
1437
1438 return foreach_data->callback(obj, info, foreach_data->data);
1439}
1440
1441static void
1442wbcheck_foreach_object(rb_wbcheck_objspace_t *objspace, int (*callback)(VALUE obj, rb_wbcheck_object_info_t *info, void *data), void *data)
1443{
1444 struct wbcheck_foreach_data foreach_data = {
1445 .callback = callback,
1446 .data = data
1447 };
1448
1449 st_foreach(objspace->object_table, wbcheck_foreach_object_i, (st_data_t)&foreach_data);
1450}
1451
1452// Helper to collect all objects into a snapshot list
1453static int
1454wbcheck_snapshot_collector(st_data_t key, st_data_t val, st_data_t arg)
1455{
1456 VALUE obj = (VALUE)key;
1458 wbcheck_object_list_append(snapshot, obj);
1459 return ST_CONTINUE;
1460}
1461
1462// Take a snapshot of all objects for safe iteration
1463static wbcheck_object_list_t *
1464wbcheck_take_object_snapshot(rb_wbcheck_objspace_t *objspace)
1465{
1466 size_t object_count = st_table_size(objspace->object_table);
1467 wbcheck_object_list_t *snapshot = wbcheck_object_list_init_with_capacity(object_count);
1468 st_foreach(objspace->object_table, wbcheck_snapshot_collector, (st_data_t)snapshot);
1469 return snapshot;
1470}
1471
1472
1473void
1474rb_gc_impl_each_objects(void *objspace_ptr, int (*callback)(void *, void *, size_t, void *), void *data)
1475{
1477 GC_ASSERT(objspace);
1478
1479 wbcheck_object_list_t *snapshot = wbcheck_take_object_snapshot(objspace);
1480
1481 for (size_t i = 0; i < snapshot->count; i++) {
1482 VALUE obj = snapshot->items[i];
1483 st_data_t value;
1484 if (st_lookup(objspace->object_table, (st_data_t)obj, &value)) {
1486 int result = callback(
1487 (void *)obj,
1488 (void *)((char *)obj + info->alloc_size),
1489 info->alloc_size,
1490 data
1491 );
1492 if (result != 0) break;
1493 }
1494 }
1495
1496 wbcheck_object_list_free(snapshot);
1497}
1498
1499static int
1500wbcheck_each_objects_shareable_i(void *start, void *end, size_t stride, void *d)
1501{
1502 struct { int (*func)(void *, void *, size_t, void *); void *data; } *args = d;
1503 if (RB_FL_TEST_RAW((VALUE)start, RUBY_FL_SHAREABLE)) {
1504 return args->func(start, end, stride, args->data);
1505 }
1506 return 0;
1507}
1508
1509void
1510rb_gc_impl_each_objects_shareable(void *objspace_ptr, int (*callback)(void *, void *, size_t, void *), void *data)
1511{
1512 struct { int (*func)(void *, void *, size_t, void *); void *data; } args = { callback, data };
1513 rb_gc_impl_each_objects(objspace_ptr, wbcheck_each_objects_shareable_i, &args);
1514}
1515
1516void
1517rb_gc_impl_each_objects_foreign(void *objspace_ptr, int (*callback)(void *, void *, size_t, void *), void *data)
1518{
1519 /* A single objspace has no foreign objects. */
1520}
1521
1522bool
1523rb_gc_impl_multi_objspace_p(void)
1524{
1525 return false;
1526}
1527
1528void
1529rb_gc_impl_objspace_retire_gc(void *objspace_ptr)
1530{
1531}
1532
1533bool
1534rb_gc_impl_during_global_gc_p(void *objspace_ptr)
1535{
1536 /* Snapshots and full GCs both run under the VM lock after rb_gc_vm_barrier(),
1537 * so every other Ractor is stopped whenever we are out of the mutator phase. */
1539 return objspace->phase != WBCHECK_PHASE_MUTATOR;
1540}
1541
1542bool
1543rb_gc_impl_during_postmortem_p(void *objspace_ptr)
1544{
1545 return false;
1546}
1547
1548bool
1549rb_gc_impl_obj_foreign_p(void *objspace_ptr, VALUE obj)
1550{
1551 return false;
1552}
1553
1554bool
1555rb_gc_impl_shref_marked_p(void *objspace_ptr, VALUE obj)
1556{
1557 return true;
1558}
1559
1560size_t
1561rb_gc_impl_heap_page_count(void *objspace_ptr)
1562{
1563 return 0;
1564}
1565
1566void
1567rb_gc_impl_objspace_absorb(void *dst_ptr, void *src_ptr)
1568{
1569}
1570
1571void
1572rb_gc_impl_obj_became_shareable(void *objspace_ptr, VALUE obj)
1573{
1574}
1575
1576int32_t
1577rb_gc_impl_zjit_writebarrier_required_offset(void)
1578{
1579 /* ZJIT must always call rb_gc_writebarrier() so wbcheck sees every barrier. */
1580 return -1;
1581}
1582
1583void
1584rb_gc_impl_each_object(void *objspace_ptr, void (*func)(VALUE obj, void *data), void *data)
1585{
1587 GC_ASSERT(objspace);
1588
1589 wbcheck_object_list_t *snapshot = wbcheck_take_object_snapshot(objspace);
1590
1591 for (size_t i = 0; i < snapshot->count; i++) {
1592 VALUE obj = snapshot->items[i];
1593 st_data_t value;
1594 if (st_lookup(objspace->object_table, (st_data_t)obj, &value)) {
1595 func(obj, data);
1596 }
1597 }
1598
1599 wbcheck_object_list_free(snapshot);
1600}
1601
1602static void
1603finalizer_jobs_push(rb_wbcheck_objspace_t *objspace, struct wbcheck_final_job *job)
1604{
1605 rb_native_mutex_lock(&objspace->finalizer_lock);
1606 job->next = objspace->finalizer_jobs;
1607 objspace->finalizer_jobs = job;
1608 rb_native_mutex_unlock(&objspace->finalizer_lock);
1609}
1610
1611static struct wbcheck_final_job *
1612finalizer_jobs_pop(rb_wbcheck_objspace_t *objspace)
1613{
1614 rb_native_mutex_lock(&objspace->finalizer_lock);
1615 struct wbcheck_final_job *job = objspace->finalizer_jobs;
1616 if (job) {
1617 objspace->finalizer_jobs = job->next;
1618 }
1619 rb_native_mutex_unlock(&objspace->finalizer_lock);
1620 return job;
1621}
1622
1623// Finalizers
1624void
1625rb_gc_impl_make_zombie(void *objspace_ptr, VALUE obj, void (*dfree)(void *), void *data)
1626{
1627 if (dfree == NULL) return;
1628
1630
1631 struct wbcheck_final_job *job = malloc(sizeof(struct wbcheck_final_job));
1632 job->kind = WBCHECK_FINAL_JOB_DFREE;
1633 job->as.dfree.func = dfree;
1634 job->as.dfree.data = data;
1635
1636 finalizer_jobs_push(objspace, job);
1637
1638 if (!ruby_free_at_exit_p()) {
1639 rb_postponed_job_trigger(objspace->finalizer_postponed_job);
1640 }
1641
1642 WBCHECK_DEBUG("wbcheck: made zombie for object %p with dfree function\n", (void *)obj);
1643}
1644
1645VALUE
1646rb_gc_impl_define_finalizer(void *objspace_ptr, VALUE obj, VALUE block)
1647{
1648 unsigned int lev = RB_GC_VM_LOCK();
1649
1650 (void)objspace_ptr;
1651 rb_wbcheck_object_info_t *info = wbcheck_get_object_info(obj);
1652
1653 GC_ASSERT(!OBJ_FROZEN(obj));
1654
1655 RBASIC(obj)->flags |= FL_FINALIZE;
1656
1657 VALUE table = info->finalizers;
1658 VALUE result = block;
1659
1660 if (!table) {
1661 /* First finalizer for this object - store object ID as first element */
1662 table = rb_ary_new3(2, rb_obj_id(obj), block);
1663 rb_obj_hide(table);
1664 info->finalizers = table;
1665 } else {
1666 /* Check for duplicate finalizers (skip index 0 which is object ID) */
1667 rb_len_t len = RARRAY_LEN(table);
1668 rb_len_t i;
1669
1670 for (i = 1; i < len; i++) {
1671 VALUE recv = RARRAY_AREF(table, i);
1672 if (rb_equal(recv, block)) {
1673 result = recv; /* Duplicate found, return existing */
1674 goto unlock_and_return;
1675 }
1676 }
1677
1678 rb_ary_push(table, block);
1679 }
1680
1681unlock_and_return:
1682 RB_GC_VM_UNLOCK(lev);
1683 return result;
1684}
1685
1686void
1687rb_gc_impl_undefine_finalizer(void *objspace_ptr, VALUE obj)
1688{
1689 unsigned int lev = RB_GC_VM_LOCK();
1690
1691 (void)objspace_ptr;
1692 rb_wbcheck_object_info_t *info = wbcheck_get_object_info(obj);
1693
1694 GC_ASSERT(!OBJ_FROZEN(obj));
1695
1696 info->finalizers = 0;
1697 FL_UNSET(obj, FL_FINALIZE);
1698
1699 RB_GC_VM_UNLOCK(lev);
1700}
1701
1702void
1703rb_gc_impl_copy_finalizer(void *objspace_ptr, VALUE dest, VALUE obj)
1704{
1705 (void)objspace_ptr;
1706
1707 if (!FL_TEST(obj, FL_FINALIZE)) return;
1708
1709 unsigned int lev = RB_GC_VM_LOCK();
1710
1711 rb_wbcheck_object_info_t *src_info = wbcheck_get_object_info(obj);
1712 rb_wbcheck_object_info_t *dest_info = wbcheck_get_object_info(dest);
1713
1714 if (src_info->finalizers) {
1715 VALUE table = rb_ary_dup(src_info->finalizers);
1716 RARRAY_ASET(table, 0, rb_obj_id(dest));
1717 rb_obj_hide(table);
1718 dest_info->finalizers = table;
1719 FL_SET(dest, FL_FINALIZE);
1720 }
1721
1722 RB_GC_VM_UNLOCK(lev);
1723}
1724
1725static VALUE
1726wbcheck_get_final(rb_len_t i, void *data)
1727{
1728 VALUE table = (VALUE)data;
1729
1730 return RARRAY_AREF(table, i + 1);
1731}
1732
1733static void
1734make_final_job(rb_wbcheck_objspace_t *objspace, VALUE obj, VALUE finalizer_array)
1735{
1737 RUBY_ASSERT(RB_BUILTIN_TYPE(finalizer_array) == T_ARRAY);
1738
1740
1741 struct wbcheck_final_job *job = malloc(sizeof(struct wbcheck_final_job));
1742 job->kind = WBCHECK_FINAL_JOB_FINALIZE;
1743 job->as.finalize.finalizer_array = finalizer_array;
1744
1745 finalizer_jobs_push(objspace, job);
1746}
1747
1748static void
1749gc_run_finalizers(void *data)
1750{
1752
1753 rb_gc_set_pending_interrupt();
1754
1755 struct wbcheck_final_job *job;
1756 while ((job = finalizer_jobs_pop(objspace)) != NULL) {
1757 switch (job->kind) {
1758 case WBCHECK_FINAL_JOB_DFREE:
1759 job->as.dfree.func(job->as.dfree.data);
1760 break;
1761 case WBCHECK_FINAL_JOB_FINALIZE: {
1762 VALUE finalizer_array = job->as.finalize.finalizer_array;
1763
1764 rb_gc_run_obj_finalizer(
1765 RARRAY_AREF(finalizer_array, 0),
1766 RARRAY_LEN(finalizer_array) - 1,
1767 wbcheck_get_final,
1768 (void *)finalizer_array
1769 );
1770
1771 RB_GC_GUARD(finalizer_array);
1772 break;
1773 }
1774 }
1775
1776 free(job);
1777 }
1778
1779 rb_gc_unset_pending_interrupt();
1780}
1781
1782static void
1783wbcheck_run_finalizers_for_object(VALUE obj, rb_wbcheck_object_info_t *info)
1784{
1785 if (info->finalizers) {
1786 VALUE table = info->finalizers;
1787 rb_len_t count = RARRAY_LEN(table) - 1;
1788 rb_gc_run_obj_finalizer(RARRAY_AREF(table, 0), count, wbcheck_get_final, (void *)table);
1789 FL_UNSET(obj, FL_FINALIZE);
1790 }
1791 info->finalizers = 0;
1792}
1793
1794static int
1795wbcheck_shutdown_call_finalizer_callback(VALUE obj, rb_wbcheck_object_info_t *info, void *data)
1796{
1797 wbcheck_run_finalizers_for_object(obj, info);
1798 return ST_CONTINUE; /* Keep iterating through all objects */
1799}
1800
1801static int
1802wbcheck_verify_all_references_callback(VALUE obj, rb_wbcheck_object_info_t *info, void *data)
1803{
1804 void *objspace_ptr = data;
1805 wbcheck_verify_object_references(objspace_ptr, obj);
1806 return ST_CONTINUE;
1807}
1808
1809static int
1810wbcheck_update_all_snapshots_callback(VALUE obj, rb_wbcheck_object_info_t *info, void *data)
1811{
1812 void *objspace_ptr = data;
1813
1814 // For wb_protected objects, do full verification if they have a snapshot
1815 if (info->wb_protected && info->state != WBCHECK_STATE_CLEAR) {
1816 wbcheck_verify_object_references(objspace_ptr, obj);
1817 } else {
1818 // For CLEAR objects (wb_protected or not) and non-wb_protected objects, just take a new snapshot
1819 wbcheck_object_list_t *current_refs = wbcheck_collect_references_from_object(obj, info);
1820 wbcheck_object_list_free(info->gc_mark_snapshot);
1821 info->gc_mark_snapshot = current_refs;
1822 info->state = WBCHECK_STATE_MARKED;
1823 }
1824
1825 return ST_CONTINUE;
1826}
1827
1828static int
1829wbcheck_shutdown_finalizer_callback(VALUE obj, rb_wbcheck_object_info_t *info, void *data)
1830{
1831 void *objspace_ptr = data;
1832
1833 if (rb_gc_shutdown_call_finalizer_p(obj)) {
1834 WBCHECK_DEBUG("wbcheck: finalizing object during shutdown: %p\n", (void *)obj);
1835 rb_gc_obj_free_vm_weak_references(obj);
1836 if (rb_gc_obj_free(objspace_ptr, obj)) {
1837 RBASIC(obj)->flags = 0;
1838 }
1839 }
1840
1841 return ST_CONTINUE;
1842}
1843
1844
1845void
1846rb_gc_impl_shutdown_call_finalizer(void *objspace_ptr)
1847{
1848 rb_wbcheck_objspace_t *objspace = objspace_ptr;
1849
1850 // Call all finalizers for all objects using our shared iteration helper
1851 wbcheck_foreach_object(objspace, wbcheck_shutdown_call_finalizer_callback, NULL);
1852
1853 // After all finalizers have been called, verify all object references
1854 unsigned int verify_lev = RB_GC_VM_LOCK();
1855 WBCHECK_DEBUG("wbcheck: verifying references for all objects after finalizers\n");
1856 wbcheck_foreach_object(objspace, wbcheck_verify_all_references_callback, objspace_ptr);
1857 WBCHECK_DEBUG("wbcheck: finished verifying all object references\n");
1858 RB_GC_VM_UNLOCK(verify_lev);
1859
1860 wbcheck_abort_if_errors(objspace);
1861 WBCHECK_DEBUG("wbcheck: no write barrier violations detected\n");
1862
1863 // Call rb_gc_obj_free on objects that need shutdown finalization (File, Data with dfree, etc.)
1864 unsigned int lev = RB_GC_VM_LOCK();
1865 WBCHECK_DEBUG("wbcheck: calling rb_gc_obj_free on objects that need shutdown finalization\n");
1866 wbcheck_foreach_object(objspace, wbcheck_shutdown_finalizer_callback, objspace_ptr);
1867 WBCHECK_DEBUG("wbcheck: finished calling rb_gc_obj_free\n");
1868
1869 // Run any pending finalizer jobs (dfree functions)
1870 WBCHECK_DEBUG("wbcheck: running pending finalizer jobs\n");
1871 gc_run_finalizers(objspace);
1872 WBCHECK_DEBUG("wbcheck: finished running finalizer jobs\n");
1873 RB_GC_VM_UNLOCK(lev);
1874}
1875
1876// Forking
1877void
1878rb_gc_impl_before_fork(void *objspace_ptr)
1879{
1880 // Verify all objects at the fork point so a pre-fork missed barrier is caught
1881 // once here, rather than in every forked child.
1882 unsigned int lev = RB_GC_VM_LOCK();
1883 rb_gc_vm_barrier();
1884 force_gc(objspace_ptr);
1885 RB_GC_VM_UNLOCK(lev);
1886}
1887
1888void
1889rb_gc_impl_after_fork(void *objspace_ptr, rb_pid_t pid)
1890{
1891 // Stub implementation
1892}
1893
1894// Statistics
1895void
1896rb_gc_impl_set_measure_total_time(void *objspace_ptr, VALUE flag)
1897{
1899 objspace->measure_total_time = RTEST(flag);
1900}
1901
1902bool
1903rb_gc_impl_get_measure_total_time(void *objspace_ptr)
1904{
1906 return objspace->measure_total_time;
1907}
1908
1909unsigned long long
1910rb_gc_impl_get_total_time(void *objspace_ptr)
1911{
1913 return objspace->measure_total_time ? objspace->simulated_gc_count : 0;
1914}
1915
1916size_t
1917rb_gc_impl_gc_count(void *objspace_ptr)
1918{
1920 if (objspace) {
1921 return objspace->simulated_gc_count;
1922 }
1923 return 0;
1924}
1925
1926VALUE
1927rb_gc_impl_latest_gc_info(void *objspace_ptr, VALUE key)
1928{
1929 // Stub implementation
1930 return Qnil;
1931}
1932
1933VALUE
1934rb_gc_impl_stat(void *objspace_ptr, VALUE hash_or_sym)
1935{
1936 if (objspace_ptr == NULL) {
1937 rb_notimplement();
1938 }
1939
1941 GC_ASSERT(objspace);
1942
1943 VALUE hash = Qnil, key = Qnil;
1944
1945 if (RB_TYPE_P(hash_or_sym, T_HASH)) {
1946 hash = hash_or_sym;
1947 }
1948 else if (SYMBOL_P(hash_or_sym)) {
1949 key = hash_or_sym;
1950 }
1951 else {
1952 rb_bug("non-hash or symbol given");
1953 }
1954
1955#define SET(name, attr) \
1956 if (key == ID2SYM(rb_intern(#name))) \
1957 return SIZET2NUM(attr); \
1958 else if (hash != Qnil) \
1959 rb_hash_aset(hash, ID2SYM(rb_intern(#name)), SIZET2NUM(attr));
1960
1961 /* Pretend each GC takes 1ms; :time is reported in milliseconds. */
1962 SET(count, objspace->simulated_gc_count);
1963 SET(time, objspace->measure_total_time ? objspace->simulated_gc_count : 0);
1964 SET(tracked_objects, st_table_size(objspace->object_table));
1965#undef SET
1966
1967 if (!NIL_P(key)) {
1968 rb_raise(rb_eArgError, "unknown key: %"PRIsVALUE, rb_sym2str(key));
1969 }
1970
1971 rb_hash_aset(hash, ID2SYM(rb_intern("gc_implementation")), rb_str_new_cstr("wbcheck"));
1972
1973 return hash;
1974}
1975
1976VALUE
1977rb_gc_impl_stat_heap(void *objspace_ptr, VALUE heap_name, VALUE hash_or_sym)
1978{
1979 if (FIXNUM_P(heap_name) && SYMBOL_P(hash_or_sym)) {
1980 int heap_idx = FIX2INT(heap_name);
1981 if (heap_idx < 0 || heap_idx >= HEAP_COUNT) {
1982 rb_raise(rb_eArgError, "size pool index out of range");
1983 }
1984
1985 if (hash_or_sym == ID2SYM(rb_intern("slot_size"))) {
1986 return SIZET2NUM(heap_sizes[heap_idx]);
1987 }
1988
1989 return Qundef;
1990 }
1991
1992 if (RB_TYPE_P(hash_or_sym, T_HASH)) {
1993 return hash_or_sym;
1994 }
1995
1996 return Qundef;
1997}
1998
1999const char *
2000rb_gc_impl_active_gc_name(void)
2001{
2002 // Stub implementation
2003 return "wbcheck";
2004}
2005
2006// Miscellaneous
2007#define WBCHECK_OBJECT_METADATA_ENTRY_COUNT 2
2008static struct rb_gc_object_metadata_entry object_metadata_entries[WBCHECK_OBJECT_METADATA_ENTRY_COUNT + 1];
2009
2011rb_gc_impl_object_metadata(void *objspace_ptr, VALUE obj)
2012{
2013 static ID ID_object_id, ID_shareable;
2014
2015 if (!ID_object_id) {
2016 ID_object_id = rb_intern("object_id");
2017 ID_shareable = rb_intern("shareable");
2018 }
2019
2020 size_t n = 0;
2021
2022#define SET_ENTRY(na, v) do { \
2023 GC_ASSERT(n < WBCHECK_OBJECT_METADATA_ENTRY_COUNT); \
2024 object_metadata_entries[n].name = ID_##na; \
2025 object_metadata_entries[n].val = v; \
2026 n++; \
2027} while (0)
2028
2029 if (rb_obj_id_p(obj)) SET_ENTRY(object_id, rb_obj_id(obj));
2030 if (FL_TEST(obj, FL_SHAREABLE)) SET_ENTRY(shareable, Qtrue);
2031#undef SET_ENTRY
2032
2033 object_metadata_entries[n].name = 0;
2034 object_metadata_entries[n].val = 0;
2035
2036 return object_metadata_entries;
2037}
2038
2039bool
2040rb_gc_impl_live_object_p(void *objspace_ptr, const void *ptr)
2041{
2042 GC_ASSERT(wbcheck_global_objspace);
2043
2044 unsigned int lev = RB_GC_VM_LOCK();
2045
2046 // Check if this pointer exists in our object tracking table
2047 st_data_t value;
2048 bool result = st_lookup(wbcheck_global_objspace->object_table, (st_data_t)ptr, &value);
2049
2050 RB_GC_VM_UNLOCK(lev);
2051 return result;
2052}
2053
2054bool
2055rb_gc_impl_garbage_object_p(void *objspace_ptr, VALUE obj)
2056{
2057 unsigned int lev = RB_GC_VM_LOCK();
2058
2059 // Check if this pointer exists in our object tracking table
2060 st_data_t value;
2061 bool result = st_lookup(wbcheck_global_objspace->object_table, (st_data_t)obj, &value);
2062
2063 RB_GC_VM_UNLOCK(lev);
2064 return !result;
2065}
2066
2067void
2068rb_gc_impl_set_event_hook(void *objspace_ptr, const rb_event_flag_t event)
2069{
2070 // Stub implementation
2071}
2072
2073void
2074rb_gc_impl_copy_attributes(void *objspace_ptr, VALUE dest, VALUE obj)
2075{
2076 rb_wbcheck_object_info_t *src_info = wbcheck_get_object_info(obj);
2077
2078 if (!src_info->wb_protected) {
2079 rb_gc_impl_writebarrier_unprotect(objspace_ptr, dest);
2080 }
2081 rb_gc_impl_copy_finalizer(objspace_ptr, dest, obj);
2082}
#define RUBY_ASSERT(...)
Asserts that the given expression is truthy if and only if RUBY_DEBUG is truthy.
Definition assert.h:219
Atomic operations.
#define rb_define_singleton_method(klass, mid, func, arity)
Defines klass.mid.
unsigned int rb_postponed_job_handle_t
The type of a handle returned from rb_postponed_job_preregister and passed to rb_postponed_job_trigge...
Definition debug.h:703
void rb_postponed_job_trigger(rb_postponed_job_handle_t h)
Triggers a pre-registered job registered with rb_postponed_job_preregister, scheduling it for executi...
Definition vm_trace.c:1899
rb_postponed_job_handle_t rb_postponed_job_preregister(unsigned int flags, rb_postponed_job_func_t func, void *data)
Pre-registers a func in Ruby's postponed job preregistration table, returning an opaque handle which ...
Definition vm_trace.c:1865
#define RUBY_INTERNAL_EVENT_GC_EXIT
gc_exit() is called.
Definition event.h:99
#define RUBY_INTERNAL_EVENT_GC_ENTER
gc_enter() is called.
Definition event.h:98
#define RUBY_INTERNAL_EVENT_GC_END_SWEEP
GC ended sweep phase.
Definition event.h:97
#define RUBY_INTERNAL_EVENT_GC_END_MARK
GC ended mark phase.
Definition event.h:96
#define RUBY_INTERNAL_EVENT_FREEOBJ
Object swept.
Definition event.h:94
#define RUBY_INTERNAL_EVENT_GC_START
GC started.
Definition event.h:95
uint32_t rb_event_flag_t
Represents event(s).
Definition event.h:108
static VALUE RB_FL_TEST(VALUE obj, VALUE flags)
Tests if the given flag(s) are set or not.
Definition fl_type.h:433
static VALUE RB_FL_TEST_RAW(VALUE obj, VALUE flags)
This is an implementation detail of RB_FL_TEST().
Definition fl_type.h:407
static void RB_FL_UNSET(VALUE obj, VALUE flags)
Clears the given flag(s).
Definition fl_type.h:619
@ RUBY_FL_SHAREABLE
This flag has something to do with Ractor.
Definition fl_type.h:253
@ RUBY_FL_WEAK_REFERENCE
This object weakly refers to other objects.
Definition fl_type.h:260
#define Qundef
Old name of RUBY_Qundef.
#define INT2FIX
Old name of RB_INT2FIX.
Definition long.h:48
#define OBJ_FROZEN
Old name of RB_OBJ_FROZEN.
Definition fl_type.h:133
#define ID2SYM
Old name of RB_ID2SYM.
Definition symbol.h:44
#define OBJ_FREEZE
Old name of RB_OBJ_FREEZE.
Definition fl_type.h:131
#define FL_SHAREABLE
Old name of RUBY_FL_SHAREABLE.
Definition fl_type.h:62
#define SIZET2NUM
Old name of RB_SIZE2NUM.
Definition size_t.h:62
#define LONG2FIX
Old name of RB_INT2FIX.
Definition long.h:49
#define FIX2INT
Old name of RB_FIX2INT.
Definition int.h:41
#define FL_FINALIZE
Old name of RUBY_FL_FINALIZE.
Definition fl_type.h:61
#define T_HASH
Old name of RUBY_T_HASH.
Definition value_type.h:65
#define FL_TEST_RAW
Old name of RB_FL_TEST_RAW.
Definition fl_type.h:128
#define FL_SET
Old name of RB_FL_SET.
Definition fl_type.h:125
#define rb_ary_new3
Old name of rb_ary_new_from_args.
Definition array.h:658
#define Qtrue
Old name of RUBY_Qtrue.
#define INT2NUM
Old name of RB_INT2NUM.
Definition int.h:43
#define Qnil
Old name of RUBY_Qnil.
#define Qfalse
Old name of RUBY_Qfalse.
#define T_ARRAY
Old name of RUBY_T_ARRAY.
Definition value_type.h:56
#define NIL_P
Old name of RB_NIL_P.
#define T_CLASS
Old name of RUBY_T_CLASS.
Definition value_type.h:58
#define FL_TEST
Old name of RB_FL_TEST.
Definition fl_type.h:127
#define FL_UNSET
Old name of RB_FL_UNSET.
Definition fl_type.h:129
#define FIXNUM_P
Old name of RB_FIXNUM_P.
#define FL_SET_RAW
Old name of RB_FL_SET_RAW.
Definition fl_type.h:126
#define SYMBOL_P
Old name of RB_SYMBOL_P.
Definition value_type.h:88
VALUE rb_obj_hide(VALUE obj)
Make the object invisible from Ruby code.
Definition object.c:94
VALUE rb_mGC
GC module.
Definition gc.c:468
VALUE rb_equal(VALUE lhs, VALUE rhs)
This function is an optimised version of calling #==.
Definition object.c:140
VALUE rb_ary_dup(VALUE ary)
Duplicates an array.
VALUE rb_ary_push(VALUE ary, VALUE elem)
Special case of rb_ary_cat() that it adds only one element.
#define rb_str_new_cstr(str)
Identical to rb_str_new, except it assumes the passed pointer is a pointer to a C string.
Definition string.h:1539
VALUE rb_f_notimplement(int argc, const VALUE *argv, VALUE obj, VALUE marker)
Raises rb_eNotImpError.
Definition vm_method.c:912
VALUE rb_sym2str(VALUE symbol)
Obtain a frozen string representation of a symbol (not including the leading colon).
Definition symbol.c:1148
int len
Length of the buffer.
Definition io.h:8
void * rb_thread_call_with_gvl(void *(*func)(void *), void *data1)
(Re-)acquires the GVL.
Definition thread.c:2322
#define RB_GC_GUARD(v)
Prevents premature destruction of local objects.
Definition memory.h:167
#define RARRAY_LEN
Just another name of rb_array_len.
Definition rarray.h:50
#define RARRAY_AREF(a, i)
References an object in an array.
Definition rarray.h:421
static void RARRAY_ASET(VALUE ary, rb_len_t i, VALUE v)
Assigns an object in an array.
Definition rarray.h:385
Defines struct RBasic.
#define RBASIC(obj)
Convenient casting macro.
Definition rbasic.h:40
int ruby_native_thread_p(void)
Queries if the thread which calls this function is a ruby's thread.
Definition thread.c:6181
static bool RB_SPECIAL_CONST_P(VALUE obj)
Checks if the given object is of enum ruby_special_consts.
#define RTEST
This is an old name of RB_TEST.
Defines old _.
C99 shim for <stdbool.h>
Ruby object's base components.
Definition rbasic.h:69
Definition gc_impl.h:34
Definition st.h:79
void rb_native_mutex_lock(rb_nativethread_lock_t *lock)
Just another name of rb_nativethread_lock_lock.
void rb_native_mutex_initialize(rb_nativethread_lock_t *lock)
Just another name of rb_nativethread_lock_initialize.
void rb_native_mutex_unlock(rb_nativethread_lock_t *lock)
Just another name of rb_nativethread_lock_unlock.
long rb_len_t
A signed integer type for lengths and indices of objects such as String and Array.
Definition value.h:142
uintptr_t ID
Type that represents a Ruby identifier such as a variable name.
Definition value.h:52
uintptr_t VALUE
Type that represents a Ruby object.
Definition value.h:40
static enum ruby_value_type RB_BUILTIN_TYPE(VALUE obj)
Queries the type of the object.
Definition value_type.h:182
static bool RB_TYPE_P(VALUE obj, enum ruby_value_type t)
Queries if the given object is of given type.
Definition value_type.h:376