diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index 10ba78d5bdc6..979a507d624d 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -2726,6 +2726,11 @@ module Crystal it "executes else" do assert_macro %({{x.else}}), "\"foo\"", {x: MacroIf.new(BoolLiteral.new(true), StringLiteral.new("test"), StringLiteral.new("foo"))} end + + it "executes is_unless?" do + assert_macro %({{x.is_unless?}}), "true", {x: MacroIf.new(BoolLiteral.new(true), StringLiteral.new("test"), StringLiteral.new("foo"), is_unless: true)} + assert_macro %({{x.is_unless?}}), "false", {x: MacroIf.new(BoolLiteral.new(false), StringLiteral.new("test"), StringLiteral.new("foo"), is_unless: false)} + end end describe "macro for methods" do diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index 897e5bf7060c..ab8b1e9edfca 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -1156,7 +1156,7 @@ module Crystal it_parses "macro foo;bar{% if x %}body{% else %}body2{%end%}baz;end", Macro.new("foo", [] of Arg, Expressions.from(["bar".macro_literal, MacroIf.new("x".var, "body".macro_literal, "body2".macro_literal), "baz;".macro_literal] of ASTNode)) it_parses "macro foo;bar{% if x %}body{% elsif y %}body2{%end%}baz;end", Macro.new("foo", [] of Arg, Expressions.from(["bar".macro_literal, MacroIf.new("x".var, "body".macro_literal, MacroIf.new("y".var, "body2".macro_literal)), "baz;".macro_literal] of ASTNode)) it_parses "macro foo;bar{% if x %}body{% elsif y %}body2{% else %}body3{%end%}baz;end", Macro.new("foo", [] of Arg, Expressions.from(["bar".macro_literal, MacroIf.new("x".var, "body".macro_literal, MacroIf.new("y".var, "body2".macro_literal, "body3".macro_literal)), "baz;".macro_literal] of ASTNode)) - it_parses "macro foo;bar{% unless x %}body{% end %}baz;end", Macro.new("foo", [] of Arg, Expressions.from(["bar".macro_literal, MacroIf.new("x".var, Nop.new, "body".macro_literal), "baz;".macro_literal] of ASTNode)) + it_parses "macro foo;bar{% unless x %}body{% end %}baz;end", Macro.new("foo", [] of Arg, Expressions.from(["bar".macro_literal, MacroIf.new("x".var, Nop.new, "body".macro_literal, is_unless: true), "baz;".macro_literal] of ASTNode)) it_parses "macro foo;bar{% for x in y %}\\ \n body{% end %}baz;end", Macro.new("foo", [] of Arg, Expressions.from(["bar".macro_literal, MacroFor.new(["x".var], "y".var, "body".macro_literal), "baz;".macro_literal] of ASTNode)) it_parses "macro foo;bar{% for x in y %}\\ \n body{% end %}\\ baz;end", Macro.new("foo", [] of Arg, Expressions.from(["bar".macro_literal, MacroFor.new(["x".var], "y".var, "body".macro_literal), "baz;".macro_literal] of ASTNode)) diff --git a/spec/std/process/status_spec.cr b/spec/std/process/status_spec.cr index b56f261c4b5c..6cbabbea5d73 100644 --- a/spec/std/process/status_spec.cr +++ b/spec/std/process/status_spec.cr @@ -42,6 +42,16 @@ describe Process::Status do status_for(:interrupted).exit_code?.should be_nil end + it "#system_exit_status" do + Process::Status.new(exit_status(0)).system_exit_status.should eq 0_u32 + Process::Status.new(exit_status(1)).system_exit_status.should eq({{ flag?(:unix) ? 0x0100_u32 : 1_u32 }}) + Process::Status.new(exit_status(127)).system_exit_status.should eq({{ flag?(:unix) ? 0x7f00_u32 : 127_u32 }}) + Process::Status.new(exit_status(128)).system_exit_status.should eq({{ flag?(:unix) ? 0x8000_u32 : 128_u32 }}) + Process::Status.new(exit_status(255)).system_exit_status.should eq({{ flag?(:unix) ? 0xFF00_u32 : 255_u32 }}) + + status_for(:interrupted).system_exit_status.should eq({% if flag?(:unix) %}Signal::INT.value{% else %}LibC::STATUS_CONTROL_C_EXIT{% end %}) + end + it "#success?" do Process::Status.new(exit_status(0)).success?.should be_true Process::Status.new(exit_status(1)).success?.should be_false diff --git a/src/compiler/crystal/macros.cr b/src/compiler/crystal/macros.cr index a2ea0aeb85fe..ae6634e83a6f 100644 --- a/src/compiler/crystal/macros.cr +++ b/src/compiler/crystal/macros.cr @@ -2361,7 +2361,7 @@ module Crystal::Macros end end - # An `if` inside a macro, e.g. + # An `if`/`unless` inside a macro, e.g. # # ``` # {% if cond %} @@ -2369,6 +2369,12 @@ module Crystal::Macros # {% else %} # puts "Else" # {% end %} + # + # {% unless cond %} + # puts "Then" + # {% else %} + # puts "Else" + # {% end %} # ``` class MacroIf < ASTNode # The condition of the `if` clause. @@ -2382,6 +2388,10 @@ module Crystal::Macros # The `else` branch of the `if`. def else : ASTNode end + + # Returns `true` if this node represents an `unless` conditional, otherwise returns `false`. + def is_unless? : BoolLiteral + end end # A `for` loop inside a macro, e.g. diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index ab7b353fec45..f2691ba707c9 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -1541,6 +1541,8 @@ module Crystal interpret_check_args { @then } when "else" interpret_check_args { @else } + when "is_unless?" + interpret_check_args { BoolLiteral.new @is_unless } else super end diff --git a/src/compiler/crystal/syntax/ast.cr b/src/compiler/crystal/syntax/ast.cr index 9ccd8dda1f69..fb443e2e6777 100644 --- a/src/compiler/crystal/syntax/ast.cr +++ b/src/compiler/crystal/syntax/ast.cr @@ -2238,8 +2238,9 @@ module Crystal property cond : ASTNode property then : ASTNode property else : ASTNode + property? is_unless : Bool - def initialize(@cond, a_then = nil, a_else = nil) + def initialize(@cond, a_then = nil, a_else = nil, @is_unless : Bool = false) @then = Expressions.from a_then @else = Expressions.from a_else end @@ -2251,10 +2252,10 @@ module Crystal end def clone_without_location - MacroIf.new(@cond.clone, @then.clone, @else.clone) + MacroIf.new(@cond.clone, @then.clone, @else.clone, @is_unless) end - def_equals_and_hash @cond, @then, @else + def_equals_and_hash @cond, @then, @else, @is_unless end # for inside a macro: diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index 569bbd4d9409..60a3ec6414a7 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -3505,7 +3505,7 @@ module Crystal end a_then, a_else = a_else, a_then if is_unless - MacroIf.new(cond, a_then, a_else).at_end(token_end_location) + MacroIf.new(cond, a_then, a_else, is_unless: is_unless).at_end(token_end_location) end def parse_expression_inside_macro diff --git a/src/crystal/event_loop/epoll.cr b/src/crystal/event_loop/epoll.cr index 2d7d08ce7c94..371b9039b6b5 100644 --- a/src/crystal/event_loop/epoll.cr +++ b/src/crystal/event_loop/epoll.cr @@ -55,7 +55,7 @@ class Crystal::EventLoop::Epoll < Crystal::EventLoop::Polling {% end %} private def system_run(blocking : Bool, & : Fiber ->) : Nil - Crystal.trace :evloop, "run", blocking: blocking ? 1 : 0 + Crystal.trace :evloop, "run", blocking: blocking # wait for events (indefinitely when blocking) buffer = uninitialized LibC::EpollEvent[128] diff --git a/src/crystal/event_loop/kqueue.cr b/src/crystal/event_loop/kqueue.cr index 52a7701ef2b1..47f00ddc9e89 100644 --- a/src/crystal/event_loop/kqueue.cr +++ b/src/crystal/event_loop/kqueue.cr @@ -73,7 +73,7 @@ class Crystal::EventLoop::Kqueue < Crystal::EventLoop::Polling private def system_run(blocking : Bool, & : Fiber ->) : Nil buffer = uninitialized LibC::Kevent[128] - Crystal.trace :evloop, "run", blocking: blocking ? 1 : 0 + Crystal.trace :evloop, "run", blocking: blocking timeout = blocking ? nil : Time::Span.zero kevents = @kqueue.wait(buffer.to_slice, timeout) diff --git a/src/crystal/main.cr b/src/crystal/main.cr index 625238229c58..9b4384f16a8c 100644 --- a/src/crystal/main.cr +++ b/src/crystal/main.cr @@ -132,8 +132,8 @@ end {% if 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/scheduler.cr b/src/crystal/scheduler.cr index ad0f2a55672e..efee6b3c06f1 100644 --- a/src/crystal/scheduler.cr +++ b/src/crystal/scheduler.cr @@ -66,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 @@ -225,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 @@ -272,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/system/thread.cr b/src/crystal/system/thread.cr index 0d6f5077633a..92136d1f3989 100644 --- a/src/crystal/system/thread.cr +++ b/src/crystal/system/thread.cr @@ -74,6 +74,10 @@ 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 @@ -182,6 +186,11 @@ 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 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/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/signal.cr b/src/crystal/system/unix/signal.cr index 802cb418db15..a68108ad327a 100644 --- a/src/crystal/system/unix/signal.cr +++ b/src/crystal/system/unix/signal.cr @@ -82,7 +82,7 @@ module Crystal::System::Signal 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 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/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/win32/process.cr b/src/crystal/system/win32/process.cr index 5eb02d826c3b..5249491bbd3f 100644 --- a/src/crystal/system/win32/process.cr +++ b/src/crystal/system/win32/process.cr @@ -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 } diff --git a/src/crystal/system/win32/wmain.cr b/src/crystal/system/win32/wmain.cr index 2120bfc06bfc..b1726f90329b 100644 --- a/src/crystal/system/win32/wmain.cr +++ b/src/crystal/system/win32/wmain.cr @@ -12,17 +12,13 @@ require "c/stdlib" 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 @@ -46,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/tracing.cr b/src/crystal/tracing.cr index 684680b10b28..d9508eda85a8 100644 --- a/src/crystal/tracing.cr +++ b/src/crystal/tracing.cr @@ -51,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/fiber.cr b/src/fiber.cr index 2b596a16017c..55745666c66d 100644 --- a/src/fiber.cr +++ b/src/fiber.cr @@ -78,6 +78,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. diff --git a/src/process/status.cr b/src/process/status.cr index db759bd1c178..268d2f9e52d6 100644 --- a/src/process/status.cr +++ b/src/process/status.cr @@ -104,10 +104,18 @@ end 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) @@ -261,7 +269,7 @@ class Process::Status # define __WEXITSTATUS(status) (((status) & 0xff00) >> 8) (@exit_status & 0xff00) >> 8 {% else %} - exit_status + @exit_status.to_i32! {% end %} end