Ruby 4.1.0dev (2026-09-27 revision f6ff9e7d02e46360f8930b280a3dd921cccbda29)
default.c
1#include "ruby/internal/config.h"
2
3#include <signal.h>
4#include <string.h>
5
6#ifndef _WIN32
7# include <sys/mman.h>
8# include <unistd.h>
9# include <fcntl.h>
10# ifdef HAVE_SYS_PRCTL_H
11# include <sys/prctl.h>
12# endif
13#endif
14
15#if !defined(PAGE_SIZE) && defined(HAVE_SYS_USER_H)
16/* LIST_HEAD conflicts with sys/queue.h on macOS */
17# include <sys/user.h>
18#endif
19
20#ifdef BUILDING_MODULAR_GC
21# define nlz_int64(x) (x == 0 ? 64 : (unsigned int)__builtin_clzll((unsigned long long)x))
22# define rb_popcount_intptr(x) ((unsigned int)__builtin_popcountll((unsigned long long)(x)))
23#else
24# include "internal/bits.h"
25#endif
26
27#include "ruby/ruby.h"
28#include "ruby/atomic.h"
29#include "ruby_atomic.h"
30#include "ruby/debug.h"
31#include "ruby/thread.h"
32#include "ruby/util.h"
33#include "ruby/vm.h"
35#include "ccan/list/list.h"
36#include "darray.h"
37#include "gc/gc.h"
38#include "gc/gc_impl.h"
39#include "yjit.h"
40#include "zjit.h"
41#include "internal/static_assert.h"
42#include "internal/vm_map.h"
43
44#ifdef BUILDING_MODULAR_GC
45/* hrtime.h transitively includes internal/time.h -> internal/bits.h, which are
46 * not available to out-of-tree modular GC builds. We only use a monotonic
47 * clock plus saturating add/sub, so provide that subset locally with the same
48 * semantics as hrtime.h. */
49# include <time.h>
50typedef uint64_t rb_hrtime_t;
51# define RB_HRTIME_PER_SEC ((rb_hrtime_t)1000000000)
52
53static inline rb_hrtime_t
54rb_hrtime_now(void)
55{
56# if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
57 struct timespec ts;
58 if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) {
59 return (rb_hrtime_t)ts.tv_sec * RB_HRTIME_PER_SEC + (rb_hrtime_t)ts.tv_nsec;
60 }
61# endif
62 return 0;
63}
64
65static inline rb_hrtime_t
66rb_hrtime_add(rb_hrtime_t a, rb_hrtime_t b)
67{
68 rb_hrtime_t c = a + b;
69 return c < a ? UINT64_MAX : c; /* saturate on overflow */
70}
71
72static inline rb_hrtime_t
73rb_hrtime_sub(rb_hrtime_t a, rb_hrtime_t b)
74{
75 return a < b ? 0 : a - b;
76}
77#else
78# include "hrtime.h"
79#endif
80
81#include "probes.h"
82
83/* cl.exe's traditional preprocessor passes __VA_ARGS__ to a nested macro as
84 * a single argument; the extra expansion re-scans it into separate ones. */
85#define RUBY_DTRACE_GC_HOOK_EXPAND(expr) expr
86#define RUBY_DTRACE_GC_HOOK(name, ...) \
87 do {if (RUBY_DTRACE_GC_##name##_ENABLED()) RUBY_DTRACE_GC_HOOK_EXPAND(RUBY_DTRACE_GC_##name(__VA_ARGS__));} while (0)
88
89#if USE_ZJIT
90# include "gc/default/zjit_fastpath.h"
91#endif
92
93#ifdef BUILDING_MODULAR_GC
94# define RB_DEBUG_COUNTER_INC(_name) ((void)0)
95# define RB_DEBUG_COUNTER_INC_IF(_name, cond) (!!(cond))
96#else
97# include "debug_counter.h"
98#endif
99
100#ifdef BUILDING_MODULAR_GC
101# define rb_asan_poison_object(obj) ((void)(obj))
102# define rb_asan_unpoison_object(obj, newobj_p) ((void)(obj), (void)(newobj_p))
103# define asan_unpoisoning_object(obj) if ((obj) || true)
104# define asan_poison_memory_region(ptr, size) ((void)(ptr), (void)(size))
105# define asan_unpoison_memory_region(ptr, size, malloc_p) ((void)(ptr), (size), (malloc_p))
106# define asan_unpoisoning_memory_region(ptr, size) if ((ptr) || (size) || true)
107
108# define VALGRIND_MAKE_MEM_DEFINED(ptr, size) ((void)(ptr), (void)(size))
109# define VALGRIND_MAKE_MEM_UNDEFINED(ptr, size) ((void)(ptr), (void)(size))
110#else
111# include "internal/sanitizers.h"
112#endif
113
114/* MALLOC_HEADERS_BEGIN */
115#ifndef HAVE_MALLOC_USABLE_SIZE
116# ifdef _WIN32
117# define HAVE_MALLOC_USABLE_SIZE
118# define malloc_usable_size(a) _msize(a)
119# elif defined HAVE_MALLOC_SIZE
120# define HAVE_MALLOC_USABLE_SIZE
121# define malloc_usable_size(a) malloc_size(a)
122# endif
123#endif
124
125#ifdef HAVE_MALLOC_USABLE_SIZE
126# ifdef RUBY_ALTERNATIVE_MALLOC_HEADER
127/* Alternative malloc header is included in ruby/missing.h */
128# elif defined(HAVE_MALLOC_H)
129# include <malloc.h>
130# elif defined(HAVE_MALLOC_NP_H)
131# include <malloc_np.h>
132# elif defined(HAVE_MALLOC_MALLOC_H)
133# include <malloc/malloc.h>
134# endif
135#endif
136
137#ifdef HAVE_MALLOC_TRIM
138# include <malloc.h>
139
140# ifdef __EMSCRIPTEN__
141/* malloc_trim is defined in emscripten/emmalloc.h on emscripten. */
142# include <emscripten/emmalloc.h>
143# endif
144#endif
145
146#ifdef HAVE_MACH_TASK_EXCEPTION_PORTS
147# include <mach/task.h>
148# include <mach/mach_init.h>
149# include <mach/mach_port.h>
150#endif
151
152#ifndef RUBY_DEBUG_LOG
153# define RUBY_DEBUG_LOG(...)
154#endif
155
156#ifndef GC_HEAP_INIT_BYTES
157#define GC_HEAP_INIT_BYTES (2560 * 1024)
158#endif
159#ifndef GC_HEAP_FREE_SLOTS
160#define GC_HEAP_FREE_SLOTS 4096
161#endif
162#ifndef GC_RACTOR_HEAP_INIT_BYTES
163/* 0 is resolved at boot to the smallest size that works. */
164#define GC_RACTOR_HEAP_INIT_BYTES 0
165#endif
166#ifndef GC_HEAP_GROWTH_FACTOR
167#define GC_HEAP_GROWTH_FACTOR 1.8
168#endif
169#ifndef GC_HEAP_GROWTH_MAX_BYTES
170#define GC_HEAP_GROWTH_MAX_BYTES 0 /* 0 is disable */
171#endif
172#ifndef GC_HEAP_REMEMBERED_WB_UNPROTECTED_OBJECTS_LIMIT_RATIO
173# define GC_HEAP_REMEMBERED_WB_UNPROTECTED_OBJECTS_LIMIT_RATIO 0.01
174#endif
175#ifndef GC_HEAP_OLDOBJECT_LIMIT_FACTOR
176#define GC_HEAP_OLDOBJECT_LIMIT_FACTOR 2.0
177#endif
178
179#ifndef GC_HEAP_FREE_SLOTS_MIN_RATIO
180#define GC_HEAP_FREE_SLOTS_MIN_RATIO 0.20
181#endif
182#ifndef GC_HEAP_FREE_SLOTS_GOAL_RATIO
183#define GC_HEAP_FREE_SLOTS_GOAL_RATIO 0.40
184#endif
185#ifndef GC_HEAP_FREE_SLOTS_MAX_RATIO
186#define GC_HEAP_FREE_SLOTS_MAX_RATIO 0.65
187#endif
188
189#ifndef GC_MALLOC_LIMIT_MIN
190#define GC_MALLOC_LIMIT_MIN (16 * 1024 * 1024 /* 16MB */)
191#endif
192#ifndef GC_MALLOC_LIMIT_MAX
193#define GC_MALLOC_LIMIT_MAX (32 * 1024 * 1024 /* 32MB */)
194#endif
195#ifndef GC_MALLOC_LIMIT_GROWTH_FACTOR
196#define GC_MALLOC_LIMIT_GROWTH_FACTOR 1.4
197#endif
198
199#ifndef GC_OLDMALLOC_LIMIT_MIN
200#define GC_OLDMALLOC_LIMIT_MIN (16 * 1024 * 1024 /* 16MB */)
201#endif
202#ifndef GC_OLDMALLOC_LIMIT_GROWTH_FACTOR
203#define GC_OLDMALLOC_LIMIT_GROWTH_FACTOR 1.2
204#endif
205#ifndef GC_OLDMALLOC_LIMIT_MAX
206#define GC_OLDMALLOC_LIMIT_MAX (128 * 1024 * 1024 /* 128MB */)
207#endif
208
209#ifndef GC_MALLOC_INCREASE_LOCAL_THRESHOLD
210#define GC_MALLOC_INCREASE_LOCAL_THRESHOLD (8 * 1024 /* 8KB */)
211#endif
212
213#ifdef RB_THREAD_LOCAL_SPECIFIER
214#define USE_MALLOC_INCREASE_LOCAL 1
215static RB_THREAD_LOCAL_SPECIFIER int malloc_increase_local;
216#else
217#define USE_MALLOC_INCREASE_LOCAL 0
218#endif
219
220#ifndef GC_CAN_COMPILE_COMPACTION
221#if defined(__wasi__) /* WebAssembly doesn't support signals */
222# define GC_CAN_COMPILE_COMPACTION 0
223#else
224# define GC_CAN_COMPILE_COMPACTION 1
225#endif
226#endif
227
228#ifndef PRINT_ENTER_EXIT_TICK
229# define PRINT_ENTER_EXIT_TICK 0
230#endif
231#ifndef PRINT_ROOT_TICKS
232#define PRINT_ROOT_TICKS 0
233#endif
234
235#define USE_TICK_T (PRINT_ENTER_EXIT_TICK || PRINT_ROOT_TICKS)
236
237#ifndef HEAP_COUNT
238# if SIZEOF_VALUE >= 8
239# define HEAP_COUNT 12
240# else
241# define HEAP_COUNT 5
242# endif
243#endif
244
245/* The reciprocal table and pool_slot_sizes array are both generated from this
246 * single definition, so they can never get out of sync. */
247#if SIZEOF_VALUE >= 8
248# define EACH_POOL_SLOT_SIZE(SLOT) \
249 SLOT(32) SLOT(40) SLOT(64) SLOT(80) SLOT(96) SLOT(128) \
250 SLOT(160) SLOT(256) SLOT(512) SLOT(640) SLOT(768) SLOT(1024)
251#else
252# define EACH_POOL_SLOT_SIZE(SLOT) \
253 SLOT(32) SLOT(64) SLOT(128) SLOT(256) SLOT(512)
254#endif
255
256typedef struct {
257 size_t heap_init_bytes;
258 size_t ractor_heap_init_bytes;
259 size_t heap_free_slots;
260 double growth_factor;
261 size_t growth_max_bytes;
262
263 double heap_free_slots_min_ratio;
264 double heap_free_slots_goal_ratio;
265 double heap_free_slots_max_ratio;
266 double uncollectible_wb_unprotected_objects_limit_ratio;
267 double oldobject_limit_factor;
268
269 size_t malloc_limit_min;
270 size_t malloc_limit_max;
271 double malloc_limit_growth_factor;
272
273 size_t oldmalloc_limit_min;
274 size_t oldmalloc_limit_max;
275 double oldmalloc_limit_growth_factor;
277
278static ruby_gc_params_t gc_params = {
279 GC_HEAP_INIT_BYTES,
280 GC_RACTOR_HEAP_INIT_BYTES,
281 GC_HEAP_FREE_SLOTS,
282 GC_HEAP_GROWTH_FACTOR,
283 GC_HEAP_GROWTH_MAX_BYTES,
284
285 GC_HEAP_FREE_SLOTS_MIN_RATIO,
286 GC_HEAP_FREE_SLOTS_GOAL_RATIO,
287 GC_HEAP_FREE_SLOTS_MAX_RATIO,
288 GC_HEAP_REMEMBERED_WB_UNPROTECTED_OBJECTS_LIMIT_RATIO,
289 GC_HEAP_OLDOBJECT_LIMIT_FACTOR,
290
291 GC_MALLOC_LIMIT_MIN,
292 GC_MALLOC_LIMIT_MAX,
293 GC_MALLOC_LIMIT_GROWTH_FACTOR,
294
295 GC_OLDMALLOC_LIMIT_MIN,
296 GC_OLDMALLOC_LIMIT_MAX,
297 GC_OLDMALLOC_LIMIT_GROWTH_FACTOR,
298};
299
300/* GC_DEBUG:
301 * enable to embed GC debugging information.
302 */
303#ifndef GC_DEBUG
304#define GC_DEBUG 0
305#endif
306
307/* RGENGC_DEBUG:
308 * 1: basic information
309 * 2: remember set operation
310 * 3: mark
311 * 4:
312 * 5: sweep
313 */
314#ifndef RGENGC_DEBUG
315#ifdef RUBY_DEVEL
316#define RGENGC_DEBUG -1
317#else
318#define RGENGC_DEBUG 0
319#endif
320#endif
321#if RGENGC_DEBUG < 0 && !defined(_MSC_VER)
322# define RGENGC_DEBUG_ENABLED(level) (-(RGENGC_DEBUG) >= (level) && ruby_rgengc_debug >= (level))
323#else
324# define RGENGC_DEBUG_ENABLED(level) ((RGENGC_DEBUG) >= (level))
325#endif
326int ruby_rgengc_debug;
327
328/* RGENGC_PROFILE
329 * 0: disable RGenGC profiling
330 * 1: enable profiling for basic information
331 * 2: enable profiling for each types
332 */
333#ifndef RGENGC_PROFILE
334# define RGENGC_PROFILE 0
335#endif
336
337/* RGENGC_ESTIMATE_OLDMALLOC
338 * Enable/disable to estimate increase size of malloc'ed size by old objects.
339 * If estimation exceeds threshold, then will invoke full GC.
340 * 0: disable estimation.
341 * 1: enable estimation.
342 */
343#ifndef RGENGC_ESTIMATE_OLDMALLOC
344# define RGENGC_ESTIMATE_OLDMALLOC 1
345#endif
346
347#ifndef GC_PROFILE_MORE_DETAIL
348# define GC_PROFILE_MORE_DETAIL 0
349#endif
350#ifndef GC_PROFILE_DETAIL_MEMORY
351# define GC_PROFILE_DETAIL_MEMORY 0
352#endif
353#ifndef GC_ENABLE_LAZY_SWEEP
354# define GC_ENABLE_LAZY_SWEEP 1
355#endif
356
357#ifndef VERIFY_FREE_SIZE
358#if RUBY_DEBUG
359#define VERIFY_FREE_SIZE 1
360#else
361#define VERIFY_FREE_SIZE 0
362#endif
363#endif
364
365#if VERIFY_FREE_SIZE
366#undef CALC_EXACT_MALLOC_SIZE
367#define CALC_EXACT_MALLOC_SIZE 1
368#endif
369
370#ifndef CALC_EXACT_MALLOC_SIZE
371# define CALC_EXACT_MALLOC_SIZE 0
372#endif
373
374#if defined(HAVE_MALLOC_USABLE_SIZE) || CALC_EXACT_MALLOC_SIZE > 0
375# ifndef MALLOC_ALLOCATED_SIZE
376# define MALLOC_ALLOCATED_SIZE 0
377# endif
378#else
379# define MALLOC_ALLOCATED_SIZE 0
380#endif
381#ifndef MALLOC_ALLOCATED_SIZE_CHECK
382# define MALLOC_ALLOCATED_SIZE_CHECK 0
383#endif
384
385#ifndef GC_DEBUG_STRESS_TO_CLASS
386# define GC_DEBUG_STRESS_TO_CLASS RUBY_DEBUG
387#endif
388
389typedef enum {
390 GPR_FLAG_NONE = 0x000,
391 /* major reason */
392 GPR_FLAG_MAJOR_BY_NOFREE = 0x001,
393 GPR_FLAG_MAJOR_BY_OLDGEN = 0x002,
394 GPR_FLAG_MAJOR_BY_SHADY = 0x004,
395 GPR_FLAG_MAJOR_BY_FORCE = 0x008,
396#if RGENGC_ESTIMATE_OLDMALLOC
397 GPR_FLAG_MAJOR_BY_OLDMALLOC = 0x020,
398#endif
399 GPR_FLAG_MAJOR_MASK = 0x0ff,
400
401 /* gc reason */
402 GPR_FLAG_NEWOBJ = 0x100,
403 GPR_FLAG_MALLOC = 0x200,
404 GPR_FLAG_METHOD = 0x400,
405 GPR_FLAG_CAPI = 0x800,
406 GPR_FLAG_STRESS = 0x1000,
407
408 /* others */
409 GPR_FLAG_IMMEDIATE_SWEEP = 0x2000,
410 GPR_FLAG_HAVE_FINALIZE = 0x4000,
411 GPR_FLAG_IMMEDIATE_MARK = 0x8000,
412 GPR_FLAG_FULL_MARK = 0x10000,
413 GPR_FLAG_COMPACT = 0x20000,
414 GPR_FLAG_GLOBAL = 0x40000,
415
416 GPR_DEFAULT_REASON =
417 (GPR_FLAG_FULL_MARK | GPR_FLAG_IMMEDIATE_MARK |
418 GPR_FLAG_IMMEDIATE_SWEEP | GPR_FLAG_CAPI),
419} gc_profile_record_flag;
420
421typedef struct gc_profile_record {
422 unsigned int flags;
423 size_t sequence;
424
425 double gc_time;
426 double gc_invoke_time;
427 rb_hrtime_t gc_wall_time;
428 rb_hrtime_t gc_invoke_wall_time;
429 rb_hrtime_t gc_pause_time;
430 rb_hrtime_t gc_stop_time;
431 rb_hrtime_t gc_stw_time;
432 rb_hrtime_t gc_mark_wall_time;
433 rb_hrtime_t gc_sweep_wall_time;
434 rb_hrtime_t gc_compact_wall_time;
435
436 size_t heap_total_objects;
437 size_t heap_use_size;
438 size_t heap_total_size;
439 size_t moved_objects;
440
441#if GC_PROFILE_MORE_DETAIL
442 double gc_mark_time;
443 double gc_sweep_time;
444
445 size_t heap_use_pages;
446 size_t heap_live_objects;
447 size_t heap_free_objects;
448
449 size_t allocate_increase;
450 size_t allocate_limit;
451
452 double prepare_time;
453 size_t removing_objects;
454 size_t empty_objects;
455#if GC_PROFILE_DETAIL_MEMORY
456 long maxrss;
457 long minflt;
458 long majflt;
459#endif
460#endif
461#if MALLOC_ALLOCATED_SIZE
462 size_t allocated_size;
463#endif
464
465#if RGENGC_PROFILE > 0
466 size_t old_objects;
467 size_t remembered_normal_objects;
468 size_t remembered_shady_objects;
469#endif
471
472struct RMoved {
473 VALUE flags;
474 VALUE dummy;
475 VALUE destination;
476};
477
478#define RMOVED(obj) ((struct RMoved *)(obj))
479
480typedef uintptr_t bits_t;
481enum {
482 BITS_SIZE = sizeof(bits_t),
483 BITS_BITLENGTH = ( BITS_SIZE * CHAR_BIT )
484};
485
487 struct heap_page *page;
488};
489
491 struct heap_page_header header;
492 /* char gap[]; */
493 /* RVALUE values[]; */
494};
495
496#define STACK_CHUNK_SIZE 500
497
498typedef struct stack_chunk {
499 VALUE data[STACK_CHUNK_SIZE];
500 struct stack_chunk *next;
502
503typedef struct mark_stack {
504 stack_chunk_t *chunk;
505 stack_chunk_t *cache;
506 int index;
507 int limit;
508 size_t cache_size;
509 size_t unused_cache_size;
511
512typedef int (*gc_compact_compare_func)(const void *l, const void *r, void *d);
513
514typedef struct rb_heap_newobj {
515 uintptr_t alloc_cursor;
516 uintptr_t alloc_cursor_end;
517 struct free_region *alloc_next_region;
518 struct heap_page *alloc_using_page;
520
521typedef struct rb_heap_struct {
522 short slot_size;
523
524 /* Basic statistics */
525 size_t total_allocated_pages;
526 size_t force_major_gc_count;
527 size_t force_incremental_marking_finish_count;
528 size_t total_allocated_objects;
529 size_t total_freed_objects;
530 size_t final_slots_count;
531
532 /* Sweeping statistics */
533 size_t freed_slots;
534 size_t empty_slots;
535
536 /* Bump-pointer allocation state; only this objspace's owner thread writes it. */
537 rb_heap_newobj_t newobj;
538
539 struct heap_page *free_pages;
540 struct ccan_list_head pages;
541 struct heap_page *sweeping_page; /* iterator for .pages */
542 struct heap_page *compact_cursor;
543 uintptr_t compact_cursor_index;
544 struct heap_page *pooled_pages;
545 size_t total_pages; /* total page count in a heap */
546 size_t total_slots; /* total slot count */
547
548} rb_heap_t;
549
550enum {
551 gc_stress_no_major,
552 gc_stress_no_immediate_sweep,
553 gc_stress_full_mark_after_malloc,
554 gc_stress_max
555};
556
557enum gc_mode {
558 gc_mode_none,
559 gc_mode_marking,
560 gc_mode_sweeping,
561 gc_mode_compacting,
562};
563
564typedef rbimpl_atomic_uint64_t gc_counter_t;
565
566#if !defined(HAVE_GCC_ATOMIC_BUILTINS_64) && !defined(_WIN32) && \
567 !(defined(__sun) && defined(HAVE_ATOMIC_H) && (defined(_LP64) || defined(_I32LPx)))
568# define MALLOC_COUNTERS_NEED_LOCK 1
569#endif
570
572 gc_counter_t malloc;
573 gc_counter_t free;
574
575 /* Baselines the increase is measured from: malloc snapshotted at GC start
576 * (gc_reset_malloc_info), free re-snapshotted at gc_sweep_finish so the
577 * sweep's own frees never count. */
578 gc_counter_t malloc_at_last_gc;
579 gc_counter_t free_at_last_gc;
580};
581
582/* -- Deferred free of non-thread-safe T_DATA --
583 *
584 * A dead T_DATA not RUBY_TYPED_THREAD_SAFE_FREE cannot have its dfree run during a parallel
585 * local sweep: the dfree is extension code that may touch process state other Ractors are using,
586 * so it needs the world stopped (not merely serialization against other dfrees). The sweep
587 * therefore copies out what the free needs, reclaims the slot immediately, and the dfrees are
588 * called later under a VM barrier. Such a type is never embedded, so the payload always outlives
589 * the slot.
590 */
591#define TDATA_UNSAFE_FREE_CHUNK_CAPA 32
592/* Drained chunks kept for reuse; the rest are freed. */
593#define TDATA_UNSAFE_FREE_CACHE_MAX 64
594
596 void (*dfree)(void *);
597 void *data;
598};
599
601 struct tdata_unsafe_free_chunk *next;
602 unsigned int count;
603 uint32_t embed_xfree_bits;
604 struct tdata_unsafe_free_entry entries[TDATA_UNSAFE_FREE_CHUNK_CAPA];
605};
606STATIC_ASSERT(tdata_unsafe_free_bits_cover_chunk,
607 TDATA_UNSAFE_FREE_CHUNK_CAPA <= 32);
608
610 uint32_t count;
611 uint32_t minor_gc_count;
612 uint32_t major_gc_count;
613 uint64_t marking_time_ns;
614 uint64_t sweeping_time_ns;
615};
616
618 uint64_t count;
619 uint64_t minor_gc_count;
620 uint64_t major_gc_count;
621 uint64_t marking_time_ns;
622 uint64_t sweeping_time_ns;
623};
624
625typedef struct rb_objspace {
626 struct {
627 struct gc_malloc_bytes counters;
628#if RGENGC_ESTIMATE_OLDMALLOC
629 struct gc_malloc_bytes oldcounters;
630#endif
631#ifdef MALLOC_COUNTERS_NEED_LOCK
632 rb_nativethread_lock_t lock;
633#endif
634 } malloc_counters;
635
636 struct {
637 size_t limit;
638#if MALLOC_ALLOCATED_SIZE
639 size_t allocated_size;
640 size_t allocations;
641#endif
642 } malloc_params;
643
645 bool full_mark;
646 } gc_config;
647
648 struct {
649 unsigned int mode : 2;
650 unsigned int immediate_sweep : 1;
651 unsigned int dont_gc : 1;
652 /* A user hold from GC.disable (kept in vm->gc.disable_holders); owner thread only. */
653 unsigned int user_gc_disabled : 1;
654 unsigned int dont_incremental : 1;
655 unsigned int during_gc : 1;
656 unsigned int during_global_gc : 1;
657 unsigned int during_compacting : 1;
658 unsigned int gc_lock_barrier : 1;
659 unsigned int during_reference_updating : 1;
660 unsigned int during_minor_gc : 1;
661 unsigned int during_incremental_marking : 1;
662 unsigned int during_postmortem : 1;
663 unsigned int measure_gc : 1;
664 } flags;
665
666 rb_event_flag_t hook_events;
667
668 rb_heap_t heaps[HEAP_COUNT];
669 size_t empty_pages_count;
670 struct heap_page *empty_pages;
671
672 struct {
673 rb_atomic_t finalizing;
674 } atomic_flags;
675
677 size_t marked_slots;
678
679 /* Moved out of the per-Ractor newobj cache: allocation state is per objspace. */
680 size_t incremental_mark_step_allocated_slots;
681
682 /* Inputs of the global GC trigger, all owned by this objspace's thread.
683 * shareable_objects is the live population of shareable objects; exceeding the
684 * limit requests a global GC. */
685 size_t shareable_objects;
686 size_t shareable_objects_limit;
687 /* Whether the last mark ran the pinned walk; the sweep asserts on it. */
688 unsigned char last_cycle_pinned;
689
690 struct {
691 rb_darray(struct heap_page *) sorted;
692
693 size_t allocated_pages;
694 size_t freed_pages;
695 uintptr_t range[2];
696 size_t freeable_pages;
697
698 size_t allocatable_bytes;
699
700 /* final */
701 VALUE deferred_final;
702 } heap_pages;
703
704 st_table *finalizer_table;
705
706 struct {
707 int run;
708 unsigned int latest_gc_info;
709 gc_profile_record *records;
710 gc_profile_record *current_record;
711 size_t next_index;
712 size_t size;
713 size_t record_count;
714 size_t max_records;
715 size_t record_sequence;
716
717#if GC_PROFILE_MORE_DETAIL
718 double prepare_time;
719#endif
720 double invoke_time;
721 rb_hrtime_t invoke_wall_time;
722
723 size_t minor_gc_count;
724 size_t major_gc_count;
725 size_t compact_count;
726 size_t read_barrier_faults;
727#if RGENGC_PROFILE > 0
728 size_t total_generated_normal_object_count;
729 size_t total_generated_shady_object_count;
730 size_t total_shade_operation_count;
731 size_t total_promoted_count;
732 size_t total_remembered_normal_object_count;
733 size_t total_remembered_shady_object_count;
734
735#if RGENGC_PROFILE >= 2
736 size_t generated_normal_object_count_types[RUBY_T_MASK];
737 size_t generated_shady_object_count_types[RUBY_T_MASK];
738 size_t shade_operation_count_types[RUBY_T_MASK];
739 size_t promoted_types[RUBY_T_MASK];
740 size_t remembered_normal_object_count_types[RUBY_T_MASK];
741 size_t remembered_shady_object_count_types[RUBY_T_MASK];
742#endif
743#endif /* RGENGC_PROFILE */
744
745 /* temporary profiling space */
746 double gc_sweep_start_time;
747 rb_hrtime_t gc_wall_start_time;
748 rb_hrtime_t gc_sweep_wall_start_time;
749 rb_hrtime_t gc_sweep_excluded_wall_time;
750 rb_hrtime_t gc_pause_start_time;
751 rb_hrtime_t gc_stw_start_time;
752 rb_hrtime_t gc_stop_time;
753 rb_hrtime_t gc_mark_phase_wall_start_time;
754 rb_hrtime_t gc_sweep_phase_wall_start_time;
755#if GC_PROFILE_MORE_DETAIL
756 size_t total_allocated_objects_at_gc_start;
757 size_t heap_used_at_gc_start;
758#endif
759
760 /* basic statistics */
761 size_t count;
762 unsigned long long marking_time_ns;
763 struct timespec marking_start_time;
764 unsigned long long sweeping_time_ns;
765 struct timespec sweeping_start_time;
766
767 /* Weak references */
768 size_t weak_references_count;
769 } profile;
770
771
772 struct {
773 bool parent_object_old_p;
774 VALUE parent_object;
775
776 int need_major_gc;
777 size_t last_major_gc;
778 size_t uncollectible_wb_unprotected_objects;
779 size_t uncollectible_wb_unprotected_objects_limit;
780 size_t old_objects;
781 size_t old_objects_limit;
782
783#if RGENGC_ESTIMATE_OLDMALLOC
784 size_t oldmalloc_increase_limit;
785#endif
786
787#if RGENGC_CHECK_MODE >= 2
788 struct st_table *allrefs_table;
789 size_t error_count;
790#endif
791 } rgengc;
792
793 struct {
794 size_t considered_count_table[T_MASK];
795 size_t moved_count_table[T_MASK];
796 size_t moved_up_count_table[T_MASK];
797 size_t moved_down_count_table[T_MASK];
798 size_t total_moved;
799
800 /* This function will be used, if set, to sort the heap prior to compaction */
801 gc_compact_compare_func compare_func;
802 } rcompactor;
803
804 struct {
805 size_t pooled_slots;
806 size_t step_slots;
807 } rincgc;
808
809#if GC_DEBUG_STRESS_TO_CLASS
810 VALUE stress_to_class;
811#endif
812
813 rb_darray(VALUE) weak_references;
814 rb_postponed_job_handle_t finalize_deferred_pjob;
815
816 /* Partially filled chunk of deferred non-thread-safe T_DATA metadata. */
818
819 int sweeping_heap_count;
820
821 int fork_vm_lock_lev;
822
823 struct rb_gc_vm_context vm_context;
824
825 /* Process-wide GC statistics publication. Default GC only: other
826 * implementations reject GC.stat(scope: :global) and never initialize
827 * this lock. */
828 struct {
829 rb_nativethread_lock_t lock;
830 struct gc_process_stat_snapshot published;
831 } process_stat;
833
834/* The one VM-global GC structure; for now it only holds the page pool. Page bodies are
835 * carved out of large mmap arenas and reused via a process-wide freelist (per-page
836 * mmap/munmap would serialize on the kernel's mmap_lock). Leaf lock: no alloc, no GC. */
837typedef struct rb_global_objspace {
838 struct {
839 rb_nativethread_lock_t lock;
840 struct heap_page_body *hot_list; /* ≤ PAGE_POOL_HOT_MAX un-advised bodies; link at body offset 0 */
841 int hot_count;
842 size_t os_page_size; /* sysconf(_SC_PAGE_SIZE), cached at init */
843 /* List of mmap'd memory regions (arenas) for page bodies. */
844 struct page_arena {
845 struct page_arena *next;
846 char *start; /* usable area, HEAP_PAGE_ALIGN aligned */
847 size_t size; /* usable bytes (a multiple of HEAP_PAGE_SIZE) */
848 struct heap_page_body *cold_freelist; /* free bodies of this arena; link at body offset 0 */
849 int free_count; /* bodies of this arena currently free (hot list + cold_freelist) */
850 int cold_count; /* bodies on cold_freelist (subset of free_count) */
851 } *arenas; /* every arena, newest first */
852 char *arena_cursor; /* first body not yet carved out of the newest arena */
853 char *arena_end; /* end of current arena */
854 struct page_arena *arena_current; /* arena that arena_cursor carves from */
855 int arena_count; /* current mapped arenas (for GC.stat total_pages) */
856 int advised_count; /* page bodies with MADV_* applied (for GC.stat discarded pages) */
857 size_t arenas_unmapped; /* cumulative arenas munmapped (for GC.stat) */
858 } page_pool;
859
860 /* Zombie pages left after the last global cycle (roughly the live data). Updated
861 * under the barrier; readers (gc_need_global_p) may be racy. */
862 size_t zombie_pages_survivors;
863
864 /* An objspace merge (objspace_absorb) is running: suppress the cross-objspace
865 * verifier while the graph is in flux. Written by the absorbing thread, read by
866 * verification with the world stopped. */
867 bool during_absorb;
868
869 /* main's objspace, for gc_enter's locking policy. main_ractor->objspace is swapped
870 * during Ractor creation; this stable pointer decides the same way at both ends of a
871 * GC. Set at boot, re-pointed in a forked child. */
872 rb_objspace_t *main_objspace;
873
874 /* GC.stress is process-global (upstream semantics). Written by GC.stress= in any
875 * Ractor (rare) and read on every Ractor's alloc and GC path; it is diagnostic, so
876 * plain store/load with last-writer-wins is fine. */
877 bool gc_stressful;
878 VALUE gc_stress_mode;
879
880 /* Global GC driver state. compacting is true during the move phase: reference
881 * updates are deferred until all forwarding exists, so a cross-objspace reference is
882 * rewritten exactly once. objspaces is the snapshot being collected. */
883 struct {
884 bool compacting;
885 struct rb_objspace **objspaces;
886 size_t n_objspaces, objspaces_capa;
887 size_t count;
888 } global_gc;
889
890 /* Index of every objspace's heap pages, ordered by body address. Writers (page
891 * alloc/free) serialize on page_pool.lock; the only reader is a stop-the-world global
892 * GC, so reads need no lock. A local GC uses its own heap_pages.sorted. */
893 struct {
894 struct heap_page **pages;
895 size_t n_pages, capa;
896 uintptr_t lomem, himem;
897 } page_index;
898
899 rb_postponed_job_handle_t tdata_deferred_free_pjob; /* atomic */
900
901 /* Pending count of deferred non-thread-safe T_DATA frees across all objspaces:
902 * bumped as each one is deferred, reset to 0 by the drain. Crossing
903 * TDATA_DEFERRED_FREE_THRESHOLD triggers the postponed job. */
904 size_t tdata_deferred_free_count; /* atomic */
905
906 /* Full chunks awaiting a drain (CAS stack), and drained chunks kept for reuse. */
907 struct tdata_unsafe_free_chunk *tdata_unsafe_free_published; /* atomic */
908 struct tdata_unsafe_free_chunk *tdata_unsafe_free_cache; /* atomic */
909 size_t tdata_unsafe_free_cache_len; /* atomic */
910
911 /* Archive of destroyed objspaces' final statistics, added once on absorption. */
912 struct gc_process_stat_total process_stat_archive;
914
915static rb_global_objspace_t rb_global_objspace_instance;
916static rb_global_objspace_t *global_objspace = NULL;
917
918/* Relaxed: every reader only asks whether a drain is worth arranging, and the drain
919 * itself stops the world before it touches a chunk. There is no atomic size_t load, so
920 * go through the VALUE one (both are word sized). */
921static inline size_t
922tdata_deferred_free_count_load(void)
923{
924 return (size_t)rbimpl_atomic_value_load(
925 (volatile VALUE *)&global_objspace->tdata_deferred_free_count, RBIMPL_ATOMIC_RELAXED);
926}
927
928/* The floor keeps a global GC from running as soon as a few shareable objects appear;
929 * the factor follows the rule used for the old-generation limit. */
930#define SHAREABLE_OBJECTS_LIMIT_MIN (1 << 16)
931#define SHAREABLE_OBJECTS_LIMIT_FACTOR 2.0
932/* Start a global GC once terminated, uninherited Ractors hold this many heap pages. A
933 * small Ractor's objspace is about 13 pages, so discarding many of them still stays
934 * below it, while a single fat zombie crosses it. */
935#define ZOMBIE_PAGES_TRIGGER 256
936/* Trigger the deferred T_DATA free postponed job once this many have accumulated
937 * across all objspaces. */
938#define TDATA_DEFERRED_FREE_THRESHOLD (1 << 15)
939
940static void objspace_absorb(rb_objspace_t *dst, rb_objspace_t *src);
941
942
943static struct heap_page_body *page_pool_acquire(struct page_arena **arena_out);
944static void page_pool_release(struct heap_page_body *body, struct page_arena *arena);
945#ifdef HAVE_MMAP
946static void page_pool_release_locked(struct heap_page_body *body, struct page_arena *arena);
947#endif
948static void page_pool_reclaim(rb_global_objspace_t *g);
949
950#if RGENGC_CHECK_MODE && !defined(_WIN32) && !defined(__wasi__) && defined(HAVE_PTHREAD_H)
951# define PAGE_POOL_LOCK_ERRORCHECK 1
952#endif
953
954static void
955page_pool_lock_initialize(rb_nativethread_lock_t *lock)
956{
957#ifdef PAGE_POOL_LOCK_ERRORCHECK
958 pthread_mutexattr_t attr;
959 pthread_mutexattr_init(&attr);
960 pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
961 pthread_mutex_init(lock, &attr);
962 pthread_mutexattr_destroy(&attr);
963#else
965#endif
966}
967
968static void
969global_objspace_init(void)
970{
971 if (global_objspace == NULL) {
972 rb_global_objspace_t *g = &rb_global_objspace_instance;
973 page_pool_lock_initialize(&g->page_pool.lock);
974 g->page_pool.hot_list = NULL;
975 g->page_pool.hot_count = 0;
976 g->page_pool.arenas = NULL;
977 g->page_pool.arena_cursor = NULL;
978 g->page_pool.arena_end = NULL;
979 g->page_pool.arena_current = NULL;
980 g->page_pool.arena_count = 0;
981 g->page_pool.advised_count = 0;
982 g->tdata_deferred_free_pjob = POSTPONED_JOB_HANDLE_INVALID;
983 g->tdata_unsafe_free_published = NULL;
984 g->tdata_unsafe_free_cache = NULL;
985 g->tdata_unsafe_free_cache_len = 0;
986 g->page_pool.arenas_unmapped = 0;
987#ifdef HAVE_MMAP
988 g->page_pool.os_page_size = sysconf(_SC_PAGE_SIZE);
989#else
990 g->page_pool.os_page_size = 0;
991#endif
992 global_objspace = g;
993 }
994}
995
996#ifndef HEAP_PAGE_ALIGN_LOG
997/* default tiny heap size: 64KiB */
998#define HEAP_PAGE_ALIGN_LOG 16
999#endif
1000
1001#if GC_DEBUG
1002struct rvalue_overhead {
1003 const char *file;
1004 int line;
1005};
1006
1007// Make sure that RVALUE_OVERHEAD aligns to sizeof(VALUE)
1008# define RVALUE_OVERHEAD (sizeof(struct { \
1009 union { \
1010 struct rvalue_overhead overhead; \
1011 VALUE value; \
1012 }; \
1013}))
1014size_t rb_gc_impl_obj_slot_size(VALUE obj);
1015# define GET_RVALUE_OVERHEAD(obj) ((struct rvalue_overhead *)((uintptr_t)obj + rb_gc_impl_obj_slot_size(obj)))
1016#else
1017# ifndef RVALUE_OVERHEAD
1018# define RVALUE_OVERHEAD 0
1019# endif
1020#endif
1021
1022#define RVALUE_SLOT_SIZE (sizeof(struct RBasic) + sizeof(VALUE[RBIMPL_RVALUE_EMBED_LEN_MAX]) + RVALUE_OVERHEAD)
1023
1024static const size_t pool_slot_sizes[HEAP_COUNT] = {
1025#define SLOT(size) ((size) + RVALUE_OVERHEAD),
1026 EACH_POOL_SLOT_SIZE(SLOT)
1027#undef SLOT
1028};
1029
1030/* An init size below one slot in the largest heap never forces that heap's first
1031 * page, and allocating there then fails with "cannot create a new page after GC". */
1032static inline size_t
1033heap_init_bytes_min(void)
1034{
1035 return pool_slot_sizes[HEAP_COUNT - 1];
1036}
1037
1038/* Precomputed reciprocals for fast slot index calculation.
1039 * For slot size d: reciprocal = ceil(2^48 / d).
1040 * Then offset / d == (uint32_t)((offset * reciprocal) >> 48)
1041 * for all offset < HEAP_PAGE_SIZE. */
1042#define SLOT_RECIPROCAL_SHIFT 48
1043#define SLOT_RECIPROCAL(size) (((1ULL << SLOT_RECIPROCAL_SHIFT) + (size) - 1) / (size))
1044
1045static const uint64_t heap_slot_reciprocal_table[HEAP_COUNT] = {
1046#define SLOT(size) SLOT_RECIPROCAL((size) + RVALUE_OVERHEAD),
1047 EACH_POOL_SLOT_SIZE(SLOT)
1048#undef SLOT
1049};
1050
1051#if SIZEOF_VALUE >= 8
1052static uint8_t size_to_heap_idx[1024 / 8 + 1];
1053#else
1054static uint8_t size_to_heap_idx[512 / 8 + 1];
1055#endif
1056
1057#ifndef MAX
1058# define MAX(a, b) (((a) > (b)) ? (a) : (b))
1059#endif
1060#ifndef MIN
1061# define MIN(a, b) (((a) < (b)) ? (a) : (b))
1062#endif
1063#define roomof(x, y) (((x) + (y) - 1) / (y))
1064#define CEILDIV(i, mod) roomof(i, mod)
1065#define MIN_POOL_SLOT_SIZE 32
1066enum {
1067 HEAP_PAGE_ALIGN = (1UL << HEAP_PAGE_ALIGN_LOG),
1068 HEAP_PAGE_ALIGN_MASK = (~(~0UL << HEAP_PAGE_ALIGN_LOG)),
1069 HEAP_PAGE_SIZE = HEAP_PAGE_ALIGN,
1070 HEAP_PAGE_BITMAP_LIMIT = CEILDIV(CEILDIV(HEAP_PAGE_SIZE, MIN_POOL_SLOT_SIZE), BITS_BITLENGTH),
1071 HEAP_PAGE_BITMAP_SIZE = (BITS_SIZE * HEAP_PAGE_BITMAP_LIMIT),
1072};
1073#define HEAP_PAGE_ALIGN (1 << HEAP_PAGE_ALIGN_LOG)
1074#define HEAP_PAGE_SIZE HEAP_PAGE_ALIGN
1075
1076#if !defined(INCREMENTAL_MARK_STEP_ALLOCATIONS)
1077# define INCREMENTAL_MARK_STEP_ALLOCATIONS 500
1078#endif
1079
1080#undef INIT_HEAP_PAGE_ALLOC_USE_MMAP
1081/* Must define either HEAP_PAGE_ALLOC_USE_MMAP or
1082 * INIT_HEAP_PAGE_ALLOC_USE_MMAP. */
1083
1084#ifndef HAVE_MMAP
1085/* We can't use mmap of course, if it is not available. */
1086static const bool HEAP_PAGE_ALLOC_USE_MMAP = false;
1087
1088#elif defined(__wasm__)
1089/* wasmtime does not have proper support for mmap.
1090 * See https://github.com/bytecodealliance/wasmtime/blob/main/docs/WASI-rationale.md#why-no-mmap-and-friends
1091 */
1092static const bool HEAP_PAGE_ALLOC_USE_MMAP = false;
1093
1094#elif HAVE_CONST_PAGE_SIZE
1095/* If we have the PAGE_SIZE and it is a constant, then we can directly use it. */
1096static const bool HEAP_PAGE_ALLOC_USE_MMAP = (PAGE_SIZE <= HEAP_PAGE_SIZE);
1097
1098#elif defined(PAGE_MAX_SIZE) && (PAGE_MAX_SIZE <= HEAP_PAGE_SIZE)
1099/* If we can use the maximum page size. */
1100static const bool HEAP_PAGE_ALLOC_USE_MMAP = true;
1101
1102#elif defined(PAGE_SIZE)
1103/* If the PAGE_SIZE macro can be used dynamically. */
1104# define INIT_HEAP_PAGE_ALLOC_USE_MMAP (PAGE_SIZE <= HEAP_PAGE_SIZE)
1105
1106#elif defined(HAVE_SYSCONF) && defined(_SC_PAGE_SIZE)
1107/* If we can use sysconf to determine the page size. */
1108# define INIT_HEAP_PAGE_ALLOC_USE_MMAP (sysconf(_SC_PAGE_SIZE) <= HEAP_PAGE_SIZE)
1109
1110#else
1111/* Otherwise we can't determine the system page size, so don't use mmap. */
1112static const bool HEAP_PAGE_ALLOC_USE_MMAP = false;
1113#endif
1114
1115#ifdef INIT_HEAP_PAGE_ALLOC_USE_MMAP
1116/* We can determine the system page size at runtime. */
1117# define HEAP_PAGE_ALLOC_USE_MMAP (heap_page_alloc_use_mmap != false)
1118
1119static bool heap_page_alloc_use_mmap;
1120#endif
1121
1122#define RVALUE_AGE_BIT_COUNT 2
1123#define RVALUE_AGE_BIT_MASK (((bits_t)1 << RVALUE_AGE_BIT_COUNT) - 1)
1124#define RVALUE_OLD_AGE 3
1125
1127 VALUE flags; /* always 0 for freed obj */
1128 uintptr_t end; /* exclusive end address of the run */
1129 struct free_region *next; /* next free region in the page */
1130};
1131
1133 /* Cache line 0: allocation fast path + SLOT_INDEX */
1134 struct free_region *free_region;
1135 uintptr_t start;
1136 uint64_t slot_size_reciprocal;
1137 unsigned short slot_size;
1138 unsigned short total_slots;
1139 unsigned short free_slots;
1140 unsigned short final_slots;
1141 unsigned short pinned_slots;
1142 /* Page state flags. A bitfield is safe: the only writers are the owning Ractor
1143 * (GVL) and the global GC driver (stop-the-world), never together. has_shareable /
1144 * has_shref hint that the page holds at least one such bit, for re-scanning. */
1145 struct {
1146 unsigned int before_sweep : 1;
1147 unsigned int has_remembered_objects : 1;
1148 unsigned int has_uncollectible_wb_unprotected_objects : 1;
1149 unsigned int has_shref_objects : 1;
1150 unsigned int has_shareable_objects : 1;
1151 } flags;
1152
1153 rb_heap_t *heap;
1154
1155 /* The objspace owning this page, so any object's owner is one load away
1156 * (GET_HEAP_OBJSPACE). Rewritten only when a page changes owner (inheritance). */
1158
1159 struct heap_page *free_next;
1160 struct heap_page_body *body;
1161 struct page_arena *arena;
1162 struct ccan_list_node page_node;
1163
1164 bits_t wb_unprotected_bits[HEAP_PAGE_BITMAP_LIMIT];
1165 /* the following three bitmaps are cleared at the beginning of full GC */
1166 bits_t mark_bits[HEAP_PAGE_BITMAP_LIMIT];
1167 bits_t uncollectible_bits[HEAP_PAGE_BITMAP_LIMIT];
1168 bits_t marking_bits[HEAP_PAGE_BITMAP_LIMIT];
1169
1170 bits_t remembered_bits[HEAP_PAGE_BITMAP_LIMIT];
1171
1172 /* Two extra bits per object. shareable_bits: what a local sweep must never free
1173 * (only a global GC decides a shareable object is dead); set at creation and by
1174 * rb_gc_impl_obj_became_shareable. shref_bits: an unshareable object referenced
1175 * from a shareable one, a local GC root; the write barrier maintains it. */
1176 bits_t shareable_bits[HEAP_PAGE_BITMAP_LIMIT];
1177 bits_t shref_bits[HEAP_PAGE_BITMAP_LIMIT];
1178
1179 /* If set, the object is not movable */
1180 bits_t pinned_bits[HEAP_PAGE_BITMAP_LIMIT];
1181 bits_t age_bits[HEAP_PAGE_BITMAP_LIMIT * RVALUE_AGE_BIT_COUNT];
1182};
1183
1184/*
1185 * When asan is enabled, this will prohibit writing to the freelist until it is unlocked
1186 */
1187static void
1188asan_lock_freelist(struct heap_page *page)
1189{
1190 asan_poison_memory_region(&page->free_region, sizeof(struct free_region *));
1191}
1192
1193/*
1194 * When asan is enabled, this will enable the ability to write to the freelist
1195 */
1196static void
1197asan_unlock_freelist(struct heap_page *page)
1198{
1199 asan_unpoison_memory_region(&page->free_region, sizeof(struct free_region *), false);
1200}
1201
1202static inline bool
1203heap_page_in_global_empty_pages_pool(rb_objspace_t *objspace, struct heap_page *page)
1204{
1205 if (page->total_slots == 0) {
1206 GC_ASSERT(page->start == 0);
1207 GC_ASSERT(page->slot_size == 0);
1208 GC_ASSERT(page->heap == NULL);
1209 GC_ASSERT(page->free_slots == 0);
1210 asan_unpoisoning_memory_region(&page->free_region, sizeof(&page->free_region)) {
1211 GC_ASSERT(page->free_region == NULL);
1212 }
1213
1214 return true;
1215 }
1216 else {
1217 GC_ASSERT(page->start != 0);
1218 GC_ASSERT(page->slot_size != 0);
1219 GC_ASSERT(page->heap != NULL);
1220
1221 return false;
1222 }
1223}
1224
1225#define GET_PAGE_BODY(x) ((struct heap_page_body *)((bits_t)(x) & ~(HEAP_PAGE_ALIGN_MASK)))
1226#define GET_PAGE_HEADER(x) (&GET_PAGE_BODY(x)->header)
1227#define GET_HEAP_PAGE(x) (GET_PAGE_HEADER(x)->page)
1228
1229static inline size_t
1230slot_index_for_offset(size_t offset, uint64_t reciprocal)
1231{
1232 return (uint32_t)(((uint64_t)offset * reciprocal) >> SLOT_RECIPROCAL_SHIFT);
1233}
1234
1235#define SLOT_INDEX(page, p) slot_index_for_offset((uintptr_t)(p) - (page)->start, (page)->slot_size_reciprocal)
1236#define SLOT_BITMAP_INDEX(page, p) (SLOT_INDEX(page, p) / BITS_BITLENGTH)
1237#define SLOT_BITMAP_OFFSET(page, p) (SLOT_INDEX(page, p) & (BITS_BITLENGTH - 1))
1238#define SLOT_BITMAP_BIT(page, p) ((bits_t)1 << SLOT_BITMAP_OFFSET(page, p))
1239
1240#define _MARKED_IN_BITMAP(bits, page, p) ((bits)[SLOT_BITMAP_INDEX(page, p)] & SLOT_BITMAP_BIT(page, p))
1241#define _MARK_IN_BITMAP(bits, page, p) ((bits)[SLOT_BITMAP_INDEX(page, p)] |= SLOT_BITMAP_BIT(page, p))
1242#define _CLEAR_IN_BITMAP(bits, page, p) ((bits)[SLOT_BITMAP_INDEX(page, p)] &= ~SLOT_BITMAP_BIT(page, p))
1243
1244#define MARKED_IN_BITMAP(bits, p) _MARKED_IN_BITMAP(bits, GET_HEAP_PAGE(p), p)
1245#define MARK_IN_BITMAP(bits, p) _MARK_IN_BITMAP(bits, GET_HEAP_PAGE(p), p)
1246#define CLEAR_IN_BITMAP(bits, p) _CLEAR_IN_BITMAP(bits, GET_HEAP_PAGE(p), p)
1247
1248#define GET_HEAP_MARK_BITS(x) (&GET_HEAP_PAGE(x)->mark_bits[0])
1249#define GET_HEAP_PINNED_BITS(x) (&GET_HEAP_PAGE(x)->pinned_bits[0])
1250#define GET_HEAP_UNCOLLECTIBLE_BITS(x) (&GET_HEAP_PAGE(x)->uncollectible_bits[0])
1251#define GET_HEAP_WB_UNPROTECTED_BITS(x) (&GET_HEAP_PAGE(x)->wb_unprotected_bits[0])
1252#define GET_HEAP_MARKING_BITS(x) (&GET_HEAP_PAGE(x)->marking_bits[0])
1253#define GET_HEAP_SHAREABLE_BITS(x) (&GET_HEAP_PAGE(x)->shareable_bits[0])
1254#define GET_HEAP_SHREF_BITS(x) (&GET_HEAP_PAGE(x)->shref_bits[0])
1255#define GET_HEAP_OBJSPACE(x) (GET_HEAP_PAGE(x)->objspace)
1256
1257/* obj lives on a page of another objspace, not the current one (i.e. it is foreign). */
1258static inline bool
1259gc_foreign_object_p(const rb_objspace_t *objspace, VALUE obj)
1260{
1261 return RB_UNLIKELY(GET_HEAP_OBJSPACE(obj) != objspace);
1262}
1263
1264/* Foreign and not inside a stop-the-world global GC. While true, a local GC must not
1265 * touch obj's per-object GC state (mark, pin, remember bits): its owner handles that,
1266 * or the global GC does with everyone stopped. */
1267static inline bool
1268gc_skip_foreign_object_p(const rb_objspace_t *objspace, VALUE obj)
1269{
1270 return gc_foreign_object_p(objspace, obj) && !objspace->flags.during_global_gc;
1271}
1272
1273/* Record obj as shareable on its owning page (bit, page flag and population counter).
1274 * Shared by born-shareable objects and make_shareable. The writer is the owner thread,
1275 * so plain bit operations suffice. */
1276static inline void
1277gc_page_add_shareable(struct heap_page *page, VALUE obj)
1278{
1279 GC_ASSERT(page == GET_HEAP_PAGE(obj));
1280 GC_ASSERT(RB_FL_TEST_RAW(obj, RUBY_FL_SHAREABLE));
1281 _MARK_IN_BITMAP(page->shareable_bits, page, obj);
1282 page->flags.has_shareable_objects = TRUE;
1283 page->objspace->shareable_objects++;
1284}
1285
1286static int
1287RVALUE_AGE_GET(VALUE obj)
1288{
1289 struct heap_page *page = GET_HEAP_PAGE(obj);
1290 bits_t *age_bits = page->age_bits;
1291 size_t slot_idx = SLOT_INDEX(page, obj);
1292 size_t idx = (slot_idx / BITS_BITLENGTH) * 2;
1293 int shift = (int)(slot_idx & (BITS_BITLENGTH - 1));
1294 int lo = (age_bits[idx] >> shift) & 1;
1295 int hi = (age_bits[idx + 1] >> shift) & 1;
1296 return lo | (hi << 1);
1297}
1298
1299static void
1300RVALUE_AGE_SET_BITMAP(VALUE obj, int age)
1301{
1302 RUBY_ASSERT(age <= RVALUE_OLD_AGE);
1303 struct heap_page *page = GET_HEAP_PAGE(obj);
1304 bits_t *age_bits = page->age_bits;
1305 size_t slot_idx = SLOT_INDEX(page, obj);
1306 size_t idx = (slot_idx / BITS_BITLENGTH) * 2;
1307 int shift = (int)(slot_idx & (BITS_BITLENGTH - 1));
1308 bits_t mask = (bits_t)1 << shift;
1309
1310 age_bits[idx] = (age_bits[idx] & ~mask) | ((bits_t)(age & 1) << shift);
1311 age_bits[idx + 1] = (age_bits[idx + 1] & ~mask) | ((bits_t)((age >> 1) & 1) << shift);
1312}
1313
1314static void
1315RVALUE_AGE_SET(VALUE obj, int age)
1316{
1317 RVALUE_AGE_SET_BITMAP(obj, age);
1318 if (age == RVALUE_OLD_AGE) {
1320 }
1321 else {
1323 }
1324}
1325
1326#define malloc_limit objspace->malloc_params.limit
1327#define malloc_increase gc_malloc_counters_increase_unsigned(objspace, &objspace->malloc_counters.counters)
1328#define malloc_allocated_size objspace->malloc_params.allocated_size
1329
1330#ifdef MALLOC_COUNTERS_NEED_LOCK
1331# define MALLOC_COUNTERS_LOCK(o) rb_native_mutex_lock(&(o)->malloc_counters.lock)
1332# define MALLOC_COUNTERS_UNLOCK(o) rb_native_mutex_unlock(&(o)->malloc_counters.lock)
1333#else
1334# define MALLOC_COUNTERS_LOCK(o) ((void)0)
1335# define MALLOC_COUNTERS_UNLOCK(o) ((void)0)
1336#endif
1337
1338static inline void
1339gc_counter_add(gc_counter_t *p, size_t delta)
1340{
1341#ifdef MALLOC_COUNTERS_NEED_LOCK
1342 *p += (gc_counter_t)delta;
1343#else
1344 rbimpl_atomic_u64_fetch_add_relaxed(p, (uint64_t)delta);
1345#endif
1346}
1347
1348static inline gc_counter_t
1349gc_counter_load_relaxed(const gc_counter_t *p)
1350{
1351#ifdef MALLOC_COUNTERS_NEED_LOCK
1352 return *p;
1353#else
1354 return rbimpl_atomic_u64_load_relaxed(p);
1355#endif
1356}
1357
1358static inline gc_counter_t
1359gc_counter_load_acquire(const gc_counter_t *p)
1360{
1361#ifdef MALLOC_COUNTERS_NEED_LOCK
1362 return *p;
1363#else
1364 return rbimpl_atomic_u64_load_acquire(p);
1365#endif
1366}
1367
1368static inline void
1369gc_counter_store_release(gc_counter_t *p, gc_counter_t v)
1370{
1371#ifdef MALLOC_COUNTERS_NEED_LOCK
1372 *p = v;
1373#else
1374 rbimpl_atomic_u64_set_release(p, v);
1375#endif
1376}
1377
1378static inline int64_t
1379gc_malloc_counters_increase(rb_objspace_t *objspace, const struct gc_malloc_bytes *c)
1380{
1381 MALLOC_COUNTERS_LOCK(objspace);
1382 gc_counter_t malloc_at = gc_counter_load_acquire(&c->malloc_at_last_gc);
1383 gc_counter_t free_at = gc_counter_load_acquire(&c->free_at_last_gc);
1384 gc_counter_t malloc_now = gc_counter_load_relaxed(&c->malloc);
1385 gc_counter_t free_now = gc_counter_load_relaxed(&c->free);
1386 MALLOC_COUNTERS_UNLOCK(objspace);
1387
1388 gc_counter_t malloc_delta = malloc_now - malloc_at;
1389 gc_counter_t free_delta = free_now - free_at;
1390
1391 if (malloc_delta >= free_delta) {
1392 return (int64_t)(malloc_delta - free_delta);
1393 }
1394 else {
1395 return -(int64_t)(free_delta - malloc_delta);
1396 }
1397}
1398
1399static inline size_t
1400gc_malloc_counters_increase_unsigned(rb_objspace_t *objspace, const struct gc_malloc_bytes *c)
1401{
1402 int64_t inc = gc_malloc_counters_increase(objspace, c);
1403 if (inc <= 0) return 0;
1404#if SIZEOF_SIZE_T < 8
1405 if ((uint64_t)inc > SIZE_MAX) return SIZE_MAX;
1406#endif
1407 return (size_t)inc;
1408}
1409
1410/* Frees done while sweeping are the GC's own work, not the mutator's: advance
1411 * free_at_last_gc past them so they cannot pay for the next cycle's allocation.
1412 * malloc_at_last_gc stays at gc_reset_malloc_info's snapshot (GC start). */
1413static inline void
1414gc_malloc_counters_snapshot_free_at_last_gc(rb_objspace_t *objspace, struct gc_malloc_bytes *c)
1415{
1416 MALLOC_COUNTERS_LOCK(objspace);
1417 gc_counter_store_release(&c->free_at_last_gc, gc_counter_load_relaxed(&c->free));
1418 MALLOC_COUNTERS_UNLOCK(objspace);
1419}
1420
1421static inline void
1422gc_malloc_counters_snapshot(rb_objspace_t *objspace, struct gc_malloc_bytes *c)
1423{
1424 MALLOC_COUNTERS_LOCK(objspace);
1425 gc_counter_t malloc_now = gc_counter_load_relaxed(&c->malloc);
1426 gc_counter_t free_now = gc_counter_load_relaxed(&c->free);
1427 gc_counter_store_release(&c->malloc_at_last_gc, malloc_now);
1428 gc_counter_store_release(&c->free_at_last_gc, free_now);
1429 MALLOC_COUNTERS_UNLOCK(objspace);
1430}
1431
1432#define heap_pages_lomem objspace->heap_pages.range[0]
1433#define heap_pages_himem objspace->heap_pages.range[1]
1434#define heap_pages_freeable_pages objspace->heap_pages.freeable_pages
1435#define heap_pages_deferred_final objspace->heap_pages.deferred_final
1436#define heaps objspace->heaps
1437#define during_gc objspace->flags.during_gc
1438#define finalizing objspace->atomic_flags.finalizing
1439#define finalizer_table objspace->finalizer_table
1440#define ruby_gc_stressful global_objspace->gc_stressful
1441#define ruby_gc_stress_mode global_objspace->gc_stress_mode
1442#if GC_DEBUG_STRESS_TO_CLASS
1443#define stress_to_class objspace->stress_to_class
1444#define set_stress_to_class(c) (stress_to_class = (c))
1445#else
1446#define stress_to_class ((void)objspace, 0)
1447#define set_stress_to_class(c) ((void)objspace, (c))
1448#endif
1449
1450#if 0
1451#define dont_gc_on() (fprintf(stderr, "dont_gc_on@%s:%d\n", __FILE__, __LINE__), objspace->flags.dont_gc = 1)
1452#define dont_gc_off() (fprintf(stderr, "dont_gc_off@%s:%d\n", __FILE__, __LINE__), objspace->flags.dont_gc = 0)
1453#define dont_gc_set(b) (fprintf(stderr, "dont_gc_set(%d)@%s:%d\n", __FILE__, __LINE__), objspace->flags.dont_gc = (int)(b))
1454#define dont_gc_val() (objspace->flags.dont_gc)
1455#else
1456#define dont_gc_on() (objspace->flags.dont_gc = 1)
1457#define dont_gc_off() (objspace->flags.dont_gc = 0)
1458#define dont_gc_set(b) (objspace->flags.dont_gc = (int)(b))
1459#define dont_gc_val() (objspace->flags.dont_gc)
1460#endif
1461
1462#define gc_config_full_mark_set(b) (objspace->gc_config.full_mark = (int)(b))
1463#define gc_config_full_mark_val (objspace->gc_config.full_mark)
1464
1465static inline enum gc_mode
1466gc_mode_verify(enum gc_mode mode)
1467{
1468#if RGENGC_CHECK_MODE > 0
1469 switch (mode) {
1470 case gc_mode_none:
1471 case gc_mode_marking:
1472 case gc_mode_sweeping:
1473 case gc_mode_compacting:
1474 break;
1475 default:
1476 rb_bug("gc_mode_verify: unreachable (%d)", (int)mode);
1477 }
1478#endif
1479 return mode;
1480}
1481
1482static inline bool
1483has_sweeping_pages(rb_objspace_t *objspace)
1484{
1485 return objspace->sweeping_heap_count != 0;
1486}
1487
1488static inline size_t
1489heap_eden_total_pages(rb_objspace_t *objspace)
1490{
1491 size_t count = 0;
1492 for (int i = 0; i < HEAP_COUNT; i++) {
1493 count += (&heaps[i])->total_pages;
1494 }
1495 return count;
1496}
1497
1498static inline size_t
1499total_allocated_objects(rb_objspace_t *objspace)
1500{
1501 size_t count = 0;
1502 for (int i = 0; i < HEAP_COUNT; i++) {
1503 rb_heap_t *heap = &heaps[i];
1504 count += heap->total_allocated_objects;
1505 }
1506 return count;
1507}
1508
1509static inline size_t
1510total_freed_objects(rb_objspace_t *objspace)
1511{
1512 size_t count = 0;
1513 for (int i = 0; i < HEAP_COUNT; i++) {
1514 rb_heap_t *heap = &heaps[i];
1515 count += heap->total_freed_objects;
1516 }
1517 return count;
1518}
1519
1520static inline size_t
1521total_final_slots_count(rb_objspace_t *objspace)
1522{
1523 size_t count = 0;
1524 for (int i = 0; i < HEAP_COUNT; i++) {
1525 rb_heap_t *heap = &heaps[i];
1526 count += heap->final_slots_count;
1527 }
1528 return count;
1529}
1530
1531#define gc_mode(objspace) gc_mode_verify((enum gc_mode)(objspace)->flags.mode)
1532#define gc_mode_set(objspace, m) ((objspace)->flags.mode = (unsigned int)gc_mode_verify(m))
1533#define gc_needs_major_flags objspace->rgengc.need_major_gc
1534
1535#define is_marking(objspace) (gc_mode(objspace) == gc_mode_marking)
1536#define is_sweeping(objspace) (gc_mode(objspace) == gc_mode_sweeping)
1537#define is_full_marking(objspace) ((objspace)->flags.during_minor_gc == FALSE)
1538#define is_incremental_marking(objspace) ((objspace)->flags.during_incremental_marking != FALSE)
1539#define will_be_incremental_marking(objspace) ((objspace)->rgengc.need_major_gc != GPR_FLAG_NONE)
1540/*
1541 * Byte budget for incremental sweep steps. Each step sweeps at most
1542 * this many bytes worth of slots before yielding. The effective slot
1543 * count per step is GC_INCREMENTAL_SWEEP_BYTES / heap->slot_size,
1544 * so larger slot pools (which are less heavily used) naturally get
1545 * fewer slots swept per step.
1546 *
1547 * Baseline: 2048 slots * RVALUE_SLOT_SIZE = 2048 * 40 = 81920 bytes,
1548 * preserving the historical behavior for the smallest heap.
1549 */
1550#define GC_INCREMENTAL_SWEEP_BYTES (2048 * RVALUE_SLOT_SIZE)
1551#define GC_INCREMENTAL_SWEEP_POOL_BYTES (1024 * RVALUE_SLOT_SIZE)
1552#define is_lazy_sweeping(objspace) (GC_ENABLE_LAZY_SWEEP && has_sweeping_pages(objspace))
1553/* In lazy sweeping or the previous incremental marking finished and did not yield a free page. */
1554#define needs_continue_sweeping(objspace, heap) \
1555 ((heap)->free_pages == NULL && is_lazy_sweeping(objspace))
1556
1557#if SIZEOF_LONG == SIZEOF_VOIDP
1558# define obj_id_to_ref(objid) ((objid) ^ FIXNUM_FLAG) /* unset FIXNUM_FLAG */
1559#elif SIZEOF_LONG_LONG == SIZEOF_VOIDP
1560# define obj_id_to_ref(objid) (FIXNUM_P(objid) ? \
1561 ((objid) ^ FIXNUM_FLAG) : (NUM2PTR(objid) << 1))
1562#else
1563# error not supported
1564#endif
1565
1566struct RZombie {
1567 VALUE flags;
1568 VALUE next;
1569 void (*dfree)(void *);
1570 void *data;
1571};
1572
1573#define RZOMBIE(o) ((struct RZombie *)(o))
1574
1575static bool ruby_enable_autocompact = false;
1576#if RGENGC_CHECK_MODE
1577static gc_compact_compare_func ruby_autocompact_compare_func;
1578#endif
1579
1580static void init_mark_stack(mark_stack_t *stack);
1581static int garbage_collect(rb_objspace_t *, unsigned int reason);
1582
1583static int gc_start(rb_objspace_t *objspace, unsigned int reason);
1584static void gc_rest(rb_objspace_t *objspace);
1585
1586/* GC cycle events (ENTER, EXIT, START, END_MARK, END_SWEEP) fire only if the objspace's
1587 * own Ractor enabled them, so a concurrent local GC never walks the VM-global hook list
1588 * while another Ractor mutates it. NEWOBJ and FREEOBJ were already restricted. */
1589#define gc_event_hook(objspace, event) do { \
1590 if (RB_UNLIKELY((objspace)->hook_events & (event))) { \
1591 rb_gc_event_hook(0, (event)); \
1592 } \
1593} while (0)
1594
1595enum gc_enter_event {
1596 gc_enter_event_start,
1597 gc_enter_event_continue,
1598 gc_enter_event_rest,
1599 gc_enter_event_finalizer,
1600 gc_enter_event_global,
1601 gc_enter_event_global_auto,
1602};
1603
1604static inline bool gc_enter(rb_objspace_t *objspace, enum gc_enter_event event, unsigned int *lock_lev);
1605static inline void gc_exit(rb_objspace_t *objspace, enum gc_enter_event event, unsigned int *lock_lev);
1606static void gc_marking_enter(rb_objspace_t *objspace);
1607static void gc_marking_exit(rb_objspace_t *objspace);
1608static void gc_sweeping_enter(rb_objspace_t *objspace);
1609static void gc_sweeping_exit(rb_objspace_t *objspace);
1610static bool gc_marks_continue(rb_objspace_t *objspace, rb_heap_t *heap);
1611
1612static void gc_sweep(rb_objspace_t *objspace);
1613static void gc_sweep_finish_heap(rb_objspace_t *objspace, rb_heap_t *heap);
1614static void gc_sweep_continue(rb_objspace_t *objspace, rb_heap_t *heap);
1615
1616static inline void gc_mark(rb_objspace_t *objspace, VALUE ptr);
1617static inline void gc_pin(rb_objspace_t *objspace, VALUE ptr);
1618static inline void gc_mark_and_pin(rb_objspace_t *objspace, VALUE ptr);
1619
1620static int gc_mark_stacked_objects_incremental(rb_objspace_t *, size_t count);
1621NO_SANITIZE("memory", static inline bool is_pointer_to_heap(rb_objspace_t *objspace, const void *ptr));
1622
1623static void gc_verify_internal_consistency(void *objspace_ptr);
1624
1625static double getrusage_time(void);
1626static inline rb_hrtime_t elapsed_hrtime_from(rb_hrtime_t start);
1627static inline void gc_prof_setup_new_record(rb_objspace_t *objspace, unsigned int reason);
1628static inline void gc_prof_timer_start(rb_objspace_t *);
1629static inline void gc_prof_timer_stop(rb_objspace_t *);
1630static inline void gc_prof_mark_timer_start(rb_objspace_t *);
1631static inline void gc_prof_mark_timer_stop(rb_objspace_t *);
1632static inline void gc_prof_sweep_timer_start(rb_objspace_t *);
1633static inline void gc_prof_sweep_timer_stop(rb_objspace_t *);
1634static inline void gc_prof_set_malloc_info(rb_objspace_t *);
1635static inline void gc_prof_set_heap_info(rb_objspace_t *);
1636
1637#define gc_prof_record(objspace) (objspace)->profile.current_record
1638#define gc_prof_enabled(objspace) ((objspace)->profile.run && (objspace)->profile.current_record)
1639
1640#define gc_report(level, objspace, ...) \
1641 if (!RGENGC_DEBUG_ENABLED(level)) {} else gc_report_body(level, objspace, __VA_ARGS__)
1642PRINTF_ARGS(static void gc_report_body(int level, rb_objspace_t *objspace, const char *fmt, ...), 3, 4);
1643
1644static void gc_finalize_deferred(void *dmy);
1645static void gc_tdata_unsafe_drain_objspaces(rb_objspace_t **objspaces, size_t n);
1646
1647#if USE_TICK_T
1648
1649/* the following code is only for internal tuning. */
1650
1651/* Source code to use RDTSC is quoted and modified from
1652 * https://www.mcs.anl.gov/~kazutomo/rdtsc.html
1653 * written by Kazutomo Yoshii <kazutomo@mcs.anl.gov>
1654 */
1655
1656#if defined(__GNUC__) && defined(__i386__)
1657typedef unsigned long long tick_t;
1658#define PRItick "llu"
1659static inline tick_t
1660tick(void)
1661{
1662 unsigned long long int x;
1663 __asm__ __volatile__ ("rdtsc" : "=A" (x));
1664 return x;
1665}
1666
1667#elif defined(__GNUC__) && defined(__x86_64__)
1668typedef unsigned long long tick_t;
1669#define PRItick "llu"
1670
1671static __inline__ tick_t
1672tick(void)
1673{
1674 unsigned long hi, lo;
1675 __asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi));
1676 return ((unsigned long long)lo)|( ((unsigned long long)hi)<<32);
1677}
1678
1679#elif defined(__powerpc64__) && (GCC_VERSION_SINCE(4,8,0) || defined(__clang__))
1680typedef unsigned long long tick_t;
1681#define PRItick "llu"
1682
1683static __inline__ tick_t
1684tick(void)
1685{
1686 unsigned long long val = __builtin_ppc_get_timebase();
1687 return val;
1688}
1689
1690#elif defined(__POWERPC__) && defined(__APPLE__)
1691/* Implementation for macOS PPC by @nobu
1692 * See: https://github.com/ruby/ruby/pull/5975#discussion_r890045558
1693 */
1694typedef unsigned long long tick_t;
1695#define PRItick "llu"
1696
1697static __inline__ tick_t
1698tick(void)
1699{
1700 unsigned long int upper, lower, tmp;
1701 # define mftbu(r) __asm__ volatile("mftbu %0" : "=r"(r))
1702 # define mftb(r) __asm__ volatile("mftb %0" : "=r"(r))
1703 do {
1704 mftbu(upper);
1705 mftb(lower);
1706 mftbu(tmp);
1707 } while (tmp != upper);
1708 return ((tick_t)upper << 32) | lower;
1709}
1710
1711#elif defined(__aarch64__) && defined(__GNUC__)
1712typedef unsigned long tick_t;
1713#define PRItick "lu"
1714
1715static __inline__ tick_t
1716tick(void)
1717{
1718 unsigned long val;
1719 __asm__ __volatile__ ("mrs %0, cntvct_el0" : "=r" (val));
1720 return val;
1721}
1722
1723
1724#elif defined(_WIN32) && defined(_MSC_VER)
1725#include <intrin.h>
1726typedef unsigned __int64 tick_t;
1727#define PRItick "llu"
1728
1729static inline tick_t
1730tick(void)
1731{
1732 return __rdtsc();
1733}
1734
1735#else /* use clock */
1736typedef clock_t tick_t;
1737#define PRItick "llu"
1738
1739static inline tick_t
1740tick(void)
1741{
1742 return clock();
1743}
1744#endif /* TSC */
1745#else /* USE_TICK_T */
1746#define MEASURE_LINE(expr) expr
1747#endif /* USE_TICK_T */
1748
1749static inline VALUE check_rvalue_consistency(rb_objspace_t *objspace, const VALUE obj);
1750
1751#define RVALUE_MARKED_BITMAP(obj) MARKED_IN_BITMAP(GET_HEAP_MARK_BITS(obj), (obj))
1752#define RVALUE_WB_UNPROTECTED_BITMAP(obj) MARKED_IN_BITMAP(GET_HEAP_WB_UNPROTECTED_BITS(obj), (obj))
1753#define RVALUE_MARKING_BITMAP(obj) MARKED_IN_BITMAP(GET_HEAP_MARKING_BITS(obj), (obj))
1754#define RVALUE_UNCOLLECTIBLE_BITMAP(obj) MARKED_IN_BITMAP(GET_HEAP_UNCOLLECTIBLE_BITS(obj), (obj))
1755#define RVALUE_PINNED_BITMAP(obj) MARKED_IN_BITMAP(GET_HEAP_PINNED_BITS(obj), (obj))
1756
1757static inline int
1758RVALUE_MARKED(rb_objspace_t *objspace, VALUE obj)
1759{
1760 check_rvalue_consistency(objspace, obj);
1761 return RVALUE_MARKED_BITMAP(obj) != 0;
1762}
1763
1764static inline int
1765RVALUE_PINNED(rb_objspace_t *objspace, VALUE obj)
1766{
1767 check_rvalue_consistency(objspace, obj);
1768 return RVALUE_PINNED_BITMAP(obj) != 0;
1769}
1770
1771static inline int
1772RVALUE_WB_UNPROTECTED(rb_objspace_t *objspace, VALUE obj)
1773{
1774 check_rvalue_consistency(objspace, obj);
1775 return RVALUE_WB_UNPROTECTED_BITMAP(obj) != 0;
1776}
1777
1778static inline int
1779RVALUE_MARKING(rb_objspace_t *objspace, VALUE obj)
1780{
1781 check_rvalue_consistency(objspace, obj);
1782 return RVALUE_MARKING_BITMAP(obj) != 0;
1783}
1784
1785static inline int
1786RVALUE_REMEMBERED(rb_objspace_t *objspace, VALUE obj)
1787{
1788 check_rvalue_consistency(objspace, obj);
1789 return MARKED_IN_BITMAP(GET_HEAP_PAGE(obj)->remembered_bits, obj) != 0;
1790}
1791
1792static inline int
1793RVALUE_UNCOLLECTIBLE(rb_objspace_t *objspace, VALUE obj)
1794{
1795 check_rvalue_consistency(objspace, obj);
1796 return RVALUE_UNCOLLECTIBLE_BITMAP(obj) != 0;
1797}
1798
1799#define RVALUE_PAGE_WB_UNPROTECTED(page, obj) MARKED_IN_BITMAP((page)->wb_unprotected_bits, (obj))
1800#define RVALUE_PAGE_UNCOLLECTIBLE(page, obj) MARKED_IN_BITMAP((page)->uncollectible_bits, (obj))
1801#define RVALUE_PAGE_MARKING(page, obj) MARKED_IN_BITMAP((page)->marking_bits, (obj))
1802
1803static void rgengc_remember(rb_objspace_t *objspace, VALUE obj);
1804static void gc_bitmaps_clear(rb_objspace_t *objspace, rb_heap_t *heap, bool clear_shref);
1805static void rgengc_rememberset_mark(rb_objspace_t *objspace, rb_heap_t *heap);
1806static bool verify_pointer_in_any_heap_p(const void *ptr); /* cross-objspace ownership test */
1807
1808static int
1809check_rvalue_consistency_force(rb_objspace_t *objspace, const VALUE obj, int terminate)
1810{
1811 int err = 0;
1812
1813 /* Under a global GC the barrier stops every Ractor, so the cross-objspace walk
1814 * below is safe without the VM lock. Sweeping an ownerless zombie objspace also
1815 * leaves GET_RACTOR() NULL, and taking the lock here would dereference it. */
1816 const bool world_stopped = objspace->flags.during_global_gc;
1817 /* The VM lock protects the cross-objspace walk while other Ractors run and
1818 * reallocate their heaps. Not taken while this objspace is in GC: pages are stable
1819 * then, the cross-objspace walk needs the world stopped anyway, and the Ractor lock
1820 * may already be held (Ractor -> VM order inversion). A global GC holds the barrier
1821 * and needs no lock. */
1822 const bool take_vm_lock = !world_stopped && !during_gc;
1823 unsigned int lev = 0;
1824 if (take_vm_lock) lev = RB_GC_VM_LOCK_NO_BARRIER();
1825 {
1826 if (SPECIAL_CONST_P(obj)) {
1827 fprintf(stderr, "check_rvalue_consistency: %p is a special const.\n", (void *)obj);
1828 err++;
1829 }
1830 else if (!is_pointer_to_heap(objspace, (void *)obj)) {
1831 /* obj may be a legitimate cross-objspace reference (a shareable object, an
1832 * in-flight shref payload); it is a non-object only if no objspace's heap
1833 * holds it. A foreign object's mark/age/remembered bits belong to its
1834 * owner and reading them would race its local GC: skip per-object checks. */
1835 if (!world_stopped) {
1836 /* A mid-local-GC verify holds no barrier, so other Ractors reallocate
1837 * heap_pages.sorted under verify_pointer_in_any_heap_p's page_index
1838 * read. Accept foreign pointers here; the global GC's world-stopped
1839 * verify does the full existence check. */
1840 }
1841 else if (!verify_pointer_in_any_heap_p((void *)obj)) {
1842 struct heap_page *empty_page = objspace->empty_pages;
1843 while (empty_page) {
1844 if ((uintptr_t)empty_page->body <= (uintptr_t)obj &&
1845 (uintptr_t)obj < (uintptr_t)empty_page->body + HEAP_PAGE_SIZE) {
1846 GC_ASSERT(heap_page_in_global_empty_pages_pool(objspace, empty_page));
1847 fprintf(stderr, "check_rvalue_consistency: %p is in an empty page (%p).\n",
1848 (void *)obj, (void *)empty_page);
1849 err++;
1850 goto skip;
1851 }
1852 empty_page = empty_page->free_next;
1853 }
1854 fprintf(stderr, "check_rvalue_consistency: %p is not a Ruby object.\n", (void *)obj);
1855 err++;
1856 skip:
1857 ;
1858 }
1859 }
1860 else {
1861 const int wb_unprotected_bit = RVALUE_WB_UNPROTECTED_BITMAP(obj) != 0;
1862 const int uncollectible_bit = RVALUE_UNCOLLECTIBLE_BITMAP(obj) != 0;
1863 const int mark_bit = RVALUE_MARKED_BITMAP(obj) != 0;
1864 const int marking_bit = RVALUE_MARKING_BITMAP(obj) != 0;
1865 const int remembered_bit = MARKED_IN_BITMAP(GET_HEAP_PAGE(obj)->remembered_bits, obj) != 0;
1866 const int age = RVALUE_AGE_GET((VALUE)obj);
1867
1868 if (heap_page_in_global_empty_pages_pool(objspace, GET_HEAP_PAGE(obj))) {
1869 fprintf(stderr, "check_rvalue_consistency: %s is in tomb page.\n", rb_obj_info(obj));
1870 err++;
1871 }
1872 if (BUILTIN_TYPE(obj) == T_NONE) {
1873 fprintf(stderr, "check_rvalue_consistency: %s is T_NONE.\n", rb_obj_info(obj));
1874 err++;
1875 }
1876 if (BUILTIN_TYPE(obj) == T_ZOMBIE) {
1877 fprintf(stderr, "check_rvalue_consistency: %s is T_ZOMBIE.\n", rb_obj_info(obj));
1878 err++;
1879 }
1880
1881 /* Do not run the memsize probe once an inconsistency (a T_NONE, say) was
1882 * found: an rb_bug inside the probe would lose the real diagnosis. */
1883 if (err == 0 && BUILTIN_TYPE(obj) != T_DATA) {
1884 rb_obj_memsize_of((VALUE)obj);
1885 }
1886
1887 /* check generation
1888 *
1889 * OLD == age == 3 && old-bitmap && mark-bit (except incremental marking)
1890 */
1891 if (age > 0 && wb_unprotected_bit) {
1892 fprintf(stderr, "check_rvalue_consistency: %s is not WB protected, but age is %d > 0.\n", rb_obj_info(obj), age);
1893 err++;
1894 }
1895
1896 if (!is_marking(objspace) && uncollectible_bit && !mark_bit) {
1897 fprintf(stderr, "check_rvalue_consistency: %s is uncollectible, but is not marked while !gc.\n", rb_obj_info(obj));
1898 err++;
1899 }
1900
1901 if (!is_full_marking(objspace)) {
1902 if (uncollectible_bit && age != RVALUE_OLD_AGE && !wb_unprotected_bit) {
1903 fprintf(stderr, "check_rvalue_consistency: %s is uncollectible, but not old (age: %d) and not WB unprotected.\n",
1904 rb_obj_info(obj), age);
1905 err++;
1906 }
1907 if (remembered_bit && age != RVALUE_OLD_AGE) {
1908 fprintf(stderr, "check_rvalue_consistency: %s is remembered, but not old (age: %d).\n",
1909 rb_obj_info(obj), age);
1910 err++;
1911 }
1912 }
1913
1914 /*
1915 * check coloring
1916 *
1917 * marking:false marking:true
1918 * marked:false white *invalid*
1919 * marked:true black grey
1920 */
1921 if (is_incremental_marking(objspace) && marking_bit) {
1922 if (!is_marking(objspace) && !mark_bit) {
1923 fprintf(stderr, "check_rvalue_consistency: %s is marking, but not marked.\n", rb_obj_info(obj));
1924 err++;
1925 }
1926 }
1927 }
1928 }
1929 if (take_vm_lock) RB_GC_VM_UNLOCK_NO_BARRIER(lev);
1930
1931 if (err > 0 && terminate) {
1932 rb_bug("check_rvalue_consistency_force: there is %d errors.", err);
1933 }
1934 return err;
1935}
1936
1937#if RGENGC_CHECK_MODE == 0
1938static inline VALUE
1939check_rvalue_consistency(rb_objspace_t *objspace, const VALUE obj)
1940{
1941 return obj;
1942}
1943#else
1944static VALUE
1945check_rvalue_consistency(rb_objspace_t *objspace, const VALUE obj)
1946{
1947 check_rvalue_consistency_force(objspace, obj, TRUE);
1948 return obj;
1949}
1950#endif
1951
1952static inline bool
1953gc_object_moved_p(rb_objspace_t *objspace, VALUE obj)
1954{
1955
1956 bool ret;
1957 asan_unpoisoning_object(obj) {
1958 ret = BUILTIN_TYPE(obj) == T_MOVED;
1959 }
1960 return ret;
1961}
1962
1963static inline int
1964RVALUE_OLD_P(rb_objspace_t *objspace, VALUE obj)
1965{
1966 GC_ASSERT(!RB_SPECIAL_CONST_P(obj));
1967 check_rvalue_consistency(objspace, obj);
1968 // Because this will only ever be called on GC controlled objects,
1969 // we can use the faster _RAW function here
1970 return RB_OBJ_PROMOTED_RAW(obj);
1971}
1972
1973static inline void
1974RVALUE_PAGE_OLD_UNCOLLECTIBLE_SET(rb_objspace_t *objspace, struct heap_page *page, VALUE obj)
1975{
1976 MARK_IN_BITMAP(&page->uncollectible_bits[0], obj);
1977 /* Count a promotion in the object's own objspace: a global GC ages every objspace's
1978 * slots from the driver, and counting them there would skew the other objspaces'
1979 * old_objects and with it their major GC frequency. */
1980 page->objspace->rgengc.old_objects++;
1981
1982#if RGENGC_PROFILE >= 2
1983 objspace->profile.total_promoted_count++;
1984 objspace->profile.promoted_types[BUILTIN_TYPE(obj)]++;
1985#endif
1986}
1987
1988static inline void
1989RVALUE_OLD_UNCOLLECTIBLE_SET(rb_objspace_t *objspace, VALUE obj)
1990{
1991 RB_DEBUG_COUNTER_INC(obj_promote);
1992 RVALUE_PAGE_OLD_UNCOLLECTIBLE_SET(objspace, GET_HEAP_PAGE(obj), obj);
1993}
1994
1995/* set age to age+1 */
1996static inline void
1997RVALUE_AGE_INC(rb_objspace_t *objspace, VALUE obj)
1998{
1999 int age = RVALUE_AGE_GET((VALUE)obj);
2000
2001 if (RGENGC_CHECK_MODE && age == RVALUE_OLD_AGE) {
2002 rb_bug("RVALUE_AGE_INC: can not increment age of OLD object %s.", rb_obj_info(obj));
2003 }
2004
2005 age++;
2006 RVALUE_AGE_SET(obj, age);
2007
2008 if (age == RVALUE_OLD_AGE) {
2009 RVALUE_OLD_UNCOLLECTIBLE_SET(objspace, obj);
2010 }
2011
2012 check_rvalue_consistency(objspace, obj);
2013}
2014
2015static inline void
2016RVALUE_AGE_SET_CANDIDATE(rb_objspace_t *objspace, VALUE obj)
2017{
2018 check_rvalue_consistency(objspace, obj);
2019 GC_ASSERT(!RVALUE_OLD_P(objspace, obj));
2020 RVALUE_AGE_SET(obj, RVALUE_OLD_AGE - 1);
2021 check_rvalue_consistency(objspace, obj);
2022}
2023
2024static inline void
2025RVALUE_AGE_RESET(VALUE obj)
2026{
2027 RVALUE_AGE_SET(obj, 0);
2028}
2029
2030static inline void
2031RVALUE_DEMOTE(rb_objspace_t *objspace, VALUE obj)
2032{
2033 check_rvalue_consistency(objspace, obj);
2034 GC_ASSERT(RVALUE_OLD_P(objspace, obj));
2035
2036 if (!is_incremental_marking(objspace) && RVALUE_REMEMBERED(objspace, obj)) {
2037 struct heap_page *page = GET_HEAP_PAGE(obj);
2038 _CLEAR_IN_BITMAP(page->remembered_bits, page, obj);
2039 }
2040
2041 CLEAR_IN_BITMAP(GET_HEAP_UNCOLLECTIBLE_BITS(obj), obj);
2042 RVALUE_AGE_RESET(obj);
2043
2044 if (RVALUE_MARKED(objspace, obj)) {
2045 /* symmetric with RVALUE_PAGE_OLD_UNCOLLECTIBLE_SET */
2046 GET_HEAP_PAGE(obj)->objspace->rgengc.old_objects--;
2047 }
2048
2049 check_rvalue_consistency(objspace, obj);
2050}
2051
2052static inline int
2053RVALUE_BLACK_P(rb_objspace_t *objspace, VALUE obj)
2054{
2055 return RVALUE_MARKED(objspace, obj) && !RVALUE_MARKING(objspace, obj);
2056}
2057
2058static inline int
2059RVALUE_WHITE_P(rb_objspace_t *objspace, VALUE obj)
2060{
2061 return !RVALUE_MARKED(objspace, obj);
2062}
2063
2064bool
2065rb_gc_impl_user_gc_disabled_set(void *objspace_ptr, bool disable)
2066{
2067 rb_objspace_t *objspace = objspace_ptr;
2068 const bool was = objspace->flags.user_gc_disabled;
2069 objspace->flags.user_gc_disabled = disable;
2070 return was;
2071}
2072
2073bool
2074rb_gc_impl_user_gc_disabled_p(void *objspace_ptr)
2075{
2076 rb_objspace_t *objspace = objspace_ptr;
2077 return objspace->flags.user_gc_disabled;
2078}
2079
2080bool
2081rb_gc_impl_gc_enabled_p(void *objspace_ptr)
2082{
2083 rb_objspace_t *objspace = objspace_ptr;
2084 return !dont_gc_val();
2085}
2086
2087void
2088rb_gc_impl_gc_enable(void *objspace_ptr)
2089{
2090 rb_objspace_t *objspace = objspace_ptr;
2091
2092 dont_gc_off();
2093}
2094
2095void
2096rb_gc_impl_gc_disable(void *objspace_ptr, bool finish_current_gc)
2097{
2098 rb_objspace_t *objspace = objspace_ptr;
2099
2100 if (finish_current_gc) {
2101 gc_rest(objspace);
2102 }
2103
2104 dont_gc_on();
2105}
2106
2107/* Finish an incremental mark or lazy sweep in progress without changing the enabled
2108 * state. gc.c uses it to settle the only objspace just before the process goes
2109 * multi-objspace. */
2110void
2111rb_gc_impl_gc_rest(void *objspace_ptr)
2112{
2113 gc_rest(objspace_ptr);
2114}
2115
2116/*
2117 --------------------------- ObjectSpace -----------------------------
2118*/
2119
2120static inline void *
2121calloc1(size_t n)
2122{
2123 return calloc(1, n);
2124}
2125
2126void
2127rb_gc_impl_set_event_hook(void *objspace_ptr, const rb_event_flag_t event)
2128{
2129 rb_objspace_t *objspace = objspace_ptr;
2130 /* FREEOBJ is main-objspace only (rb_objspace_set_event_hook masks it elsewhere). */
2131 GC_ASSERT(!(event & RUBY_INTERNAL_EVENT_FREEOBJ) ||
2132 objspace == global_objspace->main_objspace);
2133 objspace->hook_events = event & RUBY_INTERNAL_EVENT_OBJSPACE_MASK;
2134}
2135
2136unsigned long long
2137rb_gc_impl_get_total_time(void *objspace_ptr)
2138{
2139 rb_objspace_t *objspace = objspace_ptr;
2140
2141 unsigned long long marking_time = objspace->profile.marking_time_ns;
2142 unsigned long long sweeping_time = objspace->profile.sweeping_time_ns;
2143
2144 return marking_time + sweeping_time;
2145}
2146
2147void
2148rb_gc_impl_set_measure_total_time(void *objspace_ptr, VALUE flag)
2149{
2150 rb_objspace_t *objspace = objspace_ptr;
2151
2152 objspace->flags.measure_gc = RTEST(flag) ? TRUE : FALSE;
2153}
2154
2155bool
2156rb_gc_impl_get_measure_total_time(void *objspace_ptr)
2157{
2158 rb_objspace_t *objspace = objspace_ptr;
2159
2160 return objspace->flags.measure_gc;
2161}
2162
2163static void
2164gc_process_stat_capture(const rb_objspace_t *objspace,
2165 struct gc_process_stat_snapshot *out)
2166{
2167 out->count = (uint32_t)objspace->profile.count;
2168 out->minor_gc_count = (uint32_t)objspace->profile.minor_gc_count;
2169 out->major_gc_count = (uint32_t)objspace->profile.major_gc_count;
2170 out->marking_time_ns = objspace->profile.marking_time_ns;
2171 out->sweeping_time_ns = objspace->profile.sweeping_time_ns;
2172}
2173
2174static void
2175gc_process_stat_publish(rb_objspace_t *objspace)
2176{
2177 struct gc_process_stat_snapshot snap;
2178 gc_process_stat_capture(objspace, &snap);
2179 GC_ASSERT(snap.count == snap.minor_gc_count + snap.major_gc_count);
2180 rb_native_mutex_lock(&objspace->process_stat.lock);
2181 objspace->process_stat.published = snap;
2182 rb_native_mutex_unlock(&objspace->process_stat.lock);
2183}
2184
2185static void
2186gc_process_stat_add(struct gc_process_stat_total *dst,
2187 const struct gc_process_stat_snapshot *src)
2188{
2189 dst->count += src->count;
2190 dst->minor_gc_count += src->minor_gc_count;
2191 dst->major_gc_count += src->major_gc_count;
2192 dst->marking_time_ns += src->marking_time_ns;
2193 dst->sweeping_time_ns += src->sweeping_time_ns;
2194}
2195
2196/* garbage objects will be collected soon. */
2197bool
2198rb_gc_impl_garbage_object_p(void *objspace_ptr, VALUE ptr)
2199{
2200 rb_objspace_t *objspace = objspace_ptr;
2201
2202 /* A foreign object is a live leaf: reading its type or mark bit would race the
2203 * owner's local GC, so outside a global GC's barrier never report it as garbage.
2204 * The fstring/symbol weak-set lookups do reach across objspaces, but those objects
2205 * are born shareable and only a stop-the-world global GC collects them, so "not
2206 * garbage" is correct. */
2207 if (gc_skip_foreign_object_p(objspace, ptr)) {
2208 return false;
2209 }
2210
2211 /* Asking whether a freed (T_NONE), moved (T_MOVED), or finalized (T_ZOMBIE)
2212 * object is garbage gives an unreliable answer: the slot may since have been
2213 * reused for an unrelated object. A reference to one of these is stale and a
2214 * bug in the caller. */
2215 asan_unpoisoning_object(ptr) {
2216 GC_ASSERT(BUILTIN_TYPE(ptr) != T_NONE);
2217 GC_ASSERT(BUILTIN_TYPE(ptr) != T_MOVED);
2218 GC_ASSERT(BUILTIN_TYPE(ptr) != T_ZOMBIE);
2219 }
2220
2221 return is_lazy_sweeping(objspace) && GET_HEAP_PAGE(ptr)->flags.before_sweep &&
2222 !RVALUE_MARKED(objspace, ptr);
2223}
2224
2225struct rb_gc_vm_context *
2226rb_gc_impl_get_vm_context(void *objspace_ptr)
2227{
2228 rb_objspace_t *objspace = objspace_ptr;
2229
2230 return &objspace->vm_context;
2231}
2232
2233static void free_stack_chunks(mark_stack_t *);
2234static void mark_stack_free_cache(mark_stack_t *);
2235static void heap_page_free(rb_objspace_t *objspace, struct heap_page *page);
2236
2237static inline void
2238gc_check_obj_in_page(struct heap_page *page, VALUE obj)
2239{
2240 if (RGENGC_CHECK_MODE &&
2241 /* obj should belong to page */
2242 !(page->start <= (uintptr_t)obj &&
2243 (uintptr_t)obj < ((uintptr_t)page->start + (page->total_slots * page->slot_size)) &&
2244 obj % sizeof(VALUE) == 0)) {
2245 rb_bug("gc_check_obj_in_page: %p is not rvalue.", (void *)obj);
2246 }
2247}
2248
2249static inline void
2250heap_page_add_free_region(rb_objspace_t *objspace, struct heap_page *page, VALUE obj)
2251{
2252 rb_asan_unpoison_object(obj, false);
2253
2254 // Should have already been reset
2255 GC_ASSERT(RVALUE_AGE_GET(obj) == 0);
2256
2257 gc_check_obj_in_page(page, obj);
2258
2259 asan_unlock_freelist(page);
2260
2261 /* Keep a freed slot from carrying its old shareable and shref bits into the next
2262 * object born there. */
2263 CLEAR_IN_BITMAP(GET_HEAP_SHAREABLE_BITS(obj), obj);
2264 CLEAR_IN_BITMAP(GET_HEAP_SHREF_BITS(obj), obj);
2265
2266 struct free_region *region = (struct free_region *)obj;
2267 region->flags = 0;
2268 region->end = (uintptr_t)obj + page->slot_size;
2269 region->next = page->free_region;
2270 page->free_region = region;
2271
2272 asan_lock_freelist(page);
2273
2274 rb_asan_poison_object(obj);
2275 gc_report(3, objspace, "heap_page_add_free_region: %p\n", (void *)obj);
2276}
2277
2278/* The initial size is per objspace, so a Ractor's own gets a smaller one than
2279 * main's rather than paying main's again. */
2280static inline size_t
2281objspace_heap_init_bytes(const rb_objspace_t *objspace)
2282{
2283 return objspace == global_objspace->main_objspace
2284 ? gc_params.heap_init_bytes : gc_params.ractor_heap_init_bytes;
2285}
2286
2287static void
2288heap_allocatable_bytes_expand(rb_objspace_t *objspace,
2289 rb_heap_t *heap, size_t free_slots, size_t total_slots, size_t slot_size)
2290{
2291 double goal_ratio = gc_params.heap_free_slots_goal_ratio;
2292 size_t target_total_slots;
2293
2294 if (goal_ratio == 0.0) {
2295 target_total_slots = (size_t)(total_slots * gc_params.growth_factor);
2296 }
2297 else if (total_slots == 0) {
2298 target_total_slots = objspace_heap_init_bytes(objspace) / slot_size;
2299 }
2300 else {
2301 /* Find `f' where free_slots = f * total_slots * goal_ratio
2302 * => f = (total_slots - free_slots) / ((1 - goal_ratio) * total_slots)
2303 */
2304 double f = (double)(total_slots - free_slots) / ((1 - goal_ratio) * total_slots);
2305
2306 if (f > gc_params.growth_factor) f = gc_params.growth_factor;
2307 if (f < 1.0) f = 1.1;
2308
2309 target_total_slots = (size_t)(f * total_slots);
2310
2311 if (0) {
2312 fprintf(stderr,
2313 "free_slots(%8"PRIuSIZE")/total_slots(%8"PRIuSIZE")=%1.2f,"
2314 " G(%1.2f), f(%1.2f),"
2315 " total_slots(%8"PRIuSIZE") => target_total_slots(%8"PRIuSIZE")\n",
2316 free_slots, total_slots, free_slots/(double)total_slots,
2317 goal_ratio, f, total_slots, target_total_slots);
2318 }
2319 }
2320
2321 if (gc_params.growth_max_bytes > 0) {
2322 size_t max_total_slots = total_slots + gc_params.growth_max_bytes / slot_size;
2323 if (target_total_slots > max_total_slots) target_total_slots = max_total_slots;
2324 }
2325
2326 size_t extend_slot_count = target_total_slots - total_slots;
2327 /* Extend by at least 1 page. */
2328 if (extend_slot_count == 0) extend_slot_count = 1;
2329
2330 objspace->heap_pages.allocatable_bytes += extend_slot_count * slot_size;
2331}
2332
2333static inline void
2334heap_add_freepage(rb_heap_t *heap, struct heap_page *page)
2335{
2336 asan_unlock_freelist(page);
2337 GC_ASSERT(page->free_slots != 0);
2338 GC_ASSERT(page->free_region != NULL);
2339
2340 page->free_next = heap->free_pages;
2341 heap->free_pages = page;
2342
2343 RUBY_DEBUG_LOG("page:%p free_region:%p", (void *)page, (void *)page->free_region);
2344
2345 asan_lock_freelist(page);
2346}
2347
2348static inline void
2349heap_add_poolpage(rb_objspace_t *objspace, rb_heap_t *heap, struct heap_page *page)
2350{
2351 asan_unlock_freelist(page);
2352 GC_ASSERT(page->free_slots != 0);
2353 GC_ASSERT(page->free_region != NULL);
2354
2355 page->free_next = heap->pooled_pages;
2356 heap->pooled_pages = page;
2357 objspace->rincgc.pooled_slots += page->free_slots;
2358
2359 asan_lock_freelist(page);
2360}
2361
2362static void
2363heap_unlink_page(rb_objspace_t *objspace, rb_heap_t *heap, struct heap_page *page)
2364{
2365 ccan_list_del(&page->page_node);
2366 heap->total_pages--;
2367 heap->total_slots -= page->total_slots;
2368}
2369
2370static void
2371gc_aligned_free(void *ptr, size_t size)
2372{
2373#if defined __MINGW32__
2374 __mingw_aligned_free(ptr);
2375#elif defined _WIN32
2376 _aligned_free(ptr);
2377#elif defined(HAVE_POSIX_MEMALIGN) || defined(HAVE_MEMALIGN)
2378 free(ptr);
2379#else
2380 free(((void**)ptr)[-1]);
2381#endif
2382}
2383
2384static void
2385heap_page_body_free(struct heap_page_body *page_body, struct page_arena *arena)
2386{
2387 GC_ASSERT((uintptr_t)page_body % HEAP_PAGE_ALIGN == 0);
2388
2389 page_pool_release(page_body, arena);
2390}
2391
2392#ifdef PAGE_POOL_LOCK_ERRORCHECK
2393# define ASSERT_PAGE_POOL_LOCKED(g) GC_ASSERT(pthread_mutex_lock(&(g)->page_pool.lock) == EDEADLK)
2394#else
2395# define ASSERT_PAGE_POOL_LOCKED(g) ((void)0)
2396#endif
2397
2398/* Insert into page_index. Writers serialize on page_pool.lock; lomem and himem are a
2399 * monotonically growing over-approximation used for a quick reject. */
2400static void
2401global_page_index_insert(struct heap_page *page)
2402{
2403 rb_global_objspace_t *g = global_objspace;
2404 uintptr_t body = (uintptr_t)page->body;
2405
2406 rb_native_mutex_lock(&g->page_pool.lock);
2407 if (g->page_index.n_pages == g->page_index.capa) {
2408 size_t new_capa = g->page_index.capa ? g->page_index.capa * 2 : 128;
2409 struct heap_page **grown = realloc(g->page_index.pages, new_capa * sizeof(*grown));
2410 if (grown == NULL) rb_bug("global_page_index_insert: realloc failed");
2411 g->page_index.pages = grown;
2412 g->page_index.capa = new_capa;
2413 }
2414 size_t lo = 0, hi = g->page_index.n_pages;
2415 while (lo < hi) {
2416 size_t mid = (lo + hi) / 2;
2417 if ((uintptr_t)g->page_index.pages[mid]->body < body) lo = mid + 1;
2418 else hi = mid;
2419 }
2420 memmove(&g->page_index.pages[lo + 1], &g->page_index.pages[lo],
2421 (g->page_index.n_pages - lo) * sizeof(struct heap_page *));
2422 g->page_index.pages[lo] = page;
2423 g->page_index.n_pages++;
2424
2425 uintptr_t start = body + sizeof(struct heap_page_header);
2426 uintptr_t end = body + HEAP_PAGE_SIZE;
2427 if (g->page_index.lomem == 0 || g->page_index.lomem > start) g->page_index.lomem = start;
2428 if (g->page_index.himem < end) g->page_index.himem = end;
2429 rb_native_mutex_unlock(&g->page_pool.lock);
2430}
2431
2432static void
2433global_page_index_remove_locked(const struct heap_page *page)
2434{
2435 rb_global_objspace_t *g = global_objspace;
2436 uintptr_t body = (uintptr_t)page->body;
2437
2438 ASSERT_PAGE_POOL_LOCKED(g);
2439
2440 size_t lo = 0, hi = g->page_index.n_pages;
2441 while (lo < hi) {
2442 size_t mid = (lo + hi) / 2;
2443 if ((uintptr_t)g->page_index.pages[mid]->body < body) lo = mid + 1;
2444 else hi = mid;
2445 }
2446 GC_ASSERT(lo < g->page_index.n_pages && g->page_index.pages[lo] == page);
2447 memmove(&g->page_index.pages[lo], &g->page_index.pages[lo + 1],
2448 (g->page_index.n_pages - lo - 1) * sizeof(struct heap_page *));
2449 g->page_index.n_pages--;
2450}
2451
2452static void
2453global_page_index_remove(const struct heap_page *page)
2454{
2455 rb_global_objspace_t *g = global_objspace;
2456
2457 rb_native_mutex_lock(&g->page_pool.lock);
2458 global_page_index_remove_locked(page);
2459 rb_native_mutex_unlock(&g->page_pool.lock);
2460}
2461
2462static void
2463heap_page_free(rb_objspace_t *objspace, struct heap_page *page)
2464{
2465 global_page_index_remove(page);
2466 objspace->heap_pages.freed_pages++;
2467 heap_page_body_free(page->body, page->arena);
2468 free(page);
2469}
2470
2471static void
2472heap_pages_free_batch(rb_objspace_t *objspace, struct heap_page *pages)
2473{
2474 rb_global_objspace_t *g = global_objspace;
2475
2476 rb_native_mutex_lock(&g->page_pool.lock);
2477 for (struct heap_page *page = pages; page != NULL; page = page->free_next) {
2478 global_page_index_remove_locked(page);
2479 if (HEAP_PAGE_ALLOC_USE_MMAP) {
2480#ifdef HAVE_MMAP
2481 page_pool_release_locked(page->body, page->arena);
2482#endif
2483 }
2484 }
2485 rb_native_mutex_unlock(&g->page_pool.lock);
2486
2487 if (!HEAP_PAGE_ALLOC_USE_MMAP) {
2488 /* gc_aligned_free does not need the pool lock. */
2489 for (struct heap_page *page = pages; page != NULL; page = page->free_next) {
2490 heap_page_body_free(page->body, page->arena);
2491 }
2492 }
2493
2494 while (pages != NULL) {
2495 struct heap_page *next = pages->free_next;
2496 objspace->heap_pages.freed_pages++;
2497 free(pages);
2498 pages = next;
2499 }
2500}
2501
2502static void
2503heap_pages_free_unused_pages(rb_objspace_t *objspace)
2504{
2505 if (objspace->empty_pages != NULL && heap_pages_freeable_pages > 0) {
2506 GC_ASSERT(objspace->empty_pages_count > 0);
2507 objspace->empty_pages = NULL;
2508 objspace->empty_pages_count = 0;
2509
2510 size_t i, j;
2511 struct heap_page *to_free = NULL;
2512 for (i = j = 0; i < rb_darray_size(objspace->heap_pages.sorted); i++) {
2513 struct heap_page *page = rb_darray_get(objspace->heap_pages.sorted, i);
2514
2515 if (heap_page_in_global_empty_pages_pool(objspace, page) && heap_pages_freeable_pages > 0) {
2516 page->free_next = to_free;
2517 to_free = page;
2518 heap_pages_freeable_pages--;
2519 }
2520 else {
2521 if (heap_page_in_global_empty_pages_pool(objspace, page)) {
2522 page->free_next = objspace->empty_pages;
2523 objspace->empty_pages = page;
2524 objspace->empty_pages_count++;
2525 }
2526
2527 if (i != j) {
2528 rb_darray_set(objspace->heap_pages.sorted, j, page);
2529 }
2530 j++;
2531 }
2532 }
2533
2534 rb_darray_pop(objspace->heap_pages.sorted, i - j);
2535 GC_ASSERT(rb_darray_size(objspace->heap_pages.sorted) == j);
2536
2537 /* A retire GC can free every page, so an empty objspace is legitimate. */
2538 if (j > 0) {
2539 struct heap_page *hipage = rb_darray_get(objspace->heap_pages.sorted, rb_darray_size(objspace->heap_pages.sorted) - 1);
2540 uintptr_t himem = (uintptr_t)hipage->body + HEAP_PAGE_SIZE;
2541 GC_ASSERT(himem <= heap_pages_himem);
2542 heap_pages_himem = himem;
2543
2544 struct heap_page *lopage = rb_darray_get(objspace->heap_pages.sorted, 0);
2545 uintptr_t lomem = (uintptr_t)lopage->body + sizeof(struct heap_page_header);
2546 GC_ASSERT(lomem >= heap_pages_lomem);
2547 heap_pages_lomem = lomem;
2548 }
2549 else {
2550 heap_pages_lomem = 0;
2551 heap_pages_himem = 0;
2552 }
2553
2554 heap_pages_free_batch(objspace, to_free);
2555 }
2556}
2557
2558static void *
2559gc_aligned_malloc(size_t alignment, size_t size)
2560{
2561 /* alignment must be a power of 2 */
2562 GC_ASSERT(((alignment - 1) & alignment) == 0);
2563 GC_ASSERT(alignment % sizeof(void*) == 0);
2564
2565 void *res;
2566
2567#if defined __MINGW32__
2568 res = __mingw_aligned_malloc(size, alignment);
2569#elif defined _WIN32
2570 res = _aligned_malloc(size, alignment);
2571#elif defined(HAVE_POSIX_MEMALIGN)
2572 if (posix_memalign(&res, alignment, size) != 0) {
2573 return NULL;
2574 }
2575#elif defined(HAVE_MEMALIGN)
2576 res = memalign(alignment, size);
2577#else
2578 char* aligned;
2579 res = malloc(alignment + size + sizeof(void*));
2580 aligned = (char*)res + alignment + sizeof(void*);
2581 aligned -= ((VALUE)aligned & (alignment - 1));
2582 ((void**)aligned)[-1] = res;
2583 res = (void*)aligned;
2584#endif
2585
2586 GC_ASSERT((uintptr_t)res % alignment == 0);
2587
2588 return res;
2589}
2590
2591/* The page pool (global_objspace->page_pool): heap page bodies are carved out of large
2592 * arenas and reused through the pool. Free bodies are split into a small global hot
2593 * list (≤ PAGE_POOL_HOT_MAX, never madvise'd) and per-arena cold freelists (eligible for
2594 * OS release — see page_pool_reclaim). Both lists use an in-body link at offset 0. */
2595
2596#define PAGE_POOL_ARENA_SIZE (HEAP_PAGE_SIZE * 32) /* 2MiB with 64KiB pages */
2597#define PAGE_POOL_ARENA_BODIES (PAGE_POOL_ARENA_SIZE / HEAP_PAGE_SIZE) /* 32 */
2598#define PAGE_POOL_HOT_MAX 0 /* disabled — empty_pages is the retention buffer */
2599#define PAGE_POOL_ARENA_KEEP_HALF (PAGE_POOL_ARENA_BODIES / 2) /* 16 */
2600
2601/* Steal bit 0 of the in-body link word: set iff the body has been madvise'd (cold). */
2602#define PAGE_POOL_ADVISED_BIT ((uintptr_t)1)
2603
2604/* While a body is free, the arena back-pointer is stored at offset sizeof(header) — one
2605 * word past the link, inside the spared first OS page. PAGE_POOL_SCRATCH_SIZE covers
2606 * both the link (offset 0) and the tag for ASAN unpoison. */
2607#define PAGE_POOL_BODY_ARENA(body) \
2608 (*(struct page_arena **)((char *)(body) + sizeof(struct heap_page_header)))
2609#define PAGE_POOL_SCRATCH_SIZE (sizeof(struct heap_page_header) + sizeof(void *))
2610
2611#ifdef HAVE_MMAP
2612/* mmap a new arena to carve from. Called with the pool lock held, at which point the
2613 * previous arena is always fully carved. */
2614static bool
2615page_pool_add_arena(rb_global_objspace_t *g)
2616{
2617 GC_ASSERT(HEAP_PAGE_ALIGN % sysconf(_SC_PAGE_SIZE) == 0);
2618
2619 size_t mmap_size = PAGE_POOL_ARENA_SIZE + HEAP_PAGE_ALIGN;
2620 char *ptr = mmap(NULL, mmap_size,
2621 PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
2622 if (ptr == MAP_FAILED) {
2623 return false;
2624 }
2625
2626 // If we are building `default.c` as part of the ruby executable, we
2627 // may just call `ruby_annotate_mmap`. But if we are building
2628 // `default.c` as a shared library, we will not have access to private
2629 // symbols, and we have to either call prctl directly or make our own
2630 // wrapper.
2631#if defined(HAVE_SYS_PRCTL_H) && defined(PR_SET_VMA) && defined(PR_SET_VMA_ANON_NAME)
2632 prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, ptr, mmap_size, "Ruby:GC:default:page_pool_arena");
2633 errno = 0;
2634#endif
2635
2636 /* Trim the unaligned head and tail so the usable area is HEAP_PAGE_ALIGN aligned. */
2637 char *aligned = ptr + HEAP_PAGE_ALIGN;
2638 aligned -= ((uintptr_t)aligned & (HEAP_PAGE_ALIGN - 1));
2639 GC_ASSERT(aligned > ptr);
2640 GC_ASSERT(aligned <= ptr + HEAP_PAGE_ALIGN);
2641
2642 size_t start_out_of_range_size = aligned - ptr;
2643 GC_ASSERT(start_out_of_range_size % sysconf(_SC_PAGE_SIZE) == 0);
2644 if (start_out_of_range_size > 0) {
2645 if (munmap(ptr, start_out_of_range_size)) {
2646 rb_bug("page_pool_add_arena: munmap failed for start");
2647 }
2648 }
2649
2650 size_t end_out_of_range_size = HEAP_PAGE_ALIGN - start_out_of_range_size;
2651 GC_ASSERT(end_out_of_range_size % sysconf(_SC_PAGE_SIZE) == 0);
2652 if (end_out_of_range_size > 0) {
2653 if (munmap(aligned + PAGE_POOL_ARENA_SIZE, end_out_of_range_size)) {
2654 rb_bug("page_pool_add_arena: munmap failed for end");
2655 }
2656 }
2657
2658 struct page_arena *arena = calloc1(sizeof(struct page_arena));
2659 if (arena == NULL) {
2660 if (munmap(aligned, PAGE_POOL_ARENA_SIZE)) {
2661 rb_bug("page_pool_add_arena: munmap failed for arena");
2662 }
2663 return false;
2664 }
2665 arena->start = aligned;
2666 arena->size = PAGE_POOL_ARENA_SIZE;
2667 arena->cold_freelist = NULL;
2668 arena->free_count = 0;
2669 arena->cold_count = 0;
2670 arena->next = g->page_pool.arenas;
2671 g->page_pool.arenas = arena;
2672 g->page_pool.arena_count++;
2673 g->page_pool.arena_current = arena;
2674
2675 g->page_pool.arena_cursor = aligned;
2676 g->page_pool.arena_end = aligned + PAGE_POOL_ARENA_SIZE;
2677
2678 return true;
2679}
2680#endif
2681
2682static struct heap_page_body *
2683page_pool_acquire(struct page_arena **arena_out)
2684{
2685 struct heap_page_body *body = NULL;
2686
2687 if (HEAP_PAGE_ALLOC_USE_MMAP) {
2688#ifdef HAVE_MMAP
2689 bool need_reuse = false;
2690 rb_global_objspace_t *g = global_objspace;
2691
2692 rb_native_mutex_lock(&g->page_pool.lock);
2693 if (g->page_pool.hot_list != NULL) {
2694 body = g->page_pool.hot_list;
2695 asan_unpoison_memory_region(body, PAGE_POOL_SCRATCH_SIZE, false);
2696 uintptr_t link = *(uintptr_t *)body;
2697 g->page_pool.hot_list = (struct heap_page_body *)(link & ~PAGE_POOL_ADVISED_BIT);
2698 g->page_pool.hot_count--;
2699 struct page_arena *arena = PAGE_POOL_BODY_ARENA(body);
2700 arena->free_count--;
2701 *arena_out = arena;
2702 }
2703 else {
2704 // find cold page body (madvised reusable)
2705 for (struct page_arena *a = g->page_pool.arenas; a; a = a->next) {
2706 if (a->cold_count > 0) {
2707 body = a->cold_freelist;
2708 asan_unpoison_memory_region(body, PAGE_POOL_SCRATCH_SIZE, false);
2709 uintptr_t link = *(uintptr_t *)body;
2710 a->cold_freelist = (struct heap_page_body *)(link & ~PAGE_POOL_ADVISED_BIT);
2711 a->cold_count--;
2712 a->free_count--;
2713 *arena_out = a;
2714 need_reuse = (link & PAGE_POOL_ADVISED_BIT) != 0;
2715 if (need_reuse) g->page_pool.advised_count--;
2716 break;
2717 }
2718 }
2719 if (body == NULL &&
2720 (g->page_pool.arena_cursor != g->page_pool.arena_end ||
2721 page_pool_add_arena(g))) {
2722 GC_ASSERT(g->page_pool.arena_cursor + HEAP_PAGE_SIZE <= g->page_pool.arena_end);
2723 body = (struct heap_page_body *)g->page_pool.arena_cursor;
2724 g->page_pool.arena_cursor += HEAP_PAGE_SIZE;
2725 *arena_out = g->page_pool.arena_current;
2726 }
2727 }
2728 rb_native_mutex_unlock(&g->page_pool.lock);
2729
2730 if (body != NULL) {
2731 if (need_reuse) {
2732 rb_vm_map_reuse((char *)body + g->page_pool.os_page_size,
2733 HEAP_PAGE_SIZE - g->page_pool.os_page_size);
2734 }
2735 asan_unpoison_memory_region(body, HEAP_PAGE_SIZE, false);
2736 }
2737#endif
2738 }
2739 else {
2740 body = gc_aligned_malloc(HEAP_PAGE_ALIGN, HEAP_PAGE_SIZE);
2741 *arena_out = NULL;
2742 }
2743
2744 return body;
2745}
2746
2747#ifdef HAVE_MMAP
2748static void
2749page_pool_release_locked(struct heap_page_body *body, struct page_arena *arena)
2750{
2751 rb_global_objspace_t *g = global_objspace;
2752
2753 ASSERT_PAGE_POOL_LOCKED(g);
2754
2755 /* A body in the empty-pages pool stays fully poisoned (see gc_sweep_page), so
2756 * unpoison the scratch area (link + arena tag) before writing. */
2757 asan_unpoison_memory_region(body, PAGE_POOL_SCRATCH_SIZE, false);
2758 arena->free_count++;
2759 PAGE_POOL_BODY_ARENA(body) = arena;
2760 if (g->page_pool.hot_count < PAGE_POOL_HOT_MAX) {
2761 *(uintptr_t *)body = (uintptr_t)g->page_pool.hot_list;
2762 g->page_pool.hot_list = body;
2763 g->page_pool.hot_count++;
2764 }
2765 else {
2766 *(uintptr_t *)body = (uintptr_t)arena->cold_freelist;
2767 arena->cold_freelist = body;
2768 arena->cold_count++;
2769 }
2770 asan_poison_memory_region(body, HEAP_PAGE_SIZE);
2771}
2772#endif
2773
2774static void
2775page_pool_release(struct heap_page_body *body, struct page_arena *arena)
2776{
2777 if (HEAP_PAGE_ALLOC_USE_MMAP) {
2778#ifdef HAVE_MMAP
2779 rb_global_objspace_t *g = global_objspace;
2780
2781 rb_native_mutex_lock(&g->page_pool.lock);
2782 page_pool_release_locked(body, arena);
2783 rb_native_mutex_unlock(&g->page_pool.lock);
2784#endif
2785 }
2786 else {
2787 gc_aligned_free(body, HEAP_PAGE_SIZE);
2788 }
2789}
2790
2791/* Allow the OS to reclaim pool memory. Runs only at major GC in single-objspace mode
2792 * (see gc_sweep_finish).
2793 *
2794 * Step A: madvise cold bodies, sparing the first OS page (which holds the in-body
2795 * freelist link and arena tag).
2796 *
2797 * Step B: munmap arenas whose 32 bodies are all free, keeping one extra empty
2798 * arena as a retention buffer when the remaining free pool is < half an arena. */
2799static void
2800page_pool_reclaim(rb_global_objspace_t *g)
2801{
2802 if (!HEAP_PAGE_ALLOC_USE_MMAP) return;
2803#ifdef HAVE_MMAP
2804 size_t os_page_size = g->page_pool.os_page_size;
2805
2806 rb_native_mutex_lock(&g->page_pool.lock);
2807
2808 /* Advising spares the first OS page of a body (it holds the in-body freelist link
2809 * and the arena tag), so it needs sub-page granularity: when the OS page size is
2810 * >= HEAP_PAGE_SIZE (e.g. 64KiB pages on aarch64) no body is ever advised, and
2811 * advised_count must not be adjusted anywhere either. */
2812 const bool can_advise = os_page_size < HEAP_PAGE_SIZE;
2813
2814 /* Step A — advise cold bodies (immediate release: drop RSS now if the platform allows). */
2815 if (can_advise) {
2816 for (struct page_arena *a = g->page_pool.arenas; a; a = a->next) {
2817 for (struct heap_page_body *body = a->cold_freelist; body; ) {
2818 asan_unpoison_memory_region(body, PAGE_POOL_SCRATCH_SIZE, false);
2819 uintptr_t link = *(uintptr_t *)body;
2820 struct heap_page_body *next =
2821 (struct heap_page_body *)(link & ~PAGE_POOL_ADVISED_BIT);
2822 if (!(link & PAGE_POOL_ADVISED_BIT)) {
2823 rb_vm_map_reusable_immediate((char *)body + os_page_size,
2824 HEAP_PAGE_SIZE - os_page_size, 0);
2825 *(uintptr_t *)body = link | PAGE_POOL_ADVISED_BIT;
2826 g->page_pool.advised_count++;
2827 }
2828 asan_poison_memory_region(body, PAGE_POOL_SCRATCH_SIZE);
2829 body = next;
2830 }
2831 }
2832 }
2833
2834 /* Step B — munmap fully-free arenas (with retention buffer).
2835 *
2836 * total_free = Σ free_count; free_count already includes hot-list bodies
2837 * (page_pool_release increments it unconditionally), so no separate hot_count.
2838 * An arena is eligible when all 32 of its bodies are free AND none sit on
2839 * the hot list (≤5 entries, pre-scanned). Keep one extra empty arena when
2840 * the rest of the free pool is < half an arena, to avoid thrash. */
2841 int total_free = 0;
2842 for (struct page_arena *a = g->page_pool.arenas; a; a = a->next) {
2843 total_free += a->free_count;
2844 }
2845
2846 struct page_arena *hot_arenas[PAGE_POOL_HOT_MAX ? PAGE_POOL_HOT_MAX : 1];
2847 int n_hot_arenas = 0;
2848 /* Collect arenas that have a hot body (≤ PAGE_POOL_HOT_MAX entries). */
2849 for (struct heap_page_body *body = g->page_pool.hot_list; body; ) {
2850 asan_unpoison_memory_region(body, PAGE_POOL_SCRATCH_SIZE, false);
2851 uintptr_t link = *(uintptr_t *)body;
2852 struct heap_page_body *next =
2853 (struct heap_page_body *)(link & ~PAGE_POOL_ADVISED_BIT);
2854 struct page_arena *arena = PAGE_POOL_BODY_ARENA(body);
2855 bool found = false;
2856 for (int i = 0; i < n_hot_arenas; i++) {
2857 if (hot_arenas[i] == arena) { found = true; break; }
2858 }
2859 if (!found && n_hot_arenas < PAGE_POOL_HOT_MAX) {
2860 hot_arenas[n_hot_arenas++] = arena;
2861 }
2862 asan_poison_memory_region(body, PAGE_POOL_SCRATCH_SIZE);
2863 body = next;
2864 }
2865
2866 bool retained_one = false;
2867 struct page_arena **pp = &g->page_pool.arenas;
2868 // munmap fully free arenas
2869 while (*pp) {
2870 struct page_arena *a = *pp;
2871 bool has_hot = false;
2872 for (int i = 0; i < n_hot_arenas; i++) {
2873 if (hot_arenas[i] == a) { has_hot = true; break; }
2874 }
2875 if (a->free_count != PAGE_POOL_ARENA_BODIES || has_hot) {
2876 pp = &a->next;
2877 continue;
2878 }
2879 GC_ASSERT(a->cold_count == PAGE_POOL_ARENA_BODIES);
2880
2881 int free_elsewhere = total_free - PAGE_POOL_ARENA_BODIES;
2882 if (free_elsewhere < PAGE_POOL_ARENA_KEEP_HALF && !retained_one) {
2883 retained_one = true;
2884 pp = &a->next;
2885 continue;
2886 }
2887
2888 *pp = a->next;
2889 if (munmap(a->start, a->size)) {
2890 rb_bug("page_pool_reclaim: munmap failed");
2891 }
2892 total_free -= PAGE_POOL_ARENA_BODIES;
2893 /* Every body of this arena is on its cold freelist, so Step A above has just
2894 * advised all of them -- but only if this platform can advise at all. */
2895 if (can_advise) {
2896 g->page_pool.advised_count -= PAGE_POOL_ARENA_BODIES;
2897 GC_ASSERT(g->page_pool.advised_count >= 0);
2898 }
2899 g->page_pool.arena_count--;
2900 g->page_pool.arenas_unmapped++;
2901 if (a == g->page_pool.arena_current) {
2902 // During next acquire, any remaining arenas that have cold bodies are used. This is guaranteed
2903 // because of the retention buffer.
2904 g->page_pool.arena_current = NULL;
2905 g->page_pool.arena_cursor = NULL;
2906 g->page_pool.arena_end = NULL;
2907 }
2908 free(a);
2909 }
2910
2911 rb_native_mutex_unlock(&g->page_pool.lock);
2912#endif
2913}
2914
2915static struct heap_page_body *
2916heap_page_body_allocate(struct page_arena **arena_out)
2917{
2918 struct heap_page_body *page_body = page_pool_acquire(arena_out);
2919
2920 GC_ASSERT(page_body == NULL || (uintptr_t)page_body % HEAP_PAGE_ALIGN == 0);
2921
2922 return page_body;
2923}
2924
2925static struct heap_page *
2926heap_page_resurrect(rb_objspace_t *objspace)
2927{
2928 struct heap_page *page = NULL;
2929 if (objspace->empty_pages == NULL) {
2930 GC_ASSERT(objspace->empty_pages_count == 0);
2931 }
2932 else {
2933 GC_ASSERT(objspace->empty_pages_count > 0);
2934 objspace->empty_pages_count--;
2935 page = objspace->empty_pages;
2936 objspace->empty_pages = page->free_next;
2937 /* Clear the flags left over from emptying the page before reusing it, or the
2938 * shareable and shref scans would keep walking an empty bitmap forever. */
2939 page->flags.has_shareable_objects = FALSE;
2940 page->flags.has_shref_objects = FALSE;
2941 }
2942
2943 return page;
2944}
2945
2946static struct heap_page *
2947heap_page_allocate(rb_objspace_t *objspace)
2948{
2949 struct page_arena *arena;
2950 struct heap_page_body *page_body = heap_page_body_allocate(&arena);
2951 if (page_body == 0) {
2952 rb_memerror();
2953 }
2954
2955 struct heap_page *page = calloc1(sizeof(struct heap_page));
2956 if (page == 0) {
2957 heap_page_body_free(page_body, arena);
2958 rb_memerror();
2959 }
2960
2961 uintptr_t start = (uintptr_t)page_body + sizeof(struct heap_page_header);
2962 uintptr_t end = (uintptr_t)page_body + HEAP_PAGE_SIZE;
2963
2964 size_t lo = 0;
2965 size_t hi = rb_darray_size(objspace->heap_pages.sorted);
2966 while (lo < hi) {
2967 struct heap_page *mid_page;
2968
2969 size_t mid = (lo + hi) / 2;
2970 mid_page = rb_darray_get(objspace->heap_pages.sorted, mid);
2971 if ((uintptr_t)mid_page->start < start) {
2972 lo = mid + 1;
2973 }
2974 else if ((uintptr_t)mid_page->start > start) {
2975 hi = mid;
2976 }
2977 else {
2978 rb_bug("same heap page is allocated: %p at %"PRIuVALUE, (void *)page_body, (VALUE)mid);
2979 }
2980 }
2981
2982 rb_darray_insert_without_gc(&objspace->heap_pages.sorted, hi, page);
2983
2984 if (heap_pages_lomem == 0 || heap_pages_lomem > start) heap_pages_lomem = start;
2985 if (heap_pages_himem < end) heap_pages_himem = end;
2986
2987 page->body = page_body;
2988 page->arena = arena;
2989 page_body->header.page = page;
2990 page->objspace = objspace;
2991
2992 objspace->heap_pages.allocated_pages++;
2993
2994 global_page_index_insert(page);
2995
2996 return page;
2997}
2998
2999static void
3000heap_add_page(rb_objspace_t *objspace, rb_heap_t *heap, struct heap_page *page)
3001{
3002 /* Adding to eden heap during incremental sweeping is forbidden */
3003 GC_ASSERT(!heap->sweeping_page);
3004 GC_ASSERT(heap_page_in_global_empty_pages_pool(objspace, page));
3005
3006 /* Align start to slot_size boundary */
3007 uintptr_t start = (uintptr_t)page->body + sizeof(struct heap_page_header);
3008 uintptr_t rem = start % heap->slot_size;
3009 if (rem) start += heap->slot_size - rem;
3010
3011 int slot_count = (int)((HEAP_PAGE_SIZE - (start - (uintptr_t)page->body))/heap->slot_size);
3012
3013 page->start = start;
3014 page->total_slots = slot_count;
3015 page->slot_size = heap->slot_size;
3016 page->slot_size_reciprocal = heap_slot_reciprocal_table[heap - heaps];
3017 page->heap = heap;
3018
3019 memset(&page->wb_unprotected_bits[0], 0, HEAP_PAGE_BITMAP_SIZE);
3020 memset(&page->age_bits[0], 0, sizeof(page->age_bits));
3021
3022 asan_unlock_freelist(page);
3023 asan_unpoison_memory_region(page->body, HEAP_PAGE_SIZE, false);
3024
3025 uintptr_t slots_end = start + (uintptr_t)slot_count * heap->slot_size;
3026
3027 memset((void *)start, 0, slots_end - start);
3028
3029 struct free_region *region = (struct free_region *)start;
3030 region->flags = 0;
3031 region->end = slots_end;
3032 region->next = NULL;
3033 page->free_region = region;
3034
3035 /* Poison every free slot; each is unpoisoned again as it is handed out. */
3036 for (uintptr_t p = start; p < slots_end; p += heap->slot_size) {
3037 rb_asan_poison_object((VALUE)p);
3038 }
3039 asan_lock_freelist(page);
3040
3041 page->free_slots = slot_count;
3042
3043 heap->total_allocated_pages++;
3044
3045 ccan_list_add_tail(&heap->pages, &page->page_node);
3046 heap->total_pages++;
3047 heap->total_slots += page->total_slots;
3048}
3049
3050static int
3051heap_page_allocate_and_initialize(rb_objspace_t *objspace, rb_heap_t *heap)
3052{
3053 gc_report(1, objspace, "heap_page_allocate_and_initialize: rb_darray_size(objspace->heap_pages.sorted): %"PRIdSIZE", "
3054 "allocatable_bytes: %"PRIdSIZE", heap->total_pages: %"PRIdSIZE"\n",
3055 rb_darray_size(objspace->heap_pages.sorted), objspace->heap_pages.allocatable_bytes, heap->total_pages);
3056
3057 bool allocated = false;
3058 struct heap_page *page = heap_page_resurrect(objspace);
3059
3060 if (page == NULL && objspace->heap_pages.allocatable_bytes > 0) {
3061 page = heap_page_allocate(objspace);
3062 allocated = true;
3063
3064 GC_ASSERT(page != NULL);
3065 }
3066
3067 if (page != NULL) {
3068 heap_add_page(objspace, heap, page);
3069 heap_add_freepage(heap, page);
3070
3071 if (allocated) {
3072 size_t page_bytes = (size_t)page->total_slots * page->slot_size;
3073 if (objspace->heap_pages.allocatable_bytes > page_bytes) {
3074 objspace->heap_pages.allocatable_bytes -= page_bytes;
3075 }
3076 else {
3077 objspace->heap_pages.allocatable_bytes = 0;
3078 }
3079 }
3080 }
3081
3082 return page != NULL;
3083}
3084
3085static void
3086heap_page_allocate_and_initialize_force(rb_objspace_t *objspace, rb_heap_t *heap)
3087{
3088 size_t prev_allocatable_bytes = objspace->heap_pages.allocatable_bytes;
3089 objspace->heap_pages.allocatable_bytes = HEAP_PAGE_SIZE;
3090 heap_page_allocate_and_initialize(objspace, heap);
3091 GC_ASSERT(heap->free_pages != NULL);
3092 objspace->heap_pages.allocatable_bytes = prev_allocatable_bytes;
3093}
3094
3095static void
3096gc_continue(rb_objspace_t *objspace, rb_heap_t *heap)
3097{
3098 unsigned int lock_lev;
3099 bool needs_gc = is_incremental_marking(objspace) || needs_continue_sweeping(objspace, heap);
3100 if (!needs_gc) return;
3101
3102 gc_enter(objspace, gc_enter_event_continue, &lock_lev); // takes vm barrier, try to avoid
3103
3104 /* Continue marking if in incremental marking. */
3105 if (is_incremental_marking(objspace)) {
3106 if (gc_marks_continue(objspace, heap)) {
3107 gc_sweep(objspace);
3108 }
3109 }
3110
3111 if (needs_continue_sweeping(objspace, heap)) {
3112 gc_sweep_continue(objspace, heap);
3113 }
3114
3115 gc_exit(objspace, gc_enter_event_continue, &lock_lev);
3116}
3117
3118static void
3119heap_prepare(rb_objspace_t *objspace, rb_heap_t *heap)
3120{
3121 GC_ASSERT(heap->free_pages == NULL);
3122
3123 if (heap->total_slots < objspace_heap_init_bytes(objspace) / heap->slot_size &&
3124 heap->sweeping_page == NULL) {
3125 heap_page_allocate_and_initialize_force(objspace, heap);
3126 GC_ASSERT(heap->free_pages != NULL);
3127 return;
3128 }
3129
3130 /* Continue incremental marking or lazy sweeping, if in any of those steps. */
3131 gc_continue(objspace, heap);
3132
3133 if (heap->free_pages == NULL) {
3134 heap_page_allocate_and_initialize(objspace, heap);
3135 }
3136
3137 /* If we still don't have a free page and not allowed to create a new page,
3138 * we should start a new GC cycle. */
3139 if (heap->free_pages == NULL) {
3140 GC_ASSERT(objspace->empty_pages_count == 0);
3141 GC_ASSERT(objspace->heap_pages.allocatable_bytes == 0);
3142
3143 if (gc_start(objspace, GPR_FLAG_NEWOBJ) == FALSE) {
3144 rb_memerror();
3145 }
3146 else {
3147 if (objspace->heap_pages.allocatable_bytes == 0 && !gc_config_full_mark_val) {
3148 heap_allocatable_bytes_expand(objspace, heap,
3149 heap->freed_slots + heap->empty_slots,
3150 heap->total_slots, heap->slot_size);
3151 GC_ASSERT(objspace->heap_pages.allocatable_bytes > 0);
3152 }
3153 /* Do steps of incremental marking or lazy sweeping if the GC run permits. */
3154 gc_continue(objspace, heap);
3155
3156 /* If we're not incremental marking (e.g. a minor GC) or finished
3157 * sweeping and still don't have a free page, then
3158 * gc_sweep_finish_heap should allow us to create a new page. */
3159 if (heap->free_pages == NULL && !heap_page_allocate_and_initialize(objspace, heap)) {
3160 if (gc_needs_major_flags == GPR_FLAG_NONE) {
3161 rb_bug("cannot create a new page after GC");
3162 }
3163 else { // Major GC is required, which will allow us to create new page
3164 if (gc_start(objspace, GPR_FLAG_NEWOBJ) == FALSE) {
3165 rb_memerror();
3166 }
3167 else {
3168 /* Do steps of incremental marking or lazy sweeping. */
3169 gc_continue(objspace, heap);
3170
3171 if (heap->free_pages == NULL &&
3172 !heap_page_allocate_and_initialize(objspace, heap)) {
3173 rb_bug("cannot create a new page after major GC");
3174 }
3175 }
3176 }
3177 }
3178 }
3179 }
3180
3181 GC_ASSERT(heap->free_pages != NULL);
3182}
3183
3184#if GC_DEBUG
3185static inline const char*
3186rb_gc_impl_source_location_cstr(int *ptr)
3187{
3188 /* We could directly refer `rb_source_location_cstr()` before, but not any
3189 * longer. We have to heavy lift using our debugging API. */
3190 if (! ptr) {
3191 return NULL;
3192 }
3193 else if (! (*ptr = rb_sourceline())) {
3194 return NULL;
3195 }
3196 else {
3197 return rb_sourcefile();
3198 }
3199}
3200#endif
3201
3202static inline VALUE
3203newobj_init(VALUE klass, VALUE flags, int wb_protected, rb_objspace_t *objspace, VALUE obj)
3204{
3205 GC_ASSERT(BUILTIN_TYPE(obj) == T_NONE);
3206 GC_ASSERT((flags & FL_WB_PROTECTED) == 0);
3207 RBASIC(obj)->flags = flags;
3208 *((VALUE *)&RBASIC(obj)->klass) = klass;
3209#if RBASIC_SHAPE_ID_FIELD
3210 RBASIC(obj)->shape_id = 0;
3211#endif
3212
3213 if (RB_UNLIKELY(flags & RUBY_FL_SHAREABLE)) {
3214 /* A born-shareable object must be WB protected: the shref and remembered-set
3215 * rules for shareable objects assume the write barrier. A local GC roots
3216 * shareable objects from this bit (pinned_roots_mark). */
3217 GC_ASSERT(wb_protected);
3218 gc_page_add_shareable(GET_HEAP_PAGE(obj), obj);
3219 }
3220
3221#if RGENGC_CHECK_MODE
3222 int lev = RB_GC_VM_LOCK_NO_BARRIER();
3223 {
3224 check_rvalue_consistency(objspace, obj);
3225
3226 GC_ASSERT(RVALUE_MARKED(objspace, obj) == FALSE);
3227 GC_ASSERT(RVALUE_MARKING(objspace, obj) == FALSE);
3228 GC_ASSERT(RVALUE_OLD_P(objspace, obj) == FALSE);
3229 GC_ASSERT(RVALUE_WB_UNPROTECTED(objspace, obj) == FALSE);
3230
3231 if (RVALUE_REMEMBERED(objspace, obj)) rb_bug("newobj: %s is remembered.", rb_obj_info(obj));
3232 }
3233 RB_GC_VM_UNLOCK_NO_BARRIER(lev);
3234#endif
3235
3236 if (RB_UNLIKELY(wb_protected == FALSE)) {
3237 MARK_IN_BITMAP(GET_HEAP_WB_UNPROTECTED_BITS(obj), obj);
3238 }
3239
3240#if RGENGC_PROFILE
3241 if (wb_protected) {
3242 objspace->profile.total_generated_normal_object_count++;
3243#if RGENGC_PROFILE >= 2
3244 objspace->profile.generated_normal_object_count_types[BUILTIN_TYPE(obj)]++;
3245#endif
3246 }
3247 else {
3248 objspace->profile.total_generated_shady_object_count++;
3249#if RGENGC_PROFILE >= 2
3250 objspace->profile.generated_shady_object_count_types[BUILTIN_TYPE(obj)]++;
3251#endif
3252 }
3253#endif
3254
3255#if GC_DEBUG
3256 GET_RVALUE_OVERHEAD(obj)->file = rb_gc_impl_source_location_cstr(&GET_RVALUE_OVERHEAD(obj)->line);
3257 GC_ASSERT(!SPECIAL_CONST_P(obj)); /* check alignment */
3258#endif
3259
3260 gc_report(5, objspace, "newobj: %s\n", rb_obj_info(obj));
3261
3262 // RUBY_DEBUG_LOG("obj:%p (%s)", (void *)obj, rb_obj_info(obj));
3263 return obj;
3264}
3265
3266size_t
3267rb_gc_impl_obj_slot_size(VALUE obj)
3268{
3269 return GET_HEAP_PAGE(obj)->slot_size - RVALUE_OVERHEAD;
3270}
3271
3272bool
3273rb_gc_impl_pinned_p(void *objspace_ptr, VALUE obj)
3274{
3275 return RVALUE_PINNED((rb_objspace_t *)objspace_ptr, obj);
3276}
3277
3278static inline size_t
3279heap_slot_size(unsigned char pool_id)
3280{
3281 GC_ASSERT(pool_id < HEAP_COUNT);
3282
3283 return pool_slot_sizes[pool_id] - RVALUE_OVERHEAD;
3284}
3285
3286size_t
3287rb_gc_impl_max_allocation_size(void)
3288{
3289 return heap_slot_size(HEAP_COUNT - 1);
3290}
3291
3292bool
3293rb_gc_impl_size_allocatable_p(size_t size)
3294{
3295 return size <= rb_gc_impl_max_allocation_size();
3296}
3297
3298static inline bool
3299heap_advance_region(rb_heap_t *heap)
3300{
3301 struct free_region *region = heap->newobj.alloc_next_region;
3302 if (region == NULL) {
3303 return false;
3304 }
3305
3306 rb_asan_unpoison_object((VALUE)region, false);
3307 GC_ASSERT(RB_TYPE_P((VALUE)region, T_NONE));
3308 heap->newobj.alloc_cursor = (uintptr_t)region;
3309 heap->newobj.alloc_cursor_end = region->end;
3310 heap->newobj.alloc_next_region = region->next;
3311 rb_asan_poison_object((VALUE)region);
3312
3313 return true;
3314}
3315
3316/* The whole region is ours until the next refill, so charge it to the step now. */
3317static inline void
3318heap_charge_region(rb_objspace_t *objspace, const rb_heap_t *heap, size_t heap_idx)
3319{
3320 objspace->incremental_mark_step_allocated_slots +=
3321 (heap->newobj.alloc_cursor_end - heap->newobj.alloc_cursor) / pool_slot_sizes[heap_idx];
3322}
3323
3324static inline VALUE
3325heap_alloc_slot(rb_objspace_t *objspace, size_t heap_idx)
3326{
3327 rb_heap_t *heap = &heaps[heap_idx];
3328
3329 uintptr_t cursor = heap->newobj.alloc_cursor;
3330 if (RB_UNLIKELY(cursor >= heap->newobj.alloc_cursor_end)) {
3331 /* Marking owes us a step before the next region, and newobj_refill runs it. */
3332 if (RB_UNLIKELY(is_incremental_marking(objspace)) ||
3333 heap_advance_region(heap) == false) {
3334 return Qfalse;
3335 }
3336 cursor = heap->newobj.alloc_cursor;
3337 }
3338
3339 VALUE obj = (VALUE)cursor;
3340 rb_asan_unpoison_object(obj, true);
3341 heap->newobj.alloc_cursor = cursor + pool_slot_sizes[heap_idx];
3342
3343 /* Single writer (the owning Ractor under the GVL), so a plain increment is enough. */
3344 heap->total_allocated_objects++;
3345
3346#if RGENGC_CHECK_MODE
3347 GC_ASSERT(rb_gc_impl_obj_slot_size(obj) == heap_slot_size(heap_idx));
3348 // zero clear
3349 MEMZERO((char *)obj, char, heap_slot_size(heap_idx));
3350#endif
3351 return obj;
3352}
3353
3354static struct heap_page *
3355heap_next_free_page(rb_objspace_t *objspace, rb_heap_t *heap)
3356{
3357 struct heap_page *page;
3358
3359 if (heap->free_pages == NULL) {
3360 heap_prepare(objspace, heap);
3361 }
3362
3363 page = heap->free_pages;
3364 heap->free_pages = page->free_next;
3365
3366 GC_ASSERT(page->free_slots != 0);
3367
3368 asan_unlock_freelist(page);
3369
3370 return page;
3371}
3372
3373static inline void
3374heap_set_alloc_page(rb_objspace_t *objspace, size_t heap_idx, struct heap_page *page)
3375{
3376 gc_report(3, objspace, "heap_set_alloc_page: Using page %p\n", (void *)page->body);
3377
3378 rb_heap_t *heap = &heaps[heap_idx];
3379
3380 GC_ASSERT(heap->newobj.alloc_cursor >= heap->newobj.alloc_cursor_end);
3381 GC_ASSERT(heap->newobj.alloc_next_region == NULL);
3382 GC_ASSERT(page->free_slots != 0);
3383 GC_ASSERT(page->free_region != NULL);
3384
3385 heap->newobj.alloc_using_page = page;
3386
3387 struct free_region *region = page->free_region;
3388 rb_asan_unpoison_object((VALUE)region, false);
3389 GC_ASSERT(RB_TYPE_P((VALUE)region, T_NONE));
3390 heap->newobj.alloc_cursor = (uintptr_t)region;
3391 heap->newobj.alloc_cursor_end = region->end;
3392 heap->newobj.alloc_next_region = region->next;
3393 rb_asan_poison_object((VALUE)region);
3394
3395 page->free_slots = 0;
3396 page->free_region = NULL;
3397}
3398
3399static void
3400init_size_to_heap_idx(void)
3401{
3402 /* Process-wide and immutable, so build it once at boot. A rebuild in a later
3403 * objspace_init would write the same values but race other threads' lock-free
3404 * allocation-fastpath reads. */
3405 static bool initialized = false;
3406 if (initialized) return;
3407 initialized = true;
3408
3409 for (size_t i = 0; i < sizeof(size_to_heap_idx); i++) {
3410 size_t effective = i * 8 + RVALUE_OVERHEAD;
3411 uint8_t idx;
3412 for (idx = 0; idx < HEAP_COUNT; idx++) {
3413 if (effective <= pool_slot_sizes[idx]) break;
3414 }
3415 size_to_heap_idx[i] = idx;
3416 }
3417}
3418
3419static inline size_t
3420heap_idx_for_size(size_t size)
3421{
3422 size_t compressed = (size + 7) >> 3;
3423 if (compressed < sizeof(size_to_heap_idx)) {
3424 size_t heap_idx = size_to_heap_idx[compressed];
3425 if (RB_LIKELY(heap_idx < HEAP_COUNT)) return heap_idx;
3426 }
3427
3428 rb_bug("heap_idx_for_size: allocation size too large "
3429 "(size=%"PRIuSIZE")", size);
3430}
3431
3432size_t
3433rb_gc_impl_size_slot_size(void *objspace_ptr, size_t size)
3434{
3435 return heap_slot_size((unsigned char)heap_idx_for_size(size));
3436}
3437
3438bool
3439rb_gc_impl_zjit_new_obj_fastpath(void *objspace_ptr, size_t alloc_size, VALUE flags, VALUE klass,
3440 struct rb_gc_zjit_fastpath *fastpath)
3441{
3442#if USE_ZJIT
3443 size_t heap_idx = 0;
3444 size_t slot_size = 0;
3445 for (; heap_idx < HEAP_COUNT; heap_idx++) {
3446 if (alloc_size + RVALUE_OVERHEAD <= pool_slot_sizes[heap_idx]) {
3447 slot_size = pool_slot_sizes[heap_idx];
3448 break;
3449 }
3450 }
3451 if (slot_size == 0) return false;
3452
3453#undef heaps
3454 size_t base = offsetof(rb_objspace_t, heaps)
3455 + heap_idx * sizeof(rb_heap_t)
3456 + offsetof(rb_heap_t, newobj);
3457#define heaps objspace->heaps
3458
3459 struct rb_gc_zjit_default_new_obj_fastpath default_fastpath = {
3460 base + offsetof(rb_heap_newobj_t, alloc_cursor),
3461 base + offsetof(rb_heap_newobj_t, alloc_cursor_end),
3462 slot_size,
3463 base - offsetof(rb_heap_t, newobj) + offsetof(rb_heap_t, total_allocated_objects),
3464 flags,
3465 klass
3466 };
3467
3468 memset(fastpath, 0, sizeof(*fastpath));
3469 fastpath->kind = RB_GC_ZJIT_FASTPATH_DEFAULT;
3470 memcpy(fastpath->data.words, &default_fastpath, sizeof(default_fastpath));
3471
3472 return true;
3473#else
3474 return false;
3475#endif
3476}
3477
3478NOINLINE(static VALUE newobj_refill(rb_objspace_t *objspace, size_t heap_idx));
3479
3480static VALUE
3481newobj_refill(rb_objspace_t *objspace, size_t heap_idx)
3482{
3483 rb_heap_t *heap = &heaps[heap_idx];
3484 VALUE obj = Qfalse;
3485
3486 /* No lock: a heap is single-writer (its owner thread, serialized by the GVL inside
3487 * the Ractor), the page pool has its own mutex, and a GC started from here takes
3488 * whatever gc_enter needs. */
3489 if (is_incremental_marking(objspace)) {
3490 /* The fast path sends us here at every region, which is far more often than the
3491 * step size, so step only once the regions add up to it. */
3492 if (objspace->incremental_mark_step_allocated_slots >= INCREMENTAL_MARK_STEP_ALLOCATIONS) {
3493 gc_continue(objspace, heap);
3494 objspace->incremental_mark_step_allocated_slots = 0;
3495 }
3496
3497 // Move on to the region the fast path refused to take
3498 if (heap_advance_region(heap)) {
3499 heap_charge_region(objspace, heap, heap_idx);
3500 obj = heap_alloc_slot(objspace, heap_idx);
3501 }
3502 }
3503
3504 if (obj == Qfalse) {
3505 // Get next free page (possibly running GC)
3506 struct heap_page *page = heap_next_free_page(objspace, heap);
3507 heap_set_alloc_page(objspace, heap_idx, page);
3508 heap_charge_region(objspace, heap, heap_idx);
3509
3510 // Retry allocation after moving to new page
3511 obj = heap_alloc_slot(objspace, heap_idx);
3512 }
3513
3514 if (RB_UNLIKELY(obj == Qfalse)) {
3515 rb_memerror();
3516 }
3517 return obj;
3518}
3519
3520static VALUE
3521newobj_alloc(rb_objspace_t *objspace, size_t heap_idx)
3522{
3523 /* The objspace belongs to the current Ractor and is single-writer, so the fast path
3524 * needs no lock. Stress GC runs in the caller's slow path, before newobj_alloc. */
3525 VALUE obj = heap_alloc_slot(objspace, heap_idx);
3526
3527 if (RB_UNLIKELY(obj == Qfalse)) {
3528 obj = newobj_refill(objspace, heap_idx);
3529 }
3530
3531 return obj;
3532}
3533
3534ALWAYS_INLINE(static VALUE newobj_slowpath(VALUE klass, VALUE flags, rb_objspace_t *objspace, int wb_protected, size_t heap_idx));
3535
3536static inline VALUE
3537newobj_slowpath(VALUE klass, VALUE flags, rb_objspace_t *objspace, int wb_protected, size_t heap_idx)
3538{
3539 VALUE obj;
3540
3541 /* No lock (see newobj_refill); during_gc and the stress flag are this objspace's own state. */
3542 if (RB_UNLIKELY(during_gc || ruby_gc_stressful)) {
3543 if (during_gc) {
3544 dont_gc_on();
3545 during_gc = 0;
3546 if (rb_memerror_reentered()) {
3547 rb_memerror();
3548 }
3549 rb_bug("object allocation during garbage collection phase");
3550 }
3551
3552 if (ruby_gc_stressful) {
3553 if (!garbage_collect(objspace, GPR_FLAG_NEWOBJ)) {
3554 rb_memerror();
3555 }
3556 }
3557 }
3558
3559 obj = newobj_alloc(objspace, heap_idx);
3560 newobj_init(klass, flags, wb_protected, objspace, obj);
3561
3562 if (RB_UNLIKELY(ruby_gc_stressful)) {
3563 rb_heap_t *heap = &heaps[heap_idx];
3564 heap->newobj.alloc_cursor_end = heap->newobj.alloc_cursor;
3565 }
3566
3567 return obj;
3568}
3569
3570NOINLINE(static VALUE newobj_slowpath_wb_protected(VALUE klass, VALUE flags,
3571 rb_objspace_t *objspace, size_t heap_idx));
3572NOINLINE(static VALUE newobj_slowpath_wb_unprotected(VALUE klass, VALUE flags,
3573 rb_objspace_t *objspace, size_t heap_idx));
3574
3575static VALUE
3576newobj_slowpath_wb_protected(VALUE klass, VALUE flags, rb_objspace_t *objspace, size_t heap_idx)
3577{
3578 return newobj_slowpath(klass, flags, objspace, TRUE, heap_idx);
3579}
3580
3581static VALUE
3582newobj_slowpath_wb_unprotected(VALUE klass, VALUE flags, rb_objspace_t *objspace, size_t heap_idx)
3583{
3584 return newobj_slowpath(klass, flags, objspace, FALSE, heap_idx);
3585}
3586
3587VALUE
3588rb_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)
3589{
3590 VALUE obj;
3591 rb_objspace_t *objspace = objspace_ptr;
3592
3593 /* There is no per-Ractor cache; the argument stays for ABI compatibility with other
3594 * GC implementations such as MMTk. */
3595 (void)cache_ptr;
3596
3597 RB_DEBUG_COUNTER_INC(obj_newobj);
3598 (void)RB_DEBUG_COUNTER_INC_IF(obj_newobj_wb_unprotected, !wb_protected);
3599
3600 if (RB_UNLIKELY(stress_to_class)) {
3601 if (rb_hash_lookup2(stress_to_class, klass, Qundef) != Qundef) {
3602 rb_memerror();
3603 }
3604 }
3605
3606 size_t heap_idx = heap_idx_for_size(alloc_size);
3607 *actual_alloc_size = heap_slot_size((unsigned char)heap_idx);
3608
3609 if (!RB_UNLIKELY(during_gc || ruby_gc_stressful) &&
3610 wb_protected) {
3611 obj = newobj_alloc(objspace, heap_idx);
3612 newobj_init(klass, flags, wb_protected, objspace, obj);
3613 }
3614 else {
3615 RB_DEBUG_COUNTER_INC(obj_newobj_slowpath);
3616
3617 obj = wb_protected ?
3618 newobj_slowpath_wb_protected(klass, flags, objspace, heap_idx) :
3619 newobj_slowpath_wb_unprotected(klass, flags, objspace, heap_idx);
3620 }
3621
3622 return obj;
3623}
3624
3625static int
3626ptr_in_page_body_p(const void *ptr, const void *memb)
3627{
3628 struct heap_page *page = *(struct heap_page **)memb;
3629 uintptr_t p_body = (uintptr_t)page->body;
3630
3631 if ((uintptr_t)ptr >= p_body) {
3632 return (uintptr_t)ptr < (p_body + HEAP_PAGE_SIZE) ? 0 : 1;
3633 }
3634 else {
3635 return -1;
3636 }
3637}
3638
3639PUREFUNC(static inline struct heap_page *heap_page_for_ptr(rb_objspace_t *objspace, uintptr_t ptr);)
3640static inline struct heap_page *
3641heap_page_for_ptr(rb_objspace_t *objspace, uintptr_t ptr)
3642{
3643 struct heap_page **res;
3644
3645 if (ptr < (uintptr_t)heap_pages_lomem ||
3646 ptr > (uintptr_t)heap_pages_himem) {
3647 return NULL;
3648 }
3649
3650 res = bsearch((void *)ptr, rb_darray_ref(objspace->heap_pages.sorted, 0),
3651 rb_darray_size(objspace->heap_pages.sorted), sizeof(struct heap_page *),
3652 ptr_in_page_body_p);
3653
3654 if (res) {
3655 return *res;
3656 }
3657 else {
3658 return NULL;
3659 }
3660}
3661
3662PUREFUNC(static inline bool is_pointer_to_heap(rb_objspace_t *objspace, const void *ptr);)
3663static inline bool
3664is_pointer_to_heap(rb_objspace_t *objspace, const void *ptr)
3665{
3666 register uintptr_t p = (uintptr_t)ptr;
3667 register struct heap_page *page;
3668
3669 RB_DEBUG_COUNTER_INC(gc_isptr_trial);
3670
3671 if (p < heap_pages_lomem || p > heap_pages_himem) return FALSE;
3672 RB_DEBUG_COUNTER_INC(gc_isptr_range);
3673
3674 if (p % sizeof(VALUE) != 0) return FALSE;
3675 RB_DEBUG_COUNTER_INC(gc_isptr_align);
3676
3677 page = heap_page_for_ptr(objspace, (uintptr_t)ptr);
3678 if (page) {
3679 RB_DEBUG_COUNTER_INC(gc_isptr_maybe);
3680 if (heap_page_in_global_empty_pages_pool(objspace, page)) {
3681 return FALSE;
3682 }
3683 else {
3684 if (p < page->start) return FALSE;
3685 if (p >= page->start + (page->total_slots * page->slot_size)) return FALSE;
3686 if ((p - page->start) % page->slot_size != 0) return FALSE;
3687
3688 return TRUE;
3689 }
3690 }
3691 return FALSE;
3692}
3693
3694bool
3695rb_gc_impl_live_object_p(void *objspace_ptr, const void *ptr)
3696{
3697 rb_objspace_t *objspace = objspace_ptr;
3698
3699 /* Whether ptr refers to a live object. is_pointer_to_heap is the
3700 * address-only check; T_NONE, T_MOVED, and T_ZOMBIE slots are valid heap
3701 * addresses but not live objects. */
3702 if (!is_pointer_to_heap(objspace, ptr)) return false;
3703
3704 VALUE obj = (VALUE)ptr;
3705 bool live = false;
3706 asan_unpoisoning_object(obj) {
3707 switch (BUILTIN_TYPE(obj)) {
3708 case T_NONE:
3709 case T_MOVED:
3710 case T_ZOMBIE:
3711 break;
3712 default:
3713 live = true;
3714 break;
3715 }
3716 }
3717 return live;
3718}
3719
3720/* Flags preserved from the original object when it becomes a zombie, and so also the
3721 * only ones that may legitimately be set on one. */
3722#define ZOMBIE_OBJ_KEPT_FLAGS (FL_FINALIZE)
3723
3724void
3725rb_gc_impl_make_zombie(void *objspace_ptr, VALUE obj, void (*dfree)(void *), void *data)
3726{
3727 rb_objspace_t *objspace = objspace_ptr;
3728
3729 struct RZombie *zombie = RZOMBIE(obj);
3730 zombie->flags = T_ZOMBIE | (zombie->flags & ZOMBIE_OBJ_KEPT_FLAGS);
3731 zombie->dfree = dfree;
3732 zombie->data = data;
3733 VALUE prev, next = heap_pages_deferred_final;
3734 do {
3735 zombie->next = prev = next;
3736 next = RUBY_ATOMIC_VALUE_CAS(heap_pages_deferred_final, prev, obj);
3737 } while (next != prev);
3738
3739 struct heap_page *page = GET_HEAP_PAGE(obj);
3740 page->final_slots++;
3741 page->heap->final_slots_count++;
3742}
3743
3744static void
3745tdata_unsafe_free_chunk_reset(struct tdata_unsafe_free_chunk *chunk)
3746{
3747 chunk->next = NULL;
3748 chunk->count = 0;
3749 chunk->embed_xfree_bits = 0;
3750}
3751
3752static struct tdata_unsafe_free_chunk *
3753tdata_unsafe_free_chunk_alloc(void)
3754{
3755 /* Pops race each other (several Ractors can be sweeping), but pushes happen only
3756 * inside the drain, which holds a VM barrier -- and a barrier cannot complete while
3757 * a Ractor is inside gc_sweep_page. No push ever overlaps a pop, so the head only
3758 * moves forward and this CAS pop needs no ABA tagging. A sweep performed by a
3759 * thread other than the objspace's owner would break that. */
3760 struct tdata_unsafe_free_chunk *head =
3761 rbimpl_atomic_ptr_load((void **)&global_objspace->tdata_unsafe_free_cache,
3762 RBIMPL_ATOMIC_ACQUIRE);
3763 while (head) {
3764 struct tdata_unsafe_free_chunk *prev =
3765 rbimpl_atomic_ptr_cas((void **)&global_objspace->tdata_unsafe_free_cache,
3766 head, head->next,
3767 RBIMPL_ATOMIC_ACQ_REL, RBIMPL_ATOMIC_ACQUIRE);
3768 if (prev == head) {
3769 rbimpl_atomic_size_dec(&global_objspace->tdata_unsafe_free_cache_len,
3770 RBIMPL_ATOMIC_RELAXED);
3771 tdata_unsafe_free_chunk_reset(head);
3772 return head;
3773 }
3774 head = prev;
3775 }
3776
3777 /* Not xmalloc: this runs mid-sweep, and the chunks are GC bookkeeping that should not
3778 * feed back into malloc_increase (mark stack chunks do the same). */
3779 struct tdata_unsafe_free_chunk *chunk = malloc(sizeof(struct tdata_unsafe_free_chunk));
3780 if (!chunk) rb_memerror();
3781 tdata_unsafe_free_chunk_reset(chunk);
3782 return chunk;
3783}
3784
3785/* Hand this objspace's partial chunk to the global stack. The entries were counted as
3786 * they were appended, so the pending count does not change here. */
3787static void
3788gc_tdata_unsafe_free_publish(rb_objspace_t *objspace)
3789{
3790 struct tdata_unsafe_free_chunk *chunk = objspace->tdata_unsafe_free_chunk;
3791 if (chunk == NULL) return;
3792 GC_ASSERT(chunk->count > 0);
3793 objspace->tdata_unsafe_free_chunk = NULL;
3794
3795 struct tdata_unsafe_free_chunk *prev, *head =
3796 rbimpl_atomic_ptr_load((void **)&global_objspace->tdata_unsafe_free_published,
3797 RBIMPL_ATOMIC_RELAXED);
3798 do {
3799 chunk->next = prev = head;
3800 head = rbimpl_atomic_ptr_cas((void **)&global_objspace->tdata_unsafe_free_published,
3801 prev, chunk,
3802 RBIMPL_ATOMIC_ACQ_REL, RBIMPL_ATOMIC_ACQUIRE);
3803 } while (head != prev);
3804}
3805
3806/* Copy out what obj's deferred free needs, running no dfree. Returns true when the
3807 * caller may reclaim the slot and false when obj became a zombie, matching rb_gc_obj_free. */
3808static bool
3809gc_defer_thread_unsafe_free(rb_objspace_t *objspace, VALUE obj, bool *trigger)
3810{
3811 GC_ASSERT(!((uintptr_t)RTYPEDDATA(obj)->type & TYPED_DATA_EMBEDDED));
3812 const rb_data_type_t *type = RTYPEDDATA_TYPE(obj);
3813 void *data = RTYPEDDATA(obj)->data;
3814 GC_ASSERT(data != NULL);
3815
3816 rb_gc_obj_free_vm_weak_references(obj);
3817
3818 size_t count = rbimpl_atomic_size_fetch_add(&global_objspace->tdata_deferred_free_count, 1,
3819 RBIMPL_ATOMIC_RELAXED) + 1;
3820 if (!*trigger && count >= TDATA_DEFERRED_FREE_THRESHOLD) {
3821 *trigger = true;
3822 }
3823
3824 struct tdata_unsafe_free_chunk *chunk = objspace->tdata_unsafe_free_chunk;
3825 if (chunk == NULL) {
3826 chunk = objspace->tdata_unsafe_free_chunk = tdata_unsafe_free_chunk_alloc();
3827 }
3828 if (type->flags & RUBY_TYPED_EMBEDDABLE) {
3829 chunk->embed_xfree_bits |= (uint32_t)1 << chunk->count;
3830 }
3831 struct tdata_unsafe_free_entry *entry = &chunk->entries[chunk->count++];
3832 entry->dfree = type->function.dfree;
3833 entry->data = data;
3834 if (chunk->count == TDATA_UNSAFE_FREE_CHUNK_CAPA) {
3835 gc_tdata_unsafe_free_publish(objspace);
3836 }
3837
3838 if (FL_TEST_RAW(obj, FL_FINALIZE)) {
3839 /* The dfree is on the side list now, so this zombie carries none: it goes on the
3840 * regular deferred list, where the owner runs its Ruby finalizer promptly and
3841 * reclaims the slot, instead of waiting for the barrier. */
3842 rb_gc_impl_make_zombie(objspace, obj, 0, 0);
3843 return false;
3844 }
3845 return true;
3846}
3847
3848typedef int each_obj_callback(void *, void *, size_t, void *);
3849typedef int each_page_callback(struct heap_page *, void *);
3850
3853 bool reenable_incremental;
3854
3855 /* Visit only the pages that hold shareable objects, so a foreign Ractor's objspace
3856 * can be walked for its shareable objects alone, without touching the rest of its
3857 * isolated heap. */
3858 bool shareable_only;
3859
3860 /* Set when walking a foreign objspace without settling its stopped lazy sweep
3861 * (settling would run the owner's obj_free and dfree on this thread). Objects the
3862 * sweep is about to free are skipped: on an unswept page, unmarked means dead. */
3863 bool skip_unswept_dead;
3864
3865 each_obj_callback *each_obj_callback;
3866 each_page_callback *each_page_callback;
3867 void *data;
3868
3869 struct heap_page **pages[HEAP_COUNT];
3870 size_t pages_counts[HEAP_COUNT];
3871};
3872
3873static VALUE
3874objspace_each_objects_ensure(VALUE arg)
3875{
3876 struct each_obj_data *data = (struct each_obj_data *)arg;
3877 rb_objspace_t *objspace = data->objspace;
3878
3879 /* Reenable incremental GC */
3880 if (data->reenable_incremental) {
3881 objspace->flags.dont_incremental = FALSE;
3882 }
3883
3884 for (int i = 0; i < HEAP_COUNT; i++) {
3885 struct heap_page **pages = data->pages[i];
3886 free(pages);
3887 }
3888
3889 return Qnil;
3890}
3891
3892static VALUE
3893objspace_each_objects_try(VALUE arg)
3894{
3895 struct each_obj_data *data = (struct each_obj_data *)arg;
3896 rb_objspace_t *objspace = data->objspace;
3897
3898 /* Copy pages from all heaps to their respective buffers. */
3899 for (int i = 0; i < HEAP_COUNT; i++) {
3900 rb_heap_t *heap = &heaps[i];
3901 size_t size = heap->total_pages * sizeof(struct heap_page *);
3902
3903 struct heap_page **pages = malloc(size);
3904 if (!pages) rb_memerror();
3905
3906 /* Set up pages buffer by iterating over all pages in the current eden
3907 * heap. This will be a snapshot of the state of the heap before we
3908 * call the callback over each page that exists in this buffer. Thus it
3909 * is safe for the callback to allocate objects without possibly entering
3910 * an infinite loop. */
3911 struct heap_page *page = 0;
3912 size_t pages_count = 0;
3913 ccan_list_for_each(&heap->pages, page, page_node) {
3914 pages[pages_count] = page;
3915 pages_count++;
3916 }
3917 data->pages[i] = pages;
3918 data->pages_counts[i] = pages_count;
3919 GC_ASSERT(pages_count == heap->total_pages);
3920 }
3921
3922 for (int i = 0; i < HEAP_COUNT; i++) {
3923 rb_heap_t *heap = &heaps[i];
3924 size_t pages_count = data->pages_counts[i];
3925 struct heap_page **pages = data->pages[i];
3926
3927 struct heap_page *page = ccan_list_top(&heap->pages, struct heap_page, page_node);
3928 for (size_t i = 0; i < pages_count; i++) {
3929 /* If we have reached the end of the linked list then there are no
3930 * more pages, so break. */
3931 if (page == NULL) break;
3932
3933 /* If this page does not match the one in the buffer, then move to
3934 * the next page in the buffer. */
3935 if (pages[i] != page) continue;
3936
3937 uintptr_t pstart = (uintptr_t)page->start;
3938 uintptr_t pend = pstart + (page->total_slots * heap->slot_size);
3939
3940 if (data->shareable_only) {
3941 /* Hand shareable objects to the callback one slot at a time, not the
3942 * whole page: walking a foreign Ractor's objspace must never expose its
3943 * unshareable objects, which the caller cannot inspect safely. */
3944 if (page->flags.has_shareable_objects) {
3945 /* This walk runs over a foreign objspace under the barrier and
3946 * must not settle the owner's stopped lazy sweep: settling would run
3947 * the owner's obj_free and dfree on this thread with this Ractor's
3948 * identity (wrong per-Ractor tables, a foreign T_DATA dfree). So no
3949 * gc_rest, and objects the sweep is about to free are skipped: on an
3950 * unswept page unmarked means dead and its shareable bit merely has
3951 * not been bulk-cleared yet. Passing one to the callback would
3952 * resurrect it, handing out a reference the owner's sweep frees as
3953 * soon as the barrier lifts. */
3954 const bool page_unswept = is_lazy_sweeping(objspace) && page->flags.before_sweep;
3955 int planes = CEILDIV(page->total_slots, BITS_BITLENGTH);
3956 uintptr_t base = pstart;
3957 bool stop = false;
3958 for (int j = 0; j < planes && !stop; j++) {
3959 bits_t bits = page->shareable_bits[j];
3960 uintptr_t slot = base;
3961 while (bits) {
3962 if ((bits & 1) && data->each_obj_callback &&
3963 !(page_unswept && !RVALUE_MARKED(objspace, (VALUE)slot)) &&
3964 (*data->each_obj_callback)((void *)slot, (void *)(slot + heap->slot_size),
3965 heap->slot_size, data->data)) {
3966 stop = true;
3967 break;
3968 }
3969 slot += heap->slot_size;
3970 bits >>= 1;
3971 }
3972 base += BITS_BITLENGTH * heap->slot_size;
3973 }
3974 if (stop) break;
3975 }
3976 }
3977 else if (data->skip_unswept_dead &&
3978 is_lazy_sweeping(objspace) && page->flags.before_sweep) {
3979 /* A foreign page pending sweep: hand out the live objects one slot at a
3980 * time and skip the unmarked (dead) ones the owner's sweep frees as soon
3981 * as the barrier lifts. */
3982 bool stop = false;
3983 for (uintptr_t slot = pstart; slot < pend; slot += heap->slot_size) {
3984 if (!RVALUE_MARKED(objspace, (VALUE)slot)) continue;
3985 if (data->each_obj_callback &&
3986 (*data->each_obj_callback)((void *)slot, (void *)(slot + heap->slot_size),
3987 heap->slot_size, data->data)) {
3988 stop = true;
3989 break;
3990 }
3991 }
3992 if (stop) break;
3993 }
3994 else {
3995 if (data->each_obj_callback &&
3996 (*data->each_obj_callback)((void *)pstart, (void *)pend, heap->slot_size, data->data)) {
3997 break;
3998 }
3999 if (data->each_page_callback &&
4000 (*data->each_page_callback)(page, data->data)) {
4001 break;
4002 }
4003 }
4004
4005 page = ccan_list_next(&heap->pages, page, page_node);
4006 }
4007 }
4008
4009 return Qnil;
4010}
4011
4012static void
4013objspace_each_exec(bool protected, struct each_obj_data *each_obj_data)
4014{
4015 /* Disable incremental GC */
4017 bool reenable_incremental = FALSE;
4018 if (protected) {
4019 reenable_incremental = !objspace->flags.dont_incremental;
4020
4021 gc_rest(objspace);
4022 objspace->flags.dont_incremental = TRUE;
4023 }
4024
4025 each_obj_data->reenable_incremental = reenable_incremental;
4026 memset(&each_obj_data->pages, 0, sizeof(each_obj_data->pages));
4027 memset(&each_obj_data->pages_counts, 0, sizeof(each_obj_data->pages_counts));
4028 rb_ensure(objspace_each_objects_try, (VALUE)each_obj_data,
4029 objspace_each_objects_ensure, (VALUE)each_obj_data);
4030}
4031
4032static void
4033objspace_each_objects(rb_objspace_t *objspace, each_obj_callback *callback, void *data, bool protected)
4034{
4035 struct each_obj_data each_obj_data = {
4036 .objspace = objspace,
4037 .each_obj_callback = callback,
4038 .each_page_callback = NULL,
4039 .data = data,
4040 };
4041 objspace_each_exec(protected, &each_obj_data);
4042}
4043
4044void
4045rb_gc_impl_each_objects(void *objspace_ptr, each_obj_callback *callback, void *data)
4046{
4047 objspace_each_objects(objspace_ptr, callback, data, TRUE);
4048}
4049
4050/* Like rb_gc_impl_each_objects but visiting only pages that hold shareable objects, to
4051 * reach a foreign Ractor's shareable objects without walking the rest of its heap. */
4052void
4053rb_gc_impl_each_objects_shareable(void *objspace_ptr, each_obj_callback *callback, void *data)
4054{
4055 struct each_obj_data each_obj_data = {
4056 .objspace = objspace_ptr,
4057 .shareable_only = true,
4058 .each_obj_callback = callback,
4059 .each_page_callback = NULL,
4060 .data = data,
4061 };
4062 /* Not the protected variant: this objspace belongs to another Ractor (the caller
4063 * holds the barrier). The protected path calls gc_rest, which would run the owner's
4064 * stopped lazy sweep (its obj_free and dfree) on the walking thread with the
4065 * walker's Ractor identity (wrong per-Ractor tables, a foreign T_DATA dfree). The
4066 * owner is stopped and its page list is stable, and the walk itself skips dead,
4067 * unswept objects (the shareable_only branch of objspace_each_objects_try). The
4068 * walker's own incremental GC state is untouched, since this is not its objspace. */
4069 objspace_each_exec(FALSE, &each_obj_data);
4070}
4071
4072/* Walk every object of a foreign Ractor's objspace, unshareable ones included. Only for
4073 * callers that hold the barrier and whose callback is pure C (a heap dump, memory
4074 * accounting). As in the shareable walk above, the owner's stopped lazy sweep is not
4075 * settled and dead, unswept objects are skipped by the walk (skip_unswept_dead). */
4076void
4077rb_gc_impl_each_objects_foreign(void *objspace_ptr, each_obj_callback *callback, void *data)
4078{
4079 struct each_obj_data each_obj_data = {
4080 .objspace = objspace_ptr,
4081 .skip_unswept_dead = true,
4082 .each_obj_callback = callback,
4083 .each_page_callback = NULL,
4084 .data = data,
4085 };
4086 objspace_each_exec(FALSE, &each_obj_data);
4087}
4088
4089#if GC_CAN_COMPILE_COMPACTION
4090static void
4091objspace_each_pages(rb_objspace_t *objspace, each_page_callback *callback, void *data, bool protected)
4092{
4093 struct each_obj_data each_obj_data = {
4094 .objspace = objspace,
4095 .each_obj_callback = NULL,
4096 .each_page_callback = callback,
4097 .data = data,
4098 };
4099 objspace_each_exec(protected, &each_obj_data);
4100}
4101#endif
4102
4103VALUE
4104rb_gc_impl_define_finalizer(void *objspace_ptr, VALUE obj, VALUE block)
4105{
4106 rb_objspace_t *objspace = objspace_ptr;
4107 VALUE table;
4108 st_data_t data;
4109
4110 GC_ASSERT(!OBJ_FROZEN(obj));
4111
4112 /* Registering, storing and running finalizers all belong to the object's own
4113 * objspace, so refuse to define one on another Ractor's object (even a shareable
4114 * one): it would land in a table the owner's sweep never consults. */
4115 if (GET_HEAP_OBJSPACE(obj) != objspace) {
4116 rb_raise(rb_eRactorIsolationError,
4117 "can not define a finalizer for an object of another Ractor");
4118 }
4119
4120 RBASIC(obj)->flags |= FL_FINALIZE;
4121
4122 unsigned int lev = RB_GC_VM_LOCK();
4123
4124 if (st_lookup(finalizer_table, obj, &data)) {
4125 table = (VALUE)data;
4126 VALUE dup_table = rb_ary_dup(table);
4127
4128 RB_GC_VM_UNLOCK(lev);
4129 /* avoid duplicate block, table is usually small */
4130 {
4131 long len = RARRAY_LEN(table);
4132 long i;
4133
4134 for (i = 0; i < len; i++) {
4135 VALUE recv = RARRAY_AREF(dup_table, i);
4136 if (rb_equal(recv, block)) { // can't be called with VM lock held
4137 return recv;
4138 }
4139 }
4140 }
4141 lev = RB_GC_VM_LOCK();
4142 RB_GC_GUARD(dup_table);
4143
4144 rb_ary_push(table, block);
4145 }
4146 else {
4147 table = rb_ary_new3(2, rb_obj_id(obj), block);
4148 rb_obj_hide(table);
4149 st_add_direct(finalizer_table, obj, table);
4150 }
4151
4152 RB_GC_VM_UNLOCK(lev);
4153
4154 return block;
4155}
4156
4157void
4158rb_gc_impl_undefine_finalizer(void *objspace_ptr, VALUE obj)
4159{
4160 rb_objspace_t *objspace = objspace_ptr;
4161
4162 GC_ASSERT(!OBJ_FROZEN(obj));
4163
4164 /* Symmetric with define. */
4165 if (GET_HEAP_OBJSPACE(obj) != objspace) {
4166 rb_raise(rb_eRactorIsolationError,
4167 "can not undefine a finalizer of an object of another Ractor");
4168 }
4169
4170 st_data_t data = obj;
4171
4172 int lev = RB_GC_VM_LOCK();
4173 st_delete(finalizer_table, &data, 0);
4174 RB_GC_VM_UNLOCK(lev);
4175
4176 FL_UNSET(obj, FL_FINALIZE);
4177}
4178
4179void
4180rb_gc_impl_copy_finalizer(void *objspace_ptr, VALUE dest, VALUE obj)
4181{
4182 /* Finalizers do not cross objspaces: a copy of another Ractor's object starts with
4183 * none (guards the public rb_gc_copy_finalizer C API; no in-tree caller crosses).
4184 * A same-objspace copy behaves as before. Table accessed under the VM lock. */
4185 rb_objspace_t *objspace = objspace_ptr;
4186 VALUE table;
4187 st_data_t data;
4188
4189 if (!FL_TEST(obj, FL_FINALIZE)) return;
4190 if (GET_HEAP_OBJSPACE(obj) != objspace) return;
4191
4192 int lev = RB_GC_VM_LOCK();
4193 if (RB_LIKELY(st_lookup(finalizer_table, obj, &data))) {
4194 table = rb_ary_dup((VALUE)data);
4195 RARRAY_ASET(table, 0, rb_obj_id(dest));
4196 st_insert(finalizer_table, dest, table);
4197 FL_SET(dest, FL_FINALIZE);
4198 }
4199 else {
4200 rb_bug("rb_gc_copy_finalizer: FL_FINALIZE set but not found in finalizer_table: %s", rb_obj_info(obj));
4201 }
4202 RB_GC_VM_UNLOCK(lev);
4203}
4204
4205static VALUE
4206get_final(long i, void *data)
4207{
4208 VALUE table = (VALUE)data;
4209
4210 return RARRAY_AREF(table, i + 1);
4211}
4212
4213static void
4214run_final(rb_objspace_t *objspace, VALUE zombie)
4215{
4216 if (RZOMBIE(zombie)->dfree) {
4217 RZOMBIE(zombie)->dfree(RZOMBIE(zombie)->data);
4218 }
4219
4220 st_data_t key = (st_data_t)zombie;
4221 if (FL_TEST_RAW(zombie, FL_FINALIZE)) {
4222 FL_UNSET(zombie, FL_FINALIZE);
4223 st_data_t table;
4224 if (st_delete(finalizer_table, &key, &table)) {
4225 rb_gc_run_obj_finalizer(RARRAY_AREF(table, 0), RARRAY_LEN(table) - 1, get_final, (void *)table);
4226 }
4227 else {
4228 rb_bug("FL_FINALIZE flag is set, but finalizers are not found");
4229 }
4230 }
4231 else {
4232 GC_ASSERT(!st_lookup(finalizer_table, key, NULL));
4233 }
4234}
4235
4236static void
4237finalize_list(rb_objspace_t *objspace, VALUE zombie)
4238{
4239 while (zombie) {
4240 VALUE next_zombie;
4241 struct heap_page *page;
4242 rb_asan_unpoison_object(zombie, false);
4243 next_zombie = RZOMBIE(zombie)->next;
4244 page = GET_HEAP_PAGE(zombie);
4245
4246 run_final(objspace, zombie);
4247 {
4248 GC_ASSERT(BUILTIN_TYPE(zombie) == T_ZOMBIE);
4249 GC_ASSERT(page->heap->final_slots_count > 0);
4250 GC_ASSERT(page->final_slots > 0);
4251
4252 page->heap->final_slots_count--;
4253 page->final_slots--;
4254 page->free_slots++;
4255 RVALUE_AGE_SET_BITMAP(zombie, 0);
4256 heap_page_add_free_region(objspace, page, zombie);
4257 page->heap->total_freed_objects++;
4258 }
4259
4260 zombie = next_zombie;
4261 }
4262}
4263
4264static void
4265finalize_zombies(rb_objspace_t *objspace)
4266{
4267 VALUE zombie;
4268 while ((zombie = RUBY_ATOMIC_VALUE_EXCHANGE(heap_pages_deferred_final, 0)) != 0) {
4269 finalize_list(objspace, zombie);
4270 }
4271}
4272
4273static void
4274finalize_deferred(rb_objspace_t *objspace)
4275{
4276 rb_gc_set_pending_interrupt();
4277 finalize_zombies(objspace);
4278 rb_gc_unset_pending_interrupt();
4279}
4280
4281static void
4282gc_finalize_deferred(void *dmy)
4283{
4284 /* One postponed job is shared by every objspace: the preregistration table only
4285 * holds about 32 entries and Ractors are created continuously. A deferred finalizer
4286 * belongs to the objspace of the thread that ran the job, i.e. the current one. */
4287 rb_objspace_t *objspace = rb_gc_get_objspace();
4288 if (RUBY_ATOMIC_EXCHANGE(finalizing, 1)) return;
4289
4290 finalize_deferred(objspace);
4291 RUBY_ATOMIC_SET(finalizing, 0);
4292}
4293
4294static void
4295gc_finalize_deferred_register(rb_objspace_t *objspace)
4296{
4297 /* Enqueue gc_finalize_deferred on this objspace's owning Ractor. A global GC can
4298 * defer a foreign objspace's finalizers, and those must run on their owner rather
4299 * than on the driver. */
4300 rb_gc_trigger_finalize_deferred(objspace, objspace->finalize_deferred_pjob);
4301}
4302
4303static int pop_mark_stack(mark_stack_t *stack, VALUE *data);
4304
4305/* Throw away an unfinished incremental mark and leave the objspace in gc_mode_none. The
4306 * mark bits the partial mark set stay behind, so the caller must clear them before the
4307 * heap is collected again. */
4308static void
4309gc_abort_incremental_marking(rb_objspace_t *objspace)
4310{
4311 GC_ASSERT(is_incremental_marking(objspace));
4312
4313 VALUE obj;
4314 while (pop_mark_stack(&objspace->mark_stack, &obj));
4315
4316 /* gc_grey records the weak references it greys for gc_marks_finish to resolve; this
4317 * cycle never reaches it, and the entries would outlive their objects. */
4318 rb_darray_clear(objspace->weak_references);
4319
4320 objspace->flags.during_incremental_marking = FALSE;
4321 gc_mode_set(objspace, gc_mode_none);
4322}
4323
4324static void
4325gc_abort(void *objspace_ptr)
4326{
4327 rb_objspace_t *objspace = objspace_ptr;
4328
4329 if (is_incremental_marking(objspace)) {
4330 gc_abort_incremental_marking(objspace);
4331 }
4332
4333 if (is_lazy_sweeping(objspace)) {
4334 objspace->sweeping_heap_count = 0;
4335 for (int i = 0; i < HEAP_COUNT; i++) {
4336 rb_heap_t *heap = &heaps[i];
4337
4338 heap->sweeping_page = NULL;
4339 struct heap_page *page = NULL;
4340
4341 ccan_list_for_each(&heap->pages, page, page_node) {
4342 page->flags.before_sweep = false;
4343 }
4344 }
4345 }
4346
4347 for (int i = 0; i < HEAP_COUNT; i++) {
4348 rb_heap_t *heap = &heaps[i];
4349 gc_bitmaps_clear(objspace, heap, false);
4350 }
4351
4352 gc_mode_set(objspace, gc_mode_none);
4353}
4354
4355#if VERIFY_FREE_SIZE
4356# ifdef RB_THREAD_LOCAL_SPECIFIER
4357# define GC_FREEING_OBJ_TLS RB_THREAD_LOCAL_SPECIFIER
4358# else
4359# define GC_FREEING_OBJ_TLS
4360# endif
4361
4362static GC_FREEING_OBJ_TLS VALUE gc_freeing_obj;
4363
4364/* Remember what we are tearing down so that a bad xfree() underneath can name
4365 * the object and not just the buffer. Saved and restored because a dfree
4366 * callback can free another object. */
4367static bool
4368gc_obj_free(void *objspace, VALUE obj)
4369{
4370 VALUE prev = gc_freeing_obj;
4371 gc_freeing_obj = obj;
4372
4373 bool freed = rb_gc_obj_free(objspace, obj);
4374
4375 gc_freeing_obj = prev;
4376 return freed;
4377}
4378
4379static const char *
4380gc_freeing_obj_info(void)
4381{
4382 /* Not thread-local: only reachable from a rb_bug() path, where a second
4383 * thread racing us is already unrecoverable. */
4384 static char buf[128];
4385
4386 if (!gc_freeing_obj) return NULL;
4387
4388 snprintf(buf, sizeof(buf), "%p %s", (void *)gc_freeing_obj, rb_obj_info(gc_freeing_obj));
4389 return buf;
4390}
4391#else
4392# define gc_obj_free(objspace, obj) rb_gc_obj_free((objspace), (obj))
4393# define gc_freeing_obj_info() NULL
4394#endif
4395
4396void
4397rb_gc_impl_shutdown_free_objects(void *objspace_ptr)
4398{
4399 rb_objspace_t *objspace = objspace_ptr;
4400
4401 for (size_t i = 0; i < rb_darray_size(objspace->heap_pages.sorted); i++) {
4402 struct heap_page *page = rb_darray_get(objspace->heap_pages.sorted, i);
4403 short stride = page->slot_size;
4404
4405 uintptr_t p = (uintptr_t)page->start;
4406 uintptr_t pend = p + page->total_slots * stride;
4407 for (; p < pend; p += stride) {
4408 VALUE vp = (VALUE)p;
4409 asan_unpoisoning_object(vp) {
4410 if (RB_BUILTIN_TYPE(vp) != T_NONE) {
4411 rb_gc_obj_free_vm_weak_references(vp);
4412 if (gc_obj_free(objspace, vp)) {
4413 RBASIC(vp)->flags = 0;
4414 }
4415 }
4416 }
4417 }
4418 }
4419}
4420
4421static int
4422rb_gc_impl_shutdown_call_finalizer_i(st_data_t key, st_data_t val, st_data_t _data)
4423{
4424 VALUE obj = (VALUE)key;
4425 VALUE table = (VALUE)val;
4426
4427 GC_ASSERT(RB_FL_TEST(obj, FL_FINALIZE));
4428 GC_ASSERT(RB_BUILTIN_TYPE(val) == T_ARRAY);
4429
4430 rb_gc_run_obj_finalizer(RARRAY_AREF(table, 0), RARRAY_LEN(table) - 1, get_final, (void *)table);
4431
4432 FL_UNSET(obj, FL_FINALIZE);
4433
4434 return ST_DELETE;
4435}
4436
4437void
4438rb_gc_impl_shutdown_call_finalizer(void *objspace_ptr)
4439{
4440 rb_objspace_t *objspace = objspace_ptr;
4441
4442#if RGENGC_CHECK_MODE >= 2
4443 gc_verify_internal_consistency(objspace);
4444#endif
4445
4446 /* prohibit incremental GC */
4447 objspace->flags.dont_incremental = 1;
4448
4449 if (RUBY_ATOMIC_EXCHANGE(finalizing, 1)) {
4450 /* Abort incremental marking and lazy sweeping to speed up shutdown. */
4451 gc_abort(objspace);
4452 dont_gc_on();
4453 return;
4454 }
4455
4456 while (finalizer_table->num_entries) {
4457 st_foreach(finalizer_table, rb_gc_impl_shutdown_call_finalizer_i, 0);
4458 }
4459
4460 /* run finalizers */
4461 finalize_deferred(objspace);
4462 GC_ASSERT(heap_pages_deferred_final == 0);
4463
4464 /* Deferred non-thread-safe frees: their objects are long gone, so the object walk
4465 * below will not reach them. Reap them here. */
4466 gc_tdata_unsafe_drain_objspaces(&objspace, 1);
4467
4468 /* Abort incremental marking and lazy sweeping to speed up shutdown. */
4469 gc_abort(objspace);
4470
4471 /* prohibit GC because force T_DATA finalizers can break an object graph consistency */
4472 dont_gc_on();
4473
4474 /* running data/file finalizers are part of garbage collection */
4475 unsigned int lock_lev;
4476 gc_enter(objspace, gc_enter_event_finalizer, &lock_lev);
4477
4478 /* run data/file object's finalizers */
4479 for (size_t i = 0; i < rb_darray_size(objspace->heap_pages.sorted); i++) {
4480 struct heap_page *page = rb_darray_get(objspace->heap_pages.sorted, i);
4481 short stride = page->slot_size;
4482
4483 uintptr_t p = (uintptr_t)page->start;
4484 uintptr_t pend = p + page->total_slots * stride;
4485 for (; p < pend; p += stride) {
4486 VALUE vp = (VALUE)p;
4487 asan_unpoisoning_object(vp) {
4488 if (rb_gc_shutdown_call_finalizer_p(vp)) {
4489 rb_gc_obj_free_vm_weak_references(vp);
4490 if (gc_obj_free(objspace, vp)) {
4491 RBASIC(vp)->flags = 0;
4492 }
4493 }
4494 }
4495 }
4496 }
4497
4498 gc_exit(objspace, gc_enter_event_finalizer, &lock_lev);
4499
4500 finalize_zombies(objspace);
4501
4502 st_free_table(finalizer_table);
4503 finalizer_table = 0;
4504 RUBY_ATOMIC_SET(finalizing, 0);
4505}
4506
4507void
4508rb_gc_impl_each_object(void *objspace_ptr, void (*func)(VALUE obj, void *data), void *data)
4509{
4510 rb_objspace_t *objspace = objspace_ptr;
4511
4512 for (size_t i = 0; i < rb_darray_size(objspace->heap_pages.sorted); i++) {
4513 struct heap_page *page = rb_darray_get(objspace->heap_pages.sorted, i);
4514 short stride = page->slot_size;
4515
4516 uintptr_t p = (uintptr_t)page->start;
4517 uintptr_t pend = p + page->total_slots * stride;
4518 for (; p < pend; p += stride) {
4519 VALUE obj = (VALUE)p;
4520
4521 asan_unpoisoning_object(obj) {
4522 func(obj, data);
4523 }
4524 }
4525 }
4526}
4527
4528/*
4529 ------------------------ Garbage Collection ------------------------
4530*/
4531
4532/* Sweeping */
4533
4534static size_t
4535objspace_available_slots(rb_objspace_t *objspace)
4536{
4537 size_t total_slots = 0;
4538 for (int i = 0; i < HEAP_COUNT; i++) {
4539 rb_heap_t *heap = &heaps[i];
4540 total_slots += heap->total_slots;
4541 }
4542 return total_slots;
4543}
4544
4545static size_t
4546objspace_live_slots(rb_objspace_t *objspace)
4547{
4548 return total_allocated_objects(objspace) - total_freed_objects(objspace) - total_final_slots_count(objspace);
4549}
4550
4551static size_t
4552objspace_free_slots(rb_objspace_t *objspace)
4553{
4554 return objspace_available_slots(objspace) - objspace_live_slots(objspace) - total_final_slots_count(objspace);
4555}
4556
4557static void
4558gc_setup_mark_bits(struct heap_page *page)
4559{
4560 /* copy oldgen bitmap to mark bitmap */
4561 memcpy(&page->mark_bits[0], &page->uncollectible_bits[0], HEAP_PAGE_BITMAP_SIZE);
4562}
4563
4564static int gc_is_moveable_obj(rb_objspace_t *objspace, VALUE obj);
4565static VALUE gc_move(rb_objspace_t *objspace, VALUE scan, VALUE free, struct heap_page *src_page, struct heap_page *dest_page);
4566
4567#if defined(_WIN32)
4568enum {HEAP_PAGE_LOCK = PAGE_NOACCESS, HEAP_PAGE_UNLOCK = PAGE_READWRITE};
4569
4570static BOOL
4571protect_page_body(struct heap_page_body *body, DWORD protect)
4572{
4573 DWORD old_protect;
4574 return VirtualProtect(body, HEAP_PAGE_SIZE, protect, &old_protect) != 0;
4575}
4576#elif defined(__wasi__)
4577// wasi-libc's mprotect emulation does not support PROT_NONE
4578enum {HEAP_PAGE_LOCK, HEAP_PAGE_UNLOCK};
4579#define protect_page_body(body, protect) 1
4580#else
4581enum {HEAP_PAGE_LOCK = PROT_NONE, HEAP_PAGE_UNLOCK = PROT_READ | PROT_WRITE};
4582#define protect_page_body(body, protect) !mprotect((body), HEAP_PAGE_SIZE, (protect))
4583#endif
4584
4585static void
4586lock_page_body(rb_objspace_t *objspace, struct heap_page_body *body)
4587{
4588 if (!protect_page_body(body, HEAP_PAGE_LOCK)) {
4589 rb_bug("Couldn't protect page %p, errno: %s", (void *)body, strerror(errno));
4590 }
4591 else {
4592 gc_report(5, objspace, "Protecting page in move %p\n", (void *)body);
4593 }
4594}
4595
4596static void
4597unlock_page_body(rb_objspace_t *objspace, struct heap_page_body *body)
4598{
4599 if (!protect_page_body(body, HEAP_PAGE_UNLOCK)) {
4600 rb_bug("Couldn't unprotect page %p, errno: %s", (void *)body, strerror(errno));
4601 }
4602 else {
4603 gc_report(5, objspace, "Unprotecting page in move %p\n", (void *)body);
4604 }
4605}
4606
4607static uintptr_t
4608heap_page_alloc_slot_from_region(struct heap_page *free_page)
4609{
4610 asan_unlock_freelist(free_page);
4611 struct free_region *region = free_page->free_region;
4612 asan_lock_freelist(free_page);
4613
4614 if (region == NULL) {
4615 return 0;
4616 }
4617
4618 rb_asan_unpoison_object((VALUE)region, false);
4619 GC_ASSERT(RB_TYPE_P((VALUE)region, T_NONE));
4620 uintptr_t dest = (uintptr_t)region;
4621 uintptr_t region_end = region->end;
4622 struct free_region *next = region->next;
4623
4624 uintptr_t new_start = dest + free_page->slot_size;
4625
4626 asan_unlock_freelist(free_page);
4627 if (new_start < region_end) {
4628 VALUE next_start = (VALUE)new_start;
4629 rb_asan_unpoison_object(next_start, false);
4630 struct free_region *new_region = (struct free_region *)new_start;
4631 new_region->flags = 0;
4632 new_region->end = region_end;
4633 new_region->next = next;
4634 rb_asan_poison_object(next_start);
4635 free_page->free_region = new_region;
4636 }
4637 else {
4638 free_page->free_region = next;
4639 }
4640 asan_lock_freelist(free_page);
4641
4642 return dest;
4643}
4644
4645static bool
4646try_move(rb_objspace_t *objspace, rb_heap_t *heap, struct heap_page *free_page, VALUE src)
4647{
4648 GC_ASSERT(gc_is_moveable_obj(objspace, src));
4649
4650 struct heap_page *src_page = GET_HEAP_PAGE(src);
4651 if (!free_page) {
4652 return false;
4653 }
4654
4655 /* We should return true if either src is successfully moved, or src is
4656 * unmoveable. A false return will cause the sweeping cursor to be
4657 * incremented to the next page, and src will attempt to move again */
4658 GC_ASSERT(RVALUE_MARKED(objspace, src));
4659
4660 uintptr_t dest_slot = heap_page_alloc_slot_from_region(free_page);
4661 if (dest_slot == 0) {
4662 return false;
4663 }
4664 VALUE dest = (VALUE)dest_slot;
4665
4666 GC_ASSERT(RB_BUILTIN_TYPE(dest) == T_NONE);
4667
4668 if (src_page->slot_size > free_page->slot_size) {
4669 objspace->rcompactor.moved_down_count_table[BUILTIN_TYPE(src)]++;
4670 }
4671 else if (free_page->slot_size > src_page->slot_size) {
4672 objspace->rcompactor.moved_up_count_table[BUILTIN_TYPE(src)]++;
4673 }
4674 objspace->rcompactor.moved_count_table[BUILTIN_TYPE(src)]++;
4675 objspace->rcompactor.total_moved++;
4676
4677 gc_move(objspace, src, dest, src_page, free_page);
4678 gc_pin(objspace, src);
4679 free_page->free_slots--;
4680
4681 return true;
4682}
4683
4684static void
4685gc_unprotect_pages(rb_objspace_t *objspace, rb_heap_t *heap)
4686{
4687 struct heap_page *cursor = heap->compact_cursor;
4688
4689 while (cursor) {
4690 unlock_page_body(objspace, cursor->body);
4691 cursor = ccan_list_next(&heap->pages, cursor, page_node);
4692 }
4693}
4694
4695static void gc_update_references(rb_objspace_t *objspace);
4696static void gc_update_references_heap(rb_objspace_t *objspace);
4697static void gc_update_references_global(rb_objspace_t *objspace);
4698#if GC_CAN_COMPILE_COMPACTION
4699static void invalidate_moved_page(rb_objspace_t *objspace, struct heap_page *page);
4700#endif
4701
4702#if defined(__MINGW32__) || defined(_WIN32)
4703# define GC_COMPACTION_SUPPORTED 1
4704#else
4705/* If not MinGW, Windows, or does not have mmap, we cannot use mprotect for
4706 * the read barrier, so we must disable compaction. */
4707# define GC_COMPACTION_SUPPORTED (GC_CAN_COMPILE_COMPACTION && HEAP_PAGE_ALLOC_USE_MMAP)
4708#endif
4709
4710#if GC_CAN_COMPILE_COMPACTION
4711static void
4712read_barrier_handler(uintptr_t address)
4713{
4714 rb_objspace_t *objspace = (rb_objspace_t *)rb_gc_get_objspace();
4715
4716 struct heap_page_body *page_body = GET_PAGE_BODY(address);
4717
4718 /* If the page_body is NULL, then mprotect cannot handle it and will crash
4719 * with "Cannot allocate memory". */
4720 if (page_body == NULL) {
4721 rb_bug("read_barrier_handler: segmentation fault at %p", (void *)address);
4722 }
4723
4724 int lev = RB_GC_VM_LOCK();
4725 {
4726 unlock_page_body(objspace, page_body);
4727
4728 objspace->profile.read_barrier_faults++;
4729
4730 invalidate_moved_page(objspace, GET_HEAP_PAGE(address));
4731 }
4732 RB_GC_VM_UNLOCK(lev);
4733}
4734#endif
4735
4736#if !GC_CAN_COMPILE_COMPACTION
4737static void
4738uninstall_handlers(void)
4739{
4740 /* no-op */
4741}
4742
4743static void
4744install_handlers(void)
4745{
4746 /* no-op */
4747}
4748#elif defined(_WIN32)
4749static LPTOP_LEVEL_EXCEPTION_FILTER old_handler;
4750typedef void (*signal_handler)(int);
4751static signal_handler old_sigsegv_handler;
4752
4753static LONG WINAPI
4754read_barrier_signal(EXCEPTION_POINTERS *info)
4755{
4756 /* EXCEPTION_ACCESS_VIOLATION is what's raised by access to protected pages */
4757 if (info->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) {
4758 /* > The second array element specifies the virtual address of the inaccessible data.
4759 * https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-exception_record
4760 *
4761 * Use this address to invalidate the page */
4762 read_barrier_handler((uintptr_t)info->ExceptionRecord->ExceptionInformation[1]);
4763 return EXCEPTION_CONTINUE_EXECUTION;
4764 }
4765 else {
4766 return EXCEPTION_CONTINUE_SEARCH;
4767 }
4768}
4769
4770static void
4771uninstall_handlers(void)
4772{
4773 signal(SIGSEGV, old_sigsegv_handler);
4774 SetUnhandledExceptionFilter(old_handler);
4775}
4776
4777static void
4778install_handlers(void)
4779{
4780 /* Remove SEGV handler so that the Unhandled Exception Filter handles it */
4781 old_sigsegv_handler = signal(SIGSEGV, NULL);
4782 /* Unhandled Exception Filter has access to the violation address similar
4783 * to si_addr from sigaction */
4784 old_handler = SetUnhandledExceptionFilter(read_barrier_signal);
4785}
4786#else
4787static struct sigaction old_sigbus_handler;
4788static struct sigaction old_sigsegv_handler;
4789
4790#ifdef HAVE_MACH_TASK_EXCEPTION_PORTS
4791static exception_mask_t old_exception_masks[32];
4792static mach_port_t old_exception_ports[32];
4793static exception_behavior_t old_exception_behaviors[32];
4794static thread_state_flavor_t old_exception_flavors[32];
4795static mach_msg_type_number_t old_exception_count;
4796
4797static void
4798disable_mach_bad_access_exc(void)
4799{
4800 old_exception_count = sizeof(old_exception_masks) / sizeof(old_exception_masks[0]);
4801 task_swap_exception_ports(
4802 mach_task_self(), EXC_MASK_BAD_ACCESS,
4803 MACH_PORT_NULL, EXCEPTION_DEFAULT, 0,
4804 old_exception_masks, &old_exception_count,
4805 old_exception_ports, old_exception_behaviors, old_exception_flavors
4806 );
4807}
4808
4809static void
4810restore_mach_bad_access_exc(void)
4811{
4812 for (mach_msg_type_number_t i = 0; i < old_exception_count; i++) {
4813 task_set_exception_ports(
4814 mach_task_self(),
4815 old_exception_masks[i], old_exception_ports[i],
4816 old_exception_behaviors[i], old_exception_flavors[i]
4817 );
4818 }
4819}
4820#endif
4821
4822static void
4823read_barrier_signal(int sig, siginfo_t *info, void *data)
4824{
4825 // setup SEGV/BUS handlers for errors
4826 struct sigaction prev_sigbus, prev_sigsegv;
4827 sigaction(SIGBUS, &old_sigbus_handler, &prev_sigbus);
4828 sigaction(SIGSEGV, &old_sigsegv_handler, &prev_sigsegv);
4829
4830 // enable SIGBUS/SEGV
4831 sigset_t set, prev_set;
4832 sigemptyset(&set);
4833 sigaddset(&set, SIGBUS);
4834 sigaddset(&set, SIGSEGV);
4835 sigprocmask(SIG_UNBLOCK, &set, &prev_set);
4836#ifdef HAVE_MACH_TASK_EXCEPTION_PORTS
4837 disable_mach_bad_access_exc();
4838#endif
4839 // run handler
4840 read_barrier_handler((uintptr_t)info->si_addr);
4841
4842 // reset SEGV/BUS handlers
4843#ifdef HAVE_MACH_TASK_EXCEPTION_PORTS
4844 restore_mach_bad_access_exc();
4845#endif
4846 sigaction(SIGBUS, &prev_sigbus, NULL);
4847 sigaction(SIGSEGV, &prev_sigsegv, NULL);
4848 sigprocmask(SIG_SETMASK, &prev_set, NULL);
4849}
4850
4851static void
4852uninstall_handlers(void)
4853{
4854#ifdef HAVE_MACH_TASK_EXCEPTION_PORTS
4855 restore_mach_bad_access_exc();
4856#endif
4857 sigaction(SIGBUS, &old_sigbus_handler, NULL);
4858 sigaction(SIGSEGV, &old_sigsegv_handler, NULL);
4859}
4860
4861static void
4862install_handlers(void)
4863{
4864 struct sigaction action;
4865 memset(&action, 0, sizeof(struct sigaction));
4866 sigemptyset(&action.sa_mask);
4867 action.sa_sigaction = read_barrier_signal;
4868 action.sa_flags = SA_SIGINFO | SA_ONSTACK;
4869
4870 sigaction(SIGBUS, &action, &old_sigbus_handler);
4871 sigaction(SIGSEGV, &action, &old_sigsegv_handler);
4872#ifdef HAVE_MACH_TASK_EXCEPTION_PORTS
4873 disable_mach_bad_access_exc();
4874#endif
4875}
4876#endif
4877
4878static void
4879gc_compact_finish(rb_objspace_t *objspace)
4880{
4881 for (int i = 0; i < HEAP_COUNT; i++) {
4882 rb_heap_t *heap = &heaps[i];
4883 gc_unprotect_pages(objspace, heap);
4884 }
4885
4886 if (!global_objspace->global_gc.compacting) uninstall_handlers();
4887
4888 if (global_objspace->global_gc.compacting) {
4889 /* In a compacting global GC this updates only this objspace's heap references;
4890 * gc_start_global sets during_reference_updating on every objspace (the
4891 * move-or-mark decision reads it via rb_gc_get_objspace()) and runs the
4892 * non-idempotent VM-global side (gc_update_references_global) once at the end. */
4893 gc_update_references_heap(objspace);
4894 }
4895 else {
4896 gc_update_references(objspace);
4897 }
4898 objspace->profile.compact_count++;
4899
4900 for (int i = 0; i < HEAP_COUNT; i++) {
4901 rb_heap_t *heap = &heaps[i];
4902 heap->compact_cursor = NULL;
4903 heap->free_pages = NULL;
4904 heap->compact_cursor_index = 0;
4905 }
4906
4907 if (gc_prof_enabled(objspace)) {
4908 gc_profile_record *record = gc_prof_record(objspace);
4909 record->moved_objects = objspace->rcompactor.total_moved - record->moved_objects;
4910 }
4911 if (!global_objspace->global_gc.compacting) objspace->flags.during_compacting = FALSE;
4912}
4913
4915 struct heap_page *page;
4916 int final_slots;
4917 int freed_slots;
4918 int empty_slots;
4919 /* Hoisted out of the per-slot pinned-free assert: too expensive for the sweep loop
4920 * as an external call. */
4921 const bool check_pinned_free;
4922 /* This is a parallel local sweep (multi-Ractor, not a global GC), so a non-thread-safe
4923 * T_DATA dfree must be deferred to the global GC or the postponed job rather than run here. */
4924 const bool defer_thread_unsafe_local_sweep;
4925 bool trigger_thread_unsafe_sweep_postponed_job;
4926
4927 struct free_region *free_region;
4928};
4929
4930/* NOTE: We must free the root fiber during postmortem collection, otherwise another Ractor
4931 * can collect the fiber through a major GC while we're still tearing it down. Once fibers are
4932 * THREAD_SAFE_FREE, we no longer need the root fiber condition as it will be guaranteed to be
4933 * collected during this time. */
4934static bool
4935gc_obj_defer_local_free_p(rb_objspace_t *objspace, VALUE obj)
4936{
4937 if (BUILTIN_TYPE(obj) != T_DATA) return false;
4938
4939 const rb_data_type_t *type = RTYPEDDATA_TYPE(obj);
4940 if (!rb_gc_data_type_deferred_free_p(type)) return false;
4941
4942 if (RTYPEDDATA_GET_DATA(obj) == NULL) return false;
4943
4944 if (type->flags & RUBY_TYPED_FREE_IMMEDIATELY) {
4945 if (objspace->flags.during_postmortem) {
4946 if (rb_fiber_current() == obj) {
4947 return false;
4948 }
4949 }
4950 return true;
4951 }
4952 else {
4953 return false;
4954 }
4955}
4956
4957static void gc_tdata_deferred_free_job(void *unused);
4958static void gc_tdata_deferred_free_pjob_ensure(void);
4959static unsigned int gc_during_gc_get(const rb_objspace_t *objspace);
4960static void gc_during_gc_set(rb_objspace_t *objspace, unsigned int v);
4961static void gc_global_snapshot_objspaces(void);
4962
4963static void
4964gc_tdata_deferred_free_pjob_ensure(void)
4965{
4966 if (global_objspace->tdata_deferred_free_pjob == POSTPONED_JOB_HANDLE_INVALID) {
4967 global_objspace->tdata_deferred_free_pjob =
4968 rb_postponed_job_preregister(0, gc_tdata_deferred_free_job, NULL);
4969 if (global_objspace->tdata_deferred_free_pjob == POSTPONED_JOB_HANDLE_INVALID) {
4970 rb_bug("Could not preregister postponed job for deferred T_DATA free");
4971 }
4972 }
4973}
4974
4975/* A terminating Ractor's postmortem collection runs on an EC whose stack is already
4976 * torn down: it never checks interrupts again, so a job triggered there is lost and no
4977 * later sweep can rediscover the entries. Hand those to the main Ractor. */
4978static void
4979gc_tdata_deferred_free_trigger(rb_objspace_t *objspace)
4980{
4981 if (objspace->flags.during_postmortem) {
4982 rb_gc_trigger_postponed_job_on_main(global_objspace->tdata_deferred_free_pjob);
4983 }
4984 else {
4985 rb_postponed_job_trigger(global_objspace->tdata_deferred_free_pjob);
4986 }
4987}
4988
4989static void
4990gc_tdata_unsafe_free_entry(const struct tdata_unsafe_free_entry *entry, bool embed_xfree)
4991{
4992 entry->dfree(entry->data);
4993 if (embed_xfree) {
4994 xfree(entry->data);
4995 }
4996}
4997
4998static void
4999tdata_unsafe_free_chunk_recycle(struct tdata_unsafe_free_chunk *chunk)
5000{
5001 if (global_objspace->tdata_unsafe_free_cache_len >= TDATA_UNSAFE_FREE_CACHE_MAX) {
5002 free(chunk);
5003 return;
5004 }
5005 tdata_unsafe_free_chunk_reset(chunk);
5006 chunk->next = global_objspace->tdata_unsafe_free_cache;
5007 rbimpl_atomic_ptr_store((volatile void **)&global_objspace->tdata_unsafe_free_cache, chunk,
5008 RBIMPL_ATOMIC_RELEASE);
5009 global_objspace->tdata_unsafe_free_cache_len++;
5010}
5011
5012static void
5013gc_tdata_unsafe_drain_chunk(struct tdata_unsafe_free_chunk *chunk)
5014{
5015 for (unsigned int i = 0; i < chunk->count; i++) {
5016 gc_tdata_unsafe_free_entry(&chunk->entries[i],
5017 (chunk->embed_xfree_bits >> i) & 1);
5018 }
5019 tdata_unsafe_free_chunk_recycle(chunk);
5020}
5021
5022/* Run every pending deferred free: the published chunks (which belong to no objspace)
5023 * plus the given objspaces' partial chunks. The caller must have stopped the world --
5024 * VM barrier held, or a single Ractor left in the process -- and must pass every live
5025 * objspace, since the pending count is zeroed here. (Shutdown is the one exception:
5026 * nothing reads the count afterwards.) */
5027static void
5028gc_tdata_unsafe_drain_objspaces(rb_objspace_t **objspaces, size_t n)
5029{
5030 struct tdata_unsafe_free_chunk *chunk =
5031 rbimpl_atomic_ptr_exchange((void **)&global_objspace->tdata_unsafe_free_published, NULL,
5032 RBIMPL_ATOMIC_ACQ_REL);
5033 while (chunk) {
5034 struct tdata_unsafe_free_chunk *next = chunk->next;
5035 gc_tdata_unsafe_drain_chunk(chunk);
5036 chunk = next;
5037 }
5038
5039 for (size_t i = 0; i < n; i++) {
5040 rb_objspace_t *os = objspaces[i];
5041 struct tdata_unsafe_free_chunk *partial = os->tdata_unsafe_free_chunk;
5042 if (partial) {
5043 os->tdata_unsafe_free_chunk = NULL;
5044 gc_tdata_unsafe_drain_chunk(partial);
5045 }
5046 }
5047
5048 rbimpl_atomic_size_exchange(&global_objspace->tdata_deferred_free_count, 0,
5049 RBIMPL_ATOMIC_RELAXED);
5050}
5051
5052/* Stop the world and run the dfree function for all deferred T_DATAs. */
5053static void
5054gc_tdata_unsafe_drain(void)
5055{
5056 unsigned int lev = RB_GC_VM_LOCK();
5057
5058 if (tdata_deferred_free_count_load() == 0) {
5059 RB_GC_VM_UNLOCK(lev);
5060 return;
5061 }
5062
5063 rb_gc_vm_barrier();
5064
5065 gc_global_snapshot_objspaces();
5066
5067 /* Set during_gc=TRUE and init vm_context for the CURRENT objspace only.
5068 * The no-alloc guard checks only the allocating (=current) objspace's during_gc,
5069 * and rb_gc_get_ec() reads only the current objspace's vm_context.ec. */
5070 rb_objspace_t *objspace = rb_gc_get_objspace();
5071 unsigned int saved_during_gc = gc_during_gc_get(objspace);
5072 dont_gc_on();
5073 rb_gc_initialize_vm_context(&objspace->vm_context);
5074 gc_during_gc_set(objspace, TRUE);
5075
5076 gc_tdata_unsafe_drain_objspaces(global_objspace->global_gc.objspaces,
5077 global_objspace->global_gc.n_objspaces);
5078
5079 gc_during_gc_set(objspace, saved_during_gc);
5080 dont_gc_off();
5081
5082 RB_GC_VM_UNLOCK(lev);
5083}
5084
5085static void
5086gc_tdata_deferred_free_job(void *unused)
5087{
5088 (void)unused;
5089
5090 size_t count = tdata_deferred_free_count_load();
5091 if (count == 0) return;
5092 if (count < TDATA_DEFERRED_FREE_THRESHOLD && !rb_gc_single_objspace_p()) return;
5093
5094 gc_tdata_unsafe_drain();
5095}
5096
5097static inline void
5098gc_sweep_register_free_slot(rb_objspace_t *objspace, struct heap_page *page, struct gc_sweep_context *ctx, uintptr_t p, short slot_size)
5099{
5100 rb_asan_unpoison_object(p, false);
5101 ((struct RBasic *)p)->flags = 0;
5102
5103 /* Keep a freed slot from carrying its old shareable and shref bits into the next
5104 * object born there; the actual clear happens per bitmap word at the end of
5105 * gc_sweep_page rather than per slot. */
5106
5107 struct free_region *existing_region = ctx->free_region;
5108 if (existing_region) rb_asan_unpoison_object((VALUE)existing_region, false);
5109
5110 if (RB_LIKELY(existing_region && p == existing_region->end)) {
5111 existing_region->end = p + slot_size;
5112 }
5113 else {
5114 struct free_region *free_region = (struct free_region *)p;
5115 free_region->end = p + slot_size;
5116 free_region->next = existing_region;
5117
5118 ctx->free_region = free_region;
5119 }
5120
5121 if (existing_region) rb_asan_poison_object((VALUE)existing_region);
5122 rb_asan_poison_object(p);
5123}
5124
5125static inline void
5126gc_sweep_plane(rb_objspace_t *objspace, rb_heap_t *heap, uintptr_t p, bits_t bitset, struct gc_sweep_context *ctx)
5127{
5128 struct heap_page *sweep_page = ctx->page;
5129 short slot_size = sweep_page->slot_size;
5130
5131 do {
5132 VALUE vp = (VALUE)p;
5133 GC_ASSERT(vp % sizeof(VALUE) == 0);
5134
5135 rb_asan_unpoison_object(vp, false);
5136 if (bitset & 1) {
5137 switch (BUILTIN_TYPE(vp)) {
5138 case T_MOVED:
5139 if (objspace->flags.during_compacting) {
5140 /* The sweep cursor shouldn't have made it to any
5141 * T_MOVED slots while the compact flag is enabled.
5142 * The sweep cursor and compact cursor move in
5143 * opposite directions, and when they meet references will
5144 * get updated and "during_compacting" should get disabled */
5145 rb_bug("T_MOVED shouldn't be seen until compaction is finished");
5146 }
5147 gc_report(3, objspace, "page_sweep: %s is freed\n", rb_obj_info(vp));
5148 ctx->empty_slots++;
5149 gc_sweep_register_free_slot(objspace, sweep_page, ctx, p, slot_size);
5150 break;
5151 case T_ZOMBIE:
5152 /* already counted */
5153 break;
5154 case T_NONE:
5155 ctx->empty_slots++; /* already freed */
5156 gc_sweep_register_free_slot(objspace, sweep_page, ctx, p, slot_size);
5157 break;
5158
5159 default:
5160#if RGENGC_CHECK_MODE
5161 /* A local GC must never free a pinned slot; a global GC may (its exact
5162 * mark collects dead shareable objects). Reading the bits here is
5163 * CHECK-only and still valid: the bulk clear runs after the free loop. */
5164 if (ctx->check_pinned_free &&
5165 (MARKED_IN_BITMAP(GET_HEAP_SHAREABLE_BITS(vp), vp) ||
5166 MARKED_IN_BITMAP(GET_HEAP_SHREF_BITS(vp), vp))) {
5167 rb_bug("page_sweep: freeing pinned slot %s (shareable=%d shref=%d single_now=%d)",
5168 rb_obj_info(vp),
5169 (int)!!MARKED_IN_BITMAP(GET_HEAP_SHAREABLE_BITS(vp), vp),
5170 (int)!!MARKED_IN_BITMAP(GET_HEAP_SHREF_BITS(vp), vp),
5171 (int)rb_gc_single_objspace_p());
5172 }
5173#endif
5174#if RGENGC_CHECK_MODE
5175 if (!is_full_marking(objspace)) {
5176 if (RVALUE_OLD_P(objspace, vp)) rb_bug("page_sweep: %p - old while minor GC.", (void *)p);
5177 if (RVALUE_REMEMBERED(objspace, vp)) rb_bug("page_sweep: %p - remembered.", (void *)p);
5178 }
5179#endif
5180
5181#if RGENGC_CHECK_MODE
5182#define CHECK(x) if (x(objspace, vp) != FALSE) rb_bug("obj_free: " #x "(%s) != FALSE", rb_obj_info(vp))
5183 CHECK(RVALUE_WB_UNPROTECTED);
5184 CHECK(RVALUE_MARKED);
5185 CHECK(RVALUE_MARKING);
5186 CHECK(RVALUE_UNCOLLECTIBLE);
5187#undef CHECK
5188#endif
5189
5190 if (!rb_gc_obj_needs_cleanup_p(vp)) {
5191 (void)VALGRIND_MAKE_MEM_UNDEFINED((void*)p, slot_size);
5192 gc_sweep_register_free_slot(objspace, sweep_page, ctx, p, slot_size);
5193 gc_report(3, objspace, "page_sweep: %s (fast path) is freed\n", rb_obj_info(vp));
5194 ctx->freed_slots++;
5195 }
5196 else {
5197 gc_report(2, objspace, "page_sweep: free %p\n", (void *)p);
5198
5199 if (RB_UNLIKELY(ctx->defer_thread_unsafe_local_sweep && gc_obj_defer_local_free_p(objspace, vp))) {
5200 /* Defer the dfree instead of running it here: it needs the world
5201 * stopped, which a parallel local sweep cannot give it. The slot is reusable
5202 * right away unless we had to create a zombie. */
5203 if (gc_defer_thread_unsafe_free(objspace, vp,
5204 &ctx->trigger_thread_unsafe_sweep_postponed_job)) {
5205 (void)VALGRIND_MAKE_MEM_UNDEFINED((void*)p, slot_size);
5206 gc_sweep_register_free_slot(objspace, sweep_page, ctx, p, slot_size);
5207 ctx->freed_slots++;
5208 }
5209 else {
5210 ctx->final_slots++;
5211 }
5212 break;
5213 }
5214 rb_gc_obj_free_vm_weak_references(vp);
5215 if (gc_obj_free(objspace, vp)) {
5216 (void)VALGRIND_MAKE_MEM_UNDEFINED((void*)p, slot_size);
5217 gc_sweep_register_free_slot(objspace, sweep_page, ctx, p, slot_size);
5218 gc_report(3, objspace, "page_sweep: %s is freed\n", rb_obj_info(vp));
5219 ctx->freed_slots++;
5220 }
5221 else {
5222 ctx->final_slots++;
5223 }
5224 }
5225 break;
5226 }
5227 }
5228 p += slot_size;
5229 bitset >>= 1;
5230 } while (bitset);
5231}
5232
5233static inline void
5234gc_sweep_page(rb_objspace_t *objspace, rb_heap_t *heap, struct gc_sweep_context *ctx)
5235{
5236 struct heap_page *sweep_page = ctx->page;
5237 GC_ASSERT(sweep_page->heap == heap);
5238
5239 uintptr_t p;
5240 bits_t *bits, bitset;
5241
5242 gc_report(2, objspace, "page_sweep: start.\n");
5243
5244#if RGENGC_CHECK_MODE
5245 if (!objspace->flags.immediate_sweep) {
5246 GC_ASSERT(sweep_page->flags.before_sweep == TRUE);
5247 }
5248#endif
5249 sweep_page->flags.before_sweep = FALSE;
5250 sweep_page->free_slots = 0;
5251
5252 asan_unlock_freelist(sweep_page);
5253 sweep_page->free_region = NULL;
5254 asan_lock_freelist(sweep_page);
5255 ctx->free_region = NULL;
5256
5257 p = (uintptr_t)sweep_page->start;
5258 bits = sweep_page->mark_bits;
5259 short slot_size = sweep_page->slot_size;
5260 int total_slots = sweep_page->total_slots;
5261 int bitmap_plane_count = CEILDIV(total_slots, BITS_BITLENGTH);
5262
5263 int out_of_range_bits = total_slots % BITS_BITLENGTH;
5264 if (out_of_range_bits != 0) {
5265 bits[bitmap_plane_count - 1] |= ~(((bits_t)1 << out_of_range_bits) - 1);
5266 }
5267
5268 // Clear wb_unprotected and age bits for all unmarked slots
5269 {
5270 bits_t *wb_unprotected_bits = sweep_page->wb_unprotected_bits;
5271 bits_t *age_bits = sweep_page->age_bits;
5272 for (int i = 0; i < bitmap_plane_count; i++) {
5273 bits_t unmarked = ~bits[i];
5274 wb_unprotected_bits[i] &= ~unmarked;
5275 age_bits[i * 2] &= ~unmarked;
5276 age_bits[i * 2 + 1] &= ~unmarked;
5277 }
5278 }
5279
5280 for (int i = 0; i < bitmap_plane_count; i++) {
5281 bitset = ~bits[i];
5282 if (bitset) {
5283 gc_sweep_plane(objspace, heap, p, bitset, ctx);
5284 }
5285 p += BITS_BITLENGTH * slot_size;
5286 }
5287
5288 /* Bulk-clear the freed slots' shareable and shref bits before the freelist is
5289 * published, so a reused slot is clean. Freed slots are exactly the unmarked ones,
5290 * so `bits &= mark_bits` keeps live shareable objects (which must stay pinned) and
5291 * drops the rest. Pages with neither bit are skipped. */
5292 if (sweep_page->flags.has_shareable_objects || sweep_page->flags.has_shref_objects) {
5293 bits_t *shareable_bits = sweep_page->shareable_bits;
5294 bits_t *shref_bits = sweep_page->shref_bits;
5295 bits_t sh = 0, sr = 0;
5296 for (int i = 0; i < bitmap_plane_count; i++) {
5297 shareable_bits[i] &= bits[i];
5298 shref_bits[i] &= bits[i];
5299 sh |= shareable_bits[i];
5300 sr |= shref_bits[i];
5301 }
5302 if (!sh) sweep_page->flags.has_shareable_objects = FALSE;
5303 if (!sr) sweep_page->flags.has_shref_objects = FALSE;
5304 }
5305
5306 asan_unlock_freelist(sweep_page);
5307 sweep_page->free_region = ctx->free_region;
5308 asan_lock_freelist(sweep_page);
5309
5310 if (!heap->compact_cursor) {
5311 gc_setup_mark_bits(sweep_page);
5312 }
5313
5314#if GC_PROFILE_MORE_DETAIL
5315 if (gc_prof_enabled(objspace)) {
5316 gc_profile_record *record = gc_prof_record(objspace);
5317 record->removing_objects += ctx->final_slots + ctx->freed_slots;
5318 record->empty_objects += ctx->empty_slots;
5319 }
5320#endif
5321 if (0) fprintf(stderr, "gc_sweep_page(%"PRIdSIZE"): total_slots: %d, freed_slots: %d, empty_slots: %d, final_slots: %d\n",
5322 rb_gc_count(),
5323 sweep_page->total_slots,
5324 ctx->freed_slots, ctx->empty_slots, ctx->final_slots);
5325
5326 sweep_page->free_slots += ctx->freed_slots + ctx->empty_slots;
5327 sweep_page->heap->total_freed_objects += ctx->freed_slots;
5328
5329 if (heap_pages_deferred_final && !finalizing) {
5330 gc_finalize_deferred_register(objspace);
5331 }
5332
5333#if RGENGC_CHECK_MODE
5334 int region_slots = 0;
5335 asan_unlock_freelist(sweep_page);
5336 struct free_region *region = sweep_page->free_region;
5337 while (region) {
5338 rb_asan_unpoison_object((VALUE)region, false);
5339 GC_ASSERT(RB_TYPE_P((VALUE)region, T_NONE));
5340 uintptr_t region_start = (uintptr_t)region;
5341 uintptr_t region_end = region->end;
5342 struct free_region *next = region->next;
5343 rb_asan_poison_object((VALUE)region);
5344
5345 GC_ASSERT(region_end > region_start);
5346 GC_ASSERT((region_end - region_start) % slot_size == 0);
5347 region_slots += (int)((region_end - region_start) / slot_size);
5348
5349 region = next;
5350 }
5351 asan_lock_freelist(sweep_page);
5352 if (region_slots != sweep_page->free_slots) {
5353 rb_bug("inconsistent free region slots: expected %d but was %d", sweep_page->free_slots, region_slots);
5354 }
5355#endif
5356
5357 gc_report(2, objspace, "page_sweep: end.\n");
5358}
5359
5360static const char *
5361gc_mode_name(enum gc_mode mode)
5362{
5363 switch (mode) {
5364 case gc_mode_none: return "none";
5365 case gc_mode_marking: return "marking";
5366 case gc_mode_sweeping: return "sweeping";
5367 case gc_mode_compacting: return "compacting";
5368 default: rb_bug("gc_mode_name: unknown mode: %d", (int)mode);
5369 }
5370}
5371
5372static void
5373gc_mode_transition(rb_objspace_t *objspace, enum gc_mode mode)
5374{
5375#if RGENGC_CHECK_MODE
5376 enum gc_mode prev_mode = gc_mode(objspace);
5377 switch (prev_mode) {
5378 case gc_mode_none:
5379 /* A global GC marks every objspace as one heap (mark_roots on the driver), so an
5380 * individual objspace's mode stays `none` during that mark; the sweep inside the
5381 * barrier then makes the legitimate none -> sweeping transition. */
5382 GC_ASSERT(mode == gc_mode_marking ||
5383 (objspace->flags.during_global_gc && mode == gc_mode_sweeping));
5384 break;
5385 case gc_mode_marking: GC_ASSERT(mode == gc_mode_sweeping); break;
5386 case gc_mode_sweeping: GC_ASSERT(mode == gc_mode_none || mode == gc_mode_compacting); break;
5387 case gc_mode_compacting: GC_ASSERT(mode == gc_mode_none); break;
5388 }
5389#endif
5390 if (0) fprintf(stderr, "gc_mode_transition: %s->%s\n", gc_mode_name(gc_mode(objspace)), gc_mode_name(mode));
5391 gc_mode_set(objspace, mode);
5392}
5393
5394static void
5395heap_page_flush_alloc_regions(struct heap_page *page, rb_heap_t *heap)
5396{
5397 struct free_region *chain = heap->newobj.alloc_next_region;
5398
5399 if (heap->newobj.alloc_cursor < heap->newobj.alloc_cursor_end) {
5400 VALUE start = (VALUE)heap->newobj.alloc_cursor;
5401 rb_asan_unpoison_object(start, false);
5402 struct free_region *remnant = (struct free_region *)start;
5403 remnant->flags = 0;
5404 remnant->end = heap->newobj.alloc_cursor_end;
5405 remnant->next = chain;
5406 rb_asan_poison_object(start);
5407 chain = remnant;
5408 }
5409
5410 if (chain) {
5411 asan_unlock_freelist(page);
5412 if (page->free_region) {
5413 struct free_region *p = page->free_region;
5414 rb_asan_unpoison_object((VALUE)p, false);
5415 while (p->next) {
5416 struct free_region *prev = p;
5417 p = p->next;
5418 rb_asan_poison_object((VALUE)prev);
5419 rb_asan_unpoison_object((VALUE)p, false);
5420 }
5421 p->next = chain;
5422 rb_asan_poison_object((VALUE)p);
5423 }
5424 else {
5425 page->free_region = chain;
5426 }
5427 asan_lock_freelist(page);
5428 }
5429}
5430
5431static void
5432gc_sweep_start_heap(rb_objspace_t *objspace, rb_heap_t *heap)
5433{
5434 heap->sweeping_page = ccan_list_top(&heap->pages, struct heap_page, page_node);
5435 if (heap->sweeping_page) {
5436 objspace->sweeping_heap_count++;
5437 }
5438 heap->free_pages = NULL;
5439 heap->pooled_pages = NULL;
5440 if (!objspace->flags.immediate_sweep) {
5441 struct heap_page *page = NULL;
5442
5443 ccan_list_for_each(&heap->pages, page, page_node) {
5444 page->flags.before_sweep = TRUE;
5445 }
5446 }
5447}
5448
5449#if GC_CAN_COMPILE_COMPACTION
5450static void gc_sort_heap_by_compare_func(rb_objspace_t *objspace, gc_compact_compare_func compare_func);
5451static int compare_pinned_slots(const void *left, const void *right, void *d);
5452#endif
5453
5454/* Return the current allocation page and freelist to their pages, so the sweeper sees a
5455 * consistent heap. */
5456static void
5457heap_alloc_state_clear(rb_objspace_t *objspace)
5458{
5459 objspace->incremental_mark_step_allocated_slots = 0;
5460
5461 for (size_t heap_idx = 0; heap_idx < HEAP_COUNT; heap_idx++) {
5462 rb_heap_t *heap = &heaps[heap_idx];
5463
5464 struct heap_page *page = heap->newobj.alloc_using_page;
5465 RUBY_DEBUG_LOG("heap alloc_using_page:%p cursor:%p", (void *)page, (void *)heap->newobj.alloc_cursor);
5466
5467 if (page) {
5468 heap_page_flush_alloc_regions(page, heap);
5469 }
5470
5471 heap->newobj.alloc_using_page = NULL;
5472 heap->newobj.alloc_cursor = 0;
5473 heap->newobj.alloc_cursor_end = 0;
5474 heap->newobj.alloc_next_region = NULL;
5475 }
5476}
5477
5478static void
5479gc_sweep_freeobj_hooks_page(rb_objspace_t *objspace, struct heap_page *page)
5480{
5481 bits_t *bits = page->mark_bits;
5482 uintptr_t p = (uintptr_t)page->start;
5483 short slot_size = page->slot_size;
5484 int total_slots = page->total_slots;
5485 int bitmap_plane_count = CEILDIV(total_slots, BITS_BITLENGTH);
5486
5487 int out_of_range_bits = total_slots % BITS_BITLENGTH;
5488 bits_t last_plane_mask = (out_of_range_bits != 0)
5489 ? ~(((bits_t)1 << out_of_range_bits) - 1)
5490 : 0;
5491
5492 for (int j = 0; j < bitmap_plane_count; j++) {
5493 bits_t bitset = ~bits[j];
5494 if (j == bitmap_plane_count - 1) {
5495 bitset &= ~last_plane_mask;
5496 }
5497
5498 uintptr_t pp = p;
5499 while (bitset) {
5500 if (bitset & 1) {
5501 VALUE vp = (VALUE)pp;
5502 asan_unpoisoning_object(vp) {
5503 switch (BUILTIN_TYPE(vp)) {
5504 case T_NONE:
5505 case T_ZOMBIE:
5506 case T_MOVED:
5507 break;
5508 default:
5509 rb_gc_event_hook(vp, RUBY_INTERNAL_EVENT_FREEOBJ);
5510 break;
5511 }
5512 }
5513 }
5514 pp += slot_size;
5515 bitset >>= 1;
5516 }
5517 p += BITS_BITLENGTH * slot_size;
5518 }
5519}
5520
5521static void
5522gc_sweep_freeobj_hooks(rb_objspace_t *objspace)
5523{
5524 for (int i = 0; i < HEAP_COUNT; i++) {
5525 rb_heap_t *heap = &heaps[i];
5526 struct heap_page *page = NULL;
5527
5528 ccan_list_for_each(&heap->pages, page, page_node) {
5529 gc_sweep_freeobj_hooks_page(objspace, page);
5530 }
5531 }
5532}
5533
5534static void
5535gc_sweep_start(rb_objspace_t *objspace)
5536{
5537 gc_mode_transition(objspace, gc_mode_sweeping);
5538 objspace->rincgc.pooled_slots = 0;
5539
5540 if (RB_UNLIKELY(objspace->hook_events & RUBY_INTERNAL_EVENT_FREEOBJ)) {
5541 /* FREEOBJ is never enabled outside the main objspace
5542 * (rb_objspace_set_event_hook), so this hook, which runs user callbacks,
5543 * cannot fire during a non-main Ractor's lock-free local sweep. */
5544 GC_ASSERT(objspace == global_objspace->main_objspace);
5545 gc_sweep_freeobj_hooks(objspace);
5546 }
5547
5548#if GC_CAN_COMPILE_COMPACTION
5549 if (objspace->flags.during_compacting) {
5550 gc_sort_heap_by_compare_func(
5551 objspace,
5552 objspace->rcompactor.compare_func ? objspace->rcompactor.compare_func : compare_pinned_slots
5553 );
5554 }
5555#endif
5556
5557 for (int i = 0; i < HEAP_COUNT; i++) {
5558 rb_heap_t *heap = &heaps[i];
5559 gc_sweep_start_heap(objspace, heap);
5560
5561 /* We should call gc_sweep_finish_heap for size pools with no pages. */
5562 if (heap->sweeping_page == NULL) {
5563 GC_ASSERT(heap->total_pages == 0);
5564 GC_ASSERT(heap->total_slots == 0);
5565 gc_sweep_finish_heap(objspace, heap);
5566 }
5567 }
5568
5569 heap_alloc_state_clear(objspace);
5570}
5571
5572static void
5573gc_sweep_finish_heap(rb_objspace_t *objspace, rb_heap_t *heap)
5574{
5575 size_t total_slots = heap->total_slots;
5576 size_t swept_slots = heap->freed_slots + heap->empty_slots;
5577
5578 size_t init_slots = objspace_heap_init_bytes(objspace) / heap->slot_size;
5579 size_t min_free_slots = (size_t)(MAX(total_slots, init_slots) * gc_params.heap_free_slots_min_ratio);
5580
5581 if (swept_slots < min_free_slots &&
5582 /* The heap is a growth heap if it freed more slots than had empty slots. */
5583 ((heap->empty_slots == 0 && total_slots > 0) || heap->freed_slots > heap->empty_slots)) {
5584 /* If we don't have enough slots and we have pages on the tomb heap, move
5585 * pages from the tomb heap to the eden heap. This may prevent page
5586 * creation thrashing (frequently allocating and deallocting pages) and
5587 * GC thrashing (running GC more frequently than required). */
5588 struct heap_page *resurrected_page;
5589 while (swept_slots < min_free_slots &&
5590 (resurrected_page = heap_page_resurrect(objspace))) {
5591 heap_add_page(objspace, heap, resurrected_page);
5592 heap_add_freepage(heap, resurrected_page);
5593
5594 swept_slots += resurrected_page->free_slots;
5595 }
5596
5597 if (swept_slots < min_free_slots) {
5598 /* Grow this heap if we are in a major GC or if we haven't run at least
5599 * RVALUE_OLD_AGE minor GC since the last major GC. */
5600 if (is_full_marking(objspace) ||
5601 objspace->profile.count - objspace->rgengc.last_major_gc < RVALUE_OLD_AGE) {
5602 if (objspace->heap_pages.allocatable_bytes < min_free_slots * heap->slot_size) {
5603 heap_allocatable_bytes_expand(objspace, heap, swept_slots, heap->total_slots, heap->slot_size);
5604 }
5605 }
5606 else if (swept_slots < min_free_slots * 7 / 8 &&
5607 objspace->heap_pages.allocatable_bytes < (min_free_slots * 7 / 8 - swept_slots) * heap->slot_size) {
5608 gc_needs_major_flags |= GPR_FLAG_MAJOR_BY_NOFREE;
5609 heap->force_major_gc_count++;
5610 }
5611 }
5612 }
5613}
5614
5615static void
5616gc_sweep_finish(rb_objspace_t *objspace)
5617{
5618 gc_report(1, objspace, "gc_sweep_finish\n");
5619
5620 gc_prof_set_heap_info(objspace);
5621 heap_pages_free_unused_pages(objspace);
5622 if (rb_gc_single_objspace_p() && is_full_marking(objspace)) {
5623 /* gc_marks_finish retains ~2/3 of empty pages in objspace->empty_pages for reuse,
5624 * only the excess reaches the pool. */
5625 page_pool_reclaim(global_objspace);
5626 }
5627
5628 for (int i = 0; i < HEAP_COUNT; i++) {
5629 rb_heap_t *heap = &heaps[i];
5630
5631 heap->freed_slots = 0;
5632 heap->empty_slots = 0;
5633
5634 if (!will_be_incremental_marking(objspace)) {
5635 struct heap_page *end_page = heap->free_pages;
5636 if (end_page) {
5637 while (end_page->free_next) end_page = end_page->free_next;
5638 end_page->free_next = heap->pooled_pages;
5639 }
5640 else {
5641 heap->free_pages = heap->pooled_pages;
5642 }
5643 heap->pooled_pages = NULL;
5644 objspace->rincgc.pooled_slots = 0;
5645 }
5646 }
5647
5648 /* Not before: while sweeping is in progress its frees must keep reducing
5649 * malloc_increase (objspace_malloc_increase_body sweeps and retries on it). */
5650 gc_malloc_counters_snapshot_free_at_last_gc(objspace, &objspace->malloc_counters.counters);
5651#if RGENGC_ESTIMATE_OLDMALLOC
5652 gc_malloc_counters_snapshot_free_at_last_gc(objspace, &objspace->malloc_counters.oldcounters);
5653#endif
5654
5655 /* Leftovers from an earlier multi-Ractor phase: no later sweep can rediscover them
5656 * (their slots are gone), and with one Ractor left the drain's barrier has nothing
5657 * to wait for. */
5658 if (tdata_deferred_free_count_load() > 0 && rb_gc_single_objspace_p()) {
5659 gc_tdata_deferred_free_trigger(objspace);
5660 }
5661
5663 gc_mode_transition(objspace, gc_mode_none);
5664}
5665
5666static int
5667gc_sweep_step(rb_objspace_t *objspace, rb_heap_t *heap)
5668{
5669 struct heap_page *sweep_page = heap->sweeping_page;
5670 int swept_slots = 0;
5671 int pooled_slots = 0;
5672 int sweep_budget = GC_INCREMENTAL_SWEEP_BYTES / heap->slot_size;
5673 int pool_budget = GC_INCREMENTAL_SWEEP_POOL_BYTES / heap->slot_size;
5674
5675 if (sweep_page == NULL) return FALSE;
5676
5677#if GC_ENABLE_LAZY_SWEEP
5678 gc_prof_sweep_timer_start(objspace);
5679#endif
5680
5681 /* Per-slot pinned-free assert (gc_sweep_context): check only when this cycle's mark
5682 * ran the pinned walk. The current world state would misfire: a single-world
5683 * cycle leaves dead shareable objects unmarked and its sweep can straddle the switch
5684 * to multi-objspace. A global GC's exact mark does not pin, so it is excluded. */
5685 const bool check_pinned_free = objspace->last_cycle_pinned;
5686
5687 const bool defer_thread_unsafe_local_sweep =
5688 !rb_gc_single_objspace_p() && !objspace->flags.during_global_gc;
5689 bool trigger_thread_unsafe_sweep_postponed_job = false;
5690
5691 do {
5692 RUBY_DEBUG_LOG("sweep_page:%p", (void *)sweep_page);
5693
5694 struct gc_sweep_context ctx = {
5695 .page = sweep_page,
5696 .final_slots = 0,
5697 .freed_slots = 0,
5698 .empty_slots = 0,
5699 .check_pinned_free = check_pinned_free,
5700 .defer_thread_unsafe_local_sweep = defer_thread_unsafe_local_sweep,
5701 .trigger_thread_unsafe_sweep_postponed_job = trigger_thread_unsafe_sweep_postponed_job,
5702 };
5703 gc_sweep_page(objspace, heap, &ctx);
5704 int free_slots = ctx.freed_slots + ctx.empty_slots;
5705 trigger_thread_unsafe_sweep_postponed_job = ctx.trigger_thread_unsafe_sweep_postponed_job;
5706
5707 RUBY_DTRACE_GC_HOOK(SWEEP_PAGE, ctx.page->slot_size, ctx.final_slots, ctx.freed_slots, ctx.empty_slots);
5708
5709 heap->sweeping_page = ccan_list_next(&heap->pages, sweep_page, page_node);
5710
5711 if (free_slots == sweep_page->total_slots && heap->total_pages > 1) {
5712 /* There are no living objects, so move this page to the global empty pages.
5713 * The last one stays: nothing grows a heap that has no pages at all. */
5714 heap_unlink_page(objspace, heap, sweep_page);
5715
5716 sweep_page->start = 0;
5717 sweep_page->total_slots = 0;
5718 sweep_page->slot_size = 0;
5719 sweep_page->heap = NULL;
5720 sweep_page->free_slots = 0;
5721
5722 asan_unlock_freelist(sweep_page);
5723 sweep_page->free_region = NULL;
5724 asan_lock_freelist(sweep_page);
5725
5726 asan_poison_memory_region(sweep_page->body, HEAP_PAGE_SIZE);
5727
5728 objspace->empty_pages_count++;
5729 sweep_page->free_next = objspace->empty_pages;
5730 objspace->empty_pages = sweep_page;
5731 }
5732 else if (free_slots > 0) {
5733 heap->freed_slots += ctx.freed_slots;
5734 heap->empty_slots += ctx.empty_slots;
5735
5736 if (pooled_slots < pool_budget) {
5737 heap_add_poolpage(objspace, heap, sweep_page);
5738 pooled_slots += free_slots;
5739 }
5740 else {
5741 heap_add_freepage(heap, sweep_page);
5742 swept_slots += free_slots;
5743 if (swept_slots > sweep_budget) {
5744 break;
5745 }
5746 }
5747 }
5748 else {
5749 sweep_page->free_next = NULL;
5750 }
5751 } while ((sweep_page = heap->sweeping_page));
5752
5753 if (trigger_thread_unsafe_sweep_postponed_job) {
5754 gc_report(2, objspace, "thread-unsafe sweep postponed job triggered\n");
5755 gc_tdata_deferred_free_trigger(objspace);
5756 }
5757
5758 if (!heap->sweeping_page) {
5759 objspace->sweeping_heap_count--;
5760 GC_ASSERT(objspace->sweeping_heap_count >= 0);
5761 gc_sweep_finish_heap(objspace, heap);
5762
5763 if (!has_sweeping_pages(objspace)) {
5764 gc_sweep_finish(objspace);
5765 }
5766 }
5767
5768#if GC_ENABLE_LAZY_SWEEP
5769 gc_prof_sweep_timer_stop(objspace);
5770#endif
5771
5772 return heap->free_pages != NULL;
5773}
5774
5775static void
5776gc_sweep_rest(rb_objspace_t *objspace)
5777{
5778 for (int i = 0; i < HEAP_COUNT; i++) {
5779 rb_heap_t *heap = &heaps[i];
5780
5781 while (heap->sweeping_page) {
5782 gc_sweep_step(objspace, heap);
5783 }
5784 }
5785
5786 /* An objspace with no live pages never runs gc_sweep_step and so never reaches
5787 * gc_sweep_finish, leaving mode at sweeping or compacting until the next cycle's
5788 * gc_sweep_start asserts. If every heap is swept out, settle it to none here. */
5789 if (gc_mode(objspace) != gc_mode_none && !has_sweeping_pages(objspace)) {
5790 gc_sweep_finish(objspace);
5791 }
5792}
5793
5794static void
5795gc_sweep_continue(rb_objspace_t *objspace, rb_heap_t *sweep_heap)
5796{
5797 GC_ASSERT(dont_gc_val() == FALSE || objspace->profile.latest_gc_info & GPR_FLAG_METHOD);
5798 if (!GC_ENABLE_LAZY_SWEEP) return;
5799
5800 gc_sweeping_enter(objspace);
5801
5802 for (int i = 0; i < HEAP_COUNT; i++) {
5803 rb_heap_t *heap = &heaps[i];
5804 if (gc_sweep_step(objspace, heap)) {
5805 GC_ASSERT(heap->free_pages != NULL);
5806 }
5807 else if (heap == sweep_heap) {
5808 if (objspace->empty_pages_count > 0 || objspace->heap_pages.allocatable_bytes > 0) {
5809 /* [Bug #21548]
5810 *
5811 * If this heap is the heap we want to sweep, but we weren't able
5812 * to free any slots, but we also either have empty pages or could
5813 * allocate new pages, then we want to preemptively claim a page
5814 * because it's possible that sweeping another heap will call
5815 * gc_sweep_finish_heap, which may use up all of the
5816 * empty/allocatable pages. If other heaps are not finished sweeping
5817 * then we do not finish this GC and we will end up triggering a new
5818 * GC cycle during this GC phase. */
5819 heap_page_allocate_and_initialize(objspace, heap);
5820
5821 GC_ASSERT(heap->free_pages != NULL);
5822 }
5823 else {
5824 /* Not allowed to create a new page so finish sweeping. */
5825 gc_sweep_rest(objspace);
5826 GC_ASSERT(gc_mode(objspace) == gc_mode_none);
5827 break;
5828 }
5829 }
5830 }
5831
5832 gc_sweeping_exit(objspace);
5833}
5834
5835static void
5836gc_sweep_step_for_malloc(rb_objspace_t *objspace)
5837{
5838 GC_ASSERT(is_lazy_sweeping(objspace));
5839
5840 unsigned int lock_lev;
5841 gc_enter(objspace, gc_enter_event_continue, &lock_lev);
5842
5843 gc_sweeping_enter(objspace);
5844
5845 for (int i = 0; i < HEAP_COUNT; i++) {
5846 rb_heap_t *heap = &heaps[i];
5847 gc_sweep_step(objspace, heap);
5848 }
5849
5850 gc_sweeping_exit(objspace);
5851
5852 gc_exit(objspace, gc_enter_event_continue, &lock_lev);
5853}
5854
5855static bool gc_global_pointer_to_heap_p(const void *ptr);
5856
5857VALUE
5858rb_gc_impl_location(void *objspace_ptr, VALUE value)
5859{
5860 rb_objspace_t *objspace = objspace_ptr;
5861 VALUE destination;
5862
5863 /* A local (single-objspace) compaction never moves another objspace's objects, so
5864 * leave foreign references alone. A compacting global GC moves objects everywhere
5865 * under the barrier, so there every objspace's heap is searched for forwarding. */
5866 if (RB_UNLIKELY(objspace->flags.during_global_gc)
5867 ? !gc_global_pointer_to_heap_p((void *)value)
5868 : !is_pointer_to_heap(objspace_ptr, (void *)value)) {
5869 return value;
5870 }
5871
5872 asan_unpoisoning_object(value) {
5873 if (BUILTIN_TYPE(value) == T_MOVED) {
5874 destination = (VALUE)RMOVED(value)->destination;
5875 GC_ASSERT(BUILTIN_TYPE(destination) != T_NONE);
5876 }
5877 else {
5878 destination = value;
5879 }
5880 }
5881
5882 return destination;
5883}
5884
5885#if GC_CAN_COMPILE_COMPACTION
5886static void
5887invalidate_moved_plane(rb_objspace_t *objspace, struct heap_page *page, uintptr_t p, bits_t bitset)
5888{
5889 if (bitset) {
5890 do {
5891 if (bitset & 1) {
5892 VALUE forwarding_object = (VALUE)p;
5893 VALUE object;
5894
5895 if (BUILTIN_TYPE(forwarding_object) == T_MOVED) {
5896 GC_ASSERT(RVALUE_PINNED(objspace, forwarding_object));
5897 GC_ASSERT(!RVALUE_MARKED(objspace, forwarding_object));
5898
5899 CLEAR_IN_BITMAP(GET_HEAP_PINNED_BITS(forwarding_object), forwarding_object);
5900
5901 object = rb_gc_impl_location(objspace, forwarding_object);
5902 gc_move(objspace, object, forwarding_object, GET_HEAP_PAGE(object), page);
5903 /* forwarding_object is now our actual object, and "object"
5904 * is the free slot for the original page */
5905
5906 struct heap_page *orig_page = GET_HEAP_PAGE(object);
5907 orig_page->free_slots++;
5908 RVALUE_AGE_SET_BITMAP(object, 0);
5909 heap_page_add_free_region(objspace, orig_page, object);
5910
5911 GC_ASSERT(RVALUE_MARKED(objspace, forwarding_object));
5912 GC_ASSERT(BUILTIN_TYPE(forwarding_object) != T_MOVED);
5913 GC_ASSERT(BUILTIN_TYPE(forwarding_object) != T_NONE);
5914 }
5915 }
5916 p += page->slot_size;
5917 bitset >>= 1;
5918 } while (bitset);
5919 }
5920}
5921
5922static void
5923invalidate_moved_page(rb_objspace_t *objspace, struct heap_page *page)
5924{
5925 int i;
5926 bits_t *mark_bits, *pin_bits;
5927 bits_t bitset;
5928 short slot_size = page->slot_size;
5929 int total_slots = page->total_slots;
5930 int bitmap_plane_count = CEILDIV(total_slots, BITS_BITLENGTH);
5931
5932 mark_bits = page->mark_bits;
5933 pin_bits = page->pinned_bits;
5934
5935 uintptr_t p = page->start;
5936
5937 for (i=0; i < bitmap_plane_count; i++) {
5938 /* Moved objects are pinned but never marked. We reuse the pin bits
5939 * to indicate there is a moved object in this slot. */
5940 bitset = pin_bits[i] & ~mark_bits[i];
5941 invalidate_moved_plane(objspace, page, p, bitset);
5942 p += BITS_BITLENGTH * slot_size;
5943 }
5944}
5945#endif
5946
5947static void
5948gc_compact_start(rb_objspace_t *objspace)
5949{
5950 struct heap_page *page = NULL;
5951 gc_mode_transition(objspace, gc_mode_compacting);
5952
5953 for (int i = 0; i < HEAP_COUNT; i++) {
5954 rb_heap_t *heap = &heaps[i];
5955 ccan_list_for_each(&heap->pages, page, page_node) {
5956 page->flags.before_sweep = TRUE;
5957 }
5958
5959 heap->compact_cursor = ccan_list_tail(&heap->pages, struct heap_page, page_node);
5960 heap->compact_cursor_index = 0;
5961 }
5962
5963 if (gc_prof_enabled(objspace)) {
5964 gc_profile_record *record = gc_prof_record(objspace);
5965 record->moved_objects = objspace->rcompactor.total_moved;
5966 }
5967
5968 memset(objspace->rcompactor.considered_count_table, 0, T_MASK * sizeof(size_t));
5969 memset(objspace->rcompactor.moved_count_table, 0, T_MASK * sizeof(size_t));
5970 memset(objspace->rcompactor.moved_up_count_table, 0, T_MASK * sizeof(size_t));
5971 memset(objspace->rcompactor.moved_down_count_table, 0, T_MASK * sizeof(size_t));
5972
5973 /* Set up read barrier for pages containing MOVED objects */
5974 /* A compacting global GC installs the read barrier once for every objspace. */
5975 if (!global_objspace->global_gc.compacting) install_handlers();
5976}
5977
5978static void gc_sweep_compact(rb_objspace_t *objspace);
5979
5980static void
5981gc_sweep(rb_objspace_t *objspace)
5982{
5983 gc_sweeping_enter(objspace);
5984
5985 const unsigned int immediate_sweep = objspace->flags.immediate_sweep;
5986
5987 gc_report(1, objspace, "gc_sweep: immediate: %d\n", immediate_sweep);
5988
5989 gc_sweep_start(objspace);
5990 if (objspace->flags.during_compacting) {
5991 rb_hrtime_t compact_start_time = gc_prof_enabled(objspace) ? rb_hrtime_now() : 0;
5992 gc_sweep_compact(objspace);
5993 if (gc_prof_enabled(objspace)) {
5994 rb_hrtime_t compact_wall_time = elapsed_hrtime_from(compact_start_time);
5995 gc_profile_record *record = gc_prof_record(objspace);
5996 record->gc_compact_wall_time = rb_hrtime_add(record->gc_compact_wall_time,
5997 compact_wall_time);
5998 objspace->profile.gc_sweep_excluded_wall_time = rb_hrtime_add(
5999 objspace->profile.gc_sweep_excluded_wall_time,
6000 compact_wall_time);
6001 }
6002 }
6003
6004 if (immediate_sweep) {
6005#if !GC_ENABLE_LAZY_SWEEP
6006 gc_prof_sweep_timer_start(objspace);
6007#endif
6008 gc_sweep_rest(objspace);
6009#if !GC_ENABLE_LAZY_SWEEP
6010 gc_prof_sweep_timer_stop(objspace);
6011#endif
6012 }
6013 else {
6014
6015 /* Sweep every size pool. */
6016 for (int i = 0; i < HEAP_COUNT; i++) {
6017 rb_heap_t *heap = &heaps[i];
6018 gc_sweep_step(objspace, heap);
6019 }
6020 }
6021
6022 gc_sweeping_exit(objspace);
6023}
6024
6025/* Marking - Marking stack */
6026
6027static stack_chunk_t *
6028stack_chunk_alloc(void)
6029{
6030 stack_chunk_t *res;
6031
6032 res = malloc(sizeof(stack_chunk_t));
6033 if (!res)
6034 rb_memerror();
6035
6036 return res;
6037}
6038
6039static inline int
6040is_mark_stack_empty(mark_stack_t *stack)
6041{
6042 return stack->chunk == NULL;
6043}
6044
6045static size_t
6046mark_stack_size(mark_stack_t *stack)
6047{
6048 size_t size = stack->index;
6049 stack_chunk_t *chunk = stack->chunk ? stack->chunk->next : NULL;
6050
6051 while (chunk) {
6052 size += stack->limit;
6053 chunk = chunk->next;
6054 }
6055 return size;
6056}
6057
6058static void
6059add_stack_chunk_cache(mark_stack_t *stack, stack_chunk_t *chunk)
6060{
6061 chunk->next = stack->cache;
6062 stack->cache = chunk;
6063 stack->cache_size++;
6064}
6065
6066static void
6067shrink_stack_chunk_cache(mark_stack_t *stack)
6068{
6069 stack_chunk_t *chunk;
6070
6071 if (stack->unused_cache_size > (stack->cache_size/2)) {
6072 chunk = stack->cache;
6073 stack->cache = stack->cache->next;
6074 stack->cache_size--;
6075 free(chunk);
6076 }
6077 stack->unused_cache_size = stack->cache_size;
6078}
6079
6080static void
6081push_mark_stack_chunk(mark_stack_t *stack)
6082{
6083 stack_chunk_t *next;
6084
6085 GC_ASSERT(stack->index == stack->limit);
6086
6087 if (stack->cache_size > 0) {
6088 next = stack->cache;
6089 stack->cache = stack->cache->next;
6090 stack->cache_size--;
6091 if (stack->unused_cache_size > stack->cache_size)
6092 stack->unused_cache_size = stack->cache_size;
6093 }
6094 else {
6095 next = stack_chunk_alloc();
6096 }
6097 next->next = stack->chunk;
6098 stack->chunk = next;
6099 stack->index = 0;
6100}
6101
6102static void
6103pop_mark_stack_chunk(mark_stack_t *stack)
6104{
6105 stack_chunk_t *prev;
6106
6107 prev = stack->chunk->next;
6108 GC_ASSERT(stack->index == 0);
6109 add_stack_chunk_cache(stack, stack->chunk);
6110 stack->chunk = prev;
6111 stack->index = stack->limit;
6112}
6113
6114static void
6115mark_stack_chunk_list_free(stack_chunk_t *chunk)
6116{
6117 stack_chunk_t *next = NULL;
6118
6119 while (chunk != NULL) {
6120 next = chunk->next;
6121 free(chunk);
6122 chunk = next;
6123 }
6124}
6125
6126static void
6127free_stack_chunks(mark_stack_t *stack)
6128{
6129 mark_stack_chunk_list_free(stack->chunk);
6130}
6131
6132static void
6133mark_stack_free_cache(mark_stack_t *stack)
6134{
6135 mark_stack_chunk_list_free(stack->cache);
6136 stack->cache_size = 0;
6137 stack->unused_cache_size = 0;
6138}
6139
6140static void
6141push_mark_stack(mark_stack_t *stack, VALUE obj)
6142{
6143 switch (BUILTIN_TYPE(obj)) {
6144 case T_OBJECT:
6145 case T_CLASS:
6146 case T_MODULE:
6147 case T_FLOAT:
6148 case T_STRING:
6149 case T_REGEXP:
6150 case T_ARRAY:
6151 case T_HASH:
6152 case T_STRUCT:
6153 case T_BIGNUM:
6154 case T_FILE:
6155 case T_DATA:
6156 case T_MATCH:
6157 case T_COMPLEX:
6158 case T_RATIONAL:
6159 case T_TRUE:
6160 case T_FALSE:
6161 case T_SYMBOL:
6162 case T_IMEMO:
6163 case T_ICLASS:
6164 if (stack->index == stack->limit) {
6165 push_mark_stack_chunk(stack);
6166 }
6167 stack->chunk->data[stack->index++] = obj;
6168 return;
6169
6170 case T_NONE:
6171 case T_NIL:
6172 case T_FIXNUM:
6173 case T_MOVED:
6174 case T_ZOMBIE:
6175 case T_UNDEF:
6176 case T_MASK:
6177 rb_bug("push_mark_stack() called for broken object");
6178 break;
6179
6180 case T_NODE:
6181 rb_bug("push_mark_stack: unexpected T_NODE object");
6182 break;
6183 }
6184
6185 rb_bug("rb_gc_mark(): unknown data type 0x%x(%p) %s",
6186 BUILTIN_TYPE(obj), (void *)obj,
6187 is_pointer_to_heap((rb_objspace_t *)rb_gc_get_objspace(), (void *)obj) ? "corrupted object" : "non object");
6188}
6189
6190static int
6191pop_mark_stack(mark_stack_t *stack, VALUE *data)
6192{
6193 if (is_mark_stack_empty(stack)) {
6194 return FALSE;
6195 }
6196 if (stack->index == 1) {
6197 *data = stack->chunk->data[--stack->index];
6198 pop_mark_stack_chunk(stack);
6199 }
6200 else {
6201 *data = stack->chunk->data[--stack->index];
6202 }
6203 return TRUE;
6204}
6205
6206static void
6207init_mark_stack(mark_stack_t *stack)
6208{
6209 int i;
6210
6211 MEMZERO(stack, mark_stack_t, 1);
6212 stack->index = stack->limit = STACK_CHUNK_SIZE;
6213
6214 for (i=0; i < 4; i++) {
6215 add_stack_chunk_cache(stack, stack_chunk_alloc());
6216 }
6217 stack->unused_cache_size = stack->cache_size;
6218}
6219
6220/* Marking */
6221
6222ALWAYS_INLINE(static int gc_mark_set(rb_objspace_t *objspace, VALUE obj));
6223ALWAYS_INLINE(static void gc_mark_check_t_none(rb_objspace_t *objspace, VALUE obj));
6224ALWAYS_INLINE(static void rgengc_check_relation(rb_objspace_t *objspace, VALUE obj));
6225ALWAYS_INLINE(static void gc_aging(rb_objspace_t *objspace, VALUE obj));
6226ALWAYS_INLINE(static void gc_grey(rb_objspace_t *objspace, VALUE obj));
6227static void
6228rgengc_check_relation(rb_objspace_t *objspace, VALUE obj)
6229{
6230 if (objspace->rgengc.parent_object_old_p) {
6231 if (RVALUE_WB_UNPROTECTED(objspace, obj) || !RVALUE_OLD_P(objspace, obj)) {
6232 rgengc_remember(objspace, objspace->rgengc.parent_object);
6233 /* It is in the rememberset now, so its remaining children have nothing left
6234 * to ask for: stop testing them. */
6235 objspace->rgengc.parent_object_old_p = false;
6236 }
6237 }
6238}
6239
6240static inline int
6241gc_mark_set(rb_objspace_t *objspace, VALUE obj)
6242{
6243 if (RVALUE_MARKED(objspace, obj)) return 0;
6244 MARK_IN_BITMAP(GET_HEAP_MARK_BITS(obj), obj);
6245 return 1;
6246}
6247
6248static void
6249gc_aging(rb_objspace_t *objspace, VALUE obj)
6250{
6251 /* Disable aging if Major GC's are disabled. This will prevent longish lived
6252 * objects filling up the heap at the expense of marking many more objects.
6253 *
6254 * We should always pre-warm our process when disabling majors, by running
6255 * GC manually several times so that most objects likely to become oldgen
6256 * are already oldgen.
6257 */
6258 if(!gc_config_full_mark_val)
6259 return;
6260
6261 struct heap_page *page = GET_HEAP_PAGE(obj);
6262
6263 GC_ASSERT(RVALUE_MARKING(objspace, obj) == FALSE);
6264 check_rvalue_consistency(objspace, obj);
6265
6266 if (!RVALUE_PAGE_WB_UNPROTECTED(page, obj)) {
6267 if (!RVALUE_OLD_P(objspace, obj)) {
6268 int t = BUILTIN_TYPE(obj);
6269 if (t == T_CLASS || t == T_MODULE || t == T_ICLASS) {
6270 gc_report(3, objspace, "gc_aging: YOUNG class: %s\n", rb_obj_info(obj));
6271 RVALUE_AGE_SET(obj, RVALUE_OLD_AGE);
6272 RVALUE_OLD_UNCOLLECTIBLE_SET(objspace, obj);
6273 }
6274 else {
6275 gc_report(3, objspace, "gc_aging: YOUNG: %s\n", rb_obj_info(obj));
6276 RVALUE_AGE_INC(objspace, obj);
6277 }
6278 }
6279 else if (is_full_marking(objspace)) {
6280 GC_ASSERT(RVALUE_PAGE_UNCOLLECTIBLE(page, obj) == FALSE);
6281 RVALUE_PAGE_OLD_UNCOLLECTIBLE_SET(objspace, page, obj);
6282 }
6283 }
6284 check_rvalue_consistency(objspace, obj);
6285
6286 objspace->marked_slots++;
6287}
6288
6289static void
6290gc_grey(rb_objspace_t *objspace, VALUE obj)
6291{
6292#if RGENGC_CHECK_MODE
6293 if (RVALUE_MARKED(objspace, obj) == FALSE) rb_bug("gc_grey: %s is not marked.", rb_obj_info(obj));
6294 if (RVALUE_MARKING(objspace, obj) == TRUE) rb_bug("gc_grey: %s is marking/remembered.", rb_obj_info(obj));
6295#endif
6296
6297 if (is_incremental_marking(objspace)) {
6298 MARK_IN_BITMAP(GET_HEAP_MARKING_BITS(obj), obj);
6299 }
6300
6302 rb_darray_append_without_gc(&objspace->weak_references, obj);
6303 }
6304
6305 push_mark_stack(&objspace->mark_stack, obj);
6306}
6307
6308static inline void
6309gc_mark_check_t_none(rb_objspace_t *objspace, VALUE obj)
6310{
6311 if (RB_UNLIKELY(BUILTIN_TYPE(obj) == T_NONE)) {
6312 enum {info_size = 256};
6313 char obj_info_buf[info_size];
6314 rb_raw_obj_info(obj_info_buf, info_size, obj);
6315
6316 char parent_obj_info_buf[info_size];
6317 rb_raw_obj_info(parent_obj_info_buf, info_size, objspace->rgengc.parent_object);
6318
6319 rb_bug("try to mark T_NONE object (obj: %s, parent: %s)", obj_info_buf, parent_obj_info_buf);
6320 }
6321}
6322
6323static void
6324gc_mark(rb_objspace_t *objspace, VALUE obj)
6325{
6326 GC_ASSERT(during_gc);
6327 GC_ASSERT(!objspace->flags.during_reference_updating);
6328
6329 /* Never step into another objspace: a foreign object is a live leaf whose liveness
6330 * belongs to its owner, so touching its bitmaps here would be unsound. A global GC
6331 * lifts this: everyone is stopped and the bits live on the object's own page. */
6332 if (gc_skip_foreign_object_p(objspace, obj)) {
6333 return;
6334 }
6335
6336 if (RB_UNLIKELY(objspace->flags.during_global_gc)) {
6337 /* Recompute the shref of every shareable -> unshareable edge, within and across
6338 * objspaces: the clear pass dropped all shref bits and the write barrier
6339 * maintains them from here on. */
6340 VALUE parent = objspace->rgengc.parent_object;
6341 if (!UNDEF_P(parent) && parent != Qfalse &&
6344 struct heap_page *page = GET_HEAP_PAGE(obj);
6345 _MARK_IN_BITMAP(page->shref_bits, page, obj);
6346 page->flags.has_shref_objects = TRUE;
6347 }
6348 }
6349
6350 rgengc_check_relation(objspace, obj);
6351 if (!gc_mark_set(objspace, obj)) return; /* already marked */
6352
6353 if (0) { // for debug GC marking miss
6354 RUBY_DEBUG_LOG("%p (%s) parent:%p (%s)",
6355 (void *)obj, obj_type_name(obj),
6356 (void *)objspace->rgengc.parent_object, obj_type_name(objspace->rgengc.parent_object));
6357 }
6358
6359 gc_mark_check_t_none(objspace, obj);
6360
6361 gc_aging(objspace, obj);
6362 gc_grey(objspace, obj);
6363}
6364
6365static inline void
6366gc_pin(rb_objspace_t *objspace, VALUE obj)
6367{
6368 GC_ASSERT(!SPECIAL_CONST_P(obj));
6369
6370 if (RB_UNLIKELY(objspace->flags.during_compacting)) {
6371 /* Never write a foreign page's pinned bit (a global GC may: everyone is stopped). */
6372 if (gc_skip_foreign_object_p(objspace, obj)) return;
6373
6374 if (RB_LIKELY(during_gc)) {
6375 if (!RVALUE_PINNED(objspace, obj)) {
6376 GC_ASSERT(GET_HEAP_PAGE(obj)->pinned_slots <= GET_HEAP_PAGE(obj)->total_slots);
6377 GET_HEAP_PAGE(obj)->pinned_slots++;
6378 MARK_IN_BITMAP(GET_HEAP_PINNED_BITS(obj), obj);
6379 }
6380 }
6381 }
6382}
6383
6384static inline void
6385gc_mark_and_pin(rb_objspace_t *objspace, VALUE obj)
6386{
6387 gc_pin(objspace, obj);
6388 gc_mark(objspace, obj);
6389}
6390
6391void
6392rb_gc_impl_mark_and_move(void *objspace_ptr, VALUE *ptr)
6393{
6394 rb_objspace_t *objspace = objspace_ptr;
6395
6396 if (RB_UNLIKELY(objspace->flags.during_reference_updating)) {
6397 GC_ASSERT(objspace->flags.during_compacting);
6398 GC_ASSERT(during_gc);
6399
6400 VALUE destination = rb_gc_impl_location(objspace, *ptr);
6401 if (destination != *ptr) {
6402 *ptr = destination;
6403 }
6404 }
6405 else {
6406 gc_mark(objspace, *ptr);
6407 }
6408}
6409
6410void
6411rb_gc_impl_mark(void *objspace_ptr, VALUE obj)
6412{
6413 rb_objspace_t *objspace = objspace_ptr;
6414
6415 gc_mark(objspace, obj);
6416}
6417
6418void
6419rb_gc_impl_mark_and_pin(void *objspace_ptr, VALUE obj)
6420{
6421 rb_objspace_t *objspace = objspace_ptr;
6422
6423 gc_mark_and_pin(objspace, obj);
6424}
6425
6426/* A word scanned conservatively by a global GC can point into any objspace, so ownership
6427 * is decided against the driver's snapshot of every objspace (the bits then land on the
6428 * owner's page through gc_mark and gc_pin). */
6429static bool
6430gc_global_pointer_to_heap_p(const void *ptr)
6431{
6432 const rb_global_objspace_t *g = global_objspace;
6433 uintptr_t p = (uintptr_t)ptr;
6434
6435 if (p < g->page_index.lomem || p > g->page_index.himem) return false;
6436 if (p % sizeof(VALUE) != 0) return false;
6437
6438 struct heap_page **res = bsearch(ptr, g->page_index.pages, g->page_index.n_pages,
6439 sizeof(struct heap_page *), ptr_in_page_body_p);
6440 if (res == NULL) return false;
6441
6442 struct heap_page *page = *res;
6443 if (heap_page_in_global_empty_pages_pool(page->objspace, page)) return false;
6444 if (p < page->start) return false;
6445 if (p >= page->start + (page->total_slots * page->slot_size)) return false;
6446 if ((p - page->start) % page->slot_size != 0) return false;
6447 return true;
6448}
6449
6450void
6451rb_gc_impl_mark_maybe(void *objspace_ptr, VALUE obj)
6452{
6453 rb_objspace_t *objspace = objspace_ptr;
6454
6455 (void)VALGRIND_MAKE_MEM_DEFINED(&obj, sizeof(obj));
6456
6457 if (RB_UNLIKELY(objspace->flags.during_global_gc)
6458 ? gc_global_pointer_to_heap_p((void *)obj)
6459 : is_pointer_to_heap(objspace, (void *)obj)) {
6460 asan_unpoisoning_object(obj) {
6461 /* Garbage can live on the stack, so do not mark or pin */
6462 switch (BUILTIN_TYPE(obj)) {
6463 case T_ZOMBIE:
6464 case T_NONE:
6465 break;
6466 default:
6467 gc_mark_and_pin(objspace, obj);
6468 break;
6469 }
6470 }
6471 }
6472}
6473
6474static int
6475pin_value(st_data_t key, st_data_t value, st_data_t data)
6476{
6477 rb_gc_impl_mark_and_pin((void *)data, (VALUE)value);
6478
6479 return ST_CONTINUE;
6480}
6481
6482static inline void
6483gc_mark_set_parent_raw(rb_objspace_t *objspace, VALUE obj, bool old_p)
6484{
6485 asan_unpoison_memory_region(&objspace->rgengc.parent_object, sizeof(objspace->rgengc.parent_object), false);
6486 asan_unpoison_memory_region(&objspace->rgengc.parent_object_old_p, sizeof(objspace->rgengc.parent_object_old_p), false);
6487 objspace->rgengc.parent_object = obj;
6488 objspace->rgengc.parent_object_old_p = old_p;
6489}
6490
6491static inline void
6492gc_mark_set_parent(rb_objspace_t *objspace, VALUE obj)
6493{
6494 gc_mark_set_parent_raw(objspace, obj, RVALUE_OLD_P(objspace, obj));
6495}
6496
6497static inline void
6498gc_mark_set_parent_invalid(rb_objspace_t *objspace)
6499{
6500 asan_poison_memory_region(&objspace->rgengc.parent_object, sizeof(objspace->rgengc.parent_object));
6501 asan_poison_memory_region(&objspace->rgengc.parent_object_old_p, sizeof(objspace->rgengc.parent_object_old_p));
6502}
6503
6504static void pinned_roots_mark(rb_objspace_t *objspace, rb_heap_t *heap);
6505
6506static void
6507mark_roots(rb_objspace_t *objspace, const char **categoryp)
6508{
6509 VALUE objspace_guard = (VALUE)objspace;
6510#define MARK_CHECKPOINT(category) do { \
6511 if (categoryp) *categoryp = category; \
6512} while (0)
6513
6514 /* Pinning shareable objects and shrefs runs at the end of marking (gc_marks_finish),
6515 * not here: after the full walk it only has to touch what ordinary marking missed,
6516 * which is both cheap and a useful retention metric. */
6517
6518 MARK_CHECKPOINT("objspace");
6519 gc_mark_set_parent_raw(objspace, Qundef, false);
6520
6521 if (objspace->flags.during_global_gc) {
6522 /* Pin the finalizer tables of every objspace, zombies included.
6523 * (finalizer_table is a macro over the local "objspace".) */
6524 rb_objspace_t *const driver = objspace;
6525 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
6526 rb_objspace_t *objspace = global_objspace->global_gc.objspaces[i];
6527 if (finalizer_table != NULL) {
6528 st_foreach(finalizer_table, pin_value, (st_data_t)driver);
6529 }
6530 }
6531 }
6532 else if (finalizer_table != NULL) {
6533 st_foreach(finalizer_table, pin_value, (st_data_t)objspace);
6534 }
6535
6536 if (stress_to_class) rb_gc_mark(stress_to_class);
6537
6538 rb_gc_save_machine_context();
6539 rb_gc_mark_roots(objspace, categoryp);
6540 /* Keep this frame, including its saved registers, until root marking has
6541 * scanned the machine stack. */
6542 RB_GC_GUARD(objspace_guard);
6543 gc_mark_set_parent_invalid(objspace);
6544}
6545
6546static void
6547gc_mark_children(rb_objspace_t *objspace, VALUE obj)
6548{
6549 gc_mark_set_parent(objspace, obj);
6550 rb_gc_mark_children(objspace, obj);
6551 gc_mark_set_parent_invalid(objspace);
6552}
6553
6558static inline int
6559gc_mark_stacked_objects(rb_objspace_t *objspace, int incremental, size_t count)
6560{
6561 mark_stack_t *mstack = &objspace->mark_stack;
6562 VALUE obj;
6563 size_t marked_slots_at_the_beginning = objspace->marked_slots;
6564 size_t popped_count = 0;
6565
6566 while (pop_mark_stack(mstack, &obj)) {
6567 if (obj == Qundef) continue; /* skip */
6568
6569 if (RGENGC_CHECK_MODE && !RVALUE_MARKED(objspace, obj)) {
6570 rb_bug("gc_mark_stacked_objects: %s is not marked.", rb_obj_info(obj));
6571 }
6572 gc_mark_children(objspace, obj);
6573
6574 popped_count++;
6575
6576 if (incremental) {
6577 if (RGENGC_CHECK_MODE && !RVALUE_MARKING(objspace, obj)) {
6578 rb_bug("gc_mark_stacked_objects: incremental, but marking bit is 0");
6579 }
6580 CLEAR_IN_BITMAP(GET_HEAP_MARKING_BITS(obj), obj);
6581
6582 if (popped_count + (objspace->marked_slots - marked_slots_at_the_beginning) > count) {
6583 break;
6584 }
6585 }
6586 else {
6587 /* just ignore marking bits */
6588 }
6589 }
6590
6591 RUBY_DTRACE_GC_HOOK(MARK_STACKED_OBJECTS, popped_count);
6592
6593 if (RGENGC_CHECK_MODE >= 3) gc_verify_internal_consistency(objspace);
6594
6595 if (is_mark_stack_empty(mstack)) {
6596 shrink_stack_chunk_cache(mstack);
6597 return TRUE;
6598 }
6599 else {
6600 return FALSE;
6601 }
6602}
6603
6604static int
6605gc_mark_stacked_objects_incremental(rb_objspace_t *objspace, size_t count)
6606{
6607 return gc_mark_stacked_objects(objspace, TRUE, count);
6608}
6609
6610static int
6611gc_mark_stacked_objects_all(rb_objspace_t *objspace)
6612{
6613 return gc_mark_stacked_objects(objspace, FALSE, 0);
6614}
6615
6616#if RGENGC_CHECK_MODE >= 4
6617
6618#define MAKE_ROOTSIG(obj) (((VALUE)(obj) << 1) | 0x01)
6619#define IS_ROOTSIG(obj) ((VALUE)(obj) & 0x01)
6620#define GET_ROOTSIG(obj) ((const char *)((VALUE)(obj) >> 1))
6621
6622struct reflist {
6623 VALUE *list;
6624 int pos;
6625 int size;
6626};
6627
6628static struct reflist *
6629reflist_create(VALUE obj)
6630{
6631 struct reflist *refs = xmalloc(sizeof(struct reflist));
6632 refs->size = 1;
6633 refs->list = ALLOC_N(VALUE, refs->size);
6634 refs->list[0] = obj;
6635 refs->pos = 1;
6636 return refs;
6637}
6638
6639static void
6640reflist_destruct(struct reflist *refs)
6641{
6642 xfree(refs->list);
6643 xfree(refs);
6644}
6645
6646static void
6647reflist_add(struct reflist *refs, VALUE obj)
6648{
6649 if (refs->pos == refs->size) {
6650 refs->size *= 2;
6651 SIZED_REALLOC_N(refs->list, VALUE, refs->size, refs->size/2);
6652 }
6653
6654 refs->list[refs->pos++] = obj;
6655}
6656
6657static void
6658reflist_dump(struct reflist *refs)
6659{
6660 int i;
6661 for (i=0; i<refs->pos; i++) {
6662 VALUE obj = refs->list[i];
6663 if (IS_ROOTSIG(obj)) { /* root */
6664 fprintf(stderr, "<root@%s>", GET_ROOTSIG(obj));
6665 }
6666 else {
6667 fprintf(stderr, "<%s>", rb_obj_info(obj));
6668 }
6669 if (i+1 < refs->pos) fprintf(stderr, ", ");
6670 }
6671}
6672
6673static int
6674reflist_referred_from_machine_context(struct reflist *refs)
6675{
6676 int i;
6677 for (i=0; i<refs->pos; i++) {
6678 VALUE obj = refs->list[i];
6679 if (IS_ROOTSIG(obj) && strcmp(GET_ROOTSIG(obj), "machine_context") == 0) return 1;
6680 }
6681 return 0;
6682}
6683
6684struct allrefs {
6686 /* a -> obj1
6687 * b -> obj1
6688 * c -> obj1
6689 * c -> obj2
6690 * d -> obj3
6691 * #=> {obj1 => [a, b, c], obj2 => [c, d]}
6692 */
6693 struct st_table *references;
6694 const char *category;
6695 VALUE root_obj;
6697};
6698
6699static int
6700allrefs_add(struct allrefs *data, VALUE obj)
6701{
6702 struct reflist *refs;
6703 st_data_t r;
6704
6705 if (st_lookup(data->references, obj, &r)) {
6706 refs = (struct reflist *)r;
6707 reflist_add(refs, data->root_obj);
6708 return 0;
6709 }
6710 else {
6711 refs = reflist_create(data->root_obj);
6712 st_insert(data->references, obj, (st_data_t)refs);
6713 return 1;
6714 }
6715}
6716
6717static void
6718allrefs_i(VALUE obj, void *ptr)
6719{
6720 struct allrefs *data = (struct allrefs *)ptr;
6721
6722 if (allrefs_add(data, obj)) {
6723 push_mark_stack(&data->mark_stack, obj);
6724 }
6725}
6726
6727static void
6728allrefs_roots_i(VALUE obj, void *ptr)
6729{
6730 struct allrefs *data = (struct allrefs *)ptr;
6731 if (strlen(data->category) == 0) rb_bug("!!!");
6732 data->root_obj = MAKE_ROOTSIG(data->category);
6733
6734 if (allrefs_add(data, obj)) {
6735 push_mark_stack(&data->mark_stack, obj);
6736 }
6737}
6738#define PUSH_MARK_FUNC_DATA(v) do { \
6739 struct gc_mark_func_data_struct *prev_mark_func_data = GET_VM()->gc.mark_func_data; \
6740 GET_VM()->gc.mark_func_data = (v);
6741
6742#define POP_MARK_FUNC_DATA() GET_VM()->gc.mark_func_data = prev_mark_func_data;} while (0)
6743
6744static st_table *
6745objspace_allrefs(rb_objspace_t *objspace)
6746{
6747 struct allrefs data;
6748 struct gc_mark_func_data_struct mfd;
6749 VALUE obj;
6750 int prev_dont_gc = dont_gc_val();
6751 dont_gc_on();
6752
6753 data.objspace = objspace;
6754 data.references = st_init_numtable();
6755 init_mark_stack(&data.mark_stack);
6756
6757 mfd.mark_func = allrefs_roots_i;
6758 mfd.data = &data;
6759
6760 /* traverse root objects */
6761 PUSH_MARK_FUNC_DATA(&mfd);
6762 GET_VM()->gc.mark_func_data = &mfd;
6763 mark_roots(objspace, &data.category);
6764 POP_MARK_FUNC_DATA();
6765
6766 /* traverse rest objects reachable from root objects */
6767 while (pop_mark_stack(&data.mark_stack, &obj)) {
6768 rb_objspace_reachable_objects_from(data.root_obj = obj, allrefs_i, &data);
6769 }
6770 free_stack_chunks(&data.mark_stack);
6771
6772 dont_gc_set(prev_dont_gc);
6773 return data.references;
6774}
6775
6776static int
6777objspace_allrefs_destruct_i(st_data_t key, st_data_t value, st_data_t ptr)
6778{
6779 struct reflist *refs = (struct reflist *)value;
6780 reflist_destruct(refs);
6781 return ST_CONTINUE;
6782}
6783
6784static void
6785objspace_allrefs_destruct(struct st_table *refs)
6786{
6787 st_foreach(refs, objspace_allrefs_destruct_i, 0);
6788 st_free_table(refs);
6789}
6790
6791#if RGENGC_CHECK_MODE >= 5
6792static int
6793allrefs_dump_i(st_data_t k, st_data_t v, st_data_t ptr)
6794{
6795 VALUE obj = (VALUE)k;
6796 struct reflist *refs = (struct reflist *)v;
6797 fprintf(stderr, "[allrefs_dump_i] %s <- ", rb_obj_info(obj));
6798 reflist_dump(refs);
6799 fprintf(stderr, "\n");
6800 return ST_CONTINUE;
6801}
6802
6803static void
6804allrefs_dump(rb_objspace_t *objspace)
6805{
6806 VALUE size = objspace->rgengc.allrefs_table->num_entries;
6807 fprintf(stderr, "[all refs] (size: %"PRIuVALUE")\n", size);
6808 st_foreach(objspace->rgengc.allrefs_table, allrefs_dump_i, 0);
6809}
6810#endif
6811
6812static int
6813gc_check_after_marks_i(st_data_t k, st_data_t v, st_data_t ptr)
6814{
6815 VALUE obj = k;
6816 struct reflist *refs = (struct reflist *)v;
6818
6819 /* object should be marked or oldgen */
6820 if (!RVALUE_MARKED(objspace, obj)) {
6821 fprintf(stderr, "gc_check_after_marks_i: %s is not marked and not oldgen.\n", rb_obj_info(obj));
6822 fprintf(stderr, "gc_check_after_marks_i: %p is referred from ", (void *)obj);
6823 reflist_dump(refs);
6824
6825 if (reflist_referred_from_machine_context(refs)) {
6826 fprintf(stderr, " (marked from machine stack).\n");
6827 /* marked from machine context can be false positive */
6828 }
6829 else {
6830 objspace->rgengc.error_count++;
6831 fprintf(stderr, "\n");
6832 }
6833 }
6834 return ST_CONTINUE;
6835}
6836
6837static void
6838gc_marks_check(rb_objspace_t *objspace, st_foreach_callback_func *checker_func, const char *checker_name)
6839{
6840 MALLOC_COUNTERS_LOCK(objspace);
6841 struct gc_malloc_bytes saved_malloc = {
6842 .malloc = gc_counter_load_relaxed(&objspace->malloc_counters.counters.malloc),
6843 .free = gc_counter_load_relaxed(&objspace->malloc_counters.counters.free),
6844 .malloc_at_last_gc = gc_counter_load_relaxed(&objspace->malloc_counters.counters.malloc_at_last_gc),
6845 .free_at_last_gc = gc_counter_load_relaxed(&objspace->malloc_counters.counters.free_at_last_gc),
6846 };
6847#if RGENGC_ESTIMATE_OLDMALLOC
6848 struct gc_malloc_bytes saved_oldmalloc = {
6849 .malloc = gc_counter_load_relaxed(&objspace->malloc_counters.oldcounters.malloc),
6850 .free = gc_counter_load_relaxed(&objspace->malloc_counters.oldcounters.free),
6851 .malloc_at_last_gc = gc_counter_load_relaxed(&objspace->malloc_counters.oldcounters.malloc_at_last_gc),
6852 .free_at_last_gc = gc_counter_load_relaxed(&objspace->malloc_counters.oldcounters.free_at_last_gc),
6853 };
6854#endif
6855 MALLOC_COUNTERS_UNLOCK(objspace);
6856 VALUE already_disabled = rb_objspace_gc_disable(objspace);
6857
6858 objspace->rgengc.allrefs_table = objspace_allrefs(objspace);
6859
6860 if (checker_func) {
6861 st_foreach(objspace->rgengc.allrefs_table, checker_func, (st_data_t)objspace);
6862 }
6863
6864 if (objspace->rgengc.error_count > 0) {
6865#if RGENGC_CHECK_MODE >= 5
6866 allrefs_dump(objspace);
6867#endif
6868 if (checker_name) rb_bug("%s: GC has problem.", checker_name);
6869 }
6870
6871 objspace_allrefs_destruct(objspace->rgengc.allrefs_table);
6872 objspace->rgengc.allrefs_table = 0;
6873
6874 if (already_disabled == Qfalse) rb_objspace_gc_enable(objspace);
6875 MALLOC_COUNTERS_LOCK(objspace);
6876 gc_counter_store_release(&objspace->malloc_counters.counters.malloc, saved_malloc.malloc);
6877 gc_counter_store_release(&objspace->malloc_counters.counters.free, saved_malloc.free);
6878 gc_counter_store_release(&objspace->malloc_counters.counters.malloc_at_last_gc, saved_malloc.malloc_at_last_gc);
6879 gc_counter_store_release(&objspace->malloc_counters.counters.free_at_last_gc, saved_malloc.free_at_last_gc);
6880#if RGENGC_ESTIMATE_OLDMALLOC
6881 gc_counter_store_release(&objspace->malloc_counters.oldcounters.malloc, saved_oldmalloc.malloc);
6882 gc_counter_store_release(&objspace->malloc_counters.oldcounters.free, saved_oldmalloc.free);
6883 gc_counter_store_release(&objspace->malloc_counters.oldcounters.malloc_at_last_gc, saved_oldmalloc.malloc_at_last_gc);
6884 gc_counter_store_release(&objspace->malloc_counters.oldcounters.free_at_last_gc, saved_oldmalloc.free_at_last_gc);
6885#endif
6886 MALLOC_COUNTERS_UNLOCK(objspace);
6887}
6888#endif /* RGENGC_CHECK_MODE >= 4 */
6889
6892 /* True only while the world is stopped: a GC.verify holding the VM lock and barrier,
6893 * or a global GC. Cross-objspace checks (walking every objspace's pages) are sound
6894 * only then. */
6895 bool world_stopped;
6896 int err_count;
6897 size_t live_object_count;
6898 size_t zombie_object_count;
6899
6900 VALUE parent;
6901 bool parent_shareable;
6902 size_t old_object_count;
6903 size_t remembered_shady_count;
6904};
6905
6906
6907static void
6908check_generation_i(const VALUE child, void *ptr)
6909{
6911 const VALUE parent = data->parent;
6912
6913 if (RGENGC_CHECK_MODE) GC_ASSERT(RVALUE_OLD_P(data->objspace, parent));
6914
6915 /* A cross-objspace edge is kept alive by the shareable/shref mechanism and is not
6916 * tracked in this objspace's remembered set. */
6917 if (GET_HEAP_OBJSPACE(child) != data->objspace) return;
6918
6919 /* Once the process goes multi-Ractor, the shareable world is managed by pinning and
6920 * shrefs rather than by the remembered set: the pinned walk at the end of a mark
6921 * re-marks every shareable object (and its shref'd children) each local cycle, and a
6922 * global GC rebuilds the generation state. So the generational old->young invariant
6923 * does not hold when either endpoint is shareable: an old constcache, cc_table or
6924 * interned string pointing at a core class that is young after a global GC is the
6925 * typical false positive. That state outlives the return to a single Ractor until
6926 * the next major (an old shareable singleton class pointing at a young
6927 * attached_object, say), so the test uses rb_gc_ever_multi_ractor_p(), which stays
6928 * true forever once multiple Ractors existed. A program that never goes multi keeps
6929 * the strict check, and ASAN catches what is left. */
6930 if (rb_gc_ever_multi_ractor_p() &&
6931 (MARKED_IN_BITMAP(GET_HEAP_SHAREABLE_BITS(parent), parent) ||
6932 MARKED_IN_BITMAP(GET_HEAP_SHAREABLE_BITS(child), child))) {
6933 return;
6934 }
6935
6936 if (!RVALUE_OLD_P(data->objspace, child)) {
6937 /* A young shareable child is pinned and kept alive by the local GC (only a
6938 * global GC collects it), so it survives even when the old parent does not
6939 * remember it. It is outside the generational remembered set, so exclude it
6940 * from the old->young check. */
6941 if (!RVALUE_REMEMBERED(data->objspace, parent) &&
6942 !RVALUE_REMEMBERED(data->objspace, child) &&
6943 !RVALUE_UNCOLLECTIBLE(data->objspace, child) &&
6945 fprintf(stderr, "verify_internal_consistency_reachable_i: WB miss (O->Y) %s -> %s\n", rb_obj_info(parent), rb_obj_info(child));
6946 data->err_count++;
6947 }
6948 }
6949}
6950
6951static void
6952check_color_i(const VALUE child, void *ptr)
6953{
6955 const VALUE parent = data->parent;
6956
6957 /* This cycle never marks a foreign child (gc_skip_foreign_object_p) and the write
6958 * barrier is a no-op across objspaces, so its colour says nothing here. */
6959 if (GET_HEAP_OBJSPACE(child) != data->objspace) return;
6960
6961 if (!RVALUE_WB_UNPROTECTED(data->objspace, parent) && RVALUE_WHITE_P(data->objspace, child)) {
6962 fprintf(stderr, "verify_internal_consistency_reachable_i: WB miss (B->W) - %s -> %s\n",
6963 rb_obj_info(parent), rb_obj_info(child));
6964 data->err_count++;
6965 }
6966}
6967
6968static void
6969check_children_i(const VALUE child, void *ptr)
6970{
6972
6973 /* Fast path: a child in this objspace (99.99% of all edges). */
6974 if (RB_LIKELY(is_pointer_to_heap(data->objspace, (void *)child))) {
6975 if (check_rvalue_consistency_force(data->objspace, child, FALSE) != 0) {
6976 fprintf(stderr, "check_children_i: %s has error (referenced from %s)\n",
6977 rb_obj_info(child), rb_obj_info(data->parent));
6978 data->err_count++;
6979 }
6980 return;
6981 }
6982
6983 /* The remaining cross-objspace check (verify_pointer_in_any_heap_p) walks every
6984 * objspace's pages, sound only with the world stopped: mid-local-GC other Ractors
6985 * change page structures concurrently. The next world-stopped verify re-checks. */
6986 if (!data->world_stopped) return;
6987
6988 /* A non-heap child reaches this callback only when a stale field was followed by a
6989 * plain rb_gc_mark (the dmark of a live but unreachable wrapper, say). Report it and
6990 * keep going rather than aborting. */
6991 if (!verify_pointer_in_any_heap_p((void *)child)) {
6992 /* The graph is in flux mid-merge, so a transient non-heap edge is expected; it
6993 * is re-checked after the merge. */
6994 if (global_objspace->during_absorb) return;
6995 fprintf(stderr, "VERIFY-NOTE: non-heap child %p (from %s)\n",
6996 (void *)child, rb_obj_info(data->parent));
6997 return;
6998 }
6999
7000 if (GET_HEAP_OBJSPACE(child) != data->objspace) {
7001 /* A legal cross-objspace edge either starts at a shareable object or is recorded
7002 * in the child's shref bit (an in-flight send or move payload kept alive across
7003 * its owner's local GC; root_scope_check_i honours the same record). An
7004 * unshareable parent holding an unrecorded foreign unshareable child would be
7005 * invisible to both local GCs. The exception is a box's top_self, which every
7006 * thread's th->top_self points at and which is VM-permanent. Skipped during a
7007 * global GC: it clears every shref bit, so the shref exemption would not fire,
7008 * and its unified exact stop-the-world mark makes the invariant itself moot. */
7009 if (!data->parent_shareable &&
7010 child != rb_gc_vm_top_self() &&
7011 !MARKED_IN_BITMAP(GET_HEAP_SHAREABLE_BITS(child), child) &&
7012 !MARKED_IN_BITMAP(GET_HEAP_SHREF_BITS(child), child) &&
7013 !rb_gc_impl_during_global_gc_p(data->objspace) &&
7014 !global_objspace->during_absorb) {
7015 fprintf(stderr, "check_children_i: containment violation: "
7016 "unshareable %s (objspace %p) -> foreign unshareable %s (objspace %p)\n",
7017 rb_obj_info(data->parent), (void *)data->objspace,
7018 rb_obj_info(child), (void *)GET_HEAP_OBJSPACE(child));
7019 data->err_count++;
7020 }
7021
7022 /* The remaining per-objspace sanity rules belong to the owner. */
7023 return;
7024 }
7025}
7026
7027/* Whether a heap slot currently holds a live object. Returns false for empty
7028 * (T_NONE), moved (T_MOVED), and zombie (T_ZOMBIE) slots, and for garbage
7029 * objects about to be swept. */
7030static bool
7031gc_slot_live_object_p(rb_objspace_t *objspace, VALUE obj)
7032{
7033 switch (BUILTIN_TYPE(obj)) {
7034 case T_NONE:
7035 case T_MOVED:
7036 case T_ZOMBIE:
7037 return false;
7038 default:
7039 return !rb_gc_impl_garbage_object_p(objspace, obj);
7040 }
7041}
7042
7043/* Verifier only: does ptr point at a live slot in any objspace? The caller holds the VM
7044 * lock and the barrier, so page_index is stable. */
7045static bool
7046verify_pointer_in_any_heap_p(const void *ptr)
7047{
7048 return gc_global_pointer_to_heap_p(ptr);
7049}
7050
7051/* An exact root of the calling Ractor may only point at a shareable object, its own
7052 * objspace, or an in-flight payload with a recorded shref. Exempt: the conservative
7053 * machine scan (stale slots) and the VM-global containers that are cross-rooted by
7054 * design (every objspace scans them; the marker skips foreign entries). */
7055static void
7056root_scope_check_i(const char *category, VALUE obj, void *ptr)
7057{
7058 struct verify_internal_consistency_struct *data = ptr;
7059
7060 if (RB_SPECIAL_CONST_P(obj)) return;
7061 /* This check walks every objspace (verify_pointer_in_any_heap_p), so it is sound
7062 * only with the world stopped; a mid-local-GC verify races with other Ractors'
7063 * lock-free allocation. */
7064 if (!data->world_stopped) return;
7065 /* Mid-merge the VM-global root tables still point at the unmerged source (transient
7066 * non-heap or foreign roots); re-checked after the merge. */
7067 if (global_objspace->during_absorb) return;
7068 if (strcmp(category, "machine_context") == 0 ||
7069 strcmp(category, "vm_registered_objects") == 0 ||
7070 strcmp(category, "end_proc") == 0 ||
7071 strcmp(category, "trap_list") == 0) {
7072 return;
7073 }
7074
7075 if (!verify_pointer_in_any_heap_p((void *)obj)) {
7076 fprintf(stderr, "root_scope_check_i: root category \"%s\" names a non-heap pointer %p\n",
7077 category, (void *)obj);
7078 data->err_count++;
7079 return;
7080 }
7081
7082 if (GET_HEAP_OBJSPACE(obj) == data->objspace) return;
7083 if (MARKED_IN_BITMAP(GET_HEAP_SHAREABLE_BITS(obj), obj)) return;
7084 if (MARKED_IN_BITMAP(GET_HEAP_SHREF_BITS(obj), obj)) return;
7085 if (obj == rb_gc_vm_top_self()) return; /* VM-permanent (see check_children_i) */
7086
7087 fprintf(stderr, "root_scope_check_i: root category \"%s\" names a foreign "
7088 "unshareable without a shref record: %s (owner %p, self %p)\n",
7089 category, rb_obj_info(obj),
7090 (void *)GET_HEAP_OBJSPACE(obj), (void *)data->objspace);
7091 data->err_count++;
7092}
7093
7094static int
7095verify_internal_consistency_i(void *page_start, void *page_end, size_t stride,
7097{
7098 VALUE obj;
7099 rb_objspace_t *objspace = data->objspace;
7100
7101 for (obj = (VALUE)page_start; obj != (VALUE)page_end; obj += stride) {
7102 asan_unpoisoning_object(obj) {
7103 bool sh_bit = MARKED_IN_BITMAP(GET_HEAP_SHAREABLE_BITS(obj), obj) != 0;
7104 bool sr_bit = MARKED_IN_BITMAP(GET_HEAP_SHREF_BITS(obj), obj) != 0;
7105
7106 if (gc_slot_live_object_p(objspace, obj)) {
7107 /* count objects */
7108 data->live_object_count++;
7109 data->parent = obj;
7110 data->parent_shareable = sh_bit;
7111
7112 /* Bitmap invariants: a page's shareable bit matches FL_SHAREABLE
7113 * exactly, and a shref record only ever points at an unshareable
7114 * object. */
7115 if (sh_bit != !!RB_FL_TEST_RAW(obj, RUBY_FL_SHAREABLE)) {
7116 fprintf(stderr, "verify_internal_consistency_i: shareable bit %d "
7117 "disagrees with FL_SHAREABLE on %s\n", (int)sh_bit, rb_obj_info(obj));
7118 data->err_count++;
7119 }
7120 if (sr_bit && sh_bit) {
7121 fprintf(stderr, "verify_internal_consistency_i: shref bit on a shareable: %s\n",
7122 rb_obj_info(obj));
7123 data->err_count++;
7124 }
7125
7126 /* Normally, we don't expect T_MOVED objects to be in the heap.
7127 * But they can stay alive on the stack, */
7128 if (!gc_object_moved_p(objspace, obj)) {
7129 /* moved slots don't have children */
7130 rb_objspace_reachable_objects_from(obj, check_children_i, (void *)data);
7131 }
7132
7133 /* check health of children */
7134 if (RVALUE_OLD_P(objspace, obj)) data->old_object_count++;
7135 if (RVALUE_WB_UNPROTECTED(objspace, obj) && RVALUE_UNCOLLECTIBLE(objspace, obj)) data->remembered_shady_count++;
7136
7137 if (!is_marking(objspace) && RVALUE_OLD_P(objspace, obj)) {
7138 /* reachable objects from an oldgen object should be old or (young with remember) */
7139 data->parent = obj;
7140 rb_objspace_reachable_objects_from(obj, check_generation_i, (void *)data);
7141 }
7142
7143 if (!is_marking(objspace) && rb_gc_obj_shareable_p(obj)) {
7144 rb_gc_verify_shareable(obj);
7145 }
7146
7147 if (is_incremental_marking(objspace)) {
7148 if (RVALUE_BLACK_P(objspace, obj)) {
7149 /* reachable objects from black objects should be black or grey objects */
7150 data->parent = obj;
7151 rb_objspace_reachable_objects_from(obj, check_color_i, (void *)data);
7152 }
7153 }
7154 }
7155 else {
7156 /* A freed slot must not carry its old pin bit into the next object born
7157 * there (a dead object not swept yet legitimately keeps it until the
7158 * sweep arrives). */
7159 if (BUILTIN_TYPE(obj) == T_NONE && (sh_bit || sr_bit)) {
7160 fprintf(stderr, "verify_internal_consistency_i: T_NONE slot carries "
7161 "shareable=%d shref=%d bits\n", (int)sh_bit, (int)sr_bit);
7162 data->err_count++;
7163 }
7164
7165 if (BUILTIN_TYPE(obj) == T_ZOMBIE) {
7166 data->zombie_object_count++;
7167
7168 if ((RBASIC(obj)->flags & ~ZOMBIE_OBJ_KEPT_FLAGS) != T_ZOMBIE) {
7169 fprintf(stderr, "verify_internal_consistency_i: T_ZOMBIE has extra flags set: %s\n",
7170 rb_obj_info(obj));
7171 data->err_count++;
7172 }
7173
7174 if (!!FL_TEST(obj, FL_FINALIZE) != !!st_is_member(finalizer_table, obj)) {
7175 fprintf(stderr, "verify_internal_consistency_i: FL_FINALIZE %s but %s finalizer_table: %s\n",
7176 FL_TEST(obj, FL_FINALIZE) ? "set" : "not set", st_is_member(finalizer_table, obj) ? "in" : "not in",
7177 rb_obj_info(obj));
7178 data->err_count++;
7179 }
7180 }
7181 }
7182 }
7183 }
7184
7185 return 0;
7186}
7187
7188static int
7189gc_verify_heap_page(rb_objspace_t *objspace, struct heap_page *page, VALUE obj)
7190{
7191 unsigned int has_remembered_shady = FALSE;
7192 unsigned int has_remembered_old = FALSE;
7193 int remembered_old_objects = 0;
7194 int free_objects = 0;
7195 int zombie_objects = 0;
7196
7197 short slot_size = page->slot_size;
7198 uintptr_t start = (uintptr_t)page->start;
7199 uintptr_t end = start + page->total_slots * slot_size;
7200
7201 for (uintptr_t ptr = start; ptr < end; ptr += slot_size) {
7202 VALUE val = (VALUE)ptr;
7203 asan_unpoisoning_object(val) {
7204 enum ruby_value_type type = BUILTIN_TYPE(val);
7205
7206 if (type == T_NONE) free_objects++;
7207 if (type == T_ZOMBIE) zombie_objects++;
7208 if (RVALUE_PAGE_UNCOLLECTIBLE(page, val) && RVALUE_PAGE_WB_UNPROTECTED(page, val)) {
7209 has_remembered_shady = TRUE;
7210 }
7211 if (RVALUE_PAGE_MARKING(page, val)) {
7212 has_remembered_old = TRUE;
7213 remembered_old_objects++;
7214 }
7215 }
7216 }
7217
7218 if (!is_incremental_marking(objspace) &&
7219 page->flags.has_remembered_objects == FALSE && has_remembered_old == TRUE) {
7220
7221 for (uintptr_t ptr = start; ptr < end; ptr += slot_size) {
7222 VALUE val = (VALUE)ptr;
7223 if (RVALUE_PAGE_MARKING(page, val)) {
7224 fprintf(stderr, "marking -> %s\n", rb_obj_info(val));
7225 }
7226 }
7227 rb_bug("page %p's has_remembered_objects should be false, but there are remembered old objects (%d). %s",
7228 (void *)page, remembered_old_objects, obj ? rb_obj_info(obj) : "");
7229 }
7230
7231 if (page->flags.has_uncollectible_wb_unprotected_objects == FALSE && has_remembered_shady == TRUE) {
7232 rb_bug("page %p's has_remembered_shady should be false, but there are remembered shady objects. %s",
7233 (void *)page, obj ? rb_obj_info(obj) : "");
7234 }
7235
7236 if (0) {
7237 /* free_slots may not equal to free_objects */
7238 if (page->free_slots != free_objects) {
7239 rb_bug("page %p's free_slots should be %d, but %d", (void *)page, page->free_slots, free_objects);
7240 }
7241 }
7242 if (page->final_slots != zombie_objects) {
7243 rb_bug("page %p's final_slots should be %d, but %d", (void *)page, page->final_slots, zombie_objects);
7244 }
7245
7246 return remembered_old_objects;
7247}
7248
7249static int
7250gc_verify_heap_pages_(rb_objspace_t *objspace, struct ccan_list_head *head)
7251{
7252 int remembered_old_objects = 0;
7253 struct heap_page *page = 0;
7254
7255 ccan_list_for_each(head, page, page_node) {
7256 asan_unlock_freelist(page);
7257 struct free_region *region = page->free_region;
7258 while (region) {
7259 VALUE vp = (VALUE)region;
7260 rb_asan_unpoison_object(vp, false);
7261 if (BUILTIN_TYPE(vp) != T_NONE) {
7262 fprintf(stderr, "free region head expected to be T_NONE but was: %s\n", rb_obj_info(vp));
7263 }
7264 struct free_region *next = region->next;
7265 rb_asan_poison_object(vp);
7266 region = next;
7267 }
7268 asan_lock_freelist(page);
7269
7270 if (page->flags.has_remembered_objects == FALSE) {
7271 remembered_old_objects += gc_verify_heap_page(objspace, page, Qfalse);
7272 }
7273 }
7274
7275 return remembered_old_objects;
7276}
7277
7278static int
7279gc_verify_heap_pages(rb_objspace_t *objspace)
7280{
7281 int remembered_old_objects = 0;
7282 for (int i = 0; i < HEAP_COUNT; i++) {
7283 remembered_old_objects += gc_verify_heap_pages_(objspace, &((&heaps[i])->pages));
7284 }
7285 return remembered_old_objects;
7286}
7287
7288static void
7289verify_registered_addr(VALUE *slot, VALUE initial_value, void *owner_objspace, void *d)
7290{
7291 struct verify_internal_consistency_struct *data = d;
7292 VALUE v = *slot;
7293
7294 /* Conservative registration permits uninitialized data and pre-registration
7295 * values; only a store made after registration is a violation. */
7296 if (v == initial_value) return;
7297 if (SPECIAL_CONST_P(v)) return;
7298 if (!verify_pointer_in_any_heap_p((void *)v)) return;
7299
7300 bool live = false;
7301 asan_unpoisoning_object(v) {
7302 live = BUILTIN_TYPE(v) != T_NONE && BUILTIN_TYPE(v) != T_ZOMBIE;
7303 }
7304 if (!live) return;
7305
7306 rb_objspace_t *value_objspace = GET_HEAP_OBJSPACE(v);
7307 if (value_objspace == (rb_objspace_t *)owner_objspace) return;
7308 /* Join and orphan handling move a registration to the inheritor before the
7309 * source objspace merge; a global GC scans every registry while the zombie
7310 * exists, so this is a safe transient exemption. */
7311 if (rb_gc_vm_zombie_objspace_p(value_objspace)) return;
7312 if (value_objspace->flags.during_postmortem) return;
7313 if (MARKED_IN_BITMAP(GET_HEAP_SHAREABLE_BITS(v), v)) return;
7314 if (MARKED_IN_BITMAP(GET_HEAP_SHREF_BITS(v), v)) return;
7315 /* When multiple Ractors register one address, ownership by any registrant is
7316 * enough to root the value. */
7317 if (rb_gc_registered_addr_owned_by_registrant_p(slot, value_objspace)) return;
7318
7319 fprintf(stderr, "registered address %p changed since registration to an unshareable object owned by another Ractor: %s\n",
7320 (void *)slot, rb_obj_info(v));
7321 data->err_count++;
7322}
7323
7324static void
7325gc_verify_internal_consistency_(rb_objspace_t *objspace, bool world_stopped)
7326{
7327 struct verify_internal_consistency_struct data = {0};
7328
7329 data.objspace = objspace;
7330 data.world_stopped = world_stopped;
7331 gc_report(5, objspace, "gc_verify_internal_consistency: start\n");
7332
7333 /* check relations */
7334 for (size_t i = 0; i < rb_darray_size(objspace->heap_pages.sorted); i++) {
7335 struct heap_page *page = rb_darray_get(objspace->heap_pages.sorted, i);
7336 short slot_size = page->slot_size;
7337
7338 uintptr_t start = (uintptr_t)page->start;
7339 uintptr_t end = start + page->total_slots * slot_size;
7340
7341 verify_internal_consistency_i((void *)start, (void *)end, slot_size, &data);
7342 }
7343
7344 /* Check the calling Ractor's root scoping (only when verifying the current
7345 * objspace). Skipped during a global GC, which deliberately spans every Ractor's
7346 * roots and legitimately reaches foreign objects: containment does not apply. */
7347 if (!rb_gc_single_objspace_p() && objspace == rb_gc_get_objspace() &&
7348 !rb_gc_impl_during_global_gc_p(objspace)) {
7349 rb_objspace_reachable_objects_from_root(root_scope_check_i, &data);
7350 }
7351
7352 if (data.world_stopped && !global_objspace->during_absorb) {
7353 rb_gc_each_registered_addr(verify_registered_addr, &data);
7354 }
7355
7356 if (data.err_count != 0) {
7357#if RGENGC_CHECK_MODE >= 5
7358 objspace->rgengc.error_count = data.err_count;
7359 gc_marks_check(objspace, NULL, NULL);
7360 allrefs_dump(objspace);
7361#endif
7362 rb_bug("gc_verify_internal_consistency: found internal inconsistency.");
7363 }
7364
7365 /* check heap_page status */
7366 gc_verify_heap_pages(objspace);
7367
7368 /* check counters */
7369
7370 if (!is_lazy_sweeping(objspace) &&
7371 !finalizing &&
7372 !rb_gc_multi_ractor_p()) {
7373 if (objspace_live_slots(objspace) != data.live_object_count) {
7374 fprintf(stderr, "heap_pages_final_slots: %"PRIdSIZE", total_freed_objects: %"PRIdSIZE"\n",
7375 total_final_slots_count(objspace), total_freed_objects(objspace));
7376 rb_bug("inconsistent live slot number: expect %"PRIuSIZE", but %"PRIuSIZE".",
7377 objspace_live_slots(objspace), data.live_object_count);
7378 }
7379 }
7380
7381 if (!is_marking(objspace)) {
7382 if (objspace->rgengc.old_objects != data.old_object_count) {
7383 rb_bug("inconsistent old slot number: expect %"PRIuSIZE", but %"PRIuSIZE".",
7384 objspace->rgengc.old_objects, data.old_object_count);
7385 }
7386 if (objspace->rgengc.uncollectible_wb_unprotected_objects != data.remembered_shady_count) {
7387 rb_bug("inconsistent number of wb unprotected objects: expect %"PRIuSIZE", but %"PRIuSIZE".",
7388 objspace->rgengc.uncollectible_wb_unprotected_objects, data.remembered_shady_count);
7389 }
7390 }
7391
7392 if (!finalizing) {
7393 size_t list_count = 0;
7394
7395 {
7396 VALUE z = heap_pages_deferred_final;
7397 while (z) {
7398 list_count++;
7399 z = RZOMBIE(z)->next;
7400 }
7401 }
7402
7403 if (total_final_slots_count(objspace) != data.zombie_object_count ||
7404 total_final_slots_count(objspace) != list_count) {
7405
7406 rb_bug("inconsistent finalizing object count:\n"
7407 " expect %"PRIuSIZE"\n"
7408 " but %"PRIuSIZE" zombies\n"
7409 " heap_pages_deferred_final list has %"PRIuSIZE" items.",
7410 total_final_slots_count(objspace),
7411 data.zombie_object_count,
7412 list_count);
7413 }
7414 }
7415
7416 gc_report(5, objspace, "gc_verify_internal_consistency: OK\n");
7417}
7418
7419/* The `during_gc` macro expands a bare identifier to `objspace->flags.during_gc`, so a
7420 * foreign objspace's flag cannot be written directly; these helpers reach it through the
7421 * `objspace` argument. */
7422static inline unsigned int
7423gc_during_gc_get(const rb_objspace_t *objspace)
7424{
7425 return during_gc;
7426}
7427
7428static inline void
7429gc_during_gc_set(rb_objspace_t *objspace, unsigned int v)
7430{
7431 during_gc = v;
7432}
7433
7434/* Run the check with during_gc cleared in both the verified objspace and the current
7435 * Ractor's: rb_objspace_reachable_objects_from() decides on rb_gc_get_objspace(), and
7436 * under a global GC the driver verifies foreign objspaces, so the driver's during_gc
7437 * needs clearing too (a no-op when cur == objspace). */
7438static void
7439gc_verify_internal_consistency_body(rb_objspace_t *objspace, bool world_stopped)
7440{
7441 const unsigned int prev_during_gc = during_gc;
7442 during_gc = FALSE; // stop gc here
7443
7444 rb_objspace_t *const cur = rb_gc_get_objspace();
7445 const unsigned int prev_cur_during_gc = (cur != objspace) ? gc_during_gc_get(cur) : 0;
7446 if (cur != objspace) gc_during_gc_set(cur, FALSE);
7447 {
7448 gc_verify_internal_consistency_(objspace, world_stopped);
7449 }
7450 if (cur != objspace) gc_during_gc_set(cur, prev_cur_during_gc);
7451 during_gc = prev_during_gc;
7452}
7453
7454static void
7455gc_verify_internal_consistency(void *objspace_ptr)
7456{
7457 rb_objspace_t *objspace = objspace_ptr;
7458
7459 /* Called mid-GC, take neither the VM lock nor the barrier: waiting would join a
7460 * pending global barrier mid-collection (a GC must never take the VM lock) and let
7461 * the global GC sweep the heap this mark is walking. The barrier is unnecessary
7462 * anyway; the objspace is single-writer, this verify runs on its owner thread, and
7463 * the global driver that sets during_gc everywhere already holds both. */
7464 if (during_gc) {
7465 /* The world is stopped only when the global GC's driver runs this while holding
7466 * the barrier; a non-main Ractor's local GC does not stop other Ractors. */
7467 gc_verify_internal_consistency_body(objspace, rb_gc_impl_during_global_gc_p(objspace));
7468 return;
7469 }
7470
7471 unsigned int lev = RB_GC_VM_LOCK();
7472 {
7473 rb_gc_vm_barrier(); // stop other ractors
7474 gc_verify_internal_consistency_body(objspace, true); // holding the barrier, so walking every objspace is sound
7475 }
7476 RB_GC_VM_UNLOCK(lev);
7477}
7478
7479static void
7480heap_move_pooled_pages_to_free_pages(rb_heap_t *heap)
7481{
7482 if (heap->pooled_pages) {
7483 if (heap->free_pages) {
7484 struct heap_page *free_pages_tail = heap->free_pages;
7485 while (free_pages_tail->free_next) {
7486 free_pages_tail = free_pages_tail->free_next;
7487 }
7488 free_pages_tail->free_next = heap->pooled_pages;
7489 }
7490 else {
7491 heap->free_pages = heap->pooled_pages;
7492 }
7493
7494 heap->pooled_pages = NULL;
7495 }
7496}
7497
7498static int
7499gc_remember_unprotected(rb_objspace_t *objspace, VALUE obj)
7500{
7501 struct heap_page *page = GET_HEAP_PAGE(obj);
7502 bits_t *uncollectible_bits = &page->uncollectible_bits[0];
7503
7504 if (!MARKED_IN_BITMAP(uncollectible_bits, obj)) {
7505 page->flags.has_uncollectible_wb_unprotected_objects = TRUE;
7506 MARK_IN_BITMAP(uncollectible_bits, obj);
7507 /* Like RVALUE_PAGE_OLD_UNCOLLECTIBLE_SET, count it in the object's own objspace. */
7508 page->objspace->rgengc.uncollectible_wb_unprotected_objects++;
7509
7510#if RGENGC_PROFILE > 0
7511 objspace->profile.total_remembered_shady_object_count++;
7512#if RGENGC_PROFILE >= 2
7513 objspace->profile.remembered_shady_object_count_types[BUILTIN_TYPE(obj)]++;
7514#endif
7515#endif
7516 return TRUE;
7517 }
7518 else {
7519 return FALSE;
7520 }
7521}
7522
7523static inline void
7524gc_marks_wb_unprotected_objects_plane(rb_objspace_t *objspace, uintptr_t p, bits_t bits, short slot_size)
7525{
7526 if (bits) {
7527 do {
7528 if (bits & 1) {
7529 gc_report(2, objspace, "gc_marks_wb_unprotected_objects: marked shady: %s\n", rb_obj_info((VALUE)p));
7530 GC_ASSERT(RVALUE_WB_UNPROTECTED(objspace, (VALUE)p));
7531 GC_ASSERT(RVALUE_MARKED(objspace, (VALUE)p));
7532 gc_mark_children(objspace, (VALUE)p);
7533 }
7534 p += slot_size;
7535 bits >>= 1;
7536 } while (bits);
7537 }
7538}
7539
7540static void
7541gc_marks_wb_unprotected_objects(rb_objspace_t *objspace, rb_heap_t *heap)
7542{
7543 struct heap_page *page = 0;
7544
7545 ccan_list_for_each(&heap->pages, page, page_node) {
7546 bits_t *mark_bits = page->mark_bits;
7547 bits_t *wbun_bits = page->wb_unprotected_bits;
7548 uintptr_t p = page->start;
7549 short slot_size = page->slot_size;
7550 int total_slots = page->total_slots;
7551 int bitmap_plane_count = CEILDIV(total_slots, BITS_BITLENGTH);
7552 size_t j;
7553
7554 for (j=0; j<(size_t)bitmap_plane_count; j++) {
7555 bits_t bits = mark_bits[j] & wbun_bits[j];
7556 gc_marks_wb_unprotected_objects_plane(objspace, p, bits, slot_size);
7557 p += BITS_BITLENGTH * slot_size;
7558 }
7559 }
7560
7561 gc_mark_stacked_objects_all(objspace);
7562}
7563
7564void
7565rb_gc_impl_declare_weak_references(void *objspace_ptr, VALUE obj)
7566{
7568}
7569
7570bool
7571rb_gc_impl_handle_weak_references_alive_p(void *objspace_ptr, VALUE obj)
7572{
7573 rb_objspace_t *objspace = objspace_ptr;
7574
7575 /* A local GC cannot decide a foreign object's liveness, so treat it as live; its
7576 * owner or the global GC decides (a global GC's unified mark is exact). */
7577 if (gc_skip_foreign_object_p(objspace, obj)) return true;
7578
7579 bool marked = RVALUE_MARKED(objspace, obj);
7580
7581 if (marked) {
7582 rgengc_check_relation(objspace, obj);
7583 }
7584
7585 return marked;
7586}
7587
7588static void
7589gc_update_weak_references(rb_objspace_t *objspace)
7590{
7591 VALUE *obj_ptr;
7592 rb_darray_foreach(objspace->weak_references, i, obj_ptr) {
7593 gc_mark_set_parent(objspace, *obj_ptr);
7594 rb_gc_handle_weak_references(*obj_ptr);
7595 gc_mark_set_parent_invalid(objspace);
7596 }
7597
7598 size_t capa = rb_darray_capa(objspace->weak_references);
7599 size_t size = rb_darray_size(objspace->weak_references);
7600
7601 objspace->profile.weak_references_count = size;
7602
7603 rb_darray_clear(objspace->weak_references);
7604
7605 /* If the darray has capacity for more than four times the amount used, we
7606 * shrink it down to half of that capacity. */
7607 if (capa > size * 4) {
7608 rb_darray_resize_capa_without_gc(&objspace->weak_references, size * 2);
7609 }
7610}
7611
7612static void
7613gc_marks_finish(rb_objspace_t *objspace)
7614{
7615 /* finish incremental GC */
7616 if (is_incremental_marking(objspace)) {
7617 if (RGENGC_CHECK_MODE && is_mark_stack_empty(&objspace->mark_stack) == 0) {
7618 rb_bug("gc_marks_finish: mark stack is not empty (%"PRIdSIZE").",
7619 mark_stack_size(&objspace->mark_stack));
7620 }
7621
7622 mark_roots(objspace, NULL);
7623 while (gc_mark_stacked_objects_incremental(objspace, INT_MAX) == false);
7624
7625#if RGENGC_CHECK_MODE >= 2
7626 if (gc_verify_heap_pages(objspace) != 0) {
7627 rb_bug("gc_marks_finish (incremental): there are remembered old objects.");
7628 }
7629#endif
7630
7631 objspace->flags.during_incremental_marking = FALSE;
7632 /* check children of all marked wb-unprotected objects */
7633 for (int i = 0; i < HEAP_COUNT; i++) {
7634 gc_marks_wb_unprotected_objects(objspace, &heaps[i]);
7635 }
7636 }
7637
7638 /* Pin the shareable objects and shrefs ordinary marking missed: a local GC must free
7639 * neither (another objspace may hold them). Running after the full walk makes the
7640 * pin count a retention metric: an upper bound on the garbage only a global GC can
7641 * reclaim. A global GC's exact mark does not pin. (The allrefs comparison of
7642 * RGENGC_CHECK_MODE >= 4 does not model these pins; it reports false positives.)
7643 *
7644 * Running it here rather than with the other roots is also what lets an incremental
7645 * mark run while other objspaces exist. The write barrier bails out on a
7646 * cross-objspace edge, so a store made between two mark steps leaves nothing behind
7647 * but a shareable or shref bit; scanning those bitmaps after the last step picks up
7648 * every bit set during the cycle, which a scan at gc_marks_start would miss. */
7649 objspace->last_cycle_pinned = 0;
7650 if (!rb_gc_single_objspace_p() && !objspace->flags.during_global_gc) {
7651 objspace->last_cycle_pinned = 1;
7652 gc_mark_set_parent_raw(objspace, Qundef, false);
7653 for (int i = 0; i < HEAP_COUNT; i++) {
7654 pinned_roots_mark(objspace, &heaps[i]);
7655 }
7656 /* And everything they keep alive. */
7657 gc_mark_stacked_objects_all(objspace);
7658 }
7659
7660 gc_update_weak_references(objspace);
7661
7662#if RGENGC_CHECK_MODE >= 4
7663 during_gc = FALSE;
7664 gc_marks_check(objspace, gc_check_after_marks_i, "after_marks");
7665 during_gc = TRUE;
7666#endif
7667
7668 {
7669 /* Only this objspace's own Ractor allocates from it. The main objspace
7670 * keeps the VM-wide count it has used since before per-Ractor GC. */
7671 const unsigned long ractor_cnt = objspace == global_objspace->main_objspace
7672 ? rb_gc_vm_ractor_count() : 1;
7673 const unsigned long r_mul = ractor_cnt > 8 ? 8 : ractor_cnt; // upto 8
7674
7675 size_t total_slots = objspace_available_slots(objspace);
7676 size_t sweep_slots = total_slots - objspace->marked_slots; /* will be swept slots */
7677 size_t max_free_slots = (size_t)(total_slots * gc_params.heap_free_slots_max_ratio);
7678 size_t min_free_slots = (size_t)(total_slots * gc_params.heap_free_slots_min_ratio);
7679 if (min_free_slots < gc_params.heap_free_slots * r_mul) {
7680 min_free_slots = gc_params.heap_free_slots * r_mul;
7681 }
7682
7683 int full_marking = is_full_marking(objspace);
7684
7685 GC_ASSERT(objspace_available_slots(objspace) >= objspace->marked_slots);
7686
7687 /* Setup freeable slots. */
7688 size_t total_init_slots = 0;
7689 for (int i = 0; i < HEAP_COUNT; i++) {
7690 total_init_slots += (objspace_heap_init_bytes(objspace) / heaps[i].slot_size) * r_mul;
7691 }
7692
7693 if (max_free_slots < total_init_slots) {
7694 max_free_slots = total_init_slots;
7695 }
7696
7697 /* Approximate freeable pages using the average slots-per-pages across all heaps */
7698 if (sweep_slots > max_free_slots) {
7699 size_t excess_slots = sweep_slots - max_free_slots;
7700 size_t total_heap_pages = heap_eden_total_pages(objspace);
7701 heap_pages_freeable_pages = total_heap_pages > 0
7702 ? excess_slots * total_heap_pages / total_slots
7703 : 0;
7704 }
7705 else {
7706 heap_pages_freeable_pages = 0;
7707 }
7708
7709 if (objspace->heap_pages.allocatable_bytes == 0 && sweep_slots < min_free_slots) {
7710 if (!full_marking && sweep_slots < min_free_slots * 7 / 8) {
7711 if (objspace->profile.count - objspace->rgengc.last_major_gc < RVALUE_OLD_AGE) {
7712 full_marking = TRUE;
7713 }
7714 else {
7715 gc_report(1, objspace, "gc_marks_finish: next is full GC!!)\n");
7716 gc_needs_major_flags |= GPR_FLAG_MAJOR_BY_NOFREE;
7717 }
7718 }
7719
7720 if (full_marking) {
7721 heap_allocatable_bytes_expand(objspace, NULL, sweep_slots, total_slots, heaps[0].slot_size);
7722 }
7723 }
7724
7725 if (full_marking) {
7726 /* See the comment about RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR */
7727 const double r = gc_params.oldobject_limit_factor;
7728 objspace->rgengc.uncollectible_wb_unprotected_objects_limit = MAX(
7729 (size_t)(objspace->rgengc.uncollectible_wb_unprotected_objects * r),
7730 (size_t)(objspace->rgengc.old_objects * gc_params.uncollectible_wb_unprotected_objects_limit_ratio)
7731 );
7732 objspace->rgengc.old_objects_limit = (size_t)(objspace->rgengc.old_objects * r);
7733 }
7734
7735 if (objspace->rgengc.uncollectible_wb_unprotected_objects > objspace->rgengc.uncollectible_wb_unprotected_objects_limit) {
7736 gc_needs_major_flags |= GPR_FLAG_MAJOR_BY_SHADY;
7737 }
7738 if (objspace->rgengc.old_objects > objspace->rgengc.old_objects_limit) {
7739 gc_needs_major_flags |= GPR_FLAG_MAJOR_BY_OLDGEN;
7740 }
7741
7742 gc_report(1, objspace, "gc_marks_finish (marks %"PRIdSIZE" objects, "
7743 "old %"PRIdSIZE" objects, total %"PRIdSIZE" slots, "
7744 "sweep %"PRIdSIZE" slots, allocatable %"PRIdSIZE" bytes, next GC: %s)\n",
7745 objspace->marked_slots, objspace->rgengc.old_objects, objspace_available_slots(objspace), sweep_slots, objspace->heap_pages.allocatable_bytes,
7746 gc_needs_major_flags ? "major" : "minor");
7747 }
7748
7749 // TODO: refactor so we don't need to call this
7750 rb_ractor_finish_marking(is_full_marking(objspace));
7751
7753}
7754
7755static bool
7756gc_compact_heap_cursors_met_p(rb_heap_t *heap)
7757{
7758 return heap->sweeping_page == heap->compact_cursor;
7759}
7760
7761
7762static rb_heap_t *
7763gc_compact_destination_pool(rb_objspace_t *objspace, rb_heap_t *src_pool, VALUE obj)
7764{
7765 size_t obj_size = rb_gc_obj_optimal_size(obj);
7766 if (obj_size == 0) {
7767 return src_pool;
7768 }
7769
7770 GC_ASSERT(rb_gc_impl_size_allocatable_p(obj_size));
7771
7772 size_t idx = heap_idx_for_size(obj_size);
7773
7774 return &heaps[idx];
7775}
7776
7777static bool
7778gc_compact_move(rb_objspace_t *objspace, rb_heap_t *heap, VALUE src)
7779{
7780 GC_ASSERT(BUILTIN_TYPE(src) != T_MOVED);
7781 GC_ASSERT(gc_is_moveable_obj(objspace, src));
7782
7783 rb_heap_t *dest_pool = gc_compact_destination_pool(objspace, heap, src);
7784 if (gc_compact_heap_cursors_met_p(dest_pool)) {
7785 return dest_pool != heap;
7786 }
7787
7788 while (!try_move(objspace, dest_pool, dest_pool->free_pages, src)) {
7789 struct gc_sweep_context ctx = {
7790 .page = dest_pool->sweeping_page,
7791 .final_slots = 0,
7792 .freed_slots = 0,
7793 .empty_slots = 0,
7794 };
7795
7796 /* The page of src could be partially compacted, so it may contain
7797 * T_MOVED. Sweeping a page may read objects on this page, so we
7798 * need to lock the page. */
7799 lock_page_body(objspace, GET_PAGE_BODY(src));
7800 gc_sweep_page(objspace, dest_pool, &ctx);
7801 unlock_page_body(objspace, GET_PAGE_BODY(src));
7802
7803 if (dest_pool->sweeping_page->free_slots > 0) {
7804 heap_add_freepage(dest_pool, dest_pool->sweeping_page);
7805 }
7806
7807 dest_pool->sweeping_page = ccan_list_next(&dest_pool->pages, dest_pool->sweeping_page, page_node);
7808 if (gc_compact_heap_cursors_met_p(dest_pool)) {
7809 return dest_pool != heap;
7810 }
7811 }
7812
7813 return true;
7814}
7815
7816static bool
7817gc_compact_plane(rb_objspace_t *objspace, rb_heap_t *heap, uintptr_t p, bits_t bitset, struct heap_page *page)
7818{
7819 short slot_size = page->slot_size;
7820
7821 do {
7822 VALUE vp = (VALUE)p;
7823 GC_ASSERT(vp % sizeof(VALUE) == 0);
7824
7825 if (bitset & 1) {
7826 objspace->rcompactor.considered_count_table[BUILTIN_TYPE(vp)]++;
7827
7828 if (gc_is_moveable_obj(objspace, vp)) {
7829 if (!gc_compact_move(objspace, heap, vp)) {
7830 //the cursors met. bubble up
7831 return false;
7832 }
7833 }
7834 }
7835 p += slot_size;
7836 bitset >>= 1;
7837 } while (bitset);
7838
7839 return true;
7840}
7841
7842// Iterate up all the objects in page, moving them to where they want to go
7843static bool
7844gc_compact_page(rb_objspace_t *objspace, rb_heap_t *heap, struct heap_page *page)
7845{
7846 GC_ASSERT(page == heap->compact_cursor);
7847
7848 bits_t *mark_bits, *pin_bits;
7849 bits_t bitset;
7850 uintptr_t p = page->start;
7851 short slot_size = page->slot_size;
7852 int total_slots = page->total_slots;
7853 int bitmap_plane_count = CEILDIV(total_slots, BITS_BITLENGTH);
7854
7855 mark_bits = page->mark_bits;
7856 pin_bits = page->pinned_bits;
7857
7858 for (int j = 0; j < bitmap_plane_count; j++) {
7859 // objects that can be moved are marked and not pinned
7860 bitset = (mark_bits[j] & ~pin_bits[j]);
7861 if (bitset) {
7862 if (!gc_compact_plane(objspace, heap, (uintptr_t)p, bitset, page))
7863 return false;
7864 }
7865 p += BITS_BITLENGTH * slot_size;
7866 }
7867
7868 return true;
7869}
7870
7871static bool
7872gc_compact_all_compacted_p(rb_objspace_t *objspace)
7873{
7874 for (int i = 0; i < HEAP_COUNT; i++) {
7875 rb_heap_t *heap = &heaps[i];
7876
7877 if (heap->total_pages > 0 &&
7878 !gc_compact_heap_cursors_met_p(heap)) {
7879 return false;
7880 }
7881 }
7882
7883 return true;
7884}
7885
7886/* Compaction's move phase: relocate this objspace's movable objects and leave T_MOVED
7887 * forwarding behind without updating references yet. A global GC calls this for every
7888 * objspace before updating any of them (two phases), so a cross-objspace reference to a
7889 * moved object is rewritten exactly once, after all forwarding exists. */
7890static void
7891gc_compact_relocate(rb_objspace_t *objspace)
7892{
7893 gc_compact_start(objspace);
7894
7895 while (!gc_compact_all_compacted_p(objspace)) {
7896 for (int i = 0; i < HEAP_COUNT; i++) {
7897 rb_heap_t *heap = &heaps[i];
7898
7899 if (gc_compact_heap_cursors_met_p(heap)) {
7900 continue;
7901 }
7902
7903 struct heap_page *start_page = heap->compact_cursor;
7904
7905 if (!gc_compact_page(objspace, heap, start_page)) {
7906 lock_page_body(objspace, start_page->body);
7907
7908 continue;
7909 }
7910
7911 // If we get here, we've finished moving all objects on the compact_cursor page
7912 // So we can lock it and move the cursor on to the next one.
7913 lock_page_body(objspace, start_page->body);
7914 heap->compact_cursor = ccan_list_prev(&heap->pages, heap->compact_cursor, page_node);
7915 }
7916 }
7917}
7918
7919static void
7920gc_sweep_compact(rb_objspace_t *objspace)
7921{
7922 gc_compact_relocate(objspace);
7923 /* A compacting global GC defers the finish (reference update) to the second phase,
7924 * after every objspace has been relocated. */
7925 if (!global_objspace->global_gc.compacting) {
7926 gc_compact_finish(objspace);
7927 }
7928}
7929
7930static void
7931gc_marks_rest(rb_objspace_t *objspace)
7932{
7933 gc_report(1, objspace, "gc_marks_rest\n");
7934
7935 for (int i = 0; i < HEAP_COUNT; i++) {
7936 (&heaps[i])->pooled_pages = NULL;
7937 }
7938
7939 if (is_incremental_marking(objspace)) {
7940 while (gc_mark_stacked_objects_incremental(objspace, INT_MAX) == FALSE);
7941 }
7942 else {
7943 gc_mark_stacked_objects_all(objspace);
7944 }
7945
7946 gc_marks_finish(objspace);
7947}
7948
7949static bool
7950gc_marks_step(rb_objspace_t *objspace, size_t slots)
7951{
7952 bool marking_finished = false;
7953
7954 GC_ASSERT(is_marking(objspace));
7955 if (gc_mark_stacked_objects_incremental(objspace, slots)) {
7956 gc_marks_finish(objspace);
7957
7958 marking_finished = true;
7959 }
7960
7961 return marking_finished;
7962}
7963
7964static bool
7965gc_marks_continue(rb_objspace_t *objspace, rb_heap_t *heap)
7966{
7967 GC_ASSERT(dont_gc_val() == FALSE || objspace->profile.latest_gc_info & GPR_FLAG_METHOD);
7968 bool marking_finished = true;
7969
7970 gc_marking_enter(objspace);
7971
7972 if (heap->free_pages) {
7973 gc_report(2, objspace, "gc_marks_continue: has pooled pages");
7974
7975 marking_finished = gc_marks_step(objspace, objspace->rincgc.step_slots);
7976 }
7977 else {
7978 gc_report(2, objspace, "gc_marks_continue: no more pooled pages (stack depth: %"PRIdSIZE").\n",
7979 mark_stack_size(&objspace->mark_stack));
7980 heap->force_incremental_marking_finish_count++;
7981 gc_marks_rest(objspace);
7982 }
7983
7984 gc_marking_exit(objspace);
7985
7986 return marking_finished;
7987}
7988
7989/* Mark the following as roots of this objspace.
7990 * - Every shareable object: another objspace may hold the only reference, invisible to a
7991 * local GC. Marking them rather than skipping them in the sweep preserves the
7992 * generational invariants (a pinned object ages and gets promoted like any live one).
7993 * Only a global GC decides that a shareable object is dead.
7994 * - Every shref (an unshareable object referenced from a shareable one): the referring
7995 * shareable object can live in another objspace or in an in-flight message queue. The
7996 * write barrier maintains them.
7997 * Skipped while the VM has a single Ractor: a local GC is then a whole-world GC and
7998 * shareable objects may die normally. */
7999static void
8000pinned_roots_mark(rb_objspace_t *objspace, rb_heap_t *heap)
8001{
8002 struct heap_page *page = NULL;
8003
8004 /* Runs before mark_roots, so rgengc_check_relation sees a valid (absent) parent rather
8005 * than the poison left by the previous GC. */
8006 gc_mark_set_parent_raw(objspace, Qundef, false);
8007
8008 /* A local GC never frees or traverses a shareable object, and keeps its unshareable
8009 * children alive through their shref bits, so:
8010 * - a shareable object only gets its mark bit set (like an old object), which keeps
8011 * the sweep off it, and is not traversed;
8012 * - a shref is marked and traversed, like a remembered old->young target: without
8013 * that, the referring shareable object is never walked and it would look
8014 * unreachable.
8015 * Objects can become shareable between GCs, so this pass scans the bitmaps in every
8016 * mark (gc_marks_finish) instead of maintaining a pin set across the sweep. */
8017 ccan_list_for_each(&heap->pages, page, page_node) {
8018 if (!(page->flags.has_shareable_objects | page->flags.has_shref_objects)) continue;
8019
8020 uintptr_t p = page->start;
8021 short slot_size = page->slot_size;
8022 int total_slots = page->total_slots;
8023 int bitmap_plane_count = CEILDIV(total_slots, BITS_BITLENGTH);
8024
8025 for (int j = 0; j < bitmap_plane_count; j++) {
8026 bits_t sr_bits = page->shref_bits[j];
8027 /* Only the pins ordinary marking left unmarked need work here: an already
8028 * marked object (reached by traversal, or pre-marked because it is old) is a
8029 * no-op in gc_mark_set, so skip visiting it. */
8030 bits_t bitset = (page->shareable_bits[j] | sr_bits) & ~page->mark_bits[j];
8031 uintptr_t pp = p;
8032 while (bitset) {
8033 if (bitset & 1) {
8034 VALUE obj = (VALUE)pp;
8035 asan_unpoisoning_object(obj) {
8036 switch (BUILTIN_TYPE(obj)) {
8037 case T_NONE:
8038 case T_ZOMBIE:
8039 case T_MOVED:
8040 /* A dead slot (a zombie awaiting its finalizer) is not a root. */
8041 break;
8042 default:
8043 gc_report(2, objspace, "pinned_roots_mark: mark %s\n", rb_obj_info(obj));
8044 if (sr_bits & 1) {
8045 gc_mark(objspace, obj); /* shref: root + traverse */
8046 }
8047 else if (gc_mark_set(objspace, obj)) {
8048 gc_aging(objspace, obj); /* shareable: mark, no traverse */
8049 /* Pin as well when compaction runs alongside: if a shareable
8050 * object moved, the C-struct slots of other Ractors (a
8051 * port in sync, say) are not updated and go stale. */
8052 gc_pin(objspace, obj);
8053 }
8054 break;
8055 }
8056 }
8057 }
8058 pp += slot_size;
8059 bitset >>= 1;
8060 sr_bits >>= 1;
8061 }
8062 p += BITS_BITLENGTH * slot_size;
8063 }
8064 }
8065}
8066
8067static void
8068gc_marks_start(rb_objspace_t *objspace, int full_mark)
8069{
8070 /* start marking */
8071 gc_report(1, objspace, "gc_marks_start: (%s)\n", full_mark ? "full" : "minor");
8072 gc_mode_transition(objspace, gc_mode_marking);
8073
8074 if (full_mark) {
8075 size_t incremental_marking_steps = (objspace->rincgc.pooled_slots / INCREMENTAL_MARK_STEP_ALLOCATIONS) + 1;
8076 objspace->rincgc.step_slots = (objspace->marked_slots * 2) / incremental_marking_steps;
8077
8078 if (0) fprintf(stderr, "objspace->marked_slots: %"PRIdSIZE", "
8079 "objspace->rincgc.pooled_page_num: %"PRIdSIZE", "
8080 "objspace->rincgc.step_slots: %"PRIdSIZE", \n",
8081 objspace->marked_slots, objspace->rincgc.pooled_slots, objspace->rincgc.step_slots);
8082 objspace->flags.during_minor_gc = FALSE;
8083 if (ruby_enable_autocompact && rb_gc_single_objspace_p()) {
8084 objspace->flags.during_compacting |= TRUE;
8085 }
8086 objspace->profile.major_gc_count++;
8087 objspace->rgengc.uncollectible_wb_unprotected_objects = 0;
8088 objspace->rgengc.old_objects = 0;
8089 objspace->rgengc.last_major_gc = objspace->profile.count;
8090 objspace->marked_slots = 0;
8091
8092 for (int i = 0; i < HEAP_COUNT; i++) {
8093 rb_heap_t *heap = &heaps[i];
8094 gc_bitmaps_clear(objspace, heap, false);
8095 heap_move_pooled_pages_to_free_pages(heap);
8096
8097 if (objspace->flags.during_compacting) {
8098 struct heap_page *page = NULL;
8099
8100 ccan_list_for_each(&heap->pages, page, page_node) {
8101 page->pinned_slots = 0;
8102 }
8103 }
8104 }
8105 }
8106 else {
8107 objspace->flags.during_minor_gc = TRUE;
8108 objspace->marked_slots =
8109 objspace->rgengc.old_objects + objspace->rgengc.uncollectible_wb_unprotected_objects; /* uncollectible objects are marked already */
8110 objspace->profile.minor_gc_count++;
8111
8112 for (int i = 0; i < HEAP_COUNT; i++) {
8113 rgengc_rememberset_mark(objspace, &heaps[i]);
8114 }
8115 }
8116
8117 mark_roots(objspace, NULL);
8118
8119 gc_report(1, objspace, "gc_marks_start: (%s) end, stack in %"PRIdSIZE"\n",
8120 full_mark ? "full" : "minor", mark_stack_size(&objspace->mark_stack));
8121}
8122
8123static bool
8124gc_marks(rb_objspace_t *objspace, int full_mark)
8125{
8126 gc_marking_enter(objspace);
8127
8128 bool marking_finished = false;
8129
8130 /* setup marking */
8131
8132 gc_marks_start(objspace, full_mark);
8133 if (!is_incremental_marking(objspace)) {
8134 gc_marks_rest(objspace);
8135 marking_finished = true;
8136 }
8137
8138#if RGENGC_PROFILE > 0
8139 if (gc_prof_record(objspace)) {
8140 gc_profile_record *record = gc_prof_record(objspace);
8141 record->old_objects = objspace->rgengc.old_objects;
8142 }
8143#endif
8144
8145 gc_marking_exit(objspace);
8146
8147 return marking_finished;
8148}
8149
8150/* RGENGC */
8151
8152static void
8153gc_report_body(int level, rb_objspace_t *objspace, const char *fmt, ...)
8154{
8155 if (level <= RGENGC_DEBUG) {
8156 char buf[1024];
8157 FILE *out = stderr;
8158 va_list args;
8159 const char *status = " ";
8160
8161 if (during_gc) {
8162 status = is_full_marking(objspace) ? "+" : "-";
8163 }
8164 else {
8165 if (is_lazy_sweeping(objspace)) {
8166 status = "S";
8167 }
8168 if (is_incremental_marking(objspace)) {
8169 status = "M";
8170 }
8171 }
8172
8173 va_start(args, fmt);
8174 vsnprintf(buf, 1024, fmt, args);
8175 va_end(args);
8176
8177 fprintf(out, "%s|", status);
8178 fputs(buf, out);
8179 }
8180}
8181
8182/* bit operations */
8183
8184static void
8185rgengc_remembersetbits_set(rb_objspace_t *objspace, VALUE obj)
8186{
8187 struct heap_page *page = GET_HEAP_PAGE(obj);
8188 bits_t *bits = &page->remembered_bits[0];
8189
8190 /* remembered_bits writers are always serialized: the write barrier only remembers a
8191 * local a (under its Ractor's GVL) and a global GC writes from the driver alone.
8192 * Set the bit before the page flag so a page pending re-scan stays in
8193 * rememberset_mark. */
8194 _MARK_IN_BITMAP(bits, page, obj);
8195 page->flags.has_remembered_objects = TRUE;
8196}
8197
8198/* wb, etc */
8199
8200/* return FALSE if already remembered */
8201static void
8202rgengc_remember(rb_objspace_t *objspace, VALUE obj)
8203{
8204 gc_report(6, objspace, "rgengc_remember: %s %s\n", rb_obj_info(obj),
8205 RVALUE_REMEMBERED(objspace, obj) ? "was already remembered" : "is remembered now");
8206
8207 check_rvalue_consistency(objspace, obj);
8208
8209 if (RGENGC_CHECK_MODE) {
8210 if (RVALUE_WB_UNPROTECTED(objspace, obj)) rb_bug("rgengc_remember: %s is not wb protected.", rb_obj_info(obj));
8211 }
8212
8213#if RGENGC_PROFILE > 0
8214 if (!RVALUE_REMEMBERED(objspace, obj)) {
8215 if (RVALUE_WB_UNPROTECTED(objspace, obj) == 0) {
8216 objspace->profile.total_remembered_normal_object_count++;
8217#if RGENGC_PROFILE >= 2
8218 objspace->profile.remembered_normal_object_count_types[BUILTIN_TYPE(obj)]++;
8219#endif
8220 }
8221 }
8222#endif /* RGENGC_PROFILE > 0 */
8223
8224 rgengc_remembersetbits_set(objspace, obj);
8225}
8226
8227#ifndef PROFILE_REMEMBERSET_MARK
8228#define PROFILE_REMEMBERSET_MARK 0
8229#endif
8230
8231static inline void
8232rgengc_rememberset_mark_plane(rb_objspace_t *objspace, uintptr_t p, bits_t bitset, short slot_size)
8233{
8234 if (bitset) {
8235 do {
8236 if (bitset & 1) {
8237 VALUE obj = (VALUE)p;
8238 gc_report(2, objspace, "rgengc_rememberset_mark: mark %s\n", rb_obj_info(obj));
8239 GC_ASSERT(RVALUE_UNCOLLECTIBLE(objspace, obj));
8240 GC_ASSERT(RVALUE_OLD_P(objspace, obj) || RVALUE_WB_UNPROTECTED(objspace, obj));
8241
8242 gc_mark_children(objspace, obj);
8243
8245 rb_darray_append_without_gc(&objspace->weak_references, obj);
8246 }
8247 }
8248 p += slot_size;
8249 bitset >>= 1;
8250 } while (bitset);
8251 }
8252}
8253
8254static void
8255rgengc_rememberset_mark(rb_objspace_t *objspace, rb_heap_t *heap)
8256{
8257 size_t j;
8258 struct heap_page *page = 0;
8259#if PROFILE_REMEMBERSET_MARK
8260 int has_old = 0, has_shady = 0, has_both = 0, skip = 0;
8261#endif
8262 gc_report(1, objspace, "rgengc_rememberset_mark: start\n");
8263
8264 ccan_list_for_each(&heap->pages, page, page_node) {
8265 if (page->flags.has_remembered_objects | page->flags.has_uncollectible_wb_unprotected_objects) {
8266 uintptr_t p = page->start;
8267 short slot_size = page->slot_size;
8268 int total_slots = page->total_slots;
8269 int bitmap_plane_count = CEILDIV(total_slots, BITS_BITLENGTH);
8270 bits_t bitset, bits[HEAP_PAGE_BITMAP_LIMIT];
8271 bits_t *remembered_bits = page->remembered_bits;
8272 bits_t *uncollectible_bits = page->uncollectible_bits;
8273 bits_t *wb_unprotected_bits = page->wb_unprotected_bits;
8274#if PROFILE_REMEMBERSET_MARK
8275 if (page->flags.has_remembered_objects && page->flags.has_uncollectible_wb_unprotected_objects) has_both++;
8276 else if (page->flags.has_remembered_objects) has_old++;
8277 else if (page->flags.has_uncollectible_wb_unprotected_objects) has_shady++;
8278#endif
8279 /* Clear has_remembered_objects before draining the bits. A concurrent
8280 * lock-free write barrier (another Ractor remembering a shareable object on
8281 * this page) sets the bit first and the flag second, so clearing the flag first
8282 * keeps the page scheduled for re-scan even if that set interleaves. The
8283 * per-word drain is an atomic read-and-clear, so an interleaved set is not lost
8284 * (it lands in the zeroed word). */
8285 page->flags.has_remembered_objects = FALSE;
8286 for (j=0; j < (size_t)bitmap_plane_count; j++) {
8287 bits[j] = RUBY_ATOMIC_SIZE_EXCHANGE(*(volatile size_t *)&remembered_bits[j], 0)
8288 | (uncollectible_bits[j] & wb_unprotected_bits[j]);
8289 }
8290
8291 for (j=0; j < (size_t)bitmap_plane_count; j++) {
8292 bitset = bits[j];
8293 rgengc_rememberset_mark_plane(objspace, p, bitset, slot_size);
8294 p += BITS_BITLENGTH * slot_size;
8295 }
8296 }
8297#if PROFILE_REMEMBERSET_MARK
8298 else {
8299 skip++;
8300 }
8301#endif
8302 }
8303
8304#if PROFILE_REMEMBERSET_MARK
8305 fprintf(stderr, "%d\t%d\t%d\t%d\n", has_both, has_old, has_shady, skip);
8306#endif
8307 gc_report(1, objspace, "rgengc_rememberset_mark: finished\n");
8308}
8309
8310static void
8311gc_bitmaps_clear(rb_objspace_t *objspace, rb_heap_t *heap, bool clear_shref)
8312{
8313 struct heap_page *page = 0;
8314
8315 ccan_list_for_each(&heap->pages, page, page_node) {
8316 memset(&page->mark_bits[0], 0, HEAP_PAGE_BITMAP_SIZE);
8317 memset(&page->uncollectible_bits[0], 0, HEAP_PAGE_BITMAP_SIZE);
8318 memset(&page->marking_bits[0], 0, HEAP_PAGE_BITMAP_SIZE);
8319 /* A plain memset can lose a concurrent remember, but only a shareable object can
8320 * be remembered from another Ractor's thread, and pinned_roots_mark re-marks
8321 * those every local cycle, and this clear precedes a major that re-scans all. */
8322 memset(&page->remembered_bits[0], 0, HEAP_PAGE_BITMAP_SIZE);
8323 memset(&page->pinned_bits[0], 0, HEAP_PAGE_BITMAP_SIZE);
8324 page->flags.has_uncollectible_wb_unprotected_objects = FALSE;
8325 page->flags.has_remembered_objects = FALSE;
8326 /* A shref is a local GC's root, so only a stop-the-world global GC may clear them:
8327 * its unified mark re-derives them from every shareable -> unshareable edge. */
8328 if (clear_shref) {
8329 memset(&page->shref_bits[0], 0, HEAP_PAGE_BITMAP_SIZE);
8330 page->flags.has_shref_objects = FALSE;
8331 }
8332 }
8333}
8334
8335/* RGENGC: APIs */
8336
8337NOINLINE(static void gc_writebarrier_generational(VALUE a, VALUE b, rb_objspace_t *objspace));
8338
8339/* Precondition: `a` and `b` live in `objspace`. */
8340static void
8341gc_writebarrier_generational(VALUE a, VALUE b, rb_objspace_t *objspace)
8342{
8343 if (RGENGC_CHECK_MODE) {
8344 if (!RVALUE_OLD_P(objspace, a)) rb_bug("gc_writebarrier_generational: %s is not an old object.", rb_obj_info(a));
8345 if ( RVALUE_OLD_P(objspace, b)) rb_bug("gc_writebarrier_generational: %s is an old object.", rb_obj_info(b));
8346 if (is_incremental_marking(objspace)) rb_bug("gc_writebarrier_generational: called while incremental marking: %s -> %s", rb_obj_info(a), rb_obj_info(b));
8347 }
8348
8349 /* Mark and remember a (the default behaviour).
8350 * No lock: setting a remembered bit is atomic (rgengc_remembersetbits_set), and that is
8351 * the only place a concurrent local GC or another Ractor's write barrier can race. */
8352 if (!RVALUE_REMEMBERED(objspace, a)) {
8353 rgengc_remember(objspace, a);
8354
8355 gc_report(1, objspace, "gc_writebarrier_generational: %s (remembered) -> %s\n", rb_obj_info(a), rb_obj_info(b));
8356 }
8357
8358 check_rvalue_consistency(objspace, a);
8359 check_rvalue_consistency(objspace, b);
8360}
8361
8362static void
8363gc_mark_from(rb_objspace_t *objspace, VALUE obj, VALUE parent)
8364{
8365 gc_mark_set_parent(objspace, parent);
8366 rgengc_check_relation(objspace, obj);
8367 if (gc_mark_set(objspace, obj) != FALSE) {
8368 gc_aging(objspace, obj);
8369 gc_grey(objspace, obj);
8370 }
8371 gc_mark_set_parent_invalid(objspace);
8372}
8373
8374NOINLINE(static void gc_writebarrier_incremental(VALUE a, VALUE b, rb_objspace_t *objspace));
8375
8376/* Precondition: `a` and `b` live in `objspace`. */
8377static void
8378gc_writebarrier_incremental(VALUE a, VALUE b, rb_objspace_t *objspace)
8379{
8380 gc_report(2, objspace, "gc_writebarrier_incremental: [LG] %p -> %s\n", (void *)a, rb_obj_info(b));
8381
8382 if (RVALUE_BLACK_P(objspace, a)) {
8383 if (RVALUE_WHITE_P(objspace, b)) {
8384 if (!RVALUE_WB_UNPROTECTED(objspace, a)) {
8385 gc_report(2, objspace, "gc_writebarrier_incremental: [IN] %p -> %s\n", (void *)a, rb_obj_info(b));
8386 gc_mark_from(objspace, b, a);
8387 }
8388 }
8389 else if (RVALUE_OLD_P(objspace, a) && !RVALUE_OLD_P(objspace, b)) {
8390 rgengc_remember(objspace, a);
8391 }
8392
8393 if (RB_UNLIKELY(objspace->flags.during_compacting)) {
8394 MARK_IN_BITMAP(GET_HEAP_PINNED_BITS(b), b);
8395 }
8396 }
8397}
8398
8399void
8400rb_gc_impl_writebarrier(void *objspace_ptr, VALUE a, VALUE b)
8401{
8402 rb_objspace_t *objspace = objspace_ptr;
8403
8404#if RGENGC_CHECK_MODE
8405 if (SPECIAL_CONST_P(a)) rb_bug("rb_gc_writebarrier: a is special const: %"PRIxVALUE, a);
8406 if (SPECIAL_CONST_P(b)) rb_bug("rb_gc_writebarrier: b is special const: %"PRIxVALUE, b);
8407#else
8410#endif
8411
8412 GC_ASSERT(!during_gc);
8413 GC_ASSERT(RB_BUILTIN_TYPE(a) != T_NONE);
8414 GC_ASSERT(RB_BUILTIN_TYPE(a) != T_MOVED);
8415 GC_ASSERT(RB_BUILTIN_TYPE(a) != T_ZOMBIE);
8416
8417 /* A shareable object now references an unshareable one: record b as a shref so its
8418 * owner's local GC roots it (the parent may live in another objspace, untraversed
8419 * there). Only b's owner stores this, on its own page: a plain store suffices. */
8420 if (RB_UNLIKELY(RB_FL_TEST_RAW(a, RUBY_FL_SHAREABLE)) &&
8422 struct heap_page *bpage = GET_HEAP_PAGE(b);
8423 if (!_MARKED_IN_BITMAP(bpage->shref_bits, bpage, b)) {
8424 _MARK_IN_BITMAP(bpage->shref_bits, bpage, b);
8425 bpage->flags.has_shref_objects = TRUE;
8426 }
8427 }
8428
8429 if (!is_incremental_marking(objspace)) {
8430 /* The generational barrier covers old->young edges within one objspace only.
8431 * NOTE: we shouldn't even check the age of `a` or `b` if they are in another
8432 * objspace, so check locality first. The test is rb_gc_ever_multi_ractor_p, not
8433 * rb_gc_multi_ractor_p: a foreign objspace exists before the process is
8434 * multi-Ractor (rb_gc_objspace_alloc runs while the creator is still the only
8435 * Ractor) and outlives the return to one (a fork parks the others in
8436 * zombie_objspaces). */
8437 if ((rb_gc_ever_multi_ractor_p() &&
8438 (GET_HEAP_OBJSPACE(a) != objspace || GET_HEAP_OBJSPACE(b) != objspace)) ||
8439 !RVALUE_OLD_P(objspace, a) || RVALUE_OLD_P(objspace, b)) {
8440 // do nothing
8441 }
8442 else {
8443 gc_writebarrier_generational(a, b, objspace);
8444 }
8445 }
8446 else {
8447 // Shareable objects from different object spaces are kept alive by shareable bits
8448 if (rb_gc_ever_multi_ractor_p() &&
8449 (GET_HEAP_OBJSPACE(a) != objspace || GET_HEAP_OBJSPACE(b) != objspace)) {
8450 // do nothing
8451 }
8452 else {
8453 gc_writebarrier_incremental(a, b, objspace);
8454 }
8455 }
8456}
8457
8458void
8459rb_gc_impl_obj_became_shareable(void *objspace_ptr, VALUE obj)
8460{
8461 /* An object becomes shareable on its owner thread, so this page update is
8462 * single-writer. */
8463 struct heap_page *page = GET_HEAP_PAGE(obj);
8464
8465 if (_MARKED_IN_BITMAP(page->shareable_bits, page, obj)) return;
8466 gc_page_add_shareable(page, obj);
8467
8468 /* The shref bits recorded while the object was unshareable are now covered by the
8469 * shareable pin, and a shref only points at an unshareable object. The owner thread is
8470 * the only writer, so a plain clear is enough. */
8471 if (_MARKED_IN_BITMAP(page->shref_bits, page, obj)) {
8472 _CLEAR_IN_BITMAP(page->shref_bits, page, obj);
8473 // NOTE: page->has_shref_objects could become stale here (value is true even though logically false)
8474 }
8475}
8476
8477void
8478rb_gc_impl_writebarrier_unprotect(void *objspace_ptr, VALUE obj)
8479{
8480 rb_objspace_t *objspace = objspace_ptr;
8481
8482 /* A shareable object is never WB-unprotected. Keeping shrefs correct relies on every
8483 * store into s->u going through the write barrier, which keeps wb_unprotected_bits
8484 * single-writer (only the owner thread can unprotect its own unshareable objects). */
8485 GC_ASSERT(!RB_FL_TEST_RAW(obj, RUBY_FL_SHAREABLE));
8486
8487 if (RVALUE_WB_UNPROTECTED(objspace, obj)) {
8488 return;
8489 }
8490 else {
8491 gc_report(2, objspace, "rb_gc_writebarrier_unprotect: %s %s\n", rb_obj_info(obj),
8492 RVALUE_REMEMBERED(objspace, obj) ? " (already remembered)" : "");
8493
8494 /* No lock: per the assert obj is our own unshareable, so these bits
8495 * (wb_unprotected, uncollectible, age) are single-writer on an owned page, and
8496 * RVALUE_DEMOTE's remembered-bit clear is atomic against word-sharing writers. */
8497 if (RVALUE_OLD_P(objspace, obj)) {
8498 gc_report(1, objspace, "rb_gc_writebarrier_unprotect: %s\n", rb_obj_info(obj));
8499 RVALUE_DEMOTE(objspace, obj);
8500 gc_mark_set(objspace, obj);
8501 gc_remember_unprotected(objspace, obj);
8502
8503#if RGENGC_PROFILE
8504 objspace->profile.total_shade_operation_count++;
8505#if RGENGC_PROFILE >= 2
8506 objspace->profile.shade_operation_count_types[BUILTIN_TYPE(obj)]++;
8507#endif /* RGENGC_PROFILE >= 2 */
8508#endif /* RGENGC_PROFILE */
8509 }
8510 else {
8511 RVALUE_AGE_RESET(obj);
8512 }
8513
8514 RB_DEBUG_COUNTER_INC(obj_wb_unprotect);
8515 MARK_IN_BITMAP(GET_HEAP_WB_UNPROTECTED_BITS(obj), obj);
8516 }
8517}
8518
8519void
8520rb_gc_impl_copy_attributes(void *objspace_ptr, VALUE dest, VALUE obj)
8521{
8522 rb_objspace_t *objspace = objspace_ptr;
8523
8524 if (RVALUE_WB_UNPROTECTED(objspace, obj)) {
8525 rb_gc_impl_writebarrier_unprotect(objspace, dest);
8526 }
8527 rb_gc_impl_copy_finalizer(objspace, dest, obj);
8528}
8529
8530const char *
8531rb_gc_impl_active_gc_name(void)
8532{
8533 return "default";
8534}
8535
8536/* NOTE: `obj` doesn't necessarily live in `objspace_ptr`, as `objspace_ptr` is just that
8537 * of the current Ractor. */
8538void
8539rb_gc_impl_writebarrier_remember(void *objspace_ptr, VALUE obj)
8540{
8541
8542 rb_objspace_t *objspace = objspace_ptr;
8543
8544 // Shareable objects from other object spaces don't need to be put on the remembered set
8545 // and are only collected during global GC, so not while incremental marking.
8546 if (RB_LIKELY(!rb_gc_ever_multi_ractor_p() || GET_HEAP_OBJSPACE(obj) == objspace)) {
8547 gc_report(1, objspace, "rb_gc_writebarrier_remember: %s\n", rb_obj_info(obj));
8548 if (is_incremental_marking(objspace)) {
8549 if (RVALUE_BLACK_P(objspace, obj)) {
8550 gc_grey(objspace, obj);
8551 }
8552 }
8553 else if (RVALUE_OLD_P(objspace, obj)) {
8554 rgengc_remember(objspace, obj);
8555 }
8556 }
8557}
8558
8560 // Must be ID only
8561 ID ID_wb_protected, ID_age, ID_old, ID_uncollectible, ID_marking,
8562 ID_marked, ID_pinned, ID_remembered, ID_object_id, ID_shareable;
8563};
8564
8565#define RB_GC_OBJECT_METADATA_ENTRY_COUNT (sizeof(struct rb_gc_object_metadata_names) / sizeof(ID))
8566static struct rb_gc_object_metadata_entry object_metadata_entries[RB_GC_OBJECT_METADATA_ENTRY_COUNT + 1];
8567
8569rb_gc_impl_object_metadata(void *objspace_ptr, VALUE obj)
8570{
8571 rb_objspace_t *objspace = objspace_ptr;
8572 size_t n = 0;
8573 static struct rb_gc_object_metadata_names names;
8574
8575 if (!names.ID_marked) {
8576#define I(s) names.ID_##s = rb_intern(#s)
8577 I(wb_protected);
8578 I(age);
8579 I(old);
8580 I(uncollectible);
8581 I(marking);
8582 I(marked);
8583 I(pinned);
8584 I(remembered);
8585 I(object_id);
8586 I(shareable);
8587#undef I
8588 }
8589
8590#define SET_ENTRY(na, v) do { \
8591 GC_ASSERT(n <= RB_GC_OBJECT_METADATA_ENTRY_COUNT); \
8592 object_metadata_entries[n].name = names.ID_##na; \
8593 object_metadata_entries[n].val = v; \
8594 n++; \
8595} while (0)
8596
8597 if (!RVALUE_WB_UNPROTECTED(objspace, obj)) SET_ENTRY(wb_protected, Qtrue);
8598 SET_ENTRY(age, INT2FIX(RVALUE_AGE_GET(obj)));
8599 if (RVALUE_OLD_P(objspace, obj)) SET_ENTRY(old, Qtrue);
8600 if (RVALUE_UNCOLLECTIBLE(objspace, obj)) SET_ENTRY(uncollectible, Qtrue);
8601 if (RVALUE_MARKING(objspace, obj)) SET_ENTRY(marking, Qtrue);
8602 if (RVALUE_MARKED(objspace, obj)) SET_ENTRY(marked, Qtrue);
8603 if (RVALUE_PINNED(objspace, obj)) SET_ENTRY(pinned, Qtrue);
8604 if (RVALUE_REMEMBERED(objspace, obj)) SET_ENTRY(remembered, Qtrue);
8605 if (rb_obj_id_p(obj)) SET_ENTRY(object_id, rb_obj_id(obj));
8606 if (FL_TEST(obj, FL_SHAREABLE)) SET_ENTRY(shareable, Qtrue);
8607
8608 object_metadata_entries[n].name = 0;
8609 object_metadata_entries[n].val = 0;
8610#undef SET_ENTRY
8611
8612 return object_metadata_entries;
8613}
8614
8615void *
8616rb_gc_impl_ractor_cache_alloc(void *objspace_ptr, void *ractor)
8617{
8618 /* No cache needed: allocation happens in a per-Ractor objspace. */
8619 return NULL;
8620}
8621
8622void
8623rb_gc_impl_ractor_cache_free(void *objspace_ptr, void *cache)
8624{
8625 GC_ASSERT(cache == NULL);
8626}
8627
8628/* The terminating Ractor's final local GC, on its own thread: roots are minimal, so the
8629 * mark is tiny, and it reclaims what the joining side would otherwise inherit. Never
8630 * promotes to a global GC (that would STW on every Ractor death); empty pages go
8631 * straight back to the page pool. */
8632/* Finalize the zombies whose cleanup is pure C (a dfree, no Ruby-level finalizer);
8633 * the caller has no Ruby execution context any more, so zombies with a Ruby
8634 * finalizer stay deferred and travel to the inheritor as before. Returns whether
8635 * anything was finalized (those pages then need one more sweep to detach). */
8636static bool
8637finalize_deferred_dfree_only(rb_objspace_t *objspace)
8638{
8639 VALUE dfree_only = 0;
8640 VALUE zombie = RUBY_ATOMIC_VALUE_EXCHANGE(heap_pages_deferred_final, 0);
8641 while (zombie) {
8642 rb_asan_unpoison_object(zombie, false);
8643 VALUE next = RZOMBIE(zombie)->next;
8644 if (FL_TEST_RAW(zombie, FL_FINALIZE)) {
8645 /* re-defer, with the same push as rb_gc_impl_make_zombie */
8646 VALUE prev2, next2 = heap_pages_deferred_final;
8647 do {
8648 RZOMBIE(zombie)->next = prev2 = next2;
8649 next2 = RUBY_ATOMIC_VALUE_CAS(heap_pages_deferred_final, prev2, zombie);
8650 } while (next2 != prev2);
8651 rb_asan_poison_object(zombie);
8652 }
8653 else {
8654 RZOMBIE(zombie)->next = dfree_only;
8655 dfree_only = zombie;
8656 }
8657 zombie = next;
8658 }
8659 if (dfree_only) finalize_list(objspace, dfree_only);
8660 bool did = dfree_only != 0;
8661
8662 gc_tdata_unsafe_free_publish(objspace);
8663 return did;
8664}
8665
8666void
8667rb_gc_impl_objspace_retire_gc(void *objspace_ptr)
8668{
8669 rb_objspace_t *objspace = objspace_ptr;
8670
8671 /* The dying thread's stack is already torn down here, so the root scan must skip
8672 * its machine context (rb_gc_mark_roots). */
8673 objspace->flags.during_postmortem = 1;
8674
8675 gc_rest(objspace);
8676 gc_start(objspace, GPR_FLAG_FULL_MARK | GPR_FLAG_IMMEDIATE_MARK | GPR_FLAG_IMMEDIATE_SWEEP);
8677
8678 /* The sweep above turned this heap's dead IO and the like into deferred zombies
8679 * (the per-Ractor stdio holds a page per Ractor otherwise); finalize the C-only
8680 * ones here and re-sweep the nearly-empty heap so their pages detach as empty. */
8681 if (finalize_deferred_dfree_only(objspace)) {
8682 gc_start(objspace, GPR_FLAG_FULL_MARK | GPR_FLAG_IMMEDIATE_MARK | GPR_FLAG_IMMEDIATE_SWEEP);
8683 }
8684
8685 heap_pages_freeable_pages = objspace->empty_pages_count;
8686 heap_pages_free_unused_pages(objspace);
8687
8688 objspace->flags.during_postmortem = 0;
8689}
8690
8691bool
8692rb_gc_impl_during_postmortem_p(void *objspace_ptr)
8693{
8694 rb_objspace_t *objspace = objspace_ptr;
8695 return objspace->flags.during_postmortem != 0;
8696}
8697
8698static void
8699heap_ready_to_gc(rb_objspace_t *objspace, rb_heap_t *heap)
8700{
8701 if (!heap->free_pages) {
8702 if (!heap_page_allocate_and_initialize(objspace, heap)) {
8703 objspace->heap_pages.allocatable_bytes = HEAP_PAGE_SIZE;
8704 heap_page_allocate_and_initialize(objspace, heap);
8705 }
8706 }
8707}
8708
8709static int
8710ready_to_gc(rb_objspace_t *objspace)
8711{
8712 if ((!objspace->flags.during_postmortem && rb_gc_gc_disabled_global_p()) || dont_gc_val() || during_gc) {
8713 for (int i = 0; i < HEAP_COUNT; i++) {
8714 rb_heap_t *heap = &heaps[i];
8715 heap_ready_to_gc(objspace, heap);
8716 }
8717 return FALSE;
8718 }
8719 else {
8720 return TRUE;
8721 }
8722}
8723
8724static void
8725gc_reset_malloc_info(rb_objspace_t *objspace, bool full_mark)
8726{
8727 gc_prof_set_malloc_info(objspace);
8728 {
8729 int64_t inc = gc_malloc_counters_increase(objspace, &objspace->malloc_counters.counters);
8730 gc_malloc_counters_snapshot(objspace, &objspace->malloc_counters.counters);
8731 size_t old_limit = malloc_limit;
8732
8733 /* A net-negative `inc` (more freed than malloc'd since last GC) is
8734 * treated the same as "allocated less than malloc_limit".
8735 * This matches what we were doing pre-monotonic counters, but is it right? */
8736 if (inc > 0 && (size_t)inc > malloc_limit) {
8737 malloc_limit = (size_t)((size_t)inc * gc_params.malloc_limit_growth_factor);
8738 if (malloc_limit > gc_params.malloc_limit_max) {
8739 malloc_limit = gc_params.malloc_limit_max;
8740 }
8741 }
8742 else {
8743 malloc_limit = (size_t)(malloc_limit * 0.98); /* magic number */
8744 if (malloc_limit < gc_params.malloc_limit_min) {
8745 malloc_limit = gc_params.malloc_limit_min;
8746 }
8747 }
8748
8749 if (0) {
8750 if (old_limit != malloc_limit) {
8751 fprintf(stderr, "[%"PRIuSIZE"] malloc_limit: %"PRIuSIZE" -> %"PRIuSIZE"\n",
8752 rb_gc_count(), old_limit, malloc_limit);
8753 }
8754 else {
8755 fprintf(stderr, "[%"PRIuSIZE"] malloc_limit: not changed (%"PRIuSIZE")\n",
8756 rb_gc_count(), malloc_limit);
8757 }
8758 }
8759 }
8760
8761 /* reset oldmalloc info */
8762#if RGENGC_ESTIMATE_OLDMALLOC
8763 if (!full_mark) {
8764 /* No full snapshot on minor GC: oldmalloc_increase accumulates across
8765 * minors and resets at major GC. (gc_sweep_finish still advances the
8766 * free baseline after every sweep.) */
8767 int64_t oldmalloc_increase = gc_malloc_counters_increase(objspace, &objspace->malloc_counters.oldcounters);
8768 if (oldmalloc_increase > 0 &&
8769 (uint64_t)oldmalloc_increase > objspace->rgengc.oldmalloc_increase_limit) {
8770 gc_needs_major_flags |= GPR_FLAG_MAJOR_BY_OLDMALLOC;
8771 objspace->rgengc.oldmalloc_increase_limit =
8772 (size_t)(objspace->rgengc.oldmalloc_increase_limit * gc_params.oldmalloc_limit_growth_factor);
8773
8774 if (objspace->rgengc.oldmalloc_increase_limit > gc_params.oldmalloc_limit_max) {
8775 objspace->rgengc.oldmalloc_increase_limit = gc_params.oldmalloc_limit_max;
8776 }
8777 }
8778
8779 if (0) fprintf(stderr, "%"PRIdSIZE"\t%d\t%"PRId64"\t%"PRIuSIZE"\t%"PRIdSIZE"\n",
8780 rb_gc_count(),
8781 gc_needs_major_flags,
8782 oldmalloc_increase,
8783 objspace->rgengc.oldmalloc_increase_limit,
8784 gc_params.oldmalloc_limit_max);
8785 }
8786 else {
8787 gc_malloc_counters_snapshot(objspace, &objspace->malloc_counters.oldcounters);
8788
8789 if ((objspace->profile.latest_gc_info & GPR_FLAG_MAJOR_BY_OLDMALLOC) == 0) {
8790 objspace->rgengc.oldmalloc_increase_limit =
8791 (size_t)(objspace->rgengc.oldmalloc_increase_limit / ((gc_params.oldmalloc_limit_growth_factor - 1)/10 + 1));
8792 if (objspace->rgengc.oldmalloc_increase_limit < gc_params.oldmalloc_limit_min) {
8793 objspace->rgengc.oldmalloc_increase_limit = gc_params.oldmalloc_limit_min;
8794 }
8795 }
8796 }
8797#endif
8798}
8799
8800/* What a collection records about itself before it runs. A global collection reports the
8801 * driver's objspace, so it comes through here too. */
8802static void
8803gc_start_record(rb_objspace_t *objspace, unsigned int reason, bool full_mark)
8804{
8805 objspace->profile.latest_gc_info = reason;
8806#if GC_PROFILE_MORE_DETAIL
8807 objspace->profile.total_allocated_objects_at_gc_start = total_allocated_objects(objspace);
8808 objspace->profile.heap_used_at_gc_start = rb_darray_size(objspace->heap_pages.sorted);
8809#endif
8810 objspace->profile.weak_references_count = 0;
8811 gc_prof_setup_new_record(objspace, reason);
8812 gc_reset_malloc_info(objspace, full_mark);
8813}
8814
8815static bool gc_start_global(rb_objspace_t *driver, unsigned int reason, bool compact, bool allow_skip);
8816
8817/* Decide whether this collection has to be global. A local GC can reclaim neither
8818 * shareable objects nor zombie objspaces, so once those grow past their limits only a
8819 * global GC makes progress. All inputs belong to this objspace. */
8820static bool
8821gc_need_global_p(rb_objspace_t *objspace)
8822{
8823 if (rb_gc_single_objspace_p()) return false;
8824 /* A Ractor's death must not stop the world, so the retire GC stays local. */
8825 if (objspace->flags.during_postmortem) return false;
8826 if (objspace->shareable_objects > objspace->shareable_objects_limit) return true;
8827 /* A zombie's garbage only a global cycle reclaims, but what survived the last one
8828 * is live data, so retrigger only once TRIGGER more pages accumulate on top of it.
8829 * Otherwise one live-heavy unjoined zombie turns every GC stop-the-world forever. */
8830 {
8831 size_t zp = rb_gc_vm_zombie_total_pages();
8832 size_t base = global_objspace->zombie_pages_survivors < zp ? global_objspace->zombie_pages_survivors : zp;
8833 if (zp - base >= ZOMBIE_PAGES_TRIGGER) return true;
8834 }
8835 return false;
8836}
8837
8838static int
8839garbage_collect(rb_objspace_t *objspace, unsigned int reason)
8840{
8841 int ret;
8842
8843#if GC_PROFILE_MORE_DETAIL
8844 objspace->profile.prepare_time = getrusage_time();
8845#endif
8846
8847 gc_rest(objspace);
8848
8849#if GC_PROFILE_MORE_DETAIL
8850 objspace->profile.prepare_time = getrusage_time() - objspace->profile.prepare_time;
8851#endif
8852
8853 ret = gc_start(objspace, reason);
8854
8855 return ret;
8856}
8857
8858static int
8859gc_start(rb_objspace_t *objspace, unsigned int reason)
8860{
8861 unsigned int do_full_mark = !!(reason & GPR_FLAG_FULL_MARK);
8862
8863 if (!rb_darray_size(objspace->heap_pages.sorted)) return TRUE; /* heap is not ready */
8864 if (!(reason & GPR_FLAG_METHOD) && !ready_to_gc(objspace)) return TRUE; /* GC is not allowed */
8865
8866 /* An explicit GC.start(global: true) never gets here: rb_gc_impl_start has already decided from
8867 * the `global` keyword, and GPR_FLAG_METHOD keeps `global: false` from being promoted back. */
8868 if (!(reason & GPR_FLAG_METHOD) && gc_need_global_p(objspace)) {
8869 /* A global GC is always a major, so autocompact applies. */
8870 if (gc_start_global(objspace, reason, ruby_enable_autocompact, true)) {
8871 return TRUE;
8872 }
8873 /* Fall through to a local GC */
8874 }
8875
8876 rb_gc_initialize_vm_context(&objspace->vm_context);
8877
8878 GC_ASSERT(gc_mode(objspace) == gc_mode_none, "gc_mode is %s\n", gc_mode_name(gc_mode(objspace)));
8879 GC_ASSERT(!is_lazy_sweeping(objspace));
8880 GC_ASSERT(!is_incremental_marking(objspace));
8881
8882 /* reason may be clobbered, later, so keep set immediate_sweep here */
8883 objspace->flags.immediate_sweep = !!(reason & GPR_FLAG_IMMEDIATE_SWEEP);
8884
8885 if (ruby_gc_stressful) {
8886 int flag = FIXNUM_P(ruby_gc_stress_mode) ? FIX2INT(ruby_gc_stress_mode) : 0;
8887
8888 if ((flag & (1 << gc_stress_no_major)) == 0) {
8889 do_full_mark = TRUE;
8890 }
8891
8892 objspace->flags.immediate_sweep = !(flag & (1<<gc_stress_no_immediate_sweep));
8893 }
8894
8895 if (gc_needs_major_flags) {
8896 reason |= gc_needs_major_flags;
8897 do_full_mark = TRUE;
8898 }
8899
8900 /* if major gc has been disabled, never do a full mark */
8901 if (!gc_config_full_mark_val) {
8902 do_full_mark = FALSE;
8903 }
8904 gc_needs_major_flags = GPR_FLAG_NONE;
8905
8906 if (do_full_mark && (reason & GPR_FLAG_MAJOR_MASK) == 0) {
8907 reason |= GPR_FLAG_MAJOR_BY_FORCE; /* GC by CAPI, METHOD, and so on. */
8908 }
8909
8910 if (objspace->flags.dont_incremental ||
8911 reason & GPR_FLAG_IMMEDIATE_MARK ||
8912 ruby_gc_stressful) {
8913 objspace->flags.during_incremental_marking = FALSE;
8914 }
8915 else {
8916 objspace->flags.during_incremental_marking = do_full_mark;
8917 }
8918
8919 /* Compaction on the local GC path (autocompact) runs only with a single objspace:
8920 * without the stop-the-world barrier, moving objects would break cross-objspace
8921 * references. With multiple objspaces GC.compact and autocompact go through the
8922 * compacting global GC instead (rb_gc_impl_start, or the promotion above). */
8923 if (do_full_mark && ruby_enable_autocompact && rb_gc_single_objspace_p()) {
8924 objspace->flags.during_compacting = TRUE;
8925#if RGENGC_CHECK_MODE
8926 objspace->rcompactor.compare_func = ruby_autocompact_compare_func;
8927#endif
8928 }
8929 else {
8930 objspace->flags.during_compacting = !!(reason & GPR_FLAG_COMPACT);
8931 if (objspace->flags.during_compacting && !rb_gc_single_objspace_p()) {
8932 // compaction is currently global GC only with more than 1 running Ractor
8933 objspace->flags.during_compacting = FALSE;
8934 }
8935 }
8936
8937 if (!GC_ENABLE_LAZY_SWEEP || objspace->flags.dont_incremental) {
8938 objspace->flags.immediate_sweep = TRUE;
8939 }
8940
8941 if (objspace->flags.immediate_sweep) reason |= GPR_FLAG_IMMEDIATE_SWEEP;
8942
8943 /* Enter after during_compacting is decided */
8944 unsigned int lock_lev;
8945 gc_enter(objspace, gc_enter_event_start, &lock_lev);
8946
8947 gc_report(1, objspace, "gc_start(reason: %x) => %u, %d, %d\n",
8948 reason,
8949 do_full_mark, !is_incremental_marking(objspace), objspace->flags.immediate_sweep);
8950
8951 RB_DEBUG_COUNTER_INC(gc_count);
8952
8953 if (reason & GPR_FLAG_MAJOR_MASK) {
8954 (void)RB_DEBUG_COUNTER_INC_IF(gc_major_nofree, reason & GPR_FLAG_MAJOR_BY_NOFREE);
8955 (void)RB_DEBUG_COUNTER_INC_IF(gc_major_oldgen, reason & GPR_FLAG_MAJOR_BY_OLDGEN);
8956 (void)RB_DEBUG_COUNTER_INC_IF(gc_major_shady, reason & GPR_FLAG_MAJOR_BY_SHADY);
8957 (void)RB_DEBUG_COUNTER_INC_IF(gc_major_force, reason & GPR_FLAG_MAJOR_BY_FORCE);
8958#if RGENGC_ESTIMATE_OLDMALLOC
8959 (void)RB_DEBUG_COUNTER_INC_IF(gc_major_oldmalloc, reason & GPR_FLAG_MAJOR_BY_OLDMALLOC);
8960#endif
8961 }
8962 else {
8963 (void)RB_DEBUG_COUNTER_INC_IF(gc_minor_newobj, reason & GPR_FLAG_NEWOBJ);
8964 (void)RB_DEBUG_COUNTER_INC_IF(gc_minor_malloc, reason & GPR_FLAG_MALLOC);
8965 (void)RB_DEBUG_COUNTER_INC_IF(gc_minor_method, reason & GPR_FLAG_METHOD);
8966 (void)RB_DEBUG_COUNTER_INC_IF(gc_minor_capi, reason & GPR_FLAG_CAPI);
8967 (void)RB_DEBUG_COUNTER_INC_IF(gc_minor_stress, reason & GPR_FLAG_STRESS);
8968 }
8969
8970 objspace->profile.count++;
8971 gc_start_record(objspace, reason, do_full_mark);
8972
8973 gc_event_hook(objspace, RUBY_INTERNAL_EVENT_GC_START);
8974
8975 GC_ASSERT(during_gc);
8976
8977 gc_prof_timer_start(objspace);
8978 {
8979 if (gc_marks(objspace, do_full_mark)) {
8980 gc_sweep(objspace);
8981 }
8982 }
8983 gc_prof_timer_stop(objspace);
8984
8985 gc_exit(objspace, gc_enter_event_start, &lock_lev);
8986
8987 /* Verify after the GC, at a real safepoint with during_gc cleared: mid-GC it would
8988 * call rb_objspace_reachable_objects_from, whose barrier VM lock would join another
8989 * Ractor's global GC barrier and let it collect on this half-collected heap. */
8990#if RGENGC_CHECK_MODE >= 2
8991 gc_verify_internal_consistency(objspace);
8992#endif
8993 return TRUE;
8994}
8995
8996static void
8997gc_rest(rb_objspace_t *objspace)
8998{
8999 if (is_incremental_marking(objspace) || is_lazy_sweeping(objspace)) {
9000 unsigned int lock_lev;
9001 gc_enter(objspace, gc_enter_event_rest, &lock_lev);
9002
9003 if (is_incremental_marking(objspace)) {
9004 gc_marking_enter(objspace);
9005 gc_marks_rest(objspace);
9006 gc_marking_exit(objspace);
9007
9008 gc_sweep(objspace);
9009 }
9010
9011 if (is_lazy_sweeping(objspace)) {
9012 gc_sweeping_enter(objspace);
9013 gc_sweep_rest(objspace);
9014 gc_sweeping_exit(objspace);
9015 }
9016
9017 gc_exit(objspace, gc_enter_event_rest, &lock_lev);
9018
9019 if (RGENGC_CHECK_MODE >= 2) gc_verify_internal_consistency(objspace); /* after GC, see gc_start */
9020 }
9021}
9022
9025 unsigned int reason;
9026};
9027
9028static void
9029gc_current_status_fill(rb_objspace_t *objspace, char *buff)
9030{
9031 int i = 0;
9032 if (is_marking(objspace)) {
9033 buff[i++] = 'M';
9034 if (is_full_marking(objspace)) buff[i++] = 'F';
9035 if (is_incremental_marking(objspace)) buff[i++] = 'I';
9036 }
9037 else if (is_sweeping(objspace)) {
9038 buff[i++] = 'S';
9039 if (is_lazy_sweeping(objspace)) buff[i++] = 'L';
9040 }
9041 else {
9042 buff[i++] = 'N';
9043 }
9044 buff[i] = '\0';
9045}
9046
9047static const char *
9048gc_current_status(rb_objspace_t *objspace)
9049{
9050 static char buff[0x10];
9051 gc_current_status_fill(objspace, buff);
9052 return buff;
9053}
9054
9055#if PRINT_ENTER_EXIT_TICK
9056
9057static tick_t last_exit_tick;
9058static tick_t enter_tick;
9059static int enter_count = 0;
9060static char last_gc_status[0x10];
9061
9062static inline void
9063gc_record(rb_objspace_t *objspace, int direction, const char *event)
9064{
9065 if (direction == 0) { /* enter */
9066 enter_count++;
9067 enter_tick = tick();
9068 gc_current_status_fill(objspace, last_gc_status);
9069 }
9070 else { /* exit */
9071 tick_t exit_tick = tick();
9072 char current_gc_status[0x10];
9073 gc_current_status_fill(objspace, current_gc_status);
9074#if 1
9075 /* [last mutator time] [gc time] [event] */
9076 fprintf(stderr, "%"PRItick"\t%"PRItick"\t%s\t[%s->%s|%c]\n",
9077 enter_tick - last_exit_tick,
9078 exit_tick - enter_tick,
9079 event,
9080 last_gc_status, current_gc_status,
9081 (objspace->profile.latest_gc_info & GPR_FLAG_MAJOR_MASK) ? '+' : '-');
9082 last_exit_tick = exit_tick;
9083#else
9084 /* [enter_tick] [gc time] [event] */
9085 fprintf(stderr, "%"PRItick"\t%"PRItick"\t%s\t[%s->%s|%c]\n",
9086 enter_tick,
9087 exit_tick - enter_tick,
9088 event,
9089 last_gc_status, current_gc_status,
9090 (objspace->profile.latest_gc_info & GPR_FLAG_MAJOR_MASK) ? '+' : '-');
9091#endif
9092 }
9093}
9094#else /* PRINT_ENTER_EXIT_TICK */
9095static inline void
9096gc_record(rb_objspace_t *objspace, int direction, const char *event)
9097{
9098 /* null */
9099}
9100#endif /* PRINT_ENTER_EXIT_TICK */
9101
9102static const char *
9103gc_enter_event_cstr(enum gc_enter_event event)
9104{
9105 switch (event) {
9106 case gc_enter_event_start: return "start";
9107 case gc_enter_event_continue: return "continue";
9108 case gc_enter_event_rest: return "rest";
9109 case gc_enter_event_finalizer: return "finalizer";
9110 case gc_enter_event_global: return "global";
9111 case gc_enter_event_global_auto: return "global_auto";
9112 }
9113 return NULL;
9114}
9115
9116static void
9117gc_enter_count(enum gc_enter_event event)
9118{
9119 switch (event) {
9120 case gc_enter_event_start: RB_DEBUG_COUNTER_INC(gc_enter_start); break;
9121 case gc_enter_event_continue: RB_DEBUG_COUNTER_INC(gc_enter_continue); break;
9122 case gc_enter_event_rest: RB_DEBUG_COUNTER_INC(gc_enter_rest); break;
9123 case gc_enter_event_finalizer: RB_DEBUG_COUNTER_INC(gc_enter_finalizer); break;
9124 case gc_enter_event_global: RB_DEBUG_COUNTER_INC(gc_enter_start); break;
9125 case gc_enter_event_global_auto: RB_DEBUG_COUNTER_INC(gc_enter_start); break;
9126 }
9127}
9128
9129static bool current_process_time(struct timespec *ts);
9130
9131/* A gc phase must be timed on the collecting thread's own cpu. A local gc runs
9132 * while the other ractors keep going, and process cpu time counts their work as
9133 * gc: with eight busy ractors the same ten collections were reported as 131ms
9134 * instead of 3ms, more than the wall clock they ran in. The kernel also answers
9135 * this one without walking every thread in the process. */
9136static bool
9137current_thread_time(struct timespec *ts)
9138{
9139#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_THREAD_CPUTIME_ID)
9140 {
9141 static int try_clock_gettime = 1;
9142 if (try_clock_gettime) {
9143 if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, ts) == 0) {
9144 return true;
9145 }
9146 else {
9147 try_clock_gettime = 0;
9148 }
9149 }
9150 }
9151#endif
9152 return current_process_time(ts);
9153}
9154
9155static void
9156gc_clock_start(struct timespec *ts)
9157{
9158 if (!current_thread_time(ts)) {
9159 ts->tv_sec = 0;
9160 ts->tv_nsec = 0;
9161 }
9162}
9163
9164static unsigned long long
9165gc_clock_end(struct timespec *ts)
9166{
9167 struct timespec end_time;
9168
9169 if ((ts->tv_sec > 0 || ts->tv_nsec > 0) &&
9170 current_thread_time(&end_time) &&
9171 end_time.tv_sec >= ts->tv_sec) {
9172 return (unsigned long long)(end_time.tv_sec - ts->tv_sec) * (1000 * 1000 * 1000) +
9173 (end_time.tv_nsec - ts->tv_nsec);
9174 }
9175
9176 return 0;
9177}
9178
9179static void
9180gc_process_stat_after_fork_i(void *objspace_ptr, void *data)
9181{
9182 rb_objspace_t *objspace = objspace_ptr;
9183 rb_native_mutex_initialize(&objspace->process_stat.lock);
9184}
9185
9186static inline bool
9187gc_local_gc_holds_vm_lock(void)
9188{
9189 return rb_gc_single_objspace_p();
9190}
9191
9192static inline bool
9193gc_enter(rb_objspace_t *objspace, enum gc_enter_event event, unsigned int *lock_lev)
9194{
9195 /*
9196 * NOTE: The GC must never take the barrier VM lock from inside itself: the waiter could
9197 * join a pending barrier mid-collection and expose its half-collected heap to the global
9198 * GC. A no-barrier lock is safe. Other shared structures the GC paths touch use their own
9199 * native mutexes or the page-pool lock. */
9200 *lock_lev = 0;
9201
9202 RUBY_DTRACE_GC_HOOK(ENTER, event);
9203
9204 if (objspace->profile.run) {
9205 switch (event) {
9206 case gc_enter_event_start:
9207 case gc_enter_event_continue:
9208 case gc_enter_event_rest:
9209 case gc_enter_event_global:
9210 case gc_enter_event_global_auto:
9211 /* A global GC is the longest pause the process takes, so it is the last thing
9212 * the profiler may leave unmeasured. The switch below stops the world for it,
9213 * which is exactly the interval gc_stop_time is meant to name, so start the
9214 * clock here like a local collection does. */
9215 objspace->profile.gc_pause_start_time = rb_hrtime_now();
9216 break;
9217 case gc_enter_event_finalizer:
9218 break;
9219 }
9220 }
9221 switch (event) {
9222 case gc_enter_event_global:
9223 *lock_lev = RB_GC_VM_LOCK();
9224 // stop other ractors
9225 rb_gc_vm_barrier();
9226 break;
9227 case gc_enter_event_global_auto:
9228 *lock_lev = RB_GC_VM_LOCK();
9229 if (!gc_need_global_p(objspace)) {
9230 RB_GC_VM_UNLOCK(*lock_lev);
9231 *lock_lev = 0;
9232 objspace->profile.gc_pause_start_time = 0;
9233 return false;
9234 }
9235 rb_gc_vm_barrier();
9236 break;
9237 case gc_enter_event_finalizer:
9238 /* Shutdown finalizers read VM-global tables (fstring, symbol) and free T_DATA that
9239 * is not thread-safe, so take the no-barrier VM lock. */
9240 *lock_lev = RB_GC_VM_LOCK_NO_BARRIER();
9241 break;
9242 default:
9243 objspace->flags.gc_lock_barrier = FALSE;
9244 if (objspace->flags.during_compacting) {
9245 /* Compaction relocates objects and rewrites every Ractor's JIT and global
9246 * references, so it stops the world with a barrier VM lock. rb_gc_vm_barrier is
9247 * a reentrant no-op with a single Ractor, so an inner barrier request during the
9248 * move folds into this one and gc_exit ends it. */
9249 *lock_lev = RB_GC_VM_LOCK();
9250 rb_gc_vm_barrier();
9251 objspace->flags.gc_lock_barrier = TRUE;
9252 }
9253 else if (gc_local_gc_holds_vm_lock()) {
9254 *lock_lev = RB_GC_VM_LOCK_NO_BARRIER();
9255 }
9256 break;
9257 }
9258
9259 if (objspace->profile.gc_pause_start_time) {
9260 objspace->profile.gc_stw_start_time = rb_hrtime_now();
9261 objspace->profile.gc_stop_time = rb_hrtime_sub(
9262 objspace->profile.gc_stw_start_time,
9263 objspace->profile.gc_pause_start_time);
9264 }
9265
9266 gc_enter_count(event);
9267 if (RB_UNLIKELY(during_gc != 0)) rb_bug("during_gc != 0");
9268 if (RGENGC_CHECK_MODE >= 3) gc_verify_internal_consistency(objspace);
9269
9270 during_gc = TRUE;
9271 RUBY_DEBUG_LOG("%s (%s)",gc_enter_event_cstr(event), gc_current_status(objspace));
9272 gc_report(1, objspace, "gc_enter: %s [%s]\n", gc_enter_event_cstr(event), gc_current_status(objspace));
9273 gc_record(objspace, 0, gc_enter_event_cstr(event));
9274
9275 gc_event_hook(objspace, RUBY_INTERNAL_EVENT_GC_ENTER);
9276 return true;
9277}
9278
9279static inline void
9280gc_exit(rb_objspace_t *objspace, enum gc_enter_event event, unsigned int *lock_lev)
9281{
9282 GC_ASSERT(during_gc != 0);
9283
9284 RUBY_DTRACE_GC_HOOK(EXIT, event);
9285
9286 gc_event_hook(objspace, RUBY_INTERNAL_EVENT_GC_EXIT);
9287
9288 if (objspace->profile.gc_pause_start_time) {
9289 if (gc_prof_enabled(objspace)) {
9290 rb_hrtime_t now = rb_hrtime_now();
9291 gc_profile_record *record = gc_prof_record(objspace);
9292 record->gc_pause_time = rb_hrtime_add(record->gc_pause_time,
9293 rb_hrtime_sub(now, objspace->profile.gc_pause_start_time));
9294 record->gc_stop_time = rb_hrtime_add(record->gc_stop_time,
9295 objspace->profile.gc_stop_time);
9296 record->gc_stw_time = rb_hrtime_add(record->gc_stw_time,
9297 rb_hrtime_sub(now, objspace->profile.gc_stw_start_time));
9298 }
9299 objspace->profile.gc_pause_start_time = 0;
9300 objspace->profile.gc_stw_start_time = 0;
9301 objspace->profile.gc_stop_time = 0;
9302 }
9303
9304 gc_record(objspace, 1, gc_enter_event_cstr(event));
9305 RUBY_DEBUG_LOG("%s (%s)", gc_enter_event_cstr(event), gc_current_status(objspace));
9306 gc_report(1, objspace, "gc_exit: %s [%s]\n", gc_enter_event_cstr(event), gc_current_status(objspace));
9307 during_gc = FALSE;
9308 gc_process_stat_publish(objspace);
9309
9310 switch (event) {
9311 case gc_enter_event_global:
9312 case gc_enter_event_global_auto:
9313 RB_GC_VM_UNLOCK(*lock_lev);
9314 break;
9315 case gc_enter_event_finalizer:
9316 RB_GC_VM_UNLOCK_NO_BARRIER(*lock_lev);
9317 break;
9318 default:
9319 if (*lock_lev != 0) {
9320 if (objspace->flags.gc_lock_barrier) {
9321 objspace->flags.gc_lock_barrier = FALSE;
9322 RB_GC_VM_UNLOCK(*lock_lev);
9323 }
9324 else {
9325 RB_GC_VM_UNLOCK_NO_BARRIER(*lock_lev);
9326 }
9327 }
9328 break;
9329 }
9330}
9331
9332#ifndef MEASURE_GC
9333#define MEASURE_GC (objspace->flags.measure_gc)
9334#endif
9335
9336static void
9337gc_marking_enter(rb_objspace_t *objspace)
9338{
9339 GC_ASSERT(during_gc != 0);
9340
9341 gc_prof_mark_timer_start(objspace);
9342
9343 if (gc_prof_enabled(objspace)) {
9344 objspace->profile.gc_mark_phase_wall_start_time = rb_hrtime_now();
9345 }
9346
9347 if (MEASURE_GC) {
9348 gc_clock_start(&objspace->profile.marking_start_time);
9349 }
9350
9351 rb_gc_initialize_vm_context(&objspace->vm_context);
9352}
9353
9354static void
9355gc_marking_exit(rb_objspace_t *objspace)
9356{
9357 GC_ASSERT(during_gc != 0);
9358
9359 if (MEASURE_GC) {
9360 objspace->profile.marking_time_ns += gc_clock_end(&objspace->profile.marking_start_time);
9361 }
9362
9363 if (gc_prof_enabled(objspace)) {
9364 gc_profile_record *record = gc_prof_record(objspace);
9365 record->gc_mark_wall_time = rb_hrtime_add(record->gc_mark_wall_time,
9366 elapsed_hrtime_from(objspace->profile.gc_mark_phase_wall_start_time));
9367 }
9368
9369 gc_prof_mark_timer_stop(objspace);
9370}
9371
9372static void
9373gc_sweeping_cpu_enter(rb_objspace_t *objspace)
9374{
9375 if (MEASURE_GC) {
9376 gc_clock_start(&objspace->profile.sweeping_start_time);
9377 }
9378}
9379
9380static void
9381gc_sweeping_cpu_exit(rb_objspace_t *objspace)
9382{
9383 if (MEASURE_GC) {
9384 objspace->profile.sweeping_time_ns += gc_clock_end(&objspace->profile.sweeping_start_time);
9385 }
9386}
9387
9388static void
9389gc_sweeping_enter(rb_objspace_t *objspace)
9390{
9391 GC_ASSERT(during_gc != 0);
9392
9393 if (gc_prof_enabled(objspace)) {
9394 objspace->profile.gc_sweep_phase_wall_start_time = rb_hrtime_now();
9395 objspace->profile.gc_sweep_excluded_wall_time = 0;
9396 }
9397
9398 gc_sweeping_cpu_enter(objspace);
9399
9400 rb_gc_initialize_vm_context(&objspace->vm_context);
9401}
9402
9403static void
9404gc_sweeping_exit(rb_objspace_t *objspace)
9405{
9406 GC_ASSERT(during_gc != 0);
9407
9408 gc_sweeping_cpu_exit(objspace);
9409
9410 if (gc_prof_enabled(objspace)) {
9411 rb_hrtime_t sweep_wall_time = elapsed_hrtime_from(objspace->profile.gc_sweep_phase_wall_start_time);
9412 gc_profile_record *record = gc_prof_record(objspace);
9413 sweep_wall_time = rb_hrtime_sub(sweep_wall_time,
9414 objspace->profile.gc_sweep_excluded_wall_time);
9415 record->gc_sweep_wall_time = rb_hrtime_add(record->gc_sweep_wall_time,
9416 sweep_wall_time);
9417 objspace->profile.gc_sweep_excluded_wall_time = 0;
9418 }
9419}
9420
9421static void *
9422gc_with_gvl(void *ptr)
9423{
9424 struct objspace_and_reason *oar = (struct objspace_and_reason *)ptr;
9425 return (void *)(VALUE)garbage_collect(oar->objspace, oar->reason);
9426}
9427
9428int ruby_thread_has_gvl_p(void);
9429
9430static int
9431garbage_collect_with_gvl(rb_objspace_t *objspace, unsigned int reason)
9432{
9433 if (rb_gc_gc_disabled_global_p() || dont_gc_val()) {
9434 return TRUE;
9435 }
9436 else if (!ruby_native_thread_p()) {
9437 return TRUE;
9438 }
9439 else if (!ruby_thread_has_gvl_p()) {
9440 void *ret;
9441 struct objspace_and_reason oar;
9442 oar.objspace = objspace;
9443 oar.reason = reason;
9444 ret = rb_thread_call_with_gvl(gc_with_gvl, (void *)&oar);
9445
9446 return !!ret;
9447 }
9448 else {
9449 return garbage_collect(objspace, reason);
9450 }
9451}
9452
9453static int
9454gc_set_candidate_object_i(void *vstart, void *vend, size_t stride, void *data)
9455{
9457
9458 VALUE v = (VALUE)vstart;
9459 for (; v != (VALUE)vend; v += stride) {
9460 asan_unpoisoning_object(v) {
9461 switch (BUILTIN_TYPE(v)) {
9462 case T_NONE:
9463 case T_ZOMBIE:
9464 break;
9465 default:
9466 rb_gc_prepare_heap_process_object(v);
9467 if (!RVALUE_OLD_P(objspace, v) && !RVALUE_WB_UNPROTECTED(objspace, v)) {
9468 RVALUE_AGE_SET_CANDIDATE(objspace, v);
9469 }
9470 }
9471 }
9472 }
9473
9474 return 0;
9475}
9476
9477bool
9478rb_gc_impl_multi_objspace_p(void)
9479{
9480 return true;
9481}
9482
9483bool
9484rb_gc_impl_during_global_gc_p(void *objspace_ptr)
9485{
9486 rb_objspace_t *objspace = objspace_ptr;
9487 return objspace->flags.during_global_gc != 0;
9488}
9489
9490bool
9491rb_gc_impl_obj_foreign_p(void *objspace_ptr, VALUE obj)
9492{
9493 return gc_foreign_object_p(objspace_ptr, obj);
9494}
9495
9496
9497/* Whether obj is recorded as an unshareable object referenced from a shareable one. For
9498 * the verifier: a shareable -> unshareable edge is only accepted if the write barrier
9499 * recorded it here. */
9500bool
9501rb_gc_impl_shref_marked_p(void *objspace_ptr, VALUE obj)
9502{
9503 return MARKED_IN_BITMAP(GET_HEAP_SHREF_BITS(obj), obj) != 0;
9504}
9505
9506/* The objspace's current page count (used for the zombie_objspaces page accounting). */
9507size_t
9508rb_gc_impl_heap_page_count(void *objspace_ptr)
9509{
9510 rb_objspace_t *objspace = objspace_ptr;
9511 return rb_darray_size(objspace->heap_pages.sorted);
9512}
9513
9514static void
9515gc_global_objspaces_i(void *os, void *data)
9516{
9517 if (global_objspace->global_gc.n_objspaces == global_objspace->global_gc.objspaces_capa) {
9518 size_t new_capa = global_objspace->global_gc.objspaces_capa ? global_objspace->global_gc.objspaces_capa * 2 : 16;
9519 struct rb_objspace **new_list = realloc(global_objspace->global_gc.objspaces, new_capa * sizeof(*new_list));
9520 if (new_list == NULL) rb_bug("gc_global_objspaces_i: realloc failed");
9521 global_objspace->global_gc.objspaces = new_list;
9522 global_objspace->global_gc.objspaces_capa = new_capa;
9523 }
9524 global_objspace->global_gc.objspaces[global_objspace->global_gc.n_objspaces++] = os;
9525}
9526
9527/* Re-snapshot every objspace this cycle covers, zombies included. The objspaces/capa
9528 * buffer is reused from the previous cycle. */
9529static void
9530gc_global_snapshot_objspaces(void)
9531{
9532 global_objspace->global_gc.n_objspaces = 0;
9533 rb_gc_vm_each_objspace(gc_global_objspaces_i, NULL);
9534
9535#if RGENGC_CHECK_MODE
9536 /* Check that the incrementally maintained page_index agrees with the per-objspace
9537 * sorted arrays. */
9538 size_t total = 0;
9539 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9540 total += rb_darray_size(global_objspace->global_gc.objspaces[i]->heap_pages.sorted);
9541 }
9542 GC_ASSERT(total == global_objspace->page_index.n_pages);
9543#endif
9544}
9545
9546/* Global GC: stop every Ractor and clear/mark/sweep all objspaces as one heap. It is the
9547 * only collector that can free shareable objects and decide cross-objspace reachability
9548 * precisely. */
9549/* The global GC's generic_fields weak pass, after the unified mark fixpoint, before the
9550 * sweep. Per-object rb_mark_generic_ivar is a no-op during a global GC (the driver has
9551 * GET_RACTOR() != owner); the whole table is swept here instead. Weak-KEY: mark the val
9552 * (fields_obj, a strong child) only for a live key, drain dead keys' entries. Marking a
9553 * val can make another key live, so repeat to a fixpoint. */
9556 bool progress;
9557};
9558
9559static int
9560genfields_mark_i(VALUE key, VALUE val, void *arg)
9561{
9562 struct genfields_mark_arg *a = (struct genfields_mark_arg *)arg;
9563 if (RB_SPECIAL_CONST_P(val) || !RVALUE_MARKED_BITMAP(key)) {
9564 return ST_CONTINUE;
9565 }
9566 /* Record the old(key)->young(val) edge with the host (key) as parent, even when val
9567 * is already marked: a conservative machine-stack scan can mark a fresh fields_obj
9568 * parentless before this pass, and branching on the mark bit would leave the key
9569 * unremembered, so the next minor GC misses the young val ("WB miss (O->Y)").
9570 * gc_mark runs rgengc_check_relation before its already-marked return: call always. */
9571 bool newly = !RVALUE_MARKED_BITMAP(val);
9572 gc_mark_set_parent(a->objspace, key);
9573 gc_mark(a->objspace, val);
9574 if (newly) a->progress = true;
9575 return ST_CONTINUE;
9576}
9577
9578static bool
9579genfields_dead_p(VALUE key)
9580{
9581 return RVALUE_MARKED_BITMAP(key) == 0;
9582}
9583
9584static void
9585gc_global_mark_generic_fields(rb_objspace_t *driver)
9586{
9587 struct genfields_mark_arg arg = { driver, false };
9588 do {
9589 arg.progress = false;
9590 /* Each entry's mark sets parent=key (genfields_mark_i) so the generational WB is
9591 * recorded correctly. gc_mark_stacked_objects_all sets its own per-object parent,
9592 * so restore the invalid parent (the poison contract) before calling it. */
9593 rb_gc_vm_generic_fields_mark_foreach(genfields_mark_i, &arg);
9594 gc_mark_set_parent_invalid(driver);
9595 if (arg.progress) {
9596 gc_mark_stacked_objects_all(driver);
9597 }
9598 } while (arg.progress);
9599
9600 rb_gc_vm_generic_fields_drain_dead(genfields_dead_p);
9601}
9602
9603/* Two Ractors choosing a global GC at once are serialized by the VM lock in gc_enter. If two
9604 * globals start concurrently, only one global will run and the other will run a local GC after
9605 * the barrier ends. */
9606static bool
9607gc_start_global(rb_objspace_t *driver, unsigned int reason, bool compact, bool allow_skip)
9608{
9609 unsigned int lock_lev;
9610 enum gc_enter_event event = allow_skip ? gc_enter_event_global_auto : gc_enter_event_global;
9611 if (!gc_enter(driver, event, &lock_lev)) {
9612 return false;
9613 }
9614
9615 reason |= GPR_FLAG_GLOBAL;
9616
9617 /* A global GC is a collection of the driver's objspace too, and its profile.count
9618 * below says so, so report it like a local one. The driver is the objspace whose
9619 * count moves, which is the one a hook reading GC.stat would compare against. For
9620 * the same reason it records a profile entry and reports what triggered it. */
9621 gc_start_record(driver, reason, true);
9622 gc_event_hook(driver, RUBY_INTERNAL_EVENT_GC_START);
9623 gc_prof_timer_start(driver);
9624
9625 gc_global_snapshot_objspaces();
9626
9627 /* Mark every objspace as in a global GC before step 3 settles the lazy sweeps: the
9628 * settle frees other objspaces' garbage on the driver thread, and
9629 * rb_free_generic_ivar must see "global GC in progress" to defer generic_fields
9630 * removal to the weak-pass drain. */
9631 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9632 global_objspace->global_gc.objspaces[i]->flags.during_global_gc = TRUE;
9633 }
9634
9635 /* A global GC collects every objspace, so each needs the malloc-counter reset the
9636 * driver got in gc_start_record; without it, gc_sweep_finish advancing free_at_last_gc
9637 * (step 9) would leave their malloc_increase overstated by everything swept here. */
9638 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9639 rb_objspace_t *const os = global_objspace->global_gc.objspaces[i];
9640 if (os != driver) {
9641 os->profile.latest_gc_info = reason;
9642 gc_reset_malloc_info(os, true);
9643 }
9644 }
9645
9646 /* step 3: settle every lazy sweep so the mark bits' meaning is fixed before the clear
9647 * below. (during_gc is a macro over the local "objspace".) rb_gc_get_ec() resolves
9648 * through objspace->vm_context during a GC, so initialize it for all: the driver
9649 * thread runs every objspace's phases. */
9650 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9651 rb_objspace_t *objspace = global_objspace->global_gc.objspaces[i];
9652 /* The barrier can stop a Ractor between the steps of its own incremental mark, and
9653 * nothing else finishes another objspace's mark. Drop the partial mark rather
9654 * than clear its flags in step 5 under a live gray stack: the owner would resume
9655 * with a mark stack into a heap this GC has since re-marked and swept. Nothing is
9656 * lost, the unified mark below redoes the work. */
9657 if (is_incremental_marking(objspace)) {
9658 gc_abort_incremental_marking(objspace);
9659 }
9660 GC_ASSERT(!is_incremental_marking(objspace));
9661 GC_ASSERT(is_mark_stack_empty(&objspace->mark_stack));
9662 rb_gc_initialize_vm_context(&objspace->vm_context);
9663 if (objspace != driver) during_gc = TRUE;
9664 gc_sweep_rest(objspace);
9665 }
9666
9667 /* step 5: clear every objspace's mark bits, remembered sets, generation counters and
9668 * shrefs (missing even one leaves a stale mark bit and a UAF). (heaps is a macro over
9669 * the local "objspace".) */
9670 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9671 rb_objspace_t *objspace = global_objspace->global_gc.objspaces[i];
9672 objspace->flags.during_minor_gc = FALSE;
9673 objspace->flags.during_incremental_marking = FALSE;
9674 /* The unified mark is precise and does not pin, so the per-objspace sweep below must
9675 * not re-check against a stale local cycle. */
9676 objspace->last_cycle_pinned = 0;
9677 objspace->rgengc.uncollectible_wb_unprotected_objects = 0;
9678 objspace->rgengc.old_objects = 0;
9679 objspace->rgengc.last_major_gc = objspace->profile.count;
9680 objspace->rgengc.need_major_gc = GPR_FLAG_NONE;
9681 objspace->marked_slots = 0;
9682 for (int h = 0; h < HEAP_COUNT; h++) {
9683 rb_heap_t *heap = &heaps[h];
9684 gc_bitmaps_clear(objspace, heap, true);
9685 heap_move_pooled_pages_to_free_pages(heap);
9686 }
9687 }
9688 driver->profile.major_gc_count++;
9689 global_objspace->global_gc.count++;
9690
9691 /* Enable compaction in every objspace before the mark: the unified conservative root
9692 * scan then pins machine-stack referents (gc_pin only pins while during_compacting)
9693 * and step 9's sweep relocates the rest. global_gc.compacting defers the
9694 * reference-update phase to phase 2 below (two phases, safe across objspaces). */
9695 global_objspace->global_gc.compacting = compact;
9696 if (compact) {
9697 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9698 rb_objspace_t *objspace = global_objspace->global_gc.objspaces[i];
9699 objspace->flags.during_compacting = TRUE;
9700#if RGENGC_CHECK_MODE
9701 if (ruby_enable_autocompact) {
9702 objspace->rcompactor.compare_func = ruby_autocompact_compare_func;
9703 }
9704#endif
9705 /* A global GC skips gc_marks_start, which is what resets pinned_slots for a
9706 * compacting local GC, so reset it here. step 5 cleared pinned_bits; the
9707 * conservative mark re-pins machine-stack referents. */
9708 for (int h = 0; h < HEAP_COUNT; h++) {
9709 struct heap_page *page = NULL;
9710 ccan_list_for_each(&heaps[h].pages, page, page_node) {
9711 page->pinned_slots = 0;
9712 }
9713 }
9714 }
9715 }
9716
9717 /* steps 6-7: every Ractor's roots (gc.c walks them all), then one unified precise
9718 * mark. A global GC does not go through gc_marks, so the marking
9719 * phase is opened here instead; it closes after rb_ractor_finish_marking below, which is
9720 * where gc_marks_finish ends for a local collection. */
9721 gc_marking_enter(driver);
9722
9723 mark_roots(driver, NULL);
9724 gc_mark_stacked_objects_all(driver);
9725
9726 /* Run the generic_fields weak pass after the mark fixpoint: mark the vals (fields_obj)
9727 * of live keys and drain the entries of dead ones. The per-object rb_mark_generic_ivar
9728 * is a no-op during a global GC, so this is the only path that marks generic_fields. */
9729 gc_global_mark_generic_fields(driver);
9730
9731 gc_event_hook(driver, RUBY_INTERNAL_EVENT_GC_END_MARK);
9732
9733 /* step 8 */
9734 gc_update_weak_references(driver);
9735
9736 /* This cycle's root pass over every Ractor has swept the deleted ractor-local keys out of
9737 * each storage. Free the key structs while still inside the barrier (a local GC never
9738 * can; see rb_ractor_finish_marking). */
9739 rb_ractor_finish_marking(true);
9740
9741 gc_marking_exit(driver);
9742
9743 /* step 9: sweep every objspace inside the barrier, not lazily. Dead shareable objects
9744 * are reclaimed here and emptied pages go back to the pool. */
9745 if (!compact) {
9746 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9747 rb_objspace_t *os = global_objspace->global_gc.objspaces[i];
9748 unsigned int prev_immediate = os->flags.immediate_sweep;
9749 os->flags.immediate_sweep = TRUE;
9750 gc_sweep(os);
9751 os->flags.immediate_sweep = prev_immediate;
9752 }
9753 }
9754 else {
9755 /* The move -> update-references -> free flow runs as three passes across ALL
9756 * objspaces, not per objspace: (a) updating references must see every objspace's
9757 * forwarding (a reference can point at a moved foreign object), and (b) freeing
9758 * source pages must wait until everyone is updated (or another objspace's update
9759 * reads a freed T_MOVED). The read barrier is installed once for all passes. */
9760 install_handlers();
9761
9762 /* Only the driver records a profile entry for a global GC (gc_start_record), so time
9763 * only the driver's compaction work. The move/update/free below runs inside the
9764 * driver's sweep phase (gc_sweeping_enter/exit); attribute it to GC_COMPACT_WALL_TIME
9765 * and exclude it from the driver's sweep wall time so the two do not double-count,
9766 * mirroring the compacting branch of the local gc_sweep(). */
9767 const bool driver_prof = gc_prof_enabled(driver);
9768 rb_hrtime_t driver_compact_wall_time = 0;
9769
9770 /* pass 1 (move): relocate every objspace and leave T_MOVED forwarding behind. */
9771 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9772 rb_objspace_t *os = global_objspace->global_gc.objspaces[i];
9773 gc_sweeping_enter(os);
9774 gc_sweep_start(os); /* mode -> sweeping, order the heap for compaction */
9775 rb_hrtime_t t0 = (os == driver && driver_prof) ? rb_hrtime_now() : 0;
9776 gc_compact_relocate(os); /* mode -> compacting, move */
9777 if (os == driver && driver_prof) {
9778 driver_compact_wall_time = rb_hrtime_add(driver_compact_wall_time, elapsed_hrtime_from(t0));
9779 }
9780 gc_sweeping_cpu_exit(os);
9781 }
9782
9783 /* pass 2 (update): all forwarding now exists, so update every objspace's
9784 * references (cross-objspace ones resolve too); gc_compact_finish also unprotects
9785 * pages and clears during_compacting. The move-or-mark decision reads
9786 * rb_gc_get_objspace()'s during_reference_updating: set it on every objspace. */
9787 gc_sweeping_cpu_enter(driver);
9788 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9789 global_objspace->global_gc.objspaces[i]->flags.during_reference_updating = TRUE;
9790 }
9791 rb_gc_before_updating_jit_code();
9792 gc_sweeping_cpu_exit(driver);
9793 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9794 rb_objspace_t *os = global_objspace->global_gc.objspaces[i];
9795 gc_sweeping_cpu_enter(os);
9796 rb_hrtime_t t0 = (os == driver && driver_prof) ? rb_hrtime_now() : 0;
9797 gc_compact_finish(os);
9798 if (os == driver && driver_prof) {
9799 driver_compact_wall_time = rb_hrtime_add(driver_compact_wall_time, elapsed_hrtime_from(t0));
9800 }
9801 gc_sweeping_cpu_exit(os);
9802 }
9803 /* The VM-global / weak-table side of the reference update runs once (each objspace's
9804 * heap side already ran in gc_compact_finish above). */
9805 {
9806 gc_sweeping_cpu_enter(driver);
9807 rb_hrtime_t t0 = driver_prof ? rb_hrtime_now() : 0;
9808 gc_update_references_global(driver);
9809 if (driver_prof) {
9810 driver_compact_wall_time = rb_hrtime_add(driver_compact_wall_time, elapsed_hrtime_from(t0));
9811 }
9812 gc_sweeping_cpu_exit(driver);
9813 }
9814 gc_sweeping_cpu_enter(driver);
9815 rb_gc_after_updating_jit_code();
9816 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9817 global_objspace->global_gc.objspaces[i]->flags.during_reference_updating = FALSE;
9818 global_objspace->global_gc.objspaces[i]->flags.during_compacting = FALSE;
9819 }
9820 global_objspace->global_gc.compacting = false;
9821 uninstall_handlers();
9822 gc_sweeping_cpu_exit(driver);
9823
9824 /* Record the driver's compaction time and exclude it from the driver's sweep phase.
9825 * gc_sweeping_exit(driver) in pass 3 subtracts gc_sweep_excluded_wall_time from the
9826 * sweep wall time, so this must be set before it runs. The excluded value is a sum
9827 * of sub-intervals of the driver's sweep phase, so the subtraction cannot underflow. */
9828 if (driver_prof) {
9829 gc_profile_record *const record = gc_prof_record(driver);
9830 record->gc_compact_wall_time = rb_hrtime_add(record->gc_compact_wall_time,
9831 driver_compact_wall_time);
9832 driver->profile.gc_sweep_excluded_wall_time = rb_hrtime_add(
9833 driver->profile.gc_sweep_excluded_wall_time, driver_compact_wall_time);
9834 }
9835
9836 /* pass 3 (free): page-sweep every objspace, freeing dead objects and the source pages
9837 * that are now empty. during_compacting is already cleared, so the sweep treats
9838 * T_MOVED as usual. */
9839 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9840 rb_objspace_t *os = global_objspace->global_gc.objspaces[i];
9841 gc_sweeping_cpu_enter(os);
9842 gc_sweep_rest(os);
9843 gc_sweeping_exit(os);
9844 }
9845 }
9846 global_objspace->global_gc.compacting = false;
9847
9848 /* A global GC never calls gc_marks_finish, which budgets heap growth
9849 * (allocatable_bytes). An objspace still full after the global sweep (materializing
9850 * a large received copy, say) has no free pages, no empty pages, budget 0, and its next
9851 * allocation would hit newobj_refill's "cannot create a new page after a major GC".
9852 * Give every objspace stuck like that the growth budget gc_marks_finish would. */
9853 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9854 rb_objspace_t *objspace = global_objspace->global_gc.objspaces[i];
9855 if (objspace->heap_pages.allocatable_bytes != 0 || objspace->empty_pages_count != 0) {
9856 continue;
9857 }
9858 bool stuck = false;
9859 for (int h = 0; h < HEAP_COUNT; h++) {
9860 if (heaps[h].free_pages == NULL) { stuck = true; break; }
9861 }
9862 if (stuck) {
9863 heap_allocatable_bytes_expand(objspace, NULL, 0,
9864 objspace_available_slots(objspace), heaps[0].slot_size);
9865 }
9866 }
9867
9868 /* Recount the surviving shareable objects (the sweep already folded the dead ones out of
9869 * shareable_bits) and reset each trigger limit. */
9870 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9871 rb_objspace_t *objspace = global_objspace->global_gc.objspaces[i];
9872 size_t survivors = 0;
9873 for (int h = 0; h < HEAP_COUNT; h++) {
9874 struct heap_page *page = NULL;
9875 ccan_list_for_each(&heaps[h].pages, page, page_node) {
9876 if (!page->flags.has_shareable_objects) continue;
9877 for (int j = 0; j < HEAP_PAGE_BITMAP_LIMIT; j++) {
9878 survivors += rb_popcount_intptr(page->shareable_bits[j]);
9879 }
9880 }
9881 }
9882 objspace->shareable_objects = survivors;
9883 size_t new_limit = (size_t)(survivors * SHAREABLE_OBJECTS_LIMIT_FACTOR);
9884 if (new_limit < SHAREABLE_OBJECTS_LIMIT_MIN) new_limit = SHAREABLE_OBJECTS_LIMIT_MIN;
9885 objspace->shareable_objects_limit = new_limit;
9886 }
9887
9888 /* Deferred non-thread-safe frees: the world is already stopped here, so reap them
9889 * without a second barrier. Uses the driver's snapshot rather than taking its own,
9890 * which step 10 below still walks. */
9891 gc_tdata_unsafe_drain_objspaces(global_objspace->global_gc.objspaces,
9892 global_objspace->global_gc.n_objspaces);
9893 driver->profile.count++;
9894
9895 /* step 10 */
9896 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9897 rb_objspace_t *objspace = global_objspace->global_gc.objspaces[i];
9898 objspace->flags.during_global_gc = FALSE;
9899 if (objspace != driver) {
9900 during_gc = FALSE;
9901 gc_process_stat_publish(objspace);
9902 }
9903 }
9904
9905 /* The unified mark re-established the reachability of absorbed shareable objects, so a
9906 * single objspace's local mark is trustworthy again (pinning can be skipped until the
9907 * next absorb). */
9908 rb_gc_reset_absorbed_since_global_gc();
9909
9910 /* Re-measure the zombie_objspaces table now that the garbage is gone; entries are stable
9911 * inside the barrier. Without this, the page trigger above keeps firing on the stale
9912 * numbers left when a joinable (slotted) zombie retires without any pass merging it. */
9913 rb_gc_vm_refresh_zombie_pages();
9914 global_objspace->zombie_pages_survivors = rb_gc_vm_zombie_total_pages();
9915
9916 /* If the sweep above collected an unjoined Ractor object, ractor_free disowned its
9917 * zombie_objspaces entry and posted the merge to main as a postponed job; the objspace
9918 * stays enumerable until main absorbs it at its next safepoint. */
9919
9920 gc_prof_timer_stop(driver);
9921 gc_exit(driver, event, &lock_lev);
9922 return true;
9923}
9924
9925static int
9926absorb_finalizer_i(st_data_t key, st_data_t val, st_data_t data)
9927{
9929 st_insert(finalizer_table, key, val);
9930 return ST_CONTINUE;
9931}
9932
9933static void
9934gc_make_mid_mark_objspace_absorbable(rb_objspace_t *src)
9935{
9936 rb_objspace_t *objspace = src;
9937 for (int h = 0; h < HEAP_COUNT; h++) {
9938 struct heap_page *page = NULL;
9939 ccan_list_for_each(&heaps[h].pages, page, page_node) {
9940 short stride = page->slot_size;
9941 uintptr_t p = (uintptr_t)page->start;
9942 uintptr_t pend = p + page->total_slots * stride;
9943
9944 for (; p < pend; p += stride) {
9945 VALUE vp = (VALUE)p;
9946 asan_unpoisoning_object(vp) {
9947 switch (RB_BUILTIN_TYPE(vp)) {
9948 case T_NONE:
9949 case T_ZOMBIE:
9950 break;
9951 default:
9952 RVALUE_AGE_RESET(vp);
9953 break;
9954 }
9955 }
9956 }
9957 }
9958 gc_bitmaps_clear(objspace, &heaps[h], false);
9959 }
9960 objspace->rgengc.old_objects = 0;
9961 objspace->rgengc.uncollectible_wb_unprotected_objects = 0;
9962 objspace->marked_slots = 0;
9963}
9964
9965/* Merge a dead Ractor's objspace into dst under the VM lock. src has no owner thread and
9966 * dst is the calling thread's own objspace (join/value) or main with everyone stopped
9967 * (global GC), so single-writer holds throughout. Pages move whole (their bits describe
9968 * objects, not the objspace), and dst's next collection is forced full to rebuild the
9969 * generational state. */
9970static void
9971objspace_absorb(rb_objspace_t *dst, rb_objspace_t *src)
9972{
9973 GC_ASSERT(dst != src);
9974
9975 /* Suppress the cross-objspace verifier checks while the graph is in flux (see
9976 * global_objspace->during_absorb). */
9977 const bool prev_absorb = global_objspace->during_absorb;
9978 global_objspace->during_absorb = true;
9979
9980 /* Settle dst first: adding pages under a walking lazy-sweep cursor, or into a
9981 * half-marked incremental heap, would sweep the merged pages with src's stale mark
9982 * bits and free live objects. */
9983 gc_rest(dst);
9984
9985 /* Settle src: no incremental marking, lazy sweep or in-progress allocation page. */
9986 {
9987 rb_objspace_t *objspace = src;
9988 /* A zombie parked by fork (rb_ractor_terminate_atfork retires the objspace
9989 * without the retire GC) can be mid-incremental-mark, and that mark cannot be
9990 * finished. Its owner's threads did not survive the fork, so mark_roots would
9991 * scan the absorbing Ractor's roots instead of src's. Drop the cycle. */
9992 if (is_incremental_marking(objspace)) {
9993 gc_abort_incremental_marking(objspace);
9994 gc_make_mid_mark_objspace_absorbable(objspace);
9995 }
9996 gc_rest(src); // if mid-sweep
9997 heap_alloc_state_clear(objspace);
9998 /* gc_sweep_finish leaves swept pages "pooled" for a coming incremental mark; src
9999 * never runs one (it is about to be merged), so return them to its free list now,
10000 * restoring the pooled_pages == NULL the page merge below assumes (mirrors
10001 * gc_start_global step 3). */
10002 for (int h = 0; h < HEAP_COUNT; h++) {
10003 heap_move_pooled_pages_to_free_pages(&heaps[h]);
10004 }
10005 }
10006
10007 /* From here the merge must not run dst's GC: the finalizer st_insert below can cross
10008 * the malloc-accounting threshold, and a GC then would sweep src's detached finalizer
10009 * procs, reachable only from this C frame, into dangling VALUEs. Page/darray moves
10010 * allocate nothing (_without_gc), so disabling costs nothing and makes the splice
10011 * atomic. (The two settles above deliberately collect: they stay outside.) */
10012 const bool dst_gc_was_enabled = rb_gc_impl_gc_enabled_p(dst);
10013 if (dst_gc_was_enabled) rb_gc_impl_gc_disable(dst, false);
10014
10015 /* Hand over the pages size pool by size pool. ("heaps" is a macro over the local
10016 * objspace, so the arrays are taken through scoped locals.) */
10017 rb_heap_t *dst_heaps;
10018 rb_heap_t *src_heaps;
10019 {
10020 rb_objspace_t *objspace = dst;
10021 dst_heaps = heaps;
10022 }
10023 {
10024 rb_objspace_t *objspace = src;
10025 src_heaps = heaps;
10026 }
10027 for (int h = 0; h < HEAP_COUNT; h++) {
10028 rb_heap_t *dheap = &dst_heaps[h];
10029 rb_heap_t *sheap = &src_heaps[h];
10030 struct heap_page *page = NULL;
10031
10032 GC_ASSERT(sheap->sweeping_page == NULL);
10033 GC_ASSERT(sheap->pooled_pages == NULL);
10034
10035 ccan_list_for_each(&sheap->pages, page, page_node) {
10036 page->objspace = dst;
10037 page->heap = dheap;
10038 }
10039 ccan_list_append_list(&dheap->pages, &sheap->pages);
10040
10041 /* Append the free-page chain to the tail. */
10042 if (sheap->free_pages) {
10043 struct heap_page **tail = &dheap->free_pages;
10044 while (*tail) tail = &(*tail)->free_next;
10045 *tail = sheap->free_pages;
10046 sheap->free_pages = NULL;
10047 }
10048
10049 dheap->total_pages += sheap->total_pages;
10050 dheap->total_slots += sheap->total_slots;
10051 dheap->total_allocated_pages += sheap->total_allocated_pages;
10052 dheap->total_allocated_objects += sheap->total_allocated_objects;
10053 dheap->total_freed_objects += sheap->total_freed_objects;
10054 dheap->final_slots_count += sheap->final_slots_count;
10055 }
10056
10057 /* The objspace-wide page bookkeeping. */
10058 {
10059 rb_objspace_t *objspace = dst; /* for the heap_pages_* macros */
10060 struct heap_page *page = NULL;
10061 size_t srcn = rb_darray_size(src->heap_pages.sorted);
10062 for (size_t i = 0; i < srcn; i++) {
10063 page = rb_darray_get(src->heap_pages.sorted, i);
10064 /* Residents of the empty pool (no live objects) are returned to page_pool rather
10065 * than inherited; dst's allocation demand is cheaply met from the shared pool's
10066 * free list. */
10067 if (heap_page_in_global_empty_pages_pool(src, page)) {
10068 heap_page_free(src, page);
10069 continue;
10070 }
10071 uintptr_t body = (uintptr_t)page->body;
10072 uintptr_t start = body + sizeof(struct heap_page_header);
10073 uintptr_t end = body + HEAP_PAGE_SIZE;
10074
10075 /* Keep the array ordered by page BODY address: heap_page_for_ptr bsearches
10076 * body ranges, and a detached empty page has start == 0, so ordering by
10077 * page->start would miss live pages (a global GC would then fail to mark a
10078 * registered root and sweep it). */
10079 size_t lo = 0;
10080 size_t hi = rb_darray_size(objspace->heap_pages.sorted);
10081 while (lo < hi) {
10082 size_t mid = (lo + hi) / 2;
10083 struct heap_page *mid_page = rb_darray_get(objspace->heap_pages.sorted, mid);
10084 if ((uintptr_t)mid_page->body < body) lo = mid + 1;
10085 else hi = mid;
10086 }
10087 rb_darray_insert_without_gc(&objspace->heap_pages.sorted, hi, page);
10088
10089 if (heap_pages_lomem == 0 || heap_pages_lomem > start) heap_pages_lomem = start;
10090 if (heap_pages_himem < end) heap_pages_himem = end;
10091 }
10092 objspace->heap_pages.allocated_pages += src->heap_pages.allocated_pages;
10093 objspace->heap_pages.freed_pages += src->heap_pages.freed_pages;
10094 rb_darray_free_without_gc(src->heap_pages.sorted);
10095 src->heap_pages.sorted = NULL;
10096 /* The empty_pages chain's structs were freed in the loop above. */
10097 src->empty_pages = NULL;
10098 src->empty_pages_count = 0;
10099 }
10100
10101 /* Finalizers: move the table's entries, and the dead Ractor's deferred zombies are run
10102 * by dst's thread from now on. */
10103 {
10104 st_table *src_finalizers;
10105 {
10106 rb_objspace_t *objspace = src;
10107 src_finalizers = finalizer_table;
10108 finalizer_table = NULL;
10109 }
10110 if (src_finalizers) {
10111 rb_objspace_t *objspace = dst;
10112 if (finalizer_table == NULL) {
10113 finalizer_table = src_finalizers;
10114 }
10115 else {
10116 st_foreach(src_finalizers, absorb_finalizer_i, (st_data_t)dst);
10117 st_free_table(src_finalizers);
10118 }
10119 }
10120 }
10121 {
10122 VALUE src_deferred = RUBY_ATOMIC_VALUE_EXCHANGE(src->heap_pages.deferred_final, 0);
10123 if (src_deferred) {
10124 VALUE tail_obj = src_deferred;
10125 rb_asan_unpoison_object(tail_obj, false);
10126 while (RZOMBIE(tail_obj)->next) {
10127 VALUE next_obj = RZOMBIE(tail_obj)->next;
10128 rb_asan_poison_object(tail_obj);
10129 tail_obj = next_obj;
10130 rb_asan_unpoison_object(tail_obj, false);
10131 }
10132 VALUE prev;
10133 do {
10134 prev = dst->heap_pages.deferred_final;
10135 RZOMBIE(tail_obj)->next = prev;
10136 } while (RUBY_ATOMIC_VALUE_CAS(dst->heap_pages.deferred_final, prev, src_deferred) != prev);
10137 rb_asan_poison_object(tail_obj);
10138 /* No owner was left to run these zombies (register's owner walk misses a dead
10139 * Ractor). dst runs this merge, so schedule dst's job here; otherwise they wait
10140 * until dst's next GC. */
10141 rb_postponed_job_trigger(dst->finalize_deferred_pjob);
10142 }
10143 }
10144 if (src->tdata_unsafe_free_chunk) {
10145 gc_tdata_unsafe_free_publish(src);
10146 }
10147 if (tdata_deferred_free_count_load() >= TDATA_DEFERRED_FREE_THRESHOLD) {
10148 gc_tdata_deferred_free_trigger(dst);
10149 }
10150
10151 /* Counters inherited by dst. */
10152 dst->rgengc.old_objects += src->rgengc.old_objects;
10153 dst->rgengc.uncollectible_wb_unprotected_objects += src->rgengc.uncollectible_wb_unprotected_objects;
10154 dst->shareable_objects += src->shareable_objects;
10155
10156 /* Merged pages carry src's mark/age state, so dst rebuilds its view at the next
10157 * collection. Note that this can be worked around by calling `GC.config(rgengc_allow_full_mark: false)`,
10158 * so the absorbed heap should be in a state where a minor GC would also work correctly.
10159 */
10160 dst->rgengc.need_major_gc |= GPR_FLAG_MAJOR_BY_FORCE;
10161
10162 /* src's outstanding malloc pressure moves with the xmalloc'd buffers. Later frees are
10163 * charged to dst, so without this transfer dst underestimates its own heap and delays
10164 * GCs. dst is live, so take its counter lock where gc_counter_add is not atomic. */
10165 {
10166 int64_t inc = gc_malloc_counters_increase(src, &src->malloc_counters.counters);
10167#if RGENGC_ESTIMATE_OLDMALLOC
10168 int64_t oldinc = gc_malloc_counters_increase(src, &src->malloc_counters.oldcounters);
10169#endif
10170 MALLOC_COUNTERS_LOCK(dst);
10171 if (inc > 0) gc_counter_add(&dst->malloc_counters.counters.malloc, (size_t)inc);
10172#if RGENGC_ESTIMATE_OLDMALLOC
10173 if (oldinc > 0) gc_counter_add(&dst->malloc_counters.oldcounters.malloc, (size_t)oldinc);
10174#endif
10175 MALLOC_COUNTERS_UNLOCK(dst);
10176 }
10177
10178 {
10179 struct gc_process_stat_snapshot final_snap;
10180 gc_process_stat_capture(src, &final_snap);
10181 gc_process_stat_add(&global_objspace->process_stat_archive, &final_snap);
10182 }
10183 rb_native_mutex_destroy(&src->process_stat.lock);
10184
10185 /* Free the shell (as rb_gc_impl_objspace_free does). */
10186 free(src->profile.records);
10187 free_stack_chunks(&src->mark_stack);
10188 mark_stack_free_cache(&src->mark_stack);
10189 GC_ASSERT(rb_darray_size(src->weak_references) == 0);
10190 rb_darray_free_without_gc(src->weak_references);
10191#ifdef MALLOC_COUNTERS_NEED_LOCK
10192 rb_native_mutex_destroy(&src->malloc_counters.lock);
10193#endif
10194 free(src);
10195
10196 if (dst_gc_was_enabled) rb_gc_impl_gc_enable(dst);
10197
10198 /* Return the empty pages inheritance piled up in dst (mostly from the dead Ractor's
10199 * teardown material) to the pool with no budget. An empty page is by definition safe to
10200 * release, and re-acquiring one from the pool is cheap. */
10201 {
10202 rb_objspace_t *objspace = dst;
10203 heap_pages_freeable_pages = objspace->empty_pages_count;
10204 heap_pages_free_unused_pages(objspace);
10205 }
10206
10207 global_objspace->during_absorb = prev_absorb;
10208}
10209
10210void
10211rb_gc_impl_objspace_absorb(void *dst_ptr, void *src_ptr)
10212{
10213 objspace_absorb(dst_ptr, src_ptr);
10214}
10215
10216void
10217rb_gc_impl_start(void *objspace_ptr, bool full_mark, bool immediate_mark, bool immediate_sweep, bool compact, bool global)
10218{
10219 rb_objspace_t *objspace = objspace_ptr;
10220 unsigned int reason = (GPR_FLAG_FULL_MARK |
10221 GPR_FLAG_IMMEDIATE_MARK |
10222 GPR_FLAG_IMMEDIATE_SWEEP |
10223 GPR_FLAG_METHOD);
10224
10225 int full_marking_p = gc_config_full_mark_val;
10226 gc_config_full_mark_set(TRUE);
10227
10228 /* For now, compact implies full mark / sweep, so ignore other flags */
10229 if (compact) {
10230 GC_ASSERT(GC_COMPACTION_SUPPORTED);
10231
10232 reason |= GPR_FLAG_COMPACT;
10233 if (!rb_gc_single_objspace_p()) {
10234 global = true;
10235 }
10236 }
10237 else {
10238 if (!full_mark) reason &= ~GPR_FLAG_FULL_MARK;
10239 if (!immediate_mark) reason &= ~GPR_FLAG_IMMEDIATE_MARK;
10240 if (!immediate_sweep) reason &= ~GPR_FLAG_IMMEDIATE_SWEEP;
10241 }
10242
10243 if ((reason & (GPR_FLAG_FULL_MARK | GPR_FLAG_IMMEDIATE_MARK | GPR_FLAG_IMMEDIATE_SWEEP)) !=
10244 (GPR_FLAG_FULL_MARK | GPR_FLAG_IMMEDIATE_MARK | GPR_FLAG_IMMEDIATE_SWEEP)) {
10245 global = false;
10246 }
10247
10248 if (global && !rb_gc_single_objspace_p()) {
10249 /* A mid-cycle driver is settled by gc_start_global itself: it aborts the partial
10250 * mark and finishes the lazy sweep, so the dead slots are T_NONE before the
10251 * unified conservative root scan. */
10252 gc_start_global(objspace, reason, compact || ruby_enable_autocompact, false);
10253 }
10254 else {
10255 garbage_collect(objspace, reason);
10256 }
10257
10258 gc_finalize_deferred(objspace);
10259 /* An explicit GC.start is expected to reclaim immediately, so run the deferred non-thread-safe
10260 * frees synchronously instead of leaving them to gc_sweep_finish's postponed job. */
10261 if (tdata_deferred_free_count_load() > 0 && rb_gc_single_objspace_p()) {
10262 gc_tdata_unsafe_drain();
10263 }
10264 gc_config_full_mark_set(full_marking_p);
10265}
10266
10267void
10268rb_gc_impl_prepare_heap(void *objspace_ptr)
10269{
10270 rb_objspace_t *objspace = objspace_ptr;
10271
10272 rb_gc_impl_each_objects(objspace, gc_set_candidate_object_i, objspace_ptr);
10273
10274 double orig_max_free_slots = gc_params.heap_free_slots_max_ratio;
10275 /* Ensure that all empty pages are moved onto empty_pages. */
10276 gc_params.heap_free_slots_max_ratio = 0.0;
10277 rb_gc_impl_start(objspace, true, true, true, true, true);
10278 gc_params.heap_free_slots_max_ratio = orig_max_free_slots;
10279
10280 objspace->heap_pages.allocatable_bytes = 0;
10281 heap_pages_freeable_pages = objspace->empty_pages_count;
10282 heap_pages_free_unused_pages(objspace_ptr);
10283 GC_ASSERT(heap_pages_freeable_pages == 0);
10284 GC_ASSERT(objspace->empty_pages_count == 0);
10285
10286 // Process.warmup is meant to be called at the end of the boot sequence, which is commonly allocation
10287 // heavy and result in GC limits raising significantly, but it's not indicative of the limits needed
10288 // for runtime.
10289 // Recompute the allocatable_bytes limit based on `gc_params.heap_init_bytes`.
10290 GC_ASSERT(objspace->heap_pages.allocatable_bytes == 0);
10291 for (int i = 0; i < HEAP_COUNT; i++) {
10292 rb_heap_t *heap = &heaps[i];
10293 heap_allocatable_bytes_expand(objspace, heap, heap->empty_slots, heap->total_slots, heap->slot_size);
10294 }
10295
10296#if defined(HAVE_MALLOC_TRIM) && !defined(RUBY_ALTERNATIVE_MALLOC_HEADER)
10297 malloc_trim(0);
10298#endif
10299}
10300
10301static int
10302gc_is_moveable_obj(rb_objspace_t *objspace, VALUE obj)
10303{
10304 GC_ASSERT(!SPECIAL_CONST_P(obj));
10305
10306 switch (BUILTIN_TYPE(obj)) {
10307 case T_NONE:
10308 case T_MOVED:
10309 case T_ZOMBIE:
10310 return FALSE;
10311 case T_SYMBOL:
10312 case T_STRING:
10313 case T_OBJECT:
10314 case T_FLOAT:
10315 case T_IMEMO:
10316 case T_ARRAY:
10317 case T_BIGNUM:
10318 case T_ICLASS:
10319 case T_MODULE:
10320 case T_REGEXP:
10321 case T_DATA:
10322 case T_MATCH:
10323 case T_STRUCT:
10324 case T_HASH:
10325 case T_FILE:
10326 case T_COMPLEX:
10327 case T_RATIONAL:
10328 case T_NODE:
10329 case T_CLASS:
10330 if (FL_TEST_RAW(obj, FL_FINALIZE)) {
10331 /* The finalizer table is a numtable. It looks up objects by address.
10332 * We can't mark the keys in the finalizer table because that would
10333 * prevent the objects from being collected. This check prevents
10334 * objects that are keys in the finalizer table from being moved
10335 * without directly pinning them. */
10336 GC_ASSERT(st_is_member(finalizer_table, obj));
10337
10338 return FALSE;
10339 }
10340 GC_ASSERT(RVALUE_MARKED(objspace, obj));
10341 GC_ASSERT(!RVALUE_PINNED(objspace, obj));
10342
10343 return TRUE;
10344
10345 default:
10346 rb_bug("gc_is_moveable_obj: unreachable (%d)", (int)BUILTIN_TYPE(obj));
10347 break;
10348 }
10349
10350 return FALSE;
10351}
10352
10353void rb_mv_generic_ivar(VALUE src, VALUE dst);
10354
10355static VALUE
10356gc_move(rb_objspace_t *objspace, VALUE src, VALUE dest, struct heap_page *src_page, struct heap_page *dest_page)
10357{
10358 size_t src_slot_size = src_page->slot_size;
10359 size_t slot_size = dest_page->slot_size;
10360
10361 int marked;
10362 int wb_unprotected;
10363 int uncollectible;
10364 int age;
10365
10366 gc_report(4, objspace, "Moving object: %p -> %p\n", (void *)src, (void *)dest);
10367
10368 GC_ASSERT(BUILTIN_TYPE(src) != T_NONE);
10369 GC_ASSERT(!MARKED_IN_BITMAP(GET_HEAP_MARK_BITS(dest), dest));
10370
10371 GC_ASSERT(!RVALUE_MARKING(objspace, src));
10372
10373 /* Save off bits for current object. */
10374 marked = RVALUE_MARKED(objspace, src);
10375 wb_unprotected = RVALUE_WB_UNPROTECTED(objspace, src);
10376 uncollectible = RVALUE_UNCOLLECTIBLE(objspace, src);
10377 bool remembered = RVALUE_REMEMBERED(objspace, src);
10378 /* Pin bits travel with the object. Losing one during single-objspace compaction would
10379 * silently unpin it once the process goes multi-objspace, letting a local GC free a method
10380 * entry or shref target that another Ractor references. */
10381 bool shareable = MARKED_IN_BITMAP(GET_HEAP_SHAREABLE_BITS(src), src) != 0;
10382 bool shref = MARKED_IN_BITMAP(GET_HEAP_SHREF_BITS(src), src) != 0;
10383 age = RVALUE_AGE_GET(src);
10384
10385 /* Clear bits for eventual T_MOVED */
10386 CLEAR_IN_BITMAP(GET_HEAP_MARK_BITS(src), src);
10387 CLEAR_IN_BITMAP(GET_HEAP_WB_UNPROTECTED_BITS(src), src);
10388 CLEAR_IN_BITMAP(GET_HEAP_UNCOLLECTIBLE_BITS(src), src);
10389 CLEAR_IN_BITMAP(GET_HEAP_PAGE(src)->remembered_bits, src);
10390 CLEAR_IN_BITMAP(GET_HEAP_SHAREABLE_BITS(src), src);
10391 CLEAR_IN_BITMAP(GET_HEAP_SHREF_BITS(src), src);
10392
10393 /* Move the object */
10394 memcpy((void *)dest, (void *)src, MIN(src_slot_size, slot_size));
10395
10396 if (src_slot_size != slot_size) {
10397 rb_gc_obj_changed_slot_size(dest, slot_size - RVALUE_OVERHEAD);
10398 }
10399
10400 if (RVALUE_OVERHEAD > 0) {
10401 void *dest_overhead = (void *)(((uintptr_t)dest) + slot_size - RVALUE_OVERHEAD);
10402 void *src_overhead = (void *)(((uintptr_t)src) + src_slot_size - RVALUE_OVERHEAD);
10403
10404 memcpy(dest_overhead, src_overhead, RVALUE_OVERHEAD);
10405 }
10406
10407 memset((void *)src, 0, src_slot_size);
10408 RVALUE_AGE_SET_BITMAP(src, 0);
10409
10410 /* Set bits for object in new location */
10411 if (remembered) {
10412 MARK_IN_BITMAP(GET_HEAP_PAGE(dest)->remembered_bits, dest);
10413 }
10414 else {
10415 CLEAR_IN_BITMAP(GET_HEAP_PAGE(dest)->remembered_bits, dest);
10416 }
10417
10418 if (marked) {
10419 MARK_IN_BITMAP(GET_HEAP_MARK_BITS(dest), dest);
10420 }
10421 else {
10422 CLEAR_IN_BITMAP(GET_HEAP_MARK_BITS(dest), dest);
10423 }
10424
10425 if (wb_unprotected) {
10426 MARK_IN_BITMAP(GET_HEAP_WB_UNPROTECTED_BITS(dest), dest);
10427 }
10428 else {
10429 CLEAR_IN_BITMAP(GET_HEAP_WB_UNPROTECTED_BITS(dest), dest);
10430 }
10431
10432 if (uncollectible) {
10433 MARK_IN_BITMAP(GET_HEAP_UNCOLLECTIBLE_BITS(dest), dest);
10434 }
10435 else {
10436 CLEAR_IN_BITMAP(GET_HEAP_UNCOLLECTIBLE_BITS(dest), dest);
10437 }
10438
10439 if (shareable) {
10440 MARK_IN_BITMAP(GET_HEAP_SHAREABLE_BITS(dest), dest);
10441 GET_HEAP_PAGE(dest)->flags.has_shareable_objects = TRUE;
10442 }
10443 else {
10444 CLEAR_IN_BITMAP(GET_HEAP_SHAREABLE_BITS(dest), dest);
10445 }
10446
10447 if (shref) {
10448 MARK_IN_BITMAP(GET_HEAP_SHREF_BITS(dest), dest);
10449 GET_HEAP_PAGE(dest)->flags.has_shref_objects = TRUE;
10450 }
10451 else {
10452 CLEAR_IN_BITMAP(GET_HEAP_SHREF_BITS(dest), dest);
10453 }
10454
10455 RVALUE_AGE_SET(dest, age);
10456
10457 /* A re-embedded object (rb_gc_obj_changed_slot_size) references its
10458 * former fields_obj's contents directly; the write-barrier history
10459 * lived on the discarded fields_obj, so remember the object. */
10460 if (src_slot_size != slot_size && age >= RVALUE_OLD_AGE && !remembered) {
10461 rgengc_remember(objspace, dest);
10462 }
10463
10464 /* Assign forwarding address */
10465 RMOVED(src)->flags = T_MOVED;
10466 RMOVED(src)->dummy = Qundef;
10467 RMOVED(src)->destination = dest;
10468 GC_ASSERT(BUILTIN_TYPE(dest) != T_NONE);
10469
10470 GET_HEAP_PAGE(src)->heap->total_freed_objects++;
10471 GET_HEAP_PAGE(dest)->heap->total_allocated_objects++;
10472
10473 return src;
10474}
10475
10476#if GC_CAN_COMPILE_COMPACTION
10477static int
10478compare_pinned_slots(const void *left, const void *right, void *dummy)
10479{
10480 struct heap_page *left_page;
10481 struct heap_page *right_page;
10482
10483 left_page = *(struct heap_page * const *)left;
10484 right_page = *(struct heap_page * const *)right;
10485
10486 return left_page->pinned_slots - right_page->pinned_slots;
10487}
10488
10489static int
10490compare_free_slots(const void *left, const void *right, void *dummy)
10491{
10492 struct heap_page *left_page;
10493 struct heap_page *right_page;
10494
10495 left_page = *(struct heap_page * const *)left;
10496 right_page = *(struct heap_page * const *)right;
10497
10498 return left_page->free_slots - right_page->free_slots;
10499}
10500
10501static void
10502gc_sort_heap_by_compare_func(rb_objspace_t *objspace, gc_compact_compare_func compare_func)
10503{
10504 for (int j = 0; j < HEAP_COUNT; j++) {
10505 rb_heap_t *heap = &heaps[j];
10506
10507 size_t total_pages = heap->total_pages;
10508 size_t size = rb_size_mul_or_raise(total_pages, sizeof(struct heap_page *), rb_eRuntimeError);
10509 struct heap_page *page = 0, **page_list = malloc(size);
10510 size_t i = 0;
10511
10512 heap->free_pages = NULL;
10513 ccan_list_for_each(&heap->pages, page, page_node) {
10514 page_list[i++] = page;
10515 GC_ASSERT(page);
10516 }
10517
10518 GC_ASSERT((size_t)i == total_pages);
10519
10520 /* Sort the heap so "filled pages" are first. `heap_add_page` adds to the
10521 * head of the list, so empty pages will end up at the start of the heap */
10522 ruby_qsort(page_list, total_pages, sizeof(struct heap_page *), compare_func, NULL);
10523
10524 /* Reset the eden heap */
10525 ccan_list_head_init(&heap->pages);
10526
10527 for (i = 0; i < total_pages; i++) {
10528 ccan_list_add(&heap->pages, &page_list[i]->page_node);
10529 if (page_list[i]->free_slots != 0) {
10530 heap_add_freepage(heap, page_list[i]);
10531 }
10532 }
10533
10534 free(page_list);
10535 }
10536}
10537#endif
10538
10539void
10540rb_gc_impl_register_pinning_obj(void *objspace_ptr, VALUE obj)
10541{
10542 /* no-op */
10543}
10544
10545bool
10546rb_gc_impl_object_moved_p(void *objspace_ptr, VALUE obj)
10547{
10548 return gc_object_moved_p(objspace_ptr, obj);
10549}
10550
10551static int
10552gc_ref_update(void *vstart, void *vend, size_t stride, rb_objspace_t *objspace, struct heap_page *page)
10553{
10554 VALUE v = (VALUE)vstart;
10555
10556 page->flags.has_uncollectible_wb_unprotected_objects = FALSE;
10557 page->flags.has_remembered_objects = FALSE;
10558
10559 /* For each object on the page */
10560 for (; v != (VALUE)vend; v += stride) {
10561 asan_unpoisoning_object(v) {
10562 switch (BUILTIN_TYPE(v)) {
10563 case T_NONE:
10564 case T_MOVED:
10565 case T_ZOMBIE:
10566 break;
10567 default:
10568 if (RVALUE_WB_UNPROTECTED(objspace, v)) {
10569 page->flags.has_uncollectible_wb_unprotected_objects = TRUE;
10570 }
10571 if (RVALUE_REMEMBERED(objspace, v)) {
10572 page->flags.has_remembered_objects = TRUE;
10573 }
10574 if (page->flags.before_sweep) {
10575 if (RVALUE_MARKED(objspace, v)) {
10576 rb_gc_update_object_references(objspace, v);
10577 }
10578 }
10579 else {
10580 rb_gc_update_object_references(objspace, v);
10581 }
10582 }
10583 }
10584 }
10585
10586 return 0;
10587}
10588
10589static int
10590gc_update_references_weak_table_i(VALUE obj, void *data)
10591{
10592 int ret;
10593 asan_unpoisoning_object(obj) {
10594 ret = BUILTIN_TYPE(obj) == T_MOVED ? ST_REPLACE : ST_CONTINUE;
10595 }
10596 return ret;
10597}
10598
10599static int
10600gc_update_references_weak_table_replace_i(VALUE *obj, void *data)
10601{
10602 rb_gc_update_moved(obj);
10603
10604 return ST_CONTINUE;
10605}
10606
10607/* The per-objspace side of the reference update: walk this objspace's heap objects and rewrite
10608 * moved references (following T_MOVED forwarding across objspaces). A compacting global GC
10609 * runs this for every objspace. */
10610static void
10611gc_update_references_heap(rb_objspace_t *objspace)
10612{
10613 struct heap_page *page = NULL;
10614
10615 for (int i = 0; i < HEAP_COUNT; i++) {
10616 bool should_set_mark_bits = TRUE;
10617 rb_heap_t *heap = &heaps[i];
10618
10619 ccan_list_for_each(&heap->pages, page, page_node) {
10620 uintptr_t start = (uintptr_t)page->start;
10621 uintptr_t end = start + (page->total_slots * heap->slot_size);
10622
10623 gc_ref_update((void *)start, (void *)end, heap->slot_size, objspace, page);
10624 if (page == heap->sweeping_page) {
10625 should_set_mark_bits = FALSE;
10626 }
10627 if (should_set_mark_bits) {
10628 gc_setup_mark_bits(page);
10629 }
10630 }
10631 }
10632}
10633
10634/* The VM-global side of the reference update (finalizer table, every Ractor's VM roots,
10635 * weak tables). Process-wide, so a compacting global GC runs it once after every heap
10636 * side: rb_gc_update_vm_references and the weak tables' mark_and_move are not idempotent. */
10637static void
10638gc_update_references_global(rb_objspace_t *objspace)
10639{
10640 gc_update_table_refs(finalizer_table);
10641
10642 rb_gc_update_vm_references((void *)objspace);
10643
10644 for (int table = 0; table < RB_GC_VM_WEAK_TABLE_COUNT; table++) {
10645 rb_gc_vm_weak_table_foreach(
10646 gc_update_references_weak_table_i,
10647 gc_update_references_weak_table_replace_i,
10648 NULL,
10649 false,
10650 table
10651 );
10652 }
10653}
10654
10655static void
10656gc_update_references(rb_objspace_t *objspace)
10657{
10658 objspace->flags.during_reference_updating = true;
10659
10660 rb_gc_before_updating_jit_code();
10661
10662 gc_update_references_heap(objspace);
10663 gc_update_references_global(objspace);
10664
10665 rb_gc_after_updating_jit_code();
10666
10667 objspace->flags.during_reference_updating = false;
10668}
10669
10670#if GC_CAN_COMPILE_COMPACTION
10671static void
10672root_obj_check_moved_i(const char *category, VALUE obj, void *data)
10673{
10674 rb_objspace_t *objspace = data;
10675
10676 if (gc_object_moved_p(objspace, obj)) {
10677 rb_bug("ROOT %s points to MOVED: %p -> %s", category, (void *)obj, rb_obj_info(rb_gc_impl_location(objspace, obj)));
10678 }
10679}
10680
10681static void
10682reachable_object_check_moved_i(VALUE ref, void *data)
10683{
10684 VALUE parent = (VALUE)data;
10685 if (gc_object_moved_p(rb_gc_get_objspace(), ref)) {
10686 rb_bug("Object %s points to MOVED: %p -> %s", rb_obj_info(parent), (void *)ref, rb_obj_info(rb_gc_impl_location(rb_gc_get_objspace(), ref)));
10687 }
10688}
10689
10690static int
10691heap_check_moved_i(void *vstart, void *vend, size_t stride, void *data)
10692{
10693 rb_objspace_t *objspace = data;
10694
10695 VALUE v = (VALUE)vstart;
10696 for (; v != (VALUE)vend; v += stride) {
10697 if (gc_object_moved_p(objspace, v)) {
10698 /* Moved object still on the heap, something may have a reference. */
10699 }
10700 else {
10701 asan_unpoisoning_object(v) {
10702 switch (BUILTIN_TYPE(v)) {
10703 case T_NONE:
10704 case T_ZOMBIE:
10705 break;
10706 default:
10707 if (!rb_gc_impl_garbage_object_p(objspace, v)) {
10708 rb_objspace_reachable_objects_from(v, reachable_object_check_moved_i, (void *)v);
10709 }
10710 }
10711 }
10712 }
10713 }
10714
10715 return 0;
10716}
10717#endif
10718
10719bool
10720rb_gc_impl_during_gc_p(void *objspace_ptr)
10721{
10722 rb_objspace_t *objspace = objspace_ptr;
10723
10724 return during_gc;
10725}
10726
10727#if RGENGC_PROFILE >= 2
10728
10729static const char*
10730type_name(int type, VALUE obj)
10731{
10732 switch ((enum ruby_value_type)type) {
10733 case RUBY_T_NONE: return "T_NONE";
10734 case RUBY_T_OBJECT: return "T_OBJECT";
10735 case RUBY_T_CLASS: return "T_CLASS";
10736 case RUBY_T_MODULE: return "T_MODULE";
10737 case RUBY_T_FLOAT: return "T_FLOAT";
10738 case RUBY_T_STRING: return "T_STRING";
10739 case RUBY_T_REGEXP: return "T_REGEXP";
10740 case RUBY_T_ARRAY: return "T_ARRAY";
10741 case RUBY_T_HASH: return "T_HASH";
10742 case RUBY_T_STRUCT: return "T_STRUCT";
10743 case RUBY_T_BIGNUM: return "T_BIGNUM";
10744 case RUBY_T_FILE: return "T_FILE";
10745 case RUBY_T_DATA: return "T_DATA";
10746 case RUBY_T_MATCH: return "T_MATCH";
10747 case RUBY_T_COMPLEX: return "T_COMPLEX";
10748 case RUBY_T_RATIONAL: return "T_RATIONAL";
10749 case RUBY_T_NIL: return "T_NIL";
10750 case RUBY_T_TRUE: return "T_TRUE";
10751 case RUBY_T_FALSE: return "T_FALSE";
10752 case RUBY_T_SYMBOL: return "T_SYMBOL";
10753 case RUBY_T_FIXNUM: return "T_FIXNUM";
10754 case RUBY_T_UNDEF: return "T_UNDEF";
10755 case RUBY_T_IMEMO: return "T_IMEMO";
10756 case RUBY_T_NODE: return "T_NODE";
10757 case RUBY_T_ICLASS: return "T_ICLASS";
10758 case RUBY_T_ZOMBIE: return "T_ZOMBIE";
10759 case RUBY_T_MOVED: return "T_MOVED";
10760 default: return "unknown";
10761 }
10762}
10763
10764static void
10765gc_count_add_each_types(VALUE hash, const char *name, const size_t *types)
10766{
10767 VALUE result = rb_hash_new_capa(T_MASK);
10768 int i;
10769 for (i=0; i<T_MASK; i++) {
10770 const char *type = type_name(i, 0);
10771 rb_hash_aset(result, ID2SYM(rb_intern(type)), SIZET2NUM(types[i]));
10772 }
10773 rb_hash_aset(hash, ID2SYM(rb_intern(name)), result);
10774}
10775#endif
10776
10777size_t
10778rb_gc_impl_gc_count(void *objspace_ptr)
10779{
10780 rb_objspace_t *objspace = objspace_ptr;
10781
10782 return objspace->profile.count;
10783}
10784
10785/* Filled by setup_gc_latest_gc_info_symbols() at boot, not on first use. */
10786static VALUE sym_major_by, sym_gc_by, sym_immediate_sweep, sym_have_finalizer, sym_state, sym_need_major_by;
10787static VALUE sym_nofree, sym_oldgen, sym_shady, sym_force, sym_stress;
10788#if RGENGC_ESTIMATE_OLDMALLOC
10789static VALUE sym_oldmalloc;
10790#endif
10791static VALUE sym_newobj, sym_malloc, sym_method, sym_capi;
10792static VALUE sym_none, sym_marking, sym_sweeping;
10793static VALUE sym_weak_references_count;
10794
10795static void
10796setup_gc_latest_gc_info_symbols(void)
10797{
10798#define S(s) sym_##s = ID2SYM(rb_intern_const(#s))
10799 S(major_by);
10800 S(gc_by);
10801 S(immediate_sweep);
10802 S(have_finalizer);
10803 S(state);
10804 S(need_major_by);
10805
10806 S(stress);
10807 S(nofree);
10808 S(oldgen);
10809 S(shady);
10810 S(force);
10811#if RGENGC_ESTIMATE_OLDMALLOC
10812 S(oldmalloc);
10813#endif
10814 S(newobj);
10815 S(malloc);
10816 S(method);
10817 S(capi);
10818
10819 S(none);
10820 S(marking);
10821 S(sweeping);
10822
10823 S(weak_references_count);
10824#undef S
10825}
10826
10827static VALUE
10828gc_info_decode(rb_objspace_t *objspace, const VALUE hash_or_key, const unsigned int orig_flags)
10829{
10830 VALUE hash = Qnil, key = Qnil;
10831 VALUE major_by, need_major_by;
10832 unsigned int flags = orig_flags ? orig_flags : objspace->profile.latest_gc_info;
10833
10834 if (SYMBOL_P(hash_or_key)) {
10835 key = hash_or_key;
10836 }
10837 else if (RB_TYPE_P(hash_or_key, T_HASH)) {
10838 hash = hash_or_key;
10839 }
10840 else {
10841 rb_bug("gc_info_decode: non-hash or symbol given");
10842 }
10843
10844#define SET(name, attr) \
10845 if (key == sym_##name) \
10846 return (attr); \
10847 else if (hash != Qnil) \
10848 rb_hash_aset(hash, sym_##name, (attr));
10849
10850 major_by =
10851 (flags & GPR_FLAG_MAJOR_BY_NOFREE) ? sym_nofree :
10852 (flags & GPR_FLAG_MAJOR_BY_OLDGEN) ? sym_oldgen :
10853 (flags & GPR_FLAG_MAJOR_BY_SHADY) ? sym_shady :
10854 (flags & GPR_FLAG_MAJOR_BY_FORCE) ? sym_force :
10855#if RGENGC_ESTIMATE_OLDMALLOC
10856 (flags & GPR_FLAG_MAJOR_BY_OLDMALLOC) ? sym_oldmalloc :
10857#endif
10858 Qnil;
10859 SET(major_by, major_by);
10860
10861 if (orig_flags == 0) { /* set need_major_by only if flags not set explicitly */
10862 unsigned int need_major_flags = gc_needs_major_flags;
10863 need_major_by =
10864 (need_major_flags & GPR_FLAG_MAJOR_BY_NOFREE) ? sym_nofree :
10865 (need_major_flags & GPR_FLAG_MAJOR_BY_OLDGEN) ? sym_oldgen :
10866 (need_major_flags & GPR_FLAG_MAJOR_BY_SHADY) ? sym_shady :
10867 (need_major_flags & GPR_FLAG_MAJOR_BY_FORCE) ? sym_force :
10868#if RGENGC_ESTIMATE_OLDMALLOC
10869 (need_major_flags & GPR_FLAG_MAJOR_BY_OLDMALLOC) ? sym_oldmalloc :
10870#endif
10871 Qnil;
10872 SET(need_major_by, need_major_by);
10873 }
10874
10875 SET(gc_by,
10876 (flags & GPR_FLAG_NEWOBJ) ? sym_newobj :
10877 (flags & GPR_FLAG_MALLOC) ? sym_malloc :
10878 (flags & GPR_FLAG_METHOD) ? sym_method :
10879 (flags & GPR_FLAG_CAPI) ? sym_capi :
10880 (flags & GPR_FLAG_STRESS) ? sym_stress :
10881 Qnil
10882 );
10883
10884 SET(have_finalizer, (flags & GPR_FLAG_HAVE_FINALIZE) ? Qtrue : Qfalse);
10885 SET(immediate_sweep, (flags & GPR_FLAG_IMMEDIATE_SWEEP) ? Qtrue : Qfalse);
10886
10887 if (orig_flags == 0) {
10888 SET(state, gc_mode(objspace) == gc_mode_none ? sym_none :
10889 gc_mode(objspace) == gc_mode_marking ? sym_marking : sym_sweeping);
10890 }
10891
10892 SET(weak_references_count, LONG2FIX(objspace->profile.weak_references_count));
10893#undef SET
10894
10895 if (!NIL_P(key)) {
10896 // Matched key should return above
10897 return Qundef;
10898 }
10899
10900 return hash;
10901}
10902
10903VALUE
10904rb_gc_impl_latest_gc_info(void *objspace_ptr, VALUE key)
10905{
10906 rb_objspace_t *objspace = objspace_ptr;
10907
10908 return gc_info_decode(objspace, key, 0);
10909}
10910
10911
10912enum gc_stat_sym {
10913 gc_stat_sym_count,
10914 gc_stat_sym_time,
10915 gc_stat_sym_marking_time,
10916 gc_stat_sym_sweeping_time,
10917 gc_stat_sym_heap_allocated_pages,
10918 gc_stat_sym_heap_empty_pages,
10919 gc_stat_sym_heap_allocatable_bytes,
10920 gc_stat_sym_heap_available_slots,
10921 gc_stat_sym_heap_live_slots,
10922 gc_stat_sym_heap_free_slots,
10923 gc_stat_sym_heap_final_slots,
10924 gc_stat_sym_heap_marked_slots,
10925 gc_stat_sym_heap_eden_pages,
10926 gc_stat_sym_total_allocated_pages,
10927 gc_stat_sym_total_freed_pages,
10928 gc_stat_sym_total_allocated_objects,
10929 gc_stat_sym_total_freed_objects,
10930 gc_stat_sym_total_malloc_bytes,
10931 gc_stat_sym_total_free_bytes,
10932 gc_stat_sym_malloc_increase_bytes,
10933 gc_stat_sym_malloc_increase_bytes_limit,
10934 gc_stat_sym_minor_gc_count,
10935 gc_stat_sym_major_gc_count,
10936 gc_stat_sym_global_gc_count,
10937 gc_stat_sym_compact_count,
10938 gc_stat_sym_read_barrier_faults,
10939 gc_stat_sym_total_moved_objects,
10940 gc_stat_sym_remembered_wb_unprotected_objects,
10941 gc_stat_sym_remembered_wb_unprotected_objects_limit,
10942 gc_stat_sym_old_objects,
10943 gc_stat_sym_old_objects_limit,
10944#if RGENGC_ESTIMATE_OLDMALLOC
10945 gc_stat_sym_oldmalloc_increase_bytes,
10946 gc_stat_sym_oldmalloc_increase_bytes_limit,
10947#endif
10948#if RGENGC_PROFILE
10949 gc_stat_sym_total_generated_normal_object_count,
10950 gc_stat_sym_total_generated_shady_object_count,
10951 gc_stat_sym_total_shade_operation_count,
10952 gc_stat_sym_total_promoted_count,
10953 gc_stat_sym_total_remembered_normal_object_count,
10954 gc_stat_sym_total_remembered_shady_object_count,
10955#endif
10956 gc_stat_sym_page_pool_arenas,
10957 gc_stat_sym_page_pool_arenas_freed,
10958 gc_stat_sym_page_pool_total_pages,
10959 gc_stat_sym_page_pool_discarded_pages,
10960 gc_stat_sym_last
10961};
10962
10963static VALUE gc_stat_symbols[gc_stat_sym_last];
10964
10965static void
10966setup_gc_stat_symbols(void)
10967{
10968#define S(s) gc_stat_symbols[gc_stat_sym_##s] = ID2SYM(rb_intern_const(#s))
10969 S(count);
10970 S(time);
10971 S(marking_time),
10972 S(sweeping_time),
10973 S(heap_allocated_pages);
10974 S(heap_empty_pages);
10975 S(heap_allocatable_bytes);
10976 S(heap_available_slots);
10977 S(heap_live_slots);
10978 S(heap_free_slots);
10979 S(heap_final_slots);
10980 S(heap_marked_slots);
10981 S(heap_eden_pages);
10982 S(total_allocated_pages);
10983 S(total_freed_pages);
10984 S(total_allocated_objects);
10985 S(total_freed_objects);
10986 S(total_malloc_bytes);
10987 S(total_free_bytes);
10988 S(malloc_increase_bytes);
10989 S(malloc_increase_bytes_limit);
10990 S(minor_gc_count);
10991 S(major_gc_count);
10992 S(global_gc_count);
10993 S(compact_count);
10994 S(read_barrier_faults);
10995 S(total_moved_objects);
10996 S(remembered_wb_unprotected_objects);
10997 S(remembered_wb_unprotected_objects_limit);
10998 S(old_objects);
10999 S(old_objects_limit);
11000#if RGENGC_ESTIMATE_OLDMALLOC
11001 S(oldmalloc_increase_bytes);
11002 S(oldmalloc_increase_bytes_limit);
11003#endif
11004#if RGENGC_PROFILE
11005 S(total_generated_normal_object_count);
11006 S(total_generated_shady_object_count);
11007 S(total_shade_operation_count);
11008 S(total_promoted_count);
11009 S(total_remembered_normal_object_count);
11010 S(total_remembered_shady_object_count);
11011#endif /* RGENGC_PROFILE */
11012 S(page_pool_arenas);
11013 S(page_pool_arenas_freed);
11014 S(page_pool_total_pages);
11015 S(page_pool_discarded_pages);
11016#undef S
11017}
11018
11019static uint64_t
11020ns_to_ms(uint64_t ns)
11021{
11022 return ns / (1000 * 1000);
11023}
11024
11025static void malloc_increase_local_flush(rb_objspace_t *objspace);
11026
11027static void
11028gc_process_stat_accumulate_i(void *objspace_ptr, void *data)
11029{
11030 rb_objspace_t *objspace = objspace_ptr;
11031 struct gc_process_stat_total *total = (struct gc_process_stat_total *)data;
11032 struct gc_process_stat_snapshot snap;
11033 rb_native_mutex_lock(&objspace->process_stat.lock);
11034 snap = objspace->process_stat.published;
11035 rb_native_mutex_unlock(&objspace->process_stat.lock);
11036 gc_process_stat_add(total, &snap);
11037}
11038
11039static VALUE
11040gc_process_stat(VALUE hash_or_sym)
11041{
11042 VALUE hash = Qnil, key = Qnil;
11043
11044 if (RB_TYPE_P(hash_or_sym, T_HASH)) {
11045 hash = hash_or_sym;
11046 }
11047 else if (SYMBOL_P(hash_or_sym)) {
11048 key = hash_or_sym;
11049 }
11050 else {
11051 rb_bug("non-hash or symbol given");
11052 }
11053
11054 struct gc_process_stat_total total;
11055 unsigned int lev = RB_GC_VM_LOCK();
11056 total = global_objspace->process_stat_archive;
11057 rb_gc_vm_each_objspace(gc_process_stat_accumulate_i, &total);
11058 RB_GC_VM_UNLOCK(lev);
11059
11060 /* Convert to Ruby values after all collector locks are released. */
11061 uint64_t time_ns = total.marking_time_ns + total.sweeping_time_ns;
11062
11063#define SET64(name, attr) \
11064 if (key == gc_stat_symbols[gc_stat_sym_##name]) \
11065 return ULL2NUM(attr); \
11066 else if (hash != Qnil) \
11067 rb_hash_aset(hash, gc_stat_symbols[gc_stat_sym_##name], ULL2NUM(attr));
11068
11069 SET64(count, total.count);
11070 SET64(time, ns_to_ms(time_ns));
11071 SET64(marking_time, ns_to_ms(total.marking_time_ns));
11072 SET64(sweeping_time, ns_to_ms(total.sweeping_time_ns));
11073 SET64(minor_gc_count, total.minor_gc_count);
11074 SET64(major_gc_count, total.major_gc_count);
11075
11076#undef SET64
11077
11078 if (!NIL_P(key)) {
11079 /* Matched key should return above. */
11080 return Qundef;
11081 }
11082
11083 return hash;
11084}
11085
11086VALUE
11087rb_gc_impl_stat(void *objspace_ptr, VALUE hash_or_sym)
11088{
11089 if (objspace_ptr == NULL) {
11090 return gc_process_stat(hash_or_sym);
11091 }
11092
11093 rb_objspace_t *objspace = objspace_ptr;
11094 VALUE hash = Qnil, key = Qnil;
11095
11096 malloc_increase_local_flush(objspace);
11097
11098 if (RB_TYPE_P(hash_or_sym, T_HASH)) {
11099 hash = hash_or_sym;
11100 }
11101 else if (SYMBOL_P(hash_or_sym)) {
11102 key = hash_or_sym;
11103 }
11104 else {
11105 rb_bug("non-hash or symbol given");
11106 }
11107
11108#define SET(name, attr) \
11109 if (key == gc_stat_symbols[gc_stat_sym_##name]) \
11110 return SIZET2NUM(attr); \
11111 else if (hash != Qnil) \
11112 rb_hash_aset(hash, gc_stat_symbols[gc_stat_sym_##name], SIZET2NUM(attr));
11113#define SET64(name, attr) \
11114 if (key == gc_stat_symbols[gc_stat_sym_##name]) \
11115 return ULL2NUM(attr); \
11116 else if (hash != Qnil) \
11117 rb_hash_aset(hash, gc_stat_symbols[gc_stat_sym_##name], ULL2NUM(attr));
11118
11119 SET(count, objspace->profile.count);
11120 SET(time, (size_t)ns_to_ms(objspace->profile.marking_time_ns + objspace->profile.sweeping_time_ns)); // TODO: UINT64T2NUM
11121 SET(marking_time, (size_t)ns_to_ms(objspace->profile.marking_time_ns));
11122 SET(sweeping_time, (size_t)ns_to_ms(objspace->profile.sweeping_time_ns));
11123
11124 {
11125 uint64_t total_malloc = (uint64_t)gc_counter_load_relaxed(&objspace->malloc_counters.counters.malloc);
11126 uint64_t total_free = (uint64_t)gc_counter_load_relaxed(&objspace->malloc_counters.counters.free);
11127 SET64(total_malloc_bytes, total_malloc);
11128 SET64(total_free_bytes, total_free);
11129 }
11130
11131 /* implementation dependent counters (small / fixnum-safe) */
11132 SET(heap_allocated_pages, rb_darray_size(objspace->heap_pages.sorted));
11133 SET(heap_empty_pages, objspace->empty_pages_count)
11134 SET(heap_allocatable_bytes, objspace->heap_pages.allocatable_bytes);
11135 SET(heap_eden_pages, heap_eden_total_pages(objspace));
11136 SET(total_allocated_pages, objspace->heap_pages.allocated_pages);
11137 SET(total_freed_pages, objspace->heap_pages.freed_pages);
11138 SET(malloc_increase_bytes, gc_malloc_counters_increase_unsigned(objspace, &objspace->malloc_counters.counters));
11139 SET(malloc_increase_bytes_limit, malloc_limit);
11140 SET(minor_gc_count, objspace->profile.minor_gc_count);
11141 SET(major_gc_count, objspace->profile.major_gc_count);
11142 SET(global_gc_count, global_objspace->global_gc.count);
11143 SET(compact_count, objspace->profile.compact_count);
11144 SET(read_barrier_faults, objspace->profile.read_barrier_faults);
11145 SET(total_moved_objects, objspace->rcompactor.total_moved);
11146 SET(remembered_wb_unprotected_objects, objspace->rgengc.uncollectible_wb_unprotected_objects);
11147 SET(remembered_wb_unprotected_objects_limit, objspace->rgengc.uncollectible_wb_unprotected_objects_limit);
11148 SET(old_objects, objspace->rgengc.old_objects);
11149 SET(old_objects_limit, objspace->rgengc.old_objects_limit);
11150#if RGENGC_ESTIMATE_OLDMALLOC
11151 SET(oldmalloc_increase_bytes, gc_malloc_counters_increase_unsigned(objspace, &objspace->malloc_counters.oldcounters));
11152 SET(oldmalloc_increase_bytes_limit, objspace->rgengc.oldmalloc_increase_limit);
11153#endif
11154
11155 SET(total_allocated_objects, total_allocated_objects(objspace));
11156 SET(total_freed_objects, total_freed_objects(objspace));
11157 SET(heap_available_slots, objspace_available_slots(objspace));
11158 SET(heap_live_slots, objspace_live_slots(objspace));
11159 SET(heap_free_slots, objspace_free_slots(objspace));
11160 SET(heap_final_slots, total_final_slots_count(objspace));
11161 SET(heap_marked_slots, objspace->marked_slots);
11162
11163 SET(page_pool_arenas, global_objspace->page_pool.arena_count);
11164 SET(page_pool_arenas_freed, global_objspace->page_pool.arenas_unmapped);
11165 SET(page_pool_total_pages, (size_t)global_objspace->page_pool.arena_count * PAGE_POOL_ARENA_BODIES);
11166 SET(page_pool_discarded_pages, global_objspace->page_pool.advised_count);
11167
11168#if RGENGC_PROFILE
11169 SET(total_generated_normal_object_count, objspace->profile.total_generated_normal_object_count);
11170 SET(total_generated_shady_object_count, objspace->profile.total_generated_shady_object_count);
11171 SET(total_shade_operation_count, objspace->profile.total_shade_operation_count);
11172 SET(total_promoted_count, objspace->profile.total_promoted_count);
11173 SET(total_remembered_normal_object_count, objspace->profile.total_remembered_normal_object_count);
11174 SET(total_remembered_shady_object_count, objspace->profile.total_remembered_shady_object_count);
11175#endif /* RGENGC_PROFILE */
11176#undef SET
11177#undef SET64
11178
11179 if (!NIL_P(key)) {
11180 // Matched key should return above
11181 return Qundef;
11182 }
11183
11184#if defined(RGENGC_PROFILE) && RGENGC_PROFILE >= 2
11185 if (hash != Qnil) {
11186 gc_count_add_each_types(hash, "generated_normal_object_count_types", objspace->profile.generated_normal_object_count_types);
11187 gc_count_add_each_types(hash, "generated_shady_object_count_types", objspace->profile.generated_shady_object_count_types);
11188 gc_count_add_each_types(hash, "shade_operation_count_types", objspace->profile.shade_operation_count_types);
11189 gc_count_add_each_types(hash, "promoted_types", objspace->profile.promoted_types);
11190 gc_count_add_each_types(hash, "remembered_normal_object_count_types", objspace->profile.remembered_normal_object_count_types);
11191 gc_count_add_each_types(hash, "remembered_shady_object_count_types", objspace->profile.remembered_shady_object_count_types);
11192 }
11193#endif
11194
11195 return hash;
11196}
11197
11198enum gc_stat_heap_sym {
11199 gc_stat_heap_sym_slot_size,
11200 gc_stat_heap_sym_heap_live_slots,
11201 gc_stat_heap_sym_heap_free_slots,
11202 gc_stat_heap_sym_heap_final_slots,
11203 gc_stat_heap_sym_heap_eden_pages,
11204 gc_stat_heap_sym_heap_eden_slots,
11205 gc_stat_heap_sym_total_allocated_pages,
11206 gc_stat_heap_sym_force_major_gc_count,
11207 gc_stat_heap_sym_force_incremental_marking_finish_count,
11208 gc_stat_heap_sym_heap_allocatable_slots,
11209 gc_stat_heap_sym_total_allocated_objects,
11210 gc_stat_heap_sym_total_freed_objects,
11211 gc_stat_heap_sym_last
11212};
11213
11214static VALUE gc_stat_heap_symbols[gc_stat_heap_sym_last];
11215
11216static void
11217setup_gc_stat_heap_symbols(void)
11218{
11219#define S(s) gc_stat_heap_symbols[gc_stat_heap_sym_##s] = ID2SYM(rb_intern_const(#s))
11220 S(slot_size);
11221 S(heap_live_slots);
11222 S(heap_free_slots);
11223 S(heap_final_slots);
11224 S(heap_eden_pages);
11225 S(heap_eden_slots);
11226 S(heap_allocatable_slots);
11227 S(total_allocated_pages);
11228 S(force_major_gc_count);
11229 S(force_incremental_marking_finish_count);
11230 S(total_allocated_objects);
11231 S(total_freed_objects);
11232#undef S
11233}
11234
11235static VALUE
11236stat_one_heap(rb_objspace_t *objspace, rb_heap_t *heap, VALUE hash, VALUE key)
11237{
11238#define SET(name, attr) \
11239 if (key == gc_stat_heap_symbols[gc_stat_heap_sym_##name]) \
11240 return SIZET2NUM(attr); \
11241 else if (hash != Qnil) \
11242 rb_hash_aset(hash, gc_stat_heap_symbols[gc_stat_heap_sym_##name], SIZET2NUM(attr));
11243
11244 SET(slot_size, heap->slot_size);
11245 SET(heap_live_slots, heap->total_allocated_objects - heap->total_freed_objects - heap->final_slots_count);
11246 SET(heap_free_slots, heap->total_slots - (heap->total_allocated_objects - heap->total_freed_objects));
11247 SET(heap_final_slots, heap->final_slots_count);
11248 SET(heap_eden_pages, heap->total_pages);
11249 SET(heap_eden_slots, heap->total_slots);
11250 SET(heap_allocatable_slots, objspace->heap_pages.allocatable_bytes / heap->slot_size);
11251 SET(total_allocated_pages, heap->total_allocated_pages);
11252 SET(force_major_gc_count, heap->force_major_gc_count);
11253 SET(force_incremental_marking_finish_count, heap->force_incremental_marking_finish_count);
11254 SET(total_allocated_objects, heap->total_allocated_objects);
11255 SET(total_freed_objects, heap->total_freed_objects);
11256#undef SET
11257
11258 if (!NIL_P(key)) {
11259 // Matched key should return above
11260 return Qundef;
11261 }
11262
11263 return hash;
11264}
11265
11266VALUE
11267rb_gc_impl_stat_heap(void *objspace_ptr, VALUE heap_name, VALUE hash_or_sym)
11268{
11269 rb_objspace_t *objspace = objspace_ptr;
11270
11271 if (NIL_P(heap_name)) {
11272 if (!RB_TYPE_P(hash_or_sym, T_HASH)) {
11273 rb_bug("non-hash given");
11274 }
11275
11276 for (int i = 0; i < HEAP_COUNT; i++) {
11277 VALUE hash = rb_hash_aref(hash_or_sym, INT2FIX(i));
11278 if (NIL_P(hash)) {
11279 hash = rb_hash_new();
11280 rb_hash_aset(hash_or_sym, INT2FIX(i), hash);
11281 }
11282
11283 stat_one_heap(objspace, &heaps[i], hash, Qnil);
11284 }
11285 }
11286 else if (FIXNUM_P(heap_name)) {
11287 int heap_idx = FIX2INT(heap_name);
11288
11289 if (heap_idx < 0 || heap_idx >= HEAP_COUNT) {
11290 rb_raise(rb_eArgError, "size pool index out of range");
11291 }
11292
11293 if (SYMBOL_P(hash_or_sym)) {
11294 return stat_one_heap(objspace, &heaps[heap_idx], Qnil, hash_or_sym);
11295 }
11296 else if (RB_TYPE_P(hash_or_sym, T_HASH)) {
11297 return stat_one_heap(objspace, &heaps[heap_idx], hash_or_sym, Qnil);
11298 }
11299 else {
11300 rb_bug("non-hash or symbol given");
11301 }
11302 }
11303 else {
11304 rb_bug("heap_name must be nil or an Integer");
11305 }
11306
11307 return hash_or_sym;
11308}
11309
11310/* I could include internal.h for this, but doing so undefines some Array macros
11311 * necessary for initialising objects, and I don't want to include all the array
11312 * headers to get them back
11313 * TODO: Investigate why RARRAY_AREF gets undefined in internal.h
11314 */
11315#ifndef RBOOL
11316#define RBOOL(v) (v ? Qtrue : Qfalse)
11317#endif
11318
11319VALUE
11320rb_gc_impl_config_get(void *objspace_ptr)
11321{
11322#define sym(name) ID2SYM(rb_intern_const(name))
11323 rb_objspace_t *objspace = objspace_ptr;
11324 VALUE hash = rb_hash_new();
11325
11326 rb_hash_aset(hash, sym("rgengc_allow_full_mark"), RBOOL(gc_config_full_mark_val));
11327
11328 return hash;
11329}
11330
11331static int
11332gc_config_set_key(VALUE key, VALUE value, VALUE data)
11333{
11335 if (rb_sym2id(key) == rb_intern("rgengc_allow_full_mark")) {
11336 gc_rest(objspace);
11337 gc_config_full_mark_set(RTEST(value));
11338 }
11339 return ST_CONTINUE;
11340}
11341
11342void
11343rb_gc_impl_config_set(void *objspace_ptr, VALUE hash)
11344{
11345 rb_objspace_t *objspace = objspace_ptr;
11346
11347 if (!RB_TYPE_P(hash, T_HASH)) {
11348 rb_raise(rb_eArgError, "expected keyword arguments");
11349 }
11350
11351 rb_hash_foreach(hash, gc_config_set_key, (st_data_t)objspace);
11352}
11353
11354VALUE
11355rb_gc_impl_stress_get(void *objspace_ptr)
11356{
11357 return ruby_gc_stress_mode;
11358}
11359
11360void
11361rb_gc_impl_stress_set(void *objspace_ptr, VALUE flag)
11362{
11363 global_objspace->gc_stressful = RTEST(flag);
11364 global_objspace->gc_stress_mode = flag;
11365}
11366
11367static int
11368get_envparam_size(const char *name, size_t *default_value, size_t lower_bound)
11369{
11370 const char *ptr = getenv(name);
11371 ssize_t val;
11372
11373 if (ptr != NULL && *ptr) {
11374 size_t unit = 0;
11375 char *end;
11376#if SIZEOF_SIZE_T == SIZEOF_LONG_LONG
11377 val = strtoll(ptr, &end, 0);
11378#else
11379 val = strtol(ptr, &end, 0);
11380#endif
11381 switch (*end) {
11382 case 'k': case 'K':
11383 unit = 1024;
11384 ++end;
11385 break;
11386 case 'm': case 'M':
11387 unit = 1024*1024;
11388 ++end;
11389 break;
11390 case 'g': case 'G':
11391 unit = 1024*1024*1024;
11392 ++end;
11393 break;
11394 }
11395 while (*end && isspace((unsigned char)*end)) end++;
11396 if (*end) {
11397 if (RTEST(ruby_verbose)) fprintf(stderr, "invalid string for %s: %s\n", name, ptr);
11398 return 0;
11399 }
11400 if (unit > 0) {
11401 if (val < -(ssize_t)(SIZE_MAX / 2 / unit) || (ssize_t)(SIZE_MAX / 2 / unit) < val) {
11402 if (RTEST(ruby_verbose)) fprintf(stderr, "%s=%s is ignored because it overflows\n", name, ptr);
11403 return 0;
11404 }
11405 val *= unit;
11406 }
11407 if (val > 0 && (size_t)val > lower_bound) {
11408 if (RTEST(ruby_verbose)) {
11409 fprintf(stderr, "%s=%"PRIdSIZE" (default value: %"PRIuSIZE")\n", name, val, *default_value);
11410 }
11411 *default_value = (size_t)val;
11412 return 1;
11413 }
11414 else {
11415 if (RTEST(ruby_verbose)) {
11416 fprintf(stderr, "%s=%"PRIdSIZE" (default value: %"PRIuSIZE") is ignored because it must be greater than %"PRIuSIZE".\n",
11417 name, val, *default_value, lower_bound);
11418 }
11419 return 0;
11420 }
11421 }
11422 return 0;
11423}
11424
11425static int
11426get_envparam_double(const char *name, double *default_value, double lower_bound, double upper_bound, int accept_zero)
11427{
11428 const char *ptr = getenv(name);
11429 double val;
11430
11431 if (ptr != NULL && *ptr) {
11432 char *end;
11433 val = strtod(ptr, &end);
11434 if (!*ptr || *end) {
11435 if (RTEST(ruby_verbose)) fprintf(stderr, "invalid string for %s: %s\n", name, ptr);
11436 return 0;
11437 }
11438
11439 if (accept_zero && val == 0.0) {
11440 goto accept;
11441 }
11442 else if (val <= lower_bound) {
11443 if (RTEST(ruby_verbose)) {
11444 fprintf(stderr, "%s=%f (default value: %f) is ignored because it must be greater than %f.\n",
11445 name, val, *default_value, lower_bound);
11446 }
11447 }
11448 else if (upper_bound != 0.0 && /* ignore upper_bound if it is 0.0 */
11449 val > upper_bound) {
11450 if (RTEST(ruby_verbose)) {
11451 fprintf(stderr, "%s=%f (default value: %f) is ignored because it must be lower than %f.\n",
11452 name, val, *default_value, upper_bound);
11453 }
11454 }
11455 else {
11456 goto accept;
11457 }
11458 }
11459 return 0;
11460
11461 accept:
11462 if (RTEST(ruby_verbose)) fprintf(stderr, "%s=%f (default value: %f)\n", name, val, *default_value);
11463 *default_value = val;
11464 return 1;
11465}
11466
11467/*
11468 * GC tuning environment variables
11469 *
11470 * * RUBY_GC_HEAP_FREE_SLOTS
11471 * - Prepare at least this amount of slots after GC.
11472 * - Allocate slots if there are not enough slots.
11473 * * RUBY_GC_HEAP_GROWTH_FACTOR (new from 2.1)
11474 * - Allocate slots by this factor.
11475 * - (next slots number) = (current slots number) * (this factor)
11476 * * RUBY_GC_HEAP_GROWTH_MAX_BYTES (was RUBY_GC_HEAP_GROWTH_MAX_SLOTS)
11477 * - Allocation rate is limited to this number of bytes.
11478 * * RUBY_GC_HEAP_FREE_SLOTS_MIN_RATIO (new from 2.4)
11479 * - Allocate additional pages when the number of free slots is
11480 * lower than the value (total_slots * (this ratio)).
11481 * * RUBY_GC_HEAP_FREE_SLOTS_GOAL_RATIO (new from 2.4)
11482 * - Allocate slots to satisfy this formula:
11483 * free_slots = total_slots * goal_ratio
11484 * - In other words, prepare (total_slots * goal_ratio) free slots.
11485 * - if this value is 0.0, then use RUBY_GC_HEAP_GROWTH_FACTOR directly.
11486 * * RUBY_GC_HEAP_FREE_SLOTS_MAX_RATIO (new from 2.4)
11487 * - Allow to free pages when the number of free slots is
11488 * greater than the value (total_slots * (this ratio)).
11489 * * RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR (new from 2.1.1)
11490 * - Do full GC when the number of old objects is more than R * N
11491 * where R is this factor and
11492 * N is the number of old objects just after last full GC.
11493 *
11494 * * obsolete
11495 * * RUBY_FREE_MIN -> RUBY_GC_HEAP_FREE_SLOTS (from 2.1)
11496 * * RUBY_HEAP_MIN_SLOTS -> RUBY_GC_HEAP_INIT_SLOTS (from 2.1) -> RUBY_GC_HEAP_INIT_BYTES
11497 *
11498 * * RUBY_GC_MALLOC_LIMIT
11499 * * RUBY_GC_MALLOC_LIMIT_MAX (new from 2.1)
11500 * * RUBY_GC_MALLOC_LIMIT_GROWTH_FACTOR (new from 2.1)
11501 *
11502 * * RUBY_GC_OLDMALLOC_LIMIT (new from 2.1)
11503 * * RUBY_GC_OLDMALLOC_LIMIT_MAX (new from 2.1)
11504 * * RUBY_GC_OLDMALLOC_LIMIT_GROWTH_FACTOR (new from 2.1)
11505 */
11506
11507void
11508rb_gc_impl_set_params(void *objspace_ptr)
11509{
11510 rb_objspace_t *objspace = objspace_ptr;
11511 get_envparam_size("RUBY_GC_HEAP_FREE_SLOTS", &gc_params.heap_free_slots, 0);
11512
11513 get_envparam_size("RUBY_GC_HEAP_INIT_BYTES", &gc_params.heap_init_bytes,
11514 heap_init_bytes_min() - 1);
11515 get_envparam_size("RUBY_GC_RACTOR_HEAP_INIT_BYTES", &gc_params.ractor_heap_init_bytes,
11516 heap_init_bytes_min() - 1);
11517
11518 get_envparam_double("RUBY_GC_HEAP_GROWTH_FACTOR", &gc_params.growth_factor, 1.0, 0.0, FALSE);
11519 get_envparam_size ("RUBY_GC_HEAP_GROWTH_MAX_BYTES", &gc_params.growth_max_bytes, 0);
11520 get_envparam_double("RUBY_GC_HEAP_FREE_SLOTS_MIN_RATIO", &gc_params.heap_free_slots_min_ratio,
11521 0.0, 1.0, FALSE);
11522 get_envparam_double("RUBY_GC_HEAP_FREE_SLOTS_MAX_RATIO", &gc_params.heap_free_slots_max_ratio,
11523 gc_params.heap_free_slots_min_ratio, 1.0, FALSE);
11524 get_envparam_double("RUBY_GC_HEAP_FREE_SLOTS_GOAL_RATIO", &gc_params.heap_free_slots_goal_ratio,
11525 gc_params.heap_free_slots_min_ratio, gc_params.heap_free_slots_max_ratio, TRUE);
11526 get_envparam_double("RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR", &gc_params.oldobject_limit_factor, 0.0, 0.0, TRUE);
11527 get_envparam_double("RUBY_GC_HEAP_REMEMBERED_WB_UNPROTECTED_OBJECTS_LIMIT_RATIO", &gc_params.uncollectible_wb_unprotected_objects_limit_ratio, 0.0, 0.0, TRUE);
11528
11529 if (get_envparam_size("RUBY_GC_MALLOC_LIMIT", &gc_params.malloc_limit_min, 0)) {
11530 malloc_limit = gc_params.malloc_limit_min;
11531 }
11532 get_envparam_size ("RUBY_GC_MALLOC_LIMIT_MAX", &gc_params.malloc_limit_max, 0);
11533 if (!gc_params.malloc_limit_max) { /* ignore max-check if 0 */
11534 gc_params.malloc_limit_max = SIZE_MAX;
11535 }
11536 get_envparam_double("RUBY_GC_MALLOC_LIMIT_GROWTH_FACTOR", &gc_params.malloc_limit_growth_factor, 1.0, 0.0, FALSE);
11537
11538#if RGENGC_ESTIMATE_OLDMALLOC
11539 if (get_envparam_size("RUBY_GC_OLDMALLOC_LIMIT", &gc_params.oldmalloc_limit_min, 0)) {
11540 objspace->rgengc.oldmalloc_increase_limit = gc_params.oldmalloc_limit_min;
11541 }
11542 get_envparam_size ("RUBY_GC_OLDMALLOC_LIMIT_MAX", &gc_params.oldmalloc_limit_max, 0);
11543 get_envparam_double("RUBY_GC_OLDMALLOC_LIMIT_GROWTH_FACTOR", &gc_params.oldmalloc_limit_growth_factor, 1.0, 0.0, FALSE);
11544#endif
11545}
11546
11547static inline size_t
11548objspace_malloc_size(rb_objspace_t *objspace, void *ptr, size_t hint)
11549{
11550#ifdef HAVE_MALLOC_USABLE_SIZE
11551 if (!hint) {
11552 hint = malloc_usable_size(ptr);
11553 }
11554#endif
11555 return hint;
11556}
11557
11558enum memop_type {
11559 MEMOP_TYPE_MALLOC = 0,
11560 MEMOP_TYPE_FREE,
11561 MEMOP_TYPE_REALLOC
11562};
11563
11564static inline void
11565atomic_sub_nounderflow(size_t *var, size_t sub)
11566{
11567 if (sub == 0) return;
11568
11569 while (1) {
11570 size_t val = *var;
11571 if (val < sub) sub = val;
11572 if (RUBY_ATOMIC_SIZE_CAS(*var, val, val-sub) == val) break;
11573 }
11574}
11575
11576#define gc_stress_full_mark_after_malloc_p() \
11577 (FIXNUM_P(ruby_gc_stress_mode) && (FIX2LONG(ruby_gc_stress_mode) & (1<<gc_stress_full_mark_after_malloc)))
11578
11579static void
11580objspace_malloc_gc_stress(rb_objspace_t *objspace)
11581{
11582 if (ruby_gc_stressful && ruby_native_thread_p()) {
11583 unsigned int reason = (GPR_FLAG_IMMEDIATE_MARK | GPR_FLAG_IMMEDIATE_SWEEP |
11584 GPR_FLAG_STRESS | GPR_FLAG_MALLOC);
11585
11586 if (gc_stress_full_mark_after_malloc_p()) {
11587 reason |= GPR_FLAG_FULL_MARK;
11588 }
11589 garbage_collect_with_gvl(objspace, reason);
11590 }
11591}
11592
11593static void
11594malloc_increase_commit(rb_objspace_t *objspace, size_t new_size, size_t old_size)
11595{
11596 if (new_size > old_size) {
11597 size_t delta = new_size - old_size;
11598 MALLOC_COUNTERS_LOCK(objspace);
11599 gc_counter_add(&objspace->malloc_counters.counters.malloc, delta);
11600#if RGENGC_ESTIMATE_OLDMALLOC
11601 gc_counter_add(&objspace->malloc_counters.oldcounters.malloc, delta);
11602#endif
11603 MALLOC_COUNTERS_UNLOCK(objspace);
11604 }
11605 else if (old_size > new_size) {
11606 size_t delta = old_size - new_size;
11607 MALLOC_COUNTERS_LOCK(objspace);
11608 gc_counter_add(&objspace->malloc_counters.counters.free, delta);
11609#if RGENGC_ESTIMATE_OLDMALLOC
11610 gc_counter_add(&objspace->malloc_counters.oldcounters.free, delta);
11611#endif
11612 MALLOC_COUNTERS_UNLOCK(objspace);
11613 }
11614}
11615
11616#if USE_MALLOC_INCREASE_LOCAL
11617static void
11618malloc_increase_local_flush(rb_objspace_t *objspace)
11619{
11620 int delta = malloc_increase_local;
11621 if (delta == 0) return;
11622
11623 malloc_increase_local = 0;
11624 if (delta > 0) {
11625 malloc_increase_commit(objspace, (size_t)delta, 0);
11626 }
11627 else {
11628 malloc_increase_commit(objspace, 0, (size_t)(-delta));
11629 }
11630}
11631#else
11632static void
11633malloc_increase_local_flush(rb_objspace_t *objspace)
11634{
11635}
11636#endif
11637
11638static inline bool
11639objspace_malloc_increase_report(rb_objspace_t *objspace, void *mem, size_t new_size, size_t old_size, enum memop_type type, bool gc_allowed)
11640{
11641 if (0) fprintf(stderr, "increase - ptr: %p, type: %s, new_size: %"PRIdSIZE", old_size: %"PRIdSIZE"\n",
11642 mem,
11643 type == MEMOP_TYPE_MALLOC ? "malloc" :
11644 type == MEMOP_TYPE_FREE ? "free " :
11645 type == MEMOP_TYPE_REALLOC ? "realloc": "error",
11646 new_size, old_size);
11647 return false;
11648}
11649
11650static bool
11651objspace_malloc_increase_body(rb_objspace_t *objspace, void *mem, size_t new_size, size_t old_size, enum memop_type type, bool gc_allowed)
11652{
11653#if USE_MALLOC_INCREASE_LOCAL
11654 if (new_size < GC_MALLOC_INCREASE_LOCAL_THRESHOLD &&
11655 old_size < GC_MALLOC_INCREASE_LOCAL_THRESHOLD) {
11656 malloc_increase_local += (int)new_size - (int)old_size;
11657
11658 if (malloc_increase_local >= GC_MALLOC_INCREASE_LOCAL_THRESHOLD ||
11659 malloc_increase_local <= -GC_MALLOC_INCREASE_LOCAL_THRESHOLD) {
11660 malloc_increase_local_flush(objspace);
11661 }
11662 }
11663 else {
11664 malloc_increase_local_flush(objspace);
11665 malloc_increase_commit(objspace, new_size, old_size);
11666 }
11667#else
11668 malloc_increase_commit(objspace, new_size, old_size);
11669#endif
11670
11671 if (type == MEMOP_TYPE_MALLOC && gc_allowed) {
11672 retry:
11673 if (malloc_increase > malloc_limit && ruby_native_thread_p() && !dont_gc_val() && !rb_gc_gc_disabled_global_p()) {
11674 if (ruby_thread_has_gvl_p() && is_lazy_sweeping(objspace)) {
11675 gc_sweep_step_for_malloc(objspace); /* sweeping frees may reduce malloc_increase */
11676 goto retry;
11677 }
11678 garbage_collect_with_gvl(objspace, GPR_FLAG_MALLOC);
11679 }
11680 }
11681
11682#if MALLOC_ALLOCATED_SIZE
11683 if (new_size >= old_size) {
11684 RUBY_ATOMIC_SIZE_ADD(objspace->malloc_params.allocated_size, new_size - old_size);
11685 }
11686 else {
11687 size_t dec_size = old_size - new_size;
11688
11689#if MALLOC_ALLOCATED_SIZE_CHECK
11690 size_t allocated_size = objspace->malloc_params.allocated_size;
11691 if (allocated_size < dec_size) {
11692 rb_bug("objspace_malloc_increase: underflow malloc_params.allocated_size.");
11693 }
11694#endif
11695 atomic_sub_nounderflow(&objspace->malloc_params.allocated_size, dec_size);
11696 }
11697
11698 switch (type) {
11699 case MEMOP_TYPE_MALLOC:
11700 RUBY_ATOMIC_SIZE_INC(objspace->malloc_params.allocations);
11701 break;
11702 case MEMOP_TYPE_FREE:
11703 {
11704 size_t allocations = objspace->malloc_params.allocations;
11705 if (allocations > 0) {
11706 atomic_sub_nounderflow(&objspace->malloc_params.allocations, 1);
11707 }
11708#if MALLOC_ALLOCATED_SIZE_CHECK
11709 else {
11710 GC_ASSERT(objspace->malloc_params.allocations > 0);
11711 }
11712#endif
11713 }
11714 break;
11715 case MEMOP_TYPE_REALLOC: /* ignore */ break;
11716 }
11717#endif
11718 return true;
11719}
11720
11721#define objspace_malloc_increase(...) \
11722 for (bool malloc_increase_done = objspace_malloc_increase_report(__VA_ARGS__); \
11723 !malloc_increase_done; \
11724 malloc_increase_done = objspace_malloc_increase_body(__VA_ARGS__))
11725
11726struct malloc_obj_info { /* 4 words */
11727 size_t size;
11728};
11729
11730static inline size_t
11731objspace_malloc_prepare(rb_objspace_t *objspace, size_t size)
11732{
11733 if (size == 0) size = 1;
11734
11735#if CALC_EXACT_MALLOC_SIZE
11736 size += sizeof(struct malloc_obj_info);
11737#endif
11738
11739 return size;
11740}
11741
11742static bool
11743malloc_during_gc_p(rb_objspace_t *objspace)
11744{
11745 /* malloc is not allowed during GC when we're not using multiple ractors
11746 * (since ractors can run while another thread is sweeping) and when we
11747 * have the GVL (since if we don't have the GVL, we'll try to acquire the
11748 * GVL which will block and ensure the other thread finishes GC). */
11749 return during_gc && !dont_gc_val() && !rb_gc_multi_ractor_p() && ruby_thread_has_gvl_p();
11750}
11751
11752static inline void *
11753objspace_malloc_fixup(rb_objspace_t *objspace, void *mem, size_t size, bool gc_allowed)
11754{
11755 size = objspace_malloc_size(objspace, mem, size);
11756 objspace_malloc_increase(objspace, mem, size, 0, MEMOP_TYPE_MALLOC, gc_allowed) {}
11757
11758#if CALC_EXACT_MALLOC_SIZE
11759 {
11760 struct malloc_obj_info *info = mem;
11761 info->size = size;
11762 mem = info + 1;
11763 }
11764#endif
11765
11766 return mem;
11767}
11768
11769#if defined(__GNUC__) && RUBY_DEBUG
11770#define RB_BUG_INSTEAD_OF_RB_MEMERROR 1
11771#endif
11772
11773#ifndef RB_BUG_INSTEAD_OF_RB_MEMERROR
11774# define RB_BUG_INSTEAD_OF_RB_MEMERROR 0
11775#endif
11776
11777#define GC_MEMERROR(...) \
11778 ((RB_BUG_INSTEAD_OF_RB_MEMERROR+0) ? rb_bug("" __VA_ARGS__) : (void)0)
11779
11780#define TRY_WITH_GC(siz, expr) do { \
11781 const gc_profile_record_flag gpr = \
11782 GPR_FLAG_FULL_MARK | \
11783 GPR_FLAG_IMMEDIATE_MARK | \
11784 GPR_FLAG_IMMEDIATE_SWEEP | \
11785 GPR_FLAG_MALLOC; \
11786 /* stress GC must also honor gc_allowed (malloc_gc_disabled) */ \
11787 if (gc_allowed) objspace_malloc_gc_stress(objspace); \
11788 \
11789 if (RB_LIKELY((expr))) { \
11790 /* Success on 1st try */ \
11791 } \
11792 else if (gc_allowed && !garbage_collect_with_gvl(objspace, gpr)) { \
11793 /* @shyouhei thinks this doesn't happen */ \
11794 GC_MEMERROR("TRY_WITH_GC: could not GC"); \
11795 } \
11796 else if ((expr)) { \
11797 /* Success on 2nd try */ \
11798 } \
11799 else { \
11800 GC_MEMERROR("TRY_WITH_GC: could not allocate:" \
11801 "%"PRIdSIZE" bytes for %s", \
11802 siz, # expr); \
11803 } \
11804 } while (0)
11805
11806static void
11807check_malloc_not_in_gc(rb_objspace_t *objspace, const char *msg)
11808{
11809 if (RB_UNLIKELY(malloc_during_gc_p(objspace))) {
11810 dont_gc_on();
11811 during_gc = false;
11812 rb_bug("Cannot %s during GC", msg);
11813 }
11814}
11815
11816void
11817rb_gc_impl_free(void *objspace_ptr, void *ptr, size_t old_size)
11818{
11819 rb_objspace_t *objspace = objspace_ptr;
11820
11821 if (!ptr) {
11822 /*
11823 * ISO/IEC 9899 says "If ptr is a null pointer, no action occurs" since
11824 * its first version. We would better follow.
11825 */
11826 return;
11827 }
11828#if CALC_EXACT_MALLOC_SIZE
11829 struct malloc_obj_info *info = (struct malloc_obj_info *)ptr - 1;
11830#if VERIFY_FREE_SIZE
11831 if (!info->size) {
11832 const char *freeing = gc_freeing_obj_info();
11833 rb_bug("buffer %p has no recorded size%s%s. Was it allocated with ruby_mimalloc? If so it should be freed with ruby_mimfree", ptr,
11834 freeing ? ", while freeing " : "", freeing ? freeing : "");
11835 }
11836
11837 if (old_size && (old_size + sizeof(struct malloc_obj_info)) != info->size) {
11838 const char *freeing = gc_freeing_obj_info();
11839 rb_bug("buffer %p freed with old_size=%zu, but was allocated with size=%zu%s%s", ptr, old_size, info->size - sizeof(struct malloc_obj_info),
11840 freeing ? ", while freeing " : "", freeing ? freeing : "");
11841 }
11842#endif
11843 ptr = info;
11844 old_size = info->size;
11845#endif
11846 old_size = objspace_malloc_size(objspace, ptr, old_size);
11847
11848 objspace_malloc_increase(objspace, ptr, 0, old_size, MEMOP_TYPE_FREE, true) {
11849 free(ptr);
11850 ptr = NULL;
11851 RB_DEBUG_COUNTER_INC(heap_xfree);
11852 }
11853}
11854
11855void *
11856rb_gc_impl_malloc(void *objspace_ptr, size_t size, bool gc_allowed)
11857{
11858 rb_objspace_t *objspace = objspace_ptr;
11859 check_malloc_not_in_gc(objspace, "malloc");
11860
11861 void *mem;
11862
11863 size = objspace_malloc_prepare(objspace, size);
11864 TRY_WITH_GC(size, mem = malloc(size));
11865 RB_DEBUG_COUNTER_INC(heap_xmalloc);
11866 if (!mem) return mem;
11867 return objspace_malloc_fixup(objspace, mem, size, gc_allowed);
11868}
11869
11870void *
11871rb_gc_impl_calloc(void *objspace_ptr, size_t size, bool gc_allowed)
11872{
11873 rb_objspace_t *objspace = objspace_ptr;
11874
11875 if (RB_UNLIKELY(malloc_during_gc_p(objspace))) {
11876 rb_warn("calloc during GC detected, this could cause crashes if it triggers another GC");
11877#if RGENGC_CHECK_MODE || RUBY_DEBUG
11878 rb_bug("Cannot calloc during GC");
11879#endif
11880 }
11881
11882 void *mem;
11883
11884 size = objspace_malloc_prepare(objspace, size);
11885 TRY_WITH_GC(size, mem = calloc1(size));
11886 if (!mem) return mem;
11887 return objspace_malloc_fixup(objspace, mem, size, gc_allowed);
11888}
11889
11890void *
11891rb_gc_impl_realloc(void *objspace_ptr, void *ptr, size_t new_size, size_t old_size, bool gc_allowed)
11892{
11893 rb_objspace_t *objspace = objspace_ptr;
11894
11895 check_malloc_not_in_gc(objspace, "realloc");
11896
11897 void *mem;
11898
11899 if (!ptr) return rb_gc_impl_malloc(objspace, new_size, gc_allowed);
11900
11901 /*
11902 * The behavior of realloc(ptr, 0) is implementation defined.
11903 * Therefore we don't use realloc(ptr, 0) for portability reason.
11904 * see http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_400.htm
11905 */
11906 if (new_size == 0) {
11907 if ((mem = rb_gc_impl_malloc(objspace, 0, gc_allowed)) != NULL) {
11908 /*
11909 * - OpenBSD's malloc(3) man page says that when 0 is passed, it
11910 * returns a non-NULL pointer to an access-protected memory page.
11911 * The returned pointer cannot be read / written at all, but
11912 * still be a valid argument of free().
11913 *
11914 * https://man.openbsd.org/malloc.3
11915 *
11916 * - Linux's malloc(3) man page says that it _might_ perhaps return
11917 * a non-NULL pointer when its argument is 0. That return value
11918 * is safe (and is expected) to be passed to free().
11919 *
11920 * https://man7.org/linux/man-pages/man3/malloc.3.html
11921 *
11922 * - As I read the implementation jemalloc's malloc() returns fully
11923 * normal 16 bytes memory region when its argument is 0.
11924 *
11925 * - As I read the implementation musl libc's malloc() returns
11926 * fully normal 32 bytes memory region when its argument is 0.
11927 *
11928 * - Other malloc implementations can also return non-NULL.
11929 */
11930 rb_gc_impl_free(objspace, ptr, old_size);
11931 return mem;
11932 }
11933 else {
11934 /*
11935 * It is dangerous to return NULL here, because that could lead to
11936 * RCE. Fallback to 1 byte instead of zero.
11937 *
11938 * https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-11932
11939 */
11940 new_size = 1;
11941 }
11942 }
11943
11944#if CALC_EXACT_MALLOC_SIZE
11945 {
11946 struct malloc_obj_info *info = (struct malloc_obj_info *)ptr - 1;
11947 new_size += sizeof(struct malloc_obj_info);
11948 ptr = info;
11949#if VERIFY_FREE_SIZE
11950 if (old_size && (old_size + sizeof(struct malloc_obj_info)) != info->size) {
11951 const char *freeing = gc_freeing_obj_info();
11952 rb_bug("buffer %p realloced with old_size=%zu, but was allocated with size=%zu%s%s", ptr, old_size, info->size - sizeof(struct malloc_obj_info),
11953 freeing ? ", while freeing " : "", freeing ? freeing : "");
11954 }
11955#endif
11956 old_size = info->size;
11957 }
11958#endif
11959
11960 old_size = objspace_malloc_size(objspace, ptr, old_size);
11961 TRY_WITH_GC(new_size, mem = RB_GNUC_EXTENSION_BLOCK(realloc(ptr, new_size)));
11962 if (!mem) return mem;
11963 new_size = objspace_malloc_size(objspace, mem, new_size);
11964
11965#if CALC_EXACT_MALLOC_SIZE
11966 {
11967 struct malloc_obj_info *info = mem;
11968 info->size = new_size;
11969 mem = info + 1;
11970 }
11971#endif
11972
11973 objspace_malloc_increase(objspace, mem, new_size, old_size, MEMOP_TYPE_REALLOC, gc_allowed);
11974
11975 RB_DEBUG_COUNTER_INC(heap_xrealloc);
11976 return mem;
11977}
11978
11979void
11980rb_gc_impl_adjust_memory_usage(void *objspace_ptr, ssize_t diff)
11981{
11982 rb_objspace_t *objspace = objspace_ptr;
11983
11984 if (diff > 0) {
11985 objspace_malloc_increase(objspace, 0, diff, 0, MEMOP_TYPE_REALLOC, true);
11986 }
11987 else if (diff < 0) {
11988 objspace_malloc_increase(objspace, 0, 0, -diff, MEMOP_TYPE_REALLOC, true);
11989 }
11990}
11991
11992// TODO: move GC profiler stuff back into gc.c
11993/*
11994 ------------------------------ GC profiler ------------------------------
11995*/
11996
11997#define GC_PROFILE_RECORD_DEFAULT_SIZE 100
11998#define GC_PROFILE_RECORD_DEFAULT_MAX_RECORDS 4096
11999#define GC_PROFILE_RECORD_UNBOUNDED 0
12000
12001static bool
12002current_process_time(struct timespec *ts)
12003{
12004#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_PROCESS_CPUTIME_ID)
12005 {
12006 static int try_clock_gettime = 1;
12007 if (try_clock_gettime) {
12008 if (clock_gettime(CLOCK_PROCESS_CPUTIME_ID, ts) == 0) {
12009 return true;
12010 }
12011 else {
12012 try_clock_gettime = 0;
12013 }
12014 }
12015 }
12016#endif
12017
12018#ifdef RUSAGE_SELF
12019 {
12020 struct rusage usage;
12021 struct timeval time;
12022 if (getrusage(RUSAGE_SELF, &usage) == 0) {
12023 time = usage.ru_utime;
12024 ts->tv_sec = time.tv_sec;
12025 ts->tv_nsec = (int32_t)time.tv_usec * 1000;
12026 return true;
12027 }
12028 }
12029#endif
12030
12031#ifdef _WIN32
12032 {
12033 FILETIME creation_time, exit_time, kernel_time, user_time;
12034 ULARGE_INTEGER ui;
12035
12036 if (GetProcessTimes(GetCurrentProcess(),
12037 &creation_time, &exit_time, &kernel_time, &user_time) != 0) {
12038 memcpy(&ui, &user_time, sizeof(FILETIME));
12039#define PER100NSEC (uint64_t)(1000 * 1000 * 10)
12040 ts->tv_nsec = (long)(ui.QuadPart % PER100NSEC);
12041 ts->tv_sec = (time_t)(ui.QuadPart / PER100NSEC);
12042 return true;
12043 }
12044 }
12045#endif
12046
12047 return false;
12048}
12049
12050static double
12051getrusage_time(void)
12052{
12053 struct timespec ts;
12054 if (current_process_time(&ts)) {
12055 return ts.tv_sec + ts.tv_nsec * 1e-9;
12056 }
12057 else {
12058 return 0.0;
12059 }
12060}
12061
12062static inline double
12063hrtime_to_sec(rb_hrtime_t time)
12064{
12065 return (double)time / (double)RB_HRTIME_PER_SEC;
12066}
12067
12068static inline rb_hrtime_t
12069elapsed_hrtime_from(rb_hrtime_t start)
12070{
12071 return rb_hrtime_sub(rb_hrtime_now(), start);
12072}
12073
12074
12075static inline size_t
12076gc_profile_record_count(rb_objspace_t *objspace)
12077{
12078 return objspace->profile.record_count;
12079}
12080
12081static inline size_t
12082gc_profile_record_index(rb_objspace_t *objspace, size_t logical_index)
12083{
12084 if (objspace->profile.max_records != GC_PROFILE_RECORD_UNBOUNDED &&
12085 objspace->profile.record_count == objspace->profile.size) {
12086 return (objspace->profile.next_index + logical_index) % objspace->profile.size;
12087 }
12088 else {
12089 return logical_index;
12090 }
12091}
12092
12093static void
12094gc_profile_records_free(rb_objspace_t *objspace)
12095{
12096 void *p = objspace->profile.records;
12097 objspace->profile.records = NULL;
12098 objspace->profile.size = 0;
12099 objspace->profile.next_index = 0;
12100 objspace->profile.record_count = 0;
12101 objspace->profile.current_record = 0;
12102 free(p);
12103}
12104
12105static inline void
12106gc_prof_setup_new_record(rb_objspace_t *objspace, unsigned int reason)
12107{
12108 if (objspace->profile.run) {
12109 size_t index;
12110 gc_profile_record *record;
12111
12112 if (objspace->profile.max_records == GC_PROFILE_RECORD_UNBOUNDED) {
12113 index = objspace->profile.record_count++;
12114 objspace->profile.next_index = objspace->profile.record_count;
12115
12116 if (!objspace->profile.records) {
12117 objspace->profile.size = GC_PROFILE_RECORD_DEFAULT_SIZE;
12118 objspace->profile.records = malloc(xmalloc2_size(sizeof(gc_profile_record), objspace->profile.size));
12119 }
12120 if (index >= objspace->profile.size) {
12121 void *ptr;
12122 objspace->profile.size += 1000;
12123 ptr = realloc(objspace->profile.records, xmalloc2_size(sizeof(gc_profile_record), objspace->profile.size));
12124 if (!ptr) rb_memerror();
12125 objspace->profile.records = ptr;
12126 }
12127 }
12128 else {
12129 if (!objspace->profile.records) {
12130 objspace->profile.size = objspace->profile.max_records;
12131 objspace->profile.records = malloc(xmalloc2_size(sizeof(gc_profile_record), objspace->profile.size));
12132 }
12133 index = objspace->profile.next_index;
12134 objspace->profile.next_index = (objspace->profile.next_index + 1) % objspace->profile.size;
12135 if (objspace->profile.record_count < objspace->profile.size) {
12136 objspace->profile.record_count++;
12137 }
12138 }
12139
12140 if (!objspace->profile.records) {
12141 rb_bug("gc_profile malloc or realloc miss");
12142 }
12143 record = objspace->profile.current_record = &objspace->profile.records[index];
12144 MEMZERO(record, gc_profile_record, 1);
12145
12146 /* setup before-GC parameter */
12147 record->flags = reason | (ruby_gc_stressful ? GPR_FLAG_STRESS : 0);
12148 record->sequence = objspace->profile.record_sequence++;
12149 record->gc_invoke_wall_time = rb_hrtime_sub(rb_hrtime_now(),
12150 objspace->profile.invoke_wall_time);
12151#if MALLOC_ALLOCATED_SIZE
12152 record->allocated_size = malloc_allocated_size;
12153#endif
12154#if GC_PROFILE_MORE_DETAIL && GC_PROFILE_DETAIL_MEMORY
12155#ifdef RUSAGE_SELF
12156 {
12157 struct rusage usage;
12158 if (getrusage(RUSAGE_SELF, &usage) == 0) {
12159 record->maxrss = usage.ru_maxrss;
12160 record->minflt = usage.ru_minflt;
12161 record->majflt = usage.ru_majflt;
12162 }
12163 }
12164#endif
12165#endif
12166 }
12167}
12168
12169static inline void
12170gc_prof_timer_start(rb_objspace_t *objspace)
12171{
12172 if (gc_prof_enabled(objspace)) {
12173 gc_profile_record *record = gc_prof_record(objspace);
12174#if GC_PROFILE_MORE_DETAIL
12175 record->prepare_time = objspace->profile.prepare_time;
12176#endif
12177 record->gc_time = 0;
12178 record->gc_invoke_time = getrusage_time();
12179 objspace->profile.gc_wall_start_time = rb_hrtime_now();
12180 }
12181}
12182
12183static double
12184elapsed_time_from(double time)
12185{
12186 double now = getrusage_time();
12187 if (now > time) {
12188 return now - time;
12189 }
12190 else {
12191 return 0;
12192 }
12193}
12194
12195static inline void
12196gc_prof_timer_stop(rb_objspace_t *objspace)
12197{
12198 if (gc_prof_enabled(objspace)) {
12199 gc_profile_record *record = gc_prof_record(objspace);
12200 record->gc_time = elapsed_time_from(record->gc_invoke_time);
12201 record->gc_invoke_time -= objspace->profile.invoke_time;
12202 record->gc_wall_time = elapsed_hrtime_from(objspace->profile.gc_wall_start_time);
12203 }
12204}
12205
12206static inline void
12207gc_prof_mark_timer_start(rb_objspace_t *objspace)
12208{
12209 RUBY_DTRACE_GC_HOOK(MARK_BEGIN);
12210#if GC_PROFILE_MORE_DETAIL
12211 if (gc_prof_enabled(objspace)) {
12212 gc_prof_record(objspace)->gc_mark_time = getrusage_time();
12213 }
12214#endif
12215}
12216
12217static inline void
12218gc_prof_mark_timer_stop(rb_objspace_t *objspace)
12219{
12220 RUBY_DTRACE_GC_HOOK(MARK_END);
12221#if GC_PROFILE_MORE_DETAIL
12222 if (gc_prof_enabled(objspace)) {
12223 gc_profile_record *record = gc_prof_record(objspace);
12224 record->gc_mark_time = elapsed_time_from(record->gc_mark_time);
12225 }
12226#endif
12227}
12228
12229static inline void
12230gc_prof_sweep_timer_start(rb_objspace_t *objspace)
12231{
12232 RUBY_DTRACE_GC_HOOK(SWEEP_BEGIN);
12233 if (gc_prof_enabled(objspace)) {
12234 gc_profile_record *record = gc_prof_record(objspace);
12235
12236 if (record->gc_time > 0 || GC_PROFILE_MORE_DETAIL) {
12237 objspace->profile.gc_sweep_start_time = getrusage_time();
12238 objspace->profile.gc_sweep_wall_start_time = rb_hrtime_now();
12239 }
12240 }
12241}
12242
12243static inline void
12244gc_prof_sweep_timer_stop(rb_objspace_t *objspace)
12245{
12246 RUBY_DTRACE_GC_HOOK(SWEEP_END);
12247
12248 if (gc_prof_enabled(objspace)) {
12249 double sweep_time;
12250 gc_profile_record *record = gc_prof_record(objspace);
12251
12252 if (record->gc_time > 0) {
12253 sweep_time = elapsed_time_from(objspace->profile.gc_sweep_start_time);
12254 /* need to accumulate GC time for lazy sweep after gc() */
12255 record->gc_time += sweep_time;
12256 record->gc_wall_time = rb_hrtime_add(record->gc_wall_time,
12257 elapsed_hrtime_from(objspace->profile.gc_sweep_wall_start_time));
12258 }
12259 else if (GC_PROFILE_MORE_DETAIL) {
12260 sweep_time = elapsed_time_from(objspace->profile.gc_sweep_start_time);
12261 }
12262
12263#if GC_PROFILE_MORE_DETAIL
12264 record->gc_sweep_time += sweep_time;
12265 if (heap_pages_deferred_final) record->flags |= GPR_FLAG_HAVE_FINALIZE;
12266#endif
12267 if (heap_pages_deferred_final) objspace->profile.latest_gc_info |= GPR_FLAG_HAVE_FINALIZE;
12268 }
12269}
12270
12271static inline void
12272gc_prof_set_malloc_info(rb_objspace_t *objspace)
12273{
12274#if GC_PROFILE_MORE_DETAIL
12275 if (gc_prof_enabled(objspace)) {
12276 gc_profile_record *record = gc_prof_record(objspace);
12277 record->allocate_increase = malloc_increase;
12278 record->allocate_limit = malloc_limit;
12279 }
12280#endif
12281}
12282
12283static inline void
12284gc_prof_set_heap_info(rb_objspace_t *objspace)
12285{
12286 if (gc_prof_enabled(objspace)) {
12287 gc_profile_record *record = gc_prof_record(objspace);
12288
12289 /* Sum across all size pools since each has a different slot size. */
12290 size_t total = 0;
12291 size_t use_size = 0;
12292 size_t total_size = 0;
12293 for (int i = 0; i < HEAP_COUNT; i++) {
12294 rb_heap_t *heap = &heaps[i];
12295 size_t heap_live = heap->total_allocated_objects - heap->total_freed_objects - heap->final_slots_count;
12296 total += heap->total_slots;
12297 use_size += heap_live * heap->slot_size;
12298 total_size += heap->total_slots * heap->slot_size;
12299 }
12300
12301#if GC_PROFILE_MORE_DETAIL
12302 size_t live = objspace->profile.total_allocated_objects_at_gc_start - total_freed_objects(objspace);
12303 record->heap_use_pages = objspace->profile.heap_used_at_gc_start;
12304 record->heap_live_objects = live;
12305 record->heap_free_objects = total - live;
12306#endif
12307
12308 record->heap_total_objects = total;
12309 record->heap_use_size = use_size;
12310 record->heap_total_size = total_size;
12311 }
12312}
12313
12314/*
12315 * call-seq:
12316 * GC::Profiler.clear -> nil
12317 *
12318 * Clears the \GC profiler data.
12319 *
12320 */
12321
12322static VALUE
12323gc_profile_clear(VALUE _)
12324{
12325 rb_objspace_t *objspace = rb_gc_get_objspace();
12326 gc_profile_records_free(objspace);
12327 return Qnil;
12328}
12329
12330/*
12331 * call-seq:
12332 * GC::Profiler.configure(max_records: 4096) -> nil
12333 *
12334 * Configures how many raw profile records are retained by
12335 * GC::Profiler.raw_data.
12336 *
12337 * The profiler keeps at most +max_records+ records in a bounded ring buffer.
12338 * When the buffer is full, newer GC records overwrite the oldest retained
12339 * records. The default limit is 4096 records.
12340 *
12341 * Pass +nil+ to restore the historical unbounded behavior:
12342 *
12343 * GC::Profiler.configure(max_records: nil)
12344 *
12345 * Changing +max_records+ clears existing raw profile data. This method does
12346 * not enable or disable the profiler; use GC::Profiler.enable and
12347 * GC::Profiler.disable for that.
12348 */
12349
12350static VALUE
12351gc_profile_configure(int argc, VALUE *argv, VALUE _)
12352{
12353 static ID keywords[1] = {0};
12354 VALUE options, max_records;
12355 rb_objspace_t *objspace = rb_gc_get_objspace();
12356
12357 if (!keywords[0]) {
12358 keywords[0] = rb_intern("max_records");
12359 }
12360
12361 rb_scan_args_kw(rb_keyword_given_p(), argc, argv, ":", &options);
12362 rb_get_kwargs(options, keywords, 0, 1, &max_records);
12363
12364 if (max_records == Qundef) {
12365 return Qnil;
12366 }
12367 else if (NIL_P(max_records)) {
12368 objspace->profile.max_records = GC_PROFILE_RECORD_UNBOUNDED;
12369 }
12370 else {
12371 long value = NUM2LONG(max_records);
12372 if (value <= 0) {
12373 rb_raise(rb_eArgError, "max_records must be positive or nil");
12374 }
12375 objspace->profile.max_records = (size_t)value;
12376 }
12377
12378 gc_profile_records_free(objspace);
12379 return Qnil;
12380}
12381
12382/*
12383 * call-seq:
12384 * GC::Profiler.raw_data(limit: nil, since: nil) -> [Hash, ...]
12385 *
12386 * Returns an Array of retained raw profile data Hashes ordered from earliest
12387 * to latest by +:GC_INVOKE_TIME+. +limit:+ returns at most the newest
12388 * retained records. +since:+ returns records with +:GC_SEQUENCE+ greater
12389 * than the given sequence.
12390 *
12391 * For example:
12392 *
12393 * [
12394 * {
12395 * :GC_TIME=>1.3000000000000858e-05,
12396 * :GC_INVOKE_TIME=>0.010634999999999999,
12397 * :GC_WALL_TIME=>1.4000000000000001e-05,
12398 * :GC_INVOKE_WALL_TIME=>0.010640000000000000,
12399 * :GC_PAUSE_TIME=>1.5000000000000000e-05,
12400 * :GC_STOP_TIME=>1.0000000000000000e-06,
12401 * :GC_STW_TIME=>1.4000000000000001e-05,
12402 * :GC_MARK_WALL_TIME=>9.0000000000000002e-06,
12403 * :GC_SWEEP_WALL_TIME=>5.0000000000000004e-06,
12404 * :GC_COMPACT_WALL_TIME=>0.0000000000000000e+00,
12405 * :HEAP_USE_SIZE=>289640,
12406 * :HEAP_TOTAL_SIZE=>588960,
12407 * :HEAP_TOTAL_OBJECTS=>14724,
12408 * :GC_IS_MARKED=>false
12409 * },
12410 * # ...
12411 * ]
12412 *
12413 * The keys mean:
12414 *
12415 * +:GC_SEQUENCE+::
12416 * Monotonically increasing sequence number for this profiler record.
12417 * +:GC_TIME+::
12418 * CPU time elapsed in seconds for this GC run. This is process CPU time,
12419 * not elapsed wall-clock time.
12420 * +:GC_INVOKE_TIME+::
12421 * CPU time elapsed in seconds from startup to when the GC was invoked.
12422 * +:GC_WALL_TIME+::
12423 * Monotonic wall-clock counterpart to +:GC_TIME+ for this GC record.
12424 * This does not include time spent stopping other ractors before the VM
12425 * enters GC. Use the phase wall-clock fields below for mark, sweep, and
12426 * compaction attribution.
12427 * +:GC_INVOKE_WALL_TIME+::
12428 * Monotonic wall-clock time elapsed in seconds from startup to when the GC
12429 * was invoked.
12430 * +:GC_PAUSE_TIME+::
12431 * Monotonic wall-clock time elapsed in seconds while user execution was
12432 * blocked by this GC entry, including time to stop other ractors. This
12433 * may include time from incremental marking or lazy sweeping continuation
12434 * charged to this record.
12435 * +:GC_STOP_TIME+::
12436 * Monotonic wall-clock time elapsed in seconds stopping other ractors.
12437 * +:GC_STW_TIME+::
12438 * Monotonic wall-clock time elapsed in seconds after other ractors have
12439 * stopped and before the VM exits GC.
12440 * +:GC_MARK_WALL_TIME+::
12441 * Monotonic wall-clock time elapsed in seconds spent marking for this GC
12442 * record, accumulated across incremental marking continuations.
12443 * +:GC_SWEEP_WALL_TIME+::
12444 * Monotonic wall-clock time elapsed in seconds spent sweeping for this GC
12445 * record, accumulated across lazy sweeping continuations. This does not
12446 * include compaction time, which is reported separately as
12447 * +:GC_COMPACT_WALL_TIME+.
12448 * +:GC_COMPACT_WALL_TIME+::
12449 * Monotonic wall-clock time elapsed in seconds spent compacting for this GC
12450 * record, or +0.0+ if this GC did not compact.
12451 * +:HEAP_USE_SIZE+::
12452 * Total bytes of heap used
12453 * +:HEAP_TOTAL_SIZE+::
12454 * Total size of heap in bytes
12455 * +:HEAP_TOTAL_OBJECTS+::
12456 * Total number of objects
12457 * +:GC_IS_MARKED+::
12458 * Returns +true+ if the GC is in mark phase
12459 *
12460 * The wall-clock timing fields relate to each other as follows:
12461 *
12462 * GC_PAUSE_TIME == GC_STOP_TIME + GC_STW_TIME
12463 *
12464 * +:GC_MARK_WALL_TIME+, +:GC_SWEEP_WALL_TIME+, and +:GC_COMPACT_WALL_TIME+
12465 * report separate phase timings and must not be added to +:GC_WALL_TIME+.
12466 *
12467 * +:GC_WALL_TIME+ is the wall-clock counterpart to +:GC_TIME+ and is nested
12468 * inside +:GC_STW_TIME+, so it must not be added to +:GC_STW_TIME+. The difference
12469 * +GC_STW_TIME - GC_WALL_TIME+ is VM overhead inside the stopped interval
12470 * (GC event hooks, bookkeeping, consistency checks, and continuation work).
12471 *
12472 * If ruby was built with +GC_PROFILE_MORE_DETAIL+, you will also have access
12473 * to the following hash keys:
12474 *
12475 * +:GC_MARK_TIME+::
12476 * +:GC_SWEEP_TIME+::
12477 * +:ALLOCATE_INCREASE+::
12478 * +:ALLOCATE_LIMIT+::
12479 * +:HEAP_USE_PAGES+::
12480 * +:HEAP_LIVE_OBJECTS+::
12481 * +:HEAP_FREE_OBJECTS+::
12482 * +:HAVE_FINALIZE+::
12483 *
12484 */
12485
12486static VALUE
12487gc_profile_record_get(int argc, VALUE *argv, VALUE _)
12488{
12489 static ID keywords[2] = {0};
12490 VALUE prof, options, limit_value, since_value;
12491 VALUE gc_profile = rb_ary_new();
12492 size_t i, count, matching = 0, skip = 0, limit = SIZE_MAX, since = 0;
12493 bool use_since = false;
12494 rb_objspace_t *objspace = rb_gc_get_objspace();
12495
12496 if (!keywords[0]) {
12497 keywords[0] = rb_intern("limit");
12498 keywords[1] = rb_intern("since");
12499 }
12500
12501 rb_scan_args_kw(rb_keyword_given_p(), argc, argv, ":", &options);
12502 VALUE values[2] = {Qundef, Qundef};
12503 rb_get_kwargs(options, keywords, 0, 2, values);
12504 limit_value = values[0];
12505 since_value = values[1];
12506
12507 if (limit_value != Qundef && !NIL_P(limit_value)) {
12508 long value = NUM2LONG(limit_value);
12509 if (value < 0) {
12510 rb_raise(rb_eArgError, "limit must be non-negative");
12511 }
12512 limit = (size_t)value;
12513 }
12514 if (since_value != Qundef && !NIL_P(since_value)) {
12515 long value = NUM2LONG(since_value);
12516 if (value < 0) {
12517 rb_raise(rb_eArgError, "since must be non-negative");
12518 }
12519 since = (size_t)value;
12520 use_since = true;
12521 }
12522
12523 if (!objspace->profile.run) {
12524 return Qnil;
12525 }
12526
12527 count = gc_profile_record_count(objspace);
12528 for (i = 0; i < count; i++) {
12529 gc_profile_record *record = &objspace->profile.records[gc_profile_record_index(objspace, i)];
12530 if (!use_since || record->sequence > since) {
12531 matching++;
12532 }
12533 }
12534 if (limit < matching) {
12535 skip = matching - limit;
12536 }
12537
12538 for (i = 0; i < count; i++) {
12539 gc_profile_record *record = &objspace->profile.records[gc_profile_record_index(objspace, i)];
12540 if (use_since && record->sequence <= since) {
12541 continue;
12542 }
12543 if (skip > 0) {
12544 skip--;
12545 continue;
12546 }
12547
12548 prof = rb_hash_new();
12549 rb_hash_aset(prof, ID2SYM(rb_intern("GC_FLAGS")), gc_info_decode(objspace, rb_hash_new(), record->flags));
12550 rb_hash_aset(prof, ID2SYM(rb_intern("GC_SEQUENCE")), SIZET2NUM(record->sequence));
12551 rb_hash_aset(prof, ID2SYM(rb_intern("GC_TIME")), DBL2NUM(record->gc_time));
12552 rb_hash_aset(prof, ID2SYM(rb_intern("GC_INVOKE_TIME")), DBL2NUM(record->gc_invoke_time));
12553 rb_hash_aset(prof, ID2SYM(rb_intern("GC_WALL_TIME")),
12554 DBL2NUM(hrtime_to_sec(record->gc_wall_time)));
12555 rb_hash_aset(prof, ID2SYM(rb_intern("GC_INVOKE_WALL_TIME")),
12556 DBL2NUM(hrtime_to_sec(record->gc_invoke_wall_time)));
12557 rb_hash_aset(prof, ID2SYM(rb_intern("GC_PAUSE_TIME")),
12558 DBL2NUM(hrtime_to_sec(record->gc_pause_time)));
12559 rb_hash_aset(prof, ID2SYM(rb_intern("GC_STOP_TIME")),
12560 DBL2NUM(hrtime_to_sec(record->gc_stop_time)));
12561 rb_hash_aset(prof, ID2SYM(rb_intern("GC_STW_TIME")),
12562 DBL2NUM(hrtime_to_sec(record->gc_stw_time)));
12563 rb_hash_aset(prof, ID2SYM(rb_intern("GC_MARK_WALL_TIME")),
12564 DBL2NUM(hrtime_to_sec(record->gc_mark_wall_time)));
12565 rb_hash_aset(prof, ID2SYM(rb_intern("GC_SWEEP_WALL_TIME")),
12566 DBL2NUM(hrtime_to_sec(record->gc_sweep_wall_time)));
12567 rb_hash_aset(prof, ID2SYM(rb_intern("GC_COMPACT_WALL_TIME")),
12568 DBL2NUM(hrtime_to_sec(record->gc_compact_wall_time)));
12569 rb_hash_aset(prof, ID2SYM(rb_intern("HEAP_USE_SIZE")), SIZET2NUM(record->heap_use_size));
12570 rb_hash_aset(prof, ID2SYM(rb_intern("HEAP_TOTAL_SIZE")), SIZET2NUM(record->heap_total_size));
12571 rb_hash_aset(prof, ID2SYM(rb_intern("HEAP_TOTAL_OBJECTS")), SIZET2NUM(record->heap_total_objects));
12572 rb_hash_aset(prof, ID2SYM(rb_intern("MOVED_OBJECTS")), SIZET2NUM(record->moved_objects));
12573 rb_hash_aset(prof, ID2SYM(rb_intern("GC_IS_MARKED")), Qtrue);
12574#if GC_PROFILE_MORE_DETAIL
12575 rb_hash_aset(prof, ID2SYM(rb_intern("GC_MARK_TIME")), DBL2NUM(record->gc_mark_time));
12576 rb_hash_aset(prof, ID2SYM(rb_intern("GC_SWEEP_TIME")), DBL2NUM(record->gc_sweep_time));
12577 rb_hash_aset(prof, ID2SYM(rb_intern("ALLOCATE_INCREASE")), SIZET2NUM(record->allocate_increase));
12578 rb_hash_aset(prof, ID2SYM(rb_intern("ALLOCATE_LIMIT")), SIZET2NUM(record->allocate_limit));
12579 rb_hash_aset(prof, ID2SYM(rb_intern("HEAP_USE_PAGES")), SIZET2NUM(record->heap_use_pages));
12580 rb_hash_aset(prof, ID2SYM(rb_intern("HEAP_LIVE_OBJECTS")), SIZET2NUM(record->heap_live_objects));
12581 rb_hash_aset(prof, ID2SYM(rb_intern("HEAP_FREE_OBJECTS")), SIZET2NUM(record->heap_free_objects));
12582
12583 rb_hash_aset(prof, ID2SYM(rb_intern("REMOVING_OBJECTS")), SIZET2NUM(record->removing_objects));
12584 rb_hash_aset(prof, ID2SYM(rb_intern("EMPTY_OBJECTS")), SIZET2NUM(record->empty_objects));
12585
12586 rb_hash_aset(prof, ID2SYM(rb_intern("HAVE_FINALIZE")), (record->flags & GPR_FLAG_HAVE_FINALIZE) ? Qtrue : Qfalse);
12587#endif
12588
12589#if RGENGC_PROFILE > 0
12590 rb_hash_aset(prof, ID2SYM(rb_intern("OLD_OBJECTS")), SIZET2NUM(record->old_objects));
12591 rb_hash_aset(prof, ID2SYM(rb_intern("REMEMBERED_NORMAL_OBJECTS")), SIZET2NUM(record->remembered_normal_objects));
12592 rb_hash_aset(prof, ID2SYM(rb_intern("REMEMBERED_SHADY_OBJECTS")), SIZET2NUM(record->remembered_shady_objects));
12593#endif
12594 rb_ary_push(gc_profile, prof);
12595 }
12596
12597 return gc_profile;
12598}
12599
12600#if GC_PROFILE_MORE_DETAIL
12601#define MAJOR_REASON_MAX 0x10
12602
12603static char *
12604gc_profile_dump_major_reason(unsigned int flags, char *buff)
12605{
12606 unsigned int reason = flags & GPR_FLAG_MAJOR_MASK;
12607 int i = 0;
12608
12609 if (reason == GPR_FLAG_NONE) {
12610 buff[0] = '-';
12611 buff[1] = 0;
12612 }
12613 else {
12614#define C(x, s) \
12615 if (reason & GPR_FLAG_MAJOR_BY_##x) { \
12616 buff[i++] = #x[0]; \
12617 if (i >= MAJOR_REASON_MAX) rb_bug("gc_profile_dump_major_reason: overflow"); \
12618 buff[i] = 0; \
12619 }
12620 C(NOFREE, N);
12621 C(OLDGEN, O);
12622 C(SHADY, S);
12623#if RGENGC_ESTIMATE_OLDMALLOC
12624 C(OLDMALLOC, M);
12625#endif
12626#undef C
12627 }
12628 return buff;
12629}
12630#endif
12631
12632
12633
12634static void
12635gc_profile_dump_on(VALUE out, VALUE (*append)(VALUE, VALUE))
12636{
12637 rb_objspace_t *objspace = rb_gc_get_objspace();
12638 size_t count = gc_profile_record_count(objspace);
12639#ifdef MAJOR_REASON_MAX
12640 char reason_str[MAJOR_REASON_MAX];
12641#endif
12642
12643 if (objspace->profile.run && count /* > 1 */) {
12644 size_t i;
12645 const gc_profile_record *record;
12646
12647 append(out, rb_sprintf("GC %"PRIuSIZE" invokes.\n", objspace->profile.count));
12648 append(out, rb_str_new_cstr("Index Invoke Time(sec) Use Size(byte) Total Size(byte) Total Object GC Time(ms)\n"));
12649
12650 for (i = 0; i < count; i++) {
12651 record = &objspace->profile.records[gc_profile_record_index(objspace, i)];
12652 append(out, rb_sprintf("%5"PRIuSIZE" %19.3f %20"PRIuSIZE" %20"PRIuSIZE" %20"PRIuSIZE" %30.20f\n",
12653 i+1, record->gc_invoke_time, record->heap_use_size,
12654 record->heap_total_size, record->heap_total_objects, record->gc_time*1000));
12655 }
12656
12657#if GC_PROFILE_MORE_DETAIL
12658 const char *str = "\n\n" \
12659 "More detail.\n" \
12660 "Prepare Time = Previously GC's rest sweep time\n"
12661 "Index Flags Allocate Inc. Allocate Limit"
12662#if CALC_EXACT_MALLOC_SIZE
12663 " Allocated Size"
12664#endif
12665 " Use Page Mark Time(ms) Sweep Time(ms) Prepare Time(ms) LivingObj FreeObj RemovedObj EmptyObj"
12666#if RGENGC_PROFILE
12667 " OldgenObj RemNormObj RemShadObj"
12668#endif
12669#if GC_PROFILE_DETAIL_MEMORY
12670 " MaxRSS(KB) MinorFLT MajorFLT"
12671#endif
12672 "\n";
12673 append(out, rb_str_new_cstr(str));
12674
12675 for (i = 0; i < count; i++) {
12676 record = &objspace->profile.records[gc_profile_record_index(objspace, i)];
12677 append(out, rb_sprintf("%5"PRIuSIZE" %4s/%c/%6s%c %13"PRIuSIZE" %15"PRIuSIZE
12678#if CALC_EXACT_MALLOC_SIZE
12679 " %15"PRIuSIZE
12680#endif
12681 " %9"PRIuSIZE" %17.12f %17.12f %17.12f %10"PRIuSIZE" %10"PRIuSIZE" %10"PRIuSIZE" %10"PRIuSIZE
12682#if RGENGC_PROFILE
12683 "%10"PRIuSIZE" %10"PRIuSIZE" %10"PRIuSIZE
12684#endif
12685#if GC_PROFILE_DETAIL_MEMORY
12686 "%11ld %8ld %8ld"
12687#endif
12688
12689 "\n",
12690 i+1,
12691 gc_profile_dump_major_reason(record->flags, reason_str),
12692 (record->flags & GPR_FLAG_HAVE_FINALIZE) ? 'F' : '.',
12693 (record->flags & GPR_FLAG_NEWOBJ) ? "NEWOBJ" :
12694 (record->flags & GPR_FLAG_MALLOC) ? "MALLOC" :
12695 (record->flags & GPR_FLAG_METHOD) ? "METHOD" :
12696 (record->flags & GPR_FLAG_CAPI) ? "CAPI__" : "??????",
12697 (record->flags & GPR_FLAG_STRESS) ? '!' : ' ',
12698 record->allocate_increase, record->allocate_limit,
12699#if CALC_EXACT_MALLOC_SIZE
12700 record->allocated_size,
12701#endif
12702 record->heap_use_pages,
12703 record->gc_mark_time*1000,
12704 record->gc_sweep_time*1000,
12705 record->prepare_time*1000,
12706
12707 record->heap_live_objects,
12708 record->heap_free_objects,
12709 record->removing_objects,
12710 record->empty_objects
12711#if RGENGC_PROFILE
12712 ,
12713 record->old_objects,
12714 record->remembered_normal_objects,
12715 record->remembered_shady_objects
12716#endif
12717#if GC_PROFILE_DETAIL_MEMORY
12718 ,
12719 record->maxrss / 1024,
12720 record->minflt,
12721 record->majflt
12722#endif
12723
12724 ));
12725 }
12726#endif
12727 }
12728}
12729
12730/*
12731 * call-seq:
12732 * GC::Profiler.result -> String
12733 *
12734 * Returns a profile data report such as:
12735 *
12736 * GC 1 invokes.
12737 * Index Invoke Time(sec) Use Size(byte) Total Size(byte) Total Object GC time(ms)
12738 * 1 0.012 159240 212940 10647 0.00000000000001530000
12739 */
12740
12741static VALUE
12742gc_profile_result(VALUE _)
12743{
12744 VALUE str = rb_str_buf_new(0);
12745 gc_profile_dump_on(str, rb_str_buf_append);
12746 return str;
12747}
12748
12749/*
12750 * call-seq:
12751 * GC::Profiler.report
12752 * GC::Profiler.report(io)
12753 *
12754 * Writes the GC::Profiler.result to <tt>$stdout</tt> or the given IO object.
12755 *
12756 */
12757
12758static VALUE
12759gc_profile_report(int argc, VALUE *argv, VALUE self)
12760{
12761 VALUE out;
12762
12763 out = (!rb_check_arity(argc, 0, 1) ? rb_stdout : argv[0]);
12764 gc_profile_dump_on(out, rb_io_write);
12765
12766 return Qnil;
12767}
12768
12769/*
12770 * call-seq:
12771 * GC::Profiler.total_time -> float
12772 *
12773 * The total time used for garbage collection in seconds
12774 */
12775
12776static VALUE
12777gc_profile_total_time(VALUE self)
12778{
12779 double time = 0;
12780 rb_objspace_t *objspace = rb_gc_get_objspace();
12781
12782 if (objspace->profile.run && gc_profile_record_count(objspace) > 0) {
12783 size_t i;
12784 size_t count = gc_profile_record_count(objspace);
12785
12786 for (i = 0; i < count; i++) {
12787 time += objspace->profile.records[gc_profile_record_index(objspace, i)].gc_time;
12788 }
12789 }
12790 return DBL2NUM(time);
12791}
12792
12793/*
12794 * call-seq:
12795 * GC::Profiler.enabled? -> true or false
12796 *
12797 * The current status of \GC profile mode.
12798 */
12799
12800static VALUE
12801gc_profile_enable_get(VALUE self)
12802{
12803 rb_objspace_t *objspace = rb_gc_get_objspace();
12804 return objspace->profile.run ? Qtrue : Qfalse;
12805}
12806
12807/*
12808 * call-seq:
12809 * GC::Profiler.enable -> nil
12810 *
12811 * Starts the \GC profiler.
12812 *
12813 */
12814
12815static VALUE
12816gc_profile_enable(VALUE _)
12817{
12818 rb_objspace_t *objspace = rb_gc_get_objspace();
12819 objspace->profile.run = TRUE;
12820 objspace->profile.current_record = 0;
12821 return Qnil;
12822}
12823
12824/*
12825 * call-seq:
12826 * GC::Profiler.disable -> nil
12827 *
12828 * Stops the \GC profiler.
12829 *
12830 */
12831
12832static VALUE
12833gc_profile_disable(VALUE _)
12834{
12835 rb_objspace_t *objspace = rb_gc_get_objspace();
12836
12837 objspace->profile.run = FALSE;
12838 objspace->profile.current_record = 0;
12839 return Qnil;
12840}
12841
12842static void
12843rb_gc_verify_internal_consistency(void)
12844{
12845 gc_verify_internal_consistency(rb_gc_get_objspace());
12846}
12847
12848/*
12849 * call-seq:
12850 * GC.verify_internal_consistency -> nil
12851 *
12852 * Verifies internal consistency of the GC.
12853 * This method should only be used for debugging.
12854 *
12855 * This method is only expected to work on CRuby.
12856 */
12857static VALUE
12858gc_verify_internal_consistency_m(VALUE dummy)
12859{
12860 rb_gc_verify_internal_consistency();
12861 return Qnil;
12862}
12863
12864#if GC_CAN_COMPILE_COMPACTION
12865/*
12866 * call-seq:
12867 * GC.auto_compact = flag
12868 *
12869 * Updates automatic compaction mode.
12870 *
12871 * When enabled, the compactor will execute on every major collection.
12872 *
12873 * Enabling compaction will degrade performance on major collections.
12874 */
12875static VALUE
12876gc_set_auto_compact(VALUE _, VALUE v)
12877{
12878 GC_ASSERT(GC_COMPACTION_SUPPORTED);
12879
12880 ruby_enable_autocompact = RTEST(v);
12881
12882#if RGENGC_CHECK_MODE
12883 ruby_autocompact_compare_func = NULL;
12884
12885 if (SYMBOL_P(v)) {
12886 ID id = RB_SYM2ID(v);
12887 if (id == rb_intern("empty")) {
12888 ruby_autocompact_compare_func = compare_free_slots;
12889 }
12890 }
12891#endif
12892
12893 return v;
12894}
12895#else
12896# define gc_set_auto_compact rb_f_notimplement
12897#endif
12898
12899#if GC_CAN_COMPILE_COMPACTION
12900/*
12901 * call-seq:
12902 * GC.auto_compact -> true or false
12903 *
12904 * Returns whether or not automatic compaction has been enabled.
12905 */
12906static VALUE
12907gc_get_auto_compact(VALUE _)
12908{
12909 return ruby_enable_autocompact ? Qtrue : Qfalse;
12910}
12911#else
12912# define gc_get_auto_compact rb_f_notimplement
12913#endif
12914
12915#if GC_CAN_COMPILE_COMPACTION
12916/*
12917 * call-seq:
12918 * GC.latest_compact_info -> hash
12919 *
12920 * Returns information about object moved in the most recent \GC compaction.
12921 *
12922 * The returned +hash+ contains the following keys:
12923 *
12924 * [considered]
12925 * Hash containing the type of the object as the key and the number of
12926 * objects of that type that were considered for movement.
12927 * [moved]
12928 * Hash containing the type of the object as the key and the number of
12929 * objects of that type that were actually moved.
12930 * [moved_up]
12931 * Hash containing the type of the object as the key and the number of
12932 * objects of that type that were increased in size.
12933 * [moved_down]
12934 * Hash containing the type of the object as the key and the number of
12935 * objects of that type that were decreased in size.
12936 *
12937 * Some objects can't be moved (due to pinning) so these numbers can be used to
12938 * calculate compaction efficiency.
12939 */
12940static VALUE
12941gc_compact_stats(VALUE self)
12942{
12943 rb_objspace_t *objspace = rb_gc_get_objspace();
12944 VALUE h = rb_hash_new();
12945 VALUE considered = rb_hash_new();
12946 VALUE moved = rb_hash_new();
12947 VALUE moved_up = rb_hash_new();
12948 VALUE moved_down = rb_hash_new();
12949
12950 for (size_t i = 0; i < T_MASK; i++) {
12951 if (objspace->rcompactor.considered_count_table[i]) {
12952 rb_hash_aset(considered, type_sym(i), SIZET2NUM(objspace->rcompactor.considered_count_table[i]));
12953 }
12954
12955 if (objspace->rcompactor.moved_count_table[i]) {
12956 rb_hash_aset(moved, type_sym(i), SIZET2NUM(objspace->rcompactor.moved_count_table[i]));
12957 }
12958
12959 if (objspace->rcompactor.moved_up_count_table[i]) {
12960 rb_hash_aset(moved_up, type_sym(i), SIZET2NUM(objspace->rcompactor.moved_up_count_table[i]));
12961 }
12962
12963 if (objspace->rcompactor.moved_down_count_table[i]) {
12964 rb_hash_aset(moved_down, type_sym(i), SIZET2NUM(objspace->rcompactor.moved_down_count_table[i]));
12965 }
12966 }
12967
12968 rb_hash_aset(h, ID2SYM(rb_intern("considered")), considered);
12969 rb_hash_aset(h, ID2SYM(rb_intern("moved")), moved);
12970 rb_hash_aset(h, ID2SYM(rb_intern("moved_up")), moved_up);
12971 rb_hash_aset(h, ID2SYM(rb_intern("moved_down")), moved_down);
12972
12973 return h;
12974}
12975#else
12976# define gc_compact_stats rb_f_notimplement
12977#endif
12978
12979#if GC_CAN_COMPILE_COMPACTION
12980/*
12981 * call-seq:
12982 * GC.compact -> hash
12983 *
12984 * This function compacts objects together in Ruby's heap. It eliminates
12985 * unused space (or fragmentation) in the heap by moving objects in to that
12986 * unused space. If there is more than 1 running Ractor, it runs a global
12987 * GC compaction (all object spaces).
12988 *
12989 * The returned +hash+ contains statistics about the objects that were moved;
12990 * see GC.latest_compact_info.
12991 *
12992 * This method is only expected to work on CRuby.
12993 *
12994 * To test whether \GC compaction is supported, use the idiom:
12995 *
12996 * GC.respond_to?(:compact)
12997 */
12998static VALUE
12999gc_compact(VALUE self)
13000{
13001 rb_objspace_t *objspace = rb_gc_get_objspace();
13002 int full_marking_p = gc_config_full_mark_val;
13003 gc_config_full_mark_set(TRUE);
13004
13005 /* Run GC with compaction enabled */
13006 rb_gc_impl_start(rb_gc_get_objspace(), true, true, true, true, true);
13007 gc_config_full_mark_set(full_marking_p);
13008
13009 return gc_compact_stats(self);
13010}
13011#else
13012# define gc_compact rb_f_notimplement
13013#endif
13014
13015#if GC_CAN_COMPILE_COMPACTION
13016struct desired_compaction_pages_i_data {
13018 size_t required_slots[HEAP_COUNT];
13019};
13020
13021static int
13022desired_compaction_pages_i(struct heap_page *page, void *data)
13023{
13024 struct desired_compaction_pages_i_data *tdata = data;
13025 rb_objspace_t *objspace = tdata->objspace;
13026 VALUE vstart = (VALUE)page->start;
13027 VALUE vend = vstart + (VALUE)(page->total_slots * page->heap->slot_size);
13028
13029
13030 for (VALUE v = vstart; v != vend; v += page->heap->slot_size) {
13031 asan_unpoisoning_object(v) {
13032 /* skip T_NONEs; they won't be moved */
13033 if (BUILTIN_TYPE(v) != T_NONE) {
13034 rb_heap_t *dest_pool = gc_compact_destination_pool(objspace, page->heap, v);
13035 size_t dest_pool_idx = dest_pool - heaps;
13036 tdata->required_slots[dest_pool_idx]++;
13037 }
13038 }
13039 }
13040
13041 return 0;
13042}
13043
13044/* call-seq:
13045 * GC.verify_compaction_references(toward: nil, double_heap: false) -> hash
13046 *
13047 * Verify compaction reference consistency.
13048 *
13049 * This method is implementation specific. During compaction, objects that
13050 * were moved are replaced with T_MOVED objects. No object should have a
13051 * reference to a T_MOVED object after compaction.
13052 *
13053 * This function expands the heap to ensure room to move all objects,
13054 * compacts the heap to make sure everything moves, updates all references,
13055 * then performs a full \GC. If any object contains a reference to a T_MOVED
13056 * object, that object should be pushed on the mark stack, and will
13057 * make a SEGV.
13058 */
13059static VALUE
13060gc_verify_compaction_references(int argc, VALUE* argv, VALUE self)
13061{
13062 static ID keywords[3] = {0};
13063 if (!keywords[0]) {
13064 keywords[0] = rb_intern("toward");
13065 keywords[1] = rb_intern("double_heap");
13066 keywords[2] = rb_intern("expand_heap");
13067 }
13068
13069 VALUE options;
13070 rb_scan_args_kw(rb_keyword_given_p(), argc, argv, ":", &options);
13071
13072 VALUE arguments[3] = { Qnil, Qfalse, Qfalse };
13073 int kwarg_count = rb_get_kwargs(options, keywords, 0, 3, arguments);
13074 bool toward_empty = kwarg_count > 0 && SYMBOL_P(arguments[0]) && SYM2ID(arguments[0]) == rb_intern("empty");
13075 bool expand_heap = (kwarg_count > 1 && RTEST(arguments[1])) || (kwarg_count > 2 && RTEST(arguments[2]));
13076
13077 rb_objspace_t *objspace = rb_gc_get_objspace();
13078
13079 /* This verification machinery (heap expansion, toward_empty page ordering, the
13080 * moved-reference walk) is built for a single objspace, so with several demote it
13081 * to a plain full GC. Plain GC.compact does compact them via the global GC. */
13082 if (!rb_gc_single_objspace_p()) {
13083 rb_gc_impl_start(objspace, true, true, true, false, false);
13084 return gc_compact_stats(self);
13085 }
13086
13087 /* Clear the heap. */
13088 rb_gc_impl_start(objspace, true, true, true, false, false);
13089
13090 unsigned int lev = RB_GC_VM_LOCK();
13091 {
13092 gc_rest(objspace);
13093
13094 /* if both double_heap and expand_heap are set, expand_heap takes precedence */
13095 if (expand_heap) {
13096 struct desired_compaction_pages_i_data desired_compaction = {
13097 .objspace = objspace,
13098 .required_slots = {0},
13099 };
13100 /* Work out how many objects want to be in each size pool, taking account of moves */
13101 objspace_each_pages(objspace, desired_compaction_pages_i, &desired_compaction, TRUE);
13102
13103 /* Find out which pool has the most pages */
13104 size_t max_existing_pages = 0;
13105 for (int i = 0; i < HEAP_COUNT; i++) {
13106 rb_heap_t *heap = &heaps[i];
13107 max_existing_pages = MAX(max_existing_pages, heap->total_pages);
13108 }
13109
13110 /* Add pages to each size pool so that compaction is guaranteed to move every object */
13111 for (int i = 0; i < HEAP_COUNT; i++) {
13112 rb_heap_t *heap = &heaps[i];
13113
13114 size_t pages_to_add = 0;
13115 /*
13116 * Step 1: Make sure every pool has the same number of pages, by adding empty pages
13117 * to smaller pools. This is required to make sure the compact cursor can advance
13118 * through all of the pools in `gc_sweep_compact` without hitting the "sweep &
13119 * compact cursors met" condition on some pools before fully compacting others
13120 */
13121 pages_to_add += max_existing_pages - heap->total_pages;
13122 /*
13123 * Step 2: Now add additional free pages to each size pool sufficient to hold all objects
13124 * that want to be in that size pool, whether moved into it or moved within it
13125 */
13126 objspace->heap_pages.allocatable_bytes = desired_compaction.required_slots[i] * heap->slot_size;
13127 while (objspace->heap_pages.allocatable_bytes > 0) {
13128 heap_page_allocate_and_initialize(objspace, heap);
13129 }
13130 /*
13131 * Step 3: Add two more pages so that the compact & sweep cursors will meet _after_ all objects
13132 * have been moved, and not on the last iteration of the `gc_sweep_compact` loop
13133 */
13134 pages_to_add += 2;
13135
13136 for (; pages_to_add > 0; pages_to_add--) {
13137 heap_page_allocate_and_initialize_force(objspace, heap);
13138 }
13139 }
13140 }
13141
13142 if (toward_empty) {
13143 objspace->rcompactor.compare_func = compare_free_slots;
13144 }
13145 }
13146 RB_GC_VM_UNLOCK(lev);
13147
13148 rb_gc_impl_start(rb_gc_get_objspace(), true, true, true, true, false);
13149
13150 rb_objspace_reachable_objects_from_root(root_obj_check_moved_i, objspace);
13151 objspace_each_objects(objspace, heap_check_moved_i, objspace, TRUE);
13152
13153 objspace->rcompactor.compare_func = NULL;
13154
13155 return gc_compact_stats(self);
13156}
13157#else
13158# define gc_verify_compaction_references rb_f_notimplement
13159#endif
13160
13161void
13162rb_gc_impl_objspace_free(void *objspace_ptr)
13163{
13164 rb_objspace_t *objspace = objspace_ptr;
13165
13166 if (is_lazy_sweeping(objspace))
13167 rb_bug("lazy sweeping underway when freeing object space");
13168
13169 free(objspace->profile.records);
13170 objspace->profile.records = NULL;
13171
13172 for (size_t i = 0; i < rb_darray_size(objspace->heap_pages.sorted); i++) {
13173 heap_page_free(objspace, rb_darray_get(objspace->heap_pages.sorted, i));
13174 }
13175 rb_darray_free_without_gc(objspace->heap_pages.sorted);
13176 heap_pages_lomem = 0;
13177 heap_pages_himem = 0;
13178
13179 for (int i = 0; i < HEAP_COUNT; i++) {
13180 rb_heap_t *heap = &heaps[i];
13181 heap->total_pages = 0;
13182 heap->total_slots = 0;
13183 }
13184
13185 free_stack_chunks(&objspace->mark_stack);
13186 mark_stack_free_cache(&objspace->mark_stack);
13187
13188 rb_darray_free_without_gc(objspace->weak_references);
13189
13190#ifdef MALLOC_COUNTERS_NEED_LOCK
13191 rb_native_mutex_destroy(&objspace->malloc_counters.lock);
13192#endif
13193
13194 rb_native_mutex_destroy(&objspace->process_stat.lock);
13195
13196 free(objspace);
13197}
13198
13199#if MALLOC_ALLOCATED_SIZE
13200/*
13201 * call-seq:
13202 * GC.malloc_allocated_size -> Integer
13203 *
13204 * Returns the size of memory allocated by malloc().
13205 *
13206 * Only available if ruby was built with +CALC_EXACT_MALLOC_SIZE+.
13207 */
13208
13209static VALUE
13210gc_malloc_allocated_size(VALUE self)
13211{
13212 rb_objspace_t *objspace = (rb_objspace_t *)rb_gc_get_objspace();
13213 return ULL2NUM(objspace->malloc_params.allocated_size);
13214}
13215
13216/*
13217 * call-seq:
13218 * GC.malloc_allocations -> Integer
13219 *
13220 * Returns the number of malloc() allocations.
13221 *
13222 * Only available if ruby was built with +CALC_EXACT_MALLOC_SIZE+.
13223 */
13224
13225static VALUE
13226gc_malloc_allocations(VALUE self)
13227{
13228 rb_objspace_t *objspace = (rb_objspace_t *)rb_gc_get_objspace();
13229 return ULL2NUM(objspace->malloc_params.allocations);
13230}
13231#endif
13232
13233void
13234rb_gc_impl_before_fork(void *objspace_ptr)
13235{
13236 rb_objspace_t *objspace = objspace_ptr;
13237
13238 objspace->fork_vm_lock_lev = RB_GC_VM_LOCK();
13239 rb_gc_vm_barrier();
13240}
13241
13242void
13243rb_gc_impl_after_fork(void *objspace_ptr, rb_pid_t pid)
13244{
13245 rb_objspace_t *objspace = objspace_ptr;
13246
13247 if (pid == 0) {
13248 rb_gc_vm_each_objspace(gc_process_stat_after_fork_i, NULL);
13249 }
13250
13251 RB_GC_VM_UNLOCK(objspace->fork_vm_lock_lev);
13252 objspace->fork_vm_lock_lev = 0;
13253
13254 if (pid == 0) { /* child process */
13255 heap_alloc_state_clear(objspace);
13256 /* The forking Ractor becomes the child process's main Ractor. */
13257 global_objspace->main_objspace = objspace;
13258 page_pool_lock_initialize(&rb_global_objspace_instance.page_pool.lock);
13259 }
13260}
13261
13262VALUE rb_ident_hash_new_capa(long size);
13263
13264#if GC_DEBUG_STRESS_TO_CLASS
13265/*
13266 * call-seq:
13267 * GC.add_stress_to_class(class[, ...])
13268 *
13269 * Raises NoMemoryError when allocating an instance of the given classes.
13270 *
13271 */
13272static VALUE
13273rb_gcdebug_add_stress_to_class(int argc, VALUE *argv, VALUE self)
13274{
13275 rb_objspace_t *objspace = rb_gc_get_objspace();
13276
13277 if (!stress_to_class) {
13278 set_stress_to_class(rb_ident_hash_new_capa(argc));
13279 }
13280
13281 for (int i = 0; i < argc; i++) {
13282 VALUE klass = argv[i];
13283 rb_hash_aset(stress_to_class, klass, Qtrue);
13284 }
13285
13286 return self;
13287}
13288
13289/*
13290 * call-seq:
13291 * GC.remove_stress_to_class(class[, ...])
13292 *
13293 * No longer raises NoMemoryError when allocating an instance of the
13294 * given classes.
13295 *
13296 */
13297static VALUE
13298rb_gcdebug_remove_stress_to_class(int argc, VALUE *argv, VALUE self)
13299{
13300 rb_objspace_t *objspace = rb_gc_get_objspace();
13301
13302 if (stress_to_class) {
13303 for (int i = 0; i < argc; ++i) {
13304 rb_hash_delete(stress_to_class, argv[i]);
13305 }
13306
13307 if (rb_hash_size(stress_to_class) == 0) {
13308 stress_to_class = 0;
13309 }
13310 }
13311
13312 return Qnil;
13313}
13314#endif
13315
13316void *
13317rb_gc_impl_objspace_alloc(void)
13318{
13319 global_objspace_init();
13320
13321 rb_objspace_t *objspace = calloc1(sizeof(rb_objspace_t));
13322 if (objspace) {
13323 rb_native_mutex_initialize(&objspace->process_stat.lock);
13324 }
13325
13326 return objspace;
13327}
13328
13329void
13330rb_gc_impl_objspace_init(void *objspace_ptr)
13331{
13332 rb_objspace_t *objspace = objspace_ptr;
13333
13334 gc_config_full_mark_set(TRUE);
13335
13336 malloc_limit = gc_params.malloc_limit_min;
13337 objspace->shareable_objects_limit = SHAREABLE_OBJECTS_LIMIT_MIN;
13338#ifdef MALLOC_COUNTERS_NEED_LOCK
13339 rb_native_mutex_initialize(&objspace->malloc_counters.lock);
13340#endif
13341 /* Shared by every objspace. preregister deduplicates on (func, data). */
13342 objspace->finalize_deferred_pjob = rb_postponed_job_preregister(0, gc_finalize_deferred, NULL);
13343 if (objspace->finalize_deferred_pjob == POSTPONED_JOB_HANDLE_INVALID) {
13344 rb_bug("Could not preregister postponed job for GC");
13345 }
13346
13347 gc_tdata_deferred_free_pjob_ensure();
13348
13349 /* A standard RVALUE (RBasic + embedded VALUEs + debug overhead) must fit
13350 * in at least one pool. In debug builds RVALUE_OVERHEAD can push this
13351 * beyond the 48-byte pool into the 64-byte pool, which is fine. */
13352 GC_ASSERT(rb_gc_impl_size_allocatable_p(sizeof(struct RBasic) + sizeof(VALUE[RBIMPL_RVALUE_EMBED_LEN_MAX])));
13353
13354 for (int i = 0; i < HEAP_COUNT; i++) {
13355 rb_heap_t *heap = &heaps[i];
13356
13357 heap->slot_size = pool_slot_sizes[i];
13358
13359 ccan_list_head_init(&heap->pages);
13360 }
13361
13362 if (global_objspace->main_objspace == NULL) {
13363 /* Single-threaded at boot and the first objspace is main's: compute process-wide
13364 * constants once here. A later objspace_init rewriting them, even with equal
13365 * values, would race other threads' lock-free reads. */
13366 global_objspace->main_objspace = objspace;
13367
13368 init_size_to_heap_idx();
13369
13370#if defined(INIT_HEAP_PAGE_ALLOC_USE_MMAP)
13371 /* Need to determine if we can use mmap at runtime. */
13372 heap_page_alloc_use_mmap = INIT_HEAP_PAGE_ALLOC_USE_MMAP;
13373#endif
13374 gc_params.heap_init_bytes = GC_HEAP_INIT_BYTES;
13375 gc_params.ractor_heap_init_bytes = GC_RACTOR_HEAP_INIT_BYTES ? GC_RACTOR_HEAP_INIT_BYTES
13376 : heap_init_bytes_min();
13377 }
13378 // GC.measure_total_time= sets the caller's objspace only; a new Ractor's follows
13379 // its creator's, which is the objspace running this init (main starts it on).
13380 objspace->flags.measure_gc = global_objspace->main_objspace == objspace ? true
13381 : ((rb_objspace_t *)rb_gc_get_objspace())->flags.measure_gc;
13382
13383 rb_darray_make_without_gc(&objspace->heap_pages.sorted, 0);
13384 rb_darray_make_without_gc(&objspace->weak_references, 0);
13385
13386#if RGENGC_ESTIMATE_OLDMALLOC
13387 objspace->rgengc.oldmalloc_increase_limit = gc_params.oldmalloc_limit_min;
13388#endif
13389
13390 init_mark_stack(&objspace->mark_stack);
13391
13392 objspace->profile.invoke_time = getrusage_time();
13393 objspace->profile.invoke_wall_time = rb_hrtime_now();
13394 objspace->profile.max_records = GC_PROFILE_RECORD_DEFAULT_MAX_RECORDS;
13395 finalizer_table = st_init_numtable();
13396
13397 gc_process_stat_publish(objspace);
13398}
13399
13400void
13401rb_gc_impl_init(void)
13402{
13403 /* Fill the symbol tables here, where no other ractor exists yet: they used to
13404 * be filled on first use, guarded by their own first element, so a second
13405 * ractor could see a half-filled table and GC.stat raised on the rest. */
13406 setup_gc_stat_symbols();
13407 setup_gc_stat_heap_symbols();
13408 setup_gc_latest_gc_info_symbols();
13409
13410 VALUE gc_constants = rb_hash_new();
13411 rb_hash_aset(gc_constants, ID2SYM(rb_intern("DEBUG")), GC_DEBUG ? Qtrue : Qfalse);
13412 /* Minimum slot size that fits a standard RVALUE */
13413 size_t rvalue_pool = 0;
13414 for (size_t i = 0; i < HEAP_COUNT; i++) {
13415 if (pool_slot_sizes[i] >= RVALUE_SLOT_SIZE) { rvalue_pool = pool_slot_sizes[i]; break; }
13416 }
13417 rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVALUE_SIZE")), SIZET2NUM(rvalue_pool - RVALUE_OVERHEAD));
13418 rb_hash_aset(gc_constants, ID2SYM(rb_intern("RBASIC_SIZE")), SIZET2NUM(sizeof(struct RBasic)));
13419 rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVALUE_OVERHEAD")), SIZET2NUM(RVALUE_OVERHEAD));
13420 rb_hash_aset(gc_constants, ID2SYM(rb_intern("HEAP_PAGE_BITMAP_SIZE")), SIZET2NUM(HEAP_PAGE_BITMAP_SIZE));
13421 rb_hash_aset(gc_constants, ID2SYM(rb_intern("HEAP_PAGE_SIZE")), SIZET2NUM(HEAP_PAGE_SIZE));
13422 rb_hash_aset(gc_constants, ID2SYM(rb_intern("HEAP_COUNT")), LONG2FIX(HEAP_COUNT));
13423 rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVARGC_MAX_ALLOCATE_SIZE")), SIZET2NUM(rb_gc_impl_max_allocation_size()));
13424 rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVALUE_OLD_AGE")), LONG2FIX(RVALUE_OLD_AGE));
13425 if (RB_BUG_INSTEAD_OF_RB_MEMERROR+0) {
13426 rb_hash_aset(gc_constants, ID2SYM(rb_intern("RB_BUG_INSTEAD_OF_RB_MEMERROR")), Qtrue);
13427 }
13428 OBJ_FREEZE(gc_constants);
13429 /* Internal constants in the garbage collector. */
13430 rb_define_const(rb_mGC, "INTERNAL_CONSTANTS", gc_constants);
13431
13432 if (GC_COMPACTION_SUPPORTED) {
13433 rb_define_singleton_method(rb_mGC, "compact", gc_compact, 0);
13434 rb_define_singleton_method(rb_mGC, "auto_compact", gc_get_auto_compact, 0);
13435 rb_define_singleton_method(rb_mGC, "auto_compact=", gc_set_auto_compact, 1);
13436 rb_define_singleton_method(rb_mGC, "latest_compact_info", gc_compact_stats, 0);
13437 rb_define_singleton_method(rb_mGC, "verify_compaction_references", gc_verify_compaction_references, -1);
13438 }
13439 else {
13443 rb_define_singleton_method(rb_mGC, "latest_compact_info", rb_f_notimplement, 0);
13444 rb_define_singleton_method(rb_mGC, "verify_compaction_references", rb_f_notimplement, -1);
13445 }
13446
13447#if GC_DEBUG_STRESS_TO_CLASS
13448 rb_define_singleton_method(rb_mGC, "add_stress_to_class", rb_gcdebug_add_stress_to_class, -1);
13449 rb_define_singleton_method(rb_mGC, "remove_stress_to_class", rb_gcdebug_remove_stress_to_class, -1);
13450#endif
13451
13452 /* internal methods */
13453 rb_define_singleton_method(rb_mGC, "verify_internal_consistency", gc_verify_internal_consistency_m, 0);
13454
13455#if MALLOC_ALLOCATED_SIZE
13456 rb_define_singleton_method(rb_mGC, "malloc_allocated_size", gc_malloc_allocated_size, 0);
13457 rb_define_singleton_method(rb_mGC, "malloc_allocations", gc_malloc_allocations, 0);
13458#endif
13459
13460 /* Document-class: GC::Profiler
13461 *
13462 * The GC profiler provides access to information on GC runs including time,
13463 * length and object space size.
13464 *
13465 * Example:
13466 *
13467 * GC::Profiler.enable
13468 *
13469 * require 'rdoc/rdoc'
13470 *
13471 * GC::Profiler.report
13472 *
13473 * pp GC::Profiler.raw_data
13474 *
13475 * GC::Profiler.disable
13476 *
13477 * GC::Profiler.raw_data returns one Hash per GC run, including CPU time
13478 * fields such as +:GC_TIME+ and wall-clock fields such as +:GC_WALL_TIME+,
13479 * +:GC_PAUSE_TIME+, +:GC_STOP_TIME+, and +:GC_STW_TIME+. +:GC_WALL_TIME+
13480 * is the wall-clock counterpart to +:GC_TIME+, while +:GC_PAUSE_TIME+
13481 * measures how long user execution was blocked by the GC entry.
13482 *
13483 * See also GC.count, GC.malloc_allocated_size and GC.malloc_allocations
13484 */
13485 VALUE rb_mProfiler = rb_define_module_under(rb_mGC, "Profiler");
13486 rb_define_singleton_method(rb_mProfiler, "enabled?", gc_profile_enable_get, 0);
13487 rb_define_singleton_method(rb_mProfiler, "enable", gc_profile_enable, 0);
13488 rb_define_singleton_method(rb_mProfiler, "raw_data", gc_profile_record_get, -1);
13489 rb_define_singleton_method(rb_mProfiler, "disable", gc_profile_disable, 0);
13490 rb_define_singleton_method(rb_mProfiler, "clear", gc_profile_clear, 0);
13491 rb_define_singleton_method(rb_mProfiler, "configure", gc_profile_configure, -1);
13492 rb_define_singleton_method(rb_mProfiler, "result", gc_profile_result, 0);
13493 rb_define_singleton_method(rb_mProfiler, "report", gc_profile_report, -1);
13494 rb_define_singleton_method(rb_mProfiler, "total_time", gc_profile_total_time, 0);
13495
13496 {
13497 VALUE opts;
13498 /* \GC build options */
13499 rb_define_const(rb_mGC, "OPTS", opts = rb_ary_new());
13500#define OPT(o) if (o) rb_ary_push(opts, rb_interned_str(#o, sizeof(#o) - 1))
13501 OPT(GC_DEBUG);
13502 OPT(USE_RGENGC);
13503 OPT(RGENGC_DEBUG);
13504 OPT(RGENGC_CHECK_MODE);
13505 OPT(RGENGC_PROFILE);
13506 OPT(RGENGC_ESTIMATE_OLDMALLOC);
13507 OPT(GC_PROFILE_MORE_DETAIL);
13508 OPT(GC_ENABLE_LAZY_SWEEP);
13509 OPT(CALC_EXACT_MALLOC_SIZE);
13510 OPT(MALLOC_ALLOCATED_SIZE);
13511 OPT(MALLOC_ALLOCATED_SIZE_CHECK);
13512 OPT(GC_PROFILE_DETAIL_MEMORY);
13513 OPT(GC_COMPACTION_SUPPORTED);
13514#undef OPT
13515 OBJ_FREEZE(opts);
13516 }
13517}
#define RBIMPL_ASSERT_OR_ASSUME(...)
This is either RUBY_ASSERT or RBIMPL_ASSUME, depending on RUBY_DEBUG.
Definition assert.h:311
#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 RUBY_ATOMIC_VALUE_CAS(var, oldval, newval)
Identical to RUBY_ATOMIC_CAS, except it expects its arguments are VALUE.
Definition atomic.h:406
#define RUBY_ATOMIC_SIZE_EXCHANGE(var, val)
Identical to RUBY_ATOMIC_EXCHANGE, except it expects its arguments are size_t.
Definition atomic.h:270
#define RUBY_ATOMIC_SIZE_INC(var)
Identical to RUBY_ATOMIC_INC, except it expects its argument is size_t.
Definition atomic.h:246
#define RUBY_ATOMIC_SIZE_CAS(var, oldval, newval)
Identical to RUBY_ATOMIC_CAS, except it expects its arguments are size_t.
Definition atomic.h:284
std::atomic< unsigned > rb_atomic_t
Type that is eligible for atomic operations.
Definition atomic.h:69
#define RUBY_ATOMIC_SIZE_ADD(var, val)
Identical to RUBY_ATOMIC_ADD, except it expects its arguments are size_t.
Definition atomic.h:297
#define RUBY_ATOMIC_VALUE_EXCHANGE(var, val)
Identical to RUBY_ATOMIC_EXCHANGE, except it expects its arguments are VALUE.
Definition atomic.h:392
#define RUBY_ATOMIC_SET(var, val)
Identical to RUBY_ATOMIC_EXCHANGE, except for the return type.
Definition atomic.h:185
#define RUBY_ATOMIC_EXCHANGE(var, val)
Atomically replaces the value pointed by var with val.
Definition atomic.h:152
#define rb_define_singleton_method(klass, mid, func, arity)
Defines klass.mid.
unsigned int rb_postponed_job_handle_t
The type of a handle returned from rb_postponed_job_preregister and passed to rb_postponed_job_trigge...
Definition debug.h:703
void rb_postponed_job_trigger(rb_postponed_job_handle_t h)
Triggers a pre-registered job registered with rb_postponed_job_preregister, scheduling it for executi...
Definition vm_trace.c:1887
rb_postponed_job_handle_t rb_postponed_job_preregister(unsigned int flags, rb_postponed_job_func_t func, void *data)
Pre-registers a func in Ruby's postponed job preregistration table, returning an opaque handle which ...
Definition vm_trace.c:1853
#define RB_GNUC_EXTENSION_BLOCK(x)
This is expanded to the passed token for non-GCC compilers.
Definition defines.h:91
#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_OBJSPACE_MASK
Bitmask of GC events.
Definition event.h:100
#define RUBY_INTERNAL_EVENT_FREEOBJ
Object swept.
Definition event.h:94
#define RUBY_INTERNAL_EVENT_GC_START
GC started.
Definition event.h:95
uint32_t rb_event_flag_t
Represents event(s).
Definition event.h:108
static VALUE RB_FL_TEST(VALUE obj, VALUE flags)
Tests if the given flag(s) are set or not.
Definition fl_type.h:433
static VALUE RB_FL_TEST_RAW(VALUE obj, VALUE flags)
This is an implementation detail of RB_FL_TEST().
Definition fl_type.h:407
static void RB_FL_SET_RAW(VALUE obj, VALUE flags)
This is an implementation detail of RB_FL_SET().
Definition fl_type.h:544
static void RB_FL_UNSET_RAW(VALUE obj, VALUE flags)
This is an implementation detail of RB_FL_UNSET().
Definition fl_type.h:604
@ RUBY_FL_PROMOTED
Ruby objects are "generational".
Definition fl_type.h:205
@ RUBY_FL_SHAREABLE
This flag has something to do with Ractor.
Definition fl_type.h:253
@ RUBY_FL_WEAK_REFERENCE
This object weakly refers to other objects.
Definition fl_type.h:260
int rb_scan_args_kw(int kw_flag, int argc, const VALUE *argv, const char *fmt,...)
Identical to rb_scan_args(), except it also accepts kw_splat.
Definition class.c:3397
int rb_keyword_given_p(void)
Determines if the current method is given a keyword argument.
Definition eval.c:1048
int rb_get_kwargs(VALUE keyword_hash, const ID *table, int required, int optional, VALUE *values)
Keyword argument deconstructor.
Definition class.c:3173
#define T_COMPLEX
Old name of RUBY_T_COMPLEX.
Definition value_type.h:59
#define T_FILE
Old name of RUBY_T_FILE.
Definition value_type.h:62
#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 T_MASK
Old name of RUBY_T_MASK.
Definition value_type.h:68
#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 T_NIL
Old name of RUBY_T_NIL.
Definition value_type.h:72
#define T_FLOAT
Old name of RUBY_T_FLOAT.
Definition value_type.h:64
#define T_IMEMO
Old name of RUBY_T_IMEMO.
Definition value_type.h:67
#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_FIXNUM
Old name of RUBY_T_FIXNUM.
Definition value_type.h:63
#define SYM2ID
Old name of RB_SYM2ID.
Definition symbol.h:45
#define T_DATA
Old name of RUBY_T_DATA.
Definition value_type.h:60
#define FL_SHAREABLE
Old name of RUBY_FL_SHAREABLE.
Definition fl_type.h:62
#define T_NONE
Old name of RUBY_T_NONE.
Definition value_type.h:74
#define T_NODE
Old name of RUBY_T_NODE.
Definition value_type.h:73
#define SIZET2NUM
Old name of RB_SIZE2NUM.
Definition size_t.h:62
#define xmalloc
Old name of ruby_xmalloc.
Definition xmalloc.h:53
#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_MODULE
Old name of RUBY_T_MODULE.
Definition value_type.h:70
#define T_TRUE
Old name of RUBY_T_TRUE.
Definition value_type.h:81
#define T_RATIONAL
Old name of RUBY_T_RATIONAL.
Definition value_type.h:76
#define T_ICLASS
Old name of RUBY_T_ICLASS.
Definition value_type.h:66
#define T_HASH
Old name of RUBY_T_HASH.
Definition value_type.h:65
#define ALLOC_N
Old name of RB_ALLOC_N.
Definition memory.h:399
#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 T_FALSE
Old name of RUBY_T_FALSE.
Definition value_type.h:61
#define ULL2NUM
Old name of RB_ULL2NUM.
Definition long_long.h:31
#define T_UNDEF
Old name of RUBY_T_UNDEF.
Definition value_type.h:82
#define Qtrue
Old name of RUBY_Qtrue.
#define T_ZOMBIE
Old name of RUBY_T_ZOMBIE.
Definition value_type.h:83
#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 FL_WB_PROTECTED
Old name of RUBY_FL_WB_PROTECTED.
Definition fl_type.h:59
#define T_SYMBOL
Old name of RUBY_T_SYMBOL.
Definition value_type.h:80
#define DBL2NUM
Old name of rb_float_new.
Definition double.h:29
#define T_MATCH
Old name of RUBY_T_MATCH.
Definition value_type.h:69
#define T_CLASS
Old name of RUBY_T_CLASS.
Definition value_type.h:58
#define BUILTIN_TYPE
Old name of RB_BUILTIN_TYPE.
Definition value_type.h:85
#define T_MOVED
Old name of RUBY_T_MOVED.
Definition value_type.h:71
#define FL_TEST
Old name of RB_FL_TEST.
Definition fl_type.h:127
#define NUM2LONG
Old name of RB_NUM2LONG.
Definition long.h:51
#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
#define T_REGEXP
Old name of RUBY_T_REGEXP.
Definition value_type.h:77
#define ruby_verbose
This variable controls whether the interpreter is in debug mode.
Definition error.h:476
VALUE rb_eRuntimeError
RuntimeError exception.
Definition error.c:1471
void rb_warn(const char *fmt,...)
Identical to rb_warning(), except it reports unless $VERBOSE is nil.
Definition error.c:468
VALUE rb_obj_hide(VALUE obj)
Make the object invisible from Ruby code.
Definition object.c:94
VALUE rb_mGC
GC module.
Definition gc.c:468
VALUE rb_equal(VALUE lhs, VALUE rhs)
This function is an optimised version of calling #==.
Definition object.c:140
VALUE rb_stdout
STDOUT constant.
Definition io.c:207
Routines to manipulate encodings of strings.
static bool RB_OBJ_PROMOTED_RAW(VALUE obj)
This is the implementation of RB_OBJ_PROMOTED().
Definition gc.h:594
#define USE_RGENGC
Definition gc.h:464
VALUE rb_ary_dup(VALUE ary)
Duplicates an array.
VALUE rb_ary_new(void)
Allocates a new, empty array.
VALUE rb_ary_push(VALUE ary, VALUE elem)
Special case of rb_ary_cat() that it adds only one element.
static int rb_check_arity(int argc, int min, int max)
Ensures that the passed integer is in the passed range.
Definition error.h:284
VALUE rb_str_buf_append(VALUE dst, VALUE src)
Identical to rb_str_cat_cstr(), except it takes Ruby's string instead of C's.
Definition string.c:3872
VALUE rb_str_buf_new(long capa)
Allocates a "string buffer".
Definition string.c:1763
#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
const char * rb_sourcefile(void)
Resembles __FILE__.
Definition vm.c:2168
VALUE rb_f_notimplement(int argc, const VALUE *argv, VALUE obj, VALUE marker)
Raises rb_eNotImpError.
Definition vm_method.c:909
int rb_sourceline(void)
Resembles __LINE__.
Definition vm.c:2182
#define RB_SYM2ID
Just another name of rb_sym2id.
Definition symbol.h:43
ID rb_sym2id(VALUE obj)
Converts an instance of rb_cSymbol into an ID.
Definition symbol.c:1091
int capa
Designed capacity of the buffer.
Definition io.h:11
int len
Length of the buffer.
Definition io.h:8
void * rb_thread_call_with_gvl(void *(*func)(void *), void *data1)
(Re-)acquires the GVL.
Definition thread.c:2319
#define strtod(s, e)
Just another name of ruby_strtod.
Definition util.h:223
void ruby_qsort(void *, const size_t, const size_t, int(*)(const void *, const void *, void *), void *)
Reentrant implementation of quick sort.
#define MEMZERO(p, type, n)
Handy macro to erase a region of memory.
Definition memory.h:360
#define RB_GC_GUARD(v)
Prevents premature destruction of local objects.
Definition memory.h:167
VALUE type(ANYARGS)
ANYARGS-ed function type.
void rb_hash_foreach(VALUE q, int_type *w, VALUE e)
Iteration over the given hash.
VALUE rb_ensure(type *q, VALUE w, type *e, VALUE r)
An equivalent of ensure clause.
#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
#define RUBY_TYPED_FREE_IMMEDIATELY
Macros to see if each corresponding flag is defined.
Definition rtypeddata.h:122
static const rb_data_type_t * RTYPEDDATA_TYPE(VALUE obj)
Queries for the type of given object.
Definition rtypeddata.h:692
#define RTYPEDDATA(obj)
Convenient casting macro.
Definition rtypeddata.h:96
#define errno
Ractor-aware version of errno.
Definition ruby.h:388
int ruby_native_thread_p(void)
Queries if the thread which calls this function is a ruby's thread.
Definition thread.c:6157
static bool RB_SPECIAL_CONST_P(VALUE obj)
Checks if the given object is of enum ruby_special_consts.
#define RTEST
This is an old name of RB_TEST.
#define _(args)
This was a transition path from K&R to ANSI.
Definition stdarg.h:35
Ruby object's base components.
Definition rbasic.h:69
This is the struct that holds necessary info for a struct.
Definition rtypeddata.h:242
Definition gc_impl.h:34
Definition st.h:79
Definition default.c:595
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.
void rb_native_mutex_destroy(rb_nativethread_lock_t *lock)
Just another name of rb_nativethread_lock_destroy.
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
ruby_value_type
C-level type of an object.
Definition value_type.h:113
@ RUBY_T_SYMBOL
Definition value_type.h:135
@ RUBY_T_MATCH
Definition value_type.h:128
@ RUBY_T_MODULE
Definition value_type.h:118
@ RUBY_T_ICLASS
Hidden classes known as IClasses.
Definition value_type.h:141
@ RUBY_T_MOVED
Definition value_type.h:143
@ RUBY_T_FIXNUM
Integers formerly known as Fixnums.
Definition value_type.h:136
@ RUBY_T_IMEMO
Definition value_type.h:139
@ RUBY_T_NODE
Definition value_type.h:140
@ RUBY_T_OBJECT
Definition value_type.h:116
@ RUBY_T_DATA
Definition value_type.h:127
@ RUBY_T_FALSE
Definition value_type.h:134
@ RUBY_T_UNDEF
Definition value_type.h:137
@ RUBY_T_COMPLEX
Definition value_type.h:129
@ RUBY_T_STRING
Definition value_type.h:120
@ RUBY_T_HASH
Definition value_type.h:123
@ RUBY_T_NIL
Definition value_type.h:132
@ RUBY_T_CLASS
Definition value_type.h:117
@ RUBY_T_ARRAY
Definition value_type.h:122
@ RUBY_T_MASK
Bitmask of ruby_value_type.
Definition value_type.h:145
@ RUBY_T_RATIONAL
Definition value_type.h:130
@ RUBY_T_ZOMBIE
Definition value_type.h:142
@ RUBY_T_BIGNUM
Definition value_type.h:125
@ RUBY_T_TRUE
Definition value_type.h:133
@ RUBY_T_FLOAT
Definition value_type.h:119
@ RUBY_T_STRUCT
Definition value_type.h:124
@ RUBY_T_NONE
Non-object (swept etc.)
Definition value_type.h:114
@ RUBY_T_REGEXP
Definition value_type.h:121
@ RUBY_T_FILE
Definition value_type.h:126