16bbc0262SSergey Makeev // The MIT License (MIT)
26bbc0262SSergey Makeev //
36bbc0262SSergey Makeev // 	Copyright (c) 2015 Sergey Makeev, Vadim Slyusarev
46bbc0262SSergey Makeev //
56bbc0262SSergey Makeev // 	Permission is hereby granted, free of charge, to any person obtaining a copy
66bbc0262SSergey Makeev // 	of this software and associated documentation files (the "Software"), to deal
76bbc0262SSergey Makeev // 	in the Software without restriction, including without limitation the rights
86bbc0262SSergey Makeev // 	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
96bbc0262SSergey Makeev // 	copies of the Software, and to permit persons to whom the Software is
106bbc0262SSergey Makeev // 	furnished to do so, subject to the following conditions:
116bbc0262SSergey Makeev //
126bbc0262SSergey Makeev //  The above copyright notice and this permission notice shall be included in
136bbc0262SSergey Makeev // 	all copies or substantial portions of the Software.
146bbc0262SSergey Makeev //
156bbc0262SSergey Makeev // 	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
166bbc0262SSergey Makeev // 	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
176bbc0262SSergey Makeev // 	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
186bbc0262SSergey Makeev // 	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
196bbc0262SSergey Makeev // 	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
206bbc0262SSergey Makeev // 	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
216bbc0262SSergey Makeev // 	THE SOFTWARE.
226bbc0262SSergey Makeev 
236bbc0262SSergey Makeev #pragma once
246bbc0262SSergey Makeev 
256bbc0262SSergey Makeev #ifndef __MT_FIBER_OPTIMIZED__
266bbc0262SSergey Makeev #define __MT_FIBER_OPTIMIZED__
276bbc0262SSergey Makeev 
281e78cb24Ss.makeev_local #include <MTConfig.h>
2902d170cfSs.makeev_local #include <Platform/Common/MTAtomic.h>
30721f8c0bSs.makeev_local #include <string>
316bbc0262SSergey Makeev 
326bbc0262SSergey Makeev 
336bbc0262SSergey Makeev namespace MT
346bbc0262SSergey Makeev {
356bbc0262SSergey Makeev 
366bbc0262SSergey Makeev 	//
376bbc0262SSergey Makeev 	// Windows fiber implementation through GetThreadContext / SetThreadContext
386bbc0262SSergey Makeev 	// I don't use standard Windows Fibers since they are wasteful use of Virtual Memory space for the stack. ( 1Mb for each Fiber )
396bbc0262SSergey Makeev 	//
406bbc0262SSergey Makeev 	class Fiber
416bbc0262SSergey Makeev 	{
4253ef36e3Ss.makeev_local 		MW_CONTEXT fiberContext;
4353ef36e3Ss.makeev_local 
446bbc0262SSergey Makeev 		void* funcData;
456bbc0262SSergey Makeev 		TThreadEntryPoint func;
466bbc0262SSergey Makeev 
476bbc0262SSergey Makeev 		Memory::StackDesc stackDesc;
486bbc0262SSergey Makeev 
496bbc0262SSergey Makeev 		bool isInitialized;
506bbc0262SSergey Makeev 
511e78cb24Ss.makeev_local #if MT_PTR64
526bbc0262SSergey Makeev 		// https://en.wikipedia.org/wiki/X86_calling_conventions#Microsoft_x64_calling_convention
536bbc0262SSergey Makeev 		// The Microsoft x64 calling convention is followed on Microsoft Windows.
546bbc0262SSergey Makeev 		// 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.
556bbc0262SSergey Makeev 
566bbc0262SSergey Makeev 		// Additional arguments are pushed onto the stack (right to left).
FiberFuncInternal(long,long,long,long,void * pFiber)576bbc0262SSergey Makeev 		static void __stdcall FiberFuncInternal(long /*ecx*/, long /*edx*/, long /*r8*/, long /*r9*/, void *pFiber)
58d0d10efeSs.makeev #else
59d0d10efeSs.makeev 		// https://en.wikipedia.org/wiki/X86_calling_conventions#stdcall
60d0d10efeSs.makeev 		// The stdcall calling convention is a variation on the Pascal calling convention in which the callee is responsible for cleaning up the stack,
61d0d10efeSs.makeev 		// but the parameters are pushed onto the stack in right-to-left order, as in the _cdecl calling convention.
62d0d10efeSs.makeev 		static void __stdcall FiberFuncInternal(void *pFiber)
636bbc0262SSergey Makeev #endif
646bbc0262SSergey Makeev 		{
656bbc0262SSergey Makeev 			MT_ASSERT(pFiber != nullptr, "Invalid fiber");
666bbc0262SSergey Makeev 			Fiber* self = (Fiber*)pFiber;
676bbc0262SSergey Makeev 
686bbc0262SSergey Makeev 			MT_ASSERT(self->isInitialized == true, "Using non initialized fiber");
696bbc0262SSergey Makeev 
706bbc0262SSergey Makeev 			MT_ASSERT(self->func != nullptr, "Invalid fiber func");
716bbc0262SSergey Makeev 			self->func(self->funcData);
726bbc0262SSergey Makeev 		}
736bbc0262SSergey Makeev 
CleanUp()74*3d930776Ss.makeev_local 		void CleanUp()
75*3d930776Ss.makeev_local 		{
76*3d930776Ss.makeev_local 			if (isInitialized)
77*3d930776Ss.makeev_local 			{
78*3d930776Ss.makeev_local 				// if func != null than we have stack memory ownership
79*3d930776Ss.makeev_local 				if (func != nullptr)
80*3d930776Ss.makeev_local 				{
81*3d930776Ss.makeev_local 					Memory::FreeStack(stackDesc);
82*3d930776Ss.makeev_local 				}
83*3d930776Ss.makeev_local 
84*3d930776Ss.makeev_local 				isInitialized = false;
85*3d930776Ss.makeev_local 			}
86*3d930776Ss.makeev_local 		}
87*3d930776Ss.makeev_local 
88*3d930776Ss.makeev_local 
896bbc0262SSergey Makeev 	public:
906bbc0262SSergey Makeev 
912e846c40SSergey Makeev 		MT_NOCOPYABLE(Fiber);
922e846c40SSergey Makeev 
Fiber()936bbc0262SSergey Makeev 		Fiber()
946bbc0262SSergey Makeev 			: funcData(nullptr)
956bbc0262SSergey Makeev 			, func(nullptr)
966bbc0262SSergey Makeev 			, isInitialized(false)
976bbc0262SSergey Makeev 		{
981e78cb24Ss.makeev_local #if MT_PTR64
9953ef36e3Ss.makeev_local 			MT_ASSERT(IsPointerAligned( this, 16 ), "Fiber must be aligned by 16 bytes");
10053ef36e3Ss.makeev_local 			MT_ASSERT(IsPointerAligned( &fiberContext, 16 ), "MW_CONTEXT must be aligned by 16 bytes");
10153ef36e3Ss.makeev_local #endif
102d0d10efeSs.makeev 			memset(&fiberContext, 0, sizeof(MW_CONTEXT));
1036bbc0262SSergey Makeev 		}
1046bbc0262SSergey Makeev 
~Fiber()1056bbc0262SSergey Makeev 		~Fiber()
1066bbc0262SSergey Makeev 		{
107*3d930776Ss.makeev_local 			CleanUp();
1086bbc0262SSergey Makeev 		}
1096bbc0262SSergey Makeev 
CreateFromCurrentThreadAndRun(TThreadEntryPoint entryPoint,void * userData)110ae5bbefbSs.makeev_local 		void CreateFromCurrentThreadAndRun(TThreadEntryPoint entryPoint, void *userData)
1116bbc0262SSergey Makeev 		{
1126bbc0262SSergey Makeev 			MT_ASSERT(!isInitialized, "Already initialized");
1136bbc0262SSergey Makeev 
114d0d10efeSs.makeev 			fiberContext.ContextFlags = MW_CONTEXT_FULL;
115d0d10efeSs.makeev 			MW_BOOL res = GetThreadContext( GetCurrentThread(), &fiberContext );
1162e846c40SSergey Makeev 			MT_USED_IN_ASSERT(res);
1176bbc0262SSergey Makeev 			MT_ASSERT(res != 0, "GetThreadContext - failed");
1186bbc0262SSergey Makeev 
1196bbc0262SSergey Makeev 			func = nullptr;
1206bbc0262SSergey Makeev 			funcData = nullptr;
1216bbc0262SSergey Makeev 
1226bbc0262SSergey Makeev 			//Get thread stack information from thread environment block.
123d0d10efeSs.makeev 			stackDesc.stackTop = (void*)ReadTeb( MW_STACK_BASE_OFFSET /*FIELD_OFFSET(NT_TIB, StackBase)*/ );
124d0d10efeSs.makeev 			stackDesc.stackBottom = (void*)ReadTeb( MW_STACK_STACK_LIMIT_OFFSET /*FIELD_OFFSET(NT_TIB, StackLimit)*/ );
1256bbc0262SSergey Makeev 
1266bbc0262SSergey Makeev 			isInitialized = true;
12702d170cfSs.makeev_local 
12802d170cfSs.makeev_local 			entryPoint(userData);
129*3d930776Ss.makeev_local 
130*3d930776Ss.makeev_local 			CleanUp();
1316bbc0262SSergey Makeev 		}
1326bbc0262SSergey Makeev 
Create(size_t stackSize,TThreadEntryPoint entryPoint,void * userData)1336bbc0262SSergey Makeev 		void Create(size_t stackSize, TThreadEntryPoint entryPoint, void* userData)
1346bbc0262SSergey Makeev 		{
1356bbc0262SSergey Makeev 			MT_ASSERT(!isInitialized, "Already initialized");
1366bbc0262SSergey Makeev 
1376bbc0262SSergey Makeev 			func = entryPoint;
1386bbc0262SSergey Makeev 			funcData = userData;
1396bbc0262SSergey Makeev 
140d0d10efeSs.makeev 			fiberContext.ContextFlags = MW_CONTEXT_FULL;
141d0d10efeSs.makeev 			MW_BOOL res = GetThreadContext( GetCurrentThread(), &fiberContext );
1422e846c40SSergey Makeev 			MT_USED_IN_ASSERT(res);
1436bbc0262SSergey Makeev 			MT_ASSERT(res != 0, "GetThreadContext - failed");
1446bbc0262SSergey Makeev 
1456bbc0262SSergey Makeev 			stackDesc = Memory::AllocStack(stackSize);
1466bbc0262SSergey Makeev 
147721f8c0bSs.makeev_local 			void (*pFunc)() = (void(*)())&FiberFuncInternal;
1486bbc0262SSergey Makeev 
1496bbc0262SSergey Makeev 			char* sp  = (char *)stackDesc.stackTop;
1506bbc0262SSergey Makeev 			char * paramOnStack = nullptr;
1516bbc0262SSergey Makeev 
1526bbc0262SSergey Makeev 			// setup function address and stack pointer
1531e78cb24Ss.makeev_local #if MT_PTR64
1546bbc0262SSergey Makeev 
1556bbc0262SSergey Makeev 			// http://blogs.msdn.com/b/oldnewthing/archive/2004/01/14/58579.aspx
1566bbc0262SSergey Makeev 			// Furthermore, space for the register parameters is reserved on the stack, in case the called function wants to spill them
1576bbc0262SSergey Makeev 
1586bbc0262SSergey Makeev 			sp -= 16; // pointer size and stack alignment
1596bbc0262SSergey Makeev 			paramOnStack  = sp;
1606bbc0262SSergey Makeev 			sp -= 40; // reserve for register params
1616bbc0262SSergey Makeev 			fiberContext.Rsp = (unsigned long long)sp;
1626bbc0262SSergey Makeev 			MT_ASSERT(((unsigned long long)paramOnStack & 0xF) == 0, "Params on X64 stack must be alligned to 16 bytes");
163721f8c0bSs.makeev_local 			fiberContext.Rip = (unsigned long long) pFunc;
164d0d10efeSs.makeev 
165d0d10efeSs.makeev #else
166d0d10efeSs.makeev 
167d0d10efeSs.makeev 			sp -= sizeof(void*); // reserve stack space for one pointer argument
168d0d10efeSs.makeev 			paramOnStack  = sp;
169d0d10efeSs.makeev 			sp -= sizeof(void*);
170d0d10efeSs.makeev 			fiberContext.Esp = (unsigned long long)sp;
171721f8c0bSs.makeev_local 			fiberContext.Eip = (unsigned long long) pFunc;
172d0d10efeSs.makeev 
1736bbc0262SSergey Makeev #endif
1746bbc0262SSergey Makeev 
1756bbc0262SSergey Makeev 			//copy param to stack here
1766bbc0262SSergey Makeev 			*(void**)paramOnStack = (void *)this;
1776bbc0262SSergey Makeev 
178d0d10efeSs.makeev 			fiberContext.ContextFlags = MW_CONTEXT_FULL;
1796bbc0262SSergey Makeev 
1806bbc0262SSergey Makeev 			isInitialized = true;
1816bbc0262SSergey Makeev 		}
1826bbc0262SSergey Makeev 
183d7cf17b1Ss.makeev_local #ifdef MT_INSTRUMENTED_BUILD
SetName(const char * fiberName)184d7cf17b1Ss.makeev_local 		void SetName(const char* fiberName)
185d7cf17b1Ss.makeev_local 		{
186d7cf17b1Ss.makeev_local 			MT_UNUSED(fiberName);
187d7cf17b1Ss.makeev_local 		}
188d7cf17b1Ss.makeev_local #endif
1896bbc0262SSergey Makeev 
SwitchTo(Fiber & from,Fiber & to)1906bbc0262SSergey Makeev 		static void SwitchTo(Fiber & from, Fiber & to)
1916bbc0262SSergey Makeev 		{
1926bbc0262SSergey Makeev 			HardwareFullMemoryBarrier();
1936bbc0262SSergey Makeev 
1946bbc0262SSergey Makeev 			MT_ASSERT(from.isInitialized, "Invalid from fiber");
1956bbc0262SSergey Makeev 			MT_ASSERT(to.isInitialized, "Invalid to fiber");
1966bbc0262SSergey Makeev 
197d0d10efeSs.makeev 			MW_HANDLE thread = GetCurrentThread();
1986bbc0262SSergey Makeev 
199d0d10efeSs.makeev 			from.fiberContext.ContextFlags = MW_CONTEXT_FULL;
200d0d10efeSs.makeev 			MW_BOOL res = GetThreadContext(thread, &from.fiberContext );
2016bbc0262SSergey Makeev 			MT_ASSERT(res != 0, "GetThreadContext - failed");
2026bbc0262SSergey Makeev 
2036bbc0262SSergey Makeev 			// Modify current stack information in TEB
2046bbc0262SSergey Makeev 			//
2056bbc0262SSergey Makeev 			// __chkstk function use TEB info and probe sampling to commit new stack pages
2066bbc0262SSergey Makeev 			// https://support.microsoft.com/en-us/kb/100775
2076bbc0262SSergey Makeev 			//
208d0d10efeSs.makeev 			WriteTeb(MW_STACK_BASE_OFFSET /*FIELD_OFFSET(NT_TIB, StackBase)*/ , (uint64)to.stackDesc.stackTop);
209d0d10efeSs.makeev 			WriteTeb(MW_STACK_STACK_LIMIT_OFFSET/*FIELD_OFFSET(NT_TIB, StackLimit)*/, (uint64)to.stackDesc.stackBottom);
2106bbc0262SSergey Makeev 
2116bbc0262SSergey Makeev 			res = SetThreadContext(thread, &to.fiberContext );
2126bbc0262SSergey Makeev 			MT_ASSERT(res != 0, "SetThreadContext - failed");
2136bbc0262SSergey Makeev 
2146bbc0262SSergey Makeev 			//Restore stack information
215d0d10efeSs.makeev 			WriteTeb(MW_STACK_BASE_OFFSET /*FIELD_OFFSET(NT_TIB, StackBase)*/, (uint64)from.stackDesc.stackTop);
216d0d10efeSs.makeev 			WriteTeb(MW_STACK_STACK_LIMIT_OFFSET /*FIELD_OFFSET(NT_TIB, StackLimit)*/, (uint64)from.stackDesc.stackBottom);
2176bbc0262SSergey Makeev 		}
2186bbc0262SSergey Makeev 
2196bbc0262SSergey Makeev 
2206bbc0262SSergey Makeev 	};
2216bbc0262SSergey Makeev 
2226bbc0262SSergey Makeev 
2236bbc0262SSergey Makeev }
2246bbc0262SSergey Makeev 
2256bbc0262SSergey Makeev #undef ReadTeb
2266bbc0262SSergey Makeev #undef WriteTeb
2276bbc0262SSergey Makeev 
2286bbc0262SSergey Makeev 
2296bbc0262SSergey Makeev #endif
230