Ruby 4.1.0dev (2026-08-06 revision 10fa3a51bea77dce31b0df398deaac3e437470c3)
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
1295VALUE
1296rb_gc_impl_location(void *objspace_ptr, VALUE value)
1297{
1298 // Stub implementation
1299 return Qnil;
1300}
1301
1302// Write barriers
1303void
1304rb_gc_impl_writebarrier(void *objspace_ptr, VALUE a, VALUE b)
1305{
1306 if (RB_SPECIAL_CONST_P(b)) return;
1307
1308 unsigned int lev = RB_GC_VM_LOCK_NO_BARRIER();
1309
1310 rb_wbcheck_objspace_t *objspace = objspace_ptr;
1311
1312 // Get the object info for the parent object (a)
1313 rb_wbcheck_object_info_t *info = wbcheck_get_object_info(a);
1314
1315 // Only record the write barrier if we have a valid snapshot
1316 if (info->state != WBCHECK_STATE_CLEAR) {
1317 RUBY_ASSERT(info->gc_mark_snapshot);
1318
1319 // Initialize writebarrier_children list if it doesn't exist
1320 if (!info->writebarrier_children) {
1321 info->writebarrier_children = wbcheck_object_list_init();
1322 }
1323
1324 // Add the new reference to the write barrier children list
1325 wbcheck_object_list_append(info->writebarrier_children, b);
1326
1327 WBCHECK_DEBUG("wbcheck: write barrier recorded reference from %p to %p\n", (void *)a, (void *)b);
1328
1329 // If verification after write barrier is enabled, queue the object for verification
1330 if (wbcheck_verify_after_wb_enabled && info->state != WBCHECK_STATE_DIRTY) {
1331 WBCHECK_DEBUG("wbcheck: queueing object for verification after write barrier\n");
1332 info->state = WBCHECK_STATE_DIRTY; // Mark as dirty
1333 wbcheck_object_list_append(objspace->objects_to_verify, a);
1334 }
1335 } else {
1336 WBCHECK_DEBUG("wbcheck: write barrier skipped (snapshot not initialized) from %p to %p\n", (void *)a, (void *)b);
1337 }
1338
1339 RB_GC_VM_UNLOCK_NO_BARRIER(lev);
1340}
1341
1342void
1343rb_gc_impl_writebarrier_unprotect(void *objspace_ptr, VALUE obj)
1344{
1345 WBCHECK_DEBUG("wbcheck: writebarrier_unprotect called on object %p\n", (void *)obj);
1346
1347 unsigned int lev = RB_GC_VM_LOCK_NO_BARRIER();
1348
1349 rb_wbcheck_object_info_t *info = wbcheck_get_object_info(obj);
1350 info->wb_protected = false;
1351
1352 RB_GC_VM_UNLOCK_NO_BARRIER(lev);
1353}
1354
1355void
1356rb_gc_impl_writebarrier_remember(void *objspace_ptr, VALUE obj)
1357{
1358 WBCHECK_DEBUG("wbcheck: writebarrier_remember called on object %p\n", (void *)obj);
1359
1360 unsigned int lev = RB_GC_VM_LOCK_NO_BARRIER();
1361
1363 rb_wbcheck_object_info_t *info = wbcheck_get_object_info(obj);
1364
1365 // Clear existing references since they may be stale
1366 if (info->state != WBCHECK_STATE_CLEAR) {
1367 RUBY_ASSERT(info->gc_mark_snapshot);
1368 wbcheck_object_list_free(info->gc_mark_snapshot);
1369 info->gc_mark_snapshot = NULL;
1370
1371 wbcheck_object_list_free(info->mark_maybe_snapshot);
1372 info->mark_maybe_snapshot = NULL;
1373
1374 // Only re-add to objects_to_capture if it had previous snapshot
1375 // (new objects don't need to be re-added since they'll be captured at allocation)
1376 wbcheck_object_list_append(objspace->objects_to_capture, obj);
1377
1378 // Also clear write barrier children
1379 if (info->writebarrier_children) {
1380 wbcheck_object_list_free(info->writebarrier_children);
1381 info->writebarrier_children = NULL;
1382 }
1383
1384 // Reset to clear state
1385 info->state = WBCHECK_STATE_CLEAR;
1386 }
1387 RUBY_ASSERT(!info->gc_mark_snapshot);
1388 RUBY_ASSERT(!info->mark_maybe_snapshot);
1389 RUBY_ASSERT(!info->writebarrier_children);
1390
1391 RB_GC_VM_UNLOCK_NO_BARRIER(lev);
1392}
1393
1394// Heap walking
1396 int (*callback)(VALUE obj, rb_wbcheck_object_info_t *info, void *data);
1397 void *data;
1398};
1399
1400static int
1401wbcheck_foreach_object_i(st_data_t key, st_data_t val, st_data_t arg)
1402{
1403 VALUE obj = (VALUE)key;
1405 struct wbcheck_foreach_data *foreach_data = (struct wbcheck_foreach_data *)arg;
1406
1407 return foreach_data->callback(obj, info, foreach_data->data);
1408}
1409
1410static void
1411wbcheck_foreach_object(rb_wbcheck_objspace_t *objspace, int (*callback)(VALUE obj, rb_wbcheck_object_info_t *info, void *data), void *data)
1412{
1413 struct wbcheck_foreach_data foreach_data = {
1414 .callback = callback,
1415 .data = data
1416 };
1417
1418 st_foreach(objspace->object_table, wbcheck_foreach_object_i, (st_data_t)&foreach_data);
1419}
1420
1421// Helper to collect all objects into a snapshot list
1422static int
1423wbcheck_snapshot_collector(st_data_t key, st_data_t val, st_data_t arg)
1424{
1425 VALUE obj = (VALUE)key;
1427 wbcheck_object_list_append(snapshot, obj);
1428 return ST_CONTINUE;
1429}
1430
1431// Take a snapshot of all objects for safe iteration
1432static wbcheck_object_list_t *
1433wbcheck_take_object_snapshot(rb_wbcheck_objspace_t *objspace)
1434{
1435 size_t object_count = st_table_size(objspace->object_table);
1436 wbcheck_object_list_t *snapshot = wbcheck_object_list_init_with_capacity(object_count);
1437 st_foreach(objspace->object_table, wbcheck_snapshot_collector, (st_data_t)snapshot);
1438 return snapshot;
1439}
1440
1441
1442void
1443rb_gc_impl_each_objects(void *objspace_ptr, int (*callback)(void *, void *, size_t, void *), void *data)
1444{
1446 GC_ASSERT(objspace);
1447
1448 wbcheck_object_list_t *snapshot = wbcheck_take_object_snapshot(objspace);
1449
1450 for (size_t i = 0; i < snapshot->count; i++) {
1451 VALUE obj = snapshot->items[i];
1452 st_data_t value;
1453 if (st_lookup(objspace->object_table, (st_data_t)obj, &value)) {
1455 int result = callback(
1456 (void *)obj,
1457 (void *)((char *)obj + info->alloc_size),
1458 info->alloc_size,
1459 data
1460 );
1461 if (result != 0) break;
1462 }
1463 }
1464
1465 wbcheck_object_list_free(snapshot);
1466}
1467
1468void
1469rb_gc_impl_each_object(void *objspace_ptr, void (*func)(VALUE obj, void *data), void *data)
1470{
1472 GC_ASSERT(objspace);
1473
1474 wbcheck_object_list_t *snapshot = wbcheck_take_object_snapshot(objspace);
1475
1476 for (size_t i = 0; i < snapshot->count; i++) {
1477 VALUE obj = snapshot->items[i];
1478 st_data_t value;
1479 if (st_lookup(objspace->object_table, (st_data_t)obj, &value)) {
1480 func(obj, data);
1481 }
1482 }
1483
1484 wbcheck_object_list_free(snapshot);
1485}
1486
1487static void
1488finalizer_jobs_push(rb_wbcheck_objspace_t *objspace, struct wbcheck_final_job *job)
1489{
1490 rb_native_mutex_lock(&objspace->finalizer_lock);
1491 job->next = objspace->finalizer_jobs;
1492 objspace->finalizer_jobs = job;
1493 rb_native_mutex_unlock(&objspace->finalizer_lock);
1494}
1495
1496static struct wbcheck_final_job *
1497finalizer_jobs_pop(rb_wbcheck_objspace_t *objspace)
1498{
1499 rb_native_mutex_lock(&objspace->finalizer_lock);
1500 struct wbcheck_final_job *job = objspace->finalizer_jobs;
1501 if (job) {
1502 objspace->finalizer_jobs = job->next;
1503 }
1504 rb_native_mutex_unlock(&objspace->finalizer_lock);
1505 return job;
1506}
1507
1508// Finalizers
1509void
1510rb_gc_impl_make_zombie(void *objspace_ptr, VALUE obj, void (*dfree)(void *), void *data)
1511{
1512 if (dfree == NULL) return;
1513
1515
1516 struct wbcheck_final_job *job = malloc(sizeof(struct wbcheck_final_job));
1517 job->kind = WBCHECK_FINAL_JOB_DFREE;
1518 job->as.dfree.func = dfree;
1519 job->as.dfree.data = data;
1520
1521 finalizer_jobs_push(objspace, job);
1522
1523 if (!ruby_free_at_exit_p()) {
1524 rb_postponed_job_trigger(objspace->finalizer_postponed_job);
1525 }
1526
1527 WBCHECK_DEBUG("wbcheck: made zombie for object %p with dfree function\n", (void *)obj);
1528}
1529
1530VALUE
1531rb_gc_impl_define_finalizer(void *objspace_ptr, VALUE obj, VALUE block)
1532{
1533 unsigned int lev = RB_GC_VM_LOCK();
1534
1535 (void)objspace_ptr;
1536 rb_wbcheck_object_info_t *info = wbcheck_get_object_info(obj);
1537
1538 GC_ASSERT(!OBJ_FROZEN(obj));
1539
1540 RBASIC(obj)->flags |= FL_FINALIZE;
1541
1542 VALUE table = info->finalizers;
1543 VALUE result = block;
1544
1545 if (!table) {
1546 /* First finalizer for this object - store object ID as first element */
1547 table = rb_ary_new3(2, rb_obj_id(obj), block);
1548 rb_obj_hide(table);
1549 info->finalizers = table;
1550 } else {
1551 /* Check for duplicate finalizers (skip index 0 which is object ID) */
1552 long len = RARRAY_LEN(table);
1553 long i;
1554
1555 for (i = 1; i < len; i++) {
1556 VALUE recv = RARRAY_AREF(table, i);
1557 if (rb_equal(recv, block)) {
1558 result = recv; /* Duplicate found, return existing */
1559 goto unlock_and_return;
1560 }
1561 }
1562
1563 rb_ary_push(table, block);
1564 }
1565
1566unlock_and_return:
1567 RB_GC_VM_UNLOCK(lev);
1568 return result;
1569}
1570
1571void
1572rb_gc_impl_undefine_finalizer(void *objspace_ptr, VALUE obj)
1573{
1574 unsigned int lev = RB_GC_VM_LOCK();
1575
1576 (void)objspace_ptr;
1577 rb_wbcheck_object_info_t *info = wbcheck_get_object_info(obj);
1578
1579 GC_ASSERT(!OBJ_FROZEN(obj));
1580
1581 info->finalizers = 0;
1582 FL_UNSET(obj, FL_FINALIZE);
1583
1584 RB_GC_VM_UNLOCK(lev);
1585}
1586
1587void
1588rb_gc_impl_copy_finalizer(void *objspace_ptr, VALUE dest, VALUE obj)
1589{
1590 (void)objspace_ptr;
1591
1592 if (!FL_TEST(obj, FL_FINALIZE)) return;
1593
1594 unsigned int lev = RB_GC_VM_LOCK();
1595
1596 rb_wbcheck_object_info_t *src_info = wbcheck_get_object_info(obj);
1597 rb_wbcheck_object_info_t *dest_info = wbcheck_get_object_info(dest);
1598
1599 if (src_info->finalizers) {
1600 VALUE table = rb_ary_dup(src_info->finalizers);
1601 RARRAY_ASET(table, 0, rb_obj_id(dest));
1602 rb_obj_hide(table);
1603 dest_info->finalizers = table;
1604 FL_SET(dest, FL_FINALIZE);
1605 }
1606
1607 RB_GC_VM_UNLOCK(lev);
1608}
1609
1610static VALUE
1611wbcheck_get_final(long i, void *data)
1612{
1613 VALUE table = (VALUE)data;
1614
1615 return RARRAY_AREF(table, i + 1);
1616}
1617
1618static void
1619make_final_job(rb_wbcheck_objspace_t *objspace, VALUE obj, VALUE finalizer_array)
1620{
1622 RUBY_ASSERT(RB_BUILTIN_TYPE(finalizer_array) == T_ARRAY);
1623
1625
1626 struct wbcheck_final_job *job = malloc(sizeof(struct wbcheck_final_job));
1627 job->kind = WBCHECK_FINAL_JOB_FINALIZE;
1628 job->as.finalize.finalizer_array = finalizer_array;
1629
1630 finalizer_jobs_push(objspace, job);
1631}
1632
1633static void
1634gc_run_finalizers(void *data)
1635{
1637
1638 rb_gc_set_pending_interrupt();
1639
1640 struct wbcheck_final_job *job;
1641 while ((job = finalizer_jobs_pop(objspace)) != NULL) {
1642 switch (job->kind) {
1643 case WBCHECK_FINAL_JOB_DFREE:
1644 job->as.dfree.func(job->as.dfree.data);
1645 break;
1646 case WBCHECK_FINAL_JOB_FINALIZE: {
1647 VALUE finalizer_array = job->as.finalize.finalizer_array;
1648
1649 rb_gc_run_obj_finalizer(
1650 RARRAY_AREF(finalizer_array, 0),
1651 RARRAY_LEN(finalizer_array) - 1,
1652 wbcheck_get_final,
1653 (void *)finalizer_array
1654 );
1655
1656 RB_GC_GUARD(finalizer_array);
1657 break;
1658 }
1659 }
1660
1661 free(job);
1662 }
1663
1664 rb_gc_unset_pending_interrupt();
1665}
1666
1667static void
1668wbcheck_run_finalizers_for_object(VALUE obj, rb_wbcheck_object_info_t *info)
1669{
1670 if (info->finalizers) {
1671 VALUE table = info->finalizers;
1672 long count = RARRAY_LEN(table) - 1;
1673 rb_gc_run_obj_finalizer(RARRAY_AREF(table, 0), count, wbcheck_get_final, (void *)table);
1674 FL_UNSET(obj, FL_FINALIZE);
1675 }
1676 info->finalizers = 0;
1677}
1678
1679static int
1680wbcheck_shutdown_call_finalizer_callback(VALUE obj, rb_wbcheck_object_info_t *info, void *data)
1681{
1682 wbcheck_run_finalizers_for_object(obj, info);
1683 return ST_CONTINUE; /* Keep iterating through all objects */
1684}
1685
1686static int
1687wbcheck_verify_all_references_callback(VALUE obj, rb_wbcheck_object_info_t *info, void *data)
1688{
1689 void *objspace_ptr = data;
1690 wbcheck_verify_object_references(objspace_ptr, obj);
1691 return ST_CONTINUE;
1692}
1693
1694static int
1695wbcheck_update_all_snapshots_callback(VALUE obj, rb_wbcheck_object_info_t *info, void *data)
1696{
1697 void *objspace_ptr = data;
1698
1699 // For wb_protected objects, do full verification if they have a snapshot
1700 if (info->wb_protected && info->state != WBCHECK_STATE_CLEAR) {
1701 wbcheck_verify_object_references(objspace_ptr, obj);
1702 } else {
1703 // For CLEAR objects (wb_protected or not) and non-wb_protected objects, just take a new snapshot
1704 wbcheck_object_list_t *current_refs = wbcheck_collect_references_from_object(obj, info);
1705 wbcheck_object_list_free(info->gc_mark_snapshot);
1706 info->gc_mark_snapshot = current_refs;
1707 info->state = WBCHECK_STATE_MARKED;
1708 }
1709
1710 return ST_CONTINUE;
1711}
1712
1713static int
1714wbcheck_shutdown_finalizer_callback(VALUE obj, rb_wbcheck_object_info_t *info, void *data)
1715{
1716 void *objspace_ptr = data;
1717
1718 if (rb_gc_shutdown_call_finalizer_p(obj)) {
1719 WBCHECK_DEBUG("wbcheck: finalizing object during shutdown: %p\n", (void *)obj);
1720 rb_gc_obj_free_vm_weak_references(obj);
1721 if (rb_gc_obj_free(objspace_ptr, obj)) {
1722 RBASIC(obj)->flags = 0;
1723 }
1724 }
1725
1726 return ST_CONTINUE;
1727}
1728
1729
1730void
1731rb_gc_impl_shutdown_call_finalizer(void *objspace_ptr)
1732{
1733 rb_wbcheck_objspace_t *objspace = objspace_ptr;
1734
1735 // Call all finalizers for all objects using our shared iteration helper
1736 wbcheck_foreach_object(objspace, wbcheck_shutdown_call_finalizer_callback, NULL);
1737
1738 // After all finalizers have been called, verify all object references
1739 unsigned int verify_lev = RB_GC_VM_LOCK();
1740 WBCHECK_DEBUG("wbcheck: verifying references for all objects after finalizers\n");
1741 wbcheck_foreach_object(objspace, wbcheck_verify_all_references_callback, objspace_ptr);
1742 WBCHECK_DEBUG("wbcheck: finished verifying all object references\n");
1743 RB_GC_VM_UNLOCK(verify_lev);
1744
1745 wbcheck_abort_if_errors(objspace);
1746 WBCHECK_DEBUG("wbcheck: no write barrier violations detected\n");
1747
1748 // Call rb_gc_obj_free on objects that need shutdown finalization (File, Data with dfree, etc.)
1749 unsigned int lev = RB_GC_VM_LOCK();
1750 WBCHECK_DEBUG("wbcheck: calling rb_gc_obj_free on objects that need shutdown finalization\n");
1751 wbcheck_foreach_object(objspace, wbcheck_shutdown_finalizer_callback, objspace_ptr);
1752 WBCHECK_DEBUG("wbcheck: finished calling rb_gc_obj_free\n");
1753
1754 // Run any pending finalizer jobs (dfree functions)
1755 WBCHECK_DEBUG("wbcheck: running pending finalizer jobs\n");
1756 gc_run_finalizers(objspace);
1757 WBCHECK_DEBUG("wbcheck: finished running finalizer jobs\n");
1758 RB_GC_VM_UNLOCK(lev);
1759}
1760
1761// Forking
1762void
1763rb_gc_impl_before_fork(void *objspace_ptr)
1764{
1765 // Verify all objects at the fork point so a pre-fork missed barrier is caught
1766 // once here, rather than in every forked child.
1767 unsigned int lev = RB_GC_VM_LOCK();
1768 rb_gc_vm_barrier();
1769 force_gc(objspace_ptr);
1770 RB_GC_VM_UNLOCK(lev);
1771}
1772
1773void
1774rb_gc_impl_after_fork(void *objspace_ptr, rb_pid_t pid)
1775{
1776 // Stub implementation
1777}
1778
1779// Statistics
1780void
1781rb_gc_impl_set_measure_total_time(void *objspace_ptr, VALUE flag)
1782{
1784 objspace->measure_total_time = RTEST(flag);
1785}
1786
1787bool
1788rb_gc_impl_get_measure_total_time(void *objspace_ptr)
1789{
1791 return objspace->measure_total_time;
1792}
1793
1794unsigned long long
1795rb_gc_impl_get_total_time(void *objspace_ptr)
1796{
1798 return objspace->measure_total_time ? objspace->simulated_gc_count : 0;
1799}
1800
1801size_t
1802rb_gc_impl_gc_count(void *objspace_ptr)
1803{
1805 if (objspace) {
1806 return objspace->simulated_gc_count;
1807 }
1808 return 0;
1809}
1810
1811VALUE
1812rb_gc_impl_latest_gc_info(void *objspace_ptr, VALUE key)
1813{
1814 // Stub implementation
1815 return Qnil;
1816}
1817
1818VALUE
1819rb_gc_impl_stat(void *objspace_ptr, VALUE hash_or_sym)
1820{
1822 GC_ASSERT(objspace);
1823
1824 VALUE hash = Qnil, key = Qnil;
1825
1826 if (RB_TYPE_P(hash_or_sym, T_HASH)) {
1827 hash = hash_or_sym;
1828 }
1829 else if (SYMBOL_P(hash_or_sym)) {
1830 key = hash_or_sym;
1831 }
1832 else {
1833 rb_bug("non-hash or symbol given");
1834 }
1835
1836#define SET(name, attr) \
1837 if (key == ID2SYM(rb_intern(#name))) \
1838 return SIZET2NUM(attr); \
1839 else if (hash != Qnil) \
1840 rb_hash_aset(hash, ID2SYM(rb_intern(#name)), SIZET2NUM(attr));
1841
1842 /* Pretend each GC takes 1ms; :time is reported in milliseconds. */
1843 SET(count, objspace->simulated_gc_count);
1844 SET(time, objspace->measure_total_time ? objspace->simulated_gc_count : 0);
1845 SET(tracked_objects, st_table_size(objspace->object_table));
1846#undef SET
1847
1848 if (!NIL_P(key)) {
1849 rb_raise(rb_eArgError, "unknown key: %"PRIsVALUE, rb_sym2str(key));
1850 }
1851
1852 rb_hash_aset(hash, ID2SYM(rb_intern("gc_implementation")), rb_str_new_cstr("wbcheck"));
1853
1854 return hash;
1855}
1856
1857VALUE
1858rb_gc_impl_stat_heap(void *objspace_ptr, VALUE heap_name, VALUE hash_or_sym)
1859{
1860 if (FIXNUM_P(heap_name) && SYMBOL_P(hash_or_sym)) {
1861 int heap_idx = FIX2INT(heap_name);
1862 if (heap_idx < 0 || heap_idx >= HEAP_COUNT) {
1863 rb_raise(rb_eArgError, "size pool index out of range");
1864 }
1865
1866 if (hash_or_sym == ID2SYM(rb_intern("slot_size"))) {
1867 return SIZET2NUM(heap_sizes[heap_idx]);
1868 }
1869
1870 return Qundef;
1871 }
1872
1873 if (RB_TYPE_P(hash_or_sym, T_HASH)) {
1874 return hash_or_sym;
1875 }
1876
1877 return Qundef;
1878}
1879
1880const char *
1881rb_gc_impl_active_gc_name(void)
1882{
1883 // Stub implementation
1884 return "wbcheck";
1885}
1886
1887// Miscellaneous
1888#define WBCHECK_OBJECT_METADATA_ENTRY_COUNT 2
1889static struct rb_gc_object_metadata_entry object_metadata_entries[WBCHECK_OBJECT_METADATA_ENTRY_COUNT + 1];
1890
1892rb_gc_impl_object_metadata(void *objspace_ptr, VALUE obj)
1893{
1894 static ID ID_object_id, ID_shareable;
1895
1896 if (!ID_object_id) {
1897 ID_object_id = rb_intern("object_id");
1898 ID_shareable = rb_intern("shareable");
1899 }
1900
1901 size_t n = 0;
1902
1903#define SET_ENTRY(na, v) do { \
1904 GC_ASSERT(n < WBCHECK_OBJECT_METADATA_ENTRY_COUNT); \
1905 object_metadata_entries[n].name = ID_##na; \
1906 object_metadata_entries[n].val = v; \
1907 n++; \
1908} while (0)
1909
1910 if (rb_obj_id_p(obj)) SET_ENTRY(object_id, rb_obj_id(obj));
1911 if (FL_TEST(obj, FL_SHAREABLE)) SET_ENTRY(shareable, Qtrue);
1912#undef SET_ENTRY
1913
1914 object_metadata_entries[n].name = 0;
1915 object_metadata_entries[n].val = 0;
1916
1917 return object_metadata_entries;
1918}
1919
1920bool
1921rb_gc_impl_live_object_p(void *objspace_ptr, const void *ptr)
1922{
1923 GC_ASSERT(wbcheck_global_objspace);
1924
1925 unsigned int lev = RB_GC_VM_LOCK();
1926
1927 // Check if this pointer exists in our object tracking table
1928 st_data_t value;
1929 bool result = st_lookup(wbcheck_global_objspace->object_table, (st_data_t)ptr, &value);
1930
1931 RB_GC_VM_UNLOCK(lev);
1932 return result;
1933}
1934
1935bool
1936rb_gc_impl_garbage_object_p(void *objspace_ptr, VALUE obj)
1937{
1938 unsigned int lev = RB_GC_VM_LOCK();
1939
1940 // Check if this pointer exists in our object tracking table
1941 st_data_t value;
1942 bool result = st_lookup(wbcheck_global_objspace->object_table, (st_data_t)obj, &value);
1943
1944 RB_GC_VM_UNLOCK(lev);
1945 return !result;
1946}
1947
1948void
1949rb_gc_impl_set_event_hook(void *objspace_ptr, const rb_event_flag_t event)
1950{
1951 // Stub implementation
1952}
1953
1954void
1955rb_gc_impl_copy_attributes(void *objspace_ptr, VALUE dest, VALUE obj)
1956{
1957 rb_wbcheck_object_info_t *src_info = wbcheck_get_object_info(obj);
1958
1959 if (!src_info->wb_protected) {
1960 rb_gc_impl_writebarrier_unprotect(objspace_ptr, dest);
1961 }
1962 rb_gc_impl_copy_finalizer(objspace_ptr, dest, obj);
1963}
#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:1922
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:1888
#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:430
static VALUE RB_FL_TEST_RAW(VALUE obj, VALUE flags)
This is an implementation detail of RB_FL_TEST().
Definition fl_type.h:404
static void RB_FL_UNSET(VALUE obj, VALUE flags)
Clears the given flag(s).
Definition fl_type.h:616
@ 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:92
VALUE rb_mGC
GC module.
Definition gc.c:420
VALUE rb_equal(VALUE lhs, VALUE rhs)
This function is an optimised version of calling #==.
Definition object.c:138
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:905
VALUE rb_sym2str(VALUE symbol)
Obtain a frozen string representation of a symbol (not including the leading colon).
Definition symbol.c:1144
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:2122
#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:5901
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