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