Ruby 4.1.0dev (2026-10-02 revision 9f323525f8194f7670a1519d81a27cfb41dc9306)
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
4822#if defined(HAVE_PTHREAD_SIGMASK)
4823# define gc_sigmask pthread_sigmask
4824#else
4825# define gc_sigmask sigprocmask
4826#endif
4827
4828static void
4829read_barrier_signal(int sig, siginfo_t *info, void *data)
4830{
4831 // setup SEGV/BUS handlers for errors
4832 struct sigaction prev_sigbus, prev_sigsegv;
4833 sigaction(SIGBUS, &old_sigbus_handler, &prev_sigbus);
4834 sigaction(SIGSEGV, &old_sigsegv_handler, &prev_sigsegv);
4835
4836 // enable SIGBUS/SEGV
4837 sigset_t set, prev_set;
4838 sigemptyset(&set);
4839 sigaddset(&set, SIGBUS);
4840 sigaddset(&set, SIGSEGV);
4841 gc_sigmask(SIG_UNBLOCK, &set, &prev_set);
4842#ifdef HAVE_MACH_TASK_EXCEPTION_PORTS
4843 disable_mach_bad_access_exc();
4844#endif
4845 // run handler
4846 read_barrier_handler((uintptr_t)info->si_addr);
4847
4848 // reset SEGV/BUS handlers
4849#ifdef HAVE_MACH_TASK_EXCEPTION_PORTS
4850 restore_mach_bad_access_exc();
4851#endif
4852 sigaction(SIGBUS, &prev_sigbus, NULL);
4853 sigaction(SIGSEGV, &prev_sigsegv, NULL);
4854 gc_sigmask(SIG_SETMASK, &prev_set, NULL);
4855}
4856
4857static void
4858uninstall_handlers(void)
4859{
4860#ifdef HAVE_MACH_TASK_EXCEPTION_PORTS
4861 restore_mach_bad_access_exc();
4862#endif
4863 sigaction(SIGBUS, &old_sigbus_handler, NULL);
4864 sigaction(SIGSEGV, &old_sigsegv_handler, NULL);
4865}
4866
4867static void
4868install_handlers(void)
4869{
4870 struct sigaction action;
4871 memset(&action, 0, sizeof(struct sigaction));
4872 sigemptyset(&action.sa_mask);
4873 action.sa_sigaction = read_barrier_signal;
4874 action.sa_flags = SA_SIGINFO | SA_ONSTACK;
4875
4876 sigaction(SIGBUS, &action, &old_sigbus_handler);
4877 sigaction(SIGSEGV, &action, &old_sigsegv_handler);
4878#ifdef HAVE_MACH_TASK_EXCEPTION_PORTS
4879 disable_mach_bad_access_exc();
4880#endif
4881}
4882#endif
4883
4884static void
4885gc_compact_finish(rb_objspace_t *objspace)
4886{
4887 for (int i = 0; i < HEAP_COUNT; i++) {
4888 rb_heap_t *heap = &heaps[i];
4889 gc_unprotect_pages(objspace, heap);
4890 }
4891
4892 if (!global_objspace->global_gc.compacting) uninstall_handlers();
4893
4894 if (global_objspace->global_gc.compacting) {
4895 /* In a compacting global GC this updates only this objspace's heap references;
4896 * gc_start_global sets during_reference_updating on every objspace (the
4897 * move-or-mark decision reads it via rb_gc_get_objspace()) and runs the
4898 * non-idempotent VM-global side (gc_update_references_global) once at the end. */
4899 gc_update_references_heap(objspace);
4900 }
4901 else {
4902 gc_update_references(objspace);
4903 }
4904 objspace->profile.compact_count++;
4905
4906 for (int i = 0; i < HEAP_COUNT; i++) {
4907 rb_heap_t *heap = &heaps[i];
4908 heap->compact_cursor = NULL;
4909 heap->free_pages = NULL;
4910 heap->compact_cursor_index = 0;
4911 }
4912
4913 if (gc_prof_enabled(objspace)) {
4914 gc_profile_record *record = gc_prof_record(objspace);
4915 record->moved_objects = objspace->rcompactor.total_moved - record->moved_objects;
4916 }
4917 if (!global_objspace->global_gc.compacting) objspace->flags.during_compacting = FALSE;
4918}
4919
4921 struct heap_page *page;
4922 int final_slots;
4923 int freed_slots;
4924 int empty_slots;
4925 /* Hoisted out of the per-slot pinned-free assert: too expensive for the sweep loop
4926 * as an external call. */
4927 const bool check_pinned_free;
4928 /* This is a parallel local sweep (multi-Ractor, not a global GC), so a non-thread-safe
4929 * T_DATA dfree must be deferred to the global GC or the postponed job rather than run here. */
4930 const bool defer_thread_unsafe_local_sweep;
4931 bool trigger_thread_unsafe_sweep_postponed_job;
4932
4933 struct free_region *free_region;
4934};
4935
4936/* NOTE: We must free the root fiber during postmortem collection, otherwise another Ractor
4937 * can collect the fiber through a major GC while we're still tearing it down. Once fibers are
4938 * THREAD_SAFE_FREE, we no longer need the root fiber condition as it will be guaranteed to be
4939 * collected during this time. */
4940static bool
4941gc_obj_defer_local_free_p(rb_objspace_t *objspace, VALUE obj)
4942{
4943 if (BUILTIN_TYPE(obj) != T_DATA) return false;
4944
4945 const rb_data_type_t *type = RTYPEDDATA_TYPE(obj);
4946 if (!rb_gc_data_type_deferred_free_p(type)) return false;
4947
4948 if (RTYPEDDATA_GET_DATA(obj) == NULL) return false;
4949
4950 if (type->flags & RUBY_TYPED_FREE_IMMEDIATELY) {
4951 if (objspace->flags.during_postmortem) {
4952 if (rb_fiber_current() == obj) {
4953 return false;
4954 }
4955 }
4956 return true;
4957 }
4958 else {
4959 return false;
4960 }
4961}
4962
4963static void gc_tdata_deferred_free_job(void *unused);
4964static void gc_tdata_deferred_free_pjob_ensure(void);
4965static unsigned int gc_during_gc_get(const rb_objspace_t *objspace);
4966static void gc_during_gc_set(rb_objspace_t *objspace, unsigned int v);
4967static void gc_global_snapshot_objspaces(void);
4968
4969static void
4970gc_tdata_deferred_free_pjob_ensure(void)
4971{
4972 if (global_objspace->tdata_deferred_free_pjob == POSTPONED_JOB_HANDLE_INVALID) {
4973 global_objspace->tdata_deferred_free_pjob =
4974 rb_postponed_job_preregister(0, gc_tdata_deferred_free_job, NULL);
4975 if (global_objspace->tdata_deferred_free_pjob == POSTPONED_JOB_HANDLE_INVALID) {
4976 rb_bug("Could not preregister postponed job for deferred T_DATA free");
4977 }
4978 }
4979}
4980
4981/* A terminating Ractor's postmortem collection runs on an EC whose stack is already
4982 * torn down: it never checks interrupts again, so a job triggered there is lost and no
4983 * later sweep can rediscover the entries. Hand those to the main Ractor. */
4984static void
4985gc_tdata_deferred_free_trigger(rb_objspace_t *objspace)
4986{
4987 if (objspace->flags.during_postmortem) {
4988 rb_gc_trigger_postponed_job_on_main(global_objspace->tdata_deferred_free_pjob);
4989 }
4990 else {
4991 rb_postponed_job_trigger(global_objspace->tdata_deferred_free_pjob);
4992 }
4993}
4994
4995static void
4996gc_tdata_unsafe_free_entry(const struct tdata_unsafe_free_entry *entry, bool embed_xfree)
4997{
4998 entry->dfree(entry->data);
4999 if (embed_xfree) {
5000 xfree(entry->data);
5001 }
5002}
5003
5004static void
5005tdata_unsafe_free_chunk_recycle(struct tdata_unsafe_free_chunk *chunk)
5006{
5007 if (global_objspace->tdata_unsafe_free_cache_len >= TDATA_UNSAFE_FREE_CACHE_MAX) {
5008 free(chunk);
5009 return;
5010 }
5011 tdata_unsafe_free_chunk_reset(chunk);
5012 chunk->next = global_objspace->tdata_unsafe_free_cache;
5013 rbimpl_atomic_ptr_store((volatile void **)&global_objspace->tdata_unsafe_free_cache, chunk,
5014 RBIMPL_ATOMIC_RELEASE);
5015 global_objspace->tdata_unsafe_free_cache_len++;
5016}
5017
5018static void
5019gc_tdata_unsafe_drain_chunk(struct tdata_unsafe_free_chunk *chunk)
5020{
5021 for (unsigned int i = 0; i < chunk->count; i++) {
5022 gc_tdata_unsafe_free_entry(&chunk->entries[i],
5023 (chunk->embed_xfree_bits >> i) & 1);
5024 }
5025 tdata_unsafe_free_chunk_recycle(chunk);
5026}
5027
5028/* Run every pending deferred free: the published chunks (which belong to no objspace)
5029 * plus the given objspaces' partial chunks. The caller must have stopped the world --
5030 * VM barrier held, or a single Ractor left in the process -- and must pass every live
5031 * objspace, since the pending count is zeroed here. (Shutdown is the one exception:
5032 * nothing reads the count afterwards.) */
5033static void
5034gc_tdata_unsafe_drain_objspaces(rb_objspace_t **objspaces, size_t n)
5035{
5036 struct tdata_unsafe_free_chunk *chunk =
5037 rbimpl_atomic_ptr_exchange((void **)&global_objspace->tdata_unsafe_free_published, NULL,
5038 RBIMPL_ATOMIC_ACQ_REL);
5039 while (chunk) {
5040 struct tdata_unsafe_free_chunk *next = chunk->next;
5041 gc_tdata_unsafe_drain_chunk(chunk);
5042 chunk = next;
5043 }
5044
5045 for (size_t i = 0; i < n; i++) {
5046 rb_objspace_t *os = objspaces[i];
5047 struct tdata_unsafe_free_chunk *partial = os->tdata_unsafe_free_chunk;
5048 if (partial) {
5049 os->tdata_unsafe_free_chunk = NULL;
5050 gc_tdata_unsafe_drain_chunk(partial);
5051 }
5052 }
5053
5054 rbimpl_atomic_size_exchange(&global_objspace->tdata_deferred_free_count, 0,
5055 RBIMPL_ATOMIC_RELAXED);
5056}
5057
5058/* Stop the world and run the dfree function for all deferred T_DATAs. */
5059static void
5060gc_tdata_unsafe_drain(void)
5061{
5062 unsigned int lev = RB_GC_VM_LOCK();
5063
5064 if (tdata_deferred_free_count_load() == 0) {
5065 RB_GC_VM_UNLOCK(lev);
5066 return;
5067 }
5068
5069 rb_gc_vm_barrier();
5070
5071 gc_global_snapshot_objspaces();
5072
5073 /* Set during_gc=TRUE and init vm_context for the CURRENT objspace only.
5074 * The no-alloc guard checks only the allocating (=current) objspace's during_gc,
5075 * and rb_gc_get_ec() reads only the current objspace's vm_context.ec. */
5076 rb_objspace_t *objspace = rb_gc_get_objspace();
5077 unsigned int saved_during_gc = gc_during_gc_get(objspace);
5078 dont_gc_on();
5079 rb_gc_initialize_vm_context(&objspace->vm_context);
5080 gc_during_gc_set(objspace, TRUE);
5081
5082 gc_tdata_unsafe_drain_objspaces(global_objspace->global_gc.objspaces,
5083 global_objspace->global_gc.n_objspaces);
5084
5085 gc_during_gc_set(objspace, saved_during_gc);
5086 dont_gc_off();
5087
5088 RB_GC_VM_UNLOCK(lev);
5089}
5090
5091static void
5092gc_tdata_deferred_free_job(void *unused)
5093{
5094 (void)unused;
5095
5096 size_t count = tdata_deferred_free_count_load();
5097 if (count == 0) return;
5098 if (count < TDATA_DEFERRED_FREE_THRESHOLD && !rb_gc_single_objspace_p()) return;
5099
5100 gc_tdata_unsafe_drain();
5101}
5102
5103static inline void
5104gc_sweep_register_free_slot(rb_objspace_t *objspace, struct heap_page *page, struct gc_sweep_context *ctx, uintptr_t p, short slot_size)
5105{
5106 rb_asan_unpoison_object(p, false);
5107 ((struct RBasic *)p)->flags = 0;
5108
5109 /* Keep a freed slot from carrying its old shareable and shref bits into the next
5110 * object born there; the actual clear happens per bitmap word at the end of
5111 * gc_sweep_page rather than per slot. */
5112
5113 struct free_region *existing_region = ctx->free_region;
5114 if (existing_region) rb_asan_unpoison_object((VALUE)existing_region, false);
5115
5116 if (RB_LIKELY(existing_region && p == existing_region->end)) {
5117 existing_region->end = p + slot_size;
5118 }
5119 else {
5120 struct free_region *free_region = (struct free_region *)p;
5121 free_region->end = p + slot_size;
5122 free_region->next = existing_region;
5123
5124 ctx->free_region = free_region;
5125 }
5126
5127 if (existing_region) rb_asan_poison_object((VALUE)existing_region);
5128 rb_asan_poison_object(p);
5129}
5130
5131static inline void
5132gc_sweep_plane(rb_objspace_t *objspace, rb_heap_t *heap, uintptr_t p, bits_t bitset, struct gc_sweep_context *ctx)
5133{
5134 struct heap_page *sweep_page = ctx->page;
5135 short slot_size = sweep_page->slot_size;
5136
5137 do {
5138 VALUE vp = (VALUE)p;
5139 GC_ASSERT(vp % sizeof(VALUE) == 0);
5140
5141 rb_asan_unpoison_object(vp, false);
5142 if (bitset & 1) {
5143 switch (BUILTIN_TYPE(vp)) {
5144 case T_MOVED:
5145 if (objspace->flags.during_compacting) {
5146 /* The sweep cursor shouldn't have made it to any
5147 * T_MOVED slots while the compact flag is enabled.
5148 * The sweep cursor and compact cursor move in
5149 * opposite directions, and when they meet references will
5150 * get updated and "during_compacting" should get disabled */
5151 rb_bug("T_MOVED shouldn't be seen until compaction is finished");
5152 }
5153 gc_report(3, objspace, "page_sweep: %s is freed\n", rb_obj_info(vp));
5154 ctx->empty_slots++;
5155 gc_sweep_register_free_slot(objspace, sweep_page, ctx, p, slot_size);
5156 break;
5157 case T_ZOMBIE:
5158 /* already counted */
5159 break;
5160 case T_NONE:
5161 ctx->empty_slots++; /* already freed */
5162 gc_sweep_register_free_slot(objspace, sweep_page, ctx, p, slot_size);
5163 break;
5164
5165 default:
5166#if RGENGC_CHECK_MODE
5167 /* A local GC must never free a pinned slot; a global GC may (its exact
5168 * mark collects dead shareable objects). Reading the bits here is
5169 * CHECK-only and still valid: the bulk clear runs after the free loop. */
5170 if (ctx->check_pinned_free &&
5171 (MARKED_IN_BITMAP(GET_HEAP_SHAREABLE_BITS(vp), vp) ||
5172 MARKED_IN_BITMAP(GET_HEAP_SHREF_BITS(vp), vp))) {
5173 rb_bug("page_sweep: freeing pinned slot %s (shareable=%d shref=%d single_now=%d)",
5174 rb_obj_info(vp),
5175 (int)!!MARKED_IN_BITMAP(GET_HEAP_SHAREABLE_BITS(vp), vp),
5176 (int)!!MARKED_IN_BITMAP(GET_HEAP_SHREF_BITS(vp), vp),
5177 (int)rb_gc_single_objspace_p());
5178 }
5179#endif
5180#if RGENGC_CHECK_MODE
5181 if (!is_full_marking(objspace)) {
5182 if (RVALUE_OLD_P(objspace, vp)) rb_bug("page_sweep: %p - old while minor GC.", (void *)p);
5183 if (RVALUE_REMEMBERED(objspace, vp)) rb_bug("page_sweep: %p - remembered.", (void *)p);
5184 }
5185#endif
5186
5187#if RGENGC_CHECK_MODE
5188#define CHECK(x) if (x(objspace, vp) != FALSE) rb_bug("obj_free: " #x "(%s) != FALSE", rb_obj_info(vp))
5189 CHECK(RVALUE_WB_UNPROTECTED);
5190 CHECK(RVALUE_MARKED);
5191 CHECK(RVALUE_MARKING);
5192 CHECK(RVALUE_UNCOLLECTIBLE);
5193#undef CHECK
5194#endif
5195
5196 if (!rb_gc_obj_needs_cleanup_p(vp)) {
5197 (void)VALGRIND_MAKE_MEM_UNDEFINED((void*)p, slot_size);
5198 gc_sweep_register_free_slot(objspace, sweep_page, ctx, p, slot_size);
5199 gc_report(3, objspace, "page_sweep: %s (fast path) is freed\n", rb_obj_info(vp));
5200 ctx->freed_slots++;
5201 }
5202 else {
5203 gc_report(2, objspace, "page_sweep: free %p\n", (void *)p);
5204
5205 if (RB_UNLIKELY(ctx->defer_thread_unsafe_local_sweep && gc_obj_defer_local_free_p(objspace, vp))) {
5206 /* Defer the dfree instead of running it here: it needs the world
5207 * stopped, which a parallel local sweep cannot give it. The slot is reusable
5208 * right away unless we had to create a zombie. */
5209 if (gc_defer_thread_unsafe_free(objspace, vp,
5210 &ctx->trigger_thread_unsafe_sweep_postponed_job)) {
5211 (void)VALGRIND_MAKE_MEM_UNDEFINED((void*)p, slot_size);
5212 gc_sweep_register_free_slot(objspace, sweep_page, ctx, p, slot_size);
5213 ctx->freed_slots++;
5214 }
5215 else {
5216 ctx->final_slots++;
5217 }
5218 break;
5219 }
5220 rb_gc_obj_free_vm_weak_references(vp);
5221 if (gc_obj_free(objspace, vp)) {
5222 (void)VALGRIND_MAKE_MEM_UNDEFINED((void*)p, slot_size);
5223 gc_sweep_register_free_slot(objspace, sweep_page, ctx, p, slot_size);
5224 gc_report(3, objspace, "page_sweep: %s is freed\n", rb_obj_info(vp));
5225 ctx->freed_slots++;
5226 }
5227 else {
5228 ctx->final_slots++;
5229 }
5230 }
5231 break;
5232 }
5233 }
5234 p += slot_size;
5235 bitset >>= 1;
5236 } while (bitset);
5237}
5238
5239static inline void
5240gc_sweep_page(rb_objspace_t *objspace, rb_heap_t *heap, struct gc_sweep_context *ctx)
5241{
5242 struct heap_page *sweep_page = ctx->page;
5243 GC_ASSERT(sweep_page->heap == heap);
5244
5245 uintptr_t p;
5246 bits_t *bits, bitset;
5247
5248 gc_report(2, objspace, "page_sweep: start.\n");
5249
5250#if RGENGC_CHECK_MODE
5251 if (!objspace->flags.immediate_sweep) {
5252 GC_ASSERT(sweep_page->flags.before_sweep == TRUE);
5253 }
5254#endif
5255 sweep_page->flags.before_sweep = FALSE;
5256 sweep_page->free_slots = 0;
5257
5258 asan_unlock_freelist(sweep_page);
5259 sweep_page->free_region = NULL;
5260 asan_lock_freelist(sweep_page);
5261 ctx->free_region = NULL;
5262
5263 p = (uintptr_t)sweep_page->start;
5264 bits = sweep_page->mark_bits;
5265 short slot_size = sweep_page->slot_size;
5266 int total_slots = sweep_page->total_slots;
5267 int bitmap_plane_count = CEILDIV(total_slots, BITS_BITLENGTH);
5268
5269 int out_of_range_bits = total_slots % BITS_BITLENGTH;
5270 if (out_of_range_bits != 0) {
5271 bits[bitmap_plane_count - 1] |= ~(((bits_t)1 << out_of_range_bits) - 1);
5272 }
5273
5274 // Clear wb_unprotected and age bits for all unmarked slots
5275 {
5276 bits_t *wb_unprotected_bits = sweep_page->wb_unprotected_bits;
5277 bits_t *age_bits = sweep_page->age_bits;
5278 for (int i = 0; i < bitmap_plane_count; i++) {
5279 bits_t unmarked = ~bits[i];
5280 wb_unprotected_bits[i] &= ~unmarked;
5281 age_bits[i * 2] &= ~unmarked;
5282 age_bits[i * 2 + 1] &= ~unmarked;
5283 }
5284 }
5285
5286 for (int i = 0; i < bitmap_plane_count; i++) {
5287 bitset = ~bits[i];
5288 if (bitset) {
5289 gc_sweep_plane(objspace, heap, p, bitset, ctx);
5290 }
5291 p += BITS_BITLENGTH * slot_size;
5292 }
5293
5294 /* Bulk-clear the freed slots' shareable and shref bits before the freelist is
5295 * published, so a reused slot is clean. Freed slots are exactly the unmarked ones,
5296 * so `bits &= mark_bits` keeps live shareable objects (which must stay pinned) and
5297 * drops the rest. Pages with neither bit are skipped. */
5298 if (sweep_page->flags.has_shareable_objects || sweep_page->flags.has_shref_objects) {
5299 bits_t *shareable_bits = sweep_page->shareable_bits;
5300 bits_t *shref_bits = sweep_page->shref_bits;
5301 bits_t sh = 0, sr = 0;
5302 for (int i = 0; i < bitmap_plane_count; i++) {
5303 shareable_bits[i] &= bits[i];
5304 shref_bits[i] &= bits[i];
5305 sh |= shareable_bits[i];
5306 sr |= shref_bits[i];
5307 }
5308 if (!sh) sweep_page->flags.has_shareable_objects = FALSE;
5309 if (!sr) sweep_page->flags.has_shref_objects = FALSE;
5310 }
5311
5312 asan_unlock_freelist(sweep_page);
5313 sweep_page->free_region = ctx->free_region;
5314 asan_lock_freelist(sweep_page);
5315
5316 if (!heap->compact_cursor) {
5317 gc_setup_mark_bits(sweep_page);
5318 }
5319
5320#if GC_PROFILE_MORE_DETAIL
5321 if (gc_prof_enabled(objspace)) {
5322 gc_profile_record *record = gc_prof_record(objspace);
5323 record->removing_objects += ctx->final_slots + ctx->freed_slots;
5324 record->empty_objects += ctx->empty_slots;
5325 }
5326#endif
5327 if (0) fprintf(stderr, "gc_sweep_page(%"PRIdSIZE"): total_slots: %d, freed_slots: %d, empty_slots: %d, final_slots: %d\n",
5328 rb_gc_count(),
5329 sweep_page->total_slots,
5330 ctx->freed_slots, ctx->empty_slots, ctx->final_slots);
5331
5332 sweep_page->free_slots += ctx->freed_slots + ctx->empty_slots;
5333 sweep_page->heap->total_freed_objects += ctx->freed_slots;
5334
5335 if (heap_pages_deferred_final && !finalizing) {
5336 gc_finalize_deferred_register(objspace);
5337 }
5338
5339#if RGENGC_CHECK_MODE
5340 int region_slots = 0;
5341 asan_unlock_freelist(sweep_page);
5342 struct free_region *region = sweep_page->free_region;
5343 while (region) {
5344 rb_asan_unpoison_object((VALUE)region, false);
5345 GC_ASSERT(RB_TYPE_P((VALUE)region, T_NONE));
5346 uintptr_t region_start = (uintptr_t)region;
5347 uintptr_t region_end = region->end;
5348 struct free_region *next = region->next;
5349 rb_asan_poison_object((VALUE)region);
5350
5351 GC_ASSERT(region_end > region_start);
5352 GC_ASSERT((region_end - region_start) % slot_size == 0);
5353 region_slots += (int)((region_end - region_start) / slot_size);
5354
5355 region = next;
5356 }
5357 asan_lock_freelist(sweep_page);
5358 if (region_slots != sweep_page->free_slots) {
5359 rb_bug("inconsistent free region slots: expected %d but was %d", sweep_page->free_slots, region_slots);
5360 }
5361#endif
5362
5363 gc_report(2, objspace, "page_sweep: end.\n");
5364}
5365
5366static const char *
5367gc_mode_name(enum gc_mode mode)
5368{
5369 switch (mode) {
5370 case gc_mode_none: return "none";
5371 case gc_mode_marking: return "marking";
5372 case gc_mode_sweeping: return "sweeping";
5373 case gc_mode_compacting: return "compacting";
5374 default: rb_bug("gc_mode_name: unknown mode: %d", (int)mode);
5375 }
5376}
5377
5378static void
5379gc_mode_transition(rb_objspace_t *objspace, enum gc_mode mode)
5380{
5381#if RGENGC_CHECK_MODE
5382 enum gc_mode prev_mode = gc_mode(objspace);
5383 switch (prev_mode) {
5384 case gc_mode_none:
5385 /* A global GC marks every objspace as one heap (mark_roots on the driver), so an
5386 * individual objspace's mode stays `none` during that mark; the sweep inside the
5387 * barrier then makes the legitimate none -> sweeping transition. */
5388 GC_ASSERT(mode == gc_mode_marking ||
5389 (objspace->flags.during_global_gc && mode == gc_mode_sweeping));
5390 break;
5391 case gc_mode_marking: GC_ASSERT(mode == gc_mode_sweeping); break;
5392 case gc_mode_sweeping: GC_ASSERT(mode == gc_mode_none || mode == gc_mode_compacting); break;
5393 case gc_mode_compacting: GC_ASSERT(mode == gc_mode_none); break;
5394 }
5395#endif
5396 if (0) fprintf(stderr, "gc_mode_transition: %s->%s\n", gc_mode_name(gc_mode(objspace)), gc_mode_name(mode));
5397 gc_mode_set(objspace, mode);
5398}
5399
5400static void
5401heap_page_flush_alloc_regions(struct heap_page *page, rb_heap_t *heap)
5402{
5403 struct free_region *chain = heap->newobj.alloc_next_region;
5404
5405 if (heap->newobj.alloc_cursor < heap->newobj.alloc_cursor_end) {
5406 VALUE start = (VALUE)heap->newobj.alloc_cursor;
5407 rb_asan_unpoison_object(start, false);
5408 struct free_region *remnant = (struct free_region *)start;
5409 remnant->flags = 0;
5410 remnant->end = heap->newobj.alloc_cursor_end;
5411 remnant->next = chain;
5412 rb_asan_poison_object(start);
5413 chain = remnant;
5414 }
5415
5416 if (chain) {
5417 asan_unlock_freelist(page);
5418 if (page->free_region) {
5419 struct free_region *p = page->free_region;
5420 rb_asan_unpoison_object((VALUE)p, false);
5421 while (p->next) {
5422 struct free_region *prev = p;
5423 p = p->next;
5424 rb_asan_poison_object((VALUE)prev);
5425 rb_asan_unpoison_object((VALUE)p, false);
5426 }
5427 p->next = chain;
5428 rb_asan_poison_object((VALUE)p);
5429 }
5430 else {
5431 page->free_region = chain;
5432 }
5433 asan_lock_freelist(page);
5434 }
5435}
5436
5437static void
5438gc_sweep_start_heap(rb_objspace_t *objspace, rb_heap_t *heap)
5439{
5440 heap->sweeping_page = ccan_list_top(&heap->pages, struct heap_page, page_node);
5441 if (heap->sweeping_page) {
5442 objspace->sweeping_heap_count++;
5443 }
5444 heap->free_pages = NULL;
5445 heap->pooled_pages = NULL;
5446 if (!objspace->flags.immediate_sweep) {
5447 struct heap_page *page = NULL;
5448
5449 ccan_list_for_each(&heap->pages, page, page_node) {
5450 page->flags.before_sweep = TRUE;
5451 }
5452 }
5453}
5454
5455#if GC_CAN_COMPILE_COMPACTION
5456static void gc_sort_heap_by_compare_func(rb_objspace_t *objspace, gc_compact_compare_func compare_func);
5457static int compare_pinned_slots(const void *left, const void *right, void *d);
5458#endif
5459
5460/* Return the current allocation page and freelist to their pages, so the sweeper sees a
5461 * consistent heap. */
5462static void
5463heap_alloc_state_clear(rb_objspace_t *objspace)
5464{
5465 objspace->incremental_mark_step_allocated_slots = 0;
5466
5467 for (size_t heap_idx = 0; heap_idx < HEAP_COUNT; heap_idx++) {
5468 rb_heap_t *heap = &heaps[heap_idx];
5469
5470 struct heap_page *page = heap->newobj.alloc_using_page;
5471 RUBY_DEBUG_LOG("heap alloc_using_page:%p cursor:%p", (void *)page, (void *)heap->newobj.alloc_cursor);
5472
5473 if (page) {
5474 heap_page_flush_alloc_regions(page, heap);
5475 }
5476
5477 heap->newobj.alloc_using_page = NULL;
5478 heap->newobj.alloc_cursor = 0;
5479 heap->newobj.alloc_cursor_end = 0;
5480 heap->newobj.alloc_next_region = NULL;
5481 }
5482}
5483
5484static void
5485gc_sweep_freeobj_hooks_page(rb_objspace_t *objspace, struct heap_page *page)
5486{
5487 bits_t *bits = page->mark_bits;
5488 uintptr_t p = (uintptr_t)page->start;
5489 short slot_size = page->slot_size;
5490 int total_slots = page->total_slots;
5491 int bitmap_plane_count = CEILDIV(total_slots, BITS_BITLENGTH);
5492
5493 int out_of_range_bits = total_slots % BITS_BITLENGTH;
5494 bits_t last_plane_mask = (out_of_range_bits != 0)
5495 ? ~(((bits_t)1 << out_of_range_bits) - 1)
5496 : 0;
5497
5498 for (int j = 0; j < bitmap_plane_count; j++) {
5499 bits_t bitset = ~bits[j];
5500 if (j == bitmap_plane_count - 1) {
5501 bitset &= ~last_plane_mask;
5502 }
5503
5504 uintptr_t pp = p;
5505 while (bitset) {
5506 if (bitset & 1) {
5507 VALUE vp = (VALUE)pp;
5508 asan_unpoisoning_object(vp) {
5509 switch (BUILTIN_TYPE(vp)) {
5510 case T_NONE:
5511 case T_ZOMBIE:
5512 case T_MOVED:
5513 break;
5514 default:
5515 rb_gc_event_hook(vp, RUBY_INTERNAL_EVENT_FREEOBJ);
5516 break;
5517 }
5518 }
5519 }
5520 pp += slot_size;
5521 bitset >>= 1;
5522 }
5523 p += BITS_BITLENGTH * slot_size;
5524 }
5525}
5526
5527static void
5528gc_sweep_freeobj_hooks(rb_objspace_t *objspace)
5529{
5530 for (int i = 0; i < HEAP_COUNT; i++) {
5531 rb_heap_t *heap = &heaps[i];
5532 struct heap_page *page = NULL;
5533
5534 ccan_list_for_each(&heap->pages, page, page_node) {
5535 gc_sweep_freeobj_hooks_page(objspace, page);
5536 }
5537 }
5538}
5539
5540static void
5541gc_sweep_start(rb_objspace_t *objspace)
5542{
5543 gc_mode_transition(objspace, gc_mode_sweeping);
5544 objspace->rincgc.pooled_slots = 0;
5545
5546 if (RB_UNLIKELY(objspace->hook_events & RUBY_INTERNAL_EVENT_FREEOBJ)) {
5547 /* FREEOBJ is never enabled outside the main objspace
5548 * (rb_objspace_set_event_hook), so this hook, which runs user callbacks,
5549 * cannot fire during a non-main Ractor's lock-free local sweep. */
5550 GC_ASSERT(objspace == global_objspace->main_objspace);
5551 gc_sweep_freeobj_hooks(objspace);
5552 }
5553
5554#if GC_CAN_COMPILE_COMPACTION
5555 if (objspace->flags.during_compacting) {
5556 gc_sort_heap_by_compare_func(
5557 objspace,
5558 objspace->rcompactor.compare_func ? objspace->rcompactor.compare_func : compare_pinned_slots
5559 );
5560 }
5561#endif
5562
5563 for (int i = 0; i < HEAP_COUNT; i++) {
5564 rb_heap_t *heap = &heaps[i];
5565 gc_sweep_start_heap(objspace, heap);
5566
5567 /* We should call gc_sweep_finish_heap for size pools with no pages. */
5568 if (heap->sweeping_page == NULL) {
5569 GC_ASSERT(heap->total_pages == 0);
5570 GC_ASSERT(heap->total_slots == 0);
5571 gc_sweep_finish_heap(objspace, heap);
5572 }
5573 }
5574
5575 heap_alloc_state_clear(objspace);
5576}
5577
5578static void
5579gc_sweep_finish_heap(rb_objspace_t *objspace, rb_heap_t *heap)
5580{
5581 size_t total_slots = heap->total_slots;
5582 size_t swept_slots = heap->freed_slots + heap->empty_slots;
5583
5584 size_t init_slots = objspace_heap_init_bytes(objspace) / heap->slot_size;
5585 size_t min_free_slots = (size_t)(MAX(total_slots, init_slots) * gc_params.heap_free_slots_min_ratio);
5586
5587 if (swept_slots < min_free_slots &&
5588 /* The heap is a growth heap if it freed more slots than had empty slots. */
5589 ((heap->empty_slots == 0 && total_slots > 0) || heap->freed_slots > heap->empty_slots)) {
5590 /* If we don't have enough slots and we have pages on the tomb heap, move
5591 * pages from the tomb heap to the eden heap. This may prevent page
5592 * creation thrashing (frequently allocating and deallocting pages) and
5593 * GC thrashing (running GC more frequently than required). */
5594 struct heap_page *resurrected_page;
5595 while (swept_slots < min_free_slots &&
5596 (resurrected_page = heap_page_resurrect(objspace))) {
5597 heap_add_page(objspace, heap, resurrected_page);
5598 heap_add_freepage(heap, resurrected_page);
5599
5600 swept_slots += resurrected_page->free_slots;
5601 }
5602
5603 if (swept_slots < min_free_slots) {
5604 /* Grow this heap if we are in a major GC or if we haven't run at least
5605 * RVALUE_OLD_AGE minor GC since the last major GC. */
5606 if (is_full_marking(objspace) ||
5607 objspace->profile.count - objspace->rgengc.last_major_gc < RVALUE_OLD_AGE) {
5608 if (objspace->heap_pages.allocatable_bytes < min_free_slots * heap->slot_size) {
5609 heap_allocatable_bytes_expand(objspace, heap, swept_slots, heap->total_slots, heap->slot_size);
5610 }
5611 }
5612 else if (swept_slots < min_free_slots * 7 / 8 &&
5613 objspace->heap_pages.allocatable_bytes < (min_free_slots * 7 / 8 - swept_slots) * heap->slot_size) {
5614 gc_needs_major_flags |= GPR_FLAG_MAJOR_BY_NOFREE;
5615 heap->force_major_gc_count++;
5616 }
5617 }
5618 }
5619}
5620
5621static void
5622gc_sweep_finish(rb_objspace_t *objspace)
5623{
5624 gc_report(1, objspace, "gc_sweep_finish\n");
5625
5626 gc_prof_set_heap_info(objspace);
5627 heap_pages_free_unused_pages(objspace);
5628 if (rb_gc_single_objspace_p() && is_full_marking(objspace)) {
5629 /* gc_marks_finish retains ~2/3 of empty pages in objspace->empty_pages for reuse,
5630 * only the excess reaches the pool. */
5631 page_pool_reclaim(global_objspace);
5632 }
5633
5634 for (int i = 0; i < HEAP_COUNT; i++) {
5635 rb_heap_t *heap = &heaps[i];
5636
5637 heap->freed_slots = 0;
5638 heap->empty_slots = 0;
5639
5640 if (!will_be_incremental_marking(objspace)) {
5641 struct heap_page *end_page = heap->free_pages;
5642 if (end_page) {
5643 while (end_page->free_next) end_page = end_page->free_next;
5644 end_page->free_next = heap->pooled_pages;
5645 }
5646 else {
5647 heap->free_pages = heap->pooled_pages;
5648 }
5649 heap->pooled_pages = NULL;
5650 objspace->rincgc.pooled_slots = 0;
5651 }
5652 }
5653
5654 /* Not before: while sweeping is in progress its frees must keep reducing
5655 * malloc_increase (objspace_malloc_increase_body sweeps and retries on it). */
5656 gc_malloc_counters_snapshot_free_at_last_gc(objspace, &objspace->malloc_counters.counters);
5657#if RGENGC_ESTIMATE_OLDMALLOC
5658 gc_malloc_counters_snapshot_free_at_last_gc(objspace, &objspace->malloc_counters.oldcounters);
5659#endif
5660
5661 /* Leftovers from an earlier multi-Ractor phase: no later sweep can rediscover them
5662 * (their slots are gone), and with one Ractor left the drain's barrier has nothing
5663 * to wait for. */
5664 if (tdata_deferred_free_count_load() > 0 && rb_gc_single_objspace_p()) {
5665 gc_tdata_deferred_free_trigger(objspace);
5666 }
5667
5669 gc_mode_transition(objspace, gc_mode_none);
5670}
5671
5672static int
5673gc_sweep_step(rb_objspace_t *objspace, rb_heap_t *heap)
5674{
5675 struct heap_page *sweep_page = heap->sweeping_page;
5676 int swept_slots = 0;
5677 int pooled_slots = 0;
5678 int sweep_budget = GC_INCREMENTAL_SWEEP_BYTES / heap->slot_size;
5679 int pool_budget = GC_INCREMENTAL_SWEEP_POOL_BYTES / heap->slot_size;
5680
5681 if (sweep_page == NULL) return FALSE;
5682
5683#if GC_ENABLE_LAZY_SWEEP
5684 gc_prof_sweep_timer_start(objspace);
5685#endif
5686
5687 /* Per-slot pinned-free assert (gc_sweep_context): check only when this cycle's mark
5688 * ran the pinned walk. The current world state would misfire: a single-world
5689 * cycle leaves dead shareable objects unmarked and its sweep can straddle the switch
5690 * to multi-objspace. A global GC's exact mark does not pin, so it is excluded. */
5691 const bool check_pinned_free = objspace->last_cycle_pinned;
5692
5693 const bool defer_thread_unsafe_local_sweep =
5694 !rb_gc_single_objspace_p() && !objspace->flags.during_global_gc;
5695 bool trigger_thread_unsafe_sweep_postponed_job = false;
5696
5697 do {
5698 RUBY_DEBUG_LOG("sweep_page:%p", (void *)sweep_page);
5699
5700 struct gc_sweep_context ctx = {
5701 .page = sweep_page,
5702 .final_slots = 0,
5703 .freed_slots = 0,
5704 .empty_slots = 0,
5705 .check_pinned_free = check_pinned_free,
5706 .defer_thread_unsafe_local_sweep = defer_thread_unsafe_local_sweep,
5707 .trigger_thread_unsafe_sweep_postponed_job = trigger_thread_unsafe_sweep_postponed_job,
5708 };
5709 gc_sweep_page(objspace, heap, &ctx);
5710 int free_slots = ctx.freed_slots + ctx.empty_slots;
5711 trigger_thread_unsafe_sweep_postponed_job = ctx.trigger_thread_unsafe_sweep_postponed_job;
5712
5713 RUBY_DTRACE_GC_HOOK(SWEEP_PAGE, ctx.page->slot_size, ctx.final_slots, ctx.freed_slots, ctx.empty_slots);
5714
5715 heap->sweeping_page = ccan_list_next(&heap->pages, sweep_page, page_node);
5716
5717 if (free_slots == sweep_page->total_slots && heap->total_pages > 1) {
5718 /* There are no living objects, so move this page to the global empty pages.
5719 * The last one stays: nothing grows a heap that has no pages at all. */
5720 heap_unlink_page(objspace, heap, sweep_page);
5721
5722 sweep_page->start = 0;
5723 sweep_page->total_slots = 0;
5724 sweep_page->slot_size = 0;
5725 sweep_page->heap = NULL;
5726 sweep_page->free_slots = 0;
5727
5728 asan_unlock_freelist(sweep_page);
5729 sweep_page->free_region = NULL;
5730 asan_lock_freelist(sweep_page);
5731
5732 asan_poison_memory_region(sweep_page->body, HEAP_PAGE_SIZE);
5733
5734 objspace->empty_pages_count++;
5735 sweep_page->free_next = objspace->empty_pages;
5736 objspace->empty_pages = sweep_page;
5737 }
5738 else if (free_slots > 0) {
5739 heap->freed_slots += ctx.freed_slots;
5740 heap->empty_slots += ctx.empty_slots;
5741
5742 if (pooled_slots < pool_budget) {
5743 heap_add_poolpage(objspace, heap, sweep_page);
5744 pooled_slots += free_slots;
5745 }
5746 else {
5747 heap_add_freepage(heap, sweep_page);
5748 swept_slots += free_slots;
5749 if (swept_slots > sweep_budget) {
5750 break;
5751 }
5752 }
5753 }
5754 else {
5755 sweep_page->free_next = NULL;
5756 }
5757 } while ((sweep_page = heap->sweeping_page));
5758
5759 if (trigger_thread_unsafe_sweep_postponed_job) {
5760 gc_report(2, objspace, "thread-unsafe sweep postponed job triggered\n");
5761 gc_tdata_deferred_free_trigger(objspace);
5762 }
5763
5764 if (!heap->sweeping_page) {
5765 objspace->sweeping_heap_count--;
5766 GC_ASSERT(objspace->sweeping_heap_count >= 0);
5767 gc_sweep_finish_heap(objspace, heap);
5768
5769 if (!has_sweeping_pages(objspace)) {
5770 gc_sweep_finish(objspace);
5771 }
5772 }
5773
5774#if GC_ENABLE_LAZY_SWEEP
5775 gc_prof_sweep_timer_stop(objspace);
5776#endif
5777
5778 return heap->free_pages != NULL;
5779}
5780
5781static void
5782gc_sweep_rest(rb_objspace_t *objspace)
5783{
5784 for (int i = 0; i < HEAP_COUNT; i++) {
5785 rb_heap_t *heap = &heaps[i];
5786
5787 while (heap->sweeping_page) {
5788 gc_sweep_step(objspace, heap);
5789 }
5790 }
5791
5792 /* An objspace with no live pages never runs gc_sweep_step and so never reaches
5793 * gc_sweep_finish, leaving mode at sweeping or compacting until the next cycle's
5794 * gc_sweep_start asserts. If every heap is swept out, settle it to none here. */
5795 if (gc_mode(objspace) != gc_mode_none && !has_sweeping_pages(objspace)) {
5796 gc_sweep_finish(objspace);
5797 }
5798}
5799
5800static void
5801gc_sweep_continue(rb_objspace_t *objspace, rb_heap_t *sweep_heap)
5802{
5803 GC_ASSERT(dont_gc_val() == FALSE || objspace->profile.latest_gc_info & GPR_FLAG_METHOD);
5804 if (!GC_ENABLE_LAZY_SWEEP) return;
5805
5806 gc_sweeping_enter(objspace);
5807
5808 for (int i = 0; i < HEAP_COUNT; i++) {
5809 rb_heap_t *heap = &heaps[i];
5810 if (gc_sweep_step(objspace, heap)) {
5811 GC_ASSERT(heap->free_pages != NULL);
5812 }
5813 else if (heap == sweep_heap) {
5814 if (objspace->empty_pages_count > 0 || objspace->heap_pages.allocatable_bytes > 0) {
5815 /* [Bug #21548]
5816 *
5817 * If this heap is the heap we want to sweep, but we weren't able
5818 * to free any slots, but we also either have empty pages or could
5819 * allocate new pages, then we want to preemptively claim a page
5820 * because it's possible that sweeping another heap will call
5821 * gc_sweep_finish_heap, which may use up all of the
5822 * empty/allocatable pages. If other heaps are not finished sweeping
5823 * then we do not finish this GC and we will end up triggering a new
5824 * GC cycle during this GC phase. */
5825 heap_page_allocate_and_initialize(objspace, heap);
5826
5827 GC_ASSERT(heap->free_pages != NULL);
5828 }
5829 else {
5830 /* Not allowed to create a new page so finish sweeping. */
5831 gc_sweep_rest(objspace);
5832 GC_ASSERT(gc_mode(objspace) == gc_mode_none);
5833 break;
5834 }
5835 }
5836 }
5837
5838 gc_sweeping_exit(objspace);
5839}
5840
5841static void
5842gc_sweep_step_for_malloc(rb_objspace_t *objspace)
5843{
5844 GC_ASSERT(is_lazy_sweeping(objspace));
5845
5846 unsigned int lock_lev;
5847 gc_enter(objspace, gc_enter_event_continue, &lock_lev);
5848
5849 gc_sweeping_enter(objspace);
5850
5851 for (int i = 0; i < HEAP_COUNT; i++) {
5852 rb_heap_t *heap = &heaps[i];
5853 gc_sweep_step(objspace, heap);
5854 }
5855
5856 gc_sweeping_exit(objspace);
5857
5858 gc_exit(objspace, gc_enter_event_continue, &lock_lev);
5859}
5860
5861static bool gc_global_pointer_to_heap_p(const void *ptr);
5862
5863VALUE
5864rb_gc_impl_location(void *objspace_ptr, VALUE value)
5865{
5866 rb_objspace_t *objspace = objspace_ptr;
5867 VALUE destination;
5868
5869 /* A local (single-objspace) compaction never moves another objspace's objects, so
5870 * leave foreign references alone. A compacting global GC moves objects everywhere
5871 * under the barrier, so there every objspace's heap is searched for forwarding. */
5872 if (RB_UNLIKELY(objspace->flags.during_global_gc)
5873 ? !gc_global_pointer_to_heap_p((void *)value)
5874 : !is_pointer_to_heap(objspace_ptr, (void *)value)) {
5875 return value;
5876 }
5877
5878 asan_unpoisoning_object(value) {
5879 if (BUILTIN_TYPE(value) == T_MOVED) {
5880 destination = (VALUE)RMOVED(value)->destination;
5881 GC_ASSERT(BUILTIN_TYPE(destination) != T_NONE);
5882 }
5883 else {
5884 destination = value;
5885 }
5886 }
5887
5888 return destination;
5889}
5890
5891#if GC_CAN_COMPILE_COMPACTION
5892static void
5893invalidate_moved_plane(rb_objspace_t *objspace, struct heap_page *page, uintptr_t p, bits_t bitset)
5894{
5895 if (bitset) {
5896 do {
5897 if (bitset & 1) {
5898 VALUE forwarding_object = (VALUE)p;
5899 VALUE object;
5900
5901 if (BUILTIN_TYPE(forwarding_object) == T_MOVED) {
5902 GC_ASSERT(RVALUE_PINNED(objspace, forwarding_object));
5903 GC_ASSERT(!RVALUE_MARKED(objspace, forwarding_object));
5904
5905 CLEAR_IN_BITMAP(GET_HEAP_PINNED_BITS(forwarding_object), forwarding_object);
5906
5907 object = rb_gc_impl_location(objspace, forwarding_object);
5908 gc_move(objspace, object, forwarding_object, GET_HEAP_PAGE(object), page);
5909 /* forwarding_object is now our actual object, and "object"
5910 * is the free slot for the original page */
5911
5912 struct heap_page *orig_page = GET_HEAP_PAGE(object);
5913 orig_page->free_slots++;
5914 RVALUE_AGE_SET_BITMAP(object, 0);
5915 heap_page_add_free_region(objspace, orig_page, object);
5916
5917 GC_ASSERT(RVALUE_MARKED(objspace, forwarding_object));
5918 GC_ASSERT(BUILTIN_TYPE(forwarding_object) != T_MOVED);
5919 GC_ASSERT(BUILTIN_TYPE(forwarding_object) != T_NONE);
5920 }
5921 }
5922 p += page->slot_size;
5923 bitset >>= 1;
5924 } while (bitset);
5925 }
5926}
5927
5928static void
5929invalidate_moved_page(rb_objspace_t *objspace, struct heap_page *page)
5930{
5931 int i;
5932 bits_t *mark_bits, *pin_bits;
5933 bits_t bitset;
5934 short slot_size = page->slot_size;
5935 int total_slots = page->total_slots;
5936 int bitmap_plane_count = CEILDIV(total_slots, BITS_BITLENGTH);
5937
5938 mark_bits = page->mark_bits;
5939 pin_bits = page->pinned_bits;
5940
5941 uintptr_t p = page->start;
5942
5943 for (i=0; i < bitmap_plane_count; i++) {
5944 /* Moved objects are pinned but never marked. We reuse the pin bits
5945 * to indicate there is a moved object in this slot. */
5946 bitset = pin_bits[i] & ~mark_bits[i];
5947 invalidate_moved_plane(objspace, page, p, bitset);
5948 p += BITS_BITLENGTH * slot_size;
5949 }
5950}
5951#endif
5952
5953static void
5954gc_compact_start(rb_objspace_t *objspace)
5955{
5956 struct heap_page *page = NULL;
5957 gc_mode_transition(objspace, gc_mode_compacting);
5958
5959 for (int i = 0; i < HEAP_COUNT; i++) {
5960 rb_heap_t *heap = &heaps[i];
5961 ccan_list_for_each(&heap->pages, page, page_node) {
5962 page->flags.before_sweep = TRUE;
5963 }
5964
5965 heap->compact_cursor = ccan_list_tail(&heap->pages, struct heap_page, page_node);
5966 heap->compact_cursor_index = 0;
5967 }
5968
5969 if (gc_prof_enabled(objspace)) {
5970 gc_profile_record *record = gc_prof_record(objspace);
5971 record->moved_objects = objspace->rcompactor.total_moved;
5972 }
5973
5974 memset(objspace->rcompactor.considered_count_table, 0, T_MASK * sizeof(size_t));
5975 memset(objspace->rcompactor.moved_count_table, 0, T_MASK * sizeof(size_t));
5976 memset(objspace->rcompactor.moved_up_count_table, 0, T_MASK * sizeof(size_t));
5977 memset(objspace->rcompactor.moved_down_count_table, 0, T_MASK * sizeof(size_t));
5978
5979 /* Set up read barrier for pages containing MOVED objects */
5980 /* A compacting global GC installs the read barrier once for every objspace. */
5981 if (!global_objspace->global_gc.compacting) install_handlers();
5982}
5983
5984static void gc_sweep_compact(rb_objspace_t *objspace);
5985
5986static void
5987gc_sweep(rb_objspace_t *objspace)
5988{
5989 gc_sweeping_enter(objspace);
5990
5991 const unsigned int immediate_sweep = objspace->flags.immediate_sweep;
5992
5993 gc_report(1, objspace, "gc_sweep: immediate: %d\n", immediate_sweep);
5994
5995 gc_sweep_start(objspace);
5996 if (objspace->flags.during_compacting) {
5997 rb_hrtime_t compact_start_time = gc_prof_enabled(objspace) ? rb_hrtime_now() : 0;
5998 gc_sweep_compact(objspace);
5999 if (gc_prof_enabled(objspace)) {
6000 rb_hrtime_t compact_wall_time = elapsed_hrtime_from(compact_start_time);
6001 gc_profile_record *record = gc_prof_record(objspace);
6002 record->gc_compact_wall_time = rb_hrtime_add(record->gc_compact_wall_time,
6003 compact_wall_time);
6004 objspace->profile.gc_sweep_excluded_wall_time = rb_hrtime_add(
6005 objspace->profile.gc_sweep_excluded_wall_time,
6006 compact_wall_time);
6007 }
6008 }
6009
6010 if (immediate_sweep) {
6011#if !GC_ENABLE_LAZY_SWEEP
6012 gc_prof_sweep_timer_start(objspace);
6013#endif
6014 gc_sweep_rest(objspace);
6015#if !GC_ENABLE_LAZY_SWEEP
6016 gc_prof_sweep_timer_stop(objspace);
6017#endif
6018 }
6019 else {
6020
6021 /* Sweep every size pool. */
6022 for (int i = 0; i < HEAP_COUNT; i++) {
6023 rb_heap_t *heap = &heaps[i];
6024 gc_sweep_step(objspace, heap);
6025 }
6026 }
6027
6028 gc_sweeping_exit(objspace);
6029}
6030
6031/* Marking - Marking stack */
6032
6033static stack_chunk_t *
6034stack_chunk_alloc(void)
6035{
6036 stack_chunk_t *res;
6037
6038 res = malloc(sizeof(stack_chunk_t));
6039 if (!res)
6040 rb_memerror();
6041
6042 return res;
6043}
6044
6045static inline int
6046is_mark_stack_empty(mark_stack_t *stack)
6047{
6048 return stack->chunk == NULL;
6049}
6050
6051static size_t
6052mark_stack_size(mark_stack_t *stack)
6053{
6054 size_t size = stack->index;
6055 stack_chunk_t *chunk = stack->chunk ? stack->chunk->next : NULL;
6056
6057 while (chunk) {
6058 size += stack->limit;
6059 chunk = chunk->next;
6060 }
6061 return size;
6062}
6063
6064static void
6065add_stack_chunk_cache(mark_stack_t *stack, stack_chunk_t *chunk)
6066{
6067 chunk->next = stack->cache;
6068 stack->cache = chunk;
6069 stack->cache_size++;
6070}
6071
6072static void
6073shrink_stack_chunk_cache(mark_stack_t *stack)
6074{
6075 stack_chunk_t *chunk;
6076
6077 if (stack->unused_cache_size > (stack->cache_size/2)) {
6078 chunk = stack->cache;
6079 stack->cache = stack->cache->next;
6080 stack->cache_size--;
6081 free(chunk);
6082 }
6083 stack->unused_cache_size = stack->cache_size;
6084}
6085
6086static void
6087push_mark_stack_chunk(mark_stack_t *stack)
6088{
6089 stack_chunk_t *next;
6090
6091 GC_ASSERT(stack->index == stack->limit);
6092
6093 if (stack->cache_size > 0) {
6094 next = stack->cache;
6095 stack->cache = stack->cache->next;
6096 stack->cache_size--;
6097 if (stack->unused_cache_size > stack->cache_size)
6098 stack->unused_cache_size = stack->cache_size;
6099 }
6100 else {
6101 next = stack_chunk_alloc();
6102 }
6103 next->next = stack->chunk;
6104 stack->chunk = next;
6105 stack->index = 0;
6106}
6107
6108static void
6109pop_mark_stack_chunk(mark_stack_t *stack)
6110{
6111 stack_chunk_t *prev;
6112
6113 prev = stack->chunk->next;
6114 GC_ASSERT(stack->index == 0);
6115 add_stack_chunk_cache(stack, stack->chunk);
6116 stack->chunk = prev;
6117 stack->index = stack->limit;
6118}
6119
6120static void
6121mark_stack_chunk_list_free(stack_chunk_t *chunk)
6122{
6123 stack_chunk_t *next = NULL;
6124
6125 while (chunk != NULL) {
6126 next = chunk->next;
6127 free(chunk);
6128 chunk = next;
6129 }
6130}
6131
6132static void
6133free_stack_chunks(mark_stack_t *stack)
6134{
6135 mark_stack_chunk_list_free(stack->chunk);
6136}
6137
6138static void
6139mark_stack_free_cache(mark_stack_t *stack)
6140{
6141 mark_stack_chunk_list_free(stack->cache);
6142 stack->cache_size = 0;
6143 stack->unused_cache_size = 0;
6144}
6145
6146static void
6147push_mark_stack(mark_stack_t *stack, VALUE obj)
6148{
6149 switch (BUILTIN_TYPE(obj)) {
6150 case T_OBJECT:
6151 case T_CLASS:
6152 case T_MODULE:
6153 case T_FLOAT:
6154 case T_STRING:
6155 case T_REGEXP:
6156 case T_ARRAY:
6157 case T_HASH:
6158 case T_STRUCT:
6159 case T_BIGNUM:
6160 case T_FILE:
6161 case T_DATA:
6162 case T_MATCH:
6163 case T_COMPLEX:
6164 case T_RATIONAL:
6165 case T_TRUE:
6166 case T_FALSE:
6167 case T_SYMBOL:
6168 case T_IMEMO:
6169 case T_ICLASS:
6170 if (stack->index == stack->limit) {
6171 push_mark_stack_chunk(stack);
6172 }
6173 stack->chunk->data[stack->index++] = obj;
6174 return;
6175
6176 case T_NONE:
6177 case T_NIL:
6178 case T_FIXNUM:
6179 case T_MOVED:
6180 case T_ZOMBIE:
6181 case T_UNDEF:
6182 case T_MASK:
6183 rb_bug("push_mark_stack() called for broken object");
6184 break;
6185
6186 case T_NODE:
6187 rb_bug("push_mark_stack: unexpected T_NODE object");
6188 break;
6189 }
6190
6191 rb_bug("rb_gc_mark(): unknown data type 0x%x(%p) %s",
6192 BUILTIN_TYPE(obj), (void *)obj,
6193 is_pointer_to_heap((rb_objspace_t *)rb_gc_get_objspace(), (void *)obj) ? "corrupted object" : "non object");
6194}
6195
6196static int
6197pop_mark_stack(mark_stack_t *stack, VALUE *data)
6198{
6199 if (is_mark_stack_empty(stack)) {
6200 return FALSE;
6201 }
6202 if (stack->index == 1) {
6203 *data = stack->chunk->data[--stack->index];
6204 pop_mark_stack_chunk(stack);
6205 }
6206 else {
6207 *data = stack->chunk->data[--stack->index];
6208 }
6209 return TRUE;
6210}
6211
6212static void
6213init_mark_stack(mark_stack_t *stack)
6214{
6215 int i;
6216
6217 MEMZERO(stack, mark_stack_t, 1);
6218 stack->index = stack->limit = STACK_CHUNK_SIZE;
6219
6220 for (i=0; i < 4; i++) {
6221 add_stack_chunk_cache(stack, stack_chunk_alloc());
6222 }
6223 stack->unused_cache_size = stack->cache_size;
6224}
6225
6226/* Marking */
6227
6228ALWAYS_INLINE(static int gc_mark_set(rb_objspace_t *objspace, VALUE obj));
6229ALWAYS_INLINE(static void gc_mark_check_t_none(rb_objspace_t *objspace, VALUE obj));
6230ALWAYS_INLINE(static void rgengc_check_relation(rb_objspace_t *objspace, VALUE obj));
6231ALWAYS_INLINE(static void gc_aging(rb_objspace_t *objspace, VALUE obj));
6232ALWAYS_INLINE(static void gc_grey(rb_objspace_t *objspace, VALUE obj));
6233static void
6234rgengc_check_relation(rb_objspace_t *objspace, VALUE obj)
6235{
6236 if (objspace->rgengc.parent_object_old_p) {
6237 if (!RVALUE_OLD_P(objspace, obj)) {
6238 rgengc_remember(objspace, objspace->rgengc.parent_object);
6239 /* It is in the rememberset now, so its remaining children have nothing left
6240 * to ask for: stop testing them. */
6241 objspace->rgengc.parent_object_old_p = false;
6242 }
6243 }
6244}
6245
6246static inline int
6247gc_mark_set(rb_objspace_t *objspace, VALUE obj)
6248{
6249 if (RVALUE_MARKED(objspace, obj)) return 0;
6250 MARK_IN_BITMAP(GET_HEAP_MARK_BITS(obj), obj);
6251 return 1;
6252}
6253
6254static void
6255gc_aging(rb_objspace_t *objspace, VALUE obj)
6256{
6257 /* Disable aging if Major GC's are disabled. This will prevent longish lived
6258 * objects filling up the heap at the expense of marking many more objects.
6259 *
6260 * We should always pre-warm our process when disabling majors, by running
6261 * GC manually several times so that most objects likely to become oldgen
6262 * are already oldgen.
6263 */
6264 if(!gc_config_full_mark_val)
6265 return;
6266
6267 struct heap_page *page = GET_HEAP_PAGE(obj);
6268
6269 GC_ASSERT(RVALUE_MARKING(objspace, obj) == FALSE);
6270 check_rvalue_consistency(objspace, obj);
6271
6272 if (!RVALUE_PAGE_WB_UNPROTECTED(page, obj)) {
6273 if (!RVALUE_OLD_P(objspace, obj)) {
6274 int t = BUILTIN_TYPE(obj);
6275 if (t == T_CLASS || t == T_MODULE || t == T_ICLASS) {
6276 gc_report(3, objspace, "gc_aging: YOUNG class: %s\n", rb_obj_info(obj));
6277 RVALUE_AGE_SET(obj, RVALUE_OLD_AGE);
6278 RVALUE_OLD_UNCOLLECTIBLE_SET(objspace, obj);
6279 }
6280 else {
6281 gc_report(3, objspace, "gc_aging: YOUNG: %s\n", rb_obj_info(obj));
6282 RVALUE_AGE_INC(objspace, obj);
6283 }
6284 }
6285 else if (is_full_marking(objspace)) {
6286 GC_ASSERT(RVALUE_PAGE_UNCOLLECTIBLE(page, obj) == FALSE);
6287 RVALUE_PAGE_OLD_UNCOLLECTIBLE_SET(objspace, page, obj);
6288 }
6289 }
6290 check_rvalue_consistency(objspace, obj);
6291
6292 objspace->marked_slots++;
6293}
6294
6295static void
6296gc_grey(rb_objspace_t *objspace, VALUE obj)
6297{
6298#if RGENGC_CHECK_MODE
6299 if (RVALUE_MARKED(objspace, obj) == FALSE) rb_bug("gc_grey: %s is not marked.", rb_obj_info(obj));
6300 if (RVALUE_MARKING(objspace, obj) == TRUE) rb_bug("gc_grey: %s is marking/remembered.", rb_obj_info(obj));
6301#endif
6302
6303 if (is_incremental_marking(objspace)) {
6304 MARK_IN_BITMAP(GET_HEAP_MARKING_BITS(obj), obj);
6305 }
6306
6308 rb_darray_append_without_gc(&objspace->weak_references, obj);
6309 }
6310
6311 push_mark_stack(&objspace->mark_stack, obj);
6312}
6313
6314static inline void
6315gc_mark_check_t_none(rb_objspace_t *objspace, VALUE obj)
6316{
6317 if (RB_UNLIKELY(BUILTIN_TYPE(obj) == T_NONE)) {
6318 enum {info_size = 256};
6319 char obj_info_buf[info_size];
6320 rb_raw_obj_info(obj_info_buf, info_size, obj);
6321
6322 char parent_obj_info_buf[info_size];
6323 rb_raw_obj_info(parent_obj_info_buf, info_size, objspace->rgengc.parent_object);
6324
6325 rb_bug("try to mark T_NONE object (obj: %s, parent: %s)", obj_info_buf, parent_obj_info_buf);
6326 }
6327}
6328
6329static void
6330gc_mark(rb_objspace_t *objspace, VALUE obj)
6331{
6332 GC_ASSERT(during_gc);
6333 GC_ASSERT(!objspace->flags.during_reference_updating);
6334
6335 /* Never step into another objspace: a foreign object is a live leaf whose liveness
6336 * belongs to its owner, so touching its bitmaps here would be unsound. A global GC
6337 * lifts this: everyone is stopped and the bits live on the object's own page. */
6338 if (gc_skip_foreign_object_p(objspace, obj)) {
6339 return;
6340 }
6341
6342 if (RB_UNLIKELY(objspace->flags.during_global_gc)) {
6343 /* Recompute the shref of every shareable -> unshareable edge, within and across
6344 * objspaces: the clear pass dropped all shref bits and the write barrier
6345 * maintains them from here on. */
6346 VALUE parent = objspace->rgengc.parent_object;
6347 if (!UNDEF_P(parent) && parent != Qfalse &&
6350 struct heap_page *page = GET_HEAP_PAGE(obj);
6351 _MARK_IN_BITMAP(page->shref_bits, page, obj);
6352 page->flags.has_shref_objects = TRUE;
6353 }
6354 }
6355
6356 rgengc_check_relation(objspace, obj);
6357 if (!gc_mark_set(objspace, obj)) return; /* already marked */
6358
6359 if (0) { // for debug GC marking miss
6360 RUBY_DEBUG_LOG("%p (%s) parent:%p (%s)",
6361 (void *)obj, obj_type_name(obj),
6362 (void *)objspace->rgengc.parent_object, obj_type_name(objspace->rgengc.parent_object));
6363 }
6364
6365 gc_mark_check_t_none(objspace, obj);
6366
6367 gc_aging(objspace, obj);
6368 gc_grey(objspace, obj);
6369}
6370
6371static inline void
6372gc_pin(rb_objspace_t *objspace, VALUE obj)
6373{
6374 GC_ASSERT(!SPECIAL_CONST_P(obj));
6375
6376 if (RB_UNLIKELY(objspace->flags.during_compacting)) {
6377 /* Never write a foreign page's pinned bit (a global GC may: everyone is stopped). */
6378 if (gc_skip_foreign_object_p(objspace, obj)) return;
6379
6380 if (RB_LIKELY(during_gc)) {
6381 if (!RVALUE_PINNED(objspace, obj)) {
6382 GC_ASSERT(GET_HEAP_PAGE(obj)->pinned_slots <= GET_HEAP_PAGE(obj)->total_slots);
6383 GET_HEAP_PAGE(obj)->pinned_slots++;
6384 MARK_IN_BITMAP(GET_HEAP_PINNED_BITS(obj), obj);
6385 }
6386 }
6387 }
6388}
6389
6390static inline void
6391gc_mark_and_pin(rb_objspace_t *objspace, VALUE obj)
6392{
6393 gc_pin(objspace, obj);
6394 gc_mark(objspace, obj);
6395}
6396
6397void
6398rb_gc_impl_mark_and_move(void *objspace_ptr, VALUE *ptr)
6399{
6400 rb_objspace_t *objspace = objspace_ptr;
6401
6402 if (RB_UNLIKELY(objspace->flags.during_reference_updating)) {
6403 GC_ASSERT(objspace->flags.during_compacting);
6404 GC_ASSERT(during_gc);
6405
6406 VALUE destination = rb_gc_impl_location(objspace, *ptr);
6407 if (destination != *ptr) {
6408 *ptr = destination;
6409 }
6410 }
6411 else {
6412 gc_mark(objspace, *ptr);
6413 }
6414}
6415
6416void
6417rb_gc_impl_mark(void *objspace_ptr, VALUE obj)
6418{
6419 rb_objspace_t *objspace = objspace_ptr;
6420
6421 gc_mark(objspace, obj);
6422}
6423
6424void
6425rb_gc_impl_mark_and_pin(void *objspace_ptr, VALUE obj)
6426{
6427 rb_objspace_t *objspace = objspace_ptr;
6428
6429 gc_mark_and_pin(objspace, obj);
6430}
6431
6432/* A word scanned conservatively by a global GC can point into any objspace, so ownership
6433 * is decided against the driver's snapshot of every objspace (the bits then land on the
6434 * owner's page through gc_mark and gc_pin). */
6435static bool
6436gc_global_pointer_to_heap_p(const void *ptr)
6437{
6438 const rb_global_objspace_t *g = global_objspace;
6439 uintptr_t p = (uintptr_t)ptr;
6440
6441 if (p < g->page_index.lomem || p > g->page_index.himem) return false;
6442 if (p % sizeof(VALUE) != 0) return false;
6443
6444 struct heap_page **res = bsearch(ptr, g->page_index.pages, g->page_index.n_pages,
6445 sizeof(struct heap_page *), ptr_in_page_body_p);
6446 if (res == NULL) return false;
6447
6448 struct heap_page *page = *res;
6449 if (heap_page_in_global_empty_pages_pool(page->objspace, page)) return false;
6450 if (p < page->start) return false;
6451 if (p >= page->start + (page->total_slots * page->slot_size)) return false;
6452 if ((p - page->start) % page->slot_size != 0) return false;
6453 return true;
6454}
6455
6456void
6457rb_gc_impl_mark_maybe(void *objspace_ptr, VALUE obj)
6458{
6459 rb_objspace_t *objspace = objspace_ptr;
6460
6461 (void)VALGRIND_MAKE_MEM_DEFINED(&obj, sizeof(obj));
6462
6463 if (RB_UNLIKELY(objspace->flags.during_global_gc)
6464 ? gc_global_pointer_to_heap_p((void *)obj)
6465 : is_pointer_to_heap(objspace, (void *)obj)) {
6466 asan_unpoisoning_object(obj) {
6467 /* Garbage can live on the stack, so do not mark or pin */
6468 switch (BUILTIN_TYPE(obj)) {
6469 case T_ZOMBIE:
6470 case T_NONE:
6471 break;
6472 default:
6473 gc_mark_and_pin(objspace, obj);
6474 break;
6475 }
6476 }
6477 }
6478}
6479
6480static int
6481pin_value(st_data_t key, st_data_t value, st_data_t data)
6482{
6483 rb_gc_impl_mark_and_pin((void *)data, (VALUE)value);
6484
6485 return ST_CONTINUE;
6486}
6487
6488static inline void
6489gc_mark_set_parent_raw(rb_objspace_t *objspace, VALUE obj, bool old_p)
6490{
6491 asan_unpoison_memory_region(&objspace->rgengc.parent_object, sizeof(objspace->rgengc.parent_object), false);
6492 asan_unpoison_memory_region(&objspace->rgengc.parent_object_old_p, sizeof(objspace->rgengc.parent_object_old_p), false);
6493 objspace->rgengc.parent_object = obj;
6494 objspace->rgengc.parent_object_old_p = old_p;
6495}
6496
6497static inline void
6498gc_mark_set_parent(rb_objspace_t *objspace, VALUE obj)
6499{
6500 gc_mark_set_parent_raw(objspace, obj, RVALUE_OLD_P(objspace, obj));
6501}
6502
6503static inline void
6504gc_mark_set_parent_invalid(rb_objspace_t *objspace)
6505{
6506 asan_poison_memory_region(&objspace->rgengc.parent_object, sizeof(objspace->rgengc.parent_object));
6507 asan_poison_memory_region(&objspace->rgengc.parent_object_old_p, sizeof(objspace->rgengc.parent_object_old_p));
6508}
6509
6510static void pinned_roots_mark(rb_objspace_t *objspace, rb_heap_t *heap);
6511
6512static void
6513mark_roots(rb_objspace_t *objspace, const char **categoryp)
6514{
6515 VALUE objspace_guard = (VALUE)objspace;
6516#define MARK_CHECKPOINT(category) do { \
6517 if (categoryp) *categoryp = category; \
6518} while (0)
6519
6520 /* Pinning shareable objects and shrefs runs at the end of marking (gc_marks_finish),
6521 * not here: after the full walk it only has to touch what ordinary marking missed,
6522 * which is both cheap and a useful retention metric. */
6523
6524 MARK_CHECKPOINT("objspace");
6525 gc_mark_set_parent_raw(objspace, Qundef, false);
6526
6527 if (objspace->flags.during_global_gc) {
6528 /* Pin the finalizer tables of every objspace, zombies included.
6529 * (finalizer_table is a macro over the local "objspace".) */
6530 rb_objspace_t *const driver = objspace;
6531 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
6532 rb_objspace_t *objspace = global_objspace->global_gc.objspaces[i];
6533 if (finalizer_table != NULL) {
6534 st_foreach(finalizer_table, pin_value, (st_data_t)driver);
6535 }
6536 }
6537 }
6538 else if (finalizer_table != NULL) {
6539 st_foreach(finalizer_table, pin_value, (st_data_t)objspace);
6540 }
6541
6542 if (stress_to_class) rb_gc_mark(stress_to_class);
6543
6544 rb_gc_save_machine_context();
6545 rb_gc_mark_roots(objspace, categoryp);
6546 /* Keep this frame, including its saved registers, until root marking has
6547 * scanned the machine stack. */
6548 RB_GC_GUARD(objspace_guard);
6549 gc_mark_set_parent_invalid(objspace);
6550}
6551
6552static void
6553gc_mark_children(rb_objspace_t *objspace, VALUE obj)
6554{
6555 gc_mark_set_parent(objspace, obj);
6556 rb_gc_mark_children(objspace, obj);
6557 gc_mark_set_parent_invalid(objspace);
6558}
6559
6564static inline int
6565gc_mark_stacked_objects(rb_objspace_t *objspace, int incremental, size_t count)
6566{
6567 mark_stack_t *mstack = &objspace->mark_stack;
6568 VALUE obj;
6569 size_t marked_slots_at_the_beginning = objspace->marked_slots;
6570 size_t popped_count = 0;
6571
6572 while (pop_mark_stack(mstack, &obj)) {
6573 if (obj == Qundef) continue; /* skip */
6574
6575 if (RGENGC_CHECK_MODE && !RVALUE_MARKED(objspace, obj)) {
6576 rb_bug("gc_mark_stacked_objects: %s is not marked.", rb_obj_info(obj));
6577 }
6578 gc_mark_children(objspace, obj);
6579
6580 popped_count++;
6581
6582 if (incremental) {
6583 if (RGENGC_CHECK_MODE && !RVALUE_MARKING(objspace, obj)) {
6584 rb_bug("gc_mark_stacked_objects: incremental, but marking bit is 0");
6585 }
6586 CLEAR_IN_BITMAP(GET_HEAP_MARKING_BITS(obj), obj);
6587
6588 if (popped_count + (objspace->marked_slots - marked_slots_at_the_beginning) > count) {
6589 break;
6590 }
6591 }
6592 else {
6593 /* just ignore marking bits */
6594 }
6595 }
6596
6597 RUBY_DTRACE_GC_HOOK(MARK_STACKED_OBJECTS, popped_count);
6598
6599 if (RGENGC_CHECK_MODE >= 3) gc_verify_internal_consistency(objspace);
6600
6601 if (is_mark_stack_empty(mstack)) {
6602 shrink_stack_chunk_cache(mstack);
6603 return TRUE;
6604 }
6605 else {
6606 return FALSE;
6607 }
6608}
6609
6610static int
6611gc_mark_stacked_objects_incremental(rb_objspace_t *objspace, size_t count)
6612{
6613 return gc_mark_stacked_objects(objspace, TRUE, count);
6614}
6615
6616static int
6617gc_mark_stacked_objects_all(rb_objspace_t *objspace)
6618{
6619 return gc_mark_stacked_objects(objspace, FALSE, 0);
6620}
6621
6622#if RGENGC_CHECK_MODE >= 4
6623
6624#define MAKE_ROOTSIG(obj) (((VALUE)(obj) << 1) | 0x01)
6625#define IS_ROOTSIG(obj) ((VALUE)(obj) & 0x01)
6626#define GET_ROOTSIG(obj) ((const char *)((VALUE)(obj) >> 1))
6627
6628struct reflist {
6629 VALUE *list;
6630 int pos;
6631 int size;
6632};
6633
6634static struct reflist *
6635reflist_create(VALUE obj)
6636{
6637 struct reflist *refs = xmalloc(sizeof(struct reflist));
6638 refs->size = 1;
6639 refs->list = ALLOC_N(VALUE, refs->size);
6640 refs->list[0] = obj;
6641 refs->pos = 1;
6642 return refs;
6643}
6644
6645static void
6646reflist_destruct(struct reflist *refs)
6647{
6648 xfree(refs->list);
6649 xfree(refs);
6650}
6651
6652static void
6653reflist_add(struct reflist *refs, VALUE obj)
6654{
6655 if (refs->pos == refs->size) {
6656 refs->size *= 2;
6657 SIZED_REALLOC_N(refs->list, VALUE, refs->size, refs->size/2);
6658 }
6659
6660 refs->list[refs->pos++] = obj;
6661}
6662
6663static void
6664reflist_dump(struct reflist *refs)
6665{
6666 int i;
6667 for (i=0; i<refs->pos; i++) {
6668 VALUE obj = refs->list[i];
6669 if (IS_ROOTSIG(obj)) { /* root */
6670 fprintf(stderr, "<root@%s>", GET_ROOTSIG(obj));
6671 }
6672 else {
6673 fprintf(stderr, "<%s>", rb_obj_info(obj));
6674 }
6675 if (i+1 < refs->pos) fprintf(stderr, ", ");
6676 }
6677}
6678
6679static int
6680reflist_referred_from_machine_context(struct reflist *refs)
6681{
6682 int i;
6683 for (i=0; i<refs->pos; i++) {
6684 VALUE obj = refs->list[i];
6685 if (IS_ROOTSIG(obj) && strcmp(GET_ROOTSIG(obj), "machine_context") == 0) return 1;
6686 }
6687 return 0;
6688}
6689
6690struct allrefs {
6692 /* a -> obj1
6693 * b -> obj1
6694 * c -> obj1
6695 * c -> obj2
6696 * d -> obj3
6697 * #=> {obj1 => [a, b, c], obj2 => [c, d]}
6698 */
6699 struct st_table *references;
6700 const char *category;
6701 VALUE root_obj;
6703};
6704
6705static int
6706allrefs_add(struct allrefs *data, VALUE obj)
6707{
6708 struct reflist *refs;
6709 st_data_t r;
6710
6711 if (st_lookup(data->references, obj, &r)) {
6712 refs = (struct reflist *)r;
6713 reflist_add(refs, data->root_obj);
6714 return 0;
6715 }
6716 else {
6717 refs = reflist_create(data->root_obj);
6718 st_insert(data->references, obj, (st_data_t)refs);
6719 return 1;
6720 }
6721}
6722
6723static void
6724allrefs_i(VALUE obj, void *ptr)
6725{
6726 struct allrefs *data = (struct allrefs *)ptr;
6727
6728 if (allrefs_add(data, obj)) {
6729 push_mark_stack(&data->mark_stack, obj);
6730 }
6731}
6732
6733static void
6734allrefs_roots_i(VALUE obj, void *ptr)
6735{
6736 struct allrefs *data = (struct allrefs *)ptr;
6737 if (strlen(data->category) == 0) rb_bug("!!!");
6738 data->root_obj = MAKE_ROOTSIG(data->category);
6739
6740 if (allrefs_add(data, obj)) {
6741 push_mark_stack(&data->mark_stack, obj);
6742 }
6743}
6744#define PUSH_MARK_FUNC_DATA(v) do { \
6745 struct gc_mark_func_data_struct *prev_mark_func_data = GET_VM()->gc.mark_func_data; \
6746 GET_VM()->gc.mark_func_data = (v);
6747
6748#define POP_MARK_FUNC_DATA() GET_VM()->gc.mark_func_data = prev_mark_func_data;} while (0)
6749
6750static st_table *
6751objspace_allrefs(rb_objspace_t *objspace)
6752{
6753 struct allrefs data;
6754 struct gc_mark_func_data_struct mfd;
6755 VALUE obj;
6756 int prev_dont_gc = dont_gc_val();
6757 dont_gc_on();
6758
6759 data.objspace = objspace;
6760 data.references = st_init_numtable();
6761 init_mark_stack(&data.mark_stack);
6762
6763 mfd.mark_func = allrefs_roots_i;
6764 mfd.data = &data;
6765
6766 /* traverse root objects */
6767 PUSH_MARK_FUNC_DATA(&mfd);
6768 GET_VM()->gc.mark_func_data = &mfd;
6769 mark_roots(objspace, &data.category);
6770 POP_MARK_FUNC_DATA();
6771
6772 /* traverse rest objects reachable from root objects */
6773 while (pop_mark_stack(&data.mark_stack, &obj)) {
6774 rb_objspace_reachable_objects_from(data.root_obj = obj, allrefs_i, &data);
6775 }
6776 free_stack_chunks(&data.mark_stack);
6777
6778 dont_gc_set(prev_dont_gc);
6779 return data.references;
6780}
6781
6782static int
6783objspace_allrefs_destruct_i(st_data_t key, st_data_t value, st_data_t ptr)
6784{
6785 struct reflist *refs = (struct reflist *)value;
6786 reflist_destruct(refs);
6787 return ST_CONTINUE;
6788}
6789
6790static void
6791objspace_allrefs_destruct(struct st_table *refs)
6792{
6793 st_foreach(refs, objspace_allrefs_destruct_i, 0);
6794 st_free_table(refs);
6795}
6796
6797#if RGENGC_CHECK_MODE >= 5
6798static int
6799allrefs_dump_i(st_data_t k, st_data_t v, st_data_t ptr)
6800{
6801 VALUE obj = (VALUE)k;
6802 struct reflist *refs = (struct reflist *)v;
6803 fprintf(stderr, "[allrefs_dump_i] %s <- ", rb_obj_info(obj));
6804 reflist_dump(refs);
6805 fprintf(stderr, "\n");
6806 return ST_CONTINUE;
6807}
6808
6809static void
6810allrefs_dump(rb_objspace_t *objspace)
6811{
6812 VALUE size = objspace->rgengc.allrefs_table->num_entries;
6813 fprintf(stderr, "[all refs] (size: %"PRIuVALUE")\n", size);
6814 st_foreach(objspace->rgengc.allrefs_table, allrefs_dump_i, 0);
6815}
6816#endif
6817
6818static int
6819gc_check_after_marks_i(st_data_t k, st_data_t v, st_data_t ptr)
6820{
6821 VALUE obj = k;
6822 struct reflist *refs = (struct reflist *)v;
6824
6825 /* object should be marked or oldgen */
6826 if (!RVALUE_MARKED(objspace, obj)) {
6827 fprintf(stderr, "gc_check_after_marks_i: %s is not marked and not oldgen.\n", rb_obj_info(obj));
6828 fprintf(stderr, "gc_check_after_marks_i: %p is referred from ", (void *)obj);
6829 reflist_dump(refs);
6830
6831 if (reflist_referred_from_machine_context(refs)) {
6832 fprintf(stderr, " (marked from machine stack).\n");
6833 /* marked from machine context can be false positive */
6834 }
6835 else {
6836 objspace->rgengc.error_count++;
6837 fprintf(stderr, "\n");
6838 }
6839 }
6840 return ST_CONTINUE;
6841}
6842
6843static void
6844gc_marks_check(rb_objspace_t *objspace, st_foreach_callback_func *checker_func, const char *checker_name)
6845{
6846 MALLOC_COUNTERS_LOCK(objspace);
6847 struct gc_malloc_bytes saved_malloc = {
6848 .malloc = gc_counter_load_relaxed(&objspace->malloc_counters.counters.malloc),
6849 .free = gc_counter_load_relaxed(&objspace->malloc_counters.counters.free),
6850 .malloc_at_last_gc = gc_counter_load_relaxed(&objspace->malloc_counters.counters.malloc_at_last_gc),
6851 .free_at_last_gc = gc_counter_load_relaxed(&objspace->malloc_counters.counters.free_at_last_gc),
6852 };
6853#if RGENGC_ESTIMATE_OLDMALLOC
6854 struct gc_malloc_bytes saved_oldmalloc = {
6855 .malloc = gc_counter_load_relaxed(&objspace->malloc_counters.oldcounters.malloc),
6856 .free = gc_counter_load_relaxed(&objspace->malloc_counters.oldcounters.free),
6857 .malloc_at_last_gc = gc_counter_load_relaxed(&objspace->malloc_counters.oldcounters.malloc_at_last_gc),
6858 .free_at_last_gc = gc_counter_load_relaxed(&objspace->malloc_counters.oldcounters.free_at_last_gc),
6859 };
6860#endif
6861 MALLOC_COUNTERS_UNLOCK(objspace);
6862 VALUE already_disabled = rb_objspace_gc_disable(objspace);
6863
6864 objspace->rgengc.allrefs_table = objspace_allrefs(objspace);
6865
6866 if (checker_func) {
6867 st_foreach(objspace->rgengc.allrefs_table, checker_func, (st_data_t)objspace);
6868 }
6869
6870 if (objspace->rgengc.error_count > 0) {
6871#if RGENGC_CHECK_MODE >= 5
6872 allrefs_dump(objspace);
6873#endif
6874 if (checker_name) rb_bug("%s: GC has problem.", checker_name);
6875 }
6876
6877 objspace_allrefs_destruct(objspace->rgengc.allrefs_table);
6878 objspace->rgengc.allrefs_table = 0;
6879
6880 if (already_disabled == Qfalse) rb_objspace_gc_enable(objspace);
6881 MALLOC_COUNTERS_LOCK(objspace);
6882 gc_counter_store_release(&objspace->malloc_counters.counters.malloc, saved_malloc.malloc);
6883 gc_counter_store_release(&objspace->malloc_counters.counters.free, saved_malloc.free);
6884 gc_counter_store_release(&objspace->malloc_counters.counters.malloc_at_last_gc, saved_malloc.malloc_at_last_gc);
6885 gc_counter_store_release(&objspace->malloc_counters.counters.free_at_last_gc, saved_malloc.free_at_last_gc);
6886#if RGENGC_ESTIMATE_OLDMALLOC
6887 gc_counter_store_release(&objspace->malloc_counters.oldcounters.malloc, saved_oldmalloc.malloc);
6888 gc_counter_store_release(&objspace->malloc_counters.oldcounters.free, saved_oldmalloc.free);
6889 gc_counter_store_release(&objspace->malloc_counters.oldcounters.malloc_at_last_gc, saved_oldmalloc.malloc_at_last_gc);
6890 gc_counter_store_release(&objspace->malloc_counters.oldcounters.free_at_last_gc, saved_oldmalloc.free_at_last_gc);
6891#endif
6892 MALLOC_COUNTERS_UNLOCK(objspace);
6893}
6894#endif /* RGENGC_CHECK_MODE >= 4 */
6895
6898 /* True only while the world is stopped: a GC.verify holding the VM lock and barrier,
6899 * or a global GC. Cross-objspace checks (walking every objspace's pages) are sound
6900 * only then. */
6901 bool world_stopped;
6902 int err_count;
6903 size_t live_object_count;
6904 size_t zombie_object_count;
6905
6906 VALUE parent;
6907 bool parent_shareable;
6908 size_t old_object_count;
6909 size_t remembered_shady_count;
6910};
6911
6912
6913static void
6914check_generation_i(const VALUE child, void *ptr)
6915{
6917 const VALUE parent = data->parent;
6918
6919 if (RGENGC_CHECK_MODE) GC_ASSERT(RVALUE_OLD_P(data->objspace, parent));
6920
6921 /* A cross-objspace edge is kept alive by the shareable/shref mechanism and is not
6922 * tracked in this objspace's remembered set. */
6923 if (GET_HEAP_OBJSPACE(child) != data->objspace) return;
6924
6925 /* Once the process goes multi-Ractor, the shareable world is managed by pinning and
6926 * shrefs rather than by the remembered set: the pinned walk at the end of a mark
6927 * re-marks every shareable object (and its shref'd children) each local cycle, and a
6928 * global GC rebuilds the generation state. So the generational old->young invariant
6929 * does not hold when either endpoint is shareable: an old constcache, cc_table or
6930 * interned string pointing at a core class that is young after a global GC is the
6931 * typical false positive. That state outlives the return to a single Ractor until
6932 * the next major (an old shareable singleton class pointing at a young
6933 * attached_object, say), so the test uses rb_gc_ever_multi_ractor_p(), which stays
6934 * true forever once multiple Ractors existed. A program that never goes multi keeps
6935 * the strict check, and ASAN catches what is left. */
6936 if (rb_gc_ever_multi_ractor_p() &&
6937 (MARKED_IN_BITMAP(GET_HEAP_SHAREABLE_BITS(parent), parent) ||
6938 MARKED_IN_BITMAP(GET_HEAP_SHAREABLE_BITS(child), child))) {
6939 return;
6940 }
6941
6942 if (!RVALUE_OLD_P(data->objspace, child)) {
6943 /* A young shareable child is pinned and kept alive by the local GC (only a
6944 * global GC collects it), so it survives even when the old parent does not
6945 * remember it. It is outside the generational remembered set, so exclude it
6946 * from the old->young check. */
6947 if (!RVALUE_REMEMBERED(data->objspace, parent) &&
6948 !RVALUE_REMEMBERED(data->objspace, child) &&
6949 !RVALUE_UNCOLLECTIBLE(data->objspace, child) &&
6951 fprintf(stderr, "verify_internal_consistency_reachable_i: WB miss (O->Y) %s -> %s\n", rb_obj_info(parent), rb_obj_info(child));
6952 data->err_count++;
6953 }
6954 }
6955}
6956
6957static void
6958check_color_i(const VALUE child, void *ptr)
6959{
6961 const VALUE parent = data->parent;
6962
6963 /* This cycle never marks a foreign child (gc_skip_foreign_object_p) and the write
6964 * barrier is a no-op across objspaces, so its colour says nothing here. */
6965 if (GET_HEAP_OBJSPACE(child) != data->objspace) return;
6966
6967 if (!RVALUE_WB_UNPROTECTED(data->objspace, parent) && RVALUE_WHITE_P(data->objspace, child)) {
6968 fprintf(stderr, "verify_internal_consistency_reachable_i: WB miss (B->W) - %s -> %s\n",
6969 rb_obj_info(parent), rb_obj_info(child));
6970 data->err_count++;
6971 }
6972}
6973
6974static void
6975check_children_i(const VALUE child, void *ptr)
6976{
6978
6979 /* Fast path: a child in this objspace (99.99% of all edges). */
6980 if (RB_LIKELY(is_pointer_to_heap(data->objspace, (void *)child))) {
6981 if (check_rvalue_consistency_force(data->objspace, child, FALSE) != 0) {
6982 fprintf(stderr, "check_children_i: %s has error (referenced from %s)\n",
6983 rb_obj_info(child), rb_obj_info(data->parent));
6984 data->err_count++;
6985 }
6986 return;
6987 }
6988
6989 /* The remaining cross-objspace check (verify_pointer_in_any_heap_p) walks every
6990 * objspace's pages, sound only with the world stopped: mid-local-GC other Ractors
6991 * change page structures concurrently. The next world-stopped verify re-checks. */
6992 if (!data->world_stopped) return;
6993
6994 /* A non-heap child reaches this callback only when a stale field was followed by a
6995 * plain rb_gc_mark (the dmark of a live but unreachable wrapper, say). Report it and
6996 * keep going rather than aborting. */
6997 if (!verify_pointer_in_any_heap_p((void *)child)) {
6998 /* The graph is in flux mid-merge, so a transient non-heap edge is expected; it
6999 * is re-checked after the merge. */
7000 if (global_objspace->during_absorb) return;
7001 fprintf(stderr, "VERIFY-NOTE: non-heap child %p (from %s)\n",
7002 (void *)child, rb_obj_info(data->parent));
7003 return;
7004 }
7005
7006 if (GET_HEAP_OBJSPACE(child) != data->objspace) {
7007 /* A legal cross-objspace edge either starts at a shareable object or is recorded
7008 * in the child's shref bit (an in-flight send or move payload kept alive across
7009 * its owner's local GC; root_scope_check_i honours the same record). An
7010 * unshareable parent holding an unrecorded foreign unshareable child would be
7011 * invisible to both local GCs. The exception is a box's top_self, which every
7012 * thread's th->top_self points at and which is VM-permanent. Skipped during a
7013 * global GC: it clears every shref bit, so the shref exemption would not fire,
7014 * and its unified exact stop-the-world mark makes the invariant itself moot. */
7015 if (!data->parent_shareable &&
7016 child != rb_gc_vm_top_self() &&
7017 !MARKED_IN_BITMAP(GET_HEAP_SHAREABLE_BITS(child), child) &&
7018 !MARKED_IN_BITMAP(GET_HEAP_SHREF_BITS(child), child) &&
7019 !rb_gc_impl_during_global_gc_p(data->objspace) &&
7020 !global_objspace->during_absorb) {
7021 fprintf(stderr, "check_children_i: containment violation: "
7022 "unshareable %s (objspace %p) -> foreign unshareable %s (objspace %p)\n",
7023 rb_obj_info(data->parent), (void *)data->objspace,
7024 rb_obj_info(child), (void *)GET_HEAP_OBJSPACE(child));
7025 data->err_count++;
7026 }
7027
7028 /* The remaining per-objspace sanity rules belong to the owner. */
7029 return;
7030 }
7031}
7032
7033/* Whether a heap slot currently holds a live object. Returns false for empty
7034 * (T_NONE), moved (T_MOVED), and zombie (T_ZOMBIE) slots, and for garbage
7035 * objects about to be swept. */
7036static bool
7037gc_slot_live_object_p(rb_objspace_t *objspace, VALUE obj)
7038{
7039 switch (BUILTIN_TYPE(obj)) {
7040 case T_NONE:
7041 case T_MOVED:
7042 case T_ZOMBIE:
7043 return false;
7044 default:
7045 return !rb_gc_impl_garbage_object_p(objspace, obj);
7046 }
7047}
7048
7049/* Verifier only: does ptr point at a live slot in any objspace? The caller holds the VM
7050 * lock and the barrier, so page_index is stable. */
7051static bool
7052verify_pointer_in_any_heap_p(const void *ptr)
7053{
7054 return gc_global_pointer_to_heap_p(ptr);
7055}
7056
7057/* An exact root of the calling Ractor may only point at a shareable object, its own
7058 * objspace, or an in-flight payload with a recorded shref. Exempt: the conservative
7059 * machine scan (stale slots) and the VM-global containers that are cross-rooted by
7060 * design (every objspace scans them; the marker skips foreign entries). */
7061static void
7062root_scope_check_i(const char *category, VALUE obj, void *ptr)
7063{
7064 struct verify_internal_consistency_struct *data = ptr;
7065
7066 if (RB_SPECIAL_CONST_P(obj)) return;
7067 /* This check walks every objspace (verify_pointer_in_any_heap_p), so it is sound
7068 * only with the world stopped; a mid-local-GC verify races with other Ractors'
7069 * lock-free allocation. */
7070 if (!data->world_stopped) return;
7071 /* Mid-merge the VM-global root tables still point at the unmerged source (transient
7072 * non-heap or foreign roots); re-checked after the merge. */
7073 if (global_objspace->during_absorb) return;
7074 if (strcmp(category, "machine_context") == 0 ||
7075 strcmp(category, "vm_registered_objects") == 0 ||
7076 strcmp(category, "end_proc") == 0 ||
7077 strcmp(category, "trap_list") == 0) {
7078 return;
7079 }
7080
7081 if (!verify_pointer_in_any_heap_p((void *)obj)) {
7082 fprintf(stderr, "root_scope_check_i: root category \"%s\" names a non-heap pointer %p\n",
7083 category, (void *)obj);
7084 data->err_count++;
7085 return;
7086 }
7087
7088 if (GET_HEAP_OBJSPACE(obj) == data->objspace) return;
7089 if (MARKED_IN_BITMAP(GET_HEAP_SHAREABLE_BITS(obj), obj)) return;
7090 if (MARKED_IN_BITMAP(GET_HEAP_SHREF_BITS(obj), obj)) return;
7091 if (obj == rb_gc_vm_top_self()) return; /* VM-permanent (see check_children_i) */
7092
7093 fprintf(stderr, "root_scope_check_i: root category \"%s\" names a foreign "
7094 "unshareable without a shref record: %s (owner %p, self %p)\n",
7095 category, rb_obj_info(obj),
7096 (void *)GET_HEAP_OBJSPACE(obj), (void *)data->objspace);
7097 data->err_count++;
7098}
7099
7100static int
7101verify_internal_consistency_i(void *page_start, void *page_end, size_t stride,
7103{
7104 VALUE obj;
7105 rb_objspace_t *objspace = data->objspace;
7106
7107 for (obj = (VALUE)page_start; obj != (VALUE)page_end; obj += stride) {
7108 asan_unpoisoning_object(obj) {
7109 bool sh_bit = MARKED_IN_BITMAP(GET_HEAP_SHAREABLE_BITS(obj), obj) != 0;
7110 bool sr_bit = MARKED_IN_BITMAP(GET_HEAP_SHREF_BITS(obj), obj) != 0;
7111
7112 if (gc_slot_live_object_p(objspace, obj)) {
7113 /* count objects */
7114 data->live_object_count++;
7115 data->parent = obj;
7116 data->parent_shareable = sh_bit;
7117
7118 /* Bitmap invariants: a page's shareable bit matches FL_SHAREABLE
7119 * exactly, and a shref record only ever points at an unshareable
7120 * object. */
7121 if (sh_bit != !!RB_FL_TEST_RAW(obj, RUBY_FL_SHAREABLE)) {
7122 fprintf(stderr, "verify_internal_consistency_i: shareable bit %d "
7123 "disagrees with FL_SHAREABLE on %s\n", (int)sh_bit, rb_obj_info(obj));
7124 data->err_count++;
7125 }
7126 if (sr_bit && sh_bit) {
7127 fprintf(stderr, "verify_internal_consistency_i: shref bit on a shareable: %s\n",
7128 rb_obj_info(obj));
7129 data->err_count++;
7130 }
7131
7132 /* Normally, we don't expect T_MOVED objects to be in the heap.
7133 * But they can stay alive on the stack, */
7134 if (!gc_object_moved_p(objspace, obj)) {
7135 /* moved slots don't have children */
7136 rb_objspace_reachable_objects_from(obj, check_children_i, (void *)data);
7137 }
7138
7139 /* check health of children */
7140 if (RVALUE_OLD_P(objspace, obj)) data->old_object_count++;
7141 if (RVALUE_WB_UNPROTECTED(objspace, obj) && RVALUE_UNCOLLECTIBLE(objspace, obj)) data->remembered_shady_count++;
7142
7143 if (!is_marking(objspace) && RVALUE_OLD_P(objspace, obj)) {
7144 /* reachable objects from an oldgen object should be old or (young with remember) */
7145 data->parent = obj;
7146 rb_objspace_reachable_objects_from(obj, check_generation_i, (void *)data);
7147 }
7148
7149 if (!is_marking(objspace) && rb_gc_obj_shareable_p(obj)) {
7150 rb_gc_verify_shareable(obj);
7151 }
7152
7153 if (is_incremental_marking(objspace)) {
7154 if (RVALUE_BLACK_P(objspace, obj)) {
7155 /* reachable objects from black objects should be black or grey objects */
7156 data->parent = obj;
7157 rb_objspace_reachable_objects_from(obj, check_color_i, (void *)data);
7158 }
7159 }
7160 }
7161 else {
7162 /* A freed slot must not carry its old pin bit into the next object born
7163 * there (a dead object not swept yet legitimately keeps it until the
7164 * sweep arrives). */
7165 if (BUILTIN_TYPE(obj) == T_NONE && (sh_bit || sr_bit)) {
7166 fprintf(stderr, "verify_internal_consistency_i: T_NONE slot carries "
7167 "shareable=%d shref=%d bits\n", (int)sh_bit, (int)sr_bit);
7168 data->err_count++;
7169 }
7170
7171 if (BUILTIN_TYPE(obj) == T_ZOMBIE) {
7172 data->zombie_object_count++;
7173
7174 if ((RBASIC(obj)->flags & ~ZOMBIE_OBJ_KEPT_FLAGS) != T_ZOMBIE) {
7175 fprintf(stderr, "verify_internal_consistency_i: T_ZOMBIE has extra flags set: %s\n",
7176 rb_obj_info(obj));
7177 data->err_count++;
7178 }
7179
7180 if (!!FL_TEST(obj, FL_FINALIZE) != !!st_is_member(finalizer_table, obj)) {
7181 fprintf(stderr, "verify_internal_consistency_i: FL_FINALIZE %s but %s finalizer_table: %s\n",
7182 FL_TEST(obj, FL_FINALIZE) ? "set" : "not set", st_is_member(finalizer_table, obj) ? "in" : "not in",
7183 rb_obj_info(obj));
7184 data->err_count++;
7185 }
7186 }
7187 }
7188 }
7189 }
7190
7191 return 0;
7192}
7193
7194static int
7195gc_verify_heap_page(rb_objspace_t *objspace, struct heap_page *page, VALUE obj)
7196{
7197 unsigned int has_remembered_shady = FALSE;
7198 unsigned int has_remembered_old = FALSE;
7199 int remembered_old_objects = 0;
7200 int free_objects = 0;
7201 int zombie_objects = 0;
7202
7203 short slot_size = page->slot_size;
7204 uintptr_t start = (uintptr_t)page->start;
7205 uintptr_t end = start + page->total_slots * slot_size;
7206
7207 for (uintptr_t ptr = start; ptr < end; ptr += slot_size) {
7208 VALUE val = (VALUE)ptr;
7209 asan_unpoisoning_object(val) {
7210 enum ruby_value_type type = BUILTIN_TYPE(val);
7211
7212 if (type == T_NONE) free_objects++;
7213 if (type == T_ZOMBIE) zombie_objects++;
7214 if (RVALUE_PAGE_UNCOLLECTIBLE(page, val) && RVALUE_PAGE_WB_UNPROTECTED(page, val)) {
7215 has_remembered_shady = TRUE;
7216 }
7217 if (RVALUE_PAGE_MARKING(page, val)) {
7218 has_remembered_old = TRUE;
7219 remembered_old_objects++;
7220 }
7221 }
7222 }
7223
7224 if (!is_incremental_marking(objspace) &&
7225 page->flags.has_remembered_objects == FALSE && has_remembered_old == TRUE) {
7226
7227 for (uintptr_t ptr = start; ptr < end; ptr += slot_size) {
7228 VALUE val = (VALUE)ptr;
7229 if (RVALUE_PAGE_MARKING(page, val)) {
7230 fprintf(stderr, "marking -> %s\n", rb_obj_info(val));
7231 }
7232 }
7233 rb_bug("page %p's has_remembered_objects should be false, but there are remembered old objects (%d). %s",
7234 (void *)page, remembered_old_objects, obj ? rb_obj_info(obj) : "");
7235 }
7236
7237 if (page->flags.has_uncollectible_wb_unprotected_objects == FALSE && has_remembered_shady == TRUE) {
7238 rb_bug("page %p's has_remembered_shady should be false, but there are remembered shady objects. %s",
7239 (void *)page, obj ? rb_obj_info(obj) : "");
7240 }
7241
7242 if (0) {
7243 /* free_slots may not equal to free_objects */
7244 if (page->free_slots != free_objects) {
7245 rb_bug("page %p's free_slots should be %d, but %d", (void *)page, page->free_slots, free_objects);
7246 }
7247 }
7248 if (page->final_slots != zombie_objects) {
7249 rb_bug("page %p's final_slots should be %d, but %d", (void *)page, page->final_slots, zombie_objects);
7250 }
7251
7252 return remembered_old_objects;
7253}
7254
7255static int
7256gc_verify_heap_pages_(rb_objspace_t *objspace, struct ccan_list_head *head)
7257{
7258 int remembered_old_objects = 0;
7259 struct heap_page *page = 0;
7260
7261 ccan_list_for_each(head, page, page_node) {
7262 asan_unlock_freelist(page);
7263 struct free_region *region = page->free_region;
7264 while (region) {
7265 VALUE vp = (VALUE)region;
7266 rb_asan_unpoison_object(vp, false);
7267 if (BUILTIN_TYPE(vp) != T_NONE) {
7268 fprintf(stderr, "free region head expected to be T_NONE but was: %s\n", rb_obj_info(vp));
7269 }
7270 struct free_region *next = region->next;
7271 rb_asan_poison_object(vp);
7272 region = next;
7273 }
7274 asan_lock_freelist(page);
7275
7276 if (page->flags.has_remembered_objects == FALSE) {
7277 remembered_old_objects += gc_verify_heap_page(objspace, page, Qfalse);
7278 }
7279 }
7280
7281 return remembered_old_objects;
7282}
7283
7284static int
7285gc_verify_heap_pages(rb_objspace_t *objspace)
7286{
7287 int remembered_old_objects = 0;
7288 for (int i = 0; i < HEAP_COUNT; i++) {
7289 remembered_old_objects += gc_verify_heap_pages_(objspace, &((&heaps[i])->pages));
7290 }
7291 return remembered_old_objects;
7292}
7293
7294static void
7295verify_registered_addr(VALUE *slot, VALUE initial_value, void *owner_objspace, void *d)
7296{
7297 struct verify_internal_consistency_struct *data = d;
7298 VALUE v = *slot;
7299
7300 /* Conservative registration permits uninitialized data and pre-registration
7301 * values; only a store made after registration is a violation. */
7302 if (v == initial_value) return;
7303 if (SPECIAL_CONST_P(v)) return;
7304 if (!verify_pointer_in_any_heap_p((void *)v)) return;
7305
7306 bool live = false;
7307 asan_unpoisoning_object(v) {
7308 live = BUILTIN_TYPE(v) != T_NONE && BUILTIN_TYPE(v) != T_ZOMBIE;
7309 }
7310 if (!live) return;
7311
7312 rb_objspace_t *value_objspace = GET_HEAP_OBJSPACE(v);
7313 if (value_objspace == (rb_objspace_t *)owner_objspace) return;
7314 /* Join and orphan handling move a registration to the inheritor before the
7315 * source objspace merge; a global GC scans every registry while the zombie
7316 * exists, so this is a safe transient exemption. */
7317 if (rb_gc_vm_zombie_objspace_p(value_objspace)) return;
7318 if (value_objspace->flags.during_postmortem) return;
7319 if (MARKED_IN_BITMAP(GET_HEAP_SHAREABLE_BITS(v), v)) return;
7320 if (MARKED_IN_BITMAP(GET_HEAP_SHREF_BITS(v), v)) return;
7321 /* When multiple Ractors register one address, ownership by any registrant is
7322 * enough to root the value. */
7323 if (rb_gc_registered_addr_owned_by_registrant_p(slot, value_objspace)) return;
7324
7325 fprintf(stderr, "registered address %p changed since registration to an unshareable object owned by another Ractor: %s\n",
7326 (void *)slot, rb_obj_info(v));
7327 data->err_count++;
7328}
7329
7330static void
7331gc_verify_internal_consistency_(rb_objspace_t *objspace, bool world_stopped)
7332{
7333 struct verify_internal_consistency_struct data = {0};
7334
7335 data.objspace = objspace;
7336 data.world_stopped = world_stopped;
7337 gc_report(5, objspace, "gc_verify_internal_consistency: start\n");
7338
7339 /* check relations */
7340 for (size_t i = 0; i < rb_darray_size(objspace->heap_pages.sorted); i++) {
7341 struct heap_page *page = rb_darray_get(objspace->heap_pages.sorted, i);
7342 short slot_size = page->slot_size;
7343
7344 uintptr_t start = (uintptr_t)page->start;
7345 uintptr_t end = start + page->total_slots * slot_size;
7346
7347 verify_internal_consistency_i((void *)start, (void *)end, slot_size, &data);
7348 }
7349
7350 /* Check the calling Ractor's root scoping (only when verifying the current
7351 * objspace). Skipped during a global GC, which deliberately spans every Ractor's
7352 * roots and legitimately reaches foreign objects: containment does not apply. */
7353 if (!rb_gc_single_objspace_p() && objspace == rb_gc_get_objspace() &&
7354 !rb_gc_impl_during_global_gc_p(objspace)) {
7355 rb_objspace_reachable_objects_from_root(root_scope_check_i, &data);
7356 }
7357
7358 if (data.world_stopped && !global_objspace->during_absorb) {
7359 rb_gc_each_registered_addr(verify_registered_addr, &data);
7360 }
7361
7362 if (data.err_count != 0) {
7363#if RGENGC_CHECK_MODE >= 5
7364 objspace->rgengc.error_count = data.err_count;
7365 gc_marks_check(objspace, NULL, NULL);
7366 allrefs_dump(objspace);
7367#endif
7368 rb_bug("gc_verify_internal_consistency: found internal inconsistency.");
7369 }
7370
7371 /* check heap_page status */
7372 gc_verify_heap_pages(objspace);
7373
7374 /* check counters */
7375
7376 if (!is_lazy_sweeping(objspace) &&
7377 !finalizing &&
7378 !rb_gc_multi_ractor_p()) {
7379 if (objspace_live_slots(objspace) != data.live_object_count) {
7380 fprintf(stderr, "heap_pages_final_slots: %"PRIdSIZE", total_freed_objects: %"PRIdSIZE"\n",
7381 total_final_slots_count(objspace), total_freed_objects(objspace));
7382 rb_bug("inconsistent live slot number: expect %"PRIuSIZE", but %"PRIuSIZE".",
7383 objspace_live_slots(objspace), data.live_object_count);
7384 }
7385 }
7386
7387 if (!is_marking(objspace)) {
7388 if (objspace->rgengc.old_objects != data.old_object_count) {
7389 rb_bug("inconsistent old slot number: expect %"PRIuSIZE", but %"PRIuSIZE".",
7390 objspace->rgengc.old_objects, data.old_object_count);
7391 }
7392 if (objspace->rgengc.uncollectible_wb_unprotected_objects != data.remembered_shady_count) {
7393 rb_bug("inconsistent number of wb unprotected objects: expect %"PRIuSIZE", but %"PRIuSIZE".",
7394 objspace->rgengc.uncollectible_wb_unprotected_objects, data.remembered_shady_count);
7395 }
7396 }
7397
7398 if (!finalizing) {
7399 size_t list_count = 0;
7400
7401 {
7402 VALUE z = heap_pages_deferred_final;
7403 while (z) {
7404 list_count++;
7405 z = RZOMBIE(z)->next;
7406 }
7407 }
7408
7409 if (total_final_slots_count(objspace) != data.zombie_object_count ||
7410 total_final_slots_count(objspace) != list_count) {
7411
7412 rb_bug("inconsistent finalizing object count:\n"
7413 " expect %"PRIuSIZE"\n"
7414 " but %"PRIuSIZE" zombies\n"
7415 " heap_pages_deferred_final list has %"PRIuSIZE" items.",
7416 total_final_slots_count(objspace),
7417 data.zombie_object_count,
7418 list_count);
7419 }
7420 }
7421
7422 gc_report(5, objspace, "gc_verify_internal_consistency: OK\n");
7423}
7424
7425/* The `during_gc` macro expands a bare identifier to `objspace->flags.during_gc`, so a
7426 * foreign objspace's flag cannot be written directly; these helpers reach it through the
7427 * `objspace` argument. */
7428static inline unsigned int
7429gc_during_gc_get(const rb_objspace_t *objspace)
7430{
7431 return during_gc;
7432}
7433
7434static inline void
7435gc_during_gc_set(rb_objspace_t *objspace, unsigned int v)
7436{
7437 during_gc = v;
7438}
7439
7440/* Run the check with during_gc cleared in both the verified objspace and the current
7441 * Ractor's: rb_objspace_reachable_objects_from() decides on rb_gc_get_objspace(), and
7442 * under a global GC the driver verifies foreign objspaces, so the driver's during_gc
7443 * needs clearing too (a no-op when cur == objspace). */
7444static void
7445gc_verify_internal_consistency_body(rb_objspace_t *objspace, bool world_stopped)
7446{
7447 const unsigned int prev_during_gc = during_gc;
7448 during_gc = FALSE; // stop gc here
7449
7450 rb_objspace_t *const cur = rb_gc_get_objspace();
7451 const unsigned int prev_cur_during_gc = (cur != objspace) ? gc_during_gc_get(cur) : 0;
7452 if (cur != objspace) gc_during_gc_set(cur, FALSE);
7453 {
7454 gc_verify_internal_consistency_(objspace, world_stopped);
7455 }
7456 if (cur != objspace) gc_during_gc_set(cur, prev_cur_during_gc);
7457 during_gc = prev_during_gc;
7458}
7459
7460static void
7461gc_verify_internal_consistency(void *objspace_ptr)
7462{
7463 rb_objspace_t *objspace = objspace_ptr;
7464
7465 /* Called mid-GC, take neither the VM lock nor the barrier: waiting would join a
7466 * pending global barrier mid-collection (a GC must never take the VM lock) and let
7467 * the global GC sweep the heap this mark is walking. The barrier is unnecessary
7468 * anyway; the objspace is single-writer, this verify runs on its owner thread, and
7469 * the global driver that sets during_gc everywhere already holds both. */
7470 if (during_gc) {
7471 /* The world is stopped only when the global GC's driver runs this while holding
7472 * the barrier; a non-main Ractor's local GC does not stop other Ractors. */
7473 gc_verify_internal_consistency_body(objspace, rb_gc_impl_during_global_gc_p(objspace));
7474 return;
7475 }
7476
7477 unsigned int lev = RB_GC_VM_LOCK();
7478 {
7479 rb_gc_vm_barrier(); // stop other ractors
7480 gc_verify_internal_consistency_body(objspace, true); // holding the barrier, so walking every objspace is sound
7481 }
7482 RB_GC_VM_UNLOCK(lev);
7483}
7484
7485static void
7486heap_move_pooled_pages_to_free_pages(rb_heap_t *heap)
7487{
7488 if (heap->pooled_pages) {
7489 if (heap->free_pages) {
7490 struct heap_page *free_pages_tail = heap->free_pages;
7491 while (free_pages_tail->free_next) {
7492 free_pages_tail = free_pages_tail->free_next;
7493 }
7494 free_pages_tail->free_next = heap->pooled_pages;
7495 }
7496 else {
7497 heap->free_pages = heap->pooled_pages;
7498 }
7499
7500 heap->pooled_pages = NULL;
7501 }
7502}
7503
7504static int
7505gc_remember_unprotected(rb_objspace_t *objspace, VALUE obj)
7506{
7507 struct heap_page *page = GET_HEAP_PAGE(obj);
7508 bits_t *uncollectible_bits = &page->uncollectible_bits[0];
7509
7510 if (!MARKED_IN_BITMAP(uncollectible_bits, obj)) {
7511 page->flags.has_uncollectible_wb_unprotected_objects = TRUE;
7512 MARK_IN_BITMAP(uncollectible_bits, obj);
7513 /* Like RVALUE_PAGE_OLD_UNCOLLECTIBLE_SET, count it in the object's own objspace. */
7514 page->objspace->rgengc.uncollectible_wb_unprotected_objects++;
7515
7516#if RGENGC_PROFILE > 0
7517 objspace->profile.total_remembered_shady_object_count++;
7518#if RGENGC_PROFILE >= 2
7519 objspace->profile.remembered_shady_object_count_types[BUILTIN_TYPE(obj)]++;
7520#endif
7521#endif
7522 return TRUE;
7523 }
7524 else {
7525 return FALSE;
7526 }
7527}
7528
7529static inline void
7530gc_marks_wb_unprotected_objects_plane(rb_objspace_t *objspace, uintptr_t p, bits_t bits, short slot_size)
7531{
7532 if (bits) {
7533 do {
7534 if (bits & 1) {
7535 gc_report(2, objspace, "gc_marks_wb_unprotected_objects: marked shady: %s\n", rb_obj_info((VALUE)p));
7536 GC_ASSERT(RVALUE_WB_UNPROTECTED(objspace, (VALUE)p));
7537 GC_ASSERT(RVALUE_MARKED(objspace, (VALUE)p));
7538 gc_mark_children(objspace, (VALUE)p);
7539 }
7540 p += slot_size;
7541 bits >>= 1;
7542 } while (bits);
7543 }
7544}
7545
7546static void
7547gc_marks_wb_unprotected_objects(rb_objspace_t *objspace, rb_heap_t *heap)
7548{
7549 struct heap_page *page = 0;
7550
7551 ccan_list_for_each(&heap->pages, page, page_node) {
7552 bits_t *mark_bits = page->mark_bits;
7553 bits_t *wbun_bits = page->wb_unprotected_bits;
7554 uintptr_t p = page->start;
7555 short slot_size = page->slot_size;
7556 int total_slots = page->total_slots;
7557 int bitmap_plane_count = CEILDIV(total_slots, BITS_BITLENGTH);
7558 size_t j;
7559
7560 for (j=0; j<(size_t)bitmap_plane_count; j++) {
7561 bits_t bits = mark_bits[j] & wbun_bits[j];
7562 gc_marks_wb_unprotected_objects_plane(objspace, p, bits, slot_size);
7563 p += BITS_BITLENGTH * slot_size;
7564 }
7565 }
7566
7567 gc_mark_stacked_objects_all(objspace);
7568}
7569
7570void
7571rb_gc_impl_declare_weak_references(void *objspace_ptr, VALUE obj)
7572{
7574}
7575
7576bool
7577rb_gc_impl_handle_weak_references_alive_p(void *objspace_ptr, VALUE obj)
7578{
7579 rb_objspace_t *objspace = objspace_ptr;
7580
7581 /* A local GC cannot decide a foreign object's liveness, so treat it as live; its
7582 * owner or the global GC decides (a global GC's unified mark is exact). */
7583 if (gc_skip_foreign_object_p(objspace, obj)) return true;
7584
7585 bool marked = RVALUE_MARKED(objspace, obj);
7586
7587 if (marked) {
7588 rgengc_check_relation(objspace, obj);
7589 }
7590
7591 return marked;
7592}
7593
7594static void
7595gc_update_weak_references(rb_objspace_t *objspace)
7596{
7597 VALUE *obj_ptr;
7598 rb_darray_foreach(objspace->weak_references, i, obj_ptr) {
7599 gc_mark_set_parent(objspace, *obj_ptr);
7600 rb_gc_handle_weak_references(*obj_ptr);
7601 gc_mark_set_parent_invalid(objspace);
7602 }
7603
7604 size_t capa = rb_darray_capa(objspace->weak_references);
7605 size_t size = rb_darray_size(objspace->weak_references);
7606
7607 objspace->profile.weak_references_count = size;
7608
7609 rb_darray_clear(objspace->weak_references);
7610
7611 /* If the darray has capacity for more than four times the amount used, we
7612 * shrink it down to half of that capacity. */
7613 if (capa > size * 4) {
7614 rb_darray_resize_capa_without_gc(&objspace->weak_references, size * 2);
7615 }
7616}
7617
7618static void
7619gc_marks_finish(rb_objspace_t *objspace)
7620{
7621 /* finish incremental GC */
7622 if (is_incremental_marking(objspace)) {
7623 if (RGENGC_CHECK_MODE && is_mark_stack_empty(&objspace->mark_stack) == 0) {
7624 rb_bug("gc_marks_finish: mark stack is not empty (%"PRIdSIZE").",
7625 mark_stack_size(&objspace->mark_stack));
7626 }
7627
7628 mark_roots(objspace, NULL);
7629 while (gc_mark_stacked_objects_incremental(objspace, INT_MAX) == false);
7630
7631#if RGENGC_CHECK_MODE >= 2
7632 if (gc_verify_heap_pages(objspace) != 0) {
7633 rb_bug("gc_marks_finish (incremental): there are remembered old objects.");
7634 }
7635#endif
7636
7637 objspace->flags.during_incremental_marking = FALSE;
7638 /* check children of all marked wb-unprotected objects */
7639 for (int i = 0; i < HEAP_COUNT; i++) {
7640 gc_marks_wb_unprotected_objects(objspace, &heaps[i]);
7641 }
7642 }
7643
7644 /* Pin the shareable objects and shrefs ordinary marking missed: a local GC must free
7645 * neither (another objspace may hold them). Running after the full walk makes the
7646 * pin count a retention metric: an upper bound on the garbage only a global GC can
7647 * reclaim. A global GC's exact mark does not pin. (The allrefs comparison of
7648 * RGENGC_CHECK_MODE >= 4 does not model these pins; it reports false positives.)
7649 *
7650 * Running it here rather than with the other roots is also what lets an incremental
7651 * mark run while other objspaces exist. The write barrier bails out on a
7652 * cross-objspace edge, so a store made between two mark steps leaves nothing behind
7653 * but a shareable or shref bit; scanning those bitmaps after the last step picks up
7654 * every bit set during the cycle, which a scan at gc_marks_start would miss. */
7655 objspace->last_cycle_pinned = 0;
7656 if (!rb_gc_single_objspace_p() && !objspace->flags.during_global_gc) {
7657 objspace->last_cycle_pinned = 1;
7658 gc_mark_set_parent_raw(objspace, Qundef, false);
7659 for (int i = 0; i < HEAP_COUNT; i++) {
7660 pinned_roots_mark(objspace, &heaps[i]);
7661 }
7662 /* And everything they keep alive. */
7663 gc_mark_stacked_objects_all(objspace);
7664 }
7665
7666 gc_update_weak_references(objspace);
7667
7668#if RGENGC_CHECK_MODE >= 4
7669 during_gc = FALSE;
7670 gc_marks_check(objspace, gc_check_after_marks_i, "after_marks");
7671 during_gc = TRUE;
7672#endif
7673
7674 {
7675 /* Only this objspace's own Ractor allocates from it. The main objspace
7676 * keeps the VM-wide count it has used since before per-Ractor GC. */
7677 const unsigned long ractor_cnt = objspace == global_objspace->main_objspace
7678 ? rb_gc_vm_ractor_count() : 1;
7679 const unsigned long r_mul = ractor_cnt > 8 ? 8 : ractor_cnt; // upto 8
7680
7681 size_t total_slots = objspace_available_slots(objspace);
7682 size_t sweep_slots = total_slots - objspace->marked_slots; /* will be swept slots */
7683 size_t max_free_slots = (size_t)(total_slots * gc_params.heap_free_slots_max_ratio);
7684 size_t min_free_slots = (size_t)(total_slots * gc_params.heap_free_slots_min_ratio);
7685 if (min_free_slots < gc_params.heap_free_slots * r_mul) {
7686 min_free_slots = gc_params.heap_free_slots * r_mul;
7687 }
7688
7689 int full_marking = is_full_marking(objspace);
7690
7691 GC_ASSERT(objspace_available_slots(objspace) >= objspace->marked_slots);
7692
7693 /* Setup freeable slots. */
7694 size_t total_init_slots = 0;
7695 for (int i = 0; i < HEAP_COUNT; i++) {
7696 total_init_slots += (objspace_heap_init_bytes(objspace) / heaps[i].slot_size) * r_mul;
7697 }
7698
7699 if (max_free_slots < total_init_slots) {
7700 max_free_slots = total_init_slots;
7701 }
7702
7703 /* Approximate freeable pages using the average slots-per-pages across all heaps */
7704 if (sweep_slots > max_free_slots) {
7705 size_t excess_slots = sweep_slots - max_free_slots;
7706 size_t total_heap_pages = heap_eden_total_pages(objspace);
7707 heap_pages_freeable_pages = total_heap_pages > 0
7708 ? excess_slots * total_heap_pages / total_slots
7709 : 0;
7710 }
7711 else {
7712 heap_pages_freeable_pages = 0;
7713 }
7714
7715 if (objspace->heap_pages.allocatable_bytes == 0 && sweep_slots < min_free_slots) {
7716 if (!full_marking && sweep_slots < min_free_slots * 7 / 8) {
7717 if (objspace->profile.count - objspace->rgengc.last_major_gc < RVALUE_OLD_AGE) {
7718 full_marking = TRUE;
7719 }
7720 else {
7721 gc_report(1, objspace, "gc_marks_finish: next is full GC!!)\n");
7722 gc_needs_major_flags |= GPR_FLAG_MAJOR_BY_NOFREE;
7723 }
7724 }
7725
7726 if (full_marking) {
7727 heap_allocatable_bytes_expand(objspace, NULL, sweep_slots, total_slots, heaps[0].slot_size);
7728 }
7729 }
7730
7731 if (full_marking) {
7732 /* See the comment about RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR */
7733 const double r = gc_params.oldobject_limit_factor;
7734 objspace->rgengc.uncollectible_wb_unprotected_objects_limit = MAX(
7735 (size_t)(objspace->rgengc.uncollectible_wb_unprotected_objects * r),
7736 (size_t)(objspace->rgengc.old_objects * gc_params.uncollectible_wb_unprotected_objects_limit_ratio)
7737 );
7738 objspace->rgengc.old_objects_limit = (size_t)(objspace->rgengc.old_objects * r);
7739 }
7740
7741 if (objspace->rgengc.uncollectible_wb_unprotected_objects > objspace->rgengc.uncollectible_wb_unprotected_objects_limit) {
7742 gc_needs_major_flags |= GPR_FLAG_MAJOR_BY_SHADY;
7743 }
7744 if (objspace->rgengc.old_objects > objspace->rgengc.old_objects_limit) {
7745 gc_needs_major_flags |= GPR_FLAG_MAJOR_BY_OLDGEN;
7746 }
7747
7748 gc_report(1, objspace, "gc_marks_finish (marks %"PRIdSIZE" objects, "
7749 "old %"PRIdSIZE" objects, total %"PRIdSIZE" slots, "
7750 "sweep %"PRIdSIZE" slots, allocatable %"PRIdSIZE" bytes, next GC: %s)\n",
7751 objspace->marked_slots, objspace->rgengc.old_objects, objspace_available_slots(objspace), sweep_slots, objspace->heap_pages.allocatable_bytes,
7752 gc_needs_major_flags ? "major" : "minor");
7753 }
7754
7755 // TODO: refactor so we don't need to call this
7756 rb_ractor_finish_marking(is_full_marking(objspace));
7757
7759}
7760
7761static bool
7762gc_compact_heap_cursors_met_p(rb_heap_t *heap)
7763{
7764 return heap->sweeping_page == heap->compact_cursor;
7765}
7766
7767
7768static rb_heap_t *
7769gc_compact_destination_pool(rb_objspace_t *objspace, rb_heap_t *src_pool, VALUE obj)
7770{
7771 size_t obj_size = rb_gc_obj_optimal_size(obj);
7772 if (obj_size == 0) {
7773 return src_pool;
7774 }
7775
7776 GC_ASSERT(rb_gc_impl_size_allocatable_p(obj_size));
7777
7778 size_t idx = heap_idx_for_size(obj_size);
7779
7780 return &heaps[idx];
7781}
7782
7783static bool
7784gc_compact_move(rb_objspace_t *objspace, rb_heap_t *heap, VALUE src)
7785{
7786 GC_ASSERT(BUILTIN_TYPE(src) != T_MOVED);
7787 GC_ASSERT(gc_is_moveable_obj(objspace, src));
7788
7789 rb_heap_t *dest_pool = gc_compact_destination_pool(objspace, heap, src);
7790 if (gc_compact_heap_cursors_met_p(dest_pool)) {
7791 return dest_pool != heap;
7792 }
7793
7794 while (!try_move(objspace, dest_pool, dest_pool->free_pages, src)) {
7795 struct gc_sweep_context ctx = {
7796 .page = dest_pool->sweeping_page,
7797 .final_slots = 0,
7798 .freed_slots = 0,
7799 .empty_slots = 0,
7800 };
7801
7802 /* The page of src could be partially compacted, so it may contain
7803 * T_MOVED. Sweeping a page may read objects on this page, so we
7804 * need to lock the page. */
7805 lock_page_body(objspace, GET_PAGE_BODY(src));
7806 gc_sweep_page(objspace, dest_pool, &ctx);
7807 unlock_page_body(objspace, GET_PAGE_BODY(src));
7808
7809 if (dest_pool->sweeping_page->free_slots > 0) {
7810 heap_add_freepage(dest_pool, dest_pool->sweeping_page);
7811 }
7812
7813 dest_pool->sweeping_page = ccan_list_next(&dest_pool->pages, dest_pool->sweeping_page, page_node);
7814 if (gc_compact_heap_cursors_met_p(dest_pool)) {
7815 return dest_pool != heap;
7816 }
7817 }
7818
7819 return true;
7820}
7821
7822static bool
7823gc_compact_plane(rb_objspace_t *objspace, rb_heap_t *heap, uintptr_t p, bits_t bitset, struct heap_page *page)
7824{
7825 short slot_size = page->slot_size;
7826
7827 do {
7828 VALUE vp = (VALUE)p;
7829 GC_ASSERT(vp % sizeof(VALUE) == 0);
7830
7831 if (bitset & 1) {
7832 objspace->rcompactor.considered_count_table[BUILTIN_TYPE(vp)]++;
7833
7834 if (gc_is_moveable_obj(objspace, vp)) {
7835 if (!gc_compact_move(objspace, heap, vp)) {
7836 //the cursors met. bubble up
7837 return false;
7838 }
7839 }
7840 }
7841 p += slot_size;
7842 bitset >>= 1;
7843 } while (bitset);
7844
7845 return true;
7846}
7847
7848// Iterate up all the objects in page, moving them to where they want to go
7849static bool
7850gc_compact_page(rb_objspace_t *objspace, rb_heap_t *heap, struct heap_page *page)
7851{
7852 GC_ASSERT(page == heap->compact_cursor);
7853
7854 bits_t *mark_bits, *pin_bits;
7855 bits_t bitset;
7856 uintptr_t p = page->start;
7857 short slot_size = page->slot_size;
7858 int total_slots = page->total_slots;
7859 int bitmap_plane_count = CEILDIV(total_slots, BITS_BITLENGTH);
7860
7861 mark_bits = page->mark_bits;
7862 pin_bits = page->pinned_bits;
7863
7864 for (int j = 0; j < bitmap_plane_count; j++) {
7865 // objects that can be moved are marked and not pinned
7866 bitset = (mark_bits[j] & ~pin_bits[j]);
7867 if (bitset) {
7868 if (!gc_compact_plane(objspace, heap, (uintptr_t)p, bitset, page))
7869 return false;
7870 }
7871 p += BITS_BITLENGTH * slot_size;
7872 }
7873
7874 return true;
7875}
7876
7877static bool
7878gc_compact_all_compacted_p(rb_objspace_t *objspace)
7879{
7880 for (int i = 0; i < HEAP_COUNT; i++) {
7881 rb_heap_t *heap = &heaps[i];
7882
7883 if (heap->total_pages > 0 &&
7884 !gc_compact_heap_cursors_met_p(heap)) {
7885 return false;
7886 }
7887 }
7888
7889 return true;
7890}
7891
7892/* Compaction's move phase: relocate this objspace's movable objects and leave T_MOVED
7893 * forwarding behind without updating references yet. A global GC calls this for every
7894 * objspace before updating any of them (two phases), so a cross-objspace reference to a
7895 * moved object is rewritten exactly once, after all forwarding exists. */
7896static void
7897gc_compact_relocate(rb_objspace_t *objspace)
7898{
7899 gc_compact_start(objspace);
7900
7901 while (!gc_compact_all_compacted_p(objspace)) {
7902 for (int i = 0; i < HEAP_COUNT; i++) {
7903 rb_heap_t *heap = &heaps[i];
7904
7905 if (gc_compact_heap_cursors_met_p(heap)) {
7906 continue;
7907 }
7908
7909 struct heap_page *start_page = heap->compact_cursor;
7910
7911 if (!gc_compact_page(objspace, heap, start_page)) {
7912 lock_page_body(objspace, start_page->body);
7913
7914 continue;
7915 }
7916
7917 // If we get here, we've finished moving all objects on the compact_cursor page
7918 // So we can lock it and move the cursor on to the next one.
7919 lock_page_body(objspace, start_page->body);
7920 heap->compact_cursor = ccan_list_prev(&heap->pages, heap->compact_cursor, page_node);
7921 }
7922 }
7923}
7924
7925static void
7926gc_sweep_compact(rb_objspace_t *objspace)
7927{
7928 gc_compact_relocate(objspace);
7929 /* A compacting global GC defers the finish (reference update) to the second phase,
7930 * after every objspace has been relocated. */
7931 if (!global_objspace->global_gc.compacting) {
7932 gc_compact_finish(objspace);
7933 }
7934}
7935
7936static void
7937gc_marks_rest(rb_objspace_t *objspace)
7938{
7939 gc_report(1, objspace, "gc_marks_rest\n");
7940
7941 for (int i = 0; i < HEAP_COUNT; i++) {
7942 (&heaps[i])->pooled_pages = NULL;
7943 }
7944
7945 if (is_incremental_marking(objspace)) {
7946 while (gc_mark_stacked_objects_incremental(objspace, INT_MAX) == FALSE);
7947 }
7948 else {
7949 gc_mark_stacked_objects_all(objspace);
7950 }
7951
7952 gc_marks_finish(objspace);
7953}
7954
7955static bool
7956gc_marks_step(rb_objspace_t *objspace, size_t slots)
7957{
7958 bool marking_finished = false;
7959
7960 GC_ASSERT(is_marking(objspace));
7961 if (gc_mark_stacked_objects_incremental(objspace, slots)) {
7962 gc_marks_finish(objspace);
7963
7964 marking_finished = true;
7965 }
7966
7967 return marking_finished;
7968}
7969
7970static bool
7971gc_marks_continue(rb_objspace_t *objspace, rb_heap_t *heap)
7972{
7973 GC_ASSERT(dont_gc_val() == FALSE || objspace->profile.latest_gc_info & GPR_FLAG_METHOD);
7974 bool marking_finished = true;
7975
7976 gc_marking_enter(objspace);
7977
7978 if (heap->free_pages) {
7979 gc_report(2, objspace, "gc_marks_continue: has pooled pages");
7980
7981 marking_finished = gc_marks_step(objspace, objspace->rincgc.step_slots);
7982 }
7983 else {
7984 gc_report(2, objspace, "gc_marks_continue: no more pooled pages (stack depth: %"PRIdSIZE").\n",
7985 mark_stack_size(&objspace->mark_stack));
7986 heap->force_incremental_marking_finish_count++;
7987 gc_marks_rest(objspace);
7988 }
7989
7990 gc_marking_exit(objspace);
7991
7992 return marking_finished;
7993}
7994
7995/* Mark the following as roots of this objspace.
7996 * - Every shareable object: another objspace may hold the only reference, invisible to a
7997 * local GC. Marking them rather than skipping them in the sweep preserves the
7998 * generational invariants (a pinned object ages and gets promoted like any live one).
7999 * Only a global GC decides that a shareable object is dead.
8000 * - Every shref (an unshareable object referenced from a shareable one): the referring
8001 * shareable object can live in another objspace or in an in-flight message queue. The
8002 * write barrier maintains them.
8003 * Skipped while the VM has a single Ractor: a local GC is then a whole-world GC and
8004 * shareable objects may die normally. */
8005static void
8006pinned_roots_mark(rb_objspace_t *objspace, rb_heap_t *heap)
8007{
8008 struct heap_page *page = NULL;
8009
8010 /* Runs before mark_roots, so rgengc_check_relation sees a valid (absent) parent rather
8011 * than the poison left by the previous GC. */
8012 gc_mark_set_parent_raw(objspace, Qundef, false);
8013
8014 /* A local GC never frees or traverses a shareable object, and keeps its unshareable
8015 * children alive through their shref bits, so:
8016 * - a shareable object only gets its mark bit set (like an old object), which keeps
8017 * the sweep off it, and is not traversed;
8018 * - a shref is marked and traversed, like a remembered old->young target: without
8019 * that, the referring shareable object is never walked and it would look
8020 * unreachable.
8021 * Objects can become shareable between GCs, so this pass scans the bitmaps in every
8022 * mark (gc_marks_finish) instead of maintaining a pin set across the sweep. */
8023 ccan_list_for_each(&heap->pages, page, page_node) {
8024 if (!(page->flags.has_shareable_objects | page->flags.has_shref_objects)) continue;
8025
8026 uintptr_t p = page->start;
8027 short slot_size = page->slot_size;
8028 int total_slots = page->total_slots;
8029 int bitmap_plane_count = CEILDIV(total_slots, BITS_BITLENGTH);
8030
8031 for (int j = 0; j < bitmap_plane_count; j++) {
8032 bits_t sr_bits = page->shref_bits[j];
8033 /* Only the pins ordinary marking left unmarked need work here: an already
8034 * marked object (reached by traversal, or pre-marked because it is old) is a
8035 * no-op in gc_mark_set, so skip visiting it. */
8036 bits_t bitset = (page->shareable_bits[j] | sr_bits) & ~page->mark_bits[j];
8037 uintptr_t pp = p;
8038 while (bitset) {
8039 if (bitset & 1) {
8040 VALUE obj = (VALUE)pp;
8041 asan_unpoisoning_object(obj) {
8042 switch (BUILTIN_TYPE(obj)) {
8043 case T_NONE:
8044 case T_ZOMBIE:
8045 case T_MOVED:
8046 /* A dead slot (a zombie awaiting its finalizer) is not a root. */
8047 break;
8048 default:
8049 gc_report(2, objspace, "pinned_roots_mark: mark %s\n", rb_obj_info(obj));
8050 if (sr_bits & 1) {
8051 gc_mark(objspace, obj); /* shref: root + traverse */
8052 }
8053 else if (gc_mark_set(objspace, obj)) {
8054 gc_aging(objspace, obj); /* shareable: mark, no traverse */
8055 /* Pin as well when compaction runs alongside: if a shareable
8056 * object moved, the C-struct slots of other Ractors (a
8057 * port in sync, say) are not updated and go stale. */
8058 gc_pin(objspace, obj);
8059 }
8060 break;
8061 }
8062 }
8063 }
8064 pp += slot_size;
8065 bitset >>= 1;
8066 sr_bits >>= 1;
8067 }
8068 p += BITS_BITLENGTH * slot_size;
8069 }
8070 }
8071}
8072
8073static void
8074gc_marks_start(rb_objspace_t *objspace, int full_mark)
8075{
8076 /* start marking */
8077 gc_report(1, objspace, "gc_marks_start: (%s)\n", full_mark ? "full" : "minor");
8078 gc_mode_transition(objspace, gc_mode_marking);
8079
8080 if (full_mark) {
8081 size_t incremental_marking_steps = (objspace->rincgc.pooled_slots / INCREMENTAL_MARK_STEP_ALLOCATIONS) + 1;
8082 objspace->rincgc.step_slots = (objspace->marked_slots * 2) / incremental_marking_steps;
8083
8084 if (0) fprintf(stderr, "objspace->marked_slots: %"PRIdSIZE", "
8085 "objspace->rincgc.pooled_page_num: %"PRIdSIZE", "
8086 "objspace->rincgc.step_slots: %"PRIdSIZE", \n",
8087 objspace->marked_slots, objspace->rincgc.pooled_slots, objspace->rincgc.step_slots);
8088 objspace->flags.during_minor_gc = FALSE;
8089 if (ruby_enable_autocompact && rb_gc_single_objspace_p()) {
8090 objspace->flags.during_compacting |= TRUE;
8091 }
8092 objspace->profile.major_gc_count++;
8093 objspace->rgengc.uncollectible_wb_unprotected_objects = 0;
8094 objspace->rgengc.old_objects = 0;
8095 objspace->rgengc.last_major_gc = objspace->profile.count;
8096 objspace->marked_slots = 0;
8097
8098 for (int i = 0; i < HEAP_COUNT; i++) {
8099 rb_heap_t *heap = &heaps[i];
8100 gc_bitmaps_clear(objspace, heap, false);
8101 heap_move_pooled_pages_to_free_pages(heap);
8102
8103 if (objspace->flags.during_compacting) {
8104 struct heap_page *page = NULL;
8105
8106 ccan_list_for_each(&heap->pages, page, page_node) {
8107 page->pinned_slots = 0;
8108 }
8109 }
8110 }
8111 }
8112 else {
8113 objspace->flags.during_minor_gc = TRUE;
8114 objspace->marked_slots =
8115 objspace->rgengc.old_objects + objspace->rgengc.uncollectible_wb_unprotected_objects; /* uncollectible objects are marked already */
8116 objspace->profile.minor_gc_count++;
8117
8118 for (int i = 0; i < HEAP_COUNT; i++) {
8119 rgengc_rememberset_mark(objspace, &heaps[i]);
8120 }
8121 }
8122
8123 mark_roots(objspace, NULL);
8124
8125 gc_report(1, objspace, "gc_marks_start: (%s) end, stack in %"PRIdSIZE"\n",
8126 full_mark ? "full" : "minor", mark_stack_size(&objspace->mark_stack));
8127}
8128
8129static bool
8130gc_marks(rb_objspace_t *objspace, int full_mark)
8131{
8132 gc_marking_enter(objspace);
8133
8134 bool marking_finished = false;
8135
8136 /* setup marking */
8137
8138 gc_marks_start(objspace, full_mark);
8139 if (!is_incremental_marking(objspace)) {
8140 gc_marks_rest(objspace);
8141 marking_finished = true;
8142 }
8143
8144#if RGENGC_PROFILE > 0
8145 if (gc_prof_record(objspace)) {
8146 gc_profile_record *record = gc_prof_record(objspace);
8147 record->old_objects = objspace->rgengc.old_objects;
8148 }
8149#endif
8150
8151 gc_marking_exit(objspace);
8152
8153 return marking_finished;
8154}
8155
8156/* RGENGC */
8157
8158static void
8159gc_report_body(int level, rb_objspace_t *objspace, const char *fmt, ...)
8160{
8161 if (level <= RGENGC_DEBUG) {
8162 char buf[1024];
8163 FILE *out = stderr;
8164 va_list args;
8165 const char *status = " ";
8166
8167 if (during_gc) {
8168 status = is_full_marking(objspace) ? "+" : "-";
8169 }
8170 else {
8171 if (is_lazy_sweeping(objspace)) {
8172 status = "S";
8173 }
8174 if (is_incremental_marking(objspace)) {
8175 status = "M";
8176 }
8177 }
8178
8179 va_start(args, fmt);
8180 vsnprintf(buf, 1024, fmt, args);
8181 va_end(args);
8182
8183 fprintf(out, "%s|", status);
8184 fputs(buf, out);
8185 }
8186}
8187
8188/* bit operations */
8189
8190static void
8191rgengc_remembersetbits_set(rb_objspace_t *objspace, VALUE obj)
8192{
8193 struct heap_page *page = GET_HEAP_PAGE(obj);
8194 bits_t *bits = &page->remembered_bits[0];
8195
8196 /* remembered_bits writers are always serialized: the write barrier only remembers a
8197 * local a (under its Ractor's GVL) and a global GC writes from the driver alone.
8198 * Set the bit before the page flag so a page pending re-scan stays in
8199 * rememberset_mark. */
8200 _MARK_IN_BITMAP(bits, page, obj);
8201 page->flags.has_remembered_objects = TRUE;
8202}
8203
8204/* wb, etc */
8205
8206/* return FALSE if already remembered */
8207static void
8208rgengc_remember(rb_objspace_t *objspace, VALUE obj)
8209{
8210 gc_report(6, objspace, "rgengc_remember: %s %s\n", rb_obj_info(obj),
8211 RVALUE_REMEMBERED(objspace, obj) ? "was already remembered" : "is remembered now");
8212
8213 check_rvalue_consistency(objspace, obj);
8214
8215 if (RGENGC_CHECK_MODE) {
8216 if (RVALUE_WB_UNPROTECTED(objspace, obj)) rb_bug("rgengc_remember: %s is not wb protected.", rb_obj_info(obj));
8217 }
8218
8219#if RGENGC_PROFILE > 0
8220 if (!RVALUE_REMEMBERED(objspace, obj)) {
8221 if (RVALUE_WB_UNPROTECTED(objspace, obj) == 0) {
8222 objspace->profile.total_remembered_normal_object_count++;
8223#if RGENGC_PROFILE >= 2
8224 objspace->profile.remembered_normal_object_count_types[BUILTIN_TYPE(obj)]++;
8225#endif
8226 }
8227 }
8228#endif /* RGENGC_PROFILE > 0 */
8229
8230 rgengc_remembersetbits_set(objspace, obj);
8231}
8232
8233#ifndef PROFILE_REMEMBERSET_MARK
8234#define PROFILE_REMEMBERSET_MARK 0
8235#endif
8236
8237static inline void
8238rgengc_rememberset_mark_plane(rb_objspace_t *objspace, uintptr_t p, bits_t bitset, short slot_size)
8239{
8240 if (bitset) {
8241 do {
8242 if (bitset & 1) {
8243 VALUE obj = (VALUE)p;
8244 gc_report(2, objspace, "rgengc_rememberset_mark: mark %s\n", rb_obj_info(obj));
8245 GC_ASSERT(RVALUE_UNCOLLECTIBLE(objspace, obj));
8246 GC_ASSERT(RVALUE_OLD_P(objspace, obj) || RVALUE_WB_UNPROTECTED(objspace, obj));
8247
8248 gc_mark_children(objspace, obj);
8249
8251 rb_darray_append_without_gc(&objspace->weak_references, obj);
8252 }
8253 }
8254 p += slot_size;
8255 bitset >>= 1;
8256 } while (bitset);
8257 }
8258}
8259
8260static void
8261rgengc_rememberset_mark(rb_objspace_t *objspace, rb_heap_t *heap)
8262{
8263 size_t j;
8264 struct heap_page *page = 0;
8265#if PROFILE_REMEMBERSET_MARK
8266 int has_old = 0, has_shady = 0, has_both = 0, skip = 0;
8267#endif
8268 gc_report(1, objspace, "rgengc_rememberset_mark: start\n");
8269
8270 ccan_list_for_each(&heap->pages, page, page_node) {
8271 if (page->flags.has_remembered_objects | page->flags.has_uncollectible_wb_unprotected_objects) {
8272 uintptr_t p = page->start;
8273 short slot_size = page->slot_size;
8274 int total_slots = page->total_slots;
8275 int bitmap_plane_count = CEILDIV(total_slots, BITS_BITLENGTH);
8276 bits_t bitset, bits[HEAP_PAGE_BITMAP_LIMIT];
8277 bits_t *remembered_bits = page->remembered_bits;
8278 bits_t *uncollectible_bits = page->uncollectible_bits;
8279 bits_t *wb_unprotected_bits = page->wb_unprotected_bits;
8280#if PROFILE_REMEMBERSET_MARK
8281 if (page->flags.has_remembered_objects && page->flags.has_uncollectible_wb_unprotected_objects) has_both++;
8282 else if (page->flags.has_remembered_objects) has_old++;
8283 else if (page->flags.has_uncollectible_wb_unprotected_objects) has_shady++;
8284#endif
8285 /* Clear has_remembered_objects before draining the bits. A concurrent
8286 * lock-free write barrier (another Ractor remembering a shareable object on
8287 * this page) sets the bit first and the flag second, so clearing the flag first
8288 * keeps the page scheduled for re-scan even if that set interleaves. The
8289 * per-word drain is an atomic read-and-clear, so an interleaved set is not lost
8290 * (it lands in the zeroed word). */
8291 page->flags.has_remembered_objects = FALSE;
8292 for (j=0; j < (size_t)bitmap_plane_count; j++) {
8293 bits[j] = RUBY_ATOMIC_SIZE_EXCHANGE(*(volatile size_t *)&remembered_bits[j], 0)
8294 | (uncollectible_bits[j] & wb_unprotected_bits[j]);
8295 }
8296
8297 for (j=0; j < (size_t)bitmap_plane_count; j++) {
8298 bitset = bits[j];
8299 rgengc_rememberset_mark_plane(objspace, p, bitset, slot_size);
8300 p += BITS_BITLENGTH * slot_size;
8301 }
8302 }
8303#if PROFILE_REMEMBERSET_MARK
8304 else {
8305 skip++;
8306 }
8307#endif
8308 }
8309
8310#if PROFILE_REMEMBERSET_MARK
8311 fprintf(stderr, "%d\t%d\t%d\t%d\n", has_both, has_old, has_shady, skip);
8312#endif
8313 gc_report(1, objspace, "rgengc_rememberset_mark: finished\n");
8314}
8315
8316static void
8317gc_bitmaps_clear(rb_objspace_t *objspace, rb_heap_t *heap, bool clear_shref)
8318{
8319 struct heap_page *page = 0;
8320
8321 ccan_list_for_each(&heap->pages, page, page_node) {
8322 memset(&page->mark_bits[0], 0, HEAP_PAGE_BITMAP_SIZE);
8323 memset(&page->uncollectible_bits[0], 0, HEAP_PAGE_BITMAP_SIZE);
8324 memset(&page->marking_bits[0], 0, HEAP_PAGE_BITMAP_SIZE);
8325 /* A plain memset can lose a concurrent remember, but only a shareable object can
8326 * be remembered from another Ractor's thread, and pinned_roots_mark re-marks
8327 * those every local cycle, and this clear precedes a major that re-scans all. */
8328 memset(&page->remembered_bits[0], 0, HEAP_PAGE_BITMAP_SIZE);
8329 memset(&page->pinned_bits[0], 0, HEAP_PAGE_BITMAP_SIZE);
8330 page->flags.has_uncollectible_wb_unprotected_objects = FALSE;
8331 page->flags.has_remembered_objects = FALSE;
8332 /* A shref is a local GC's root, so only a stop-the-world global GC may clear them:
8333 * its unified mark re-derives them from every shareable -> unshareable edge. */
8334 if (clear_shref) {
8335 memset(&page->shref_bits[0], 0, HEAP_PAGE_BITMAP_SIZE);
8336 page->flags.has_shref_objects = FALSE;
8337 }
8338 }
8339}
8340
8341/* RGENGC: APIs */
8342
8343NOINLINE(static void gc_writebarrier_generational(VALUE a, VALUE b, rb_objspace_t *objspace));
8344
8345/* Precondition: `a` and `b` live in `objspace`. */
8346static void
8347gc_writebarrier_generational(VALUE a, VALUE b, rb_objspace_t *objspace)
8348{
8349 if (RGENGC_CHECK_MODE) {
8350 if (!RVALUE_OLD_P(objspace, a)) rb_bug("gc_writebarrier_generational: %s is not an old object.", rb_obj_info(a));
8351 if ( RVALUE_OLD_P(objspace, b)) rb_bug("gc_writebarrier_generational: %s is an old object.", rb_obj_info(b));
8352 if (is_incremental_marking(objspace)) rb_bug("gc_writebarrier_generational: called while incremental marking: %s -> %s", rb_obj_info(a), rb_obj_info(b));
8353 }
8354
8355 /* Mark and remember a (the default behaviour).
8356 * No lock: setting a remembered bit is atomic (rgengc_remembersetbits_set), and that is
8357 * the only place a concurrent local GC or another Ractor's write barrier can race. */
8358 if (!RVALUE_REMEMBERED(objspace, a)) {
8359 rgengc_remember(objspace, a);
8360
8361 gc_report(1, objspace, "gc_writebarrier_generational: %s (remembered) -> %s\n", rb_obj_info(a), rb_obj_info(b));
8362 }
8363
8364 check_rvalue_consistency(objspace, a);
8365 check_rvalue_consistency(objspace, b);
8366}
8367
8368static void
8369gc_mark_from(rb_objspace_t *objspace, VALUE obj, VALUE parent)
8370{
8371 gc_mark_set_parent(objspace, parent);
8372 rgengc_check_relation(objspace, obj);
8373 if (gc_mark_set(objspace, obj) != FALSE) {
8374 gc_aging(objspace, obj);
8375 gc_grey(objspace, obj);
8376 }
8377 gc_mark_set_parent_invalid(objspace);
8378}
8379
8380NOINLINE(static void gc_writebarrier_incremental(VALUE a, VALUE b, rb_objspace_t *objspace));
8381
8382/* Precondition: `a` and `b` live in `objspace`. */
8383static void
8384gc_writebarrier_incremental(VALUE a, VALUE b, rb_objspace_t *objspace)
8385{
8386 gc_report(2, objspace, "gc_writebarrier_incremental: [LG] %p -> %s\n", (void *)a, rb_obj_info(b));
8387
8388 if (RVALUE_BLACK_P(objspace, a)) {
8389 if (RVALUE_WHITE_P(objspace, b)) {
8390 if (!RVALUE_WB_UNPROTECTED(objspace, a)) {
8391 gc_report(2, objspace, "gc_writebarrier_incremental: [IN] %p -> %s\n", (void *)a, rb_obj_info(b));
8392 gc_mark_from(objspace, b, a);
8393 }
8394 }
8395 else if (RVALUE_OLD_P(objspace, a) && !RVALUE_OLD_P(objspace, b)) {
8396 rgengc_remember(objspace, a);
8397 }
8398
8399 if (RB_UNLIKELY(objspace->flags.during_compacting)) {
8400 MARK_IN_BITMAP(GET_HEAP_PINNED_BITS(b), b);
8401 }
8402 }
8403}
8404
8405void
8406rb_gc_impl_writebarrier(void *objspace_ptr, VALUE a, VALUE b)
8407{
8408 rb_objspace_t *objspace = objspace_ptr;
8409
8410#if RGENGC_CHECK_MODE
8411 if (SPECIAL_CONST_P(a)) rb_bug("rb_gc_writebarrier: a is special const: %"PRIxVALUE, a);
8412 if (SPECIAL_CONST_P(b)) rb_bug("rb_gc_writebarrier: b is special const: %"PRIxVALUE, b);
8413#else
8416#endif
8417
8418 GC_ASSERT(!during_gc);
8419 GC_ASSERT(RB_BUILTIN_TYPE(a) != T_NONE);
8420 GC_ASSERT(RB_BUILTIN_TYPE(a) != T_MOVED);
8421 GC_ASSERT(RB_BUILTIN_TYPE(a) != T_ZOMBIE);
8422
8423 /* A shareable object now references an unshareable one: record b as a shref so its
8424 * owner's local GC roots it (the parent may live in another objspace, untraversed
8425 * there). Only b's owner stores this, on its own page: a plain store suffices. */
8426 if (RB_UNLIKELY(RB_FL_TEST_RAW(a, RUBY_FL_SHAREABLE)) &&
8428 struct heap_page *bpage = GET_HEAP_PAGE(b);
8429 if (!_MARKED_IN_BITMAP(bpage->shref_bits, bpage, b)) {
8430 _MARK_IN_BITMAP(bpage->shref_bits, bpage, b);
8431 bpage->flags.has_shref_objects = TRUE;
8432 }
8433 }
8434
8435 if (!is_incremental_marking(objspace)) {
8436 /* The generational barrier covers old->young edges within one objspace only.
8437 * NOTE: we shouldn't even check the age of `a` or `b` if they are in another
8438 * objspace, so check locality first. The test is rb_gc_ever_multi_ractor_p, not
8439 * rb_gc_multi_ractor_p: a foreign objspace exists before the process is
8440 * multi-Ractor (rb_gc_objspace_alloc runs while the creator is still the only
8441 * Ractor) and outlives the return to one (a fork parks the others in
8442 * zombie_objspaces). */
8443 if ((rb_gc_ever_multi_ractor_p() &&
8444 (GET_HEAP_OBJSPACE(a) != objspace || GET_HEAP_OBJSPACE(b) != objspace)) ||
8445 !RVALUE_OLD_P(objspace, a) || RVALUE_OLD_P(objspace, b)) {
8446 // do nothing
8447 }
8448 else {
8449 gc_writebarrier_generational(a, b, objspace);
8450 }
8451 }
8452 else {
8453 // Shareable objects from different object spaces are kept alive by shareable bits
8454 if (rb_gc_ever_multi_ractor_p() &&
8455 (GET_HEAP_OBJSPACE(a) != objspace || GET_HEAP_OBJSPACE(b) != objspace)) {
8456 // do nothing
8457 }
8458 else {
8459 gc_writebarrier_incremental(a, b, objspace);
8460 }
8461 }
8462}
8463
8464void
8465rb_gc_impl_obj_became_shareable(void *objspace_ptr, VALUE obj)
8466{
8467 /* An object becomes shareable on its owner thread, so this page update is
8468 * single-writer. */
8469 struct heap_page *page = GET_HEAP_PAGE(obj);
8470
8471 if (_MARKED_IN_BITMAP(page->shareable_bits, page, obj)) return;
8472 gc_page_add_shareable(page, obj);
8473
8474 /* The shref bits recorded while the object was unshareable are now covered by the
8475 * shareable pin, and a shref only points at an unshareable object. The owner thread is
8476 * the only writer, so a plain clear is enough. */
8477 if (_MARKED_IN_BITMAP(page->shref_bits, page, obj)) {
8478 _CLEAR_IN_BITMAP(page->shref_bits, page, obj);
8479 // NOTE: page->has_shref_objects could become stale here (value is true even though logically false)
8480 }
8481}
8482
8483void
8484rb_gc_impl_writebarrier_unprotect(void *objspace_ptr, VALUE obj)
8485{
8486 rb_objspace_t *objspace = objspace_ptr;
8487
8488 /* A shareable object is never WB-unprotected. Keeping shrefs correct relies on every
8489 * store into s->u going through the write barrier, which keeps wb_unprotected_bits
8490 * single-writer (only the owner thread can unprotect its own unshareable objects). */
8491 GC_ASSERT(!RB_FL_TEST_RAW(obj, RUBY_FL_SHAREABLE));
8492
8493 if (RVALUE_WB_UNPROTECTED(objspace, obj)) {
8494 return;
8495 }
8496 else {
8497 gc_report(2, objspace, "rb_gc_writebarrier_unprotect: %s %s\n", rb_obj_info(obj),
8498 RVALUE_REMEMBERED(objspace, obj) ? " (already remembered)" : "");
8499
8500 /* No lock: per the assert obj is our own unshareable, so these bits
8501 * (wb_unprotected, uncollectible, age) are single-writer on an owned page, and
8502 * RVALUE_DEMOTE's remembered-bit clear is atomic against word-sharing writers. */
8503 if (RVALUE_OLD_P(objspace, obj)) {
8504 gc_report(1, objspace, "rb_gc_writebarrier_unprotect: %s\n", rb_obj_info(obj));
8505 RVALUE_DEMOTE(objspace, obj);
8506 gc_mark_set(objspace, obj);
8507 gc_remember_unprotected(objspace, obj);
8508
8509#if RGENGC_PROFILE
8510 objspace->profile.total_shade_operation_count++;
8511#if RGENGC_PROFILE >= 2
8512 objspace->profile.shade_operation_count_types[BUILTIN_TYPE(obj)]++;
8513#endif /* RGENGC_PROFILE >= 2 */
8514#endif /* RGENGC_PROFILE */
8515 }
8516 else {
8517 RVALUE_AGE_RESET(obj);
8518 }
8519
8520 RB_DEBUG_COUNTER_INC(obj_wb_unprotect);
8521 MARK_IN_BITMAP(GET_HEAP_WB_UNPROTECTED_BITS(obj), obj);
8522 }
8523}
8524
8525void
8526rb_gc_impl_copy_attributes(void *objspace_ptr, VALUE dest, VALUE obj)
8527{
8528 rb_objspace_t *objspace = objspace_ptr;
8529
8530 if (RVALUE_WB_UNPROTECTED(objspace, obj)) {
8531 rb_gc_impl_writebarrier_unprotect(objspace, dest);
8532 }
8533 rb_gc_impl_copy_finalizer(objspace, dest, obj);
8534}
8535
8536const char *
8537rb_gc_impl_active_gc_name(void)
8538{
8539 return "default";
8540}
8541
8542/* NOTE: `obj` doesn't necessarily live in `objspace_ptr`, as `objspace_ptr` is just that
8543 * of the current Ractor. */
8544void
8545rb_gc_impl_writebarrier_remember(void *objspace_ptr, VALUE obj)
8546{
8547
8548 rb_objspace_t *objspace = objspace_ptr;
8549
8550 // Shareable objects from other object spaces don't need to be put on the remembered set
8551 // and are only collected during global GC, so not while incremental marking.
8552 if (RB_LIKELY(!rb_gc_ever_multi_ractor_p() || GET_HEAP_OBJSPACE(obj) == objspace)) {
8553 gc_report(1, objspace, "rb_gc_writebarrier_remember: %s\n", rb_obj_info(obj));
8554 if (is_incremental_marking(objspace)) {
8555 if (RVALUE_BLACK_P(objspace, obj)) {
8556 gc_grey(objspace, obj);
8557 }
8558 }
8559 else if (RVALUE_OLD_P(objspace, obj)) {
8560 rgengc_remember(objspace, obj);
8561 }
8562 }
8563}
8564
8566 // Must be ID only
8567 ID ID_wb_protected, ID_age, ID_old, ID_uncollectible, ID_marking,
8568 ID_marked, ID_pinned, ID_remembered, ID_object_id, ID_shareable;
8569};
8570
8571#define RB_GC_OBJECT_METADATA_ENTRY_COUNT (sizeof(struct rb_gc_object_metadata_names) / sizeof(ID))
8572static struct rb_gc_object_metadata_entry object_metadata_entries[RB_GC_OBJECT_METADATA_ENTRY_COUNT + 1];
8573
8575rb_gc_impl_object_metadata(void *objspace_ptr, VALUE obj)
8576{
8577 rb_objspace_t *objspace = objspace_ptr;
8578 size_t n = 0;
8579 static struct rb_gc_object_metadata_names names;
8580
8581 if (!names.ID_marked) {
8582#define I(s) names.ID_##s = rb_intern(#s)
8583 I(wb_protected);
8584 I(age);
8585 I(old);
8586 I(uncollectible);
8587 I(marking);
8588 I(marked);
8589 I(pinned);
8590 I(remembered);
8591 I(object_id);
8592 I(shareable);
8593#undef I
8594 }
8595
8596#define SET_ENTRY(na, v) do { \
8597 GC_ASSERT(n <= RB_GC_OBJECT_METADATA_ENTRY_COUNT); \
8598 object_metadata_entries[n].name = names.ID_##na; \
8599 object_metadata_entries[n].val = v; \
8600 n++; \
8601} while (0)
8602
8603 if (!RVALUE_WB_UNPROTECTED(objspace, obj)) SET_ENTRY(wb_protected, Qtrue);
8604 SET_ENTRY(age, INT2FIX(RVALUE_AGE_GET(obj)));
8605 if (RVALUE_OLD_P(objspace, obj)) SET_ENTRY(old, Qtrue);
8606 if (RVALUE_UNCOLLECTIBLE(objspace, obj)) SET_ENTRY(uncollectible, Qtrue);
8607 if (RVALUE_MARKING(objspace, obj)) SET_ENTRY(marking, Qtrue);
8608 if (RVALUE_MARKED(objspace, obj)) SET_ENTRY(marked, Qtrue);
8609 if (RVALUE_PINNED(objspace, obj)) SET_ENTRY(pinned, Qtrue);
8610 if (RVALUE_REMEMBERED(objspace, obj)) SET_ENTRY(remembered, Qtrue);
8611 if (rb_obj_id_p(obj)) SET_ENTRY(object_id, rb_obj_id(obj));
8612 if (FL_TEST(obj, FL_SHAREABLE)) SET_ENTRY(shareable, Qtrue);
8613
8614 object_metadata_entries[n].name = 0;
8615 object_metadata_entries[n].val = 0;
8616#undef SET_ENTRY
8617
8618 return object_metadata_entries;
8619}
8620
8621void *
8622rb_gc_impl_ractor_cache_alloc(void *objspace_ptr, void *ractor)
8623{
8624 /* No cache needed: allocation happens in a per-Ractor objspace. */
8625 return NULL;
8626}
8627
8628void
8629rb_gc_impl_ractor_cache_free(void *objspace_ptr, void *cache)
8630{
8631 GC_ASSERT(cache == NULL);
8632}
8633
8634/* The terminating Ractor's final local GC, on its own thread: roots are minimal, so the
8635 * mark is tiny, and it reclaims what the joining side would otherwise inherit. Never
8636 * promotes to a global GC (that would STW on every Ractor death); empty pages go
8637 * straight back to the page pool. */
8638/* Finalize the zombies whose cleanup is pure C (a dfree, no Ruby-level finalizer);
8639 * the caller has no Ruby execution context any more, so zombies with a Ruby
8640 * finalizer stay deferred and travel to the inheritor as before. Returns whether
8641 * anything was finalized (those pages then need one more sweep to detach). */
8642static bool
8643finalize_deferred_dfree_only(rb_objspace_t *objspace)
8644{
8645 VALUE dfree_only = 0;
8646 VALUE zombie = RUBY_ATOMIC_VALUE_EXCHANGE(heap_pages_deferred_final, 0);
8647 while (zombie) {
8648 rb_asan_unpoison_object(zombie, false);
8649 VALUE next = RZOMBIE(zombie)->next;
8650 if (FL_TEST_RAW(zombie, FL_FINALIZE)) {
8651 /* re-defer, with the same push as rb_gc_impl_make_zombie */
8652 VALUE prev2, next2 = heap_pages_deferred_final;
8653 do {
8654 RZOMBIE(zombie)->next = prev2 = next2;
8655 next2 = RUBY_ATOMIC_VALUE_CAS(heap_pages_deferred_final, prev2, zombie);
8656 } while (next2 != prev2);
8657 rb_asan_poison_object(zombie);
8658 }
8659 else {
8660 RZOMBIE(zombie)->next = dfree_only;
8661 dfree_only = zombie;
8662 }
8663 zombie = next;
8664 }
8665 if (dfree_only) finalize_list(objspace, dfree_only);
8666 bool did = dfree_only != 0;
8667
8668 gc_tdata_unsafe_free_publish(objspace);
8669 return did;
8670}
8671
8672void
8673rb_gc_impl_objspace_retire_gc(void *objspace_ptr)
8674{
8675 rb_objspace_t *objspace = objspace_ptr;
8676
8677 /* The dying thread's stack is already torn down here, so the root scan must skip
8678 * its machine context (rb_gc_mark_roots). */
8679 objspace->flags.during_postmortem = 1;
8680
8681 gc_rest(objspace);
8682 gc_start(objspace, GPR_FLAG_FULL_MARK | GPR_FLAG_IMMEDIATE_MARK | GPR_FLAG_IMMEDIATE_SWEEP);
8683
8684 /* The sweep above turned this heap's dead IO and the like into deferred zombies
8685 * (the per-Ractor stdio holds a page per Ractor otherwise); finalize the C-only
8686 * ones here and re-sweep the nearly-empty heap so their pages detach as empty. */
8687 if (finalize_deferred_dfree_only(objspace)) {
8688 gc_start(objspace, GPR_FLAG_FULL_MARK | GPR_FLAG_IMMEDIATE_MARK | GPR_FLAG_IMMEDIATE_SWEEP);
8689 }
8690
8691 heap_pages_freeable_pages = objspace->empty_pages_count;
8692 heap_pages_free_unused_pages(objspace);
8693
8694 objspace->flags.during_postmortem = 0;
8695}
8696
8697bool
8698rb_gc_impl_during_postmortem_p(void *objspace_ptr)
8699{
8700 rb_objspace_t *objspace = objspace_ptr;
8701 return objspace->flags.during_postmortem != 0;
8702}
8703
8704static void
8705heap_ready_to_gc(rb_objspace_t *objspace, rb_heap_t *heap)
8706{
8707 if (!heap->free_pages) {
8708 if (!heap_page_allocate_and_initialize(objspace, heap)) {
8709 objspace->heap_pages.allocatable_bytes = HEAP_PAGE_SIZE;
8710 heap_page_allocate_and_initialize(objspace, heap);
8711 }
8712 }
8713}
8714
8715static int
8716ready_to_gc(rb_objspace_t *objspace)
8717{
8718 if ((!objspace->flags.during_postmortem && rb_gc_gc_disabled_global_p()) || dont_gc_val() || during_gc) {
8719 for (int i = 0; i < HEAP_COUNT; i++) {
8720 rb_heap_t *heap = &heaps[i];
8721 heap_ready_to_gc(objspace, heap);
8722 }
8723 return FALSE;
8724 }
8725 else {
8726 return TRUE;
8727 }
8728}
8729
8730static void
8731gc_reset_malloc_info(rb_objspace_t *objspace, bool full_mark)
8732{
8733 gc_prof_set_malloc_info(objspace);
8734 {
8735 int64_t inc = gc_malloc_counters_increase(objspace, &objspace->malloc_counters.counters);
8736 gc_malloc_counters_snapshot(objspace, &objspace->malloc_counters.counters);
8737 size_t old_limit = malloc_limit;
8738
8739 /* A net-negative `inc` (more freed than malloc'd since last GC) is
8740 * treated the same as "allocated less than malloc_limit".
8741 * This matches what we were doing pre-monotonic counters, but is it right? */
8742 if (inc > 0 && (size_t)inc > malloc_limit) {
8743 malloc_limit = (size_t)((size_t)inc * gc_params.malloc_limit_growth_factor);
8744 if (malloc_limit > gc_params.malloc_limit_max) {
8745 malloc_limit = gc_params.malloc_limit_max;
8746 }
8747 }
8748 else {
8749 malloc_limit = (size_t)(malloc_limit * 0.98); /* magic number */
8750 if (malloc_limit < gc_params.malloc_limit_min) {
8751 malloc_limit = gc_params.malloc_limit_min;
8752 }
8753 }
8754
8755 if (0) {
8756 if (old_limit != malloc_limit) {
8757 fprintf(stderr, "[%"PRIuSIZE"] malloc_limit: %"PRIuSIZE" -> %"PRIuSIZE"\n",
8758 rb_gc_count(), old_limit, malloc_limit);
8759 }
8760 else {
8761 fprintf(stderr, "[%"PRIuSIZE"] malloc_limit: not changed (%"PRIuSIZE")\n",
8762 rb_gc_count(), malloc_limit);
8763 }
8764 }
8765 }
8766
8767 /* reset oldmalloc info */
8768#if RGENGC_ESTIMATE_OLDMALLOC
8769 if (!full_mark) {
8770 /* No full snapshot on minor GC: oldmalloc_increase accumulates across
8771 * minors and resets at major GC. (gc_sweep_finish still advances the
8772 * free baseline after every sweep.) */
8773 int64_t oldmalloc_increase = gc_malloc_counters_increase(objspace, &objspace->malloc_counters.oldcounters);
8774 if (oldmalloc_increase > 0 &&
8775 (uint64_t)oldmalloc_increase > objspace->rgengc.oldmalloc_increase_limit) {
8776 gc_needs_major_flags |= GPR_FLAG_MAJOR_BY_OLDMALLOC;
8777 objspace->rgengc.oldmalloc_increase_limit =
8778 (size_t)(objspace->rgengc.oldmalloc_increase_limit * gc_params.oldmalloc_limit_growth_factor);
8779
8780 if (objspace->rgengc.oldmalloc_increase_limit > gc_params.oldmalloc_limit_max) {
8781 objspace->rgengc.oldmalloc_increase_limit = gc_params.oldmalloc_limit_max;
8782 }
8783 }
8784
8785 if (0) fprintf(stderr, "%"PRIdSIZE"\t%d\t%"PRId64"\t%"PRIuSIZE"\t%"PRIdSIZE"\n",
8786 rb_gc_count(),
8787 gc_needs_major_flags,
8788 oldmalloc_increase,
8789 objspace->rgengc.oldmalloc_increase_limit,
8790 gc_params.oldmalloc_limit_max);
8791 }
8792 else {
8793 gc_malloc_counters_snapshot(objspace, &objspace->malloc_counters.oldcounters);
8794
8795 if ((objspace->profile.latest_gc_info & GPR_FLAG_MAJOR_BY_OLDMALLOC) == 0) {
8796 objspace->rgengc.oldmalloc_increase_limit =
8797 (size_t)(objspace->rgengc.oldmalloc_increase_limit / ((gc_params.oldmalloc_limit_growth_factor - 1)/10 + 1));
8798 if (objspace->rgengc.oldmalloc_increase_limit < gc_params.oldmalloc_limit_min) {
8799 objspace->rgengc.oldmalloc_increase_limit = gc_params.oldmalloc_limit_min;
8800 }
8801 }
8802 }
8803#endif
8804}
8805
8806/* What a collection records about itself before it runs. A global collection reports the
8807 * driver's objspace, so it comes through here too. */
8808static void
8809gc_start_record(rb_objspace_t *objspace, unsigned int reason, bool full_mark)
8810{
8811 objspace->profile.latest_gc_info = reason;
8812#if GC_PROFILE_MORE_DETAIL
8813 objspace->profile.total_allocated_objects_at_gc_start = total_allocated_objects(objspace);
8814 objspace->profile.heap_used_at_gc_start = rb_darray_size(objspace->heap_pages.sorted);
8815#endif
8816 objspace->profile.weak_references_count = 0;
8817 gc_prof_setup_new_record(objspace, reason);
8818 gc_reset_malloc_info(objspace, full_mark);
8819}
8820
8821static bool gc_start_global(rb_objspace_t *driver, unsigned int reason, bool compact, bool allow_skip);
8822
8823/* Decide whether this collection has to be global. A local GC can reclaim neither
8824 * shareable objects nor zombie objspaces, so once those grow past their limits only a
8825 * global GC makes progress. All inputs belong to this objspace. */
8826static bool
8827gc_need_global_p(rb_objspace_t *objspace)
8828{
8829 if (rb_gc_single_objspace_p()) return false;
8830 /* A Ractor's death must not stop the world, so the retire GC stays local. */
8831 if (objspace->flags.during_postmortem) return false;
8832 if (objspace->shareable_objects > objspace->shareable_objects_limit) return true;
8833 /* A zombie's garbage only a global cycle reclaims, but what survived the last one
8834 * is live data, so retrigger only once TRIGGER more pages accumulate on top of it.
8835 * Otherwise one live-heavy unjoined zombie turns every GC stop-the-world forever. */
8836 {
8837 size_t zp = rb_gc_vm_zombie_total_pages();
8838 size_t base = global_objspace->zombie_pages_survivors < zp ? global_objspace->zombie_pages_survivors : zp;
8839 if (zp - base >= ZOMBIE_PAGES_TRIGGER) return true;
8840 }
8841 return false;
8842}
8843
8844static int
8845garbage_collect(rb_objspace_t *objspace, unsigned int reason)
8846{
8847 int ret;
8848
8849#if GC_PROFILE_MORE_DETAIL
8850 objspace->profile.prepare_time = getrusage_time();
8851#endif
8852
8853 gc_rest(objspace);
8854
8855#if GC_PROFILE_MORE_DETAIL
8856 objspace->profile.prepare_time = getrusage_time() - objspace->profile.prepare_time;
8857#endif
8858
8859 ret = gc_start(objspace, reason);
8860
8861 return ret;
8862}
8863
8864static int
8865gc_start(rb_objspace_t *objspace, unsigned int reason)
8866{
8867 unsigned int do_full_mark = !!(reason & GPR_FLAG_FULL_MARK);
8868
8869 if (!rb_darray_size(objspace->heap_pages.sorted)) return TRUE; /* heap is not ready */
8870 if (!(reason & GPR_FLAG_METHOD) && !ready_to_gc(objspace)) return TRUE; /* GC is not allowed */
8871
8872 /* An explicit GC.start(global: true) never gets here: rb_gc_impl_start has already decided from
8873 * the `global` keyword, and GPR_FLAG_METHOD keeps `global: false` from being promoted back. */
8874 if (!(reason & GPR_FLAG_METHOD) && gc_need_global_p(objspace)) {
8875 /* A global GC is always a major, so autocompact applies. */
8876 if (gc_start_global(objspace, reason, ruby_enable_autocompact, true)) {
8877 return TRUE;
8878 }
8879 /* Fall through to a local GC */
8880 }
8881
8882 rb_gc_initialize_vm_context(&objspace->vm_context);
8883
8884 GC_ASSERT(gc_mode(objspace) == gc_mode_none, "gc_mode is %s\n", gc_mode_name(gc_mode(objspace)));
8885 GC_ASSERT(!is_lazy_sweeping(objspace));
8886 GC_ASSERT(!is_incremental_marking(objspace));
8887
8888 /* reason may be clobbered, later, so keep set immediate_sweep here */
8889 objspace->flags.immediate_sweep = !!(reason & GPR_FLAG_IMMEDIATE_SWEEP);
8890
8891 if (ruby_gc_stressful) {
8892 int flag = FIXNUM_P(ruby_gc_stress_mode) ? FIX2INT(ruby_gc_stress_mode) : 0;
8893
8894 if ((flag & (1 << gc_stress_no_major)) == 0) {
8895 do_full_mark = TRUE;
8896 }
8897
8898 objspace->flags.immediate_sweep = !(flag & (1<<gc_stress_no_immediate_sweep));
8899 }
8900
8901 if (gc_needs_major_flags) {
8902 reason |= gc_needs_major_flags;
8903 do_full_mark = TRUE;
8904 }
8905
8906 /* if major gc has been disabled, never do a full mark */
8907 if (!gc_config_full_mark_val) {
8908 do_full_mark = FALSE;
8909 }
8910 gc_needs_major_flags = GPR_FLAG_NONE;
8911
8912 if (do_full_mark && (reason & GPR_FLAG_MAJOR_MASK) == 0) {
8913 reason |= GPR_FLAG_MAJOR_BY_FORCE; /* GC by CAPI, METHOD, and so on. */
8914 }
8915
8916 if (objspace->flags.dont_incremental ||
8917 reason & GPR_FLAG_IMMEDIATE_MARK ||
8918 ruby_gc_stressful) {
8919 objspace->flags.during_incremental_marking = FALSE;
8920 }
8921 else {
8922 objspace->flags.during_incremental_marking = do_full_mark;
8923 }
8924
8925 /* Compaction on the local GC path (autocompact) runs only with a single objspace:
8926 * without the stop-the-world barrier, moving objects would break cross-objspace
8927 * references. With multiple objspaces GC.compact and autocompact go through the
8928 * compacting global GC instead (rb_gc_impl_start, or the promotion above). */
8929 if (do_full_mark && ruby_enable_autocompact && rb_gc_single_objspace_p()) {
8930 objspace->flags.during_compacting = TRUE;
8931#if RGENGC_CHECK_MODE
8932 objspace->rcompactor.compare_func = ruby_autocompact_compare_func;
8933#endif
8934 }
8935 else {
8936 objspace->flags.during_compacting = !!(reason & GPR_FLAG_COMPACT);
8937 if (objspace->flags.during_compacting && !rb_gc_single_objspace_p()) {
8938 // compaction is currently global GC only with more than 1 running Ractor
8939 objspace->flags.during_compacting = FALSE;
8940 }
8941 }
8942
8943 if (!GC_ENABLE_LAZY_SWEEP || objspace->flags.dont_incremental) {
8944 objspace->flags.immediate_sweep = TRUE;
8945 }
8946
8947 if (objspace->flags.immediate_sweep) reason |= GPR_FLAG_IMMEDIATE_SWEEP;
8948
8949 /* Enter after during_compacting is decided */
8950 unsigned int lock_lev;
8951 gc_enter(objspace, gc_enter_event_start, &lock_lev);
8952
8953 gc_report(1, objspace, "gc_start(reason: %x) => %u, %d, %d\n",
8954 reason,
8955 do_full_mark, !is_incremental_marking(objspace), objspace->flags.immediate_sweep);
8956
8957 RB_DEBUG_COUNTER_INC(gc_count);
8958
8959 if (reason & GPR_FLAG_MAJOR_MASK) {
8960 (void)RB_DEBUG_COUNTER_INC_IF(gc_major_nofree, reason & GPR_FLAG_MAJOR_BY_NOFREE);
8961 (void)RB_DEBUG_COUNTER_INC_IF(gc_major_oldgen, reason & GPR_FLAG_MAJOR_BY_OLDGEN);
8962 (void)RB_DEBUG_COUNTER_INC_IF(gc_major_shady, reason & GPR_FLAG_MAJOR_BY_SHADY);
8963 (void)RB_DEBUG_COUNTER_INC_IF(gc_major_force, reason & GPR_FLAG_MAJOR_BY_FORCE);
8964#if RGENGC_ESTIMATE_OLDMALLOC
8965 (void)RB_DEBUG_COUNTER_INC_IF(gc_major_oldmalloc, reason & GPR_FLAG_MAJOR_BY_OLDMALLOC);
8966#endif
8967 }
8968 else {
8969 (void)RB_DEBUG_COUNTER_INC_IF(gc_minor_newobj, reason & GPR_FLAG_NEWOBJ);
8970 (void)RB_DEBUG_COUNTER_INC_IF(gc_minor_malloc, reason & GPR_FLAG_MALLOC);
8971 (void)RB_DEBUG_COUNTER_INC_IF(gc_minor_method, reason & GPR_FLAG_METHOD);
8972 (void)RB_DEBUG_COUNTER_INC_IF(gc_minor_capi, reason & GPR_FLAG_CAPI);
8973 (void)RB_DEBUG_COUNTER_INC_IF(gc_minor_stress, reason & GPR_FLAG_STRESS);
8974 }
8975
8976 objspace->profile.count++;
8977 gc_start_record(objspace, reason, do_full_mark);
8978
8979 gc_event_hook(objspace, RUBY_INTERNAL_EVENT_GC_START);
8980
8981 GC_ASSERT(during_gc);
8982
8983 gc_prof_timer_start(objspace);
8984 {
8985 if (gc_marks(objspace, do_full_mark)) {
8986 gc_sweep(objspace);
8987 }
8988 }
8989 gc_prof_timer_stop(objspace);
8990
8991 gc_exit(objspace, gc_enter_event_start, &lock_lev);
8992
8993 /* Verify after the GC, at a real safepoint with during_gc cleared: mid-GC it would
8994 * call rb_objspace_reachable_objects_from, whose barrier VM lock would join another
8995 * Ractor's global GC barrier and let it collect on this half-collected heap. */
8996#if RGENGC_CHECK_MODE >= 2
8997 gc_verify_internal_consistency(objspace);
8998#endif
8999 return TRUE;
9000}
9001
9002static void
9003gc_rest(rb_objspace_t *objspace)
9004{
9005 if (is_incremental_marking(objspace) || is_lazy_sweeping(objspace)) {
9006 unsigned int lock_lev;
9007 gc_enter(objspace, gc_enter_event_rest, &lock_lev);
9008
9009 if (is_incremental_marking(objspace)) {
9010 gc_marking_enter(objspace);
9011 gc_marks_rest(objspace);
9012 gc_marking_exit(objspace);
9013
9014 gc_sweep(objspace);
9015 }
9016
9017 if (is_lazy_sweeping(objspace)) {
9018 gc_sweeping_enter(objspace);
9019 gc_sweep_rest(objspace);
9020 gc_sweeping_exit(objspace);
9021 }
9022
9023 gc_exit(objspace, gc_enter_event_rest, &lock_lev);
9024
9025 if (RGENGC_CHECK_MODE >= 2) gc_verify_internal_consistency(objspace); /* after GC, see gc_start */
9026 }
9027}
9028
9031 unsigned int reason;
9032};
9033
9034static void
9035gc_current_status_fill(rb_objspace_t *objspace, char *buff)
9036{
9037 int i = 0;
9038 if (is_marking(objspace)) {
9039 buff[i++] = 'M';
9040 if (is_full_marking(objspace)) buff[i++] = 'F';
9041 if (is_incremental_marking(objspace)) buff[i++] = 'I';
9042 }
9043 else if (is_sweeping(objspace)) {
9044 buff[i++] = 'S';
9045 if (is_lazy_sweeping(objspace)) buff[i++] = 'L';
9046 }
9047 else {
9048 buff[i++] = 'N';
9049 }
9050 buff[i] = '\0';
9051}
9052
9053static const char *
9054gc_current_status(rb_objspace_t *objspace)
9055{
9056 static char buff[0x10];
9057 gc_current_status_fill(objspace, buff);
9058 return buff;
9059}
9060
9061#if PRINT_ENTER_EXIT_TICK
9062
9063static tick_t last_exit_tick;
9064static tick_t enter_tick;
9065static int enter_count = 0;
9066static char last_gc_status[0x10];
9067
9068static inline void
9069gc_record(rb_objspace_t *objspace, int direction, const char *event)
9070{
9071 if (direction == 0) { /* enter */
9072 enter_count++;
9073 enter_tick = tick();
9074 gc_current_status_fill(objspace, last_gc_status);
9075 }
9076 else { /* exit */
9077 tick_t exit_tick = tick();
9078 char current_gc_status[0x10];
9079 gc_current_status_fill(objspace, current_gc_status);
9080#if 1
9081 /* [last mutator time] [gc time] [event] */
9082 fprintf(stderr, "%"PRItick"\t%"PRItick"\t%s\t[%s->%s|%c]\n",
9083 enter_tick - last_exit_tick,
9084 exit_tick - enter_tick,
9085 event,
9086 last_gc_status, current_gc_status,
9087 (objspace->profile.latest_gc_info & GPR_FLAG_MAJOR_MASK) ? '+' : '-');
9088 last_exit_tick = exit_tick;
9089#else
9090 /* [enter_tick] [gc time] [event] */
9091 fprintf(stderr, "%"PRItick"\t%"PRItick"\t%s\t[%s->%s|%c]\n",
9092 enter_tick,
9093 exit_tick - enter_tick,
9094 event,
9095 last_gc_status, current_gc_status,
9096 (objspace->profile.latest_gc_info & GPR_FLAG_MAJOR_MASK) ? '+' : '-');
9097#endif
9098 }
9099}
9100#else /* PRINT_ENTER_EXIT_TICK */
9101static inline void
9102gc_record(rb_objspace_t *objspace, int direction, const char *event)
9103{
9104 /* null */
9105}
9106#endif /* PRINT_ENTER_EXIT_TICK */
9107
9108static const char *
9109gc_enter_event_cstr(enum gc_enter_event event)
9110{
9111 switch (event) {
9112 case gc_enter_event_start: return "start";
9113 case gc_enter_event_continue: return "continue";
9114 case gc_enter_event_rest: return "rest";
9115 case gc_enter_event_finalizer: return "finalizer";
9116 case gc_enter_event_global: return "global";
9117 case gc_enter_event_global_auto: return "global_auto";
9118 }
9119 return NULL;
9120}
9121
9122static void
9123gc_enter_count(enum gc_enter_event event)
9124{
9125 switch (event) {
9126 case gc_enter_event_start: RB_DEBUG_COUNTER_INC(gc_enter_start); break;
9127 case gc_enter_event_continue: RB_DEBUG_COUNTER_INC(gc_enter_continue); break;
9128 case gc_enter_event_rest: RB_DEBUG_COUNTER_INC(gc_enter_rest); break;
9129 case gc_enter_event_finalizer: RB_DEBUG_COUNTER_INC(gc_enter_finalizer); break;
9130 case gc_enter_event_global: RB_DEBUG_COUNTER_INC(gc_enter_start); break;
9131 case gc_enter_event_global_auto: RB_DEBUG_COUNTER_INC(gc_enter_start); break;
9132 }
9133}
9134
9135static bool current_process_time(struct timespec *ts);
9136
9137/* A gc phase must be timed on the collecting thread's own cpu. A local gc runs
9138 * while the other ractors keep going, and process cpu time counts their work as
9139 * gc: with eight busy ractors the same ten collections were reported as 131ms
9140 * instead of 3ms, more than the wall clock they ran in. The kernel also answers
9141 * this one without walking every thread in the process. */
9142static bool
9143current_thread_time(struct timespec *ts)
9144{
9145#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_THREAD_CPUTIME_ID)
9146 {
9147 static int try_clock_gettime = 1;
9148 if (try_clock_gettime) {
9149 if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, ts) == 0) {
9150 return true;
9151 }
9152 else {
9153 try_clock_gettime = 0;
9154 }
9155 }
9156 }
9157#endif
9158 return current_process_time(ts);
9159}
9160
9161static void
9162gc_clock_start(struct timespec *ts)
9163{
9164 if (!current_thread_time(ts)) {
9165 ts->tv_sec = 0;
9166 ts->tv_nsec = 0;
9167 }
9168}
9169
9170static unsigned long long
9171gc_clock_end(struct timespec *ts)
9172{
9173 struct timespec end_time;
9174
9175 if ((ts->tv_sec > 0 || ts->tv_nsec > 0) &&
9176 current_thread_time(&end_time) &&
9177 end_time.tv_sec >= ts->tv_sec) {
9178 return (unsigned long long)(end_time.tv_sec - ts->tv_sec) * (1000 * 1000 * 1000) +
9179 (end_time.tv_nsec - ts->tv_nsec);
9180 }
9181
9182 return 0;
9183}
9184
9185static void
9186gc_process_stat_after_fork_i(void *objspace_ptr, void *data)
9187{
9188 rb_objspace_t *objspace = objspace_ptr;
9189 rb_native_mutex_initialize(&objspace->process_stat.lock);
9190}
9191
9192static inline bool
9193gc_local_gc_holds_vm_lock(void)
9194{
9195 return rb_gc_single_objspace_p();
9196}
9197
9198static inline bool
9199gc_enter(rb_objspace_t *objspace, enum gc_enter_event event, unsigned int *lock_lev)
9200{
9201 /*
9202 * NOTE: The GC must never take the barrier VM lock from inside itself: the waiter could
9203 * join a pending barrier mid-collection and expose its half-collected heap to the global
9204 * GC. A no-barrier lock is safe. Other shared structures the GC paths touch use their own
9205 * native mutexes or the page-pool lock. */
9206 *lock_lev = 0;
9207
9208 RUBY_DTRACE_GC_HOOK(ENTER, event);
9209
9210 if (objspace->profile.run) {
9211 switch (event) {
9212 case gc_enter_event_start:
9213 case gc_enter_event_continue:
9214 case gc_enter_event_rest:
9215 case gc_enter_event_global:
9216 case gc_enter_event_global_auto:
9217 /* A global GC is the longest pause the process takes, so it is the last thing
9218 * the profiler may leave unmeasured. The switch below stops the world for it,
9219 * which is exactly the interval gc_stop_time is meant to name, so start the
9220 * clock here like a local collection does. */
9221 objspace->profile.gc_pause_start_time = rb_hrtime_now();
9222 break;
9223 case gc_enter_event_finalizer:
9224 break;
9225 }
9226 }
9227 switch (event) {
9228 case gc_enter_event_global:
9229 *lock_lev = RB_GC_VM_LOCK();
9230 // stop other ractors
9231 rb_gc_vm_barrier();
9232 break;
9233 case gc_enter_event_global_auto:
9234 *lock_lev = RB_GC_VM_LOCK();
9235 if (!gc_need_global_p(objspace)) {
9236 RB_GC_VM_UNLOCK(*lock_lev);
9237 *lock_lev = 0;
9238 objspace->profile.gc_pause_start_time = 0;
9239 return false;
9240 }
9241 rb_gc_vm_barrier();
9242 break;
9243 case gc_enter_event_finalizer:
9244 /* Shutdown finalizers read VM-global tables (fstring, symbol) and free T_DATA that
9245 * is not thread-safe, so take the no-barrier VM lock. */
9246 *lock_lev = RB_GC_VM_LOCK_NO_BARRIER();
9247 break;
9248 default:
9249 objspace->flags.gc_lock_barrier = FALSE;
9250 if (objspace->flags.during_compacting) {
9251 /* Compaction relocates objects and rewrites every Ractor's JIT and global
9252 * references, so it stops the world with a barrier VM lock. rb_gc_vm_barrier is
9253 * a reentrant no-op with a single Ractor, so an inner barrier request during the
9254 * move folds into this one and gc_exit ends it. */
9255 *lock_lev = RB_GC_VM_LOCK();
9256 rb_gc_vm_barrier();
9257 objspace->flags.gc_lock_barrier = TRUE;
9258 }
9259 else if (gc_local_gc_holds_vm_lock()) {
9260 *lock_lev = RB_GC_VM_LOCK_NO_BARRIER();
9261 }
9262 break;
9263 }
9264
9265 if (objspace->profile.gc_pause_start_time) {
9266 objspace->profile.gc_stw_start_time = rb_hrtime_now();
9267 objspace->profile.gc_stop_time = rb_hrtime_sub(
9268 objspace->profile.gc_stw_start_time,
9269 objspace->profile.gc_pause_start_time);
9270 }
9271
9272 gc_enter_count(event);
9273 if (RB_UNLIKELY(during_gc != 0)) rb_bug("during_gc != 0");
9274 if (RGENGC_CHECK_MODE >= 3) gc_verify_internal_consistency(objspace);
9275
9276 during_gc = TRUE;
9277 RUBY_DEBUG_LOG("%s (%s)",gc_enter_event_cstr(event), gc_current_status(objspace));
9278 gc_report(1, objspace, "gc_enter: %s [%s]\n", gc_enter_event_cstr(event), gc_current_status(objspace));
9279 gc_record(objspace, 0, gc_enter_event_cstr(event));
9280
9281 gc_event_hook(objspace, RUBY_INTERNAL_EVENT_GC_ENTER);
9282 return true;
9283}
9284
9285static inline void
9286gc_exit(rb_objspace_t *objspace, enum gc_enter_event event, unsigned int *lock_lev)
9287{
9288 GC_ASSERT(during_gc != 0);
9289
9290 RUBY_DTRACE_GC_HOOK(EXIT, event);
9291
9292 gc_event_hook(objspace, RUBY_INTERNAL_EVENT_GC_EXIT);
9293
9294 if (objspace->profile.gc_pause_start_time) {
9295 if (gc_prof_enabled(objspace)) {
9296 rb_hrtime_t now = rb_hrtime_now();
9297 gc_profile_record *record = gc_prof_record(objspace);
9298 record->gc_pause_time = rb_hrtime_add(record->gc_pause_time,
9299 rb_hrtime_sub(now, objspace->profile.gc_pause_start_time));
9300 record->gc_stop_time = rb_hrtime_add(record->gc_stop_time,
9301 objspace->profile.gc_stop_time);
9302 record->gc_stw_time = rb_hrtime_add(record->gc_stw_time,
9303 rb_hrtime_sub(now, objspace->profile.gc_stw_start_time));
9304 }
9305 objspace->profile.gc_pause_start_time = 0;
9306 objspace->profile.gc_stw_start_time = 0;
9307 objspace->profile.gc_stop_time = 0;
9308 }
9309
9310 gc_record(objspace, 1, gc_enter_event_cstr(event));
9311 RUBY_DEBUG_LOG("%s (%s)", gc_enter_event_cstr(event), gc_current_status(objspace));
9312 gc_report(1, objspace, "gc_exit: %s [%s]\n", gc_enter_event_cstr(event), gc_current_status(objspace));
9313 during_gc = FALSE;
9314 gc_process_stat_publish(objspace);
9315
9316 switch (event) {
9317 case gc_enter_event_global:
9318 case gc_enter_event_global_auto:
9319 RB_GC_VM_UNLOCK(*lock_lev);
9320 break;
9321 case gc_enter_event_finalizer:
9322 RB_GC_VM_UNLOCK_NO_BARRIER(*lock_lev);
9323 break;
9324 default:
9325 if (*lock_lev != 0) {
9326 if (objspace->flags.gc_lock_barrier) {
9327 objspace->flags.gc_lock_barrier = FALSE;
9328 RB_GC_VM_UNLOCK(*lock_lev);
9329 }
9330 else {
9331 RB_GC_VM_UNLOCK_NO_BARRIER(*lock_lev);
9332 }
9333 }
9334 break;
9335 }
9336}
9337
9338#ifndef MEASURE_GC
9339#define MEASURE_GC (objspace->flags.measure_gc)
9340#endif
9341
9342static void
9343gc_marking_enter(rb_objspace_t *objspace)
9344{
9345 GC_ASSERT(during_gc != 0);
9346
9347 gc_prof_mark_timer_start(objspace);
9348
9349 if (gc_prof_enabled(objspace)) {
9350 objspace->profile.gc_mark_phase_wall_start_time = rb_hrtime_now();
9351 }
9352
9353 if (MEASURE_GC) {
9354 gc_clock_start(&objspace->profile.marking_start_time);
9355 }
9356
9357 rb_gc_initialize_vm_context(&objspace->vm_context);
9358}
9359
9360static void
9361gc_marking_exit(rb_objspace_t *objspace)
9362{
9363 GC_ASSERT(during_gc != 0);
9364
9365 if (MEASURE_GC) {
9366 objspace->profile.marking_time_ns += gc_clock_end(&objspace->profile.marking_start_time);
9367 }
9368
9369 if (gc_prof_enabled(objspace)) {
9370 gc_profile_record *record = gc_prof_record(objspace);
9371 record->gc_mark_wall_time = rb_hrtime_add(record->gc_mark_wall_time,
9372 elapsed_hrtime_from(objspace->profile.gc_mark_phase_wall_start_time));
9373 }
9374
9375 gc_prof_mark_timer_stop(objspace);
9376}
9377
9378static void
9379gc_sweeping_cpu_enter(rb_objspace_t *objspace)
9380{
9381 if (MEASURE_GC) {
9382 gc_clock_start(&objspace->profile.sweeping_start_time);
9383 }
9384}
9385
9386static void
9387gc_sweeping_cpu_exit(rb_objspace_t *objspace)
9388{
9389 if (MEASURE_GC) {
9390 objspace->profile.sweeping_time_ns += gc_clock_end(&objspace->profile.sweeping_start_time);
9391 }
9392}
9393
9394static void
9395gc_sweeping_enter(rb_objspace_t *objspace)
9396{
9397 GC_ASSERT(during_gc != 0);
9398
9399 if (gc_prof_enabled(objspace)) {
9400 objspace->profile.gc_sweep_phase_wall_start_time = rb_hrtime_now();
9401 objspace->profile.gc_sweep_excluded_wall_time = 0;
9402 }
9403
9404 gc_sweeping_cpu_enter(objspace);
9405
9406 rb_gc_initialize_vm_context(&objspace->vm_context);
9407}
9408
9409static void
9410gc_sweeping_exit(rb_objspace_t *objspace)
9411{
9412 GC_ASSERT(during_gc != 0);
9413
9414 gc_sweeping_cpu_exit(objspace);
9415
9416 if (gc_prof_enabled(objspace)) {
9417 rb_hrtime_t sweep_wall_time = elapsed_hrtime_from(objspace->profile.gc_sweep_phase_wall_start_time);
9418 gc_profile_record *record = gc_prof_record(objspace);
9419 sweep_wall_time = rb_hrtime_sub(sweep_wall_time,
9420 objspace->profile.gc_sweep_excluded_wall_time);
9421 record->gc_sweep_wall_time = rb_hrtime_add(record->gc_sweep_wall_time,
9422 sweep_wall_time);
9423 objspace->profile.gc_sweep_excluded_wall_time = 0;
9424 }
9425}
9426
9427static void *
9428gc_with_gvl(void *ptr)
9429{
9430 struct objspace_and_reason *oar = (struct objspace_and_reason *)ptr;
9431 return (void *)(VALUE)garbage_collect(oar->objspace, oar->reason);
9432}
9433
9434int ruby_thread_has_gvl_p(void);
9435
9436static int
9437garbage_collect_with_gvl(rb_objspace_t *objspace, unsigned int reason)
9438{
9439 if (rb_gc_gc_disabled_global_p() || dont_gc_val()) {
9440 return TRUE;
9441 }
9442 else if (!ruby_native_thread_p()) {
9443 return TRUE;
9444 }
9445 else if (!ruby_thread_has_gvl_p()) {
9446 void *ret;
9447 struct objspace_and_reason oar;
9448 oar.objspace = objspace;
9449 oar.reason = reason;
9450 ret = rb_thread_call_with_gvl(gc_with_gvl, (void *)&oar);
9451
9452 return !!ret;
9453 }
9454 else {
9455 return garbage_collect(objspace, reason);
9456 }
9457}
9458
9459static int
9460gc_set_candidate_object_i(void *vstart, void *vend, size_t stride, void *data)
9461{
9463
9464 VALUE v = (VALUE)vstart;
9465 for (; v != (VALUE)vend; v += stride) {
9466 asan_unpoisoning_object(v) {
9467 switch (BUILTIN_TYPE(v)) {
9468 case T_NONE:
9469 case T_ZOMBIE:
9470 break;
9471 default:
9472 rb_gc_prepare_heap_process_object(v);
9473 if (!RVALUE_OLD_P(objspace, v) && !RVALUE_WB_UNPROTECTED(objspace, v)) {
9474 RVALUE_AGE_SET_CANDIDATE(objspace, v);
9475 }
9476 }
9477 }
9478 }
9479
9480 return 0;
9481}
9482
9483bool
9484rb_gc_impl_multi_objspace_p(void)
9485{
9486 return true;
9487}
9488
9489bool
9490rb_gc_impl_during_global_gc_p(void *objspace_ptr)
9491{
9492 rb_objspace_t *objspace = objspace_ptr;
9493 return objspace->flags.during_global_gc != 0;
9494}
9495
9496bool
9497rb_gc_impl_obj_foreign_p(void *objspace_ptr, VALUE obj)
9498{
9499 return gc_foreign_object_p(objspace_ptr, obj);
9500}
9501
9502
9503/* Whether obj is recorded as an unshareable object referenced from a shareable one. For
9504 * the verifier: a shareable -> unshareable edge is only accepted if the write barrier
9505 * recorded it here. */
9506bool
9507rb_gc_impl_shref_marked_p(void *objspace_ptr, VALUE obj)
9508{
9509 return MARKED_IN_BITMAP(GET_HEAP_SHREF_BITS(obj), obj) != 0;
9510}
9511
9512/* The objspace's current page count (used for the zombie_objspaces page accounting). */
9513size_t
9514rb_gc_impl_heap_page_count(void *objspace_ptr)
9515{
9516 rb_objspace_t *objspace = objspace_ptr;
9517 return rb_darray_size(objspace->heap_pages.sorted);
9518}
9519
9520static void
9521gc_global_objspaces_i(void *os, void *data)
9522{
9523 if (global_objspace->global_gc.n_objspaces == global_objspace->global_gc.objspaces_capa) {
9524 size_t new_capa = global_objspace->global_gc.objspaces_capa ? global_objspace->global_gc.objspaces_capa * 2 : 16;
9525 struct rb_objspace **new_list = realloc(global_objspace->global_gc.objspaces, new_capa * sizeof(*new_list));
9526 if (new_list == NULL) rb_bug("gc_global_objspaces_i: realloc failed");
9527 global_objspace->global_gc.objspaces = new_list;
9528 global_objspace->global_gc.objspaces_capa = new_capa;
9529 }
9530 global_objspace->global_gc.objspaces[global_objspace->global_gc.n_objspaces++] = os;
9531}
9532
9533/* Re-snapshot every objspace this cycle covers, zombies included. The objspaces/capa
9534 * buffer is reused from the previous cycle. */
9535static void
9536gc_global_snapshot_objspaces(void)
9537{
9538 global_objspace->global_gc.n_objspaces = 0;
9539 rb_gc_vm_each_objspace(gc_global_objspaces_i, NULL);
9540
9541#if RGENGC_CHECK_MODE
9542 /* Check that the incrementally maintained page_index agrees with the per-objspace
9543 * sorted arrays. */
9544 size_t total = 0;
9545 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9546 total += rb_darray_size(global_objspace->global_gc.objspaces[i]->heap_pages.sorted);
9547 }
9548 GC_ASSERT(total == global_objspace->page_index.n_pages);
9549#endif
9550}
9551
9552/* Global GC: stop every Ractor and clear/mark/sweep all objspaces as one heap. It is the
9553 * only collector that can free shareable objects and decide cross-objspace reachability
9554 * precisely. */
9555/* The global GC's generic_fields weak pass, after the unified mark fixpoint, before the
9556 * sweep. Per-object rb_mark_generic_ivar is a no-op during a global GC (the driver has
9557 * GET_RACTOR() != owner); the whole table is swept here instead. Weak-KEY: mark the val
9558 * (fields_obj, a strong child) only for a live key, drain dead keys' entries. Marking a
9559 * val can make another key live, so repeat to a fixpoint. */
9562 bool progress;
9563};
9564
9565static int
9566genfields_mark_i(VALUE key, VALUE val, void *arg)
9567{
9568 struct genfields_mark_arg *a = (struct genfields_mark_arg *)arg;
9569 if (RB_SPECIAL_CONST_P(val) || !RVALUE_MARKED_BITMAP(key)) {
9570 return ST_CONTINUE;
9571 }
9572 /* Record the old(key)->young(val) edge with the host (key) as parent, even when val
9573 * is already marked: a conservative machine-stack scan can mark a fresh fields_obj
9574 * parentless before this pass, and branching on the mark bit would leave the key
9575 * unremembered, so the next minor GC misses the young val ("WB miss (O->Y)").
9576 * gc_mark runs rgengc_check_relation before its already-marked return: call always. */
9577 bool newly = !RVALUE_MARKED_BITMAP(val);
9578 gc_mark_set_parent(a->objspace, key);
9579 gc_mark(a->objspace, val);
9580 if (newly) a->progress = true;
9581 return ST_CONTINUE;
9582}
9583
9584static bool
9585genfields_dead_p(VALUE key)
9586{
9587 return RVALUE_MARKED_BITMAP(key) == 0;
9588}
9589
9590static void
9591gc_global_mark_generic_fields(rb_objspace_t *driver)
9592{
9593 struct genfields_mark_arg arg = { driver, false };
9594 do {
9595 arg.progress = false;
9596 /* Each entry's mark sets parent=key (genfields_mark_i) so the generational WB is
9597 * recorded correctly. gc_mark_stacked_objects_all sets its own per-object parent,
9598 * so restore the invalid parent (the poison contract) before calling it. */
9599 rb_gc_vm_generic_fields_mark_foreach(genfields_mark_i, &arg);
9600 gc_mark_set_parent_invalid(driver);
9601 if (arg.progress) {
9602 gc_mark_stacked_objects_all(driver);
9603 }
9604 } while (arg.progress);
9605
9606 rb_gc_vm_generic_fields_drain_dead(genfields_dead_p);
9607}
9608
9609/* Two Ractors choosing a global GC at once are serialized by the VM lock in gc_enter. If two
9610 * globals start concurrently, only one global will run and the other will run a local GC after
9611 * the barrier ends. */
9612static bool
9613gc_start_global(rb_objspace_t *driver, unsigned int reason, bool compact, bool allow_skip)
9614{
9615 unsigned int lock_lev;
9616 enum gc_enter_event event = allow_skip ? gc_enter_event_global_auto : gc_enter_event_global;
9617 if (!gc_enter(driver, event, &lock_lev)) {
9618 return false;
9619 }
9620
9621 reason |= GPR_FLAG_GLOBAL;
9622
9623 /* A global GC is a collection of the driver's objspace too, and its profile.count
9624 * below says so, so report it like a local one. The driver is the objspace whose
9625 * count moves, which is the one a hook reading GC.stat would compare against. For
9626 * the same reason it records a profile entry and reports what triggered it. */
9627 gc_start_record(driver, reason, true);
9628 gc_event_hook(driver, RUBY_INTERNAL_EVENT_GC_START);
9629 gc_prof_timer_start(driver);
9630
9631 gc_global_snapshot_objspaces();
9632
9633 /* Mark every objspace as in a global GC before step 3 settles the lazy sweeps: the
9634 * settle frees other objspaces' garbage on the driver thread, and
9635 * rb_free_generic_ivar must see "global GC in progress" to defer generic_fields
9636 * removal to the weak-pass drain. */
9637 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9638 global_objspace->global_gc.objspaces[i]->flags.during_global_gc = TRUE;
9639 }
9640
9641 /* A global GC collects every objspace, so each needs the malloc-counter reset the
9642 * driver got in gc_start_record; without it, gc_sweep_finish advancing free_at_last_gc
9643 * (step 9) would leave their malloc_increase overstated by everything swept here. */
9644 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9645 rb_objspace_t *const os = global_objspace->global_gc.objspaces[i];
9646 if (os != driver) {
9647 os->profile.latest_gc_info = reason;
9648 gc_reset_malloc_info(os, true);
9649 }
9650 }
9651
9652 /* step 3: settle every lazy sweep so the mark bits' meaning is fixed before the clear
9653 * below. (during_gc is a macro over the local "objspace".) rb_gc_get_ec() resolves
9654 * through objspace->vm_context during a GC, so initialize it for all: the driver
9655 * thread runs every objspace's phases. */
9656 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9657 rb_objspace_t *objspace = global_objspace->global_gc.objspaces[i];
9658 /* The barrier can stop a Ractor between the steps of its own incremental mark, and
9659 * nothing else finishes another objspace's mark. Drop the partial mark rather
9660 * than clear its flags in step 5 under a live gray stack: the owner would resume
9661 * with a mark stack into a heap this GC has since re-marked and swept. Nothing is
9662 * lost, the unified mark below redoes the work. */
9663 if (is_incremental_marking(objspace)) {
9664 gc_abort_incremental_marking(objspace);
9665 }
9666 GC_ASSERT(!is_incremental_marking(objspace));
9667 GC_ASSERT(is_mark_stack_empty(&objspace->mark_stack));
9668 rb_gc_initialize_vm_context(&objspace->vm_context);
9669 if (objspace != driver) during_gc = TRUE;
9670 gc_sweep_rest(objspace);
9671 }
9672
9673 /* step 5: clear every objspace's mark bits, remembered sets, generation counters and
9674 * shrefs (missing even one leaves a stale mark bit and a UAF). (heaps is a macro over
9675 * the local "objspace".) */
9676 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9677 rb_objspace_t *objspace = global_objspace->global_gc.objspaces[i];
9678 objspace->flags.during_minor_gc = FALSE;
9679 objspace->flags.during_incremental_marking = FALSE;
9680 /* The unified mark is precise and does not pin, so the per-objspace sweep below must
9681 * not re-check against a stale local cycle. */
9682 objspace->last_cycle_pinned = 0;
9683 objspace->rgengc.uncollectible_wb_unprotected_objects = 0;
9684 objspace->rgengc.old_objects = 0;
9685 objspace->rgengc.last_major_gc = objspace->profile.count;
9686 objspace->rgengc.need_major_gc = GPR_FLAG_NONE;
9687 objspace->marked_slots = 0;
9688 for (int h = 0; h < HEAP_COUNT; h++) {
9689 rb_heap_t *heap = &heaps[h];
9690 gc_bitmaps_clear(objspace, heap, true);
9691 heap_move_pooled_pages_to_free_pages(heap);
9692 }
9693 }
9694 driver->profile.major_gc_count++;
9695 global_objspace->global_gc.count++;
9696
9697 /* Enable compaction in every objspace before the mark: the unified conservative root
9698 * scan then pins machine-stack referents (gc_pin only pins while during_compacting)
9699 * and step 9's sweep relocates the rest. global_gc.compacting defers the
9700 * reference-update phase to phase 2 below (two phases, safe across objspaces). */
9701 global_objspace->global_gc.compacting = compact;
9702 if (compact) {
9703 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9704 rb_objspace_t *objspace = global_objspace->global_gc.objspaces[i];
9705 objspace->flags.during_compacting = TRUE;
9706#if RGENGC_CHECK_MODE
9707 if (ruby_enable_autocompact) {
9708 objspace->rcompactor.compare_func = ruby_autocompact_compare_func;
9709 }
9710#endif
9711 /* A global GC skips gc_marks_start, which is what resets pinned_slots for a
9712 * compacting local GC, so reset it here. step 5 cleared pinned_bits; the
9713 * conservative mark re-pins machine-stack referents. */
9714 for (int h = 0; h < HEAP_COUNT; h++) {
9715 struct heap_page *page = NULL;
9716 ccan_list_for_each(&heaps[h].pages, page, page_node) {
9717 page->pinned_slots = 0;
9718 }
9719 }
9720 }
9721 }
9722
9723 /* steps 6-7: every Ractor's roots (gc.c walks them all), then one unified precise
9724 * mark. A global GC does not go through gc_marks, so the marking
9725 * phase is opened here instead; it closes after rb_ractor_finish_marking below, which is
9726 * where gc_marks_finish ends for a local collection. */
9727 gc_marking_enter(driver);
9728
9729 mark_roots(driver, NULL);
9730 gc_mark_stacked_objects_all(driver);
9731
9732 /* Run the generic_fields weak pass after the mark fixpoint: mark the vals (fields_obj)
9733 * of live keys and drain the entries of dead ones. The per-object rb_mark_generic_ivar
9734 * is a no-op during a global GC, so this is the only path that marks generic_fields. */
9735 gc_global_mark_generic_fields(driver);
9736
9737 gc_event_hook(driver, RUBY_INTERNAL_EVENT_GC_END_MARK);
9738
9739 /* step 8 */
9740 gc_update_weak_references(driver);
9741
9742 /* This cycle's root pass over every Ractor has swept the deleted ractor-local keys out of
9743 * each storage. Free the key structs while still inside the barrier (a local GC never
9744 * can; see rb_ractor_finish_marking). */
9745 rb_ractor_finish_marking(true);
9746
9747 gc_marking_exit(driver);
9748
9749 /* step 9: sweep every objspace inside the barrier, not lazily. Dead shareable objects
9750 * are reclaimed here and emptied pages go back to the pool. */
9751 if (!compact) {
9752 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9753 rb_objspace_t *os = global_objspace->global_gc.objspaces[i];
9754 unsigned int prev_immediate = os->flags.immediate_sweep;
9755 os->flags.immediate_sweep = TRUE;
9756 gc_sweep(os);
9757 os->flags.immediate_sweep = prev_immediate;
9758 }
9759 }
9760 else {
9761 /* The move -> update-references -> free flow runs as three passes across ALL
9762 * objspaces, not per objspace: (a) updating references must see every objspace's
9763 * forwarding (a reference can point at a moved foreign object), and (b) freeing
9764 * source pages must wait until everyone is updated (or another objspace's update
9765 * reads a freed T_MOVED). The read barrier is installed once for all passes. */
9766 install_handlers();
9767
9768 /* Only the driver records a profile entry for a global GC (gc_start_record), so time
9769 * only the driver's compaction work. The move/update/free below runs inside the
9770 * driver's sweep phase (gc_sweeping_enter/exit); attribute it to GC_COMPACT_WALL_TIME
9771 * and exclude it from the driver's sweep wall time so the two do not double-count,
9772 * mirroring the compacting branch of the local gc_sweep(). */
9773 const bool driver_prof = gc_prof_enabled(driver);
9774 rb_hrtime_t driver_compact_wall_time = 0;
9775
9776 /* pass 1 (move): relocate every objspace and leave T_MOVED forwarding behind. */
9777 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9778 rb_objspace_t *os = global_objspace->global_gc.objspaces[i];
9779 gc_sweeping_enter(os);
9780 gc_sweep_start(os); /* mode -> sweeping, order the heap for compaction */
9781 rb_hrtime_t t0 = (os == driver && driver_prof) ? rb_hrtime_now() : 0;
9782 gc_compact_relocate(os); /* mode -> compacting, move */
9783 if (os == driver && driver_prof) {
9784 driver_compact_wall_time = rb_hrtime_add(driver_compact_wall_time, elapsed_hrtime_from(t0));
9785 }
9786 gc_sweeping_cpu_exit(os);
9787 }
9788
9789 /* pass 2 (update): all forwarding now exists, so update every objspace's
9790 * references (cross-objspace ones resolve too); gc_compact_finish also unprotects
9791 * pages and clears during_compacting. The move-or-mark decision reads
9792 * rb_gc_get_objspace()'s during_reference_updating: set it on every objspace. */
9793 gc_sweeping_cpu_enter(driver);
9794 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9795 global_objspace->global_gc.objspaces[i]->flags.during_reference_updating = TRUE;
9796 }
9797
9798 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9799 rb_objspace_t *objspace = global_objspace->global_gc.objspaces[i];
9800 for (int h = 0; h < HEAP_COUNT; h++) {
9801 gc_unprotect_pages(objspace, &heaps[h]);
9802 }
9803 }
9804 rb_gc_before_updating_jit_code();
9805 gc_sweeping_cpu_exit(driver);
9806 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9807 rb_objspace_t *os = global_objspace->global_gc.objspaces[i];
9808 gc_sweeping_cpu_enter(os);
9809 rb_hrtime_t t0 = (os == driver && driver_prof) ? rb_hrtime_now() : 0;
9810 gc_compact_finish(os);
9811 if (os == driver && driver_prof) {
9812 driver_compact_wall_time = rb_hrtime_add(driver_compact_wall_time, elapsed_hrtime_from(t0));
9813 }
9814 gc_sweeping_cpu_exit(os);
9815 }
9816 /* The VM-global / weak-table side of the reference update runs once (each objspace's
9817 * heap side already ran in gc_compact_finish above). */
9818 {
9819 gc_sweeping_cpu_enter(driver);
9820 rb_hrtime_t t0 = driver_prof ? rb_hrtime_now() : 0;
9821 gc_update_references_global(driver);
9822 if (driver_prof) {
9823 driver_compact_wall_time = rb_hrtime_add(driver_compact_wall_time, elapsed_hrtime_from(t0));
9824 }
9825 gc_sweeping_cpu_exit(driver);
9826 }
9827 gc_sweeping_cpu_enter(driver);
9828 rb_gc_after_updating_jit_code();
9829 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9830 global_objspace->global_gc.objspaces[i]->flags.during_reference_updating = FALSE;
9831 global_objspace->global_gc.objspaces[i]->flags.during_compacting = FALSE;
9832 }
9833 global_objspace->global_gc.compacting = false;
9834 uninstall_handlers();
9835 gc_sweeping_cpu_exit(driver);
9836
9837 /* Record the driver's compaction time and exclude it from the driver's sweep phase.
9838 * gc_sweeping_exit(driver) in pass 3 subtracts gc_sweep_excluded_wall_time from the
9839 * sweep wall time, so this must be set before it runs. The excluded value is a sum
9840 * of sub-intervals of the driver's sweep phase, so the subtraction cannot underflow. */
9841 if (driver_prof) {
9842 gc_profile_record *const record = gc_prof_record(driver);
9843 record->gc_compact_wall_time = rb_hrtime_add(record->gc_compact_wall_time,
9844 driver_compact_wall_time);
9845 driver->profile.gc_sweep_excluded_wall_time = rb_hrtime_add(
9846 driver->profile.gc_sweep_excluded_wall_time, driver_compact_wall_time);
9847 }
9848
9849 /* pass 3 (free): page-sweep every objspace, freeing dead objects and the source pages
9850 * that are now empty. during_compacting is already cleared, so the sweep treats
9851 * T_MOVED as usual. */
9852 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9853 rb_objspace_t *os = global_objspace->global_gc.objspaces[i];
9854 gc_sweeping_cpu_enter(os);
9855 gc_sweep_rest(os);
9856 gc_sweeping_exit(os);
9857 }
9858 }
9859 global_objspace->global_gc.compacting = false;
9860
9861 /* A global GC never calls gc_marks_finish, which budgets heap growth
9862 * (allocatable_bytes). An objspace still full after the global sweep (materializing
9863 * a large received copy, say) has no free pages, no empty pages, budget 0, and its next
9864 * allocation would hit newobj_refill's "cannot create a new page after a major GC".
9865 * Give every objspace stuck like that the growth budget gc_marks_finish would. */
9866 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9867 rb_objspace_t *objspace = global_objspace->global_gc.objspaces[i];
9868 if (objspace->heap_pages.allocatable_bytes != 0 || objspace->empty_pages_count != 0) {
9869 continue;
9870 }
9871 bool stuck = false;
9872 for (int h = 0; h < HEAP_COUNT; h++) {
9873 if (heaps[h].free_pages == NULL) { stuck = true; break; }
9874 }
9875 if (stuck) {
9876 heap_allocatable_bytes_expand(objspace, NULL, 0,
9877 objspace_available_slots(objspace), heaps[0].slot_size);
9878 }
9879 }
9880
9881 /* Recount the surviving shareable objects (the sweep already folded the dead ones out of
9882 * shareable_bits) and reset each trigger limit. */
9883 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9884 rb_objspace_t *objspace = global_objspace->global_gc.objspaces[i];
9885 size_t survivors = 0;
9886 for (int h = 0; h < HEAP_COUNT; h++) {
9887 struct heap_page *page = NULL;
9888 ccan_list_for_each(&heaps[h].pages, page, page_node) {
9889 if (!page->flags.has_shareable_objects) continue;
9890 for (int j = 0; j < HEAP_PAGE_BITMAP_LIMIT; j++) {
9891 survivors += rb_popcount_intptr(page->shareable_bits[j]);
9892 }
9893 }
9894 }
9895 objspace->shareable_objects = survivors;
9896 size_t new_limit = (size_t)(survivors * SHAREABLE_OBJECTS_LIMIT_FACTOR);
9897 if (new_limit < SHAREABLE_OBJECTS_LIMIT_MIN) new_limit = SHAREABLE_OBJECTS_LIMIT_MIN;
9898 objspace->shareable_objects_limit = new_limit;
9899 }
9900
9901 /* Deferred non-thread-safe frees: the world is already stopped here, so reap them
9902 * without a second barrier. Uses the driver's snapshot rather than taking its own,
9903 * which step 10 below still walks. */
9904 gc_tdata_unsafe_drain_objspaces(global_objspace->global_gc.objspaces,
9905 global_objspace->global_gc.n_objspaces);
9906 driver->profile.count++;
9907
9908 /* step 10 */
9909 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9910 rb_objspace_t *objspace = global_objspace->global_gc.objspaces[i];
9911 objspace->flags.during_global_gc = FALSE;
9912 if (objspace != driver) {
9913 during_gc = FALSE;
9914 gc_process_stat_publish(objspace);
9915 }
9916 }
9917
9918 /* The unified mark re-established the reachability of absorbed shareable objects, so a
9919 * single objspace's local mark is trustworthy again (pinning can be skipped until the
9920 * next absorb). */
9921 rb_gc_reset_absorbed_since_global_gc();
9922
9923 /* Re-measure the zombie_objspaces table now that the garbage is gone; entries are stable
9924 * inside the barrier. Without this, the page trigger above keeps firing on the stale
9925 * numbers left when a joinable (slotted) zombie retires without any pass merging it. */
9926 rb_gc_vm_refresh_zombie_pages();
9927 global_objspace->zombie_pages_survivors = rb_gc_vm_zombie_total_pages();
9928
9929 /* If the sweep above collected an unjoined Ractor object, ractor_free disowned its
9930 * zombie_objspaces entry and posted the merge to main as a postponed job; the objspace
9931 * stays enumerable until main absorbs it at its next safepoint. */
9932
9933 gc_prof_timer_stop(driver);
9934 gc_exit(driver, event, &lock_lev);
9935 return true;
9936}
9937
9938static int
9939absorb_finalizer_i(st_data_t key, st_data_t val, st_data_t data)
9940{
9942 st_insert(finalizer_table, key, val);
9943 return ST_CONTINUE;
9944}
9945
9946static void
9947gc_make_mid_mark_objspace_absorbable(rb_objspace_t *src)
9948{
9949 rb_objspace_t *objspace = src;
9950 for (int h = 0; h < HEAP_COUNT; h++) {
9951 struct heap_page *page = NULL;
9952 ccan_list_for_each(&heaps[h].pages, page, page_node) {
9953 short stride = page->slot_size;
9954 uintptr_t p = (uintptr_t)page->start;
9955 uintptr_t pend = p + page->total_slots * stride;
9956
9957 for (; p < pend; p += stride) {
9958 VALUE vp = (VALUE)p;
9959 asan_unpoisoning_object(vp) {
9960 switch (RB_BUILTIN_TYPE(vp)) {
9961 case T_NONE:
9962 case T_ZOMBIE:
9963 break;
9964 default:
9965 RVALUE_AGE_RESET(vp);
9966 break;
9967 }
9968 }
9969 }
9970 }
9971 gc_bitmaps_clear(objspace, &heaps[h], false);
9972 }
9973 objspace->rgengc.old_objects = 0;
9974 objspace->rgengc.uncollectible_wb_unprotected_objects = 0;
9975 objspace->marked_slots = 0;
9976}
9977
9978/* Merge a dead Ractor's objspace into dst under the VM lock. src has no owner thread and
9979 * dst is the calling thread's own objspace (join/value) or main with everyone stopped
9980 * (global GC), so single-writer holds throughout. Pages move whole (their bits describe
9981 * objects, not the objspace), and dst's next collection is forced full to rebuild the
9982 * generational state. */
9983static void
9984objspace_absorb(rb_objspace_t *dst, rb_objspace_t *src)
9985{
9986 GC_ASSERT(dst != src);
9987
9988 /* Suppress the cross-objspace verifier checks while the graph is in flux (see
9989 * global_objspace->during_absorb). */
9990 const bool prev_absorb = global_objspace->during_absorb;
9991 global_objspace->during_absorb = true;
9992
9993 /* Settle dst first: adding pages under a walking lazy-sweep cursor, or into a
9994 * half-marked incremental heap, would sweep the merged pages with src's stale mark
9995 * bits and free live objects. */
9996 gc_rest(dst);
9997
9998 /* Settle src: no incremental marking, lazy sweep or in-progress allocation page. */
9999 {
10000 rb_objspace_t *objspace = src;
10001 /* A zombie parked by fork (rb_ractor_terminate_atfork retires the objspace
10002 * without the retire GC) can be mid-incremental-mark, and that mark cannot be
10003 * finished. Its owner's threads did not survive the fork, so mark_roots would
10004 * scan the absorbing Ractor's roots instead of src's. Drop the cycle. */
10005 if (is_incremental_marking(objspace)) {
10006 gc_abort_incremental_marking(objspace);
10007 gc_make_mid_mark_objspace_absorbable(objspace);
10008 }
10009 gc_rest(src); // if mid-sweep
10010 heap_alloc_state_clear(objspace);
10011 /* gc_sweep_finish leaves swept pages "pooled" for a coming incremental mark; src
10012 * never runs one (it is about to be merged), so return them to its free list now,
10013 * restoring the pooled_pages == NULL the page merge below assumes (mirrors
10014 * gc_start_global step 3). */
10015 for (int h = 0; h < HEAP_COUNT; h++) {
10016 heap_move_pooled_pages_to_free_pages(&heaps[h]);
10017 }
10018 }
10019
10020 /* From here the merge must not run dst's GC: the finalizer st_insert below can cross
10021 * the malloc-accounting threshold, and a GC then would sweep src's detached finalizer
10022 * procs, reachable only from this C frame, into dangling VALUEs. Page/darray moves
10023 * allocate nothing (_without_gc), so disabling costs nothing and makes the splice
10024 * atomic. (The two settles above deliberately collect: they stay outside.) */
10025 const bool dst_gc_was_enabled = rb_gc_impl_gc_enabled_p(dst);
10026 if (dst_gc_was_enabled) rb_gc_impl_gc_disable(dst, false);
10027
10028 /* Hand over the pages size pool by size pool. ("heaps" is a macro over the local
10029 * objspace, so the arrays are taken through scoped locals.) */
10030 rb_heap_t *dst_heaps;
10031 rb_heap_t *src_heaps;
10032 {
10033 rb_objspace_t *objspace = dst;
10034 dst_heaps = heaps;
10035 }
10036 {
10037 rb_objspace_t *objspace = src;
10038 src_heaps = heaps;
10039 }
10040 for (int h = 0; h < HEAP_COUNT; h++) {
10041 rb_heap_t *dheap = &dst_heaps[h];
10042 rb_heap_t *sheap = &src_heaps[h];
10043 struct heap_page *page = NULL;
10044
10045 GC_ASSERT(sheap->sweeping_page == NULL);
10046 GC_ASSERT(sheap->pooled_pages == NULL);
10047
10048 ccan_list_for_each(&sheap->pages, page, page_node) {
10049 page->objspace = dst;
10050 page->heap = dheap;
10051 }
10052 ccan_list_append_list(&dheap->pages, &sheap->pages);
10053
10054 /* Append the free-page chain to the tail. */
10055 if (sheap->free_pages) {
10056 struct heap_page **tail = &dheap->free_pages;
10057 while (*tail) tail = &(*tail)->free_next;
10058 *tail = sheap->free_pages;
10059 sheap->free_pages = NULL;
10060 }
10061
10062 dheap->total_pages += sheap->total_pages;
10063 dheap->total_slots += sheap->total_slots;
10064 dheap->total_allocated_pages += sheap->total_allocated_pages;
10065 dheap->total_allocated_objects += sheap->total_allocated_objects;
10066 dheap->total_freed_objects += sheap->total_freed_objects;
10067 dheap->final_slots_count += sheap->final_slots_count;
10068 }
10069
10070 /* The objspace-wide page bookkeeping. */
10071 {
10072 rb_objspace_t *objspace = dst; /* for the heap_pages_* macros */
10073 struct heap_page *page = NULL;
10074 size_t srcn = rb_darray_size(src->heap_pages.sorted);
10075 for (size_t i = 0; i < srcn; i++) {
10076 page = rb_darray_get(src->heap_pages.sorted, i);
10077 /* Residents of the empty pool (no live objects) are returned to page_pool rather
10078 * than inherited; dst's allocation demand is cheaply met from the shared pool's
10079 * free list. */
10080 if (heap_page_in_global_empty_pages_pool(src, page)) {
10081 heap_page_free(src, page);
10082 continue;
10083 }
10084 uintptr_t body = (uintptr_t)page->body;
10085 uintptr_t start = body + sizeof(struct heap_page_header);
10086 uintptr_t end = body + HEAP_PAGE_SIZE;
10087
10088 /* Keep the array ordered by page BODY address: heap_page_for_ptr bsearches
10089 * body ranges, and a detached empty page has start == 0, so ordering by
10090 * page->start would miss live pages (a global GC would then fail to mark a
10091 * registered root and sweep it). */
10092 size_t lo = 0;
10093 size_t hi = rb_darray_size(objspace->heap_pages.sorted);
10094 while (lo < hi) {
10095 size_t mid = (lo + hi) / 2;
10096 struct heap_page *mid_page = rb_darray_get(objspace->heap_pages.sorted, mid);
10097 if ((uintptr_t)mid_page->body < body) lo = mid + 1;
10098 else hi = mid;
10099 }
10100 rb_darray_insert_without_gc(&objspace->heap_pages.sorted, hi, page);
10101
10102 if (heap_pages_lomem == 0 || heap_pages_lomem > start) heap_pages_lomem = start;
10103 if (heap_pages_himem < end) heap_pages_himem = end;
10104 }
10105 objspace->heap_pages.allocated_pages += src->heap_pages.allocated_pages;
10106 objspace->heap_pages.freed_pages += src->heap_pages.freed_pages;
10107 rb_darray_free_without_gc(src->heap_pages.sorted);
10108 src->heap_pages.sorted = NULL;
10109 /* The empty_pages chain's structs were freed in the loop above. */
10110 src->empty_pages = NULL;
10111 src->empty_pages_count = 0;
10112 }
10113
10114 /* Finalizers: move the table's entries, and the dead Ractor's deferred zombies are run
10115 * by dst's thread from now on. */
10116 {
10117 st_table *src_finalizers;
10118 {
10119 rb_objspace_t *objspace = src;
10120 src_finalizers = finalizer_table;
10121 finalizer_table = NULL;
10122 }
10123 if (src_finalizers) {
10124 rb_objspace_t *objspace = dst;
10125 if (finalizer_table == NULL) {
10126 finalizer_table = src_finalizers;
10127 }
10128 else {
10129 st_foreach(src_finalizers, absorb_finalizer_i, (st_data_t)dst);
10130 st_free_table(src_finalizers);
10131 }
10132 }
10133 }
10134 {
10135 VALUE src_deferred = RUBY_ATOMIC_VALUE_EXCHANGE(src->heap_pages.deferred_final, 0);
10136 if (src_deferred) {
10137 VALUE tail_obj = src_deferred;
10138 rb_asan_unpoison_object(tail_obj, false);
10139 while (RZOMBIE(tail_obj)->next) {
10140 VALUE next_obj = RZOMBIE(tail_obj)->next;
10141 rb_asan_poison_object(tail_obj);
10142 tail_obj = next_obj;
10143 rb_asan_unpoison_object(tail_obj, false);
10144 }
10145 VALUE prev;
10146 do {
10147 prev = dst->heap_pages.deferred_final;
10148 RZOMBIE(tail_obj)->next = prev;
10149 } while (RUBY_ATOMIC_VALUE_CAS(dst->heap_pages.deferred_final, prev, src_deferred) != prev);
10150 rb_asan_poison_object(tail_obj);
10151 /* No owner was left to run these zombies (register's owner walk misses a dead
10152 * Ractor). dst runs this merge, so schedule dst's job here; otherwise they wait
10153 * until dst's next GC. */
10154 rb_postponed_job_trigger(dst->finalize_deferred_pjob);
10155 }
10156 }
10157 if (src->tdata_unsafe_free_chunk) {
10158 gc_tdata_unsafe_free_publish(src);
10159 }
10160 if (tdata_deferred_free_count_load() >= TDATA_DEFERRED_FREE_THRESHOLD) {
10161 gc_tdata_deferred_free_trigger(dst);
10162 }
10163
10164 /* Counters inherited by dst. */
10165 dst->rgengc.old_objects += src->rgengc.old_objects;
10166 dst->rgengc.uncollectible_wb_unprotected_objects += src->rgengc.uncollectible_wb_unprotected_objects;
10167 dst->shareable_objects += src->shareable_objects;
10168
10169 /* Merged pages carry src's mark/age state, so dst rebuilds its view at the next
10170 * collection. Note that this can be worked around by calling `GC.config(rgengc_allow_full_mark: false)`,
10171 * so the absorbed heap should be in a state where a minor GC would also work correctly.
10172 */
10173 dst->rgengc.need_major_gc |= GPR_FLAG_MAJOR_BY_FORCE;
10174
10175 /* src's outstanding malloc pressure moves with the xmalloc'd buffers. Later frees are
10176 * charged to dst, so without this transfer dst underestimates its own heap and delays
10177 * GCs. dst is live, so take its counter lock where gc_counter_add is not atomic. */
10178 {
10179 int64_t inc = gc_malloc_counters_increase(src, &src->malloc_counters.counters);
10180#if RGENGC_ESTIMATE_OLDMALLOC
10181 int64_t oldinc = gc_malloc_counters_increase(src, &src->malloc_counters.oldcounters);
10182#endif
10183 MALLOC_COUNTERS_LOCK(dst);
10184 if (inc > 0) gc_counter_add(&dst->malloc_counters.counters.malloc, (size_t)inc);
10185#if RGENGC_ESTIMATE_OLDMALLOC
10186 if (oldinc > 0) gc_counter_add(&dst->malloc_counters.oldcounters.malloc, (size_t)oldinc);
10187#endif
10188 MALLOC_COUNTERS_UNLOCK(dst);
10189 }
10190
10191 {
10192 struct gc_process_stat_snapshot final_snap;
10193 gc_process_stat_capture(src, &final_snap);
10194 gc_process_stat_add(&global_objspace->process_stat_archive, &final_snap);
10195 }
10196 rb_native_mutex_destroy(&src->process_stat.lock);
10197
10198 /* Free the shell (as rb_gc_impl_objspace_free does). */
10199 free(src->profile.records);
10200 free_stack_chunks(&src->mark_stack);
10201 mark_stack_free_cache(&src->mark_stack);
10202 GC_ASSERT(rb_darray_size(src->weak_references) == 0);
10203 rb_darray_free_without_gc(src->weak_references);
10204#ifdef MALLOC_COUNTERS_NEED_LOCK
10205 rb_native_mutex_destroy(&src->malloc_counters.lock);
10206#endif
10207 free(src);
10208
10209 if (dst_gc_was_enabled) rb_gc_impl_gc_enable(dst);
10210
10211 /* Return the empty pages inheritance piled up in dst (mostly from the dead Ractor's
10212 * teardown material) to the pool with no budget. An empty page is by definition safe to
10213 * release, and re-acquiring one from the pool is cheap. */
10214 {
10215 rb_objspace_t *objspace = dst;
10216 heap_pages_freeable_pages = objspace->empty_pages_count;
10217 heap_pages_free_unused_pages(objspace);
10218 }
10219
10220 global_objspace->during_absorb = prev_absorb;
10221}
10222
10223void
10224rb_gc_impl_objspace_absorb(void *dst_ptr, void *src_ptr)
10225{
10226 objspace_absorb(dst_ptr, src_ptr);
10227}
10228
10229void
10230rb_gc_impl_start(void *objspace_ptr, bool full_mark, bool immediate_mark, bool immediate_sweep, bool compact, bool global)
10231{
10232 rb_objspace_t *objspace = objspace_ptr;
10233 unsigned int reason = (GPR_FLAG_FULL_MARK |
10234 GPR_FLAG_IMMEDIATE_MARK |
10235 GPR_FLAG_IMMEDIATE_SWEEP |
10236 GPR_FLAG_METHOD);
10237
10238 int full_marking_p = gc_config_full_mark_val;
10239 gc_config_full_mark_set(TRUE);
10240
10241 /* For now, compact implies full mark / sweep, so ignore other flags */
10242 if (compact) {
10243 GC_ASSERT(GC_COMPACTION_SUPPORTED);
10244
10245 reason |= GPR_FLAG_COMPACT;
10246 if (!rb_gc_single_objspace_p()) {
10247 global = true;
10248 }
10249 }
10250 else {
10251 if (!full_mark) reason &= ~GPR_FLAG_FULL_MARK;
10252 if (!immediate_mark) reason &= ~GPR_FLAG_IMMEDIATE_MARK;
10253 if (!immediate_sweep) reason &= ~GPR_FLAG_IMMEDIATE_SWEEP;
10254 }
10255
10256 if ((reason & (GPR_FLAG_FULL_MARK | GPR_FLAG_IMMEDIATE_MARK | GPR_FLAG_IMMEDIATE_SWEEP)) !=
10257 (GPR_FLAG_FULL_MARK | GPR_FLAG_IMMEDIATE_MARK | GPR_FLAG_IMMEDIATE_SWEEP)) {
10258 global = false;
10259 }
10260
10261 if (global && !rb_gc_single_objspace_p()) {
10262 /* A mid-cycle driver is settled by gc_start_global itself: it aborts the partial
10263 * mark and finishes the lazy sweep, so the dead slots are T_NONE before the
10264 * unified conservative root scan. */
10265 gc_start_global(objspace, reason, compact || ruby_enable_autocompact, false);
10266 }
10267 else {
10268 garbage_collect(objspace, reason);
10269 }
10270
10271 gc_finalize_deferred(objspace);
10272 /* An explicit GC.start is expected to reclaim immediately, so run the deferred non-thread-safe
10273 * frees synchronously instead of leaving them to gc_sweep_finish's postponed job. */
10274 if (tdata_deferred_free_count_load() > 0 && rb_gc_single_objspace_p()) {
10275 gc_tdata_unsafe_drain();
10276 }
10277 gc_config_full_mark_set(full_marking_p);
10278}
10279
10280void
10281rb_gc_impl_prepare_heap(void *objspace_ptr)
10282{
10283 rb_objspace_t *objspace = objspace_ptr;
10284
10285 rb_gc_impl_each_objects(objspace, gc_set_candidate_object_i, objspace_ptr);
10286
10287 double orig_max_free_slots = gc_params.heap_free_slots_max_ratio;
10288 /* Ensure that all empty pages are moved onto empty_pages. */
10289 gc_params.heap_free_slots_max_ratio = 0.0;
10290 rb_gc_impl_start(objspace, true, true, true, true, true);
10291 gc_params.heap_free_slots_max_ratio = orig_max_free_slots;
10292
10293 objspace->heap_pages.allocatable_bytes = 0;
10294 heap_pages_freeable_pages = objspace->empty_pages_count;
10295 heap_pages_free_unused_pages(objspace_ptr);
10296 GC_ASSERT(heap_pages_freeable_pages == 0);
10297 GC_ASSERT(objspace->empty_pages_count == 0);
10298
10299 // Process.warmup is meant to be called at the end of the boot sequence, which is commonly allocation
10300 // heavy and result in GC limits raising significantly, but it's not indicative of the limits needed
10301 // for runtime.
10302 // Recompute the allocatable_bytes limit based on `gc_params.heap_init_bytes`.
10303 GC_ASSERT(objspace->heap_pages.allocatable_bytes == 0);
10304 for (int i = 0; i < HEAP_COUNT; i++) {
10305 rb_heap_t *heap = &heaps[i];
10306 heap_allocatable_bytes_expand(objspace, heap, heap->empty_slots, heap->total_slots, heap->slot_size);
10307 }
10308
10309#if defined(HAVE_MALLOC_TRIM) && !defined(RUBY_ALTERNATIVE_MALLOC_HEADER)
10310 malloc_trim(0);
10311#endif
10312}
10313
10314static int
10315gc_is_moveable_obj(rb_objspace_t *objspace, VALUE obj)
10316{
10317 GC_ASSERT(!SPECIAL_CONST_P(obj));
10318
10319 switch (BUILTIN_TYPE(obj)) {
10320 case T_NONE:
10321 case T_MOVED:
10322 case T_ZOMBIE:
10323 return FALSE;
10324 case T_SYMBOL:
10325 case T_STRING:
10326 case T_OBJECT:
10327 case T_FLOAT:
10328 case T_IMEMO:
10329 case T_ARRAY:
10330 case T_BIGNUM:
10331 case T_ICLASS:
10332 case T_MODULE:
10333 case T_REGEXP:
10334 case T_DATA:
10335 case T_MATCH:
10336 case T_STRUCT:
10337 case T_HASH:
10338 case T_FILE:
10339 case T_COMPLEX:
10340 case T_RATIONAL:
10341 case T_NODE:
10342 case T_CLASS:
10343 if (FL_TEST_RAW(obj, FL_FINALIZE)) {
10344 /* The finalizer table is a numtable. It looks up objects by address.
10345 * We can't mark the keys in the finalizer table because that would
10346 * prevent the objects from being collected. This check prevents
10347 * objects that are keys in the finalizer table from being moved
10348 * without directly pinning them. */
10349 GC_ASSERT(st_is_member(finalizer_table, obj));
10350
10351 return FALSE;
10352 }
10353 GC_ASSERT(RVALUE_MARKED(objspace, obj));
10354 GC_ASSERT(!RVALUE_PINNED(objspace, obj));
10355
10356 return TRUE;
10357
10358 default:
10359 rb_bug("gc_is_moveable_obj: unreachable (%d)", (int)BUILTIN_TYPE(obj));
10360 break;
10361 }
10362
10363 return FALSE;
10364}
10365
10366void rb_mv_generic_ivar(VALUE src, VALUE dst);
10367
10368static VALUE
10369gc_move(rb_objspace_t *objspace, VALUE src, VALUE dest, struct heap_page *src_page, struct heap_page *dest_page)
10370{
10371 size_t src_slot_size = src_page->slot_size;
10372 size_t slot_size = dest_page->slot_size;
10373
10374 int marked;
10375 int wb_unprotected;
10376 int uncollectible;
10377 int age;
10378
10379 gc_report(4, objspace, "Moving object: %p -> %p\n", (void *)src, (void *)dest);
10380
10381 GC_ASSERT(BUILTIN_TYPE(src) != T_NONE);
10382 GC_ASSERT(!MARKED_IN_BITMAP(GET_HEAP_MARK_BITS(dest), dest));
10383
10384 GC_ASSERT(!RVALUE_MARKING(objspace, src));
10385
10386 /* Save off bits for current object. */
10387 marked = RVALUE_MARKED(objspace, src);
10388 wb_unprotected = RVALUE_WB_UNPROTECTED(objspace, src);
10389 uncollectible = RVALUE_UNCOLLECTIBLE(objspace, src);
10390 bool remembered = RVALUE_REMEMBERED(objspace, src);
10391 /* Pin bits travel with the object. Losing one during single-objspace compaction would
10392 * silently unpin it once the process goes multi-objspace, letting a local GC free a method
10393 * entry or shref target that another Ractor references. */
10394 bool shareable = MARKED_IN_BITMAP(GET_HEAP_SHAREABLE_BITS(src), src) != 0;
10395 bool shref = MARKED_IN_BITMAP(GET_HEAP_SHREF_BITS(src), src) != 0;
10396 age = RVALUE_AGE_GET(src);
10397
10398 /* Clear bits for eventual T_MOVED */
10399 CLEAR_IN_BITMAP(GET_HEAP_MARK_BITS(src), src);
10400 CLEAR_IN_BITMAP(GET_HEAP_WB_UNPROTECTED_BITS(src), src);
10401 CLEAR_IN_BITMAP(GET_HEAP_UNCOLLECTIBLE_BITS(src), src);
10402 CLEAR_IN_BITMAP(GET_HEAP_PAGE(src)->remembered_bits, src);
10403 CLEAR_IN_BITMAP(GET_HEAP_SHAREABLE_BITS(src), src);
10404 CLEAR_IN_BITMAP(GET_HEAP_SHREF_BITS(src), src);
10405
10406 /* Move the object */
10407 memcpy((void *)dest, (void *)src, MIN(src_slot_size, slot_size));
10408
10409 if (src_slot_size != slot_size) {
10410 rb_gc_obj_changed_slot_size(dest, slot_size - RVALUE_OVERHEAD);
10411 }
10412
10413 if (RVALUE_OVERHEAD > 0) {
10414 void *dest_overhead = (void *)(((uintptr_t)dest) + slot_size - RVALUE_OVERHEAD);
10415 void *src_overhead = (void *)(((uintptr_t)src) + src_slot_size - RVALUE_OVERHEAD);
10416
10417 memcpy(dest_overhead, src_overhead, RVALUE_OVERHEAD);
10418 }
10419
10420 memset((void *)src, 0, src_slot_size);
10421 RVALUE_AGE_SET_BITMAP(src, 0);
10422
10423 /* Set bits for object in new location */
10424 if (remembered) {
10425 MARK_IN_BITMAP(GET_HEAP_PAGE(dest)->remembered_bits, dest);
10426 }
10427 else {
10428 CLEAR_IN_BITMAP(GET_HEAP_PAGE(dest)->remembered_bits, dest);
10429 }
10430
10431 if (marked) {
10432 MARK_IN_BITMAP(GET_HEAP_MARK_BITS(dest), dest);
10433 }
10434 else {
10435 CLEAR_IN_BITMAP(GET_HEAP_MARK_BITS(dest), dest);
10436 }
10437
10438 if (wb_unprotected) {
10439 MARK_IN_BITMAP(GET_HEAP_WB_UNPROTECTED_BITS(dest), dest);
10440 }
10441 else {
10442 CLEAR_IN_BITMAP(GET_HEAP_WB_UNPROTECTED_BITS(dest), dest);
10443 }
10444
10445 if (uncollectible) {
10446 MARK_IN_BITMAP(GET_HEAP_UNCOLLECTIBLE_BITS(dest), dest);
10447 }
10448 else {
10449 CLEAR_IN_BITMAP(GET_HEAP_UNCOLLECTIBLE_BITS(dest), dest);
10450 }
10451
10452 if (shareable) {
10453 MARK_IN_BITMAP(GET_HEAP_SHAREABLE_BITS(dest), dest);
10454 GET_HEAP_PAGE(dest)->flags.has_shareable_objects = TRUE;
10455 }
10456 else {
10457 CLEAR_IN_BITMAP(GET_HEAP_SHAREABLE_BITS(dest), dest);
10458 }
10459
10460 if (shref) {
10461 MARK_IN_BITMAP(GET_HEAP_SHREF_BITS(dest), dest);
10462 GET_HEAP_PAGE(dest)->flags.has_shref_objects = TRUE;
10463 }
10464 else {
10465 CLEAR_IN_BITMAP(GET_HEAP_SHREF_BITS(dest), dest);
10466 }
10467
10468 RVALUE_AGE_SET(dest, age);
10469
10470 /* A re-embedded object (rb_gc_obj_changed_slot_size) references its
10471 * former fields_obj's contents directly; the write-barrier history
10472 * lived on the discarded fields_obj, so remember the object. */
10473 if (src_slot_size != slot_size && age >= RVALUE_OLD_AGE && !remembered) {
10474 rgengc_remember(objspace, dest);
10475 }
10476
10477 /* Assign forwarding address */
10478 RMOVED(src)->flags = T_MOVED;
10479 RMOVED(src)->dummy = Qundef;
10480 RMOVED(src)->destination = dest;
10481 GC_ASSERT(BUILTIN_TYPE(dest) != T_NONE);
10482
10483 GET_HEAP_PAGE(src)->heap->total_freed_objects++;
10484 GET_HEAP_PAGE(dest)->heap->total_allocated_objects++;
10485
10486 return src;
10487}
10488
10489#if GC_CAN_COMPILE_COMPACTION
10490static int
10491compare_pinned_slots(const void *left, const void *right, void *dummy)
10492{
10493 struct heap_page *left_page;
10494 struct heap_page *right_page;
10495
10496 left_page = *(struct heap_page * const *)left;
10497 right_page = *(struct heap_page * const *)right;
10498
10499 return left_page->pinned_slots - right_page->pinned_slots;
10500}
10501
10502static int
10503compare_free_slots(const void *left, const void *right, void *dummy)
10504{
10505 struct heap_page *left_page;
10506 struct heap_page *right_page;
10507
10508 left_page = *(struct heap_page * const *)left;
10509 right_page = *(struct heap_page * const *)right;
10510
10511 return left_page->free_slots - right_page->free_slots;
10512}
10513
10514static void
10515gc_sort_heap_by_compare_func(rb_objspace_t *objspace, gc_compact_compare_func compare_func)
10516{
10517 for (int j = 0; j < HEAP_COUNT; j++) {
10518 rb_heap_t *heap = &heaps[j];
10519
10520 size_t total_pages = heap->total_pages;
10521 size_t size = rb_size_mul_or_raise(total_pages, sizeof(struct heap_page *), rb_eRuntimeError);
10522 struct heap_page *page = 0, **page_list = malloc(size);
10523 size_t i = 0;
10524
10525 heap->free_pages = NULL;
10526 ccan_list_for_each(&heap->pages, page, page_node) {
10527 page_list[i++] = page;
10528 GC_ASSERT(page);
10529 }
10530
10531 GC_ASSERT((size_t)i == total_pages);
10532
10533 /* Sort the heap so "filled pages" are first. `heap_add_page` adds to the
10534 * head of the list, so empty pages will end up at the start of the heap */
10535 ruby_qsort(page_list, total_pages, sizeof(struct heap_page *), compare_func, NULL);
10536
10537 /* Reset the eden heap */
10538 ccan_list_head_init(&heap->pages);
10539
10540 for (i = 0; i < total_pages; i++) {
10541 ccan_list_add(&heap->pages, &page_list[i]->page_node);
10542 if (page_list[i]->free_slots != 0) {
10543 heap_add_freepage(heap, page_list[i]);
10544 }
10545 }
10546
10547 free(page_list);
10548 }
10549}
10550#endif
10551
10552void
10553rb_gc_impl_register_pinning_obj(void *objspace_ptr, VALUE obj)
10554{
10555 /* no-op */
10556}
10557
10558bool
10559rb_gc_impl_object_moved_p(void *objspace_ptr, VALUE obj)
10560{
10561 return gc_object_moved_p(objspace_ptr, obj);
10562}
10563
10564static int
10565gc_ref_update(void *vstart, void *vend, size_t stride, rb_objspace_t *objspace, struct heap_page *page)
10566{
10567 VALUE v = (VALUE)vstart;
10568
10569 page->flags.has_uncollectible_wb_unprotected_objects = FALSE;
10570 page->flags.has_remembered_objects = FALSE;
10571
10572 /* For each object on the page */
10573 for (; v != (VALUE)vend; v += stride) {
10574 asan_unpoisoning_object(v) {
10575 switch (BUILTIN_TYPE(v)) {
10576 case T_NONE:
10577 case T_MOVED:
10578 case T_ZOMBIE:
10579 break;
10580 default:
10581 if (RVALUE_WB_UNPROTECTED(objspace, v)) {
10582 page->flags.has_uncollectible_wb_unprotected_objects = TRUE;
10583 }
10584 if (RVALUE_REMEMBERED(objspace, v)) {
10585 page->flags.has_remembered_objects = TRUE;
10586 }
10587 if (page->flags.before_sweep) {
10588 if (RVALUE_MARKED(objspace, v)) {
10589 rb_gc_update_object_references(objspace, v);
10590 }
10591 }
10592 else {
10593 rb_gc_update_object_references(objspace, v);
10594 }
10595 }
10596 }
10597 }
10598
10599 return 0;
10600}
10601
10602static int
10603gc_update_references_weak_table_i(VALUE obj, void *data)
10604{
10605 int ret;
10606 asan_unpoisoning_object(obj) {
10607 ret = BUILTIN_TYPE(obj) == T_MOVED ? ST_REPLACE : ST_CONTINUE;
10608 }
10609 return ret;
10610}
10611
10612static int
10613gc_update_references_weak_table_replace_i(VALUE *obj, void *data)
10614{
10615 rb_gc_update_moved(obj);
10616
10617 return ST_CONTINUE;
10618}
10619
10620/* The per-objspace side of the reference update: walk this objspace's heap objects and rewrite
10621 * moved references (following T_MOVED forwarding across objspaces). A compacting global GC
10622 * runs this for every objspace. */
10623static void
10624gc_update_references_heap(rb_objspace_t *objspace)
10625{
10626 struct heap_page *page = NULL;
10627
10628 for (int i = 0; i < HEAP_COUNT; i++) {
10629 bool should_set_mark_bits = TRUE;
10630 rb_heap_t *heap = &heaps[i];
10631
10632 ccan_list_for_each(&heap->pages, page, page_node) {
10633 uintptr_t start = (uintptr_t)page->start;
10634 uintptr_t end = start + (page->total_slots * heap->slot_size);
10635
10636 gc_ref_update((void *)start, (void *)end, heap->slot_size, objspace, page);
10637 if (page == heap->sweeping_page) {
10638 should_set_mark_bits = FALSE;
10639 }
10640 if (should_set_mark_bits) {
10641 gc_setup_mark_bits(page);
10642 }
10643 }
10644 }
10645}
10646
10647/* The VM-global side of the reference update (finalizer table, every Ractor's VM roots,
10648 * weak tables). Process-wide, so a compacting global GC runs it once after every heap
10649 * side: rb_gc_update_vm_references and the weak tables' mark_and_move are not idempotent. */
10650static void
10651gc_update_references_global(rb_objspace_t *objspace)
10652{
10653 gc_update_table_refs(finalizer_table);
10654
10655 rb_gc_update_vm_references((void *)objspace);
10656
10657 for (int table = 0; table < RB_GC_VM_WEAK_TABLE_COUNT; table++) {
10658 rb_gc_vm_weak_table_foreach(
10659 gc_update_references_weak_table_i,
10660 gc_update_references_weak_table_replace_i,
10661 NULL,
10662 false,
10663 table
10664 );
10665 }
10666}
10667
10668static void
10669gc_update_references(rb_objspace_t *objspace)
10670{
10671 objspace->flags.during_reference_updating = true;
10672
10673 rb_gc_before_updating_jit_code();
10674
10675 gc_update_references_heap(objspace);
10676 gc_update_references_global(objspace);
10677
10678 rb_gc_after_updating_jit_code();
10679
10680 objspace->flags.during_reference_updating = false;
10681}
10682
10683#if GC_CAN_COMPILE_COMPACTION
10684static void
10685root_obj_check_moved_i(const char *category, VALUE obj, void *data)
10686{
10687 rb_objspace_t *objspace = data;
10688
10689 if (gc_object_moved_p(objspace, obj)) {
10690 rb_bug("ROOT %s points to MOVED: %p -> %s", category, (void *)obj, rb_obj_info(rb_gc_impl_location(objspace, obj)));
10691 }
10692}
10693
10694static void
10695reachable_object_check_moved_i(VALUE ref, void *data)
10696{
10697 VALUE parent = (VALUE)data;
10698 if (gc_object_moved_p(rb_gc_get_objspace(), ref)) {
10699 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)));
10700 }
10701}
10702
10703static int
10704heap_check_moved_i(void *vstart, void *vend, size_t stride, void *data)
10705{
10706 rb_objspace_t *objspace = data;
10707
10708 VALUE v = (VALUE)vstart;
10709 for (; v != (VALUE)vend; v += stride) {
10710 if (gc_object_moved_p(objspace, v)) {
10711 /* Moved object still on the heap, something may have a reference. */
10712 }
10713 else {
10714 asan_unpoisoning_object(v) {
10715 switch (BUILTIN_TYPE(v)) {
10716 case T_NONE:
10717 case T_ZOMBIE:
10718 break;
10719 default:
10720 if (!rb_gc_impl_garbage_object_p(objspace, v)) {
10721 rb_objspace_reachable_objects_from(v, reachable_object_check_moved_i, (void *)v);
10722 }
10723 }
10724 }
10725 }
10726 }
10727
10728 return 0;
10729}
10730#endif
10731
10732bool
10733rb_gc_impl_during_gc_p(void *objspace_ptr)
10734{
10735 rb_objspace_t *objspace = objspace_ptr;
10736
10737 return during_gc;
10738}
10739
10740#if RGENGC_PROFILE >= 2
10741
10742static const char*
10743type_name(int type, VALUE obj)
10744{
10745 switch ((enum ruby_value_type)type) {
10746 case RUBY_T_NONE: return "T_NONE";
10747 case RUBY_T_OBJECT: return "T_OBJECT";
10748 case RUBY_T_CLASS: return "T_CLASS";
10749 case RUBY_T_MODULE: return "T_MODULE";
10750 case RUBY_T_FLOAT: return "T_FLOAT";
10751 case RUBY_T_STRING: return "T_STRING";
10752 case RUBY_T_REGEXP: return "T_REGEXP";
10753 case RUBY_T_ARRAY: return "T_ARRAY";
10754 case RUBY_T_HASH: return "T_HASH";
10755 case RUBY_T_STRUCT: return "T_STRUCT";
10756 case RUBY_T_BIGNUM: return "T_BIGNUM";
10757 case RUBY_T_FILE: return "T_FILE";
10758 case RUBY_T_DATA: return "T_DATA";
10759 case RUBY_T_MATCH: return "T_MATCH";
10760 case RUBY_T_COMPLEX: return "T_COMPLEX";
10761 case RUBY_T_RATIONAL: return "T_RATIONAL";
10762 case RUBY_T_NIL: return "T_NIL";
10763 case RUBY_T_TRUE: return "T_TRUE";
10764 case RUBY_T_FALSE: return "T_FALSE";
10765 case RUBY_T_SYMBOL: return "T_SYMBOL";
10766 case RUBY_T_FIXNUM: return "T_FIXNUM";
10767 case RUBY_T_UNDEF: return "T_UNDEF";
10768 case RUBY_T_IMEMO: return "T_IMEMO";
10769 case RUBY_T_NODE: return "T_NODE";
10770 case RUBY_T_ICLASS: return "T_ICLASS";
10771 case RUBY_T_ZOMBIE: return "T_ZOMBIE";
10772 case RUBY_T_MOVED: return "T_MOVED";
10773 default: return "unknown";
10774 }
10775}
10776
10777static void
10778gc_count_add_each_types(VALUE hash, const char *name, const size_t *types)
10779{
10780 VALUE result = rb_hash_new_capa(T_MASK);
10781 int i;
10782 for (i=0; i<T_MASK; i++) {
10783 const char *type = type_name(i, 0);
10784 rb_hash_aset(result, ID2SYM(rb_intern(type)), SIZET2NUM(types[i]));
10785 }
10786 rb_hash_aset(hash, ID2SYM(rb_intern(name)), result);
10787}
10788#endif
10789
10790size_t
10791rb_gc_impl_gc_count(void *objspace_ptr)
10792{
10793 rb_objspace_t *objspace = objspace_ptr;
10794
10795 return objspace->profile.count;
10796}
10797
10798/* Filled by setup_gc_latest_gc_info_symbols() at boot, not on first use. */
10799static VALUE sym_major_by, sym_gc_by, sym_immediate_sweep, sym_have_finalizer, sym_state, sym_need_major_by;
10800static VALUE sym_nofree, sym_oldgen, sym_shady, sym_force, sym_stress;
10801#if RGENGC_ESTIMATE_OLDMALLOC
10802static VALUE sym_oldmalloc;
10803#endif
10804static VALUE sym_newobj, sym_malloc, sym_method, sym_capi;
10805static VALUE sym_none, sym_marking, sym_sweeping;
10806static VALUE sym_weak_references_count;
10807
10808static void
10809setup_gc_latest_gc_info_symbols(void)
10810{
10811#define S(s) sym_##s = ID2SYM(rb_intern_const(#s))
10812 S(major_by);
10813 S(gc_by);
10814 S(immediate_sweep);
10815 S(have_finalizer);
10816 S(state);
10817 S(need_major_by);
10818
10819 S(stress);
10820 S(nofree);
10821 S(oldgen);
10822 S(shady);
10823 S(force);
10824#if RGENGC_ESTIMATE_OLDMALLOC
10825 S(oldmalloc);
10826#endif
10827 S(newobj);
10828 S(malloc);
10829 S(method);
10830 S(capi);
10831
10832 S(none);
10833 S(marking);
10834 S(sweeping);
10835
10836 S(weak_references_count);
10837#undef S
10838}
10839
10840static VALUE
10841gc_info_decode(rb_objspace_t *objspace, const VALUE hash_or_key, const unsigned int orig_flags)
10842{
10843 VALUE hash = Qnil, key = Qnil;
10844 VALUE major_by, need_major_by;
10845 unsigned int flags = orig_flags ? orig_flags : objspace->profile.latest_gc_info;
10846
10847 if (SYMBOL_P(hash_or_key)) {
10848 key = hash_or_key;
10849 }
10850 else if (RB_TYPE_P(hash_or_key, T_HASH)) {
10851 hash = hash_or_key;
10852 }
10853 else {
10854 rb_bug("gc_info_decode: non-hash or symbol given");
10855 }
10856
10857#define SET(name, attr) \
10858 if (key == sym_##name) \
10859 return (attr); \
10860 else if (hash != Qnil) \
10861 rb_hash_aset(hash, sym_##name, (attr));
10862
10863 major_by =
10864 (flags & GPR_FLAG_MAJOR_BY_NOFREE) ? sym_nofree :
10865 (flags & GPR_FLAG_MAJOR_BY_OLDGEN) ? sym_oldgen :
10866 (flags & GPR_FLAG_MAJOR_BY_SHADY) ? sym_shady :
10867 (flags & GPR_FLAG_MAJOR_BY_FORCE) ? sym_force :
10868#if RGENGC_ESTIMATE_OLDMALLOC
10869 (flags & GPR_FLAG_MAJOR_BY_OLDMALLOC) ? sym_oldmalloc :
10870#endif
10871 Qnil;
10872 SET(major_by, major_by);
10873
10874 if (orig_flags == 0) { /* set need_major_by only if flags not set explicitly */
10875 unsigned int need_major_flags = gc_needs_major_flags;
10876 need_major_by =
10877 (need_major_flags & GPR_FLAG_MAJOR_BY_NOFREE) ? sym_nofree :
10878 (need_major_flags & GPR_FLAG_MAJOR_BY_OLDGEN) ? sym_oldgen :
10879 (need_major_flags & GPR_FLAG_MAJOR_BY_SHADY) ? sym_shady :
10880 (need_major_flags & GPR_FLAG_MAJOR_BY_FORCE) ? sym_force :
10881#if RGENGC_ESTIMATE_OLDMALLOC
10882 (need_major_flags & GPR_FLAG_MAJOR_BY_OLDMALLOC) ? sym_oldmalloc :
10883#endif
10884 Qnil;
10885 SET(need_major_by, need_major_by);
10886 }
10887
10888 SET(gc_by,
10889 (flags & GPR_FLAG_NEWOBJ) ? sym_newobj :
10890 (flags & GPR_FLAG_MALLOC) ? sym_malloc :
10891 (flags & GPR_FLAG_METHOD) ? sym_method :
10892 (flags & GPR_FLAG_CAPI) ? sym_capi :
10893 (flags & GPR_FLAG_STRESS) ? sym_stress :
10894 Qnil
10895 );
10896
10897 SET(have_finalizer, (flags & GPR_FLAG_HAVE_FINALIZE) ? Qtrue : Qfalse);
10898 SET(immediate_sweep, (flags & GPR_FLAG_IMMEDIATE_SWEEP) ? Qtrue : Qfalse);
10899
10900 if (orig_flags == 0) {
10901 SET(state, gc_mode(objspace) == gc_mode_none ? sym_none :
10902 gc_mode(objspace) == gc_mode_marking ? sym_marking : sym_sweeping);
10903 }
10904
10905 SET(weak_references_count, LONG2FIX(objspace->profile.weak_references_count));
10906#undef SET
10907
10908 if (!NIL_P(key)) {
10909 // Matched key should return above
10910 return Qundef;
10911 }
10912
10913 return hash;
10914}
10915
10916VALUE
10917rb_gc_impl_latest_gc_info(void *objspace_ptr, VALUE key)
10918{
10919 rb_objspace_t *objspace = objspace_ptr;
10920
10921 return gc_info_decode(objspace, key, 0);
10922}
10923
10924
10925enum gc_stat_sym {
10926 gc_stat_sym_count,
10927 gc_stat_sym_time,
10928 gc_stat_sym_marking_time,
10929 gc_stat_sym_sweeping_time,
10930 gc_stat_sym_heap_allocated_pages,
10931 gc_stat_sym_heap_empty_pages,
10932 gc_stat_sym_heap_allocatable_bytes,
10933 gc_stat_sym_heap_available_slots,
10934 gc_stat_sym_heap_live_slots,
10935 gc_stat_sym_heap_free_slots,
10936 gc_stat_sym_heap_final_slots,
10937 gc_stat_sym_heap_marked_slots,
10938 gc_stat_sym_heap_eden_pages,
10939 gc_stat_sym_total_allocated_pages,
10940 gc_stat_sym_total_freed_pages,
10941 gc_stat_sym_total_allocated_objects,
10942 gc_stat_sym_total_freed_objects,
10943 gc_stat_sym_total_malloc_bytes,
10944 gc_stat_sym_total_free_bytes,
10945 gc_stat_sym_malloc_increase_bytes,
10946 gc_stat_sym_malloc_increase_bytes_limit,
10947 gc_stat_sym_minor_gc_count,
10948 gc_stat_sym_major_gc_count,
10949 gc_stat_sym_global_gc_count,
10950 gc_stat_sym_compact_count,
10951 gc_stat_sym_read_barrier_faults,
10952 gc_stat_sym_total_moved_objects,
10953 gc_stat_sym_remembered_wb_unprotected_objects,
10954 gc_stat_sym_remembered_wb_unprotected_objects_limit,
10955 gc_stat_sym_old_objects,
10956 gc_stat_sym_old_objects_limit,
10957#if RGENGC_ESTIMATE_OLDMALLOC
10958 gc_stat_sym_oldmalloc_increase_bytes,
10959 gc_stat_sym_oldmalloc_increase_bytes_limit,
10960#endif
10961#if RGENGC_PROFILE
10962 gc_stat_sym_total_generated_normal_object_count,
10963 gc_stat_sym_total_generated_shady_object_count,
10964 gc_stat_sym_total_shade_operation_count,
10965 gc_stat_sym_total_promoted_count,
10966 gc_stat_sym_total_remembered_normal_object_count,
10967 gc_stat_sym_total_remembered_shady_object_count,
10968#endif
10969 gc_stat_sym_page_pool_arenas,
10970 gc_stat_sym_page_pool_arenas_freed,
10971 gc_stat_sym_page_pool_total_pages,
10972 gc_stat_sym_page_pool_discarded_pages,
10973 gc_stat_sym_last
10974};
10975
10976static VALUE gc_stat_symbols[gc_stat_sym_last];
10977
10978static void
10979setup_gc_stat_symbols(void)
10980{
10981#define S(s) gc_stat_symbols[gc_stat_sym_##s] = ID2SYM(rb_intern_const(#s))
10982 S(count);
10983 S(time);
10984 S(marking_time),
10985 S(sweeping_time),
10986 S(heap_allocated_pages);
10987 S(heap_empty_pages);
10988 S(heap_allocatable_bytes);
10989 S(heap_available_slots);
10990 S(heap_live_slots);
10991 S(heap_free_slots);
10992 S(heap_final_slots);
10993 S(heap_marked_slots);
10994 S(heap_eden_pages);
10995 S(total_allocated_pages);
10996 S(total_freed_pages);
10997 S(total_allocated_objects);
10998 S(total_freed_objects);
10999 S(total_malloc_bytes);
11000 S(total_free_bytes);
11001 S(malloc_increase_bytes);
11002 S(malloc_increase_bytes_limit);
11003 S(minor_gc_count);
11004 S(major_gc_count);
11005 S(global_gc_count);
11006 S(compact_count);
11007 S(read_barrier_faults);
11008 S(total_moved_objects);
11009 S(remembered_wb_unprotected_objects);
11010 S(remembered_wb_unprotected_objects_limit);
11011 S(old_objects);
11012 S(old_objects_limit);
11013#if RGENGC_ESTIMATE_OLDMALLOC
11014 S(oldmalloc_increase_bytes);
11015 S(oldmalloc_increase_bytes_limit);
11016#endif
11017#if RGENGC_PROFILE
11018 S(total_generated_normal_object_count);
11019 S(total_generated_shady_object_count);
11020 S(total_shade_operation_count);
11021 S(total_promoted_count);
11022 S(total_remembered_normal_object_count);
11023 S(total_remembered_shady_object_count);
11024#endif /* RGENGC_PROFILE */
11025 S(page_pool_arenas);
11026 S(page_pool_arenas_freed);
11027 S(page_pool_total_pages);
11028 S(page_pool_discarded_pages);
11029#undef S
11030}
11031
11032static uint64_t
11033ns_to_ms(uint64_t ns)
11034{
11035 return ns / (1000 * 1000);
11036}
11037
11038static void malloc_increase_local_flush(rb_objspace_t *objspace);
11039
11040static void
11041gc_process_stat_accumulate_i(void *objspace_ptr, void *data)
11042{
11043 rb_objspace_t *objspace = objspace_ptr;
11044 struct gc_process_stat_total *total = (struct gc_process_stat_total *)data;
11045 struct gc_process_stat_snapshot snap;
11046 rb_native_mutex_lock(&objspace->process_stat.lock);
11047 snap = objspace->process_stat.published;
11048 rb_native_mutex_unlock(&objspace->process_stat.lock);
11049 gc_process_stat_add(total, &snap);
11050}
11051
11052static VALUE
11053gc_process_stat(VALUE hash_or_sym)
11054{
11055 VALUE hash = Qnil, key = Qnil;
11056
11057 if (RB_TYPE_P(hash_or_sym, T_HASH)) {
11058 hash = hash_or_sym;
11059 }
11060 else if (SYMBOL_P(hash_or_sym)) {
11061 key = hash_or_sym;
11062 }
11063 else {
11064 rb_bug("non-hash or symbol given");
11065 }
11066
11067 struct gc_process_stat_total total;
11068 unsigned int lev = RB_GC_VM_LOCK();
11069 total = global_objspace->process_stat_archive;
11070 rb_gc_vm_each_objspace(gc_process_stat_accumulate_i, &total);
11071 RB_GC_VM_UNLOCK(lev);
11072
11073 /* Convert to Ruby values after all collector locks are released. */
11074 uint64_t time_ns = total.marking_time_ns + total.sweeping_time_ns;
11075
11076#define SET64(name, attr) \
11077 if (key == gc_stat_symbols[gc_stat_sym_##name]) \
11078 return ULL2NUM(attr); \
11079 else if (hash != Qnil) \
11080 rb_hash_aset(hash, gc_stat_symbols[gc_stat_sym_##name], ULL2NUM(attr));
11081
11082 SET64(count, total.count);
11083 SET64(time, ns_to_ms(time_ns));
11084 SET64(marking_time, ns_to_ms(total.marking_time_ns));
11085 SET64(sweeping_time, ns_to_ms(total.sweeping_time_ns));
11086 SET64(minor_gc_count, total.minor_gc_count);
11087 SET64(major_gc_count, total.major_gc_count);
11088
11089#undef SET64
11090
11091 if (!NIL_P(key)) {
11092 /* Matched key should return above. */
11093 return Qundef;
11094 }
11095
11096 return hash;
11097}
11098
11099VALUE
11100rb_gc_impl_stat(void *objspace_ptr, VALUE hash_or_sym)
11101{
11102 if (objspace_ptr == NULL) {
11103 return gc_process_stat(hash_or_sym);
11104 }
11105
11106 rb_objspace_t *objspace = objspace_ptr;
11107 VALUE hash = Qnil, key = Qnil;
11108
11109 malloc_increase_local_flush(objspace);
11110
11111 if (RB_TYPE_P(hash_or_sym, T_HASH)) {
11112 hash = hash_or_sym;
11113 }
11114 else if (SYMBOL_P(hash_or_sym)) {
11115 key = hash_or_sym;
11116 }
11117 else {
11118 rb_bug("non-hash or symbol given");
11119 }
11120
11121#define SET(name, attr) \
11122 if (key == gc_stat_symbols[gc_stat_sym_##name]) \
11123 return SIZET2NUM(attr); \
11124 else if (hash != Qnil) \
11125 rb_hash_aset(hash, gc_stat_symbols[gc_stat_sym_##name], SIZET2NUM(attr));
11126#define SET64(name, attr) \
11127 if (key == gc_stat_symbols[gc_stat_sym_##name]) \
11128 return ULL2NUM(attr); \
11129 else if (hash != Qnil) \
11130 rb_hash_aset(hash, gc_stat_symbols[gc_stat_sym_##name], ULL2NUM(attr));
11131
11132 SET(count, objspace->profile.count);
11133 SET(time, (size_t)ns_to_ms(objspace->profile.marking_time_ns + objspace->profile.sweeping_time_ns)); // TODO: UINT64T2NUM
11134 SET(marking_time, (size_t)ns_to_ms(objspace->profile.marking_time_ns));
11135 SET(sweeping_time, (size_t)ns_to_ms(objspace->profile.sweeping_time_ns));
11136
11137 {
11138 uint64_t total_malloc = (uint64_t)gc_counter_load_relaxed(&objspace->malloc_counters.counters.malloc);
11139 uint64_t total_free = (uint64_t)gc_counter_load_relaxed(&objspace->malloc_counters.counters.free);
11140 SET64(total_malloc_bytes, total_malloc);
11141 SET64(total_free_bytes, total_free);
11142 }
11143
11144 /* implementation dependent counters (small / fixnum-safe) */
11145 SET(heap_allocated_pages, rb_darray_size(objspace->heap_pages.sorted));
11146 SET(heap_empty_pages, objspace->empty_pages_count)
11147 SET(heap_allocatable_bytes, objspace->heap_pages.allocatable_bytes);
11148 SET(heap_eden_pages, heap_eden_total_pages(objspace));
11149 SET(total_allocated_pages, objspace->heap_pages.allocated_pages);
11150 SET(total_freed_pages, objspace->heap_pages.freed_pages);
11151 SET(malloc_increase_bytes, gc_malloc_counters_increase_unsigned(objspace, &objspace->malloc_counters.counters));
11152 SET(malloc_increase_bytes_limit, malloc_limit);
11153 SET(minor_gc_count, objspace->profile.minor_gc_count);
11154 SET(major_gc_count, objspace->profile.major_gc_count);
11155 SET(global_gc_count, global_objspace->global_gc.count);
11156 SET(compact_count, objspace->profile.compact_count);
11157 SET(read_barrier_faults, objspace->profile.read_barrier_faults);
11158 SET(total_moved_objects, objspace->rcompactor.total_moved);
11159 SET(remembered_wb_unprotected_objects, objspace->rgengc.uncollectible_wb_unprotected_objects);
11160 SET(remembered_wb_unprotected_objects_limit, objspace->rgengc.uncollectible_wb_unprotected_objects_limit);
11161 SET(old_objects, objspace->rgengc.old_objects);
11162 SET(old_objects_limit, objspace->rgengc.old_objects_limit);
11163#if RGENGC_ESTIMATE_OLDMALLOC
11164 SET(oldmalloc_increase_bytes, gc_malloc_counters_increase_unsigned(objspace, &objspace->malloc_counters.oldcounters));
11165 SET(oldmalloc_increase_bytes_limit, objspace->rgengc.oldmalloc_increase_limit);
11166#endif
11167
11168 SET(total_allocated_objects, total_allocated_objects(objspace));
11169 SET(total_freed_objects, total_freed_objects(objspace));
11170 SET(heap_available_slots, objspace_available_slots(objspace));
11171 SET(heap_live_slots, objspace_live_slots(objspace));
11172 SET(heap_free_slots, objspace_free_slots(objspace));
11173 SET(heap_final_slots, total_final_slots_count(objspace));
11174 SET(heap_marked_slots, objspace->marked_slots);
11175
11176 SET(page_pool_arenas, global_objspace->page_pool.arena_count);
11177 SET(page_pool_arenas_freed, global_objspace->page_pool.arenas_unmapped);
11178 SET(page_pool_total_pages, (size_t)global_objspace->page_pool.arena_count * PAGE_POOL_ARENA_BODIES);
11179 SET(page_pool_discarded_pages, global_objspace->page_pool.advised_count);
11180
11181#if RGENGC_PROFILE
11182 SET(total_generated_normal_object_count, objspace->profile.total_generated_normal_object_count);
11183 SET(total_generated_shady_object_count, objspace->profile.total_generated_shady_object_count);
11184 SET(total_shade_operation_count, objspace->profile.total_shade_operation_count);
11185 SET(total_promoted_count, objspace->profile.total_promoted_count);
11186 SET(total_remembered_normal_object_count, objspace->profile.total_remembered_normal_object_count);
11187 SET(total_remembered_shady_object_count, objspace->profile.total_remembered_shady_object_count);
11188#endif /* RGENGC_PROFILE */
11189#undef SET
11190#undef SET64
11191
11192 if (!NIL_P(key)) {
11193 // Matched key should return above
11194 return Qundef;
11195 }
11196
11197#if defined(RGENGC_PROFILE) && RGENGC_PROFILE >= 2
11198 if (hash != Qnil) {
11199 gc_count_add_each_types(hash, "generated_normal_object_count_types", objspace->profile.generated_normal_object_count_types);
11200 gc_count_add_each_types(hash, "generated_shady_object_count_types", objspace->profile.generated_shady_object_count_types);
11201 gc_count_add_each_types(hash, "shade_operation_count_types", objspace->profile.shade_operation_count_types);
11202 gc_count_add_each_types(hash, "promoted_types", objspace->profile.promoted_types);
11203 gc_count_add_each_types(hash, "remembered_normal_object_count_types", objspace->profile.remembered_normal_object_count_types);
11204 gc_count_add_each_types(hash, "remembered_shady_object_count_types", objspace->profile.remembered_shady_object_count_types);
11205 }
11206#endif
11207
11208 return hash;
11209}
11210
11211enum gc_stat_heap_sym {
11212 gc_stat_heap_sym_slot_size,
11213 gc_stat_heap_sym_heap_live_slots,
11214 gc_stat_heap_sym_heap_free_slots,
11215 gc_stat_heap_sym_heap_final_slots,
11216 gc_stat_heap_sym_heap_eden_pages,
11217 gc_stat_heap_sym_heap_eden_slots,
11218 gc_stat_heap_sym_total_allocated_pages,
11219 gc_stat_heap_sym_force_major_gc_count,
11220 gc_stat_heap_sym_force_incremental_marking_finish_count,
11221 gc_stat_heap_sym_heap_allocatable_slots,
11222 gc_stat_heap_sym_total_allocated_objects,
11223 gc_stat_heap_sym_total_freed_objects,
11224 gc_stat_heap_sym_last
11225};
11226
11227static VALUE gc_stat_heap_symbols[gc_stat_heap_sym_last];
11228
11229static void
11230setup_gc_stat_heap_symbols(void)
11231{
11232#define S(s) gc_stat_heap_symbols[gc_stat_heap_sym_##s] = ID2SYM(rb_intern_const(#s))
11233 S(slot_size);
11234 S(heap_live_slots);
11235 S(heap_free_slots);
11236 S(heap_final_slots);
11237 S(heap_eden_pages);
11238 S(heap_eden_slots);
11239 S(heap_allocatable_slots);
11240 S(total_allocated_pages);
11241 S(force_major_gc_count);
11242 S(force_incremental_marking_finish_count);
11243 S(total_allocated_objects);
11244 S(total_freed_objects);
11245#undef S
11246}
11247
11248static VALUE
11249stat_one_heap(rb_objspace_t *objspace, rb_heap_t *heap, VALUE hash, VALUE key)
11250{
11251#define SET(name, attr) \
11252 if (key == gc_stat_heap_symbols[gc_stat_heap_sym_##name]) \
11253 return SIZET2NUM(attr); \
11254 else if (hash != Qnil) \
11255 rb_hash_aset(hash, gc_stat_heap_symbols[gc_stat_heap_sym_##name], SIZET2NUM(attr));
11256
11257 SET(slot_size, heap->slot_size);
11258 SET(heap_live_slots, heap->total_allocated_objects - heap->total_freed_objects - heap->final_slots_count);
11259 SET(heap_free_slots, heap->total_slots - (heap->total_allocated_objects - heap->total_freed_objects));
11260 SET(heap_final_slots, heap->final_slots_count);
11261 SET(heap_eden_pages, heap->total_pages);
11262 SET(heap_eden_slots, heap->total_slots);
11263 SET(heap_allocatable_slots, objspace->heap_pages.allocatable_bytes / heap->slot_size);
11264 SET(total_allocated_pages, heap->total_allocated_pages);
11265 SET(force_major_gc_count, heap->force_major_gc_count);
11266 SET(force_incremental_marking_finish_count, heap->force_incremental_marking_finish_count);
11267 SET(total_allocated_objects, heap->total_allocated_objects);
11268 SET(total_freed_objects, heap->total_freed_objects);
11269#undef SET
11270
11271 if (!NIL_P(key)) {
11272 // Matched key should return above
11273 return Qundef;
11274 }
11275
11276 return hash;
11277}
11278
11279VALUE
11280rb_gc_impl_stat_heap(void *objspace_ptr, VALUE heap_name, VALUE hash_or_sym)
11281{
11282 rb_objspace_t *objspace = objspace_ptr;
11283
11284 if (NIL_P(heap_name)) {
11285 if (!RB_TYPE_P(hash_or_sym, T_HASH)) {
11286 rb_bug("non-hash given");
11287 }
11288
11289 for (int i = 0; i < HEAP_COUNT; i++) {
11290 VALUE hash = rb_hash_aref(hash_or_sym, INT2FIX(i));
11291 if (NIL_P(hash)) {
11292 hash = rb_hash_new();
11293 rb_hash_aset(hash_or_sym, INT2FIX(i), hash);
11294 }
11295
11296 stat_one_heap(objspace, &heaps[i], hash, Qnil);
11297 }
11298 }
11299 else if (FIXNUM_P(heap_name)) {
11300 int heap_idx = FIX2INT(heap_name);
11301
11302 if (heap_idx < 0 || heap_idx >= HEAP_COUNT) {
11303 rb_raise(rb_eArgError, "size pool index out of range");
11304 }
11305
11306 if (SYMBOL_P(hash_or_sym)) {
11307 return stat_one_heap(objspace, &heaps[heap_idx], Qnil, hash_or_sym);
11308 }
11309 else if (RB_TYPE_P(hash_or_sym, T_HASH)) {
11310 return stat_one_heap(objspace, &heaps[heap_idx], hash_or_sym, Qnil);
11311 }
11312 else {
11313 rb_bug("non-hash or symbol given");
11314 }
11315 }
11316 else {
11317 rb_bug("heap_name must be nil or an Integer");
11318 }
11319
11320 return hash_or_sym;
11321}
11322
11323/* I could include internal.h for this, but doing so undefines some Array macros
11324 * necessary for initialising objects, and I don't want to include all the array
11325 * headers to get them back
11326 * TODO: Investigate why RARRAY_AREF gets undefined in internal.h
11327 */
11328#ifndef RBOOL
11329#define RBOOL(v) (v ? Qtrue : Qfalse)
11330#endif
11331
11332VALUE
11333rb_gc_impl_config_get(void *objspace_ptr)
11334{
11335#define sym(name) ID2SYM(rb_intern_const(name))
11336 rb_objspace_t *objspace = objspace_ptr;
11337 VALUE hash = rb_hash_new();
11338
11339 rb_hash_aset(hash, sym("rgengc_allow_full_mark"), RBOOL(gc_config_full_mark_val));
11340
11341 return hash;
11342}
11343
11344static int
11345gc_config_set_key(VALUE key, VALUE value, VALUE data)
11346{
11348 if (rb_sym2id(key) == rb_intern("rgengc_allow_full_mark")) {
11349 gc_rest(objspace);
11350 gc_config_full_mark_set(RTEST(value));
11351 }
11352 return ST_CONTINUE;
11353}
11354
11355void
11356rb_gc_impl_config_set(void *objspace_ptr, VALUE hash)
11357{
11358 rb_objspace_t *objspace = objspace_ptr;
11359
11360 if (!RB_TYPE_P(hash, T_HASH)) {
11361 rb_raise(rb_eArgError, "expected keyword arguments");
11362 }
11363
11364 rb_hash_foreach(hash, gc_config_set_key, (st_data_t)objspace);
11365}
11366
11367VALUE
11368rb_gc_impl_stress_get(void *objspace_ptr)
11369{
11370 return ruby_gc_stress_mode;
11371}
11372
11373void
11374rb_gc_impl_stress_set(void *objspace_ptr, VALUE flag)
11375{
11376 global_objspace->gc_stressful = RTEST(flag);
11377 global_objspace->gc_stress_mode = flag;
11378}
11379
11380static int
11381get_envparam_size(const char *name, size_t *default_value, size_t lower_bound)
11382{
11383 const char *ptr = getenv(name);
11384 ssize_t val;
11385
11386 if (ptr != NULL && *ptr) {
11387 size_t unit = 0;
11388 char *end;
11389#if SIZEOF_SIZE_T == SIZEOF_LONG_LONG
11390 val = strtoll(ptr, &end, 0);
11391#else
11392 val = strtol(ptr, &end, 0);
11393#endif
11394 switch (*end) {
11395 case 'k': case 'K':
11396 unit = 1024;
11397 ++end;
11398 break;
11399 case 'm': case 'M':
11400 unit = 1024*1024;
11401 ++end;
11402 break;
11403 case 'g': case 'G':
11404 unit = 1024*1024*1024;
11405 ++end;
11406 break;
11407 }
11408 while (*end && isspace((unsigned char)*end)) end++;
11409 if (*end) {
11410 if (RTEST(ruby_verbose)) fprintf(stderr, "invalid string for %s: %s\n", name, ptr);
11411 return 0;
11412 }
11413 if (unit > 0) {
11414 if (val < -(ssize_t)(SIZE_MAX / 2 / unit) || (ssize_t)(SIZE_MAX / 2 / unit) < val) {
11415 if (RTEST(ruby_verbose)) fprintf(stderr, "%s=%s is ignored because it overflows\n", name, ptr);
11416 return 0;
11417 }
11418 val *= unit;
11419 }
11420 if (val > 0 && (size_t)val > lower_bound) {
11421 if (RTEST(ruby_verbose)) {
11422 fprintf(stderr, "%s=%"PRIdSIZE" (default value: %"PRIuSIZE")\n", name, val, *default_value);
11423 }
11424 *default_value = (size_t)val;
11425 return 1;
11426 }
11427 else {
11428 if (RTEST(ruby_verbose)) {
11429 fprintf(stderr, "%s=%"PRIdSIZE" (default value: %"PRIuSIZE") is ignored because it must be greater than %"PRIuSIZE".\n",
11430 name, val, *default_value, lower_bound);
11431 }
11432 return 0;
11433 }
11434 }
11435 return 0;
11436}
11437
11438static int
11439get_envparam_double(const char *name, double *default_value, double lower_bound, double upper_bound, int accept_zero)
11440{
11441 const char *ptr = getenv(name);
11442 double val;
11443
11444 if (ptr != NULL && *ptr) {
11445 char *end;
11446 val = strtod(ptr, &end);
11447 if (!*ptr || *end) {
11448 if (RTEST(ruby_verbose)) fprintf(stderr, "invalid string for %s: %s\n", name, ptr);
11449 return 0;
11450 }
11451
11452 if (accept_zero && val == 0.0) {
11453 goto accept;
11454 }
11455 else if (val <= lower_bound) {
11456 if (RTEST(ruby_verbose)) {
11457 fprintf(stderr, "%s=%f (default value: %f) is ignored because it must be greater than %f.\n",
11458 name, val, *default_value, lower_bound);
11459 }
11460 }
11461 else if (upper_bound != 0.0 && /* ignore upper_bound if it is 0.0 */
11462 val > upper_bound) {
11463 if (RTEST(ruby_verbose)) {
11464 fprintf(stderr, "%s=%f (default value: %f) is ignored because it must be lower than %f.\n",
11465 name, val, *default_value, upper_bound);
11466 }
11467 }
11468 else {
11469 goto accept;
11470 }
11471 }
11472 return 0;
11473
11474 accept:
11475 if (RTEST(ruby_verbose)) fprintf(stderr, "%s=%f (default value: %f)\n", name, val, *default_value);
11476 *default_value = val;
11477 return 1;
11478}
11479
11480/*
11481 * GC tuning environment variables
11482 *
11483 * * RUBY_GC_HEAP_FREE_SLOTS
11484 * - Prepare at least this amount of slots after GC.
11485 * - Allocate slots if there are not enough slots.
11486 * * RUBY_GC_HEAP_GROWTH_FACTOR (new from 2.1)
11487 * - Allocate slots by this factor.
11488 * - (next slots number) = (current slots number) * (this factor)
11489 * * RUBY_GC_HEAP_GROWTH_MAX_BYTES (was RUBY_GC_HEAP_GROWTH_MAX_SLOTS)
11490 * - Allocation rate is limited to this number of bytes.
11491 * * RUBY_GC_HEAP_FREE_SLOTS_MIN_RATIO (new from 2.4)
11492 * - Allocate additional pages when the number of free slots is
11493 * lower than the value (total_slots * (this ratio)).
11494 * * RUBY_GC_HEAP_FREE_SLOTS_GOAL_RATIO (new from 2.4)
11495 * - Allocate slots to satisfy this formula:
11496 * free_slots = total_slots * goal_ratio
11497 * - In other words, prepare (total_slots * goal_ratio) free slots.
11498 * - if this value is 0.0, then use RUBY_GC_HEAP_GROWTH_FACTOR directly.
11499 * * RUBY_GC_HEAP_FREE_SLOTS_MAX_RATIO (new from 2.4)
11500 * - Allow to free pages when the number of free slots is
11501 * greater than the value (total_slots * (this ratio)).
11502 * * RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR (new from 2.1.1)
11503 * - Do full GC when the number of old objects is more than R * N
11504 * where R is this factor and
11505 * N is the number of old objects just after last full GC.
11506 *
11507 * * obsolete
11508 * * RUBY_FREE_MIN -> RUBY_GC_HEAP_FREE_SLOTS (from 2.1)
11509 * * RUBY_HEAP_MIN_SLOTS -> RUBY_GC_HEAP_INIT_SLOTS (from 2.1) -> RUBY_GC_HEAP_INIT_BYTES
11510 *
11511 * * RUBY_GC_MALLOC_LIMIT
11512 * * RUBY_GC_MALLOC_LIMIT_MAX (new from 2.1)
11513 * * RUBY_GC_MALLOC_LIMIT_GROWTH_FACTOR (new from 2.1)
11514 *
11515 * * RUBY_GC_OLDMALLOC_LIMIT (new from 2.1)
11516 * * RUBY_GC_OLDMALLOC_LIMIT_MAX (new from 2.1)
11517 * * RUBY_GC_OLDMALLOC_LIMIT_GROWTH_FACTOR (new from 2.1)
11518 */
11519
11520void
11521rb_gc_impl_set_params(void *objspace_ptr)
11522{
11523 rb_objspace_t *objspace = objspace_ptr;
11524 get_envparam_size("RUBY_GC_HEAP_FREE_SLOTS", &gc_params.heap_free_slots, 0);
11525
11526 get_envparam_size("RUBY_GC_HEAP_INIT_BYTES", &gc_params.heap_init_bytes,
11527 heap_init_bytes_min() - 1);
11528 get_envparam_size("RUBY_GC_RACTOR_HEAP_INIT_BYTES", &gc_params.ractor_heap_init_bytes,
11529 heap_init_bytes_min() - 1);
11530
11531 get_envparam_double("RUBY_GC_HEAP_GROWTH_FACTOR", &gc_params.growth_factor, 1.0, 0.0, FALSE);
11532 get_envparam_size ("RUBY_GC_HEAP_GROWTH_MAX_BYTES", &gc_params.growth_max_bytes, 0);
11533 get_envparam_double("RUBY_GC_HEAP_FREE_SLOTS_MIN_RATIO", &gc_params.heap_free_slots_min_ratio,
11534 0.0, 1.0, FALSE);
11535 get_envparam_double("RUBY_GC_HEAP_FREE_SLOTS_MAX_RATIO", &gc_params.heap_free_slots_max_ratio,
11536 gc_params.heap_free_slots_min_ratio, 1.0, FALSE);
11537 get_envparam_double("RUBY_GC_HEAP_FREE_SLOTS_GOAL_RATIO", &gc_params.heap_free_slots_goal_ratio,
11538 gc_params.heap_free_slots_min_ratio, gc_params.heap_free_slots_max_ratio, TRUE);
11539 get_envparam_double("RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR", &gc_params.oldobject_limit_factor, 0.0, 0.0, TRUE);
11540 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);
11541
11542 if (get_envparam_size("RUBY_GC_MALLOC_LIMIT", &gc_params.malloc_limit_min, 0)) {
11543 malloc_limit = gc_params.malloc_limit_min;
11544 }
11545 get_envparam_size ("RUBY_GC_MALLOC_LIMIT_MAX", &gc_params.malloc_limit_max, 0);
11546 if (!gc_params.malloc_limit_max) { /* ignore max-check if 0 */
11547 gc_params.malloc_limit_max = SIZE_MAX;
11548 }
11549 get_envparam_double("RUBY_GC_MALLOC_LIMIT_GROWTH_FACTOR", &gc_params.malloc_limit_growth_factor, 1.0, 0.0, FALSE);
11550
11551#if RGENGC_ESTIMATE_OLDMALLOC
11552 if (get_envparam_size("RUBY_GC_OLDMALLOC_LIMIT", &gc_params.oldmalloc_limit_min, 0)) {
11553 objspace->rgengc.oldmalloc_increase_limit = gc_params.oldmalloc_limit_min;
11554 }
11555 get_envparam_size ("RUBY_GC_OLDMALLOC_LIMIT_MAX", &gc_params.oldmalloc_limit_max, 0);
11556 get_envparam_double("RUBY_GC_OLDMALLOC_LIMIT_GROWTH_FACTOR", &gc_params.oldmalloc_limit_growth_factor, 1.0, 0.0, FALSE);
11557#endif
11558}
11559
11560static inline size_t
11561objspace_malloc_size(rb_objspace_t *objspace, void *ptr, size_t hint)
11562{
11563#ifdef HAVE_MALLOC_USABLE_SIZE
11564 if (!hint) {
11565 hint = malloc_usable_size(ptr);
11566 }
11567#endif
11568 return hint;
11569}
11570
11571enum memop_type {
11572 MEMOP_TYPE_MALLOC = 0,
11573 MEMOP_TYPE_FREE,
11574 MEMOP_TYPE_REALLOC
11575};
11576
11577static inline void
11578atomic_sub_nounderflow(size_t *var, size_t sub)
11579{
11580 if (sub == 0) return;
11581
11582 while (1) {
11583 size_t val = *var;
11584 if (val < sub) sub = val;
11585 if (RUBY_ATOMIC_SIZE_CAS(*var, val, val-sub) == val) break;
11586 }
11587}
11588
11589#define gc_stress_full_mark_after_malloc_p() \
11590 (FIXNUM_P(ruby_gc_stress_mode) && (FIX2LONG(ruby_gc_stress_mode) & (1<<gc_stress_full_mark_after_malloc)))
11591
11592static void
11593objspace_malloc_gc_stress(rb_objspace_t *objspace)
11594{
11595 if (ruby_gc_stressful && ruby_native_thread_p()) {
11596 unsigned int reason = (GPR_FLAG_IMMEDIATE_MARK | GPR_FLAG_IMMEDIATE_SWEEP |
11597 GPR_FLAG_STRESS | GPR_FLAG_MALLOC);
11598
11599 if (gc_stress_full_mark_after_malloc_p()) {
11600 reason |= GPR_FLAG_FULL_MARK;
11601 }
11602 garbage_collect_with_gvl(objspace, reason);
11603 }
11604}
11605
11606static void
11607malloc_increase_commit(rb_objspace_t *objspace, size_t new_size, size_t old_size)
11608{
11609 if (new_size > old_size) {
11610 size_t delta = new_size - old_size;
11611 MALLOC_COUNTERS_LOCK(objspace);
11612 gc_counter_add(&objspace->malloc_counters.counters.malloc, delta);
11613#if RGENGC_ESTIMATE_OLDMALLOC
11614 gc_counter_add(&objspace->malloc_counters.oldcounters.malloc, delta);
11615#endif
11616 MALLOC_COUNTERS_UNLOCK(objspace);
11617 }
11618 else if (old_size > new_size) {
11619 size_t delta = old_size - new_size;
11620 MALLOC_COUNTERS_LOCK(objspace);
11621 gc_counter_add(&objspace->malloc_counters.counters.free, delta);
11622#if RGENGC_ESTIMATE_OLDMALLOC
11623 gc_counter_add(&objspace->malloc_counters.oldcounters.free, delta);
11624#endif
11625 MALLOC_COUNTERS_UNLOCK(objspace);
11626 }
11627}
11628
11629#if USE_MALLOC_INCREASE_LOCAL
11630static void
11631malloc_increase_local_flush(rb_objspace_t *objspace)
11632{
11633 int delta = malloc_increase_local;
11634 if (delta == 0) return;
11635
11636 malloc_increase_local = 0;
11637 if (delta > 0) {
11638 malloc_increase_commit(objspace, (size_t)delta, 0);
11639 }
11640 else {
11641 malloc_increase_commit(objspace, 0, (size_t)(-delta));
11642 }
11643}
11644#else
11645static void
11646malloc_increase_local_flush(rb_objspace_t *objspace)
11647{
11648}
11649#endif
11650
11651static inline bool
11652objspace_malloc_increase_report(rb_objspace_t *objspace, void *mem, size_t new_size, size_t old_size, enum memop_type type, bool gc_allowed)
11653{
11654 if (0) fprintf(stderr, "increase - ptr: %p, type: %s, new_size: %"PRIdSIZE", old_size: %"PRIdSIZE"\n",
11655 mem,
11656 type == MEMOP_TYPE_MALLOC ? "malloc" :
11657 type == MEMOP_TYPE_FREE ? "free " :
11658 type == MEMOP_TYPE_REALLOC ? "realloc": "error",
11659 new_size, old_size);
11660 return false;
11661}
11662
11663static bool
11664objspace_malloc_increase_body(rb_objspace_t *objspace, void *mem, size_t new_size, size_t old_size, enum memop_type type, bool gc_allowed)
11665{
11666#if USE_MALLOC_INCREASE_LOCAL
11667 if (new_size < GC_MALLOC_INCREASE_LOCAL_THRESHOLD &&
11668 old_size < GC_MALLOC_INCREASE_LOCAL_THRESHOLD) {
11669 malloc_increase_local += (int)new_size - (int)old_size;
11670
11671 if (malloc_increase_local >= GC_MALLOC_INCREASE_LOCAL_THRESHOLD ||
11672 malloc_increase_local <= -GC_MALLOC_INCREASE_LOCAL_THRESHOLD) {
11673 malloc_increase_local_flush(objspace);
11674 }
11675 }
11676 else {
11677 malloc_increase_local_flush(objspace);
11678 malloc_increase_commit(objspace, new_size, old_size);
11679 }
11680#else
11681 malloc_increase_commit(objspace, new_size, old_size);
11682#endif
11683
11684 if (type == MEMOP_TYPE_MALLOC && gc_allowed) {
11685 retry:
11686 if (malloc_increase > malloc_limit && ruby_native_thread_p() && !dont_gc_val() && !rb_gc_gc_disabled_global_p()) {
11687 if (ruby_thread_has_gvl_p() && is_lazy_sweeping(objspace)) {
11688 gc_sweep_step_for_malloc(objspace); /* sweeping frees may reduce malloc_increase */
11689 goto retry;
11690 }
11691 garbage_collect_with_gvl(objspace, GPR_FLAG_MALLOC);
11692 }
11693 }
11694
11695#if MALLOC_ALLOCATED_SIZE
11696 if (new_size >= old_size) {
11697 RUBY_ATOMIC_SIZE_ADD(objspace->malloc_params.allocated_size, new_size - old_size);
11698 }
11699 else {
11700 size_t dec_size = old_size - new_size;
11701
11702#if MALLOC_ALLOCATED_SIZE_CHECK
11703 size_t allocated_size = objspace->malloc_params.allocated_size;
11704 if (allocated_size < dec_size) {
11705 rb_bug("objspace_malloc_increase: underflow malloc_params.allocated_size.");
11706 }
11707#endif
11708 atomic_sub_nounderflow(&objspace->malloc_params.allocated_size, dec_size);
11709 }
11710
11711 switch (type) {
11712 case MEMOP_TYPE_MALLOC:
11713 RUBY_ATOMIC_SIZE_INC(objspace->malloc_params.allocations);
11714 break;
11715 case MEMOP_TYPE_FREE:
11716 {
11717 size_t allocations = objspace->malloc_params.allocations;
11718 if (allocations > 0) {
11719 atomic_sub_nounderflow(&objspace->malloc_params.allocations, 1);
11720 }
11721#if MALLOC_ALLOCATED_SIZE_CHECK
11722 else {
11723 GC_ASSERT(objspace->malloc_params.allocations > 0);
11724 }
11725#endif
11726 }
11727 break;
11728 case MEMOP_TYPE_REALLOC: /* ignore */ break;
11729 }
11730#endif
11731 return true;
11732}
11733
11734#define objspace_malloc_increase(...) \
11735 for (bool malloc_increase_done = objspace_malloc_increase_report(__VA_ARGS__); \
11736 !malloc_increase_done; \
11737 malloc_increase_done = objspace_malloc_increase_body(__VA_ARGS__))
11738
11739struct malloc_obj_info { /* 4 words */
11740 size_t size;
11741};
11742
11743static inline size_t
11744objspace_malloc_prepare(rb_objspace_t *objspace, size_t size)
11745{
11746 if (size == 0) size = 1;
11747
11748#if CALC_EXACT_MALLOC_SIZE
11749 size += sizeof(struct malloc_obj_info);
11750#endif
11751
11752 return size;
11753}
11754
11755static bool
11756malloc_during_gc_p(rb_objspace_t *objspace)
11757{
11758 /* malloc is not allowed during GC when we're not using multiple ractors
11759 * (since ractors can run while another thread is sweeping) and when we
11760 * have the GVL (since if we don't have the GVL, we'll try to acquire the
11761 * GVL which will block and ensure the other thread finishes GC). */
11762 return during_gc && !dont_gc_val() && !rb_gc_multi_ractor_p() && ruby_thread_has_gvl_p();
11763}
11764
11765static inline void *
11766objspace_malloc_fixup(rb_objspace_t *objspace, void *mem, size_t size, bool gc_allowed)
11767{
11768 size = objspace_malloc_size(objspace, mem, size);
11769 objspace_malloc_increase(objspace, mem, size, 0, MEMOP_TYPE_MALLOC, gc_allowed) {}
11770
11771#if CALC_EXACT_MALLOC_SIZE
11772 {
11773 struct malloc_obj_info *info = mem;
11774 info->size = size;
11775 mem = info + 1;
11776 }
11777#endif
11778
11779 return mem;
11780}
11781
11782#if defined(__GNUC__) && RUBY_DEBUG
11783#define RB_BUG_INSTEAD_OF_RB_MEMERROR 1
11784#endif
11785
11786#ifndef RB_BUG_INSTEAD_OF_RB_MEMERROR
11787# define RB_BUG_INSTEAD_OF_RB_MEMERROR 0
11788#endif
11789
11790#define GC_MEMERROR(...) \
11791 ((RB_BUG_INSTEAD_OF_RB_MEMERROR+0) ? rb_bug("" __VA_ARGS__) : (void)0)
11792
11793#define TRY_WITH_GC(siz, expr) do { \
11794 const gc_profile_record_flag gpr = \
11795 GPR_FLAG_FULL_MARK | \
11796 GPR_FLAG_IMMEDIATE_MARK | \
11797 GPR_FLAG_IMMEDIATE_SWEEP | \
11798 GPR_FLAG_MALLOC; \
11799 /* stress GC must also honor gc_allowed (malloc_gc_disabled) */ \
11800 if (gc_allowed) objspace_malloc_gc_stress(objspace); \
11801 \
11802 if (RB_LIKELY((expr))) { \
11803 /* Success on 1st try */ \
11804 } \
11805 else if (gc_allowed && !garbage_collect_with_gvl(objspace, gpr)) { \
11806 /* @shyouhei thinks this doesn't happen */ \
11807 GC_MEMERROR("TRY_WITH_GC: could not GC"); \
11808 } \
11809 else if ((expr)) { \
11810 /* Success on 2nd try */ \
11811 } \
11812 else { \
11813 GC_MEMERROR("TRY_WITH_GC: could not allocate:" \
11814 "%"PRIdSIZE" bytes for %s", \
11815 siz, # expr); \
11816 } \
11817 } while (0)
11818
11819static void
11820check_malloc_not_in_gc(rb_objspace_t *objspace, const char *msg)
11821{
11822 if (RB_UNLIKELY(malloc_during_gc_p(objspace))) {
11823 dont_gc_on();
11824 during_gc = false;
11825 rb_bug("Cannot %s during GC", msg);
11826 }
11827}
11828
11829void
11830rb_gc_impl_free(void *objspace_ptr, void *ptr, size_t old_size)
11831{
11832 rb_objspace_t *objspace = objspace_ptr;
11833
11834 if (!ptr) {
11835 /*
11836 * ISO/IEC 9899 says "If ptr is a null pointer, no action occurs" since
11837 * its first version. We would better follow.
11838 */
11839 return;
11840 }
11841#if CALC_EXACT_MALLOC_SIZE
11842 struct malloc_obj_info *info = (struct malloc_obj_info *)ptr - 1;
11843#if VERIFY_FREE_SIZE
11844 if (!info->size) {
11845 const char *freeing = gc_freeing_obj_info();
11846 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,
11847 freeing ? ", while freeing " : "", freeing ? freeing : "");
11848 }
11849
11850 if (old_size && (old_size + sizeof(struct malloc_obj_info)) != info->size) {
11851 const char *freeing = gc_freeing_obj_info();
11852 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),
11853 freeing ? ", while freeing " : "", freeing ? freeing : "");
11854 }
11855#endif
11856 ptr = info;
11857 old_size = info->size;
11858#endif
11859 old_size = objspace_malloc_size(objspace, ptr, old_size);
11860
11861 objspace_malloc_increase(objspace, ptr, 0, old_size, MEMOP_TYPE_FREE, true) {
11862 free(ptr);
11863 ptr = NULL;
11864 RB_DEBUG_COUNTER_INC(heap_xfree);
11865 }
11866}
11867
11868void *
11869rb_gc_impl_malloc(void *objspace_ptr, size_t size, bool gc_allowed)
11870{
11871 rb_objspace_t *objspace = objspace_ptr;
11872 check_malloc_not_in_gc(objspace, "malloc");
11873
11874 void *mem;
11875
11876 size = objspace_malloc_prepare(objspace, size);
11877 TRY_WITH_GC(size, mem = malloc(size));
11878 RB_DEBUG_COUNTER_INC(heap_xmalloc);
11879 if (!mem) return mem;
11880 return objspace_malloc_fixup(objspace, mem, size, gc_allowed);
11881}
11882
11883void *
11884rb_gc_impl_calloc(void *objspace_ptr, size_t size, bool gc_allowed)
11885{
11886 rb_objspace_t *objspace = objspace_ptr;
11887
11888 if (RB_UNLIKELY(malloc_during_gc_p(objspace))) {
11889 rb_warn("calloc during GC detected, this could cause crashes if it triggers another GC");
11890#if RGENGC_CHECK_MODE || RUBY_DEBUG
11891 rb_bug("Cannot calloc during GC");
11892#endif
11893 }
11894
11895 void *mem;
11896
11897 size = objspace_malloc_prepare(objspace, size);
11898 TRY_WITH_GC(size, mem = calloc1(size));
11899 if (!mem) return mem;
11900 return objspace_malloc_fixup(objspace, mem, size, gc_allowed);
11901}
11902
11903void *
11904rb_gc_impl_realloc(void *objspace_ptr, void *ptr, size_t new_size, size_t old_size, bool gc_allowed)
11905{
11906 rb_objspace_t *objspace = objspace_ptr;
11907
11908 check_malloc_not_in_gc(objspace, "realloc");
11909
11910 void *mem;
11911
11912 if (!ptr) return rb_gc_impl_malloc(objspace, new_size, gc_allowed);
11913
11914 /*
11915 * The behavior of realloc(ptr, 0) is implementation defined.
11916 * Therefore we don't use realloc(ptr, 0) for portability reason.
11917 * see http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_400.htm
11918 */
11919 if (new_size == 0) {
11920 if ((mem = rb_gc_impl_malloc(objspace, 0, gc_allowed)) != NULL) {
11921 /*
11922 * - OpenBSD's malloc(3) man page says that when 0 is passed, it
11923 * returns a non-NULL pointer to an access-protected memory page.
11924 * The returned pointer cannot be read / written at all, but
11925 * still be a valid argument of free().
11926 *
11927 * https://man.openbsd.org/malloc.3
11928 *
11929 * - Linux's malloc(3) man page says that it _might_ perhaps return
11930 * a non-NULL pointer when its argument is 0. That return value
11931 * is safe (and is expected) to be passed to free().
11932 *
11933 * https://man7.org/linux/man-pages/man3/malloc.3.html
11934 *
11935 * - As I read the implementation jemalloc's malloc() returns fully
11936 * normal 16 bytes memory region when its argument is 0.
11937 *
11938 * - As I read the implementation musl libc's malloc() returns
11939 * fully normal 32 bytes memory region when its argument is 0.
11940 *
11941 * - Other malloc implementations can also return non-NULL.
11942 */
11943 rb_gc_impl_free(objspace, ptr, old_size);
11944 return mem;
11945 }
11946 else {
11947 /*
11948 * It is dangerous to return NULL here, because that could lead to
11949 * RCE. Fallback to 1 byte instead of zero.
11950 *
11951 * https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-11932
11952 */
11953 new_size = 1;
11954 }
11955 }
11956
11957#if CALC_EXACT_MALLOC_SIZE
11958 {
11959 struct malloc_obj_info *info = (struct malloc_obj_info *)ptr - 1;
11960 new_size += sizeof(struct malloc_obj_info);
11961 ptr = info;
11962#if VERIFY_FREE_SIZE
11963 if (old_size && (old_size + sizeof(struct malloc_obj_info)) != info->size) {
11964 const char *freeing = gc_freeing_obj_info();
11965 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),
11966 freeing ? ", while freeing " : "", freeing ? freeing : "");
11967 }
11968#endif
11969 old_size = info->size;
11970 }
11971#endif
11972
11973 old_size = objspace_malloc_size(objspace, ptr, old_size);
11974 TRY_WITH_GC(new_size, mem = RB_GNUC_EXTENSION_BLOCK(realloc(ptr, new_size)));
11975 if (!mem) return mem;
11976 new_size = objspace_malloc_size(objspace, mem, new_size);
11977
11978#if CALC_EXACT_MALLOC_SIZE
11979 {
11980 struct malloc_obj_info *info = mem;
11981 info->size = new_size;
11982 mem = info + 1;
11983 }
11984#endif
11985
11986 objspace_malloc_increase(objspace, mem, new_size, old_size, MEMOP_TYPE_REALLOC, gc_allowed);
11987
11988 RB_DEBUG_COUNTER_INC(heap_xrealloc);
11989 return mem;
11990}
11991
11992void
11993rb_gc_impl_adjust_memory_usage(void *objspace_ptr, ssize_t diff)
11994{
11995 rb_objspace_t *objspace = objspace_ptr;
11996
11997 if (diff > 0) {
11998 objspace_malloc_increase(objspace, 0, diff, 0, MEMOP_TYPE_REALLOC, true);
11999 }
12000 else if (diff < 0) {
12001 objspace_malloc_increase(objspace, 0, 0, -diff, MEMOP_TYPE_REALLOC, true);
12002 }
12003}
12004
12005// TODO: move GC profiler stuff back into gc.c
12006/*
12007 ------------------------------ GC profiler ------------------------------
12008*/
12009
12010#define GC_PROFILE_RECORD_DEFAULT_SIZE 100
12011#define GC_PROFILE_RECORD_DEFAULT_MAX_RECORDS 4096
12012#define GC_PROFILE_RECORD_UNBOUNDED 0
12013
12014static bool
12015current_process_time(struct timespec *ts)
12016{
12017#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_PROCESS_CPUTIME_ID)
12018 {
12019 static int try_clock_gettime = 1;
12020 if (try_clock_gettime) {
12021 if (clock_gettime(CLOCK_PROCESS_CPUTIME_ID, ts) == 0) {
12022 return true;
12023 }
12024 else {
12025 try_clock_gettime = 0;
12026 }
12027 }
12028 }
12029#endif
12030
12031#ifdef RUSAGE_SELF
12032 {
12033 struct rusage usage;
12034 struct timeval time;
12035 if (getrusage(RUSAGE_SELF, &usage) == 0) {
12036 time = usage.ru_utime;
12037 ts->tv_sec = time.tv_sec;
12038 ts->tv_nsec = (int32_t)time.tv_usec * 1000;
12039 return true;
12040 }
12041 }
12042#endif
12043
12044#ifdef _WIN32
12045 {
12046 FILETIME creation_time, exit_time, kernel_time, user_time;
12047 ULARGE_INTEGER ui;
12048
12049 if (GetProcessTimes(GetCurrentProcess(),
12050 &creation_time, &exit_time, &kernel_time, &user_time) != 0) {
12051 memcpy(&ui, &user_time, sizeof(FILETIME));
12052#define PER100NSEC (uint64_t)(1000 * 1000 * 10)
12053 ts->tv_nsec = (long)(ui.QuadPart % PER100NSEC);
12054 ts->tv_sec = (time_t)(ui.QuadPart / PER100NSEC);
12055 return true;
12056 }
12057 }
12058#endif
12059
12060 return false;
12061}
12062
12063static double
12064getrusage_time(void)
12065{
12066 struct timespec ts;
12067 if (current_process_time(&ts)) {
12068 return ts.tv_sec + ts.tv_nsec * 1e-9;
12069 }
12070 else {
12071 return 0.0;
12072 }
12073}
12074
12075static inline double
12076hrtime_to_sec(rb_hrtime_t time)
12077{
12078 return (double)time / (double)RB_HRTIME_PER_SEC;
12079}
12080
12081static inline rb_hrtime_t
12082elapsed_hrtime_from(rb_hrtime_t start)
12083{
12084 return rb_hrtime_sub(rb_hrtime_now(), start);
12085}
12086
12087
12088static inline size_t
12089gc_profile_record_count(rb_objspace_t *objspace)
12090{
12091 return objspace->profile.record_count;
12092}
12093
12094static inline size_t
12095gc_profile_record_index(rb_objspace_t *objspace, size_t logical_index)
12096{
12097 if (objspace->profile.max_records != GC_PROFILE_RECORD_UNBOUNDED &&
12098 objspace->profile.record_count == objspace->profile.size) {
12099 return (objspace->profile.next_index + logical_index) % objspace->profile.size;
12100 }
12101 else {
12102 return logical_index;
12103 }
12104}
12105
12106static void
12107gc_profile_records_free(rb_objspace_t *objspace)
12108{
12109 void *p = objspace->profile.records;
12110 objspace->profile.records = NULL;
12111 objspace->profile.size = 0;
12112 objspace->profile.next_index = 0;
12113 objspace->profile.record_count = 0;
12114 objspace->profile.current_record = 0;
12115 free(p);
12116}
12117
12118static inline void
12119gc_prof_setup_new_record(rb_objspace_t *objspace, unsigned int reason)
12120{
12121 if (objspace->profile.run) {
12122 size_t index;
12123 gc_profile_record *record;
12124
12125 if (objspace->profile.max_records == GC_PROFILE_RECORD_UNBOUNDED) {
12126 index = objspace->profile.record_count++;
12127 objspace->profile.next_index = objspace->profile.record_count;
12128
12129 if (!objspace->profile.records) {
12130 objspace->profile.size = GC_PROFILE_RECORD_DEFAULT_SIZE;
12131 objspace->profile.records = malloc(xmalloc2_size(sizeof(gc_profile_record), objspace->profile.size));
12132 }
12133 if (index >= objspace->profile.size) {
12134 void *ptr;
12135 objspace->profile.size += 1000;
12136 ptr = realloc(objspace->profile.records, xmalloc2_size(sizeof(gc_profile_record), objspace->profile.size));
12137 if (!ptr) rb_memerror();
12138 objspace->profile.records = ptr;
12139 }
12140 }
12141 else {
12142 if (!objspace->profile.records) {
12143 objspace->profile.size = objspace->profile.max_records;
12144 objspace->profile.records = malloc(xmalloc2_size(sizeof(gc_profile_record), objspace->profile.size));
12145 }
12146 index = objspace->profile.next_index;
12147 objspace->profile.next_index = (objspace->profile.next_index + 1) % objspace->profile.size;
12148 if (objspace->profile.record_count < objspace->profile.size) {
12149 objspace->profile.record_count++;
12150 }
12151 }
12152
12153 if (!objspace->profile.records) {
12154 rb_bug("gc_profile malloc or realloc miss");
12155 }
12156 record = objspace->profile.current_record = &objspace->profile.records[index];
12157 MEMZERO(record, gc_profile_record, 1);
12158
12159 /* setup before-GC parameter */
12160 record->flags = reason | (ruby_gc_stressful ? GPR_FLAG_STRESS : 0);
12161 record->sequence = objspace->profile.record_sequence++;
12162 record->gc_invoke_wall_time = rb_hrtime_sub(rb_hrtime_now(),
12163 objspace->profile.invoke_wall_time);
12164#if MALLOC_ALLOCATED_SIZE
12165 record->allocated_size = malloc_allocated_size;
12166#endif
12167#if GC_PROFILE_MORE_DETAIL && GC_PROFILE_DETAIL_MEMORY
12168#ifdef RUSAGE_SELF
12169 {
12170 struct rusage usage;
12171 if (getrusage(RUSAGE_SELF, &usage) == 0) {
12172 record->maxrss = usage.ru_maxrss;
12173 record->minflt = usage.ru_minflt;
12174 record->majflt = usage.ru_majflt;
12175 }
12176 }
12177#endif
12178#endif
12179 }
12180}
12181
12182static inline void
12183gc_prof_timer_start(rb_objspace_t *objspace)
12184{
12185 if (gc_prof_enabled(objspace)) {
12186 gc_profile_record *record = gc_prof_record(objspace);
12187#if GC_PROFILE_MORE_DETAIL
12188 record->prepare_time = objspace->profile.prepare_time;
12189#endif
12190 record->gc_time = 0;
12191 record->gc_invoke_time = getrusage_time();
12192 objspace->profile.gc_wall_start_time = rb_hrtime_now();
12193 }
12194}
12195
12196static double
12197elapsed_time_from(double time)
12198{
12199 double now = getrusage_time();
12200 if (now > time) {
12201 return now - time;
12202 }
12203 else {
12204 return 0;
12205 }
12206}
12207
12208static inline void
12209gc_prof_timer_stop(rb_objspace_t *objspace)
12210{
12211 if (gc_prof_enabled(objspace)) {
12212 gc_profile_record *record = gc_prof_record(objspace);
12213 record->gc_time = elapsed_time_from(record->gc_invoke_time);
12214 record->gc_invoke_time -= objspace->profile.invoke_time;
12215 record->gc_wall_time = elapsed_hrtime_from(objspace->profile.gc_wall_start_time);
12216 }
12217}
12218
12219static inline void
12220gc_prof_mark_timer_start(rb_objspace_t *objspace)
12221{
12222 RUBY_DTRACE_GC_HOOK(MARK_BEGIN);
12223#if GC_PROFILE_MORE_DETAIL
12224 if (gc_prof_enabled(objspace)) {
12225 gc_prof_record(objspace)->gc_mark_time = getrusage_time();
12226 }
12227#endif
12228}
12229
12230static inline void
12231gc_prof_mark_timer_stop(rb_objspace_t *objspace)
12232{
12233 RUBY_DTRACE_GC_HOOK(MARK_END);
12234#if GC_PROFILE_MORE_DETAIL
12235 if (gc_prof_enabled(objspace)) {
12236 gc_profile_record *record = gc_prof_record(objspace);
12237 record->gc_mark_time = elapsed_time_from(record->gc_mark_time);
12238 }
12239#endif
12240}
12241
12242static inline void
12243gc_prof_sweep_timer_start(rb_objspace_t *objspace)
12244{
12245 RUBY_DTRACE_GC_HOOK(SWEEP_BEGIN);
12246 if (gc_prof_enabled(objspace)) {
12247 gc_profile_record *record = gc_prof_record(objspace);
12248
12249 if (record->gc_time > 0 || GC_PROFILE_MORE_DETAIL) {
12250 objspace->profile.gc_sweep_start_time = getrusage_time();
12251 objspace->profile.gc_sweep_wall_start_time = rb_hrtime_now();
12252 }
12253 }
12254}
12255
12256static inline void
12257gc_prof_sweep_timer_stop(rb_objspace_t *objspace)
12258{
12259 RUBY_DTRACE_GC_HOOK(SWEEP_END);
12260
12261 if (gc_prof_enabled(objspace)) {
12262 double sweep_time;
12263 gc_profile_record *record = gc_prof_record(objspace);
12264
12265 if (record->gc_time > 0) {
12266 sweep_time = elapsed_time_from(objspace->profile.gc_sweep_start_time);
12267 /* need to accumulate GC time for lazy sweep after gc() */
12268 record->gc_time += sweep_time;
12269 record->gc_wall_time = rb_hrtime_add(record->gc_wall_time,
12270 elapsed_hrtime_from(objspace->profile.gc_sweep_wall_start_time));
12271 }
12272 else if (GC_PROFILE_MORE_DETAIL) {
12273 sweep_time = elapsed_time_from(objspace->profile.gc_sweep_start_time);
12274 }
12275
12276#if GC_PROFILE_MORE_DETAIL
12277 record->gc_sweep_time += sweep_time;
12278 if (heap_pages_deferred_final) record->flags |= GPR_FLAG_HAVE_FINALIZE;
12279#endif
12280 if (heap_pages_deferred_final) objspace->profile.latest_gc_info |= GPR_FLAG_HAVE_FINALIZE;
12281 }
12282}
12283
12284static inline void
12285gc_prof_set_malloc_info(rb_objspace_t *objspace)
12286{
12287#if GC_PROFILE_MORE_DETAIL
12288 if (gc_prof_enabled(objspace)) {
12289 gc_profile_record *record = gc_prof_record(objspace);
12290 record->allocate_increase = malloc_increase;
12291 record->allocate_limit = malloc_limit;
12292 }
12293#endif
12294}
12295
12296static inline void
12297gc_prof_set_heap_info(rb_objspace_t *objspace)
12298{
12299 if (gc_prof_enabled(objspace)) {
12300 gc_profile_record *record = gc_prof_record(objspace);
12301
12302 /* Sum across all size pools since each has a different slot size. */
12303 size_t total = 0;
12304 size_t use_size = 0;
12305 size_t total_size = 0;
12306 for (int i = 0; i < HEAP_COUNT; i++) {
12307 rb_heap_t *heap = &heaps[i];
12308 size_t heap_live = heap->total_allocated_objects - heap->total_freed_objects - heap->final_slots_count;
12309 total += heap->total_slots;
12310 use_size += heap_live * heap->slot_size;
12311 total_size += heap->total_slots * heap->slot_size;
12312 }
12313
12314#if GC_PROFILE_MORE_DETAIL
12315 size_t live = objspace->profile.total_allocated_objects_at_gc_start - total_freed_objects(objspace);
12316 record->heap_use_pages = objspace->profile.heap_used_at_gc_start;
12317 record->heap_live_objects = live;
12318 record->heap_free_objects = total - live;
12319#endif
12320
12321 record->heap_total_objects = total;
12322 record->heap_use_size = use_size;
12323 record->heap_total_size = total_size;
12324 }
12325}
12326
12327/*
12328 * call-seq:
12329 * GC::Profiler.clear -> nil
12330 *
12331 * Clears the \GC profiler data.
12332 *
12333 */
12334
12335static VALUE
12336gc_profile_clear(VALUE _)
12337{
12338 rb_objspace_t *objspace = rb_gc_get_objspace();
12339 gc_profile_records_free(objspace);
12340 return Qnil;
12341}
12342
12343/*
12344 * call-seq:
12345 * GC::Profiler.configure(max_records: 4096) -> nil
12346 *
12347 * Configures how many raw profile records are retained by
12348 * GC::Profiler.raw_data.
12349 *
12350 * The profiler keeps at most +max_records+ records in a bounded ring buffer.
12351 * When the buffer is full, newer GC records overwrite the oldest retained
12352 * records. The default limit is 4096 records.
12353 *
12354 * Pass +nil+ to restore the historical unbounded behavior:
12355 *
12356 * GC::Profiler.configure(max_records: nil)
12357 *
12358 * Changing +max_records+ clears existing raw profile data. This method does
12359 * not enable or disable the profiler; use GC::Profiler.enable and
12360 * GC::Profiler.disable for that.
12361 */
12362
12363static VALUE
12364gc_profile_configure(int argc, VALUE *argv, VALUE _)
12365{
12366 static ID keywords[1] = {0};
12367 VALUE options, max_records;
12368 rb_objspace_t *objspace = rb_gc_get_objspace();
12369
12370 if (!keywords[0]) {
12371 keywords[0] = rb_intern("max_records");
12372 }
12373
12374 rb_scan_args_kw(rb_keyword_given_p(), argc, argv, ":", &options);
12375 rb_get_kwargs(options, keywords, 0, 1, &max_records);
12376
12377 if (max_records == Qundef) {
12378 return Qnil;
12379 }
12380 else if (NIL_P(max_records)) {
12381 objspace->profile.max_records = GC_PROFILE_RECORD_UNBOUNDED;
12382 }
12383 else {
12384 long value = NUM2LONG(max_records);
12385 if (value <= 0) {
12386 rb_raise(rb_eArgError, "max_records must be positive or nil");
12387 }
12388 objspace->profile.max_records = (size_t)value;
12389 }
12390
12391 gc_profile_records_free(objspace);
12392 return Qnil;
12393}
12394
12395/*
12396 * call-seq:
12397 * GC::Profiler.raw_data(limit: nil, since: nil) -> [Hash, ...]
12398 *
12399 * Returns an Array of retained raw profile data Hashes ordered from earliest
12400 * to latest by +:GC_INVOKE_TIME+. +limit:+ returns at most the newest
12401 * retained records. +since:+ returns records with +:GC_SEQUENCE+ greater
12402 * than the given sequence.
12403 *
12404 * For example:
12405 *
12406 * [
12407 * {
12408 * :GC_TIME=>1.3000000000000858e-05,
12409 * :GC_INVOKE_TIME=>0.010634999999999999,
12410 * :GC_WALL_TIME=>1.4000000000000001e-05,
12411 * :GC_INVOKE_WALL_TIME=>0.010640000000000000,
12412 * :GC_PAUSE_TIME=>1.5000000000000000e-05,
12413 * :GC_STOP_TIME=>1.0000000000000000e-06,
12414 * :GC_STW_TIME=>1.4000000000000001e-05,
12415 * :GC_MARK_WALL_TIME=>9.0000000000000002e-06,
12416 * :GC_SWEEP_WALL_TIME=>5.0000000000000004e-06,
12417 * :GC_COMPACT_WALL_TIME=>0.0000000000000000e+00,
12418 * :HEAP_USE_SIZE=>289640,
12419 * :HEAP_TOTAL_SIZE=>588960,
12420 * :HEAP_TOTAL_OBJECTS=>14724,
12421 * :GC_IS_MARKED=>false
12422 * },
12423 * # ...
12424 * ]
12425 *
12426 * The keys mean:
12427 *
12428 * +:GC_SEQUENCE+::
12429 * Monotonically increasing sequence number for this profiler record.
12430 * +:GC_TIME+::
12431 * CPU time elapsed in seconds for this GC run. This is process CPU time,
12432 * not elapsed wall-clock time.
12433 * +:GC_INVOKE_TIME+::
12434 * CPU time elapsed in seconds from startup to when the GC was invoked.
12435 * +:GC_WALL_TIME+::
12436 * Monotonic wall-clock counterpart to +:GC_TIME+ for this GC record.
12437 * This does not include time spent stopping other ractors before the VM
12438 * enters GC. Use the phase wall-clock fields below for mark, sweep, and
12439 * compaction attribution.
12440 * +:GC_INVOKE_WALL_TIME+::
12441 * Monotonic wall-clock time elapsed in seconds from startup to when the GC
12442 * was invoked.
12443 * +:GC_PAUSE_TIME+::
12444 * Monotonic wall-clock time elapsed in seconds while user execution was
12445 * blocked by this GC entry, including time to stop other ractors. This
12446 * may include time from incremental marking or lazy sweeping continuation
12447 * charged to this record.
12448 * +:GC_STOP_TIME+::
12449 * Monotonic wall-clock time elapsed in seconds stopping other ractors.
12450 * +:GC_STW_TIME+::
12451 * Monotonic wall-clock time elapsed in seconds after other ractors have
12452 * stopped and before the VM exits GC.
12453 * +:GC_MARK_WALL_TIME+::
12454 * Monotonic wall-clock time elapsed in seconds spent marking for this GC
12455 * record, accumulated across incremental marking continuations.
12456 * +:GC_SWEEP_WALL_TIME+::
12457 * Monotonic wall-clock time elapsed in seconds spent sweeping for this GC
12458 * record, accumulated across lazy sweeping continuations. This does not
12459 * include compaction time, which is reported separately as
12460 * +:GC_COMPACT_WALL_TIME+.
12461 * +:GC_COMPACT_WALL_TIME+::
12462 * Monotonic wall-clock time elapsed in seconds spent compacting for this GC
12463 * record, or +0.0+ if this GC did not compact.
12464 * +:HEAP_USE_SIZE+::
12465 * Total bytes of heap used
12466 * +:HEAP_TOTAL_SIZE+::
12467 * Total size of heap in bytes
12468 * +:HEAP_TOTAL_OBJECTS+::
12469 * Total number of objects
12470 * +:GC_IS_MARKED+::
12471 * Returns +true+ if the GC is in mark phase
12472 *
12473 * The wall-clock timing fields relate to each other as follows:
12474 *
12475 * GC_PAUSE_TIME == GC_STOP_TIME + GC_STW_TIME
12476 *
12477 * +:GC_MARK_WALL_TIME+, +:GC_SWEEP_WALL_TIME+, and +:GC_COMPACT_WALL_TIME+
12478 * report separate phase timings and must not be added to +:GC_WALL_TIME+.
12479 *
12480 * +:GC_WALL_TIME+ is the wall-clock counterpart to +:GC_TIME+ and is nested
12481 * inside +:GC_STW_TIME+, so it must not be added to +:GC_STW_TIME+. The difference
12482 * +GC_STW_TIME - GC_WALL_TIME+ is VM overhead inside the stopped interval
12483 * (GC event hooks, bookkeeping, consistency checks, and continuation work).
12484 *
12485 * If ruby was built with +GC_PROFILE_MORE_DETAIL+, you will also have access
12486 * to the following hash keys:
12487 *
12488 * +:GC_MARK_TIME+::
12489 * +:GC_SWEEP_TIME+::
12490 * +:ALLOCATE_INCREASE+::
12491 * +:ALLOCATE_LIMIT+::
12492 * +:HEAP_USE_PAGES+::
12493 * +:HEAP_LIVE_OBJECTS+::
12494 * +:HEAP_FREE_OBJECTS+::
12495 * +:HAVE_FINALIZE+::
12496 *
12497 */
12498
12499static VALUE
12500gc_profile_record_get(int argc, VALUE *argv, VALUE _)
12501{
12502 static ID keywords[2] = {0};
12503 VALUE prof, options, limit_value, since_value;
12504 VALUE gc_profile = rb_ary_new();
12505 size_t i, count, matching = 0, skip = 0, limit = SIZE_MAX, since = 0;
12506 bool use_since = false;
12507 rb_objspace_t *objspace = rb_gc_get_objspace();
12508
12509 if (!keywords[0]) {
12510 keywords[0] = rb_intern("limit");
12511 keywords[1] = rb_intern("since");
12512 }
12513
12514 rb_scan_args_kw(rb_keyword_given_p(), argc, argv, ":", &options);
12515 VALUE values[2] = {Qundef, Qundef};
12516 rb_get_kwargs(options, keywords, 0, 2, values);
12517 limit_value = values[0];
12518 since_value = values[1];
12519
12520 if (limit_value != Qundef && !NIL_P(limit_value)) {
12521 long value = NUM2LONG(limit_value);
12522 if (value < 0) {
12523 rb_raise(rb_eArgError, "limit must be non-negative");
12524 }
12525 limit = (size_t)value;
12526 }
12527 if (since_value != Qundef && !NIL_P(since_value)) {
12528 long value = NUM2LONG(since_value);
12529 if (value < 0) {
12530 rb_raise(rb_eArgError, "since must be non-negative");
12531 }
12532 since = (size_t)value;
12533 use_since = true;
12534 }
12535
12536 if (!objspace->profile.run) {
12537 return Qnil;
12538 }
12539
12540 count = gc_profile_record_count(objspace);
12541 for (i = 0; i < count; i++) {
12542 gc_profile_record *record = &objspace->profile.records[gc_profile_record_index(objspace, i)];
12543 if (!use_since || record->sequence > since) {
12544 matching++;
12545 }
12546 }
12547 if (limit < matching) {
12548 skip = matching - limit;
12549 }
12550
12551 for (i = 0; i < count; i++) {
12552 gc_profile_record *record = &objspace->profile.records[gc_profile_record_index(objspace, i)];
12553 if (use_since && record->sequence <= since) {
12554 continue;
12555 }
12556 if (skip > 0) {
12557 skip--;
12558 continue;
12559 }
12560
12561 prof = rb_hash_new();
12562 rb_hash_aset(prof, ID2SYM(rb_intern("GC_FLAGS")), gc_info_decode(objspace, rb_hash_new(), record->flags));
12563 rb_hash_aset(prof, ID2SYM(rb_intern("GC_SEQUENCE")), SIZET2NUM(record->sequence));
12564 rb_hash_aset(prof, ID2SYM(rb_intern("GC_TIME")), DBL2NUM(record->gc_time));
12565 rb_hash_aset(prof, ID2SYM(rb_intern("GC_INVOKE_TIME")), DBL2NUM(record->gc_invoke_time));
12566 rb_hash_aset(prof, ID2SYM(rb_intern("GC_WALL_TIME")),
12567 DBL2NUM(hrtime_to_sec(record->gc_wall_time)));
12568 rb_hash_aset(prof, ID2SYM(rb_intern("GC_INVOKE_WALL_TIME")),
12569 DBL2NUM(hrtime_to_sec(record->gc_invoke_wall_time)));
12570 rb_hash_aset(prof, ID2SYM(rb_intern("GC_PAUSE_TIME")),
12571 DBL2NUM(hrtime_to_sec(record->gc_pause_time)));
12572 rb_hash_aset(prof, ID2SYM(rb_intern("GC_STOP_TIME")),
12573 DBL2NUM(hrtime_to_sec(record->gc_stop_time)));
12574 rb_hash_aset(prof, ID2SYM(rb_intern("GC_STW_TIME")),
12575 DBL2NUM(hrtime_to_sec(record->gc_stw_time)));
12576 rb_hash_aset(prof, ID2SYM(rb_intern("GC_MARK_WALL_TIME")),
12577 DBL2NUM(hrtime_to_sec(record->gc_mark_wall_time)));
12578 rb_hash_aset(prof, ID2SYM(rb_intern("GC_SWEEP_WALL_TIME")),
12579 DBL2NUM(hrtime_to_sec(record->gc_sweep_wall_time)));
12580 rb_hash_aset(prof, ID2SYM(rb_intern("GC_COMPACT_WALL_TIME")),
12581 DBL2NUM(hrtime_to_sec(record->gc_compact_wall_time)));
12582 rb_hash_aset(prof, ID2SYM(rb_intern("HEAP_USE_SIZE")), SIZET2NUM(record->heap_use_size));
12583 rb_hash_aset(prof, ID2SYM(rb_intern("HEAP_TOTAL_SIZE")), SIZET2NUM(record->heap_total_size));
12584 rb_hash_aset(prof, ID2SYM(rb_intern("HEAP_TOTAL_OBJECTS")), SIZET2NUM(record->heap_total_objects));
12585 rb_hash_aset(prof, ID2SYM(rb_intern("MOVED_OBJECTS")), SIZET2NUM(record->moved_objects));
12586 rb_hash_aset(prof, ID2SYM(rb_intern("GC_IS_MARKED")), Qtrue);
12587#if GC_PROFILE_MORE_DETAIL
12588 rb_hash_aset(prof, ID2SYM(rb_intern("GC_MARK_TIME")), DBL2NUM(record->gc_mark_time));
12589 rb_hash_aset(prof, ID2SYM(rb_intern("GC_SWEEP_TIME")), DBL2NUM(record->gc_sweep_time));
12590 rb_hash_aset(prof, ID2SYM(rb_intern("ALLOCATE_INCREASE")), SIZET2NUM(record->allocate_increase));
12591 rb_hash_aset(prof, ID2SYM(rb_intern("ALLOCATE_LIMIT")), SIZET2NUM(record->allocate_limit));
12592 rb_hash_aset(prof, ID2SYM(rb_intern("HEAP_USE_PAGES")), SIZET2NUM(record->heap_use_pages));
12593 rb_hash_aset(prof, ID2SYM(rb_intern("HEAP_LIVE_OBJECTS")), SIZET2NUM(record->heap_live_objects));
12594 rb_hash_aset(prof, ID2SYM(rb_intern("HEAP_FREE_OBJECTS")), SIZET2NUM(record->heap_free_objects));
12595
12596 rb_hash_aset(prof, ID2SYM(rb_intern("REMOVING_OBJECTS")), SIZET2NUM(record->removing_objects));
12597 rb_hash_aset(prof, ID2SYM(rb_intern("EMPTY_OBJECTS")), SIZET2NUM(record->empty_objects));
12598
12599 rb_hash_aset(prof, ID2SYM(rb_intern("HAVE_FINALIZE")), (record->flags & GPR_FLAG_HAVE_FINALIZE) ? Qtrue : Qfalse);
12600#endif
12601
12602#if RGENGC_PROFILE > 0
12603 rb_hash_aset(prof, ID2SYM(rb_intern("OLD_OBJECTS")), SIZET2NUM(record->old_objects));
12604 rb_hash_aset(prof, ID2SYM(rb_intern("REMEMBERED_NORMAL_OBJECTS")), SIZET2NUM(record->remembered_normal_objects));
12605 rb_hash_aset(prof, ID2SYM(rb_intern("REMEMBERED_SHADY_OBJECTS")), SIZET2NUM(record->remembered_shady_objects));
12606#endif
12607 rb_ary_push(gc_profile, prof);
12608 }
12609
12610 return gc_profile;
12611}
12612
12613#if GC_PROFILE_MORE_DETAIL
12614#define MAJOR_REASON_MAX 0x10
12615
12616static char *
12617gc_profile_dump_major_reason(unsigned int flags, char *buff)
12618{
12619 unsigned int reason = flags & GPR_FLAG_MAJOR_MASK;
12620 int i = 0;
12621
12622 if (reason == GPR_FLAG_NONE) {
12623 buff[0] = '-';
12624 buff[1] = 0;
12625 }
12626 else {
12627#define C(x, s) \
12628 if (reason & GPR_FLAG_MAJOR_BY_##x) { \
12629 buff[i++] = #x[0]; \
12630 if (i >= MAJOR_REASON_MAX) rb_bug("gc_profile_dump_major_reason: overflow"); \
12631 buff[i] = 0; \
12632 }
12633 C(NOFREE, N);
12634 C(OLDGEN, O);
12635 C(SHADY, S);
12636#if RGENGC_ESTIMATE_OLDMALLOC
12637 C(OLDMALLOC, M);
12638#endif
12639#undef C
12640 }
12641 return buff;
12642}
12643#endif
12644
12645
12646
12647static void
12648gc_profile_dump_on(VALUE out, VALUE (*append)(VALUE, VALUE))
12649{
12650 rb_objspace_t *objspace = rb_gc_get_objspace();
12651 size_t count = gc_profile_record_count(objspace);
12652#ifdef MAJOR_REASON_MAX
12653 char reason_str[MAJOR_REASON_MAX];
12654#endif
12655
12656 if (objspace->profile.run && count /* > 1 */) {
12657 size_t i;
12658 const gc_profile_record *record;
12659
12660 append(out, rb_sprintf("GC %"PRIuSIZE" invokes.\n", objspace->profile.count));
12661 append(out, rb_str_new_cstr("Index Invoke Time(sec) Use Size(byte) Total Size(byte) Total Object GC Time(ms)\n"));
12662
12663 for (i = 0; i < count; i++) {
12664 record = &objspace->profile.records[gc_profile_record_index(objspace, i)];
12665 append(out, rb_sprintf("%5"PRIuSIZE" %19.3f %20"PRIuSIZE" %20"PRIuSIZE" %20"PRIuSIZE" %30.20f\n",
12666 i+1, record->gc_invoke_time, record->heap_use_size,
12667 record->heap_total_size, record->heap_total_objects, record->gc_time*1000));
12668 }
12669
12670#if GC_PROFILE_MORE_DETAIL
12671 const char *str = "\n\n" \
12672 "More detail.\n" \
12673 "Prepare Time = Previously GC's rest sweep time\n"
12674 "Index Flags Allocate Inc. Allocate Limit"
12675#if CALC_EXACT_MALLOC_SIZE
12676 " Allocated Size"
12677#endif
12678 " Use Page Mark Time(ms) Sweep Time(ms) Prepare Time(ms) LivingObj FreeObj RemovedObj EmptyObj"
12679#if RGENGC_PROFILE
12680 " OldgenObj RemNormObj RemShadObj"
12681#endif
12682#if GC_PROFILE_DETAIL_MEMORY
12683 " MaxRSS(KB) MinorFLT MajorFLT"
12684#endif
12685 "\n";
12686 append(out, rb_str_new_cstr(str));
12687
12688 for (i = 0; i < count; i++) {
12689 record = &objspace->profile.records[gc_profile_record_index(objspace, i)];
12690 append(out, rb_sprintf("%5"PRIuSIZE" %4s/%c/%6s%c %13"PRIuSIZE" %15"PRIuSIZE
12691#if CALC_EXACT_MALLOC_SIZE
12692 " %15"PRIuSIZE
12693#endif
12694 " %9"PRIuSIZE" %17.12f %17.12f %17.12f %10"PRIuSIZE" %10"PRIuSIZE" %10"PRIuSIZE" %10"PRIuSIZE
12695#if RGENGC_PROFILE
12696 "%10"PRIuSIZE" %10"PRIuSIZE" %10"PRIuSIZE
12697#endif
12698#if GC_PROFILE_DETAIL_MEMORY
12699 "%11ld %8ld %8ld"
12700#endif
12701
12702 "\n",
12703 i+1,
12704 gc_profile_dump_major_reason(record->flags, reason_str),
12705 (record->flags & GPR_FLAG_HAVE_FINALIZE) ? 'F' : '.',
12706 (record->flags & GPR_FLAG_NEWOBJ) ? "NEWOBJ" :
12707 (record->flags & GPR_FLAG_MALLOC) ? "MALLOC" :
12708 (record->flags & GPR_FLAG_METHOD) ? "METHOD" :
12709 (record->flags & GPR_FLAG_CAPI) ? "CAPI__" : "??????",
12710 (record->flags & GPR_FLAG_STRESS) ? '!' : ' ',
12711 record->allocate_increase, record->allocate_limit,
12712#if CALC_EXACT_MALLOC_SIZE
12713 record->allocated_size,
12714#endif
12715 record->heap_use_pages,
12716 record->gc_mark_time*1000,
12717 record->gc_sweep_time*1000,
12718 record->prepare_time*1000,
12719
12720 record->heap_live_objects,
12721 record->heap_free_objects,
12722 record->removing_objects,
12723 record->empty_objects
12724#if RGENGC_PROFILE
12725 ,
12726 record->old_objects,
12727 record->remembered_normal_objects,
12728 record->remembered_shady_objects
12729#endif
12730#if GC_PROFILE_DETAIL_MEMORY
12731 ,
12732 record->maxrss / 1024,
12733 record->minflt,
12734 record->majflt
12735#endif
12736
12737 ));
12738 }
12739#endif
12740 }
12741}
12742
12743/*
12744 * call-seq:
12745 * GC::Profiler.result -> String
12746 *
12747 * Returns a profile data report such as:
12748 *
12749 * GC 1 invokes.
12750 * Index Invoke Time(sec) Use Size(byte) Total Size(byte) Total Object GC time(ms)
12751 * 1 0.012 159240 212940 10647 0.00000000000001530000
12752 */
12753
12754static VALUE
12755gc_profile_result(VALUE _)
12756{
12757 VALUE str = rb_str_buf_new(0);
12758 gc_profile_dump_on(str, rb_str_buf_append);
12759 return str;
12760}
12761
12762/*
12763 * call-seq:
12764 * GC::Profiler.report
12765 * GC::Profiler.report(io)
12766 *
12767 * Writes the GC::Profiler.result to <tt>$stdout</tt> or the given IO object.
12768 *
12769 */
12770
12771static VALUE
12772gc_profile_report(int argc, VALUE *argv, VALUE self)
12773{
12774 VALUE out;
12775
12776 out = (!rb_check_arity(argc, 0, 1) ? rb_stdout : argv[0]);
12777 gc_profile_dump_on(out, rb_io_write);
12778
12779 return Qnil;
12780}
12781
12782/*
12783 * call-seq:
12784 * GC::Profiler.total_time -> float
12785 *
12786 * The total time used for garbage collection in seconds
12787 */
12788
12789static VALUE
12790gc_profile_total_time(VALUE self)
12791{
12792 double time = 0;
12793 rb_objspace_t *objspace = rb_gc_get_objspace();
12794
12795 if (objspace->profile.run && gc_profile_record_count(objspace) > 0) {
12796 size_t i;
12797 size_t count = gc_profile_record_count(objspace);
12798
12799 for (i = 0; i < count; i++) {
12800 time += objspace->profile.records[gc_profile_record_index(objspace, i)].gc_time;
12801 }
12802 }
12803 return DBL2NUM(time);
12804}
12805
12806/*
12807 * call-seq:
12808 * GC::Profiler.enabled? -> true or false
12809 *
12810 * The current status of \GC profile mode.
12811 */
12812
12813static VALUE
12814gc_profile_enable_get(VALUE self)
12815{
12816 rb_objspace_t *objspace = rb_gc_get_objspace();
12817 return objspace->profile.run ? Qtrue : Qfalse;
12818}
12819
12820/*
12821 * call-seq:
12822 * GC::Profiler.enable -> nil
12823 *
12824 * Starts the \GC profiler.
12825 *
12826 */
12827
12828static VALUE
12829gc_profile_enable(VALUE _)
12830{
12831 rb_objspace_t *objspace = rb_gc_get_objspace();
12832 objspace->profile.run = TRUE;
12833 objspace->profile.current_record = 0;
12834 return Qnil;
12835}
12836
12837/*
12838 * call-seq:
12839 * GC::Profiler.disable -> nil
12840 *
12841 * Stops the \GC profiler.
12842 *
12843 */
12844
12845static VALUE
12846gc_profile_disable(VALUE _)
12847{
12848 rb_objspace_t *objspace = rb_gc_get_objspace();
12849
12850 objspace->profile.run = FALSE;
12851 objspace->profile.current_record = 0;
12852 return Qnil;
12853}
12854
12855static void
12856rb_gc_verify_internal_consistency(void)
12857{
12858 gc_verify_internal_consistency(rb_gc_get_objspace());
12859}
12860
12861/*
12862 * call-seq:
12863 * GC.verify_internal_consistency -> nil
12864 *
12865 * Verifies internal consistency of the GC.
12866 * This method should only be used for debugging.
12867 *
12868 * This method is only expected to work on CRuby.
12869 */
12870static VALUE
12871gc_verify_internal_consistency_m(VALUE dummy)
12872{
12873 rb_gc_verify_internal_consistency();
12874 return Qnil;
12875}
12876
12877#if GC_CAN_COMPILE_COMPACTION
12878/*
12879 * call-seq:
12880 * GC.auto_compact = flag
12881 *
12882 * Updates automatic compaction mode.
12883 *
12884 * When enabled, the compactor will execute on every major collection.
12885 *
12886 * Enabling compaction will degrade performance on major collections.
12887 */
12888static VALUE
12889gc_set_auto_compact(VALUE _, VALUE v)
12890{
12891 GC_ASSERT(GC_COMPACTION_SUPPORTED);
12892
12893 ruby_enable_autocompact = RTEST(v);
12894
12895#if RGENGC_CHECK_MODE
12896 ruby_autocompact_compare_func = NULL;
12897
12898 if (SYMBOL_P(v)) {
12899 ID id = RB_SYM2ID(v);
12900 if (id == rb_intern("empty")) {
12901 ruby_autocompact_compare_func = compare_free_slots;
12902 }
12903 }
12904#endif
12905
12906 return v;
12907}
12908#else
12909# define gc_set_auto_compact rb_f_notimplement
12910#endif
12911
12912#if GC_CAN_COMPILE_COMPACTION
12913/*
12914 * call-seq:
12915 * GC.auto_compact -> true or false
12916 *
12917 * Returns whether or not automatic compaction has been enabled.
12918 */
12919static VALUE
12920gc_get_auto_compact(VALUE _)
12921{
12922 return ruby_enable_autocompact ? Qtrue : Qfalse;
12923}
12924#else
12925# define gc_get_auto_compact rb_f_notimplement
12926#endif
12927
12928#if GC_CAN_COMPILE_COMPACTION
12929/*
12930 * call-seq:
12931 * GC.latest_compact_info -> hash
12932 *
12933 * Returns information about object moved in the most recent \GC compaction.
12934 *
12935 * The returned +hash+ contains the following keys:
12936 *
12937 * [considered]
12938 * Hash containing the type of the object as the key and the number of
12939 * objects of that type that were considered for movement.
12940 * [moved]
12941 * Hash containing the type of the object as the key and the number of
12942 * objects of that type that were actually moved.
12943 * [moved_up]
12944 * Hash containing the type of the object as the key and the number of
12945 * objects of that type that were increased in size.
12946 * [moved_down]
12947 * Hash containing the type of the object as the key and the number of
12948 * objects of that type that were decreased in size.
12949 *
12950 * Some objects can't be moved (due to pinning) so these numbers can be used to
12951 * calculate compaction efficiency.
12952 */
12953static VALUE
12954gc_compact_stats(VALUE self)
12955{
12956 rb_objspace_t *objspace = rb_gc_get_objspace();
12957 VALUE h = rb_hash_new();
12958 VALUE considered = rb_hash_new();
12959 VALUE moved = rb_hash_new();
12960 VALUE moved_up = rb_hash_new();
12961 VALUE moved_down = rb_hash_new();
12962
12963 for (size_t i = 0; i < T_MASK; i++) {
12964 if (objspace->rcompactor.considered_count_table[i]) {
12965 rb_hash_aset(considered, type_sym(i), SIZET2NUM(objspace->rcompactor.considered_count_table[i]));
12966 }
12967
12968 if (objspace->rcompactor.moved_count_table[i]) {
12969 rb_hash_aset(moved, type_sym(i), SIZET2NUM(objspace->rcompactor.moved_count_table[i]));
12970 }
12971
12972 if (objspace->rcompactor.moved_up_count_table[i]) {
12973 rb_hash_aset(moved_up, type_sym(i), SIZET2NUM(objspace->rcompactor.moved_up_count_table[i]));
12974 }
12975
12976 if (objspace->rcompactor.moved_down_count_table[i]) {
12977 rb_hash_aset(moved_down, type_sym(i), SIZET2NUM(objspace->rcompactor.moved_down_count_table[i]));
12978 }
12979 }
12980
12981 rb_hash_aset(h, ID2SYM(rb_intern("considered")), considered);
12982 rb_hash_aset(h, ID2SYM(rb_intern("moved")), moved);
12983 rb_hash_aset(h, ID2SYM(rb_intern("moved_up")), moved_up);
12984 rb_hash_aset(h, ID2SYM(rb_intern("moved_down")), moved_down);
12985
12986 return h;
12987}
12988#else
12989# define gc_compact_stats rb_f_notimplement
12990#endif
12991
12992#if GC_CAN_COMPILE_COMPACTION
12993/*
12994 * call-seq:
12995 * GC.compact -> hash
12996 *
12997 * This function compacts objects together in Ruby's heap. It eliminates
12998 * unused space (or fragmentation) in the heap by moving objects in to that
12999 * unused space. If there is more than 1 running Ractor, it runs a global
13000 * GC compaction (all object spaces).
13001 *
13002 * The returned +hash+ contains statistics about the objects that were moved;
13003 * see GC.latest_compact_info.
13004 *
13005 * This method is only expected to work on CRuby.
13006 *
13007 * To test whether \GC compaction is supported, use the idiom:
13008 *
13009 * GC.respond_to?(:compact)
13010 */
13011static VALUE
13012gc_compact(VALUE self)
13013{
13014 rb_objspace_t *objspace = rb_gc_get_objspace();
13015 int full_marking_p = gc_config_full_mark_val;
13016 gc_config_full_mark_set(TRUE);
13017
13018 /* Run GC with compaction enabled */
13019 rb_gc_impl_start(rb_gc_get_objspace(), true, true, true, true, true);
13020 gc_config_full_mark_set(full_marking_p);
13021
13022 return gc_compact_stats(self);
13023}
13024#else
13025# define gc_compact rb_f_notimplement
13026#endif
13027
13028#if GC_CAN_COMPILE_COMPACTION
13029struct desired_compaction_pages_i_data {
13031 size_t required_slots[HEAP_COUNT];
13032};
13033
13034static int
13035desired_compaction_pages_i(struct heap_page *page, void *data)
13036{
13037 struct desired_compaction_pages_i_data *tdata = data;
13038 rb_objspace_t *objspace = tdata->objspace;
13039 VALUE vstart = (VALUE)page->start;
13040 VALUE vend = vstart + (VALUE)(page->total_slots * page->heap->slot_size);
13041
13042
13043 for (VALUE v = vstart; v != vend; v += page->heap->slot_size) {
13044 asan_unpoisoning_object(v) {
13045 /* skip T_NONEs; they won't be moved */
13046 if (BUILTIN_TYPE(v) != T_NONE) {
13047 rb_heap_t *dest_pool = gc_compact_destination_pool(objspace, page->heap, v);
13048 size_t dest_pool_idx = dest_pool - heaps;
13049 tdata->required_slots[dest_pool_idx]++;
13050 }
13051 }
13052 }
13053
13054 return 0;
13055}
13056
13057/* call-seq:
13058 * GC.verify_compaction_references(toward: nil, double_heap: false) -> hash
13059 *
13060 * Verify compaction reference consistency.
13061 *
13062 * This method is implementation specific. During compaction, objects that
13063 * were moved are replaced with T_MOVED objects. No object should have a
13064 * reference to a T_MOVED object after compaction.
13065 *
13066 * This function expands the heap to ensure room to move all objects,
13067 * compacts the heap to make sure everything moves, updates all references,
13068 * then performs a full \GC. If any object contains a reference to a T_MOVED
13069 * object, that object should be pushed on the mark stack, and will
13070 * make a SEGV.
13071 */
13072static VALUE
13073gc_verify_compaction_references(int argc, VALUE* argv, VALUE self)
13074{
13075 static ID keywords[3] = {0};
13076 if (!keywords[0]) {
13077 keywords[0] = rb_intern("toward");
13078 keywords[1] = rb_intern("double_heap");
13079 keywords[2] = rb_intern("expand_heap");
13080 }
13081
13082 VALUE options;
13083 rb_scan_args_kw(rb_keyword_given_p(), argc, argv, ":", &options);
13084
13085 VALUE arguments[3] = { Qnil, Qfalse, Qfalse };
13086 int kwarg_count = rb_get_kwargs(options, keywords, 0, 3, arguments);
13087 bool toward_empty = kwarg_count > 0 && SYMBOL_P(arguments[0]) && SYM2ID(arguments[0]) == rb_intern("empty");
13088 bool expand_heap = (kwarg_count > 1 && RTEST(arguments[1])) || (kwarg_count > 2 && RTEST(arguments[2]));
13089
13090 rb_objspace_t *objspace = rb_gc_get_objspace();
13091
13092 /* This verification machinery (heap expansion, toward_empty page ordering, the
13093 * moved-reference walk) is built for a single objspace, so with several demote it
13094 * to a plain full GC. Plain GC.compact does compact them via the global GC. */
13095 if (!rb_gc_single_objspace_p()) {
13096 rb_gc_impl_start(objspace, true, true, true, false, false);
13097 return gc_compact_stats(self);
13098 }
13099
13100 /* Clear the heap. */
13101 rb_gc_impl_start(objspace, true, true, true, false, false);
13102
13103 unsigned int lev = RB_GC_VM_LOCK();
13104 {
13105 gc_rest(objspace);
13106
13107 /* if both double_heap and expand_heap are set, expand_heap takes precedence */
13108 if (expand_heap) {
13109 struct desired_compaction_pages_i_data desired_compaction = {
13110 .objspace = objspace,
13111 .required_slots = {0},
13112 };
13113 /* Work out how many objects want to be in each size pool, taking account of moves */
13114 objspace_each_pages(objspace, desired_compaction_pages_i, &desired_compaction, TRUE);
13115
13116 /* Find out which pool has the most pages */
13117 size_t max_existing_pages = 0;
13118 for (int i = 0; i < HEAP_COUNT; i++) {
13119 rb_heap_t *heap = &heaps[i];
13120 max_existing_pages = MAX(max_existing_pages, heap->total_pages);
13121 }
13122
13123 /* Add pages to each size pool so that compaction is guaranteed to move every object */
13124 for (int i = 0; i < HEAP_COUNT; i++) {
13125 rb_heap_t *heap = &heaps[i];
13126
13127 size_t pages_to_add = 0;
13128 /*
13129 * Step 1: Make sure every pool has the same number of pages, by adding empty pages
13130 * to smaller pools. This is required to make sure the compact cursor can advance
13131 * through all of the pools in `gc_sweep_compact` without hitting the "sweep &
13132 * compact cursors met" condition on some pools before fully compacting others
13133 */
13134 pages_to_add += max_existing_pages - heap->total_pages;
13135 /*
13136 * Step 2: Now add additional free pages to each size pool sufficient to hold all objects
13137 * that want to be in that size pool, whether moved into it or moved within it
13138 */
13139 objspace->heap_pages.allocatable_bytes = desired_compaction.required_slots[i] * heap->slot_size;
13140 while (objspace->heap_pages.allocatable_bytes > 0) {
13141 heap_page_allocate_and_initialize(objspace, heap);
13142 }
13143 /*
13144 * Step 3: Add two more pages so that the compact & sweep cursors will meet _after_ all objects
13145 * have been moved, and not on the last iteration of the `gc_sweep_compact` loop
13146 */
13147 pages_to_add += 2;
13148
13149 for (; pages_to_add > 0; pages_to_add--) {
13150 heap_page_allocate_and_initialize_force(objspace, heap);
13151 }
13152 }
13153 }
13154
13155 if (toward_empty) {
13156 objspace->rcompactor.compare_func = compare_free_slots;
13157 }
13158 }
13159 RB_GC_VM_UNLOCK(lev);
13160
13161 rb_gc_impl_start(rb_gc_get_objspace(), true, true, true, true, false);
13162
13163 rb_objspace_reachable_objects_from_root(root_obj_check_moved_i, objspace);
13164 objspace_each_objects(objspace, heap_check_moved_i, objspace, TRUE);
13165
13166 objspace->rcompactor.compare_func = NULL;
13167
13168 return gc_compact_stats(self);
13169}
13170#else
13171# define gc_verify_compaction_references rb_f_notimplement
13172#endif
13173
13174void
13175rb_gc_impl_objspace_free(void *objspace_ptr)
13176{
13177 rb_objspace_t *objspace = objspace_ptr;
13178
13179 if (is_lazy_sweeping(objspace))
13180 rb_bug("lazy sweeping underway when freeing object space");
13181
13182 free(objspace->profile.records);
13183 objspace->profile.records = NULL;
13184
13185 for (size_t i = 0; i < rb_darray_size(objspace->heap_pages.sorted); i++) {
13186 heap_page_free(objspace, rb_darray_get(objspace->heap_pages.sorted, i));
13187 }
13188 rb_darray_free_without_gc(objspace->heap_pages.sorted);
13189 heap_pages_lomem = 0;
13190 heap_pages_himem = 0;
13191
13192 for (int i = 0; i < HEAP_COUNT; i++) {
13193 rb_heap_t *heap = &heaps[i];
13194 heap->total_pages = 0;
13195 heap->total_slots = 0;
13196 }
13197
13198 free_stack_chunks(&objspace->mark_stack);
13199 mark_stack_free_cache(&objspace->mark_stack);
13200
13201 rb_darray_free_without_gc(objspace->weak_references);
13202
13203#ifdef MALLOC_COUNTERS_NEED_LOCK
13204 rb_native_mutex_destroy(&objspace->malloc_counters.lock);
13205#endif
13206
13207 rb_native_mutex_destroy(&objspace->process_stat.lock);
13208
13209 free(objspace);
13210}
13211
13212#if MALLOC_ALLOCATED_SIZE
13213/*
13214 * call-seq:
13215 * GC.malloc_allocated_size -> Integer
13216 *
13217 * Returns the size of memory allocated by malloc().
13218 *
13219 * Only available if ruby was built with +CALC_EXACT_MALLOC_SIZE+.
13220 */
13221
13222static VALUE
13223gc_malloc_allocated_size(VALUE self)
13224{
13225 rb_objspace_t *objspace = (rb_objspace_t *)rb_gc_get_objspace();
13226 return ULL2NUM(objspace->malloc_params.allocated_size);
13227}
13228
13229/*
13230 * call-seq:
13231 * GC.malloc_allocations -> Integer
13232 *
13233 * Returns the number of malloc() allocations.
13234 *
13235 * Only available if ruby was built with +CALC_EXACT_MALLOC_SIZE+.
13236 */
13237
13238static VALUE
13239gc_malloc_allocations(VALUE self)
13240{
13241 rb_objspace_t *objspace = (rb_objspace_t *)rb_gc_get_objspace();
13242 return ULL2NUM(objspace->malloc_params.allocations);
13243}
13244#endif
13245
13246void
13247rb_gc_impl_before_fork(void *objspace_ptr)
13248{
13249 rb_objspace_t *objspace = objspace_ptr;
13250
13251 objspace->fork_vm_lock_lev = RB_GC_VM_LOCK();
13252 rb_gc_vm_barrier();
13253}
13254
13255void
13256rb_gc_impl_after_fork(void *objspace_ptr, rb_pid_t pid)
13257{
13258 rb_objspace_t *objspace = objspace_ptr;
13259
13260 if (pid == 0) {
13261 rb_gc_vm_each_objspace(gc_process_stat_after_fork_i, NULL);
13262 }
13263
13264 RB_GC_VM_UNLOCK(objspace->fork_vm_lock_lev);
13265 objspace->fork_vm_lock_lev = 0;
13266
13267 if (pid == 0) { /* child process */
13268 heap_alloc_state_clear(objspace);
13269 /* The forking Ractor becomes the child process's main Ractor. */
13270 global_objspace->main_objspace = objspace;
13271 page_pool_lock_initialize(&rb_global_objspace_instance.page_pool.lock);
13272 }
13273}
13274
13275VALUE rb_ident_hash_new_capa(long size);
13276
13277#if GC_DEBUG_STRESS_TO_CLASS
13278/*
13279 * call-seq:
13280 * GC.add_stress_to_class(class[, ...])
13281 *
13282 * Raises NoMemoryError when allocating an instance of the given classes.
13283 *
13284 */
13285static VALUE
13286rb_gcdebug_add_stress_to_class(int argc, VALUE *argv, VALUE self)
13287{
13288 rb_objspace_t *objspace = rb_gc_get_objspace();
13289
13290 if (!stress_to_class) {
13291 set_stress_to_class(rb_ident_hash_new_capa(argc));
13292 }
13293
13294 for (int i = 0; i < argc; i++) {
13295 VALUE klass = argv[i];
13296 rb_hash_aset(stress_to_class, klass, Qtrue);
13297 }
13298
13299 return self;
13300}
13301
13302/*
13303 * call-seq:
13304 * GC.remove_stress_to_class(class[, ...])
13305 *
13306 * No longer raises NoMemoryError when allocating an instance of the
13307 * given classes.
13308 *
13309 */
13310static VALUE
13311rb_gcdebug_remove_stress_to_class(int argc, VALUE *argv, VALUE self)
13312{
13313 rb_objspace_t *objspace = rb_gc_get_objspace();
13314
13315 if (stress_to_class) {
13316 for (int i = 0; i < argc; ++i) {
13317 rb_hash_delete(stress_to_class, argv[i]);
13318 }
13319
13320 if (rb_hash_size(stress_to_class) == 0) {
13321 stress_to_class = 0;
13322 }
13323 }
13324
13325 return Qnil;
13326}
13327#endif
13328
13329void *
13330rb_gc_impl_objspace_alloc(void)
13331{
13332 global_objspace_init();
13333
13334 rb_objspace_t *objspace = calloc1(sizeof(rb_objspace_t));
13335 if (objspace) {
13336 rb_native_mutex_initialize(&objspace->process_stat.lock);
13337 }
13338
13339 return objspace;
13340}
13341
13342void
13343rb_gc_impl_objspace_init(void *objspace_ptr)
13344{
13345 rb_objspace_t *objspace = objspace_ptr;
13346
13347 gc_config_full_mark_set(TRUE);
13348
13349 malloc_limit = gc_params.malloc_limit_min;
13350 objspace->shareable_objects_limit = SHAREABLE_OBJECTS_LIMIT_MIN;
13351#ifdef MALLOC_COUNTERS_NEED_LOCK
13352 rb_native_mutex_initialize(&objspace->malloc_counters.lock);
13353#endif
13354 /* Shared by every objspace. preregister deduplicates on (func, data). */
13355 objspace->finalize_deferred_pjob = rb_postponed_job_preregister(0, gc_finalize_deferred, NULL);
13356 if (objspace->finalize_deferred_pjob == POSTPONED_JOB_HANDLE_INVALID) {
13357 rb_bug("Could not preregister postponed job for GC");
13358 }
13359
13360 gc_tdata_deferred_free_pjob_ensure();
13361
13362 /* A standard RVALUE (RBasic + embedded VALUEs + debug overhead) must fit
13363 * in at least one pool. In debug builds RVALUE_OVERHEAD can push this
13364 * beyond the 48-byte pool into the 64-byte pool, which is fine. */
13365 GC_ASSERT(rb_gc_impl_size_allocatable_p(sizeof(struct RBasic) + sizeof(VALUE[RBIMPL_RVALUE_EMBED_LEN_MAX])));
13366
13367 for (int i = 0; i < HEAP_COUNT; i++) {
13368 rb_heap_t *heap = &heaps[i];
13369
13370 heap->slot_size = pool_slot_sizes[i];
13371
13372 ccan_list_head_init(&heap->pages);
13373 }
13374
13375 if (global_objspace->main_objspace == NULL) {
13376 /* Single-threaded at boot and the first objspace is main's: compute process-wide
13377 * constants once here. A later objspace_init rewriting them, even with equal
13378 * values, would race other threads' lock-free reads. */
13379 global_objspace->main_objspace = objspace;
13380
13381 init_size_to_heap_idx();
13382
13383#if defined(INIT_HEAP_PAGE_ALLOC_USE_MMAP)
13384 /* Need to determine if we can use mmap at runtime. */
13385 heap_page_alloc_use_mmap = INIT_HEAP_PAGE_ALLOC_USE_MMAP;
13386#endif
13387 gc_params.heap_init_bytes = GC_HEAP_INIT_BYTES;
13388 gc_params.ractor_heap_init_bytes = GC_RACTOR_HEAP_INIT_BYTES ? GC_RACTOR_HEAP_INIT_BYTES
13389 : heap_init_bytes_min();
13390 }
13391 // GC.measure_total_time= sets the caller's objspace only; a new Ractor's follows
13392 // its creator's, which is the objspace running this init (main starts it on).
13393 objspace->flags.measure_gc = global_objspace->main_objspace == objspace ? true
13394 : ((rb_objspace_t *)rb_gc_get_objspace())->flags.measure_gc;
13395
13396 rb_darray_make_without_gc(&objspace->heap_pages.sorted, 0);
13397 rb_darray_make_without_gc(&objspace->weak_references, 0);
13398
13399#if RGENGC_ESTIMATE_OLDMALLOC
13400 objspace->rgengc.oldmalloc_increase_limit = gc_params.oldmalloc_limit_min;
13401#endif
13402
13403 init_mark_stack(&objspace->mark_stack);
13404
13405 objspace->profile.invoke_time = getrusage_time();
13406 objspace->profile.invoke_wall_time = rb_hrtime_now();
13407 objspace->profile.max_records = GC_PROFILE_RECORD_DEFAULT_MAX_RECORDS;
13408 finalizer_table = st_init_numtable();
13409
13410 gc_process_stat_publish(objspace);
13411}
13412
13413void
13414rb_gc_impl_init(void)
13415{
13416 /* Fill the symbol tables here, where no other ractor exists yet: they used to
13417 * be filled on first use, guarded by their own first element, so a second
13418 * ractor could see a half-filled table and GC.stat raised on the rest. */
13419 setup_gc_stat_symbols();
13420 setup_gc_stat_heap_symbols();
13421 setup_gc_latest_gc_info_symbols();
13422
13423 VALUE gc_constants = rb_hash_new();
13424 rb_hash_aset(gc_constants, ID2SYM(rb_intern("DEBUG")), GC_DEBUG ? Qtrue : Qfalse);
13425 /* Minimum slot size that fits a standard RVALUE */
13426 size_t rvalue_pool = 0;
13427 for (size_t i = 0; i < HEAP_COUNT; i++) {
13428 if (pool_slot_sizes[i] >= RVALUE_SLOT_SIZE) { rvalue_pool = pool_slot_sizes[i]; break; }
13429 }
13430 rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVALUE_SIZE")), SIZET2NUM(rvalue_pool - RVALUE_OVERHEAD));
13431 rb_hash_aset(gc_constants, ID2SYM(rb_intern("RBASIC_SIZE")), SIZET2NUM(sizeof(struct RBasic)));
13432 rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVALUE_OVERHEAD")), SIZET2NUM(RVALUE_OVERHEAD));
13433 rb_hash_aset(gc_constants, ID2SYM(rb_intern("HEAP_PAGE_BITMAP_SIZE")), SIZET2NUM(HEAP_PAGE_BITMAP_SIZE));
13434 rb_hash_aset(gc_constants, ID2SYM(rb_intern("HEAP_PAGE_SIZE")), SIZET2NUM(HEAP_PAGE_SIZE));
13435 rb_hash_aset(gc_constants, ID2SYM(rb_intern("HEAP_COUNT")), LONG2FIX(HEAP_COUNT));
13436 rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVARGC_MAX_ALLOCATE_SIZE")), SIZET2NUM(rb_gc_impl_max_allocation_size()));
13437 rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVALUE_OLD_AGE")), LONG2FIX(RVALUE_OLD_AGE));
13438 if (RB_BUG_INSTEAD_OF_RB_MEMERROR+0) {
13439 rb_hash_aset(gc_constants, ID2SYM(rb_intern("RB_BUG_INSTEAD_OF_RB_MEMERROR")), Qtrue);
13440 }
13441 OBJ_FREEZE(gc_constants);
13442 /* Internal constants in the garbage collector. */
13443 rb_define_const(rb_mGC, "INTERNAL_CONSTANTS", gc_constants);
13444
13445 if (GC_COMPACTION_SUPPORTED) {
13446 rb_define_singleton_method(rb_mGC, "compact", gc_compact, 0);
13447 rb_define_singleton_method(rb_mGC, "auto_compact", gc_get_auto_compact, 0);
13448 rb_define_singleton_method(rb_mGC, "auto_compact=", gc_set_auto_compact, 1);
13449 rb_define_singleton_method(rb_mGC, "latest_compact_info", gc_compact_stats, 0);
13450 rb_define_singleton_method(rb_mGC, "verify_compaction_references", gc_verify_compaction_references, -1);
13451 }
13452 else {
13456 rb_define_singleton_method(rb_mGC, "latest_compact_info", rb_f_notimplement, 0);
13457 rb_define_singleton_method(rb_mGC, "verify_compaction_references", rb_f_notimplement, -1);
13458 }
13459
13460#if GC_DEBUG_STRESS_TO_CLASS
13461 rb_define_singleton_method(rb_mGC, "add_stress_to_class", rb_gcdebug_add_stress_to_class, -1);
13462 rb_define_singleton_method(rb_mGC, "remove_stress_to_class", rb_gcdebug_remove_stress_to_class, -1);
13463#endif
13464
13465 /* internal methods */
13466 rb_define_singleton_method(rb_mGC, "verify_internal_consistency", gc_verify_internal_consistency_m, 0);
13467
13468#if MALLOC_ALLOCATED_SIZE
13469 rb_define_singleton_method(rb_mGC, "malloc_allocated_size", gc_malloc_allocated_size, 0);
13470 rb_define_singleton_method(rb_mGC, "malloc_allocations", gc_malloc_allocations, 0);
13471#endif
13472
13473 /* Document-class: GC::Profiler
13474 *
13475 * The GC profiler provides access to information on GC runs including time,
13476 * length and object space size.
13477 *
13478 * Example:
13479 *
13480 * GC::Profiler.enable
13481 *
13482 * require 'rdoc/rdoc'
13483 *
13484 * GC::Profiler.report
13485 *
13486 * pp GC::Profiler.raw_data
13487 *
13488 * GC::Profiler.disable
13489 *
13490 * GC::Profiler.raw_data returns one Hash per GC run, including CPU time
13491 * fields such as +:GC_TIME+ and wall-clock fields such as +:GC_WALL_TIME+,
13492 * +:GC_PAUSE_TIME+, +:GC_STOP_TIME+, and +:GC_STW_TIME+. +:GC_WALL_TIME+
13493 * is the wall-clock counterpart to +:GC_TIME+, while +:GC_PAUSE_TIME+
13494 * measures how long user execution was blocked by the GC entry.
13495 *
13496 * See also GC.count, GC.malloc_allocated_size and GC.malloc_allocations
13497 */
13498 VALUE rb_mProfiler = rb_define_module_under(rb_mGC, "Profiler");
13499 rb_define_singleton_method(rb_mProfiler, "enabled?", gc_profile_enable_get, 0);
13500 rb_define_singleton_method(rb_mProfiler, "enable", gc_profile_enable, 0);
13501 rb_define_singleton_method(rb_mProfiler, "raw_data", gc_profile_record_get, -1);
13502 rb_define_singleton_method(rb_mProfiler, "disable", gc_profile_disable, 0);
13503 rb_define_singleton_method(rb_mProfiler, "clear", gc_profile_clear, 0);
13504 rb_define_singleton_method(rb_mProfiler, "configure", gc_profile_configure, -1);
13505 rb_define_singleton_method(rb_mProfiler, "result", gc_profile_result, 0);
13506 rb_define_singleton_method(rb_mProfiler, "report", gc_profile_report, -1);
13507 rb_define_singleton_method(rb_mProfiler, "total_time", gc_profile_total_time, 0);
13508
13509 {
13510 VALUE opts;
13511 /* \GC build options */
13512 rb_define_const(rb_mGC, "OPTS", opts = rb_ary_new());
13513#define OPT(o) if (o) rb_ary_push(opts, rb_interned_str(#o, sizeof(#o) - 1))
13514 OPT(GC_DEBUG);
13515 OPT(USE_RGENGC);
13516 OPT(RGENGC_DEBUG);
13517 OPT(RGENGC_CHECK_MODE);
13518 OPT(RGENGC_PROFILE);
13519 OPT(RGENGC_ESTIMATE_OLDMALLOC);
13520 OPT(GC_PROFILE_MORE_DETAIL);
13521 OPT(GC_ENABLE_LAZY_SWEEP);
13522 OPT(CALC_EXACT_MALLOC_SIZE);
13523 OPT(MALLOC_ALLOCATED_SIZE);
13524 OPT(MALLOC_ALLOCATED_SIZE_CHECK);
13525 OPT(GC_PROFILE_DETAIL_MEMORY);
13526 OPT(GC_COMPACTION_SUPPORTED);
13527#undef OPT
13528 OBJ_FREEZE(opts);
13529 }
13530}
#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:1899
rb_postponed_job_handle_t rb_postponed_job_preregister(unsigned int flags, rb_postponed_job_func_t func, void *data)
Pre-registers a func in Ruby's postponed job preregistration table, returning an opaque handle which ...
Definition vm_trace.c:1865
#define 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:203
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:3879
VALUE rb_str_buf_new(long capa)
Allocates a "string buffer".
Definition string.c:1769
#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:1539
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:912
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