1 /* cilk_fiber.cpp                  -*-C++-*-
2  *
3  *************************************************************************
4  *
5  *  @copyright
6  *  Copyright (C) 2012-2013, Intel Corporation
7  *  All rights reserved.
8  *
9  *  @copyright
10  *  Redistribution and use in source and binary forms, with or without
11  *  modification, are permitted provided that the following conditions
12  *  are met:
13  *
14  *    * Redistributions of source code must retain the above copyright
15  *      notice, this list of conditions and the following disclaimer.
16  *    * Redistributions in binary form must reproduce the above copyright
17  *      notice, this list of conditions and the following disclaimer in
18  *      the documentation and/or other materials provided with the
19  *      distribution.
20  *    * Neither the name of Intel Corporation nor the names of its
21  *      contributors may be used to endorse or promote products derived
22  *      from this software without specific prior written permission.
23  *
24  *  @copyright
25  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26  *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
27  *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
28  *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
29  *  HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
30  *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
31  *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
32  *  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
33  *  AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34  *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
35  *  WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36  *  POSSIBILITY OF SUCH DAMAGE.
37  **************************************************************************/
38 
39 /* Implementations of non-platform-specific aspects of cilk_fiber, especially
40  * the cilk_fiber_pool interface.
41  */
42 #include "cilk_fiber.h"
43 #ifdef _WIN32
44 #   include "cilk_fiber-win.h"
45 #else
46 #   include "cilk_fiber-unix.h"
47 #endif
48 #include "cilk_malloc.h"
49 #include "bug.h"
50 #include <new>
51 
52 #include <climits>
53 #include <cstdio>
54 #include <cstdlib>
55 #include <cstring>
56 
57 #include "sysdep.h"
58 
59 
60 extern "C" {
61 
cilk_fiber_pool_sanity_check(cilk_fiber_pool * pool,const char * desc)62 inline int cilk_fiber_pool_sanity_check(cilk_fiber_pool *pool, const char* desc)
63 {
64     int errors = 0;
65 #if FIBER_DEBUG >= 1
66     if ((NULL != pool) && pool->total > 0) {
67 
68         // Root pool should not allocate more fibers than alloc_max
69         errors += ((pool->parent == NULL) &&
70                    (pool->total > pool->alloc_max));
71         errors += (pool->total > pool->high_water);
72 
73         if (errors) {
74             fprintf(stderr, "ERROR at %s: pool=%p has max_size=%u, total=%d, high_water=%d\n",
75                     desc,
76                     pool, pool->max_size, pool->total, pool->high_water);
77         }
78     }
79 #endif
80     return (errors == 0);
81 }
82 
increment_pool_total(cilk_fiber_pool * pool)83 inline void increment_pool_total(cilk_fiber_pool* pool)
84 {
85     ++pool->total;
86     if (pool->high_water < pool->total)
87         pool->high_water = pool->total;
88 }
89 
decrement_pool_total(cilk_fiber_pool * pool,int fibers_freed)90 inline void decrement_pool_total(cilk_fiber_pool* pool, int fibers_freed)
91 {
92     pool->total -= fibers_freed;
93 }
94 
95 
96 /**
97  * @brief Free fibers from this pool until we have at most @c
98  * num_to_keep fibers remaining, and then put a fiber back.
99  *
100  * @pre   We do not hold @c pool->lock
101  * @post  After completion, we do not hold @c pool->lock
102  */
cilk_fiber_pool_free_fibers_from_pool(cilk_fiber_pool * pool,unsigned num_to_keep,cilk_fiber * fiber_to_return)103 static void cilk_fiber_pool_free_fibers_from_pool(cilk_fiber_pool* pool,
104                                                   unsigned num_to_keep,
105                                                   cilk_fiber* fiber_to_return)
106 {
107     // Free our own fibers, until we fall below our desired threshold.
108     // Each iteration of this loop proceeds in the following stages:
109     //   1.  Acquire the pool lock,
110     //   2.  Grabs up to B fibers from the pool, stores them into a buffer.
111     //   3.  Check if pool is empty enough.  If yes, put the last fiber back,
112     //       and remember that we should quit.
113     //   4.  Release the pool lock, and actually free any buffered fibers.
114     //   5.  Check if we are done and should exit the loop.  Otherwise, try again.
115     //
116     const bool need_lock = pool->lock;
117     bool last_fiber_returned = false;
118 
119     do {
120         const int B = 10;   // Pull at most this many fibers from the
121                             // parent for one lock acquisition.  Make
122                             // this value large enough to amortize
123                             // against the cost of acquiring and
124                             // releasing the lock.
125         int num_to_free = 0;
126         cilk_fiber* fibers_to_free[B];
127 
128         // Stage 1: Grab the lock.
129         if (need_lock) {
130             spin_mutex_lock(pool->lock);
131         }
132 
133         // Stage 2: Grab up to B fibers to free.
134         int fibers_freed = 0;
135         while ((pool->size > num_to_keep) && (num_to_free < B)) {
136             fibers_to_free[num_to_free++] = pool->fibers[--pool->size];
137             fibers_freed++;
138         }
139         decrement_pool_total(pool, fibers_freed);
140 
141         // Stage 3.  Pool is below threshold.  Put extra fiber back.
142         if (pool->size <= num_to_keep) {
143             // Put the last fiber back into the pool.
144             if (fiber_to_return) {
145                 CILK_ASSERT(pool->size < pool->max_size);
146                 pool->fibers[pool->size] = fiber_to_return;
147                 pool->size++;
148             }
149             last_fiber_returned = true;
150         }
151 
152         // Stage 4: Release the lock, and actually free any fibers
153         // buffered.
154         if (need_lock) {
155             spin_mutex_unlock(pool->lock);
156         }
157 
158         for (int i = 0; i < num_to_free; ++i) {
159             fibers_to_free[i]->deallocate_to_heap();
160         }
161 
162     } while (!last_fiber_returned);
163 }
164 
165 
166 /******************************************************************
167  * TBD: We want to simplify / rework the logic for allocating and
168  * deallocating fibers, so that they are hopefully simpler and work
169  * more elegantly for more than two levels.
170  ******************************************************************/
171 
172 /**
173  * @brief Transfer fibers from @c pool to @c pool->parent.
174  *
175  * @pre   Must hold @c pool->lock if it exists.
176  * @post  After completion, some number of fibers
177  *        have been moved from this pool to the parent.
178  *        The lock @c pool->lock is still held.
179  *
180  * TBD: Do we wish to guarantee that the lock has never been
181  * released?  It may depend on the implementation...
182  */
cilk_fiber_pool_move_fibers_to_parent_pool(cilk_fiber_pool * pool,unsigned num_to_keep)183 static void cilk_fiber_pool_move_fibers_to_parent_pool(cilk_fiber_pool* pool,
184                                                        unsigned num_to_keep)
185 {
186     // ASSERT: We should hold the lock on pool (if it has one).
187     CILK_ASSERT(pool->parent);
188     cilk_fiber_pool* parent_pool = pool->parent;
189 
190     // Move fibers from our pool to the parent until we either run out
191     // of space in the parent, or hit our threshold.
192     //
193     // This operation must be done while holding the parent lock.
194 
195     // If the parent pool appears to be full, just return early.
196     if (parent_pool->size >= parent_pool->max_size)
197         return;
198 
199     spin_mutex_lock(pool->parent->lock);
200     while ((parent_pool->size < parent_pool->max_size) &&
201            (pool->size > num_to_keep)) {
202         parent_pool->fibers[parent_pool->size++] =
203             pool->fibers[--pool->size];
204     }
205 
206     // If the child pool has deallocated more than fibers to the heap
207     // than it has allocated, then transfer this "surplus" to the
208     // parent, so that the parent is free to allocate more from the
209     // heap.
210     //
211     // This transfer means that the total in the parent can
212     // temporarily go negative.
213     if (pool->total < 0) {
214         // Reduce parent total by the surplus we have in the local
215         // pool.
216         parent_pool->total += pool->total;
217         pool->total = 0;
218     }
219 
220     spin_mutex_unlock(pool->parent->lock);
221 }
222 
cilk_fiber_pool_init(cilk_fiber_pool * pool,cilk_fiber_pool * parent,size_t stack_size,unsigned buffer_size,int alloc_max,int is_shared)223 void cilk_fiber_pool_init(cilk_fiber_pool* pool,
224                           cilk_fiber_pool* parent,
225                           size_t           stack_size,
226                           unsigned         buffer_size,
227                           int              alloc_max,
228                           int              is_shared)
229 {
230 #if FIBER_DEBUG >= 1
231     fprintf(stderr, "fiber_pool_init, pool=%p, parent=%p, alloc_max=%u\n",
232             pool, parent, alloc_max);
233 #endif
234 
235     pool->lock       = (is_shared ? spin_mutex_create() : NULL);
236     pool->parent     = parent;
237     pool->stack_size = stack_size;
238     pool->max_size   = buffer_size;
239     pool->size       = 0;
240     pool->total      = 0;
241     pool->high_water = 0;
242     pool->alloc_max  = alloc_max;
243     pool->fibers     =
244         (cilk_fiber**) __cilkrts_malloc(buffer_size * sizeof(cilk_fiber*));
245     CILK_ASSERT(NULL != pool->fibers);
246 
247 #ifdef __MIC__
248 #define PREALLOCATE_FIBERS
249 #endif
250 
251 #ifdef PREALLOCATE_FIBERS
252     // Pre-allocate 1/4 of fibers in the pools ahead of time.  This
253     // value is somewhat arbitrary.  It was chosen to be less than the
254     // threshold (of about 3/4) of fibers to keep in the pool when
255     // transferring fibers to the parent.
256 
257     int pre_allocate_count = buffer_size/4;
258     for (pool->size = 0; pool->size < pre_allocate_count; pool->size++) {
259         pool->fibers[pool->size] = cilk_fiber::allocate_from_heap(pool->stack_size);
260     }
261 #endif
262 }
263 
264 
cilk_fiber_pool_set_fiber_limit(cilk_fiber_pool * root_pool,unsigned max_fibers_to_allocate)265 void cilk_fiber_pool_set_fiber_limit(cilk_fiber_pool* root_pool,
266                                      unsigned max_fibers_to_allocate)
267 {
268     // Should only set limit on root pool, not children.
269     CILK_ASSERT(NULL == root_pool->parent);
270     root_pool->alloc_max = max_fibers_to_allocate;
271 }
272 
cilk_fiber_pool_destroy(cilk_fiber_pool * pool)273 void cilk_fiber_pool_destroy(cilk_fiber_pool* pool)
274 {
275     CILK_ASSERT(cilk_fiber_pool_sanity_check(pool, "pool_destroy"));
276 
277     // Lock my own pool, if I need to.
278     if (pool->lock) {
279         spin_mutex_lock(pool->lock);
280     }
281 
282     // Give any remaining fibers to parent pool.
283     if (pool->parent) {
284         cilk_fiber_pool_move_fibers_to_parent_pool(pool, 0);
285     }
286 
287     // Unlock pool.
288     if (pool->lock) {
289         spin_mutex_unlock(pool->lock);
290     }
291 
292     // If I have any left in my pool, just free them myself.
293     // This method may acquire the pool lock.
294     cilk_fiber_pool_free_fibers_from_pool(pool, 0, NULL);
295 
296     // Destroy the lock if there is one.
297     if (pool->lock) {
298         spin_mutex_destroy(pool->lock);
299     }
300     __cilkrts_free(pool->fibers);
301 }
302 
303 
cilk_fiber_allocate(cilk_fiber_pool * pool)304 cilk_fiber* cilk_fiber_allocate(cilk_fiber_pool* pool)
305 {
306     CILK_ASSERT(cilk_fiber_pool_sanity_check(pool, "allocate"));
307     return cilk_fiber::allocate(pool);
308 }
309 
cilk_fiber_allocate_from_heap(size_t stack_size)310 cilk_fiber* cilk_fiber_allocate_from_heap(size_t stack_size)
311 {
312     return cilk_fiber::allocate_from_heap(stack_size);
313 }
314 
cilk_fiber_reset_state(cilk_fiber * fiber,cilk_fiber_proc start_proc)315 void cilk_fiber_reset_state(cilk_fiber* fiber, cilk_fiber_proc start_proc)
316 {
317     fiber->reset_state(start_proc);
318 }
319 
cilk_fiber_remove_reference(cilk_fiber * fiber,cilk_fiber_pool * pool)320 int cilk_fiber_remove_reference(cilk_fiber *fiber, cilk_fiber_pool *pool)
321 {
322     return fiber->remove_reference(pool);
323 }
324 
cilk_fiber_allocate_from_thread()325 cilk_fiber* cilk_fiber_allocate_from_thread()
326 {
327     return cilk_fiber::allocate_from_thread();
328 }
329 
cilk_fiber_deallocate_from_thread(cilk_fiber * fiber)330 int cilk_fiber_deallocate_from_thread(cilk_fiber *fiber)
331 {
332     return fiber->deallocate_from_thread();
333 }
334 
cilk_fiber_remove_reference_from_thread(cilk_fiber * fiber)335 int cilk_fiber_remove_reference_from_thread(cilk_fiber *fiber)
336 {
337     return fiber->remove_reference_from_thread();
338 }
339 
cilk_fiber_is_allocated_from_thread(cilk_fiber * fiber)340 int cilk_fiber_is_allocated_from_thread(cilk_fiber *fiber)
341 {
342     return fiber->is_allocated_from_thread();
343 }
344 
345 #if SUPPORT_GET_CURRENT_FIBER
cilk_fiber_get_current_fiber(void)346 cilk_fiber* cilk_fiber_get_current_fiber(void)
347 {
348     return cilk_fiber::get_current_fiber();
349 }
350 #endif
351 
cilk_fiber_suspend_self_and_resume_other(cilk_fiber * self,cilk_fiber * other)352 void cilk_fiber_suspend_self_and_resume_other(cilk_fiber* self,
353                                               cilk_fiber* other)
354 {
355     self->suspend_self_and_resume_other(other);
356 }
357 
358 
reset_state(cilk_fiber_proc start_proc)359 void cilk_fiber::reset_state(cilk_fiber_proc start_proc)
360 {
361     // Setup the fiber and return.
362     this->m_start_proc = start_proc;
363 
364     CILK_ASSERT(!this->is_resumable());
365     CILK_ASSERT(NULL == this->m_pending_remove_ref);
366     CILK_ASSERT(NULL == this->m_pending_pool);
367 }
368 
369 NORETURN
cilk_fiber_remove_reference_from_self_and_resume_other(cilk_fiber * self,cilk_fiber_pool * self_pool,cilk_fiber * other)370 cilk_fiber_remove_reference_from_self_and_resume_other(cilk_fiber*      self,
371                                                        cilk_fiber_pool* self_pool,
372                                                        cilk_fiber*      other)
373 {
374 #if FIBER_DEBUG >= 3
375     __cilkrts_worker* w = __cilkrts_get_tls_worker();
376     fprintf(stderr, "W=%d: cilk_fiber_deactivate_self_and_resume_other: self=%p, other=%p\n",
377             w->self,
378             self, other);
379 #endif
380     CILK_ASSERT(cilk_fiber_pool_sanity_check(self_pool, "remove_reference_from_self_resume_other"));
381     self->remove_reference_from_self_and_resume_other(self_pool, other);
382 
383     // We should never return here.
384 }
385 
cilk_fiber_set_post_switch_proc(cilk_fiber * self,cilk_fiber_proc post_switch_proc)386 void cilk_fiber_set_post_switch_proc(cilk_fiber *self,
387                                      cilk_fiber_proc post_switch_proc)
388 {
389     self->set_post_switch_proc(post_switch_proc);
390 }
391 
cilk_fiber_invoke_tbb_stack_op(cilk_fiber * fiber,__cilk_tbb_stack_op op)392 void cilk_fiber_invoke_tbb_stack_op(cilk_fiber* fiber,
393                                     __cilk_tbb_stack_op op)
394 {
395     fiber->invoke_tbb_stack_op(op);
396 }
397 
cilk_fiber_get_data(cilk_fiber * fiber)398 cilk_fiber_data* cilk_fiber_get_data(cilk_fiber* fiber)
399 {
400     return fiber->get_data();
401 
402     /// TBD: Change this code to "return (cilk_fiber_data*)fiber;"
403     //       plus a static assert, so that this function is
404     //       more easily inlined by the compiler.
405 }
406 
cilk_fiber_is_resumable(cilk_fiber * fiber)407 int cilk_fiber_is_resumable(cilk_fiber *fiber)
408 {
409     return fiber->is_resumable();
410 }
411 
cilk_fiber_get_stack_base(cilk_fiber * fiber)412 char* cilk_fiber_get_stack_base(cilk_fiber *fiber)
413 {
414     return fiber->get_stack_base();
415 }
416 
417 
418 #if defined(_WIN32) && 0 // Only works on Windows.  Disable debugging for now.
419 #define DBG_STACK_OPS(_fmt, ...) __cilkrts_dbgprintf(_fmt, __VA_ARGS__)
420 #else
421 #define DBG_STACK_OPS(_fmt, ...)
422 #endif
423 
cilk_fiber_set_stack_op(cilk_fiber * fiber,__cilk_tbb_stack_op_thunk o)424 void cilk_fiber_set_stack_op(cilk_fiber *fiber,
425                              __cilk_tbb_stack_op_thunk o)
426 {
427     cilk_fiber_data *fdata = cilk_fiber_get_data(fiber);
428     DBG_STACK_OPS ("cilk_fiber_set_stack_op - cilk_fiber %p, routine: %p, data: %p\n",
429                    fiber,
430                    o.routine,
431                    o.data);
432     fdata->stack_op_routine = o.routine;
433     fdata->stack_op_data = o.data;
434 }
435 
436 #if 0    // Debugging function
437 static
438 const char *NameStackOp (enum __cilk_tbb_stack_op op)
439 {
440     switch(op)
441     {
442         case CILK_TBB_STACK_ORPHAN: return "CILK_TBB_STACK_ORPHAN";
443         case CILK_TBB_STACK_ADOPT: return "CILK_TBB_STACK_ADOPT";
444         case CILK_TBB_STACK_RELEASE: return "CILK_TBB_STACK_RELEASE";
445         default: return "Unknown";
446     }
447 }
448 #endif
449 
450 /*
451  * Save TBB interop information for an unbound thread.  It will get picked
452  * up when the thread is bound to the runtime.
453  */
cilk_fiber_tbb_interop_save_stack_op_info(__cilk_tbb_stack_op_thunk o)454 void cilk_fiber_tbb_interop_save_stack_op_info(__cilk_tbb_stack_op_thunk o)
455 {
456     __cilk_tbb_stack_op_thunk *saved_thunk =
457         __cilkrts_get_tls_tbb_interop();
458 
459     DBG_STACK_OPS("Calling save_stack_op; o.routine=%p, o.data=%p, saved_thunk=%p\n",
460                   o.routine, o.data, saved_thunk);
461 
462     // If there is not already space allocated, allocate some.
463     if (NULL == saved_thunk) {
464         saved_thunk = (__cilk_tbb_stack_op_thunk*)
465             __cilkrts_malloc(sizeof(__cilk_tbb_stack_op_thunk));
466         __cilkrts_set_tls_tbb_interop(saved_thunk);
467     }
468 
469     *saved_thunk = o;
470 
471     DBG_STACK_OPS ("Unbound Thread %04x: tbb_interop_save_stack_op_info - saved info\n",
472                    cilkos_get_current_thread_id());
473 }
474 
475 /*
476  * Save TBB interop information from the cilk_fiber.  It will get picked
477  * up when the thread is bound to the runtime next time.
478  */
cilk_fiber_tbb_interop_save_info_from_stack(cilk_fiber * fiber)479 void cilk_fiber_tbb_interop_save_info_from_stack(cilk_fiber *fiber)
480 {
481     __cilk_tbb_stack_op_thunk *saved_thunk;
482     cilk_fiber_data* fdata;
483 
484     if (NULL == fiber)
485         return;
486 
487     fdata = cilk_fiber_get_data(fiber);
488     // If there is no TBB interop data, just return
489     if (NULL == fdata->stack_op_routine)
490         return;
491 
492     saved_thunk = __cilkrts_get_tls_tbb_interop();
493 
494     // If there is not already space allocated, allocate some.
495     if (NULL == saved_thunk) {
496         saved_thunk = (__cilk_tbb_stack_op_thunk*)
497             __cilkrts_malloc(sizeof(__cilk_tbb_stack_op_thunk));
498         __cilkrts_set_tls_tbb_interop(saved_thunk);
499     }
500 
501     saved_thunk->routine = fdata->stack_op_routine;
502     saved_thunk->data = fdata->stack_op_data;
503 }
504 
505 /*
506  * If there's TBB interop information that was saved before the thread was
507  * bound, apply it now
508  */
cilk_fiber_tbb_interop_use_saved_stack_op_info(cilk_fiber * fiber)509 void cilk_fiber_tbb_interop_use_saved_stack_op_info(cilk_fiber* fiber)
510 {
511     __cilk_tbb_stack_op_thunk *saved_thunk =
512         __cilkrts_get_tls_tbb_interop();
513 
514     CILK_ASSERT(fiber);
515     // If we haven't allocated a TBB interop index, we don't have any saved info
516     if (NULL == saved_thunk) {
517         DBG_STACK_OPS ("cilk_fiber %p: tbb_interop_use_saved_stack_op_info - no saved info\n",
518                        fiber);
519         return;
520     }
521 
522     DBG_STACK_OPS ("cilk_fiber %p: tbb_interop_use_saved_stack_op_info - using saved info\n",
523                    fiber);
524 
525      // Associate the saved info with the __cilkrts_stack
526     cilk_fiber_set_stack_op(fiber, *saved_thunk);
527 
528     // Free the saved data.  We'll save it again if needed when the code
529     // returns from the initial function
530     cilk_fiber_tbb_interop_free_stack_op_info();
531 }
532 
533 /*
534  * Free saved TBB interop memory.  Should only be called when the thread is
535  * not bound.
536  */
cilk_fiber_tbb_interop_free_stack_op_info(void)537 void cilk_fiber_tbb_interop_free_stack_op_info(void)
538 {
539     __cilk_tbb_stack_op_thunk *saved_thunk =
540         __cilkrts_get_tls_tbb_interop();
541 
542     // If we haven't allocated a TBB interop index, we don't have any saved info
543     if (NULL == saved_thunk)
544         return;
545 
546     DBG_STACK_OPS ("tbb_interop_free_stack_op_info - freeing saved info\n");
547 
548     // Free the memory and wipe out the TLS value
549     __cilkrts_free(saved_thunk);
550     __cilkrts_set_tls_tbb_interop(NULL);
551 }
552 
553 
554 
555 #if NEED_FIBER_REF_COUNTS
cilk_fiber_has_references(cilk_fiber * fiber)556 int cilk_fiber_has_references(cilk_fiber *fiber)
557 {
558     return (fiber->get_ref_count() > 0);
559 }
560 
cilk_fiber_get_ref_count(cilk_fiber * fiber)561 int cilk_fiber_get_ref_count(cilk_fiber *fiber)
562 {
563     return fiber->get_ref_count();
564 }
565 
cilk_fiber_add_reference(cilk_fiber * fiber)566 void cilk_fiber_add_reference(cilk_fiber *fiber)
567 {
568     fiber->inc_ref_count();
569 }
570 #endif // NEED_FIBER_REF_COUNTS
571 
572 
573 } // End extern "C"
574 
575 
sysdep()576 cilk_fiber_sysdep* cilk_fiber::sysdep()
577 {
578     return static_cast<cilk_fiber_sysdep*>(this);
579 }
580 
581 
cilk_fiber()582 cilk_fiber::cilk_fiber()
583     : m_start_proc(NULL)
584     , m_post_switch_proc(NULL)
585     , m_pending_remove_ref(NULL)
586     , m_pending_pool(NULL)
587     , m_flags(0)
588 {
589     // Clear cilk_fiber_data base-class data members
590     std::memset((cilk_fiber_data*) this, 0, sizeof(cilk_fiber_data));
591 
592     // cilk_fiber data members
593     init_ref_count(0);
594 }
595 
cilk_fiber(std::size_t stack_size)596 cilk_fiber::cilk_fiber(std::size_t stack_size)
597 {
598     *this = cilk_fiber();  // A delegating constructor would be nice here
599     this->stack_size = stack_size;
600 }
601 
~cilk_fiber()602 cilk_fiber::~cilk_fiber()
603 {
604     // Empty destructor.
605 }
606 
607 
get_stack_base()608 char* cilk_fiber::get_stack_base()
609 {
610     return this->sysdep()->get_stack_base_sysdep();
611 }
612 
allocate_from_heap(std::size_t stack_size)613 cilk_fiber* cilk_fiber::allocate_from_heap(std::size_t stack_size)
614 {
615     // Case 1: pool is NULL. create a new fiber from the heap
616     // No need for locks here.
617     cilk_fiber_sysdep* ret =
618         (cilk_fiber_sysdep*) __cilkrts_malloc(sizeof(cilk_fiber_sysdep));
619 
620     // Error condition. If we failed to allocate a fiber from the
621     // heap, we are in trouble though...
622     if (!ret)
623         return NULL;
624 
625     ::new(ret) cilk_fiber_sysdep(stack_size);
626 
627     CILK_ASSERT(0 == ret->m_flags);
628     CILK_ASSERT(NULL == ret->m_pending_remove_ref);
629     CILK_ASSERT(NULL == ret->m_pending_pool);
630     ret->init_ref_count(1);
631     return ret;
632 }
633 
634 
635 #if USE_FIBER_TRY_ALLOCATE_FROM_POOL
636 /**
637  * Helper method: try to allocate a fiber from this pool or its
638  * ancestors without going to the OS / heap.
639  *
640  * Returns allocated pool, or NULL if no pool is found.
641  *
642  * If pool contains a suitable fiber. Return it.  Otherwise, try to
643  * recursively grab a fiber from the parent pool, if there is one.
644  *
645  * This method will not allocate a fiber from the heap.
646  *
647  * This method could be written either recursively or iteratively.
648  * It probably does not matter which one we do.
649  *
650  * @note This method is compiled, but may not be used unless the
651  * USE_FIBER_TRY_ALLOCATE_FROM_POOL switch is set.
652  */
try_allocate_from_pool_recursive(cilk_fiber_pool * pool)653 cilk_fiber* cilk_fiber::try_allocate_from_pool_recursive(cilk_fiber_pool* pool)
654 {
655     cilk_fiber* ret = NULL;
656 
657     if (pool->size > 0) {
658         // Try to get the lock.
659         if (pool->lock) {
660             // For some reason, it seems to be better to just block on the parent
661             // pool lock, instead of using a try-lock?
662 #define USE_TRY_LOCK_IN_FAST_ALLOCATE 0
663 #if USE_TRY_LOCK_IN_FAST_ALLOCATE
664             int got_lock = spin_mutex_trylock(pool->lock);
665             if (!got_lock) {
666                 // If we fail, skip to the parent.
667                 if (pool->parent) {
668                     return try_allocate_from_pool_recursive(pool->parent);
669                 }
670             }
671 #else
672             spin_mutex_lock(pool->lock);
673 #endif
674         }
675 
676         // Check in the pool if we have the lock.
677         if (pool->size > 0) {
678             ret = pool->fibers[--pool->size];
679         }
680 
681         // Release the lock once we are done updating pool fields.
682         if (pool->lock) {
683             spin_mutex_unlock(pool->lock);
684         }
685     }
686 
687     if ((!ret) && (pool->parent)) {
688         return try_allocate_from_pool_recursive(pool->parent);
689     }
690 
691     if (ret) {
692         // When we pull a fiber out of the pool, set its reference
693         // count before we return it.
694         ret->init_ref_count(1);
695     }
696     return ret;
697 }
698 #endif // USE_FIBER_TRY_ALLOCATE_FROM_POOL
699 
700 
allocate(cilk_fiber_pool * pool)701 cilk_fiber* cilk_fiber::allocate(cilk_fiber_pool* pool)
702 {
703     // Pool should not be NULL in this method.  But I'm not going to
704     // actually assert it, because we are likely to seg fault anyway
705     // if it is.
706     // CILK_ASSERT(NULL != pool);
707 
708     cilk_fiber *ret = NULL;
709 
710 #if USE_FIBER_TRY_ALLOCATE_FROM_POOL
711     // "Fast" path, which doesn't go to the heap or OS until checking
712     // the ancestors first.
713     ret = try_allocate_from_pool_recursive(pool);
714     if (ret)
715         return ret;
716 #endif
717 
718     // If we don't get anything from the "fast path", then go through
719     // a slower path to look for a fiber.
720     //
721     //  1. Lock the pool if it is shared.
722     //  2. Look in our local pool.  If we find one, release the lock
723     //     and quit searching.
724     //  3. Otherwise, check whether we can allocate from heap.
725     //  4. Release the lock if it was acquired.
726     //  5. Try to allocate from the heap, if step 3 said we could.
727     //     If we find a fiber, then quit searching.
728     //  6. If none of these steps work, just recursively try again
729     //     from the parent.
730 
731     // 1. Lock the pool if it is shared.
732     if (pool->lock) {
733         spin_mutex_lock(pool->lock);
734     }
735 
736     // 2. Look in local pool.
737     if (pool->size > 0) {
738         ret = pool->fibers[--pool->size];
739         if (ret) {
740             // If we found one, release the lock once we are
741             // done updating pool fields, and break out of the
742             // loop.
743             if (pool->lock) {
744                 spin_mutex_unlock(pool->lock);
745             }
746 
747             // When we pull a fiber out of the pool, set its reference
748             // count just in case.
749             ret->init_ref_count(1);
750             return ret;
751         }
752     }
753 
754     // 3. Check whether we can allocate from the heap.
755     bool can_allocate_from_heap = false;
756     if (pool->total < pool->alloc_max) {
757         // Track that we are allocating a new fiber from the
758         // heap, originating from this pool.
759         // This increment may be undone if we happen to fail to
760         // allocate from the heap.
761         increment_pool_total(pool);
762         can_allocate_from_heap = true;
763     }
764 
765     // 4. Unlock the pool, and then allocate from the heap.
766     if (pool->lock) {
767         spin_mutex_unlock(pool->lock);
768     }
769 
770     // 5. Actually try to allocate from the heap / OS.
771     if (can_allocate_from_heap) {
772         ret = allocate_from_heap(pool->stack_size);
773         // If we got something from the heap, just return it.
774         if (ret) {
775             return ret;
776         }
777 
778         // Otherwise, we failed in our attempt to allocate a
779         // fiber from the heap.  Grab the lock and decrement
780         // the total again.
781         if (pool->lock) {
782             spin_mutex_lock(pool->lock);
783         }
784         decrement_pool_total(pool, 1);
785         if (pool->lock) {
786             spin_mutex_unlock(pool->lock);
787         }
788     }
789 
790     // 6. If we get here, then searching this pool failed.  Go search
791     // the parent instead if we have one.
792     if (pool->parent) {
793         return allocate(pool->parent);
794     }
795 
796     return ret;
797 }
798 
remove_reference(cilk_fiber_pool * pool)799 int cilk_fiber::remove_reference(cilk_fiber_pool* pool)
800 {
801     int ref_count = this->dec_ref_count();
802     if (ref_count == 0) {
803         if (pool) {
804             deallocate_self(pool);
805         }
806         else {
807             deallocate_to_heap();
808         }
809     }
810     return ref_count;
811 }
812 
allocate_from_thread()813 cilk_fiber* cilk_fiber::allocate_from_thread()
814 {
815     void* retmem = __cilkrts_malloc(sizeof(cilk_fiber_sysdep));
816     CILK_ASSERT(retmem);
817     cilk_fiber_sysdep* ret = ::new(retmem) cilk_fiber_sysdep(from_thread);
818 
819     // A fiber allocated from a thread begins with a reference count
820     // of 2.  The first is for being created, and the second is for
821     // being running.
822     //
823     // Suspending this fiber will decrement the count down to 1.
824     ret->init_ref_count(2);
825 
826 #if SUPPORT_GET_CURRENT_FIBER
827     // We're creating the main fiber for this thread. Set this fiber as the
828     // current fiber.
829     cilkos_set_tls_cilk_fiber(ret);
830 #endif
831     return ret;
832 }
833 
deallocate_from_thread()834 int cilk_fiber::deallocate_from_thread()
835 {
836     CILK_ASSERT(this->is_allocated_from_thread());
837 #if SUPPORT_GET_CURRENT_FIBER
838     CILK_ASSERT(this == cilkos_get_tls_cilk_fiber());
839     // Reverse of "allocate_from_thread".
840     cilkos_set_tls_cilk_fiber(NULL);
841 #endif
842 
843     this->assert_ref_count_at_least(2);
844 
845     // Suspending the fiber should conceptually decrement the ref
846     // count by 1.
847     cilk_fiber_sysdep* self = this->sysdep();
848     self->convert_fiber_back_to_thread();
849 
850     // Then, freeing the fiber itself decrements the ref count again.
851     int ref_count = this->sub_from_ref_count(2);
852     if (ref_count == 0) {
853         self->~cilk_fiber_sysdep();
854         __cilkrts_free(self);
855     }
856     return ref_count;
857 }
858 
remove_reference_from_thread()859 int cilk_fiber::remove_reference_from_thread()
860 {
861     int ref_count = dec_ref_count();
862     if (ref_count == 0) {
863         cilk_fiber_sysdep* self = this->sysdep();
864         self->~cilk_fiber_sysdep();
865         __cilkrts_free(self);
866     }
867     return ref_count;
868 }
869 
870 
871 #if SUPPORT_GET_CURRENT_FIBER
get_current_fiber()872 cilk_fiber* cilk_fiber::get_current_fiber()
873 {
874     return cilk_fiber_sysdep::get_current_fiber_sysdep();
875 }
876 #endif
877 
do_post_switch_actions()878 void cilk_fiber::do_post_switch_actions()
879 {
880     if (m_post_switch_proc)
881     {
882         cilk_fiber_proc proc = m_post_switch_proc;
883         m_post_switch_proc = NULL;
884         proc(this);
885     }
886 
887     if (m_pending_remove_ref)
888     {
889         m_pending_remove_ref->remove_reference(m_pending_pool);
890 
891         // Even if we don't free it,
892         m_pending_remove_ref = NULL;
893         m_pending_pool   = NULL;
894     }
895 }
896 
suspend_self_and_resume_other(cilk_fiber * other)897 void cilk_fiber::suspend_self_and_resume_other(cilk_fiber* other)
898 {
899 #if FIBER_DEBUG >=1
900     fprintf(stderr, "suspend_self_and_resume_other: self =%p, other=%p [owner=%p, resume_sf=%p]\n",
901             this, other, other->owner, other->resume_sf);
902 #endif
903 
904     // Decrement my reference count (to suspend)
905     // Increment other's count (to resume)
906     // Suspended fiber should have a reference count of at least 1.  (It is not in a pool).
907     this->dec_ref_count();
908     other->inc_ref_count();
909     this->assert_ref_count_at_least(1);
910 
911     // Pass along my owner.
912     other->owner = this->owner;
913     this->owner  = NULL;
914 
915     // Change this fiber to resumable.
916     CILK_ASSERT(!this->is_resumable());
917     this->set_resumable(true);
918 
919     // Normally, I'd assert other->is_resumable().  But this flag may
920     // be false the first time we try to "resume" a fiber.
921     cilk_fiber_sysdep* self = this->sysdep();
922     self->suspend_self_and_resume_other_sysdep(other->sysdep());
923 
924     // HAVE RESUMED EXECUTION
925     // When we come back here, we should have at least two references:
926     // one for the fiber being allocated / out of a pool, and one for it being active.
927     this->assert_ref_count_at_least(2);
928 }
929 
930 NORETURN
remove_reference_from_self_and_resume_other(cilk_fiber_pool * self_pool,cilk_fiber * other)931 cilk_fiber::remove_reference_from_self_and_resume_other(cilk_fiber_pool* self_pool,
932                                                         cilk_fiber*      other)
933 {
934     // Decrement my reference count once (to suspend)
935     // Increment other's count (to resume)
936     // Suspended fiber should have a reference count of at least 1.  (It is not in a pool).
937     this->dec_ref_count();
938     other->inc_ref_count();
939 
940     // Set a pending remove reference for this fiber, once we have
941     // actually switched off.
942     other->m_pending_remove_ref = this;
943     other->m_pending_pool   = self_pool;
944 
945     // Pass along my owner.
946     other->owner = this->owner;
947     this->owner  = NULL;
948 
949     // Since we are deallocating self, this fiber does not become
950     // resumable.
951     CILK_ASSERT(!this->is_resumable());
952 
953     cilk_fiber_sysdep* self = this->sysdep();
954     self->jump_to_resume_other_sysdep(other->sysdep());
955 
956     __cilkrts_bug("Deallocating fiber.  We should never come back here.");
957     std::abort();
958 }
959 
960 
deallocate_to_heap()961 void cilk_fiber::deallocate_to_heap()
962 {
963     cilk_fiber_sysdep* self = this->sysdep();
964     self->~cilk_fiber_sysdep();
965     __cilkrts_free(self);
966 }
967 
deallocate_self(cilk_fiber_pool * pool)968 void cilk_fiber::deallocate_self(cilk_fiber_pool* pool)
969 {
970     this->set_resumable(false);
971 
972     CILK_ASSERT(NULL != pool);
973     CILK_ASSERT(!this->is_allocated_from_thread());
974     this->assert_ref_count_equals(0);
975 
976     // Cases:
977     //
978     // 1. pool has space:  Add to this pool.
979     // 2. pool is full:    Give some fibers to parent, and then free
980     //                     enough to make space for the fiber we are deallocating.
981     //                     Then put the fiber back into the pool.
982 
983     const bool need_lock = pool->lock;
984     // Grab the lock for the remaining cases.
985     if (need_lock) {
986         spin_mutex_lock(pool->lock);
987     }
988 
989     // Case 1: this pool has space.  Return the fiber.
990     if (pool->size < pool->max_size)
991     {
992         // Add this fiber to pool
993         pool->fibers[pool->size++] = this;
994         if (need_lock) {
995             spin_mutex_unlock(pool->lock);
996         }
997         return;
998     }
999 
1000     // Case 2: Pool is full.
1001     //
1002     // First free up some space by giving fibers to the parent.
1003     if (pool->parent)
1004     {
1005         // Pool is full.  Move all but "num_to_keep" fibers to parent,
1006         // if we can.
1007         unsigned num_to_keep = pool->max_size/2 + pool->max_size/4;
1008         cilk_fiber_pool_move_fibers_to_parent_pool(pool, num_to_keep);
1009     }
1010 
1011     if (need_lock) {
1012         spin_mutex_unlock(pool->lock);
1013     }
1014 
1015     // Now, free a fiber to make room for the one we need to put back,
1016     // and then put this fiber back.  This step may actually return
1017     // fibers to the heap.
1018     cilk_fiber_pool_free_fibers_from_pool(pool, pool->max_size -1, this);
1019 }
1020 
1021 
1022 // NOTE: Except for print-debug, this code is the same as in Windows.
invoke_tbb_stack_op(__cilk_tbb_stack_op op)1023 void cilk_fiber::invoke_tbb_stack_op(__cilk_tbb_stack_op op)
1024 {
1025     cilk_fiber_data *fdata = this->get_data();
1026 
1027     if (0 == fdata->stack_op_routine)
1028     {
1029         if  (CILK_TBB_STACK_RELEASE != op)
1030             DBG_STACK_OPS ("Wkr %p: invoke_tbb_stack_op - %s (%d) for cilk_fiber %p, fiber %p, thread id %04x - No stack op routine\n",
1031                            fdata->owner,
1032                            NameStackOp(op),
1033                            op,
1034                            fdata,
1035                            this,
1036                            cilkos_get_current_thread_id());
1037         return;
1038     }
1039 
1040     // Call TBB to do it's thing
1041     DBG_STACK_OPS ("Wkr %p: invoke_tbb_stack_op - op %s data %p for cilk_fiber %p, fiber %p, thread id %04x\n",
1042                    fdata->owner,
1043                    NameStackOp(op),
1044                    fdata->stack_op_data,
1045                    fdata,
1046                    this,
1047                    cilkos_get_current_thread_id());
1048 
1049     (*fdata->stack_op_routine)(op, fdata->stack_op_data);
1050     if (op == CILK_TBB_STACK_RELEASE)
1051     {
1052         fdata->stack_op_routine = 0;
1053         fdata->stack_op_data = 0;
1054     }
1055 }
1056 
1057 
1058 
1059 #if NEED_FIBER_REF_COUNTS
1060 
atomic_inc_ref_count()1061 void cilk_fiber::atomic_inc_ref_count()
1062 {
1063     cilkos_atomic_add(&m_outstanding_references, 1);
1064 }
1065 
atomic_dec_ref_count()1066 long cilk_fiber::atomic_dec_ref_count()
1067 {
1068     return cilkos_atomic_add(&m_outstanding_references, -1);
1069 }
1070 
atomic_sub_from_ref_count(long v)1071 long cilk_fiber::atomic_sub_from_ref_count(long v)
1072 {
1073     return cilkos_atomic_add(&m_outstanding_references, -v);
1074 }
1075 
1076 #endif // NEED_FIBER_REF_COUNTS
1077 
1078 /* End cilk_fibers.cpp */
1079