crystalruby 0.1.13 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,234 @@
1
+ module CrystalRuby
2
+ # This class represents a single Crystalized function.
3
+ # Each such function belongs a shared lib (See: CrystalRuby::Library)
4
+ # and is attached to a single owner (a class or a module).
5
+ class Function
6
+ include Typemaps
7
+ include Config
8
+
9
+ attr_accessor :owner, :method_name, :args, :returns, :function_body, :lib, :async, :block, :attached
10
+
11
+ def initialize(method:, args:, returns:, function_body:, lib:, async: false, &block)
12
+ self.owner = method.owner
13
+ self.method_name = method.name
14
+ self.args = args
15
+ self.returns = returns
16
+ self.function_body = function_body
17
+ self.lib = lib
18
+ self.async = async
19
+ self.block = block
20
+ self.attached = false
21
+ end
22
+
23
+ # This is where we write/overwrite the class and instance methods
24
+ # with their crystalized equivalents.
25
+ # We also perform JIT compilation and JIT attachment of the FFI functions.
26
+ # Crystalized methods can be redefined without restarting, if running in a live-reloading environment.
27
+ # If they are redefined with a different function body, the new function body
28
+ # will result in a new digest and the FFI function will be recompiled and reattached.
29
+ def define_crystalized_methods!(lib)
30
+ func = self
31
+ [owner, owner.singleton_class].each do |receiver|
32
+ receiver.undef_method(method_name) if receiver.method_defined?(method_name)
33
+ receiver.define_method(method_name) do |*args|
34
+ unless lib.compiled?
35
+ lib.build!
36
+ return send(func.method_name, *args)
37
+ end
38
+ unless func.attached?
39
+ should_reenter = func.attach_ffi_lib_functions!
40
+ return send(func.method_name, *args) if should_reenter
41
+ end
42
+ # All crystalruby functions are executed on the reactor to ensure Crystal/Ruby interop code is executed
43
+ # from a single same thread. (Needed to make GC and Fiber scheduler happy)
44
+ # Type mapping (if required) is applied on arguments and on return values.
45
+ func.map_retval(
46
+ Reactor.schedule_work!(
47
+ func.owner,
48
+ func.ffi_name,
49
+ *func.map_args(args),
50
+ func.ffi_ret_type,
51
+ async: func.async,
52
+ lib: lib
53
+ )
54
+ )
55
+ end
56
+ end
57
+ end
58
+
59
+ # This is where we attach the top-level FFI functions of the shared object
60
+ # to our library (yield and init) needed for successful operation of the reactor.
61
+ # We also initialize the shared object (needed to start the GC) and
62
+ # start the reactor, unless we are in single-thread mode.
63
+ def attach_ffi_lib_functions!
64
+ should_reenter = unwrapped?
65
+ lib_file = lib.lib_file
66
+ lib.methods.each_value(&:attach_ffi_func!)
67
+ lib.singleton_class.class_eval do
68
+ extend FFI::Library
69
+ ffi_lib lib_file
70
+ %i[yield init].each do |method_name|
71
+ singleton_class.undef_method(method_name) if singleton_class.method_defined?(method_name)
72
+ undef_method(method_name) if method_defined?(method_name)
73
+ end
74
+ attach_function :init, %i[string pointer], :void
75
+ attach_function :yield, %i[], :int
76
+ end
77
+
78
+ if CrystalRuby.config.single_thread_mode
79
+ Reactor.init_single_thread_mode!
80
+ else
81
+ Reactor.start!
82
+ end
83
+ Reactor.schedule_work!(lib, :init, lib.name, Reactor::ERROR_CALLBACK, :void, blocking: true, async: false)
84
+ should_reenter
85
+ end
86
+
87
+ # Attaches the crystalized FFI functions to their related Ruby modules and classes.
88
+ # If a wrapper block has been passed to the crystalize function,
89
+ # then the we also wrap the crystalized function using a prepended Module.
90
+ def attach_ffi_func!
91
+ argtypes = ffi_types
92
+ rettype = ffi_ret_type
93
+ if async && !config.single_thread_mode
94
+ argtypes += %i[int pointer]
95
+ rettype = :void
96
+ end
97
+
98
+ owner.extend FFI::Library unless owner.is_a?(FFI::Library)
99
+
100
+ unless (owner.instance_variable_get(:@ffi_libs) || [])
101
+ .map(&:name)
102
+ .map(&File.method(:basename))
103
+ .include?(File.basename(lib.lib_file))
104
+ owner.ffi_lib lib.lib_file
105
+ end
106
+
107
+ if owner.method_defined?(ffi_name)
108
+ owner.undef_method(ffi_name)
109
+ owner.singleton_class.undef_method(ffi_name)
110
+ end
111
+
112
+ owner.attach_function ffi_name, argtypes, rettype, blocking: true
113
+ around_wrapper_block = block
114
+ method_name = self.method_name
115
+ @attached = true
116
+ return unless around_wrapper_block
117
+
118
+ @around_wrapper ||= begin
119
+ wrapper_module = Module.new {}
120
+ [owner, owner.singleton_class].each do |receiver|
121
+ receiver.prepend(wrapper_module)
122
+ end
123
+ wrapper_module
124
+ end
125
+ @around_wrapper.undef_method(method_name) if @around_wrapper.method_defined?(method_name)
126
+ @around_wrapper.define_method(method_name, &around_wrapper_block)
127
+ rescue StandardError => e
128
+ CrystalRuby.log_error("Error attaching #{method_name} as #{ffi_name} to #{owner.name}")
129
+ CrystalRuby.log_error(e.message)
130
+ CrystalRuby.log_error(e.backtrace.join("\n"))
131
+ end
132
+
133
+ def unwrapped?
134
+ block && !@around_wrapper
135
+ end
136
+
137
+ def attached?
138
+ @attached
139
+ end
140
+
141
+ def unattach!
142
+ @attached = false
143
+ end
144
+
145
+ def ffi_name
146
+ lib_fn_name + (async && !config.single_thread_mode ? "_async" : "")
147
+ end
148
+
149
+ def lib_fn_name
150
+ @lib_fn_name ||= "#{owner.name.downcase.gsub("::", "_")}_#{method_name}_#{Digest::MD5.hexdigest(function_body)}"
151
+ end
152
+
153
+ def arg_type_map
154
+ @arg_type_map ||= args.transform_values(&method(:build_type_map))
155
+ end
156
+
157
+ def lib_fn_args
158
+ @lib_fn_args ||= arg_type_map.map { |k, arg_type|
159
+ "_#{k} : #{arg_type[:lib_type]}"
160
+ }.join(",") + (arg_type_map.empty? ? "" : ", ")
161
+ end
162
+
163
+ def lib_fn_arg_names
164
+ @lib_fn_arg_names ||= arg_type_map.map { |k, _arg_type|
165
+ "_#{k}"
166
+ }.join(",") + (arg_type_map.empty? ? "" : ", ")
167
+ end
168
+
169
+ def return_type_map
170
+ @return_type_map ||= build_type_map(returns)
171
+ end
172
+
173
+ def ffi_types
174
+ @ffi_types ||= arg_type_map.map { |_k, arg_type| arg_type[:ffi_type] }
175
+ end
176
+
177
+ def arg_maps
178
+ @arg_maps ||= arg_type_map.map { |_k, arg_type| arg_type[:arg_mapper] }
179
+ end
180
+
181
+ def ffi_ret_type
182
+ @ffi_ret_type ||= return_type_map[:ffi_ret_type]
183
+ end
184
+
185
+ def register_custom_types!(lib)
186
+ [*arg_type_map.values, return_type_map].map { |t| t[:crystal_ruby_type] }.each do |crystalruby_type|
187
+ if crystalruby_type.is_a?(Types::TypeSerializer) && !crystalruby_type.anonymous?
188
+ lib.register_type!(crystalruby_type)
189
+ end
190
+ end
191
+ end
192
+
193
+ def map_args(args)
194
+ return args unless arg_maps.any?
195
+
196
+ arg_maps.each_with_index do |argmap, index|
197
+ next unless argmap
198
+
199
+ args[index] = argmap[args[index]]
200
+ end
201
+ args
202
+ end
203
+
204
+ def map_retval(retval)
205
+ return retval unless return_type_map[:retval_mapper]
206
+
207
+ return_type_map[:retval_mapper][retval]
208
+ end
209
+
210
+ def chunk
211
+ @chunk ||= Template::Function.render(
212
+ {
213
+ module_name: owner.name,
214
+ lib_fn_name: lib_fn_name,
215
+ fn_name: method_name,
216
+ fn_body: function_body,
217
+ callback_call: returns == :void ? "callback.call(thread_id)" : "callback.call(thread_id, converted)",
218
+ callback_type: return_type_map[:ffi_type] == :void ? "UInt32 -> Void" : " UInt32, #{return_type_map[:lib_type]} -> Void",
219
+ fn_args: arg_type_map.map { |k, arg_type| "#{k} : #{arg_type[:crystal_type]}" }.join(","),
220
+ fn_ret_type: return_type_map[:crystal_type],
221
+ lib_fn_args: lib_fn_args,
222
+ lib_fn_arg_names: lib_fn_arg_names,
223
+ lib_fn_ret_type: return_type_map[:lib_type],
224
+ convert_lib_args: arg_type_map.map do |k, arg_type|
225
+ "#{k} = #{arg_type[:convert_lib_to_crystal_type]["_#{k}"]}"
226
+ end.join("\n "),
227
+ arg_names: args.keys.join(","),
228
+ convert_return_type: return_type_map[:convert_crystal_to_lib_type]["return_value"],
229
+ error_value: return_type_map[:error_value]
230
+ }
231
+ )
232
+ end
233
+ end
234
+ end
@@ -0,0 +1,191 @@
1
+ module CrystalRuby
2
+ class Library
3
+ include Typemaps
4
+ include Config
5
+
6
+ # *CR_ATTACH_MUX* and *CR_COMPILE_MUX* are used to only allow a single FFI compile or attach operation at once
7
+ # to avoid a rare scenario where the same function is attached simultaneously across two or more threads.
8
+ CR_COMPILE_MUX = Mutex.new
9
+ CR_ATTACH_MUX = Mutex.new
10
+
11
+ attr_accessor :name, :methods, :chunks, :root_dir, :lib_dir, :src_dir, :codegen_dir, :reactor
12
+
13
+ @libs_by_name = {}
14
+
15
+ def self.all
16
+ @libs_by_name.values
17
+ end
18
+
19
+ def self.[](name)
20
+ @libs_by_name[name] ||= begin
21
+ CrystalRuby.initialize_crystal_ruby! unless CrystalRuby.initialized?
22
+ Library.new(name)
23
+ end
24
+ end
25
+
26
+ # A Library represents a single Crystal shared object.
27
+ # It holds code as either methods (invokable from Ruby and attached) or
28
+ # anonymous chunks, which are just raw Crystal code.
29
+ def initialize(name)
30
+ self.name = name
31
+ self.methods = {}
32
+ self.chunks = []
33
+ initialize_library!
34
+ end
35
+
36
+ # Bootstraps the library filesystem and generates top level index.cr and shard files if
37
+ # these do not already exist.
38
+ def initialize_library!
39
+ @root_dir, @lib_dir, @src_dir, @codegen_dir = [
40
+ config.crystal_src_dir_abs / name,
41
+ config.crystal_src_dir_abs / name / "lib",
42
+ config.crystal_src_dir_abs / name / "src",
43
+ config.crystal_src_dir_abs / name / "src" / config.crystal_codegen_dir
44
+ ].each do |dir|
45
+ FileUtils.mkdir_p(dir)
46
+ end
47
+ IO.write main_file, "require \"./#{config.crystal_codegen_dir}/index\"\n" unless File.exist?(main_file)
48
+
49
+ return if File.exist?(shard_file)
50
+
51
+ IO.write(shard_file, <<~YAML)
52
+ name: src
53
+ version: 0.1.0
54
+ YAML
55
+ end
56
+
57
+ # Generates and stores a reference to a new CrystalRuby::Function
58
+ # and triggers the generation of the crystal code. (See write_chunk)
59
+ def crystalize_method(method, args, returns, function_body, async, &block)
60
+ CR_ATTACH_MUX.synchronize do
61
+ methods.each_value(&:unattach!)
62
+ method_key = "#{method.owner.name}/#{method.name}"
63
+ methods[method_key] = Function.new(
64
+ method: method,
65
+ args: args,
66
+ returns: returns,
67
+ function_body: function_body,
68
+ async: async,
69
+ lib: self,
70
+ &block
71
+ ).tap do |func|
72
+ func.define_crystalized_methods!(self)
73
+ func.register_custom_types!(self)
74
+ write_chunk(method.owner.name, method.name, func.chunk)
75
+ end
76
+ end
77
+ end
78
+
79
+ def main_file
80
+ src_dir / "#{name}.cr"
81
+ end
82
+
83
+ def lib_file
84
+ lib_dir / "#{name}_#{digest}"
85
+ end
86
+
87
+ def shard_file
88
+ src_dir / "shard.yml"
89
+ end
90
+
91
+ def crystalize_chunk(mod, chunk_name, body)
92
+ write_chunk(mod.name, chunk_name, body)
93
+ end
94
+
95
+ def instantiated?
96
+ @instantiated
97
+ end
98
+
99
+ def compiled?
100
+ @compiled ||= File.exist?(lib_file) && chunks.all? do |chunk|
101
+ chunk_data = chunk[:body]
102
+ file_digest = Digest::MD5.hexdigest chunk_data
103
+ fname = chunk[:chunk_name]
104
+ index_contents.include?("#{chunk[:module_name]}/#{fname}_#{file_digest}.cr")
105
+ end
106
+ end
107
+
108
+ def index_contents
109
+ IO.read(codegen_dir / "index.cr")
110
+ rescue StandardError
111
+ ""
112
+ end
113
+
114
+ def register_type!(type)
115
+ @types_cache ||= {}
116
+ @types_cache[type.name] = type.type_defn
117
+ end
118
+
119
+ def type_modules
120
+ (@types_cache || {}).map do |type_name, expr|
121
+ parts = type_name.split("::")
122
+ typedef = parts[0...-1].each_with_index.reduce("") do |acc, (part, index)|
123
+ acc + "#{" " * index}module #{part}\n"
124
+ end
125
+ typedef += "#{" " * (parts.size - 1)}alias #{parts.last} = #{expr}\n"
126
+ typedef + parts[0...-1].reverse.each_with_index.reduce("") do |acc, (_part, index)|
127
+ acc + "#{" " * (parts.size - 2 - index)}end\n"
128
+ end
129
+ end.join("\n")
130
+ end
131
+
132
+ def requires
133
+ chunks.map do |chunk|
134
+ chunk_data = chunk[:body]
135
+ file_digest = Digest::MD5.hexdigest chunk_data
136
+ fname = chunk[:chunk_name]
137
+ "require \"./#{chunk[:module_name]}/#{fname}_#{file_digest}.cr\"\n"
138
+ end.join("\n")
139
+ end
140
+
141
+ def build!
142
+ CR_COMPILE_MUX.synchronize do
143
+ File.write codegen_dir / "index.cr", Template::Index.render(
144
+ type_modules: type_modules,
145
+ requires: requires
146
+ )
147
+
148
+ unless compiled?
149
+ CrystalRuby::Compilation.compile!(
150
+ verbose: config.verbose,
151
+ debug: config.debug,
152
+ src: main_file,
153
+ lib: lib_file
154
+ )
155
+ end
156
+ end
157
+ end
158
+
159
+ def digest
160
+ Digest::MD5.hexdigest(File.read(codegen_dir / "index.cr")) if File.exist?(codegen_dir / "index.cr")
161
+ end
162
+
163
+ def self.chunk_store
164
+ @chunk_store ||= []
165
+ end
166
+
167
+ def write_chunk(module_name, chunk_name, body)
168
+ chunks.delete_if { |chnk| chnk[:module_name] == module_name && chnk[:chunk_name] == chunk_name }
169
+ chunks << { module_name: module_name, chunk_name: chunk_name, body: body }
170
+ existing = Dir.glob(codegen_dir / "**/*.cr")
171
+ chunks.each do |chunk|
172
+ module_name, chunk_name, body = chunk.values_at(:module_name, :chunk_name, :body)
173
+
174
+ file_digest = Digest::MD5.hexdigest body
175
+ filename = (codegen_dir / module_name / "#{chunk_name}_#{file_digest}.cr").to_s
176
+
177
+ unless existing.delete(filename)
178
+ methods.each_value(&:unattach!)
179
+ @compiled = false
180
+ FileUtils.mkdir_p(codegen_dir / module_name)
181
+ File.write(filename, body)
182
+ end
183
+ existing.select do |f|
184
+ f =~ /#{config.crystal_codegen_dir / module_name / "#{chunk_name}_[a-f0-9]{32}\.cr"}/
185
+ end.each do |fl|
186
+ File.delete(fl) unless fl.eql?(filename)
187
+ end
188
+ end
189
+ end
190
+ end
191
+ end
@@ -1,8 +1,12 @@
1
1
  module CrystalRuby
