Ruby 4.1.0dev (2026-09-10 revision e25bcba0ed791e4b9210c3d6986211f22510c0b8)
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/vm_map.h"
42
43#ifdef BUILDING_MODULAR_GC
44/* hrtime.h transitively includes internal/time.h -> internal/bits.h, which are
45 * not available to out-of-tree modular GC builds. We only use a monotonic
46 * clock plus saturating add/sub, so provide that subset locally with the same
47 * semantics as hrtime.h. */
48# include <time.h>
49typedef uint64_t rb_hrtime_t;
50# define RB_HRTIME_PER_SEC ((rb_hrtime_t)1000000000)
51
52static inline rb_hrtime_t
53rb_hrtime_now(void)
54{
55# if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
56 struct timespec ts;
57 if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) {
58 return (rb_hrtime_t)ts.tv_sec * RB_HRTIME_PER_SEC + (rb_hrtime_t)ts.tv_nsec;
59 }
60# endif
61 return 0;
62}
63
64static inline rb_hrtime_t
65rb_hrtime_add(rb_hrtime_t a, rb_hrtime_t b)
66{
67 rb_hrtime_t c = a + b;
68 return c < a ? UINT64_MAX : c; /* saturate on overflow */
69}
70
71static inline rb_hrtime_t
72rb_hrtime_sub(rb_hrtime_t a, rb_hrtime_t b)
73{
74 return a < b ? 0 : a - b;
75}
76#else
77# include "hrtime.h"
78#endif
79
80#include "probes.h"
81
82/* cl.exe's traditional preprocessor passes __VA_ARGS__ to a nested macro as
83 * a single argument; the extra expansion re-scans it into separate ones. */
84#define RUBY_DTRACE_GC_HOOK_EXPAND(expr) expr
85#define RUBY_DTRACE_GC_HOOK(name, ...) \
86 do {if (RUBY_DTRACE_GC_##name##_ENABLED()) RUBY_DTRACE_GC_HOOK_EXPAND(RUBY_DTRACE_GC_##name(__VA_ARGS__));} while (0)
87
88#if USE_ZJIT
89# include "gc/default/zjit_fastpath.h"
90#endif
91
92#ifdef BUILDING_MODULAR_GC
93# define RB_DEBUG_COUNTER_INC(_name) ((void)0)
94# define RB_DEBUG_COUNTER_INC_IF(_name, cond) (!!(cond))
95#else
96# include "debug_counter.h"
97#endif
98
99#ifdef BUILDING_MODULAR_GC
100# define rb_asan_poison_object(obj) ((void)(obj))
101# define rb_asan_unpoison_object(obj, newobj_p) ((void)(obj), (void)(newobj_p))
102# define asan_unpoisoning_object(obj) if ((obj) || true)
103# define asan_poison_memory_region(ptr, size) ((void)(ptr), (void)(size))
104# define asan_unpoison_memory_region(ptr, size, malloc_p) ((void)(ptr), (size), (malloc_p))
105# define asan_unpoisoning_memory_region(ptr, size) if ((ptr) || (size) || true)
106
107# define VALGRIND_MAKE_MEM_DEFINED(ptr, size) ((void)(ptr), (void)(size))
108# define VALGRIND_MAKE_MEM_UNDEFINED(ptr, size) ((void)(ptr), (void)(size))
109#else
110# include "internal/sanitizers.h"
111#endif
112
113/* MALLOC_HEADERS_BEGIN */
114#ifndef HAVE_MALLOC_USABLE_SIZE
115# ifdef _WIN32
116# define HAVE_MALLOC_USABLE_SIZE
117# define malloc_usable_size(a) _msize(a)
118# elif defined HAVE_MALLOC_SIZE
119# define HAVE_MALLOC_USABLE_SIZE
120# define malloc_usable_size(a) malloc_size(a)
121# endif
122#endif
123
124#ifdef HAVE_MALLOC_USABLE_SIZE
125# ifdef RUBY_ALTERNATIVE_MALLOC_HEADER
126/* Alternative malloc header is included in ruby/missing.h */
127# elif defined(HAVE_MALLOC_H)
128# include <malloc.h>
129# elif defined(HAVE_MALLOC_NP_H)
130# include <malloc_np.h>
131# elif defined(HAVE_MALLOC_MALLOC_H)
132# include <malloc/malloc.h>
133# endif
134#endif
135
136#ifdef HAVE_MALLOC_TRIM
137# include <malloc.h>
138
139# ifdef __EMSCRIPTEN__
140/* malloc_trim is defined in emscripten/emmalloc.h on emscripten. */
141# include <emscripten/emmalloc.h>
142# endif
143#endif
144
145#ifdef HAVE_MACH_TASK_EXCEPTION_PORTS
146# include <mach/task.h>
147# include <mach/mach_init.h>
148# include <mach/mach_port.h>
149#endif
150
151#ifndef RUBY_DEBUG_LOG
152# define RUBY_DEBUG_LOG(...)
153#endif
154
155#ifndef GC_HEAP_INIT_BYTES
156#define GC_HEAP_INIT_BYTES (2560 * 1024)
157#endif
158#ifndef GC_HEAP_FREE_SLOTS
159#define GC_HEAP_FREE_SLOTS 4096
160#endif
161#ifndef GC_HEAP_GROWTH_FACTOR
162#define GC_HEAP_GROWTH_FACTOR 1.8
163#endif
164#ifndef GC_HEAP_GROWTH_MAX_BYTES
165#define GC_HEAP_GROWTH_MAX_BYTES 0 /* 0 is disable */
166#endif
167#ifndef GC_HEAP_REMEMBERED_WB_UNPROTECTED_OBJECTS_LIMIT_RATIO
168# define GC_HEAP_REMEMBERED_WB_UNPROTECTED_OBJECTS_LIMIT_RATIO 0.01
169#endif
170#ifndef GC_HEAP_OLDOBJECT_LIMIT_FACTOR
171#define GC_HEAP_OLDOBJECT_LIMIT_FACTOR 2.0
172#endif
173
174#ifndef GC_HEAP_FREE_SLOTS_MIN_RATIO
175#define GC_HEAP_FREE_SLOTS_MIN_RATIO 0.20
176#endif
177#ifndef GC_HEAP_FREE_SLOTS_GOAL_RATIO
178#define GC_HEAP_FREE_SLOTS_GOAL_RATIO 0.40
179#endif
180#ifndef GC_HEAP_FREE_SLOTS_MAX_RATIO
181#define GC_HEAP_FREE_SLOTS_MAX_RATIO 0.65
182#endif
183
184#ifndef GC_MALLOC_LIMIT_MIN
185#define GC_MALLOC_LIMIT_MIN (16 * 1024 * 1024 /* 16MB */)
186#endif
187#ifndef GC_MALLOC_LIMIT_MAX
188#define GC_MALLOC_LIMIT_MAX (32 * 1024 * 1024 /* 32MB */)
189#endif
190#ifndef GC_MALLOC_LIMIT_GROWTH_FACTOR
191#define GC_MALLOC_LIMIT_GROWTH_FACTOR 1.4
192#endif
193
194#ifndef GC_OLDMALLOC_LIMIT_MIN
195#define GC_OLDMALLOC_LIMIT_MIN (16 * 1024 * 1024 /* 16MB */)
196#endif
197#ifndef GC_OLDMALLOC_LIMIT_GROWTH_FACTOR
198#define GC_OLDMALLOC_LIMIT_GROWTH_FACTOR 1.2
199#endif
200#ifndef GC_OLDMALLOC_LIMIT_MAX
201#define GC_OLDMALLOC_LIMIT_MAX (128 * 1024 * 1024 /* 128MB */)
202#endif
203
204#ifndef GC_MALLOC_INCREASE_LOCAL_THRESHOLD
205#define GC_MALLOC_INCREASE_LOCAL_THRESHOLD (8 * 1024 /* 8KB */)
206#endif
207
208#ifdef RB_THREAD_LOCAL_SPECIFIER
209#define USE_MALLOC_INCREASE_LOCAL 1
210static RB_THREAD_LOCAL_SPECIFIER int malloc_increase_local;
211#else
212#define USE_MALLOC_INCREASE_LOCAL 0
213#endif
214
215#ifndef GC_CAN_COMPILE_COMPACTION
216#if defined(__wasi__) /* WebAssembly doesn't support signals */
217# define GC_CAN_COMPILE_COMPACTION 0
218#else
219# define GC_CAN_COMPILE_COMPACTION 1
220#endif
221#endif
222
223#ifndef PRINT_ENTER_EXIT_TICK
224# define PRINT_ENTER_EXIT_TICK 0
225#endif
226#ifndef PRINT_ROOT_TICKS
227#define PRINT_ROOT_TICKS 0
228#endif
229
230#define USE_TICK_T (PRINT_ENTER_EXIT_TICK || PRINT_ROOT_TICKS)
231
232#ifndef HEAP_COUNT
233# if SIZEOF_VALUE >= 8
234# define HEAP_COUNT 12
235# else
236# define HEAP_COUNT 5
237# endif
238#endif
239
240/* The reciprocal table and pool_slot_sizes array are both generated from this
241 * single definition, so they can never get out of sync. */
242#if SIZEOF_VALUE >= 8
243# define EACH_POOL_SLOT_SIZE(SLOT) \
244 SLOT(32) SLOT(40) SLOT(64) SLOT(80) SLOT(96) SLOT(128) \
245 SLOT(160) SLOT(256) SLOT(512) SLOT(640) SLOT(768) SLOT(1024)
246#else
247# define EACH_POOL_SLOT_SIZE(SLOT) \
248 SLOT(32) SLOT(64) SLOT(128) SLOT(256) SLOT(512)
249#endif
250
251typedef struct {
252 size_t heap_init_bytes;
253 size_t heap_free_slots;
254 double growth_factor;
255 size_t growth_max_bytes;
256
257 double heap_free_slots_min_ratio;
258 double heap_free_slots_goal_ratio;
259 double heap_free_slots_max_ratio;
260 double uncollectible_wb_unprotected_objects_limit_ratio;
261 double oldobject_limit_factor;
262
263 size_t malloc_limit_min;
264 size_t malloc_limit_max;
265 double malloc_limit_growth_factor;
266
267 size_t oldmalloc_limit_min;
268 size_t oldmalloc_limit_max;
269 double oldmalloc_limit_growth_factor;
271
272static ruby_gc_params_t gc_params = {
273 GC_HEAP_INIT_BYTES,
274 GC_HEAP_FREE_SLOTS,
275 GC_HEAP_GROWTH_FACTOR,
276 GC_HEAP_GROWTH_MAX_BYTES,
277
278 GC_HEAP_FREE_SLOTS_MIN_RATIO,
279 GC_HEAP_FREE_SLOTS_GOAL_RATIO,
280 GC_HEAP_FREE_SLOTS_MAX_RATIO,
281 GC_HEAP_REMEMBERED_WB_UNPROTECTED_OBJECTS_LIMIT_RATIO,
282 GC_HEAP_OLDOBJECT_LIMIT_FACTOR,
283
284 GC_MALLOC_LIMIT_MIN,
285 GC_MALLOC_LIMIT_MAX,
286 GC_MALLOC_LIMIT_GROWTH_FACTOR,
287
288 GC_OLDMALLOC_LIMIT_MIN,
289 GC_OLDMALLOC_LIMIT_MAX,
290 GC_OLDMALLOC_LIMIT_GROWTH_FACTOR,
291};
292
293/* GC_DEBUG:
294 * enable to embed GC debugging information.
295 */
296#ifndef GC_DEBUG
297#define GC_DEBUG 0
298#endif
299
300/* RGENGC_DEBUG:
301 * 1: basic information
302 * 2: remember set operation
303 * 3: mark
304 * 4:
305 * 5: sweep
306 */
307#ifndef RGENGC_DEBUG
308#ifdef RUBY_DEVEL
309#define RGENGC_DEBUG -1
310#else
311#define RGENGC_DEBUG 0
312#endif
313#endif
314#if RGENGC_DEBUG < 0 && !defined(_MSC_VER)
315# define RGENGC_DEBUG_ENABLED(level) (-(RGENGC_DEBUG) >= (level) && ruby_rgengc_debug >= (level))
316#else
317# define RGENGC_DEBUG_ENABLED(level) ((RGENGC_DEBUG) >= (level))
318#endif
319int ruby_rgengc_debug;
320
321/* RGENGC_PROFILE
322 * 0: disable RGenGC profiling
323 * 1: enable profiling for basic information
324 * 2: enable profiling for each types
325 */
326#ifndef RGENGC_PROFILE
327# define RGENGC_PROFILE 0
328#endif
329
330/* RGENGC_ESTIMATE_OLDMALLOC
331 * Enable/disable to estimate increase size of malloc'ed size by old objects.
332 * If estimation exceeds threshold, then will invoke full GC.
333 * 0: disable estimation.
334 * 1: enable estimation.
335 */
336#ifndef RGENGC_ESTIMATE_OLDMALLOC
337# define RGENGC_ESTIMATE_OLDMALLOC 1
338#endif
339
340#ifndef GC_PROFILE_MORE_DETAIL
341# define GC_PROFILE_MORE_DETAIL 0
342#endif
343#ifndef GC_PROFILE_DETAIL_MEMORY
344# define GC_PROFILE_DETAIL_MEMORY 0
345#endif
346#ifndef GC_ENABLE_LAZY_SWEEP
347# define GC_ENABLE_LAZY_SWEEP 1
348#endif
349
350#ifndef VERIFY_FREE_SIZE
351#if RUBY_DEBUG
352#define VERIFY_FREE_SIZE 1
353#else
354#define VERIFY_FREE_SIZE 0
355#endif
356#endif
357
358#if VERIFY_FREE_SIZE
359#undef CALC_EXACT_MALLOC_SIZE
360#define CALC_EXACT_MALLOC_SIZE 1
361#endif
362
363#ifndef CALC_EXACT_MALLOC_SIZE
364# define CALC_EXACT_MALLOC_SIZE 0
365#endif
366
367#if defined(HAVE_MALLOC_USABLE_SIZE) || CALC_EXACT_MALLOC_SIZE > 0
368# ifndef MALLOC_ALLOCATED_SIZE
369# define MALLOC_ALLOCATED_SIZE 0
370# endif
371#else
372# define MALLOC_ALLOCATED_SIZE 0
373#endif
374#ifndef MALLOC_ALLOCATED_SIZE_CHECK
375# define MALLOC_ALLOCATED_SIZE_CHECK 0
376#endif
377
378#ifndef GC_DEBUG_STRESS_TO_CLASS
379# define GC_DEBUG_STRESS_TO_CLASS RUBY_DEBUG
380#endif
381
382typedef enum {
383 GPR_FLAG_NONE = 0x000,
384 /* major reason */
385 GPR_FLAG_MAJOR_BY_NOFREE = 0x001,
386 GPR_FLAG_MAJOR_BY_OLDGEN = 0x002,
387 GPR_FLAG_MAJOR_BY_SHADY = 0x004,
388 GPR_FLAG_MAJOR_BY_FORCE = 0x008,
389#if RGENGC_ESTIMATE_OLDMALLOC
390 GPR_FLAG_MAJOR_BY_OLDMALLOC = 0x020,
391#endif
392 GPR_FLAG_MAJOR_MASK = 0x0ff,
393
394 /* gc reason */
395 GPR_FLAG_NEWOBJ = 0x100,
396 GPR_FLAG_MALLOC = 0x200,
397 GPR_FLAG_METHOD = 0x400,
398 GPR_FLAG_CAPI = 0x800,
399 GPR_FLAG_STRESS = 0x1000,
400
401 /* others */
402 GPR_FLAG_IMMEDIATE_SWEEP = 0x2000,
403 GPR_FLAG_HAVE_FINALIZE = 0x4000,
404 GPR_FLAG_IMMEDIATE_MARK = 0x8000,
405 GPR_FLAG_FULL_MARK = 0x10000,
406 GPR_FLAG_COMPACT = 0x20000,
407
408 GPR_DEFAULT_REASON =
409 (GPR_FLAG_FULL_MARK | GPR_FLAG_IMMEDIATE_MARK |
410 GPR_FLAG_IMMEDIATE_SWEEP | GPR_FLAG_CAPI),
411} gc_profile_record_flag;
412
413typedef struct gc_profile_record {
414 unsigned int flags;
415 size_t sequence;
416
417 double gc_time;
418 double gc_invoke_time;
419 rb_hrtime_t gc_wall_time;
420 rb_hrtime_t gc_invoke_wall_time;
421 rb_hrtime_t gc_pause_time;
422 rb_hrtime_t gc_stop_time;
423 rb_hrtime_t gc_stw_time;
424 rb_hrtime_t gc_mark_wall_time;
425 rb_hrtime_t gc_sweep_wall_time;
426 rb_hrtime_t gc_compact_wall_time;
427
428 size_t heap_total_objects;
429 size_t heap_use_size;
430 size_t heap_total_size;
431 size_t moved_objects;
432
433#if GC_PROFILE_MORE_DETAIL
434 double gc_mark_time;
435 double gc_sweep_time;
436
437 size_t heap_use_pages;
438 size_t heap_live_objects;
439 size_t heap_free_objects;
440
441 size_t allocate_increase;
442 size_t allocate_limit;
443
444 double prepare_time;
445 size_t removing_objects;
446 size_t empty_objects;
447#if GC_PROFILE_DETAIL_MEMORY
448 long maxrss;
449 long minflt;
450 long majflt;
451#endif
452#endif
453#if MALLOC_ALLOCATED_SIZE
454 size_t allocated_size;
455#endif
456
457#if RGENGC_PROFILE > 0
458 size_t old_objects;
459 size_t remembered_normal_objects;
460 size_t remembered_shady_objects;
461#endif
463
464struct RMoved {
465 VALUE flags;
466 VALUE dummy;
467 VALUE destination;
468};
469
470#define RMOVED(obj) ((struct RMoved *)(obj))
471
472typedef uintptr_t bits_t;
473enum {
474 BITS_SIZE = sizeof(bits_t),
475 BITS_BITLENGTH = ( BITS_SIZE * CHAR_BIT )
476};
477
479 struct heap_page *page;
480};
481
483 struct heap_page_header header;
484 /* char gap[]; */
485 /* RVALUE values[]; */
486};
487
488#define STACK_CHUNK_SIZE 500
489
490typedef struct stack_chunk {
491 VALUE data[STACK_CHUNK_SIZE];
492 struct stack_chunk *next;
494
495typedef struct mark_stack {
496 stack_chunk_t *chunk;
497 stack_chunk_t *cache;
498 int index;
499 int limit;
500 size_t cache_size;
501 size_t unused_cache_size;
503
504typedef int (*gc_compact_compare_func)(const void *l, const void *r, void *d);
505
506typedef struct rb_heap_newobj {
507 uintptr_t alloc_cursor;
508 uintptr_t alloc_cursor_end;
509 struct free_region *alloc_next_region;
510 struct heap_page *alloc_using_page;
512
513typedef struct rb_heap_struct {
514 short slot_size;
515
516 /* Basic statistics */
517 size_t total_allocated_pages;
518 size_t force_major_gc_count;
519 size_t force_incremental_marking_finish_count;
520 size_t total_allocated_objects;
521 size_t total_freed_objects;
522 size_t final_slots_count;
523
524 /* Sweeping statistics */
525 size_t freed_slots;
526 size_t empty_slots;
527
528 /* Bump-pointer allocation state; only this objspace's owner thread writes it. */
529 rb_heap_newobj_t newobj;
530
531 struct heap_page *free_pages;
532 struct ccan_list_head pages;
533 struct heap_page *sweeping_page; /* iterator for .pages */
534 struct heap_page *compact_cursor;
535 uintptr_t compact_cursor_index;
536 struct heap_page *pooled_pages;
537 size_t total_pages; /* total page count in a heap */
538 size_t total_slots; /* total slot count */
539
540} rb_heap_t;
541
542enum {
543 gc_stress_no_major,
544 gc_stress_no_immediate_sweep,
545 gc_stress_full_mark_after_malloc,
546 gc_stress_max
547};
548
549enum gc_mode {
550 gc_mode_none,
551 gc_mode_marking,
552 gc_mode_sweeping,
553 gc_mode_compacting,
554};
555
556typedef rbimpl_atomic_uint64_t gc_counter_t;
557
558#if !defined(HAVE_GCC_ATOMIC_BUILTINS_64) && !defined(_WIN32) && \
559 !(defined(__sun) && defined(HAVE_ATOMIC_H) && (defined(_LP64) || defined(_I32LPx)))
560# define MALLOC_COUNTERS_NEED_LOCK 1
561#endif
562
564 gc_counter_t malloc;
565 gc_counter_t free;
566
567 /* Baselines the increase is measured from: malloc snapshotted at GC start
568 * (gc_reset_malloc_info), free re-snapshotted at gc_sweep_finish so the
569 * sweep's own frees never count. */
570 gc_counter_t malloc_at_last_gc;
571 gc_counter_t free_at_last_gc;
572};
573
574typedef struct rb_objspace {
575 struct {
576 struct gc_malloc_bytes counters;
577#if RGENGC_ESTIMATE_OLDMALLOC
578 struct gc_malloc_bytes oldcounters;
579#endif
580#ifdef MALLOC_COUNTERS_NEED_LOCK
581 rb_nativethread_lock_t lock;
582#endif
583 } malloc_counters;
584
585 struct {
586 size_t limit;
587#if MALLOC_ALLOCATED_SIZE
588 size_t allocated_size;
589 size_t allocations;
590#endif
591 } malloc_params;
592
594 bool full_mark;
595 } gc_config;
596
597 struct {
598 unsigned int mode : 2;
599 unsigned int immediate_sweep : 1;
600 unsigned int dont_gc : 1;
601 /* A user hold from GC.disable (kept in vm->gc.disable_holders); owner thread only. */
602 unsigned int user_gc_disabled : 1;
603 unsigned int dont_incremental : 1;
604 unsigned int during_gc : 1;
605 unsigned int during_global_gc : 1;
606 unsigned int during_compacting : 1;
607 unsigned int gc_lock_barrier : 1;
608 unsigned int during_reference_updating : 1;
609 unsigned int during_minor_gc : 1;
610 unsigned int during_incremental_marking : 1;
611 unsigned int during_postmortem : 1;
612 unsigned int measure_gc : 1;
613 } flags;
614
615 rb_event_flag_t hook_events;
616
617 rb_heap_t heaps[HEAP_COUNT];
618 size_t empty_pages_count;
619 struct heap_page *empty_pages;
620
621 struct {
622 rb_atomic_t finalizing;
623 } atomic_flags;
624
626 size_t marked_slots;
627
628 /* Moved out of the per-Ractor newobj cache: allocation state is per objspace. */
629 size_t incremental_mark_step_allocated_slots;
630
631 /* Inputs of the global GC trigger, all owned by this objspace's thread.
632 * shareable_objects is the live population of shareable objects; exceeding the
633 * limit requests a global GC. */
634 size_t shareable_objects;
635 size_t shareable_objects_limit;
636 /* Whether the last mark ran the pinned walk; the sweep asserts on it. */
637 unsigned char last_cycle_pinned;
638
639 struct {
640 rb_darray(struct heap_page *) sorted;
641
642 size_t allocated_pages;
643 size_t freed_pages;
644 uintptr_t range[2];
645 size_t freeable_pages;
646
647 size_t allocatable_bytes;
648
649 /* final */
650 VALUE deferred_final;
651 } heap_pages;
652
653 st_table *finalizer_table;
654
655 struct {
656 int run;
657 unsigned int latest_gc_info;
658 gc_profile_record *records;
659 gc_profile_record *current_record;
660 size_t next_index;
661 size_t size;
662 size_t record_count;
663 size_t max_records;
664 size_t record_sequence;
665
666#if GC_PROFILE_MORE_DETAIL
667 double prepare_time;
668#endif
669 double invoke_time;
670 rb_hrtime_t invoke_wall_time;
671
672 size_t minor_gc_count;
673 size_t major_gc_count;
674 size_t compact_count;
675 size_t read_barrier_faults;
676#if RGENGC_PROFILE > 0
677 size_t total_generated_normal_object_count;
678 size_t total_generated_shady_object_count;
679 size_t total_shade_operation_count;
680 size_t total_promoted_count;
681 size_t total_remembered_normal_object_count;
682 size_t total_remembered_shady_object_count;
683
684#if RGENGC_PROFILE >= 2
685 size_t generated_normal_object_count_types[RUBY_T_MASK];
686 size_t generated_shady_object_count_types[RUBY_T_MASK];
687 size_t shade_operation_count_types[RUBY_T_MASK];
688 size_t promoted_types[RUBY_T_MASK];
689 size_t remembered_normal_object_count_types[RUBY_T_MASK];
690 size_t remembered_shady_object_count_types[RUBY_T_MASK];
691#endif
692#endif /* RGENGC_PROFILE */
693
694 /* temporary profiling space */
695 double gc_sweep_start_time;
696 rb_hrtime_t gc_wall_start_time;
697 rb_hrtime_t gc_sweep_wall_start_time;
698 rb_hrtime_t gc_sweep_excluded_wall_time;
699 rb_hrtime_t gc_pause_start_time;
700 rb_hrtime_t gc_stw_start_time;
701 rb_hrtime_t gc_stop_time;
702 rb_hrtime_t gc_mark_phase_wall_start_time;
703 rb_hrtime_t gc_sweep_phase_wall_start_time;
704#if GC_PROFILE_MORE_DETAIL
705 size_t total_allocated_objects_at_gc_start;
706 size_t heap_used_at_gc_start;
707#endif
708
709 /* basic statistics */
710 size_t count;
711 unsigned long long marking_time_ns;
712 struct timespec marking_start_time;
713 unsigned long long sweeping_time_ns;
714 struct timespec sweeping_start_time;
715
716 /* Weak references */
717 size_t weak_references_count;
718 } profile;
719
720
721 struct {
722 bool parent_object_old_p;
723 VALUE parent_object;
724
725 int need_major_gc;
726 size_t last_major_gc;
727 size_t uncollectible_wb_unprotected_objects;
728 size_t uncollectible_wb_unprotected_objects_limit;
729 size_t old_objects;
730 size_t old_objects_limit;
731
732#if RGENGC_ESTIMATE_OLDMALLOC
733 size_t oldmalloc_increase_limit;
734#endif
735
736#if RGENGC_CHECK_MODE >= 2
737 struct st_table *allrefs_table;
738 size_t error_count;
739#endif
740 } rgengc;
741
742 struct {
743 size_t considered_count_table[T_MASK];
744 size_t moved_count_table[T_MASK];
745 size_t moved_up_count_table[T_MASK];
746 size_t moved_down_count_table[T_MASK];
747 size_t total_moved;
748
749 /* This function will be used, if set, to sort the heap prior to compaction */
750 gc_compact_compare_func compare_func;
751 } rcompactor;
752
753 struct {
754 size_t pooled_slots;
755 size_t step_slots;
756 } rincgc;
757
758#if GC_DEBUG_STRESS_TO_CLASS
759 VALUE stress_to_class;
760#endif
761
762 rb_darray(VALUE) weak_references;
763 rb_postponed_job_handle_t finalize_deferred_pjob;
764
765
766 int sweeping_heap_count;
767
768 int fork_vm_lock_lev;
769
770 struct rb_gc_vm_context vm_context;
772
773/* The one VM-global GC structure; for now it only holds the page pool. Page bodies are
774 * carved out of large mmap arenas and reused via a process-wide freelist (per-page
775 * mmap/munmap would serialize on the kernel's mmap_lock). Leaf lock: no alloc, no GC. */
776typedef struct rb_global_objspace {
777 struct {
778 rb_nativethread_lock_t lock;
779 struct heap_page_body *hot_list; /* ≤ PAGE_POOL_HOT_MAX un-advised bodies; link at body offset 0 */
780 int hot_count;
781 size_t os_page_size; /* sysconf(_SC_PAGE_SIZE), cached at init */
782 /* List of mmap'd memory regions (arenas) for page bodies. */
783 struct page_arena {
784 struct page_arena *next;
785 char *start; /* usable area, HEAP_PAGE_ALIGN aligned */
786 size_t size; /* usable bytes (a multiple of HEAP_PAGE_SIZE) */
787 struct heap_page_body *cold_freelist; /* free bodies of this arena; link at body offset 0 */
788 int free_count; /* bodies of this arena currently free (hot list + cold_freelist) */
789 int cold_count; /* bodies on cold_freelist (subset of free_count) */
790 } *arenas; /* every arena, newest first */
791 char *arena_cursor; /* first body not yet carved out of the newest arena */
792 char *arena_end; /* end of current arena */
793 struct page_arena *arena_current; /* arena that arena_cursor carves from */
794 int arena_count; /* current mapped arenas (for GC.stat total_pages) */
795 int advised_count; /* page bodies with MADV_* applied (for GC.stat discarded pages) */
796 size_t arenas_unmapped; /* cumulative arenas munmapped (for GC.stat) */
797 } page_pool;
798
799 /* Zombie pages left after the last global cycle (roughly the live data). Updated
800 * under the barrier; readers (gc_need_global_p) may be racy. */
801 size_t zombie_pages_survivors;
802
803 /* An objspace merge (objspace_absorb) is running: suppress the cross-objspace
804 * verifier while the graph is in flux. Written by the absorbing thread, read by
805 * verification with the world stopped. */
806 bool during_absorb;
807
808 /* main's objspace, for gc_enter's locking policy. main_ractor->objspace is swapped
809 * during Ractor creation; this stable pointer decides the same way at both ends of a
810 * GC. Set at boot, re-pointed in a forked child. */
811 rb_objspace_t *main_objspace;
812
813 /* GC.stress is process-global (upstream semantics). Written by GC.stress= in any
814 * Ractor (rare) and read on every Ractor's alloc and GC path; it is diagnostic, so
815 * plain store/load with last-writer-wins is fine. */
816 bool gc_stressful;
817 VALUE gc_stress_mode;
818
819 /* Global GC driver state. compacting is true during the move phase: reference
820 * updates are deferred until all forwarding exists, so a cross-objspace reference is
821 * rewritten exactly once. objspaces is the snapshot being collected. */
822 struct {
823 bool compacting;
824 struct rb_objspace **objspaces;
825 size_t n_objspaces, objspaces_capa;
826 } global_gc;
827
828 /* Index of every objspace's heap pages, ordered by body address. Writers (page
829 * alloc/free) serialize on page_pool.lock; the only reader is a stop-the-world global
830 * GC, so reads need no lock. A local GC uses its own heap_pages.sorted. */
831 struct {
832 struct heap_page **pages;
833 size_t n_pages, capa;
834 uintptr_t lomem, himem;
835 } page_index;
837
838static rb_global_objspace_t rb_global_objspace_instance;
839static rb_global_objspace_t *global_objspace = NULL;
840
841/* The floor keeps a global GC from running as soon as a few shareable objects appear;
842 * the factor follows the rule used for the old-generation limit. */
843#define SHAREABLE_OBJECTS_LIMIT_MIN (1 << 16)
844#define SHAREABLE_OBJECTS_LIMIT_FACTOR 2.0
845/* Start a global GC once terminated, uninherited Ractors hold this many heap pages. A
846 * small Ractor's objspace is about 13 pages, so discarding many of them still stays
847 * below it, while a single fat zombie crosses it. */
848#define ZOMBIE_PAGES_TRIGGER 256
849
850static void objspace_absorb(rb_objspace_t *dst, rb_objspace_t *src);
851
852
853static struct heap_page_body *page_pool_acquire(struct page_arena **arena_out);
854static void page_pool_release(struct heap_page_body *body, struct page_arena *arena);
855#ifdef HAVE_MMAP
856static void page_pool_release_locked(struct heap_page_body *body, struct page_arena *arena);
857#endif
858static void page_pool_reclaim(rb_global_objspace_t *g);
859
860#if RGENGC_CHECK_MODE && !defined(_WIN32) && !defined(__wasi__) && defined(HAVE_PTHREAD_H)
861# define PAGE_POOL_LOCK_ERRORCHECK 1
862#endif
863
864static void
865page_pool_lock_initialize(rb_nativethread_lock_t *lock)
866{
867#ifdef PAGE_POOL_LOCK_ERRORCHECK
868 pthread_mutexattr_t attr;
869 pthread_mutexattr_init(&attr);
870 pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
871 pthread_mutex_init(lock, &attr);
872 pthread_mutexattr_destroy(&attr);
873#else
875#endif
876}
877
878static void
879global_objspace_init(void)
880{
881 if (global_objspace == NULL) {
882 rb_global_objspace_t *g = &rb_global_objspace_instance;
883 page_pool_lock_initialize(&g->page_pool.lock);
884 g->page_pool.hot_list = NULL;
885 g->page_pool.hot_count = 0;
886 g->page_pool.arenas = NULL;
887 g->page_pool.arena_cursor = NULL;
888 g->page_pool.arena_end = NULL;
889 g->page_pool.arena_current = NULL;
890 g->page_pool.arena_count = 0;
891 g->page_pool.advised_count = 0;
892 g->page_pool.arenas_unmapped = 0;
893#ifdef HAVE_MMAP
894 g->page_pool.os_page_size = sysconf(_SC_PAGE_SIZE);
895#else
896 g->page_pool.os_page_size = 0;
897#endif
898 global_objspace = g;
899 }
900}
901
902#ifndef HEAP_PAGE_ALIGN_LOG
903/* default tiny heap size: 64KiB */
904#define HEAP_PAGE_ALIGN_LOG 16
905#endif
906
907#if GC_DEBUG
908struct rvalue_overhead {
909 const char *file;
910 int line;
911};
912
913// Make sure that RVALUE_OVERHEAD aligns to sizeof(VALUE)
914# define RVALUE_OVERHEAD (sizeof(struct { \
915 union { \
916 struct rvalue_overhead overhead; \
917 VALUE value; \
918 }; \
919}))
920size_t rb_gc_impl_obj_slot_size(VALUE obj);
921# define GET_RVALUE_OVERHEAD(obj) ((struct rvalue_overhead *)((uintptr_t)obj + rb_gc_impl_obj_slot_size(obj)))
922#else
923# ifndef RVALUE_OVERHEAD
924# define RVALUE_OVERHEAD 0
925# endif
926#endif
927
928#define RVALUE_SLOT_SIZE (sizeof(struct RBasic) + sizeof(VALUE[RBIMPL_RVALUE_EMBED_LEN_MAX]) + RVALUE_OVERHEAD)
929
930static const size_t pool_slot_sizes[HEAP_COUNT] = {
931#define SLOT(size) ((size) + RVALUE_OVERHEAD),
932 EACH_POOL_SLOT_SIZE(SLOT)
933#undef SLOT
934};
935
936/* Precomputed reciprocals for fast slot index calculation.
937 * For slot size d: reciprocal = ceil(2^48 / d).
938 * Then offset / d == (uint32_t)((offset * reciprocal) >> 48)
939 * for all offset < HEAP_PAGE_SIZE. */
940#define SLOT_RECIPROCAL_SHIFT 48
941#define SLOT_RECIPROCAL(size) (((1ULL << SLOT_RECIPROCAL_SHIFT) + (size) - 1) / (size))
942
943static const uint64_t heap_slot_reciprocal_table[HEAP_COUNT] = {
944#define SLOT(size) SLOT_RECIPROCAL((size) + RVALUE_OVERHEAD),
945 EACH_POOL_SLOT_SIZE(SLOT)
946#undef SLOT
947};
948
949#if SIZEOF_VALUE >= 8
950static uint8_t size_to_heap_idx[1024 / 8 + 1];
951#else
952static uint8_t size_to_heap_idx[512 / 8 + 1];
953#endif
954
955#ifndef MAX
956# define MAX(a, b) (((a) > (b)) ? (a) : (b))
957#endif
958#ifndef MIN
959# define MIN(a, b) (((a) < (b)) ? (a) : (b))
960#endif
961#define roomof(x, y) (((x) + (y) - 1) / (y))
962#define CEILDIV(i, mod) roomof(i, mod)
963#define MIN_POOL_SLOT_SIZE 32
964enum {
965 HEAP_PAGE_ALIGN = (1UL << HEAP_PAGE_ALIGN_LOG),
966 HEAP_PAGE_ALIGN_MASK = (~(~0UL << HEAP_PAGE_ALIGN_LOG)),
967 HEAP_PAGE_SIZE = HEAP_PAGE_ALIGN,
968 HEAP_PAGE_BITMAP_LIMIT = CEILDIV(CEILDIV(HEAP_PAGE_SIZE, MIN_POOL_SLOT_SIZE), BITS_BITLENGTH),
969 HEAP_PAGE_BITMAP_SIZE = (BITS_SIZE * HEAP_PAGE_BITMAP_LIMIT),
970};
971#define HEAP_PAGE_ALIGN (1 << HEAP_PAGE_ALIGN_LOG)
972#define HEAP_PAGE_SIZE HEAP_PAGE_ALIGN
973
974#if !defined(INCREMENTAL_MARK_STEP_ALLOCATIONS)
975# define INCREMENTAL_MARK_STEP_ALLOCATIONS 500
976#endif
977
978#undef INIT_HEAP_PAGE_ALLOC_USE_MMAP
979/* Must define either HEAP_PAGE_ALLOC_USE_MMAP or
980 * INIT_HEAP_PAGE_ALLOC_USE_MMAP. */
981
982#ifndef HAVE_MMAP
983/* We can't use mmap of course, if it is not available. */
984static const bool HEAP_PAGE_ALLOC_USE_MMAP = false;
985
986#elif defined(__wasm__)
987/* wasmtime does not have proper support for mmap.
988 * See https://github.com/bytecodealliance/wasmtime/blob/main/docs/WASI-rationale.md#why-no-mmap-and-friends
989 */
990static const bool HEAP_PAGE_ALLOC_USE_MMAP = false;
991
992#elif HAVE_CONST_PAGE_SIZE
993/* If we have the PAGE_SIZE and it is a constant, then we can directly use it. */
994static const bool HEAP_PAGE_ALLOC_USE_MMAP = (PAGE_SIZE <= HEAP_PAGE_SIZE);
995
996#elif defined(PAGE_MAX_SIZE) && (PAGE_MAX_SIZE <= HEAP_PAGE_SIZE)
997/* If we can use the maximum page size. */
998static const bool HEAP_PAGE_ALLOC_USE_MMAP = true;
999
1000#elif defined(PAGE_SIZE)
1001/* If the PAGE_SIZE macro can be used dynamically. */
1002# define INIT_HEAP_PAGE_ALLOC_USE_MMAP (PAGE_SIZE <= HEAP_PAGE_SIZE)
1003
1004#elif defined(HAVE_SYSCONF) && defined(_SC_PAGE_SIZE)
1005/* If we can use sysconf to determine the page size. */
1006# define INIT_HEAP_PAGE_ALLOC_USE_MMAP (sysconf(_SC_PAGE_SIZE) <= HEAP_PAGE_SIZE)
1007
1008#else
1009/* Otherwise we can't determine the system page size, so don't use mmap. */
1010static const bool HEAP_PAGE_ALLOC_USE_MMAP = false;
1011#endif
1012
1013#ifdef INIT_HEAP_PAGE_ALLOC_USE_MMAP
1014/* We can determine the system page size at runtime. */
1015# define HEAP_PAGE_ALLOC_USE_MMAP (heap_page_alloc_use_mmap != false)
1016
1017static bool heap_page_alloc_use_mmap;
1018#endif
1019
1020#define RVALUE_AGE_BIT_COUNT 2
1021#define RVALUE_AGE_BIT_MASK (((bits_t)1 << RVALUE_AGE_BIT_COUNT) - 1)
1022#define RVALUE_OLD_AGE 3
1023
1025 VALUE flags; /* always 0 for freed obj */
1026 uintptr_t end; /* exclusive end address of the run */
1027 struct free_region *next; /* next free region in the page */
1028};
1029
1031 /* Cache line 0: allocation fast path + SLOT_INDEX */
1032 struct free_region *free_region;
1033 uintptr_t start;
1034 uint64_t slot_size_reciprocal;
1035 unsigned short slot_size;
1036 unsigned short total_slots;
1037 unsigned short free_slots;
1038 unsigned short final_slots;
1039 unsigned short pinned_slots;
1040 /* Page state flags. A bitfield is safe: the only writers are the owning Ractor
1041 * (GVL) and the global GC driver (stop-the-world), never together. has_shareable /
1042 * has_shref hint that the page holds at least one such bit, for re-scanning. */
1043 struct {
1044 unsigned int before_sweep : 1;
1045 unsigned int has_remembered_objects : 1;
1046 unsigned int has_uncollectible_wb_unprotected_objects : 1;
1047 unsigned int has_shref_objects : 1;
1048 unsigned int has_shareable_objects : 1;
1049 } flags;
1050
1051 rb_heap_t *heap;
1052
1053 /* The objspace owning this page, so any object's owner is one load away
1054 * (GET_HEAP_OBJSPACE). Rewritten only when a page changes owner (inheritance). */
1056
1057 struct heap_page *free_next;
1058 struct heap_page_body *body;
1059 struct page_arena *arena;
1060 struct ccan_list_node page_node;
1061
1062 bits_t wb_unprotected_bits[HEAP_PAGE_BITMAP_LIMIT];
1063 /* the following three bitmaps are cleared at the beginning of full GC */
1064 bits_t mark_bits[HEAP_PAGE_BITMAP_LIMIT];
1065 bits_t uncollectible_bits[HEAP_PAGE_BITMAP_LIMIT];
1066 bits_t marking_bits[HEAP_PAGE_BITMAP_LIMIT];
1067
1068 bits_t remembered_bits[HEAP_PAGE_BITMAP_LIMIT];
1069
1070 /* Two extra bits per object. shareable_bits: what a local sweep must never free
1071 * (only a global GC decides a shareable object is dead); set at creation and by
1072 * rb_gc_impl_obj_became_shareable. shref_bits: an unshareable object referenced
1073 * from a shareable one, a local GC root; the write barrier maintains it. */
1074 bits_t shareable_bits[HEAP_PAGE_BITMAP_LIMIT];
1075 bits_t shref_bits[HEAP_PAGE_BITMAP_LIMIT];
1076
1077 /* If set, the object is not movable */
1078 bits_t pinned_bits[HEAP_PAGE_BITMAP_LIMIT];
1079 bits_t age_bits[HEAP_PAGE_BITMAP_LIMIT * RVALUE_AGE_BIT_COUNT];
1080};
1081
1082/*
1083 * When asan is enabled, this will prohibit writing to the freelist until it is unlocked
1084 */
1085static void
1086asan_lock_freelist(struct heap_page *page)
1087{
1088 asan_poison_memory_region(&page->free_region, sizeof(struct free_region *));
1089}
1090
1091/*
1092 * When asan is enabled, this will enable the ability to write to the freelist
1093 */
1094static void
1095asan_unlock_freelist(struct heap_page *page)
1096{
1097 asan_unpoison_memory_region(&page->free_region, sizeof(struct free_region *), false);
1098}
1099
1100static inline bool
1101heap_page_in_global_empty_pages_pool(rb_objspace_t *objspace, struct heap_page *page)
1102{
1103 if (page->total_slots == 0) {
1104 GC_ASSERT(page->start == 0);
1105 GC_ASSERT(page->slot_size == 0);
1106 GC_ASSERT(page->heap == NULL);
1107 GC_ASSERT(page->free_slots == 0);
1108 asan_unpoisoning_memory_region(&page->free_region, sizeof(&page->free_region)) {
1109 GC_ASSERT(page->free_region == NULL);
1110 }
1111
1112 return true;
1113 }
1114 else {
1115 GC_ASSERT(page->start != 0);
1116 GC_ASSERT(page->slot_size != 0);
1117 GC_ASSERT(page->heap != NULL);
1118
1119 return false;
1120 }
1121}
1122
1123#define GET_PAGE_BODY(x) ((struct heap_page_body *)((bits_t)(x) & ~(HEAP_PAGE_ALIGN_MASK)))
1124#define GET_PAGE_HEADER(x) (&GET_PAGE_BODY(x)->header)
1125#define GET_HEAP_PAGE(x) (GET_PAGE_HEADER(x)->page)
1126
1127static inline size_t
1128slot_index_for_offset(size_t offset, uint64_t reciprocal)
1129{
1130 return (uint32_t)(((uint64_t)offset * reciprocal) >> SLOT_RECIPROCAL_SHIFT);
1131}
1132
1133#define SLOT_INDEX(page, p) slot_index_for_offset((uintptr_t)(p) - (page)->start, (page)->slot_size_reciprocal)
1134#define SLOT_BITMAP_INDEX(page, p) (SLOT_INDEX(page, p) / BITS_BITLENGTH)
1135#define SLOT_BITMAP_OFFSET(page, p) (SLOT_INDEX(page, p) & (BITS_BITLENGTH - 1))
1136#define SLOT_BITMAP_BIT(page, p) ((bits_t)1 << SLOT_BITMAP_OFFSET(page, p))
1137
1138#define _MARKED_IN_BITMAP(bits, page, p) ((bits)[SLOT_BITMAP_INDEX(page, p)] & SLOT_BITMAP_BIT(page, p))
1139#define _MARK_IN_BITMAP(bits, page, p) ((bits)[SLOT_BITMAP_INDEX(page, p)] |= SLOT_BITMAP_BIT(page, p))
1140#define _CLEAR_IN_BITMAP(bits, page, p) ((bits)[SLOT_BITMAP_INDEX(page, p)] &= ~SLOT_BITMAP_BIT(page, p))
1141
1142#define MARKED_IN_BITMAP(bits, p) _MARKED_IN_BITMAP(bits, GET_HEAP_PAGE(p), p)
1143#define MARK_IN_BITMAP(bits, p) _MARK_IN_BITMAP(bits, GET_HEAP_PAGE(p), p)
1144#define CLEAR_IN_BITMAP(bits, p) _CLEAR_IN_BITMAP(bits, GET_HEAP_PAGE(p), p)
1145
1146#define GET_HEAP_MARK_BITS(x) (&GET_HEAP_PAGE(x)->mark_bits[0])
1147#define GET_HEAP_PINNED_BITS(x) (&GET_HEAP_PAGE(x)->pinned_bits[0])
1148#define GET_HEAP_UNCOLLECTIBLE_BITS(x) (&GET_HEAP_PAGE(x)->uncollectible_bits[0])
1149#define GET_HEAP_WB_UNPROTECTED_BITS(x) (&GET_HEAP_PAGE(x)->wb_unprotected_bits[0])
1150#define GET_HEAP_MARKING_BITS(x) (&GET_HEAP_PAGE(x)->marking_bits[0])
1151#define GET_HEAP_SHAREABLE_BITS(x) (&GET_HEAP_PAGE(x)->shareable_bits[0])
1152#define GET_HEAP_SHREF_BITS(x) (&GET_HEAP_PAGE(x)->shref_bits[0])
1153#define GET_HEAP_OBJSPACE(x) (GET_HEAP_PAGE(x)->objspace)
1154
1155/* obj lives on a page of another objspace, not the current one (i.e. it is foreign). */
1156static inline bool
1157gc_foreign_object_p(const rb_objspace_t *objspace, VALUE obj)
1158{
1159 return RB_UNLIKELY(GET_HEAP_OBJSPACE(obj) != objspace);
1160}
1161
1162/* Foreign and not inside a stop-the-world global GC. While true, a local GC must not
1163 * touch obj's per-object GC state (mark, pin, remember bits): its owner handles that,
1164 * or the global GC does with everyone stopped. */
1165static inline bool
1166gc_skip_foreign_object_p(const rb_objspace_t *objspace, VALUE obj)
1167{
1168 return gc_foreign_object_p(objspace, obj) && !objspace->flags.during_global_gc;
1169}
1170
1171/* Record obj as shareable on its owning page (bit, page flag and population counter).
1172 * Shared by born-shareable objects and make_shareable. The writer is the owner thread,
1173 * so plain bit operations suffice. */
1174static inline void
1175gc_page_add_shareable(struct heap_page *page, VALUE obj)
1176{
1177 GC_ASSERT(page == GET_HEAP_PAGE(obj));
1178 GC_ASSERT(RB_FL_TEST_RAW(obj, RUBY_FL_SHAREABLE));
1179 _MARK_IN_BITMAP(page->shareable_bits, page, obj);
1180 page->flags.has_shareable_objects = TRUE;
1181 page->objspace->shareable_objects++;
1182}
1183
1184static int
1185RVALUE_AGE_GET(VALUE obj)
1186{
1187 struct heap_page *page = GET_HEAP_PAGE(obj);
1188 bits_t *age_bits = page->age_bits;
1189 size_t slot_idx = SLOT_INDEX(page, obj);
1190 size_t idx = (slot_idx / BITS_BITLENGTH) * 2;
1191 int shift = (int)(slot_idx & (BITS_BITLENGTH - 1));
1192 int lo = (age_bits[idx] >> shift) & 1;
1193 int hi = (age_bits[idx + 1] >> shift) & 1;
1194 return lo | (hi << 1);
1195}
1196
1197static void
1198RVALUE_AGE_SET_BITMAP(VALUE obj, int age)
1199{
1200 RUBY_ASSERT(age <= RVALUE_OLD_AGE);
1201 struct heap_page *page = GET_HEAP_PAGE(obj);
1202 bits_t *age_bits = page->age_bits;
1203 size_t slot_idx = SLOT_INDEX(page, obj);
1204 size_t idx = (slot_idx / BITS_BITLENGTH) * 2;
1205 int shift = (int)(slot_idx & (BITS_BITLENGTH - 1));
1206 bits_t mask = (bits_t)1 << shift;
1207
1208 age_bits[idx] = (age_bits[idx] & ~mask) | ((bits_t)(age & 1) << shift);
1209 age_bits[idx + 1] = (age_bits[idx + 1] & ~mask) | ((bits_t)((age >> 1) & 1) << shift);
1210}
1211
1212static void
1213RVALUE_AGE_SET(VALUE obj, int age)
1214{
1215 RVALUE_AGE_SET_BITMAP(obj, age);
1216 if (age == RVALUE_OLD_AGE) {
1218 }
1219 else {
1221 }
1222}
1223
1224#define malloc_limit objspace->malloc_params.limit
1225#define malloc_increase gc_malloc_counters_increase_unsigned(objspace, &objspace->malloc_counters.counters)
1226#define malloc_allocated_size objspace->malloc_params.allocated_size
1227
1228#ifdef MALLOC_COUNTERS_NEED_LOCK
1229# define MALLOC_COUNTERS_LOCK(o) rb_native_mutex_lock(&(o)->malloc_counters.lock)
1230# define MALLOC_COUNTERS_UNLOCK(o) rb_native_mutex_unlock(&(o)->malloc_counters.lock)
1231#else
1232# define MALLOC_COUNTERS_LOCK(o) ((void)0)
1233# define MALLOC_COUNTERS_UNLOCK(o) ((void)0)
1234#endif
1235
1236static inline void
1237gc_counter_add(gc_counter_t *p, size_t delta)
1238{
1239#ifdef MALLOC_COUNTERS_NEED_LOCK
1240 *p += (gc_counter_t)delta;
1241#else
1242 rbimpl_atomic_u64_fetch_add_relaxed(p, (uint64_t)delta);
1243#endif
1244}
1245
1246static inline gc_counter_t
1247gc_counter_load_relaxed(const gc_counter_t *p)
1248{
1249#ifdef MALLOC_COUNTERS_NEED_LOCK
1250 return *p;
1251#else
1252 return rbimpl_atomic_u64_load_relaxed(p);
1253#endif
1254}
1255
1256static inline gc_counter_t
1257gc_counter_load_acquire(const gc_counter_t *p)
1258{
1259#ifdef MALLOC_COUNTERS_NEED_LOCK
1260 return *p;
1261#else
1262 return rbimpl_atomic_u64_load_acquire(p);
1263#endif
1264}
1265
1266static inline void
1267gc_counter_store_release(gc_counter_t *p, gc_counter_t v)
1268{
1269#ifdef MALLOC_COUNTERS_NEED_LOCK
1270 *p = v;
1271#else
1272 rbimpl_atomic_u64_set_release(p, v);
1273#endif
1274}
1275
1276static inline int64_t
1277gc_malloc_counters_increase(rb_objspace_t *objspace, const struct gc_malloc_bytes *c)
1278{
1279 MALLOC_COUNTERS_LOCK(objspace);
1280 gc_counter_t malloc_at = gc_counter_load_acquire(&c->malloc_at_last_gc);
1281 gc_counter_t free_at = gc_counter_load_acquire(&c->free_at_last_gc);
1282 gc_counter_t malloc_now = gc_counter_load_relaxed(&c->malloc);
1283 gc_counter_t free_now = gc_counter_load_relaxed(&c->free);
1284 MALLOC_COUNTERS_UNLOCK(objspace);
1285
1286 gc_counter_t malloc_delta = malloc_now - malloc_at;
1287 gc_counter_t free_delta = free_now - free_at;
1288
1289 if (malloc_delta >= free_delta) {
1290 return (int64_t)(malloc_delta - free_delta);
1291 }
1292 else {
1293 return -(int64_t)(free_delta - malloc_delta);
1294 }
1295}
1296
1297static inline size_t
1298gc_malloc_counters_increase_unsigned(rb_objspace_t *objspace, const struct gc_malloc_bytes *c)
1299{
1300 int64_t inc = gc_malloc_counters_increase(objspace, c);
1301 if (inc <= 0) return 0;
1302#if SIZEOF_SIZE_T < 8
1303 if ((uint64_t)inc > SIZE_MAX) return SIZE_MAX;
1304#endif
1305 return (size_t)inc;
1306}
1307
1308/* Frees done while sweeping are the GC's own work, not the mutator's: advance
1309 * free_at_last_gc past them so they cannot pay for the next cycle's allocation.
1310 * malloc_at_last_gc stays at gc_reset_malloc_info's snapshot (GC start). */
1311static inline void
1312gc_malloc_counters_snapshot_free_at_last_gc(rb_objspace_t *objspace, struct gc_malloc_bytes *c)
1313{
1314 MALLOC_COUNTERS_LOCK(objspace);
1315 gc_counter_store_release(&c->free_at_last_gc, gc_counter_load_relaxed(&c->free));
1316 MALLOC_COUNTERS_UNLOCK(objspace);
1317}
1318
1319static inline void
1320gc_malloc_counters_snapshot(rb_objspace_t *objspace, struct gc_malloc_bytes *c)
1321{
1322 MALLOC_COUNTERS_LOCK(objspace);
1323 gc_counter_t malloc_now = gc_counter_load_relaxed(&c->malloc);
1324 gc_counter_t free_now = gc_counter_load_relaxed(&c->free);
1325 gc_counter_store_release(&c->malloc_at_last_gc, malloc_now);
1326 gc_counter_store_release(&c->free_at_last_gc, free_now);
1327 MALLOC_COUNTERS_UNLOCK(objspace);
1328}
1329
1330#define heap_pages_lomem objspace->heap_pages.range[0]
1331#define heap_pages_himem objspace->heap_pages.range[1]
1332#define heap_pages_freeable_pages objspace->heap_pages.freeable_pages
1333#define heap_pages_deferred_final objspace->heap_pages.deferred_final
1334#define heaps objspace->heaps
1335#define during_gc objspace->flags.during_gc
1336#define finalizing objspace->atomic_flags.finalizing
1337#define finalizer_table objspace->finalizer_table
1338#define ruby_gc_stressful global_objspace->gc_stressful
1339#define ruby_gc_stress_mode global_objspace->gc_stress_mode
1340#if GC_DEBUG_STRESS_TO_CLASS
1341#define stress_to_class objspace->stress_to_class
1342#define set_stress_to_class(c) (stress_to_class = (c))
1343#else
1344#define stress_to_class ((void)objspace, 0)
1345#define set_stress_to_class(c) ((void)objspace, (c))
1346#endif
1347
1348#if 0
1349#define dont_gc_on() (fprintf(stderr, "dont_gc_on@%s:%d\n", __FILE__, __LINE__), objspace->flags.dont_gc = 1)
1350#define dont_gc_off() (fprintf(stderr, "dont_gc_off@%s:%d\n", __FILE__, __LINE__), objspace->flags.dont_gc = 0)
1351#define dont_gc_set(b) (fprintf(stderr, "dont_gc_set(%d)@%s:%d\n", __FILE__, __LINE__), objspace->flags.dont_gc = (int)(b))
1352#define dont_gc_val() (objspace->flags.dont_gc)
1353#else
1354#define dont_gc_on() (objspace->flags.dont_gc = 1)
1355#define dont_gc_off() (objspace->flags.dont_gc = 0)
1356#define dont_gc_set(b) (objspace->flags.dont_gc = (int)(b))
1357#define dont_gc_val() (objspace->flags.dont_gc)
1358#endif
1359
1360#define gc_config_full_mark_set(b) (objspace->gc_config.full_mark = (int)(b))
1361#define gc_config_full_mark_val (objspace->gc_config.full_mark)
1362
1363static inline enum gc_mode
1364gc_mode_verify(enum gc_mode mode)
1365{
1366#if RGENGC_CHECK_MODE > 0
1367 switch (mode) {
1368 case gc_mode_none:
1369 case gc_mode_marking:
1370 case gc_mode_sweeping:
1371 case gc_mode_compacting:
1372 break;
1373 default:
1374 rb_bug("gc_mode_verify: unreachable (%d)", (int)mode);
1375 }
1376#endif
1377 return mode;
1378}
1379
1380static inline bool
1381has_sweeping_pages(rb_objspace_t *objspace)
1382{
1383 return objspace->sweeping_heap_count != 0;
1384}
1385
1386static inline size_t
1387heap_eden_total_pages(rb_objspace_t *objspace)
1388{
1389 size_t count = 0;
1390 for (int i = 0; i < HEAP_COUNT; i++) {
1391 count += (&heaps[i])->total_pages;
1392 }
1393 return count;
1394}
1395
1396static inline size_t
1397total_allocated_objects(rb_objspace_t *objspace)
1398{
1399 size_t count = 0;
1400 for (int i = 0; i < HEAP_COUNT; i++) {
1401 rb_heap_t *heap = &heaps[i];
1402 count += heap->total_allocated_objects;
1403 }
1404 return count;
1405}
1406
1407static inline size_t
1408total_freed_objects(rb_objspace_t *objspace)
1409{
1410 size_t count = 0;
1411 for (int i = 0; i < HEAP_COUNT; i++) {
1412 rb_heap_t *heap = &heaps[i];
1413 count += heap->total_freed_objects;
1414 }
1415 return count;
1416}
1417
1418static inline size_t
1419total_final_slots_count(rb_objspace_t *objspace)
1420{
1421 size_t count = 0;
1422 for (int i = 0; i < HEAP_COUNT; i++) {
1423 rb_heap_t *heap = &heaps[i];
1424 count += heap->final_slots_count;
1425 }
1426 return count;
1427}
1428
1429#define gc_mode(objspace) gc_mode_verify((enum gc_mode)(objspace)->flags.mode)
1430#define gc_mode_set(objspace, m) ((objspace)->flags.mode = (unsigned int)gc_mode_verify(m))
1431#define gc_needs_major_flags objspace->rgengc.need_major_gc
1432
1433#define is_marking(objspace) (gc_mode(objspace) == gc_mode_marking)
1434#define is_sweeping(objspace) (gc_mode(objspace) == gc_mode_sweeping)
1435#define is_full_marking(objspace) ((objspace)->flags.during_minor_gc == FALSE)
1436#define is_incremental_marking(objspace) ((objspace)->flags.during_incremental_marking != FALSE)
1437#define will_be_incremental_marking(objspace) ((objspace)->rgengc.need_major_gc != GPR_FLAG_NONE)
1438/*
1439 * Byte budget for incremental sweep steps. Each step sweeps at most
1440 * this many bytes worth of slots before yielding. The effective slot
1441 * count per step is GC_INCREMENTAL_SWEEP_BYTES / heap->slot_size,
1442 * so larger slot pools (which are less heavily used) naturally get
1443 * fewer slots swept per step.
1444 *
1445 * Baseline: 2048 slots * RVALUE_SLOT_SIZE = 2048 * 40 = 81920 bytes,
1446 * preserving the historical behavior for the smallest heap.
1447 */
1448#define GC_INCREMENTAL_SWEEP_BYTES (2048 * RVALUE_SLOT_SIZE)
1449#define GC_INCREMENTAL_SWEEP_POOL_BYTES (1024 * RVALUE_SLOT_SIZE)
1450#define is_lazy_sweeping(objspace) (GC_ENABLE_LAZY_SWEEP && has_sweeping_pages(objspace))
1451/* In lazy sweeping or the previous incremental marking finished and did not yield a free page. */
1452#define needs_continue_sweeping(objspace, heap) \
1453 ((heap)->free_pages == NULL && is_lazy_sweeping(objspace))
1454
1455#if SIZEOF_LONG == SIZEOF_VOIDP
1456# define obj_id_to_ref(objid) ((objid) ^ FIXNUM_FLAG) /* unset FIXNUM_FLAG */
1457#elif SIZEOF_LONG_LONG == SIZEOF_VOIDP
1458# define obj_id_to_ref(objid) (FIXNUM_P(objid) ? \
1459 ((objid) ^ FIXNUM_FLAG) : (NUM2PTR(objid) << 1))
1460#else
1461# error not supported
1462#endif
1463
1464struct RZombie {
1465 VALUE flags;
1466 VALUE next;
1467 void (*dfree)(void *);
1468 void *data;
1469};
1470
1471#define RZOMBIE(o) ((struct RZombie *)(o))
1472
1473static bool ruby_enable_autocompact = false;
1474#if RGENGC_CHECK_MODE
1475static gc_compact_compare_func ruby_autocompact_compare_func;
1476#endif
1477
1478static void init_mark_stack(mark_stack_t *stack);
1479static int garbage_collect(rb_objspace_t *, unsigned int reason);
1480
1481static int gc_start(rb_objspace_t *objspace, unsigned int reason);
1482static int gc_start_body(rb_objspace_t *objspace, unsigned int reason, bool allow_global);
1483static void gc_rest(rb_objspace_t *objspace);
1484
1485/* GC cycle events (ENTER, EXIT, START, END_MARK, END_SWEEP) fire only if the objspace's
1486 * own Ractor enabled them, so a concurrent local GC never walks the VM-global hook list
1487 * while another Ractor mutates it. NEWOBJ and FREEOBJ were already restricted. */
1488#define gc_event_hook(objspace, event) do { \
1489 if (RB_UNLIKELY((objspace)->hook_events & (event))) { \
1490 rb_gc_event_hook(0, (event)); \
1491 } \
1492} while (0)
1493
1494enum gc_enter_event {
1495 gc_enter_event_start,
1496 gc_enter_event_continue,
1497 gc_enter_event_rest,
1498 gc_enter_event_finalizer,
1499 gc_enter_event_global,
1500 gc_enter_event_global_auto,
1501};
1502
1503static inline bool gc_enter(rb_objspace_t *objspace, enum gc_enter_event event, unsigned int *lock_lev);
1504static inline void gc_exit(rb_objspace_t *objspace, enum gc_enter_event event, unsigned int *lock_lev);
1505static void gc_marking_enter(rb_objspace_t *objspace);
1506static void gc_marking_exit(rb_objspace_t *objspace);
1507static void gc_sweeping_enter(rb_objspace_t *objspace);
1508static void gc_sweeping_exit(rb_objspace_t *objspace);
1509static bool gc_marks_continue(rb_objspace_t *objspace, rb_heap_t *heap);
1510
1511static void gc_sweep(rb_objspace_t *objspace);
1512static void gc_sweep_finish_heap(rb_objspace_t *objspace, rb_heap_t *heap);
1513static void gc_sweep_continue(rb_objspace_t *objspace, rb_heap_t *heap);
1514
1515static inline void gc_mark(rb_objspace_t *objspace, VALUE ptr);
1516static inline void gc_pin(rb_objspace_t *objspace, VALUE ptr);
1517static inline void gc_mark_and_pin(rb_objspace_t *objspace, VALUE ptr);
1518
1519static int gc_mark_stacked_objects_incremental(rb_objspace_t *, size_t count);
1520NO_SANITIZE("memory", static inline bool is_pointer_to_heap(rb_objspace_t *objspace, const void *ptr));
1521
1522static void gc_verify_internal_consistency(void *objspace_ptr);
1523
1524static double getrusage_time(void);
1525static inline rb_hrtime_t elapsed_hrtime_from(rb_hrtime_t start);
1526static inline void gc_prof_setup_new_record(rb_objspace_t *objspace, unsigned int reason);
1527static inline void gc_prof_timer_start(rb_objspace_t *);
1528static inline void gc_prof_timer_stop(rb_objspace_t *);
1529static inline void gc_prof_mark_timer_start(rb_objspace_t *);
1530static inline void gc_prof_mark_timer_stop(rb_objspace_t *);
1531static inline void gc_prof_sweep_timer_start(rb_objspace_t *);
1532static inline void gc_prof_sweep_timer_stop(rb_objspace_t *);
1533static inline void gc_prof_set_malloc_info(rb_objspace_t *);
1534static inline void gc_prof_set_heap_info(rb_objspace_t *);
1535
1536#define gc_prof_record(objspace) (objspace)->profile.current_record
1537#define gc_prof_enabled(objspace) ((objspace)->profile.run && (objspace)->profile.current_record)
1538
1539#define gc_report(level, objspace, ...) \
1540 if (!RGENGC_DEBUG_ENABLED(level)) {} else gc_report_body(level, objspace, __VA_ARGS__)
1541PRINTF_ARGS(static void gc_report_body(int level, rb_objspace_t *objspace, const char *fmt, ...), 3, 4);
1542
1543static void gc_finalize_deferred(void *dmy);
1544
1545#if USE_TICK_T
1546
1547/* the following code is only for internal tuning. */
1548
1549/* Source code to use RDTSC is quoted and modified from
1550 * https://www.mcs.anl.gov/~kazutomo/rdtsc.html
1551 * written by Kazutomo Yoshii <kazutomo@mcs.anl.gov>
1552 */
1553
1554#if defined(__GNUC__) && defined(__i386__)
1555typedef unsigned long long tick_t;
1556#define PRItick "llu"
1557static inline tick_t
1558tick(void)
1559{
1560 unsigned long long int x;
1561 __asm__ __volatile__ ("rdtsc" : "=A" (x));
1562 return x;
1563}
1564
1565#elif defined(__GNUC__) && defined(__x86_64__)
1566typedef unsigned long long tick_t;
1567#define PRItick "llu"
1568
1569static __inline__ tick_t
1570tick(void)
1571{
1572 unsigned long hi, lo;
1573 __asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi));
1574 return ((unsigned long long)lo)|( ((unsigned long long)hi)<<32);
1575}
1576
1577#elif defined(__powerpc64__) && (GCC_VERSION_SINCE(4,8,0) || defined(__clang__))
1578typedef unsigned long long tick_t;
1579#define PRItick "llu"
1580
1581static __inline__ tick_t
1582tick(void)
1583{
1584 unsigned long long val = __builtin_ppc_get_timebase();
1585 return val;
1586}
1587
1588#elif defined(__POWERPC__) && defined(__APPLE__)
1589/* Implementation for macOS PPC by @nobu
1590 * See: https://github.com/ruby/ruby/pull/5975#discussion_r890045558
1591 */
1592typedef unsigned long long tick_t;
1593#define PRItick "llu"
1594
1595static __inline__ tick_t
1596tick(void)
1597{
1598 unsigned long int upper, lower, tmp;
1599 # define mftbu(r) __asm__ volatile("mftbu %0" : "=r"(r))
1600 # define mftb(r) __asm__ volatile("mftb %0" : "=r"(r))
1601 do {
1602 mftbu(upper);
1603 mftb(lower);
1604 mftbu(tmp);
1605 } while (tmp != upper);
1606 return ((tick_t)upper << 32) | lower;
1607}
1608
1609#elif defined(__aarch64__) && defined(__GNUC__)
1610typedef unsigned long tick_t;
1611#define PRItick "lu"
1612
1613static __inline__ tick_t
1614tick(void)
1615{
1616 unsigned long val;
1617 __asm__ __volatile__ ("mrs %0, cntvct_el0" : "=r" (val));
1618 return val;
1619}
1620
1621
1622#elif defined(_WIN32) && defined(_MSC_VER)
1623#include <intrin.h>
1624typedef unsigned __int64 tick_t;
1625#define PRItick "llu"
1626
1627static inline tick_t
1628tick(void)
1629{
1630 return __rdtsc();
1631}
1632
1633#else /* use clock */
1634typedef clock_t tick_t;
1635#define PRItick "llu"
1636
1637static inline tick_t
1638tick(void)
1639{
1640 return clock();
1641}
1642#endif /* TSC */
1643#else /* USE_TICK_T */
1644#define MEASURE_LINE(expr) expr
1645#endif /* USE_TICK_T */
1646
1647static inline VALUE check_rvalue_consistency(rb_objspace_t *objspace, const VALUE obj);
1648
1649#define RVALUE_MARKED_BITMAP(obj) MARKED_IN_BITMAP(GET_HEAP_MARK_BITS(obj), (obj))
1650#define RVALUE_WB_UNPROTECTED_BITMAP(obj) MARKED_IN_BITMAP(GET_HEAP_WB_UNPROTECTED_BITS(obj), (obj))
1651#define RVALUE_MARKING_BITMAP(obj) MARKED_IN_BITMAP(GET_HEAP_MARKING_BITS(obj), (obj))
1652#define RVALUE_UNCOLLECTIBLE_BITMAP(obj) MARKED_IN_BITMAP(GET_HEAP_UNCOLLECTIBLE_BITS(obj), (obj))
1653#define RVALUE_PINNED_BITMAP(obj) MARKED_IN_BITMAP(GET_HEAP_PINNED_BITS(obj), (obj))
1654
1655static inline int
1656RVALUE_MARKED(rb_objspace_t *objspace, VALUE obj)
1657{
1658 check_rvalue_consistency(objspace, obj);
1659 return RVALUE_MARKED_BITMAP(obj) != 0;
1660}
1661
1662static inline int
1663RVALUE_PINNED(rb_objspace_t *objspace, VALUE obj)
1664{
1665 check_rvalue_consistency(objspace, obj);
1666 return RVALUE_PINNED_BITMAP(obj) != 0;
1667}
1668
1669static inline int
1670RVALUE_WB_UNPROTECTED(rb_objspace_t *objspace, VALUE obj)
1671{
1672 check_rvalue_consistency(objspace, obj);
1673 return RVALUE_WB_UNPROTECTED_BITMAP(obj) != 0;
1674}
1675
1676static inline int
1677RVALUE_MARKING(rb_objspace_t *objspace, VALUE obj)
1678{
1679 check_rvalue_consistency(objspace, obj);
1680 return RVALUE_MARKING_BITMAP(obj) != 0;
1681}
1682
1683static inline int
1684RVALUE_REMEMBERED(rb_objspace_t *objspace, VALUE obj)
1685{
1686 check_rvalue_consistency(objspace, obj);
1687 return MARKED_IN_BITMAP(GET_HEAP_PAGE(obj)->remembered_bits, obj) != 0;
1688}
1689
1690static inline int
1691RVALUE_UNCOLLECTIBLE(rb_objspace_t *objspace, VALUE obj)
1692{
1693 check_rvalue_consistency(objspace, obj);
1694 return RVALUE_UNCOLLECTIBLE_BITMAP(obj) != 0;
1695}
1696
1697#define RVALUE_PAGE_WB_UNPROTECTED(page, obj) MARKED_IN_BITMAP((page)->wb_unprotected_bits, (obj))
1698#define RVALUE_PAGE_UNCOLLECTIBLE(page, obj) MARKED_IN_BITMAP((page)->uncollectible_bits, (obj))
1699#define RVALUE_PAGE_MARKING(page, obj) MARKED_IN_BITMAP((page)->marking_bits, (obj))
1700
1701static void rgengc_remember(rb_objspace_t *objspace, VALUE obj);
1702static void gc_bitmaps_clear(rb_objspace_t *objspace, rb_heap_t *heap, bool clear_shref);
1703static void rgengc_rememberset_mark(rb_objspace_t *objspace, rb_heap_t *heap);
1704static bool verify_pointer_in_any_heap_p(const void *ptr); /* cross-objspace ownership test */
1705
1706static int
1707check_rvalue_consistency_force(rb_objspace_t *objspace, const VALUE obj, int terminate)
1708{
1709 int err = 0;
1710
1711 /* Under a global GC the barrier stops every Ractor, so the cross-objspace walk
1712 * below is safe without the VM lock. Sweeping an ownerless zombie objspace also
1713 * leaves GET_RACTOR() NULL, and taking the lock here would dereference it. */
1714 const bool world_stopped = objspace->flags.during_global_gc;
1715 /* The VM lock protects the cross-objspace walk while other Ractors run and
1716 * reallocate their heaps. Not taken while this objspace is in GC: pages are stable
1717 * then, the cross-objspace walk needs the world stopped anyway, and the Ractor lock
1718 * may already be held (Ractor -> VM order inversion). A global GC holds the barrier
1719 * and needs no lock. */
1720 const bool take_vm_lock = !world_stopped && !during_gc;
1721 unsigned int lev = 0;
1722 if (take_vm_lock) lev = RB_GC_VM_LOCK_NO_BARRIER();
1723 {
1724 if (SPECIAL_CONST_P(obj)) {
1725 fprintf(stderr, "check_rvalue_consistency: %p is a special const.\n", (void *)obj);
1726 err++;
1727 }
1728 else if (!is_pointer_to_heap(objspace, (void *)obj)) {
1729 /* obj may be a legitimate cross-objspace reference (a shareable object, an
1730 * in-flight shref payload); it is a non-object only if no objspace's heap
1731 * holds it. A foreign object's mark/age/remembered bits belong to its
1732 * owner and reading them would race its local GC: skip per-object checks. */
1733 if (!world_stopped) {
1734 /* A mid-local-GC verify holds no barrier, so other Ractors reallocate
1735 * heap_pages.sorted under verify_pointer_in_any_heap_p's page_index
1736 * read. Accept foreign pointers here; the global GC's world-stopped
1737 * verify does the full existence check. */
1738 }
1739 else if (!verify_pointer_in_any_heap_p((void *)obj)) {
1740 struct heap_page *empty_page = objspace->empty_pages;
1741 while (empty_page) {
1742 if ((uintptr_t)empty_page->body <= (uintptr_t)obj &&
1743 (uintptr_t)obj < (uintptr_t)empty_page->body + HEAP_PAGE_SIZE) {
1744 GC_ASSERT(heap_page_in_global_empty_pages_pool(objspace, empty_page));
1745 fprintf(stderr, "check_rvalue_consistency: %p is in an empty page (%p).\n",
1746 (void *)obj, (void *)empty_page);
1747 err++;
1748 goto skip;
1749 }
1750 empty_page = empty_page->free_next;
1751 }
1752 fprintf(stderr, "check_rvalue_consistency: %p is not a Ruby object.\n", (void *)obj);
1753 err++;
1754 skip:
1755 ;
1756 }
1757 }
1758 else {
1759 const int wb_unprotected_bit = RVALUE_WB_UNPROTECTED_BITMAP(obj) != 0;
1760 const int uncollectible_bit = RVALUE_UNCOLLECTIBLE_BITMAP(obj) != 0;
1761 const int mark_bit = RVALUE_MARKED_BITMAP(obj) != 0;
1762 const int marking_bit = RVALUE_MARKING_BITMAP(obj) != 0;
1763 const int remembered_bit = MARKED_IN_BITMAP(GET_HEAP_PAGE(obj)->remembered_bits, obj) != 0;
1764 const int age = RVALUE_AGE_GET((VALUE)obj);
1765
1766 if (heap_page_in_global_empty_pages_pool(objspace, GET_HEAP_PAGE(obj))) {
1767 fprintf(stderr, "check_rvalue_consistency: %s is in tomb page.\n", rb_obj_info(obj));
1768 err++;
1769 }
1770 if (BUILTIN_TYPE(obj) == T_NONE) {
1771 fprintf(stderr, "check_rvalue_consistency: %s is T_NONE.\n", rb_obj_info(obj));
1772 err++;
1773 }
1774 if (BUILTIN_TYPE(obj) == T_ZOMBIE) {
1775 fprintf(stderr, "check_rvalue_consistency: %s is T_ZOMBIE.\n", rb_obj_info(obj));
1776 err++;
1777 }
1778
1779 /* Do not run the memsize probe once an inconsistency (a T_NONE, say) was
1780 * found: an rb_bug inside the probe would lose the real diagnosis. */
1781 if (err == 0 && BUILTIN_TYPE(obj) != T_DATA) {
1782 rb_obj_memsize_of((VALUE)obj);
1783 }
1784
1785 /* check generation
1786 *
1787 * OLD == age == 3 && old-bitmap && mark-bit (except incremental marking)
1788 */
1789 if (age > 0 && wb_unprotected_bit) {
1790 fprintf(stderr, "check_rvalue_consistency: %s is not WB protected, but age is %d > 0.\n", rb_obj_info(obj), age);
1791 err++;
1792 }
1793
1794 if (!is_marking(objspace) && uncollectible_bit && !mark_bit) {
1795 fprintf(stderr, "check_rvalue_consistency: %s is uncollectible, but is not marked while !gc.\n", rb_obj_info(obj));
1796 err++;
1797 }
1798
1799 if (!is_full_marking(objspace)) {
1800 if (uncollectible_bit && age != RVALUE_OLD_AGE && !wb_unprotected_bit) {
1801 fprintf(stderr, "check_rvalue_consistency: %s is uncollectible, but not old (age: %d) and not WB unprotected.\n",
1802 rb_obj_info(obj), age);
1803 err++;
1804 }
1805 if (remembered_bit && age != RVALUE_OLD_AGE) {
1806 fprintf(stderr, "check_rvalue_consistency: %s is remembered, but not old (age: %d).\n",
1807 rb_obj_info(obj), age);
1808 err++;
1809 }
1810 }
1811
1812 /*
1813 * check coloring
1814 *
1815 * marking:false marking:true
1816 * marked:false white *invalid*
1817 * marked:true black grey
1818 */
1819 if (is_incremental_marking(objspace) && marking_bit) {
1820 if (!is_marking(objspace) && !mark_bit) {
1821 fprintf(stderr, "check_rvalue_consistency: %s is marking, but not marked.\n", rb_obj_info(obj));
1822 err++;
1823 }
1824 }
1825 }
1826 }
1827 if (take_vm_lock) RB_GC_VM_UNLOCK_NO_BARRIER(lev);
1828
1829 if (err > 0 && terminate) {
1830 rb_bug("check_rvalue_consistency_force: there is %d errors.", err);
1831 }
1832 return err;
1833}
1834
1835#if RGENGC_CHECK_MODE == 0
1836static inline VALUE
1837check_rvalue_consistency(rb_objspace_t *objspace, const VALUE obj)
1838{
1839 return obj;
1840}
1841#else
1842static VALUE
1843check_rvalue_consistency(rb_objspace_t *objspace, const VALUE obj)
1844{
1845 check_rvalue_consistency_force(objspace, obj, TRUE);
1846 return obj;
1847}
1848#endif
1849
1850static inline bool
1851gc_object_moved_p(rb_objspace_t *objspace, VALUE obj)
1852{
1853
1854 bool ret;
1855 asan_unpoisoning_object(obj) {
1856 ret = BUILTIN_TYPE(obj) == T_MOVED;
1857 }
1858 return ret;
1859}
1860
1861static inline int
1862RVALUE_OLD_P(rb_objspace_t *objspace, VALUE obj)
1863{
1864 GC_ASSERT(!RB_SPECIAL_CONST_P(obj));
1865 check_rvalue_consistency(objspace, obj);
1866 // Because this will only ever be called on GC controlled objects,
1867 // we can use the faster _RAW function here
1868 return RB_OBJ_PROMOTED_RAW(obj);
1869}
1870
1871static inline void
1872RVALUE_PAGE_OLD_UNCOLLECTIBLE_SET(rb_objspace_t *objspace, struct heap_page *page, VALUE obj)
1873{
1874 MARK_IN_BITMAP(&page->uncollectible_bits[0], obj);
1875 /* Count a promotion in the object's own objspace: a global GC ages every objspace's
1876 * slots from the driver, and counting them there would skew the other objspaces'
1877 * old_objects and with it their major GC frequency. */
1878 page->objspace->rgengc.old_objects++;
1879
1880#if RGENGC_PROFILE >= 2
1881 objspace->profile.total_promoted_count++;
1882 objspace->profile.promoted_types[BUILTIN_TYPE(obj)]++;
1883#endif
1884}
1885
1886static inline void
1887RVALUE_OLD_UNCOLLECTIBLE_SET(rb_objspace_t *objspace, VALUE obj)
1888{
1889 RB_DEBUG_COUNTER_INC(obj_promote);
1890 RVALUE_PAGE_OLD_UNCOLLECTIBLE_SET(objspace, GET_HEAP_PAGE(obj), obj);
1891}
1892
1893/* set age to age+1 */
1894static inline void
1895RVALUE_AGE_INC(rb_objspace_t *objspace, VALUE obj)
1896{
1897 int age = RVALUE_AGE_GET((VALUE)obj);
1898
1899 if (RGENGC_CHECK_MODE && age == RVALUE_OLD_AGE) {
1900 rb_bug("RVALUE_AGE_INC: can not increment age of OLD object %s.", rb_obj_info(obj));
1901 }
1902
1903 age++;
1904 RVALUE_AGE_SET(obj, age);
1905
1906 if (age == RVALUE_OLD_AGE) {
1907 RVALUE_OLD_UNCOLLECTIBLE_SET(objspace, obj);
1908 }
1909
1910 check_rvalue_consistency(objspace, obj);
1911}
1912
1913static inline void
1914RVALUE_AGE_SET_CANDIDATE(rb_objspace_t *objspace, VALUE obj)
1915{
1916 check_rvalue_consistency(objspace, obj);
1917 GC_ASSERT(!RVALUE_OLD_P(objspace, obj));
1918 RVALUE_AGE_SET(obj, RVALUE_OLD_AGE - 1);
1919 check_rvalue_consistency(objspace, obj);
1920}
1921
1922static inline void
1923RVALUE_AGE_RESET(VALUE obj)
1924{
1925 RVALUE_AGE_SET(obj, 0);
1926}
1927
1928static inline void
1929RVALUE_DEMOTE(rb_objspace_t *objspace, VALUE obj)
1930{
1931 check_rvalue_consistency(objspace, obj);
1932 GC_ASSERT(RVALUE_OLD_P(objspace, obj));
1933
1934 if (!is_incremental_marking(objspace) && RVALUE_REMEMBERED(objspace, obj)) {
1935 struct heap_page *page = GET_HEAP_PAGE(obj);
1936 _CLEAR_IN_BITMAP(page->remembered_bits, page, obj);
1937 }
1938
1939 CLEAR_IN_BITMAP(GET_HEAP_UNCOLLECTIBLE_BITS(obj), obj);
1940 RVALUE_AGE_RESET(obj);
1941
1942 if (RVALUE_MARKED(objspace, obj)) {
1943 /* symmetric with RVALUE_PAGE_OLD_UNCOLLECTIBLE_SET */
1944 GET_HEAP_PAGE(obj)->objspace->rgengc.old_objects--;
1945 }
1946
1947 check_rvalue_consistency(objspace, obj);
1948}
1949
1950static inline int
1951RVALUE_BLACK_P(rb_objspace_t *objspace, VALUE obj)
1952{
1953 return RVALUE_MARKED(objspace, obj) && !RVALUE_MARKING(objspace, obj);
1954}
1955
1956static inline int
1957RVALUE_WHITE_P(rb_objspace_t *objspace, VALUE obj)
1958{
1959 return !RVALUE_MARKED(objspace, obj);
1960}
1961
1962bool
1963rb_gc_impl_user_gc_disabled_set(void *objspace_ptr, bool disable)
1964{
1965 rb_objspace_t *objspace = objspace_ptr;
1966 const bool was = objspace->flags.user_gc_disabled;
1967 objspace->flags.user_gc_disabled = disable;
1968 return was;
1969}
1970
1971bool
1972rb_gc_impl_user_gc_disabled_p(void *objspace_ptr)
1973{
1974 rb_objspace_t *objspace = objspace_ptr;
1975 return objspace->flags.user_gc_disabled;
1976}
1977
1978bool
1979rb_gc_impl_gc_enabled_p(void *objspace_ptr)
1980{
1981 rb_objspace_t *objspace = objspace_ptr;
1982 return !dont_gc_val();
1983}
1984
1985void
1986rb_gc_impl_gc_enable(void *objspace_ptr)
1987{
1988 rb_objspace_t *objspace = objspace_ptr;
1989
1990 dont_gc_off();
1991}
1992
1993void
1994rb_gc_impl_gc_disable(void *objspace_ptr, bool finish_current_gc)
1995{
1996 rb_objspace_t *objspace = objspace_ptr;
1997
1998 if (finish_current_gc) {
1999 gc_rest(objspace);
2000 }
2001
2002 dont_gc_on();
2003}
2004
2005/* Finish an incremental mark or lazy sweep in progress without changing the enabled
2006 * state. gc.c uses it to settle the only objspace just before the process goes
2007 * multi-objspace. */
2008void
2009rb_gc_impl_gc_rest(void *objspace_ptr)
2010{
2011 gc_rest(objspace_ptr);
2012}
2013
2014/*
2015 --------------------------- ObjectSpace -----------------------------
2016*/
2017
2018static inline void *
2019calloc1(size_t n)
2020{
2021 return calloc(1, n);
2022}
2023
2024void
2025rb_gc_impl_set_event_hook(void *objspace_ptr, const rb_event_flag_t event)
2026{
2027 rb_objspace_t *objspace = objspace_ptr;
2028 /* FREEOBJ is main-objspace only (rb_objspace_set_event_hook masks it elsewhere). */
2029 GC_ASSERT(!(event & RUBY_INTERNAL_EVENT_FREEOBJ) ||
2030 objspace == global_objspace->main_objspace);
2031 objspace->hook_events = event & RUBY_INTERNAL_EVENT_OBJSPACE_MASK;
2032}
2033
2034unsigned long long
2035rb_gc_impl_get_total_time(void *objspace_ptr)
2036{
2037 rb_objspace_t *objspace = objspace_ptr;
2038
2039 unsigned long long marking_time = objspace->profile.marking_time_ns;
2040 unsigned long long sweeping_time = objspace->profile.sweeping_time_ns;
2041
2042 return marking_time + sweeping_time;
2043}
2044
2045void
2046rb_gc_impl_set_measure_total_time(void *objspace_ptr, VALUE flag)
2047{
2048 rb_objspace_t *objspace = objspace_ptr;
2049
2050 objspace->flags.measure_gc = RTEST(flag) ? TRUE : FALSE;
2051}
2052
2053bool
2054rb_gc_impl_get_measure_total_time(void *objspace_ptr)
2055{
2056 rb_objspace_t *objspace = objspace_ptr;
2057
2058 return objspace->flags.measure_gc;
2059}
2060
2061/* garbage objects will be collected soon. */
2062bool
2063rb_gc_impl_garbage_object_p(void *objspace_ptr, VALUE ptr)
2064{
2065 rb_objspace_t *objspace = objspace_ptr;
2066
2067 /* A foreign object is a live leaf: reading its type or mark bit would race the
2068 * owner's local GC, so outside a global GC's barrier never report it as garbage.
2069 * The fstring/symbol weak-set lookups do reach across objspaces, but those objects
2070 * are born shareable and only a stop-the-world global GC collects them, so "not
2071 * garbage" is correct. */
2072 if (gc_skip_foreign_object_p(objspace, ptr)) {
2073 return false;
2074 }
2075
2076 /* Asking whether a freed (T_NONE), moved (T_MOVED), or finalized (T_ZOMBIE)
2077 * object is garbage gives an unreliable answer: the slot may since have been
2078 * reused for an unrelated object. A reference to one of these is stale and a
2079 * bug in the caller. */
2080 asan_unpoisoning_object(ptr) {
2081 GC_ASSERT(BUILTIN_TYPE(ptr) != T_NONE);
2082 GC_ASSERT(BUILTIN_TYPE(ptr) != T_MOVED);
2083 GC_ASSERT(BUILTIN_TYPE(ptr) != T_ZOMBIE);
2084 }
2085
2086 return is_lazy_sweeping(objspace) && GET_HEAP_PAGE(ptr)->flags.before_sweep &&
2087 !RVALUE_MARKED(objspace, ptr);
2088}
2089
2090struct rb_gc_vm_context *
2091rb_gc_impl_get_vm_context(void *objspace_ptr)
2092{
2093 rb_objspace_t *objspace = objspace_ptr;
2094
2095 return &objspace->vm_context;
2096}
2097
2098static void free_stack_chunks(mark_stack_t *);
2099static void mark_stack_free_cache(mark_stack_t *);
2100static void heap_page_free(rb_objspace_t *objspace, struct heap_page *page);
2101
2102static inline void
2103gc_check_obj_in_page(struct heap_page *page, VALUE obj)
2104{
2105 if (RGENGC_CHECK_MODE &&
2106 /* obj should belong to page */
2107 !(page->start <= (uintptr_t)obj &&
2108 (uintptr_t)obj < ((uintptr_t)page->start + (page->total_slots * page->slot_size)) &&
2109 obj % sizeof(VALUE) == 0)) {
2110 rb_bug("gc_check_obj_in_page: %p is not rvalue.", (void *)obj);
2111 }
2112}
2113
2114static inline void
2115heap_page_add_free_region(rb_objspace_t *objspace, struct heap_page *page, VALUE obj)
2116{
2117 rb_asan_unpoison_object(obj, false);
2118
2119 // Should have already been reset
2120 GC_ASSERT(RVALUE_AGE_GET(obj) == 0);
2121
2122 gc_check_obj_in_page(page, obj);
2123
2124 asan_unlock_freelist(page);
2125
2126 /* Keep a freed slot from carrying its old shareable and shref bits into the next
2127 * object born there. */
2128 CLEAR_IN_BITMAP(GET_HEAP_SHAREABLE_BITS(obj), obj);
2129 CLEAR_IN_BITMAP(GET_HEAP_SHREF_BITS(obj), obj);
2130
2131 struct free_region *region = (struct free_region *)obj;
2132 region->flags = 0;
2133 region->end = (uintptr_t)obj + page->slot_size;
2134 region->next = page->free_region;
2135 page->free_region = region;
2136
2137 asan_lock_freelist(page);
2138
2139 rb_asan_poison_object(obj);
2140 gc_report(3, objspace, "heap_page_add_free_region: %p\n", (void *)obj);
2141}
2142
2143static void
2144heap_allocatable_bytes_expand(rb_objspace_t *objspace,
2145 rb_heap_t *heap, size_t free_slots, size_t total_slots, size_t slot_size)
2146{
2147 double goal_ratio = gc_params.heap_free_slots_goal_ratio;
2148 size_t target_total_slots;
2149
2150 if (goal_ratio == 0.0) {
2151 target_total_slots = (size_t)(total_slots * gc_params.growth_factor);
2152 }
2153 else if (total_slots == 0) {
2154 target_total_slots = gc_params.heap_init_bytes / slot_size;
2155 }
2156 else {
2157 /* Find `f' where free_slots = f * total_slots * goal_ratio
2158 * => f = (total_slots - free_slots) / ((1 - goal_ratio) * total_slots)
2159 */
2160 double f = (double)(total_slots - free_slots) / ((1 - goal_ratio) * total_slots);
2161
2162 if (f > gc_params.growth_factor) f = gc_params.growth_factor;
2163 if (f < 1.0) f = 1.1;
2164
2165 target_total_slots = (size_t)(f * total_slots);
2166
2167 if (0) {
2168 fprintf(stderr,
2169 "free_slots(%8"PRIuSIZE")/total_slots(%8"PRIuSIZE")=%1.2f,"
2170 " G(%1.2f), f(%1.2f),"
2171 " total_slots(%8"PRIuSIZE") => target_total_slots(%8"PRIuSIZE")\n",
2172 free_slots, total_slots, free_slots/(double)total_slots,
2173 goal_ratio, f, total_slots, target_total_slots);
2174 }
2175 }
2176
2177 if (gc_params.growth_max_bytes > 0) {
2178 size_t max_total_slots = total_slots + gc_params.growth_max_bytes / slot_size;
2179 if (target_total_slots > max_total_slots) target_total_slots = max_total_slots;
2180 }
2181
2182 size_t extend_slot_count = target_total_slots - total_slots;
2183 /* Extend by at least 1 page. */
2184 if (extend_slot_count == 0) extend_slot_count = 1;
2185
2186 objspace->heap_pages.allocatable_bytes += extend_slot_count * slot_size;
2187}
2188
2189static inline void
2190heap_add_freepage(rb_heap_t *heap, struct heap_page *page)
2191{
2192 asan_unlock_freelist(page);
2193 GC_ASSERT(page->free_slots != 0);
2194 GC_ASSERT(page->free_region != NULL);
2195
2196 page->free_next = heap->free_pages;
2197 heap->free_pages = page;
2198
2199 RUBY_DEBUG_LOG("page:%p free_region:%p", (void *)page, (void *)page->free_region);
2200
2201 asan_lock_freelist(page);
2202}
2203
2204static inline void
2205heap_add_poolpage(rb_objspace_t *objspace, rb_heap_t *heap, struct heap_page *page)
2206{
2207 asan_unlock_freelist(page);
2208 GC_ASSERT(page->free_slots != 0);
2209 GC_ASSERT(page->free_region != NULL);
2210
2211 page->free_next = heap->pooled_pages;
2212 heap->pooled_pages = page;
2213 objspace->rincgc.pooled_slots += page->free_slots;
2214
2215 asan_lock_freelist(page);
2216}
2217
2218static void
2219heap_unlink_page(rb_objspace_t *objspace, rb_heap_t *heap, struct heap_page *page)
2220{
2221 ccan_list_del(&page->page_node);
2222 heap->total_pages--;
2223 heap->total_slots -= page->total_slots;
2224}
2225
2226static void
2227gc_aligned_free(void *ptr, size_t size)
2228{
2229#if defined __MINGW32__
2230 __mingw_aligned_free(ptr);
2231#elif defined _WIN32
2232 _aligned_free(ptr);
2233#elif defined(HAVE_POSIX_MEMALIGN) || defined(HAVE_MEMALIGN)
2234 free(ptr);
2235#else
2236 free(((void**)ptr)[-1]);
2237#endif
2238}
2239
2240static void
2241heap_page_body_free(struct heap_page_body *page_body, struct page_arena *arena)
2242{
2243 GC_ASSERT((uintptr_t)page_body % HEAP_PAGE_ALIGN == 0);
2244
2245 page_pool_release(page_body, arena);
2246}
2247
2248#ifdef PAGE_POOL_LOCK_ERRORCHECK
2249# define ASSERT_PAGE_POOL_LOCKED(g) GC_ASSERT(pthread_mutex_lock(&(g)->page_pool.lock) == EDEADLK)
2250#else
2251# define ASSERT_PAGE_POOL_LOCKED(g) ((void)0)
2252#endif
2253
2254/* Insert into page_index. Writers serialize on page_pool.lock; lomem and himem are a
2255 * monotonically growing over-approximation used for a quick reject. */
2256static void
2257global_page_index_insert(struct heap_page *page)
2258{
2259 rb_global_objspace_t *g = global_objspace;
2260 uintptr_t body = (uintptr_t)page->body;
2261
2262 rb_native_mutex_lock(&g->page_pool.lock);
2263 if (g->page_index.n_pages == g->page_index.capa) {
2264 size_t new_capa = g->page_index.capa ? g->page_index.capa * 2 : 128;
2265 struct heap_page **grown = realloc(g->page_index.pages, new_capa * sizeof(*grown));
2266 if (grown == NULL) rb_bug("global_page_index_insert: realloc failed");
2267 g->page_index.pages = grown;
2268 g->page_index.capa = new_capa;
2269 }
2270 size_t lo = 0, hi = g->page_index.n_pages;
2271 while (lo < hi) {
2272 size_t mid = (lo + hi) / 2;
2273 if ((uintptr_t)g->page_index.pages[mid]->body < body) lo = mid + 1;
2274 else hi = mid;
2275 }
2276 memmove(&g->page_index.pages[lo + 1], &g->page_index.pages[lo],
2277 (g->page_index.n_pages - lo) * sizeof(struct heap_page *));
2278 g->page_index.pages[lo] = page;
2279 g->page_index.n_pages++;
2280
2281 uintptr_t start = body + sizeof(struct heap_page_header);
2282 uintptr_t end = body + HEAP_PAGE_SIZE;
2283 if (g->page_index.lomem == 0 || g->page_index.lomem > start) g->page_index.lomem = start;
2284 if (g->page_index.himem < end) g->page_index.himem = end;
2285 rb_native_mutex_unlock(&g->page_pool.lock);
2286}
2287
2288static void
2289global_page_index_remove_locked(const struct heap_page *page)
2290{
2291 rb_global_objspace_t *g = global_objspace;
2292 uintptr_t body = (uintptr_t)page->body;
2293
2294 ASSERT_PAGE_POOL_LOCKED(g);
2295
2296 size_t lo = 0, hi = g->page_index.n_pages;
2297 while (lo < hi) {
2298 size_t mid = (lo + hi) / 2;
2299 if ((uintptr_t)g->page_index.pages[mid]->body < body) lo = mid + 1;
2300 else hi = mid;
2301 }
2302 GC_ASSERT(lo < g->page_index.n_pages && g->page_index.pages[lo] == page);
2303 memmove(&g->page_index.pages[lo], &g->page_index.pages[lo + 1],
2304 (g->page_index.n_pages - lo - 1) * sizeof(struct heap_page *));
2305 g->page_index.n_pages--;
2306}
2307
2308static void
2309global_page_index_remove(const struct heap_page *page)
2310{
2311 rb_global_objspace_t *g = global_objspace;
2312
2313 rb_native_mutex_lock(&g->page_pool.lock);
2314 global_page_index_remove_locked(page);
2315 rb_native_mutex_unlock(&g->page_pool.lock);
2316}
2317
2318static void
2319heap_page_free(rb_objspace_t *objspace, struct heap_page *page)
2320{
2321 global_page_index_remove(page);
2322 objspace->heap_pages.freed_pages++;
2323 heap_page_body_free(page->body, page->arena);
2324 free(page);
2325}
2326
2327static void
2328heap_pages_free_batch(rb_objspace_t *objspace, struct heap_page *pages)
2329{
2330 rb_global_objspace_t *g = global_objspace;
2331
2332 rb_native_mutex_lock(&g->page_pool.lock);
2333 for (struct heap_page *page = pages; page != NULL; page = page->free_next) {
2334 global_page_index_remove_locked(page);
2335 if (HEAP_PAGE_ALLOC_USE_MMAP) {
2336#ifdef HAVE_MMAP
2337 page_pool_release_locked(page->body, page->arena);
2338#endif
2339 }
2340 }
2341 rb_native_mutex_unlock(&g->page_pool.lock);
2342
2343 if (!HEAP_PAGE_ALLOC_USE_MMAP) {
2344 /* gc_aligned_free does not need the pool lock. */
2345 for (struct heap_page *page = pages; page != NULL; page = page->free_next) {
2346 heap_page_body_free(page->body, page->arena);
2347 }
2348 }
2349
2350 while (pages != NULL) {
2351 struct heap_page *next = pages->free_next;
2352 objspace->heap_pages.freed_pages++;
2353 free(pages);
2354 pages = next;
2355 }
2356}
2357
2358static void
2359heap_pages_free_unused_pages(rb_objspace_t *objspace)
2360{
2361 if (objspace->empty_pages != NULL && heap_pages_freeable_pages > 0) {
2362 GC_ASSERT(objspace->empty_pages_count > 0);
2363 objspace->empty_pages = NULL;
2364 objspace->empty_pages_count = 0;
2365
2366 size_t i, j;
2367 struct heap_page *to_free = NULL;
2368 for (i = j = 0; i < rb_darray_size(objspace->heap_pages.sorted); i++) {
2369 struct heap_page *page = rb_darray_get(objspace->heap_pages.sorted, i);
2370
2371 if (heap_page_in_global_empty_pages_pool(objspace, page) && heap_pages_freeable_pages > 0) {
2372 page->free_next = to_free;
2373 to_free = page;
2374 heap_pages_freeable_pages--;
2375 }
2376 else {
2377 if (heap_page_in_global_empty_pages_pool(objspace, page)) {
2378 page->free_next = objspace->empty_pages;
2379 objspace->empty_pages = page;
2380 objspace->empty_pages_count++;
2381 }
2382
2383 if (i != j) {
2384 rb_darray_set(objspace->heap_pages.sorted, j, page);
2385 }
2386 j++;
2387 }
2388 }
2389
2390 rb_darray_pop(objspace->heap_pages.sorted, i - j);
2391 GC_ASSERT(rb_darray_size(objspace->heap_pages.sorted) == j);
2392
2393 /* A retire GC can free every page, so an empty objspace is legitimate. */
2394 if (j > 0) {
2395 struct heap_page *hipage = rb_darray_get(objspace->heap_pages.sorted, rb_darray_size(objspace->heap_pages.sorted) - 1);
2396 uintptr_t himem = (uintptr_t)hipage->body + HEAP_PAGE_SIZE;
2397 GC_ASSERT(himem <= heap_pages_himem);
2398 heap_pages_himem = himem;
2399
2400 struct heap_page *lopage = rb_darray_get(objspace->heap_pages.sorted, 0);
2401 uintptr_t lomem = (uintptr_t)lopage->body + sizeof(struct heap_page_header);
2402 GC_ASSERT(lomem >= heap_pages_lomem);
2403 heap_pages_lomem = lomem;
2404 }
2405 else {
2406 heap_pages_lomem = 0;
2407 heap_pages_himem = 0;
2408 }
2409
2410 heap_pages_free_batch(objspace, to_free);
2411 }
2412}
2413
2414static void *
2415gc_aligned_malloc(size_t alignment, size_t size)
2416{
2417 /* alignment must be a power of 2 */
2418 GC_ASSERT(((alignment - 1) & alignment) == 0);
2419 GC_ASSERT(alignment % sizeof(void*) == 0);
2420
2421 void *res;
2422
2423#if defined __MINGW32__
2424 res = __mingw_aligned_malloc(size, alignment);
2425#elif defined _WIN32
2426 void *_aligned_malloc(size_t, size_t);
2427 res = _aligned_malloc(size, alignment);
2428#elif defined(HAVE_POSIX_MEMALIGN)
2429 if (posix_memalign(&res, alignment, size) != 0) {
2430 return NULL;
2431 }
2432#elif defined(HAVE_MEMALIGN)
2433 res = memalign(alignment, size);
2434#else
2435 char* aligned;
2436 res = malloc(alignment + size + sizeof(void*));
2437 aligned = (char*)res + alignment + sizeof(void*);
2438 aligned -= ((VALUE)aligned & (alignment - 1));
2439 ((void**)aligned)[-1] = res;
2440 res = (void*)aligned;
2441#endif
2442
2443 GC_ASSERT((uintptr_t)res % alignment == 0);
2444
2445 return res;
2446}
2447
2448/* The page pool (global_objspace->page_pool): heap page bodies are carved out of large
2449 * arenas and reused through the pool. Free bodies are split into a small global hot
2450 * list (≤ PAGE_POOL_HOT_MAX, never madvise'd) and per-arena cold freelists (eligible for
2451 * OS release — see page_pool_reclaim). Both lists use an in-body link at offset 0. */
2452
2453#define PAGE_POOL_ARENA_SIZE (HEAP_PAGE_SIZE * 32) /* 2MiB with 64KiB pages */
2454#define PAGE_POOL_ARENA_BODIES (PAGE_POOL_ARENA_SIZE / HEAP_PAGE_SIZE) /* 32 */
2455#define PAGE_POOL_HOT_MAX 0 /* disabled — empty_pages is the retention buffer */
2456#define PAGE_POOL_ARENA_KEEP_HALF (PAGE_POOL_ARENA_BODIES / 2) /* 16 */
2457
2458/* Steal bit 0 of the in-body link word: set iff the body has been madvise'd (cold). */
2459#define PAGE_POOL_ADVISED_BIT ((uintptr_t)1)
2460
2461/* While a body is free, the arena back-pointer is stored at offset sizeof(header) — one
2462 * word past the link, inside the spared first OS page. PAGE_POOL_SCRATCH_SIZE covers
2463 * both the link (offset 0) and the tag for ASAN unpoison. */
2464#define PAGE_POOL_BODY_ARENA(body) \
2465 (*(struct page_arena **)((char *)(body) + sizeof(struct heap_page_header)))
2466#define PAGE_POOL_SCRATCH_SIZE (sizeof(struct heap_page_header) + sizeof(void *))
2467
2468#ifdef HAVE_MMAP
2469/* mmap a new arena to carve from. Called with the pool lock held, at which point the
2470 * previous arena is always fully carved. */
2471static bool
2472page_pool_add_arena(rb_global_objspace_t *g)
2473{
2474 GC_ASSERT(HEAP_PAGE_ALIGN % sysconf(_SC_PAGE_SIZE) == 0);
2475
2476 size_t mmap_size = PAGE_POOL_ARENA_SIZE + HEAP_PAGE_ALIGN;
2477 char *ptr = mmap(NULL, mmap_size,
2478 PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
2479 if (ptr == MAP_FAILED) {
2480 return false;
2481 }
2482
2483 // If we are building `default.c` as part of the ruby executable, we
2484 // may just call `ruby_annotate_mmap`. But if we are building
2485 // `default.c` as a shared library, we will not have access to private
2486 // symbols, and we have to either call prctl directly or make our own
2487 // wrapper.
2488#if defined(HAVE_SYS_PRCTL_H) && defined(PR_SET_VMA) && defined(PR_SET_VMA_ANON_NAME)
2489 prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, ptr, mmap_size, "Ruby:GC:default:page_pool_arena");
2490 errno = 0;
2491#endif
2492
2493 /* Trim the unaligned head and tail so the usable area is HEAP_PAGE_ALIGN aligned. */
2494 char *aligned = ptr + HEAP_PAGE_ALIGN;
2495 aligned -= ((uintptr_t)aligned & (HEAP_PAGE_ALIGN - 1));
2496 GC_ASSERT(aligned > ptr);
2497 GC_ASSERT(aligned <= ptr + HEAP_PAGE_ALIGN);
2498
2499 size_t start_out_of_range_size = aligned - ptr;
2500 GC_ASSERT(start_out_of_range_size % sysconf(_SC_PAGE_SIZE) == 0);
2501 if (start_out_of_range_size > 0) {
2502 if (munmap(ptr, start_out_of_range_size)) {
2503 rb_bug("page_pool_add_arena: munmap failed for start");
2504 }
2505 }
2506
2507 size_t end_out_of_range_size = HEAP_PAGE_ALIGN - start_out_of_range_size;
2508 GC_ASSERT(end_out_of_range_size % sysconf(_SC_PAGE_SIZE) == 0);
2509 if (end_out_of_range_size > 0) {
2510 if (munmap(aligned + PAGE_POOL_ARENA_SIZE, end_out_of_range_size)) {
2511 rb_bug("page_pool_add_arena: munmap failed for end");
2512 }
2513 }
2514
2515 struct page_arena *arena = calloc1(sizeof(struct page_arena));
2516 if (arena == NULL) {
2517 if (munmap(aligned, PAGE_POOL_ARENA_SIZE)) {
2518 rb_bug("page_pool_add_arena: munmap failed for arena");
2519 }
2520 return false;
2521 }
2522 arena->start = aligned;
2523 arena->size = PAGE_POOL_ARENA_SIZE;
2524 arena->cold_freelist = NULL;
2525 arena->free_count = 0;
2526 arena->cold_count = 0;
2527 arena->next = g->page_pool.arenas;
2528 g->page_pool.arenas = arena;
2529 g->page_pool.arena_count++;
2530 g->page_pool.arena_current = arena;
2531
2532 g->page_pool.arena_cursor = aligned;
2533 g->page_pool.arena_end = aligned + PAGE_POOL_ARENA_SIZE;
2534
2535 return true;
2536}
2537#endif
2538
2539static struct heap_page_body *
2540page_pool_acquire(struct page_arena **arena_out)
2541{
2542 struct heap_page_body *body = NULL;
2543 bool need_reuse = false;
2544
2545 if (HEAP_PAGE_ALLOC_USE_MMAP) {
2546#ifdef HAVE_MMAP
2547 rb_global_objspace_t *g = global_objspace;
2548
2549 rb_native_mutex_lock(&g->page_pool.lock);
2550 if (g->page_pool.hot_list != NULL) {
2551 body = g->page_pool.hot_list;
2552 asan_unpoison_memory_region(body, PAGE_POOL_SCRATCH_SIZE, false);
2553 uintptr_t link = *(uintptr_t *)body;
2554 g->page_pool.hot_list = (struct heap_page_body *)(link & ~PAGE_POOL_ADVISED_BIT);
2555 g->page_pool.hot_count--;
2556 struct page_arena *arena = PAGE_POOL_BODY_ARENA(body);
2557 arena->free_count--;
2558 *arena_out = arena;
2559 }
2560 else {
2561 // find cold page body (madvised reusable)
2562 for (struct page_arena *a = g->page_pool.arenas; a; a = a->next) {
2563 if (a->cold_count > 0) {
2564 body = a->cold_freelist;
2565 asan_unpoison_memory_region(body, PAGE_POOL_SCRATCH_SIZE, false);
2566 uintptr_t link = *(uintptr_t *)body;
2567 a->cold_freelist = (struct heap_page_body *)(link & ~PAGE_POOL_ADVISED_BIT);
2568 a->cold_count--;
2569 a->free_count--;
2570 *arena_out = a;
2571 need_reuse = (link & PAGE_POOL_ADVISED_BIT) != 0;
2572 if (need_reuse) g->page_pool.advised_count--;
2573 break;
2574 }
2575 }
2576 if (body == NULL &&
2577 (g->page_pool.arena_cursor != g->page_pool.arena_end ||
2578 page_pool_add_arena(g))) {
2579 GC_ASSERT(g->page_pool.arena_cursor + HEAP_PAGE_SIZE <= g->page_pool.arena_end);
2580 body = (struct heap_page_body *)g->page_pool.arena_cursor;
2581 g->page_pool.arena_cursor += HEAP_PAGE_SIZE;
2582 *arena_out = g->page_pool.arena_current;
2583 }
2584 }
2585 rb_native_mutex_unlock(&g->page_pool.lock);
2586
2587 if (body != NULL) {
2588 if (need_reuse) {
2589 rb_vm_map_reuse((char *)body + g->page_pool.os_page_size,
2590 HEAP_PAGE_SIZE - g->page_pool.os_page_size);
2591 }
2592 asan_unpoison_memory_region(body, HEAP_PAGE_SIZE, false);
2593 }
2594#endif
2595 }
2596 else {
2597 body = gc_aligned_malloc(HEAP_PAGE_ALIGN, HEAP_PAGE_SIZE);
2598 *arena_out = NULL;
2599 }
2600
2601 return body;
2602}
2603
2604#ifdef HAVE_MMAP
2605static void
2606page_pool_release_locked(struct heap_page_body *body, struct page_arena *arena)
2607{
2608 rb_global_objspace_t *g = global_objspace;
2609
2610 ASSERT_PAGE_POOL_LOCKED(g);
2611
2612 /* A body in the empty-pages pool stays fully poisoned (see gc_sweep_page), so
2613 * unpoison the scratch area (link + arena tag) before writing. */
2614 asan_unpoison_memory_region(body, PAGE_POOL_SCRATCH_SIZE, false);
2615 arena->free_count++;
2616 PAGE_POOL_BODY_ARENA(body) = arena;
2617 if (g->page_pool.hot_count < PAGE_POOL_HOT_MAX) {
2618 *(uintptr_t *)body = (uintptr_t)g->page_pool.hot_list;
2619 g->page_pool.hot_list = body;
2620 g->page_pool.hot_count++;
2621 }
2622 else {
2623 *(uintptr_t *)body = (uintptr_t)arena->cold_freelist;
2624 arena->cold_freelist = body;
2625 arena->cold_count++;
2626 }
2627 asan_poison_memory_region(body, HEAP_PAGE_SIZE);
2628}
2629#endif
2630
2631static void
2632page_pool_release(struct heap_page_body *body, struct page_arena *arena)
2633{
2634 if (HEAP_PAGE_ALLOC_USE_MMAP) {
2635#ifdef HAVE_MMAP
2636 rb_global_objspace_t *g = global_objspace;
2637
2638 rb_native_mutex_lock(&g->page_pool.lock);
2639 page_pool_release_locked(body, arena);
2640 rb_native_mutex_unlock(&g->page_pool.lock);
2641#endif
2642 }
2643 else {
2644 gc_aligned_free(body, HEAP_PAGE_SIZE);
2645 }
2646}
2647
2648/* Allow the OS to reclaim pool memory. Runs only at major GC in single-objspace mode
2649 * (see gc_sweep_finish).
2650 *
2651 * Step A: madvise cold bodies, sparing the first OS page (which holds the in-body
2652 * freelist link and arena tag).
2653 *
2654 * Step B: munmap arenas whose 32 bodies are all free, keeping one extra empty
2655 * arena as a retention buffer when the remaining free pool is < half an arena. */
2656static void
2657page_pool_reclaim(rb_global_objspace_t *g)
2658{
2659 if (!HEAP_PAGE_ALLOC_USE_MMAP) return;
2660#ifdef HAVE_MMAP
2661 size_t os_page_size = g->page_pool.os_page_size;
2662
2663 rb_native_mutex_lock(&g->page_pool.lock);
2664
2665 /* Advising spares the first OS page of a body (it holds the in-body freelist link
2666 * and the arena tag), so it needs sub-page granularity: when the OS page size is
2667 * >= HEAP_PAGE_SIZE (e.g. 64KiB pages on aarch64) no body is ever advised, and
2668 * advised_count must not be adjusted anywhere either. */
2669 const bool can_advise = os_page_size < HEAP_PAGE_SIZE;
2670
2671 /* Step A — advise cold bodies (immediate release: drop RSS now if the platform allows). */
2672 if (can_advise) {
2673 for (struct page_arena *a = g->page_pool.arenas; a; a = a->next) {
2674 for (struct heap_page_body *body = a->cold_freelist; body; ) {
2675 asan_unpoison_memory_region(body, PAGE_POOL_SCRATCH_SIZE, false);
2676 uintptr_t link = *(uintptr_t *)body;
2677 struct heap_page_body *next =
2678 (struct heap_page_body *)(link & ~PAGE_POOL_ADVISED_BIT);
2679 if (!(link & PAGE_POOL_ADVISED_BIT)) {
2680 rb_vm_map_reusable_immediate((char *)body + os_page_size,
2681 HEAP_PAGE_SIZE - os_page_size, 0);
2682 *(uintptr_t *)body = link | PAGE_POOL_ADVISED_BIT;
2683 g->page_pool.advised_count++;
2684 }
2685 asan_poison_memory_region(body, PAGE_POOL_SCRATCH_SIZE);
2686 body = next;
2687 }
2688 }
2689 }
2690
2691 /* Step B — munmap fully-free arenas (with retention buffer).
2692 *
2693 * total_free = Σ free_count; free_count already includes hot-list bodies
2694 * (page_pool_release increments it unconditionally), so no separate hot_count.
2695 * An arena is eligible when all 32 of its bodies are free AND none sit on
2696 * the hot list (≤5 entries, pre-scanned). Keep one extra empty arena when
2697 * the rest of the free pool is < half an arena, to avoid thrash. */
2698 int total_free = 0;
2699 for (struct page_arena *a = g->page_pool.arenas; a; a = a->next) {
2700 total_free += a->free_count;
2701 }
2702
2703 struct page_arena *hot_arenas[PAGE_POOL_HOT_MAX ? PAGE_POOL_HOT_MAX : 1];
2704 int n_hot_arenas = 0;
2705 /* Collect arenas that have a hot body (≤ PAGE_POOL_HOT_MAX entries). */
2706 for (struct heap_page_body *body = g->page_pool.hot_list; body; ) {
2707 asan_unpoison_memory_region(body, PAGE_POOL_SCRATCH_SIZE, false);
2708 uintptr_t link = *(uintptr_t *)body;
2709 struct heap_page_body *next =
2710 (struct heap_page_body *)(link & ~PAGE_POOL_ADVISED_BIT);
2711 struct page_arena *arena = PAGE_POOL_BODY_ARENA(body);
2712 bool found = false;
2713 for (int i = 0; i < n_hot_arenas; i++) {
2714 if (hot_arenas[i] == arena) { found = true; break; }
2715 }
2716 if (!found && n_hot_arenas < PAGE_POOL_HOT_MAX) {
2717 hot_arenas[n_hot_arenas++] = arena;
2718 }
2719 asan_poison_memory_region(body, PAGE_POOL_SCRATCH_SIZE);
2720 body = next;
2721 }
2722
2723 bool retained_one = false;
2724 struct page_arena **pp = &g->page_pool.arenas;
2725 // munmap fully free arenas
2726 while (*pp) {
2727 struct page_arena *a = *pp;
2728 bool has_hot = false;
2729 for (int i = 0; i < n_hot_arenas; i++) {
2730 if (hot_arenas[i] == a) { has_hot = true; break; }
2731 }
2732 if (a->free_count != PAGE_POOL_ARENA_BODIES || has_hot) {
2733 pp = &a->next;
2734 continue;
2735 }
2736 GC_ASSERT(a->cold_count == PAGE_POOL_ARENA_BODIES);
2737
2738 int free_elsewhere = total_free - PAGE_POOL_ARENA_BODIES;
2739 if (free_elsewhere < PAGE_POOL_ARENA_KEEP_HALF && !retained_one) {
2740 retained_one = true;
2741 pp = &a->next;
2742 continue;
2743 }
2744
2745 *pp = a->next;
2746 if (munmap(a->start, a->size)) {
2747 rb_bug("page_pool_reclaim: munmap failed");
2748 }
2749 total_free -= PAGE_POOL_ARENA_BODIES;
2750 /* Every body of this arena is on its cold freelist, so Step A above has just
2751 * advised all of them -- but only if this platform can advise at all. */
2752 if (can_advise) {
2753 g->page_pool.advised_count -= PAGE_POOL_ARENA_BODIES;
2754 GC_ASSERT(g->page_pool.advised_count >= 0);
2755 }
2756 g->page_pool.arena_count--;
2757 g->page_pool.arenas_unmapped++;
2758 if (a == g->page_pool.arena_current) {
2759 // During next acquire, any remaining arenas that have cold bodies are used. This is guaranteed
2760 // because of the retention buffer.
2761 g->page_pool.arena_current = NULL;
2762 g->page_pool.arena_cursor = NULL;
2763 g->page_pool.arena_end = NULL;
2764 }
2765 free(a);
2766 }
2767
2768 rb_native_mutex_unlock(&g->page_pool.lock);
2769#endif
2770}
2771
2772static struct heap_page_body *
2773heap_page_body_allocate(struct page_arena **arena_out)
2774{
2775 struct heap_page_body *page_body = page_pool_acquire(arena_out);
2776
2777 GC_ASSERT(page_body == NULL || (uintptr_t)page_body % HEAP_PAGE_ALIGN == 0);
2778
2779 return page_body;
2780}
2781
2782static struct heap_page *
2783heap_page_resurrect(rb_objspace_t *objspace)
2784{
2785 struct heap_page *page = NULL;
2786 if (objspace->empty_pages == NULL) {
2787 GC_ASSERT(objspace->empty_pages_count == 0);
2788 }
2789 else {
2790 GC_ASSERT(objspace->empty_pages_count > 0);
2791 objspace->empty_pages_count--;
2792 page = objspace->empty_pages;
2793 objspace->empty_pages = page->free_next;
2794 /* Clear the flags left over from emptying the page before reusing it, or the
2795 * shareable and shref scans would keep walking an empty bitmap forever. */
2796 page->flags.has_shareable_objects = FALSE;
2797 page->flags.has_shref_objects = FALSE;
2798 }
2799
2800 return page;
2801}
2802
2803static struct heap_page *
2804heap_page_allocate(rb_objspace_t *objspace)
2805{
2806 struct page_arena *arena;
2807 struct heap_page_body *page_body = heap_page_body_allocate(&arena);
2808 if (page_body == 0) {
2809 rb_memerror();
2810 }
2811
2812 struct heap_page *page = calloc1(sizeof(struct heap_page));
2813 if (page == 0) {
2814 heap_page_body_free(page_body, arena);
2815 rb_memerror();
2816 }
2817
2818 uintptr_t start = (uintptr_t)page_body + sizeof(struct heap_page_header);
2819 uintptr_t end = (uintptr_t)page_body + HEAP_PAGE_SIZE;
2820
2821 size_t lo = 0;
2822 size_t hi = rb_darray_size(objspace->heap_pages.sorted);
2823 while (lo < hi) {
2824 struct heap_page *mid_page;
2825
2826 size_t mid = (lo + hi) / 2;
2827 mid_page = rb_darray_get(objspace->heap_pages.sorted, mid);
2828 if ((uintptr_t)mid_page->start < start) {
2829 lo = mid + 1;
2830 }
2831 else if ((uintptr_t)mid_page->start > start) {
2832 hi = mid;
2833 }
2834 else {
2835 rb_bug("same heap page is allocated: %p at %"PRIuVALUE, (void *)page_body, (VALUE)mid);
2836 }
2837 }
2838
2839 rb_darray_insert_without_gc(&objspace->heap_pages.sorted, hi, page);
2840
2841 if (heap_pages_lomem == 0 || heap_pages_lomem > start) heap_pages_lomem = start;
2842 if (heap_pages_himem < end) heap_pages_himem = end;
2843
2844 page->body = page_body;
2845 page->arena = arena;
2846 page_body->header.page = page;
2847 page->objspace = objspace;
2848
2849 objspace->heap_pages.allocated_pages++;
2850
2851 global_page_index_insert(page);
2852
2853 return page;
2854}
2855
2856static void
2857heap_add_page(rb_objspace_t *objspace, rb_heap_t *heap, struct heap_page *page)
2858{
2859 /* Adding to eden heap during incremental sweeping is forbidden */
2860 GC_ASSERT(!heap->sweeping_page);
2861 GC_ASSERT(heap_page_in_global_empty_pages_pool(objspace, page));
2862
2863 /* Align start to slot_size boundary */
2864 uintptr_t start = (uintptr_t)page->body + sizeof(struct heap_page_header);
2865 uintptr_t rem = start % heap->slot_size;
2866 if (rem) start += heap->slot_size - rem;
2867
2868 int slot_count = (int)((HEAP_PAGE_SIZE - (start - (uintptr_t)page->body))/heap->slot_size);
2869
2870 page->start = start;
2871 page->total_slots = slot_count;
2872 page->slot_size = heap->slot_size;
2873 page->slot_size_reciprocal = heap_slot_reciprocal_table[heap - heaps];
2874 page->heap = heap;
2875
2876 memset(&page->wb_unprotected_bits[0], 0, HEAP_PAGE_BITMAP_SIZE);
2877 memset(&page->age_bits[0], 0, sizeof(page->age_bits));
2878
2879 asan_unlock_freelist(page);
2880 asan_unpoison_memory_region(page->body, HEAP_PAGE_SIZE, false);
2881
2882 uintptr_t slots_end = start + (uintptr_t)slot_count * heap->slot_size;
2883
2884 memset((void *)start, 0, slots_end - start);
2885
2886 struct free_region *region = (struct free_region *)start;
2887 region->flags = 0;
2888 region->end = slots_end;
2889 region->next = NULL;
2890 page->free_region = region;
2891
2892 /* Poison every free slot; each is unpoisoned again as it is handed out. */
2893 for (uintptr_t p = start; p < slots_end; p += heap->slot_size) {
2894 rb_asan_poison_object((VALUE)p);
2895 }
2896 asan_lock_freelist(page);
2897
2898 page->free_slots = slot_count;
2899
2900 heap->total_allocated_pages++;
2901
2902 ccan_list_add_tail(&heap->pages, &page->page_node);
2903 heap->total_pages++;
2904 heap->total_slots += page->total_slots;
2905}
2906
2907static int
2908heap_page_allocate_and_initialize(rb_objspace_t *objspace, rb_heap_t *heap)
2909{
2910 gc_report(1, objspace, "heap_page_allocate_and_initialize: rb_darray_size(objspace->heap_pages.sorted): %"PRIdSIZE", "
2911 "allocatable_bytes: %"PRIdSIZE", heap->total_pages: %"PRIdSIZE"\n",
2912 rb_darray_size(objspace->heap_pages.sorted), objspace->heap_pages.allocatable_bytes, heap->total_pages);
2913
2914 bool allocated = false;
2915 struct heap_page *page = heap_page_resurrect(objspace);
2916
2917 if (page == NULL && objspace->heap_pages.allocatable_bytes > 0) {
2918 page = heap_page_allocate(objspace);
2919 allocated = true;
2920
2921 GC_ASSERT(page != NULL);
2922 }
2923
2924 if (page != NULL) {
2925 heap_add_page(objspace, heap, page);
2926 heap_add_freepage(heap, page);
2927
2928 if (allocated) {
2929 size_t page_bytes = (size_t)page->total_slots * page->slot_size;
2930 if (objspace->heap_pages.allocatable_bytes > page_bytes) {
2931 objspace->heap_pages.allocatable_bytes -= page_bytes;
2932 }
2933 else {
2934 objspace->heap_pages.allocatable_bytes = 0;
2935 }
2936 }
2937 }
2938
2939 return page != NULL;
2940}
2941
2942static void
2943heap_page_allocate_and_initialize_force(rb_objspace_t *objspace, rb_heap_t *heap)
2944{
2945 size_t prev_allocatable_bytes = objspace->heap_pages.allocatable_bytes;
2946 objspace->heap_pages.allocatable_bytes = HEAP_PAGE_SIZE;
2947 heap_page_allocate_and_initialize(objspace, heap);
2948 GC_ASSERT(heap->free_pages != NULL);
2949 objspace->heap_pages.allocatable_bytes = prev_allocatable_bytes;
2950}
2951
2952static void
2953gc_continue(rb_objspace_t *objspace, rb_heap_t *heap)
2954{
2955 unsigned int lock_lev;
2956 bool needs_gc = is_incremental_marking(objspace) || needs_continue_sweeping(objspace, heap);
2957 if (!needs_gc) return;
2958
2959 gc_enter(objspace, gc_enter_event_continue, &lock_lev); // takes vm barrier, try to avoid
2960
2961 /* Continue marking if in incremental marking. */
2962 if (is_incremental_marking(objspace)) {
2963 if (gc_marks_continue(objspace, heap)) {
2964 gc_sweep(objspace);
2965 }
2966 }
2967
2968 if (needs_continue_sweeping(objspace, heap)) {
2969 gc_sweep_continue(objspace, heap);
2970 }
2971
2972 gc_exit(objspace, gc_enter_event_continue, &lock_lev);
2973}
2974
2975static void
2976heap_prepare(rb_objspace_t *objspace, rb_heap_t *heap)
2977{
2978 GC_ASSERT(heap->free_pages == NULL);
2979
2980 if (heap->total_slots < gc_params.heap_init_bytes / heap->slot_size &&
2981 heap->sweeping_page == NULL) {
2982 heap_page_allocate_and_initialize_force(objspace, heap);
2983 GC_ASSERT(heap->free_pages != NULL);
2984 return;
2985 }
2986
2987 /* Continue incremental marking or lazy sweeping, if in any of those steps. */
2988 gc_continue(objspace, heap);
2989
2990 if (heap->free_pages == NULL) {
2991 heap_page_allocate_and_initialize(objspace, heap);
2992 }
2993
2994 /* If we still don't have a free page and not allowed to create a new page,
2995 * we should start a new GC cycle. */
2996 if (heap->free_pages == NULL) {
2997 GC_ASSERT(objspace->empty_pages_count == 0);
2998 GC_ASSERT(objspace->heap_pages.allocatable_bytes == 0);
2999
3000 if (gc_start(objspace, GPR_FLAG_NEWOBJ) == FALSE) {
3001 rb_memerror();
3002 }
3003 else {
3004 if (objspace->heap_pages.allocatable_bytes == 0 && !gc_config_full_mark_val) {
3005 heap_allocatable_bytes_expand(objspace, heap,
3006 heap->freed_slots + heap->empty_slots,
3007 heap->total_slots, heap->slot_size);
3008 GC_ASSERT(objspace->heap_pages.allocatable_bytes > 0);
3009 }
3010 /* Do steps of incremental marking or lazy sweeping if the GC run permits. */
3011 gc_continue(objspace, heap);
3012
3013 /* If we're not incremental marking (e.g. a minor GC) or finished
3014 * sweeping and still don't have a free page, then
3015 * gc_sweep_finish_heap should allow us to create a new page. */
3016 if (heap->free_pages == NULL && !heap_page_allocate_and_initialize(objspace, heap)) {
3017 if (gc_needs_major_flags == GPR_FLAG_NONE) {
3018 rb_bug("cannot create a new page after GC");
3019 }
3020 else { // Major GC is required, which will allow us to create new page
3021 if (gc_start(objspace, GPR_FLAG_NEWOBJ) == FALSE) {
3022 rb_memerror();
3023 }
3024 else {
3025 /* Do steps of incremental marking or lazy sweeping. */
3026 gc_continue(objspace, heap);
3027
3028 if (heap->free_pages == NULL &&
3029 !heap_page_allocate_and_initialize(objspace, heap)) {
3030 rb_bug("cannot create a new page after major GC");
3031 }
3032 }
3033 }
3034 }
3035 }
3036 }
3037
3038 GC_ASSERT(heap->free_pages != NULL);
3039}
3040
3041#if GC_DEBUG
3042static inline const char*
3043rb_gc_impl_source_location_cstr(int *ptr)
3044{
3045 /* We could directly refer `rb_source_location_cstr()` before, but not any
3046 * longer. We have to heavy lift using our debugging API. */
3047 if (! ptr) {
3048 return NULL;
3049 }
3050 else if (! (*ptr = rb_sourceline())) {
3051 return NULL;
3052 }
3053 else {
3054 return rb_sourcefile();
3055 }
3056}
3057#endif
3058
3059static inline VALUE
3060newobj_init(VALUE klass, VALUE flags, int wb_protected, rb_objspace_t *objspace, VALUE obj)
3061{
3062 GC_ASSERT(BUILTIN_TYPE(obj) == T_NONE);
3063 GC_ASSERT((flags & FL_WB_PROTECTED) == 0);
3064 RBASIC(obj)->flags = flags;
3065 *((VALUE *)&RBASIC(obj)->klass) = klass;
3066#if RBASIC_SHAPE_ID_FIELD
3067 RBASIC(obj)->shape_id = 0;
3068#endif
3069
3070 if (RB_UNLIKELY(flags & RUBY_FL_SHAREABLE)) {
3071 /* A born-shareable object must be WB protected: the shref and remembered-set
3072 * rules for shareable objects assume the write barrier. A local GC roots
3073 * shareable objects from this bit (pinned_roots_mark). */
3074 GC_ASSERT(wb_protected);
3075 gc_page_add_shareable(GET_HEAP_PAGE(obj), obj);
3076 }
3077
3078#if RGENGC_CHECK_MODE
3079 int lev = RB_GC_VM_LOCK_NO_BARRIER();
3080 {
3081 check_rvalue_consistency(objspace, obj);
3082
3083 GC_ASSERT(RVALUE_MARKED(objspace, obj) == FALSE);
3084 GC_ASSERT(RVALUE_MARKING(objspace, obj) == FALSE);
3085 GC_ASSERT(RVALUE_OLD_P(objspace, obj) == FALSE);
3086 GC_ASSERT(RVALUE_WB_UNPROTECTED(objspace, obj) == FALSE);
3087
3088 if (RVALUE_REMEMBERED(objspace, obj)) rb_bug("newobj: %s is remembered.", rb_obj_info(obj));
3089 }
3090 RB_GC_VM_UNLOCK_NO_BARRIER(lev);
3091#endif
3092
3093 if (RB_UNLIKELY(wb_protected == FALSE)) {
3094 MARK_IN_BITMAP(GET_HEAP_WB_UNPROTECTED_BITS(obj), obj);
3095 }
3096
3097#if RGENGC_PROFILE
3098 if (wb_protected) {
3099 objspace->profile.total_generated_normal_object_count++;
3100#if RGENGC_PROFILE >= 2
3101 objspace->profile.generated_normal_object_count_types[BUILTIN_TYPE(obj)]++;
3102#endif
3103 }
3104 else {
3105 objspace->profile.total_generated_shady_object_count++;
3106#if RGENGC_PROFILE >= 2
3107 objspace->profile.generated_shady_object_count_types[BUILTIN_TYPE(obj)]++;
3108#endif
3109 }
3110#endif
3111
3112#if GC_DEBUG
3113 GET_RVALUE_OVERHEAD(obj)->file = rb_gc_impl_source_location_cstr(&GET_RVALUE_OVERHEAD(obj)->line);
3114 GC_ASSERT(!SPECIAL_CONST_P(obj)); /* check alignment */
3115#endif
3116
3117 gc_report(5, objspace, "newobj: %s\n", rb_obj_info(obj));
3118
3119 // RUBY_DEBUG_LOG("obj:%p (%s)", (void *)obj, rb_obj_info(obj));
3120 return obj;
3121}
3122
3123size_t
3124rb_gc_impl_obj_slot_size(VALUE obj)
3125{
3126 return GET_HEAP_PAGE(obj)->slot_size - RVALUE_OVERHEAD;
3127}
3128
3129bool
3130rb_gc_impl_pinned_p(void *objspace_ptr, VALUE obj)
3131{
3132 return RVALUE_PINNED((rb_objspace_t *)objspace_ptr, obj);
3133}
3134
3135static inline size_t
3136heap_slot_size(unsigned char pool_id)
3137{
3138 GC_ASSERT(pool_id < HEAP_COUNT);
3139
3140 return pool_slot_sizes[pool_id] - RVALUE_OVERHEAD;
3141}
3142
3143size_t
3144rb_gc_impl_max_allocation_size(void)
3145{
3146 return heap_slot_size(HEAP_COUNT - 1);
3147}
3148
3149bool
3150rb_gc_impl_size_allocatable_p(size_t size)
3151{
3152 return size <= rb_gc_impl_max_allocation_size();
3153}
3154
3155static inline bool
3156heap_advance_region(rb_heap_t *heap)
3157{
3158 struct free_region *region = heap->newobj.alloc_next_region;
3159 if (region == NULL) {
3160 return false;
3161 }
3162
3163 rb_asan_unpoison_object((VALUE)region, false);
3164 GC_ASSERT(RB_TYPE_P((VALUE)region, T_NONE));
3165 heap->newobj.alloc_cursor = (uintptr_t)region;
3166 heap->newobj.alloc_cursor_end = region->end;
3167 heap->newobj.alloc_next_region = region->next;
3168 rb_asan_poison_object((VALUE)region);
3169
3170 return true;
3171}
3172
3173/* The whole region is ours until the next refill, so charge it to the step now. */
3174static inline void
3175heap_charge_region(rb_objspace_t *objspace, const rb_heap_t *heap, size_t heap_idx)
3176{
3177 objspace->incremental_mark_step_allocated_slots +=
3178 (heap->newobj.alloc_cursor_end - heap->newobj.alloc_cursor) / pool_slot_sizes[heap_idx];
3179}
3180
3181static inline VALUE
3182heap_alloc_slot(rb_objspace_t *objspace, size_t heap_idx)
3183{
3184 rb_heap_t *heap = &heaps[heap_idx];
3185
3186 uintptr_t cursor = heap->newobj.alloc_cursor;
3187 if (RB_UNLIKELY(cursor >= heap->newobj.alloc_cursor_end)) {
3188 /* Marking owes us a step before the next region, and newobj_refill runs it. */
3189 if (RB_UNLIKELY(is_incremental_marking(objspace)) ||
3190 heap_advance_region(heap) == false) {
3191 return Qfalse;
3192 }
3193 cursor = heap->newobj.alloc_cursor;
3194 }
3195
3196 VALUE obj = (VALUE)cursor;
3197 rb_asan_unpoison_object(obj, true);
3198 heap->newobj.alloc_cursor = cursor + pool_slot_sizes[heap_idx];
3199
3200 /* Single writer (the owning Ractor under the GVL), so a plain increment is enough. */
3201 heap->total_allocated_objects++;
3202
3203#if RGENGC_CHECK_MODE
3204 GC_ASSERT(rb_gc_impl_obj_slot_size(obj) == heap_slot_size(heap_idx));
3205 // zero clear
3206 MEMZERO((char *)obj, char, heap_slot_size(heap_idx));
3207#endif
3208 return obj;
3209}
3210
3211static struct heap_page *
3212heap_next_free_page(rb_objspace_t *objspace, rb_heap_t *heap)
3213{
3214 struct heap_page *page;
3215
3216 if (heap->free_pages == NULL) {
3217 heap_prepare(objspace, heap);
3218 }
3219
3220 page = heap->free_pages;
3221 heap->free_pages = page->free_next;
3222
3223 GC_ASSERT(page->free_slots != 0);
3224
3225 asan_unlock_freelist(page);
3226
3227 return page;
3228}
3229
3230static inline void
3231heap_set_alloc_page(rb_objspace_t *objspace, size_t heap_idx, struct heap_page *page)
3232{
3233 gc_report(3, objspace, "heap_set_alloc_page: Using page %p\n", (void *)page->body);
3234
3235 rb_heap_t *heap = &heaps[heap_idx];
3236
3237 GC_ASSERT(heap->newobj.alloc_cursor >= heap->newobj.alloc_cursor_end);
3238 GC_ASSERT(heap->newobj.alloc_next_region == NULL);
3239 GC_ASSERT(page->free_slots != 0);
3240 GC_ASSERT(page->free_region != NULL);
3241
3242 heap->newobj.alloc_using_page = page;
3243
3244 struct free_region *region = page->free_region;
3245 rb_asan_unpoison_object((VALUE)region, false);
3246 GC_ASSERT(RB_TYPE_P((VALUE)region, T_NONE));
3247 heap->newobj.alloc_cursor = (uintptr_t)region;
3248 heap->newobj.alloc_cursor_end = region->end;
3249 heap->newobj.alloc_next_region = region->next;
3250 rb_asan_poison_object((VALUE)region);
3251
3252 page->free_slots = 0;
3253 page->free_region = NULL;
3254}
3255
3256static void
3257init_size_to_heap_idx(void)
3258{
3259 /* Process-wide and immutable, so build it once at boot. A rebuild in a later
3260 * objspace_init would write the same values but race other threads' lock-free
3261 * allocation-fastpath reads. */
3262 static bool initialized = false;
3263 if (initialized) return;
3264 initialized = true;
3265
3266 for (size_t i = 0; i < sizeof(size_to_heap_idx); i++) {
3267 size_t effective = i * 8 + RVALUE_OVERHEAD;
3268 uint8_t idx;
3269 for (idx = 0; idx < HEAP_COUNT; idx++) {
3270 if (effective <= pool_slot_sizes[idx]) break;
3271 }
3272 size_to_heap_idx[i] = idx;
3273 }
3274}
3275
3276static inline size_t
3277heap_idx_for_size(size_t size)
3278{
3279 size_t compressed = (size + 7) >> 3;
3280 if (compressed < sizeof(size_to_heap_idx)) {
3281 size_t heap_idx = size_to_heap_idx[compressed];
3282 if (RB_LIKELY(heap_idx < HEAP_COUNT)) return heap_idx;
3283 }
3284
3285 rb_bug("heap_idx_for_size: allocation size too large "
3286 "(size=%"PRIuSIZE")", size);
3287}
3288
3289size_t
3290rb_gc_impl_size_slot_size(void *objspace_ptr, size_t size)
3291{
3292 return heap_slot_size((unsigned char)heap_idx_for_size(size));
3293}
3294
3295bool
3296rb_gc_impl_zjit_new_obj_fastpath(void *objspace_ptr, size_t alloc_size, VALUE flags, VALUE klass,
3297 struct rb_gc_zjit_fastpath *fastpath)
3298{
3299#if USE_ZJIT
3300 size_t heap_idx = 0;
3301 size_t slot_size = 0;
3302 for (; heap_idx < HEAP_COUNT; heap_idx++) {
3303 if (alloc_size + RVALUE_OVERHEAD <= pool_slot_sizes[heap_idx]) {
3304 slot_size = pool_slot_sizes[heap_idx];
3305 break;
3306 }
3307 }
3308 if (slot_size == 0) return false;
3309
3310#undef heaps
3311 size_t base = offsetof(rb_objspace_t, heaps)
3312 + heap_idx * sizeof(rb_heap_t)
3313 + offsetof(rb_heap_t, newobj);
3314#define heaps objspace->heaps
3315
3316 struct rb_gc_zjit_default_new_obj_fastpath default_fastpath = {
3317 base + offsetof(rb_heap_newobj_t, alloc_cursor),
3318 base + offsetof(rb_heap_newobj_t, alloc_cursor_end),
3319 slot_size,
3320 base - offsetof(rb_heap_t, newobj) + offsetof(rb_heap_t, total_allocated_objects),
3321 flags,
3322 klass
3323 };
3324
3325 memset(fastpath, 0, sizeof(*fastpath));
3326 fastpath->kind = RB_GC_ZJIT_FASTPATH_DEFAULT;
3327 memcpy(fastpath->data.words, &default_fastpath, sizeof(default_fastpath));
3328
3329 return true;
3330#else
3331 return false;
3332#endif
3333}
3334
3335NOINLINE(static VALUE newobj_refill(rb_objspace_t *objspace, size_t heap_idx));
3336
3337static VALUE
3338newobj_refill(rb_objspace_t *objspace, size_t heap_idx)
3339{
3340 rb_heap_t *heap = &heaps[heap_idx];
3341 VALUE obj = Qfalse;
3342
3343 /* No lock: a heap is single-writer (its owner thread, serialized by the GVL inside
3344 * the Ractor), the page pool has its own mutex, and a GC started from here takes
3345 * whatever gc_enter needs. */
3346 if (is_incremental_marking(objspace)) {
3347 /* The fast path sends us here at every region, which is far more often than the
3348 * step size, so step only once the regions add up to it. */
3349 if (objspace->incremental_mark_step_allocated_slots >= INCREMENTAL_MARK_STEP_ALLOCATIONS) {
3350 gc_continue(objspace, heap);
3351 objspace->incremental_mark_step_allocated_slots = 0;
3352 }
3353
3354 // Move on to the region the fast path refused to take
3355 if (heap_advance_region(heap)) {
3356 heap_charge_region(objspace, heap, heap_idx);
3357 obj = heap_alloc_slot(objspace, heap_idx);
3358 }
3359 }
3360
3361 if (obj == Qfalse) {
3362 // Get next free page (possibly running GC)
3363 struct heap_page *page = heap_next_free_page(objspace, heap);
3364 heap_set_alloc_page(objspace, heap_idx, page);
3365 heap_charge_region(objspace, heap, heap_idx);
3366
3367 // Retry allocation after moving to new page
3368 obj = heap_alloc_slot(objspace, heap_idx);
3369 }
3370
3371 if (RB_UNLIKELY(obj == Qfalse)) {
3372 rb_memerror();
3373 }
3374 return obj;
3375}
3376
3377static VALUE
3378newobj_alloc(rb_objspace_t *objspace, size_t heap_idx)
3379{
3380 /* The objspace belongs to the current Ractor and is single-writer, so the fast path
3381 * needs no lock. Stress GC runs in the caller's slow path, before newobj_alloc. */
3382 VALUE obj = heap_alloc_slot(objspace, heap_idx);
3383
3384 if (RB_UNLIKELY(obj == Qfalse)) {
3385 obj = newobj_refill(objspace, heap_idx);
3386 }
3387
3388 return obj;
3389}
3390
3391ALWAYS_INLINE(static VALUE newobj_slowpath(VALUE klass, VALUE flags, rb_objspace_t *objspace, int wb_protected, size_t heap_idx));
3392
3393static inline VALUE
3394newobj_slowpath(VALUE klass, VALUE flags, rb_objspace_t *objspace, int wb_protected, size_t heap_idx)
3395{
3396 VALUE obj;
3397
3398 /* No lock (see newobj_refill); during_gc and the stress flag are this objspace's own state. */
3399 if (RB_UNLIKELY(during_gc || ruby_gc_stressful)) {
3400 if (during_gc) {
3401 dont_gc_on();
3402 during_gc = 0;
3403 if (rb_memerror_reentered()) {
3404 rb_memerror();
3405 }
3406 rb_bug("object allocation during garbage collection phase");
3407 }
3408
3409 if (ruby_gc_stressful) {
3410 if (!garbage_collect(objspace, GPR_FLAG_NEWOBJ)) {
3411 rb_memerror();
3412 }
3413 }
3414 }
3415
3416 obj = newobj_alloc(objspace, heap_idx);
3417 newobj_init(klass, flags, wb_protected, objspace, obj);
3418
3419 if (RB_UNLIKELY(ruby_gc_stressful)) {
3420 rb_heap_t *heap = &heaps[heap_idx];
3421 heap->newobj.alloc_cursor_end = heap->newobj.alloc_cursor;
3422 }
3423
3424 return obj;
3425}
3426
3427NOINLINE(static VALUE newobj_slowpath_wb_protected(VALUE klass, VALUE flags,
3428 rb_objspace_t *objspace, size_t heap_idx));
3429NOINLINE(static VALUE newobj_slowpath_wb_unprotected(VALUE klass, VALUE flags,
3430 rb_objspace_t *objspace, size_t heap_idx));
3431
3432static VALUE
3433newobj_slowpath_wb_protected(VALUE klass, VALUE flags, rb_objspace_t *objspace, size_t heap_idx)
3434{
3435 return newobj_slowpath(klass, flags, objspace, TRUE, heap_idx);
3436}
3437
3438static VALUE
3439newobj_slowpath_wb_unprotected(VALUE klass, VALUE flags, rb_objspace_t *objspace, size_t heap_idx)
3440{
3441 return newobj_slowpath(klass, flags, objspace, FALSE, heap_idx);
3442}
3443
3444VALUE
3445rb_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)
3446{
3447 VALUE obj;
3448 rb_objspace_t *objspace = objspace_ptr;
3449
3450 /* There is no per-Ractor cache; the argument stays for ABI compatibility with other
3451 * GC implementations such as MMTk. */
3452 (void)cache_ptr;
3453
3454 RB_DEBUG_COUNTER_INC(obj_newobj);
3455 (void)RB_DEBUG_COUNTER_INC_IF(obj_newobj_wb_unprotected, !wb_protected);
3456
3457 if (RB_UNLIKELY(stress_to_class)) {
3458 if (rb_hash_lookup2(stress_to_class, klass, Qundef) != Qundef) {
3459 rb_memerror();
3460 }
3461 }
3462
3463 size_t heap_idx = heap_idx_for_size(alloc_size);
3464 *actual_alloc_size = heap_slot_size((unsigned char)heap_idx);
3465
3466 if (!RB_UNLIKELY(during_gc || ruby_gc_stressful) &&
3467 wb_protected) {
3468 obj = newobj_alloc(objspace, heap_idx);
3469 newobj_init(klass, flags, wb_protected, objspace, obj);
3470 }
3471 else {
3472 RB_DEBUG_COUNTER_INC(obj_newobj_slowpath);
3473
3474 obj = wb_protected ?
3475 newobj_slowpath_wb_protected(klass, flags, objspace, heap_idx) :
3476 newobj_slowpath_wb_unprotected(klass, flags, objspace, heap_idx);
3477 }
3478
3479 return obj;
3480}
3481
3482static int
3483ptr_in_page_body_p(const void *ptr, const void *memb)
3484{
3485 struct heap_page *page = *(struct heap_page **)memb;
3486 uintptr_t p_body = (uintptr_t)page->body;
3487
3488 if ((uintptr_t)ptr >= p_body) {
3489 return (uintptr_t)ptr < (p_body + HEAP_PAGE_SIZE) ? 0 : 1;
3490 }
3491 else {
3492 return -1;
3493 }
3494}
3495
3496PUREFUNC(static inline struct heap_page *heap_page_for_ptr(rb_objspace_t *objspace, uintptr_t ptr);)
3497static inline struct heap_page *
3498heap_page_for_ptr(rb_objspace_t *objspace, uintptr_t ptr)
3499{
3500 struct heap_page **res;
3501
3502 if (ptr < (uintptr_t)heap_pages_lomem ||
3503 ptr > (uintptr_t)heap_pages_himem) {
3504 return NULL;
3505 }
3506
3507 res = bsearch((void *)ptr, rb_darray_ref(objspace->heap_pages.sorted, 0),
3508 rb_darray_size(objspace->heap_pages.sorted), sizeof(struct heap_page *),
3509 ptr_in_page_body_p);
3510
3511 if (res) {
3512 return *res;
3513 }
3514 else {
3515 return NULL;
3516 }
3517}
3518
3519PUREFUNC(static inline bool is_pointer_to_heap(rb_objspace_t *objspace, const void *ptr);)
3520static inline bool
3521is_pointer_to_heap(rb_objspace_t *objspace, const void *ptr)
3522{
3523 register uintptr_t p = (uintptr_t)ptr;
3524 register struct heap_page *page;
3525
3526 RB_DEBUG_COUNTER_INC(gc_isptr_trial);
3527
3528 if (p < heap_pages_lomem || p > heap_pages_himem) return FALSE;
3529 RB_DEBUG_COUNTER_INC(gc_isptr_range);
3530
3531 if (p % sizeof(VALUE) != 0) return FALSE;
3532 RB_DEBUG_COUNTER_INC(gc_isptr_align);
3533
3534 page = heap_page_for_ptr(objspace, (uintptr_t)ptr);
3535 if (page) {
3536 RB_DEBUG_COUNTER_INC(gc_isptr_maybe);
3537 if (heap_page_in_global_empty_pages_pool(objspace, page)) {
3538 return FALSE;
3539 }
3540 else {
3541 if (p < page->start) return FALSE;
3542 if (p >= page->start + (page->total_slots * page->slot_size)) return FALSE;
3543 if ((p - page->start) % page->slot_size != 0) return FALSE;
3544
3545 return TRUE;
3546 }
3547 }
3548 return FALSE;
3549}
3550
3551bool
3552rb_gc_impl_live_object_p(void *objspace_ptr, const void *ptr)
3553{
3554 rb_objspace_t *objspace = objspace_ptr;
3555
3556 /* Whether ptr refers to a live object. is_pointer_to_heap is the
3557 * address-only check; T_NONE, T_MOVED, and T_ZOMBIE slots are valid heap
3558 * addresses but not live objects. */
3559 if (!is_pointer_to_heap(objspace, ptr)) return false;
3560
3561 VALUE obj = (VALUE)ptr;
3562 bool live = false;
3563 asan_unpoisoning_object(obj) {
3564 switch (BUILTIN_TYPE(obj)) {
3565 case T_NONE:
3566 case T_MOVED:
3567 case T_ZOMBIE:
3568 break;
3569 default:
3570 live = true;
3571 break;
3572 }
3573 }
3574 return live;
3575}
3576
3577#define ZOMBIE_OBJ_KEPT_FLAGS (FL_FINALIZE)
3578
3579void
3580rb_gc_impl_make_zombie(void *objspace_ptr, VALUE obj, void (*dfree)(void *), void *data)
3581{
3582 rb_objspace_t *objspace = objspace_ptr;
3583
3584 struct RZombie *zombie = RZOMBIE(obj);
3585 zombie->flags = T_ZOMBIE | (zombie->flags & ZOMBIE_OBJ_KEPT_FLAGS);
3586 zombie->dfree = dfree;
3587 zombie->data = data;
3588 VALUE prev, next = heap_pages_deferred_final;
3589 do {
3590 zombie->next = prev = next;
3591 next = RUBY_ATOMIC_VALUE_CAS(heap_pages_deferred_final, prev, obj);
3592 } while (next != prev);
3593
3594 struct heap_page *page = GET_HEAP_PAGE(obj);
3595 page->final_slots++;
3596 page->heap->final_slots_count++;
3597}
3598
3599typedef int each_obj_callback(void *, void *, size_t, void *);
3600typedef int each_page_callback(struct heap_page *, void *);
3601
3604 bool reenable_incremental;
3605
3606 /* Visit only the pages that hold shareable objects, so a foreign Ractor's objspace
3607 * can be walked for its shareable objects alone, without touching the rest of its
3608 * isolated heap. */
3609 bool shareable_only;
3610
3611 /* Set when walking a foreign objspace without settling its stopped lazy sweep
3612 * (settling would run the owner's obj_free and dfree on this thread). Objects the
3613 * sweep is about to free are skipped: on an unswept page, unmarked means dead. */
3614 bool skip_unswept_dead;
3615
3616 each_obj_callback *each_obj_callback;
3617 each_page_callback *each_page_callback;
3618 void *data;
3619
3620 struct heap_page **pages[HEAP_COUNT];
3621 size_t pages_counts[HEAP_COUNT];
3622};
3623
3624static VALUE
3625objspace_each_objects_ensure(VALUE arg)
3626{
3627 struct each_obj_data *data = (struct each_obj_data *)arg;
3628 rb_objspace_t *objspace = data->objspace;
3629
3630 /* Reenable incremental GC */
3631 if (data->reenable_incremental) {
3632 objspace->flags.dont_incremental = FALSE;
3633 }
3634
3635 for (int i = 0; i < HEAP_COUNT; i++) {
3636 struct heap_page **pages = data->pages[i];
3637 free(pages);
3638 }
3639
3640 return Qnil;
3641}
3642
3643static VALUE
3644objspace_each_objects_try(VALUE arg)
3645{
3646 struct each_obj_data *data = (struct each_obj_data *)arg;
3647 rb_objspace_t *objspace = data->objspace;
3648
3649 /* Copy pages from all heaps to their respective buffers. */
3650 for (int i = 0; i < HEAP_COUNT; i++) {
3651 rb_heap_t *heap = &heaps[i];
3652 size_t size = heap->total_pages * sizeof(struct heap_page *);
3653
3654 struct heap_page **pages = malloc(size);
3655 if (!pages) rb_memerror();
3656
3657 /* Set up pages buffer by iterating over all pages in the current eden
3658 * heap. This will be a snapshot of the state of the heap before we
3659 * call the callback over each page that exists in this buffer. Thus it
3660 * is safe for the callback to allocate objects without possibly entering
3661 * an infinite loop. */
3662 struct heap_page *page = 0;
3663 size_t pages_count = 0;
3664 ccan_list_for_each(&heap->pages, page, page_node) {
3665 pages[pages_count] = page;
3666 pages_count++;
3667 }
3668 data->pages[i] = pages;
3669 data->pages_counts[i] = pages_count;
3670 GC_ASSERT(pages_count == heap->total_pages);
3671 }
3672
3673 for (int i = 0; i < HEAP_COUNT; i++) {
3674 rb_heap_t *heap = &heaps[i];
3675 size_t pages_count = data->pages_counts[i];
3676 struct heap_page **pages = data->pages[i];
3677
3678 struct heap_page *page = ccan_list_top(&heap->pages, struct heap_page, page_node);
3679 for (size_t i = 0; i < pages_count; i++) {
3680 /* If we have reached the end of the linked list then there are no
3681 * more pages, so break. */
3682 if (page == NULL) break;
3683
3684 /* If this page does not match the one in the buffer, then move to
3685 * the next page in the buffer. */
3686 if (pages[i] != page) continue;
3687
3688 uintptr_t pstart = (uintptr_t)page->start;
3689 uintptr_t pend = pstart + (page->total_slots * heap->slot_size);
3690
3691 if (data->shareable_only) {
3692 /* Hand shareable objects to the callback one slot at a time, not the
3693 * whole page: walking a foreign Ractor's objspace must never expose its
3694 * unshareable objects, which the caller cannot inspect safely. */
3695 if (page->flags.has_shareable_objects) {
3696 /* This walk runs over a foreign objspace under the barrier and
3697 * must not settle the owner's stopped lazy sweep: settling would run
3698 * the owner's obj_free and dfree on this thread with this Ractor's
3699 * identity (wrong per-Ractor tables, a foreign T_DATA dfree). So no
3700 * gc_rest, and objects the sweep is about to free are skipped: on an
3701 * unswept page unmarked means dead and its shareable bit merely has
3702 * not been bulk-cleared yet. Passing one to the callback would
3703 * resurrect it, handing out a reference the owner's sweep frees as
3704 * soon as the barrier lifts. */
3705 const bool page_unswept = is_lazy_sweeping(objspace) && page->flags.before_sweep;
3706 int planes = CEILDIV(page->total_slots, BITS_BITLENGTH);
3707 uintptr_t base = pstart;
3708 bool stop = false;
3709 for (int j = 0; j < planes && !stop; j++) {
3710 bits_t bits = page->shareable_bits[j];
3711 uintptr_t slot = base;
3712 while (bits) {
3713 if ((bits & 1) && data->each_obj_callback &&
3714 !(page_unswept && !RVALUE_MARKED(objspace, (VALUE)slot)) &&
3715 (*data->each_obj_callback)((void *)slot, (void *)(slot + heap->slot_size),
3716 heap->slot_size, data->data)) {
3717 stop = true;
3718 break;
3719 }
3720 slot += heap->slot_size;
3721 bits >>= 1;
3722 }
3723 base += BITS_BITLENGTH * heap->slot_size;
3724 }
3725 if (stop) break;
3726 }
3727 }
3728 else if (data->skip_unswept_dead &&
3729 is_lazy_sweeping(objspace) && page->flags.before_sweep) {
3730 /* A foreign page pending sweep: hand out the live objects one slot at a
3731 * time and skip the unmarked (dead) ones the owner's sweep frees as soon
3732 * as the barrier lifts. */
3733 bool stop = false;
3734 for (uintptr_t slot = pstart; slot < pend; slot += heap->slot_size) {
3735 if (!RVALUE_MARKED(objspace, (VALUE)slot)) continue;
3736 if (data->each_obj_callback &&
3737 (*data->each_obj_callback)((void *)slot, (void *)(slot + heap->slot_size),
3738 heap->slot_size, data->data)) {
3739 stop = true;
3740 break;
3741 }
3742 }
3743 if (stop) break;
3744 }
3745 else {
3746 if (data->each_obj_callback &&
3747 (*data->each_obj_callback)((void *)pstart, (void *)pend, heap->slot_size, data->data)) {
3748 break;
3749 }
3750 if (data->each_page_callback &&
3751 (*data->each_page_callback)(page, data->data)) {
3752 break;
3753 }
3754 }
3755
3756 page = ccan_list_next(&heap->pages, page, page_node);
3757 }
3758 }
3759
3760 return Qnil;
3761}
3762
3763static void
3764objspace_each_exec(bool protected, struct each_obj_data *each_obj_data)
3765{
3766 /* Disable incremental GC */
3768 bool reenable_incremental = FALSE;
3769 if (protected) {
3770 reenable_incremental = !objspace->flags.dont_incremental;
3771
3772 gc_rest(objspace);
3773 objspace->flags.dont_incremental = TRUE;
3774 }
3775
3776 each_obj_data->reenable_incremental = reenable_incremental;
3777 memset(&each_obj_data->pages, 0, sizeof(each_obj_data->pages));
3778 memset(&each_obj_data->pages_counts, 0, sizeof(each_obj_data->pages_counts));
3779 rb_ensure(objspace_each_objects_try, (VALUE)each_obj_data,
3780 objspace_each_objects_ensure, (VALUE)each_obj_data);
3781}
3782
3783static void
3784objspace_each_objects(rb_objspace_t *objspace, each_obj_callback *callback, void *data, bool protected)
3785{
3786 struct each_obj_data each_obj_data = {
3787 .objspace = objspace,
3788 .each_obj_callback = callback,
3789 .each_page_callback = NULL,
3790 .data = data,
3791 };
3792 objspace_each_exec(protected, &each_obj_data);
3793}
3794
3795void
3796rb_gc_impl_each_objects(void *objspace_ptr, each_obj_callback *callback, void *data)
3797{
3798 objspace_each_objects(objspace_ptr, callback, data, TRUE);
3799}
3800
3801/* Like rb_gc_impl_each_objects but visiting only pages that hold shareable objects, to
3802 * reach a foreign Ractor's shareable objects without walking the rest of its heap. */
3803void
3804rb_gc_impl_each_objects_shareable(void *objspace_ptr, each_obj_callback *callback, void *data)
3805{
3806 struct each_obj_data each_obj_data = {
3807 .objspace = objspace_ptr,
3808 .shareable_only = true,
3809 .each_obj_callback = callback,
3810 .each_page_callback = NULL,
3811 .data = data,
3812 };
3813 /* Not the protected variant: this objspace belongs to another Ractor (the caller
3814 * holds the barrier). The protected path calls gc_rest, which would run the owner's
3815 * stopped lazy sweep (its obj_free and dfree) on the walking thread with the
3816 * walker's Ractor identity (wrong per-Ractor tables, a foreign T_DATA dfree). The
3817 * owner is stopped and its page list is stable, and the walk itself skips dead,
3818 * unswept objects (the shareable_only branch of objspace_each_objects_try). The
3819 * walker's own incremental GC state is untouched, since this is not its objspace. */
3820 objspace_each_exec(FALSE, &each_obj_data);
3821}
3822
3823/* Walk every object of a foreign Ractor's objspace, unshareable ones included. Only for
3824 * callers that hold the barrier and whose callback is pure C (a heap dump, memory
3825 * accounting). As in the shareable walk above, the owner's stopped lazy sweep is not
3826 * settled and dead, unswept objects are skipped by the walk (skip_unswept_dead). */
3827void
3828rb_gc_impl_each_objects_foreign(void *objspace_ptr, each_obj_callback *callback, void *data)
3829{
3830 struct each_obj_data each_obj_data = {
3831 .objspace = objspace_ptr,
3832 .skip_unswept_dead = true,
3833 .each_obj_callback = callback,
3834 .each_page_callback = NULL,
3835 .data = data,
3836 };
3837 objspace_each_exec(FALSE, &each_obj_data);
3838}
3839
3840#if GC_CAN_COMPILE_COMPACTION
3841static void
3842objspace_each_pages(rb_objspace_t *objspace, each_page_callback *callback, void *data, bool protected)
3843{
3844 struct each_obj_data each_obj_data = {
3845 .objspace = objspace,
3846 .each_obj_callback = NULL,
3847 .each_page_callback = callback,
3848 .data = data,
3849 };
3850 objspace_each_exec(protected, &each_obj_data);
3851}
3852#endif
3853
3854VALUE
3855rb_gc_impl_define_finalizer(void *objspace_ptr, VALUE obj, VALUE block)
3856{
3857 rb_objspace_t *objspace = objspace_ptr;
3858 VALUE table;
3859 st_data_t data;
3860
3861 GC_ASSERT(!OBJ_FROZEN(obj));
3862
3863 /* Registering, storing and running finalizers all belong to the object's own
3864 * objspace, so refuse to define one on another Ractor's object (even a shareable
3865 * one): it would land in a table the owner's sweep never consults. */
3866 if (GET_HEAP_OBJSPACE(obj) != objspace) {
3867 rb_raise(rb_eRactorIsolationError,
3868 "can not define a finalizer for an object of another Ractor");
3869 }
3870
3871 RBASIC(obj)->flags |= FL_FINALIZE;
3872
3873 unsigned int lev = RB_GC_VM_LOCK();
3874
3875 if (st_lookup(finalizer_table, obj, &data)) {
3876 table = (VALUE)data;
3877 VALUE dup_table = rb_ary_dup(table);
3878
3879 RB_GC_VM_UNLOCK(lev);
3880 /* avoid duplicate block, table is usually small */
3881 {
3882 long len = RARRAY_LEN(table);
3883 long i;
3884
3885 for (i = 0; i < len; i++) {
3886 VALUE recv = RARRAY_AREF(dup_table, i);
3887 if (rb_equal(recv, block)) { // can't be called with VM lock held
3888 return recv;
3889 }
3890 }
3891 }
3892 lev = RB_GC_VM_LOCK();
3893 RB_GC_GUARD(dup_table);
3894
3895 rb_ary_push(table, block);
3896 }
3897 else {
3898 table = rb_ary_new3(2, rb_obj_id(obj), block);
3899 rb_obj_hide(table);
3900 st_add_direct(finalizer_table, obj, table);
3901 }
3902
3903 RB_GC_VM_UNLOCK(lev);
3904
3905 return block;
3906}
3907
3908void
3909rb_gc_impl_undefine_finalizer(void *objspace_ptr, VALUE obj)
3910{
3911 rb_objspace_t *objspace = objspace_ptr;
3912
3913 GC_ASSERT(!OBJ_FROZEN(obj));
3914
3915 /* Symmetric with define. */
3916 if (GET_HEAP_OBJSPACE(obj) != objspace) {
3917 rb_raise(rb_eRactorIsolationError,
3918 "can not undefine a finalizer of an object of another Ractor");
3919 }
3920
3921 st_data_t data = obj;
3922
3923 int lev = RB_GC_VM_LOCK();
3924 st_delete(finalizer_table, &data, 0);
3925 RB_GC_VM_UNLOCK(lev);
3926
3927 FL_UNSET(obj, FL_FINALIZE);
3928}
3929
3930void
3931rb_gc_impl_copy_finalizer(void *objspace_ptr, VALUE dest, VALUE obj)
3932{
3933 /* Finalizers do not cross objspaces: a copy of another Ractor's object starts with
3934 * none (guards the public rb_gc_copy_finalizer C API; no in-tree caller crosses).
3935 * A same-objspace copy behaves as before. Table accessed under the VM lock. */
3936 rb_objspace_t *objspace = objspace_ptr;
3937 VALUE table;
3938 st_data_t data;
3939
3940 if (!FL_TEST(obj, FL_FINALIZE)) return;
3941 if (GET_HEAP_OBJSPACE(obj) != objspace) return;
3942
3943 int lev = RB_GC_VM_LOCK();
3944 if (RB_LIKELY(st_lookup(finalizer_table, obj, &data))) {
3945 table = rb_ary_dup((VALUE)data);
3946 RARRAY_ASET(table, 0, rb_obj_id(dest));
3947 st_insert(finalizer_table, dest, table);
3948 FL_SET(dest, FL_FINALIZE);
3949 }
3950 else {
3951 rb_bug("rb_gc_copy_finalizer: FL_FINALIZE set but not found in finalizer_table: %s", rb_obj_info(obj));
3952 }
3953 RB_GC_VM_UNLOCK(lev);
3954}
3955
3956static VALUE
3957get_final(long i, void *data)
3958{
3959 VALUE table = (VALUE)data;
3960
3961 return RARRAY_AREF(table, i + 1);
3962}
3963
3964static unsigned int
3965run_final(rb_objspace_t *objspace, VALUE zombie, unsigned int lev)
3966{
3967 if (RZOMBIE(zombie)->dfree) {
3968 RZOMBIE(zombie)->dfree(RZOMBIE(zombie)->data);
3969 }
3970
3971 st_data_t key = (st_data_t)zombie;
3972 if (FL_TEST_RAW(zombie, FL_FINALIZE)) {
3973 FL_UNSET(zombie, FL_FINALIZE);
3974 st_data_t table;
3975 if (st_delete(finalizer_table, &key, &table)) {
3976 RB_GC_VM_UNLOCK(lev);
3977 rb_gc_run_obj_finalizer(RARRAY_AREF(table, 0), RARRAY_LEN(table) - 1, get_final, (void *)table);
3978 lev = RB_GC_VM_LOCK();
3979 }
3980 else {
3981 rb_bug("FL_FINALIZE flag is set, but finalizers are not found");
3982 }
3983 }
3984 else {
3985 GC_ASSERT(!st_lookup(finalizer_table, key, NULL));
3986 }
3987 return lev;
3988}
3989
3990static void
3991finalize_list(rb_objspace_t *objspace, VALUE zombie)
3992{
3993 while (zombie) {
3994 VALUE next_zombie;
3995 struct heap_page *page;
3996 rb_asan_unpoison_object(zombie, false);
3997 next_zombie = RZOMBIE(zombie)->next;
3998 page = GET_HEAP_PAGE(zombie);
3999
4000 unsigned int lev = RB_GC_VM_LOCK();
4001
4002 lev = run_final(objspace, zombie, lev);
4003 {
4004 GC_ASSERT(BUILTIN_TYPE(zombie) == T_ZOMBIE);
4005 GC_ASSERT(page->heap->final_slots_count > 0);
4006 GC_ASSERT(page->final_slots > 0);
4007
4008 page->heap->final_slots_count--;
4009 page->final_slots--;
4010 page->free_slots++;
4011 RVALUE_AGE_SET_BITMAP(zombie, 0);
4012 heap_page_add_free_region(objspace, page, zombie);
4013 page->heap->total_freed_objects++;
4014 }
4015 RB_GC_VM_UNLOCK(lev);
4016
4017 zombie = next_zombie;
4018 }
4019}
4020
4021static void
4022finalize_deferred_heap_pages(rb_objspace_t *objspace)
4023{
4024 VALUE zombie;
4025 while ((zombie = RUBY_ATOMIC_VALUE_EXCHANGE(heap_pages_deferred_final, 0)) != 0) {
4026 finalize_list(objspace, zombie);
4027 }
4028}
4029
4030static void
4031finalize_deferred(rb_objspace_t *objspace)
4032{
4033 rb_gc_set_pending_interrupt();
4034 finalize_deferred_heap_pages(objspace);
4035 rb_gc_unset_pending_interrupt();
4036}
4037
4038static void
4039gc_finalize_deferred(void *dmy)
4040{
4041 /* One postponed job is shared by every objspace: the preregistration table only
4042 * holds about 32 entries and Ractors are created continuously. A deferred finalizer
4043 * belongs to the objspace of the thread that ran the job, i.e. the current one. */
4044 rb_objspace_t *objspace = rb_gc_get_objspace();
4045 if (RUBY_ATOMIC_EXCHANGE(finalizing, 1)) return;
4046
4047 finalize_deferred(objspace);
4048 RUBY_ATOMIC_SET(finalizing, 0);
4049}
4050
4051static void
4052gc_finalize_deferred_register(rb_objspace_t *objspace)
4053{
4054 /* Enqueue gc_finalize_deferred on this objspace's owning Ractor. A global GC can
4055 * defer a foreign objspace's finalizers, and those must run on their owner rather
4056 * than on the driver. */
4057 rb_gc_trigger_finalize_deferred(objspace, objspace->finalize_deferred_pjob);
4058}
4059
4060static int pop_mark_stack(mark_stack_t *stack, VALUE *data);
4061
4062static void
4063gc_abort(void *objspace_ptr)
4064{
4065 rb_objspace_t *objspace = objspace_ptr;
4066
4067 if (is_incremental_marking(objspace)) {
4068 /* Remove all objects from the mark stack. */
4069 VALUE obj;
4070 while (pop_mark_stack(&objspace->mark_stack, &obj));
4071
4072 objspace->flags.during_incremental_marking = FALSE;
4073 }
4074
4075 if (is_lazy_sweeping(objspace)) {
4076 objspace->sweeping_heap_count = 0;
4077 for (int i = 0; i < HEAP_COUNT; i++) {
4078 rb_heap_t *heap = &heaps[i];
4079
4080 heap->sweeping_page = NULL;
4081 struct heap_page *page = NULL;
4082
4083 ccan_list_for_each(&heap->pages, page, page_node) {
4084 page->flags.before_sweep = false;
4085 }
4086 }
4087 }
4088
4089 for (int i = 0; i < HEAP_COUNT; i++) {
4090 rb_heap_t *heap = &heaps[i];
4091 gc_bitmaps_clear(objspace, heap, false);
4092 }
4093
4094 gc_mode_set(objspace, gc_mode_none);
4095}
4096
4097void
4098rb_gc_impl_shutdown_free_objects(void *objspace_ptr)
4099{
4100 rb_objspace_t *objspace = objspace_ptr;
4101
4102 for (size_t i = 0; i < rb_darray_size(objspace->heap_pages.sorted); i++) {
4103 struct heap_page *page = rb_darray_get(objspace->heap_pages.sorted, i);
4104 short stride = page->slot_size;
4105
4106 uintptr_t p = (uintptr_t)page->start;
4107 uintptr_t pend = p + page->total_slots * stride;
4108 for (; p < pend; p += stride) {
4109 VALUE vp = (VALUE)p;
4110 asan_unpoisoning_object(vp) {
4111 if (RB_BUILTIN_TYPE(vp) != T_NONE) {
4112 rb_gc_obj_free_vm_weak_references(vp);
4113 if (rb_gc_obj_free(objspace, vp)) {
4114 RBASIC(vp)->flags = 0;
4115 }
4116 }
4117 }
4118 }
4119 }
4120}
4121
4122static int
4123rb_gc_impl_shutdown_call_finalizer_i(st_data_t key, st_data_t val, st_data_t _data)
4124{
4125 VALUE obj = (VALUE)key;
4126 VALUE table = (VALUE)val;
4127
4128 GC_ASSERT(RB_FL_TEST(obj, FL_FINALIZE));
4129 GC_ASSERT(RB_BUILTIN_TYPE(val) == T_ARRAY);
4130
4131 rb_gc_run_obj_finalizer(RARRAY_AREF(table, 0), RARRAY_LEN(table) - 1, get_final, (void *)table);
4132
4133 FL_UNSET(obj, FL_FINALIZE);
4134
4135 return ST_DELETE;
4136}
4137
4138void
4139rb_gc_impl_shutdown_call_finalizer(void *objspace_ptr)
4140{
4141 rb_objspace_t *objspace = objspace_ptr;
4142
4143#if RGENGC_CHECK_MODE >= 2
4144 gc_verify_internal_consistency(objspace);
4145#endif
4146
4147 /* prohibit incremental GC */
4148 objspace->flags.dont_incremental = 1;
4149
4150 if (RUBY_ATOMIC_EXCHANGE(finalizing, 1)) {
4151 /* Abort incremental marking and lazy sweeping to speed up shutdown. */
4152 gc_abort(objspace);
4153 dont_gc_on();
4154 return;
4155 }
4156
4157 while (finalizer_table->num_entries) {
4158 st_foreach(finalizer_table, rb_gc_impl_shutdown_call_finalizer_i, 0);
4159 }
4160
4161 /* run finalizers */
4162 finalize_deferred(objspace);
4163 GC_ASSERT(heap_pages_deferred_final == 0);
4164
4165 /* Abort incremental marking and lazy sweeping to speed up shutdown. */
4166 gc_abort(objspace);
4167
4168 /* prohibit GC because force T_DATA finalizers can break an object graph consistency */
4169 dont_gc_on();
4170
4171 /* running data/file finalizers are part of garbage collection */
4172 unsigned int lock_lev;
4173 gc_enter(objspace, gc_enter_event_finalizer, &lock_lev);
4174
4175 /* run data/file object's finalizers */
4176 for (size_t i = 0; i < rb_darray_size(objspace->heap_pages.sorted); i++) {
4177 struct heap_page *page = rb_darray_get(objspace->heap_pages.sorted, i);
4178 short stride = page->slot_size;
4179
4180 uintptr_t p = (uintptr_t)page->start;
4181 uintptr_t pend = p + page->total_slots * stride;
4182 for (; p < pend; p += stride) {
4183 VALUE vp = (VALUE)p;
4184 asan_unpoisoning_object(vp) {
4185 if (rb_gc_shutdown_call_finalizer_p(vp)) {
4186 rb_gc_obj_free_vm_weak_references(vp);
4187 if (rb_gc_obj_free(objspace, vp)) {
4188 RBASIC(vp)->flags = 0;
4189 }
4190 }
4191 }
4192 }
4193 }
4194
4195 gc_exit(objspace, gc_enter_event_finalizer, &lock_lev);
4196
4197 finalize_deferred_heap_pages(objspace);
4198
4199 st_free_table(finalizer_table);
4200 finalizer_table = 0;
4201 RUBY_ATOMIC_SET(finalizing, 0);
4202}
4203
4204void
4205rb_gc_impl_each_object(void *objspace_ptr, void (*func)(VALUE obj, void *data), void *data)
4206{
4207 rb_objspace_t *objspace = objspace_ptr;
4208
4209 for (size_t i = 0; i < rb_darray_size(objspace->heap_pages.sorted); i++) {
4210 struct heap_page *page = rb_darray_get(objspace->heap_pages.sorted, i);
4211 short stride = page->slot_size;
4212
4213 uintptr_t p = (uintptr_t)page->start;
4214 uintptr_t pend = p + page->total_slots * stride;
4215 for (; p < pend; p += stride) {
4216 VALUE obj = (VALUE)p;
4217
4218 asan_unpoisoning_object(obj) {
4219 func(obj, data);
4220 }
4221 }
4222 }
4223}
4224
4225/*
4226 ------------------------ Garbage Collection ------------------------
4227*/
4228
4229/* Sweeping */
4230
4231static size_t
4232objspace_available_slots(rb_objspace_t *objspace)
4233{
4234 size_t total_slots = 0;
4235 for (int i = 0; i < HEAP_COUNT; i++) {
4236 rb_heap_t *heap = &heaps[i];
4237 total_slots += heap->total_slots;
4238 }
4239 return total_slots;
4240}
4241
4242static size_t
4243objspace_live_slots(rb_objspace_t *objspace)
4244{
4245 return total_allocated_objects(objspace) - total_freed_objects(objspace) - total_final_slots_count(objspace);
4246}
4247
4248static size_t
4249objspace_free_slots(rb_objspace_t *objspace)
4250{
4251 return objspace_available_slots(objspace) - objspace_live_slots(objspace) - total_final_slots_count(objspace);
4252}
4253
4254static void
4255gc_setup_mark_bits(struct heap_page *page)
4256{
4257 /* copy oldgen bitmap to mark bitmap */
4258 memcpy(&page->mark_bits[0], &page->uncollectible_bits[0], HEAP_PAGE_BITMAP_SIZE);
4259}
4260
4261static int gc_is_moveable_obj(rb_objspace_t *objspace, VALUE obj);
4262static VALUE gc_move(rb_objspace_t *objspace, VALUE scan, VALUE free, struct heap_page *src_page, struct heap_page *dest_page);
4263
4264#if defined(_WIN32)
4265enum {HEAP_PAGE_LOCK = PAGE_NOACCESS, HEAP_PAGE_UNLOCK = PAGE_READWRITE};
4266
4267static BOOL
4268protect_page_body(struct heap_page_body *body, DWORD protect)
4269{
4270 DWORD old_protect;
4271 return VirtualProtect(body, HEAP_PAGE_SIZE, protect, &old_protect) != 0;
4272}
4273#elif defined(__wasi__)
4274// wasi-libc's mprotect emulation does not support PROT_NONE
4275enum {HEAP_PAGE_LOCK, HEAP_PAGE_UNLOCK};
4276#define protect_page_body(body, protect) 1
4277#else
4278enum {HEAP_PAGE_LOCK = PROT_NONE, HEAP_PAGE_UNLOCK = PROT_READ | PROT_WRITE};
4279#define protect_page_body(body, protect) !mprotect((body), HEAP_PAGE_SIZE, (protect))
4280#endif
4281
4282static void
4283lock_page_body(rb_objspace_t *objspace, struct heap_page_body *body)
4284{
4285 if (!protect_page_body(body, HEAP_PAGE_LOCK)) {
4286 rb_bug("Couldn't protect page %p, errno: %s", (void *)body, strerror(errno));
4287 }
4288 else {
4289 gc_report(5, objspace, "Protecting page in move %p\n", (void *)body);
4290 }
4291}
4292
4293static void
4294unlock_page_body(rb_objspace_t *objspace, struct heap_page_body *body)
4295{
4296 if (!protect_page_body(body, HEAP_PAGE_UNLOCK)) {
4297 rb_bug("Couldn't unprotect page %p, errno: %s", (void *)body, strerror(errno));
4298 }
4299 else {
4300 gc_report(5, objspace, "Unprotecting page in move %p\n", (void *)body);
4301 }
4302}
4303
4304static uintptr_t
4305heap_page_alloc_slot_from_region(struct heap_page *free_page)
4306{
4307 asan_unlock_freelist(free_page);
4308 struct free_region *region = free_page->free_region;
4309 asan_lock_freelist(free_page);
4310
4311 if (region == NULL) {
4312 return 0;
4313 }
4314
4315 rb_asan_unpoison_object((VALUE)region, false);
4316 GC_ASSERT(RB_TYPE_P((VALUE)region, T_NONE));
4317 uintptr_t dest = (uintptr_t)region;
4318 uintptr_t region_end = region->end;
4319 struct free_region *next = region->next;
4320
4321 uintptr_t new_start = dest + free_page->slot_size;
4322
4323 asan_unlock_freelist(free_page);
4324 if (new_start < region_end) {
4325 VALUE next_start = (VALUE)new_start;
4326 rb_asan_unpoison_object(next_start, false);
4327 struct free_region *new_region = (struct free_region *)new_start;
4328 new_region->flags = 0;
4329 new_region->end = region_end;
4330 new_region->next = next;
4331 rb_asan_poison_object(next_start);
4332 free_page->free_region = new_region;
4333 }
4334 else {
4335 free_page->free_region = next;
4336 }
4337 asan_lock_freelist(free_page);
4338
4339 return dest;
4340}
4341
4342static bool
4343try_move(rb_objspace_t *objspace, rb_heap_t *heap, struct heap_page *free_page, VALUE src)
4344{
4345 GC_ASSERT(gc_is_moveable_obj(objspace, src));
4346
4347 struct heap_page *src_page = GET_HEAP_PAGE(src);
4348 if (!free_page) {
4349 return false;
4350 }
4351
4352 /* We should return true if either src is successfully moved, or src is
4353 * unmoveable. A false return will cause the sweeping cursor to be
4354 * incremented to the next page, and src will attempt to move again */
4355 GC_ASSERT(RVALUE_MARKED(objspace, src));
4356
4357 uintptr_t dest_slot = heap_page_alloc_slot_from_region(free_page);
4358 if (dest_slot == 0) {
4359 return false;
4360 }
4361 VALUE dest = (VALUE)dest_slot;
4362
4363 GC_ASSERT(RB_BUILTIN_TYPE(dest) == T_NONE);
4364
4365 if (src_page->slot_size > free_page->slot_size) {
4366 objspace->rcompactor.moved_down_count_table[BUILTIN_TYPE(src)]++;
4367 }
4368 else if (free_page->slot_size > src_page->slot_size) {
4369 objspace->rcompactor.moved_up_count_table[BUILTIN_TYPE(src)]++;
4370 }
4371 objspace->rcompactor.moved_count_table[BUILTIN_TYPE(src)]++;
4372 objspace->rcompactor.total_moved++;
4373
4374 gc_move(objspace, src, dest, src_page, free_page);
4375 gc_pin(objspace, src);
4376 free_page->free_slots--;
4377
4378 return true;
4379}
4380
4381static void
4382gc_unprotect_pages(rb_objspace_t *objspace, rb_heap_t *heap)
4383{
4384 struct heap_page *cursor = heap->compact_cursor;
4385
4386 while (cursor) {
4387 unlock_page_body(objspace, cursor->body);
4388 cursor = ccan_list_next(&heap->pages, cursor, page_node);
4389 }
4390}
4391
4392static void gc_update_references(rb_objspace_t *objspace);
4393static void gc_update_references_heap(rb_objspace_t *objspace);
4394static void gc_update_references_global(rb_objspace_t *objspace);
4395#if GC_CAN_COMPILE_COMPACTION
4396static void invalidate_moved_page(rb_objspace_t *objspace, struct heap_page *page);
4397#endif
4398
4399#if defined(__MINGW32__) || defined(_WIN32)
4400# define GC_COMPACTION_SUPPORTED 1
4401#else
4402/* If not MinGW, Windows, or does not have mmap, we cannot use mprotect for
4403 * the read barrier, so we must disable compaction. */
4404# define GC_COMPACTION_SUPPORTED (GC_CAN_COMPILE_COMPACTION && HEAP_PAGE_ALLOC_USE_MMAP)
4405#endif
4406
4407#if GC_CAN_COMPILE_COMPACTION
4408static void
4409read_barrier_handler(uintptr_t address)
4410{
4411 rb_objspace_t *objspace = (rb_objspace_t *)rb_gc_get_objspace();
4412
4413 struct heap_page_body *page_body = GET_PAGE_BODY(address);
4414
4415 /* If the page_body is NULL, then mprotect cannot handle it and will crash
4416 * with "Cannot allocate memory". */
4417 if (page_body == NULL) {
4418 rb_bug("read_barrier_handler: segmentation fault at %p", (void *)address);
4419 }
4420
4421 int lev = RB_GC_VM_LOCK();
4422 {
4423 unlock_page_body(objspace, page_body);
4424
4425 objspace->profile.read_barrier_faults++;
4426
4427 invalidate_moved_page(objspace, GET_HEAP_PAGE(address));
4428 }
4429 RB_GC_VM_UNLOCK(lev);
4430}
4431#endif
4432
4433#if !GC_CAN_COMPILE_COMPACTION
4434static void
4435uninstall_handlers(void)
4436{
4437 /* no-op */
4438}
4439
4440static void
4441install_handlers(void)
4442{
4443 /* no-op */
4444}
4445#elif defined(_WIN32)
4446static LPTOP_LEVEL_EXCEPTION_FILTER old_handler;
4447typedef void (*signal_handler)(int);
4448static signal_handler old_sigsegv_handler;
4449
4450static LONG WINAPI
4451read_barrier_signal(EXCEPTION_POINTERS *info)
4452{
4453 /* EXCEPTION_ACCESS_VIOLATION is what's raised by access to protected pages */
4454 if (info->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) {
4455 /* > The second array element specifies the virtual address of the inaccessible data.
4456 * https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-exception_record
4457 *
4458 * Use this address to invalidate the page */
4459 read_barrier_handler((uintptr_t)info->ExceptionRecord->ExceptionInformation[1]);
4460 return EXCEPTION_CONTINUE_EXECUTION;
4461 }
4462 else {
4463 return EXCEPTION_CONTINUE_SEARCH;
4464 }
4465}
4466
4467static void
4468uninstall_handlers(void)
4469{
4470 signal(SIGSEGV, old_sigsegv_handler);
4471 SetUnhandledExceptionFilter(old_handler);
4472}
4473
4474static void
4475install_handlers(void)
4476{
4477 /* Remove SEGV handler so that the Unhandled Exception Filter handles it */
4478 old_sigsegv_handler = signal(SIGSEGV, NULL);
4479 /* Unhandled Exception Filter has access to the violation address similar
4480 * to si_addr from sigaction */
4481 old_handler = SetUnhandledExceptionFilter(read_barrier_signal);
4482}
4483#else
4484static struct sigaction old_sigbus_handler;
4485static struct sigaction old_sigsegv_handler;
4486
4487#ifdef HAVE_MACH_TASK_EXCEPTION_PORTS
4488static exception_mask_t old_exception_masks[32];
4489static mach_port_t old_exception_ports[32];
4490static exception_behavior_t old_exception_behaviors[32];
4491static thread_state_flavor_t old_exception_flavors[32];
4492static mach_msg_type_number_t old_exception_count;
4493
4494static void
4495disable_mach_bad_access_exc(void)
4496{
4497 old_exception_count = sizeof(old_exception_masks) / sizeof(old_exception_masks[0]);
4498 task_swap_exception_ports(
4499 mach_task_self(), EXC_MASK_BAD_ACCESS,
4500 MACH_PORT_NULL, EXCEPTION_DEFAULT, 0,
4501 old_exception_masks, &old_exception_count,
4502 old_exception_ports, old_exception_behaviors, old_exception_flavors
4503 );
4504}
4505
4506static void
4507restore_mach_bad_access_exc(void)
4508{
4509 for (mach_msg_type_number_t i = 0; i < old_exception_count; i++) {
4510 task_set_exception_ports(
4511 mach_task_self(),
4512 old_exception_masks[i], old_exception_ports[i],
4513 old_exception_behaviors[i], old_exception_flavors[i]
4514 );
4515 }
4516}
4517#endif
4518
4519static void
4520read_barrier_signal(int sig, siginfo_t *info, void *data)
4521{
4522 // setup SEGV/BUS handlers for errors
4523 struct sigaction prev_sigbus, prev_sigsegv;
4524 sigaction(SIGBUS, &old_sigbus_handler, &prev_sigbus);
4525 sigaction(SIGSEGV, &old_sigsegv_handler, &prev_sigsegv);
4526
4527 // enable SIGBUS/SEGV
4528 sigset_t set, prev_set;
4529 sigemptyset(&set);
4530 sigaddset(&set, SIGBUS);
4531 sigaddset(&set, SIGSEGV);
4532 sigprocmask(SIG_UNBLOCK, &set, &prev_set);
4533#ifdef HAVE_MACH_TASK_EXCEPTION_PORTS
4534 disable_mach_bad_access_exc();
4535#endif
4536 // run handler
4537 read_barrier_handler((uintptr_t)info->si_addr);
4538
4539 // reset SEGV/BUS handlers
4540#ifdef HAVE_MACH_TASK_EXCEPTION_PORTS
4541 restore_mach_bad_access_exc();
4542#endif
4543 sigaction(SIGBUS, &prev_sigbus, NULL);
4544 sigaction(SIGSEGV, &prev_sigsegv, NULL);
4545 sigprocmask(SIG_SETMASK, &prev_set, NULL);
4546}
4547
4548static void
4549uninstall_handlers(void)
4550{
4551#ifdef HAVE_MACH_TASK_EXCEPTION_PORTS
4552 restore_mach_bad_access_exc();
4553#endif
4554 sigaction(SIGBUS, &old_sigbus_handler, NULL);
4555 sigaction(SIGSEGV, &old_sigsegv_handler, NULL);
4556}
4557
4558static void
4559install_handlers(void)
4560{
4561 struct sigaction action;
4562 memset(&action, 0, sizeof(struct sigaction));
4563 sigemptyset(&action.sa_mask);
4564 action.sa_sigaction = read_barrier_signal;
4565 action.sa_flags = SA_SIGINFO | SA_ONSTACK;
4566
4567 sigaction(SIGBUS, &action, &old_sigbus_handler);
4568 sigaction(SIGSEGV, &action, &old_sigsegv_handler);
4569#ifdef HAVE_MACH_TASK_EXCEPTION_PORTS
4570 disable_mach_bad_access_exc();
4571#endif
4572}
4573#endif
4574
4575static void
4576gc_compact_finish(rb_objspace_t *objspace)
4577{
4578 for (int i = 0; i < HEAP_COUNT; i++) {
4579 rb_heap_t *heap = &heaps[i];
4580 gc_unprotect_pages(objspace, heap);
4581 }
4582
4583 if (!global_objspace->global_gc.compacting) uninstall_handlers();
4584
4585 if (global_objspace->global_gc.compacting) {
4586 /* In a compacting global GC this updates only this objspace's heap references;
4587 * gc_start_global sets during_reference_updating on every objspace (the
4588 * move-or-mark decision reads it via rb_gc_get_objspace()) and runs the
4589 * non-idempotent VM-global side (gc_update_references_global) once at the end. */
4590 gc_update_references_heap(objspace);
4591 }
4592 else {
4593 gc_update_references(objspace);
4594 }
4595 objspace->profile.compact_count++;
4596
4597 for (int i = 0; i < HEAP_COUNT; i++) {
4598 rb_heap_t *heap = &heaps[i];
4599 heap->compact_cursor = NULL;
4600 heap->free_pages = NULL;
4601 heap->compact_cursor_index = 0;
4602 }
4603
4604 if (gc_prof_enabled(objspace)) {
4605 gc_profile_record *record = gc_prof_record(objspace);
4606 record->moved_objects = objspace->rcompactor.total_moved - record->moved_objects;
4607 }
4608 if (!global_objspace->global_gc.compacting) objspace->flags.during_compacting = FALSE;
4609}
4610
4612 struct heap_page *page;
4613 int final_slots;
4614 int freed_slots;
4615 int empty_slots;
4616 /* Hoisted out of the per-slot pinned-free assert: too expensive for the sweep loop
4617 * as an external call. */
4618 unsigned char check_pinned_free;
4619
4620 struct free_region *free_region;
4621};
4622
4623static inline void
4624gc_sweep_register_free_slot(rb_objspace_t *objspace, struct heap_page *page, struct gc_sweep_context *ctx, uintptr_t p, short slot_size)
4625{
4626 rb_asan_unpoison_object(p, false);
4627 ((struct RBasic *)p)->flags = 0;
4628
4629 /* Keep a freed slot from carrying its old shareable and shref bits into the next
4630 * object born there; the actual clear happens per bitmap word at the end of
4631 * gc_sweep_page rather than per slot. */
4632
4633 struct free_region *existing_region = ctx->free_region;
4634 if (existing_region) rb_asan_unpoison_object((VALUE)existing_region, false);
4635
4636 if (RB_LIKELY(existing_region && p == existing_region->end)) {
4637 existing_region->end = p + slot_size;
4638 }
4639 else {
4640 struct free_region *free_region = (struct free_region *)p;
4641 free_region->end = p + slot_size;
4642 free_region->next = existing_region;
4643
4644 ctx->free_region = free_region;
4645 }
4646
4647 if (existing_region) rb_asan_poison_object((VALUE)existing_region);
4648 rb_asan_poison_object(p);
4649}
4650
4651static inline void
4652gc_sweep_plane(rb_objspace_t *objspace, rb_heap_t *heap, uintptr_t p, bits_t bitset, struct gc_sweep_context *ctx)
4653{
4654 struct heap_page *sweep_page = ctx->page;
4655 short slot_size = sweep_page->slot_size;
4656
4657 do {
4658 VALUE vp = (VALUE)p;
4659 GC_ASSERT(vp % sizeof(VALUE) == 0);
4660
4661 rb_asan_unpoison_object(vp, false);
4662 if (bitset & 1) {
4663 switch (BUILTIN_TYPE(vp)) {
4664 case T_MOVED:
4665 if (objspace->flags.during_compacting) {
4666 /* The sweep cursor shouldn't have made it to any
4667 * T_MOVED slots while the compact flag is enabled.
4668 * The sweep cursor and compact cursor move in
4669 * opposite directions, and when they meet references will
4670 * get updated and "during_compacting" should get disabled */
4671 rb_bug("T_MOVED shouldn't be seen until compaction is finished");
4672 }
4673 gc_report(3, objspace, "page_sweep: %s is freed\n", rb_obj_info(vp));
4674 ctx->empty_slots++;
4675 gc_sweep_register_free_slot(objspace, sweep_page, ctx, p, slot_size);
4676 break;
4677 case T_ZOMBIE:
4678 /* already counted */
4679 break;
4680 case T_NONE:
4681 ctx->empty_slots++; /* already freed */
4682 gc_sweep_register_free_slot(objspace, sweep_page, ctx, p, slot_size);
4683 break;
4684
4685 default:
4686#if RGENGC_CHECK_MODE
4687 /* A local GC must never free a pinned slot; a global GC may (its exact
4688 * mark collects dead shareable objects). Reading the bits here is
4689 * CHECK-only and still valid: the bulk clear runs after the free loop. */
4690 if (ctx->check_pinned_free &&
4691 (MARKED_IN_BITMAP(GET_HEAP_SHAREABLE_BITS(vp), vp) ||
4692 MARKED_IN_BITMAP(GET_HEAP_SHREF_BITS(vp), vp))) {
4693 rb_bug("page_sweep: freeing pinned slot %s (shareable=%d shref=%d single_now=%d)",
4694 rb_obj_info(vp),
4695 (int)!!MARKED_IN_BITMAP(GET_HEAP_SHAREABLE_BITS(vp), vp),
4696 (int)!!MARKED_IN_BITMAP(GET_HEAP_SHREF_BITS(vp), vp),
4697 (int)rb_gc_single_objspace_p());
4698 }
4699#endif
4700#if RGENGC_CHECK_MODE
4701 if (!is_full_marking(objspace)) {
4702 if (RVALUE_OLD_P(objspace, vp)) rb_bug("page_sweep: %p - old while minor GC.", (void *)p);
4703 if (RVALUE_REMEMBERED(objspace, vp)) rb_bug("page_sweep: %p - remembered.", (void *)p);
4704 }
4705#endif
4706
4707#if RGENGC_CHECK_MODE
4708#define CHECK(x) if (x(objspace, vp) != FALSE) rb_bug("obj_free: " #x "(%s) != FALSE", rb_obj_info(vp))
4709 CHECK(RVALUE_WB_UNPROTECTED);
4710 CHECK(RVALUE_MARKED);
4711 CHECK(RVALUE_MARKING);
4712 CHECK(RVALUE_UNCOLLECTIBLE);
4713#undef CHECK
4714#endif
4715
4716 if (!rb_gc_obj_needs_cleanup_p(vp)) {
4717 (void)VALGRIND_MAKE_MEM_UNDEFINED((void*)p, slot_size);
4718 gc_sweep_register_free_slot(objspace, sweep_page, ctx, p, slot_size);
4719 gc_report(3, objspace, "page_sweep: %s (fast path) is freed\n", rb_obj_info(vp));
4720 ctx->freed_slots++;
4721 }
4722 else {
4723 gc_report(2, objspace, "page_sweep: free %p\n", (void *)p);
4724
4725 rb_gc_obj_free_vm_weak_references(vp);
4726 if (rb_gc_obj_free(objspace, vp)) {
4727 (void)VALGRIND_MAKE_MEM_UNDEFINED((void*)p, slot_size);
4728 gc_sweep_register_free_slot(objspace, sweep_page, ctx, p, slot_size);
4729 gc_report(3, objspace, "page_sweep: %s is freed\n", rb_obj_info(vp));
4730 ctx->freed_slots++;
4731 }
4732 else {
4733 ctx->final_slots++;
4734 }
4735 }
4736 break;
4737 }
4738 }
4739 p += slot_size;
4740 bitset >>= 1;
4741 } while (bitset);
4742}
4743
4744static inline void
4745gc_sweep_page(rb_objspace_t *objspace, rb_heap_t *heap, struct gc_sweep_context *ctx)
4746{
4747 struct heap_page *sweep_page = ctx->page;
4748 GC_ASSERT(sweep_page->heap == heap);
4749
4750 uintptr_t p;
4751 bits_t *bits, bitset;
4752
4753 gc_report(2, objspace, "page_sweep: start.\n");
4754
4755#if RGENGC_CHECK_MODE
4756 if (!objspace->flags.immediate_sweep) {
4757 GC_ASSERT(sweep_page->flags.before_sweep == TRUE);
4758 }
4759#endif
4760 sweep_page->flags.before_sweep = FALSE;
4761 sweep_page->free_slots = 0;
4762
4763 asan_unlock_freelist(sweep_page);
4764 sweep_page->free_region = NULL;
4765 asan_lock_freelist(sweep_page);
4766 ctx->free_region = NULL;
4767
4768 p = (uintptr_t)sweep_page->start;
4769 bits = sweep_page->mark_bits;
4770 short slot_size = sweep_page->slot_size;
4771 int total_slots = sweep_page->total_slots;
4772 int bitmap_plane_count = CEILDIV(total_slots, BITS_BITLENGTH);
4773
4774 int out_of_range_bits = total_slots % BITS_BITLENGTH;
4775 if (out_of_range_bits != 0) {
4776 bits[bitmap_plane_count - 1] |= ~(((bits_t)1 << out_of_range_bits) - 1);
4777 }
4778
4779 // Clear wb_unprotected and age bits for all unmarked slots
4780 {
4781 bits_t *wb_unprotected_bits = sweep_page->wb_unprotected_bits;
4782 bits_t *age_bits = sweep_page->age_bits;
4783 for (int i = 0; i < bitmap_plane_count; i++) {
4784 bits_t unmarked = ~bits[i];
4785 wb_unprotected_bits[i] &= ~unmarked;
4786 age_bits[i * 2] &= ~unmarked;
4787 age_bits[i * 2 + 1] &= ~unmarked;
4788 }
4789 }
4790
4791 /* main's local GC is lock-free, but freeing a dead object can mutate VM-global state
4792 * that other Ractors rewrite under the VM lock: weak tables (rb_gc_obj_free_vm_weak_
4793 * references: ci_table, fstring, symbol, cme). (JIT iseq frees are not reached here:
4794 * iseqs are born shareable and a local GC never frees shareable objects.) Wrap the
4795 * page's free loop in a no-barrier VM lock (FIXME). */
4796 const bool sweep_needs_vm_lock =
4797 objspace == global_objspace->main_objspace && rb_gc_multi_ractor_p() && !objspace->flags.during_global_gc;
4798 unsigned int sweep_lock_lev = 0;
4799 if (sweep_needs_vm_lock) sweep_lock_lev = RB_GC_VM_LOCK_NO_BARRIER();
4800
4801 for (int i = 0; i < bitmap_plane_count; i++) {
4802 bitset = ~bits[i];
4803 if (bitset) {
4804 gc_sweep_plane(objspace, heap, p, bitset, ctx);
4805 }
4806 p += BITS_BITLENGTH * slot_size;
4807 }
4808
4809 if (sweep_needs_vm_lock) RB_GC_VM_UNLOCK_NO_BARRIER(sweep_lock_lev);
4810
4811 /* Bulk-clear the freed slots' shareable and shref bits before the freelist is
4812 * published, so a reused slot is clean. Freed slots are exactly the unmarked ones,
4813 * so `bits &= mark_bits` keeps live shareable objects (which must stay pinned) and
4814 * drops the rest. Pages with neither bit are skipped. */
4815 if (sweep_page->flags.has_shareable_objects || sweep_page->flags.has_shref_objects) {
4816 bits_t *shareable_bits = sweep_page->shareable_bits;
4817 bits_t *shref_bits = sweep_page->shref_bits;
4818 bits_t sh = 0, sr = 0;
4819 for (int i = 0; i < bitmap_plane_count; i++) {
4820 shareable_bits[i] &= bits[i];
4821 shref_bits[i] &= bits[i];
4822 sh |= shareable_bits[i];
4823 sr |= shref_bits[i];
4824 }
4825 if (!sh) sweep_page->flags.has_shareable_objects = FALSE;
4826 if (!sr) sweep_page->flags.has_shref_objects = FALSE;
4827 }
4828
4829 asan_unlock_freelist(sweep_page);
4830 sweep_page->free_region = ctx->free_region;
4831 asan_lock_freelist(sweep_page);
4832
4833 if (!heap->compact_cursor) {
4834 gc_setup_mark_bits(sweep_page);
4835 }
4836
4837#if GC_PROFILE_MORE_DETAIL
4838 if (gc_prof_enabled(objspace)) {
4839 gc_profile_record *record = gc_prof_record(objspace);
4840 record->removing_objects += ctx->final_slots + ctx->freed_slots;
4841 record->empty_objects += ctx->empty_slots;
4842 }
4843#endif
4844 if (0) fprintf(stderr, "gc_sweep_page(%"PRIdSIZE"): total_slots: %d, freed_slots: %d, empty_slots: %d, final_slots: %d\n",
4845 rb_gc_count(),
4846 sweep_page->total_slots,
4847 ctx->freed_slots, ctx->empty_slots, ctx->final_slots);
4848
4849 sweep_page->free_slots += ctx->freed_slots + ctx->empty_slots;
4850 sweep_page->heap->total_freed_objects += ctx->freed_slots;
4851
4852 if (heap_pages_deferred_final && !finalizing) {
4853 gc_finalize_deferred_register(objspace);
4854 }
4855
4856#if RGENGC_CHECK_MODE
4857 int region_slots = 0;
4858 asan_unlock_freelist(sweep_page);
4859 struct free_region *region = sweep_page->free_region;
4860 while (region) {
4861 rb_asan_unpoison_object((VALUE)region, false);
4862 GC_ASSERT(RB_TYPE_P((VALUE)region, T_NONE));
4863 uintptr_t region_start = (uintptr_t)region;
4864 uintptr_t region_end = region->end;
4865 struct free_region *next = region->next;
4866 rb_asan_poison_object((VALUE)region);
4867
4868 GC_ASSERT(region_end > region_start);
4869 GC_ASSERT((region_end - region_start) % slot_size == 0);
4870 region_slots += (int)((region_end - region_start) / slot_size);
4871
4872 region = next;
4873 }
4874 asan_lock_freelist(sweep_page);
4875 if (region_slots != sweep_page->free_slots) {
4876 rb_bug("inconsistent free region slots: expected %d but was %d", sweep_page->free_slots, region_slots);
4877 }
4878#endif
4879
4880 gc_report(2, objspace, "page_sweep: end.\n");
4881}
4882
4883static const char *
4884gc_mode_name(enum gc_mode mode)
4885{
4886 switch (mode) {
4887 case gc_mode_none: return "none";
4888 case gc_mode_marking: return "marking";
4889 case gc_mode_sweeping: return "sweeping";
4890 case gc_mode_compacting: return "compacting";
4891 default: rb_bug("gc_mode_name: unknown mode: %d", (int)mode);
4892 }
4893}
4894
4895static void
4896gc_mode_transition(rb_objspace_t *objspace, enum gc_mode mode)
4897{
4898#if RGENGC_CHECK_MODE
4899 enum gc_mode prev_mode = gc_mode(objspace);
4900 switch (prev_mode) {
4901 case gc_mode_none:
4902 /* A global GC marks every objspace as one heap (mark_roots on the driver), so an
4903 * individual objspace's mode stays `none` during that mark; the sweep inside the
4904 * barrier then makes the legitimate none -> sweeping transition. */
4905 GC_ASSERT(mode == gc_mode_marking ||
4906 (objspace->flags.during_global_gc && mode == gc_mode_sweeping));
4907 break;
4908 case gc_mode_marking: GC_ASSERT(mode == gc_mode_sweeping); break;
4909 case gc_mode_sweeping: GC_ASSERT(mode == gc_mode_none || mode == gc_mode_compacting); break;
4910 case gc_mode_compacting: GC_ASSERT(mode == gc_mode_none); break;
4911 }
4912#endif
4913 if (0) fprintf(stderr, "gc_mode_transition: %s->%s\n", gc_mode_name(gc_mode(objspace)), gc_mode_name(mode));
4914 gc_mode_set(objspace, mode);
4915}
4916
4917static void
4918heap_page_flush_alloc_regions(struct heap_page *page, rb_heap_t *heap)
4919{
4920 struct free_region *chain = heap->newobj.alloc_next_region;
4921
4922 if (heap->newobj.alloc_cursor < heap->newobj.alloc_cursor_end) {
4923 VALUE start = (VALUE)heap->newobj.alloc_cursor;
4924 rb_asan_unpoison_object(start, false);
4925 struct free_region *remnant = (struct free_region *)start;
4926 remnant->flags = 0;
4927 remnant->end = heap->newobj.alloc_cursor_end;
4928 remnant->next = chain;
4929 rb_asan_poison_object(start);
4930 chain = remnant;
4931 }
4932
4933 if (chain) {
4934 asan_unlock_freelist(page);
4935 if (page->free_region) {
4936 struct free_region *p = page->free_region;
4937 rb_asan_unpoison_object((VALUE)p, false);
4938 while (p->next) {
4939 struct free_region *prev = p;
4940 p = p->next;
4941 rb_asan_poison_object((VALUE)prev);
4942 rb_asan_unpoison_object((VALUE)p, false);
4943 }
4944 p->next = chain;
4945 rb_asan_poison_object((VALUE)p);
4946 }
4947 else {
4948 page->free_region = chain;
4949 }
4950 asan_lock_freelist(page);
4951 }
4952}
4953
4954static void
4955gc_sweep_start_heap(rb_objspace_t *objspace, rb_heap_t *heap)
4956{
4957 heap->sweeping_page = ccan_list_top(&heap->pages, struct heap_page, page_node);
4958 if (heap->sweeping_page) {
4959 objspace->sweeping_heap_count++;
4960 }
4961 heap->free_pages = NULL;
4962 heap->pooled_pages = NULL;
4963 if (!objspace->flags.immediate_sweep) {
4964 struct heap_page *page = NULL;
4965
4966 ccan_list_for_each(&heap->pages, page, page_node) {
4967 page->flags.before_sweep = TRUE;
4968 }
4969 }
4970}
4971
4972#if GC_CAN_COMPILE_COMPACTION
4973static void gc_sort_heap_by_compare_func(rb_objspace_t *objspace, gc_compact_compare_func compare_func);
4974static int compare_pinned_slots(const void *left, const void *right, void *d);
4975#endif
4976
4977/* Return the current allocation page and freelist to their pages, so the sweeper sees a
4978 * consistent heap. */
4979static void
4980heap_alloc_state_clear(rb_objspace_t *objspace)
4981{
4982 objspace->incremental_mark_step_allocated_slots = 0;
4983
4984 for (size_t heap_idx = 0; heap_idx < HEAP_COUNT; heap_idx++) {
4985 rb_heap_t *heap = &heaps[heap_idx];
4986
4987 struct heap_page *page = heap->newobj.alloc_using_page;
4988 RUBY_DEBUG_LOG("heap alloc_using_page:%p cursor:%p", (void *)page, (void *)heap->newobj.alloc_cursor);
4989
4990 if (page) {
4991 heap_page_flush_alloc_regions(page, heap);
4992 }
4993
4994 heap->newobj.alloc_using_page = NULL;
4995 heap->newobj.alloc_cursor = 0;
4996 heap->newobj.alloc_cursor_end = 0;
4997 heap->newobj.alloc_next_region = NULL;
4998 }
4999}
5000
5001static void
5002gc_sweep_freeobj_hooks_page(rb_objspace_t *objspace, struct heap_page *page)
5003{
5004 bits_t *bits = page->mark_bits;
5005 uintptr_t p = (uintptr_t)page->start;
5006 short slot_size = page->slot_size;
5007 int total_slots = page->total_slots;
5008 int bitmap_plane_count = CEILDIV(total_slots, BITS_BITLENGTH);
5009
5010 int out_of_range_bits = total_slots % BITS_BITLENGTH;
5011 bits_t last_plane_mask = (out_of_range_bits != 0)
5012 ? ~(((bits_t)1 << out_of_range_bits) - 1)
5013 : 0;
5014
5015 for (int j = 0; j < bitmap_plane_count; j++) {
5016 bits_t bitset = ~bits[j];
5017 if (j == bitmap_plane_count - 1) {
5018 bitset &= ~last_plane_mask;
5019 }
5020
5021 uintptr_t pp = p;
5022 while (bitset) {
5023 if (bitset & 1) {
5024 VALUE vp = (VALUE)pp;
5025 asan_unpoisoning_object(vp) {
5026 switch (BUILTIN_TYPE(vp)) {
5027 case T_NONE:
5028 case T_ZOMBIE:
5029 case T_MOVED:
5030 break;
5031 default:
5032 rb_gc_event_hook(vp, RUBY_INTERNAL_EVENT_FREEOBJ);
5033 break;
5034 }
5035 }
5036 }
5037 pp += slot_size;
5038 bitset >>= 1;
5039 }
5040 p += BITS_BITLENGTH * slot_size;
5041 }
5042}
5043
5044static void
5045gc_sweep_freeobj_hooks(rb_objspace_t *objspace)
5046{
5047 for (int i = 0; i < HEAP_COUNT; i++) {
5048 rb_heap_t *heap = &heaps[i];
5049 struct heap_page *page = NULL;
5050
5051 ccan_list_for_each(&heap->pages, page, page_node) {
5052 gc_sweep_freeobj_hooks_page(objspace, page);
5053 }
5054 }
5055}
5056
5057static void
5058gc_sweep_start(rb_objspace_t *objspace)
5059{
5060 gc_mode_transition(objspace, gc_mode_sweeping);
5061 objspace->rincgc.pooled_slots = 0;
5062
5063 if (RB_UNLIKELY(objspace->hook_events & RUBY_INTERNAL_EVENT_FREEOBJ)) {
5064 /* FREEOBJ is never enabled outside the main objspace
5065 * (rb_objspace_set_event_hook), so this hook, which runs user callbacks,
5066 * cannot fire during a non-main Ractor's lock-free local sweep. */
5067 GC_ASSERT(objspace == global_objspace->main_objspace);
5068 gc_sweep_freeobj_hooks(objspace);
5069 }
5070
5071#if GC_CAN_COMPILE_COMPACTION
5072 if (objspace->flags.during_compacting) {
5073 gc_sort_heap_by_compare_func(
5074 objspace,
5075 objspace->rcompactor.compare_func ? objspace->rcompactor.compare_func : compare_pinned_slots
5076 );
5077 }
5078#endif
5079
5080 for (int i = 0; i < HEAP_COUNT; i++) {
5081 rb_heap_t *heap = &heaps[i];
5082 gc_sweep_start_heap(objspace, heap);
5083
5084 /* We should call gc_sweep_finish_heap for size pools with no pages. */
5085 if (heap->sweeping_page == NULL) {
5086 GC_ASSERT(heap->total_pages == 0);
5087 GC_ASSERT(heap->total_slots == 0);
5088 gc_sweep_finish_heap(objspace, heap);
5089 }
5090 }
5091
5092 heap_alloc_state_clear(objspace);
5093}
5094
5095static void
5096gc_sweep_finish_heap(rb_objspace_t *objspace, rb_heap_t *heap)
5097{
5098 size_t total_slots = heap->total_slots;
5099 size_t swept_slots = heap->freed_slots + heap->empty_slots;
5100
5101 size_t init_slots = gc_params.heap_init_bytes / heap->slot_size;
5102 size_t min_free_slots = (size_t)(MAX(total_slots, init_slots) * gc_params.heap_free_slots_min_ratio);
5103
5104 if (swept_slots < min_free_slots &&
5105 /* The heap is a growth heap if it freed more slots than had empty slots. */
5106 ((heap->empty_slots == 0 && total_slots > 0) || heap->freed_slots > heap->empty_slots)) {
5107 /* If we don't have enough slots and we have pages on the tomb heap, move
5108 * pages from the tomb heap to the eden heap. This may prevent page
5109 * creation thrashing (frequently allocating and deallocting pages) and
5110 * GC thrashing (running GC more frequently than required). */
5111 struct heap_page *resurrected_page;
5112 while (swept_slots < min_free_slots &&
5113 (resurrected_page = heap_page_resurrect(objspace))) {
5114 heap_add_page(objspace, heap, resurrected_page);
5115 heap_add_freepage(heap, resurrected_page);
5116
5117 swept_slots += resurrected_page->free_slots;
5118 }
5119
5120 if (swept_slots < min_free_slots) {
5121 /* Grow this heap if we are in a major GC or if we haven't run at least
5122 * RVALUE_OLD_AGE minor GC since the last major GC. */
5123 if (is_full_marking(objspace) ||
5124 objspace->profile.count - objspace->rgengc.last_major_gc < RVALUE_OLD_AGE) {
5125 if (objspace->heap_pages.allocatable_bytes < min_free_slots * heap->slot_size) {
5126 heap_allocatable_bytes_expand(objspace, heap, swept_slots, heap->total_slots, heap->slot_size);
5127 }
5128 }
5129 else if (swept_slots < min_free_slots * 7 / 8 &&
5130 objspace->heap_pages.allocatable_bytes < (min_free_slots * 7 / 8 - swept_slots) * heap->slot_size) {
5131 gc_needs_major_flags |= GPR_FLAG_MAJOR_BY_NOFREE;
5132 heap->force_major_gc_count++;
5133 }
5134 }
5135 }
5136}
5137
5138static void
5139gc_sweep_finish(rb_objspace_t *objspace)
5140{
5141 gc_report(1, objspace, "gc_sweep_finish\n");
5142
5143 gc_prof_set_heap_info(objspace);
5144 heap_pages_free_unused_pages(objspace);
5145 if (rb_gc_single_objspace_p() && is_full_marking(objspace)) {
5146 /* gc_marks_finish retains ~2/3 of empty pages in objspace->empty_pages for reuse,
5147 * only the excess reaches the pool. */
5148 page_pool_reclaim(global_objspace);
5149 }
5150
5151 for (int i = 0; i < HEAP_COUNT; i++) {
5152 rb_heap_t *heap = &heaps[i];
5153
5154 heap->freed_slots = 0;
5155 heap->empty_slots = 0;
5156
5157 if (!will_be_incremental_marking(objspace)) {
5158 struct heap_page *end_page = heap->free_pages;
5159 if (end_page) {
5160 while (end_page->free_next) end_page = end_page->free_next;
5161 end_page->free_next = heap->pooled_pages;
5162 }
5163 else {
5164 heap->free_pages = heap->pooled_pages;
5165 }
5166 heap->pooled_pages = NULL;
5167 objspace->rincgc.pooled_slots = 0;
5168 }
5169 }
5170
5171 /* Not before: while sweeping is in progress its frees must keep reducing
5172 * malloc_increase (objspace_malloc_increase_body sweeps and retries on it). */
5173 gc_malloc_counters_snapshot_free_at_last_gc(objspace, &objspace->malloc_counters.counters);
5174#if RGENGC_ESTIMATE_OLDMALLOC
5175 gc_malloc_counters_snapshot_free_at_last_gc(objspace, &objspace->malloc_counters.oldcounters);
5176#endif
5177
5179 gc_mode_transition(objspace, gc_mode_none);
5180}
5181
5182static int
5183gc_sweep_step(rb_objspace_t *objspace, rb_heap_t *heap)
5184{
5185 struct heap_page *sweep_page = heap->sweeping_page;
5186 int swept_slots = 0;
5187 int pooled_slots = 0;
5188 int sweep_budget = GC_INCREMENTAL_SWEEP_BYTES / heap->slot_size;
5189 int pool_budget = GC_INCREMENTAL_SWEEP_POOL_BYTES / heap->slot_size;
5190
5191 if (sweep_page == NULL) return FALSE;
5192
5193#if GC_ENABLE_LAZY_SWEEP
5194 gc_prof_sweep_timer_start(objspace);
5195#endif
5196
5197 /* Per-slot pinned-free assert (gc_sweep_context): check only when this cycle's mark
5198 * ran the pinned walk. The current world state would misfire: a single-world
5199 * cycle leaves dead shareable objects unmarked and its sweep can straddle the switch
5200 * to multi-objspace. A global GC's exact mark does not pin, so it is excluded. */
5201 const unsigned char check_pinned_free = objspace->last_cycle_pinned;
5202
5203 do {
5204 RUBY_DEBUG_LOG("sweep_page:%p", (void *)sweep_page);
5205
5206 struct gc_sweep_context ctx = {
5207 .page = sweep_page,
5208 .final_slots = 0,
5209 .freed_slots = 0,
5210 .empty_slots = 0,
5211 .check_pinned_free = check_pinned_free,
5212 };
5213 gc_sweep_page(objspace, heap, &ctx);
5214 int free_slots = ctx.freed_slots + ctx.empty_slots;
5215
5216 RUBY_DTRACE_GC_HOOK(SWEEP_PAGE, ctx.page->slot_size, ctx.final_slots, ctx.freed_slots, ctx.empty_slots);
5217
5218 heap->sweeping_page = ccan_list_next(&heap->pages, sweep_page, page_node);
5219
5220 if (free_slots == sweep_page->total_slots) {
5221 /* There are no living objects, so move this page to the global empty pages. */
5222 heap_unlink_page(objspace, heap, sweep_page);
5223
5224 sweep_page->start = 0;
5225 sweep_page->total_slots = 0;
5226 sweep_page->slot_size = 0;
5227 sweep_page->heap = NULL;
5228 sweep_page->free_slots = 0;
5229
5230 asan_unlock_freelist(sweep_page);
5231 sweep_page->free_region = NULL;
5232 asan_lock_freelist(sweep_page);
5233
5234 asan_poison_memory_region(sweep_page->body, HEAP_PAGE_SIZE);
5235
5236 objspace->empty_pages_count++;
5237 sweep_page->free_next = objspace->empty_pages;
5238 objspace->empty_pages = sweep_page;
5239 }
5240 else if (free_slots > 0) {
5241 heap->freed_slots += ctx.freed_slots;
5242 heap->empty_slots += ctx.empty_slots;
5243
5244 if (pooled_slots < pool_budget) {
5245 heap_add_poolpage(objspace, heap, sweep_page);
5246 pooled_slots += free_slots;
5247 }
5248 else {
5249 heap_add_freepage(heap, sweep_page);
5250 swept_slots += free_slots;
5251 if (swept_slots > sweep_budget) {
5252 break;
5253 }
5254 }
5255 }
5256 else {
5257 sweep_page->free_next = NULL;
5258 }
5259 } while ((sweep_page = heap->sweeping_page));
5260
5261 if (!heap->sweeping_page) {
5262 objspace->sweeping_heap_count--;
5263 GC_ASSERT(objspace->sweeping_heap_count >= 0);
5264 gc_sweep_finish_heap(objspace, heap);
5265
5266 if (!has_sweeping_pages(objspace)) {
5267 gc_sweep_finish(objspace);
5268 }
5269 }
5270
5271#if GC_ENABLE_LAZY_SWEEP
5272 gc_prof_sweep_timer_stop(objspace);
5273#endif
5274
5275 return heap->free_pages != NULL;
5276}
5277
5278static void
5279gc_sweep_rest(rb_objspace_t *objspace)
5280{
5281 for (int i = 0; i < HEAP_COUNT; i++) {
5282 rb_heap_t *heap = &heaps[i];
5283
5284 while (heap->sweeping_page) {
5285 gc_sweep_step(objspace, heap);
5286 }
5287 }
5288
5289 /* An objspace with no live pages never runs gc_sweep_step and so never reaches
5290 * gc_sweep_finish, leaving mode at sweeping or compacting until the next cycle's
5291 * gc_sweep_start asserts. If every heap is swept out, settle it to none here. */
5292 if (gc_mode(objspace) != gc_mode_none && !has_sweeping_pages(objspace)) {
5293 gc_sweep_finish(objspace);
5294 }
5295}
5296
5297static void
5298gc_sweep_continue(rb_objspace_t *objspace, rb_heap_t *sweep_heap)
5299{
5300 GC_ASSERT(dont_gc_val() == FALSE || objspace->profile.latest_gc_info & GPR_FLAG_METHOD);
5301 if (!GC_ENABLE_LAZY_SWEEP) return;
5302
5303 gc_sweeping_enter(objspace);
5304
5305 for (int i = 0; i < HEAP_COUNT; i++) {
5306 rb_heap_t *heap = &heaps[i];
5307 if (gc_sweep_step(objspace, heap)) {
5308 GC_ASSERT(heap->free_pages != NULL);
5309 }
5310 else if (heap == sweep_heap) {
5311 if (objspace->empty_pages_count > 0 || objspace->heap_pages.allocatable_bytes > 0) {
5312 /* [Bug #21548]
5313 *
5314 * If this heap is the heap we want to sweep, but we weren't able
5315 * to free any slots, but we also either have empty pages or could
5316 * allocate new pages, then we want to preemptively claim a page
5317 * because it's possible that sweeping another heap will call
5318 * gc_sweep_finish_heap, which may use up all of the
5319 * empty/allocatable pages. If other heaps are not finished sweeping
5320 * then we do not finish this GC and we will end up triggering a new
5321 * GC cycle during this GC phase. */
5322 heap_page_allocate_and_initialize(objspace, heap);
5323
5324 GC_ASSERT(heap->free_pages != NULL);
5325 }
5326 else {
5327 /* Not allowed to create a new page so finish sweeping. */
5328 gc_sweep_rest(objspace);
5329 GC_ASSERT(gc_mode(objspace) == gc_mode_none);
5330 break;
5331 }
5332 }
5333 }
5334
5335 gc_sweeping_exit(objspace);
5336}
5337
5338static void
5339gc_sweep_step_for_malloc(rb_objspace_t *objspace)
5340{
5341 GC_ASSERT(is_lazy_sweeping(objspace));
5342
5343 unsigned int lock_lev;
5344 gc_enter(objspace, gc_enter_event_continue, &lock_lev);
5345
5346 gc_sweeping_enter(objspace);
5347
5348 for (int i = 0; i < HEAP_COUNT; i++) {
5349 rb_heap_t *heap = &heaps[i];
5350 gc_sweep_step(objspace, heap);
5351 }
5352
5353 gc_sweeping_exit(objspace);
5354
5355 gc_exit(objspace, gc_enter_event_continue, &lock_lev);
5356}
5357
5358static bool gc_global_pointer_to_heap_p(const void *ptr);
5359
5360VALUE
5361rb_gc_impl_location(void *objspace_ptr, VALUE value)
5362{
5363 rb_objspace_t *objspace = objspace_ptr;
5364 VALUE destination;
5365
5366 /* A local (single-objspace) compaction never moves another objspace's objects, so
5367 * leave foreign references alone. A compacting global GC moves objects everywhere
5368 * under the barrier, so there every objspace's heap is searched for forwarding. */
5369 if (RB_UNLIKELY(objspace->flags.during_global_gc)
5370 ? !gc_global_pointer_to_heap_p((void *)value)
5371 : !is_pointer_to_heap(objspace_ptr, (void *)value)) {
5372 return value;
5373 }
5374
5375 asan_unpoisoning_object(value) {
5376 if (BUILTIN_TYPE(value) == T_MOVED) {
5377 destination = (VALUE)RMOVED(value)->destination;
5378 GC_ASSERT(BUILTIN_TYPE(destination) != T_NONE);
5379 }
5380 else {
5381 destination = value;
5382 }
5383 }
5384
5385 return destination;
5386}
5387
5388#if GC_CAN_COMPILE_COMPACTION
5389static void
5390invalidate_moved_plane(rb_objspace_t *objspace, struct heap_page *page, uintptr_t p, bits_t bitset)
5391{
5392 if (bitset) {
5393 do {
5394 if (bitset & 1) {
5395 VALUE forwarding_object = (VALUE)p;
5396 VALUE object;
5397
5398 if (BUILTIN_TYPE(forwarding_object) == T_MOVED) {
5399 GC_ASSERT(RVALUE_PINNED(objspace, forwarding_object));
5400 GC_ASSERT(!RVALUE_MARKED(objspace, forwarding_object));
5401
5402 CLEAR_IN_BITMAP(GET_HEAP_PINNED_BITS(forwarding_object), forwarding_object);
5403
5404 object = rb_gc_impl_location(objspace, forwarding_object);
5405 gc_move(objspace, object, forwarding_object, GET_HEAP_PAGE(object), page);
5406 /* forwarding_object is now our actual object, and "object"
5407 * is the free slot for the original page */
5408
5409 struct heap_page *orig_page = GET_HEAP_PAGE(object);
5410 orig_page->free_slots++;
5411 RVALUE_AGE_SET_BITMAP(object, 0);
5412 heap_page_add_free_region(objspace, orig_page, object);
5413
5414 GC_ASSERT(RVALUE_MARKED(objspace, forwarding_object));
5415 GC_ASSERT(BUILTIN_TYPE(forwarding_object) != T_MOVED);
5416 GC_ASSERT(BUILTIN_TYPE(forwarding_object) != T_NONE);
5417 }
5418 }
5419 p += page->slot_size;
5420 bitset >>= 1;
5421 } while (bitset);
5422 }
5423}
5424
5425static void
5426invalidate_moved_page(rb_objspace_t *objspace, struct heap_page *page)
5427{
5428 int i;
5429 bits_t *mark_bits, *pin_bits;
5430 bits_t bitset;
5431 short slot_size = page->slot_size;
5432 int total_slots = page->total_slots;
5433 int bitmap_plane_count = CEILDIV(total_slots, BITS_BITLENGTH);
5434
5435 mark_bits = page->mark_bits;
5436 pin_bits = page->pinned_bits;
5437
5438 uintptr_t p = page->start;
5439
5440 for (i=0; i < bitmap_plane_count; i++) {
5441 /* Moved objects are pinned but never marked. We reuse the pin bits
5442 * to indicate there is a moved object in this slot. */
5443 bitset = pin_bits[i] & ~mark_bits[i];
5444 invalidate_moved_plane(objspace, page, p, bitset);
5445 p += BITS_BITLENGTH * slot_size;
5446 }
5447}
5448#endif
5449
5450static void
5451gc_compact_start(rb_objspace_t *objspace)
5452{
5453 struct heap_page *page = NULL;
5454 gc_mode_transition(objspace, gc_mode_compacting);
5455
5456 for (int i = 0; i < HEAP_COUNT; i++) {
5457 rb_heap_t *heap = &heaps[i];
5458 ccan_list_for_each(&heap->pages, page, page_node) {
5459 page->flags.before_sweep = TRUE;
5460 }
5461
5462 heap->compact_cursor = ccan_list_tail(&heap->pages, struct heap_page, page_node);
5463 heap->compact_cursor_index = 0;
5464 }
5465
5466 if (gc_prof_enabled(objspace)) {
5467 gc_profile_record *record = gc_prof_record(objspace);
5468 record->moved_objects = objspace->rcompactor.total_moved;
5469 }
5470
5471 memset(objspace->rcompactor.considered_count_table, 0, T_MASK * sizeof(size_t));
5472 memset(objspace->rcompactor.moved_count_table, 0, T_MASK * sizeof(size_t));
5473 memset(objspace->rcompactor.moved_up_count_table, 0, T_MASK * sizeof(size_t));
5474 memset(objspace->rcompactor.moved_down_count_table, 0, T_MASK * sizeof(size_t));
5475
5476 /* Set up read barrier for pages containing MOVED objects */
5477 /* A compacting global GC installs the read barrier once for every objspace. */
5478 if (!global_objspace->global_gc.compacting) install_handlers();
5479}
5480
5481static void gc_sweep_compact(rb_objspace_t *objspace);
5482
5483static void
5484gc_sweep(rb_objspace_t *objspace)
5485{
5486 gc_sweeping_enter(objspace);
5487
5488 const unsigned int immediate_sweep = objspace->flags.immediate_sweep;
5489
5490 gc_report(1, objspace, "gc_sweep: immediate: %d\n", immediate_sweep);
5491
5492 gc_sweep_start(objspace);
5493 if (objspace->flags.during_compacting) {
5494 rb_hrtime_t compact_start_time = gc_prof_enabled(objspace) ? rb_hrtime_now() : 0;
5495 gc_sweep_compact(objspace);
5496 if (gc_prof_enabled(objspace)) {
5497 rb_hrtime_t compact_wall_time = elapsed_hrtime_from(compact_start_time);
5498 gc_profile_record *record = gc_prof_record(objspace);
5499 record->gc_compact_wall_time = rb_hrtime_add(record->gc_compact_wall_time,
5500 compact_wall_time);
5501 objspace->profile.gc_sweep_excluded_wall_time = rb_hrtime_add(
5502 objspace->profile.gc_sweep_excluded_wall_time,
5503 compact_wall_time);
5504 }
5505 }
5506
5507 if (immediate_sweep) {
5508#if !GC_ENABLE_LAZY_SWEEP
5509 gc_prof_sweep_timer_start(objspace);
5510#endif
5511 gc_sweep_rest(objspace);
5512#if !GC_ENABLE_LAZY_SWEEP
5513 gc_prof_sweep_timer_stop(objspace);
5514#endif
5515 }
5516 else {
5517
5518 /* Sweep every size pool. */
5519 for (int i = 0; i < HEAP_COUNT; i++) {
5520 rb_heap_t *heap = &heaps[i];
5521 gc_sweep_step(objspace, heap);
5522 }
5523 }
5524
5525 gc_sweeping_exit(objspace);
5526}
5527
5528/* Marking - Marking stack */
5529
5530static stack_chunk_t *
5531stack_chunk_alloc(void)
5532{
5533 stack_chunk_t *res;
5534
5535 res = malloc(sizeof(stack_chunk_t));
5536 if (!res)
5537 rb_memerror();
5538
5539 return res;
5540}
5541
5542static inline int
5543is_mark_stack_empty(mark_stack_t *stack)
5544{
5545 return stack->chunk == NULL;
5546}
5547
5548static size_t
5549mark_stack_size(mark_stack_t *stack)
5550{
5551 size_t size = stack->index;
5552 stack_chunk_t *chunk = stack->chunk ? stack->chunk->next : NULL;
5553
5554 while (chunk) {
5555 size += stack->limit;
5556 chunk = chunk->next;
5557 }
5558 return size;
5559}
5560
5561static void
5562add_stack_chunk_cache(mark_stack_t *stack, stack_chunk_t *chunk)
5563{
5564 chunk->next = stack->cache;
5565 stack->cache = chunk;
5566 stack->cache_size++;
5567}
5568
5569static void
5570shrink_stack_chunk_cache(mark_stack_t *stack)
5571{
5572 stack_chunk_t *chunk;
5573
5574 if (stack->unused_cache_size > (stack->cache_size/2)) {
5575 chunk = stack->cache;
5576 stack->cache = stack->cache->next;
5577 stack->cache_size--;
5578 free(chunk);
5579 }
5580 stack->unused_cache_size = stack->cache_size;
5581}
5582
5583static void
5584push_mark_stack_chunk(mark_stack_t *stack)
5585{
5586 stack_chunk_t *next;
5587
5588 GC_ASSERT(stack->index == stack->limit);
5589
5590 if (stack->cache_size > 0) {
5591 next = stack->cache;
5592 stack->cache = stack->cache->next;
5593 stack->cache_size--;
5594 if (stack->unused_cache_size > stack->cache_size)
5595 stack->unused_cache_size = stack->cache_size;
5596 }
5597 else {
5598 next = stack_chunk_alloc();
5599 }
5600 next->next = stack->chunk;
5601 stack->chunk = next;
5602 stack->index = 0;
5603}
5604
5605static void
5606pop_mark_stack_chunk(mark_stack_t *stack)
5607{
5608 stack_chunk_t *prev;
5609
5610 prev = stack->chunk->next;
5611 GC_ASSERT(stack->index == 0);
5612 add_stack_chunk_cache(stack, stack->chunk);
5613 stack->chunk = prev;
5614 stack->index = stack->limit;
5615}
5616
5617static void
5618mark_stack_chunk_list_free(stack_chunk_t *chunk)
5619{
5620 stack_chunk_t *next = NULL;
5621
5622 while (chunk != NULL) {
5623 next = chunk->next;
5624 free(chunk);
5625 chunk = next;
5626 }
5627}
5628
5629static void
5630free_stack_chunks(mark_stack_t *stack)
5631{
5632 mark_stack_chunk_list_free(stack->chunk);
5633}
5634
5635static void
5636mark_stack_free_cache(mark_stack_t *stack)
5637{
5638 mark_stack_chunk_list_free(stack->cache);
5639 stack->cache_size = 0;
5640 stack->unused_cache_size = 0;
5641}
5642
5643static void
5644push_mark_stack(mark_stack_t *stack, VALUE obj)
5645{
5646 switch (BUILTIN_TYPE(obj)) {
5647 case T_OBJECT:
5648 case T_CLASS:
5649 case T_MODULE:
5650 case T_FLOAT:
5651 case T_STRING:
5652 case T_REGEXP:
5653 case T_ARRAY:
5654 case T_HASH:
5655 case T_STRUCT:
5656 case T_BIGNUM:
5657 case T_FILE:
5658 case T_DATA:
5659 case T_MATCH:
5660 case T_COMPLEX:
5661 case T_RATIONAL:
5662 case T_TRUE:
5663 case T_FALSE:
5664 case T_SYMBOL:
5665 case T_IMEMO:
5666 case T_ICLASS:
5667 if (stack->index == stack->limit) {
5668 push_mark_stack_chunk(stack);
5669 }
5670 stack->chunk->data[stack->index++] = obj;
5671 return;
5672
5673 case T_NONE:
5674 case T_NIL:
5675 case T_FIXNUM:
5676 case T_MOVED:
5677 case T_ZOMBIE:
5678 case T_UNDEF:
5679 case T_MASK:
5680 rb_bug("push_mark_stack() called for broken object");
5681 break;
5682
5683 case T_NODE:
5684 rb_bug("push_mark_stack: unexpected T_NODE object");
5685 break;
5686 }
5687
5688 rb_bug("rb_gc_mark(): unknown data type 0x%x(%p) %s",
5689 BUILTIN_TYPE(obj), (void *)obj,
5690 is_pointer_to_heap((rb_objspace_t *)rb_gc_get_objspace(), (void *)obj) ? "corrupted object" : "non object");
5691}
5692
5693static int
5694pop_mark_stack(mark_stack_t *stack, VALUE *data)
5695{
5696 if (is_mark_stack_empty(stack)) {
5697 return FALSE;
5698 }
5699 if (stack->index == 1) {
5700 *data = stack->chunk->data[--stack->index];
5701 pop_mark_stack_chunk(stack);
5702 }
5703 else {
5704 *data = stack->chunk->data[--stack->index];
5705 }
5706 return TRUE;
5707}
5708
5709static void
5710init_mark_stack(mark_stack_t *stack)
5711{
5712 int i;
5713
5714 MEMZERO(stack, mark_stack_t, 1);
5715 stack->index = stack->limit = STACK_CHUNK_SIZE;
5716
5717 for (i=0; i < 4; i++) {
5718 add_stack_chunk_cache(stack, stack_chunk_alloc());
5719 }
5720 stack->unused_cache_size = stack->cache_size;
5721}
5722
5723/* Marking */
5724
5725ALWAYS_INLINE(static int gc_mark_set(rb_objspace_t *objspace, VALUE obj));
5726ALWAYS_INLINE(static void gc_mark_check_t_none(rb_objspace_t *objspace, VALUE obj));
5727ALWAYS_INLINE(static void rgengc_check_relation(rb_objspace_t *objspace, VALUE obj));
5728ALWAYS_INLINE(static void gc_aging(rb_objspace_t *objspace, VALUE obj));
5729ALWAYS_INLINE(static void gc_grey(rb_objspace_t *objspace, VALUE obj));
5730static void
5731rgengc_check_relation(rb_objspace_t *objspace, VALUE obj)
5732{
5733 if (objspace->rgengc.parent_object_old_p) {
5734 if (RVALUE_WB_UNPROTECTED(objspace, obj) || !RVALUE_OLD_P(objspace, obj)) {
5735 rgengc_remember(objspace, objspace->rgengc.parent_object);
5736 /* It is in the rememberset now, so its remaining children have nothing left
5737 * to ask for: stop testing them. */
5738 objspace->rgengc.parent_object_old_p = false;
5739 }
5740 }
5741}
5742
5743static inline int
5744gc_mark_set(rb_objspace_t *objspace, VALUE obj)
5745{
5746 if (RVALUE_MARKED(objspace, obj)) return 0;
5747 MARK_IN_BITMAP(GET_HEAP_MARK_BITS(obj), obj);
5748 return 1;
5749}
5750
5751static void
5752gc_aging(rb_objspace_t *objspace, VALUE obj)
5753{
5754 /* Disable aging if Major GC's are disabled. This will prevent longish lived
5755 * objects filling up the heap at the expense of marking many more objects.
5756 *
5757 * We should always pre-warm our process when disabling majors, by running
5758 * GC manually several times so that most objects likely to become oldgen
5759 * are already oldgen.
5760 */
5761 if(!gc_config_full_mark_val)
5762 return;
5763
5764 struct heap_page *page = GET_HEAP_PAGE(obj);
5765
5766 GC_ASSERT(RVALUE_MARKING(objspace, obj) == FALSE);
5767 check_rvalue_consistency(objspace, obj);
5768
5769 if (!RVALUE_PAGE_WB_UNPROTECTED(page, obj)) {
5770 if (!RVALUE_OLD_P(objspace, obj)) {
5771 int t = BUILTIN_TYPE(obj);
5772 if (t == T_CLASS || t == T_MODULE || t == T_ICLASS) {
5773 gc_report(3, objspace, "gc_aging: YOUNG class: %s\n", rb_obj_info(obj));
5774 RVALUE_AGE_SET(obj, RVALUE_OLD_AGE);
5775 RVALUE_OLD_UNCOLLECTIBLE_SET(objspace, obj);
5776 }
5777 else {
5778 gc_report(3, objspace, "gc_aging: YOUNG: %s\n", rb_obj_info(obj));
5779 RVALUE_AGE_INC(objspace, obj);
5780 }
5781 }
5782 else if (is_full_marking(objspace)) {
5783 GC_ASSERT(RVALUE_PAGE_UNCOLLECTIBLE(page, obj) == FALSE);
5784 RVALUE_PAGE_OLD_UNCOLLECTIBLE_SET(objspace, page, obj);
5785 }
5786 }
5787 check_rvalue_consistency(objspace, obj);
5788
5789 objspace->marked_slots++;
5790}
5791
5792static void
5793gc_grey(rb_objspace_t *objspace, VALUE obj)
5794{
5795#if RGENGC_CHECK_MODE
5796 if (RVALUE_MARKED(objspace, obj) == FALSE) rb_bug("gc_grey: %s is not marked.", rb_obj_info(obj));
5797 if (RVALUE_MARKING(objspace, obj) == TRUE) rb_bug("gc_grey: %s is marking/remembered.", rb_obj_info(obj));
5798#endif
5799
5800 if (is_incremental_marking(objspace)) {
5801 MARK_IN_BITMAP(GET_HEAP_MARKING_BITS(obj), obj);
5802 }
5803
5805 rb_darray_append_without_gc(&objspace->weak_references, obj);
5806 }
5807
5808 push_mark_stack(&objspace->mark_stack, obj);
5809}
5810
5811static inline void
5812gc_mark_check_t_none(rb_objspace_t *objspace, VALUE obj)
5813{
5814 if (RB_UNLIKELY(BUILTIN_TYPE(obj) == T_NONE)) {
5815 enum {info_size = 256};
5816 char obj_info_buf[info_size];
5817 rb_raw_obj_info(obj_info_buf, info_size, obj);
5818
5819 char parent_obj_info_buf[info_size];
5820 rb_raw_obj_info(parent_obj_info_buf, info_size, objspace->rgengc.parent_object);
5821
5822 rb_bug("try to mark T_NONE object (obj: %s, parent: %s)", obj_info_buf, parent_obj_info_buf);
5823 }
5824}
5825
5826static void
5827gc_mark(rb_objspace_t *objspace, VALUE obj)
5828{
5829 GC_ASSERT(during_gc);
5830 GC_ASSERT(!objspace->flags.during_reference_updating);
5831
5832 /* Never step into another objspace: a foreign object is a live leaf whose liveness
5833 * belongs to its owner, so touching its bitmaps here would be unsound. A global GC
5834 * lifts this: everyone is stopped and the bits live on the object's own page. */
5835 if (gc_skip_foreign_object_p(objspace, obj)) {
5836 return;
5837 }
5838
5839 if (RB_UNLIKELY(objspace->flags.during_global_gc)) {
5840 /* Recompute the shref of every shareable -> unshareable edge, within and across
5841 * objspaces: the clear pass dropped all shref bits and the write barrier
5842 * maintains them from here on. */
5843 VALUE parent = objspace->rgengc.parent_object;
5844 if (!UNDEF_P(parent) && parent != Qfalse &&
5847 struct heap_page *page = GET_HEAP_PAGE(obj);
5848 _MARK_IN_BITMAP(page->shref_bits, page, obj);
5849 page->flags.has_shref_objects = TRUE;
5850 }
5851 }
5852
5853 rgengc_check_relation(objspace, obj);
5854 if (!gc_mark_set(objspace, obj)) return; /* already marked */
5855
5856 if (0) { // for debug GC marking miss
5857 RUBY_DEBUG_LOG("%p (%s) parent:%p (%s)",
5858 (void *)obj, obj_type_name(obj),
5859 (void *)objspace->rgengc.parent_object, obj_type_name(objspace->rgengc.parent_object));
5860 }
5861
5862 gc_mark_check_t_none(objspace, obj);
5863
5864 gc_aging(objspace, obj);
5865 gc_grey(objspace, obj);
5866}
5867
5868static inline void
5869gc_pin(rb_objspace_t *objspace, VALUE obj)
5870{
5871 GC_ASSERT(!SPECIAL_CONST_P(obj));
5872
5873 if (RB_UNLIKELY(objspace->flags.during_compacting)) {
5874 /* Never write a foreign page's pinned bit (a global GC may: everyone is stopped). */
5875 if (gc_skip_foreign_object_p(objspace, obj)) return;
5876
5877 if (RB_LIKELY(during_gc)) {
5878 if (!RVALUE_PINNED(objspace, obj)) {
5879 GC_ASSERT(GET_HEAP_PAGE(obj)->pinned_slots <= GET_HEAP_PAGE(obj)->total_slots);
5880 GET_HEAP_PAGE(obj)->pinned_slots++;
5881 MARK_IN_BITMAP(GET_HEAP_PINNED_BITS(obj), obj);
5882 }
5883 }
5884 }
5885}
5886
5887static inline void
5888gc_mark_and_pin(rb_objspace_t *objspace, VALUE obj)
5889{
5890 gc_pin(objspace, obj);
5891 gc_mark(objspace, obj);
5892}
5893
5894void
5895rb_gc_impl_mark_and_move(void *objspace_ptr, VALUE *ptr)
5896{
5897 rb_objspace_t *objspace = objspace_ptr;
5898
5899 if (RB_UNLIKELY(objspace->flags.during_reference_updating)) {
5900 GC_ASSERT(objspace->flags.during_compacting);
5901 GC_ASSERT(during_gc);
5902
5903 VALUE destination = rb_gc_impl_location(objspace, *ptr);
5904 if (destination != *ptr) {
5905 *ptr = destination;
5906 }
5907 }
5908 else {
5909 gc_mark(objspace, *ptr);
5910 }
5911}
5912
5913void
5914rb_gc_impl_mark(void *objspace_ptr, VALUE obj)
5915{
5916 rb_objspace_t *objspace = objspace_ptr;
5917
5918 gc_mark(objspace, obj);
5919}
5920
5921void
5922rb_gc_impl_mark_and_pin(void *objspace_ptr, VALUE obj)
5923{
5924 rb_objspace_t *objspace = objspace_ptr;
5925
5926 gc_mark_and_pin(objspace, obj);
5927}
5928
5929/* A word scanned conservatively by a global GC can point into any objspace, so ownership
5930 * is decided against the driver's snapshot of every objspace (the bits then land on the
5931 * owner's page through gc_mark and gc_pin). */
5932static bool
5933gc_global_pointer_to_heap_p(const void *ptr)
5934{
5935 const rb_global_objspace_t *g = global_objspace;
5936 uintptr_t p = (uintptr_t)ptr;
5937
5938 if (p < g->page_index.lomem || p > g->page_index.himem) return false;
5939 if (p % sizeof(VALUE) != 0) return false;
5940
5941 struct heap_page **res = bsearch(ptr, g->page_index.pages, g->page_index.n_pages,
5942 sizeof(struct heap_page *), ptr_in_page_body_p);
5943 if (res == NULL) return false;
5944
5945 struct heap_page *page = *res;
5946 if (heap_page_in_global_empty_pages_pool(page->objspace, page)) return false;
5947 if (p < page->start) return false;
5948 if (p >= page->start + (page->total_slots * page->slot_size)) return false;
5949 if ((p - page->start) % page->slot_size != 0) return false;
5950 return true;
5951}
5952
5953void
5954rb_gc_impl_mark_maybe(void *objspace_ptr, VALUE obj)
5955{
5956 rb_objspace_t *objspace = objspace_ptr;
5957
5958 (void)VALGRIND_MAKE_MEM_DEFINED(&obj, sizeof(obj));
5959
5960 if (RB_UNLIKELY(objspace->flags.during_global_gc)
5961 ? gc_global_pointer_to_heap_p((void *)obj)
5962 : is_pointer_to_heap(objspace, (void *)obj)) {
5963 asan_unpoisoning_object(obj) {
5964 /* Garbage can live on the stack, so do not mark or pin */
5965 switch (BUILTIN_TYPE(obj)) {
5966 case T_ZOMBIE:
5967 case T_NONE:
5968 break;
5969 default:
5970 gc_mark_and_pin(objspace, obj);
5971 break;
5972 }
5973 }
5974 }
5975}
5976
5977static int
5978pin_value(st_data_t key, st_data_t value, st_data_t data)
5979{
5980 rb_gc_impl_mark_and_pin((void *)data, (VALUE)value);
5981
5982 return ST_CONTINUE;
5983}
5984
5985static inline void
5986gc_mark_set_parent_raw(rb_objspace_t *objspace, VALUE obj, bool old_p)
5987{
5988 asan_unpoison_memory_region(&objspace->rgengc.parent_object, sizeof(objspace->rgengc.parent_object), false);
5989 asan_unpoison_memory_region(&objspace->rgengc.parent_object_old_p, sizeof(objspace->rgengc.parent_object_old_p), false);
5990 objspace->rgengc.parent_object = obj;
5991 objspace->rgengc.parent_object_old_p = old_p;
5992}
5993
5994static inline void
5995gc_mark_set_parent(rb_objspace_t *objspace, VALUE obj)
5996{
5997 gc_mark_set_parent_raw(objspace, obj, RVALUE_OLD_P(objspace, obj));
5998}
5999
6000static inline void
6001gc_mark_set_parent_invalid(rb_objspace_t *objspace)
6002{
6003 asan_poison_memory_region(&objspace->rgengc.parent_object, sizeof(objspace->rgengc.parent_object));
6004 asan_poison_memory_region(&objspace->rgengc.parent_object_old_p, sizeof(objspace->rgengc.parent_object_old_p));
6005}
6006
6007static void pinned_roots_mark(rb_objspace_t *objspace, rb_heap_t *heap);
6008
6009static void
6010mark_roots(rb_objspace_t *objspace, const char **categoryp)
6011{
6012 VALUE objspace_guard = (VALUE)objspace;
6013#define MARK_CHECKPOINT(category) do { \
6014 if (categoryp) *categoryp = category; \
6015} while (0)
6016
6017 /* Pinning shareable objects and shrefs runs at the end of marking (gc_marks_finish),
6018 * not here: after the full walk it only has to touch what ordinary marking missed,
6019 * which is both cheap and a useful retention metric. */
6020
6021 MARK_CHECKPOINT("objspace");
6022 gc_mark_set_parent_raw(objspace, Qundef, false);
6023
6024 if (objspace->flags.during_global_gc) {
6025 /* Pin the finalizer tables of every objspace, zombies included.
6026 * (finalizer_table is a macro over the local "objspace".) */
6027 rb_objspace_t *const driver = objspace;
6028 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
6029 rb_objspace_t *objspace = global_objspace->global_gc.objspaces[i];
6030 if (finalizer_table != NULL) {
6031 st_foreach(finalizer_table, pin_value, (st_data_t)driver);
6032 }
6033 }
6034 }
6035 else if (finalizer_table != NULL) {
6036 st_foreach(finalizer_table, pin_value, (st_data_t)objspace);
6037 }
6038
6039 if (stress_to_class) rb_gc_mark(stress_to_class);
6040
6041 rb_gc_save_machine_context();
6042 rb_gc_mark_roots(objspace, categoryp);
6043 /* Keep this frame, including its saved registers, until root marking has
6044 * scanned the machine stack. */
6045 RB_GC_GUARD(objspace_guard);
6046 gc_mark_set_parent_invalid(objspace);
6047}
6048
6049static void
6050gc_mark_children(rb_objspace_t *objspace, VALUE obj)
6051{
6052 gc_mark_set_parent(objspace, obj);
6053 rb_gc_mark_children(objspace, obj);
6054 gc_mark_set_parent_invalid(objspace);
6055}
6056
6061static inline int
6062gc_mark_stacked_objects(rb_objspace_t *objspace, int incremental, size_t count)
6063{
6064 mark_stack_t *mstack = &objspace->mark_stack;
6065 VALUE obj;
6066 size_t marked_slots_at_the_beginning = objspace->marked_slots;
6067 size_t popped_count = 0;
6068
6069 while (pop_mark_stack(mstack, &obj)) {
6070 if (obj == Qundef) continue; /* skip */
6071
6072 if (RGENGC_CHECK_MODE && !RVALUE_MARKED(objspace, obj)) {
6073 rb_bug("gc_mark_stacked_objects: %s is not marked.", rb_obj_info(obj));
6074 }
6075 gc_mark_children(objspace, obj);
6076
6077 popped_count++;
6078
6079 if (incremental) {
6080 if (RGENGC_CHECK_MODE && !RVALUE_MARKING(objspace, obj)) {
6081 rb_bug("gc_mark_stacked_objects: incremental, but marking bit is 0");
6082 }
6083 CLEAR_IN_BITMAP(GET_HEAP_MARKING_BITS(obj), obj);
6084
6085 if (popped_count + (objspace->marked_slots - marked_slots_at_the_beginning) > count) {
6086 break;
6087 }
6088 }
6089 else {
6090 /* just ignore marking bits */
6091 }
6092 }
6093
6094 RUBY_DTRACE_GC_HOOK(MARK_STACKED_OBJECTS, popped_count);
6095
6096 if (RGENGC_CHECK_MODE >= 3) gc_verify_internal_consistency(objspace);
6097
6098 if (is_mark_stack_empty(mstack)) {
6099 shrink_stack_chunk_cache(mstack);
6100 return TRUE;
6101 }
6102 else {
6103 return FALSE;
6104 }
6105}
6106
6107static int
6108gc_mark_stacked_objects_incremental(rb_objspace_t *objspace, size_t count)
6109{
6110 return gc_mark_stacked_objects(objspace, TRUE, count);
6111}
6112
6113static int
6114gc_mark_stacked_objects_all(rb_objspace_t *objspace)
6115{
6116 return gc_mark_stacked_objects(objspace, FALSE, 0);
6117}
6118
6119#if RGENGC_CHECK_MODE >= 4
6120
6121#define MAKE_ROOTSIG(obj) (((VALUE)(obj) << 1) | 0x01)
6122#define IS_ROOTSIG(obj) ((VALUE)(obj) & 0x01)
6123#define GET_ROOTSIG(obj) ((const char *)((VALUE)(obj) >> 1))
6124
6125struct reflist {
6126 VALUE *list;
6127 int pos;
6128 int size;
6129};
6130
6131static struct reflist *
6132reflist_create(VALUE obj)
6133{
6134 struct reflist *refs = xmalloc(sizeof(struct reflist));
6135 refs->size = 1;
6136 refs->list = ALLOC_N(VALUE, refs->size);
6137 refs->list[0] = obj;
6138 refs->pos = 1;
6139 return refs;
6140}
6141
6142static void
6143reflist_destruct(struct reflist *refs)
6144{
6145 xfree(refs->list);
6146 xfree(refs);
6147}
6148
6149static void
6150reflist_add(struct reflist *refs, VALUE obj)
6151{
6152 if (refs->pos == refs->size) {
6153 refs->size *= 2;
6154 SIZED_REALLOC_N(refs->list, VALUE, refs->size, refs->size/2);
6155 }
6156
6157 refs->list[refs->pos++] = obj;
6158}
6159
6160static void
6161reflist_dump(struct reflist *refs)
6162{
6163 int i;
6164 for (i=0; i<refs->pos; i++) {
6165 VALUE obj = refs->list[i];
6166 if (IS_ROOTSIG(obj)) { /* root */
6167 fprintf(stderr, "<root@%s>", GET_ROOTSIG(obj));
6168 }
6169 else {
6170 fprintf(stderr, "<%s>", rb_obj_info(obj));
6171 }
6172 if (i+1 < refs->pos) fprintf(stderr, ", ");
6173 }
6174}
6175
6176static int
6177reflist_referred_from_machine_context(struct reflist *refs)
6178{
6179 int i;
6180 for (i=0; i<refs->pos; i++) {
6181 VALUE obj = refs->list[i];
6182 if (IS_ROOTSIG(obj) && strcmp(GET_ROOTSIG(obj), "machine_context") == 0) return 1;
6183 }
6184 return 0;
6185}
6186
6187struct allrefs {
6189 /* a -> obj1
6190 * b -> obj1
6191 * c -> obj1
6192 * c -> obj2
6193 * d -> obj3
6194 * #=> {obj1 => [a, b, c], obj2 => [c, d]}
6195 */
6196 struct st_table *references;
6197 const char *category;
6198 VALUE root_obj;
6200};
6201
6202static int
6203allrefs_add(struct allrefs *data, VALUE obj)
6204{
6205 struct reflist *refs;
6206 st_data_t r;
6207
6208 if (st_lookup(data->references, obj, &r)) {
6209 refs = (struct reflist *)r;
6210 reflist_add(refs, data->root_obj);
6211 return 0;
6212 }
6213 else {
6214 refs = reflist_create(data->root_obj);
6215 st_insert(data->references, obj, (st_data_t)refs);
6216 return 1;
6217 }
6218}
6219
6220static void
6221allrefs_i(VALUE obj, void *ptr)
6222{
6223 struct allrefs *data = (struct allrefs *)ptr;
6224
6225 if (allrefs_add(data, obj)) {
6226 push_mark_stack(&data->mark_stack, obj);
6227 }
6228}
6229
6230static void
6231allrefs_roots_i(VALUE obj, void *ptr)
6232{
6233 struct allrefs *data = (struct allrefs *)ptr;
6234 if (strlen(data->category) == 0) rb_bug("!!!");
6235 data->root_obj = MAKE_ROOTSIG(data->category);
6236
6237 if (allrefs_add(data, obj)) {
6238 push_mark_stack(&data->mark_stack, obj);
6239 }
6240}
6241#define PUSH_MARK_FUNC_DATA(v) do { \
6242 struct gc_mark_func_data_struct *prev_mark_func_data = GET_VM()->gc.mark_func_data; \
6243 GET_VM()->gc.mark_func_data = (v);
6244
6245#define POP_MARK_FUNC_DATA() GET_VM()->gc.mark_func_data = prev_mark_func_data;} while (0)
6246
6247static st_table *
6248objspace_allrefs(rb_objspace_t *objspace)
6249{
6250 struct allrefs data;
6251 struct gc_mark_func_data_struct mfd;
6252 VALUE obj;
6253 int prev_dont_gc = dont_gc_val();
6254 dont_gc_on();
6255
6256 data.objspace = objspace;
6257 data.references = st_init_numtable();
6258 init_mark_stack(&data.mark_stack);
6259
6260 mfd.mark_func = allrefs_roots_i;
6261 mfd.data = &data;
6262
6263 /* traverse root objects */
6264 PUSH_MARK_FUNC_DATA(&mfd);
6265 GET_VM()->gc.mark_func_data = &mfd;
6266 mark_roots(objspace, &data.category);
6267 POP_MARK_FUNC_DATA();
6268
6269 /* traverse rest objects reachable from root objects */
6270 while (pop_mark_stack(&data.mark_stack, &obj)) {
6271 rb_objspace_reachable_objects_from(data.root_obj = obj, allrefs_i, &data);
6272 }
6273 free_stack_chunks(&data.mark_stack);
6274
6275 dont_gc_set(prev_dont_gc);
6276 return data.references;
6277}
6278
6279static int
6280objspace_allrefs_destruct_i(st_data_t key, st_data_t value, st_data_t ptr)
6281{
6282 struct reflist *refs = (struct reflist *)value;
6283 reflist_destruct(refs);
6284 return ST_CONTINUE;
6285}
6286
6287static void
6288objspace_allrefs_destruct(struct st_table *refs)
6289{
6290 st_foreach(refs, objspace_allrefs_destruct_i, 0);
6291 st_free_table(refs);
6292}
6293
6294#if RGENGC_CHECK_MODE >= 5
6295static int
6296allrefs_dump_i(st_data_t k, st_data_t v, st_data_t ptr)
6297{
6298 VALUE obj = (VALUE)k;
6299 struct reflist *refs = (struct reflist *)v;
6300 fprintf(stderr, "[allrefs_dump_i] %s <- ", rb_obj_info(obj));
6301 reflist_dump(refs);
6302 fprintf(stderr, "\n");
6303 return ST_CONTINUE;
6304}
6305
6306static void
6307allrefs_dump(rb_objspace_t *objspace)
6308{
6309 VALUE size = objspace->rgengc.allrefs_table->num_entries;
6310 fprintf(stderr, "[all refs] (size: %"PRIuVALUE")\n", size);
6311 st_foreach(objspace->rgengc.allrefs_table, allrefs_dump_i, 0);
6312}
6313#endif
6314
6315static int
6316gc_check_after_marks_i(st_data_t k, st_data_t v, st_data_t ptr)
6317{
6318 VALUE obj = k;
6319 struct reflist *refs = (struct reflist *)v;
6321
6322 /* object should be marked or oldgen */
6323 if (!RVALUE_MARKED(objspace, obj)) {
6324 fprintf(stderr, "gc_check_after_marks_i: %s is not marked and not oldgen.\n", rb_obj_info(obj));
6325 fprintf(stderr, "gc_check_after_marks_i: %p is referred from ", (void *)obj);
6326 reflist_dump(refs);
6327
6328 if (reflist_referred_from_machine_context(refs)) {
6329 fprintf(stderr, " (marked from machine stack).\n");
6330 /* marked from machine context can be false positive */
6331 }
6332 else {
6333 objspace->rgengc.error_count++;
6334 fprintf(stderr, "\n");
6335 }
6336 }
6337 return ST_CONTINUE;
6338}
6339
6340static void
6341gc_marks_check(rb_objspace_t *objspace, st_foreach_callback_func *checker_func, const char *checker_name)
6342{
6343 MALLOC_COUNTERS_LOCK(objspace);
6344 struct gc_malloc_bytes saved_malloc = {
6345 .malloc = gc_counter_load_relaxed(&objspace->malloc_counters.counters.malloc),
6346 .free = gc_counter_load_relaxed(&objspace->malloc_counters.counters.free),
6347 .malloc_at_last_gc = gc_counter_load_relaxed(&objspace->malloc_counters.counters.malloc_at_last_gc),
6348 .free_at_last_gc = gc_counter_load_relaxed(&objspace->malloc_counters.counters.free_at_last_gc),
6349 };
6350#if RGENGC_ESTIMATE_OLDMALLOC
6351 struct gc_malloc_bytes saved_oldmalloc = {
6352 .malloc = gc_counter_load_relaxed(&objspace->malloc_counters.oldcounters.malloc),
6353 .free = gc_counter_load_relaxed(&objspace->malloc_counters.oldcounters.free),
6354 .malloc_at_last_gc = gc_counter_load_relaxed(&objspace->malloc_counters.oldcounters.malloc_at_last_gc),
6355 .free_at_last_gc = gc_counter_load_relaxed(&objspace->malloc_counters.oldcounters.free_at_last_gc),
6356 };
6357#endif
6358 MALLOC_COUNTERS_UNLOCK(objspace);
6359 VALUE already_disabled = rb_objspace_gc_disable(objspace);
6360
6361 objspace->rgengc.allrefs_table = objspace_allrefs(objspace);
6362
6363 if (checker_func) {
6364 st_foreach(objspace->rgengc.allrefs_table, checker_func, (st_data_t)objspace);
6365 }
6366
6367 if (objspace->rgengc.error_count > 0) {
6368#if RGENGC_CHECK_MODE >= 5
6369 allrefs_dump(objspace);
6370#endif
6371 if (checker_name) rb_bug("%s: GC has problem.", checker_name);
6372 }
6373
6374 objspace_allrefs_destruct(objspace->rgengc.allrefs_table);
6375 objspace->rgengc.allrefs_table = 0;
6376
6377 if (already_disabled == Qfalse) rb_objspace_gc_enable(objspace);
6378 MALLOC_COUNTERS_LOCK(objspace);
6379 gc_counter_store_release(&objspace->malloc_counters.counters.malloc, saved_malloc.malloc);
6380 gc_counter_store_release(&objspace->malloc_counters.counters.free, saved_malloc.free);
6381 gc_counter_store_release(&objspace->malloc_counters.counters.malloc_at_last_gc, saved_malloc.malloc_at_last_gc);
6382 gc_counter_store_release(&objspace->malloc_counters.counters.free_at_last_gc, saved_malloc.free_at_last_gc);
6383#if RGENGC_ESTIMATE_OLDMALLOC
6384 gc_counter_store_release(&objspace->malloc_counters.oldcounters.malloc, saved_oldmalloc.malloc);
6385 gc_counter_store_release(&objspace->malloc_counters.oldcounters.free, saved_oldmalloc.free);
6386 gc_counter_store_release(&objspace->malloc_counters.oldcounters.malloc_at_last_gc, saved_oldmalloc.malloc_at_last_gc);
6387 gc_counter_store_release(&objspace->malloc_counters.oldcounters.free_at_last_gc, saved_oldmalloc.free_at_last_gc);
6388#endif
6389 MALLOC_COUNTERS_UNLOCK(objspace);
6390}
6391#endif /* RGENGC_CHECK_MODE >= 4 */
6392
6395 /* True only while the world is stopped: a GC.verify holding the VM lock and barrier,
6396 * or a global GC. Cross-objspace checks (walking every objspace's pages) are sound
6397 * only then. */
6398 bool world_stopped;
6399 int err_count;
6400 size_t live_object_count;
6401 size_t zombie_object_count;
6402
6403 VALUE parent;
6404 bool parent_shareable;
6405 size_t old_object_count;
6406 size_t remembered_shady_count;
6407};
6408
6409
6410static void
6411check_generation_i(const VALUE child, void *ptr)
6412{
6414 const VALUE parent = data->parent;
6415
6416 if (RGENGC_CHECK_MODE) GC_ASSERT(RVALUE_OLD_P(data->objspace, parent));
6417
6418 /* A cross-objspace edge is kept alive by the shareable/shref mechanism and is not
6419 * tracked in this objspace's remembered set. */
6420 if (GET_HEAP_OBJSPACE(child) != data->objspace) return;
6421
6422 /* Once the process goes multi-Ractor, the shareable world is managed by pinning and
6423 * shrefs rather than by the remembered set: the pinned walk at the end of a mark
6424 * re-marks every shareable object (and its shref'd children) each local cycle, and a
6425 * global GC rebuilds the generation state. So the generational old->young invariant
6426 * does not hold when either endpoint is shareable: an old constcache, cc_table or
6427 * interned string pointing at a core class that is young after a global GC is the
6428 * typical false positive. That state outlives the return to a single Ractor until
6429 * the next major (an old shareable singleton class pointing at a young
6430 * attached_object, say), so the test uses rb_gc_ever_multi_ractor_p(), which stays
6431 * true forever once multiple Ractors existed. A program that never goes multi keeps
6432 * the strict check, and ASAN catches what is left. */
6433 if (rb_gc_ever_multi_ractor_p() &&
6434 (MARKED_IN_BITMAP(GET_HEAP_SHAREABLE_BITS(parent), parent) ||
6435 MARKED_IN_BITMAP(GET_HEAP_SHAREABLE_BITS(child), child))) {
6436 return;
6437 }
6438
6439 if (!RVALUE_OLD_P(data->objspace, child)) {
6440 /* A young shareable child is pinned and kept alive by the local GC (only a
6441 * global GC collects it), so it survives even when the old parent does not
6442 * remember it. It is outside the generational remembered set, so exclude it
6443 * from the old->young check. */
6444 if (!RVALUE_REMEMBERED(data->objspace, parent) &&
6445 !RVALUE_REMEMBERED(data->objspace, child) &&
6446 !RVALUE_UNCOLLECTIBLE(data->objspace, child) &&
6448 fprintf(stderr, "verify_internal_consistency_reachable_i: WB miss (O->Y) %s -> %s\n", rb_obj_info(parent), rb_obj_info(child));
6449 data->err_count++;
6450 }
6451 }
6452}
6453
6454static void
6455check_color_i(const VALUE child, void *ptr)
6456{
6458 const VALUE parent = data->parent;
6459
6460 if (!RVALUE_WB_UNPROTECTED(data->objspace, parent) && RVALUE_WHITE_P(data->objspace, child)) {
6461 fprintf(stderr, "verify_internal_consistency_reachable_i: WB miss (B->W) - %s -> %s\n",
6462 rb_obj_info(parent), rb_obj_info(child));
6463 data->err_count++;
6464 }
6465}
6466
6467static void
6468check_children_i(const VALUE child, void *ptr)
6469{
6471
6472 /* Fast path: a child in this objspace (99.99% of all edges). */
6473 if (RB_LIKELY(is_pointer_to_heap(data->objspace, (void *)child))) {
6474 if (check_rvalue_consistency_force(data->objspace, child, FALSE) != 0) {
6475 fprintf(stderr, "check_children_i: %s has error (referenced from %s)\n",
6476 rb_obj_info(child), rb_obj_info(data->parent));
6477 data->err_count++;
6478 }
6479 return;
6480 }
6481
6482 /* The remaining cross-objspace check (verify_pointer_in_any_heap_p) walks every
6483 * objspace's pages, sound only with the world stopped: mid-local-GC other Ractors
6484 * change page structures concurrently. The next world-stopped verify re-checks. */
6485 if (!data->world_stopped) return;
6486
6487 /* A non-heap child reaches this callback only when a stale field was followed by a
6488 * plain rb_gc_mark (the dmark of a live but unreachable wrapper, say). Report it and
6489 * keep going rather than aborting. */
6490 if (!verify_pointer_in_any_heap_p((void *)child)) {
6491 /* The graph is in flux mid-merge, so a transient non-heap edge is expected; it
6492 * is re-checked after the merge. */
6493 if (global_objspace->during_absorb) return;
6494 fprintf(stderr, "VERIFY-NOTE: non-heap child %p (from %s)\n",
6495 (void *)child, rb_obj_info(data->parent));
6496 return;
6497 }
6498
6499 if (GET_HEAP_OBJSPACE(child) != data->objspace) {
6500 /* A legal cross-objspace edge either starts at a shareable object or is recorded
6501 * in the child's shref bit (an in-flight send or move payload kept alive across
6502 * its owner's local GC; root_scope_check_i honours the same record). An
6503 * unshareable parent holding an unrecorded foreign unshareable child would be
6504 * invisible to both local GCs. The exception is a box's top_self, which every
6505 * thread's th->top_self points at and which is VM-permanent. Skipped during a
6506 * global GC: it clears every shref bit, so the shref exemption would not fire,
6507 * and its unified exact stop-the-world mark makes the invariant itself moot. */
6508 if (!data->parent_shareable &&
6509 child != rb_gc_vm_top_self() &&
6510 !MARKED_IN_BITMAP(GET_HEAP_SHAREABLE_BITS(child), child) &&
6511 !MARKED_IN_BITMAP(GET_HEAP_SHREF_BITS(child), child) &&
6512 !rb_gc_impl_during_global_gc_p(data->objspace) &&
6513 !global_objspace->during_absorb) {
6514 fprintf(stderr, "check_children_i: containment violation: "
6515 "unshareable %s (objspace %p) -> foreign unshareable %s (objspace %p)\n",
6516 rb_obj_info(data->parent), (void *)data->objspace,
6517 rb_obj_info(child), (void *)GET_HEAP_OBJSPACE(child));
6518 data->err_count++;
6519 }
6520
6521 /* The remaining per-objspace sanity rules belong to the owner. */
6522 return;
6523 }
6524}
6525
6526/* Whether a heap slot currently holds a live object. Returns false for empty
6527 * (T_NONE), moved (T_MOVED), and zombie (T_ZOMBIE) slots, and for garbage
6528 * objects about to be swept. */
6529static bool
6530gc_slot_live_object_p(rb_objspace_t *objspace, VALUE obj)
6531{
6532 switch (BUILTIN_TYPE(obj)) {
6533 case T_NONE:
6534 case T_MOVED:
6535 case T_ZOMBIE:
6536 return false;
6537 default:
6538 return !rb_gc_impl_garbage_object_p(objspace, obj);
6539 }
6540}
6541
6542/* Verifier only: does ptr point at a live slot in any objspace? The caller holds the VM
6543 * lock and the barrier, so page_index is stable. */
6544static bool
6545verify_pointer_in_any_heap_p(const void *ptr)
6546{
6547 return gc_global_pointer_to_heap_p(ptr);
6548}
6549
6550/* An exact root of the calling Ractor may only point at a shareable object, its own
6551 * objspace, or an in-flight payload with a recorded shref. Exempt: the conservative
6552 * machine scan (stale slots) and the VM-global containers that are cross-rooted by
6553 * design (every objspace scans them; the marker skips foreign entries). */
6554static void
6555root_scope_check_i(const char *category, VALUE obj, void *ptr)
6556{
6557 struct verify_internal_consistency_struct *data = ptr;
6558
6559 if (RB_SPECIAL_CONST_P(obj)) return;
6560 /* This check walks every objspace (verify_pointer_in_any_heap_p), so it is sound
6561 * only with the world stopped; a mid-local-GC verify races with other Ractors'
6562 * lock-free allocation. */
6563 if (!data->world_stopped) return;
6564 /* Mid-merge the VM-global root tables still point at the unmerged source (transient
6565 * non-heap or foreign roots); re-checked after the merge. */
6566 if (global_objspace->during_absorb) return;
6567 if (strcmp(category, "machine_context") == 0 ||
6568 strcmp(category, "vm_registered_objects") == 0 ||
6569 strcmp(category, "end_proc") == 0 ||
6570 strcmp(category, "trap_list") == 0 ||
6571 /* Every Ractor's root scan walks the one VM-wide registered-globals list (a slot
6572 * can hold another objspace's value); rb_gc_mark_maybe filters to its own
6573 * objspace, so a foreign entry here is by design, not a leak. */
6574 strcmp(category, "registered_globals") == 0) {
6575 return;
6576 }
6577
6578 if (!verify_pointer_in_any_heap_p((void *)obj)) {
6579 fprintf(stderr, "root_scope_check_i: root category \"%s\" names a non-heap pointer %p\n",
6580 category, (void *)obj);
6581 data->err_count++;
6582 return;
6583 }
6584
6585 if (GET_HEAP_OBJSPACE(obj) == data->objspace) return;
6586 if (MARKED_IN_BITMAP(GET_HEAP_SHAREABLE_BITS(obj), obj)) return;
6587 if (MARKED_IN_BITMAP(GET_HEAP_SHREF_BITS(obj), obj)) return;
6588 if (obj == rb_gc_vm_top_self()) return; /* VM-permanent (see check_children_i) */
6589
6590 fprintf(stderr, "root_scope_check_i: root category \"%s\" names a foreign "
6591 "unshareable without a shref record: %s (owner %p, self %p)\n",
6592 category, rb_obj_info(obj),
6593 (void *)GET_HEAP_OBJSPACE(obj), (void *)data->objspace);
6594 data->err_count++;
6595}
6596
6597static int
6598verify_internal_consistency_i(void *page_start, void *page_end, size_t stride,
6600{
6601 VALUE obj;
6602 rb_objspace_t *objspace = data->objspace;
6603
6604 for (obj = (VALUE)page_start; obj != (VALUE)page_end; obj += stride) {
6605 asan_unpoisoning_object(obj) {
6606 bool sh_bit = MARKED_IN_BITMAP(GET_HEAP_SHAREABLE_BITS(obj), obj) != 0;
6607 bool sr_bit = MARKED_IN_BITMAP(GET_HEAP_SHREF_BITS(obj), obj) != 0;
6608
6609 if (gc_slot_live_object_p(objspace, obj)) {
6610 /* count objects */
6611 data->live_object_count++;
6612 data->parent = obj;
6613 data->parent_shareable = sh_bit;
6614
6615 /* Bitmap invariants: a page's shareable bit matches FL_SHAREABLE
6616 * exactly, and a shref record only ever points at an unshareable
6617 * object. */
6618 if (sh_bit != !!RB_FL_TEST_RAW(obj, RUBY_FL_SHAREABLE)) {
6619 fprintf(stderr, "verify_internal_consistency_i: shareable bit %d "
6620 "disagrees with FL_SHAREABLE on %s\n", (int)sh_bit, rb_obj_info(obj));
6621 data->err_count++;
6622 }
6623 if (sr_bit && sh_bit) {
6624 fprintf(stderr, "verify_internal_consistency_i: shref bit on a shareable: %s\n",
6625 rb_obj_info(obj));
6626 data->err_count++;
6627 }
6628
6629 /* Normally, we don't expect T_MOVED objects to be in the heap.
6630 * But they can stay alive on the stack, */
6631 if (!gc_object_moved_p(objspace, obj)) {
6632 /* moved slots don't have children */
6633 rb_objspace_reachable_objects_from(obj, check_children_i, (void *)data);
6634 }
6635
6636 /* check health of children */
6637 if (RVALUE_OLD_P(objspace, obj)) data->old_object_count++;
6638 if (RVALUE_WB_UNPROTECTED(objspace, obj) && RVALUE_UNCOLLECTIBLE(objspace, obj)) data->remembered_shady_count++;
6639
6640 if (!is_marking(objspace) && RVALUE_OLD_P(objspace, obj)) {
6641 /* reachable objects from an oldgen object should be old or (young with remember) */
6642 data->parent = obj;
6643 rb_objspace_reachable_objects_from(obj, check_generation_i, (void *)data);
6644 }
6645
6646 if (!is_marking(objspace) && rb_gc_obj_shareable_p(obj)) {
6647 rb_gc_verify_shareable(obj);
6648 }
6649
6650 if (is_incremental_marking(objspace)) {
6651 if (RVALUE_BLACK_P(objspace, obj)) {
6652 /* reachable objects from black objects should be black or grey objects */
6653 data->parent = obj;
6654 rb_objspace_reachable_objects_from(obj, check_color_i, (void *)data);
6655 }
6656 }
6657 }
6658 else {
6659 /* A freed slot must not carry its old pin bit into the next object born
6660 * there (a dead object not swept yet legitimately keeps it until the
6661 * sweep arrives). */
6662 if (BUILTIN_TYPE(obj) == T_NONE && (sh_bit || sr_bit)) {
6663 fprintf(stderr, "verify_internal_consistency_i: T_NONE slot carries "
6664 "shareable=%d shref=%d bits\n", (int)sh_bit, (int)sr_bit);
6665 data->err_count++;
6666 }
6667
6668 if (BUILTIN_TYPE(obj) == T_ZOMBIE) {
6669 data->zombie_object_count++;
6670
6671 if ((RBASIC(obj)->flags & ~ZOMBIE_OBJ_KEPT_FLAGS) != T_ZOMBIE) {
6672 fprintf(stderr, "verify_internal_consistency_i: T_ZOMBIE has extra flags set: %s\n",
6673 rb_obj_info(obj));
6674 data->err_count++;
6675 }
6676
6677 if (!!FL_TEST(obj, FL_FINALIZE) != !!st_is_member(finalizer_table, obj)) {
6678 fprintf(stderr, "verify_internal_consistency_i: FL_FINALIZE %s but %s finalizer_table: %s\n",
6679 FL_TEST(obj, FL_FINALIZE) ? "set" : "not set", st_is_member(finalizer_table, obj) ? "in" : "not in",
6680 rb_obj_info(obj));
6681 data->err_count++;
6682 }
6683 }
6684 }
6685 }
6686 }
6687
6688 return 0;
6689}
6690
6691static int
6692gc_verify_heap_page(rb_objspace_t *objspace, struct heap_page *page, VALUE obj)
6693{
6694 unsigned int has_remembered_shady = FALSE;
6695 unsigned int has_remembered_old = FALSE;
6696 int remembered_old_objects = 0;
6697 int free_objects = 0;
6698 int zombie_objects = 0;
6699
6700 short slot_size = page->slot_size;
6701 uintptr_t start = (uintptr_t)page->start;
6702 uintptr_t end = start + page->total_slots * slot_size;
6703
6704 for (uintptr_t ptr = start; ptr < end; ptr += slot_size) {
6705 VALUE val = (VALUE)ptr;
6706 asan_unpoisoning_object(val) {
6707 enum ruby_value_type type = BUILTIN_TYPE(val);
6708
6709 if (type == T_NONE) free_objects++;
6710 if (type == T_ZOMBIE) zombie_objects++;
6711 if (RVALUE_PAGE_UNCOLLECTIBLE(page, val) && RVALUE_PAGE_WB_UNPROTECTED(page, val)) {
6712 has_remembered_shady = TRUE;
6713 }
6714 if (RVALUE_PAGE_MARKING(page, val)) {
6715 has_remembered_old = TRUE;
6716 remembered_old_objects++;
6717 }
6718 }
6719 }
6720
6721 if (!is_incremental_marking(objspace) &&
6722 page->flags.has_remembered_objects == FALSE && has_remembered_old == TRUE) {
6723
6724 for (uintptr_t ptr = start; ptr < end; ptr += slot_size) {
6725 VALUE val = (VALUE)ptr;
6726 if (RVALUE_PAGE_MARKING(page, val)) {
6727 fprintf(stderr, "marking -> %s\n", rb_obj_info(val));
6728 }
6729 }
6730 rb_bug("page %p's has_remembered_objects should be false, but there are remembered old objects (%d). %s",
6731 (void *)page, remembered_old_objects, obj ? rb_obj_info(obj) : "");
6732 }
6733
6734 if (page->flags.has_uncollectible_wb_unprotected_objects == FALSE && has_remembered_shady == TRUE) {
6735 rb_bug("page %p's has_remembered_shady should be false, but there are remembered shady objects. %s",
6736 (void *)page, obj ? rb_obj_info(obj) : "");
6737 }
6738
6739 if (0) {
6740 /* free_slots may not equal to free_objects */
6741 if (page->free_slots != free_objects) {
6742 rb_bug("page %p's free_slots should be %d, but %d", (void *)page, page->free_slots, free_objects);
6743 }
6744 }
6745 if (page->final_slots != zombie_objects) {
6746 rb_bug("page %p's final_slots should be %d, but %d", (void *)page, page->final_slots, zombie_objects);
6747 }
6748
6749 return remembered_old_objects;
6750}
6751
6752static int
6753gc_verify_heap_pages_(rb_objspace_t *objspace, struct ccan_list_head *head)
6754{
6755 int remembered_old_objects = 0;
6756 struct heap_page *page = 0;
6757
6758 ccan_list_for_each(head, page, page_node) {
6759 asan_unlock_freelist(page);
6760 struct free_region *region = page->free_region;
6761 while (region) {
6762 VALUE vp = (VALUE)region;
6763 rb_asan_unpoison_object(vp, false);
6764 if (BUILTIN_TYPE(vp) != T_NONE) {
6765 fprintf(stderr, "free region head expected to be T_NONE but was: %s\n", rb_obj_info(vp));
6766 }
6767 struct free_region *next = region->next;
6768 rb_asan_poison_object(vp);
6769 region = next;
6770 }
6771 asan_lock_freelist(page);
6772
6773 if (page->flags.has_remembered_objects == FALSE) {
6774 remembered_old_objects += gc_verify_heap_page(objspace, page, Qfalse);
6775 }
6776 }
6777
6778 return remembered_old_objects;
6779}
6780
6781static int
6782gc_verify_heap_pages(rb_objspace_t *objspace)
6783{
6784 int remembered_old_objects = 0;
6785 for (int i = 0; i < HEAP_COUNT; i++) {
6786 remembered_old_objects += gc_verify_heap_pages_(objspace, &((&heaps[i])->pages));
6787 }
6788 return remembered_old_objects;
6789}
6790
6791static void
6792gc_verify_internal_consistency_(rb_objspace_t *objspace, bool world_stopped)
6793{
6794 struct verify_internal_consistency_struct data = {0};
6795
6796 data.objspace = objspace;
6797 data.world_stopped = world_stopped;
6798 gc_report(5, objspace, "gc_verify_internal_consistency: start\n");
6799
6800 /* check relations */
6801 for (size_t i = 0; i < rb_darray_size(objspace->heap_pages.sorted); i++) {
6802 struct heap_page *page = rb_darray_get(objspace->heap_pages.sorted, i);
6803 short slot_size = page->slot_size;
6804
6805 uintptr_t start = (uintptr_t)page->start;
6806 uintptr_t end = start + page->total_slots * slot_size;
6807
6808 verify_internal_consistency_i((void *)start, (void *)end, slot_size, &data);
6809 }
6810
6811 /* Check the calling Ractor's root scoping (only when verifying the current
6812 * objspace). Skipped during a global GC, which deliberately spans every Ractor's
6813 * roots and legitimately reaches foreign objects: containment does not apply. */
6814 if (!rb_gc_single_objspace_p() && objspace == rb_gc_get_objspace() &&
6815 !rb_gc_impl_during_global_gc_p(objspace)) {
6816 rb_objspace_reachable_objects_from_root(root_scope_check_i, &data);
6817 }
6818
6819 if (data.err_count != 0) {
6820#if RGENGC_CHECK_MODE >= 5
6821 objspace->rgengc.error_count = data.err_count;
6822 gc_marks_check(objspace, NULL, NULL);
6823 allrefs_dump(objspace);
6824#endif
6825 rb_bug("gc_verify_internal_consistency: found internal inconsistency.");
6826 }
6827
6828 /* check heap_page status */
6829 gc_verify_heap_pages(objspace);
6830
6831 /* check counters */
6832
6833 if (!is_lazy_sweeping(objspace) &&
6834 !finalizing &&
6835 !rb_gc_multi_ractor_p()) {
6836 if (objspace_live_slots(objspace) != data.live_object_count) {
6837 fprintf(stderr, "heap_pages_final_slots: %"PRIdSIZE", total_freed_objects: %"PRIdSIZE"\n",
6838 total_final_slots_count(objspace), total_freed_objects(objspace));
6839 rb_bug("inconsistent live slot number: expect %"PRIuSIZE", but %"PRIuSIZE".",
6840 objspace_live_slots(objspace), data.live_object_count);
6841 }
6842 }
6843
6844 if (!is_marking(objspace)) {
6845 if (objspace->rgengc.old_objects != data.old_object_count) {
6846 rb_bug("inconsistent old slot number: expect %"PRIuSIZE", but %"PRIuSIZE".",
6847 objspace->rgengc.old_objects, data.old_object_count);
6848 }
6849 if (objspace->rgengc.uncollectible_wb_unprotected_objects != data.remembered_shady_count) {
6850 rb_bug("inconsistent number of wb unprotected objects: expect %"PRIuSIZE", but %"PRIuSIZE".",
6851 objspace->rgengc.uncollectible_wb_unprotected_objects, data.remembered_shady_count);
6852 }
6853 }
6854
6855 if (!finalizing) {
6856 size_t list_count = 0;
6857
6858 {
6859 VALUE z = heap_pages_deferred_final;
6860 while (z) {
6861 list_count++;
6862 z = RZOMBIE(z)->next;
6863 }
6864 }
6865
6866 if (total_final_slots_count(objspace) != data.zombie_object_count ||
6867 total_final_slots_count(objspace) != list_count) {
6868
6869 rb_bug("inconsistent finalizing object count:\n"
6870 " expect %"PRIuSIZE"\n"
6871 " but %"PRIuSIZE" zombies\n"
6872 " heap_pages_deferred_final list has %"PRIuSIZE" items.",
6873 total_final_slots_count(objspace),
6874 data.zombie_object_count,
6875 list_count);
6876 }
6877 }
6878
6879 gc_report(5, objspace, "gc_verify_internal_consistency: OK\n");
6880}
6881
6882/* The `during_gc` macro expands a bare identifier to `objspace->flags.during_gc`, so a
6883 * foreign objspace's flag cannot be written directly; these helpers reach it through the
6884 * `objspace` argument. */
6885static inline unsigned int
6886gc_during_gc_get(const rb_objspace_t *objspace)
6887{
6888 return during_gc;
6889}
6890
6891static inline void
6892gc_during_gc_set(rb_objspace_t *objspace, unsigned int v)
6893{
6894 during_gc = v;
6895}
6896
6897/* Run the check with during_gc cleared in both the verified objspace and the current
6898 * Ractor's: rb_objspace_reachable_objects_from() decides on rb_gc_get_objspace(), and
6899 * under a global GC the driver verifies foreign objspaces, so the driver's during_gc
6900 * needs clearing too (a no-op when cur == objspace). */
6901static void
6902gc_verify_internal_consistency_body(rb_objspace_t *objspace, bool world_stopped)
6903{
6904 const unsigned int prev_during_gc = during_gc;
6905 during_gc = FALSE; // stop gc here
6906
6907 rb_objspace_t *const cur = rb_gc_get_objspace();
6908 const unsigned int prev_cur_during_gc = (cur != objspace) ? gc_during_gc_get(cur) : 0;
6909 if (cur != objspace) gc_during_gc_set(cur, FALSE);
6910 {
6911 gc_verify_internal_consistency_(objspace, world_stopped);
6912 }
6913 if (cur != objspace) gc_during_gc_set(cur, prev_cur_during_gc);
6914 during_gc = prev_during_gc;
6915}
6916
6917static void
6918gc_verify_internal_consistency(void *objspace_ptr)
6919{
6920 rb_objspace_t *objspace = objspace_ptr;
6921
6922 /* Called mid-GC, take neither the VM lock nor the barrier: waiting would join a
6923 * pending global barrier mid-collection (a GC must never take the VM lock) and let
6924 * the global GC sweep the heap this mark is walking. The barrier is unnecessary
6925 * anyway; the objspace is single-writer, this verify runs on its owner thread, and
6926 * the global driver that sets during_gc everywhere already holds both. */
6927 if (during_gc) {
6928 /* The world is stopped only when the global GC's driver runs this while holding
6929 * the barrier; a non-main Ractor's local GC does not stop other Ractors. */
6930 gc_verify_internal_consistency_body(objspace, rb_gc_impl_during_global_gc_p(objspace));
6931 return;
6932 }
6933
6934 unsigned int lev = RB_GC_VM_LOCK();
6935 {
6936 rb_gc_vm_barrier(); // stop other ractors
6937 gc_verify_internal_consistency_body(objspace, true); // holding the barrier, so walking every objspace is sound
6938 }
6939 RB_GC_VM_UNLOCK(lev);
6940}
6941
6942static void
6943heap_move_pooled_pages_to_free_pages(rb_heap_t *heap)
6944{
6945 if (heap->pooled_pages) {
6946 if (heap->free_pages) {
6947 struct heap_page *free_pages_tail = heap->free_pages;
6948 while (free_pages_tail->free_next) {
6949 free_pages_tail = free_pages_tail->free_next;
6950 }
6951 free_pages_tail->free_next = heap->pooled_pages;
6952 }
6953 else {
6954 heap->free_pages = heap->pooled_pages;
6955 }
6956
6957 heap->pooled_pages = NULL;
6958 }
6959}
6960
6961static int
6962gc_remember_unprotected(rb_objspace_t *objspace, VALUE obj)
6963{
6964 struct heap_page *page = GET_HEAP_PAGE(obj);
6965 bits_t *uncollectible_bits = &page->uncollectible_bits[0];
6966
6967 if (!MARKED_IN_BITMAP(uncollectible_bits, obj)) {
6968 page->flags.has_uncollectible_wb_unprotected_objects = TRUE;
6969 MARK_IN_BITMAP(uncollectible_bits, obj);
6970 /* Like RVALUE_PAGE_OLD_UNCOLLECTIBLE_SET, count it in the object's own objspace. */
6971 page->objspace->rgengc.uncollectible_wb_unprotected_objects++;
6972
6973#if RGENGC_PROFILE > 0
6974 objspace->profile.total_remembered_shady_object_count++;
6975#if RGENGC_PROFILE >= 2
6976 objspace->profile.remembered_shady_object_count_types[BUILTIN_TYPE(obj)]++;
6977#endif
6978#endif
6979 return TRUE;
6980 }
6981 else {
6982 return FALSE;
6983 }
6984}
6985
6986static inline void
6987gc_marks_wb_unprotected_objects_plane(rb_objspace_t *objspace, uintptr_t p, bits_t bits, short slot_size)
6988{
6989 if (bits) {
6990 do {
6991 if (bits & 1) {
6992 gc_report(2, objspace, "gc_marks_wb_unprotected_objects: marked shady: %s\n", rb_obj_info((VALUE)p));
6993 GC_ASSERT(RVALUE_WB_UNPROTECTED(objspace, (VALUE)p));
6994 GC_ASSERT(RVALUE_MARKED(objspace, (VALUE)p));
6995 gc_mark_children(objspace, (VALUE)p);
6996 }
6997 p += slot_size;
6998 bits >>= 1;
6999 } while (bits);
7000 }
7001}
7002
7003static void
7004gc_marks_wb_unprotected_objects(rb_objspace_t *objspace, rb_heap_t *heap)
7005{
7006 struct heap_page *page = 0;
7007
7008 ccan_list_for_each(&heap->pages, page, page_node) {
7009 bits_t *mark_bits = page->mark_bits;
7010 bits_t *wbun_bits = page->wb_unprotected_bits;
7011 uintptr_t p = page->start;
7012 short slot_size = page->slot_size;
7013 int total_slots = page->total_slots;
7014 int bitmap_plane_count = CEILDIV(total_slots, BITS_BITLENGTH);
7015 size_t j;
7016
7017 for (j=0; j<(size_t)bitmap_plane_count; j++) {
7018 bits_t bits = mark_bits[j] & wbun_bits[j];
7019 gc_marks_wb_unprotected_objects_plane(objspace, p, bits, slot_size);
7020 p += BITS_BITLENGTH * slot_size;
7021 }
7022 }
7023
7024 gc_mark_stacked_objects_all(objspace);
7025}
7026
7027void
7028rb_gc_impl_declare_weak_references(void *objspace_ptr, VALUE obj)
7029{
7031}
7032
7033bool
7034rb_gc_impl_handle_weak_references_alive_p(void *objspace_ptr, VALUE obj)
7035{
7036 rb_objspace_t *objspace = objspace_ptr;
7037
7038 /* A local GC cannot decide a foreign object's liveness, so treat it as live; its
7039 * owner or the global GC decides (a global GC's unified mark is exact). */
7040 if (gc_skip_foreign_object_p(objspace, obj)) return true;
7041
7042 bool marked = RVALUE_MARKED(objspace, obj);
7043
7044 if (marked) {
7045 rgengc_check_relation(objspace, obj);
7046 }
7047
7048 return marked;
7049}
7050
7051static void
7052gc_update_weak_references(rb_objspace_t *objspace)
7053{
7054 VALUE *obj_ptr;
7055 rb_darray_foreach(objspace->weak_references, i, obj_ptr) {
7056 gc_mark_set_parent(objspace, *obj_ptr);
7057 rb_gc_handle_weak_references(*obj_ptr);
7058 gc_mark_set_parent_invalid(objspace);
7059 }
7060
7061 size_t capa = rb_darray_capa(objspace->weak_references);
7062 size_t size = rb_darray_size(objspace->weak_references);
7063
7064 objspace->profile.weak_references_count = size;
7065
7066 rb_darray_clear(objspace->weak_references);
7067
7068 /* If the darray has capacity for more than four times the amount used, we
7069 * shrink it down to half of that capacity. */
7070 if (capa > size * 4) {
7071 rb_darray_resize_capa_without_gc(&objspace->weak_references, size * 2);
7072 }
7073}
7074
7075static void
7076gc_marks_finish(rb_objspace_t *objspace)
7077{
7078 /* finish incremental GC */
7079 if (is_incremental_marking(objspace)) {
7080 if (RGENGC_CHECK_MODE && is_mark_stack_empty(&objspace->mark_stack) == 0) {
7081 rb_bug("gc_marks_finish: mark stack is not empty (%"PRIdSIZE").",
7082 mark_stack_size(&objspace->mark_stack));
7083 }
7084
7085 mark_roots(objspace, NULL);
7086 while (gc_mark_stacked_objects_incremental(objspace, INT_MAX) == false);
7087
7088#if RGENGC_CHECK_MODE >= 2
7089 if (gc_verify_heap_pages(objspace) != 0) {
7090 rb_bug("gc_marks_finish (incremental): there are remembered old objects.");
7091 }
7092#endif
7093
7094 objspace->flags.during_incremental_marking = FALSE;
7095 /* check children of all marked wb-unprotected objects */
7096 for (int i = 0; i < HEAP_COUNT; i++) {
7097 gc_marks_wb_unprotected_objects(objspace, &heaps[i]);
7098 }
7099 }
7100
7101 /* Pin the shareable objects and shrefs ordinary marking missed: a local GC must free
7102 * neither (another objspace may hold them). Running after the full walk makes the
7103 * pin count a retention metric: an upper bound on the garbage only a global GC can
7104 * reclaim. A global GC's exact mark does not pin. (The allrefs comparison of
7105 * RGENGC_CHECK_MODE >= 4 does not model these pins; it reports false positives.) */
7106 objspace->last_cycle_pinned = 0;
7107 if (!rb_gc_single_objspace_p() && !objspace->flags.during_global_gc) {
7108 objspace->last_cycle_pinned = 1;
7109 gc_mark_set_parent_raw(objspace, Qundef, false);
7110 for (int i = 0; i < HEAP_COUNT; i++) {
7111 pinned_roots_mark(objspace, &heaps[i]);
7112 }
7113 /* And everything they keep alive. */
7114 gc_mark_stacked_objects_all(objspace);
7115 }
7116
7117 gc_update_weak_references(objspace);
7118
7119#if RGENGC_CHECK_MODE >= 4
7120 during_gc = FALSE;
7121 gc_marks_check(objspace, gc_check_after_marks_i, "after_marks");
7122 during_gc = TRUE;
7123#endif
7124
7125 {
7126 const unsigned long ractor_cnt = rb_gc_vm_ractor_count();
7127 const unsigned long r_mul = ractor_cnt > 8 ? 8 : ractor_cnt; // upto 8
7128
7129 size_t total_slots = objspace_available_slots(objspace);
7130 size_t sweep_slots = total_slots - objspace->marked_slots; /* will be swept slots */
7131 size_t max_free_slots = (size_t)(total_slots * gc_params.heap_free_slots_max_ratio);
7132 size_t min_free_slots = (size_t)(total_slots * gc_params.heap_free_slots_min_ratio);
7133 if (min_free_slots < gc_params.heap_free_slots * r_mul) {
7134 min_free_slots = gc_params.heap_free_slots * r_mul;
7135 }
7136
7137 int full_marking = is_full_marking(objspace);
7138
7139 GC_ASSERT(objspace_available_slots(objspace) >= objspace->marked_slots);
7140
7141 /* Setup freeable slots. */
7142 size_t total_init_slots = 0;
7143 for (int i = 0; i < HEAP_COUNT; i++) {
7144 total_init_slots += (gc_params.heap_init_bytes / heaps[i].slot_size) * r_mul;
7145 }
7146
7147 if (max_free_slots < total_init_slots) {
7148 max_free_slots = total_init_slots;
7149 }
7150
7151 /* Approximate freeable pages using the average slots-per-pages across all heaps */
7152 if (sweep_slots > max_free_slots) {
7153 size_t excess_slots = sweep_slots - max_free_slots;
7154 size_t total_heap_pages = heap_eden_total_pages(objspace);
7155 heap_pages_freeable_pages = total_heap_pages > 0
7156 ? excess_slots * total_heap_pages / total_slots
7157 : 0;
7158 }
7159 else {
7160 heap_pages_freeable_pages = 0;
7161 }
7162
7163 if (objspace->heap_pages.allocatable_bytes == 0 && sweep_slots < min_free_slots) {
7164 if (!full_marking && sweep_slots < min_free_slots * 7 / 8) {
7165 if (objspace->profile.count - objspace->rgengc.last_major_gc < RVALUE_OLD_AGE) {
7166 full_marking = TRUE;
7167 }
7168 else {
7169 gc_report(1, objspace, "gc_marks_finish: next is full GC!!)\n");
7170 gc_needs_major_flags |= GPR_FLAG_MAJOR_BY_NOFREE;
7171 }
7172 }
7173
7174 if (full_marking) {
7175 heap_allocatable_bytes_expand(objspace, NULL, sweep_slots, total_slots, heaps[0].slot_size);
7176 }
7177 }
7178
7179 if (full_marking) {
7180 /* See the comment about RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR */
7181 const double r = gc_params.oldobject_limit_factor;
7182 objspace->rgengc.uncollectible_wb_unprotected_objects_limit = MAX(
7183 (size_t)(objspace->rgengc.uncollectible_wb_unprotected_objects * r),
7184 (size_t)(objspace->rgengc.old_objects * gc_params.uncollectible_wb_unprotected_objects_limit_ratio)
7185 );
7186 objspace->rgengc.old_objects_limit = (size_t)(objspace->rgengc.old_objects * r);
7187 }
7188
7189 if (objspace->rgengc.uncollectible_wb_unprotected_objects > objspace->rgengc.uncollectible_wb_unprotected_objects_limit) {
7190 gc_needs_major_flags |= GPR_FLAG_MAJOR_BY_SHADY;
7191 }
7192 if (objspace->rgengc.old_objects > objspace->rgengc.old_objects_limit) {
7193 gc_needs_major_flags |= GPR_FLAG_MAJOR_BY_OLDGEN;
7194 }
7195
7196 gc_report(1, objspace, "gc_marks_finish (marks %"PRIdSIZE" objects, "
7197 "old %"PRIdSIZE" objects, total %"PRIdSIZE" slots, "
7198 "sweep %"PRIdSIZE" slots, allocatable %"PRIdSIZE" bytes, next GC: %s)\n",
7199 objspace->marked_slots, objspace->rgengc.old_objects, objspace_available_slots(objspace), sweep_slots, objspace->heap_pages.allocatable_bytes,
7200 gc_needs_major_flags ? "major" : "minor");
7201 }
7202
7203 // TODO: refactor so we don't need to call this
7204 rb_ractor_finish_marking(is_full_marking(objspace));
7205
7207}
7208
7209static bool
7210gc_compact_heap_cursors_met_p(rb_heap_t *heap)
7211{
7212 return heap->sweeping_page == heap->compact_cursor;
7213}
7214
7215
7216static rb_heap_t *
7217gc_compact_destination_pool(rb_objspace_t *objspace, rb_heap_t *src_pool, VALUE obj)
7218{
7219 size_t obj_size = rb_gc_obj_optimal_size(obj);
7220 if (obj_size == 0) {
7221 return src_pool;
7222 }
7223
7224 GC_ASSERT(rb_gc_impl_size_allocatable_p(obj_size));
7225
7226 size_t idx = heap_idx_for_size(obj_size);
7227
7228 return &heaps[idx];
7229}
7230
7231static bool
7232gc_compact_move(rb_objspace_t *objspace, rb_heap_t *heap, VALUE src)
7233{
7234 GC_ASSERT(BUILTIN_TYPE(src) != T_MOVED);
7235 GC_ASSERT(gc_is_moveable_obj(objspace, src));
7236
7237 rb_heap_t *dest_pool = gc_compact_destination_pool(objspace, heap, src);
7238 if (gc_compact_heap_cursors_met_p(dest_pool)) {
7239 return dest_pool != heap;
7240 }
7241
7242 while (!try_move(objspace, dest_pool, dest_pool->free_pages, src)) {
7243 struct gc_sweep_context ctx = {
7244 .page = dest_pool->sweeping_page,
7245 .final_slots = 0,
7246 .freed_slots = 0,
7247 .empty_slots = 0,
7248 };
7249
7250 /* The page of src could be partially compacted, so it may contain
7251 * T_MOVED. Sweeping a page may read objects on this page, so we
7252 * need to lock the page. */
7253 lock_page_body(objspace, GET_PAGE_BODY(src));
7254 gc_sweep_page(objspace, dest_pool, &ctx);
7255 unlock_page_body(objspace, GET_PAGE_BODY(src));
7256
7257 if (dest_pool->sweeping_page->free_slots > 0) {
7258 heap_add_freepage(dest_pool, dest_pool->sweeping_page);
7259 }
7260
7261 dest_pool->sweeping_page = ccan_list_next(&dest_pool->pages, dest_pool->sweeping_page, page_node);
7262 if (gc_compact_heap_cursors_met_p(dest_pool)) {
7263 return dest_pool != heap;
7264 }
7265 }
7266
7267 return true;
7268}
7269
7270static bool
7271gc_compact_plane(rb_objspace_t *objspace, rb_heap_t *heap, uintptr_t p, bits_t bitset, struct heap_page *page)
7272{
7273 short slot_size = page->slot_size;
7274
7275 do {
7276 VALUE vp = (VALUE)p;
7277 GC_ASSERT(vp % sizeof(VALUE) == 0);
7278
7279 if (bitset & 1) {
7280 objspace->rcompactor.considered_count_table[BUILTIN_TYPE(vp)]++;
7281
7282 if (gc_is_moveable_obj(objspace, vp)) {
7283 if (!gc_compact_move(objspace, heap, vp)) {
7284 //the cursors met. bubble up
7285 return false;
7286 }
7287 }
7288 }
7289 p += slot_size;
7290 bitset >>= 1;
7291 } while (bitset);
7292
7293 return true;
7294}
7295
7296// Iterate up all the objects in page, moving them to where they want to go
7297static bool
7298gc_compact_page(rb_objspace_t *objspace, rb_heap_t *heap, struct heap_page *page)
7299{
7300 GC_ASSERT(page == heap->compact_cursor);
7301
7302 bits_t *mark_bits, *pin_bits;
7303 bits_t bitset;
7304 uintptr_t p = page->start;
7305 short slot_size = page->slot_size;
7306 int total_slots = page->total_slots;
7307 int bitmap_plane_count = CEILDIV(total_slots, BITS_BITLENGTH);
7308
7309 mark_bits = page->mark_bits;
7310 pin_bits = page->pinned_bits;
7311
7312 for (int j = 0; j < bitmap_plane_count; j++) {
7313 // objects that can be moved are marked and not pinned
7314 bitset = (mark_bits[j] & ~pin_bits[j]);
7315 if (bitset) {
7316 if (!gc_compact_plane(objspace, heap, (uintptr_t)p, bitset, page))
7317 return false;
7318 }
7319 p += BITS_BITLENGTH * slot_size;
7320 }
7321
7322 return true;
7323}
7324
7325static bool
7326gc_compact_all_compacted_p(rb_objspace_t *objspace)
7327{
7328 for (int i = 0; i < HEAP_COUNT; i++) {
7329 rb_heap_t *heap = &heaps[i];
7330
7331 if (heap->total_pages > 0 &&
7332 !gc_compact_heap_cursors_met_p(heap)) {
7333 return false;
7334 }
7335 }
7336
7337 return true;
7338}
7339
7340/* Compaction's move phase: relocate this objspace's movable objects and leave T_MOVED
7341 * forwarding behind without updating references yet. A global GC calls this for every
7342 * objspace before updating any of them (two phases), so a cross-objspace reference to a
7343 * moved object is rewritten exactly once, after all forwarding exists. */
7344static void
7345gc_compact_relocate(rb_objspace_t *objspace)
7346{
7347 gc_compact_start(objspace);
7348
7349 while (!gc_compact_all_compacted_p(objspace)) {
7350 for (int i = 0; i < HEAP_COUNT; i++) {
7351 rb_heap_t *heap = &heaps[i];
7352
7353 if (gc_compact_heap_cursors_met_p(heap)) {
7354 continue;
7355 }
7356
7357 struct heap_page *start_page = heap->compact_cursor;
7358
7359 if (!gc_compact_page(objspace, heap, start_page)) {
7360 lock_page_body(objspace, start_page->body);
7361
7362 continue;
7363 }
7364
7365 // If we get here, we've finished moving all objects on the compact_cursor page
7366 // So we can lock it and move the cursor on to the next one.
7367 lock_page_body(objspace, start_page->body);
7368 heap->compact_cursor = ccan_list_prev(&heap->pages, heap->compact_cursor, page_node);
7369 }
7370 }
7371}
7372
7373static void
7374gc_sweep_compact(rb_objspace_t *objspace)
7375{
7376 gc_compact_relocate(objspace);
7377 /* A compacting global GC defers the finish (reference update) to the second phase,
7378 * after every objspace has been relocated. */
7379 if (!global_objspace->global_gc.compacting) {
7380 gc_compact_finish(objspace);
7381 }
7382}
7383
7384static void
7385gc_marks_rest(rb_objspace_t *objspace)
7386{
7387 gc_report(1, objspace, "gc_marks_rest\n");
7388
7389 for (int i = 0; i < HEAP_COUNT; i++) {
7390 (&heaps[i])->pooled_pages = NULL;
7391 }
7392
7393 if (is_incremental_marking(objspace)) {
7394 while (gc_mark_stacked_objects_incremental(objspace, INT_MAX) == FALSE);
7395 }
7396 else {
7397 gc_mark_stacked_objects_all(objspace);
7398 }
7399
7400 gc_marks_finish(objspace);
7401}
7402
7403static bool
7404gc_marks_step(rb_objspace_t *objspace, size_t slots)
7405{
7406 bool marking_finished = false;
7407
7408 GC_ASSERT(is_marking(objspace));
7409 if (gc_mark_stacked_objects_incremental(objspace, slots)) {
7410 gc_marks_finish(objspace);
7411
7412 marking_finished = true;
7413 }
7414
7415 return marking_finished;
7416}
7417
7418static bool
7419gc_marks_continue(rb_objspace_t *objspace, rb_heap_t *heap)
7420{
7421 GC_ASSERT(dont_gc_val() == FALSE || objspace->profile.latest_gc_info & GPR_FLAG_METHOD);
7422 bool marking_finished = true;
7423
7424 gc_marking_enter(objspace);
7425
7426 if (heap->free_pages) {
7427 gc_report(2, objspace, "gc_marks_continue: has pooled pages");
7428
7429 marking_finished = gc_marks_step(objspace, objspace->rincgc.step_slots);
7430 }
7431 else {
7432 gc_report(2, objspace, "gc_marks_continue: no more pooled pages (stack depth: %"PRIdSIZE").\n",
7433 mark_stack_size(&objspace->mark_stack));
7434 heap->force_incremental_marking_finish_count++;
7435 gc_marks_rest(objspace);
7436 }
7437
7438 gc_marking_exit(objspace);
7439
7440 return marking_finished;
7441}
7442
7443/* Mark the following as roots of this objspace.
7444 * - Every shareable object: another objspace may hold the only reference, invisible to a
7445 * local GC. Marking them rather than skipping them in the sweep preserves the
7446 * generational invariants (a pinned object ages and gets promoted like any live one).
7447 * Only a global GC decides that a shareable object is dead.
7448 * - Every shref (an unshareable object referenced from a shareable one): the referring
7449 * shareable object can live in another objspace or in an in-flight message queue. The
7450 * write barrier maintains them.
7451 * Skipped while the VM has a single Ractor: a local GC is then a whole-world GC and
7452 * shareable objects may die normally. */
7453static void
7454pinned_roots_mark(rb_objspace_t *objspace, rb_heap_t *heap)
7455{
7456 struct heap_page *page = NULL;
7457
7458 /* Runs before mark_roots, so rgengc_check_relation sees a valid (absent) parent rather
7459 * than the poison left by the previous GC. */
7460 gc_mark_set_parent_raw(objspace, Qundef, false);
7461
7462 /* A local GC never frees or traverses a shareable object, and keeps its unshareable
7463 * children alive through their shref bits, so:
7464 * - a shareable object only gets its mark bit set (like an old object), which keeps
7465 * the sweep off it, and is not traversed;
7466 * - a shref is marked and traversed, like a remembered old->young target: without
7467 * that, the referring shareable object is never walked and it would look
7468 * unreachable.
7469 * Objects can become shareable between GCs, so this pass scans the bitmaps in every
7470 * mark (gc_marks_finish) instead of maintaining a pin set across the sweep. */
7471 ccan_list_for_each(&heap->pages, page, page_node) {
7472 if (!(page->flags.has_shareable_objects | page->flags.has_shref_objects)) continue;
7473
7474 uintptr_t p = page->start;
7475 short slot_size = page->slot_size;
7476 int total_slots = page->total_slots;
7477 int bitmap_plane_count = CEILDIV(total_slots, BITS_BITLENGTH);
7478
7479 for (int j = 0; j < bitmap_plane_count; j++) {
7480 bits_t sr_bits = page->shref_bits[j];
7481 /* Only the pins ordinary marking left unmarked need work here: an already
7482 * marked object (reached by traversal, or pre-marked because it is old) is a
7483 * no-op in gc_mark_set, so skip visiting it. */
7484 bits_t bitset = (page->shareable_bits[j] | sr_bits) & ~page->mark_bits[j];
7485 uintptr_t pp = p;
7486 while (bitset) {
7487 if (bitset & 1) {
7488 VALUE obj = (VALUE)pp;
7489 asan_unpoisoning_object(obj) {
7490 switch (BUILTIN_TYPE(obj)) {
7491 case T_NONE:
7492 case T_ZOMBIE:
7493 case T_MOVED:
7494 /* A dead slot (a zombie awaiting its finalizer) is not a root. */
7495 break;
7496 default:
7497 gc_report(2, objspace, "pinned_roots_mark: mark %s\n", rb_obj_info(obj));
7498 if (sr_bits & 1) {
7499 gc_mark(objspace, obj); /* shref: root + traverse */
7500 }
7501 else if (gc_mark_set(objspace, obj)) {
7502 gc_aging(objspace, obj); /* shareable: mark, no traverse */
7503 /* Pin as well when compaction runs alongside: if a shareable
7504 * object moved, the C-struct slots of other Ractors (a
7505 * port in sync, say) are not updated and go stale. */
7506 gc_pin(objspace, obj);
7507 }
7508 break;
7509 }
7510 }
7511 }
7512 pp += slot_size;
7513 bitset >>= 1;
7514 sr_bits >>= 1;
7515 }
7516 p += BITS_BITLENGTH * slot_size;
7517 }
7518 }
7519}
7520
7521static void
7522gc_marks_start(rb_objspace_t *objspace, int full_mark)
7523{
7524 /* start marking */
7525 gc_report(1, objspace, "gc_marks_start: (%s)\n", full_mark ? "full" : "minor");
7526 gc_mode_transition(objspace, gc_mode_marking);
7527
7528 if (full_mark) {
7529 size_t incremental_marking_steps = (objspace->rincgc.pooled_slots / INCREMENTAL_MARK_STEP_ALLOCATIONS) + 1;
7530 objspace->rincgc.step_slots = (objspace->marked_slots * 2) / incremental_marking_steps;
7531
7532 if (0) fprintf(stderr, "objspace->marked_slots: %"PRIdSIZE", "
7533 "objspace->rincgc.pooled_page_num: %"PRIdSIZE", "
7534 "objspace->rincgc.step_slots: %"PRIdSIZE", \n",
7535 objspace->marked_slots, objspace->rincgc.pooled_slots, objspace->rincgc.step_slots);
7536 objspace->flags.during_minor_gc = FALSE;
7537 if (ruby_enable_autocompact && rb_gc_single_objspace_p()) {
7538 objspace->flags.during_compacting |= TRUE;
7539 }
7540 objspace->profile.major_gc_count++;
7541 objspace->rgengc.uncollectible_wb_unprotected_objects = 0;
7542 objspace->rgengc.old_objects = 0;
7543 objspace->rgengc.last_major_gc = objspace->profile.count;
7544 objspace->marked_slots = 0;
7545
7546 for (int i = 0; i < HEAP_COUNT; i++) {
7547 rb_heap_t *heap = &heaps[i];
7548 gc_bitmaps_clear(objspace, heap, false);
7549 heap_move_pooled_pages_to_free_pages(heap);
7550
7551 if (objspace->flags.during_compacting) {
7552 struct heap_page *page = NULL;
7553
7554 ccan_list_for_each(&heap->pages, page, page_node) {
7555 page->pinned_slots = 0;
7556 }
7557 }
7558 }
7559 }
7560 else {
7561 objspace->flags.during_minor_gc = TRUE;
7562 objspace->marked_slots =
7563 objspace->rgengc.old_objects + objspace->rgengc.uncollectible_wb_unprotected_objects; /* uncollectible objects are marked already */
7564 objspace->profile.minor_gc_count++;
7565
7566 for (int i = 0; i < HEAP_COUNT; i++) {
7567 rgengc_rememberset_mark(objspace, &heaps[i]);
7568 }
7569 }
7570
7571 mark_roots(objspace, NULL);
7572
7573 gc_report(1, objspace, "gc_marks_start: (%s) end, stack in %"PRIdSIZE"\n",
7574 full_mark ? "full" : "minor", mark_stack_size(&objspace->mark_stack));
7575}
7576
7577static bool
7578gc_marks(rb_objspace_t *objspace, int full_mark)
7579{
7580 gc_marking_enter(objspace);
7581
7582 bool marking_finished = false;
7583
7584 /* setup marking */
7585
7586 gc_marks_start(objspace, full_mark);
7587 if (!is_incremental_marking(objspace)) {
7588 gc_marks_rest(objspace);
7589 marking_finished = true;
7590 }
7591
7592#if RGENGC_PROFILE > 0
7593 if (gc_prof_record(objspace)) {
7594 gc_profile_record *record = gc_prof_record(objspace);
7595 record->old_objects = objspace->rgengc.old_objects;
7596 }
7597#endif
7598
7599 gc_marking_exit(objspace);
7600
7601 return marking_finished;
7602}
7603
7604/* RGENGC */
7605
7606static void
7607gc_report_body(int level, rb_objspace_t *objspace, const char *fmt, ...)
7608{
7609 if (level <= RGENGC_DEBUG) {
7610 char buf[1024];
7611 FILE *out = stderr;
7612 va_list args;
7613 const char *status = " ";
7614
7615 if (during_gc) {
7616 status = is_full_marking(objspace) ? "+" : "-";
7617 }
7618 else {
7619 if (is_lazy_sweeping(objspace)) {
7620 status = "S";
7621 }
7622 if (is_incremental_marking(objspace)) {
7623 status = "M";
7624 }
7625 }
7626
7627 va_start(args, fmt);
7628 vsnprintf(buf, 1024, fmt, args);
7629 va_end(args);
7630
7631 fprintf(out, "%s|", status);
7632 fputs(buf, out);
7633 }
7634}
7635
7636/* bit operations */
7637
7638static void
7639rgengc_remembersetbits_set(rb_objspace_t *objspace, VALUE obj)
7640{
7641 struct heap_page *page = GET_HEAP_PAGE(obj);
7642 bits_t *bits = &page->remembered_bits[0];
7643
7644 /* remembered_bits writers are always serialized: the write barrier only remembers a
7645 * local a (under its Ractor's GVL) and a global GC writes from the driver alone.
7646 * Set the bit before the page flag so a page pending re-scan stays in
7647 * rememberset_mark. */
7648 _MARK_IN_BITMAP(bits, page, obj);
7649 page->flags.has_remembered_objects = TRUE;
7650}
7651
7652/* wb, etc */
7653
7654/* return FALSE if already remembered */
7655static void
7656rgengc_remember(rb_objspace_t *objspace, VALUE obj)
7657{
7658 gc_report(6, objspace, "rgengc_remember: %s %s\n", rb_obj_info(obj),
7659 RVALUE_REMEMBERED(objspace, obj) ? "was already remembered" : "is remembered now");
7660
7661 check_rvalue_consistency(objspace, obj);
7662
7663 if (RGENGC_CHECK_MODE) {
7664 if (RVALUE_WB_UNPROTECTED(objspace, obj)) rb_bug("rgengc_remember: %s is not wb protected.", rb_obj_info(obj));
7665 }
7666
7667#if RGENGC_PROFILE > 0
7668 if (!RVALUE_REMEMBERED(objspace, obj)) {
7669 if (RVALUE_WB_UNPROTECTED(objspace, obj) == 0) {
7670 objspace->profile.total_remembered_normal_object_count++;
7671#if RGENGC_PROFILE >= 2
7672 objspace->profile.remembered_normal_object_count_types[BUILTIN_TYPE(obj)]++;
7673#endif
7674 }
7675 }
7676#endif /* RGENGC_PROFILE > 0 */
7677
7678 rgengc_remembersetbits_set(objspace, obj);
7679}
7680
7681#ifndef PROFILE_REMEMBERSET_MARK
7682#define PROFILE_REMEMBERSET_MARK 0
7683#endif
7684
7685static inline void
7686rgengc_rememberset_mark_plane(rb_objspace_t *objspace, uintptr_t p, bits_t bitset, short slot_size)
7687{
7688 if (bitset) {
7689 do {
7690 if (bitset & 1) {
7691 VALUE obj = (VALUE)p;
7692 gc_report(2, objspace, "rgengc_rememberset_mark: mark %s\n", rb_obj_info(obj));
7693 GC_ASSERT(RVALUE_UNCOLLECTIBLE(objspace, obj));
7694 GC_ASSERT(RVALUE_OLD_P(objspace, obj) || RVALUE_WB_UNPROTECTED(objspace, obj));
7695
7696 gc_mark_children(objspace, obj);
7697
7699 rb_darray_append_without_gc(&objspace->weak_references, obj);
7700 }
7701 }
7702 p += slot_size;
7703 bitset >>= 1;
7704 } while (bitset);
7705 }
7706}
7707
7708static void
7709rgengc_rememberset_mark(rb_objspace_t *objspace, rb_heap_t *heap)
7710{
7711 size_t j;
7712 struct heap_page *page = 0;
7713#if PROFILE_REMEMBERSET_MARK
7714 int has_old = 0, has_shady = 0, has_both = 0, skip = 0;
7715#endif
7716 gc_report(1, objspace, "rgengc_rememberset_mark: start\n");
7717
7718 ccan_list_for_each(&heap->pages, page, page_node) {
7719 if (page->flags.has_remembered_objects | page->flags.has_uncollectible_wb_unprotected_objects) {
7720 uintptr_t p = page->start;
7721 short slot_size = page->slot_size;
7722 int total_slots = page->total_slots;
7723 int bitmap_plane_count = CEILDIV(total_slots, BITS_BITLENGTH);
7724 bits_t bitset, bits[HEAP_PAGE_BITMAP_LIMIT];
7725 bits_t *remembered_bits = page->remembered_bits;
7726 bits_t *uncollectible_bits = page->uncollectible_bits;
7727 bits_t *wb_unprotected_bits = page->wb_unprotected_bits;
7728#if PROFILE_REMEMBERSET_MARK
7729 if (page->flags.has_remembered_objects && page->flags.has_uncollectible_wb_unprotected_objects) has_both++;
7730 else if (page->flags.has_remembered_objects) has_old++;
7731 else if (page->flags.has_uncollectible_wb_unprotected_objects) has_shady++;
7732#endif
7733 /* Clear has_remembered_objects before draining the bits. A concurrent
7734 * lock-free write barrier (another Ractor remembering a shareable object on
7735 * this page) sets the bit first and the flag second, so clearing the flag first
7736 * keeps the page scheduled for re-scan even if that set interleaves. The
7737 * per-word drain is an atomic read-and-clear, so an interleaved set is not lost
7738 * (it lands in the zeroed word). */
7739 page->flags.has_remembered_objects = FALSE;
7740 for (j=0; j < (size_t)bitmap_plane_count; j++) {
7741 bits[j] = RUBY_ATOMIC_SIZE_EXCHANGE(*(volatile size_t *)&remembered_bits[j], 0)
7742 | (uncollectible_bits[j] & wb_unprotected_bits[j]);
7743 }
7744
7745 for (j=0; j < (size_t)bitmap_plane_count; j++) {
7746 bitset = bits[j];
7747 rgengc_rememberset_mark_plane(objspace, p, bitset, slot_size);
7748 p += BITS_BITLENGTH * slot_size;
7749 }
7750 }
7751#if PROFILE_REMEMBERSET_MARK
7752 else {
7753 skip++;
7754 }
7755#endif
7756 }
7757
7758#if PROFILE_REMEMBERSET_MARK
7759 fprintf(stderr, "%d\t%d\t%d\t%d\n", has_both, has_old, has_shady, skip);
7760#endif
7761 gc_report(1, objspace, "rgengc_rememberset_mark: finished\n");
7762}
7763
7764static void
7765gc_bitmaps_clear(rb_objspace_t *objspace, rb_heap_t *heap, bool clear_shref)
7766{
7767 struct heap_page *page = 0;
7768
7769 ccan_list_for_each(&heap->pages, page, page_node) {
7770 memset(&page->mark_bits[0], 0, HEAP_PAGE_BITMAP_SIZE);
7771 memset(&page->uncollectible_bits[0], 0, HEAP_PAGE_BITMAP_SIZE);
7772 memset(&page->marking_bits[0], 0, HEAP_PAGE_BITMAP_SIZE);
7773 /* A plain memset can lose a concurrent remember, but only a shareable object can
7774 * be remembered from another Ractor's thread, and pinned_roots_mark re-marks
7775 * those every local cycle, and this clear precedes a major that re-scans all. */
7776 memset(&page->remembered_bits[0], 0, HEAP_PAGE_BITMAP_SIZE);
7777 memset(&page->pinned_bits[0], 0, HEAP_PAGE_BITMAP_SIZE);
7778 page->flags.has_uncollectible_wb_unprotected_objects = FALSE;
7779 page->flags.has_remembered_objects = FALSE;
7780 /* A shref is a local GC's root, so only a stop-the-world global GC may clear them:
7781 * its unified mark re-derives them from every shareable -> unshareable edge. */
7782 if (clear_shref) {
7783 memset(&page->shref_bits[0], 0, HEAP_PAGE_BITMAP_SIZE);
7784 page->flags.has_shref_objects = FALSE;
7785 }
7786 }
7787}
7788
7789/* RGENGC: APIs */
7790
7791NOINLINE(static void gc_writebarrier_generational(VALUE a, VALUE b, rb_objspace_t *objspace));
7792
7793static void
7794gc_writebarrier_generational(VALUE a, VALUE b, rb_objspace_t *objspace)
7795{
7796 if (RGENGC_CHECK_MODE) {
7797 if (!RVALUE_OLD_P(objspace, a)) rb_bug("gc_writebarrier_generational: %s is not an old object.", rb_obj_info(a));
7798 if ( RVALUE_OLD_P(objspace, b)) rb_bug("gc_writebarrier_generational: %s is an old object.", rb_obj_info(b));
7799 if (is_incremental_marking(objspace)) rb_bug("gc_writebarrier_generational: called while incremental marking: %s -> %s", rb_obj_info(a), rb_obj_info(b));
7800 }
7801
7802 /* Mark and remember a (the default behaviour).
7803 * No lock: setting a remembered bit is atomic (rgengc_remembersetbits_set), and that is
7804 * the only place a concurrent local GC or another Ractor's write barrier can race. */
7805 if (!RVALUE_REMEMBERED(objspace, a)) {
7806 rgengc_remember(objspace, a);
7807
7808 gc_report(1, objspace, "gc_writebarrier_generational: %s (remembered) -> %s\n", rb_obj_info(a), rb_obj_info(b));
7809 }
7810
7811 check_rvalue_consistency(objspace, a);
7812 check_rvalue_consistency(objspace, b);
7813}
7814
7815static void
7816gc_mark_from(rb_objspace_t *objspace, VALUE obj, VALUE parent)
7817{
7818 gc_mark_set_parent(objspace, parent);
7819 rgengc_check_relation(objspace, obj);
7820 if (gc_mark_set(objspace, obj) != FALSE) {
7821 gc_aging(objspace, obj);
7822 gc_grey(objspace, obj);
7823 }
7824 gc_mark_set_parent_invalid(objspace);
7825}
7826
7827NOINLINE(static void gc_writebarrier_incremental(VALUE a, VALUE b, rb_objspace_t *objspace));
7828
7829static void
7830gc_writebarrier_incremental(VALUE a, VALUE b, rb_objspace_t *objspace)
7831{
7832 gc_report(2, objspace, "gc_writebarrier_incremental: [LG] %p -> %s\n", (void *)a, rb_obj_info(b));
7833
7834 if (RVALUE_BLACK_P(objspace, a)) {
7835 if (RVALUE_WHITE_P(objspace, b)) {
7836 if (!RVALUE_WB_UNPROTECTED(objspace, a)) {
7837 gc_report(2, objspace, "gc_writebarrier_incremental: [IN] %p -> %s\n", (void *)a, rb_obj_info(b));
7838 gc_mark_from(objspace, b, a);
7839 }
7840 }
7841 else if (RVALUE_OLD_P(objspace, a) && !RVALUE_OLD_P(objspace, b)) {
7842 rgengc_remember(objspace, a);
7843 }
7844
7845 if (RB_UNLIKELY(objspace->flags.during_compacting)) {
7846 MARK_IN_BITMAP(GET_HEAP_PINNED_BITS(b), b);
7847 }
7848 }
7849}
7850
7851void
7852rb_gc_impl_writebarrier(void *objspace_ptr, VALUE a, VALUE b)
7853{
7854 rb_objspace_t *objspace = objspace_ptr;
7855
7856#if RGENGC_CHECK_MODE
7857 if (SPECIAL_CONST_P(a)) rb_bug("rb_gc_writebarrier: a is special const: %"PRIxVALUE, a);
7858 if (SPECIAL_CONST_P(b)) rb_bug("rb_gc_writebarrier: b is special const: %"PRIxVALUE, b);
7859#else
7862#endif
7863
7864 GC_ASSERT(!during_gc);
7865 GC_ASSERT(RB_BUILTIN_TYPE(a) != T_NONE);
7866 GC_ASSERT(RB_BUILTIN_TYPE(a) != T_MOVED);
7867 GC_ASSERT(RB_BUILTIN_TYPE(a) != T_ZOMBIE);
7868
7869 /* A shareable object now references an unshareable one: record b as a shref so its
7870 * owner's local GC roots it (the parent may live in another objspace, untraversed
7871 * there). Only b's owner stores this, on its own page: a plain store suffices. */
7872 if (RB_UNLIKELY(RB_FL_TEST_RAW(a, RUBY_FL_SHAREABLE)) &&
7874 struct heap_page *bpage = GET_HEAP_PAGE(b);
7875 if (!_MARKED_IN_BITMAP(bpage->shref_bits, bpage, b)) {
7876 _MARK_IN_BITMAP(bpage->shref_bits, bpage, b);
7877 bpage->flags.has_shref_objects = TRUE;
7878 }
7879 }
7880
7881 retry:
7882 if (!is_incremental_marking(objspace)) {
7883 /* The generational barrier covers old->young edges within one objspace only; a
7884 * foreign a or b has age bits another objspace mutates, unsafe to read, so check
7885 * locality first when multi-Ractor (a foreign a is shareable and the shref above
7886 * already keeps b alive). With a single Ractor nothing is foreign. */
7887 if ((rb_gc_multi_ractor_p() &&
7888 (GET_HEAP_OBJSPACE(a) != objspace || GET_HEAP_OBJSPACE(b) != objspace)) ||
7889 !RVALUE_OLD_P(objspace, a) || RVALUE_OLD_P(objspace, b)) {
7890 // do nothing
7891 }
7892 else {
7893 gc_writebarrier_generational(a, b, objspace);
7894 }
7895 }
7896 else {
7897 /* Slow path, no lock: incremental marking only runs while the process has a single
7898 * objspace, so the owning Ractor's GVL already serializes this barrier against its
7899 * own GC. */
7900 if (is_incremental_marking(objspace)) {
7901 gc_writebarrier_incremental(a, b, objspace);
7902 }
7903 else {
7904 goto retry;
7905 }
7906 }
7907 return;
7908}
7909
7910void
7911rb_gc_impl_obj_became_shareable(void *objspace_ptr, VALUE obj)
7912{
7913 /* An object becomes shareable on its owner thread, so this page update is
7914 * single-writer. */
7915 struct heap_page *page = GET_HEAP_PAGE(obj);
7916 if (_MARKED_IN_BITMAP(page->shareable_bits, page, obj)) return;
7917 gc_page_add_shareable(page, obj);
7918
7919 /* The shref bits recorded while the object was unshareable are now covered by the
7920 * shareable pin, and a shref only points at an unshareable object. The owner thread is
7921 * the only writer, so a plain clear is enough. */
7922 if (_MARKED_IN_BITMAP(page->shref_bits, page, obj)) {
7923 _CLEAR_IN_BITMAP(page->shref_bits, page, obj);
7924 // NOTE: page->has_shref_objects could become stale here (value is true even though logically false)
7925 }
7926}
7927
7928void
7929rb_gc_impl_writebarrier_unprotect(void *objspace_ptr, VALUE obj)
7930{
7931 rb_objspace_t *objspace = objspace_ptr;
7932
7933 /* A shareable object is never WB-unprotected. Keeping shrefs correct relies on every
7934 * store into s->u going through the write barrier, which keeps wb_unprotected_bits
7935 * single-writer (only the owner thread can unprotect its own unshareable objects). */
7936 GC_ASSERT(!RB_FL_TEST_RAW(obj, RUBY_FL_SHAREABLE));
7937
7938 if (RVALUE_WB_UNPROTECTED(objspace, obj)) {
7939 return;
7940 }
7941 else {
7942 gc_report(2, objspace, "rb_gc_writebarrier_unprotect: %s %s\n", rb_obj_info(obj),
7943 RVALUE_REMEMBERED(objspace, obj) ? " (already remembered)" : "");
7944
7945 /* No lock: per the assert obj is our own unshareable, so these bits
7946 * (wb_unprotected, uncollectible, age) are single-writer on an owned page, and
7947 * RVALUE_DEMOTE's remembered-bit clear is atomic against word-sharing writers. */
7948 if (RVALUE_OLD_P(objspace, obj)) {
7949 gc_report(1, objspace, "rb_gc_writebarrier_unprotect: %s\n", rb_obj_info(obj));
7950 RVALUE_DEMOTE(objspace, obj);
7951 gc_mark_set(objspace, obj);
7952 gc_remember_unprotected(objspace, obj);
7953
7954#if RGENGC_PROFILE
7955 objspace->profile.total_shade_operation_count++;
7956#if RGENGC_PROFILE >= 2
7957 objspace->profile.shade_operation_count_types[BUILTIN_TYPE(obj)]++;
7958#endif /* RGENGC_PROFILE >= 2 */
7959#endif /* RGENGC_PROFILE */
7960 }
7961 else {
7962 RVALUE_AGE_RESET(obj);
7963 }
7964
7965 RB_DEBUG_COUNTER_INC(obj_wb_unprotect);
7966 MARK_IN_BITMAP(GET_HEAP_WB_UNPROTECTED_BITS(obj), obj);
7967 }
7968}
7969
7970void
7971rb_gc_impl_copy_attributes(void *objspace_ptr, VALUE dest, VALUE obj)
7972{
7973 rb_objspace_t *objspace = objspace_ptr;
7974
7975 if (RVALUE_WB_UNPROTECTED(objspace, obj)) {
7976 rb_gc_impl_writebarrier_unprotect(objspace, dest);
7977 }
7978 rb_gc_impl_copy_finalizer(objspace, dest, obj);
7979}
7980
7981const char *
7982rb_gc_impl_active_gc_name(void)
7983{
7984 return "default";
7985}
7986
7987void
7988rb_gc_impl_writebarrier_remember(void *objspace_ptr, VALUE obj)
7989{
7990 rb_objspace_t *objspace = objspace_ptr;
7991
7992 gc_report(1, objspace, "rb_gc_writebarrier_remember: %s\n", rb_obj_info(obj));
7993
7994 /* No lock, for the same reason as rb_gc_impl_writebarrier: remembering is an atomic
7995 * bitmap set, and the incremental branch only runs with a single objspace, where the
7996 * Ractor's GVL serializes it against its own GC. */
7997 if (is_incremental_marking(objspace)) {
7998 if (RVALUE_BLACK_P(objspace, obj)) {
7999 gc_grey(objspace, obj);
8000 }
8001 }
8002 else if (RVALUE_OLD_P(objspace, obj)) {
8003 rgengc_remember(objspace, obj);
8004 }
8005}
8006
8008 // Must be ID only
8009 ID ID_wb_protected, ID_age, ID_old, ID_uncollectible, ID_marking,
8010 ID_marked, ID_pinned, ID_remembered, ID_object_id, ID_shareable;
8011};
8012
8013#define RB_GC_OBJECT_METADATA_ENTRY_COUNT (sizeof(struct rb_gc_object_metadata_names) / sizeof(ID))
8014static struct rb_gc_object_metadata_entry object_metadata_entries[RB_GC_OBJECT_METADATA_ENTRY_COUNT + 1];
8015
8017rb_gc_impl_object_metadata(void *objspace_ptr, VALUE obj)
8018{
8019 rb_objspace_t *objspace = objspace_ptr;
8020 size_t n = 0;
8021 static struct rb_gc_object_metadata_names names;
8022
8023 if (!names.ID_marked) {
8024#define I(s) names.ID_##s = rb_intern(#s)
8025 I(wb_protected);
8026 I(age);
8027 I(old);
8028 I(uncollectible);
8029 I(marking);
8030 I(marked);
8031 I(pinned);
8032 I(remembered);
8033 I(object_id);
8034 I(shareable);
8035#undef I
8036 }
8037
8038#define SET_ENTRY(na, v) do { \
8039 GC_ASSERT(n <= RB_GC_OBJECT_METADATA_ENTRY_COUNT); \
8040 object_metadata_entries[n].name = names.ID_##na; \
8041 object_metadata_entries[n].val = v; \
8042 n++; \
8043} while (0)
8044
8045 if (!RVALUE_WB_UNPROTECTED(objspace, obj)) SET_ENTRY(wb_protected, Qtrue);
8046 SET_ENTRY(age, INT2FIX(RVALUE_AGE_GET(obj)));
8047 if (RVALUE_OLD_P(objspace, obj)) SET_ENTRY(old, Qtrue);
8048 if (RVALUE_UNCOLLECTIBLE(objspace, obj)) SET_ENTRY(uncollectible, Qtrue);
8049 if (RVALUE_MARKING(objspace, obj)) SET_ENTRY(marking, Qtrue);
8050 if (RVALUE_MARKED(objspace, obj)) SET_ENTRY(marked, Qtrue);
8051 if (RVALUE_PINNED(objspace, obj)) SET_ENTRY(pinned, Qtrue);
8052 if (RVALUE_REMEMBERED(objspace, obj)) SET_ENTRY(remembered, Qtrue);
8053 if (rb_obj_id_p(obj)) SET_ENTRY(object_id, rb_obj_id(obj));
8054 if (FL_TEST(obj, FL_SHAREABLE)) SET_ENTRY(shareable, Qtrue);
8055
8056 object_metadata_entries[n].name = 0;
8057 object_metadata_entries[n].val = 0;
8058#undef SET_ENTRY
8059
8060 return object_metadata_entries;
8061}
8062
8063void *
8064rb_gc_impl_ractor_cache_alloc(void *objspace_ptr, void *ractor)
8065{
8066 /* No cache needed: allocation happens in a per-Ractor objspace. */
8067 return NULL;
8068}
8069
8070void
8071rb_gc_impl_ractor_cache_free(void *objspace_ptr, void *cache)
8072{
8073 GC_ASSERT(cache == NULL);
8074}
8075
8076/* The terminating Ractor's final local GC, on its own thread: roots are minimal, so the
8077 * mark is tiny, and it reclaims what the joining side would otherwise inherit. Never
8078 * promotes to a global GC (that would STW on every Ractor death); empty pages go
8079 * straight back to the page pool. */
8080/* Finalize the zombies whose cleanup is pure C (a dfree, no Ruby-level finalizer);
8081 * the caller has no Ruby execution context any more, so zombies with a Ruby
8082 * finalizer stay deferred and travel to the inheritor as before. Returns whether
8083 * anything was finalized (those pages then need one more sweep to detach). */
8084static bool
8085finalize_deferred_dfree_only(rb_objspace_t *objspace)
8086{
8087 VALUE dfree_only = 0;
8088 VALUE zombie = RUBY_ATOMIC_VALUE_EXCHANGE(heap_pages_deferred_final, 0);
8089 while (zombie) {
8090 rb_asan_unpoison_object(zombie, false);
8091 VALUE next = RZOMBIE(zombie)->next;
8092 if (FL_TEST_RAW(zombie, FL_FINALIZE)) {
8093 /* re-defer, with the same push as rb_gc_impl_make_zombie */
8094 VALUE prev2, next2 = heap_pages_deferred_final;
8095 do {
8096 RZOMBIE(zombie)->next = prev2 = next2;
8097 next2 = RUBY_ATOMIC_VALUE_CAS(heap_pages_deferred_final, prev2, zombie);
8098 } while (next2 != prev2);
8099 rb_asan_poison_object(zombie);
8100 }
8101 else {
8102 RZOMBIE(zombie)->next = dfree_only;
8103 dfree_only = zombie;
8104 }
8105 zombie = next;
8106 }
8107 if (dfree_only) finalize_list(objspace, dfree_only);
8108 return dfree_only != 0;
8109}
8110
8111void
8112rb_gc_impl_objspace_retire_gc(void *objspace_ptr)
8113{
8114 rb_objspace_t *objspace = objspace_ptr;
8115
8116 /* The dying thread's stack is already torn down here, so the root scan must skip
8117 * its machine context (rb_gc_mark_roots). */
8118 objspace->flags.during_postmortem = 1;
8119
8120 gc_rest(objspace);
8121 gc_start_body(objspace, GPR_FLAG_FULL_MARK | GPR_FLAG_IMMEDIATE_MARK | GPR_FLAG_IMMEDIATE_SWEEP,
8122 false);
8123
8124 /* The sweep above turned this heap's dead IO and the like into deferred zombies
8125 * (the per-Ractor stdio holds a page per Ractor otherwise); finalize the C-only
8126 * ones here and re-sweep the nearly-empty heap so their pages detach as empty. */
8127 if (finalize_deferred_dfree_only(objspace)) {
8128 gc_start_body(objspace, GPR_FLAG_FULL_MARK | GPR_FLAG_IMMEDIATE_MARK | GPR_FLAG_IMMEDIATE_SWEEP,
8129 false);
8130 }
8131
8132 heap_pages_freeable_pages = objspace->empty_pages_count;
8133 heap_pages_free_unused_pages(objspace);
8134
8135 objspace->flags.during_postmortem = 0;
8136}
8137
8138bool
8139rb_gc_impl_during_postmortem_p(void *objspace_ptr)
8140{
8141 rb_objspace_t *objspace = objspace_ptr;
8142 return objspace->flags.during_postmortem != 0;
8143}
8144
8145static void
8146heap_ready_to_gc(rb_objspace_t *objspace, rb_heap_t *heap)
8147{
8148 if (!heap->free_pages) {
8149 if (!heap_page_allocate_and_initialize(objspace, heap)) {
8150 objspace->heap_pages.allocatable_bytes = HEAP_PAGE_SIZE;
8151 heap_page_allocate_and_initialize(objspace, heap);
8152 }
8153 }
8154}
8155
8156static int
8157ready_to_gc(rb_objspace_t *objspace)
8158{
8159 if (rb_gc_gc_disabled_global_p() || dont_gc_val() || during_gc) {
8160 for (int i = 0; i < HEAP_COUNT; i++) {
8161 rb_heap_t *heap = &heaps[i];
8162 heap_ready_to_gc(objspace, heap);
8163 }
8164 return FALSE;
8165 }
8166 else {
8167 return TRUE;
8168 }
8169}
8170
8171static void
8172gc_reset_malloc_info(rb_objspace_t *objspace, bool full_mark)
8173{
8174 gc_prof_set_malloc_info(objspace);
8175 {
8176 int64_t inc = gc_malloc_counters_increase(objspace, &objspace->malloc_counters.counters);
8177 gc_malloc_counters_snapshot(objspace, &objspace->malloc_counters.counters);
8178 size_t old_limit = malloc_limit;
8179
8180 /* A net-negative `inc` (more freed than malloc'd since last GC) is
8181 * treated the same as "allocated less than malloc_limit".
8182 * This matches what we were doing pre-monotonic counters, but is it right? */
8183 if (inc > 0 && (size_t)inc > malloc_limit) {
8184 malloc_limit = (size_t)((size_t)inc * gc_params.malloc_limit_growth_factor);
8185 if (malloc_limit > gc_params.malloc_limit_max) {
8186 malloc_limit = gc_params.malloc_limit_max;
8187 }
8188 }
8189 else {
8190 malloc_limit = (size_t)(malloc_limit * 0.98); /* magic number */
8191 if (malloc_limit < gc_params.malloc_limit_min) {
8192 malloc_limit = gc_params.malloc_limit_min;
8193 }
8194 }
8195
8196 if (0) {
8197 if (old_limit != malloc_limit) {
8198 fprintf(stderr, "[%"PRIuSIZE"] malloc_limit: %"PRIuSIZE" -> %"PRIuSIZE"\n",
8199 rb_gc_count(), old_limit, malloc_limit);
8200 }
8201 else {
8202 fprintf(stderr, "[%"PRIuSIZE"] malloc_limit: not changed (%"PRIuSIZE")\n",
8203 rb_gc_count(), malloc_limit);
8204 }
8205 }
8206 }
8207
8208 /* reset oldmalloc info */
8209#if RGENGC_ESTIMATE_OLDMALLOC
8210 if (!full_mark) {
8211 /* No full snapshot on minor GC: oldmalloc_increase accumulates across
8212 * minors and resets at major GC. (gc_sweep_finish still advances the
8213 * free baseline after every sweep.) */
8214 int64_t oldmalloc_increase = gc_malloc_counters_increase(objspace, &objspace->malloc_counters.oldcounters);
8215 if (oldmalloc_increase > 0 &&
8216 (uint64_t)oldmalloc_increase > objspace->rgengc.oldmalloc_increase_limit) {
8217 gc_needs_major_flags |= GPR_FLAG_MAJOR_BY_OLDMALLOC;
8218 objspace->rgengc.oldmalloc_increase_limit =
8219 (size_t)(objspace->rgengc.oldmalloc_increase_limit * gc_params.oldmalloc_limit_growth_factor);
8220
8221 if (objspace->rgengc.oldmalloc_increase_limit > gc_params.oldmalloc_limit_max) {
8222 objspace->rgengc.oldmalloc_increase_limit = gc_params.oldmalloc_limit_max;
8223 }
8224 }
8225
8226 if (0) fprintf(stderr, "%"PRIdSIZE"\t%d\t%"PRId64"\t%"PRIuSIZE"\t%"PRIdSIZE"\n",
8227 rb_gc_count(),
8228 gc_needs_major_flags,
8229 oldmalloc_increase,
8230 objspace->rgengc.oldmalloc_increase_limit,
8231 gc_params.oldmalloc_limit_max);
8232 }
8233 else {
8234 gc_malloc_counters_snapshot(objspace, &objspace->malloc_counters.oldcounters);
8235
8236 if ((objspace->profile.latest_gc_info & GPR_FLAG_MAJOR_BY_OLDMALLOC) == 0) {
8237 objspace->rgengc.oldmalloc_increase_limit =
8238 (size_t)(objspace->rgengc.oldmalloc_increase_limit / ((gc_params.oldmalloc_limit_growth_factor - 1)/10 + 1));
8239 if (objspace->rgengc.oldmalloc_increase_limit < gc_params.oldmalloc_limit_min) {
8240 objspace->rgengc.oldmalloc_increase_limit = gc_params.oldmalloc_limit_min;
8241 }
8242 }
8243 }
8244#endif
8245}
8246
8247/* What a collection records about itself before it runs. A global collection reports the
8248 * driver's objspace, so it comes through here too. */
8249static void
8250gc_start_record(rb_objspace_t *objspace, unsigned int reason, bool full_mark)
8251{
8252 objspace->profile.latest_gc_info = reason;
8253#if GC_PROFILE_MORE_DETAIL
8254 objspace->profile.total_allocated_objects_at_gc_start = total_allocated_objects(objspace);
8255 objspace->profile.heap_used_at_gc_start = rb_darray_size(objspace->heap_pages.sorted);
8256#endif
8257 objspace->profile.weak_references_count = 0;
8258 gc_prof_setup_new_record(objspace, reason);
8259 gc_reset_malloc_info(objspace, full_mark);
8260}
8261
8262static bool gc_start_global(rb_objspace_t *driver, unsigned int reason, bool compact, bool allow_skip);
8263
8264/* Decide whether this collection has to be global. A local GC can reclaim neither
8265 * shareable objects nor zombie objspaces, so once those grow past their limits only a
8266 * global GC makes progress. All inputs belong to this objspace. */
8267static bool
8268gc_need_global_p(rb_objspace_t *objspace)
8269{
8270 if (rb_gc_single_objspace_p()) return false;
8271 if (objspace->shareable_objects > objspace->shareable_objects_limit) return true;
8272 /* A zombie's garbage only a global cycle reclaims, but what survived the last one
8273 * is live data, so retrigger only once TRIGGER more pages accumulate on top of it.
8274 * Otherwise one live-heavy unjoined zombie turns every GC stop-the-world forever. */
8275 {
8276 size_t zp = rb_gc_vm_zombie_total_pages();
8277 size_t base = global_objspace->zombie_pages_survivors < zp ? global_objspace->zombie_pages_survivors : zp;
8278 if (zp - base >= ZOMBIE_PAGES_TRIGGER) return true;
8279 }
8280 return false;
8281}
8282
8283static int
8284garbage_collect(rb_objspace_t *objspace, unsigned int reason)
8285{
8286 int ret;
8287
8288#if GC_PROFILE_MORE_DETAIL
8289 objspace->profile.prepare_time = getrusage_time();
8290#endif
8291
8292 gc_rest(objspace);
8293
8294#if GC_PROFILE_MORE_DETAIL
8295 objspace->profile.prepare_time = getrusage_time() - objspace->profile.prepare_time;
8296#endif
8297
8298 ret = gc_start(objspace, reason);
8299
8300 return ret;
8301}
8302
8303static int
8304gc_start_body(rb_objspace_t *objspace, unsigned int reason, bool allow_global)
8305{
8306 unsigned int do_full_mark = !!(reason & GPR_FLAG_FULL_MARK);
8307
8308 if (!rb_darray_size(objspace->heap_pages.sorted)) return TRUE; /* heap is not ready */
8309 if (!(reason & GPR_FLAG_METHOD) && !ready_to_gc(objspace)) return TRUE; /* GC is not allowed */
8310
8311 /* Every local GC entry asks whether a global cycle is needed instead, including the
8312 * allocation slow path, or an allocation-driven workload slips past every threshold
8313 * (only a global cycle reclaims dead shareable objects and zombie pages). The
8314 * exception is the retire GC, which never promotes: a Ractor's death must not STW. */
8315 if (allow_global && gc_need_global_p(objspace)) {
8316 if (gc_start_global(objspace, reason, false, true)) {
8317 return TRUE;
8318 }
8319 // Fall through to a local GC
8320 }
8321
8322 rb_gc_initialize_vm_context(&objspace->vm_context);
8323
8324 GC_ASSERT(gc_mode(objspace) == gc_mode_none, "gc_mode is %s\n", gc_mode_name(gc_mode(objspace)));
8325 GC_ASSERT(!is_lazy_sweeping(objspace));
8326 GC_ASSERT(!is_incremental_marking(objspace));
8327
8328 /* reason may be clobbered, later, so keep set immediate_sweep here */
8329 objspace->flags.immediate_sweep = !!(reason & GPR_FLAG_IMMEDIATE_SWEEP);
8330
8331 if (ruby_gc_stressful) {
8332 int flag = FIXNUM_P(ruby_gc_stress_mode) ? FIX2INT(ruby_gc_stress_mode) : 0;
8333
8334 if ((flag & (1 << gc_stress_no_major)) == 0) {
8335 do_full_mark = TRUE;
8336 }
8337
8338 objspace->flags.immediate_sweep = !(flag & (1<<gc_stress_no_immediate_sweep));
8339 }
8340
8341 if (gc_needs_major_flags) {
8342 reason |= gc_needs_major_flags;
8343 do_full_mark = TRUE;
8344 }
8345
8346 /* if major gc has been disabled, never do a full mark */
8347 if (!gc_config_full_mark_val) {
8348 do_full_mark = FALSE;
8349 }
8350 gc_needs_major_flags = GPR_FLAG_NONE;
8351
8352 if (do_full_mark && (reason & GPR_FLAG_MAJOR_MASK) == 0) {
8353 reason |= GPR_FLAG_MAJOR_BY_FORCE; /* GC by CAPI, METHOD, and so on. */
8354 }
8355
8356 if (objspace->flags.dont_incremental ||
8357 reason & GPR_FLAG_IMMEDIATE_MARK ||
8358 ruby_gc_stressful ||
8359 /* No incremental marking while multiple objspaces exist: between steps another
8360 * Ractor can create and share objects behind this objspace's already-scanned
8361 * roots. */
8362 !rb_gc_single_objspace_p()) {
8363 objspace->flags.during_incremental_marking = FALSE;
8364 }
8365 else {
8366 objspace->flags.during_incremental_marking = do_full_mark;
8367 }
8368
8369 /* Compaction on the local GC path (autocompact) runs only with a single objspace:
8370 * without the stop-the-world barrier, moving objects would break cross-objspace
8371 * references. With multiple objspaces GC.compact and autocompact go through the
8372 * compacting global GC instead (rb_gc_impl_start -> gc_start_global). */
8373 if (do_full_mark && ruby_enable_autocompact && rb_gc_single_objspace_p()) {
8374 objspace->flags.during_compacting = TRUE;
8375#if RGENGC_CHECK_MODE
8376 objspace->rcompactor.compare_func = ruby_autocompact_compare_func;
8377#endif
8378 }
8379 else {
8380 objspace->flags.during_compacting = !!(reason & GPR_FLAG_COMPACT);
8381 /* The local path was chosen with a single objspace, but another Ractor can be
8382 * born before this point; local compaction would then move shareable objects and
8383 * leave other Ractors' C-struct slots stale, so give up. */
8384 if (objspace->flags.during_compacting && !rb_gc_single_objspace_p()) {
8385 objspace->flags.during_compacting = FALSE;
8386 }
8387 }
8388
8389 if (!GC_ENABLE_LAZY_SWEEP || objspace->flags.dont_incremental) {
8390 objspace->flags.immediate_sweep = TRUE;
8391 }
8392
8393 if (objspace->flags.immediate_sweep) reason |= GPR_FLAG_IMMEDIATE_SWEEP;
8394
8395 /* Enter after during_compacting is decided: gc_local_gc_holds_vm_lock reads it. */
8396 unsigned int lock_lev;
8397 gc_enter(objspace, gc_enter_event_start, &lock_lev);
8398
8399 gc_report(1, objspace, "gc_start(reason: %x) => %u, %d, %d\n",
8400 reason,
8401 do_full_mark, !is_incremental_marking(objspace), objspace->flags.immediate_sweep);
8402
8403 RB_DEBUG_COUNTER_INC(gc_count);
8404
8405 if (reason & GPR_FLAG_MAJOR_MASK) {
8406 (void)RB_DEBUG_COUNTER_INC_IF(gc_major_nofree, reason & GPR_FLAG_MAJOR_BY_NOFREE);
8407 (void)RB_DEBUG_COUNTER_INC_IF(gc_major_oldgen, reason & GPR_FLAG_MAJOR_BY_OLDGEN);
8408 (void)RB_DEBUG_COUNTER_INC_IF(gc_major_shady, reason & GPR_FLAG_MAJOR_BY_SHADY);
8409 (void)RB_DEBUG_COUNTER_INC_IF(gc_major_force, reason & GPR_FLAG_MAJOR_BY_FORCE);
8410#if RGENGC_ESTIMATE_OLDMALLOC
8411 (void)RB_DEBUG_COUNTER_INC_IF(gc_major_oldmalloc, reason & GPR_FLAG_MAJOR_BY_OLDMALLOC);
8412#endif
8413 }
8414 else {
8415 (void)RB_DEBUG_COUNTER_INC_IF(gc_minor_newobj, reason & GPR_FLAG_NEWOBJ);
8416 (void)RB_DEBUG_COUNTER_INC_IF(gc_minor_malloc, reason & GPR_FLAG_MALLOC);
8417 (void)RB_DEBUG_COUNTER_INC_IF(gc_minor_method, reason & GPR_FLAG_METHOD);
8418 (void)RB_DEBUG_COUNTER_INC_IF(gc_minor_capi, reason & GPR_FLAG_CAPI);
8419 (void)RB_DEBUG_COUNTER_INC_IF(gc_minor_stress, reason & GPR_FLAG_STRESS);
8420 }
8421
8422 objspace->profile.count++;
8423 gc_start_record(objspace, reason, do_full_mark);
8424
8425 gc_event_hook(objspace, RUBY_INTERNAL_EVENT_GC_START);
8426
8427 GC_ASSERT(during_gc);
8428
8429 gc_prof_timer_start(objspace);
8430 {
8431 if (gc_marks(objspace, do_full_mark)) {
8432 gc_sweep(objspace);
8433 }
8434 }
8435 gc_prof_timer_stop(objspace);
8436
8437 gc_exit(objspace, gc_enter_event_start, &lock_lev);
8438
8439 /* Verify after the GC, at a real safepoint with during_gc cleared: mid-GC it would
8440 * call rb_objspace_reachable_objects_from, whose barrier VM lock would join another
8441 * Ractor's global GC barrier and let it collect on this half-collected heap. */
8442#if RGENGC_CHECK_MODE >= 2
8443 gc_verify_internal_consistency(objspace);
8444#endif
8445 return TRUE;
8446}
8447
8448static int
8449gc_start(rb_objspace_t *objspace, unsigned int reason)
8450{
8451 return gc_start_body(objspace, reason, true);
8452}
8453
8454static void
8455gc_rest(rb_objspace_t *objspace)
8456{
8457 if (is_incremental_marking(objspace) || is_lazy_sweeping(objspace)) {
8458 unsigned int lock_lev;
8459 gc_enter(objspace, gc_enter_event_rest, &lock_lev);
8460
8461 if (is_incremental_marking(objspace)) {
8462 gc_marking_enter(objspace);
8463 gc_marks_rest(objspace);
8464 gc_marking_exit(objspace);
8465
8466 gc_sweep(objspace);
8467 }
8468
8469 if (is_lazy_sweeping(objspace)) {
8470 gc_sweeping_enter(objspace);
8471 gc_sweep_rest(objspace);
8472 gc_sweeping_exit(objspace);
8473 }
8474
8475 gc_exit(objspace, gc_enter_event_rest, &lock_lev);
8476
8477 if (RGENGC_CHECK_MODE >= 2) gc_verify_internal_consistency(objspace); /* after GC, see gc_start */
8478 }
8479}
8480
8483 unsigned int reason;
8484};
8485
8486static void
8487gc_current_status_fill(rb_objspace_t *objspace, char *buff)
8488{
8489 int i = 0;
8490 if (is_marking(objspace)) {
8491 buff[i++] = 'M';
8492 if (is_full_marking(objspace)) buff[i++] = 'F';
8493 if (is_incremental_marking(objspace)) buff[i++] = 'I';
8494 }
8495 else if (is_sweeping(objspace)) {
8496 buff[i++] = 'S';
8497 if (is_lazy_sweeping(objspace)) buff[i++] = 'L';
8498 }
8499 else {
8500 buff[i++] = 'N';
8501 }
8502 buff[i] = '\0';
8503}
8504
8505static const char *
8506gc_current_status(rb_objspace_t *objspace)
8507{
8508 static char buff[0x10];
8509 gc_current_status_fill(objspace, buff);
8510 return buff;
8511}
8512
8513#if PRINT_ENTER_EXIT_TICK
8514
8515static tick_t last_exit_tick;
8516static tick_t enter_tick;
8517static int enter_count = 0;
8518static char last_gc_status[0x10];
8519
8520static inline void
8521gc_record(rb_objspace_t *objspace, int direction, const char *event)
8522{
8523 if (direction == 0) { /* enter */
8524 enter_count++;
8525 enter_tick = tick();
8526 gc_current_status_fill(objspace, last_gc_status);
8527 }
8528 else { /* exit */
8529 tick_t exit_tick = tick();
8530 char current_gc_status[0x10];
8531 gc_current_status_fill(objspace, current_gc_status);
8532#if 1
8533 /* [last mutator time] [gc time] [event] */
8534 fprintf(stderr, "%"PRItick"\t%"PRItick"\t%s\t[%s->%s|%c]\n",
8535 enter_tick - last_exit_tick,
8536 exit_tick - enter_tick,
8537 event,
8538 last_gc_status, current_gc_status,
8539 (objspace->profile.latest_gc_info & GPR_FLAG_MAJOR_MASK) ? '+' : '-');
8540 last_exit_tick = exit_tick;
8541#else
8542 /* [enter_tick] [gc time] [event] */
8543 fprintf(stderr, "%"PRItick"\t%"PRItick"\t%s\t[%s->%s|%c]\n",
8544 enter_tick,
8545 exit_tick - enter_tick,
8546 event,
8547 last_gc_status, current_gc_status,
8548 (objspace->profile.latest_gc_info & GPR_FLAG_MAJOR_MASK) ? '+' : '-');
8549#endif
8550 }
8551}
8552#else /* PRINT_ENTER_EXIT_TICK */
8553static inline void
8554gc_record(rb_objspace_t *objspace, int direction, const char *event)
8555{
8556 /* null */
8557}
8558#endif /* PRINT_ENTER_EXIT_TICK */
8559
8560static const char *
8561gc_enter_event_cstr(enum gc_enter_event event)
8562{
8563 switch (event) {
8564 case gc_enter_event_start: return "start";
8565 case gc_enter_event_continue: return "continue";
8566 case gc_enter_event_rest: return "rest";
8567 case gc_enter_event_finalizer: return "finalizer";
8568 case gc_enter_event_global: return "global";
8569 case gc_enter_event_global_auto: return "global_auto";
8570 }
8571 return NULL;
8572}
8573
8574static void
8575gc_enter_count(enum gc_enter_event event)
8576{
8577 switch (event) {
8578 case gc_enter_event_start: RB_DEBUG_COUNTER_INC(gc_enter_start); break;
8579 case gc_enter_event_continue: RB_DEBUG_COUNTER_INC(gc_enter_continue); break;
8580 case gc_enter_event_rest: RB_DEBUG_COUNTER_INC(gc_enter_rest); break;
8581 case gc_enter_event_finalizer: RB_DEBUG_COUNTER_INC(gc_enter_finalizer); break;
8582 case gc_enter_event_global: RB_DEBUG_COUNTER_INC(gc_enter_start); break;
8583 case gc_enter_event_global_auto: RB_DEBUG_COUNTER_INC(gc_enter_start); break;
8584 }
8585}
8586
8587static bool current_process_time(struct timespec *ts);
8588
8589/* A gc phase must be timed on the collecting thread's own cpu. A local gc runs
8590 * while the other ractors keep going, and process cpu time counts their work as
8591 * gc: with eight busy ractors the same ten collections were reported as 131ms
8592 * instead of 3ms, more than the wall clock they ran in. The kernel also answers
8593 * this one without walking every thread in the process. */
8594static bool
8595current_thread_time(struct timespec *ts)
8596{
8597#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_THREAD_CPUTIME_ID)
8598 {
8599 static int try_clock_gettime = 1;
8600 if (try_clock_gettime && clock_gettime(CLOCK_THREAD_CPUTIME_ID, ts) == 0) {
8601 return true;
8602 }
8603 else {
8604 try_clock_gettime = 0;
8605 }
8606 }
8607#endif
8608 return current_process_time(ts);
8609}
8610
8611static void
8612gc_clock_start(struct timespec *ts)
8613{
8614 if (!current_thread_time(ts)) {
8615 ts->tv_sec = 0;
8616 ts->tv_nsec = 0;
8617 }
8618}
8619
8620static unsigned long long
8621gc_clock_end(struct timespec *ts)
8622{
8623 struct timespec end_time;
8624
8625 if ((ts->tv_sec > 0 || ts->tv_nsec > 0) &&
8626 current_thread_time(&end_time) &&
8627 end_time.tv_sec >= ts->tv_sec) {
8628 return (unsigned long long)(end_time.tv_sec - ts->tv_sec) * (1000 * 1000 * 1000) +
8629 (end_time.tv_nsec - ts->tv_nsec);
8630 }
8631
8632 return 0;
8633}
8634
8635/* Whether a non-global local GC holds the no-barrier VM lock for its whole run. Main's
8636 * ordinary local GC is lock-free; only compaction holds it (see the comment in the
8637 * function body). */
8638static inline bool
8639gc_local_gc_holds_vm_lock(const rb_objspace_t *objspace)
8640{
8641 /* Main's local GC is lock-free at the gc_enter level. The VM-global roots and JIT
8642 * root marks that need the VM lock take a bounded no-barrier window in rb_gc_mark_roots.
8643 * (JIT iseq payload marks and frees are not reached during a local GC: iseqs are born
8644 * shareable and a local GC never traverses or frees them.) Compaction takes its
8645 * barrier lock separately (gc_enter handles it before this function runs). */
8646 return objspace == global_objspace->main_objspace &&
8647 objspace->flags.during_compacting;
8648}
8649
8650static inline bool
8651gc_enter(rb_objspace_t *objspace, enum gc_enter_event event, unsigned int *lock_lev)
8652{
8653 /* A local GC runs on its owner thread and takes neither the VM lock nor a barrier:
8654 * containment makes the heap single-writer (only a stop-the-world global GC writes pages
8655 * across objspaces).
8656 *
8657 * Main's local GC walks VM-global roots (rb_vm_mark) and JIT root marks that change under
8658 * the VM lock but takes the lock in rb_gc_mark_roots rather than holding it for the full GC.
8659 *
8660 * NOTE: The GC must never take the barrier VM lock from inside itself: the waiter could
8661 * join a pending barrier mid-collection and expose its half-collected heap to the global
8662 * GC. A no-barrier lock is safe. Other shared structures the GC paths touch use their own
8663 * native mutexes or the page-pool lock. */
8664 *lock_lev = 0;
8665
8666 RUBY_DTRACE_GC_HOOK(ENTER, event);
8667
8668 if (objspace->profile.run) {
8669 switch (event) {
8670 case gc_enter_event_start:
8671 case gc_enter_event_continue:
8672 case gc_enter_event_rest:
8673 case gc_enter_event_global:
8674 case gc_enter_event_global_auto:
8675 /* A global GC is the longest pause the process takes, so it is the last thing
8676 * the profiler may leave unmeasured. The switch below stops the world for it,
8677 * which is exactly the interval gc_stop_time is meant to name, so start the
8678 * clock here like a local collection does. */
8679 objspace->profile.gc_pause_start_time = rb_hrtime_now();
8680 break;
8681 case gc_enter_event_finalizer:
8682 break;
8683 }
8684 }
8685 switch (event) {
8686 case gc_enter_event_global:
8687 *lock_lev = RB_GC_VM_LOCK();
8688 // stop other ractors
8689 rb_gc_vm_barrier();
8690 break;
8691 case gc_enter_event_global_auto:
8692 *lock_lev = RB_GC_VM_LOCK();
8693 if (!gc_need_global_p(objspace)) {
8694 RB_GC_VM_UNLOCK(*lock_lev);
8695 *lock_lev = 0;
8696 objspace->profile.gc_pause_start_time = 0;
8697 return false;
8698 }
8699 rb_gc_vm_barrier();
8700 break;
8701 case gc_enter_event_finalizer:
8702 /* Shutdown finalizers read VM-global tables (fstring, symbol) and free T_DATA that
8703 * is not thread-safe, so take the no-barrier VM lock. */
8704 *lock_lev = RB_GC_VM_LOCK_NO_BARRIER();
8705 break;
8706 default:
8707 objspace->flags.gc_lock_barrier = FALSE;
8708 if (objspace->flags.during_compacting) {
8709 /* Compaction relocates objects and rewrites every Ractor's JIT and global
8710 * references, so it stops the world with a barrier VM lock. rb_gc_vm_barrier is
8711 * a reentrant no-op with a single Ractor, so an inner barrier request during the
8712 * move folds into this one and gc_exit ends it. */
8713 *lock_lev = RB_GC_VM_LOCK();
8714 rb_gc_vm_barrier();
8715 objspace->flags.gc_lock_barrier = TRUE;
8716 }
8717 else if (gc_local_gc_holds_vm_lock(objspace)) {
8718 *lock_lev = RB_GC_VM_LOCK_NO_BARRIER();
8719 }
8720 break;
8721 }
8722
8723 if (objspace->profile.gc_pause_start_time) {
8724 objspace->profile.gc_stw_start_time = rb_hrtime_now();
8725 objspace->profile.gc_stop_time = rb_hrtime_sub(
8726 objspace->profile.gc_stw_start_time,
8727 objspace->profile.gc_pause_start_time);
8728 }
8729
8730 gc_enter_count(event);
8731 if (RB_UNLIKELY(during_gc != 0)) rb_bug("during_gc != 0");
8732 if (RGENGC_CHECK_MODE >= 3) gc_verify_internal_consistency(objspace);
8733
8734 during_gc = TRUE;
8735 RUBY_DEBUG_LOG("%s (%s)",gc_enter_event_cstr(event), gc_current_status(objspace));
8736 gc_report(1, objspace, "gc_enter: %s [%s]\n", gc_enter_event_cstr(event), gc_current_status(objspace));
8737 gc_record(objspace, 0, gc_enter_event_cstr(event));
8738
8739 gc_event_hook(objspace, RUBY_INTERNAL_EVENT_GC_ENTER);
8740 return true;
8741}
8742
8743static inline void
8744gc_exit(rb_objspace_t *objspace, enum gc_enter_event event, unsigned int *lock_lev)
8745{
8746 GC_ASSERT(during_gc != 0);
8747
8748 RUBY_DTRACE_GC_HOOK(EXIT, event);
8749
8750 gc_event_hook(objspace, RUBY_INTERNAL_EVENT_GC_EXIT);
8751
8752 if (objspace->profile.gc_pause_start_time) {
8753 if (gc_prof_enabled(objspace)) {
8754 rb_hrtime_t now = rb_hrtime_now();
8755 gc_profile_record *record = gc_prof_record(objspace);
8756 record->gc_pause_time = rb_hrtime_add(record->gc_pause_time,
8757 rb_hrtime_sub(now, objspace->profile.gc_pause_start_time));
8758 record->gc_stop_time = rb_hrtime_add(record->gc_stop_time,
8759 objspace->profile.gc_stop_time);
8760 record->gc_stw_time = rb_hrtime_add(record->gc_stw_time,
8761 rb_hrtime_sub(now, objspace->profile.gc_stw_start_time));
8762 }
8763 objspace->profile.gc_pause_start_time = 0;
8764 objspace->profile.gc_stw_start_time = 0;
8765 objspace->profile.gc_stop_time = 0;
8766 }
8767
8768 gc_record(objspace, 1, gc_enter_event_cstr(event));
8769 RUBY_DEBUG_LOG("%s (%s)", gc_enter_event_cstr(event), gc_current_status(objspace));
8770 gc_report(1, objspace, "gc_exit: %s [%s]\n", gc_enter_event_cstr(event), gc_current_status(objspace));
8771 during_gc = FALSE;
8772
8773 switch (event) {
8774 case gc_enter_event_global:
8775 case gc_enter_event_global_auto:
8776 RB_GC_VM_UNLOCK(*lock_lev);
8777 break;
8778 case gc_enter_event_finalizer:
8779 RB_GC_VM_UNLOCK_NO_BARRIER(*lock_lev);
8780 break;
8781 default:
8782 if (*lock_lev != 0) {
8783 if (objspace->flags.gc_lock_barrier) {
8784 objspace->flags.gc_lock_barrier = FALSE;
8785 RB_GC_VM_UNLOCK(*lock_lev);
8786 }
8787 else {
8788 RB_GC_VM_UNLOCK_NO_BARRIER(*lock_lev);
8789 }
8790 }
8791 break;
8792 }
8793}
8794
8795#ifndef MEASURE_GC
8796#define MEASURE_GC (objspace->flags.measure_gc)
8797#endif
8798
8799static void
8800gc_marking_enter(rb_objspace_t *objspace)
8801{
8802 GC_ASSERT(during_gc != 0);
8803
8804 gc_prof_mark_timer_start(objspace);
8805
8806 if (gc_prof_enabled(objspace)) {
8807 objspace->profile.gc_mark_phase_wall_start_time = rb_hrtime_now();
8808 }
8809
8810 if (MEASURE_GC) {
8811 gc_clock_start(&objspace->profile.marking_start_time);
8812 }
8813
8814 rb_gc_initialize_vm_context(&objspace->vm_context);
8815}
8816
8817static void
8818gc_marking_exit(rb_objspace_t *objspace)
8819{
8820 GC_ASSERT(during_gc != 0);
8821
8822 if (MEASURE_GC) {
8823 objspace->profile.marking_time_ns += gc_clock_end(&objspace->profile.marking_start_time);
8824 }
8825
8826 if (gc_prof_enabled(objspace)) {
8827 gc_profile_record *record = gc_prof_record(objspace);
8828 record->gc_mark_wall_time = rb_hrtime_add(record->gc_mark_wall_time,
8829 elapsed_hrtime_from(objspace->profile.gc_mark_phase_wall_start_time));
8830 }
8831
8832 gc_prof_mark_timer_stop(objspace);
8833}
8834
8835static void
8836gc_sweeping_enter(rb_objspace_t *objspace)
8837{
8838 GC_ASSERT(during_gc != 0);
8839
8840 if (gc_prof_enabled(objspace)) {
8841 objspace->profile.gc_sweep_phase_wall_start_time = rb_hrtime_now();
8842 objspace->profile.gc_sweep_excluded_wall_time = 0;
8843 }
8844
8845 if (MEASURE_GC) {
8846 gc_clock_start(&objspace->profile.sweeping_start_time);
8847 }
8848
8849 rb_gc_initialize_vm_context(&objspace->vm_context);
8850}
8851
8852static void
8853gc_sweeping_exit(rb_objspace_t *objspace)
8854{
8855 GC_ASSERT(during_gc != 0);
8856
8857 if (MEASURE_GC) {
8858 objspace->profile.sweeping_time_ns += gc_clock_end(&objspace->profile.sweeping_start_time);
8859 }
8860
8861 if (gc_prof_enabled(objspace)) {
8862 rb_hrtime_t sweep_wall_time = elapsed_hrtime_from(objspace->profile.gc_sweep_phase_wall_start_time);
8863 gc_profile_record *record = gc_prof_record(objspace);
8864 sweep_wall_time = rb_hrtime_sub(sweep_wall_time,
8865 objspace->profile.gc_sweep_excluded_wall_time);
8866 record->gc_sweep_wall_time = rb_hrtime_add(record->gc_sweep_wall_time,
8867 sweep_wall_time);
8868 objspace->profile.gc_sweep_excluded_wall_time = 0;
8869 }
8870}
8871
8872static void *
8873gc_with_gvl(void *ptr)
8874{
8875 struct objspace_and_reason *oar = (struct objspace_and_reason *)ptr;
8876 return (void *)(VALUE)garbage_collect(oar->objspace, oar->reason);
8877}
8878
8879int ruby_thread_has_gvl_p(void);
8880
8881static int
8882garbage_collect_with_gvl(rb_objspace_t *objspace, unsigned int reason)
8883{
8884 if (rb_gc_gc_disabled_global_p() || dont_gc_val()) {
8885 return TRUE;
8886 }
8887 else if (!ruby_native_thread_p()) {
8888 return TRUE;
8889 }
8890 else if (!ruby_thread_has_gvl_p()) {
8891 void *ret;
8892 struct objspace_and_reason oar;
8893 oar.objspace = objspace;
8894 oar.reason = reason;
8895 ret = rb_thread_call_with_gvl(gc_with_gvl, (void *)&oar);
8896
8897 return !!ret;
8898 }
8899 else {
8900 return garbage_collect(objspace, reason);
8901 }
8902}
8903
8904static int
8905gc_set_candidate_object_i(void *vstart, void *vend, size_t stride, void *data)
8906{
8908
8909 VALUE v = (VALUE)vstart;
8910 for (; v != (VALUE)vend; v += stride) {
8911 asan_unpoisoning_object(v) {
8912 switch (BUILTIN_TYPE(v)) {
8913 case T_NONE:
8914 case T_ZOMBIE:
8915 break;
8916 default:
8917 rb_gc_prepare_heap_process_object(v);
8918 if (!RVALUE_OLD_P(objspace, v) && !RVALUE_WB_UNPROTECTED(objspace, v)) {
8919 RVALUE_AGE_SET_CANDIDATE(objspace, v);
8920 }
8921 }
8922 }
8923 }
8924
8925 return 0;
8926}
8927
8928bool
8929rb_gc_impl_multi_objspace_p(void)
8930{
8931 return true;
8932}
8933
8934bool
8935rb_gc_impl_during_global_gc_p(void *objspace_ptr)
8936{
8937 rb_objspace_t *objspace = objspace_ptr;
8938 return objspace->flags.during_global_gc != 0;
8939}
8940
8941bool
8942rb_gc_impl_obj_foreign_p(void *objspace_ptr, VALUE obj)
8943{
8944 return gc_foreign_object_p(objspace_ptr, obj);
8945}
8946
8947
8948/* Whether obj is recorded as an unshareable object referenced from a shareable one. For
8949 * the verifier: a shareable -> unshareable edge is only accepted if the write barrier
8950 * recorded it here. */
8951bool
8952rb_gc_impl_shref_marked_p(void *objspace_ptr, VALUE obj)
8953{
8954 return MARKED_IN_BITMAP(GET_HEAP_SHREF_BITS(obj), obj) != 0;
8955}
8956
8957/* The objspace's current page count (used for the zombie_objspaces page accounting). */
8958size_t
8959rb_gc_impl_heap_page_count(void *objspace_ptr)
8960{
8961 rb_objspace_t *objspace = objspace_ptr;
8962 return rb_darray_size(objspace->heap_pages.sorted);
8963}
8964
8965static void
8966gc_global_objspaces_i(void *os, void *data)
8967{
8968 if (global_objspace->global_gc.n_objspaces == global_objspace->global_gc.objspaces_capa) {
8969 size_t new_capa = global_objspace->global_gc.objspaces_capa ? global_objspace->global_gc.objspaces_capa * 2 : 16;
8970 struct rb_objspace **new_list = realloc(global_objspace->global_gc.objspaces, new_capa * sizeof(*new_list));
8971 if (new_list == NULL) rb_bug("gc_global_objspaces_i: realloc failed");
8972 global_objspace->global_gc.objspaces = new_list;
8973 global_objspace->global_gc.objspaces_capa = new_capa;
8974 }
8975 global_objspace->global_gc.objspaces[global_objspace->global_gc.n_objspaces++] = os;
8976}
8977
8978/* Re-snapshot every objspace this cycle covers, zombies included. The objspaces/capa
8979 * buffer is reused from the previous cycle. */
8980static void
8981gc_global_snapshot_objspaces(void)
8982{
8983 global_objspace->global_gc.n_objspaces = 0;
8984 rb_gc_vm_each_objspace(gc_global_objspaces_i, NULL);
8985
8986#if RGENGC_CHECK_MODE
8987 /* Check that the incrementally maintained page_index agrees with the per-objspace
8988 * sorted arrays. */
8989 size_t total = 0;
8990 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
8991 total += rb_darray_size(global_objspace->global_gc.objspaces[i]->heap_pages.sorted);
8992 }
8993 GC_ASSERT(total == global_objspace->page_index.n_pages);
8994#endif
8995}
8996
8997/* Global GC: stop every Ractor and clear/mark/sweep all objspaces as one heap. It is the
8998 * only collector that can free shareable objects and decide cross-objspace reachability
8999 * precisely. */
9000/* The global GC's generic_fields weak pass, after the unified mark fixpoint, before the
9001 * sweep. Per-object rb_mark_generic_ivar is a no-op during a global GC (the driver has
9002 * GET_RACTOR() != owner); the whole table is swept here instead. Weak-KEY: mark the val
9003 * (fields_obj, a strong child) only for a live key, drain dead keys' entries. Marking a
9004 * val can make another key live, so repeat to a fixpoint. */
9007 bool progress;
9008};
9009
9010static int
9011genfields_mark_i(VALUE key, VALUE val, void *arg)
9012{
9013 struct genfields_mark_arg *a = (struct genfields_mark_arg *)arg;
9014 if (RB_SPECIAL_CONST_P(val) || !RVALUE_MARKED_BITMAP(key)) {
9015 return ST_CONTINUE;
9016 }
9017 /* Record the old(key)->young(val) edge with the host (key) as parent, even when val
9018 * is already marked: a conservative machine-stack scan can mark a fresh fields_obj
9019 * parentless before this pass, and branching on the mark bit would leave the key
9020 * unremembered, so the next minor GC misses the young val ("WB miss (O->Y)").
9021 * gc_mark runs rgengc_check_relation before its already-marked return: call always. */
9022 bool newly = !RVALUE_MARKED_BITMAP(val);
9023 gc_mark_set_parent(a->objspace, key);
9024 gc_mark(a->objspace, val);
9025 if (newly) a->progress = true;
9026 return ST_CONTINUE;
9027}
9028
9029static bool
9030genfields_dead_p(VALUE key)
9031{
9032 return RVALUE_MARKED_BITMAP(key) == 0;
9033}
9034
9035static void
9036gc_global_mark_generic_fields(rb_objspace_t *driver)
9037{
9038 struct genfields_mark_arg arg = { driver, false };
9039 do {
9040 arg.progress = false;
9041 /* Each entry's mark sets parent=key (genfields_mark_i) so the generational WB is
9042 * recorded correctly. gc_mark_stacked_objects_all sets its own per-object parent,
9043 * so restore the invalid parent (the poison contract) before calling it. */
9044 rb_gc_vm_generic_fields_mark_foreach(genfields_mark_i, &arg);
9045 gc_mark_set_parent_invalid(driver);
9046 if (arg.progress) {
9047 gc_mark_stacked_objects_all(driver);
9048 }
9049 } while (arg.progress);
9050
9051 rb_gc_vm_generic_fields_drain_dead(genfields_dead_p);
9052}
9053
9054/* Two Ractors choosing a global GC at once are serialized by the VM lock in gc_enter. If two
9055 * globals start concurrently, only one global will run and the other will run a local GC after
9056 * the barrier ends. */
9057static bool
9058gc_start_global(rb_objspace_t *driver, unsigned int reason, bool compact, bool allow_skip)
9059{
9060 unsigned int lock_lev;
9061 enum gc_enter_event event = allow_skip ? gc_enter_event_global_auto : gc_enter_event_global;
9062 if (!gc_enter(driver, event, &lock_lev)) {
9063 return false;
9064 }
9065
9066 /* A global GC is a collection of the driver's objspace too, and its profile.count
9067 * below says so, so report it like a local one. The driver is the objspace whose
9068 * count moves, which is the one a hook reading GC.stat would compare against. For
9069 * the same reason it records a profile entry and reports what triggered it. */
9070 gc_start_record(driver, reason, true);
9071 gc_event_hook(driver, RUBY_INTERNAL_EVENT_GC_START);
9072 gc_prof_timer_start(driver);
9073
9074 GC_ASSERT(is_mark_stack_empty(&driver->mark_stack));
9075
9076 gc_global_snapshot_objspaces();
9077
9078 /* Mark every objspace as in a global GC before step 3 settles the lazy sweeps: the
9079 * settle frees other objspaces' garbage on the driver thread, and
9080 * rb_free_generic_ivar must see "global GC in progress" to defer generic_fields
9081 * removal to the weak-pass drain. */
9082 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9083 global_objspace->global_gc.objspaces[i]->flags.during_global_gc = TRUE;
9084 }
9085
9086 /* A global GC collects every objspace, so each needs the malloc-counter reset the
9087 * driver got in gc_start_record; without it, gc_sweep_finish advancing free_at_last_gc
9088 * (step 9) would leave their malloc_increase overstated by everything swept here. */
9089 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9090 rb_objspace_t *const os = global_objspace->global_gc.objspaces[i];
9091 if (os != driver) {
9092 os->profile.latest_gc_info = reason;
9093 gc_reset_malloc_info(os, true);
9094 }
9095 }
9096
9097 /* step 3: settle every lazy sweep so the mark bits' meaning is fixed before the clear
9098 * below. (during_gc is a macro over the local "objspace".) rb_gc_get_ec() resolves
9099 * through objspace->vm_context during a GC, so initialize it for all: the driver
9100 * thread runs every objspace's phases. */
9101 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9102 rb_objspace_t *objspace = global_objspace->global_gc.objspaces[i];
9103 /* No objspace can be mid-incremental-mark here: that only runs single-objspace
9104 * and vm_insert_ractor0 settles it on the transition. Clearing flags in step 5
9105 * under a live gray stack would break the owner's GC state machine. */
9106 GC_ASSERT(!is_incremental_marking(objspace));
9107 GC_ASSERT(is_mark_stack_empty(&objspace->mark_stack));
9108 rb_gc_initialize_vm_context(&objspace->vm_context);
9109 if (objspace != driver) during_gc = TRUE;
9110 gc_sweep_rest(objspace);
9111 }
9112
9113 /* step 5: clear every objspace's mark bits, remembered sets, generation counters and
9114 * shrefs (missing even one leaves a stale mark bit and a UAF). (heaps is a macro over
9115 * the local "objspace".) */
9116 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9117 rb_objspace_t *objspace = global_objspace->global_gc.objspaces[i];
9118 objspace->flags.during_minor_gc = FALSE;
9119 objspace->flags.during_incremental_marking = FALSE;
9120 /* The unified mark is precise and does not pin, so the per-objspace sweep below must
9121 * not re-check against a stale local cycle. */
9122 objspace->last_cycle_pinned = 0;
9123 objspace->rgengc.uncollectible_wb_unprotected_objects = 0;
9124 objspace->rgengc.old_objects = 0;
9125 objspace->rgengc.last_major_gc = objspace->profile.count;
9126 objspace->marked_slots = 0;
9127 for (int h = 0; h < HEAP_COUNT; h++) {
9128 rb_heap_t *heap = &heaps[h];
9129 gc_bitmaps_clear(objspace, heap, true);
9130 heap_move_pooled_pages_to_free_pages(heap);
9131 }
9132 }
9133 driver->profile.major_gc_count++;
9134
9135 /* Enable compaction in every objspace before the mark: the unified conservative root
9136 * scan then pins machine-stack referents (gc_pin only pins while during_compacting)
9137 * and step 9's sweep relocates the rest. global_gc.compacting defers the
9138 * reference-update phase to phase 2 below (two phases, safe across objspaces). */
9139 global_objspace->global_gc.compacting = compact;
9140 if (compact) {
9141 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9142 rb_objspace_t *objspace = global_objspace->global_gc.objspaces[i];
9143 objspace->flags.during_compacting = TRUE;
9144 /* A global GC skips gc_marks_start, which is what resets pinned_slots for a
9145 * compacting local GC, so reset it here. step 5 cleared pinned_bits; the
9146 * conservative mark re-pins machine-stack referents. */
9147 for (int h = 0; h < HEAP_COUNT; h++) {
9148 struct heap_page *page = NULL;
9149 ccan_list_for_each(&heaps[h].pages, page, page_node) {
9150 page->pinned_slots = 0;
9151 }
9152 }
9153 }
9154 }
9155
9156 /* steps 6-7: every Ractor's roots (gc.c walks them all), then one unified precise
9157 * mark. A global GC does not go through gc_marks, so the marking
9158 * phase is opened here instead; it closes after rb_ractor_finish_marking below, which is
9159 * where gc_marks_finish ends for a local collection. */
9160 gc_marking_enter(driver);
9161
9162 mark_roots(driver, NULL);
9163 gc_mark_stacked_objects_all(driver);
9164
9165 /* Run the generic_fields weak pass after the mark fixpoint: mark the vals (fields_obj)
9166 * of live keys and drain the entries of dead ones. The per-object rb_mark_generic_ivar
9167 * is a no-op during a global GC, so this is the only path that marks generic_fields. */
9168 gc_global_mark_generic_fields(driver);
9169
9170 gc_event_hook(driver, RUBY_INTERNAL_EVENT_GC_END_MARK);
9171
9172 /* step 8 */
9173 gc_update_weak_references(driver);
9174
9175 /* This cycle's root pass over every Ractor has swept the deleted ractor-local keys out of
9176 * each storage. Free the key structs while still inside the barrier (a local GC never
9177 * can; see rb_ractor_finish_marking). */
9178 rb_ractor_finish_marking(true);
9179
9180 gc_marking_exit(driver);
9181
9182 /* step 9: sweep every objspace inside the barrier, not lazily. Dead shareable objects
9183 * are reclaimed here and emptied pages go back to the pool. */
9184 if (!compact) {
9185 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9186 rb_objspace_t *os = global_objspace->global_gc.objspaces[i];
9187 unsigned int prev_immediate = os->flags.immediate_sweep;
9188 os->flags.immediate_sweep = TRUE;
9189 gc_sweep(os);
9190 os->flags.immediate_sweep = prev_immediate;
9191 }
9192 }
9193 else {
9194 /* The move -> update-references -> free flow runs as three passes across ALL
9195 * objspaces, not per objspace: (a) updating references must see every objspace's
9196 * forwarding (a reference can point at a moved foreign object), and (b) freeing
9197 * source pages must wait until everyone is updated (or another objspace's update
9198 * reads a freed T_MOVED). The read barrier is installed once for all passes. */
9199 install_handlers();
9200
9201 /* Only the driver records a profile entry for a global GC (gc_start_record), so time
9202 * only the driver's compaction work. The move/update/free below runs inside the
9203 * driver's sweep phase (gc_sweeping_enter/exit); attribute it to GC_COMPACT_WALL_TIME
9204 * and exclude it from the driver's sweep wall time so the two do not double-count,
9205 * mirroring the compacting branch of the local gc_sweep(). */
9206 const bool driver_prof = gc_prof_enabled(driver);
9207 rb_hrtime_t driver_compact_wall_time = 0;
9208
9209 /* pass 1 (move): relocate every objspace and leave T_MOVED forwarding behind. */
9210 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9211 rb_objspace_t *os = global_objspace->global_gc.objspaces[i];
9212 gc_sweeping_enter(os);
9213 gc_sweep_start(os); /* mode -> sweeping, order the heap for compaction */
9214 rb_hrtime_t t0 = (os == driver && driver_prof) ? rb_hrtime_now() : 0;
9215 gc_compact_relocate(os); /* mode -> compacting, move */
9216 if (os == driver && driver_prof) {
9217 driver_compact_wall_time = rb_hrtime_add(driver_compact_wall_time, elapsed_hrtime_from(t0));
9218 }
9219 }
9220
9221 /* pass 2 (update): all forwarding now exists, so update every objspace's
9222 * references (cross-objspace ones resolve too); gc_compact_finish also unprotects
9223 * pages and clears during_compacting. The move-or-mark decision reads
9224 * rb_gc_get_objspace()'s during_reference_updating: set it on every objspace. */
9225 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9226 global_objspace->global_gc.objspaces[i]->flags.during_reference_updating = TRUE;
9227 }
9228 rb_gc_before_updating_jit_code();
9229 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9230 rb_objspace_t *os = global_objspace->global_gc.objspaces[i];
9231 rb_hrtime_t t0 = (os == driver && driver_prof) ? rb_hrtime_now() : 0;
9232 gc_compact_finish(os);
9233 if (os == driver && driver_prof) {
9234 driver_compact_wall_time = rb_hrtime_add(driver_compact_wall_time, elapsed_hrtime_from(t0));
9235 }
9236 }
9237 /* The VM-global / weak-table side of the reference update runs once (each objspace's
9238 * heap side already ran in gc_compact_finish above). */
9239 {
9240 rb_hrtime_t t0 = driver_prof ? rb_hrtime_now() : 0;
9241 gc_update_references_global(driver);
9242 if (driver_prof) {
9243 driver_compact_wall_time = rb_hrtime_add(driver_compact_wall_time, elapsed_hrtime_from(t0));
9244 }
9245 }
9246 rb_gc_after_updating_jit_code();
9247 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9248 global_objspace->global_gc.objspaces[i]->flags.during_reference_updating = FALSE;
9249 global_objspace->global_gc.objspaces[i]->flags.during_compacting = FALSE;
9250 }
9251 global_objspace->global_gc.compacting = false;
9252 uninstall_handlers();
9253
9254 /* Record the driver's compaction time and exclude it from the driver's sweep phase.
9255 * gc_sweeping_exit(driver) in pass 3 subtracts gc_sweep_excluded_wall_time from the
9256 * sweep wall time, so this must be set before it runs. The excluded value is a sum
9257 * of sub-intervals of the driver's sweep phase, so the subtraction cannot underflow. */
9258 if (driver_prof) {
9259 gc_profile_record *const record = gc_prof_record(driver);
9260 record->gc_compact_wall_time = rb_hrtime_add(record->gc_compact_wall_time,
9261 driver_compact_wall_time);
9262 driver->profile.gc_sweep_excluded_wall_time = rb_hrtime_add(
9263 driver->profile.gc_sweep_excluded_wall_time, driver_compact_wall_time);
9264 }
9265
9266 /* pass 3 (free): page-sweep every objspace, freeing dead objects and the source pages
9267 * that are now empty. during_compacting is already cleared, so the sweep treats
9268 * T_MOVED as usual. */
9269 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9270 rb_objspace_t *os = global_objspace->global_gc.objspaces[i];
9271 gc_sweep_rest(os);
9272 gc_sweeping_exit(os);
9273 }
9274 }
9275 global_objspace->global_gc.compacting = false;
9276
9277 /* A global GC never calls gc_marks_finish, which budgets heap growth
9278 * (allocatable_bytes). An objspace still full after the global sweep (materializing
9279 * a large received copy, say) has no free pages, no empty pages, budget 0, and its next
9280 * allocation would hit newobj_refill's "cannot create a new page after a major GC".
9281 * Give every objspace stuck like that the growth budget gc_marks_finish would. */
9282 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9283 rb_objspace_t *objspace = global_objspace->global_gc.objspaces[i];
9284 if (objspace->heap_pages.allocatable_bytes != 0 || objspace->empty_pages_count != 0) {
9285 continue;
9286 }
9287 bool stuck = false;
9288 for (int h = 0; h < HEAP_COUNT; h++) {
9289 if (heaps[h].free_pages == NULL) { stuck = true; break; }
9290 }
9291 if (stuck) {
9292 heap_allocatable_bytes_expand(objspace, NULL, 0,
9293 objspace_available_slots(objspace), heaps[0].slot_size);
9294 }
9295 }
9296
9297 /* Recount the surviving shareable objects (the sweep already folded the dead ones out of
9298 * shareable_bits) and reset each trigger limit. */
9299 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9300 rb_objspace_t *objspace = global_objspace->global_gc.objspaces[i];
9301 size_t survivors = 0;
9302 for (int h = 0; h < HEAP_COUNT; h++) {
9303 struct heap_page *page = NULL;
9304 ccan_list_for_each(&heaps[h].pages, page, page_node) {
9305 if (!page->flags.has_shareable_objects) continue;
9306 for (int j = 0; j < HEAP_PAGE_BITMAP_LIMIT; j++) {
9307 survivors += rb_popcount_intptr(page->shareable_bits[j]);
9308 }
9309 }
9310 }
9311 objspace->shareable_objects = survivors;
9312 size_t new_limit = (size_t)(survivors * SHAREABLE_OBJECTS_LIMIT_FACTOR);
9313 if (new_limit < SHAREABLE_OBJECTS_LIMIT_MIN) new_limit = SHAREABLE_OBJECTS_LIMIT_MIN;
9314 objspace->shareable_objects_limit = new_limit;
9315 }
9316 driver->profile.count++;
9317
9318 /* step 10 */
9319 for (size_t i = 0; i < global_objspace->global_gc.n_objspaces; i++) {
9320 rb_objspace_t *objspace = global_objspace->global_gc.objspaces[i];
9321 objspace->flags.during_global_gc = FALSE;
9322 if (objspace != driver) during_gc = FALSE;
9323 }
9324
9325 /* The unified mark re-established the reachability of absorbed shareable objects, so a
9326 * single objspace's local mark is trustworthy again (pinning can be skipped until the
9327 * next absorb). */
9328 rb_gc_reset_absorbed_since_global_gc();
9329
9330 /* Re-measure the zombie_objspaces table now that the garbage is gone; entries are stable
9331 * inside the barrier. Without this, the page trigger above keeps firing on the stale
9332 * numbers left when a joinable (slotted) zombie retires without any pass merging it. */
9333 rb_gc_vm_refresh_zombie_pages();
9334 global_objspace->zombie_pages_survivors = rb_gc_vm_zombie_total_pages();
9335
9336 /* If the sweep above collected an unjoined Ractor object, ractor_free disowned its
9337 * zombie_objspaces entry and posted the merge to main as a postponed job; the objspace
9338 * stays enumerable until main absorbs it at its next safepoint. */
9339
9340 gc_prof_timer_stop(driver);
9341 gc_exit(driver, event, &lock_lev);
9342 return true;
9343}
9344
9345static int
9346absorb_finalizer_i(st_data_t key, st_data_t val, st_data_t data)
9347{
9349 st_insert(finalizer_table, key, val);
9350 return ST_CONTINUE;
9351}
9352
9353/* Merge a dead Ractor's objspace into dst under the VM lock. src has no owner thread and
9354 * dst is the calling thread's own objspace (join/value) or main with everyone stopped
9355 * (global GC), so single-writer holds throughout. Pages move whole (their bits describe
9356 * objects, not the objspace), and dst's next collection is forced full to rebuild the
9357 * generational state. */
9358static void
9359objspace_absorb(rb_objspace_t *dst, rb_objspace_t *src)
9360{
9361 GC_ASSERT(dst != src);
9362
9363 /* Suppress the cross-objspace verifier checks while the graph is in flux (see
9364 * global_objspace->during_absorb). */
9365 const bool prev_absorb = global_objspace->during_absorb;
9366 global_objspace->during_absorb = true;
9367
9368 /* Settle dst first: adding pages under a walking lazy-sweep cursor, or into a
9369 * half-marked incremental heap, would sweep the merged pages with src's stale mark
9370 * bits and free live objects. (Normally settled already: vm_insert_ractor0's settle
9371 * means no objspace is incremental while a zombie waits to be absorbed.) */
9372 gc_rest(dst);
9373
9374 /* Settle src: no lazy sweep and no in-progress allocation page. */
9375 {
9376 rb_objspace_t *objspace = src;
9377 during_gc = TRUE;
9378 gc_sweep_rest(objspace);
9379 during_gc = FALSE;
9380 heap_alloc_state_clear(objspace);
9381 /* gc_sweep_finish leaves swept pages "pooled" for a coming incremental mark; src
9382 * never runs one (it is about to be merged), so return them to its free list now,
9383 * restoring the pooled_pages == NULL the page merge below assumes (mirrors
9384 * gc_start_global step 3). */
9385 for (int h = 0; h < HEAP_COUNT; h++) {
9386 heap_move_pooled_pages_to_free_pages(&heaps[h]);
9387 }
9388 }
9389
9390 /* From here the merge must not run dst's GC: the finalizer st_insert below can cross
9391 * the malloc-accounting threshold, and a GC then would sweep src's detached finalizer
9392 * procs, reachable only from this C frame, into dangling VALUEs. Page/darray moves
9393 * allocate nothing (_without_gc), so disabling costs nothing and makes the splice
9394 * atomic. (The two settles above deliberately collect: they stay outside.) */
9395 const bool dst_gc_was_enabled = rb_gc_impl_gc_enabled_p(dst);
9396 if (dst_gc_was_enabled) rb_gc_impl_gc_disable(dst, false);
9397
9398 /* Hand over the pages size pool by size pool. ("heaps" is a macro over the local
9399 * objspace, so the arrays are taken through scoped locals.) */
9400 rb_heap_t *dst_heaps;
9401 rb_heap_t *src_heaps;
9402 {
9403 rb_objspace_t *objspace = dst;
9404 dst_heaps = heaps;
9405 }
9406 {
9407 rb_objspace_t *objspace = src;
9408 src_heaps = heaps;
9409 }
9410 for (int h = 0; h < HEAP_COUNT; h++) {
9411 rb_heap_t *dheap = &dst_heaps[h];
9412 rb_heap_t *sheap = &src_heaps[h];
9413 struct heap_page *page = NULL;
9414
9415 GC_ASSERT(sheap->sweeping_page == NULL);
9416 GC_ASSERT(sheap->pooled_pages == NULL);
9417
9418 ccan_list_for_each(&sheap->pages, page, page_node) {
9419 page->objspace = dst;
9420 page->heap = dheap;
9421 }
9422 ccan_list_append_list(&dheap->pages, &sheap->pages);
9423
9424 /* Append the free-page chain to the tail. */
9425 if (sheap->free_pages) {
9426 struct heap_page **tail = &dheap->free_pages;
9427 while (*tail) tail = &(*tail)->free_next;
9428 *tail = sheap->free_pages;
9429 sheap->free_pages = NULL;
9430 }
9431
9432 dheap->total_pages += sheap->total_pages;
9433 dheap->total_slots += sheap->total_slots;
9434 dheap->total_allocated_pages += sheap->total_allocated_pages;
9435 dheap->total_allocated_objects += sheap->total_allocated_objects;
9436 dheap->total_freed_objects += sheap->total_freed_objects;
9437 dheap->final_slots_count += sheap->final_slots_count;
9438 }
9439
9440 /* The objspace-wide page bookkeeping. */
9441 {
9442 rb_objspace_t *objspace = dst; /* for the heap_pages_* macros */
9443 struct heap_page *page = NULL;
9444 size_t srcn = rb_darray_size(src->heap_pages.sorted);
9445 for (size_t i = 0; i < srcn; i++) {
9446 page = rb_darray_get(src->heap_pages.sorted, i);
9447 /* Residents of the empty pool (no live objects) are returned to page_pool rather
9448 * than inherited; dst's allocation demand is cheaply met from the shared pool's
9449 * free list. */
9450 if (heap_page_in_global_empty_pages_pool(src, page)) {
9451 heap_page_free(src, page);
9452 continue;
9453 }
9454 uintptr_t body = (uintptr_t)page->body;
9455 uintptr_t start = body + sizeof(struct heap_page_header);
9456 uintptr_t end = body + HEAP_PAGE_SIZE;
9457
9458 /* Keep the array ordered by page BODY address: heap_page_for_ptr bsearches
9459 * body ranges, and a detached empty page has start == 0, so ordering by
9460 * page->start would miss live pages (a global GC would then fail to mark a
9461 * registered root and sweep it). */
9462 size_t lo = 0;
9463 size_t hi = rb_darray_size(objspace->heap_pages.sorted);
9464 while (lo < hi) {
9465 size_t mid = (lo + hi) / 2;
9466 struct heap_page *mid_page = rb_darray_get(objspace->heap_pages.sorted, mid);
9467 if ((uintptr_t)mid_page->body < body) lo = mid + 1;
9468 else hi = mid;
9469 }
9470 rb_darray_insert_without_gc(&objspace->heap_pages.sorted, hi, page);
9471
9472 if (heap_pages_lomem == 0 || heap_pages_lomem > start) heap_pages_lomem = start;
9473 if (heap_pages_himem < end) heap_pages_himem = end;
9474 }
9475 objspace->heap_pages.allocated_pages += src->heap_pages.allocated_pages;
9476 objspace->heap_pages.freed_pages += src->heap_pages.freed_pages;
9477 rb_darray_free_without_gc(src->heap_pages.sorted);
9478 src->heap_pages.sorted = NULL;
9479 /* The empty_pages chain's structs were freed in the loop above. */
9480 src->empty_pages = NULL;
9481 src->empty_pages_count = 0;
9482 }
9483
9484 /* Finalizers: move the table's entries, and the dead Ractor's deferred zombies are run
9485 * by dst's thread from now on. */
9486 {
9487 st_table *src_finalizers;
9488 {
9489 rb_objspace_t *objspace = src;
9490 src_finalizers = finalizer_table;
9491 finalizer_table = NULL;
9492 }
9493 if (src_finalizers) {
9494 rb_objspace_t *objspace = dst;
9495 if (finalizer_table == NULL) {
9496 finalizer_table = src_finalizers;
9497 }
9498 else {
9499 st_foreach(src_finalizers, absorb_finalizer_i, (st_data_t)dst);
9500 st_free_table(src_finalizers);
9501 }
9502 }
9503 }
9504 {
9505 VALUE src_deferred = RUBY_ATOMIC_VALUE_EXCHANGE(src->heap_pages.deferred_final, 0);
9506 if (src_deferred) {
9507 VALUE tail_obj = src_deferred;
9508 rb_asan_unpoison_object(tail_obj, false);
9509 while (RZOMBIE(tail_obj)->next) {
9510 VALUE next_obj = RZOMBIE(tail_obj)->next;
9511 rb_asan_poison_object(tail_obj);
9512 tail_obj = next_obj;
9513 rb_asan_unpoison_object(tail_obj, false);
9514 }
9515 VALUE prev;
9516 do {
9517 prev = dst->heap_pages.deferred_final;
9518 RZOMBIE(tail_obj)->next = prev;
9519 } while (RUBY_ATOMIC_VALUE_CAS(dst->heap_pages.deferred_final, prev, src_deferred) != prev);
9520 rb_asan_poison_object(tail_obj);
9521 /* No owner was left to run these zombies (register's owner walk misses a dead
9522 * Ractor). dst runs this merge, so schedule dst's job here; otherwise they wait
9523 * until dst's next GC. */
9524 rb_postponed_job_trigger(dst->finalize_deferred_pjob);
9525 }
9526 }
9527
9528 /* Counters inherited by dst. */
9529 dst->rgengc.old_objects += src->rgengc.old_objects;
9530 dst->rgengc.uncollectible_wb_unprotected_objects += src->rgengc.uncollectible_wb_unprotected_objects;
9531 dst->shareable_objects += src->shareable_objects;
9532
9533 /* Merged pages carry src's mark/age state, so dst rebuilds its view at the next
9534 * collection. */
9535 dst->rgengc.need_major_gc |= GPR_FLAG_MAJOR_BY_FORCE;
9536
9537 /* src's outstanding malloc pressure moves with the xmalloc'd buffers. Later frees are
9538 * charged to dst, so without this transfer dst underestimates its own heap and delays
9539 * GCs. dst is live, so take its counter lock where gc_counter_add is not atomic. */
9540 {
9541 int64_t inc = gc_malloc_counters_increase(src, &src->malloc_counters.counters);
9542#if RGENGC_ESTIMATE_OLDMALLOC
9543 int64_t oldinc = gc_malloc_counters_increase(src, &src->malloc_counters.oldcounters);
9544#endif
9545 MALLOC_COUNTERS_LOCK(dst);
9546 if (inc > 0) gc_counter_add(&dst->malloc_counters.counters.malloc, (size_t)inc);
9547#if RGENGC_ESTIMATE_OLDMALLOC
9548 if (oldinc > 0) gc_counter_add(&dst->malloc_counters.oldcounters.malloc, (size_t)oldinc);
9549#endif
9550 MALLOC_COUNTERS_UNLOCK(dst);
9551 }
9552
9553 /* Free the shell (as rb_gc_impl_objspace_free does). */
9554 free(src->profile.records);
9555 free_stack_chunks(&src->mark_stack);
9556 mark_stack_free_cache(&src->mark_stack);
9557 GC_ASSERT(rb_darray_size(src->weak_references) == 0);
9558 rb_darray_free_without_gc(src->weak_references);
9559#ifdef MALLOC_COUNTERS_NEED_LOCK
9560 rb_native_mutex_destroy(&src->malloc_counters.lock);
9561#endif
9562 free(src);
9563
9564 if (dst_gc_was_enabled) rb_gc_impl_gc_enable(dst);
9565
9566 /* Return the empty pages inheritance piled up in dst (mostly from the dead Ractor's
9567 * teardown material) to the pool with no budget. An empty page is by definition safe to
9568 * release, and re-acquiring one from the pool is cheap. */
9569 {
9570 rb_objspace_t *objspace = dst;
9571 heap_pages_freeable_pages = objspace->empty_pages_count;
9572 heap_pages_free_unused_pages(objspace);
9573 }
9574
9575 global_objspace->during_absorb = prev_absorb;
9576}
9577
9578void
9579rb_gc_impl_objspace_absorb(void *dst_ptr, void *src_ptr)
9580{
9581 objspace_absorb(dst_ptr, src_ptr);
9582}
9583
9584void
9585rb_gc_impl_start(void *objspace_ptr, bool full_mark, bool immediate_mark, bool immediate_sweep, bool compact)
9586{
9587 rb_objspace_t *objspace = objspace_ptr;
9588 unsigned int reason = (GPR_FLAG_FULL_MARK |
9589 GPR_FLAG_IMMEDIATE_MARK |
9590 GPR_FLAG_IMMEDIATE_SWEEP |
9591 GPR_FLAG_METHOD);
9592
9593 int full_marking_p = gc_config_full_mark_val;
9594 gc_config_full_mark_set(TRUE);
9595
9596 /* With multiple objspaces the global GC's barrier makes relocation and the two-phase
9597 * reference update safe across all of them (gc_start_global with compact=true below);
9598 * single-objspace compaction takes the usual local path (gc_start w/ during_compacting). */
9599
9600 /* For now, compact implies full mark / sweep, so ignore other flags */
9601 if (compact) {
9602 GC_ASSERT(GC_COMPACTION_SUPPORTED);
9603
9604 reason |= GPR_FLAG_COMPACT;
9605 }
9606 else {
9607 if (!full_mark) reason &= ~GPR_FLAG_FULL_MARK;
9608 if (!immediate_mark) reason &= ~GPR_FLAG_IMMEDIATE_MARK;
9609 if (!immediate_sweep) reason &= ~GPR_FLAG_IMMEDIATE_SWEEP;
9610 }
9611
9612 /* An explicit full GC.start with multiple objspaces runs a global GC, the only
9613 * collector that reclaims shareable and cross-objspace garbage. It stops the world,
9614 * so auto_compact is honoured here too (mirroring full mark x autocompact locally). */
9615 if (!rb_gc_single_objspace_p() && (reason & GPR_FLAG_FULL_MARK)) {
9616 gc_start_global(objspace, reason, compact || ruby_enable_autocompact, false);
9617 }
9618 else {
9619 garbage_collect(objspace, reason);
9620 }
9621
9622 gc_finalize_deferred(objspace);
9623 gc_config_full_mark_set(full_marking_p);
9624}
9625
9626void
9627rb_gc_impl_prepare_heap(void *objspace_ptr)
9628{
9629 rb_objspace_t *objspace = objspace_ptr;
9630
9631 rb_gc_impl_each_objects(objspace, gc_set_candidate_object_i, objspace_ptr);
9632
9633 double orig_max_free_slots = gc_params.heap_free_slots_max_ratio;
9634 /* Ensure that all empty pages are moved onto empty_pages. */
9635 gc_params.heap_free_slots_max_ratio = 0.0;
9636 rb_gc_impl_start(objspace, true, true, true, true);
9637 gc_params.heap_free_slots_max_ratio = orig_max_free_slots;
9638
9639 objspace->heap_pages.allocatable_bytes = 0;
9640 heap_pages_freeable_pages = objspace->empty_pages_count;
9641 heap_pages_free_unused_pages(objspace_ptr);
9642 GC_ASSERT(heap_pages_freeable_pages == 0);
9643 GC_ASSERT(objspace->empty_pages_count == 0);
9644
9645 // Process.warmup is meant to be called at the end of the boot sequence, which is commonly allocation
9646 // heavy and result in GC limits raising significantly, but it's not indicative of the limits needed
9647 // for runtime.
9648 // Recompute the allocatable_bytes limit based on `gc_params.heap_init_bytes`.
9649 GC_ASSERT(objspace->heap_pages.allocatable_bytes == 0);
9650 for (int i = 0; i < HEAP_COUNT; i++) {
9651 rb_heap_t *heap = &heaps[i];
9652 heap_allocatable_bytes_expand(objspace, heap, heap->empty_slots, heap->total_slots, heap->slot_size);
9653 }
9654
9655#if defined(HAVE_MALLOC_TRIM) && !defined(RUBY_ALTERNATIVE_MALLOC_HEADER)
9656 malloc_trim(0);
9657#endif
9658}
9659
9660static int
9661gc_is_moveable_obj(rb_objspace_t *objspace, VALUE obj)
9662{
9663 GC_ASSERT(!SPECIAL_CONST_P(obj));
9664
9665 switch (BUILTIN_TYPE(obj)) {
9666 case T_NONE:
9667 case T_MOVED:
9668 case T_ZOMBIE:
9669 return FALSE;
9670 case T_SYMBOL:
9671 case T_STRING:
9672 case T_OBJECT:
9673 case T_FLOAT:
9674 case T_IMEMO:
9675 case T_ARRAY:
9676 case T_BIGNUM:
9677 case T_ICLASS:
9678 case T_MODULE:
9679 case T_REGEXP:
9680 case T_DATA:
9681 case T_MATCH:
9682 case T_STRUCT:
9683 case T_HASH:
9684 case T_FILE:
9685 case T_COMPLEX:
9686 case T_RATIONAL:
9687 case T_NODE:
9688 case T_CLASS:
9689 if (FL_TEST_RAW(obj, FL_FINALIZE)) {
9690 /* The finalizer table is a numtable. It looks up objects by address.
9691 * We can't mark the keys in the finalizer table because that would
9692 * prevent the objects from being collected. This check prevents
9693 * objects that are keys in the finalizer table from being moved
9694 * without directly pinning them. */
9695 GC_ASSERT(st_is_member(finalizer_table, obj));
9696
9697 return FALSE;
9698 }
9699 GC_ASSERT(RVALUE_MARKED(objspace, obj));
9700 GC_ASSERT(!RVALUE_PINNED(objspace, obj));
9701
9702 return TRUE;
9703
9704 default:
9705 rb_bug("gc_is_moveable_obj: unreachable (%d)", (int)BUILTIN_TYPE(obj));
9706 break;
9707 }
9708
9709 return FALSE;
9710}
9711
9712void rb_mv_generic_ivar(VALUE src, VALUE dst);
9713
9714static VALUE
9715gc_move(rb_objspace_t *objspace, VALUE src, VALUE dest, struct heap_page *src_page, struct heap_page *dest_page)
9716{
9717 size_t src_slot_size = src_page->slot_size;
9718 size_t slot_size = dest_page->slot_size;
9719
9720 int marked;
9721 int wb_unprotected;
9722 int uncollectible;
9723 int age;
9724
9725 gc_report(4, objspace, "Moving object: %p -> %p\n", (void *)src, (void *)dest);
9726
9727 GC_ASSERT(BUILTIN_TYPE(src) != T_NONE);
9728 GC_ASSERT(!MARKED_IN_BITMAP(GET_HEAP_MARK_BITS(dest), dest));
9729
9730 GC_ASSERT(!RVALUE_MARKING(objspace, src));
9731
9732 /* Save off bits for current object. */
9733 marked = RVALUE_MARKED(objspace, src);
9734 wb_unprotected = RVALUE_WB_UNPROTECTED(objspace, src);
9735 uncollectible = RVALUE_UNCOLLECTIBLE(objspace, src);
9736 bool remembered = RVALUE_REMEMBERED(objspace, src);
9737 /* Pin bits travel with the object. Losing one during single-objspace compaction would
9738 * silently unpin it once the process goes multi-objspace, letting a local GC free a method
9739 * entry or shref target that another Ractor references. */
9740 bool shareable = MARKED_IN_BITMAP(GET_HEAP_SHAREABLE_BITS(src), src) != 0;
9741 bool shref = MARKED_IN_BITMAP(GET_HEAP_SHREF_BITS(src), src) != 0;
9742 age = RVALUE_AGE_GET(src);
9743
9744 /* Clear bits for eventual T_MOVED */
9745 CLEAR_IN_BITMAP(GET_HEAP_MARK_BITS(src), src);
9746 CLEAR_IN_BITMAP(GET_HEAP_WB_UNPROTECTED_BITS(src), src);
9747 CLEAR_IN_BITMAP(GET_HEAP_UNCOLLECTIBLE_BITS(src), src);
9748 CLEAR_IN_BITMAP(GET_HEAP_PAGE(src)->remembered_bits, src);
9749 CLEAR_IN_BITMAP(GET_HEAP_SHAREABLE_BITS(src), src);
9750 CLEAR_IN_BITMAP(GET_HEAP_SHREF_BITS(src), src);
9751
9752 /* Move the object */
9753 memcpy((void *)dest, (void *)src, MIN(src_slot_size, slot_size));
9754
9755 if (src_slot_size != slot_size) {
9756 rb_gc_obj_changed_slot_size(dest, slot_size - RVALUE_OVERHEAD);
9757 }
9758
9759 if (RVALUE_OVERHEAD > 0) {
9760 void *dest_overhead = (void *)(((uintptr_t)dest) + slot_size - RVALUE_OVERHEAD);
9761 void *src_overhead = (void *)(((uintptr_t)src) + src_slot_size - RVALUE_OVERHEAD);
9762
9763 memcpy(dest_overhead, src_overhead, RVALUE_OVERHEAD);
9764 }
9765
9766 memset((void *)src, 0, src_slot_size);
9767 RVALUE_AGE_SET_BITMAP(src, 0);
9768
9769 /* Set bits for object in new location */
9770 if (remembered) {
9771 MARK_IN_BITMAP(GET_HEAP_PAGE(dest)->remembered_bits, dest);
9772 }
9773 else {
9774 CLEAR_IN_BITMAP(GET_HEAP_PAGE(dest)->remembered_bits, dest);
9775 }
9776
9777 if (marked) {
9778 MARK_IN_BITMAP(GET_HEAP_MARK_BITS(dest), dest);
9779 }
9780 else {
9781 CLEAR_IN_BITMAP(GET_HEAP_MARK_BITS(dest), dest);
9782 }
9783
9784 if (wb_unprotected) {
9785 MARK_IN_BITMAP(GET_HEAP_WB_UNPROTECTED_BITS(dest), dest);
9786 }
9787 else {
9788 CLEAR_IN_BITMAP(GET_HEAP_WB_UNPROTECTED_BITS(dest), dest);
9789 }
9790
9791 if (uncollectible) {
9792 MARK_IN_BITMAP(GET_HEAP_UNCOLLECTIBLE_BITS(dest), dest);
9793 }
9794 else {
9795 CLEAR_IN_BITMAP(GET_HEAP_UNCOLLECTIBLE_BITS(dest), dest);
9796 }
9797
9798 if (shareable) {
9799 MARK_IN_BITMAP(GET_HEAP_SHAREABLE_BITS(dest), dest);
9800 GET_HEAP_PAGE(dest)->flags.has_shareable_objects = TRUE;
9801 }
9802 else {
9803 CLEAR_IN_BITMAP(GET_HEAP_SHAREABLE_BITS(dest), dest);
9804 }
9805
9806 if (shref) {
9807 MARK_IN_BITMAP(GET_HEAP_SHREF_BITS(dest), dest);
9808 GET_HEAP_PAGE(dest)->flags.has_shref_objects = TRUE;
9809 }
9810 else {
9811 CLEAR_IN_BITMAP(GET_HEAP_SHREF_BITS(dest), dest);
9812 }
9813
9814 RVALUE_AGE_SET(dest, age);
9815
9816 /* A re-embedded object (rb_gc_obj_changed_slot_size) references its
9817 * former fields_obj's contents directly; the write-barrier history
9818 * lived on the discarded fields_obj, so remember the object. */
9819 if (src_slot_size != slot_size && age >= RVALUE_OLD_AGE && !remembered) {
9820 rgengc_remember(objspace, dest);
9821 }
9822
9823 /* Assign forwarding address */
9824 RMOVED(src)->flags = T_MOVED;
9825 RMOVED(src)->dummy = Qundef;
9826 RMOVED(src)->destination = dest;
9827 GC_ASSERT(BUILTIN_TYPE(dest) != T_NONE);
9828
9829 GET_HEAP_PAGE(src)->heap->total_freed_objects++;
9830 GET_HEAP_PAGE(dest)->heap->total_allocated_objects++;
9831
9832 return src;
9833}
9834
9835#if GC_CAN_COMPILE_COMPACTION
9836static int
9837compare_pinned_slots(const void *left, const void *right, void *dummy)
9838{
9839 struct heap_page *left_page;
9840 struct heap_page *right_page;
9841
9842 left_page = *(struct heap_page * const *)left;
9843 right_page = *(struct heap_page * const *)right;
9844
9845 return left_page->pinned_slots - right_page->pinned_slots;
9846}
9847
9848static int
9849compare_free_slots(const void *left, const void *right, void *dummy)
9850{
9851 struct heap_page *left_page;
9852 struct heap_page *right_page;
9853
9854 left_page = *(struct heap_page * const *)left;
9855 right_page = *(struct heap_page * const *)right;
9856
9857 return left_page->free_slots - right_page->free_slots;
9858}
9859
9860static void
9861gc_sort_heap_by_compare_func(rb_objspace_t *objspace, gc_compact_compare_func compare_func)
9862{
9863 for (int j = 0; j < HEAP_COUNT; j++) {
9864 rb_heap_t *heap = &heaps[j];
9865
9866 size_t total_pages = heap->total_pages;
9867 size_t size = rb_size_mul_or_raise(total_pages, sizeof(struct heap_page *), rb_eRuntimeError);
9868 struct heap_page *page = 0, **page_list = malloc(size);
9869 size_t i = 0;
9870
9871 heap->free_pages = NULL;
9872 ccan_list_for_each(&heap->pages, page, page_node) {
9873 page_list[i++] = page;
9874 GC_ASSERT(page);
9875 }
9876
9877 GC_ASSERT((size_t)i == total_pages);
9878
9879 /* Sort the heap so "filled pages" are first. `heap_add_page` adds to the
9880 * head of the list, so empty pages will end up at the start of the heap */
9881 ruby_qsort(page_list, total_pages, sizeof(struct heap_page *), compare_func, NULL);
9882
9883 /* Reset the eden heap */
9884 ccan_list_head_init(&heap->pages);
9885
9886 for (i = 0; i < total_pages; i++) {
9887 ccan_list_add(&heap->pages, &page_list[i]->page_node);
9888 if (page_list[i]->free_slots != 0) {
9889 heap_add_freepage(heap, page_list[i]);
9890 }
9891 }
9892
9893 free(page_list);
9894 }
9895}
9896#endif
9897
9898void
9899rb_gc_impl_register_pinning_obj(void *objspace_ptr, VALUE obj)
9900{
9901 /* no-op */
9902}
9903
9904bool
9905rb_gc_impl_object_moved_p(void *objspace_ptr, VALUE obj)
9906{
9907 return gc_object_moved_p(objspace_ptr, obj);
9908}
9909
9910static int
9911gc_ref_update(void *vstart, void *vend, size_t stride, rb_objspace_t *objspace, struct heap_page *page)
9912{
9913 VALUE v = (VALUE)vstart;
9914
9915 page->flags.has_uncollectible_wb_unprotected_objects = FALSE;
9916 page->flags.has_remembered_objects = FALSE;
9917
9918 /* For each object on the page */
9919 for (; v != (VALUE)vend; v += stride) {
9920 asan_unpoisoning_object(v) {
9921 switch (BUILTIN_TYPE(v)) {
9922 case T_NONE:
9923 case T_MOVED:
9924 case T_ZOMBIE:
9925 break;
9926 default:
9927 if (RVALUE_WB_UNPROTECTED(objspace, v)) {
9928 page->flags.has_uncollectible_wb_unprotected_objects = TRUE;
9929 }
9930 if (RVALUE_REMEMBERED(objspace, v)) {
9931 page->flags.has_remembered_objects = TRUE;
9932 }
9933 if (page->flags.before_sweep) {
9934 if (RVALUE_MARKED(objspace, v)) {
9935 rb_gc_update_object_references(objspace, v);
9936 }
9937 }
9938 else {
9939 rb_gc_update_object_references(objspace, v);
9940 }
9941 }
9942 }
9943 }
9944
9945 return 0;
9946}
9947
9948static int
9949gc_update_references_weak_table_i(VALUE obj, void *data)
9950{
9951 int ret;
9952 asan_unpoisoning_object(obj) {
9953 ret = BUILTIN_TYPE(obj) == T_MOVED ? ST_REPLACE : ST_CONTINUE;
9954 }
9955 return ret;
9956}
9957
9958static int
9959gc_update_references_weak_table_replace_i(VALUE *obj, void *data)
9960{
9961 rb_gc_update_moved(obj);
9962
9963 return ST_CONTINUE;
9964}
9965
9966/* The per-objspace side of the reference update: walk this objspace's heap objects and rewrite
9967 * moved references (following T_MOVED forwarding across objspaces). A compacting global GC
9968 * runs this for every objspace. */
9969static void
9970gc_update_references_heap(rb_objspace_t *objspace)
9971{
9972 struct heap_page *page = NULL;
9973
9974 for (int i = 0; i < HEAP_COUNT; i++) {
9975 bool should_set_mark_bits = TRUE;
9976 rb_heap_t *heap = &heaps[i];
9977
9978 ccan_list_for_each(&heap->pages, page, page_node) {
9979 uintptr_t start = (uintptr_t)page->start;
9980 uintptr_t end = start + (page->total_slots * heap->slot_size);
9981
9982 gc_ref_update((void *)start, (void *)end, heap->slot_size, objspace, page);
9983 if (page == heap->sweeping_page) {
9984 should_set_mark_bits = FALSE;
9985 }
9986 if (should_set_mark_bits) {
9987 gc_setup_mark_bits(page);
9988 }
9989 }
9990 }
9991}
9992
9993/* The VM-global side of the reference update (finalizer table, every Ractor's VM roots,
9994 * weak tables). Process-wide, so a compacting global GC runs it once after every heap
9995 * side: rb_gc_update_vm_references and the weak tables' mark_and_move are not idempotent. */
9996static void
9997gc_update_references_global(rb_objspace_t *objspace)
9998{
9999 gc_update_table_refs(finalizer_table);
10000
10001 rb_gc_update_vm_references((void *)objspace);
10002
10003 for (int table = 0; table < RB_GC_VM_WEAK_TABLE_COUNT; table++) {
10004 rb_gc_vm_weak_table_foreach(
10005 gc_update_references_weak_table_i,
10006 gc_update_references_weak_table_replace_i,
10007 NULL,
10008 false,
10009 table
10010 );
10011 }
10012}
10013
10014static void
10015gc_update_references(rb_objspace_t *objspace)
10016{
10017 objspace->flags.during_reference_updating = true;
10018
10019 rb_gc_before_updating_jit_code();
10020
10021 gc_update_references_heap(objspace);
10022 gc_update_references_global(objspace);
10023
10024 rb_gc_after_updating_jit_code();
10025
10026 objspace->flags.during_reference_updating = false;
10027}
10028
10029#if GC_CAN_COMPILE_COMPACTION
10030static void
10031root_obj_check_moved_i(const char *category, VALUE obj, void *data)
10032{
10033 rb_objspace_t *objspace = data;
10034
10035 if (gc_object_moved_p(objspace, obj)) {
10036 rb_bug("ROOT %s points to MOVED: %p -> %s", category, (void *)obj, rb_obj_info(rb_gc_impl_location(objspace, obj)));
10037 }
10038}
10039
10040static void
10041reachable_object_check_moved_i(VALUE ref, void *data)
10042{
10043 VALUE parent = (VALUE)data;
10044 if (gc_object_moved_p(rb_gc_get_objspace(), ref)) {
10045 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)));
10046 }
10047}
10048
10049static int
10050heap_check_moved_i(void *vstart, void *vend, size_t stride, void *data)
10051{
10052 rb_objspace_t *objspace = data;
10053
10054 VALUE v = (VALUE)vstart;
10055 for (; v != (VALUE)vend; v += stride) {
10056 if (gc_object_moved_p(objspace, v)) {
10057 /* Moved object still on the heap, something may have a reference. */
10058 }
10059 else {
10060 asan_unpoisoning_object(v) {
10061 switch (BUILTIN_TYPE(v)) {
10062 case T_NONE:
10063 case T_ZOMBIE:
10064 break;
10065 default:
10066 if (!rb_gc_impl_garbage_object_p(objspace, v)) {
10067 rb_objspace_reachable_objects_from(v, reachable_object_check_moved_i, (void *)v);
10068 }
10069 }
10070 }
10071 }
10072 }
10073
10074 return 0;
10075}
10076#endif
10077
10078bool
10079rb_gc_impl_during_gc_p(void *objspace_ptr)
10080{
10081 rb_objspace_t *objspace = objspace_ptr;
10082
10083 return during_gc;
10084}
10085
10086#if RGENGC_PROFILE >= 2
10087
10088static const char*
10089type_name(int type, VALUE obj)
10090{
10091 switch ((enum ruby_value_type)type) {
10092 case RUBY_T_NONE: return "T_NONE";
10093 case RUBY_T_OBJECT: return "T_OBJECT";
10094 case RUBY_T_CLASS: return "T_CLASS";
10095 case RUBY_T_MODULE: return "T_MODULE";
10096 case RUBY_T_FLOAT: return "T_FLOAT";
10097 case RUBY_T_STRING: return "T_STRING";
10098 case RUBY_T_REGEXP: return "T_REGEXP";
10099 case RUBY_T_ARRAY: return "T_ARRAY";
10100 case RUBY_T_HASH: return "T_HASH";
10101 case RUBY_T_STRUCT: return "T_STRUCT";
10102 case RUBY_T_BIGNUM: return "T_BIGNUM";
10103 case RUBY_T_FILE: return "T_FILE";
10104 case RUBY_T_DATA: return "T_DATA";
10105 case RUBY_T_MATCH: return "T_MATCH";
10106 case RUBY_T_COMPLEX: return "T_COMPLEX";
10107 case RUBY_T_RATIONAL: return "T_RATIONAL";
10108 case RUBY_T_NIL: return "T_NIL";
10109 case RUBY_T_TRUE: return "T_TRUE";
10110 case RUBY_T_FALSE: return "T_FALSE";
10111 case RUBY_T_SYMBOL: return "T_SYMBOL";
10112 case RUBY_T_FIXNUM: return "T_FIXNUM";
10113 case RUBY_T_UNDEF: return "T_UNDEF";
10114 case RUBY_T_IMEMO: return "T_IMEMO";
10115 case RUBY_T_NODE: return "T_NODE";
10116 case RUBY_T_ICLASS: return "T_ICLASS";
10117 case RUBY_T_ZOMBIE: return "T_ZOMBIE";
10118 case RUBY_T_MOVED: return "T_MOVED";
10119 default: return "unknown";
10120 }
10121}
10122
10123static void
10124gc_count_add_each_types(VALUE hash, const char *name, const size_t *types)
10125{
10126 VALUE result = rb_hash_new_capa(T_MASK);
10127 int i;
10128 for (i=0; i<T_MASK; i++) {
10129 const char *type = type_name(i, 0);
10130 rb_hash_aset(result, ID2SYM(rb_intern(type)), SIZET2NUM(types[i]));
10131 }
10132 rb_hash_aset(hash, ID2SYM(rb_intern(name)), result);
10133}
10134#endif
10135
10136size_t
10137rb_gc_impl_gc_count(void *objspace_ptr)
10138{
10139 rb_objspace_t *objspace = objspace_ptr;
10140
10141 return objspace->profile.count;
10142}
10143
10144static VALUE
10145gc_info_decode(rb_objspace_t *objspace, const VALUE hash_or_key, const unsigned int orig_flags)
10146{
10147 static VALUE sym_major_by = Qnil, sym_gc_by, sym_immediate_sweep, sym_have_finalizer, sym_state, sym_need_major_by;
10148 static VALUE sym_nofree, sym_oldgen, sym_shady, sym_force, sym_stress;
10149#if RGENGC_ESTIMATE_OLDMALLOC
10150 static VALUE sym_oldmalloc;
10151#endif
10152 static VALUE sym_newobj, sym_malloc, sym_method, sym_capi;
10153 static VALUE sym_none, sym_marking, sym_sweeping;
10154 static VALUE sym_weak_references_count;
10155 VALUE hash = Qnil, key = Qnil;
10156 VALUE major_by, need_major_by;
10157 unsigned int flags = orig_flags ? orig_flags : objspace->profile.latest_gc_info;
10158
10159 if (SYMBOL_P(hash_or_key)) {
10160 key = hash_or_key;
10161 }
10162 else if (RB_TYPE_P(hash_or_key, T_HASH)) {
10163 hash = hash_or_key;
10164 }
10165 else {
10166 rb_bug("gc_info_decode: non-hash or symbol given");
10167 }
10168
10169 if (NIL_P(sym_major_by)) {
10170#define S(s) sym_##s = ID2SYM(rb_intern_const(#s))
10171 S(major_by);
10172 S(gc_by);
10173 S(immediate_sweep);
10174 S(have_finalizer);
10175 S(state);
10176 S(need_major_by);
10177
10178 S(stress);
10179 S(nofree);
10180 S(oldgen);
10181 S(shady);
10182 S(force);
10183#if RGENGC_ESTIMATE_OLDMALLOC
10184 S(oldmalloc);
10185#endif
10186 S(newobj);
10187 S(malloc);
10188 S(method);
10189 S(capi);
10190
10191 S(none);
10192 S(marking);
10193 S(sweeping);
10194
10195 S(weak_references_count);
10196#undef S
10197 }
10198
10199#define SET(name, attr) \
10200 if (key == sym_##name) \
10201 return (attr); \
10202 else if (hash != Qnil) \
10203 rb_hash_aset(hash, sym_##name, (attr));
10204
10205 major_by =
10206 (flags & GPR_FLAG_MAJOR_BY_NOFREE) ? sym_nofree :
10207 (flags & GPR_FLAG_MAJOR_BY_OLDGEN) ? sym_oldgen :
10208 (flags & GPR_FLAG_MAJOR_BY_SHADY) ? sym_shady :
10209 (flags & GPR_FLAG_MAJOR_BY_FORCE) ? sym_force :
10210#if RGENGC_ESTIMATE_OLDMALLOC
10211 (flags & GPR_FLAG_MAJOR_BY_OLDMALLOC) ? sym_oldmalloc :
10212#endif
10213 Qnil;
10214 SET(major_by, major_by);
10215
10216 if (orig_flags == 0) { /* set need_major_by only if flags not set explicitly */
10217 unsigned int need_major_flags = gc_needs_major_flags;
10218 need_major_by =
10219 (need_major_flags & GPR_FLAG_MAJOR_BY_NOFREE) ? sym_nofree :
10220 (need_major_flags & GPR_FLAG_MAJOR_BY_OLDGEN) ? sym_oldgen :
10221 (need_major_flags & GPR_FLAG_MAJOR_BY_SHADY) ? sym_shady :
10222 (need_major_flags & GPR_FLAG_MAJOR_BY_FORCE) ? sym_force :
10223#if RGENGC_ESTIMATE_OLDMALLOC
10224 (need_major_flags & GPR_FLAG_MAJOR_BY_OLDMALLOC) ? sym_oldmalloc :
10225#endif
10226 Qnil;
10227 SET(need_major_by, need_major_by);
10228 }
10229
10230 SET(gc_by,
10231 (flags & GPR_FLAG_NEWOBJ) ? sym_newobj :
10232 (flags & GPR_FLAG_MALLOC) ? sym_malloc :
10233 (flags & GPR_FLAG_METHOD) ? sym_method :
10234 (flags & GPR_FLAG_CAPI) ? sym_capi :
10235 (flags & GPR_FLAG_STRESS) ? sym_stress :
10236 Qnil
10237 );
10238
10239 SET(have_finalizer, (flags & GPR_FLAG_HAVE_FINALIZE) ? Qtrue : Qfalse);
10240 SET(immediate_sweep, (flags & GPR_FLAG_IMMEDIATE_SWEEP) ? Qtrue : Qfalse);
10241
10242 if (orig_flags == 0) {
10243 SET(state, gc_mode(objspace) == gc_mode_none ? sym_none :
10244 gc_mode(objspace) == gc_mode_marking ? sym_marking : sym_sweeping);
10245 }
10246
10247 SET(weak_references_count, LONG2FIX(objspace->profile.weak_references_count));
10248#undef SET
10249
10250 if (!NIL_P(key)) {
10251 // Matched key should return above
10252 return Qundef;
10253 }
10254
10255 return hash;
10256}
10257
10258VALUE
10259rb_gc_impl_latest_gc_info(void *objspace_ptr, VALUE key)
10260{
10261 rb_objspace_t *objspace = objspace_ptr;
10262
10263 return gc_info_decode(objspace, key, 0);
10264}
10265
10266
10267enum gc_stat_sym {
10268 gc_stat_sym_count,
10269 gc_stat_sym_time,
10270 gc_stat_sym_marking_time,
10271 gc_stat_sym_sweeping_time,
10272 gc_stat_sym_heap_allocated_pages,
10273 gc_stat_sym_heap_empty_pages,
10274 gc_stat_sym_heap_allocatable_bytes,
10275 gc_stat_sym_heap_available_slots,
10276 gc_stat_sym_heap_live_slots,
10277 gc_stat_sym_heap_free_slots,
10278 gc_stat_sym_heap_final_slots,
10279 gc_stat_sym_heap_marked_slots,
10280 gc_stat_sym_heap_eden_pages,
10281 gc_stat_sym_total_allocated_pages,
10282 gc_stat_sym_total_freed_pages,
10283 gc_stat_sym_total_allocated_objects,
10284 gc_stat_sym_total_freed_objects,
10285 gc_stat_sym_total_malloc_bytes,
10286 gc_stat_sym_total_free_bytes,
10287 gc_stat_sym_malloc_increase_bytes,
10288 gc_stat_sym_malloc_increase_bytes_limit,
10289 gc_stat_sym_minor_gc_count,
10290 gc_stat_sym_major_gc_count,
10291 gc_stat_sym_compact_count,
10292 gc_stat_sym_read_barrier_faults,
10293 gc_stat_sym_total_moved_objects,
10294 gc_stat_sym_remembered_wb_unprotected_objects,
10295 gc_stat_sym_remembered_wb_unprotected_objects_limit,
10296 gc_stat_sym_old_objects,
10297 gc_stat_sym_old_objects_limit,
10298#if RGENGC_ESTIMATE_OLDMALLOC
10299 gc_stat_sym_oldmalloc_increase_bytes,
10300 gc_stat_sym_oldmalloc_increase_bytes_limit,
10301#endif
10302#if RGENGC_PROFILE
10303 gc_stat_sym_total_generated_normal_object_count,
10304 gc_stat_sym_total_generated_shady_object_count,
10305 gc_stat_sym_total_shade_operation_count,
10306 gc_stat_sym_total_promoted_count,
10307 gc_stat_sym_total_remembered_normal_object_count,
10308 gc_stat_sym_total_remembered_shady_object_count,
10309#endif
10310 gc_stat_sym_page_pool_arenas,
10311 gc_stat_sym_page_pool_arenas_freed,
10312 gc_stat_sym_page_pool_total_pages,
10313 gc_stat_sym_page_pool_discarded_pages,
10314 gc_stat_sym_last
10315};
10316
10317static VALUE gc_stat_symbols[gc_stat_sym_last];
10318
10319static void
10320setup_gc_stat_symbols(void)
10321{
10322 if (gc_stat_symbols[0] == 0) {
10323#define S(s) gc_stat_symbols[gc_stat_sym_##s] = ID2SYM(rb_intern_const(#s))
10324 S(count);
10325 S(time);
10326 S(marking_time),
10327 S(sweeping_time),
10328 S(heap_allocated_pages);
10329 S(heap_empty_pages);
10330 S(heap_allocatable_bytes);
10331 S(heap_available_slots);
10332 S(heap_live_slots);
10333 S(heap_free_slots);
10334 S(heap_final_slots);
10335 S(heap_marked_slots);
10336 S(heap_eden_pages);
10337 S(total_allocated_pages);
10338 S(total_freed_pages);
10339 S(total_allocated_objects);
10340 S(total_freed_objects);
10341 S(total_malloc_bytes);
10342 S(total_free_bytes);
10343 S(malloc_increase_bytes);
10344 S(malloc_increase_bytes_limit);
10345 S(minor_gc_count);
10346 S(major_gc_count);
10347 S(compact_count);
10348 S(read_barrier_faults);
10349 S(total_moved_objects);
10350 S(remembered_wb_unprotected_objects);
10351 S(remembered_wb_unprotected_objects_limit);
10352 S(old_objects);
10353 S(old_objects_limit);
10354#if RGENGC_ESTIMATE_OLDMALLOC
10355 S(oldmalloc_increase_bytes);
10356 S(oldmalloc_increase_bytes_limit);
10357#endif
10358#if RGENGC_PROFILE
10359 S(total_generated_normal_object_count);
10360 S(total_generated_shady_object_count);
10361 S(total_shade_operation_count);
10362 S(total_promoted_count);
10363 S(total_remembered_normal_object_count);
10364 S(total_remembered_shady_object_count);
10365#endif /* RGENGC_PROFILE */
10366 S(page_pool_arenas);
10367 S(page_pool_arenas_freed);
10368 S(page_pool_total_pages);
10369 S(page_pool_discarded_pages);
10370#undef S
10371 }
10372}
10373
10374static uint64_t
10375ns_to_ms(uint64_t ns)
10376{
10377 return ns / (1000 * 1000);
10378}
10379
10380static void malloc_increase_local_flush(rb_objspace_t *objspace);
10381
10382VALUE
10383rb_gc_impl_stat(void *objspace_ptr, VALUE hash_or_sym)
10384{
10385 rb_objspace_t *objspace = objspace_ptr;
10386 VALUE hash = Qnil, key = Qnil;
10387
10388 setup_gc_stat_symbols();
10389
10390 malloc_increase_local_flush(objspace);
10391
10392 if (RB_TYPE_P(hash_or_sym, T_HASH)) {
10393 hash = hash_or_sym;
10394 }
10395 else if (SYMBOL_P(hash_or_sym)) {
10396 key = hash_or_sym;
10397 }
10398 else {
10399 rb_bug("non-hash or symbol given");
10400 }
10401
10402#define SET(name, attr) \
10403 if (key == gc_stat_symbols[gc_stat_sym_##name]) \
10404 return SIZET2NUM(attr); \
10405 else if (hash != Qnil) \
10406 rb_hash_aset(hash, gc_stat_symbols[gc_stat_sym_##name], SIZET2NUM(attr));
10407#define SET64(name, attr) \
10408 if (key == gc_stat_symbols[gc_stat_sym_##name]) \
10409 return ULL2NUM(attr); \
10410 else if (hash != Qnil) \
10411 rb_hash_aset(hash, gc_stat_symbols[gc_stat_sym_##name], ULL2NUM(attr));
10412
10413 SET(count, objspace->profile.count);
10414 SET(time, (size_t)ns_to_ms(objspace->profile.marking_time_ns + objspace->profile.sweeping_time_ns)); // TODO: UINT64T2NUM
10415 SET(marking_time, (size_t)ns_to_ms(objspace->profile.marking_time_ns));
10416 SET(sweeping_time, (size_t)ns_to_ms(objspace->profile.sweeping_time_ns));
10417
10418 {
10419 uint64_t total_malloc = (uint64_t)gc_counter_load_relaxed(&objspace->malloc_counters.counters.malloc);
10420 uint64_t total_free = (uint64_t)gc_counter_load_relaxed(&objspace->malloc_counters.counters.free);
10421 SET64(total_malloc_bytes, total_malloc);
10422 SET64(total_free_bytes, total_free);
10423 }
10424
10425 /* implementation dependent counters (small / fixnum-safe) */
10426 SET(heap_allocated_pages, rb_darray_size(objspace->heap_pages.sorted));
10427 SET(heap_empty_pages, objspace->empty_pages_count)
10428 SET(heap_allocatable_bytes, objspace->heap_pages.allocatable_bytes);
10429 SET(heap_eden_pages, heap_eden_total_pages(objspace));
10430 SET(total_allocated_pages, objspace->heap_pages.allocated_pages);
10431 SET(total_freed_pages, objspace->heap_pages.freed_pages);
10432 SET(malloc_increase_bytes, gc_malloc_counters_increase_unsigned(objspace, &objspace->malloc_counters.counters));
10433 SET(malloc_increase_bytes_limit, malloc_limit);
10434 SET(minor_gc_count, objspace->profile.minor_gc_count);
10435 SET(major_gc_count, objspace->profile.major_gc_count);
10436 SET(compact_count, objspace->profile.compact_count);
10437 SET(read_barrier_faults, objspace->profile.read_barrier_faults);
10438 SET(total_moved_objects, objspace->rcompactor.total_moved);
10439 SET(remembered_wb_unprotected_objects, objspace->rgengc.uncollectible_wb_unprotected_objects);
10440 SET(remembered_wb_unprotected_objects_limit, objspace->rgengc.uncollectible_wb_unprotected_objects_limit);
10441 SET(old_objects, objspace->rgengc.old_objects);
10442 SET(old_objects_limit, objspace->rgengc.old_objects_limit);
10443#if RGENGC_ESTIMATE_OLDMALLOC
10444 SET(oldmalloc_increase_bytes, gc_malloc_counters_increase_unsigned(objspace, &objspace->malloc_counters.oldcounters));
10445 SET(oldmalloc_increase_bytes_limit, objspace->rgengc.oldmalloc_increase_limit);
10446#endif
10447
10448 SET(total_allocated_objects, total_allocated_objects(objspace));
10449 SET(total_freed_objects, total_freed_objects(objspace));
10450 SET(heap_available_slots, objspace_available_slots(objspace));
10451 SET(heap_live_slots, objspace_live_slots(objspace));
10452 SET(heap_free_slots, objspace_free_slots(objspace));
10453 SET(heap_final_slots, total_final_slots_count(objspace));
10454 SET(heap_marked_slots, objspace->marked_slots);
10455
10456 SET(page_pool_arenas, global_objspace->page_pool.arena_count);
10457 SET(page_pool_arenas_freed, global_objspace->page_pool.arenas_unmapped);
10458 SET(page_pool_total_pages, (size_t)global_objspace->page_pool.arena_count * PAGE_POOL_ARENA_BODIES);
10459 SET(page_pool_discarded_pages, global_objspace->page_pool.advised_count);
10460
10461#if RGENGC_PROFILE
10462 SET(total_generated_normal_object_count, objspace->profile.total_generated_normal_object_count);
10463 SET(total_generated_shady_object_count, objspace->profile.total_generated_shady_object_count);
10464 SET(total_shade_operation_count, objspace->profile.total_shade_operation_count);
10465 SET(total_promoted_count, objspace->profile.total_promoted_count);
10466 SET(total_remembered_normal_object_count, objspace->profile.total_remembered_normal_object_count);
10467 SET(total_remembered_shady_object_count, objspace->profile.total_remembered_shady_object_count);
10468#endif /* RGENGC_PROFILE */
10469#undef SET
10470#undef SET64
10471
10472 if (!NIL_P(key)) {
10473 // Matched key should return above
10474 return Qundef;
10475 }
10476
10477#if defined(RGENGC_PROFILE) && RGENGC_PROFILE >= 2
10478 if (hash != Qnil) {
10479 gc_count_add_each_types(hash, "generated_normal_object_count_types", objspace->profile.generated_normal_object_count_types);
10480 gc_count_add_each_types(hash, "generated_shady_object_count_types", objspace->profile.generated_shady_object_count_types);
10481 gc_count_add_each_types(hash, "shade_operation_count_types", objspace->profile.shade_operation_count_types);
10482 gc_count_add_each_types(hash, "promoted_types", objspace->profile.promoted_types);
10483 gc_count_add_each_types(hash, "remembered_normal_object_count_types", objspace->profile.remembered_normal_object_count_types);
10484 gc_count_add_each_types(hash, "remembered_shady_object_count_types", objspace->profile.remembered_shady_object_count_types);
10485 }
10486#endif
10487
10488 return hash;
10489}
10490
10491enum gc_stat_heap_sym {
10492 gc_stat_heap_sym_slot_size,
10493 gc_stat_heap_sym_heap_live_slots,
10494 gc_stat_heap_sym_heap_free_slots,
10495 gc_stat_heap_sym_heap_final_slots,
10496 gc_stat_heap_sym_heap_eden_pages,
10497 gc_stat_heap_sym_heap_eden_slots,
10498 gc_stat_heap_sym_total_allocated_pages,
10499 gc_stat_heap_sym_force_major_gc_count,
10500 gc_stat_heap_sym_force_incremental_marking_finish_count,
10501 gc_stat_heap_sym_heap_allocatable_slots,
10502 gc_stat_heap_sym_total_allocated_objects,
10503 gc_stat_heap_sym_total_freed_objects,
10504 gc_stat_heap_sym_last
10505};
10506
10507static VALUE gc_stat_heap_symbols[gc_stat_heap_sym_last];
10508
10509static void
10510setup_gc_stat_heap_symbols(void)
10511{
10512 if (gc_stat_heap_symbols[0] == 0) {
10513#define S(s) gc_stat_heap_symbols[gc_stat_heap_sym_##s] = ID2SYM(rb_intern_const(#s))
10514 S(slot_size);
10515 S(heap_live_slots);
10516 S(heap_free_slots);
10517 S(heap_final_slots);
10518 S(heap_eden_pages);
10519 S(heap_eden_slots);
10520 S(heap_allocatable_slots);
10521 S(total_allocated_pages);
10522 S(force_major_gc_count);
10523 S(force_incremental_marking_finish_count);
10524 S(total_allocated_objects);
10525 S(total_freed_objects);
10526#undef S
10527 }
10528}
10529
10530static VALUE
10531stat_one_heap(rb_objspace_t *objspace, rb_heap_t *heap, VALUE hash, VALUE key)
10532{
10533#define SET(name, attr) \
10534 if (key == gc_stat_heap_symbols[gc_stat_heap_sym_##name]) \
10535 return SIZET2NUM(attr); \
10536 else if (hash != Qnil) \
10537 rb_hash_aset(hash, gc_stat_heap_symbols[gc_stat_heap_sym_##name], SIZET2NUM(attr));
10538
10539 SET(slot_size, heap->slot_size);
10540 SET(heap_live_slots, heap->total_allocated_objects - heap->total_freed_objects - heap->final_slots_count);
10541 SET(heap_free_slots, heap->total_slots - (heap->total_allocated_objects - heap->total_freed_objects));
10542 SET(heap_final_slots, heap->final_slots_count);
10543 SET(heap_eden_pages, heap->total_pages);
10544 SET(heap_eden_slots, heap->total_slots);
10545 SET(heap_allocatable_slots, objspace->heap_pages.allocatable_bytes / heap->slot_size);
10546 SET(total_allocated_pages, heap->total_allocated_pages);
10547 SET(force_major_gc_count, heap->force_major_gc_count);
10548 SET(force_incremental_marking_finish_count, heap->force_incremental_marking_finish_count);
10549 SET(total_allocated_objects, heap->total_allocated_objects);
10550 SET(total_freed_objects, heap->total_freed_objects);
10551#undef SET
10552
10553 if (!NIL_P(key)) {
10554 // Matched key should return above
10555 return Qundef;
10556 }
10557
10558 return hash;
10559}
10560
10561VALUE
10562rb_gc_impl_stat_heap(void *objspace_ptr, VALUE heap_name, VALUE hash_or_sym)
10563{
10564 rb_objspace_t *objspace = objspace_ptr;
10565
10566 setup_gc_stat_heap_symbols();
10567
10568 if (NIL_P(heap_name)) {
10569 if (!RB_TYPE_P(hash_or_sym, T_HASH)) {
10570 rb_bug("non-hash given");
10571 }
10572
10573 for (int i = 0; i < HEAP_COUNT; i++) {
10574 VALUE hash = rb_hash_aref(hash_or_sym, INT2FIX(i));
10575 if (NIL_P(hash)) {
10576 hash = rb_hash_new();
10577 rb_hash_aset(hash_or_sym, INT2FIX(i), hash);
10578 }
10579
10580 stat_one_heap(objspace, &heaps[i], hash, Qnil);
10581 }
10582 }
10583 else if (FIXNUM_P(heap_name)) {
10584 int heap_idx = FIX2INT(heap_name);
10585
10586 if (heap_idx < 0 || heap_idx >= HEAP_COUNT) {
10587 rb_raise(rb_eArgError, "size pool index out of range");
10588 }
10589
10590 if (SYMBOL_P(hash_or_sym)) {
10591 return stat_one_heap(objspace, &heaps[heap_idx], Qnil, hash_or_sym);
10592 }
10593 else if (RB_TYPE_P(hash_or_sym, T_HASH)) {
10594 return stat_one_heap(objspace, &heaps[heap_idx], hash_or_sym, Qnil);
10595 }
10596 else {
10597 rb_bug("non-hash or symbol given");
10598 }
10599 }
10600 else {
10601 rb_bug("heap_name must be nil or an Integer");
10602 }
10603
10604 return hash_or_sym;
10605}
10606
10607/* I could include internal.h for this, but doing so undefines some Array macros
10608 * necessary for initialising objects, and I don't want to include all the array
10609 * headers to get them back
10610 * TODO: Investigate why RARRAY_AREF gets undefined in internal.h
10611 */
10612#ifndef RBOOL
10613#define RBOOL(v) (v ? Qtrue : Qfalse)
10614#endif
10615
10616VALUE
10617rb_gc_impl_config_get(void *objspace_ptr)
10618{
10619#define sym(name) ID2SYM(rb_intern_const(name))
10620 rb_objspace_t *objspace = objspace_ptr;
10621 VALUE hash = rb_hash_new();
10622
10623 rb_hash_aset(hash, sym("rgengc_allow_full_mark"), RBOOL(gc_config_full_mark_val));
10624
10625 return hash;
10626}
10627
10628static int
10629gc_config_set_key(VALUE key, VALUE value, VALUE data)
10630{
10632 if (rb_sym2id(key) == rb_intern("rgengc_allow_full_mark")) {
10633 gc_rest(objspace);
10634 gc_config_full_mark_set(RTEST(value));
10635 }
10636 return ST_CONTINUE;
10637}
10638
10639void
10640rb_gc_impl_config_set(void *objspace_ptr, VALUE hash)
10641{
10642 rb_objspace_t *objspace = objspace_ptr;
10643
10644 if (!RB_TYPE_P(hash, T_HASH)) {
10645 rb_raise(rb_eArgError, "expected keyword arguments");
10646 }
10647
10648 rb_hash_foreach(hash, gc_config_set_key, (st_data_t)objspace);
10649}
10650
10651VALUE
10652rb_gc_impl_stress_get(void *objspace_ptr)
10653{
10654 return ruby_gc_stress_mode;
10655}
10656
10657void
10658rb_gc_impl_stress_set(void *objspace_ptr, VALUE flag)
10659{
10660 global_objspace->gc_stressful = RTEST(flag);
10661 global_objspace->gc_stress_mode = flag;
10662}
10663
10664static int
10665get_envparam_size(const char *name, size_t *default_value, size_t lower_bound)
10666{
10667 const char *ptr = getenv(name);
10668 ssize_t val;
10669
10670 if (ptr != NULL && *ptr) {
10671 size_t unit = 0;
10672 char *end;
10673#if SIZEOF_SIZE_T == SIZEOF_LONG_LONG
10674 val = strtoll(ptr, &end, 0);
10675#else
10676 val = strtol(ptr, &end, 0);
10677#endif
10678 switch (*end) {
10679 case 'k': case 'K':
10680 unit = 1024;
10681 ++end;
10682 break;
10683 case 'm': case 'M':
10684 unit = 1024*1024;
10685 ++end;
10686 break;
10687 case 'g': case 'G':
10688 unit = 1024*1024*1024;
10689 ++end;
10690 break;
10691 }
10692 while (*end && isspace((unsigned char)*end)) end++;
10693 if (*end) {
10694 if (RTEST(ruby_verbose)) fprintf(stderr, "invalid string for %s: %s\n", name, ptr);
10695 return 0;
10696 }
10697 if (unit > 0) {
10698 if (val < -(ssize_t)(SIZE_MAX / 2 / unit) || (ssize_t)(SIZE_MAX / 2 / unit) < val) {
10699 if (RTEST(ruby_verbose)) fprintf(stderr, "%s=%s is ignored because it overflows\n", name, ptr);
10700 return 0;
10701 }
10702 val *= unit;
10703 }
10704 if (val > 0 && (size_t)val > lower_bound) {
10705 if (RTEST(ruby_verbose)) {
10706 fprintf(stderr, "%s=%"PRIdSIZE" (default value: %"PRIuSIZE")\n", name, val, *default_value);
10707 }
10708 *default_value = (size_t)val;
10709 return 1;
10710 }
10711 else {
10712 if (RTEST(ruby_verbose)) {
10713 fprintf(stderr, "%s=%"PRIdSIZE" (default value: %"PRIuSIZE") is ignored because it must be greater than %"PRIuSIZE".\n",
10714 name, val, *default_value, lower_bound);
10715 }
10716 return 0;
10717 }
10718 }
10719 return 0;
10720}
10721
10722static int
10723get_envparam_double(const char *name, double *default_value, double lower_bound, double upper_bound, int accept_zero)
10724{
10725 const char *ptr = getenv(name);
10726 double val;
10727
10728 if (ptr != NULL && *ptr) {
10729 char *end;
10730 val = strtod(ptr, &end);
10731 if (!*ptr || *end) {
10732 if (RTEST(ruby_verbose)) fprintf(stderr, "invalid string for %s: %s\n", name, ptr);
10733 return 0;
10734 }
10735
10736 if (accept_zero && val == 0.0) {
10737 goto accept;
10738 }
10739 else if (val <= lower_bound) {
10740 if (RTEST(ruby_verbose)) {
10741 fprintf(stderr, "%s=%f (default value: %f) is ignored because it must be greater than %f.\n",
10742 name, val, *default_value, lower_bound);
10743 }
10744 }
10745 else if (upper_bound != 0.0 && /* ignore upper_bound if it is 0.0 */
10746 val > upper_bound) {
10747 if (RTEST(ruby_verbose)) {
10748 fprintf(stderr, "%s=%f (default value: %f) is ignored because it must be lower than %f.\n",
10749 name, val, *default_value, upper_bound);
10750 }
10751 }
10752 else {
10753 goto accept;
10754 }
10755 }
10756 return 0;
10757
10758 accept:
10759 if (RTEST(ruby_verbose)) fprintf(stderr, "%s=%f (default value: %f)\n", name, val, *default_value);
10760 *default_value = val;
10761 return 1;
10762}
10763
10764/*
10765 * GC tuning environment variables
10766 *
10767 * * RUBY_GC_HEAP_FREE_SLOTS
10768 * - Prepare at least this amount of slots after GC.
10769 * - Allocate slots if there are not enough slots.
10770 * * RUBY_GC_HEAP_GROWTH_FACTOR (new from 2.1)
10771 * - Allocate slots by this factor.
10772 * - (next slots number) = (current slots number) * (this factor)
10773 * * RUBY_GC_HEAP_GROWTH_MAX_BYTES (was RUBY_GC_HEAP_GROWTH_MAX_SLOTS)
10774 * - Allocation rate is limited to this number of bytes.
10775 * * RUBY_GC_HEAP_FREE_SLOTS_MIN_RATIO (new from 2.4)
10776 * - Allocate additional pages when the number of free slots is
10777 * lower than the value (total_slots * (this ratio)).
10778 * * RUBY_GC_HEAP_FREE_SLOTS_GOAL_RATIO (new from 2.4)
10779 * - Allocate slots to satisfy this formula:
10780 * free_slots = total_slots * goal_ratio
10781 * - In other words, prepare (total_slots * goal_ratio) free slots.
10782 * - if this value is 0.0, then use RUBY_GC_HEAP_GROWTH_FACTOR directly.
10783 * * RUBY_GC_HEAP_FREE_SLOTS_MAX_RATIO (new from 2.4)
10784 * - Allow to free pages when the number of free slots is
10785 * greater than the value (total_slots * (this ratio)).
10786 * * RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR (new from 2.1.1)
10787 * - Do full GC when the number of old objects is more than R * N
10788 * where R is this factor and
10789 * N is the number of old objects just after last full GC.
10790 *
10791 * * obsolete
10792 * * RUBY_FREE_MIN -> RUBY_GC_HEAP_FREE_SLOTS (from 2.1)
10793 * * RUBY_HEAP_MIN_SLOTS -> RUBY_GC_HEAP_INIT_SLOTS (from 2.1) -> RUBY_GC_HEAP_INIT_BYTES
10794 *
10795 * * RUBY_GC_MALLOC_LIMIT
10796 * * RUBY_GC_MALLOC_LIMIT_MAX (new from 2.1)
10797 * * RUBY_GC_MALLOC_LIMIT_GROWTH_FACTOR (new from 2.1)
10798 *
10799 * * RUBY_GC_OLDMALLOC_LIMIT (new from 2.1)
10800 * * RUBY_GC_OLDMALLOC_LIMIT_MAX (new from 2.1)
10801 * * RUBY_GC_OLDMALLOC_LIMIT_GROWTH_FACTOR (new from 2.1)
10802 */
10803
10804void
10805rb_gc_impl_set_params(void *objspace_ptr)
10806{
10807 rb_objspace_t *objspace = objspace_ptr;
10808 get_envparam_size("RUBY_GC_HEAP_FREE_SLOTS", &gc_params.heap_free_slots, 0);
10809
10810 get_envparam_size("RUBY_GC_HEAP_INIT_BYTES", &gc_params.heap_init_bytes, 0);
10811
10812 get_envparam_double("RUBY_GC_HEAP_GROWTH_FACTOR", &gc_params.growth_factor, 1.0, 0.0, FALSE);
10813 get_envparam_size ("RUBY_GC_HEAP_GROWTH_MAX_BYTES", &gc_params.growth_max_bytes, 0);
10814 get_envparam_double("RUBY_GC_HEAP_FREE_SLOTS_MIN_RATIO", &gc_params.heap_free_slots_min_ratio,
10815 0.0, 1.0, FALSE);
10816 get_envparam_double("RUBY_GC_HEAP_FREE_SLOTS_MAX_RATIO", &gc_params.heap_free_slots_max_ratio,
10817 gc_params.heap_free_slots_min_ratio, 1.0, FALSE);
10818 get_envparam_double("RUBY_GC_HEAP_FREE_SLOTS_GOAL_RATIO", &gc_params.heap_free_slots_goal_ratio,
10819 gc_params.heap_free_slots_min_ratio, gc_params.heap_free_slots_max_ratio, TRUE);
10820 get_envparam_double("RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR", &gc_params.oldobject_limit_factor, 0.0, 0.0, TRUE);
10821 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);
10822
10823 if (get_envparam_size("RUBY_GC_MALLOC_LIMIT", &gc_params.malloc_limit_min, 0)) {
10824 malloc_limit = gc_params.malloc_limit_min;
10825 }
10826 get_envparam_size ("RUBY_GC_MALLOC_LIMIT_MAX", &gc_params.malloc_limit_max, 0);
10827 if (!gc_params.malloc_limit_max) { /* ignore max-check if 0 */
10828 gc_params.malloc_limit_max = SIZE_MAX;
10829 }
10830 get_envparam_double("RUBY_GC_MALLOC_LIMIT_GROWTH_FACTOR", &gc_params.malloc_limit_growth_factor, 1.0, 0.0, FALSE);
10831
10832#if RGENGC_ESTIMATE_OLDMALLOC
10833 if (get_envparam_size("RUBY_GC_OLDMALLOC_LIMIT", &gc_params.oldmalloc_limit_min, 0)) {
10834 objspace->rgengc.oldmalloc_increase_limit = gc_params.oldmalloc_limit_min;
10835 }
10836 get_envparam_size ("RUBY_GC_OLDMALLOC_LIMIT_MAX", &gc_params.oldmalloc_limit_max, 0);
10837 get_envparam_double("RUBY_GC_OLDMALLOC_LIMIT_GROWTH_FACTOR", &gc_params.oldmalloc_limit_growth_factor, 1.0, 0.0, FALSE);
10838#endif
10839}
10840
10841static inline size_t
10842objspace_malloc_size(rb_objspace_t *objspace, void *ptr, size_t hint)
10843{
10844#ifdef HAVE_MALLOC_USABLE_SIZE
10845 if (!hint) {
10846 hint = malloc_usable_size(ptr);
10847 }
10848#endif
10849 return hint;
10850}
10851
10852enum memop_type {
10853 MEMOP_TYPE_MALLOC = 0,
10854 MEMOP_TYPE_FREE,
10855 MEMOP_TYPE_REALLOC
10856};
10857
10858static inline void
10859atomic_sub_nounderflow(size_t *var, size_t sub)
10860{
10861 if (sub == 0) return;
10862
10863 while (1) {
10864 size_t val = *var;
10865 if (val < sub) sub = val;
10866 if (RUBY_ATOMIC_SIZE_CAS(*var, val, val-sub) == val) break;
10867 }
10868}
10869
10870#define gc_stress_full_mark_after_malloc_p() \
10871 (FIXNUM_P(ruby_gc_stress_mode) && (FIX2LONG(ruby_gc_stress_mode) & (1<<gc_stress_full_mark_after_malloc)))
10872
10873static void
10874objspace_malloc_gc_stress(rb_objspace_t *objspace)
10875{
10876 if (ruby_gc_stressful && ruby_native_thread_p()) {
10877 unsigned int reason = (GPR_FLAG_IMMEDIATE_MARK | GPR_FLAG_IMMEDIATE_SWEEP |
10878 GPR_FLAG_STRESS | GPR_FLAG_MALLOC);
10879
10880 if (gc_stress_full_mark_after_malloc_p()) {
10881 reason |= GPR_FLAG_FULL_MARK;
10882 }
10883 garbage_collect_with_gvl(objspace, reason);
10884 }
10885}
10886
10887static void
10888malloc_increase_commit(rb_objspace_t *objspace, size_t new_size, size_t old_size)
10889{
10890 if (new_size > old_size) {
10891 size_t delta = new_size - old_size;
10892 MALLOC_COUNTERS_LOCK(objspace);
10893 gc_counter_add(&objspace->malloc_counters.counters.malloc, delta);
10894#if RGENGC_ESTIMATE_OLDMALLOC
10895 gc_counter_add(&objspace->malloc_counters.oldcounters.malloc, delta);
10896#endif
10897 MALLOC_COUNTERS_UNLOCK(objspace);
10898 }
10899 else if (old_size > new_size) {
10900 size_t delta = old_size - new_size;
10901 MALLOC_COUNTERS_LOCK(objspace);
10902 gc_counter_add(&objspace->malloc_counters.counters.free, delta);
10903#if RGENGC_ESTIMATE_OLDMALLOC
10904 gc_counter_add(&objspace->malloc_counters.oldcounters.free, delta);
10905#endif
10906 MALLOC_COUNTERS_UNLOCK(objspace);
10907 }
10908}
10909
10910#if USE_MALLOC_INCREASE_LOCAL
10911static void
10912malloc_increase_local_flush(rb_objspace_t *objspace)
10913{
10914 int delta = malloc_increase_local;
10915 if (delta == 0) return;
10916
10917 malloc_increase_local = 0;
10918 if (delta > 0) {
10919 malloc_increase_commit(objspace, (size_t)delta, 0);
10920 }
10921 else {
10922 malloc_increase_commit(objspace, 0, (size_t)(-delta));
10923 }
10924}
10925#else
10926static void
10927malloc_increase_local_flush(rb_objspace_t *objspace)
10928{
10929}
10930#endif
10931
10932static inline bool
10933objspace_malloc_increase_report(rb_objspace_t *objspace, void *mem, size_t new_size, size_t old_size, enum memop_type type, bool gc_allowed)
10934{
10935 if (0) fprintf(stderr, "increase - ptr: %p, type: %s, new_size: %"PRIdSIZE", old_size: %"PRIdSIZE"\n",
10936 mem,
10937 type == MEMOP_TYPE_MALLOC ? "malloc" :
10938 type == MEMOP_TYPE_FREE ? "free " :
10939 type == MEMOP_TYPE_REALLOC ? "realloc": "error",
10940 new_size, old_size);
10941 return false;
10942}
10943
10944static bool
10945objspace_malloc_increase_body(rb_objspace_t *objspace, void *mem, size_t new_size, size_t old_size, enum memop_type type, bool gc_allowed)
10946{
10947#if USE_MALLOC_INCREASE_LOCAL
10948 if (new_size < GC_MALLOC_INCREASE_LOCAL_THRESHOLD &&
10949 old_size < GC_MALLOC_INCREASE_LOCAL_THRESHOLD) {
10950 malloc_increase_local += (int)new_size - (int)old_size;
10951
10952 if (malloc_increase_local >= GC_MALLOC_INCREASE_LOCAL_THRESHOLD ||
10953 malloc_increase_local <= -GC_MALLOC_INCREASE_LOCAL_THRESHOLD) {
10954 malloc_increase_local_flush(objspace);
10955 }
10956 }
10957 else {
10958 malloc_increase_local_flush(objspace);
10959 malloc_increase_commit(objspace, new_size, old_size);
10960 }
10961#else
10962 malloc_increase_commit(objspace, new_size, old_size);
10963#endif
10964
10965 if (type == MEMOP_TYPE_MALLOC && gc_allowed) {
10966 retry:
10967 if (malloc_increase > malloc_limit && ruby_native_thread_p() && !dont_gc_val() && !rb_gc_gc_disabled_global_p()) {
10968 if (ruby_thread_has_gvl_p() && is_lazy_sweeping(objspace)) {
10969 gc_sweep_step_for_malloc(objspace); /* sweeping frees may reduce malloc_increase */
10970 goto retry;
10971 }
10972 garbage_collect_with_gvl(objspace, GPR_FLAG_MALLOC);
10973 }
10974 }
10975
10976#if MALLOC_ALLOCATED_SIZE
10977 if (new_size >= old_size) {
10978 RUBY_ATOMIC_SIZE_ADD(objspace->malloc_params.allocated_size, new_size - old_size);
10979 }
10980 else {
10981 size_t dec_size = old_size - new_size;
10982
10983#if MALLOC_ALLOCATED_SIZE_CHECK
10984 size_t allocated_size = objspace->malloc_params.allocated_size;
10985 if (allocated_size < dec_size) {
10986 rb_bug("objspace_malloc_increase: underflow malloc_params.allocated_size.");
10987 }
10988#endif
10989 atomic_sub_nounderflow(&objspace->malloc_params.allocated_size, dec_size);
10990 }
10991
10992 switch (type) {
10993 case MEMOP_TYPE_MALLOC:
10994 RUBY_ATOMIC_SIZE_INC(objspace->malloc_params.allocations);
10995 break;
10996 case MEMOP_TYPE_FREE:
10997 {
10998 size_t allocations = objspace->malloc_params.allocations;
10999 if (allocations > 0) {
11000 atomic_sub_nounderflow(&objspace->malloc_params.allocations, 1);
11001 }
11002#if MALLOC_ALLOCATED_SIZE_CHECK
11003 else {
11004 GC_ASSERT(objspace->malloc_params.allocations > 0);
11005 }
11006#endif
11007 }
11008 break;
11009 case MEMOP_TYPE_REALLOC: /* ignore */ break;
11010 }
11011#endif
11012 return true;
11013}
11014
11015#define objspace_malloc_increase(...) \
11016 for (bool malloc_increase_done = objspace_malloc_increase_report(__VA_ARGS__); \
11017 !malloc_increase_done; \
11018 malloc_increase_done = objspace_malloc_increase_body(__VA_ARGS__))
11019
11020struct malloc_obj_info { /* 4 words */
11021 size_t size;
11022};
11023
11024static inline size_t
11025objspace_malloc_prepare(rb_objspace_t *objspace, size_t size)
11026{
11027 if (size == 0) size = 1;
11028
11029#if CALC_EXACT_MALLOC_SIZE
11030 size += sizeof(struct malloc_obj_info);
11031#endif
11032
11033 return size;
11034}
11035
11036static bool
11037malloc_during_gc_p(rb_objspace_t *objspace)
11038{
11039 /* malloc is not allowed during GC when we're not using multiple ractors
11040 * (since ractors can run while another thread is sweeping) and when we
11041 * have the GVL (since if we don't have the GVL, we'll try to acquire the
11042 * GVL which will block and ensure the other thread finishes GC). */
11043 return during_gc && !dont_gc_val() && !rb_gc_multi_ractor_p() && ruby_thread_has_gvl_p();
11044}
11045
11046static inline void *
11047objspace_malloc_fixup(rb_objspace_t *objspace, void *mem, size_t size, bool gc_allowed)
11048{
11049 size = objspace_malloc_size(objspace, mem, size);
11050 objspace_malloc_increase(objspace, mem, size, 0, MEMOP_TYPE_MALLOC, gc_allowed) {}
11051
11052#if CALC_EXACT_MALLOC_SIZE
11053 {
11054 struct malloc_obj_info *info = mem;
11055 info->size = size;
11056 mem = info + 1;
11057 }
11058#endif
11059
11060 return mem;
11061}
11062
11063#if defined(__GNUC__) && RUBY_DEBUG
11064#define RB_BUG_INSTEAD_OF_RB_MEMERROR 1
11065#endif
11066
11067#ifndef RB_BUG_INSTEAD_OF_RB_MEMERROR
11068# define RB_BUG_INSTEAD_OF_RB_MEMERROR 0
11069#endif
11070
11071#define GC_MEMERROR(...) \
11072 ((RB_BUG_INSTEAD_OF_RB_MEMERROR+0) ? rb_bug("" __VA_ARGS__) : (void)0)
11073
11074#define TRY_WITH_GC(siz, expr) do { \
11075 const gc_profile_record_flag gpr = \
11076 GPR_FLAG_FULL_MARK | \
11077 GPR_FLAG_IMMEDIATE_MARK | \
11078 GPR_FLAG_IMMEDIATE_SWEEP | \
11079 GPR_FLAG_MALLOC; \
11080 /* stress GC must also honor gc_allowed (malloc_gc_disabled) */ \
11081 if (gc_allowed) objspace_malloc_gc_stress(objspace); \
11082 \
11083 if (RB_LIKELY((expr))) { \
11084 /* Success on 1st try */ \
11085 } \
11086 else if (gc_allowed && !garbage_collect_with_gvl(objspace, gpr)) { \
11087 /* @shyouhei thinks this doesn't happen */ \
11088 GC_MEMERROR("TRY_WITH_GC: could not GC"); \
11089 } \
11090 else if ((expr)) { \
11091 /* Success on 2nd try */ \
11092 } \
11093 else { \
11094 GC_MEMERROR("TRY_WITH_GC: could not allocate:" \
11095 "%"PRIdSIZE" bytes for %s", \
11096 siz, # expr); \
11097 } \
11098 } while (0)
11099
11100static void
11101check_malloc_not_in_gc(rb_objspace_t *objspace, const char *msg)
11102{
11103 if (RB_UNLIKELY(malloc_during_gc_p(objspace))) {
11104 dont_gc_on();
11105 during_gc = false;
11106 rb_bug("Cannot %s during GC", msg);
11107 }
11108}
11109
11110void
11111rb_gc_impl_free(void *objspace_ptr, void *ptr, size_t old_size)
11112{
11113 rb_objspace_t *objspace = objspace_ptr;
11114
11115 if (!ptr) {
11116 /*
11117 * ISO/IEC 9899 says "If ptr is a null pointer, no action occurs" since
11118 * its first version. We would better follow.
11119 */
11120 return;
11121 }
11122#if CALC_EXACT_MALLOC_SIZE
11123 struct malloc_obj_info *info = (struct malloc_obj_info *)ptr - 1;
11124#if VERIFY_FREE_SIZE
11125 if (!info->size) {
11126 rb_bug("buffer %p has no recorded size. Was it allocated with ruby_mimalloc? If so it should be freed with ruby_mimfree", ptr);
11127 }
11128
11129 if (old_size && (old_size + sizeof(struct malloc_obj_info)) != info->size) {
11130 rb_bug("buffer %p freed with old_size=%zu, but was allocated with size=%zu", ptr, old_size, info->size - sizeof(struct malloc_obj_info));
11131 }
11132#endif
11133 ptr = info;
11134 old_size = info->size;
11135#endif
11136 old_size = objspace_malloc_size(objspace, ptr, old_size);
11137
11138 objspace_malloc_increase(objspace, ptr, 0, old_size, MEMOP_TYPE_FREE, true) {
11139 free(ptr);
11140 ptr = NULL;
11141 RB_DEBUG_COUNTER_INC(heap_xfree);
11142 }
11143}
11144
11145void *
11146rb_gc_impl_malloc(void *objspace_ptr, size_t size, bool gc_allowed)
11147{
11148 rb_objspace_t *objspace = objspace_ptr;
11149 check_malloc_not_in_gc(objspace, "malloc");
11150
11151 void *mem;
11152
11153 size = objspace_malloc_prepare(objspace, size);
11154 TRY_WITH_GC(size, mem = malloc(size));
11155 RB_DEBUG_COUNTER_INC(heap_xmalloc);
11156 if (!mem) return mem;
11157 return objspace_malloc_fixup(objspace, mem, size, gc_allowed);
11158}
11159
11160void *
11161rb_gc_impl_calloc(void *objspace_ptr, size_t size, bool gc_allowed)
11162{
11163 rb_objspace_t *objspace = objspace_ptr;
11164
11165 if (RB_UNLIKELY(malloc_during_gc_p(objspace))) {
11166 rb_warn("calloc during GC detected, this could cause crashes if it triggers another GC");
11167#if RGENGC_CHECK_MODE || RUBY_DEBUG
11168 rb_bug("Cannot calloc during GC");
11169#endif
11170 }
11171
11172 void *mem;
11173
11174 size = objspace_malloc_prepare(objspace, size);
11175 TRY_WITH_GC(size, mem = calloc1(size));
11176 if (!mem) return mem;
11177 return objspace_malloc_fixup(objspace, mem, size, gc_allowed);
11178}
11179
11180void *
11181rb_gc_impl_realloc(void *objspace_ptr, void *ptr, size_t new_size, size_t old_size, bool gc_allowed)
11182{
11183 rb_objspace_t *objspace = objspace_ptr;
11184
11185 check_malloc_not_in_gc(objspace, "realloc");
11186
11187 void *mem;
11188
11189 if (!ptr) return rb_gc_impl_malloc(objspace, new_size, gc_allowed);
11190
11191 /*
11192 * The behavior of realloc(ptr, 0) is implementation defined.
11193 * Therefore we don't use realloc(ptr, 0) for portability reason.
11194 * see http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_400.htm
11195 */
11196 if (new_size == 0) {
11197 if ((mem = rb_gc_impl_malloc(objspace, 0, gc_allowed)) != NULL) {
11198 /*
11199 * - OpenBSD's malloc(3) man page says that when 0 is passed, it
11200 * returns a non-NULL pointer to an access-protected memory page.
11201 * The returned pointer cannot be read / written at all, but
11202 * still be a valid argument of free().
11203 *
11204 * https://man.openbsd.org/malloc.3
11205 *
11206 * - Linux's malloc(3) man page says that it _might_ perhaps return
11207 * a non-NULL pointer when its argument is 0. That return value
11208 * is safe (and is expected) to be passed to free().
11209 *
11210 * https://man7.org/linux/man-pages/man3/malloc.3.html
11211 *
11212 * - As I read the implementation jemalloc's malloc() returns fully
11213 * normal 16 bytes memory region when its argument is 0.
11214 *
11215 * - As I read the implementation musl libc's malloc() returns
11216 * fully normal 32 bytes memory region when its argument is 0.
11217 *
11218 * - Other malloc implementations can also return non-NULL.
11219 */
11220 rb_gc_impl_free(objspace, ptr, old_size);
11221 return mem;
11222 }
11223 else {
11224 /*
11225 * It is dangerous to return NULL here, because that could lead to
11226 * RCE. Fallback to 1 byte instead of zero.
11227 *
11228 * https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-11932
11229 */
11230 new_size = 1;
11231 }
11232 }
11233
11234#if CALC_EXACT_MALLOC_SIZE
11235 {
11236 struct malloc_obj_info *info = (struct malloc_obj_info *)ptr - 1;
11237 new_size += sizeof(struct malloc_obj_info);
11238 ptr = info;
11239#if VERIFY_FREE_SIZE
11240 if (old_size && (old_size + sizeof(struct malloc_obj_info)) != info->size) {
11241 rb_bug("buffer %p realloced with old_size=%zu, but was allocated with size=%zu", ptr, old_size, info->size - sizeof(struct malloc_obj_info));
11242 }
11243#endif
11244 old_size = info->size;
11245 }
11246#endif
11247
11248 old_size = objspace_malloc_size(objspace, ptr, old_size);
11249 TRY_WITH_GC(new_size, mem = RB_GNUC_EXTENSION_BLOCK(realloc(ptr, new_size)));
11250 if (!mem) return mem;
11251 new_size = objspace_malloc_size(objspace, mem, new_size);
11252
11253#if CALC_EXACT_MALLOC_SIZE
11254 {
11255 struct malloc_obj_info *info = mem;
11256 info->size = new_size;
11257 mem = info + 1;
11258 }
11259#endif
11260
11261 objspace_malloc_increase(objspace, mem, new_size, old_size, MEMOP_TYPE_REALLOC, gc_allowed);
11262
11263 RB_DEBUG_COUNTER_INC(heap_xrealloc);
11264 return mem;
11265}
11266
11267void
11268rb_gc_impl_adjust_memory_usage(void *objspace_ptr, ssize_t diff)
11269{
11270 rb_objspace_t *objspace = objspace_ptr;
11271
11272 if (diff > 0) {
11273 objspace_malloc_increase(objspace, 0, diff, 0, MEMOP_TYPE_REALLOC, true);
11274 }
11275 else if (diff < 0) {
11276 objspace_malloc_increase(objspace, 0, 0, -diff, MEMOP_TYPE_REALLOC, true);
11277 }
11278}
11279
11280// TODO: move GC profiler stuff back into gc.c
11281/*
11282 ------------------------------ GC profiler ------------------------------
11283*/
11284
11285#define GC_PROFILE_RECORD_DEFAULT_SIZE 100
11286#define GC_PROFILE_RECORD_DEFAULT_MAX_RECORDS 4096
11287#define GC_PROFILE_RECORD_UNBOUNDED 0
11288
11289static bool
11290current_process_time(struct timespec *ts)
11291{
11292#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_PROCESS_CPUTIME_ID)
11293 {
11294 static int try_clock_gettime = 1;
11295 if (try_clock_gettime && clock_gettime(CLOCK_PROCESS_CPUTIME_ID, ts) == 0) {
11296 return true;
11297 }
11298 else {
11299 try_clock_gettime = 0;
11300 }
11301 }
11302#endif
11303
11304#ifdef RUSAGE_SELF
11305 {
11306 struct rusage usage;
11307 struct timeval time;
11308 if (getrusage(RUSAGE_SELF, &usage) == 0) {
11309 time = usage.ru_utime;
11310 ts->tv_sec = time.tv_sec;
11311 ts->tv_nsec = (int32_t)time.tv_usec * 1000;
11312 return true;
11313 }
11314 }
11315#endif
11316
11317#ifdef _WIN32
11318 {
11319 FILETIME creation_time, exit_time, kernel_time, user_time;
11320 ULARGE_INTEGER ui;
11321
11322 if (GetProcessTimes(GetCurrentProcess(),
11323 &creation_time, &exit_time, &kernel_time, &user_time) != 0) {
11324 memcpy(&ui, &user_time, sizeof(FILETIME));
11325#define PER100NSEC (uint64_t)(1000 * 1000 * 10)
11326 ts->tv_nsec = (long)(ui.QuadPart % PER100NSEC);
11327 ts->tv_sec = (time_t)(ui.QuadPart / PER100NSEC);
11328 return true;
11329 }
11330 }
11331#endif
11332
11333 return false;
11334}
11335
11336static double
11337getrusage_time(void)
11338{
11339 struct timespec ts;
11340 if (current_process_time(&ts)) {
11341 return ts.tv_sec + ts.tv_nsec * 1e-9;
11342 }
11343 else {
11344 return 0.0;
11345 }
11346}
11347
11348static inline double
11349hrtime_to_sec(rb_hrtime_t time)
11350{
11351 return (double)time / (double)RB_HRTIME_PER_SEC;
11352}
11353
11354static inline rb_hrtime_t
11355elapsed_hrtime_from(rb_hrtime_t start)
11356{
11357 return rb_hrtime_sub(rb_hrtime_now(), start);
11358}
11359
11360
11361static inline size_t
11362gc_profile_record_count(rb_objspace_t *objspace)
11363{
11364 return objspace->profile.record_count;
11365}
11366
11367static inline size_t
11368gc_profile_record_index(rb_objspace_t *objspace, size_t logical_index)
11369{
11370 if (objspace->profile.max_records != GC_PROFILE_RECORD_UNBOUNDED &&
11371 objspace->profile.record_count == objspace->profile.size) {
11372 return (objspace->profile.next_index + logical_index) % objspace->profile.size;
11373 }
11374 else {
11375 return logical_index;
11376 }
11377}
11378
11379static void
11380gc_profile_records_free(rb_objspace_t *objspace)
11381{
11382 void *p = objspace->profile.records;
11383 objspace->profile.records = NULL;
11384 objspace->profile.size = 0;
11385 objspace->profile.next_index = 0;
11386 objspace->profile.record_count = 0;
11387 objspace->profile.current_record = 0;
11388 free(p);
11389}
11390
11391static inline void
11392gc_prof_setup_new_record(rb_objspace_t *objspace, unsigned int reason)
11393{
11394 if (objspace->profile.run) {
11395 size_t index;
11396 gc_profile_record *record;
11397
11398 if (objspace->profile.max_records == GC_PROFILE_RECORD_UNBOUNDED) {
11399 index = objspace->profile.record_count++;
11400 objspace->profile.next_index = objspace->profile.record_count;
11401
11402 if (!objspace->profile.records) {
11403 objspace->profile.size = GC_PROFILE_RECORD_DEFAULT_SIZE;
11404 objspace->profile.records = malloc(xmalloc2_size(sizeof(gc_profile_record), objspace->profile.size));
11405 }
11406 if (index >= objspace->profile.size) {
11407 void *ptr;
11408 objspace->profile.size += 1000;
11409 ptr = realloc(objspace->profile.records, xmalloc2_size(sizeof(gc_profile_record), objspace->profile.size));
11410 if (!ptr) rb_memerror();
11411 objspace->profile.records = ptr;
11412 }
11413 }
11414 else {
11415 if (!objspace->profile.records) {
11416 objspace->profile.size = objspace->profile.max_records;
11417 objspace->profile.records = malloc(xmalloc2_size(sizeof(gc_profile_record), objspace->profile.size));
11418 }
11419 index = objspace->profile.next_index;
11420 objspace->profile.next_index = (objspace->profile.next_index + 1) % objspace->profile.size;
11421 if (objspace->profile.record_count < objspace->profile.size) {
11422 objspace->profile.record_count++;
11423 }
11424 }
11425
11426 if (!objspace->profile.records) {
11427 rb_bug("gc_profile malloc or realloc miss");
11428 }
11429 record = objspace->profile.current_record = &objspace->profile.records[index];
11430 MEMZERO(record, gc_profile_record, 1);
11431
11432 /* setup before-GC parameter */
11433 record->flags = reason | (ruby_gc_stressful ? GPR_FLAG_STRESS : 0);
11434 record->sequence = objspace->profile.record_sequence++;
11435 record->gc_invoke_wall_time = rb_hrtime_sub(rb_hrtime_now(),
11436 objspace->profile.invoke_wall_time);
11437#if MALLOC_ALLOCATED_SIZE
11438 record->allocated_size = malloc_allocated_size;
11439#endif
11440#if GC_PROFILE_MORE_DETAIL && GC_PROFILE_DETAIL_MEMORY
11441#ifdef RUSAGE_SELF
11442 {
11443 struct rusage usage;
11444 if (getrusage(RUSAGE_SELF, &usage) == 0) {
11445 record->maxrss = usage.ru_maxrss;
11446 record->minflt = usage.ru_minflt;
11447 record->majflt = usage.ru_majflt;
11448 }
11449 }
11450#endif
11451#endif
11452 }
11453}
11454
11455static inline void
11456gc_prof_timer_start(rb_objspace_t *objspace)
11457{
11458 if (gc_prof_enabled(objspace)) {
11459 gc_profile_record *record = gc_prof_record(objspace);
11460#if GC_PROFILE_MORE_DETAIL
11461 record->prepare_time = objspace->profile.prepare_time;
11462#endif
11463 record->gc_time = 0;
11464 record->gc_invoke_time = getrusage_time();
11465 objspace->profile.gc_wall_start_time = rb_hrtime_now();
11466 }
11467}
11468
11469static double
11470elapsed_time_from(double time)
11471{
11472 double now = getrusage_time();
11473 if (now > time) {
11474 return now - time;
11475 }
11476 else {
11477 return 0;
11478 }
11479}
11480
11481static inline void
11482gc_prof_timer_stop(rb_objspace_t *objspace)
11483{
11484 if (gc_prof_enabled(objspace)) {
11485 gc_profile_record *record = gc_prof_record(objspace);
11486 record->gc_time = elapsed_time_from(record->gc_invoke_time);
11487 record->gc_invoke_time -= objspace->profile.invoke_time;
11488 record->gc_wall_time = elapsed_hrtime_from(objspace->profile.gc_wall_start_time);
11489 }
11490}
11491
11492static inline void
11493gc_prof_mark_timer_start(rb_objspace_t *objspace)
11494{
11495 RUBY_DTRACE_GC_HOOK(MARK_BEGIN);
11496#if GC_PROFILE_MORE_DETAIL
11497 if (gc_prof_enabled(objspace)) {
11498 gc_prof_record(objspace)->gc_mark_time = getrusage_time();
11499 }
11500#endif
11501}
11502
11503static inline void
11504gc_prof_mark_timer_stop(rb_objspace_t *objspace)
11505{
11506 RUBY_DTRACE_GC_HOOK(MARK_END);
11507#if GC_PROFILE_MORE_DETAIL
11508 if (gc_prof_enabled(objspace)) {
11509 gc_profile_record *record = gc_prof_record(objspace);
11510 record->gc_mark_time = elapsed_time_from(record->gc_mark_time);
11511 }
11512#endif
11513}
11514
11515static inline void
11516gc_prof_sweep_timer_start(rb_objspace_t *objspace)
11517{
11518 RUBY_DTRACE_GC_HOOK(SWEEP_BEGIN);
11519 if (gc_prof_enabled(objspace)) {
11520 gc_profile_record *record = gc_prof_record(objspace);
11521
11522 if (record->gc_time > 0 || GC_PROFILE_MORE_DETAIL) {
11523 objspace->profile.gc_sweep_start_time = getrusage_time();
11524 objspace->profile.gc_sweep_wall_start_time = rb_hrtime_now();
11525 }
11526 }
11527}
11528
11529static inline void
11530gc_prof_sweep_timer_stop(rb_objspace_t *objspace)
11531{
11532 RUBY_DTRACE_GC_HOOK(SWEEP_END);
11533
11534 if (gc_prof_enabled(objspace)) {
11535 double sweep_time;
11536 gc_profile_record *record = gc_prof_record(objspace);
11537
11538 if (record->gc_time > 0) {
11539 sweep_time = elapsed_time_from(objspace->profile.gc_sweep_start_time);
11540 /* need to accumulate GC time for lazy sweep after gc() */
11541 record->gc_time += sweep_time;
11542 record->gc_wall_time = rb_hrtime_add(record->gc_wall_time,
11543 elapsed_hrtime_from(objspace->profile.gc_sweep_wall_start_time));
11544 }
11545 else if (GC_PROFILE_MORE_DETAIL) {
11546 sweep_time = elapsed_time_from(objspace->profile.gc_sweep_start_time);
11547 }
11548
11549#if GC_PROFILE_MORE_DETAIL
11550 record->gc_sweep_time += sweep_time;
11551 if (heap_pages_deferred_final) record->flags |= GPR_FLAG_HAVE_FINALIZE;
11552#endif
11553 if (heap_pages_deferred_final) objspace->profile.latest_gc_info |= GPR_FLAG_HAVE_FINALIZE;
11554 }
11555}
11556
11557static inline void
11558gc_prof_set_malloc_info(rb_objspace_t *objspace)
11559{
11560#if GC_PROFILE_MORE_DETAIL
11561 if (gc_prof_enabled(objspace)) {
11562 gc_profile_record *record = gc_prof_record(objspace);
11563 record->allocate_increase = malloc_increase;
11564 record->allocate_limit = malloc_limit;
11565 }
11566#endif
11567}
11568
11569static inline void
11570gc_prof_set_heap_info(rb_objspace_t *objspace)
11571{
11572 if (gc_prof_enabled(objspace)) {
11573 gc_profile_record *record = gc_prof_record(objspace);
11574
11575 /* Sum across all size pools since each has a different slot size. */
11576 size_t total = 0;
11577 size_t use_size = 0;
11578 size_t total_size = 0;
11579 for (int i = 0; i < HEAP_COUNT; i++) {
11580 rb_heap_t *heap = &heaps[i];
11581 size_t heap_live = heap->total_allocated_objects - heap->total_freed_objects - heap->final_slots_count;
11582 total += heap->total_slots;
11583 use_size += heap_live * heap->slot_size;
11584 total_size += heap->total_slots * heap->slot_size;
11585 }
11586
11587#if GC_PROFILE_MORE_DETAIL
11588 size_t live = objspace->profile.total_allocated_objects_at_gc_start - total_freed_objects(objspace);
11589 record->heap_use_pages = objspace->profile.heap_used_at_gc_start;
11590 record->heap_live_objects = live;
11591 record->heap_free_objects = total - live;
11592#endif
11593
11594 record->heap_total_objects = total;
11595 record->heap_use_size = use_size;
11596 record->heap_total_size = total_size;
11597 }
11598}
11599
11600/*
11601 * call-seq:
11602 * GC::Profiler.clear -> nil
11603 *
11604 * Clears the \GC profiler data.
11605 *
11606 */
11607
11608static VALUE
11609gc_profile_clear(VALUE _)
11610{
11611 rb_objspace_t *objspace = rb_gc_get_objspace();
11612 gc_profile_records_free(objspace);
11613 return Qnil;
11614}
11615
11616/*
11617 * call-seq:
11618 * GC::Profiler.configure(max_records: 4096) -> nil
11619 *
11620 * Configures how many raw profile records are retained by
11621 * GC::Profiler.raw_data.
11622 *
11623 * The profiler keeps at most +max_records+ records in a bounded ring buffer.
11624 * When the buffer is full, newer GC records overwrite the oldest retained
11625 * records. The default limit is 4096 records.
11626 *
11627 * Pass +nil+ to restore the historical unbounded behavior:
11628 *
11629 * GC::Profiler.configure(max_records: nil)
11630 *
11631 * Changing +max_records+ clears existing raw profile data. This method does
11632 * not enable or disable the profiler; use GC::Profiler.enable and
11633 * GC::Profiler.disable for that.
11634 */
11635
11636static VALUE
11637gc_profile_configure(int argc, VALUE *argv, VALUE _)
11638{
11639 static ID keywords[1] = {0};
11640 VALUE options, max_records;
11641 rb_objspace_t *objspace = rb_gc_get_objspace();
11642
11643 if (!keywords[0]) {
11644 keywords[0] = rb_intern("max_records");
11645 }
11646
11647 rb_scan_args_kw(rb_keyword_given_p(), argc, argv, ":", &options);
11648 rb_get_kwargs(options, keywords, 0, 1, &max_records);
11649
11650 if (max_records == Qundef) {
11651 return Qnil;
11652 }
11653 else if (NIL_P(max_records)) {
11654 objspace->profile.max_records = GC_PROFILE_RECORD_UNBOUNDED;
11655 }
11656 else {
11657 long value = NUM2LONG(max_records);
11658 if (value <= 0) {
11659 rb_raise(rb_eArgError, "max_records must be positive or nil");
11660 }
11661 objspace->profile.max_records = (size_t)value;
11662 }
11663
11664 gc_profile_records_free(objspace);
11665 return Qnil;
11666}
11667
11668/*
11669 * call-seq:
11670 * GC::Profiler.raw_data(limit: nil, since: nil) -> [Hash, ...]
11671 *
11672 * Returns an Array of retained raw profile data Hashes ordered from earliest
11673 * to latest by +:GC_INVOKE_TIME+. +limit:+ returns at most the newest
11674 * retained records. +since:+ returns records with +:GC_SEQUENCE+ greater
11675 * than the given sequence.
11676 *
11677 * For example:
11678 *
11679 * [
11680 * {
11681 * :GC_TIME=>1.3000000000000858e-05,
11682 * :GC_INVOKE_TIME=>0.010634999999999999,
11683 * :GC_WALL_TIME=>1.4000000000000001e-05,
11684 * :GC_INVOKE_WALL_TIME=>0.010640000000000000,
11685 * :GC_PAUSE_TIME=>1.5000000000000000e-05,
11686 * :GC_STOP_TIME=>1.0000000000000000e-06,
11687 * :GC_STW_TIME=>1.4000000000000001e-05,
11688 * :GC_MARK_WALL_TIME=>9.0000000000000002e-06,
11689 * :GC_SWEEP_WALL_TIME=>5.0000000000000004e-06,
11690 * :GC_COMPACT_WALL_TIME=>0.0000000000000000e+00,
11691 * :HEAP_USE_SIZE=>289640,
11692 * :HEAP_TOTAL_SIZE=>588960,
11693 * :HEAP_TOTAL_OBJECTS=>14724,
11694 * :GC_IS_MARKED=>false
11695 * },
11696 * # ...
11697 * ]
11698 *
11699 * The keys mean:
11700 *
11701 * +:GC_SEQUENCE+::
11702 * Monotonically increasing sequence number for this profiler record.
11703 * +:GC_TIME+::
11704 * CPU time elapsed in seconds for this GC run. This is process CPU time,
11705 * not elapsed wall-clock time.
11706 * +:GC_INVOKE_TIME+::
11707 * CPU time elapsed in seconds from startup to when the GC was invoked.
11708 * +:GC_WALL_TIME+::
11709 * Monotonic wall-clock counterpart to +:GC_TIME+ for this GC record.
11710 * This does not include time spent stopping other ractors before the VM
11711 * enters GC. Use the phase wall-clock fields below for mark, sweep, and
11712 * compaction attribution.
11713 * +:GC_INVOKE_WALL_TIME+::
11714 * Monotonic wall-clock time elapsed in seconds from startup to when the GC
11715 * was invoked.
11716 * +:GC_PAUSE_TIME+::
11717 * Monotonic wall-clock time elapsed in seconds while user execution was
11718 * blocked by this GC entry, including time to stop other ractors. This
11719 * may include time from incremental marking or lazy sweeping continuation
11720 * charged to this record.
11721 * +:GC_STOP_TIME+::
11722 * Monotonic wall-clock time elapsed in seconds stopping other ractors.
11723 * +:GC_STW_TIME+::
11724 * Monotonic wall-clock time elapsed in seconds after other ractors have
11725 * stopped and before the VM exits GC.
11726 * +:GC_MARK_WALL_TIME+::
11727 * Monotonic wall-clock time elapsed in seconds spent marking for this GC
11728 * record, accumulated across incremental marking continuations.
11729 * +:GC_SWEEP_WALL_TIME+::
11730 * Monotonic wall-clock time elapsed in seconds spent sweeping for this GC
11731 * record, accumulated across lazy sweeping continuations. This does not
11732 * include compaction time, which is reported separately as
11733 * +:GC_COMPACT_WALL_TIME+.
11734 * +:GC_COMPACT_WALL_TIME+::
11735 * Monotonic wall-clock time elapsed in seconds spent compacting for this GC
11736 * record, or +0.0+ if this GC did not compact.
11737 * +:HEAP_USE_SIZE+::
11738 * Total bytes of heap used
11739 * +:HEAP_TOTAL_SIZE+::
11740 * Total size of heap in bytes
11741 * +:HEAP_TOTAL_OBJECTS+::
11742 * Total number of objects
11743 * +:GC_IS_MARKED+::
11744 * Returns +true+ if the GC is in mark phase
11745 *
11746 * The wall-clock timing fields relate to each other as follows:
11747 *
11748 * GC_PAUSE_TIME == GC_STOP_TIME + GC_STW_TIME
11749 *
11750 * +:GC_MARK_WALL_TIME+, +:GC_SWEEP_WALL_TIME+, and +:GC_COMPACT_WALL_TIME+
11751 * report separate phase timings and must not be added to +:GC_WALL_TIME+.
11752 *
11753 * +:GC_WALL_TIME+ is the wall-clock counterpart to +:GC_TIME+ and is nested
11754 * inside +:GC_STW_TIME+, so it must not be added to +:GC_STW_TIME+. The difference
11755 * +GC_STW_TIME - GC_WALL_TIME+ is VM overhead inside the stopped interval
11756 * (GC event hooks, bookkeeping, consistency checks, and continuation work).
11757 *
11758 * If ruby was built with +GC_PROFILE_MORE_DETAIL+, you will also have access
11759 * to the following hash keys:
11760 *
11761 * +:GC_MARK_TIME+::
11762 * +:GC_SWEEP_TIME+::
11763 * +:ALLOCATE_INCREASE+::
11764 * +:ALLOCATE_LIMIT+::
11765 * +:HEAP_USE_PAGES+::
11766 * +:HEAP_LIVE_OBJECTS+::
11767 * +:HEAP_FREE_OBJECTS+::
11768 * +:HAVE_FINALIZE+::
11769 *
11770 */
11771
11772static VALUE
11773gc_profile_record_get(int argc, VALUE *argv, VALUE _)
11774{
11775 static ID keywords[2] = {0};
11776 VALUE prof, options, limit_value, since_value;
11777 VALUE gc_profile = rb_ary_new();
11778 size_t i, count, matching = 0, skip = 0, limit = SIZE_MAX, since = 0;
11779 bool use_since = false;
11780 rb_objspace_t *objspace = rb_gc_get_objspace();
11781
11782 if (!keywords[0]) {
11783 keywords[0] = rb_intern("limit");
11784 keywords[1] = rb_intern("since");
11785 }
11786
11787 rb_scan_args_kw(rb_keyword_given_p(), argc, argv, ":", &options);
11788 VALUE values[2] = {Qundef, Qundef};
11789 rb_get_kwargs(options, keywords, 0, 2, values);
11790 limit_value = values[0];
11791 since_value = values[1];
11792
11793 if (limit_value != Qundef && !NIL_P(limit_value)) {
11794 long value = NUM2LONG(limit_value);
11795 if (value < 0) {
11796 rb_raise(rb_eArgError, "limit must be non-negative");
11797 }
11798 limit = (size_t)value;
11799 }
11800 if (since_value != Qundef && !NIL_P(since_value)) {
11801 long value = NUM2LONG(since_value);
11802 if (value < 0) {
11803 rb_raise(rb_eArgError, "since must be non-negative");
11804 }
11805 since = (size_t)value;
11806 use_since = true;
11807 }
11808
11809 if (!objspace->profile.run) {
11810 return Qnil;
11811 }
11812
11813 count = gc_profile_record_count(objspace);
11814 for (i = 0; i < count; i++) {
11815 gc_profile_record *record = &objspace->profile.records[gc_profile_record_index(objspace, i)];
11816 if (!use_since || record->sequence > since) {
11817 matching++;
11818 }
11819 }
11820 if (limit < matching) {
11821 skip = matching - limit;
11822 }
11823
11824 for (i = 0; i < count; i++) {
11825 gc_profile_record *record = &objspace->profile.records[gc_profile_record_index(objspace, i)];
11826 if (use_since && record->sequence <= since) {
11827 continue;
11828 }
11829 if (skip > 0) {
11830 skip--;
11831 continue;
11832 }
11833
11834 prof = rb_hash_new();
11835 rb_hash_aset(prof, ID2SYM(rb_intern("GC_FLAGS")), gc_info_decode(objspace, rb_hash_new(), record->flags));
11836 rb_hash_aset(prof, ID2SYM(rb_intern("GC_SEQUENCE")), SIZET2NUM(record->sequence));
11837 rb_hash_aset(prof, ID2SYM(rb_intern("GC_TIME")), DBL2NUM(record->gc_time));
11838 rb_hash_aset(prof, ID2SYM(rb_intern("GC_INVOKE_TIME")), DBL2NUM(record->gc_invoke_time));
11839 rb_hash_aset(prof, ID2SYM(rb_intern("GC_WALL_TIME")),
11840 DBL2NUM(hrtime_to_sec(record->gc_wall_time)));
11841 rb_hash_aset(prof, ID2SYM(rb_intern("GC_INVOKE_WALL_TIME")),
11842 DBL2NUM(hrtime_to_sec(record->gc_invoke_wall_time)));
11843 rb_hash_aset(prof, ID2SYM(rb_intern("GC_PAUSE_TIME")),
11844 DBL2NUM(hrtime_to_sec(record->gc_pause_time)));
11845 rb_hash_aset(prof, ID2SYM(rb_intern("GC_STOP_TIME")),
11846 DBL2NUM(hrtime_to_sec(record->gc_stop_time)));
11847 rb_hash_aset(prof, ID2SYM(rb_intern("GC_STW_TIME")),
11848 DBL2NUM(hrtime_to_sec(record->gc_stw_time)));
11849 rb_hash_aset(prof, ID2SYM(rb_intern("GC_MARK_WALL_TIME")),
11850 DBL2NUM(hrtime_to_sec(record->gc_mark_wall_time)));
11851 rb_hash_aset(prof, ID2SYM(rb_intern("GC_SWEEP_WALL_TIME")),
11852 DBL2NUM(hrtime_to_sec(record->gc_sweep_wall_time)));
11853 rb_hash_aset(prof, ID2SYM(rb_intern("GC_COMPACT_WALL_TIME")),
11854 DBL2NUM(hrtime_to_sec(record->gc_compact_wall_time)));
11855 rb_hash_aset(prof, ID2SYM(rb_intern("HEAP_USE_SIZE")), SIZET2NUM(record->heap_use_size));
11856 rb_hash_aset(prof, ID2SYM(rb_intern("HEAP_TOTAL_SIZE")), SIZET2NUM(record->heap_total_size));
11857 rb_hash_aset(prof, ID2SYM(rb_intern("HEAP_TOTAL_OBJECTS")), SIZET2NUM(record->heap_total_objects));
11858 rb_hash_aset(prof, ID2SYM(rb_intern("MOVED_OBJECTS")), SIZET2NUM(record->moved_objects));
11859 rb_hash_aset(prof, ID2SYM(rb_intern("GC_IS_MARKED")), Qtrue);
11860#if GC_PROFILE_MORE_DETAIL
11861 rb_hash_aset(prof, ID2SYM(rb_intern("GC_MARK_TIME")), DBL2NUM(record->gc_mark_time));
11862 rb_hash_aset(prof, ID2SYM(rb_intern("GC_SWEEP_TIME")), DBL2NUM(record->gc_sweep_time));
11863 rb_hash_aset(prof, ID2SYM(rb_intern("ALLOCATE_INCREASE")), SIZET2NUM(record->allocate_increase));
11864 rb_hash_aset(prof, ID2SYM(rb_intern("ALLOCATE_LIMIT")), SIZET2NUM(record->allocate_limit));
11865 rb_hash_aset(prof, ID2SYM(rb_intern("HEAP_USE_PAGES")), SIZET2NUM(record->heap_use_pages));
11866 rb_hash_aset(prof, ID2SYM(rb_intern("HEAP_LIVE_OBJECTS")), SIZET2NUM(record->heap_live_objects));
11867 rb_hash_aset(prof, ID2SYM(rb_intern("HEAP_FREE_OBJECTS")), SIZET2NUM(record->heap_free_objects));
11868
11869 rb_hash_aset(prof, ID2SYM(rb_intern("REMOVING_OBJECTS")), SIZET2NUM(record->removing_objects));
11870 rb_hash_aset(prof, ID2SYM(rb_intern("EMPTY_OBJECTS")), SIZET2NUM(record->empty_objects));
11871
11872 rb_hash_aset(prof, ID2SYM(rb_intern("HAVE_FINALIZE")), (record->flags & GPR_FLAG_HAVE_FINALIZE) ? Qtrue : Qfalse);
11873#endif
11874
11875#if RGENGC_PROFILE > 0
11876 rb_hash_aset(prof, ID2SYM(rb_intern("OLD_OBJECTS")), SIZET2NUM(record->old_objects));
11877 rb_hash_aset(prof, ID2SYM(rb_intern("REMEMBERED_NORMAL_OBJECTS")), SIZET2NUM(record->remembered_normal_objects));
11878 rb_hash_aset(prof, ID2SYM(rb_intern("REMEMBERED_SHADY_OBJECTS")), SIZET2NUM(record->remembered_shady_objects));
11879#endif
11880 rb_ary_push(gc_profile, prof);
11881 }
11882
11883 return gc_profile;
11884}
11885
11886#if GC_PROFILE_MORE_DETAIL
11887#define MAJOR_REASON_MAX 0x10
11888
11889static char *
11890gc_profile_dump_major_reason(unsigned int flags, char *buff)
11891{
11892 unsigned int reason = flags & GPR_FLAG_MAJOR_MASK;
11893 int i = 0;
11894
11895 if (reason == GPR_FLAG_NONE) {
11896 buff[0] = '-';
11897 buff[1] = 0;
11898 }
11899 else {
11900#define C(x, s) \
11901 if (reason & GPR_FLAG_MAJOR_BY_##x) { \
11902 buff[i++] = #x[0]; \
11903 if (i >= MAJOR_REASON_MAX) rb_bug("gc_profile_dump_major_reason: overflow"); \
11904 buff[i] = 0; \
11905 }
11906 C(NOFREE, N);
11907 C(OLDGEN, O);
11908 C(SHADY, S);
11909#if RGENGC_ESTIMATE_OLDMALLOC
11910 C(OLDMALLOC, M);
11911#endif
11912#undef C
11913 }
11914 return buff;
11915}
11916#endif
11917
11918
11919
11920static void
11921gc_profile_dump_on(VALUE out, VALUE (*append)(VALUE, VALUE))
11922{
11923 rb_objspace_t *objspace = rb_gc_get_objspace();
11924 size_t count = gc_profile_record_count(objspace);
11925#ifdef MAJOR_REASON_MAX
11926 char reason_str[MAJOR_REASON_MAX];
11927#endif
11928
11929 if (objspace->profile.run && count /* > 1 */) {
11930 size_t i;
11931 const gc_profile_record *record;
11932
11933 append(out, rb_sprintf("GC %"PRIuSIZE" invokes.\n", objspace->profile.count));
11934 append(out, rb_str_new_cstr("Index Invoke Time(sec) Use Size(byte) Total Size(byte) Total Object GC Time(ms)\n"));
11935
11936 for (i = 0; i < count; i++) {
11937 record = &objspace->profile.records[gc_profile_record_index(objspace, i)];
11938 append(out, rb_sprintf("%5"PRIuSIZE" %19.3f %20"PRIuSIZE" %20"PRIuSIZE" %20"PRIuSIZE" %30.20f\n",
11939 i+1, record->gc_invoke_time, record->heap_use_size,
11940 record->heap_total_size, record->heap_total_objects, record->gc_time*1000));
11941 }
11942
11943#if GC_PROFILE_MORE_DETAIL
11944 const char *str = "\n\n" \
11945 "More detail.\n" \
11946 "Prepare Time = Previously GC's rest sweep time\n"
11947 "Index Flags Allocate Inc. Allocate Limit"
11948#if CALC_EXACT_MALLOC_SIZE
11949 " Allocated Size"
11950#endif
11951 " Use Page Mark Time(ms) Sweep Time(ms) Prepare Time(ms) LivingObj FreeObj RemovedObj EmptyObj"
11952#if RGENGC_PROFILE
11953 " OldgenObj RemNormObj RemShadObj"
11954#endif
11955#if GC_PROFILE_DETAIL_MEMORY
11956 " MaxRSS(KB) MinorFLT MajorFLT"
11957#endif
11958 "\n";
11959 append(out, rb_str_new_cstr(str));
11960
11961 for (i = 0; i < count; i++) {
11962 record = &objspace->profile.records[gc_profile_record_index(objspace, i)];
11963 append(out, rb_sprintf("%5"PRIuSIZE" %4s/%c/%6s%c %13"PRIuSIZE" %15"PRIuSIZE
11964#if CALC_EXACT_MALLOC_SIZE
11965 " %15"PRIuSIZE
11966#endif
11967 " %9"PRIuSIZE" %17.12f %17.12f %17.12f %10"PRIuSIZE" %10"PRIuSIZE" %10"PRIuSIZE" %10"PRIuSIZE
11968#if RGENGC_PROFILE
11969 "%10"PRIuSIZE" %10"PRIuSIZE" %10"PRIuSIZE
11970#endif
11971#if GC_PROFILE_DETAIL_MEMORY
11972 "%11ld %8ld %8ld"
11973#endif
11974
11975 "\n",
11976 i+1,
11977 gc_profile_dump_major_reason(record->flags, reason_str),
11978 (record->flags & GPR_FLAG_HAVE_FINALIZE) ? 'F' : '.',
11979 (record->flags & GPR_FLAG_NEWOBJ) ? "NEWOBJ" :
11980 (record->flags & GPR_FLAG_MALLOC) ? "MALLOC" :
11981 (record->flags & GPR_FLAG_METHOD) ? "METHOD" :
11982 (record->flags & GPR_FLAG_CAPI) ? "CAPI__" : "??????",
11983 (record->flags & GPR_FLAG_STRESS) ? '!' : ' ',
11984 record->allocate_increase, record->allocate_limit,
11985#if CALC_EXACT_MALLOC_SIZE
11986 record->allocated_size,
11987#endif
11988 record->heap_use_pages,
11989 record->gc_mark_time*1000,
11990 record->gc_sweep_time*1000,
11991 record->prepare_time*1000,
11992
11993 record->heap_live_objects,
11994 record->heap_free_objects,
11995 record->removing_objects,
11996 record->empty_objects
11997#if RGENGC_PROFILE
11998 ,
11999 record->old_objects,
12000 record->remembered_normal_objects,
12001 record->remembered_shady_objects
12002#endif
12003#if GC_PROFILE_DETAIL_MEMORY
12004 ,
12005 record->maxrss / 1024,
12006 record->minflt,
12007 record->majflt
12008#endif
12009
12010 ));
12011 }
12012#endif
12013 }
12014}
12015
12016/*
12017 * call-seq:
12018 * GC::Profiler.result -> String
12019 *
12020 * Returns a profile data report such as:
12021 *
12022 * GC 1 invokes.
12023 * Index Invoke Time(sec) Use Size(byte) Total Size(byte) Total Object GC time(ms)
12024 * 1 0.012 159240 212940 10647 0.00000000000001530000
12025 */
12026
12027static VALUE
12028gc_profile_result(VALUE _)
12029{
12030 VALUE str = rb_str_buf_new(0);
12031 gc_profile_dump_on(str, rb_str_buf_append);
12032 return str;
12033}
12034
12035/*
12036 * call-seq:
12037 * GC::Profiler.report
12038 * GC::Profiler.report(io)
12039 *
12040 * Writes the GC::Profiler.result to <tt>$stdout</tt> or the given IO object.
12041 *
12042 */
12043
12044static VALUE
12045gc_profile_report(int argc, VALUE *argv, VALUE self)
12046{
12047 VALUE out;
12048
12049 out = (!rb_check_arity(argc, 0, 1) ? rb_stdout : argv[0]);
12050 gc_profile_dump_on(out, rb_io_write);
12051
12052 return Qnil;
12053}
12054
12055/*
12056 * call-seq:
12057 * GC::Profiler.total_time -> float
12058 *
12059 * The total time used for garbage collection in seconds
12060 */
12061
12062static VALUE
12063gc_profile_total_time(VALUE self)
12064{
12065 double time = 0;
12066 rb_objspace_t *objspace = rb_gc_get_objspace();
12067
12068 if (objspace->profile.run && gc_profile_record_count(objspace) > 0) {
12069 size_t i;
12070 size_t count = gc_profile_record_count(objspace);
12071
12072 for (i = 0; i < count; i++) {
12073 time += objspace->profile.records[gc_profile_record_index(objspace, i)].gc_time;
12074 }
12075 }
12076 return DBL2NUM(time);
12077}
12078
12079/*
12080 * call-seq:
12081 * GC::Profiler.enabled? -> true or false
12082 *
12083 * The current status of \GC profile mode.
12084 */
12085
12086static VALUE
12087gc_profile_enable_get(VALUE self)
12088{
12089 rb_objspace_t *objspace = rb_gc_get_objspace();
12090 return objspace->profile.run ? Qtrue : Qfalse;
12091}
12092
12093/*
12094 * call-seq:
12095 * GC::Profiler.enable -> nil
12096 *
12097 * Starts the \GC profiler.
12098 *
12099 */
12100
12101static VALUE
12102gc_profile_enable(VALUE _)
12103{
12104 rb_objspace_t *objspace = rb_gc_get_objspace();
12105 objspace->profile.run = TRUE;
12106 objspace->profile.current_record = 0;
12107 return Qnil;
12108}
12109
12110/*
12111 * call-seq:
12112 * GC::Profiler.disable -> nil
12113 *
12114 * Stops the \GC profiler.
12115 *
12116 */
12117
12118static VALUE
12119gc_profile_disable(VALUE _)
12120{
12121 rb_objspace_t *objspace = rb_gc_get_objspace();
12122
12123 objspace->profile.run = FALSE;
12124 objspace->profile.current_record = 0;
12125 return Qnil;
12126}
12127
12128static void
12129rb_gc_verify_internal_consistency(void)
12130{
12131 gc_verify_internal_consistency(rb_gc_get_objspace());
12132}
12133
12134/*
12135 * call-seq:
12136 * GC.verify_internal_consistency -> nil
12137 *
12138 * Verifies internal consistency of the GC.
12139 * This method should only be used for debugging.
12140 *
12141 * This method is only expected to work on CRuby.
12142 */
12143static VALUE
12144gc_verify_internal_consistency_m(VALUE dummy)
12145{
12146 rb_gc_verify_internal_consistency();
12147 return Qnil;
12148}
12149
12150#if GC_CAN_COMPILE_COMPACTION
12151/*
12152 * call-seq:
12153 * GC.auto_compact = flag
12154 *
12155 * Updates automatic compaction mode.
12156 *
12157 * When enabled, the compactor will execute on every major collection.
12158 *
12159 * Enabling compaction will degrade performance on major collections.
12160 */
12161static VALUE
12162gc_set_auto_compact(VALUE _, VALUE v)
12163{
12164 GC_ASSERT(GC_COMPACTION_SUPPORTED);
12165
12166 ruby_enable_autocompact = RTEST(v);
12167
12168#if RGENGC_CHECK_MODE
12169 ruby_autocompact_compare_func = NULL;
12170
12171 if (SYMBOL_P(v)) {
12172 ID id = RB_SYM2ID(v);
12173 if (id == rb_intern("empty")) {
12174 ruby_autocompact_compare_func = compare_free_slots;
12175 }
12176 }
12177#endif
12178
12179 return v;
12180}
12181#else
12182# define gc_set_auto_compact rb_f_notimplement
12183#endif
12184
12185#if GC_CAN_COMPILE_COMPACTION
12186/*
12187 * call-seq:
12188 * GC.auto_compact -> true or false
12189 *
12190 * Returns whether or not automatic compaction has been enabled.
12191 */
12192static VALUE
12193gc_get_auto_compact(VALUE _)
12194{
12195 return ruby_enable_autocompact ? Qtrue : Qfalse;
12196}
12197#else
12198# define gc_get_auto_compact rb_f_notimplement
12199#endif
12200
12201#if GC_CAN_COMPILE_COMPACTION
12202/*
12203 * call-seq:
12204 * GC.latest_compact_info -> hash
12205 *
12206 * Returns information about object moved in the most recent \GC compaction.
12207 *
12208 * The returned +hash+ contains the following keys:
12209 *
12210 * [considered]
12211 * Hash containing the type of the object as the key and the number of
12212 * objects of that type that were considered for movement.
12213 * [moved]
12214 * Hash containing the type of the object as the key and the number of
12215 * objects of that type that were actually moved.
12216 * [moved_up]
12217 * Hash containing the type of the object as the key and the number of
12218 * objects of that type that were increased in size.
12219 * [moved_down]
12220 * Hash containing the type of the object as the key and the number of
12221 * objects of that type that were decreased in size.
12222 *
12223 * Some objects can't be moved (due to pinning) so these numbers can be used to
12224 * calculate compaction efficiency.
12225 */
12226static VALUE
12227gc_compact_stats(VALUE self)
12228{
12229 rb_objspace_t *objspace = rb_gc_get_objspace();
12230 VALUE h = rb_hash_new();
12231 VALUE considered = rb_hash_new();
12232 VALUE moved = rb_hash_new();
12233 VALUE moved_up = rb_hash_new();
12234 VALUE moved_down = rb_hash_new();
12235
12236 for (size_t i = 0; i < T_MASK; i++) {
12237 if (objspace->rcompactor.considered_count_table[i]) {
12238 rb_hash_aset(considered, type_sym(i), SIZET2NUM(objspace->rcompactor.considered_count_table[i]));
12239 }
12240
12241 if (objspace->rcompactor.moved_count_table[i]) {
12242 rb_hash_aset(moved, type_sym(i), SIZET2NUM(objspace->rcompactor.moved_count_table[i]));
12243 }
12244
12245 if (objspace->rcompactor.moved_up_count_table[i]) {
12246 rb_hash_aset(moved_up, type_sym(i), SIZET2NUM(objspace->rcompactor.moved_up_count_table[i]));
12247 }
12248
12249 if (objspace->rcompactor.moved_down_count_table[i]) {
12250 rb_hash_aset(moved_down, type_sym(i), SIZET2NUM(objspace->rcompactor.moved_down_count_table[i]));
12251 }
12252 }
12253
12254 rb_hash_aset(h, ID2SYM(rb_intern("considered")), considered);
12255 rb_hash_aset(h, ID2SYM(rb_intern("moved")), moved);
12256 rb_hash_aset(h, ID2SYM(rb_intern("moved_up")), moved_up);
12257 rb_hash_aset(h, ID2SYM(rb_intern("moved_down")), moved_down);
12258
12259 return h;
12260}
12261#else
12262# define gc_compact_stats rb_f_notimplement
12263#endif
12264
12265#if GC_CAN_COMPILE_COMPACTION
12266/*
12267 * call-seq:
12268 * GC.compact -> hash
12269 *
12270 * This function compacts objects together in Ruby's heap. It eliminates
12271 * unused space (or fragmentation) in the heap by moving objects in to that
12272 * unused space.
12273 *
12274 * The returned +hash+ contains statistics about the objects that were moved;
12275 * see GC.latest_compact_info.
12276 *
12277 * This method is only expected to work on CRuby.
12278 *
12279 * To test whether \GC compaction is supported, use the idiom:
12280 *
12281 * GC.respond_to?(:compact)
12282 */
12283static VALUE
12284gc_compact(VALUE self)
12285{
12286 rb_objspace_t *objspace = rb_gc_get_objspace();
12287 int full_marking_p = gc_config_full_mark_val;
12288 gc_config_full_mark_set(TRUE);
12289
12290 /* Run GC with compaction enabled */
12291 rb_gc_impl_start(rb_gc_get_objspace(), true, true, true, true);
12292 gc_config_full_mark_set(full_marking_p);
12293
12294 return gc_compact_stats(self);
12295}
12296#else
12297# define gc_compact rb_f_notimplement
12298#endif
12299
12300#if GC_CAN_COMPILE_COMPACTION
12301struct desired_compaction_pages_i_data {
12303 size_t required_slots[HEAP_COUNT];
12304};
12305
12306static int
12307desired_compaction_pages_i(struct heap_page *page, void *data)
12308{
12309 struct desired_compaction_pages_i_data *tdata = data;
12310 rb_objspace_t *objspace = tdata->objspace;
12311 VALUE vstart = (VALUE)page->start;
12312 VALUE vend = vstart + (VALUE)(page->total_slots * page->heap->slot_size);
12313
12314
12315 for (VALUE v = vstart; v != vend; v += page->heap->slot_size) {
12316 asan_unpoisoning_object(v) {
12317 /* skip T_NONEs; they won't be moved */
12318 if (BUILTIN_TYPE(v) != T_NONE) {
12319 rb_heap_t *dest_pool = gc_compact_destination_pool(objspace, page->heap, v);
12320 size_t dest_pool_idx = dest_pool - heaps;
12321 tdata->required_slots[dest_pool_idx]++;
12322 }
12323 }
12324 }
12325
12326 return 0;
12327}
12328
12329/* call-seq:
12330 * GC.verify_compaction_references(toward: nil, double_heap: false) -> hash
12331 *
12332 * Verify compaction reference consistency.
12333 *
12334 * This method is implementation specific. During compaction, objects that
12335 * were moved are replaced with T_MOVED objects. No object should have a
12336 * reference to a T_MOVED object after compaction.
12337 *
12338 * This function expands the heap to ensure room to move all objects,
12339 * compacts the heap to make sure everything moves, updates all references,
12340 * then performs a full \GC. If any object contains a reference to a T_MOVED
12341 * object, that object should be pushed on the mark stack, and will
12342 * make a SEGV.
12343 */
12344static VALUE
12345gc_verify_compaction_references(int argc, VALUE* argv, VALUE self)
12346{
12347 static ID keywords[3] = {0};
12348 if (!keywords[0]) {
12349 keywords[0] = rb_intern("toward");
12350 keywords[1] = rb_intern("double_heap");
12351 keywords[2] = rb_intern("expand_heap");
12352 }
12353
12354 VALUE options;
12355 rb_scan_args_kw(rb_keyword_given_p(), argc, argv, ":", &options);
12356
12357 VALUE arguments[3] = { Qnil, Qfalse, Qfalse };
12358 int kwarg_count = rb_get_kwargs(options, keywords, 0, 3, arguments);
12359 bool toward_empty = kwarg_count > 0 && SYMBOL_P(arguments[0]) && SYM2ID(arguments[0]) == rb_intern("empty");
12360 bool expand_heap = (kwarg_count > 1 && RTEST(arguments[1])) || (kwarg_count > 2 && RTEST(arguments[2]));
12361
12362 rb_objspace_t *objspace = rb_gc_get_objspace();
12363
12364 /* This verification machinery (heap expansion, toward_empty page ordering, the
12365 * moved-reference walk) is built for a single objspace, so with several demote it
12366 * to a plain full GC. Plain GC.compact does compact them via the global GC. */
12367 if (!rb_gc_single_objspace_p()) {
12368 rb_gc_impl_start(objspace, true, true, true, false);
12369 return gc_compact_stats(self);
12370 }
12371
12372 /* Clear the heap. */
12373 rb_gc_impl_start(objspace, true, true, true, false);
12374
12375 unsigned int lev = RB_GC_VM_LOCK();
12376 {
12377 gc_rest(objspace);
12378
12379 /* if both double_heap and expand_heap are set, expand_heap takes precedence */
12380 if (expand_heap) {
12381 struct desired_compaction_pages_i_data desired_compaction = {
12382 .objspace = objspace,
12383 .required_slots = {0},
12384 };
12385 /* Work out how many objects want to be in each size pool, taking account of moves */
12386 objspace_each_pages(objspace, desired_compaction_pages_i, &desired_compaction, TRUE);
12387
12388 /* Find out which pool has the most pages */
12389 size_t max_existing_pages = 0;
12390 for (int i = 0; i < HEAP_COUNT; i++) {
12391 rb_heap_t *heap = &heaps[i];
12392 max_existing_pages = MAX(max_existing_pages, heap->total_pages);
12393 }
12394
12395 /* Add pages to each size pool so that compaction is guaranteed to move every object */
12396 for (int i = 0; i < HEAP_COUNT; i++) {
12397 rb_heap_t *heap = &heaps[i];
12398
12399 size_t pages_to_add = 0;
12400 /*
12401 * Step 1: Make sure every pool has the same number of pages, by adding empty pages
12402 * to smaller pools. This is required to make sure the compact cursor can advance
12403 * through all of the pools in `gc_sweep_compact` without hitting the "sweep &
12404 * compact cursors met" condition on some pools before fully compacting others
12405 */
12406 pages_to_add += max_existing_pages - heap->total_pages;
12407 /*
12408 * Step 2: Now add additional free pages to each size pool sufficient to hold all objects
12409 * that want to be in that size pool, whether moved into it or moved within it
12410 */
12411 objspace->heap_pages.allocatable_bytes = desired_compaction.required_slots[i] * heap->slot_size;
12412 while (objspace->heap_pages.allocatable_bytes > 0) {
12413 heap_page_allocate_and_initialize(objspace, heap);
12414 }
12415 /*
12416 * Step 3: Add two more pages so that the compact & sweep cursors will meet _after_ all objects
12417 * have been moved, and not on the last iteration of the `gc_sweep_compact` loop
12418 */
12419 pages_to_add += 2;
12420
12421 for (; pages_to_add > 0; pages_to_add--) {
12422 heap_page_allocate_and_initialize_force(objspace, heap);
12423 }
12424 }
12425 }
12426
12427 if (toward_empty) {
12428 objspace->rcompactor.compare_func = compare_free_slots;
12429 }
12430 }
12431 RB_GC_VM_UNLOCK(lev);
12432
12433 rb_gc_impl_start(rb_gc_get_objspace(), true, true, true, true);
12434
12435 rb_objspace_reachable_objects_from_root(root_obj_check_moved_i, objspace);
12436 objspace_each_objects(objspace, heap_check_moved_i, objspace, TRUE);
12437
12438 objspace->rcompactor.compare_func = NULL;
12439
12440 return gc_compact_stats(self);
12441}
12442#else
12443# define gc_verify_compaction_references rb_f_notimplement
12444#endif
12445
12446void
12447rb_gc_impl_objspace_free(void *objspace_ptr)
12448{
12449 rb_objspace_t *objspace = objspace_ptr;
12450
12451 if (is_lazy_sweeping(objspace))
12452 rb_bug("lazy sweeping underway when freeing object space");
12453
12454 free(objspace->profile.records);
12455 objspace->profile.records = NULL;
12456
12457 for (size_t i = 0; i < rb_darray_size(objspace->heap_pages.sorted); i++) {
12458 heap_page_free(objspace, rb_darray_get(objspace->heap_pages.sorted, i));
12459 }
12460 rb_darray_free_without_gc(objspace->heap_pages.sorted);
12461 heap_pages_lomem = 0;
12462 heap_pages_himem = 0;
12463
12464 for (int i = 0; i < HEAP_COUNT; i++) {
12465 rb_heap_t *heap = &heaps[i];
12466 heap->total_pages = 0;
12467 heap->total_slots = 0;
12468 }
12469
12470 free_stack_chunks(&objspace->mark_stack);
12471 mark_stack_free_cache(&objspace->mark_stack);
12472
12473 rb_darray_free_without_gc(objspace->weak_references);
12474
12475#ifdef MALLOC_COUNTERS_NEED_LOCK
12476 rb_native_mutex_destroy(&objspace->malloc_counters.lock);
12477#endif
12478
12479 free(objspace);
12480}
12481
12482#if MALLOC_ALLOCATED_SIZE
12483/*
12484 * call-seq:
12485 * GC.malloc_allocated_size -> Integer
12486 *
12487 * Returns the size of memory allocated by malloc().
12488 *
12489 * Only available if ruby was built with +CALC_EXACT_MALLOC_SIZE+.
12490 */
12491
12492static VALUE
12493gc_malloc_allocated_size(VALUE self)
12494{
12495 rb_objspace_t *objspace = (rb_objspace_t *)rb_gc_get_objspace();
12496 return ULL2NUM(objspace->malloc_params.allocated_size);
12497}
12498
12499/*
12500 * call-seq:
12501 * GC.malloc_allocations -> Integer
12502 *
12503 * Returns the number of malloc() allocations.
12504 *
12505 * Only available if ruby was built with +CALC_EXACT_MALLOC_SIZE+.
12506 */
12507
12508static VALUE
12509gc_malloc_allocations(VALUE self)
12510{
12511 rb_objspace_t *objspace = (rb_objspace_t *)rb_gc_get_objspace();
12512 return ULL2NUM(objspace->malloc_params.allocations);
12513}
12514#endif
12515
12516void
12517rb_gc_impl_before_fork(void *objspace_ptr)
12518{
12519 rb_objspace_t *objspace = objspace_ptr;
12520
12521 objspace->fork_vm_lock_lev = RB_GC_VM_LOCK();
12522 rb_gc_vm_barrier();
12523}
12524
12525void
12526rb_gc_impl_after_fork(void *objspace_ptr, rb_pid_t pid)
12527{
12528 rb_objspace_t *objspace = objspace_ptr;
12529
12530 RB_GC_VM_UNLOCK(objspace->fork_vm_lock_lev);
12531 objspace->fork_vm_lock_lev = 0;
12532
12533 if (pid == 0) { /* child process */
12534 heap_alloc_state_clear(objspace);
12535 /* The forking Ractor becomes the child process's main Ractor. */
12536 global_objspace->main_objspace = objspace;
12537 page_pool_lock_initialize(&rb_global_objspace_instance.page_pool.lock);
12538 }
12539}
12540
12541VALUE rb_ident_hash_new_capa(long size);
12542
12543#if GC_DEBUG_STRESS_TO_CLASS
12544/*
12545 * call-seq:
12546 * GC.add_stress_to_class(class[, ...])
12547 *
12548 * Raises NoMemoryError when allocating an instance of the given classes.
12549 *
12550 */
12551static VALUE
12552rb_gcdebug_add_stress_to_class(int argc, VALUE *argv, VALUE self)
12553{
12554 rb_objspace_t *objspace = rb_gc_get_objspace();
12555
12556 if (!stress_to_class) {
12557 set_stress_to_class(rb_ident_hash_new_capa(argc));
12558 }
12559
12560 for (int i = 0; i < argc; i++) {
12561 VALUE klass = argv[i];
12562 rb_hash_aset(stress_to_class, klass, Qtrue);
12563 }
12564
12565 return self;
12566}
12567
12568/*
12569 * call-seq:
12570 * GC.remove_stress_to_class(class[, ...])
12571 *
12572 * No longer raises NoMemoryError when allocating an instance of the
12573 * given classes.
12574 *
12575 */
12576static VALUE
12577rb_gcdebug_remove_stress_to_class(int argc, VALUE *argv, VALUE self)
12578{
12579 rb_objspace_t *objspace = rb_gc_get_objspace();
12580
12581 if (stress_to_class) {
12582 for (int i = 0; i < argc; ++i) {
12583 rb_hash_delete(stress_to_class, argv[i]);
12584 }
12585
12586 if (rb_hash_size(stress_to_class) == 0) {
12587 stress_to_class = 0;
12588 }
12589 }
12590
12591 return Qnil;
12592}
12593#endif
12594
12595void *
12596rb_gc_impl_objspace_alloc(void)
12597{
12598 global_objspace_init();
12599
12600 rb_objspace_t *objspace = calloc1(sizeof(rb_objspace_t));
12601
12602 return objspace;
12603}
12604
12605void
12606rb_gc_impl_objspace_init(void *objspace_ptr)
12607{
12608 rb_objspace_t *objspace = objspace_ptr;
12609
12610 gc_config_full_mark_set(TRUE);
12611
12612 objspace->flags.measure_gc = true;
12613 malloc_limit = gc_params.malloc_limit_min;
12614 objspace->shareable_objects_limit = SHAREABLE_OBJECTS_LIMIT_MIN;
12615#ifdef MALLOC_COUNTERS_NEED_LOCK
12616 rb_native_mutex_initialize(&objspace->malloc_counters.lock);
12617#endif
12618 /* Shared by every objspace. preregister deduplicates on (func, data). */
12619 objspace->finalize_deferred_pjob = rb_postponed_job_preregister(0, gc_finalize_deferred, NULL);
12620 if (objspace->finalize_deferred_pjob == POSTPONED_JOB_HANDLE_INVALID) {
12621 rb_bug("Could not preregister postponed job for GC");
12622 }
12623
12624 /* A standard RVALUE (RBasic + embedded VALUEs + debug overhead) must fit
12625 * in at least one pool. In debug builds RVALUE_OVERHEAD can push this
12626 * beyond the 48-byte pool into the 64-byte pool, which is fine. */
12627 GC_ASSERT(rb_gc_impl_size_allocatable_p(sizeof(struct RBasic) + sizeof(VALUE[RBIMPL_RVALUE_EMBED_LEN_MAX])));
12628
12629 for (int i = 0; i < HEAP_COUNT; i++) {
12630 rb_heap_t *heap = &heaps[i];
12631
12632 heap->slot_size = pool_slot_sizes[i];
12633
12634 ccan_list_head_init(&heap->pages);
12635 }
12636
12637 if (global_objspace->main_objspace == NULL) {
12638 /* Single-threaded at boot and the first objspace is main's: compute process-wide
12639 * constants once here. A later objspace_init rewriting them, even with equal
12640 * values, would race other threads' lock-free reads. */
12641 global_objspace->main_objspace = objspace;
12642
12643 init_size_to_heap_idx();
12644
12645#if defined(INIT_HEAP_PAGE_ALLOC_USE_MMAP)
12646 /* Need to determine if we can use mmap at runtime. */
12647 heap_page_alloc_use_mmap = INIT_HEAP_PAGE_ALLOC_USE_MMAP;
12648#endif
12649 gc_params.heap_init_bytes = GC_HEAP_INIT_BYTES;
12650 }
12651
12652 rb_darray_make_without_gc(&objspace->heap_pages.sorted, 0);
12653 rb_darray_make_without_gc(&objspace->weak_references, 0);
12654
12655#if RGENGC_ESTIMATE_OLDMALLOC
12656 objspace->rgengc.oldmalloc_increase_limit = gc_params.oldmalloc_limit_min;
12657#endif
12658
12659 init_mark_stack(&objspace->mark_stack);
12660
12661 objspace->profile.invoke_time = getrusage_time();
12662 objspace->profile.invoke_wall_time = rb_hrtime_now();
12663 objspace->profile.max_records = GC_PROFILE_RECORD_DEFAULT_MAX_RECORDS;
12664 finalizer_table = st_init_numtable();
12665}
12666
12667void
12668rb_gc_impl_init(void)
12669{
12670 VALUE gc_constants = rb_hash_new();
12671 rb_hash_aset(gc_constants, ID2SYM(rb_intern("DEBUG")), GC_DEBUG ? Qtrue : Qfalse);
12672 /* Minimum slot size that fits a standard RVALUE */
12673 size_t rvalue_pool = 0;
12674 for (size_t i = 0; i < HEAP_COUNT; i++) {
12675 if (pool_slot_sizes[i] >= RVALUE_SLOT_SIZE) { rvalue_pool = pool_slot_sizes[i]; break; }
12676 }
12677 rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVALUE_SIZE")), SIZET2NUM(rvalue_pool - RVALUE_OVERHEAD));
12678 rb_hash_aset(gc_constants, ID2SYM(rb_intern("RBASIC_SIZE")), SIZET2NUM(sizeof(struct RBasic)));
12679 rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVALUE_OVERHEAD")), SIZET2NUM(RVALUE_OVERHEAD));
12680 rb_hash_aset(gc_constants, ID2SYM(rb_intern("HEAP_PAGE_BITMAP_SIZE")), SIZET2NUM(HEAP_PAGE_BITMAP_SIZE));
12681 rb_hash_aset(gc_constants, ID2SYM(rb_intern("HEAP_PAGE_SIZE")), SIZET2NUM(HEAP_PAGE_SIZE));
12682 rb_hash_aset(gc_constants, ID2SYM(rb_intern("HEAP_COUNT")), LONG2FIX(HEAP_COUNT));
12683 rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVARGC_MAX_ALLOCATE_SIZE")), SIZET2NUM(rb_gc_impl_max_allocation_size()));
12684 rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVALUE_OLD_AGE")), LONG2FIX(RVALUE_OLD_AGE));
12685 if (RB_BUG_INSTEAD_OF_RB_MEMERROR+0) {
12686 rb_hash_aset(gc_constants, ID2SYM(rb_intern("RB_BUG_INSTEAD_OF_RB_MEMERROR")), Qtrue);
12687 }
12688 OBJ_FREEZE(gc_constants);
12689 /* Internal constants in the garbage collector. */
12690 rb_define_const(rb_mGC, "INTERNAL_CONSTANTS", gc_constants);
12691
12692 if (GC_COMPACTION_SUPPORTED) {
12693 rb_define_singleton_method(rb_mGC, "compact", gc_compact, 0);
12694 rb_define_singleton_method(rb_mGC, "auto_compact", gc_get_auto_compact, 0);
12695 rb_define_singleton_method(rb_mGC, "auto_compact=", gc_set_auto_compact, 1);
12696 rb_define_singleton_method(rb_mGC, "latest_compact_info", gc_compact_stats, 0);
12697 rb_define_singleton_method(rb_mGC, "verify_compaction_references", gc_verify_compaction_references, -1);
12698 }
12699 else {
12703 rb_define_singleton_method(rb_mGC, "latest_compact_info", rb_f_notimplement, 0);
12704 rb_define_singleton_method(rb_mGC, "verify_compaction_references", rb_f_notimplement, -1);
12705 }
12706
12707#if GC_DEBUG_STRESS_TO_CLASS
12708 rb_define_singleton_method(rb_mGC, "add_stress_to_class", rb_gcdebug_add_stress_to_class, -1);
12709 rb_define_singleton_method(rb_mGC, "remove_stress_to_class", rb_gcdebug_remove_stress_to_class, -1);
12710#endif
12711
12712 /* internal methods */
12713 rb_define_singleton_method(rb_mGC, "verify_internal_consistency", gc_verify_internal_consistency_m, 0);
12714
12715#if MALLOC_ALLOCATED_SIZE
12716 rb_define_singleton_method(rb_mGC, "malloc_allocated_size", gc_malloc_allocated_size, 0);
12717 rb_define_singleton_method(rb_mGC, "malloc_allocations", gc_malloc_allocations, 0);
12718#endif
12719
12720 /* Document-class: GC::Profiler
12721 *
12722 * The GC profiler provides access to information on GC runs including time,
12723 * length and object space size.
12724 *
12725 * Example:
12726 *
12727 * GC::Profiler.enable
12728 *
12729 * require 'rdoc/rdoc'
12730 *
12731 * GC::Profiler.report
12732 *
12733 * pp GC::Profiler.raw_data
12734 *
12735 * GC::Profiler.disable
12736 *
12737 * GC::Profiler.raw_data returns one Hash per GC run, including CPU time
12738 * fields such as +:GC_TIME+ and wall-clock fields such as +:GC_WALL_TIME+,
12739 * +:GC_PAUSE_TIME+, +:GC_STOP_TIME+, and +:GC_STW_TIME+. +:GC_WALL_TIME+
12740 * is the wall-clock counterpart to +:GC_TIME+, while +:GC_PAUSE_TIME+
12741 * measures how long user execution was blocked by the GC entry.
12742 *
12743 * See also GC.count, GC.malloc_allocated_size and GC.malloc_allocations
12744 */
12745 VALUE rb_mProfiler = rb_define_module_under(rb_mGC, "Profiler");
12746 rb_define_singleton_method(rb_mProfiler, "enabled?", gc_profile_enable_get, 0);
12747 rb_define_singleton_method(rb_mProfiler, "enable", gc_profile_enable, 0);
12748 rb_define_singleton_method(rb_mProfiler, "raw_data", gc_profile_record_get, -1);
12749 rb_define_singleton_method(rb_mProfiler, "disable", gc_profile_disable, 0);
12750 rb_define_singleton_method(rb_mProfiler, "clear", gc_profile_clear, 0);
12751 rb_define_singleton_method(rb_mProfiler, "configure", gc_profile_configure, -1);
12752 rb_define_singleton_method(rb_mProfiler, "result", gc_profile_result, 0);
12753 rb_define_singleton_method(rb_mProfiler, "report", gc_profile_report, -1);
12754 rb_define_singleton_method(rb_mProfiler, "total_time", gc_profile_total_time, 0);
12755
12756 {
12757 VALUE opts;
12758 /* \GC build options */
12759 rb_define_const(rb_mGC, "OPTS", opts = rb_ary_new());
12760#define OPT(o) if (o) rb_ary_push(opts, rb_interned_str(#o, sizeof(#o) - 1))
12761 OPT(GC_DEBUG);
12762 OPT(USE_RGENGC);
12763 OPT(RGENGC_DEBUG);
12764 OPT(RGENGC_CHECK_MODE);
12765 OPT(RGENGC_PROFILE);
12766 OPT(RGENGC_ESTIMATE_OLDMALLOC);
12767 OPT(GC_PROFILE_MORE_DETAIL);
12768 OPT(GC_ENABLE_LAZY_SWEEP);
12769 OPT(CALC_EXACT_MALLOC_SIZE);
12770 OPT(MALLOC_ALLOCATED_SIZE);
12771 OPT(MALLOC_ALLOCATED_SIZE_CHECK);
12772 OPT(GC_PROFILE_DETAIL_MEMORY);
12773 OPT(GC_COMPACTION_SUPPORTED);
12774#undef OPT
12775 OBJ_FREEZE(opts);
12776 }
12777}
#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:1934
rb_postponed_job_handle_t rb_postponed_job_preregister(unsigned int flags, rb_postponed_job_func_t func, void *data)
Pre-registers a func in Ruby's postponed job preregistration table, returning an opaque handle which ...
Definition vm_trace.c:1900
#define 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:430
static VALUE RB_FL_TEST_RAW(VALUE obj, VALUE flags)
This is an implementation detail of RB_FL_TEST().
Definition fl_type.h:404
static void RB_FL_SET_RAW(VALUE obj, VALUE flags)
This is an implementation detail of RB_FL_SET().
Definition fl_type.h:541
static void RB_FL_UNSET_RAW(VALUE obj, VALUE flags)
This is an implementation detail of RB_FL_UNSET().
Definition fl_type.h:601
@ 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:3231
int rb_keyword_given_p(void)
Determines if the current method is given a keyword argument.
Definition eval.c:1046
int rb_get_kwargs(VALUE keyword_hash, const ID *table, int required, int optional, VALUE *values)
Keyword argument deconstructor.
Definition class.c:3007
#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:1461
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:436
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:571
#define USE_RGENGC
Definition gc.h:441
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:3864
VALUE rb_str_buf_new(long capa)
Allocates a "string buffer".
Definition string.c:1755
#define rb_str_new_cstr(str)
Identical to rb_str_new, except it assumes the passed pointer is a pointer to a C string.
Definition string.h:1515
const char * rb_sourcefile(void)
Resembles __FILE__.
Definition vm.c:2144
VALUE rb_f_notimplement(int argc, const VALUE *argv, VALUE obj, VALUE marker)
Raises rb_eNotImpError.
Definition vm_method.c:909
int rb_sourceline(void)
Resembles __LINE__.
Definition vm.c:2158
#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:2281
#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 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:6073
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
Definition gc_impl.h:34
Definition st.h:79
void rb_native_mutex_lock(rb_nativethread_lock_t *lock)
Just another name of rb_nativethread_lock_lock.
void rb_native_mutex_initialize(rb_nativethread_lock_t *lock)
Just another name of rb_nativethread_lock_initialize.
void rb_native_mutex_unlock(rb_nativethread_lock_t *lock)
Just another name of rb_nativethread_lock_unlock.
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