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