From: Ivan Maidanski Date: Fri, 18 May 2018 07:58:54 +0000 (+0300) Subject: Fix missing GC_dirty calls for GC-allocated objects used internally X-Git-Tag: v7.6.8~38 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=c664ef4fe2b01f267d5f7a648c8c13200901e0ce;p=gc Fix missing GC_dirty calls for GC-allocated objects used internally (a cherry-pick of commits 73d30d2, e5fb574, 8eb6f8d, 0553eb0 from 'master') This change matters only in case of MANUAL_VDB mode. Also, GC_reachable_here calls are inserted after GC_dirty. Also, this commit actually disables multiple objects allocation in GC_generic_malloc_many if MANUAL_VDB and the incremental mode are on. * finalize.c (GC_grow_table, GC_register_disappearing_link, GC_unregister_disappearing_link_inner, GC_process_togglerefs, GC_toggleref_add, GC_move_disappearing_link_inner, GC_register_finalizer_inner, ITERATE_DL_HASHTBL_END, DELETE_DL_HASHTBL_ENTRY, GC_finalize, GC_enqueue_all_finalizers): Call GC_dirty where needed. * gcj_mlc.c [GC_GCJ_SUPPORT] (GC_gcj_malloc, GC_debug_gcj_malloc, GC_gcj_malloc_ignore_off_page): Likewise. * gcj_mlc.c (GC_gcj_malloc, GC_debug_gcj_malloc, GC_gcj_malloc_ignore_off_page): Call REACHABLE_AFTER_DIRTY(ptr_to_struct_containing_descr) after GC_dirty(op). * mallocx.c [MANUAL_VDB] (GC_generic_malloc_many): Always fall back to GC_generic_malloc (unless !GC_incremental). * mallocx.c [MANUAL_VDB] (GC_generic_malloc_many): If GC_is_heap_ptr(result) then call GC_dirty(result) and REACHABLE_AFTER_DIRTY(op) after storing op pointer. * pthread_start.c [GC_PTHREADS && !GC_WIN32_THREADS] (GC_inner_start_routine): Likewise. * pthread_support.c [GC_PTHREADS && !GC_WIN32_THREADS] (GC_new_thread, GC_delete_thread, GC_delete_gc_thread): Likewise. * specific.c [USE_CUSTOM_SPECIFIC] (GC_setspecific, GC_remove_specific_after_fork): Likewise. * typd_mlc.c (GC_make_sequence_descriptor, GC_malloc_explicitly_typed, GC_malloc_explicitly_typed_ignore_off_page, GC_calloc_explicitly_typed): Likewise. * win32_threads.c (GC_new_thread, GC_delete_gc_thread_no_free, GC_delete_thread, GC_CreateThread): Likewise. * win32_threads.c [!CYGWIN32 && !MSWINCE && !MSWIN_XBOX1] (GC_beginthreadex): Likewise. * win32_threads.c [GC_PTHREADS] (GC_pthread_create, GC_pthread_start_inner): Likewise. * typd_mlc.c (GC_make_sequence_descriptor): Call REACHABLE_AFTER_DIRTY for the stored pointers after GC_dirty(result). * typd_mlc.c (GC_malloc_explicitly_typed, GC_malloc_explicitly_typed_ignore_off_page, GC_calloc_explicitly_typed): Call REACHABLE_AFTER_DIRTY(d) after GC_dirty(op). * win32_threads.c (GC_CreateThread, GC_beginthreadex, GC_pthread_create): Call REACHABLE_AFTER_DIRTY for the stored pointer after GC_dirty. * include/gc_inline.h (GC_FAST_MALLOC_GRANS): Call GC_end_stubborn_change(my_fl) after GC_FAST_M_AO_STORE() call unless kind is GC_I_PTRFREE. * include/gc_inline.h (GC_FAST_MALLOC_GRANS): Call GC_reachable_here(next) after GC_end_stubborn_change(my_fl). * include/gc_inline.h (GC_CONS): Call GC_end_stubborn_change(result). * include/gc_inline.h (GC_CONS): Call GC_reachable_here for the stored pointers after GC_end_stubborn_change call. * include/gc_inline.h (GC_CONS): Declare l and r local variables; compute first and second expression even in case of GC_MALLOC_WORDS_KIND failure; pass l and r to GC_reachable_here (instead of first and second). * include/private/gc_priv.h (REACHABLE_AFTER_DIRTY): New macro. --- diff --git a/finalize.c b/finalize.c index 30501715..95bbda7b 100644 --- a/finalize.c +++ b/finalize.c @@ -144,12 +144,14 @@ STATIC void GC_grow_table(struct hash_chain_entry ***table, size_t new_hash = HASH3(real_key, new_size, log_new_size); p -> next = new_table[new_hash]; + GC_dirty(p); new_table[new_hash] = p; p = next; } } *log_size_ptr = log_new_size; *table = new_table; + GC_dirty(new_table); /* entire object */ } GC_API int GC_CALL GC_register_disappearing_link(void * * link) @@ -224,8 +226,10 @@ STATIC int GC_register_disappearing_link_inner( new_dl -> dl_hidden_obj = GC_HIDE_POINTER(obj); new_dl -> dl_hidden_link = GC_HIDE_POINTER(link); dl_set_next(new_dl, dl_hashtbl -> head[index]); + GC_dirty(new_dl); dl_hashtbl -> head[index] = new_dl; dl_hashtbl -> entries++; + GC_dirty(dl_hashtbl->head + index); UNLOCK(); return GC_SUCCESS; } @@ -264,8 +268,10 @@ GC_INLINE struct disappearing_link *GC_unregister_disappearing_link_inner( /* Remove found entry from the table. */ if (NULL == prev_dl) { dl_hashtbl -> head[index] = dl_next(curr_dl); + GC_dirty(dl_hashtbl->head + index); } else { dl_set_next(prev_dl, dl_next(curr_dl)); + GC_dirty(prev_dl); } dl_hashtbl -> entries--; break; @@ -307,6 +313,7 @@ GC_API int GC_CALL GC_unregister_disappearing_link(void * * link) { int i; int new_size = 0; + GC_bool needs_barrier = FALSE; GC_ASSERT(I_HOLD_LOCK()); for (i = 0; i < GC_toggleref_array_size; ++i) { @@ -324,6 +331,7 @@ GC_API int GC_CALL GC_unregister_disappearing_link(void * * link) break; case GC_TOGGLE_REF_STRONG: GC_toggleref_arr[new_size++].strong_ref = obj; + needs_barrier = TRUE; break; case GC_TOGGLE_REF_WEAK: GC_toggleref_arr[new_size++].weak_ref = GC_HIDE_POINTER(obj); @@ -338,6 +346,8 @@ GC_API int GC_CALL GC_unregister_disappearing_link(void * * link) (GC_toggleref_array_size - new_size) * sizeof(GCToggleRef)); GC_toggleref_array_size = new_size; } + if (needs_barrier) + GC_dirty(GC_toggleref_arr); /* entire object */ } STATIC void GC_normal_finalize_mark_proc(ptr_t); @@ -451,8 +461,11 @@ GC_API int GC_CALL GC_unregister_disappearing_link(void * * link) if (!ensure_toggleref_capacity(1)) { res = GC_NO_MEMORY; } else { - GC_toggleref_arr[GC_toggleref_array_size++].strong_ref = + GC_toggleref_arr[GC_toggleref_array_size].strong_ref = is_strong_ref ? obj : (void *)GC_HIDE_POINTER(obj); + if (is_strong_ref) + GC_dirty(GC_toggleref_arr + GC_toggleref_array_size); + GC_toggleref_array_size++; } } UNLOCK(); @@ -557,10 +570,13 @@ GC_API GC_await_finalize_proc GC_CALL GC_get_await_finalize_proc(void) dl_hashtbl -> head[curr_index] = dl_next(curr_dl); } else { dl_set_next(prev_dl, dl_next(curr_dl)); + GC_dirty(prev_dl); } curr_dl -> dl_hidden_link = new_hidden_link; dl_set_next(curr_dl, dl_hashtbl -> head[new_index]); dl_hashtbl -> head[new_index] = curr_dl; + GC_dirty(curr_dl); + GC_dirty(dl_hashtbl->head); /* entire object */ return GC_SUCCESS; } @@ -699,6 +715,7 @@ STATIC void GC_register_finalizer_inner(void * obj, GC_fnlz_roots.fo_head[index] = fo_next(curr_fo); } else { fo_set_next(prev_fo, fo_next(curr_fo)); + GC_dirty(prev_fo); } if (fn == 0) { GC_fo_entries--; @@ -712,14 +729,18 @@ STATIC void GC_register_finalizer_inner(void * obj, curr_fo -> fo_fn = fn; curr_fo -> fo_client_data = (ptr_t)cd; curr_fo -> fo_mark_proc = mp; + GC_dirty(curr_fo); /* Reinsert it. We deleted it first to maintain */ /* consistency in the event of a signal. */ if (prev_fo == 0) { GC_fnlz_roots.fo_head[index] = curr_fo; } else { fo_set_next(prev_fo, curr_fo); + GC_dirty(prev_fo); } } + if (NULL == prev_fo) + GC_dirty(GC_fnlz_roots.fo_head + index); UNLOCK(); # ifndef DBG_HDRS_ALL if (EXPECT(new_fo != 0, FALSE)) { @@ -780,8 +801,10 @@ STATIC void GC_register_finalizer_inner(void * obj, new_fo -> fo_object_size = hhdr -> hb_sz; new_fo -> fo_mark_proc = mp; fo_set_next(new_fo, GC_fnlz_roots.fo_head[index]); + GC_dirty(new_fo); GC_fo_entries++; GC_fnlz_roots.fo_head[index] = new_fo; + GC_dirty(GC_fnlz_roots.fo_head + index); UNLOCK(); } @@ -909,6 +932,7 @@ GC_API void GC_CALL GC_register_finalizer_unreachable(void * obj, size_t i; \ size_t dl_size = dl_hashtbl->log_size == -1 ? 0 : \ (size_t)1 << dl_hashtbl->log_size; \ + GC_bool needs_barrier = FALSE; \ for (i = 0; i < dl_size; i++) { \ struct disappearing_link *prev_dl = NULL; \ curr_dl = dl_hashtbl -> head[i]; \ @@ -919,6 +943,8 @@ GC_API void GC_CALL GC_register_finalizer_unreachable(void * obj, curr_dl = dl_next(curr_dl); \ } \ } \ + if (needs_barrier) \ + GC_dirty(dl_hashtbl -> head); /* entire object */ \ } #define DELETE_DL_HASHTBL_ENTRY(dl_hashtbl, curr_dl, prev_dl, next_dl) \ @@ -926,8 +952,10 @@ GC_API void GC_CALL GC_register_finalizer_unreachable(void * obj, next_dl = dl_next(curr_dl); \ if (NULL == prev_dl) { \ dl_hashtbl -> head[i] = next_dl; \ + needs_barrier = TRUE; \ } else { \ dl_set_next(prev_dl, next_dl); \ + GC_dirty(prev_dl); \ } \ GC_clear_mark_bit(curr_dl); \ dl_hashtbl -> entries--; \ @@ -975,6 +1003,7 @@ GC_INNER void GC_finalize(void) size_t i; size_t fo_size = log_fo_table_size == -1 ? 0 : (size_t)1 << log_fo_table_size; + GC_bool needs_barrier = FALSE; # ifndef SMALL_CONFIG /* Save current GC_[dl/ll]_entries value for stats printing */ @@ -1022,8 +1051,14 @@ GC_INNER void GC_finalize(void) next_fo = fo_next(curr_fo); if (NULL == prev_fo) { GC_fnlz_roots.fo_head[i] = next_fo; + if (GC_object_finalized_proc) { + GC_dirty(GC_fnlz_roots.fo_head + i); + } else { + needs_barrier = TRUE; + } } else { fo_set_next(prev_fo, next_fo); + GC_dirty(prev_fo); } GC_fo_entries--; if (GC_object_finalized_proc) @@ -1031,6 +1066,7 @@ GC_INNER void GC_finalize(void) /* Add to list of objects awaiting finalization. */ fo_set_next(curr_fo, GC_fnlz_roots.finalize_now); + GC_dirty(curr_fo); GC_fnlz_roots.finalize_now = curr_fo; /* unhide object pointer so any future collections will */ /* see it. */ @@ -1084,6 +1120,7 @@ GC_INNER void GC_finalize(void) GC_fnlz_roots.finalize_now = next_fo; } else { fo_set_next(prev_fo, next_fo); + GC_dirty(prev_fo); } curr_fo -> fo_hidden_base = GC_HIDE_POINTER(curr_fo -> fo_hidden_base); @@ -1092,9 +1129,11 @@ GC_INNER void GC_finalize(void) i = HASH2(real_ptr, log_fo_table_size); fo_set_next(curr_fo, GC_fnlz_roots.fo_head[i]); + GC_dirty(curr_fo); GC_fo_entries++; GC_fnlz_roots.fo_head[i] = curr_fo; curr_fo = prev_fo; + needs_barrier = TRUE; } } prev_fo = curr_fo; @@ -1102,6 +1141,8 @@ GC_INNER void GC_finalize(void) } } } + if (needs_barrier) + GC_dirty(GC_fnlz_roots.fo_head); /* entire object */ GC_remove_dangling_disappearing_links(&GC_dl_hashtbl); # ifndef GC_TOGGLE_REFS_NOT_NEEDED @@ -1148,6 +1189,7 @@ GC_INNER void GC_finalize(void) /* Add to list of objects awaiting finalization. */ fo_set_next(curr_fo, GC_fnlz_roots.finalize_now); + GC_dirty(curr_fo); GC_fnlz_roots.finalize_now = curr_fo; /* unhide object pointer so any future collections will */ diff --git a/gcj_mlc.c b/gcj_mlc.c index 64f0d146..8879a3bb 100644 --- a/gcj_mlc.c +++ b/gcj_mlc.c @@ -192,7 +192,9 @@ static void maybe_finalize(void) } *(void **)op = ptr_to_struct_containing_descr; UNLOCK(); - return((void *) op); + GC_dirty(op); + REACHABLE_AFTER_DIRTY(ptr_to_struct_containing_descr); + return (void *)op; } /* Similar to GC_gcj_malloc, but add debug info. This is allocated */ @@ -223,6 +225,8 @@ GC_API GC_ATTR_MALLOC void * GC_CALL GC_debug_gcj_malloc(size_t lb, ADD_CALL_CHAIN(result, ra); result = GC_store_debug_info_inner(result, (word)lb, s, i); UNLOCK(); + GC_dirty(result); + REACHABLE_AFTER_DIRTY(ptr_to_struct_containing_descr); return result; } @@ -263,7 +267,9 @@ GC_API GC_ATTR_MALLOC void * GC_CALL GC_gcj_malloc_ignore_off_page(size_t lb, } *(void **)op = ptr_to_struct_containing_descr; UNLOCK(); - return((void *) op); + GC_dirty(op); + REACHABLE_AFTER_DIRTY(ptr_to_struct_containing_descr); + return (void *)op; } #endif /* GC_GCJ_SUPPORT */ diff --git a/include/gc_inline.h b/include/gc_inline.h index dcbd8083..7372fadd 100644 --- a/include/gc_inline.h +++ b/include/gc_inline.h @@ -106,6 +106,10 @@ GC_API GC_ATTR_MALLOC GC_ATTR_ALLOC_SIZE(1) void * GC_CALL *my_fl = next; \ init; \ GC_PREFETCH_FOR_WRITE(next); \ + if ((kind) != GC_I_PTRFREE) { \ + GC_end_stubborn_change(my_fl); \ + GC_reachable_here(next); \ + } \ GC_ASSERT(GC_size(result) >= (granules)*GC_GRANULE_BYTES); \ GC_ASSERT((kind) == GC_I_PTRFREE \ || ((GC_word *)result)[1] == 0); \ @@ -162,10 +166,15 @@ GC_API GC_ATTR_MALLOC GC_ATTR_ALLOC_SIZE(1) void * GC_CALL /* And once more for two word initialized objects: */ # define GC_CONS(result, first, second, tiny_fl) \ do { \ + void *l = (void *)(first); \ + void *r = (void *)(second); \ GC_MALLOC_WORDS_KIND(result, 2, tiny_fl, GC_I_NORMAL, (void)0); \ if ((result) != NULL) { \ - *(void **)(result) = (void *)(first); \ - ((void **)(result))[1] = (void *)(second); \ + *(void **)(result) = l; \ + ((void **)(result))[1] = r; \ + GC_end_stubborn_change(result); \ + GC_reachable_here(l); \ + GC_reachable_here(r); \ } \ } while (0) diff --git a/include/private/gc_priv.h b/include/private/gc_priv.h index 19a15ee9..9080f05c 100644 --- a/include/private/gc_priv.h +++ b/include/private/gc_priv.h @@ -2150,8 +2150,10 @@ GC_EXTERN GC_bool GC_print_back_height; #ifdef MANUAL_VDB GC_INNER void GC_dirty_inner(const void *p); /* does not require locking */ # define GC_dirty(p) (GC_incremental ? GC_dirty_inner(p) : (void)0) +# define REACHABLE_AFTER_DIRTY(p) GC_reachable_here(p) #else # define GC_dirty(p) (void)(p) +# define REACHABLE_AFTER_DIRTY(p) (void)(p) #endif /* Same as GC_base but excepts and returns a pointer to const object. */ diff --git a/mallocx.c b/mallocx.c index d6b7f965..fc5c963c 100644 --- a/mallocx.c +++ b/mallocx.c @@ -286,11 +286,24 @@ GC_API void GC_CALL GC_generic_malloc_many(size_t lb, int k, void **result) DCL_LOCK_STATE; GC_ASSERT(lb != 0 && (lb & (GRANULE_BYTES-1)) == 0); - if (!SMALL_OBJ(lb)) { + if (!SMALL_OBJ(lb) +# ifdef MANUAL_VDB + /* Currently a single object is allocated. */ + /* TODO: GC_dirty should be called for each linked object (but */ + /* the last one) to support multiple objects allocation. */ + || GC_incremental +# endif + ) { op = GC_generic_malloc(lb, k); if (EXPECT(0 != op, TRUE)) obj_link(op) = 0; *result = op; +# ifdef MANUAL_VDB + if (GC_is_heap_ptr(result)) { + GC_dirty(result); + REACHABLE_AFTER_DIRTY(op); + } +# endif return; } GC_ASSERT(k < MAXOBJKINDS); diff --git a/pthread_start.c b/pthread_start.c index bd4fc4a0..f83dc434 100644 --- a/pthread_start.c +++ b/pthread_start.c @@ -59,6 +59,7 @@ GC_INNER_PTHRSTART void * GC_CALLBACK GC_inner_start_routine( GC_log_printf("Finishing thread %p\n", (void *)pthread_self()); # endif me -> status = result; + GC_dirty(me); # ifndef NACL pthread_cleanup_pop(1); /* Cleanup acquires lock, ensuring that we can't exit while */ diff --git a/pthread_support.c b/pthread_support.c index 969f477a..3dc7b59e 100644 --- a/pthread_support.c +++ b/pthread_support.c @@ -563,6 +563,8 @@ STATIC GC_thread GC_new_thread(pthread_t id) GC_nacl_initialize_gc_thread(); # endif GC_ASSERT(result -> flags == 0 && result -> thread_blocked == 0); + if (EXPECT(result != &first_thread, TRUE)) + GC_dirty(result); return(result); } @@ -594,6 +596,7 @@ STATIC void GC_delete_thread(pthread_t id) GC_threads[hv] = p -> next; } else { prev -> next = p -> next; + GC_dirty(prev); } if (p != &first_thread) { # ifdef GC_DARWIN_THREADS @@ -623,6 +626,7 @@ STATIC void GC_delete_gc_thread(GC_thread t) GC_threads[hv] = p -> next; } else { prev -> next = p -> next; + GC_dirty(prev); } # ifdef GC_DARWIN_THREADS mach_port_deallocate(mach_task_self(), p->stop_info.mach_thread); diff --git a/specific.c b/specific.c index 75917294..579be6f7 100644 --- a/specific.c +++ b/specific.c @@ -68,6 +68,8 @@ GC_INNER int GC_setspecific(tsd * key, void * value) /* There can only be one writer at a time, but this needs to be */ /* atomic with respect to concurrent readers. */ AO_store_release(&key->hash[hash_val].ao, (AO_t)entry); + GC_dirty((/* no volatile */ void *)entry); + GC_dirty(key->hash + hash_val); pthread_mutex_unlock(&(key -> lock)); return 0; } @@ -99,8 +101,10 @@ GC_INNER void GC_remove_specific_after_fork(tsd * key, pthread_t t) entry -> qtid = INVALID_QTID; if (NULL == prev) { key->hash[hash_val].p = entry->next; + GC_dirty(key->hash + hash_val); } else { prev->next = entry->next; + GC_dirty(prev); } /* Atomic! concurrent accesses still work. */ /* They must, since readers don't lock. */ diff --git a/typd_mlc.c b/typd_mlc.c index 42c20c9a..22b980c5 100644 --- a/typd_mlc.c +++ b/typd_mlc.c @@ -321,6 +321,9 @@ GC_make_sequence_descriptor(complex_descriptor *first, result -> sd_tag = SEQUENCE_TAG; result -> sd_first = first; result -> sd_second = second; + GC_dirty(result); + REACHABLE_AFTER_DIRTY(first); + REACHABLE_AFTER_DIRTY(second); } return((complex_descriptor *)result); } @@ -599,6 +602,8 @@ GC_API GC_ATTR_MALLOC void * GC_CALL GC_malloc_explicitly_typed(size_t lb, /* the former might be updated asynchronously. */ lg = BYTES_TO_GRANULES(GC_size(op)); op[GRANULES_TO_WORDS(lg) - 1] = d; + GC_dirty(op + GRANULES_TO_WORDS(lg) - 1); + REACHABLE_AFTER_DIRTY(d); return op; } @@ -634,6 +639,8 @@ GC_API GC_ATTR_MALLOC void * GC_CALL lg = BYTES_TO_GRANULES(GC_size(op)); } ((word *)op)[GRANULES_TO_WORDS(lg) - 1] = d; + GC_dirty(op + GRANULES_TO_WORDS(lg) - 1); + REACHABLE_AFTER_DIRTY(d); return op; } @@ -687,6 +694,9 @@ GC_API GC_ATTR_MALLOC void * GC_CALL GC_calloc_explicitly_typed(size_t n, size_t lw = GRANULES_TO_WORDS(lg); op[lw - 1] = (word)complex_descr; + GC_dirty(op + lw - 1); + REACHABLE_AFTER_DIRTY(complex_descr); + /* Make sure the descriptor is cleared once there is any danger */ /* it may have been collected. */ if (EXPECT(GC_general_register_disappearing_link( diff --git a/win32_threads.c b/win32_threads.c index b2753f85..a9cff3fc 100644 --- a/win32_threads.c +++ b/win32_threads.c @@ -356,6 +356,8 @@ STATIC GC_thread GC_new_thread(DWORD id) GC_ASSERT(result -> flags == 0); # endif GC_ASSERT(result -> thread_blocked_sp == NULL); + if (EXPECT(result != &first_thread, TRUE)) + GC_dirty(result); return(result); } @@ -678,6 +680,7 @@ STATIC void GC_delete_gc_thread_no_free(GC_vthread t) GC_threads[hv] = p -> tm.next; } else { prev -> tm.next = p -> tm.next; + GC_dirty(prev); } } } @@ -715,6 +718,7 @@ STATIC void GC_delete_thread(DWORD id) GC_threads[hv] = p -> tm.next; } else { prev -> tm.next = p -> tm.next; + GC_dirty(prev); } if (p != &first_thread) { GC_INTERNAL_FREE(p); @@ -2251,6 +2255,8 @@ GC_INNER void GC_get_next_stack(char *start, char *limit, /* set up thread arguments */ args -> start = lpStartAddress; args -> param = lpParameter; + GC_dirty(args); + REACHABLE_AFTER_DIRTY(lpParameter); set_need_to_lock(); thread_h = CreateThread(lpThreadAttributes, dwStackSize, GC_win32_start, @@ -2303,6 +2309,8 @@ GC_INNER void GC_get_next_stack(char *start, char *limit, /* set up thread arguments */ args -> start = (LPTHREAD_START_ROUTINE)start_address; args -> param = arglist; + GC_dirty(args); + REACHABLE_AFTER_DIRTY(arglist); set_need_to_lock(); thread_h = _beginthreadex(security, stack_size, @@ -2589,6 +2597,8 @@ GC_INNER void GC_thr_init(void) si -> start_routine = start_routine; si -> arg = arg; + GC_dirty(si); + REACHABLE_AFTER_DIRTY(arg); if (attr != 0 && pthread_attr_getdetachstate(attr, &si->detached) == PTHREAD_CREATE_DETACHED) { @@ -2648,6 +2658,7 @@ GC_INNER void GC_thr_init(void) pthread_cleanup_push(GC_thread_exit_proc, (void *)me); result = (*start)(start_arg); me -> status = result; + GC_dirty(me); pthread_cleanup_pop(1); # ifdef DEBUG_THREADS