From c63ace5b4500d093b8b2f75977fc69e4164a6923 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 20 Oct 2018 00:09:55 +0200 Subject: [PATCH] Implement Fiber and Scheduler for win32 The actual stack context switch is still missing. --- src/concurrent.cr | 2 ++ src/crystal/scheduler.cr | 27 ++++++++++++++++--- src/crystal/system/fiber.cr | 2 ++ src/crystal/system/win32/fiber.cr | 27 +++++++++++++++++++ src/fiber.cr | 8 ++++++ src/kernel.cr | 14 +++++----- src/lib_c/x86_64-windows-msvc/c/basetsd.cr | 1 + .../c/processthreadsapi.cr | 5 ++++ src/lib_c/x86_64-windows-msvc/c/winbase.cr | 12 +++++++++ src/lib_c/x86_64-windows-msvc/c/winnt.cr | 3 +++ src/prelude.cr | 2 +- 11 files changed, 92 insertions(+), 11 deletions(-) create mode 100644 src/crystal/system/win32/fiber.cr create mode 100644 src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr diff --git a/src/concurrent.cr b/src/concurrent.cr index 2b420c1ac923..70eacce607e3 100644 --- a/src/concurrent.cr +++ b/src/concurrent.cr @@ -3,6 +3,7 @@ require "channel" require "crystal/scheduler" require "./concurrent/*" +{% unless flag?(:win32) %} # Blocks the current fiber for the specified number of seconds. # # While this fiber is waiting this time other ready-to-execute @@ -22,6 +23,7 @@ end def sleep(time : Time::Span) Crystal::Scheduler.sleep(time) end +{% end %} # Blocks the current fiber forever. # diff --git a/src/crystal/scheduler.cr b/src/crystal/scheduler.cr index f6315ec3f8b4..5d11b0057d04 100644 --- a/src/crystal/scheduler.cr +++ b/src/crystal/scheduler.cr @@ -68,7 +68,14 @@ class Crystal::Scheduler if runnable = @runnables.shift? runnable.resume else - Crystal::EventLoop.resume + {% if flag?(:unix) %} + Crystal::EventLoop.resume + {% elsif flag?(:win32) %} + STDERR.puts "Warning: No runnables in scheduler. Exiting program." + ::exit + {% else %} + {% raise "unsupported platform" %} + {% end %} end end @@ -78,11 +85,25 @@ class Crystal::Scheduler end protected def yield : Nil - sleep(0.seconds) + {% if flag?(:unix) %} + sleep(0.seconds) + {% elsif flag?(:win32) %} + @runnables << @current + reschedule + {% else %} + {% raise "unsupported platform" %} + {% end %} end protected def yield(fiber : Fiber) : Nil - @current.resume_event.add(0.seconds) + {% if flag?(:unix) %} + @current.resume_event.add(0.seconds) + {% elsif flag?(:win32) %} + @runnables << fiber + {% else %} + {% raise "unsupported platform" %} + {% end %} + resume(fiber) end end diff --git a/src/crystal/system/fiber.cr b/src/crystal/system/fiber.cr index afd6f17a82ff..23794756bc4b 100644 --- a/src/crystal/system/fiber.cr +++ b/src/crystal/system/fiber.cr @@ -11,6 +11,8 @@ end {% if flag?(:unix) %} require "./unix/fiber" +{% elsif flag?(:win32) %} + require "./win32/fiber" {% else %} {% raise "fiber not supported" %} {% end %} diff --git a/src/crystal/system/win32/fiber.cr b/src/crystal/system/win32/fiber.cr new file mode 100644 index 000000000000..24843c665e3d --- /dev/null +++ b/src/crystal/system/win32/fiber.cr @@ -0,0 +1,27 @@ +require "c/winbase" +require "c/winnt" +require "c/processthreadsapi" + +module Crystal::System::Fiber + def self.allocate_stack(stack_size) : Void* + memory_pointer = LibC.VirtualAlloc(nil, stack_size, LibC::MEM_COMMIT | LibC::MEM_RESERVE, LibC::PAGE_READWRITE) + + if memory_pointer.null? + raise WinError.new("VirtualAlloc") + end + + memory_pointer + end + + def self.free_stack(stack : Void*, stack_size) : Nil + if LibC.VirtualFree(stack, stack_size, LibC::MEM_RELEASE) == 0 + raise WinError.new("VirtualFree") + end + end + + def self.main_fiber_stack(stack_bottom : Void*) : Void* + LibC.GetCurrentThreadStackLimits(out low_limit, out high_limit) + + Pointer(Void).new(low_limit) + end +end diff --git a/src/fiber.cr b/src/fiber.cr index 8efec9af1e5f..f810058af01b 100644 --- a/src/fiber.cr +++ b/src/fiber.cr @@ -48,7 +48,11 @@ class Fiber @@stack_pool = [] of Void* @stack : Void* + + {% unless flag?(:win32) %} @resume_event : Crystal::Event? + {% end %} + @stack_top = Pointer(Void).null protected property stack_top : Void* protected property stack_bottom : Void* @@ -130,8 +134,10 @@ class Fiber # Remove the current fiber from the linked list @@fibers.delete(self) + {% unless flag?(:win32) %} # Delete the resume event if it was used by `yield` or `sleep` @resume_event.try &.free + {% end %} Crystal::Scheduler.reschedule end @@ -144,10 +150,12 @@ class Fiber Crystal::Scheduler.resume(self) end + {% unless flag?(:win32) %} # :nodoc: def resume_event @resume_event ||= Crystal::EventLoop.create_resume_event(self) end + {% end %} def self.yield Crystal::Scheduler.yield diff --git a/src/kernel.cr b/src/kernel.cr index e6e4265eedab..407f380a0de6 100644 --- a/src/kernel.cr +++ b/src/kernel.cr @@ -470,15 +470,15 @@ class Process end end -{% unless flag?(:win32) %} - # Background loop to cleanup unused fiber stacks. - spawn do - loop do - sleep 5 - Fiber.stack_pool_collect - end +# Background loop to cleanup unused fiber stacks. +spawn do + loop do + sleep 5 + Fiber.stack_pool_collect end +end +{% unless flag?(:win32) %} Signal.setup_default_handlers LibExt.setup_sigfault_handler {% end %} diff --git a/src/lib_c/x86_64-windows-msvc/c/basetsd.cr b/src/lib_c/x86_64-windows-msvc/c/basetsd.cr index 9092e31ad946..1adc397f8ea0 100644 --- a/src/lib_c/x86_64-windows-msvc/c/basetsd.cr +++ b/src/lib_c/x86_64-windows-msvc/c/basetsd.cr @@ -4,4 +4,5 @@ lib LibC {% else %} alias ULONG_PTR = ULong {% end %} + alias PULONG_PTR = ULONG_PTR* end diff --git a/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr b/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr new file mode 100644 index 000000000000..edcb0c90f361 --- /dev/null +++ b/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr @@ -0,0 +1,5 @@ +require "./basetsd" + +lib LibC + fun GetCurrentThreadStackLimits(lowLimit : PULONG_PTR, highLimit : PULONG_PTR) : Void +end diff --git a/src/lib_c/x86_64-windows-msvc/c/winbase.cr b/src/lib_c/x86_64-windows-msvc/c/winbase.cr index 6fb4c3620bf0..08de4a2845f1 100644 --- a/src/lib_c/x86_64-windows-msvc/c/winbase.cr +++ b/src/lib_c/x86_64-windows-msvc/c/winbase.cr @@ -90,4 +90,16 @@ lib LibC fun GetEnvironmentStringsW : LPWCH fun FreeEnvironmentStringsW(lpszEnvironmentBlock : LPWCH) : BOOL fun SetEnvironmentVariableW(lpName : LPWSTR, lpValue : LPWSTR) : BOOL + + MEM_COMMIT = 0x00001000 + MEM_RESERVE = 0x00002000 + MEM_RESET = 0x00080000 + MEM_RESET_UNDO = 0x01000000 + + fun VirtualAlloc(lpAddress : Void*, dwSize : SizeT, flAllocationType : DWORD, flProtect : DWORD) : Void* + + MEM_DECOMMIT = 0x4000 + MEM_RELEASE = 0x8000 + + fun VirtualFree(lpAddress : Void*, dwSize : SizeT, dwFreeType : DWORD) : BOOL end diff --git a/src/lib_c/x86_64-windows-msvc/c/winnt.cr b/src/lib_c/x86_64-windows-msvc/c/winnt.cr index 87c0e2722349..c440d9d7b547 100644 --- a/src/lib_c/x86_64-windows-msvc/c/winnt.cr +++ b/src/lib_c/x86_64-windows-msvc/c/winnt.cr @@ -16,4 +16,7 @@ lib LibC FILE_ATTRIBUTE_REPARSE_POINT = 0x400 FILE_READ_ATTRIBUTES = 0x80 + + # Memory protection constants + PAGE_READWRITE = 0x04 end diff --git a/src/prelude.cr b/src/prelude.cr index 453cf3bbb0f8..1897310b57f7 100644 --- a/src/prelude.cr +++ b/src/prelude.cr @@ -35,7 +35,7 @@ require "box" require "char" require "char/reader" require "class" -no_win require "concurrent" +require "concurrent" require "crystal/main" require "deque" require "dir"