2
+ # The Reactor represents a singleton Thread responsible for running all Ruby/crystal interop code.
3
+ # Crystal's Fiber scheduler and GC assume all code is run on a single thread.
4
+ # This class is responsible for multiplexing Ruby and Crystal code on a single thread.
5
+ # Functions annotated with async: true, are executed using callbacks to allow these to be interleaved without blocking.
6
+
2
7
  module Reactor
3
8
  module_function
4
9
 
5
- class ReactorStoppedException < StandardError; end
6
10
  class SingleThreadViolation < StandardError; end
7
11
 
8
12
  REACTOR_QUEUE = Queue.new
@@ -39,7 +43,8 @@ module CrystalRuby
39
43
  error_type = error_type.to_sym
40
44
  is_exception_type = Object.const_defined?(error_type) && Object.const_get(error_type).ancestors.include?(Exception)
41
45
  error_type = is_exception_type ? Object.const_get(error_type) : RuntimeError
42
- tid = tid.zero? ? Reactor.current_thread_id : tid
46
+ raise error_type.new(message) unless THREAD_MAP.key?(tid)
47
+
43
48
  THREAD_MAP[tid][:error] = error_type.new(message)
44
49
  THREAD_MAP[tid][:result] = nil
45
50
  THREAD_MAP[tid][:cond].signal
