Skip to content

Commit

Permalink
Merge branch 'crystal-lang:master' into fix-15269
Browse files Browse the repository at this point in the history
  • Loading branch information
rvprasad authored Dec 26, 2024
2 parents a7321e8 + eb56b55 commit 237607a
Show file tree
Hide file tree
Showing 22 changed files with 147 additions and 53 deletions.
5 changes: 5 additions & 0 deletions spec/compiler/macro/macro_methods_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion spec/compiler/parser/parser_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
10 changes: 10 additions & 0 deletions spec/std/process/status_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 11 additions & 1 deletion src/compiler/crystal/macros.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2361,14 +2361,20 @@ module Crystal::Macros
end
end

# An `if` inside a macro, e.g.
# An `if`/`unless` inside a macro, e.g.
#
# ```
# {% if cond %}
# puts "Then"
# {% else %}
# puts "Else"
# {% end %}
#
# {% unless cond %}
# puts "Then"
# {% else %}
# puts "Else"
# {% end %}
# ```
class MacroIf < ASTNode
# The condition of the `if` clause.
Expand All @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions src/compiler/crystal/macros/methods.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 4 additions & 3 deletions src/compiler/crystal/syntax/ast.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/crystal/syntax/parser.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/crystal/event_loop/epoll.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion src/crystal/event_loop/kqueue.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
6 changes: 3 additions & 3 deletions src/crystal/main.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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 %}
6 changes: 3 additions & 3 deletions src/crystal/scheduler.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions src/crystal/system/thread.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
24 changes: 18 additions & 6 deletions src/crystal/system/thread_linked_list.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
11 changes: 11 additions & 0 deletions src/crystal/system/unix/main.cr
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion src/crystal/system/unix/signal.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 2 additions & 3 deletions src/crystal/system/unix/time.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion src/crystal/system/wasi/main.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion src/crystal/system/win32/process.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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 }

Expand Down
22 changes: 11 additions & 11 deletions src/crystal/system/win32/wmain.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
51 changes: 36 additions & 15 deletions src/crystal/tracing.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit 237607a

Please sign in to comment.