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(_M_X64)
33 
34 #define ReadTeb(offset) __readgsqword(offset);
35 #define WriteTeb(offset, v) __writegsqword(offset, v)
36 
37 
38 #else
39 
40 #define ReadTeb(offset) __readfsdword(offset);
41 #define WriteTeb(offset, v) __writefsdword(offset, v)
42 
43 
44 #endif
45 
46 
47 
48 namespace MT
49 {
50 
51 	//
52 	// Windows fiber implementation through GetThreadContext / SetThreadContext
53 	// I don't use standard Windows Fibers since they are wasteful use of Virtual Memory space for the stack. ( 1Mb for each Fiber )
54 	//
55 	class Fiber
56 	{
57 		void* funcData;
58 		TThreadEntryPoint func;
59 
60 		Memory::StackDesc stackDesc;
61 
62 		MW_CONTEXT fiberContext;
63 		bool isInitialized;
64 
65 #if defined(_M_X64)
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 			memset(&fiberContext, 0, sizeof(MW_CONTEXT));
98 		}
99 
100 		~Fiber()
101 		{
102 			if (isInitialized)
103 			{
104 				// if func != null than we have stack memory ownership
105 				if (func != nullptr)
106 				{
107 					Memory::FreeStack(stackDesc);
108 				}
109 
110 				isInitialized = false;
111 			}
112 		}
113 
114 		void CreateFromThread(Thread & thread)
115 		{
116 			MT_USED_IN_ASSERT(thread);
117 
118 			MT_ASSERT(!isInitialized, "Already initialized");
119 			MT_ASSERT(thread.IsCurrentThread(), "ERROR: Can create fiber only from current thread!");
120 
121 			fiberContext.ContextFlags = MW_CONTEXT_FULL;
122 			MW_BOOL res = GetThreadContext( GetCurrentThread(), &fiberContext );
123 			MT_USED_IN_ASSERT(res);
124 			MT_ASSERT(res != 0, "GetThreadContext - failed");
125 
126 			func = nullptr;
127 			funcData = nullptr;
128 
129 			//Get thread stack information from thread environment block.
130 			stackDesc.stackTop = (void*)ReadTeb( MW_STACK_BASE_OFFSET /*FIELD_OFFSET(NT_TIB, StackBase)*/ );
131 			stackDesc.stackBottom = (void*)ReadTeb( MW_STACK_STACK_LIMIT_OFFSET /*FIELD_OFFSET(NT_TIB, StackLimit)*/ );
132 
133 			isInitialized = true;
134 		}
135 
136 		void Create(size_t stackSize, TThreadEntryPoint entryPoint, void* userData)
137 		{
138 			MT_ASSERT(!isInitialized, "Already initialized");
139 
140 			func = entryPoint;
141 			funcData = userData;
142 
143 			fiberContext.ContextFlags = MW_CONTEXT_FULL;
144 			MW_BOOL res = GetThreadContext( GetCurrentThread(), &fiberContext );
145 			MT_USED_IN_ASSERT(res);
146 			MT_ASSERT(res != 0, "GetThreadContext - failed");
147 
148 			stackDesc = Memory::AllocStack(stackSize);
149 
150 			void (*func)() = (void(*)())&FiberFuncInternal;
151 
152 			char* sp  = (char *)stackDesc.stackTop;
153 			char * paramOnStack = nullptr;
154 
155 			// setup function address and stack pointer
156 #if defined(_M_X64)
157 
158 			// http://blogs.msdn.com/b/oldnewthing/archive/2004/01/14/58579.aspx
159 			// Furthermore, space for the register parameters is reserved on the stack, in case the called function wants to spill them
160 
161 			sp -= 16; // pointer size and stack alignment
162 			paramOnStack  = sp;
163 			sp -= 40; // reserve for register params
164 			fiberContext.Rsp = (unsigned long long)sp;
165 			MT_ASSERT(((unsigned long long)paramOnStack & 0xF) == 0, "Params on X64 stack must be alligned to 16 bytes");
166 			fiberContext.Rip = (unsigned long long) func;
167 
168 #else
169 
170 			sp -= sizeof(void*); // reserve stack space for one pointer argument
171 			paramOnStack  = sp;
172 			sp -= sizeof(void*);
173 			fiberContext.Esp = (unsigned long long)sp;
174 			fiberContext.Eip = (unsigned long long) func;
175 
176 #endif
177 
178 			//copy param to stack here
179 			*(void**)paramOnStack = (void *)this;
180 
181 			fiberContext.ContextFlags = MW_CONTEXT_FULL;
182 
183 			isInitialized = true;
184 		}
185 
186 
187 		static void SwitchTo(Fiber & from, Fiber & to)
188 		{
189 			HardwareFullMemoryBarrier();
190 
191 			MT_ASSERT(from.isInitialized, "Invalid from fiber");
192 			MT_ASSERT(to.isInitialized, "Invalid to fiber");
193 
194 			MW_HANDLE thread = GetCurrentThread();
195 
196 			from.fiberContext.ContextFlags = MW_CONTEXT_FULL;
197 			MW_BOOL res = GetThreadContext(thread, &from.fiberContext );
198 			MT_ASSERT(res != 0, "GetThreadContext - failed");
199 
200 			// Modify current stack information in TEB
201 			//
202 			// __chkstk function use TEB info and probe sampling to commit new stack pages
203 			// https://support.microsoft.com/en-us/kb/100775
204 			//
205 			WriteTeb(MW_STACK_BASE_OFFSET /*FIELD_OFFSET(NT_TIB, StackBase)*/ , (uint64)to.stackDesc.stackTop);
206 			WriteTeb(MW_STACK_STACK_LIMIT_OFFSET/*FIELD_OFFSET(NT_TIB, StackLimit)*/, (uint64)to.stackDesc.stackBottom);
207 
208 			res = SetThreadContext(thread, &to.fiberContext );
209 			MT_ASSERT(res != 0, "SetThreadContext - failed");
210 
211 			//Restore stack information
212 			WriteTeb(MW_STACK_BASE_OFFSET /*FIELD_OFFSET(NT_TIB, StackBase)*/, (uint64)from.stackDesc.stackTop);
213 			WriteTeb(MW_STACK_STACK_LIMIT_OFFSET /*FIELD_OFFSET(NT_TIB, StackLimit)*/, (uint64)from.stackDesc.stackBottom);
214 		}
215 
216 
217 	};
218 
219 
220 }
221 
222 #undef ReadTeb
223 #undef WriteTeb
224 
225 
226 #endif