@@ -57,15 +62,26 @@ module CrystalRuby
57
62
  THREAD_MAP[thread_id][:result]
58
63
  end
59
64
 
65
+ def start!
66
+ @main_loop ||= Thread.new do
67
+ CrystalRuby.log_debug("Starting reactor")
68
+ CrystalRuby.log_debug("CrystalRuby initialized")
69
+ loop do
70
+ REACTOR_QUEUE.pop[]
71
+ end
72
+ rescue StandardError => e
73
+ CrystalRuby.log_error "Error: #{e}"
74
+ CrystalRuby.log_error e.backtrace
75
+ end
76
+ end
77
+
60
78
  def thread_id
61
79
  Thread.current.object_id
62
80
  end
63
81
 
64
- def yield!(time: 0)
65
- Thread.new do
66
- sleep time
67
- schedule_work!(Reactor, :yield, nil, async: false, blocking: false)
68
- end
82
+ def yield!(lib: nil, time: 0.0)
83
+ schedule_work!(lib, :yield, nil, async: false, blocking: false, lib: lib) if running? && lib
84
+ nil
69
85
  end
70
86
 
71
87
  def current_thread_id=(val)
@@ -76,9 +92,7 @@ module CrystalRuby
76
92
  @current_thread_id
77
93
  end
