Skip to content

Commit

Permalink
Refactor: explicit Crystal.once_init instead of __crystal_once_init
Browse files Browse the repository at this point in the history
Prefer a def explictly called by `Crystal.init_runtime` over the
implicit `__crystal_once_init` fun injected by the compiler.

The fun is no longer defined for new compiler builds, however it's still
defined to support older compiler releases.
  • Loading branch information
ysbaddaden committed Jan 27, 2025
1 parent 9e475f6 commit ffd6b86
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 56 deletions.
20 changes: 8 additions & 12 deletions src/compiler/crystal/codegen/once.cr
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,16 @@ class Crystal::CodeGenVisitor

def once_init
if once_init_fun = typed_fun?(@main_mod, ONCE_INIT)
# legacy (kept for backward compatibility): the compiler must save the
# state returned by __crystal_once_init
once_init_fun = check_main_fun ONCE_INIT, once_init_fun

if once_init_fun.type.return_type.void?
call once_init_fun
else
# legacy (kept for backward compatibility): the compiler must save the
# state returned by __crystal_once_init
once_state_global = @main_mod.globals.add(once_init_fun.type.return_type, ONCE_STATE)
once_state_global.linkage = LLVM::Linkage::Internal if @single_module
once_state_global.initializer = once_init_fun.type.return_type.null

state = call once_init_fun
store state, once_state_global
end
once_state_global = @main_mod.globals.add(once_init_fun.type.return_type, ONCE_STATE)
once_state_global.linkage = LLVM::Linkage::Internal if @single_module
once_state_global.initializer = once_init_fun.type.return_type.null

state = call once_init_fun
store state, once_state_global
end
end

Expand Down
3 changes: 2 additions & 1 deletion src/crystal/main.cr
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,10 @@ module Crystal
# :nodoc:
def self.init_runtime : Nil
# `__crystal_once` directly or indirectly depends on `Fiber` and `Thread`
# so we explicitly initialize their class vars
# so we explicitly initialize their class vars, then init crystal/once
Thread.init
Fiber.init
Crystal.once_init
end

# :nodoc:
Expand Down
88 changes: 45 additions & 43 deletions src/crystal/once.cr
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# This file defines two functions expected by the compiler:
# 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.
#
# - `__crystal_once_init`: executed only once at the beginning of the program
# and, for the legacy implementation, the result is passed on each call to
# `__crystal_once`.
# 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`.
#
# - `__crystal_once`: 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.

# In multithread mode a mutex is used to avoid race conditions between threads.
#
# On Win32, `Crystal::System::FileDescriptor#@@reader_thread` spawns a new
Expand All @@ -28,12 +28,15 @@

{% if flag?(:preview_mt) || flag?(:win32) %}
@@once_mutex = uninitialized Mutex

# :nodoc:
def self.once_mutex=(@@once_mutex : Mutex)
end
{% 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).
Expand Down Expand Up @@ -67,13 +70,6 @@
end
end

# :nodoc:
fun __crystal_once_init : Nil
{% if flag?(:preview_mt) || flag?(:win32) %}
Crystal.once_mutex = Mutex.new(:reentrant)
{% end %}
end

# :nodoc:
#
# Using `@[AlwaysInline]` allows LLVM to optimize const accesses. Since this
Expand All @@ -94,37 +90,43 @@
# This implementation uses a global array to store the initialization flag
# pointers for each value to find infinite loops and raise an error.

# :nodoc:
class Crystal::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

@rec.pop
end
end

{% if flag?(:preview_mt) || flag?(:win32) %}
@mutex = Mutex.new(:reentrant)
module Crystal
# :nodoc:
class OnceState
@rec = [] of Bool*

@[NoInline]
def once(flag : Bool*, initializer : Void*)
unless flag.value
@mutex.synchronize do
previous_def
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

@rec.pop
end
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

# :nodoc:
def self.once_init : Nil
end
end

# :nodoc:
Expand Down

0 comments on commit ffd6b86

Please sign in to comment.