|
| 1 | +From 5668de71107022a316ee967162bc16c10754b9ce Mon Sep 17 00:00:00 2001 |
| 2 | +From: "Brian J. Cardiff" < [email protected]> |
| 3 | +Date: Mon, 6 May 2019 12:06:12 +0300 |
| 4 | +Subject: [PATCH] Add API functions to get and set the stack bottom of each |
| 5 | + thread |
| 6 | + |
| 7 | +Issue #277 (bdwgc). |
| 8 | + |
| 9 | +This API is useful to support coroutines. |
| 10 | + |
| 11 | +* include/gc.h (GC_get_my_stackbottom, GC_set_stackbottom): New API |
| 12 | +function declaration. |
| 13 | +* misc.c [!THREADS] (GC_set_stackbottom, GC_get_my_stackbottom): New |
| 14 | +function definition. |
| 15 | +* pthread_support.c [GC_PTHREADS && !GC_WIN32_THREADS] |
| 16 | +(GC_set_stackbottom, GC_get_my_stackbottom): Likewise. |
| 17 | +* win32_threads.c [GC_WIN32_THREADS] (GC_set_stackbottom, |
| 18 | +GC_get_my_stackbottom): Likewise. |
| 19 | +* tests/test.c (struct thr_hndl_sb_s): Define. |
| 20 | +* tests/test.c (set_stackbottom): New function (which calls |
| 21 | +GC_set_stackbottom). |
| 22 | +* tests/test.c (run_one_test): Define thr_hndl_sb local variable; |
| 23 | +call GC_get_my_stackbottom() and set_stackbottom(). |
| 24 | +* win32_threads.c [GC_WIN32_THREADS && I386] (struct GC_Thread_Rep): |
| 25 | +Add initial_stack_base field. |
| 26 | +* win32_threads.c [GC_WIN32_THREADS && I386] (GC_record_stack_base, |
| 27 | +GC_call_with_gc_active): Set initial_stack_base field. |
| 28 | +* win32_threads.c [GC_WIN32_THREADS && I386] (GC_push_stack_for): Handle |
| 29 | +the case when GetThreadContext() might return stale register values, |
| 30 | +thread stack_base != initial_stack_base but the stack is not inside |
| 31 | +the TIB stack (use context.Esp but call WARN); add TODO. |
| 32 | +--- |
| 33 | + include/gc.h | 24 +++++++++++++++ |
| 34 | + misc.c | 24 +++++++++++++++ |
| 35 | + pthread_support.c | 56 +++++++++++++++++++++++++++++++++++ |
| 36 | + tests/test.c | 16 ++++++++++ |
| 37 | + win32_threads.c | 74 ++++++++++++++++++++++++++++++++++++++++++++++- |
| 38 | + 5 files changed, 193 insertions(+), 1 deletion(-) |
| 39 | + |
| 40 | +diff --git a/include/gc.h b/include/gc.h |
| 41 | +index d318cf3f..a7164c34 100644 |
| 42 | +--- a/include/gc.h |
| 43 | ++++ b/include/gc.h |
| 44 | +@@ -1578,6 +1578,30 @@ GC_API void * GC_CALL GC_call_with_gc_active(GC_fn_type /* fn */, |
| 45 | + GC_API int GC_CALL GC_get_stack_base(struct GC_stack_base *) |
| 46 | + GC_ATTR_NONNULL(1); |
| 47 | + |
| 48 | ++/* Fill in the GC_stack_base structure with the cold end (bottom) of */ |
| 49 | ++/* the stack of the current thread (or coroutine). */ |
| 50 | ++/* Unlike GC_get_stack_base, it retrieves the value stored in the */ |
| 51 | ++/* collector (which is initially set by the collector upon the thread */ |
| 52 | ++/* is started or registered manually but it could be later updated by */ |
| 53 | ++/* client using GC_set_stackbottom). Returns the GC-internal non-NULL */ |
| 54 | ++/* handle of the thread which could be passed to GC_set_stackbottom */ |
| 55 | ++/* later. It is assumed that the collector is already initialized and */ |
| 56 | ++/* the thread is registered. Acquires the GC lock to avoid data races. */ |
| 57 | ++GC_API void * GC_CALL GC_get_my_stackbottom(struct GC_stack_base *) |
| 58 | ++ GC_ATTR_NONNULL(1); |
| 59 | ++ |
| 60 | ++/* Set the cool end of the user (coroutine) stack of the specified */ |
| 61 | ++/* thread. The GC thread handle is either the one returned by */ |
| 62 | ++/* GC_get_my_stackbottom or NULL (the latter designates the current */ |
| 63 | ++/* thread). The caller should hold the GC lock (e.g. using */ |
| 64 | ++/* GC_call_with_alloc_lock). Also, the function could be used for */ |
| 65 | ++/* setting GC_stackbottom value (the bottom of the primordial thread) */ |
| 66 | ++/* before the collector is initialized (the GC lock is not needed to be */ |
| 67 | ++/* acquired in this case). */ |
| 68 | ++GC_API void GC_CALL GC_set_stackbottom(void * /* gc_thread_handle */, |
| 69 | ++ const struct GC_stack_base *) |
| 70 | ++ GC_ATTR_NONNULL(2); |
| 71 | ++ |
| 72 | + /* The following routines are primarily intended for use with a */ |
| 73 | + /* preprocessor which inserts calls to check C pointer arithmetic. */ |
| 74 | + /* They indicate failure by invoking the corresponding _print_proc. */ |
| 75 | +diff --git a/misc.c b/misc.c |
| 76 | +index 5b473569..322422fb 100644 |
| 77 | +--- a/misc.c |
| 78 | ++++ b/misc.c |
| 79 | +@@ -2200,6 +2200,30 @@ STATIC void GC_do_blocking_inner(ptr_t data, void * context GC_ATTR_UNUSED) |
| 80 | + GC_blocked_sp = NULL; |
| 81 | + } |
| 82 | + |
| 83 | ++ GC_API void GC_CALL GC_set_stackbottom(void *gc_thread_handle, |
| 84 | ++ const struct GC_stack_base *sb) |
| 85 | ++ { |
| 86 | ++ GC_ASSERT(sb -> mem_base != NULL); |
| 87 | ++ GC_ASSERT(NULL == gc_thread_handle || &GC_stackbottom == gc_thread_handle); |
| 88 | ++ GC_ASSERT(NULL == GC_blocked_sp |
| 89 | ++ && NULL == GC_traced_stack_sect); /* for now */ |
| 90 | ++ (void)gc_thread_handle; |
| 91 | ++ |
| 92 | ++ GC_stackbottom = (char *)sb->mem_base; |
| 93 | ++# ifdef IA64 |
| 94 | ++ GC_register_stackbottom = (ptr_t)sb->reg_base; |
| 95 | ++# endif |
| 96 | ++ } |
| 97 | ++ |
| 98 | ++ GC_API void * GC_CALL GC_get_my_stackbottom(struct GC_stack_base *sb) |
| 99 | ++ { |
| 100 | ++ GC_ASSERT(GC_is_initialized); |
| 101 | ++ sb -> mem_base = GC_stackbottom; |
| 102 | ++# ifdef IA64 |
| 103 | ++ sb -> reg_base = GC_register_stackbottom; |
| 104 | ++# endif |
| 105 | ++ return &GC_stackbottom; /* gc_thread_handle */ |
| 106 | ++ } |
| 107 | + #endif /* !THREADS */ |
| 108 | + |
| 109 | + GC_API void * GC_CALL GC_do_blocking(GC_fn_type fn, void * client_data) |
| 110 | +diff --git a/pthread_support.c b/pthread_support.c |
| 111 | +index 6c138c1c..0b2af4ac 100644 |
| 112 | +--- a/pthread_support.c |
| 113 | ++++ b/pthread_support.c |
| 114 | +@@ -1403,6 +1403,62 @@ GC_INNER void GC_do_blocking_inner(ptr_t data, void * context GC_ATTR_UNUSED) |
| 115 | + UNLOCK(); |
| 116 | + } |
| 117 | + |
| 118 | ++GC_API void GC_CALL GC_set_stackbottom(void *gc_thread_handle, |
| 119 | ++ const struct GC_stack_base *sb) |
| 120 | ++{ |
| 121 | ++ GC_thread t = (GC_thread)gc_thread_handle; |
| 122 | ++ |
| 123 | ++ GC_ASSERT(sb -> mem_base != NULL); |
| 124 | ++ if (!EXPECT(GC_is_initialized, TRUE)) { |
| 125 | ++ GC_ASSERT(NULL == t); |
| 126 | ++ } else { |
| 127 | ++ GC_ASSERT(I_HOLD_LOCK()); |
| 128 | ++ if (NULL == t) /* current thread? */ |
| 129 | ++ t = GC_lookup_thread(pthread_self()); |
| 130 | ++ GC_ASSERT((t -> flags & FINISHED) == 0); |
| 131 | ++ GC_ASSERT(!(t -> thread_blocked) |
| 132 | ++ && NULL == t -> traced_stack_sect); /* for now */ |
| 133 | ++ |
| 134 | ++ if ((t -> flags & MAIN_THREAD) == 0) { |
| 135 | ++ t -> stack_end = (ptr_t)sb->mem_base; |
| 136 | ++# ifdef IA64 |
| 137 | ++ t -> backing_store_end = (ptr_t)sb->reg_base; |
| 138 | ++# endif |
| 139 | ++ return; |
| 140 | ++ } |
| 141 | ++ /* Otherwise alter the stack bottom of the primordial thread. */ |
| 142 | ++ } |
| 143 | ++ |
| 144 | ++ GC_stackbottom = (char*)sb->mem_base; |
| 145 | ++# ifdef IA64 |
| 146 | ++ GC_register_stackbottom = (ptr_t)sb->reg_base; |
| 147 | ++# endif |
| 148 | ++} |
| 149 | ++ |
| 150 | ++GC_API void * GC_CALL GC_get_my_stackbottom(struct GC_stack_base *sb) |
| 151 | ++{ |
| 152 | ++ pthread_t self = pthread_self(); |
| 153 | ++ GC_thread me; |
| 154 | ++ DCL_LOCK_STATE; |
| 155 | ++ |
| 156 | ++ LOCK(); |
| 157 | ++ me = GC_lookup_thread(self); |
| 158 | ++ /* The thread is assumed to be registered. */ |
| 159 | ++ if ((me -> flags & MAIN_THREAD) == 0) { |
| 160 | ++ sb -> mem_base = me -> stack_end; |
| 161 | ++# ifdef IA64 |
| 162 | ++ sb -> reg_base = me -> backing_store_end; |
| 163 | ++# endif |
| 164 | ++ } else { |
| 165 | ++ sb -> mem_base = GC_stackbottom; |
| 166 | ++# ifdef IA64 |
| 167 | ++ sb -> reg_base = GC_register_stackbottom; |
| 168 | ++# endif |
| 169 | ++ } |
| 170 | ++ UNLOCK(); |
| 171 | ++ return (void *)me; /* gc_thread_handle */ |
| 172 | ++} |
| 173 | ++ |
| 174 | + /* GC_call_with_gc_active() has the opposite to GC_do_blocking() */ |
| 175 | + /* functionality. It might be called from a user function invoked by */ |
| 176 | + /* GC_do_blocking() to temporarily back allow calling any GC function */ |
| 177 | +diff --git a/tests/test.c b/tests/test.c |
| 178 | +index f7db8495..c5338005 100644 |
| 179 | +--- a/tests/test.c |
| 180 | ++++ b/tests/test.c |
| 181 | +@@ -1312,6 +1312,18 @@ void * GC_CALLBACK inc_int_counter(void *pcounter) |
| 182 | + return NULL; |
| 183 | + } |
| 184 | + |
| 185 | ++struct thr_hndl_sb_s { |
| 186 | ++ void *gc_thread_handle; |
| 187 | ++ struct GC_stack_base sb; |
| 188 | ++}; |
| 189 | ++ |
| 190 | ++void * GC_CALLBACK set_stackbottom(void *cd) |
| 191 | ++{ |
| 192 | ++ GC_set_stackbottom(((struct thr_hndl_sb_s *)cd)->gc_thread_handle, |
| 193 | ++ &((struct thr_hndl_sb_s *)cd)->sb); |
| 194 | ++ return NULL; |
| 195 | ++} |
| 196 | ++ |
| 197 | + #ifndef MIN_WORDS |
| 198 | + # define MIN_WORDS 2 |
| 199 | + #endif |
| 200 | +@@ -1332,6 +1344,7 @@ void run_one_test(void) |
| 201 | + pid_t pid; |
| 202 | + int wstatus; |
| 203 | + # endif |
| 204 | ++ struct thr_hndl_sb_s thr_hndl_sb; |
| 205 | + |
| 206 | + GC_FREE(0); |
| 207 | + # ifdef THREADS |
| 208 | +@@ -1476,6 +1489,7 @@ void run_one_test(void) |
| 209 | + GC_FREE(GC_MALLOC_IGNORE_OFF_PAGE(2)); |
| 210 | + } |
| 211 | + } |
| 212 | ++ thr_hndl_sb.gc_thread_handle = GC_get_my_stackbottom(&thr_hndl_sb.sb); |
| 213 | + # ifdef GC_GCJ_SUPPORT |
| 214 | + GC_REGISTER_DISPLACEMENT(sizeof(struct fake_vtable *)); |
| 215 | + GC_init_gcj_malloc(0, (void *)(GC_word)fake_gcj_mark_proc); |
| 216 | +@@ -1545,6 +1559,8 @@ void run_one_test(void) |
| 217 | + exit(0); |
| 218 | + } |
| 219 | + # endif |
| 220 | ++ (void)GC_call_with_alloc_lock(set_stackbottom, &thr_hndl_sb); |
| 221 | ++ |
| 222 | + /* Repeated list reversal test. */ |
| 223 | + # ifndef NO_CLOCK |
| 224 | + GET_TIME(start_time); |
| 225 | +diff --git a/win32_threads.c b/win32_threads.c |
| 226 | +index 2e9f37fb..f45e8c04 100644 |
| 227 | +--- a/win32_threads.c |
| 228 | ++++ b/win32_threads.c |
| 229 | +@@ -214,6 +214,11 @@ struct GC_Thread_Rep { |
| 230 | + # ifdef IA64 |
| 231 | + ptr_t backing_store_end; |
| 232 | + ptr_t backing_store_ptr; |
| 233 | ++# elif defined(I386) |
| 234 | ++ ptr_t initial_stack_base; |
| 235 | ++ /* The cold end of the stack saved by */ |
| 236 | ++ /* GC_record_stack_base (never modified */ |
| 237 | ++ /* by GC_set_stackbottom). */ |
| 238 | + # endif |
| 239 | + |
| 240 | + ptr_t thread_blocked_sp; /* Protected by GC lock. */ |
| 241 | +@@ -374,6 +379,8 @@ GC_INLINE void GC_record_stack_base(GC_vthread me, |
| 242 | + me -> stack_base = (ptr_t)sb->mem_base; |
| 243 | + # ifdef IA64 |
| 244 | + me -> backing_store_end = (ptr_t)sb->reg_base; |
| 245 | ++# elif defined(I386) |
| 246 | ++ me -> initial_stack_base = (ptr_t)sb->mem_base; |
| 247 | + # endif |
| 248 | + if (me -> stack_base == NULL) |
| 249 | + ABORT("Bad stack base in GC_register_my_thread"); |
| 250 | +@@ -913,8 +920,12 @@ GC_API void * GC_CALL GC_call_with_gc_active(GC_fn_type fn, |
| 251 | + /* Adjust our stack bottom pointer (this could happen unless */ |
| 252 | + /* GC_get_stack_base() was used which returned GC_SUCCESS). */ |
| 253 | + GC_ASSERT(me -> stack_base != NULL); |
| 254 | +- if ((word)me->stack_base < (word)(&stacksect)) |
| 255 | ++ if ((word)me->stack_base < (word)(&stacksect)) { |
| 256 | + me -> stack_base = (ptr_t)(&stacksect); |
| 257 | ++# if defined(I386) |
| 258 | ++ me -> initial_stack_base = me -> stack_base; |
| 259 | ++# endif |
| 260 | ++ } |
| 261 | + |
| 262 | + if (me -> thread_blocked_sp == NULL) { |
| 263 | + /* We are not inside GC_do_blocking() - do nothing more. */ |
| 264 | +@@ -958,6 +969,53 @@ GC_API void * GC_CALL GC_call_with_gc_active(GC_fn_type fn, |
| 265 | + return client_data; /* result */ |
| 266 | + } |
| 267 | + |
| 268 | ++GC_API void GC_CALL GC_set_stackbottom(void *gc_thread_handle, |
| 269 | ++ const struct GC_stack_base *sb) |
| 270 | ++{ |
| 271 | ++ GC_thread t = (GC_thread)gc_thread_handle; |
| 272 | ++ |
| 273 | ++ GC_ASSERT(sb -> mem_base != NULL); |
| 274 | ++ if (!EXPECT(GC_is_initialized, TRUE)) { |
| 275 | ++ GC_ASSERT(NULL == t); |
| 276 | ++ GC_stackbottom = (char *)sb->mem_base; |
| 277 | ++# ifdef IA64 |
| 278 | ++ GC_register_stackbottom = (ptr_t)sb->reg_base; |
| 279 | ++# endif |
| 280 | ++ return; |
| 281 | ++ } |
| 282 | ++ |
| 283 | ++ GC_ASSERT(I_HOLD_LOCK()); |
| 284 | ++ if (NULL == t) { /* current thread? */ |
| 285 | ++ t = GC_lookup_thread_inner(GetCurrentThreadId()); |
| 286 | ++ CHECK_LOOKUP_MY_THREAD(t); |
| 287 | ++ } |
| 288 | ++ GC_ASSERT(!KNOWN_FINISHED(t)); |
| 289 | ++ GC_ASSERT(NULL == t -> thread_blocked_sp |
| 290 | ++ && NULL == t -> traced_stack_sect); /* for now */ |
| 291 | ++ t -> stack_base = (ptr_t)sb->mem_base; |
| 292 | ++ t -> last_stack_min = ADDR_LIMIT; /* reset the known minimum */ |
| 293 | ++# ifdef IA64 |
| 294 | ++ t -> backing_store_end = (ptr_t)sb->reg_base; |
| 295 | ++# endif |
| 296 | ++} |
| 297 | ++ |
| 298 | ++GC_API void * GC_CALL GC_get_my_stackbottom(struct GC_stack_base *sb) |
| 299 | ++{ |
| 300 | ++ DWORD thread_id = GetCurrentThreadId(); |
| 301 | ++ GC_thread me; |
| 302 | ++ DCL_LOCK_STATE; |
| 303 | ++ |
| 304 | ++ LOCK(); |
| 305 | ++ me = GC_lookup_thread_inner(thread_id); |
| 306 | ++ CHECK_LOOKUP_MY_THREAD(me); /* the thread is assumed to be registered */ |
| 307 | ++ sb -> mem_base = me -> stack_base; |
| 308 | ++# ifdef IA64 |
| 309 | ++ sb -> reg_base = me -> backing_store_end; |
| 310 | ++# endif |
| 311 | ++ UNLOCK(); |
| 312 | ++ return (void *)me; /* gc_thread_handle */ |
| 313 | ++} |
| 314 | ++ |
| 315 | + #ifdef GC_PTHREADS |
| 316 | + |
| 317 | + /* A quick-and-dirty cache of the mapping between pthread_t */ |
| 318 | +@@ -1454,6 +1512,20 @@ STATIC word GC_push_stack_for(GC_thread thread, DWORD me) |
| 319 | + # endif |
| 320 | + GC_ASSERT(!((word)thread->stack_base |
| 321 | + COOLER_THAN (word)tib->StackBase)); |
| 322 | ++ if (thread->stack_base != thread->initial_stack_base) { |
| 323 | ++ /* We are in a coroutine. */ |
| 324 | ++ if ((word)thread->stack_base <= (word)sp /* StackLimit */ |
| 325 | ++ || (word)tib->StackBase < (word)thread->stack_base) { |
| 326 | ++ /* The coroutine stack is not within TIB stack. */ |
| 327 | ++ sp = (ptr_t)context.Esp; |
| 328 | ++ WARN("GetThreadContext might return stale register values" |
| 329 | ++ " including ESP=%p\n", sp); |
| 330 | ++ /* TODO: Because of WoW64 bug, there is no guarantee that */ |
| 331 | ++ /* sp really points to the stack top but, for now, we do */ |
| 332 | ++ /* our best as the TIB stack limit/base cannot be used */ |
| 333 | ++ /* while we are inside a coroutine. */ |
| 334 | ++ } |
| 335 | ++ } |
| 336 | + } else { |
| 337 | + # ifdef DEBUG_THREADS |
| 338 | + { |
0 commit comments