1 //  Copyright (c) 2006, Giovanni P. Deretta
2 //
3 //  This code may be used under either of the following two licences:
4 //
5 //  Permission is hereby granted, free of charge, to any person obtaining a copy
6 //  of this software and associated documentation files (the "Software"), to deal
7 //  in the Software without restriction, including without limitation the rights
8 //  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 //  copies of the Software, and to permit persons to whom the Software is
10 //  furnished to do so, subject to the following conditions:
11 //
12 //  The above copyright notice and this permission notice shall be included in
13 //  all copies or substantial portions of the Software.
14 //
15 //  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 //  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 //  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 //  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 //  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 //  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 //  THE SOFTWARE. OF SUCH DAMAGE.
22 //
23 //  Or:
24 //
25 //  Distributed under the Boost Software License, Version 1.0.
26 //  (See accompanying file LICENSE_1_0.txt or copy at
27 //  http://www.boost.org/LICENSE_1_0.txt)
28 
29 #ifndef HPX_RUNTIME_THREADS_COROUTINES_DETAIL_CONTEXT_WINDOWS_HPP
30 #define HPX_RUNTIME_THREADS_COROUTINES_DETAIL_CONTEXT_WINDOWS_HPP
31 
32 #include <windows.h>
33 #include <winnt.h>
34 
35 #include <hpx/config.hpp>
36 #include <hpx/runtime/threads/coroutines/detail/swap_context.hpp>
37 #include <hpx/runtime/threads/coroutines/exception.hpp>
38 #include <hpx/util/assert.hpp>
39 #include <hpx/util/get_and_reset_value.hpp>
40 #include <hpx/util/unused.hpp>
41 
42 #include <boost/system/error_code.hpp>
43 #include <boost/system/system_error.hpp>
44 
45 #include <atomic>
46 #include <cstddef>
47 #include <cstdint>
48 
49 #if defined(HPX_HAVE_SWAP_CONTEXT_EMULATION)
50 extern "C" void switch_to_fiber(void* lpFiber) throw();
51 #endif
52 
53 namespace hpx { namespace threads { namespace coroutines
54 {
55     // On Windows we need a special preparation for the main coroutines thread
56     struct prepare_main_thread
57     {
prepare_main_threadhpx::threads::coroutines::prepare_main_thread58         prepare_main_thread()
59         {
60             LPVOID result = ConvertThreadToFiber(nullptr);
61             HPX_ASSERT(nullptr != result);
62             HPX_UNUSED(result);
63         }
64 
~prepare_main_threadhpx::threads::coroutines::prepare_main_thread65         ~prepare_main_thread()
66         {
67             BOOL result = ConvertFiberToThread();
68             HPX_ASSERT(FALSE != result);
69             HPX_UNUSED(result);
70         }
71     };
72 
73     namespace detail { namespace windows
74     {
75         typedef LPVOID fiber_ptr;
76 
77 #if _WIN32_WINNT < 0x0600
78         /*
79          * This number (0x1E00) has been sighted in the wild (at least on
80          * windows XP systems) as return value from GetCurrentFiber() on non
81          * fibrous threads.
82          * This is somehow related to OS/2 where the current fiber pointer is
83          * overloaded as a version field.
84          * On non-NT systems, 0 is returned.
85          */
86         fiber_ptr const fiber_magic = reinterpret_cast<fiber_ptr>(0x1E00);
87 #endif
88 
89         /*
90          * Return true if current thread is a fiber.
91          */
is_fiber()92         inline bool is_fiber()
93         {
94 #if _WIN32_WINNT >= 0x0600
95             return IsThreadAFiber() ? true : false;
96 #else
97             fiber_ptr current = GetCurrentFiber();
98             return current != nullptr && current != fiber_magic;
99 #endif
100         }
101 
102         /*
103          * Windows implementation for the context_impl_base class.
104          * @note context_impl is not required to be consistent
105          * If not initialized it can only be swapped out, not in
106          * (at that point it will be initialized).
107          */
108         class fibers_context_impl_base : detail::context_impl_base
109         {
110         public:
111             /**
112              * Create an empty context.
113              * An empty context cannot be restored from,
114              * but can be saved in.
115              */
fibers_context_impl_base()116             fibers_context_impl_base() : m_ctx(nullptr) {}
117 
118             /*
119              * Free function. Saves the current context in @p from
120              * and restores the context in @p to. On windows the from
121              * parameter is ignored. The current context is saved on the
122              * current fiber.
123              * Note that if the current thread is not a fiber, it will be
124              * converted to fiber on the fly on call and unconverted before
125              * return. This is expensive. The user should convert the
126              * current thread to a fiber once on thread creation for better performance.
127              * Note that we can't leave the thread unconverted on return or else we
128              * will leak resources on thread destruction. Do the right thing by
129              * default.
130              */
swap_context(fibers_context_impl_base & from,const fibers_context_impl_base & to,default_hint)131             friend void swap_context(fibers_context_impl_base& from,
132                 const fibers_context_impl_base& to, default_hint)
133             {
134                 if (!is_fiber())
135                 {
136                     HPX_ASSERT(from.m_ctx == nullptr);
137                     from.m_ctx = ConvertThreadToFiber(nullptr);
138                     HPX_ASSERT(from.m_ctx != nullptr);
139 
140 #if defined(HPX_HAVE_SWAP_CONTEXT_EMULATION)
141                     switch_to_fiber(to.m_ctx);
142 #else
143                     SwitchToFiber(to.m_ctx);
144 #endif
145                     BOOL result = ConvertFiberToThread();
146                     HPX_ASSERT(result);
147                     HPX_UNUSED(result);
148                     from.m_ctx = nullptr;
149                 } else {
150                     bool call_from_main = from.m_ctx == nullptr;
151                     if (call_from_main)
152                         from.m_ctx = GetCurrentFiber();
153 #if defined(HPX_HAVE_SWAP_CONTEXT_EMULATION)
154                     switch_to_fiber(to.m_ctx);
155 #else
156                     SwitchToFiber(to.m_ctx);
157 #endif
158                     if (call_from_main)
159                         from.m_ctx = nullptr;
160                 }
161             }
162 
~fibers_context_impl_base()163             ~fibers_context_impl_base() {}
164 
165         protected:
fibers_context_impl_base(fiber_ptr ctx)166             explicit fibers_context_impl_base(fiber_ptr ctx)
167               : m_ctx(ctx)
168             {}
169 
170             fiber_ptr m_ctx;
171         };
172 
173         template <typename T>
trampoline(LPVOID pv)174         HPX_FORCEINLINE VOID CALLBACK trampoline(LPVOID pv)
175         {
176             T* fun = static_cast<T*>(pv);
177             HPX_ASSERT(fun);
178             (*fun)();
179         }
180 
181         // initial stack size (grows as needed)
182         static const std::size_t stack_size = sizeof(void*) >= 8 ? 2048 : 1024;
183 
184         class fibers_context_impl
185           : public fibers_context_impl_base
186         {
187         public:
188             HPX_NON_COPYABLE(fibers_context_impl);
189 
190         public:
191             typedef fibers_context_impl_base context_impl_base;
192 
193             enum { default_stack_size = stack_size };
194 
195             /**
196              * Create a context that on restore invokes Functor on
197              *  a new stack. The stack size can be optionally specified.
198              */
199             template<typename Functor>
fibers_context_impl(Functor & cb,std::ptrdiff_t stack_size)200             explicit fibers_context_impl(Functor& cb, std::ptrdiff_t stack_size)
201               : fibers_context_impl_base(
202                     CreateFiberEx(stack_size == -1 ? default_stack_size : stack_size,
203                         stack_size == -1 ? default_stack_size : stack_size, 0,
204                         static_cast<LPFIBER_START_ROUTINE>(&trampoline<Functor>),
205                         static_cast<LPVOID>(&cb))
206                     ),
207                 stacksize_(stack_size == -1 ? default_stack_size : stack_size)
208             {
209                 if (nullptr == m_ctx)
210                 {
211                     throw boost::system::system_error(
212                         boost::system::error_code(
213                             GetLastError(),
214                             boost::system::system_category()
215                             )
216                         );
217                 }
218             }
219 
~fibers_context_impl()220             ~fibers_context_impl()
221             {
222                 if (m_ctx != nullptr)
223                     DeleteFiber(m_ctx);
224             }
225 
226             // Return the size of the reserved stack address space.
get_stacksize() const227             std::ptrdiff_t get_stacksize() const
228             {
229                 return stacksize_;
230             }
231 
reset_stack()232             void reset_stack()
233             {
234             }
235 
rebind_stack()236             void rebind_stack()
237             {
238                 increment_stack_recycle_count();
239             }
240 
241             typedef std::atomic<std::int64_t> counter_type;
242 
get_stack_recycle_counter()243             static counter_type& get_stack_recycle_counter()
244             {
245                 static counter_type counter(0);
246                 return counter;
247             }
248 
get_stack_recycle_count(bool reset)249             static std::uint64_t get_stack_recycle_count(bool reset)
250             {
251                 return util::get_and_reset_value(get_stack_recycle_counter(), reset);
252             }
253 
increment_stack_recycle_count()254             static std::uint64_t increment_stack_recycle_count()
255             {
256                 return ++get_stack_recycle_counter();
257             }
258 
259             // global functions to be called for each OS-thread after it started
260             // running and before it exits
thread_startup(char const * thread_type)261             static void thread_startup(char const* thread_type) {}
thread_shutdown()262             static void thread_shutdown() {}
263 
264         private:
265             std::ptrdiff_t stacksize_;
266         };
267 
268         typedef fibers_context_impl context_impl;
269     }}
270 }}}
271 
272 #endif /*HPX_RUNTIME_THREADS_COROUTINES_DETAIL_CONTEXT_WINDOWS_HPP*/
273