Commit 609679fb authored by Kenton Varda's avatar Kenton Varda

Implement fibers on Windows.

It was so easy that it compiled on the first try and almost totally worked... only thing I missed was calling switchToMain() explicitly at the end of the fiber main func.
parent e8a14a7a
...@@ -896,8 +896,12 @@ private: ...@@ -896,8 +896,12 @@ private:
size_t stackSize; size_t stackSize;
#if _WIN32
void* osFiber;
#else
struct Impl; struct Impl;
Impl& impl; Impl& impl;
#endif
_::PromiseNode* currentInner = nullptr; _::PromiseNode* currentInner = nullptr;
OnReadyEvent onReadyEvent; OnReadyEvent onReadyEvent;
......
...@@ -40,7 +40,7 @@ ...@@ -40,7 +40,7 @@
#include "mutex.h" #include "mutex.h"
#if _WIN32 #if _WIN32
#include <windows.h> // just for Sleep(0) #include <windows.h> // for Sleep(0) and fibers
#include "windows-sanity.h" #include "windows-sanity.h"
#else #else
#include <sched.h> // just for sched_yield() #include <sched.h> // just for sched_yield()
...@@ -710,6 +710,7 @@ const Executor& getCurrentThreadExecutor() { ...@@ -710,6 +710,7 @@ const Executor& getCurrentThreadExecutor() {
namespace _ { // private namespace _ { // private
#if !_WIN32
struct FiberBase::Impl { struct FiberBase::Impl {
// This struct serves two purposes: // This struct serves two purposes:
// - It contains OS-specific state that we don't want to declare in the header. // - It contains OS-specific state that we don't want to declare in the header.
...@@ -778,8 +779,20 @@ struct FiberBase::Impl { ...@@ -778,8 +779,20 @@ struct FiberBase::Impl {
return result; return result;
} }
}; };
#endif
struct FiberBase::StartRoutine { struct FiberBase::StartRoutine {
#if _WIN32
static void WINAPI run(LPVOID ptr) {
// This is the static C-style function we pass to CreateFiber().
auto& fiber = *reinterpret_cast<FiberBase*>(ptr);
fiber.run();
// On Windows, if the fiber main function returns, the thread exits. We need to explicitly switch
// back to the main stack.
fiber.switchToMain();
}
#else
static void run(int arg1, int arg2) { static void run(int arg1, int arg2) {
// This is the static C-style function we pass to makeContext(). // This is the static C-style function we pass to makeContext().
...@@ -789,14 +802,28 @@ struct FiberBase::StartRoutine { ...@@ -789,14 +802,28 @@ struct FiberBase::StartRoutine {
ptr |= static_cast<uintptr_t>(static_cast<uint>(arg2)) << (sizeof(ptr) * 4); ptr |= static_cast<uintptr_t>(static_cast<uint>(arg2)) << (sizeof(ptr) * 4);
reinterpret_cast<FiberBase*>(ptr)->run(); reinterpret_cast<FiberBase*>(ptr)->run();
} }
#endif
}; };
FiberBase::FiberBase(size_t stackSizeParam, _::ExceptionOrValue& result) FiberBase::FiberBase(size_t stackSizeParam, _::ExceptionOrValue& result)
: state(WAITING), : state(WAITING),
// Force stackSize to a reasonable minimum. // Force stackSize to a reasonable minimum.
stackSize(kj::max(stackSizeParam, 65536)), stackSize(kj::max(stackSizeParam, 65536)),
#if !_WIN32
impl(Impl::alloc(stackSize)), impl(Impl::alloc(stackSize)),
#endif
result(result) { result(result) {
#if _WIN32
auto& eventLoop = currentEventLoop();
if (eventLoop.mainFiber == nullptr) {
// First time we've created a fiber. We need to convert the main stack into a fiber as well
// before we can start switching.
eventLoop.mainFiber = ConvertThreadToFiber(nullptr);
}
KJ_WIN32(osFiber = CreateFiber(stackSize, &StartRoutine::run, this));
#else
// Note: Nothing below here can throw. If that changes then we need to call Impl::free(impl) // Note: Nothing below here can throw. If that changes then we need to call Impl::free(impl)
// on exceptions... // on exceptions...
...@@ -807,12 +834,15 @@ FiberBase::FiberBase(size_t stackSizeParam, _::ExceptionOrValue& result) ...@@ -807,12 +834,15 @@ FiberBase::FiberBase(size_t stackSizeParam, _::ExceptionOrValue& result)
int arg2 = ptr >> (sizeof(ptr) * 4); int arg2 = ptr >> (sizeof(ptr) * 4);
makecontext(&impl.fiberContext, reinterpret_cast<void(*)()>(&StartRoutine::run), 2, arg1, arg2); makecontext(&impl.fiberContext, reinterpret_cast<void(*)()>(&StartRoutine::run), 2, arg1, arg2);
#endif
} }
FiberBase::~FiberBase() noexcept(false) { FiberBase::~FiberBase() noexcept(false) {
KJ_DEFER({ #if _WIN32
Impl::free(impl, stackSize); KJ_DEFER(DeleteFiber(osFiber));
}); #else
KJ_DEFER(Impl::free(impl, stackSize));
#endif
switch (state) { switch (state) {
case WAITING: case WAITING:
...@@ -849,12 +879,20 @@ Maybe<Own<Event>> FiberBase::fire() { ...@@ -849,12 +879,20 @@ Maybe<Own<Event>> FiberBase::fire() {
void FiberBase::switchToFiber() { void FiberBase::switchToFiber() {
// Switch from the main stack to the fiber. Returns once the fiber either calls switchToMain() // Switch from the main stack to the fiber. Returns once the fiber either calls switchToMain()
// or returns from its main function. // or returns from its main function.
#if _WIN32
SwitchToFiber(osFiber);
#else
KJ_SYSCALL(swapcontext(&impl.originalContext, &impl.fiberContext)); KJ_SYSCALL(swapcontext(&impl.originalContext, &impl.fiberContext));
#endif
} }
void FiberBase::switchToMain() { void FiberBase::switchToMain() {
// Switch from the fiber to the main stack. Returns the next time the main stack calls // Switch from the fiber to the main stack. Returns the next time the main stack calls
// switchToFiber(). // switchToFiber().
#if _WIN32
SwitchToFiber(currentEventLoop().mainFiber);
#else
KJ_SYSCALL(swapcontext(&impl.fiberContext, &impl.originalContext)); KJ_SYSCALL(swapcontext(&impl.fiberContext, &impl.originalContext));
#endif
} }
void FiberBase::run() { void FiberBase::run() {
...@@ -898,6 +936,15 @@ EventLoop::EventLoop(EventPort& port) ...@@ -898,6 +936,15 @@ EventLoop::EventLoop(EventPort& port)
daemons(kj::heap<TaskSet>(_::LoggingErrorHandler::instance)) {} daemons(kj::heap<TaskSet>(_::LoggingErrorHandler::instance)) {}
EventLoop::~EventLoop() noexcept(false) { EventLoop::~EventLoop() noexcept(false) {
#if _WIN32
KJ_DEFER({
if (mainFiber != nullptr) {
// We converted the thread to a fiber, need to convert it back.
KJ_WIN32(ConvertFiberToThread());
}
});
#endif
// Destroy all "daemon" tasks, noting that their destructors might try to access the EventLoop // Destroy all "daemon" tasks, noting that their destructors might try to access the EventLoop
// some more. // some more.
daemons = nullptr; daemons = nullptr;
......
...@@ -907,6 +907,10 @@ private: ...@@ -907,6 +907,10 @@ private:
Own<TaskSet> daemons; Own<TaskSet> daemons;
#if _WIN32
void* mainFiber = nullptr;
#endif
bool turn(); bool turn();
void setRunnable(bool runnable); void setRunnable(bool runnable);
void enterScope(); void enterScope();
...@@ -923,6 +927,7 @@ private: ...@@ -923,6 +927,7 @@ private:
friend class WaitScope; friend class WaitScope;
friend class Executor; friend class Executor;
friend class _::XThreadEvent; friend class _::XThreadEvent;
friend class _::FiberBase;
}; };
class WaitScope { class WaitScope {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment