Ruby 4.1.0dev (2026-09-21 revision 61de3dd727146cfcf24e8051595bb2fb842f37aa)
wbcheck.c
1#include "internal.h"
2#include "ruby/ruby.h"
3#include "ruby/assert.h"
4#include "ruby/atomic.h"
5#include "ruby/debug.h"
7#include "ruby/st.h"
8#include "internal/object.h"
9#include "internal/array.h"
10#include "internal/class.h"
11
12#include "ruby/thread.h"
13#include "gc/gc.h"
14#include "gc/gc_impl.h"
15
16#include <stdbool.h>
17#include <stdarg.h>
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 gc_stress; // GC stress mode (run GC on every allocation)
238 size_t gc_threshold; // Trigger GC when object count reaches this
239 size_t missed_write_barrier_parents; // Number of parent objects with missed write barriers
240 size_t missed_write_barrier_children; // Total number of missed write barriers detected
241 size_t simulated_gc_count; // Simulated GC count incremented on each GC.start
242 bool measure_total_time; // Whether to accumulate :time in stats
243 struct wbcheck_final_job *finalizer_jobs; // Linked list of finalizer jobs
244 rb_nativethread_lock_t finalizer_lock; // Protects finalizer_jobs list
245 rb_postponed_job_handle_t finalizer_postponed_job; // Postponed job handle for finalizers
246 struct rb_gc_vm_context vm_context;
248
249// Global objspace pointer for accessing from obj_slot_size function
250static rb_wbcheck_objspace_t *wbcheck_global_objspace = NULL;
251
252// Forward declarations
253static void wbcheck_foreach_object(rb_wbcheck_objspace_t *objspace, int (*callback)(VALUE obj, rb_wbcheck_object_info_t *info, void *data), void *data);
254static int wbcheck_verify_all_references_callback(VALUE obj, rb_wbcheck_object_info_t *info, void *data);
255static int wbcheck_update_all_snapshots_callback(VALUE obj, rb_wbcheck_object_info_t *info, void *data);
256static void wbcheck_run_finalizers_for_object(VALUE obj, rb_wbcheck_object_info_t *info);
257static void gc_run_finalizers(void *data);
258static void make_final_job(rb_wbcheck_objspace_t *objspace, VALUE obj, VALUE finalizer_array);
259
260// Helper functions for object tracking
262wbcheck_get_object_info(VALUE obj)
263{
264 // Objspace must be initialized by this point
265 GC_ASSERT(wbcheck_global_objspace);
266
267 st_data_t value;
268 if (st_lookup(wbcheck_global_objspace->object_table, (st_data_t)obj, &value)) {
269 return (rb_wbcheck_object_info_t *)value;
270 }
271
272 fprintf(stderr, "wbcheck: object not found in tracking table\n");
273 char buff[0x100];
274 fprintf(stderr, "%s\n", rb_raw_obj_info(buff, sizeof(buff), obj));
275
276 // Force ASAN crash?
277 ((volatile VALUE *)obj)[0];
278
279 // Object not found in tracking table - this should never happen
280 rb_bug("wbcheck: object not found in tracking table");
281}
282
283static void
284wbcheck_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)
285{
287
288 rb_wbcheck_object_info_t *parent_info = wbcheck_get_object_info(parent_obj);
289
290 size_t snapshot_count = gc_mark_snapshot ? gc_mark_snapshot->count : 0;
291 size_t wb_count = writebarrier_children ? writebarrier_children->count : 0;
292
293 fprintf(stderr, "WBCHECK ERROR: Missed write barrier detected!\n");
294 fprintf(stderr, " Parent object: %p (wb_protected: %s)\n",
295 (void *)parent_obj, parent_info->wb_protected ? "true" : "false");
296 char buff[0x100];
297 fprintf(stderr, " %s\n", rb_raw_obj_info(buff, sizeof(buff), parent_obj));
298 fprintf(stderr, " Reference counts - snapshot: %zu, writebarrier: %zu, current: %zu, missed: %zu\n",
299 snapshot_count, wb_count, current_refs->count, missed_refs->count);
300
301 for (size_t i = 0; i < missed_refs->count; i++) {
302 VALUE missed_ref = missed_refs->items[i];
303 char buff[0x100];
304 fprintf(stderr, " Missing reference to: %p\n %s\n", (void *)missed_ref, rb_raw_obj_info(buff, sizeof(buff), missed_ref));
305 }
306
307 fprintf(stderr, "\n");
308 objspace->missed_write_barrier_parents++;
309 objspace->missed_write_barrier_children += missed_refs->count;
310}
311
312// Fail if a verification sweep reported any missed write barriers. Called at
313// each sweep boundary so every offender in the sweep is printed before we fail.
314static void
315wbcheck_abort_if_errors(rb_wbcheck_objspace_t *objspace)
316{
317 if (objspace->missed_write_barrier_parents > 0) {
318 fflush(stderr);
319 rb_bug("wbcheck: missed write barrier detected (%zu object(s), %zu reference(s))",
320 objspace->missed_write_barrier_parents,
321 objspace->missed_write_barrier_children);
322 }
323}
324
325static void
326wbcheck_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)
327{
329 (void)objspace;
330
331 size_t snapshot_count = gc_mark_snapshot ? gc_mark_snapshot->count : 0;
332 size_t wb_count = writebarrier_children ? writebarrier_children->count : 0;
333
334 WBCHECK_DEBUG("wbcheck: comparing references for object %p\n", (void *)parent_obj);
335 WBCHECK_DEBUG("wbcheck: current refs: %zu, snapshot refs: %zu, wb refs: %zu\n",
336 current_refs->count, snapshot_count, wb_count);
337
338 // Collect missed references (lazily allocated)
339 wbcheck_object_list_t *missed_refs = NULL;
340
341 // Use circular comparison for better performance when lists are mostly similar
342 size_t snapshot_idx = 0;
343
344 // Check each object in current_refs to see if it's in either stored list
345 for (size_t i = 0; i < current_refs->count; i++) {
346 VALUE current_ref = current_refs->items[i];
347
348 // Usually the lists are nearly identical. We take advantage of this by
349 // attempting to loop over both lists in sequence. When the next element
350 // of the snapshot doesn't match the next element of our current_refs,
351 // we'll loop around the list to try to find it and continue from that
352 // match, so any runs of identical items can be matched efficiently.
353 //
354 // Pathologically this is O(N**2), but is O(N * num_changes)
355 bool found_in_snapshot = false;
356 if (gc_mark_snapshot && snapshot_count > 0) {
357 size_t start_idx = snapshot_idx;
358 do {
359 if (gc_mark_snapshot->items[snapshot_idx] == current_ref) {
360 found_in_snapshot = true;
361 snapshot_idx++;
362 if (snapshot_idx >= snapshot_count) snapshot_idx = 0;
363 break;
364 }
365 snapshot_idx++;
366 if (snapshot_idx >= snapshot_count) snapshot_idx = 0;
367 } while (snapshot_idx != start_idx);
368 }
369
370 if (found_in_snapshot) {
371 continue;
372 }
373
374 // Built-in immortal classes can be assigned via RBASIC_SET_CLASS_RAW,
375 // which bypasses the write barrier. They're pinned as VM roots and
376 // can never be collected, so a missing WB to them is harmless.
377 if (RB_TYPE_P(current_ref, T_CLASS) && FL_TEST_RAW(current_ref, RCLASS_IS_ROOT)) {
378 continue;
379 }
380
381 // Self reference... Weird but okay I guess
382 if (current_ref == parent_obj) {
383 continue;
384 }
385
386
387 // Check if reference exists in writebarrier_children
388 if (writebarrier_children && wbcheck_object_list_contains(writebarrier_children, current_ref)) {
389 continue;
390 }
391
392 // If we get here, the reference wasn't found in either list
393 // Lazily allocate missed_refs list on first miss
394 if (!missed_refs) {
395 missed_refs = wbcheck_object_list_init();
396 }
397 wbcheck_object_list_append(missed_refs, current_ref);
398 }
399
400 // Report any errors found
401 if (missed_refs) {
402 wbcheck_report_error(objspace_ptr, parent_obj, current_refs, gc_mark_snapshot, writebarrier_children, missed_refs);
403 wbcheck_object_list_free(missed_refs);
404 }
405}
406
407static void
408wbcheck_register_object(void *objspace_ptr, VALUE obj, size_t alloc_size, bool wb_protected)
409{
411 GC_ASSERT(objspace);
412
413 // Allocate and initialize object info structure
414 rb_wbcheck_object_info_t *info = calloc(1, sizeof(rb_wbcheck_object_info_t));
415 if (!info) rb_bug("wbcheck_register_object: failed to allocate object info");
416
417 info->alloc_size = alloc_size;
418 info->wb_protected = wb_protected;
419 info->finalizers = 0; /* No finalizers initially */
420 info->gc_mark_snapshot = NULL; /* No snapshot initially */
421 info->mark_maybe_snapshot = NULL; /* No mark_maybe snapshot initially */
422 info->writebarrier_children = NULL; /* No write barrier children initially */
423 info->state = WBCHECK_STATE_CLEAR; /* Start in clear state */
424 info->color = WBCHECK_COLOR_BLACK; /* Start as black to survive current GC */
425
426 // Store object info in hash table (VALUE -> rb_wbcheck_object_info_t*)
427 st_insert(objspace->object_table, (st_data_t)obj, (st_data_t)info);
428}
429
430static void
431wbcheck_unregister_object(void *objspace_ptr, VALUE obj)
432{
435
436 if (st_delete(objspace->object_table, (st_data_t *)&obj, (st_data_t *)&info)) {
437 // Free object lists if they were allocated
438 wbcheck_object_list_free(info->gc_mark_snapshot);
439 wbcheck_object_list_free(info->mark_maybe_snapshot);
440 wbcheck_object_list_free(info->writebarrier_children);
441 free(info);
442 } else {
443 rb_bug("wbcheck_unregister_object: object not found in table");
444 }
445}
446
447// Bootup
448void *
449rb_gc_impl_objspace_alloc(void)
450{
451 wbcheck_configure_from_env();
452
454 if (!objspace) rb_bug("wbcheck: failed to allocate objspace");
455
456 objspace->object_table = st_init_numtable();
457 if (!objspace->object_table) {
458 free(objspace);
459 rb_bug("wbcheck: failed to create object table");
460 }
461
462 objspace->objects_to_capture = wbcheck_object_list_init(); // Initialize empty list
463 objspace->objects_to_verify = wbcheck_object_list_init(); // Initialize empty list
464 objspace->current_refs = NULL; // No current refs initially
465 objspace->current_maybe_refs = NULL; // No current maybe refs initially
466 objspace->mark_queue = wbcheck_object_list_init(); // Initialize mark queue
467 objspace->weak_references = wbcheck_object_list_init(); // Initialize weak references array
468 objspace->phase = WBCHECK_PHASE_MUTATOR; // Start in mutator phase
469 objspace->gc_enabled = true; // GC enabled by default (like default GC)
470 objspace->gc_stress = false; // GC stress disabled by default
471 objspace->gc_threshold = 1000; // Start with 1000 objects, will adjust after first GC
472 objspace->missed_write_barrier_parents = 0; // No errors found yet
473 objspace->missed_write_barrier_children = 0; // No errors found yet
474 objspace->simulated_gc_count = 0; // Start with GC count of 0
475 objspace->measure_total_time = true; // On by default
476
477 return objspace;
478}
479
480void
481rb_gc_impl_objspace_init(void *objspace_ptr)
482{
483 rb_wbcheck_objspace_t *objspace = objspace_ptr;
484
485 // Object table is already initialized in objspace_alloc
486 // Set up global objspace pointer for obj_slot_size function
487 wbcheck_global_objspace = objspace;
488
489 // Initialize postponed job for finalizers
490 rb_native_mutex_initialize(&objspace->finalizer_lock);
491 objspace->finalizer_postponed_job = rb_postponed_job_preregister(0, gc_run_finalizers, objspace);
492}
493
494void *
495rb_gc_impl_ractor_cache_alloc(void *objspace_ptr, void *ractor)
496{
497 // Stub implementation
498 return NULL;
499}
500
501bool
502rb_gc_impl_zjit_new_obj_fastpath(void *objspace_ptr, size_t alloc_size, VALUE flags, VALUE klass,
503 struct rb_gc_zjit_fastpath *fastpath)
504{
505 return false;
506}
507
508void
509rb_gc_impl_set_params(void *objspace_ptr)
510{
511 // Stub implementation
512}
513
514static VALUE
515gc_verify_internal_consistency(VALUE self)
516{
517 return Qnil;
518}
519
520void
521rb_gc_impl_init(void)
522{
523 VALUE gc_constants = rb_hash_new();
524 //rb_hash_aset(gc_constants, ID2SYM(rb_intern("BASE_SLOT_SIZE")), SIZET2NUM(BASE_SLOT_SIZE));
525 rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVALUE_SIZE")), SIZET2NUM(sizeof(struct RBasic) + sizeof(VALUE[RBIMPL_RVALUE_EMBED_LEN_MAX])));
526 rb_hash_aset(gc_constants, ID2SYM(rb_intern("RBASIC_SIZE")), SIZET2NUM(sizeof(struct RBasic)));
527 rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVALUE_OVERHEAD")), INT2NUM(0));
528 rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVARGC_MAX_ALLOCATE_SIZE")), SIZET2NUM(rb_gc_impl_max_allocation_size()));
529 rb_hash_aset(gc_constants, ID2SYM(rb_intern("HEAP_COUNT")), LONG2FIX(HEAP_COUNT));
530 rb_hash_aset(gc_constants, ID2SYM(rb_intern("SIZE_POOL_COUNT")), LONG2FIX(HEAP_COUNT));
531 rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVALUE_OLD_AGE")), INT2FIX(3));
532 OBJ_FREEZE(gc_constants);
533 rb_define_const(rb_mGC, "INTERNAL_CONSTANTS", gc_constants);
534
535 // no-ops for compatibility
536 rb_define_singleton_method(rb_mGC, "verify_internal_consistency", gc_verify_internal_consistency, 0);
537
541 rb_define_singleton_method(rb_mGC, "latest_compact_info", rb_f_notimplement, 0);
542 rb_define_singleton_method(rb_mGC, "verify_compaction_references", rb_f_notimplement, -1);
543 // Stub implementation
544}
545
546// Shutdown
547void
548rb_gc_impl_shutdown_free_objects(void *objspace_ptr)
549{
550 // Stub implementation
551}
552
553void
554rb_gc_impl_objspace_free(void *objspace_ptr)
555{
556 // This should free everything, but we'll just let it leak
557}
558
559void
560rb_gc_impl_ractor_cache_free(void *objspace_ptr, void *cache)
561{
562 // Stub implementation
563}
564
565// GC
566void
567rb_gc_impl_start(void *objspace_ptr, bool full_mark, bool immediate_mark, bool immediate_sweep, bool compact)
568{
570 if (objspace) {
571 objspace->simulated_gc_count++;
572 }
573
574 if (!ruby_native_thread_p()) return;
575
576 unsigned int lev = RB_GC_VM_LOCK();
577 rb_gc_vm_barrier();
578 force_gc(objspace_ptr);
579 RB_GC_VM_UNLOCK(lev);
580}
581
582bool
583rb_gc_impl_during_gc_p(void *objspace_ptr)
584{
586 return objspace->phase != WBCHECK_PHASE_MUTATOR;
587}
588
589static void
590wbcheck_prepare_heap_i(VALUE obj, void *data)
591{
592 rb_gc_prepare_heap_process_object(obj);
593}
594
595void
596rb_gc_impl_prepare_heap(void *objspace_ptr)
597{
598 rb_gc_impl_each_object(objspace_ptr, wbcheck_prepare_heap_i, NULL);
599}
600
601void
602rb_gc_impl_gc_enable(void *objspace_ptr)
603{
605 objspace->gc_enabled = true;
606}
607
608void
609rb_gc_impl_gc_disable(void *objspace_ptr, bool finish_current_gc)
610{
612 objspace->gc_enabled = false;
613}
614
615bool
616rb_gc_impl_gc_enabled_p(void *objspace_ptr)
617{
619 return objspace->gc_enabled;
620}
621
622void
623rb_gc_impl_stress_set(void *objspace_ptr, VALUE flag)
624{
626 objspace->gc_stress = RTEST(flag);
627}
628
629VALUE
630rb_gc_impl_stress_get(void *objspace_ptr)
631{
633 return objspace->gc_stress ? Qtrue : Qfalse;
634}
635
636VALUE
637rb_gc_impl_config_get(void *objspace_ptr)
638{
639 return rb_hash_new();
640}
641
642void
643rb_gc_impl_config_set(void *objspace_ptr, VALUE hash)
644{
645}
646
647struct rb_gc_vm_context *
648rb_gc_impl_get_vm_context(void *objspace_ptr)
649{
650 rb_wbcheck_objspace_t *objspace = objspace_ptr;
651
652 return &objspace->vm_context;
653}
654
656wbcheck_collect_references_from_object(VALUE obj, rb_wbcheck_object_info_t *info)
657{
658 rb_wbcheck_objspace_t *objspace = wbcheck_global_objspace;
659
660 // Use combination of writebarrier children and last snapshot as capacity hint
661 size_t snapshot_count = (info->gc_mark_snapshot) ? info->gc_mark_snapshot->count : 0;
662 size_t wb_children_count = (info->writebarrier_children) ? info->writebarrier_children->count : 0;
663 size_t capacity_hint = snapshot_count + wb_children_count;
664 wbcheck_object_list_t *new_list = wbcheck_object_list_init_with_capacity(capacity_hint);
665
666 // Set up objspace state for marking. current_maybe_refs is allocated lazily
667 // by rb_gc_impl_mark_maybe, since most objects have no conservative refs.
668 objspace->current_refs = new_list;
669 objspace->current_maybe_refs = NULL;
670 objspace->phase = WBCHECK_PHASE_SNAPSHOT;
671 rb_gc_initialize_vm_context(&objspace->vm_context);
672
673 // Use the marking infrastructure to collect references
674 rb_gc_mark_children(objspace, obj);
675
676 // Clean up objspace state
677 objspace->phase = WBCHECK_PHASE_MUTATOR;
678 objspace->current_refs = NULL;
679
680 // Update the mark_maybe snapshot in place. These references don't participate
681 // in verification, but we need to keep them so full GC can mark them gray.
682 wbcheck_object_list_free(info->mark_maybe_snapshot);
683 info->mark_maybe_snapshot = objspace->current_maybe_refs;
684 objspace->current_maybe_refs = NULL;
685
686 if (wbcheck_debug_enabled) {
687 WBCHECK_DEBUG("wbcheck: collected %zu references from %p\n", new_list->count, (void *)obj);
688 char buff[0x100];
689 fprintf(stderr, "%s\n", rb_raw_obj_info(buff, sizeof(buff), obj));
690 wbcheck_object_list_debug_print(new_list);
691 }
692
693 return new_list;
694}
695
696static void
697wbcheck_collect_initial_references(void *objspace_ptr, VALUE obj)
698{
699 WBCHECK_DEBUG("wbcheck: collecting initial references from %p:\n", obj);
700 wbcheck_debug_obj_info_dump(obj);
701
702 // Get the object info and set the initial GC mark snapshot
703 rb_wbcheck_object_info_t *info = wbcheck_get_object_info(obj);
704 wbcheck_object_list_t *new_list = wbcheck_collect_references_from_object(obj, info);
705 RUBY_ASSERT(!info->gc_mark_snapshot);
706 RUBY_ASSERT(info->state == WBCHECK_STATE_CLEAR);
707 info->gc_mark_snapshot = new_list; // Set the initial snapshot
708 info->state = WBCHECK_STATE_MARKED; // Transition to marked state
709}
710
711static void
712wbcheck_verify_object_references(void *objspace_ptr, VALUE obj)
713{
714 rb_wbcheck_object_info_t *info = wbcheck_get_object_info(obj);
715
716 // Ignore objects which are not write barrier protected
717 if (!info->wb_protected) {
718 return;
719 }
720
721 // We hadn't captured initial references
722 if (info->state == WBCHECK_STATE_CLEAR) {
723 RUBY_ASSERT(!info->gc_mark_snapshot);
724 return;
725 }
726
727 WBCHECK_DEBUG("wbcheck: verifying references for object:\n");
728 wbcheck_debug_obj_info_dump(obj);
729
730 // Get the current references from the object
731 wbcheck_object_list_t *current_refs = wbcheck_collect_references_from_object(obj, info);
732
733 // Check for useless write barriers before clearing them
734 if (wbcheck_warn_useless_wb_enabled && info->writebarrier_children) {
735 for (size_t i = 0; i < info->writebarrier_children->count; i++) {
736 VALUE wb_ref = info->writebarrier_children->items[i];
737 if (!wbcheck_object_list_contains(current_refs, wb_ref)) {
738 fprintf(stderr, "WBCHECK WARNING: Potentially useless write barrier detected for object %p\n", (void *)obj);
739 fprintf(stderr, " Write barrier was recorded for reference to %p, but object no longer references it\n", (void *)wb_ref);
740 char buff[0x100];
741 fprintf(stderr, " Parent: %s\n", rb_raw_obj_info(buff, sizeof(buff), obj));
742 fprintf(stderr, " Stale reference: %s\n", rb_raw_obj_info(buff, sizeof(buff), wb_ref));
743 }
744 }
745 }
746
747 // Compare current_refs against both stored lists to detect missed write barriers
748 wbcheck_compare_references(objspace_ptr, obj, current_refs, info->gc_mark_snapshot, info->writebarrier_children);
749
750 // Update the snapshot with current references and clear write barrier children
751 wbcheck_object_list_free(info->gc_mark_snapshot);
752 wbcheck_object_list_free(info->writebarrier_children);
753 info->gc_mark_snapshot = current_refs;
754 info->writebarrier_children = NULL;
755 info->state = WBCHECK_STATE_MARKED; // Back to marked state after verification
756}
757
758// Mark object as gray (add to mark queue)
759static void
760wbcheck_mark_gray(rb_wbcheck_objspace_t *objspace, VALUE obj)
761{
762 if (RB_SPECIAL_CONST_P(obj)) return;
763
764 st_data_t value;
765 if (!st_lookup(objspace->object_table, (st_data_t)obj, &value)) {
766 rb_bug("wbcheck: asked to mark object %p not in our object table", (void *)obj);
767 }
768
770 if (info->color != WBCHECK_COLOR_WHITE) {
771 return; // Already marked
772 }
773
774 info->color = WBCHECK_COLOR_GRAY;
775 wbcheck_object_list_append(objspace->mark_queue, obj);
776
778 wbcheck_object_list_append(objspace->weak_references, obj);
779 }
780
781 WBCHECK_DEBUG("wbcheck: marked gray: %p\n", (void *)obj);
782}
783
784// Reset all objects to white
785static int
786st_foreach_reset_white(st_data_t key, st_data_t val, st_data_t arg)
787{
789 info->color = WBCHECK_COLOR_WHITE;
790 return ST_CONTINUE;
791}
792
793// Mark all finalizer arrays to keep them alive during GC
794static int
795st_foreach_mark_finalizers(st_data_t key, st_data_t val, st_data_t arg)
796{
799
800 if (info->finalizers) {
801 wbcheck_mark_gray(objspace, info->finalizers);
802 }
803
804 return ST_CONTINUE;
805}
806
807// Full mark phase using tri-color marking with snapshots
808static void
809wbcheck_mark_phase(rb_wbcheck_objspace_t *objspace)
810{
811 WBCHECK_DEBUG("wbcheck: starting GC mark phase\n");
812
813 objspace->phase = WBCHECK_PHASE_FULL_GC;
814 rb_gc_initialize_vm_context(&objspace->vm_context);
815
816 // Clear mark queue and reset all objects to white
817 objspace->mark_queue->count = 0;
818 st_foreach(objspace->object_table, st_foreach_reset_white, 0);
819
820 // Mark all finalizer arrays first to keep them alive
821 st_foreach(objspace->object_table, st_foreach_mark_finalizers, (st_data_t)objspace);
822
823 // Mark finalizer arrays in pending jobs to keep them alive.
824 // No lock needed: all other threads are stopped during GC.
825 struct wbcheck_final_job *job = objspace->finalizer_jobs;
826 while (job != NULL) {
827 switch (job->kind) {
828 case WBCHECK_FINAL_JOB_DFREE:
829 break;
830 case WBCHECK_FINAL_JOB_FINALIZE:
831 wbcheck_mark_gray(objspace, job->as.finalize.finalizer_array);
832 break;
833 default:
834 rb_bug("wbcheck_mark_phase: unknown final job type %d", job->kind);
835 }
836 job = job->next;
837 }
838
839 // Mark roots gray
840 rb_gc_save_machine_context();
841 rb_gc_mark_roots(objspace, NULL);
842
843 // Process gray queue until empty
844 while (objspace->mark_queue->count > 0) {
845 // Get last object from queue (LIFO)
846 VALUE obj = objspace->mark_queue->items[--objspace->mark_queue->count];
847
848 st_data_t value;
849 if (st_lookup(objspace->object_table, (st_data_t)obj, &value)) {
851 if (info->color == WBCHECK_COLOR_GRAY) {
852 // Mark all children from snapshot gray
853 if (info->gc_mark_snapshot) {
854 for (size_t i = 0; i < info->gc_mark_snapshot->count; i++) {
855 wbcheck_mark_gray(objspace, info->gc_mark_snapshot->items[i]);
856 }
857 }
858
859 // Conservatively-scanned children must also be kept alive
860 if (info->mark_maybe_snapshot) {
861 for (size_t i = 0; i < info->mark_maybe_snapshot->count; i++) {
862 wbcheck_mark_gray(objspace, info->mark_maybe_snapshot->items[i]);
863 }
864 }
865
866 // Mark this object black
867 info->color = WBCHECK_COLOR_BLACK;
868 WBCHECK_DEBUG("wbcheck: marked black: %p\n", (void *)obj);
869 }
870 }
871 }
872
873 objspace->phase = WBCHECK_PHASE_MUTATOR;
874
875 WBCHECK_DEBUG("wbcheck: tri-color mark phase complete\n");
876}
877
878// Sweep phase callback - free white objects
879static int
880wbcheck_sweep_callback(st_data_t key, st_data_t val, st_data_t arg, int error)
881{
882 VALUE obj = (VALUE)key;
885
886 if (info->color == WBCHECK_COLOR_WHITE) {
887 WBCHECK_DEBUG("wbcheck: sweeping unmarked object %p\n", (void *)obj);
888
889 rb_gc_event_hook(obj, RUBY_INTERNAL_EVENT_FREEOBJ);
890
891 // Clear weak references first
892 rb_gc_obj_free_vm_weak_references(obj);
893
894 // Queue finalizers for postponed job if they exist
895 if (info->finalizers) {
896 make_final_job(objspace, obj, info->finalizers);
897 rb_postponed_job_trigger(objspace->finalizer_postponed_job);
898 }
899
900 // Call rb_gc_obj_free which handles finalizers/zombies
901 if (rb_gc_obj_free(objspace, obj)) {
902 // Object was actually freed, clean up our tracking
903 wbcheck_object_list_free(info->gc_mark_snapshot);
904 wbcheck_object_list_free(info->mark_maybe_snapshot);
905 wbcheck_object_list_free(info->writebarrier_children);
906 free(info);
907
908 // Free the actual object memory
909 free((void *)obj);
910
911 return ST_DELETE; // Remove from hash table
912 } else {
913 // Object became a zombie - it will be freed by postponed job
914 // Remove from tracking since we can't safely access it anymore
915 wbcheck_object_list_free(info->gc_mark_snapshot);
916 wbcheck_object_list_free(info->mark_maybe_snapshot);
917 wbcheck_object_list_free(info->writebarrier_children);
918 free(info);
919
920 // Free the actual object memory
921 free((void *)obj);
922
923 return ST_DELETE; // Remove from hash table
924 }
925 }
926
927 return ST_CONTINUE; // Keep marked objects
928}
929
930static void
931wbcheck_sweep_phase(rb_wbcheck_objspace_t *objspace)
932{
933 WBCHECK_DEBUG("wbcheck: starting sweep phase\n");
934
935 size_t objects_before = st_table_size(objspace->object_table);
936
937 // Sweep unmarked objects
938 st_foreach_check(objspace->object_table, wbcheck_sweep_callback, (st_data_t)objspace, 0);
939
940 size_t objects_after = st_table_size(objspace->object_table);
941 size_t freed_objects = objects_before - objects_after;
942
943 // Update GC threshold: 2x the live set after GC
944 objspace->gc_threshold = objects_after * 2;
945
946 WBCHECK_DEBUG("wbcheck: sweep phase complete - freed %zu objects (%zu -> %zu), new threshold: %zu\n",
947 freed_objects, objects_before, objects_after, objspace->gc_threshold);
948}
949
950// Process weak references after marking - call rb_gc_handle_weak_references
951// on each object that was flagged with RUBY_FL_WEAK_REFERENCE and collected
952// during the mark phase.
953static void
954wbcheck_process_weak_references(rb_wbcheck_objspace_t *objspace)
955{
956 WBCHECK_DEBUG("wbcheck: processing %zu weak reference objects\n", objspace->weak_references->count);
957
958 for (size_t i = 0; i < objspace->weak_references->count; i++) {
959 VALUE obj = objspace->weak_references->items[i];
960 rb_gc_handle_weak_references(obj);
961 }
962
963 objspace->weak_references->count = 0;
964}
965
966// Full GC: verify all objects then mark from roots
967static void
968wbcheck_full_gc(rb_wbcheck_objspace_t *objspace)
969{
970 WBCHECK_DEBUG("wbcheck: starting full GC\n");
971
972 rb_gc_event_hook(0, RUBY_INTERNAL_EVENT_GC_ENTER);
973 rb_gc_event_hook(0, RUBY_INTERNAL_EVENT_GC_START);
974
975 // First, update snapshots for all objects (verify wb_protected ones)
976 WBCHECK_DEBUG("wbcheck: updating snapshots for all objects\n");
977 wbcheck_foreach_object(objspace, wbcheck_update_all_snapshots_callback, objspace);
978
979 // Now start tri-color marking
980 wbcheck_mark_phase(objspace);
981
982 rb_gc_event_hook(0, RUBY_INTERNAL_EVENT_GC_END_MARK);
983
984 // Process weak references after marking, before sweeping
985 wbcheck_process_weak_references(objspace);
986
987 // Sweep unmarked objects
988 wbcheck_sweep_phase(objspace);
989
990 rb_gc_event_hook(0, RUBY_INTERNAL_EVENT_GC_END_SWEEP);
991 rb_gc_event_hook(0, RUBY_INTERNAL_EVENT_GC_EXIT);
992
993 WBCHECK_DEBUG("wbcheck: full GC complete\n");
994}
995
996static void
997gc_step(void *objspace_ptr, bool force)
998{
1000
1001 // Not initialized yet
1002 if (!objspace) return;
1003
1004 if (!objspace->gc_enabled && !force) return;
1005
1006 // Process all objects that need verification after write barriers (if enabled)
1007 if (wbcheck_verify_after_wb_enabled) {
1008 for (size_t i = 0; i < objspace->objects_to_verify->count; i++) {
1009 VALUE obj = objspace->objects_to_verify->items[i];
1010 wbcheck_verify_object_references(objspace_ptr, obj);
1011 }
1012
1013 // Clear the list after processing
1014 objspace->objects_to_verify->count = 0;
1015
1016 wbcheck_abort_if_errors(objspace);
1017 }
1018
1019 // Process all objects that need initial reference capture
1020 for (size_t i = 0; i < objspace->objects_to_capture->count; i++) {
1021 VALUE obj = objspace->objects_to_capture->items[i];
1022 wbcheck_collect_initial_references(objspace_ptr, obj);
1023 }
1024
1025 // Clear the list after processing
1026 objspace->objects_to_capture->count = 0;
1027
1028 // Run full GC if forced, if we exceed the threshold, or if gc_stress is enabled
1029 if (ruby_native_thread_p() &&
1030 (force ||
1031 (objspace->gc_enabled &&
1032 (objspace->gc_stress || st_table_size(objspace->object_table) >= objspace->gc_threshold)))) {
1033 wbcheck_full_gc(objspace);
1034 wbcheck_abort_if_errors(objspace);
1035 }
1036
1037}
1038
1039static void
1040maybe_gc(void *objspace_ptr)
1041{
1042 gc_step(objspace_ptr, false);
1043}
1044
1045static void
1046force_gc(void *objspace_ptr)
1047{
1048 gc_step(objspace_ptr, true);
1049}
1050
1051int ruby_thread_has_gvl_p(void);
1052
1053static void *
1054lock_and_maybe_gc_gvl(void *objspace_ptr)
1055{
1056 unsigned int lev = RB_GC_VM_LOCK();
1057 rb_gc_vm_barrier();
1058
1059 maybe_gc(objspace_ptr);
1060
1061 RB_GC_VM_UNLOCK(lev);
1062 return NULL;
1063}
1064
1065static void
1066lock_and_maybe_gc(void *objspace_ptr)
1067{
1068 if (!ruby_native_thread_p()) return;
1069
1070 if (!ruby_thread_has_gvl_p()) {
1071 rb_thread_call_with_gvl(lock_and_maybe_gc_gvl, objspace_ptr);
1072 }
1073 else {
1074 lock_and_maybe_gc_gvl(objspace_ptr);
1075 }
1076}
1077
1078VALUE
1079rb_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)
1080{
1081 unsigned int lev = RB_GC_VM_LOCK();
1082 rb_gc_vm_barrier();
1083
1084 // Check if we should trigger GC before allocating
1085 maybe_gc(objspace_ptr);
1086
1087 // Ensure minimum allocation size of BASE_SLOT_SIZE
1088 alloc_size = rb_gc_impl_size_slot_size(objspace_ptr, alloc_size);
1089
1090 // Allocate memory for the object
1091 VALUE *mem = malloc(alloc_size);
1092 if (!mem) rb_bug("FIXME: malloc failed");
1093
1094 *actual_alloc_size = alloc_size;
1095
1096 // Initialize the object
1097 VALUE obj = (VALUE)mem;
1098 RBASIC(obj)->flags = flags;
1099 *((VALUE *)&RBASIC(obj)->klass) = klass;
1100
1101 // Register the new object in our tracking table
1102 wbcheck_register_object(objspace_ptr, obj, alloc_size, wb_protected);
1103
1104 // Add this object to the list of objects that need initial reference capture
1106 wbcheck_object_list_append(objspace->objects_to_capture, obj);
1107
1108 RB_GC_VM_UNLOCK(lev);
1109 return obj;
1110}
1111
1112size_t
1113rb_gc_impl_obj_slot_size(VALUE obj)
1114{
1115 unsigned int lev = RB_GC_VM_LOCK();
1116
1117 rb_wbcheck_object_info_t *info = wbcheck_get_object_info(obj);
1118 size_t result = info->alloc_size;
1119
1120 RB_GC_VM_UNLOCK(lev);
1121 return result;
1122}
1123
1124size_t
1125rb_gc_impl_size_slot_size(void *objspace_ptr, size_t size)
1126{
1127 for (int i = 0; i < HEAP_COUNT; i++) {
1128 if (size <= heap_sizes[i]) return heap_sizes[i];
1129 }
1130 rb_bug("size too big");
1131}
1132
1133bool
1134rb_gc_impl_size_allocatable_p(size_t size)
1135{
1136 // Only allow sizes up to the largest heap size
1137 return size <= rb_gc_impl_max_allocation_size();
1138}
1139
1140size_t
1141rb_gc_impl_max_allocation_size(void)
1142{
1143 return MAX_HEAP_SIZE;
1144}
1145
1146// Malloc
1147void *
1148rb_gc_impl_malloc(void *objspace_ptr, size_t size, bool gc_allowed)
1149{
1150 if (gc_allowed) {
1151 lock_and_maybe_gc(objspace_ptr);
1152 }
1153 return malloc(size);
1154}
1155
1156void *
1157rb_gc_impl_calloc(void *objspace_ptr, size_t size, bool gc_allowed)
1158{
1159 if (gc_allowed) {
1160 lock_and_maybe_gc(objspace_ptr);
1161 }
1162 return calloc(1, size);
1163}
1164
1165void *
1166rb_gc_impl_realloc(void *objspace_ptr, void *ptr, size_t new_size, size_t old_size, bool gc_allowed)
1167{
1168 if (gc_allowed) {
1169 lock_and_maybe_gc(objspace_ptr);
1170 }
1171 return realloc(ptr, new_size);
1172}
1173
1174void
1175rb_gc_impl_free(void *objspace_ptr, void *ptr, size_t old_size)
1176{
1177 free(ptr);
1178}
1179
1180void
1181rb_gc_impl_adjust_memory_usage(void *objspace_ptr, ssize_t diff)
1182{
1183 // For wbcheck, we don't track memory usage
1184}
1185
1186// Marking
1187static void
1189{
1190 WBCHECK_DEBUG("wbcheck: gc_mark called\n");
1191 wbcheck_debug_obj_info_dump(obj);
1192
1193 if (RB_SPECIAL_CONST_P(obj)) return;
1194
1195 switch (objspace->phase) {
1196 case WBCHECK_PHASE_SNAPSHOT:
1197 // Collecting references during verification
1198 GC_ASSERT(objspace->current_refs);
1199 wbcheck_object_list_append(objspace->current_refs, obj);
1200 break;
1201 case WBCHECK_PHASE_FULL_GC:
1202 // Marking during full GC
1203 wbcheck_mark_gray(objspace, obj);
1204 break;
1205 case WBCHECK_PHASE_MUTATOR:
1206 // Should not be called during mutator phase
1207 rb_bug("wbcheck: gc_mark called during mutator phase");
1208 break;
1209 }
1210}
1211
1212void
1213rb_gc_impl_mark(void *objspace_ptr, VALUE obj)
1214{
1215 rb_wbcheck_objspace_t *objspace = objspace_ptr;
1216 gc_mark(objspace, obj);
1217}
1218
1219void
1220rb_gc_impl_mark_and_move(void *objspace_ptr, VALUE *ptr)
1221{
1222 rb_wbcheck_objspace_t *objspace = objspace_ptr;
1223 gc_mark(objspace, *ptr);
1224}
1225
1226void
1227rb_gc_impl_mark_and_pin(void *objspace_ptr, VALUE obj)
1228{
1229 rb_wbcheck_objspace_t *objspace = objspace_ptr;
1230 gc_mark(objspace, obj);
1231}
1232
1233void
1234rb_gc_impl_mark_maybe(void *objspace_ptr, VALUE obj)
1235{
1236 rb_wbcheck_objspace_t *objspace = objspace_ptr;
1237
1238 if (!rb_gc_impl_live_object_p(objspace_ptr, (void *)obj)) return;
1239
1240 switch (objspace->phase) {
1241 case WBCHECK_PHASE_SNAPSHOT:
1242 // We don't know if this is actually a reference or just a value
1243 // that looks like one, so we can't expect a write barrier for it.
1244 // Keep it separate from the verifiable refs, but retain it so full
1245 // GC can mark the target gray if it does turn out to be live.
1246 if (!objspace->current_maybe_refs) {
1247 objspace->current_maybe_refs = wbcheck_object_list_init();
1248 }
1249 wbcheck_object_list_append(objspace->current_maybe_refs, obj);
1250 break;
1251 case WBCHECK_PHASE_FULL_GC:
1252 wbcheck_mark_gray(objspace, obj);
1253 break;
1254 case WBCHECK_PHASE_MUTATOR:
1255 rb_bug("wbcheck: rb_gc_impl_mark_maybe called during mutator phase");
1256 break;
1257 }
1258}
1259
1260// Weak references
1261void
1262rb_gc_impl_declare_weak_references(void *objspace_ptr, VALUE obj)
1263{
1265}
1266
1267bool
1268rb_gc_impl_handle_weak_references_alive_p(void *objspace_ptr, VALUE obj)
1269{
1271
1272 st_data_t value;
1273 if (st_lookup(objspace->object_table, (st_data_t)obj, &value)) {
1275 return info->color != WBCHECK_COLOR_WHITE;
1276 }
1277
1278 return false;
1279}
1280
1281// Compaction
1282void
1283rb_gc_impl_register_pinning_obj(void *objspace_ptr, VALUE obj)
1284{
1285 /* no-op */
1286}
1287
1288bool
1289rb_gc_impl_object_moved_p(void *objspace_ptr, VALUE obj)
1290{
1291 // Stub implementation
1292 return false;
1293}
1294
1295bool
1296rb_gc_impl_pinned_p(void *objspace_ptr, VALUE obj)
1297{
1298 // Stub implementation
1299 return false;
1300}
1301
1302VALUE
1303rb_gc_impl_location(void *objspace_ptr, VALUE value)
1304{
1305 // Stub implementation
1306 return Qnil;
1307}
1308
1309// Write barriers
1310void
1311rb_gc_impl_writebarrier(void *objspace_ptr, VALUE a, VALUE b)
1312{
1313 if (RB_SPECIAL_CONST_P(b)) return;
1314
1315 unsigned int lev = RB_GC_VM_LOCK_NO_BARRIER();
1316
1317 rb_wbcheck_objspace_t *objspace = objspace_ptr;
1318
1319 // Get the object info for the parent object (a)
1320 rb_wbcheck_object_info_t *info = wbcheck_get_object_info(a);
1321
1322 // Only record the write barrier if we have a valid snapshot
1323 if (info->state != WBCHECK_STATE_CLEAR) {
1324 RUBY_ASSERT(info->gc_mark_snapshot);
1325
1326 // Initialize writebarrier_children list if it doesn't exist
1327 if (!info->writebarrier_children) {
1328 info->writebarrier_children = wbcheck_object_list_init();
1329 }
1330
1331 // Add the new reference to the write barrier children list
1332 wbcheck_object_list_append(info->writebarrier_children, b);
1333
1334 WBCHECK_DEBUG("wbcheck: write barrier recorded reference from %p to %p\n", (void *)a, (void *)b);
1335
1336 // If verification after write barrier is enabled, queue the object for verification
1337 if (wbcheck_verify_after_wb_enabled && info->state != WBCHECK_STATE_DIRTY) {
1338 WBCHECK_DEBUG("wbcheck: queueing object for verification after write barrier\n");
1339 info->state = WBCHECK_STATE_DIRTY; // Mark as dirty
1340 wbcheck_object_list_append(objspace->objects_to_verify, a);
1341 }
1342 } else {
1343 WBCHECK_DEBUG("wbcheck: write barrier skipped (snapshot not initialized) from %p to %p\n", (void *)a, (void *)b);
1344 }
1345
1346 RB_GC_VM_UNLOCK_NO_BARRIER(lev);
1347}
1348
1349void
1350rb_gc_impl_writebarrier_unprotect(void *objspace_ptr, VALUE obj)
1351{
1352 WBCHECK_DEBUG("wbcheck: writebarrier_unprotect called on object %p\n", (void *)obj);
1353
1354 unsigned int lev = RB_GC_VM_LOCK_NO_BARRIER();
1355
1356 rb_wbcheck_object_info_t *info = wbcheck_get_object_info(obj);
1357 info->wb_protected = false;
1358
1359 RB_GC_VM_UNLOCK_NO_BARRIER(lev);
1360}
1361
1362void
1363rb_gc_impl_writebarrier_remember(void *objspace_ptr, VALUE obj)
1364{
1365 WBCHECK_DEBUG("wbcheck: writebarrier_remember called on object %p\n", (void *)obj);
1366
1367 unsigned int lev = RB_GC_VM_LOCK_NO_BARRIER();
1368
1370 rb_wbcheck_object_info_t *info = wbcheck_get_object_info(obj);
1371
1372 // Clear existing references since they may be stale
1373 if (info->state != WBCHECK_STATE_CLEAR) {
1374 RUBY_ASSERT(info->gc_mark_snapshot);
1375 wbcheck_object_list_free(info->gc_mark_snapshot);
1376 info->gc_mark_snapshot = NULL;
1377
1378 wbcheck_object_list_free(info->mark_maybe_snapshot);
1379 info->mark_maybe_snapshot = NULL;
1380
1381 // Only re-add to objects_to_capture if it had previous snapshot
1382 // (new objects don't need to be re-added since they'll be captured at allocation)
1383 wbcheck_object_list_append(objspace->objects_to_capture, obj);
1384
1385 // Also clear write barrier children
1386 if (info->writebarrier_children) {
1387 wbcheck_object_list_free(info->writebarrier_children);
1388 info->writebarrier_children = NULL;
1389 }
1390
1391 // Reset to clear state
1392 info->state = WBCHECK_STATE_CLEAR;
1393 }
1394 RUBY_ASSERT(!info->gc_mark_snapshot);
1395 RUBY_ASSERT(!info->mark_maybe_snapshot);
1396 RUBY_ASSERT(!info->writebarrier_children);
1397
1398 RB_GC_VM_UNLOCK_NO_BARRIER(lev);
1399}
1400
1401// Heap walking
1403 int (*callback)(VALUE obj, rb_wbcheck_object_info_t *info, void *data);
1404 void *data;
1405};
1406
1407static int
1408wbcheck_foreach_object_i(st_data_t key, st_data_t val, st_data_t arg)
1409{
1410 VALUE obj = (VALUE)key;
1412 struct wbcheck_foreach_data *foreach_data = (struct wbcheck_foreach_data *)arg;
1413
1414 return foreach_data->callback(obj, info, foreach_data->data);
1415}
1416
1417static void
1418wbcheck_foreach_object(rb_wbcheck_objspace_t *objspace, int (*callback)(VALUE obj, rb_wbcheck_object_info_t *info, void *data), void *data)
1419{
1420 struct wbcheck_foreach_data foreach_data = {
1421 .callback = callback,
1422 .data = data
1423 };
1424
1425 st_foreach(objspace->object_table, wbcheck_foreach_object_i, (st_data_t)&foreach_data);
1426}
1427
1428// Helper to collect all objects into a snapshot list
1429static int
1430wbcheck_snapshot_collector(st_data_t key, st_data_t val, st_data_t arg)
1431{
1432 VALUE obj = (VALUE)key;
1434 wbcheck_object_list_append(snapshot, obj);
1435 return ST_CONTINUE;
1436}
1437
1438// Take a snapshot of all objects for safe iteration
1439static wbcheck_object_list_t *
1440wbcheck_take_object_snapshot(rb_wbcheck_objspace_t *objspace)
1441{
1442 size_t object_count = st_table_size(objspace->object_table);
1443 wbcheck_object_list_t *snapshot = wbcheck_object_list_init_with_capacity(object_count);
1444 st_foreach(objspace->object_table, wbcheck_snapshot_collector, (st_data_t)snapshot);
1445 return snapshot;
1446}
1447
1448
1449void
1450rb_gc_impl_each_objects(void *objspace_ptr, int (*callback)(void *, void *, size_t, void *), void *data)
1451{
1453 GC_ASSERT(objspace);
1454
1455 wbcheck_object_list_t *snapshot = wbcheck_take_object_snapshot(objspace);
1456
1457 for (size_t i = 0; i < snapshot->count; i++) {
1458 VALUE obj = snapshot->items[i];
1459 st_data_t value;
1460 if (st_lookup(objspace->object_table, (st_data_t)obj, &value)) {
1462 int result = callback(
1463 (void *)obj,
1464 (void *)((char *)obj + info->alloc_size),
1465 info->alloc_size,
1466 data
1467 );
1468 if (result != 0) break;
1469 }
1470 }
1471
1472 wbcheck_object_list_free(snapshot);
1473}
1474
1475void
1476rb_gc_impl_each_object(void *objspace_ptr, void (*func)(VALUE obj, void *data), void *data)
1477{
1479 GC_ASSERT(objspace);
1480
1481 wbcheck_object_list_t *snapshot = wbcheck_take_object_snapshot(objspace);
1482
1483 for (size_t i = 0; i < snapshot->count; i++) {
1484 VALUE obj = snapshot->items[i];
1485 st_data_t value;
1486 if (st_lookup(objspace->object_table, (st_data_t)obj, &value)) {
1487 func(obj, data);
1488 }
1489 }
1490
1491 wbcheck_object_list_free(snapshot);
1492}
1493
1494static void
1495finalizer_jobs_push(rb_wbcheck_objspace_t *objspace, struct wbcheck_final_job *job)
1496{
1497 rb_native_mutex_lock(&objspace->finalizer_lock);
1498 job->next = objspace->finalizer_jobs;
1499 objspace->finalizer_jobs = job;
1500 rb_native_mutex_unlock(&objspace->finalizer_lock);
1501}
1502
1503static struct wbcheck_final_job *
1504finalizer_jobs_pop(rb_wbcheck_objspace_t *objspace)
1505{
1506 rb_native_mutex_lock(&objspace->finalizer_lock);
1507 struct wbcheck_final_job *job = objspace->finalizer_jobs;
1508 if (job) {
1509 objspace->finalizer_jobs = job->next;
1510 }
1511 rb_native_mutex_unlock(&objspace->finalizer_lock);
1512 return job;
1513}
1514
1515// Finalizers
1516void
1517rb_gc_impl_make_zombie(void *objspace_ptr, VALUE obj, void (*dfree)(void *), void *data)
1518{
1519 if (dfree == NULL) return;
1520
1522
1523 struct wbcheck_final_job *job = malloc(sizeof(struct wbcheck_final_job));
1524 job->kind = WBCHECK_FINAL_JOB_DFREE;
1525 job->as.dfree.func = dfree;
1526 job->as.dfree.data = data;
1527
1528 finalizer_jobs_push(objspace, job);
1529
1530 if (!ruby_free_at_exit_p()) {
1531 rb_postponed_job_trigger(objspace->finalizer_postponed_job);
1532 }
1533
1534 WBCHECK_DEBUG("wbcheck: made zombie for object %p with dfree function\n", (void *)obj);
1535}
1536
1537VALUE
1538rb_gc_impl_define_finalizer(void *objspace_ptr, VALUE obj, VALUE block)
1539{
1540 unsigned int lev = RB_GC_VM_LOCK();
1541
1542 (void)objspace_ptr;
1543 rb_wbcheck_object_info_t *info = wbcheck_get_object_info(obj);
1544
1545 GC_ASSERT(!OBJ_FROZEN(obj));
1546
1547 RBASIC(obj)->flags |= FL_FINALIZE;
1548
1549 VALUE table = info->finalizers;
1550 VALUE result = block;
1551
1552 if (!table) {
1553 /* First finalizer for this object - store object ID as first element */
1554 table = rb_ary_new3(2, rb_obj_id(obj), block);
1555 rb_obj_hide(table);
1556 info->finalizers = table;
1557 } else {
1558 /* Check for duplicate finalizers (skip index 0 which is object ID) */
1559 long len = RARRAY_LEN(table);
1560 long i;
1561
1562 for (i = 1; i < len; i++) {
1563 VALUE recv = RARRAY_AREF(table, i);
1564 if (rb_equal(recv, block)) {
1565 result = recv; /* Duplicate found, return existing */
1566 goto unlock_and_return;
1567 }
1568 }
1569
1570 rb_ary_push(table, block);
1571 }
1572
1573unlock_and_return:
1574 RB_GC_VM_UNLOCK(lev);
1575 return result;
1576}
1577
1578void
1579rb_gc_impl_undefine_finalizer(void *objspace_ptr, VALUE obj)
1580{
1581 unsigned int lev = RB_GC_VM_LOCK();
1582
1583 (void)objspace_ptr;
1584 rb_wbcheck_object_info_t *info = wbcheck_get_object_info(obj);
1585
1586 GC_ASSERT(!OBJ_FROZEN(obj));
1587
1588 info->finalizers = 0;
1589 FL_UNSET(obj, FL_FINALIZE);
1590
1591 RB_GC_VM_UNLOCK(lev);
1592}
1593
1594void
1595rb_gc_impl_copy_finalizer(void *objspace_ptr, VALUE dest, VALUE obj)
1596{
1597 (void)objspace_ptr;
1598
1599 if (!FL_TEST(obj, FL_FINALIZE)) return;
1600
1601 unsigned int lev = RB_GC_VM_LOCK();
1602
1603 rb_wbcheck_object_info_t *src_info = wbcheck_get_object_info(obj);
1604 rb_wbcheck_object_info_t *dest_info = wbcheck_get_object_info(dest);
1605
1606 if (src_info->finalizers) {
1607 VALUE table = rb_ary_dup(src_info->finalizers);
1608 RARRAY_ASET(table, 0, rb_obj_id(dest));
1609 rb_obj_hide(table);
1610 dest_info->finalizers = table;
1611 FL_SET(dest, FL_FINALIZE);
1612 }
1613
1614 RB_GC_VM_UNLOCK(lev);
1615}
1616
1617static VALUE
1618wbcheck_get_final(long i, void *data)
1619{
1620 VALUE table = (VALUE)data;
1621
1622 return RARRAY_AREF(table, i + 1);
1623}
1624
1625static void
1626make_final_job(rb_wbcheck_objspace_t *objspace, VALUE obj, VALUE finalizer_array)
1627{
1629 RUBY_ASSERT(RB_BUILTIN_TYPE(finalizer_array) == T_ARRAY);
1630
1632
1633 struct wbcheck_final_job *job = malloc(sizeof(struct wbcheck_final_job));
1634 job->kind = WBCHECK_FINAL_JOB_FINALIZE;
1635 job->as.finalize.finalizer_array = finalizer_array;
1636
1637 finalizer_jobs_push(objspace, job);
1638}
1639
1640static void
1641gc_run_finalizers(void *data)
1642{
1644
1645 rb_gc_set_pending_interrupt();
1646
1647 struct wbcheck_final_job *job;
1648 while ((job = finalizer_jobs_pop(objspace)) != NULL) {
1649 switch (job->kind) {
1650 case WBCHECK_FINAL_JOB_DFREE:
1651 job->as.dfree.func(job->as.dfree.data);
1652 break;
1653 case WBCHECK_FINAL_JOB_FINALIZE: {
1654 VALUE finalizer_array = job->as.finalize.finalizer_array;
1655
1656 rb_gc_run_obj_finalizer(
1657 RARRAY_AREF(finalizer_array, 0),
1658 RARRAY_LEN(finalizer_array) - 1,
1659 wbcheck_get_final,
1660 (void *)finalizer_array
1661 );
1662
1663 RB_GC_GUARD(finalizer_array);
1664 break;
1665 }
1666 }
1667
1668 free(job);
1669 }
1670
1671 rb_gc_unset_pending_interrupt();
1672}
1673
1674static void
1675wbcheck_run_finalizers_for_object(VALUE obj, rb_wbcheck_object_info_t *info)
1676{
1677 if (info->finalizers) {
1678 VALUE table = info->finalizers;
1679 long count = RARRAY_LEN(table) - 1;
1680 rb_gc_run_obj_finalizer(RARRAY_AREF(table, 0), count, wbcheck_get_final, (void *)table);
1681 FL_UNSET(obj, FL_FINALIZE);
1682 }
1683 info->finalizers = 0;
1684}
1685
1686static int
1687wbcheck_shutdown_call_finalizer_callback(VALUE obj, rb_wbcheck_object_info_t *info, void *data)
1688{
1689 wbcheck_run_finalizers_for_object(obj, info);
1690 return ST_CONTINUE; /* Keep iterating through all objects */
1691}
1692
1693static int
1694wbcheck_verify_all_references_callback(VALUE obj, rb_wbcheck_object_info_t *info, void *data)
1695{
1696 void *objspace_ptr = data;
1697 wbcheck_verify_object_references(objspace_ptr, obj);
1698 return ST_CONTINUE;
1699}
1700
1701static int
1702wbcheck_update_all_snapshots_callback(VALUE obj, rb_wbcheck_object_info_t *info, void *data)
1703{
1704 void *objspace_ptr = data;
1705
1706 // For wb_protected objects, do full verification if they have a snapshot
1707 if (info->wb_protected && info->state != WBCHECK_STATE_CLEAR) {
1708 wbcheck_verify_object_references(objspace_ptr, obj);
1709 } else {
1710 // For CLEAR objects (wb_protected or not) and non-wb_protected objects, just take a new snapshot
1711 wbcheck_object_list_t *current_refs = wbcheck_collect_references_from_object(obj, info);
1712 wbcheck_object_list_free(info->gc_mark_snapshot);
1713 info->gc_mark_snapshot = current_refs;
1714 info->state = WBCHECK_STATE_MARKED;
1715 }
1716
1717 return ST_CONTINUE;
1718}
1719
1720static int
1721wbcheck_shutdown_finalizer_callback(VALUE obj, rb_wbcheck_object_info_t *info, void *data)
1722{
1723 void *objspace_ptr = data;
1724
1725 if (rb_gc_shutdown_call_finalizer_p(obj)) {
1726 WBCHECK_DEBUG("wbcheck: finalizing object during shutdown: %p\n", (void *)obj);
1727 rb_gc_obj_free_vm_weak_references(obj);
1728 if (rb_gc_obj_free(objspace_ptr, obj)) {
1729 RBASIC(obj)->flags = 0;
1730 }
1731 }
1732
1733 return ST_CONTINUE;
1734}
1735
1736
1737void
1738rb_gc_impl_shutdown_call_finalizer(void *objspace_ptr)
1739{
1740 rb_wbcheck_objspace_t *objspace = objspace_ptr;
1741
1742 // Call all finalizers for all objects using our shared iteration helper
1743 wbcheck_foreach_object(objspace, wbcheck_shutdown_call_finalizer_callback, NULL);
1744
1745 // After all finalizers have been called, verify all object references
1746 unsigned int verify_lev = RB_GC_VM_LOCK();
1747 WBCHECK_DEBUG("wbcheck: verifying references for all objects after finalizers\n");
1748 wbcheck_foreach_object(objspace, wbcheck_verify_all_references_callback, objspace_ptr);
1749 WBCHECK_DEBUG("wbcheck: finished verifying all object references\n");
1750 RB_GC_VM_UNLOCK(verify_lev);
1751
1752 wbcheck_abort_if_errors(objspace);
1753 WBCHECK_DEBUG("wbcheck: no write barrier violations detected\n");
1754
1755 // Call rb_gc_obj_free on objects that need shutdown finalization (File, Data with dfree, etc.)
1756 unsigned int lev = RB_GC_VM_LOCK();
1757 WBCHECK_DEBUG("wbcheck: calling rb_gc_obj_free on objects that need shutdown finalization\n");
1758 wbcheck_foreach_object(objspace, wbcheck_shutdown_finalizer_callback, objspace_ptr);
1759 WBCHECK_DEBUG("wbcheck: finished calling rb_gc_obj_free\n");
1760
1761 // Run any pending finalizer jobs (dfree functions)
1762 WBCHECK_DEBUG("wbcheck: running pending finalizer jobs\n");
1763 gc_run_finalizers(objspace);
1764 WBCHECK_DEBUG("wbcheck: finished running finalizer jobs\n");
1765 RB_GC_VM_UNLOCK(lev);
1766}
1767
1768// Forking
1769void
1770rb_gc_impl_before_fork(void *objspace_ptr)
1771{
1772 // Verify all objects at the fork point so a pre-fork missed barrier is caught
1773 // once here, rather than in every forked child.
1774 unsigned int lev = RB_GC_VM_LOCK();
1775 rb_gc_vm_barrier();
1776 force_gc(objspace_ptr);
1777 RB_GC_VM_UNLOCK(lev);
1778}
1779
1780void
1781rb_gc_impl_after_fork(void *objspace_ptr, rb_pid_t pid)
1782{
1783 // Stub implementation
1784}
1785
1786// Statistics
1787void
1788rb_gc_impl_set_measure_total_time(void *objspace_ptr, VALUE flag)
1789{
1791 objspace->measure_total_time = RTEST(flag);
1792}
1793
1794bool
1795rb_gc_impl_get_measure_total_time(void *objspace_ptr)
1796{
1798 return objspace->measure_total_time;
1799}
1800
1801unsigned long long
1802rb_gc_impl_get_total_time(void *objspace_ptr)
1803{
1805 return objspace->measure_total_time ? objspace->simulated_gc_count : 0;
1806}
1807
1808size_t
1809rb_gc_impl_gc_count(void *objspace_ptr)
1810{
1812 if (objspace) {
1813 return objspace->simulated_gc_count;
1814 }
1815 return 0;
1816}
1817
1818VALUE
1819rb_gc_impl_latest_gc_info(void *objspace_ptr, VALUE key)
1820{
1821 // Stub implementation
1822 return Qnil;
1823}
1824
1825VALUE
1826rb_gc_impl_stat(void *objspace_ptr, VALUE hash_or_sym)
1827{
1829 GC_ASSERT(objspace);
1830
1831 VALUE hash = Qnil, key = Qnil;
1832
1833 if (RB_TYPE_P(hash_or_sym, T_HASH)) {
1834 hash = hash_or_sym;
1835 }
1836 else if (SYMBOL_P(hash_or_sym)) {
1837 key = hash_or_sym;
1838 }
1839 else {
1840 rb_bug("non-hash or symbol given");
1841 }
1842
1843#define SET(name, attr) \
1844 if (key == ID2SYM(rb_intern(#name))) \
1845 return SIZET2NUM(attr); \
1846 else if (hash != Qnil) \
1847 rb_hash_aset(hash, ID2SYM(rb_intern(#name)), SIZET2NUM(attr));
1848
1849 /* Pretend each GC takes 1ms; :time is reported in milliseconds. */
1850 SET(count, objspace->simulated_gc_count);
1851 SET(time, objspace->measure_total_time ? objspace->simulated_gc_count : 0);
1852 SET(tracked_objects, st_table_size(objspace->object_table));
1853#undef SET
1854
1855 if (!NIL_P(key)) {
1856 rb_raise(rb_eArgError, "unknown key: %"PRIsVALUE, rb_sym2str(key));
1857 }
1858
1859 rb_hash_aset(hash, ID2SYM(rb_intern("gc_implementation")), rb_str_new_cstr("wbcheck"));
1860
1861 return hash;
1862}
1863
1864VALUE
1865rb_gc_impl_stat_heap(void *objspace_ptr, VALUE heap_name, VALUE hash_or_sym)
1866{
1867 if (FIXNUM_P(heap_name) && SYMBOL_P(hash_or_sym)) {
1868 int heap_idx = FIX2INT(heap_name);
1869 if (heap_idx < 0 || heap_idx >= HEAP_COUNT) {
1870 rb_raise(rb_eArgError, "size pool index out of range");
1871 }
1872
1873 if (hash_or_sym == ID2SYM(rb_intern("slot_size"))) {
1874 return SIZET2NUM(heap_sizes[heap_idx]);
1875 }
1876
1877 return Qundef;
1878 }
1879
1880 if (RB_TYPE_P(hash_or_sym, T_HASH)) {
1881 return hash_or_sym;
1882 }
1883
1884 return Qundef;
1885}
1886
1887const char *
1888rb_gc_impl_active_gc_name(void)
1889{
1890 // Stub implementation
1891 return "wbcheck";
1892}
1893
1894// Miscellaneous
1895#define WBCHECK_OBJECT_METADATA_ENTRY_COUNT 2
1896static struct rb_gc_object_metadata_entry object_metadata_entries[WBCHECK_OBJECT_METADATA_ENTRY_COUNT + 1];
1897
1899rb_gc_impl_object_metadata(void *objspace_ptr, VALUE obj)
1900{
1901 static ID ID_object_id, ID_shareable;
1902
1903 if (!ID_object_id) {
1904 ID_object_id = rb_intern("object_id");
1905 ID_shareable = rb_intern("shareable");
1906 }
1907
1908 size_t n = 0;
1909
1910#define SET_ENTRY(na, v) do { \
1911 GC_ASSERT(n < WBCHECK_OBJECT_METADATA_ENTRY_COUNT); \
1912 object_metadata_entries[n].name = ID_##na; \
1913 object_metadata_entries[n].val = v; \
1914 n++; \
1915} while (0)
1916
1917 if (rb_obj_id_p(obj)) SET_ENTRY(object_id, rb_obj_id(obj));
1918 if (FL_TEST(obj, FL_SHAREABLE)) SET_ENTRY(shareable, Qtrue);
1919#undef SET_ENTRY
1920
1921 object_metadata_entries[n].name = 0;
1922 object_metadata_entries[n].val = 0;
1923
1924 return object_metadata_entries;
1925}
1926
1927bool
1928rb_gc_impl_live_object_p(void *objspace_ptr, const void *ptr)
1929{
1930 GC_ASSERT(wbcheck_global_objspace);
1931
1932 unsigned int lev = RB_GC_VM_LOCK();
1933
1934 // Check if this pointer exists in our object tracking table
1935 st_data_t value;
1936 bool result = st_lookup(wbcheck_global_objspace->object_table, (st_data_t)ptr, &value);
1937
1938 RB_GC_VM_UNLOCK(lev);
1939 return result;
1940}
1941
1942bool
1943rb_gc_impl_garbage_object_p(void *objspace_ptr, VALUE obj)
1944{
1945 unsigned int lev = RB_GC_VM_LOCK();
1946
1947 // Check if this pointer exists in our object tracking table
1948 st_data_t value;
1949 bool result = st_lookup(wbcheck_global_objspace->object_table, (st_data_t)obj, &value);
1950
1951 RB_GC_VM_UNLOCK(lev);
1952 return !result;
1953}
1954
1955void
1956rb_gc_impl_set_event_hook(void *objspace_ptr, const rb_event_flag_t event)
1957{
1958 // Stub implementation
1959}
1960
1961void
1962rb_gc_impl_copy_attributes(void *objspace_ptr, VALUE dest, VALUE obj)
1963{
1964 rb_wbcheck_object_info_t *src_info = wbcheck_get_object_info(obj);
1965
1966 if (!src_info->wb_protected) {
1967 rb_gc_impl_writebarrier_unprotect(objspace_ptr, dest);
1968 }
1969 rb_gc_impl_copy_finalizer(objspace_ptr, dest, obj);
1970}
#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:1887
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:1853
#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_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:443
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:1515
VALUE rb_f_notimplement(int argc, const VALUE *argv, VALUE obj, VALUE marker)
Raises rb_eNotImpError.
Definition vm_method.c:909
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:2319
#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
static void RARRAY_ASET(VALUE ary, long i, VALUE v)
Assigns an object in an array.
Definition rarray.h:385
#define RARRAY_AREF(a, i)
Definition rarray.h:402
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:6157
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.
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