78
94
 
79
- def schedule_work!(receiver, op_name, *args, return_type, blocking: true, async: true)
80
- raise ReactorStoppedException, "Reactor has been terminated, no new work can be scheduled" if @stopped
81
-
95
+ def schedule_work!(receiver, op_name, *args, return_type, blocking: true, async: true, lib: nil)
82
96
  if @single_thread_mode
83
97
  unless Thread.current.object_id == @main_thread_id
84
98
  raise SingleThreadViolation,
@@ -99,7 +113,7 @@ module CrystalRuby
99
113
  op_name, *args, tvars[:thread_id],
100
114
  CALLBACKS_MAP[return_type]
101
115
  )
102
- yield!(time: 0)
116
+ yield!(lib: lib, time: 0)
103
117
  }
104
118
  when blocking
105
119
  lambda {
@@ -116,7 +130,7 @@ module CrystalRuby
116
130
  else
117
131
  lambda {
118
132
  outstanding_jobs = receiver.send(op_name, *args)
119
- yield!(time: 0.01) unless outstanding_jobs.zero?
133
+ yield!(lib: lib, time: 0) unless outstanding_jobs == 0
120
134
  }
121
135
  end
122
136
  )
@@ -124,61 +138,15 @@ module CrystalRuby
124
138
  end
