1/* cilk_fiber-unix.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#include "cilk_fiber-unix.h"
40#include "cilk_malloc.h"
41#include "bug.h"
42#include "os.h"
43
44#include <cstdio>
45#include <cstdlib>
46
47#include <errno.h>
48#include <sys/mman.h>
49#include <unistd.h>
50
51// You'd think that getting a defintion for alloca would be easy.  But you'd
52// be wrong. Here's a variant on what's recommended in the autoconf doc.  I've
53// remove the Windows portion since this is Unix-specific code.
54#if defined HAVE_ALLOCA_H
55#   include <alloca.h>
56#elif defined __GNUC__
57#   define alloca __builtin_alloca
58#elif defined _AIX
59#   define alloca __alloca
60#else
61#   include <stddef.h>
62#   ifdef  __cplusplus
63extern "C"
64#   endif
65void *alloca (size_t);
66#endif
67
68// MAP_ANON is deprecated on Linux, but seems to be required on Mac...
69#ifndef MAP_ANONYMOUS
70#define MAP_ANONYMOUS MAP_ANON
71#endif
72
73// Magic number for sanity checking fiber structure
74const unsigned magic_number = 0x5afef00d;
75
76int cilk_fiber_sysdep::s_page_size = getpagesize();
77
78cilk_fiber_sysdep::cilk_fiber_sysdep(std::size_t stack_size)
79    : cilk_fiber(stack_size)
80    , m_magic(magic_number)
81{
82    // Set m_stack and m_stack_base.
83    make_stack(stack_size);
84
85    // Get high-address of stack, with 32-bytes of spare space, and rounded
86    // down to the nearest 32-byte boundary.
87    const uintptr_t align_mask = 32 - 1;
88    m_stack_base -= ((std::size_t) m_stack_base) & align_mask;
89}
90
91cilk_fiber_sysdep::cilk_fiber_sysdep(from_thread_t)
92    : cilk_fiber()
93    , m_magic(magic_number)
94{
95    this->set_allocated_from_thread(true);
96
97    // Dummy stack data for thread-main fiber
98    m_stack      = NULL;
99    m_stack_base = NULL;
100}
101
102void cilk_fiber_sysdep::convert_fiber_back_to_thread()
103{
104    // Does nothing on Linux.
105}
106
107cilk_fiber_sysdep::~cilk_fiber_sysdep()
108{
109    CILK_ASSERT(magic_number == m_magic);
110    if (!this->is_allocated_from_thread())
111        free_stack();
112}
113
114#if SUPPORT_GET_CURRENT_FIBER
115cilk_fiber_sysdep* cilk_fiber_sysdep::get_current_fiber_sysdep()
116{
117    return cilkos_get_tls_cilk_fiber();
118}
119#endif
120
121// Jump to resume other fiber.  We may or may not come back.
122inline void cilk_fiber_sysdep::resume_other_sysdep(cilk_fiber_sysdep* other)
123{
124    if (other->is_resumable()) {
125        other->set_resumable(false);
126        // Resume by longjmp'ing to the place where we suspended.
127        CILK_LONGJMP(other->m_resume_jmpbuf);
128    }
129    else {
130        // Otherwise, we've never ran this fiber before.  Start the
131        // proc method.
132        other->run();
133    }
134}
135
136void cilk_fiber_sysdep::suspend_self_and_resume_other_sysdep(cilk_fiber_sysdep* other)
137{
138#if SUPPORT_GET_CURRENT_FIBER
139    cilkos_set_tls_cilk_fiber(other);
140#endif
141    CILK_ASSERT(this->is_resumable());
142
143
144    // Jump to the other fiber.  We expect to come back.
145    if (! CILK_SETJMP(m_resume_jmpbuf)) {
146        resume_other_sysdep(other);
147    }
148
149    // Return here when another fiber resumes me.
150    // If the fiber that switched to me wants to be deallocated, do it now.
151    do_post_switch_actions();
152}
153
154NORETURN cilk_fiber_sysdep::jump_to_resume_other_sysdep(cilk_fiber_sysdep* other)
155{
156#if SUPPORT_GET_CURRENT_FIBER
157    cilkos_set_tls_cilk_fiber(other);
158#endif
159    CILK_ASSERT(!this->is_resumable());
160
161    // Jump to the other fiber.  But we are never coming back because
162    // this fiber is being reset.
163    resume_other_sysdep(other);
164
165    // We should never come back here...
166    __cilkrts_bug("Should not get here");
167}
168
169// GCC doesn't allow us to call __builtin_longjmp in the same function that
170// calls __builtin_setjmp, so create a new function to house the call to
171// __builtin_longjmp
172static void __attribute__((noinline))
173do_cilk_longjmp(__CILK_JUMP_BUFFER jmpbuf)
174{
175    CILK_LONGJMP(jmpbuf);
176}
177
178NORETURN cilk_fiber_sysdep::run()
179{
180    // Only fibers created from a pool have a proc method to run and execute.
181    CILK_ASSERT(m_start_proc);
182    CILK_ASSERT(!this->is_allocated_from_thread());
183    CILK_ASSERT(!this->is_resumable());
184
185    // TBD: This setjmp/longjmp pair simply changes the stack pointer.
186    // We could probably replace this code with some assembly.
187    if (! CILK_SETJMP(m_resume_jmpbuf))
188    {
189        // Calculate the size of the current stack frame (i.e., this
190        // run() function.
191        size_t frame_size = (size_t)JMPBUF_FP(m_resume_jmpbuf) - (size_t)JMPBUF_SP(m_resume_jmpbuf);
192
193        // Macs require 16-byte alignment.  Do it always because it just
194        // doesn't matter
195        if (frame_size & (16-1))
196            frame_size += 16 - (frame_size  & (16-1));
197
198        // Assert that we are getting a reasonable frame size out of
199        // it.  If this run() function is using more than 4096 bytes
200        // of space for its local variables / any state that spills to
201        // registers, something is probably *very* wrong here...
202        //
203        // 4096 bytes just happens to be a number that seems "large
204        // enough" --- for an example GCC 32-bit compilation, the
205        // frame size was 48 bytes.
206        CILK_ASSERT(frame_size < 4096);
207
208        // Change stack pointer to fiber stack.  Offset the
209        // calculation by the frame size, so that we've allocated
210        // enough extra space from the top of the stack we are
211        // switching to for any temporaries required for this run()
212        // function.
213        JMPBUF_SP(m_resume_jmpbuf) = m_stack_base - frame_size;
214
215        // GCC doesn't allow us to call __builtin_longjmp in the same function
216        // that calls __builtin_setjmp, so it's been moved into it's own
217        // function that cannot be inlined.
218        do_cilk_longjmp(m_resume_jmpbuf);
219    }
220
221    // Note: our resetting of the stack pointer is valid only if the
222    // compiler has not saved any temporaries onto the stack for this
223    // function before the longjmp that we still care about at this
224    // point.
225
226    // Verify that 1) 'this' is still valid and 2) '*this' has not been
227    // corrupted.
228    CILK_ASSERT(magic_number == m_magic);
229
230    // If the fiber that switched to me wants to be deallocated, do it now.
231    do_post_switch_actions();
232
233    // Now call the user proc on the new stack
234    m_start_proc(this);
235
236    // alloca() to force generation of frame pointer.  The argument to alloca
237    // is contrived to prevent the compiler from optimizing it away.  This
238    // code should never actually be executed.
239    int* dummy = (int*) alloca((sizeof(int) + (std::size_t) m_start_proc) & 0x1);
240    *dummy = 0xface;
241
242    // User proc should never return.
243    __cilkrts_bug("Should not get here");
244}
245
246void cilk_fiber_sysdep::make_stack(size_t stack_size)
247{
248    char* p;
249    // We've already validated that the stack size is page-aligned and
250    // is a reasonable value.  No need to do any extra rounding here.
251    size_t rounded_stack_size = stack_size;
252
253    // Normally, we have already validated that the stack size is
254    // aligned to 4K.  In the rare case that pages are huge though, we
255    // need to do some extra checks.
256    if (rounded_stack_size < 3 * (size_t)s_page_size) {
257        // If the specified stack size is too small, round up to 3
258        // pages.  We need at least 2 extra for the guard pages.
259        rounded_stack_size = 3 * (size_t)s_page_size;
260    }
261    else {
262        // Otherwise, the stack size is large enough, but might not be
263        // a multiple of page size.  Round up to nearest multiple of
264        // s_page_size, just to be safe.
265        size_t remainder = rounded_stack_size % s_page_size;
266        if (remainder) {
267            rounded_stack_size += s_page_size - remainder;
268        }
269    }
270
271    p = (char*)mmap(0, rounded_stack_size,
272                    PROT_READ|PROT_WRITE,
273                    MAP_PRIVATE|MAP_ANONYMOUS,
274                    -1, 0);
275    if (MAP_FAILED == p) {
276        // For whatever reason (probably ran out of memory), mmap() failed.
277        // There is no stack to return, so the program loses parallelism.
278        m_stack = NULL;
279        m_stack_base = NULL;
280        return;
281    }
282
283    // mprotect guard pages.
284    mprotect(p + rounded_stack_size - s_page_size, s_page_size, PROT_NONE);
285    mprotect(p, s_page_size, PROT_NONE);
286
287    m_stack = p;
288    m_stack_base = p + rounded_stack_size - s_page_size;
289}
290
291
292void cilk_fiber_sysdep::free_stack()
293{
294    if (m_stack) {
295        size_t rounded_stack_size = m_stack_base - m_stack + s_page_size;
296        if (munmap(m_stack, rounded_stack_size) < 0)
297            __cilkrts_bug("Cilk: stack munmap failed error %d\n", errno);
298    }
299}
300
301/* End cilk_fiber-unix.cpp */
302