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