125
139
  end
126
140
 
127
- def init_single_thread_mode!
128
- @single_thread_mode = true
129
- @main_thread_id = Thread.current.object_id
130
- init_crystal_ruby!
131
- end
132
-
133
- def init_crystal_ruby!
134
- attach_lib!
135
- init(ERROR_CALLBACK)
136
- end
137
-
138
- def attach_lib!
139
- CrystalRuby.log_debug("Attaching lib")
140
- extend FFI::Library
141
- ffi_lib CrystalRuby.config.crystal_lib_dir / CrystalRuby.config.crystal_lib_name
142
- attach_function :init, [:pointer], :void
143
- attach_function :stop, [], :void
144
- attach_function :yield, %i[], :int
145
- end
146
-
147
- def stop!
148
- CrystalRuby.log_debug("Stopping reactor")
149
- @stopped = true
150
- sleep 1
151
- @main_loop&.kill
152
- @main_loop = nil
153
- CrystalRuby.log_debug("Reactor stopped")
154
- end
155
-
156
141
  def running?
157
142
  @main_loop&.alive?
158
143
  end
159
144
 
160
- def start!
161
- @main_loop ||= begin
162
- attach_lib!
163
- Thread.new do
164
- CrystalRuby.log_debug("Starting reactor")
165
- init(ERROR_CALLBACK)
166
- CrystalRuby.log_debug("CrystalRuby initialized")
167
- loop do
168
- REACTOR_QUEUE.pop[]
169
- break if @stopped
170
- end
171
- stop
172
- CrystalRuby.log_debug("Stopping reactor")
173
- rescue StandardError => e
174
- puts "Error: #{e}"
175
- puts e.backtrace
176
- end
145
+ def init_single_thread_mode!
146
+ @single_thread_mode ||= begin
147
+ @main_thread_id = Thread.current.object_id
148
+ true
177
149
  end
