Ruby 4.1.0dev (2026-11-12 revision e6e232d5facd495b6a7f143cf6e9c7960392ca25)
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 full GC
243 size_t total_allocated_objects; // Total objects allocated over the process lifetime
244 bool measure_total_time; // Whether to accumulate :time in stats
245 struct wbcheck_final_job *finalizer_jobs; // Linked list of finalizer jobs
246 rb_nativethread_lock_t finalizer_lock; // Protects finalizer_jobs list
247 rb_postponed_job_handle_t finalizer_postponed_job; // Postponed job handle for finalizers
248 struct rb_gc_vm_context vm_context;
250
251// Global objspace pointer for accessing from obj_slot_size function
252static rb_wbcheck_objspace_t *wbcheck_global_objspace = NULL;
253
254// Forward declarations
255static void wbcheck_foreach_object(rb_wbcheck_objspace_t *objspace, int (*callback)(VALUE obj, rb_wbcheck_object_info_t *info, void *data), void *data);
256static int wbcheck_verify_all_references_callback(VALUE obj, rb_wbcheck_object_info_t *info, void *data);
257static int wbcheck_update_all_snapshots_callback(VALUE obj, rb_wbcheck_object_info_t *info, void *data);
258static void wbcheck_run_finalizers_for_object(VALUE obj, rb_wbcheck_object_info_t *info);
259static void gc_run_finalizers(void *data);
260static void make_final_job(rb_wbcheck_objspace_t *objspace, VALUE obj, VALUE finalizer_array);
261
262// Helper functions for object tracking
264wbcheck_get_object_info(VALUE obj)
265{
266 // Objspace must be initialized by this point
267 GC_ASSERT(wbcheck_global_objspace);
268
269 st_data_t value;
270 if (st_lookup(wbcheck_global_objspace->object_table, (st_data_t)obj, &value)) {
271 return (rb_wbcheck_object_info_t *)value;
272 }
273
274 fprintf(stderr, "wbcheck: object not found in tracking table\n");
275 char buff[0x100];
276 fprintf(stderr, "%s\n", rb_raw_obj_info(buff, sizeof(buff), obj));
277
278 // Force ASAN crash?
279 ((volatile VALUE *)obj)[0];
280
281 // Object not found in tracking table - this should never happen
282 rb_bug("wbcheck: object not found in tracking table");
283}
284
285static void
286wbcheck_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)
287{
289
290 rb_wbcheck_object_info_t *parent_info = wbcheck_get_object_info(parent_obj);
291
292 size_t snapshot_count = gc_mark_snapshot ? gc_mark_snapshot->count : 0;
293 size_t wb_count = writebarrier_children ? writebarrier_children->count : 0;
294
295 fprintf(stderr, "WBCHECK ERROR: Missed write barrier detected!\n");
296 fprintf(stderr, " Parent object: %p (wb_protected: %s)\n",
297 (void *)parent_obj, parent_info->wb_protected ? "true" : "false");
298 char buff[0x100];
299 fprintf(stderr, " %s\n", rb_raw_obj_info(buff, sizeof(buff), parent_obj));
300 fprintf(stderr, " Reference counts - snapshot: %zu, writebarrier: %zu, current: %zu, missed: %zu\n",
301 snapshot_count, wb_count, current_refs->count, missed_refs->count);
302
303 for (size_t i = 0; i < missed_refs->count; i++) {
304 VALUE missed_ref = missed_refs->items[i];
305 char buff[0x100];
306 fprintf(stderr, " Missing reference to: %p\n %s\n", (void *)missed_ref, rb_raw_obj_info(buff, sizeof(buff), missed_ref));
307 }
308
309 fprintf(stderr, "\n");
310 objspace->missed_write_barrier_parents++;
311 objspace->missed_write_barrier_children += missed_refs->count;
312}
313
314// Fail if a verification sweep reported any missed write barriers. Called at
315// each sweep boundary so every offender in the sweep is printed before we fail.
316static void
317wbcheck_abort_if_errors(rb_wbcheck_objspace_t *objspace)
318{
319 if (objspace->missed_write_barrier_parents > 0) {
320 fflush(stderr);
321 rb_bug("wbcheck: missed write barrier detected (%zu object(s), %zu reference(s))",
322 objspace->missed_write_barrier_parents,
323 objspace->missed_write_barrier_children);
324 }
325}
326
327static void
328wbcheck_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)
329{
331 (void)objspace;
332
333 size_t snapshot_count = gc_mark_snapshot ? gc_mark_snapshot->count : 0;
334 size_t wb_count = writebarrier_children ? writebarrier_children->count : 0;
335
336 WBCHECK_DEBUG("wbcheck: comparing references for object %p\n", (void *)parent_obj);
337 WBCHECK_DEBUG("wbcheck: current refs: %zu, snapshot refs: %zu, wb refs: %zu\n",
338 current_refs->count, snapshot_count, wb_count);
339
340 // Collect missed references (lazily allocated)
341 wbcheck_object_list_t *missed_refs = NULL;
342
343 // Use circular comparison for better performance when lists are mostly similar
344 size_t snapshot_idx = 0;
345
346 // Check each object in current_refs to see if it's in either stored list
347 for (size_t i = 0; i < current_refs->count; i++) {
348 VALUE current_ref = current_refs->items[i];
349
350 // Usually the lists are nearly identical. We take advantage of this by
351 // attempting to loop over both lists in sequence. When the next element
352 // of the snapshot doesn't match the next element of our current_refs,
353 // we'll loop around the list to try to find it and continue from that
354 // match, so any runs of identical items can be matched efficiently.
355 //
356 // Pathologically this is O(N**2), but is O(N * num_changes)
357 bool found_in_snapshot = false;
358 if (gc_mark_snapshot && snapshot_count > 0) {
359 size_t start_idx = snapshot_idx;
360 do {
361 if (gc_mark_snapshot->items[snapshot_idx] == current_ref) {
362 found_in_snapshot = true;
363 snapshot_idx++;
364 if (snapshot_idx >= snapshot_count) snapshot_idx = 0;
365 break;
366 }
367 snapshot_idx++;
368 if (snapshot_idx >= snapshot_count) snapshot_idx = 0;
369 } while (snapshot_idx != start_idx);
370 }
371
372 if (found_in_snapshot) {
373 continue;
374 }
375
376 // Built-in immortal classes can be assigned via RBASIC_SET_CLASS_RAW,
377 // which bypasses the write barrier. They're pinned as VM roots and
378 // can never be collected, so a missing WB to them is harmless.
379 if (RB_TYPE_P(current_ref, T_CLASS) && FL_TEST_RAW(current_ref, WBCHECK_RCLASS_IS_ROOT)) {
380 continue;
381 }
382
383 // Self reference... Weird but okay I guess
384 if (current_ref == parent_obj) {
385 continue;
386 }
387
388
389 // Check if reference exists in writebarrier_children
390 if (writebarrier_children && wbcheck_object_list_contains(writebarrier_children, current_ref)) {
391 continue;
392 }
393
394 // If we get here, the reference wasn't found in either list
395 // Lazily allocate missed_refs list on first miss
396 if (!missed_refs) {
397 missed_refs = wbcheck_object_list_init();
398 }
399 wbcheck_object_list_append(missed_refs, current_ref);
400 }
401
402 // Report any errors found
403 if (missed_refs) {
404 wbcheck_report_error(objspace_ptr, parent_obj, current_refs, gc_mark_snapshot, writebarrier_children, missed_refs);
405 wbcheck_object_list_free(missed_refs);
406 }
407}
408
409static void
410wbcheck_register_object(void *objspace_ptr, VALUE obj, size_t alloc_size, bool wb_protected)
411{
413 GC_ASSERT(objspace);
414
415 // Allocate and initialize object info structure
416 rb_wbcheck_object_info_t *info = calloc(1, sizeof(rb_wbcheck_object_info_t));
417 if (!info) rb_bug("wbcheck_register_object: failed to allocate object info");
418
419 info->alloc_size = alloc_size;
420 info->wb_protected = wb_protected;
421 info->finalizers = 0; /* No finalizers initially */
422 info->gc_mark_snapshot = NULL; /* No snapshot initially */
423 info->mark_maybe_snapshot = NULL; /* No mark_maybe snapshot initially */
424 info->writebarrier_children = NULL; /* No write barrier children initially */
425 info->state = WBCHECK_STATE_CLEAR; /* Start in clear state */
426 info->color = WBCHECK_COLOR_BLACK; /* Start as black to survive current GC */
427
428 // Store object info in hash table (VALUE -> rb_wbcheck_object_info_t*)
429 st_insert(objspace->object_table, (st_data_t)obj, (st_data_t)info);
430 objspace->total_allocated_objects++;
431}
432
433static void
434wbcheck_unregister_object(void *objspace_ptr, VALUE obj)
435{
438
439 if (st_delete(objspace->object_table, (st_data_t *)&obj, (st_data_t *)&info)) {
440 // Free object lists if they were allocated
441 wbcheck_object_list_free(info->gc_mark_snapshot);
442 wbcheck_object_list_free(info->mark_maybe_snapshot);
443 wbcheck_object_list_free(info->writebarrier_children);
444 free(info);
445 } else {
446 rb_bug("wbcheck_unregister_object: object not found in table");
447 }
448}
449
450// Bootup
451void *
452rb_gc_impl_objspace_alloc(void)
453{
454 wbcheck_configure_from_env();
455
457 if (!objspace) rb_bug("wbcheck: failed to allocate objspace");
458
459 objspace->objects_to_capture = wbcheck_object_list_init(); // Initialize empty list
460 objspace->objects_to_verify = wbcheck_object_list_init(); // Initialize empty list
461 objspace->current_refs = NULL; // No current refs initially
462 objspace->current_maybe_refs = NULL; // No current maybe refs initially
463 objspace->mark_queue = wbcheck_object_list_init(); // Initialize mark queue
464 objspace->weak_references = wbcheck_object_list_init(); // Initialize weak references array
465 objspace->phase = WBCHECK_PHASE_MUTATOR; // Start in mutator phase
466 objspace->gc_enabled = true; // GC enabled by default (like default GC)
467 objspace->gc_stress = false; // GC stress disabled by default
468 objspace->gc_threshold = 1000; // Start with 1000 objects, will adjust after first GC
469 objspace->missed_write_barrier_parents = 0; // No errors found yet
470 objspace->missed_write_barrier_children = 0; // No errors found yet
471 objspace->simulated_gc_count = 0; // Start with GC count of 0
472 objspace->measure_total_time = true; // On by default
473
474 return objspace;
475}
476
477void
478rb_gc_impl_objspace_init(void *objspace_ptr)
479{
480 rb_wbcheck_objspace_t *objspace = objspace_ptr;
481
482 // st_init_numtable allocates through ruby_xmalloc, which needs the objspace
483 // to exist, so the object table can't be created in objspace_alloc.
484 objspace->object_table = st_init_numtable();
485 if (!objspace->object_table) {
486 rb_bug("wbcheck: failed to create object table");
487 }
488
489 // Set up global objspace pointer for obj_slot_size function
490 wbcheck_global_objspace = objspace;
491
492 // Initialize postponed job for finalizers
493 rb_native_mutex_initialize(&objspace->finalizer_lock);
494 objspace->finalizer_postponed_job = rb_postponed_job_preregister(0, gc_run_finalizers, objspace);
495}
496
497void *
498rb_gc_impl_ractor_cache_alloc(void *objspace_ptr, void *ractor)
499{
500 // Stub implementation
501 return NULL;
502}
503
504bool
505rb_gc_impl_zjit_new_obj_fastpath(void *objspace_ptr, size_t alloc_size, VALUE flags, VALUE klass,
506 struct rb_gc_zjit_fastpath *fastpath)
507{
508 return false;
509}
510
511void
512rb_gc_impl_set_params(void *objspace_ptr)
513{
514 // Stub implementation
515}
516
517static VALUE
518gc_verify_internal_consistency(VALUE self)
519{
520 return Qnil;
521}
522
523void
524rb_gc_impl_init(void)
525{
526 VALUE gc_constants = rb_hash_new();
527 //rb_hash_aset(gc_constants, ID2SYM(rb_intern("BASE_SLOT_SIZE")), SIZET2NUM(BASE_SLOT_SIZE));
528 rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVALUE_SIZE")), SIZET2NUM(sizeof(struct RBasic) + sizeof(VALUE[RBIMPL_RVALUE_EMBED_LEN_MAX])));
529 rb_hash_aset(gc_constants, ID2SYM(rb_intern("RBASIC_SIZE")), SIZET2NUM(sizeof(struct RBasic)));
530 rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVALUE_OVERHEAD")), INT2NUM(0));
531 rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVARGC_MAX_ALLOCATE_SIZE")), SIZET2NUM(rb_gc_impl_max_allocation_size()));
532 rb_hash_aset(gc_constants, ID2SYM(rb_intern("HEAP_COUNT")), LONG2FIX(HEAP_COUNT));
533 rb_hash_aset(gc_constants, ID2SYM(rb_intern("SIZE_POOL_COUNT")), LONG2FIX(HEAP_COUNT));
534 rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVALUE_OLD_AGE")), INT2FIX(3));
535 OBJ_FREEZE(gc_constants);
536 rb_define_const(rb_mGC, "INTERNAL_CONSTANTS", gc_constants);
537
538 // no-ops for compatibility
539 rb_define_singleton_method(rb_mGC, "verify_internal_consistency", gc_verify_internal_consistency, 0);
540
544 rb_define_singleton_method(rb_mGC, "latest_compact_info", rb_f_notimplement, 0);
545 rb_define_singleton_method(rb_mGC, "verify_compaction_references", rb_f_notimplement, -1);
546 // Stub implementation
547}
548
549// Shutdown
550void
551rb_gc_impl_shutdown_free_objects(void *objspace_ptr)
552{
553 // Stub implementation
554}
555
556void
557rb_gc_impl_objspace_free(void *objspace_ptr)
558{
559 // This should free everything, but we'll just let it leak
560}
561
562void
563rb_gc_impl_ractor_cache_free(void *objspace_ptr, void *cache)
564{
565 // Stub implementation
566}
567
568// GC
569void
570rb_gc_impl_start(void *objspace_ptr, bool full_mark, bool immediate_mark, bool immediate_sweep, bool compact, bool global)
571{
572 if (!ruby_native_thread_p()) return;
573
574 unsigned int lev = RB_GC_VM_LOCK();
575 rb_gc_vm_barrier();
576 force_gc(objspace_ptr);
577 RB_GC_VM_UNLOCK(lev);
578}
579
580bool
581rb_gc_impl_during_gc_p(void *objspace_ptr)
582{
584 return objspace->phase != WBCHECK_PHASE_MUTATOR;
585}
586
587static void
588wbcheck_prepare_heap_i(VALUE obj, void *data)
589{
590 rb_gc_prepare_heap_process_object(obj);
591}
592
593void
594rb_gc_impl_prepare_heap(void *objspace_ptr)
595{
596 rb_gc_impl_each_object(objspace_ptr, wbcheck_prepare_heap_i, NULL);
597}
598
599void
600rb_gc_impl_gc_enable(void *objspace_ptr)
601{
603 objspace->gc_enabled = true;
604}
605
606void
607rb_gc_impl_gc_disable(void *objspace_ptr, bool finish_current_gc)
608{
610 objspace->gc_enabled = false;
611}
612
613bool
614rb_gc_impl_gc_enabled_p(void *objspace_ptr)
615{
617 return objspace->gc_enabled;
618}
619
620bool
621rb_gc_impl_user_gc_disabled_set(void *objspace_ptr, bool disable)
622{
624 const bool was = objspace->user_gc_disabled;
625 objspace->user_gc_disabled = disable;
626 objspace->gc_enabled = !disable;
627 return was;
628}
629
630bool
631rb_gc_impl_user_gc_disabled_p(void *objspace_ptr)
632{
634 return objspace->user_gc_disabled;
635}
636
637void
638rb_gc_impl_gc_rest(void *objspace_ptr)
639{
640 /* wbcheck GCs are stop-the-world: no incremental state to finish. */
641}
642
643void
644rb_gc_impl_stress_set(void *objspace_ptr, VALUE flag)
645{
647 objspace->gc_stress = RTEST(flag);
648}
649
650VALUE
651rb_gc_impl_stress_get(void *objspace_ptr)
652{
654 return objspace->gc_stress ? Qtrue : Qfalse;
655}
656
657VALUE
658rb_gc_impl_config_get(void *objspace_ptr)
659{
660 return rb_hash_new();
661}
662
663void
664rb_gc_impl_config_set(void *objspace_ptr, VALUE hash)
665{
666}
667
668struct rb_gc_vm_context *
669rb_gc_impl_get_vm_context(void *objspace_ptr)
670{
671 rb_wbcheck_objspace_t *objspace = objspace_ptr;
672
673 return &objspace->vm_context;
674}
675
677wbcheck_collect_references_from_object(VALUE obj, rb_wbcheck_object_info_t *info)
678{
679 rb_wbcheck_objspace_t *objspace = wbcheck_global_objspace;
680
681 // Use combination of writebarrier children and last snapshot as capacity hint
682 size_t snapshot_count = (info->gc_mark_snapshot) ? info->gc_mark_snapshot->count : 0;
683 size_t wb_children_count = (info->writebarrier_children) ? info->writebarrier_children->count : 0;
684 size_t capacity_hint = snapshot_count + wb_children_count;
685 wbcheck_object_list_t *new_list = wbcheck_object_list_init_with_capacity(capacity_hint);
686
687 // Set up objspace state for marking. current_maybe_refs is allocated lazily
688 // by rb_gc_impl_mark_maybe, since most objects have no conservative refs.
689 objspace->current_refs = new_list;
690 objspace->current_maybe_refs = NULL;
691 objspace->phase = WBCHECK_PHASE_SNAPSHOT;
692 rb_gc_initialize_vm_context(&objspace->vm_context);
693
694 // Use the marking infrastructure to collect references
695 rb_gc_mark_children(objspace, obj);
696
697 // Clean up objspace state
698 objspace->phase = WBCHECK_PHASE_MUTATOR;
699 objspace->current_refs = NULL;
700
701 // Update the mark_maybe snapshot in place. These references don't participate
702 // in verification, but we need to keep them so full GC can mark them gray.
703 wbcheck_object_list_free(info->mark_maybe_snapshot);
704 info->mark_maybe_snapshot = objspace->current_maybe_refs;
705 objspace->current_maybe_refs = NULL;
706
707 if (wbcheck_debug_enabled) {
708 WBCHECK_DEBUG("wbcheck: collected %zu references from %p\n", new_list->count, (void *)obj);
709 char buff[0x100];
710 fprintf(stderr, "%s\n", rb_raw_obj_info(buff, sizeof(buff), obj));
711 wbcheck_object_list_debug_print(new_list);
712 }
713
714 return new_list;
715}
716
717static void
718wbcheck_collect_initial_references(void *objspace_ptr, VALUE obj)
719{
720 WBCHECK_DEBUG("wbcheck: collecting initial references from %p:\n", obj);
721 wbcheck_debug_obj_info_dump(obj);
722
723 // Get the object info and set the initial GC mark snapshot
724 rb_wbcheck_object_info_t *info = wbcheck_get_object_info(obj);
725 wbcheck_object_list_t *new_list = wbcheck_collect_references_from_object(obj, info);
726 RUBY_ASSERT(!info->gc_mark_snapshot);
727 RUBY_ASSERT(info->state == WBCHECK_STATE_CLEAR);
728 info->gc_mark_snapshot = new_list; // Set the initial snapshot
729 info->state = WBCHECK_STATE_MARKED; // Transition to marked state
730}
731
732static void
733wbcheck_verify_object_references(void *objspace_ptr, VALUE obj)
734{
735 rb_wbcheck_object_info_t *info = wbcheck_get_object_info(obj);
736
737 // Ignore objects which are not write barrier protected
738 if (!info->wb_protected) {
739 return;
740 }
741
742 // We hadn't captured initial references
743 if (info->state == WBCHECK_STATE_CLEAR) {
744 RUBY_ASSERT(!info->gc_mark_snapshot);
745 return;
746 }
747
748 WBCHECK_DEBUG("wbcheck: verifying references for object:\n");
749 wbcheck_debug_obj_info_dump(obj);
750
751 // Get the current references from the object
752 wbcheck_object_list_t *current_refs = wbcheck_collect_references_from_object(obj, info);
753
754 // Check for useless write barriers before clearing them
755 if (wbcheck_warn_useless_wb_enabled && info->writebarrier_children) {
756 for (size_t i = 0; i < info->writebarrier_children->count; i++) {
757 VALUE wb_ref = info->writebarrier_children->items[i];
758 if (!wbcheck_object_list_contains(current_refs, wb_ref)) {
759 fprintf(stderr, "WBCHECK WARNING: Potentially useless write barrier detected for object %p\n", (void *)obj);
760 fprintf(stderr, " Write barrier was recorded for reference to %p, but object no longer references it\n", (void *)wb_ref);
761 char buff[0x100];
762 fprintf(stderr, " Parent: %s\n", rb_raw_obj_info(buff, sizeof(buff), obj));
763 fprintf(stderr, " Stale reference: %s\n", rb_raw_obj_info(buff, sizeof(buff), wb_ref));
764 }
765 }
766 }
767
768 // Compare current_refs against both stored lists to detect missed write barriers
769 wbcheck_compare_references(objspace_ptr, obj, current_refs, info->gc_mark_snapshot, info->writebarrier_children);
770
771 // Update the snapshot with current references and clear write barrier children
772 wbcheck_object_list_free(info->gc_mark_snapshot);
773 wbcheck_object_list_free(info->writebarrier_children);
774 info->gc_mark_snapshot = current_refs;
775 info->writebarrier_children = NULL;
776 info->state = WBCHECK_STATE_MARKED; // Back to marked state after verification
777}
778
779// Mark object as gray (add to mark queue)
780static void
781wbcheck_mark_gray(rb_wbcheck_objspace_t *objspace, VALUE obj)
782{
783 if (RB_SPECIAL_CONST_P(obj)) return;
784
785 st_data_t value;
786 if (!st_lookup(objspace->object_table, (st_data_t)obj, &value)) {
787 rb_bug("wbcheck: asked to mark object %p not in our object table", (void *)obj);
788 }
789
791 if (info->color != WBCHECK_COLOR_WHITE) {
792 return; // Already marked
793 }
794
795 info->color = WBCHECK_COLOR_GRAY;
796 wbcheck_object_list_append(objspace->mark_queue, obj);
797
799 wbcheck_object_list_append(objspace->weak_references, obj);
800 }
801
802 WBCHECK_DEBUG("wbcheck: marked gray: %p\n", (void *)obj);
803}
804
805// Reset all objects to white
806static int
807st_foreach_reset_white(st_data_t key, st_data_t val, st_data_t arg)
808{
810 info->color = WBCHECK_COLOR_WHITE;
811 return ST_CONTINUE;
812}
813
814// Mark all finalizer arrays to keep them alive during GC
815static int
816st_foreach_mark_finalizers(st_data_t key, st_data_t val, st_data_t arg)
817{
820
821 if (info->finalizers) {
822 wbcheck_mark_gray(objspace, info->finalizers);
823 }
824
825 return ST_CONTINUE;
826}
827
828// Full mark phase using tri-color marking with snapshots
829static void
830wbcheck_mark_phase(rb_wbcheck_objspace_t *objspace)
831{
832 WBCHECK_DEBUG("wbcheck: starting GC mark phase\n");
833
834 objspace->phase = WBCHECK_PHASE_FULL_GC;
835 rb_gc_initialize_vm_context(&objspace->vm_context);
836
837 // Clear mark queue and reset all objects to white
838 objspace->mark_queue->count = 0;
839 st_foreach(objspace->object_table, st_foreach_reset_white, 0);
840
841 // Mark all finalizer arrays first to keep them alive
842 st_foreach(objspace->object_table, st_foreach_mark_finalizers, (st_data_t)objspace);
843
844 // Mark finalizer arrays in pending jobs to keep them alive.
845 // No lock needed: all other threads are stopped during GC.
846 struct wbcheck_final_job *job = objspace->finalizer_jobs;
847 while (job != NULL) {
848 switch (job->kind) {
849 case WBCHECK_FINAL_JOB_DFREE:
850 break;
851 case WBCHECK_FINAL_JOB_FINALIZE:
852 wbcheck_mark_gray(objspace, job->as.finalize.finalizer_array);
853 break;
854 default:
855 rb_bug("wbcheck_mark_phase: unknown final job type %d", job->kind);
856 }
857 job = job->next;
858 }
859
860 // Mark roots gray
861 rb_gc_save_machine_context();
862 rb_gc_mark_roots(objspace, NULL);
863
864 // Process gray queue until empty
865 while (objspace->mark_queue->count > 0) {
866 // Get last object from queue (LIFO)
867 VALUE obj = objspace->mark_queue->items[--objspace->mark_queue->count];
868
869 st_data_t value;
870 if (st_lookup(objspace->object_table, (st_data_t)obj, &value)) {
872 if (info->color == WBCHECK_COLOR_GRAY) {
873 // Mark all children from snapshot gray
874 if (info->gc_mark_snapshot) {
875 for (size_t i = 0; i < info->gc_mark_snapshot->count; i++) {
876 wbcheck_mark_gray(objspace, info->gc_mark_snapshot->items[i]);
877 }
878 }
879
880 // Conservatively-scanned children must also be kept alive
881 if (info->mark_maybe_snapshot) {
882 for (size_t i = 0; i < info->mark_maybe_snapshot->count; i++) {
883 wbcheck_mark_gray(objspace, info->mark_maybe_snapshot->items[i]);
884 }
885 }
886
887 // Mark this object black
888 info->color = WBCHECK_COLOR_BLACK;
889 WBCHECK_DEBUG("wbcheck: marked black: %p\n", (void *)obj);
890 }
891 }
892 }
893
894 objspace->phase = WBCHECK_PHASE_MUTATOR;
895
896 WBCHECK_DEBUG("wbcheck: tri-color mark phase complete\n");
897}
898
899// Sweep phase callback - free white objects
900static int
901wbcheck_sweep_callback(st_data_t key, st_data_t val, st_data_t arg, int error)
902{
903 VALUE obj = (VALUE)key;
906
907 if (info->color == WBCHECK_COLOR_WHITE) {
908 WBCHECK_DEBUG("wbcheck: sweeping unmarked object %p\n", (void *)obj);
909
910 rb_gc_event_hook(obj, RUBY_INTERNAL_EVENT_FREEOBJ);
911
912 // Clear weak references first
913 rb_gc_obj_free_vm_weak_references(obj);
914
915 // Queue finalizers for postponed job if they exist
916 if (info->finalizers) {
917 make_final_job(objspace, obj, info->finalizers);
918 rb_postponed_job_trigger(objspace->finalizer_postponed_job);
919 }
920
921 // Call rb_gc_obj_free which handles finalizers/zombies
922 if (rb_gc_obj_free(objspace, obj)) {
923 // Object was actually freed, clean up our tracking
924 wbcheck_object_list_free(info->gc_mark_snapshot);
925 wbcheck_object_list_free(info->mark_maybe_snapshot);
926 wbcheck_object_list_free(info->writebarrier_children);
927 free(info);
928
929 // Free the actual object memory
930 free((void *)obj);
931
932 return ST_DELETE; // Remove from hash table
933 } else {
934 // Object became a zombie - it will be freed by postponed job
935 // Remove from tracking since we can't safely access it anymore
936 wbcheck_object_list_free(info->gc_mark_snapshot);
937 wbcheck_object_list_free(info->mark_maybe_snapshot);
938 wbcheck_object_list_free(info->writebarrier_children);
939 free(info);
940
941 // Free the actual object memory
942 free((void *)obj);
943
944 return ST_DELETE; // Remove from hash table
945 }
946 }
947
948 return ST_CONTINUE; // Keep marked objects
949}
950
951static void
952wbcheck_sweep_phase(rb_wbcheck_objspace_t *objspace)
953{
954 WBCHECK_DEBUG("wbcheck: starting sweep phase\n");
955
956 size_t objects_before = st_table_size(objspace->object_table);
957
958 // Sweep unmarked objects
959 st_foreach_check(objspace->object_table, wbcheck_sweep_callback, (st_data_t)objspace, 0);
960
961 size_t objects_after = st_table_size(objspace->object_table);
962 size_t freed_objects = objects_before - objects_after;
963
964 // Update GC threshold: 2x the live set after GC
965 objspace->gc_threshold = objects_after * 2;
966
967 WBCHECK_DEBUG("wbcheck: sweep phase complete - freed %zu objects (%zu -> %zu), new threshold: %zu\n",
968 freed_objects, objects_before, objects_after, objspace->gc_threshold);
969}
970
971// Process weak references after marking - call rb_gc_handle_weak_references
972// on each object that was flagged with RUBY_FL_WEAK_REFERENCE and collected
973// during the mark phase.
974static void
975wbcheck_process_weak_references(rb_wbcheck_objspace_t *objspace)
976{
977 WBCHECK_DEBUG("wbcheck: processing %zu weak reference objects\n", objspace->weak_references->count);
978
979 for (size_t i = 0; i < objspace->weak_references->count; i++) {
980 VALUE obj = objspace->weak_references->items[i];
981 rb_gc_handle_weak_references(objspace, obj);
982 }
983
984 objspace->weak_references->count = 0;
985}
986
987// Full GC: verify all objects then mark from roots
988static void
989wbcheck_full_gc(rb_wbcheck_objspace_t *objspace)
990{
991 WBCHECK_DEBUG("wbcheck: starting full GC\n");
992
993 objspace->simulated_gc_count++;
994
995 rb_gc_event_hook(0, RUBY_INTERNAL_EVENT_GC_ENTER);
996 rb_gc_event_hook(0, RUBY_INTERNAL_EVENT_GC_START);
997
998 // First, update snapshots for all objects (verify wb_protected ones)
999 WBCHECK_DEBUG("wbcheck: updating snapshots for all objects\n");
1000 wbcheck_foreach_object(objspace, wbcheck_update_all_snapshots_callback, objspace);
1001
1002 // Now start tri-color marking
1003 wbcheck_mark_phase(objspace);
1004
1005 rb_gc_event_hook(0, RUBY_INTERNAL_EVENT_GC_END_MARK);
1006
1007 // Process weak references after marking, before sweeping
1008 wbcheck_process_weak_references(objspace);
1009
1010 // Sweep unmarked objects
1011 wbcheck_sweep_phase(objspace);
1012
1013 rb_gc_event_hook(0, RUBY_INTERNAL_EVENT_GC_END_SWEEP);
1014 rb_gc_event_hook(0, RUBY_INTERNAL_EVENT_GC_EXIT);
1015
1016 WBCHECK_DEBUG("wbcheck: full GC complete\n");
1017}
1018
1019static void
1020gc_step(void *objspace_ptr, bool force)
1021{
1023
1024 // Not initialized yet
1025 if (!objspace) return;
1026
1027 if (!objspace->gc_enabled && !force) return;
1028
1029 // Process all objects that need verification after write barriers (if enabled)
1030 if (wbcheck_verify_after_wb_enabled) {
1031 for (size_t i = 0; i < objspace->objects_to_verify->count; i++) {
1032 VALUE obj = objspace->objects_to_verify->items[i];
1033 wbcheck_verify_object_references(objspace_ptr, obj);
1034 }
1035
1036 // Clear the list after processing
1037 objspace->objects_to_verify->count = 0;
1038
1039 wbcheck_abort_if_errors(objspace);
1040 }
1041
1042 // Process all objects that need initial reference capture
1043 for (size_t i = 0; i < objspace->objects_to_capture->count; i++) {
1044 VALUE obj = objspace->objects_to_capture->items[i];
1045 wbcheck_collect_initial_references(objspace_ptr, obj);
1046 }
1047
1048 // Clear the list after processing
1049 objspace->objects_to_capture->count = 0;
1050
1051 // Run full GC if forced, if we exceed the threshold, or if gc_stress is enabled
1052 if (ruby_native_thread_p() &&
1053 (force ||
1054 (objspace->gc_enabled &&
1055 (objspace->gc_stress || st_table_size(objspace->object_table) >= objspace->gc_threshold)))) {
1056 wbcheck_full_gc(objspace);
1057 wbcheck_abort_if_errors(objspace);
1058 }
1059
1060}
1061
1062// No barrier when GC is disabled: the NEWOBJ hook holds the VM lock without barrier support.
1063static void
1064maybe_gc_with_barrier(void *objspace_ptr)
1065{
1067
1068 if (objspace && objspace->gc_enabled) {
1069 rb_gc_vm_barrier();
1070 gc_step(objspace_ptr, false);
1071 }
1072}
1073
1074static void
1075force_gc(void *objspace_ptr)
1076{
1077 gc_step(objspace_ptr, true);
1078}
1079
1080int ruby_thread_has_gvl_p(void);
1081
1082static void *
1083lock_and_maybe_gc_gvl(void *objspace_ptr)
1084{
1085 unsigned int lev = RB_GC_VM_LOCK();
1086 maybe_gc_with_barrier(objspace_ptr);
1087 RB_GC_VM_UNLOCK(lev);
1088 return NULL;
1089}
1090
1091static void
1092lock_and_maybe_gc(void *objspace_ptr)
1093{
1094 if (!ruby_native_thread_p()) return;
1095
1096 if (!ruby_thread_has_gvl_p()) {
1097 rb_thread_call_with_gvl(lock_and_maybe_gc_gvl, objspace_ptr);
1098 }
1099 else {
1100 lock_and_maybe_gc_gvl(objspace_ptr);
1101 }
1102}
1103
1104VALUE
1105rb_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)
1106{
1107 unsigned int lev = RB_GC_VM_LOCK();
1108
1109 // Check if we should trigger GC before allocating
1110 maybe_gc_with_barrier(objspace_ptr);
1111
1112 // Ensure minimum allocation size of BASE_SLOT_SIZE
1113 alloc_size = rb_gc_impl_size_slot_size(objspace_ptr, alloc_size);
1114
1115 // Allocate memory for the object
1116 VALUE *mem = malloc(alloc_size);
1117 if (!mem) rb_bug("FIXME: malloc failed");
1118
1119 *actual_alloc_size = alloc_size;
1120
1121 // Initialize the object
1122 VALUE obj = (VALUE)mem;
1123 RBASIC(obj)->flags = flags;
1124 *((VALUE *)&RBASIC(obj)->klass) = klass;
1125
1126 // Register the new object in our tracking table
1127 wbcheck_register_object(objspace_ptr, obj, alloc_size, wb_protected);
1128
1129 // Add this object to the list of objects that need initial reference capture
1131 wbcheck_object_list_append(objspace->objects_to_capture, obj);
1132
1133 RB_GC_VM_UNLOCK(lev);
1134 return obj;
1135}
1136
1137size_t
1138rb_gc_impl_obj_slot_size(VALUE obj)
1139{
1140 unsigned int lev = RB_GC_VM_LOCK();
1141
1142 rb_wbcheck_object_info_t *info = wbcheck_get_object_info(obj);
1143 size_t result = info->alloc_size;
1144
1145 RB_GC_VM_UNLOCK(lev);
1146 return result;
1147}
1148
1149size_t
1150rb_gc_impl_size_slot_size(void *objspace_ptr, size_t size)
1151{
1152 for (int i = 0; i < HEAP_COUNT; i++) {
1153 if (size <= heap_sizes[i]) return heap_sizes[i];
1154 }
1155 rb_bug("size too big");
1156}
1157
1158bool
1159rb_gc_impl_size_allocatable_p(size_t size)
1160{
1161 // Only allow sizes up to the largest heap size
1162 return size <= rb_gc_impl_max_allocation_size();
1163}
1164
1165size_t
1166rb_gc_impl_max_allocation_size(void)
1167{
1168 return MAX_HEAP_SIZE;
1169}
1170
1171// Malloc
1172void *
1173rb_gc_impl_malloc(void *objspace_ptr, size_t size, bool gc_allowed)
1174{
1175 if (gc_allowed) {
1176 lock_and_maybe_gc(objspace_ptr);
1177 }
1178 return malloc(size);
1179}
1180
1181void *
1182rb_gc_impl_calloc(void *objspace_ptr, size_t size, bool gc_allowed)
1183{
1184 if (gc_allowed) {
1185 lock_and_maybe_gc(objspace_ptr);
1186 }
1187 return calloc(1, size);
1188}
1189
1190void *
1191rb_gc_impl_realloc(void *objspace_ptr, void *ptr, size_t new_size, size_t old_size, bool gc_allowed)
1192{
1193 if (gc_allowed) {
1194 lock_and_maybe_gc(objspace_ptr);
1195 }
1196 return realloc(ptr, new_size);
1197}
1198
1199void
1200rb_gc_impl_free(void *objspace_ptr, void *ptr, size_t old_size)
1201{
1202 free(ptr);
1203}
1204
1205void
1206rb_gc_impl_adjust_memory_usage(void *objspace_ptr, ssize_t diff)
1207{
1208 // For wbcheck, we don't track memory usage
1209}
1210
1211// Marking
1212static void
1214{
1215 WBCHECK_DEBUG("wbcheck: gc_mark called\n");
1216 wbcheck_debug_obj_info_dump(obj);
1217
1218 if (RB_SPECIAL_CONST_P(obj)) return;
1219
1220 switch (objspace->phase) {
1221 case WBCHECK_PHASE_SNAPSHOT:
1222 // Collecting references during verification
1223 GC_ASSERT(objspace->current_refs);
1224 wbcheck_object_list_append(objspace->current_refs, obj);
1225 break;
1226 case WBCHECK_PHASE_FULL_GC:
1227 // Marking during full GC
1228 wbcheck_mark_gray(objspace, obj);
1229 break;
1230 case WBCHECK_PHASE_MUTATOR:
1231 // Should not be called during mutator phase
1232 rb_bug("wbcheck: gc_mark called during mutator phase");
1233 break;
1234 }
1235}
1236
1237void
1238rb_gc_impl_mark(void *objspace_ptr, VALUE obj)
1239{
1240 rb_wbcheck_objspace_t *objspace = objspace_ptr;
1241 gc_mark(objspace, obj);
1242}
1243
1244void
1245rb_gc_impl_mark_and_move(void *objspace_ptr, VALUE *ptr)
1246{
1247 rb_wbcheck_objspace_t *objspace = objspace_ptr;
1248 gc_mark(objspace, *ptr);
1249}
1250
1251void
1252rb_gc_impl_mark_and_pin(void *objspace_ptr, VALUE obj)
1253{
1254 rb_wbcheck_objspace_t *objspace = objspace_ptr;
1255 gc_mark(objspace, obj);
1256}
1257
1258void
1259rb_gc_impl_mark_maybe(void *objspace_ptr, VALUE obj)
1260{
1261 rb_wbcheck_objspace_t *objspace = objspace_ptr;
1262
1263 if (!rb_gc_impl_live_object_p(objspace_ptr, (void *)obj)) return;
1264
1265 switch (objspace->phase) {
1266 case WBCHECK_PHASE_SNAPSHOT:
1267 // We don't know if this is actually a reference or just a value
1268 // that looks like one, so we can't expect a write barrier for it.
1269 // Keep it separate from the verifiable refs, but retain it so full
1270 // GC can mark the target gray if it does turn out to be live.
1271 if (!objspace->current_maybe_refs) {
1272 objspace->current_maybe_refs = wbcheck_object_list_init();
1273 }
1274 wbcheck_object_list_append(objspace->current_maybe_refs, obj);
1275 break;
1276 case WBCHECK_PHASE_FULL_GC:
1277 wbcheck_mark_gray(objspace, obj);
1278 break;
1279 case WBCHECK_PHASE_MUTATOR:
1280 rb_bug("wbcheck: rb_gc_impl_mark_maybe called during mutator phase");
1281 break;
1282 }
1283}
1284
1285// Weak references
1286void
1287rb_gc_impl_declare_weak_references(void *objspace_ptr, VALUE obj)
1288{
1290}
1291
1292bool
1293rb_gc_impl_handle_weak_references_alive_p(void *objspace_ptr, VALUE obj)
1294{
1296
1297 st_data_t value;
1298 if (st_lookup(objspace->object_table, (st_data_t)obj, &value)) {
1300 return info->color != WBCHECK_COLOR_WHITE;
1301 }
1302
1303 return false;
1304}
1305
1306// Compaction
1307void
1308rb_gc_impl_register_pinning_obj(void *objspace_ptr, VALUE obj)
1309{
1310 /* no-op */
1311}
1312
1313bool
1314rb_gc_impl_object_moved_p(void *objspace_ptr, VALUE obj)
1315{
1316 // Stub implementation
1317 return false;
1318}
1319
1320bool
1321rb_gc_impl_pinned_p(void *objspace_ptr, VALUE obj)
1322{
1323 // Stub implementation
1324 return false;
1325}
1326
1327VALUE
1328rb_gc_impl_location(void *objspace_ptr, VALUE value)
1329{
1330 // Stub implementation
1331 return Qnil;
1332}
1333
1334// Write barriers
1335void
1336rb_gc_impl_writebarrier(void *objspace_ptr, VALUE a, VALUE b)
1337{
1338 if (RB_SPECIAL_CONST_P(b)) return;
1339
1340 unsigned int lev = RB_GC_VM_LOCK_NO_BARRIER();
1341
1342 rb_wbcheck_objspace_t *objspace = objspace_ptr;
1343
1344 // Get the object info for the parent object (a)
1345 rb_wbcheck_object_info_t *info = wbcheck_get_object_info(a);
1346
1347 // Only record the write barrier if we have a valid snapshot
1348 if (info->state != WBCHECK_STATE_CLEAR) {
1349 RUBY_ASSERT(info->gc_mark_snapshot);
1350
1351 // Initialize writebarrier_children list if it doesn't exist
1352 if (!info->writebarrier_children) {
1353 info->writebarrier_children = wbcheck_object_list_init();
1354 }
1355
1356 // Add the new reference to the write barrier children list
1357 wbcheck_object_list_append(info->writebarrier_children, b);
1358
1359 WBCHECK_DEBUG("wbcheck: write barrier recorded reference from %p to %p\n", (void *)a, (void *)b);
1360
1361 // If verification after write barrier is enabled, queue the object for verification
1362 if (wbcheck_verify_after_wb_enabled && info->state != WBCHECK_STATE_DIRTY) {
1363 WBCHECK_DEBUG("wbcheck: queueing object for verification after write barrier\n");
1364 info->state = WBCHECK_STATE_DIRTY; // Mark as dirty
1365 wbcheck_object_list_append(objspace->objects_to_verify, a);
1366 }
1367 } else {
1368 WBCHECK_DEBUG("wbcheck: write barrier skipped (snapshot not initialized) from %p to %p\n", (void *)a, (void *)b);
1369 }
1370
1371 RB_GC_VM_UNLOCK_NO_BARRIER(lev);
1372}
1373
1374void
1375rb_gc_impl_writebarrier_unprotect(void *objspace_ptr, VALUE obj)
1376{
1377 WBCHECK_DEBUG("wbcheck: writebarrier_unprotect called on object %p\n", (void *)obj);
1378
1379 unsigned int lev = RB_GC_VM_LOCK_NO_BARRIER();
1380
1381 rb_wbcheck_object_info_t *info = wbcheck_get_object_info(obj);
1382 info->wb_protected = false;
1383
1384 RB_GC_VM_UNLOCK_NO_BARRIER(lev);
1385}
1386
1387void
1388rb_gc_impl_writebarrier_remember(void *objspace_ptr, VALUE obj)
1389{
1390 WBCHECK_DEBUG("wbcheck: writebarrier_remember called on object %p\n", (void *)obj);
1391
1392 unsigned int lev = RB_GC_VM_LOCK_NO_BARRIER();
1393
1395 rb_wbcheck_object_info_t *info = wbcheck_get_object_info(obj);
1396
1397 // Clear existing references since they may be stale
1398 if (info->state != WBCHECK_STATE_CLEAR) {
1399 RUBY_ASSERT(info->gc_mark_snapshot);
1400 wbcheck_object_list_free(info->gc_mark_snapshot);
1401 info->gc_mark_snapshot = NULL;
1402
1403 wbcheck_object_list_free(info->mark_maybe_snapshot);
1404 info->mark_maybe_snapshot = NULL;
1405
1406 // Only re-add to objects_to_capture if it had previous snapshot
1407 // (new objects don't need to be re-added since they'll be captured at allocation)
1408 wbcheck_object_list_append(objspace->objects_to_capture, obj);
1409
1410 // Also clear write barrier children
1411 if (info->writebarrier_children) {
1412 wbcheck_object_list_free(info->writebarrier_children);
1413 info->writebarrier_children = NULL;
1414 }
1415
1416 // Reset to clear state
1417 info->state = WBCHECK_STATE_CLEAR;
1418 }
1419 RUBY_ASSERT(!info->gc_mark_snapshot);
1420 RUBY_ASSERT(!info->mark_maybe_snapshot);
1421 RUBY_ASSERT(!info->writebarrier_children);
1422
1423 RB_GC_VM_UNLOCK_NO_BARRIER(lev);
1424}
1425
1426// Heap walking
1428 int (*callback)(VALUE obj, rb_wbcheck_object_info_t *info, void *data);
1429 void *data;
1430};
1431
1432static int
1433wbcheck_foreach_object_i(st_data_t key, st_data_t val, st_data_t arg)
1434{
1435 VALUE obj = (VALUE)key;
1437 struct wbcheck_foreach_data *foreach_data = (struct wbcheck_foreach_data *)arg;
1438
1439 return foreach_data->callback(obj, info, foreach_data->data);
1440}
1441
1442static void
1443wbcheck_foreach_object(rb_wbcheck_objspace_t *objspace, int (*callback)(VALUE obj, rb_wbcheck_object_info_t *info, void *data), void *data)
1444{
1445 struct wbcheck_foreach_data foreach_data = {
1446 .callback = callback,
1447 .data = data
1448 };
1449
1450 st_foreach(objspace->object_table, wbcheck_foreach_object_i, (st_data_t)&foreach_data);
1451}
1452
1453// Helper to collect all objects into a snapshot list
1454static int
1455wbcheck_snapshot_collector(st_data_t key, st_data_t val, st_data_t arg)
1456{
1457 VALUE obj = (VALUE)key;
1459 wbcheck_object_list_append(snapshot, obj);
1460 return ST_CONTINUE;
1461}
1462
1463// Take a snapshot of all objects for safe iteration
1464static wbcheck_object_list_t *
1465wbcheck_take_object_snapshot(rb_wbcheck_objspace_t *objspace)
1466{
1467 size_t object_count = st_table_size(objspace->object_table);
1468 wbcheck_object_list_t *snapshot = wbcheck_object_list_init_with_capacity(object_count);
1469 st_foreach(objspace->object_table, wbcheck_snapshot_collector, (st_data_t)snapshot);
1470 return snapshot;
1471}
1472
1473
1474void
1475rb_gc_impl_each_objects(void *objspace_ptr, int (*callback)(void *, void *, size_t, void *), void *data)
1476{
1478 GC_ASSERT(objspace);
1479
1480 wbcheck_object_list_t *snapshot = wbcheck_take_object_snapshot(objspace);
1481
1482 for (size_t i = 0; i < snapshot->count; i++) {
1483 VALUE obj = snapshot->items[i];
1484 st_data_t value;
1485 if (st_lookup(objspace->object_table, (st_data_t)obj, &value)) {
1487 int result = callback(
1488 (void *)obj,
1489 (void *)((char *)obj + info->alloc_size),
1490 info->alloc_size,
1491 data
1492 );
1493 if (result != 0) break;
1494 }
1495 }
1496
1497 wbcheck_object_list_free(snapshot);
1498}
1499
1500static int
1501wbcheck_each_objects_shareable_i(void *start, void *end, size_t stride, void *d)
1502{
1503 struct { int (*func)(void *, void *, size_t, void *); void *data; } *args = d;
1504 if (RB_FL_TEST_RAW((VALUE)start, RUBY_FL_SHAREABLE)) {
1505 return args->func(start, end, stride, args->data);
1506 }
1507 return 0;
1508}
1509
1510void
1511rb_gc_impl_each_objects_shareable(void *objspace_ptr, int (*callback)(void *, void *, size_t, void *), void *data)
1512{
1513 struct { int (*func)(void *, void *, size_t, void *); void *data; } args = { callback, data };
1514 rb_gc_impl_each_objects(objspace_ptr, wbcheck_each_objects_shareable_i, &args);
1515}
1516
1517void
1518rb_gc_impl_each_objects_foreign(void *objspace_ptr, int (*callback)(void *, void *, size_t, void *), void *data)
1519{
1520 /* A single objspace has no foreign objects. */
1521}
1522
1523bool
1524rb_gc_impl_multi_objspace_p(void)
1525{
1526 return false;
1527}
1528
1529void
1530rb_gc_impl_objspace_retire_gc(void *objspace_ptr)
1531{
1532}
1533
1534bool
1535rb_gc_impl_during_global_gc_p(void *objspace_ptr)
1536{
1537 /* Snapshots and full GCs both run under the VM lock after rb_gc_vm_barrier(),
1538 * so every other Ractor is stopped whenever we are out of the mutator phase. */
1540 return objspace->phase != WBCHECK_PHASE_MUTATOR;
1541}
1542
1543bool
1544rb_gc_impl_during_postmortem_p(void *objspace_ptr)
1545{
1546 return false;
1547}
1548
1549bool
1550rb_gc_impl_obj_foreign_p(void *objspace_ptr, VALUE obj)
1551{
1552 return false;
1553}
1554
1555bool
1556rb_gc_impl_shref_marked_p(void *objspace_ptr, VALUE obj)
1557{
1558 return true;
1559}
1560
1561void
1562rb_gc_impl_objspace_zombify(void *objspace_ptr)
1563{
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 hash_or_key)
1928{
1929 VALUE hash = Qnil, key = Qnil;
1930
1931 if (SYMBOL_P(hash_or_key)) {
1932 key = hash_or_key;
1933 }
1934 else if (RB_TYPE_P(hash_or_key, T_HASH)) {
1935 hash = hash_or_key;
1936 }
1937 else {
1938 rb_bug("rb_gc_impl_latest_gc_info: non-hash or symbol given");
1939 }
1940
1941#define SET(name, attr) \
1942 if (key == ID2SYM(rb_intern_const(#name))) \
1943 return (attr); \
1944 else if (hash != Qnil) \
1945 rb_hash_aset(hash, ID2SYM(rb_intern_const(#name)), (attr));
1946
1947 /* wbcheck doesn't do incremental GC, so the state is always :none. */
1948 SET(state, ID2SYM(rb_intern_const("none")));
1949#undef SET
1950
1951 if (!NIL_P(key)) {
1952 return Qundef;
1953 }
1954
1955 return hash;
1956}
1957
1958VALUE
1959rb_gc_impl_stat(void *objspace_ptr, VALUE hash_or_sym)
1960{
1961 if (objspace_ptr == NULL) {
1962 rb_notimplement();
1963 }
1964
1966 GC_ASSERT(objspace);
1967
1968 VALUE hash = Qnil, key = Qnil;
1969
1970 if (RB_TYPE_P(hash_or_sym, T_HASH)) {
1971 hash = hash_or_sym;
1972 }
1973 else if (SYMBOL_P(hash_or_sym)) {
1974 key = hash_or_sym;
1975 }
1976 else {
1977 rb_bug("non-hash or symbol given");
1978 }
1979
1980#define SET(name, attr) \
1981 if (key == ID2SYM(rb_intern(#name))) \
1982 return SIZET2NUM(attr); \
1983 else if (hash != Qnil) \
1984 rb_hash_aset(hash, ID2SYM(rb_intern(#name)), SIZET2NUM(attr));
1985
1986 /* Pretend each GC takes 1ms; :time is reported in milliseconds. */
1987 SET(count, objspace->simulated_gc_count);
1988 SET(time, objspace->measure_total_time ? objspace->simulated_gc_count : 0);
1989 SET(total_allocated_objects, objspace->total_allocated_objects);
1990 SET(tracked_objects, st_table_size(objspace->object_table));
1991#undef SET
1992
1993 if (!NIL_P(key)) {
1994 rb_raise(rb_eArgError, "unknown key: %"PRIsVALUE, rb_sym2str(key));
1995 }
1996
1997 return hash;
1998}
1999
2000VALUE
2001rb_gc_impl_stat_heap(void *objspace_ptr, VALUE heap_name, VALUE hash_or_sym)
2002{
2003 if (FIXNUM_P(heap_name) && SYMBOL_P(hash_or_sym)) {
2004 int heap_idx = FIX2INT(heap_name);
2005 if (heap_idx < 0 || heap_idx >= HEAP_COUNT) {
2006 rb_raise(rb_eArgError, "size pool index out of range");
2007 }
2008
2009 if (hash_or_sym == ID2SYM(rb_intern("slot_size"))) {
2010 return SIZET2NUM(heap_sizes[heap_idx]);
2011 }
2012
2013 return Qundef;
2014 }
2015
2016 if (RB_TYPE_P(hash_or_sym, T_HASH)) {
2017 return hash_or_sym;
2018 }
2019
2020 return Qundef;
2021}
2022
2023const char *
2024rb_gc_impl_active_gc_name(void)
2025{
2026 // Stub implementation
2027 return "wbcheck";
2028}
2029
2030// Miscellaneous
2031#define WBCHECK_OBJECT_METADATA_ENTRY_COUNT 3
2032static struct rb_gc_object_metadata_entry object_metadata_entries[WBCHECK_OBJECT_METADATA_ENTRY_COUNT + 1];
2033
2035rb_gc_impl_object_metadata(void *objspace_ptr, VALUE obj)
2036{
2037 static ID ID_wb_protected, ID_object_id, ID_shareable;
2038
2039 if (!ID_object_id) {
2040 ID_wb_protected = rb_intern("wb_protected");
2041 ID_object_id = rb_intern("object_id");
2042 ID_shareable = rb_intern("shareable");
2043 }
2044
2045 unsigned int lev = RB_GC_VM_LOCK();
2046 bool wb_protected = wbcheck_get_object_info(obj)->wb_protected;
2047 RB_GC_VM_UNLOCK(lev);
2048
2049 size_t n = 0;
2050
2051#define SET_ENTRY(na, v) do { \
2052 GC_ASSERT(n < WBCHECK_OBJECT_METADATA_ENTRY_COUNT); \
2053 object_metadata_entries[n].name = ID_##na; \
2054 object_metadata_entries[n].val = v; \
2055 n++; \
2056} while (0)
2057
2058 if (wb_protected) SET_ENTRY(wb_protected, Qtrue);
2059 if (rb_obj_id_p(obj)) SET_ENTRY(object_id, rb_obj_id(obj));
2060 if (FL_TEST(obj, FL_SHAREABLE)) SET_ENTRY(shareable, Qtrue);
2061#undef SET_ENTRY
2062
2063 object_metadata_entries[n].name = 0;
2064 object_metadata_entries[n].val = 0;
2065
2066 return object_metadata_entries;
2067}
2068
2069bool
2070rb_gc_impl_live_object_p(void *objspace_ptr, const void *ptr)
2071{
2072 GC_ASSERT(wbcheck_global_objspace);
2073
2074 unsigned int lev = RB_GC_VM_LOCK();
2075
2076 // Check if this pointer exists in our object tracking table
2077 st_data_t value;
2078 bool result = st_lookup(wbcheck_global_objspace->object_table, (st_data_t)ptr, &value);
2079
2080 RB_GC_VM_UNLOCK(lev);
2081 return result;
2082}
2083
2084bool
2085rb_gc_impl_garbage_object_p(void *objspace_ptr, VALUE obj)
2086{
2087 unsigned int lev = RB_GC_VM_LOCK();
2088
2089 // Check if this pointer exists in our object tracking table
2090 st_data_t value;
2091 bool result = st_lookup(wbcheck_global_objspace->object_table, (st_data_t)obj, &value);
2092
2093 RB_GC_VM_UNLOCK(lev);
2094 return !result;
2095}
2096
2097void
2098rb_gc_impl_set_event_hook(void *objspace_ptr, const rb_event_flag_t event)
2099{
2100 // Stub implementation
2101}
2102
2103void
2104rb_gc_impl_copy_attributes(void *objspace_ptr, VALUE dest, VALUE obj)
2105{
2106 rb_wbcheck_object_info_t *src_info = wbcheck_get_object_info(obj);
2107
2108 if (!src_info->wb_protected) {
2109 rb_gc_impl_writebarrier_unprotect(objspace_ptr, dest);
2110 }
2111 rb_gc_impl_copy_finalizer(objspace_ptr, dest, obj);
2112}
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:477
#define RUBY_ASSERT(...)
Asserts that the given expression is truthy if and only if RUBY_DEBUG is truthy.
Definition assert.h:219
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.
VALUE rb_f_notimplement(int argc, const VALUE *argv, VALUE obj, VALUE marker)
Raises rb_eNotImpError.
Definition vm_method.c:913
static ID rb_intern_const(const char *str)
This is a "tiny optimisation" over rb_intern().
Definition symbol.h:285
VALUE rb_sym2str(VALUE symbol)
Obtain a frozen string representation of a symbol (not including the leading colon).
Definition symbol.c:1155
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:2323
#define RB_GC_GUARD(v)
Prevents premature destruction of local objects.
Definition memory.h:170
#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:6183
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