- <%= method.abstract? ? "abstract " : "" %>
+ <%= method.abstract? ? "abstract " : "" %><%= method.visibility.try(&.+(" ")) %>
<%= method.kind %>
<%= method.name %><%= method.args_to_html %>
#
diff --git a/src/compiler/crystal/tools/doc/html/type.html b/src/compiler/crystal/tools/doc/html/type.html
index 10c7e51fedd3..4438ebb2b883 100644
--- a/src/compiler/crystal/tools/doc/html/type.html
+++ b/src/compiler/crystal/tools/doc/html/type.html
@@ -19,7 +19,9 @@
<% if type.program? %>
<%= type.full_name.gsub("::", "::") %>
<% else %>
- <%= type.abstract? ? "abstract " : ""%><%= type.kind %> <%= type.full_name.gsub("::", "::") %>
+
+ <%= type.abstract? ? "abstract " : ""%><%= type.visibility.try(&.+(" ")) %><%= type.kind %>
+ <%= type.full_name.gsub("::", "::") %>
<% end %>
diff --git a/src/compiler/crystal/tools/doc/macro.cr b/src/compiler/crystal/tools/doc/macro.cr
index 49b9c30795bc..629eccc2e225 100644
--- a/src/compiler/crystal/tools/doc/macro.cr
+++ b/src/compiler/crystal/tools/doc/macro.cr
@@ -54,6 +54,10 @@ class Crystal::Doc::Macro
false
end
+ def visibility
+ @type.visibility
+ end
+
def kind
"macro "
end
diff --git a/src/compiler/crystal/tools/doc/method.cr b/src/compiler/crystal/tools/doc/method.cr
index 069deb48ee61..c43309ddf9a0 100644
--- a/src/compiler/crystal/tools/doc/method.cr
+++ b/src/compiler/crystal/tools/doc/method.cr
@@ -43,7 +43,7 @@ class Crystal::Doc::Method
# This docs not include the "Description copied from ..." banner
# in case it's needed.
def doc
- doc_info.doc
+ doc_info.doc.try &.strip.lchop(":showdoc:").strip
end
# Returns the type this method's docs are copied from, but
@@ -135,6 +135,16 @@ class Crystal::Doc::Method
end
end
+ def visibility
+ case @def.visibility
+ in .public?
+ in .protected?
+ "protected"
+ in .private?
+ "private"
+ end
+ end
+
def constructor?
return false unless @class_method
return true if name == "new"
@@ -323,6 +333,7 @@ class Crystal::Doc::Method
builder.field "doc", doc unless doc.nil?
builder.field "summary", formatted_summary unless formatted_summary.nil?
builder.field "abstract", abstract?
+ builder.field "visibility", visibility if visibility
builder.field "args", args unless args.empty?
builder.field "args_string", args_to_s unless args.empty?
builder.field "args_html", args_to_html unless args.empty?
diff --git a/src/compiler/crystal/tools/doc/type.cr b/src/compiler/crystal/tools/doc/type.cr
index 9a40bd23e189..15cd3d5f2172 100644
--- a/src/compiler/crystal/tools/doc/type.cr
+++ b/src/compiler/crystal/tools/doc/type.cr
@@ -3,6 +3,13 @@ require "./item"
class Crystal::Doc::Type
include Item
+ PSEUDO_CLASS_PREFIX = "CRYSTAL_PSEUDO__"
+ PSEUDO_CLASS_NOTE = <<-DOC
+
+ NOTE: This is a pseudo-class provided directly by the Crystal compiler.
+ It cannot be reopened nor overridden.
+ DOC
+
getter type : Crystal::Type
def initialize(@generator : Generator, type : Crystal::Type)
@@ -39,7 +46,11 @@ class Crystal::Doc::Type
when Program
"Top Level Namespace"
when NamedType
- type.name
+ if @generator.project_info.crystal_stdlib?
+ type.name.lchop(PSEUDO_CLASS_PREFIX)
+ else
+ type.name
+ end
when NoReturnType
"NoReturn"
when VoidType
@@ -70,6 +81,10 @@ class Crystal::Doc::Type
@type.abstract?
end
+ def visibility
+ @type.private? ? "private" : nil
+ end
+
def parents_of?(type)
return false unless type
@@ -170,7 +185,7 @@ class Crystal::Doc::Type
defs = [] of Method
@type.defs.try &.each do |def_name, defs_with_metadata|
defs_with_metadata.each do |def_with_metadata|
- next unless def_with_metadata.def.visibility.public?
+ next if !def_with_metadata.def.visibility.public? && !showdoc?(def_with_metadata.def)
next unless @generator.must_include? def_with_metadata.def
defs << method(def_with_metadata.def, false)
@@ -181,6 +196,10 @@ class Crystal::Doc::Type
end
end
+ private def showdoc?(adef)
+ @generator.showdoc?(adef.doc.try &.strip)
+ end
+
private def sort_order(item)
# Sort operators first, then alphanumeric (case-insensitive).
{item.name[0].alphanumeric? ? 1 : 0, item.name.downcase}
@@ -194,7 +213,7 @@ class Crystal::Doc::Type
@type.metaclass.defs.try &.each_value do |defs_with_metadata|
defs_with_metadata.each do |def_with_metadata|
a_def = def_with_metadata.def
- next unless a_def.visibility.public?
+ next if !def_with_metadata.def.visibility.public? && !showdoc?(def_with_metadata.def)
body = a_def.body
@@ -225,7 +244,9 @@ class Crystal::Doc::Type
macros = [] of Macro
@type.metaclass.macros.try &.each_value do |the_macros|
the_macros.each do |a_macro|
- if a_macro.visibility.public? && @generator.must_include? a_macro
+ next if !a_macro.visibility.public? && !showdoc?(a_macro)
+
+ if @generator.must_include? a_macro
macros << self.macro(a_macro)
end
end
@@ -403,7 +424,11 @@ class Crystal::Doc::Type
end
def doc
- @type.doc
+ if (t = type).is_a?(NamedType) && t.name.starts_with?(PSEUDO_CLASS_PREFIX)
+ "#{@type.doc}#{PSEUDO_CLASS_NOTE}"
+ else
+ @type.doc
+ end
end
def lookup_path(path_or_names : Path | Array(String))
diff --git a/src/compiler/crystal/tools/formatter.cr b/src/compiler/crystal/tools/formatter.cr
index 796afe0730de..7ea32627078e 100644
--- a/src/compiler/crystal/tools/formatter.cr
+++ b/src/compiler/crystal/tools/formatter.cr
@@ -1476,7 +1476,7 @@ module Crystal
# this formats `def foo # ...` to `def foo(&) # ...` for yielding
# methods before consuming the comment line
if node.block_arity && node.args.empty? && !node.block_arg && !node.double_splat
- write "(&)" if flag?("method_signature_yield")
+ write "(&)"
end
skip_space consume_newline: false
@@ -1523,7 +1523,7 @@ module Crystal
end
def format_def_args(node : Def | Macro)
- yields = node.is_a?(Def) && !node.block_arity.nil? && flag?("method_signature_yield")
+ yields = node.is_a?(Def) && !node.block_arity.nil?
format_def_args node.args, node.block_arg, node.splat_index, false, node.double_splat, yields
end
@@ -1651,7 +1651,7 @@ module Crystal
yield
# Write "," before skipping spaces to prevent inserting comment between argument and comma.
- write "," if has_more || (wrote_newline && @token.type.op_comma?) || (write_trailing_comma && flag?("def_trailing_comma"))
+ write "," if has_more || (wrote_newline && @token.type.op_comma?) || write_trailing_comma
just_wrote_newline = skip_space
if @token.type.newline?
@@ -1681,7 +1681,7 @@ module Crystal
elsif @token.type.op_rparen? && has_more && !just_wrote_newline
# if we found a `)` and there are still more parameters to write, it
# must have been a missing `&` for a def that yields
- write " " if flag?("method_signature_yield")
+ write " "
end
just_wrote_newline
@@ -4273,7 +4273,7 @@ module Crystal
skip_space_or_newline
end
- write " " if a_def.args.present? || return_type || flag?("proc_literal_whitespace") || whitespace_after_op_minus_gt
+ write " "
is_do = false
if @token.keyword?(:do)
@@ -4281,7 +4281,7 @@ module Crystal
is_do = true
else
write_token :OP_LCURLY
- write " " if a_def.body.is_a?(Nop) && (flag?("proc_literal_whitespace") || @token.type.space?)
+ write " " if a_def.body.is_a?(Nop)
end
skip_space
diff --git a/src/compiler/crystal/tools/implementations.cr b/src/compiler/crystal/tools/implementations.cr
index e2dbee001346..e4a6d210d922 100644
--- a/src/compiler/crystal/tools/implementations.cr
+++ b/src/compiler/crystal/tools/implementations.cr
@@ -53,7 +53,9 @@ module Crystal
@line = macro_location.line_number + loc.line_number
@column = loc.column_number
else
- raise "not implemented"
+ @line = loc.line_number
+ @column = loc.column_number
+ @filename = "
"
end
end
@@ -111,7 +113,7 @@ module Crystal
if target_defs = node.target_defs
target_defs.each do |target_def|
- @locations << target_def.location.not_nil!
+ @locations << (target_def.location || Location.new(nil, 0, 0))
end
end
false
diff --git a/src/compiler/crystal/tools/init.cr b/src/compiler/crystal/tools/init.cr
index 96b004eec2fd..01b2e137c578 100644
--- a/src/compiler/crystal/tools/init.cr
+++ b/src/compiler/crystal/tools/init.cr
@@ -157,7 +157,7 @@ module Crystal
@github_name = "none",
@silent = false,
@force = false,
- @skip_existing = false
+ @skip_existing = false,
)
end
diff --git a/src/compiler/crystal/tools/unreachable.cr b/src/compiler/crystal/tools/unreachable.cr
index a8886fecf596..4ba681240385 100644
--- a/src/compiler/crystal/tools/unreachable.cr
+++ b/src/compiler/crystal/tools/unreachable.cr
@@ -6,7 +6,7 @@ require "csv"
module Crystal
class Command
private def unreachable
- config, result = compile_no_codegen "tool unreachable", path_filter: true, unreachable_command: true, allowed_formats: %w[text json csv]
+ config, result = compile_no_codegen "tool unreachable", path_filter: true, unreachable_command: true, allowed_formats: %w[text json csv codecov]
unreachable = UnreachableVisitor.new
@@ -42,6 +42,8 @@ module Crystal
to_json(STDOUT)
when "csv"
to_csv(STDOUT)
+ when "codecov"
+ to_codecov(STDOUT)
else
to_text(STDOUT)
end
@@ -111,6 +113,31 @@ module Crystal
end
end
end
+
+ # https://docs.codecov.com/docs/codecov-custom-coverage-format
+ def to_codecov(io)
+ hits = Hash(String, Hash(Int32, Int32)).new { |hash, key| hash[key] = Hash(Int32, Int32).new(0) }
+
+ each do |a_def, location, count|
+ hits[location.filename][location.line_number] = count
+ end
+
+ JSON.build io do |builder|
+ builder.object do
+ builder.string "coverage"
+ builder.object do
+ hits.each do |filename, line_coverage|
+ builder.string filename
+ builder.object do
+ line_coverage.each do |line, count|
+ builder.field line, count
+ end
+ end
+ end
+ end
+ end
+ end
+ end
end
# This visitor walks the entire reachable code tree and collect locations
diff --git a/src/compiler/crystal/types.cr b/src/compiler/crystal/types.cr
index 5d903b763050..3a2a759b3158 100644
--- a/src/compiler/crystal/types.cr
+++ b/src/compiler/crystal/types.cr
@@ -373,6 +373,10 @@ module Crystal
nil
end
+ def lookup_name(name)
+ types?.try(&.[name]?)
+ end
+
def parents
nil
end
@@ -1389,10 +1393,10 @@ module Crystal
# Float64 mantissa has 52 bits
case kind
when .i8?, .u8?, .i16?, .u16?
- # Less than 23 bits, so convertable to Float32 and Float64 without precision loss
+ # Less than 23 bits, so convertible to Float32 and Float64 without precision loss
true
when .i32?, .u32?
- # Less than 52 bits, so convertable to Float64 without precision loss
+ # Less than 52 bits, so convertible to Float64 without precision loss
other_type.kind.f64?
else
false
@@ -2756,17 +2760,9 @@ module Crystal
delegate lookup_defs, lookup_defs_with_modules, lookup_first_def,
lookup_macro, lookup_macros, to: aliased_type
- def types?
+ def lookup_name(name)
process_value
- if aliased_type = @aliased_type
- aliased_type.types?
- else
- nil
- end
- end
-
- def types
- types?.not_nil!
+ @aliased_type.try(&.lookup_name(name))
end
def remove_alias
diff --git a/src/compiler/crystal/util.cr b/src/compiler/crystal/util.cr
index c33bfa5d0d42..d0de6f226f36 100644
--- a/src/compiler/crystal/util.cr
+++ b/src/compiler/crystal/util.cr
@@ -41,7 +41,7 @@ module Crystal
source : String | Array(String),
highlight_line_number = nil,
color = false,
- line_number_start = 1
+ line_number_start = 1,
)
source = source.lines if source.is_a? String
line_number_padding = (source.size + line_number_start).to_s.chars.size
diff --git a/src/complex.cr b/src/complex.cr
index 65fbc9204b59..e2a5830b395a 100644
--- a/src/complex.cr
+++ b/src/complex.cr
@@ -237,14 +237,28 @@ struct Complex
# Divides `self` by *other*.
def /(other : Complex) : Complex
- if other.real <= other.imag
- r = other.real / other.imag
- d = other.imag + r * other.real
- Complex.new((@real * r + @imag) / d, (@imag * r - @real) / d)
- else
+ if other.real.nan? || other.imag.nan?
+ Complex.new(Float64::NAN, Float64::NAN)
+ elsif other.imag.abs < other.real.abs
r = other.imag / other.real
d = other.real + r * other.imag
- Complex.new((@real + @imag * r) / d, (@imag - @real * r) / d)
+
+ if d.nan? || d == 0
+ Complex.new(Float64::NAN, Float64::NAN)
+ else
+ Complex.new((@real + @imag * r) / d, (@imag - @real * r) / d)
+ end
+ elsif other.imag == 0 # other.real == 0
+ Complex.new(@real / other.real, @imag / other.real)
+ else # 0 < other.real.abs <= other.imag.abs
+ r = other.real / other.imag
+ d = other.imag + r * other.real
+
+ if d.nan? || d == 0
+ Complex.new(Float64::NAN, Float64::NAN)
+ else
+ Complex.new((@real * r + @imag) / d, (@imag * r - @real) / d)
+ end
end
end
diff --git a/src/concurrent.cr b/src/concurrent.cr
index 6f3a58291a22..07ae945a84f6 100644
--- a/src/concurrent.cr
+++ b/src/concurrent.cr
@@ -7,6 +7,7 @@ require "crystal/tracing"
#
# While this fiber is waiting this time, other ready-to-execute
# fibers might start their execution.
+@[Deprecated("Use `::sleep(Time::Span)` instead")]
def sleep(seconds : Number) : Nil
if seconds < 0
raise ArgumentError.new "Sleep seconds must be positive"
@@ -32,31 +33,35 @@ end
# Spawns a new fiber.
#
-# The newly created fiber doesn't run as soon as spawned.
+# NOTE: The newly created fiber doesn't run as soon as spawned.
#
# Example:
# ```
# # Write "1" every 1 second and "2" every 2 seconds for 6 seconds.
#
-# ch = Channel(Nil).new
+# require "wait_group"
+#
+# wg = WaitGroup.new 2
#
# spawn do
# 6.times do
-# sleep 1
+# sleep 1.second
# puts 1
# end
-# ch.send(nil)
+# ensure
+# wg.done
# end
#
# spawn do
# 3.times do
-# sleep 2
+# sleep 2.seconds
# puts 2
# end
-# ch.send(nil)
+# ensure
+# wg.done
# end
#
-# 2.times { ch.receive }
+# wg.wait
# ```
def spawn(*, name : String? = nil, same_thread = false, &block)
fiber = Fiber.new(name, &block)
diff --git a/src/crystal/system/event_loop.cr b/src/crystal/event_loop.cr
similarity index 66%
rename from src/crystal/system/event_loop.cr
rename to src/crystal/event_loop.cr
index 46954e6034ff..00bcb86040b6 100644
--- a/src/crystal/system/event_loop.cr
+++ b/src/crystal/event_loop.cr
@@ -1,22 +1,40 @@
abstract class Crystal::EventLoop
- # Creates an event loop instance
- def self.create : self
+ def self.backend_class
{% if flag?(:wasi) %}
- Crystal::Wasi::EventLoop.new
+ Crystal::EventLoop::Wasi
{% elsif flag?(:unix) %}
- Crystal::LibEvent::EventLoop.new
+ # TODO: enable more targets by default (need manual tests or fixes)
+ {% if flag?("evloop=libevent") %}
+ Crystal::EventLoop::LibEvent
+ {% elsif flag?("evloop=epoll") || flag?(:android) || flag?(:linux) %}
+ Crystal::EventLoop::Epoll
+ {% elsif flag?("evloop=kqueue") || flag?(:darwin) || flag?(:freebsd) %}
+ Crystal::EventLoop::Kqueue
+ {% else %}
+ Crystal::EventLoop::LibEvent
+ {% end %}
{% elsif flag?(:win32) %}
- Crystal::IOCP::EventLoop.new
+ Crystal::EventLoop::IOCP
{% else %}
{% raise "Event loop not supported" %}
{% end %}
end
+ # Creates an event loop instance
+ def self.create : self
+ backend_class.new
+ end
+
@[AlwaysInline]
def self.current : self
Crystal::Scheduler.event_loop
end
+ @[AlwaysInline]
+ def self.current? : self?
+ Crystal::Scheduler.event_loop?
+ end
+
# Runs the loop.
#
# Returns immediately if events are activable. Set `blocking` to false to
@@ -51,7 +69,7 @@ abstract class Crystal::EventLoop
abstract def free : Nil
# Adds a new timeout to this event.
- abstract def add(timeout : Time::Span?) : Nil
+ abstract def add(timeout : Time::Span) : Nil
end
end
@@ -71,11 +89,19 @@ abstract class Crystal::EventLoop
end
{% if flag?(:wasi) %}
- require "./wasi/event_loop"
+ require "./event_loop/wasi"
{% elsif flag?(:unix) %}
- require "./unix/event_loop_libevent"
+ {% if flag?("evloop=libevent") %}
+ require "./event_loop/libevent"
+ {% elsif flag?("evloop=epoll") || flag?(:android) || flag?(:linux) %}
+ require "./event_loop/epoll"
+ {% elsif flag?("evloop=kqueue") || flag?(:darwin) || flag?(:freebsd) %}
+ require "./event_loop/kqueue"
+ {% else %}
+ require "./event_loop/libevent"
+ {% end %}
{% elsif flag?(:win32) %}
- require "./win32/event_loop_iocp"
+ require "./event_loop/iocp"
{% else %}
{% raise "Event loop not supported" %}
{% end %}
diff --git a/src/crystal/event_loop/epoll.cr b/src/crystal/event_loop/epoll.cr
new file mode 100644
index 000000000000..371b9039b6b5
--- /dev/null
+++ b/src/crystal/event_loop/epoll.cr
@@ -0,0 +1,142 @@
+require "./polling"
+require "../system/unix/epoll"
+require "../system/unix/eventfd"
+require "../system/unix/timerfd"
+
+class Crystal::EventLoop::Epoll < Crystal::EventLoop::Polling
+ def initialize
+ # the epoll instance
+ @epoll = System::Epoll.new
+
+ # notification to interrupt a run
+ @interrupted = Atomic::Flag.new
+ @eventfd = System::EventFD.new
+ @epoll.add(@eventfd.fd, LibC::EPOLLIN, u64: @eventfd.fd.to_u64!)
+
+ # we use timerfd to go below the millisecond precision of epoll_wait; it
+ # also allows to avoid locking timers before every epoll_wait call
+ @timerfd = System::TimerFD.new
+ @epoll.add(@timerfd.fd, LibC::EPOLLIN, u64: @timerfd.fd.to_u64!)
+ end
+
+ def after_fork_before_exec : Nil
+ super
+
+ # O_CLOEXEC would close these automatically, but we don't want to mess with
+ # the parent process fds (it would mess the parent evloop)
+ @epoll.close
+ @eventfd.close
+ @timerfd.close
+ end
+
+ {% unless flag?(:preview_mt) %}
+ def after_fork : Nil
+ super
+
+ # close inherited fds
+ @epoll.close
+ @eventfd.close
+ @timerfd.close
+
+ # create new fds
+ @epoll = System::Epoll.new
+
+ @interrupted.clear
+ @eventfd = System::EventFD.new
+ @epoll.add(@eventfd.fd, LibC::EPOLLIN, u64: @eventfd.fd.to_u64!)
+
+ @timerfd = System::TimerFD.new
+ @epoll.add(@timerfd.fd, LibC::EPOLLIN, u64: @timerfd.fd.to_u64!)
+ system_set_timer(@timers.next_ready?)
+
+ # re-add all registered fds
+ Polling.arena.each_index { |fd, index| system_add(fd, index) }
+ end
+ {% end %}
+
+ private def system_run(blocking : Bool, & : Fiber ->) : Nil
+ Crystal.trace :evloop, "run", blocking: blocking
+
+ # wait for events (indefinitely when blocking)
+ buffer = uninitialized LibC::EpollEvent[128]
+ epoll_events = @epoll.wait(buffer.to_slice, timeout: blocking ? -1 : 0)
+
+ timer_triggered = false
+
+ # process events
+ epoll_events.size.times do |i|
+ epoll_event = epoll_events.to_unsafe + i
+
+ case epoll_event.value.data.u64
+ when @eventfd.fd
+ # TODO: panic if epoll_event.value.events != LibC::EPOLLIN (could be EPOLLERR or EPLLHUP)
+ Crystal.trace :evloop, "interrupted"
+ @eventfd.read
+ @interrupted.clear
+ when @timerfd.fd
+ # TODO: panic if epoll_event.value.events != LibC::EPOLLIN (could be EPOLLERR or EPLLHUP)
+ Crystal.trace :evloop, "timer"
+ timer_triggered = true
+ else
+ process_io(epoll_event) { |fiber| yield fiber }
+ end
+ end
+
+ # OPTIMIZE: only process timers when timer_triggered (?)
+ process_timers(timer_triggered) { |fiber| yield fiber }
+ end
+
+ private def process_io(epoll_event : LibC::EpollEvent*, &) : Nil
+ index = Polling::Arena::Index.new(epoll_event.value.data.u64)
+ events = epoll_event.value.events
+
+ Crystal.trace :evloop, "event", fd: index.index, index: index.to_i64, events: events
+
+ Polling.arena.get?(index) do |pd|
+ if (events & (LibC::EPOLLERR | LibC::EPOLLHUP)) != 0
+ pd.value.@readers.ready_all { |event| unsafe_resume_io(event) { |fiber| yield fiber } }
+ pd.value.@writers.ready_all { |event| unsafe_resume_io(event) { |fiber| yield fiber } }
+ return
+ end
+
+ if (events & LibC::EPOLLRDHUP) == LibC::EPOLLRDHUP
+ pd.value.@readers.ready_all { |event| unsafe_resume_io(event) { |fiber| yield fiber } }
+ elsif (events & LibC::EPOLLIN) == LibC::EPOLLIN
+ pd.value.@readers.ready_one { |event| unsafe_resume_io(event) { |fiber| yield fiber } }
+ end
+
+ if (events & LibC::EPOLLOUT) == LibC::EPOLLOUT
+ pd.value.@writers.ready_one { |event| unsafe_resume_io(event) { |fiber| yield fiber } }
+ end
+ end
+ end
+
+ def interrupt : Nil
+ # the atomic makes sure we only write once
+ @eventfd.write(1) if @interrupted.test_and_set
+ end
+
+ protected def system_add(fd : Int32, index : Polling::Arena::Index) : Nil
+ Crystal.trace :evloop, "epoll_ctl", op: "add", fd: fd, index: index.to_i64
+ events = LibC::EPOLLIN | LibC::EPOLLOUT | LibC::EPOLLRDHUP | LibC::EPOLLET
+ @epoll.add(fd, events, u64: index.to_u64)
+ end
+
+ protected def system_del(fd : Int32, closing = true) : Nil
+ Crystal.trace :evloop, "epoll_ctl", op: "del", fd: fd
+ @epoll.delete(fd)
+ end
+
+ protected def system_del(fd : Int32, closing = true, &) : Nil
+ Crystal.trace :evloop, "epoll_ctl", op: "del", fd: fd
+ @epoll.delete(fd) { yield }
+ end
+
+ private def system_set_timer(time : Time::Span?) : Nil
+ if time
+ @timerfd.set(time)
+ else
+ @timerfd.cancel
+ end
+ end
+end
diff --git a/src/crystal/event_loop/file_descriptor.cr b/src/crystal/event_loop/file_descriptor.cr
new file mode 100644
index 000000000000..633fa180db68
--- /dev/null
+++ b/src/crystal/event_loop/file_descriptor.cr
@@ -0,0 +1,44 @@
+abstract class Crystal::EventLoop
+ module FileDescriptor
+ # Reads at least one byte from the file descriptor into *slice*.
+ #
+ # Blocks the current fiber if no data is available for reading, continuing
+ # when available. Otherwise returns immediately.
+ #
+ # Returns the number of bytes read (up to `slice.size`).
+ # Returns 0 when EOF is reached.
+ abstract def read(file_descriptor : Crystal::System::FileDescriptor, slice : Bytes) : Int32
+
+ # Blocks the current fiber until the file descriptor is ready for read.
+ abstract def wait_readable(file_descriptor : Crystal::System::FileDescriptor) : Nil
+
+ # Writes at least one byte from *slice* to the file descriptor.
+ #
+ # Blocks the current fiber if the file descriptor isn't ready for writing,
+ # continuing when ready. Otherwise returns immediately.
+ #
+ # Returns the number of bytes written (up to `slice.size`).
+ abstract def write(file_descriptor : Crystal::System::FileDescriptor, slice : Bytes) : Int32
+
+ # Blocks the current fiber until the file descriptor is ready for write.
+ abstract def wait_writable(file_descriptor : Crystal::System::FileDescriptor) : Nil
+
+ # Closes the file descriptor resource.
+ abstract def close(file_descriptor : Crystal::System::FileDescriptor) : Nil
+ end
+
+ # Removes the file descriptor from the event loop. Can be used to free up
+ # memory resources associated with the file descriptor, as well as removing
+ # the file descriptor from kernel data structures.
+ #
+ # Called by `::IO::FileDescriptor#finalize` before closing the file
+ # descriptor. Errors shall be silently ignored.
+ def self.remove(file_descriptor : Crystal::System::FileDescriptor) : Nil
+ backend_class.remove_impl(file_descriptor)
+ end
+
+ # Actual implementation for `.remove`. Must be implemented on a subclass of
+ # `Crystal::EventLoop` when needed.
+ protected def self.remove_impl(file_descriptor : Crystal::System::FileDescriptor) : Nil
+ end
+end
diff --git a/src/crystal/event_loop/iocp.cr b/src/crystal/event_loop/iocp.cr
new file mode 100644
index 000000000000..da827079312a
--- /dev/null
+++ b/src/crystal/event_loop/iocp.cr
@@ -0,0 +1,336 @@
+# forward declaration for the require below to not create a module
+class Crystal::EventLoop::IOCP < Crystal::EventLoop
+end
+
+require "c/ntdll"
+require "../system/win32/iocp"
+require "../system/win32/waitable_timer"
+require "./timers"
+require "./iocp/*"
+
+# :nodoc:
+class Crystal::EventLoop::IOCP < Crystal::EventLoop
+ @waitable_timer : System::WaitableTimer?
+ @timer_packet : LibC::HANDLE?
+ @timer_key : System::IOCP::CompletionKey?
+
+ def initialize
+ @mutex = Thread::Mutex.new
+ @timers = Timers(Timer).new
+
+ # the completion port
+ @iocp = System::IOCP.new
+
+ # custom completion to interrupt a blocking run
+ @interrupted = Atomic(Bool).new(false)
+ @interrupt_key = System::IOCP::CompletionKey.new(:interrupt)
+
+ # On Windows 10+ we leverage a high resolution timer with completion packet
+ # to notify a completion port; on legacy Windows we fallback to the low
+ # resolution timeout (~15.6ms)
+ if System::IOCP.wait_completion_packet_methods?
+ @waitable_timer = System::WaitableTimer.new
+ @timer_packet = @iocp.create_wait_completion_packet
+ @timer_key = System::IOCP::CompletionKey.new(:timer)
+ end
+ end
+
+ # Returns the base IO Completion Port.
+ def iocp_handle : LibC::HANDLE
+ @iocp.handle
+ end
+
+ def create_completion_port(handle : LibC::HANDLE) : LibC::HANDLE
+ iocp = LibC.CreateIoCompletionPort(handle, @iocp.handle, nil, 0)
+ raise IO::Error.from_winerror("CreateIoCompletionPort") if iocp.null?
+
+ # all overlapped operations may finish synchronously, in which case we do
+ # not reschedule the running fiber; the following call tells Win32 not to
+ # queue an I/O completion packet to the associated IOCP as well, as this
+ # would be done by default
+ if LibC.SetFileCompletionNotificationModes(handle, LibC::FILE_SKIP_COMPLETION_PORT_ON_SUCCESS) == 0
+ raise IO::Error.from_winerror("SetFileCompletionNotificationModes")
+ end
+
+ iocp
+ end
+
+ def run(blocking : Bool) : Bool
+ enqueued = false
+
+ run_impl(blocking) do |fiber|
+ fiber.enqueue
+ enqueued = true
+ end
+
+ enqueued
+ end
+
+ # Runs the event loop and enqueues the fiber for the next upcoming event or
+ # completion.
+ private def run_impl(blocking : Bool, &) : Nil
+ Crystal.trace :evloop, "run", blocking: blocking ? 1 : 0
+
+ if @waitable_timer
+ timeout = blocking ? LibC::INFINITE : 0_i64
+ elsif blocking
+ if time = @mutex.synchronize { @timers.next_ready? }
+ # convert absolute time of next timer to relative time, expressed in
+ # milliseconds, rounded up
+ seconds, nanoseconds = System::Time.monotonic
+ relative = time - Time::Span.new(seconds: seconds, nanoseconds: nanoseconds)
+ timeout = (relative.to_i * 1000 + (relative.nanoseconds + 999_999) // 1_000_000).clamp(0_i64..)
+ else
+ timeout = LibC::INFINITE
+ end
+ else
+ timeout = 0_i64
+ end
+
+ # the array must be at least as large as `overlapped_entries` in
+ # `System::IOCP#wait_queued_completions`
+ events = uninitialized FiberEvent[64]
+ size = 0
+
+ @iocp.wait_queued_completions(timeout) do |fiber|
+ if (event = fiber.@resume_event) && event.wake_at?
+ events[size] = event
+ size += 1
+ end
+ yield fiber
+ end
+
+ @mutex.synchronize do
+ # cancel the timeout of completed operations
+ events.to_slice[0...size].each do |event|
+ @timers.delete(pointerof(event.@timer))
+ event.clear
+ end
+
+ # run expired timers
+ @timers.dequeue_ready do |timer|
+ process_timer(timer) { |fiber| yield fiber }
+ end
+
+ # update timer
+ rearm_waitable_timer(@timers.next_ready?, interruptible: false)
+ end
+
+ @interrupted.set(false, :release)
+ end
+
+ private def process_timer(timer : Pointer(Timer), &)
+ fiber = timer.value.fiber
+
+ case timer.value.type
+ in .sleep?
+ timer.value.timed_out!
+ fiber.@resume_event.as(FiberEvent).clear
+ in .select_timeout?
+ return unless select_action = fiber.timeout_select_action
+ fiber.timeout_select_action = nil
+ return unless select_action.time_expired?
+ fiber.@timeout_event.as(FiberEvent).clear
+ end
+
+ yield fiber
+ end
+
+ def interrupt : Nil
+ unless @interrupted.get(:acquire)
+ @iocp.post_queued_completion_status(@interrupt_key)
+ end
+ end
+
+ protected def add_timer(timer : Pointer(Timer)) : Nil
+ @mutex.synchronize do
+ is_next_ready = @timers.add(timer)
+ rearm_waitable_timer(timer.value.wake_at, interruptible: true) if is_next_ready
+ end
+ end
+
+ protected def delete_timer(timer : Pointer(Timer)) : Nil
+ @mutex.synchronize do
+ _, was_next_ready = @timers.delete(timer)
+ rearm_waitable_timer(@timers.next_ready?, interruptible: false) if was_next_ready
+ end
+ end
+
+ protected def rearm_waitable_timer(time : Time::Span?, interruptible : Bool) : Nil
+ if waitable_timer = @waitable_timer
+ status = @iocp.cancel_wait_completion_packet(@timer_packet.not_nil!, true)
+ if time
+ waitable_timer.set(time)
+ if status == LibC::STATUS_PENDING
+ interrupt
+ else
+ # STATUS_CANCELLED, STATUS_SUCCESS
+ @iocp.associate_wait_completion_packet(@timer_packet.not_nil!, waitable_timer.handle, @timer_key.not_nil!)
+ end
+ else
+ waitable_timer.cancel
+ end
+ elsif interruptible
+ interrupt
+ end
+ end
+
+ def create_resume_event(fiber : Fiber) : EventLoop::Event
+ FiberEvent.new(:sleep, fiber)
+ end
+
+ def create_timeout_event(fiber : Fiber) : EventLoop::Event
+ FiberEvent.new(:select_timeout, fiber)
+ end
+
+ def read(file_descriptor : Crystal::System::FileDescriptor, slice : Bytes) : Int32
+ System::IOCP.overlapped_operation(file_descriptor, "ReadFile", file_descriptor.read_timeout) do |overlapped|
+ ret = LibC.ReadFile(file_descriptor.windows_handle, slice, slice.size, out byte_count, overlapped)
+ {ret, byte_count}
+ end.to_i32
+ end
+
+ def wait_readable(file_descriptor : Crystal::System::FileDescriptor) : Nil
+ raise NotImplementedError.new("Crystal::System::IOCP#wait_readable(FileDescriptor)")
+ end
+
+ def write(file_descriptor : Crystal::System::FileDescriptor, slice : Bytes) : Int32
+ System::IOCP.overlapped_operation(file_descriptor, "WriteFile", file_descriptor.write_timeout, writing: true) do |overlapped|
+ ret = LibC.WriteFile(file_descriptor.windows_handle, slice, slice.size, out byte_count, overlapped)
+ {ret, byte_count}
+ end.to_i32
+ end
+
+ def wait_writable(file_descriptor : Crystal::System::FileDescriptor) : Nil
+ raise NotImplementedError.new("Crystal::System::IOCP#wait_writable(FileDescriptor)")
+ end
+
+ def close(file_descriptor : Crystal::System::FileDescriptor) : Nil
+ LibC.CancelIoEx(file_descriptor.windows_handle, nil) unless file_descriptor.system_blocking?
+ end
+
+ private def wsa_buffer(bytes)
+ wsabuf = LibC::WSABUF.new
+ wsabuf.len = bytes.size
+ wsabuf.buf = bytes.to_unsafe
+ wsabuf
+ end
+
+ def read(socket : ::Socket, slice : Bytes) : Int32
+ wsabuf = wsa_buffer(slice)
+
+ bytes_read = System::IOCP.wsa_overlapped_operation(socket, socket.fd, "WSARecv", socket.read_timeout, connreset_is_error: false) do |overlapped|
+ flags = 0_u32
+ ret = LibC.WSARecv(socket.fd, pointerof(wsabuf), 1, out bytes_received, pointerof(flags), overlapped, nil)
+ {ret, bytes_received}
+ end
+
+ bytes_read.to_i32
+ end
+
+ def wait_readable(socket : ::Socket) : Nil
+ # NOTE: Windows 10+ has `ProcessSocketNotifications` to associate sockets to
+ # a completion port and be notified of socket readiness. See
+ #
+ raise NotImplementedError.new("Crystal::System::IOCP#wait_readable(Socket)")
+ end
+
+ def write(socket : ::Socket, slice : Bytes) : Int32
+ wsabuf = wsa_buffer(slice)
+
+ bytes = System::IOCP.wsa_overlapped_operation(socket, socket.fd, "WSASend", socket.write_timeout) do |overlapped|
+ ret = LibC.WSASend(socket.fd, pointerof(wsabuf), 1, out bytes_sent, 0, overlapped, nil)
+ {ret, bytes_sent}
+ end
+
+ bytes.to_i32
+ end
+
+ def wait_writable(socket : ::Socket) : Nil
+ # NOTE: Windows 10+ has `ProcessSocketNotifications` to associate sockets to
+ # a completion port and be notified of socket readiness. See
+ #
+ raise NotImplementedError.new("Crystal::System::IOCP#wait_writable(Socket)")
+ end
+
+ def send_to(socket : ::Socket, slice : Bytes, address : ::Socket::Address) : Int32
+ wsabuf = wsa_buffer(slice)
+ bytes_written = System::IOCP.wsa_overlapped_operation(socket, socket.fd, "WSASendTo", socket.write_timeout) do |overlapped|
+ ret = LibC.WSASendTo(socket.fd, pointerof(wsabuf), 1, out bytes_sent, 0, address, address.size, overlapped, nil)
+ {ret, bytes_sent}
+ end
+ raise ::Socket::Error.from_wsa_error("Error sending datagram to #{address}") if bytes_written == -1
+
+ # to_i32 is fine because string/slice sizes are an Int32
+ bytes_written.to_i32
+ end
+
+ def receive(socket : ::Socket, slice : Bytes) : Int32
+ receive_from(socket, slice)[0]
+ end
+
+ def receive_from(socket : ::Socket, slice : Bytes) : Tuple(Int32, ::Socket::Address)
+ sockaddr = Pointer(LibC::SOCKADDR_STORAGE).malloc.as(LibC::Sockaddr*)
+ # initialize sockaddr with the initialized family of the socket
+ copy = sockaddr.value
+ copy.sa_family = socket.family
+ sockaddr.value = copy
+
+ addrlen = sizeof(LibC::SOCKADDR_STORAGE)
+
+ wsabuf = wsa_buffer(slice)
+
+ flags = 0_u32
+ bytes_read = System::IOCP.wsa_overlapped_operation(socket, socket.fd, "WSARecvFrom", socket.read_timeout) do |overlapped|
+ ret = LibC.WSARecvFrom(socket.fd, pointerof(wsabuf), 1, out bytes_received, pointerof(flags), sockaddr, pointerof(addrlen), overlapped, nil)
+ {ret, bytes_received}
+ end
+
+ {bytes_read.to_i32, ::Socket::Address.from(sockaddr, addrlen)}
+ end
+
+ def connect(socket : ::Socket, address : ::Socket::Addrinfo | ::Socket::Address, timeout : ::Time::Span?) : IO::Error?
+ socket.overlapped_connect(socket.fd, "ConnectEx", timeout) do |overlapped|
+ # This is: LibC.ConnectEx(fd, address, address.size, nil, 0, nil, overlapped)
+ Crystal::System::Socket.connect_ex.call(socket.fd, address.to_unsafe, address.size, Pointer(Void).null, 0_u32, Pointer(UInt32).null, overlapped.to_unsafe)
+ end
+ end
+
+ def accept(socket : ::Socket) : ::Socket::Handle?
+ socket.system_accept do |client_handle|
+ address_size = sizeof(LibC::SOCKADDR_STORAGE) + 16
+
+ # buffer_size is set to zero to only accept the connection and don't receive any data.
+ # That will be a different operation.
+ #
+ # > If dwReceiveDataLength is zero, accepting the connection will not result in a receive operation.
+ # > Instead, AcceptEx completes as soon as a connection arrives, without waiting for any data.
+ #
+ # TODO: Investigate benefits from receiving data here directly. It's hard to integrate into the event loop and socket API.
+ buffer_size = 0
+ output_buffer = Bytes.new(address_size * 2 + buffer_size)
+
+ success = socket.overlapped_accept(socket.fd, "AcceptEx") do |overlapped|
+ # This is: LibC.AcceptEx(fd, client_handle, output_buffer, buffer_size, address_size, address_size, out received_bytes, overlapped)
+ received_bytes = uninitialized UInt32
+ Crystal::System::Socket.accept_ex.call(socket.fd, client_handle,
+ output_buffer.to_unsafe.as(Void*), buffer_size.to_u32!,
+ address_size.to_u32!, address_size.to_u32!, pointerof(received_bytes), overlapped.to_unsafe)
+ end
+
+ if success
+ # AcceptEx does not automatically set the socket options on the accepted
+ # socket to match those of the listening socket, we need to ask for that
+ # explicitly with SO_UPDATE_ACCEPT_CONTEXT
+ socket.system_setsockopt client_handle, LibC::SO_UPDATE_ACCEPT_CONTEXT, socket.fd
+
+ true
+ else
+ false
+ end
+ end
+ end
+
+ def close(socket : ::Socket) : Nil
+ end
+end
diff --git a/src/crystal/event_loop/iocp/fiber_event.cr b/src/crystal/event_loop/iocp/fiber_event.cr
new file mode 100644
index 000000000000..481648016210
--- /dev/null
+++ b/src/crystal/event_loop/iocp/fiber_event.cr
@@ -0,0 +1,34 @@
+class Crystal::EventLoop::IOCP::FiberEvent
+ include Crystal::EventLoop::Event
+
+ delegate type, wake_at, wake_at?, fiber, timed_out?, to: @timer
+
+ def initialize(type : Timer::Type, fiber : Fiber)
+ @timer = Timer.new(type, fiber)
+ end
+
+ # io timeout, sleep, or select timeout
+ def add(timeout : Time::Span) : Nil
+ seconds, nanoseconds = System::Time.monotonic
+ now = Time::Span.new(seconds: seconds, nanoseconds: nanoseconds)
+ @timer.wake_at = now + timeout
+ EventLoop.current.add_timer(pointerof(@timer))
+ end
+
+ # select timeout has been cancelled
+ def delete : Nil
+ return unless @timer.wake_at?
+ EventLoop.current.delete_timer(pointerof(@timer))
+ clear
+ end
+
+ # fiber died
+ def free : Nil
+ delete
+ end
+
+ # the timer triggered (already dequeued from eventloop)
+ def clear : Nil
+ @timer.wake_at = nil
+ end
+end
diff --git a/src/crystal/event_loop/iocp/timer.cr b/src/crystal/event_loop/iocp/timer.cr
new file mode 100644
index 000000000000..b7284d53e130
--- /dev/null
+++ b/src/crystal/event_loop/iocp/timer.cr
@@ -0,0 +1,40 @@
+# NOTE: this struct is only needed to be able to re-use `PointerPairingHeap`
+# because EventLoop::Polling uses pointers. If `EventLoop::Polling::Event` was a
+# reference, then `PairingHeap` wouldn't need pointers, and this struct could be
+# merged into `Event`.
+struct Crystal::EventLoop::IOCP::Timer
+ enum Type
+ Sleep
+ SelectTimeout
+ end
+
+ getter type : Type
+
+ # The `Fiber` that is waiting on the event and that the `EventLoop` shall
+ # resume.
+ getter fiber : Fiber
+
+ # The absolute time, against the monotonic clock, at which a timed event shall
+ # trigger. Nil for IO events without a timeout.
+ getter! wake_at : Time::Span
+
+ # True if an IO event has timed out (i.e. we're past `#wake_at`).
+ getter? timed_out : Bool = false
+
+ # The event can be added to the `Timers` list.
+ include PointerPairingHeap::Node
+
+ def initialize(@type : Type, @fiber)
+ end
+
+ def wake_at=(@wake_at)
+ end
+
+ def timed_out! : Bool
+ @timed_out = true
+ end
+
+ def heap_compare(other : Pointer(self)) : Bool
+ wake_at < other.value.wake_at
+ end
+end
diff --git a/src/crystal/event_loop/kqueue.cr b/src/crystal/event_loop/kqueue.cr
new file mode 100644
index 000000000000..47f00ddc9e89
--- /dev/null
+++ b/src/crystal/event_loop/kqueue.cr
@@ -0,0 +1,246 @@
+require "./polling"
+require "../system/unix/kqueue"
+
+class Crystal::EventLoop::Kqueue < Crystal::EventLoop::Polling
+ # the following are arbitrary numbers to identify specific events
+ INTERRUPT_IDENTIFIER = 9
+ TIMER_IDENTIFIER = 10
+
+ {% unless LibC.has_constant?(:EVFILT_USER) %}
+ @pipe = uninitialized LibC::Int[2]
+ {% end %}
+
+ def initialize
+ # the kqueue instance
+ @kqueue = System::Kqueue.new
+
+ # notification to interrupt a run
+ @interrupted = Atomic::Flag.new
+
+ {% if LibC.has_constant?(:EVFILT_USER) %}
+ @kqueue.kevent(
+ INTERRUPT_IDENTIFIER,
+ LibC::EVFILT_USER,
+ LibC::EV_ADD | LibC::EV_ENABLE | LibC::EV_CLEAR)
+ {% else %}
+ @pipe = System::FileDescriptor.system_pipe
+ @kqueue.kevent(@pipe[0], LibC::EVFILT_READ, LibC::EV_ADD)
+ {% end %}
+ end
+
+ def after_fork_before_exec : Nil
+ super
+
+ # O_CLOEXEC would close these automatically but we don't want to mess with
+ # the parent process fds (that would mess the parent evloop)
+
+ # kqueue isn't inherited by fork on darwin/dragonfly, but we still close
+ @kqueue.close
+
+ {% unless LibC.has_constant?(:EVFILT_USER) %}
+ @pipe.each { |fd| LibC.close(fd) }
+ {% end %}
+ end
+
+ {% unless flag?(:preview_mt) %}
+ def after_fork : Nil
+ super
+
+ # kqueue isn't inherited by fork on darwin/dragonfly, but we still close
+ @kqueue.close
+ @kqueue = System::Kqueue.new
+
+ @interrupted.clear
+
+ {% if LibC.has_constant?(:EVFILT_USER) %}
+ @kqueue.kevent(
+ INTERRUPT_IDENTIFIER,
+ LibC::EVFILT_USER,
+ LibC::EV_ADD | LibC::EV_ENABLE | LibC::EV_CLEAR)
+ {% else %}
+ @pipe.each { |fd| LibC.close(fd) }
+ @pipe = System::FileDescriptor.system_pipe
+ @kqueue.kevent(@pipe[0], LibC::EVFILT_READ, LibC::EV_ADD)
+ {% end %}
+
+ system_set_timer(@timers.next_ready?)
+
+ # re-add all registered fds
+ Polling.arena.each_index { |fd, index| system_add(fd, index) }
+ end
+ {% end %}
+
+ private def system_run(blocking : Bool, & : Fiber ->) : Nil
+ buffer = uninitialized LibC::Kevent[128]
+
+ Crystal.trace :evloop, "run", blocking: blocking
+ timeout = blocking ? nil : Time::Span.zero
+ kevents = @kqueue.wait(buffer.to_slice, timeout)
+
+ timer_triggered = false
+
+ # process events
+ kevents.size.times do |i|
+ kevent = kevents.to_unsafe + i
+
+ if process_interrupt?(kevent)
+ # nothing special
+ elsif kevent.value.filter == LibC::EVFILT_TIMER
+ # nothing special
+ timer_triggered = true
+ else
+ process_io(kevent) { |fiber| yield fiber }
+ end
+ end
+
+ # OPTIMIZE: only process timers when timer_triggered (?)
+ process_timers(timer_triggered) { |fiber| yield fiber }
+ end
+
+ private def process_interrupt?(kevent)
+ {% if LibC.has_constant?(:EVFILT_USER) %}
+ if kevent.value.filter == LibC::EVFILT_USER
+ @interrupted.clear if kevent.value.ident == INTERRUPT_IDENTIFIER
+ return true
+ end
+ {% else %}
+ if kevent.value.filter == LibC::EVFILT_READ && kevent.value.ident == @pipe[0]
+ ident = 0
+ ret = LibC.read(@pipe[0], pointerof(ident), sizeof(Int32))
+ raise RuntimeError.from_errno("read") if ret == -1
+ @interrupted.clear if ident == INTERRUPT_IDENTIFIER
+ return true
+ end
+ {% end %}
+ false
+ end
+
+ private def process_io(kevent : LibC::Kevent*, &) : Nil
+ index =
+ {% if flag?(:bits64) %}
+ Polling::Arena::Index.new(kevent.value.udata.address)
+ {% else %}
+ # assuming 32-bit target: rebuild the arena index
+ Polling::Arena::Index.new(kevent.value.ident.to_i32!, kevent.value.udata.address.to_u32!)
+ {% end %}
+
+ Crystal.trace :evloop, "event", fd: kevent.value.ident, index: index.to_i64,
+ filter: kevent.value.filter, flags: kevent.value.flags, fflags: kevent.value.fflags
+
+ Polling.arena.get?(index) do |pd|
+ if (kevent.value.fflags & LibC::EV_EOF) == LibC::EV_EOF
+ # apparently some systems may report EOF on write with EVFILT_READ instead
+ # of EVFILT_WRITE, so let's wake all waiters:
+ pd.value.@readers.ready_all { |event| unsafe_resume_io(event) { |fiber| yield fiber } }
+ pd.value.@writers.ready_all { |event| unsafe_resume_io(event) { |fiber| yield fiber } }
+ return
+ end
+
+ case kevent.value.filter
+ when LibC::EVFILT_READ
+ if (kevent.value.fflags & LibC::EV_ERROR) == LibC::EV_ERROR
+ # OPTIMIZE: pass errno (kevent.data) through PollDescriptor
+ pd.value.@readers.ready_all { |event| unsafe_resume_io(event) { |fiber| yield fiber } }
+ else
+ pd.value.@readers.ready_one { |event| unsafe_resume_io(event) { |fiber| yield fiber } }
+ end
+ when LibC::EVFILT_WRITE
+ if (kevent.value.fflags & LibC::EV_ERROR) == LibC::EV_ERROR
+ # OPTIMIZE: pass errno (kevent.data) through PollDescriptor
+ pd.value.@writers.ready_all { |event| unsafe_resume_io(event) { |fiber| yield fiber } }
+ else
+ pd.value.@writers.ready_one { |event| unsafe_resume_io(event) { |fiber| yield fiber } }
+ end
+ end
+ end
+ end
+
+ def interrupt : Nil
+ return unless @interrupted.test_and_set
+
+ {% if LibC.has_constant?(:EVFILT_USER) %}
+ @kqueue.kevent(INTERRUPT_IDENTIFIER, LibC::EVFILT_USER, 0, LibC::NOTE_TRIGGER)
+ {% else %}
+ ident = INTERRUPT_IDENTIFIER
+ ret = LibC.write(@pipe[1], pointerof(ident), sizeof(Int32))
+ raise RuntimeError.from_errno("write") if ret == -1
+ {% end %}
+ end
+
+ protected def system_add(fd : Int32, index : Polling::Arena::Index) : Nil
+ Crystal.trace :evloop, "kevent", op: "add", fd: fd, index: index.to_i64
+
+ # register both read and write events
+ kevents = uninitialized LibC::Kevent[2]
+ {LibC::EVFILT_READ, LibC::EVFILT_WRITE}.each_with_index do |filter, i|
+ kevent = kevents.to_unsafe + i
+ udata =
+ {% if flag?(:bits64) %}
+ Pointer(Void).new(index.to_u64)
+ {% else %}
+ # assuming 32-bit target: pass the generation as udata (ident is the fd/index)
+ Pointer(Void).new(index.generation)
+ {% end %}
+ System::Kqueue.set(kevent, fd, filter, LibC::EV_ADD | LibC::EV_CLEAR, udata: udata)
+ end
+
+ @kqueue.kevent(kevents.to_slice) do
+ raise RuntimeError.from_errno("kevent")
+ end
+ end
+
+ protected def system_del(fd : Int32, closing = true) : Nil
+ system_del(fd, closing) do
+ raise RuntimeError.from_errno("kevent")
+ end
+ end
+
+ protected def system_del(fd : Int32, closing = true, &) : Nil
+ return if closing # nothing to do: close(2) will do the cleanup
+
+ Crystal.trace :evloop, "kevent", op: "del", fd: fd
+
+ # unregister both read and write events
+ kevents = uninitialized LibC::Kevent[2]
+ {LibC::EVFILT_READ, LibC::EVFILT_WRITE}.each_with_index do |filter, i|
+ kevent = kevents.to_unsafe + i
+ System::Kqueue.set(kevent, fd, filter, LibC::EV_DELETE)
+ end
+
+ @kqueue.kevent(kevents.to_slice) do
+ raise RuntimeError.from_errno("kevent")
+ end
+ end
+
+ private def system_set_timer(time : Time::Span?) : Nil
+ if time
+ flags = LibC::EV_ADD | LibC::EV_ONESHOT | LibC::EV_CLEAR
+
+ seconds, nanoseconds = System::Time.monotonic
+ now = Time::Span.new(seconds: seconds, nanoseconds: nanoseconds)
+ t = time - now
+
+ data =
+ {% if LibC.has_constant?(:NOTE_NSECONDS) %}
+ t.total_nanoseconds.to_i64!.clamp(0..)
+ {% else %}
+ # legacy BSD (and DragonFly) only have millisecond precision
+ t.positive? ? t.total_milliseconds.to_i64!.clamp(1..) : 0
+ {% end %}
+ else
+ flags = LibC::EV_DELETE
+ data = 0_u64
+ end
+
+ fflags =
+ {% if LibC.has_constant?(:NOTE_NSECONDS) %}
+ LibC::NOTE_NSECONDS
+ {% else %}
+ 0
+ {% end %}
+
+ @kqueue.kevent(TIMER_IDENTIFIER, LibC::EVFILT_TIMER, flags, fflags, data) do
+ raise RuntimeError.from_errno("kevent") unless Errno.value == Errno::ENOENT
+ end
+ end
+end
diff --git a/src/crystal/system/unix/event_loop_libevent.cr b/src/crystal/event_loop/libevent.cr
similarity index 67%
rename from src/crystal/system/unix/event_loop_libevent.cr
rename to src/crystal/event_loop/libevent.cr
index 32c9c8409b17..636d01331624 100644
--- a/src/crystal/system/unix/event_loop_libevent.cr
+++ b/src/crystal/event_loop/libevent.cr
@@ -1,8 +1,11 @@
-require "./event_libevent"
+require "./libevent/event"
# :nodoc:
-class Crystal::LibEvent::EventLoop < Crystal::EventLoop
- private getter(event_base) { Crystal::LibEvent::Event::Base.new }
+class Crystal::EventLoop::LibEvent < Crystal::EventLoop
+ private getter(event_base) { Crystal::EventLoop::LibEvent::Event::Base.new }
+
+ def after_fork_before_exec : Nil
+ end
{% unless flag?(:preview_mt) %}
# Reinitializes the event loop after a fork.
@@ -12,7 +15,9 @@ class Crystal::LibEvent::EventLoop < Crystal::EventLoop
{% end %}
def run(blocking : Bool) : Bool
- event_base.loop(once: true, nonblock: !blocking)
+ flags = LibEvent2::EventLoopFlags::Once
+ flags |= blocking ? LibEvent2::EventLoopFlags::NoExitOnEmpty : LibEvent2::EventLoopFlags::NonBlock
+ event_base.loop(flags)
end
def interrupt : Nil
@@ -20,14 +25,14 @@ class Crystal::LibEvent::EventLoop < Crystal::EventLoop
end
# Create a new resume event for a fiber.
- def create_resume_event(fiber : Fiber) : Crystal::EventLoop::Event
+ def create_resume_event(fiber : Fiber) : Crystal::EventLoop::LibEvent::Event
event_base.new_event(-1, LibEvent2::EventFlags::None, fiber) do |s, flags, data|
data.as(Fiber).enqueue
end
end
# Creates a timeout_event.
- def create_timeout_event(fiber) : Crystal::EventLoop::Event
+ def create_timeout_event(fiber) : Crystal::EventLoop::LibEvent::Event
event_base.new_event(-1, LibEvent2::EventFlags::None, fiber) do |s, flags, data|
f = data.as(Fiber)
if (select_action = f.timeout_select_action)
@@ -70,7 +75,7 @@ class Crystal::LibEvent::EventLoop < Crystal::EventLoop
end
def read(file_descriptor : Crystal::System::FileDescriptor, slice : Bytes) : Int32
- file_descriptor.evented_read("Error reading file_descriptor") do
+ evented_read(file_descriptor, "Error reading file_descriptor") do
LibC.read(file_descriptor.fd, slice, slice.size).tap do |return_code|
if return_code == -1 && Errno.value == Errno::EBADF
raise IO::Error.new "File not open for reading", target: file_descriptor
@@ -79,8 +84,14 @@ class Crystal::LibEvent::EventLoop < Crystal::EventLoop
end
end
+ def wait_readable(file_descriptor : Crystal::System::FileDescriptor) : Nil
+ file_descriptor.evented_wait_readable(raise_if_closed: false) do
+ raise IO::TimeoutError.new("Read timed out")
+ end
+ end
+
def write(file_descriptor : Crystal::System::FileDescriptor, slice : Bytes) : Int32
- file_descriptor.evented_write("Error writing file_descriptor") do
+ evented_write(file_descriptor, "Error writing file_descriptor") do
LibC.write(file_descriptor.fd, slice, slice.size).tap do |return_code|
if return_code == -1 && Errno.value == Errno::EBADF
raise IO::Error.new "File not open for writing", target: file_descriptor
@@ -89,22 +100,40 @@ class Crystal::LibEvent::EventLoop < Crystal::EventLoop
end
end
+ def wait_writable(file_descriptor : Crystal::System::FileDescriptor) : Nil
+ file_descriptor.evented_wait_writable do
+ raise IO::TimeoutError.new("Write timed out")
+ end
+ end
+
def close(file_descriptor : Crystal::System::FileDescriptor) : Nil
file_descriptor.evented_close
end
def read(socket : ::Socket, slice : Bytes) : Int32
- socket.evented_read("Error reading socket") do
+ evented_read(socket, "Error reading socket") do
LibC.recv(socket.fd, slice, slice.size, 0).to_i32
end
end
+ def wait_readable(socket : ::Socket) : Nil
+ socket.evented_wait_readable(raise_if_closed: false) do
+ raise IO::TimeoutError.new("Read timed out")
+ end
+ end
+
def write(socket : ::Socket, slice : Bytes) : Int32
- socket.evented_write("Error writing to socket") do
+ evented_write(socket, "Error writing to socket") do
LibC.send(socket.fd, slice, slice.size, 0).to_i32
end
end
+ def wait_writable(socket : ::Socket) : Nil
+ socket.evented_wait_writable do
+ raise IO::TimeoutError.new("Write timed out")
+ end
+ end
+
def receive_from(socket : ::Socket, slice : Bytes) : Tuple(Int32, ::Socket::Address)
sockaddr = Pointer(LibC::SockaddrStorage).malloc.as(LibC::Sockaddr*)
# initialize sockaddr with the initialized family of the socket
@@ -114,7 +143,7 @@ class Crystal::LibEvent::EventLoop < Crystal::EventLoop
addrlen = LibC::SocklenT.new(sizeof(LibC::SockaddrStorage))
- bytes_read = socket.evented_read("Error receiving datagram") do
+ bytes_read = evented_read(socket, "Error receiving datagram") do
LibC.recvfrom(socket.fd, slice, slice.size, 0, sockaddr, pointerof(addrlen))
end
@@ -137,7 +166,7 @@ class Crystal::LibEvent::EventLoop < Crystal::EventLoop
when Errno::EISCONN
return
when Errno::EINPROGRESS, Errno::EALREADY
- socket.wait_writable(timeout: timeout) do
+ socket.evented_wait_writable(timeout: timeout) do
return IO::TimeoutError.new("connect timed out")
end
else
@@ -169,7 +198,7 @@ class Crystal::LibEvent::EventLoop < Crystal::EventLoop
if socket.closed?
return
elsif Errno.value == Errno::EAGAIN
- socket.wait_readable(raise_if_closed: false) do
+ socket.evented_wait_readable(raise_if_closed: false) do
raise IO::TimeoutError.new("Accept timed out")
end
return if socket.closed?
@@ -185,4 +214,45 @@ class Crystal::LibEvent::EventLoop < Crystal::EventLoop
def close(socket : ::Socket) : Nil
socket.evented_close
end
+
+ def evented_read(target, errno_msg : String, &) : Int32
+ loop do
+ bytes_read = yield
+ if bytes_read != -1
+ # `to_i32` is acceptable because `Slice#size` is an Int32
+ return bytes_read.to_i32
+ end
+
+ if Errno.value == Errno::EAGAIN
+ target.evented_wait_readable do
+ raise IO::TimeoutError.new("Read timed out")
+ end
+ else
+ raise IO::Error.from_errno(errno_msg, target: target)
+ end
+ end
+ ensure
+ target.evented_resume_pending_readers
+ end
+
+ def evented_write(target, errno_msg : String, &) : Int32
+ begin
+ loop do
+ bytes_written = yield
+ if bytes_written != -1
+ return bytes_written.to_i32
+ end
+
+ if Errno.value == Errno::EAGAIN
+ target.evented_wait_writable do
+ raise IO::TimeoutError.new("Write timed out")
+ end
+ else
+ raise IO::Error.from_errno(errno_msg, target: target)
+ end
+ end
+ ensure
+ target.evented_resume_pending_writers
+ end
+ end
end
diff --git a/src/crystal/system/unix/event_libevent.cr b/src/crystal/event_loop/libevent/event.cr
similarity index 77%
rename from src/crystal/system/unix/event_libevent.cr
rename to src/crystal/event_loop/libevent/event.cr
index 21d6765646d1..084ba30bb1d2 100644
--- a/src/crystal/system/unix/event_libevent.cr
+++ b/src/crystal/event_loop/libevent/event.cr
@@ -5,7 +5,7 @@ require "./lib_event2"
{% end %}
# :nodoc:
-module Crystal::LibEvent
+class Crystal::EventLoop::LibEvent < Crystal::EventLoop
struct Event
include Crystal::EventLoop::Event
@@ -19,16 +19,16 @@ module Crystal::LibEvent
@freed = false
end
- def add(timeout : Time::Span?) : Nil
- if timeout
- timeval = LibC::Timeval.new(
- tv_sec: LibC::TimeT.new(timeout.total_seconds),
- tv_usec: timeout.nanoseconds // 1_000
- )
- LibEvent2.event_add(@event, pointerof(timeval))
- else
- LibEvent2.event_add(@event, nil)
- end
+ def add(timeout : Time::Span) : Nil
+ timeval = LibC::Timeval.new(
+ tv_sec: LibC::TimeT.new(timeout.total_seconds),
+ tv_usec: timeout.nanoseconds // 1_000
+ )
+ LibEvent2.event_add(@event, pointerof(timeval))
+ end
+
+ def add(timeout : Nil) : Nil
+ LibEvent2.event_add(@event, nil)
end
def free : Nil
@@ -56,15 +56,12 @@ module Crystal::LibEvent
def new_event(s : Int32, flags : LibEvent2::EventFlags, data, &callback : LibEvent2::Callback)
event = LibEvent2.event_new(@base, s, flags, callback, data.as(Void*))
- Crystal::LibEvent::Event.new(event)
+ LibEvent::Event.new(event)
end
# NOTE: may return `true` even if no event has been triggered (e.g.
# nonblocking), but `false` means that nothing was processed.
- def loop(once : Bool, nonblock : Bool) : Bool
- flags = LibEvent2::EventLoopFlags::None
- flags |= LibEvent2::EventLoopFlags::Once if once
- flags |= LibEvent2::EventLoopFlags::NonBlock if nonblock
+ def loop(flags : LibEvent2::EventLoopFlags) : Bool
LibEvent2.event_base_loop(@base, flags) == 0
end
diff --git a/src/crystal/system/unix/lib_event2.cr b/src/crystal/event_loop/libevent/lib_event2.cr
similarity index 91%
rename from src/crystal/system/unix/lib_event2.cr
rename to src/crystal/event_loop/libevent/lib_event2.cr
index 2cd3e4635194..98280f407df3 100644
--- a/src/crystal/system/unix/lib_event2.cr
+++ b/src/crystal/event_loop/libevent/lib_event2.cr
@@ -7,6 +7,11 @@ require "c/netdb"
@[Link("rt")]
{% end %}
+# Supported library versions:
+#
+# * libevent2
+#
+# See https://crystal-lang.org/reference/man/required_libraries.html#other-runtime-libraries
{% if flag?(:openbsd) %}
@[Link("event_core")]
@[Link("event_extra")]
@@ -26,8 +31,9 @@ lib LibEvent2
@[Flags]
enum EventLoopFlags
- Once = 0x01
- NonBlock = 0x02
+ Once = 0x01
+ NonBlock = 0x02
+ NoExitOnEmpty = 0x04
end
@[Flags]
diff --git a/src/crystal/event_loop/polling.cr b/src/crystal/event_loop/polling.cr
new file mode 100644
index 000000000000..4df9eff7bc8e
--- /dev/null
+++ b/src/crystal/event_loop/polling.cr
@@ -0,0 +1,557 @@
+# forward declaration for the require below to not create a module
+abstract class Crystal::EventLoop::Polling < Crystal::EventLoop; end
+
+require "./polling/*"
+require "./timers"
+
+module Crystal::System::FileDescriptor
+ # user data (generation index for the arena)
+ property __evloop_data : EventLoop::Polling::Arena::Index = EventLoop::Polling::Arena::INVALID_INDEX
+end
+
+module Crystal::System::Socket
+ # user data (generation index for the arena)
+ property __evloop_data : EventLoop::Polling::Arena::Index = EventLoop::Polling::Arena::INVALID_INDEX
+end
+
+# Polling EventLoop.
+#
+# This is the abstract interface that implements `Crystal::EventLoop` for
+# polling based UNIX targets, such as epoll (linux), kqueue (bsd), or poll
+# (posix) syscalls. This class only implements the generic parts for the
+# external world to interact with the loop. A specific implementation is
+# required to handle the actual syscalls. See `Crystal::Epoll::EventLoop` and
+# `Crystal::Kqueue::EventLoop`.
+#
+# The event loop registers the fd into the kernel data structures when an IO
+# operation would block, then keeps it there until the fd is closed.
+#
+# NOTE: the fds must have `O_NONBLOCK` set.
+#
+# It is possible to have multiple event loop instances, but an fd can only be in
+# one instance at a time. When trying to block from another loop, the fd will be
+# removed from its associated loop and added to the current one (this is
+# automatic). Trying to move a fd to another loop with pending waiters is
+# unsupported and will raise an exception. See `PollDescriptor#remove`.
+#
+# A timed event such as sleep or select timeout follows the following logic:
+#
+# 1. create an `Event` (actually reuses it, see `FiberChannel`);
+# 2. register the event in `@timers`;
+# 3. supend the current fiber.
+#
+# The timer will eventually trigger and resume the fiber.
+# When an IO operation on fd would block, the loop follows the following logic:
+#
+# 1. register the fd (once);
+# 2. create an `Event`;
+# 3. suspend the current fiber;
+#
+# When the IO operation is ready, the fiber will eventually be resumed (one
+# fiber at a time). If it's an IO operation, the operation is tried again which
+# may block again, until the operation succeeds or an error occured (e.g.
+# closed, broken pipe).
+#
+# If the IO operation has a timeout, the event is also registered into `@timers`
+# before suspending the fiber, then after resume it will raise
+# `IO::TimeoutError` if the event timed out, and continue otherwise.
+abstract class Crystal::EventLoop::Polling < Crystal::EventLoop
+ # The generational arena:
+ #
+ # 1. decorrelates the fd from the IO since the evloop only really cares about
+ # the fd state and to resume pending fibers (it could monitor a fd without
+ # an IO object);
+ #
+ # 2. permits to avoid pushing raw pointers to IO objects into kernel data
+ # structures that are unknown to the GC, and to safely check whether the
+ # allocation is still valid before trying to dereference the pointer. Since
+ # `PollDescriptor` also doesn't have pointers to the actual IO object, it
+ # won't prevent the GC from collecting lost IO objects (and spares us from
+ # using weak references).
+ #
+ # 3. to a lesser extent, it also allows to keep the `PollDescriptor` allocated
+ # together in the same region, and polluting the IO object itself with
+ # specific evloop data (except for the generation index).
+ #
+ # The implementation takes advantage of the fd being unique per process and
+ # that the operating system will always reuse the lowest fd (POSIX compliance)
+ # and will only grow when the process needs that many file descriptors, so the
+ # allocated memory region won't grow larger than necessary. This assumption
+ # allows the arena to skip maintaining a list of free indexes. Some systems
+ # may deviate from the POSIX default, but all systems seem to follow it, as it
+ # allows optimizations to the OS (it can reuse already allocated resources),
+ # and either the man page explicitly says so (Linux), or they don't (BSD) and
+ # they must follow the POSIX definition.
+ #
+ # The block size is set to 64KB because it's a multiple of:
+ # - 4KB (usual page size)
+ # - 1024 (common soft limit for open files)
+ # - sizeof(Arena::Entry(PollDescriptor))
+ protected class_getter arena = Arena(PollDescriptor, 65536).new(max_fds)
+
+ private def self.max_fds : Int32
+ if LibC.getrlimit(LibC::RLIMIT_NOFILE, out rlimit) == -1
+ raise RuntimeError.from_errno("getrlimit(RLIMIT_NOFILE)")
+ end
+ rlimit.rlim_max.clamp(..Int32::MAX).to_i32!
+ end
+
+ @lock = SpinLock.new # protects parallel accesses to @timers
+ @timers = Timers(Event).new
+
+ # reset the mutexes since another thread may have acquired the lock of one
+ # event loop, which would prevent closing file descriptors for example.
+ def after_fork_before_exec : Nil
+ @lock = SpinLock.new
+ end
+
+ {% unless flag?(:preview_mt) %}
+ # no parallelism issues, but let's clean-up anyway
+ def after_fork : Nil
+ @lock = SpinLock.new
+ end
+ {% end %}
+
+ # NOTE: thread unsafe
+ def run(blocking : Bool) : Bool
+ system_run(blocking) do |fiber|
+ Crystal::Scheduler.enqueue(fiber)
+ end
+ true
+ end
+
+ # fiber interface, see Crystal::EventLoop
+
+ def create_resume_event(fiber : Fiber) : FiberEvent
+ FiberEvent.new(:sleep, fiber)
+ end
+
+ def create_timeout_event(fiber : Fiber) : FiberEvent
+ FiberEvent.new(:select_timeout, fiber)
+ end
+
+ # file descriptor interface, see Crystal::EventLoop::FileDescriptor
+
+ def read(file_descriptor : System::FileDescriptor, slice : Bytes) : Int32
+ size = evented_read(file_descriptor, slice, file_descriptor.@read_timeout)
+
+ if size == -1
+ if Errno.value == Errno::EBADF
+ raise IO::Error.new("File not open for reading", target: file_descriptor)
+ else
+ raise IO::Error.from_errno("read", target: file_descriptor)
+ end
+ else
+ size.to_i32
+ end
+ end
+
+ def wait_readable(file_descriptor : System::FileDescriptor) : Nil
+ wait_readable(file_descriptor, file_descriptor.@read_timeout) do
+ raise IO::TimeoutError.new
+ end
+ end
+
+ def write(file_descriptor : System::FileDescriptor, slice : Bytes) : Int32
+ size = evented_write(file_descriptor, slice, file_descriptor.@write_timeout)
+
+ if size == -1
+ if Errno.value == Errno::EBADF
+ raise IO::Error.new("File not open for writing", target: file_descriptor)
+ else
+ raise IO::Error.from_errno("write", target: file_descriptor)
+ end
+ else
+ size.to_i32
+ end
+ end
+
+ def wait_writable(file_descriptor : System::FileDescriptor) : Nil
+ wait_writable(file_descriptor, file_descriptor.@write_timeout) do
+ raise IO::TimeoutError.new
+ end
+ end
+
+ def close(file_descriptor : System::FileDescriptor) : Nil
+ evented_close(file_descriptor)
+ end
+
+ protected def self.remove_impl(file_descriptor : System::FileDescriptor) : Nil
+ internal_remove(file_descriptor)
+ end
+
+ # socket interface, see Crystal::EventLoop::Socket
+
+ def read(socket : ::Socket, slice : Bytes) : Int32
+ size = evented_read(socket, slice, socket.@read_timeout)
+ raise IO::Error.from_errno("read", target: socket) if size == -1
+ size
+ end
+
+ def wait_readable(socket : ::Socket) : Nil
+ wait_readable(socket, socket.@read_timeout) do
+ raise IO::TimeoutError.new
+ end
+ end
+
+ def write(socket : ::Socket, slice : Bytes) : Int32
+ size = evented_write(socket, slice, socket.@write_timeout)
+ raise IO::Error.from_errno("write", target: socket) if size == -1
+ size
+ end
+
+ def wait_writable(socket : ::Socket) : Nil
+ wait_writable(socket, socket.@write_timeout) do
+ raise IO::TimeoutError.new
+ end
+ end
+
+ def accept(socket : ::Socket) : ::Socket::Handle?
+ loop do
+ client_fd =
+ {% if LibC.has_method?(:accept4) %}
+ LibC.accept4(socket.fd, nil, nil, LibC::SOCK_CLOEXEC)
+ {% else %}
+ # we may fail to set FD_CLOEXEC between `accept` and `fcntl` but we
+ # can't call `Crystal::System::Socket.lock_read` because the socket
+ # might be in blocking mode and accept would block until the socket
+ # receives a connection.
+ #
+ # we could lock when `socket.blocking?` is false, but another thread
+ # could change the socket back to blocking mode between the condition
+ # check and the `accept` call.
+ LibC.accept(socket.fd, nil, nil).tap do |fd|
+ System::Socket.fcntl(fd, LibC::F_SETFD, LibC::FD_CLOEXEC) unless fd == -1
+ end
+ {% end %}
+
+ return client_fd unless client_fd == -1
+ return if socket.closed?
+
+ if Errno.value == Errno::EAGAIN
+ wait_readable(socket, socket.@read_timeout) do
+ raise IO::TimeoutError.new("Accept timed out")
+ end
+ return if socket.closed?
+ else
+ raise ::Socket::Error.from_errno("accept")
+ end
+ end
+ end
+
+ def connect(socket : ::Socket, address : ::Socket::Addrinfo | ::Socket::Address, timeout : Time::Span?) : IO::Error?
+ loop do
+ ret = LibC.connect(socket.fd, address, address.size)
+ return unless ret == -1
+
+ case Errno.value
+ when Errno::EISCONN
+ return
+ when Errno::EINPROGRESS, Errno::EALREADY
+ wait_writable(socket, timeout) do
+ return IO::TimeoutError.new("Connect timed out")
+ end
+ else
+ return ::Socket::ConnectError.from_errno("connect")
+ end
+ end
+ end
+
+ def send_to(socket : ::Socket, slice : Bytes, address : ::Socket::Address) : Int32
+ bytes_sent = LibC.sendto(socket.fd, slice.to_unsafe.as(Void*), slice.size, 0, address, address.size)
+ raise ::Socket::Error.from_errno("Error sending datagram to #{address}") if bytes_sent == -1
+ bytes_sent.to_i32
+ end
+
+ def receive_from(socket : ::Socket, slice : Bytes) : {Int32, ::Socket::Address}
+ sockaddr = Pointer(LibC::SockaddrStorage).malloc.as(LibC::Sockaddr*)
+
+ # initialize sockaddr with the initialized family of the socket
+ copy = sockaddr.value
+ copy.sa_family = socket.family
+ sockaddr.value = copy
+ addrlen = LibC::SocklenT.new(sizeof(LibC::SockaddrStorage))
+
+ loop do
+ size = LibC.recvfrom(socket.fd, slice, slice.size, 0, sockaddr, pointerof(addrlen))
+ if size == -1
+ if Errno.value == Errno::EAGAIN
+ wait_readable(socket, socket.@read_timeout)
+ check_open(socket)
+ else
+ raise IO::Error.from_errno("recvfrom", target: socket)
+ end
+ else
+ return {size.to_i32, ::Socket::Address.from(sockaddr, addrlen)}
+ end
+ end
+ end
+
+ def close(socket : ::Socket) : Nil
+ evented_close(socket)
+ end
+
+ protected def self.remove_impl(socket : ::Socket) : Nil
+ internal_remove(socket)
+ end
+
+ # internals: IO
+
+ private def evented_read(io, slice : Bytes, timeout : Time::Span?) : Int32
+ loop do
+ ret = LibC.read(io.fd, slice, slice.size)
+ if ret == -1 && Errno.value == Errno::EAGAIN
+ wait_readable(io, timeout)
+ check_open(io)
+ else
+ return ret.to_i
+ end
+ end
+ end
+
+ private def evented_write(io, slice : Bytes, timeout : Time::Span?) : Int32
+ loop do
+ ret = LibC.write(io.fd, slice, slice.size)
+ if ret == -1 && Errno.value == Errno::EAGAIN
+ wait_writable(io, timeout)
+ check_open(io)
+ else
+ return ret.to_i
+ end
+ end
+ end
+
+ protected def evented_close(io)
+ return unless (index = io.__evloop_data).valid?
+
+ Polling.arena.free(index) do |pd|
+ pd.value.@readers.ready_all do |event|
+ pd.value.@event_loop.try(&.unsafe_resume_io(event) do |fiber|
+ Crystal::Scheduler.enqueue(fiber)
+ end)
+ end
+
+ pd.value.@writers.ready_all do |event|
+ pd.value.@event_loop.try(&.unsafe_resume_io(event) do |fiber|
+ Crystal::Scheduler.enqueue(fiber)
+ end)
+ end
+
+ pd.value.remove(io.fd)
+ end
+ end
+
+ private def self.internal_remove(io)
+ return unless (index = io.__evloop_data).valid?
+
+ Polling.arena.free(index) do |pd|
+ pd.value.remove(io.fd) { } # ignore system error
+ end
+ end
+
+ private def wait_readable(io, timeout = nil) : Nil
+ wait_readable(io, timeout) do
+ raise IO::TimeoutError.new("Read timed out")
+ end
+ end
+
+ private def wait_writable(io, timeout = nil) : Nil
+ wait_writable(io, timeout) do
+ raise IO::TimeoutError.new("Write timed out")
+ end
+ end
+
+ private def wait_readable(io, timeout = nil, &) : Nil
+ yield if wait(:io_read, io, timeout) do |pd, event|
+ # don't wait if the waiter has already been marked ready (see Waiters#add)
+ return unless pd.value.@readers.add(event)
+ end
+ end
+
+ private def wait_writable(io, timeout = nil, &) : Nil
+ yield if wait(:io_write, io, timeout) do |pd, event|
+ # don't wait if the waiter has already been marked ready (see Waiters#add)
+ return unless pd.value.@writers.add(event)
+ end
+ end
+
+ private def wait(type : Polling::Event::Type, io, timeout, &)
+ # prepare event (on the stack); we can't initialize it properly until we get
+ # the arena index below; we also can't use a nilable since `pointerof` would
+ # point to the union, not the event
+ event = uninitialized Event
+
+ # add the event to the waiting list; in case we can't access or allocate the
+ # poll descriptor into the arena, we merely return to let the caller handle
+ # the situation (maybe the IO got closed?)
+ if (index = io.__evloop_data).valid?
+ event = Event.new(type, Fiber.current, index, timeout)
+
+ return false unless Polling.arena.get?(index) do |pd|
+ yield pd, pointerof(event)
+ end
+ else
+ # OPTIMIZE: failing to allocate may be a simple conflict with 2 fibers
+ # starting to read or write on the same fd, we may want to detect any
+ # error situation instead of returning and retrying a syscall
+ return false unless Polling.arena.allocate_at?(io.fd) do |pd, index|
+ # register the fd with the event loop (once), it should usually merely add
+ # the fd to the current evloop but may "transfer" the ownership from
+ # another event loop:
+ io.__evloop_data = index
+ pd.value.take_ownership(self, io.fd, index)
+
+ event = Event.new(type, Fiber.current, index, timeout)
+ yield pd, pointerof(event)
+ end
+ end
+
+ if event.wake_at?
+ add_timer(pointerof(event))
+
+ Fiber.suspend
+
+ # no need to delete the timer: either it triggered which means it was
+ # dequeued, or `#unsafe_resume_io` was called to resume the IO and the
+ # timer got deleted from the timers before the fiber got reenqueued.
+ return event.timed_out?
+ end
+
+ Fiber.suspend
+ false
+ end
+
+ private def check_open(io : IO)
+ raise IO::Error.new("Closed stream") if io.closed?
+ end
+
+ # internals: timers
+
+ protected def add_timer(event : Event*)
+ @lock.sync do
+ is_next_ready = @timers.add(event)
+ system_set_timer(event.value.wake_at) if is_next_ready
+ end
+ end
+
+ protected def delete_timer(event : Event*) : Bool
+ @lock.sync do
+ dequeued, was_next_ready = @timers.delete(event)
+ # update system timer if we deleted the next timer
+ system_set_timer(@timers.next_ready?) if was_next_ready
+ dequeued
+ end
+ end
+
+ # Helper to resume the fiber associated to an IO event and remove the event
+ # from timers if applicable. Returns true if the fiber has been enqueued.
+ #
+ # Thread unsafe: we must hold the poll descriptor waiter lock for the whole
+ # duration of the dequeue/resume_io otherwise we might conflict with timers
+ # trying to cancel an IO event.
+ protected def unsafe_resume_io(event : Event*, &) : Bool
+ # we only partially own the poll descriptor; thanks to the lock we know that
+ # another thread won't dequeue it, yet it may still be in the timers queue,
+ # which at worst may be waiting on the lock to be released, so event* can be
+ # dereferenced safely.
+
+ if !event.value.wake_at? || delete_timer(event)
+ # no timeout or we canceled it: we fully own the event
+ yield event.value.fiber
+ true
+ else
+ # failed to cancel the timeout so the timer owns the event (by rule)
+ false
+ end
+ end
+
+ # Process ready timers.
+ #
+ # Shall be called after processing IO events. IO events with a timeout that
+ # have succeeded shall already have been removed from `@timers` otherwise the
+ # fiber could be resumed twice!
+ private def process_timers(timer_triggered : Bool, &) : Nil
+ # collect ready timers before processing them —this is safe— to avoids a
+ # deadlock situation when another thread tries to process a ready IO event
+ # (in poll descriptor waiters) with a timeout (same event* in timers)
+ buffer = uninitialized StaticArray(Pointer(Event), 128)
+ size = 0
+
+ @lock.sync do
+ @timers.dequeue_ready do |event|
+ buffer.to_unsafe[size] = event
+ break if (size &+= 1) == buffer.size
+ end
+
+ if size > 0 || timer_triggered
+ system_set_timer(@timers.next_ready?)
+ end
+ end
+
+ buffer.to_slice[0, size].each do |event|
+ process_timer(event) { |fiber| yield fiber }
+ end
+ end
+
+ private def process_timer(event : Event*, &)
+ # we dequeued the event from timers, and by rule we own it, so event* can
+ # safely be dereferenced:
+ fiber = event.value.fiber
+
+ case event.value.type
+ when .io_read?
+ # reached read timeout: cancel io event; by rule the timer always wins,
+ # even in case of conflict with #unsafe_resume_io we must resume the fiber
+ Polling.arena.get?(event.value.index) { |pd| pd.value.@readers.delete(event) }
+ event.value.timed_out!
+ when .io_write?
+ # reached write timeout: cancel io event; by rule the timer always wins,
+ # even in case of conflict with #unsafe_resume_io we must resume the fiber
+ Polling.arena.get?(event.value.index) { |pd| pd.value.@writers.delete(event) }
+ event.value.timed_out!
+ when .select_timeout?
+ # always dequeue the event but only enqueue the fiber if we win the
+ # atomic CAS
+ return unless select_action = fiber.timeout_select_action
+ fiber.timeout_select_action = nil
+ return unless select_action.time_expired?
+ fiber.@timeout_event.as(FiberEvent).clear
+ when .sleep?
+ # cleanup
+ fiber.@resume_event.as(FiberEvent).clear
+ else
+ raise RuntimeError.new("BUG: unexpected event in timers: #{event.value}%s\n")
+ end
+
+ yield fiber
+ end
+
+ # internals: system
+
+ # Process ready events and timers.
+ #
+ # The loop must always process ready events and timers before returning. When
+ # *blocking* is `true` the loop must wait for events to become ready (possibly
+ # indefinitely); when `false` the loop shall return immediately.
+ #
+ # The `PollDescriptor` of IO events can be retrieved using the *index*
+ # from the system event's user data.
+ private abstract def system_run(blocking : Bool, & : Fiber ->) : Nil
+
+ # Add *fd* to the polling system, setting *index* as user data.
+ protected abstract def system_add(fd : Int32, index : Arena::Index) : Nil
+
+ # Remove *fd* from the polling system. Must raise a `RuntimeError` on error.
+ #
+ # If *closing* is true, then it preceeds a call to `close(2)`. Some
+ # implementations may take advantage of close doing the book keeping.
+ #
+ # If *closing* is false then the fd must be deleted from the polling system.
+ protected abstract def system_del(fd : Int32, closing = true) : Nil
+
+ # Identical to `#system_del` but yields on error.
+ protected abstract def system_del(fd : Int32, closing = true, &) : Nil
+
+ # Arm a timer to interrupt a run at *time*. Set to `nil` to disarm the timer.
+ private abstract def system_set_timer(time : Time::Span?) : Nil
+end
diff --git a/src/crystal/event_loop/polling/arena.cr b/src/crystal/event_loop/polling/arena.cr
new file mode 100644
index 000000000000..a7bcb181a66f
--- /dev/null
+++ b/src/crystal/event_loop/polling/arena.cr
@@ -0,0 +1,247 @@
+# Generational Arena.
+#
+# The arena allocates objects `T` at a predefined index. The object iself is
+# uninitialized (outside of having its memory initialized to zero). The object
+# can be allocated and later retrieved using the generation index (Arena::Index)
+# that contains both the actual index (Int32) and the generation number
+# (UInt32). Deallocating the object increases the generation number, which
+# allows the object to be reallocated later on. Trying to retrieve the
+# allocation using the generation index will fail if the generation number
+# changed (it's a new allocation).
+#
+# This arena isn't generic as it won't keep a list of free indexes. It assumes
+# that something else will maintain the uniqueness of indexes and reuse indexes
+# as much as possible instead of growing.
+#
+# For example this arena is used to hold `Crystal::EventLoop::Polling::PollDescriptor`
+# allocations for all the fd in a program, where the fd is used as the index.
+# They're unique to the process and the OS always reuses the lowest fd numbers
+# before growing.
+#
+# Thread safety: the memory region is divided in blocks of size BLOCK_BYTESIZE
+# allocated in the GC. Pointers are thus never invalidated. Mutating the blocks
+# is protected by a mutual exclusion lock. Individual (de)allocations of objects
+# are protected with a fine grained lock.
+#
+# Guarantees: blocks' memory is initialized to zero, which means `T` objects are
+# initialized to zero by default, then `#free` will also clear the memory, so
+# the next allocation shall be initialized to zero, too.
+class Crystal::EventLoop::Polling::Arena(T, BLOCK_BYTESIZE)
+ INVALID_INDEX = Index.new(-1, 0)
+
+ struct Index
+ def initialize(index : Int32, generation : UInt32)
+ @data = (index.to_i64! << 32) | generation.to_u64!
+ end
+
+ def initialize(@data : Int64)
+ end
+
+ def initialize(data : UInt64)
+ @data = data.to_i64!
+ end
+
+ # Returns the generation number.
+ def generation : UInt32
+ @data.to_u32!
+ end
+
+ # Returns the actual index.
+ def index : Int32
+ (@data >> 32).to_i32!
+ end
+
+ def to_i64 : Int64
+ @data
+ end
+
+ def to_u64 : UInt64
+ @data.to_u64!
+ end
+
+ def valid? : Bool
+ @data >= 0
+ end
+ end
+
+ struct Entry(T)
+ @lock = SpinLock.new # protects parallel allocate/free calls
+ property? allocated = false
+ property generation = 0_u32
+ @object = uninitialized T
+
+ def pointer : Pointer(T)
+ pointerof(@object)
+ end
+
+ def free : Nil
+ @generation &+= 1_u32
+ @allocated = false
+ pointer.clear(1)
+ end
+ end
+
+ @blocks : Slice(Pointer(Entry(T)))
+ @capacity : Int32
+
+ def initialize(@capacity : Int32)
+ @blocks = Slice(Pointer(Entry(T))).new(1) { allocate_block }
+ @mutex = Thread::Mutex.new
+ end
+
+ # Allocates the object at *index* unless already allocated, then yields a
+ # pointer to the object at *index* and the current generation index to later
+ # retrieve and free the allocated object. Eventually returns the generation
+ # index.
+ #
+ # Does nothing if the object has already been allocated and returns `nil`.
+ #
+ # There are no generational checks.
+ # Raises if *index* is out of bounds.
+ def allocate_at?(index : Int32, & : (Pointer(T), Index) ->) : Index?
+ entry = at(index, grow: true)
+
+ entry.value.@lock.sync do
+ return if entry.value.allocated?
+
+ entry.value.allocated = true
+
+ gen_index = Index.new(index, entry.value.generation)
+ yield entry.value.pointer, gen_index
+
+ gen_index
+ end
+ end
+
+ # Same as `#allocate_at?` but raises when already allocated.
+ def allocate_at(index : Int32, & : (Pointer(T), Index) ->) : Index?
+ allocate_at?(index) { |ptr, idx| yield ptr, idx } ||
+ raise RuntimeError.new("#{self.class.name}: already allocated index=#{index}")
+ end
+
+ # Yields a pointer to the object previously allocated at *index*.
+ #
+ # Raises if the object isn't allocated, the generation has changed (i.e. the
+ # object has been freed then reallocated) or *index* is out of bounds.
+ def get(index : Index, &) : Nil
+ at(index) do |entry|
+ yield entry.value.pointer
+ end
+ end
+
+ # Yields a pointer to the object previously allocated at *index* and returns
+ # true.
+ #
+ # Does nothing if the object isn't allocated, the generation has changed or
+ # *index* is out of bounds.
+ def get?(index : Index, &) : Bool
+ at?(index) do |entry|
+ yield entry.value.pointer
+ return true
+ end
+ false
+ end
+
+ # Yields the object previously allocated at *index* then releases it.
+ #
+ # Does nothing if the object isn't allocated, the generation has changed or
+ # *index* is out of bounds.
+ def free(index : Index, &) : Nil
+ at?(index) do |entry|
+ begin
+ yield entry.value.pointer
+ ensure
+ entry.value.free
+ end
+ end
+ end
+
+ private def at(index : Index, &) : Nil
+ entry = at(index.index, grow: false)
+ entry.value.@lock.lock
+
+ unless entry.value.allocated? && entry.value.generation == index.generation
+ entry.value.@lock.unlock
+ raise RuntimeError.new("#{self.class.name}: invalid reference index=#{index.index}:#{index.generation} current=#{index.index}:#{entry.value.generation}")
+ end
+
+ begin
+ yield entry
+ ensure
+ entry.value.@lock.unlock
+ end
+ end
+
+ private def at?(index : Index, &) : Nil
+ return unless entry = at?(index.index)
+
+ entry.value.@lock.sync do
+ return unless entry.value.allocated?
+ return unless entry.value.generation == index.generation
+
+ yield entry
+ end
+ end
+
+ private def at(index : Int32, grow : Bool) : Pointer(Entry(T))
+ raise IndexError.new unless 0 <= index < @capacity
+
+ n, j = index.divmod(entries_per_block)
+
+ if n >= @blocks.size
+ raise RuntimeError.new("#{self.class.name}: not allocated index=#{index}") unless grow
+ @mutex.synchronize { unsafe_grow(n) if n >= @blocks.size }
+ end
+
+ @blocks.to_unsafe[n] + j
+ end
+
+ private def at?(index : Int32) : Pointer(Entry(T))?
+ return unless 0 <= index < @capacity
+
+ n, j = index.divmod(entries_per_block)
+
+ if block = @blocks[n]?
+ block + j
+ end
+ end
+
+ private def unsafe_grow(n)
+ # we manually dup instead of using realloc to avoid parallelism issues, for
+ # example fork or another thread trying to iterate after realloc but before
+ # we got the time to set @blocks or to allocate the new blocks
+ new_size = n + 1
+ new_pointer = GC.malloc(new_size * sizeof(Pointer(Entry(T)))).as(Pointer(Pointer(Entry(T))))
+ @blocks.to_unsafe.copy_to(new_pointer, @blocks.size)
+ @blocks.size.upto(n) { |j| new_pointer[j] = allocate_block }
+
+ @blocks = Slice.new(new_pointer, new_size)
+ end
+
+ private def allocate_block
+ GC.malloc(BLOCK_BYTESIZE).as(Pointer(Entry(T)))
+ end
+
+ # Iterates all allocated objects, yields the actual index as well as the
+ # generation index.
+ def each_index(&) : Nil
+ index = 0
+
+ @blocks.each do |block|
+ entries_per_block.times do |j|
+ entry = block + j
+
+ if entry.value.allocated?
+ yield index, Index.new(index, entry.value.generation)
+ end
+
+ index += 1
+ end
+ end
+ end
+
+ private def entries_per_block
+ # can't be a constant: can't access a generic when assigning a constant
+ BLOCK_BYTESIZE // sizeof(Entry(T))
+ end
+end
diff --git a/src/crystal/event_loop/polling/event.cr b/src/crystal/event_loop/polling/event.cr
new file mode 100644
index 000000000000..93caf843b049
--- /dev/null
+++ b/src/crystal/event_loop/polling/event.cr
@@ -0,0 +1,66 @@
+require "crystal/pointer_linked_list"
+require "crystal/pointer_pairing_heap"
+
+# Information about the event that a `Fiber` is waiting on.
+#
+# The event can be waiting for `IO` with or without a timeout, or be a timed
+# event such as sleep or a select timeout (without IO).
+#
+# The events can be found in different queues, for example `Timers` and/or
+# `Waiters` depending on their type.
+struct Crystal::EventLoop::Polling::Event
+ enum Type
+ IoRead
+ IoWrite
+ Sleep
+ SelectTimeout
+ end
+
+ getter type : Type
+
+ # The `Fiber` that is waiting on the event and that the `EventLoop` shall
+ # resume.
+ getter fiber : Fiber
+
+ # Arena index to access the associated `PollDescriptor` when processing an IO
+ # event. Nil for timed events (sleep, select timeout).
+ getter! index : Arena::Index?
+
+ # The absolute time, against the monotonic clock, at which a timed event shall
+ # trigger. Nil for IO events without a timeout.
+ getter! wake_at : Time::Span
+
+ # True if an IO event has timed out (i.e. we're past `#wake_at`).
+ getter? timed_out : Bool = false
+
+ # The event can be added to `Waiters` lists.
+ include PointerLinkedList::Node
+
+ # The event can be added to the `Timers` list.
+ include PointerPairingHeap::Node
+
+ def initialize(@type : Type, @fiber, @index = nil, timeout : Time::Span? = nil)
+ if timeout
+ seconds, nanoseconds = System::Time.monotonic
+ now = Time::Span.new(seconds: seconds, nanoseconds: nanoseconds)
+ @wake_at = now + timeout
+ end
+ end
+
+ # Mark the IO event as timed out.
+ def timed_out! : Bool
+ @timed_out = true
+ end
+
+ # Manually set the absolute time (against the monotonic clock). This is meant
+ # for `FiberEvent` to set and cancel its inner sleep or select timeout; these
+ # objects are allocated once per `Fiber`.
+ #
+ # NOTE: musn't be changed after registering the event into `Timers`!
+ def wake_at=(@wake_at)
+ end
+
+ def heap_compare(other : Pointer(self)) : Bool
+ wake_at < other.value.wake_at
+ end
+end
diff --git a/src/crystal/event_loop/polling/fiber_event.cr b/src/crystal/event_loop/polling/fiber_event.cr
new file mode 100644
index 000000000000..10f3e5858e13
--- /dev/null
+++ b/src/crystal/event_loop/polling/fiber_event.cr
@@ -0,0 +1,33 @@
+class Crystal::EventLoop::Polling::FiberEvent
+ include Crystal::EventLoop::Event
+
+ def initialize(type : Event::Type, fiber : Fiber)
+ @event = Event.new(type, fiber)
+ end
+
+ # sleep or select timeout
+ def add(timeout : Time::Span) : Nil
+ seconds, nanoseconds = System::Time.monotonic
+ now = Time::Span.new(seconds: seconds, nanoseconds: nanoseconds)
+ @event.wake_at = now + timeout
+ EventLoop.current.add_timer(pointerof(@event))
+ end
+
+ # select timeout has been cancelled
+ def delete : Nil
+ return unless @event.wake_at?
+
+ EventLoop.current.delete_timer(pointerof(@event))
+ clear
+ end
+
+ # fiber died
+ def free : Nil
+ delete
+ end
+
+ # the timer triggered (already dequeued from eventloop)
+ def clear : Nil
+ @event.wake_at = nil
+ end
+end
diff --git a/src/crystal/event_loop/polling/poll_descriptor.cr b/src/crystal/event_loop/polling/poll_descriptor.cr
new file mode 100644
index 000000000000..801d1b148d89
--- /dev/null
+++ b/src/crystal/event_loop/polling/poll_descriptor.cr
@@ -0,0 +1,48 @@
+# Information related to the evloop for a fd, such as the read and write queues
+# (waiting `Event`), as well as which evloop instance currently owns the fd.
+#
+# Thread-unsafe: parallel mutations must be protected with a lock.
+struct Crystal::EventLoop::Polling::PollDescriptor
+ @event_loop : Polling?
+ @readers = Waiters.new
+ @writers = Waiters.new
+
+ # Makes *event_loop* the new owner of *fd*.
+ # Removes *fd* from the current event loop (if any).
+ def take_ownership(event_loop : EventLoop, fd : Int32, index : Arena::Index) : Nil
+ current = @event_loop
+
+ if event_loop == current
+ raise "BUG: evloop already owns the poll-descriptor for fd=#{fd}"
+ end
+
+ # ensure we can't have cross enqueues after we transfer the fd, so we
+ # can optimize (all enqueues are local) and we don't end up with a timer
+ # from evloop A to cancel an event from evloop B (currently unsafe)
+ if current && !empty?
+ raise RuntimeError.new("BUG: transfering fd=#{fd} to another evloop with pending reader/writer fibers")
+ end
+
+ @event_loop = event_loop
+ event_loop.system_add(fd, index)
+ current.try(&.system_del(fd, closing: false))
+ end
+
+ # Removes *fd* from its owner event loop. Raises on errors.
+ def remove(fd : Int32) : Nil
+ current, @event_loop = @event_loop, nil
+ current.try(&.system_del(fd))
+ end
+
+ # Same as `#remove` but yields on errors.
+ def remove(fd : Int32, &) : Nil
+ current, @event_loop = @event_loop, nil
+ current.try(&.system_del(fd) { yield })
+ end
+
+ # Returns true when there is at least one reader or writer. Returns false
+ # otherwise.
+ def empty? : Bool
+ @readers.@list.empty? && @writers.@list.empty?
+ end
+end
diff --git a/src/crystal/event_loop/polling/waiters.cr b/src/crystal/event_loop/polling/waiters.cr
new file mode 100644
index 000000000000..85d10fd6f5ba
--- /dev/null
+++ b/src/crystal/event_loop/polling/waiters.cr
@@ -0,0 +1,60 @@
+# A FIFO queue of `Event` waiting on the same operation (either read or write)
+# for a fd. See `PollDescriptor`.
+#
+# Race conditions on the state of the waiting list are handled through the ready
+# always ready variables.
+#
+# Thread unsafe: parallel mutations must be protected with a lock.
+struct Crystal::EventLoop::Polling::Waiters
+ @list = PointerLinkedList(Event).new
+ @ready = false
+ @always_ready = false
+
+ # Adds an event to the waiting list. May return false immediately if another
+ # thread marked the list as ready in parallel, returns true otherwise.
+ def add(event : Pointer(Event)) : Bool
+ if @always_ready
+ # another thread closed the fd or we received a fd error or hup event:
+ # the fd will never block again
+ return false
+ end
+
+ if @ready
+ # another thread readied the fd before the current thread got to add
+ # the event: don't block and resets @ready for the next loop
+ @ready = false
+ return false
+ end
+
+ @list.push(event)
+ true
+ end
+
+ def delete(event : Pointer(Event)) : Nil
+ @list.delete(event) if event.value.next
+ end
+
+ # Removes one pending event or marks the list as ready when there are no
+ # pending events (we got notified of readiness before a thread enqueued).
+ def ready_one(& : Pointer(Event) -> Bool) : Nil
+ # loop until the block succesfully processes an event (it may have to
+ # dequeue the timeout from timers)
+ loop do
+ if event = @list.shift?
+ break if yield event
+ else
+ # no event queued but another thread may be waiting for the lock to
+ # add an event: set as ready to resolve the race condition
+ @ready = true
+ return
+ end
+ end
+ end
+
+ # Dequeues all pending events and marks the list as always ready. This must be
+ # called when a fd is closed or an error or hup event occurred.
+ def ready_all(& : Pointer(Event) ->) : Nil
+ @list.consume_each { |event| yield event }
+ @always_ready = true
+ end
+end
diff --git a/src/crystal/system/event_loop/socket.cr b/src/crystal/event_loop/socket.cr
similarity index 74%
rename from src/crystal/system/event_loop/socket.cr
rename to src/crystal/event_loop/socket.cr
index e6f35478b487..2e3679e615c5 100644
--- a/src/crystal/system/event_loop/socket.cr
+++ b/src/crystal/event_loop/socket.cr
@@ -1,4 +1,4 @@
-# This file is only required when sockets are used (`require "./event_loop/socket"` in `src/crystal/system/socket.cr`)
+# This file is only required when sockets are used (`require "crystal/event_loop/socket"` in `src/crystal/system/socket.cr`)
#
# It fills `Crystal::EventLoop::Socket` with abstract defs.
@@ -12,9 +12,12 @@ abstract class Crystal::EventLoop
# Returns the number of bytes read (up to `slice.size`).
# Returns 0 when the socket is closed and no data available.
#
- # Use `#send_to` for sending a message to a specific target address.
+ # Use `#receive_from` for capturing the source address of a message.
abstract def read(socket : ::Socket, slice : Bytes) : Int32
+ # Blocks the current fiber until the socket is ready for read.
+ abstract def wait_readable(socket : ::Socket) : Nil
+
# Writes at least one byte from *slice* to the socket.
#
# Blocks the current fiber if the socket is not ready for writing,
@@ -22,9 +25,12 @@ abstract class Crystal::EventLoop
#
# Returns the number of bytes written (up to `slice.size`).
#
- # Use `#receive_from` for capturing the source address of a message.
+ # Use `#send_to` for sending a message to a specific target address.
abstract def write(socket : ::Socket, slice : Bytes) : Int32
+ # Blocks the current fiber until the socket is ready for write.
+ abstract def wait_writable(socket : ::Socket) : Nil
+
# Accepts an incoming TCP connection on the socket.
#
# Blocks the current fiber if no connection is waiting, continuing when one
@@ -63,4 +69,19 @@ abstract class Crystal::EventLoop
# Closes the socket.
abstract def close(socket : ::Socket) : Nil
end
+
+ # Removes the socket from the event loop. Can be used to free up memory
+ # resources associated with the socket, as well as removing the socket from
+ # kernel data structures.
+ #
+ # Called by `::Socket#finalize` before closing the socket. Errors shall be
+ # silently ignored.
+ def self.remove(socket : ::Socket) : Nil
+ backend_class.remove_impl(socket)
+ end
+
+ # Actual implementation for `.remove`. Must be implemented on a subclass of
+ # `Crystal::EventLoop` when needed.
+ protected def self.remove_impl(socket : ::Socket) : Nil
+ end
end
diff --git a/src/crystal/event_loop/timers.cr b/src/crystal/event_loop/timers.cr
new file mode 100644
index 000000000000..0ea686efad82
--- /dev/null
+++ b/src/crystal/event_loop/timers.cr
@@ -0,0 +1,60 @@
+require "crystal/pointer_pairing_heap"
+
+# List of `Pointer(T)` to `T` structs.
+#
+# Internally wraps a `PointerPairingHeap(T)` and thus requires that `T`
+# implements `PointerPairingHeap::Node`.
+#
+# Thread unsafe: parallel accesses must be protected!
+#
+# NOTE: this is a struct because it only wraps a const pointer to an object
+# allocated in the heap.
+struct Crystal::EventLoop::Timers(T)
+ def initialize
+ @heap = PointerPairingHeap(T).new
+ end
+
+ def empty? : Bool
+ @heap.empty?
+ end
+
+ # Returns the time of the next ready timer (if any).
+ def next_ready? : Time::Span?
+ @heap.first?.try(&.value.wake_at)
+ end
+
+ # Dequeues and yields each ready timer (their `#wake_at` is lower than
+ # `System::Time.monotonic`) from the oldest to the most recent (i.e. time
+ # ascending).
+ def dequeue_ready(& : Pointer(T) -> Nil) : Nil
+ seconds, nanoseconds = System::Time.monotonic
+ now = Time::Span.new(seconds: seconds, nanoseconds: nanoseconds)
+
+ while event = @heap.first?
+ break if event.value.wake_at > now
+ @heap.shift?
+ yield event
+ end
+ end
+
+ # Add a new timer into the list. Returns true if it is the next ready timer.
+ def add(event : Pointer(T)) : Bool
+ @heap.add(event)
+ @heap.first? == event
+ end
+
+ # Remove a timer from the list. Returns a tuple(dequeued, was_next_ready) of
+ # booleans. The first bool tells whether the event was dequeued, in which case
+ # the second one tells if it was the next ready event.
+ def delete(event : Pointer(T)) : {Bool, Bool}
+ if @heap.first? == event
+ @heap.shift?
+ {true, true}
+ elsif event.value.heap_previous?
+ @heap.delete(event)
+ {true, false}
+ else
+ {false, false}
+ end
+ end
+end
diff --git a/src/crystal/system/wasi/event_loop.cr b/src/crystal/event_loop/wasi.cr
similarity index 57%
rename from src/crystal/system/wasi/event_loop.cr
rename to src/crystal/event_loop/wasi.cr
index 5aaf54452571..028eb7e0e9a8 100644
--- a/src/crystal/system/wasi/event_loop.cr
+++ b/src/crystal/event_loop/wasi.cr
@@ -1,5 +1,5 @@
# :nodoc:
-class Crystal::Wasi::EventLoop < Crystal::EventLoop
+class Crystal::EventLoop::Wasi < Crystal::EventLoop
# Runs the event loop.
def run(blocking : Bool) : Bool
raise NotImplementedError.new("Crystal::Wasi::EventLoop.run")
@@ -30,7 +30,7 @@ class Crystal::Wasi::EventLoop < Crystal::EventLoop
end
def read(file_descriptor : Crystal::System::FileDescriptor, slice : Bytes) : Int32
- file_descriptor.evented_read("Error reading file_descriptor") do
+ evented_read(file_descriptor, "Error reading file_descriptor") do
LibC.read(file_descriptor.fd, slice, slice.size).tap do |return_code|
if return_code == -1 && Errno.value == Errno::EBADF
raise IO::Error.new "File not open for reading", target: file_descriptor
@@ -39,8 +39,14 @@ class Crystal::Wasi::EventLoop < Crystal::EventLoop
end
end
+ def wait_readable(file_descriptor : Crystal::System::FileDescriptor) : Nil
+ file_descriptor.evented_wait_readable(raise_if_closed: false) do
+ raise IO::TimeoutError.new("Read timed out")
+ end
+ end
+
def write(file_descriptor : Crystal::System::FileDescriptor, slice : Bytes) : Int32
- file_descriptor.evented_write("Error writing file_descriptor") do
+ evented_write(file_descriptor, "Error writing file_descriptor") do
LibC.write(file_descriptor.fd, slice, slice.size).tap do |return_code|
if return_code == -1 && Errno.value == Errno::EBADF
raise IO::Error.new "File not open for writing", target: file_descriptor
@@ -49,22 +55,40 @@ class Crystal::Wasi::EventLoop < Crystal::EventLoop
end
end
+ def wait_writable(file_descriptor : Crystal::System::FileDescriptor) : Nil
+ file_descriptor.evented_wait_writable(raise_if_closed: false) do
+ raise IO::TimeoutError.new("Write timed out")
+ end
+ end
+
def close(file_descriptor : Crystal::System::FileDescriptor) : Nil
file_descriptor.evented_close
end
def read(socket : ::Socket, slice : Bytes) : Int32
- socket.evented_read("Error reading socket") do
+ evented_read(socket, "Error reading socket") do
LibC.recv(socket.fd, slice, slice.size, 0).to_i32
end
end
+ def wait_readable(socket : ::Socket) : Nil
+ socket.evented_wait_readable do
+ raise IO::TimeoutError.new("Read timed out")
+ end
+ end
+
def write(socket : ::Socket, slice : Bytes) : Int32
- socket.evented_write("Error writing to socket") do
+ evented_write(socket, "Error writing to socket") do
LibC.send(socket.fd, slice, slice.size, 0)
end
end
+ def wait_writable(socket : ::Socket) : Nil
+ socket.evented_wait_writable do
+ raise IO::TimeoutError.new("Write timed out")
+ end
+ end
+
def receive_from(socket : ::Socket, slice : Bytes) : Tuple(Int32, ::Socket::Address)
raise NotImplementedError.new "Crystal::Wasi::EventLoop#receive_from"
end
@@ -84,12 +108,56 @@ class Crystal::Wasi::EventLoop < Crystal::EventLoop
def close(socket : ::Socket) : Nil
socket.evented_close
end
+
+ def evented_read(target, errno_msg : String, &) : Int32
+ loop do
+ bytes_read = yield
+ if bytes_read != -1
+ # `to_i32` is acceptable because `Slice#size` is an Int32
+ return bytes_read.to_i32
+ end
+
+ if Errno.value == Errno::EAGAIN
+ target.evented_wait_readable do
+ raise IO::TimeoutError.new("Read timed out")
+ end
+ else
+ raise IO::Error.from_errno(errno_msg, target: target)
+ end
+ end
+ ensure
+ target.evented_resume_pending_readers
+ end
+
+ def evented_write(target, errno_msg : String, &) : Int32
+ begin
+ loop do
+ bytes_written = yield
+ if bytes_written != -1
+ return bytes_written.to_i32
+ end
+
+ if Errno.value == Errno::EAGAIN
+ target.evented_wait_writable do
+ raise IO::TimeoutError.new("Write timed out")
+ end
+ else
+ raise IO::Error.from_errno(errno_msg, target: target)
+ end
+ end
+ ensure
+ target.evented_resume_pending_writers
+ end
+ end
end
-struct Crystal::Wasi::Event
+struct Crystal::EventLoop::Wasi::Event
include Crystal::EventLoop::Event
- def add(timeout : Time::Span?) : Nil
+ def add(timeout : Time::Span) : Nil
+ end
+
+ def add(timeout : Nil) : Nil
end
def free : Nil
diff --git a/src/crystal/fiber_channel.cr b/src/crystal/fiber_channel.cr
deleted file mode 100644
index dbe0cc6187b9..000000000000
--- a/src/crystal/fiber_channel.cr
+++ /dev/null
@@ -1,23 +0,0 @@
-# :nodoc:
-#
-# This struct wraps around a IO pipe to send and receive fibers between
-# worker threads. The receiving thread will hang on listening for new fibers
-# or fibers that become runnable by the execution of other threads, at the same
-# time it waits for other IO events or timers within the event loop
-struct Crystal::FiberChannel
- @worker_in : IO::FileDescriptor
- @worker_out : IO::FileDescriptor
-
- def initialize
- @worker_out, @worker_in = IO.pipe
- end
-
- def send(fiber : Fiber)
- @worker_in.write_bytes(fiber.object_id)
- end
-
- def receive
- oid = @worker_out.read_bytes(UInt64)
- Pointer(Fiber).new(oid).as(Fiber)
- end
-end
diff --git a/src/crystal/interpreter.cr b/src/crystal/interpreter.cr
index d3b3589d50cb..bad67420f5f3 100644
--- a/src/crystal/interpreter.cr
+++ b/src/crystal/interpreter.cr
@@ -24,5 +24,15 @@ module Crystal
@[Primitive(:interpreter_fiber_resumable)]
def self.fiber_resumable(context) : LibC::Long
end
+
+ {% if compare_versions(Crystal::VERSION, "1.15.0-dev") >= 0 %}
+ @[Primitive(:interpreter_signal_descriptor)]
+ def self.signal_descriptor(fd : Int32) : Nil
+ end
+
+ @[Primitive(:interpreter_signal)]
+ def self.signal(signum : Int32, handler : Int32) : Nil
+ end
+ {% end %}
end
end
diff --git a/src/crystal/lib_iconv.cr b/src/crystal/lib_iconv.cr
index 5f1506758454..dafcb7a75d53 100644
--- a/src/crystal/lib_iconv.cr
+++ b/src/crystal/lib_iconv.cr
@@ -4,9 +4,14 @@ require "c/stddef"
{% raise "The `without_iconv` flag is preventing you to use the LibIconv module" %}
{% end %}
+# Supported library versions:
+#
+# * libiconv-gnu
+#
+# See https://crystal-lang.org/reference/man/required_libraries.html#internationalization-conversion
@[Link("iconv")]
{% if compare_versions(Crystal::VERSION, "1.11.0-dev") >= 0 %}
- @[Link(dll: "libiconv.dll")]
+ @[Link(dll: "iconv-2.dll")]
{% end %}
lib LibIconv
type IconvT = Void*
diff --git a/src/crystal/main.cr b/src/crystal/main.cr
index 625238229c58..704153fe13f6 100644
--- a/src/crystal/main.cr
+++ b/src/crystal/main.cr
@@ -8,7 +8,7 @@ end
module Crystal
# Defines the main routine run by normal Crystal programs:
#
- # - Initializes the GC
+ # - Initializes runtime requirements (GC, ...)
# - Invokes the given *block*
# - Handles unhandled exceptions
# - Invokes `at_exit` handlers
@@ -37,6 +37,8 @@ module Crystal
{% if flag?(:tracing) %} Crystal::Tracing.init {% end %}
GC.init
+ init_runtime
+
status =
begin
yield
@@ -48,6 +50,15 @@ module Crystal
exit(status, ex)
end
+ # :nodoc:
+ def self.init_runtime : Nil
+ # `__crystal_once` directly or indirectly depends on `Fiber` and `Thread`
+ # so we explicitly initialize their class vars, then init crystal/once
+ Thread.init
+ Fiber.init
+ Crystal.once_init
+ end
+
# :nodoc:
def self.exit(status : Int32, exception : Exception?) : Int32
status = Crystal::AtExitHandlers.run status, exception
@@ -130,10 +141,13 @@ fun main(argc : Int32, argv : UInt8**) : Int32
Crystal.main(argc, argv)
end
-{% if flag?(:win32) %}
+{% if flag?(:interpreted) %}
+ # the interpreter doesn't call Crystal.main(&)
+ Crystal.init_runtime
+{% elsif flag?(:win32) %}
require "./system/win32/wmain"
-{% end %}
-
-{% if flag?(:wasi) %}
+{% elsif flag?(:wasi) %}
require "./system/wasi/main"
+{% else %}
+ require "./system/unix/main"
{% end %}
diff --git a/src/crystal/once.cr b/src/crystal/once.cr
index 1e6243669809..87fa9147d56b 100644
--- a/src/crystal/once.cr
+++ b/src/crystal/once.cr
@@ -1,51 +1,144 @@
-# This file defines the functions `__crystal_once_init` and `__crystal_once` expected
-# by the compiler. `__crystal_once` is called each time a constant or class variable
-# has to be initialized and is its responsibility to verify the initializer is executed
-# only once. `__crystal_once_init` is executed only once at the beginning of the program
-# and the result is passed on each call to `__crystal_once`.
-
-# This implementation uses an array to store the initialization flag pointers for each value
-# to find infinite loops and raise an error. In multithread mode a mutex is used to
-# avoid race conditions between threads.
-
-# :nodoc:
-class Crystal::OnceState
- @rec = [] of Bool*
- {% if flag?(:preview_mt) %}
- @mutex = Mutex.new(:reentrant)
- {% end %}
-
- def once(flag : Bool*, initializer : Void*)
- unless flag.value
- if @rec.includes?(flag)
+# This file defines the `__crystal_once` functions expected by the compiler. It
+# is called each time a constant or class variable has to be initialized and is
+# its responsibility to verify the initializer is executed only once and to fail
+# on recursion.
+#
+# It also defines the `__crystal_once_init` function for backward compatibility
+# with older compiler releases. It is executed only once at the beginning of the
+# program and, for the legacy implementation, the result is passed on each call
+# to `__crystal_once`.
+#
+# In multithread mode a mutex is used to avoid race conditions between threads.
+#
+# On Win32, `Crystal::System::FileDescriptor#@@reader_thread` spawns a new
+# thread even without the `preview_mt` flag, and the thread can also reference
+# Crystal constants, leading to race conditions, so we always enable the mutex.
+
+{% if compare_versions(Crystal::VERSION, "1.16.0-dev") >= 0 %}
+ # This implementation uses an enum over the initialization flag pointer for
+ # each value to find infinite loops and raise an error.
+
+ module Crystal
+ # :nodoc:
+ enum OnceState : Int8
+ Processing = -1
+ Uninitialized = 0
+ Initialized = 1
+ end
+
+ {% if flag?(:preview_mt) || flag?(:win32) %}
+ @@once_mutex = uninitialized Mutex
+ {% end %}
+
+ # :nodoc:
+ def self.once_init : Nil
+ {% if flag?(:preview_mt) || flag?(:win32) %}
+ @@once_mutex = Mutex.new(:reentrant)
+ {% end %}
+ end
+
+ # :nodoc:
+ # Using @[NoInline] so LLVM optimizes for the hot path (var already
+ # initialized).
+ @[NoInline]
+ def self.once(flag : OnceState*, initializer : Void*) : Nil
+ {% if flag?(:preview_mt) || flag?(:win32) %}
+ @@once_mutex.synchronize { once_exec(flag, initializer) }
+ {% else %}
+ once_exec(flag, initializer)
+ {% end %}
+
+ # safety check, and allows to safely call `Intrinsics.unreachable` in
+ # `__crystal_once`
+ unless flag.value.initialized?
+ System.print_error "BUG: failed to initialize constant or class variable\n"
+ LibC._exit(1)
+ end
+ end
+
+ private def self.once_exec(flag : OnceState*, initializer : Void*) : Nil
+ case flag.value
+ in .initialized?
+ return
+ in .uninitialized?
+ flag.value = :processing
+ Proc(Nil).new(initializer, Pointer(Void).null).call
+ flag.value = :initialized
+ in .processing?
raise "Recursion while initializing class variables and/or constants"
end
- @rec << flag
+ end
+ end
- Proc(Nil).new(initializer, Pointer(Void).null).call
- flag.value = true
+ # :nodoc:
+ #
+ # Using `@[AlwaysInline]` allows LLVM to optimize const accesses. Since this
+ # is a `fun` the function will still appear in the symbol table, though it
+ # will never be called.
+ @[AlwaysInline]
+ fun __crystal_once(flag : Crystal::OnceState*, initializer : Void*) : Nil
+ return if flag.value.initialized?
- @rec.pop
- end
+ Crystal.once(flag, initializer)
+
+ # tell LLVM that it can optimize away repeated `__crystal_once` calls for
+ # this global (e.g. repeated access to constant in a single funtion);
+ # this is truly unreachable otherwise `Crystal.once` would have panicked
+ Intrinsics.unreachable unless flag.value.initialized?
end
+{% else %}
+ # This implementation uses a global array to store the initialization flag
+ # pointers for each value to find infinite loops and raise an error.
+
+ module Crystal
+ # :nodoc:
+ class OnceState
+ @rec = [] of Bool*
+
+ @[NoInline]
+ def once(flag : Bool*, initializer : Void*)
+ unless flag.value
+ if @rec.includes?(flag)
+ raise "Recursion while initializing class variables and/or constants"
+ end
+ @rec << flag
+
+ Proc(Nil).new(initializer, Pointer(Void).null).call
+ flag.value = true
- {% if flag?(:preview_mt) %}
- def once(flag : Bool*, initializer : Void*)
- unless flag.value
- @mutex.synchronize do
- previous_def
+ @rec.pop
end
end
+
+ {% if flag?(:preview_mt) || flag?(:win32) %}
+ @mutex = Mutex.new(:reentrant)
+
+ @[NoInline]
+ def once(flag : Bool*, initializer : Void*)
+ unless flag.value
+ @mutex.synchronize do
+ previous_def
+ end
+ end
+ end
+ {% end %}
end
- {% end %}
-end
-
-# :nodoc:
-fun __crystal_once_init : Void*
- Crystal::OnceState.new.as(Void*)
-end
-
-# :nodoc:
-fun __crystal_once(state : Void*, flag : Bool*, initializer : Void*)
- state.as(Crystal::OnceState).once(flag, initializer)
-end
+
+ # :nodoc:
+ def self.once_init : Nil
+ end
+ end
+
+ # :nodoc:
+ fun __crystal_once_init : Void*
+ Crystal::OnceState.new.as(Void*)
+ end
+
+ # :nodoc:
+ @[AlwaysInline]
+ fun __crystal_once(state : Void*, flag : Bool*, initializer : Void*)
+ return if flag.value
+ state.as(Crystal::OnceState).once(flag, initializer)
+ Intrinsics.unreachable unless flag.value
+ end
+{% end %}
diff --git a/src/crystal/pe.cr b/src/crystal/pe.cr
new file mode 100644
index 000000000000..d1b19401ad19
--- /dev/null
+++ b/src/crystal/pe.cr
@@ -0,0 +1,110 @@
+module Crystal
+ # :nodoc:
+ #
+ # Portable Executable reader.
+ #
+ # Documentation:
+ # -
+ struct PE
+ class Error < Exception
+ end
+
+ record SectionHeader, name : String, virtual_offset : UInt32, offset : UInt32, size : UInt32
+
+ record COFFSymbol, offset : UInt32, name : String
+
+ # addresses in COFF debug info are relative to this image base; used by
+ # `Exception::CallStack.read_dwarf_sections` to calculate the real relocated
+ # addresses
+ getter original_image_base : UInt64
+
+ @section_headers : Slice(SectionHeader)
+ @string_table_base : UInt32
+
+ # mapping from zero-based section index to list of symbols sorted by
+ # offsets within that section
+ getter coff_symbols = Hash(Int32, Array(COFFSymbol)).new
+
+ def self.open(path : String | ::Path, &)
+ File.open(path, "r") do |file|
+ yield new(file)
+ end
+ end
+
+ def initialize(@io : IO::FileDescriptor)
+ dos_header = uninitialized LibC::IMAGE_DOS_HEADER
+ io.read_fully(pointerof(dos_header).to_slice(1).to_unsafe_bytes)
+ raise Error.new("Invalid DOS header") unless dos_header.e_magic == 0x5A4D # MZ
+
+ io.seek(dos_header.e_lfanew)
+ nt_header = uninitialized LibC::IMAGE_NT_HEADERS
+ io.read_fully(pointerof(nt_header).to_slice(1).to_unsafe_bytes)
+ raise Error.new("Invalid PE header") unless nt_header.signature == 0x00004550 # PE\0\0
+
+ @original_image_base = nt_header.optionalHeader.imageBase
+ @string_table_base = nt_header.fileHeader.pointerToSymbolTable + nt_header.fileHeader.numberOfSymbols * sizeof(LibC::IMAGE_SYMBOL)
+
+ section_count = nt_header.fileHeader.numberOfSections
+ nt_section_headers = Pointer(LibC::IMAGE_SECTION_HEADER).malloc(section_count).to_slice(section_count)
+ io.read_fully(nt_section_headers.to_unsafe_bytes)
+
+ @section_headers = nt_section_headers.map do |nt_header|
+ if nt_header.name[0] === '/'
+ # section name is longer than 8 bytes; look up the COFF string table
+ name_buf = nt_header.name.to_slice + 1
+ string_offset = String.new(name_buf.to_unsafe, name_buf.index(0) || name_buf.size).to_i
+ io.seek(@string_table_base + string_offset)
+ name = io.gets('\0', chomp: true).not_nil!
+ else
+ name = String.new(nt_header.name.to_unsafe, nt_header.name.index(0) || nt_header.name.size)
+ end
+
+ SectionHeader.new(name: name, virtual_offset: nt_header.virtualAddress, offset: nt_header.pointerToRawData, size: nt_header.virtualSize)
+ end
+
+ io.seek(nt_header.fileHeader.pointerToSymbolTable)
+ image_symbol_count = nt_header.fileHeader.numberOfSymbols
+ image_symbols = Pointer(LibC::IMAGE_SYMBOL).malloc(image_symbol_count).to_slice(image_symbol_count)
+ io.read_fully(image_symbols.to_unsafe_bytes)
+
+ aux_count = 0
+ image_symbols.each_with_index do |sym, i|
+ if aux_count == 0
+ aux_count = sym.numberOfAuxSymbols.to_i
+ else
+ aux_count &-= 1
+ end
+
+ next unless aux_count == 0
+ next unless sym.type.bits_set?(0x20) # COFF function
+ next unless sym.sectionNumber > 0 # one-based section index
+ next unless sym.storageClass.in?(LibC::IMAGE_SYM_CLASS_EXTERNAL, LibC::IMAGE_SYM_CLASS_STATIC)
+
+ if sym.n.name.short == 0
+ io.seek(@string_table_base + sym.n.name.long)
+ name = io.gets('\0', chomp: true).not_nil!
+ else
+ name = String.new(sym.n.shortName.to_slice).rstrip('\0')
+ end
+
+ # `@coff_symbols` uses zero-based indices
+ section_coff_symbols = @coff_symbols.put_if_absent(sym.sectionNumber.to_i &- 1) { [] of COFFSymbol }
+ section_coff_symbols << COFFSymbol.new(sym.value, name)
+ end
+
+ # add one sentinel symbol to ensure binary search on the offsets works
+ @coff_symbols.each_with_index do |(_, symbols), i|
+ symbols.sort_by!(&.offset)
+ symbols << COFFSymbol.new(@section_headers[i].size, "??")
+ end
+ end
+
+ def read_section?(name : String, &)
+ if sh = @section_headers.find(&.name.== name)
+ @io.seek(sh.offset) do
+ yield sh, @io
+ end
+ end
+ end
+ end
+end
diff --git a/src/crystal/pointer_linked_list.cr b/src/crystal/pointer_linked_list.cr
index 03109979d662..cde9b0b79ddc 100644
--- a/src/crystal/pointer_linked_list.cr
+++ b/src/crystal/pointer_linked_list.cr
@@ -7,8 +7,8 @@ struct Crystal::PointerLinkedList(T)
module Node
macro included
- property previous : Pointer(self) = Pointer(self).null
- property next : Pointer(self) = Pointer(self).null
+ property previous : ::Pointer(self) = ::Pointer(self).null
+ property next : ::Pointer(self) = ::Pointer(self).null
end
end
diff --git a/src/crystal/pointer_pairing_heap.cr b/src/crystal/pointer_pairing_heap.cr
new file mode 100644
index 000000000000..1b0d73d06bcf
--- /dev/null
+++ b/src/crystal/pointer_pairing_heap.cr
@@ -0,0 +1,158 @@
+# :nodoc:
+#
+# Tree of `T` structs referenced as pointers.
+# `T` must include `Crystal::PointerPairingHeap::Node`.
+class Crystal::PointerPairingHeap(T)
+ module Node
+ macro included
+ property? heap_previous : Pointer(self)?
+ property? heap_next : Pointer(self)?
+ property? heap_child : Pointer(self)?
+ end
+
+ # Compare self with other. For example:
+ #
+ # Use `<` to create a min heap.
+ # Use `>` to create a max heap.
+ abstract def heap_compare(other : Pointer(self)) : Bool
+ end
+
+ @head : Pointer(T)?
+
+ private def head=(head)
+ @head = head
+ head.value.heap_previous = nil if head
+ head
+ end
+
+ def empty?
+ @head.nil?
+ end
+
+ def first? : Pointer(T)?
+ @head
+ end
+
+ def shift? : Pointer(T)?
+ if node = @head
+ self.head = merge_pairs(node.value.heap_child?)
+ node.value.heap_child = nil
+ node
+ end
+ end
+
+ def add(node : Pointer(T)) : Nil
+ if node.value.heap_previous? || node.value.heap_next? || node.value.heap_child?
+ raise ArgumentError.new("The node is already in a Pairing Heap tree")
+ end
+ self.head = meld(@head, node)
+ end
+
+ def delete(node : Pointer(T)) : Nil
+ if previous_node = node.value.heap_previous?
+ next_sibling = node.value.heap_next?
+
+ if previous_node.value.heap_next? == node
+ previous_node.value.heap_next = next_sibling
+ else
+ previous_node.value.heap_child = next_sibling
+ end
+
+ if next_sibling
+ next_sibling.value.heap_previous = previous_node
+ end
+
+ subtree = merge_pairs(node.value.heap_child?)
+ clear(node)
+ self.head = meld(@head, subtree)
+ else
+ # removing head
+ self.head = merge_pairs(node.value.heap_child?)
+ node.value.heap_child = nil
+ end
+ end
+
+ def clear : Nil
+ if node = @head
+ clear_recursive(node)
+ @head = nil
+ end
+ end
+
+ private def clear_recursive(node)
+ child = node.value.heap_child?
+ while child
+ clear_recursive(child)
+ child = child.value.heap_next?
+ end
+ clear(node)
+ end
+
+ private def meld(a : Pointer(T), b : Pointer(T)) : Pointer(T)
+ if a.value.heap_compare(b)
+ add_child(a, b)
+ else
+ add_child(b, a)
+ end
+ end
+
+ private def meld(a : Pointer(T), b : Nil) : Pointer(T)
+ a
+ end
+
+ private def meld(a : Nil, b : Pointer(T)) : Pointer(T)
+ b
+ end
+
+ private def meld(a : Nil, b : Nil) : Nil
+ end
+
+ private def add_child(parent : Pointer(T), node : Pointer(T)) : Pointer(T)
+ first_child = parent.value.heap_child?
+ parent.value.heap_child = node
+
+ first_child.value.heap_previous = node if first_child
+ node.value.heap_previous = parent
+ node.value.heap_next = first_child
+
+ parent
+ end
+
+ private def merge_pairs(node : Pointer(T)?) : Pointer(T)?
+ return unless node
+
+ # 1st pass: meld children into pairs (left to right)
+ tail = nil
+
+ while a = node
+ if b = a.value.heap_next?
+ node = b.value.heap_next?
+ root = meld(a, b)
+ root.value.heap_previous = tail
+ tail = root
+ else
+ a.value.heap_previous = tail
+ tail = a
+ break
+ end
+ end
+
+ # 2nd pass: meld the pairs back into a single tree (right to left)
+ root = nil
+
+ while tail
+ node = tail.value.heap_previous?
+ root = meld(root, tail)
+ tail = node
+ end
+
+ root.value.heap_next = nil if root
+ root
+ end
+
+ private def clear(node) : Nil
+ node.value.heap_previous = nil
+ node.value.heap_next = nil
+ node.value.heap_child = nil
+ end
+end
diff --git a/src/crystal/print_buffered.cr b/src/crystal/print_buffered.cr
new file mode 100644
index 000000000000..e58423f0f08b
--- /dev/null
+++ b/src/crystal/print_buffered.cr
@@ -0,0 +1,42 @@
+module Crystal
+ # Prepares an error message, with an optional exception or backtrace, to an
+ # in-memory buffer, before writing to an IO, usually STDERR, in a single write
+ # operation.
+ #
+ # Avoids intermingled messages caused by multiple threads writing to a STDIO
+ # in parallel. This may still happen, since writes may not be atomic when the
+ # overall size is larger than PIPE_BUF, buf it should at least write 512 bytes
+ # atomically.
+ def self.print_buffered(message : String, *args, to io : IO, exception = nil, backtrace = nil) : Nil
+ buf = buffered_message(message, *args, exception: exception, backtrace: backtrace)
+ io.write(buf.to_slice)
+ io.flush unless io.sync?
+ end
+
+ # Identical to `#print_buffered` but eventually calls `System.print_error(bytes)`
+ # to write to stderr without going through the event loop.
+ def self.print_error_buffered(message : String, *args, exception = nil, backtrace = nil) : Nil
+ buf = buffered_message(message, *args, exception: exception, backtrace: backtrace)
+ System.print_error(buf.to_slice)
+ end
+
+ private def self.buffered_message(message : String, *args, exception = nil, backtrace = nil)
+ buf = IO::Memory.new(4096)
+
+ if args.empty?
+ buf << message
+ else
+ System.printf(message, *args) { |bytes| buf.write(bytes) }
+ end
+
+ if exception
+ buf << ": "
+ exception.inspect_with_backtrace(buf)
+ else
+ buf.puts
+ backtrace.try(&.each { |line| buf << " from " << line << '\n' })
+ end
+
+ buf
+ end
+end
diff --git a/src/crystal/scheduler.cr b/src/crystal/scheduler.cr
index d3634e9aea6a..efee6b3c06f1 100644
--- a/src/crystal/scheduler.cr
+++ b/src/crystal/scheduler.cr
@@ -1,6 +1,5 @@
-require "crystal/system/event_loop"
+require "crystal/event_loop"
require "crystal/system/print_error"
-require "./fiber_channel"
require "fiber"
require "fiber/stack_pool"
require "crystal/system/thread"
@@ -24,6 +23,12 @@ class Crystal::Scheduler
Thread.current.scheduler.@event_loop
end
+ def self.event_loop?
+ if scheduler = Thread.current?.try(&.scheduler?)
+ scheduler.@event_loop
+ end
+ end
+
def self.enqueue(fiber : Fiber) : Nil
Crystal.trace :sched, "enqueue", fiber: fiber do
thread = Thread.current
@@ -61,7 +66,7 @@ class Crystal::Scheduler
end
def self.sleep(time : Time::Span) : Nil
- Crystal.trace :sched, "sleep", for: time.total_nanoseconds.to_i64!
+ Crystal.trace :sched, "sleep", for: time
Thread.current.scheduler.sleep(time)
end
@@ -91,10 +96,6 @@ class Crystal::Scheduler
{% end %}
end
- {% if flag?(:preview_mt) %}
- private getter(fiber_channel : Crystal::FiberChannel) { Crystal::FiberChannel.new }
- {% end %}
-
@main : Fiber
@lock = Crystal::SpinLock.new
@sleeping = false
@@ -174,6 +175,7 @@ class Crystal::Scheduler
end
{% if flag?(:preview_mt) %}
+ private getter! worker_fiber : Fiber
@rr_target = 0
protected def find_target_thread
@@ -186,38 +188,34 @@ class Crystal::Scheduler
end
def run_loop
+ @worker_fiber = Fiber.current
+
spawn_stack_pool_collector
- fiber_channel = self.fiber_channel
loop do
@lock.lock
if runnable = @runnables.shift?
- @runnables << Fiber.current
+ @runnables << worker_fiber
@lock.unlock
resume(runnable)
else
@sleeping = true
@lock.unlock
-
Crystal.trace :sched, "mt:sleeping"
- fiber = Crystal.trace(:sched, "mt:slept") { fiber_channel.receive }
-
- @lock.lock
- @sleeping = false
- @runnables << Fiber.current
- @lock.unlock
- resume(fiber)
+ Crystal.trace(:sched, "mt:slept") { ::Fiber.suspend }
end
end
end
def send_fiber(fiber : Fiber)
@lock.lock
+ @runnables << fiber
+
if @sleeping
- fiber_channel.send(fiber)
- else
- @runnables << fiber
+ @sleeping = false
+ @runnables << worker_fiber
+ @event_loop.interrupt
end
@lock.unlock
end
@@ -227,7 +225,7 @@ class Crystal::Scheduler
pending = Atomic(Int32).new(count - 1)
@@workers = Array(Thread).new(count) do |i|
if i == 0
- worker_loop = Fiber.new(name: "Worker Loop") { Thread.current.scheduler.run_loop }
+ worker_loop = Fiber.new(name: "worker-loop") { Thread.current.scheduler.run_loop }
worker_loop.set_current_thread
Thread.current.scheduler.enqueue worker_loop
Thread.current
@@ -274,7 +272,7 @@ class Crystal::Scheduler
# Background loop to cleanup unused fiber stacks.
def spawn_stack_pool_collector
- fiber = Fiber.new(name: "Stack pool collector", &->@stack_pool.collect_loop)
+ fiber = Fiber.new(name: "stack-pool-collector", &->@stack_pool.collect_loop)
{% if flag?(:preview_mt) %} fiber.set_current_thread {% end %}
enqueue(fiber)
end
diff --git a/src/crystal/spin_lock.cr b/src/crystal/spin_lock.cr
index 4255fcae7bbd..105c235e0c66 100644
--- a/src/crystal/spin_lock.cr
+++ b/src/crystal/spin_lock.cr
@@ -1,5 +1,5 @@
# :nodoc:
-class Crystal::SpinLock
+struct Crystal::SpinLock
private UNLOCKED = 0
private LOCKED = 1
diff --git a/src/crystal/syntax_highlighter.cr b/src/crystal/syntax_highlighter.cr
index 1d4abcb60c70..a7794e96a21c 100644
--- a/src/crystal/syntax_highlighter.cr
+++ b/src/crystal/syntax_highlighter.cr
@@ -84,6 +84,8 @@ abstract class Crystal::SyntaxHighlighter
space_before = false
while true
+ previous_delimiter_state = lexer.token.delimiter_state
+
token = lexer.next_token
case token.type
@@ -105,6 +107,7 @@ abstract class Crystal::SyntaxHighlighter
highlight_token token, last_is_def
else
highlight_delimiter_state lexer, token
+ token.delimiter_state = previous_delimiter_state
end
when .string_array_start?, .symbol_array_start?
highlight_string_array lexer, token
diff --git a/src/crystal/system/addrinfo.cr b/src/crystal/system/addrinfo.cr
new file mode 100644
index 000000000000..ff9166f3aca1
--- /dev/null
+++ b/src/crystal/system/addrinfo.cr
@@ -0,0 +1,40 @@
+module Crystal::System::Addrinfo
+ # alias Handle
+
+ # protected def initialize(addrinfo : Handle)
+
+ # def system_ip_address : ::Socket::IPAddress
+
+ # def self.getaddrinfo(domain, service, family, type, protocol, timeout) : Handle
+
+ # def self.next_addrinfo(addrinfo : Handle) : Handle
+
+ # def self.free_addrinfo(addrinfo : Handle)
+
+ def self.getaddrinfo(domain, service, family, type, protocol, timeout, & : ::Socket::Addrinfo ->)
+ addrinfo = root = getaddrinfo(domain, service, family, type, protocol, timeout)
+
+ begin
+ while addrinfo
+ yield ::Socket::Addrinfo.new(addrinfo)
+ addrinfo = next_addrinfo(addrinfo)
+ end
+ ensure
+ free_addrinfo(root)
+ end
+ end
+end
+
+{% if flag?(:wasi) %}
+ require "./wasi/addrinfo"
+{% elsif flag?(:unix) %}
+ require "./unix/addrinfo"
+{% elsif flag?(:win32) %}
+ {% if flag?(:win7) %}
+ require "./win32/addrinfo_win7"
+ {% else %}
+ require "./win32/addrinfo"
+ {% end %}
+{% else %}
+ {% raise "No Crystal::System::Addrinfo implementation available" %}
+{% end %}
diff --git a/src/crystal/system/event_loop/file_descriptor.cr b/src/crystal/system/event_loop/file_descriptor.cr
deleted file mode 100644
index a041263609d9..000000000000
--- a/src/crystal/system/event_loop/file_descriptor.cr
+++ /dev/null
@@ -1,23 +0,0 @@
-abstract class Crystal::EventLoop
- module FileDescriptor
- # Reads at least one byte from the file descriptor into *slice*.
- #
- # Blocks the current fiber if no data is available for reading, continuing
- # when available. Otherwise returns immediately.
- #
- # Returns the number of bytes read (up to `slice.size`).
- # Returns 0 when EOF is reached.
- abstract def read(file_descriptor : Crystal::System::FileDescriptor, slice : Bytes) : Int32
-
- # Writes at least one byte from *slice* to the file descriptor.
- #
- # Blocks the current fiber if the file descriptor isn't ready for writing,
- # continuing when ready. Otherwise returns immediately.
- #
- # Returns the number of bytes written (up to `slice.size`).
- abstract def write(file_descriptor : Crystal::System::FileDescriptor, slice : Bytes) : Int32
-
- # Closes the file descriptor resource.
- abstract def close(file_descriptor : Crystal::System::FileDescriptor) : Nil
- end
-end
diff --git a/src/crystal/system/fiber.cr b/src/crystal/system/fiber.cr
index 1cc47e2917e1..1f15d2fe5535 100644
--- a/src/crystal/system/fiber.cr
+++ b/src/crystal/system/fiber.cr
@@ -1,12 +1,12 @@
module Crystal::System::Fiber
# Allocates memory for a stack.
- # def self.allocate_stack(stack_size : Int) : Void*
+ # def self.allocate_stack(stack_size : Int, protect : Bool) : Void*
+
+ # Prepares an existing, unused stack for use again.
+ # def self.reset_stack(stack : Void*, stack_size : Int, protect : Bool) : Nil
# Frees memory of a stack.
# def self.free_stack(stack : Void*, stack_size : Int) : Nil
-
- # Determines location of the top of the main process fiber's stack.
- # def self.main_fiber_stack(stack_bottom : Void*) : Void*
end
{% if flag?(:wasi) %}
diff --git a/src/crystal/system/file.cr b/src/crystal/system/file.cr
index 75985c107fd5..84dbd0fa5c98 100644
--- a/src/crystal/system/file.cr
+++ b/src/crystal/system/file.cr
@@ -65,7 +65,7 @@ module Crystal::System::File
io << suffix
end
- handle, errno = open(path, mode, perm)
+ handle, errno = open(path, mode, perm, blocking: true)
if error_is_none?(errno)
return {handle, path}
@@ -87,13 +87,6 @@ module Crystal::System::File
private def self.error_is_file_exists?(errno)
errno.in?(Errno::EEXIST, WinError::ERROR_FILE_EXISTS)
end
-
- # Closes the internal file descriptor without notifying libevent.
- # This is directly used after the fork of a process to close the
- # parent's Crystal::System::Signal.@@pipe reference before re initializing
- # the event loop. In the case of a fork that will exec there is even
- # no need to initialize the event loop at all.
- # def file_descriptor_close
end
{% if flag?(:wasi) %}
diff --git a/src/crystal/system/file_descriptor.cr b/src/crystal/system/file_descriptor.cr
index 0180627d59ce..03868bc07034 100644
--- a/src/crystal/system/file_descriptor.cr
+++ b/src/crystal/system/file_descriptor.cr
@@ -14,6 +14,23 @@ module Crystal::System::FileDescriptor
# cooked mode otherwise.
# private def system_raw(enable : Bool, & : ->)
+ # Closes the internal file descriptor without notifying the event loop.
+ # This is directly used after the fork of a process to close the
+ # parent's Crystal::System::Signal.@@pipe reference before re initializing
+ # the event loop. In the case of a fork that will exec there is even
+ # no need to initialize the event loop at all.
+ # Also used in `IO::FileDescriptor#finalize`.
+ # def file_descriptor_close
+
+ # Returns `true` or `false` if this file descriptor pretends to block or not
+ # to block the caller thread regardless of the underlying internal file
+ # descriptor's implementation. Returns `nil` if nothing needs to be done, i.e.
+ # `#blocking` is identical to `#system_blocking?`.
+ #
+ # Currently used by console STDIN on Windows.
+ private def emulated_blocking? : Bool?
+ end
+
private def system_read(slice : Bytes) : Int32
event_loop.read(self, slice)
end
@@ -22,6 +39,10 @@ module Crystal::System::FileDescriptor
event_loop.write(self, slice)
end
+ private def event_loop? : Crystal::EventLoop::FileDescriptor?
+ Crystal::EventLoop.current?
+ end
+
private def event_loop : Crystal::EventLoop::FileDescriptor
Crystal::EventLoop.current
end
diff --git a/src/crystal/system/group.cr b/src/crystal/system/group.cr
index dce631e8c1ab..6cb93739a900 100644
--- a/src/crystal/system/group.cr
+++ b/src/crystal/system/group.cr
@@ -1,7 +1,19 @@
+module Crystal::System::Group
+ # def system_name : String
+
+ # def system_id : String
+
+ # def self.from_name?(groupname : String) : ::System::Group?
+
+ # def self.from_id?(groupid : String) : ::System::Group?
+end
+
{% if flag?(:wasi) %}
require "./wasi/group"
{% elsif flag?(:unix) %}
require "./unix/group"
+{% elsif flag?(:win32) %}
+ require "./win32/group"
{% else %}
{% raise "No Crystal::System::Group implementation available" %}
{% end %}
diff --git a/src/crystal/system/print_error.cr b/src/crystal/system/print_error.cr
index 796579bf256a..b55e05e51ec6 100644
--- a/src/crystal/system/print_error.cr
+++ b/src/crystal/system/print_error.cr
@@ -23,7 +23,7 @@ module Crystal::System
String.each_utf16_char(bytes) do |char|
if appender.size > utf8.size - char.bytesize
# buffer is full (char won't fit)
- print_error utf8.to_slice[0...appender.size]
+ print_error appender.to_slice
appender = utf8.to_unsafe.appender
end
@@ -33,7 +33,7 @@ module Crystal::System
end
if appender.size > 0
- print_error utf8.to_slice[0...appender.size]
+ print_error appender.to_slice
end
end
diff --git a/src/crystal/system/random.cr b/src/crystal/system/random.cr
index 1a5b3c8f4677..ccf9d6dfa344 100644
--- a/src/crystal/system/random.cr
+++ b/src/crystal/system/random.cr
@@ -13,7 +13,12 @@ end
{% if flag?(:wasi) %}
require "./wasi/random"
{% elsif flag?(:linux) %}
- require "./unix/getrandom"
+ require "c/sys/random"
+ \{% if LibC.has_method?(:getrandom) %}
+ require "./unix/getrandom"
+ \{% else %}
+ require "./unix/urandom"
+ \{% end %}
{% elsif flag?(:bsd) || flag?(:darwin) %}
require "./unix/arc4random"
{% elsif flag?(:unix) %}
diff --git a/src/crystal/system/socket.cr b/src/crystal/system/socket.cr
index 2669b4c57bca..54648f17f7db 100644
--- a/src/crystal/system/socket.cr
+++ b/src/crystal/system/socket.cr
@@ -1,4 +1,4 @@
-require "./event_loop/socket"
+require "../event_loop/socket"
module Crystal::System::Socket
# Creates a file descriptor / socket handle
@@ -91,6 +91,18 @@ module Crystal::System::Socket
# private def system_close
+ # Closes the internal handle without notifying the event loop.
+ # This is directly used after the fork of a process to close the
+ # parent's Crystal::System::Signal.@@pipe reference before re initializing
+ # the event loop. In the case of a fork that will exec there is even
+ # no need to initialize the event loop at all.
+ # Also used in `Socket#finalize`
+ # def socket_close
+
+ private def event_loop? : Crystal::EventLoop::Socket?
+ Crystal::EventLoop.current?
+ end
+
private def event_loop : Crystal::EventLoop::Socket
Crystal::EventLoop.current
end
diff --git a/src/crystal/system/thread.cr b/src/crystal/system/thread.cr
index d9dc6acf17dc..878a27e4c578 100644
--- a/src/crystal/system/thread.cr
+++ b/src/crystal/system/thread.cr
@@ -2,6 +2,8 @@
module Crystal::System::Thread
# alias Handle
+ # def self.init : Nil
+
# def self.new_handle(thread_obj : ::Thread) : Handle
# def self.current_handle : Handle
@@ -23,6 +25,14 @@ module Crystal::System::Thread
# private def stack_address : Void*
# private def system_name=(String) : String
+
+ # def self.init_suspend_resume : Nil
+
+ # private def system_suspend : Nil
+
+ # private def system_wait_suspended : Nil
+
+ # private def system_resume : Nil
end
{% if flag?(:wasi) %}
@@ -40,7 +50,16 @@ class Thread
include Crystal::System::Thread
# all thread objects, so the GC can see them (it doesn't scan thread locals)
- protected class_getter(threads) { Thread::LinkedList(Thread).new }
+ @@threads = uninitialized Thread::LinkedList(Thread)
+
+ protected def self.threads : Thread::LinkedList(Thread)
+ @@threads
+ end
+
+ def self.init : Nil
+ @@threads = Thread::LinkedList(Thread).new
+ Crystal::System::Thread.init
+ end
@system_handle : Crystal::System::Thread::Handle
@exception : Exception?
@@ -66,6 +85,18 @@ class Thread
@@threads.try(&.unsafe_each { |thread| yield thread })
end
+ def self.each(&)
+ threads.each { |thread| yield thread }
+ end
+
+ def self.lock : Nil
+ threads.@mutex.lock
+ end
+
+ def self.unlock : Nil
+ threads.@mutex.unlock
+ end
+
# Creates and starts a new system thread.
def initialize(@name : String? = nil, &@func : Thread ->)
@system_handle = uninitialized Crystal::System::Thread::Handle
@@ -75,7 +106,7 @@ class Thread
# Used once to initialize the thread object representing the main thread of
# the process (that already exists).
def initialize
- @func = ->(t : Thread) {}
+ @func = ->(t : Thread) { }
@system_handle = Crystal::System::Thread.current_handle
@current_fiber = @main_fiber = Fiber.new(stack_address, self)
@@ -166,8 +197,33 @@ class Thread
self.system_name = name
end
+ # Changes the Thread#name property but doesn't update the system name. Useful
+ # on the main thread where we'd change the process name (e.g. top, ps, ...).
+ def internal_name=(@name : String)
+ end
+
# Holds the GC thread handler
property gc_thread_handler : Void* = Pointer(Void).null
+
+ def suspend : Nil
+ system_suspend
+ end
+
+ def wait_suspended : Nil
+ system_wait_suspended
+ end
+
+ def resume : Nil
+ system_resume
+ end
+
+ def self.stop_world : Nil
+ GC.stop_world
+ end
+
+ def self.start_world : Nil
+ GC.start_world
+ end
end
require "./thread_linked_list"
diff --git a/src/crystal/system/thread_linked_list.cr b/src/crystal/system/thread_linked_list.cr
index b6f3ccf65d4e..f43dd04fedc2 100644
--- a/src/crystal/system/thread_linked_list.cr
+++ b/src/crystal/system/thread_linked_list.cr
@@ -24,6 +24,13 @@ class Thread
end
end
+ # Safely iterates the list.
+ def each(&) : Nil
+ @mutex.synchronize do
+ unsafe_each { |node| yield node }
+ end
+ end
+
# Appends a node to the tail of the list. The operation is thread-safe.
#
# There are no guarantees that a node being pushed will be iterated by
@@ -47,16 +54,21 @@ class Thread
# `#unsafe_each` until the method has returned.
def delete(node : T) : Nil
@mutex.synchronize do
- if previous = node.previous
- previous.next = node.next
+ previous = node.previous
+ _next = node.next
+
+ if previous
+ node.previous = nil
+ previous.next = _next
else
- @head = node.next
+ @head = _next
end
- if _next = node.next
- _next.previous = node.previous
+ if _next
+ node.next = nil
+ _next.previous = previous
else
- @tail = node.previous
+ @tail = previous
end
end
end
diff --git a/src/crystal/system/unix/addrinfo.cr b/src/crystal/system/unix/addrinfo.cr
new file mode 100644
index 000000000000..7f1e51558397
--- /dev/null
+++ b/src/crystal/system/unix/addrinfo.cr
@@ -0,0 +1,71 @@
+module Crystal::System::Addrinfo
+ alias Handle = LibC::Addrinfo*
+
+ @addr : LibC::SockaddrIn6
+
+ protected def initialize(addrinfo : Handle)
+ @family = ::Socket::Family.from_value(addrinfo.value.ai_family)
+ @type = ::Socket::Type.from_value(addrinfo.value.ai_socktype)
+ @protocol = ::Socket::Protocol.from_value(addrinfo.value.ai_protocol)
+ @size = addrinfo.value.ai_addrlen.to_i
+
+ @addr = uninitialized LibC::SockaddrIn6
+
+ case @family
+ when ::Socket::Family::INET6
+ addrinfo.value.ai_addr.as(LibC::SockaddrIn6*).copy_to(pointerof(@addr).as(LibC::SockaddrIn6*), 1)
+ when ::Socket::Family::INET
+ addrinfo.value.ai_addr.as(LibC::SockaddrIn*).copy_to(pointerof(@addr).as(LibC::SockaddrIn*), 1)
+ else
+ # TODO: (asterite) UNSPEC and UNIX unsupported?
+ end
+ end
+
+ def system_ip_address : ::Socket::IPAddress
+ ::Socket::IPAddress.from(to_unsafe, size)
+ end
+
+ def to_unsafe
+ pointerof(@addr).as(LibC::Sockaddr*)
+ end
+
+ def self.getaddrinfo(domain, service, family, type, protocol, timeout) : Handle
+ hints = LibC::Addrinfo.new
+ hints.ai_family = (family || ::Socket::Family::UNSPEC).to_i32
+ hints.ai_socktype = type
+ hints.ai_protocol = protocol
+ hints.ai_flags = 0
+
+ if service.is_a?(Int)
+ hints.ai_flags |= LibC::AI_NUMERICSERV
+ end
+
+ # On OS X < 10.12, the libsystem implementation of getaddrinfo segfaults
+ # if AI_NUMERICSERV is set, and servname is NULL or 0.
+ {% if flag?(:darwin) %}
+ if service.in?(0, nil) && (hints.ai_flags & LibC::AI_NUMERICSERV)
+ hints.ai_flags |= LibC::AI_NUMERICSERV
+ service = "00"
+ end
+ {% end %}
+
+ ret = LibC.getaddrinfo(domain, service.to_s, pointerof(hints), out ptr)
+ unless ret.zero?
+ if ret == LibC::EAI_SYSTEM
+ raise ::Socket::Addrinfo::Error.from_os_error nil, Errno.value, domain: domain
+ end
+
+ error = Errno.new(ret)
+ raise ::Socket::Addrinfo::Error.from_os_error(nil, error, domain: domain, type: type, protocol: protocol, service: service)
+ end
+ ptr
+ end
+
+ def self.next_addrinfo(addrinfo : Handle) : Handle
+ addrinfo.value.ai_next
+ end
+
+ def self.free_addrinfo(addrinfo : Handle)
+ LibC.freeaddrinfo(addrinfo)
+ end
+end
diff --git a/src/crystal/system/unix/dir.cr b/src/crystal/system/unix/dir.cr
index 5e66b33b65e7..72d1183dcc72 100644
--- a/src/crystal/system/unix/dir.cr
+++ b/src/crystal/system/unix/dir.cr
@@ -42,7 +42,12 @@ module Crystal::System::Dir
end
def self.info(dir, path) : ::File::Info
- Crystal::System::FileDescriptor.system_info LibC.dirfd(dir)
+ fd = {% if flag?(:netbsd) %}
+ dir.value.dd_fd
+ {% else %}
+ LibC.dirfd(dir)
+ {% end %}
+ Crystal::System::FileDescriptor.system_info(fd)
end
def self.close(dir, path) : Nil
diff --git a/src/crystal/system/unix/epoll.cr b/src/crystal/system/unix/epoll.cr
new file mode 100644
index 000000000000..28a157ae3360
--- /dev/null
+++ b/src/crystal/system/unix/epoll.cr
@@ -0,0 +1,66 @@
+require "c/sys/epoll"
+
+struct Crystal::System::Epoll
+ def initialize
+ @epfd = LibC.epoll_create1(LibC::EPOLL_CLOEXEC)
+ raise RuntimeError.from_errno("epoll_create1") if @epfd == -1
+ end
+
+ def fd : Int32
+ @epfd
+ end
+
+ def add(fd : Int32, epoll_event : LibC::EpollEvent*) : Nil
+ if LibC.epoll_ctl(@epfd, LibC::EPOLL_CTL_ADD, fd, epoll_event) == -1
+ raise RuntimeError.from_errno("epoll_ctl(EPOLL_CTL_ADD)") unless Errno.value == Errno::EPERM
+ end
+ end
+
+ def add(fd : Int32, events : UInt32, u64 : UInt64) : Nil
+ epoll_event = uninitialized LibC::EpollEvent
+ epoll_event.events = events
+ epoll_event.data.u64 = u64
+ add(fd, pointerof(epoll_event))
+ end
+
+ def modify(fd : Int32, epoll_event : LibC::EpollEvent*) : Nil
+ if LibC.epoll_ctl(@epfd, LibC::EPOLL_CTL_MOD, fd, epoll_event) == -1
+ raise RuntimeError.from_errno("epoll_ctl(EPOLL_CTL_MOD)")
+ end
+ end
+
+ def delete(fd : Int32) : Nil
+ delete(fd) do
+ raise RuntimeError.from_errno("epoll_ctl(EPOLL_CTL_DEL)")
+ end
+ end
+
+ def delete(fd : Int32, &) : Nil
+ if LibC.epoll_ctl(@epfd, LibC::EPOLL_CTL_DEL, fd, nil) == -1
+ yield
+ end
+ end
+
+ # `timeout` is in milliseconds; -1 will wait indefinitely; 0 will never wait.
+ def wait(events : Slice(LibC::EpollEvent), timeout : Int32) : Slice(LibC::EpollEvent)
+ count = 0
+
+ loop do
+ count = LibC.epoll_wait(@epfd, events.to_unsafe, events.size, timeout)
+ break unless count == -1
+
+ if Errno.value == Errno::EINTR
+ # retry when waiting indefinitely, return otherwise
+ break unless timeout == -1
+ else
+ raise RuntimeError.from_errno("epoll_wait")
+ end
+ end
+
+ events[0, count.clamp(0..)]
+ end
+
+ def close : Nil
+ LibC.close(@epfd)
+ end
+end
diff --git a/src/crystal/system/unix/eventfd.cr b/src/crystal/system/unix/eventfd.cr
new file mode 100644
index 000000000000..6180bf90bf23
--- /dev/null
+++ b/src/crystal/system/unix/eventfd.cr
@@ -0,0 +1,31 @@
+require "c/sys/eventfd"
+
+struct Crystal::System::EventFD
+ # NOTE: no need to concern ourselves with endianness: we interpret the bytes
+ # in the system order and eventfd can only be used locally (no cross system
+ # issues).
+
+ getter fd : Int32
+
+ def initialize(value = 0)
+ @fd = LibC.eventfd(value, LibC::EFD_CLOEXEC)
+ raise RuntimeError.from_errno("eventfd") if @fd == -1
+ end
+
+ def read : UInt64
+ buf = uninitialized UInt8[8]
+ bytes_read = LibC.read(@fd, buf.to_unsafe, buf.size)
+ raise RuntimeError.from_errno("eventfd_read") unless bytes_read == 8
+ buf.unsafe_as(UInt64)
+ end
+
+ def write(value : UInt64) : Nil
+ buf = value.unsafe_as(StaticArray(UInt8, 8))
+ bytes_written = LibC.write(@fd, buf.to_unsafe, buf.size)
+ raise RuntimeError.from_errno("eventfd_write") unless bytes_written == 8
+ end
+
+ def close : Nil
+ LibC.close(@fd)
+ end
+end
diff --git a/src/crystal/system/unix/fiber.cr b/src/crystal/system/unix/fiber.cr
index 317a3f7fbd41..42153b28bed2 100644
--- a/src/crystal/system/unix/fiber.cr
+++ b/src/crystal/system/unix/fiber.cr
@@ -21,6 +21,9 @@ module Crystal::System::Fiber
pointer
end
+ def self.reset_stack(stack : Void*, stack_size : Int, protect : Bool) : Nil
+ end
+
def self.free_stack(stack : Void*, stack_size) : Nil
LibC.munmap(stack, stack_size)
end
diff --git a/src/crystal/system/unix/file.cr b/src/crystal/system/unix/file.cr
index a353cf29cd3c..d95aece69f55 100644
--- a/src/crystal/system/unix/file.cr
+++ b/src/crystal/system/unix/file.cr
@@ -3,10 +3,10 @@ require "file/error"
# :nodoc:
module Crystal::System::File
- def self.open(filename : String, mode : String, perm : Int32 | ::File::Permissions)
+ def self.open(filename : String, mode : String, perm : Int32 | ::File::Permissions, blocking)
perm = ::File::Permissions.new(perm) if perm.is_a? Int32
- fd, errno = open(filename, open_flag(mode), perm)
+ fd, errno = open(filename, open_flag(mode), perm, blocking)
unless errno.none?
raise ::File::Error.from_os_error("Error opening file with mode '#{mode}'", errno, file: filename)
@@ -15,7 +15,7 @@ module Crystal::System::File
fd
end
- def self.open(filename : String, flags : Int32, perm : ::File::Permissions) : {LibC::Int, Errno}
+ def self.open(filename : String, flags : Int32, perm : ::File::Permissions, blocking _blocking) : {LibC::Int, Errno}
filename.check_no_null_byte
flags |= LibC::O_CLOEXEC
@@ -24,6 +24,9 @@ module Crystal::System::File
{fd, fd < 0 ? Errno.value : Errno::NONE}
end
+ protected def system_set_mode(mode : String)
+ end
+
def self.info?(path : String, follow_symlinks : Bool) : ::File::Info?
stat = uninitialized LibC::Stat
if follow_symlinks
@@ -182,10 +185,19 @@ module Crystal::System::File
end
def self.utime(atime : ::Time, mtime : ::Time, filename : String) : Nil
- timevals = uninitialized LibC::Timeval[2]
- timevals[0] = Crystal::System::Time.to_timeval(atime)
- timevals[1] = Crystal::System::Time.to_timeval(mtime)
- ret = LibC.utimes(filename, timevals)
+ ret =
+ {% if LibC.has_method?("utimensat") %}
+ timespecs = uninitialized LibC::Timespec[2]
+ timespecs[0] = Crystal::System::Time.to_timespec(atime)
+ timespecs[1] = Crystal::System::Time.to_timespec(mtime)
+ LibC.utimensat(LibC::AT_FDCWD, filename, timespecs, 0)
+ {% else %}
+ timevals = uninitialized LibC::Timeval[2]
+ timevals[0] = Crystal::System::Time.to_timeval(atime)
+ timevals[1] = Crystal::System::Time.to_timeval(mtime)
+ LibC.utimes(filename, timevals)
+ {% end %}
+
if ret != 0
raise ::File::Error.from_errno("Error setting time on file", file: filename)
end
diff --git a/src/crystal/system/unix/file_descriptor.cr b/src/crystal/system/unix/file_descriptor.cr
index 0c3ece9cfff8..4aa1ec580d32 100644
--- a/src/crystal/system/unix/file_descriptor.cr
+++ b/src/crystal/system/unix/file_descriptor.cr
@@ -1,5 +1,4 @@
require "c/fcntl"
-require "io/evented"
require "termios"
{% if flag?(:android) && LibC::ANDROID_API < 28 %}
require "c/sys/ioctl"
@@ -7,7 +6,9 @@ require "termios"
# :nodoc:
module Crystal::System::FileDescriptor
- include IO::Evented
+ {% if IO.has_constant?(:Evented) %}
+ include IO::Evented
+ {% end %}
# Platform-specific type to represent a file descriptor handle to the operating
# system.
@@ -120,7 +121,14 @@ module Crystal::System::FileDescriptor
file_descriptor_close
end
- def file_descriptor_close : Nil
+ def file_descriptor_close(&) : Nil
+ # It would usually be set by IO::Buffered#unbuffered_close but we sometimes
+ # close file descriptors directly (i.e. signal/process pipes) and the IO
+ # object wouldn't be marked as closed, leading IO::FileDescriptor#finalize
+ # to try to close the fd again (pointless) and lead to other issues if we
+ # try to do more cleanup in the finalizer (error)
+ @closed = true
+
# Clear the @volatile_fd before actually closing it in order to
# reduce the chance of reading an outdated fd value
_fd = @volatile_fd.swap(-1)
@@ -130,11 +138,17 @@ module Crystal::System::FileDescriptor
when Errno::EINTR, Errno::EINPROGRESS
# ignore
else
- raise IO::Error.from_errno("Error closing file", target: self)
+ yield
end
end
end
+ def file_descriptor_close
+ file_descriptor_close do
+ raise IO::Error.from_errno("Error closing file", target: self)
+ end
+ end
+
private def system_flock_shared(blocking)
flock LibC::FlockOp::SH, blocking
end
@@ -152,7 +166,7 @@ module Crystal::System::FileDescriptor
if retry
until flock(op)
- sleep 0.1
+ sleep 0.1.seconds
end
else
flock(op) || raise IO::Error.from_errno("Error applying file lock: file is already locked", target: self)
@@ -190,6 +204,14 @@ module Crystal::System::FileDescriptor
end
def self.pipe(read_blocking, write_blocking)
+ pipe_fds = system_pipe
+ r = IO::FileDescriptor.new(pipe_fds[0], read_blocking)
+ w = IO::FileDescriptor.new(pipe_fds[1], write_blocking)
+ w.sync = true
+ {r, w}
+ end
+
+ def self.system_pipe : StaticArray(LibC::Int, 2)
pipe_fds = uninitialized StaticArray(LibC::Int, 2)
{% if LibC.has_method?(:pipe2) %}
@@ -206,18 +228,14 @@ module Crystal::System::FileDescriptor
end
{% end %}
- r = IO::FileDescriptor.new(pipe_fds[0], read_blocking)
- w = IO::FileDescriptor.new(pipe_fds[1], write_blocking)
- w.sync = true
-
- {r, w}
+ pipe_fds
end
- def self.pread(fd, buffer, offset)
- bytes_read = LibC.pread(fd, buffer, buffer.size, offset).to_i64
+ def self.pread(file, buffer, offset)
+ bytes_read = LibC.pread(file.fd, buffer, buffer.size, offset).to_i64
if bytes_read == -1
- raise IO::Error.from_errno "Error reading file"
+ raise IO::Error.from_errno("Error reading file", target: file)
end
bytes_read
@@ -242,6 +260,20 @@ module Crystal::System::FileDescriptor
io
end
+ # Helper to write *size* values at *pointer* to a given *fd*.
+ def self.write_fully(fd : LibC::Int, pointer : Pointer, size : Int32 = 1) : Nil
+ write_fully(fd, Slice.new(pointer, size).unsafe_slice_of(UInt8))
+ end
+
+ # Helper to fully write a slice to a given *fd*.
+ def self.write_fully(fd : LibC::Int, slice : Slice(UInt8)) : Nil
+ until slice.size == 0
+ size = LibC.write(fd, slice, slice.size)
+ break if size == -1
+ slice += size
+ end
+ end
+
private def system_echo(enable : Bool, mode = nil)
new_mode = mode || FileDescriptor.tcgetattr(fd)
flags = LibC::ECHO | LibC::ECHOE | LibC::ECHOK | LibC::ECHONL
diff --git a/src/crystal/system/unix/getrandom.cr b/src/crystal/system/unix/getrandom.cr
index 229716a3d846..6ad217c7cbf2 100644
--- a/src/crystal/system/unix/getrandom.cr
+++ b/src/crystal/system/unix/getrandom.cr
@@ -1,116 +1,39 @@
-{% skip_file unless flag?(:linux) %}
-
-require "c/unistd"
-require "./syscall"
-
-{% if flag?(:interpreted) %}
- lib LibC
- fun getrandom(buf : Void*, buflen : SizeT, flags : UInt32) : LibC::SSizeT
- end
-
- module Crystal::System::Syscall
- GRND_NONBLOCK = 1u32
-
- # TODO: Implement syscall for interpreter
- def self.getrandom(buf : UInt8*, buflen : LibC::SizeT, flags : UInt32) : LibC::SSizeT
- LibC.getrandom(buf, buflen, flags)
- end
- end
-{% end %}
+require "c/sys/random"
module Crystal::System::Random
- @@initialized = false
- @@getrandom_available = false
- @@urandom : ::File?
-
- private def self.init
- @@initialized = true
-
- if has_sys_getrandom
- @@getrandom_available = true
- else
- urandom = ::File.open("/dev/urandom", "r")
- return unless urandom.info.type.character_device?
-
- urandom.close_on_exec = true
- urandom.read_buffering = false # don't buffer bytes
- @@urandom = urandom
- end
- end
-
- private def self.has_sys_getrandom
- sys_getrandom(Bytes.new(16))
- true
- rescue
- false
- end
-
# Reads n random bytes using the Linux `getrandom(2)` syscall.
- def self.random_bytes(buf : Bytes) : Nil
- init unless @@initialized
-
- if @@getrandom_available
- getrandom(buf)
- elsif urandom = @@urandom
- urandom.read_fully(buf)
- else
- raise "Failed to access secure source to generate random bytes!"
- end
+ def self.random_bytes(buffer : Bytes) : Nil
+ getrandom(buffer)
end
def self.next_u : UInt8
- init unless @@initialized
-
- if @@getrandom_available
- buf = uninitialized UInt8
- getrandom(pointerof(buf).to_slice(1))
- buf
- elsif urandom = @@urandom
- urandom.read_byte.not_nil!
- else
- raise "Failed to access secure source to generate random bytes!"
- end
+ buffer = uninitialized UInt8
+ getrandom(pointerof(buffer).to_slice(1))
+ buffer
end
# Reads n random bytes using the Linux `getrandom(2)` syscall.
- private def self.getrandom(buf)
+ private def self.getrandom(buffer)
# getrandom(2) may only read up to 256 bytes at once without being
# interrupted or returning early
chunk_size = 256
- while buf.size > 0
- if buf.size < chunk_size
- chunk_size = buf.size
- end
+ while buffer.size > 0
+ read_bytes = 0
- read_bytes = sys_getrandom(buf[0, chunk_size])
+ loop do
+ # pass GRND_NONBLOCK flag so that it fails with EAGAIN if the requested
+ # entropy was not available
+ read_bytes = LibC.getrandom(buffer, buffer.size.clamp(..chunk_size), LibC::GRND_NONBLOCK)
+ break unless read_bytes == -1
- buf += read_bytes
- end
- end
+ err = Errno.value
+ raise RuntimeError.from_os_error("getrandom", err) unless err.in?(Errno::EINTR, Errno::EAGAIN)
- # Low-level wrapper for the `getrandom(2)` syscall, returns the number of
- # bytes read or the errno as a negative number if an error occurred (or the
- # syscall isn't available). The GRND_NONBLOCK=1 flag is passed as last argument,
- # so that it returns -EAGAIN if the requested entropy was not available.
- #
- # We use the kernel syscall instead of the `getrandom` C function so any
- # binary compiled for Linux will always use getrandom if the kernel is 3.17+
- # and silently fallback to read from /dev/urandom if not (so it's more
- # portable).
- private def self.sys_getrandom(buf : Bytes)
- loop do
- read_bytes = Syscall.getrandom(buf.to_unsafe, LibC::SizeT.new(buf.size), Syscall::GRND_NONBLOCK)
- if read_bytes < 0
- err = Errno.new(-read_bytes.to_i)
- if err.in?(Errno::EINTR, Errno::EAGAIN)
- ::Fiber.yield
- else
- raise RuntimeError.from_os_error("getrandom", err)
- end
- else
- return read_bytes
+ ::Fiber.yield
end
+
+ buffer += read_bytes
end
end
end
diff --git a/src/crystal/system/unix/group.cr b/src/crystal/system/unix/group.cr
index d7d408f77608..d4562cc7d286 100644
--- a/src/crystal/system/unix/group.cr
+++ b/src/crystal/system/unix/group.cr
@@ -4,11 +4,22 @@ require "../unix"
module Crystal::System::Group
private GETGR_R_SIZE_MAX = 1024 * 16
- private def from_struct(grp)
- new(String.new(grp.gr_name), grp.gr_gid.to_s)
+ def initialize(@name : String, @id : String)
end
- private def from_name?(groupname : String)
+ def system_name
+ @name
+ end
+
+ def system_id
+ @id
+ end
+
+ private def self.from_struct(grp)
+ ::System::Group.new(String.new(grp.gr_name), grp.gr_gid.to_s)
+ end
+
+ def self.from_name?(groupname : String)
groupname.check_no_null_byte
grp = uninitialized LibC::Group
@@ -21,7 +32,7 @@ module Crystal::System::Group
end
end
- private def from_id?(groupid : String)
+ def self.from_id?(groupid : String)
groupid = groupid.to_u32?
return unless groupid
diff --git a/src/crystal/system/unix/kqueue.cr b/src/crystal/system/unix/kqueue.cr
new file mode 100644
index 000000000000..9f7cb1f414b9
--- /dev/null
+++ b/src/crystal/system/unix/kqueue.cr
@@ -0,0 +1,89 @@
+require "c/sys/event"
+
+struct Crystal::System::Kqueue
+ @kq : LibC::Int
+
+ def initialize
+ @kq =
+ {% if LibC.has_method?(:kqueue1) %}
+ LibC.kqueue1(LibC::O_CLOEXEC)
+ {% else %}
+ LibC.kqueue
+ {% end %}
+ if @kq == -1
+ function_name = {% if LibC.has_method?(:kqueue1) %} "kqueue1" {% else %} "kqueue" {% end %}
+ raise RuntimeError.from_errno(function_name)
+ end
+ end
+
+ # Helper to register a single event. Returns immediately.
+ def kevent(ident, filter, flags, fflags = 0, data = 0, udata = nil, &) : Nil
+ kevent = uninitialized LibC::Kevent
+ Kqueue.set pointerof(kevent), ident, filter, flags, fflags, data, udata
+ ret = LibC.kevent(@kq, pointerof(kevent), 1, nil, 0, nil)
+ yield if ret == -1
+ end
+
+ # Helper to register a single event. Returns immediately.
+ def kevent(ident, filter, flags, fflags = 0, data = 0, udata = nil) : Nil
+ kevent(ident, filter, flags, fflags, data, udata) do
+ raise RuntimeError.from_errno("kevent")
+ end
+ end
+
+ # Helper to register multiple *changes*. Returns immediately.
+ def kevent(changes : Slice(LibC::Kevent), &) : Nil
+ ret = LibC.kevent(@kq, changes.to_unsafe, changes.size, nil, 0, nil)
+ yield if ret == -1
+ end
+
+ # Waits for registered events to become active. Returns a subslice to
+ # *events*.
+ #
+ # Timeout is relative to now; blocks indefinitely if `nil`; returns
+ # immediately if zero.
+ def wait(events : Slice(LibC::Kevent), timeout : ::Time::Span? = nil) : Slice(LibC::Kevent)
+ if timeout
+ ts = uninitialized LibC::Timespec
+ ts.tv_sec = typeof(ts.tv_sec).new!(timeout.@seconds)
+ ts.tv_nsec = typeof(ts.tv_nsec).new!(timeout.@nanoseconds)
+ tsp = pointerof(ts)
+ else
+ tsp = Pointer(LibC::Timespec).null
+ end
+
+ changes = Slice(LibC::Kevent).empty
+ count = 0
+
+ loop do
+ count = LibC.kevent(@kq, changes.to_unsafe, changes.size, events.to_unsafe, events.size, tsp)
+ break unless count == -1
+
+ if Errno.value == Errno::EINTR
+ # retry when waiting indefinitely, return otherwise
+ break if timeout
+ else
+ raise RuntimeError.from_errno("kevent")
+ end
+ end
+
+ events[0, count.clamp(0..)]
+ end
+
+ def close : Nil
+ LibC.close(@kq)
+ end
+
+ @[AlwaysInline]
+ def self.set(kevent : LibC::Kevent*, ident, filter, flags, fflags = 0, data = 0, udata = nil) : Nil
+ kevent.value.ident = ident
+ kevent.value.filter = filter
+ kevent.value.flags = flags
+ kevent.value.fflags = fflags
+ kevent.value.data = data
+ kevent.value.udata = udata ? udata.as(Void*) : Pointer(Void).null
+ {% if LibC::Kevent.has_method?(:ext) %}
+ kevent.value.ext.fill(0)
+ {% end %}
+ end
+end
diff --git a/src/crystal/system/unix/main.cr b/src/crystal/system/unix/main.cr
new file mode 100644
index 000000000000..1592a6342002
--- /dev/null
+++ b/src/crystal/system/unix/main.cr
@@ -0,0 +1,11 @@
+require "c/stdlib"
+
+# Prefer explicit exit over returning the status, so we are free to resume the
+# main thread's fiber on any thread, without occuring a weird behavior where
+# another thread returns from main when the caller might expect the main thread
+# to be the one returning.
+
+fun main(argc : Int32, argv : UInt8**) : Int32
+ status = Crystal.main(argc, argv)
+ LibC.exit(status)
+end
diff --git a/src/crystal/system/unix/process.cr b/src/crystal/system/unix/process.cr
index 83f95cc8648c..a4b5ff45c0cc 100644
--- a/src/crystal/system/unix/process.cr
+++ b/src/crystal/system/unix/process.cr
@@ -176,7 +176,14 @@ struct Crystal::System::Process
newmask = uninitialized LibC::SigsetT
oldmask = uninitialized LibC::SigsetT
+ # block signals while we fork, so the child process won't forward signals it
+ # may receive to the parent through the signal pipe, but make sure to not
+ # block stop-the-world signals as it appears to create deadlocks in glibc
+ # for example; this is safe because these signal handlers musn't be
+ # registered through `Signal.trap` but directly through `sigaction`.
LibC.sigfillset(pointerof(newmask))
+ LibC.sigdelset(pointerof(newmask), System::Thread.sig_suspend)
+ LibC.sigdelset(pointerof(newmask), System::Thread.sig_resume)
ret = LibC.pthread_sigmask(LibC::SIG_SETMASK, pointerof(newmask), pointerof(oldmask))
raise RuntimeError.from_errno("Failed to disable signals") unless ret == 0
@@ -185,6 +192,9 @@ struct Crystal::System::Process
# child:
pid = nil
if will_exec
+ # notify event loop
+ Crystal::EventLoop.current.after_fork_before_exec
+
# reset signal handlers, then sigmask (inherited on exec):
Crystal::System::Signal.after_fork_before_exec
LibC.sigemptyset(pointerof(newmask))
@@ -231,43 +241,47 @@ struct Crystal::System::Process
end
def self.spawn(command_args, env, clear_env, input, output, error, chdir)
- reader_pipe, writer_pipe = IO.pipe
+ r, w = FileDescriptor.system_pipe
pid = self.fork(will_exec: true)
if !pid
+ LibC.close(r)
begin
- reader_pipe.close
- writer_pipe.close_on_exec = true
self.try_replace(command_args, env, clear_env, input, output, error, chdir)
- writer_pipe.write_byte(1)
- writer_pipe.write_bytes(Errno.value.to_i)
+ byte = 1_u8
+ errno = Errno.value.to_i32
+ FileDescriptor.write_fully(w, pointerof(byte))
+ FileDescriptor.write_fully(w, pointerof(errno))
rescue ex
- writer_pipe.write_byte(0)
- writer_pipe.write_bytes(ex.message.try(&.bytesize) || 0)
- writer_pipe << ex.message
- writer_pipe.close
+ byte = 0_u8
+ message = ex.inspect_with_backtrace
+ FileDescriptor.write_fully(w, pointerof(byte))
+ FileDescriptor.write_fully(w, message.to_slice)
ensure
+ LibC.close(w)
LibC._exit 127
end
end
- writer_pipe.close
+ LibC.close(w)
+ reader_pipe = IO::FileDescriptor.new(r, blocking: false)
+
begin
case reader_pipe.read_byte
when nil
# Pipe was closed, no error
when 0
# Error message coming
- message_size = reader_pipe.read_bytes(Int32)
- if message_size > 0
- message = String.build(message_size) { |io| IO.copy(reader_pipe, io, message_size) }
- end
- reader_pipe.close
+ message = reader_pipe.gets_to_end
raise RuntimeError.new("Error executing process: '#{command_args[0]}': #{message}")
when 1
# Errno coming
- errno = Errno.new(reader_pipe.read_bytes(Int32))
- self.raise_exception_from_errno(command_args[0], errno)
+ # can't use IO#read_bytes(Int32) because we skipped system/network
+ # endianness check when writing the integer while read_bytes would;
+ # we thus read it in the same as order as written
+ buf = uninitialized StaticArray(UInt8, 4)
+ reader_pipe.read_fully(buf.to_slice)
+ raise_exception_from_errno(command_args[0], Errno.new(buf.unsafe_as(Int32)))
else
raise RuntimeError.new("BUG: Invalid error response received from subprocess")
end
@@ -338,15 +352,18 @@ struct Crystal::System::Process
private def self.reopen_io(src_io : IO::FileDescriptor, dst_io : IO::FileDescriptor)
if src_io.closed?
- dst_io.close
- return
- end
+ Crystal::EventLoop.remove(dst_io)
+ dst_io.file_descriptor_close
+ else
+ src_io = to_real_fd(src_io)
- src_io = to_real_fd(src_io)
+ # dst_io.reopen(src_io)
+ ret = LibC.dup2(src_io.fd, dst_io.fd)
+ raise IO::Error.from_errno("dup2") if ret == -1
- dst_io.reopen(src_io)
- dst_io.blocking = true
- dst_io.close_on_exec = false
+ dst_io.blocking = true
+ dst_io.close_on_exec = false
+ end
end
private def self.to_real_fd(fd : IO::FileDescriptor)
diff --git a/src/crystal/system/unix/pthread.cr b/src/crystal/system/unix/pthread.cr
index d38e52ee012a..e91990689084 100644
--- a/src/crystal/system/unix/pthread.cr
+++ b/src/crystal/system/unix/pthread.cr
@@ -1,5 +1,6 @@
require "c/pthread"
require "c/sched"
+require "../panic"
module Crystal::System::Thread
alias Handle = LibC::PthreadT
@@ -8,20 +9,45 @@ module Crystal::System::Thread
@system_handle
end
+ protected setter system_handle
+
private def init_handle
- # NOTE: the thread may start before `pthread_create` returns, so
- # `@system_handle` must be set as soon as possible; we cannot use a separate
- # handle and assign it to `@system_handle`, which would have been too late
+ # NOTE: `@system_handle` needs to be set here too, not just in
+ # `.thread_proc`, since the current thread might progress first; the value
+ # of `LibC.pthread_self` inside the new thread must be equal to this
+ # `@system_handle` after `pthread_create` returns
ret = GC.pthread_create(
thread: pointerof(@system_handle),
attr: Pointer(LibC::PthreadAttrT).null,
- start: ->(data : Void*) { data.as(::Thread).start; Pointer(Void).null },
+ start: ->Thread.thread_proc(Void*),
arg: self.as(Void*),
)
raise RuntimeError.from_os_error("pthread_create", Errno.new(ret)) unless ret == 0
end
+ def self.init : Nil
+ {% if flag?(:musl) %}
+ @@main_handle = current_handle
+ {% elsif flag?(:openbsd) || flag?(:android) %}
+ ret = LibC.pthread_key_create(out current_key, nil)
+ raise RuntimeError.from_os_error("pthread_key_create", Errno.new(ret)) unless ret == 0
+ @@current_key = current_key
+ {% end %}
+ end
+
+ def self.thread_proc(data : Void*) : Void*
+ th = data.as(::Thread)
+
+ # `#start` calls `#stack_address`, which might read `@system_handle` before
+ # `GC.pthread_create` updates it in the original thread that spawned the
+ # current one, so we also assign to it here
+ th.system_handle = current_handle
+
+ th.start
+ Pointer(Void).null
+ end
+
def self.current_handle : Handle
LibC.pthread_self
end
@@ -37,13 +63,7 @@ module Crystal::System::Thread
# Android appears to support TLS to some degree, but executables fail with
# an underaligned TLS segment, see https://github.com/crystal-lang/crystal/issues/13951
{% if flag?(:openbsd) || flag?(:android) %}
- @@current_key : LibC::PthreadKeyT
-
- @@current_key = begin
- ret = LibC.pthread_key_create(out current_key, nil)
- raise RuntimeError.from_os_error("pthread_key_create", Errno.new(ret)) unless ret == 0
- current_key
- end
+ @@current_key = uninitialized LibC::PthreadKeyT
def self.current_thread : ::Thread
if ptr = LibC.pthread_getspecific(@@current_key)
@@ -68,11 +88,18 @@ module Crystal::System::Thread
end
{% else %}
@[ThreadLocal]
- class_property current_thread : ::Thread { ::Thread.new }
+ @@current_thread : ::Thread?
+
+ def self.current_thread : ::Thread
+ @@current_thread ||= ::Thread.new
+ end
def self.current_thread? : ::Thread?
@@current_thread
end
+
+ def self.current_thread=(@@current_thread : ::Thread)
+ end
{% end %}
def self.sleep(time : ::Time::Span) : Nil
@@ -115,11 +142,26 @@ module Crystal::System::Thread
ret = LibC.pthread_attr_destroy(pointerof(attr))
raise RuntimeError.from_os_error("pthread_attr_destroy", Errno.new(ret)) unless ret == 0
{% elsif flag?(:linux) %}
- if LibC.pthread_getattr_np(@system_handle, out attr) == 0
- LibC.pthread_attr_getstack(pointerof(attr), pointerof(address), out _)
- end
+ ret = LibC.pthread_getattr_np(@system_handle, out attr)
+ raise RuntimeError.from_os_error("pthread_getattr_np", Errno.new(ret)) unless ret == 0
+
+ LibC.pthread_attr_getstack(pointerof(attr), pointerof(address), out stack_size)
+
ret = LibC.pthread_attr_destroy(pointerof(attr))
raise RuntimeError.from_os_error("pthread_attr_destroy", Errno.new(ret)) unless ret == 0
+
+ # with musl-libc, the main thread does not respect `rlimit -Ss` and
+ # instead returns the same default stack size as non-default threads, so
+ # we obtain the rlimit to correct the stack address manually
+ {% if flag?(:musl) %}
+ if Thread.current_is_main?
+ if LibC.getrlimit(LibC::RLIMIT_STACK, out rlim) == 0
+ address = address + stack_size - rlim.rlim_cur
+ else
+ raise RuntimeError.from_errno("getrlimit")
+ end
+ end
+ {% end %}
{% elsif flag?(:openbsd) %}
ret = LibC.pthread_stackseg_np(@system_handle, out stack)
raise RuntimeError.from_os_error("pthread_stackseg_np", Errno.new(ret)) unless ret == 0
@@ -137,6 +179,14 @@ module Crystal::System::Thread
address
end
+ {% if flag?(:musl) %}
+ @@main_handle = uninitialized Handle
+
+ def self.current_is_main?
+ current_handle == @@main_handle
+ end
+ {% end %}
+
# Warning: must be called from the current thread itself, because Darwin
# doesn't allow to set the name of any thread but the current one!
private def system_name=(name : String) : String
@@ -153,6 +203,97 @@ module Crystal::System::Thread
{% end %}
name
end
+
+ @suspended = Atomic(Bool).new(false)
+
+ def self.init_suspend_resume : Nil
+ install_sig_suspend_signal_handler
+ install_sig_resume_signal_handler
+ end
+
+ private def self.install_sig_suspend_signal_handler
+ action = LibC::Sigaction.new
+ action.sa_flags = LibC::SA_SIGINFO
+ action.sa_sigaction = LibC::SigactionHandlerT.new do |_, _, _|
+ # notify that the thread has been interrupted
+ Thread.current_thread.@suspended.set(true)
+
+ # block all signals but SIG_RESUME
+ mask = uninitialized LibC::SigsetT
+ LibC.sigfillset(pointerof(mask))
+ LibC.sigdelset(pointerof(mask), SIG_RESUME)
+
+ # suspend the thread until it receives the SIG_RESUME signal
+ LibC.sigsuspend(pointerof(mask))
+ end
+ LibC.sigemptyset(pointerof(action.@sa_mask))
+ LibC.sigaction(SIG_SUSPEND, pointerof(action), nil)
+ end
+
+ private def self.install_sig_resume_signal_handler
+ action = LibC::Sigaction.new
+ action.sa_flags = 0
+ action.sa_sigaction = LibC::SigactionHandlerT.new do |_, _, _|
+ # do nothing (a handler is still required to receive the signal)
+ end
+ LibC.sigemptyset(pointerof(action.@sa_mask))
+ LibC.sigaction(SIG_RESUME, pointerof(action), nil)
+ end
+
+ private def system_suspend : Nil
+ @suspended.set(false)
+
+ if LibC.pthread_kill(@system_handle, SIG_SUSPEND) == -1
+ System.panic("pthread_kill()", Errno.value)
+ end
+ end
+
+ private def system_wait_suspended : Nil
+ until @suspended.get
+ Thread.yield_current
+ end
+ end
+
+ private def system_resume : Nil
+ if LibC.pthread_kill(@system_handle, SIG_RESUME) == -1
+ System.panic("pthread_kill()", Errno.value)
+ end
+ end
+
+ # the suspend/resume signals try to follow BDWGC but aren't exact (e.g. it may
+ # use SIGUSR1 and SIGUSR2 on FreeBSD instead of SIGRT).
+
+ private SIG_SUSPEND =
+ {% if flag?(:linux) %}
+ LibC::SIGPWR
+ {% elsif LibC.has_constant?(:SIGRTMIN) %}
+ LibC::SIGRTMIN + 6
+ {% else %}
+ LibC::SIGXFSZ
+ {% end %}
+
+ private SIG_RESUME =
+ {% if LibC.has_constant?(:SIGRTMIN) %}
+ LibC::SIGRTMIN + 5
+ {% else %}
+ LibC::SIGXCPU
+ {% end %}
+
+ def self.sig_suspend : ::Signal
+ if (gc = GC).responds_to?(:sig_suspend)
+ gc.sig_suspend
+ else
+ ::Signal.new(SIG_SUSPEND)
+ end
+ end
+
+ def self.sig_resume : ::Signal
+ if (gc = GC).responds_to?(:sig_resume)
+ gc.sig_resume
+ else
+ ::Signal.new(SIG_RESUME)
+ end
+ end
end
# In musl (alpine) the calls to unwind API segfaults
diff --git a/src/crystal/system/unix/signal.cr b/src/crystal/system/unix/signal.cr
index 1d1e885fc71d..12804ea00267 100644
--- a/src/crystal/system/unix/signal.cr
+++ b/src/crystal/system/unix/signal.cr
@@ -22,17 +22,21 @@ module Crystal::System::Signal
@@mutex.synchronize do
unless @@handlers[signal]?
@@sigset << signal
- action = LibC::Sigaction.new
-
- # restart some interrupted syscalls (read, write, accept, ...) instead
- # of returning EINTR:
- action.sa_flags = LibC::SA_RESTART
-
- action.sa_sigaction = LibC::SigactionHandlerT.new do |value, _, _|
- writer.write_bytes(value) unless writer.closed?
- end
- LibC.sigemptyset(pointerof(action.@sa_mask))
- LibC.sigaction(signal, pointerof(action), nil)
+ {% if flag?(:interpreted) && Crystal::Interpreter.has_method?(:signal) %}
+ Crystal::Interpreter.signal(signal.value, 2)
+ {% else %}
+ action = LibC::Sigaction.new
+
+ # restart some interrupted syscalls (read, write, accept, ...) instead
+ # of returning EINTR:
+ action.sa_flags = LibC::SA_RESTART
+
+ action.sa_sigaction = LibC::SigactionHandlerT.new do |value, _, _|
+ FileDescriptor.write_fully(writer.fd, pointerof(value)) unless writer.closed?
+ end
+ LibC.sigemptyset(pointerof(action.@sa_mask))
+ LibC.sigaction(signal, pointerof(action), nil)
+ {% end %}
end
@@handlers[signal] = handler
end
@@ -62,14 +66,23 @@ module Crystal::System::Signal
else
@@mutex.synchronize do
@@handlers.delete(signal)
- LibC.signal(signal, handler)
+ {% if flag?(:interpreted) && Crystal::Interpreter.has_method?(:signal) %}
+ h = case handler
+ when LibC::SIG_DFL then 0
+ when LibC::SIG_IGN then 1
+ else 2
+ end
+ Crystal::Interpreter.signal(signal.value, h)
+ {% else %}
+ LibC.signal(signal, handler)
+ {% end %}
@@sigset.delete(signal)
end
end
end
private def self.start_loop
- spawn(name: "Signal Loop") do
+ spawn(name: "signal-loop") do
loop do
value = reader.read_bytes(Int32)
rescue IO::Error
@@ -97,7 +110,10 @@ module Crystal::System::Signal
# Replaces the signal pipe so the child process won't share the file
# descriptors of the parent process and send it received signals.
def self.after_fork
- @@pipe.each(&.file_descriptor_close)
+ @@pipe.each do |pipe_io|
+ Crystal::EventLoop.remove(pipe_io)
+ pipe_io.file_descriptor_close { }
+ end
ensure
@@pipe = IO.pipe(read_blocking: false, write_blocking: true)
end
@@ -116,7 +132,13 @@ module Crystal::System::Signal
# sub-process.
def self.after_fork_before_exec
::Signal.each do |signal|
- LibC.signal(signal, LibC::SIG_DFL) if @@sigset.includes?(signal)
+ next unless @@sigset.includes?(signal)
+
+ {% if flag?(:interpreted) && Crystal::Interpreter.has_method?(:signal) %}
+ Crystal::Interpreter.signal(signal.value, 0)
+ {% else %}
+ LibC.signal(signal, LibC::SIG_DFL)
+ {% end %}
end
ensure
{% unless flag?(:preview_mt) %}
@@ -132,6 +154,14 @@ module Crystal::System::Signal
@@pipe[1]
end
+ {% unless flag?(:interpreted) %}
+ # :nodoc:
+ def self.writer=(writer : IO::FileDescriptor)
+ @@pipe = {@@pipe[0], writer}
+ writer
+ end
+ {% end %}
+
private def self.fatal(message : String)
STDERR.puts("FATAL: #{message}, exiting")
STDERR.flush
@@ -175,7 +205,16 @@ module Crystal::System::Signal
return unless @@setup_default_handlers.test_and_set
@@sigset.clear
start_loop
- ::Signal::PIPE.ignore
+
+ {% if flag?(:interpreted) && Interpreter.has_method?(:signal_descriptor) %}
+ # replace the interpreter's writer pipe with the interpreted, so signals
+ # will be received by the interpreter, but handled by the interpreted
+ # signal loop
+ Crystal::Interpreter.signal_descriptor(@@pipe[1].fd)
+ {% else %}
+ ::Signal::PIPE.ignore
+ {% end %}
+
::Signal::CHLD.reset
end
diff --git a/src/crystal/system/unix/socket.cr b/src/crystal/system/unix/socket.cr
index 33ac70659b9f..2ca502aa28f8 100644
--- a/src/crystal/system/unix/socket.cr
+++ b/src/crystal/system/unix/socket.cr
@@ -1,10 +1,11 @@
require "c/netdb"
require "c/netinet/tcp"
require "c/sys/socket"
-require "io/evented"
module Crystal::System::Socket
- include IO::Evented
+ {% if IO.has_constant?(:Evented) %}
+ include IO::Evented
+ {% end %}
alias Handle = Int32
@@ -24,6 +25,9 @@ module Crystal::System::Socket
end
private def initialize_handle(fd)
+ {% if Crystal::EventLoop.has_constant?(:Polling) %}
+ @__evloop_data = Crystal::EventLoop::Polling::Arena::INVALID_INDEX
+ {% end %}
end
# Tries to bind the socket to a local address.
@@ -208,6 +212,10 @@ module Crystal::System::Socket
# always lead to undefined results. This is not specific to libevent.
event_loop.close(self)
+ socket_close
+ end
+
+ private def socket_close(&)
# Clear the @volatile_fd before actually closing it in order to
# reduce the chance of reading an outdated fd value
fd = @volatile_fd.swap(-1)
@@ -219,11 +227,17 @@ module Crystal::System::Socket
when Errno::EINTR, Errno::EINPROGRESS
# ignore
else
- raise ::Socket::Error.from_errno("Error closing socket")
+ yield
end
end
end
+ private def socket_close
+ socket_close do
+ raise ::Socket::Error.from_errno("Error closing socket")
+ end
+ end
+
private def system_local_address
sockaddr6 = uninitialized LibC::SockaddrIn6
sockaddr = pointerof(sockaddr6).as(LibC::Sockaddr*)
diff --git a/src/crystal/system/unix/time.cr b/src/crystal/system/unix/time.cr
index 2ead3bdb0fa2..5ffcc6f373a2 100644
--- a/src/crystal/system/unix/time.cr
+++ b/src/crystal/system/unix/time.cr
@@ -39,9 +39,8 @@ module Crystal::System::Time
nanoseconds = total_nanoseconds.remainder(NANOSECONDS_PER_SECOND)
{seconds.to_i64, nanoseconds.to_i32}
{% else %}
- if LibC.clock_gettime(LibC::CLOCK_MONOTONIC, out tp) == 1
- raise RuntimeError.from_errno("clock_gettime(CLOCK_MONOTONIC)")
- end
+ ret = LibC.clock_gettime(LibC::CLOCK_MONOTONIC, out tp)
+ raise RuntimeError.from_errno("clock_gettime(CLOCK_MONOTONIC)") unless ret == 0
{tp.tv_sec.to_i64, tp.tv_nsec.to_i32}
{% end %}
end
diff --git a/src/crystal/system/unix/timerfd.cr b/src/crystal/system/unix/timerfd.cr
new file mode 100644
index 000000000000..34edbbec7482
--- /dev/null
+++ b/src/crystal/system/unix/timerfd.cr
@@ -0,0 +1,33 @@
+require "c/sys/timerfd"
+
+struct Crystal::System::TimerFD
+ getter fd : Int32
+
+ # Create a `timerfd` instance set to the monotonic clock.
+ def initialize
+ @fd = LibC.timerfd_create(LibC::CLOCK_MONOTONIC, LibC::TFD_CLOEXEC)
+ raise RuntimeError.from_errno("timerfd_settime") if @fd == -1
+ end
+
+ # Arm (start) the timer to run at *time* (absolute time).
+ def set(time : ::Time::Span) : Nil
+ itimerspec = uninitialized LibC::Itimerspec
+ itimerspec.it_interval.tv_sec = 0
+ itimerspec.it_interval.tv_nsec = 0
+ itimerspec.it_value.tv_sec = typeof(itimerspec.it_value.tv_sec).new!(time.@seconds)
+ itimerspec.it_value.tv_nsec = typeof(itimerspec.it_value.tv_nsec).new!(time.@nanoseconds)
+ ret = LibC.timerfd_settime(@fd, LibC::TFD_TIMER_ABSTIME, pointerof(itimerspec), nil)
+ raise RuntimeError.from_errno("timerfd_settime") if ret == -1
+ end
+
+ # Disarm (stop) the timer.
+ def cancel : Nil
+ itimerspec = LibC::Itimerspec.new
+ ret = LibC.timerfd_settime(@fd, LibC::TFD_TIMER_ABSTIME, pointerof(itimerspec), nil)
+ raise RuntimeError.from_errno("timerfd_settime") if ret == -1
+ end
+
+ def close
+ LibC.close(@fd)
+ end
+end
diff --git a/src/crystal/system/unix/urandom.cr b/src/crystal/system/unix/urandom.cr
index 7ac025f43e6b..fe81129a8ade 100644
--- a/src/crystal/system/unix/urandom.cr
+++ b/src/crystal/system/unix/urandom.cr
@@ -1,5 +1,3 @@
-{% skip_file unless flag?(:unix) && !flag?(:netbsd) && !flag?(:openbsd) && !flag?(:linux) %}
-
module Crystal::System::Random
@@initialized = false
@@urandom : ::File?
diff --git a/src/crystal/system/unix/user.cr b/src/crystal/system/unix/user.cr
index 8e4f16e8c1c4..c1f91d0f118c 100644
--- a/src/crystal/system/unix/user.cr
+++ b/src/crystal/system/unix/user.cr
@@ -4,14 +4,41 @@ require "../unix"
module Crystal::System::User
GETPW_R_SIZE_MAX = 1024 * 16
- private def from_struct(pwd)
+ def initialize(@username : String, @id : String, @group_id : String, @name : String, @home_directory : String, @shell : String)
+ end
+
+ def system_username
+ @username
+ end
+
+ def system_id
+ @id
+ end
+
+ def system_group_id
+ @group_id
+ end
+
+ def system_name
+ @name
+ end
+
+ def system_home_directory
+ @home_directory
+ end
+
+ def system_shell
+ @shell
+ end
+
+ private def self.from_struct(pwd)
username = String.new(pwd.pw_name)
# `pw_gecos` is not part of POSIX and bionic for example always leaves it null
user = pwd.pw_gecos ? String.new(pwd.pw_gecos).partition(',')[0] : username
- new(username, pwd.pw_uid.to_s, pwd.pw_gid.to_s, user, String.new(pwd.pw_dir), String.new(pwd.pw_shell))
+ ::System::User.new(username, pwd.pw_uid.to_s, pwd.pw_gid.to_s, user, String.new(pwd.pw_dir), String.new(pwd.pw_shell))
end
- private def from_username?(username : String)
+ def self.from_username?(username : String)
username.check_no_null_byte
pwd = uninitialized LibC::Passwd
@@ -24,7 +51,7 @@ module Crystal::System::User
end
end
- private def from_id?(id : String)
+ def self.from_id?(id : String)
id = id.to_u32?
return unless id
diff --git a/src/crystal/system/user.cr b/src/crystal/system/user.cr
index ecee92c8dcb5..88766496a9d8 100644
--- a/src/crystal/system/user.cr
+++ b/src/crystal/system/user.cr
@@ -1,7 +1,27 @@
+module Crystal::System::User
+ # def system_username : String
+
+ # def system_id : String
+
+ # def system_group_id : String
+
+ # def system_name : String
+
+ # def system_home_directory : String
+
+ # def system_shell : String
+
+ # def self.from_username?(username : String) : ::System::User?
+
+ # def self.from_id?(id : String) : ::System::User?
+end
+
{% if flag?(:wasi) %}
require "./wasi/user"
{% elsif flag?(:unix) %}
require "./unix/user"
+{% elsif flag?(:win32) %}
+ require "./win32/user"
{% else %}
{% raise "No Crystal::System::User implementation available" %}
{% end %}
diff --git a/src/crystal/system/wasi/addrinfo.cr b/src/crystal/system/wasi/addrinfo.cr
new file mode 100644
index 000000000000..29ba8e0b3cfc
--- /dev/null
+++ b/src/crystal/system/wasi/addrinfo.cr
@@ -0,0 +1,27 @@
+module Crystal::System::Addrinfo
+ alias Handle = NoReturn
+
+ protected def initialize(addrinfo : Handle)
+ raise NotImplementedError.new("Crystal::System::Addrinfo#initialize")
+ end
+
+ def system_ip_address : ::Socket::IPAddress
+ raise NotImplementedError.new("Crystal::System::Addrinfo#system_ip_address")
+ end
+
+ def to_unsafe
+ raise NotImplementedError.new("Crystal::System::Addrinfo#to_unsafe")
+ end
+
+ def self.getaddrinfo(domain, service, family, type, protocol, timeout) : Handle
+ raise NotImplementedError.new("Crystal::System::Addrinfo.getaddrinfo")
+ end
+
+ def self.next_addrinfo(addrinfo : Handle) : Handle
+ raise NotImplementedError.new("Crystal::System::Addrinfo.next_addrinfo")
+ end
+
+ def self.free_addrinfo(addrinfo : Handle)
+ raise NotImplementedError.new("Crystal::System::Addrinfo.free_addrinfo")
+ end
+end
diff --git a/src/crystal/system/wasi/fiber.cr b/src/crystal/system/wasi/fiber.cr
index 516fcc10a29a..8461bb15d00c 100644
--- a/src/crystal/system/wasi/fiber.cr
+++ b/src/crystal/system/wasi/fiber.cr
@@ -3,6 +3,9 @@ module Crystal::System::Fiber
LibC.malloc(stack_size)
end
+ def self.reset_stack(stack : Void*, stack_size : Int, protect : Bool) : Nil
+ end
+
def self.free_stack(stack : Void*, stack_size) : Nil
LibC.free(stack)
end
diff --git a/src/crystal/system/wasi/file.cr b/src/crystal/system/wasi/file.cr
index 0d197550e3db..a48463eded4e 100644
--- a/src/crystal/system/wasi/file.cr
+++ b/src/crystal/system/wasi/file.cr
@@ -2,6 +2,9 @@ require "../unix/file"
# :nodoc:
module Crystal::System::File
+ protected def system_set_mode(mode : String)
+ end
+
def self.chmod(path, mode)
raise NotImplementedError.new "Crystal::System::File.chmod"
end
diff --git a/src/crystal/system/wasi/group.cr b/src/crystal/system/wasi/group.cr
index 0aa09bd40aa8..c94fffa4fe6e 100644
--- a/src/crystal/system/wasi/group.cr
+++ b/src/crystal/system/wasi/group.cr
@@ -1,9 +1,17 @@
module Crystal::System::Group
- private def from_name?(groupname : String)
- raise NotImplementedError.new("Crystal::System::Group#from_name?")
+ def system_name
+ raise NotImplementedError.new("Crystal::System::Group#system_name")
end
- private def from_id?(groupid : String)
- raise NotImplementedError.new("Crystal::System::Group#from_id?")
+ def system_id
+ raise NotImplementedError.new("Crystal::System::Group#system_id")
+ end
+
+ def self.from_name?(groupname : String)
+ raise NotImplementedError.new("Crystal::System::Group.from_name?")
+ end
+
+ def self.from_id?(groupid : String)
+ raise NotImplementedError.new("Crystal::System::Group.from_id?")
end
end
diff --git a/src/crystal/system/wasi/main.cr b/src/crystal/system/wasi/main.cr
index 57ffd5f3f43c..9a3394809271 100644
--- a/src/crystal/system/wasi/main.cr
+++ b/src/crystal/system/wasi/main.cr
@@ -27,7 +27,8 @@ fun _start
LibWasi.proc_exit(status) if status != 0
end
-# `__main_argc_argv` is called by wasi-libc's `__main_void` with the program arguments.
+# `__main_argc_argv` is called by wasi-libc's `__main_void` with the program
+# arguments.
fun __main_argc_argv(argc : Int32, argv : UInt8**) : Int32
main(argc, argv)
end
diff --git a/src/crystal/system/wasi/thread.cr b/src/crystal/system/wasi/thread.cr
index 6f0c0cbe8260..d103c7d9fc44 100644
--- a/src/crystal/system/wasi/thread.cr
+++ b/src/crystal/system/wasi/thread.cr
@@ -1,6 +1,9 @@
module Crystal::System::Thread
alias Handle = Nil
+ def self.init : Nil
+ end
+
def self.new_handle(thread_obj : ::Thread) : Handle
raise NotImplementedError.new("Crystal::System::Thread.new_handle")
end
@@ -13,7 +16,16 @@ module Crystal::System::Thread
raise NotImplementedError.new("Crystal::System::Thread.yield_current")
end
- class_property current_thread : ::Thread { ::Thread.new }
+ def self.current_thread : ::Thread
+ @@current_thread ||= ::Thread.new
+ end
+
+ def self.current_thread? : ::Thread?
+ @@current_thread
+ end
+
+ def self.current_thread=(@@current_thread : ::Thread)
+ end
def self.sleep(time : ::Time::Span) : Nil
req = uninitialized LibC::Timespec
@@ -38,4 +50,19 @@ module Crystal::System::Thread
# TODO: Implement
Pointer(Void).null
end
+
+ def self.init_suspend_resume : Nil
+ end
+
+ private def system_suspend : Nil
+ raise NotImplementedError.new("Crystal::System::Thread.system_suspend")
+ end
+
+ private def system_wait_suspended : Nil
+ raise NotImplementedError.new("Crystal::System::Thread.system_wait_suspended")
+ end
+
+ private def system_resume : Nil
+ raise NotImplementedError.new("Crystal::System::Thread.system_resume")
+ end
end
diff --git a/src/crystal/system/wasi/user.cr b/src/crystal/system/wasi/user.cr
index 06415897000e..2d1c6e91b770 100644
--- a/src/crystal/system/wasi/user.cr
+++ b/src/crystal/system/wasi/user.cr
@@ -1,9 +1,33 @@
module Crystal::System::User
- private def from_username?(username : String)
- raise NotImplementedError.new("Crystal::System::User#from_username?")
+ def system_username
+ raise NotImplementedError.new("Crystal::System::User#system_username")
end
- private def from_id?(id : String)
- raise NotImplementedError.new("Crystal::System::User#from_id?")
+ def system_id
+ raise NotImplementedError.new("Crystal::System::User#system_id")
+ end
+
+ def system_group_id
+ raise NotImplementedError.new("Crystal::System::User#system_group_id")
+ end
+
+ def system_name
+ raise NotImplementedError.new("Crystal::System::User#system_name")
+ end
+
+ def system_home_directory
+ raise NotImplementedError.new("Crystal::System::User#system_home_directory")
+ end
+
+ def system_shell
+ raise NotImplementedError.new("Crystal::System::User#system_shell")
+ end
+
+ def self.from_username?(username : String)
+ raise NotImplementedError.new("Crystal::System::User.from_username?")
+ end
+
+ def self.from_id?(id : String)
+ raise NotImplementedError.new("Crystal::System::User.from_id?")
end
end
diff --git a/src/crystal/system/win32/addrinfo.cr b/src/crystal/system/win32/addrinfo.cr
new file mode 100644
index 000000000000..24cff9c9aec3
--- /dev/null
+++ b/src/crystal/system/win32/addrinfo.cr
@@ -0,0 +1,88 @@
+module Crystal::System::Addrinfo
+ alias Handle = LibC::ADDRINFOEXW*
+
+ @addr : LibC::SockaddrIn6
+
+ protected def initialize(addrinfo : Handle)
+ @family = ::Socket::Family.from_value(addrinfo.value.ai_family)
+ @type = ::Socket::Type.from_value(addrinfo.value.ai_socktype)
+ @protocol = ::Socket::Protocol.from_value(addrinfo.value.ai_protocol)
+ @size = addrinfo.value.ai_addrlen.to_i
+
+ @addr = uninitialized LibC::SockaddrIn6
+
+ case @family
+ when ::Socket::Family::INET6
+ addrinfo.value.ai_addr.as(LibC::SockaddrIn6*).copy_to(pointerof(@addr).as(LibC::SockaddrIn6*), 1)
+ when ::Socket::Family::INET
+ addrinfo.value.ai_addr.as(LibC::SockaddrIn*).copy_to(pointerof(@addr).as(LibC::SockaddrIn*), 1)
+ else
+ # TODO: (asterite) UNSPEC and UNIX unsupported?
+ end
+ end
+
+ def system_ip_address : ::Socket::IPAddress
+ ::Socket::IPAddress.from(to_unsafe, size)
+ end
+
+ def to_unsafe
+ pointerof(@addr).as(LibC::Sockaddr*)
+ end
+
+ def self.getaddrinfo(domain, service, family, type, protocol, timeout) : Handle
+ hints = LibC::ADDRINFOEXW.new
+ hints.ai_family = (family || ::Socket::Family::UNSPEC).to_i32
+ hints.ai_socktype = type
+ hints.ai_protocol = protocol
+ hints.ai_flags = 0
+
+ if service.is_a?(Int)
+ hints.ai_flags |= LibC::AI_NUMERICSERV
+ if service < 0
+ raise ::Socket::Addrinfo::Error.from_os_error(nil, WinError::WSATYPE_NOT_FOUND, domain: domain, type: type, protocol: protocol, service: service)
+ end
+ end
+
+ IOCP::GetAddrInfoOverlappedOperation.run(Crystal::EventLoop.current.iocp_handle) do |operation|
+ completion_routine = LibC::LPLOOKUPSERVICE_COMPLETION_ROUTINE.new do |dwError, dwBytes, lpOverlapped|
+ orig_operation = IOCP::GetAddrInfoOverlappedOperation.unbox(lpOverlapped)
+ LibC.PostQueuedCompletionStatus(orig_operation.iocp, 0, 0, lpOverlapped)
+ end
+
+ # NOTE: we handle the timeout ourselves so we don't pass a `LibC::Timeval`
+ # to Win32 here
+ result = LibC.GetAddrInfoExW(
+ Crystal::System.to_wstr(domain), Crystal::System.to_wstr(service.to_s), LibC::NS_DNS, nil, pointerof(hints),
+ out addrinfos, nil, operation, completion_routine, out cancel_handle)
+
+ if result == 0
+ return addrinfos
+ else
+ case error = WinError.new(result.to_u32!)
+ when .wsa_io_pending?
+ # used in `IOCP::OverlappedOperation#try_cancel_getaddrinfo`
+ operation.cancel_handle = cancel_handle
+ else
+ raise ::Socket::Addrinfo::Error.from_os_error("GetAddrInfoExW", error, domain: domain, type: type, protocol: protocol, service: service)
+ end
+ end
+
+ operation.wait_for_result(timeout) do |error|
+ case error
+ when .wsa_e_cancelled?
+ raise IO::TimeoutError.new("GetAddrInfoExW timed out")
+ else
+ raise ::Socket::Addrinfo::Error.from_os_error("GetAddrInfoExW", error, domain: domain, type: type, protocol: protocol, service: service)
+ end
+ end
+ end
+ end
+
+ def self.next_addrinfo(addrinfo : Handle) : Handle
+ addrinfo.value.ai_next
+ end
+
+ def self.free_addrinfo(addrinfo : Handle)
+ LibC.FreeAddrInfoExW(addrinfo)
+ end
+end
diff --git a/src/crystal/system/win32/addrinfo_win7.cr b/src/crystal/system/win32/addrinfo_win7.cr
new file mode 100644
index 000000000000..b033d61f16e7
--- /dev/null
+++ b/src/crystal/system/win32/addrinfo_win7.cr
@@ -0,0 +1,61 @@
+module Crystal::System::Addrinfo
+ alias Handle = LibC::Addrinfo*
+
+ @addr : LibC::SockaddrIn6
+
+ protected def initialize(addrinfo : Handle)
+ @family = ::Socket::Family.from_value(addrinfo.value.ai_family)
+ @type = ::Socket::Type.from_value(addrinfo.value.ai_socktype)
+ @protocol = ::Socket::Protocol.from_value(addrinfo.value.ai_protocol)
+ @size = addrinfo.value.ai_addrlen.to_i
+
+ @addr = uninitialized LibC::SockaddrIn6
+
+ case @family
+ when ::Socket::Family::INET6
+ addrinfo.value.ai_addr.as(LibC::SockaddrIn6*).copy_to(pointerof(@addr).as(LibC::SockaddrIn6*), 1)
+ when ::Socket::Family::INET
+ addrinfo.value.ai_addr.as(LibC::SockaddrIn*).copy_to(pointerof(@addr).as(LibC::SockaddrIn*), 1)
+ else
+ # TODO: (asterite) UNSPEC and UNIX unsupported?
+ end
+ end
+
+ def system_ip_address : ::Socket::IPAddress
+ ::Socket::IPAddress.from(to_unsafe, size)
+ end
+
+ def to_unsafe
+ pointerof(@addr).as(LibC::Sockaddr*)
+ end
+
+ def self.getaddrinfo(domain, service, family, type, protocol, timeout) : Handle
+ hints = LibC::Addrinfo.new
+ hints.ai_family = (family || ::Socket::Family::UNSPEC).to_i32
+ hints.ai_socktype = type
+ hints.ai_protocol = protocol
+ hints.ai_flags = 0
+
+ if service.is_a?(Int)
+ hints.ai_flags |= LibC::AI_NUMERICSERV
+ if service < 0
+ raise ::Socket::Addrinfo::Error.from_os_error(nil, WinError::WSATYPE_NOT_FOUND, domain: domain, type: type, protocol: protocol, service: service)
+ end
+ end
+
+ ret = LibC.getaddrinfo(domain, service.to_s, pointerof(hints), out ptr)
+ unless ret.zero?
+ error = WinError.new(ret.to_u32!)
+ raise ::Socket::Addrinfo::Error.from_os_error(nil, error, domain: domain, type: type, protocol: protocol, service: service)
+ end
+ ptr
+ end
+
+ def self.next_addrinfo(addrinfo : Handle) : Handle
+ addrinfo.value.ai_next
+ end
+
+ def self.free_addrinfo(addrinfo : Handle)
+ LibC.freeaddrinfo(addrinfo)
+ end
+end
diff --git a/src/crystal/system/win32/event_loop_iocp.cr b/src/crystal/system/win32/event_loop_iocp.cr
deleted file mode 100644
index 25c8db41d9ff..000000000000
--- a/src/crystal/system/win32/event_loop_iocp.cr
+++ /dev/null
@@ -1,302 +0,0 @@
-require "c/ioapiset"
-require "crystal/system/print_error"
-require "./iocp"
-
-# :nodoc:
-class Crystal::IOCP::EventLoop < Crystal::EventLoop
- # This is a list of resume and timeout events managed outside of IOCP.
- @queue = Deque(Crystal::IOCP::Event).new
-
- @lock = Crystal::SpinLock.new
- @interrupted = Atomic(Bool).new(false)
- @blocked_thread = Atomic(Thread?).new(nil)
-
- # Returns the base IO Completion Port
- getter iocp : LibC::HANDLE do
- create_completion_port(LibC::INVALID_HANDLE_VALUE, nil)
- end
-
- def create_completion_port(handle : LibC::HANDLE, parent : LibC::HANDLE? = iocp)
- iocp = LibC.CreateIoCompletionPort(handle, parent, nil, 0)
- if iocp.null?
- raise IO::Error.from_winerror("CreateIoCompletionPort")
- end
- if parent
- # all overlapped operations may finish synchronously, in which case we do
- # not reschedule the running fiber; the following call tells Win32 not to
- # queue an I/O completion packet to the associated IOCP as well, as this
- # would be done by default
- if LibC.SetFileCompletionNotificationModes(handle, LibC::FILE_SKIP_COMPLETION_PORT_ON_SUCCESS) == 0
- raise IO::Error.from_winerror("SetFileCompletionNotificationModes")
- end
- end
- iocp
- end
-
- # Runs the event loop and enqueues the fiber for the next upcoming event or
- # completion.
- def run(blocking : Bool) : Bool
- # Pull the next upcoming event from the event queue. This determines the
- # timeout for waiting on the completion port.
- # OPTIMIZE: Implement @queue as a priority queue in order to avoid this
- # explicit search for the lowest value and dequeue more efficient.
- next_event = @queue.min_by?(&.wake_at)
-
- # no registered events: nothing to wait for
- return false unless next_event
-
- now = Time.monotonic
-
- if next_event.wake_at > now
- # There is no event ready to wake. We wait for completions until the next
- # event wake time, unless nonblocking or already interrupted (timeout
- # immediately).
- if blocking
- @lock.sync do
- if @interrupted.get(:acquire)
- blocking = false
- else
- # memorize the blocked thread (so we can alert it)
- @blocked_thread.set(Thread.current, :release)
- end
- end
- end
-
- wait_time = blocking ? (next_event.wake_at - now).total_milliseconds : 0
- timed_out = IOCP.wait_queued_completions(wait_time, alertable: blocking) do |fiber|
- # This block may run multiple times. Every single fiber gets enqueued.
- fiber.enqueue
- end
-
- @blocked_thread.set(nil, :release)
- @interrupted.set(false, :release)
-
- # The wait for completion enqueued events.
- return true unless timed_out
-
- # Wait for completion timed out but it may have been interrupted or we ask
- # for immediate timeout (nonblocking), so we check for the next event
- # readyness again:
- return false if next_event.wake_at > Time.monotonic
- end
-
- # next_event gets activated because its wake time is passed, either from the
- # start or because completion wait has timed out.
-
- dequeue next_event
-
- fiber = next_event.fiber
-
- # If the waiting fiber was already shut down in the mean time, we can just
- # abandon here. There's no need to go for the next event because the scheduler
- # will just try again.
- # OPTIMIZE: It might still be worth considering to start over from the top
- # or call recursively, in order to ensure at least one fiber get enqueued.
- # This would avoid the scheduler needing to looking at runnable again just
- # to notice it's still empty. The lock involved there should typically be
- # uncontested though, so it's probably not a big deal.
- return false if fiber.dead?
-
- # A timeout event needs special handling because it does not necessarily
- # means to resume the fiber directly, in case a different select branch
- # was already activated.
- if next_event.timeout? && (select_action = fiber.timeout_select_action)
- fiber.timeout_select_action = nil
- select_action.time_expired(fiber)
- else
- fiber.enqueue
- end
-
- # We enqueued a fiber.
- true
- end
-
- def interrupt : Nil
- thread = nil
-
- @lock.sync do
- @interrupted.set(true)
- thread = @blocked_thread.swap(nil, :acquire)
- end
- return unless thread
-
- # alert the thread to interrupt GetQueuedCompletionStatusEx
- LibC.QueueUserAPC(->(ptr : LibC::ULONG_PTR) {}, thread, LibC::ULONG_PTR.new(0))
- end
-
- def enqueue(event : Crystal::IOCP::Event)
- unless @queue.includes?(event)
- @queue << event
- end
- end
-
- def dequeue(event : Crystal::IOCP::Event)
- @queue.delete(event)
- end
-
- # Create a new resume event for a fiber.
- def create_resume_event(fiber : Fiber) : Crystal::EventLoop::Event
- Crystal::IOCP::Event.new(fiber)
- end
-
- def create_timeout_event(fiber) : Crystal::EventLoop::Event
- Crystal::IOCP::Event.new(fiber, timeout: true)
- end
-
- def read(file_descriptor : Crystal::System::FileDescriptor, slice : Bytes) : Int32
- handle = file_descriptor.windows_handle
- IOCP.overlapped_operation(file_descriptor, handle, "ReadFile", file_descriptor.read_timeout) do |overlapped|
- ret = LibC.ReadFile(handle, slice, slice.size, out byte_count, overlapped)
- {ret, byte_count}
- end.to_i32
- end
-
- def write(file_descriptor : Crystal::System::FileDescriptor, slice : Bytes) : Int32
- handle = file_descriptor.windows_handle
-
- IOCP.overlapped_operation(file_descriptor, handle, "WriteFile", file_descriptor.write_timeout, writing: true) do |overlapped|
- ret = LibC.WriteFile(handle, slice, slice.size, out byte_count, overlapped)
- {ret, byte_count}
- end.to_i32
- end
-
- def close(file_descriptor : Crystal::System::FileDescriptor) : Nil
- LibC.CancelIoEx(file_descriptor.windows_handle, nil) unless file_descriptor.system_blocking?
- end
-
- private def wsa_buffer(bytes)
- wsabuf = LibC::WSABUF.new
- wsabuf.len = bytes.size
- wsabuf.buf = bytes.to_unsafe
- wsabuf
- end
-
- def read(socket : ::Socket, slice : Bytes) : Int32
- wsabuf = wsa_buffer(slice)
-
- bytes_read = IOCP.wsa_overlapped_operation(socket, socket.fd, "WSARecv", socket.read_timeout, connreset_is_error: false) do |overlapped|
- flags = 0_u32
- ret = LibC.WSARecv(socket.fd, pointerof(wsabuf), 1, out bytes_received, pointerof(flags), overlapped, nil)
- {ret, bytes_received}
- end
-
- bytes_read.to_i32
- end
-
- def write(socket : ::Socket, slice : Bytes) : Int32
- wsabuf = wsa_buffer(slice)
-
- bytes = IOCP.wsa_overlapped_operation(socket, socket.fd, "WSASend", socket.write_timeout) do |overlapped|
- ret = LibC.WSASend(socket.fd, pointerof(wsabuf), 1, out bytes_sent, 0, overlapped, nil)
- {ret, bytes_sent}
- end
-
- bytes.to_i32
- end
-
- def send_to(socket : ::Socket, slice : Bytes, address : ::Socket::Address) : Int32
- wsabuf = wsa_buffer(slice)
- bytes_written = IOCP.wsa_overlapped_operation(socket, socket.fd, "WSASendTo", socket.write_timeout) do |overlapped|
- ret = LibC.WSASendTo(socket.fd, pointerof(wsabuf), 1, out bytes_sent, 0, address, address.size, overlapped, nil)
- {ret, bytes_sent}
- end
- raise ::Socket::Error.from_wsa_error("Error sending datagram to #{address}") if bytes_written == -1
-
- # to_i32 is fine because string/slice sizes are an Int32
- bytes_written.to_i32
- end
-
- def receive(socket : ::Socket, slice : Bytes) : Int32
- receive_from(socket, slice)[0]
- end
-
- def receive_from(socket : ::Socket, slice : Bytes) : Tuple(Int32, ::Socket::Address)
- sockaddr = Pointer(LibC::SOCKADDR_STORAGE).malloc.as(LibC::Sockaddr*)
- # initialize sockaddr with the initialized family of the socket
- copy = sockaddr.value
- copy.sa_family = socket.family
- sockaddr.value = copy
-
- addrlen = sizeof(LibC::SOCKADDR_STORAGE)
-
- wsabuf = wsa_buffer(slice)
-
- flags = 0_u32
- bytes_read = IOCP.wsa_overlapped_operation(socket, socket.fd, "WSARecvFrom", socket.read_timeout) do |overlapped|
- ret = LibC.WSARecvFrom(socket.fd, pointerof(wsabuf), 1, out bytes_received, pointerof(flags), sockaddr, pointerof(addrlen), overlapped, nil)
- {ret, bytes_received}
- end
-
- {bytes_read.to_i32, ::Socket::Address.from(sockaddr, addrlen)}
- end
-
- def connect(socket : ::Socket, address : ::Socket::Addrinfo | ::Socket::Address, timeout : ::Time::Span?) : IO::Error?
- socket.overlapped_connect(socket.fd, "ConnectEx") do |overlapped|
- # This is: LibC.ConnectEx(fd, address, address.size, nil, 0, nil, overlapped)
- Crystal::System::Socket.connect_ex.call(socket.fd, address.to_unsafe, address.size, Pointer(Void).null, 0_u32, Pointer(UInt32).null, overlapped.to_unsafe)
- end
- end
-
- def accept(socket : ::Socket) : ::Socket::Handle?
- socket.system_accept do |client_handle|
- address_size = sizeof(LibC::SOCKADDR_STORAGE) + 16
-
- # buffer_size is set to zero to only accept the connection and don't receive any data.
- # That will be a different operation.
- #
- # > If dwReceiveDataLength is zero, accepting the connection will not result in a receive operation.
- # > Instead, AcceptEx completes as soon as a connection arrives, without waiting for any data.
- #
- # TODO: Investigate benefits from receiving data here directly. It's hard to integrate into the event loop and socket API.
- buffer_size = 0
- output_buffer = Bytes.new(address_size * 2 + buffer_size)
-
- success = socket.overlapped_accept(socket.fd, "AcceptEx") do |overlapped|
- # This is: LibC.AcceptEx(fd, client_handle, output_buffer, buffer_size, address_size, address_size, out received_bytes, overlapped)
- received_bytes = uninitialized UInt32
- Crystal::System::Socket.accept_ex.call(socket.fd, client_handle,
- output_buffer.to_unsafe.as(Void*), buffer_size.to_u32!,
- address_size.to_u32!, address_size.to_u32!, pointerof(received_bytes), overlapped.to_unsafe)
- end
-
- if success
- # AcceptEx does not automatically set the socket options on the accepted
- # socket to match those of the listening socket, we need to ask for that
- # explicitly with SO_UPDATE_ACCEPT_CONTEXT
- socket.system_setsockopt client_handle, LibC::SO_UPDATE_ACCEPT_CONTEXT, socket.fd
-
- true
- else
- false
- end
- end
- end
-
- def close(socket : ::Socket) : Nil
- end
-end
-
-class Crystal::IOCP::Event
- include Crystal::EventLoop::Event
-
- getter fiber
- getter wake_at
- getter? timeout
-
- def initialize(@fiber : Fiber, @wake_at = Time.monotonic, *, @timeout = false)
- end
-
- # Frees the event
- def free : Nil
- Crystal::EventLoop.current.dequeue(self)
- end
-
- def delete
- free
- end
-
- def add(timeout : Time::Span?) : Nil
- @wake_at = timeout ? Time.monotonic + timeout : Time.monotonic
- Crystal::EventLoop.current.enqueue(self)
- end
-end
diff --git a/src/crystal/system/win32/fiber.cr b/src/crystal/system/win32/fiber.cr
index 9e6495ee594e..05fd230a9cac 100644
--- a/src/crystal/system/win32/fiber.cr
+++ b/src/crystal/system/win32/fiber.cr
@@ -7,28 +7,63 @@ module Crystal::System::Fiber
# overflow
RESERVED_STACK_SIZE = LibC::DWORD.new(0x10000)
- # the reserved stack size, plus the size of a single page
- @@total_reserved_size : LibC::DWORD = begin
- LibC.GetNativeSystemInfo(out system_info)
- system_info.dwPageSize + RESERVED_STACK_SIZE
- end
-
def self.allocate_stack(stack_size, protect) : Void*
- unless memory_pointer = LibC.VirtualAlloc(nil, stack_size, LibC::MEM_COMMIT | LibC::MEM_RESERVE, LibC::PAGE_READWRITE)
- raise RuntimeError.from_winerror("VirtualAlloc")
+ if stack_top = LibC.VirtualAlloc(nil, stack_size, LibC::MEM_RESERVE, LibC::PAGE_READWRITE)
+ if protect
+ if commit_and_guard(stack_top, stack_size)
+ return stack_top
+ end
+ else
+ # for the interpreter, the stack is just ordinary memory so the entire
+ # range is committed
+ if LibC.VirtualAlloc(stack_top, stack_size, LibC::MEM_COMMIT, LibC::PAGE_READWRITE)
+ return stack_top
+ end
+ end
+
+ # failure
+ LibC.VirtualFree(stack_top, 0, LibC::MEM_RELEASE)
end
- # Detects stack overflows by guarding the top of the stack, similar to
- # `LibC.mprotect`. Windows will fail to allocate a new guard page for these
- # fiber stacks and trigger a stack overflow exception
+ raise RuntimeError.from_winerror("VirtualAlloc")
+ end
+
+ def self.reset_stack(stack : Void*, stack_size : Int, protect : Bool) : Nil
if protect
- if LibC.VirtualProtect(memory_pointer, @@total_reserved_size, LibC::PAGE_READWRITE | LibC::PAGE_GUARD, out _) == 0
- LibC.VirtualFree(memory_pointer, 0, LibC::MEM_RELEASE)
- raise RuntimeError.from_winerror("VirtualProtect")
+ if LibC.VirtualFree(stack, 0, LibC::MEM_DECOMMIT) == 0
+ raise RuntimeError.from_winerror("VirtualFree")
+ end
+ unless commit_and_guard(stack, stack_size)
+ raise RuntimeError.from_winerror("VirtualAlloc")
end
end
+ end
+
+ # Commits the bottommost page and sets up the guard pages above it, in the
+ # same manner as each thread's main stack. When the stack hits a guard page
+ # for the first time, a page fault is generated, the page's guard status is
+ # reset, and Windows checks if a reserved page is available above. On success,
+ # a new guard page is committed, and on failure, a stack overflow exception is
+ # triggered after the `RESERVED_STACK_SIZE` portion is made available.
+ private def self.commit_and_guard(stack_top, stack_size)
+ stack_bottom = stack_top + stack_size
+
+ LibC.GetNativeSystemInfo(out system_info)
+ stack_commit_size = system_info.dwPageSize
+ stack_commit_top = stack_bottom - stack_commit_size
+ unless LibC.VirtualAlloc(stack_commit_top, stack_commit_size, LibC::MEM_COMMIT, LibC::PAGE_READWRITE)
+ return false
+ end
+
+ # the reserved stack size, plus a final guard page for when the stack
+ # overflow handler itself overflows the stack
+ stack_guard_size = system_info.dwPageSize + RESERVED_STACK_SIZE
+ stack_guard_top = stack_commit_top - stack_guard_size
+ unless LibC.VirtualAlloc(stack_guard_top, stack_guard_size, LibC::MEM_COMMIT, LibC::PAGE_READWRITE | LibC::PAGE_GUARD)
+ return false
+ end
- memory_pointer
+ true
end
def self.free_stack(stack : Void*, stack_size) : Nil
diff --git a/src/crystal/system/win32/file.cr b/src/crystal/system/win32/file.cr
index 83d6afcf18ca..b6f9cf2b7ccd 100644
--- a/src/crystal/system/win32/file.cr
+++ b/src/crystal/system/win32/file.cr
@@ -9,7 +9,12 @@ require "c/ntifs"
require "c/winioctl"
module Crystal::System::File
- def self.open(filename : String, mode : String, perm : Int32 | ::File::Permissions) : FileDescriptor::Handle
+ # On Windows we cannot rely on the system mode `FILE_APPEND_DATA` and
+ # keep track of append mode explicitly. When writing data, this ensures to only
+ # write at the end of the file.
+ @system_append = false
+
+ def self.open(filename : String, mode : String, perm : Int32 | ::File::Permissions, blocking : Bool?) : FileDescriptor::Handle
perm = ::File::Permissions.new(perm) if perm.is_a? Int32
# Only the owner writable bit is used, since windows only supports
# the read only attribute.
@@ -19,7 +24,7 @@ module Crystal::System::File
perm = LibC::S_IREAD
end
- handle, error = open(filename, open_flag(mode), ::File::Permissions.new(perm))
+ handle, error = open(filename, open_flag(mode), ::File::Permissions.new(perm), blocking != false)
unless error.error_success?
raise ::File::Error.from_os_error("Error opening file with mode '#{mode}'", error, file: filename)
end
@@ -27,8 +32,8 @@ module Crystal::System::File
handle
end
- def self.open(filename : String, flags : Int32, perm : ::File::Permissions) : {FileDescriptor::Handle, WinError}
- access, disposition, attributes = self.posix_to_open_opts flags, perm
+ def self.open(filename : String, flags : Int32, perm : ::File::Permissions, blocking : Bool) : {FileDescriptor::Handle, WinError}
+ access, disposition, attributes = self.posix_to_open_opts flags, perm, blocking
handle = LibC.CreateFileW(
System.to_wstr(filename),
@@ -43,7 +48,7 @@ module Crystal::System::File
{handle.address, handle == LibC::INVALID_HANDLE_VALUE ? WinError.value : WinError::ERROR_SUCCESS}
end
- private def self.posix_to_open_opts(flags : Int32, perm : ::File::Permissions)
+ private def self.posix_to_open_opts(flags : Int32, perm : ::File::Permissions, blocking : Bool)
access = if flags.bits_set? LibC::O_WRONLY
LibC::FILE_GENERIC_WRITE
elsif flags.bits_set? LibC::O_RDWR
@@ -52,10 +57,9 @@ module Crystal::System::File
LibC::FILE_GENERIC_READ
end
- if flags.bits_set? LibC::O_APPEND
- access |= LibC::FILE_APPEND_DATA
- access &= ~LibC::FILE_WRITE_DATA
- end
+ # do not handle `O_APPEND`, because Win32 append mode relies on removing
+ # `FILE_WRITE_DATA` which breaks file truncation and locking; instead,
+ # simply set the end of the file as the write offset in `#write_blocking`
if flags.bits_set? LibC::O_TRUNC
if flags.bits_set? LibC::O_CREAT
@@ -73,7 +77,7 @@ module Crystal::System::File
disposition = LibC::OPEN_EXISTING
end
- attributes = LibC::FILE_ATTRIBUTE_NORMAL
+ attributes = 0
unless perm.owner_write?
attributes |= LibC::FILE_ATTRIBUTE_READONLY
end
@@ -93,13 +97,26 @@ module Crystal::System::File
attributes |= LibC::FILE_FLAG_RANDOM_ACCESS
end
+ unless blocking
+ attributes |= LibC::FILE_FLAG_OVERLAPPED
+ end
+
{access, disposition, attributes}
end
+ protected def system_set_mode(mode : String)
+ @system_append = true if mode.starts_with?('a')
+ end
+
+ private def write_blocking(handle, slice)
+ write_blocking(handle, slice, pos: @system_append ? UInt64::MAX : nil)
+ end
+
NOT_FOUND_ERRORS = {
WinError::ERROR_FILE_NOT_FOUND,
WinError::ERROR_PATH_NOT_FOUND,
WinError::ERROR_INVALID_NAME,
+ WinError::ERROR_DIRECTORY,
}
def self.check_not_found_error(message, path)
diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr
index dc8d479532be..4a99d82e9134 100644
--- a/src/crystal/system/win32/file_descriptor.cr
+++ b/src/crystal/system/win32/file_descriptor.cr
@@ -3,6 +3,7 @@ require "c/consoleapi"
require "c/consoleapi2"
require "c/winnls"
require "crystal/system/win32/iocp"
+require "crystal/system/thread"
module Crystal::System::FileDescriptor
# Platform-specific type to represent a file descriptor handle to the operating
@@ -52,8 +53,17 @@ module Crystal::System::FileDescriptor
end
end
- private def write_blocking(handle, slice)
- ret = LibC.WriteFile(handle, slice, slice.size, out bytes_written, nil)
+ private def write_blocking(handle, slice, pos = nil)
+ overlapped = LibC::OVERLAPPED.new
+ if pos
+ overlapped.union.offset.offset = LibC::DWORD.new!(pos)
+ overlapped.union.offset.offsetHigh = LibC::DWORD.new!(pos >> 32)
+ overlapped_ptr = pointerof(overlapped)
+ else
+ overlapped_ptr = Pointer(LibC::OVERLAPPED).null
+ end
+
+ ret = LibC.WriteFile(handle, slice, slice.size, out bytes_written, overlapped_ptr)
if ret.zero?
case error = WinError.value
when .error_access_denied?
@@ -67,19 +77,31 @@ module Crystal::System::FileDescriptor
bytes_written
end
+ def emulated_blocking? : Bool?
+ # reading from STDIN is done via a separate thread (see
+ # `ConsoleUtils.read_console` below)
+ handle = windows_handle
+ if LibC.GetConsoleMode(handle, out _) != 0
+ if handle == LibC.GetStdHandle(LibC::STD_INPUT_HANDLE)
+ return false
+ end
+ end
+ end
+
# :nodoc:
def system_blocking?
@system_blocking
end
private def system_blocking=(blocking)
- unless blocking == @system_blocking
+ unless blocking == self.blocking
raise IO::Error.new("Cannot reconfigure `IO::FileDescriptor#blocking` after creation")
end
end
private def system_blocking_init(value)
@system_blocking = value
+ Crystal::EventLoop.current.create_completion_port(windows_handle) unless value
end
private def system_close_on_exec?
@@ -110,10 +132,6 @@ module Crystal::System::FileDescriptor
end
protected def windows_handle
- FileDescriptor.windows_handle(fd)
- end
-
- def self.windows_handle(fd)
LibC::HANDLE.new(fd)
end
@@ -176,8 +194,18 @@ module Crystal::System::FileDescriptor
file_descriptor_close
end
+ def file_descriptor_close(&)
+ # Clear the @volatile_fd before actually closing it in order to
+ # reduce the chance of reading an outdated handle value
+ handle = LibC::HANDLE.new(@volatile_fd.swap(LibC::INVALID_HANDLE_VALUE.address))
+
+ if LibC.CloseHandle(handle) == 0
+ yield
+ end
+ end
+
def file_descriptor_close
- if LibC.CloseHandle(windows_handle) == 0
+ file_descriptor_close do
raise IO::Error.from_winerror("Error closing file", target: self)
end
end
@@ -195,41 +223,62 @@ module Crystal::System::FileDescriptor
end
private def flock(exclusive, retry)
- flags = LibC::LOCKFILE_FAIL_IMMEDIATELY
+ flags = 0_u32
+ flags |= LibC::LOCKFILE_FAIL_IMMEDIATELY if !retry || system_blocking?
flags |= LibC::LOCKFILE_EXCLUSIVE_LOCK if exclusive
handle = windows_handle
- if retry
+ if retry && system_blocking?
until lock_file(handle, flags)
- sleep 0.1
+ sleep 0.1.seconds
end
else
- lock_file(handle, flags) || raise IO::Error.from_winerror("Error applying file lock: file is already locked")
+ lock_file(handle, flags) || raise IO::Error.from_winerror("Error applying file lock: file is already locked", target: self)
end
end
private def lock_file(handle, flags)
- # lpOverlapped must be provided despite the synchronous use of this method.
- overlapped = LibC::OVERLAPPED.new
- # lock the entire file with offset 0 in overlapped and number of bytes set to max value
- if 0 != LibC.LockFileEx(handle, flags, 0, 0xFFFF_FFFF, 0xFFFF_FFFF, pointerof(overlapped))
- true
- else
- winerror = WinError.value
- if winerror == WinError::ERROR_LOCK_VIOLATION
- false
+ IOCP::IOOverlappedOperation.run(handle) do |operation|
+ result = LibC.LockFileEx(handle, flags, 0, 0xFFFF_FFFF, 0xFFFF_FFFF, operation)
+
+ if result == 0
+ case error = WinError.value
+ when .error_io_pending?
+ # the operation is running asynchronously; do nothing
+ when .error_lock_violation?
+ # synchronous failure
+ return false
+ else
+ raise IO::Error.from_os_error("LockFileEx", error, target: self)
+ end
else
- raise IO::Error.from_os_error("LockFileEx", winerror, target: self)
+ return true
end
+
+ operation.wait_for_result(nil) do |error|
+ raise IO::Error.from_os_error("LockFileEx", error, target: self)
+ end
+
+ true
end
end
private def unlock_file(handle)
- # lpOverlapped must be provided despite the synchronous use of this method.
- overlapped = LibC::OVERLAPPED.new
- # unlock the entire file with offset 0 in overlapped and number of bytes set to max value
- if 0 == LibC.UnlockFileEx(handle, 0, 0xFFFF_FFFF, 0xFFFF_FFFF, pointerof(overlapped))
- raise IO::Error.from_winerror("UnLockFileEx")
+ IOCP::IOOverlappedOperation.run(handle) do |operation|
+ result = LibC.UnlockFileEx(handle, 0, 0xFFFF_FFFF, 0xFFFF_FFFF, operation)
+
+ if result == 0
+ error = WinError.value
+ unless error.error_io_pending?
+ raise IO::Error.from_os_error("UnlockFileEx", error, target: self)
+ end
+ else
+ return
+ end
+
+ operation.wait_for_result(nil) do |error|
+ raise IO::Error.from_os_error("UnlockFileEx", error, target: self)
+ end
end
end
@@ -249,13 +298,11 @@ module Crystal::System::FileDescriptor
w_pipe_flags |= LibC::FILE_FLAG_OVERLAPPED unless write_blocking
w_pipe = LibC.CreateNamedPipeA(pipe_name, w_pipe_flags, pipe_mode, 1, PIPE_BUFFER_SIZE, PIPE_BUFFER_SIZE, 0, nil)
raise IO::Error.from_winerror("CreateNamedPipeA") if w_pipe == LibC::INVALID_HANDLE_VALUE
- Crystal::EventLoop.current.create_completion_port(w_pipe) unless write_blocking
r_pipe_flags = LibC::FILE_FLAG_NO_BUFFERING
r_pipe_flags |= LibC::FILE_FLAG_OVERLAPPED unless read_blocking
r_pipe = LibC.CreateFileW(System.to_wstr(pipe_name), LibC::GENERIC_READ | LibC::FILE_WRITE_ATTRIBUTES, 0, nil, LibC::OPEN_EXISTING, r_pipe_flags, nil)
raise IO::Error.from_winerror("CreateFileW") if r_pipe == LibC::INVALID_HANDLE_VALUE
- Crystal::EventLoop.current.create_completion_port(r_pipe) unless read_blocking
r = IO::FileDescriptor.new(r_pipe.address, read_blocking)
w = IO::FileDescriptor.new(w_pipe.address, write_blocking)
@@ -264,19 +311,26 @@ module Crystal::System::FileDescriptor
{r, w}
end
- def self.pread(fd, buffer, offset)
- handle = windows_handle(fd)
+ def self.pread(file, buffer, offset)
+ handle = file.windows_handle
- overlapped = LibC::OVERLAPPED.new
- overlapped.union.offset.offset = LibC::DWORD.new!(offset)
- overlapped.union.offset.offsetHigh = LibC::DWORD.new!(offset >> 32)
- if LibC.ReadFile(handle, buffer, buffer.size, out bytes_read, pointerof(overlapped)) == 0
- error = WinError.value
- return 0_i64 if error == WinError::ERROR_HANDLE_EOF
- raise IO::Error.from_os_error "Error reading file", error, target: self
- end
+ if file.system_blocking?
+ overlapped = LibC::OVERLAPPED.new
+ overlapped.union.offset.offset = LibC::DWORD.new!(offset)
+ overlapped.union.offset.offsetHigh = LibC::DWORD.new!(offset >> 32)
+ if LibC.ReadFile(handle, buffer, buffer.size, out bytes_read, pointerof(overlapped)) == 0
+ error = WinError.value
+ return 0_i64 if error == WinError::ERROR_HANDLE_EOF
+ raise IO::Error.from_os_error "Error reading file", error, target: file
+ end
- bytes_read.to_i64
+ bytes_read.to_i64
+ else
+ IOCP.overlapped_operation(file, "ReadFile", file.read_timeout, offset: offset) do |overlapped|
+ ret = LibC.ReadFile(handle, buffer, buffer.size, out byte_count, overlapped)
+ {ret, byte_count}
+ end.to_i64
+ end
end
def self.from_stdio(fd)
@@ -301,7 +355,11 @@ module Crystal::System::FileDescriptor
end
end
+ # `blocking` must be set to `true` because the underlying handles never
+ # support overlapped I/O; instead, `#emulated_blocking?` should return
+ # `false` for `STDIN` as it uses a separate thread
io = IO::FileDescriptor.new(handle.address, blocking: true)
+
# Set sync or flush_on_newline as described in STDOUT and STDERR docs.
# See https://crystal-lang.org/api/toplevel.html#STDERR
if console_handle
@@ -423,15 +481,65 @@ private module ConsoleUtils
appender << byte
end
end
- @@buffer = @@utf8_buffer[0, appender.size]
+ @@buffer = appender.to_slice
end
private def self.read_console(handle : LibC::HANDLE, slice : Slice(UInt16)) : Int32
+ @@mtx.synchronize do
+ @@read_requests << ReadRequest.new(
+ handle: handle,
+ slice: slice,
+ iocp: Crystal::EventLoop.current.iocp_handle,
+ completion_key: Crystal::System::IOCP::CompletionKey.new(:stdin_read, ::Fiber.current),
+ )
+ @@read_cv.signal
+ end
+
+ ::Fiber.suspend
+
+ @@mtx.synchronize do
+ @@bytes_read.shift
+ end
+ end
+
+ private def self.read_console_blocking(handle : LibC::HANDLE, slice : Slice(UInt16)) : Int32
if 0 == LibC.ReadConsoleW(handle, slice, slice.size, out units_read, nil)
raise IO::Error.from_winerror("ReadConsoleW")
end
units_read.to_i32
end
+
+ record ReadRequest,
+ handle : LibC::HANDLE,
+ slice : Slice(UInt16),
+ iocp : LibC::HANDLE,
+ completion_key : Crystal::System::IOCP::CompletionKey
+
+ @@read_cv = ::Thread::ConditionVariable.new
+ @@read_requests = Deque(ReadRequest).new
+ @@bytes_read = Deque(Int32).new
+ @@mtx = ::Thread::Mutex.new
+ @@reader_thread = ::Thread.new { reader_loop }
+
+ private def self.reader_loop
+ while true
+ request = @@mtx.synchronize do
+ loop do
+ if entry = @@read_requests.shift?
+ break entry
+ end
+ @@read_cv.wait(@@mtx)
+ end
+ end
+
+ bytes = read_console_blocking(request.handle, request.slice)
+
+ @@mtx.synchronize do
+ @@bytes_read << bytes
+ LibC.PostQueuedCompletionStatus(request.iocp, LibC::JOB_OBJECT_MSG_EXIT_PROCESS, request.completion_key.object_id, nil)
+ end
+ end
+ end
end
# Enable UTF-8 console I/O for the duration of program execution
diff --git a/src/crystal/system/win32/group.cr b/src/crystal/system/win32/group.cr
new file mode 100644
index 000000000000..3b40774ac2d8
--- /dev/null
+++ b/src/crystal/system/win32/group.cr
@@ -0,0 +1,82 @@
+require "crystal/system/windows"
+
+# This file contains source code derived from the following:
+#
+# * https://cs.opensource.google/go/go/+/refs/tags/go1.23.0:src/os/user/lookup_windows.go
+# * https://cs.opensource.google/go/go/+/refs/tags/go1.23.0:src/syscall/security_windows.go
+#
+# The following is their license:
+#
+# Copyright 2009 The Go Authors.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google LLC nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+module Crystal::System::Group
+ def initialize(@name : String, @id : String)
+ end
+
+ def system_name : String
+ @name
+ end
+
+ def system_id : String
+ @id
+ end
+
+ def self.from_name?(groupname : String) : ::System::Group?
+ if found = Crystal::System.name_to_sid(groupname)
+ from_sid(found.sid)
+ end
+ end
+
+ def self.from_id?(groupid : String) : ::System::Group?
+ if sid = Crystal::System.sid_from_s(groupid)
+ begin
+ from_sid(sid)
+ ensure
+ LibC.LocalFree(sid)
+ end
+ end
+ end
+
+ private def self.from_sid(sid : LibC::SID*) : ::System::Group?
+ canonical = Crystal::System.sid_to_name(sid) || return
+
+ # https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/7b2aeb27-92fc-41f6-8437-deb65d950921#gt_0387e636-5654-4910-9519-1f8326cf5ec0
+ # SidTypeAlias should also be treated as a group type next to SidTypeGroup
+ # and SidTypeWellKnownGroup:
+ # "alias object -> resource group: A group object..."
+ #
+ # Tests show that "Administrators" can be considered of type SidTypeAlias.
+ case canonical.type
+ when .sid_type_group?, .sid_type_well_known_group?, .sid_type_alias?
+ domain_and_group = canonical.domain.empty? ? canonical.name : "#{canonical.domain}\\#{canonical.name}"
+ gid = Crystal::System.sid_to_s(sid)
+ ::System::Group.new(domain_and_group, gid)
+ end
+ end
+end
diff --git a/src/crystal/system/win32/iocp.cr b/src/crystal/system/win32/iocp.cr
index ba0f11eb2af5..70048d24cf8c 100644
--- a/src/crystal/system/win32/iocp.cr
+++ b/src/crystal/system/win32/iocp.cr
@@ -1,23 +1,99 @@
{% skip_file unless flag?(:win32) %}
require "c/handleapi"
+require "c/ioapiset"
+require "c/ntdll"
require "crystal/system/thread_linked_list"
# :nodoc:
-module Crystal::IOCP
+struct Crystal::System::IOCP
+ @@wait_completion_packet_methods : Bool? = nil
+
+ {% if flag?(:interpreted) %}
+ # We can't load the symbols from interpreted code since it would create
+ # interpreted Proc. We thus merely check for the existence of the symbols,
+ # then let the interpreter load the symbols, which will create interpreter
+ # Proc (not interpreted) that can be called.
+ class_getter?(wait_completion_packet_methods : Bool) do
+ detect_wait_completion_packet_methods
+ end
+
+ private def self.detect_wait_completion_packet_methods : Bool
+ if handle = LibC.LoadLibraryExW(Crystal::System.to_wstr("ntdll.dll"), nil, 0)
+ !LibC.GetProcAddress(handle, "NtCreateWaitCompletionPacket").null?
+ else
+ false
+ end
+ end
+ {% else %}
+ @@_NtCreateWaitCompletionPacket = uninitialized LibNTDLL::NtCreateWaitCompletionPacketProc
+ @@_NtAssociateWaitCompletionPacket = uninitialized LibNTDLL::NtAssociateWaitCompletionPacketProc
+ @@_NtCancelWaitCompletionPacket = uninitialized LibNTDLL::NtCancelWaitCompletionPacketProc
+
+ class_getter?(wait_completion_packet_methods : Bool) do
+ load_wait_completion_packet_methods
+ end
+
+ private def self.load_wait_completion_packet_methods : Bool
+ handle = LibC.LoadLibraryExW(Crystal::System.to_wstr("ntdll.dll"), nil, 0)
+ return false if handle.null?
+
+ pointer = LibC.GetProcAddress(handle, "NtCreateWaitCompletionPacket")
+ return false if pointer.null?
+ @@_NtCreateWaitCompletionPacket = LibNTDLL::NtCreateWaitCompletionPacketProc.new(pointer, Pointer(Void).null)
+
+ pointer = LibC.GetProcAddress(handle, "NtAssociateWaitCompletionPacket")
+ @@_NtAssociateWaitCompletionPacket = LibNTDLL::NtAssociateWaitCompletionPacketProc.new(pointer, Pointer(Void).null)
+
+ pointer = LibC.GetProcAddress(handle, "NtCancelWaitCompletionPacket")
+ @@_NtCancelWaitCompletionPacket = LibNTDLL::NtCancelWaitCompletionPacketProc.new(pointer, Pointer(Void).null)
+
+ true
+ end
+ {% end %}
+
# :nodoc:
class CompletionKey
- property fiber : Fiber?
+ enum Tag
+ ProcessRun
+ StdinRead
+ Interrupt
+ Timer
+ end
+
+ property fiber : ::Fiber?
+ getter tag : Tag
+
+ def initialize(@tag : Tag, @fiber : ::Fiber? = nil)
+ end
+
+ def valid?(number_of_bytes_transferred)
+ case tag
+ in .process_run?
+ number_of_bytes_transferred.in?(LibC::JOB_OBJECT_MSG_EXIT_PROCESS, LibC::JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS)
+ in .stdin_read?, .interrupt?, .timer?
+ true
+ end
+ end
+ end
+
+ getter handle : LibC::HANDLE
+
+ def initialize
+ @handle = LibC.CreateIoCompletionPort(LibC::INVALID_HANDLE_VALUE, nil, nil, 0)
+ raise IO::Error.from_winerror("CreateIoCompletionPort") if @handle.null?
end
- def self.wait_queued_completions(timeout, alertable = false, &)
- overlapped_entries = uninitialized LibC::OVERLAPPED_ENTRY[1]
+ def wait_queued_completions(timeout, alertable = false, &)
+ overlapped_entries = uninitialized LibC::OVERLAPPED_ENTRY[64]
if timeout > UInt64::MAX
timeout = LibC::INFINITE
else
timeout = timeout.to_u64
end
- result = LibC.GetQueuedCompletionStatusEx(Crystal::EventLoop.current.iocp, overlapped_entries, overlapped_entries.size, out removed, timeout, alertable)
+
+ result = LibC.GetQueuedCompletionStatusEx(@handle, overlapped_entries, overlapped_entries.size, out removed, timeout, alertable)
+
if result == 0
error = WinError.value
if timeout && error.wait_timeout?
@@ -33,26 +109,29 @@ module Crystal::IOCP
raise IO::Error.new("GetQueuedCompletionStatusEx returned 0")
end
+ # TODO: wouldn't the processing fit better in `EventLoop::IOCP#run`?
removed.times do |i|
entry = overlapped_entries[i]
- # at the moment only `::Process#wait` uses a non-nil completion key; all
- # I/O operations, including socket ones, do not set this field
+ # See `CompletionKey` for the operations that use a non-nil completion
+ # key. All IO operations (include File, Socket) do not set this field.
case completion_key = Pointer(Void).new(entry.lpCompletionKey).as(CompletionKey?)
- when Nil
+ in Nil
operation = OverlappedOperation.unbox(entry.lpOverlapped)
+ Crystal.trace :evloop, "operation", op: operation.class.name, fiber: operation.@fiber
operation.schedule { |fiber| yield fiber }
- else
- case entry.dwNumberOfBytesTransferred
- when LibC::JOB_OBJECT_MSG_EXIT_PROCESS, LibC::JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS
+ in CompletionKey
+ Crystal.trace :evloop, "completion", tag: completion_key.tag.to_s, bytes: entry.dwNumberOfBytesTransferred, fiber: completion_key.fiber
+
+ if completion_key.valid?(entry.dwNumberOfBytesTransferred)
+ # if `Process` exits before a call to `#wait`, this fiber will be
+ # reset already
if fiber = completion_key.fiber
- # this ensures the `::Process` doesn't keep an indirect reference to
- # `::Thread.current`, as that leads to a finalization cycle
+ # this ensures existing references to `completion_key` do not keep
+ # an indirect reference to `::Thread.current`, as that leads to a
+ # finalization cycle
completion_key.fiber = nil
-
yield fiber
- else
- # the `Process` exits before a call to `#wait`; do nothing
end
end
end
@@ -61,49 +140,122 @@ module Crystal::IOCP
false
end
- class OverlappedOperation
+ def post_queued_completion_status(completion_key : CompletionKey, number_of_bytes_transferred = 0)
+ result = LibC.PostQueuedCompletionStatus(@handle, number_of_bytes_transferred, completion_key.as(Void*).address, nil)
+ raise RuntimeError.from_winerror("PostQueuedCompletionStatus") if result == 0
+ end
+
+ def create_wait_completion_packet : LibC::HANDLE
+ packet_handle = LibC::HANDLE.null
+ object_attributes = Pointer(LibC::OBJECT_ATTRIBUTES).null
+ status =
+ {% if flag?(:interpreted) %}
+ LibNTDLL.NtCreateWaitCompletionPacket(pointerof(packet_handle), LibNTDLL::GENERIC_ALL, object_attributes)
+ {% else %}
+ @@_NtCreateWaitCompletionPacket.call(pointerof(packet_handle), LibNTDLL::GENERIC_ALL, object_attributes)
+ {% end %}
+ raise RuntimeError.from_os_error("NtCreateWaitCompletionPacket", WinError.from_ntstatus(status)) unless status == 0
+ packet_handle
+ end
+
+ def associate_wait_completion_packet(wait_handle : LibC::HANDLE, target_handle : LibC::HANDLE, completion_key : CompletionKey) : Bool
+ signaled = 0_u8
+ status =
+ {% if flag?(:interpreted) %}
+ LibNTDLL.NtAssociateWaitCompletionPacket(wait_handle, @handle,
+ target_handle, completion_key.as(Void*), nil, 0, nil, pointerof(signaled))
+ {% else %}
+ @@_NtAssociateWaitCompletionPacket.call(wait_handle, @handle,
+ target_handle, completion_key.as(Void*), Pointer(Void).null,
+ LibNTDLL::NTSTATUS.new!(0), Pointer(LibC::ULONG).null,
+ pointerof(signaled))
+ {% end %}
+ raise RuntimeError.from_os_error("NtAssociateWaitCompletionPacket", WinError.from_ntstatus(status)) unless status == 0
+ signaled == 1
+ end
+
+ def cancel_wait_completion_packet(wait_handle : LibC::HANDLE, remove_signaled : Bool) : LibNTDLL::NTSTATUS
+ status =
+ {% if flag?(:interpreted) %}
+ LibNTDLL.NtCancelWaitCompletionPacket(wait_handle, remove_signaled ? 1 : 0)
+ {% else %}
+ @@_NtCancelWaitCompletionPacket.call(wait_handle, remove_signaled ? 1_u8 : 0_u8)
+ {% end %}
+ case status
+ when LibC::STATUS_CANCELLED, LibC::STATUS_SUCCESS, LibC::STATUS_PENDING
+ status
+ else
+ raise RuntimeError.from_os_error("NtCancelWaitCompletionPacket", WinError.from_ntstatus(status))
+ end
+ end
+
+ abstract class OverlappedOperation
enum State
STARTED
DONE
- CANCELLED
end
+ abstract def wait_for_result(timeout, & : WinError ->)
+ private abstract def try_cancel : Bool
+
@overlapped = LibC::OVERLAPPED.new
- @fiber = Fiber.current
+ @fiber = ::Fiber.current
@state : State = :started
- property next : OverlappedOperation?
- property previous : OverlappedOperation?
- @@canceled = Thread::LinkedList(OverlappedOperation).new
- def initialize(@handle : LibC::HANDLE)
+ def self.run(*args, **opts, &)
+ operation_storage = uninitialized ReferenceStorage(self)
+ operation = unsafe_construct(pointerof(operation_storage), *args, **opts)
+ yield operation
end
- def initialize(handle : LibC::SOCKET)
- @handle = LibC::HANDLE.new(handle)
+ def self.unbox(overlapped : LibC::OVERLAPPED*) : self
+ start = overlapped.as(Pointer(UInt8)) - offsetof(self, @overlapped)
+ Box(self).unbox(start.as(Pointer(Void)))
end
- def self.run(handle, &)
- operation = OverlappedOperation.new(handle)
- begin
- yield operation
- ensure
- operation.done
- end
+ def to_unsafe
+ pointerof(@overlapped)
end
- def self.unbox(overlapped : LibC::OVERLAPPED*)
- start = overlapped.as(Pointer(UInt8)) - offsetof(OverlappedOperation, @overlapped)
- Box(OverlappedOperation).unbox(start.as(Pointer(Void)))
+ protected def schedule(&)
+ done!
+ yield @fiber
end
- def to_unsafe
- pointerof(@overlapped)
+ private def done!
+ @state = :done
end
- def wait_for_result(timeout, &)
- wait_for_completion(timeout)
+ private def wait_for_completion(timeout)
+ if timeout
+ event = ::Fiber.current.resume_event
+ event.add(timeout)
- raise Exception.new("Invalid state #{@state}") unless @state.done? || @state.started?
+ ::Fiber.suspend
+
+ if event.timed_out?
+ # By the time the fiber was resumed, the operation may have completed
+ # concurrently.
+ return if @state.done?
+ return unless try_cancel
+
+ # We cancelled the operation or failed to cancel it (e.g. race
+ # condition), we must suspend the fiber again until the completion
+ # port is notified of the actual result.
+ ::Fiber.suspend
+ end
+ else
+ ::Fiber.suspend
+ end
+ end
+ end
+
+ class IOOverlappedOperation < OverlappedOperation
+ def initialize(@handle : LibC::HANDLE)
+ end
+
+ def wait_for_result(timeout, & : WinError ->)
+ wait_for_completion(timeout)
result = LibC.GetOverlappedResult(@handle, self, out bytes, 0)
if result.zero?
@@ -116,15 +268,35 @@ module Crystal::IOCP
bytes
end
- def wait_for_wsa_result(timeout, &)
- wait_for_completion(timeout)
- wsa_result { |error| yield error }
+ private def try_cancel : Bool
+ # Microsoft documentation:
+ # The application must not free or reuse the OVERLAPPED structure
+ # associated with the canceled I/O operations until they have completed
+ # (this does not apply to asynchronous operations that finished
+ # synchronously, as nothing would be queued to the IOCP)
+ ret = LibC.CancelIoEx(@handle, self)
+ if ret.zero?
+ case error = WinError.value
+ when .error_not_found?
+ # Operation has already completed, do nothing
+ return false
+ else
+ raise RuntimeError.from_os_error("CancelIoEx", os_error: error)
+ end
+ end
+ true
+ end
+ end
+
+ class WSAOverlappedOperation < OverlappedOperation
+ def initialize(@handle : LibC::SOCKET)
end
- def wsa_result(&)
- raise Exception.new("Invalid state #{@state}") unless @state.done? || @state.started?
+ def wait_for_result(timeout, & : WinError ->)
+ wait_for_completion(timeout)
+
flags = 0_u32
- result = LibC.WSAGetOverlappedResult(LibC::SOCKET.new(@handle.address), self, out bytes, false, pointerof(flags))
+ result = LibC.WSAGetOverlappedResult(@handle, self, out bytes, false, pointerof(flags))
if result.zero?
error = WinError.wsa_value
yield error
@@ -135,55 +307,73 @@ module Crystal::IOCP
bytes
end
- protected def schedule(&)
- case @state
- when .started?
- yield @fiber
- done!
- when .cancelled?
- @@canceled.delete(self)
- else
- raise Exception.new("Invalid state #{@state}")
- end
- end
-
- protected def done
- case @state
- when .started?
- # https://learn.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-cancelioex
- # > The application must not free or reuse the OVERLAPPED structure
- # associated with the canceled I/O operations until they have completed
- if LibC.CancelIoEx(@handle, self) != 0
- @state = :cancelled
- @@canceled.push(self) # to increase lifetime
+ private def try_cancel : Bool
+ # Microsoft documentation:
+ # The application must not free or reuse the OVERLAPPED structure
+ # associated with the canceled I/O operations until they have completed
+ # (this does not apply to asynchronous operations that finished
+ # synchronously, as nothing would be queued to the IOCP)
+ ret = LibC.CancelIoEx(Pointer(Void).new(@handle), self)
+ if ret.zero?
+ case error = WinError.value
+ when .error_not_found?
+ # Operation has already completed, do nothing
+ return false
+ else
+ raise RuntimeError.from_os_error("CancelIoEx", os_error: error)
end
end
+ true
end
+ end
- def done!
- @state = :done
+ class GetAddrInfoOverlappedOperation < OverlappedOperation
+ getter iocp
+ setter cancel_handle : LibC::HANDLE = LibC::INVALID_HANDLE_VALUE
+
+ def initialize(@iocp : LibC::HANDLE)
end
- def wait_for_completion(timeout)
- if timeout
- timeout_event = Crystal::IOCP::Event.new(Fiber.current)
- timeout_event.add(timeout)
- else
- timeout_event = Crystal::IOCP::Event.new(Fiber.current, Time::Span::MAX)
+ def wait_for_result(timeout, & : WinError ->)
+ wait_for_completion(timeout)
+
+ result = LibC.GetAddrInfoExOverlappedResult(self)
+ unless result.zero?
+ error = WinError.new(result.to_u32!)
+ yield error
+
+ raise ::Socket::Addrinfo::Error.from_os_error("GetAddrInfoExOverlappedResult", error)
end
- # memoize event loop to make sure that we still target the same instance
- # after wakeup (guaranteed by current MT model but let's be future proof)
- event_loop = Crystal::EventLoop.current
- event_loop.enqueue(timeout_event)
- Fiber.suspend
+ @overlapped.union.pointer.as(LibC::ADDRINFOEXW**).value
+ end
- event_loop.dequeue(timeout_event)
+ private def try_cancel : Bool
+ ret = LibC.GetAddrInfoExCancel(pointerof(@cancel_handle))
+ unless ret.zero?
+ case error = WinError.new(ret.to_u32!)
+ when .wsa_invalid_handle?
+ # Operation has already completed, do nothing
+ return false
+ else
+ raise ::Socket::Addrinfo::Error.from_os_error("GetAddrInfoExCancel", error)
+ end
+ end
+ true
end
end
- def self.overlapped_operation(target, handle, method, timeout, *, writing = false, &)
- OverlappedOperation.run(handle) do |operation|
+ def self.overlapped_operation(file_descriptor, method, timeout, *, offset = nil, writing = false, &)
+ handle = file_descriptor.windows_handle
+ seekable = LibC.SetFilePointerEx(handle, 0, out original_offset, IO::Seek::Current) != 0
+
+ IOOverlappedOperation.run(handle) do |operation|
+ overlapped = operation.to_unsafe
+ if seekable
+ start_offset = offset || original_offset
+ overlapped.value.union.offset.offset = LibC::DWORD.new!(start_offset)
+ overlapped.value.union.offset.offsetHigh = LibC::DWORD.new!(start_offset >> 32)
+ end
result, value = yield operation
if result == 0
@@ -195,18 +385,21 @@ module Crystal::IOCP
when .error_io_pending?
# the operation is running asynchronously; do nothing
when .error_access_denied?
- raise IO::Error.new "File not open for #{writing ? "writing" : "reading"}", target: target
+ raise IO::Error.new "File not open for #{writing ? "writing" : "reading"}", target: file_descriptor
else
- raise IO::Error.from_os_error(method, error, target: target)
+ raise IO::Error.from_os_error(method, error, target: file_descriptor)
end
else
- operation.done!
+ # operation completed synchronously; seek forward by number of bytes
+ # read or written if handle is seekable, since overlapped I/O doesn't do
+ # it automatically
+ LibC.SetFilePointerEx(handle, value, nil, IO::Seek::Current) if seekable
return value
end
- operation.wait_for_result(timeout) do |error|
+ byte_count = operation.wait_for_result(timeout) do |error|
case error
- when .error_io_incomplete?
+ when .error_io_incomplete?, .error_operation_aborted?
raise IO::TimeoutError.new("#{method} timed out")
when .error_handle_eof?
return 0_u32
@@ -215,11 +408,20 @@ module Crystal::IOCP
return 0_u32
end
end
+
+ # operation completed asynchronously; seek to the original file position
+ # plus the number of bytes read or written (other operations might have
+ # moved the file pointer so we don't use `IO::Seek::Current` here), unless
+ # we are calling `Crystal::System::FileDescriptor.pread`
+ if seekable && !offset
+ LibC.SetFilePointerEx(handle, original_offset + byte_count, nil, IO::Seek::Set)
+ end
+ byte_count
end
end
def self.wsa_overlapped_operation(target, socket, method, timeout, connreset_is_error = true, &)
- OverlappedOperation.run(socket) do |operation|
+ WSAOverlappedOperation.run(socket) do |operation|
result, value = yield operation
if result == LibC::SOCKET_ERROR
@@ -230,13 +432,12 @@ module Crystal::IOCP
raise IO::Error.from_os_error(method, error, target: target)
end
else
- operation.done!
return value
end
- operation.wait_for_wsa_result(timeout) do |error|
+ operation.wait_for_result(timeout) do |error|
case error
- when .wsa_io_incomplete?
+ when .wsa_io_incomplete?, .error_operation_aborted?
raise IO::TimeoutError.new("#{method} timed out")
when .wsaeconnreset?
return 0_u32 unless connreset_is_error
diff --git a/src/crystal/system/win32/library_archive.cr b/src/crystal/system/win32/library_archive.cr
index 775677938bac..24c50f3405fa 100644
--- a/src/crystal/system/win32/library_archive.cr
+++ b/src/crystal/system/win32/library_archive.cr
@@ -17,6 +17,10 @@ module Crystal::System::LibraryArchive
private struct COFFReader
getter dlls = Set(String).new
+ # MSVC-style import libraries include the `__NULL_IMPORT_DESCRIPTOR` symbol,
+ # MinGW-style ones do not
+ getter? msvc = false
+
def initialize(@ar : ::File)
end
@@ -39,6 +43,7 @@ module Crystal::System::LibraryArchive
if first
first = false
return unless filename == "/"
+ handle_first_member(io)
elsif !filename.in?("/", "//")
handle_standard_member(io)
end
@@ -62,26 +67,69 @@ module Crystal::System::LibraryArchive
@ar.seek(new_pos)
end
+ private def handle_first_member(io)
+ symbol_count = io.read_bytes(UInt32, IO::ByteFormat::BigEndian)
+
+ # 4-byte offset per symbol
+ io.skip(symbol_count * 4)
+
+ symbol_count.times do
+ symbol = io.gets('\0', chomp: true)
+ if symbol == "__NULL_IMPORT_DESCRIPTOR"
+ @msvc = true
+ break
+ end
+ end
+ end
+
private def handle_standard_member(io)
- sig1 = io.read_bytes(UInt16, IO::ByteFormat::LittleEndian)
- return unless sig1 == 0x0000 # IMAGE_FILE_MACHINE_UNKNOWN
+ machine = io.read_bytes(UInt16, IO::ByteFormat::LittleEndian)
+ section_count = io.read_bytes(UInt16, IO::ByteFormat::LittleEndian)
- sig2 = io.read_bytes(UInt16, IO::ByteFormat::LittleEndian)
- return unless sig2 == 0xFFFF
+ if machine == 0x0000 && section_count == 0xFFFF
+ # short import library
+ version = io.read_bytes(UInt16, IO::ByteFormat::LittleEndian)
+ return unless version == 0 # 1 and 2 are used by object files (ANON_OBJECT_HEADER)
- version = io.read_bytes(UInt16, IO::ByteFormat::LittleEndian)
- return unless version == 0 # 1 and 2 are used by object files (ANON_OBJECT_HEADER)
+ # machine(2) + time(4) + size(4) + ordinal/hint(2) + flags(2)
+ io.skip(14)
- # machine(2) + time(4) + size(4) + ordinal/hint(2) + flags(2)
- io.skip(14)
+ # TODO: is there a way to do this without constructing a temporary string,
+ # but with the optimizations present in `IO#gets`?
+ return unless io.gets('\0') # symbol name
- # TODO: is there a way to do this without constructing a temporary string,
- # but with the optimizations present in `IO#gets`?
- return unless io.gets('\0') # symbol name
+ if dll_name = io.gets('\0', chomp: true)
+ @dlls << dll_name if valid_dll?(dll_name)
+ end
+ else
+ # long import library, code based on GNU binutils `dlltool -I`:
+ # https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=binutils/dlltool.c;hb=967dc35c78adb85ee1e2e596047d9dc69107a9db#l3231
+
+ # timeDateStamp(4) + pointerToSymbolTable(4) + numberOfSymbols(4) + sizeOfOptionalHeader(2) + characteristics(2)
+ io.skip(16)
+
+ section_count.times do |i|
+ section_header = uninitialized LibC::IMAGE_SECTION_HEADER
+ return unless io.read_fully?(pointerof(section_header).to_slice(1).to_unsafe_bytes)
+
+ name = String.new(section_header.name.to_unsafe, section_header.name.index(0) || section_header.name.size)
+ next unless name == (msvc? ? ".idata$6" : ".idata$7")
+
+ if msvc? ? section_header.characteristics.bits_set?(LibC::IMAGE_SCN_CNT_INITIALIZED_DATA) : section_header.pointerToRelocations == 0
+ bytes_read = sizeof(LibC::IMAGE_FILE_HEADER) + sizeof(LibC::IMAGE_SECTION_HEADER) * (i + 1)
+ io.skip(section_header.pointerToRawData - bytes_read)
+ if dll_name = io.gets('\0', chomp: true, limit: section_header.sizeOfRawData)
+ @dlls << dll_name if valid_dll?(dll_name)
+ end
+ end
- if dll_name = io.gets('\0', chomp: true)
- @dlls << dll_name
+ return
+ end
end
end
+
+ private def valid_dll?(name)
+ name.size >= 5 && name[-4..].compare(".dll", case_insensitive: true) == 0
+ end
end
end
diff --git a/src/crystal/system/win32/path.cr b/src/crystal/system/win32/path.cr
index 06f9346a2bae..f7bb1d23191b 100644
--- a/src/crystal/system/win32/path.cr
+++ b/src/crystal/system/win32/path.cr
@@ -4,18 +4,16 @@ require "c/shlobj_core"
module Crystal::System::Path
def self.home : String
- if home_path = ENV["USERPROFILE"]?.presence
- home_path
+ ENV["USERPROFILE"]?.presence || known_folder_path(LibC::FOLDERID_Profile)
+ end
+
+ def self.known_folder_path(guid : LibC::GUID) : String
+ if LibC.SHGetKnownFolderPath(pointerof(guid), 0, nil, out path_ptr) == 0
+ path, _ = String.from_utf16(path_ptr)
+ LibC.CoTaskMemFree(path_ptr)
+ path
else
- # TODO: interpreter doesn't implement pointerof(Path)` yet
- folderid = LibC::FOLDERID_Profile
- if LibC.SHGetKnownFolderPath(pointerof(folderid), 0, nil, out path_ptr) == 0
- home_path, _ = String.from_utf16(path_ptr)
- LibC.CoTaskMemFree(path_ptr)
- home_path
- else
- raise RuntimeError.from_winerror("SHGetKnownFolderPath")
- end
+ raise RuntimeError.from_winerror("SHGetKnownFolderPath")
end
end
end
diff --git a/src/crystal/system/win32/process.cr b/src/crystal/system/win32/process.cr
index 05b2ea36584e..5249491bbd3f 100644
--- a/src/crystal/system/win32/process.cr
+++ b/src/crystal/system/win32/process.cr
@@ -17,7 +17,7 @@ struct Crystal::System::Process
@thread_id : LibC::DWORD
@process_handle : LibC::HANDLE
@job_object : LibC::HANDLE
- @completion_key = IOCP::CompletionKey.new
+ @completion_key = IOCP::CompletionKey.new(:process_run)
@@interrupt_handler : Proc(::Process::ExitReason, Nil)?
@@interrupt_count = Crystal::AtomicSemaphore.new
@@ -37,7 +37,7 @@ struct Crystal::System::Process
LibC::JOBOBJECTINFOCLASS::AssociateCompletionPortInformation,
LibC::JOBOBJECT_ASSOCIATE_COMPLETION_PORT.new(
completionKey: @completion_key.as(Void*),
- completionPort: Crystal::EventLoop.current.iocp,
+ completionPort: Crystal::EventLoop.current.iocp_handle,
),
)
@@ -203,7 +203,7 @@ struct Crystal::System::Process
def self.start_interrupt_loop : Nil
return unless @@setup_interrupt_handler.test_and_set
- spawn(name: "Interrupt signal loop") do
+ spawn(name: "interrupt-signal-loop") do
while true
@@interrupt_count.wait { sleep 50.milliseconds }
@@ -326,9 +326,9 @@ struct Crystal::System::Process
end
private def self.try_replace(command_args, env, clear_env, input, output, error, chdir)
- reopen_io(input, ORIGINAL_STDIN)
- reopen_io(output, ORIGINAL_STDOUT)
- reopen_io(error, ORIGINAL_STDERR)
+ old_input_fd = reopen_io(input, ORIGINAL_STDIN)
+ old_output_fd = reopen_io(output, ORIGINAL_STDOUT)
+ old_error_fd = reopen_io(error, ORIGINAL_STDERR)
ENV.clear if clear_env
env.try &.each do |key, val|
@@ -351,11 +351,18 @@ struct Crystal::System::Process
argv << Pointer(LibC::WCHAR).null
LibC._wexecvp(command, argv)
+
+ # exec failed; restore the original C runtime file descriptors
+ errno = Errno.value
+ LibC._dup2(old_input_fd, 0)
+ LibC._dup2(old_output_fd, 1)
+ LibC._dup2(old_error_fd, 2)
+ errno
end
def self.replace(command_args, env, clear_env, input, output, error, chdir) : NoReturn
- try_replace(command_args, env, clear_env, input, output, error, chdir)
- raise_exception_from_errno(command_args.is_a?(String) ? command_args : command_args[0])
+ errno = try_replace(command_args, env, clear_env, input, output, error, chdir)
+ raise_exception_from_errno(command_args.is_a?(String) ? command_args : command_args[0], errno)
end
private def self.raise_exception_from_errno(command, errno = Errno.value)
@@ -367,21 +374,41 @@ struct Crystal::System::Process
end
end
+ # Replaces the C standard streams' file descriptors, not Win32's, since
+ # `try_replace` uses the C `LibC._wexecvp` and only cares about the former.
+ # Returns a duplicate of the original file descriptor
private def self.reopen_io(src_io : IO::FileDescriptor, dst_io : IO::FileDescriptor)
- src_io = to_real_fd(src_io)
+ unless src_io.system_blocking?
+ raise IO::Error.new("Non-blocking streams are not supported in `Process.exec`", target: src_io)
+ end
- dst_io.reopen(src_io)
- dst_io.blocking = true
- dst_io.close_on_exec = false
- end
+ src_fd =
+ case src_io
+ when STDIN then 0
+ when STDOUT then 1
+ when STDERR then 2
+ else
+ LibC._open_osfhandle(src_io.windows_handle, 0)
+ end
- private def self.to_real_fd(fd : IO::FileDescriptor)
- case fd
- when STDIN then ORIGINAL_STDIN
- when STDOUT then ORIGINAL_STDOUT
- when STDERR then ORIGINAL_STDERR
- else fd
+ dst_fd =
+ case dst_io
+ when ORIGINAL_STDIN then 0
+ when ORIGINAL_STDOUT then 1
+ when ORIGINAL_STDERR then 2
+ else
+ raise "BUG: Invalid destination IO"
+ end
+
+ return src_fd if dst_fd == src_fd
+
+ orig_src_fd = LibC._dup(src_fd)
+
+ if LibC._dup2(src_fd, dst_fd) == -1
+ raise IO::Error.from_errno("Failed to replace C file descriptor", target: dst_io)
end
+
+ orig_src_fd
end
def self.chroot(path)
diff --git a/src/crystal/system/win32/signal.cr b/src/crystal/system/win32/signal.cr
index d805ea4fd1ab..4cebe7cf9c6a 100644
--- a/src/crystal/system/win32/signal.cr
+++ b/src/crystal/system/win32/signal.cr
@@ -1,4 +1,5 @@
require "c/signal"
+require "c/malloc"
module Crystal::System::Signal
def self.trap(signal, handler) : Nil
@@ -16,4 +17,47 @@ module Crystal::System::Signal
def self.ignore(signal) : Nil
raise NotImplementedError.new("Crystal::System::Signal.ignore")
end
+
+ def self.setup_seh_handler
+ LibC.AddVectoredExceptionHandler(1, ->(exception_info) do
+ case exception_info.value.exceptionRecord.value.exceptionCode
+ when LibC::EXCEPTION_ACCESS_VIOLATION
+ addr = exception_info.value.exceptionRecord.value.exceptionInformation[1]
+ Crystal::System.print_error "Invalid memory access (C0000005) at address %p\n", Pointer(Void).new(addr)
+ {% if flag?(:gnu) %}
+ Exception::CallStack.print_backtrace
+ {% else %}
+ Exception::CallStack.print_backtrace(exception_info)
+ {% end %}
+ LibC._exit(1)
+ when LibC::EXCEPTION_STACK_OVERFLOW
+ LibC._resetstkoflw
+ Crystal::System.print_error "Stack overflow (e.g., infinite or very deep recursion)\n"
+ {% if flag?(:gnu) %}
+ Exception::CallStack.print_backtrace
+ {% else %}
+ Exception::CallStack.print_backtrace(exception_info)
+ {% end %}
+ LibC._exit(1)
+ else
+ LibC::EXCEPTION_CONTINUE_SEARCH
+ end
+ end)
+
+ # ensure that even in the case of stack overflow there is enough reserved
+ # stack space for recovery (for other threads this is done in
+ # `Crystal::System::Thread.thread_proc`)
+ stack_size = Crystal::System::Fiber::RESERVED_STACK_SIZE
+ LibC.SetThreadStackGuarantee(pointerof(stack_size))
+
+ # this catches invalid argument checks inside the C runtime library
+ LibC._set_invalid_parameter_handler(->(expression, _function, _file, _line, _pReserved) do
+ message = expression ? String.from_utf16(expression)[0] : "(no message)"
+ Crystal::System.print_error "CRT invalid parameter handler invoked: %s\n", message
+ caller.each do |frame|
+ Crystal::System.print_error " from %s\n", frame
+ end
+ LibC._exit(1)
+ end)
+ end
end
diff --git a/src/crystal/system/win32/socket.cr b/src/crystal/system/win32/socket.cr
index 6a5d44ab5133..bfb82581204b 100644
--- a/src/crystal/system/win32/socket.cr
+++ b/src/crystal/system/win32/socket.cr
@@ -128,8 +128,8 @@ module Crystal::System::Socket
end
# :nodoc:
- def overlapped_connect(socket, method, &)
- IOCP::OverlappedOperation.run(socket) do |operation|
+ def overlapped_connect(socket, method, timeout, &)
+ IOCP::WSAOverlappedOperation.run(socket) do |operation|
result = yield operation
if result == 0
@@ -142,11 +142,10 @@ module Crystal::System::Socket
return ::Socket::Error.from_os_error("ConnectEx", error)
end
else
- operation.done!
return nil
end
- operation.wait_for_wsa_result(read_timeout) do |error|
+ operation.wait_for_result(timeout) do |error|
case error
when .wsa_io_incomplete?, .wsaeconnrefused?
return ::Socket::ConnectError.from_os_error(method, error)
@@ -193,7 +192,7 @@ module Crystal::System::Socket
end
def overlapped_accept(socket, method, &)
- IOCP::OverlappedOperation.run(socket) do |operation|
+ IOCP::WSAOverlappedOperation.run(socket) do |operation|
result = yield operation
if result == 0
@@ -204,18 +203,15 @@ module Crystal::System::Socket
return false
end
else
- operation.done!
return true
end
- unless operation.wait_for_completion(read_timeout)
- raise IO::TimeoutError.new("#{method} timed out")
- end
-
- operation.wsa_result do |error|
+ operation.wait_for_result(read_timeout) do |error|
case error
when .wsa_io_incomplete?, .wsaenotsock?
return false
+ when .error_operation_aborted?
+ raise IO::TimeoutError.new("#{method} timed out")
end
end
@@ -370,6 +366,10 @@ module Crystal::System::Socket
end
def system_close
+ socket_close
+ end
+
+ private def socket_close(&)
handle = @volatile_fd.swap(LibC::INVALID_SOCKET)
ret = LibC.closesocket(handle)
@@ -379,11 +379,17 @@ module Crystal::System::Socket
when WinError::WSAEINTR, WinError::WSAEINPROGRESS
# ignore
else
- raise ::Socket::Error.from_os_error("Error closing socket", err)
+ yield err
end
end
end
+ def socket_close
+ socket_close do |err|
+ raise ::Socket::Error.from_os_error("Error closing socket", err)
+ end
+ end
+
private def system_local_address
sockaddr6 = uninitialized LibC::SockaddrIn6
sockaddr = pointerof(sockaddr6).as(LibC::Sockaddr*)
diff --git a/src/crystal/system/win32/thread.cr b/src/crystal/system/win32/thread.cr
index ddfe3298b20a..2ff7ca438d87 100644
--- a/src/crystal/system/win32/thread.cr
+++ b/src/crystal/system/win32/thread.cr
@@ -1,5 +1,6 @@
require "c/processthreadsapi"
require "c/synchapi"
+require "../panic"
module Crystal::System::Thread
alias Handle = LibC::HANDLE
@@ -19,6 +20,16 @@ module Crystal::System::Thread
)
end
+ def self.init : Nil
+ {% if flag?(:gnu) %}
+ current_key = LibC.TlsAlloc
+ if current_key == LibC::TLS_OUT_OF_INDEXES
+ Crystal::System.panic("TlsAlloc()", WinError.value)
+ end
+ @@current_key = current_key
+ {% end %}
+ end
+
def self.thread_proc(data : Void*) : LibC::UInt
# ensure that even in the case of stack overflow there is enough reserved
# stack space for recovery (for the main thread this is done in
@@ -44,12 +55,50 @@ module Crystal::System::Thread
LibC.SwitchToThread
end
- @[ThreadLocal]
- class_property current_thread : ::Thread { ::Thread.new }
+ # MinGW does not support TLS correctly
+ {% if flag?(:gnu) %}
+ @@current_key = uninitialized LibC::DWORD
- def self.current_thread? : ::Thread?
- @@current_thread
- end
+ def self.current_thread : ::Thread
+ th = current_thread?
+ return th if th
+
+ # Thread#start sets `Thread.current` as soon it starts. Thus we know
+ # that if `Thread.current` is not set then we are in the main thread
+ self.current_thread = ::Thread.new
+ end
+
+ def self.current_thread? : ::Thread?
+ ptr = LibC.TlsGetValue(@@current_key)
+ err = WinError.value
+ unless err == WinError::ERROR_SUCCESS
+ Crystal::System.panic("TlsGetValue()", err)
+ end
+
+ ptr.as(::Thread?)
+ end
+
+ def self.current_thread=(thread : ::Thread)
+ if LibC.TlsSetValue(@@current_key, thread.as(Void*)) == 0
+ Crystal::System.panic("TlsSetValue()", WinError.value)
+ end
+ thread
+ end
+ {% else %}
+ @[ThreadLocal]
+ @@current_thread : ::Thread?
+
+ def self.current_thread : ::Thread
+ @@current_thread ||= ::Thread.new
+ end
+
+ def self.current_thread? : ::Thread?
+ @@current_thread
+ end
+
+ def self.current_thread=(@@current_thread : ::Thread)
+ end
+ {% end %}
def self.sleep(time : ::Time::Span) : Nil
LibC.Sleep(time.total_milliseconds.to_i.clamp(1..))
@@ -75,7 +124,9 @@ module Crystal::System::Thread
{% else %}
tib = LibC.NtCurrentTeb
high_limit = tib.value.stackBase
- LibC.VirtualQuery(tib.value.stackLimit, out mbi, sizeof(LibC::MEMORY_BASIC_INFORMATION))
+ if LibC.VirtualQuery(tib.value.stackLimit, out mbi, sizeof(LibC::MEMORY_BASIC_INFORMATION)) == 0
+ raise RuntimeError.from_winerror("VirtualQuery")
+ end
low_limit = mbi.allocationBase
low_limit
{% end %}
@@ -87,4 +138,31 @@ module Crystal::System::Thread
{% end %}
name
end
+
+ def self.init_suspend_resume : Nil
+ end
+
+ private def system_suspend : Nil
+ if LibC.SuspendThread(@system_handle) == -1
+ Crystal::System.panic("SuspendThread()", WinError.value)
+ end
+ end
+
+ private def system_wait_suspended : Nil
+ # context must be aligned on 16 bytes but we lack a mean to force the
+ # alignment on the struct, so we overallocate then realign the pointer:
+ local = uninitialized UInt8[sizeof(Tuple(LibC::CONTEXT, UInt8[15]))]
+ thread_context = Pointer(LibC::CONTEXT).new(local.to_unsafe.address &+ 15_u64 & ~15_u64)
+ thread_context.value.contextFlags = LibC::CONTEXT_FULL
+
+ if LibC.GetThreadContext(@system_handle, thread_context) == -1
+ Crystal::System.panic("GetThreadContext()", WinError.value)
+ end
+ end
+
+ private def system_resume : Nil
+ if LibC.ResumeThread(@system_handle) == -1
+ Crystal::System.panic("ResumeThread()", WinError.value)
+ end
+ end
end
diff --git a/src/crystal/system/win32/user.cr b/src/crystal/system/win32/user.cr
new file mode 100644
index 000000000000..4a06570c72b8
--- /dev/null
+++ b/src/crystal/system/win32/user.cr
@@ -0,0 +1,222 @@
+require "crystal/system/windows"
+require "c/lm"
+require "c/userenv"
+require "c/security"
+
+# This file contains source code derived from the following:
+#
+# * https://cs.opensource.google/go/go/+/refs/tags/go1.23.0:src/os/user/lookup_windows.go
+# * https://cs.opensource.google/go/go/+/refs/tags/go1.23.0:src/syscall/security_windows.go
+#
+# The following is their license:
+#
+# Copyright 2009 The Go Authors.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google LLC nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+module Crystal::System::User
+ def initialize(@username : String, @id : String, @group_id : String, @name : String, @home_directory : String)
+ end
+
+ def system_username
+ @username
+ end
+
+ def system_id
+ @id
+ end
+
+ def system_group_id
+ @group_id
+ end
+
+ def system_name
+ @name
+ end
+
+ def system_home_directory
+ @home_directory
+ end
+
+ def system_shell
+ Crystal::System::User.cmd_path
+ end
+
+ class_getter(cmd_path : String) do
+ "#{Crystal::System::Path.known_folder_path(LibC::FOLDERID_System)}\\cmd.exe"
+ end
+
+ def self.from_username?(username : String) : ::System::User?
+ if found = Crystal::System.name_to_sid(username)
+ if found.type.sid_type_user?
+ from_sid(found.sid)
+ end
+ end
+ end
+
+ def self.from_id?(id : String) : ::System::User?
+ if sid = Crystal::System.sid_from_s(id)
+ begin
+ from_sid(sid)
+ ensure
+ LibC.LocalFree(sid)
+ end
+ end
+ end
+
+ private def self.from_sid(sid : LibC::SID*) : ::System::User?
+ canonical = Crystal::System.sid_to_name(sid) || return
+ return unless canonical.type.sid_type_user?
+
+ domain_and_user = "#{canonical.domain}\\#{canonical.name}"
+ full_name = lookup_full_name(canonical.name, canonical.domain, domain_and_user) || return
+ pgid = lookup_primary_group_id(canonical.name, canonical.domain) || return
+ uid = Crystal::System.sid_to_s(sid)
+ home_dir = lookup_home_directory(uid, canonical.name) || return
+
+ ::System::User.new(domain_and_user, uid, pgid, full_name, home_dir)
+ end
+
+ private def self.lookup_full_name(name : String, domain : String, domain_and_user : String) : String?
+ if domain_joined?
+ domain_and_user = Crystal::System.to_wstr(domain_and_user)
+ Crystal::System.retry_wstr_buffer do |buffer, small_buf|
+ len = LibC::ULong.new(buffer.size)
+ if LibC.TranslateNameW(domain_and_user, LibC::EXTENDED_NAME_FORMAT::NameSamCompatible, LibC::EXTENDED_NAME_FORMAT::NameDisplay, buffer, pointerof(len)) != 0
+ return String.from_utf16(buffer[0, len - 1])
+ elsif small_buf && len > 0
+ next len
+ else
+ break
+ end
+ end
+ end
+
+ info = uninitialized LibC::USER_INFO_10*
+ if LibC.NetUserGetInfo(Crystal::System.to_wstr(domain), Crystal::System.to_wstr(name), 10, pointerof(info).as(LibC::BYTE**)) == LibC::NERR_Success
+ begin
+ str, _ = String.from_utf16(info.value.usri10_full_name)
+ return str
+ ensure
+ LibC.NetApiBufferFree(info)
+ end
+ end
+
+ # domain worked neither as a domain nor as a server
+ # could be domain server unavailable
+ # pretend username is fullname
+ name
+ end
+
+ # obtains the primary group SID for a user using this method:
+ # https://support.microsoft.com/en-us/help/297951/how-to-use-the-primarygroupid-attribute-to-find-the-primary-group-for
+ # The method follows this formula: domainRID + "-" + primaryGroupRID
+ private def self.lookup_primary_group_id(name : String, domain : String) : String?
+ domain_sid = Crystal::System.name_to_sid(domain) || return
+ return unless domain_sid.type.sid_type_domain?
+
+ domain_sid_str = Crystal::System.sid_to_s(domain_sid.sid)
+
+ # If the user has joined a domain use the RID of the default primary group
+ # called "Domain Users":
+ # https://support.microsoft.com/en-us/help/243330/well-known-security-identifiers-in-windows-operating-systems
+ # SID: S-1-5-21domain-513
+ #
+ # The correct way to obtain the primary group of a domain user is
+ # probing the user primaryGroupID attribute in the server Active Directory:
+ # https://learn.microsoft.com/en-us/windows/win32/adschema/a-primarygroupid
+ #
+ # Note that the primary group of domain users should not be modified
+ # on Windows for performance reasons, even if it's possible to do that.
+ # The .NET Developer's Guide to Directory Services Programming - Page 409
+ # https://books.google.bg/books?id=kGApqjobEfsC&lpg=PA410&ots=p7oo-eOQL7&dq=primary%20group%20RID&hl=bg&pg=PA409#v=onepage&q&f=false
+ return "#{domain_sid_str}-513" if domain_joined?
+
+ # For non-domain users call NetUserGetInfo() with level 4, which
+ # in this case would not have any network overhead.
+ # The primary group should not change from RID 513 here either
+ # but the group will be called "None" instead:
+ # https://www.adampalmer.me/iodigitalsec/2013/08/10/windows-null-session-enumeration/
+ # "Group 'None' (RID: 513)"
+ info = uninitialized LibC::USER_INFO_4*
+ if LibC.NetUserGetInfo(Crystal::System.to_wstr(domain), Crystal::System.to_wstr(name), 4, pointerof(info).as(LibC::BYTE**)) == LibC::NERR_Success
+ begin
+ "#{domain_sid_str}-#{info.value.usri4_primary_group_id}"
+ ensure
+ LibC.NetApiBufferFree(info)
+ end
+ end
+ end
+
+ private REGISTRY_PROFILE_LIST = %q(SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList).to_utf16
+ private ProfileImagePath = "ProfileImagePath".to_utf16
+
+ private def self.lookup_home_directory(uid : String, username : String) : String?
+ # If this user has logged in at least once their home path should be stored
+ # in the registry under the specified SID. References:
+ # https://social.technet.microsoft.com/wiki/contents/articles/13895.how-to-remove-a-corrupted-user-profile-from-the-registry.aspx
+ # https://support.asperasoft.com/hc/en-us/articles/216127438-How-to-delete-Windows-user-profiles
+ #
+ # The registry is the most reliable way to find the home path as the user
+ # might have decided to move it outside of the default location,
+ # (e.g. C:\users). Reference:
+ # https://answers.microsoft.com/en-us/windows/forum/windows_7-security/how-do-i-set-a-home-directory-outside-cusers-for-a/aed68262-1bf4-4a4d-93dc-7495193a440f
+ reg_home_dir = WindowsRegistry.open?(LibC::HKEY_LOCAL_MACHINE, REGISTRY_PROFILE_LIST) do |key_handle|
+ WindowsRegistry.open?(key_handle, uid.to_utf16) do |sub_handle|
+ WindowsRegistry.get_string(sub_handle, ProfileImagePath)
+ end
+ end
+ return reg_home_dir if reg_home_dir
+
+ # If the home path does not exist in the registry, the user might
+ # have not logged in yet; fall back to using getProfilesDirectory().
+ # Find the username based on a SID and append that to the result of
+ # getProfilesDirectory(). The domain is not relevant here.
+ # NOTE: the user has not logged in so this directory might not exist
+ profile_dir = Crystal::System.retry_wstr_buffer do |buffer, small_buf|
+ len = LibC::DWORD.new(buffer.size)
+ if LibC.GetProfilesDirectoryW(buffer, pointerof(len)) != 0
+ break String.from_utf16(buffer[0, len - 1])
+ elsif small_buf && len > 0
+ next len
+ else
+ break nil
+ end
+ end
+ return "#{profile_dir}\\#{username}" if profile_dir
+ end
+
+ private def self.domain_joined? : Bool
+ status = LibC.NetGetJoinInformation(nil, out domain, out type)
+ if status != LibC::NERR_Success
+ raise RuntimeError.from_os_error("NetGetJoinInformation", WinError.new(status))
+ end
+ is_domain = type.net_setup_domain_name?
+ LibC.NetApiBufferFree(domain)
+ is_domain
+ end
+end
diff --git a/src/crystal/system/win32/waitable_timer.cr b/src/crystal/system/win32/waitable_timer.cr
new file mode 100644
index 000000000000..68ec821d6922
--- /dev/null
+++ b/src/crystal/system/win32/waitable_timer.cr
@@ -0,0 +1,38 @@
+require "c/ntdll"
+require "c/synchapi"
+require "c/winternl"
+
+class Crystal::System::WaitableTimer
+ getter handle : LibC::HANDLE
+
+ def initialize
+ flags = LibC::CREATE_WAITABLE_TIMER_HIGH_RESOLUTION
+ desired_access = LibC::SYNCHRONIZE | LibC::TIMER_QUERY_STATE | LibC::TIMER_MODIFY_STATE
+ @handle = LibC.CreateWaitableTimerExW(nil, nil, flags, desired_access)
+ raise RuntimeError.from_winerror("CreateWaitableTimerExW") if @handle.null?
+ end
+
+ def set(time : ::Time::Span) : Nil
+ # convert absolute time to relative time, expressed in 100ns interval,
+ # rounded up
+ seconds, nanoseconds = System::Time.monotonic
+ relative = time - ::Time::Span.new(seconds: seconds, nanoseconds: nanoseconds)
+ ticks = (relative.to_i * 10_000_000 + (relative.nanoseconds + 99) // 100).clamp(0_i64..)
+
+ # negative duration means relative time (positive would mean absolute
+ # realtime clock)
+ duration = -ticks
+
+ ret = LibC.SetWaitableTimer(@handle, pointerof(duration), 0, nil, nil, 0)
+ raise RuntimeError.from_winerror("SetWaitableTimer") if ret == 0
+ end
+
+ def cancel : Nil
+ ret = LibC.CancelWaitableTimer(@handle)
+ raise RuntimeError.from_winerror("CancelWaitableTimer") if ret == 0
+ end
+
+ def close : Nil
+ LibC.CloseHandle(@handle)
+ end
+end
diff --git a/src/crystal/system/win32/wmain.cr b/src/crystal/system/win32/wmain.cr
index 71383c66a88a..b1726f90329b 100644
--- a/src/crystal/system/win32/wmain.cr
+++ b/src/crystal/system/win32/wmain.cr
@@ -2,24 +2,23 @@ require "c/stringapiset"
require "c/winnls"
require "c/stdlib"
-{% begin %}
- # we have both `main` and `wmain`, so we must choose an unambiguous entry point
- @[Link({{ flag?(:static) ? "libcmt" : "msvcrt" }}, ldflags: "/ENTRY:wmainCRTStartup")]
+# we have both `main` and `wmain`, so we must choose an unambiguous entry point
+{% if flag?(:msvc) %}
+ @[Link({{ flag?(:static) ? "libcmt" : "msvcrt" }})]
+ @[Link(ldflags: "/ENTRY:wmainCRTStartup")]
+{% elsif flag?(:gnu) && !flag?(:interpreted) %}
+ @[Link(ldflags: "-municode")]
{% end %}
lib LibCrystalMain
end
-# The actual entry point for Windows executables. This is necessary because
-# *argv* (and Win32's `GetCommandLineA`) mistranslate non-ASCII characters to
-# Windows-1252, so `PROGRAM_NAME` and `ARGV` would be garbled; to avoid that, we
-# use this Windows-exclusive entry point which contains the correctly encoded
-# UTF-16 *argv*, convert it to UTF-8, and then forward it to the original
-# `main`.
+# The actual entry point for Windows executables.
#
-# The different main functions in `src/crystal/main.cr` need not be aware that
-# such an alternate entry point exists, nor that the original command line was
-# not UTF-8. Thus all other aspects of program initialization still occur there,
-# and uses of those main functions continue to work across platforms.
+# This is necessary because *argv* (and Win32's `GetCommandLineA`) mistranslate
+# non-ASCII characters to Windows-1252, so `PROGRAM_NAME` and `ARGV` would be
+# garbled; to avoid that, we use this Windows-exclusive entry point which
+# contains the correctly encoded UTF-16 *argv*, convert it to UTF-8, and then
+# forward it to the original `main`.
#
# NOTE: we cannot use anything from the standard library here, including the GC.
fun wmain(argc : Int32, argv : UInt16**) : Int32
@@ -43,5 +42,9 @@ fun wmain(argc : Int32, argv : UInt16**) : Int32
end
LibC.free(utf8_argv)
- status
+ # prefer explicit exit over returning the status, so we are free to resume the
+ # main thread's fiber on any thread, without occuring a weird behavior where
+ # another thread returns from main when the caller might expect the main
+ # thread to be the one returning.
+ LibC.exit(status)
end
diff --git a/src/crystal/system/windows.cr b/src/crystal/system/windows.cr
index b303d4d61f6d..90b38396cf8f 100644
--- a/src/crystal/system/windows.cr
+++ b/src/crystal/system/windows.cr
@@ -1,3 +1,5 @@
+require "c/sddl"
+
# :nodoc:
module Crystal::System
def self.retry_wstr_buffer(&)
@@ -13,4 +15,55 @@ module Crystal::System
def self.to_wstr(str : String, name : String? = nil) : LibC::LPWSTR
str.check_no_null_byte(name).to_utf16.to_unsafe
end
+
+ def self.sid_to_s(sid : LibC::SID*) : String
+ if LibC.ConvertSidToStringSidW(sid, out ptr) == 0
+ raise RuntimeError.from_winerror("ConvertSidToStringSidW")
+ end
+ str, _ = String.from_utf16(ptr)
+ LibC.LocalFree(ptr)
+ str
+ end
+
+ def self.sid_from_s(str : String) : LibC::SID*
+ status = LibC.ConvertStringSidToSidW(to_wstr(str), out sid)
+ status != 0 ? sid : Pointer(LibC::SID).null
+ end
+
+ record SIDLookupResult, sid : LibC::SID*, domain : String, type : LibC::SID_NAME_USE
+
+ def self.name_to_sid(name : String) : SIDLookupResult?
+ utf16_name = to_wstr(name)
+
+ sid_size = LibC::DWORD.zero
+ domain_buf_size = LibC::DWORD.zero
+ LibC.LookupAccountNameW(nil, utf16_name, nil, pointerof(sid_size), nil, pointerof(domain_buf_size), out _)
+
+ unless WinError.value.error_none_mapped?
+ sid = Pointer(UInt8).malloc(sid_size).as(LibC::SID*)
+ domain_buf = Slice(LibC::WCHAR).new(domain_buf_size)
+ if LibC.LookupAccountNameW(nil, utf16_name, sid, pointerof(sid_size), domain_buf, pointerof(domain_buf_size), out sid_type) != 0
+ domain = String.from_utf16(domain_buf[..-2])
+ SIDLookupResult.new(sid, domain, sid_type)
+ end
+ end
+ end
+
+ record NameLookupResult, name : String, domain : String, type : LibC::SID_NAME_USE
+
+ def self.sid_to_name(sid : LibC::SID*) : NameLookupResult?
+ name_buf_size = LibC::DWORD.zero
+ domain_buf_size = LibC::DWORD.zero
+ LibC.LookupAccountSidW(nil, sid, nil, pointerof(name_buf_size), nil, pointerof(domain_buf_size), out _)
+
+ unless WinError.value.error_none_mapped?
+ name_buf = Slice(LibC::WCHAR).new(name_buf_size)
+ domain_buf = Slice(LibC::WCHAR).new(domain_buf_size)
+ if LibC.LookupAccountSidW(nil, sid, name_buf, pointerof(name_buf_size), domain_buf, pointerof(domain_buf_size), out sid_type) != 0
+ name = String.from_utf16(name_buf[..-2])
+ domain = String.from_utf16(domain_buf[..-2])
+ NameLookupResult.new(name, domain, sid_type)
+ end
+ end
+ end
end
diff --git a/src/crystal/tracing.cr b/src/crystal/tracing.cr
index a680bfea717f..d9508eda85a8 100644
--- a/src/crystal/tracing.cr
+++ b/src/crystal/tracing.cr
@@ -7,6 +7,7 @@ module Crystal
enum Section
GC
Sched
+ Evloop
def self.from_id(slice) : self
{% begin %}
@@ -50,37 +51,58 @@ module Crystal
@size = 0
end
- def write(bytes : Bytes) : Nil
+ def write(value : Bytes) : Nil
pos = @size
remaining = N - pos
return if remaining == 0
- n = bytes.size.clamp(..remaining)
- bytes.to_unsafe.copy_to(@buf.to_unsafe + pos, n)
+ n = value.size.clamp(..remaining)
+ value.to_unsafe.copy_to(@buf.to_unsafe + pos, n)
@size = pos + n
end
- def write(string : String) : Nil
- write string.to_slice
+ def write(value : String) : Nil
+ write value.to_slice
end
- def write(fiber : Fiber) : Nil
- write fiber.as(Void*)
- write ":"
- write fiber.name || "?"
+ def write(value : Char) : Nil
+ chars = uninitialized UInt8[4]
+ i = 0
+ value.each_byte do |byte|
+ chars[i] = byte
+ i += 1
+ end
+ write chars.to_slice[0, i]
+ end
+
+ def write(value : Fiber) : Nil
+ write value.as(Void*)
+ write ':'
+ write value.name || '?'
end
- def write(ptr : Pointer) : Nil
+ def write(value : Pointer) : Nil
write "0x"
- System.to_int_slice(ptr.address, 16, true, 2) { |bytes| write(bytes) }
+ System.to_int_slice(value.address, 16, true, 2) { |bytes| write(bytes) }
+ end
+
+ def write(value : Int::Signed) : Nil
+ System.to_int_slice(value, 10, true, 2) { |bytes| write(bytes) }
+ end
+
+ def write(value : Int::Unsigned) : Nil
+ System.to_int_slice(value, 10, false, 2) { |bytes| write(bytes) }
+ end
+
+ def write(value : Time::Span) : Nil
+ write(value.seconds * Time::NANOSECONDS_PER_SECOND + value.nanoseconds)
end
- def write(int : Int::Signed) : Nil
- System.to_int_slice(int, 10, true, 2) { |bytes| write(bytes) }
+ def write(value : Bool) : Nil
+ write value ? '1' : '0'
end
- def write(uint : Int::Unsigned) : Nil
- System.to_int_slice(uint, 10, false, 2) { |bytes| write(bytes) }
+ def write(value : Nil) : Nil
end
def to_slice : Bytes
diff --git a/src/dir/glob.cr b/src/dir/glob.cr
index cd45f0a03baf..2fc8d988c20a 100644
--- a/src/dir/glob.cr
+++ b/src/dir/glob.cr
@@ -37,6 +37,10 @@ class Dir
# Returns an array of all files that match against any of *patterns*.
#
+ # ```
+ # Dir.glob "path/to/folder/*.txt" # Returns all files in the target folder that end in ".txt".
+ # Dir.glob "path/to/folder/**/*" # Returns all files in the target folder and its subfolders.
+ # ```
# The pattern syntax is similar to shell filename globbing, see `File.match?` for details.
#
# NOTE: Path separator in patterns needs to be always `/`. The returned file names use system-specific path separators.
diff --git a/src/docs_main.cr b/src/docs_main.cr
index 5769678ca131..1fec70580a04 100644
--- a/src/docs_main.cr
+++ b/src/docs_main.cr
@@ -52,11 +52,11 @@ require "./string_pool"
require "./string_scanner"
require "./unicode/unicode"
require "./uri"
+require "./uri/json"
+require "./uri/params/serializable"
require "./uuid"
require "./uuid/json"
require "./syscall"
-{% unless flag?(:win32) %}
- require "./system/*"
-{% end %}
+require "./system/*"
require "./wait_group"
require "./docs_pseudo_methods"
diff --git a/src/docs_pseudo_methods.cr b/src/docs_pseudo_methods.cr
index d4f1fb832263..d789f4a9ecc8 100644
--- a/src/docs_pseudo_methods.cr
+++ b/src/docs_pseudo_methods.cr
@@ -200,3 +200,33 @@ class Object
def __crystal_pseudo_responds_to?(name : Symbol) : Bool
end
end
+
+# Some expressions won't return to the current scope and therefore have no return type.
+# This is expressed as the special return type `NoReturn`.
+#
+# Typical examples for non-returning methods and keywords are `return`, `exit`, `raise`, `next`, and `break`.
+#
+# This is for example useful for deconstructing union types:
+#
+# ```
+# string = STDIN.gets
+# typeof(string) # => String?
+# typeof(raise "Empty input") # => NoReturn
+# typeof(string || raise "Empty input") # => String
+# ```
+#
+# The compiler recognizes that in case string is Nil, the right hand side of the expression `string || raise` will be evaluated.
+# Since `typeof(raise "Empty input")` is `NoReturn` the execution would not return to the current scope in that case.
+# That leaves only `String` as resulting type of the expression.
+#
+# Every expression whose code paths all result in `NoReturn` will be `NoReturn` as well.
+# `NoReturn` does not show up in a union type because it would essentially be included in every expression's type.
+# It is only used when an expression will never return to the current scope.
+#
+# `NoReturn` can be explicitly set as return type of a method or function definition but will usually be inferred by the compiler.
+struct CRYSTAL_PSEUDO__NoReturn
+end
+
+# Similar in usage to `Nil`. `Void` is preferred for C lib bindings.
+struct CRYSTAL_PSEUDO__Void
+end
diff --git a/src/ecr/lexer.cr b/src/ecr/lexer.cr
index e32de726040f..81fedac17087 100644
--- a/src/ecr/lexer.cr
+++ b/src/ecr/lexer.cr
@@ -25,6 +25,15 @@ class ECR::Lexer
end
end
+ class SyntaxException < Exception
+ getter line_number : Int32
+ getter column_number : Int32
+
+ def initialize(message, @line_number, @column_number)
+ super(message)
+ end
+ end
+
def initialize(string)
@reader = Char::Reader.new(string)
@token = Token.new
@@ -198,4 +207,8 @@ class ECR::Lexer
private def string_range(start_pos, end_pos)
@reader.string.byte_slice(start_pos, end_pos - start_pos)
end
+
+ private def raise(message : String)
+ raise SyntaxException.new(message, @line_number, @column_number)
+ end
end
diff --git a/src/ecr/macros.cr b/src/ecr/macros.cr
index 92c02cc4284a..5e051232271b 100644
--- a/src/ecr/macros.cr
+++ b/src/ecr/macros.cr
@@ -34,7 +34,7 @@ module ECR
# ```
macro def_to_s(filename)
def to_s(__io__ : IO) : Nil
- ECR.embed {{filename}}, "__io__"
+ ::ECR.embed {{filename}}, "__io__"
end
end
diff --git a/src/empty.cr b/src/empty.cr
index 204e30da48c0..cb79610a5be3 100644
--- a/src/empty.cr
+++ b/src/empty.cr
@@ -1,6 +1,6 @@
require "primitives"
-{% if flag?(:win32) %}
+{% if flag?(:msvc) %}
@[Link({{ flag?(:static) ? "libcmt" : "msvcrt" }})] # For `mainCRTStartup`
{% end %}
lib LibCrystalMain
diff --git a/src/enum.cr b/src/enum.cr
index 8b6ca9eebbae..058c17f6ee1c 100644
--- a/src/enum.cr
+++ b/src/enum.cr
@@ -100,7 +100,7 @@
#
# Color::Red.value # : UInt8
# ```
-struct Enum
+abstract struct Enum
include Comparable(self)
# Returns *value*.
@@ -225,7 +225,8 @@ struct Enum
end
end
- private def member_name
+ # :nodoc:
+ def member_name : String?
# Can't use `case` here because case with duplicate values do
# not compile, but enums can have duplicates (such as `enum Foo; FOO = 1; BAR = 1; end`).
{% for member in @type.constants %}
diff --git a/src/enumerable.cr b/src/enumerable.cr
index ff49de1ff308..9fcba66ddf3a 100644
--- a/src/enumerable.cr
+++ b/src/enumerable.cr
@@ -558,6 +558,25 @@ module Enumerable(T)
raise Enumerable::NotFoundError.new
end
+ # Yields each value until the first truthy block result and returns that result.
+ #
+ # Accepts an optional parameter `if_none`, to set what gets returned if
+ # no element is found (defaults to `nil`).
+ #
+ # ```
+ # [1, 2, 3, 4].find_value { |i| i > 2 } # => true
+ # [1, 2, 3, 4].find_value { |i| i > 8 } # => nil
+ # [1, 2, 3, 4].find_value(-1) { |i| i > 8 } # => -1
+ # ```
+ def find_value(if_none = nil, & : T ->)
+ each do |i|
+ if result = yield i
+ return result
+ end
+ end
+ if_none
+ end
+
# Returns the first element in the collection,
# If the collection is empty, calls the block and returns its value.
#
@@ -1014,9 +1033,9 @@ module Enumerable(T)
# [1, 2, 3].map { |i| i * 10 } # => [10, 20, 30]
# ```
def map(& : T -> U) : Array(U) forall U
- ary = [] of U
- each { |e| ary << yield e }
- ary
+ map_with_index do |e|
+ yield e
+ end
end
# Like `map`, but the block gets passed both the element and its index.
@@ -1994,9 +2013,7 @@ module Enumerable(T)
# (1..5).to_a # => [1, 2, 3, 4, 5]
# ```
def to_a : Array(T)
- ary = [] of T
- each { |e| ary << e }
- ary
+ to_a(&.as(T))
end
# Returns an `Array` with the results of running *block* against each element of the collection.
diff --git a/src/env.cr b/src/env.cr
index b28e4014ea22..13779f3051aa 100644
--- a/src/env.cr
+++ b/src/env.cr
@@ -60,7 +60,7 @@ module ENV
# Retrieves a value corresponding to the given *key*. Return the second argument's value
# if the *key* does not exist.
- def self.fetch(key, default) : String?
+ def self.fetch(key, default : T) : String | T forall T
fetch(key) { default }
end
diff --git a/src/errno.cr b/src/errno.cr
index 9d608c80bc1b..c519a8ab9fdb 100644
--- a/src/errno.cr
+++ b/src/errno.cr
@@ -38,7 +38,10 @@ enum Errno
{% end %}
{% end %}
- # Convert an Errno to an error message
+ # Returns the system error message associated with this errno.
+ #
+ # NOTE: The result may depend on the current system locale. Specs and
+ # comparisons should use `#value` instead of this method.
def message : String
unsafe_message { |slice| String.new(slice) }
end
diff --git a/src/exception/call_stack.cr b/src/exception/call_stack.cr
index c80f73a6ce48..506317d2580e 100644
--- a/src/exception/call_stack.cr
+++ b/src/exception/call_stack.cr
@@ -1,6 +1,6 @@
{% if flag?(:interpreted) %}
require "./call_stack/interpreter"
-{% elsif flag?(:win32) %}
+{% elsif flag?(:win32) && !flag?(:gnu) %}
require "./call_stack/stackwalk"
{% elsif flag?(:wasm32) %}
require "./call_stack/null"
@@ -31,10 +31,11 @@ struct Exception::CallStack
@callstack : Array(Void*)
@backtrace : Array(String)?
- def initialize
- @callstack = CallStack.unwind
+ def initialize(@callstack : Array(Void*) = CallStack.unwind)
end
+ class_getter empty = new([] of Void*)
+
def printable_backtrace : Array(String)
@backtrace ||= decode_backtrace
end
diff --git a/src/exception/call_stack/dwarf.cr b/src/exception/call_stack/dwarf.cr
index 96d99f03205a..253a72a38ebc 100644
--- a/src/exception/call_stack/dwarf.cr
+++ b/src/exception/call_stack/dwarf.cr
@@ -10,6 +10,10 @@ struct Exception::CallStack
@@dwarf_line_numbers : Crystal::DWARF::LineNumbers?
@@dwarf_function_names : Array(Tuple(LibC::SizeT, LibC::SizeT, String))?
+ {% if flag?(:win32) %}
+ @@coff_symbols : Hash(Int32, Array(Crystal::PE::COFFSymbol))?
+ {% end %}
+
# :nodoc:
def self.load_debug_info : Nil
return if ENV["CRYSTAL_LOAD_DEBUG_INFO"]? == "0"
diff --git a/src/exception/call_stack/elf.cr b/src/exception/call_stack/elf.cr
index efa54f41329c..51d565528577 100644
--- a/src/exception/call_stack/elf.cr
+++ b/src/exception/call_stack/elf.cr
@@ -1,65 +1,83 @@
-require "crystal/elf"
-{% unless flag?(:wasm32) %}
- require "c/link"
+{% if flag?(:win32) %}
+ require "crystal/pe"
+{% else %}
+ require "crystal/elf"
+ {% unless flag?(:wasm32) %}
+ require "c/link"
+ {% end %}
{% end %}
struct Exception::CallStack
- private struct DlPhdrData
- getter program : String
- property base_address : LibC::Elf_Addr = 0
+ {% unless flag?(:win32) %}
+ private struct DlPhdrData
+ getter program : String
+ property base_address : LibC::Elf_Addr = 0
- def initialize(@program : String)
+ def initialize(@program : String)
+ end
end
- end
+ {% end %}
protected def self.load_debug_info_impl : Nil
program = Process.executable_path
return unless program && File::Info.readable? program
- data = DlPhdrData.new(program)
-
- phdr_callback = LibC::DlPhdrCallback.new do |info, size, data|
- # `dl_iterate_phdr` does not always visit the current program first; on
- # Android the first object is `/system/bin/linker64`, the second is the
- # full program path (not the empty string), so we check both here
- name_c_str = info.value.name
- if name_c_str && (name_c_str.value == 0 || LibC.strcmp(name_c_str, data.as(DlPhdrData*).value.program) == 0)
- # The first entry is the header for the current program.
- # Note that we avoid allocating here and just store the base address
- # to be passed to self.read_dwarf_sections when dl_iterate_phdr returns.
- # Calling self.read_dwarf_sections from this callback may lead to reallocations
- # and deadlocks due to the internal lock held by dl_iterate_phdr (#10084).
- data.as(DlPhdrData*).value.base_address = info.value.addr
- 1
- else
- 0
+
+ {% if flag?(:win32) %}
+ if LibC.GetModuleHandleExW(LibC::GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, nil, out hmodule) != 0
+ self.read_dwarf_sections(program, hmodule.address)
end
- end
+ {% else %}
+ data = DlPhdrData.new(program)
- LibC.dl_iterate_phdr(phdr_callback, pointerof(data))
- self.read_dwarf_sections(data.program, data.base_address)
+ phdr_callback = LibC::DlPhdrCallback.new do |info, size, data|
+ # `dl_iterate_phdr` does not always visit the current program first; on
+ # Android the first object is `/system/bin/linker64`, the second is the
+ # full program path (not the empty string), so we check both here
+ name_c_str = info.value.name
+ if name_c_str && (name_c_str.value == 0 || LibC.strcmp(name_c_str, data.as(DlPhdrData*).value.program) == 0)
+ # The first entry is the header for the current program.
+ # Note that we avoid allocating here and just store the base address
+ # to be passed to self.read_dwarf_sections when dl_iterate_phdr returns.
+ # Calling self.read_dwarf_sections from this callback may lead to reallocations
+ # and deadlocks due to the internal lock held by dl_iterate_phdr (#10084).
+ data.as(DlPhdrData*).value.base_address = info.value.addr
+ 1
+ else
+ 0
+ end
+ end
+
+ LibC.dl_iterate_phdr(phdr_callback, pointerof(data))
+ self.read_dwarf_sections(data.program, data.base_address)
+ {% end %}
end
protected def self.read_dwarf_sections(program, base_address = 0)
- Crystal::ELF.open(program) do |elf|
- line_strings = elf.read_section?(".debug_line_str") do |sh, io|
+ {{ flag?(:win32) ? Crystal::PE : Crystal::ELF }}.open(program) do |image|
+ {% if flag?(:win32) %}
+ base_address -= image.original_image_base
+ @@coff_symbols = image.coff_symbols
+ {% end %}
+
+ line_strings = image.read_section?(".debug_line_str") do |sh, io|
Crystal::DWARF::Strings.new(io, sh.offset, sh.size)
end
- strings = elf.read_section?(".debug_str") do |sh, io|
+ strings = image.read_section?(".debug_str") do |sh, io|
Crystal::DWARF::Strings.new(io, sh.offset, sh.size)
end
- elf.read_section?(".debug_line") do |sh, io|
+ image.read_section?(".debug_line") do |sh, io|
@@dwarf_line_numbers = Crystal::DWARF::LineNumbers.new(io, sh.size, base_address, strings, line_strings)
end
- elf.read_section?(".debug_info") do |sh, io|
+ image.read_section?(".debug_info") do |sh, io|
names = [] of {LibC::SizeT, LibC::SizeT, String}
while (offset = io.pos - sh.offset) < sh.size
info = Crystal::DWARF::Info.new(io, offset)
- elf.read_section?(".debug_abbrev") do |sh, io|
+ image.read_section?(".debug_abbrev") do |sh, io|
info.read_abbreviations(io)
end
diff --git a/src/exception/call_stack/libunwind.cr b/src/exception/call_stack/libunwind.cr
index 73a851a00339..c0f75867aeba 100644
--- a/src/exception/call_stack/libunwind.cr
+++ b/src/exception/call_stack/libunwind.cr
@@ -1,9 +1,11 @@
-require "c/dlfcn"
+{% unless flag?(:win32) %}
+ require "c/dlfcn"
+{% end %}
require "c/stdio"
require "c/string"
require "../lib_unwind"
-{% if flag?(:darwin) || flag?(:bsd) || flag?(:linux) || flag?(:solaris) %}
+{% if flag?(:darwin) || flag?(:bsd) || flag?(:linux) || flag?(:solaris) || flag?(:win32) %}
require "./dwarf"
{% else %}
require "./null"
@@ -33,7 +35,11 @@ struct Exception::CallStack
{% end %}
def self.setup_crash_handler
- Crystal::System::Signal.setup_segfault_handler
+ {% if flag?(:win32) %}
+ Crystal::System::Signal.setup_seh_handler
+ {% else %}
+ Crystal::System::Signal.setup_segfault_handler
+ {% end %}
end
{% if flag?(:interpreted) %} @[Primitive(:interpreter_call_stack_unwind)] {% end %}
@@ -122,32 +128,18 @@ struct Exception::CallStack
end
{% end %}
- if frame = unsafe_decode_frame(repeated_frame.ip)
- offset, sname, fname = frame
+ unsafe_decode_frame(repeated_frame.ip) do |offset, sname, fname|
Crystal::System.print_error "%s +%lld in %s", sname, offset.to_i64, fname
- else
- Crystal::System.print_error "???"
+ return
end
- end
- protected def self.decode_frame(ip, original_ip = ip)
- if LibC.dladdr(ip, out info) != 0
- offset = original_ip - info.dli_saddr
+ Crystal::System.print_error "???"
+ end
- if offset == 0
- return decode_frame(ip - 1, original_ip)
- end
- return if info.dli_sname.null? && info.dli_fname.null?
- if info.dli_sname.null?
- symbol = "??"
- else
- symbol = String.new(info.dli_sname)
- end
- if info.dli_fname.null?
- file = "??"
- else
- file = String.new(info.dli_fname)
- end
+ protected def self.decode_frame(ip)
+ decode_frame(ip) do |offset, symbol, file|
+ symbol = symbol ? String.new(symbol) : "??"
+ file = file ? String.new(file) : "??"
{offset, symbol, file}
end
end
@@ -155,19 +147,128 @@ struct Exception::CallStack
# variant of `.decode_frame` that returns the C strings directly instead of
# wrapping them in `String.new`, since the SIGSEGV handler cannot allocate
# memory via the GC
- protected def self.unsafe_decode_frame(ip)
+ protected def self.unsafe_decode_frame(ip, &)
+ decode_frame(ip) do |offset, symbol, file|
+ symbol ||= "??".to_unsafe
+ file ||= "??".to_unsafe
+ yield offset, symbol, file
+ end
+ end
+
+ private def self.decode_frame(ip, &)
original_ip = ip
- while LibC.dladdr(ip, out info) != 0
- offset = original_ip - info.dli_saddr
- if offset == 0
- ip -= 1
- next
+ while true
+ retry = dladdr(ip) do |file, symbol, address|
+ offset = original_ip - address
+ if offset == 0
+ ip -= 1
+ true
+ elsif symbol.null? && file.null?
+ false
+ else
+ return yield offset, symbol, file
+ end
end
-
- return if info.dli_sname.null? && info.dli_fname.null?
- symbol = info.dli_sname || "??".to_unsafe
- file = info.dli_fname || "??".to_unsafe
- return {offset, symbol, file}
+ break unless retry
end
end
+
+ {% if flag?(:win32) %}
+ def self.dladdr(ip, &)
+ if LibC.GetModuleHandleExW(LibC::GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT | LibC::GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, ip.as(LibC::LPWSTR), out hmodule) != 0
+ symbol, address = internal_symbol(hmodule, ip) || external_symbol(hmodule, ip) || return
+
+ utf16_file = uninitialized LibC::WCHAR[LibC::MAX_PATH]
+ len = LibC.GetModuleFileNameW(hmodule, utf16_file, utf16_file.size)
+ if 0 < len < utf16_file.size
+ utf8_file = uninitialized UInt8[sizeof(UInt8[LibC::MAX_PATH][3])]
+ file = utf8_file.to_unsafe
+ appender = file.appender
+ String.each_utf16_char(utf16_file.to_slice[0, len + 1]) do |ch|
+ ch.each_byte { |b| appender << b }
+ end
+ else
+ file = Pointer(UInt8).null
+ end
+
+ yield file, symbol, address
+ end
+ end
+
+ private def self.internal_symbol(hmodule, ip)
+ if coff_symbols = @@coff_symbols
+ if LibC.GetModuleHandleExW(LibC::GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, nil, out this_hmodule) != 0 && this_hmodule == hmodule
+ section_base, section_index = lookup_section(hmodule, ip) || return
+ offset = ip - section_base
+ section_coff_symbols = coff_symbols[section_index]? || return
+ next_sym = section_coff_symbols.bsearch_index { |sym| offset < sym.offset } || return
+ sym = section_coff_symbols[next_sym - 1]? || return
+
+ {sym.name.to_unsafe, section_base + sym.offset}
+ end
+ end
+ end
+
+ private def self.external_symbol(hmodule, ip)
+ if dir = data_directory(hmodule, LibC::IMAGE_DIRECTORY_ENTRY_EXPORT)
+ exports = dir.to_unsafe.as(LibC::IMAGE_EXPORT_DIRECTORY*).value
+
+ found_address = Pointer(Void).null
+ found_index = -1
+
+ func_address_offsets = (hmodule + exports.addressOfFunctions).as(LibC::DWORD*).to_slice(exports.numberOfFunctions)
+ func_address_offsets.each_with_index do |offset, i|
+ address = hmodule + offset
+ if found_address < address <= ip
+ found_address, found_index = address, i
+ end
+ end
+
+ return unless found_address
+
+ func_name_ordinals = (hmodule + exports.addressOfNameOrdinals).as(LibC::WORD*).to_slice(exports.numberOfNames)
+ if ordinal_index = func_name_ordinals.index(&.== found_index)
+ symbol = (hmodule + (hmodule + exports.addressOfNames).as(LibC::DWORD*)[ordinal_index]).as(UInt8*)
+ {symbol, found_address}
+ end
+ end
+ end
+
+ private def self.lookup_section(hmodule, ip)
+ dos_header = hmodule.as(LibC::IMAGE_DOS_HEADER*)
+ return unless dos_header.value.e_magic == 0x5A4D # MZ
+
+ nt_header = (hmodule + dos_header.value.e_lfanew).as(LibC::IMAGE_NT_HEADERS*)
+ return unless nt_header.value.signature == 0x00004550 # PE\0\0
+
+ section_headers = (nt_header + 1).as(LibC::IMAGE_SECTION_HEADER*).to_slice(nt_header.value.fileHeader.numberOfSections)
+ section_headers.each_with_index do |header, i|
+ base = hmodule + header.virtualAddress
+ if base <= ip < base + header.virtualSize
+ return base, i
+ end
+ end
+ end
+
+ private def self.data_directory(hmodule, index)
+ dos_header = hmodule.as(LibC::IMAGE_DOS_HEADER*)
+ return unless dos_header.value.e_magic == 0x5A4D # MZ
+
+ nt_header = (hmodule + dos_header.value.e_lfanew).as(LibC::IMAGE_NT_HEADERS*)
+ return unless nt_header.value.signature == 0x00004550 # PE\0\0
+ return unless nt_header.value.optionalHeader.magic == {{ flag?(:bits64) ? 0x20b : 0x10b }}
+ return unless index.in?(0...{16, nt_header.value.optionalHeader.numberOfRvaAndSizes}.min)
+
+ directory = nt_header.value.optionalHeader.dataDirectory.to_unsafe[index]
+ if directory.virtualAddress != 0
+ Bytes.new(hmodule.as(UInt8*) + directory.virtualAddress, directory.size, read_only: true)
+ end
+ end
+ {% else %}
+ private def self.dladdr(ip, &)
+ if LibC.dladdr(ip, out info) != 0
+ yield info.dli_fname, info.dli_sname, info.dli_saddr
+ end
+ end
+ {% end %}
end
diff --git a/src/exception/call_stack/stackwalk.cr b/src/exception/call_stack/stackwalk.cr
index 2b9a03b472c7..d7e3da8e35f1 100644
--- a/src/exception/call_stack/stackwalk.cr
+++ b/src/exception/call_stack/stackwalk.cr
@@ -1,5 +1,4 @@
require "c/dbghelp"
-require "c/malloc"
# :nodoc:
struct Exception::CallStack
@@ -33,38 +32,7 @@ struct Exception::CallStack
end
def self.setup_crash_handler
- LibC.AddVectoredExceptionHandler(1, ->(exception_info) do
- case exception_info.value.exceptionRecord.value.exceptionCode
- when LibC::EXCEPTION_ACCESS_VIOLATION
- addr = exception_info.value.exceptionRecord.value.exceptionInformation[1]
- Crystal::System.print_error "Invalid memory access (C0000005) at address %p\n", Pointer(Void).new(addr)
- print_backtrace(exception_info)
- LibC._exit(1)
- when LibC::EXCEPTION_STACK_OVERFLOW
- LibC._resetstkoflw
- Crystal::System.print_error "Stack overflow (e.g., infinite or very deep recursion)\n"
- print_backtrace(exception_info)
- LibC._exit(1)
- else
- LibC::EXCEPTION_CONTINUE_SEARCH
- end
- end)
-
- # ensure that even in the case of stack overflow there is enough reserved
- # stack space for recovery (for other threads this is done in
- # `Crystal::System::Thread.thread_proc`)
- stack_size = Crystal::System::Fiber::RESERVED_STACK_SIZE
- LibC.SetThreadStackGuarantee(pointerof(stack_size))
-
- # this catches invalid argument checks inside the C runtime library
- LibC._set_invalid_parameter_handler(->(expression, _function, _file, _line, _pReserved) do
- message = expression ? String.from_utf16(expression)[0] : "(no message)"
- Crystal::System.print_error "CRT invalid parameter handler invoked: %s\n", message
- caller.each do |frame|
- Crystal::System.print_error " from %s\n", frame
- end
- LibC._exit(1)
- end)
+ Crystal::System::Signal.setup_seh_handler
end
{% if flag?(:interpreted) %} @[Primitive(:interpreter_call_stack_unwind)] {% end %}
@@ -93,6 +61,8 @@ struct Exception::CallStack
{% elsif flag?(:i386) %}
# TODO: use WOW64_CONTEXT in place of CONTEXT
{% raise "x86 not supported" %}
+ {% elsif flag?(:aarch64) %}
+ LibC::IMAGE_FILE_MACHINE_ARM64
{% else %}
{% raise "Architecture not supported" %}
{% end %}
@@ -102,9 +72,15 @@ struct Exception::CallStack
stack_frame.addrFrame.mode = LibC::ADDRESS_MODE::AddrModeFlat
stack_frame.addrStack.mode = LibC::ADDRESS_MODE::AddrModeFlat
- stack_frame.addrPC.offset = context.value.rip
- stack_frame.addrFrame.offset = context.value.rbp
- stack_frame.addrStack.offset = context.value.rsp
+ {% if flag?(:x86_64) %}
+ stack_frame.addrPC.offset = context.value.rip
+ stack_frame.addrFrame.offset = context.value.rbp
+ stack_frame.addrStack.offset = context.value.rsp
+ {% elsif flag?(:aarch64) %}
+ stack_frame.addrPC.offset = context.value.pc
+ stack_frame.addrFrame.offset = context.value.x[29]
+ stack_frame.addrStack.offset = context.value.sp
+ {% end %}
last_frame = nil
cur_proc = LibC.GetCurrentProcess
diff --git a/src/exception/lib_unwind.cr b/src/exception/lib_unwind.cr
index 7c9c6fd75ec5..83350c12fe3a 100644
--- a/src/exception/lib_unwind.cr
+++ b/src/exception/lib_unwind.cr
@@ -113,8 +113,12 @@ lib LibUnwind
struct Exception
exception_class : LibC::SizeT
exception_cleanup : LibC::SizeT
- private1 : UInt64
- private2 : UInt64
+ {% if flag?(:win32) && flag?(:gnu) %}
+ private_ : UInt64[6]
+ {% else %}
+ private1 : UInt64
+ private2 : UInt64
+ {% end %}
exception_object : Void*
exception_type_id : Int32
end
diff --git a/src/fiber.cr b/src/fiber.cr
index 0d471e5a96e4..60162e5872a5 100644
--- a/src/fiber.cr
+++ b/src/fiber.cr
@@ -1,4 +1,5 @@
require "crystal/system/thread_linked_list"
+require "crystal/print_buffered"
require "./fiber/context"
# :nodoc:
@@ -43,8 +44,16 @@ end
# notifications that IO is ready or a timeout reached. When a fiber can be woken,
# the event loop enqueues it in the scheduler
class Fiber
+ @@fibers = uninitialized Thread::LinkedList(Fiber)
+
+ protected def self.fibers : Thread::LinkedList(Fiber)
+ @@fibers
+ end
+
# :nodoc:
- protected class_getter(fibers) { Thread::LinkedList(Fiber).new }
+ def self.init : Nil
+ @@fibers = Thread::LinkedList(Fiber).new
+ end
@context : Context
@stack : Void*
@@ -78,6 +87,11 @@ class Fiber
@@fibers.try(&.unsafe_each { |fiber| yield fiber })
end
+ # :nodoc:
+ def self.each(&)
+ fibers.each { |fiber| yield fiber }
+ end
+
# Creates a new `Fiber` instance.
#
# When the fiber is executed, it runs *proc* in its context.
@@ -94,21 +108,9 @@ class Fiber
fiber_main = ->(f : Fiber) { f.run }
- # FIXME: This line shouldn't be necessary (#7975)
- stack_ptr = nil
- {% if flag?(:win32) %}
- # align stack bottom to 16 bytes
- @stack_bottom = Pointer(Void).new(@stack_bottom.address & ~0x0f_u64)
-
- # It's the caller's responsibility to allocate 32 bytes of "shadow space" on the stack right
- # before calling the function (regardless of the actual number of parameters used)
-
- stack_ptr = @stack_bottom - sizeof(Void*) * 6
- {% else %}
- # point to first addressable pointer on the stack (@stack_bottom points past
- # the stack because the stack grows down):
- stack_ptr = @stack_bottom - sizeof(Void*)
- {% end %}
+ # point to first addressable pointer on the stack (@stack_bottom points past
+ # the stack because the stack grows down):
+ stack_ptr = @stack_bottom - sizeof(Void*)
# align the stack pointer to 16 bytes:
stack_ptr = Pointer(Void*).new(stack_ptr.address & ~0x0f_u64)
@@ -142,21 +144,11 @@ class Fiber
GC.unlock_read
@proc.call
rescue ex
- io = {% if flag?(:preview_mt) %}
- IO::Memory.new(4096) # PIPE_BUF
- {% else %}
- STDERR
- {% end %}
if name = @name
- io << "Unhandled exception in spawn(name: " << name << "): "
+ Crystal.print_buffered("Unhandled exception in spawn(name: %s)", name, exception: ex, to: STDERR)
else
- io << "Unhandled exception in spawn: "
+ Crystal.print_buffered("Unhandled exception in spawn", exception: ex, to: STDERR)
end
- ex.inspect_with_backtrace(io)
- {% if flag?(:preview_mt) %}
- STDERR.write(io.to_slice)
- {% end %}
- STDERR.flush
ensure
# Remove the current fiber from the linked list
Fiber.inactive(self)
@@ -166,6 +158,10 @@ class Fiber
@timeout_event.try &.free
@timeout_select_action = nil
+ # Additional cleanup (avoid stale references)
+ @exec_recursive_hash = nil
+ @exec_recursive_clone_hash = nil
+
@alive = false
{% unless flag?(:interpreted) %}
Crystal::Scheduler.stack_pool.release(@stack)
@@ -234,23 +230,27 @@ class Fiber
end
# :nodoc:
- def timeout(timeout : Time::Span?, select_action : Channel::TimeoutAction? = nil) : Nil
+ def timeout(timeout : Time::Span, select_action : Channel::TimeoutAction) : Nil
@timeout_select_action = select_action
timeout_event.add(timeout)
end
# :nodoc:
def cancel_timeout : Nil
+ return unless @timeout_select_action
@timeout_select_action = nil
@timeout_event.try &.delete
end
+ # :nodoc:
+ #
# The current fiber will resume after a period of time.
# The timeout can be cancelled with `cancel_timeout`
- def self.timeout(timeout : Time::Span?, select_action : Channel::TimeoutAction? = nil) : Nil
+ def self.timeout(timeout : Time::Span, select_action : Channel::TimeoutAction) : Nil
Fiber.current.timeout(timeout, select_action)
end
+ # :nodoc:
def self.cancel_timeout : Nil
Fiber.current.cancel_timeout
end
@@ -331,4 +331,18 @@ class Fiber
@current_thread.lazy_get
end
{% end %}
+
+ # :nodoc:
+ #
+ # See `Reference#exec_recursive` for details.
+ def exec_recursive_hash
+ @exec_recursive_hash ||= Hash({UInt64, Symbol}, Nil).new
+ end
+
+ # :nodoc:
+ #
+ # See `Reference#exec_recursive_clone` for details.
+ def exec_recursive_clone_hash
+ @exec_recursive_clone_hash ||= Hash(UInt64, UInt64).new
+ end
end
diff --git a/src/fiber/context/aarch64.cr b/src/fiber/context/aarch64-generic.cr
similarity index 98%
rename from src/fiber/context/aarch64.cr
rename to src/fiber/context/aarch64-generic.cr
index 4bd811200fc1..2839ee030ef5 100644
--- a/src/fiber/context/aarch64.cr
+++ b/src/fiber/context/aarch64-generic.cr
@@ -1,4 +1,4 @@
-{% skip_file unless flag?(:aarch64) %}
+{% skip_file unless flag?(:aarch64) && !flag?(:win32) %}
class Fiber
# :nodoc:
diff --git a/src/fiber/context/aarch64-microsoft.cr b/src/fiber/context/aarch64-microsoft.cr
new file mode 100644
index 000000000000..b2fa76580418
--- /dev/null
+++ b/src/fiber/context/aarch64-microsoft.cr
@@ -0,0 +1,149 @@
+{% skip_file unless flag?(:aarch64) && flag?(:win32) %}
+
+class Fiber
+ # :nodoc:
+ def makecontext(stack_ptr, fiber_main) : Nil
+ # ARM64 Windows also follows the AAPCS64 for the most part, except extra
+ # bookkeeping information needs to be kept in the Thread Information Block,
+ # referenceable from the x18 register
+
+ # 12 general-purpose registers + 8 FPU registers + 1 parameter + 3 qwords for NT_TIB
+ @context.stack_top = (stack_ptr - 24).as(Void*)
+ @context.resumable = 1
+
+ # actual stack top, not including guard pages and reserved pages
+ LibC.GetNativeSystemInfo(out system_info)
+ stack_top = @stack_bottom - system_info.dwPageSize
+
+ stack_ptr[-4] = self.as(Void*) # x0 (r0): puts `self` as first argument for `fiber_main`
+ stack_ptr[-16] = fiber_main.pointer # x30 (lr): initial `resume` will `ret` to this address
+
+ # The following three values are stored in the Thread Information Block (NT_TIB)
+ # and are used by Windows to track the current stack limits
+ stack_ptr[-3] = @stack # [x18, #0x1478]: Win32 DeallocationStack
+ stack_ptr[-2] = stack_top # [x18, #16]: Stack Limit
+ stack_ptr[-1] = @stack_bottom # [x18, #8]: Stack Base
+ end
+
+ # :nodoc:
+ @[NoInline]
+ @[Naked]
+ def self.swapcontext(current_context, new_context) : Nil
+ # x0 , x1
+
+ # see also `./aarch64-generic.cr`
+ {% if compare_versions(Crystal::LLVM_VERSION, "9.0.0") >= 0 %}
+ asm("
+ stp d15, d14, [sp, #-24*8]!
+ stp d13, d12, [sp, #2*8]
+ stp d11, d10, [sp, #4*8]
+ stp d9, d8, [sp, #6*8]
+ stp x30, x29, [sp, #8*8] // lr, fp
+ stp x28, x27, [sp, #10*8]
+ stp x26, x25, [sp, #12*8]
+ stp x24, x23, [sp, #14*8]
+ stp x22, x21, [sp, #16*8]
+ stp x20, x19, [sp, #18*8]
+ str x0, [sp, #20*8] // push 1st argument
+
+ ldr x19, [x18, #0x1478] // Thread Information Block: Win32 DeallocationStack
+ str x19, [sp, #21*8]
+ ldr x19, [x18, #16] // Thread Information Block: Stack Limit
+ str x19, [sp, #22*8]
+ ldr x19, [x18, #8] // Thread Information Block: Stack Base
+ str x19, [sp, #23*8]
+
+ mov x19, sp // current_context.stack_top = sp
+ str x19, [x0, #0]
+ mov x19, #1 // current_context.resumable = 1
+ str x19, [x0, #8]
+
+ mov x19, #0 // new_context.resumable = 0
+ str x19, [x1, #8]
+ ldr x19, [x1, #0] // sp = new_context.stack_top (x19)
+ mov sp, x19
+
+ ldr x19, [sp, #23*8]
+ str x19, [x18, #8]
+ ldr x19, [sp, #22*8]
+ str x19, [x18, #16]
+ ldr x19, [sp, #21*8]
+ str x19, [x18, #0x1478]
+
+ ldr x0, [sp, #20*8] // pop 1st argument (+ alignment)
+ ldp x20, x19, [sp, #18*8]
+ ldp x22, x21, [sp, #16*8]
+ ldp x24, x23, [sp, #14*8]
+ ldp x26, x25, [sp, #12*8]
+ ldp x28, x27, [sp, #10*8]
+ ldp x30, x29, [sp, #8*8] // lr, fp
+ ldp d9, d8, [sp, #6*8]
+ ldp d11, d10, [sp, #4*8]
+ ldp d13, d12, [sp, #2*8]
+ ldp d15, d14, [sp], #24*8
+
+ // avoid a stack corruption that will confuse the unwinder
+ mov x16, x30 // save lr
+ mov x30, #0 // reset lr
+ br x16 // jump to new pc value
+ ")
+ {% else %}
+ # On LLVM < 9.0 using the previous code emits some additional
+ # instructions that breaks the context switching.
+ asm("
+ stp d15, d14, [sp, #-24*8]!
+ stp d13, d12, [sp, #2*8]
+ stp d11, d10, [sp, #4*8]
+ stp d9, d8, [sp, #6*8]
+ stp x30, x29, [sp, #8*8] // lr, fp
+ stp x28, x27, [sp, #10*8]
+ stp x26, x25, [sp, #12*8]
+ stp x24, x23, [sp, #14*8]
+ stp x22, x21, [sp, #16*8]
+ stp x20, x19, [sp, #18*8]
+ str x0, [sp, #20*8] // push 1st argument
+
+ ldr x19, [x18, #0x1478] // Thread Information Block: Win32 DeallocationStack
+ str x19, [sp, #21*8]
+ ldr x19, [x18, #16] // Thread Information Block: Stack Limit
+ str x19, [sp, #22*8]
+ ldr x19, [x18, #8] // Thread Information Block: Stack Base
+ str x19, [sp, #23*8]
+
+ mov x19, sp // current_context.stack_top = sp
+ str x19, [$0, #0]
+ mov x19, #1 // current_context.resumable = 1
+ str x19, [$0, #8]
+
+ mov x19, #0 // new_context.resumable = 0
+ str x19, [$1, #8]
+ ldr x19, [$1, #0] // sp = new_context.stack_top (x19)
+ mov sp, x19
+
+ ldr x19, [sp, #23*8]
+ str x19, [x18, #8]
+ ldr x19, [sp, #22*8]
+ str x19, [x18, #16]
+ ldr x19, [sp, #21*8]
+ str x19, [x18, #0x1478]
+
+ ldr x0, [sp, #20*8] // pop 1st argument (+ alignment)
+ ldp x20, x19, [sp, #18*8]
+ ldp x22, x21, [sp, #16*8]
+ ldp x24, x23, [sp, #14*8]
+ ldp x26, x25, [sp, #12*8]
+ ldp x28, x27, [sp, #10*8]
+ ldp x30, x29, [sp, #8*8] // lr, fp
+ ldp d9, d8, [sp, #6*8]
+ ldp d11, d10, [sp, #4*8]
+ ldp d13, d12, [sp, #2*8]
+ ldp d15, d14, [sp], #24*8
+
+ // avoid a stack corruption that will confuse the unwinder
+ mov x16, x30 // save lr
+ mov x30, #0 // reset lr
+ br x16 // jump to new pc value
+ " :: "r"(current_context), "r"(new_context))
+ {% end %}
+ end
+end
diff --git a/src/fiber/context/x86_64-microsoft.cr b/src/fiber/context/x86_64-microsoft.cr
index 55d893cb8184..a1b9fa281074 100644
--- a/src/fiber/context/x86_64-microsoft.cr
+++ b/src/fiber/context/x86_64-microsoft.cr
@@ -4,19 +4,25 @@ class Fiber
# :nodoc:
def makecontext(stack_ptr, fiber_main) : Nil
# A great explanation on stack contexts for win32:
- # https://cfsamson.gitbook.io/green-threads-explained-in-200-lines-of-rust/supporting-windows
+ # https://web.archive.org/web/20220527113808/https://cfsamson.gitbook.io/green-threads-explained-in-200-lines-of-rust/supporting-windows
- # 8 registers + 2 qwords for NT_TIB + 1 parameter + 10 128bit XMM registers
- @context.stack_top = (stack_ptr - (11 + 10*2)).as(Void*)
+ # 4 shadow space + (8 registers + 3 qwords for NT_TIB + 1 parameter) + 10 128bit XMM registers
+ @context.stack_top = (stack_ptr - (4 + 12 + 10*2)).as(Void*)
@context.resumable = 1
+ # actual stack top, not including guard pages and reserved pages
+ LibC.GetNativeSystemInfo(out system_info)
+ stack_top = @stack_bottom - system_info.dwPageSize
+
+ stack_ptr -= 4 # shadow space (or home space) before return address
stack_ptr[0] = fiber_main.pointer # %rbx: Initial `resume` will `ret` to this address
stack_ptr[-1] = self.as(Void*) # %rcx: puts `self` as first argument for `fiber_main`
- # The following two values are stored in the Thread Information Block (NT_TIB)
+ # The following three values are stored in the Thread Information Block (NT_TIB)
# and are used by Windows to track the current stack limits
- stack_ptr[-2] = @stack # %gs:0x10: Stack Limit
- stack_ptr[-3] = @stack_bottom # %gs:0x08: Stack Base
+ stack_ptr[-2] = @stack # %gs:0x1478: Win32 DeallocationStack
+ stack_ptr[-3] = stack_top # %gs:0x10: Stack Limit
+ stack_ptr[-4] = @stack_bottom # %gs:0x08: Stack Base
end
# :nodoc:
@@ -27,6 +33,7 @@ class Fiber
# %rcx , %rdx
asm("
pushq %rcx
+ pushq %gs:0x1478 // Thread Information Block: Win32 DeallocationStack
pushq %gs:0x10 // Thread Information Block: Stack Limit
pushq %gs:0x08 // Thread Information Block: Stack Base
pushq %rdi // push 1st argument (because of initial resume)
@@ -73,6 +80,7 @@ class Fiber
popq %rdi // pop 1st argument (for initial resume)
popq %gs:0x08
popq %gs:0x10
+ popq %gs:0x1478
popq %rcx
")
{% else %}
@@ -80,6 +88,7 @@ class Fiber
# instructions that breaks the context switching.
asm("
pushq %rcx
+ pushq %gs:0x1478 // Thread Information Block: Win32 DeallocationStack
pushq %gs:0x10 // Thread Information Block: Stack Limit
pushq %gs:0x08 // Thread Information Block: Stack Base
pushq %rdi // push 1st argument (because of initial resume)
@@ -126,6 +135,7 @@ class Fiber
popq %rdi // pop 1st argument (for initial resume)
popq %gs:0x08
popq %gs:0x10
+ popq %gs:0x1478
popq %rcx
" :: "r"(current_context), "r"(new_context))
{% end %}
diff --git a/src/fiber/pointer_linked_list_node.cr b/src/fiber/pointer_linked_list_node.cr
new file mode 100644
index 000000000000..45994fe5c489
--- /dev/null
+++ b/src/fiber/pointer_linked_list_node.cr
@@ -0,0 +1,15 @@
+require "crystal/pointer_linked_list"
+
+class Fiber
+ # :nodoc:
+ struct PointerLinkedListNode
+ include Crystal::PointerLinkedList::Node
+
+ def initialize(@fiber : Fiber)
+ end
+
+ def enqueue : Nil
+ @fiber.enqueue
+ end
+ end
+end
diff --git a/src/fiber/stack_pool.cr b/src/fiber/stack_pool.cr
index c9ea3ceb68e0..8f809335f46c 100644
--- a/src/fiber/stack_pool.cr
+++ b/src/fiber/stack_pool.cr
@@ -42,7 +42,11 @@ class Fiber
# Removes a stack from the bottom of the pool, or allocates a new one.
def checkout : {Void*, Void*}
- stack = @deque.pop? || Crystal::System::Fiber.allocate_stack(STACK_SIZE, @protect)
+ if stack = @deque.pop?
+ Crystal::System::Fiber.reset_stack(stack, STACK_SIZE, @protect)
+ else
+ stack = Crystal::System::Fiber.allocate_stack(STACK_SIZE, @protect)
+ end
{stack, stack + STACK_SIZE}
end
diff --git a/src/file.cr b/src/file.cr
index ff6c68ef4d03..1d12a01f4209 100644
--- a/src/file.cr
+++ b/src/file.cr
@@ -165,15 +165,15 @@ class File < IO::FileDescriptor
# *blocking* must be set to `false` on POSIX targets when the file to open
# isn't a regular file but a character device (e.g. `/dev/tty`) or fifo. These
# files depend on another process or thread to also be reading or writing, and
- # system event queues will properly report readyness.
+ # system event queues will properly report readiness.
#
# *blocking* may also be set to `nil` in which case the blocking or
# non-blocking flag will be determined automatically, at the expense of an
# additional syscall.
def self.new(filename : Path | String, mode = "r", perm = DEFAULT_CREATE_PERMISSIONS, encoding = nil, invalid = nil, blocking = true)
filename = filename.to_s
- fd = Crystal::System::File.open(filename, mode, perm: perm)
- new(filename, fd, blocking: blocking, encoding: encoding, invalid: invalid)
+ fd = Crystal::System::File.open(filename, mode, perm: perm, blocking: blocking)
+ new(filename, fd, blocking: blocking, encoding: encoding, invalid: invalid).tap { |f| f.system_set_mode(mode) }
end
getter path : String
diff --git a/src/file/preader.cr b/src/file/preader.cr
index d366457314ce..9f7d09643305 100644
--- a/src/file/preader.cr
+++ b/src/file/preader.cr
@@ -20,7 +20,7 @@ class File::PReader < IO
count = slice.size
count = Math.min(count, @bytesize - @pos)
- bytes_read = Crystal::System::FileDescriptor.pread(@file.fd, slice[0, count], @offset + @pos)
+ bytes_read = Crystal::System::FileDescriptor.pread(@file, slice[0, count], @offset + @pos)
@pos += bytes_read
diff --git a/src/float/fast_float.cr b/src/float/fast_float.cr
new file mode 100644
index 000000000000..010476db4bca
--- /dev/null
+++ b/src/float/fast_float.cr
@@ -0,0 +1,75 @@
+struct Float
+ # :nodoc:
+ # Source port of the floating-point part of fast_float for C++:
+ # https://github.com/fastfloat/fast_float
+ #
+ # fast_float implements the C++17 `std::from_chars`, which accepts a subset of
+ # the C `strtod` / `strtof`'s string format:
+ #
+ # - a leading plus sign is disallowed, but both fast_float and this port
+ # accept it;
+ # - the exponent may be required or disallowed, depending on the format
+ # argument (this port always allows both);
+ # - hexfloats are not enabled by default, and fast_float doesn't implement it;
+ # (https://github.com/fastfloat/fast_float/issues/124)
+ # - hexfloats cannot start with `0x` or `0X`.
+ #
+ # The following is their license:
+ #
+ # Licensed under either of Apache License, Version 2.0 or MIT license or
+ # BOOST license.
+ #
+ # Unless you explicitly state otherwise, any contribution intentionally
+ # submitted for inclusion in this repository by you, as defined in the
+ # Apache-2.0 license, shall be triple licensed as above, without any
+ # additional terms or conditions.
+ #
+ # Main differences from the original fast_float:
+ #
+ # - Only `UC == UInt8` is implemented and tested, not the other wide chars;
+ # - No explicit SIMD (the original mainly uses this for wide char strings).
+ #
+ # The following compile-time configuration is assumed:
+ #
+ # - #define FASTFLOAT_ALLOWS_LEADING_PLUS
+ # - #define FLT_EVAL_METHOD 0
+ module FastFloat
+ # Current revision: https://github.com/fastfloat/fast_float/tree/v6.1.6
+
+ def self.to_f64?(str : String, whitespace : Bool, strict : Bool) : Float64?
+ value = uninitialized Float64
+ start = str.to_unsafe
+ finish = start + str.bytesize
+ options = ParseOptionsT(typeof(str.to_unsafe.value)).new(format: :general)
+
+ if whitespace
+ start += str.calc_excess_left
+ finish -= str.calc_excess_right
+ end
+
+ ret = BinaryFormat_Float64.new.from_chars_advanced(start, finish, pointerof(value), options)
+ if ret.ec == Errno::NONE && (!strict || ret.ptr == finish)
+ value
+ end
+ end
+
+ def self.to_f32?(str : String, whitespace : Bool, strict : Bool) : Float32?
+ value = uninitialized Float32
+ start = str.to_unsafe
+ finish = start + str.bytesize
+ options = ParseOptionsT(typeof(str.to_unsafe.value)).new(format: :general)
+
+ if whitespace
+ start += str.calc_excess_left
+ finish -= str.calc_excess_right
+ end
+
+ ret = BinaryFormat_Float32.new.from_chars_advanced(start, finish, pointerof(value), options)
+ if ret.ec == Errno::NONE && (!strict || ret.ptr == finish)
+ value
+ end
+ end
+ end
+end
+
+require "./fast_float/parse_number"
diff --git a/src/float/fast_float/ascii_number.cr b/src/float/fast_float/ascii_number.cr
new file mode 100644
index 000000000000..1c4b43ea4b7d
--- /dev/null
+++ b/src/float/fast_float/ascii_number.cr
@@ -0,0 +1,270 @@
+require "./float_common"
+
+module Float::FastFloat
+ # Next function can be micro-optimized, but compilers are entirely able to
+ # optimize it well.
+ def self.is_integer?(c : UC) : Bool forall UC
+ !(c > '9'.ord || c < '0'.ord)
+ end
+
+ # Read 8 UC into a u64. Truncates UC if not char.
+ def self.read8_to_u64(chars : UC*) : UInt64 forall UC
+ val = uninitialized UInt64
+ chars.as(UInt8*).copy_to(pointerof(val).as(UInt8*), sizeof(UInt64))
+ {% if IO::ByteFormat::SystemEndian == IO::ByteFormat::BigEndian %}
+ val.byte_swap
+ {% else %}
+ val
+ {% end %}
+ end
+
+ # credit @aqrit
+ def self.parse_eight_digits_unrolled(val : UInt64) : UInt32
+ mask = 0x000000FF000000FF_u64
+ mul1 = 0x000F424000000064_u64 # 100 + (1000000ULL << 32)
+ mul2 = 0x0000271000000001_u64 # 1 + (10000ULL << 32)
+ val &-= 0x3030303030303030
+ val = (val &* 10) &+ val.unsafe_shr(8) # val = (val * 2561) >> 8
+ val = (((val & mask) &* mul1) &+ ((val.unsafe_shr(16) & mask) &* mul2)).unsafe_shr(32)
+ val.to_u32!
+ end
+
+ # Call this if chars are definitely 8 digits.
+ def self.parse_eight_digits_unrolled(chars : UC*) : UInt32 forall UC
+ parse_eight_digits_unrolled(read8_to_u64(chars))
+ end
+
+ # credit @aqrit
+ def self.is_made_of_eight_digits_fast?(val : UInt64) : Bool
+ ((val &+ 0x4646464646464646_u64) | (val &- 0x3030303030303030_u64)) & 0x8080808080808080_u64 == 0
+ end
+
+ # NOTE(crystal): returns {p, i}
+ def self.loop_parse_if_eight_digits(p : UInt8*, pend : UInt8*, i : UInt64) : {UInt8*, UInt64}
+ # optimizes better than parse_if_eight_digits_unrolled() for UC = char.
+ while pend - p >= 8 && is_made_of_eight_digits_fast?(read8_to_u64(p))
+ i = i &* 100000000 &+ parse_eight_digits_unrolled(read8_to_u64(p)) # in rare cases, this will overflow, but that's ok
+ p += 8
+ end
+ {p, i}
+ end
+
+ enum ParseError
+ NoError
+
+ # [JSON-only] The minus sign must be followed by an integer.
+ MissingIntegerAfterSign
+
+ # A sign must be followed by an integer or dot.
+ MissingIntegerOrDotAfterSign
+
+ # [JSON-only] The integer part must not have leading zeros.
+ LeadingZerosInIntegerPart
+
+ # [JSON-only] The integer part must have at least one digit.
+ NoDigitsInIntegerPart
+
+ # [JSON-only] If there is a decimal point, there must be digits in the
+ # fractional part.
+ NoDigitsInFractionalPart
+
+ # The mantissa must have at least one digit.
+ NoDigitsInMantissa
+
+ # Scientific notation requires an exponential part.
+ MissingExponentialPart
+ end
+
+ struct ParsedNumberStringT(UC)
+ property exponent : Int64 = 0
+ property mantissa : UInt64 = 0
+ property lastmatch : UC* = Pointer(UC).null
+ property negative : Bool = false
+ property valid : Bool = false
+ property too_many_digits : Bool = false
+ # contains the range of the significant digits
+ property integer : Slice(UC) = Slice(UC).empty # non-nullable
+ property fraction : Slice(UC) = Slice(UC).empty # nullable
+ property error : ParseError = :no_error
+ end
+
+ alias ByteSpan = ::Bytes
+ alias ParsedNumberString = ParsedNumberStringT(UInt8)
+
+ def self.report_parse_error(p : UC*, error : ParseError) : ParsedNumberStringT(UC) forall UC
+ answer = ParsedNumberStringT(UC).new
+ answer.valid = false
+ answer.lastmatch = p
+ answer.error = error
+ answer
+ end
+
+ # Assuming that you use no more than 19 digits, this will parse an ASCII
+ # string.
+ def self.parse_number_string(p : UC*, pend : UC*, options : ParseOptionsT(UC)) : ParsedNumberStringT(UC) forall UC
+ fmt = options.format
+ decimal_point = options.decimal_point
+
+ answer = ParsedNumberStringT(UInt8).new
+ answer.valid = false
+ answer.too_many_digits = false
+ answer.negative = p.value === '-'
+
+ if p.value === '-' || (!fmt.json_fmt? && p.value === '+')
+ p += 1
+ if p == pend
+ return report_parse_error(p, :missing_integer_or_dot_after_sign)
+ end
+ if fmt.json_fmt?
+ if !is_integer?(p.value) # a sign must be followed by an integer
+ return report_parse_error(p, :missing_integer_after_sign)
+ end
+ else
+ if !is_integer?(p.value) && p.value != decimal_point # a sign must be followed by an integer or the dot
+ return report_parse_error(p, :missing_integer_or_dot_after_sign)
+ end
+ end
+ end
+ start_digits = p
+
+ i = 0_u64 # an unsigned int avoids signed overflows (which are bad)
+
+ while p != pend && is_integer?(p.value)
+ # a multiplication by 10 is cheaper than an arbitrary integer multiplication
+ i = i &* 10 &+ (p.value &- '0'.ord).to_u64! # might overflow, we will handle the overflow later
+ p += 1
+ end
+ end_of_integer_part = p
+ digit_count = (end_of_integer_part - start_digits).to_i32!
+ answer.integer = Slice.new(start_digits, digit_count)
+ if fmt.json_fmt?
+ # at least 1 digit in integer part, without leading zeros
+ if digit_count == 0
+ return report_parse_error(p, :no_digits_in_integer_part)
+ end
+ if start_digits[0] === '0' && digit_count > 1
+ return report_parse_error(p, :leading_zeros_in_integer_part)
+ end
+ end
+
+ exponent = 0_i64
+ has_decimal_point = p != pend && p.value == decimal_point
+ if has_decimal_point
+ p += 1
+ before = p
+ # can occur at most twice without overflowing, but let it occur more, since
+ # for integers with many digits, digit parsing is the primary bottleneck.
+ p, i = loop_parse_if_eight_digits(p, pend, i)
+
+ while p != pend && is_integer?(p.value)
+ digit = (p.value &- '0'.ord).to_u8!
+ p += 1
+ i = i &* 10 &+ digit # in rare cases, this will overflow, but that's ok
+ end
+ exponent = before - p
+ answer.fraction = Slice.new(before, (p - before).to_i32!)
+ digit_count &-= exponent
+ end
+ if fmt.json_fmt?
+ # at least 1 digit in fractional part
+ if has_decimal_point && exponent == 0
+ return report_parse_error(p, :no_digits_in_fractional_part)
+ end
+ elsif digit_count == 0 # we must have encountered at least one integer!
+ return report_parse_error(p, :no_digits_in_mantissa)
+ end
+ exp_number = 0_i64 # explicit exponential part
+ if (fmt.scientific? && p != pend && p.value.unsafe_chr.in?('e', 'E')) ||
+ (fmt.fortran_fmt? && p != pend && p.value.unsafe_chr.in?('+', '-', 'd', 'D'))
+ location_of_e = p
+ if p.value.unsafe_chr.in?('e', 'E', 'd', 'D')
+ p += 1
+ end
+ neg_exp = false
+ if p != pend && p.value === '-'
+ neg_exp = true
+ p += 1
+ elsif p != pend && p.value === '+' # '+' on exponent is allowed by C++17 20.19.3.(7.1)
+ p += 1
+ end
+ if p == pend || !is_integer?(p.value)
+ if !fmt.fixed?
+ # The exponential part is invalid for scientific notation, so it must
+ # be a trailing token for fixed notation. However, fixed notation is
+ # disabled, so report a scientific notation error.
+ return report_parse_error(p, :missing_exponential_part)
+ end
+ # Otherwise, we will be ignoring the 'e'.
+ p = location_of_e
+ else
+ while p != pend && is_integer?(p.value)
+ digit = (p.value &- '0'.ord).to_u8!
+ if exp_number < 0x10000000
+ exp_number = exp_number &* 10 &+ digit
+ end
+ p += 1
+ end
+ if neg_exp
+ exp_number = 0_i64 &- exp_number
+ end
+ exponent &+= exp_number
+ end
+ else
+ # If it scientific and not fixed, we have to bail out.
+ if fmt.scientific? && !fmt.fixed?
+ return report_parse_error(p, :missing_exponential_part)
+ end
+ end
+ answer.lastmatch = p
+ answer.valid = true
+
+ # If we frequently had to deal with long strings of digits,
+ # we could extend our code by using a 128-bit integer instead
+ # of a 64-bit integer. However, this is uncommon.
+ #
+ # We can deal with up to 19 digits.
+ if digit_count > 19 # this is uncommon
+ # It is possible that the integer had an overflow.
+ # We have to handle the case where we have 0.0000somenumber.
+ # We need to be mindful of the case where we only have zeroes...
+ # E.g., 0.000000000...000.
+ start = start_digits
+ while start != pend && (start.value === '0' || start.value == decimal_point)
+ if start.value === '0'
+ digit_count &-= 1
+ end
+ start += 1
+ end
+
+ if digit_count > 19
+ answer.too_many_digits = true
+ # Let us start again, this time, avoiding overflows.
+ # We don't need to check if is_integer, since we use the
+ # pre-tokenized spans from above.
+ i = 0_u64
+ p = answer.integer.to_unsafe
+ int_end = p + answer.integer.size
+ minimal_nineteen_digit_integer = 1000000000000000000_u64
+ while i < minimal_nineteen_digit_integer && p != int_end
+ i = i &* 10 &+ (p.value &- '0'.ord).to_u64!
+ p += 1
+ end
+ if i >= minimal_nineteen_digit_integer # We have a big integers
+ exponent = (end_of_integer_part - p) &+ exp_number
+ else # We have a value with a fractional component.
+ p = answer.fraction.to_unsafe
+ frac_end = p + answer.fraction.size
+ while i < minimal_nineteen_digit_integer && p != frac_end
+ i = i &* 10 &+ (p.value &- '0'.ord).to_u64!
+ p += 1
+ end
+ exponent = (answer.fraction.to_unsafe - p) &+ exp_number
+ end
+ # We have now corrected both exponent and i, to a truncated value
+ end
+ end
+ answer.exponent = exponent
+ answer.mantissa = i
+ answer
+ end
+end
diff --git a/src/float/fast_float/bigint.cr b/src/float/fast_float/bigint.cr
new file mode 100644
index 000000000000..14b0bb2d0549
--- /dev/null
+++ b/src/float/fast_float/bigint.cr
@@ -0,0 +1,577 @@
+require "./float_common"
+
+module Float::FastFloat
+ # the limb width: we want efficient multiplication of double the bits in
+ # limb, or for 64-bit limbs, at least 64-bit multiplication where we can
+ # extract the high and low parts efficiently. this is every 64-bit
+ # architecture except for sparc, which emulates 128-bit multiplication.
+ # we might have platforms where `CHAR_BIT` is not 8, so let's avoid
+ # doing `8 * sizeof(limb)`.
+ {% if flag?(:bits64) %}
+ alias Limb = UInt64
+ LIMB_BITS = 64
+ {% else %}
+ alias Limb = UInt32
+ LIMB_BITS = 32
+ {% end %}
+
+ alias LimbSpan = Slice(Limb)
+
+ # number of bits in a bigint. this needs to be at least the number
+ # of bits required to store the largest bigint, which is
+ # `log2(10**(digits + max_exp))`, or `log2(10**(767 + 342))`, or
+ # ~3600 bits, so we round to 4000.
+ BIGINT_BITS = 4000
+ {% begin %}
+ BIGINT_LIMBS = {{ BIGINT_BITS // LIMB_BITS }}
+ {% end %}
+
+ # vector-like type that is allocated on the stack. the entire
+ # buffer is pre-allocated, and only the length changes.
+ # NOTE(crystal): Deviates a lot from the original implementation to reuse
+ # `Indexable` as much as possible. Contrast with `Crystal::SmallDeque` and
+ # `Crystal::Tracing::BufferIO`
+ struct Stackvec(Size)
+ include Indexable::Mutable(Limb)
+
+ @data = uninitialized Limb[Size]
+
+ # we never need more than 150 limbs
+ @length = 0_u16
+
+ def unsafe_fetch(index : Int) : Limb
+ @data.to_unsafe[index]
+ end
+
+ def unsafe_put(index : Int, value : Limb) : Limb
+ @data.to_unsafe[index] = value
+ end
+
+ def size : Int32
+ @length.to_i32!
+ end
+
+ def to_unsafe : Limb*
+ @data.to_unsafe
+ end
+
+ def to_slice : LimbSpan
+ LimbSpan.new(@data.to_unsafe, @length)
+ end
+
+ def initialize
+ end
+
+ # create stack vector from existing limb span.
+ def initialize(s : LimbSpan)
+ try_extend(s)
+ end
+
+ # index from the end of the container
+ def rindex(index : Int) : Limb
+ rindex = @length &- index &- 1
+ @data.to_unsafe[rindex]
+ end
+
+ # set the length, without bounds checking.
+ def size=(@length : UInt16) : UInt16
+ length
+ end
+
+ def capacity : Int32
+ Size.to_i32!
+ end
+
+ # append item to vector, without bounds checking.
+ def push_unchecked(value : Limb) : Nil
+ @data.to_unsafe[@length] = value
+ @length &+= 1
+ end
+
+ # append item to vector, returning if item was added
+ def try_push(value : Limb) : Bool
+ if size < capacity
+ push_unchecked(value)
+ true
+ else
+ false
+ end
+ end
+
+ # add items to the vector, from a span, without bounds checking
+ def extend_unchecked(s : LimbSpan) : Nil
+ ptr = @data.to_unsafe + @length
+ s.to_unsafe.copy_to(ptr, s.size)
+ @length &+= s.size
+ end
+
+ # try to add items to the vector, returning if items were added
+ def try_extend(s : LimbSpan) : Bool
+ if size &+ s.size <= capacity
+ extend_unchecked(s)
+ true
+ else
+ false
+ end
+ end
+
+ # resize the vector, without bounds checking
+ # if the new size is longer than the vector, assign value to each
+ # appended item.
+ def resize_unchecked(new_len : UInt16, value : Limb) : Nil
+ if new_len > @length
+ count = new_len &- @length
+ first = @data.to_unsafe + @length
+ count.times { |i| first[i] = value }
+ @length = new_len
+ else
+ @length = new_len
+ end
+ end
+
+ # try to resize the vector, returning if the vector was resized.
+ def try_resize(new_len : UInt16, value : Limb) : Bool
+ if new_len > capacity
+ false
+ else
+ resize_unchecked(new_len, value)
+ true
+ end
+ end
+
+ # check if any limbs are non-zero after the given index.
+ # this needs to be done in reverse order, since the index
+ # is relative to the most significant limbs.
+ def nonzero?(index : Int) : Bool
+ while index < size
+ if rindex(index) != 0
+ return true
+ end
+ index &+= 1
+ end
+ false
+ end
+
+ # normalize the big integer, so most-significant zero limbs are removed.
+ def normalize : Nil
+ while @length > 0 && rindex(0) == 0
+ @length &-= 1
+ end
+ end
+ end
+
+ # NOTE(crystal): returns also *truncated* by value (ditto below)
+ def self.empty_hi64 : {UInt64, Bool}
+ truncated = false
+ {0_u64, truncated}
+ end
+
+ def self.uint64_hi64(r0 : UInt64) : {UInt64, Bool}
+ truncated = false
+ shl = r0.leading_zeros_count
+ {r0.unsafe_shl(shl), truncated}
+ end
+
+ def self.uint64_hi64(r0 : UInt64, r1 : UInt64) : {UInt64, Bool}
+ shl = r0.leading_zeros_count
+ if shl == 0
+ truncated = r1 != 0
+ {r0, truncated}
+ else
+ shr = 64 &- shl
+ truncated = r1.unsafe_shl(shl) != 0
+ {r0.unsafe_shl(shl) | r1.unsafe_shr(shr), truncated}
+ end
+ end
+
+ def self.uint32_hi64(r0 : UInt32) : {UInt64, Bool}
+ uint64_hi64(r0.to_u64!)
+ end
+
+ def self.uint32_hi64(r0 : UInt32, r1 : UInt32) : {UInt64, Bool}
+ x0 = r0.to_u64!
+ x1 = r1.to_u64!
+ uint64_hi64(x0.unsafe_shl(32) | x1)
+ end
+
+ def self.uint32_hi64(r0 : UInt32, r1 : UInt32, r2 : UInt32) : {UInt64, Bool}
+ x0 = r0.to_u64!
+ x1 = r1.to_u64!
+ x2 = r2.to_u64!
+ uint64_hi64(x0, x1.unsafe_shl(32) | x2)
+ end
+
+ # add two small integers, checking for overflow.
+ # we want an efficient operation.
+ # NOTE(crystal): returns also *overflow* by value
+ def self.scalar_add(x : Limb, y : Limb) : {Limb, Bool}
+ z = x &+ y
+ overflow = z < x
+ {z, overflow}
+ end
+
+ # multiply two small integers, getting both the high and low bits.
+ # NOTE(crystal): passes *carry* in and out by value
+ def self.scalar_mul(x : Limb, y : Limb, carry : Limb) : {Limb, Limb}
+ {% if Limb == UInt64 %}
+ z = x.to_u128! &* y.to_u128! &+ carry
+ carry = z.unsafe_shr(LIMB_BITS).to_u64!
+ {z.to_u64!, carry}
+ {% else %}
+ z = x.to_u64! &* y.to_u64! &+ carry
+ carry = z.unsafe_shr(LIMB_BITS).to_u32!
+ {z.to_u32!, carry}
+ {% end %}
+ end
+
+ # add scalar value to bigint starting from offset.
+ # used in grade school multiplication
+ def self.small_add_from(vec : Stackvec(Size)*, y : Limb, start : Int) : Bool forall Size
+ index = start
+ carry = y
+
+ while carry != 0 && index < vec.value.size
+ x, overflow = scalar_add(vec.value.unsafe_fetch(index), carry)
+ vec.value.unsafe_put(index, x)
+ carry = Limb.new!(overflow ? 1 : 0)
+ index &+= 1
+ end
+ if carry != 0
+ fastfloat_try vec.value.try_push(carry)
+ end
+ true
+ end
+
+ # add scalar value to bigint.
+ def self.small_add(vec : Stackvec(Size)*, y : Limb) : Bool forall Size
+ small_add_from(vec, y, 0)
+ end
+
+ # multiply bigint by scalar value.
+ def self.small_mul(vec : Stackvec(Size)*, y : Limb) : Bool forall Size
+ carry = Limb.zero
+ i = 0
+ while i < vec.value.size
+ xi = vec.value.unsafe_fetch(i)
+ z, carry = scalar_mul(xi, y, carry)
+ vec.value.unsafe_put(i, z)
+ i &+= 1
+ end
+ if carry != 0
+ fastfloat_try vec.value.try_push(carry)
+ end
+ true
+ end
+
+ # add bigint to bigint starting from index.
+ # used in grade school multiplication
+ def self.large_add_from(x : Stackvec(Size)*, y : LimbSpan, start : Int) : Bool forall Size
+ # the effective x buffer is from `xstart..x.len()`, so exit early
+ # if we can't get that current range.
+ if x.value.size < start || y.size > x.value.size &- start
+ fastfloat_try x.value.try_resize((y.size &+ start).to_u16!, 0)
+ end
+
+ carry = false
+ index = 0
+ while index < y.size
+ xi = x.value.unsafe_fetch(index &+ start)
+ yi = y.unsafe_fetch(index)
+ c2 = false
+ xi, c1 = scalar_add(xi, yi)
+ if carry
+ xi, c2 = scalar_add(xi, 1)
+ end
+ x.value.unsafe_put(index &+ start, xi)
+ carry = c1 || c2
+ index &+= 1
+ end
+
+ # handle overflow
+ if carry
+ fastfloat_try small_add_from(x, 1, y.size &+ start)
+ end
+ true
+ end
+
+ # add bigint to bigint.
+ def self.large_add_from(x : Stackvec(Size)*, y : LimbSpan) : Bool forall Size
+ large_add_from(x, y, 0)
+ end
+
+ # grade-school multiplication algorithm
+ def self.long_mul(x : Stackvec(Size)*, y : LimbSpan) : Bool forall Size
+ xs = x.value.to_slice
+ z = Stackvec(Size).new(xs)
+ zs = z.to_slice
+
+ if y.size != 0
+ y0 = y.unsafe_fetch(0)
+ fastfloat_try small_mul(x, y0)
+ (1...y.size).each do |index|
+ yi = y.unsafe_fetch(index)
+ zi = Stackvec(Size).new
+ if yi != 0
+ # re-use the same buffer throughout
+ zi.size = 0
+ fastfloat_try zi.try_extend(zs)
+ fastfloat_try small_mul(pointerof(zi), yi)
+ zis = zi.to_slice
+ fastfloat_try large_add_from(x, zis, index)
+ end
+ end
+ end
+
+ x.value.normalize
+ true
+ end
+
+ # grade-school multiplication algorithm
+ def self.large_mul(x : Stackvec(Size)*, y : LimbSpan) : Bool forall Size
+ if y.size == 1
+ fastfloat_try small_mul(x, y.unsafe_fetch(0))
+ else
+ fastfloat_try long_mul(x, y)
+ end
+ true
+ end
+
+ module Pow5Tables
+ LARGE_STEP = 135_u32
+
+ SMALL_POWER_OF_5 = [
+ 1_u64,
+ 5_u64,
+ 25_u64,
+ 125_u64,
+ 625_u64,
+ 3125_u64,
+ 15625_u64,
+ 78125_u64,
+ 390625_u64,
+ 1953125_u64,
+ 9765625_u64,
+ 48828125_u64,
+ 244140625_u64,
+ 1220703125_u64,
+ 6103515625_u64,
+ 30517578125_u64,
+ 152587890625_u64,
+ 762939453125_u64,
+ 3814697265625_u64,
+ 19073486328125_u64,
+ 95367431640625_u64,
+ 476837158203125_u64,
+ 2384185791015625_u64,
+ 11920928955078125_u64,
+ 59604644775390625_u64,
+ 298023223876953125_u64,
+ 1490116119384765625_u64,
+ 7450580596923828125_u64,
+ ]
+
+ {% if Limb == UInt64 %}
+ LARGE_POWER_OF_5 = Slice[
+ 1414648277510068013_u64, 9180637584431281687_u64, 4539964771860779200_u64,
+ 10482974169319127550_u64, 198276706040285095_u64,
+ ]
+ {% else %}
+ LARGE_POWER_OF_5 = Slice[
+ 4279965485_u32, 329373468_u32, 4020270615_u32, 2137533757_u32, 4287402176_u32,
+ 1057042919_u32, 1071430142_u32, 2440757623_u32, 381945767_u32, 46164893_u32,
+ ]
+ {% end %}
+ end
+
+ # big integer type. implements a small subset of big integer
+ # arithmetic, using simple algorithms since asymptotically
+ # faster algorithms are slower for a small number of limbs.
+ # all operations assume the big-integer is normalized.
+ # NOTE(crystal): contrast with ::BigInt
+ struct Bigint
+ # storage of the limbs, in little-endian order.
+ @vec = Stackvec(BIGINT_LIMBS).new
+
+ def initialize
+ end
+
+ def initialize(value : UInt64)
+ {% if Limb == UInt64 %}
+ @vec.push_unchecked(value)
+ {% else %}
+ @vec.push_unchecked(value.to_u32!)
+ @vec.push_unchecked(value.unsafe_shr(32).to_u32!)
+ {% end %}
+ @vec.normalize
+ end
+
+ # get the high 64 bits from the vector, and if bits were truncated.
+ # this is to get the significant digits for the float.
+ # NOTE(crystal): returns also *truncated* by value
+ def hi64 : {UInt64, Bool}
+ {% if Limb == UInt64 %}
+ if @vec.empty?
+ FastFloat.empty_hi64
+ elsif @vec.size == 1
+ FastFloat.uint64_hi64(@vec.rindex(0))
+ else
+ result, truncated = FastFloat.uint64_hi64(@vec.rindex(0), @vec.rindex(1))
+ truncated ||= @vec.nonzero?(2)
+ {result, truncated}
+ end
+ {% else %}
+ if @vec.empty?
+ FastFloat.empty_hi64
+ elsif @vec.size == 1
+ FastFloat.uint32_hi64(@vec.rindex(0))
+ elsif @vec.size == 2
+ FastFloat.uint32_hi64(@vec.rindex(0), @vec.rindex(1))
+ else
+ result, truncated = FastFloat.uint32_hi64(@vec.rindex(0), @vec.rindex(1), @vec.rindex(2))
+ truncated ||= @vec.nonzero?(3)
+ {result, truncated}
+ end
+ {% end %}
+ end
+
+ # compare two big integers, returning the large value.
+ # assumes both are normalized. if the return value is
+ # negative, other is larger, if the return value is
+ # positive, this is larger, otherwise they are equal.
+ # the limbs are stored in little-endian order, so we
+ # must compare the limbs in ever order.
+ def compare(other : Bigint*) : Int32
+ if @vec.size > other.value.@vec.size
+ 1
+ elsif @vec.size < other.value.@vec.size
+ -1
+ else
+ index = @vec.size
+ while index > 0
+ xi = @vec.unsafe_fetch(index &- 1)
+ yi = other.value.@vec.unsafe_fetch(index &- 1)
+ if xi > yi
+ return 1
+ elsif xi < yi
+ return -1
+ end
+ index &-= 1
+ end
+ 0
+ end
+ end
+
+ # shift left each limb n bits, carrying over to the new limb
+ # returns true if we were able to shift all the digits.
+ def shl_bits(n : Int) : Bool
+ # Internally, for each item, we shift left by n, and add the previous
+ # right shifted limb-bits.
+ # For example, we transform (for u8) shifted left 2, to:
+ # b10100100 b01000010
+ # b10 b10010001 b00001000
+ shl = n
+ shr = LIMB_BITS &- n
+ prev = Limb.zero
+ index = 0
+ while index < @vec.size
+ xi = @vec.unsafe_fetch(index)
+ @vec.unsafe_put(index, xi.unsafe_shl(shl) | prev.unsafe_shr(shr))
+ prev = xi
+ index &+= 1
+ end
+
+ carry = prev.unsafe_shr(shr)
+ if carry != 0
+ return @vec.try_push(carry)
+ end
+ true
+ end
+
+ # move the limbs left by `n` limbs.
+ def shl_limbs(n : Int) : Bool
+ if n &+ @vec.size > @vec.capacity
+ false
+ elsif !@vec.empty?
+ # move limbs
+ dst = @vec.to_unsafe + n
+ src = @vec.to_unsafe
+ src.move_to(dst, @vec.size)
+ # fill in empty limbs
+ first = @vec.to_unsafe
+ n.times { |i| first[i] = 0 }
+ @vec.size = (@vec.size &+ n).to_u16!
+ true
+ else
+ true
+ end
+ end
+
+ # move the limbs left by `n` bits.
+ def shl(n : Int) : Bool
+ rem = n.unsafe_mod(LIMB_BITS)
+ div = n.unsafe_div(LIMB_BITS)
+ if rem != 0
+ FastFloat.fastfloat_try shl_bits(rem)
+ end
+ if div != 0
+ FastFloat.fastfloat_try shl_limbs(div)
+ end
+ true
+ end
+
+ # get the number of leading zeros in the bigint.
+ def ctlz : Int32
+ if @vec.empty?
+ 0
+ else
+ @vec.rindex(0).leading_zeros_count.to_i32!
+ end
+ end
+
+ # get the number of bits in the bigint.
+ def bit_length : Int32
+ lz = ctlz
+ (LIMB_BITS &* @vec.size &- lz).to_i32!
+ end
+
+ def mul(y : Limb) : Bool
+ FastFloat.small_mul(pointerof(@vec), y)
+ end
+
+ def add(y : Limb) : Bool
+ FastFloat.small_add(pointerof(@vec), y)
+ end
+
+ # multiply as if by 2 raised to a power.
+ def pow2(exp : UInt32) : Bool
+ shl(exp)
+ end
+
+ # multiply as if by 5 raised to a power.
+ def pow5(exp : UInt32) : Bool
+ # multiply by a power of 5
+ large = Pow5Tables::LARGE_POWER_OF_5
+ while exp >= Pow5Tables::LARGE_STEP
+ FastFloat.fastfloat_try FastFloat.large_mul(pointerof(@vec), large)
+ exp &-= Pow5Tables::LARGE_STEP
+ end
+ small_step = {{ Limb == UInt64 ? 27_u32 : 13_u32 }}
+ max_native = {{ Limb == UInt64 ? 7450580596923828125_u64 : 1220703125_u32 }}
+ while exp >= small_step
+ FastFloat.fastfloat_try FastFloat.small_mul(pointerof(@vec), max_native)
+ exp &-= small_step
+ end
+ if exp != 0
+ FastFloat.fastfloat_try FastFloat.small_mul(pointerof(@vec), Limb.new!(Pow5Tables::SMALL_POWER_OF_5.unsafe_fetch(exp)))
+ end
+
+ true
+ end
+
+ # multiply as if by 10 raised to a power.
+ def pow10(exp : UInt32) : Bool
+ FastFloat.fastfloat_try pow5(exp)
+ pow2(exp)
+ end
+ end
+end
diff --git a/src/float/fast_float/decimal_to_binary.cr b/src/float/fast_float/decimal_to_binary.cr
new file mode 100644
index 000000000000..eea77c44c6be
--- /dev/null
+++ b/src/float/fast_float/decimal_to_binary.cr
@@ -0,0 +1,177 @@
+require "./float_common"
+require "./fast_table"
+
+module Float::FastFloat
+ # This will compute or rather approximate w * 5**q and return a pair of 64-bit
+ # words approximating the result, with the "high" part corresponding to the
+ # most significant bits and the low part corresponding to the least significant
+ # bits.
+ def self.compute_product_approximation(q : Int64, w : UInt64, bit_precision : Int) : Value128
+ power_of_five_128 = Powers::POWER_OF_FIVE_128.to_unsafe
+
+ index = 2 &* (q &- Powers::SMALLEST_POWER_OF_FIVE)
+ # For small values of q, e.g., q in [0,27], the answer is always exact
+ # because The line value128 firstproduct = full_multiplication(w,
+ # power_of_five_128[index]); gives the exact answer.
+ firstproduct = w.to_u128! &* power_of_five_128[index]
+
+ precision_mask = bit_precision < 64 ? 0xFFFFFFFFFFFFFFFF_u64.unsafe_shr(bit_precision) : 0xFFFFFFFFFFFFFFFF_u64
+ if firstproduct.unsafe_shr(64).bits_set?(precision_mask) # could further guard with (lower + w < lower)
+ # regarding the second product, we only need secondproduct.high, but our
+ # expectation is that the compiler will optimize this extra work away if
+ # needed.
+ secondproduct = w.to_u128! &* power_of_five_128[index &+ 1]
+ firstproduct &+= secondproduct.unsafe_shr(64)
+ end
+ Value128.new(firstproduct)
+ end
+
+ module Detail
+ # For q in (0,350), we have that
+ # f = (((152170 + 65536) * q ) >> 16);
+ # is equal to
+ # floor(p) + q
+ # where
+ # p = log(5**q)/log(2) = q * log(5)/log(2)
+ #
+ # For negative values of q in (-400,0), we have that
+ # f = (((152170 + 65536) * q ) >> 16);
+ # is equal to
+ # -ceil(p) + q
+ # where
+ # p = log(5**-q)/log(2) = -q * log(5)/log(2)
+ def self.power(q : Int32) : Int32
+ ((152170 &+ 65536) &* q).unsafe_shr(16) &+ 63
+ end
+ end
+
+ module BinaryFormat(T, EquivUint)
+ # create an adjusted mantissa, biased by the invalid power2
+ # for significant digits already multiplied by 10 ** q.
+ def compute_error_scaled(q : Int64, w : UInt64, lz : Int) : AdjustedMantissa
+ hilz = w.unsafe_shr(63).to_i32! ^ 1
+ bias = mantissa_explicit_bits &- minimum_exponent
+
+ AdjustedMantissa.new(
+ mantissa: w.unsafe_shl(hilz),
+ power2: Detail.power(q.to_i32!) &+ bias &- hilz &- lz &- 62 &+ INVALID_AM_BIAS,
+ )
+ end
+
+ # w * 10 ** q, without rounding the representation up.
+ # the power2 in the exponent will be adjusted by invalid_am_bias.
+ def compute_error(q : Int64, w : UInt64) : AdjustedMantissa
+ lz = w.leading_zeros_count.to_i32!
+ w = w.unsafe_shl(lz)
+ product = FastFloat.compute_product_approximation(q, w, mantissa_explicit_bits &+ 3)
+ compute_error_scaled(q, product.high, lz)
+ end
+
+ # w * 10 ** q
+ # The returned value should be a valid ieee64 number that simply need to be
+ # packed. However, in some very rare cases, the computation will fail. In such
+ # cases, we return an adjusted_mantissa with a negative power of 2: the caller
+ # should recompute in such cases.
+ def compute_float(q : Int64, w : UInt64) : AdjustedMantissa
+ if w == 0 || q < smallest_power_of_ten
+ # result should be zero
+ return AdjustedMantissa.new(
+ power2: 0,
+ mantissa: 0,
+ )
+ end
+ if q > largest_power_of_ten
+ # we want to get infinity:
+ return AdjustedMantissa.new(
+ power2: infinite_power,
+ mantissa: 0,
+ )
+ end
+ # At this point in time q is in [powers::smallest_power_of_five,
+ # powers::largest_power_of_five].
+
+ # We want the most significant bit of i to be 1. Shift if needed.
+ lz = w.leading_zeros_count
+ w = w.unsafe_shl(lz)
+
+ # The required precision is binary::mantissa_explicit_bits() + 3 because
+ # 1. We need the implicit bit
+ # 2. We need an extra bit for rounding purposes
+ # 3. We might lose a bit due to the "upperbit" routine (result too small,
+ # requiring a shift)
+
+ product = FastFloat.compute_product_approximation(q, w, mantissa_explicit_bits &+ 3)
+ # The computed 'product' is always sufficient.
+ # Mathematical proof:
+ # Noble Mushtak and Daniel Lemire, Fast Number Parsing Without Fallback (to
+ # appear) See script/mushtak_lemire.py
+
+ # The "compute_product_approximation" function can be slightly slower than a
+ # branchless approach: value128 product = compute_product(q, w); but in
+ # practice, we can win big with the compute_product_approximation if its
+ # additional branch is easily predicted. Which is best is data specific.
+ upperbit = product.high.unsafe_shr(63).to_i32!
+ shift = upperbit &+ 64 &- mantissa_explicit_bits &- 3
+
+ mantissa = product.high.unsafe_shr(shift)
+
+ power2 = (Detail.power(q.to_i32!) &+ upperbit &- lz &- minimum_exponent).to_i32!
+ if power2 <= 0 # we have a subnormal?
+ # Here have that answer.power2 <= 0 so -answer.power2 >= 0
+ if 1 &- power2 >= 64 # if we have more than 64 bits below the minimum exponent, you have a zero for sure.
+ # result should be zero
+ return AdjustedMantissa.new(
+ power2: 0,
+ mantissa: 0,
+ )
+ end
+ # next line is safe because -answer.power2 + 1 < 64
+ mantissa = mantissa.unsafe_shr(1 &- power2)
+ # Thankfully, we can't have both "round-to-even" and subnormals because
+ # "round-to-even" only occurs for powers close to 0.
+ mantissa &+= mantissa & 1
+ mantissa = mantissa.unsafe_shr(1)
+ # There is a weird scenario where we don't have a subnormal but just.
+ # Suppose we start with 2.2250738585072013e-308, we end up
+ # with 0x3fffffffffffff x 2^-1023-53 which is technically subnormal
+ # whereas 0x40000000000000 x 2^-1023-53 is normal. Now, we need to round
+ # up 0x3fffffffffffff x 2^-1023-53 and once we do, we are no longer
+ # subnormal, but we can only know this after rounding.
+ # So we only declare a subnormal if we are smaller than the threshold.
+ power2 = mantissa < 1_u64.unsafe_shl(mantissa_explicit_bits) ? 0 : 1
+ return AdjustedMantissa.new(power2: power2, mantissa: mantissa)
+ end
+
+ # usually, we round *up*, but if we fall right in between and and we have an
+ # even basis, we need to round down
+ # We are only concerned with the cases where 5**q fits in single 64-bit word.
+ if product.low <= 1 && q >= min_exponent_round_to_even && q <= max_exponent_round_to_even && mantissa & 3 == 1
+ # we may fall between two floats!
+ # To be in-between two floats we need that in doing
+ # answer.mantissa = product.high >> (upperbit + 64 -
+ # binary::mantissa_explicit_bits() - 3);
+ # ... we dropped out only zeroes. But if this happened, then we can go
+ # back!!!
+ if mantissa.unsafe_shl(shift) == product.high
+ mantissa &= ~1_u64 # flip it so that we do not round up
+ end
+ end
+
+ mantissa &+= mantissa & 1 # round up
+ mantissa = mantissa.unsafe_shr(1)
+ if mantissa >= 2_u64.unsafe_shl(mantissa_explicit_bits)
+ mantissa = 1_u64.unsafe_shl(mantissa_explicit_bits)
+ power2 &+= 1 # undo previous addition
+ end
+
+ mantissa &= ~(1_u64.unsafe_shl(mantissa_explicit_bits))
+ if power2 >= infinite_power # infinity
+ return AdjustedMantissa.new(
+ power2: infinite_power,
+ mantissa: 0,
+ )
+ end
+ AdjustedMantissa.new(power2: power2, mantissa: mantissa)
+ end
+ end
+end
diff --git a/src/float/fast_float/digit_comparison.cr b/src/float/fast_float/digit_comparison.cr
new file mode 100644
index 000000000000..2da4c455bac4
--- /dev/null
+++ b/src/float/fast_float/digit_comparison.cr
@@ -0,0 +1,399 @@
+require "./float_common"
+require "./bigint"
+require "./ascii_number"
+
+module Float::FastFloat
+ # 1e0 to 1e19
+ POWERS_OF_TEN_UINT64 = [
+ 1_u64,
+ 10_u64,
+ 100_u64,
+ 1000_u64,
+ 10000_u64,
+ 100000_u64,
+ 1000000_u64,
+ 10000000_u64,
+ 100000000_u64,
+ 1000000000_u64,
+ 10000000000_u64,
+ 100000000000_u64,
+ 1000000000000_u64,
+ 10000000000000_u64,
+ 100000000000000_u64,
+ 1000000000000000_u64,
+ 10000000000000000_u64,
+ 100000000000000000_u64,
+ 1000000000000000000_u64,
+ 10000000000000000000_u64,
+ ]
+
+ # calculate the exponent, in scientific notation, of the number.
+ # this algorithm is not even close to optimized, but it has no practical
+ # effect on performance: in order to have a faster algorithm, we'd need
+ # to slow down performance for faster algorithms, and this is still fast.
+ def self.scientific_exponent(num : ParsedNumberStringT(UC)) : Int32 forall UC
+ mantissa = num.mantissa
+ exponent = num.exponent.to_i32!
+ while mantissa >= 10000
+ mantissa = mantissa.unsafe_div(10000)
+ exponent &+= 4
+ end
+ while mantissa >= 100
+ mantissa = mantissa.unsafe_div(100)
+ exponent &+= 2
+ end
+ while mantissa >= 10
+ mantissa = mantissa.unsafe_div(10)
+ exponent &+= 1
+ end
+ exponent
+ end
+
+ module BinaryFormat(T, EquivUint)
+ # this converts a native floating-point number to an extended-precision float.
+ def to_extended(value : T) : AdjustedMantissa
+ exponent_mask = self.exponent_mask
+ mantissa_mask = self.mantissa_mask
+ hidden_bit_mask = self.hidden_bit_mask
+
+ bias = mantissa_explicit_bits &- minimum_exponent
+ bits = value.unsafe_as(EquivUint)
+ if bits & exponent_mask == 0
+ # denormal
+ power2 = 1 &- bias
+ mantissa = bits & mantissa_mask
+ else
+ # normal
+ power2 = (bits & exponent_mask).unsafe_shr(mantissa_explicit_bits).to_i32!
+ power2 &-= bias
+ mantissa = (bits & mantissa_mask) | hidden_bit_mask
+ end
+
+ AdjustedMantissa.new(power2: power2, mantissa: mantissa.to_u64!)
+ end
+
+ # get the extended precision value of the halfway point between b and b+u.
+ # we are given a native float that represents b, so we need to adjust it
+ # halfway between b and b+u.
+ def to_extended_halfway(value : T) : AdjustedMantissa
+ am = to_extended(value)
+ am.mantissa = am.mantissa.unsafe_shl(1)
+ am.mantissa &+= 1
+ am.power2 &-= 1
+ am
+ end
+
+ # round an extended-precision float to the nearest machine float.
+ # NOTE(crystal): passes *am* in and out by value
+ def round(am : AdjustedMantissa, & : AdjustedMantissa, Int32 -> AdjustedMantissa) : AdjustedMantissa
+ mantissa_shift = 64 &- mantissa_explicit_bits &- 1
+ if 0 &- am.power2 >= mantissa_shift
+ # have a denormal float
+ shift = 1 &- am.power2
+ am = yield am, {shift, 64}.min
+ # check for round-up: if rounding-nearest carried us to the hidden bit.
+ am.power2 = am.mantissa < 1_u64.unsafe_shl(mantissa_explicit_bits) ? 0 : 1
+ return am
+ end
+
+ # have a normal float, use the default shift.
+ am = yield am, mantissa_shift
+
+ # check for carry
+ if am.mantissa >= 2_u64.unsafe_shl(mantissa_explicit_bits)
+ am.mantissa = 1_u64.unsafe_shl(mantissa_explicit_bits)
+ am.power2 &+= 1
+ end
+
+ # check for infinite: we could have carried to an infinite power
+ am.mantissa &= ~(1_u64.unsafe_shl(mantissa_explicit_bits))
+ if am.power2 >= infinite_power
+ am.power2 = infinite_power
+ am.mantissa = 0
+ end
+
+ am
+ end
+
+ # NOTE(crystal): passes *am* in and out by value
+ def round_nearest_tie_even(am : AdjustedMantissa, shift : Int32, & : Bool, Bool, Bool -> Bool) : AdjustedMantissa
+ mask = shift == 64 ? UInt64::MAX : 1_u64.unsafe_shl(shift) &- 1
+ halfway = shift == 0 ? 0_u64 : 1_u64.unsafe_shl(shift &- 1)
+ truncated_bits = am.mantissa & mask
+ is_above = truncated_bits > halfway
+ is_halfway = truncated_bits == halfway
+
+ # shift digits into position
+ if shift == 64
+ am.mantissa = 0
+ else
+ am.mantissa = am.mantissa.unsafe_shr(shift)
+ end
+ am.power2 &+= shift
+
+ is_odd = am.mantissa.bits_set?(1)
+ am.mantissa &+= (yield is_odd, is_halfway, is_above) ? 1 : 0
+ am
+ end
+
+ # NOTE(crystal): passes *am* in and out by value
+ def round_down(am : AdjustedMantissa, shift : Int32) : AdjustedMantissa
+ if shift == 64
+ am.mantissa = 0
+ else
+ am.mantissa = am.mantissa.unsafe_shr(shift)
+ end
+ am.power2 &+= shift
+ am
+ end
+
+ # NOTE(crystal): returns the new *first* by value
+ def skip_zeros(first : UC*, last : UC*) : UC* forall UC
+ int_cmp_len = FastFloat.int_cmp_len(UC)
+ int_cmp_zeros = FastFloat.int_cmp_zeros(UC)
+
+ val = uninitialized UInt64
+ while last - first >= int_cmp_len
+ first.copy_to(pointerof(val).as(UC*), int_cmp_len)
+ if val != int_cmp_zeros
+ break
+ end
+ first += int_cmp_len
+ end
+ while first != last
+ unless first.value === '0'
+ break
+ end
+ first += 1
+ end
+ first
+ end
+
+ # determine if any non-zero digits were truncated.
+ # all characters must be valid digits.
+ def is_truncated?(first : UC*, last : UC*) : Bool forall UC
+ int_cmp_len = FastFloat.int_cmp_len(UC)
+ int_cmp_zeros = FastFloat.int_cmp_zeros(UC)
+
+ # do 8-bit optimizations, can just compare to 8 literal 0s.
+
+ val = uninitialized UInt64
+ while last - first >= int_cmp_len
+ first.copy_to(pointerof(val).as(UC*), int_cmp_len)
+ if val != int_cmp_zeros
+ return true
+ end
+ first += int_cmp_len
+ end
+ while first != last
+ unless first.value === '0'
+ return true
+ end
+ first += 1
+ end
+ false
+ end
+
+ def is_truncated?(s : Slice(UC)) : Bool forall UC
+ is_truncated?(s.to_unsafe, s.to_unsafe + s.size)
+ end
+
+ macro parse_eight_digits(p, value, counter, count)
+ {{ value }} = {{ value }} &* 100000000 &+ FastFloat.parse_eight_digits_unrolled({{ p }})
+ {{ p }} += 8
+ {{ counter }} &+= 8
+ {{ count }} &+= 8
+ end
+
+ macro parse_one_digit(p, value, counter, count)
+ {{ value }} = {{ value }} &* 10 &+ {{ p }}.value &- '0'.ord
+ {{ p }} += 1
+ {{ counter }} &+= 1
+ {{ count }} &+= 1
+ end
+
+ macro add_native(big, power, value)
+ {{ big }}.value.mul({{ power }})
+ {{ big }}.value.add({{ value }})
+ end
+
+ macro round_up_bigint(big, count)
+ # need to round-up the digits, but need to avoid rounding
+ # ....9999 to ...10000, which could cause a false halfway point.
+ add_native({{ big }}, 10, 1)
+ {{ count }} &+= 1
+ end
+
+ # parse the significant digits into a big integer
+ # NOTE(crystal): returns the new *digits* by value
+ def parse_mantissa(result : Bigint*, num : ParsedNumberStringT(UC), max_digits : Int) : Int forall UC
+ # try to minimize the number of big integer and scalar multiplication.
+ # therefore, try to parse 8 digits at a time, and multiply by the largest
+ # scalar value (9 or 19 digits) for each step.
+ counter = 0
+ digits = 0
+ value = Limb.zero
+ step = {{ Limb == UInt64 ? 19 : 9 }}
+
+ # process all integer digits.
+ p = num.integer.to_unsafe
+ pend = p + num.integer.size
+ p = skip_zeros(p, pend)
+ # process all digits, in increments of step per loop
+ while p != pend
+ while pend - p >= 8 && step &- counter >= 8 && max_digits &- digits >= 8
+ parse_eight_digits(p, value, counter, digits)
+ end
+ while counter < step && p != pend && digits < max_digits
+ parse_one_digit(p, value, counter, digits)
+ end
+ if digits == max_digits
+ # add the temporary value, then check if we've truncated any digits
+ add_native(result, Limb.new!(POWERS_OF_TEN_UINT64.unsafe_fetch(counter)), value)
+ truncated = is_truncated?(p, pend)
+ unless num.fraction.empty?
+ truncated ||= is_truncated?(num.fraction)
+ end
+ if truncated
+ round_up_bigint(result, digits)
+ end
+ return digits
+ else
+ add_native(result, Limb.new!(POWERS_OF_TEN_UINT64.unsafe_fetch(counter)), value)
+ counter = 0
+ value = Limb.zero
+ end
+ end
+
+ # add our fraction digits, if they're available.
+ unless num.fraction.empty?
+ p = num.fraction.to_unsafe
+ pend = p + num.fraction.size
+ if digits == 0
+ p = skip_zeros(p, pend)
+ end
+ # process all digits, in increments of step per loop
+ while p != pend
+ while pend - p >= 8 && step &- counter >= 8 && max_digits &- digits >= 8
+ parse_eight_digits(p, value, counter, digits)
+ end
+ while counter < step && p != pend && digits < max_digits
+ parse_one_digit(p, value, counter, digits)
+ end
+ if digits == max_digits
+ # add the temporary value, then check if we've truncated any digits
+ add_native(result, Limb.new!(POWERS_OF_TEN_UINT64.unsafe_fetch(counter)), value)
+ truncated = is_truncated?(p, pend)
+ if truncated
+ round_up_bigint(result, digits)
+ end
+ return digits
+ else
+ add_native(result, Limb.new!(POWERS_OF_TEN_UINT64.unsafe_fetch(counter)), value)
+ counter = 0
+ value = Limb.zero
+ end
+ end
+ end
+
+ if counter != 0
+ add_native(result, Limb.new!(POWERS_OF_TEN_UINT64.unsafe_fetch(counter)), value)
+ end
+
+ digits
+ end
+
+ def positive_digit_comp(bigmant : Bigint*, exponent : Int32) : AdjustedMantissa
+ bigmant.value.pow10(exponent.to_u32!)
+ mantissa, truncated = bigmant.value.hi64
+ bias = mantissa_explicit_bits &- minimum_exponent
+ power2 = bigmant.value.bit_length &- 64 &+ bias
+ answer = AdjustedMantissa.new(power2: power2, mantissa: mantissa)
+
+ answer = round(answer) do |a, shift|
+ round_nearest_tie_even(a, shift) do |is_odd, is_halfway, is_above|
+ is_above || (is_halfway && truncated) || (is_odd && is_halfway)
+ end
+ end
+
+ answer
+ end
+
+ # the scaling here is quite simple: we have, for the real digits `m * 10^e`,
+ # and for the theoretical digits `n * 2^f`. Since `e` is always negative,
+ # to scale them identically, we do `n * 2^f * 5^-f`, so we now have `m * 2^e`.
+ # we then need to scale by `2^(f- e)`, and then the two significant digits
+ # are of the same magnitude.
+ def negative_digit_comp(bigmant : Bigint*, am : AdjustedMantissa, exponent : Int32) : AdjustedMantissa
+ real_digits = bigmant
+ real_exp = exponent
+
+ # get the value of `b`, rounded down, and get a bigint representation of b+h
+ am_b = round(am) do |a, shift|
+ round_down(a, shift)
+ end
+ b = to_float(false, am_b)
+ theor = to_extended_halfway(b)
+ theor_digits = Bigint.new(theor.mantissa)
+ theor_exp = theor.power2
+
+ # scale real digits and theor digits to be same power.
+ pow2_exp = theor_exp &- real_exp
+ pow5_exp = 0_u32 &- real_exp
+ if pow5_exp != 0
+ theor_digits.pow5(pow5_exp)
+ end
+ if pow2_exp > 0
+ theor_digits.pow2(pow2_exp.to_u32!)
+ elsif pow2_exp < 0
+ real_digits.value.pow2(0_u32 &- pow2_exp)
+ end
+
+ # compare digits, and use it to director rounding
+ ord = real_digits.value.compare(pointerof(theor_digits))
+ answer = round(am) do |a, shift|
+ round_nearest_tie_even(a, shift) do |is_odd, _, _|
+ if ord > 0
+ true
+ elsif ord < 0
+ false
+ else
+ is_odd
+ end
+ end
+ end
+
+ answer
+ end
+
+ # parse the significant digits as a big integer to unambiguously round the
+ # the significant digits. here, we are trying to determine how to round
+ # an extended float representation close to `b+h`, halfway between `b`
+ # (the float rounded-down) and `b+u`, the next positive float. this
+ # algorithm is always correct, and uses one of two approaches. when
+ # the exponent is positive relative to the significant digits (such as
+ # 1234), we create a big-integer representation, get the high 64-bits,
+ # determine if any lower bits are truncated, and use that to direct
+ # rounding. in case of a negative exponent relative to the significant
+ # digits (such as 1.2345), we create a theoretical representation of
+ # `b` as a big-integer type, scaled to the same binary exponent as
+ # the actual digits. we then compare the big integer representations
+ # of both, and use that to direct rounding.
+ def digit_comp(num : ParsedNumberStringT(UC), am : AdjustedMantissa) : AdjustedMantissa forall UC
+ # remove the invalid exponent bias
+ am.power2 &-= INVALID_AM_BIAS
+
+ sci_exp = FastFloat.scientific_exponent(num)
+ max_digits = self.max_digits
+ bigmant = Bigint.new
+ digits = parse_mantissa(pointerof(bigmant), num, max_digits)
+ # can't underflow, since digits is at most max_digits.
+ exponent = sci_exp &+ 1 &- digits
+ if exponent >= 0
+ positive_digit_comp(pointerof(bigmant), exponent)
+ else
+ negative_digit_comp(pointerof(bigmant), am, exponent)
+ end
+ end
+ end
+end
diff --git a/src/float/fast_float/fast_table.cr b/src/float/fast_float/fast_table.cr
new file mode 100644
index 000000000000..a2c2b2e9d1c9
--- /dev/null
+++ b/src/float/fast_float/fast_table.cr
@@ -0,0 +1,695 @@
+module Float::FastFloat
+ # When mapping numbers from decimal to binary,
+ # we go from w * 10^q to m * 2^p but we have
+ # 10^q = 5^q * 2^q, so effectively
+ # we are trying to match
+ # w * 2^q * 5^q to m * 2^p. Thus the powers of two
+ # are not a concern since they can be represented
+ # exactly using the binary notation, only the powers of five
+ # affect the binary significand.
+
+ # The smallest non-zero float (binary64) is 2^-1074.
+ # We take as input numbers of the form w x 10^q where w < 2^64.
+ # We have that w * 10^-343 < 2^(64-344) 5^-343 < 2^-1076.
+ # However, we have that
+ # (2^64-1) * 10^-342 = (2^64-1) * 2^-342 * 5^-342 > 2^-1074.
+ # Thus it is possible for a number of the form w * 10^-342 where
+ # w is a 64-bit value to be a non-zero floating-point number.
+ #
+ # Any number of form w * 10^309 where w>= 1 is going to be
+ # infinite in binary64 so we never need to worry about powers
+ # of 5 greater than 308.
+ module Powers
+ SMALLEST_POWER_OF_FIVE = -342
+ LARGEST_POWER_OF_FIVE = 308
+ NUMBER_OF_ENTRIES = {{ 2 * (LARGEST_POWER_OF_FIVE - SMALLEST_POWER_OF_FIVE + 1) }}
+
+ # TODO: this is needed to avoid generating lots of allocas
+ # in LLVM, which makes LLVM really slow. The compiler should
+ # try to avoid/reuse temporary allocas.
+ # Explanation: https://github.com/crystal-lang/crystal/issues/4516#issuecomment-306226171
+ private def self.put(array, value) : Nil
+ array << value
+ end
+
+ # Powers of five from 5^-342 all the way to 5^308 rounded toward one.
+ # NOTE(crystal): this is very similar to
+ # `Float::Printer::Dragonbox::ImplInfo_Float64::CACHE`, except the endpoints
+ # are different and the rounding is in a different direction
+ POWER_OF_FIVE_128 = begin
+ array = Array(UInt64).new(NUMBER_OF_ENTRIES)
+ put(array, 0xeef453d6923bd65a_u64); put(array, 0x113faa2906a13b3f_u64)
+ put(array, 0x9558b4661b6565f8_u64); put(array, 0x4ac7ca59a424c507_u64)
+ put(array, 0xbaaee17fa23ebf76_u64); put(array, 0x5d79bcf00d2df649_u64)
+ put(array, 0xe95a99df8ace6f53_u64); put(array, 0xf4d82c2c107973dc_u64)
+ put(array, 0x91d8a02bb6c10594_u64); put(array, 0x79071b9b8a4be869_u64)
+ put(array, 0xb64ec836a47146f9_u64); put(array, 0x9748e2826cdee284_u64)
+ put(array, 0xe3e27a444d8d98b7_u64); put(array, 0xfd1b1b2308169b25_u64)
+ put(array, 0x8e6d8c6ab0787f72_u64); put(array, 0xfe30f0f5e50e20f7_u64)
+ put(array, 0xb208ef855c969f4f_u64); put(array, 0xbdbd2d335e51a935_u64)
+ put(array, 0xde8b2b66b3bc4723_u64); put(array, 0xad2c788035e61382_u64)
+ put(array, 0x8b16fb203055ac76_u64); put(array, 0x4c3bcb5021afcc31_u64)
+ put(array, 0xaddcb9e83c6b1793_u64); put(array, 0xdf4abe242a1bbf3d_u64)
+ put(array, 0xd953e8624b85dd78_u64); put(array, 0xd71d6dad34a2af0d_u64)
+ put(array, 0x87d4713d6f33aa6b_u64); put(array, 0x8672648c40e5ad68_u64)
+ put(array, 0xa9c98d8ccb009506_u64); put(array, 0x680efdaf511f18c2_u64)
+ put(array, 0xd43bf0effdc0ba48_u64); put(array, 0x212bd1b2566def2_u64)
+ put(array, 0x84a57695fe98746d_u64); put(array, 0x14bb630f7604b57_u64)
+ put(array, 0xa5ced43b7e3e9188_u64); put(array, 0x419ea3bd35385e2d_u64)
+ put(array, 0xcf42894a5dce35ea_u64); put(array, 0x52064cac828675b9_u64)
+ put(array, 0x818995ce7aa0e1b2_u64); put(array, 0x7343efebd1940993_u64)
+ put(array, 0xa1ebfb4219491a1f_u64); put(array, 0x1014ebe6c5f90bf8_u64)
+ put(array, 0xca66fa129f9b60a6_u64); put(array, 0xd41a26e077774ef6_u64)
+ put(array, 0xfd00b897478238d0_u64); put(array, 0x8920b098955522b4_u64)
+ put(array, 0x9e20735e8cb16382_u64); put(array, 0x55b46e5f5d5535b0_u64)
+ put(array, 0xc5a890362fddbc62_u64); put(array, 0xeb2189f734aa831d_u64)
+ put(array, 0xf712b443bbd52b7b_u64); put(array, 0xa5e9ec7501d523e4_u64)
+ put(array, 0x9a6bb0aa55653b2d_u64); put(array, 0x47b233c92125366e_u64)
+ put(array, 0xc1069cd4eabe89f8_u64); put(array, 0x999ec0bb696e840a_u64)
+ put(array, 0xf148440a256e2c76_u64); put(array, 0xc00670ea43ca250d_u64)
+ put(array, 0x96cd2a865764dbca_u64); put(array, 0x380406926a5e5728_u64)
+ put(array, 0xbc807527ed3e12bc_u64); put(array, 0xc605083704f5ecf2_u64)
+ put(array, 0xeba09271e88d976b_u64); put(array, 0xf7864a44c633682e_u64)
+ put(array, 0x93445b8731587ea3_u64); put(array, 0x7ab3ee6afbe0211d_u64)
+ put(array, 0xb8157268fdae9e4c_u64); put(array, 0x5960ea05bad82964_u64)
+ put(array, 0xe61acf033d1a45df_u64); put(array, 0x6fb92487298e33bd_u64)
+ put(array, 0x8fd0c16206306bab_u64); put(array, 0xa5d3b6d479f8e056_u64)
+ put(array, 0xb3c4f1ba87bc8696_u64); put(array, 0x8f48a4899877186c_u64)
+ put(array, 0xe0b62e2929aba83c_u64); put(array, 0x331acdabfe94de87_u64)
+ put(array, 0x8c71dcd9ba0b4925_u64); put(array, 0x9ff0c08b7f1d0b14_u64)
+ put(array, 0xaf8e5410288e1b6f_u64); put(array, 0x7ecf0ae5ee44dd9_u64)
+ put(array, 0xdb71e91432b1a24a_u64); put(array, 0xc9e82cd9f69d6150_u64)
+ put(array, 0x892731ac9faf056e_u64); put(array, 0xbe311c083a225cd2_u64)
+ put(array, 0xab70fe17c79ac6ca_u64); put(array, 0x6dbd630a48aaf406_u64)
+ put(array, 0xd64d3d9db981787d_u64); put(array, 0x92cbbccdad5b108_u64)
+ put(array, 0x85f0468293f0eb4e_u64); put(array, 0x25bbf56008c58ea5_u64)
+ put(array, 0xa76c582338ed2621_u64); put(array, 0xaf2af2b80af6f24e_u64)
+ put(array, 0xd1476e2c07286faa_u64); put(array, 0x1af5af660db4aee1_u64)
+ put(array, 0x82cca4db847945ca_u64); put(array, 0x50d98d9fc890ed4d_u64)
+ put(array, 0xa37fce126597973c_u64); put(array, 0xe50ff107bab528a0_u64)
+ put(array, 0xcc5fc196fefd7d0c_u64); put(array, 0x1e53ed49a96272c8_u64)
+ put(array, 0xff77b1fcbebcdc4f_u64); put(array, 0x25e8e89c13bb0f7a_u64)
+ put(array, 0x9faacf3df73609b1_u64); put(array, 0x77b191618c54e9ac_u64)
+ put(array, 0xc795830d75038c1d_u64); put(array, 0xd59df5b9ef6a2417_u64)
+ put(array, 0xf97ae3d0d2446f25_u64); put(array, 0x4b0573286b44ad1d_u64)
+ put(array, 0x9becce62836ac577_u64); put(array, 0x4ee367f9430aec32_u64)
+ put(array, 0xc2e801fb244576d5_u64); put(array, 0x229c41f793cda73f_u64)
+ put(array, 0xf3a20279ed56d48a_u64); put(array, 0x6b43527578c1110f_u64)
+ put(array, 0x9845418c345644d6_u64); put(array, 0x830a13896b78aaa9_u64)
+ put(array, 0xbe5691ef416bd60c_u64); put(array, 0x23cc986bc656d553_u64)
+ put(array, 0xedec366b11c6cb8f_u64); put(array, 0x2cbfbe86b7ec8aa8_u64)
+ put(array, 0x94b3a202eb1c3f39_u64); put(array, 0x7bf7d71432f3d6a9_u64)
+ put(array, 0xb9e08a83a5e34f07_u64); put(array, 0xdaf5ccd93fb0cc53_u64)
+ put(array, 0xe858ad248f5c22c9_u64); put(array, 0xd1b3400f8f9cff68_u64)
+ put(array, 0x91376c36d99995be_u64); put(array, 0x23100809b9c21fa1_u64)
+ put(array, 0xb58547448ffffb2d_u64); put(array, 0xabd40a0c2832a78a_u64)
+ put(array, 0xe2e69915b3fff9f9_u64); put(array, 0x16c90c8f323f516c_u64)
+ put(array, 0x8dd01fad907ffc3b_u64); put(array, 0xae3da7d97f6792e3_u64)
+ put(array, 0xb1442798f49ffb4a_u64); put(array, 0x99cd11cfdf41779c_u64)
+ put(array, 0xdd95317f31c7fa1d_u64); put(array, 0x40405643d711d583_u64)
+ put(array, 0x8a7d3eef7f1cfc52_u64); put(array, 0x482835ea666b2572_u64)
+ put(array, 0xad1c8eab5ee43b66_u64); put(array, 0xda3243650005eecf_u64)
+ put(array, 0xd863b256369d4a40_u64); put(array, 0x90bed43e40076a82_u64)
+ put(array, 0x873e4f75e2224e68_u64); put(array, 0x5a7744a6e804a291_u64)
+ put(array, 0xa90de3535aaae202_u64); put(array, 0x711515d0a205cb36_u64)
+ put(array, 0xd3515c2831559a83_u64); put(array, 0xd5a5b44ca873e03_u64)
+ put(array, 0x8412d9991ed58091_u64); put(array, 0xe858790afe9486c2_u64)
+ put(array, 0xa5178fff668ae0b6_u64); put(array, 0x626e974dbe39a872_u64)
+ put(array, 0xce5d73ff402d98e3_u64); put(array, 0xfb0a3d212dc8128f_u64)
+ put(array, 0x80fa687f881c7f8e_u64); put(array, 0x7ce66634bc9d0b99_u64)
+ put(array, 0xa139029f6a239f72_u64); put(array, 0x1c1fffc1ebc44e80_u64)
+ put(array, 0xc987434744ac874e_u64); put(array, 0xa327ffb266b56220_u64)
+ put(array, 0xfbe9141915d7a922_u64); put(array, 0x4bf1ff9f0062baa8_u64)
+ put(array, 0x9d71ac8fada6c9b5_u64); put(array, 0x6f773fc3603db4a9_u64)
+ put(array, 0xc4ce17b399107c22_u64); put(array, 0xcb550fb4384d21d3_u64)
+ put(array, 0xf6019da07f549b2b_u64); put(array, 0x7e2a53a146606a48_u64)
+ put(array, 0x99c102844f94e0fb_u64); put(array, 0x2eda7444cbfc426d_u64)
+ put(array, 0xc0314325637a1939_u64); put(array, 0xfa911155fefb5308_u64)
+ put(array, 0xf03d93eebc589f88_u64); put(array, 0x793555ab7eba27ca_u64)
+ put(array, 0x96267c7535b763b5_u64); put(array, 0x4bc1558b2f3458de_u64)
+ put(array, 0xbbb01b9283253ca2_u64); put(array, 0x9eb1aaedfb016f16_u64)
+ put(array, 0xea9c227723ee8bcb_u64); put(array, 0x465e15a979c1cadc_u64)
+ put(array, 0x92a1958a7675175f_u64); put(array, 0xbfacd89ec191ec9_u64)
+ put(array, 0xb749faed14125d36_u64); put(array, 0xcef980ec671f667b_u64)
+ put(array, 0xe51c79a85916f484_u64); put(array, 0x82b7e12780e7401a_u64)
+ put(array, 0x8f31cc0937ae58d2_u64); put(array, 0xd1b2ecb8b0908810_u64)
+ put(array, 0xb2fe3f0b8599ef07_u64); put(array, 0x861fa7e6dcb4aa15_u64)
+ put(array, 0xdfbdcece67006ac9_u64); put(array, 0x67a791e093e1d49a_u64)
+ put(array, 0x8bd6a141006042bd_u64); put(array, 0xe0c8bb2c5c6d24e0_u64)
+ put(array, 0xaecc49914078536d_u64); put(array, 0x58fae9f773886e18_u64)
+ put(array, 0xda7f5bf590966848_u64); put(array, 0xaf39a475506a899e_u64)
+ put(array, 0x888f99797a5e012d_u64); put(array, 0x6d8406c952429603_u64)
+ put(array, 0xaab37fd7d8f58178_u64); put(array, 0xc8e5087ba6d33b83_u64)
+ put(array, 0xd5605fcdcf32e1d6_u64); put(array, 0xfb1e4a9a90880a64_u64)
+ put(array, 0x855c3be0a17fcd26_u64); put(array, 0x5cf2eea09a55067f_u64)
+ put(array, 0xa6b34ad8c9dfc06f_u64); put(array, 0xf42faa48c0ea481e_u64)
+ put(array, 0xd0601d8efc57b08b_u64); put(array, 0xf13b94daf124da26_u64)
+ put(array, 0x823c12795db6ce57_u64); put(array, 0x76c53d08d6b70858_u64)
+ put(array, 0xa2cb1717b52481ed_u64); put(array, 0x54768c4b0c64ca6e_u64)
+ put(array, 0xcb7ddcdda26da268_u64); put(array, 0xa9942f5dcf7dfd09_u64)
+ put(array, 0xfe5d54150b090b02_u64); put(array, 0xd3f93b35435d7c4c_u64)
+ put(array, 0x9efa548d26e5a6e1_u64); put(array, 0xc47bc5014a1a6daf_u64)
+ put(array, 0xc6b8e9b0709f109a_u64); put(array, 0x359ab6419ca1091b_u64)
+ put(array, 0xf867241c8cc6d4c0_u64); put(array, 0xc30163d203c94b62_u64)
+ put(array, 0x9b407691d7fc44f8_u64); put(array, 0x79e0de63425dcf1d_u64)
+ put(array, 0xc21094364dfb5636_u64); put(array, 0x985915fc12f542e4_u64)
+ put(array, 0xf294b943e17a2bc4_u64); put(array, 0x3e6f5b7b17b2939d_u64)
+ put(array, 0x979cf3ca6cec5b5a_u64); put(array, 0xa705992ceecf9c42_u64)
+ put(array, 0xbd8430bd08277231_u64); put(array, 0x50c6ff782a838353_u64)
+ put(array, 0xece53cec4a314ebd_u64); put(array, 0xa4f8bf5635246428_u64)
+ put(array, 0x940f4613ae5ed136_u64); put(array, 0x871b7795e136be99_u64)
+ put(array, 0xb913179899f68584_u64); put(array, 0x28e2557b59846e3f_u64)
+ put(array, 0xe757dd7ec07426e5_u64); put(array, 0x331aeada2fe589cf_u64)
+ put(array, 0x9096ea6f3848984f_u64); put(array, 0x3ff0d2c85def7621_u64)
+ put(array, 0xb4bca50b065abe63_u64); put(array, 0xfed077a756b53a9_u64)
+ put(array, 0xe1ebce4dc7f16dfb_u64); put(array, 0xd3e8495912c62894_u64)
+ put(array, 0x8d3360f09cf6e4bd_u64); put(array, 0x64712dd7abbbd95c_u64)
+ put(array, 0xb080392cc4349dec_u64); put(array, 0xbd8d794d96aacfb3_u64)
+ put(array, 0xdca04777f541c567_u64); put(array, 0xecf0d7a0fc5583a0_u64)
+ put(array, 0x89e42caaf9491b60_u64); put(array, 0xf41686c49db57244_u64)
+ put(array, 0xac5d37d5b79b6239_u64); put(array, 0x311c2875c522ced5_u64)
+ put(array, 0xd77485cb25823ac7_u64); put(array, 0x7d633293366b828b_u64)
+ put(array, 0x86a8d39ef77164bc_u64); put(array, 0xae5dff9c02033197_u64)
+ put(array, 0xa8530886b54dbdeb_u64); put(array, 0xd9f57f830283fdfc_u64)
+ put(array, 0xd267caa862a12d66_u64); put(array, 0xd072df63c324fd7b_u64)
+ put(array, 0x8380dea93da4bc60_u64); put(array, 0x4247cb9e59f71e6d_u64)
+ put(array, 0xa46116538d0deb78_u64); put(array, 0x52d9be85f074e608_u64)
+ put(array, 0xcd795be870516656_u64); put(array, 0x67902e276c921f8b_u64)
+ put(array, 0x806bd9714632dff6_u64); put(array, 0xba1cd8a3db53b6_u64)
+ put(array, 0xa086cfcd97bf97f3_u64); put(array, 0x80e8a40eccd228a4_u64)
+ put(array, 0xc8a883c0fdaf7df0_u64); put(array, 0x6122cd128006b2cd_u64)
+ put(array, 0xfad2a4b13d1b5d6c_u64); put(array, 0x796b805720085f81_u64)
+ put(array, 0x9cc3a6eec6311a63_u64); put(array, 0xcbe3303674053bb0_u64)
+ put(array, 0xc3f490aa77bd60fc_u64); put(array, 0xbedbfc4411068a9c_u64)
+ put(array, 0xf4f1b4d515acb93b_u64); put(array, 0xee92fb5515482d44_u64)
+ put(array, 0x991711052d8bf3c5_u64); put(array, 0x751bdd152d4d1c4a_u64)
+ put(array, 0xbf5cd54678eef0b6_u64); put(array, 0xd262d45a78a0635d_u64)
+ put(array, 0xef340a98172aace4_u64); put(array, 0x86fb897116c87c34_u64)
+ put(array, 0x9580869f0e7aac0e_u64); put(array, 0xd45d35e6ae3d4da0_u64)
+ put(array, 0xbae0a846d2195712_u64); put(array, 0x8974836059cca109_u64)
+ put(array, 0xe998d258869facd7_u64); put(array, 0x2bd1a438703fc94b_u64)
+ put(array, 0x91ff83775423cc06_u64); put(array, 0x7b6306a34627ddcf_u64)
+ put(array, 0xb67f6455292cbf08_u64); put(array, 0x1a3bc84c17b1d542_u64)
+ put(array, 0xe41f3d6a7377eeca_u64); put(array, 0x20caba5f1d9e4a93_u64)
+ put(array, 0x8e938662882af53e_u64); put(array, 0x547eb47b7282ee9c_u64)
+ put(array, 0xb23867fb2a35b28d_u64); put(array, 0xe99e619a4f23aa43_u64)
+ put(array, 0xdec681f9f4c31f31_u64); put(array, 0x6405fa00e2ec94d4_u64)
+ put(array, 0x8b3c113c38f9f37e_u64); put(array, 0xde83bc408dd3dd04_u64)
+ put(array, 0xae0b158b4738705e_u64); put(array, 0x9624ab50b148d445_u64)
+ put(array, 0xd98ddaee19068c76_u64); put(array, 0x3badd624dd9b0957_u64)
+ put(array, 0x87f8a8d4cfa417c9_u64); put(array, 0xe54ca5d70a80e5d6_u64)
+ put(array, 0xa9f6d30a038d1dbc_u64); put(array, 0x5e9fcf4ccd211f4c_u64)
+ put(array, 0xd47487cc8470652b_u64); put(array, 0x7647c3200069671f_u64)
+ put(array, 0x84c8d4dfd2c63f3b_u64); put(array, 0x29ecd9f40041e073_u64)
+ put(array, 0xa5fb0a17c777cf09_u64); put(array, 0xf468107100525890_u64)
+ put(array, 0xcf79cc9db955c2cc_u64); put(array, 0x7182148d4066eeb4_u64)
+ put(array, 0x81ac1fe293d599bf_u64); put(array, 0xc6f14cd848405530_u64)
+ put(array, 0xa21727db38cb002f_u64); put(array, 0xb8ada00e5a506a7c_u64)
+ put(array, 0xca9cf1d206fdc03b_u64); put(array, 0xa6d90811f0e4851c_u64)
+ put(array, 0xfd442e4688bd304a_u64); put(array, 0x908f4a166d1da663_u64)
+ put(array, 0x9e4a9cec15763e2e_u64); put(array, 0x9a598e4e043287fe_u64)
+ put(array, 0xc5dd44271ad3cdba_u64); put(array, 0x40eff1e1853f29fd_u64)
+ put(array, 0xf7549530e188c128_u64); put(array, 0xd12bee59e68ef47c_u64)
+ put(array, 0x9a94dd3e8cf578b9_u64); put(array, 0x82bb74f8301958ce_u64)
+ put(array, 0xc13a148e3032d6e7_u64); put(array, 0xe36a52363c1faf01_u64)
+ put(array, 0xf18899b1bc3f8ca1_u64); put(array, 0xdc44e6c3cb279ac1_u64)
+ put(array, 0x96f5600f15a7b7e5_u64); put(array, 0x29ab103a5ef8c0b9_u64)
+ put(array, 0xbcb2b812db11a5de_u64); put(array, 0x7415d448f6b6f0e7_u64)
+ put(array, 0xebdf661791d60f56_u64); put(array, 0x111b495b3464ad21_u64)
+ put(array, 0x936b9fcebb25c995_u64); put(array, 0xcab10dd900beec34_u64)
+ put(array, 0xb84687c269ef3bfb_u64); put(array, 0x3d5d514f40eea742_u64)
+ put(array, 0xe65829b3046b0afa_u64); put(array, 0xcb4a5a3112a5112_u64)
+ put(array, 0x8ff71a0fe2c2e6dc_u64); put(array, 0x47f0e785eaba72ab_u64)
+ put(array, 0xb3f4e093db73a093_u64); put(array, 0x59ed216765690f56_u64)
+ put(array, 0xe0f218b8d25088b8_u64); put(array, 0x306869c13ec3532c_u64)
+ put(array, 0x8c974f7383725573_u64); put(array, 0x1e414218c73a13fb_u64)
+ put(array, 0xafbd2350644eeacf_u64); put(array, 0xe5d1929ef90898fa_u64)
+ put(array, 0xdbac6c247d62a583_u64); put(array, 0xdf45f746b74abf39_u64)
+ put(array, 0x894bc396ce5da772_u64); put(array, 0x6b8bba8c328eb783_u64)
+ put(array, 0xab9eb47c81f5114f_u64); put(array, 0x66ea92f3f326564_u64)
+ put(array, 0xd686619ba27255a2_u64); put(array, 0xc80a537b0efefebd_u64)
+ put(array, 0x8613fd0145877585_u64); put(array, 0xbd06742ce95f5f36_u64)
+ put(array, 0xa798fc4196e952e7_u64); put(array, 0x2c48113823b73704_u64)
+ put(array, 0xd17f3b51fca3a7a0_u64); put(array, 0xf75a15862ca504c5_u64)
+ put(array, 0x82ef85133de648c4_u64); put(array, 0x9a984d73dbe722fb_u64)
+ put(array, 0xa3ab66580d5fdaf5_u64); put(array, 0xc13e60d0d2e0ebba_u64)
+ put(array, 0xcc963fee10b7d1b3_u64); put(array, 0x318df905079926a8_u64)
+ put(array, 0xffbbcfe994e5c61f_u64); put(array, 0xfdf17746497f7052_u64)
+ put(array, 0x9fd561f1fd0f9bd3_u64); put(array, 0xfeb6ea8bedefa633_u64)
+ put(array, 0xc7caba6e7c5382c8_u64); put(array, 0xfe64a52ee96b8fc0_u64)
+ put(array, 0xf9bd690a1b68637b_u64); put(array, 0x3dfdce7aa3c673b0_u64)
+ put(array, 0x9c1661a651213e2d_u64); put(array, 0x6bea10ca65c084e_u64)
+ put(array, 0xc31bfa0fe5698db8_u64); put(array, 0x486e494fcff30a62_u64)
+ put(array, 0xf3e2f893dec3f126_u64); put(array, 0x5a89dba3c3efccfa_u64)
+ put(array, 0x986ddb5c6b3a76b7_u64); put(array, 0xf89629465a75e01c_u64)
+ put(array, 0xbe89523386091465_u64); put(array, 0xf6bbb397f1135823_u64)
+ put(array, 0xee2ba6c0678b597f_u64); put(array, 0x746aa07ded582e2c_u64)
+ put(array, 0x94db483840b717ef_u64); put(array, 0xa8c2a44eb4571cdc_u64)
+ put(array, 0xba121a4650e4ddeb_u64); put(array, 0x92f34d62616ce413_u64)
+ put(array, 0xe896a0d7e51e1566_u64); put(array, 0x77b020baf9c81d17_u64)
+ put(array, 0x915e2486ef32cd60_u64); put(array, 0xace1474dc1d122e_u64)
+ put(array, 0xb5b5ada8aaff80b8_u64); put(array, 0xd819992132456ba_u64)
+ put(array, 0xe3231912d5bf60e6_u64); put(array, 0x10e1fff697ed6c69_u64)
+ put(array, 0x8df5efabc5979c8f_u64); put(array, 0xca8d3ffa1ef463c1_u64)
+ put(array, 0xb1736b96b6fd83b3_u64); put(array, 0xbd308ff8a6b17cb2_u64)
+ put(array, 0xddd0467c64bce4a0_u64); put(array, 0xac7cb3f6d05ddbde_u64)
+ put(array, 0x8aa22c0dbef60ee4_u64); put(array, 0x6bcdf07a423aa96b_u64)
+ put(array, 0xad4ab7112eb3929d_u64); put(array, 0x86c16c98d2c953c6_u64)
+ put(array, 0xd89d64d57a607744_u64); put(array, 0xe871c7bf077ba8b7_u64)
+ put(array, 0x87625f056c7c4a8b_u64); put(array, 0x11471cd764ad4972_u64)
+ put(array, 0xa93af6c6c79b5d2d_u64); put(array, 0xd598e40d3dd89bcf_u64)
+ put(array, 0xd389b47879823479_u64); put(array, 0x4aff1d108d4ec2c3_u64)
+ put(array, 0x843610cb4bf160cb_u64); put(array, 0xcedf722a585139ba_u64)
+ put(array, 0xa54394fe1eedb8fe_u64); put(array, 0xc2974eb4ee658828_u64)
+ put(array, 0xce947a3da6a9273e_u64); put(array, 0x733d226229feea32_u64)
+ put(array, 0x811ccc668829b887_u64); put(array, 0x806357d5a3f525f_u64)
+ put(array, 0xa163ff802a3426a8_u64); put(array, 0xca07c2dcb0cf26f7_u64)
+ put(array, 0xc9bcff6034c13052_u64); put(array, 0xfc89b393dd02f0b5_u64)
+ put(array, 0xfc2c3f3841f17c67_u64); put(array, 0xbbac2078d443ace2_u64)
+ put(array, 0x9d9ba7832936edc0_u64); put(array, 0xd54b944b84aa4c0d_u64)
+ put(array, 0xc5029163f384a931_u64); put(array, 0xa9e795e65d4df11_u64)
+ put(array, 0xf64335bcf065d37d_u64); put(array, 0x4d4617b5ff4a16d5_u64)
+ put(array, 0x99ea0196163fa42e_u64); put(array, 0x504bced1bf8e4e45_u64)
+ put(array, 0xc06481fb9bcf8d39_u64); put(array, 0xe45ec2862f71e1d6_u64)
+ put(array, 0xf07da27a82c37088_u64); put(array, 0x5d767327bb4e5a4c_u64)
+ put(array, 0x964e858c91ba2655_u64); put(array, 0x3a6a07f8d510f86f_u64)
+ put(array, 0xbbe226efb628afea_u64); put(array, 0x890489f70a55368b_u64)
+ put(array, 0xeadab0aba3b2dbe5_u64); put(array, 0x2b45ac74ccea842e_u64)
+ put(array, 0x92c8ae6b464fc96f_u64); put(array, 0x3b0b8bc90012929d_u64)
+ put(array, 0xb77ada0617e3bbcb_u64); put(array, 0x9ce6ebb40173744_u64)
+ put(array, 0xe55990879ddcaabd_u64); put(array, 0xcc420a6a101d0515_u64)
+ put(array, 0x8f57fa54c2a9eab6_u64); put(array, 0x9fa946824a12232d_u64)
+ put(array, 0xb32df8e9f3546564_u64); put(array, 0x47939822dc96abf9_u64)
+ put(array, 0xdff9772470297ebd_u64); put(array, 0x59787e2b93bc56f7_u64)
+ put(array, 0x8bfbea76c619ef36_u64); put(array, 0x57eb4edb3c55b65a_u64)
+ put(array, 0xaefae51477a06b03_u64); put(array, 0xede622920b6b23f1_u64)
+ put(array, 0xdab99e59958885c4_u64); put(array, 0xe95fab368e45eced_u64)
+ put(array, 0x88b402f7fd75539b_u64); put(array, 0x11dbcb0218ebb414_u64)
+ put(array, 0xaae103b5fcd2a881_u64); put(array, 0xd652bdc29f26a119_u64)
+ put(array, 0xd59944a37c0752a2_u64); put(array, 0x4be76d3346f0495f_u64)
+ put(array, 0x857fcae62d8493a5_u64); put(array, 0x6f70a4400c562ddb_u64)
+ put(array, 0xa6dfbd9fb8e5b88e_u64); put(array, 0xcb4ccd500f6bb952_u64)
+ put(array, 0xd097ad07a71f26b2_u64); put(array, 0x7e2000a41346a7a7_u64)
+ put(array, 0x825ecc24c873782f_u64); put(array, 0x8ed400668c0c28c8_u64)
+ put(array, 0xa2f67f2dfa90563b_u64); put(array, 0x728900802f0f32fa_u64)
+ put(array, 0xcbb41ef979346bca_u64); put(array, 0x4f2b40a03ad2ffb9_u64)
+ put(array, 0xfea126b7d78186bc_u64); put(array, 0xe2f610c84987bfa8_u64)
+ put(array, 0x9f24b832e6b0f436_u64); put(array, 0xdd9ca7d2df4d7c9_u64)
+ put(array, 0xc6ede63fa05d3143_u64); put(array, 0x91503d1c79720dbb_u64)
+ put(array, 0xf8a95fcf88747d94_u64); put(array, 0x75a44c6397ce912a_u64)
+ put(array, 0x9b69dbe1b548ce7c_u64); put(array, 0xc986afbe3ee11aba_u64)
+ put(array, 0xc24452da229b021b_u64); put(array, 0xfbe85badce996168_u64)
+ put(array, 0xf2d56790ab41c2a2_u64); put(array, 0xfae27299423fb9c3_u64)
+ put(array, 0x97c560ba6b0919a5_u64); put(array, 0xdccd879fc967d41a_u64)
+ put(array, 0xbdb6b8e905cb600f_u64); put(array, 0x5400e987bbc1c920_u64)
+ put(array, 0xed246723473e3813_u64); put(array, 0x290123e9aab23b68_u64)
+ put(array, 0x9436c0760c86e30b_u64); put(array, 0xf9a0b6720aaf6521_u64)
+ put(array, 0xb94470938fa89bce_u64); put(array, 0xf808e40e8d5b3e69_u64)
+ put(array, 0xe7958cb87392c2c2_u64); put(array, 0xb60b1d1230b20e04_u64)
+ put(array, 0x90bd77f3483bb9b9_u64); put(array, 0xb1c6f22b5e6f48c2_u64)
+ put(array, 0xb4ecd5f01a4aa828_u64); put(array, 0x1e38aeb6360b1af3_u64)
+ put(array, 0xe2280b6c20dd5232_u64); put(array, 0x25c6da63c38de1b0_u64)
+ put(array, 0x8d590723948a535f_u64); put(array, 0x579c487e5a38ad0e_u64)
+ put(array, 0xb0af48ec79ace837_u64); put(array, 0x2d835a9df0c6d851_u64)
+ put(array, 0xdcdb1b2798182244_u64); put(array, 0xf8e431456cf88e65_u64)
+ put(array, 0x8a08f0f8bf0f156b_u64); put(array, 0x1b8e9ecb641b58ff_u64)
+ put(array, 0xac8b2d36eed2dac5_u64); put(array, 0xe272467e3d222f3f_u64)
+ put(array, 0xd7adf884aa879177_u64); put(array, 0x5b0ed81dcc6abb0f_u64)
+ put(array, 0x86ccbb52ea94baea_u64); put(array, 0x98e947129fc2b4e9_u64)
+ put(array, 0xa87fea27a539e9a5_u64); put(array, 0x3f2398d747b36224_u64)
+ put(array, 0xd29fe4b18e88640e_u64); put(array, 0x8eec7f0d19a03aad_u64)
+ put(array, 0x83a3eeeef9153e89_u64); put(array, 0x1953cf68300424ac_u64)
+ put(array, 0xa48ceaaab75a8e2b_u64); put(array, 0x5fa8c3423c052dd7_u64)
+ put(array, 0xcdb02555653131b6_u64); put(array, 0x3792f412cb06794d_u64)
+ put(array, 0x808e17555f3ebf11_u64); put(array, 0xe2bbd88bbee40bd0_u64)
+ put(array, 0xa0b19d2ab70e6ed6_u64); put(array, 0x5b6aceaeae9d0ec4_u64)
+ put(array, 0xc8de047564d20a8b_u64); put(array, 0xf245825a5a445275_u64)
+ put(array, 0xfb158592be068d2e_u64); put(array, 0xeed6e2f0f0d56712_u64)
+ put(array, 0x9ced737bb6c4183d_u64); put(array, 0x55464dd69685606b_u64)
+ put(array, 0xc428d05aa4751e4c_u64); put(array, 0xaa97e14c3c26b886_u64)
+ put(array, 0xf53304714d9265df_u64); put(array, 0xd53dd99f4b3066a8_u64)
+ put(array, 0x993fe2c6d07b7fab_u64); put(array, 0xe546a8038efe4029_u64)
+ put(array, 0xbf8fdb78849a5f96_u64); put(array, 0xde98520472bdd033_u64)
+ put(array, 0xef73d256a5c0f77c_u64); put(array, 0x963e66858f6d4440_u64)
+ put(array, 0x95a8637627989aad_u64); put(array, 0xdde7001379a44aa8_u64)
+ put(array, 0xbb127c53b17ec159_u64); put(array, 0x5560c018580d5d52_u64)
+ put(array, 0xe9d71b689dde71af_u64); put(array, 0xaab8f01e6e10b4a6_u64)
+ put(array, 0x9226712162ab070d_u64); put(array, 0xcab3961304ca70e8_u64)
+ put(array, 0xb6b00d69bb55c8d1_u64); put(array, 0x3d607b97c5fd0d22_u64)
+ put(array, 0xe45c10c42a2b3b05_u64); put(array, 0x8cb89a7db77c506a_u64)
+ put(array, 0x8eb98a7a9a5b04e3_u64); put(array, 0x77f3608e92adb242_u64)
+ put(array, 0xb267ed1940f1c61c_u64); put(array, 0x55f038b237591ed3_u64)
+ put(array, 0xdf01e85f912e37a3_u64); put(array, 0x6b6c46dec52f6688_u64)
+ put(array, 0x8b61313bbabce2c6_u64); put(array, 0x2323ac4b3b3da015_u64)
+ put(array, 0xae397d8aa96c1b77_u64); put(array, 0xabec975e0a0d081a_u64)
+ put(array, 0xd9c7dced53c72255_u64); put(array, 0x96e7bd358c904a21_u64)
+ put(array, 0x881cea14545c7575_u64); put(array, 0x7e50d64177da2e54_u64)
+ put(array, 0xaa242499697392d2_u64); put(array, 0xdde50bd1d5d0b9e9_u64)
+ put(array, 0xd4ad2dbfc3d07787_u64); put(array, 0x955e4ec64b44e864_u64)
+ put(array, 0x84ec3c97da624ab4_u64); put(array, 0xbd5af13bef0b113e_u64)
+ put(array, 0xa6274bbdd0fadd61_u64); put(array, 0xecb1ad8aeacdd58e_u64)
+ put(array, 0xcfb11ead453994ba_u64); put(array, 0x67de18eda5814af2_u64)
+ put(array, 0x81ceb32c4b43fcf4_u64); put(array, 0x80eacf948770ced7_u64)
+ put(array, 0xa2425ff75e14fc31_u64); put(array, 0xa1258379a94d028d_u64)
+ put(array, 0xcad2f7f5359a3b3e_u64); put(array, 0x96ee45813a04330_u64)
+ put(array, 0xfd87b5f28300ca0d_u64); put(array, 0x8bca9d6e188853fc_u64)
+ put(array, 0x9e74d1b791e07e48_u64); put(array, 0x775ea264cf55347e_u64)
+ put(array, 0xc612062576589dda_u64); put(array, 0x95364afe032a819e_u64)
+ put(array, 0xf79687aed3eec551_u64); put(array, 0x3a83ddbd83f52205_u64)
+ put(array, 0x9abe14cd44753b52_u64); put(array, 0xc4926a9672793543_u64)
+ put(array, 0xc16d9a0095928a27_u64); put(array, 0x75b7053c0f178294_u64)
+ put(array, 0xf1c90080baf72cb1_u64); put(array, 0x5324c68b12dd6339_u64)
+ put(array, 0x971da05074da7bee_u64); put(array, 0xd3f6fc16ebca5e04_u64)
+ put(array, 0xbce5086492111aea_u64); put(array, 0x88f4bb1ca6bcf585_u64)
+ put(array, 0xec1e4a7db69561a5_u64); put(array, 0x2b31e9e3d06c32e6_u64)
+ put(array, 0x9392ee8e921d5d07_u64); put(array, 0x3aff322e62439fd0_u64)
+ put(array, 0xb877aa3236a4b449_u64); put(array, 0x9befeb9fad487c3_u64)
+ put(array, 0xe69594bec44de15b_u64); put(array, 0x4c2ebe687989a9b4_u64)
+ put(array, 0x901d7cf73ab0acd9_u64); put(array, 0xf9d37014bf60a11_u64)
+ put(array, 0xb424dc35095cd80f_u64); put(array, 0x538484c19ef38c95_u64)
+ put(array, 0xe12e13424bb40e13_u64); put(array, 0x2865a5f206b06fba_u64)
+ put(array, 0x8cbccc096f5088cb_u64); put(array, 0xf93f87b7442e45d4_u64)
+ put(array, 0xafebff0bcb24aafe_u64); put(array, 0xf78f69a51539d749_u64)
+ put(array, 0xdbe6fecebdedd5be_u64); put(array, 0xb573440e5a884d1c_u64)
+ put(array, 0x89705f4136b4a597_u64); put(array, 0x31680a88f8953031_u64)
+ put(array, 0xabcc77118461cefc_u64); put(array, 0xfdc20d2b36ba7c3e_u64)
+ put(array, 0xd6bf94d5e57a42bc_u64); put(array, 0x3d32907604691b4d_u64)
+ put(array, 0x8637bd05af6c69b5_u64); put(array, 0xa63f9a49c2c1b110_u64)
+ put(array, 0xa7c5ac471b478423_u64); put(array, 0xfcf80dc33721d54_u64)
+ put(array, 0xd1b71758e219652b_u64); put(array, 0xd3c36113404ea4a9_u64)
+ put(array, 0x83126e978d4fdf3b_u64); put(array, 0x645a1cac083126ea_u64)
+ put(array, 0xa3d70a3d70a3d70a_u64); put(array, 0x3d70a3d70a3d70a4_u64)
+ put(array, 0xcccccccccccccccc_u64); put(array, 0xcccccccccccccccd_u64)
+ put(array, 0x8000000000000000_u64); put(array, 0x0_u64)
+ put(array, 0xa000000000000000_u64); put(array, 0x0_u64)
+ put(array, 0xc800000000000000_u64); put(array, 0x0_u64)
+ put(array, 0xfa00000000000000_u64); put(array, 0x0_u64)
+ put(array, 0x9c40000000000000_u64); put(array, 0x0_u64)
+ put(array, 0xc350000000000000_u64); put(array, 0x0_u64)
+ put(array, 0xf424000000000000_u64); put(array, 0x0_u64)
+ put(array, 0x9896800000000000_u64); put(array, 0x0_u64)
+ put(array, 0xbebc200000000000_u64); put(array, 0x0_u64)
+ put(array, 0xee6b280000000000_u64); put(array, 0x0_u64)
+ put(array, 0x9502f90000000000_u64); put(array, 0x0_u64)
+ put(array, 0xba43b74000000000_u64); put(array, 0x0_u64)
+ put(array, 0xe8d4a51000000000_u64); put(array, 0x0_u64)
+ put(array, 0x9184e72a00000000_u64); put(array, 0x0_u64)
+ put(array, 0xb5e620f480000000_u64); put(array, 0x0_u64)
+ put(array, 0xe35fa931a0000000_u64); put(array, 0x0_u64)
+ put(array, 0x8e1bc9bf04000000_u64); put(array, 0x0_u64)
+ put(array, 0xb1a2bc2ec5000000_u64); put(array, 0x0_u64)
+ put(array, 0xde0b6b3a76400000_u64); put(array, 0x0_u64)
+ put(array, 0x8ac7230489e80000_u64); put(array, 0x0_u64)
+ put(array, 0xad78ebc5ac620000_u64); put(array, 0x0_u64)
+ put(array, 0xd8d726b7177a8000_u64); put(array, 0x0_u64)
+ put(array, 0x878678326eac9000_u64); put(array, 0x0_u64)
+ put(array, 0xa968163f0a57b400_u64); put(array, 0x0_u64)
+ put(array, 0xd3c21bcecceda100_u64); put(array, 0x0_u64)
+ put(array, 0x84595161401484a0_u64); put(array, 0x0_u64)
+ put(array, 0xa56fa5b99019a5c8_u64); put(array, 0x0_u64)
+ put(array, 0xcecb8f27f4200f3a_u64); put(array, 0x0_u64)
+ put(array, 0x813f3978f8940984_u64); put(array, 0x4000000000000000_u64)
+ put(array, 0xa18f07d736b90be5_u64); put(array, 0x5000000000000000_u64)
+ put(array, 0xc9f2c9cd04674ede_u64); put(array, 0xa400000000000000_u64)
+ put(array, 0xfc6f7c4045812296_u64); put(array, 0x4d00000000000000_u64)
+ put(array, 0x9dc5ada82b70b59d_u64); put(array, 0xf020000000000000_u64)
+ put(array, 0xc5371912364ce305_u64); put(array, 0x6c28000000000000_u64)
+ put(array, 0xf684df56c3e01bc6_u64); put(array, 0xc732000000000000_u64)
+ put(array, 0x9a130b963a6c115c_u64); put(array, 0x3c7f400000000000_u64)
+ put(array, 0xc097ce7bc90715b3_u64); put(array, 0x4b9f100000000000_u64)
+ put(array, 0xf0bdc21abb48db20_u64); put(array, 0x1e86d40000000000_u64)
+ put(array, 0x96769950b50d88f4_u64); put(array, 0x1314448000000000_u64)
+ put(array, 0xbc143fa4e250eb31_u64); put(array, 0x17d955a000000000_u64)
+ put(array, 0xeb194f8e1ae525fd_u64); put(array, 0x5dcfab0800000000_u64)
+ put(array, 0x92efd1b8d0cf37be_u64); put(array, 0x5aa1cae500000000_u64)
+ put(array, 0xb7abc627050305ad_u64); put(array, 0xf14a3d9e40000000_u64)
+ put(array, 0xe596b7b0c643c719_u64); put(array, 0x6d9ccd05d0000000_u64)
+ put(array, 0x8f7e32ce7bea5c6f_u64); put(array, 0xe4820023a2000000_u64)
+ put(array, 0xb35dbf821ae4f38b_u64); put(array, 0xdda2802c8a800000_u64)
+ put(array, 0xe0352f62a19e306e_u64); put(array, 0xd50b2037ad200000_u64)
+ put(array, 0x8c213d9da502de45_u64); put(array, 0x4526f422cc340000_u64)
+ put(array, 0xaf298d050e4395d6_u64); put(array, 0x9670b12b7f410000_u64)
+ put(array, 0xdaf3f04651d47b4c_u64); put(array, 0x3c0cdd765f114000_u64)
+ put(array, 0x88d8762bf324cd0f_u64); put(array, 0xa5880a69fb6ac800_u64)
+ put(array, 0xab0e93b6efee0053_u64); put(array, 0x8eea0d047a457a00_u64)
+ put(array, 0xd5d238a4abe98068_u64); put(array, 0x72a4904598d6d880_u64)
+ put(array, 0x85a36366eb71f041_u64); put(array, 0x47a6da2b7f864750_u64)
+ put(array, 0xa70c3c40a64e6c51_u64); put(array, 0x999090b65f67d924_u64)
+ put(array, 0xd0cf4b50cfe20765_u64); put(array, 0xfff4b4e3f741cf6d_u64)
+ put(array, 0x82818f1281ed449f_u64); put(array, 0xbff8f10e7a8921a4_u64)
+ put(array, 0xa321f2d7226895c7_u64); put(array, 0xaff72d52192b6a0d_u64)
+ put(array, 0xcbea6f8ceb02bb39_u64); put(array, 0x9bf4f8a69f764490_u64)
+ put(array, 0xfee50b7025c36a08_u64); put(array, 0x2f236d04753d5b4_u64)
+ put(array, 0x9f4f2726179a2245_u64); put(array, 0x1d762422c946590_u64)
+ put(array, 0xc722f0ef9d80aad6_u64); put(array, 0x424d3ad2b7b97ef5_u64)
+ put(array, 0xf8ebad2b84e0d58b_u64); put(array, 0xd2e0898765a7deb2_u64)
+ put(array, 0x9b934c3b330c8577_u64); put(array, 0x63cc55f49f88eb2f_u64)
+ put(array, 0xc2781f49ffcfa6d5_u64); put(array, 0x3cbf6b71c76b25fb_u64)
+ put(array, 0xf316271c7fc3908a_u64); put(array, 0x8bef464e3945ef7a_u64)
+ put(array, 0x97edd871cfda3a56_u64); put(array, 0x97758bf0e3cbb5ac_u64)
+ put(array, 0xbde94e8e43d0c8ec_u64); put(array, 0x3d52eeed1cbea317_u64)
+ put(array, 0xed63a231d4c4fb27_u64); put(array, 0x4ca7aaa863ee4bdd_u64)
+ put(array, 0x945e455f24fb1cf8_u64); put(array, 0x8fe8caa93e74ef6a_u64)
+ put(array, 0xb975d6b6ee39e436_u64); put(array, 0xb3e2fd538e122b44_u64)
+ put(array, 0xe7d34c64a9c85d44_u64); put(array, 0x60dbbca87196b616_u64)
+ put(array, 0x90e40fbeea1d3a4a_u64); put(array, 0xbc8955e946fe31cd_u64)
+ put(array, 0xb51d13aea4a488dd_u64); put(array, 0x6babab6398bdbe41_u64)
+ put(array, 0xe264589a4dcdab14_u64); put(array, 0xc696963c7eed2dd1_u64)
+ put(array, 0x8d7eb76070a08aec_u64); put(array, 0xfc1e1de5cf543ca2_u64)
+ put(array, 0xb0de65388cc8ada8_u64); put(array, 0x3b25a55f43294bcb_u64)
+ put(array, 0xdd15fe86affad912_u64); put(array, 0x49ef0eb713f39ebe_u64)
+ put(array, 0x8a2dbf142dfcc7ab_u64); put(array, 0x6e3569326c784337_u64)
+ put(array, 0xacb92ed9397bf996_u64); put(array, 0x49c2c37f07965404_u64)
+ put(array, 0xd7e77a8f87daf7fb_u64); put(array, 0xdc33745ec97be906_u64)
+ put(array, 0x86f0ac99b4e8dafd_u64); put(array, 0x69a028bb3ded71a3_u64)
+ put(array, 0xa8acd7c0222311bc_u64); put(array, 0xc40832ea0d68ce0c_u64)
+ put(array, 0xd2d80db02aabd62b_u64); put(array, 0xf50a3fa490c30190_u64)
+ put(array, 0x83c7088e1aab65db_u64); put(array, 0x792667c6da79e0fa_u64)
+ put(array, 0xa4b8cab1a1563f52_u64); put(array, 0x577001b891185938_u64)
+ put(array, 0xcde6fd5e09abcf26_u64); put(array, 0xed4c0226b55e6f86_u64)
+ put(array, 0x80b05e5ac60b6178_u64); put(array, 0x544f8158315b05b4_u64)
+ put(array, 0xa0dc75f1778e39d6_u64); put(array, 0x696361ae3db1c721_u64)
+ put(array, 0xc913936dd571c84c_u64); put(array, 0x3bc3a19cd1e38e9_u64)
+ put(array, 0xfb5878494ace3a5f_u64); put(array, 0x4ab48a04065c723_u64)
+ put(array, 0x9d174b2dcec0e47b_u64); put(array, 0x62eb0d64283f9c76_u64)
+ put(array, 0xc45d1df942711d9a_u64); put(array, 0x3ba5d0bd324f8394_u64)
+ put(array, 0xf5746577930d6500_u64); put(array, 0xca8f44ec7ee36479_u64)
+ put(array, 0x9968bf6abbe85f20_u64); put(array, 0x7e998b13cf4e1ecb_u64)
+ put(array, 0xbfc2ef456ae276e8_u64); put(array, 0x9e3fedd8c321a67e_u64)
+ put(array, 0xefb3ab16c59b14a2_u64); put(array, 0xc5cfe94ef3ea101e_u64)
+ put(array, 0x95d04aee3b80ece5_u64); put(array, 0xbba1f1d158724a12_u64)
+ put(array, 0xbb445da9ca61281f_u64); put(array, 0x2a8a6e45ae8edc97_u64)
+ put(array, 0xea1575143cf97226_u64); put(array, 0xf52d09d71a3293bd_u64)
+ put(array, 0x924d692ca61be758_u64); put(array, 0x593c2626705f9c56_u64)
+ put(array, 0xb6e0c377cfa2e12e_u64); put(array, 0x6f8b2fb00c77836c_u64)
+ put(array, 0xe498f455c38b997a_u64); put(array, 0xb6dfb9c0f956447_u64)
+ put(array, 0x8edf98b59a373fec_u64); put(array, 0x4724bd4189bd5eac_u64)
+ put(array, 0xb2977ee300c50fe7_u64); put(array, 0x58edec91ec2cb657_u64)
+ put(array, 0xdf3d5e9bc0f653e1_u64); put(array, 0x2f2967b66737e3ed_u64)
+ put(array, 0x8b865b215899f46c_u64); put(array, 0xbd79e0d20082ee74_u64)
+ put(array, 0xae67f1e9aec07187_u64); put(array, 0xecd8590680a3aa11_u64)
+ put(array, 0xda01ee641a708de9_u64); put(array, 0xe80e6f4820cc9495_u64)
+ put(array, 0x884134fe908658b2_u64); put(array, 0x3109058d147fdcdd_u64)
+ put(array, 0xaa51823e34a7eede_u64); put(array, 0xbd4b46f0599fd415_u64)
+ put(array, 0xd4e5e2cdc1d1ea96_u64); put(array, 0x6c9e18ac7007c91a_u64)
+ put(array, 0x850fadc09923329e_u64); put(array, 0x3e2cf6bc604ddb0_u64)
+ put(array, 0xa6539930bf6bff45_u64); put(array, 0x84db8346b786151c_u64)
+ put(array, 0xcfe87f7cef46ff16_u64); put(array, 0xe612641865679a63_u64)
+ put(array, 0x81f14fae158c5f6e_u64); put(array, 0x4fcb7e8f3f60c07e_u64)
+ put(array, 0xa26da3999aef7749_u64); put(array, 0xe3be5e330f38f09d_u64)
+ put(array, 0xcb090c8001ab551c_u64); put(array, 0x5cadf5bfd3072cc5_u64)
+ put(array, 0xfdcb4fa002162a63_u64); put(array, 0x73d9732fc7c8f7f6_u64)
+ put(array, 0x9e9f11c4014dda7e_u64); put(array, 0x2867e7fddcdd9afa_u64)
+ put(array, 0xc646d63501a1511d_u64); put(array, 0xb281e1fd541501b8_u64)
+ put(array, 0xf7d88bc24209a565_u64); put(array, 0x1f225a7ca91a4226_u64)
+ put(array, 0x9ae757596946075f_u64); put(array, 0x3375788de9b06958_u64)
+ put(array, 0xc1a12d2fc3978937_u64); put(array, 0x52d6b1641c83ae_u64)
+ put(array, 0xf209787bb47d6b84_u64); put(array, 0xc0678c5dbd23a49a_u64)
+ put(array, 0x9745eb4d50ce6332_u64); put(array, 0xf840b7ba963646e0_u64)
+ put(array, 0xbd176620a501fbff_u64); put(array, 0xb650e5a93bc3d898_u64)
+ put(array, 0xec5d3fa8ce427aff_u64); put(array, 0xa3e51f138ab4cebe_u64)
+ put(array, 0x93ba47c980e98cdf_u64); put(array, 0xc66f336c36b10137_u64)
+ put(array, 0xb8a8d9bbe123f017_u64); put(array, 0xb80b0047445d4184_u64)
+ put(array, 0xe6d3102ad96cec1d_u64); put(array, 0xa60dc059157491e5_u64)
+ put(array, 0x9043ea1ac7e41392_u64); put(array, 0x87c89837ad68db2f_u64)
+ put(array, 0xb454e4a179dd1877_u64); put(array, 0x29babe4598c311fb_u64)
+ put(array, 0xe16a1dc9d8545e94_u64); put(array, 0xf4296dd6fef3d67a_u64)
+ put(array, 0x8ce2529e2734bb1d_u64); put(array, 0x1899e4a65f58660c_u64)
+ put(array, 0xb01ae745b101e9e4_u64); put(array, 0x5ec05dcff72e7f8f_u64)
+ put(array, 0xdc21a1171d42645d_u64); put(array, 0x76707543f4fa1f73_u64)
+ put(array, 0x899504ae72497eba_u64); put(array, 0x6a06494a791c53a8_u64)
+ put(array, 0xabfa45da0edbde69_u64); put(array, 0x487db9d17636892_u64)
+ put(array, 0xd6f8d7509292d603_u64); put(array, 0x45a9d2845d3c42b6_u64)
+ put(array, 0x865b86925b9bc5c2_u64); put(array, 0xb8a2392ba45a9b2_u64)
+ put(array, 0xa7f26836f282b732_u64); put(array, 0x8e6cac7768d7141e_u64)
+ put(array, 0xd1ef0244af2364ff_u64); put(array, 0x3207d795430cd926_u64)
+ put(array, 0x8335616aed761f1f_u64); put(array, 0x7f44e6bd49e807b8_u64)
+ put(array, 0xa402b9c5a8d3a6e7_u64); put(array, 0x5f16206c9c6209a6_u64)
+ put(array, 0xcd036837130890a1_u64); put(array, 0x36dba887c37a8c0f_u64)
+ put(array, 0x802221226be55a64_u64); put(array, 0xc2494954da2c9789_u64)
+ put(array, 0xa02aa96b06deb0fd_u64); put(array, 0xf2db9baa10b7bd6c_u64)
+ put(array, 0xc83553c5c8965d3d_u64); put(array, 0x6f92829494e5acc7_u64)
+ put(array, 0xfa42a8b73abbf48c_u64); put(array, 0xcb772339ba1f17f9_u64)
+ put(array, 0x9c69a97284b578d7_u64); put(array, 0xff2a760414536efb_u64)
+ put(array, 0xc38413cf25e2d70d_u64); put(array, 0xfef5138519684aba_u64)
+ put(array, 0xf46518c2ef5b8cd1_u64); put(array, 0x7eb258665fc25d69_u64)
+ put(array, 0x98bf2f79d5993802_u64); put(array, 0xef2f773ffbd97a61_u64)
+ put(array, 0xbeeefb584aff8603_u64); put(array, 0xaafb550ffacfd8fa_u64)
+ put(array, 0xeeaaba2e5dbf6784_u64); put(array, 0x95ba2a53f983cf38_u64)
+ put(array, 0x952ab45cfa97a0b2_u64); put(array, 0xdd945a747bf26183_u64)
+ put(array, 0xba756174393d88df_u64); put(array, 0x94f971119aeef9e4_u64)
+ put(array, 0xe912b9d1478ceb17_u64); put(array, 0x7a37cd5601aab85d_u64)
+ put(array, 0x91abb422ccb812ee_u64); put(array, 0xac62e055c10ab33a_u64)
+ put(array, 0xb616a12b7fe617aa_u64); put(array, 0x577b986b314d6009_u64)
+ put(array, 0xe39c49765fdf9d94_u64); put(array, 0xed5a7e85fda0b80b_u64)
+ put(array, 0x8e41ade9fbebc27d_u64); put(array, 0x14588f13be847307_u64)
+ put(array, 0xb1d219647ae6b31c_u64); put(array, 0x596eb2d8ae258fc8_u64)
+ put(array, 0xde469fbd99a05fe3_u64); put(array, 0x6fca5f8ed9aef3bb_u64)
+ put(array, 0x8aec23d680043bee_u64); put(array, 0x25de7bb9480d5854_u64)
+ put(array, 0xada72ccc20054ae9_u64); put(array, 0xaf561aa79a10ae6a_u64)
+ put(array, 0xd910f7ff28069da4_u64); put(array, 0x1b2ba1518094da04_u64)
+ put(array, 0x87aa9aff79042286_u64); put(array, 0x90fb44d2f05d0842_u64)
+ put(array, 0xa99541bf57452b28_u64); put(array, 0x353a1607ac744a53_u64)
+ put(array, 0xd3fa922f2d1675f2_u64); put(array, 0x42889b8997915ce8_u64)
+ put(array, 0x847c9b5d7c2e09b7_u64); put(array, 0x69956135febada11_u64)
+ put(array, 0xa59bc234db398c25_u64); put(array, 0x43fab9837e699095_u64)
+ put(array, 0xcf02b2c21207ef2e_u64); put(array, 0x94f967e45e03f4bb_u64)
+ put(array, 0x8161afb94b44f57d_u64); put(array, 0x1d1be0eebac278f5_u64)
+ put(array, 0xa1ba1ba79e1632dc_u64); put(array, 0x6462d92a69731732_u64)
+ put(array, 0xca28a291859bbf93_u64); put(array, 0x7d7b8f7503cfdcfe_u64)
+ put(array, 0xfcb2cb35e702af78_u64); put(array, 0x5cda735244c3d43e_u64)
+ put(array, 0x9defbf01b061adab_u64); put(array, 0x3a0888136afa64a7_u64)
+ put(array, 0xc56baec21c7a1916_u64); put(array, 0x88aaa1845b8fdd0_u64)
+ put(array, 0xf6c69a72a3989f5b_u64); put(array, 0x8aad549e57273d45_u64)
+ put(array, 0x9a3c2087a63f6399_u64); put(array, 0x36ac54e2f678864b_u64)
+ put(array, 0xc0cb28a98fcf3c7f_u64); put(array, 0x84576a1bb416a7dd_u64)
+ put(array, 0xf0fdf2d3f3c30b9f_u64); put(array, 0x656d44a2a11c51d5_u64)
+ put(array, 0x969eb7c47859e743_u64); put(array, 0x9f644ae5a4b1b325_u64)
+ put(array, 0xbc4665b596706114_u64); put(array, 0x873d5d9f0dde1fee_u64)
+ put(array, 0xeb57ff22fc0c7959_u64); put(array, 0xa90cb506d155a7ea_u64)
+ put(array, 0x9316ff75dd87cbd8_u64); put(array, 0x9a7f12442d588f2_u64)
+ put(array, 0xb7dcbf5354e9bece_u64); put(array, 0xc11ed6d538aeb2f_u64)
+ put(array, 0xe5d3ef282a242e81_u64); put(array, 0x8f1668c8a86da5fa_u64)
+ put(array, 0x8fa475791a569d10_u64); put(array, 0xf96e017d694487bc_u64)
+ put(array, 0xb38d92d760ec4455_u64); put(array, 0x37c981dcc395a9ac_u64)
+ put(array, 0xe070f78d3927556a_u64); put(array, 0x85bbe253f47b1417_u64)
+ put(array, 0x8c469ab843b89562_u64); put(array, 0x93956d7478ccec8e_u64)
+ put(array, 0xaf58416654a6babb_u64); put(array, 0x387ac8d1970027b2_u64)
+ put(array, 0xdb2e51bfe9d0696a_u64); put(array, 0x6997b05fcc0319e_u64)
+ put(array, 0x88fcf317f22241e2_u64); put(array, 0x441fece3bdf81f03_u64)
+ put(array, 0xab3c2fddeeaad25a_u64); put(array, 0xd527e81cad7626c3_u64)
+ put(array, 0xd60b3bd56a5586f1_u64); put(array, 0x8a71e223d8d3b074_u64)
+ put(array, 0x85c7056562757456_u64); put(array, 0xf6872d5667844e49_u64)
+ put(array, 0xa738c6bebb12d16c_u64); put(array, 0xb428f8ac016561db_u64)
+ put(array, 0xd106f86e69d785c7_u64); put(array, 0xe13336d701beba52_u64)
+ put(array, 0x82a45b450226b39c_u64); put(array, 0xecc0024661173473_u64)
+ put(array, 0xa34d721642b06084_u64); put(array, 0x27f002d7f95d0190_u64)
+ put(array, 0xcc20ce9bd35c78a5_u64); put(array, 0x31ec038df7b441f4_u64)
+ put(array, 0xff290242c83396ce_u64); put(array, 0x7e67047175a15271_u64)
+ put(array, 0x9f79a169bd203e41_u64); put(array, 0xf0062c6e984d386_u64)
+ put(array, 0xc75809c42c684dd1_u64); put(array, 0x52c07b78a3e60868_u64)
+ put(array, 0xf92e0c3537826145_u64); put(array, 0xa7709a56ccdf8a82_u64)
+ put(array, 0x9bbcc7a142b17ccb_u64); put(array, 0x88a66076400bb691_u64)
+ put(array, 0xc2abf989935ddbfe_u64); put(array, 0x6acff893d00ea435_u64)
+ put(array, 0xf356f7ebf83552fe_u64); put(array, 0x583f6b8c4124d43_u64)
+ put(array, 0x98165af37b2153de_u64); put(array, 0xc3727a337a8b704a_u64)
+ put(array, 0xbe1bf1b059e9a8d6_u64); put(array, 0x744f18c0592e4c5c_u64)
+ put(array, 0xeda2ee1c7064130c_u64); put(array, 0x1162def06f79df73_u64)
+ put(array, 0x9485d4d1c63e8be7_u64); put(array, 0x8addcb5645ac2ba8_u64)
+ put(array, 0xb9a74a0637ce2ee1_u64); put(array, 0x6d953e2bd7173692_u64)
+ put(array, 0xe8111c87c5c1ba99_u64); put(array, 0xc8fa8db6ccdd0437_u64)
+ put(array, 0x910ab1d4db9914a0_u64); put(array, 0x1d9c9892400a22a2_u64)
+ put(array, 0xb54d5e4a127f59c8_u64); put(array, 0x2503beb6d00cab4b_u64)
+ put(array, 0xe2a0b5dc971f303a_u64); put(array, 0x2e44ae64840fd61d_u64)
+ put(array, 0x8da471a9de737e24_u64); put(array, 0x5ceaecfed289e5d2_u64)
+ put(array, 0xb10d8e1456105dad_u64); put(array, 0x7425a83e872c5f47_u64)
+ put(array, 0xdd50f1996b947518_u64); put(array, 0xd12f124e28f77719_u64)
+ put(array, 0x8a5296ffe33cc92f_u64); put(array, 0x82bd6b70d99aaa6f_u64)
+ put(array, 0xace73cbfdc0bfb7b_u64); put(array, 0x636cc64d1001550b_u64)
+ put(array, 0xd8210befd30efa5a_u64); put(array, 0x3c47f7e05401aa4e_u64)
+ put(array, 0x8714a775e3e95c78_u64); put(array, 0x65acfaec34810a71_u64)
+ put(array, 0xa8d9d1535ce3b396_u64); put(array, 0x7f1839a741a14d0d_u64)
+ put(array, 0xd31045a8341ca07c_u64); put(array, 0x1ede48111209a050_u64)
+ put(array, 0x83ea2b892091e44d_u64); put(array, 0x934aed0aab460432_u64)
+ put(array, 0xa4e4b66b68b65d60_u64); put(array, 0xf81da84d5617853f_u64)
+ put(array, 0xce1de40642e3f4b9_u64); put(array, 0x36251260ab9d668e_u64)
+ put(array, 0x80d2ae83e9ce78f3_u64); put(array, 0xc1d72b7c6b426019_u64)
+ put(array, 0xa1075a24e4421730_u64); put(array, 0xb24cf65b8612f81f_u64)
+ put(array, 0xc94930ae1d529cfc_u64); put(array, 0xdee033f26797b627_u64)
+ put(array, 0xfb9b7cd9a4a7443c_u64); put(array, 0x169840ef017da3b1_u64)
+ put(array, 0x9d412e0806e88aa5_u64); put(array, 0x8e1f289560ee864e_u64)
+ put(array, 0xc491798a08a2ad4e_u64); put(array, 0xf1a6f2bab92a27e2_u64)
+ put(array, 0xf5b5d7ec8acb58a2_u64); put(array, 0xae10af696774b1db_u64)
+ put(array, 0x9991a6f3d6bf1765_u64); put(array, 0xacca6da1e0a8ef29_u64)
+ put(array, 0xbff610b0cc6edd3f_u64); put(array, 0x17fd090a58d32af3_u64)
+ put(array, 0xeff394dcff8a948e_u64); put(array, 0xddfc4b4cef07f5b0_u64)
+ put(array, 0x95f83d0a1fb69cd9_u64); put(array, 0x4abdaf101564f98e_u64)
+ put(array, 0xbb764c4ca7a4440f_u64); put(array, 0x9d6d1ad41abe37f1_u64)
+ put(array, 0xea53df5fd18d5513_u64); put(array, 0x84c86189216dc5ed_u64)
+ put(array, 0x92746b9be2f8552c_u64); put(array, 0x32fd3cf5b4e49bb4_u64)
+ put(array, 0xb7118682dbb66a77_u64); put(array, 0x3fbc8c33221dc2a1_u64)
+ put(array, 0xe4d5e82392a40515_u64); put(array, 0xfabaf3feaa5334a_u64)
+ put(array, 0x8f05b1163ba6832d_u64); put(array, 0x29cb4d87f2a7400e_u64)
+ put(array, 0xb2c71d5bca9023f8_u64); put(array, 0x743e20e9ef511012_u64)
+ put(array, 0xdf78e4b2bd342cf6_u64); put(array, 0x914da9246b255416_u64)
+ put(array, 0x8bab8eefb6409c1a_u64); put(array, 0x1ad089b6c2f7548e_u64)
+ put(array, 0xae9672aba3d0c320_u64); put(array, 0xa184ac2473b529b1_u64)
+ put(array, 0xda3c0f568cc4f3e8_u64); put(array, 0xc9e5d72d90a2741e_u64)
+ put(array, 0x8865899617fb1871_u64); put(array, 0x7e2fa67c7a658892_u64)
+ put(array, 0xaa7eebfb9df9de8d_u64); put(array, 0xddbb901b98feeab7_u64)
+ put(array, 0xd51ea6fa85785631_u64); put(array, 0x552a74227f3ea565_u64)
+ put(array, 0x8533285c936b35de_u64); put(array, 0xd53a88958f87275f_u64)
+ put(array, 0xa67ff273b8460356_u64); put(array, 0x8a892abaf368f137_u64)
+ put(array, 0xd01fef10a657842c_u64); put(array, 0x2d2b7569b0432d85_u64)
+ put(array, 0x8213f56a67f6b29b_u64); put(array, 0x9c3b29620e29fc73_u64)
+ put(array, 0xa298f2c501f45f42_u64); put(array, 0x8349f3ba91b47b8f_u64)
+ put(array, 0xcb3f2f7642717713_u64); put(array, 0x241c70a936219a73_u64)
+ put(array, 0xfe0efb53d30dd4d7_u64); put(array, 0xed238cd383aa0110_u64)
+ put(array, 0x9ec95d1463e8a506_u64); put(array, 0xf4363804324a40aa_u64)
+ put(array, 0xc67bb4597ce2ce48_u64); put(array, 0xb143c6053edcd0d5_u64)
+ put(array, 0xf81aa16fdc1b81da_u64); put(array, 0xdd94b7868e94050a_u64)
+ put(array, 0x9b10a4e5e9913128_u64); put(array, 0xca7cf2b4191c8326_u64)
+ put(array, 0xc1d4ce1f63f57d72_u64); put(array, 0xfd1c2f611f63a3f0_u64)
+ put(array, 0xf24a01a73cf2dccf_u64); put(array, 0xbc633b39673c8cec_u64)
+ put(array, 0x976e41088617ca01_u64); put(array, 0xd5be0503e085d813_u64)
+ put(array, 0xbd49d14aa79dbc82_u64); put(array, 0x4b2d8644d8a74e18_u64)
+ put(array, 0xec9c459d51852ba2_u64); put(array, 0xddf8e7d60ed1219e_u64)
+ put(array, 0x93e1ab8252f33b45_u64); put(array, 0xcabb90e5c942b503_u64)
+ put(array, 0xb8da1662e7b00a17_u64); put(array, 0x3d6a751f3b936243_u64)
+ put(array, 0xe7109bfba19c0c9d_u64); put(array, 0xcc512670a783ad4_u64)
+ put(array, 0x906a617d450187e2_u64); put(array, 0x27fb2b80668b24c5_u64)
+ put(array, 0xb484f9dc9641e9da_u64); put(array, 0xb1f9f660802dedf6_u64)
+ put(array, 0xe1a63853bbd26451_u64); put(array, 0x5e7873f8a0396973_u64)
+ put(array, 0x8d07e33455637eb2_u64); put(array, 0xdb0b487b6423e1e8_u64)
+ put(array, 0xb049dc016abc5e5f_u64); put(array, 0x91ce1a9a3d2cda62_u64)
+ put(array, 0xdc5c5301c56b75f7_u64); put(array, 0x7641a140cc7810fb_u64)
+ put(array, 0x89b9b3e11b6329ba_u64); put(array, 0xa9e904c87fcb0a9d_u64)
+ put(array, 0xac2820d9623bf429_u64); put(array, 0x546345fa9fbdcd44_u64)
+ put(array, 0xd732290fbacaf133_u64); put(array, 0xa97c177947ad4095_u64)
+ put(array, 0x867f59a9d4bed6c0_u64); put(array, 0x49ed8eabcccc485d_u64)
+ put(array, 0xa81f301449ee8c70_u64); put(array, 0x5c68f256bfff5a74_u64)
+ put(array, 0xd226fc195c6a2f8c_u64); put(array, 0x73832eec6fff3111_u64)
+ put(array, 0x83585d8fd9c25db7_u64); put(array, 0xc831fd53c5ff7eab_u64)
+ put(array, 0xa42e74f3d032f525_u64); put(array, 0xba3e7ca8b77f5e55_u64)
+ put(array, 0xcd3a1230c43fb26f_u64); put(array, 0x28ce1bd2e55f35eb_u64)
+ put(array, 0x80444b5e7aa7cf85_u64); put(array, 0x7980d163cf5b81b3_u64)
+ put(array, 0xa0555e361951c366_u64); put(array, 0xd7e105bcc332621f_u64)
+ put(array, 0xc86ab5c39fa63440_u64); put(array, 0x8dd9472bf3fefaa7_u64)
+ put(array, 0xfa856334878fc150_u64); put(array, 0xb14f98f6f0feb951_u64)
+ put(array, 0x9c935e00d4b9d8d2_u64); put(array, 0x6ed1bf9a569f33d3_u64)
+ put(array, 0xc3b8358109e84f07_u64); put(array, 0xa862f80ec4700c8_u64)
+ put(array, 0xf4a642e14c6262c8_u64); put(array, 0xcd27bb612758c0fa_u64)
+ put(array, 0x98e7e9cccfbd7dbd_u64); put(array, 0x8038d51cb897789c_u64)
+ put(array, 0xbf21e44003acdd2c_u64); put(array, 0xe0470a63e6bd56c3_u64)
+ put(array, 0xeeea5d5004981478_u64); put(array, 0x1858ccfce06cac74_u64)
+ put(array, 0x95527a5202df0ccb_u64); put(array, 0xf37801e0c43ebc8_u64)
+ put(array, 0xbaa718e68396cffd_u64); put(array, 0xd30560258f54e6ba_u64)
+ put(array, 0xe950df20247c83fd_u64); put(array, 0x47c6b82ef32a2069_u64)
+ put(array, 0x91d28b7416cdd27e_u64); put(array, 0x4cdc331d57fa5441_u64)
+ put(array, 0xb6472e511c81471d_u64); put(array, 0xe0133fe4adf8e952_u64)
+ put(array, 0xe3d8f9e563a198e5_u64); put(array, 0x58180fddd97723a6_u64)
+ put(array, 0x8e679c2f5e44ff8f_u64); put(array, 0x570f09eaa7ea7648_u64)
+ array
+ end
+ end
+end
diff --git a/src/float/fast_float/float_common.cr b/src/float/fast_float/float_common.cr
new file mode 100644
index 000000000000..a66dc99f82f7
--- /dev/null
+++ b/src/float/fast_float/float_common.cr
@@ -0,0 +1,294 @@
+module Float::FastFloat
+ @[Flags]
+ enum CharsFormat
+ Scientific = 1 << 0
+ Fixed = 1 << 2
+ Hex = 1 << 3
+ NoInfnan = 1 << 4
+ JsonFmt = 1 << 5
+ FortranFmt = 1 << 6
+
+ # RFC 8259: https://datatracker.ietf.org/doc/html/rfc8259#section-6
+ Json = JsonFmt | Fixed | Scientific | NoInfnan
+
+ # Extension of RFC 8259 where, e.g., "inf" and "nan" are allowed.
+ JsonOrInfnan = JsonFmt | Fixed | Scientific
+
+ Fortran = FortranFmt | Fixed | Scientific
+ General = Fixed | Scientific
+ end
+
+ # NOTE(crystal): uses `Errno` to represent C++'s `std::errc`
+ record FromCharsResultT(UC), ptr : UC*, ec : Errno
+
+ alias FromCharsResult = FromCharsResultT(UInt8)
+
+ record ParseOptionsT(UC), format : CharsFormat = :general, decimal_point : UC = 0x2E # '.'.ord
+
+ alias ParseOptions = ParseOptionsT(UInt8)
+
+ # rust style `try!()` macro, or `?` operator
+ macro fastfloat_try(x)
+ unless {{ x }}
+ return false
+ end
+ end
+
+ # Compares two ASCII strings in a case insensitive manner.
+ def self.fastfloat_strncasecmp(input1 : UC*, input2 : UC*, length : Int) : Bool forall UC
+ running_diff = 0_u8
+ length.times do |i|
+ running_diff |= input1[i].to_u8! ^ input2[i].to_u8!
+ end
+ running_diff.in?(0_u8, 32_u8)
+ end
+
+ record Value128, low : UInt64, high : UInt64 do
+ def self.new(x : UInt128) : self
+ new(low: x.to_u64!, high: x.unsafe_shr(64).to_u64!)
+ end
+ end
+
+ struct AdjustedMantissa
+ property mantissa : UInt64
+ property power2 : Int32
+
+ def initialize(@mantissa : UInt64 = 0, @power2 : Int32 = 0)
+ end
+ end
+
+ INVALID_AM_BIAS = -0x8000
+
+ CONSTANT_55555 = 3125_u64
+
+ module BinaryFormat(T, EquivUint)
+ end
+
+ struct BinaryFormat_Float64
+ include BinaryFormat(Float64, UInt64)
+
+ POWERS_OF_TEN = [
+ 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11,
+ 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22,
+ ]
+
+ # Largest integer value v so that (5**index * v) <= 1<<53.
+ # 0x20000000000000 == 1 << 53
+ MAX_MANTISSA = [
+ 0x20000000000000_u64,
+ 0x20000000000000_u64.unsafe_div(5),
+ 0x20000000000000_u64.unsafe_div(5 * 5),
+ 0x20000000000000_u64.unsafe_div(5 * 5 * 5),
+ 0x20000000000000_u64.unsafe_div(5 * 5 * 5 * 5),
+ 0x20000000000000_u64.unsafe_div(CONSTANT_55555),
+ 0x20000000000000_u64.unsafe_div(CONSTANT_55555 * 5),
+ 0x20000000000000_u64.unsafe_div(CONSTANT_55555 * 5 * 5),
+ 0x20000000000000_u64.unsafe_div(CONSTANT_55555 * 5 * 5 * 5),
+ 0x20000000000000_u64.unsafe_div(CONSTANT_55555 * 5 * 5 * 5 * 5),
+ 0x20000000000000_u64.unsafe_div(CONSTANT_55555 * CONSTANT_55555),
+ 0x20000000000000_u64.unsafe_div(CONSTANT_55555 * CONSTANT_55555 * 5),
+ 0x20000000000000_u64.unsafe_div(CONSTANT_55555 * CONSTANT_55555 * 5 * 5),
+ 0x20000000000000_u64.unsafe_div(CONSTANT_55555 * CONSTANT_55555 * 5 * 5 * 5),
+ 0x20000000000000_u64.unsafe_div(CONSTANT_55555 * CONSTANT_55555 * CONSTANT_55555),
+ 0x20000000000000_u64.unsafe_div(CONSTANT_55555 * CONSTANT_55555 * CONSTANT_55555 * 5),
+ 0x20000000000000_u64.unsafe_div(CONSTANT_55555 * CONSTANT_55555 * CONSTANT_55555 * 5 * 5),
+ 0x20000000000000_u64.unsafe_div(CONSTANT_55555 * CONSTANT_55555 * CONSTANT_55555 * 5 * 5 * 5),
+ 0x20000000000000_u64.unsafe_div(CONSTANT_55555 * CONSTANT_55555 * CONSTANT_55555 * 5 * 5 * 5 * 5),
+ 0x20000000000000_u64.unsafe_div(CONSTANT_55555 * CONSTANT_55555 * CONSTANT_55555 * CONSTANT_55555),
+ 0x20000000000000_u64.unsafe_div(CONSTANT_55555 * CONSTANT_55555 * CONSTANT_55555 * CONSTANT_55555 * 5),
+ 0x20000000000000_u64.unsafe_div(CONSTANT_55555 * CONSTANT_55555 * CONSTANT_55555 * CONSTANT_55555 * 5 * 5),
+ 0x20000000000000_u64.unsafe_div(CONSTANT_55555 * CONSTANT_55555 * CONSTANT_55555 * CONSTANT_55555 * 5 * 5 * 5),
+ 0x20000000000000_u64.unsafe_div(CONSTANT_55555 * CONSTANT_55555 * CONSTANT_55555 * CONSTANT_55555 * 5 * 5 * 5 * 5),
+ ]
+
+ def min_exponent_fast_path : Int32
+ -22
+ end
+
+ def mantissa_explicit_bits : Int32
+ 52
+ end
+
+ def max_exponent_round_to_even : Int32
+ 23
+ end
+
+ def min_exponent_round_to_even : Int32
+ -4
+ end
+
+ def minimum_exponent : Int32
+ -1023
+ end
+
+ def infinite_power : Int32
+ 0x7FF
+ end
+
+ def sign_index : Int32
+ 63
+ end
+
+ def max_exponent_fast_path : Int32
+ 22
+ end
+
+ def max_mantissa_fast_path : UInt64
+ 0x20000000000000_u64
+ end
+
+ def max_mantissa_fast_path(power : Int64) : UInt64
+ # caller is responsible to ensure that
+ # power >= 0 && power <= 22
+ MAX_MANTISSA.unsafe_fetch(power)
+ end
+
+ def exact_power_of_ten(power : Int64) : Float64
+ POWERS_OF_TEN.unsafe_fetch(power)
+ end
+
+ def largest_power_of_ten : Int32
+ 308
+ end
+
+ def smallest_power_of_ten : Int32
+ -342
+ end
+
+ def max_digits : Int32
+ 769
+ end
+
+ def exponent_mask : EquivUint
+ 0x7FF0000000000000_u64
+ end
+
+ def mantissa_mask : EquivUint
+ 0x000FFFFFFFFFFFFF_u64
+ end
+
+ def hidden_bit_mask : EquivUint
+ 0x0010000000000000_u64
+ end
+ end
+
+ struct BinaryFormat_Float32
+ include BinaryFormat(Float32, UInt32)
+
+ POWERS_OF_TEN = [
+ 1e0f32, 1e1f32, 1e2f32, 1e3f32, 1e4f32, 1e5f32, 1e6f32, 1e7f32, 1e8f32, 1e9f32, 1e10f32,
+ ]
+
+ # Largest integer value v so that (5**index * v) <= 1<<24.
+ # 0x1000000 == 1<<24
+ MAX_MANTISSA = [
+ 0x1000000_u64,
+ 0x1000000_u64.unsafe_div(5),
+ 0x1000000_u64.unsafe_div(5 * 5),
+ 0x1000000_u64.unsafe_div(5 * 5 * 5),
+ 0x1000000_u64.unsafe_div(5 * 5 * 5 * 5),
+ 0x1000000_u64.unsafe_div(CONSTANT_55555),
+ 0x1000000_u64.unsafe_div(CONSTANT_55555 * 5),
+ 0x1000000_u64.unsafe_div(CONSTANT_55555 * 5 * 5),
+ 0x1000000_u64.unsafe_div(CONSTANT_55555 * 5 * 5 * 5),
+ 0x1000000_u64.unsafe_div(CONSTANT_55555 * 5 * 5 * 5 * 5),
+ 0x1000000_u64.unsafe_div(CONSTANT_55555 * CONSTANT_55555),
+ 0x1000000_u64.unsafe_div(CONSTANT_55555 * CONSTANT_55555 * 5),
+ ]
+
+ def min_exponent_fast_path : Int32
+ -10
+ end
+
+ def mantissa_explicit_bits : Int32
+ 23
+ end
+
+ def max_exponent_round_to_even : Int32
+ 10
+ end
+
+ def min_exponent_round_to_even : Int32
+ -17
+ end
+
+ def minimum_exponent : Int32
+ -127
+ end
+
+ def infinite_power : Int32
+ 0xFF
+ end
+
+ def sign_index : Int32
+ 31
+ end
+
+ def max_exponent_fast_path : Int32
+ 10
+ end
+
+ def max_mantissa_fast_path : UInt64
+ 0x1000000_u64
+ end
+
+ def max_mantissa_fast_path(power : Int64) : UInt64
+ # caller is responsible to ensure that
+ # power >= 0 && power <= 10
+ MAX_MANTISSA.unsafe_fetch(power)
+ end
+
+ def exact_power_of_ten(power : Int64) : Float32
+ POWERS_OF_TEN.unsafe_fetch(power)
+ end
+
+ def largest_power_of_ten : Int32
+ 38
+ end
+
+ def smallest_power_of_ten : Int32
+ -64
+ end
+
+ def max_digits : Int32
+ 114
+ end
+
+ def exponent_mask : EquivUint
+ 0x7F800000_u32
+ end
+
+ def mantissa_mask : EquivUint
+ 0x007FFFFF_u32
+ end
+
+ def hidden_bit_mask : EquivUint
+ 0x00800000_u32
+ end
+ end
+
+ module BinaryFormat(T, EquivUint)
+ # NOTE(crystal): returns the new *value* by value
+ def to_float(negative : Bool, am : AdjustedMantissa) : T
+ word = EquivUint.new!(am.mantissa)
+ word |= EquivUint.new!(am.power2).unsafe_shl(mantissa_explicit_bits)
+ word |= EquivUint.new!(negative ? 1 : 0).unsafe_shl(sign_index)
+ word.unsafe_as(T)
+ end
+ end
+
+ def self.int_cmp_zeros(uc : UC.class) : UInt64 forall UC
+ case sizeof(UC)
+ when 1
+ 0x3030303030303030_u64
+ when 2
+ 0x0030003000300030_u64
+ else
+ 0x0000003000000030_u64
+ end
+ end
+
+ def self.int_cmp_len(uc : UC.class) : Int32 forall UC
+ sizeof(UInt64).unsafe_div(sizeof(UC))
+ end
+end
diff --git a/src/float/fast_float/parse_number.cr b/src/float/fast_float/parse_number.cr
new file mode 100644
index 000000000000..3c1ac4c1cb24
--- /dev/null
+++ b/src/float/fast_float/parse_number.cr
@@ -0,0 +1,197 @@
+require "./ascii_number"
+require "./decimal_to_binary"
+require "./digit_comparison"
+require "./float_common"
+
+module Float::FastFloat
+ module Detail
+ def self.parse_infnan(first : UC*, last : UC*, value : T*) : FromCharsResultT(UC) forall T, UC
+ ptr = first
+ ec = Errno::NONE # be optimistic
+ minus_sign = false
+ if first.value === '-' # assume first < last, so dereference without checks
+ minus_sign = true
+ first += 1
+ elsif first.value === '+'
+ first += 1
+ end
+
+ if last - first >= 3
+ if FastFloat.fastfloat_strncasecmp(first, "nan".to_unsafe, 3)
+ first += 3
+ ptr = first
+ value.value = minus_sign ? -T::NAN : T::NAN
+ # Check for possible nan(n-char-seq-opt), C++17 20.19.3.7,
+ # C11 7.20.1.3.3. At least MSVC produces nan(ind) and nan(snan).
+ if first != last && first.value === '('
+ ptr2 = first + 1
+ while ptr2 != last
+ case ptr2.value.unsafe_chr
+ when ')'
+ ptr = ptr2 + 1 # valid nan(n-char-seq-opt)
+ break
+ when 'a'..'z', 'A'..'Z', '0'..'9', '_'
+ # Do nothing
+ else
+ break # forbidden char, not nan(n-char-seq-opt)
+ end
+ ptr2 += 1
+ end
+ end
+ return FromCharsResultT(UC).new(ptr, ec)
+ end
+ end
+ if FastFloat.fastfloat_strncasecmp(first, "inf".to_unsafe, 3)
+ if last - first >= 8 && FastFloat.fastfloat_strncasecmp(first + 3, "inity".to_unsafe, 5)
+ ptr = first + 8
+ else
+ ptr = first + 3
+ end
+ value.value = minus_sign ? -T::INFINITY : T::INFINITY
+ return FromCharsResultT(UC).new(ptr, ec)
+ end
+
+ ec = Errno::EINVAL
+ FromCharsResultT(UC).new(ptr, ec)
+ end
+
+ # See
+ # A fast function to check your floating-point rounding mode
+ # https://lemire.me/blog/2022/11/16/a-fast-function-to-check-your-floating-point-rounding-mode/
+ #
+ # This function is meant to be equivalent to :
+ # prior: #include
+ # return fegetround() == FE_TONEAREST;
+ # However, it is expected to be much faster than the fegetround()
+ # function call.
+ #
+ # NOTE(crystal): uses a pointer instead of a volatile variable to prevent
+ # LLVM optimization
+ @@fmin : Float32* = Pointer(Float32).malloc(1, Float32::MIN_POSITIVE)
+
+ # Returns true if the floating-pointing rounding mode is to 'nearest'.
+ # It is the default on most system. This function is meant to be inexpensive.
+ # Credit : @mwalcott3
+ def self.rounds_to_nearest? : Bool
+ fmin = @@fmin.value # we copy it so that it gets loaded at most once.
+
+ # Explanation:
+ # Only when fegetround() == FE_TONEAREST do we have that
+ # fmin + 1.0f == 1.0f - fmin.
+ #
+ # FE_UPWARD:
+ # fmin + 1.0f > 1
+ # 1.0f - fmin == 1
+ #
+ # FE_DOWNWARD or FE_TOWARDZERO:
+ # fmin + 1.0f == 1
+ # 1.0f - fmin < 1
+ #
+ # Note: This may fail to be accurate if fast-math has been
+ # enabled, as rounding conventions may not apply.
+ fmin + 1.0_f32 == 1.0_f32 - fmin
+ end
+ end
+
+ module BinaryFormat(T, EquivUint)
+ def from_chars_advanced(pns : ParsedNumberStringT(UC), value : T*) : FromCharsResultT(UC) forall UC
+ {% raise "only some floating-point types are supported" unless T == Float32 || T == Float64 %}
+
+ # TODO(crystal): support UInt16 and UInt32
+ {% raise "only UInt8 is supported" unless UC == UInt8 %}
+
+ ec = Errno::NONE # be optimistic
+ ptr = pns.lastmatch
+ # The implementation of the Clinger's fast path is convoluted because
+ # we want round-to-nearest in all cases, irrespective of the rounding mode
+ # selected on the thread.
+ # We proceed optimistically, assuming that detail::rounds_to_nearest()
+ # returns true.
+ if (min_exponent_fast_path <= pns.exponent <= max_exponent_fast_path) && !pns.too_many_digits
+ # Unfortunately, the conventional Clinger's fast path is only possible
+ # when the system rounds to the nearest float.
+ #
+ # We expect the next branch to almost always be selected.
+ # We could check it first (before the previous branch), but
+ # there might be performance advantages at having the check
+ # be last.
+ if Detail.rounds_to_nearest?
+ # We have that fegetround() == FE_TONEAREST.
+ # Next is Clinger's fast path.
+ if pns.mantissa <= max_mantissa_fast_path
+ if pns.mantissa == 0
+ value.value = pns.negative ? T.new(-0.0) : T.new(0.0)
+ return FromCharsResultT(UC).new(ptr, ec)
+ end
+ value.value = T.new(pns.mantissa)
+ if pns.exponent < 0
+ value.value /= exact_power_of_ten(0_i64 &- pns.exponent)
+ else
+ value.value *= exact_power_of_ten(pns.exponent)
+ end
+ if pns.negative
+ value.value = -value.value
+ end
+ return FromCharsResultT(UC).new(ptr, ec)
+ end
+ else
+ # We do not have that fegetround() == FE_TONEAREST.
+ # Next is a modified Clinger's fast path, inspired by Jakub Jelínek's
+ # proposal
+ if pns.exponent >= 0 && pns.mantissa <= max_mantissa_fast_path(pns.exponent)
+ # Clang may map 0 to -0.0 when fegetround() == FE_DOWNWARD
+ if pns.mantissa == 0
+ value.value = pns.negative ? T.new(-0.0) : T.new(0.0)
+ return FromCharsResultT(UC).new(ptr, ec)
+ end
+ value.value = T.new(pns.mantissa) * exact_power_of_ten(pns.exponent)
+ if pns.negative
+ value.value = -value.value
+ end
+ return FromCharsResultT(UC).new(ptr, ec)
+ end
+ end
+ end
+ am = compute_float(pns.exponent, pns.mantissa)
+ if pns.too_many_digits && am.power2 >= 0
+ if am != compute_float(pns.exponent, pns.mantissa &+ 1)
+ am = compute_error(pns.exponent, pns.mantissa)
+ end
+ end
+ # If we called compute_float>(pns.exponent, pns.mantissa)
+ # and we have an invalid power (am.power2 < 0), then we need to go the long
+ # way around again. This is very uncommon.
+ if am.power2 < 0
+ am = digit_comp(pns, am)
+ end
+ value.value = to_float(pns.negative, am)
+ # Test for over/underflow.
+ if (pns.mantissa != 0 && am.mantissa == 0 && am.power2 == 0) || am.power2 == infinite_power
+ ec = Errno::ERANGE
+ end
+ FromCharsResultT(UC).new(ptr, ec)
+ end
+
+ def from_chars_advanced(first : UC*, last : UC*, value : T*, options : ParseOptionsT(UC)) : FromCharsResultT(UC) forall UC
+ {% raise "only some floating-point types are supported" unless T == Float32 || T == Float64 %}
+
+ # TODO(crystal): support UInt16 and UInt32
+ {% raise "only UInt8 is supported" unless UC == UInt8 %}
+
+ if first == last
+ return FromCharsResultT(UC).new(first, Errno::EINVAL)
+ end
+ pns = FastFloat.parse_number_string(first, last, options)
+ if !pns.valid
+ if options.format.no_infnan?
+ return FromCharsResultT(UC).new(first, Errno::EINVAL)
+ else
+ return Detail.parse_infnan(first, last, value)
+ end
+ end
+
+ # call overload that takes parsed_number_string_t directly.
+ from_chars_advanced(pns, value)
+ end
+ end
+end
diff --git a/src/gc/boehm.cr b/src/gc/boehm.cr
index 8ccc1bb7b6e8..327b3d50409f 100644
--- a/src/gc/boehm.cr
+++ b/src/gc/boehm.cr
@@ -32,6 +32,11 @@ require "crystal/tracing"
@[Link("gc", pkg_config: "bdw-gc")]
{% end %}
+# Supported library versions:
+#
+# * libgc (8.2.0+; earlier versions require a patch for MT support)
+#
+# See https://crystal-lang.org/reference/man/required_libraries.html#other-runtime-libraries
{% if compare_versions(Crystal::VERSION, "1.11.0-dev") >= 0 %}
@[Link(dll: "gc.dll")]
{% end %}
@@ -161,11 +166,16 @@ lib LibGC
alias WarnProc = LibC::Char*, Word ->
fun set_warn_proc = GC_set_warn_proc(WarnProc)
$warn_proc = GC_current_warn_proc : WarnProc
+
+ fun stop_world_external = GC_stop_world_external
+ fun start_world_external = GC_start_world_external
+ fun get_suspend_signal = GC_get_suspend_signal : Int
+ fun get_thr_restart_signal = GC_get_thr_restart_signal : Int
end
module GC
{% if flag?(:preview_mt) %}
- @@lock = Crystal::RWLock.new
+ @@lock = uninitialized Crystal::RWLock
{% end %}
# :nodoc:
@@ -195,10 +205,33 @@ module GC
{% end %}
LibGC.init
- LibGC.set_start_callback ->do
+ {% if flag?(:preview_mt) %}
+ @@lock = Crystal::RWLock.new
+ {% end %}
+
+ LibGC.set_start_callback -> do
GC.lock_write
end
+ # pushes the stack of pending fibers when the GC wants to collect memory:
+ {% unless flag?(:interpreted) %}
+ GC.before_collect do
+ Fiber.unsafe_each do |fiber|
+ fiber.push_gc_roots unless fiber.running?
+ end
+
+ {% if flag?(:preview_mt) %}
+ Thread.unsafe_each do |thread|
+ if fiber = thread.current_fiber?
+ GC.set_stackbottom(thread.gc_thread_handler, fiber.@stack_bottom)
+ end
+ end
+ {% end %}
+
+ GC.unlock_write
+ end
+ {% end %}
+
{% if flag?(:tracing) %}
if ::Crystal::Tracing.enabled?(:gc)
set_on_heap_resize_proc
@@ -446,28 +479,31 @@ module GC
@@curr_push_other_roots = block
@@prev_push_other_roots = LibGC.get_push_other_roots
- LibGC.set_push_other_roots ->do
+ LibGC.set_push_other_roots -> do
@@curr_push_other_roots.try(&.call)
@@prev_push_other_roots.try(&.call)
end
end
- # pushes the stack of pending fibers when the GC wants to collect memory:
- {% unless flag?(:interpreted) %}
- GC.before_collect do
- Fiber.unsafe_each do |fiber|
- fiber.push_gc_roots unless fiber.running?
- end
+ # :nodoc:
+ def self.stop_world : Nil
+ LibGC.stop_world_external
+ end
- {% if flag?(:preview_mt) %}
- Thread.unsafe_each do |thread|
- if fiber = thread.current_fiber?
- GC.set_stackbottom(thread.gc_thread_handler, fiber.@stack_bottom)
- end
- end
- {% end %}
+ # :nodoc:
+ def self.start_world : Nil
+ LibGC.start_world_external
+ end
+
+ {% if flag?(:unix) %}
+ # :nodoc:
+ def self.sig_suspend : Signal
+ Signal.new(LibGC.get_suspend_signal)
+ end
- GC.unlock_write
+ # :nodoc:
+ def self.sig_resume : Signal
+ Signal.new(LibGC.get_thr_restart_signal)
end
{% end %}
end
diff --git a/src/gc/none.cr b/src/gc/none.cr
index 640e6e8f927d..651027266e5b 100644
--- a/src/gc/none.cr
+++ b/src/gc/none.cr
@@ -1,30 +1,53 @@
{% if flag?(:win32) %}
require "c/process"
+ require "c/heapapi"
{% end %}
require "crystal/tracing"
module GC
def self.init
+ Crystal::System::Thread.init_suspend_resume
end
# :nodoc:
def self.malloc(size : LibC::SizeT) : Void*
Crystal.trace :gc, "malloc", size: size
- # libc malloc is not guaranteed to return cleared memory, so we need to
- # explicitly clear it. Ref: https://github.com/crystal-lang/crystal/issues/14678
- LibC.malloc(size).tap(&.clear)
+
+ {% if flag?(:win32) %}
+ LibC.HeapAlloc(LibC.GetProcessHeap, LibC::HEAP_ZERO_MEMORY, size)
+ {% else %}
+ # libc malloc is not guaranteed to return cleared memory, so we need to
+ # explicitly clear it. Ref: https://github.com/crystal-lang/crystal/issues/14678
+ LibC.malloc(size).tap(&.clear)
+ {% end %}
end
# :nodoc:
def self.malloc_atomic(size : LibC::SizeT) : Void*
Crystal.trace :gc, "malloc", size: size, atomic: 1
- LibC.malloc(size)
+
+ {% if flag?(:win32) %}
+ LibC.HeapAlloc(LibC.GetProcessHeap, 0, size)
+ {% else %}
+ LibC.malloc(size)
+ {% end %}
end
# :nodoc:
def self.realloc(pointer : Void*, size : LibC::SizeT) : Void*
Crystal.trace :gc, "realloc", size: size
- LibC.realloc(pointer, size)
+
+ {% if flag?(:win32) %}
+ # realloc with a null pointer should behave like plain malloc, but Win32
+ # doesn't do that
+ if pointer
+ LibC.HeapReAlloc(LibC.GetProcessHeap, LibC::HEAP_ZERO_MEMORY, pointer, size)
+ else
+ LibC.HeapAlloc(LibC.GetProcessHeap, LibC::HEAP_ZERO_MEMORY, size)
+ end
+ {% else %}
+ LibC.realloc(pointer, size)
+ {% end %}
end
def self.collect
@@ -38,7 +61,12 @@ module GC
def self.free(pointer : Void*) : Nil
Crystal.trace :gc, "free"
- LibC.free(pointer)
+
+ {% if flag?(:win32) %}
+ LibC.HeapFree(LibC.GetProcessHeap, 0, pointer)
+ {% else %}
+ LibC.free(pointer)
+ {% end %}
end
def self.is_heap_ptr(pointer : Void*) : Bool
@@ -138,4 +166,57 @@ module GC
# :nodoc:
def self.push_stack(stack_top, stack_bottom)
end
+
+ # Stop and start the world.
+ #
+ # This isn't a GC-safe stop-the-world implementation (it may allocate objects
+ # while stopping the world), but the guarantees are enough for the purpose of
+ # gc_none. It could be GC-safe if Thread::LinkedList(T) became a struct, and
+ # Thread::Mutex either became a struct or provide low level abstraction
+ # methods that directly interact with syscalls (without allocating).
+ #
+ # Thread safety is guaranteed by the mutex in Thread::LinkedList: either a
+ # thread is starting and hasn't added itself to the list (it will block until
+ # it can acquire the lock), or is currently adding itself (the current thread
+ # will block until it can acquire the lock).
+ #
+ # In both cases there can't be a deadlock since we won't suspend another
+ # thread until it has successfully added (or removed) itself to (from) the
+ # linked list and released the lock, and the other thread won't progress until
+ # it can add (or remove) itself from the list.
+ #
+ # Finally, we lock the mutex and keep it locked until we resume the world, so
+ # any thread waiting on the mutex will only be resumed when the world is
+ # resumed.
+
+ # :nodoc:
+ def self.stop_world : Nil
+ current_thread = Thread.current
+
+ # grab the lock (and keep it until the world is restarted)
+ Thread.lock
+
+ # tell all threads to stop (async)
+ Thread.unsafe_each do |thread|
+ thread.suspend unless thread == current_thread
+ end
+
+ # wait for all threads to have stopped
+ Thread.unsafe_each do |thread|
+ thread.wait_suspended unless thread == current_thread
+ end
+ end
+
+ # :nodoc:
+ def self.start_world : Nil
+ current_thread = Thread.current
+
+ # tell all threads to resume
+ Thread.unsafe_each do |thread|
+ thread.resume unless thread == current_thread
+ end
+
+ # finally, we can release the lock
+ Thread.unlock
+ end
end
diff --git a/src/hash.cr b/src/hash.cr
index 8d48e1cd8c08..c145bda36309 100644
--- a/src/hash.cr
+++ b/src/hash.cr
@@ -236,12 +236,14 @@ class Hash(K, V)
# Translate initial capacity to the nearest power of 2, but keep it a minimum of 8.
if initial_capacity < 8
initial_entries_size = 8
+ elsif initial_capacity > 2**30
+ initial_entries_size = Int32::MAX
else
initial_entries_size = Math.pw2ceil(initial_capacity)
end
# Because we always keep indice_size >= entries_size * 2
- initial_indices_size = initial_entries_size * 2
+ initial_indices_size = initial_entries_size.to_u64 * 2
@entries = malloc_entries(initial_entries_size)
@@ -830,7 +832,7 @@ class Hash(K, V)
# The actual number of bytes needed to allocate `@indices`.
private def indices_malloc_size(size)
- size * @indices_bytesize
+ size.to_u64 * @indices_bytesize
end
# Reallocates `size` number of indices for `@indices`.
@@ -872,7 +874,8 @@ class Hash(K, V)
# Marks an entry in `@entries` at `index` as deleted
# *without* modifying any counters (`@size` and `@deleted_count`).
private def delete_entry(index) : Nil
- set_entry(index, Entry(K, V).deleted)
+ # sets `Entry#@hash` to 0 and removes stale references to key and value
+ (@entries + index).clear
end
# Marks an entry in `@entries` at `index` as deleted
@@ -1054,7 +1057,7 @@ class Hash(K, V)
self
end
- # Returns `true` of this Hash is comparing keys by `object_id`.
+ # Returns `true` if this Hash is comparing keys by `object_id`.
#
# See `compare_by_identity`.
getter? compare_by_identity : Bool
@@ -1746,7 +1749,8 @@ class Hash(K, V)
# hash.transform_keys { |key, value| key.to_s * value } # => {"a" => 1, "bb" => 2, "ccc" => 3}
# ```
def transform_keys(& : K, V -> K2) : Hash(K2, V) forall K2
- each_with_object({} of K2 => V) do |(key, value), memo|
+ copy = Hash(K2, V).new(initial_capacity: entries_capacity)
+ each_with_object(copy) do |(key, value), memo|
memo[yield(key, value)] = value
end
end
@@ -1761,7 +1765,8 @@ class Hash(K, V)
# hash.transform_values { |value, key| "#{key}#{value}" } # => {:a => "a1", :b => "b2", :c => "c3"}
# ```
def transform_values(& : V, K -> V2) : Hash(K, V2) forall V2
- each_with_object({} of K => V2) do |(key, value), memo|
+ copy = Hash(K, V2).new(initial_capacity: entries_capacity)
+ each_with_object(copy) do |(key, value), memo|
memo[key] = yield(value, key)
end
end
@@ -2078,7 +2083,7 @@ class Hash(K, V)
#
# The order of the array follows the order the keys were inserted in the Hash.
def to_a : Array({K, V})
- to_a(&.itself)
+ super
end
# Returns an `Array` with the results of running *block* against tuples with key and values
@@ -2148,18 +2153,13 @@ class Hash(K, V)
hash
end
+ # :nodoc:
struct Entry(K, V)
getter key, value, hash
def initialize(@hash : UInt32, @key : K, @value : V)
end
- def self.deleted
- key = uninitialized K
- value = uninitialized V
- new(0_u32, key, value)
- end
-
def deleted? : Bool
@hash == 0_u32
end
diff --git a/src/http/client.cr b/src/http/client.cr
index b641065ac930..7324bdf7d639 100644
--- a/src/http/client.cr
+++ b/src/http/client.cr
@@ -343,10 +343,10 @@ class HTTP::Client
# ```
setter connect_timeout : Time::Span?
- # **This method has no effect right now**
- #
# Sets the number of seconds to wait when resolving a name, before raising an `IO::TimeoutError`.
#
+ # NOTE: *dns_timeout* is currently only supported on Windows.
+ #
# ```
# require "http/client"
#
@@ -363,10 +363,10 @@ class HTTP::Client
self.dns_timeout = dns_timeout.seconds
end
- # **This method has no effect right now**
- #
# Sets the number of seconds to wait when resolving a name with a `Time::Span`, before raising an `IO::TimeoutError`.
#
+ # NOTE: *dns_timeout* is currently only supported on Windows.
+ #
# ```
# require "http/client"
#
diff --git a/src/http/cookie.cr b/src/http/cookie.cr
index 8138249aa830..8a9a29855318 100644
--- a/src/http/cookie.cr
+++ b/src/http/cookie.cr
@@ -104,31 +104,82 @@ module HTTP
end
end
+ # Returns an unambiguous string representation of this cookie.
+ #
+ # It uses the `Set-Cookie` serialization from `#to_set_cookie_header` which
+ # represents the full state of the cookie.
+ #
+ # ```
+ # HTTP::Cookie.new("foo", "bar").inspect # => HTTP::Cookie["foo=bar"]
+ # HTTP::Cookie.new("foo", "bar", domain: "example.com").inspect # => HTTP::Cookie["foo=bar; domain=example.com"]
+ # ```
+ def inspect(io : IO) : Nil
+ io << "HTTP::Cookie["
+ to_s.inspect(io)
+ io << "]"
+ end
+
+ # Returns a string representation of this cookie.
+ #
+ # It uses the `Set-Cookie` serialization from `#to_set_cookie_header` which
+ # represents the full state of the cookie.
+ #
+ # ```
+ # HTTP::Cookie.new("foo", "bar").to_s # => "foo=bar"
+ # HTTP::Cookie.new("foo", "bar", domain: "example.com").to_s # => "foo=bar; domain=example.com"
+ # ```
+ def to_s(io : IO) : Nil
+ to_set_cookie_header(io)
+ end
+
+ # Returns a string representation of this cookie in the format used by the
+ # `Set-Cookie` header of an HTTP response.
+ #
+ # ```
+ # HTTP::Cookie.new("foo", "bar").to_set_cookie_header # => "foo=bar"
+ # HTTP::Cookie.new("foo", "bar", domain: "example.com").to_set_cookie_header # => "foo=bar; domain=example.com"
+ # ```
def to_set_cookie_header : String
+ String.build do |header|
+ to_set_cookie_header(header)
+ end
+ end
+
+ # :ditto:
+ def to_set_cookie_header(io : IO) : Nil
path = @path
expires = @expires
max_age = @max_age
domain = @domain
samesite = @samesite
- String.build do |header|
- to_cookie_header(header)
- header << "; domain=#{domain}" if domain
- header << "; path=#{path}" if path
- header << "; expires=#{HTTP.format_time(expires)}" if expires
- header << "; max-age=#{max_age.to_i}" if max_age
- header << "; Secure" if @secure
- header << "; HttpOnly" if @http_only
- header << "; SameSite=#{samesite}" if samesite
- header << "; #{@extension}" if @extension
- end
- end
+ to_cookie_header(io)
+ io << "; domain=#{domain}" if domain
+ io << "; path=#{path}" if path
+ io << "; expires=#{HTTP.format_time(expires)}" if expires
+ io << "; max-age=#{max_age.to_i}" if max_age
+ io << "; Secure" if @secure
+ io << "; HttpOnly" if @http_only
+ io << "; SameSite=#{samesite}" if samesite
+ io << "; #{@extension}" if @extension
+ end
+
+ # Returns a string representation of this cookie in the format used by the
+ # `Cookie` header of an HTTP request.
+ # This includes only the `#name` and `#value`. All other attributes are left
+ # out.
+ #
+ # ```
+ # HTTP::Cookie.new("foo", "bar").to_cookie_header # => "foo=bar"
+ # HTTP::Cookie.new("foo", "bar", domain: "example.com").to_cookie_header # => "foo=bar
+ # ```
def to_cookie_header : String
String.build(@name.bytesize + @value.bytesize + 1) do |io|
to_cookie_header(io)
end
end
+ # :ditto:
def to_cookie_header(io) : Nil
io << @name
io << '='
@@ -192,6 +243,24 @@ module HTTP
end
end
+ # Expires the cookie.
+ #
+ # Causes the cookie to be destroyed. Sets the value to the empty string and
+ # expires its lifetime.
+ #
+ # ```
+ # cookie = HTTP::Cookie.new("hello", "world")
+ # cookie.expire
+ #
+ # cookie.value # => ""
+ # cookie.expired? # => true
+ # ```
+ def expire
+ self.value = ""
+ self.expires = Time::UNIX_EPOCH
+ self.max_age = Time::Span.zero
+ end
+
# :nodoc:
module Parser
module Regex
@@ -488,5 +557,32 @@ module HTTP
def to_h : Hash(String, Cookie)
@cookies.dup
end
+
+ # Returns a string representation of this cookies list.
+ #
+ # It uses the `Set-Cookie` serialization from `Cookie#to_set_cookie_header` which
+ # represents the full state of the cookie.
+ #
+ # ```
+ # HTTP::Cookies{
+ # HTTP::Cookie.new("foo", "bar"),
+ # HTTP::Cookie.new("foo", "bar", domain: "example.com"),
+ # }.to_s # => "HTTP::Cookies{\"foo=bar\", \"foo=bar; domain=example.com\"}"
+ # ```
+ def to_s(io : IO)
+ io << "HTTP::Cookies{"
+ join(io, ", ") { |cookie| cookie.to_set_cookie_header.inspect(io) }
+ io << "}"
+ end
+
+ # :ditto:
+ def inspect(io : IO)
+ to_s(io)
+ end
+
+ # :ditto:
+ def pretty_print(pp) : Nil
+ pp.list("HTTP::Cookies{", self, "}") { |elem| pp.text(elem.to_set_cookie_header.inspect) }
+ end
end
end
diff --git a/src/http/server/handlers/static_file_handler.cr b/src/http/server/handlers/static_file_handler.cr
index cba0ff993ad2..665f466c7e46 100644
--- a/src/http/server/handlers/static_file_handler.cr
+++ b/src/http/server/handlers/static_file_handler.cr
@@ -68,7 +68,7 @@ class HTTP::StaticFileHandler
file_path = @public_dir.join(expanded_path.to_kind(Path::Kind.native))
file_info = File.info? file_path
- is_dir = file_info && file_info.directory?
+ is_dir = @directory_listing && file_info && file_info.directory?
is_file = file_info && file_info.file?
if request_path != expanded_path || is_dir && !is_dir_path
@@ -85,7 +85,7 @@ class HTTP::StaticFileHandler
context.response.headers["Accept-Ranges"] = "bytes"
- if @directory_listing && is_dir
+ if is_dir
context.response.content_type = "text/html; charset=utf-8"
directory_listing(context.response, request_path, file_path)
elsif is_file
diff --git a/src/http/server/response.cr b/src/http/server/response.cr
index 5c80b31cce00..4dd6968ac560 100644
--- a/src/http/server/response.cr
+++ b/src/http/server/response.cr
@@ -255,7 +255,9 @@ class HTTP::Server
private def unbuffered_write(slice : Bytes) : Nil
return if slice.empty?
- unless response.wrote_headers?
+ if response.headers["Transfer-Encoding"]? == "chunked"
+ @chunked = true
+ elsif !response.wrote_headers?
if response.version != "HTTP/1.0" && !response.headers.has_key?("Content-Length")
response.headers["Transfer-Encoding"] = "chunked"
@chunked = true
@@ -289,7 +291,7 @@ class HTTP::Server
status = response.status
set_content_length = !(status.not_modified? || status.no_content? || status.informational?)
- if !response.wrote_headers? && !response.headers.has_key?("Content-Length") && set_content_length
+ if !response.wrote_headers? && !response.headers.has_key?("Transfer-Encoding") && !response.headers.has_key?("Content-Length") && set_content_length
response.content_length = @out_count
end
diff --git a/src/humanize.cr b/src/humanize.cr
index bb285fe3a07d..e3e4ed4428c7 100644
--- a/src/humanize.cr
+++ b/src/humanize.cr
@@ -151,18 +151,21 @@ struct Number
# *separator* describes the decimal separator, *delimiter* the thousands
# delimiter (see `#format`).
#
+ # *unit_separator* is inserted between the value and the unit.
+ # Users are encouraged to use a non-breaking space ('\u00A0') to prevent output being split across lines.
+ #
# See `Int#humanize_bytes` to format a file size.
- def humanize(io : IO, precision = 3, separator = '.', delimiter = ',', *, base = 10 ** 3, significant = true, prefixes : Indexable = SI_PREFIXES) : Nil
- humanize(io, precision, separator, delimiter, base: base, significant: significant) do |magnitude, _|
+ def humanize(io : IO, precision = 3, separator = '.', delimiter = ',', *, base = 10 ** 3, significant = true, unit_separator = nil, prefixes : Indexable = SI_PREFIXES) : Nil
+ humanize(io, precision, separator, delimiter, base: base, significant: significant, unit_separator: unit_separator) do |magnitude, _|
magnitude = Number.prefix_index(magnitude, prefixes: prefixes)
{magnitude, Number.si_prefix(magnitude, prefixes)}
end
end
# :ditto:
- def humanize(precision = 3, separator = '.', delimiter = ',', *, base = 10 ** 3, significant = true, prefixes = SI_PREFIXES) : String
+ def humanize(precision = 3, separator = '.', delimiter = ',', *, base = 10 ** 3, significant = true, unit_separator = nil, prefixes = SI_PREFIXES) : String
String.build do |io|
- humanize(io, precision, separator, delimiter, base: base, significant: significant, prefixes: prefixes)
+ humanize(io, precision, separator, delimiter, base: base, significant: significant, unit_separator: unit_separator, prefixes: prefixes)
end
end
@@ -215,8 +218,8 @@ struct Number
# ```
#
# See `Int#humanize_bytes` to format a file size.
- def humanize(io : IO, precision = 3, separator = '.', delimiter = ',', *, base = 10 ** 3, significant = true, &prefixes : (Int32, Float64) -> {Int32, _} | {Int32, _, Bool}) : Nil
- if zero?
+ def humanize(io : IO, precision = 3, separator = '.', delimiter = ',', *, base = 10 ** 3, significant = true, unit_separator = nil, &prefixes : (Int32, Float64) -> {Int32, _} | {Int32, _, Bool}) : Nil
+ if zero? || (responds_to?(:infinite?) && self.infinite?) || (responds_to?(:nan?) && self.nan?)
digits = 0
else
log = Math.log10(abs)
@@ -259,29 +262,30 @@ struct Number
number.format(io, separator, delimiter, decimal_places: decimal_places, only_significant: significant)
+ io << unit_separator if unit
io << unit
end
# :ditto:
- def humanize(precision = 3, separator = '.', delimiter = ',', *, base = 10 ** 3, significant = true, &) : String
+ def humanize(precision = 3, separator = '.', delimiter = ',', *, base = 10 ** 3, significant = true, unit_separator = nil, &) : String
String.build do |io|
- humanize(io, precision, separator, delimiter, base: base, significant: significant) do |magnitude, number|
+ humanize(io, precision, separator, delimiter, base: base, significant: significant, unit_separator: unit_separator) do |magnitude, number|
yield magnitude, number
end
end
end
# :ditto:
- def humanize(io : IO, precision = 3, separator = '.', delimiter = ',', *, base = 10 ** 3, significant = true, prefixes : Proc) : Nil
- humanize(io, precision, separator, delimiter, base: base, significant: significant) do |magnitude, number|
+ def humanize(io : IO, precision = 3, separator = '.', delimiter = ',', *, base = 10 ** 3, significant = true, unit_separator = nil, prefixes : Proc) : Nil
+ humanize(io, precision, separator, delimiter, base: base, significant: significant, unit_separator: unit_separator) do |magnitude, number|
prefixes.call(magnitude, number)
end
end
# :ditto:
- def humanize(precision = 3, separator = '.', delimiter = ',', *, base = 10 ** 3, significant = true, prefixes : Proc) : String
+ def humanize(precision = 3, separator = '.', delimiter = ',', *, base = 10 ** 3, significant = true, unit_separator = nil, prefixes : Proc) : String
String.build do |io|
- humanize(io, precision, separator, delimiter, base: base, significant: significant, prefixes: prefixes)
+ humanize(io, precision, separator, delimiter, base: base, significant: significant, unit_separator: unit_separator, prefixes: prefixes)
end
end
end
@@ -321,7 +325,7 @@ struct Int
# ```
#
# See `Number#humanize` for more details on the behaviour and arguments.
- def humanize_bytes(io : IO, precision : Int = 3, separator = '.', *, significant : Bool = true, format : BinaryPrefixFormat = :IEC) : Nil
+ def humanize_bytes(io : IO, precision : Int = 3, separator = '.', *, significant : Bool = true, unit_separator = nil, format : BinaryPrefixFormat = :IEC) : Nil
humanize(io, precision, separator, nil, base: 1024, significant: significant) do |magnitude|
magnitude = Number.prefix_index(magnitude)
@@ -330,9 +334,9 @@ struct Int
unit = "B"
else
if format.iec?
- unit = "#{prefix}iB"
+ unit = "#{unit_separator}#{prefix}iB"
else
- unit = "#{prefix.upcase}B"
+ unit = "#{unit_separator}#{prefix.upcase}B"
end
end
{magnitude, unit, magnitude > 0}
@@ -340,9 +344,9 @@ struct Int
end
# :ditto:
- def humanize_bytes(precision : Int = 3, separator = '.', *, significant : Bool = true, format : BinaryPrefixFormat = :IEC) : String
+ def humanize_bytes(precision : Int = 3, separator = '.', *, significant : Bool = true, unit_separator = nil, format : BinaryPrefixFormat = :IEC) : String
String.build do |io|
- humanize_bytes(io, precision, separator, significant: significant, format: format)
+ humanize_bytes(io, precision, separator, significant: significant, unit_separator: unit_separator, format: format)
end
end
end
diff --git a/src/indexable.cr b/src/indexable.cr
index 4a3990e83870..3f6dca1762b1 100644
--- a/src/indexable.cr
+++ b/src/indexable.cr
@@ -693,17 +693,6 @@ module Indexable(T)
end
end
- # Returns an `Array` with all the elements in the collection.
- #
- # ```
- # {1, 2, 3}.to_a # => [1, 2, 3]
- # ```
- def to_a : Array(T)
- ary = Array(T).new(size)
- each { |e| ary << e }
- ary
- end
-
# Returns an `Array` with the results of running *block* against each element of the collection.
#
# ```
diff --git a/src/intrinsics.cr b/src/intrinsics.cr
index c5ae837d8931..3ccc47996e0b 100644
--- a/src/intrinsics.cr
+++ b/src/intrinsics.cr
@@ -163,8 +163,13 @@ lib LibIntrinsics
{% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_fshr128)] {% end %}
fun fshr128 = "llvm.fshr.i128"(a : UInt128, b : UInt128, count : UInt128) : UInt128
- fun va_start = "llvm.va_start"(ap : Void*)
- fun va_end = "llvm.va_end"(ap : Void*)
+ {% if compare_versions(Crystal::LLVM_VERSION, "19.1.0") < 0 %}
+ fun va_start = "llvm.va_start"(ap : Void*)
+ fun va_end = "llvm.va_end"(ap : Void*)
+ {% else %}
+ fun va_start = "llvm.va_start.p0"(ap : Void*)
+ fun va_end = "llvm.va_end.p0"(ap : Void*)
+ {% end %}
{% if flag?(:i386) || flag?(:x86_64) %}
{% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_pause)] {% end %}
@@ -179,7 +184,7 @@ end
module Intrinsics
macro debugtrap
- LibIntrinsics.debugtrap
+ ::LibIntrinsics.debugtrap
end
def self.pause
@@ -191,15 +196,15 @@ module Intrinsics
end
macro memcpy(dest, src, len, is_volatile)
- LibIntrinsics.memcpy({{dest}}, {{src}}, {{len}}, {{is_volatile}})
+ ::LibIntrinsics.memcpy({{dest}}, {{src}}, {{len}}, {{is_volatile}})
end
macro memmove(dest, src, len, is_volatile)
- LibIntrinsics.memmove({{dest}}, {{src}}, {{len}}, {{is_volatile}})
+ ::LibIntrinsics.memmove({{dest}}, {{src}}, {{len}}, {{is_volatile}})
end
macro memset(dest, val, len, is_volatile)
- LibIntrinsics.memset({{dest}}, {{val}}, {{len}}, {{is_volatile}})
+ ::LibIntrinsics.memset({{dest}}, {{val}}, {{len}}, {{is_volatile}})
end
def self.read_cycle_counter
@@ -263,43 +268,43 @@ module Intrinsics
end
macro countleading8(src, zero_is_undef)
- LibIntrinsics.countleading8({{src}}, {{zero_is_undef}})
+ ::LibIntrinsics.countleading8({{src}}, {{zero_is_undef}})
end
macro countleading16(src, zero_is_undef)
- LibIntrinsics.countleading16({{src}}, {{zero_is_undef}})
+ ::LibIntrinsics.countleading16({{src}}, {{zero_is_undef}})
end
macro countleading32(src, zero_is_undef)
- LibIntrinsics.countleading32({{src}}, {{zero_is_undef}})
+ ::LibIntrinsics.countleading32({{src}}, {{zero_is_undef}})
end
macro countleading64(src, zero_is_undef)
- LibIntrinsics.countleading64({{src}}, {{zero_is_undef}})
+ ::LibIntrinsics.countleading64({{src}}, {{zero_is_undef}})
end
macro countleading128(src, zero_is_undef)
- LibIntrinsics.countleading128({{src}}, {{zero_is_undef}})
+ ::LibIntrinsics.countleading128({{src}}, {{zero_is_undef}})
end
macro counttrailing8(src, zero_is_undef)
- LibIntrinsics.counttrailing8({{src}}, {{zero_is_undef}})
+ ::LibIntrinsics.counttrailing8({{src}}, {{zero_is_undef}})
end
macro counttrailing16(src, zero_is_undef)
- LibIntrinsics.counttrailing16({{src}}, {{zero_is_undef}})
+ ::LibIntrinsics.counttrailing16({{src}}, {{zero_is_undef}})
end
macro counttrailing32(src, zero_is_undef)
- LibIntrinsics.counttrailing32({{src}}, {{zero_is_undef}})
+ ::LibIntrinsics.counttrailing32({{src}}, {{zero_is_undef}})
end
macro counttrailing64(src, zero_is_undef)
- LibIntrinsics.counttrailing64({{src}}, {{zero_is_undef}})
+ ::LibIntrinsics.counttrailing64({{src}}, {{zero_is_undef}})
end
macro counttrailing128(src, zero_is_undef)
- LibIntrinsics.counttrailing128({{src}}, {{zero_is_undef}})
+ ::LibIntrinsics.counttrailing128({{src}}, {{zero_is_undef}})
end
def self.fshl8(a, b, count) : UInt8
@@ -343,14 +348,31 @@ module Intrinsics
end
macro va_start(ap)
- LibIntrinsics.va_start({{ap}})
+ ::LibIntrinsics.va_start({{ap}})
end
macro va_end(ap)
- LibIntrinsics.va_end({{ap}})
+ ::LibIntrinsics.va_end({{ap}})
+ end
+
+ # Should codegen to the following LLVM IR (before being inlined):
+ # ```
+ # define internal void @"*Intrinsics::unreachable:NoReturn"() #12 {
+ # entry:
+ # unreachable
+ # }
+ # ```
+ #
+ # Can be used like `@llvm.assume(i1 cond)` as `unreachable unless (assumption)`.
+ #
+ # WARNING: the behaviour of the program is undefined if the assumption is broken!
+ @[AlwaysInline]
+ def self.unreachable : NoReturn
+ x = uninitialized NoReturn
+ x
end
end
macro debugger
- Intrinsics.debugtrap
+ ::Intrinsics.debugtrap
end
diff --git a/src/io/buffered.cr b/src/io/buffered.cr
index 0e69872a638f..8bd65210aef2 100644
--- a/src/io/buffered.cr
+++ b/src/io/buffered.cr
@@ -49,7 +49,7 @@ module IO::Buffered
# Set the buffer size of both the read and write buffer
# Cannot be changed after any of the buffers have been allocated
def buffer_size=(value)
- if @in_buffer || @out_buffer
+ if (@in_buffer || @out_buffer) && (buffer_size != value)
raise ArgumentError.new("Cannot change buffer_size after buffers have been allocated")
end
@buffer_size = value
diff --git a/src/io/evented.cr b/src/io/evented.cr
index ccc040932285..635c399d9239 100644
--- a/src/io/evented.cr
+++ b/src/io/evented.cr
@@ -1,4 +1,6 @@
-{% skip_file if flag?(:win32) %}
+require "crystal/event_loop"
+
+{% skip_file unless flag?(:wasi) || Crystal::EventLoop.has_constant?(:LibEvent) %}
require "crystal/thread_local_value"
@@ -13,43 +15,6 @@ module IO::Evented
@read_event = Crystal::ThreadLocalValue(Crystal::EventLoop::Event).new
@write_event = Crystal::ThreadLocalValue(Crystal::EventLoop::Event).new
- def evented_read(errno_msg : String, &) : Int32
- loop do
- bytes_read = yield
- if bytes_read != -1
- # `to_i32` is acceptable because `Slice#size` is an Int32
- return bytes_read.to_i32
- end
-
- if Errno.value == Errno::EAGAIN
- wait_readable
- else
- raise IO::Error.from_errno(errno_msg, target: self)
- end
- end
- ensure
- resume_pending_readers
- end
-
- def evented_write(errno_msg : String, &) : Int32
- begin
- loop do
- bytes_written = yield
- if bytes_written != -1
- return bytes_written.to_i32
- end
-
- if Errno.value == Errno::EAGAIN
- wait_writable
- else
- raise IO::Error.from_errno(errno_msg, target: self)
- end
- end
- ensure
- resume_pending_writers
- end
- end
-
# :nodoc:
def resume_read(timed_out = false) : Nil
@read_timed_out = timed_out
@@ -69,12 +34,7 @@ module IO::Evented
end
# :nodoc:
- def wait_readable(timeout = @read_timeout) : Nil
- wait_readable(timeout: timeout) { raise TimeoutError.new("Read timed out") }
- end
-
- # :nodoc:
- def wait_readable(timeout = @read_timeout, *, raise_if_closed = true, &) : Nil
+ def evented_wait_readable(timeout = @read_timeout, *, raise_if_closed = true, &) : Nil
readers = @readers.get { Deque(Fiber).new }
readers << Fiber.current
add_read_event(timeout)
@@ -94,12 +54,7 @@ module IO::Evented
end
# :nodoc:
- def wait_writable(timeout = @write_timeout) : Nil
- wait_writable(timeout: timeout) { raise TimeoutError.new("Write timed out") }
- end
-
- # :nodoc:
- def wait_writable(timeout = @write_timeout, &) : Nil
+ def evented_wait_writable(timeout = @write_timeout, &) : Nil
writers = @writers.get { Deque(Fiber).new }
writers << Fiber.current
add_write_event(timeout)
@@ -132,13 +87,15 @@ module IO::Evented
end
end
- private def resume_pending_readers
+ # :nodoc:
+ def evented_resume_pending_readers
if (readers = @readers.get?) && !readers.empty?
add_read_event
end
end
- private def resume_pending_writers
+ # :nodoc:
+ def evented_resume_pending_writers
if (writers = @writers.get?) && !writers.empty?
add_write_event
end
diff --git a/src/io/file_descriptor.cr b/src/io/file_descriptor.cr
index d4459e9bbe0c..82c2b8ac232f 100644
--- a/src/io/file_descriptor.cr
+++ b/src/io/file_descriptor.cr
@@ -66,7 +66,15 @@ class IO::FileDescriptor < IO
Crystal::System::FileDescriptor.from_stdio(fd)
end
+ # Returns whether I/O operations on this file descriptor block the current
+ # thread. If false, operations might opt to suspend the current fiber instead.
+ #
+ # This might be different from the internal file descriptor. For example, when
+ # `STDIN` is a terminal on Windows, this returns `false` since the underlying
+ # blocking reads are done on a completely separate thread.
def blocking
+ emulated = emulated_blocking?
+ return emulated unless emulated.nil?
system_blocking?
end
@@ -233,10 +241,22 @@ class IO::FileDescriptor < IO
system_flock_unlock
end
+ # Finalizes the file descriptor resource.
+ #
+ # This involves releasing the handle to the operating system, i.e. closing it.
+ # It does *not* implicitly call `#flush`, so data waiting in the buffer may be
+ # lost.
+ # It's recommended to always close the file descriptor explicitly via `#close`
+ # (or implicitly using the `.open` constructor).
+ #
+ # Resource release can be disabled with `close_on_finalize = false`.
+ #
+ # This method is a no-op if the file descriptor has already been closed.
def finalize
return if closed? || !close_on_finalize?
- close rescue nil
+ Crystal::EventLoop.remove(self)
+ file_descriptor_close { } # ignore error
end
def closed? : Bool
diff --git a/src/iterator.cr b/src/iterator.cr
index a46c813b36b3..6a1513ef2130 100644
--- a/src/iterator.cr
+++ b/src/iterator.cr
@@ -144,6 +144,19 @@ module Iterator(T)
Stop::INSTANCE
end
+ # Returns an empty iterator.
+ def self.empty
+ EmptyIterator(T).new
+ end
+
+ private struct EmptyIterator(T)
+ include Iterator(T)
+
+ def next
+ stop
+ end
+ end
+
def self.of(element : T)
SingletonIterator(T).new(element)
end
diff --git a/src/json/from_json.cr b/src/json/from_json.cr
index 1c6a9e3c9c29..92edf0472c77 100644
--- a/src/json/from_json.cr
+++ b/src/json/from_json.cr
@@ -440,6 +440,28 @@ def Union.new(pull : JSON::PullParser)
{% end %}
end
+def Union.from_json_object_key?(key : String)
+ {% begin %}
+ # String must come last because any key can be parsed into a String.
+ # So, we give a chance first to other types in the union to be parsed.
+ {% string_type = T.find { |type| type == ::String } %}
+
+ {% for type in T %}
+ {% unless type == string_type %}
+ if result = {{ type }}.from_json_object_key?(key)
+ return result
+ end
+ {% end %}
+ {% end %}
+
+ {% if string_type %}
+ if result = {{ string_type }}.from_json_object_key?(key)
+ return result
+ end
+ {% end %}
+ {% end %}
+end
+
# Reads a string from JSON parser as a time formatted according to [RFC 3339](https://tools.ietf.org/html/rfc3339)
# or other variations of [ISO 8601](http://xml.coverpages.org/ISO-FDIS-8601.pdf).
#
diff --git a/src/json/serialization.cr b/src/json/serialization.cr
index b1eb86d15082..15d948f02f40 100644
--- a/src/json/serialization.cr
+++ b/src/json/serialization.cr
@@ -164,7 +164,7 @@ module JSON
private def self.new_from_json_pull_parser(pull : ::JSON::PullParser)
instance = allocate
instance.initialize(__pull_for_json_serializable: pull)
- GC.add_finalizer(instance) if instance.responds_to?(:finalize)
+ ::GC.add_finalizer(instance) if instance.responds_to?(:finalize)
instance
end
@@ -422,8 +422,8 @@ module JSON
# Try to find the discriminator while also getting the raw
# string value of the parsed JSON, so then we can pass it
# to the final type.
- json = String.build do |io|
- JSON.build(io) do |builder|
+ json = ::String.build do |io|
+ ::JSON.build(io) do |builder|
builder.start_object
pull.read_object do |key|
if key == {{field.id.stringify}}
diff --git a/src/kernel.cr b/src/kernel.cr
index 8c84a197b78f..34763b994839 100644
--- a/src/kernel.cr
+++ b/src/kernel.cr
@@ -584,14 +584,14 @@ end
# Hooks are defined here due to load order problems.
def self.after_fork_child_callbacks
@@after_fork_child_callbacks ||= [
- # clean ups (don't depend on event loop):
+ # reinit event loop first:
+ -> { Crystal::EventLoop.current.after_fork },
+
+ # reinit signal handling:
->Crystal::System::Signal.after_fork,
->Crystal::System::SignalChildHandler.after_fork,
- # reinit event loop:
- ->{ Crystal::EventLoop.current.after_fork },
-
- # more clean ups (may depend on event loop):
+ # additional reinitialization
->Random::DEFAULT.new_seed,
] of -> Nil
end
@@ -616,3 +616,7 @@ end
Crystal::System::Signal.setup_default_handlers
{% end %}
{% end %}
+
+{% if flag?(:interpreted) && flag?(:unix) && Crystal::Interpreter.has_method?(:signal_descriptor) %}
+ Crystal::System::Signal.setup_default_handlers
+{% end %}
diff --git a/src/levenshtein.cr b/src/levenshtein.cr
index e890d59c90ef..01ad1bc40784 100644
--- a/src/levenshtein.cr
+++ b/src/levenshtein.cr
@@ -139,7 +139,7 @@ module Levenshtein
# end
# best_match # => "ello"
# ```
- def self.find(name, tolerance = nil, &)
+ def self.find(name, tolerance = nil, &) : String?
Finder.find(name, tolerance) do |sn|
yield sn
end
@@ -154,7 +154,7 @@ module Levenshtein
# Levenshtein.find("hello", ["hullo", "hel", "hall", "hell"], 2) # => "hullo"
# Levenshtein.find("hello", ["hurlo", "hel", "hall"], 1) # => nil
# ```
- def self.find(name, all_names, tolerance = nil)
+ def self.find(name, all_names, tolerance = nil) : String?
Finder.find(name, all_names, tolerance)
end
end
diff --git a/src/lib_c.cr b/src/lib_c.cr
index 0bd8d2c2cc35..c52ea52bfcbc 100644
--- a/src/lib_c.cr
+++ b/src/lib_c.cr
@@ -1,4 +1,15 @@
-{% if flag?(:win32) %}
+# Supported library versions:
+#
+# * glibc (2.26+)
+# * musl libc (1.2+)
+# * system libraries of several BSDs
+# * macOS system library (11+)
+# * MSVCRT
+# * WASI
+# * bionic libc
+#
+# See https://crystal-lang.org/reference/man/required_libraries.html#system-library
+{% if flag?(:msvc) %}
@[Link({{ flag?(:static) ? "libucrt" : "ucrt" }})]
{% end %}
lib LibC
diff --git a/src/lib_c/aarch64-android/c/fcntl.cr b/src/lib_c/aarch64-android/c/fcntl.cr
index bf9b5ac46f13..ae6a11f26cff 100644
--- a/src/lib_c/aarch64-android/c/fcntl.cr
+++ b/src/lib_c/aarch64-android/c/fcntl.cr
@@ -19,6 +19,7 @@ lib LibC
O_RDONLY = 0o0
O_RDWR = 0o2
O_WRONLY = 0o1
+ AT_FDCWD = -100
fun fcntl(__fd : Int, __cmd : Int, ...) : Int
fun open(__path : Char*, __flags : Int, ...) : Int
diff --git a/src/lib_c/aarch64-android/c/signal.cr b/src/lib_c/aarch64-android/c/signal.cr
index 741c8f0efb65..27676c3f733f 100644
--- a/src/lib_c/aarch64-android/c/signal.cr
+++ b/src/lib_c/aarch64-android/c/signal.cr
@@ -79,6 +79,7 @@ lib LibC
fun kill(__pid : PidT, __signal : Int) : Int
fun pthread_sigmask(__how : Int, __new_set : SigsetT*, __old_set : SigsetT*) : Int
+ fun pthread_kill(__thread : PthreadT, __sig : Int) : Int
fun sigaction(__signal : Int, __new_action : Sigaction*, __old_action : Sigaction*) : Int
fun sigaltstack(__new_signal_stack : StackT*, __old_signal_stack : StackT*) : Int
{% if ANDROID_API >= 21 %}
@@ -89,5 +90,6 @@ lib LibC
fun sigaddset(__set : SigsetT*, __signal : Int) : Int
fun sigdelset(__set : SigsetT*, __signal : Int) : Int
fun sigismember(__set : SigsetT*, __signal : Int) : Int
+ fun sigsuspend(__mask : SigsetT*) : Int
{% end %}
end
diff --git a/src/lib_c/aarch64-android/c/sys/epoll.cr b/src/lib_c/aarch64-android/c/sys/epoll.cr
new file mode 100644
index 000000000000..ba34b8414732
--- /dev/null
+++ b/src/lib_c/aarch64-android/c/sys/epoll.cr
@@ -0,0 +1,32 @@
+lib LibC
+ EPOLLIN = 0x001_u32
+ EPOLLOUT = 0x004_u32
+ EPOLLERR = 0x008_u32
+ EPOLLHUP = 0x010_u32
+ EPOLLRDHUP = 0x2000_u32
+
+ EPOLLEXCLUSIVE = 1_u32 << 28
+ EPOLLET = 1_u32 << 31
+
+ EPOLL_CTL_ADD = 1
+ EPOLL_CTL_DEL = 2
+ EPOLL_CTL_MOD = 3
+
+ EPOLL_CLOEXEC = 0o2000000
+
+ union EpollDataT
+ ptr : Void*
+ fd : Int
+ u32 : UInt32
+ u64 : UInt64
+ end
+
+ struct EpollEvent
+ events : UInt32
+ data : EpollDataT
+ end
+
+ fun epoll_create1(Int) : Int
+ fun epoll_ctl(Int, Int, Int, EpollEvent*) : Int
+ fun epoll_wait(Int, EpollEvent*, Int, Int) : Int
+end
diff --git a/src/lib_c/aarch64-android/c/sys/eventfd.cr b/src/lib_c/aarch64-android/c/sys/eventfd.cr
new file mode 100644
index 000000000000..12f24428a8f4
--- /dev/null
+++ b/src/lib_c/aarch64-android/c/sys/eventfd.cr
@@ -0,0 +1,5 @@
+lib LibC
+ EFD_CLOEXEC = 0o2000000
+
+ fun eventfd(count : UInt, flags : Int) : Int
+end
diff --git a/src/lib_c/aarch64-android/c/sys/random.cr b/src/lib_c/aarch64-android/c/sys/random.cr
new file mode 100644
index 000000000000..77e193958ff2
--- /dev/null
+++ b/src/lib_c/aarch64-android/c/sys/random.cr
@@ -0,0 +1,7 @@
+lib LibC
+ {% if ANDROID_API >= 28 %}
+ GRND_NONBLOCK = 1_u32
+
+ fun getrandom(buf : Void*, buflen : SizeT, flags : UInt32) : SSizeT
+ {% end %}
+end
diff --git a/src/lib_c/aarch64-android/c/sys/resource.cr b/src/lib_c/aarch64-android/c/sys/resource.cr
index c6bfe1cf2e7b..52fe82cd446a 100644
--- a/src/lib_c/aarch64-android/c/sys/resource.cr
+++ b/src/lib_c/aarch64-android/c/sys/resource.cr
@@ -22,4 +22,15 @@ lib LibC
RUSAGE_CHILDREN = -1
fun getrusage(__who : Int, __usage : RUsage*) : Int
+
+ alias RlimT = ULongLong
+
+ struct Rlimit
+ rlim_cur : RlimT
+ rlim_max : RlimT
+ end
+
+ RLIMIT_NOFILE = 7
+
+ fun getrlimit(resource : Int, rlim : Rlimit*) : Int
end
diff --git a/src/lib_c/aarch64-android/c/sys/stat.cr b/src/lib_c/aarch64-android/c/sys/stat.cr
index 9216942441f3..befbc7653f99 100644
--- a/src/lib_c/aarch64-android/c/sys/stat.cr
+++ b/src/lib_c/aarch64-android/c/sys/stat.cr
@@ -53,4 +53,5 @@ lib LibC
fun mkdir(__path : Char*, __mode : ModeT) : Int
fun stat(__path : Char*, __buf : Stat*) : Int
fun umask(__mask : ModeT) : ModeT
+ fun utimensat(fd : Int, path : Char*, times : Timespec[2], flag : Int) : Int
end
diff --git a/src/lib_c/aarch64-android/c/sys/timerfd.cr b/src/lib_c/aarch64-android/c/sys/timerfd.cr
new file mode 100644
index 000000000000..0632646b0e14
--- /dev/null
+++ b/src/lib_c/aarch64-android/c/sys/timerfd.cr
@@ -0,0 +1,10 @@
+require "../time"
+
+lib LibC
+ TFD_NONBLOCK = 0o0004000
+ TFD_CLOEXEC = 0o2000000
+ TFD_TIMER_ABSTIME = 1 << 0
+
+ fun timerfd_create(ClockidT, Int) : Int
+ fun timerfd_settime(Int, Int, Itimerspec*, Itimerspec*) : Int
+end
diff --git a/src/lib_c/aarch64-android/c/time.cr b/src/lib_c/aarch64-android/c/time.cr
index 8f8b81291f0d..5007584d3069 100644
--- a/src/lib_c/aarch64-android/c/time.cr
+++ b/src/lib_c/aarch64-android/c/time.cr
@@ -23,6 +23,11 @@ lib LibC
tv_nsec : Long
end
+ struct Itimerspec
+ it_interval : Timespec
+ it_value : Timespec
+ end
+
fun clock_gettime(__clock : ClockidT, __ts : Timespec*) : Int
fun clock_settime(__clock : ClockidT, __ts : Timespec*) : Int
fun gmtime_r(__t : TimeT*, __tm : Tm*) : Tm*
diff --git a/src/lib_c/aarch64-darwin/c/fcntl.cr b/src/lib_c/aarch64-darwin/c/fcntl.cr
index cf6ce527a729..42e77a654587 100644
--- a/src/lib_c/aarch64-darwin/c/fcntl.cr
+++ b/src/lib_c/aarch64-darwin/c/fcntl.cr
@@ -19,6 +19,7 @@ lib LibC
O_RDONLY = 0x0000
O_RDWR = 0x0002
O_WRONLY = 0x0001
+ AT_FDCWD = -2
struct Flock
l_start : OffT
diff --git a/src/lib_c/aarch64-darwin/c/signal.cr b/src/lib_c/aarch64-darwin/c/signal.cr
index e58adc30289f..0034eef42834 100644
--- a/src/lib_c/aarch64-darwin/c/signal.cr
+++ b/src/lib_c/aarch64-darwin/c/signal.cr
@@ -77,6 +77,7 @@ lib LibC
fun kill(x0 : PidT, x1 : Int) : Int
fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int
+ fun pthread_kill(PthreadT, Int) : Int
fun signal(x0 : Int, x1 : Int -> Void) : Int -> Void
fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int
fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int
@@ -85,4 +86,5 @@ lib LibC
fun sigaddset(SigsetT*, Int) : Int
fun sigdelset(SigsetT*, Int) : Int
fun sigismember(SigsetT*, Int) : Int
+ fun sigsuspend(SigsetT*) : Int
end
diff --git a/src/lib_c/aarch64-darwin/c/sys/event.cr b/src/lib_c/aarch64-darwin/c/sys/event.cr
new file mode 100644
index 000000000000..1fd68b6d1975
--- /dev/null
+++ b/src/lib_c/aarch64-darwin/c/sys/event.cr
@@ -0,0 +1,31 @@
+require "../time"
+
+lib LibC
+ EVFILT_READ = -1_i16
+ EVFILT_WRITE = -2_i16
+ EVFILT_TIMER = -7_i16
+ EVFILT_USER = -10_i16
+
+ EV_ADD = 0x0001_u16
+ EV_DELETE = 0x0002_u16
+ EV_ENABLE = 0x0004_u16
+ EV_ONESHOT = 0x0010_u16
+ EV_CLEAR = 0x0020_u16
+ EV_EOF = 0x8000_u16
+ EV_ERROR = 0x4000_u16
+
+ NOTE_NSECONDS = 0x00000004_u32
+ NOTE_TRIGGER = 0x01000000_u32
+
+ struct Kevent
+ ident : SizeT # UintptrT
+ filter : Int16
+ flags : UInt16
+ fflags : UInt32
+ data : SSizeT # IntptrT
+ udata : Void*
+ end
+
+ fun kqueue : Int
+ fun kevent(kq : Int, changelist : Kevent*, nchanges : Int, eventlist : Kevent*, nevents : Int, timeout : Timespec*) : Int
+end
diff --git a/src/lib_c/aarch64-darwin/c/sys/resource.cr b/src/lib_c/aarch64-darwin/c/sys/resource.cr
index daa583ac5895..4759e8c9b3e3 100644
--- a/src/lib_c/aarch64-darwin/c/sys/resource.cr
+++ b/src/lib_c/aarch64-darwin/c/sys/resource.cr
@@ -6,6 +6,8 @@ lib LibC
rlim_max : RlimT
end
+ RLIMIT_NOFILE = 8
+
fun getrlimit(Int, Rlimit*) : Int
RLIMIT_STACK = 3
diff --git a/src/lib_c/aarch64-darwin/c/sys/stat.cr b/src/lib_c/aarch64-darwin/c/sys/stat.cr
index 9176a15083dd..556e29954120 100644
--- a/src/lib_c/aarch64-darwin/c/sys/stat.cr
+++ b/src/lib_c/aarch64-darwin/c/sys/stat.cr
@@ -56,4 +56,5 @@ lib LibC
fun mknod(x0 : Char*, x1 : ModeT, x2 : DevT) : Int
fun stat = stat64(x0 : Char*, x1 : Stat*) : Int
fun umask(x0 : ModeT) : ModeT
+ fun utimensat(fd : Int, path : Char*, times : Timespec[2], flag : Int) : Int
end
diff --git a/src/lib_c/aarch64-darwin/c/sys/time.cr b/src/lib_c/aarch64-darwin/c/sys/time.cr
index f74ab38733f0..5e2e5919812c 100644
--- a/src/lib_c/aarch64-darwin/c/sys/time.cr
+++ b/src/lib_c/aarch64-darwin/c/sys/time.cr
@@ -12,6 +12,5 @@ lib LibC
end
fun gettimeofday(x0 : Timeval*, x1 : Void*) : Int
- fun utimes(path : Char*, times : Timeval[2]) : Int
- fun futimes(fd : Int, times : Timeval[2]) : Int
+ fun futimens(fd : Int, times : Timespec[2]) : Int
end
diff --git a/src/lib_c/aarch64-linux-gnu/c/fcntl.cr b/src/lib_c/aarch64-linux-gnu/c/fcntl.cr
index e52f375d8dc4..a834cbe0b78e 100644
--- a/src/lib_c/aarch64-linux-gnu/c/fcntl.cr
+++ b/src/lib_c/aarch64-linux-gnu/c/fcntl.cr
@@ -19,6 +19,7 @@ lib LibC
O_RDONLY = 0o0
O_RDWR = 0o2
O_WRONLY = 0o1
+ AT_FDCWD = -100
struct Flock
l_type : Short
diff --git a/src/lib_c/aarch64-linux-gnu/c/signal.cr b/src/lib_c/aarch64-linux-gnu/c/signal.cr
index 1f7d82eb2145..7ff9fcda1b07 100644
--- a/src/lib_c/aarch64-linux-gnu/c/signal.cr
+++ b/src/lib_c/aarch64-linux-gnu/c/signal.cr
@@ -78,6 +78,7 @@ lib LibC
fun kill(pid : PidT, sig : Int) : Int
fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int
+ fun pthread_kill(PthreadT, Int) : Int
fun signal(sig : Int, handler : Int -> Void) : Int -> Void
fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int
fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int
@@ -86,4 +87,5 @@ lib LibC
fun sigaddset(SigsetT*, Int) : Int
fun sigdelset(SigsetT*, Int) : Int
fun sigismember(SigsetT*, Int) : Int
+ fun sigsuspend(SigsetT*) : Int
end
diff --git a/src/lib_c/aarch64-linux-gnu/c/sys/epoll.cr b/src/lib_c/aarch64-linux-gnu/c/sys/epoll.cr
new file mode 100644
index 000000000000..ba34b8414732
--- /dev/null
+++ b/src/lib_c/aarch64-linux-gnu/c/sys/epoll.cr
@@ -0,0 +1,32 @@
+lib LibC
+ EPOLLIN = 0x001_u32
+ EPOLLOUT = 0x004_u32
+ EPOLLERR = 0x008_u32
+ EPOLLHUP = 0x010_u32
+ EPOLLRDHUP = 0x2000_u32
+
+ EPOLLEXCLUSIVE = 1_u32 << 28
+ EPOLLET = 1_u32 << 31
+
+ EPOLL_CTL_ADD = 1
+ EPOLL_CTL_DEL = 2
+ EPOLL_CTL_MOD = 3
+
+ EPOLL_CLOEXEC = 0o2000000
+
+ union EpollDataT
+ ptr : Void*
+ fd : Int
+ u32 : UInt32
+ u64 : UInt64
+ end
+
+ struct EpollEvent
+ events : UInt32
+ data : EpollDataT
+ end
+
+ fun epoll_create1(Int) : Int
+ fun epoll_ctl(Int, Int, Int, EpollEvent*) : Int
+ fun epoll_wait(Int, EpollEvent*, Int, Int) : Int
+end
diff --git a/src/lib_c/aarch64-linux-gnu/c/sys/eventfd.cr b/src/lib_c/aarch64-linux-gnu/c/sys/eventfd.cr
new file mode 100644
index 000000000000..12f24428a8f4
--- /dev/null
+++ b/src/lib_c/aarch64-linux-gnu/c/sys/eventfd.cr
@@ -0,0 +1,5 @@
+lib LibC
+ EFD_CLOEXEC = 0o2000000
+
+ fun eventfd(count : UInt, flags : Int) : Int
+end
diff --git a/src/lib_c/aarch64-linux-gnu/c/sys/random.cr b/src/lib_c/aarch64-linux-gnu/c/sys/random.cr
new file mode 100644
index 000000000000..2c74de96abfb
--- /dev/null
+++ b/src/lib_c/aarch64-linux-gnu/c/sys/random.cr
@@ -0,0 +1,5 @@
+lib LibC
+ GRND_NONBLOCK = 1_u32
+
+ fun getrandom(buf : Void*, buflen : SizeT, flags : UInt32) : SSizeT
+end
diff --git a/src/lib_c/aarch64-linux-gnu/c/sys/resource.cr b/src/lib_c/aarch64-linux-gnu/c/sys/resource.cr
index a0900a4730c4..444c4ba692c8 100644
--- a/src/lib_c/aarch64-linux-gnu/c/sys/resource.cr
+++ b/src/lib_c/aarch64-linux-gnu/c/sys/resource.cr
@@ -22,4 +22,15 @@ lib LibC
RUSAGE_CHILDREN = -1
fun getrusage(who : Int, usage : RUsage*) : Int
+
+ alias RlimT = ULongLong
+
+ struct Rlimit
+ rlim_cur : RlimT
+ rlim_max : RlimT
+ end
+
+ RLIMIT_NOFILE = 7
+
+ fun getrlimit(resource : Int, rlim : Rlimit*) : Int
end
diff --git a/src/lib_c/aarch64-linux-gnu/c/sys/stat.cr b/src/lib_c/aarch64-linux-gnu/c/sys/stat.cr
index 6a8373908586..df832238046a 100644
--- a/src/lib_c/aarch64-linux-gnu/c/sys/stat.cr
+++ b/src/lib_c/aarch64-linux-gnu/c/sys/stat.cr
@@ -54,4 +54,5 @@ lib LibC
fun mknod(path : Char*, mode : ModeT, dev : DevT) : Int
fun stat(file : Char*, buf : Stat*) : Int
fun umask(mask : ModeT) : ModeT
+ fun utimensat(fd : Int, path : Char*, times : Timespec[2], flag : Int) : Int
end
diff --git a/src/lib_c/aarch64-linux-gnu/c/sys/time.cr b/src/lib_c/aarch64-linux-gnu/c/sys/time.cr
index 664de111502a..9e7d921c2728 100644
--- a/src/lib_c/aarch64-linux-gnu/c/sys/time.cr
+++ b/src/lib_c/aarch64-linux-gnu/c/sys/time.cr
@@ -12,6 +12,5 @@ lib LibC
end
fun gettimeofday(tv : Timeval*, tz : Void*) : Int
- fun utimes(path : Char*, times : Timeval[2]) : Int
fun futimens(fd : Int, times : Timespec[2]) : Int
end
diff --git a/src/lib_c/aarch64-linux-gnu/c/sys/timerfd.cr b/src/lib_c/aarch64-linux-gnu/c/sys/timerfd.cr
new file mode 100644
index 000000000000..0632646b0e14
--- /dev/null
+++ b/src/lib_c/aarch64-linux-gnu/c/sys/timerfd.cr
@@ -0,0 +1,10 @@
+require "../time"
+
+lib LibC
+ TFD_NONBLOCK = 0o0004000
+ TFD_CLOEXEC = 0o2000000
+ TFD_TIMER_ABSTIME = 1 << 0
+
+ fun timerfd_create(ClockidT, Int) : Int
+ fun timerfd_settime(Int, Int, Itimerspec*, Itimerspec*) : Int
+end
diff --git a/src/lib_c/aarch64-linux-gnu/c/time.cr b/src/lib_c/aarch64-linux-gnu/c/time.cr
index 710d477e269b..d00579281b41 100644
--- a/src/lib_c/aarch64-linux-gnu/c/time.cr
+++ b/src/lib_c/aarch64-linux-gnu/c/time.cr
@@ -23,6 +23,11 @@ lib LibC
tv_nsec : Long
end
+ struct Itimerspec
+ it_interval : Timespec
+ it_value : Timespec
+ end
+
fun clock_gettime(clock_id : ClockidT, tp : Timespec*) : Int
fun clock_settime(clock_id : ClockidT, tp : Timespec*) : Int
fun gmtime_r(timer : TimeT*, tp : Tm*) : Tm*
diff --git a/src/lib_c/aarch64-linux-musl/c/fcntl.cr b/src/lib_c/aarch64-linux-musl/c/fcntl.cr
index 7664c411a36c..3959fff298df 100644
--- a/src/lib_c/aarch64-linux-musl/c/fcntl.cr
+++ b/src/lib_c/aarch64-linux-musl/c/fcntl.cr
@@ -19,6 +19,7 @@ lib LibC
O_RDONLY = 0o0
O_RDWR = 0o2
O_WRONLY = 0o1
+ AT_FDCWD = -100
struct Flock
l_type : Short
diff --git a/src/lib_c/aarch64-linux-musl/c/signal.cr b/src/lib_c/aarch64-linux-musl/c/signal.cr
index 5bfa187b14ec..c65fbb0ff653 100644
--- a/src/lib_c/aarch64-linux-musl/c/signal.cr
+++ b/src/lib_c/aarch64-linux-musl/c/signal.cr
@@ -77,6 +77,7 @@ lib LibC
fun kill(x0 : PidT, x1 : Int) : Int
fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int
+ fun pthread_kill(PthreadT, Int) : Int
fun signal(x0 : Int, x1 : Int -> Void) : Int -> Void
fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int
fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int
@@ -85,4 +86,5 @@ lib LibC
fun sigaddset(SigsetT*, Int) : Int
fun sigdelset(SigsetT*, Int) : Int
fun sigismember(SigsetT*, Int) : Int
+ fun sigsuspend(SigsetT*) : Int
end
diff --git a/src/lib_c/aarch64-linux-musl/c/sys/epoll.cr b/src/lib_c/aarch64-linux-musl/c/sys/epoll.cr
new file mode 100644
index 000000000000..ba34b8414732
--- /dev/null
+++ b/src/lib_c/aarch64-linux-musl/c/sys/epoll.cr
@@ -0,0 +1,32 @@
+lib LibC
+ EPOLLIN = 0x001_u32
+ EPOLLOUT = 0x004_u32
+ EPOLLERR = 0x008_u32
+ EPOLLHUP = 0x010_u32
+ EPOLLRDHUP = 0x2000_u32
+
+ EPOLLEXCLUSIVE = 1_u32 << 28
+ EPOLLET = 1_u32 << 31
+
+ EPOLL_CTL_ADD = 1
+ EPOLL_CTL_DEL = 2
+ EPOLL_CTL_MOD = 3
+
+ EPOLL_CLOEXEC = 0o2000000
+
+ union EpollDataT
+ ptr : Void*
+ fd : Int
+ u32 : UInt32
+ u64 : UInt64
+ end
+
+ struct EpollEvent
+ events : UInt32
+ data : EpollDataT
+ end
+
+ fun epoll_create1(Int) : Int
+ fun epoll_ctl(Int, Int, Int, EpollEvent*) : Int
+ fun epoll_wait(Int, EpollEvent*, Int, Int) : Int
+end
diff --git a/src/lib_c/aarch64-linux-musl/c/sys/eventfd.cr b/src/lib_c/aarch64-linux-musl/c/sys/eventfd.cr
new file mode 100644
index 000000000000..12f24428a8f4
--- /dev/null
+++ b/src/lib_c/aarch64-linux-musl/c/sys/eventfd.cr
@@ -0,0 +1,5 @@
+lib LibC
+ EFD_CLOEXEC = 0o2000000
+
+ fun eventfd(count : UInt, flags : Int) : Int
+end
diff --git a/src/lib_c/aarch64-linux-musl/c/sys/random.cr b/src/lib_c/aarch64-linux-musl/c/sys/random.cr
new file mode 100644
index 000000000000..2c74de96abfb
--- /dev/null
+++ b/src/lib_c/aarch64-linux-musl/c/sys/random.cr
@@ -0,0 +1,5 @@
+lib LibC
+ GRND_NONBLOCK = 1_u32
+
+ fun getrandom(buf : Void*, buflen : SizeT, flags : UInt32) : SSizeT
+end
diff --git a/src/lib_c/aarch64-linux-musl/c/sys/resource.cr b/src/lib_c/aarch64-linux-musl/c/sys/resource.cr
index 7f550c37a622..656e43cb0379 100644
--- a/src/lib_c/aarch64-linux-musl/c/sys/resource.cr
+++ b/src/lib_c/aarch64-linux-musl/c/sys/resource.cr
@@ -1,4 +1,17 @@
lib LibC
+ alias RlimT = ULongLong
+
+ struct Rlimit
+ rlim_cur : RlimT
+ rlim_max : RlimT
+ end
+
+ RLIMIT_NOFILE = 7
+
+ fun getrlimit(Int, Rlimit*) : Int
+
+ RLIMIT_STACK = 3
+
struct RUsage
ru_utime : Timeval
ru_stime : Timeval
diff --git a/src/lib_c/aarch64-linux-musl/c/sys/stat.cr b/src/lib_c/aarch64-linux-musl/c/sys/stat.cr
index db3548e2e378..96938a86c69b 100644
--- a/src/lib_c/aarch64-linux-musl/c/sys/stat.cr
+++ b/src/lib_c/aarch64-linux-musl/c/sys/stat.cr
@@ -54,4 +54,5 @@ lib LibC
fun mknod(x0 : Char*, x1 : ModeT, x2 : DevT) : Int
fun stat(x0 : Char*, x1 : Stat*) : Int
fun umask(x0 : ModeT) : ModeT
+ fun utimensat(fd : Int, path : Char*, times : Timespec[2], flag : Int) : Int
end
diff --git a/src/lib_c/aarch64-linux-musl/c/sys/time.cr b/src/lib_c/aarch64-linux-musl/c/sys/time.cr
index 711894a3da7e..5e2e5919812c 100644
--- a/src/lib_c/aarch64-linux-musl/c/sys/time.cr
+++ b/src/lib_c/aarch64-linux-musl/c/sys/time.cr
@@ -12,6 +12,5 @@ lib LibC
end
fun gettimeofday(x0 : Timeval*, x1 : Void*) : Int
- fun utimes(path : Char*, times : Timeval[2]) : Int
fun futimens(fd : Int, times : Timespec[2]) : Int
end
diff --git a/src/lib_c/aarch64-linux-musl/c/sys/timerfd.cr b/src/lib_c/aarch64-linux-musl/c/sys/timerfd.cr
new file mode 100644
index 000000000000..0632646b0e14
--- /dev/null
+++ b/src/lib_c/aarch64-linux-musl/c/sys/timerfd.cr
@@ -0,0 +1,10 @@
+require "../time"
+
+lib LibC
+ TFD_NONBLOCK = 0o0004000
+ TFD_CLOEXEC = 0o2000000
+ TFD_TIMER_ABSTIME = 1 << 0
+
+ fun timerfd_create(ClockidT, Int) : Int
+ fun timerfd_settime(Int, Int, Itimerspec*, Itimerspec*) : Int
+end
diff --git a/src/lib_c/aarch64-linux-musl/c/time.cr b/src/lib_c/aarch64-linux-musl/c/time.cr
index f687c8b35db4..4bf25a7f9efc 100644
--- a/src/lib_c/aarch64-linux-musl/c/time.cr
+++ b/src/lib_c/aarch64-linux-musl/c/time.cr
@@ -23,6 +23,11 @@ lib LibC
tv_nsec : Long
end
+ struct Itimerspec
+ it_interval : Timespec
+ it_value : Timespec
+ end
+
fun clock_gettime(x0 : ClockidT, x1 : Timespec*) : Int
fun clock_settime(x0 : ClockidT, x1 : Timespec*) : Int
fun gmtime_r(x0 : TimeT*, x1 : Tm*) : Tm*
diff --git a/src/lib_c/aarch64-windows-gnu b/src/lib_c/aarch64-windows-gnu
new file mode 120000
index 000000000000..072348f65d09
--- /dev/null
+++ b/src/lib_c/aarch64-windows-gnu
@@ -0,0 +1 @@
+x86_64-windows-msvc
\ No newline at end of file
diff --git a/src/lib_c/aarch64-windows-msvc b/src/lib_c/aarch64-windows-msvc
new file mode 120000
index 000000000000..072348f65d09
--- /dev/null
+++ b/src/lib_c/aarch64-windows-msvc
@@ -0,0 +1 @@
+x86_64-windows-msvc
\ No newline at end of file
diff --git a/src/lib_c/arm-linux-gnueabihf/c/fcntl.cr b/src/lib_c/arm-linux-gnueabihf/c/fcntl.cr
index e52f375d8dc4..a834cbe0b78e 100644
--- a/src/lib_c/arm-linux-gnueabihf/c/fcntl.cr
+++ b/src/lib_c/arm-linux-gnueabihf/c/fcntl.cr
@@ -19,6 +19,7 @@ lib LibC
O_RDONLY = 0o0
O_RDWR = 0o2
O_WRONLY = 0o1
+ AT_FDCWD = -100
struct Flock
l_type : Short
diff --git a/src/lib_c/arm-linux-gnueabihf/c/signal.cr b/src/lib_c/arm-linux-gnueabihf/c/signal.cr
index d94d657e1ca8..0113c045341c 100644
--- a/src/lib_c/arm-linux-gnueabihf/c/signal.cr
+++ b/src/lib_c/arm-linux-gnueabihf/c/signal.cr
@@ -77,6 +77,7 @@ lib LibC
fun kill(pid : PidT, sig : Int) : Int
fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int
+ fun pthread_kill(PthreadT, Int) : Int
fun signal(sig : Int, handler : Int -> Void) : Int -> Void
fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int
fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int
@@ -85,4 +86,5 @@ lib LibC
fun sigaddset(SigsetT*, Int) : Int
fun sigdelset(SigsetT*, Int) : Int
fun sigismember(SigsetT*, Int) : Int
+ fun sigsuspend(SigsetT*) : Int
end
diff --git a/src/lib_c/arm-linux-gnueabihf/c/sys/epoll.cr b/src/lib_c/arm-linux-gnueabihf/c/sys/epoll.cr
new file mode 100644
index 000000000000..ba34b8414732
--- /dev/null
+++ b/src/lib_c/arm-linux-gnueabihf/c/sys/epoll.cr
@@ -0,0 +1,32 @@
+lib LibC
+ EPOLLIN = 0x001_u32
+ EPOLLOUT = 0x004_u32
+ EPOLLERR = 0x008_u32
+ EPOLLHUP = 0x010_u32
+ EPOLLRDHUP = 0x2000_u32
+
+ EPOLLEXCLUSIVE = 1_u32 << 28
+ EPOLLET = 1_u32 << 31
+
+ EPOLL_CTL_ADD = 1
+ EPOLL_CTL_DEL = 2
+ EPOLL_CTL_MOD = 3
+
+ EPOLL_CLOEXEC = 0o2000000
+
+ union EpollDataT
+ ptr : Void*
+ fd : Int
+ u32 : UInt32
+ u64 : UInt64
+ end
+
+ struct EpollEvent
+ events : UInt32
+ data : EpollDataT
+ end
+
+ fun epoll_create1(Int) : Int
+ fun epoll_ctl(Int, Int, Int, EpollEvent*) : Int
+ fun epoll_wait(Int, EpollEvent*, Int, Int) : Int
+end
diff --git a/src/lib_c/arm-linux-gnueabihf/c/sys/eventfd.cr b/src/lib_c/arm-linux-gnueabihf/c/sys/eventfd.cr
new file mode 100644
index 000000000000..12f24428a8f4
--- /dev/null
+++ b/src/lib_c/arm-linux-gnueabihf/c/sys/eventfd.cr
@@ -0,0 +1,5 @@
+lib LibC
+ EFD_CLOEXEC = 0o2000000
+
+ fun eventfd(count : UInt, flags : Int) : Int
+end
diff --git a/src/lib_c/arm-linux-gnueabihf/c/sys/random.cr b/src/lib_c/arm-linux-gnueabihf/c/sys/random.cr
new file mode 100644
index 000000000000..2c74de96abfb
--- /dev/null
+++ b/src/lib_c/arm-linux-gnueabihf/c/sys/random.cr
@@ -0,0 +1,5 @@
+lib LibC
+ GRND_NONBLOCK = 1_u32
+
+ fun getrandom(buf : Void*, buflen : SizeT, flags : UInt32) : SSizeT
+end
diff --git a/src/lib_c/arm-linux-gnueabihf/c/sys/resource.cr b/src/lib_c/arm-linux-gnueabihf/c/sys/resource.cr
index 7f550c37a622..1c2c2fb678f5 100644
--- a/src/lib_c/arm-linux-gnueabihf/c/sys/resource.cr
+++ b/src/lib_c/arm-linux-gnueabihf/c/sys/resource.cr
@@ -22,4 +22,15 @@ lib LibC
RUSAGE_CHILDREN = -1
fun getrusage(who : Int, usage : RUsage*) : Int16
+
+ alias RlimT = ULongLong
+
+ struct Rlimit
+ rlim_cur : RlimT
+ rlim_max : RlimT
+ end
+
+ RLIMIT_NOFILE = 7
+
+ fun getrlimit(resource : Int, rlim : Rlimit*) : Int
end
diff --git a/src/lib_c/arm-linux-gnueabihf/c/sys/stat.cr b/src/lib_c/arm-linux-gnueabihf/c/sys/stat.cr
index dec65002e27a..2ed61591c9bb 100644
--- a/src/lib_c/arm-linux-gnueabihf/c/sys/stat.cr
+++ b/src/lib_c/arm-linux-gnueabihf/c/sys/stat.cr
@@ -55,4 +55,5 @@ lib LibC
fun mknod(path : Char*, mode : ModeT, dev : DevT) : Int
fun stat(file : Char*, buf : Stat*) : Int
fun umask(mask : ModeT) : ModeT
+ fun utimensat(fd : Int, path : Char*, times : Timespec[2], flag : Int) : Int
end
diff --git a/src/lib_c/arm-linux-gnueabihf/c/sys/time.cr b/src/lib_c/arm-linux-gnueabihf/c/sys/time.cr
index 664de111502a..9e7d921c2728 100644
--- a/src/lib_c/arm-linux-gnueabihf/c/sys/time.cr
+++ b/src/lib_c/arm-linux-gnueabihf/c/sys/time.cr
@@ -12,6 +12,5 @@ lib LibC
end
fun gettimeofday(tv : Timeval*, tz : Void*) : Int
- fun utimes(path : Char*, times : Timeval[2]) : Int
fun futimens(fd : Int, times : Timespec[2]) : Int
end
diff --git a/src/lib_c/arm-linux-gnueabihf/c/sys/timerfd.cr b/src/lib_c/arm-linux-gnueabihf/c/sys/timerfd.cr
new file mode 100644
index 000000000000..0632646b0e14
--- /dev/null
+++ b/src/lib_c/arm-linux-gnueabihf/c/sys/timerfd.cr
@@ -0,0 +1,10 @@
+require "../time"
+
+lib LibC
+ TFD_NONBLOCK = 0o0004000
+ TFD_CLOEXEC = 0o2000000
+ TFD_TIMER_ABSTIME = 1 << 0
+
+ fun timerfd_create(ClockidT, Int) : Int
+ fun timerfd_settime(Int, Int, Itimerspec*, Itimerspec*) : Int
+end
diff --git a/src/lib_c/arm-linux-gnueabihf/c/time.cr b/src/lib_c/arm-linux-gnueabihf/c/time.cr
index 710d477e269b..d00579281b41 100644
--- a/src/lib_c/arm-linux-gnueabihf/c/time.cr
+++ b/src/lib_c/arm-linux-gnueabihf/c/time.cr
@@ -23,6 +23,11 @@ lib LibC
tv_nsec : Long
end
+ struct Itimerspec
+ it_interval : Timespec
+ it_value : Timespec
+ end
+
fun clock_gettime(clock_id : ClockidT, tp : Timespec*) : Int
fun clock_settime(clock_id : ClockidT, tp : Timespec*) : Int
fun gmtime_r(timer : TimeT*, tp : Tm*) : Tm*
diff --git a/src/lib_c/i386-linux-gnu/c/fcntl.cr b/src/lib_c/i386-linux-gnu/c/fcntl.cr
index cea8630785da..61eba795f182 100644
--- a/src/lib_c/i386-linux-gnu/c/fcntl.cr
+++ b/src/lib_c/i386-linux-gnu/c/fcntl.cr
@@ -19,6 +19,7 @@ lib LibC
O_RDONLY = 0o0
O_RDWR = 0o2
O_WRONLY = 0o1
+ AT_FDCWD = -100
struct Flock
l_type : Short
diff --git a/src/lib_c/i386-linux-gnu/c/signal.cr b/src/lib_c/i386-linux-gnu/c/signal.cr
index 11aab8bfe6bb..1a5260073c2d 100644
--- a/src/lib_c/i386-linux-gnu/c/signal.cr
+++ b/src/lib_c/i386-linux-gnu/c/signal.cr
@@ -77,6 +77,7 @@ lib LibC
fun kill(pid : PidT, sig : Int) : Int
fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int
+ fun pthread_kill(PthreadT, Int) : Int
fun signal(sig : Int, handler : Int -> Void) : Int -> Void
fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int
fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int
@@ -85,4 +86,5 @@ lib LibC
fun sigaddset(SigsetT*, Int) : Int
fun sigdelset(SigsetT*, Int) : Int
fun sigismember(SigsetT*, Int) : Int
+ fun sigsuspend(SigsetT*) : Int
end
diff --git a/src/lib_c/i386-linux-gnu/c/sys/epoll.cr b/src/lib_c/i386-linux-gnu/c/sys/epoll.cr
new file mode 100644
index 000000000000..ba34b8414732
--- /dev/null
+++ b/src/lib_c/i386-linux-gnu/c/sys/epoll.cr
@@ -0,0 +1,32 @@
+lib LibC
+ EPOLLIN = 0x001_u32
+ EPOLLOUT = 0x004_u32
+ EPOLLERR = 0x008_u32
+ EPOLLHUP = 0x010_u32
+ EPOLLRDHUP = 0x2000_u32
+
+ EPOLLEXCLUSIVE = 1_u32 << 28
+ EPOLLET = 1_u32 << 31
+
+ EPOLL_CTL_ADD = 1
+ EPOLL_CTL_DEL = 2
+ EPOLL_CTL_MOD = 3
+
+ EPOLL_CLOEXEC = 0o2000000
+
+ union EpollDataT
+ ptr : Void*
+ fd : Int
+ u32 : UInt32
+ u64 : UInt64
+ end
+
+ struct EpollEvent
+ events : UInt32
+ data : EpollDataT
+ end
+
+ fun epoll_create1(Int) : Int
+ fun epoll_ctl(Int, Int, Int, EpollEvent*) : Int
+ fun epoll_wait(Int, EpollEvent*, Int, Int) : Int
+end
diff --git a/src/lib_c/i386-linux-gnu/c/sys/eventfd.cr b/src/lib_c/i386-linux-gnu/c/sys/eventfd.cr
new file mode 100644
index 000000000000..12f24428a8f4
--- /dev/null
+++ b/src/lib_c/i386-linux-gnu/c/sys/eventfd.cr
@@ -0,0 +1,5 @@
+lib LibC
+ EFD_CLOEXEC = 0o2000000
+
+ fun eventfd(count : UInt, flags : Int) : Int
+end
diff --git a/src/lib_c/i386-linux-gnu/c/sys/random.cr b/src/lib_c/i386-linux-gnu/c/sys/random.cr
new file mode 100644
index 000000000000..2c74de96abfb
--- /dev/null
+++ b/src/lib_c/i386-linux-gnu/c/sys/random.cr
@@ -0,0 +1,5 @@
+lib LibC
+ GRND_NONBLOCK = 1_u32
+
+ fun getrandom(buf : Void*, buflen : SizeT, flags : UInt32) : SSizeT
+end
diff --git a/src/lib_c/i386-linux-gnu/c/sys/resource.cr b/src/lib_c/i386-linux-gnu/c/sys/resource.cr
index a0900a4730c4..444c4ba692c8 100644
--- a/src/lib_c/i386-linux-gnu/c/sys/resource.cr
+++ b/src/lib_c/i386-linux-gnu/c/sys/resource.cr
@@ -22,4 +22,15 @@ lib LibC
RUSAGE_CHILDREN = -1
fun getrusage(who : Int, usage : RUsage*) : Int
+
+ alias RlimT = ULongLong
+
+ struct Rlimit
+ rlim_cur : RlimT
+ rlim_max : RlimT
+ end
+
+ RLIMIT_NOFILE = 7
+
+ fun getrlimit(resource : Int, rlim : Rlimit*) : Int
end
diff --git a/src/lib_c/i386-linux-gnu/c/sys/stat.cr b/src/lib_c/i386-linux-gnu/c/sys/stat.cr
index 7a6dca15c3ba..e8e178a4de7d 100644
--- a/src/lib_c/i386-linux-gnu/c/sys/stat.cr
+++ b/src/lib_c/i386-linux-gnu/c/sys/stat.cr
@@ -54,4 +54,5 @@ lib LibC
fun mknod(path : Char*, mode : ModeT, dev : DevT) : Int
fun stat = stat64(file : Char*, buf : Stat*) : Int
fun umask(mask : ModeT) : ModeT
+ fun utimensat(fd : Int, path : Char*, times : Timespec[2], flag : Int) : Int
end
diff --git a/src/lib_c/i386-linux-gnu/c/sys/time.cr b/src/lib_c/i386-linux-gnu/c/sys/time.cr
index 664de111502a..9e7d921c2728 100644
--- a/src/lib_c/i386-linux-gnu/c/sys/time.cr
+++ b/src/lib_c/i386-linux-gnu/c/sys/time.cr
@@ -12,6 +12,5 @@ lib LibC
end
fun gettimeofday(tv : Timeval*, tz : Void*) : Int
- fun utimes(path : Char*, times : Timeval[2]) : Int
fun futimens(fd : Int, times : Timespec[2]) : Int
end
diff --git a/src/lib_c/i386-linux-gnu/c/sys/timerfd.cr b/src/lib_c/i386-linux-gnu/c/sys/timerfd.cr
new file mode 100644
index 000000000000..0632646b0e14
--- /dev/null
+++ b/src/lib_c/i386-linux-gnu/c/sys/timerfd.cr
@@ -0,0 +1,10 @@
+require "../time"
+
+lib LibC
+ TFD_NONBLOCK = 0o0004000
+ TFD_CLOEXEC = 0o2000000
+ TFD_TIMER_ABSTIME = 1 << 0
+
+ fun timerfd_create(ClockidT, Int) : Int
+ fun timerfd_settime(Int, Int, Itimerspec*, Itimerspec*) : Int
+end
diff --git a/src/lib_c/i386-linux-gnu/c/time.cr b/src/lib_c/i386-linux-gnu/c/time.cr
index 710d477e269b..d00579281b41 100644
--- a/src/lib_c/i386-linux-gnu/c/time.cr
+++ b/src/lib_c/i386-linux-gnu/c/time.cr
@@ -23,6 +23,11 @@ lib LibC
tv_nsec : Long
end
+ struct Itimerspec
+ it_interval : Timespec
+ it_value : Timespec
+ end
+
fun clock_gettime(clock_id : ClockidT, tp : Timespec*) : Int
fun clock_settime(clock_id : ClockidT, tp : Timespec*) : Int
fun gmtime_r(timer : TimeT*, tp : Tm*) : Tm*
diff --git a/src/lib_c/i386-linux-musl/c/fcntl.cr b/src/lib_c/i386-linux-musl/c/fcntl.cr
index 27a5cf0c22d3..fa53d4b1e378 100644
--- a/src/lib_c/i386-linux-musl/c/fcntl.cr
+++ b/src/lib_c/i386-linux-musl/c/fcntl.cr
@@ -19,6 +19,7 @@ lib LibC
O_RDONLY = 0o0
O_RDWR = 0o2
O_WRONLY = 0o1
+ AT_FDCWD = -100
struct Flock
l_type : Short
diff --git a/src/lib_c/i386-linux-musl/c/signal.cr b/src/lib_c/i386-linux-musl/c/signal.cr
index f2e554942b69..ac374b684c76 100644
--- a/src/lib_c/i386-linux-musl/c/signal.cr
+++ b/src/lib_c/i386-linux-musl/c/signal.cr
@@ -76,6 +76,7 @@ lib LibC
fun kill(x0 : PidT, x1 : Int) : Int
fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int
+ fun pthread_kill(PthreadT, Int) : Int
fun signal(x0 : Int, x1 : Int -> Void) : Int -> Void
fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int
fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int
@@ -84,4 +85,5 @@ lib LibC
fun sigaddset(SigsetT*, Int) : Int
fun sigdelset(SigsetT*, Int) : Int
fun sigismember(SigsetT*, Int) : Int
+ fun sigsuspend(SigsetT*) : Int
end
diff --git a/src/lib_c/i386-linux-musl/c/sys/epoll.cr b/src/lib_c/i386-linux-musl/c/sys/epoll.cr
new file mode 100644
index 000000000000..ba34b8414732
--- /dev/null
+++ b/src/lib_c/i386-linux-musl/c/sys/epoll.cr
@@ -0,0 +1,32 @@
+lib LibC
+ EPOLLIN = 0x001_u32
+ EPOLLOUT = 0x004_u32
+ EPOLLERR = 0x008_u32
+ EPOLLHUP = 0x010_u32
+ EPOLLRDHUP = 0x2000_u32
+
+ EPOLLEXCLUSIVE = 1_u32 << 28
+ EPOLLET = 1_u32 << 31
+
+ EPOLL_CTL_ADD = 1
+ EPOLL_CTL_DEL = 2
+ EPOLL_CTL_MOD = 3
+
+ EPOLL_CLOEXEC = 0o2000000
+
+ union EpollDataT
+ ptr : Void*
+ fd : Int
+ u32 : UInt32
+ u64 : UInt64
+ end
+
+ struct EpollEvent
+ events : UInt32
+ data : EpollDataT
+ end
+
+ fun epoll_create1(Int) : Int
+ fun epoll_ctl(Int, Int, Int, EpollEvent*) : Int
+ fun epoll_wait(Int, EpollEvent*, Int, Int) : Int
+end
diff --git a/src/lib_c/i386-linux-musl/c/sys/eventfd.cr b/src/lib_c/i386-linux-musl/c/sys/eventfd.cr
new file mode 100644
index 000000000000..12f24428a8f4
--- /dev/null
+++ b/src/lib_c/i386-linux-musl/c/sys/eventfd.cr
@@ -0,0 +1,5 @@
+lib LibC
+ EFD_CLOEXEC = 0o2000000
+
+ fun eventfd(count : UInt, flags : Int) : Int
+end
diff --git a/src/lib_c/i386-linux-musl/c/sys/random.cr b/src/lib_c/i386-linux-musl/c/sys/random.cr
new file mode 100644
index 000000000000..2c74de96abfb
--- /dev/null
+++ b/src/lib_c/i386-linux-musl/c/sys/random.cr
@@ -0,0 +1,5 @@
+lib LibC
+ GRND_NONBLOCK = 1_u32
+
+ fun getrandom(buf : Void*, buflen : SizeT, flags : UInt32) : SSizeT
+end
diff --git a/src/lib_c/i386-linux-musl/c/sys/resource.cr b/src/lib_c/i386-linux-musl/c/sys/resource.cr
index 7f550c37a622..656e43cb0379 100644
--- a/src/lib_c/i386-linux-musl/c/sys/resource.cr
+++ b/src/lib_c/i386-linux-musl/c/sys/resource.cr
@@ -1,4 +1,17 @@
lib LibC
+ alias RlimT = ULongLong
+
+ struct Rlimit
+ rlim_cur : RlimT
+ rlim_max : RlimT
+ end
+
+ RLIMIT_NOFILE = 7
+
+ fun getrlimit(Int, Rlimit*) : Int
+
+ RLIMIT_STACK = 3
+
struct RUsage
ru_utime : Timeval
ru_stime : Timeval
diff --git a/src/lib_c/i386-linux-musl/c/sys/stat.cr b/src/lib_c/i386-linux-musl/c/sys/stat.cr
index c8a96f47a329..679cec5ff0f4 100644
--- a/src/lib_c/i386-linux-musl/c/sys/stat.cr
+++ b/src/lib_c/i386-linux-musl/c/sys/stat.cr
@@ -54,4 +54,5 @@ lib LibC
fun mknod(x0 : Char*, x1 : ModeT, x2 : DevT) : Int
fun stat(x0 : Char*, x1 : Stat*) : Int
fun umask(x0 : ModeT) : ModeT
+ fun utimensat(fd : Int, path : Char*, times : Timespec[2], flag : Int) : Int
end
diff --git a/src/lib_c/i386-linux-musl/c/sys/time.cr b/src/lib_c/i386-linux-musl/c/sys/time.cr
index 711894a3da7e..5e2e5919812c 100644
--- a/src/lib_c/i386-linux-musl/c/sys/time.cr
+++ b/src/lib_c/i386-linux-musl/c/sys/time.cr
@@ -12,6 +12,5 @@ lib LibC
end
fun gettimeofday(x0 : Timeval*, x1 : Void*) : Int
- fun utimes(path : Char*, times : Timeval[2]) : Int
fun futimens(fd : Int, times : Timespec[2]) : Int
end
diff --git a/src/lib_c/i386-linux-musl/c/sys/timerfd.cr b/src/lib_c/i386-linux-musl/c/sys/timerfd.cr
new file mode 100644
index 000000000000..0632646b0e14
--- /dev/null
+++ b/src/lib_c/i386-linux-musl/c/sys/timerfd.cr
@@ -0,0 +1,10 @@
+require "../time"
+
+lib LibC
+ TFD_NONBLOCK = 0o0004000
+ TFD_CLOEXEC = 0o2000000
+ TFD_TIMER_ABSTIME = 1 << 0
+
+ fun timerfd_create(ClockidT, Int) : Int
+ fun timerfd_settime(Int, Int, Itimerspec*, Itimerspec*) : Int
+end
diff --git a/src/lib_c/i386-linux-musl/c/time.cr b/src/lib_c/i386-linux-musl/c/time.cr
index f687c8b35db4..4bf25a7f9efc 100644
--- a/src/lib_c/i386-linux-musl/c/time.cr
+++ b/src/lib_c/i386-linux-musl/c/time.cr
@@ -23,6 +23,11 @@ lib LibC
tv_nsec : Long
end
+ struct Itimerspec
+ it_interval : Timespec
+ it_value : Timespec
+ end
+
fun clock_gettime(x0 : ClockidT, x1 : Timespec*) : Int
fun clock_settime(x0 : ClockidT, x1 : Timespec*) : Int
fun gmtime_r(x0 : TimeT*, x1 : Tm*) : Tm*
diff --git a/src/lib_c/x86_64-darwin/c/signal.cr b/src/lib_c/x86_64-darwin/c/signal.cr
index e58adc30289f..0034eef42834 100644
--- a/src/lib_c/x86_64-darwin/c/signal.cr
+++ b/src/lib_c/x86_64-darwin/c/signal.cr
@@ -77,6 +77,7 @@ lib LibC
fun kill(x0 : PidT, x1 : Int) : Int
fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int
+ fun pthread_kill(PthreadT, Int) : Int
fun signal(x0 : Int, x1 : Int -> Void) : Int -> Void
fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int
fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int
@@ -85,4 +86,5 @@ lib LibC
fun sigaddset(SigsetT*, Int) : Int
fun sigdelset(SigsetT*, Int) : Int
fun sigismember(SigsetT*, Int) : Int
+ fun sigsuspend(SigsetT*) : Int
end
diff --git a/src/lib_c/x86_64-darwin/c/sys/event.cr b/src/lib_c/x86_64-darwin/c/sys/event.cr
new file mode 100644
index 000000000000..1fd68b6d1975
--- /dev/null
+++ b/src/lib_c/x86_64-darwin/c/sys/event.cr
@@ -0,0 +1,31 @@
+require "../time"
+
+lib LibC
+ EVFILT_READ = -1_i16
+ EVFILT_WRITE = -2_i16
+ EVFILT_TIMER = -7_i16
+ EVFILT_USER = -10_i16
+
+ EV_ADD = 0x0001_u16
+ EV_DELETE = 0x0002_u16
+ EV_ENABLE = 0x0004_u16
+ EV_ONESHOT = 0x0010_u16
+ EV_CLEAR = 0x0020_u16
+ EV_EOF = 0x8000_u16
+ EV_ERROR = 0x4000_u16
+
+ NOTE_NSECONDS = 0x00000004_u32
+ NOTE_TRIGGER = 0x01000000_u32
+
+ struct Kevent
+ ident : SizeT # UintptrT
+ filter : Int16
+ flags : UInt16
+ fflags : UInt32
+ data : SSizeT # IntptrT
+ udata : Void*
+ end
+
+ fun kqueue : Int
+ fun kevent(kq : Int, changelist : Kevent*, nchanges : Int, eventlist : Kevent*, nevents : Int, timeout : Timespec*) : Int
+end
diff --git a/src/lib_c/x86_64-darwin/c/sys/resource.cr b/src/lib_c/x86_64-darwin/c/sys/resource.cr
index daa583ac5895..4759e8c9b3e3 100644
--- a/src/lib_c/x86_64-darwin/c/sys/resource.cr
+++ b/src/lib_c/x86_64-darwin/c/sys/resource.cr
@@ -6,6 +6,8 @@ lib LibC
rlim_max : RlimT
end
+ RLIMIT_NOFILE = 8
+
fun getrlimit(Int, Rlimit*) : Int
RLIMIT_STACK = 3
diff --git a/src/lib_c/x86_64-dragonfly/c/fcntl.cr b/src/lib_c/x86_64-dragonfly/c/fcntl.cr
index c9b832e2e919..9f1c643332c3 100644
--- a/src/lib_c/x86_64-dragonfly/c/fcntl.cr
+++ b/src/lib_c/x86_64-dragonfly/c/fcntl.cr
@@ -3,22 +3,23 @@ require "./sys/stat"
require "./unistd"
lib LibC
- F_GETFD = 1
- F_SETFD = 2
- F_GETFL = 3
- F_SETFL = 4
- FD_CLOEXEC = 1
- O_CLOEXEC = 0x20000
- O_EXCL = 0x0800
- O_TRUNC = 0x0400
- O_CREAT = 0x0200
- O_NOFOLLOW = 0x0100
- O_SYNC = 0x0080
- O_APPEND = 0x0008
- O_NONBLOCK = 0x0004
- O_RDWR = 0x0002
- O_WRONLY = 0x0001
- O_RDONLY = 0x0000
+ F_GETFD = 1
+ F_SETFD = 2
+ F_GETFL = 3
+ F_SETFL = 4
+ FD_CLOEXEC = 1
+ O_CLOEXEC = 0x20000
+ O_EXCL = 0x0800
+ O_TRUNC = 0x0400
+ O_CREAT = 0x0200
+ O_NOFOLLOW = 0x0100
+ O_SYNC = 0x0080
+ O_APPEND = 0x0008
+ O_NONBLOCK = 0x0004
+ O_RDWR = 0x0002
+ O_WRONLY = 0x0001
+ O_RDONLY = 0x0000
+ AT_FDCWD = 0xFFFAFDCD
struct Flock
l_start : OffT
diff --git a/src/lib_c/x86_64-dragonfly/c/signal.cr b/src/lib_c/x86_64-dragonfly/c/signal.cr
index 1751eeed3176..e362ef1fa218 100644
--- a/src/lib_c/x86_64-dragonfly/c/signal.cr
+++ b/src/lib_c/x86_64-dragonfly/c/signal.cr
@@ -90,6 +90,7 @@ lib LibC
fun kill(x0 : PidT, x1 : Int) : Int
fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int
+ fun pthread_kill(PthreadT, Int) : Int
fun signal(x0 : Int, x1 : Int -> Void) : Int -> Void
fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int
fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int
@@ -98,4 +99,5 @@ lib LibC
fun sigaddset(SigsetT*, Int) : Int
fun sigdelset(SigsetT*, Int) : Int
fun sigismember(SigsetT*, Int) : Int
+ fun sigsuspend(SigsetT*) : Int
end
diff --git a/src/lib_c/x86_64-dragonfly/c/sys/event.cr b/src/lib_c/x86_64-dragonfly/c/sys/event.cr
new file mode 100644
index 000000000000..aff6274b8fd1
--- /dev/null
+++ b/src/lib_c/x86_64-dragonfly/c/sys/event.cr
@@ -0,0 +1,30 @@
+require "../time"
+
+lib LibC
+ EVFILT_READ = -1_i16
+ EVFILT_WRITE = -2_i16
+ EVFILT_TIMER = -7_i16
+ EVFILT_USER = -9_i16
+
+ EV_ADD = 0x0001_u16
+ EV_DELETE = 0x0002_u16
+ EV_ENABLE = 0x0004_u16
+ EV_ONESHOT = 0x0010_u16
+ EV_CLEAR = 0x0020_u16
+ EV_EOF = 0x8000_u16
+ EV_ERROR = 0x4000_u16
+
+ NOTE_TRIGGER = 0x01000000_u32
+
+ struct Kevent
+ ident : SizeT # UintptrT
+ filter : Short
+ flags : UShort
+ fflags : UInt
+ data : SSizeT # IntptrT
+ udata : Void*
+ end
+
+ fun kqueue : Int
+ fun kevent(kq : Int, changelist : Kevent*, nchanges : Int, eventlist : Kevent*, nevents : Int, timeout : Timespec*) : Int
+end
diff --git a/src/lib_c/x86_64-dragonfly/c/sys/resource.cr b/src/lib_c/x86_64-dragonfly/c/sys/resource.cr
index d52182f69bce..388b52651f21 100644
--- a/src/lib_c/x86_64-dragonfly/c/sys/resource.cr
+++ b/src/lib_c/x86_64-dragonfly/c/sys/resource.cr
@@ -22,4 +22,15 @@ lib LibC
RUSAGE_CHILDREN = -1
fun getrusage(who : Int, usage : RUsage*) : Int
+
+ alias RlimT = UInt64
+
+ struct Rlimit
+ rlim_cur : RlimT
+ rlim_max : RlimT
+ end
+
+ RLIMIT_NOFILE = 8
+
+ fun getrlimit(resource : Int, rlim : Rlimit*) : Int
end
diff --git a/src/lib_c/x86_64-dragonfly/c/sys/stat.cr b/src/lib_c/x86_64-dragonfly/c/sys/stat.cr
index 6415607a2bad..14d1ed8350ff 100644
--- a/src/lib_c/x86_64-dragonfly/c/sys/stat.cr
+++ b/src/lib_c/x86_64-dragonfly/c/sys/stat.cr
@@ -59,4 +59,5 @@ lib LibC
fun mknod(x0 : Char*, x1 : ModeT, x2 : DevT) : Int
fun stat(x0 : Char*, x1 : Stat*) : Int
fun umask(x0 : ModeT) : ModeT
+ fun utimensat(fd : Int, path : Char*, times : Timespec[2], flag : Int) : Int
end
diff --git a/src/lib_c/x86_64-dragonfly/c/sys/time.cr b/src/lib_c/x86_64-dragonfly/c/sys/time.cr
index 9795c61a3119..c40e74752968 100644
--- a/src/lib_c/x86_64-dragonfly/c/sys/time.cr
+++ b/src/lib_c/x86_64-dragonfly/c/sys/time.cr
@@ -12,6 +12,5 @@ lib LibC
end
fun gettimeofday(x0 : Timeval*, x1 : Timezone*) : Int
- fun utimes(path : Char*, times : Timeval[2]) : Int
fun futimens(fd : Int, times : Timespec[2]) : Int
end
diff --git a/src/lib_c/x86_64-freebsd/c/fcntl.cr b/src/lib_c/x86_64-freebsd/c/fcntl.cr
index d5c507efac29..e0de63751ff7 100644
--- a/src/lib_c/x86_64-freebsd/c/fcntl.cr
+++ b/src/lib_c/x86_64-freebsd/c/fcntl.cr
@@ -19,6 +19,7 @@ lib LibC
O_RDONLY = 0x0000
O_RDWR = 0x0002
O_WRONLY = 0x0001
+ AT_FDCWD = -100
struct Flock
l_start : OffT
diff --git a/src/lib_c/x86_64-freebsd/c/signal.cr b/src/lib_c/x86_64-freebsd/c/signal.cr
index fd8d07cfd4cc..c79d0630511b 100644
--- a/src/lib_c/x86_64-freebsd/c/signal.cr
+++ b/src/lib_c/x86_64-freebsd/c/signal.cr
@@ -8,31 +8,33 @@ lib LibC
SIGILL = 4
SIGTRAP = 5
SIGIOT = LibC::SIGABRT
- SIGABRT = 6
- SIGFPE = 8
- SIGKILL = 9
- SIGBUS = 10
- SIGSEGV = 11
- SIGSYS = 12
- SIGPIPE = 13
- SIGALRM = 14
- SIGTERM = 15
- SIGURG = 16
- SIGSTOP = 17
- SIGTSTP = 18
- SIGCONT = 19
- SIGCHLD = 20
- SIGTTIN = 21
- SIGTTOU = 22
- SIGIO = 23
- SIGXCPU = 24
- SIGXFSZ = 25
- SIGVTALRM = 26
- SIGUSR1 = 30
- SIGUSR2 = 31
- SIGEMT = 7
- SIGINFO = 29
- SIGWINCH = 28
+ SIGABRT = 6
+ SIGFPE = 8
+ SIGKILL = 9
+ SIGBUS = 10
+ SIGSEGV = 11
+ SIGSYS = 12
+ SIGPIPE = 13
+ SIGALRM = 14
+ SIGTERM = 15
+ SIGURG = 16
+ SIGSTOP = 17
+ SIGTSTP = 18
+ SIGCONT = 19
+ SIGCHLD = 20
+ SIGTTIN = 21
+ SIGTTOU = 22
+ SIGIO = 23
+ SIGXCPU = 24
+ SIGXFSZ = 25
+ SIGVTALRM = 26
+ SIGUSR1 = 30
+ SIGUSR2 = 31
+ SIGEMT = 7
+ SIGINFO = 29
+ SIGWINCH = 28
+ SIGRTMIN = 65
+ SIGRTMAX = 126
SIGSTKSZ = 2048 + 32768 # MINSIGSTKSZ + 32768
SIG_SETMASK = 3
@@ -85,6 +87,7 @@ lib LibC
fun kill(x0 : PidT, x1 : Int) : Int
fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int
+ fun pthread_kill(PthreadT, Int) : Int
fun signal(x0 : Int, x1 : Int -> Void) : Int -> Void
fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int
fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int
@@ -93,4 +96,5 @@ lib LibC
fun sigaddset(SigsetT*, Int) : Int
fun sigdelset(SigsetT*, Int) : Int
fun sigismember(SigsetT*, Int) : Int
+ fun sigsuspend(SigsetT*) : Int
end
diff --git a/src/lib_c/x86_64-freebsd/c/sys/event.cr b/src/lib_c/x86_64-freebsd/c/sys/event.cr
new file mode 100644
index 000000000000..0abe0686aba0
--- /dev/null
+++ b/src/lib_c/x86_64-freebsd/c/sys/event.cr
@@ -0,0 +1,32 @@
+require "../time"
+
+lib LibC
+ EVFILT_READ = -1_i16
+ EVFILT_WRITE = -2_i16
+ EVFILT_TIMER = -7_i16
+ EVFILT_USER = -11_i16
+
+ EV_ADD = 0x0001_u16
+ EV_DELETE = 0x0002_u16
+ EV_ENABLE = 0x0004_u16
+ EV_ONESHOT = 0x0010_u16
+ EV_CLEAR = 0x0020_u16
+ EV_EOF = 0x8000_u16
+ EV_ERROR = 0x4000_u16
+
+ NOTE_NSECONDS = 0x00000008_u32
+ NOTE_TRIGGER = 0x01000000_u32
+
+ struct Kevent
+ ident : SizeT # UintptrT
+ filter : Short
+ flags : UShort
+ fflags : UInt
+ data : Int64
+ udata : Void*
+ ext : UInt64[4]
+ end
+
+ fun kqueue1(flags : Int) : Int
+ fun kevent(kq : Int, changelist : Kevent*, nchanges : Int, eventlist : Kevent*, nevents : Int, timeout : Timespec*) : Int
+end
diff --git a/src/lib_c/x86_64-freebsd/c/sys/resource.cr b/src/lib_c/x86_64-freebsd/c/sys/resource.cr
index 7f550c37a622..6f078dda986d 100644
--- a/src/lib_c/x86_64-freebsd/c/sys/resource.cr
+++ b/src/lib_c/x86_64-freebsd/c/sys/resource.cr
@@ -22,4 +22,15 @@ lib LibC
RUSAGE_CHILDREN = -1
fun getrusage(who : Int, usage : RUsage*) : Int16
+
+ alias RlimT = UInt64
+
+ struct Rlimit
+ rlim_cur : RlimT
+ rlim_max : RlimT
+ end
+
+ RLIMIT_NOFILE = 8
+
+ fun getrlimit(resource : Int, rlim : Rlimit*) : Int
end
diff --git a/src/lib_c/x86_64-freebsd/c/sys/stat.cr b/src/lib_c/x86_64-freebsd/c/sys/stat.cr
index 32334987cdb0..59334c508453 100644
--- a/src/lib_c/x86_64-freebsd/c/sys/stat.cr
+++ b/src/lib_c/x86_64-freebsd/c/sys/stat.cr
@@ -59,4 +59,5 @@ lib LibC
fun mknod(x0 : Char*, x1 : ModeT, x2 : DevT) : Int
fun stat(x0 : Char*, x1 : Stat*) : Int
fun umask(x0 : ModeT) : ModeT
+ fun utimensat(fd : Int, path : Char*, times : Timespec[2], flag : Int) : Int
end
diff --git a/src/lib_c/x86_64-freebsd/c/sys/time.cr b/src/lib_c/x86_64-freebsd/c/sys/time.cr
index 9795c61a3119..c40e74752968 100644
--- a/src/lib_c/x86_64-freebsd/c/sys/time.cr
+++ b/src/lib_c/x86_64-freebsd/c/sys/time.cr
@@ -12,6 +12,5 @@ lib LibC
end
fun gettimeofday(x0 : Timeval*, x1 : Timezone*) : Int
- fun utimes(path : Char*, times : Timeval[2]) : Int
fun futimens(fd : Int, times : Timespec[2]) : Int
end
diff --git a/src/lib_c/x86_64-linux-gnu/c/fcntl.cr b/src/lib_c/x86_64-linux-gnu/c/fcntl.cr
index 7f46cb647918..4b33c823760f 100644
--- a/src/lib_c/x86_64-linux-gnu/c/fcntl.cr
+++ b/src/lib_c/x86_64-linux-gnu/c/fcntl.cr
@@ -19,6 +19,7 @@ lib LibC
O_RDONLY = 0o0
O_RDWR = 0o2
O_WRONLY = 0o1
+ AT_FDCWD = -100
struct Flock
l_type : Short
diff --git a/src/lib_c/x86_64-linux-gnu/c/signal.cr b/src/lib_c/x86_64-linux-gnu/c/signal.cr
index 07d8e0fe1ae6..b5ed2f8c8fb3 100644
--- a/src/lib_c/x86_64-linux-gnu/c/signal.cr
+++ b/src/lib_c/x86_64-linux-gnu/c/signal.cr
@@ -78,6 +78,7 @@ lib LibC
fun kill(pid : PidT, sig : Int) : Int
fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int
+ fun pthread_kill(PthreadT, Int) : Int
fun signal(sig : Int, handler : Int -> Void) : Int -> Void
fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int
fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int
@@ -86,4 +87,5 @@ lib LibC
fun sigaddset(SigsetT*, Int) : Int
fun sigdelset(SigsetT*, Int) : Int
fun sigismember(SigsetT*, Int) : Int
+ fun sigsuspend(SigsetT*) : Int
end
diff --git a/src/lib_c/x86_64-linux-gnu/c/sys/epoll.cr b/src/lib_c/x86_64-linux-gnu/c/sys/epoll.cr
new file mode 100644
index 000000000000..4dc752f64652
--- /dev/null
+++ b/src/lib_c/x86_64-linux-gnu/c/sys/epoll.cr
@@ -0,0 +1,33 @@
+lib LibC
+ EPOLLIN = 0x001_u32
+ EPOLLOUT = 0x004_u32
+ EPOLLERR = 0x008_u32
+ EPOLLHUP = 0x010_u32
+ EPOLLRDHUP = 0x2000_u32
+
+ EPOLLEXCLUSIVE = 1_u32 << 28
+ EPOLLET = 1_u32 << 31
+
+ EPOLL_CTL_ADD = 1
+ EPOLL_CTL_DEL = 2
+ EPOLL_CTL_MOD = 3
+
+ EPOLL_CLOEXEC = 0o2000000
+
+ union EpollDataT
+ ptr : Void*
+ fd : Int
+ u32 : UInt32
+ u64 : UInt64
+ end
+
+ @[Packed]
+ struct EpollEvent
+ events : UInt32
+ data : EpollDataT
+ end
+
+ fun epoll_create1(Int) : Int
+ fun epoll_ctl(Int, Int, Int, EpollEvent*) : Int
+ fun epoll_wait(Int, EpollEvent*, Int, Int) : Int
+end
diff --git a/src/lib_c/x86_64-linux-gnu/c/sys/eventfd.cr b/src/lib_c/x86_64-linux-gnu/c/sys/eventfd.cr
new file mode 100644
index 000000000000..12f24428a8f4
--- /dev/null
+++ b/src/lib_c/x86_64-linux-gnu/c/sys/eventfd.cr
@@ -0,0 +1,5 @@
+lib LibC
+ EFD_CLOEXEC = 0o2000000
+
+ fun eventfd(count : UInt, flags : Int) : Int
+end
diff --git a/src/lib_c/x86_64-linux-gnu/c/sys/random.cr b/src/lib_c/x86_64-linux-gnu/c/sys/random.cr
new file mode 100644
index 000000000000..2c74de96abfb
--- /dev/null
+++ b/src/lib_c/x86_64-linux-gnu/c/sys/random.cr
@@ -0,0 +1,5 @@
+lib LibC
+ GRND_NONBLOCK = 1_u32
+
+ fun getrandom(buf : Void*, buflen : SizeT, flags : UInt32) : SSizeT
+end
diff --git a/src/lib_c/x86_64-linux-gnu/c/sys/resource.cr b/src/lib_c/x86_64-linux-gnu/c/sys/resource.cr
index a0900a4730c4..444c4ba692c8 100644
--- a/src/lib_c/x86_64-linux-gnu/c/sys/resource.cr
+++ b/src/lib_c/x86_64-linux-gnu/c/sys/resource.cr
@@ -22,4 +22,15 @@ lib LibC
RUSAGE_CHILDREN = -1
fun getrusage(who : Int, usage : RUsage*) : Int
+
+ alias RlimT = ULongLong
+
+ struct Rlimit
+ rlim_cur : RlimT
+ rlim_max : RlimT
+ end
+
+ RLIMIT_NOFILE = 7
+
+ fun getrlimit(resource : Int, rlim : Rlimit*) : Int
end
diff --git a/src/lib_c/x86_64-linux-gnu/c/sys/stat.cr b/src/lib_c/x86_64-linux-gnu/c/sys/stat.cr
index 281f0f160d54..36df0ce15cdc 100644
--- a/src/lib_c/x86_64-linux-gnu/c/sys/stat.cr
+++ b/src/lib_c/x86_64-linux-gnu/c/sys/stat.cr
@@ -58,4 +58,5 @@ lib LibC
fun stat(file : Char*, buf : Stat*) : Int
fun __xstat(ver : Int, file : Char*, buf : Stat*) : Int
fun umask(mask : ModeT) : ModeT
+ fun utimensat(fd : Int, path : Char*, times : Timespec[2], flag : Int) : Int
end
diff --git a/src/lib_c/x86_64-linux-gnu/c/sys/time.cr b/src/lib_c/x86_64-linux-gnu/c/sys/time.cr
index 664de111502a..9e7d921c2728 100644
--- a/src/lib_c/x86_64-linux-gnu/c/sys/time.cr
+++ b/src/lib_c/x86_64-linux-gnu/c/sys/time.cr
@@ -12,6 +12,5 @@ lib LibC
end
fun gettimeofday(tv : Timeval*, tz : Void*) : Int
- fun utimes(path : Char*, times : Timeval[2]) : Int
fun futimens(fd : Int, times : Timespec[2]) : Int
end
diff --git a/src/lib_c/x86_64-linux-gnu/c/sys/timerfd.cr b/src/lib_c/x86_64-linux-gnu/c/sys/timerfd.cr
new file mode 100644
index 000000000000..0632646b0e14
--- /dev/null
+++ b/src/lib_c/x86_64-linux-gnu/c/sys/timerfd.cr
@@ -0,0 +1,10 @@
+require "../time"
+
+lib LibC
+ TFD_NONBLOCK = 0o0004000
+ TFD_CLOEXEC = 0o2000000
+ TFD_TIMER_ABSTIME = 1 << 0
+
+ fun timerfd_create(ClockidT, Int) : Int
+ fun timerfd_settime(Int, Int, Itimerspec*, Itimerspec*) : Int
+end
diff --git a/src/lib_c/x86_64-linux-gnu/c/time.cr b/src/lib_c/x86_64-linux-gnu/c/time.cr
index 710d477e269b..d00579281b41 100644
--- a/src/lib_c/x86_64-linux-gnu/c/time.cr
+++ b/src/lib_c/x86_64-linux-gnu/c/time.cr
@@ -23,6 +23,11 @@ lib LibC
tv_nsec : Long
end
+ struct Itimerspec
+ it_interval : Timespec
+ it_value : Timespec
+ end
+
fun clock_gettime(clock_id : ClockidT, tp : Timespec*) : Int
fun clock_settime(clock_id : ClockidT, tp : Timespec*) : Int
fun gmtime_r(timer : TimeT*, tp : Tm*) : Tm*
diff --git a/src/lib_c/x86_64-linux-musl/c/fcntl.cr b/src/lib_c/x86_64-linux-musl/c/fcntl.cr
index 27a5cf0c22d3..fa53d4b1e378 100644
--- a/src/lib_c/x86_64-linux-musl/c/fcntl.cr
+++ b/src/lib_c/x86_64-linux-musl/c/fcntl.cr
@@ -19,6 +19,7 @@ lib LibC
O_RDONLY = 0o0
O_RDWR = 0o2
O_WRONLY = 0o1
+ AT_FDCWD = -100
struct Flock
l_type : Short
diff --git a/src/lib_c/x86_64-linux-musl/c/signal.cr b/src/lib_c/x86_64-linux-musl/c/signal.cr
index bba7e0c7c21a..42c2aead3e0f 100644
--- a/src/lib_c/x86_64-linux-musl/c/signal.cr
+++ b/src/lib_c/x86_64-linux-musl/c/signal.cr
@@ -77,6 +77,7 @@ lib LibC
fun kill(x0 : PidT, x1 : Int) : Int
fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int
+ fun pthread_kill(PthreadT, Int) : Int
fun signal(x0 : Int, x1 : Int -> Void) : Int -> Void
fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int
fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int
@@ -85,4 +86,5 @@ lib LibC
fun sigaddset(SigsetT*, Int) : Int
fun sigdelset(SigsetT*, Int) : Int
fun sigismember(SigsetT*, Int) : Int
+ fun sigsuspend(SigsetT*) : Int
end
diff --git a/src/lib_c/x86_64-linux-musl/c/sys/epoll.cr b/src/lib_c/x86_64-linux-musl/c/sys/epoll.cr
new file mode 100644
index 000000000000..4dc752f64652
--- /dev/null
+++ b/src/lib_c/x86_64-linux-musl/c/sys/epoll.cr
@@ -0,0 +1,33 @@
+lib LibC
+ EPOLLIN = 0x001_u32
+ EPOLLOUT = 0x004_u32
+ EPOLLERR = 0x008_u32
+ EPOLLHUP = 0x010_u32
+ EPOLLRDHUP = 0x2000_u32
+
+ EPOLLEXCLUSIVE = 1_u32 << 28
+ EPOLLET = 1_u32 << 31
+
+ EPOLL_CTL_ADD = 1
+ EPOLL_CTL_DEL = 2
+ EPOLL_CTL_MOD = 3
+
+ EPOLL_CLOEXEC = 0o2000000
+
+ union EpollDataT
+ ptr : Void*
+ fd : Int
+ u32 : UInt32
+ u64 : UInt64
+ end
+
+ @[Packed]
+ struct EpollEvent
+ events : UInt32
+ data : EpollDataT
+ end
+
+ fun epoll_create1(Int) : Int
+ fun epoll_ctl(Int, Int, Int, EpollEvent*) : Int
+ fun epoll_wait(Int, EpollEvent*, Int, Int) : Int
+end
diff --git a/src/lib_c/x86_64-linux-musl/c/sys/eventfd.cr b/src/lib_c/x86_64-linux-musl/c/sys/eventfd.cr
new file mode 100644
index 000000000000..12f24428a8f4
--- /dev/null
+++ b/src/lib_c/x86_64-linux-musl/c/sys/eventfd.cr
@@ -0,0 +1,5 @@
+lib LibC
+ EFD_CLOEXEC = 0o2000000
+
+ fun eventfd(count : UInt, flags : Int) : Int
+end
diff --git a/src/lib_c/x86_64-linux-musl/c/sys/random.cr b/src/lib_c/x86_64-linux-musl/c/sys/random.cr
new file mode 100644
index 000000000000..2c74de96abfb
--- /dev/null
+++ b/src/lib_c/x86_64-linux-musl/c/sys/random.cr
@@ -0,0 +1,5 @@
+lib LibC
+ GRND_NONBLOCK = 1_u32
+
+ fun getrandom(buf : Void*, buflen : SizeT, flags : UInt32) : SSizeT
+end
diff --git a/src/lib_c/x86_64-linux-musl/c/sys/resource.cr b/src/lib_c/x86_64-linux-musl/c/sys/resource.cr
index 7f550c37a622..656e43cb0379 100644
--- a/src/lib_c/x86_64-linux-musl/c/sys/resource.cr
+++ b/src/lib_c/x86_64-linux-musl/c/sys/resource.cr
@@ -1,4 +1,17 @@
lib LibC
+ alias RlimT = ULongLong
+
+ struct Rlimit
+ rlim_cur : RlimT
+ rlim_max : RlimT
+ end
+
+ RLIMIT_NOFILE = 7
+
+ fun getrlimit(Int, Rlimit*) : Int
+
+ RLIMIT_STACK = 3
+
struct RUsage
ru_utime : Timeval
ru_stime : Timeval
diff --git a/src/lib_c/x86_64-linux-musl/c/sys/stat.cr b/src/lib_c/x86_64-linux-musl/c/sys/stat.cr
index 921c108cef66..fc2b814ad203 100644
--- a/src/lib_c/x86_64-linux-musl/c/sys/stat.cr
+++ b/src/lib_c/x86_64-linux-musl/c/sys/stat.cr
@@ -53,4 +53,5 @@ lib LibC
fun mknod(x0 : Char*, x1 : ModeT, x2 : DevT) : Int
fun stat(x0 : Char*, x1 : Stat*) : Int
fun umask(x0 : ModeT) : ModeT
+ fun utimensat(fd : Int, path : Char*, times : Timespec[2], flag : Int) : Int
end
diff --git a/src/lib_c/x86_64-linux-musl/c/sys/time.cr b/src/lib_c/x86_64-linux-musl/c/sys/time.cr
index 711894a3da7e..5e2e5919812c 100644
--- a/src/lib_c/x86_64-linux-musl/c/sys/time.cr
+++ b/src/lib_c/x86_64-linux-musl/c/sys/time.cr
@@ -12,6 +12,5 @@ lib LibC
end
fun gettimeofday(x0 : Timeval*, x1 : Void*) : Int
- fun utimes(path : Char*, times : Timeval[2]) : Int
fun futimens(fd : Int, times : Timespec[2]) : Int
end
diff --git a/src/lib_c/x86_64-linux-musl/c/sys/timerfd.cr b/src/lib_c/x86_64-linux-musl/c/sys/timerfd.cr
new file mode 100644
index 000000000000..0632646b0e14
--- /dev/null
+++ b/src/lib_c/x86_64-linux-musl/c/sys/timerfd.cr
@@ -0,0 +1,10 @@
+require "../time"
+
+lib LibC
+ TFD_NONBLOCK = 0o0004000
+ TFD_CLOEXEC = 0o2000000
+ TFD_TIMER_ABSTIME = 1 << 0
+
+ fun timerfd_create(ClockidT, Int) : Int
+ fun timerfd_settime(Int, Int, Itimerspec*, Itimerspec*) : Int
+end
diff --git a/src/lib_c/x86_64-linux-musl/c/time.cr b/src/lib_c/x86_64-linux-musl/c/time.cr
index f687c8b35db4..4bf25a7f9efc 100644
--- a/src/lib_c/x86_64-linux-musl/c/time.cr
+++ b/src/lib_c/x86_64-linux-musl/c/time.cr
@@ -23,6 +23,11 @@ lib LibC
tv_nsec : Long
end
+ struct Itimerspec
+ it_interval : Timespec
+ it_value : Timespec
+ end
+
fun clock_gettime(x0 : ClockidT, x1 : Timespec*) : Int
fun clock_settime(x0 : ClockidT, x1 : Timespec*) : Int
fun gmtime_r(x0 : TimeT*, x1 : Tm*) : Tm*
diff --git a/src/lib_c/x86_64-netbsd/c/dirent.cr b/src/lib_c/x86_64-netbsd/c/dirent.cr
index 71dabe7b08ce..e3b8492083f7 100644
--- a/src/lib_c/x86_64-netbsd/c/dirent.cr
+++ b/src/lib_c/x86_64-netbsd/c/dirent.cr
@@ -29,5 +29,4 @@ lib LibC
fun opendir = __opendir30(x0 : Char*) : DIR*
fun readdir = __readdir30(x0 : DIR*) : Dirent*
fun rewinddir(x0 : DIR*) : Void
- fun dirfd(dirp : DIR*) : Int
end
diff --git a/src/lib_c/x86_64-netbsd/c/fcntl.cr b/src/lib_c/x86_64-netbsd/c/fcntl.cr
index 3a1ffe9d85c6..e3ec78a5e70d 100644
--- a/src/lib_c/x86_64-netbsd/c/fcntl.cr
+++ b/src/lib_c/x86_64-netbsd/c/fcntl.cr
@@ -19,6 +19,7 @@ lib LibC
O_RDONLY = 0x0000
O_RDWR = 0x0002
O_WRONLY = 0x0001
+ AT_FDCWD = -100
struct Flock
l_start : OffT
diff --git a/src/lib_c/x86_64-netbsd/c/netdb.cr b/src/lib_c/x86_64-netbsd/c/netdb.cr
index 4443325cd487..c098ab2f5fc6 100644
--- a/src/lib_c/x86_64-netbsd/c/netdb.cr
+++ b/src/lib_c/x86_64-netbsd/c/netdb.cr
@@ -13,6 +13,7 @@ lib LibC
EAI_FAIL = 4
EAI_FAMILY = 5
EAI_MEMORY = 6
+ EAI_NODATA = 7
EAI_NONAME = 8
EAI_SERVICE = 9
EAI_SOCKTYPE = 10
diff --git a/src/lib_c/x86_64-netbsd/c/signal.cr b/src/lib_c/x86_64-netbsd/c/signal.cr
index 93d42e38b093..0b21c5c3f839 100644
--- a/src/lib_c/x86_64-netbsd/c/signal.cr
+++ b/src/lib_c/x86_64-netbsd/c/signal.cr
@@ -77,6 +77,7 @@ lib LibC
fun kill(x0 : PidT, x1 : Int) : Int
fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int
+ fun pthread_kill(PthreadT, Int) : Int
fun signal(x0 : Int, x1 : Int -> Void) : Int -> Void
fun sigaction = __sigaction14(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int
fun sigaltstack = __sigaltstack14(x0 : StackT*, x1 : StackT*) : Int
@@ -85,4 +86,5 @@ lib LibC
fun sigaddset = __sigaddset14(SigsetT*, Int) : Int
fun sigdelset = __sigdelset14(SigsetT*, Int) : Int
fun sigismember = __sigismember14(SigsetT*, Int) : Int
+ fun sigsuspend(SigsetT*) : Int
end
diff --git a/src/lib_c/x86_64-netbsd/c/sys/event.cr b/src/lib_c/x86_64-netbsd/c/sys/event.cr
new file mode 100644
index 000000000000..91da3cea1a04
--- /dev/null
+++ b/src/lib_c/x86_64-netbsd/c/sys/event.cr
@@ -0,0 +1,32 @@
+require "../time"
+
+lib LibC
+ EVFILT_READ = 0_u32
+ EVFILT_WRITE = 1_u32
+ EVFILT_TIMER = 6_u32
+ EVFILT_USER = 8_u32
+
+ EV_ADD = 0x0001_u32
+ EV_DELETE = 0x0002_u32
+ EV_ENABLE = 0x0004_u16
+ EV_ONESHOT = 0x0010_u32
+ EV_CLEAR = 0x0020_u32
+ EV_EOF = 0x8000_u32
+ EV_ERROR = 0x4000_u32
+
+ NOTE_NSECONDS = 0x00000003_u32
+ NOTE_TRIGGER = 0x01000000_u32
+
+ struct Kevent
+ ident : SizeT # UintptrT
+ filter : UInt32
+ flags : UInt32
+ fflags : UInt32
+ data : Int64
+ udata : Void*
+ ext : UInt64[4]
+ end
+
+ fun kqueue1(flags : Int) : Int
+ fun kevent = __kevent50(kq : Int, changelist : Kevent*, nchanges : SizeT, eventlist : Kevent*, nevents : SizeT, timeout : Timespec*) : Int
+end
diff --git a/src/lib_c/x86_64-netbsd/c/sys/resource.cr b/src/lib_c/x86_64-netbsd/c/sys/resource.cr
index d52182f69bce..388b52651f21 100644
--- a/src/lib_c/x86_64-netbsd/c/sys/resource.cr
+++ b/src/lib_c/x86_64-netbsd/c/sys/resource.cr
@@ -22,4 +22,15 @@ lib LibC
RUSAGE_CHILDREN = -1
fun getrusage(who : Int, usage : RUsage*) : Int
+
+ alias RlimT = UInt64
+
+ struct Rlimit
+ rlim_cur : RlimT
+ rlim_max : RlimT
+ end
+
+ RLIMIT_NOFILE = 8
+
+ fun getrlimit(resource : Int, rlim : Rlimit*) : Int
end
diff --git a/src/lib_c/x86_64-netbsd/c/sys/stat.cr b/src/lib_c/x86_64-netbsd/c/sys/stat.cr
index 0da836e1c8eb..62b0db89770e 100644
--- a/src/lib_c/x86_64-netbsd/c/sys/stat.cr
+++ b/src/lib_c/x86_64-netbsd/c/sys/stat.cr
@@ -55,4 +55,5 @@ lib LibC
fun mknod = __mknod50(x0 : Char*, x1 : ModeT, x2 : DevT) : Int
fun stat = __stat50(x0 : Char*, x1 : Stat*) : Int
fun umask(x0 : ModeT) : ModeT
+ fun utimensat(fd : Int, path : Char*, times : Timespec[2], flag : Int) : Int
end
diff --git a/src/lib_c/x86_64-netbsd/c/sys/time.cr b/src/lib_c/x86_64-netbsd/c/sys/time.cr
index f276784708c0..6a739b4a89db 100644
--- a/src/lib_c/x86_64-netbsd/c/sys/time.cr
+++ b/src/lib_c/x86_64-netbsd/c/sys/time.cr
@@ -12,6 +12,5 @@ lib LibC
end
fun gettimeofday = __gettimeofday50(x0 : Timeval*, x1 : Timezone*) : Int
- fun utimes = __utimes50(path : Char*, times : Timeval[2]) : Int
- fun futimens = __futimens50(fd : Int, times : Timespec[2]) : Int
+ fun futimens(fd : Int, times : Timespec[2]) : Int
end
diff --git a/src/lib_c/x86_64-openbsd/c/fcntl.cr b/src/lib_c/x86_64-openbsd/c/fcntl.cr
index 6de726e50bf5..1b74e9a75f69 100644
--- a/src/lib_c/x86_64-openbsd/c/fcntl.cr
+++ b/src/lib_c/x86_64-openbsd/c/fcntl.cr
@@ -19,6 +19,7 @@ lib LibC
O_RDONLY = 0x0000
O_RDWR = 0x0002
O_WRONLY = 0x0001
+ AT_FDCWD = -100
struct Flock
l_start : OffT
diff --git a/src/lib_c/x86_64-openbsd/c/netdb.cr b/src/lib_c/x86_64-openbsd/c/netdb.cr
index be3c5f06ab2d..6dd1e6c8513f 100644
--- a/src/lib_c/x86_64-openbsd/c/netdb.cr
+++ b/src/lib_c/x86_64-openbsd/c/netdb.cr
@@ -13,6 +13,7 @@ lib LibC
EAI_FAIL = -4
EAI_FAMILY = -6
EAI_MEMORY = -10
+ EAI_NODATA = -5
EAI_NONAME = -2
EAI_SERVICE = -8
EAI_SOCKTYPE = -7
diff --git a/src/lib_c/x86_64-openbsd/c/signal.cr b/src/lib_c/x86_64-openbsd/c/signal.cr
index 04aa27000219..1c9b86137e4a 100644
--- a/src/lib_c/x86_64-openbsd/c/signal.cr
+++ b/src/lib_c/x86_64-openbsd/c/signal.cr
@@ -76,6 +76,7 @@ lib LibC
fun kill(x0 : PidT, x1 : Int) : Int
fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int
+ fun pthread_kill(PthreadT, Int) : Int
fun signal(x0 : Int, x1 : Int -> Void) : Int -> Void
fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int
fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int
@@ -84,4 +85,5 @@ lib LibC
fun sigaddset(SigsetT*, Int) : Int
fun sigdelset(SigsetT*, Int) : Int
fun sigismember(SigsetT*, Int) : Int
+ fun sigsuspend(SigsetT*) : Int
end
diff --git a/src/lib_c/x86_64-openbsd/c/sys/event.cr b/src/lib_c/x86_64-openbsd/c/sys/event.cr
new file mode 100644
index 000000000000..b95764cb7f54
--- /dev/null
+++ b/src/lib_c/x86_64-openbsd/c/sys/event.cr
@@ -0,0 +1,28 @@
+require "../time"
+
+lib LibC
+ EVFILT_READ = -1_i16
+ EVFILT_WRITE = -2_i16
+ EVFILT_TIMER = -7_i16
+
+ EV_ADD = 0x0001_u16
+ EV_DELETE = 0x0002_u16
+ EV_ONESHOT = 0x0010_u16
+ EV_CLEAR = 0x0020_u16
+ EV_EOF = 0x8000_u16
+ EV_ERROR = 0x4000_u16
+
+ NOTE_NSECONDS = 0x00000003_u32
+
+ struct Kevent
+ ident : SizeT # UintptrT
+ filter : Short
+ flags : UShort
+ fflags : UInt
+ data : Int64
+ udata : Void*
+ end
+
+ fun kqueue1(flags : Int) : Int
+ fun kevent(kq : Int, changelist : Kevent*, nchanges : Int, eventlist : Kevent*, nevents : Int, timeout : Timespec*) : Int
+end
diff --git a/src/lib_c/x86_64-openbsd/c/sys/resource.cr b/src/lib_c/x86_64-openbsd/c/sys/resource.cr
index 7f550c37a622..6f078dda986d 100644
--- a/src/lib_c/x86_64-openbsd/c/sys/resource.cr
+++ b/src/lib_c/x86_64-openbsd/c/sys/resource.cr
@@ -22,4 +22,15 @@ lib LibC
RUSAGE_CHILDREN = -1
fun getrusage(who : Int, usage : RUsage*) : Int16
+
+ alias RlimT = UInt64
+
+ struct Rlimit
+ rlim_cur : RlimT
+ rlim_max : RlimT
+ end
+
+ RLIMIT_NOFILE = 8
+
+ fun getrlimit(resource : Int, rlim : Rlimit*) : Int
end
diff --git a/src/lib_c/x86_64-openbsd/c/sys/stat.cr b/src/lib_c/x86_64-openbsd/c/sys/stat.cr
index 4d40ac1479d5..f3e8af683bb4 100644
--- a/src/lib_c/x86_64-openbsd/c/sys/stat.cr
+++ b/src/lib_c/x86_64-openbsd/c/sys/stat.cr
@@ -54,4 +54,5 @@ lib LibC
fun mknod(x0 : Char*, x1 : ModeT, x2 : DevT) : Int
fun stat(x0 : Char*, x1 : Stat*) : Int
fun umask(x0 : ModeT) : ModeT
+ fun utimensat(fd : Int, path : Char*, times : Timespec[2], flag : Int) : Int
end
diff --git a/src/lib_c/x86_64-openbsd/c/sys/time.cr b/src/lib_c/x86_64-openbsd/c/sys/time.cr
index 9795c61a3119..c40e74752968 100644
--- a/src/lib_c/x86_64-openbsd/c/sys/time.cr
+++ b/src/lib_c/x86_64-openbsd/c/sys/time.cr
@@ -12,6 +12,5 @@ lib LibC
end
fun gettimeofday(x0 : Timeval*, x1 : Timezone*) : Int
- fun utimes(path : Char*, times : Timeval[2]) : Int
fun futimens(fd : Int, times : Timespec[2]) : Int
end
diff --git a/src/lib_c/x86_64-solaris/c/fcntl.cr b/src/lib_c/x86_64-solaris/c/fcntl.cr
index 6bb34dbdd169..20e8bb699aa1 100644
--- a/src/lib_c/x86_64-solaris/c/fcntl.cr
+++ b/src/lib_c/x86_64-solaris/c/fcntl.cr
@@ -3,22 +3,23 @@ require "./sys/stat"
require "./unistd"
lib LibC
- F_GETFD = 1
- F_SETFD = 2
- F_GETFL = 3
- F_SETFL = 4
- FD_CLOEXEC = 1
- O_CLOEXEC = 0x800000
- O_CREAT = 0x100
- O_NOFOLLOW = 0x20000
- O_TRUNC = 0x200
- O_EXCL = 0x400
- O_APPEND = 0x08
- O_NONBLOCK = 0x80
- O_SYNC = 0x10
- O_RDONLY = 0
- O_RDWR = 2
- O_WRONLY = 1
+ F_GETFD = 1
+ F_SETFD = 2
+ F_GETFL = 3
+ F_SETFL = 4
+ FD_CLOEXEC = 1
+ O_CLOEXEC = 0x800000
+ O_CREAT = 0x100
+ O_NOFOLLOW = 0x20000
+ O_TRUNC = 0x200
+ O_EXCL = 0x400
+ O_APPEND = 0x08
+ O_NONBLOCK = 0x80
+ O_SYNC = 0x10
+ O_RDONLY = 0
+ O_RDWR = 2
+ O_WRONLY = 1
+ AT_FDCWD = 0xffd19553
struct Flock
l_type : Short
diff --git a/src/lib_c/x86_64-solaris/c/signal.cr b/src/lib_c/x86_64-solaris/c/signal.cr
index 9bde30946054..ee502aa621e4 100644
--- a/src/lib_c/x86_64-solaris/c/signal.cr
+++ b/src/lib_c/x86_64-solaris/c/signal.cr
@@ -90,6 +90,7 @@ lib LibC
fun kill(x0 : PidT, x1 : Int) : Int
fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int
+ fun pthread_kill(PthreadT, Int) : Int
fun signal(x0 : Int, x1 : Int -> Void) : Int -> Void
fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int
fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int
@@ -98,4 +99,5 @@ lib LibC
fun sigaddset(SigsetT*, Int) : Int
fun sigdelset(SigsetT*, Int) : Int
fun sigismember(SigsetT*, Int) : Int
+ fun sigsuspend(SigsetT*) : Int
end
diff --git a/src/lib_c/x86_64-solaris/c/sys/epoll.cr b/src/lib_c/x86_64-solaris/c/sys/epoll.cr
new file mode 100644
index 000000000000..4dc752f64652
--- /dev/null
+++ b/src/lib_c/x86_64-solaris/c/sys/epoll.cr
@@ -0,0 +1,33 @@
+lib LibC
+ EPOLLIN = 0x001_u32
+ EPOLLOUT = 0x004_u32
+ EPOLLERR = 0x008_u32
+ EPOLLHUP = 0x010_u32
+ EPOLLRDHUP = 0x2000_u32
+
+ EPOLLEXCLUSIVE = 1_u32 << 28
+ EPOLLET = 1_u32 << 31
+
+ EPOLL_CTL_ADD = 1
+ EPOLL_CTL_DEL = 2
+ EPOLL_CTL_MOD = 3
+
+ EPOLL_CLOEXEC = 0o2000000
+
+ union EpollDataT
+ ptr : Void*
+ fd : Int
+ u32 : UInt32
+ u64 : UInt64
+ end
+
+ @[Packed]
+ struct EpollEvent
+ events : UInt32
+ data : EpollDataT
+ end
+
+ fun epoll_create1(Int) : Int
+ fun epoll_ctl(Int, Int, Int, EpollEvent*) : Int
+ fun epoll_wait(Int, EpollEvent*, Int, Int) : Int
+end
diff --git a/src/lib_c/x86_64-solaris/c/sys/eventfd.cr b/src/lib_c/x86_64-solaris/c/sys/eventfd.cr
new file mode 100644
index 000000000000..12f24428a8f4
--- /dev/null
+++ b/src/lib_c/x86_64-solaris/c/sys/eventfd.cr
@@ -0,0 +1,5 @@
+lib LibC
+ EFD_CLOEXEC = 0o2000000
+
+ fun eventfd(count : UInt, flags : Int) : Int
+end
diff --git a/src/lib_c/x86_64-solaris/c/sys/resource.cr b/src/lib_c/x86_64-solaris/c/sys/resource.cr
index d52182f69bce..74f9b56f9971 100644
--- a/src/lib_c/x86_64-solaris/c/sys/resource.cr
+++ b/src/lib_c/x86_64-solaris/c/sys/resource.cr
@@ -22,4 +22,15 @@ lib LibC
RUSAGE_CHILDREN = -1
fun getrusage(who : Int, usage : RUsage*) : Int
+
+ alias RlimT = ULongLong
+
+ struct Rlimit
+ rlim_cur : RlimT
+ rlim_max : RlimT
+ end
+
+ RLIMIT_NOFILE = 5
+
+ fun getrlimit(resource : Int, rlim : Rlimit*) : Int
end
diff --git a/src/lib_c/x86_64-solaris/c/sys/stat.cr b/src/lib_c/x86_64-solaris/c/sys/stat.cr
index a5a1f3f1c5fc..c1c22c9b1872 100644
--- a/src/lib_c/x86_64-solaris/c/sys/stat.cr
+++ b/src/lib_c/x86_64-solaris/c/sys/stat.cr
@@ -56,4 +56,5 @@ lib LibC
fun mknod(x0 : Char*, x1 : ModeT, x2 : DevT) : Int
fun stat(x0 : Char*, x1 : Stat*) : Int
fun umask(x0 : ModeT) : ModeT
+ fun utimensat(x0 : Int, x1 : Char*, x2 : Timespec[2], x3 : Int) : Int
end
diff --git a/src/lib_c/x86_64-solaris/c/sys/time.cr b/src/lib_c/x86_64-solaris/c/sys/time.cr
index 711894a3da7e..5e2e5919812c 100644
--- a/src/lib_c/x86_64-solaris/c/sys/time.cr
+++ b/src/lib_c/x86_64-solaris/c/sys/time.cr
@@ -12,6 +12,5 @@ lib LibC
end
fun gettimeofday(x0 : Timeval*, x1 : Void*) : Int
- fun utimes(path : Char*, times : Timeval[2]) : Int
fun futimens(fd : Int, times : Timespec[2]) : Int
end
diff --git a/src/lib_c/x86_64-solaris/c/sys/timerfd.cr b/src/lib_c/x86_64-solaris/c/sys/timerfd.cr
new file mode 100644
index 000000000000..0632646b0e14
--- /dev/null
+++ b/src/lib_c/x86_64-solaris/c/sys/timerfd.cr
@@ -0,0 +1,10 @@
+require "../time"
+
+lib LibC
+ TFD_NONBLOCK = 0o0004000
+ TFD_CLOEXEC = 0o2000000
+ TFD_TIMER_ABSTIME = 1 << 0
+
+ fun timerfd_create(ClockidT, Int) : Int
+ fun timerfd_settime(Int, Int, Itimerspec*, Itimerspec*) : Int
+end
diff --git a/src/lib_c/x86_64-solaris/c/time.cr b/src/lib_c/x86_64-solaris/c/time.cr
index 531f8e373f4b..0aa8f3fce053 100644
--- a/src/lib_c/x86_64-solaris/c/time.cr
+++ b/src/lib_c/x86_64-solaris/c/time.cr
@@ -21,6 +21,11 @@ lib LibC
tv_nsec : Long
end
+ struct Itimerspec
+ it_interval : Timespec
+ it_value : Timespec
+ end
+
fun clock_gettime(x0 : ClockidT, x1 : Timespec*) : Int
fun clock_settime(x0 : ClockidT, x1 : Timespec*) : Int
fun gmtime_r(x0 : TimeT*, x1 : Tm*) : Tm*
diff --git a/src/lib_c/x86_64-windows-gnu b/src/lib_c/x86_64-windows-gnu
new file mode 120000
index 000000000000..072348f65d09
--- /dev/null
+++ b/src/lib_c/x86_64-windows-gnu
@@ -0,0 +1 @@
+x86_64-windows-msvc
\ No newline at end of file
diff --git a/src/lib_c/x86_64-windows-msvc/c/consoleapi.cr b/src/lib_c/x86_64-windows-msvc/c/consoleapi.cr
index fe2fbe381d03..7f7160a6448b 100644
--- a/src/lib_c/x86_64-windows-msvc/c/consoleapi.cr
+++ b/src/lib_c/x86_64-windows-msvc/c/consoleapi.cr
@@ -19,7 +19,7 @@ lib LibC
lpBuffer : Void*,
nNumberOfCharsToRead : DWORD,
lpNumberOfCharsRead : DWORD*,
- pInputControl : Void*
+ pInputControl : Void*,
) : BOOL
CTRL_C_EVENT = 0
diff --git a/src/lib_c/x86_64-windows-msvc/c/dbghelp.cr b/src/lib_c/x86_64-windows-msvc/c/dbghelp.cr
index af37cb0c7f0c..abd9e0b36104 100644
--- a/src/lib_c/x86_64-windows-msvc/c/dbghelp.cr
+++ b/src/lib_c/x86_64-windows-msvc/c/dbghelp.cr
@@ -122,6 +122,7 @@ lib LibC
end
IMAGE_FILE_MACHINE_AMD64 = DWORD.new!(0x8664)
+ IMAGE_FILE_MACHINE_ARM64 = DWORD.new!(0xAA64)
alias PREAD_PROCESS_MEMORY_ROUTINE64 = HANDLE, DWORD64, Void*, DWORD, DWORD* -> BOOL
alias PFUNCTION_TABLE_ACCESS_ROUTINE64 = HANDLE, DWORD64 -> Void*
@@ -131,6 +132,6 @@ lib LibC
fun StackWalk64(
machineType : DWORD, hProcess : HANDLE, hThread : HANDLE, stackFrame : STACKFRAME64*, contextRecord : Void*,
readMemoryRoutine : PREAD_PROCESS_MEMORY_ROUTINE64, functionTableAccessRoutine : PFUNCTION_TABLE_ACCESS_ROUTINE64,
- getModuleBaseRoutine : PGET_MODULE_BASE_ROUTINE64, translateAddress : PTRANSLATE_ADDRESS_ROUTINE64
+ getModuleBaseRoutine : PGET_MODULE_BASE_ROUTINE64, translateAddress : PTRANSLATE_ADDRESS_ROUTINE64,
) : BOOL
end
diff --git a/src/lib_c/x86_64-windows-msvc/c/fileapi.cr b/src/lib_c/x86_64-windows-msvc/c/fileapi.cr
index c17c0fb48a9a..94714b557cbe 100644
--- a/src/lib_c/x86_64-windows-msvc/c/fileapi.cr
+++ b/src/lib_c/x86_64-windows-msvc/c/fileapi.cr
@@ -107,14 +107,14 @@ lib LibC
dwReserved : DWORD,
nNumberOfBytesToLockLow : DWORD,
nNumberOfBytesToLockHigh : DWORD,
- lpOverlapped : OVERLAPPED*
+ lpOverlapped : OVERLAPPED*,
) : BOOL
fun UnlockFileEx(
hFile : HANDLE,
dwReserved : DWORD,
nNumberOfBytesToUnlockLow : DWORD,
nNumberOfBytesToUnlockHigh : DWORD,
- lpOverlapped : OVERLAPPED*
+ lpOverlapped : OVERLAPPED*,
) : BOOL
fun SetFileTime(hFile : HANDLE, lpCreationTime : FILETIME*,
lpLastAccessTime : FILETIME*, lpLastWriteTime : FILETIME*) : BOOL
diff --git a/src/lib_c/x86_64-windows-msvc/c/heapapi.cr b/src/lib_c/x86_64-windows-msvc/c/heapapi.cr
index 1738cf774cac..8db5152585bc 100644
--- a/src/lib_c/x86_64-windows-msvc/c/heapapi.cr
+++ b/src/lib_c/x86_64-windows-msvc/c/heapapi.cr
@@ -1,6 +1,8 @@
require "c/winnt"
lib LibC
+ HEAP_ZERO_MEMORY = 0x00000008
+
fun GetProcessHeap : HANDLE
fun HeapAlloc(hHeap : HANDLE, dwFlags : DWORD, dwBytes : SizeT) : Void*
fun HeapReAlloc(hHeap : HANDLE, dwFlags : DWORD, lpMem : Void*, dwBytes : SizeT) : Void*
diff --git a/src/lib_c/x86_64-windows-msvc/c/io.cr b/src/lib_c/x86_64-windows-msvc/c/io.cr
index 75da8c18e5b9..ccbaa15f2d1b 100644
--- a/src/lib_c/x86_64-windows-msvc/c/io.cr
+++ b/src/lib_c/x86_64-windows-msvc/c/io.cr
@@ -2,12 +2,13 @@ require "c/stdint"
lib LibC
fun _wexecvp(cmdname : WCHAR*, argv : WCHAR**) : IntPtrT
+ fun _open_osfhandle(osfhandle : HANDLE, flags : LibC::Int) : LibC::Int
+ fun _dup(fd : Int) : Int
+ fun _dup2(fd1 : Int, fd2 : Int) : Int
# unused
- fun _open_osfhandle(osfhandle : HANDLE, flags : LibC::Int) : LibC::Int
fun _get_osfhandle(fd : Int) : IntPtrT
fun _close(fd : Int) : Int
- fun _dup2(fd1 : Int, fd2 : Int) : Int
fun _isatty(fd : Int) : Int
fun _write(fd : Int, buffer : UInt8*, count : UInt) : Int
fun _read(fd : Int, buffer : UInt8*, count : UInt) : Int
diff --git a/src/lib_c/x86_64-windows-msvc/c/ioapiset.cr b/src/lib_c/x86_64-windows-msvc/c/ioapiset.cr
index 1c94b66db4c8..d6632e329f6b 100644
--- a/src/lib_c/x86_64-windows-msvc/c/ioapiset.cr
+++ b/src/lib_c/x86_64-windows-msvc/c/ioapiset.cr
@@ -3,14 +3,14 @@ lib LibC
hFile : HANDLE,
lpOverlapped : OVERLAPPED*,
lpNumberOfBytesTransferred : DWORD*,
- bWait : BOOL
+ bWait : BOOL,
) : BOOL
fun CreateIoCompletionPort(
fileHandle : HANDLE,
existingCompletionPort : HANDLE,
completionKey : ULong*,
- numberOfConcurrentThreads : DWORD
+ numberOfConcurrentThreads : DWORD,
) : HANDLE
fun GetQueuedCompletionStatusEx(
@@ -19,14 +19,22 @@ lib LibC
ulCount : ULong,
ulNumEntriesRemoved : ULong*,
dwMilliseconds : DWORD,
- fAlertable : BOOL
+ fAlertable : BOOL,
) : BOOL
+
+ fun PostQueuedCompletionStatus(
+ completionPort : HANDLE,
+ dwNumberOfBytesTransferred : DWORD,
+ dwCompletionKey : ULONG_PTR,
+ lpOverlapped : OVERLAPPED*,
+ ) : BOOL
+
fun CancelIoEx(
hFile : HANDLE,
- lpOverlapped : OVERLAPPED*
+ lpOverlapped : OVERLAPPED*,
) : BOOL
fun CancelIo(
- hFile : HANDLE
+ hFile : HANDLE,
) : BOOL
fun DeviceIoControl(
@@ -37,6 +45,6 @@ lib LibC
lpOutBuffer : Void*,
nOutBufferSize : DWORD,
lpBytesReturned : DWORD*,
- lpOverlapped : OVERLAPPED*
+ lpOverlapped : OVERLAPPED*,
) : BOOL
end
diff --git a/src/lib_c/x86_64-windows-msvc/c/knownfolders.cr b/src/lib_c/x86_64-windows-msvc/c/knownfolders.cr
index 04c16573cc76..6ce1831cb1e5 100644
--- a/src/lib_c/x86_64-windows-msvc/c/knownfolders.cr
+++ b/src/lib_c/x86_64-windows-msvc/c/knownfolders.cr
@@ -2,4 +2,5 @@ require "c/guiddef"
lib LibC
FOLDERID_Profile = GUID.new(0x5e6c858f, 0x0e22, 0x4760, UInt8.static_array(0x9a, 0xfe, 0xea, 0x33, 0x17, 0xb6, 0x71, 0x73))
+ FOLDERID_System = GUID.new(0x1ac14e77, 0x02e7, 0x4e5d, UInt8.static_array(0xb7, 0x44, 0x2e, 0xb1, 0xae, 0x51, 0x98, 0xb7))
end
diff --git a/src/lib_c/x86_64-windows-msvc/c/libloaderapi.cr b/src/lib_c/x86_64-windows-msvc/c/libloaderapi.cr
index 37a95f3fa089..67b114bfc80f 100644
--- a/src/lib_c/x86_64-windows-msvc/c/libloaderapi.cr
+++ b/src/lib_c/x86_64-windows-msvc/c/libloaderapi.cr
@@ -1,6 +1,6 @@
require "c/winnt"
-@[Link("Kernel32")]
+@[Link("kernel32")]
lib LibC
alias FARPROC = Void*
@@ -9,6 +9,9 @@ lib LibC
fun LoadLibraryExW(lpLibFileName : LPWSTR, hFile : HANDLE, dwFlags : DWORD) : HMODULE
fun FreeLibrary(hLibModule : HMODULE) : BOOL
+ GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT = 0x00000002
+ GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS = 0x00000004
+
fun GetModuleHandleExW(dwFlags : DWORD, lpModuleName : LPWSTR, phModule : HMODULE*) : BOOL
fun GetProcAddress(hModule : HMODULE, lpProcName : LPSTR) : FARPROC
diff --git a/src/lib_c/x86_64-windows-msvc/c/lm.cr b/src/lib_c/x86_64-windows-msvc/c/lm.cr
new file mode 100644
index 000000000000..72f5affc9b55
--- /dev/null
+++ b/src/lib_c/x86_64-windows-msvc/c/lm.cr
@@ -0,0 +1,59 @@
+require "c/winnt"
+
+@[Link("netapi32")]
+lib LibC
+ alias NET_API_STATUS = DWORD
+
+ NERR_Success = NET_API_STATUS.new!(0)
+
+ enum NETSETUP_JOIN_STATUS
+ NetSetupUnknownStatus = 0
+ NetSetupUnjoined
+ NetSetupWorkgroupName
+ NetSetupDomainName
+ end
+
+ fun NetGetJoinInformation(lpServer : LPWSTR, lpNameBuffer : LPWSTR*, bufferType : NETSETUP_JOIN_STATUS*) : NET_API_STATUS
+
+ struct USER_INFO_4
+ usri4_name : LPWSTR
+ usri4_password : LPWSTR
+ usri4_password_age : DWORD
+ usri4_priv : DWORD
+ usri4_home_dir : LPWSTR
+ usri4_comment : LPWSTR
+ usri4_flags : DWORD
+ usri4_script_path : LPWSTR
+ usri4_auth_flags : DWORD
+ usri4_full_name : LPWSTR
+ usri4_usr_comment : LPWSTR
+ usri4_parms : LPWSTR
+ usri4_workstations : LPWSTR
+ usri4_last_logon : DWORD
+ usri4_last_logoff : DWORD
+ usri4_acct_expires : DWORD
+ usri4_max_storage : DWORD
+ usri4_units_per_week : DWORD
+ usri4_logon_hours : BYTE*
+ usri4_bad_pw_count : DWORD
+ usri4_num_logons : DWORD
+ usri4_logon_server : LPWSTR
+ usri4_country_code : DWORD
+ usri4_code_page : DWORD
+ usri4_user_sid : SID*
+ usri4_primary_group_id : DWORD
+ usri4_profile : LPWSTR
+ usri4_home_dir_drive : LPWSTR
+ usri4_password_expired : DWORD
+ end
+
+ struct USER_INFO_10
+ usri10_name : LPWSTR
+ usri10_comment : LPWSTR
+ usri10_usr_comment : LPWSTR
+ usri10_full_name : LPWSTR
+ end
+
+ fun NetUserGetInfo(servername : LPWSTR, username : LPWSTR, level : DWORD, bufptr : BYTE**) : NET_API_STATUS
+ fun NetApiBufferFree(buffer : Void*) : NET_API_STATUS
+end
diff --git a/src/lib_c/x86_64-windows-msvc/c/memoryapi.cr b/src/lib_c/x86_64-windows-msvc/c/memoryapi.cr
index 7b0103713d8a..0ea28b8262f6 100644
--- a/src/lib_c/x86_64-windows-msvc/c/memoryapi.cr
+++ b/src/lib_c/x86_64-windows-msvc/c/memoryapi.cr
@@ -11,5 +11,5 @@ lib LibC
fun VirtualFree(lpAddress : Void*, dwSize : SizeT, dwFreeType : DWORD) : BOOL
fun VirtualProtect(lpAddress : Void*, dwSize : SizeT, flNewProtect : DWORD, lpfOldProtect : DWORD*) : BOOL
- fun VirtualQuery(lpAddress : Void*, lpBuffer : MEMORY_BASIC_INFORMATION*, dwLength : SizeT)
+ fun VirtualQuery(lpAddress : Void*, lpBuffer : MEMORY_BASIC_INFORMATION*, dwLength : SizeT) : SizeT
end
diff --git a/src/lib_c/x86_64-windows-msvc/c/ntdef.cr b/src/lib_c/x86_64-windows-msvc/c/ntdef.cr
new file mode 100644
index 000000000000..a9a07a07b27e
--- /dev/null
+++ b/src/lib_c/x86_64-windows-msvc/c/ntdef.cr
@@ -0,0 +1,16 @@
+lib LibC
+ struct UNICODE_STRING
+ length : USHORT
+ maximumLength : USHORT
+ buffer : LPWSTR
+ end
+
+ struct OBJECT_ATTRIBUTES
+ length : ULONG
+ rootDirectory : HANDLE
+ objectName : UNICODE_STRING*
+ attributes : ULONG
+ securityDescriptor : Void*
+ securityQualityOfService : Void*
+ end
+end
diff --git a/src/lib_c/x86_64-windows-msvc/c/ntdll.cr b/src/lib_c/x86_64-windows-msvc/c/ntdll.cr
new file mode 100644
index 000000000000..8d2653b8bb31
--- /dev/null
+++ b/src/lib_c/x86_64-windows-msvc/c/ntdll.cr
@@ -0,0 +1,36 @@
+require "c/ntdef"
+require "c/winnt"
+
+@[Link("ntdll")]
+lib LibNTDLL
+ alias NTSTATUS = LibC::ULONG
+ alias ACCESS_MASK = LibC::DWORD
+
+ GENERIC_ALL = 0x10000000_u32
+
+ alias NtCreateWaitCompletionPacketProc = Proc(LibC::HANDLE*, ACCESS_MASK, LibC::OBJECT_ATTRIBUTES*, NTSTATUS)
+ alias NtAssociateWaitCompletionPacketProc = Proc(LibC::HANDLE, LibC::HANDLE, LibC::HANDLE, Void*, Void*, NTSTATUS, LibC::ULONG*, LibC::BOOLEAN*, NTSTATUS)
+ alias NtCancelWaitCompletionPacketProc = Proc(LibC::HANDLE, LibC::BOOLEAN, NTSTATUS)
+
+ fun NtCreateWaitCompletionPacket(
+ waitCompletionPacketHandle : LibC::HANDLE*,
+ desiredAccess : ACCESS_MASK,
+ objectAttributes : LibC::OBJECT_ATTRIBUTES*,
+ ) : NTSTATUS
+
+ fun NtAssociateWaitCompletionPacket(
+ waitCompletionPacketHandle : LibC::HANDLE,
+ ioCompletionHandle : LibC::HANDLE,
+ targetObjectHandle : LibC::HANDLE,
+ keyContext : Void*,
+ apcContext : Void*,
+ ioStatus : NTSTATUS,
+ ioStatusInformation : LibC::ULONG*,
+ alreadySignaled : LibC::BOOLEAN*,
+ ) : NTSTATUS
+
+ fun NtCancelWaitCompletionPacket(
+ waitCompletionPacketHandle : LibC::HANDLE,
+ removeSignaledPacket : LibC::BOOLEAN,
+ ) : NTSTATUS
+end
diff --git a/src/lib_c/x86_64-windows-msvc/c/ntstatus.cr b/src/lib_c/x86_64-windows-msvc/c/ntstatus.cr
index 2a013036adb4..0596c641bcc3 100644
--- a/src/lib_c/x86_64-windows-msvc/c/ntstatus.cr
+++ b/src/lib_c/x86_64-windows-msvc/c/ntstatus.cr
@@ -1,6 +1,7 @@
require "lib_c"
lib LibC
+ STATUS_SUCCESS = 0x00000000_u32
STATUS_FATAL_APP_EXIT = 0x40000015_u32
STATUS_DATATYPE_MISALIGNMENT = 0x80000002_u32
STATUS_BREAKPOINT = 0x80000003_u32
@@ -13,5 +14,6 @@ lib LibC
STATUS_FLOAT_UNDERFLOW = 0xC0000093_u32
STATUS_PRIVILEGED_INSTRUCTION = 0xC0000096_u32
STATUS_STACK_OVERFLOW = 0xC00000FD_u32
+ STATUS_CANCELLED = 0xC0000120_u32
STATUS_CONTROL_C_EXIT = 0xC000013A_u32
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
index d1e13eced324..22001cfc1632 100644
--- a/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr
+++ b/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr
@@ -59,5 +59,15 @@ lib LibC
fun SwitchToThread : BOOL
fun QueueUserAPC(pfnAPC : PAPCFUNC, hThread : HANDLE, dwData : ULONG_PTR) : DWORD
+ fun GetThreadContext(hThread : HANDLE, lpContext : CONTEXT*) : DWORD
+ fun ResumeThread(hThread : HANDLE) : DWORD
+ fun SuspendThread(hThread : HANDLE) : DWORD
+
+ TLS_OUT_OF_INDEXES = 0xFFFFFFFF_u32
+
+ fun TlsAlloc : DWORD
+ fun TlsGetValue(dwTlsIndex : DWORD) : Void*
+ fun TlsSetValue(dwTlsIndex : DWORD, lpTlsValue : Void*) : BOOL
+
PROCESS_QUERY_INFORMATION = 0x0400
end
diff --git a/src/lib_c/x86_64-windows-msvc/c/sddl.cr b/src/lib_c/x86_64-windows-msvc/c/sddl.cr
new file mode 100644
index 000000000000..64e1fa8b25c1
--- /dev/null
+++ b/src/lib_c/x86_64-windows-msvc/c/sddl.cr
@@ -0,0 +1,6 @@
+require "c/winnt"
+
+lib LibC
+ fun ConvertSidToStringSidW(sid : SID*, stringSid : LPWSTR*) : BOOL
+ fun ConvertStringSidToSidW(stringSid : LPWSTR, sid : SID**) : BOOL
+end
diff --git a/src/lib_c/x86_64-windows-msvc/c/security.cr b/src/lib_c/x86_64-windows-msvc/c/security.cr
new file mode 100644
index 000000000000..5a904c51df40
--- /dev/null
+++ b/src/lib_c/x86_64-windows-msvc/c/security.cr
@@ -0,0 +1,21 @@
+require "c/winnt"
+
+@[Link("secur32")]
+lib LibC
+ enum EXTENDED_NAME_FORMAT
+ NameUnknown = 0
+ NameFullyQualifiedDN = 1
+ NameSamCompatible = 2
+ NameDisplay = 3
+ NameUniqueId = 6
+ NameCanonical = 7
+ NameUserPrincipal = 8
+ NameCanonicalEx = 9
+ NameServicePrincipal = 10
+ NameDnsDomain = 12
+ NameGivenName = 13
+ NameSurname = 14
+ end
+
+ fun TranslateNameW(lpAccountName : LPWSTR, accountNameFormat : EXTENDED_NAME_FORMAT, desiredNameFormat : EXTENDED_NAME_FORMAT, lpTranslatedName : LPWSTR, nSize : ULong*) : BOOLEAN
+end
diff --git a/src/lib_c/x86_64-windows-msvc/c/stdio.cr b/src/lib_c/x86_64-windows-msvc/c/stdio.cr
index f23bba8503f6..ddfa97235d87 100644
--- a/src/lib_c/x86_64-windows-msvc/c/stdio.cr
+++ b/src/lib_c/x86_64-windows-msvc/c/stdio.cr
@@ -1,6 +1,8 @@
require "./stddef"
-@[Link("legacy_stdio_definitions")]
+{% if flag?(:msvc) %}
+ @[Link("legacy_stdio_definitions")]
+{% end %}
lib LibC
# unused
fun printf(format : Char*, ...) : Int
diff --git a/src/lib_c/x86_64-windows-msvc/c/stdlib.cr b/src/lib_c/x86_64-windows-msvc/c/stdlib.cr
index 63c38003fd6a..140e49a229a7 100644
--- a/src/lib_c/x86_64-windows-msvc/c/stdlib.cr
+++ b/src/lib_c/x86_64-windows-msvc/c/stdlib.cr
@@ -11,13 +11,13 @@ lib LibC
fun free(ptr : Void*) : Void
fun malloc(size : SizeT) : Void*
fun realloc(ptr : Void*, size : SizeT) : Void*
- fun strtof(nptr : Char*, endptr : Char**) : Float
- fun strtod(nptr : Char*, endptr : Char**) : Double
alias InvalidParameterHandler = WCHAR*, WCHAR*, WCHAR*, UInt, UIntPtrT ->
fun _set_invalid_parameter_handler(pNew : InvalidParameterHandler) : InvalidParameterHandler
# unused
+ fun strtof(nptr : Char*, endptr : Char**) : Float
+ fun strtod(nptr : Char*, endptr : Char**) : Double
fun atof(nptr : Char*) : Double
fun div(numer : Int, denom : Int) : DivT
fun putenv(string : Char*) : Int
diff --git a/src/lib_c/x86_64-windows-msvc/c/stringapiset.cr b/src/lib_c/x86_64-windows-msvc/c/stringapiset.cr
index f60e80a59328..c22bd1dfab31 100644
--- a/src/lib_c/x86_64-windows-msvc/c/stringapiset.cr
+++ b/src/lib_c/x86_64-windows-msvc/c/stringapiset.cr
@@ -8,13 +8,13 @@ lib LibC
fun WideCharToMultiByte(
codePage : UInt, dwFlags : DWORD, lpWideCharStr : LPWSTR,
cchWideChar : Int, lpMultiByteStr : LPSTR, cbMultiByte : Int,
- lpDefaultChar : CHAR*, lpUsedDefaultChar : BOOL*
+ lpDefaultChar : CHAR*, lpUsedDefaultChar : BOOL*,
) : Int
# this was for the now removed delay-load helper, all other code should use
# `String#to_utf16` instead
fun MultiByteToWideChar(
codePage : UInt, dwFlags : DWORD, lpMultiByteStr : LPSTR,
- cbMultiByte : Int, lpWideCharStr : LPWSTR, cchWideChar : Int
+ cbMultiByte : Int, lpWideCharStr : LPWSTR, cchWideChar : Int,
) : Int
end
diff --git a/src/lib_c/x86_64-windows-msvc/c/synchapi.cr b/src/lib_c/x86_64-windows-msvc/c/synchapi.cr
index e101b7f6284b..e85f0af1eb8f 100644
--- a/src/lib_c/x86_64-windows-msvc/c/synchapi.cr
+++ b/src/lib_c/x86_64-windows-msvc/c/synchapi.cr
@@ -32,4 +32,11 @@ lib LibC
fun Sleep(dwMilliseconds : DWORD)
fun WaitForSingleObject(hHandle : HANDLE, dwMilliseconds : DWORD) : DWORD
+
+ alias PTIMERAPCROUTINE = (Void*, DWORD, DWORD) ->
+ CREATE_WAITABLE_TIMER_HIGH_RESOLUTION = 0x00000002_u32
+
+ fun CreateWaitableTimerExW(lpTimerAttributes : SECURITY_ATTRIBUTES*, lpTimerName : LPWSTR, dwFlags : DWORD, dwDesiredAccess : DWORD) : HANDLE
+ fun SetWaitableTimer(hTimer : HANDLE, lpDueTime : LARGE_INTEGER*, lPeriod : LONG, pfnCompletionRoutine : PTIMERAPCROUTINE*, lpArgToCompletionRoutine : Void*, fResume : BOOL) : BOOL
+ fun CancelWaitableTimer(hTimer : HANDLE) : BOOL
end
diff --git a/src/lib_c/x86_64-windows-msvc/c/userenv.cr b/src/lib_c/x86_64-windows-msvc/c/userenv.cr
new file mode 100644
index 000000000000..bb32977d79f7
--- /dev/null
+++ b/src/lib_c/x86_64-windows-msvc/c/userenv.cr
@@ -0,0 +1,6 @@
+require "c/winnt"
+
+@[Link("userenv")]
+lib LibC
+ fun GetProfilesDirectoryW(lpProfileDir : LPWSTR, lpcchSize : DWORD*) : BOOL
+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 0a736a4fa89c..7b7a8735ddf2 100644
--- a/src/lib_c/x86_64-windows-msvc/c/winbase.cr
+++ b/src/lib_c/x86_64-windows-msvc/c/winbase.cr
@@ -4,6 +4,10 @@ require "c/int_safe"
require "c/minwinbase"
lib LibC
+ alias HLOCAL = Void*
+
+ fun LocalFree(hMem : HLOCAL)
+
FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100_u32
FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200_u32
FORMAT_MESSAGE_FROM_STRING = 0x00000400_u32
@@ -69,4 +73,7 @@ lib LibC
end
fun GetFileInformationByHandleEx(hFile : HANDLE, fileInformationClass : FILE_INFO_BY_HANDLE_CLASS, lpFileInformation : Void*, dwBufferSize : DWORD) : BOOL
+
+ fun LookupAccountNameW(lpSystemName : LPWSTR, lpAccountName : LPWSTR, sid : SID*, cbSid : DWORD*, referencedDomainName : LPWSTR, cchReferencedDomainName : DWORD*, peUse : SID_NAME_USE*) : BOOL
+ fun LookupAccountSidW(lpSystemName : LPWSTR, sid : SID*, name : LPWSTR, cchName : DWORD*, referencedDomainName : LPWSTR, cchReferencedDomainName : DWORD*, peUse : SID_NAME_USE*) : 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 e1f133dcae48..1bee1cb173ab 100644
--- a/src/lib_c/x86_64-windows-msvc/c/winnt.cr
+++ b/src/lib_c/x86_64-windows-msvc/c/winnt.cr
@@ -3,6 +3,8 @@ require "c/int_safe"
lib LibC
alias BOOLEAN = BYTE
alias LONG = Int32
+ alias ULONG = UInt32
+ alias USHORT = UInt16
alias LARGE_INTEGER = Int64
alias CHAR = UChar
@@ -95,6 +97,31 @@ lib LibC
WRITE = 0x20006
end
+ struct SID_IDENTIFIER_AUTHORITY
+ value : BYTE[6]
+ end
+
+ struct SID
+ revision : BYTE
+ subAuthorityCount : BYTE
+ identifierAuthority : SID_IDENTIFIER_AUTHORITY
+ subAuthority : DWORD[1]
+ end
+
+ enum SID_NAME_USE
+ SidTypeUser = 1
+ SidTypeGroup
+ SidTypeDomain
+ SidTypeAlias
+ SidTypeWellKnownGroup
+ SidTypeDeletedAccount
+ SidTypeInvalid
+ SidTypeUnknown
+ SidTypeComputer
+ SidTypeLabel
+ SidTypeLogonSession
+ end
+
enum JOBOBJECTINFOCLASS
AssociateCompletionPortInformation = 7
ExtendedLimitInformation = 9
@@ -140,54 +167,84 @@ lib LibC
JOB_OBJECT_MSG_EXIT_PROCESS = 7
JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS = 8
- struct CONTEXT
- p1Home : DWORD64
- p2Home : DWORD64
- p3Home : DWORD64
- p4Home : DWORD64
- p5Home : DWORD64
- p6Home : DWORD64
- contextFlags : DWORD
- mxCsr : DWORD
- segCs : WORD
- segDs : WORD
- segEs : WORD
- segFs : WORD
- segGs : WORD
- segSs : WORD
- eFlags : DWORD
- dr0 : DWORD64
- dr1 : DWORD64
- dr2 : DWORD64
- dr3 : DWORD64
- dr6 : DWORD64
- dr7 : DWORD64
- rax : DWORD64
- rcx : DWORD64
- rdx : DWORD64
- rbx : DWORD64
- rsp : DWORD64
- rbp : DWORD64
- rsi : DWORD64
- rdi : DWORD64
- r8 : DWORD64
- r9 : DWORD64
- r10 : DWORD64
- r11 : DWORD64
- r12 : DWORD64
- r13 : DWORD64
- r14 : DWORD64
- r15 : DWORD64
- rip : DWORD64
- fltSave : UInt8[512] # DUMMYUNIONNAME
- vectorRegister : UInt8[16][26] # M128A[26]
- vectorControl : DWORD64
- debugControl : DWORD64
- lastBranchToRip : DWORD64
- lastBranchFromRip : DWORD64
- lastExceptionToRip : DWORD64
- lastExceptionFromRip : DWORD64
- end
+ {% if flag?(:x86_64) %}
+ struct CONTEXT
+ p1Home : DWORD64
+ p2Home : DWORD64
+ p3Home : DWORD64
+ p4Home : DWORD64
+ p5Home : DWORD64
+ p6Home : DWORD64
+ contextFlags : DWORD
+ mxCsr : DWORD
+ segCs : WORD
+ segDs : WORD
+ segEs : WORD
+ segFs : WORD
+ segGs : WORD
+ segSs : WORD
+ eFlags : DWORD
+ dr0 : DWORD64
+ dr1 : DWORD64
+ dr2 : DWORD64
+ dr3 : DWORD64
+ dr6 : DWORD64
+ dr7 : DWORD64
+ rax : DWORD64
+ rcx : DWORD64
+ rdx : DWORD64
+ rbx : DWORD64
+ rsp : DWORD64
+ rbp : DWORD64
+ rsi : DWORD64
+ rdi : DWORD64
+ r8 : DWORD64
+ r9 : DWORD64
+ r10 : DWORD64
+ r11 : DWORD64
+ r12 : DWORD64
+ r13 : DWORD64
+ r14 : DWORD64
+ r15 : DWORD64
+ rip : DWORD64
+ fltSave : UInt8[512] # DUMMYUNIONNAME
+ vectorRegister : UInt8[16][26] # M128A[26]
+ vectorControl : DWORD64
+ debugControl : DWORD64
+ lastBranchToRip : DWORD64
+ lastBranchFromRip : DWORD64
+ lastExceptionToRip : DWORD64
+ lastExceptionFromRip : DWORD64
+ end
+ {% elsif flag?(:aarch64) %}
+ struct ARM64_NT_NEON128_DUMMYSTRUCTNAME
+ low : ULongLong
+ high : LongLong
+ end
+
+ union ARM64_NT_NEON128
+ dummystructname : ARM64_NT_NEON128_DUMMYSTRUCTNAME
+ d : Double[2]
+ s : Float[4]
+ h : WORD[8]
+ b : BYTE[16]
+ end
+
+ struct CONTEXT
+ contextFlags : DWORD
+ cpsr : DWORD
+ x : DWORD64[31] # x29 = fp, x30 = lr
+ sp : DWORD64
+ pc : DWORD64
+ v : ARM64_NT_NEON128[32]
+ fpcr : DWORD
+ fpsr : DWORD
+ bcr : DWORD[8]
+ bvr : DWORD64[8]
+ wcr : DWORD[8]
+ wvr : DWORD64[8]
+ end
+ {% end %}
{% if flag?(:x86_64) %}
CONTEXT_AMD64 = DWORD.new!(0x00100000)
@@ -211,6 +268,14 @@ lib LibC
CONTEXT_EXTENDED_REGISTERS = CONTEXT_i386 | 0x00000020
CONTEXT_FULL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS
+ {% elsif flag?(:aarch64) %}
+ CONTEXT_ARM64 = DWORD.new!(0x00400000)
+
+ CONTEXT_ARM64_CONTROL = CONTEXT_ARM64 | 0x1
+ CONTEXT_ARM64_INTEGER = CONTEXT_ARM64 | 0x2
+ CONTEXT_ARM64_FLOATING_POINT = CONTEXT_ARM64 | 0x4
+
+ CONTEXT_FULL = CONTEXT_ARM64_CONTROL | CONTEXT_ARM64_INTEGER | CONTEXT_ARM64_FLOATING_POINT
{% end %}
fun RtlCaptureContext(contextRecord : CONTEXT*)
@@ -329,11 +394,67 @@ lib LibC
optionalHeader : IMAGE_OPTIONAL_HEADER64
end
+ IMAGE_DIRECTORY_ENTRY_EXPORT = 0
+ IMAGE_DIRECTORY_ENTRY_IMPORT = 1
+ IMAGE_DIRECTORY_ENTRY_IAT = 12
+
+ IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040
+
+ struct IMAGE_SECTION_HEADER
+ name : BYTE[8]
+ virtualSize : DWORD
+ virtualAddress : DWORD
+ sizeOfRawData : DWORD
+ pointerToRawData : DWORD
+ pointerToRelocations : DWORD
+ pointerToLinenumbers : DWORD
+ numberOfRelocations : WORD
+ numberOfLinenumbers : WORD
+ characteristics : DWORD
+ end
+
+ struct IMAGE_EXPORT_DIRECTORY
+ characteristics : DWORD
+ timeDateStamp : DWORD
+ majorVersion : WORD
+ minorVersion : WORD
+ name : DWORD
+ base : DWORD
+ numberOfFunctions : DWORD
+ numberOfNames : DWORD
+ addressOfFunctions : DWORD
+ addressOfNames : DWORD
+ addressOfNameOrdinals : DWORD
+ end
+
struct IMAGE_IMPORT_BY_NAME
hint : WORD
name : CHAR[1]
end
+ struct IMAGE_SYMBOL_n_name
+ short : DWORD
+ long : DWORD
+ end
+
+ union IMAGE_SYMBOL_n
+ shortName : BYTE[8]
+ name : IMAGE_SYMBOL_n_name
+ end
+
+ IMAGE_SYM_CLASS_EXTERNAL = 2
+ IMAGE_SYM_CLASS_STATIC = 3
+
+ @[Packed]
+ struct IMAGE_SYMBOL
+ n : IMAGE_SYMBOL_n
+ value : DWORD
+ sectionNumber : Short
+ type : WORD
+ storageClass : BYTE
+ numberOfAuxSymbols : BYTE
+ end
+
union IMAGE_THUNK_DATA64_u1
forwarderString : ULongLong
function : ULongLong
@@ -350,4 +471,7 @@ lib LibC
alias IMAGE_NT_HEADERS = IMAGE_NT_HEADERS64
alias IMAGE_THUNK_DATA = IMAGE_THUNK_DATA64
IMAGE_ORDINAL_FLAG = IMAGE_ORDINAL_FLAG64
+
+ TIMER_QUERY_STATE = 0x0001
+ TIMER_MODIFY_STATE = 0x0002
end
diff --git a/src/lib_c/x86_64-windows-msvc/c/winsock2.cr b/src/lib_c/x86_64-windows-msvc/c/winsock2.cr
index 223c2366b072..9a2dde0ca146 100644
--- a/src/lib_c/x86_64-windows-msvc/c/winsock2.cr
+++ b/src/lib_c/x86_64-windows-msvc/c/winsock2.cr
@@ -3,7 +3,7 @@ require "./basetsd"
require "./guiddef"
require "./winbase"
-@[Link("WS2_32")]
+@[Link("ws2_32")]
lib LibC
alias SOCKET = UINT_PTR
@@ -20,6 +20,8 @@ lib LibC
lpVendorInfo : Char*
end
+ NS_DNS = 12_u32
+
INVALID_SOCKET = ~SOCKET.new(0)
SOCKET_ERROR = -1
@@ -111,6 +113,11 @@ lib LibC
alias WSAOVERLAPPED_COMPLETION_ROUTINE = Proc(DWORD, DWORD, WSAOVERLAPPED*, DWORD, Void)
+ struct Timeval
+ tv_sec : Long
+ tv_usec : Long
+ end
+
struct Linger
l_onoff : UShort
l_linger : UShort
@@ -147,7 +154,7 @@ lib LibC
addr : Sockaddr*,
addrlen : Int*,
lpfnCondition : LPCONDITIONPROC,
- dwCallbackData : DWORD*
+ dwCallbackData : DWORD*,
) : SOCKET
fun WSAConnect(
@@ -157,21 +164,21 @@ lib LibC
lpCallerData : WSABUF*,
lpCalleeData : WSABUF*,
lpSQOS : LPQOS,
- lpGQOS : LPQOS
+ lpGQOS : LPQOS,
)
fun WSACreateEvent : WSAEVENT
fun WSAEventSelect(
s : SOCKET,
hEventObject : WSAEVENT,
- lNetworkEvents : Long
+ lNetworkEvents : Long,
) : Int
fun WSAGetOverlappedResult(
s : SOCKET,
lpOverlapped : WSAOVERLAPPED*,
lpcbTransfer : DWORD*,
fWait : BOOL,
- lpdwFlags : DWORD*
+ lpdwFlags : DWORD*,
) : BOOL
fun WSAIoctl(
s : SOCKET,
@@ -182,7 +189,7 @@ lib LibC
cbOutBuffer : DWORD,
lpcbBytesReturned : DWORD*,
lpOverlapped : WSAOVERLAPPED*,
- lpCompletionRoutine : WSAOVERLAPPED_COMPLETION_ROUTINE*
+ lpCompletionRoutine : WSAOVERLAPPED_COMPLETION_ROUTINE*,
) : Int
fun WSARecv(
s : SOCKET,
@@ -191,7 +198,7 @@ lib LibC
lpNumberOfBytesRecvd : DWORD*,
lpFlags : DWORD*,
lpOverlapped : WSAOVERLAPPED*,
- lpCompletionRoutine : WSAOVERLAPPED_COMPLETION_ROUTINE*
+ lpCompletionRoutine : WSAOVERLAPPED_COMPLETION_ROUTINE*,
) : Int
fun WSARecvFrom(
s : SOCKET,
@@ -202,10 +209,10 @@ lib LibC
lpFrom : Sockaddr*,
lpFromlen : Int*,
lpOverlapped : WSAOVERLAPPED*,
- lpCompletionRoutine : WSAOVERLAPPED_COMPLETION_ROUTINE*
+ lpCompletionRoutine : WSAOVERLAPPED_COMPLETION_ROUTINE*,
) : Int
fun WSAResetEvent(
- hEvent : WSAEVENT
+ hEvent : WSAEVENT,
) : BOOL
fun WSASend(
s : SOCKET,
@@ -214,7 +221,7 @@ lib LibC
lpNumberOfBytesSent : DWORD*,
dwFlags : DWORD,
lpOverlapped : WSAOVERLAPPED*,
- lpCompletionRoutine : WSAOVERLAPPED_COMPLETION_ROUTINE*
+ lpCompletionRoutine : WSAOVERLAPPED_COMPLETION_ROUTINE*,
) : Int
fun WSASendTo(
s : SOCKET,
@@ -225,7 +232,7 @@ lib LibC
lpTo : Sockaddr*,
iTolen : Int,
lpOverlapped : WSAOVERLAPPED*,
- lpCompletionRoutine : WSAOVERLAPPED_COMPLETION_ROUTINE*
+ lpCompletionRoutine : WSAOVERLAPPED_COMPLETION_ROUTINE*,
) : Int
fun WSASocketW(
af : Int,
@@ -233,13 +240,13 @@ lib LibC
protocol : Int,
lpProtocolInfo : WSAPROTOCOL_INFOW*,
g : GROUP,
- dwFlags : DWORD
+ dwFlags : DWORD,
) : SOCKET
fun WSAWaitForMultipleEvents(
cEvents : DWORD,
lphEvents : WSAEVENT*,
fWaitAll : BOOL,
dwTimeout : DWORD,
- fAlertable : BOOL
+ fAlertable : BOOL,
) : DWORD
end
diff --git a/src/lib_c/x86_64-windows-msvc/c/winternl.cr b/src/lib_c/x86_64-windows-msvc/c/winternl.cr
new file mode 100644
index 000000000000..7046370a1035
--- /dev/null
+++ b/src/lib_c/x86_64-windows-msvc/c/winternl.cr
@@ -0,0 +1,4 @@
+@[Link("ntdll")]
+lib LibNTDLL
+ fun RtlNtStatusToDosError(status : LibC::ULONG) : LibC::ULONG
+end
diff --git a/src/lib_c/x86_64-windows-msvc/c/ws2def.cr b/src/lib_c/x86_64-windows-msvc/c/ws2def.cr
index 9fc19857f4a3..41e0a1a408eb 100644
--- a/src/lib_c/x86_64-windows-msvc/c/ws2def.cr
+++ b/src/lib_c/x86_64-windows-msvc/c/ws2def.cr
@@ -208,4 +208,18 @@ lib LibC
ai_addr : Sockaddr*
ai_next : Addrinfo*
end
+
+ struct ADDRINFOEXW
+ ai_flags : Int
+ ai_family : Int
+ ai_socktype : Int
+ ai_protocol : Int
+ ai_addrlen : SizeT
+ ai_canonname : LPWSTR
+ ai_addr : Sockaddr*
+ ai_blob : Void*
+ ai_bloblen : SizeT
+ ai_provider : GUID*
+ ai_next : ADDRINFOEXW*
+ end
end
diff --git a/src/lib_c/x86_64-windows-msvc/c/ws2tcpip.cr b/src/lib_c/x86_64-windows-msvc/c/ws2tcpip.cr
index 338063ccf6f6..3b3f61ba7fdb 100644
--- a/src/lib_c/x86_64-windows-msvc/c/ws2tcpip.cr
+++ b/src/lib_c/x86_64-windows-msvc/c/ws2tcpip.cr
@@ -17,4 +17,24 @@ lib LibC
fun getaddrinfo(pNodeName : Char*, pServiceName : Char*, pHints : Addrinfo*, ppResult : Addrinfo**) : Int
fun inet_ntop(family : Int, pAddr : Void*, pStringBuf : Char*, stringBufSize : SizeT) : Char*
fun inet_pton(family : Int, pszAddrString : Char*, pAddrBuf : Void*) : Int
+
+ fun FreeAddrInfoExW(pAddrInfoEx : ADDRINFOEXW*)
+
+ alias LPLOOKUPSERVICE_COMPLETION_ROUTINE = DWORD, DWORD, WSAOVERLAPPED* ->
+
+ fun GetAddrInfoExW(
+ pName : LPWSTR,
+ pServiceName : LPWSTR,
+ dwNameSpace : DWORD,
+ lpNspId : GUID*,
+ hints : ADDRINFOEXW*,
+ ppResult : ADDRINFOEXW**,
+ timeout : Timeval*,
+ lpOverlapped : OVERLAPPED*,
+ lpCompletionRoutine : LPLOOKUPSERVICE_COMPLETION_ROUTINE,
+ lpHandle : HANDLE*,
+ ) : Int
+
+ fun GetAddrInfoExOverlappedResult(lpOverlapped : OVERLAPPED*) : Int
+ fun GetAddrInfoExCancel(lpHandle : HANDLE*) : Int
end
diff --git a/src/lib_z/lib_z.cr b/src/lib_z/lib_z.cr
index 1c88cb67bba8..47de2981e2f6 100644
--- a/src/lib_z/lib_z.cr
+++ b/src/lib_z/lib_z.cr
@@ -1,3 +1,8 @@
+# Supported library versions:
+#
+# * zlib
+#
+# See https://crystal-lang.org/reference/man/required_libraries.html#other-stdlib-libraries
@[Link("z")]
{% if compare_versions(Crystal::VERSION, "1.11.0-dev") >= 0 %}
@[Link(dll: "zlib1.dll")]
diff --git a/src/llvm.cr b/src/llvm.cr
index 6fb8767cad54..d431a9c4c4d0 100644
--- a/src/llvm.cr
+++ b/src/llvm.cr
@@ -4,6 +4,22 @@ require "c/string"
module LLVM
@@initialized = false
+ # Returns the runtime version of LLVM.
+ #
+ # Starting with LLVM 16, this method returns the version as reported by
+ # `LLVMGetVersion` at runtime. Older versions of LLVM do not expose this
+ # information, so the value falls back to `LibLLVM::VERSION` which is
+ # determined at compile time and might slightly be out of sync to the
+ # dynamic library loaded at runtime.
+ def self.version
+ {% if LibLLVM.has_method?(:get_version) %}
+ LibLLVM.get_version(out major, out minor, out patch)
+ "#{major}.#{minor}.#{patch}"
+ {% else %}
+ LibLLVM::VERSION
+ {% end %}
+ end
+
def self.init_x86 : Nil
return if @@initialized_x86
@@initialized_x86 = true
@@ -140,6 +156,13 @@ module LLVM
string
end
+ protected def self.assert(error : LibLLVM::ErrorRef)
+ if error
+ chars = LibLLVM.get_error_message(error)
+ raise String.new(chars).tap { LibLLVM.dispose_error_message(chars) }
+ end
+ end
+
{% unless LibLLVM::IS_LT_130 %}
def self.run_passes(module mod : Module, passes : String, target_machine : TargetMachine, options : PassBuilderOptions)
LibLLVM.run_passes(mod, passes, target_machine, options)
diff --git a/src/llvm/builder.cr b/src/llvm/builder.cr
index 741f9ee8eb5c..b406d84145e5 100644
--- a/src/llvm/builder.cr
+++ b/src/llvm/builder.cr
@@ -239,11 +239,13 @@ class LLVM::Builder
end
{% end %}
- def not(value, name = "")
- # check_value(value)
+ {% for name in %w(not neg fneg) %}
+ def {{name.id}}(value, name = "")
+ # check_value(value)
- Value.new LibLLVM.build_not(self, value, name)
- end
+ Value.new LibLLVM.build_{{name.id}}(self, value, name)
+ end
+ {% end %}
def unreachable
Value.new LibLLVM.build_unreachable(self)
@@ -385,6 +387,10 @@ class LLVM::Builder
LibLLVM.dispose_builder(@unwrap)
end
+ def finalize
+ dispose
+ end
+
# The next lines are for ease debugging when a types/values
# are incorrectly used across contexts.
diff --git a/src/llvm/context.cr b/src/llvm/context.cr
index 987e8f13ba6b..84c96610a96f 100644
--- a/src/llvm/context.cr
+++ b/src/llvm/context.cr
@@ -108,7 +108,11 @@ class LLVM::Context
end
def const_string(string : String) : Value
- Value.new LibLLVM.const_string_in_context(self, string, string.bytesize, 0)
+ {% if LibLLVM::IS_LT_190 %}
+ Value.new LibLLVM.const_string_in_context(self, string, string.bytesize, 0)
+ {% else %}
+ Value.new LibLLVM.const_string_in_context2(self, string, string.bytesize, 0)
+ {% end %}
end
def const_struct(values : Array(LLVM::Value), packed = false) : Value
diff --git a/src/llvm/di_builder.cr b/src/llvm/di_builder.cr
index 98676431de16..7a06a7041349 100644
--- a/src/llvm/di_builder.cr
+++ b/src/llvm/di_builder.cr
@@ -1,5 +1,6 @@
require "./lib_llvm"
+@[Experimental("The C API wrapped by this type is marked as experimental by LLVM.")]
struct LLVM::DIBuilder
private DW_TAG_structure_type = 19
@@ -95,7 +96,11 @@ struct LLVM::DIBuilder
end
def insert_declare_at_end(storage, var_info, expr, dl : LibLLVM::MetadataRef, block)
- LibLLVM.di_builder_insert_declare_at_end(self, storage, var_info, expr, dl, block)
+ {% if LibLLVM::IS_LT_190 %}
+ LibLLVM.di_builder_insert_declare_at_end(self, storage, var_info, expr, dl, block)
+ {% else %}
+ LibLLVM.di_builder_insert_declare_record_at_end(self, storage, var_info, expr, dl, block)
+ {% end %}
end
def get_or_create_array(elements : Array(LibLLVM::MetadataRef))
diff --git a/src/llvm/enums.cr b/src/llvm/enums.cr
index ac23fa711560..7fb7c59b60df 100644
--- a/src/llvm/enums.cr
+++ b/src/llvm/enums.cr
@@ -249,7 +249,12 @@ module LLVM
Pointer
Vector
Metadata
- X86_MMX
+ X86_MMX # deleted in LLVM 20
+ Token
+ ScalableVector
+ BFloat
+ X86_AMX
+ TargetExt
end
end
diff --git a/src/llvm/ext/find-llvm-config b/src/llvm/ext/find-llvm-config
index 40be636e1b23..5aa381aaf13b 100755
--- a/src/llvm/ext/find-llvm-config
+++ b/src/llvm/ext/find-llvm-config
@@ -16,7 +16,14 @@ if ! LLVM_CONFIG=$(command -v "$LLVM_CONFIG"); then
fi
if [ "$LLVM_CONFIG" ]; then
- printf "$LLVM_CONFIG"
+ case "$(uname -s)" in
+ MINGW32_NT*|MINGW64_NT*)
+ printf "%s" "$(cygpath -w "$LLVM_CONFIG")"
+ ;;
+ *)
+ printf "%s" "$LLVM_CONFIG"
+ ;;
+ esac
else
printf "Error: Could not find location of llvm-config. Please specify path in environment variable LLVM_CONFIG.\n" >&2
printf "Supported LLVM versions: $(cat "$(dirname $0)/llvm-versions.txt" | sed 's/\.0//g')\n" >&2
diff --git a/src/llvm/ext/llvm-versions.txt b/src/llvm/ext/llvm-versions.txt
index 92ae5ecbaa5a..a5d4cfac2515 100644
--- a/src/llvm/ext/llvm-versions.txt
+++ b/src/llvm/ext/llvm-versions.txt
@@ -1 +1 @@
-18.1 17.0 16.0 15.0 14.0 13.0 12.0 11.1 11.0 10.0 9.0 8.0
+20.1 19.1 18.1 17.0 16.0 15.0 14.0 13.0 12.0 11.1 11.0 10.0 9.0 8.0
diff --git a/src/llvm/function_collection.cr b/src/llvm/function_collection.cr
index 62e2bd2e6fc2..d2fdb75a97a1 100644
--- a/src/llvm/function_collection.cr
+++ b/src/llvm/function_collection.cr
@@ -30,7 +30,13 @@ struct LLVM::FunctionCollection
end
def []?(name)
- func = LibLLVM.get_named_function(@mod, name)
+ func =
+ {% if LibLLVM::IS_LT_200 %}
+ LibLLVM.get_named_function(@mod, name)
+ {% else %}
+ LibLLVM.get_named_function_with_length(@mod, name, name.bytesize)
+ {% end %}
+
func ? Function.new(func) : nil
end
diff --git a/src/llvm/global_collection.cr b/src/llvm/global_collection.cr
index 06d27a98de5e..7b214aed34af 100644
--- a/src/llvm/global_collection.cr
+++ b/src/llvm/global_collection.cr
@@ -9,7 +9,13 @@ struct LLVM::GlobalCollection
end
def []?(name)
- global = LibLLVM.get_named_global(@mod, name)
+ global =
+ {% if LibLLVM::IS_LT_200 %}
+ LibLLVM.get_named_global(@mod, name)
+ {% else %}
+ LibLLVM.get_named_global_with_length(@mod, name, name.bytesize)
+ {% end %}
+
global ? Value.new(global) : nil
end
diff --git a/src/llvm/jit_compiler.cr b/src/llvm/jit_compiler.cr
index 33d03e697107..4acae901f381 100644
--- a/src/llvm/jit_compiler.cr
+++ b/src/llvm/jit_compiler.cr
@@ -39,6 +39,10 @@ class LLVM::JITCompiler
LibLLVM.get_pointer_to_global(self, value)
end
+ def function_address(name : String) : Void*
+ Pointer(Void).new(LibLLVM.get_function_address(self, name.check_no_null_byte))
+ end
+
def to_unsafe
@unwrap
end
diff --git a/src/llvm/lib_llvm.cr b/src/llvm/lib_llvm.cr
index 976cedc90df5..14dae405097b 100644
--- a/src/llvm/lib_llvm.cr
+++ b/src/llvm/lib_llvm.cr
@@ -1,5 +1,5 @@
{% begin %}
- {% if flag?(:win32) && !flag?(:static) %}
+ {% if flag?(:msvc) && !flag?(:static) %}
{% config = nil %}
{% for dir in Crystal::LIBRARY_PATH.split(Crystal::System::Process::HOST_PATH_DELIMITER) %}
{% config ||= read_file?("#{dir.id}/llvm_VERSION") %}
@@ -21,7 +21,7 @@
lib LibLLVM
end
{% else %}
- {% llvm_config = env("LLVM_CONFIG") || `#{__DIR__}/ext/find-llvm-config`.stringify %}
+ {% llvm_config = env("LLVM_CONFIG") || `sh #{__DIR__}/ext/find-llvm-config`.stringify %}
{% llvm_version = `#{llvm_config.id} --version`.stringify %}
{% llvm_targets = env("LLVM_TARGETS") || `#{llvm_config.id} --targets-built`.stringify %}
{% llvm_ldflags = "`#{llvm_config.id} --libs --system-libs --ldflags#{" --link-static".id if flag?(:static)}#{" 2> /dev/null".id unless flag?(:win32)}`" %}
@@ -35,11 +35,16 @@
@[Link(ldflags: {{ llvm_ldflags }})]
lib LibLLVM
- VERSION = {{ llvm_version.strip.gsub(/git/, "").gsub(/rc.*/, "") }}
+ VERSION = {{ llvm_version.strip.gsub(/git/, "").gsub(/-?rc.*/, "") }}
BUILT_TARGETS = {{ llvm_targets.strip.downcase.split(' ').map(&.id.symbolize) }}
end
{% end %}
+# Supported library versions:
+#
+# * LLVM (8-19; aarch64 requires 13+)
+#
+# See https://crystal-lang.org/reference/man/required_libraries.html#other-stdlib-libraries
{% begin %}
lib LibLLVM
IS_180 = {{LibLLVM::VERSION.starts_with?("18.0")}}
@@ -65,6 +70,8 @@
IS_LT_160 = {{compare_versions(LibLLVM::VERSION, "16.0.0") < 0}}
IS_LT_170 = {{compare_versions(LibLLVM::VERSION, "17.0.0") < 0}}
IS_LT_180 = {{compare_versions(LibLLVM::VERSION, "18.0.0") < 0}}
+ IS_LT_190 = {{compare_versions(LibLLVM::VERSION, "19.0.0") < 0}}
+ IS_LT_200 = {{compare_versions(LibLLVM::VERSION, "20.0.0") < 0}}
end
{% end %}
diff --git a/src/llvm/lib_llvm/bit_reader.cr b/src/llvm/lib_llvm/bit_reader.cr
new file mode 100644
index 000000000000..9bfd271cbbe2
--- /dev/null
+++ b/src/llvm/lib_llvm/bit_reader.cr
@@ -0,0 +1,5 @@
+require "./types"
+
+lib LibLLVM
+ fun parse_bitcode_in_context2 = LLVMParseBitcodeInContext2(c : ContextRef, mb : MemoryBufferRef, m : ModuleRef*) : Int
+end
diff --git a/src/llvm/lib_llvm/core.cr b/src/llvm/lib_llvm/core.cr
index de6f04010cfa..1c5a580e6c5b 100644
--- a/src/llvm/lib_llvm/core.cr
+++ b/src/llvm/lib_llvm/core.cr
@@ -5,13 +5,22 @@ lib LibLLVM
# counterparts (e.g. `LLVMModuleFlagBehavior` v.s. `LLVM::Module::ModFlagBehavior`)
enum ModuleFlagBehavior
- Warning = 1
+ Error = 0
+ Warning = 1
+ Require = 2
+ Override = 3
+ Append = 4
+ AppendUnique = 5
end
alias AttributeIndex = UInt
fun dispose_message = LLVMDisposeMessage(message : Char*)
+ {% unless LibLLVM::IS_LT_160 %}
+ fun get_version = LLVMGetVersion(major : UInt*, minor : UInt*, patch : UInt*) : Void
+ {% end %}
+
fun create_context = LLVMContextCreate : ContextRef
fun dispose_context = LLVMContextDispose(c : ContextRef)
@@ -41,7 +50,11 @@ lib LibLLVM
fun get_module_context = LLVMGetModuleContext(m : ModuleRef) : ContextRef
fun add_function = LLVMAddFunction(m : ModuleRef, name : Char*, function_ty : TypeRef) : ValueRef
- fun get_named_function = LLVMGetNamedFunction(m : ModuleRef, name : Char*) : ValueRef
+ {% if LibLLVM::IS_LT_200 %}
+ fun get_named_function = LLVMGetNamedFunction(m : ModuleRef, name : Char*) : ValueRef
+ {% else %}
+ fun get_named_function_with_length = LLVMGetNamedFunctionWithLength(m : ModuleRef, name : Char*, length : SizeT) : ValueRef
+ {% end %}
fun get_first_function = LLVMGetFirstFunction(m : ModuleRef) : ValueRef
fun get_next_function = LLVMGetNextFunction(fn : ValueRef) : ValueRef
@@ -116,7 +129,11 @@ lib LibLLVM
fun const_int_get_zext_value = LLVMConstIntGetZExtValue(constant_val : ValueRef) : ULongLong
fun const_int_get_sext_value = LLVMConstIntGetSExtValue(constant_val : ValueRef) : LongLong
- fun const_string_in_context = LLVMConstStringInContext(c : ContextRef, str : Char*, length : UInt, dont_null_terminate : Bool) : ValueRef
+ {% if LibLLVM::IS_LT_190 %}
+ fun const_string_in_context = LLVMConstStringInContext(c : ContextRef, str : Char*, length : UInt, dont_null_terminate : Bool) : ValueRef
+ {% else %}
+ fun const_string_in_context2 = LLVMConstStringInContext2(c : ContextRef, str : Char*, length : SizeT, dont_null_terminate : Bool) : ValueRef
+ {% end %}
fun const_struct_in_context = LLVMConstStructInContext(c : ContextRef, constant_vals : ValueRef*, count : UInt, packed : Bool) : ValueRef
fun const_array = LLVMConstArray(element_ty : TypeRef, constant_vals : ValueRef*, length : UInt) : ValueRef
@@ -131,7 +148,11 @@ lib LibLLVM
fun set_alignment = LLVMSetAlignment(v : ValueRef, bytes : UInt)
fun add_global = LLVMAddGlobal(m : ModuleRef, ty : TypeRef, name : Char*) : ValueRef
- fun get_named_global = LLVMGetNamedGlobal(m : ModuleRef, name : Char*) : ValueRef
+ {% if LibLLVM::IS_LT_200 %}
+ fun get_named_global = LLVMGetNamedGlobal(m : ModuleRef, name : Char*) : ValueRef
+ {% else %}
+ fun get_named_global_with_length = LLVMGetNamedGlobalWithLength(m : ModuleRef, name : Char*, length : SizeT) : ValueRef
+ {% end %}
fun get_initializer = LLVMGetInitializer(global_var : ValueRef) : ValueRef
fun set_initializer = LLVMSetInitializer(global_var : ValueRef, constant_val : ValueRef)
fun is_thread_local = LLVMIsThreadLocal(global_var : ValueRef) : Bool
@@ -239,6 +260,8 @@ lib LibLLVM
fun build_or = LLVMBuildOr(BuilderRef, lhs : ValueRef, rhs : ValueRef, name : Char*) : ValueRef
fun build_xor = LLVMBuildXor(BuilderRef, lhs : ValueRef, rhs : ValueRef, name : Char*) : ValueRef
fun build_not = LLVMBuildNot(BuilderRef, value : ValueRef, name : Char*) : ValueRef
+ fun build_neg = LLVMBuildNeg(BuilderRef, value : ValueRef, name : Char*) : ValueRef
+ fun build_fneg = LLVMBuildFNeg(BuilderRef, value : ValueRef, name : Char*) : ValueRef
fun build_malloc = LLVMBuildMalloc(BuilderRef, ty : TypeRef, name : Char*) : ValueRef
fun build_array_malloc = LLVMBuildArrayMalloc(BuilderRef, ty : TypeRef, val : ValueRef, name : Char*) : ValueRef
diff --git a/src/llvm/lib_llvm/debug_info.cr b/src/llvm/lib_llvm/debug_info.cr
index e97e8c71a177..15d2eca3ebd6 100644
--- a/src/llvm/lib_llvm/debug_info.cr
+++ b/src/llvm/lib_llvm/debug_info.cr
@@ -14,7 +14,7 @@ lib LibLLVM
builder : DIBuilderRef, lang : LLVM::DwarfSourceLanguage, file_ref : MetadataRef, producer : Char*,
producer_len : SizeT, is_optimized : Bool, flags : Char*, flags_len : SizeT, runtime_ver : UInt,
split_name : Char*, split_name_len : SizeT, kind : DWARFEmissionKind, dwo_id : UInt,
- split_debug_inlining : Bool, debug_info_for_profiling : Bool
+ split_debug_inlining : Bool, debug_info_for_profiling : Bool,
) : MetadataRef
{% else %}
fun di_builder_create_compile_unit = LLVMDIBuilderCreateCompileUnit(
@@ -22,82 +22,82 @@ lib LibLLVM
producer_len : SizeT, is_optimized : Bool, flags : Char*, flags_len : SizeT, runtime_ver : UInt,
split_name : Char*, split_name_len : SizeT, kind : DWARFEmissionKind, dwo_id : UInt,
split_debug_inlining : Bool, debug_info_for_profiling : Bool, sys_root : Char*,
- sys_root_len : SizeT, sdk : Char*, sdk_len : SizeT
+ sys_root_len : SizeT, sdk : Char*, sdk_len : SizeT,
) : MetadataRef
{% end %}
fun di_builder_create_file = LLVMDIBuilderCreateFile(
builder : DIBuilderRef, filename : Char*, filename_len : SizeT,
- directory : Char*, directory_len : SizeT
+ directory : Char*, directory_len : SizeT,
) : MetadataRef
fun di_builder_create_function = LLVMDIBuilderCreateFunction(
builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT,
linkage_name : Char*, linkage_name_len : SizeT, file : MetadataRef, line_no : UInt,
ty : MetadataRef, is_local_to_unit : Bool, is_definition : Bool, scope_line : UInt,
- flags : LLVM::DIFlags, is_optimized : Bool
+ flags : LLVM::DIFlags, is_optimized : Bool,
) : MetadataRef
fun di_builder_create_lexical_block = LLVMDIBuilderCreateLexicalBlock(
- builder : DIBuilderRef, scope : MetadataRef, file : MetadataRef, line : UInt, column : UInt
+ builder : DIBuilderRef, scope : MetadataRef, file : MetadataRef, line : UInt, column : UInt,
) : MetadataRef
fun di_builder_create_lexical_block_file = LLVMDIBuilderCreateLexicalBlockFile(
- builder : DIBuilderRef, scope : MetadataRef, file_scope : MetadataRef, discriminator : UInt
+ builder : DIBuilderRef, scope : MetadataRef, file_scope : MetadataRef, discriminator : UInt,
) : MetadataRef
fun di_builder_create_debug_location = LLVMDIBuilderCreateDebugLocation(
- ctx : ContextRef, line : UInt, column : UInt, scope : MetadataRef, inlined_at : MetadataRef
+ ctx : ContextRef, line : UInt, column : UInt, scope : MetadataRef, inlined_at : MetadataRef,
) : MetadataRef
fun di_builder_get_or_create_type_array = LLVMDIBuilderGetOrCreateTypeArray(builder : DIBuilderRef, types : MetadataRef*, length : SizeT) : MetadataRef
fun di_builder_create_subroutine_type = LLVMDIBuilderCreateSubroutineType(
builder : DIBuilderRef, file : MetadataRef, parameter_types : MetadataRef*,
- num_parameter_types : UInt, flags : LLVM::DIFlags
+ num_parameter_types : UInt, flags : LLVM::DIFlags,
) : MetadataRef
{% unless LibLLVM::IS_LT_90 %}
fun di_builder_create_enumerator = LLVMDIBuilderCreateEnumerator(
- builder : DIBuilderRef, name : Char*, name_len : SizeT, value : Int64, is_unsigned : Bool
+ builder : DIBuilderRef, name : Char*, name_len : SizeT, value : Int64, is_unsigned : Bool,
) : MetadataRef
{% end %}
fun di_builder_create_enumeration_type = LLVMDIBuilderCreateEnumerationType(
builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, file : MetadataRef,
line_number : UInt, size_in_bits : UInt64, align_in_bits : UInt32,
- elements : MetadataRef*, num_elements : UInt, class_ty : MetadataRef
+ elements : MetadataRef*, num_elements : UInt, class_ty : MetadataRef,
) : MetadataRef
fun di_builder_create_union_type = LLVMDIBuilderCreateUnionType(
builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, file : MetadataRef,
line_number : UInt, size_in_bits : UInt64, align_in_bits : UInt32, flags : LLVM::DIFlags,
- elements : MetadataRef*, num_elements : UInt, run_time_lang : UInt, unique_id : Char*, unique_id_len : SizeT
+ elements : MetadataRef*, num_elements : UInt, run_time_lang : UInt, unique_id : Char*, unique_id_len : SizeT,
) : MetadataRef
fun di_builder_create_array_type = LLVMDIBuilderCreateArrayType(
builder : DIBuilderRef, size : UInt64, align_in_bits : UInt32,
- ty : MetadataRef, subscripts : MetadataRef*, num_subscripts : UInt
+ ty : MetadataRef, subscripts : MetadataRef*, num_subscripts : UInt,
) : MetadataRef
fun di_builder_create_unspecified_type = LLVMDIBuilderCreateUnspecifiedType(builder : DIBuilderRef, name : Char*, name_len : SizeT) : MetadataRef
fun di_builder_create_basic_type = LLVMDIBuilderCreateBasicType(
builder : DIBuilderRef, name : Char*, name_len : SizeT, size_in_bits : UInt64,
- encoding : UInt, flags : LLVM::DIFlags
+ encoding : UInt, flags : LLVM::DIFlags,
) : MetadataRef
fun di_builder_create_pointer_type = LLVMDIBuilderCreatePointerType(
builder : DIBuilderRef, pointee_ty : MetadataRef, size_in_bits : UInt64, align_in_bits : UInt32,
- address_space : UInt, name : Char*, name_len : SizeT
+ address_space : UInt, name : Char*, name_len : SizeT,
) : MetadataRef
fun di_builder_create_struct_type = LLVMDIBuilderCreateStructType(
builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, file : MetadataRef,
line_number : UInt, size_in_bits : UInt64, align_in_bits : UInt32, flags : LLVM::DIFlags,
derived_from : MetadataRef, elements : MetadataRef*, num_elements : UInt,
- run_time_lang : UInt, v_table_holder : MetadataRef, unique_id : Char*, unique_id_len : SizeT
+ run_time_lang : UInt, v_table_holder : MetadataRef, unique_id : Char*, unique_id_len : SizeT,
) : MetadataRef
fun di_builder_create_member_type = LLVMDIBuilderCreateMemberType(
builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, file : MetadataRef,
line_no : UInt, size_in_bits : UInt64, align_in_bits : UInt32, offset_in_bits : UInt64,
- flags : LLVM::DIFlags, ty : MetadataRef
+ flags : LLVM::DIFlags, ty : MetadataRef,
) : MetadataRef
fun di_builder_create_replaceable_composite_type = LLVMDIBuilderCreateReplaceableCompositeType(
builder : DIBuilderRef, tag : UInt, name : Char*, name_len : SizeT, scope : MetadataRef,
file : MetadataRef, line : UInt, runtime_lang : UInt, size_in_bits : UInt64, align_in_bits : UInt32,
- flags : LLVM::DIFlags, unique_identifier : Char*, unique_identifier_len : SizeT
+ flags : LLVM::DIFlags, unique_identifier : Char*, unique_identifier_len : SizeT,
) : MetadataRef
fun di_builder_get_or_create_subrange = LLVMDIBuilderGetOrCreateSubrange(builder : DIBuilderRef, lo : Int64, count : Int64) : MetadataRef
@@ -111,18 +111,25 @@ lib LibLLVM
fun metadata_replace_all_uses_with = LLVMMetadataReplaceAllUsesWith(target_metadata : MetadataRef, replacement : MetadataRef)
- fun di_builder_insert_declare_at_end = LLVMDIBuilderInsertDeclareAtEnd(
- builder : DIBuilderRef, storage : ValueRef, var_info : MetadataRef,
- expr : MetadataRef, debug_loc : MetadataRef, block : BasicBlockRef
- ) : ValueRef
+ {% if LibLLVM::IS_LT_190 %}
+ fun di_builder_insert_declare_at_end = LLVMDIBuilderInsertDeclareAtEnd(
+ builder : DIBuilderRef, storage : ValueRef, var_info : MetadataRef,
+ expr : MetadataRef, debug_loc : MetadataRef, block : BasicBlockRef,
+ ) : ValueRef
+ {% else %}
+ fun di_builder_insert_declare_record_at_end = LLVMDIBuilderInsertDeclareRecordAtEnd(
+ builder : DIBuilderRef, storage : ValueRef, var_info : MetadataRef,
+ expr : MetadataRef, debug_loc : MetadataRef, block : BasicBlockRef,
+ ) : DbgRecordRef
+ {% end %}
fun di_builder_create_auto_variable = LLVMDIBuilderCreateAutoVariable(
builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, file : MetadataRef,
- line_no : UInt, ty : MetadataRef, always_preserve : Bool, flags : LLVM::DIFlags, align_in_bits : UInt32
+ line_no : UInt, ty : MetadataRef, always_preserve : Bool, flags : LLVM::DIFlags, align_in_bits : UInt32,
) : MetadataRef
fun di_builder_create_parameter_variable = LLVMDIBuilderCreateParameterVariable(
builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, arg_no : UInt,
- file : MetadataRef, line_no : UInt, ty : MetadataRef, always_preserve : Bool, flags : LLVM::DIFlags
+ file : MetadataRef, line_no : UInt, ty : MetadataRef, always_preserve : Bool, flags : LLVM::DIFlags,
) : MetadataRef
fun set_subprogram = LLVMSetSubprogram(func : ValueRef, sp : MetadataRef)
diff --git a/src/llvm/lib_llvm/error.cr b/src/llvm/lib_llvm/error.cr
index b816a7e2088b..5a035b5f80a5 100644
--- a/src/llvm/lib_llvm/error.cr
+++ b/src/llvm/lib_llvm/error.cr
@@ -1,3 +1,6 @@
lib LibLLVM
type ErrorRef = Void*
+
+ fun get_error_message = LLVMGetErrorMessage(err : ErrorRef) : Char*
+ fun dispose_error_message = LLVMDisposeErrorMessage(err_msg : Char*)
end
diff --git a/src/llvm/lib_llvm/execution_engine.cr b/src/llvm/lib_llvm/execution_engine.cr
index f9de5c10ea39..bfc2e23154db 100644
--- a/src/llvm/lib_llvm/execution_engine.cr
+++ b/src/llvm/lib_llvm/execution_engine.cr
@@ -30,4 +30,5 @@ lib LibLLVM
fun run_function = LLVMRunFunction(ee : ExecutionEngineRef, f : ValueRef, num_args : UInt, args : GenericValueRef*) : GenericValueRef
fun get_execution_engine_target_machine = LLVMGetExecutionEngineTargetMachine(ee : ExecutionEngineRef) : TargetMachineRef
fun get_pointer_to_global = LLVMGetPointerToGlobal(ee : ExecutionEngineRef, global : ValueRef) : Void*
+ fun get_function_address = LLVMGetFunctionAddress(ee : ExecutionEngineRef, name : Char*) : UInt64
end
diff --git a/src/llvm/lib_llvm/lljit.cr b/src/llvm/lib_llvm/lljit.cr
new file mode 100644
index 000000000000..93c2089c9db0
--- /dev/null
+++ b/src/llvm/lib_llvm/lljit.cr
@@ -0,0 +1,17 @@
+{% skip_file if LibLLVM::IS_LT_110 %}
+
+lib LibLLVM
+ alias OrcLLJITBuilderRef = Void*
+ alias OrcLLJITRef = Void*
+
+ fun orc_create_lljit_builder = LLVMOrcCreateLLJITBuilder : OrcLLJITBuilderRef
+ fun orc_dispose_lljit_builder = LLVMOrcDisposeLLJITBuilder(builder : OrcLLJITBuilderRef)
+
+ fun orc_create_lljit = LLVMOrcCreateLLJIT(result : OrcLLJITRef*, builder : OrcLLJITBuilderRef) : ErrorRef
+ fun orc_dispose_lljit = LLVMOrcDisposeLLJIT(j : OrcLLJITRef) : ErrorRef
+
+ fun orc_lljit_get_main_jit_dylib = LLVMOrcLLJITGetMainJITDylib(j : OrcLLJITRef) : OrcJITDylibRef
+ fun orc_lljit_get_global_prefix = LLVMOrcLLJITGetGlobalPrefix(j : OrcLLJITRef) : Char
+ fun orc_lljit_add_llvm_ir_module = LLVMOrcLLJITAddLLVMIRModule(j : OrcLLJITRef, jd : OrcJITDylibRef, tsm : OrcThreadSafeModuleRef) : ErrorRef
+ fun orc_lljit_lookup = LLVMOrcLLJITLookup(j : OrcLLJITRef, result : OrcExecutorAddress*, name : Char*) : ErrorRef
+end
diff --git a/src/llvm/lib_llvm/orc.cr b/src/llvm/lib_llvm/orc.cr
new file mode 100644
index 000000000000..278a9c4aab5d
--- /dev/null
+++ b/src/llvm/lib_llvm/orc.cr
@@ -0,0 +1,26 @@
+{% skip_file if LibLLVM::IS_LT_110 %}
+
+lib LibLLVM
+ # OrcJITTargetAddress before LLVM 13.0 (also an alias of UInt64)
+ alias OrcExecutorAddress = UInt64
+ alias OrcSymbolStringPoolEntryRef = Void*
+ alias OrcJITDylibRef = Void*
+ alias OrcDefinitionGeneratorRef = Void*
+ alias OrcSymbolPredicate = Void*, OrcSymbolStringPoolEntryRef -> Int
+ alias OrcThreadSafeContextRef = Void*
+ alias OrcThreadSafeModuleRef = Void*
+
+ fun orc_create_dynamic_library_search_generator_for_process = LLVMOrcCreateDynamicLibrarySearchGeneratorForProcess(
+ result : OrcDefinitionGeneratorRef*, global_prefx : Char,
+ filter : OrcSymbolPredicate, filter_ctx : Void*,
+ ) : ErrorRef
+
+ fun orc_jit_dylib_add_generator = LLVMOrcJITDylibAddGenerator(jd : OrcJITDylibRef, dg : OrcDefinitionGeneratorRef)
+
+ fun orc_create_new_thread_safe_context = LLVMOrcCreateNewThreadSafeContext : OrcThreadSafeContextRef
+ fun orc_thread_safe_context_get_context = LLVMOrcThreadSafeContextGetContext(ts_ctx : OrcThreadSafeContextRef) : ContextRef
+ fun orc_dispose_thread_safe_context = LLVMOrcDisposeThreadSafeContext(ts_ctx : OrcThreadSafeContextRef)
+
+ fun orc_create_new_thread_safe_module = LLVMOrcCreateNewThreadSafeModule(m : ModuleRef, ts_ctx : OrcThreadSafeContextRef) : OrcThreadSafeModuleRef
+ fun orc_dispose_thread_safe_module = LLVMOrcDisposeThreadSafeModule(tsm : OrcThreadSafeModuleRef)
+end
diff --git a/src/llvm/lib_llvm/types.cr b/src/llvm/lib_llvm/types.cr
index a1b374f30219..532078394794 100644
--- a/src/llvm/lib_llvm/types.cr
+++ b/src/llvm/lib_llvm/types.cr
@@ -17,4 +17,5 @@ lib LibLLVM
{% end %}
type OperandBundleRef = Void*
type AttributeRef = Void*
+ type DbgRecordRef = Void*
end
diff --git a/src/llvm/module.cr b/src/llvm/module.cr
index f216d485055c..0e73e983358a 100644
--- a/src/llvm/module.cr
+++ b/src/llvm/module.cr
@@ -6,6 +6,12 @@ class LLVM::Module
getter context : Context
+ def self.parse(memory_buffer : MemoryBuffer, context : Context) : self
+ LibLLVM.parse_bitcode_in_context2(context, memory_buffer, out module_ref)
+ raise "BUG: failed to parse LLVM bitcode from memory buffer" unless module_ref
+ new(module_ref, context)
+ end
+
def initialize(@unwrap : LibLLVM::ModuleRef, @context : Context)
@owned = false
end
@@ -39,6 +45,10 @@ class LLVM::Module
GlobalCollection.new(self)
end
+ def add_flag(module_flag : LibLLVM::ModuleFlagBehavior, key : String, val : Int32)
+ add_flag(module_flag, key, @context.int32.const_int(val))
+ end
+
def add_flag(module_flag : LibLLVM::ModuleFlagBehavior, key : String, val : Value)
LibLLVM.add_module_flag(
self,
diff --git a/src/llvm/orc/jit_dylib.cr b/src/llvm/orc/jit_dylib.cr
new file mode 100644
index 000000000000..b1050725110b
--- /dev/null
+++ b/src/llvm/orc/jit_dylib.cr
@@ -0,0 +1,16 @@
+{% skip_file if LibLLVM::IS_LT_110 %}
+
+@[Experimental("The C API wrapped by this type is marked as experimental by LLVM.")]
+class LLVM::Orc::JITDylib
+ protected def initialize(@unwrap : LibLLVM::OrcJITDylibRef)
+ end
+
+ def to_unsafe
+ @unwrap
+ end
+
+ def link_symbols_from_current_process(global_prefix : Char) : Nil
+ LLVM.assert LibLLVM.orc_create_dynamic_library_search_generator_for_process(out dg, global_prefix.ord.to_u8, nil, nil)
+ LibLLVM.orc_jit_dylib_add_generator(self, dg)
+ end
+end
diff --git a/src/llvm/orc/lljit.cr b/src/llvm/orc/lljit.cr
new file mode 100644
index 000000000000..62fcc7f0519f
--- /dev/null
+++ b/src/llvm/orc/lljit.cr
@@ -0,0 +1,46 @@
+{% skip_file if LibLLVM::IS_LT_110 %}
+
+@[Experimental("The C API wrapped by this type is marked as experimental by LLVM.")]
+class LLVM::Orc::LLJIT
+ protected def initialize(@unwrap : LibLLVM::OrcLLJITRef)
+ end
+
+ def self.new(builder : LLJITBuilder)
+ builder.take_ownership { raise "Failed to take ownership of LLVM::Orc::LLJITBuilder" }
+ LLVM.assert LibLLVM.orc_create_lljit(out unwrap, builder)
+ new(unwrap)
+ end
+
+ def to_unsafe
+ @unwrap
+ end
+
+ def dispose : Nil
+ LLVM.assert LibLLVM.orc_dispose_lljit(self)
+ @unwrap = LibLLVM::OrcLLJITRef.null
+ end
+
+ def finalize
+ if @unwrap
+ LibLLVM.orc_dispose_lljit(self)
+ end
+ end
+
+ def main_jit_dylib : JITDylib
+ JITDylib.new(LibLLVM.orc_lljit_get_main_jit_dylib(self))
+ end
+
+ def global_prefix : Char
+ LibLLVM.orc_lljit_get_global_prefix(self).unsafe_chr
+ end
+
+ def add_llvm_ir_module(dylib : JITDylib, tsm : ThreadSafeModule) : Nil
+ tsm.take_ownership { raise "Failed to take ownership of LLVM::Orc::ThreadSafeModule" }
+ LLVM.assert LibLLVM.orc_lljit_add_llvm_ir_module(self, dylib, tsm)
+ end
+
+ def lookup(name : String) : Void*
+ LLVM.assert LibLLVM.orc_lljit_lookup(self, out address, name.check_no_null_byte)
+ Pointer(Void).new(address)
+ end
+end
diff --git a/src/llvm/orc/lljit_builder.cr b/src/llvm/orc/lljit_builder.cr
new file mode 100644
index 000000000000..8147e5947376
--- /dev/null
+++ b/src/llvm/orc/lljit_builder.cr
@@ -0,0 +1,35 @@
+{% skip_file if LibLLVM::IS_LT_110 %}
+
+@[Experimental("The C API wrapped by this type is marked as experimental by LLVM.")]
+class LLVM::Orc::LLJITBuilder
+ protected def initialize(@unwrap : LibLLVM::OrcLLJITBuilderRef)
+ @dispose_on_finalize = true
+ end
+
+ def self.new
+ new(LibLLVM.orc_create_lljit_builder)
+ end
+
+ def to_unsafe
+ @unwrap
+ end
+
+ def dispose : Nil
+ LibLLVM.orc_dispose_lljit_builder(self)
+ @unwrap = LibLLVM::OrcLLJITBuilderRef.null
+ end
+
+ def finalize
+ if @dispose_on_finalize && @unwrap
+ dispose
+ end
+ end
+
+ def take_ownership(&) : Nil
+ if @dispose_on_finalize
+ @dispose_on_finalize = false
+ else
+ yield
+ end
+ end
+end
diff --git a/src/llvm/orc/thread_safe_context.cr b/src/llvm/orc/thread_safe_context.cr
new file mode 100644
index 000000000000..38c4ece7a50a
--- /dev/null
+++ b/src/llvm/orc/thread_safe_context.cr
@@ -0,0 +1,30 @@
+{% skip_file if LibLLVM::IS_LT_110 %}
+
+@[Experimental("The C API wrapped by this type is marked as experimental by LLVM.")]
+class LLVM::Orc::ThreadSafeContext
+ protected def initialize(@unwrap : LibLLVM::OrcThreadSafeContextRef)
+ end
+
+ def self.new
+ new(LibLLVM.orc_create_new_thread_safe_context)
+ end
+
+ def to_unsafe
+ @unwrap
+ end
+
+ def dispose : Nil
+ LibLLVM.orc_dispose_thread_safe_context(self)
+ @unwrap = LibLLVM::OrcThreadSafeContextRef.null
+ end
+
+ def finalize
+ if @unwrap
+ dispose
+ end
+ end
+
+ def context : LLVM::Context
+ LLVM::Context.new(LibLLVM.orc_thread_safe_context_get_context(self), false)
+ end
+end
diff --git a/src/llvm/orc/thread_safe_module.cr b/src/llvm/orc/thread_safe_module.cr
new file mode 100644
index 000000000000..5e29667fd9cd
--- /dev/null
+++ b/src/llvm/orc/thread_safe_module.cr
@@ -0,0 +1,36 @@
+{% skip_file if LibLLVM::IS_LT_110 %}
+
+@[Experimental("The C API wrapped by this type is marked as experimental by LLVM.")]
+class LLVM::Orc::ThreadSafeModule
+ protected def initialize(@unwrap : LibLLVM::OrcThreadSafeModuleRef)
+ @dispose_on_finalize = true
+ end
+
+ def self.new(llvm_mod : LLVM::Module, ts_ctx : ThreadSafeContext)
+ llvm_mod.take_ownership { raise "Failed to take ownership of LLVM::Module" }
+ new(LibLLVM.orc_create_new_thread_safe_module(llvm_mod, ts_ctx))
+ end
+
+ def to_unsafe
+ @unwrap
+ end
+
+ def dispose : Nil
+ LibLLVM.orc_dispose_thread_safe_module(self)
+ @unwrap = LibLLVM::OrcThreadSafeModuleRef.null
+ end
+
+ def finalize
+ if @dispose_on_finalize && @unwrap
+ dispose
+ end
+ end
+
+ def take_ownership(&) : Nil
+ if @dispose_on_finalize
+ @dispose_on_finalize = false
+ else
+ yield
+ end
+ end
+end
diff --git a/src/llvm/target_machine.cr b/src/llvm/target_machine.cr
index b9de8296d5c8..6e31836ef7f2 100644
--- a/src/llvm/target_machine.cr
+++ b/src/llvm/target_machine.cr
@@ -48,7 +48,7 @@ class LLVM::TargetMachine
def abi
triple = self.triple
case triple
- when /x86_64.+windows-msvc/
+ when /x86_64.+windows-(?:msvc|gnu)/
ABI::X86_Win64.new(self)
when /x86_64|amd64/
ABI::X86_64.new(self)
diff --git a/src/log/log.cr b/src/log/log.cr
index 3480cfecf33b..f7c8cf4f1cf9 100644
--- a/src/log/log.cr
+++ b/src/log/log.cr
@@ -41,6 +41,15 @@ class Log
{% for method in %w(trace debug info notice warn error fatal) %}
{% severity = method.id.camelcase %}
+ # Logs the given *exception* if the logger's current severity is lower than
+ # or equal to `Severity::{{severity}}`.
+ def {{method.id}}(*, exception : Exception) : Nil
+ severity = Severity::{{severity}}
+ if level <= severity && (backend = @backend)
+ backend.dispatch Emitter.new(@source, severity, exception).emit("")
+ end
+ end
+
# Logs a message if the logger's current severity is lower than or equal to
# `Severity::{{ severity }}`.
#
@@ -65,13 +74,16 @@ class Log
dsl = Emitter.new(@source, severity, exception)
result = yield dsl
- case result
- when Entry
- backend.dispatch result
- when Nil
- # emit nothing
- else
- backend.dispatch dsl.emit(result.to_s)
+ unless result.nil? && exception.nil?
+ entry =
+ case result
+ when Entry
+ result
+ else
+ dsl.emit(result.to_s)
+ end
+
+ backend.dispatch entry
end
end
{% end %}
diff --git a/src/log/main.cr b/src/log/main.cr
index 3ff86e169ba4..91d0b03d0817 100644
--- a/src/log/main.cr
+++ b/src/log/main.cr
@@ -36,6 +36,11 @@ class Log
private Top = Log.for("")
{% for method in %i(trace debug info notice warn error fatal) %}
+ # See `Log#{{method.id}}`.
+ def self.{{method.id}}(*, exception : Exception) : Nil
+ Top.{{method.id}}(exception: exception)
+ end
+
# See `Log#{{method.id}}`.
def self.{{method.id}}(*, exception : Exception? = nil)
Top.{{method.id}}(exception: exception) do |dsl|
diff --git a/src/mutex.cr b/src/mutex.cr
index 780eac468201..14d1aedf7923 100644
--- a/src/mutex.cr
+++ b/src/mutex.cr
@@ -1,3 +1,4 @@
+require "fiber/pointer_linked_list_node"
require "crystal/spin_lock"
# A fiber-safe mutex.
@@ -22,7 +23,7 @@ class Mutex
@state = Atomic(Int32).new(UNLOCKED)
@mutex_fiber : Fiber?
@lock_count = 0
- @queue = Deque(Fiber).new
+ @queue = Crystal::PointerLinkedList(Fiber::PointerLinkedListNode).new
@queue_count = Atomic(Int32).new(0)
@lock = Crystal::SpinLock.new
@@ -59,6 +60,8 @@ class Mutex
loop do
break if try_lock
+ waiting = Fiber::PointerLinkedListNode.new(Fiber.current)
+
@lock.sync do
@queue_count.add(1)
@@ -71,7 +74,7 @@ class Mutex
end
end
- @queue.push Fiber.current
+ @queue.push pointerof(waiting)
end
Fiber.suspend
@@ -116,17 +119,18 @@ class Mutex
return
end
- fiber = nil
+ waiting = nil
@lock.sync do
if @queue_count.get == 0
return
end
- if fiber = @queue.shift?
+ if waiting = @queue.shift?
@queue_count.add(-1)
end
end
- fiber.enqueue if fiber
+
+ waiting.try(&.value.enqueue)
end
def synchronize(&)
diff --git a/src/number.cr b/src/number.cr
index f7c82aa4cded..9d955c065df3 100644
--- a/src/number.cr
+++ b/src/number.cr
@@ -59,7 +59,7 @@ struct Number
# :nodoc:
macro expand_div(rhs_types, result_type)
{% for rhs in rhs_types %}
- @[AlwaysInline]
+ @[::AlwaysInline]
def /(other : {{rhs}}) : {{result_type}}
{{result_type}}.new(self) / {{result_type}}.new(other)
end
@@ -84,7 +84,7 @@ struct Number
# [1, 2, 3, 4] of Int64 # : Array(Int64)
# ```
macro [](*nums)
- Array({{@type}}).build({{nums.size}}) do |%buffer|
+ ::Array({{@type}}).build({{nums.size}}) do |%buffer|
{% for num, i in nums %}
%buffer[{{i}}] = {{@type}}.new({{num}})
{% end %}
@@ -113,7 +113,7 @@ struct Number
# Slice[1_i64, 2_i64, 3_i64, 4_i64] # : Slice(Int64)
# ```
macro slice(*nums, read_only = false)
- %slice = Slice({{@type}}).new({{nums.size}}, read_only: {{read_only}})
+ %slice = ::Slice({{@type}}).new({{nums.size}}, read_only: {{read_only}})
{% for num, i in nums %}
%slice.to_unsafe[{{i}}] = {{@type}}.new!({{num}})
{% end %}
@@ -139,7 +139,7 @@ struct Number
# StaticArray[1_i64, 2_i64, 3_i64, 4_i64] # : StaticArray(Int64)
# ```
macro static_array(*nums)
- %array = uninitialized StaticArray({{@type}}, {{nums.size}})
+ %array = uninitialized ::StaticArray({{@type}}, {{nums.size}})
{% for num, i in nums %}
%array.to_unsafe[{{i}}] = {{@type}}.new!({{num}})
{% end %}
diff --git a/src/object.cr b/src/object.cr
index ba818ac2979e..4443eaec3916 100644
--- a/src/object.cr
+++ b/src/object.cr
@@ -457,18 +457,18 @@ class Object
{{var_prefix}}\{{name.var.id}} : \{{name.type}}?
def {{method_prefix}}\{{name.var.id}} : \{{name.type}}
- if (value = {{var_prefix}}\{{name.var.id}}).nil?
+ if (%value = {{var_prefix}}\{{name.var.id}}).nil?
{{var_prefix}}\{{name.var.id}} = \{{yield}}
else
- value
+ %value
end
end
\{% else %}
def {{method_prefix}}\{{name.id}}
- if (value = {{var_prefix}}\{{name.id}}).nil?
+ if (%value = {{var_prefix}}\{{name.id}}).nil?
{{var_prefix}}\{{name.id}} = \{{yield}}
else
- value
+ %value
end
end
\{% end %}
@@ -561,10 +561,10 @@ class Object
end
def {{method_prefix}}\{{name.var.id}} : \{{name.type}}
- if (value = {{var_prefix}}\{{name.var.id}}).nil?
- ::raise NilAssertionError.new("\{{@type}}\{{"{{doc_prefix}}".id}}\{{name.var.id}} cannot be nil")
+ if (%value = {{var_prefix}}\{{name.var.id}}).nil?
+ ::raise ::NilAssertionError.new("\{{@type}}\{{"{{doc_prefix}}".id}}\{{name.var.id}} cannot be nil")
else
- value
+ %value
end
end
\{% else %}
@@ -573,10 +573,10 @@ class Object
end
def {{method_prefix}}\{{name.id}}
- if (value = {{var_prefix}}\{{name.id}}).nil?
- ::raise NilAssertionError.new("\{{@type}}\{{"{{doc_prefix}}".id}}\{{name.id}} cannot be nil")
+ if (%value = {{var_prefix}}\{{name.id}}).nil?
+ ::raise ::NilAssertionError.new("\{{@type}}\{{"{{doc_prefix}}".id}}\{{name.id}} cannot be nil")
else
- value
+ %value
end
end
\{% end %}
@@ -688,18 +688,18 @@ class Object
{{var_prefix}}\{{name.var.id}} : \{{name.type}}?
def {{method_prefix}}\{{name.var.id}}? : \{{name.type}}
- if (value = {{var_prefix}}\{{name.var.id}}).nil?
+ if (%value = {{var_prefix}}\{{name.var.id}}).nil?
{{var_prefix}}\{{name.var.id}} = \{{yield}}
else
- value
+ %value
end
end
\{% else %}
def {{method_prefix}}\{{name.id}}?
- if (value = {{var_prefix}}\{{name.id}}).nil?
+ if (%value = {{var_prefix}}\{{name.id}}).nil?
{{var_prefix}}\{{name.id}} = \{{yield}}
else
- value
+ %value
end
end
\{% end %}
@@ -970,10 +970,10 @@ class Object
{{var_prefix}}\{{name.var.id}} : \{{name.type}}?
def {{method_prefix}}\{{name.var.id}} : \{{name.type}}
- if (value = {{var_prefix}}\{{name.var.id}}).nil?
+ if (%value = {{var_prefix}}\{{name.var.id}}).nil?
{{var_prefix}}\{{name.var.id}} = \{{yield}}
else
- value
+ %value
end
end
@@ -981,10 +981,10 @@ class Object
end
\{% else %}
def {{method_prefix}}\{{name.id}}
- if (value = {{var_prefix}}\{{name.id}}).nil?
+ if (%value = {{var_prefix}}\{{name.id}}).nil?
{{var_prefix}}\{{name.id}} = \{{yield}}
else
- value
+ %value
end
end
@@ -1216,10 +1216,10 @@ class Object
{{var_prefix}}\{{name.var.id}} : \{{name.type}}?
def {{method_prefix}}\{{name.var.id}}? : \{{name.type}}
- if (value = {{var_prefix}}\{{name.var.id}}).nil?
+ if (%value = {{var_prefix}}\{{name.var.id}}).nil?
{{var_prefix}}\{{name.var.id}} = \{{yield}}
else
- value
+ %value
end
end
@@ -1227,10 +1227,10 @@ class Object
end
\{% else %}
def {{method_prefix}}\{{name.id}}?
- if (value = {{var_prefix}}\{{name.id}}).nil?
+ if (%value = {{var_prefix}}\{{name.id}}).nil?
{{var_prefix}}\{{name.id}} = \{{yield}}
else
- value
+ %value
end
end
@@ -1293,7 +1293,7 @@ class Object
# wrapper.capitalize # => "Hello"
# ```
macro delegate(*methods, to object)
- {% if compare_versions(Crystal::VERSION, "1.12.0-dev") >= 0 %}
+ {% if compare_versions(::Crystal::VERSION, "1.12.0-dev") >= 0 %}
{% eq_operators = %w(<= >= == != []= ===) %}
{% for method in methods %}
{% if method.id.ends_with?('=') && !eq_operators.includes?(method.id.stringify) %}
@@ -1427,18 +1427,18 @@ class Object
macro def_clone
# Returns a copy of `self` with all instance variables cloned.
def clone
- \{% if @type < Reference && !@type.instance_vars.map(&.type).all? { |t| t == ::Bool || t == ::Char || t == ::Symbol || t == ::String || t < ::Number::Primitive } %}
+ \{% if @type < ::Reference && !@type.instance_vars.map(&.type).all? { |t| t == ::Bool || t == ::Char || t == ::Symbol || t == ::String || t < ::Number::Primitive } %}
exec_recursive_clone do |hash|
clone = \{{@type}}.allocate
hash[object_id] = clone.object_id
clone.initialize_copy(self)
- GC.add_finalizer(clone) if clone.responds_to?(:finalize)
+ ::GC.add_finalizer(clone) if clone.responds_to?(:finalize)
clone
end
\{% else %}
clone = \{{@type}}.allocate
clone.initialize_copy(self)
- GC.add_finalizer(clone) if clone.responds_to?(:finalize)
+ ::GC.add_finalizer(clone) if clone.responds_to?(:finalize)
clone
\{% end %}
end
diff --git a/src/openssl/lib_crypto.cr b/src/openssl/lib_crypto.cr
index aef6a238f663..b75474951764 100644
--- a/src/openssl/lib_crypto.cr
+++ b/src/openssl/lib_crypto.cr
@@ -1,6 +1,12 @@
+# Supported library versions:
+#
+# * openssl (1.1.0–3.3+)
+# * libressl (2.0–4.0+)
+#
+# See https://crystal-lang.org/reference/man/required_libraries.html#tls
{% begin %}
lib LibCrypto
- {% if flag?(:win32) %}
+ {% if flag?(:msvc) %}
{% from_libressl = false %}
{% ssl_version = nil %}
{% for dir in Crystal::LIBRARY_PATH.split(Crystal::System::Process::HOST_PATH_DELIMITER) %}
@@ -13,10 +19,12 @@
{% end %}
{% ssl_version ||= "0.0.0" %}
{% else %}
- {% from_libressl = (`hash pkg-config 2> /dev/null || printf %s false` != "false") &&
- (`test -f $(pkg-config --silence-errors --variable=includedir libcrypto)/openssl/opensslv.h || printf %s false` != "false") &&
- (`printf "#include \nLIBRESSL_VERSION_NUMBER" | ${CC:-cc} $(pkg-config --cflags --silence-errors libcrypto || true) -E -`.chomp.split('\n').last != "LIBRESSL_VERSION_NUMBER") %}
- {% ssl_version = `hash pkg-config 2> /dev/null && pkg-config --silence-errors --modversion libcrypto || printf %s 0.0.0`.split.last.gsub(/[^0-9.]/, "") %}
+ # these have to be wrapped in `sh -c` since for MinGW-w64 the compiler
+ # passes the command string to `LibC.CreateProcessW`
+ {% from_libressl = (`sh -c 'hash pkg-config 2> /dev/null || printf %s false'` != "false") &&
+ (`sh -c 'test -f $(pkg-config --silence-errors --variable=includedir libcrypto)/openssl/opensslv.h || printf %s false'` != "false") &&
+ (`sh -c 'printf "#include \nLIBRESSL_VERSION_NUMBER" | ${CC:-cc} $(pkg-config --cflags --silence-errors libcrypto || true) -E -'`.chomp.split('\n').last != "LIBRESSL_VERSION_NUMBER") %}
+ {% ssl_version = `sh -c 'hash pkg-config 2> /dev/null && pkg-config --silence-errors --modversion libcrypto || printf %s 0.0.0'`.split.last.gsub(/[^0-9.]/, "") %}
{% end %}
{% if from_libressl %}
@@ -57,7 +65,10 @@ lib LibCrypto
struct Bio
method : Void*
- callback : (Void*, Int, Char*, Int, Long, Long) -> Long
+ callback : BIO_callback_fn
+ {% if compare_versions(LIBRESSL_VERSION, "3.5.0") >= 0 %}
+ callback_ex : BIO_callback_fn_ex
+ {% end %}
cb_arg : Char*
init : Int
shutdown : Int
@@ -72,6 +83,9 @@ lib LibCrypto
num_write : ULong
end
+ alias BIO_callback_fn = (Bio*, Int, Char*, Int, Long, Long) -> Long
+ alias BIO_callback_fn_ex = (Bio*, Int, Char, SizeT, Int, Long, Int, SizeT*) -> Long
+
PKCS5_SALT_LEN = 8
EVP_MAX_KEY_LENGTH = 32
EVP_MAX_IV_LENGTH = 16
@@ -95,7 +109,7 @@ lib LibCrypto
alias BioMethodDestroy = Bio* -> Int
alias BioMethodCallbackCtrl = (Bio*, Int, Void*) -> Long
- {% if compare_versions(LibCrypto::OPENSSL_VERSION, "1.1.0") >= 0 %}
+ {% if compare_versions(LibCrypto::OPENSSL_VERSION, "1.1.0") >= 0 || compare_versions(LibCrypto::LIBRESSL_VERSION, "2.7.0") >= 0 %}
type BioMethod = Void
{% else %}
struct BioMethod
@@ -115,7 +129,7 @@ lib LibCrypto
fun BIO_new(BioMethod*) : Bio*
fun BIO_free(Bio*) : Int
- {% if compare_versions(LibCrypto::OPENSSL_VERSION, "1.1.0") >= 0 %}
+ {% if compare_versions(LibCrypto::OPENSSL_VERSION, "1.1.0") >= 0 || compare_versions(LibCrypto::LIBRESSL_VERSION, "2.7.0") >= 0 %}
fun BIO_set_data(Bio*, Void*)
fun BIO_get_data(Bio*) : Void*
fun BIO_set_init(Bio*, Int)
@@ -131,6 +145,7 @@ lib LibCrypto
fun BIO_meth_set_destroy(BioMethod*, BioMethodDestroy)
fun BIO_meth_set_callback_ctrl(BioMethod*, BioMethodCallbackCtrl)
{% end %}
+ # LibreSSL does not define these symbols
{% if compare_versions(LibCrypto::OPENSSL_VERSION, "1.1.1") >= 0 %}
fun BIO_meth_set_read_ex(BioMethod*, BioMethodRead)
fun BIO_meth_set_write_ex(BioMethod*, BioMethodWrite)
@@ -215,7 +230,7 @@ lib LibCrypto
fun evp_digestfinal_ex = EVP_DigestFinal_ex(ctx : EVP_MD_CTX, md : UInt8*, size : UInt32*) : Int32
- {% if compare_versions(OPENSSL_VERSION, "1.1.0") >= 0 %}
+ {% if compare_versions(OPENSSL_VERSION, "1.1.0") >= 0 || compare_versions(LibCrypto::LIBRESSL_VERSION, "2.7.0") >= 0 %}
fun evp_md_ctx_new = EVP_MD_CTX_new : EVP_MD_CTX
fun evp_md_ctx_free = EVP_MD_CTX_free(ctx : EVP_MD_CTX)
{% else %}
@@ -231,14 +246,14 @@ lib LibCrypto
fun evp_cipher_block_size = EVP_CIPHER_get_block_size(cipher : EVP_CIPHER) : Int32
fun evp_cipher_key_length = EVP_CIPHER_get_key_length(cipher : EVP_CIPHER) : Int32
fun evp_cipher_iv_length = EVP_CIPHER_get_iv_length(cipher : EVP_CIPHER) : Int32
- fun evp_cipher_flags = EVP_CIPHER_get_flags(ctx : EVP_CIPHER_CTX) : CipherFlags
+ fun evp_cipher_flags = EVP_CIPHER_get_flags(cipher : EVP_CIPHER) : CipherFlags
{% else %}
fun evp_cipher_name = EVP_CIPHER_name(cipher : EVP_CIPHER) : UInt8*
fun evp_cipher_nid = EVP_CIPHER_nid(cipher : EVP_CIPHER) : Int32
fun evp_cipher_block_size = EVP_CIPHER_block_size(cipher : EVP_CIPHER) : Int32
fun evp_cipher_key_length = EVP_CIPHER_key_length(cipher : EVP_CIPHER) : Int32
fun evp_cipher_iv_length = EVP_CIPHER_iv_length(cipher : EVP_CIPHER) : Int32
- fun evp_cipher_flags = EVP_CIPHER_flags(ctx : EVP_CIPHER_CTX) : CipherFlags
+ fun evp_cipher_flags = EVP_CIPHER_flags(cipher : EVP_CIPHER) : CipherFlags
{% end %}
fun evp_cipher_ctx_new = EVP_CIPHER_CTX_new : EVP_CIPHER_CTX
@@ -292,7 +307,7 @@ lib LibCrypto
fun md5 = MD5(data : UInt8*, length : LibC::SizeT, md : UInt8*) : UInt8*
fun pkcs5_pbkdf2_hmac_sha1 = PKCS5_PBKDF2_HMAC_SHA1(pass : LibC::Char*, passlen : LibC::Int, salt : UInt8*, saltlen : LibC::Int, iter : LibC::Int, keylen : LibC::Int, out : UInt8*) : LibC::Int
- {% if compare_versions(OPENSSL_VERSION, "1.0.0") >= 0 %}
+ {% if compare_versions(OPENSSL_VERSION, "1.0.0") >= 0 || LIBRESSL_VERSION != "0.0.0" %}
fun pkcs5_pbkdf2_hmac = PKCS5_PBKDF2_HMAC(pass : LibC::Char*, passlen : LibC::Int, salt : UInt8*, saltlen : LibC::Int, iter : LibC::Int, digest : EVP_MD, keylen : LibC::Int, out : UInt8*) : LibC::Int
{% end %}
@@ -366,12 +381,12 @@ lib LibCrypto
fun x509_store_add_cert = X509_STORE_add_cert(ctx : X509_STORE, x : X509) : Int
- {% unless compare_versions(OPENSSL_VERSION, "1.1.0") >= 0 %}
+ {% unless compare_versions(OPENSSL_VERSION, "1.1.0") >= 0 || compare_versions(LibCrypto::LIBRESSL_VERSION, "3.0.0") >= 0 %}
fun err_load_crypto_strings = ERR_load_crypto_strings
fun openssl_add_all_algorithms = OPENSSL_add_all_algorithms_noconf
{% end %}
- {% if compare_versions(OPENSSL_VERSION, "1.0.2") >= 0 %}
+ {% if compare_versions(OPENSSL_VERSION, "1.0.2") >= 0 || LIBRESSL_VERSION != "0.0.0" %}
type X509VerifyParam = Void*
@[Flags]
diff --git a/src/openssl/lib_ssl.cr b/src/openssl/lib_ssl.cr
index 6adb3f172a3b..449f35dd0f72 100644
--- a/src/openssl/lib_ssl.cr
+++ b/src/openssl/lib_ssl.cr
@@ -4,9 +4,15 @@ require "./lib_crypto"
{% raise "The `without_openssl` flag is preventing you to use the LibSSL module" %}
{% end %}
+# Supported library versions:
+#
+# * openssl (1.1.0–3.3+)
+# * libressl (2.0–4.0+)
+#
+# See https://crystal-lang.org/reference/man/required_libraries.html#tls
{% begin %}
lib LibSSL
- {% if flag?(:win32) %}
+ {% if flag?(:msvc) %}
{% from_libressl = false %}
{% ssl_version = nil %}
{% for dir in Crystal::LIBRARY_PATH.split(Crystal::System::Process::HOST_PATH_DELIMITER) %}
@@ -19,10 +25,12 @@ require "./lib_crypto"
{% end %}
{% ssl_version ||= "0.0.0" %}
{% else %}
- {% from_libressl = (`hash pkg-config 2> /dev/null || printf %s false` != "false") &&
- (`test -f $(pkg-config --silence-errors --variable=includedir libssl)/openssl/opensslv.h || printf %s false` != "false") &&
- (`printf "#include \nLIBRESSL_VERSION_NUMBER" | ${CC:-cc} $(pkg-config --cflags --silence-errors libssl || true) -E -`.chomp.split('\n').last != "LIBRESSL_VERSION_NUMBER") %}
- {% ssl_version = `hash pkg-config 2> /dev/null && pkg-config --silence-errors --modversion libssl || printf %s 0.0.0`.split.last.gsub(/[^0-9.]/, "") %}
+ # these have to be wrapped in `sh -c` since for MinGW-w64 the compiler
+ # passes the command string to `LibC.CreateProcessW`
+ {% from_libressl = (`sh -c 'hash pkg-config 2> /dev/null || printf %s false'` != "false") &&
+ (`sh -c 'test -f $(pkg-config --silence-errors --variable=includedir libssl)/openssl/opensslv.h || printf %s false'` != "false") &&
+ (`sh -c 'printf "#include \nLIBRESSL_VERSION_NUMBER" | ${CC:-cc} $(pkg-config --cflags --silence-errors libssl || true) -E -'`.chomp.split('\n').last != "LIBRESSL_VERSION_NUMBER") %}
+ {% ssl_version = `sh -c 'hash pkg-config 2> /dev/null && pkg-config --silence-errors --modversion libssl || printf %s 0.0.0'`.split.last.gsub(/[^0-9.]/, "") %}
{% end %}
{% if from_libressl %}
@@ -137,7 +145,7 @@ lib LibSSL
NETSCAPE_DEMO_CIPHER_CHANGE_BUG = 0x40000000
CRYPTOPRO_TLSEXT_BUG = 0x80000000
- {% if compare_versions(OPENSSL_VERSION, "1.1.0") >= 0 %}
+ {% if compare_versions(OPENSSL_VERSION, "1.1.0") >= 0 || compare_versions(LIBRESSL_VERSION, "2.3.0") >= 0 %}
MICROSOFT_SESS_ID_BUG = 0x00000000
NETSCAPE_CHALLENGE_BUG = 0x00000000
NETSCAPE_REUSE_CIPHER_CHANGE_BUG = 0x00000000
@@ -235,6 +243,7 @@ lib LibSSL
fun ssl_get_peer_certificate = SSL_get_peer_certificate(handle : SSL) : LibCrypto::X509
{% end %}
+ # In LibreSSL these functions are implemented as macros
{% if compare_versions(OPENSSL_VERSION, "1.1.0") >= 0 %}
fun ssl_ctx_get_options = SSL_CTX_get_options(ctx : SSLContext) : ULong
fun ssl_ctx_set_options = SSL_CTX_set_options(ctx : SSLContext, larg : ULong) : ULong
@@ -249,12 +258,13 @@ lib LibSSL
fun ssl_ctx_set_cert_verify_callback = SSL_CTX_set_cert_verify_callback(ctx : SSLContext, callback : CertVerifyCallback, arg : Void*)
# control TLS 1.3 session ticket generation
+ # LibreSSL does not seem to implement these functions
{% if compare_versions(OPENSSL_VERSION, "1.1.1") >= 0 %}
fun ssl_ctx_set_num_tickets = SSL_CTX_set_num_tickets(ctx : SSLContext, larg : LibC::SizeT) : Int
fun ssl_set_num_tickets = SSL_set_num_tickets(ctx : SSL, larg : LibC::SizeT) : Int
{% end %}
- {% if compare_versions(OPENSSL_VERSION, "1.1.0") >= 0 %}
+ {% if compare_versions(LibSSL::OPENSSL_VERSION, "1.1.0") >= 0 || compare_versions(LibSSL::LIBRESSL_VERSION, "2.3.0") >= 0 %}
fun tls_method = TLS_method : SSLMethod
{% else %}
fun ssl_library_init = SSL_library_init
@@ -262,7 +272,7 @@ lib LibSSL
fun sslv23_method = SSLv23_method : SSLMethod
{% end %}
- {% if compare_versions(OPENSSL_VERSION, "1.0.2") >= 0 || compare_versions(LIBRESSL_VERSION, "2.5.0") >= 0 %}
+ {% if compare_versions(OPENSSL_VERSION, "1.0.2") >= 0 || compare_versions(LIBRESSL_VERSION, "2.1.0") >= 0 %}
alias ALPNCallback = (SSL, Char**, Char*, Char*, Int, Void*) -> Int
fun ssl_get0_alpn_selected = SSL_get0_alpn_selected(handle : SSL, data : Char**, len : LibC::UInt*) : Void
@@ -270,7 +280,7 @@ lib LibSSL
fun ssl_ctx_set_alpn_protos = SSL_CTX_set_alpn_protos(ctx : SSLContext, protos : Char*, protos_len : Int) : Int
{% end %}
- {% if compare_versions(OPENSSL_VERSION, "1.0.2") >= 0 %}
+ {% if compare_versions(OPENSSL_VERSION, "1.0.2") >= 0 || compare_versions(LIBRESSL_VERSION, "2.7.0") >= 0 %}
alias X509VerifyParam = LibCrypto::X509VerifyParam
fun dtls_method = DTLS_method : SSLMethod
@@ -280,7 +290,7 @@ lib LibSSL
fun ssl_ctx_set1_param = SSL_CTX_set1_param(ctx : SSLContext, param : X509VerifyParam) : Int
{% end %}
- {% if compare_versions(OPENSSL_VERSION, "1.1.0") >= 0 %}
+ {% if compare_versions(OPENSSL_VERSION, "1.1.0") >= 0 || compare_versions(LIBRESSL_VERSION, "3.6.0") >= 0 %}
fun ssl_ctx_set_security_level = SSL_CTX_set_security_level(ctx : SSLContext, level : Int) : Void
fun ssl_ctx_get_security_level = SSL_CTX_get_security_level(ctx : SSLContext) : Int
{% end %}
@@ -291,7 +301,7 @@ lib LibSSL
{% end %}
end
-{% unless compare_versions(LibSSL::OPENSSL_VERSION, "1.1.0") >= 0 %}
+{% if LibSSL.has_method?(:ssl_library_init) %}
LibSSL.ssl_library_init
LibSSL.ssl_load_error_strings
LibCrypto.openssl_add_all_algorithms
diff --git a/src/pointer.cr b/src/pointer.cr
index 2479ef0bbb77..87da18b25fa5 100644
--- a/src/pointer.cr
+++ b/src/pointer.cr
@@ -52,6 +52,20 @@ struct Pointer(T)
def pointer
@pointer
end
+
+ # Creates a slice pointing at the values appended by this instance.
+ #
+ # ```
+ # slice = Slice(Int32).new(5)
+ # appender = slice.to_unsafe.appender
+ # appender << 1
+ # appender << 2
+ # appender << 3
+ # appender.to_slice # => Slice[1, 2, 3]
+ # ```
+ def to_slice : Slice(T)
+ @start.to_slice(size)
+ end
end
include Comparable(self)
@@ -272,18 +286,20 @@ struct Pointer(T)
self
end
- # Compares *count* elements from this pointer and *other*, byte by byte.
+ # Compares *count* elements from this pointer and *other*, lexicographically.
#
- # Returns 0 if both pointers point to the same sequence of *count* bytes. Otherwise
- # returns the difference between the first two differing bytes (treated as UInt8).
+ # Returns 0 if both pointers point to the same sequence of *count* bytes.
+ # Otherwise, if the first two differing bytes (treated as UInt8) from `self`
+ # and *other* are `x` and `y` respectively, returns a negative value if
+ # `x < y`, or a positive value if `x > y`.
#
# ```
# ptr1 = Pointer.malloc(4) { |i| i + 1 } # [1, 2, 3, 4]
# ptr2 = Pointer.malloc(4) { |i| i + 11 } # [11, 12, 13, 14]
#
- # ptr1.memcmp(ptr2, 4) # => -10
- # ptr2.memcmp(ptr1, 4) # => 10
- # ptr1.memcmp(ptr1, 4) # => 0
+ # ptr1.memcmp(ptr2, 4) < 0 # => true
+ # ptr2.memcmp(ptr1, 4) > 0 # => true
+ # ptr1.memcmp(ptr1, 4) == 0 # => true
# ```
def memcmp(other : Pointer(T), count : Int) : Int32
LibC.memcmp(self.as(Void*), (other.as(Void*)), (count * sizeof(T)))
@@ -418,6 +434,7 @@ struct Pointer(T)
# ptr = Pointer(Int32).new(5678)
# ptr.address # => 5678
# ```
+ @[Deprecated("Call `.new(UInt64)` directly instead")]
def self.new(address : Int)
new address.to_u64!
end
diff --git a/src/primitives.cr b/src/primitives.cr
index a3594b4543d9..e033becdfbd2 100644
--- a/src/primitives.cr
+++ b/src/primitives.cr
@@ -206,12 +206,8 @@ struct Pointer(T)
# ```
#
# The implementation uses `GC.malloc` if the compiler is aware that the
- # allocated type contains inner address pointers. Otherwise it uses
- # `GC.malloc_atomic`. Primitive types are expected to not contain pointers,
- # except `Void`. `Proc` and `Pointer` are expected to contain pointers.
- # For unions, structs and collection types (tuples, static array)
- # it depends on the contained types. All other types, including classes are
- # expected to contain inner address pointers.
+ # allocated type contains inner address pointers. See
+ # `Crystal::Macros::TypeNode#has_inner_pointers?` for details.
#
# To override this implicit behaviour, `GC.malloc` and `GC.malloc_atomic`
# can be used directly instead.
@@ -237,6 +233,20 @@ struct Pointer(T)
# ptr.value = 42
# ptr.value # => 42
# ```
+ #
+ # WARNING: The pointer must be appropriately aligned, i.e. `address` must be
+ # a multiple of `alignof(T)`. It is undefined behavior to load from a
+ # misaligned pointer. Such reads should instead be done via a cast to
+ # `Pointer(UInt8)`, which is guaranteed to have byte alignment:
+ #
+ # ```
+ # # raises SIGSEGV on X86 if `ptr` is misaligned
+ # x = ptr.as(UInt128*).value
+ #
+ # # okay, `ptr` can have any alignment
+ # x = uninitialized UInt128
+ # ptr.as(UInt8*).copy_to(pointerof(x).as(UInt8*), sizeof(typeof(x)))
+ # ```
@[Primitive(:pointer_get)]
def value : T
end
@@ -248,6 +258,20 @@ struct Pointer(T)
# ptr.value = 42
# ptr.value # => 42
# ```
+ #
+ # WARNING: The pointer must be appropriately aligned, i.e. `address` must be
+ # a multiple of `alignof(T)`. It is undefined behavior to store to a
+ # misaligned pointer. Such writes should instead be done via a cast to
+ # `Pointer(UInt8)`, which is guaranteed to have byte alignment:
+ #
+ # ```
+ # # raises SIGSEGV on X86 if `ptr` is misaligned
+ # x = 123_u128
+ # ptr.as(UInt128*).value = x
+ #
+ # # okay, `ptr` can have any alignment
+ # ptr.as(UInt8*).copy_from(pointerof(x).as(UInt8*), sizeof(typeof(x)))
+ # ```
@[Primitive(:pointer_set)]
def value=(value : T)
end
diff --git a/src/proc.cr b/src/proc.cr
index fca714517dbf..69c0ebf5cd0e 100644
--- a/src/proc.cr
+++ b/src/proc.cr
@@ -3,7 +3,7 @@
#
# ```
# # A proc without arguments
-# ->{ 1 } # Proc(Int32)
+# -> { 1 } # Proc(Int32)
#
# # A proc with one argument
# ->(x : Int32) { x.to_s } # Proc(Int32, String)
diff --git a/src/process.cr b/src/process.cr
index c8364196373f..63b78bf0f716 100644
--- a/src/process.cr
+++ b/src/process.cr
@@ -291,33 +291,20 @@ class Process
private def stdio_to_fd(stdio : Stdio, for dst_io : IO::FileDescriptor) : IO::FileDescriptor
case stdio
- when IO::FileDescriptor
- stdio
- when IO
- if stdio.closed?
- if dst_io == STDIN
- return File.open(File::NULL, "r").tap(&.close)
- else
- return File.open(File::NULL, "w").tap(&.close)
+ in IO::FileDescriptor
+ # on Windows, only async pipes can be passed to child processes, async
+ # regular files will report an error and those require a separate pipe
+ # (https://github.com/crystal-lang/crystal/pull/13362#issuecomment-1519082712)
+ {% if flag?(:win32) %}
+ unless stdio.blocking || stdio.info.type.pipe?
+ return io_to_fd(stdio, for: dst_io)
end
- end
-
- if dst_io == STDIN
- fork_io, process_io = IO.pipe(read_blocking: true)
-
- @wait_count += 1
- ensure_channel
- spawn { copy_io(stdio, process_io, channel, close_dst: true) }
- else
- process_io, fork_io = IO.pipe(write_blocking: true)
+ {% end %}
- @wait_count += 1
- ensure_channel
- spawn { copy_io(process_io, stdio, channel, close_src: true) }
- end
-
- fork_io
- when Redirect::Pipe
+ stdio
+ in IO
+ io_to_fd(stdio, for: dst_io)
+ in Redirect::Pipe
case dst_io
when STDIN
fork_io, @input = IO.pipe(read_blocking: true)
@@ -330,17 +317,41 @@ class Process
end
fork_io
- when Redirect::Inherit
+ in Redirect::Inherit
dst_io
- when Redirect::Close
+ in Redirect::Close
if dst_io == STDIN
File.open(File::NULL, "r")
else
File.open(File::NULL, "w")
end
+ end
+ end
+
+ private def io_to_fd(stdio : Stdio, for dst_io : IO::FileDescriptor) : IO::FileDescriptor
+ if stdio.closed?
+ if dst_io == STDIN
+ return File.open(File::NULL, "r").tap(&.close)
+ else
+ return File.open(File::NULL, "w").tap(&.close)
+ end
+ end
+
+ if dst_io == STDIN
+ fork_io, process_io = IO.pipe(read_blocking: true)
+
+ @wait_count += 1
+ ensure_channel
+ spawn { copy_io(stdio, process_io, channel, close_dst: true) }
else
- raise "BUG: Impossible type in stdio #{stdio.class}"
+ process_io, fork_io = IO.pipe(write_blocking: true)
+
+ @wait_count += 1
+ ensure_channel
+ spawn { copy_io(process_io, stdio, channel, close_src: true) }
end
+
+ fork_io
end
# :nodoc:
diff --git a/src/process/status.cr b/src/process/status.cr
index de29351ff12f..78cff49f0dc9 100644
--- a/src/process/status.cr
+++ b/src/process/status.cr
@@ -14,7 +14,7 @@ enum Process::ExitReason
# reserved for normal exits.
Normal
- # The process terminated abnormally.
+ # The process terminated due to an abort request.
#
# * On Unix-like systems, this corresponds to `Signal::ABRT`, `Signal::KILL`,
# and `Signal::QUIT`.
@@ -91,16 +91,31 @@ enum Process::ExitReason
# * On Unix-like systems, this corresponds to `Signal::TERM`.
# * On Windows, this corresponds to the `CTRL_LOGOFF_EVENT` and `CTRL_SHUTDOWN_EVENT` messages.
SessionEnded
+
+ # Returns `true` if the process exited abnormally.
+ #
+ # This includes all values except `Normal`.
+ def abnormal?
+ !normal?
+ end
end
# The status of a terminated process. Returned by `Process#wait`.
class Process::Status
# Platform-specific exit status code, which usually contains either the exit code or a termination signal.
# The other `Process::Status` methods extract the values from `exit_status`.
+ @[Deprecated("Use `#exit_reason`, `#exit_code`, or `#system_exit_status` instead")]
def exit_status : Int32
@exit_status.to_i32!
end
+ # Returns the exit status as indicated by the operating system.
+ #
+ # It can encode exit codes and termination signals and is platform-specific.
+ def system_exit_status : UInt32
+ @exit_status.to_u32!
+ end
+
{% if flag?(:win32) %}
# :nodoc:
def initialize(@exit_status : UInt32)
@@ -135,36 +150,30 @@ class Process::Status
@exit_status & 0xC0000000_u32 == 0 ? ExitReason::Normal : ExitReason::Unknown
end
{% elsif flag?(:unix) && !flag?(:wasm32) %}
- if normal_exit?
+ case exit_signal?
+ when Nil
ExitReason::Normal
- elsif signal_exit?
- case Signal.from_value?(signal_code)
- when Nil
- ExitReason::Signal
- when .abrt?, .kill?, .quit?
- ExitReason::Aborted
- when .hup?
- ExitReason::TerminalDisconnected
- when .term?
- ExitReason::SessionEnded
- when .int?
- ExitReason::Interrupted
- when .trap?
- ExitReason::Breakpoint
- when .segv?
- ExitReason::AccessViolation
- when .bus?
- ExitReason::BadMemoryAccess
- when .ill?
- ExitReason::BadInstruction
- when .fpe?
- ExitReason::FloatException
- else
- ExitReason::Signal
- end
+ when .abrt?, .kill?, .quit?
+ ExitReason::Aborted
+ when .hup?
+ ExitReason::TerminalDisconnected
+ when .term?
+ ExitReason::SessionEnded
+ when .int?
+ ExitReason::Interrupted
+ when .trap?
+ ExitReason::Breakpoint
+ when .segv?
+ ExitReason::AccessViolation
+ when .bus?
+ ExitReason::BadMemoryAccess
+ when .ill?
+ ExitReason::BadInstruction
+ when .fpe?
+ ExitReason::FloatException
else
# TODO: stop / continue
- ExitReason::Unknown
+ ExitReason::Signal
end
{% else %}
raise NotImplementedError.new("Process::Status#exit_reason")
@@ -172,22 +181,34 @@ class Process::Status
end
# Returns `true` if the process was terminated by a signal.
+ #
+ # NOTE: In contrast to `WIFSIGNALED` in glibc, the status code `0x7E` (`SIGSTOP`)
+ # is considered a signal.
+ #
+ # * `#abnormal_exit?` is a more portable alternative.
+ # * `#exit_signal?` provides more information about the signal.
def signal_exit? : Bool
- {% if flag?(:unix) %}
- 0x01 <= (@exit_status & 0x7F) <= 0x7E
- {% else %}
- false
- {% end %}
+ !!exit_signal?
end
# Returns `true` if the process terminated normally.
+ #
+ # Equivalent to `ExitReason::Normal`
+ #
+ # * `#exit_reason` provides more insights into other exit reasons.
+ # * `#abnormal_exit?` returns the inverse.
def normal_exit? : Bool
- {% if flag?(:unix) %}
- # define __WIFEXITED(status) (__WTERMSIG(status) == 0)
- signal_code == 0
- {% else %}
- true
- {% end %}
+ exit_reason.normal?
+ end
+
+ # Returns `true` if the process terminated abnormally.
+ #
+ # Equivalent to `ExitReason#abnormal?`
+ #
+ # * `#exit_reason` provides more insights into the specific exit reason.
+ # * `#normal_exit?` returns the inverse.
+ def abnormal_exit? : Bool
+ exit_reason.abnormal?
end
# If `signal_exit?` is `true`, returns the *Signal* the process
@@ -199,25 +220,62 @@ class Process::Status
# which also works on Windows.
def exit_signal : Signal
{% if flag?(:unix) && !flag?(:wasm32) %}
- Signal.from_value(signal_code)
+ Signal.new(signal_code)
{% else %}
raise NotImplementedError.new("Process::Status#exit_signal")
{% end %}
end
- # If `normal_exit?` is `true`, returns the exit code of the process.
+ # Returns the exit `Signal` or `nil` if there is none.
+ #
+ # On Windows returns always `nil`.
+ #
+ # * `#exit_reason` is a portable alternative.
+ def exit_signal? : Signal?
+ {% if flag?(:unix) && !flag?(:wasm32) %}
+ code = signal_code
+ unless code.zero?
+ Signal.new(code)
+ end
+ {% end %}
+ end
+
+ # Returns the exit code of the process if it exited normally (`#normal_exit?`).
+ #
+ # Raises `RuntimeError` if the status describes an abnormal exit.
+ #
+ # ```
+ # Process.run("true").exit_code # => 0
+ # Process.run("exit 123", shell: true).exit_code # => 123
+ # Process.new("sleep", ["10"]).tap(&.terminate).wait.exit_code # RuntimeError: Abnormal exit has no exit code
+ # ```
def exit_code : Int32
+ exit_code? || raise RuntimeError.new("Abnormal exit has no exit code")
+ end
+
+ # Returns the exit code of the process if it exited normally.
+ #
+ # Returns `nil` if the status describes an abnormal exit.
+ #
+ # ```
+ # Process.run("true").exit_code? # => 0
+ # Process.run("exit 123", shell: true).exit_code? # => 123
+ # Process.new("sleep", ["10"]).tap(&.terminate).wait.exit_code? # => nil
+ # ```
+ def exit_code? : Int32?
+ return unless normal_exit?
+
{% if flag?(:unix) %}
# define __WEXITSTATUS(status) (((status) & 0xff00) >> 8)
(@exit_status & 0xff00) >> 8
{% else %}
- exit_status
+ @exit_status.to_i32!
{% end %}
end
# Returns `true` if the process exited normally with an exit code of `0`.
def success? : Bool
- normal_exit? && exit_code == 0
+ exit_code? == 0
end
private def signal_code
@@ -229,40 +287,97 @@ class Process::Status
# Prints a textual representation of the process status to *io*.
#
- # The result is equivalent to `#to_s`, but prefixed by the type name and
- # delimited by square brackets: `Process::Status[0]`, `Process::Status[1]`,
- # `Process::Status[Signal::HUP]`.
+ # The result is similar to `#to_s`, but prefixed by the type name,
+ # delimited by square brackets, and constants use full paths:
+ # `Process::Status[0]`, `Process::Status[1]`, `Process::Status[Signal::HUP]`,
+ # `Process::Status[LibC::STATUS_CONTROL_C_EXIT]`.
def inspect(io : IO) : Nil
io << "Process::Status["
- if normal_exit?
- exit_code.inspect(io)
- else
- exit_signal.inspect(io)
- end
+ {% if flag?(:win32) %}
+ if name = name_for_win32_exit_status
+ io << "LibC::" << name
+ else
+ stringify_exit_status_windows(io)
+ end
+ {% else %}
+ if signal = exit_signal?
+ signal.inspect(io)
+ else
+ exit_code.inspect(io)
+ end
+ {% end %}
io << "]"
end
+ private def name_for_win32_exit_status
+ case @exit_status
+ # Ignoring LibC::STATUS_SUCCESS here because we prefer its numerical representation `0`
+ when LibC::STATUS_FATAL_APP_EXIT then "STATUS_FATAL_APP_EXIT"
+ when LibC::STATUS_DATATYPE_MISALIGNMENT then "STATUS_DATATYPE_MISALIGNMENT"
+ when LibC::STATUS_BREAKPOINT then "STATUS_BREAKPOINT"
+ when LibC::STATUS_ACCESS_VIOLATION then "STATUS_ACCESS_VIOLATION"
+ when LibC::STATUS_ILLEGAL_INSTRUCTION then "STATUS_ILLEGAL_INSTRUCTION"
+ when LibC::STATUS_FLOAT_DIVIDE_BY_ZERO then "STATUS_FLOAT_DIVIDE_BY_ZERO"
+ when LibC::STATUS_FLOAT_INEXACT_RESULT then "STATUS_FLOAT_INEXACT_RESULT"
+ when LibC::STATUS_FLOAT_INVALID_OPERATION then "STATUS_FLOAT_INVALID_OPERATION"
+ when LibC::STATUS_FLOAT_OVERFLOW then "STATUS_FLOAT_OVERFLOW"
+ when LibC::STATUS_FLOAT_UNDERFLOW then "STATUS_FLOAT_UNDERFLOW"
+ when LibC::STATUS_PRIVILEGED_INSTRUCTION then "STATUS_PRIVILEGED_INSTRUCTION"
+ when LibC::STATUS_STACK_OVERFLOW then "STATUS_STACK_OVERFLOW"
+ when LibC::STATUS_CANCELLED then "STATUS_CANCELLED"
+ when LibC::STATUS_CONTROL_C_EXIT then "STATUS_CONTROL_C_EXIT"
+ end
+ end
+
# Prints a textual representation of the process status to *io*.
#
- # A normal exit status prints the numerical value (`0`, `1` etc).
+ # A normal exit status prints the numerical value (`0`, `1` etc) or a named
+ # status (e.g. `STATUS_CONTROL_C_EXIT` on Windows).
# A signal exit status prints the name of the `Signal` member (`HUP`, `INT`, etc.).
def to_s(io : IO) : Nil
- if normal_exit?
- io << exit_code
- else
- io << exit_signal
- end
+ {% if flag?(:win32) %}
+ if name = name_for_win32_exit_status
+ io << name
+ else
+ stringify_exit_status_windows(io)
+ end
+ {% else %}
+ if signal = exit_signal?
+ if name = signal.member_name
+ io << name
+ else
+ signal.inspect(io)
+ end
+ else
+ io << exit_code
+ end
+ {% end %}
end
# Returns a textual representation of the process status.
#
- # A normal exit status prints the numerical value (`0`, `1` etc).
+ # A normal exit status prints the numerical value (`0`, `1` etc) or a named
+ # status (e.g. `STATUS_CONTROL_C_EXIT` on Windows).
# A signal exit status prints the name of the `Signal` member (`HUP`, `INT`, etc.).
def to_s : String
- if normal_exit?
- exit_code.to_s
+ {% if flag?(:win32) %}
+ name_for_win32_exit_status || String.build { |io| stringify_exit_status_windows(io) }
+ {% else %}
+ if signal = exit_signal?
+ signal.member_name || signal.inspect
+ else
+ exit_code.to_s
+ end
+ {% end %}
+ end
+
+ private def stringify_exit_status_windows(io)
+ # On Windows large status codes are typically expressed in hexadecimal
+ if @exit_status >= UInt16::MAX
+ io << "0x"
+ @exit_status.to_s(base: 16, upcase: true).rjust(io, 8, '0')
else
- exit_signal.to_s
+ @exit_status.to_s(io)
end
end
end
diff --git a/src/raise.cr b/src/raise.cr
index ff8684795e77..1ba0243def28 100644
--- a/src/raise.cr
+++ b/src/raise.cr
@@ -91,7 +91,7 @@ end
{% if flag?(:interpreted) %}
# interpreter does not need `__crystal_personality`
-{% elsif flag?(:win32) %}
+{% elsif flag?(:win32) && !flag?(:gnu) %}
require "exception/lib_unwind"
{% begin %}
@@ -181,8 +181,11 @@ end
0u64
end
{% else %}
+ {% mingw = flag?(:win32) && flag?(:gnu) %}
# :nodoc:
- fun __crystal_personality(version : Int32, actions : LibUnwind::Action, exception_class : UInt64, exception_object : LibUnwind::Exception*, context : Void*) : LibUnwind::ReasonCode
+ fun {{ mingw ? "__crystal_personality_imp".id : "__crystal_personality".id }}(
+ version : Int32, actions : LibUnwind::Action, exception_class : UInt64, exception_object : LibUnwind::Exception*, context : Void*,
+ ) : LibUnwind::ReasonCode
start = LibUnwind.get_region_start(context)
ip = LibUnwind.get_ip(context)
lsd = LibUnwind.get_language_specific_data(context)
@@ -197,9 +200,28 @@ end
return LibUnwind::ReasonCode::CONTINUE_UNWIND
end
+
+ {% if mingw %}
+ lib LibC
+ alias EXCEPTION_DISPOSITION = Int
+ alias DISPATCHER_CONTEXT = Void
+ end
+
+ # :nodoc:
+ lib LibUnwind
+ alias PersonalityFn = Int32, Action, UInt64, Exception*, Void* -> ReasonCode
+
+ fun _GCC_specific_handler(ms_exc : LibC::EXCEPTION_RECORD64*, this_frame : Void*, ms_orig_context : LibC::CONTEXT*, ms_disp : LibC::DISPATCHER_CONTEXT*, gcc_per : PersonalityFn) : LibC::EXCEPTION_DISPOSITION
+ end
+
+ # :nodoc:
+ fun __crystal_personality(ms_exc : LibC::EXCEPTION_RECORD64*, this_frame : Void*, ms_orig_context : LibC::CONTEXT*, ms_disp : LibC::DISPATCHER_CONTEXT*) : LibC::EXCEPTION_DISPOSITION
+ LibUnwind._GCC_specific_handler(ms_exc, this_frame, ms_orig_context, ms_disp, ->__crystal_personality_imp)
+ end
+ {% end %}
{% end %}
-{% unless flag?(:interpreted) || flag?(:win32) || flag?(:wasm32) %}
+{% unless flag?(:interpreted) || (flag?(:win32) && !flag?(:gnu)) || flag?(:wasm32) %}
# :nodoc:
@[Raises]
fun __crystal_raise(unwind_ex : LibUnwind::Exception*) : NoReturn
@@ -244,7 +266,7 @@ def raise(message : String) : NoReturn
raise Exception.new(message)
end
-{% if flag?(:win32) %}
+{% if flag?(:win32) && !flag?(:gnu) %}
# :nodoc:
{% if flag?(:interpreted) %} @[Primitive(:interpreter_raise_without_backtrace)] {% end %}
def raise_without_backtrace(exception : Exception) : NoReturn
@@ -274,6 +296,7 @@ fun __crystal_raise_overflow : NoReturn
end
{% if flag?(:interpreted) %}
+ # :nodoc:
def __crystal_raise_cast_failed(obj, type_name : String, location : String)
raise TypeCastError.new("Cast from #{obj.class} to #{type_name} failed, at #{location}")
end
diff --git a/src/random/isaac.cr b/src/random/isaac.cr
index c877cb9dbae9..294d439fb82d 100644
--- a/src/random/isaac.cr
+++ b/src/random/isaac.cr
@@ -61,7 +61,7 @@ class Random::ISAAC
a = b = c = d = e = f = g = h = 0x9e3779b9_u32
- mix = ->{
+ mix = -> {
a ^= b << 11; d &+= a; b &+= c
b ^= c >> 2; e &+= b; c &+= d
c ^= d << 8; f &+= c; d &+= e
diff --git a/src/random/secure.cr b/src/random/secure.cr
index 1722b5e6e884..a6b9df03063f 100644
--- a/src/random/secure.cr
+++ b/src/random/secure.cr
@@ -12,7 +12,7 @@ require "crystal/system/random"
# ```
#
# On BSD-based systems and macOS/Darwin, it uses [`arc4random`](https://man.openbsd.org/arc4random),
-# on Linux [`getrandom`](http://man7.org/linux/man-pages/man2/getrandom.2.html) (if the kernel supports it),
+# on Linux [`getrandom`](http://man7.org/linux/man-pages/man2/getrandom.2.html),
# on Windows [`RtlGenRandom`](https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom),
# and falls back to reading from `/dev/urandom` on UNIX systems.
module Random::Secure
diff --git a/src/range.cr b/src/range.cr
index 39d8119dff6e..e8ee24b190cb 100644
--- a/src/range.cr
+++ b/src/range.cr
@@ -480,7 +480,10 @@ struct Range(B, E)
# (3..8).size # => 6
# (3...8).size # => 5
# ```
- def size
+ #
+ # Raises `OverflowError` if the difference is bigger than `Int32`.
+ # Raises `ArgumentError` if either `begin` or `end` are `nil`.
+ def size : Int32
b = self.begin
e = self.end
@@ -488,7 +491,7 @@ struct Range(B, E)
if b.is_a?(Int) && e.is_a?(Int)
e -= 1 if @exclusive
n = e - b + 1
- n < 0 ? 0 : n
+ n < 0 ? 0 : n.to_i32
else
if b.nil? || e.nil?
raise ArgumentError.new("Can't calculate size of an open range")
diff --git a/src/reference.cr b/src/reference.cr
index f70697282fa0..42bdcba2327a 100644
--- a/src/reference.cr
+++ b/src/reference.cr
@@ -1,7 +1,3 @@
-{% if flag?(:preview_mt) %}
- require "crystal/thread_local_value"
-{% end %}
-
# `Reference` is the base class of classes you define in your program.
# It is set as a class' superclass when you don't specify one:
#
@@ -180,28 +176,9 @@ class Reference
io << '>'
end
- # :nodoc:
- module ExecRecursive
- # NOTE: can't use `Set` here because of prelude require order
- alias Registry = Hash({UInt64, Symbol}, Nil)
-
- {% if flag?(:preview_mt) %}
- @@exec_recursive = Crystal::ThreadLocalValue(Registry).new
- {% else %}
- @@exec_recursive = Registry.new
- {% end %}
-
- def self.hash
- {% if flag?(:preview_mt) %}
- @@exec_recursive.get { Registry.new }
- {% else %}
- @@exec_recursive
- {% end %}
- end
- end
-
private def exec_recursive(method, &)
- hash = ExecRecursive.hash
+ # NOTE: can't use `Set` because of prelude require order
+ hash = Fiber.current.exec_recursive_hash
key = {object_id, method}
hash.put(key, nil) do
yield
@@ -211,25 +188,6 @@ class Reference
false
end
- # :nodoc:
- module ExecRecursiveClone
- alias Registry = Hash(UInt64, UInt64)
-
- {% if flag?(:preview_mt) %}
- @@exec_recursive = Crystal::ThreadLocalValue(Registry).new
- {% else %}
- @@exec_recursive = Registry.new
- {% end %}
-
- def self.hash
- {% if flag?(:preview_mt) %}
- @@exec_recursive.get { Registry.new }
- {% else %}
- @@exec_recursive
- {% end %}
- end
- end
-
# Helper method to perform clone by also checking recursiveness.
# When clone is wanted, call this method. Then create the clone
# instance without any contents (don't fill it out yet), then
@@ -249,7 +207,8 @@ class Reference
# end
# ```
private def exec_recursive_clone(&)
- hash = ExecRecursiveClone.hash
+ # NOTE: can't use `Set` because of prelude require order
+ hash = Fiber.current.exec_recursive_clone_hash
clone_object_id = hash[object_id]?
unless clone_object_id
clone_object_id = yield(hash).object_id
diff --git a/src/regex.cr b/src/regex.cr
index 69dd500226a9..c71ac9cd673a 100644
--- a/src/regex.cr
+++ b/src/regex.cr
@@ -240,12 +240,17 @@ class Regex
# flag that activates both behaviours, so here we do the same by
# mapping `MULTILINE` to `PCRE_MULTILINE | PCRE_DOTALL`.
# The same applies for PCRE2 except that the native values are 0x200 and 0x400.
+ #
+ # For the behaviour of `PCRE_MULTILINE` use `MULTILINE_ONLY`.
# Multiline matching.
#
# Equivalent to `MULTILINE | DOTALL` in PCRE and PCRE2.
MULTILINE = 0x0000_0006
+ # Equivalent to `MULTILINE` in PCRE and PCRE2.
+ MULTILINE_ONLY = 0x0000_0004
+
DOTALL = 0x0000_0002
# Ignore white space and `#` comments.
diff --git a/src/regex/lib_pcre.cr b/src/regex/lib_pcre.cr
index da3ac3beb764..8502e5531d3e 100644
--- a/src/regex/lib_pcre.cr
+++ b/src/regex/lib_pcre.cr
@@ -1,3 +1,8 @@
+# Supported library versions:
+#
+# * libpcre
+#
+# See https://crystal-lang.org/reference/man/required_libraries.html#regular-expression-engine
@[Link("pcre", pkg_config: "libpcre")]
{% if compare_versions(Crystal::VERSION, "1.11.0-dev") >= 0 %}
@[Link(dll: "pcre.dll")]
diff --git a/src/regex/lib_pcre2.cr b/src/regex/lib_pcre2.cr
index 651a1c95bef2..6f45a4465219 100644
--- a/src/regex/lib_pcre2.cr
+++ b/src/regex/lib_pcre2.cr
@@ -1,3 +1,8 @@
+# Supported library versions:
+#
+# * libpcre2 (recommended: 10.36+)
+#
+# See https://crystal-lang.org/reference/man/required_libraries.html#regular-expression-engine
@[Link("pcre2-8", pkg_config: "libpcre2-8")]
{% if compare_versions(Crystal::VERSION, "1.11.0-dev") >= 0 %}
@[Link(dll: "pcre2-8.dll")]
diff --git a/src/regex/pcre.cr b/src/regex/pcre.cr
index e6cf6eaca7b0..19decbb66712 100644
--- a/src/regex/pcre.cr
+++ b/src/regex/pcre.cr
@@ -6,7 +6,7 @@ module Regex::PCRE
String.new(LibPCRE.version)
end
- class_getter version_number : {Int32, Int32} = begin
+ class_getter version_number : {Int32, Int32} do
version = self.version
dot = version.index('.') || raise RuntimeError.new("Invalid libpcre2 version")
space = version.index(' ', dot) || raise RuntimeError.new("Invalid libpcre2 version")
@@ -36,7 +36,8 @@ module Regex::PCRE
if options.includes?(option)
flag |= case option
when .ignore_case? then LibPCRE::CASELESS
- when .multiline? then LibPCRE::DOTALL | LibPCRE::MULTILINE
+ when .multiline? then LibPCRE::MULTILINE | LibPCRE::DOTALL
+ when .multiline_only? then LibPCRE::MULTILINE
when .dotall? then LibPCRE::DOTALL
when .extended? then LibPCRE::EXTENDED
when .anchored? then LibPCRE::ANCHORED
diff --git a/src/regex/pcre2.cr b/src/regex/pcre2.cr
index da811225842f..b56a4ea68839 100644
--- a/src/regex/pcre2.cr
+++ b/src/regex/pcre2.cr
@@ -13,7 +13,7 @@ module Regex::PCRE2
end
end
- class_getter version_number : {Int32, Int32} = begin
+ class_getter version_number : {Int32, Int32} do
version = self.version
dot = version.index('.') || raise RuntimeError.new("Invalid libpcre2 version")
space = version.index(' ', dot) || raise RuntimeError.new("Invalid libpcre2 version")
@@ -67,7 +67,8 @@ module Regex::PCRE2
if options.includes?(option)
flag |= case option
when .ignore_case? then LibPCRE2::CASELESS
- when .multiline? then LibPCRE2::DOTALL | LibPCRE2::MULTILINE
+ when .multiline? then LibPCRE2::MULTILINE | LibPCRE2::DOTALL
+ when .multiline_only? then LibPCRE2::MULTILINE
when .dotall? then LibPCRE2::DOTALL
when .extended? then LibPCRE2::EXTENDED
when .anchored? then LibPCRE2::ANCHORED
diff --git a/src/set.cr b/src/set.cr
index c998fab949a1..1bcc5178fbb0 100644
--- a/src/set.cr
+++ b/src/set.cr
@@ -73,7 +73,7 @@ struct Set(T)
self
end
- # Returns `true` of this Set is comparing objects by `object_id`.
+ # Returns `true` if this Set is comparing objects by `object_id`.
#
# See `compare_by_identity`.
def compare_by_identity? : Bool
diff --git a/src/signal.cr b/src/signal.cr
index e0f59a9f57d3..37999c76b9e1 100644
--- a/src/signal.cr
+++ b/src/signal.cr
@@ -8,17 +8,17 @@ require "crystal/system/signal"
#
# ```
# puts "Ctrl+C still has the OS default action (stops the program)"
-# sleep 3
+# sleep 3.seconds
#
# Signal::INT.trap do
# puts "Gotcha!"
# end
# puts "Ctrl+C will be caught from now on"
-# sleep 3
+# sleep 3.seconds
#
# Signal::INT.reset
# puts "Ctrl+C is back to the OS default action"
-# sleep 3
+# sleep 3.seconds
# ```
#
# WARNING: An uncaught exception in a signal handler is a fatal error.
diff --git a/src/slice.cr b/src/slice.cr
index 196a29a768dd..266d7bb31249 100644
--- a/src/slice.cr
+++ b/src/slice.cr
@@ -34,14 +34,14 @@ struct Slice(T)
macro [](*args, read_only = false)
# TODO: there should be a better way to check this, probably
# asking if @type was instantiated or if T is defined
- {% if @type.name != "Slice(T)" && T < Number %}
+ {% if @type.name != "Slice(T)" && T < ::Number %}
{{T}}.slice({{args.splat(", ")}}read_only: {{read_only}})
{% else %}
- %ptr = Pointer(typeof({{args.splat}})).malloc({{args.size}})
+ %ptr = ::Pointer(typeof({{args.splat}})).malloc({{args.size}})
{% for arg, i in args %}
%ptr[{{i}}] = {{arg}}
{% end %}
- Slice.new(%ptr, {{args.size}}, read_only: {{read_only}})
+ ::Slice.new(%ptr, {{args.size}}, read_only: {{read_only}})
{% end %}
end
@@ -222,35 +222,49 @@ struct Slice(T)
end
# Returns a new slice that starts at *start* elements from this slice's start,
- # and of *count* size.
+ # and of exactly *count* size.
#
+ # Negative *start* is added to `#size`, thus it's treated as index counting
+ # from the end of the array, `-1` designating the last element.
+ #
+ # Raises `ArgumentError` if *count* is negative.
# Returns `nil` if the new slice falls outside this slice.
#
# ```
# slice = Slice.new(5) { |i| i + 10 }
# slice # => Slice[10, 11, 12, 13, 14]
#
- # slice[1, 3]? # => Slice[11, 12, 13]
- # slice[1, 33]? # => nil
+ # slice[1, 3]? # => Slice[11, 12, 13]
+ # slice[1, 33]? # => nil
+ # slice[-3, 2]? # => Slice[12, 13]
+ # slice[-3, 10]? # => nil
# ```
def []?(start : Int, count : Int) : Slice(T)?
- return unless 0 <= start <= @size
- return unless 0 <= count <= @size - start
+ # we skip the calculated count because the subslice must contain exactly
+ # *count* elements
+ start, _ = Indexable.normalize_start_and_count(start, count, size) { return }
+ return unless count <= @size - start
Slice.new(@pointer + start, count, read_only: @read_only)
end
# Returns a new slice that starts at *start* elements from this slice's start,
- # and of *count* size.
+ # and of exactly *count* size.
+ #
+ # Negative *start* is added to `#size`, thus it's treated as index counting
+ # from the end of the array, `-1` designating the last element.
#
+ # Raises `ArgumentError` if *count* is negative.
# Raises `IndexError` if the new slice falls outside this slice.
#
# ```
# slice = Slice.new(5) { |i| i + 10 }
# slice # => Slice[10, 11, 12, 13, 14]
#
- # slice[1, 3] # => Slice[11, 12, 13]
- # slice[1, 33] # raises IndexError
+ # slice[1, 3] # => Slice[11, 12, 13]
+ # slice[1, 33] # raises IndexError
+ # slice[-3, 2] # => Slice[12, 13]
+ # slice[-3, 10] # raises IndexError
# ```
def [](start : Int, count : Int) : Slice(T)
self[start, count]? || raise IndexError.new
@@ -811,6 +825,9 @@ struct Slice(T)
# Bytes[1, 2] <=> Bytes[1, 2] # => 0
# ```
def <=>(other : Slice(U)) forall U
+ # If both slices are identical references, we can skip the memory comparison.
+ return 0 if same?(other)
+
min_size = Math.min(size, other.size)
{% if T == UInt8 && U == UInt8 %}
cmp = to_unsafe.memcmp(other.to_unsafe, min_size)
@@ -833,8 +850,13 @@ struct Slice(T)
# Bytes[1, 2] == Bytes[1, 2, 3] # => false
# ```
def ==(other : Slice(U)) : Bool forall U
+ # If both slices are of different sizes, they cannot be equal.
return false if size != other.size
+ # If both slices are identical references, we can skip the memory comparison.
+ # Not using `same?` here because we have already compared sizes.
+ return true if to_unsafe == other.to_unsafe
+
{% if T == UInt8 && U == UInt8 %}
to_unsafe.memcmp(other.to_unsafe, size) == 0
{% else %}
@@ -845,6 +867,21 @@ struct Slice(T)
{% end %}
end
+ # Returns `true` if `self` and *other* point to the same memory, i.e. pointer
+ # and size are identical.
+ #
+ # ```
+ # slice = Slice[1, 2, 3]
+ # slice.same?(slice) # => true
+ # slice == Slice[1, 2, 3] # => false
+ # slice.same?(slice + 1) # => false
+ # (slice + 1).same?(slice + 1) # => true
+ # slice.same?(slice[0, 2]) # => false
+ # ```
+ def same?(other : self) : Bool
+ to_unsafe == other.to_unsafe && size == other.size
+ end
+
def to_slice : self
self
end
@@ -981,13 +1018,23 @@ struct Slice(T)
# the result could also be `[b, a]`.
#
# If stability is expendable, `#unstable_sort!` provides a performance
- # advantage over stable sort.
+ # advantage over stable sort. As an optimization, if `T` is any primitive
+ # integer type, `Char`, any enum type, any `Pointer` instance, `Symbol`, or
+ # `Time::Span`, then an unstable sort is automatically used.
#
# Raises `ArgumentError` if the comparison between any two elements returns `nil`.
def sort! : self
- Slice.merge_sort!(self)
+ # If two values `x, y : T` have the same binary representation whenever they
+ # compare equal, i.e. `x <=> y == 0` implies
+ # `pointerof(x).memcmp(pointerof(y), 1) == 0`, then swapping the two values
+ # is a no-op and therefore a stable sort isn't required
+ {% if T.union_types.size == 1 && (T <= Int::Primitive || T <= Char || T <= Enum || T <= Pointer || T <= Symbol || T <= Time::Span) %}
+ unstable_sort!
+ {% else %}
+ Slice.merge_sort!(self)
- self
+ self
+ {% end %}
end
# Sorts all elements in `self` based on the return value of the comparison
diff --git a/src/socket.cr b/src/socket.cr
index ca484c0140cc..b862c30e2f9e 100644
--- a/src/socket.cr
+++ b/src/socket.cr
@@ -419,10 +419,19 @@ class Socket < IO
self.class.fcntl fd, cmd, arg
end
+ # Finalizes the socket resource.
+ #
+ # This involves releasing the handle to the operating system, i.e. closing it.
+ # It does *not* implicitly call `#flush`, so data waiting in the buffer may be
+ # lost. By default write buffering is disabled, though (`sync? == true`).
+ # It's recommended to always close the socket explicitly via `#close`.
+ #
+ # This method is a no-op if the file descriptor has already been closed.
def finalize
return if closed?
- close rescue nil
+ Crystal::EventLoop.remove(self)
+ socket_close { } # ignore error
end
def closed? : Bool
diff --git a/src/socket/address.cr b/src/socket/address.cr
index 20fca43544e6..c07505ad43ab 100644
--- a/src/socket/address.cr
+++ b/src/socket/address.cr
@@ -21,6 +21,26 @@ class Socket
end
end
+ # :ditto:
+ def self.from(sockaddr : LibC::Sockaddr*) : Address
+ case family = Family.new(sockaddr.value.sa_family)
+ when Family::INET6
+ sockaddr = sockaddr.as(LibC::SockaddrIn6*)
+
+ IPAddress.new(sockaddr, sizeof(typeof(sockaddr)))
+ when Family::INET
+ sockaddr = sockaddr.as(LibC::SockaddrIn*)
+
+ IPAddress.new(sockaddr, sizeof(typeof(sockaddr)))
+ when Family::UNIX
+ sockaddr = sockaddr.as(LibC::SockaddrUn*)
+
+ UNIXAddress.new(sockaddr, sizeof(typeof(sockaddr)))
+ else
+ raise "Unsupported family type: #{family} (#{family.value})"
+ end
+ end
+
# Parses a `Socket::Address` from an URI.
#
# Supported formats:
@@ -113,6 +133,22 @@ class Socket
end
end
+ # :ditto:
+ def self.from(sockaddr : LibC::Sockaddr*) : IPAddress
+ case family = Family.new(sockaddr.value.sa_family)
+ when Family::INET6
+ sockaddr = sockaddr.as(LibC::SockaddrIn6*)
+
+ new(sockaddr, sizeof(typeof(sockaddr)))
+ when Family::INET
+ sockaddr = sockaddr.as(LibC::SockaddrIn*)
+
+ new(sockaddr, sizeof(typeof(sockaddr)))
+ else
+ raise "Unsupported family type: #{family} (#{family.value})"
+ end
+ end
+
# Parses a `Socket::IPAddress` from an URI.
#
# It expects the URI to include `://:` where `scheme` as
@@ -729,7 +765,8 @@ class Socket
sizeof(typeof(LibC::SockaddrUn.new.sun_path)) - 1
{% end %}
- def initialize(@path : String)
+ def initialize(path : Path | String)
+ @path = path.to_s
if @path.bytesize > MAX_PATH_SIZE
raise ArgumentError.new("Path size exceeds the maximum size of #{MAX_PATH_SIZE} bytes")
end
@@ -750,6 +787,17 @@ class Socket
{% end %}
end
+ # :ditto:
+ def self.from(sockaddr : LibC::Sockaddr*) : UNIXAddress
+ {% if flag?(:wasm32) %}
+ raise NotImplementedError.new "Socket::UNIXAddress.from"
+ {% else %}
+ sockaddr = sockaddr.as(LibC::SockaddrUn*)
+
+ new(sockaddr, sizeof(typeof(sockaddr)))
+ {% end %}
+ end
+
# Parses a `Socket::UNIXAddress` from an URI.
#
# It expects the URI to include `://` where `scheme` as well
diff --git a/src/socket/addrinfo.cr b/src/socket/addrinfo.cr
index 83ef561c88ac..411c09143411 100644
--- a/src/socket/addrinfo.cr
+++ b/src/socket/addrinfo.cr
@@ -1,17 +1,30 @@
require "uri/punycode"
require "./address"
+require "crystal/system/addrinfo"
class Socket
# Domain name resolver.
+ #
+ # # Query Concurrency Behaviour
+ #
+ # On most platforms, DNS queries are currently resolved synchronously.
+ # Calling a resolve method blocks the entire thread until it returns.
+ # This can cause latencies, especially in single-threaded processes.
+ #
+ # DNS queries resolve asynchronously on the following platforms:
+ #
+ # * Windows 8 and higher
+ #
+ # NOTE: Follow the discussion in [Async DNS resolution (#13619)](https://github.com/crystal-lang/crystal/issues/13619)
+ # for more details.
struct Addrinfo
+ include Crystal::System::Addrinfo
+
getter family : Family
getter type : Type
getter protocol : Protocol
getter size : Int32
- @addr : LibC::SockaddrIn6
- @next : LibC::Addrinfo*
-
# Resolves a domain that best matches the given options.
#
# - *domain* may be an IP address or a domain name.
@@ -23,6 +36,9 @@ class Socket
# specified.
# - *protocol* is the intended socket protocol (e.g. `Protocol::TCP`) and
# should be specified.
+ # - *timeout* is optional and specifies the maximum time to wait before
+ # `IO::TimeoutError` is raised. Currently this is only supported on
+ # Windows.
#
# Example:
# ```
@@ -34,13 +50,10 @@ class Socket
addrinfos = [] of Addrinfo
getaddrinfo(domain, service, family, type, protocol, timeout) do |addrinfo|
- loop do
- addrinfos << addrinfo.not_nil!
- unless addrinfo = addrinfo.next?
- return addrinfos
- end
- end
+ addrinfos << addrinfo
end
+
+ addrinfos
end
# Resolves a domain that best matches the given options.
@@ -57,28 +70,29 @@ class Socket
# The iteration will be stopped once the block returns something that isn't
# an `Exception` (e.g. a `Socket` or `nil`).
def self.resolve(domain : String, service, family : Family? = nil, type : Type = nil, protocol : Protocol = Protocol::IP, timeout = nil, &)
- getaddrinfo(domain, service, family, type, protocol, timeout) do |addrinfo|
- loop do
- value = yield addrinfo.not_nil!
+ exception = nil
- if value.is_a?(Exception)
- unless addrinfo = addrinfo.try(&.next?)
- if value.is_a?(Socket::ConnectError)
- raise Socket::ConnectError.from_os_error("Error connecting to '#{domain}:#{service}'", value.os_error)
- else
- {% if flag?(:win32) && compare_versions(Crystal::LLVM_VERSION, "13.0.0") < 0 %}
- # FIXME: Workaround for https://github.com/crystal-lang/crystal/issues/11047
- array = StaticArray(UInt8, 0).new(0)
- {% end %}
+ getaddrinfo(domain, service, family, type, protocol, timeout) do |addrinfo|
+ value = yield addrinfo
- raise value
- end
- end
- else
- return value
- end
+ if value.is_a?(Exception)
+ exception = value
+ else
+ return value
end
end
+
+ case exception
+ when Socket::ConnectError
+ raise Socket::ConnectError.from_os_error("Error connecting to '#{domain}:#{service}'", exception.os_error)
+ when Exception
+ {% if flag?(:win32) && compare_versions(Crystal::LLVM_VERSION, "13.0.0") < 0 %}
+ # FIXME: Workaround for https://github.com/crystal-lang/crystal/issues/11047
+ array = StaticArray(UInt8, 0).new(0)
+ {% end %}
+
+ raise exception
+ end
end
class Error < Socket::Error
@@ -109,8 +123,11 @@ class Socket
"Hostname lookup for #{domain} failed"
end
- def self.os_error_message(os_error : Errno, *, type, service, protocol, **opts)
- case os_error.value
+ def self.os_error_message(os_error : Errno | WinError, *, type, service, protocol, **opts)
+ # when `EAI_NONAME` etc. is an integer then only `os_error.value` can
+ # match; when `EAI_NONAME` is a `WinError` then `os_error` itself can
+ # match
+ case os_error.is_a?(Errno) ? os_error.value : os_error
when LibC::EAI_NONAME
"No address found"
when LibC::EAI_SOCKTYPE
@@ -118,73 +135,28 @@ class Socket
when LibC::EAI_SERVICE
"The requested service #{service} is not available for the requested socket type #{type}"
else
- {% unless flag?(:win32) %}
- # There's no need for a special win32 branch because the os_error on Windows
- # is of type WinError, which wouldn't match this overload anyways.
-
- String.new(LibC.gai_strerror(os_error.value))
+ # Win32 also has this method, but `WinError` is already sufficient
+ {% if LibC.has_method?(:gai_strerror) %}
+ if os_error.is_a?(Errno)
+ return String.new(LibC.gai_strerror(os_error))
+ end
{% end %}
+
+ super
end
end
end
private def self.getaddrinfo(domain, service, family, type, protocol, timeout, &)
- {% if flag?(:wasm32) %}
- raise NotImplementedError.new "Socket::Addrinfo.getaddrinfo"
- {% else %}
- # RFC 3986 says:
- # > When a non-ASCII registered name represents an internationalized domain name
- # > intended for resolution via the DNS, the name must be transformed to the IDNA
- # > encoding [RFC3490] prior to name lookup.
- domain = URI::Punycode.to_ascii domain
-
- hints = LibC::Addrinfo.new
- hints.ai_family = (family || Family::UNSPEC).to_i32
- hints.ai_socktype = type
- hints.ai_protocol = protocol
- hints.ai_flags = 0
-
- if service.is_a?(Int)
- hints.ai_flags |= LibC::AI_NUMERICSERV
- end
-
- # On OS X < 10.12, the libsystem implementation of getaddrinfo segfaults
- # if AI_NUMERICSERV is set, and servname is NULL or 0.
- {% if flag?(:darwin) %}
- if service.in?(0, nil) && (hints.ai_flags & LibC::AI_NUMERICSERV)
- hints.ai_flags |= LibC::AI_NUMERICSERV
- service = "00"
- end
- {% end %}
- {% if flag?(:win32) %}
- if service.is_a?(Int) && service < 0
- raise Error.from_os_error(nil, WinError::WSATYPE_NOT_FOUND, domain: domain, type: type, protocol: protocol, service: service)
- end
- {% end %}
-
- ret = LibC.getaddrinfo(domain, service.to_s, pointerof(hints), out ptr)
- unless ret.zero?
- {% if flag?(:unix) %}
- # EAI_SYSTEM is not defined on win32
- if ret == LibC::EAI_SYSTEM
- raise Error.from_os_error nil, Errno.value, domain: domain
- end
- {% end %}
-
- error = {% if flag?(:win32) %}
- WinError.new(ret.to_u32!)
- {% else %}
- Errno.new(ret)
- {% end %}
- raise Error.from_os_error(nil, error, domain: domain, type: type, protocol: protocol, service: service)
- end
-
- begin
- yield new(ptr)
- ensure
- LibC.freeaddrinfo(ptr)
- end
- {% end %}
+ # RFC 3986 says:
+ # > When a non-ASCII registered name represents an internationalized domain name
+ # > intended for resolution via the DNS, the name must be transformed to the IDNA
+ # > encoding [RFC3490] prior to name lookup.
+ domain = URI::Punycode.to_ascii domain
+
+ Crystal::System::Addrinfo.getaddrinfo(domain, service, family, type, protocol, timeout) do |addrinfo|
+ yield addrinfo
+ end
end
# Resolves *domain* for the TCP protocol and returns an `Array` of possible
@@ -197,13 +169,13 @@ class Socket
# addrinfos = Socket::Addrinfo.tcp("example.org", 80)
# ```
def self.tcp(domain : String, service, family = Family::UNSPEC, timeout = nil) : Array(Addrinfo)
- resolve(domain, service, family, Type::STREAM, Protocol::TCP)
+ resolve(domain, service, family, Type::STREAM, Protocol::TCP, timeout)
end
# Resolves a domain for the TCP protocol with STREAM type, and yields each
# possible `Addrinfo`. See `#resolve` for details.
def self.tcp(domain : String, service, family = Family::UNSPEC, timeout = nil, &)
- resolve(domain, service, family, Type::STREAM, Protocol::TCP) { |addrinfo| yield addrinfo }
+ resolve(domain, service, family, Type::STREAM, Protocol::TCP, timeout) { |addrinfo| yield addrinfo }
end
# Resolves *domain* for the UDP protocol and returns an `Array` of possible
@@ -216,39 +188,18 @@ class Socket
# addrinfos = Socket::Addrinfo.udp("example.org", 53)
# ```
def self.udp(domain : String, service, family = Family::UNSPEC, timeout = nil) : Array(Addrinfo)
- resolve(domain, service, family, Type::DGRAM, Protocol::UDP)
+ resolve(domain, service, family, Type::DGRAM, Protocol::UDP, timeout)
end
# Resolves a domain for the UDP protocol with DGRAM type, and yields each
# possible `Addrinfo`. See `#resolve` for details.
def self.udp(domain : String, service, family = Family::UNSPEC, timeout = nil, &)
- resolve(domain, service, family, Type::DGRAM, Protocol::UDP) { |addrinfo| yield addrinfo }
- end
-
- protected def initialize(addrinfo : LibC::Addrinfo*)
- @family = Family.from_value(addrinfo.value.ai_family)
- @type = Type.from_value(addrinfo.value.ai_socktype)
- @protocol = Protocol.from_value(addrinfo.value.ai_protocol)
- @size = addrinfo.value.ai_addrlen.to_i
-
- @addr = uninitialized LibC::SockaddrIn6
- @next = addrinfo.value.ai_next
-
- case @family
- when Family::INET6
- addrinfo.value.ai_addr.as(LibC::SockaddrIn6*).copy_to(pointerof(@addr).as(LibC::SockaddrIn6*), 1)
- when Family::INET
- addrinfo.value.ai_addr.as(LibC::SockaddrIn*).copy_to(pointerof(@addr).as(LibC::SockaddrIn*), 1)
- else
- # TODO: (asterite) UNSPEC and UNIX unsupported?
- end
+ resolve(domain, service, family, Type::DGRAM, Protocol::UDP, timeout) { |addrinfo| yield addrinfo }
end
- @ip_address : IPAddress?
-
# Returns an `IPAddress` matching this addrinfo.
- def ip_address : Socket::IPAddress
- @ip_address ||= IPAddress.from(to_unsafe, size)
+ getter(ip_address : Socket::IPAddress) do
+ system_ip_address
end
def inspect(io : IO)
@@ -259,15 +210,5 @@ class Socket
io << protocol
io << ")"
end
-
- def to_unsafe
- pointerof(@addr).as(LibC::Sockaddr*)
- end
-
- protected def next?
- if addrinfo = @next
- Addrinfo.new(addrinfo)
- end
- end
end
end
diff --git a/src/socket/tcp_socket.cr b/src/socket/tcp_socket.cr
index 387417211a1a..4edcb3d08e5f 100644
--- a/src/socket/tcp_socket.cr
+++ b/src/socket/tcp_socket.cr
@@ -25,7 +25,7 @@ class TCPSocket < IPSocket
# connection time to the remote server with `connect_timeout`. Both values
# must be in seconds (integers or floats).
#
- # Note that `dns_timeout` is currently ignored.
+ # NOTE: *dns_timeout* is currently only supported on Windows.
def initialize(host : String, port, dns_timeout = nil, connect_timeout = nil, blocking = false)
Addrinfo.tcp(host, port, timeout: dns_timeout) do |addrinfo|
super(addrinfo.family, addrinfo.type, addrinfo.protocol, blocking)
diff --git a/src/socket/unix_server.cr b/src/socket/unix_server.cr
index 75195a09ff70..e2f9b07b6157 100644
--- a/src/socket/unix_server.cr
+++ b/src/socket/unix_server.cr
@@ -37,7 +37,8 @@ class UNIXServer < UNIXSocket
# ```
#
# [Only the stream type is supported on Windows](https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/#unsupportedunavailable).
- def initialize(@path : String, type : Type = Type::STREAM, backlog : Int = 128)
+ def initialize(path : Path | String, type : Type = Type::STREAM, backlog : Int = 128)
+ @path = path = path.to_s
super(Family::UNIX, type)
system_bind(UNIXAddress.new(path), path) do |error|
@@ -53,15 +54,16 @@ class UNIXServer < UNIXSocket
end
# Creates a UNIXServer from an already configured raw file descriptor
- def initialize(*, fd : Handle, type : Type = Type::STREAM, @path : String? = nil)
- super(fd: fd, type: type, path: @path)
+ def initialize(*, fd : Handle, type : Type = Type::STREAM, path : Path | String? = nil)
+ @path = path = path.to_s
+ super(fd: fd, type: type, path: path)
end
# Creates a new UNIX server and yields it to the block. Eventually closes the
# server socket when the block returns.
#
# Returns the value of the block.
- def self.open(path, type : Type = Type::STREAM, backlog = 128, &)
+ def self.open(path : Path | String, type : Type = Type::STREAM, backlog = 128, &)
server = new(path, type, backlog)
begin
yield server
diff --git a/src/socket/unix_socket.cr b/src/socket/unix_socket.cr
index 201fd8410bf7..914a2a62fd1d 100644
--- a/src/socket/unix_socket.cr
+++ b/src/socket/unix_socket.cr
@@ -18,7 +18,8 @@ class UNIXSocket < Socket
getter path : String?
# Connects a named UNIX socket, bound to a filesystem pathname.
- def initialize(@path : String, type : Type = Type::STREAM)
+ def initialize(path : Path | String, type : Type = Type::STREAM)
+ @path = path = path.to_s
super(Family::UNIX, type, Protocol::IP)
connect(UNIXAddress.new(path)) do |error|
@@ -32,7 +33,8 @@ class UNIXSocket < Socket
end
# Creates a UNIXSocket from an already configured raw file descriptor
- def initialize(*, fd : Handle, type : Type = Type::STREAM, @path : String? = nil)
+ def initialize(*, fd : Handle, type : Type = Type::STREAM, path : Path | String? = nil)
+ @path = path.to_s
super fd, Family::UNIX, type, Protocol::IP
end
@@ -40,7 +42,7 @@ class UNIXSocket < Socket
# eventually closes the socket when the block returns.
#
# Returns the value of the block.
- def self.open(path, type : Type = Type::STREAM, &)
+ def self.open(path : Path | String, type : Type = Type::STREAM, &)
sock = new(path, type)
begin
yield sock
@@ -97,8 +99,8 @@ class UNIXSocket < Socket
UNIXAddress.new(path.to_s)
end
- def receive
- bytes_read, sockaddr, addrlen = recvfrom
- {bytes_read, UNIXAddress.from(sockaddr, addrlen)}
+ def receive(max_message_size = 512) : {String, UNIXAddress}
+ message, address = super(max_message_size)
+ {message, address.as(UNIXAddress)}
end
end
diff --git a/src/spec/dsl.cr b/src/spec/dsl.cr
index 578076b86d69..d712aa59da4f 100644
--- a/src/spec/dsl.cr
+++ b/src/spec/dsl.cr
@@ -298,8 +298,8 @@ module Spec
# If the "log" module is required it is configured to emit no entries by default.
def log_setup
defined?(::Log) do
- if Log.responds_to?(:setup)
- Log.setup_from_env(default_level: :none)
+ if ::Log.responds_to?(:setup)
+ ::Log.setup_from_env(default_level: :none)
end
end
end
diff --git a/src/spec/expectations.cr b/src/spec/expectations.cr
index ac93de54975e..f50658a5d787 100644
--- a/src/spec/expectations.cr
+++ b/src/spec/expectations.cr
@@ -65,11 +65,21 @@ module Spec
end
def failure_message(actual_value)
- "Expected: #{@expected_value.pretty_inspect} (object_id: #{@expected_value.object_id})\n got: #{actual_value.pretty_inspect} (object_id: #{actual_value.object_id})"
+ "Expected: #{@expected_value.pretty_inspect} (#{identify(@expected_value)})\n got: #{actual_value.pretty_inspect} (#{identify(actual_value)})"
end
def negative_failure_message(actual_value)
- "Expected: value.same? #{@expected_value.pretty_inspect} (object_id: #{@expected_value.object_id})\n got: #{actual_value.pretty_inspect} (object_id: #{actual_value.object_id})"
+ "Expected: #{@expected_value.pretty_inspect} (#{identify(@expected_value)})\n got: #{actual_value.pretty_inspect} (#{identify(actual_value)})"
+ end
+
+ private def identify(value)
+ if value.responds_to?(:to_unsafe)
+ if !value.responds_to?(:object_id)
+ return value.to_unsafe
+ end
+ end
+
+ "object_id: #{value.object_id}"
end
end
diff --git a/src/spec/helpers/iterate.cr b/src/spec/helpers/iterate.cr
index be302ebb49c2..7a70f83408ca 100644
--- a/src/spec/helpers/iterate.cr
+++ b/src/spec/helpers/iterate.cr
@@ -47,7 +47,7 @@ module Spec::Methods
# See `.it_iterates` for details.
macro assert_iterates_yielding(expected, method, *, infinite = false, tuple = false)
%remaining = ({{expected}}).size
- %ary = [] of typeof(Enumerable.element_type({{ expected }}))
+ %ary = [] of typeof(::Enumerable.element_type({{ expected }}))
{{ method.id }} do |{% if tuple %}*{% end %}x|
if %remaining == 0
if {{ infinite }}
@@ -73,11 +73,11 @@ module Spec::Methods
#
# See `.it_iterates` for details.
macro assert_iterates_iterator(expected, method, *, infinite = false)
- %ary = [] of typeof(Enumerable.element_type({{ expected }}))
+ %ary = [] of typeof(::Enumerable.element_type({{ expected }}))
%iter = {{ method.id }}
({{ expected }}).size.times do
%v = %iter.next
- if %v.is_a?(Iterator::Stop)
+ if %v.is_a?(::Iterator::Stop)
# Compare the actual value directly. Since there are less
# then expected values, the expectation will fail and raise.
%ary.should eq({{ expected }})
@@ -86,7 +86,7 @@ module Spec::Methods
%ary << %v
end
unless {{ infinite }}
- %iter.next.should be_a(Iterator::Stop)
+ %iter.next.should be_a(::Iterator::Stop)
end
%ary.should eq({{ expected }})
diff --git a/src/static_array.cr b/src/static_array.cr
index 2c09e21df166..3d00705bc21a 100644
--- a/src/static_array.cr
+++ b/src/static_array.cr
@@ -50,7 +50,7 @@ struct StaticArray(T, N)
# * `Number.static_array` is a convenient alternative for designating a
# specific numerical item type.
macro [](*args)
- %array = uninitialized StaticArray(typeof({{args.splat}}), {{args.size}})
+ %array = uninitialized ::StaticArray(typeof({{args.splat}}), {{args.size}})
{% for arg, i in args %}
%array.to_unsafe[{{i}}] = {{arg}}
{% end %}
diff --git a/src/string.cr b/src/string.cr
index d3bc7d6998b2..9bc9d0c22701 100644
--- a/src/string.cr
+++ b/src/string.cr
@@ -1,9 +1,9 @@
-require "c/stdlib"
require "c/string"
require "crystal/small_deque"
{% unless flag?(:without_iconv) %}
require "crystal/iconv"
{% end %}
+require "float/fast_float"
# A `String` represents an immutable sequence of UTF-8 characters.
#
@@ -317,7 +317,9 @@ class String
# * **whitespace**: if `true`, leading and trailing whitespaces are allowed
# * **underscore**: if `true`, underscores in numbers are allowed
# * **prefix**: if `true`, the prefixes `"0x"`, `"0o"` and `"0b"` override the base
- # * **strict**: if `true`, extraneous characters past the end of the number are disallowed
+ # * **strict**: if `true`, extraneous characters past the end of the number
+ # are disallowed, unless **whitespace** is also `true` and all the trailing
+ # characters past the number are whitespaces
# * **leading_zero_is_octal**: if `true`, then a number prefixed with `"0"` will be treated as an octal
#
# ```
@@ -692,7 +694,9 @@ class String
#
# Options:
# * **whitespace**: if `true`, leading and trailing whitespaces are allowed
- # * **strict**: if `true`, extraneous characters past the end of the number are disallowed
+ # * **strict**: if `true`, extraneous characters past the end of the number
+ # are disallowed, unless **whitespace** is also `true` and all the trailing
+ # characters past the number are whitespaces
#
# ```
# "123.45e1".to_f # => 1234.5
@@ -717,7 +721,9 @@ class String
#
# Options:
# * **whitespace**: if `true`, leading and trailing whitespaces are allowed
- # * **strict**: if `true`, extraneous characters past the end of the number are disallowed
+ # * **strict**: if `true`, extraneous characters past the end of the number
+ # are disallowed, unless **whitespace** is also `true` and all the trailing
+ # characters past the number are whitespaces
#
# ```
# "123.45e1".to_f? # => 1234.5
@@ -732,10 +738,7 @@ class String
# :ditto:
def to_f64?(whitespace : Bool = true, strict : Bool = true) : Float64?
- to_f_impl(whitespace: whitespace, strict: strict) do
- v = LibC.strtod self, out endptr
- {v, endptr}
- end
+ Float::FastFloat.to_f64?(self, whitespace, strict)
end
# Same as `#to_f` but returns a Float32.
@@ -745,58 +748,7 @@ class String
# Same as `#to_f?` but returns a Float32.
def to_f32?(whitespace : Bool = true, strict : Bool = true) : Float32?
- to_f_impl(whitespace: whitespace, strict: strict) do
- v = LibC.strtof self, out endptr
- {v, endptr}
- end
- end
-
- private def to_f_impl(whitespace : Bool = true, strict : Bool = true, &)
- return unless whitespace || '0' <= self[0] <= '9' || self[0].in?('-', '+', 'i', 'I', 'n', 'N')
-
- v, endptr = yield
-
- unless v.finite?
- startptr = to_unsafe
- if whitespace
- while startptr.value.unsafe_chr.ascii_whitespace?
- startptr += 1
- end
- end
- if startptr.value.unsafe_chr.in?('+', '-')
- startptr += 1
- end
-
- if v.nan?
- return unless startptr.value.unsafe_chr.in?('n', 'N')
- else
- return unless startptr.value.unsafe_chr.in?('i', 'I')
- end
- end
-
- string_end = to_unsafe + bytesize
-
- # blank string
- return if endptr == to_unsafe
-
- if strict
- if whitespace
- while endptr < string_end && endptr.value.unsafe_chr.ascii_whitespace?
- endptr += 1
- end
- end
- # reached the end of the string
- v if endptr == string_end
- else
- ptr = to_unsafe
- if whitespace
- while ptr < string_end && ptr.value.unsafe_chr.ascii_whitespace?
- ptr += 1
- end
- end
- # consumed some bytes
- v if endptr > ptr
- end
+ Float::FastFloat.to_f32?(self, whitespace, strict)
end
# Returns the `Char` at the given *index*.
@@ -1506,15 +1458,17 @@ class String
end
end
- # Returns a new `String` with the first letter after any space converted to uppercase and every
- # other letter converted to lowercase.
+ # Returns a new `String` with the first letter after any space converted to uppercase and every other letter converted to lowercase.
+ # Optionally, if *underscore_to_space* is `true`, underscores (`_`) will be converted to a space and the following letter converted to uppercase.
#
# ```
- # "hEllO tAb\tworld".titleize # => "Hello Tab\tWorld"
- # " spaces before".titleize # => " Spaces Before"
- # "x-men: the last stand".titleize # => "X-men: The Last Stand"
+ # "hEllO tAb\tworld".titleize # => "Hello Tab\tWorld"
+ # " spaces before".titleize # => " Spaces Before"
+ # "x-men: the last stand".titleize # => "X-men: The Last Stand"
+ # "foo_bar".titleize # => "Foo_bar"
+ # "foo_bar".titleize(underscore_to_space: true) # => "Foo Bar"
# ```
- def titleize(options : Unicode::CaseOptions = :none) : String
+ def titleize(options : Unicode::CaseOptions = :none, *, underscore_to_space : Bool = false) : String
return self if empty?
if single_byte_optimizable? && (options.none? || options.ascii?)
@@ -1525,9 +1479,15 @@ class String
byte = to_unsafe[i]
if byte < 0x80
char = byte.unsafe_chr
- replaced_char = upcase_next ? char.upcase : char.downcase
+ replaced_char, upcase_next = if upcase_next
+ {char.upcase, false}
+ elsif underscore_to_space && '_' == char
+ {' ', true}
+ else
+ {char.downcase, char.ascii_whitespace?}
+ end
+
buffer[i] = replaced_char.ord.to_u8!
- upcase_next = char.ascii_whitespace?
else
buffer[i] = byte
upcase_next = false
@@ -1537,26 +1497,31 @@ class String
end
end
- String.build(bytesize) { |io| titleize io, options }
+ String.build(bytesize) { |io| titleize io, options, underscore_to_space: underscore_to_space }
end
# Writes a titleized version of `self` to the given *io*.
+ # Optionally, if *underscore_to_space* is `true`, underscores (`_`) will be converted to a space and the following letter converted to uppercase.
#
# ```
# io = IO::Memory.new
# "x-men: the last stand".titleize io
# io.to_s # => "X-men: The Last Stand"
# ```
- def titleize(io : IO, options : Unicode::CaseOptions = :none) : Nil
+ def titleize(io : IO, options : Unicode::CaseOptions = :none, *, underscore_to_space : Bool = false) : Nil
upcase_next = true
each_char_with_index do |char, i|
if upcase_next
+ upcase_next = false
char.titlecase(options) { |c| io << c }
+ elsif underscore_to_space && '_' == char
+ upcase_next = true
+ io << ' '
else
+ upcase_next = char.whitespace?
char.downcase(options) { |c| io << c }
end
- upcase_next = char.whitespace?
end
end
@@ -1647,12 +1612,12 @@ class String
case to_unsafe[bytesize - 1]
when '\n'
if bytesize > 1 && to_unsafe[bytesize - 2] === '\r'
- unsafe_byte_slice_string(0, bytesize - 2)
+ unsafe_byte_slice_string(0, bytesize - 2, @length > 0 ? @length - 2 : 0)
else
- unsafe_byte_slice_string(0, bytesize - 1)
+ unsafe_byte_slice_string(0, bytesize - 1, @length > 0 ? @length - 1 : 0)
end
when '\r'
- unsafe_byte_slice_string(0, bytesize - 1)
+ unsafe_byte_slice_string(0, bytesize - 1, @length > 0 ? @length - 1 : 0)
else
self
end
@@ -1784,11 +1749,7 @@ class String
def rchop? : String?
return if empty?
- if to_unsafe[bytesize - 1] < 0x80 || single_byte_optimizable?
- return unsafe_byte_slice_string(0, bytesize - 1)
- end
-
- self[0, size - 1]
+ unsafe_byte_slice_string(0, Char::Reader.new(at_end: self).pos, @length > 0 ? @length - 1 : 0)
end
# Returns a new `String` with *suffix* removed from the end of the string if possible, else returns `nil`.
@@ -2150,7 +2111,8 @@ class String
remove_excess_left(excess_left)
end
- private def calc_excess_right
+ # :nodoc:
+ def calc_excess_right
if single_byte_optimizable?
i = bytesize - 1
while i >= 0 && to_unsafe[i].unsafe_chr.ascii_whitespace?
@@ -2188,7 +2150,8 @@ class String
bytesize - byte_index
end
- private def calc_excess_left
+ # :nodoc:
+ def calc_excess_left
if single_byte_optimizable?
excess_left = 0
# All strings end with '\0', and it's not a whitespace
@@ -3072,8 +3035,18 @@ class String
# "abcdef".compare("ABCDEF", case_insensitive: true) == 0 # => true
# ```
def ==(other : self) : Bool
+ # Quick pointer comparison if both strings are identical references
return true if same?(other)
- return false unless bytesize == other.bytesize
+
+ # If the bytesize differs, they cannot be equal
+ return false if bytesize != other.bytesize
+
+ # If the character size of both strings differs, they cannot be equal.
+ # We need to exclude the case that @length of either string might not have
+ # been calculated (indicated by `0`).
+ return false if @length != other.@length && @length != 0 && other.@length != 0
+
+ # All meta data matches up, so we need to compare byte-by-byte.
to_unsafe.memcmp(other.to_unsafe, bytesize) == 0
end
@@ -3335,11 +3308,21 @@ class String
def index(search : Char, offset = 0) : Int32?
# If it's ASCII we can delegate to slice
if single_byte_optimizable?
- # With `single_byte_optimizable?` there are only ASCII characters and invalid UTF-8 byte
- # sequences and we can immediately reject any non-ASCII codepoint.
- return unless search.ascii?
+ # With `single_byte_optimizable?` there are only ASCII characters and
+ # invalid UTF-8 byte sequences, and we can reject anything that is neither
+ # ASCII nor the replacement character.
+ case search
+ when .ascii?
+ return to_slice.fast_index(search.ord.to_u8!, offset)
+ when Char::REPLACEMENT
+ offset.upto(bytesize - 1) do |i|
+ if to_unsafe[i] >= 0x80
+ return i.to_i
+ end
+ end
+ end
- return to_slice.fast_index(search.ord.to_u8, offset)
+ return nil
end
offset += size if offset < 0
@@ -3449,17 +3432,27 @@ class String
# ```
# "Hello, World".rindex('o') # => 8
# "Hello, World".rindex('Z') # => nil
- # "Hello, World".rindex("o", 5) # => 4
- # "Hello, World".rindex("W", 2) # => nil
+ # "Hello, World".rindex('o', 5) # => 4
+ # "Hello, World".rindex('W', 2) # => nil
# ```
def rindex(search : Char, offset = size - 1)
# If it's ASCII we can delegate to slice
if single_byte_optimizable?
- # With `single_byte_optimizable?` there are only ASCII characters and invalid UTF-8 byte
- # sequences and we can immediately reject any non-ASCII codepoint.
- return unless search.ascii?
+ # With `single_byte_optimizable?` there are only ASCII characters and
+ # invalid UTF-8 byte sequences, and we can reject anything that is neither
+ # ASCII nor the replacement character.
+ case search
+ when .ascii?
+ return to_slice.rindex(search.ord.to_u8!, offset)
+ when Char::REPLACEMENT
+ offset.downto(0) do |i|
+ if to_unsafe[i] >= 0x80
+ return i.to_i
+ end
+ end
+ end
- return to_slice.rindex(search.ord.to_u8, offset)
+ return nil
end
offset += size if offset < 0
@@ -3485,7 +3478,16 @@ class String
end
end
- # :ditto:
+ # Returns the index of the _last_ appearance of *search* in the string,
+ # If *offset* is present, it defines the position to _end_ the search
+ # (characters beyond this point are ignored).
+ #
+ # ```
+ # "Hello, World".rindex("orld") # => 8
+ # "Hello, World".rindex("snorlax") # => nil
+ # "Hello, World".rindex("o", 5) # => 4
+ # "Hello, World".rindex("W", 2) # => nil
+ # ```
def rindex(search : String, offset = size - search.size) : Int32?
offset += size if offset < 0
return if offset < 0
@@ -3538,7 +3540,16 @@ class String
end
end
- # :ditto:
+ # Returns the index of the _last_ appearance of *search* in the string,
+ # If *offset* is present, it defines the position to _end_ the search
+ # (characters beyond this point are ignored).
+ #
+ # ```
+ # "Hello, World".rindex(/world/i) # => 7
+ # "Hello, World".rindex(/world/) # => nil
+ # "Hello, World".rindex(/o/, 5) # => 4
+ # "Hello, World".rindex(/W/, 2) # => nil
+ # ```
def rindex(search : Regex, offset = size, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : Int32?
offset += size if offset < 0
return nil unless 0 <= offset <= size
@@ -3552,21 +3563,49 @@ class String
match_result.try &.begin
end
- # :ditto:
- #
+ # Returns the index of the _last_ appearance of *search* in the string,
+ # If *offset* is present, it defines the position to _end_ the search
+ # (characters beyond this point are ignored).
# Raises `Enumerable::NotFoundError` if *search* does not occur in `self`.
- def rindex!(search : Regex, offset = size, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : Int32
- rindex(search, offset, options: options) || raise Enumerable::NotFoundError.new
+ #
+ # ```
+ # "Hello, World".rindex!('o') # => 8
+ # "Hello, World".rindex!('Z') # raises Enumerable::NotFoundError
+ # "Hello, World".rindex!('o', 5) # => 4
+ # "Hello, World".rindex!('W', 2) # raises Enumerable::NotFoundError
+ # ```
+ def rindex!(search : Char, offset = size - 1) : Int32
+ rindex(search, offset) || raise Enumerable::NotFoundError.new
end
- # :ditto:
+ # Returns the index of the _last_ appearance of *search* in the string,
+ # If *offset* is present, it defines the position to _end_ the search
+ # (characters beyond this point are ignored).
+ # Raises `Enumerable::NotFoundError` if *search* does not occur in `self`.
+ #
+ # ```
+ # "Hello, World".rindex!("orld") # => 8
+ # "Hello, World".rindex!("snorlax") # raises Enumerable::NotFoundError
+ # "Hello, World".rindex!("o", 5) # => 4
+ # "Hello, World".rindex!("W", 2) # raises Enumerable::NotFoundError
+ # ```
def rindex!(search : String, offset = size - search.size) : Int32
rindex(search, offset) || raise Enumerable::NotFoundError.new
end
- # :ditto:
- def rindex!(search : Char, offset = size - 1) : Int32
- rindex(search, offset) || raise Enumerable::NotFoundError.new
+ # Returns the index of the _last_ appearance of *search* in the string,
+ # If *offset* is present, it defines the position to _end_ the search
+ # (characters beyond this point are ignored).
+ # Raises `Enumerable::NotFoundError` if *search* does not occur in `self`.
+ #
+ # ```
+ # "Hello, World".rindex!(/world/i) # => 7
+ # "Hello, World".rindex!(/world/) # raises Enumerable::NotFoundError
+ # "Hello, World".rindex!(/o/, 5) # => 4
+ # "Hello, World".rindex!(/W/, 2) # raises Enumerable::NotFoundError
+ # ```
+ def rindex!(search : Regex, offset = size, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : Int32
+ rindex(search, offset, options: options) || raise Enumerable::NotFoundError.new
end
# Searches separator or pattern (`Regex`) in the string, and returns
@@ -3681,7 +3720,7 @@ class String
# "Dizzy Miss Lizzy".byte_index('z'.ord, -4) # => 13
# "Dizzy Miss Lizzy".byte_index('z'.ord, -17) # => nil
# ```
- def byte_index(byte : Int, offset = 0) : Int32?
+ def byte_index(byte : Int, offset : Int32 = 0) : Int32?
offset += bytesize if offset < 0
return if offset < 0
@@ -3794,6 +3833,27 @@ class String
nil
end
+ # Returns the byte index of the regex *pattern* in the string, or `nil` if the pattern does not find a match.
+ # If *offset* is present, it defines the position to start the search.
+ #
+ # Negative *offset* can be used to start the search from the end of the string.
+ #
+ # ```
+ # "hello world".byte_index(/o/) # => 4
+ # "hello world".byte_index(/o/, offset: 4) # => 4
+ # "hello world".byte_index(/o/, offset: 5) # => 7
+ # "hello world".byte_index(/o/, offset: -1) # => nil
+ # "hello world".byte_index(/y/) # => nil
+ # ```
+ def byte_index(pattern : Regex, offset = 0, options : Regex::MatchOptions = Regex::MatchOptions::None) : Int32?
+ offset += bytesize if offset < 0
+ return if offset < 0
+
+ if match = pattern.match_at_byte_index(self, offset, options: options)
+ match.byte_begin
+ end
+ end
+
# Returns the byte index of a char index, or `nil` if out of bounds.
#
# It is valid to pass `#size` to *index*, and in this case the answer
@@ -5472,12 +5532,12 @@ class String
Slice.new(to_unsafe + byte_offset, bytesize - byte_offset, read_only: true)
end
- protected def unsafe_byte_slice_string(byte_offset)
- String.new(unsafe_byte_slice(byte_offset))
+ protected def unsafe_byte_slice_string(byte_offset, *, size = 0)
+ String.new(to_unsafe + byte_offset, bytesize - byte_offset, size)
end
- protected def unsafe_byte_slice_string(byte_offset, count)
- String.new(unsafe_byte_slice(byte_offset, count))
+ protected def unsafe_byte_slice_string(byte_offset, count, size = 0)
+ String.new(to_unsafe + byte_offset, count, size)
end
protected def self.char_bytes_and_bytesize(char : Char)
diff --git a/src/string/formatter.cr b/src/string/formatter.cr
index 60da55a2601f..347d65bcb340 100644
--- a/src/string/formatter.cr
+++ b/src/string/formatter.cr
@@ -248,6 +248,12 @@ struct String::Formatter(A)
end
def char(flags, arg) : Nil
+ if arg.is_a?(Int::Primitive)
+ arg = arg.chr
+ end
+ unless arg.is_a?(Char)
+ raise ArgumentError.new("Expected a char or integer, not #{arg.inspect}")
+ end
pad 1, flags if flags.left_padding?
@io << arg
pad 1, flags if flags.right_padding?
diff --git a/src/string/grapheme/properties.cr b/src/string/grapheme/properties.cr
index 65b51fba0935..4d87254b7600 100644
--- a/src/string/grapheme/properties.cr
+++ b/src/string/grapheme/properties.cr
@@ -58,9 +58,9 @@ struct String::Grapheme
# ranges in this slice are numerically sorted.
#
# These ranges were taken from
- # http://www.unicode.org/Public/15.1.0/ucd/auxiliary/GraphemeBreakProperty.txt
+ # http://www.unicode.org/Public/16.0.0/ucd/auxiliary/GraphemeBreakProperty.txt
# as well as
- # http://www.unicode.org/Public/15.1.0/ucd/emoji/emoji-data.txt
+ # http://www.unicode.org/Public/16.0.0/ucd/emoji/emoji-data.txt
# ("Extended_Pictographic" only). See
# https://www.unicode.org/license.html for the Unicode license agreement.
@@codepoints : Array(Tuple(Int32, Int32, Property))?
@@ -68,7 +68,7 @@ struct String::Grapheme
# :nodoc:
protected def self.codepoints
@@codepoints ||= begin
- data = Array(Tuple(Int32, Int32, Property)).new(1447)
+ data = Array(Tuple(Int32, Int32, Property)).new(1452)
put(data, {0x0000, 0x0009, Property::Control})
put(data, {0x000A, 0x000A, Property::LF})
put(data, {0x000B, 0x000C, Property::Control})
@@ -105,7 +105,7 @@ struct String::Grapheme
put(data, {0x0829, 0x082D, Property::Extend})
put(data, {0x0859, 0x085B, Property::Extend})
put(data, {0x0890, 0x0891, Property::Prepend})
- put(data, {0x0898, 0x089F, Property::Extend})
+ put(data, {0x0897, 0x089F, Property::Extend})
put(data, {0x08CA, 0x08E1, Property::Extend})
put(data, {0x08E2, 0x08E2, Property::Prepend})
put(data, {0x08E3, 0x0902, Property::Extend})
@@ -187,14 +187,12 @@ struct String::Grapheme
put(data, {0x0C82, 0x0C83, Property::SpacingMark})
put(data, {0x0CBC, 0x0CBC, Property::Extend})
put(data, {0x0CBE, 0x0CBE, Property::SpacingMark})
- put(data, {0x0CBF, 0x0CBF, Property::Extend})
- put(data, {0x0CC0, 0x0CC1, Property::SpacingMark})
+ put(data, {0x0CBF, 0x0CC0, Property::Extend})
+ put(data, {0x0CC1, 0x0CC1, Property::SpacingMark})
put(data, {0x0CC2, 0x0CC2, Property::Extend})
put(data, {0x0CC3, 0x0CC4, Property::SpacingMark})
- put(data, {0x0CC6, 0x0CC6, Property::Extend})
- put(data, {0x0CC7, 0x0CC8, Property::SpacingMark})
- put(data, {0x0CCA, 0x0CCB, Property::SpacingMark})
- put(data, {0x0CCC, 0x0CCD, Property::Extend})
+ put(data, {0x0CC6, 0x0CC8, Property::Extend})
+ put(data, {0x0CCA, 0x0CCD, Property::Extend})
put(data, {0x0CD5, 0x0CD6, Property::Extend})
put(data, {0x0CE2, 0x0CE3, Property::Extend})
put(data, {0x0CF3, 0x0CF3, Property::SpacingMark})
@@ -259,10 +257,8 @@ struct String::Grapheme
put(data, {0x1160, 0x11A7, Property::V})
put(data, {0x11A8, 0x11FF, Property::T})
put(data, {0x135D, 0x135F, Property::Extend})
- put(data, {0x1712, 0x1714, Property::Extend})
- put(data, {0x1715, 0x1715, Property::SpacingMark})
- put(data, {0x1732, 0x1733, Property::Extend})
- put(data, {0x1734, 0x1734, Property::SpacingMark})
+ put(data, {0x1712, 0x1715, Property::Extend})
+ put(data, {0x1732, 0x1734, Property::Extend})
put(data, {0x1752, 0x1753, Property::Extend})
put(data, {0x1772, 0x1773, Property::Extend})
put(data, {0x17B4, 0x17B5, Property::Extend})
@@ -302,29 +298,23 @@ struct String::Grapheme
put(data, {0x1AB0, 0x1ACE, Property::Extend})
put(data, {0x1B00, 0x1B03, Property::Extend})
put(data, {0x1B04, 0x1B04, Property::SpacingMark})
- put(data, {0x1B34, 0x1B3A, Property::Extend})
- put(data, {0x1B3B, 0x1B3B, Property::SpacingMark})
- put(data, {0x1B3C, 0x1B3C, Property::Extend})
- put(data, {0x1B3D, 0x1B41, Property::SpacingMark})
- put(data, {0x1B42, 0x1B42, Property::Extend})
- put(data, {0x1B43, 0x1B44, Property::SpacingMark})
+ put(data, {0x1B34, 0x1B3D, Property::Extend})
+ put(data, {0x1B3E, 0x1B41, Property::SpacingMark})
+ put(data, {0x1B42, 0x1B44, Property::Extend})
put(data, {0x1B6B, 0x1B73, Property::Extend})
put(data, {0x1B80, 0x1B81, Property::Extend})
put(data, {0x1B82, 0x1B82, Property::SpacingMark})
put(data, {0x1BA1, 0x1BA1, Property::SpacingMark})
put(data, {0x1BA2, 0x1BA5, Property::Extend})
put(data, {0x1BA6, 0x1BA7, Property::SpacingMark})
- put(data, {0x1BA8, 0x1BA9, Property::Extend})
- put(data, {0x1BAA, 0x1BAA, Property::SpacingMark})
- put(data, {0x1BAB, 0x1BAD, Property::Extend})
+ put(data, {0x1BA8, 0x1BAD, Property::Extend})
put(data, {0x1BE6, 0x1BE6, Property::Extend})
put(data, {0x1BE7, 0x1BE7, Property::SpacingMark})
put(data, {0x1BE8, 0x1BE9, Property::Extend})
put(data, {0x1BEA, 0x1BEC, Property::SpacingMark})
put(data, {0x1BED, 0x1BED, Property::Extend})
put(data, {0x1BEE, 0x1BEE, Property::SpacingMark})
- put(data, {0x1BEF, 0x1BF1, Property::Extend})
- put(data, {0x1BF2, 0x1BF3, Property::SpacingMark})
+ put(data, {0x1BEF, 0x1BF3, Property::Extend})
put(data, {0x1C24, 0x1C2B, Property::SpacingMark})
put(data, {0x1C2C, 0x1C33, Property::Extend})
put(data, {0x1C34, 0x1C35, Property::SpacingMark})
@@ -416,7 +406,8 @@ struct String::Grapheme
put(data, {0xA8FF, 0xA8FF, Property::Extend})
put(data, {0xA926, 0xA92D, Property::Extend})
put(data, {0xA947, 0xA951, Property::Extend})
- put(data, {0xA952, 0xA953, Property::SpacingMark})
+ put(data, {0xA952, 0xA952, Property::SpacingMark})
+ put(data, {0xA953, 0xA953, Property::Extend})
put(data, {0xA960, 0xA97C, Property::L})
put(data, {0xA980, 0xA982, Property::Extend})
put(data, {0xA983, 0xA983, Property::SpacingMark})
@@ -425,7 +416,8 @@ struct String::Grapheme
put(data, {0xA9B6, 0xA9B9, Property::Extend})
put(data, {0xA9BA, 0xA9BB, Property::SpacingMark})
put(data, {0xA9BC, 0xA9BD, Property::Extend})
- put(data, {0xA9BE, 0xA9C0, Property::SpacingMark})
+ put(data, {0xA9BE, 0xA9BF, Property::SpacingMark})
+ put(data, {0xA9C0, 0xA9C0, Property::Extend})
put(data, {0xA9E5, 0xA9E5, Property::Extend})
put(data, {0xAA29, 0xAA2E, Property::Extend})
put(data, {0xAA2F, 0xAA30, Property::SpacingMark})
@@ -1269,8 +1261,9 @@ struct String::Grapheme
put(data, {0x10A3F, 0x10A3F, Property::Extend})
put(data, {0x10AE5, 0x10AE6, Property::Extend})
put(data, {0x10D24, 0x10D27, Property::Extend})
+ put(data, {0x10D69, 0x10D6D, Property::Extend})
put(data, {0x10EAB, 0x10EAC, Property::Extend})
- put(data, {0x10EFD, 0x10EFF, Property::Extend})
+ put(data, {0x10EFC, 0x10EFF, Property::Extend})
put(data, {0x10F46, 0x10F50, Property::Extend})
put(data, {0x10F82, 0x10F85, Property::Extend})
put(data, {0x11000, 0x11000, Property::SpacingMark})
@@ -1298,7 +1291,8 @@ struct String::Grapheme
put(data, {0x11182, 0x11182, Property::SpacingMark})
put(data, {0x111B3, 0x111B5, Property::SpacingMark})
put(data, {0x111B6, 0x111BE, Property::Extend})
- put(data, {0x111BF, 0x111C0, Property::SpacingMark})
+ put(data, {0x111BF, 0x111BF, Property::SpacingMark})
+ put(data, {0x111C0, 0x111C0, Property::Extend})
put(data, {0x111C2, 0x111C3, Property::Prepend})
put(data, {0x111C9, 0x111CC, Property::Extend})
put(data, {0x111CE, 0x111CE, Property::SpacingMark})
@@ -1306,9 +1300,7 @@ struct String::Grapheme
put(data, {0x1122C, 0x1122E, Property::SpacingMark})
put(data, {0x1122F, 0x11231, Property::Extend})
put(data, {0x11232, 0x11233, Property::SpacingMark})
- put(data, {0x11234, 0x11234, Property::Extend})
- put(data, {0x11235, 0x11235, Property::SpacingMark})
- put(data, {0x11236, 0x11237, Property::Extend})
+ put(data, {0x11234, 0x11237, Property::Extend})
put(data, {0x1123E, 0x1123E, Property::Extend})
put(data, {0x11241, 0x11241, Property::Extend})
put(data, {0x112DF, 0x112DF, Property::Extend})
@@ -1322,11 +1314,24 @@ struct String::Grapheme
put(data, {0x11340, 0x11340, Property::Extend})
put(data, {0x11341, 0x11344, Property::SpacingMark})
put(data, {0x11347, 0x11348, Property::SpacingMark})
- put(data, {0x1134B, 0x1134D, Property::SpacingMark})
+ put(data, {0x1134B, 0x1134C, Property::SpacingMark})
+ put(data, {0x1134D, 0x1134D, Property::Extend})
put(data, {0x11357, 0x11357, Property::Extend})
put(data, {0x11362, 0x11363, Property::SpacingMark})
put(data, {0x11366, 0x1136C, Property::Extend})
put(data, {0x11370, 0x11374, Property::Extend})
+ put(data, {0x113B8, 0x113B8, Property::Extend})
+ put(data, {0x113B9, 0x113BA, Property::SpacingMark})
+ put(data, {0x113BB, 0x113C0, Property::Extend})
+ put(data, {0x113C2, 0x113C2, Property::Extend})
+ put(data, {0x113C5, 0x113C5, Property::Extend})
+ put(data, {0x113C7, 0x113C9, Property::Extend})
+ put(data, {0x113CA, 0x113CA, Property::SpacingMark})
+ put(data, {0x113CC, 0x113CD, Property::SpacingMark})
+ put(data, {0x113CE, 0x113D0, Property::Extend})
+ put(data, {0x113D1, 0x113D1, Property::Prepend})
+ put(data, {0x113D2, 0x113D2, Property::Extend})
+ put(data, {0x113E1, 0x113E2, Property::Extend})
put(data, {0x11435, 0x11437, Property::SpacingMark})
put(data, {0x11438, 0x1143F, Property::Extend})
put(data, {0x11440, 0x11441, Property::SpacingMark})
@@ -1363,10 +1368,10 @@ struct String::Grapheme
put(data, {0x116AC, 0x116AC, Property::SpacingMark})
put(data, {0x116AD, 0x116AD, Property::Extend})
put(data, {0x116AE, 0x116AF, Property::SpacingMark})
- put(data, {0x116B0, 0x116B5, Property::Extend})
- put(data, {0x116B6, 0x116B6, Property::SpacingMark})
- put(data, {0x116B7, 0x116B7, Property::Extend})
- put(data, {0x1171D, 0x1171F, Property::Extend})
+ put(data, {0x116B0, 0x116B7, Property::Extend})
+ put(data, {0x1171D, 0x1171D, Property::Extend})
+ put(data, {0x1171E, 0x1171E, Property::SpacingMark})
+ put(data, {0x1171F, 0x1171F, Property::Extend})
put(data, {0x11722, 0x11725, Property::Extend})
put(data, {0x11726, 0x11726, Property::SpacingMark})
put(data, {0x11727, 0x1172B, Property::Extend})
@@ -1377,9 +1382,7 @@ struct String::Grapheme
put(data, {0x11930, 0x11930, Property::Extend})
put(data, {0x11931, 0x11935, Property::SpacingMark})
put(data, {0x11937, 0x11938, Property::SpacingMark})
- put(data, {0x1193B, 0x1193C, Property::Extend})
- put(data, {0x1193D, 0x1193D, Property::SpacingMark})
- put(data, {0x1193E, 0x1193E, Property::Extend})
+ put(data, {0x1193B, 0x1193E, Property::Extend})
put(data, {0x1193F, 0x1193F, Property::Prepend})
put(data, {0x11940, 0x11940, Property::SpacingMark})
put(data, {0x11941, 0x11941, Property::Prepend})
@@ -1436,28 +1439,29 @@ struct String::Grapheme
put(data, {0x11F34, 0x11F35, Property::SpacingMark})
put(data, {0x11F36, 0x11F3A, Property::Extend})
put(data, {0x11F3E, 0x11F3F, Property::SpacingMark})
- put(data, {0x11F40, 0x11F40, Property::Extend})
- put(data, {0x11F41, 0x11F41, Property::SpacingMark})
- put(data, {0x11F42, 0x11F42, Property::Extend})
+ put(data, {0x11F40, 0x11F42, Property::Extend})
+ put(data, {0x11F5A, 0x11F5A, Property::Extend})
put(data, {0x13430, 0x1343F, Property::Control})
put(data, {0x13440, 0x13440, Property::Extend})
put(data, {0x13447, 0x13455, Property::Extend})
+ put(data, {0x1611E, 0x16129, Property::Extend})
+ put(data, {0x1612A, 0x1612C, Property::SpacingMark})
+ put(data, {0x1612D, 0x1612F, Property::Extend})
put(data, {0x16AF0, 0x16AF4, Property::Extend})
put(data, {0x16B30, 0x16B36, Property::Extend})
+ put(data, {0x16D63, 0x16D63, Property::V})
+ put(data, {0x16D67, 0x16D6A, Property::V})
put(data, {0x16F4F, 0x16F4F, Property::Extend})
put(data, {0x16F51, 0x16F87, Property::SpacingMark})
put(data, {0x16F8F, 0x16F92, Property::Extend})
put(data, {0x16FE4, 0x16FE4, Property::Extend})
- put(data, {0x16FF0, 0x16FF1, Property::SpacingMark})
+ put(data, {0x16FF0, 0x16FF1, Property::Extend})
put(data, {0x1BC9D, 0x1BC9E, Property::Extend})
put(data, {0x1BCA0, 0x1BCA3, Property::Control})
put(data, {0x1CF00, 0x1CF2D, Property::Extend})
put(data, {0x1CF30, 0x1CF46, Property::Extend})
- put(data, {0x1D165, 0x1D165, Property::Extend})
- put(data, {0x1D166, 0x1D166, Property::SpacingMark})
- put(data, {0x1D167, 0x1D169, Property::Extend})
- put(data, {0x1D16D, 0x1D16D, Property::SpacingMark})
- put(data, {0x1D16E, 0x1D172, Property::Extend})
+ put(data, {0x1D165, 0x1D169, Property::Extend})
+ put(data, {0x1D16D, 0x1D172, Property::Extend})
put(data, {0x1D173, 0x1D17A, Property::Control})
put(data, {0x1D17B, 0x1D182, Property::Extend})
put(data, {0x1D185, 0x1D18B, Property::Extend})
@@ -1479,6 +1483,7 @@ struct String::Grapheme
put(data, {0x1E2AE, 0x1E2AE, Property::Extend})
put(data, {0x1E2EC, 0x1E2EF, Property::Extend})
put(data, {0x1E4EC, 0x1E4EF, Property::Extend})
+ put(data, {0x1E5EE, 0x1E5EF, Property::Extend})
put(data, {0x1E8D0, 0x1E8D6, Property::Extend})
put(data, {0x1E944, 0x1E94A, Property::Extend})
put(data, {0x1F000, 0x1F0FF, Property::ExtendedPictographic})
diff --git a/src/syscall/aarch64-linux.cr b/src/syscall/aarch64-linux.cr
index 5a61e8e7eed8..77b891fe2a7c 100644
--- a/src/syscall/aarch64-linux.cr
+++ b/src/syscall/aarch64-linux.cr
@@ -334,7 +334,7 @@ module Syscall
end
macro def_syscall(name, return_type, *args)
- @[AlwaysInline]
+ @[::AlwaysInline]
def self.{{name.id}}({{args.splat}}) : {{return_type}}
ret = uninitialized {{return_type}}
diff --git a/src/syscall/arm-linux.cr b/src/syscall/arm-linux.cr
index 97119fc4b3f3..da349dd45301 100644
--- a/src/syscall/arm-linux.cr
+++ b/src/syscall/arm-linux.cr
@@ -409,7 +409,7 @@ module Syscall
end
macro def_syscall(name, return_type, *args)
- @[AlwaysInline]
+ @[::AlwaysInline]
def self.{{name.id}}({{args.splat}}) : {{return_type}}
ret = uninitialized {{return_type}}
diff --git a/src/syscall/i386-linux.cr b/src/syscall/i386-linux.cr
index 843b2d1fd856..a0f94a51160a 100644
--- a/src/syscall/i386-linux.cr
+++ b/src/syscall/i386-linux.cr
@@ -445,7 +445,7 @@ module Syscall
end
macro def_syscall(name, return_type, *args)
- @[AlwaysInline]
+ @[::AlwaysInline]
def self.{{name.id}}({{args.splat}}) : {{return_type}}
ret = uninitialized {{return_type}}
diff --git a/src/syscall/x86_64-linux.cr b/src/syscall/x86_64-linux.cr
index 1f01c9226658..5a63b6ee2e1a 100644
--- a/src/syscall/x86_64-linux.cr
+++ b/src/syscall/x86_64-linux.cr
@@ -368,7 +368,7 @@ module Syscall
end
macro def_syscall(name, return_type, *args)
- @[AlwaysInline]
+ @[::AlwaysInline]
def self.{{name.id}}({{args.splat}}) : {{return_type}}
ret = uninitialized {{return_type}}
diff --git a/src/system/group.cr b/src/system/group.cr
index bd992e6af19d..47b9768cca52 100644
--- a/src/system/group.cr
+++ b/src/system/group.cr
@@ -17,19 +17,20 @@ class System::Group
class NotFoundError < Exception
end
- extend Crystal::System::Group
+ include Crystal::System::Group
# The group's name.
- getter name : String
+ def name : String
+ system_name
+ end
# The group's identifier.
- getter id : String
-
- def_equals_and_hash @id
-
- private def initialize(@name, @id)
+ def id : String
+ system_id
end
+ def_equals_and_hash id
+
# Returns the group associated with the given name.
#
# Raises `NotFoundError` if no such group exists.
@@ -41,7 +42,7 @@ class System::Group
#
# Returns `nil` if no such group exists.
def self.find_by?(*, name : String) : System::Group?
- from_name?(name)
+ Crystal::System::Group.from_name?(name)
end
# Returns the group associated with the given ID.
@@ -55,7 +56,7 @@ class System::Group
#
# Returns `nil` if no such group exists.
def self.find_by?(*, id : String) : System::Group?
- from_id?(id)
+ Crystal::System::Group.from_id?(id)
end
def to_s(io)
diff --git a/src/system/user.cr b/src/system/user.cr
index 7d6c250689da..01c8d11d9e1c 100644
--- a/src/system/user.cr
+++ b/src/system/user.cr
@@ -17,34 +17,43 @@ class System::User
class NotFoundError < Exception
end
- extend Crystal::System::User
+ include Crystal::System::User
# The user's username.
- getter username : String
+ def username : String
+ system_username
+ end
# The user's identifier.
- getter id : String
+ def id : String
+ system_id
+ end
# The user's primary group identifier.
- getter group_id : String
+ def group_id : String
+ system_group_id
+ end
# The user's real or full name.
#
# May not be present on all platforms. Returns the same value as `#username`
# if neither a real nor full name is available.
- getter name : String
+ def name : String
+ system_name
+ end
# The user's home directory.
- getter home_directory : String
+ def home_directory : String
+ system_home_directory
+ end
# The user's login shell.
- getter shell : String
-
- def_equals_and_hash @id
-
- private def initialize(@username, @id, @group_id, @name, @home_directory, @shell)
+ def shell : String
+ system_shell
end
+ def_equals_and_hash id
+
# Returns the user associated with the given username.
#
# Raises `NotFoundError` if no such user exists.
@@ -56,7 +65,7 @@ class System::User
#
# Returns `nil` if no such user exists.
def self.find_by?(*, name : String) : System::User?
- from_username?(name)
+ Crystal::System::User.from_username?(name)
end
# Returns the user associated with the given ID.
@@ -70,7 +79,7 @@ class System::User
#
# Returns `nil` if no such user exists.
def self.find_by?(*, id : String) : System::User?
- from_id?(id)
+ Crystal::System::User.from_id?(id)
end
def to_s(io)
diff --git a/src/time/format/custom/http_date.cr b/src/time/format/custom/http_date.cr
index d9ca38b9d7e5..25847b21aa00 100644
--- a/src/time/format/custom/http_date.cr
+++ b/src/time/format/custom/http_date.cr
@@ -102,6 +102,7 @@ struct Time::Format
ansi_c_format = current_char != ','
next_char unless ansi_c_format
+ raise "Invalid date format" unless current_char.ascii_whitespace?
whitespace
ansi_c_format
diff --git a/src/tuple.cr b/src/tuple.cr
index 2f9cde352e4f..a8dd3a040727 100644
--- a/src/tuple.cr
+++ b/src/tuple.cr
@@ -545,11 +545,7 @@ struct Tuple
# {1, 2, 3, 4, 5}.to_a # => [1, 2, 3, 4, 5]
# ```
def to_a : Array(Union(*T))
- {% if compare_versions(Crystal::VERSION, "1.1.0") < 0 %}
- to_a(&.itself.as(Union(*T)))
- {% else %}
- to_a(&.itself)
- {% end %}
+ super
end
# Returns an `Array` with the results of running *block* against each element of the tuple.
@@ -557,8 +553,8 @@ struct Tuple
# ```
# {1, 2, 3, 4, 5}).to_a { |i| i * 2 } # => [2, 4, 6, 8, 10]
# ```
- def to_a(& : Union(*T) -> _)
- Array(Union(*T)).build(size) do |buffer|
+ def to_a(& : Union(*T) -> U) forall U
+ Array(U).build(size) do |buffer|
{% for i in 0...T.size %}
buffer[{{i}}] = yield self[{{i}}]
{% end %}
diff --git a/src/unicode/data.cr b/src/unicode/data.cr
index a02db251d0c8..ccb7d702e892 100644
--- a/src/unicode/data.cr
+++ b/src/unicode/data.cr
@@ -8,7 +8,7 @@ module Unicode
# Most case conversions map a range to another range.
# Here we store: {from, to, delta}
private class_getter upcase_ranges : Array({Int32, Int32, Int32}) do
- data = Array({Int32, Int32, Int32}).new(141)
+ data = Array({Int32, Int32, Int32}).new(144)
put(data, 97, 122, -32)
put(data, 181, 181, 743)
put(data, 224, 246, -32)
@@ -19,6 +19,7 @@ module Unicode
put(data, 384, 384, 195)
put(data, 405, 405, 97)
put(data, 410, 410, 163)
+ put(data, 411, 411, 42561)
put(data, 414, 414, 130)
put(data, 447, 447, 56)
put(data, 454, 454, -2)
@@ -39,6 +40,7 @@ module Unicode
put(data, 608, 608, -205)
put(data, 609, 609, 42315)
put(data, 611, 611, -207)
+ put(data, 612, 612, 42343)
put(data, 613, 613, 42280)
put(data, 614, 614, 42308)
put(data, 616, 616, -209)
@@ -147,6 +149,7 @@ module Unicode
put(data, 66995, 67001, -39)
put(data, 67003, 67004, -39)
put(data, 68800, 68850, -64)
+ put(data, 68976, 68997, -32)
put(data, 71872, 71903, -32)
put(data, 93792, 93823, -32)
put(data, 125218, 125251, -34)
@@ -156,7 +159,7 @@ module Unicode
# Most case conversions map a range to another range.
# Here we store: {from, to, delta}
private class_getter downcase_ranges : Array({Int32, Int32, Int32}) do
- data = Array({Int32, Int32, Int32}).new(125)
+ data = Array({Int32, Int32, Int32}).new(128)
put(data, 65, 90, 32)
put(data, 192, 214, 32)
put(data, 216, 222, 32)
@@ -271,6 +274,8 @@ module Unicode
put(data, 42948, 42948, -48)
put(data, 42949, 42949, -42307)
put(data, 42950, 42950, -35384)
+ put(data, 42955, 42955, -42343)
+ put(data, 42972, 42972, -42561)
put(data, 65313, 65338, 32)
put(data, 66560, 66599, 40)
put(data, 66736, 66771, 40)
@@ -279,6 +284,7 @@ module Unicode
put(data, 66956, 66962, 39)
put(data, 66964, 66965, 39)
put(data, 68736, 68786, 64)
+ put(data, 68944, 68965, 32)
put(data, 71840, 71871, 32)
put(data, 93760, 93791, 32)
put(data, 125184, 125217, 34)
@@ -289,7 +295,7 @@ module Unicode
# of uppercase/lowercase transformations
# Here we store {from, to}
private class_getter alternate_ranges : Array({Int32, Int32}) do
- data = Array({Int32, Int32}).new(60)
+ data = Array({Int32, Int32}).new(62)
put(data, 256, 303)
put(data, 306, 311)
put(data, 313, 328)
@@ -326,6 +332,7 @@ module Unicode
put(data, 1162, 1215)
put(data, 1217, 1230)
put(data, 1232, 1327)
+ put(data, 7305, 7306)
put(data, 7680, 7829)
put(data, 7840, 7935)
put(data, 8579, 8580)
@@ -347,8 +354,9 @@ module Unicode
put(data, 42902, 42921)
put(data, 42932, 42947)
put(data, 42951, 42954)
+ put(data, 42956, 42957)
put(data, 42960, 42961)
- put(data, 42966, 42969)
+ put(data, 42966, 42971)
put(data, 42997, 42998)
data
end
@@ -363,7 +371,7 @@ module Unicode
# The values are: 1..10, 11, 13, 15
private class_getter category_Lu : Array({Int32, Int32, Int32}) do
- data = Array({Int32, Int32, Int32}).new(149)
+ data = Array({Int32, Int32, Int32}).new(152)
put(data, 65, 90, 1)
put(data, 192, 214, 1)
put(data, 216, 222, 1)
@@ -420,7 +428,8 @@ module Unicode
put(data, 4256, 4293, 1)
put(data, 4295, 4301, 6)
put(data, 5024, 5109, 1)
- put(data, 7312, 7354, 1)
+ put(data, 7305, 7312, 7)
+ put(data, 7313, 7354, 1)
put(data, 7357, 7359, 1)
put(data, 7680, 7828, 2)
put(data, 7838, 7934, 2)
@@ -469,8 +478,9 @@ module Unicode
put(data, 42928, 42932, 1)
put(data, 42934, 42948, 2)
put(data, 42949, 42951, 1)
- put(data, 42953, 42960, 7)
- put(data, 42966, 42968, 2)
+ put(data, 42953, 42955, 2)
+ put(data, 42956, 42960, 4)
+ put(data, 42966, 42972, 2)
put(data, 42997, 65313, 22316)
put(data, 65314, 65338, 1)
put(data, 66560, 66599, 1)
@@ -480,6 +490,7 @@ module Unicode
put(data, 66956, 66962, 1)
put(data, 66964, 66965, 1)
put(data, 68736, 68786, 1)
+ put(data, 68944, 68965, 1)
put(data, 71840, 71871, 1)
put(data, 93760, 93791, 1)
put(data, 119808, 119833, 1)
@@ -516,7 +527,7 @@ module Unicode
data
end
private class_getter category_Ll : Array({Int32, Int32, Int32}) do
- data = Array({Int32, Int32, Int32}).new(163)
+ data = Array({Int32, Int32, Int32}).new(166)
put(data, 97, 122, 1)
put(data, 181, 223, 42)
put(data, 224, 246, 1)
@@ -572,7 +583,8 @@ module Unicode
put(data, 4349, 4351, 1)
put(data, 5112, 5117, 1)
put(data, 7296, 7304, 1)
- put(data, 7424, 7467, 1)
+ put(data, 7306, 7424, 118)
+ put(data, 7425, 7467, 1)
put(data, 7531, 7543, 1)
put(data, 7545, 7578, 1)
put(data, 7681, 7829, 2)
@@ -631,7 +643,8 @@ module Unicode
put(data, 42927, 42933, 6)
put(data, 42935, 42947, 2)
put(data, 42952, 42954, 2)
- put(data, 42961, 42969, 2)
+ put(data, 42957, 42961, 4)
+ put(data, 42963, 42971, 2)
put(data, 42998, 43002, 4)
put(data, 43824, 43866, 1)
put(data, 43872, 43880, 1)
@@ -646,6 +659,7 @@ module Unicode
put(data, 66995, 67001, 1)
put(data, 67003, 67004, 1)
put(data, 68800, 68850, 1)
+ put(data, 68976, 68997, 1)
put(data, 71872, 71903, 1)
put(data, 93792, 93823, 1)
put(data, 119834, 119859, 1)
@@ -694,7 +708,7 @@ module Unicode
data
end
private class_getter category_Lm : Array({Int32, Int32, Int32}) do
- data = Array({Int32, Int32, Int32}).new(54)
+ data = Array({Int32, Int32, Int32}).new(57)
put(data, 688, 705, 1)
put(data, 710, 721, 1)
put(data, 736, 740, 1)
@@ -739,7 +753,10 @@ module Unicode
put(data, 67456, 67461, 1)
put(data, 67463, 67504, 1)
put(data, 67506, 67514, 1)
+ put(data, 68942, 68975, 33)
put(data, 92992, 92995, 1)
+ put(data, 93504, 93506, 1)
+ put(data, 93547, 93548, 1)
put(data, 94099, 94111, 1)
put(data, 94176, 94177, 1)
put(data, 94179, 110576, 16397)
@@ -752,7 +769,7 @@ module Unicode
data
end
private class_getter category_Lo : Array({Int32, Int32, Int32}) do
- data = Array({Int32, Int32, Int32}).new(486)
+ data = Array({Int32, Int32, Int32}).new(502)
put(data, 170, 186, 16)
put(data, 443, 448, 5)
put(data, 449, 451, 1)
@@ -1052,6 +1069,7 @@ module Unicode
put(data, 66640, 66717, 1)
put(data, 66816, 66855, 1)
put(data, 66864, 66915, 1)
+ put(data, 67008, 67059, 1)
put(data, 67072, 67382, 1)
put(data, 67392, 67413, 1)
put(data, 67424, 67431, 1)
@@ -1083,8 +1101,11 @@ module Unicode
put(data, 68480, 68497, 1)
put(data, 68608, 68680, 1)
put(data, 68864, 68899, 1)
- put(data, 69248, 69289, 1)
+ put(data, 68938, 68941, 1)
+ put(data, 68943, 69248, 305)
+ put(data, 69249, 69289, 1)
put(data, 69296, 69297, 1)
+ put(data, 69314, 69316, 1)
put(data, 69376, 69404, 1)
put(data, 69415, 69424, 9)
put(data, 69425, 69445, 1)
@@ -1120,7 +1141,12 @@ module Unicode
put(data, 70453, 70457, 1)
put(data, 70461, 70480, 19)
put(data, 70493, 70497, 1)
- put(data, 70656, 70708, 1)
+ put(data, 70528, 70537, 1)
+ put(data, 70539, 70542, 3)
+ put(data, 70544, 70581, 1)
+ put(data, 70583, 70609, 26)
+ put(data, 70611, 70656, 45)
+ put(data, 70657, 70708, 1)
put(data, 70727, 70730, 1)
put(data, 70751, 70753, 1)
put(data, 70784, 70831, 1)
@@ -1150,6 +1176,7 @@ module Unicode
put(data, 72284, 72329, 1)
put(data, 72349, 72368, 19)
put(data, 72369, 72440, 1)
+ put(data, 72640, 72672, 1)
put(data, 72704, 72712, 1)
put(data, 72714, 72750, 1)
put(data, 72768, 72818, 50)
@@ -1172,7 +1199,9 @@ module Unicode
put(data, 77712, 77808, 1)
put(data, 77824, 78895, 1)
put(data, 78913, 78918, 1)
+ put(data, 78944, 82938, 1)
put(data, 82944, 83526, 1)
+ put(data, 90368, 90397, 1)
put(data, 92160, 92728, 1)
put(data, 92736, 92766, 1)
put(data, 92784, 92862, 1)
@@ -1180,12 +1209,14 @@ module Unicode
put(data, 92928, 92975, 1)
put(data, 93027, 93047, 1)
put(data, 93053, 93071, 1)
+ put(data, 93507, 93546, 1)
put(data, 93952, 94026, 1)
put(data, 94032, 94208, 176)
put(data, 100343, 100352, 9)
put(data, 100353, 101589, 1)
- put(data, 101632, 101640, 1)
- put(data, 110592, 110882, 1)
+ put(data, 101631, 101632, 1)
+ put(data, 101640, 110592, 8952)
+ put(data, 110593, 110882, 1)
put(data, 110898, 110928, 30)
put(data, 110929, 110930, 1)
put(data, 110933, 110948, 15)
@@ -1201,7 +1232,9 @@ module Unicode
put(data, 123537, 123565, 1)
put(data, 123584, 123627, 1)
put(data, 124112, 124138, 1)
- put(data, 124896, 124902, 1)
+ put(data, 124368, 124397, 1)
+ put(data, 124400, 124896, 496)
+ put(data, 124897, 124902, 1)
put(data, 124904, 124907, 1)
put(data, 124909, 124910, 1)
put(data, 124912, 124926, 1)
@@ -1242,7 +1275,7 @@ module Unicode
data
end
private class_getter category_Mn : Array({Int32, Int32, Int32}) do
- data = Array({Int32, Int32, Int32}).new(308)
+ data = Array({Int32, Int32, Int32}).new(315)
put(data, 768, 879, 1)
put(data, 1155, 1159, 1)
put(data, 1425, 1469, 1)
@@ -1266,7 +1299,7 @@ module Unicode
put(data, 2085, 2087, 1)
put(data, 2089, 2093, 1)
put(data, 2137, 2139, 1)
- put(data, 2200, 2207, 1)
+ put(data, 2199, 2207, 1)
put(data, 2250, 2273, 1)
put(data, 2275, 2306, 1)
put(data, 2362, 2364, 2)
@@ -1435,8 +1468,9 @@ module Unicode
put(data, 68159, 68325, 166)
put(data, 68326, 68900, 574)
put(data, 68901, 68903, 1)
+ put(data, 68969, 68973, 1)
put(data, 69291, 69292, 1)
- put(data, 69373, 69375, 1)
+ put(data, 69372, 69375, 1)
put(data, 69446, 69456, 1)
put(data, 69506, 69509, 1)
put(data, 69633, 69688, 55)
@@ -1465,6 +1499,9 @@ module Unicode
put(data, 70464, 70502, 38)
put(data, 70503, 70508, 1)
put(data, 70512, 70516, 1)
+ put(data, 70587, 70592, 1)
+ put(data, 70606, 70610, 2)
+ put(data, 70625, 70626, 1)
put(data, 70712, 70719, 1)
put(data, 70722, 70724, 1)
put(data, 70726, 70750, 24)
@@ -1482,8 +1519,8 @@ module Unicode
put(data, 71341, 71344, 3)
put(data, 71345, 71349, 1)
put(data, 71351, 71453, 102)
- put(data, 71454, 71455, 1)
- put(data, 71458, 71461, 1)
+ put(data, 71455, 71458, 3)
+ put(data, 71459, 71461, 1)
put(data, 71463, 71467, 1)
put(data, 71727, 71735, 1)
put(data, 71737, 71738, 1)
@@ -1518,8 +1555,10 @@ module Unicode
put(data, 73473, 73526, 53)
put(data, 73527, 73530, 1)
put(data, 73536, 73538, 2)
- put(data, 78912, 78919, 7)
- put(data, 78920, 78933, 1)
+ put(data, 73562, 78912, 5350)
+ put(data, 78919, 78933, 1)
+ put(data, 90398, 90409, 1)
+ put(data, 90413, 90415, 1)
put(data, 92912, 92916, 1)
put(data, 92976, 92982, 1)
put(data, 94031, 94095, 64)
@@ -1548,13 +1587,14 @@ module Unicode
put(data, 123566, 123628, 62)
put(data, 123629, 123631, 1)
put(data, 124140, 124143, 1)
+ put(data, 124398, 124399, 1)
put(data, 125136, 125142, 1)
put(data, 125252, 125258, 1)
put(data, 917760, 917999, 1)
data
end
private class_getter category_Mc : Array({Int32, Int32, Int32}) do
- data = Array({Int32, Int32, Int32}).new(158)
+ data = Array({Int32, Int32, Int32}).new(165)
put(data, 2307, 2363, 56)
put(data, 2366, 2368, 1)
put(data, 2377, 2380, 1)
@@ -1672,7 +1712,12 @@ module Unicode
put(data, 70471, 70472, 1)
put(data, 70475, 70477, 1)
put(data, 70487, 70498, 11)
- put(data, 70499, 70709, 210)
+ put(data, 70499, 70584, 85)
+ put(data, 70585, 70586, 1)
+ put(data, 70594, 70597, 3)
+ put(data, 70599, 70602, 1)
+ put(data, 70604, 70605, 1)
+ put(data, 70607, 70709, 102)
put(data, 70710, 70711, 1)
put(data, 70720, 70721, 1)
put(data, 70725, 70832, 107)
@@ -1687,9 +1732,10 @@ module Unicode
put(data, 71227, 71228, 1)
put(data, 71230, 71340, 110)
put(data, 71342, 71343, 1)
- put(data, 71350, 71456, 106)
- put(data, 71457, 71462, 5)
- put(data, 71724, 71726, 1)
+ put(data, 71350, 71454, 104)
+ put(data, 71456, 71457, 1)
+ put(data, 71462, 71724, 262)
+ put(data, 71725, 71726, 1)
put(data, 71736, 71984, 248)
put(data, 71985, 71989, 1)
put(data, 71991, 71992, 1)
@@ -1708,8 +1754,9 @@ module Unicode
put(data, 73462, 73475, 13)
put(data, 73524, 73525, 1)
put(data, 73534, 73535, 1)
- put(data, 73537, 94033, 20496)
- put(data, 94034, 94087, 1)
+ put(data, 73537, 90410, 16873)
+ put(data, 90411, 90412, 1)
+ put(data, 94033, 94087, 1)
put(data, 94192, 94193, 1)
put(data, 119141, 119142, 1)
put(data, 119149, 119154, 1)
@@ -1725,7 +1772,7 @@ module Unicode
data
end
private class_getter category_Nd : Array({Int32, Int32, Int32}) do
- data = Array({Int32, Int32, Int32}).new(64)
+ data = Array({Int32, Int32, Int32}).new(71)
put(data, 48, 57, 1)
put(data, 1632, 1641, 1)
put(data, 1776, 1785, 1)
@@ -1765,6 +1812,7 @@ module Unicode
put(data, 65296, 65305, 1)
put(data, 66720, 66729, 1)
put(data, 68912, 68921, 1)
+ put(data, 68928, 68937, 1)
put(data, 69734, 69743, 1)
put(data, 69872, 69881, 1)
put(data, 69942, 69951, 1)
@@ -1774,20 +1822,26 @@ module Unicode
put(data, 70864, 70873, 1)
put(data, 71248, 71257, 1)
put(data, 71360, 71369, 1)
+ put(data, 71376, 71395, 1)
put(data, 71472, 71481, 1)
put(data, 71904, 71913, 1)
put(data, 72016, 72025, 1)
+ put(data, 72688, 72697, 1)
put(data, 72784, 72793, 1)
put(data, 73040, 73049, 1)
put(data, 73120, 73129, 1)
put(data, 73552, 73561, 1)
+ put(data, 90416, 90425, 1)
put(data, 92768, 92777, 1)
put(data, 92864, 92873, 1)
put(data, 93008, 93017, 1)
+ put(data, 93552, 93561, 1)
+ put(data, 118000, 118009, 1)
put(data, 120782, 120831, 1)
put(data, 123200, 123209, 1)
put(data, 123632, 123641, 1)
put(data, 124144, 124153, 1)
+ put(data, 124401, 124410, 1)
put(data, 125264, 125273, 1)
put(data, 130032, 130041, 1)
data
@@ -1951,7 +2005,7 @@ module Unicode
# Most casefold conversions map a range to another range.
# Here we store: {from, to, delta}
private class_getter casefold_ranges : Array({Int32, Int32, Int32}) do
- data = Array({Int32, Int32, Int32}).new(681)
+ data = Array({Int32, Int32, Int32}).new(687)
put(data, 65, 90, 32)
put(data, 181, 181, 775)
put(data, 192, 214, 32)
@@ -2276,6 +2330,7 @@ module Unicode
put(data, 7302, 7302, -6204)
put(data, 7303, 7303, -6180)
put(data, 7304, 7304, 35267)
+ put(data, 7305, 7305, 1)
put(data, 7312, 7354, -3008)
put(data, 7357, 7359, -3008)
put(data, 7680, 7680, 1)
@@ -2617,9 +2672,13 @@ module Unicode
put(data, 42950, 42950, -35384)
put(data, 42951, 42951, 1)
put(data, 42953, 42953, 1)
+ put(data, 42955, 42955, -42343)
+ put(data, 42956, 42956, 1)
put(data, 42960, 42960, 1)
put(data, 42966, 42966, 1)
put(data, 42968, 42968, 1)
+ put(data, 42970, 42970, 1)
+ put(data, 42972, 42972, -42561)
put(data, 42997, 42997, 1)
put(data, 43888, 43967, -38864)
put(data, 65313, 65338, 32)
@@ -2630,6 +2689,7 @@ module Unicode
put(data, 66956, 66962, 39)
put(data, 66964, 66965, 39)
put(data, 68736, 68786, 64)
+ put(data, 68944, 68965, 32)
put(data, 71840, 71871, 32)
put(data, 93760, 93791, 32)
put(data, 125184, 125217, 34)
@@ -2963,7 +3023,7 @@ module Unicode
# guarantees that all class values are within `0..254`.
# Here we store: {from, to, class}
private class_getter canonical_combining_classes : Array({Int32, Int32, UInt8}) do
- data = Array({Int32, Int32, UInt8}).new(392)
+ data = Array({Int32, Int32, UInt8}).new(399)
put(data, 768, 788, 230_u8)
put(data, 789, 789, 232_u8)
put(data, 790, 793, 220_u8)
@@ -3084,7 +3144,7 @@ module Unicode
put(data, 2085, 2087, 230_u8)
put(data, 2089, 2093, 230_u8)
put(data, 2137, 2139, 220_u8)
- put(data, 2200, 2200, 230_u8)
+ put(data, 2199, 2200, 230_u8)
put(data, 2201, 2203, 220_u8)
put(data, 2204, 2207, 230_u8)
put(data, 2250, 2254, 230_u8)
@@ -3273,6 +3333,7 @@ module Unicode
put(data, 68325, 68325, 230_u8)
put(data, 68326, 68326, 220_u8)
put(data, 68900, 68903, 230_u8)
+ put(data, 68969, 68973, 230_u8)
put(data, 69291, 69292, 230_u8)
put(data, 69373, 69375, 220_u8)
put(data, 69446, 69447, 220_u8)
@@ -3302,6 +3363,9 @@ module Unicode
put(data, 70477, 70477, 9_u8)
put(data, 70502, 70508, 230_u8)
put(data, 70512, 70516, 230_u8)
+ put(data, 70606, 70606, 9_u8)
+ put(data, 70607, 70607, 9_u8)
+ put(data, 70608, 70608, 9_u8)
put(data, 70722, 70722, 9_u8)
put(data, 70726, 70726, 7_u8)
put(data, 70750, 70750, 230_u8)
@@ -3328,6 +3392,7 @@ module Unicode
put(data, 73111, 73111, 9_u8)
put(data, 73537, 73537, 9_u8)
put(data, 73538, 73538, 9_u8)
+ put(data, 90415, 90415, 9_u8)
put(data, 92912, 92916, 1_u8)
put(data, 92976, 92982, 230_u8)
put(data, 94192, 94193, 6_u8)
@@ -3353,6 +3418,8 @@ module Unicode
put(data, 124140, 124141, 232_u8)
put(data, 124142, 124142, 220_u8)
put(data, 124143, 124143, 230_u8)
+ put(data, 124398, 124398, 230_u8)
+ put(data, 124399, 124399, 220_u8)
put(data, 125136, 125142, 220_u8)
put(data, 125252, 125257, 230_u8)
put(data, 125258, 125258, 7_u8)
@@ -3363,7 +3430,7 @@ module Unicode
# transformation is always 2 codepoints, so we store them all as 2 codepoints
# and 0 means end.
private class_getter canonical_decompositions : Hash(Int32, {Int32, Int32}) do
- data = Hash(Int32, {Int32, Int32}).new(initial_capacity: 2061)
+ data = Hash(Int32, {Int32, Int32}).new(initial_capacity: 2081)
put(data, 192, 65, 768)
put(data, 193, 65, 769)
put(data, 194, 65, 770)
@@ -4857,6 +4924,8 @@ module Unicode
put(data, 64332, 1489, 1471)
put(data, 64333, 1499, 1471)
put(data, 64334, 1508, 1471)
+ put(data, 67017, 67026, 775)
+ put(data, 67044, 67034, 775)
put(data, 69786, 69785, 69818)
put(data, 69788, 69787, 69818)
put(data, 69803, 69797, 69818)
@@ -4864,12 +4933,30 @@ module Unicode
put(data, 69935, 69938, 69927)
put(data, 70475, 70471, 70462)
put(data, 70476, 70471, 70487)
+ put(data, 70531, 70530, 70601)
+ put(data, 70533, 70532, 70587)
+ put(data, 70542, 70539, 70594)
+ put(data, 70545, 70544, 70601)
+ put(data, 70597, 70594, 70594)
+ put(data, 70599, 70594, 70584)
+ put(data, 70600, 70594, 70601)
put(data, 70843, 70841, 70842)
put(data, 70844, 70841, 70832)
put(data, 70846, 70841, 70845)
put(data, 71098, 71096, 71087)
put(data, 71099, 71097, 71087)
put(data, 71992, 71989, 71984)
+ put(data, 90401, 90398, 90398)
+ put(data, 90402, 90398, 90409)
+ put(data, 90403, 90398, 90399)
+ put(data, 90404, 90409, 90399)
+ put(data, 90405, 90398, 90400)
+ put(data, 90406, 90401, 90399)
+ put(data, 90407, 90402, 90399)
+ put(data, 90408, 90401, 90400)
+ put(data, 93544, 93543, 93543)
+ put(data, 93545, 93539, 93543)
+ put(data, 93546, 93545, 93543)
put(data, 119134, 119127, 119141)
put(data, 119135, 119128, 119141)
put(data, 119136, 119135, 119150)
@@ -8669,7 +8756,7 @@ module Unicode
# codepoints.
# Here we store: codepoint => {index, count}
private class_getter compatibility_decompositions : Hash(Int32, {Int32, Int32}) do
- data = Hash(Int32, {Int32, Int32}).new(initial_capacity: 3796)
+ data = Hash(Int32, {Int32, Int32}).new(initial_capacity: 3832)
put(data, 160, 0, 1)
put(data, 168, 1, 2)
put(data, 170, 3, 1)
@@ -11121,6 +11208,42 @@ module Unicode
put(data, 67512, 2953, 1)
put(data, 67513, 2954, 1)
put(data, 67514, 2955, 1)
+ put(data, 117974, 119, 1)
+ put(data, 117975, 121, 1)
+ put(data, 117976, 248, 1)
+ put(data, 117977, 35, 1)
+ put(data, 117978, 122, 1)
+ put(data, 117979, 259, 1)
+ put(data, 117980, 124, 1)
+ put(data, 117981, 125, 1)
+ put(data, 117982, 24, 1)
+ put(data, 117983, 25, 1)
+ put(data, 117984, 126, 1)
+ put(data, 117985, 28, 1)
+ put(data, 117986, 127, 1)
+ put(data, 117987, 47, 1)
+ put(data, 117988, 128, 1)
+ put(data, 117989, 130, 1)
+ put(data, 117990, 263, 1)
+ put(data, 117991, 131, 1)
+ put(data, 117992, 264, 1)
+ put(data, 117993, 132, 1)
+ put(data, 117994, 133, 1)
+ put(data, 117995, 333, 1)
+ put(data, 117996, 134, 1)
+ put(data, 117997, 277, 1)
+ put(data, 117998, 606, 1)
+ put(data, 117999, 54, 1)
+ put(data, 118000, 229, 1)
+ put(data, 118001, 13, 1)
+ put(data, 118002, 6, 1)
+ put(data, 118003, 7, 1)
+ put(data, 118004, 17, 1)
+ put(data, 118005, 230, 1)
+ put(data, 118006, 231, 1)
+ put(data, 118007, 232, 1)
+ put(data, 118008, 233, 1)
+ put(data, 118009, 234, 1)
put(data, 119808, 119, 1)
put(data, 119809, 121, 1)
put(data, 119810, 248, 1)
@@ -12473,7 +12596,7 @@ module Unicode
# composition exclusions.
# Here we store: (first << 21 | second) => codepoint
private class_getter canonical_compositions : Hash(Int64, Int32) do
- data = Hash(Int64, Int32).new(initial_capacity: 941)
+ data = Hash(Int64, Int32).new(initial_capacity: 961)
put(data, 136315648_i64, 192)
put(data, 136315649_i64, 193)
put(data, 136315650_i64, 194)
@@ -13402,6 +13525,8 @@ module Unicode
put(data, 26275229849_i64, 12537)
put(data, 26277327001_i64, 12538)
put(data, 26300395673_i64, 12542)
+ put(data, 140563710727_i64, 67017)
+ put(data, 140580487943_i64, 67044)
put(data, 146349822138_i64, 69786)
put(data, 146354016442_i64, 69788)
put(data, 146374987962_i64, 69803)
@@ -13409,12 +13534,30 @@ module Unicode
put(data, 146670686503_i64, 69935)
put(data, 147788469054_i64, 70475)
put(data, 147788469079_i64, 70476)
+ put(data, 147912201161_i64, 70531)
+ put(data, 147916395451_i64, 70533)
+ put(data, 147931075522_i64, 70542)
+ put(data, 147941561289_i64, 70545)
+ put(data, 148046418882_i64, 70597)
+ put(data, 148046418872_i64, 70599)
+ put(data, 148046418889_i64, 70600)
put(data, 148564415674_i64, 70843)
put(data, 148564415664_i64, 70844)
put(data, 148564415677_i64, 70846)
put(data, 149099189679_i64, 71098)
put(data, 149101286831_i64, 71099)
put(data, 150971947312_i64, 71992)
+ put(data, 189578436894_i64, 90401)
+ put(data, 189578436905_i64, 90402)
+ put(data, 189578436895_i64, 90403)
+ put(data, 189601505567_i64, 90404)
+ put(data, 189578436896_i64, 90405)
+ put(data, 189584728351_i64, 90406)
+ put(data, 189586825503_i64, 90407)
+ put(data, 189584728352_i64, 90408)
+ put(data, 196173983079_i64, 93544)
+ put(data, 196165594471_i64, 93545)
+ put(data, 196178177383_i64, 93546)
data
end
@@ -13422,7 +13565,7 @@ module Unicode
# Form C (yes if absent in this table).
# Here we store: {low, high, result (no or maybe)}
private class_getter nfc_quick_check : Array({Int32, Int32, QuickCheckResult}) do
- data = Array({Int32, Int32, QuickCheckResult}).new(117)
+ data = Array({Int32, Int32, QuickCheckResult}).new(124)
put(data, 768, 772, QuickCheckResult::Maybe)
put(data, 774, 780, QuickCheckResult::Maybe)
put(data, 783, 783, QuickCheckResult::Maybe)
@@ -13532,11 +13675,18 @@ module Unicode
put(data, 69927, 69927, QuickCheckResult::Maybe)
put(data, 70462, 70462, QuickCheckResult::Maybe)
put(data, 70487, 70487, QuickCheckResult::Maybe)
+ put(data, 70584, 70584, QuickCheckResult::Maybe)
+ put(data, 70587, 70587, QuickCheckResult::Maybe)
+ put(data, 70594, 70594, QuickCheckResult::Maybe)
+ put(data, 70597, 70597, QuickCheckResult::Maybe)
+ put(data, 70599, 70601, QuickCheckResult::Maybe)
put(data, 70832, 70832, QuickCheckResult::Maybe)
put(data, 70842, 70842, QuickCheckResult::Maybe)
put(data, 70845, 70845, QuickCheckResult::Maybe)
put(data, 71087, 71087, QuickCheckResult::Maybe)
put(data, 71984, 71984, QuickCheckResult::Maybe)
+ put(data, 90398, 90409, QuickCheckResult::Maybe)
+ put(data, 93543, 93544, QuickCheckResult::Maybe)
put(data, 119134, 119140, QuickCheckResult::No)
put(data, 119227, 119232, QuickCheckResult::No)
put(data, 194560, 195101, QuickCheckResult::No)
@@ -13547,7 +13697,7 @@ module Unicode
# Form KC (yes if absent in this table).
# Here we store: {low, high, result (no or maybe)}
private class_getter nfkc_quick_check : Array({Int32, Int32, QuickCheckResult}) do
- data = Array({Int32, Int32, QuickCheckResult}).new(436)
+ data = Array({Int32, Int32, QuickCheckResult}).new(445)
put(data, 160, 160, QuickCheckResult::No)
put(data, 168, 168, QuickCheckResult::No)
put(data, 170, 170, QuickCheckResult::No)
@@ -13891,11 +14041,20 @@ module Unicode
put(data, 69927, 69927, QuickCheckResult::Maybe)
put(data, 70462, 70462, QuickCheckResult::Maybe)
put(data, 70487, 70487, QuickCheckResult::Maybe)
+ put(data, 70584, 70584, QuickCheckResult::Maybe)
+ put(data, 70587, 70587, QuickCheckResult::Maybe)
+ put(data, 70594, 70594, QuickCheckResult::Maybe)
+ put(data, 70597, 70597, QuickCheckResult::Maybe)
+ put(data, 70599, 70601, QuickCheckResult::Maybe)
put(data, 70832, 70832, QuickCheckResult::Maybe)
put(data, 70842, 70842, QuickCheckResult::Maybe)
put(data, 70845, 70845, QuickCheckResult::Maybe)
put(data, 71087, 71087, QuickCheckResult::Maybe)
put(data, 71984, 71984, QuickCheckResult::Maybe)
+ put(data, 90398, 90409, QuickCheckResult::Maybe)
+ put(data, 93543, 93544, QuickCheckResult::Maybe)
+ put(data, 117974, 117999, QuickCheckResult::No)
+ put(data, 118000, 118009, QuickCheckResult::No)
put(data, 119134, 119140, QuickCheckResult::No)
put(data, 119227, 119232, QuickCheckResult::No)
put(data, 119808, 119892, QuickCheckResult::No)
@@ -13992,7 +14151,7 @@ module Unicode
# codepoints contained here may not appear under NFD.
# Here we store: {low, high}
private class_getter nfd_quick_check : Array({Int32, Int32}) do
- data = Array({Int32, Int32}).new(243)
+ data = Array({Int32, Int32}).new(253)
put(data, 192, 197)
put(data, 199, 207)
put(data, 209, 214)
@@ -14224,15 +14383,25 @@ module Unicode
put(data, 64320, 64321)
put(data, 64323, 64324)
put(data, 64326, 64334)
+ put(data, 67017, 67017)
+ put(data, 67044, 67044)
put(data, 69786, 69786)
put(data, 69788, 69788)
put(data, 69803, 69803)
put(data, 69934, 69935)
put(data, 70475, 70476)
+ put(data, 70531, 70531)
+ put(data, 70533, 70533)
+ put(data, 70542, 70542)
+ put(data, 70545, 70545)
+ put(data, 70597, 70597)
+ put(data, 70599, 70600)
put(data, 70843, 70844)
put(data, 70846, 70846)
put(data, 71098, 71099)
put(data, 71992, 71992)
+ put(data, 90401, 90408)
+ put(data, 93544, 93546)
put(data, 119134, 119140)
put(data, 119227, 119232)
put(data, 194560, 195101)
@@ -14244,7 +14413,7 @@ module Unicode
# codepoints contained here may not appear under NFKD.
# Here we store: {low, high}
private class_getter nfkd_quick_check : Array({Int32, Int32}) do
- data = Array({Int32, Int32}).new(548)
+ data = Array({Int32, Int32}).new(560)
put(data, 160, 160)
put(data, 168, 168)
put(data, 170, 170)
@@ -14693,6 +14862,8 @@ module Unicode
put(data, 65512, 65512)
put(data, 65513, 65516)
put(data, 65517, 65518)
+ put(data, 67017, 67017)
+ put(data, 67044, 67044)
put(data, 67457, 67461)
put(data, 67463, 67504)
put(data, 67506, 67514)
@@ -14701,10 +14872,20 @@ module Unicode
put(data, 69803, 69803)
put(data, 69934, 69935)
put(data, 70475, 70476)
+ put(data, 70531, 70531)
+ put(data, 70533, 70533)
+ put(data, 70542, 70542)
+ put(data, 70545, 70545)
+ put(data, 70597, 70597)
+ put(data, 70599, 70600)
put(data, 70843, 70844)
put(data, 70846, 70846)
put(data, 71098, 71099)
put(data, 71992, 71992)
+ put(data, 90401, 90408)
+ put(data, 93544, 93546)
+ put(data, 117974, 117999)
+ put(data, 118000, 118009)
put(data, 119134, 119140)
put(data, 119227, 119232)
put(data, 119808, 119892)
diff --git a/src/unicode/unicode.cr b/src/unicode/unicode.cr
index 1fb4b530686b..ab49ea31368b 100644
--- a/src/unicode/unicode.cr
+++ b/src/unicode/unicode.cr
@@ -1,7 +1,7 @@
# Provides the `Unicode::CaseOptions` enum for special case conversions like Turkic.
module Unicode
# The currently supported [Unicode](https://home.unicode.org) version.
- VERSION = "15.1.0"
+ VERSION = "16.0.0"
# Case options to pass to various `Char` and `String` methods such as `upcase` or `downcase`.
@[Flags]
diff --git a/src/uri/json.cr b/src/uri/json.cr
index 9767c9e98a02..00b58f419be5 100644
--- a/src/uri/json.cr
+++ b/src/uri/json.cr
@@ -25,4 +25,18 @@ class URI
def to_json(builder : JSON::Builder)
builder.string self
end
+
+ # Deserializes the given JSON *key* into a `URI`
+ #
+ # NOTE: `require "uri/json"` is required to opt-in to this feature.
+ def self.from_json_object_key?(key : String) : URI?
+ parse key
+ rescue URI::Error
+ nil
+ end
+
+ # :nodoc:
+ def to_json_object_key : String
+ to_s
+ end
end
diff --git a/src/uri/params/from_www_form.cr b/src/uri/params/from_www_form.cr
new file mode 100644
index 000000000000..819c9fc9d82e
--- /dev/null
+++ b/src/uri/params/from_www_form.cr
@@ -0,0 +1,67 @@
+# :nodoc:
+def Object.from_www_form(params : URI::Params, name : String)
+ return unless value = params[name]?
+
+ self.from_www_form value
+end
+
+# :nodoc:
+def Array.from_www_form(params : URI::Params, name : String)
+ name = if params.has_key? name
+ name
+ elsif params.has_key? "#{name}[]"
+ "#{name}[]"
+ else
+ return
+ end
+
+ params.fetch_all(name).map { |item| T.from_www_form(item).as T }
+end
+
+# :nodoc:
+def Bool.from_www_form(value : String)
+ case value
+ when "true", "1", "yes", "on" then true
+ when "false", "0", "no", "off" then false
+ end
+end
+
+# :nodoc:
+def Number.from_www_form(value : String)
+ new value, whitespace: false
+end
+
+# :nodoc:
+def String.from_www_form(value : String)
+ value
+end
+
+# :nodoc:
+def Enum.from_www_form(value : String)
+ parse value
+end
+
+# :nodoc:
+def Time.from_www_form(value : String)
+ Time::Format::ISO_8601_DATE_TIME.parse value
+end
+
+# :nodoc:
+def Union.from_www_form(params : URI::Params, name : String)
+ # Process non nilable types first as they are more likely to work.
+ {% for type in T.sort_by { |t| t.nilable? ? 1 : 0 } %}
+ begin
+ return {{type}}.from_www_form params, name
+ rescue
+ # Noop to allow next T to be tried.
+ end
+ {% end %}
+ raise ArgumentError.new "Invalid #{self}: '#{params[name]}'."
+end
+
+# :nodoc:
+def Nil.from_www_form(value : String) : Nil
+ return if value.empty?
+
+ raise ArgumentError.new "Invalid Nil value: '#{value}'."
+end
diff --git a/src/uri/params/serializable.cr b/src/uri/params/serializable.cr
new file mode 100644
index 000000000000..54d3b970e53c
--- /dev/null
+++ b/src/uri/params/serializable.cr
@@ -0,0 +1,129 @@
+require "uri"
+
+require "./to_www_form"
+require "./from_www_form"
+
+struct URI::Params
+ annotation Field; end
+
+ # The `URI::Params::Serializable` module automatically generates methods for `x-www-form-urlencoded` serialization when included.
+ #
+ # NOTE: To use this module, you must explicitly import it with `require "uri/params/serializable"`.
+ #
+ # ### Example
+ #
+ # ```
+ # require "uri/params/serializable"
+ #
+ # struct Applicant
+ # include URI::Params::Serializable
+ #
+ # getter first_name : String
+ # getter last_name : String
+ # getter qualities : Array(String)
+ # end
+ #
+ # applicant = Applicant.from_www_form "first_name=John&last_name=Doe&qualities=kind&qualities=smart"
+ # applicant.first_name # => "John"
+ # applicant.last_name # => "Doe"
+ # applicant.qualities # => ["kind", "smart"]
+ # applicant.to_www_form # => "first_name=John&last_name=Doe&qualities=kind&qualities=smart"
+ # ```
+ #
+ # ### Usage
+ #
+ # Including `URI::Params::Serializable` will create `#to_www_form` and `self.from_www_form` methods on the current class.
+ # By default, these methods serialize into a www form encoded string containing the value of every instance variable, the keys being the instance variable name.
+ # Union types are also supported, including unions with nil.
+ # If multiple types in a union parse correctly, it is undefined which one will be chosen.
+ #
+ # To change how individual instance variables are parsed, the annotation `URI::Params::Field` can be placed on the instance variable.
+ # Annotating property, getter and setter macros is also allowed.
+ #
+ # `URI::Params::Field` properties:
+ # * **converter**: specify an alternate type for parsing. The converter must define `.from_www_form(params : URI::Params, name : String)`.
+ # An example use case would be customizing the format when parsing `Time` instances, or supporting a type not natively supported.
+ #
+ # Deserialization also respects default values of variables:
+ # ```
+ # require "uri/params/serializable"
+ #
+ # struct A
+ # include URI::Params::Serializable
+ #
+ # @a : Int32
+ # @b : Float64 = 1.0
+ # end
+ #
+ # A.from_www_form("a=1") # => A(@a=1, @b=1.0)
+ # ```
+ module Serializable
+ macro included
+ def self.from_www_form(params : ::String)
+ new_from_www_form ::URI::Params.parse params
+ end
+
+ # :nodoc:
+ #
+ # This is needed so that nested types can pass the name thru internally.
+ # Has to be public so the generated code can call it, but should be considered an implementation detail.
+ def self.from_www_form(params : ::URI::Params, name : ::String)
+ new_from_www_form(params, name)
+ end
+
+ protected def self.new_from_www_form(params : ::URI::Params, name : ::String? = nil)
+ instance = allocate
+ instance.initialize(__uri_params: params, name: name)
+ GC.add_finalizer(instance) if instance.responds_to?(:finalize)
+ instance
+ end
+
+ macro inherited
+ def self.from_www_form(params : ::String)
+ new_from_www_form ::URI::Params.parse params
+ end
+
+ # :nodoc:
+ def self.from_www_form(params : ::URI::Params, name : ::String)
+ new_from_www_form(params, name)
+ end
+ end
+ end
+
+ # :nodoc:
+ def initialize(*, __uri_params params : ::URI::Params, name : String?)
+ {% begin %}
+ {% for ivar, idx in @type.instance_vars %}
+ %name{idx} = name.nil? ? {{ivar.name.stringify}} : "#{name}[#{{{ivar.name.stringify}}}]"
+ %value{idx} = {{(ann = ivar.annotation(URI::Params::Field)) && (converter = ann["converter"]) ? converter : ivar.type}}.from_www_form params, %name{idx}
+
+ unless %value{idx}.nil?
+ @{{ivar.name.id}} = %value{idx}
+ else
+ {% unless ivar.type.resolve.nilable? || ivar.has_default_value? %}
+ raise URI::SerializableError.new "Missing required property: '#{%name{idx}}'."
+ {% end %}
+ end
+ {% end %}
+ {% end %}
+ end
+
+ def to_www_form(*, space_to_plus : Bool = true) : String
+ URI::Params.build(space_to_plus: space_to_plus) do |form|
+ {% for ivar in @type.instance_vars %}
+ @{{ivar.name.id}}.to_www_form form, {{ivar.name.stringify}}
+ {% end %}
+ end
+ end
+
+ # :nodoc:
+ def to_www_form(builder : URI::Params::Builder, name : String)
+ {% for ivar in @type.instance_vars %}
+ @{{ivar.name.id}}.to_www_form builder, "#{name}[#{{{ivar.name.stringify}}}]"
+ {% end %}
+ end
+ end
+end
+
+class URI::SerializableError < URI::Error
+end
diff --git a/src/uri/params/to_www_form.cr b/src/uri/params/to_www_form.cr
new file mode 100644
index 000000000000..3a0007781e64
--- /dev/null
+++ b/src/uri/params/to_www_form.cr
@@ -0,0 +1,48 @@
+struct Bool
+ # :nodoc:
+ def to_www_form(builder : URI::Params::Builder, name : String) : Nil
+ builder.add name, to_s
+ end
+end
+
+class Array
+ # :nodoc:
+ def to_www_form(builder : URI::Params::Builder, name : String) : Nil
+ each &.to_www_form builder, name
+ end
+end
+
+class String
+ # :nodoc:
+ def to_www_form(builder : URI::Params::Builder, name : String) : Nil
+ builder.add name, self
+ end
+end
+
+struct Number
+ # :nodoc:
+ def to_www_form(builder : URI::Params::Builder, name : String) : Nil
+ builder.add name, to_s
+ end
+end
+
+struct Nil
+ # :nodoc:
+ def to_www_form(builder : URI::Params::Builder, name : String) : Nil
+ builder.add name, self
+ end
+end
+
+struct Enum
+ # :nodoc:
+ def to_www_form(builder : URI::Params::Builder, name : String) : Nil
+ builder.add name, to_s.underscore
+ end
+end
+
+struct Time
+ # :nodoc:
+ def to_www_form(builder : URI::Params::Builder, name : String) : Nil
+ builder.add name, to_rfc3339
+ end
+end
diff --git a/src/wait_group.cr b/src/wait_group.cr
index 2fd49c593b56..003921bd9f46 100644
--- a/src/wait_group.cr
+++ b/src/wait_group.cr
@@ -1,6 +1,6 @@
require "fiber"
+require "fiber/pointer_linked_list_node"
require "crystal/spin_lock"
-require "crystal/pointer_linked_list"
# Suspend execution until a collection of fibers are finished.
#
@@ -31,23 +31,46 @@ require "crystal/pointer_linked_list"
# wg.wait
# ```
class WaitGroup
- private struct Waiting
- include Crystal::PointerLinkedList::Node
-
- def initialize(@fiber : Fiber)
- end
-
- def enqueue : Nil
- @fiber.enqueue
- end
+ # Yields a `WaitGroup` instance and waits at the end of the block for all of
+ # the work enqueued inside it to complete.
+ #
+ # ```
+ # WaitGroup.wait do |wg|
+ # items.each do |item|
+ # wg.spawn { process item }
+ # end
+ # end
+ # ```
+ def self.wait(&) : Nil
+ instance = new
+ yield instance
+ instance.wait
end
def initialize(n : Int32 = 0)
- @waiting = Crystal::PointerLinkedList(Waiting).new
+ @waiting = Crystal::PointerLinkedList(Fiber::PointerLinkedListNode).new
@lock = Crystal::SpinLock.new
@counter = Atomic(Int32).new(n)
end
+ # Increment the counter by 1, perform the work inside the block in a separate
+ # fiber, decrementing the counter after it completes or raises. Returns the
+ # `Fiber` that was spawned.
+ #
+ # ```
+ # wg = WaitGroup.new
+ # wg.spawn { do_something }
+ # wg.wait
+ # ```
+ def spawn(*, name : String? = nil, &block) : Fiber
+ add
+ ::spawn(name: name) do
+ block.call
+ ensure
+ done
+ end
+ end
+
# Increments the counter by how many fibers we want to wait for.
#
# A negative value decrements the counter. When the counter reaches zero,
@@ -94,7 +117,7 @@ class WaitGroup
def wait : Nil
return if done?
- waiting = Waiting.new(Fiber.current)
+ waiting = Fiber::PointerLinkedListNode.new(Fiber.current)
@lock.sync do
# must check again to avoid a race condition where #done may have
diff --git a/src/winerror.cr b/src/winerror.cr
index ab978769d553..ae4eceb1f18e 100644
--- a/src/winerror.cr
+++ b/src/winerror.cr
@@ -2,6 +2,7 @@
require "c/winbase"
require "c/errhandlingapi"
require "c/winsock2"
+ require "c/winternl"
{% end %}
# `WinError` represents Windows' [System Error Codes](https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes#system-error-codes-1).
@@ -54,12 +55,23 @@ enum WinError : UInt32
{% end %}
end
+ def self.from_ntstatus(status) : self
+ {% if flag?(:win32) %}
+ WinError.new(LibNTDLL.RtlNtStatusToDosError(status))
+ {% else %}
+ raise NotImplementedError.new("WinError.from_ntstatus")
+ {% end %}
+ end
+
# Returns the system error message associated with this error code.
#
# The message is retrieved via [`FormatMessageW`](https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-formatmessagew)
# using the current default `LANGID`.
#
# On non-win32 platforms the result is always an empty string.
+ #
+ # NOTE: The result may depend on the current system locale. Specs and
+ # comparisons should use `#value` instead of this method.
def message : String
{% if flag?(:win32) %}
unsafe_message { |slice| String.from_utf16(slice).strip }
@@ -2305,6 +2317,7 @@ enum WinError : UInt32
ERROR_STATE_CONTAINER_NAME_SIZE_LIMIT_EXCEEDED = 15818_u32
ERROR_API_UNAVAILABLE = 15841_u32
- WSA_IO_PENDING = ERROR_IO_PENDING
- WSA_IO_INCOMPLETE = ERROR_IO_INCOMPLETE
+ WSA_IO_PENDING = ERROR_IO_PENDING
+ WSA_IO_INCOMPLETE = ERROR_IO_INCOMPLETE
+ WSA_INVALID_HANDLE = ERROR_INVALID_HANDLE
end
diff --git a/src/xml.cr b/src/xml.cr
index e0529be130f3..a9c9eab5d64e 100644
--- a/src/xml.cr
+++ b/src/xml.cr
@@ -107,12 +107,7 @@ module XML
end
protected def self.with_indent_tree_output(indent : Bool, &)
- ptr = {% if flag?(:win32) %}
- LibXML.__xmlIndentTreeOutput
- {% else %}
- pointerof(LibXML.xmlIndentTreeOutput)
- {% end %}
-
+ ptr = LibXML.__xmlIndentTreeOutput
old, ptr.value = ptr.value, indent ? 1 : 0
begin
yield
@@ -122,12 +117,7 @@ module XML
end
protected def self.with_tree_indent_string(string : String, &)
- ptr = {% if flag?(:win32) %}
- LibXML.__xmlTreeIndentString
- {% else %}
- pointerof(LibXML.xmlTreeIndentString)
- {% end %}
-
+ ptr = LibXML.__xmlTreeIndentString
old, ptr.value = ptr.value, string.to_unsafe
begin
yield
diff --git a/src/xml/error.cr b/src/xml/error.cr
index 868dfeb4bd00..389aa53910c2 100644
--- a/src/xml/error.cr
+++ b/src/xml/error.cr
@@ -11,22 +11,9 @@ class XML::Error < Exception
super(message)
end
- @@errors = [] of self
-
- # :nodoc:
- protected def self.add_errors(errors)
- @@errors.concat(errors)
- end
-
@[Deprecated("This class accessor is deprecated. XML errors are accessible directly in the respective context via `XML::Reader#errors` and `XML::Node#errors`.")]
def self.errors : Array(XML::Error)?
- if @@errors.empty?
- nil
- else
- errors = @@errors.dup
- @@errors.clear
- errors
- end
+ {% raise "`XML::Error.errors` was removed because it leaks memory when it's not used. XML errors are accessible directly in the respective context via `XML::Reader#errors` and `XML::Node#errors`.\nSee https://github.com/crystal-lang/crystal/issues/14934 for details. " %}
end
def self.collect(errors, &)
diff --git a/src/xml/libxml2.cr b/src/xml/libxml2.cr
index e1c2b8d12372..05b255ba23dc 100644
--- a/src/xml/libxml2.cr
+++ b/src/xml/libxml2.cr
@@ -4,6 +4,11 @@ require "./parser_options"
require "./html_parser_options"
require "./save_options"
+# Supported library versions:
+#
+# * libxml2
+#
+# See https://crystal-lang.org/reference/man/required_libraries.html#other-stdlib-libraries
@[Link("xml2", pkg_config: "libxml-2.0")]
{% if compare_versions(Crystal::VERSION, "1.11.0-dev") >= 0 %}
@[Link(dll: "libxml2.dll")]
@@ -13,14 +18,8 @@ lib LibXML
fun xmlInitParser
- # TODO: check if other platforms also support per-thread globals
- {% if flag?(:win32) %}
- fun __xmlIndentTreeOutput : Int*
- fun __xmlTreeIndentString : UInt8**
- {% else %}
- $xmlIndentTreeOutput : Int
- $xmlTreeIndentString : UInt8*
- {% end %}
+ fun __xmlIndentTreeOutput : Int*
+ fun __xmlTreeIndentString : UInt8**
alias Dtd = Void*
alias Dict = Void*
diff --git a/src/xml/reader.cr b/src/xml/reader.cr
index decdd8468185..d4dbe91f7eeb 100644
--- a/src/xml/reader.cr
+++ b/src/xml/reader.cr
@@ -198,9 +198,7 @@ class XML::Reader
end
private def collect_errors(&)
- Error.collect(@errors) { yield }.tap do
- Error.add_errors(@errors)
- end
+ Error.collect(@errors) { yield }
end
private def check_no_null_byte(attribute)
diff --git a/src/yaml/enums.cr b/src/yaml/enums.cr
index 2ab6789e0a4c..bf1d44fa2043 100644
--- a/src/yaml/enums.cr
+++ b/src/yaml/enums.cr
@@ -20,6 +20,10 @@ module YAML
DOUBLE_QUOTED
LITERAL
FOLDED
+
+ def quoted?
+ single_quoted? || double_quoted?
+ end
end
enum SequenceStyle
diff --git a/src/yaml/from_yaml.cr b/src/yaml/from_yaml.cr
index b9b6e7fae45c..227adb64c3c0 100644
--- a/src/yaml/from_yaml.cr
+++ b/src/yaml/from_yaml.cr
@@ -298,6 +298,13 @@ def Union.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node)
# So, we give a chance first to types in the union to be parsed.
{% string_type = T.find { |type| type == ::String } %}
+ {% if string_type %}
+ if node.as?(YAML::Nodes::Scalar).try(&.style.quoted?)
+ # do prefer String if it's a quoted scalar though
+ return String.new(ctx, node)
+ end
+ {% end %}
+
{% for type in T %}
{% unless type == string_type %}
begin
diff --git a/src/yaml/lib_yaml.cr b/src/yaml/lib_yaml.cr
index 0b4248afc793..d1527db63be2 100644
--- a/src/yaml/lib_yaml.cr
+++ b/src/yaml/lib_yaml.cr
@@ -1,5 +1,10 @@
require "./enums"
+# Supported library versions:
+#
+# * libyaml
+#
+# See https://crystal-lang.org/reference/man/required_libraries.html#other-stdlib-libraries
@[Link("yaml", pkg_config: "yaml-0.1")]
{% if compare_versions(Crystal::VERSION, "1.11.0-dev") >= 0 %}
@[Link(dll: "yaml.dll")]
diff --git a/src/yaml/serialization.cr b/src/yaml/serialization.cr
index d5fae8dfe9c0..4a1521469dea 100644
--- a/src/yaml/serialization.cr
+++ b/src/yaml/serialization.cr
@@ -156,11 +156,11 @@ module YAML
# Define a `new` directly in the included type,
# so it overloads well with other possible initializes
- def self.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node)
+ def self.new(ctx : ::YAML::ParseContext, node : ::YAML::Nodes::Node)
new_from_yaml_node(ctx, node)
end
- private def self.new_from_yaml_node(ctx : YAML::ParseContext, node : YAML::Nodes::Node)
+ private def self.new_from_yaml_node(ctx : ::YAML::ParseContext, node : ::YAML::Nodes::Node)
ctx.read_alias(node, self) do |obj|
return obj
end
@@ -170,7 +170,7 @@ module YAML
ctx.record_anchor(node, instance)
instance.initialize(__context_for_yaml_serializable: ctx, __node_for_yaml_serializable: node)
- GC.add_finalizer(instance) if instance.responds_to?(:finalize)
+ ::GC.add_finalizer(instance) if instance.responds_to?(:finalize)
instance
end
@@ -178,7 +178,7 @@ module YAML
# so it can compete with other possible initializes
macro inherited
- def self.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node)
+ def self.new(ctx : ::YAML::ParseContext, node : ::YAML::Nodes::Node)
new_from_yaml_node(ctx, node)
end
end
@@ -409,17 +409,17 @@ module YAML
{% mapping.raise "Mapping argument must be a HashLiteral or a NamedTupleLiteral, not #{mapping.class_name.id}" %}
{% end %}
- def self.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node)
+ def self.new(ctx : ::YAML::ParseContext, node : ::YAML::Nodes::Node)
ctx.read_alias(node, \{{@type}}) do |obj|
return obj
end
- unless node.is_a?(YAML::Nodes::Mapping)
+ unless node.is_a?(::YAML::Nodes::Mapping)
node.raise "Expected YAML mapping, not #{node.class}"
end
node.each do |key, value|
- next unless key.is_a?(YAML::Nodes::Scalar) && value.is_a?(YAML::Nodes::Scalar)
+ next unless key.is_a?(::YAML::Nodes::Scalar) && value.is_a?(::YAML::Nodes::Scalar)
next unless key.value == {{field.id.stringify}}
discriminator_value = value.value