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