]>
Commit | Line | Data |
---|---|---|
03385ed3 | 1 | /** |
2 | * The thread module provides support for thread creation and management. | |
3 | * | |
4 | * Copyright: Copyright Sean Kelly 2005 - 2012. | |
5 | * License: Distributed under the | |
6 | * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). | |
7 | * (See accompanying file LICENSE) | |
8 | * Authors: Sean Kelly, Walter Bright, Alex Rønne Petersen, Martin Nowak | |
9 | * Source: $(DRUNTIMESRC core/_thread.d) | |
10 | */ | |
11 | ||
12 | /* NOTE: This file has been patched from the original DMD distribution to | |
13 | * work with the GDC compiler. | |
14 | */ | |
15 | module core.thread; | |
16 | ||
17 | ||
18 | public import core.time; // for Duration | |
19 | import core.exception : onOutOfMemoryError; | |
20 | ||
21 | version (OSX) | |
22 | version = Darwin; | |
23 | else version (iOS) | |
24 | version = Darwin; | |
25 | else version (TVOS) | |
26 | version = Darwin; | |
27 | else version (WatchOS) | |
28 | version = Darwin; | |
29 | ||
30 | private | |
31 | { | |
32 | // interface to rt.tlsgc | |
33 | import core.internal.traits : externDFunc; | |
34 | ||
35 | alias rt_tlsgc_init = externDFunc!("rt.tlsgc.init", void* function() nothrow @nogc); | |
36 | alias rt_tlsgc_destroy = externDFunc!("rt.tlsgc.destroy", void function(void*) nothrow @nogc); | |
37 | ||
38 | alias ScanDg = void delegate(void* pstart, void* pend) nothrow; | |
39 | alias rt_tlsgc_scan = | |
40 | externDFunc!("rt.tlsgc.scan", void function(void*, scope ScanDg) nothrow); | |
41 | ||
42 | alias rt_tlsgc_processGCMarks = | |
43 | externDFunc!("rt.tlsgc.processGCMarks", void function(void*, scope IsMarkedDg) nothrow); | |
44 | } | |
45 | ||
46 | version (Solaris) | |
47 | { | |
48 | import core.sys.solaris.sys.priocntl; | |
49 | import core.sys.solaris.sys.types; | |
50 | } | |
51 | ||
52 | // this should be true for most architectures | |
53 | version (GNU_StackGrowsDown) | |
54 | version = StackGrowsDown; | |
55 | ||
56 | /** | |
57 | * Returns the process ID of the calling process, which is guaranteed to be | |
58 | * unique on the system. This call is always successful. | |
59 | * | |
60 | * Example: | |
61 | * --- | |
62 | * writefln("Current process id: %s", getpid()); | |
63 | * --- | |
64 | */ | |
65 | version (Posix) | |
66 | { | |
67 | alias getpid = core.sys.posix.unistd.getpid; | |
68 | } | |
69 | else version (Windows) | |
70 | { | |
71 | alias getpid = core.sys.windows.windows.GetCurrentProcessId; | |
72 | } | |
73 | ||
74 | ||
75 | /////////////////////////////////////////////////////////////////////////////// | |
76 | // Thread and Fiber Exceptions | |
77 | /////////////////////////////////////////////////////////////////////////////// | |
78 | ||
79 | ||
80 | /** | |
81 | * Base class for thread exceptions. | |
82 | */ | |
83 | class ThreadException : Exception | |
84 | { | |
85 | @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) | |
86 | { | |
87 | super(msg, file, line, next); | |
88 | } | |
89 | ||
90 | @safe pure nothrow this(string msg, Throwable next, string file = __FILE__, size_t line = __LINE__) | |
91 | { | |
92 | super(msg, file, line, next); | |
93 | } | |
94 | } | |
95 | ||
96 | ||
97 | /** | |
98 | * Base class for thread errors to be used for function inside GC when allocations are unavailable. | |
99 | */ | |
100 | class ThreadError : Error | |
101 | { | |
102 | @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) | |
103 | { | |
104 | super(msg, file, line, next); | |
105 | } | |
106 | ||
107 | @safe pure nothrow this(string msg, Throwable next, string file = __FILE__, size_t line = __LINE__) | |
108 | { | |
109 | super(msg, file, line, next); | |
110 | } | |
111 | } | |
112 | ||
113 | private | |
114 | { | |
115 | import core.atomic, core.memory, core.sync.mutex; | |
116 | ||
ca44cbe7 | 117 | // Handling unaligned mutexes are not supported on all platforms, so we must |
118 | // ensure that the address of all shared data are appropriately aligned. | |
119 | import core.internal.traits : classInstanceAlignment; | |
120 | ||
121 | enum mutexAlign = classInstanceAlignment!Mutex; | |
122 | enum mutexClassInstanceSize = __traits(classInstanceSize, Mutex); | |
123 | ||
03385ed3 | 124 | // |
125 | // exposed by compiler runtime | |
126 | // | |
127 | extern (C) void rt_moduleTlsCtor(); | |
128 | extern (C) void rt_moduleTlsDtor(); | |
129 | ||
130 | /** | |
131 | * Hook for whatever EH implementation is used to save/restore some data | |
132 | * per stack. | |
133 | * | |
134 | * Params: | |
135 | * newContext = The return value of the prior call to this function | |
136 | * where the stack was last swapped out, or null when a fiber stack | |
137 | * is switched in for the first time. | |
138 | */ | |
139 | extern(C) void* _d_eh_swapContext(void* newContext) nothrow @nogc; | |
140 | ||
141 | version (DigitalMars) | |
142 | { | |
143 | version (Windows) | |
144 | alias swapContext = _d_eh_swapContext; | |
145 | else | |
146 | { | |
147 | extern(C) void* _d_eh_swapContextDwarf(void* newContext) nothrow @nogc; | |
148 | ||
149 | void* swapContext(void* newContext) nothrow @nogc | |
150 | { | |
151 | /* Detect at runtime which scheme is being used. | |
152 | * Eventually, determine it statically. | |
153 | */ | |
154 | static int which = 0; | |
155 | final switch (which) | |
156 | { | |
157 | case 0: | |
158 | { | |
159 | assert(newContext == null); | |
160 | auto p = _d_eh_swapContext(newContext); | |
161 | auto pdwarf = _d_eh_swapContextDwarf(newContext); | |
162 | if (p) | |
163 | { | |
164 | which = 1; | |
165 | return p; | |
166 | } | |
167 | else if (pdwarf) | |
168 | { | |
169 | which = 2; | |
170 | return pdwarf; | |
171 | } | |
172 | return null; | |
173 | } | |
174 | case 1: | |
175 | return _d_eh_swapContext(newContext); | |
176 | case 2: | |
177 | return _d_eh_swapContextDwarf(newContext); | |
178 | } | |
179 | } | |
180 | } | |
181 | } | |
182 | else | |
183 | alias swapContext = _d_eh_swapContext; | |
184 | } | |
185 | ||
186 | ||
187 | /////////////////////////////////////////////////////////////////////////////// | |
188 | // Thread Entry Point and Signal Handlers | |
189 | /////////////////////////////////////////////////////////////////////////////// | |
190 | ||
191 | ||
192 | version (Windows) | |
193 | { | |
194 | private | |
195 | { | |
196 | import core.stdc.stdint : uintptr_t; // for _beginthreadex decl below | |
197 | import core.stdc.stdlib; // for malloc, atexit | |
198 | import core.sys.windows.windows; | |
199 | import core.sys.windows.threadaux; // for OpenThreadHandle | |
200 | ||
201 | extern (Windows) alias btex_fptr = uint function(void*); | |
202 | extern (C) uintptr_t _beginthreadex(void*, uint, btex_fptr, void*, uint, uint*) nothrow; | |
203 | ||
204 | // | |
205 | // Entry point for Windows threads | |
206 | // | |
207 | extern (Windows) uint thread_entryPoint( void* arg ) nothrow | |
208 | { | |
209 | Thread obj = cast(Thread) arg; | |
210 | assert( obj ); | |
211 | ||
212 | assert( obj.m_curr is &obj.m_main ); | |
213 | obj.m_main.bstack = getStackBottom(); | |
214 | obj.m_main.tstack = obj.m_main.bstack; | |
215 | obj.m_tlsgcdata = rt_tlsgc_init(); | |
216 | ||
217 | Thread.setThis(obj); | |
218 | Thread.add(obj); | |
219 | scope (exit) | |
220 | { | |
221 | Thread.remove(obj); | |
222 | } | |
223 | Thread.add(&obj.m_main); | |
224 | ||
225 | // NOTE: No GC allocations may occur until the stack pointers have | |
226 | // been set and Thread.getThis returns a valid reference to | |
227 | // this thread object (this latter condition is not strictly | |
228 | // necessary on Windows but it should be followed for the | |
229 | // sake of consistency). | |
230 | ||
231 | // TODO: Consider putting an auto exception object here (using | |
232 | // alloca) forOutOfMemoryError plus something to track | |
233 | // whether an exception is in-flight? | |
234 | ||
235 | void append( Throwable t ) | |
236 | { | |
237 | if ( obj.m_unhandled is null ) | |
238 | obj.m_unhandled = t; | |
239 | else | |
240 | { | |
241 | Throwable last = obj.m_unhandled; | |
242 | while ( last.next !is null ) | |
243 | last = last.next; | |
244 | last.next = t; | |
245 | } | |
246 | } | |
247 | ||
248 | version (D_InlineAsm_X86) | |
249 | { | |
250 | asm nothrow @nogc { fninit; } | |
251 | } | |
252 | ||
253 | try | |
254 | { | |
255 | rt_moduleTlsCtor(); | |
256 | try | |
257 | { | |
258 | obj.run(); | |
259 | } | |
260 | catch ( Throwable t ) | |
261 | { | |
262 | append( t ); | |
263 | } | |
264 | rt_moduleTlsDtor(); | |
265 | } | |
266 | catch ( Throwable t ) | |
267 | { | |
268 | append( t ); | |
269 | } | |
270 | return 0; | |
271 | } | |
272 | ||
273 | ||
274 | HANDLE GetCurrentThreadHandle() nothrow @nogc | |
275 | { | |
276 | const uint DUPLICATE_SAME_ACCESS = 0x00000002; | |
277 | ||
278 | HANDLE curr = GetCurrentThread(), | |
279 | proc = GetCurrentProcess(), | |
280 | hndl; | |
281 | ||
282 | DuplicateHandle( proc, curr, proc, &hndl, 0, TRUE, DUPLICATE_SAME_ACCESS ); | |
283 | return hndl; | |
284 | } | |
285 | } | |
286 | } | |
287 | else version (Posix) | |
288 | { | |
289 | private | |
290 | { | |
291 | import core.stdc.errno; | |
292 | import core.sys.posix.semaphore; | |
293 | import core.sys.posix.stdlib; // for malloc, valloc, free, atexit | |
294 | import core.sys.posix.pthread; | |
295 | import core.sys.posix.signal; | |
296 | import core.sys.posix.time; | |
297 | ||
298 | version (Darwin) | |
299 | { | |
300 | import core.sys.darwin.mach.thread_act; | |
301 | import core.sys.darwin.pthread : pthread_mach_thread_np; | |
302 | } | |
303 | ||
304 | version (GNU) | |
305 | { | |
306 | import gcc.builtins; | |
307 | } | |
308 | ||
309 | // | |
310 | // Entry point for POSIX threads | |
311 | // | |
312 | extern (C) void* thread_entryPoint( void* arg ) nothrow | |
313 | { | |
314 | version (Shared) | |
315 | { | |
316 | import rt.sections; | |
317 | Thread obj = cast(Thread)(cast(void**)arg)[0]; | |
318 | auto loadedLibraries = (cast(void**)arg)[1]; | |
319 | .free(arg); | |
320 | } | |
321 | else | |
322 | { | |
323 | Thread obj = cast(Thread)arg; | |
324 | } | |
325 | assert( obj ); | |
326 | ||
327 | // loadedLibraries need to be inherited from parent thread | |
328 | // before initilizing GC for TLS (rt_tlsgc_init) | |
329 | version (Shared) inheritLoadedLibraries(loadedLibraries); | |
330 | ||
331 | assert( obj.m_curr is &obj.m_main ); | |
332 | obj.m_main.bstack = getStackBottom(); | |
333 | obj.m_main.tstack = obj.m_main.bstack; | |
334 | obj.m_tlsgcdata = rt_tlsgc_init(); | |
335 | ||
336 | atomicStore!(MemoryOrder.raw)(obj.m_isRunning, true); | |
337 | Thread.setThis(obj); // allocates lazy TLS (see Issue 11981) | |
338 | Thread.add(obj); // can only receive signals from here on | |
339 | scope (exit) | |
340 | { | |
341 | Thread.remove(obj); | |
342 | atomicStore!(MemoryOrder.raw)(obj.m_isRunning, false); | |
343 | } | |
344 | Thread.add(&obj.m_main); | |
345 | ||
346 | static extern (C) void thread_cleanupHandler( void* arg ) nothrow @nogc | |
347 | { | |
348 | Thread obj = cast(Thread) arg; | |
349 | assert( obj ); | |
350 | ||
351 | // NOTE: If the thread terminated abnormally, just set it as | |
352 | // not running and let thread_suspendAll remove it from | |
353 | // the thread list. This is safer and is consistent | |
354 | // with the Windows thread code. | |
355 | atomicStore!(MemoryOrder.raw)(obj.m_isRunning,false); | |
356 | } | |
357 | ||
358 | // NOTE: Using void to skip the initialization here relies on | |
359 | // knowledge of how pthread_cleanup is implemented. It may | |
360 | // not be appropriate for all platforms. However, it does | |
361 | // avoid the need to link the pthread module. If any | |
362 | // implementation actually requires default initialization | |
363 | // then pthread_cleanup should be restructured to maintain | |
364 | // the current lack of a link dependency. | |
365 | static if ( __traits( compiles, pthread_cleanup ) ) | |
366 | { | |
367 | pthread_cleanup cleanup = void; | |
368 | cleanup.push( &thread_cleanupHandler, cast(void*) obj ); | |
369 | } | |
370 | else static if ( __traits( compiles, pthread_cleanup_push ) ) | |
371 | { | |
372 | pthread_cleanup_push( &thread_cleanupHandler, cast(void*) obj ); | |
373 | } | |
374 | else | |
375 | { | |
376 | static assert( false, "Platform not supported." ); | |
377 | } | |
378 | ||
379 | // NOTE: No GC allocations may occur until the stack pointers have | |
380 | // been set and Thread.getThis returns a valid reference to | |
381 | // this thread object (this latter condition is not strictly | |
382 | // necessary on Windows but it should be followed for the | |
383 | // sake of consistency). | |
384 | ||
385 | // TODO: Consider putting an auto exception object here (using | |
386 | // alloca) forOutOfMemoryError plus something to track | |
387 | // whether an exception is in-flight? | |
388 | ||
389 | void append( Throwable t ) | |
390 | { | |
391 | if ( obj.m_unhandled is null ) | |
392 | obj.m_unhandled = t; | |
393 | else | |
394 | { | |
395 | Throwable last = obj.m_unhandled; | |
396 | while ( last.next !is null ) | |
397 | last = last.next; | |
398 | last.next = t; | |
399 | } | |
400 | } | |
401 | ||
402 | try | |
403 | { | |
404 | rt_moduleTlsCtor(); | |
405 | try | |
406 | { | |
407 | obj.run(); | |
408 | } | |
409 | catch ( Throwable t ) | |
410 | { | |
411 | append( t ); | |
412 | } | |
413 | rt_moduleTlsDtor(); | |
414 | version (Shared) cleanupLoadedLibraries(); | |
415 | } | |
416 | catch ( Throwable t ) | |
417 | { | |
418 | append( t ); | |
419 | } | |
420 | ||
421 | // NOTE: Normal cleanup is handled by scope(exit). | |
422 | ||
423 | static if ( __traits( compiles, pthread_cleanup ) ) | |
424 | { | |
425 | cleanup.pop( 0 ); | |
426 | } | |
427 | else static if ( __traits( compiles, pthread_cleanup_push ) ) | |
428 | { | |
429 | pthread_cleanup_pop( 0 ); | |
430 | } | |
431 | ||
432 | return null; | |
433 | } | |
434 | ||
435 | ||
436 | // | |
437 | // Used to track the number of suspended threads | |
438 | // | |
439 | __gshared sem_t suspendCount; | |
440 | ||
441 | ||
442 | extern (C) void thread_suspendHandler( int sig ) nothrow | |
443 | in | |
444 | { | |
445 | assert( sig == suspendSignalNumber ); | |
446 | } | |
447 | body | |
448 | { | |
449 | void op(void* sp) nothrow | |
450 | { | |
451 | // NOTE: Since registers are being pushed and popped from the | |
452 | // stack, any other stack data used by this function should | |
453 | // be gone before the stack cleanup code is called below. | |
454 | Thread obj = Thread.getThis(); | |
455 | assert(obj !is null); | |
456 | ||
457 | if ( !obj.m_lock ) | |
458 | { | |
459 | obj.m_curr.tstack = getStackTop(); | |
460 | } | |
461 | ||
462 | sigset_t sigres = void; | |
463 | int status; | |
464 | ||
465 | status = sigfillset( &sigres ); | |
466 | assert( status == 0 ); | |
467 | ||
468 | status = sigdelset( &sigres, resumeSignalNumber ); | |
469 | assert( status == 0 ); | |
470 | ||
471 | version (FreeBSD) obj.m_suspendagain = false; | |
472 | status = sem_post( &suspendCount ); | |
473 | assert( status == 0 ); | |
474 | ||
475 | sigsuspend( &sigres ); | |
476 | ||
477 | if ( !obj.m_lock ) | |
478 | { | |
479 | obj.m_curr.tstack = obj.m_curr.bstack; | |
480 | } | |
481 | } | |
482 | ||
483 | // avoid deadlocks on FreeBSD, see Issue 13416 | |
484 | version (FreeBSD) | |
485 | { | |
486 | auto obj = Thread.getThis(); | |
487 | if (THR_IN_CRITICAL(obj.m_addr)) | |
488 | { | |
489 | obj.m_suspendagain = true; | |
490 | if (sem_post(&suspendCount)) assert(0); | |
491 | return; | |
492 | } | |
493 | } | |
494 | ||
495 | callWithStackShell(&op); | |
496 | } | |
497 | ||
498 | ||
499 | extern (C) void thread_resumeHandler( int sig ) nothrow | |
500 | in | |
501 | { | |
502 | assert( sig == resumeSignalNumber ); | |
503 | } | |
504 | body | |
505 | { | |
506 | ||
507 | } | |
508 | ||
509 | // HACK libthr internal (thr_private.h) macro, used to | |
510 | // avoid deadlocks in signal handler, see Issue 13416 | |
511 | version (FreeBSD) bool THR_IN_CRITICAL(pthread_t p) nothrow @nogc | |
512 | { | |
513 | import core.sys.posix.config : c_long; | |
514 | import core.sys.posix.sys.types : lwpid_t; | |
515 | ||
516 | // If the begin of pthread would be changed in libthr (unlikely) | |
517 | // we'll run into undefined behavior, compare with thr_private.h. | |
518 | static struct pthread | |
519 | { | |
520 | c_long tid; | |
521 | static struct umutex { lwpid_t owner; uint flags; uint[2] ceilings; uint[4] spare; } | |
522 | umutex lock; | |
523 | uint cycle; | |
524 | int locklevel; | |
525 | int critical_count; | |
526 | // ... | |
527 | } | |
528 | auto priv = cast(pthread*)p; | |
529 | return priv.locklevel > 0 || priv.critical_count > 0; | |
530 | } | |
531 | } | |
532 | } | |
533 | else | |
534 | { | |
535 | // NOTE: This is the only place threading versions are checked. If a new | |
536 | // version is added, the module code will need to be searched for | |
537 | // places where version-specific code may be required. This can be | |
538 | // easily accomlished by searching for 'Windows' or 'Posix'. | |
539 | static assert( false, "Unknown threading implementation." ); | |
540 | } | |
541 | ||
542 | ||
543 | /////////////////////////////////////////////////////////////////////////////// | |
544 | // Thread | |
545 | /////////////////////////////////////////////////////////////////////////////// | |
546 | ||
547 | ||
548 | /** | |
549 | * This class encapsulates all threading functionality for the D | |
550 | * programming language. As thread manipulation is a required facility | |
551 | * for garbage collection, all user threads should derive from this | |
552 | * class, and instances of this class should never be explicitly deleted. | |
553 | * A new thread may be created using either derivation or composition, as | |
554 | * in the following example. | |
555 | */ | |
556 | class Thread | |
557 | { | |
558 | /////////////////////////////////////////////////////////////////////////// | |
559 | // Initialization | |
560 | /////////////////////////////////////////////////////////////////////////// | |
561 | ||
562 | ||
563 | /** | |
564 | * Initializes a thread object which is associated with a static | |
565 | * D function. | |
566 | * | |
567 | * Params: | |
568 | * fn = The thread function. | |
569 | * sz = The stack size for this thread. | |
570 | * | |
571 | * In: | |
572 | * fn must not be null. | |
573 | */ | |
574 | this( void function() fn, size_t sz = 0 ) @safe pure nothrow @nogc | |
575 | in | |
576 | { | |
577 | assert( fn ); | |
578 | } | |
579 | body | |
580 | { | |
581 | this(sz); | |
582 | () @trusted { m_fn = fn; }(); | |
583 | m_call = Call.FN; | |
584 | m_curr = &m_main; | |
585 | } | |
586 | ||
587 | ||
588 | /** | |
589 | * Initializes a thread object which is associated with a dynamic | |
590 | * D function. | |
591 | * | |
592 | * Params: | |
593 | * dg = The thread function. | |
594 | * sz = The stack size for this thread. | |
595 | * | |
596 | * In: | |
597 | * dg must not be null. | |
598 | */ | |
599 | this( void delegate() dg, size_t sz = 0 ) @safe pure nothrow @nogc | |
600 | in | |
601 | { | |
602 | assert( dg ); | |
603 | } | |
604 | body | |
605 | { | |
606 | this(sz); | |
607 | () @trusted { m_dg = dg; }(); | |
608 | m_call = Call.DG; | |
609 | m_curr = &m_main; | |
610 | } | |
611 | ||
612 | ||
613 | /** | |
614 | * Cleans up any remaining resources used by this object. | |
615 | */ | |
616 | ~this() nothrow @nogc | |
617 | { | |
618 | if ( m_addr == m_addr.init ) | |
619 | { | |
620 | return; | |
621 | } | |
622 | ||
623 | version (Windows) | |
624 | { | |
625 | m_addr = m_addr.init; | |
626 | CloseHandle( m_hndl ); | |
627 | m_hndl = m_hndl.init; | |
628 | } | |
629 | else version (Posix) | |
630 | { | |
631 | pthread_detach( m_addr ); | |
632 | m_addr = m_addr.init; | |
633 | } | |
634 | version (Darwin) | |
635 | { | |
636 | m_tmach = m_tmach.init; | |
637 | } | |
638 | rt_tlsgc_destroy( m_tlsgcdata ); | |
639 | m_tlsgcdata = null; | |
640 | } | |
641 | ||
642 | ||
643 | /////////////////////////////////////////////////////////////////////////// | |
644 | // General Actions | |
645 | /////////////////////////////////////////////////////////////////////////// | |
646 | ||
647 | ||
648 | /** | |
649 | * Starts the thread and invokes the function or delegate passed upon | |
650 | * construction. | |
651 | * | |
652 | * In: | |
653 | * This routine may only be called once per thread instance. | |
654 | * | |
655 | * Throws: | |
656 | * ThreadException if the thread fails to start. | |
657 | */ | |
658 | final Thread start() nothrow | |
659 | in | |
660 | { | |
661 | assert( !next && !prev ); | |
662 | } | |
663 | body | |
664 | { | |
665 | auto wasThreaded = multiThreadedFlag; | |
666 | multiThreadedFlag = true; | |
667 | scope( failure ) | |
668 | { | |
669 | if ( !wasThreaded ) | |
670 | multiThreadedFlag = false; | |
671 | } | |
672 | ||
673 | version (Windows) {} else | |
674 | version (Posix) | |
675 | { | |
676 | pthread_attr_t attr; | |
677 | ||
678 | if ( pthread_attr_init( &attr ) ) | |
679 | onThreadError( "Error initializing thread attributes" ); | |
680 | if ( m_sz && pthread_attr_setstacksize( &attr, m_sz ) ) | |
681 | onThreadError( "Error initializing thread stack size" ); | |
682 | } | |
683 | ||
684 | version (Windows) | |
685 | { | |
686 | // NOTE: If a thread is just executing DllMain() | |
687 | // while another thread is started here, it holds an OS internal | |
688 | // lock that serializes DllMain with CreateThread. As the code | |
689 | // might request a synchronization on slock (e.g. in thread_findByAddr()), | |
690 | // we cannot hold that lock while creating the thread without | |
691 | // creating a deadlock | |
692 | // | |
693 | // Solution: Create the thread in suspended state and then | |
694 | // add and resume it with slock acquired | |
695 | assert(m_sz <= uint.max, "m_sz must be less than or equal to uint.max"); | |
696 | m_hndl = cast(HANDLE) _beginthreadex( null, cast(uint) m_sz, &thread_entryPoint, cast(void*) this, CREATE_SUSPENDED, &m_addr ); | |
697 | if ( cast(size_t) m_hndl == 0 ) | |
698 | onThreadError( "Error creating thread" ); | |
699 | } | |
700 | ||
701 | slock.lock_nothrow(); | |
702 | scope(exit) slock.unlock_nothrow(); | |
703 | { | |
704 | ++nAboutToStart; | |
705 | pAboutToStart = cast(Thread*)realloc(pAboutToStart, Thread.sizeof * nAboutToStart); | |
706 | pAboutToStart[nAboutToStart - 1] = this; | |
707 | version (Windows) | |
708 | { | |
709 | if ( ResumeThread( m_hndl ) == -1 ) | |
710 | onThreadError( "Error resuming thread" ); | |
711 | } | |
712 | else version (Posix) | |
713 | { | |
714 | // NOTE: This is also set to true by thread_entryPoint, but set it | |
715 | // here as well so the calling thread will see the isRunning | |
716 | // state immediately. | |
717 | atomicStore!(MemoryOrder.raw)(m_isRunning, true); | |
718 | scope( failure ) atomicStore!(MemoryOrder.raw)(m_isRunning, false); | |
719 | ||
720 | version (Shared) | |
721 | { | |
722 | import rt.sections; | |
723 | auto libs = pinLoadedLibraries(); | |
724 | auto ps = cast(void**).malloc(2 * size_t.sizeof); | |
725 | if (ps is null) onOutOfMemoryError(); | |
726 | ps[0] = cast(void*)this; | |
727 | ps[1] = cast(void*)libs; | |
728 | if ( pthread_create( &m_addr, &attr, &thread_entryPoint, ps ) != 0 ) | |
729 | { | |
730 | unpinLoadedLibraries(libs); | |
731 | .free(ps); | |
732 | onThreadError( "Error creating thread" ); | |
733 | } | |
734 | } | |
735 | else | |
736 | { | |
737 | if ( pthread_create( &m_addr, &attr, &thread_entryPoint, cast(void*) this ) != 0 ) | |
738 | onThreadError( "Error creating thread" ); | |
739 | } | |
740 | } | |
741 | version (Darwin) | |
742 | { | |
743 | m_tmach = pthread_mach_thread_np( m_addr ); | |
744 | if ( m_tmach == m_tmach.init ) | |
745 | onThreadError( "Error creating thread" ); | |
746 | } | |
747 | ||
748 | return this; | |
749 | } | |
750 | } | |
751 | ||
752 | /** | |
753 | * Waits for this thread to complete. If the thread terminated as the | |
754 | * result of an unhandled exception, this exception will be rethrown. | |
755 | * | |
756 | * Params: | |
757 | * rethrow = Rethrow any unhandled exception which may have caused this | |
758 | * thread to terminate. | |
759 | * | |
760 | * Throws: | |
761 | * ThreadException if the operation fails. | |
762 | * Any exception not handled by the joined thread. | |
763 | * | |
764 | * Returns: | |
765 | * Any exception not handled by this thread if rethrow = false, null | |
766 | * otherwise. | |
767 | */ | |
768 | final Throwable join( bool rethrow = true ) | |
769 | { | |
770 | version (Windows) | |
771 | { | |
772 | if ( WaitForSingleObject( m_hndl, INFINITE ) != WAIT_OBJECT_0 ) | |
773 | throw new ThreadException( "Unable to join thread" ); | |
774 | // NOTE: m_addr must be cleared before m_hndl is closed to avoid | |
775 | // a race condition with isRunning. The operation is done | |
776 | // with atomicStore to prevent compiler reordering. | |
777 | atomicStore!(MemoryOrder.raw)(*cast(shared)&m_addr, m_addr.init); | |
778 | CloseHandle( m_hndl ); | |
779 | m_hndl = m_hndl.init; | |
780 | } | |
781 | else version (Posix) | |
782 | { | |
783 | if ( pthread_join( m_addr, null ) != 0 ) | |
784 | throw new ThreadException( "Unable to join thread" ); | |
785 | // NOTE: pthread_join acts as a substitute for pthread_detach, | |
786 | // which is normally called by the dtor. Setting m_addr | |
787 | // to zero ensures that pthread_detach will not be called | |
788 | // on object destruction. | |
789 | m_addr = m_addr.init; | |
790 | } | |
791 | if ( m_unhandled ) | |
792 | { | |
793 | if ( rethrow ) | |
794 | throw m_unhandled; | |
795 | return m_unhandled; | |
796 | } | |
797 | return null; | |
798 | } | |
799 | ||
800 | ||
801 | /////////////////////////////////////////////////////////////////////////// | |
802 | // General Properties | |
803 | /////////////////////////////////////////////////////////////////////////// | |
804 | ||
805 | ||
806 | /** | |
807 | * Gets the OS identifier for this thread. | |
808 | * | |
809 | * Returns: | |
810 | * If the thread hasn't been started yet, returns $(LREF ThreadID)$(D.init). | |
811 | * Otherwise, returns the result of $(D GetCurrentThreadId) on Windows, | |
812 | * and $(D pthread_self) on POSIX. | |
813 | * | |
814 | * The value is unique for the current process. | |
815 | */ | |
816 | final @property ThreadID id() @safe @nogc | |
817 | { | |
818 | synchronized( this ) | |
819 | { | |
820 | return m_addr; | |
821 | } | |
822 | } | |
823 | ||
824 | ||
825 | /** | |
826 | * Gets the user-readable label for this thread. | |
827 | * | |
828 | * Returns: | |
829 | * The name of this thread. | |
830 | */ | |
831 | final @property string name() @safe @nogc | |
832 | { | |
833 | synchronized( this ) | |
834 | { | |
835 | return m_name; | |
836 | } | |
837 | } | |
838 | ||
839 | ||
840 | /** | |
841 | * Sets the user-readable label for this thread. | |
842 | * | |
843 | * Params: | |
844 | * val = The new name of this thread. | |
845 | */ | |
846 | final @property void name( string val ) @safe @nogc | |
847 | { | |
848 | synchronized( this ) | |
849 | { | |
850 | m_name = val; | |
851 | } | |
852 | } | |
853 | ||
854 | ||
855 | /** | |
856 | * Gets the daemon status for this thread. While the runtime will wait for | |
857 | * all normal threads to complete before tearing down the process, daemon | |
858 | * threads are effectively ignored and thus will not prevent the process | |
859 | * from terminating. In effect, daemon threads will be terminated | |
860 | * automatically by the OS when the process exits. | |
861 | * | |
862 | * Returns: | |
863 | * true if this is a daemon thread. | |
864 | */ | |
865 | final @property bool isDaemon() @safe @nogc | |
866 | { | |
867 | synchronized( this ) | |
868 | { | |
869 | return m_isDaemon; | |
870 | } | |
871 | } | |
872 | ||
873 | ||
874 | /** | |
875 | * Sets the daemon status for this thread. While the runtime will wait for | |
876 | * all normal threads to complete before tearing down the process, daemon | |
877 | * threads are effectively ignored and thus will not prevent the process | |
878 | * from terminating. In effect, daemon threads will be terminated | |
879 | * automatically by the OS when the process exits. | |
880 | * | |
881 | * Params: | |
882 | * val = The new daemon status for this thread. | |
883 | */ | |
884 | final @property void isDaemon( bool val ) @safe @nogc | |
885 | { | |
886 | synchronized( this ) | |
887 | { | |
888 | m_isDaemon = val; | |
889 | } | |
890 | } | |
891 | ||
892 | ||
893 | /** | |
894 | * Tests whether this thread is running. | |
895 | * | |
896 | * Returns: | |
897 | * true if the thread is running, false if not. | |
898 | */ | |
899 | final @property bool isRunning() nothrow @nogc | |
900 | { | |
901 | if ( m_addr == m_addr.init ) | |
902 | { | |
903 | return false; | |
904 | } | |
905 | ||
906 | version (Windows) | |
907 | { | |
908 | uint ecode = 0; | |
909 | GetExitCodeThread( m_hndl, &ecode ); | |
910 | return ecode == STILL_ACTIVE; | |
911 | } | |
912 | else version (Posix) | |
913 | { | |
914 | return atomicLoad(m_isRunning); | |
915 | } | |
916 | } | |
917 | ||
918 | ||
919 | /////////////////////////////////////////////////////////////////////////// | |
920 | // Thread Priority Actions | |
921 | /////////////////////////////////////////////////////////////////////////// | |
922 | ||
923 | version (Windows) | |
924 | { | |
925 | @property static int PRIORITY_MIN() @nogc nothrow pure @safe | |
926 | { | |
927 | return THREAD_PRIORITY_IDLE; | |
928 | } | |
929 | ||
930 | @property static const(int) PRIORITY_MAX() @nogc nothrow pure @safe | |
931 | { | |
932 | return THREAD_PRIORITY_TIME_CRITICAL; | |
933 | } | |
934 | ||
935 | @property static int PRIORITY_DEFAULT() @nogc nothrow pure @safe | |
936 | { | |
937 | return THREAD_PRIORITY_NORMAL; | |
938 | } | |
939 | } | |
940 | else | |
941 | { | |
942 | private struct Priority | |
943 | { | |
944 | int PRIORITY_MIN = int.min; | |
945 | int PRIORITY_DEFAULT = int.min; | |
946 | int PRIORITY_MAX = int.min; | |
947 | } | |
948 | ||
949 | /* | |
950 | Lazily loads one of the members stored in a hidden global variable of | |
951 | type `Priority`. Upon the first access of either member, the entire | |
952 | `Priority` structure is initialized. Multiple initializations from | |
953 | different threads calling this function are tolerated. | |
954 | ||
955 | `which` must be one of `PRIORITY_MIN`, `PRIORITY_DEFAULT`, | |
956 | `PRIORITY_MAX`. | |
957 | */ | |
958 | private static int loadGlobal(string which)() | |
959 | { | |
960 | static shared Priority cache; | |
961 | auto local = atomicLoad(mixin("cache." ~ which)); | |
962 | if (local != local.min) return local; | |
963 | // There will be benign races | |
964 | cache = loadPriorities; | |
965 | return atomicLoad(mixin("cache." ~ which)); | |
966 | } | |
967 | ||
968 | /* | |
969 | Loads all priorities and returns them as a `Priority` structure. This | |
970 | function is thread-neutral. | |
971 | */ | |
972 | private static Priority loadPriorities() @nogc nothrow @trusted | |
973 | { | |
974 | Priority result; | |
975 | version (Solaris) | |
976 | { | |
977 | pcparms_t pcParms; | |
978 | pcinfo_t pcInfo; | |
979 | ||
980 | pcParms.pc_cid = PC_CLNULL; | |
981 | if (priocntl(idtype_t.P_PID, P_MYID, PC_GETPARMS, &pcParms) == -1) | |
982 | assert( 0, "Unable to get scheduling class" ); | |
983 | ||
984 | pcInfo.pc_cid = pcParms.pc_cid; | |
985 | // PC_GETCLINFO ignores the first two args, use dummy values | |
986 | if (priocntl(idtype_t.P_PID, 0, PC_GETCLINFO, &pcInfo) == -1) | |
987 | assert( 0, "Unable to get scheduling class info" ); | |
988 | ||
989 | pri_t* clparms = cast(pri_t*)&pcParms.pc_clparms; | |
990 | pri_t* clinfo = cast(pri_t*)&pcInfo.pc_clinfo; | |
991 | ||
992 | result.PRIORITY_MAX = clparms[0]; | |
993 | ||
994 | if (pcInfo.pc_clname == "RT") | |
995 | { | |
996 | m_isRTClass = true; | |
997 | ||
998 | // For RT class, just assume it can't be changed | |
999 | result.PRIORITY_MIN = clparms[0]; | |
1000 | result.PRIORITY_DEFAULT = clparms[0]; | |
1001 | } | |
1002 | else | |
1003 | { | |
1004 | m_isRTClass = false; | |
1005 | ||
1006 | // For all other scheduling classes, there are | |
1007 | // two key values -- uprilim and maxupri. | |
1008 | // maxupri is the maximum possible priority defined | |
1009 | // for the scheduling class, and valid priorities | |
1010 | // range are in [-maxupri, maxupri]. | |
1011 | // | |
1012 | // However, uprilim is an upper limit that the | |
1013 | // current thread can set for the current scheduling | |
1014 | // class, which can be less than maxupri. As such, | |
1015 | // use this value for priorityMax since this is | |
1016 | // the effective maximum. | |
1017 | ||
1018 | // maxupri | |
1019 | result.PRIORITY_MIN = -clinfo[0]; | |
1020 | // by definition | |
1021 | result.PRIORITY_DEFAULT = 0; | |
1022 | } | |
1023 | } | |
1024 | else version (Posix) | |
1025 | { | |
1026 | int policy; | |
1027 | sched_param param; | |
1028 | pthread_getschedparam( pthread_self(), &policy, ¶m ) == 0 | |
1029 | || assert(0, "Internal error in pthread_getschedparam"); | |
1030 | ||
1031 | result.PRIORITY_MIN = sched_get_priority_min( policy ); | |
1032 | result.PRIORITY_MIN != -1 | |
1033 | || assert(0, "Internal error in sched_get_priority_min"); | |
1034 | result.PRIORITY_DEFAULT = param.sched_priority; | |
1035 | result.PRIORITY_MAX = sched_get_priority_max( policy ); | |
1036 | result.PRIORITY_MAX != -1 || | |
1037 | assert(0, "Internal error in sched_get_priority_max"); | |
1038 | } | |
1039 | else | |
1040 | { | |
1041 | static assert(0, "Your code here."); | |
1042 | } | |
1043 | return result; | |
1044 | } | |
1045 | ||
1046 | /** | |
1047 | * The minimum scheduling priority that may be set for a thread. On | |
1048 | * systems where multiple scheduling policies are defined, this value | |
1049 | * represents the minimum valid priority for the scheduling policy of | |
1050 | * the process. | |
1051 | */ | |
1052 | @property static int PRIORITY_MIN() @nogc nothrow pure @trusted | |
1053 | { | |
1054 | return (cast(int function() @nogc nothrow pure @safe) | |
1055 | &loadGlobal!"PRIORITY_MIN")(); | |
1056 | } | |
1057 | ||
1058 | /** | |
1059 | * The maximum scheduling priority that may be set for a thread. On | |
1060 | * systems where multiple scheduling policies are defined, this value | |
1061 | * represents the maximum valid priority for the scheduling policy of | |
1062 | * the process. | |
1063 | */ | |
1064 | @property static const(int) PRIORITY_MAX() @nogc nothrow pure @trusted | |
1065 | { | |
1066 | return (cast(int function() @nogc nothrow pure @safe) | |
1067 | &loadGlobal!"PRIORITY_MAX")(); | |
1068 | } | |
1069 | ||
1070 | /** | |
1071 | * The default scheduling priority that is set for a thread. On | |
1072 | * systems where multiple scheduling policies are defined, this value | |
1073 | * represents the default priority for the scheduling policy of | |
1074 | * the process. | |
1075 | */ | |
1076 | @property static int PRIORITY_DEFAULT() @nogc nothrow pure @trusted | |
1077 | { | |
1078 | return (cast(int function() @nogc nothrow pure @safe) | |
1079 | &loadGlobal!"PRIORITY_DEFAULT")(); | |
1080 | } | |
1081 | } | |
1082 | ||
1083 | version (NetBSD) | |
1084 | { | |
1085 | //NetBSD does not support priority for default policy | |
1086 | // and it is not possible change policy without root access | |
1087 | int fakePriority = int.max; | |
1088 | } | |
1089 | ||
1090 | /** | |
1091 | * Gets the scheduling priority for the associated thread. | |
1092 | * | |
1093 | * Note: Getting the priority of a thread that already terminated | |
1094 | * might return the default priority. | |
1095 | * | |
1096 | * Returns: | |
1097 | * The scheduling priority of this thread. | |
1098 | */ | |
1099 | final @property int priority() | |
1100 | { | |
1101 | version (Windows) | |
1102 | { | |
1103 | return GetThreadPriority( m_hndl ); | |
1104 | } | |
1105 | else version (NetBSD) | |
1106 | { | |
1107 | return fakePriority==int.max? PRIORITY_DEFAULT : fakePriority; | |
1108 | } | |
1109 | else version (Posix) | |
1110 | { | |
1111 | int policy; | |
1112 | sched_param param; | |
1113 | ||
1114 | if (auto err = pthread_getschedparam(m_addr, &policy, ¶m)) | |
1115 | { | |
1116 | // ignore error if thread is not running => Bugzilla 8960 | |
1117 | if (!atomicLoad(m_isRunning)) return PRIORITY_DEFAULT; | |
1118 | throw new ThreadException("Unable to get thread priority"); | |
1119 | } | |
1120 | return param.sched_priority; | |
1121 | } | |
1122 | } | |
1123 | ||
1124 | ||
1125 | /** | |
1126 | * Sets the scheduling priority for the associated thread. | |
1127 | * | |
1128 | * Note: Setting the priority of a thread that already terminated | |
1129 | * might have no effect. | |
1130 | * | |
1131 | * Params: | |
1132 | * val = The new scheduling priority of this thread. | |
1133 | */ | |
1134 | final @property void priority( int val ) | |
1135 | in | |
1136 | { | |
1137 | assert(val >= PRIORITY_MIN); | |
1138 | assert(val <= PRIORITY_MAX); | |
1139 | } | |
1140 | body | |
1141 | { | |
1142 | version (Windows) | |
1143 | { | |
1144 | if ( !SetThreadPriority( m_hndl, val ) ) | |
1145 | throw new ThreadException( "Unable to set thread priority" ); | |
1146 | } | |
1147 | else version (Solaris) | |
1148 | { | |
1149 | // the pthread_setschedprio(3c) and pthread_setschedparam functions | |
1150 | // are broken for the default (TS / time sharing) scheduling class. | |
1151 | // instead, we use priocntl(2) which gives us the desired behavior. | |
1152 | ||
1153 | // We hardcode the min and max priorities to the current value | |
1154 | // so this is a no-op for RT threads. | |
1155 | if (m_isRTClass) | |
1156 | return; | |
1157 | ||
1158 | pcparms_t pcparm; | |
1159 | ||
1160 | pcparm.pc_cid = PC_CLNULL; | |
1161 | if (priocntl(idtype_t.P_LWPID, P_MYID, PC_GETPARMS, &pcparm) == -1) | |
1162 | throw new ThreadException( "Unable to get scheduling class" ); | |
1163 | ||
1164 | pri_t* clparms = cast(pri_t*)&pcparm.pc_clparms; | |
1165 | ||
1166 | // clparms is filled in by the PC_GETPARMS call, only necessary | |
1167 | // to adjust the element that contains the thread priority | |
1168 | clparms[1] = cast(pri_t) val; | |
1169 | ||
1170 | if (priocntl(idtype_t.P_LWPID, P_MYID, PC_SETPARMS, &pcparm) == -1) | |
1171 | throw new ThreadException( "Unable to set scheduling class" ); | |
1172 | } | |
1173 | else version (NetBSD) | |
1174 | { | |
1175 | fakePriority = val; | |
1176 | } | |
1177 | else version (Posix) | |
1178 | { | |
1179 | static if (__traits(compiles, pthread_setschedprio)) | |
1180 | { | |
1181 | if (auto err = pthread_setschedprio(m_addr, val)) | |
1182 | { | |
1183 | // ignore error if thread is not running => Bugzilla 8960 | |
1184 | if (!atomicLoad(m_isRunning)) return; | |
1185 | throw new ThreadException("Unable to set thread priority"); | |
1186 | } | |
1187 | } | |
1188 | else | |
1189 | { | |
1190 | // NOTE: pthread_setschedprio is not implemented on Darwin or FreeBSD, so use | |
1191 | // the more complicated get/set sequence below. | |
1192 | int policy; | |
1193 | sched_param param; | |
1194 | ||
1195 | if (auto err = pthread_getschedparam(m_addr, &policy, ¶m)) | |
1196 | { | |
1197 | // ignore error if thread is not running => Bugzilla 8960 | |
1198 | if (!atomicLoad(m_isRunning)) return; | |
1199 | throw new ThreadException("Unable to set thread priority"); | |
1200 | } | |
1201 | param.sched_priority = val; | |
1202 | if (auto err = pthread_setschedparam(m_addr, policy, ¶m)) | |
1203 | { | |
1204 | // ignore error if thread is not running => Bugzilla 8960 | |
1205 | if (!atomicLoad(m_isRunning)) return; | |
1206 | throw new ThreadException("Unable to set thread priority"); | |
1207 | } | |
1208 | } | |
1209 | } | |
1210 | } | |
1211 | ||
1212 | ||
1213 | unittest | |
1214 | { | |
1215 | auto thr = Thread.getThis(); | |
1216 | immutable prio = thr.priority; | |
1217 | scope (exit) thr.priority = prio; | |
1218 | ||
1219 | assert(prio == PRIORITY_DEFAULT); | |
1220 | assert(prio >= PRIORITY_MIN && prio <= PRIORITY_MAX); | |
1221 | thr.priority = PRIORITY_MIN; | |
1222 | assert(thr.priority == PRIORITY_MIN); | |
1223 | thr.priority = PRIORITY_MAX; | |
1224 | assert(thr.priority == PRIORITY_MAX); | |
1225 | } | |
1226 | ||
1227 | unittest // Bugzilla 8960 | |
1228 | { | |
1229 | import core.sync.semaphore; | |
1230 | ||
1231 | auto thr = new Thread({}); | |
1232 | thr.start(); | |
1233 | Thread.sleep(1.msecs); // wait a little so the thread likely has finished | |
1234 | thr.priority = PRIORITY_MAX; // setting priority doesn't cause error | |
1235 | auto prio = thr.priority; // getting priority doesn't cause error | |
1236 | assert(prio >= PRIORITY_MIN && prio <= PRIORITY_MAX); | |
1237 | } | |
1238 | ||
1239 | /////////////////////////////////////////////////////////////////////////// | |
1240 | // Actions on Calling Thread | |
1241 | /////////////////////////////////////////////////////////////////////////// | |
1242 | ||
1243 | ||
1244 | /** | |
1245 | * Suspends the calling thread for at least the supplied period. This may | |
1246 | * result in multiple OS calls if period is greater than the maximum sleep | |
1247 | * duration supported by the operating system. | |
1248 | * | |
1249 | * Params: | |
1250 | * val = The minimum duration the calling thread should be suspended. | |
1251 | * | |
1252 | * In: | |
1253 | * period must be non-negative. | |
1254 | * | |
1255 | * Example: | |
1256 | * ------------------------------------------------------------------------ | |
1257 | * | |
1258 | * Thread.sleep( dur!("msecs")( 50 ) ); // sleep for 50 milliseconds | |
1259 | * Thread.sleep( dur!("seconds")( 5 ) ); // sleep for 5 seconds | |
1260 | * | |
1261 | * ------------------------------------------------------------------------ | |
1262 | */ | |
1263 | static void sleep( Duration val ) @nogc nothrow | |
1264 | in | |
1265 | { | |
1266 | assert( !val.isNegative ); | |
1267 | } | |
1268 | body | |
1269 | { | |
1270 | version (Windows) | |
1271 | { | |
1272 | auto maxSleepMillis = dur!("msecs")( uint.max - 1 ); | |
1273 | ||
1274 | // avoid a non-zero time to be round down to 0 | |
1275 | if ( val > dur!"msecs"( 0 ) && val < dur!"msecs"( 1 ) ) | |
1276 | val = dur!"msecs"( 1 ); | |
1277 | ||
1278 | // NOTE: In instances where all other threads in the process have a | |
1279 | // lower priority than the current thread, the current thread | |
1280 | // will not yield with a sleep time of zero. However, unlike | |
1281 | // yield(), the user is not asking for a yield to occur but | |
1282 | // only for execution to suspend for the requested interval. | |
1283 | // Therefore, expected performance may not be met if a yield | |
1284 | // is forced upon the user. | |
1285 | while ( val > maxSleepMillis ) | |
1286 | { | |
1287 | Sleep( cast(uint) | |
1288 | maxSleepMillis.total!"msecs" ); | |
1289 | val -= maxSleepMillis; | |
1290 | } | |
1291 | Sleep( cast(uint) val.total!"msecs" ); | |
1292 | } | |
1293 | else version (Posix) | |
1294 | { | |
1295 | timespec tin = void; | |
1296 | timespec tout = void; | |
1297 | ||
1298 | val.split!("seconds", "nsecs")(tin.tv_sec, tin.tv_nsec); | |
1299 | if ( val.total!"seconds" > tin.tv_sec.max ) | |
1300 | tin.tv_sec = tin.tv_sec.max; | |
1301 | while ( true ) | |
1302 | { | |
1303 | if ( !nanosleep( &tin, &tout ) ) | |
1304 | return; | |
1305 | if ( errno != EINTR ) | |
1306 | assert(0, "Unable to sleep for the specified duration"); | |
1307 | tin = tout; | |
1308 | } | |
1309 | } | |
1310 | } | |
1311 | ||
1312 | ||
1313 | /** | |
1314 | * Forces a context switch to occur away from the calling thread. | |
1315 | */ | |
1316 | static void yield() @nogc nothrow | |
1317 | { | |
1318 | version (Windows) | |
1319 | SwitchToThread(); | |
1320 | else version (Posix) | |
1321 | sched_yield(); | |
1322 | } | |
1323 | ||
1324 | ||
1325 | /////////////////////////////////////////////////////////////////////////// | |
1326 | // Thread Accessors | |
1327 | /////////////////////////////////////////////////////////////////////////// | |
1328 | ||
1329 | /** | |
1330 | * Provides a reference to the calling thread. | |
1331 | * | |
1332 | * Returns: | |
1333 | * The thread object representing the calling thread. The result of | |
1334 | * deleting this object is undefined. If the current thread is not | |
1335 | * attached to the runtime, a null reference is returned. | |
1336 | */ | |
1337 | static Thread getThis() @safe nothrow @nogc | |
1338 | { | |
1339 | // NOTE: This function may not be called until thread_init has | |
1340 | // completed. See thread_suspendAll for more information | |
1341 | // on why this might occur. | |
1342 | return sm_this; | |
1343 | } | |
1344 | ||
1345 | ||
1346 | /** | |
1347 | * Provides a list of all threads currently being tracked by the system. | |
1348 | * Note that threads in the returned array might no longer run (see | |
1349 | * $(D Thread.)$(LREF isRunning)). | |
1350 | * | |
1351 | * Returns: | |
1352 | * An array containing references to all threads currently being | |
1353 | * tracked by the system. The result of deleting any contained | |
1354 | * objects is undefined. | |
1355 | */ | |
1356 | static Thread[] getAll() | |
1357 | { | |
1358 | static void resize(ref Thread[] buf, size_t nlen) | |
1359 | { | |
1360 | buf.length = nlen; | |
1361 | } | |
1362 | return getAllImpl!resize(); | |
1363 | } | |
1364 | ||
1365 | ||
1366 | /** | |
1367 | * Operates on all threads currently being tracked by the system. The | |
1368 | * result of deleting any Thread object is undefined. | |
1369 | * Note that threads passed to the callback might no longer run (see | |
1370 | * $(D Thread.)$(LREF isRunning)). | |
1371 | * | |
1372 | * Params: | |
1373 | * dg = The supplied code as a delegate. | |
1374 | * | |
1375 | * Returns: | |
1376 | * Zero if all elemented are visited, nonzero if not. | |
1377 | */ | |
1378 | static int opApply(scope int delegate(ref Thread) dg) | |
1379 | { | |
1380 | import core.stdc.stdlib : free, realloc; | |
1381 | ||
1382 | static void resize(ref Thread[] buf, size_t nlen) | |
1383 | { | |
1384 | buf = (cast(Thread*)realloc(buf.ptr, nlen * Thread.sizeof))[0 .. nlen]; | |
1385 | } | |
1386 | auto buf = getAllImpl!resize; | |
1387 | scope(exit) if (buf.ptr) free(buf.ptr); | |
1388 | ||
1389 | foreach (t; buf) | |
1390 | { | |
1391 | if (auto res = dg(t)) | |
1392 | return res; | |
1393 | } | |
1394 | return 0; | |
1395 | } | |
1396 | ||
1397 | unittest | |
1398 | { | |
1399 | auto t1 = new Thread({ | |
1400 | foreach (_; 0 .. 20) | |
1401 | Thread.getAll; | |
1402 | }).start; | |
1403 | auto t2 = new Thread({ | |
1404 | foreach (_; 0 .. 20) | |
1405 | GC.collect; | |
1406 | }).start; | |
1407 | t1.join(); | |
1408 | t2.join(); | |
1409 | } | |
1410 | ||
1411 | private static Thread[] getAllImpl(alias resize)() | |
1412 | { | |
1413 | import core.atomic; | |
1414 | ||
1415 | Thread[] buf; | |
1416 | while (true) | |
1417 | { | |
1418 | immutable len = atomicLoad!(MemoryOrder.raw)(*cast(shared)&sm_tlen); | |
1419 | resize(buf, len); | |
1420 | assert(buf.length == len); | |
1421 | synchronized (slock) | |
1422 | { | |
1423 | if (len == sm_tlen) | |
1424 | { | |
1425 | size_t pos; | |
1426 | for (Thread t = sm_tbeg; t; t = t.next) | |
1427 | buf[pos++] = t; | |
1428 | return buf; | |
1429 | } | |
1430 | } | |
1431 | } | |
1432 | } | |
1433 | ||
1434 | /////////////////////////////////////////////////////////////////////////// | |
1435 | // Stuff That Should Go Away | |
1436 | /////////////////////////////////////////////////////////////////////////// | |
1437 | ||
1438 | ||
1439 | private: | |
1440 | // | |
1441 | // Initializes a thread object which has no associated executable function. | |
1442 | // This is used for the main thread initialized in thread_init(). | |
1443 | // | |
1444 | this(size_t sz = 0) @safe pure nothrow @nogc | |
1445 | { | |
1446 | if (sz) | |
1447 | { | |
1448 | version (Posix) | |
1449 | { | |
1450 | // stack size must be a multiple of PAGESIZE | |
1451 | sz += PAGESIZE - 1; | |
1452 | sz -= sz % PAGESIZE; | |
1453 | // and at least PTHREAD_STACK_MIN | |
1454 | if (PTHREAD_STACK_MIN > sz) | |
1455 | sz = PTHREAD_STACK_MIN; | |
1456 | } | |
1457 | m_sz = sz; | |
1458 | } | |
1459 | m_call = Call.NO; | |
1460 | m_curr = &m_main; | |
1461 | } | |
1462 | ||
1463 | ||
1464 | // | |
1465 | // Thread entry point. Invokes the function or delegate passed on | |
1466 | // construction (if any). | |
1467 | // | |
1468 | final void run() | |
1469 | { | |
1470 | switch ( m_call ) | |
1471 | { | |
1472 | case Call.FN: | |
1473 | m_fn(); | |
1474 | break; | |
1475 | case Call.DG: | |
1476 | m_dg(); | |
1477 | break; | |
1478 | default: | |
1479 | break; | |
1480 | } | |
1481 | } | |
1482 | ||
1483 | ||
1484 | private: | |
1485 | // | |
1486 | // The type of routine passed on thread construction. | |
1487 | // | |
1488 | enum Call | |
1489 | { | |
1490 | NO, | |
1491 | FN, | |
1492 | DG | |
1493 | } | |
1494 | ||
1495 | ||
1496 | // | |
1497 | // Standard types | |
1498 | // | |
1499 | version (Windows) | |
1500 | { | |
1501 | alias TLSKey = uint; | |
1502 | } | |
1503 | else version (Posix) | |
1504 | { | |
1505 | alias TLSKey = pthread_key_t; | |
1506 | } | |
1507 | ||
1508 | ||
1509 | // | |
1510 | // Local storage | |
1511 | // | |
1512 | static Thread sm_this; | |
1513 | ||
1514 | ||
1515 | // | |
1516 | // Main process thread | |
1517 | // | |
1518 | __gshared Thread sm_main; | |
1519 | ||
1520 | version (FreeBSD) | |
1521 | { | |
1522 | // set when suspend failed and should be retried, see Issue 13416 | |
1523 | shared bool m_suspendagain; | |
1524 | } | |
1525 | ||
1526 | ||
1527 | // | |
1528 | // Standard thread data | |
1529 | // | |
1530 | version (Windows) | |
1531 | { | |
1532 | HANDLE m_hndl; | |
1533 | } | |
1534 | else version (Darwin) | |
1535 | { | |
1536 | mach_port_t m_tmach; | |
1537 | } | |
1538 | ThreadID m_addr; | |
1539 | Call m_call; | |
1540 | string m_name; | |
1541 | union | |
1542 | { | |
1543 | void function() m_fn; | |
1544 | void delegate() m_dg; | |
1545 | } | |
1546 | size_t m_sz; | |
1547 | version (Posix) | |
1548 | { | |
1549 | shared bool m_isRunning; | |
1550 | } | |
1551 | bool m_isDaemon; | |
1552 | bool m_isInCriticalRegion; | |
1553 | Throwable m_unhandled; | |
1554 | ||
1555 | version (Solaris) | |
1556 | { | |
d491a51f | 1557 | __gshared bool m_isRTClass; |
03385ed3 | 1558 | } |
1559 | ||
1560 | private: | |
1561 | /////////////////////////////////////////////////////////////////////////// | |
1562 | // Storage of Active Thread | |
1563 | /////////////////////////////////////////////////////////////////////////// | |
1564 | ||
1565 | ||
1566 | // | |
1567 | // Sets a thread-local reference to the current thread object. | |
1568 | // | |
1569 | static void setThis( Thread t ) nothrow @nogc | |
1570 | { | |
1571 | sm_this = t; | |
1572 | } | |
1573 | ||
1574 | ||
1575 | private: | |
1576 | /////////////////////////////////////////////////////////////////////////// | |
1577 | // Thread Context and GC Scanning Support | |
1578 | /////////////////////////////////////////////////////////////////////////// | |
1579 | ||
1580 | ||
1581 | final void pushContext( Context* c ) nothrow @nogc | |
1582 | in | |
1583 | { | |
1584 | assert( !c.within ); | |
1585 | } | |
1586 | body | |
1587 | { | |
1588 | m_curr.ehContext = swapContext(c.ehContext); | |
1589 | c.within = m_curr; | |
1590 | m_curr = c; | |
1591 | } | |
1592 | ||
1593 | ||
1594 | final void popContext() nothrow @nogc | |
1595 | in | |
1596 | { | |
1597 | assert( m_curr && m_curr.within ); | |
1598 | } | |
1599 | body | |
1600 | { | |
1601 | Context* c = m_curr; | |
1602 | m_curr = c.within; | |
1603 | c.ehContext = swapContext(m_curr.ehContext); | |
1604 | c.within = null; | |
1605 | } | |
1606 | ||
1607 | ||
1608 | final Context* topContext() nothrow @nogc | |
1609 | in | |
1610 | { | |
1611 | assert( m_curr ); | |
1612 | } | |
1613 | body | |
1614 | { | |
1615 | return m_curr; | |
1616 | } | |
1617 | ||
1618 | ||
1619 | static struct Context | |
1620 | { | |
1621 | void* bstack, | |
1622 | tstack; | |
1623 | ||
1624 | /// Slot for the EH implementation to keep some state for each stack | |
1625 | /// (will be necessary for exception chaining, etc.). Opaque as far as | |
1626 | /// we are concerned here. | |
1627 | void* ehContext; | |
1628 | ||
1629 | Context* within; | |
1630 | Context* next, | |
1631 | prev; | |
1632 | } | |
1633 | ||
1634 | ||
1635 | Context m_main; | |
1636 | Context* m_curr; | |
1637 | bool m_lock; | |
1638 | void* m_tlsgcdata; | |
1639 | ||
1640 | version (Windows) | |
1641 | { | |
1642 | version (X86) | |
1643 | { | |
1644 | uint[8] m_reg; // edi,esi,ebp,esp,ebx,edx,ecx,eax | |
1645 | } | |
1646 | else version (X86_64) | |
1647 | { | |
1648 | ulong[16] m_reg; // rdi,rsi,rbp,rsp,rbx,rdx,rcx,rax | |
1649 | // r8,r9,r10,r11,r12,r13,r14,r15 | |
1650 | } | |
1651 | else | |
1652 | { | |
1653 | static assert(false, "Architecture not supported." ); | |
1654 | } | |
1655 | } | |
1656 | else version (Darwin) | |
1657 | { | |
1658 | version (X86) | |
1659 | { | |
1660 | uint[8] m_reg; // edi,esi,ebp,esp,ebx,edx,ecx,eax | |
1661 | } | |
1662 | else version (X86_64) | |
1663 | { | |
1664 | ulong[16] m_reg; // rdi,rsi,rbp,rsp,rbx,rdx,rcx,rax | |
1665 | // r8,r9,r10,r11,r12,r13,r14,r15 | |
1666 | } | |
1667 | else | |
1668 | { | |
1669 | static assert(false, "Architecture not supported." ); | |
1670 | } | |
1671 | } | |
1672 | ||
1673 | ||
1674 | private: | |
1675 | /////////////////////////////////////////////////////////////////////////// | |
1676 | // GC Scanning Support | |
1677 | /////////////////////////////////////////////////////////////////////////// | |
1678 | ||
1679 | ||
1680 | // NOTE: The GC scanning process works like so: | |
1681 | // | |
1682 | // 1. Suspend all threads. | |
1683 | // 2. Scan the stacks of all suspended threads for roots. | |
1684 | // 3. Resume all threads. | |
1685 | // | |
1686 | // Step 1 and 3 require a list of all threads in the system, while | |
1687 | // step 2 requires a list of all thread stacks (each represented by | |
1688 | // a Context struct). Traditionally, there was one stack per thread | |
1689 | // and the Context structs were not necessary. However, Fibers have | |
1690 | // changed things so that each thread has its own 'main' stack plus | |
1691 | // an arbitrary number of nested stacks (normally referenced via | |
1692 | // m_curr). Also, there may be 'free-floating' stacks in the system, | |
1693 | // which are Fibers that are not currently executing on any specific | |
1694 | // thread but are still being processed and still contain valid | |
1695 | // roots. | |
1696 | // | |
1697 | // To support all of this, the Context struct has been created to | |
1698 | // represent a stack range, and a global list of Context structs has | |
1699 | // been added to enable scanning of these stack ranges. The lifetime | |
1700 | // (and presence in the Context list) of a thread's 'main' stack will | |
1701 | // be equivalent to the thread's lifetime. So the Ccontext will be | |
1702 | // added to the list on thread entry, and removed from the list on | |
1703 | // thread exit (which is essentially the same as the presence of a | |
1704 | // Thread object in its own global list). The lifetime of a Fiber's | |
1705 | // context, however, will be tied to the lifetime of the Fiber object | |
1706 | // itself, and Fibers are expected to add/remove their Context struct | |
1707 | // on construction/deletion. | |
1708 | ||
1709 | ||
1710 | // | |
1711 | // All use of the global thread lists/array should synchronize on this lock. | |
1712 | // | |
1713 | // Careful as the GC acquires this lock after the GC lock to suspend all | |
1714 | // threads any GC usage with slock held can result in a deadlock through | |
1715 | // lock order inversion. | |
1716 | @property static Mutex slock() nothrow @nogc | |
1717 | { | |
ca44cbe7 | 1718 | return cast(Mutex)_slock.ptr; |
03385ed3 | 1719 | } |
1720 | ||
1721 | @property static Mutex criticalRegionLock() nothrow @nogc | |
1722 | { | |
ca44cbe7 | 1723 | return cast(Mutex)_criticalRegionLock.ptr; |
03385ed3 | 1724 | } |
1725 | ||
ca44cbe7 | 1726 | __gshared align(mutexAlign) void[mutexClassInstanceSize] _slock; |
1727 | __gshared align(mutexAlign) void[mutexClassInstanceSize] _criticalRegionLock; | |
03385ed3 | 1728 | |
1729 | static void initLocks() | |
1730 | { | |
ca44cbe7 | 1731 | _slock[] = typeid(Mutex).initializer[]; |
1732 | (cast(Mutex)_slock.ptr).__ctor(); | |
1733 | ||
1734 | _criticalRegionLock[] = typeid(Mutex).initializer[]; | |
1735 | (cast(Mutex)_criticalRegionLock.ptr).__ctor(); | |
03385ed3 | 1736 | } |
1737 | ||
1738 | static void termLocks() | |
1739 | { | |
ca44cbe7 | 1740 | (cast(Mutex)_slock.ptr).__dtor(); |
1741 | (cast(Mutex)_criticalRegionLock.ptr).__dtor(); | |
03385ed3 | 1742 | } |
1743 | ||
1744 | __gshared Context* sm_cbeg; | |
1745 | ||
1746 | __gshared Thread sm_tbeg; | |
1747 | __gshared size_t sm_tlen; | |
1748 | ||
1749 | // can't use rt.util.array in public code | |
1750 | __gshared Thread* pAboutToStart; | |
1751 | __gshared size_t nAboutToStart; | |
1752 | ||
1753 | // | |
1754 | // Used for ordering threads in the global thread list. | |
1755 | // | |
1756 | Thread prev; | |
1757 | Thread next; | |
1758 | ||
1759 | ||
1760 | /////////////////////////////////////////////////////////////////////////// | |
1761 | // Global Context List Operations | |
1762 | /////////////////////////////////////////////////////////////////////////// | |
1763 | ||
1764 | ||
1765 | // | |
1766 | // Add a context to the global context list. | |
1767 | // | |
1768 | static void add( Context* c ) nothrow @nogc | |
1769 | in | |
1770 | { | |
1771 | assert( c ); | |
1772 | assert( !c.next && !c.prev ); | |
1773 | } | |
1774 | body | |
1775 | { | |
1776 | slock.lock_nothrow(); | |
1777 | scope(exit) slock.unlock_nothrow(); | |
1778 | assert(!suspendDepth); // must be 0 b/c it's only set with slock held | |
1779 | ||
1780 | if (sm_cbeg) | |
1781 | { | |
1782 | c.next = sm_cbeg; | |
1783 | sm_cbeg.prev = c; | |
1784 | } | |
1785 | sm_cbeg = c; | |
1786 | } | |
1787 | ||
1788 | ||
1789 | // | |
1790 | // Remove a context from the global context list. | |
1791 | // | |
1792 | // This assumes slock being acquired. This isn't done here to | |
1793 | // avoid double locking when called from remove(Thread) | |
1794 | static void remove( Context* c ) nothrow @nogc | |
1795 | in | |
1796 | { | |
1797 | assert( c ); | |
1798 | assert( c.next || c.prev ); | |
1799 | } | |
1800 | body | |
1801 | { | |
1802 | if ( c.prev ) | |
1803 | c.prev.next = c.next; | |
1804 | if ( c.next ) | |
1805 | c.next.prev = c.prev; | |
1806 | if ( sm_cbeg == c ) | |
1807 | sm_cbeg = c.next; | |
1808 | // NOTE: Don't null out c.next or c.prev because opApply currently | |
1809 | // follows c.next after removing a node. This could be easily | |
1810 | // addressed by simply returning the next node from this | |
1811 | // function, however, a context should never be re-added to the | |
1812 | // list anyway and having next and prev be non-null is a good way | |
1813 | // to ensure that. | |
1814 | } | |
1815 | ||
1816 | ||
1817 | /////////////////////////////////////////////////////////////////////////// | |
1818 | // Global Thread List Operations | |
1819 | /////////////////////////////////////////////////////////////////////////// | |
1820 | ||
1821 | ||
1822 | // | |
1823 | // Add a thread to the global thread list. | |
1824 | // | |
1825 | static void add( Thread t, bool rmAboutToStart = true ) nothrow @nogc | |
1826 | in | |
1827 | { | |
1828 | assert( t ); | |
1829 | assert( !t.next && !t.prev ); | |
1830 | } | |
1831 | body | |
1832 | { | |
1833 | slock.lock_nothrow(); | |
1834 | scope(exit) slock.unlock_nothrow(); | |
1835 | assert(t.isRunning); // check this with slock to ensure pthread_create already returned | |
1836 | assert(!suspendDepth); // must be 0 b/c it's only set with slock held | |
1837 | ||
1838 | if (rmAboutToStart) | |
1839 | { | |
1840 | size_t idx = -1; | |
1841 | foreach (i, thr; pAboutToStart[0 .. nAboutToStart]) | |
1842 | { | |
1843 | if (thr is t) | |
1844 | { | |
1845 | idx = i; | |
1846 | break; | |
1847 | } | |
1848 | } | |
1849 | assert(idx != -1); | |
1850 | import core.stdc.string : memmove; | |
1851 | memmove(pAboutToStart + idx, pAboutToStart + idx + 1, Thread.sizeof * (nAboutToStart - idx - 1)); | |
1852 | pAboutToStart = | |
1853 | cast(Thread*)realloc(pAboutToStart, Thread.sizeof * --nAboutToStart); | |
1854 | } | |
1855 | ||
1856 | if (sm_tbeg) | |
1857 | { | |
1858 | t.next = sm_tbeg; | |
1859 | sm_tbeg.prev = t; | |
1860 | } | |
1861 | sm_tbeg = t; | |
1862 | ++sm_tlen; | |
1863 | } | |
1864 | ||
1865 | ||
1866 | // | |
1867 | // Remove a thread from the global thread list. | |
1868 | // | |
1869 | static void remove( Thread t ) nothrow @nogc | |
1870 | in | |
1871 | { | |
1872 | assert( t ); | |
1873 | } | |
1874 | body | |
1875 | { | |
1876 | // Thread was already removed earlier, might happen b/c of thread_detachInstance | |
1877 | if (!t.next && !t.prev) | |
1878 | return; | |
1879 | slock.lock_nothrow(); | |
1880 | { | |
1881 | // NOTE: When a thread is removed from the global thread list its | |
1882 | // main context is invalid and should be removed as well. | |
1883 | // It is possible that t.m_curr could reference more | |
1884 | // than just the main context if the thread exited abnormally | |
1885 | // (if it was terminated), but we must assume that the user | |
1886 | // retains a reference to them and that they may be re-used | |
1887 | // elsewhere. Therefore, it is the responsibility of any | |
1888 | // object that creates contexts to clean them up properly | |
1889 | // when it is done with them. | |
1890 | remove( &t.m_main ); | |
1891 | ||
1892 | if ( t.prev ) | |
1893 | t.prev.next = t.next; | |
1894 | if ( t.next ) | |
1895 | t.next.prev = t.prev; | |
1896 | if ( sm_tbeg is t ) | |
1897 | sm_tbeg = t.next; | |
1898 | t.prev = t.next = null; | |
1899 | --sm_tlen; | |
1900 | } | |
1901 | // NOTE: Don't null out t.next or t.prev because opApply currently | |
1902 | // follows t.next after removing a node. This could be easily | |
1903 | // addressed by simply returning the next node from this | |
1904 | // function, however, a thread should never be re-added to the | |
1905 | // list anyway and having next and prev be non-null is a good way | |
1906 | // to ensure that. | |
1907 | slock.unlock_nothrow(); | |
1908 | } | |
1909 | } | |
1910 | ||
1911 | /// | |
1912 | unittest | |
1913 | { | |
1914 | class DerivedThread : Thread | |
1915 | { | |
1916 | this() | |
1917 | { | |
1918 | super(&run); | |
1919 | } | |
1920 | ||
1921 | private: | |
1922 | void run() | |
1923 | { | |
1924 | // Derived thread running. | |
1925 | } | |
1926 | } | |
1927 | ||
1928 | void threadFunc() | |
1929 | { | |
1930 | // Composed thread running. | |
1931 | } | |
1932 | ||
1933 | // create and start instances of each type | |
1934 | auto derived = new DerivedThread().start(); | |
1935 | auto composed = new Thread(&threadFunc).start(); | |
1936 | new Thread({ | |
1937 | // Codes to run in the newly created thread. | |
1938 | }).start(); | |
1939 | } | |
1940 | ||
1941 | unittest | |
1942 | { | |
1943 | int x = 0; | |
1944 | ||
1945 | new Thread( | |
1946 | { | |
1947 | x++; | |
1948 | }).start().join(); | |
1949 | assert( x == 1 ); | |
1950 | } | |
1951 | ||
1952 | ||
1953 | unittest | |
1954 | { | |
1955 | enum MSG = "Test message."; | |
1956 | string caughtMsg; | |
1957 | ||
1958 | try | |
1959 | { | |
1960 | new Thread( | |
1961 | { | |
1962 | throw new Exception( MSG ); | |
1963 | }).start().join(); | |
1964 | assert( false, "Expected rethrown exception." ); | |
1965 | } | |
1966 | catch ( Throwable t ) | |
1967 | { | |
1968 | assert( t.msg == MSG ); | |
1969 | } | |
1970 | } | |
1971 | ||
1972 | ||
1973 | /////////////////////////////////////////////////////////////////////////////// | |
1974 | // GC Support Routines | |
1975 | /////////////////////////////////////////////////////////////////////////////// | |
1976 | ||
1977 | version (CoreDdoc) | |
1978 | { | |
1979 | /** | |
1980 | * Instruct the thread module, when initialized, to use a different set of | |
1981 | * signals besides SIGUSR1 and SIGUSR2 for suspension and resumption of threads. | |
1982 | * This function should be called at most once, prior to thread_init(). | |
1983 | * This function is Posix-only. | |
1984 | */ | |
1985 | extern (C) void thread_setGCSignals(int suspendSignalNo, int resumeSignalNo) nothrow @nogc | |
1986 | { | |
1987 | } | |
1988 | } | |
1989 | else version (Posix) | |
1990 | { | |
1991 | extern (C) void thread_setGCSignals(int suspendSignalNo, int resumeSignalNo) nothrow @nogc | |
1992 | in | |
1993 | { | |
1994 | assert(suspendSignalNumber == 0); | |
1995 | assert(resumeSignalNumber == 0); | |
1996 | assert(suspendSignalNo != 0); | |
1997 | assert(resumeSignalNo != 0); | |
1998 | } | |
1999 | out | |
2000 | { | |
2001 | assert(suspendSignalNumber != 0); | |
2002 | assert(resumeSignalNumber != 0); | |
2003 | } | |
2004 | body | |
2005 | { | |
2006 | suspendSignalNumber = suspendSignalNo; | |
2007 | resumeSignalNumber = resumeSignalNo; | |
2008 | } | |
2009 | } | |
2010 | ||
2011 | version (Posix) | |
2012 | { | |
2013 | __gshared int suspendSignalNumber; | |
2014 | __gshared int resumeSignalNumber; | |
2015 | } | |
2016 | ||
2017 | /** | |
2018 | * Initializes the thread module. This function must be called by the | |
2019 | * garbage collector on startup and before any other thread routines | |
2020 | * are called. | |
2021 | */ | |
2022 | extern (C) void thread_init() | |
2023 | { | |
2024 | // NOTE: If thread_init itself performs any allocations then the thread | |
2025 | // routines reserved for garbage collector use may be called while | |
2026 | // thread_init is being processed. However, since no memory should | |
2027 | // exist to be scanned at this point, it is sufficient for these | |
2028 | // functions to detect the condition and return immediately. | |
2029 | ||
2030 | Thread.initLocks(); | |
2031 | // The Android VM runtime intercepts SIGUSR1 and apparently doesn't allow | |
2032 | // its signal handler to run, so swap the two signals on Android, since | |
2033 | // thread_resumeHandler does nothing. | |
2034 | version (Android) thread_setGCSignals(SIGUSR2, SIGUSR1); | |
2035 | ||
2036 | version (Darwin) | |
2037 | { | |
2038 | } | |
2039 | else version (Posix) | |
2040 | { | |
2041 | if ( suspendSignalNumber == 0 ) | |
2042 | { | |
2043 | suspendSignalNumber = SIGUSR1; | |
2044 | } | |
2045 | ||
2046 | if ( resumeSignalNumber == 0 ) | |
2047 | { | |
2048 | resumeSignalNumber = SIGUSR2; | |
2049 | } | |
2050 | ||
2051 | int status; | |
2052 | sigaction_t sigusr1 = void; | |
2053 | sigaction_t sigusr2 = void; | |
2054 | ||
2055 | // This is a quick way to zero-initialize the structs without using | |
2056 | // memset or creating a link dependency on their static initializer. | |
2057 | (cast(byte*) &sigusr1)[0 .. sigaction_t.sizeof] = 0; | |
2058 | (cast(byte*) &sigusr2)[0 .. sigaction_t.sizeof] = 0; | |
2059 | ||
2060 | // NOTE: SA_RESTART indicates that system calls should restart if they | |
2061 | // are interrupted by a signal, but this is not available on all | |
2062 | // Posix systems, even those that support multithreading. | |
2063 | static if ( __traits( compiles, SA_RESTART ) ) | |
2064 | sigusr1.sa_flags = SA_RESTART; | |
2065 | else | |
2066 | sigusr1.sa_flags = 0; | |
2067 | sigusr1.sa_handler = &thread_suspendHandler; | |
2068 | // NOTE: We want to ignore all signals while in this handler, so fill | |
2069 | // sa_mask to indicate this. | |
2070 | status = sigfillset( &sigusr1.sa_mask ); | |
2071 | assert( status == 0 ); | |
2072 | ||
2073 | // NOTE: Since resumeSignalNumber should only be issued for threads within the | |
2074 | // suspend handler, we don't want this signal to trigger a | |
2075 | // restart. | |
2076 | sigusr2.sa_flags = 0; | |
2077 | sigusr2.sa_handler = &thread_resumeHandler; | |
2078 | // NOTE: We want to ignore all signals while in this handler, so fill | |
2079 | // sa_mask to indicate this. | |
2080 | status = sigfillset( &sigusr2.sa_mask ); | |
2081 | assert( status == 0 ); | |
2082 | ||
2083 | status = sigaction( suspendSignalNumber, &sigusr1, null ); | |
2084 | assert( status == 0 ); | |
2085 | ||
2086 | status = sigaction( resumeSignalNumber, &sigusr2, null ); | |
2087 | assert( status == 0 ); | |
2088 | ||
2089 | status = sem_init( &suspendCount, 0, 0 ); | |
2090 | assert( status == 0 ); | |
2091 | } | |
2092 | Thread.sm_main = thread_attachThis(); | |
2093 | } | |
2094 | ||
2095 | ||
2096 | /** | |
2097 | * Terminates the thread module. No other thread routine may be called | |
2098 | * afterwards. | |
2099 | */ | |
2100 | extern (C) void thread_term() | |
2101 | { | |
2102 | assert(Thread.sm_tbeg && Thread.sm_tlen == 1); | |
2103 | assert(!Thread.nAboutToStart); | |
2104 | if (Thread.pAboutToStart) // in case realloc(p, 0) doesn't return null | |
2105 | { | |
2106 | free(Thread.pAboutToStart); | |
2107 | Thread.pAboutToStart = null; | |
2108 | } | |
2109 | Thread.termLocks(); | |
2110 | } | |
2111 | ||
2112 | ||
2113 | /** | |
2114 | * | |
2115 | */ | |
2116 | extern (C) bool thread_isMainThread() nothrow @nogc | |
2117 | { | |
2118 | return Thread.getThis() is Thread.sm_main; | |
2119 | } | |
2120 | ||
2121 | ||
2122 | /** | |
2123 | * Registers the calling thread for use with the D Runtime. If this routine | |
2124 | * is called for a thread which is already registered, no action is performed. | |
2125 | * | |
2126 | * NOTE: This routine does not run thread-local static constructors when called. | |
2127 | * If full functionality as a D thread is desired, the following function | |
2128 | * must be called after thread_attachThis: | |
2129 | * | |
2130 | * extern (C) void rt_moduleTlsCtor(); | |
2131 | */ | |
2132 | extern (C) Thread thread_attachThis() | |
2133 | { | |
2134 | GC.disable(); scope(exit) GC.enable(); | |
2135 | ||
2136 | if (auto t = Thread.getThis()) | |
2137 | return t; | |
2138 | ||
2139 | Thread thisThread = new Thread(); | |
2140 | Thread.Context* thisContext = &thisThread.m_main; | |
2141 | assert( thisContext == thisThread.m_curr ); | |
2142 | ||
2143 | version (Windows) | |
2144 | { | |
2145 | thisThread.m_addr = GetCurrentThreadId(); | |
2146 | thisThread.m_hndl = GetCurrentThreadHandle(); | |
2147 | thisContext.bstack = getStackBottom(); | |
2148 | thisContext.tstack = thisContext.bstack; | |
2149 | } | |
2150 | else version (Posix) | |
2151 | { | |
2152 | thisThread.m_addr = pthread_self(); | |
2153 | thisContext.bstack = getStackBottom(); | |
2154 | thisContext.tstack = thisContext.bstack; | |
2155 | ||
2156 | atomicStore!(MemoryOrder.raw)(thisThread.m_isRunning, true); | |
2157 | } | |
2158 | thisThread.m_isDaemon = true; | |
2159 | thisThread.m_tlsgcdata = rt_tlsgc_init(); | |
2160 | Thread.setThis( thisThread ); | |
2161 | ||
2162 | version (Darwin) | |
2163 | { | |
2164 | thisThread.m_tmach = pthread_mach_thread_np( thisThread.m_addr ); | |
2165 | assert( thisThread.m_tmach != thisThread.m_tmach.init ); | |
2166 | } | |
2167 | ||
2168 | Thread.add( thisThread, false ); | |
2169 | Thread.add( thisContext ); | |
2170 | if ( Thread.sm_main !is null ) | |
2171 | multiThreadedFlag = true; | |
2172 | return thisThread; | |
2173 | } | |
2174 | ||
2175 | ||
2176 | version (Windows) | |
2177 | { | |
2178 | // NOTE: These calls are not safe on Posix systems that use signals to | |
2179 | // perform garbage collection. The suspendHandler uses getThis() | |
2180 | // to get the thread handle so getThis() must be a simple call. | |
2181 | // Mutexes can't safely be acquired inside signal handlers, and | |
2182 | // even if they could, the mutex needed (Thread.slock) is held by | |
2183 | // thread_suspendAll(). So in short, these routines will remain | |
2184 | // Windows-specific. If they are truly needed elsewhere, the | |
2185 | // suspendHandler will need a way to call a version of getThis() | |
2186 | // that only does the TLS lookup without the fancy fallback stuff. | |
2187 | ||
2188 | /// ditto | |
2189 | extern (C) Thread thread_attachByAddr( ThreadID addr ) | |
2190 | { | |
2191 | return thread_attachByAddrB( addr, getThreadStackBottom( addr ) ); | |
2192 | } | |
2193 | ||
2194 | ||
2195 | /// ditto | |
2196 | extern (C) Thread thread_attachByAddrB( ThreadID addr, void* bstack ) | |
2197 | { | |
2198 | GC.disable(); scope(exit) GC.enable(); | |
2199 | ||
2200 | if (auto t = thread_findByAddr(addr)) | |
2201 | return t; | |
2202 | ||
2203 | Thread thisThread = new Thread(); | |
2204 | Thread.Context* thisContext = &thisThread.m_main; | |
2205 | assert( thisContext == thisThread.m_curr ); | |
2206 | ||
2207 | thisThread.m_addr = addr; | |
2208 | thisContext.bstack = bstack; | |
2209 | thisContext.tstack = thisContext.bstack; | |
2210 | ||
2211 | thisThread.m_isDaemon = true; | |
2212 | ||
2213 | if ( addr == GetCurrentThreadId() ) | |
2214 | { | |
2215 | thisThread.m_hndl = GetCurrentThreadHandle(); | |
2216 | thisThread.m_tlsgcdata = rt_tlsgc_init(); | |
2217 | Thread.setThis( thisThread ); | |
2218 | } | |
2219 | else | |
2220 | { | |
2221 | thisThread.m_hndl = OpenThreadHandle( addr ); | |
2222 | impersonate_thread(addr, | |
2223 | { | |
2224 | thisThread.m_tlsgcdata = rt_tlsgc_init(); | |
2225 | Thread.setThis( thisThread ); | |
2226 | }); | |
2227 | } | |
2228 | ||
2229 | Thread.add( thisThread, false ); | |
2230 | Thread.add( thisContext ); | |
2231 | if ( Thread.sm_main !is null ) | |
2232 | multiThreadedFlag = true; | |
2233 | return thisThread; | |
2234 | } | |
2235 | } | |
2236 | ||
2237 | ||
2238 | /** | |
2239 | * Deregisters the calling thread from use with the runtime. If this routine | |
2240 | * is called for a thread which is not registered, the result is undefined. | |
2241 | * | |
2242 | * NOTE: This routine does not run thread-local static destructors when called. | |
2243 | * If full functionality as a D thread is desired, the following function | |
2244 | * must be called after thread_detachThis, particularly if the thread is | |
2245 | * being detached at some indeterminate time before program termination: | |
2246 | * | |
2247 | * $(D extern(C) void rt_moduleTlsDtor();) | |
2248 | */ | |
2249 | extern (C) void thread_detachThis() nothrow @nogc | |
2250 | { | |
2251 | if (auto t = Thread.getThis()) | |
2252 | Thread.remove(t); | |
2253 | } | |
2254 | ||
2255 | ||
2256 | /** | |
2257 | * Deregisters the given thread from use with the runtime. If this routine | |
2258 | * is called for a thread which is not registered, the result is undefined. | |
2259 | * | |
2260 | * NOTE: This routine does not run thread-local static destructors when called. | |
2261 | * If full functionality as a D thread is desired, the following function | |
2262 | * must be called by the detached thread, particularly if the thread is | |
2263 | * being detached at some indeterminate time before program termination: | |
2264 | * | |
2265 | * $(D extern(C) void rt_moduleTlsDtor();) | |
2266 | */ | |
2267 | extern (C) void thread_detachByAddr( ThreadID addr ) | |
2268 | { | |
2269 | if ( auto t = thread_findByAddr( addr ) ) | |
2270 | Thread.remove( t ); | |
2271 | } | |
2272 | ||
2273 | ||
2274 | /// ditto | |
2275 | extern (C) void thread_detachInstance( Thread t ) nothrow @nogc | |
2276 | { | |
2277 | Thread.remove( t ); | |
2278 | } | |
2279 | ||
2280 | ||
2281 | unittest | |
2282 | { | |
2283 | import core.sync.semaphore; | |
2284 | auto sem = new Semaphore(); | |
2285 | ||
2286 | auto t = new Thread( | |
2287 | { | |
2288 | sem.notify(); | |
2289 | Thread.sleep(100.msecs); | |
2290 | }).start(); | |
2291 | ||
2292 | sem.wait(); // thread cannot be detached while being started | |
2293 | thread_detachInstance(t); | |
2294 | foreach (t2; Thread) | |
2295 | assert(t !is t2); | |
2296 | t.join(); | |
2297 | } | |
2298 | ||
2299 | ||
2300 | /** | |
2301 | * Search the list of all threads for a thread with the given thread identifier. | |
2302 | * | |
2303 | * Params: | |
2304 | * addr = The thread identifier to search for. | |
2305 | * Returns: | |
2306 | * The thread object associated with the thread identifier, null if not found. | |
2307 | */ | |
2308 | static Thread thread_findByAddr( ThreadID addr ) | |
2309 | { | |
2310 | Thread.slock.lock_nothrow(); | |
2311 | scope(exit) Thread.slock.unlock_nothrow(); | |
2312 | ||
2313 | // also return just spawned thread so that | |
2314 | // DLL_THREAD_ATTACH knows it's a D thread | |
2315 | foreach (t; Thread.pAboutToStart[0 .. Thread.nAboutToStart]) | |
2316 | if (t.m_addr == addr) | |
2317 | return t; | |
2318 | ||
2319 | foreach (t; Thread) | |
2320 | if (t.m_addr == addr) | |
2321 | return t; | |
2322 | ||
2323 | return null; | |
2324 | } | |
2325 | ||
2326 | ||
2327 | /** | |
2328 | * Sets the current thread to a specific reference. Only to be used | |
2329 | * when dealing with externally-created threads (in e.g. C code). | |
2330 | * The primary use of this function is when Thread.getThis() must | |
2331 | * return a sensible value in, for example, TLS destructors. In | |
2332 | * other words, don't touch this unless you know what you're doing. | |
2333 | * | |
2334 | * Params: | |
2335 | * t = A reference to the current thread. May be null. | |
2336 | */ | |
2337 | extern (C) void thread_setThis(Thread t) nothrow @nogc | |
2338 | { | |
2339 | Thread.setThis(t); | |
2340 | } | |
2341 | ||
2342 | ||
2343 | /** | |
2344 | * Joins all non-daemon threads that are currently running. This is done by | |
2345 | * performing successive scans through the thread list until a scan consists | |
2346 | * of only daemon threads. | |
2347 | */ | |
2348 | extern (C) void thread_joinAll() | |
2349 | { | |
2350 | Lagain: | |
2351 | Thread.slock.lock_nothrow(); | |
2352 | // wait for just spawned threads | |
2353 | if (Thread.nAboutToStart) | |
2354 | { | |
2355 | Thread.slock.unlock_nothrow(); | |
2356 | Thread.yield(); | |
2357 | goto Lagain; | |
2358 | } | |
2359 | ||
2360 | // join all non-daemon threads, the main thread is also a daemon | |
2361 | auto t = Thread.sm_tbeg; | |
2362 | while (t) | |
2363 | { | |
2364 | if (!t.isRunning) | |
2365 | { | |
2366 | auto tn = t.next; | |
2367 | Thread.remove(t); | |
2368 | t = tn; | |
2369 | } | |
2370 | else if (t.isDaemon) | |
2371 | { | |
2372 | t = t.next; | |
2373 | } | |
2374 | else | |
2375 | { | |
2376 | Thread.slock.unlock_nothrow(); | |
2377 | t.join(); // might rethrow | |
2378 | goto Lagain; // must restart iteration b/c of unlock | |
2379 | } | |
2380 | } | |
2381 | Thread.slock.unlock_nothrow(); | |
2382 | } | |
2383 | ||
2384 | ||
2385 | /** | |
2386 | * Performs intermediate shutdown of the thread module. | |
2387 | */ | |
2388 | shared static ~this() | |
2389 | { | |
2390 | // NOTE: The functionality related to garbage collection must be minimally | |
2391 | // operable after this dtor completes. Therefore, only minimal | |
2392 | // cleanup may occur. | |
2393 | auto t = Thread.sm_tbeg; | |
2394 | while (t) | |
2395 | { | |
2396 | auto tn = t.next; | |
2397 | if (!t.isRunning) | |
2398 | Thread.remove(t); | |
2399 | t = tn; | |
2400 | } | |
2401 | } | |
2402 | ||
2403 | ||
2404 | // Used for needLock below. | |
2405 | private __gshared bool multiThreadedFlag = false; | |
2406 | ||
2407 | version (PPC64) version = ExternStackShell; | |
2408 | ||
2409 | version (ExternStackShell) | |
2410 | { | |
2411 | extern(D) public void callWithStackShell(scope void delegate(void* sp) nothrow fn) nothrow; | |
2412 | } | |
2413 | else | |
2414 | { | |
2415 | // Calls the given delegate, passing the current thread's stack pointer to it. | |
2416 | private void callWithStackShell(scope void delegate(void* sp) nothrow fn) nothrow | |
2417 | in | |
2418 | { | |
2419 | assert(fn); | |
2420 | } | |
2421 | body | |
2422 | { | |
2423 | // The purpose of the 'shell' is to ensure all the registers get | |
2424 | // put on the stack so they'll be scanned. We only need to push | |
2425 | // the callee-save registers. | |
2426 | void *sp = void; | |
2427 | ||
2428 | version (GNU) | |
2429 | { | |
2430 | __builtin_unwind_init(); | |
2431 | sp = &sp; | |
2432 | } | |
2433 | else version (AsmX86_Posix) | |
2434 | { | |
2435 | size_t[3] regs = void; | |
2436 | asm pure nothrow @nogc | |
2437 | { | |
2438 | mov [regs + 0 * 4], EBX; | |
2439 | mov [regs + 1 * 4], ESI; | |
2440 | mov [regs + 2 * 4], EDI; | |
2441 | ||
2442 | mov sp[EBP], ESP; | |
2443 | } | |
2444 | } | |
2445 | else version (AsmX86_Windows) | |
2446 | { | |
2447 | size_t[3] regs = void; | |
2448 | asm pure nothrow @nogc | |
2449 | { | |
2450 | mov [regs + 0 * 4], EBX; | |
2451 | mov [regs + 1 * 4], ESI; | |
2452 | mov [regs + 2 * 4], EDI; | |
2453 | ||
2454 | mov sp[EBP], ESP; | |
2455 | } | |
2456 | } | |
2457 | else version (AsmX86_64_Posix) | |
2458 | { | |
2459 | size_t[5] regs = void; | |
2460 | asm pure nothrow @nogc | |
2461 | { | |
2462 | mov [regs + 0 * 8], RBX; | |
2463 | mov [regs + 1 * 8], R12; | |
2464 | mov [regs + 2 * 8], R13; | |
2465 | mov [regs + 3 * 8], R14; | |
2466 | mov [regs + 4 * 8], R15; | |
2467 | ||
2468 | mov sp[RBP], RSP; | |
2469 | } | |
2470 | } | |
2471 | else version (AsmX86_64_Windows) | |
2472 | { | |
2473 | size_t[7] regs = void; | |
2474 | asm pure nothrow @nogc | |
2475 | { | |
2476 | mov [regs + 0 * 8], RBX; | |
2477 | mov [regs + 1 * 8], RSI; | |
2478 | mov [regs + 2 * 8], RDI; | |
2479 | mov [regs + 3 * 8], R12; | |
2480 | mov [regs + 4 * 8], R13; | |
2481 | mov [regs + 5 * 8], R14; | |
2482 | mov [regs + 6 * 8], R15; | |
2483 | ||
2484 | mov sp[RBP], RSP; | |
2485 | } | |
2486 | } | |
2487 | else | |
2488 | { | |
2489 | static assert(false, "Architecture not supported."); | |
2490 | } | |
2491 | ||
2492 | fn(sp); | |
2493 | } | |
2494 | } | |
2495 | ||
2496 | // Used for suspendAll/resumeAll below. | |
2497 | private __gshared uint suspendDepth = 0; | |
2498 | ||
2499 | /** | |
2500 | * Suspend the specified thread and load stack and register information for | |
2501 | * use by thread_scanAll. If the supplied thread is the calling thread, | |
2502 | * stack and register information will be loaded but the thread will not | |
2503 | * be suspended. If the suspend operation fails and the thread is not | |
2504 | * running then it will be removed from the global thread list, otherwise | |
2505 | * an exception will be thrown. | |
2506 | * | |
2507 | * Params: | |
2508 | * t = The thread to suspend. | |
2509 | * | |
2510 | * Throws: | |
2511 | * ThreadError if the suspend operation fails for a running thread. | |
2512 | * Returns: | |
2513 | * Whether the thread is now suspended (true) or terminated (false). | |
2514 | */ | |
2515 | private bool suspend( Thread t ) nothrow | |
2516 | { | |
2517 | Duration waittime = dur!"usecs"(10); | |
2518 | Lagain: | |
2519 | if (!t.isRunning) | |
2520 | { | |
2521 | Thread.remove(t); | |
2522 | return false; | |
2523 | } | |
2524 | else if (t.m_isInCriticalRegion) | |
2525 | { | |
2526 | Thread.criticalRegionLock.unlock_nothrow(); | |
2527 | Thread.sleep(waittime); | |
2528 | if (waittime < dur!"msecs"(10)) waittime *= 2; | |
2529 | Thread.criticalRegionLock.lock_nothrow(); | |
2530 | goto Lagain; | |
2531 | } | |
2532 | ||
2533 | version (Windows) | |
2534 | { | |
2535 | if ( t.m_addr != GetCurrentThreadId() && SuspendThread( t.m_hndl ) == 0xFFFFFFFF ) | |
2536 | { | |
2537 | if ( !t.isRunning ) | |
2538 | { | |
2539 | Thread.remove( t ); | |
2540 | return false; | |
2541 | } | |
2542 | onThreadError( "Unable to suspend thread" ); | |
2543 | } | |
2544 | ||
2545 | CONTEXT context = void; | |
2546 | context.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL; | |
2547 | ||
2548 | if ( !GetThreadContext( t.m_hndl, &context ) ) | |
2549 | onThreadError( "Unable to load thread context" ); | |
2550 | version (X86) | |
2551 | { | |
2552 | if ( !t.m_lock ) | |
2553 | t.m_curr.tstack = cast(void*) context.Esp; | |
2554 | // eax,ebx,ecx,edx,edi,esi,ebp,esp | |
2555 | t.m_reg[0] = context.Eax; | |
2556 | t.m_reg[1] = context.Ebx; | |
2557 | t.m_reg[2] = context.Ecx; | |
2558 | t.m_reg[3] = context.Edx; | |
2559 | t.m_reg[4] = context.Edi; | |
2560 | t.m_reg[5] = context.Esi; | |
2561 | t.m_reg[6] = context.Ebp; | |
2562 | t.m_reg[7] = context.Esp; | |
2563 | } | |
2564 | else version (X86_64) | |
2565 | { | |
2566 | if ( !t.m_lock ) | |
2567 | t.m_curr.tstack = cast(void*) context.Rsp; | |
2568 | // rax,rbx,rcx,rdx,rdi,rsi,rbp,rsp | |
2569 | t.m_reg[0] = context.Rax; | |
2570 | t.m_reg[1] = context.Rbx; | |
2571 | t.m_reg[2] = context.Rcx; | |
2572 | t.m_reg[3] = context.Rdx; | |
2573 | t.m_reg[4] = context.Rdi; | |
2574 | t.m_reg[5] = context.Rsi; | |
2575 | t.m_reg[6] = context.Rbp; | |
2576 | t.m_reg[7] = context.Rsp; | |
2577 | // r8,r9,r10,r11,r12,r13,r14,r15 | |
2578 | t.m_reg[8] = context.R8; | |
2579 | t.m_reg[9] = context.R9; | |
2580 | t.m_reg[10] = context.R10; | |
2581 | t.m_reg[11] = context.R11; | |
2582 | t.m_reg[12] = context.R12; | |
2583 | t.m_reg[13] = context.R13; | |
2584 | t.m_reg[14] = context.R14; | |
2585 | t.m_reg[15] = context.R15; | |
2586 | } | |
2587 | else | |
2588 | { | |
2589 | static assert(false, "Architecture not supported." ); | |
2590 | } | |
2591 | } | |
2592 | else version (Darwin) | |
2593 | { | |
2594 | if ( t.m_addr != pthread_self() && thread_suspend( t.m_tmach ) != KERN_SUCCESS ) | |
2595 | { | |
2596 | if ( !t.isRunning ) | |
2597 | { | |
2598 | Thread.remove( t ); | |
2599 | return false; | |
2600 | } | |
2601 | onThreadError( "Unable to suspend thread" ); | |
2602 | } | |
2603 | ||
2604 | version (X86) | |
2605 | { | |
2606 | x86_thread_state32_t state = void; | |
2607 | mach_msg_type_number_t count = x86_THREAD_STATE32_COUNT; | |
2608 | ||
2609 | if ( thread_get_state( t.m_tmach, x86_THREAD_STATE32, &state, &count ) != KERN_SUCCESS ) | |
2610 | onThreadError( "Unable to load thread state" ); | |
2611 | if ( !t.m_lock ) | |
2612 | t.m_curr.tstack = cast(void*) state.esp; | |
2613 | // eax,ebx,ecx,edx,edi,esi,ebp,esp | |
2614 | t.m_reg[0] = state.eax; | |
2615 | t.m_reg[1] = state.ebx; | |
2616 | t.m_reg[2] = state.ecx; | |
2617 | t.m_reg[3] = state.edx; | |
2618 | t.m_reg[4] = state.edi; | |
2619 | t.m_reg[5] = state.esi; | |
2620 | t.m_reg[6] = state.ebp; | |
2621 | t.m_reg[7] = state.esp; | |
2622 | } | |
2623 | else version (X86_64) | |
2624 | { | |
2625 | x86_thread_state64_t state = void; | |
2626 | mach_msg_type_number_t count = x86_THREAD_STATE64_COUNT; | |
2627 | ||
2628 | if ( thread_get_state( t.m_tmach, x86_THREAD_STATE64, &state, &count ) != KERN_SUCCESS ) | |
2629 | onThreadError( "Unable to load thread state" ); | |
2630 | if ( !t.m_lock ) | |
2631 | t.m_curr.tstack = cast(void*) state.rsp; | |
2632 | // rax,rbx,rcx,rdx,rdi,rsi,rbp,rsp | |
2633 | t.m_reg[0] = state.rax; | |
2634 | t.m_reg[1] = state.rbx; | |
2635 | t.m_reg[2] = state.rcx; | |
2636 | t.m_reg[3] = state.rdx; | |
2637 | t.m_reg[4] = state.rdi; | |
2638 | t.m_reg[5] = state.rsi; | |
2639 | t.m_reg[6] = state.rbp; | |
2640 | t.m_reg[7] = state.rsp; | |
2641 | // r8,r9,r10,r11,r12,r13,r14,r15 | |
2642 | t.m_reg[8] = state.r8; | |
2643 | t.m_reg[9] = state.r9; | |
2644 | t.m_reg[10] = state.r10; | |
2645 | t.m_reg[11] = state.r11; | |
2646 | t.m_reg[12] = state.r12; | |
2647 | t.m_reg[13] = state.r13; | |
2648 | t.m_reg[14] = state.r14; | |
2649 | t.m_reg[15] = state.r15; | |
2650 | } | |
2651 | else | |
2652 | { | |
2653 | static assert(false, "Architecture not supported." ); | |
2654 | } | |
2655 | } | |
2656 | else version (Posix) | |
2657 | { | |
2658 | if ( t.m_addr != pthread_self() ) | |
2659 | { | |
2660 | if ( pthread_kill( t.m_addr, suspendSignalNumber ) != 0 ) | |
2661 | { | |
2662 | if ( !t.isRunning ) | |
2663 | { | |
2664 | Thread.remove( t ); | |
2665 | return false; | |
2666 | } | |
2667 | onThreadError( "Unable to suspend thread" ); | |
2668 | } | |
2669 | } | |
2670 | else if ( !t.m_lock ) | |
2671 | { | |
2672 | t.m_curr.tstack = getStackTop(); | |
2673 | } | |
2674 | } | |
2675 | return true; | |
2676 | } | |
2677 | ||
2678 | /** | |
2679 | * Suspend all threads but the calling thread for "stop the world" garbage | |
2680 | * collection runs. This function may be called multiple times, and must | |
2681 | * be followed by a matching number of calls to thread_resumeAll before | |
2682 | * processing is resumed. | |
2683 | * | |
2684 | * Throws: | |
2685 | * ThreadError if the suspend operation fails for a running thread. | |
2686 | */ | |
2687 | extern (C) void thread_suspendAll() nothrow | |
2688 | { | |
2689 | // NOTE: We've got an odd chicken & egg problem here, because while the GC | |
2690 | // is required to call thread_init before calling any other thread | |
2691 | // routines, thread_init may allocate memory which could in turn | |
2692 | // trigger a collection. Thus, thread_suspendAll, thread_scanAll, | |
2693 | // and thread_resumeAll must be callable before thread_init | |
2694 | // completes, with the assumption that no other GC memory has yet | |
2695 | // been allocated by the system, and thus there is no risk of losing | |
2696 | // data if the global thread list is empty. The check of | |
2697 | // Thread.sm_tbeg below is done to ensure thread_init has completed, | |
2698 | // and therefore that calling Thread.getThis will not result in an | |
2699 | // error. For the short time when Thread.sm_tbeg is null, there is | |
2700 | // no reason not to simply call the multithreaded code below, with | |
2701 | // the expectation that the foreach loop will never be entered. | |
2702 | if ( !multiThreadedFlag && Thread.sm_tbeg ) | |
2703 | { | |
2704 | if ( ++suspendDepth == 1 ) | |
2705 | suspend( Thread.getThis() ); | |
2706 | ||
2707 | return; | |
2708 | } | |
2709 | ||
2710 | Thread.slock.lock_nothrow(); | |
2711 | { | |
2712 | if ( ++suspendDepth > 1 ) | |
2713 | return; | |
2714 | ||
2715 | Thread.criticalRegionLock.lock_nothrow(); | |
2716 | scope (exit) Thread.criticalRegionLock.unlock_nothrow(); | |
2717 | size_t cnt; | |
2718 | auto t = Thread.sm_tbeg; | |
2719 | while (t) | |
2720 | { | |
2721 | auto tn = t.next; | |
2722 | if (suspend(t)) | |
2723 | ++cnt; | |
2724 | t = tn; | |
2725 | } | |
2726 | ||
2727 | version (Darwin) | |
2728 | {} | |
2729 | else version (Posix) | |
2730 | { | |
2731 | // subtract own thread | |
2732 | assert(cnt >= 1); | |
2733 | --cnt; | |
2734 | Lagain: | |
2735 | // wait for semaphore notifications | |
2736 | for (; cnt; --cnt) | |
2737 | { | |
2738 | while (sem_wait(&suspendCount) != 0) | |
2739 | { | |
2740 | if (errno != EINTR) | |
2741 | onThreadError("Unable to wait for semaphore"); | |
2742 | errno = 0; | |
2743 | } | |
2744 | } | |
2745 | version (FreeBSD) | |
2746 | { | |
2747 | // avoid deadlocks, see Issue 13416 | |
2748 | t = Thread.sm_tbeg; | |
2749 | while (t) | |
2750 | { | |
2751 | auto tn = t.next; | |
2752 | if (t.m_suspendagain && suspend(t)) | |
2753 | ++cnt; | |
2754 | t = tn; | |
2755 | } | |
2756 | if (cnt) | |
2757 | goto Lagain; | |
2758 | } | |
2759 | } | |
2760 | } | |
2761 | } | |
2762 | ||
2763 | /** | |
2764 | * Resume the specified thread and unload stack and register information. | |
2765 | * If the supplied thread is the calling thread, stack and register | |
2766 | * information will be unloaded but the thread will not be resumed. If | |
2767 | * the resume operation fails and the thread is not running then it will | |
2768 | * be removed from the global thread list, otherwise an exception will be | |
2769 | * thrown. | |
2770 | * | |
2771 | * Params: | |
2772 | * t = The thread to resume. | |
2773 | * | |
2774 | * Throws: | |
2775 | * ThreadError if the resume fails for a running thread. | |
2776 | */ | |
2777 | private void resume( Thread t ) nothrow | |
2778 | { | |
2779 | version (Windows) | |
2780 | { | |
2781 | if ( t.m_addr != GetCurrentThreadId() && ResumeThread( t.m_hndl ) == 0xFFFFFFFF ) | |
2782 | { | |
2783 | if ( !t.isRunning ) | |
2784 | { | |
2785 | Thread.remove( t ); | |
2786 | return; | |
2787 | } | |
2788 | onThreadError( "Unable to resume thread" ); | |
2789 | } | |
2790 | ||
2791 | if ( !t.m_lock ) | |
2792 | t.m_curr.tstack = t.m_curr.bstack; | |
2793 | t.m_reg[0 .. $] = 0; | |
2794 | } | |
2795 | else version (Darwin) | |
2796 | { | |
2797 | if ( t.m_addr != pthread_self() && thread_resume( t.m_tmach ) != KERN_SUCCESS ) | |
2798 | { | |
2799 | if ( !t.isRunning ) | |
2800 | { | |
2801 | Thread.remove( t ); | |
2802 | return; | |
2803 | } | |
2804 | onThreadError( "Unable to resume thread" ); | |
2805 | } | |
2806 | ||
2807 | if ( !t.m_lock ) | |
2808 | t.m_curr.tstack = t.m_curr.bstack; | |
2809 | t.m_reg[0 .. $] = 0; | |
2810 | } | |
2811 | else version (Posix) | |
2812 | { | |
2813 | if ( t.m_addr != pthread_self() ) | |
2814 | { | |
2815 | if ( pthread_kill( t.m_addr, resumeSignalNumber ) != 0 ) | |
2816 | { | |
2817 | if ( !t.isRunning ) | |
2818 | { | |
2819 | Thread.remove( t ); | |
2820 | return; | |
2821 | } | |
2822 | onThreadError( "Unable to resume thread" ); | |
2823 | } | |
2824 | } | |
2825 | else if ( !t.m_lock ) | |
2826 | { | |
2827 | t.m_curr.tstack = t.m_curr.bstack; | |
2828 | } | |
2829 | } | |
2830 | } | |
2831 | ||
2832 | /** | |
2833 | * Resume all threads but the calling thread for "stop the world" garbage | |
2834 | * collection runs. This function must be called once for each preceding | |
2835 | * call to thread_suspendAll before the threads are actually resumed. | |
2836 | * | |
2837 | * In: | |
2838 | * This routine must be preceded by a call to thread_suspendAll. | |
2839 | * | |
2840 | * Throws: | |
2841 | * ThreadError if the resume operation fails for a running thread. | |
2842 | */ | |
2843 | extern (C) void thread_resumeAll() nothrow | |
2844 | in | |
2845 | { | |
2846 | assert( suspendDepth > 0 ); | |
2847 | } | |
2848 | body | |
2849 | { | |
2850 | // NOTE: See thread_suspendAll for the logic behind this. | |
2851 | if ( !multiThreadedFlag && Thread.sm_tbeg ) | |
2852 | { | |
2853 | if ( --suspendDepth == 0 ) | |
2854 | resume( Thread.getThis() ); | |
2855 | return; | |
2856 | } | |
2857 | ||
2858 | scope(exit) Thread.slock.unlock_nothrow(); | |
2859 | { | |
2860 | if ( --suspendDepth > 0 ) | |
2861 | return; | |
2862 | ||
2863 | for ( Thread t = Thread.sm_tbeg; t; t = t.next ) | |
2864 | { | |
2865 | // NOTE: We do not need to care about critical regions at all | |
2866 | // here. thread_suspendAll takes care of everything. | |
2867 | resume( t ); | |
2868 | } | |
2869 | } | |
2870 | } | |
2871 | ||
2872 | /** | |
2873 | * Indicates the kind of scan being performed by $(D thread_scanAllType). | |
2874 | */ | |
2875 | enum ScanType | |
2876 | { | |
2877 | stack, /// The stack and/or registers are being scanned. | |
2878 | tls, /// TLS data is being scanned. | |
2879 | } | |
2880 | ||
2881 | alias ScanAllThreadsFn = void delegate(void*, void*) nothrow; /// The scanning function. | |
2882 | alias ScanAllThreadsTypeFn = void delegate(ScanType, void*, void*) nothrow; /// ditto | |
2883 | ||
2884 | /** | |
2885 | * The main entry point for garbage collection. The supplied delegate | |
2886 | * will be passed ranges representing both stack and register values. | |
2887 | * | |
2888 | * Params: | |
2889 | * scan = The scanner function. It should scan from p1 through p2 - 1. | |
2890 | * | |
2891 | * In: | |
2892 | * This routine must be preceded by a call to thread_suspendAll. | |
2893 | */ | |
2894 | extern (C) void thread_scanAllType( scope ScanAllThreadsTypeFn scan ) nothrow | |
2895 | in | |
2896 | { | |
2897 | assert( suspendDepth > 0 ); | |
2898 | } | |
2899 | body | |
2900 | { | |
2901 | callWithStackShell(sp => scanAllTypeImpl(scan, sp)); | |
2902 | } | |
2903 | ||
2904 | ||
2905 | private void scanAllTypeImpl( scope ScanAllThreadsTypeFn scan, void* curStackTop ) nothrow | |
2906 | { | |
2907 | Thread thisThread = null; | |
2908 | void* oldStackTop = null; | |
2909 | ||
2910 | if ( Thread.sm_tbeg ) | |
2911 | { | |
2912 | thisThread = Thread.getThis(); | |
2913 | if ( !thisThread.m_lock ) | |
2914 | { | |
2915 | oldStackTop = thisThread.m_curr.tstack; | |
2916 | thisThread.m_curr.tstack = curStackTop; | |
2917 | } | |
2918 | } | |
2919 | ||
2920 | scope( exit ) | |
2921 | { | |
2922 | if ( Thread.sm_tbeg ) | |
2923 | { | |
2924 | if ( !thisThread.m_lock ) | |
2925 | { | |
2926 | thisThread.m_curr.tstack = oldStackTop; | |
2927 | } | |
2928 | } | |
2929 | } | |
2930 | ||
2931 | // NOTE: Synchronizing on Thread.slock is not needed because this | |
2932 | // function may only be called after all other threads have | |
2933 | // been suspended from within the same lock. | |
2934 | if (Thread.nAboutToStart) | |
2935 | scan(ScanType.stack, Thread.pAboutToStart, Thread.pAboutToStart + Thread.nAboutToStart); | |
2936 | ||
2937 | for ( Thread.Context* c = Thread.sm_cbeg; c; c = c.next ) | |
2938 | { | |
2939 | version (StackGrowsDown) | |
2940 | { | |
2941 | // NOTE: We can't index past the bottom of the stack | |
2942 | // so don't do the "+1" for StackGrowsDown. | |
2943 | if ( c.tstack && c.tstack < c.bstack ) | |
2944 | scan( ScanType.stack, c.tstack, c.bstack ); | |
2945 | } | |
2946 | else | |
2947 | { | |
2948 | if ( c.bstack && c.bstack < c.tstack ) | |
2949 | scan( ScanType.stack, c.bstack, c.tstack + 1 ); | |
2950 | } | |
2951 | } | |
2952 | ||
2953 | for ( Thread t = Thread.sm_tbeg; t; t = t.next ) | |
2954 | { | |
2955 | version (Windows) | |
2956 | { | |
2957 | // Ideally, we'd pass ScanType.regs or something like that, but this | |
2958 | // would make portability annoying because it only makes sense on Windows. | |
2959 | scan( ScanType.stack, t.m_reg.ptr, t.m_reg.ptr + t.m_reg.length ); | |
2960 | } | |
2961 | ||
2962 | if (t.m_tlsgcdata !is null) | |
2963 | rt_tlsgc_scan(t.m_tlsgcdata, (p1, p2) => scan(ScanType.tls, p1, p2)); | |
2964 | } | |
2965 | } | |
2966 | ||
2967 | /** | |
2968 | * The main entry point for garbage collection. The supplied delegate | |
2969 | * will be passed ranges representing both stack and register values. | |
2970 | * | |
2971 | * Params: | |
2972 | * scan = The scanner function. It should scan from p1 through p2 - 1. | |
2973 | * | |
2974 | * In: | |
2975 | * This routine must be preceded by a call to thread_suspendAll. | |
2976 | */ | |
2977 | extern (C) void thread_scanAll( scope ScanAllThreadsFn scan ) nothrow | |
2978 | { | |
2979 | thread_scanAllType((type, p1, p2) => scan(p1, p2)); | |
2980 | } | |
2981 | ||
2982 | ||
2983 | /** | |
2984 | * Signals that the code following this call is a critical region. Any code in | |
2985 | * this region must finish running before the calling thread can be suspended | |
2986 | * by a call to thread_suspendAll. | |
2987 | * | |
2988 | * This function is, in particular, meant to help maintain garbage collector | |
2989 | * invariants when a lock is not used. | |
2990 | * | |
2991 | * A critical region is exited with thread_exitCriticalRegion. | |
2992 | * | |
2993 | * $(RED Warning): | |
2994 | * Using critical regions is extremely error-prone. For instance, using locks | |
2995 | * inside a critical region can easily result in a deadlock when another thread | |
2996 | * holding the lock already got suspended. | |
2997 | * | |
2998 | * The term and concept of a 'critical region' comes from | |
2999 | * $(LINK2 https://github.com/mono/mono/blob/521f4a198e442573c400835ef19bbb36b60b0ebb/mono/metadata/sgen-gc.h#L925 Mono's SGen garbage collector). | |
3000 | * | |
3001 | * In: | |
3002 | * The calling thread must be attached to the runtime. | |
3003 | */ | |
3004 | extern (C) void thread_enterCriticalRegion() @nogc | |
3005 | in | |
3006 | { | |
3007 | assert(Thread.getThis()); | |
3008 | } | |
3009 | body | |
3010 | { | |
3011 | synchronized (Thread.criticalRegionLock) | |
3012 | Thread.getThis().m_isInCriticalRegion = true; | |
3013 | } | |
3014 | ||
3015 | ||
3016 | /** | |
3017 | * Signals that the calling thread is no longer in a critical region. Following | |
3018 | * a call to this function, the thread can once again be suspended. | |
3019 | * | |
3020 | * In: | |
3021 | * The calling thread must be attached to the runtime. | |
3022 | */ | |
3023 | extern (C) void thread_exitCriticalRegion() @nogc | |
3024 | in | |
3025 | { | |
3026 | assert(Thread.getThis()); | |
3027 | } | |
3028 | body | |
3029 | { | |
3030 | synchronized (Thread.criticalRegionLock) | |
3031 | Thread.getThis().m_isInCriticalRegion = false; | |
3032 | } | |
3033 | ||
3034 | ||
3035 | /** | |
3036 | * Returns true if the current thread is in a critical region; otherwise, false. | |
3037 | * | |
3038 | * In: | |
3039 | * The calling thread must be attached to the runtime. | |
3040 | */ | |
3041 | extern (C) bool thread_inCriticalRegion() @nogc | |
3042 | in | |
3043 | { | |
3044 | assert(Thread.getThis()); | |
3045 | } | |
3046 | body | |
3047 | { | |
3048 | synchronized (Thread.criticalRegionLock) | |
3049 | return Thread.getThis().m_isInCriticalRegion; | |
3050 | } | |
3051 | ||
3052 | ||
3053 | /** | |
3054 | * A callback for thread errors in D during collections. Since an allocation is not possible | |
3055 | * a preallocated ThreadError will be used as the Error instance | |
3056 | * | |
3057 | * Throws: | |
3058 | * ThreadError. | |
3059 | */ | |
3060 | private void onThreadError(string msg = null, Throwable next = null) nothrow | |
3061 | { | |
3062 | __gshared ThreadError error = new ThreadError(null); | |
3063 | error.msg = msg; | |
3064 | error.next = next; | |
3065 | import core.exception : SuppressTraceInfo; | |
3066 | error.info = SuppressTraceInfo.instance; | |
3067 | throw error; | |
3068 | } | |
3069 | ||
3070 | ||
3071 | unittest | |
3072 | { | |
3073 | assert(!thread_inCriticalRegion()); | |
3074 | ||
3075 | { | |
3076 | thread_enterCriticalRegion(); | |
3077 | ||
3078 | scope (exit) | |
3079 | thread_exitCriticalRegion(); | |
3080 | ||
3081 | assert(thread_inCriticalRegion()); | |
3082 | } | |
3083 | ||
3084 | assert(!thread_inCriticalRegion()); | |
3085 | } | |
3086 | ||
3087 | unittest | |
3088 | { | |
3089 | // NOTE: This entire test is based on the assumption that no | |
3090 | // memory is allocated after the child thread is | |
3091 | // started. If an allocation happens, a collection could | |
3092 | // trigger, which would cause the synchronization below | |
3093 | // to cause a deadlock. | |
3094 | // NOTE: DO NOT USE LOCKS IN CRITICAL REGIONS IN NORMAL CODE. | |
3095 | ||
3096 | import core.sync.semaphore; | |
3097 | ||
3098 | auto sema = new Semaphore(), | |
3099 | semb = new Semaphore(); | |
3100 | ||
3101 | auto thr = new Thread( | |
3102 | { | |
3103 | thread_enterCriticalRegion(); | |
3104 | assert(thread_inCriticalRegion()); | |
3105 | sema.notify(); | |
3106 | ||
3107 | semb.wait(); | |
3108 | assert(thread_inCriticalRegion()); | |
3109 | ||
3110 | thread_exitCriticalRegion(); | |
3111 | assert(!thread_inCriticalRegion()); | |
3112 | sema.notify(); | |
3113 | ||
3114 | semb.wait(); | |
3115 | assert(!thread_inCriticalRegion()); | |
3116 | }); | |
3117 | ||
3118 | thr.start(); | |
3119 | ||
3120 | sema.wait(); | |
3121 | synchronized (Thread.criticalRegionLock) | |
3122 | assert(thr.m_isInCriticalRegion); | |
3123 | semb.notify(); | |
3124 | ||
3125 | sema.wait(); | |
3126 | synchronized (Thread.criticalRegionLock) | |
3127 | assert(!thr.m_isInCriticalRegion); | |
3128 | semb.notify(); | |
3129 | ||
3130 | thr.join(); | |
3131 | } | |
3132 | ||
3133 | unittest | |
3134 | { | |
3135 | import core.sync.semaphore; | |
3136 | ||
3137 | shared bool inCriticalRegion; | |
3138 | auto sema = new Semaphore(), | |
3139 | semb = new Semaphore(); | |
3140 | ||
3141 | auto thr = new Thread( | |
3142 | { | |
3143 | thread_enterCriticalRegion(); | |
3144 | inCriticalRegion = true; | |
3145 | sema.notify(); | |
3146 | semb.wait(); | |
3147 | ||
3148 | Thread.sleep(dur!"msecs"(1)); | |
3149 | inCriticalRegion = false; | |
3150 | thread_exitCriticalRegion(); | |
3151 | }); | |
3152 | thr.start(); | |
3153 | ||
3154 | sema.wait(); | |
3155 | assert(inCriticalRegion); | |
3156 | semb.notify(); | |
3157 | ||
3158 | thread_suspendAll(); | |
3159 | assert(!inCriticalRegion); | |
3160 | thread_resumeAll(); | |
3161 | } | |
3162 | ||
3163 | /** | |
3164 | * Indicates whether an address has been marked by the GC. | |
3165 | */ | |
3166 | enum IsMarked : int | |
3167 | { | |
3168 | no, /// Address is not marked. | |
3169 | yes, /// Address is marked. | |
3170 | unknown, /// Address is not managed by the GC. | |
3171 | } | |
3172 | ||
3173 | alias IsMarkedDg = int delegate( void* addr ) nothrow; /// The isMarked callback function. | |
3174 | ||
3175 | /** | |
3176 | * This routine allows the runtime to process any special per-thread handling | |
3177 | * for the GC. This is needed for taking into account any memory that is | |
3178 | * referenced by non-scanned pointers but is about to be freed. That currently | |
3179 | * means the array append cache. | |
3180 | * | |
3181 | * Params: | |
3182 | * isMarked = The function used to check if $(D addr) is marked. | |
3183 | * | |
3184 | * In: | |
3185 | * This routine must be called just prior to resuming all threads. | |
3186 | */ | |
3187 | extern(C) void thread_processGCMarks( scope IsMarkedDg isMarked ) nothrow | |
3188 | { | |
3189 | for ( Thread t = Thread.sm_tbeg; t; t = t.next ) | |
3190 | { | |
3191 | /* Can be null if collection was triggered between adding a | |
3192 | * thread and calling rt_tlsgc_init. | |
3193 | */ | |
3194 | if (t.m_tlsgcdata !is null) | |
3195 | rt_tlsgc_processGCMarks(t.m_tlsgcdata, isMarked); | |
3196 | } | |
3197 | } | |
3198 | ||
3199 | ||
3200 | extern (C) @nogc nothrow | |
3201 | { | |
3202 | version (CRuntime_Glibc) int pthread_getattr_np(pthread_t thread, pthread_attr_t* attr); | |
3203 | version (FreeBSD) int pthread_attr_get_np(pthread_t thread, pthread_attr_t* attr); | |
3204 | version (NetBSD) int pthread_attr_get_np(pthread_t thread, pthread_attr_t* attr); | |
3205 | version (Solaris) int thr_stksegment(stack_t* stk); | |
3206 | version (CRuntime_Bionic) int pthread_getattr_np(pthread_t thid, pthread_attr_t* attr); | |
3207 | } | |
3208 | ||
3209 | ||
3210 | private void* getStackTop() nothrow @nogc | |
3211 | { | |
3212 | version (D_InlineAsm_X86) | |
3213 | asm pure nothrow @nogc { naked; mov EAX, ESP; ret; } | |
3214 | else version (D_InlineAsm_X86_64) | |
3215 | asm pure nothrow @nogc { naked; mov RAX, RSP; ret; } | |
3216 | else version (GNU) | |
3217 | return __builtin_frame_address(0); | |
3218 | else | |
3219 | static assert(false, "Architecture not supported."); | |
3220 | } | |
3221 | ||
3222 | ||
3223 | private void* getStackBottom() nothrow @nogc | |
3224 | { | |
3225 | version (Windows) | |
3226 | { | |
3227 | version (D_InlineAsm_X86) | |
3228 | asm pure nothrow @nogc { naked; mov EAX, FS:4; ret; } | |
3229 | else version (D_InlineAsm_X86_64) | |
3230 | asm pure nothrow @nogc | |
3231 | { naked; | |
3232 | mov RAX, 8; | |
3233 | mov RAX, GS:[RAX]; | |
3234 | ret; | |
3235 | } | |
3236 | else version (GNU_InlineAsm) | |
3237 | { | |
3238 | void *bottom; | |
3239 | ||
3240 | version (X86) | |
3241 | asm pure nothrow @nogc { "movl %%fs:4, %0;" : "=r" bottom; } | |
3242 | else version (X86_64) | |
3243 | asm pure nothrow @nogc { "movq %%gs:8, %0;" : "=r" bottom; } | |
3244 | else | |
3245 | static assert(false, "Platform not supported."); | |
3246 | ||
3247 | return bottom; | |
3248 | } | |
3249 | else | |
3250 | static assert(false, "Architecture not supported."); | |
3251 | } | |
3252 | else version (Darwin) | |
3253 | { | |
3254 | import core.sys.darwin.pthread; | |
3255 | return pthread_get_stackaddr_np(pthread_self()); | |
3256 | } | |
3257 | else version (CRuntime_Glibc) | |
3258 | { | |
3259 | pthread_attr_t attr; | |
3260 | void* addr; size_t size; | |
3261 | ||
3262 | pthread_getattr_np(pthread_self(), &attr); | |
3263 | pthread_attr_getstack(&attr, &addr, &size); | |
3264 | pthread_attr_destroy(&attr); | |
d0a41de7 | 3265 | version (StackGrowsDown) |
3266 | addr += size; | |
3267 | return addr; | |
03385ed3 | 3268 | } |
3269 | else version (FreeBSD) | |
3270 | { | |
3271 | pthread_attr_t attr; | |
3272 | void* addr; size_t size; | |
3273 | ||
3274 | pthread_attr_init(&attr); | |
3275 | pthread_attr_get_np(pthread_self(), &attr); | |
3276 | pthread_attr_getstack(&attr, &addr, &size); | |
3277 | pthread_attr_destroy(&attr); | |
d0a41de7 | 3278 | version (StackGrowsDown) |
3279 | addr += size; | |
3280 | return addr; | |
03385ed3 | 3281 | } |
3282 | else version (NetBSD) | |
3283 | { | |
3284 | pthread_attr_t attr; | |
3285 | void* addr; size_t size; | |
3286 | ||
3287 | pthread_attr_init(&attr); | |
3288 | pthread_attr_get_np(pthread_self(), &attr); | |
3289 | pthread_attr_getstack(&attr, &addr, &size); | |
3290 | pthread_attr_destroy(&attr); | |
d0a41de7 | 3291 | version (StackGrowsDown) |
3292 | addr += size; | |
3293 | return addr; | |
03385ed3 | 3294 | } |
3295 | else version (Solaris) | |
3296 | { | |
3297 | stack_t stk; | |
3298 | ||
3299 | thr_stksegment(&stk); | |
3300 | return stk.ss_sp; | |
3301 | } | |
3302 | else version (CRuntime_Bionic) | |
3303 | { | |
3304 | pthread_attr_t attr; | |
3305 | void* addr; size_t size; | |
3306 | ||
3307 | pthread_getattr_np(pthread_self(), &attr); | |
3308 | pthread_attr_getstack(&attr, &addr, &size); | |
3309 | pthread_attr_destroy(&attr); | |
d0a41de7 | 3310 | version (StackGrowsDown) |
3311 | addr += size; | |
3312 | return addr; | |
03385ed3 | 3313 | } |
3314 | else | |
3315 | static assert(false, "Platform not supported."); | |
3316 | } | |
3317 | ||
3318 | ||
3319 | /** | |
3320 | * Returns the stack top of the currently active stack within the calling | |
3321 | * thread. | |
3322 | * | |
3323 | * In: | |
3324 | * The calling thread must be attached to the runtime. | |
3325 | * | |
3326 | * Returns: | |
3327 | * The address of the stack top. | |
3328 | */ | |
3329 | extern (C) void* thread_stackTop() nothrow @nogc | |
3330 | in | |
3331 | { | |
3332 | // Not strictly required, but it gives us more flexibility. | |
3333 | assert(Thread.getThis()); | |
3334 | } | |
3335 | body | |
3336 | { | |
3337 | return getStackTop(); | |
3338 | } | |
3339 | ||
3340 | ||
3341 | /** | |
3342 | * Returns the stack bottom of the currently active stack within the calling | |
3343 | * thread. | |
3344 | * | |
3345 | * In: | |
3346 | * The calling thread must be attached to the runtime. | |
3347 | * | |
3348 | * Returns: | |
3349 | * The address of the stack bottom. | |
3350 | */ | |
3351 | extern (C) void* thread_stackBottom() nothrow @nogc | |
3352 | in | |
3353 | { | |
3354 | assert(Thread.getThis()); | |
3355 | } | |
3356 | body | |
3357 | { | |
3358 | return Thread.getThis().topContext().bstack; | |
3359 | } | |
3360 | ||
3361 | ||
3362 | /////////////////////////////////////////////////////////////////////////////// | |
3363 | // Thread Group | |
3364 | /////////////////////////////////////////////////////////////////////////////// | |
3365 | ||
3366 | ||
3367 | /** | |
3368 | * This class is intended to simplify certain common programming techniques. | |
3369 | */ | |
3370 | class ThreadGroup | |
3371 | { | |
3372 | /** | |
3373 | * Creates and starts a new Thread object that executes fn and adds it to | |
3374 | * the list of tracked threads. | |
3375 | * | |
3376 | * Params: | |
3377 | * fn = The thread function. | |
3378 | * | |
3379 | * Returns: | |
3380 | * A reference to the newly created thread. | |
3381 | */ | |
3382 | final Thread create( void function() fn ) | |
3383 | { | |
3384 | Thread t = new Thread( fn ).start(); | |
3385 | ||
3386 | synchronized( this ) | |
3387 | { | |
3388 | m_all[t] = t; | |
3389 | } | |
3390 | return t; | |
3391 | } | |
3392 | ||
3393 | ||
3394 | /** | |
3395 | * Creates and starts a new Thread object that executes dg and adds it to | |
3396 | * the list of tracked threads. | |
3397 | * | |
3398 | * Params: | |
3399 | * dg = The thread function. | |
3400 | * | |
3401 | * Returns: | |
3402 | * A reference to the newly created thread. | |
3403 | */ | |
3404 | final Thread create( void delegate() dg ) | |
3405 | { | |
3406 | Thread t = new Thread( dg ).start(); | |
3407 | ||
3408 | synchronized( this ) | |
3409 | { | |
3410 | m_all[t] = t; | |
3411 | } | |
3412 | return t; | |
3413 | } | |
3414 | ||
3415 | ||
3416 | /** | |
3417 | * Add t to the list of tracked threads if it is not already being tracked. | |
3418 | * | |
3419 | * Params: | |
3420 | * t = The thread to add. | |
3421 | * | |
3422 | * In: | |
3423 | * t must not be null. | |
3424 | */ | |
3425 | final void add( Thread t ) | |
3426 | in | |
3427 | { | |
3428 | assert( t ); | |
3429 | } | |
3430 | body | |
3431 | { | |
3432 | synchronized( this ) | |
3433 | { | |
3434 | m_all[t] = t; | |
3435 | } | |
3436 | } | |
3437 | ||
3438 | ||
3439 | /** | |
3440 | * Removes t from the list of tracked threads. No operation will be | |
3441 | * performed if t is not currently being tracked by this object. | |
3442 | * | |
3443 | * Params: | |
3444 | * t = The thread to remove. | |
3445 | * | |
3446 | * In: | |
3447 | * t must not be null. | |
3448 | */ | |
3449 | final void remove( Thread t ) | |
3450 | in | |
3451 | { | |
3452 | assert( t ); | |
3453 | } | |
3454 | body | |
3455 | { | |
3456 | synchronized( this ) | |
3457 | { | |
3458 | m_all.remove( t ); | |
3459 | } | |
3460 | } | |
3461 | ||
3462 | ||
3463 | /** | |
3464 | * Operates on all threads currently tracked by this object. | |
3465 | */ | |
3466 | final int opApply( scope int delegate( ref Thread ) dg ) | |
3467 | { | |
3468 | synchronized( this ) | |
3469 | { | |
3470 | int ret = 0; | |
3471 | ||
3472 | // NOTE: This loop relies on the knowledge that m_all uses the | |
3473 | // Thread object for both the key and the mapped value. | |
3474 | foreach ( Thread t; m_all.keys ) | |
3475 | { | |
3476 | ret = dg( t ); | |
3477 | if ( ret ) | |
3478 | break; | |
3479 | } | |
3480 | return ret; | |
3481 | } | |
3482 | } | |
3483 | ||
3484 | ||
3485 | /** | |
3486 | * Iteratively joins all tracked threads. This function will block add, | |
3487 | * remove, and opApply until it completes. | |
3488 | * | |
3489 | * Params: | |
3490 | * rethrow = Rethrow any unhandled exception which may have caused the | |
3491 | * current thread to terminate. | |
3492 | * | |
3493 | * Throws: | |
3494 | * Any exception not handled by the joined threads. | |
3495 | */ | |
3496 | final void joinAll( bool rethrow = true ) | |
3497 | { | |
3498 | synchronized( this ) | |
3499 | { | |
3500 | // NOTE: This loop relies on the knowledge that m_all uses the | |
3501 | // Thread object for both the key and the mapped value. | |
3502 | foreach ( Thread t; m_all.keys ) | |
3503 | { | |
3504 | t.join( rethrow ); | |
3505 | } | |
3506 | } | |
3507 | } | |
3508 | ||
3509 | ||
3510 | private: | |
3511 | Thread[Thread] m_all; | |
3512 | } | |
3513 | ||
3514 | ||
3515 | /////////////////////////////////////////////////////////////////////////////// | |
3516 | // Fiber Platform Detection and Memory Allocation | |
3517 | /////////////////////////////////////////////////////////////////////////////// | |
3518 | ||
3519 | ||
3520 | private | |
3521 | { | |
3522 | version (D_InlineAsm_X86) | |
3523 | { | |
3524 | version (Windows) | |
3525 | version = AsmX86_Windows; | |
3526 | else version (Posix) | |
3527 | version = AsmX86_Posix; | |
3528 | ||
3529 | version (Darwin) | |
3530 | version = AlignFiberStackTo16Byte; | |
3531 | } | |
3532 | else version (D_InlineAsm_X86_64) | |
3533 | { | |
3534 | version (Windows) | |
3535 | { | |
3536 | version = AsmX86_64_Windows; | |
3537 | version = AlignFiberStackTo16Byte; | |
3538 | } | |
3539 | else version (Posix) | |
3540 | { | |
3541 | version = AsmX86_64_Posix; | |
3542 | version = AlignFiberStackTo16Byte; | |
3543 | } | |
3544 | } | |
3545 | else version (X86) | |
3546 | { | |
3547 | version = AsmExternal; | |
3548 | ||
3549 | version (MinGW) | |
3550 | { | |
3551 | version = GNU_AsmX86_Windows; | |
3552 | version = AlignFiberStackTo16Byte; | |
3553 | } | |
3554 | else version (Posix) | |
3555 | { | |
3556 | version = AsmX86_Posix; | |
3557 | version (OSX) | |
3558 | version = AlignFiberStackTo16Byte; | |
3559 | } | |
3560 | } | |
3561 | else version (X86_64) | |
3562 | { | |
3563 | version (D_X32) | |
3564 | { | |
3565 | // let X32 be handled by ucontext swapcontext | |
3566 | } | |
3567 | else | |
3568 | { | |
3569 | version = AsmExternal; | |
3570 | version = AlignFiberStackTo16Byte; | |
3571 | ||
3572 | version (MinGW) | |
3573 | version = GNU_AsmX86_64_Windows; | |
3574 | else version (Posix) | |
3575 | version = AsmX86_64_Posix; | |
3576 | } | |
3577 | } | |
3578 | else version (PPC) | |
3579 | { | |
3580 | version (Posix) | |
3581 | { | |
3582 | version = AsmPPC_Posix; | |
3583 | version = AsmExternal; | |
3584 | } | |
3585 | } | |
3586 | else version (PPC64) | |
3587 | { | |
3588 | version (Posix) | |
3589 | { | |
3590 | version = AlignFiberStackTo16Byte; | |
3591 | } | |
3592 | } | |
3593 | else version (MIPS_O32) | |
3594 | { | |
3595 | version (Posix) | |
3596 | { | |
3597 | version = AsmMIPS_O32_Posix; | |
3598 | version = AsmExternal; | |
3599 | } | |
3600 | } | |
7c871402 | 3601 | else version (AArch64) |
3602 | { | |
3603 | version (Posix) | |
3604 | { | |
3605 | version = AsmAArch64_Posix; | |
3606 | version = AsmExternal; | |
3607 | version = AlignFiberStackTo16Byte; | |
3608 | } | |
3609 | } | |
03385ed3 | 3610 | else version (ARM) |
3611 | { | |
3612 | version (Posix) | |
3613 | { | |
3614 | version = AsmARM_Posix; | |
3615 | version = AsmExternal; | |
3616 | } | |
3617 | } | |
c5d64f62 | 3618 | else version (SPARC) |
3619 | { | |
3620 | // NOTE: The SPARC ABI specifies only doubleword alignment. | |
3621 | version = AlignFiberStackTo16Byte; | |
3622 | } | |
3623 | else version (SPARC64) | |
3624 | { | |
3625 | version = AlignFiberStackTo16Byte; | |
3626 | } | |
03385ed3 | 3627 | |
3628 | version (Posix) | |
3629 | { | |
3630 | import core.sys.posix.unistd; // for sysconf | |
3631 | ||
3632 | version (AsmX86_Windows) {} else | |
3633 | version (AsmX86_Posix) {} else | |
3634 | version (AsmX86_64_Windows) {} else | |
3635 | version (AsmX86_64_Posix) {} else | |
3636 | version (AsmExternal) {} else | |
3637 | { | |
3638 | // NOTE: The ucontext implementation requires architecture specific | |
3639 | // data definitions to operate so testing for it must be done | |
3640 | // by checking for the existence of ucontext_t rather than by | |
3641 | // a version identifier. Please note that this is considered | |
3642 | // an obsolescent feature according to the POSIX spec, so a | |
3643 | // custom solution is still preferred. | |
3644 | import core.sys.posix.ucontext; | |
3645 | } | |
3646 | } | |
3647 | ||
3648 | static immutable size_t PAGESIZE; | |
3649 | version (Posix) static immutable size_t PTHREAD_STACK_MIN; | |
3650 | } | |
3651 | ||
3652 | ||
3653 | shared static this() | |
3654 | { | |
3655 | version (Windows) | |
3656 | { | |
3657 | SYSTEM_INFO info; | |
3658 | GetSystemInfo(&info); | |
3659 | ||
3660 | PAGESIZE = info.dwPageSize; | |
3661 | assert(PAGESIZE < int.max); | |
3662 | } | |
3663 | else version (Posix) | |
3664 | { | |
3665 | PAGESIZE = cast(size_t)sysconf(_SC_PAGESIZE); | |
3666 | PTHREAD_STACK_MIN = cast(size_t)sysconf(_SC_THREAD_STACK_MIN); | |
3667 | } | |
3668 | else | |
3669 | { | |
3670 | static assert(0, "unimplemented"); | |
3671 | } | |
3672 | } | |
3673 | ||
3674 | ||
3675 | /////////////////////////////////////////////////////////////////////////////// | |
3676 | // Fiber Entry Point and Context Switch | |
3677 | /////////////////////////////////////////////////////////////////////////////// | |
3678 | ||
3679 | ||
3680 | private | |
3681 | { | |
3682 | extern (C) void fiber_entryPoint() nothrow | |
3683 | { | |
3684 | Fiber obj = Fiber.getThis(); | |
3685 | assert( obj ); | |
3686 | ||
3687 | assert( Thread.getThis().m_curr is obj.m_ctxt ); | |
3688 | atomicStore!(MemoryOrder.raw)(*cast(shared)&Thread.getThis().m_lock, false); | |
3689 | obj.m_ctxt.tstack = obj.m_ctxt.bstack; | |
3690 | obj.m_state = Fiber.State.EXEC; | |
3691 | ||
3692 | try | |
3693 | { | |
3694 | obj.run(); | |
3695 | } | |
3696 | catch ( Throwable t ) | |
3697 | { | |
3698 | obj.m_unhandled = t; | |
3699 | } | |
3700 | ||
3701 | static if ( __traits( compiles, ucontext_t ) ) | |
3702 | obj.m_ucur = &obj.m_utxt; | |
3703 | ||
3704 | obj.m_state = Fiber.State.TERM; | |
3705 | obj.switchOut(); | |
3706 | } | |
3707 | ||
3708 | // Look above the definition of 'class Fiber' for some information about the implementation of this routine | |
3709 | version (AsmExternal) | |
7c871402 | 3710 | { |
3711 | extern (C) void fiber_switchContext( void** oldp, void* newp ) nothrow @nogc; | |
3712 | version (AArch64) | |
3713 | extern (C) void fiber_trampoline() nothrow; | |
3714 | } | |
03385ed3 | 3715 | else |
3716 | extern (C) void fiber_switchContext( void** oldp, void* newp ) nothrow @nogc | |
3717 | { | |
3718 | // NOTE: The data pushed and popped in this routine must match the | |
3719 | // default stack created by Fiber.initStack or the initial | |
3720 | // switch into a new context will fail. | |
3721 | ||
3722 | version (AsmX86_Windows) | |
3723 | { | |
3724 | asm pure nothrow @nogc | |
3725 | { | |
3726 | naked; | |
3727 | ||
3728 | // save current stack state | |
3729 | push EBP; | |
3730 | mov EBP, ESP; | |
3731 | push EDI; | |
3732 | push ESI; | |
3733 | push EBX; | |
3734 | push dword ptr FS:[0]; | |
3735 | push dword ptr FS:[4]; | |
3736 | push dword ptr FS:[8]; | |
3737 | push EAX; | |
3738 | ||
3739 | // store oldp again with more accurate address | |
3740 | mov EAX, dword ptr 8[EBP]; | |
3741 | mov [EAX], ESP; | |
3742 | // load newp to begin context switch | |
3743 | mov ESP, dword ptr 12[EBP]; | |
3744 | ||
3745 | // load saved state from new stack | |
3746 | pop EAX; | |
3747 | pop dword ptr FS:[8]; | |
3748 | pop dword ptr FS:[4]; | |
3749 | pop dword ptr FS:[0]; | |
3750 | pop EBX; | |
3751 | pop ESI; | |
3752 | pop EDI; | |
3753 | pop EBP; | |
3754 | ||
3755 | // 'return' to complete switch | |
3756 | pop ECX; | |
3757 | jmp ECX; | |
3758 | } | |
3759 | } | |
3760 | else version (AsmX86_64_Windows) | |
3761 | { | |
3762 | asm pure nothrow @nogc | |
3763 | { | |
3764 | naked; | |
3765 | ||
3766 | // save current stack state | |
3767 | // NOTE: When changing the layout of registers on the stack, | |
3768 | // make sure that the XMM registers are still aligned. | |
3769 | // On function entry, the stack is guaranteed to not | |
3770 | // be aligned to 16 bytes because of the return address | |
3771 | // on the stack. | |
3772 | push RBP; | |
3773 | mov RBP, RSP; | |
3774 | push R12; | |
3775 | push R13; | |
3776 | push R14; | |
3777 | push R15; | |
3778 | push RDI; | |
3779 | push RSI; | |
3780 | // 7 registers = 56 bytes; stack is now aligned to 16 bytes | |
3781 | sub RSP, 160; | |
3782 | movdqa [RSP + 144], XMM6; | |
3783 | movdqa [RSP + 128], XMM7; | |
3784 | movdqa [RSP + 112], XMM8; | |
3785 | movdqa [RSP + 96], XMM9; | |
3786 | movdqa [RSP + 80], XMM10; | |
3787 | movdqa [RSP + 64], XMM11; | |
3788 | movdqa [RSP + 48], XMM12; | |
3789 | movdqa [RSP + 32], XMM13; | |
3790 | movdqa [RSP + 16], XMM14; | |
3791 | movdqa [RSP], XMM15; | |
3792 | push RBX; | |
3793 | xor RAX,RAX; | |
3794 | push qword ptr GS:[RAX]; | |
3795 | push qword ptr GS:8[RAX]; | |
3796 | push qword ptr GS:16[RAX]; | |
3797 | ||
3798 | // store oldp | |
3799 | mov [RCX], RSP; | |
3800 | // load newp to begin context switch | |
3801 | mov RSP, RDX; | |
3802 | ||
3803 | // load saved state from new stack | |
3804 | pop qword ptr GS:16[RAX]; | |
3805 | pop qword ptr GS:8[RAX]; | |
3806 | pop qword ptr GS:[RAX]; | |
3807 | pop RBX; | |
3808 | movdqa XMM15, [RSP]; | |
3809 | movdqa XMM14, [RSP + 16]; | |
3810 | movdqa XMM13, [RSP + 32]; | |
3811 | movdqa XMM12, [RSP + 48]; | |
3812 | movdqa XMM11, [RSP + 64]; | |
3813 | movdqa XMM10, [RSP + 80]; | |
3814 | movdqa XMM9, [RSP + 96]; | |
3815 | movdqa XMM8, [RSP + 112]; | |
3816 | movdqa XMM7, [RSP + 128]; | |
3817 | movdqa XMM6, [RSP + 144]; | |
3818 | add RSP, 160; | |
3819 | pop RSI; | |
3820 | pop RDI; | |
3821 | pop R15; | |
3822 | pop R14; | |
3823 | pop R13; | |
3824 | pop R12; | |
3825 | pop RBP; | |
3826 | ||
3827 | // 'return' to complete switch | |
3828 | pop RCX; | |
3829 | jmp RCX; | |
3830 | } | |
3831 | } | |
3832 | else version (AsmX86_Posix) | |
3833 | { | |
3834 | asm pure nothrow @nogc | |
3835 | { | |
3836 | naked; | |
3837 | ||
3838 | // save current stack state | |
3839 | push EBP; | |
3840 | mov EBP, ESP; | |
3841 | push EDI; | |
3842 | push ESI; | |
3843 | push EBX; | |
3844 | push EAX; | |
3845 | ||
3846 | // store oldp again with more accurate address | |
3847 | mov EAX, dword ptr 8[EBP]; | |
3848 | mov [EAX], ESP; | |
3849 | // load newp to begin context switch | |
3850 | mov ESP, dword ptr 12[EBP]; | |
3851 | ||
3852 | // load saved state from new stack | |
3853 | pop EAX; | |
3854 | pop EBX; | |
3855 | pop ESI; | |
3856 | pop EDI; | |
3857 | pop EBP; | |
3858 | ||
3859 | // 'return' to complete switch | |
3860 | pop ECX; | |
3861 | jmp ECX; | |
3862 | } | |
3863 | } | |
3864 | else version (AsmX86_64_Posix) | |
3865 | { | |
3866 | asm pure nothrow @nogc | |
3867 | { | |
3868 | naked; | |
3869 | ||
3870 | // save current stack state | |
3871 | push RBP; | |
3872 | mov RBP, RSP; | |
3873 | push RBX; | |
3874 | push R12; | |
3875 | push R13; | |
3876 | push R14; | |
3877 | push R15; | |
3878 | ||
3879 | // store oldp | |
3880 | mov [RDI], RSP; | |
3881 | // load newp to begin context switch | |
3882 | mov RSP, RSI; | |
3883 | ||
3884 | // load saved state from new stack | |
3885 | pop R15; | |
3886 | pop R14; | |
3887 | pop R13; | |
3888 | pop R12; | |
3889 | pop RBX; | |
3890 | pop RBP; | |
3891 | ||
3892 | // 'return' to complete switch | |
3893 | pop RCX; | |
3894 | jmp RCX; | |
3895 | } | |
3896 | } | |
3897 | else static if ( __traits( compiles, ucontext_t ) ) | |
3898 | { | |
3899 | Fiber cfib = Fiber.getThis(); | |
3900 | void* ucur = cfib.m_ucur; | |
3901 | ||
3902 | *oldp = &ucur; | |
3903 | swapcontext( **(cast(ucontext_t***) oldp), | |
3904 | *(cast(ucontext_t**) newp) ); | |
3905 | } | |
3906 | else | |
3907 | static assert(0, "Not implemented"); | |
3908 | } | |
3909 | } | |
3910 | ||
3911 | ||
3912 | /////////////////////////////////////////////////////////////////////////////// | |
3913 | // Fiber | |
3914 | /////////////////////////////////////////////////////////////////////////////// | |
3915 | /* | |
3916 | * Documentation of Fiber internals: | |
3917 | * | |
3918 | * The main routines to implement when porting Fibers to new architectures are | |
3919 | * fiber_switchContext and initStack. Some version constants have to be defined | |
3920 | * for the new platform as well, search for "Fiber Platform Detection and Memory Allocation". | |
3921 | * | |
3922 | * Fibers are based on a concept called 'Context'. A Context describes the execution | |
3923 | * state of a Fiber or main thread which is fully described by the stack, some | |
3924 | * registers and a return address at which the Fiber/Thread should continue executing. | |
3925 | * Please note that not only each Fiber has a Context, but each thread also has got a | |
3926 | * Context which describes the threads stack and state. If you call Fiber fib; fib.call | |
3927 | * the first time in a thread you switch from Threads Context into the Fibers Context. | |
3928 | * If you call fib.yield in that Fiber you switch out of the Fibers context and back | |
3929 | * into the Thread Context. (However, this is not always the case. You can call a Fiber | |
3930 | * from within another Fiber, then you switch Contexts between the Fibers and the Thread | |
3931 | * Context is not involved) | |
3932 | * | |
3933 | * In all current implementations the registers and the return address are actually | |
3934 | * saved on a Contexts stack. | |
3935 | * | |
3936 | * The fiber_switchContext routine has got two parameters: | |
3937 | * void** a: This is the _location_ where we have to store the current stack pointer, | |
3938 | * the stack pointer of the currently executing Context (Fiber or Thread). | |
3939 | * void* b: This is the pointer to the stack of the Context which we want to switch into. | |
3940 | * Note that we get the same pointer here as the one we stored into the void** a | |
3941 | * in a previous call to fiber_switchContext. | |
3942 | * | |
3943 | * In the simplest case, a fiber_switchContext rountine looks like this: | |
3944 | * fiber_switchContext: | |
3945 | * push {return Address} | |
3946 | * push {registers} | |
3947 | * copy {stack pointer} into {location pointed to by a} | |
3948 | * //We have now switch to the stack of a different Context! | |
3949 | * copy {b} into {stack pointer} | |
3950 | * pop {registers} | |
3951 | * pop {return Address} | |
3952 | * jump to {return Address} | |
3953 | * | |
3954 | * The GC uses the value returned in parameter a to scan the Fibers stack. It scans from | |
3955 | * the stack base to that value. As the GC dislikes false pointers we can actually optimize | |
3956 | * this a little: By storing registers which can not contain references to memory managed | |
3957 | * by the GC outside of the region marked by the stack base pointer and the stack pointer | |
3958 | * saved in fiber_switchContext we can prevent the GC from scanning them. | |
3959 | * Such registers are usually floating point registers and the return address. In order to | |
3960 | * implement this, we return a modified stack pointer from fiber_switchContext. However, | |
3961 | * we have to remember that when we restore the registers from the stack! | |
3962 | * | |
3963 | * --------------------------- <= Stack Base | |
3964 | * | Frame | <= Many other stack frames | |
3965 | * | Frame | | |
3966 | * |-------------------------| <= The last stack frame. This one is created by fiber_switchContext | |
3967 | * | registers with pointers | | |
3968 | * | | <= Stack pointer. GC stops scanning here | |
3969 | * | return address | | |
3970 | * |floating point registers | | |
3971 | * --------------------------- <= Real Stack End | |
3972 | * | |
3973 | * fiber_switchContext: | |
3974 | * push {registers with pointers} | |
3975 | * copy {stack pointer} into {location pointed to by a} | |
3976 | * push {return Address} | |
3977 | * push {Floating point registers} | |
3978 | * //We have now switch to the stack of a different Context! | |
3979 | * copy {b} into {stack pointer} | |
3980 | * //We now have to adjust the stack pointer to point to 'Real Stack End' so we can pop | |
3981 | * //the FP registers | |
3982 | * //+ or - depends on if your stack grows downwards or upwards | |
3983 | * {stack pointer} = {stack pointer} +- ({FPRegisters}.sizeof + {return address}.sizeof} | |
3984 | * pop {Floating point registers} | |
3985 | * pop {return Address} | |
3986 | * pop {registers with pointers} | |
3987 | * jump to {return Address} | |
3988 | * | |
3989 | * So the question now is which registers need to be saved? This depends on the specific | |
3990 | * architecture ABI of course, but here are some general guidelines: | |
3991 | * - If a register is callee-save (if the callee modifies the register it must saved and | |
3992 | * restored by the callee) it needs to be saved/restored in switchContext | |
3993 | * - If a register is caller-save it needn't be saved/restored. (Calling fiber_switchContext | |
3994 | * is a function call and the compiler therefore already must save these registers before | |
3995 | * calling fiber_switchContext) | |
3996 | * - Argument registers used for passing parameters to functions needn't be saved/restored | |
3997 | * - The return register needn't be saved/restored (fiber_switchContext hasn't got a return type) | |
3998 | * - All scratch registers needn't be saved/restored | |
3999 | * - The link register usually needn't be saved/restored (but sometimes it must be cleared - | |
4000 | * see below for details) | |
4001 | * - The frame pointer register - if it exists - is usually callee-save | |
4002 | * - All current implementations do not save control registers | |
4003 | * | |
4004 | * What happens on the first switch into a Fiber? We never saved a state for this fiber before, | |
4005 | * but the initial state is prepared in the initStack routine. (This routine will also be called | |
4006 | * when a Fiber is being resetted). initStack must produce exactly the same stack layout as the | |
4007 | * part of fiber_switchContext which saves the registers. Pay special attention to set the stack | |
4008 | * pointer correctly if you use the GC optimization mentioned before. the return Address saved in | |
4009 | * initStack must be the address of fiber_entrypoint. | |
4010 | * | |
4011 | * There's now a small but important difference between the first context switch into a fiber and | |
4012 | * further context switches. On the first switch, Fiber.call is used and the returnAddress in | |
4013 | * fiber_switchContext will point to fiber_entrypoint. The important thing here is that this jump | |
4014 | * is a _function call_, we call fiber_entrypoint by jumping before it's function prologue. On later | |
4015 | * calls, the user used yield() in a function, and therefore the return address points into a user | |
4016 | * function, after the yield call. So here the jump in fiber_switchContext is a _function return_, | |
4017 | * not a function call! | |
4018 | * | |
4019 | * The most important result of this is that on entering a function, i.e. fiber_entrypoint, we | |
4020 | * would have to provide a return address / set the link register once fiber_entrypoint | |
4021 | * returns. Now fiber_entrypoint does never return and therefore the actual value of the return | |
4022 | * address / link register is never read/used and therefore doesn't matter. When fiber_switchContext | |
4023 | * performs a _function return_ the value in the link register doesn't matter either. | |
4024 | * However, the link register will still be saved to the stack in fiber_entrypoint and some | |
4025 | * exception handling / stack unwinding code might read it from this stack location and crash. | |
4026 | * The exact solution depends on your architecture, but see the ARM implementation for a way | |
4027 | * to deal with this issue. | |
4028 | * | |
4029 | * The ARM implementation is meant to be used as a kind of documented example implementation. | |
4030 | * Look there for a concrete example. | |
4031 | * | |
4032 | * FIXME: fiber_entrypoint might benefit from a @noreturn attribute, but D doesn't have one. | |
4033 | */ | |
4034 | ||
4035 | /** | |
4036 | * This class provides a cooperative concurrency mechanism integrated with the | |
4037 | * threading and garbage collection functionality. Calling a fiber may be | |
4038 | * considered a blocking operation that returns when the fiber yields (via | |
4039 | * Fiber.yield()). Execution occurs within the context of the calling thread | |
4040 | * so synchronization is not necessary to guarantee memory visibility so long | |
4041 | * as the same thread calls the fiber each time. Please note that there is no | |
4042 | * requirement that a fiber be bound to one specific thread. Rather, fibers | |
4043 | * may be freely passed between threads so long as they are not currently | |
4044 | * executing. Like threads, a new fiber thread may be created using either | |
4045 | * derivation or composition, as in the following example. | |
4046 | * | |
4047 | * Warning: | |
4048 | * Status registers are not saved by the current implementations. This means | |
4049 | * floating point exception status bits (overflow, divide by 0), rounding mode | |
4050 | * and similar stuff is set per-thread, not per Fiber! | |
4051 | * | |
4052 | * Warning: | |
4053 | * On ARM FPU registers are not saved if druntime was compiled as ARM_SoftFloat. | |
4054 | * If such a build is used on a ARM_SoftFP system which actually has got a FPU | |
4055 | * and other libraries are using the FPU registers (other code is compiled | |
4056 | * as ARM_SoftFP) this can cause problems. Druntime must be compiled as | |
4057 | * ARM_SoftFP in this case. | |
4058 | * | |
4059 | * Example: | |
4060 | * ---------------------------------------------------------------------- | |
4061 | * | |
4062 | * class DerivedFiber : Fiber | |
4063 | * { | |
4064 | * this() | |
4065 | * { | |
4066 | * super( &run ); | |
4067 | * } | |
4068 | * | |
4069 | * private : | |
4070 | * void run() | |
4071 | * { | |
4072 | * printf( "Derived fiber running.\n" ); | |
4073 | * } | |
4074 | * } | |
4075 | * | |
4076 | * void fiberFunc() | |
4077 | * { | |
4078 | * printf( "Composed fiber running.\n" ); | |
4079 | * Fiber.yield(); | |
4080 | * printf( "Composed fiber running.\n" ); | |
4081 | * } | |
4082 | * | |
4083 | * // create instances of each type | |
4084 | * Fiber derived = new DerivedFiber(); | |
4085 | * Fiber composed = new Fiber( &fiberFunc ); | |
4086 | * | |
4087 | * // call both fibers once | |
4088 | * derived.call(); | |
4089 | * composed.call(); | |
4090 | * printf( "Execution returned to calling context.\n" ); | |
4091 | * composed.call(); | |
4092 | * | |
4093 | * // since each fiber has run to completion, each should have state TERM | |
4094 | * assert( derived.state == Fiber.State.TERM ); | |
4095 | * assert( composed.state == Fiber.State.TERM ); | |
4096 | * | |
4097 | * ---------------------------------------------------------------------- | |
4098 | * | |
4099 | * Authors: Based on a design by Mikola Lysenko. | |
4100 | */ | |
4101 | class Fiber | |
4102 | { | |
4103 | /////////////////////////////////////////////////////////////////////////// | |
4104 | // Initialization | |
4105 | /////////////////////////////////////////////////////////////////////////// | |
4106 | ||
4107 | ||
4108 | /** | |
4109 | * Initializes a fiber object which is associated with a static | |
4110 | * D function. | |
4111 | * | |
4112 | * Params: | |
4113 | * fn = The fiber function. | |
4114 | * sz = The stack size for this fiber. | |
4115 | * guardPageSize = size of the guard page to trap fiber's stack | |
4116 | * overflows | |
4117 | * | |
4118 | * In: | |
4119 | * fn must not be null. | |
4120 | */ | |
4121 | this( void function() fn, size_t sz = PAGESIZE*4, | |
4122 | size_t guardPageSize = PAGESIZE ) nothrow | |
4123 | in | |
4124 | { | |
4125 | assert( fn ); | |
4126 | } | |
4127 | body | |
4128 | { | |
4129 | allocStack( sz, guardPageSize ); | |
4130 | reset( fn ); | |
4131 | } | |
4132 | ||
4133 | ||
4134 | /** | |
4135 | * Initializes a fiber object which is associated with a dynamic | |
4136 | * D function. | |
4137 | * | |
4138 | * Params: | |
4139 | * dg = The fiber function. | |
4140 | * sz = The stack size for this fiber. | |
4141 | * guardPageSize = size of the guard page to trap fiber's stack | |
4142 | * overflows | |
4143 | * | |
4144 | * In: | |
4145 | * dg must not be null. | |
4146 | */ | |
4147 | this( void delegate() dg, size_t sz = PAGESIZE*4, | |
4148 | size_t guardPageSize = PAGESIZE ) nothrow | |
4149 | in | |
4150 | { | |
4151 | assert( dg ); | |
4152 | } | |
4153 | body | |
4154 | { | |
4155 | allocStack( sz, guardPageSize); | |
4156 | reset( dg ); | |
4157 | } | |
4158 | ||
4159 | ||
4160 | /** | |
4161 | * Cleans up any remaining resources used by this object. | |
4162 | */ | |
4163 | ~this() nothrow @nogc | |
4164 | { | |
4165 | // NOTE: A live reference to this object will exist on its associated | |
4166 | // stack from the first time its call() method has been called | |
4167 | // until its execution completes with State.TERM. Thus, the only | |
4168 | // times this dtor should be called are either if the fiber has | |
4169 | // terminated (and therefore has no active stack) or if the user | |
4170 | // explicitly deletes this object. The latter case is an error | |
4171 | // but is not easily tested for, since State.HOLD may imply that | |
4172 | // the fiber was just created but has never been run. There is | |
4173 | // not a compelling case to create a State.INIT just to offer a | |
4174 | // means of ensuring the user isn't violating this object's | |
4175 | // contract, so for now this requirement will be enforced by | |
4176 | // documentation only. | |
4177 | freeStack(); | |
4178 | } | |
4179 | ||
4180 | ||
4181 | /////////////////////////////////////////////////////////////////////////// | |
4182 | // General Actions | |
4183 | /////////////////////////////////////////////////////////////////////////// | |
4184 | ||
4185 | ||
4186 | /** | |
4187 | * Transfers execution to this fiber object. The calling context will be | |
4188 | * suspended until the fiber calls Fiber.yield() or until it terminates | |
4189 | * via an unhandled exception. | |
4190 | * | |
4191 | * Params: | |
4192 | * rethrow = Rethrow any unhandled exception which may have caused this | |
4193 | * fiber to terminate. | |
4194 | * | |
4195 | * In: | |
4196 | * This fiber must be in state HOLD. | |
4197 | * | |
4198 | * Throws: | |
4199 | * Any exception not handled by the joined thread. | |
4200 | * | |
4201 | * Returns: | |
4202 | * Any exception not handled by this fiber if rethrow = false, null | |
4203 | * otherwise. | |
4204 | */ | |
4205 | // Not marked with any attributes, even though `nothrow @nogc` works | |
4206 | // because it calls arbitrary user code. Most of the implementation | |
4207 | // is already `@nogc nothrow`, but in order for `Fiber.call` to | |
4208 | // propagate the attributes of the user's function, the Fiber | |
4209 | // class needs to be templated. | |
4210 | final Throwable call( Rethrow rethrow = Rethrow.yes ) | |
4211 | { | |
4212 | return rethrow ? call!(Rethrow.yes)() : call!(Rethrow.no); | |
4213 | } | |
4214 | ||
4215 | /// ditto | |
4216 | final Throwable call( Rethrow rethrow )() | |
4217 | { | |
4218 | callImpl(); | |
4219 | if ( m_unhandled ) | |
4220 | { | |
4221 | Throwable t = m_unhandled; | |
4222 | m_unhandled = null; | |
4223 | static if ( rethrow ) | |
4224 | throw t; | |
4225 | else | |
4226 | return t; | |
4227 | } | |
4228 | return null; | |
4229 | } | |
4230 | ||
4231 | /// ditto | |
4232 | deprecated("Please pass Fiber.Rethrow.yes or .no instead of a boolean.") | |
4233 | final Throwable call( bool rethrow ) | |
4234 | { | |
4235 | return rethrow ? call!(Rethrow.yes)() : call!(Rethrow.no); | |
4236 | } | |
4237 | ||
4238 | private void callImpl() nothrow @nogc | |
4239 | in | |
4240 | { | |
4241 | assert( m_state == State.HOLD ); | |
4242 | } | |
4243 | body | |
4244 | { | |
4245 | Fiber cur = getThis(); | |
4246 | ||
4247 | static if ( __traits( compiles, ucontext_t ) ) | |
4248 | m_ucur = cur ? &cur.m_utxt : &Fiber.sm_utxt; | |
4249 | ||
4250 | setThis( this ); | |
4251 | this.switchIn(); | |
4252 | setThis( cur ); | |
4253 | ||
4254 | static if ( __traits( compiles, ucontext_t ) ) | |
4255 | m_ucur = null; | |
4256 | ||
4257 | // NOTE: If the fiber has terminated then the stack pointers must be | |
4258 | // reset. This ensures that the stack for this fiber is not | |
4259 | // scanned if the fiber has terminated. This is necessary to | |
4260 | // prevent any references lingering on the stack from delaying | |
4261 | // the collection of otherwise dead objects. The most notable | |
4262 | // being the current object, which is referenced at the top of | |
4263 | // fiber_entryPoint. | |
4264 | if ( m_state == State.TERM ) | |
4265 | { | |
4266 | m_ctxt.tstack = m_ctxt.bstack; | |
4267 | } | |
4268 | } | |
4269 | ||
4270 | /// Flag to control rethrow behavior of $(D $(LREF call)) | |
4271 | enum Rethrow : bool { no, yes } | |
4272 | ||
4273 | /** | |
4274 | * Resets this fiber so that it may be re-used, optionally with a | |
4275 | * new function/delegate. This routine should only be called for | |
4276 | * fibers that have terminated, as doing otherwise could result in | |
4277 | * scope-dependent functionality that is not executed. | |
4278 | * Stack-based classes, for example, may not be cleaned up | |
4279 | * properly if a fiber is reset before it has terminated. | |
4280 | * | |
4281 | * In: | |
4282 | * This fiber must be in state TERM or HOLD. | |
4283 | */ | |
4284 | final void reset() nothrow @nogc | |
4285 | in | |
4286 | { | |
4287 | assert( m_state == State.TERM || m_state == State.HOLD ); | |
4288 | } | |
4289 | body | |
4290 | { | |
4291 | m_ctxt.tstack = m_ctxt.bstack; | |
4292 | m_state = State.HOLD; | |
4293 | initStack(); | |
4294 | m_unhandled = null; | |
4295 | } | |
4296 | ||
4297 | /// ditto | |
4298 | final void reset( void function() fn ) nothrow @nogc | |
4299 | { | |
4300 | reset(); | |
4301 | m_fn = fn; | |
4302 | m_call = Call.FN; | |
4303 | } | |
4304 | ||
4305 | /// ditto | |
4306 | final void reset( void delegate() dg ) nothrow @nogc | |
4307 | { | |
4308 | reset(); | |
4309 | m_dg = dg; | |
4310 | m_call = Call.DG; | |
4311 | } | |
4312 | ||
4313 | /////////////////////////////////////////////////////////////////////////// | |
4314 | // General Properties | |
4315 | /////////////////////////////////////////////////////////////////////////// | |
4316 | ||
4317 | ||
4318 | /** | |
4319 | * A fiber may occupy one of three states: HOLD, EXEC, and TERM. The HOLD | |
4320 | * state applies to any fiber that is suspended and ready to be called. | |
4321 | * The EXEC state will be set for any fiber that is currently executing. | |
4322 | * And the TERM state is set when a fiber terminates. Once a fiber | |
4323 | * terminates, it must be reset before it may be called again. | |
4324 | */ | |
4325 | enum State | |
4326 | { | |
4327 | HOLD, /// | |
4328 | EXEC, /// | |
4329 | TERM /// | |
4330 | } | |
4331 | ||
4332 | ||
4333 | /** | |
4334 | * Gets the current state of this fiber. | |
4335 | * | |
4336 | * Returns: | |
4337 | * The state of this fiber as an enumerated value. | |
4338 | */ | |
4339 | final @property State state() const @safe pure nothrow @nogc | |
4340 | { | |
4341 | return m_state; | |
4342 | } | |
4343 | ||
4344 | ||
4345 | /////////////////////////////////////////////////////////////////////////// | |
4346 | // Actions on Calling Fiber | |
4347 | /////////////////////////////////////////////////////////////////////////// | |
4348 | ||
4349 | ||
4350 | /** | |
4351 | * Forces a context switch to occur away from the calling fiber. | |
4352 | */ | |
4353 | static void yield() nothrow @nogc | |
4354 | { | |
4355 | Fiber cur = getThis(); | |
4356 | assert( cur, "Fiber.yield() called with no active fiber" ); | |
4357 | assert( cur.m_state == State.EXEC ); | |
4358 | ||
4359 | static if ( __traits( compiles, ucontext_t ) ) | |
4360 | cur.m_ucur = &cur.m_utxt; | |
4361 | ||
4362 | cur.m_state = State.HOLD; | |
4363 | cur.switchOut(); | |
4364 | cur.m_state = State.EXEC; | |
4365 | } | |
4366 | ||
4367 | ||
4368 | /** | |
4369 | * Forces a context switch to occur away from the calling fiber and then | |
4370 | * throws obj in the calling fiber. | |
4371 | * | |
4372 | * Params: | |
4373 | * t = The object to throw. | |
4374 | * | |
4375 | * In: | |
4376 | * t must not be null. | |
4377 | */ | |
4378 | static void yieldAndThrow( Throwable t ) nothrow @nogc | |
4379 | in | |
4380 | { | |
4381 | assert( t ); | |
4382 | } | |
4383 | body | |
4384 | { | |
4385 | Fiber cur = getThis(); | |
4386 | assert( cur, "Fiber.yield() called with no active fiber" ); | |
4387 | assert( cur.m_state == State.EXEC ); | |
4388 | ||
4389 | static if ( __traits( compiles, ucontext_t ) ) | |
4390 | cur.m_ucur = &cur.m_utxt; | |
4391 | ||
4392 | cur.m_unhandled = t; | |
4393 | cur.m_state = State.HOLD; | |
4394 | cur.switchOut(); | |
4395 | cur.m_state = State.EXEC; | |
4396 | } | |
4397 | ||
4398 | ||
4399 | /////////////////////////////////////////////////////////////////////////// | |
4400 | // Fiber Accessors | |
4401 | /////////////////////////////////////////////////////////////////////////// | |
4402 | ||
4403 | ||
4404 | /** | |
4405 | * Provides a reference to the calling fiber or null if no fiber is | |
4406 | * currently active. | |
4407 | * | |
4408 | * Returns: | |
4409 | * The fiber object representing the calling fiber or null if no fiber | |
4410 | * is currently active within this thread. The result of deleting this object is undefined. | |
4411 | */ | |
4412 | static Fiber getThis() @safe nothrow @nogc | |
4413 | { | |
4414 | return sm_this; | |
4415 | } | |
4416 | ||
4417 | ||
4418 | /////////////////////////////////////////////////////////////////////////// | |
4419 | // Static Initialization | |
4420 | /////////////////////////////////////////////////////////////////////////// | |
4421 | ||
4422 | ||
4423 | version (Posix) | |
4424 | { | |
4425 | static this() | |
4426 | { | |
4427 | static if ( __traits( compiles, ucontext_t ) ) | |
4428 | { | |
4429 | int status = getcontext( &sm_utxt ); | |
4430 | assert( status == 0 ); | |
4431 | } | |
4432 | } | |
4433 | } | |
4434 | ||
4435 | private: | |
4436 | // | |
4437 | // Initializes a fiber object which has no associated executable function. | |
4438 | // | |
4439 | this() @safe pure nothrow @nogc | |
4440 | { | |
4441 | m_call = Call.NO; | |
4442 | } | |
4443 | ||
4444 | ||
4445 | // | |
4446 | // Fiber entry point. Invokes the function or delegate passed on | |
4447 | // construction (if any). | |
4448 | // | |
4449 | final void run() | |
4450 | { | |
4451 | switch ( m_call ) | |
4452 | { | |
4453 | case Call.FN: | |
4454 | m_fn(); | |
4455 | break; | |
4456 | case Call.DG: | |
4457 | m_dg(); | |
4458 | break; | |
4459 | default: | |
4460 | break; | |
4461 | } | |
4462 | } | |
4463 | ||
4464 | ||
4465 | private: | |
4466 | // | |
4467 | // The type of routine passed on fiber construction. | |
4468 | // | |
4469 | enum Call | |
4470 | { | |
4471 | NO, | |
4472 | FN, | |
4473 | DG | |
4474 | } | |
4475 | ||
4476 | ||
4477 | // | |
4478 | // Standard fiber data | |
4479 | // | |
4480 | Call m_call; | |
4481 | union | |
4482 | { | |
4483 | void function() m_fn; | |
4484 | void delegate() m_dg; | |
4485 | } | |
4486 | bool m_isRunning; | |
4487 | Throwable m_unhandled; | |
4488 | State m_state; | |
4489 | ||
4490 | ||
4491 | private: | |
4492 | /////////////////////////////////////////////////////////////////////////// | |
4493 | // Stack Management | |
4494 | /////////////////////////////////////////////////////////////////////////// | |
4495 | ||
4496 | ||
4497 | // | |
4498 | // Allocate a new stack for this fiber. | |
4499 | // | |
4500 | final void allocStack( size_t sz, size_t guardPageSize ) nothrow | |
4501 | in | |
4502 | { | |
4503 | assert( !m_pmem && !m_ctxt ); | |
4504 | } | |
4505 | body | |
4506 | { | |
4507 | // adjust alloc size to a multiple of PAGESIZE | |
4508 | sz += PAGESIZE - 1; | |
4509 | sz -= sz % PAGESIZE; | |
4510 | ||
4511 | // NOTE: This instance of Thread.Context is dynamic so Fiber objects | |
4512 | // can be collected by the GC so long as no user level references | |
4513 | // to the object exist. If m_ctxt were not dynamic then its | |
4514 | // presence in the global context list would be enough to keep | |
4515 | // this object alive indefinitely. An alternative to allocating | |
4516 | // room for this struct explicitly would be to mash it into the | |
4517 | // base of the stack being allocated below. However, doing so | |
4518 | // requires too much special logic to be worthwhile. | |
4519 | m_ctxt = new Thread.Context; | |
4520 | ||
4521 | static if ( __traits( compiles, VirtualAlloc ) ) | |
4522 | { | |
4523 | // reserve memory for stack | |
4524 | m_pmem = VirtualAlloc( null, | |
4525 | sz + guardPageSize, | |
4526 | MEM_RESERVE, | |
4527 | PAGE_NOACCESS ); | |
4528 | if ( !m_pmem ) | |
4529 | onOutOfMemoryError(); | |
4530 | ||
4531 | version (StackGrowsDown) | |
4532 | { | |
4533 | void* stack = m_pmem + guardPageSize; | |
4534 | void* guard = m_pmem; | |
4535 | void* pbase = stack + sz; | |
4536 | } | |
4537 | else | |
4538 | { | |
4539 | void* stack = m_pmem; | |
4540 | void* guard = m_pmem + sz; | |
4541 | void* pbase = stack; | |
4542 | } | |
4543 | ||
4544 | // allocate reserved stack segment | |
4545 | stack = VirtualAlloc( stack, | |
4546 | sz, | |
4547 | MEM_COMMIT, | |
4548 | PAGE_READWRITE ); | |
4549 | if ( !stack ) | |
4550 | onOutOfMemoryError(); | |
4551 | ||
4552 | if (guardPageSize) | |
4553 | { | |
4554 | // allocate reserved guard page | |
4555 | guard = VirtualAlloc( guard, | |
4556 | guardPageSize, | |
4557 | MEM_COMMIT, | |
4558 | PAGE_READWRITE | PAGE_GUARD ); | |
4559 | if ( !guard ) | |
4560 | onOutOfMemoryError(); | |
4561 | } | |
4562 | ||
4563 | m_ctxt.bstack = pbase; | |
4564 | m_ctxt.tstack = pbase; | |
4565 | m_size = sz; | |
4566 | } | |
4567 | else | |
4568 | { | |
4569 | version (Posix) import core.sys.posix.sys.mman; // mmap | |
4570 | version (FreeBSD) import core.sys.freebsd.sys.mman : MAP_ANON; | |
4571 | version (NetBSD) import core.sys.netbsd.sys.mman : MAP_ANON; | |
4572 | version (CRuntime_Glibc) import core.sys.linux.sys.mman : MAP_ANON; | |
4573 | version (Darwin) import core.sys.darwin.sys.mman : MAP_ANON; | |
4574 | ||
4575 | static if ( __traits( compiles, mmap ) ) | |
4576 | { | |
4577 | // Allocate more for the memory guard | |
4578 | sz += guardPageSize; | |
4579 | ||
4580 | m_pmem = mmap( null, | |
4581 | sz, | |
4582 | PROT_READ | PROT_WRITE, | |
4583 | MAP_PRIVATE | MAP_ANON, | |
4584 | -1, | |
4585 | 0 ); | |
4586 | if ( m_pmem == MAP_FAILED ) | |
4587 | m_pmem = null; | |
4588 | } | |
4589 | else static if ( __traits( compiles, valloc ) ) | |
4590 | { | |
4591 | m_pmem = valloc( sz ); | |
4592 | } | |
4593 | else static if ( __traits( compiles, malloc ) ) | |
4594 | { | |
4595 | m_pmem = malloc( sz ); | |
4596 | } | |
4597 | else | |
4598 | { | |
4599 | m_pmem = null; | |
4600 | } | |
4601 | ||
4602 | if ( !m_pmem ) | |
4603 | onOutOfMemoryError(); | |
4604 | ||
4605 | version (StackGrowsDown) | |
4606 | { | |
4607 | m_ctxt.bstack = m_pmem + sz; | |
4608 | m_ctxt.tstack = m_pmem + sz; | |
4609 | void* guard = m_pmem; | |
4610 | } | |
4611 | else | |
4612 | { | |
4613 | m_ctxt.bstack = m_pmem; | |
4614 | m_ctxt.tstack = m_pmem; | |
4615 | void* guard = m_pmem + sz - guardPageSize; | |
4616 | } | |
4617 | m_size = sz; | |
4618 | ||
4619 | static if ( __traits( compiles, mmap ) ) | |
4620 | { | |
4621 | if (guardPageSize) | |
4622 | { | |
4623 | // protect end of stack | |
4624 | if ( mprotect(guard, guardPageSize, PROT_NONE) == -1 ) | |
4625 | abort(); | |
4626 | } | |
4627 | } | |
4628 | else | |
4629 | { | |
4630 | // Supported only for mmap allocated memory - results are | |
4631 | // undefined if applied to memory not obtained by mmap | |
4632 | } | |
4633 | } | |
4634 | ||
4635 | Thread.add( m_ctxt ); | |
4636 | } | |
4637 | ||
4638 | ||
4639 | // | |
4640 | // Free this fiber's stack. | |
4641 | // | |
4642 | final void freeStack() nothrow @nogc | |
4643 | in | |
4644 | { | |
4645 | assert( m_pmem && m_ctxt ); | |
4646 | } | |
4647 | body | |
4648 | { | |
4649 | // NOTE: m_ctxt is guaranteed to be alive because it is held in the | |
4650 | // global context list. | |
4651 | Thread.slock.lock_nothrow(); | |
4652 | scope(exit) Thread.slock.unlock_nothrow(); | |
4653 | Thread.remove( m_ctxt ); | |
4654 | ||
4655 | static if ( __traits( compiles, VirtualAlloc ) ) | |
4656 | { | |
4657 | VirtualFree( m_pmem, 0, MEM_RELEASE ); | |
4658 | } | |
4659 | else | |
4660 | { | |
4661 | import core.sys.posix.sys.mman; // munmap | |
4662 | ||
4663 | static if ( __traits( compiles, mmap ) ) | |
4664 | { | |
4665 | munmap( m_pmem, m_size ); | |
4666 | } | |
4667 | else static if ( __traits( compiles, valloc ) ) | |
4668 | { | |
4669 | free( m_pmem ); | |
4670 | } | |
4671 | else static if ( __traits( compiles, malloc ) ) | |
4672 | { | |
4673 | free( m_pmem ); | |
4674 | } | |
4675 | } | |
4676 | m_pmem = null; | |
4677 | m_ctxt = null; | |
4678 | } | |
4679 | ||
4680 | ||
4681 | // | |
4682 | // Initialize the allocated stack. | |
4683 | // Look above the definition of 'class Fiber' for some information about the implementation of this routine | |
4684 | // | |
4685 | final void initStack() nothrow @nogc | |
4686 | in | |
4687 | { | |
4688 | assert( m_ctxt.tstack && m_ctxt.tstack == m_ctxt.bstack ); | |
4689 | assert( cast(size_t) m_ctxt.bstack % (void*).sizeof == 0 ); | |
4690 | } | |
4691 | body | |
4692 | { | |
4693 | void* pstack = m_ctxt.tstack; | |
4694 | scope( exit ) m_ctxt.tstack = pstack; | |
4695 | ||
4696 | void push( size_t val ) nothrow | |
4697 | { | |
4698 | version (StackGrowsDown) | |
4699 | { | |
4700 | pstack -= size_t.sizeof; | |
4701 | *(cast(size_t*) pstack) = val; | |
4702 | } | |
4703 | else | |
4704 | { | |
4705 | pstack += size_t.sizeof; | |
4706 | *(cast(size_t*) pstack) = val; | |
4707 | } | |
4708 | } | |
4709 | ||
4710 | // NOTE: On OS X the stack must be 16-byte aligned according | |
4711 | // to the IA-32 call spec. For x86_64 the stack also needs to | |
4712 | // be aligned to 16-byte according to SysV AMD64 ABI. | |
4713 | version (AlignFiberStackTo16Byte) | |
4714 | { | |
4715 | version (StackGrowsDown) | |
4716 | { | |
4717 | pstack = cast(void*)(cast(size_t)(pstack) - (cast(size_t)(pstack) & 0x0F)); | |
4718 | } | |
4719 | else | |
4720 | { | |
4721 | pstack = cast(void*)(cast(size_t)(pstack) + (cast(size_t)(pstack) & 0x0F)); | |
4722 | } | |
4723 | } | |
4724 | ||
4725 | version (AsmX86_Windows) | |
4726 | { | |
4727 | version (StackGrowsDown) {} else static assert( false ); | |
4728 | ||
4729 | // On Windows Server 2008 and 2008 R2, an exploit mitigation | |
4730 | // technique known as SEHOP is activated by default. To avoid | |
4731 | // hijacking of the exception handler chain, the presence of a | |
4732 | // Windows-internal handler (ntdll.dll!FinalExceptionHandler) at | |
4733 | // its end is tested by RaiseException. If it is not present, all | |
4734 | // handlers are disregarded, and the program is thus aborted | |
4735 | // (see http://blogs.technet.com/b/srd/archive/2009/02/02/ | |
4736 | // preventing-the-exploitation-of-seh-overwrites-with-sehop.aspx). | |
4737 | // For new threads, this handler is installed by Windows immediately | |
4738 | // after creation. To make exception handling work in fibers, we | |
4739 | // have to insert it for our new stacks manually as well. | |
4740 | // | |
4741 | // To do this, we first determine the handler by traversing the SEH | |
4742 | // chain of the current thread until its end, and then construct a | |
4743 | // registration block for the last handler on the newly created | |
4744 | // thread. We then continue to push all the initial register values | |
4745 | // for the first context switch as for the other implementations. | |
4746 | // | |
4747 | // Note that this handler is never actually invoked, as we install | |
4748 | // our own one on top of it in the fiber entry point function. | |
4749 | // Thus, it should not have any effects on OSes not implementing | |
4750 | // exception chain verification. | |
4751 | ||
4752 | alias fp_t = void function(); // Actual signature not relevant. | |
4753 | static struct EXCEPTION_REGISTRATION | |
4754 | { | |
4755 | EXCEPTION_REGISTRATION* next; // sehChainEnd if last one. | |
4756 | fp_t handler; | |
4757 | } | |
4758 | enum sehChainEnd = cast(EXCEPTION_REGISTRATION*) 0xFFFFFFFF; | |
4759 | ||
4760 | __gshared static fp_t finalHandler = null; | |
4761 | if ( finalHandler is null ) | |
4762 | { | |
4763 | static EXCEPTION_REGISTRATION* fs0() nothrow | |
4764 | { | |
4765 | asm pure nothrow @nogc | |
4766 | { | |
4767 | naked; | |
4768 | mov EAX, FS:[0]; | |
4769 | ret; | |
4770 | } | |
4771 | } | |
4772 | auto reg = fs0(); | |
4773 | while ( reg.next != sehChainEnd ) reg = reg.next; | |
4774 | ||
4775 | // Benign races are okay here, just to avoid re-lookup on every | |
4776 | // fiber creation. | |
4777 | finalHandler = reg.handler; | |
4778 | } | |
4779 | ||
4780 | // When linking with /safeseh (supported by LDC, but not DMD) | |
4781 | // the exception chain must not extend to the very top | |
4782 | // of the stack, otherwise the exception chain is also considered | |
4783 | // invalid. Reserving additional 4 bytes at the top of the stack will | |
4784 | // keep the EXCEPTION_REGISTRATION below that limit | |
4785 | size_t reserve = EXCEPTION_REGISTRATION.sizeof + 4; | |
4786 | pstack -= reserve; | |
4787 | *(cast(EXCEPTION_REGISTRATION*)pstack) = | |
4788 | EXCEPTION_REGISTRATION( sehChainEnd, finalHandler ); | |
4789 | ||
4790 | push( cast(size_t) &fiber_entryPoint ); // EIP | |
4791 | push( cast(size_t) m_ctxt.bstack - reserve ); // EBP | |
4792 | push( 0x00000000 ); // EDI | |
4793 | push( 0x00000000 ); // ESI | |
4794 | push( 0x00000000 ); // EBX | |
4795 | push( cast(size_t) m_ctxt.bstack - reserve ); // FS:[0] | |
4796 | push( cast(size_t) m_ctxt.bstack ); // FS:[4] | |
4797 | push( cast(size_t) m_ctxt.bstack - m_size ); // FS:[8] | |
4798 | push( 0x00000000 ); // EAX | |
4799 | } | |
4800 | else version (AsmX86_64_Windows) | |
4801 | { | |
4802 | // Using this trampoline instead of the raw fiber_entryPoint | |
4803 | // ensures that during context switches, source and destination | |
4804 | // stacks have the same alignment. Otherwise, the stack would need | |
4805 | // to be shifted by 8 bytes for the first call, as fiber_entryPoint | |
4806 | // is an actual function expecting a stack which is not aligned | |
4807 | // to 16 bytes. | |
4808 | static void trampoline() | |
4809 | { | |
4810 | asm pure nothrow @nogc | |
4811 | { | |
4812 | naked; | |
4813 | sub RSP, 32; // Shadow space (Win64 calling convention) | |
4814 | call fiber_entryPoint; | |
4815 | xor RCX, RCX; // This should never be reached, as | |
4816 | jmp RCX; // fiber_entryPoint must never return. | |
4817 | } | |
4818 | } | |
4819 | ||
4820 | push( cast(size_t) &trampoline ); // RIP | |
4821 | push( 0x00000000_00000000 ); // RBP | |
4822 | push( 0x00000000_00000000 ); // R12 | |
4823 | push( 0x00000000_00000000 ); // R13 | |
4824 | push( 0x00000000_00000000 ); // R14 | |
4825 | push( 0x00000000_00000000 ); // R15 | |
4826 | push( 0x00000000_00000000 ); // RDI | |
4827 | push( 0x00000000_00000000 ); // RSI | |
4828 | push( 0x00000000_00000000 ); // XMM6 (high) | |
4829 | push( 0x00000000_00000000 ); // XMM6 (low) | |
4830 | push( 0x00000000_00000000 ); // XMM7 (high) | |
4831 | push( 0x00000000_00000000 ); // XMM7 (low) | |
4832 | push( 0x00000000_00000000 ); // XMM8 (high) | |
4833 | push( 0x00000000_00000000 ); // XMM8 (low) | |
4834 | push( 0x00000000_00000000 ); // XMM9 (high) | |
4835 | push( 0x00000000_00000000 ); // XMM9 (low) | |
4836 | push( 0x00000000_00000000 ); // XMM10 (high) | |
4837 | push( 0x00000000_00000000 ); // XMM10 (low) | |
4838 | push( 0x00000000_00000000 ); // XMM11 (high) | |
4839 | push( 0x00000000_00000000 ); // XMM11 (low) | |
4840 | push( 0x00000000_00000000 ); // XMM12 (high) | |
4841 | push( 0x00000000_00000000 ); // XMM12 (low) | |
4842 | push( 0x00000000_00000000 ); // XMM13 (high) | |
4843 | push( 0x00000000_00000000 ); // XMM13 (low) | |
4844 | push( 0x00000000_00000000 ); // XMM14 (high) | |
4845 | push( 0x00000000_00000000 ); // XMM14 (low) | |
4846 | push( 0x00000000_00000000 ); // XMM15 (high) | |
4847 | push( 0x00000000_00000000 ); // XMM15 (low) | |
4848 | push( 0x00000000_00000000 ); // RBX | |
4849 | push( 0xFFFFFFFF_FFFFFFFF ); // GS:[0] | |
4850 | version (StackGrowsDown) | |
4851 | { | |
4852 | push( cast(size_t) m_ctxt.bstack ); // GS:[8] | |
4853 | push( cast(size_t) m_ctxt.bstack - m_size ); // GS:[16] | |
4854 | } | |
4855 | else | |
4856 | { | |
4857 | push( cast(size_t) m_ctxt.bstack ); // GS:[8] | |
4858 | push( cast(size_t) m_ctxt.bstack + m_size ); // GS:[16] | |
4859 | } | |
4860 | } | |
4861 | else version (AsmX86_Posix) | |
4862 | { | |
4863 | push( 0x00000000 ); // Return address of fiber_entryPoint call | |
4864 | push( cast(size_t) &fiber_entryPoint ); // EIP | |
4865 | push( cast(size_t) m_ctxt.bstack ); // EBP | |
4866 | push( 0x00000000 ); // EDI | |
4867 | push( 0x00000000 ); // ESI | |
4868 | push( 0x00000000 ); // EBX | |
4869 | push( 0x00000000 ); // EAX | |
4870 | } | |
4871 | else version (AsmX86_64_Posix) | |
4872 | { | |
4873 | push( 0x00000000_00000000 ); // Return address of fiber_entryPoint call | |
4874 | push( cast(size_t) &fiber_entryPoint ); // RIP | |
4875 | push( cast(size_t) m_ctxt.bstack ); // RBP | |
4876 | push( 0x00000000_00000000 ); // RBX | |
4877 | push( 0x00000000_00000000 ); // R12 | |
4878 | push( 0x00000000_00000000 ); // R13 | |
4879 | push( 0x00000000_00000000 ); // R14 | |
4880 | push( 0x00000000_00000000 ); // R15 | |
4881 | } | |
4882 | else version (AsmPPC_Posix) | |
4883 | { | |
4884 | version (StackGrowsDown) | |
4885 | { | |
4886 | pstack -= int.sizeof * 5; | |
4887 | } | |
4888 | else | |
4889 | { | |
4890 | pstack += int.sizeof * 5; | |
4891 | } | |
4892 | ||
4893 | push( cast(size_t) &fiber_entryPoint ); // link register | |
4894 | push( 0x00000000 ); // control register | |
4895 | push( 0x00000000 ); // old stack pointer | |
4896 | ||
4897 | // GPR values | |
4898 | version (StackGrowsDown) | |
4899 | { | |
4900 | pstack -= int.sizeof * 20; | |
4901 | } | |
4902 | else | |
4903 | { | |
4904 | pstack += int.sizeof * 20; | |
4905 | } | |
4906 | ||
4907 | assert( (cast(size_t) pstack & 0x0f) == 0 ); | |
4908 | } | |
4909 | else version (AsmMIPS_O32_Posix) | |
4910 | { | |
4911 | version (StackGrowsDown) {} | |
4912 | else static assert(0); | |
4913 | ||
4914 | /* We keep the FP registers and the return address below | |
4915 | * the stack pointer, so they don't get scanned by the | |
4916 | * GC. The last frame before swapping the stack pointer is | |
4917 | * organized like the following. | |
4918 | * | |
4919 | * |-----------|<= frame pointer | |
4920 | * | $gp | | |
4921 | * | $s0-8 | | |
4922 | * |-----------|<= stack pointer | |
4923 | * | $ra | | |
4924 | * | align(8) | | |
4925 | * | $f20-30 | | |
4926 | * |-----------| | |
4927 | * | |
4928 | */ | |
4929 | enum SZ_GP = 10 * size_t.sizeof; // $gp + $s0-8 | |
4930 | enum SZ_RA = size_t.sizeof; // $ra | |
4931 | version (MIPS_HardFloat) | |
4932 | { | |
4933 | enum SZ_FP = 6 * 8; // $f20-30 | |
4934 | enum ALIGN = -(SZ_FP + SZ_RA) & (8 - 1); | |
4935 | } | |
4936 | else | |
4937 | { | |
4938 | enum SZ_FP = 0; | |
4939 | enum ALIGN = 0; | |
4940 | } | |
4941 | ||
4942 | enum BELOW = SZ_FP + ALIGN + SZ_RA; | |
4943 | enum ABOVE = SZ_GP; | |
4944 | enum SZ = BELOW + ABOVE; | |
4945 | ||
4946 | (cast(ubyte*)pstack - SZ)[0 .. SZ] = 0; | |
4947 | pstack -= ABOVE; | |
4948 | *cast(size_t*)(pstack - SZ_RA) = cast(size_t)&fiber_entryPoint; | |
4949 | } | |
7c871402 | 4950 | else version (AsmAArch64_Posix) |
4951 | { | |
4952 | // Like others, FP registers and return address (lr) are kept | |
4953 | // below the saved stack top (tstack) to hide from GC scanning. | |
4954 | // fiber_switchContext expects newp sp to look like this: | |
4955 | // 19: x19 | |
4956 | // ... | |
4957 | // 9: x29 (fp) <-- newp tstack | |
4958 | // 8: x30 (lr) [&fiber_entryPoint] | |
4959 | // 7: d8 | |
4960 | // ... | |
4961 | // 0: d15 | |
4962 | ||
4963 | version (StackGrowsDown) {} | |
4964 | else | |
4965 | static assert(false, "Only full descending stacks supported on AArch64"); | |
4966 | ||
4967 | // Only need to set return address (lr). Everything else is fine | |
4968 | // zero initialized. | |
4969 | pstack -= size_t.sizeof * 11; // skip past x19-x29 | |
4970 | push(cast(size_t) &fiber_trampoline); // see threadasm.S for docs | |
4971 | pstack += size_t.sizeof; // adjust sp (newp) above lr | |
4972 | } | |
03385ed3 | 4973 | else version (AsmARM_Posix) |
4974 | { | |
4975 | /* We keep the FP registers and the return address below | |
4976 | * the stack pointer, so they don't get scanned by the | |
4977 | * GC. The last frame before swapping the stack pointer is | |
4978 | * organized like the following. | |
4979 | * | |
4980 | * | |-----------|<= 'frame starts here' | |
4981 | * | | fp | (the actual frame pointer, r11 isn't | |
4982 | * | | r10-r4 | updated and still points to the previous frame) | |
4983 | * | |-----------|<= stack pointer | |
4984 | * | | lr | | |
4985 | * | | 4byte pad | | |
4986 | * | | d15-d8 |(if FP supported) | |
4987 | * | |-----------| | |
4988 | * Y | |
4989 | * stack grows down: The pointer value here is smaller than some lines above | |
4990 | */ | |
4991 | // frame pointer can be zero, r10-r4 also zero initialized | |
4992 | version (StackGrowsDown) | |
4993 | pstack -= int.sizeof * 8; | |
4994 | else | |
4995 | static assert(false, "Only full descending stacks supported on ARM"); | |
4996 | ||
4997 | // link register | |
4998 | push( cast(size_t) &fiber_entryPoint ); | |
4999 | /* | |
5000 | * We do not push padding and d15-d8 as those are zero initialized anyway | |
5001 | * Position the stack pointer above the lr register | |
5002 | */ | |
5003 | pstack += int.sizeof * 1; | |
5004 | } | |
5005 | else version (GNU_AsmX86_Windows) | |
5006 | { | |
5007 | version (StackGrowsDown) {} else static assert( false ); | |
5008 | ||
5009 | // Currently, MinGW doesn't utilize SEH exceptions. | |
5010 | // See DMD AsmX86_Windows If this code ever becomes fails and SEH is used. | |
5011 | ||
5012 | push( 0x00000000 ); // Return address of fiber_entryPoint call | |
5013 | push( cast(size_t) &fiber_entryPoint ); // EIP | |
5014 | push( 0x00000000 ); // EBP | |
5015 | push( 0x00000000 ); // EDI | |
5016 | push( 0x00000000 ); // ESI | |
5017 | push( 0x00000000 ); // EBX | |
5018 | push( 0xFFFFFFFF ); // FS:[0] - Current SEH frame | |
5019 | push( cast(size_t) m_ctxt.bstack ); // FS:[4] - Top of stack | |
5020 | push( cast(size_t) m_ctxt.bstack - m_size ); // FS:[8] - Bottom of stack | |
5021 | push( 0x00000000 ); // EAX | |
5022 | } | |
5023 | else version (GNU_AsmX86_64_Windows) | |
5024 | { | |
5025 | push( 0x00000000_00000000 ); // Return address of fiber_entryPoint call | |
5026 | push( cast(size_t) &fiber_entryPoint ); // RIP | |
5027 | push( 0x00000000_00000000 ); // RBP | |
5028 | push( 0x00000000_00000000 ); // RBX | |
5029 | push( 0x00000000_00000000 ); // R12 | |
5030 | push( 0x00000000_00000000 ); // R13 | |
5031 | push( 0x00000000_00000000 ); // R14 | |
5032 | push( 0x00000000_00000000 ); // R15 | |
5033 | push( 0xFFFFFFFF_FFFFFFFF ); // GS:[0] - Current SEH frame | |
5034 | version (StackGrowsDown) | |
5035 | { | |
5036 | push( cast(size_t) m_ctxt.bstack ); // GS:[8] - Top of stack | |
5037 | push( cast(size_t) m_ctxt.bstack - m_size ); // GS:[16] - Bottom of stack | |
5038 | } | |
5039 | else | |
5040 | { | |
5041 | push( cast(size_t) m_ctxt.bstack ); // GS:[8] - Top of stack | |
5042 | push( cast(size_t) m_ctxt.bstack + m_size ); // GS:[16] - Bottom of stack | |
5043 | } | |
5044 | } | |
5045 | else static if ( __traits( compiles, ucontext_t ) ) | |
5046 | { | |
5047 | getcontext( &m_utxt ); | |
5048 | m_utxt.uc_stack.ss_sp = m_pmem; | |
5049 | m_utxt.uc_stack.ss_size = m_size; | |
5050 | makecontext( &m_utxt, &fiber_entryPoint, 0 ); | |
5051 | // NOTE: If ucontext is being used then the top of the stack will | |
5052 | // be a pointer to the ucontext_t struct for that fiber. | |
5053 | push( cast(size_t) &m_utxt ); | |
5054 | } | |
5055 | else | |
5056 | static assert(0, "Not implemented"); | |
5057 | } | |
5058 | ||
5059 | ||
5060 | Thread.Context* m_ctxt; | |
5061 | size_t m_size; | |
5062 | void* m_pmem; | |
5063 | ||
5064 | static if ( __traits( compiles, ucontext_t ) ) | |
5065 | { | |
5066 | // NOTE: The static ucontext instance is used to represent the context | |
5067 | // of the executing thread. | |
5068 | static ucontext_t sm_utxt = void; | |
5069 | ucontext_t m_utxt = void; | |
5070 | ucontext_t* m_ucur = null; | |
5071 | } | |
5072 | ||
5073 | ||
5074 | private: | |
5075 | /////////////////////////////////////////////////////////////////////////// | |
5076 | // Storage of Active Fiber | |
5077 | /////////////////////////////////////////////////////////////////////////// | |
5078 | ||
5079 | ||
5080 | // | |
5081 | // Sets a thread-local reference to the current fiber object. | |
5082 | // | |
5083 | static void setThis( Fiber f ) nothrow @nogc | |
5084 | { | |
5085 | sm_this = f; | |
5086 | } | |
5087 | ||
5088 | static Fiber sm_this; | |
5089 | ||
5090 | ||
5091 | private: | |
5092 | /////////////////////////////////////////////////////////////////////////// | |
5093 | // Context Switching | |
5094 | /////////////////////////////////////////////////////////////////////////// | |
5095 | ||
5096 | ||
5097 | // | |
5098 | // Switches into the stack held by this fiber. | |
5099 | // | |
5100 | final void switchIn() nothrow @nogc | |
5101 | { | |
5102 | Thread tobj = Thread.getThis(); | |
5103 | void** oldp = &tobj.m_curr.tstack; | |
5104 | void* newp = m_ctxt.tstack; | |
5105 | ||
5106 | // NOTE: The order of operations here is very important. The current | |
5107 | // stack top must be stored before m_lock is set, and pushContext | |
5108 | // must not be called until after m_lock is set. This process | |
5109 | // is intended to prevent a race condition with the suspend | |
5110 | // mechanism used for garbage collection. If it is not followed, | |
5111 | // a badly timed collection could cause the GC to scan from the | |
5112 | // bottom of one stack to the top of another, or to miss scanning | |
5113 | // a stack that still contains valid data. The old stack pointer | |
5114 | // oldp will be set again before the context switch to guarantee | |
5115 | // that it points to exactly the correct stack location so the | |
5116 | // successive pop operations will succeed. | |
5117 | *oldp = getStackTop(); | |
5118 | atomicStore!(MemoryOrder.raw)(*cast(shared)&tobj.m_lock, true); | |
5119 | tobj.pushContext( m_ctxt ); | |
5120 | ||
5121 | fiber_switchContext( oldp, newp ); | |
5122 | ||
5123 | // NOTE: As above, these operations must be performed in a strict order | |
5124 | // to prevent Bad Things from happening. | |
5125 | tobj.popContext(); | |
5126 | atomicStore!(MemoryOrder.raw)(*cast(shared)&tobj.m_lock, false); | |
5127 | tobj.m_curr.tstack = tobj.m_curr.bstack; | |
5128 | } | |
5129 | ||
5130 | ||
5131 | // | |
5132 | // Switches out of the current stack and into the enclosing stack. | |
5133 | // | |
5134 | final void switchOut() nothrow @nogc | |
5135 | { | |
5136 | Thread tobj = Thread.getThis(); | |
5137 | void** oldp = &m_ctxt.tstack; | |
5138 | void* newp = tobj.m_curr.within.tstack; | |
5139 | ||
5140 | // NOTE: The order of operations here is very important. The current | |
5141 | // stack top must be stored before m_lock is set, and pushContext | |
5142 | // must not be called until after m_lock is set. This process | |
5143 | // is intended to prevent a race condition with the suspend | |
5144 | // mechanism used for garbage collection. If it is not followed, | |
5145 | // a badly timed collection could cause the GC to scan from the | |
5146 | // bottom of one stack to the top of another, or to miss scanning | |
5147 | // a stack that still contains valid data. The old stack pointer | |
5148 | // oldp will be set again before the context switch to guarantee | |
5149 | // that it points to exactly the correct stack location so the | |
5150 | // successive pop operations will succeed. | |
5151 | *oldp = getStackTop(); | |
5152 | atomicStore!(MemoryOrder.raw)(*cast(shared)&tobj.m_lock, true); | |
5153 | ||
5154 | fiber_switchContext( oldp, newp ); | |
5155 | ||
5156 | // NOTE: As above, these operations must be performed in a strict order | |
5157 | // to prevent Bad Things from happening. | |
5158 | // NOTE: If use of this fiber is multiplexed across threads, the thread | |
5159 | // executing here may be different from the one above, so get the | |
5160 | // current thread handle before unlocking, etc. | |
5161 | tobj = Thread.getThis(); | |
5162 | atomicStore!(MemoryOrder.raw)(*cast(shared)&tobj.m_lock, false); | |
5163 | tobj.m_curr.tstack = tobj.m_curr.bstack; | |
5164 | } | |
5165 | } | |
5166 | ||
5167 | ||
5168 | version (unittest) | |
5169 | { | |
5170 | class TestFiber : Fiber | |
5171 | { | |
5172 | this() | |
5173 | { | |
5174 | super(&run); | |
5175 | } | |
5176 | ||
5177 | void run() | |
5178 | { | |
5179 | foreach (i; 0 .. 1000) | |
5180 | { | |
5181 | sum += i; | |
5182 | Fiber.yield(); | |
5183 | } | |
5184 | } | |
5185 | ||
5186 | enum expSum = 1000 * 999 / 2; | |
5187 | size_t sum; | |
5188 | } | |
5189 | ||
5190 | void runTen() | |
5191 | { | |
5192 | TestFiber[10] fibs; | |
5193 | foreach (ref fib; fibs) | |
5194 | fib = new TestFiber(); | |
5195 | ||
5196 | bool cont; | |
5197 | do { | |
5198 | cont = false; | |
5199 | foreach (fib; fibs) { | |
5200 | if (fib.state == Fiber.State.HOLD) | |
5201 | { | |
5202 | fib.call(); | |
5203 | cont |= fib.state != Fiber.State.TERM; | |
5204 | } | |
5205 | } | |
5206 | } while (cont); | |
5207 | ||
5208 | foreach (fib; fibs) | |
5209 | { | |
5210 | assert(fib.sum == TestFiber.expSum); | |
5211 | } | |
5212 | } | |
5213 | } | |
5214 | ||
5215 | ||
5216 | // Single thread running separate fibers | |
5217 | unittest | |
5218 | { | |
5219 | runTen(); | |
5220 | } | |
5221 | ||
5222 | ||
5223 | // Multiple threads running separate fibers | |
5224 | unittest | |
5225 | { | |
5226 | auto group = new ThreadGroup(); | |
5227 | foreach (_; 0 .. 4) | |
5228 | { | |
5229 | group.create(&runTen); | |
5230 | } | |
5231 | group.joinAll(); | |
5232 | } | |
5233 | ||
5234 | ||
5235 | // Multiple threads running shared fibers | |
5236 | unittest | |
5237 | { | |
5238 | shared bool[10] locks; | |
5239 | TestFiber[10] fibs; | |
5240 | ||
5241 | void runShared() | |
5242 | { | |
5243 | bool cont; | |
5244 | do { | |
5245 | cont = false; | |
5246 | foreach (idx; 0 .. 10) | |
5247 | { | |
5248 | if (cas(&locks[idx], false, true)) | |
5249 | { | |
5250 | if (fibs[idx].state == Fiber.State.HOLD) | |
5251 | { | |
5252 | fibs[idx].call(); | |
5253 | cont |= fibs[idx].state != Fiber.State.TERM; | |
5254 | } | |
5255 | locks[idx] = false; | |
5256 | } | |
5257 | else | |
5258 | { | |
5259 | cont = true; | |
5260 | } | |
5261 | } | |
5262 | } while (cont); | |
5263 | } | |
5264 | ||
5265 | foreach (ref fib; fibs) | |
5266 | { | |
5267 | fib = new TestFiber(); | |
5268 | } | |
5269 | ||
5270 | auto group = new ThreadGroup(); | |
5271 | foreach (_; 0 .. 4) | |
5272 | { | |
5273 | group.create(&runShared); | |
5274 | } | |
5275 | group.joinAll(); | |
5276 | ||
5277 | foreach (fib; fibs) | |
5278 | { | |
5279 | assert(fib.sum == TestFiber.expSum); | |
5280 | } | |
5281 | } | |
5282 | ||
5283 | ||
5284 | // Test exception handling inside fibers. | |
5285 | version (Win32) { | |
5286 | // broken on win32 under windows server 2012: bug 13821 | |
5287 | } else unittest { | |
5288 | enum MSG = "Test message."; | |
5289 | string caughtMsg; | |
5290 | (new Fiber({ | |
5291 | try | |
5292 | { | |
5293 | throw new Exception(MSG); | |
5294 | } | |
5295 | catch (Exception e) | |
5296 | { | |
5297 | caughtMsg = e.msg; | |
5298 | } | |
5299 | })).call(); | |
5300 | assert(caughtMsg == MSG); | |
5301 | } | |
5302 | ||
5303 | ||
5304 | unittest | |
5305 | { | |
5306 | int x = 0; | |
5307 | ||
5308 | (new Fiber({ | |
5309 | x++; | |
5310 | })).call(); | |
5311 | assert( x == 1 ); | |
5312 | } | |
5313 | ||
5314 | nothrow unittest | |
5315 | { | |
5316 | new Fiber({}).call!(Fiber.Rethrow.no)(); | |
5317 | } | |
5318 | ||
5319 | unittest | |
5320 | { | |
5321 | new Fiber({}).call(Fiber.Rethrow.yes); | |
5322 | new Fiber({}).call(Fiber.Rethrow.no); | |
5323 | } | |
5324 | ||
5325 | deprecated unittest | |
5326 | { | |
5327 | new Fiber({}).call(true); | |
5328 | new Fiber({}).call(false); | |
5329 | } | |
5330 | ||
5331 | version (Win32) { | |
5332 | // broken on win32 under windows server 2012: bug 13821 | |
5333 | } else unittest { | |
5334 | enum MSG = "Test message."; | |
5335 | ||
5336 | try | |
5337 | { | |
5338 | (new Fiber({ | |
5339 | throw new Exception( MSG ); | |
5340 | })).call(); | |
5341 | assert( false, "Expected rethrown exception." ); | |
5342 | } | |
5343 | catch ( Throwable t ) | |
5344 | { | |
5345 | assert( t.msg == MSG ); | |
5346 | } | |
5347 | } | |
5348 | ||
5349 | // Test exception chaining when switching contexts in finally blocks. | |
5350 | unittest | |
5351 | { | |
5352 | static void throwAndYield(string msg) { | |
5353 | try { | |
5354 | throw new Exception(msg); | |
5355 | } finally { | |
5356 | Fiber.yield(); | |
5357 | } | |
5358 | } | |
5359 | ||
5360 | static void fiber(string name) { | |
5361 | try { | |
5362 | try { | |
5363 | throwAndYield(name ~ ".1"); | |
5364 | } finally { | |
5365 | throwAndYield(name ~ ".2"); | |
5366 | } | |
5367 | } catch (Exception e) { | |
5368 | assert(e.msg == name ~ ".1"); | |
5369 | assert(e.next); | |
5370 | assert(e.next.msg == name ~ ".2"); | |
5371 | assert(!e.next.next); | |
5372 | } | |
5373 | } | |
5374 | ||
5375 | auto first = new Fiber(() => fiber("first")); | |
5376 | auto second = new Fiber(() => fiber("second")); | |
5377 | first.call(); | |
5378 | second.call(); | |
5379 | first.call(); | |
5380 | second.call(); | |
5381 | first.call(); | |
5382 | second.call(); | |
5383 | assert(first.state == Fiber.State.TERM); | |
5384 | assert(second.state == Fiber.State.TERM); | |
5385 | } | |
5386 | ||
5387 | // Test Fiber resetting | |
5388 | unittest | |
5389 | { | |
5390 | static string method; | |
5391 | ||
5392 | static void foo() | |
5393 | { | |
5394 | method = "foo"; | |
5395 | } | |
5396 | ||
5397 | void bar() | |
5398 | { | |
5399 | method = "bar"; | |
5400 | } | |
5401 | ||
5402 | static void expect(Fiber fib, string s) | |
5403 | { | |
5404 | assert(fib.state == Fiber.State.HOLD); | |
5405 | fib.call(); | |
5406 | assert(fib.state == Fiber.State.TERM); | |
5407 | assert(method == s); method = null; | |
5408 | } | |
5409 | auto fib = new Fiber(&foo); | |
5410 | expect(fib, "foo"); | |
5411 | ||
5412 | fib.reset(); | |
5413 | expect(fib, "foo"); | |
5414 | ||
5415 | fib.reset(&foo); | |
5416 | expect(fib, "foo"); | |
5417 | ||
5418 | fib.reset(&bar); | |
5419 | expect(fib, "bar"); | |
5420 | ||
5421 | fib.reset(function void(){method = "function";}); | |
5422 | expect(fib, "function"); | |
5423 | ||
5424 | fib.reset(delegate void(){method = "delegate";}); | |
5425 | expect(fib, "delegate"); | |
5426 | } | |
5427 | ||
5428 | // Test unsafe reset in hold state | |
5429 | unittest | |
5430 | { | |
5431 | auto fib = new Fiber(function {ubyte[2048] buf = void; Fiber.yield();}, 4096); | |
5432 | foreach (_; 0 .. 10) | |
5433 | { | |
5434 | fib.call(); | |
5435 | assert(fib.state == Fiber.State.HOLD); | |
5436 | fib.reset(); | |
5437 | } | |
5438 | } | |
5439 | ||
5440 | // stress testing GC stack scanning | |
5441 | unittest | |
5442 | { | |
5443 | import core.memory; | |
5444 | ||
5445 | static void unreferencedThreadObject() | |
5446 | { | |
5447 | static void sleep() { Thread.sleep(dur!"msecs"(100)); } | |
5448 | auto thread = new Thread(&sleep).start(); | |
5449 | } | |
5450 | unreferencedThreadObject(); | |
5451 | GC.collect(); | |
5452 | ||
5453 | static class Foo | |
5454 | { | |
5455 | this(int value) | |
5456 | { | |
5457 | _value = value; | |
5458 | } | |
5459 | ||
5460 | int bar() | |
5461 | { | |
5462 | return _value; | |
5463 | } | |
5464 | ||
5465 | int _value; | |
5466 | } | |
5467 | ||
5468 | static void collect() | |
5469 | { | |
5470 | auto foo = new Foo(2); | |
5471 | assert(foo.bar() == 2); | |
5472 | GC.collect(); | |
5473 | Fiber.yield(); | |
5474 | GC.collect(); | |
5475 | assert(foo.bar() == 2); | |
5476 | } | |
5477 | ||
5478 | auto fiber = new Fiber(&collect); | |
5479 | ||
5480 | fiber.call(); | |
5481 | GC.collect(); | |
5482 | fiber.call(); | |
5483 | ||
5484 | // thread reference | |
5485 | auto foo = new Foo(2); | |
5486 | ||
5487 | void collect2() | |
5488 | { | |
5489 | assert(foo.bar() == 2); | |
5490 | GC.collect(); | |
5491 | Fiber.yield(); | |
5492 | GC.collect(); | |
5493 | assert(foo.bar() == 2); | |
5494 | } | |
5495 | ||
5496 | fiber = new Fiber(&collect2); | |
5497 | ||
5498 | fiber.call(); | |
5499 | GC.collect(); | |
5500 | fiber.call(); | |
5501 | ||
5502 | static void recurse(size_t cnt) | |
5503 | { | |
5504 | --cnt; | |
5505 | Fiber.yield(); | |
5506 | if (cnt) | |
5507 | { | |
5508 | auto fib = new Fiber(() { recurse(cnt); }); | |
5509 | fib.call(); | |
5510 | GC.collect(); | |
5511 | fib.call(); | |
5512 | } | |
5513 | } | |
5514 | fiber = new Fiber(() { recurse(20); }); | |
5515 | fiber.call(); | |
5516 | } | |
5517 | ||
5518 | ||
5519 | version (AsmX86_64_Windows) | |
5520 | { | |
5521 | // Test Windows x64 calling convention | |
5522 | unittest | |
5523 | { | |
5524 | void testNonvolatileRegister(alias REG)() | |
5525 | { | |
5526 | auto zeroRegister = new Fiber(() { | |
5527 | mixin("asm pure nothrow @nogc { naked; xor "~REG~", "~REG~"; ret; }"); | |
5528 | }); | |
5529 | long after; | |
5530 | ||
5531 | mixin("asm pure nothrow @nogc { mov "~REG~", 0xFFFFFFFFFFFFFFFF; }"); | |
5532 | zeroRegister.call(); | |
5533 | mixin("asm pure nothrow @nogc { mov after, "~REG~"; }"); | |
5534 | ||
5535 | assert(after == -1); | |
5536 | } | |
5537 | ||
5538 | void testNonvolatileRegisterSSE(alias REG)() | |
5539 | { | |
5540 | auto zeroRegister = new Fiber(() { | |
5541 | mixin("asm pure nothrow @nogc { naked; xorpd "~REG~", "~REG~"; ret; }"); | |
5542 | }); | |
5543 | long[2] before = [0xFFFFFFFF_FFFFFFFF, 0xFFFFFFFF_FFFFFFFF], after; | |
5544 | ||
5545 | mixin("asm pure nothrow @nogc { movdqu "~REG~", before; }"); | |
5546 | zeroRegister.call(); | |
5547 | mixin("asm pure nothrow @nogc { movdqu after, "~REG~"; }"); | |
5548 | ||
5549 | assert(before == after); | |
5550 | } | |
5551 | ||
5552 | testNonvolatileRegister!("R12")(); | |
5553 | testNonvolatileRegister!("R13")(); | |
5554 | testNonvolatileRegister!("R14")(); | |
5555 | testNonvolatileRegister!("R15")(); | |
5556 | testNonvolatileRegister!("RDI")(); | |
5557 | testNonvolatileRegister!("RSI")(); | |
5558 | testNonvolatileRegister!("RBX")(); | |
5559 | ||
5560 | testNonvolatileRegisterSSE!("XMM6")(); | |
5561 | testNonvolatileRegisterSSE!("XMM7")(); | |
5562 | testNonvolatileRegisterSSE!("XMM8")(); | |
5563 | testNonvolatileRegisterSSE!("XMM9")(); | |
5564 | testNonvolatileRegisterSSE!("XMM10")(); | |
5565 | testNonvolatileRegisterSSE!("XMM11")(); | |
5566 | testNonvolatileRegisterSSE!("XMM12")(); | |
5567 | testNonvolatileRegisterSSE!("XMM13")(); | |
5568 | testNonvolatileRegisterSSE!("XMM14")(); | |
5569 | testNonvolatileRegisterSSE!("XMM15")(); | |
5570 | } | |
5571 | } | |
5572 | ||
5573 | ||
5574 | version (D_InlineAsm_X86_64) | |
5575 | { | |
5576 | unittest | |
5577 | { | |
5578 | void testStackAlignment() | |
5579 | { | |
5580 | void* pRSP; | |
5581 | asm pure nothrow @nogc | |
5582 | { | |
5583 | mov pRSP, RSP; | |
5584 | } | |
5585 | assert((cast(size_t)pRSP & 0xF) == 0); | |
5586 | } | |
5587 | ||
5588 | auto fib = new Fiber(&testStackAlignment); | |
5589 | fib.call(); | |
5590 | } | |
5591 | } | |
5592 | ||
5593 | // regression test for Issue 13416 | |
5594 | version (FreeBSD) unittest | |
5595 | { | |
5596 | static void loop() | |
5597 | { | |
5598 | pthread_attr_t attr; | |
5599 | pthread_attr_init(&attr); | |
5600 | auto thr = pthread_self(); | |
5601 | foreach (i; 0 .. 50) | |
5602 | pthread_attr_get_np(thr, &attr); | |
5603 | pthread_attr_destroy(&attr); | |
5604 | } | |
5605 | ||
5606 | auto thr = new Thread(&loop).start(); | |
5607 | foreach (i; 0 .. 50) | |
5608 | { | |
5609 | thread_suspendAll(); | |
5610 | thread_resumeAll(); | |
5611 | } | |
5612 | thr.join(); | |
5613 | } | |
5614 | ||
5615 | unittest | |
5616 | { | |
5617 | // use >PAGESIZE to avoid stack overflow (e.g. in an syscall) | |
5618 | auto thr = new Thread(function{}, 4096 + 1).start(); | |
5619 | thr.join(); | |
5620 | } | |
5621 | ||
5622 | /** | |
5623 | * Represents the ID of a thread, as returned by $(D Thread.)$(LREF id). | |
5624 | * The exact type varies from platform to platform. | |
5625 | */ | |
5626 | version (Windows) | |
5627 | alias ThreadID = uint; | |
5628 | else | |
5629 | version (Posix) | |
5630 | alias ThreadID = pthread_t; |