@@ -541,13 +541,15 @@ typedef struct rb_objspace {
541541 unsigned int during_reference_updating : 1 ;
542542 unsigned int gc_stressful : 1 ;
543543 unsigned int has_newobj_hook : 1 ;
544- unsigned int during_minor_gc : 1 ;
545544 unsigned int during_incremental_marking : 1 ;
546- unsigned int during_lazy_sweeping : 1 ;
547-
548545
549546 unsigned int measure_gc : 1 ;
550547 } flags ;
548+ // This can't be a bitfield because it's accessed in garbage_object_p() from the sweep thread
549+ // while the ruby GC thread could be running and changing other bitfields.
550+ bool during_lazy_sweeping ;
551+ // This one too, it's accessed in debug_free_check
552+ bool during_minor_gc ;
551553
552554 rb_event_flag_t hook_events ;
553555
@@ -563,10 +565,10 @@ typedef struct rb_objspace {
563565 bool sweep_thread_sweep_exited ;
564566 bool sweep_thread_waiting_request ;
565567 bool sweep_thread_sweeping ;
568+ rb_atomic_t use_background_sweep_thread ;
566569 bool background_sweep_mode ;
567570 bool background_sweep_abort ;
568571 bool background_sweep_restart_heaps ;
569- bool use_background_sweep_thread ;
570572 bool sweep_rest ;
571573 unsigned int heaps_done_background_sweep ;
572574
@@ -1349,12 +1351,12 @@ total_final_slots_count(rb_objspace_t *objspace)
13491351
13501352#define is_marking (objspace ) (gc_mode(objspace) == gc_mode_marking)
13511353#define is_sweeping (objspace ) (gc_mode(objspace) == gc_mode_sweeping)
1352- #define is_full_marking (objspace ) ((objspace)->flags. during_minor_gc == FALSE)
1354+ #define is_full_marking (objspace ) ((objspace)->during_minor_gc == FALSE)
13531355#define is_incremental_marking (objspace ) ((objspace)->flags.during_incremental_marking != FALSE)
13541356#define will_be_incremental_marking (objspace ) ((objspace)->rgengc.need_major_gc != GPR_FLAG_NONE)
13551357#define GC_INCREMENTAL_SWEEP_SLOT_COUNT 2048
13561358#define GC_INCREMENTAL_SWEEP_POOL_SLOT_COUNT 1024
1357- #define is_lazy_sweeping (objspace ) ((objspace)->flags. during_lazy_sweeping != FALSE)
1359+ #define is_lazy_sweeping (objspace ) ((objspace)->during_lazy_sweeping != FALSE)
13581360/* In lazy sweeping or the previous incremental marking finished and did not yield a free page. */
13591361#define needs_continue_sweeping (objspace , heap ) \
13601362 ((heap)->free_pages == NULL && is_lazy_sweeping(objspace))
@@ -1557,6 +1559,14 @@ RVALUE_MARKED(rb_objspace_t *objspace, VALUE obj)
15571559 return RVALUE_MARKED_BITMAP (obj ) != 0 ;
15581560}
15591561
1562+ static inline int
1563+ RVALUE_MARKED_ATOMIC (rb_objspace_t * objspace , VALUE obj )
1564+ {
1565+ bits_t * bits = GET_HEAP_MARK_BITS (obj );
1566+ bits_t word = __atomic_load_n (& bits [BITMAP_INDEX (obj )], __ATOMIC_SEQ_CST );
1567+ return (word & BITMAP_BIT (obj )) != 0 ;
1568+ }
1569+
15601570static inline int
15611571RVALUE_PINNED (rb_objspace_t * objspace , VALUE obj )
15621572{
@@ -1912,8 +1922,11 @@ rb_gc_impl_garbage_object_p(void *objspace_ptr, VALUE ptr)
19121922
19131923 bool dead = false;
19141924
1915- if (!objspace -> background_sweep_mode ) { // set to false/true by ruby GC thread when entering/exiting GC
1916- // psweep: not safe to read flags on object if during background sweeping
1925+ // Set to false/true by the ruby GC thread when entering/exiting GC, so shouldn't change throughout this call.
1926+ rb_atomic_t use_sweep_thread = RUBY_ATOMIC_LOAD (objspace -> use_background_sweep_thread );
1927+
1928+ if (!use_sweep_thread ) {
1929+ // It's not safe to read flags on an object if the sweep thread is running
19171930 asan_unpoisoning_object (ptr ) {
19181931 switch (BUILTIN_TYPE (ptr )) {
19191932 case T_NONE :
@@ -1932,17 +1945,19 @@ rb_gc_impl_garbage_object_p(void *objspace_ptr, VALUE ptr)
19321945 struct heap_page * page = GET_HEAP_PAGE (ptr );
19331946 bool during_lazy_sweep = is_lazy_sweeping (objspace );
19341947
1935- if (!objspace -> background_sweep_mode ) {
1936- return during_lazy_sweep && !RVALUE_MARKED (objspace , ptr ) && RUBY_ATOMIC_LOAD (page -> before_sweep );
1948+ if (!use_sweep_thread ) {
1949+ // The ruby GC thread or a user thread called us
1950+ bool marked = RVALUE_MARKED (objspace , ptr );
1951+ GC_ASSERT (marked == RVALUE_MARKED_ATOMIC (objspace , ptr ));
1952+ return during_lazy_sweep && !marked && RUBY_ATOMIC_LOAD (page -> before_sweep );
19371953 }
19381954 // we're currently lazy sweeping with the sweep thread in background mode
19391955 else if (during_lazy_sweep ) {
1940- bool is_before1 , is_before2 ;
1941- // This is technically UB because reading of mark bits is not synchronized, but I think it's fine.
1942- bool is_garbage = ((is_before1 = RUBY_ATOMIC_LOAD (page -> before_sweep )) &&
1943- !RVALUE_MARKED (objspace , ptr ) && (is_before2 = RUBY_ATOMIC_LOAD (page -> before_sweep )));
1956+ bool marked = RVALUE_MARKED_ATOMIC (objspace , ptr ); // load it atomically so it can't be re-ordered past the next atomic load
1957+ bool before_sweep = RUBY_ATOMIC_LOAD (page -> before_sweep );
1958+ bool is_garbage = !marked && before_sweep ;
19441959 if (is_garbage ) return true;
1945- if (is_before1 && is_before2 ) return false; // must be marked (before_sweep and marked)
1960+ if (marked && before_sweep ) return false;
19461961 // already swept page, just check flags
19471962 return BUILTIN_TYPE (ptr ) == T_NONE || BUILTIN_TYPE (ptr ) == T_MOVED || BUILTIN_TYPE (ptr ) == T_ZOMBIE ;
19481963 }
@@ -4402,9 +4417,7 @@ debug_free_check(rb_objspace_t *objspace, VALUE vp)
44024417 if (RVALUE_OLD_P (objspace , vp )) rb_bug ("page_sweep: %p - old while minor GC." , (void * )p );
44034418 if (RVALUE_REMEMBERED (objspace , vp )) rb_bug ("page_sweep: %p - remembered." , (void * )p );
44044419 }
4405- if (RVALUE_WB_UNPROTECTED (objspace , vp )) CLEAR_IN_BITMAP (GET_HEAP_WB_UNPROTECTED_BITS (vp ), vp );
44064420#define CHECK (x ) if (x(objspace, vp) != FALSE) rb_bug("obj_free: " #x "(%s) != FALSE", rb_obj_info(vp))
4407- CHECK (RVALUE_WB_UNPROTECTED );
44084421 CHECK (RVALUE_MARKED );
44094422 CHECK (RVALUE_MARKING );
44104423 CHECK (RVALUE_UNCOLLECTIBLE );
@@ -4944,7 +4957,7 @@ static void
49444957gc_sweep_start (rb_objspace_t * objspace )
49454958{
49464959 gc_mode_transition (objspace , gc_mode_sweeping );
4947- objspace -> flags . during_lazy_sweeping = TRUE;
4960+ objspace -> during_lazy_sweeping = TRUE;
49484961 objspace -> rincgc .pooled_slots = 0 ;
49494962
49504963// Background sweeping cannot be happening
@@ -4984,7 +4997,7 @@ gc_sweep_start(rb_objspace_t *objspace)
49844997 (objspace -> profile .latest_gc_info & GPR_FLAG_METHOD ) == 0 &&
49854998 !(objspace -> hook_events & RUBY_INTERNAL_EVENT_FREEOBJ )) {
49864999
4987- objspace -> use_background_sweep_thread = true;
5000+ RUBY_ATOMIC_SET ( objspace -> use_background_sweep_thread , true) ;
49885001 psweep_debug (-1 , "[gc] gc_sweep_start: requesting sweep thread\n" );
49895002 sweep_lock_lock (& objspace -> sweep_lock );
49905003 {
@@ -4994,7 +5007,7 @@ gc_sweep_start(rb_objspace_t *objspace)
49945007 sweep_lock_unlock (& objspace -> sweep_lock );
49955008 }
49965009 else {
4997- objspace -> use_background_sweep_thread = false;
5010+ RUBY_ATOMIC_SET ( objspace -> use_background_sweep_thread , false) ;
49985011 psweep_debug (-1 , "[gc] gc_sweep_start: not using background sweep thread\n" );
49995012 }
50005013}
@@ -5053,7 +5066,7 @@ gc_sweep_finish(rb_objspace_t *objspace)
50535066 gc_report (1 , objspace , "gc_sweep_finish\n" );
50545067 psweep_debug (-1 , "[gc] gc_sweep_finish\n" );
50555068
5056- objspace -> use_background_sweep_thread = false;
5069+ RUBY_ATOMIC_SET ( objspace -> use_background_sweep_thread , false) ;
50575070
50585071 gc_prof_set_heap_info (objspace );
50595072 heap_pages_free_unused_pages (objspace );
@@ -5095,7 +5108,7 @@ gc_sweep_finish(rb_objspace_t *objspace)
50955108
50965109 rb_gc_event_hook (0 , RUBY_INTERNAL_EVENT_GC_END_SWEEP );
50975110 gc_mode_transition (objspace , gc_mode_none );
5098- objspace -> flags . during_lazy_sweeping = FALSE;
5111+ objspace -> during_lazy_sweeping = FALSE;
50995112
51005113#if RGENGC_CHECK_MODE >= 2
51015114 gc_verify_internal_consistency (objspace );
@@ -7348,7 +7361,7 @@ gc_marks_start(rb_objspace_t *objspace, int full_mark)
73487361 "objspace->rincgc.pooled_page_num: %" PRIdSIZE ", "
73497362 "objspace->rincgc.step_slots: %" PRIdSIZE ", \n" ,
73507363 objspace -> marked_slots , objspace -> rincgc .pooled_slots , objspace -> rincgc .step_slots );
7351- objspace -> flags . during_minor_gc = FALSE;
7364+ objspace -> during_minor_gc = FALSE;
73527365 if (ruby_enable_autocompact ) {
73537366 objspace -> flags .during_compacting |= TRUE;
73547367 }
@@ -7373,7 +7386,7 @@ gc_marks_start(rb_objspace_t *objspace, int full_mark)
73737386 }
73747387 }
73757388 else {
7376- objspace -> flags . during_minor_gc = TRUE;
7389+ objspace -> during_minor_gc = TRUE;
73777390 objspace -> marked_slots =
73787391 objspace -> rgengc .old_objects + objspace -> rgengc .uncollectible_wb_unprotected_objects ; /* uncollectible objects are marked already */
73797392 objspace -> profile .minor_gc_count ++ ;
0 commit comments