/// <summary> /// CLR controlled fiber-enabled execution. To be called from unmanaged environment. /// </summary> /// <param name="fiberGcHandleIntPtr">The handle to the special fibers that are part of schedulers.</param> public void Proc(IntPtr fiberGcHandleIntPtr) { var fiberGcHandle = GCHandle.FromIntPtr(fiberGcHandleIntPtr); if (IsRegularManagedThread()) { throw new NotSupportedException( "Thread that at least once called regular C# thread cannot be used as Fiber-Enabled thread."); } // Initializing System.Threading.Thread in a special way, so CLR VM will handle different fiber-oriented behavior. InitAsFiberEnabledThread(); // Moving to hard-hack of the stack. if (!Cpp.setjmp(ref _beforeFiberStack)) { // Stop using C++ // Working on assembly level // switching C++ OS stack to ClrFiber managed stack: var fiber = (ClrFiber)fiberGcHandle.Target; // Very special stack condition var returnAddress = NativeCodeConstants.InstructionOffset("exitThread"); Cpu.StackBasePointer = Cpu.StackBasePointer + 0x10; // Hacking stack base pointer, now it's tuned to the address of the "returnAddress" variable. // After this hack the "ret" assembly command will jump to "exitThread". ; // What happens if we call Native method inside the "Managed Stack" // Making thread exit fiber as current. ThreadExitFiber._suspendState.StackPointer = Cpu.StackPointer; ThreadExitFiber._suspendState.StackBasePointer = Cpu.StackBasePointer; // This is native stack pointer ThreadExitFiber._suspendState.ContinuationInstructionAddress = NativeCodeConstants.InstructionOffset("exitThread"); ClrFiber._current = ThreadExitFiber; // Do the same code as this IL instruction produces. // Whenever ClrFiber executes "ret" assembly instruction will return to "exitThread". ThreadExitFiber.TransferFlowOpcode( fiber); // After this call, current thread is fully controlled by the preemptive multitasking. // When preemptive multitasking switch control to the "ThreadExitFiber", thread flow will jump to this location: exitThread :; } // Cpp now works again. ReleaseFiberEnabledThread(); }
[CpuRegistryBarrier] // JIT should not rely on Cpu registers after this IL instruction. public void TransferFlowOpcode(ClrFiber nextFiber) { // Generated assembly instructions pseudo code: // Transferring flow to our self. // What should be guarantied before this instruction: if (!ReferenceEquals(_current, nextFiber) || !nextFiber.IsSuspended) { throw new UnpredictableBehavior(); } // Saving resume point in the current fiber. _suspendState.StackPointer = Cpu.StackPointer; // On x64 machine it's an RSP register. _suspendState.StackBasePointer = Cpu.StackBasePointer; // On x64 machine it's an RBP register. _suspendState.ContinuationInstructionAddress = NativeCodeConstants.InstructionOffset("resume"); // JIT generated code constant // Currently we are in the critical state, setting-up stack state for the new fiber. _current = nextFiber; nextFiber._suspendState.RestoreBaseCpuRegisters(); Cpu.StackPointer = nextFiber._suspendState.StackPointer; Cpu.StackBasePointer = nextFiber._suspendState.StackBasePointer; Cpu.Jump(nextFiber._suspendState.ContinuationInstructionAddress); // Critical state ended. // After this call we appear on the "resume:" label of the !!!nextFiber!!!, not this. // ------------------------------------------------------------------------------- Preemptive switch // RESUME from other ClrFiber // This instruction already performed it's work // The "Resume" label it's a next instruction location resume :; // Here there is no any assumption about a state of CPU registers }