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