1 // The MIT License (MIT)
2 //
3 // 	Copyright (c) 2015 Sergey Makeev, Vadim Slyusarev
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 THE
18 // 	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.
22 
23 #pragma once
24 
25 #ifndef __MT_FIBER_OPTIMIZED__
26 #define __MT_FIBER_OPTIMIZED__
27 
28 
29 #include <MTAllocator.h>
30 #include "MTAtomic.h"
31 
32 #if defined(_X86_)
33 
34 #define ReadTeb(offset) __readfsdword(offset);
35 #define WriteTeb(offset, v) __writefsdword(offset, v)
36 
37 #else
38 
39 #define ReadTeb(offset) __readgsqword(offset);
40 #define WriteTeb(offset, v) __writegsqword(offset, v)
41 
42 #endif
43 
44 
45 
46 namespace MT
47 {
48 
49 	//
50 	// Windows fiber implementation through GetThreadContext / SetThreadContext
51 	// I don't use standard Windows Fibers since they are wasteful use of Virtual Memory space for the stack. ( 1Mb for each Fiber )
52 	//
53 	class Fiber
54 	{
55 		void * funcData;
56 		TThreadEntryPoint func;
57 
58 		Memory::StackDesc stackDesc;
59 
60 		CONTEXT fiberContext;
61 		bool isInitialized;
62 
63 #if defined(_X86_)
64 	// https://en.wikipedia.org/wiki/X86_calling_conventions#stdcall
65 	// The stdcall calling convention is a variation on the Pascal calling convention in which the callee is responsible for cleaning up the stack,
66 	// but the parameters are pushed onto the stack in right-to-left order, as in the _cdecl calling convention.
67 	static void __stdcall FiberFuncInternal(void *pFiber)
68 #else
69 	// https://en.wikipedia.org/wiki/X86_calling_conventions#Microsoft_x64_calling_convention
70 	// The Microsoft x64 calling convention is followed on Microsoft Windows.
71   // It uses registers RCX, RDX, R8, R9 for the first four integer or pointer arguments (in that order), and XMM0, XMM1, XMM2, XMM3 are used for floating point arguments.
72 
73 	// Additional arguments are pushed onto the stack (right to left).
74 	static void __stdcall FiberFuncInternal(long /*ecx*/, long /*edx*/, long /*r8*/, long /*r9*/, void *pFiber)
75 #endif
76 		{
77 			MT_ASSERT(pFiber != nullptr, "Invalid fiber");
78 			Fiber* self = (Fiber*)pFiber;
79 
80 			MT_ASSERT(self->isInitialized == true, "Using non initialized fiber");
81 
82 			MT_ASSERT(self->func != nullptr, "Invalid fiber func");
83 			self->func(self->funcData);
84 		}
85 
86 	private:
87 
88 		Fiber(const Fiber &) {}
89 		void operator=(const Fiber &) {}
90 
91 	public:
92 
93 		Fiber()
94 			: funcData(nullptr)
95 			, func(nullptr)
96 			, isInitialized(false)
97 		{
98 			memset(&fiberContext, 0, sizeof(CONTEXT));
99 		}
100 
101 		~Fiber()
102 		{
103 			if (isInitialized)
104 			{
105 				// if func != null than we have stack memory ownership
106 				if (func != nullptr)
107 				{
108 					Memory::FreeStack(stackDesc);
109 				}
110 
111 				isInitialized = false;
112 			}
113 		}
114 
115 		void CreateFromThread(Thread & thread)
116 		{
117 			MT_ASSERT(!isInitialized, "Already initialized");
118 			MT_ASSERT(thread.IsCurrentThread(), "ERROR: Can create fiber only from current thread!");
119 
120 			fiberContext.ContextFlags = CONTEXT_FULL;
121 			BOOL res = GetThreadContext( GetCurrentThread(), &fiberContext );
122 			MT_ASSERT(res != 0, "GetThreadContext - failed");
123 
124 			func = nullptr;
125 			funcData = nullptr;
126 
127 			//Get thread stack information from thread environment block.
128 			stackDesc.stackTop = (void*)ReadTeb(FIELD_OFFSET(NT_TIB, StackBase));
129 			stackDesc.stackBottom = (void*)ReadTeb(FIELD_OFFSET(NT_TIB, StackLimit));
130 
131 			isInitialized = true;
132 		}
133 
134 		void Create(size_t stackSize, TThreadEntryPoint entryPoint, void *userData)
135 		{
136 			MT_ASSERT(!isInitialized, "Already initialized");
137 
138 			func = entryPoint;
139 			funcData = userData;
140 
141 			fiberContext.ContextFlags = CONTEXT_FULL;
142 			BOOL res = GetThreadContext( GetCurrentThread(), &fiberContext );
143 			MT_ASSERT(res != 0, "GetThreadContext - failed");
144 
145 			stackDesc = Memory::AllocStack(stackSize);
146 
147 			void (*func)() = (void(*)())&FiberFuncInternal;
148 
149 			char* sp  = (char *)stackDesc.stackTop;
150 			char * paramOnStack = nullptr;
151 
152 			// setup function address and stack pointer
153 #if defined(_X86_)
154 
155 			sp -= sizeof(void*); // reserve stack space for one pointer argument
156 			paramOnStack  = sp;
157 			sp -= sizeof(void*);
158 			fiberContext.Esp = (unsigned long long)sp;
159 			fiberContext.Eip = (unsigned long long) func;
160 
161 #else
162 
163 			// http://blogs.msdn.com/b/oldnewthing/archive/2004/01/14/58579.aspx
164 			// Furthermore, space for the register parameters is reserved on the stack, in case the called function wants to spill them
165 
166 			sp -= 16; // pointer size and stack alignment
167 			paramOnStack  = sp;
168 			sp -= 40; // reserve for register params
169 			fiberContext.Rsp = (unsigned long long)sp;
170 			MT_ASSERT(((unsigned long long)paramOnStack & 0xF) == 0, "Params on X64 stack must be alligned to 16 bytes");
171 			fiberContext.Rip = (unsigned long long) func;
172 #endif
173 
174 			//copy param to stack here
175 			*(void**)paramOnStack = (void *)this;
176 
177 			fiberContext.ContextFlags = CONTEXT_FULL;
178 
179 			isInitialized = true;
180 		}
181 
182 
183 		static void SwitchTo(Fiber & from, Fiber & to)
184 		{
185 			HardwareFullMemoryBarrier();
186 
187 			MT_ASSERT(from.isInitialized, "Invalid from fiber");
188 			MT_ASSERT(to.isInitialized, "Invalid to fiber");
189 
190 			HANDLE thread = GetCurrentThread();
191 
192 			from.fiberContext.ContextFlags = CONTEXT_FULL;
193 			BOOL res = GetThreadContext(thread, &from.fiberContext );
194 			MT_ASSERT(res != 0, "GetThreadContext - failed");
195 
196 			// Modify current stack information in TEB
197 			//
198 			// __chkstk function use TEB info and probe sampling to commit new stack pages
199 			// https://support.microsoft.com/en-us/kb/100775
200 			//
201 			WriteTeb(FIELD_OFFSET(NT_TIB, StackBase), (DWORD64)to.stackDesc.stackTop);
202 			WriteTeb(FIELD_OFFSET(NT_TIB, StackLimit), (DWORD64)to.stackDesc.stackBottom);
203 
204 			res = SetThreadContext(thread, &to.fiberContext );
205 			MT_ASSERT(res != 0, "SetThreadContext - failed");
206 
207 			//Restore stack information
208 			WriteTeb(FIELD_OFFSET(NT_TIB, StackBase), (DWORD64)from.stackDesc.stackTop);
209 			WriteTeb(FIELD_OFFSET(NT_TIB, StackLimit), (DWORD64)from.stackDesc.stackBottom);
210 		}
211 
212 
213 	};
214 
215 
216 }
217 
218 #undef ReadTeb
219 #undef WriteTeb
220 
221 
222 #endif