Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions src/crystal/system/thread_local.cr
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
class Thread
struct Local(T)
# Reserves space for saving a `T` reference on each thread.
def initialize
{% unless T < Reference || T < Pointer || T.union_types.all? { |t| t == Nil || t < Reference } %}
{% raise "Can only create Thread::Local with reference types, nilable reference types, or pointer types, not {{T}}" %}
{% end %}
end
# def self.new
# {% unless T < Reference || T < Pointer || T.union_types.all? { |t| t == Nil || t < Reference } %}
# {% raise "Can only create Thread::Local with reference types, nilable reference types, or pointer types, not {{T}}" %}
# {% end %}
# end

# Reserves space for saving a `T` reference on each thread and registers a
# destructor that will be called when a thread terminates if the local value
# has been set.
def initialize(&destructor : Proc(T, Nil))
{% unless T < Reference || T < Pointer || T.union_types.all? { |t| t == Nil || t < Reference } %}
{% raise "Can only create Thread::Local with reference types, nilable reference types, or pointer types, not {{T}}" %}
{% end %}
end
# def self.new(&destructor : Proc(T, Nil))
# {% unless T < Reference || T < Pointer || T.union_types.all? { |t| t == Nil || t < Reference } %}
# {% raise "Can only create Thread::Local with reference types, nilable reference types, or pointer types, not {{T}}" %}
# {% end %}
# end

# Returns the current local value for the thread; if unset constructs one by
# yielding and sets it as the current local value.
Expand Down
24 changes: 12 additions & 12 deletions src/crystal/system/unix/thread_local.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,22 @@ require "c/pthread"

class Thread
struct Local(T)
@key = uninitialized LibC::PthreadKeyT

def initialize
previous_def
@key = pthread_key_create(nil)
def self.new : self
err = LibC.pthread_key_create(out key, nil)
raise RuntimeError.from_os_error("pthread_key_create", Errno.new(err)) unless err == 0
new(key)
end

def initialize(&destructor : Proc(T, Nil))
previous_def(&destructor)
@key = pthread_key_create(destructor.unsafe_as(Proc(Void*, Nil)))
def self.new(&destructor : Proc(T, Nil)) : self
err = LibC.pthread_key_create(out key, destructor.unsafe_as(Proc(Void*, Nil)))
raise RuntimeError.from_os_error("pthread_key_create", Errno.new(err)) unless err == 0
new(key)
end

private def pthread_key_create(destructor)
err = LibC.pthread_key_create(out key, destructor)
raise RuntimeError.from_os_error("pthread_key_create", Errno.new(err)) unless err == 0
key
def initialize(@key : LibC::PthreadKeyT)
{% unless T < Reference || T < Pointer || T.union_types.all? { |t| t == Nil || t < Reference } %}
{% raise "Can only create Thread::Local with reference types, nilable reference types, or pointer types, not {{T}}" %}
{% end %}
end

def get? : T?
Expand Down
10 changes: 6 additions & 4 deletions src/crystal/system/wasi/thread_local.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ class Thread
struct Local(T)
@value : T?

def initialize
previous_def
def self.new(&destructor : T ->) : self
new
end

def initialize(&destructor : T ->)
previous_def(&destructor)
def initialize
{% unless T < Reference || T < Pointer || T.union_types.all? { |t| t == Nil || t < Reference } %}
{% raise "Can only create Thread::Local with reference types, nilable reference types, or pointer types, not {{T}}" %}
{% end %}
end

def get? : T?
Expand Down
24 changes: 12 additions & 12 deletions src/crystal/system/win32/thread_local.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,22 @@ require "c/fibersapi"

class Thread
struct Local(T)
@key = uninitialized LibC::DWORD

def initialize
previous_def
@key = fls_alloc(nil)
def self.new : self
key = LibC.FlsAlloc(nil)
raise RuntimeError.from_winerror("FlsAlloc: out of indexes") if key == LibC::FLS_OUT_OF_INDEXES
new(key)
end

def initialize(&destructor : Proc(T, Nil))
previous_def(&destructor)
@key = fls_alloc(destructor.unsafe_as(LibC::FLS_CALLBACK_FUNCTION))
def self.new(&destructor : Proc(T, Nil))
key = LibC.FlsAlloc(destructor.unsafe_as(LibC::FLS_CALLBACK_FUNCTION))
raise RuntimeError.from_winerror("FlsAlloc: out of indexes") if key == LibC::FLS_OUT_OF_INDEXES
new(key)
end

private def fls_alloc(destructor)
key = LibC.FlsAlloc(destructor)
raise RuntimeError.from_winerror("FlsAlloc: out of indexes") if key == LibC::FLS_OUT_OF_INDEXES
key
def initialize(@key : LibC::DWORD)
{% unless T < Reference || T < Pointer || T.union_types.all? { |t| t == Nil || t < Reference } %}
{% raise "Can only create Thread::Local with reference types, nilable reference types, or pointer types, not {{T}}" %}
{% end %}
end

def get? : T?
Expand Down
2 changes: 2 additions & 0 deletions src/regex/lib_pcre2.cr
Original file line number Diff line number Diff line change
Expand Up @@ -264,10 +264,12 @@ lib LibPCRE2

