crystalruby 0.1.13 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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