From 07ab99bf6af3f520d72b01d4f5b4b441b8f09333 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 24 Jul 2019 14:37:54 +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 | 42 ++++++++++++++++--- src/crystal/system/fiber.cr | 2 + src/crystal/system/win32/fiber.cr | 21 ++++++++++ src/fiber.cr | 10 ++++- src/kernel.cr | 14 +++---- .../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 +- src/thread/thread_win32.cr | 9 +++- 11 files changed, 107 insertions(+), 15 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 d244bdc3832f..6f56ef0e5c7e 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 64504cc56e22..434a927656f8 100644 --- a/src/crystal/scheduler.cr +++ b/src/crystal/scheduler.cr @@ -99,16 +99,34 @@ class Crystal::Scheduler end private def fatal_resume_error(fiber, message) - LibC.dprintf 2, "\nFATAL: #{message}: #{fiber}\n" - caller.each { |line| LibC.dprintf(2, " from #{line}\n") } + print_error "\nFATAL: #{message}: #{fiber}\n" + {% unless flag?(:win32) %} + caller.each { |line| print_error " from #{line}\n" } + {% end %} + exit 1 end + private def print_error(message) + {% if flag?(:unix) %} + LibC.dprintf 2, message + {% elsif flag?(:win32) %} + LibC._write 2, message, message.bytesize + {% end %} + end + protected def reschedule : Nil if runnable = @runnables.shift? runnable.resume else - Crystal::EventLoop.resume + {% if flag?(:unix) %} + Crystal::EventLoop.resume + {% elsif flag?(:win32) %} + print_error "Warning: No runnables in scheduler. Exiting program." + ::exit + {% else %} + {% raise "unsupported platform" %} + {% end %} end end @@ -118,11 +136,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..9c3fb3667069 --- /dev/null +++ b/src/crystal/system/win32/fiber.cr @@ -0,0 +1,21 @@ +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 +end diff --git a/src/fiber.cr b/src/fiber.cr index 57464a9888a5..51af1fddd879 100644 --- a/src/fiber.cr +++ b/src/fiber.cr @@ -17,7 +17,11 @@ class Fiber @context : Context @stack : Void* - @resume_event : Crystal::Event? + + {% unless flag?(:win32) %} + @resume_event : Crystal::Event? + {% end %} + protected property stack_bottom : Void* property name : String? @alive = true @@ -85,8 +89,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 %} @alive = false Crystal::Scheduler.reschedule @@ -118,10 +124,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 9c3a815a9c07..93fcae30da27 100644 --- a/src/kernel.cr +++ b/src/kernel.cr @@ -537,15 +537,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/processthreadsapi.cr b/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr new file mode 100644 index 000000000000..b252b70d307a --- /dev/null +++ b/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr @@ -0,0 +1,5 @@ +require "./basetsd" + +lib LibC + fun GetCurrentThreadStackLimits(lowLimit : ULONG_PTR*, highLimit : ULONG_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 777b85adb520..a9c19140e475 100644 --- a/src/prelude.cr +++ b/src/prelude.cr @@ -36,7 +36,7 @@ require "box" require "char" require "char/reader" require "class" -no_win require "concurrent" +require "concurrent" require "crystal/main" require "deque" require "dir" diff --git a/src/thread/thread_win32.cr b/src/thread/thread_win32.cr index 7f0dd23b81aa..5e7cb8e6c903 100644 --- a/src/thread/thread_win32.cr +++ b/src/thread/thread_win32.cr @@ -3,7 +3,8 @@ class Thread class_property! current : Thread? def initialize - @main_fiber = Fiber.new + @main_fiber = Fiber.new(stack_address, self) + @@threads.push(self) end # Returns the Thread object associated to the running system thread. @@ -14,4 +15,10 @@ class Thread # Associates the Thread object to the running system thread. protected def self.current=(@@current : Thread) : Thread end + + private def stack_address : Void* + LibC.GetCurrentThreadStackLimits(out low_limit, out high_limit) + + Pointer(Void).new(low_limit) + end end