fun jit_stack_create = pcre2_jit_stack_create_8(startsize : LibC::SizeT, maxsize : LibC::SizeT, gcontext : GeneralContext*) : JITStack*
fun jit_stack_assign = pcre2_jit_stack_assign_8(mcontext : MatchContext*, callable_function : Void* -> JITStack*, callable_data : Void*) : Void
fun jit_stack_free = pcre2_jit_stack_free_8(jit_stack : JITStack*) : Void

fun pattern_info = pcre2_pattern_info_8(code : Code*, what : UInt32, where : Void*) : Int

fun match = pcre2_match_8(code : Code*, subject : UInt8*, length : LibC::SizeT, startoffset : LibC::SizeT, options : UInt32, match_data : MatchData*, mcontext : MatchContext*) : Int
fun match_data_create = pcre2_match_data_create_8(ovecsize : UInt32, gcontext : GeneralContext*) : MatchData*
fun match_data_create_from_pattern = pcre2_match_data_create_from_pattern_8(code : Code*, gcontext : GeneralContext*) : MatchData*
fun match_data_free = pcre2_match_data_free_8(match_data : MatchData*) : Void

Expand Down
55 changes: 33 additions & 22 deletions src/regex/pcre2.cr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
require "./lib_pcre2"
require "crystal/thread_local_value"
require "crystal/system/thread_local"

# :nodoc:
module Regex::PCRE2
Expand Down Expand Up @@ -207,12 +207,14 @@ module Regex::PCRE2
private def match_impl(str, byte_index, options)
match_data = match_data(str, byte_index, options) || return

ovector_count = LibPCRE2.get_ovector_count(match_data)
# we reuse the same matchdata allocation, so we must "reimplement" the
# behavior of pcre2_match_data_create_from_pattern (get_ovector_count always
# returns 65535, aka the maximum):
ovector_count = capture_count_impl &+ 1
ovector = Slice.new(LibPCRE2.get_ovector_pointer(match_data), ovector_count &* 2)

# We need to dup the ovector because `match_data` is re-used for subsequent
# matches (see `@match_data`).
# Dup brings the ovector data into the realm of the GC.
# matches. Dup brings the ovector data into the realm of the GC.
ovector = ovector.dup

::Regex::MatchData.new(self, @re, str, byte_index, ovector.to_unsafe, ovector_count.to_i32 &- 1)
Expand All @@ -228,43 +230,52 @@ module Regex::PCRE2

class_getter match_context : LibPCRE2::MatchContext* do
match_context = LibPCRE2.match_context_create(nil)
LibPCRE2.jit_stack_assign(match_context, ->(_data) { Regex::PCRE2.jit_stack }, nil)
LibPCRE2.jit_stack_assign(match_context, ->(_data) { current_jit_stack }, nil)
match_context
end

# Returns a JIT stack that's shared in the current thread.
# JIT stack is unique per thread.
#
# Only a single `match` function can run per thread at any given time, so there
# can't be any concurrent access to the JIT stack.
@@jit_stack = Crystal::ThreadLocalValue(LibPCRE2::JITStack*).new
# Only a single `match` function can run per thread at any given time, so
# there can't be any concurrent access to the JIT stack.
protected class_getter(jit_stack) do
Thread::Local(LibPCRE2::JITStack*).new do |jit_stack|
LibPCRE2.jit_stack_free(jit_stack)
end
end

def self.jit_stack
@@jit_stack.get do
protected def self.current_jit_stack
jit_stack.get do
LibPCRE2.jit_stack_create(32_768, 1_048_576, nil) || raise "Error allocating JIT stack"
end
end

# Match data is shared per instance and thread.
# Match data is unique per thread.
#
# Match data contains a buffer for backtracking when matching in interpreted mode (non-JIT).
# This buffer is heap-allocated and should be re-used for subsequent matches.
@match_data = Crystal::ThreadLocalValue(LibPCRE2::MatchData*).new
# Match data contains a buffer for backtracking when matching in interpreted
# mode (non-JIT). This buffer is heap-allocated and should be re-used for
# subsequent matches.
protected class_getter(match_data) do
Thread::Local(LibPCRE2::MatchData*).new do |match_data|
LibPCRE2.match_data_free(match_data)
end
end

private def match_data
@match_data.get do
LibPCRE2.match_data_create_from_pattern(@re, nil)
protected def self.current_match_data : LibPCRE2::MatchData*
match_data.get do
# the ovector size is clamped to 65535 pairs; we declare the maximum
# because we allocate the match data buffer once for the thread and need
# to adapt to any regular expression
LibPCRE2.match_data_create(65_535, nil)
end
end

def finalize
@match_data.consume_each do |match_data|
LibPCRE2.match_data_free(match_data)
end
LibPCRE2.code_free @re
end

private def match_data(str, byte_index, options)
match_data = self.match_data
match_data = Regex::PCRE2.current_match_data
match_count = LibPCRE2.match(@re, str, str.bytesize, byte_index, pcre2_match_options(options), match_data, PCRE2.match_context)

if match_count < 0
Expand Down
Loading