178
150
  end
179
-
180
- at_exit do
181
- @stopped = true
182
- end
183
151
  end
184
152
  end
@@ -4,7 +4,6 @@ module CrystalRuby
4
4
  template_name = File.basename(file, File.extname(file)).split("_").map(&:capitalize).join
5
5
  template_value = File.read(file)
6
6
  template_value.define_singleton_method(:render) do |context|
7
- CrystalRuby.log_debug("Template.render: #{template_name}")
8
7
  self % context
9
8
  end
10
9
  const_set(template_name, template_value)
@@ -1,13 +1,15 @@
1
- alias ErrorCallback = (Pointer(UInt8), Pointer(UInt8), UInt32 -> Void)
1
+ module CrystalRuby
2
2
 
3
- ARGV1 = "crystalruby"
4
- CALLBACK_MUX = Mutex.new
3
+ ARGV1 = "crystalruby"
4
+ CALLBACK_MUX = Mutex.new
5
+
6
+ alias ErrorCallback = (Pointer(UInt8), Pointer(UInt8), UInt32 -> Void)
5
7
 
6
- module CrystalRuby
7
8
  # Initializing Crystal Ruby invokes init on the Crystal garbage collector.
8
9
  # We need to be sure to only do this once.
9
10
  @@initialized = false
