Ruby 4.1.0dev (2026-09-05 revision ce1eaf724f17561a1978f61d780b8977230930c8)
mmtk.c
1#include <pthread.h>
2#include <stdbool.h>
3#include <string.h>
4
5#include "ruby/assert.h"
6#include "ruby/atomic.h"
7#include "ruby/debug.h"
8
9#include "gc/gc.h"
10#include "gc/gc_impl.h"
11#include "gc/mmtk/mmtk.h"
12
13#if USE_ZJIT
14# include "gc/mmtk/zjit_fastpath.h"
15#endif
16
17#include "ccan/list/list.h"
18#include "darray.h"
19
20#ifdef __APPLE__
21#include <sys/sysctl.h>
22#endif
23
24struct objspace {
25 bool user_gc_disabled;
26 bool measure_gc_time;
27 bool gc_stress;
28
29 size_t gc_count;
30 size_t moving_gc_count;
31 size_t total_gc_time;
32 size_t total_allocated_objects;
33
34 st_table *finalizer_table;
35 struct MMTk_final_job *finalizer_jobs;
36 rb_postponed_job_handle_t finalizer_postponed_job;
37
38 struct ccan_list_head ractor_caches;
39 unsigned long live_ractor_cache_count;
40
41 pthread_mutex_t mutex;
42 rb_atomic_t mutator_blocking_count;
43 bool world_stopped;
44 pthread_cond_t cond_world_stopped;
45 pthread_cond_t cond_world_started;
46 size_t start_the_world_count;
47
48 pthread_mutex_t event_hook_mutex;
49
50 struct {
51 bool gc_thread_crashed;
52 char crash_msg[256];
53 } crash_context;
54
55 struct rb_gc_vm_context vm_context;
56
57 unsigned int fork_hook_vm_lock_lev;
58
59 uintptr_t vo_bit_log_region_size;
60 uintptr_t vo_bit_base_addr;
61 size_t max_non_los_default_alloc_bytes;
62};
63
64#define OBJ_FREE_BUF_CAPACITY 128
65
67 struct ccan_list_node list_node;
68
69 MMTk_Mutator *mutator;
70 bool gc_mutator_p;
71
72 MMTk_BumpPointer *bump_pointer;
73
74 MMTk_ObjectReference obj_free_parallel_buf[OBJ_FREE_BUF_CAPACITY];
75 size_t obj_free_parallel_count;
76 MMTk_ObjectReference obj_free_non_parallel_buf[OBJ_FREE_BUF_CAPACITY];
77 size_t obj_free_non_parallel_count;
78};
79
81 struct MMTk_final_job *next;
82 enum {
83 MMTK_FINAL_JOB_DFREE,
84 MMTK_FINAL_JOB_FINALIZE,
85 } kind;
86 union {
87 struct {
88 void (*func)(void *);
89 void *data;
90 } dfree;
91 struct {
92 /* HACK: we store the object ID on the 0th element of this array. */
93 VALUE finalizer_array;
94 } finalize;
95 } as;
96};
97
98#ifdef RB_THREAD_LOCAL_SPECIFIER
99RB_THREAD_LOCAL_SPECIFIER struct MMTk_GCThreadTLS *rb_mmtk_gc_thread_tls;
100
101RB_THREAD_LOCAL_SPECIFIER VALUE marking_parent_object;
102#else
103# error We currently need language-supported TLS
104#endif
105
106#ifdef MMTK_DEBUG
107# define MMTK_ASSERT(expr, ...) RUBY_ASSERT_ALWAYS(expr, #expr RBIMPL_VA_OPT_ARGS(__VA_ARGS__))
108#else
109# define MMTK_ASSERT(expr, ...) ((void)0)
110#endif
111
112#include <pthread.h>
113
114#define MMTK_ALLOCATION_SEMANTICS_DEFAULT 0
115#define MMTK_ALLOCATION_SEMANTICS_LOS 2
116
117static inline VALUE rb_mmtk_call_object_closure(VALUE obj, bool pin);
118
119static void
120rb_mmtk_init_gc_worker_thread(MMTk_VMWorkerThread gc_thread_tls)
121{
122 rb_mmtk_gc_thread_tls = gc_thread_tls;
123}
124
125static bool
126rb_mmtk_is_mutator(void)
127{
128 return ruby_native_thread_p();
129}
130
131static void
132rb_mmtk_stop_the_world(void)
133{
134 struct objspace *objspace = rb_gc_get_objspace();
135
136 int err;
137 if ((err = pthread_mutex_lock(&objspace->mutex)) != 0) {
138 rb_bug("ERROR: cannot lock objspace->mutex: %s", strerror(err));
139 }
140
141 while (!objspace->world_stopped) {
142 pthread_cond_wait(&objspace->cond_world_stopped, &objspace->mutex);
143 }
144
145 if ((err = pthread_mutex_unlock(&objspace->mutex)) != 0) {
146 rb_bug("ERROR: cannot release objspace->mutex: %s", strerror(err));
147 }
148}
149
150static void
151rb_mmtk_resume_mutators(bool current_gc_may_move)
152{
153 struct objspace *objspace = rb_gc_get_objspace();
154
155 int err;
156 if ((err = pthread_mutex_lock(&objspace->mutex)) != 0) {
157 rb_bug("ERROR: cannot lock objspace->mutex: %s", strerror(err));
158 }
159
160 objspace->world_stopped = false;
161 objspace->gc_count++;
162 if (current_gc_may_move) objspace->moving_gc_count++;
163 pthread_cond_broadcast(&objspace->cond_world_started);
164
165 if ((err = pthread_mutex_unlock(&objspace->mutex)) != 0) {
166 rb_bug("ERROR: cannot release objspace->mutex: %s", strerror(err));
167 }
168}
169
170static void mmtk_flush_obj_free_buffer(struct MMTk_ractor_cache *cache);
171
172static void
173rb_mmtk_block_for_gc(MMTk_VMMutatorThread mutator)
174{
175 struct objspace *objspace = rb_gc_get_objspace();
176
177 size_t starting_gc_count = objspace->gc_count;
178 RUBY_ATOMIC_INC(objspace->mutator_blocking_count);
179 int lock_lev = RB_GC_VM_LOCK();
180 RUBY_ATOMIC_DEC(objspace->mutator_blocking_count);
181 int err;
182 if ((err = pthread_mutex_lock(&objspace->mutex)) != 0) {
183 rb_bug("ERROR: cannot lock objspace->mutex: %s", strerror(err));
184 }
185
186 if (objspace->gc_count == starting_gc_count) {
187 rb_gc_event_hook(0, RUBY_INTERNAL_EVENT_GC_START);
188
189 rb_gc_initialize_vm_context(&objspace->vm_context);
190
191 mutator->gc_mutator_p = true;
192
193 struct timespec gc_start_time;
194 if (objspace->measure_gc_time) {
195 clock_gettime(CLOCK_MONOTONIC, &gc_start_time);
196 }
197
198 rb_gc_save_machine_context();
199
200 rb_gc_vm_barrier();
201
202 struct MMTk_ractor_cache *rc;
203 ccan_list_for_each(&objspace->ractor_caches, rc, list_node) {
204 mmtk_flush_obj_free_buffer(rc);
205 }
206
207 objspace->world_stopped = true;
208
209 pthread_cond_broadcast(&objspace->cond_world_stopped);
210
211 // Wait for GC end
212 while (objspace->world_stopped) {
213 pthread_cond_wait(&objspace->cond_world_started, &objspace->mutex);
214 }
215
216 if (RB_UNLIKELY(objspace->crash_context.gc_thread_crashed)) {
217 rb_bug("%s", objspace->crash_context.crash_msg);
218 }
219
220 if (objspace->measure_gc_time) {
221 struct timespec gc_end_time;
222 clock_gettime(CLOCK_MONOTONIC, &gc_end_time);
223
224 objspace->total_gc_time +=
225 (gc_end_time.tv_sec - gc_start_time.tv_sec) * (1000 * 1000 * 1000) +
226 (gc_end_time.tv_nsec - gc_start_time.tv_nsec);
227 }
228 }
229
230 if ((err = pthread_mutex_unlock(&objspace->mutex)) != 0) {
231 rb_bug("ERROR: cannot release objspace->mutex: %s", strerror(err));
232 }
233 RB_GC_VM_UNLOCK(lock_lev);
234}
235
236static void
237rb_mmtk_before_updating_jit_code(void)
238{
239 rb_gc_before_updating_jit_code();
240}
241
242static void
243rb_mmtk_after_updating_jit_code(void)
244{
245 rb_gc_after_updating_jit_code();
246}
247
248static size_t
249rb_mmtk_number_of_mutators(void)
250{
251 struct objspace *objspace = rb_gc_get_objspace();
252 return objspace->live_ractor_cache_count;
253}
254
255static void
256rb_mmtk_get_mutators(void (*visit_mutator)(MMTk_Mutator *mutator, void *data), void *data)
257{
258 struct objspace *objspace = rb_gc_get_objspace();
259 struct MMTk_ractor_cache *ractor_cache;
260
261 ccan_list_for_each(&objspace->ractor_caches, ractor_cache, list_node) {
262 visit_mutator(ractor_cache->mutator, data);
263 }
264}
265
266static void
267rb_mmtk_scan_gc_roots(void)
268{
269 struct objspace *objspace = rb_gc_get_objspace();
270
271 rb_gc_mark_roots(objspace, NULL);
272}
273
274static int
275pin_value(st_data_t key, st_data_t value, st_data_t data)
276{
277 rb_gc_impl_mark_and_pin((void *)data, (VALUE)value);
278
279 return ST_CONTINUE;
280}
281
282static void
283rb_mmtk_scan_objspace(void)
284{
285 struct objspace *objspace = rb_gc_get_objspace();
286
287 if (objspace->finalizer_table != NULL) {
288 st_foreach(objspace->finalizer_table, pin_value, (st_data_t)objspace);
289 }
290
291 struct MMTk_final_job *job = objspace->finalizer_jobs;
292 while (job != NULL) {
293 switch (job->kind) {
294 case MMTK_FINAL_JOB_DFREE:
295 break;
296 case MMTK_FINAL_JOB_FINALIZE:
297 rb_gc_impl_mark(objspace, job->as.finalize.finalizer_array);
298 break;
299 default:
300 rb_bug("rb_mmtk_scan_objspace: unknown final job type %d", job->kind);
301 }
302
303 job = job->next;
304 }
305}
306
307static void
308rb_mmtk_move_obj_during_marking(MMTk_ObjectReference from, MMTk_ObjectReference to)
309{
310 rb_gc_move_obj_during_marking((VALUE)from, (VALUE)to);
311}
312
313static void
314rb_mmtk_update_object_references(MMTk_ObjectReference mmtk_object)
315{
316 VALUE object = (VALUE)mmtk_object;
317
318 if (!RB_FL_TEST(object, RUBY_FL_WEAK_REFERENCE)) {
319 marking_parent_object = object;
320 rb_gc_update_object_references(rb_gc_get_objspace(), object);
321 marking_parent_object = 0;
322 }
323}
324
325static void
326rb_mmtk_call_gc_mark_children(MMTk_ObjectReference object)
327{
328 marking_parent_object = (VALUE)object;
329 rb_gc_mark_children(rb_gc_get_objspace(), (VALUE)object);
330 marking_parent_object = 0;
331}
332
333static void
334rb_mmtk_handle_weak_references(MMTk_ObjectReference mmtk_object, bool moving)
335{
336 VALUE object = (VALUE)mmtk_object;
337
338 marking_parent_object = object;
339
340 rb_gc_handle_weak_references(object);
341
342 if (moving) {
343 rb_gc_update_object_references(rb_gc_get_objspace(), object);
344 }
345
346 marking_parent_object = 0;
347}
348
349static void
350rb_mmtk_call_obj_free(MMTk_ObjectReference object)
351{
352 VALUE obj = (VALUE)object;
353 struct objspace *objspace = rb_gc_get_objspace();
354
355 if (RB_UNLIKELY(rb_gc_event_hook_required_p(RUBY_INTERNAL_EVENT_FREEOBJ))) {
356 pthread_mutex_lock(&objspace->event_hook_mutex);
357 rb_gc_event_hook(obj, RUBY_INTERNAL_EVENT_FREEOBJ);
358 pthread_mutex_unlock(&objspace->event_hook_mutex);
359 }
360
361 if (RB_UNLIKELY(rb_gc_obj_needs_cleanup_p(obj))) {
362 rb_gc_obj_free(objspace, obj);
363 }
364
365#ifdef MMTK_DEBUG
366 memset((void *)obj, 0, rb_gc_impl_obj_slot_size(obj));
367#endif
368}
369
370static size_t
371rb_mmtk_vm_live_bytes(void)
372{
373 return 0;
374}
375
376static void
377make_final_job(struct objspace *objspace, VALUE obj, VALUE table)
378{
379 MMTK_ASSERT(RB_BUILTIN_TYPE(table) == T_ARRAY);
380
381 struct MMTk_final_job *job = xmalloc(sizeof(struct MMTk_final_job));
382 job->next = objspace->finalizer_jobs;
383 job->kind = MMTK_FINAL_JOB_FINALIZE;
384 job->as.finalize.finalizer_array = table;
385
386 objspace->finalizer_jobs = job;
387}
388
389static int
390rb_mmtk_update_finalizer_table_i(st_data_t key, st_data_t value, st_data_t data, int error)
391{
392 MMTK_ASSERT(mmtk_is_reachable((MMTk_ObjectReference)value));
393 MMTK_ASSERT(RB_BUILTIN_TYPE(value) == T_ARRAY);
394
395 struct objspace *objspace = (struct objspace *)data;
396
397 if (mmtk_is_reachable((MMTk_ObjectReference)key)) {
398 VALUE new_key_location = rb_mmtk_call_object_closure((VALUE)key, false);
399
400 MMTK_ASSERT(RB_FL_TEST(new_key_location, RUBY_FL_FINALIZE));
401
402 if (new_key_location != key) {
403 return ST_REPLACE;
404 }
405 }
406 else {
407 make_final_job(objspace, (VALUE)key, (VALUE)value);
408
409 rb_postponed_job_trigger(objspace->finalizer_postponed_job);
410
411 return ST_DELETE;
412 }
413
414 return ST_CONTINUE;
415}
416
417static int
418rb_mmtk_update_finalizer_table_replace_i(st_data_t *key, st_data_t *value, st_data_t data, int existing)
419{
420 *key = rb_mmtk_call_object_closure((VALUE)*key, false);
421
422 return ST_CONTINUE;
423}
424
425static void
426rb_mmtk_update_finalizer_table(void)
427{
428 struct objspace *objspace = rb_gc_get_objspace();
429
430 st_foreach_with_replace(
431 objspace->finalizer_table,
432 rb_mmtk_update_finalizer_table_i,
433 rb_mmtk_update_finalizer_table_replace_i,
434 (st_data_t)objspace
435 );
436}
437
438static int
439rb_mmtk_global_tables_count(void)
440{
441 return RB_GC_VM_WEAK_TABLE_COUNT;
442}
443
444static inline VALUE rb_mmtk_call_object_closure(VALUE obj, bool pin);
445
446static int
447rb_mmtk_update_global_tables_i(VALUE val, void *data)
448{
449 if (!mmtk_is_reachable((MMTk_ObjectReference)val)) {
450 return ST_DELETE;
451 }
452
453 // TODO: check only if in moving GC
454 if (rb_mmtk_call_object_closure(val, false) != val) {
455 return ST_REPLACE;
456 }
457
458 return ST_CONTINUE;
459}
460
461static int
462rb_mmtk_update_global_tables_replace_i(VALUE *ptr, void *data)
463{
464 // TODO: cache the new location so we don't call rb_mmtk_call_object_closure twice
465 *ptr = rb_mmtk_call_object_closure(*ptr, false);
466
467 return ST_CONTINUE;
468}
469
470static void
471rb_mmtk_update_global_tables(int table, bool moving)
472{
473 MMTK_ASSERT(table < RB_GC_VM_WEAK_TABLE_COUNT);
474
475 rb_gc_vm_weak_table_foreach(
476 rb_mmtk_update_global_tables_i,
477 rb_mmtk_update_global_tables_replace_i,
478 NULL,
479 !moving,
480 (enum rb_gc_vm_weak_tables)table
481 );
482}
483
484static bool
485rb_mmtk_special_const_p(MMTk_ObjectReference object)
486{
487 VALUE obj = (VALUE)object;
488
489 return RB_SPECIAL_CONST_P(obj);
490}
491
492RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 1, 2)
494static void
495rb_mmtk_gc_thread_bug(const char *msg, ...)
496{
497 struct objspace *objspace = rb_gc_get_objspace();
498
499 objspace->crash_context.gc_thread_crashed = true;
500
501 va_list args;
502 va_start(args, msg);
503 vsnprintf(objspace->crash_context.crash_msg, sizeof(objspace->crash_context.crash_msg), msg, args);
504 va_end(args);
505
506 fprintf(stderr, "-- GC thread backtrace "
507 "-------------------------------------------\n");
508 rb_gc_print_backtrace();
509 fprintf(stderr, "\n");
510
511 rb_mmtk_resume_mutators(false);
512
513 sleep(5);
514
515 rb_bug("rb_mmtk_gc_thread_bug");
516}
517
519static void
520rb_mmtk_gc_thread_panic_handler(void)
521{
522 rb_mmtk_gc_thread_bug("MMTk GC thread panicked");
523}
524
526static void
527rb_mmtk_mutator_thread_panic_handler(void)
528{
529 rb_bug("Ruby mutator thread panicked");
530}
531
532// Bootup
533MMTk_RubyUpcalls ruby_upcalls = {
534 rb_mmtk_init_gc_worker_thread,
535 rb_mmtk_is_mutator,
536 rb_mmtk_stop_the_world,
537 rb_mmtk_resume_mutators,
538 rb_mmtk_block_for_gc,
539 rb_mmtk_before_updating_jit_code,
540 rb_mmtk_after_updating_jit_code,
541 rb_mmtk_number_of_mutators,
542 rb_mmtk_get_mutators,
543 rb_mmtk_scan_gc_roots,
544 rb_mmtk_scan_objspace,
545 rb_mmtk_move_obj_during_marking,
546 rb_mmtk_update_object_references,
547 rb_mmtk_call_gc_mark_children,
548 rb_mmtk_handle_weak_references,
549 rb_mmtk_call_obj_free,
550 rb_mmtk_vm_live_bytes,
551 rb_mmtk_update_global_tables,
552 rb_mmtk_global_tables_count,
553 rb_mmtk_update_finalizer_table,
554 rb_mmtk_special_const_p,
555 rb_mmtk_mutator_thread_panic_handler,
556 rb_mmtk_gc_thread_panic_handler,
557};
558
559// Use max 80% of the available memory by default for MMTk
560#define RB_MMTK_HEAP_LIMIT_PERC 80
561#define RB_MMTK_DEFAULT_HEAP_MIN (1024 * 1024)
562#define RB_MMTK_DEFAULT_HEAP_MAX (rb_mmtk_system_physical_memory() / 100 * RB_MMTK_HEAP_LIMIT_PERC)
563
564enum mmtk_heap_mode {
565 RB_MMTK_DYNAMIC_HEAP,
566 RB_MMTK_FIXED_HEAP
567};
568
569MMTk_Builder *
570rb_mmtk_builder_init(void)
571{
572 MMTk_Builder *builder = mmtk_builder_default();
573 return builder;
574}
575
576
577void *
578rb_gc_impl_objspace_alloc(void)
579{
580 /* One heap, binding and objspace per process (multi_objspace_p=false). The VM
581 * shares the same objspace across all Ractors, so a re-entry returns the same
582 * instance. */
583 static struct objspace *the_objspace = NULL;
584 if (the_objspace == NULL) {
585 MMTk_Builder *builder = rb_mmtk_builder_init();
586 MMTk_RubyBindingOptions binding_options = {
587 .suffix_size = 0,
588 };
589 mmtk_init_binding(builder, &binding_options, &ruby_upcalls);
590 the_objspace = calloc(1, sizeof(struct objspace));
591 }
592
593 return the_objspace;
594}
595
596bool
597rb_gc_impl_multi_objspace_p(void)
598{
599 return false;
600}
601
602static void gc_run_finalizers(void *data);
603
604void
605rb_gc_impl_objspace_init(void *objspace_ptr)
606{
607 struct objspace *objspace = objspace_ptr;
608
609 /* The objspace is a singleton (see rb_gc_impl_objspace_alloc). A re-init must
610 * not clobber finalizer_table or ractor_caches. */
611 if (objspace->finalizer_table != NULL) return;
612
613 objspace->measure_gc_time = true;
614
615 objspace->finalizer_table = st_init_numtable();
616 objspace->finalizer_postponed_job = rb_postponed_job_preregister(0, gc_run_finalizers, objspace);
617
618 ccan_list_head_init(&objspace->ractor_caches);
619
620 objspace->mutex = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;
621 objspace->cond_world_stopped = (pthread_cond_t)PTHREAD_COND_INITIALIZER;
622 objspace->cond_world_started = (pthread_cond_t)PTHREAD_COND_INITIALIZER;
623
624 objspace->event_hook_mutex = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;
625
626 objspace->vo_bit_log_region_size = mmtk_get_vo_bit_log_region_size();
627 objspace->vo_bit_base_addr = mmtk_get_vo_bit_base_addr();
628 objspace->max_non_los_default_alloc_bytes = mmtk_max_non_los_default_alloc_bytes();
629}
630
631void
632rb_gc_impl_objspace_free(void *objspace_ptr)
633{
634 /* The objspace is a process-lifetime singleton. */
635}
636
637void *
638rb_gc_impl_ractor_cache_alloc(void *objspace_ptr, void *ractor)
639{
640 struct objspace *objspace = objspace_ptr;
641 if (objspace->live_ractor_cache_count == 0) {
642 mmtk_initialize_collection(ractor);
643 }
644 objspace->live_ractor_cache_count++;
645
646 struct MMTk_ractor_cache *cache = calloc(1, sizeof(struct MMTk_ractor_cache));
647 ccan_list_add(&objspace->ractor_caches, &cache->list_node);
648
649 cache->mutator = mmtk_bind_mutator(cache);
650 cache->bump_pointer = mmtk_get_bump_pointer_allocator(cache->mutator);
651
652 return cache;
653}
654
655void
656rb_gc_impl_objspace_retire_gc(void *objspace_ptr)
657{
658 /* A single objspace needs no per-Ractor GC at teardown. */
659}
660
661void
662rb_gc_impl_ractor_cache_free(void *objspace_ptr, void *cache_ptr)
663{
664 struct objspace *objspace = objspace_ptr;
665 struct MMTk_ractor_cache *cache = cache_ptr;
666
667 ccan_list_del(&cache->list_node);
668
669 mmtk_flush_obj_free_buffer(cache);
670
671 if (ruby_free_at_exit_p()) {
672 MMTK_ASSERT(objspace->live_ractor_cache_count > 0);
673 }
674 else {
675 MMTK_ASSERT(objspace->live_ractor_cache_count > 1);
676 }
677
678 objspace->live_ractor_cache_count--;
679
680 mmtk_destroy_mutator(cache->mutator);
681
682 free(cache);
683}
684
685static bool
686zjit_mmtk_gc_stress_p(void *objspace_ptr)
687{
688 return ((struct objspace *)objspace_ptr)->gc_stress;
689}
690
691static bool
692zjit_mmtk_newobj_tracing_p(void)
693{
694 return rb_gc_event_hook_required_p(RUBY_INTERNAL_EVENT_NEWOBJ);
695}
696
697void rb_gc_impl_set_params(void *objspace_ptr) { }
698
699static VALUE gc_verify_internal_consistency(VALUE self) { return Qnil; }
700
701static inline size_t
702rb_mmtk_align_obj_size(size_t object_size)
703{
704 return (object_size + MMTk_MIN_OBJ_ALIGN - 1) & ~((size_t)MMTk_MIN_OBJ_ALIGN - 1);
705}
706
707bool
708rb_gc_impl_zjit_new_obj_fastpath(void *objspace_ptr, size_t alloc_size, VALUE flags, VALUE klass,
709 struct rb_gc_zjit_fastpath *fastpath)
710{
711#if USE_ZJIT
712 struct objspace *objspace = objspace_ptr;
713
714 size_t total_size = rb_mmtk_align_obj_size(alloc_size + sizeof(VALUE));
715 size_t object_size = total_size - sizeof(VALUE);
716 size_t value_size_shift = sizeof(VALUE) == 8 ? 3 : 2;
717
718 if (total_size > objspace->max_non_los_default_alloc_bytes) return false;
719
720 struct rb_gc_zjit_mmtk_new_obj_fastpath mmtk_fastpath = {
721 objspace,
722 offsetof(struct objspace, total_allocated_objects),
723 offsetof(struct MMTk_ractor_cache, mutator),
724 offsetof(struct MMTk_ractor_cache, bump_pointer),
725 offsetof(struct MMTk_ractor_cache, obj_free_parallel_buf),
726 offsetof(struct MMTk_ractor_cache, obj_free_parallel_count),
727 offsetof(MMTk_BumpPointer, cursor),
728 offsetof(MMTk_BumpPointer, limit),
729 MMTk_MIN_OBJ_ALIGN,
730 object_size,
731 total_size,
732 MMTK_ALLOCATION_SEMANTICS_DEFAULT,
733 (uintptr_t)zjit_mmtk_gc_stress_p,
734 (uintptr_t)zjit_mmtk_newobj_tracing_p,
735 (uintptr_t)mmtk_post_alloc,
736 OBJ_FREE_BUF_CAPACITY - 1,
737 value_size_shift,
738 flags,
739 klass
740 };
741
742 memset(fastpath, 0, sizeof(*fastpath));
743 fastpath->kind = RB_GC_ZJIT_FASTPATH_MMTK;
744 memcpy(fastpath->data.words, &mmtk_fastpath, sizeof(mmtk_fastpath));
745
746 return true;
747#else
748 return false;
749#endif
750}
751
752void
753rb_gc_impl_init(void)
754{
755 VALUE gc_constants = rb_hash_new();
756 rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVALUE_SIZE")), SIZET2NUM(sizeof(struct RBasic) + sizeof(VALUE[RBIMPL_RVALUE_EMBED_LEN_MAX])));
757 rb_hash_aset(gc_constants, ID2SYM(rb_intern("RBASIC_SIZE")), SIZET2NUM(sizeof(struct RBasic)));
758 rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVALUE_OVERHEAD")), INT2NUM(0));
759 // TODO: correctly set RVALUE_OLD_AGE when we have generational GC support
760 rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVALUE_OLD_AGE")), INT2FIX(0));
761 OBJ_FREEZE(gc_constants);
762 rb_define_const(rb_mGC, "INTERNAL_CONSTANTS", gc_constants);
763
764 // no-ops for compatibility
765 rb_define_singleton_method(rb_mGC, "verify_internal_consistency", gc_verify_internal_consistency, 0);
766
770 rb_define_singleton_method(rb_mGC, "latest_compact_info", rb_f_notimplement, 0);
771 rb_define_singleton_method(rb_mGC, "verify_compaction_references", rb_f_notimplement, -1);
772}
773
774int
775rb_mmtk_obj_free_iter_wrapper(VALUE obj, void *data)
776{
777 struct objspace *objspace = data;
778
779 if (!RB_TYPE_P(obj, T_NONE)) {
780 rb_gc_obj_free_vm_weak_references(obj);
781 rb_gc_obj_free(objspace, obj);
782 }
783
784 return 0;
785}
786
787// Shutdown
788static void each_object(struct objspace *objspace, int (*func)(VALUE, void *), void *data);
789
790void
791rb_gc_impl_shutdown_free_objects(void *objspace_ptr)
792{
793 mmtk_set_gc_enabled(false);
794 each_object(objspace_ptr, rb_mmtk_obj_free_iter_wrapper, objspace_ptr);
795 mmtk_set_gc_enabled(true);
796}
797
798// GC
799void
800rb_gc_impl_start(void *objspace_ptr, bool full_mark, bool immediate_mark, bool immediate_sweep, bool compact)
801{
802 mmtk_handle_user_collection_request(rb_gc_get_ractor_newobj_cache(), true, full_mark);
803}
804
805bool
806rb_gc_impl_during_gc_p(void *objspace_ptr)
807{
808 struct objspace *objspace = objspace_ptr;
809 return objspace->world_stopped;
810}
811
812static void
813rb_gc_impl_prepare_heap_i(MMTk_ObjectReference obj, void *d)
814{
815 rb_gc_prepare_heap_process_object((VALUE)obj);
816}
817
818void
819rb_gc_impl_prepare_heap(void *objspace_ptr)
820{
821 mmtk_enumerate_objects(rb_gc_impl_prepare_heap_i, NULL);
822}
823
824void
825rb_gc_impl_gc_enable(void *objspace_ptr)
826{
827 mmtk_set_gc_enabled(true);
828}
829
830void
831rb_gc_impl_gc_disable(void *objspace_ptr, bool finish_current_gc)
832{
833 mmtk_set_gc_enabled(false);
834}
835
836bool
837rb_gc_impl_user_gc_disabled_set(void *objspace_ptr, bool disable)
838{
839 struct objspace *objspace = objspace_ptr;
840 const bool was = objspace->user_gc_disabled;
841 objspace->user_gc_disabled = disable;
842
843 if (was != disable) {
844 mmtk_set_gc_enabled(!disable);
845 objspace->user_gc_disabled = disable;
846 }
847
848 return was;
849}
850
851bool
852rb_gc_impl_user_gc_disabled_p(void *objspace_ptr)
853{
854 struct objspace *objspace = objspace_ptr;
855 return objspace->user_gc_disabled;
856}
857
858bool
859rb_gc_impl_gc_enabled_p(void *objspace_ptr)
860{
861 return mmtk_gc_enabled_p();
862}
863
864void
865rb_gc_impl_stress_set(void *objspace_ptr, VALUE flag)
866{
867 struct objspace *objspace = objspace_ptr;
868
869 objspace->gc_stress = RTEST(flag);
870}
871
872VALUE
873rb_gc_impl_stress_get(void *objspace_ptr)
874{
875 struct objspace *objspace = objspace_ptr;
876
877 return objspace->gc_stress ? Qtrue : Qfalse;
878}
879
880VALUE
881rb_gc_impl_config_get(void *objspace_ptr)
882{
883 VALUE hash = rb_hash_new();
884
885 rb_hash_aset(hash, ID2SYM(rb_intern_const("mmtk_worker_count")), RB_ULONG2NUM(mmtk_worker_count()));
886 rb_hash_aset(hash, ID2SYM(rb_intern_const("mmtk_plan")), rb_str_new_cstr((const char *)mmtk_plan()));
887 rb_hash_aset(hash, ID2SYM(rb_intern_const("mmtk_heap_mode")), rb_str_new_cstr((const char *)mmtk_heap_mode()));
888 size_t heap_min = mmtk_heap_min();
889 if (heap_min > 0) rb_hash_aset(hash, ID2SYM(rb_intern_const("mmtk_heap_min")), RB_ULONG2NUM(heap_min));
890 rb_hash_aset(hash, ID2SYM(rb_intern_const("mmtk_heap_max")), RB_ULONG2NUM(mmtk_heap_max()));
891
892 return hash;
893}
894
895void
896rb_gc_impl_config_set(void *objspace_ptr, VALUE hash)
897{
898 // TODO
899}
900
901struct rb_gc_vm_context *
902rb_gc_impl_get_vm_context(void *objspace_ptr)
903{
904 struct objspace *objspace = objspace_ptr;
905
906 return &objspace->vm_context;
907}
908
909// Object allocation
910
911static VALUE
912rb_mmtk_alloc_fast_path(struct objspace *objspace, struct MMTk_ractor_cache *ractor_cache, size_t size, size_t align)
913{
914 MMTk_BumpPointer *bump_pointer = ractor_cache->bump_pointer;
915 if (bump_pointer == NULL) return 0;
916
917 uintptr_t cursor = bump_pointer->cursor;
918
919 // Ensure cursor is aligned
920 size_t mask = align - 1;
921 cursor = (cursor + mask) & ~mask;
922
923 cursor += size;
924
925 if (cursor > bump_pointer->limit) {
926 return 0;
927 }
928 else {
929 VALUE obj = cursor - size;
930 bump_pointer->cursor = cursor;
931 return obj;
932 }
933}
934
935static bool
936obj_can_parallel_free_p(VALUE obj)
937{
938 switch (RB_BUILTIN_TYPE(obj)) {
939 case T_ARRAY:
940 case T_BIGNUM:
941 case T_COMPLEX:
942 case T_FLOAT:
943 case T_HASH:
944 case T_OBJECT:
945 case T_RATIONAL:
946 case T_REGEXP:
947 case T_STRING:
948 case T_STRUCT:
949 case T_SYMBOL:
950 return true;
951 default:
952 return false;
953 }
954}
955
956static bool
957obj_need_obj_free_p(VALUE obj)
958{
959 switch (RB_BUILTIN_TYPE(obj)) {
960 case T_FLOAT:
961 case T_RATIONAL:
962 case T_COMPLEX:
963 case T_OBJECT:
964 return false;
965 default:
966 return true;
967 }
968}
969
970static void
971mmtk_flush_obj_free_buffer(struct MMTk_ractor_cache *cache)
972{
973 if (cache->obj_free_parallel_count > 0) {
974 mmtk_add_obj_free_candidates(cache->obj_free_parallel_buf,
975 cache->obj_free_parallel_count, true);
976 cache->obj_free_parallel_count = 0;
977 }
978 if (cache->obj_free_non_parallel_count > 0) {
979 mmtk_add_obj_free_candidates(cache->obj_free_non_parallel_buf,
980 cache->obj_free_non_parallel_count, false);
981 cache->obj_free_non_parallel_count = 0;
982 }
983}
984
985static inline void
986mmtk_buffer_obj_free_candidate(struct MMTk_ractor_cache *cache, VALUE obj)
987{
988 if (obj_can_parallel_free_p(obj)) {
989 cache->obj_free_parallel_buf[cache->obj_free_parallel_count++] = (MMTk_ObjectReference)obj;
990 if (cache->obj_free_parallel_count >= OBJ_FREE_BUF_CAPACITY) {
991 mmtk_add_obj_free_candidates(cache->obj_free_parallel_buf,
992 cache->obj_free_parallel_count, true);
993 cache->obj_free_parallel_count = 0;
994 }
995 }
996 else {
997 cache->obj_free_non_parallel_buf[cache->obj_free_non_parallel_count++] = (MMTk_ObjectReference)obj;
998 if (cache->obj_free_non_parallel_count >= OBJ_FREE_BUF_CAPACITY) {
999 mmtk_add_obj_free_candidates(cache->obj_free_non_parallel_buf,
1000 cache->obj_free_non_parallel_count, false);
1001 cache->obj_free_non_parallel_count = 0;
1002 }
1003 }
1004}
1005
1006static void
1007mmtk_post_alloc_fast_immix(struct objspace *objspace, struct MMTk_ractor_cache *ractor_cache, uintptr_t obj)
1008{
1009 uintptr_t region_offset = obj >> objspace->vo_bit_log_region_size;
1010 uintptr_t byte_offset = region_offset / 8;
1011 uintptr_t bit_offset = region_offset % 8;
1012 uintptr_t meta_byte_address = objspace->vo_bit_base_addr + byte_offset;
1013 uint8_t byte = 1 << bit_offset;
1014 uint8_t *meta_byte_ptr = (uint8_t*)meta_byte_address;
1015 *meta_byte_ptr |= byte;
1016}
1017
1018VALUE
1019rb_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)
1020{
1021 struct objspace *objspace = objspace_ptr;
1022 struct MMTk_ractor_cache *ractor_cache = cache_ptr;
1023
1024 if (alloc_size == 0) {
1025 rb_bug("rb_gc_impl_new_obj: allocation size out of range (size=%"PRIuSIZE")", alloc_size);
1026 }
1027
1028 // Layout: [hidden size header (sizeof(VALUE))][payload (alloc_size)]
1029 size_t total_size = rb_mmtk_align_obj_size(alloc_size + sizeof(VALUE));
1030 size_t object_size = total_size - sizeof(VALUE);
1031 MMTk_AllocationSemantics semantics = total_size > objspace->max_non_los_default_alloc_bytes
1032 ? MMTK_ALLOCATION_SEMANTICS_LOS
1033 : MMTK_ALLOCATION_SEMANTICS_DEFAULT;
1034 *actual_alloc_size = object_size;
1035
1036 if (objspace->gc_stress) {
1037 mmtk_handle_user_collection_request(ractor_cache, false, false);
1038 }
1039
1040 VALUE *alloc_obj = semantics == MMTK_ALLOCATION_SEMANTICS_DEFAULT
1041 ? (VALUE *)rb_mmtk_alloc_fast_path(objspace, ractor_cache, total_size, MMTk_MIN_OBJ_ALIGN)
1042 : NULL;
1043 if (!alloc_obj) {
1044 alloc_obj = mmtk_alloc(ractor_cache->mutator, total_size, MMTk_MIN_OBJ_ALIGN, 0, semantics);
1045
1046 // On heap exhaustion raise NoMemoryError.
1047 if (RB_UNLIKELY(alloc_obj == NULL)) {
1048 rb_memerror();
1049 }
1050 }
1051
1052 alloc_obj++;
1053 alloc_obj[-1] = object_size;
1054 alloc_obj[0] = flags;
1055 alloc_obj[1] = klass;
1056
1057 if (semantics == MMTK_ALLOCATION_SEMANTICS_LOS || ractor_cache->bump_pointer == NULL) {
1058 mmtk_post_alloc(ractor_cache->mutator, (void*)alloc_obj, total_size, semantics);
1059 }
1060 else {
1061 // We can use the post alloc fast path if we're using Immix bump pointer allocator
1062 mmtk_post_alloc_fast_immix(objspace, ractor_cache, (uintptr_t)alloc_obj);
1063 }
1064
1065 if (obj_need_obj_free_p((VALUE)alloc_obj)) {
1066 mmtk_buffer_obj_free_candidate(ractor_cache, (VALUE)alloc_obj);
1067 }
1068
1069 objspace->total_allocated_objects++;
1070
1071 return (VALUE)alloc_obj;
1072}
1073
1074size_t
1075rb_gc_impl_obj_slot_size(VALUE obj)
1076{
1077 return ((VALUE *)obj)[-1];
1078}
1079
1080size_t
1081rb_gc_impl_size_slot_size(void *objspace_ptr, size_t size)
1082{
1083 if (size == 0) {
1084 rb_bug("rb_gc_impl_size_slot_size: size too large (size=%"PRIuSIZE")", size);
1085 }
1086
1087 return rb_mmtk_align_obj_size(size + sizeof(VALUE)) - sizeof(VALUE);
1088}
1089
1090bool
1091rb_gc_impl_size_allocatable_p(size_t size)
1092{
1093 return true;
1094}
1095
1096size_t
1097rb_gc_impl_max_allocation_size(void)
1098{
1099 return SIZE_MAX;
1100}
1101
1102// Malloc
1103void *
1104rb_gc_impl_malloc(void *objspace_ptr, size_t size, bool gc_allowed)
1105{
1106 // TODO: don't use system malloc
1107 return malloc(size);
1108}
1109
1110void *
1111rb_gc_impl_calloc(void *objspace_ptr, size_t size, bool gc_allowed)
1112{
1113 // TODO: don't use system calloc
1114 return calloc(1, size);
1115}
1116
1117void *
1118rb_gc_impl_realloc(void *objspace_ptr, void *ptr, size_t new_size, size_t old_size, bool gc_allowed)
1119{
1120 // TODO: don't use system realloc
1121 return realloc(ptr, new_size);
1122}
1123
1124void
1125rb_gc_impl_free(void *objspace_ptr, void *ptr, size_t old_size)
1126{
1127 // TODO: don't use system free
1128 free(ptr);
1129}
1130
1131void rb_gc_impl_adjust_memory_usage(void *objspace_ptr, ssize_t diff) { }
1132
1133// Marking
1134static inline VALUE
1135rb_mmtk_call_object_closure(VALUE obj, bool pin)
1136{
1137 if (RB_UNLIKELY(RB_BUILTIN_TYPE(obj) == T_NONE)) {
1138 enum { info_size = 256 };
1139 char obj_info_buf[info_size];
1140 rb_raw_obj_info(obj_info_buf, info_size, obj);
1141
1142 char parent_obj_info_buf[info_size];
1143 rb_raw_obj_info(parent_obj_info_buf, info_size, marking_parent_object);
1144
1145 rb_mmtk_gc_thread_bug("try to mark T_NONE object (obj: %s, parent: %s)", obj_info_buf, parent_obj_info_buf);
1146 }
1147
1148 return (VALUE)rb_mmtk_gc_thread_tls->object_closure.c_function(
1149 rb_mmtk_gc_thread_tls->object_closure.rust_closure,
1150 rb_mmtk_gc_thread_tls->gc_context,
1151 (MMTk_ObjectReference)obj,
1152 pin
1153 );
1154}
1155
1156void
1157rb_gc_impl_mark(void *objspace_ptr, VALUE obj)
1158{
1159 if (RB_SPECIAL_CONST_P(obj)) return;
1160
1161 rb_mmtk_call_object_closure(obj, false);
1162}
1163
1164void
1165rb_gc_impl_mark_and_move(void *objspace_ptr, VALUE *ptr)
1166{
1167 if (RB_SPECIAL_CONST_P(*ptr)) return;
1168
1169 VALUE new_obj = rb_mmtk_call_object_closure(*ptr, false);
1170 if (new_obj != *ptr) {
1171 *ptr = new_obj;
1172 }
1173}
1174
1175void
1176rb_gc_impl_mark_and_pin(void *objspace_ptr, VALUE obj)
1177{
1178 if (RB_SPECIAL_CONST_P(obj)) return;
1179
1180 rb_mmtk_call_object_closure(obj, true);
1181}
1182
1183void
1184rb_gc_impl_mark_maybe(void *objspace_ptr, VALUE obj)
1185{
1186 if (rb_gc_impl_live_object_p(objspace_ptr, (const void *)obj)) {
1187 rb_gc_impl_mark_and_pin(objspace_ptr, obj);
1188 }
1189}
1190
1191void
1192rb_gc_impl_declare_weak_references(void *objspace_ptr, VALUE obj)
1193{
1195 mmtk_declare_weak_references((MMTk_ObjectReference)obj);
1196}
1197
1198bool
1199rb_gc_impl_handle_weak_references_alive_p(void *objspace_ptr, VALUE obj)
1200{
1201 return mmtk_weak_references_alive_p((MMTk_ObjectReference)obj);
1202}
1203
1204// Compaction
1205void
1206rb_gc_impl_register_pinning_obj(void *objspace_ptr, VALUE obj)
1207{
1208 mmtk_register_pinning_obj((MMTk_ObjectReference)obj);
1209}
1210
1211bool
1212rb_gc_impl_object_moved_p(void *objspace_ptr, VALUE obj)
1213{
1214 return rb_mmtk_call_object_closure(obj, false) != obj;
1215}
1216
1217bool
1218rb_gc_impl_pinned_p(void *objspace_ptr, VALUE obj)
1219{
1220 /* MMTk tracks pinning separately */
1221 return false;
1222}
1223
1224VALUE
1225rb_gc_impl_location(void *objspace_ptr, VALUE obj)
1226{
1227 return rb_mmtk_call_object_closure(obj, false);
1228}
1229
1230// Write barriers
1231void
1232rb_gc_impl_writebarrier(void *objspace_ptr, VALUE a, VALUE b)
1233{
1234 struct MMTk_ractor_cache *cache = rb_gc_get_ractor_newobj_cache();
1235
1236 if (SPECIAL_CONST_P(b)) return;
1237
1238#ifdef MMTK_DEBUG
1239 if (!rb_gc_impl_live_object_p(objspace_ptr, (void *)a)) {
1240 char buff[256];
1241 rb_bug("a: %s is not an object", rb_raw_obj_info(buff, 256, a));
1242 }
1243
1244 if (!rb_gc_impl_live_object_p(objspace_ptr, (void *)b)) {
1245 char buff[256];
1246 rb_bug("b: %s is not an object", rb_raw_obj_info(buff, 256, b));
1247 }
1248#endif
1249
1250 MMTK_ASSERT(BUILTIN_TYPE(a) != T_NONE);
1251 MMTK_ASSERT(BUILTIN_TYPE(b) != T_NONE);
1252
1253 mmtk_object_reference_write_post(cache->mutator, (MMTk_ObjectReference)a);
1254}
1255
1256void
1257rb_gc_impl_writebarrier_unprotect(void *objspace_ptr, VALUE obj)
1258{
1259 mmtk_register_wb_unprotected_object((MMTk_ObjectReference)obj);
1260}
1261
1262void
1263rb_gc_impl_obj_became_shareable(void *objspace_ptr, VALUE obj)
1264{
1265 /* MMTk has no per-page shareable bits. */
1266}
1267
1268void
1269rb_gc_impl_writebarrier_remember(void *objspace_ptr, VALUE obj)
1270{
1271 struct MMTk_ractor_cache *cache = rb_gc_get_ractor_newobj_cache();
1272
1273 mmtk_object_reference_write_post(cache->mutator, (MMTk_ObjectReference)obj);
1274}
1275
1276// Heap walking
1277static void
1278each_objects_i(MMTk_ObjectReference obj, void *d)
1279{
1280 rb_darray(VALUE) *objs = d;
1281
1282 rb_darray_append(objs, (VALUE)obj);
1283}
1284
1285static void
1286each_object(struct objspace *objspace, int (*func)(VALUE, void *), void *data)
1287{
1288 rb_darray(VALUE) objs;
1289 rb_darray_make(&objs, 0);
1290
1291 mmtk_enumerate_objects(each_objects_i, &objs);
1292
1293 VALUE *obj_ptr;
1294 rb_darray_foreach(objs, i, obj_ptr) {
1295 if (!mmtk_is_mmtk_object((MMTk_ObjectReference)*obj_ptr)) continue;
1296
1297 if (func(*obj_ptr, data) != 0) {
1298 break;
1299 }
1300 }
1301
1302 rb_darray_free(objs);
1303}
1304
1306 int (*func)(void *, void *, size_t, void *);
1307 void *data;
1308};
1309
1310static int
1311rb_gc_impl_each_objects_i(VALUE obj, void *d)
1312{
1313 struct rb_gc_impl_each_objects_data *data = d;
1314
1315 size_t slot_size = rb_gc_impl_obj_slot_size(obj);
1316
1317 return data->func((void *)obj, (void *)(obj + slot_size), slot_size, data->data);
1318}
1319
1320void
1321rb_gc_impl_each_objects(void *objspace_ptr, int (*func)(void *, void *, size_t, void *), void *data)
1322{
1323 struct rb_gc_impl_each_objects_data each_objects_data = {
1324 .func = func,
1325 .data = data
1326 };
1327
1328 each_object(objspace_ptr, rb_gc_impl_each_objects_i, &each_objects_data);
1329}
1330
1332 void (*func)(VALUE, void *);
1333 void *data;
1334};
1335
1336static int
1337rb_gc_impl_each_object_i(VALUE obj, void *d)
1338{
1339 struct rb_gc_impl_each_object_data *data = d;
1340
1341 data->func(obj, data->data);
1342
1343 return 0;
1344}
1345
1346void
1347rb_gc_impl_each_object(void *objspace_ptr, void (*func)(VALUE, void *), void *data)
1348{
1349 struct rb_gc_impl_each_object_data each_object_data = {
1350 .func = func,
1351 .data = data
1352 };
1353
1354 each_object(objspace_ptr, rb_gc_impl_each_object_i, &each_object_data);
1355}
1356
1357// Finalizers
1358static VALUE
1359gc_run_finalizers_get_final(long i, void *data)
1360{
1361 VALUE table = (VALUE)data;
1362
1363 return RARRAY_AREF(table, i + 1);
1364}
1365
1366static void
1367gc_run_finalizers(void *data)
1368{
1369 struct objspace *objspace = data;
1370
1371 rb_gc_set_pending_interrupt();
1372
1373 while (objspace->finalizer_jobs != NULL) {
1374 struct MMTk_final_job *job = objspace->finalizer_jobs;
1375 objspace->finalizer_jobs = job->next;
1376
1377 switch (job->kind) {
1378 case MMTK_FINAL_JOB_DFREE:
1379 job->as.dfree.func(job->as.dfree.data);
1380 break;
1381 case MMTK_FINAL_JOB_FINALIZE: {
1382 VALUE finalizer_array = job->as.finalize.finalizer_array;
1383
1384 rb_gc_run_obj_finalizer(
1385 RARRAY_AREF(finalizer_array, 0),
1386 RARRAY_LEN(finalizer_array) - 1,
1387 gc_run_finalizers_get_final,
1388 (void *)finalizer_array
1389 );
1390
1391 RB_GC_GUARD(finalizer_array);
1392 break;
1393 }
1394 }
1395
1396 xfree(job);
1397 }
1398
1399 rb_gc_unset_pending_interrupt();
1400}
1401
1402void
1403rb_gc_impl_make_zombie(void *objspace_ptr, VALUE obj, void (*dfree)(void *), void *data)
1404{
1405 if (dfree == NULL) return;
1406
1407 struct objspace *objspace = objspace_ptr;
1408
1409 struct MMTk_final_job *job = xmalloc(sizeof(struct MMTk_final_job));
1410 job->kind = MMTK_FINAL_JOB_DFREE;
1411 job->as.dfree.func = dfree;
1412 job->as.dfree.data = data;
1413
1414 struct MMTk_final_job *prev;
1415 do {
1416 job->next = objspace->finalizer_jobs;
1417 prev = RUBY_ATOMIC_PTR_CAS(objspace->finalizer_jobs, job->next, job);
1418 } while (prev != job->next);
1419
1420 if (!ruby_free_at_exit_p()) {
1421 rb_postponed_job_trigger(objspace->finalizer_postponed_job);
1422 }
1423}
1424
1425VALUE
1426rb_gc_impl_define_finalizer(void *objspace_ptr, VALUE obj, VALUE block)
1427{
1428 struct objspace *objspace = objspace_ptr;
1429 VALUE table;
1430 st_data_t data;
1431
1432 RBASIC(obj)->flags |= FL_FINALIZE;
1433
1434 int lev = RB_GC_VM_LOCK();
1435
1436 if (st_lookup(objspace->finalizer_table, obj, &data)) {
1437 table = (VALUE)data;
1438
1439 /* avoid duplicate block, table is usually small */
1440 {
1441 long len = RARRAY_LEN(table);
1442 long i;
1443
1444 for (i = 0; i < len; i++) {
1445 VALUE recv = RARRAY_AREF(table, i);
1446 if (rb_equal(recv, block)) {
1447 RB_GC_VM_UNLOCK(lev);
1448 return recv;
1449 }
1450 }
1451 }
1452
1453 rb_ary_push(table, block);
1454 }
1455 else {
1456 table = rb_ary_new3(2, rb_obj_id(obj), block);
1457 rb_obj_hide(table);
1458 st_add_direct(objspace->finalizer_table, obj, table);
1459 }
1460
1461 RB_GC_VM_UNLOCK(lev);
1462
1463 return block;
1464}
1465
1466void
1467rb_gc_impl_undefine_finalizer(void *objspace_ptr, VALUE obj)
1468{
1469 struct objspace *objspace = objspace_ptr;
1470
1471 st_data_t data = obj;
1472
1473 int lev = RB_GC_VM_LOCK();
1474 st_delete(objspace->finalizer_table, &data, 0);
1475 RB_GC_VM_UNLOCK(lev);
1476
1477 FL_UNSET(obj, FL_FINALIZE);
1478}
1479
1480void
1481rb_gc_impl_copy_finalizer(void *objspace_ptr, VALUE dest, VALUE obj)
1482{
1483 struct objspace *objspace = objspace_ptr;
1484 VALUE table;
1485 st_data_t data;
1486
1487 if (!FL_TEST(obj, FL_FINALIZE)) return;
1488
1489 int lev = RB_GC_VM_LOCK();
1490 if (RB_LIKELY(st_lookup(objspace->finalizer_table, obj, &data))) {
1491 table = rb_ary_dup((VALUE)data);
1492 RARRAY_ASET(table, 0, rb_obj_id(dest));
1493 st_insert(objspace->finalizer_table, dest, table);
1494 FL_SET(dest, FL_FINALIZE);
1495 }
1496 else {
1497 rb_bug("rb_gc_copy_finalizer: FL_FINALIZE set but not found in finalizer_table: %s", rb_obj_info(obj));
1498 }
1499 RB_GC_VM_UNLOCK(lev);
1500}
1501
1502static int
1503move_finalizer_from_table_i(st_data_t key, st_data_t val, st_data_t arg)
1504{
1505 struct objspace *objspace = (struct objspace *)arg;
1506
1507 make_final_job(objspace, (VALUE)key, (VALUE)val);
1508
1509 return ST_DELETE;
1510}
1511
1512void
1513rb_gc_impl_shutdown_call_finalizer(void *objspace_ptr)
1514{
1515 struct objspace *objspace = objspace_ptr;
1516
1517 while (objspace->finalizer_table->num_entries) {
1518 st_foreach(objspace->finalizer_table, move_finalizer_from_table_i, (st_data_t)objspace);
1519
1520 gc_run_finalizers(objspace);
1521 }
1522
1523 unsigned int lev = RB_GC_VM_LOCK();
1524 {
1525 struct MMTk_ractor_cache *rc;
1526 ccan_list_for_each(&objspace->ractor_caches, rc, list_node) {
1527 mmtk_flush_obj_free_buffer(rc);
1528 }
1529
1530 struct MMTk_RawVecOfObjRef registered_candidates = mmtk_get_all_obj_free_candidates();
1531 for (size_t i = 0; i < registered_candidates.len; i++) {
1532 VALUE obj = (VALUE)registered_candidates.ptr[i];
1533
1534 if (rb_gc_shutdown_call_finalizer_p(obj)) {
1535 rb_gc_obj_free(objspace_ptr, obj);
1536 RBASIC(obj)->flags = 0;
1537 }
1538 }
1539 mmtk_free_raw_vec_of_obj_ref(registered_candidates);
1540 }
1541 RB_GC_VM_UNLOCK(lev);
1542
1543 gc_run_finalizers(objspace);
1544}
1545
1546// Forking
1547
1548void
1549rb_gc_impl_before_fork(void *objspace_ptr)
1550{
1551 struct objspace *objspace = objspace_ptr;
1552
1553 retry:
1554 objspace->fork_hook_vm_lock_lev = RB_GC_VM_LOCK();
1555 rb_gc_vm_barrier();
1556
1557 /* At this point, we know that all the Ractors are paused because of the
1558 * rb_gc_vm_barrier above. Since rb_mmtk_block_for_gc is a barrier point,
1559 * one or more Ractors could be paused there. However, mmtk_before_fork is
1560 * not compatible with that because it assumes that the MMTk workers are idle,
1561 * but the workers are not idle because they are busy working on a GC.
1562 *
1563 * This essentially implements a trylock. It will optimistically lock but will
1564 * release the lock if it detects that any other Ractors are waiting in
1565 * rb_mmtk_block_for_gc.
1566 */
1567 rb_atomic_t mutator_blocking_count = RUBY_ATOMIC_LOAD(objspace->mutator_blocking_count);
1568 if (mutator_blocking_count != 0) {
1569 RB_GC_VM_UNLOCK(objspace->fork_hook_vm_lock_lev);
1570 goto retry;
1571 }
1572
1573 mmtk_before_fork();
1574}
1575
1576void
1577rb_gc_impl_after_fork(void *objspace_ptr, rb_pid_t pid)
1578{
1579 struct objspace *objspace = objspace_ptr;
1580
1581 mmtk_after_fork(rb_gc_get_ractor_newobj_cache());
1582
1583 RB_GC_VM_UNLOCK(objspace->fork_hook_vm_lock_lev);
1584}
1585
1586// Statistics
1587
1588void
1589rb_gc_impl_set_measure_total_time(void *objspace_ptr, VALUE flag)
1590{
1591 struct objspace *objspace = objspace_ptr;
1592
1593 objspace->measure_gc_time = RTEST(flag);
1594}
1595
1596bool
1597rb_gc_impl_get_measure_total_time(void *objspace_ptr)
1598{
1599 struct objspace *objspace = objspace_ptr;
1600
1601 return objspace->measure_gc_time;
1602}
1603
1604unsigned long long
1605rb_gc_impl_get_total_time(void *objspace_ptr)
1606{
1607 struct objspace *objspace = objspace_ptr;
1608
1609 return objspace->total_gc_time;
1610}
1611
1612size_t
1613rb_gc_impl_gc_count(void *objspace_ptr)
1614{
1615 struct objspace *objspace = objspace_ptr;
1616
1617 return objspace->gc_count;
1618}
1619
1620VALUE
1621rb_gc_impl_latest_gc_info(void *objspace_ptr, VALUE hash_or_key)
1622{
1623 VALUE hash = Qnil, key = Qnil;
1624
1625 if (SYMBOL_P(hash_or_key)) {
1626 key = hash_or_key;
1627 }
1628 else if (RB_TYPE_P(hash_or_key, T_HASH)) {
1629 hash = hash_or_key;
1630 }
1631 else {
1632 rb_bug("gc_info_decode: non-hash or symbol given");
1633 }
1634
1635#define SET(name, attr) \
1636 if (key == ID2SYM(rb_intern_const(#name))) \
1637 return (attr); \
1638 else if (hash != Qnil) \
1639 rb_hash_aset(hash, ID2SYM(rb_intern_const(#name)), (attr));
1640
1641 /* Hack to get StackProf working because it calls rb_gc_latest_gc_info with
1642 * the :state key and expects a result. This always returns the :none state. */
1643 SET(state, ID2SYM(rb_intern_const("none")));
1644#undef SET
1645
1646 if (!NIL_P(key)) {
1647 // Matched key should return above
1648 return Qundef;
1649 }
1650
1651 return hash;
1652}
1653
1654enum gc_stat_sym {
1655 gc_stat_sym_count,
1656 gc_stat_sym_moving_gc_count,
1657 gc_stat_sym_time,
1658 gc_stat_sym_total_allocated_objects,
1659 gc_stat_sym_total_bytes,
1660 gc_stat_sym_used_bytes,
1661 gc_stat_sym_free_bytes,
1662 gc_stat_sym_starting_heap_address,
1663 gc_stat_sym_last_heap_address,
1664 gc_stat_sym_weak_references_count,
1665 gc_stat_sym_last
1666};
1667
1668static VALUE gc_stat_symbols[gc_stat_sym_last];
1669
1670static void
1671setup_gc_stat_symbols(void)
1672{
1673 if (gc_stat_symbols[0] == 0) {
1674#define S(s) gc_stat_symbols[gc_stat_sym_##s] = ID2SYM(rb_intern_const(#s))
1675 S(count);
1676 S(moving_gc_count);
1677 S(time);
1678 S(total_allocated_objects);
1679 S(total_bytes);
1680 S(used_bytes);
1681 S(free_bytes);
1682 S(starting_heap_address);
1683 S(last_heap_address);
1684 S(weak_references_count);
1685 }
1686}
1687
1688VALUE
1689rb_gc_impl_stat(void *objspace_ptr, VALUE hash_or_sym)
1690{
1691 struct objspace *objspace = objspace_ptr;
1692 VALUE hash = Qnil, key = Qnil;
1693
1694 setup_gc_stat_symbols();
1695
1696 if (RB_TYPE_P(hash_or_sym, T_HASH)) {
1697 hash = hash_or_sym;
1698 }
1699 else if (SYMBOL_P(hash_or_sym)) {
1700 key = hash_or_sym;
1701 }
1702 else {
1703 rb_bug("non-hash or symbol given");
1704 }
1705
1706#define SET(name, attr) \
1707 if (key == gc_stat_symbols[gc_stat_sym_##name]) \
1708 return SIZET2NUM(attr); \
1709 else if (hash != Qnil) \
1710 rb_hash_aset(hash, gc_stat_symbols[gc_stat_sym_##name], SIZET2NUM(attr));
1711
1712 SET(count, objspace->gc_count);
1713 SET(moving_gc_count, objspace->moving_gc_count);
1714 SET(time, objspace->total_gc_time / (1000 * 1000));
1715 SET(total_allocated_objects, objspace->total_allocated_objects);
1716 SET(total_bytes, mmtk_total_bytes());
1717 SET(used_bytes, mmtk_used_bytes());
1718 SET(free_bytes, mmtk_free_bytes());
1719 SET(starting_heap_address, (size_t)mmtk_starting_heap_address());
1720 SET(last_heap_address, (size_t)mmtk_last_heap_address());
1721 SET(weak_references_count, mmtk_weak_references_count());
1722#undef SET
1723
1724 if (!NIL_P(key)) {
1725 // Matched key should return above
1726 return Qundef;
1727 }
1728
1729 return hash;
1730}
1731
1732VALUE
1733rb_gc_impl_stat_heap(void *objspace_ptr, VALUE heap_name, VALUE hash_or_sym)
1734{
1735 if (NIL_P(heap_name) && RB_TYPE_P(hash_or_sym, T_HASH)) {
1736 return hash_or_sym;
1737 }
1738
1739 return Qundef;
1740}
1741
1742// Miscellaneous
1743
1744#define RB_GC_OBJECT_METADATA_ENTRY_COUNT 1
1745static struct rb_gc_object_metadata_entry object_metadata_entries[RB_GC_OBJECT_METADATA_ENTRY_COUNT + 1];
1746
1748rb_gc_impl_object_metadata(void *objspace_ptr, VALUE obj)
1749{
1750 static ID ID_object_id;
1751
1752 if (!ID_object_id) {
1753#define I(s) ID_##s = rb_intern(#s);
1754 I(object_id);
1755#undef I
1756 }
1757
1758 size_t n = 0;
1759
1760#define SET_ENTRY(na, v) do { \
1761 MMTK_ASSERT(n <= RB_GC_OBJECT_METADATA_ENTRY_COUNT); \
1762 object_metadata_entries[n].name = ID_##na; \
1763 object_metadata_entries[n].val = v; \
1764 n++; \
1765} while (0)
1766
1767 if (rb_obj_id_p(obj)) SET_ENTRY(object_id, rb_obj_id(obj));
1768
1769 object_metadata_entries[n].name = 0;
1770 object_metadata_entries[n].val = 0;
1771
1772 return object_metadata_entries;
1773}
1774
1775bool
1776rb_gc_impl_live_object_p(void *objspace_ptr, const void *ptr)
1777{
1778 if (ptr == NULL) return false;
1779 if ((uintptr_t)ptr % sizeof(void*) != 0) return false;
1780 return mmtk_is_mmtk_object((MMTk_Address)ptr);
1781}
1782
1783bool
1784rb_gc_impl_garbage_object_p(void *objspace_ptr, VALUE obj)
1785{
1786 return false;
1787}
1788
1789void rb_gc_impl_set_event_hook(void *objspace_ptr, const rb_event_flag_t event) { }
1790
1791void
1792rb_gc_impl_copy_attributes(void *objspace_ptr, VALUE dest, VALUE obj)
1793{
1794 if (mmtk_object_wb_unprotected_p((MMTk_ObjectReference)obj)) {
1795 rb_gc_impl_writebarrier_unprotect(objspace_ptr, dest);
1796 }
1797
1798 rb_gc_impl_copy_finalizer(objspace_ptr, dest, obj);
1799}
1800
1801// GC Identification
1802
1803const char *
1804rb_gc_impl_active_gc_name(void)
1805{
1806 return "mmtk";
1807}
1808
1809bool
1810rb_gc_impl_during_global_gc_p(void *objspace_ptr)
1811{
1812 /* An mmtk GC is always a stop-the-world global GC. Helpers used during marking
1813 * (ractor_sync_mark and friends) read this to tell whether every mutator has
1814 * stopped. */
1815 struct objspace *objspace = objspace_ptr;
1816 return objspace->world_stopped;
1817}
1818
1819bool
1820rb_gc_impl_during_postmortem_p(void *objspace_ptr)
1821{
1822 /* mmtk has a single objspace and no per-Ractor retire collection. */
1823 return false;
1824}
1825
1826bool
1827rb_gc_impl_obj_foreign_p(void *objspace_ptr, VALUE obj)
1828{
1829 /* With a single objspace every object is our own. */
1830 return false;
1831}
1832
1833bool
1834rb_gc_impl_shref_marked_p(void *objspace_ptr, VALUE obj)
1835{
1836 /* With a single objspace there is no cross-objspace pinning to track. */
1837 return true;
1838}
1839
1840size_t
1841rb_gc_impl_heap_page_count(void *objspace_ptr)
1842{
1843 /* With a single objspace zombie_objspaces is always empty. */
1844 return 0;
1845}
1846
1847void
1848rb_gc_impl_objspace_absorb(void *dst_ptr, void *src_ptr)
1849{
1850 /* A single objspace. */
1851}
1852
1853void
1854rb_gc_impl_gc_rest(void *objspace_ptr)
1855{
1856 /* An mmtk GC completes stop-the-world: there is no in-progress incremental
1857 * mark or lazy sweep state. */
1858}
1859
1861 int (*func)(void *, void *, size_t, void *);
1862 void *data;
1863};
1864
1865static int
1866each_objects_shareable_i(void *start, void *end, size_t stride, void *d)
1867{
1868 struct each_objects_shareable_data *data = d;
1869 for (VALUE obj = (VALUE)start; obj < (VALUE)end; obj += stride) {
1871 int ret = data->func((void *)obj, (void *)(obj + stride), stride, data->data);
1872 if (ret) return ret;
1873 }
1874 }
1875 return 0;
1876}
1877
1878void
1879rb_gc_impl_each_objects_shareable(void *objspace_ptr, int (*func)(void *, void *, size_t, void *), void *data)
1880{
1881 struct each_objects_shareable_data d = { func, data };
1882 rb_gc_impl_each_objects(objspace_ptr, each_objects_shareable_i, &d);
1883}
1884
1885void
1886rb_gc_impl_each_objects_foreign(void *objspace_ptr, int (*func)(void *, void *, size_t, void *), void *data)
1887{
1888 /* With a single objspace no object lives in a foreign objspace. */
1889}
Atomic operations.
#define RUBY_ATOMIC_INC(var)
Atomically increments the value pointed by var.
Definition atomic.h:214
#define RUBY_ATOMIC_PTR_CAS(var, oldval, newval)
Identical to RUBY_ATOMIC_CAS, except it expects its arguments are void*.
Definition atomic.h:365
std::atomic< unsigned > rb_atomic_t
Type that is eligible for atomic operations.
Definition atomic.h:69
#define RUBY_ATOMIC_DEC(var)
Atomically decrements the value pointed by var.
Definition atomic.h:223
#define RUBY_ATOMIC_LOAD(var)
Atomic load.
Definition atomic.h:175
#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:1934
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:1900
#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
#define RUBY_INTERNAL_EVENT_NEWOBJ
Object allocated.
Definition event.h:93
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_SET(VALUE obj, VALUE flags)
Sets the given flag(s).
Definition fl_type.h:561
@ RUBY_FL_SHAREABLE
This flag has something to do with Ractor.
Definition fl_type.h:253
@ RUBY_FL_FINALIZE
This flag has something to do with finalisers.
Definition fl_type.h:226
@ RUBY_FL_WEAK_REFERENCE
This object weakly refers to other objects.
Definition fl_type.h:260
#define T_COMPLEX
Old name of RUBY_T_COMPLEX.
Definition value_type.h:59
#define T_STRING
Old name of RUBY_T_STRING.
Definition value_type.h:78
#define xfree
Old name of ruby_xfree.
Definition xmalloc.h:58
#define Qundef
Old name of RUBY_Qundef.
#define INT2FIX
Old name of RB_INT2FIX.
Definition long.h:48
#define T_FLOAT
Old name of RUBY_T_FLOAT.
Definition value_type.h:64
#define ID2SYM
Old name of RB_ID2SYM.
Definition symbol.h:44
#define T_BIGNUM
Old name of RUBY_T_BIGNUM.
Definition value_type.h:57
#define SPECIAL_CONST_P
Old name of RB_SPECIAL_CONST_P.
#define T_STRUCT
Old name of RUBY_T_STRUCT.
Definition value_type.h:79
#define OBJ_FREEZE
Old name of RB_OBJ_FREEZE.
Definition fl_type.h:131
#define T_NONE
Old name of RUBY_T_NONE.
Definition value_type.h:74
#define SIZET2NUM
Old name of RB_SIZE2NUM.
Definition size_t.h:62
#define xmalloc
Old name of ruby_xmalloc.
Definition xmalloc.h:53
#define FL_FINALIZE
Old name of RUBY_FL_FINALIZE.
Definition fl_type.h:61
#define T_RATIONAL
Old name of RUBY_T_RATIONAL.
Definition value_type.h:76
#define T_HASH
Old name of RUBY_T_HASH.
Definition value_type.h:65
#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 T_OBJECT
Old name of RUBY_T_OBJECT.
Definition value_type.h:75
#define NIL_P
Old name of RB_NIL_P.
#define T_SYMBOL
Old name of RUBY_T_SYMBOL.
Definition value_type.h:80
#define BUILTIN_TYPE
Old name of RB_BUILTIN_TYPE.
Definition value_type.h:85
#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 SYMBOL_P
Old name of RB_SYMBOL_P.
Definition value_type.h:88
#define T_REGEXP
Old name of RUBY_T_REGEXP.
Definition value_type.h:77
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:436
VALUE rb_equal(VALUE lhs, VALUE rhs)
This function is an optimised version of calling #==.
Definition object.c:140
#define RBIMPL_ATTR_FORMAT(x, y, z)
Wraps (or simulates) __attribute__((format))
Definition format.h:33
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
static ID rb_intern_const(const char *str)
This is a "tiny optimisation" over rb_intern().
Definition symbol.h:285
int len
Length of the buffer.
Definition io.h:8
#define RB_ULONG2NUM
Just another name of rb_ulong2num_inline.
Definition long.h:59
#define RB_GC_GUARD(v)
Prevents premature destruction of local objects.
Definition memory.h:167
#define RBIMPL_ATTR_NORETURN()
Wraps (or simulates) [[noreturn]]
Definition noreturn.h:38
#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
#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:6069
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.
C99 shim for <stdbool.h>
void * rust_closure
The pointer to the Rust-level closure object.
Definition mmtk.h:49
MMTk_ObjectClosureFunction c_function
The function to be called from C.
Definition mmtk.h:45
Ruby object's base components.
Definition rbasic.h:69
Definition gc_impl.h:34
Definition st.h:79
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