10
11
 
12
+ @@libname = "crystalruby"
11
13
  # Our Ruby <-> Crystal Reactor uses Fibers, with callbacks to allow
12
14
  # multiple concurrent Crystal operations to be queued
13
15
  @@callbacks = [] of Proc(Nil)
@@ -25,13 +27,13 @@ module CrystalRuby
25
27
  # 1. Initialize the Crystal garbage collector
26
28
  # 2. Set the error callback
27
29
  # 3. Call the Crystal main function
28
- def self.init(error_callback : ErrorCallback)
30
+ def self.init(libname : Pointer(UInt8), error_callback : ErrorCallback)
29
31
  return if @@initialized
30
32
  @@initialized = true
31
- GC.init
32
33
  argv_ptr = ARGV1.to_unsafe
33
- Crystal.main(0, pointerof(argv_ptr))
34
+ Crystal.main_user_code(0, pointerof(argv_ptr))
34
35
  @@error_callback = error_callback
36
+ @@libname = String.new(libname)
35
37
  end
36
38
 
37
39
  # Explicit error handling (triggers exception within Ruby on the same thread)
@@ -70,6 +72,10 @@ module CrystalRuby
70
72
  @@callbacks.size
71
73
  end
72
74
 
75
+ def self.libname : String
76
+ @@libname
77
+ end
78
+
73
79
  # Flush all callbacks
74
80
  def self.flush_callbacks : Int32
75
81
  CALLBACK_MUX.synchronize do
@@ -84,12 +90,28 @@ module CrystalRuby
84
90
  end
85
91
 
86
92
  # Initialize CrystalRuby
87
- fun init(cb : ErrorCallback): Void
88
- CrystalRuby.init(cb)
93
+ fun init(libname : Pointer(UInt8), cb : CrystalRuby::ErrorCallback): Void
94
+ CrystalRuby.init(libname, cb)
95
+ end
96
+
97
+ fun stop : Void
98
+ LibGC.deinit()
89
99
  end
90
100
 
91
- fun stop(): Void
92
- GC.disable
101
+ @[Link("gc")]
102
+ lib LibGC
103
+ $stackbottom = GC_stackbottom : Void*
104
+ fun deinit = GC_deinit
105
+ end
106
+
107
+ module GC
108
+ def self.current_thread_stack_bottom
109
+ {Pointer(Void).null, LibGC.stackbottom}
110
+ end
111
+
112
+ def self.set_stackbottom(stack_bottom : Void*)
113
+ LibGC.stackbottom = stack_bottom
114
+ end
93
115
  end
94
116
 
95
117
  # Yield to the Crystal scheduler from Ruby
@@ -99,7 +121,6 @@ end
99
121
  # once this figure reaches 0).
100
122
  fun yield() : Int32
101
123
  if CrystalRuby.count_callbacks == 0
102
-
103
124
  Fiber.yield
104
125
 
105
126
  # TODO: We should apply backpressure here to prevent busy waiting if the number of outstanding tasks is not decreasing.
@@ -19,6 +19,7 @@ module CrystalRuby
19
19
  nil
20
20
  end
21
21
  restores << [context, method_name, old_method]
22
+ context.singleton_class.undef_method(method_name) if old_method
22
23
  context.define_singleton_method(method_name) do |*args|
23
24
  Types.send(method_name, *args)
24
25
  end
@@ -26,6 +27,7 @@ module CrystalRuby
26
27
  yield
27
28
  ensure
28
29
  restores.each do |context, method_name, old_method|
30
+ context.singleton_class.undef_method(method_name)
29
31
  context.define_singleton_method(method_name, old_method) if old_method
30
32
  end
31
33
  end