crystalruby 0.1.13 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,228 @@
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
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
+ end
21
+
22
+ # This is where we write/overwrite the class and instance methods
23
+ # with their crystalized equivalents.
24
+ # We also perform JIT compilation and JIT attachment of the FFI functions.
25
+ # Crystalized methods work in a live-reloading manner environment.
26
+ # If they are redefined with a different function body, the new function body
27
+ # will result in a new digest and the FFI function will be recompiled and reattached.
28
+ def define_crystalized_methods!(lib)
29
+ func = self
30
+ [owner, owner.singleton_class].each do |receiver|
31
+ receiver.undef_method(method_name) if receiver.method_defined?(method_name)
32
+ receiver.define_method(method_name) do |*args|
33
+ unless lib.compiled?
34
+ lib.build!
35
+ return send(func.method_name, *args)
36
+ end
37
+ unless lib.attached?(func.owner)
38
+ should_reenter = func.attach_ffi_lib_functions!
39
+ return send(func.method_name, *args) if should_reenter
40
+ end
41
+ # All crystalruby functions are executed on the reactor to ensure
42
+ # all Crystal interop is executed from the same thread. (Needed to make GC and Fiber scheduler happy)
43
+ # Type mapping (if required) is applied on arguments and on return values.
44
+ func.map_retval(
45
+ Reactor.schedule_work!(
46
+ func.owner,
47
+ func.ffi_name,
48
+ *func.map_args(args),
49
+ func.ffi_ret_type,
50
+ async: func.async,
51
+ lib: lib
52
+ )
53
+ )
54
+ end
55
+ end
56
+ end
57
+
58
+ # This is where we attach the top-level FFI functions of the shared object
59
+ # to our library (yield and init) needed for successful operation of the reactor.
60
+ # We also initialize the shared object (needed to start the GC) and
61
+ # start the reactor unless we are in single-thread mode.
62
+ def attach_ffi_lib_functions!
63
+ should_reenter = unwrapped?
64
+ lib_file = lib.lib_file
65
+ lib.attachments[owner] = true
66
+ lib.methods.each_value do |method|
67
+ method.attach_ffi_func!
68
+ end
69
+ lib.singleton_class.class_eval do
70
+ extend FFI::Library
71
+ ffi_lib lib_file
72
+ %i[yield init].each do |method_name|
73
+ singleton_class.undef_method(method_name) if singleton_class.method_defined?(method_name)
74
+ undef_method(method_name) if method_defined?(method_name)
75
+ end
76
+ attach_function :init, %i[string pointer], :void
77
+ attach_function :yield, %i[], :int
78
+ end
79
+
80
+ if CrystalRuby.config.single_thread_mode
81
+ Reactor.init_single_thread_mode!
82
+ else
83
+ Reactor.start!
84
+ end
85
+ Reactor.schedule_work!(lib, :init, lib.name, Reactor::ERROR_CALLBACK, :void, blocking: true, async: false)
86
+ should_reenter
87
+ end
88
+
89
+ # This is where we attach the crystalized FFI functions to their related
90
+ # Ruby modules and classes. If a wrapper block has been passed to the crystalize function,
91
+ # then the we also wrap the crystalized function using a prepended Module.
92
+ def attach_ffi_func!
93
+ argtypes = ffi_types
94
+ rettype = ffi_ret_type
95
+ if async && !config.single_thread_mode
96
+ argtypes += %i[int pointer]
97
+ rettype = :void
98
+ end
99
+
100
+ owner.extend FFI::Library unless owner.is_a?(FFI::Library)
101
+
102
+ unless (owner.instance_variable_get(:@ffi_libs) || [])
103
+ .map(&:name)
104
+ .map(&File.method(:basename))
105
+ .include?(File.basename(lib.lib_file))
106
+ owner.ffi_lib lib.lib_file
107
+ end
108
+
109
+ if owner.method_defined?(ffi_name)
110
+ owner.undef_method(ffi_name)
111
+ owner.singleton_class.undef_method(ffi_name)
112
+ end
113
+
114
+ owner.attach_function ffi_name, argtypes, rettype, blocking: true
115
+ around_wrapper_block = block
116
+ method_name = self.method_name
117
+
118
+ return unless around_wrapper_block
119
+
120
+ @around_wrapper ||= begin
121
+ wrapper_module = Module.new {}
122
+ [owner, owner.singleton_class].each do |receiver|
123
+ receiver.prepend(wrapper_module)
124
+ end
125
+ wrapper_module
126
+ end
127
+ @around_wrapper.undef_method(method_name) if @around_wrapper.method_defined?(method_name)
128
+ @around_wrapper.define_method(method_name, &around_wrapper_block)
129
+ rescue StandardError => e
130
+ CrystalRuby.log_error("Error attaching #{method_name} as #{ffi_name} to #{owner.name}")
131
+ CrystalRuby.log_error(e.message)
132
+ CrystalRuby.log_error(e.backtrace.join("\n"))
133
+ end
134
+
135
+ def unwrapped?
136
+ block && !@around_wrapper
137
+ end
138
+
139
+ def ffi_name
140
+ lib_fn_name + (async && !config.single_thread_mode ? "_async" : "")
141
+ end
142
+
143
+ def lib_fn_name
144
+ @lib_fn_name ||= "#{owner.name.downcase.gsub("::", "_")}_#{method_name}_#{Digest::MD5.hexdigest(function_body)}"
145
+ end
146
+
147
+ def arg_type_map
148
+ @arg_type_map ||= args.transform_values(&method(:build_type_map))
149
+ end
150
+
151
+ def lib_fn_args
152
+ @lib_fn_args ||= arg_type_map.map { |k, arg_type|
153
+ "_#{k} : #{arg_type[:lib_type]}"
154
+ }.join(",") + (arg_type_map.empty? ? "" : ", ")
155
+ end
156
+
157
+ def lib_fn_arg_names
158
+ @lib_fn_arg_names ||= arg_type_map.map { |k, _arg_type|
159
+ "_#{k}"
160
+ }.join(",") + (arg_type_map.empty? ? "" : ", ")
161
+ end
162
+
163
+ def return_type_map
164
+ @return_type_map ||= build_type_map(returns)
165
+ end
166
+
167
+ def ffi_types
168
+ @ffi_types ||= arg_type_map.map { |_k, arg_type| arg_type[:ffi_type] }
169
+ end
170
+
171
+ def arg_maps
172
+ @arg_maps ||= arg_type_map.map { |_k, arg_type| arg_type[:arg_mapper] }
173
+ end
174
+
175
+ def ffi_ret_type
176
+ @ffi_ret_type ||= return_type_map[:ffi_ret_type]
177
+ end
178
+
179
+ def register_custom_types!(lib)
180
+ [*arg_type_map.values, return_type_map].map { |t| t[:crystal_ruby_type] }.each do |crystalruby_type|
181
+ if crystalruby_type.is_a?(Types::TypeSerializer) && !crystalruby_type.anonymous?
182
+ lib.register_type!(crystalruby_type)
183
+ end
184
+ end
185
+ end
186
+
187
+ def map_args(args)
188
+ return args unless arg_maps.any?
189
+
190
+ arg_maps.each_with_index do |argmap, index|
191
+ next unless argmap
192
+
193
+ args[index] = argmap[args[index]]
194
+ end
195
+ args
196
+ end
197
+
198
+ def map_retval(retval)
199
+ return retval unless return_type_map[:retval_mapper]
200
+
201
+ return_type_map[:retval_mapper][retval]
202
+ end
203
+
204
+ def chunk
205
+ @chunk ||= Template::Function.render(
206
+ {
207
+ module_name: owner.name,
208
+ lib_fn_name: lib_fn_name,
209
+ fn_name: method_name,
210
+ fn_body: function_body,
211
+ callback_call: returns == :void ? "callback.call(thread_id)" : "callback.call(thread_id, converted)",
212
+ callback_type: return_type_map[:ffi_type] == :void ? "UInt32 -> Void" : " UInt32, #{return_type_map[:lib_type]} -> Void",
213
+ fn_args: arg_type_map.map { |k, arg_type| "#{k} : #{arg_type[:crystal_type]}" }.join(","),
214
+ fn_ret_type: return_type_map[:crystal_type],
215
+ lib_fn_args: lib_fn_args,
216
+ lib_fn_arg_names: lib_fn_arg_names,
217
+ lib_fn_ret_type: return_type_map[:lib_type],
218
+ convert_lib_args: arg_type_map.map do |k, arg_type|
219
+ "#{k} = #{arg_type[:convert_lib_to_crystal_type]["_#{k}"]}"
220
+ end.join("\n "),
221
+ arg_names: args.keys.join(","),
222
+ convert_return_type: return_type_map[:convert_crystal_to_lib_type]["return_value"],
223
+ error_value: return_type_map[:error_value]
224
+ }
225
+ )
226
+ end
227
+ end
228
+ end
@@ -0,0 +1,198 @@
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, :attachments, :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
+ self.attachments = Hash.new(false)
34
+ initialize_library!
35
+ end
36
+
37
+ # This method is used to
38
+ # bootstrap a library filesystem,
39
+ # and generate a top level index.cr and shard file if
40
+ # these do not already exist.
41
+ def initialize_library!
42
+ @root_dir, @lib_dir, @src_dir, @codegen_dir = [
43
+ config.crystal_src_dir_abs / name,
44
+ config.crystal_src_dir_abs / name / "lib",
45
+ config.crystal_src_dir_abs / name / "src",
46
+ config.crystal_src_dir_abs / name / "src" / config.crystal_codegen_dir
47
+ ].each do |dir|
48
+ FileUtils.mkdir_p(dir)
49
+ end
50
+ IO.write main_file, "require \"./#{config.crystal_codegen_dir}/index\"\n" unless File.exist?(main_file)
51
+
52
+ return if File.exist?(shard_file)
53
+
54
+ IO.write(shard_file, <<~YAML)
55
+ name: src
56
+ version: 0.1.0
57
+ YAML
58
+ end
59
+
60
+ # This is where we instantiate the crystalized method as a CrystalRuby::Function
61
+ # and trigger the generation of the crystal code.
62
+ def crystalize_method(method, args, returns, function_body, async, &block)
63
+ CR_ATTACH_MUX.synchronize do
64
+ attachments.delete(method.owner)
65
+ method_key = "#{method.owner.name}/#{method.name}"
66
+ methods[method_key] = Function.new(
67
+ method: method,
68
+ args: args,
69
+ returns: returns,
70
+ function_body: function_body,
71
+ async: async,
72
+ lib: self,
73
+ &block
74
+ ).tap do |func|
75
+ func.define_crystalized_methods!(self)
76
+ func.register_custom_types!(self)
77
+ write_chunk(method.owner.name, method.name, func.chunk)
78
+ end
79
+ end
80
+ end
81
+
82
+ def main_file
83
+ src_dir / "#{name}.cr"
84
+ end
85
+
86
+ def lib_file
87
+ lib_dir / "#{name}_#{digest}"
88
+ end
89
+
90
+ def shard_file
91
+ src_dir / "shard.yml"
92
+ end
93
+
94
+ def crystalize_chunk(mod, chunk_name, body)
95
+ write_chunk(mod.name, chunk_name, body)
96
+ end
97
+
98
+ def instantiated?
99
+ @instantiated
100
+ end
101
+
102
+ def compiled?
103
+ index_contents = self.index_contents
104
+ File.exist?(lib_file) && chunks.all? do |chunk|
105
+ chunk_data = chunk[:body]
106
+ file_digest = Digest::MD5.hexdigest chunk_data
107
+ fname = chunk[:chunk_name]
108
+ index_contents.include?("#{chunk[:module_name]}/#{fname}_#{file_digest}.cr")
109
+ end
110
+ end
111
+
112
+ def index_contents
113
+ IO.read(codegen_dir / "index.cr")
114
+ rescue StandardError
115
+ ""
116
+ end
117
+
118
+ def attached?(owner)
119
+ attachments[owner]
120
+ end
121
+
122
+ def register_type!(type)
123
+ @types_cache ||= {}
124
+ @types_cache[type.name] = type.type_defn
125
+ end
126
+
127
+ def type_modules
128
+ (@types_cache || {}).map do |type_name, expr|
129
+ parts = type_name.split("::")
130
+ typedef = parts[0...-1].each_with_index.reduce("") do |acc, (part, index)|
131
+ acc + "#{" " * index}module #{part}\n"
132
+ end
133
+ typedef += "#{" " * (parts.size - 1)}alias #{parts.last} = #{expr}\n"
134
+ typedef + parts[0...-1].reverse.each_with_index.reduce("") do |acc, (_part, index)|
135
+ acc + "#{" " * (parts.size - 2 - index)}end\n"
136
+ end
137
+ end.join("\n")
138
+ end
139
+
140
+ def requires
141
+ chunks.map do |chunk|
142
+ chunk_data = chunk[:body]
143
+ file_digest = Digest::MD5.hexdigest chunk_data
144
+ fname = chunk[:chunk_name]
145
+ "require \"./#{chunk[:module_name]}/#{fname}_#{file_digest}.cr\"\n"
146
+ end.join("\n")
147
+ end
148
+
149
+ def build!
150
+ CR_COMPILE_MUX.synchronize do
151
+ File.write codegen_dir / "index.cr", Template::Index.render(
152
+ type_modules: type_modules,
153
+ requires: requires
154
+ )
155
+
156
+ unless compiled?
157
+ CrystalRuby::Compilation.compile!(
158
+ verbose: config.verbose,
159
+ debug: config.debug,
160
+ src: main_file,
161
+ lib: lib_file
162
+ )
163
+ end
164
+ end
165
+ end
166
+
167
+ def digest
168
+ Digest::MD5.hexdigest(File.read(codegen_dir / "index.cr")) if File.exist?(codegen_dir / "index.cr")
169
+ end
170
+
171
+ def self.chunk_store
172
+ @chunk_store ||= []
173
+ end
174
+
175
+ def write_chunk(module_name, chunk_name, body)
176
+ chunks.delete_if { |chnk| chnk[:module_name] == module_name && chnk[:chunk_name] == chunk_name }
177
+ chunks << { module_name: module_name, chunk_name: chunk_name, body: body }
178
+ existing = Dir.glob(codegen_dir / "**/*.cr")
179
+ chunks.each do |chunk|
180
+ module_name, chunk_name, body = chunk.values_at(:module_name, :chunk_name, :body)
181
+
182
+ file_digest = Digest::MD5.hexdigest body
183
+ filename = (codegen_dir / module_name / "#{chunk_name}_#{file_digest}.cr").to_s
184
+
185
+ unless existing.delete(filename)
186
+ @attached = false
187
+ FileUtils.mkdir_p(codegen_dir / module_name)
188
+ File.write(filename, body)
189
+ end
190
+ existing.select do |f|
191
+ f =~ /#{config.crystal_codegen_dir / module_name / "#{chunk_name}_[a-f0-9]{32}\.cr"}/
192
+ end.each do |fl|
193
+ File.delete(fl) unless fl.eql?(filename)
194
+ end
195
+ end
196
+ end
197
+ end
198
+ end
@@ -1,8 +1,14 @@
1
1
  module CrystalRuby
2
+ # The Reactor represents a singleton Thread
3
+ # responsible for running all Ruby/crystal interop code.
4
+ # Crystal's Fiber scheduler and GC assumes all code is run on a single thread.
5
+ # This class is responsible for multiplexing Ruby and Crystal code on a single thread,
6
+ # to allow safe invocation of Crystal code from across any number of Ruby threads.
7
+ # Functions annotated with async: true, are executed using callbacks to allow these to be multi-plexed in a non-blocking manner.
8
+
2
9
  module Reactor
3
10
  module_function
4
11
 
5
- class ReactorStoppedException < StandardError; end
6
12
  class SingleThreadViolation < StandardError; end
7
13
 
8
14
  REACTOR_QUEUE = Queue.new
@@ -40,6 +46,8 @@ module CrystalRuby
40
46
  is_exception_type = Object.const_defined?(error_type) && Object.const_get(error_type).ancestors.include?(Exception)
41
47
  error_type = is_exception_type ? Object.const_get(error_type) : RuntimeError
42
48
  tid = tid.zero? ? Reactor.current_thread_id : tid
49
+ raise error_type.new(message) unless THREAD_MAP.key?(tid)
50
+
43
51
  THREAD_MAP[tid][:error] = error_type.new(message)
44
52
  THREAD_MAP[tid][:result] = nil
45
53
  THREAD_MAP[tid][:cond].signal
@@ -57,15 +65,26 @@ module CrystalRuby
57
65
  THREAD_MAP[thread_id][:result]
58
66
  end
59
67
 
68
+ def start!
69
+ @main_loop ||= Thread.new do
70
+ CrystalRuby.log_debug("Starting reactor")
71
+ CrystalRuby.log_debug("CrystalRuby initialized")
72
+ loop do
73
+ REACTOR_QUEUE.pop[]
74
+ end
75
+ rescue StandardError => e
76
+ CrystalRuby.log_error "Error: #{e}"
77
+ CrystalRuby.log_error e.backtrace
78
+ end
79
+ end
80
+
60
81
  def thread_id
61
82
  Thread.current.object_id
62
83
  end
63
84
 
64
- def yield!(time: 0)
65
- Thread.new do
66
- sleep time
67
- schedule_work!(Reactor, :yield, nil, async: false, blocking: false)
68
- end
85
+ def yield!(lib: nil, time: 0.0)
86
+ schedule_work!(lib, :yield, nil, async: false, blocking: false, lib: lib) if running? && lib
87
+ nil
69
88
  end
70
89
 
71
90
  def current_thread_id=(val)
@@ -76,9 +95,7 @@ module CrystalRuby
76
95
  @current_thread_id
77
96
  end
78
97
 
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
-
98
+ def schedule_work!(receiver, op_name, *args, return_type, blocking: true, async: true, lib: nil)
82
99
  if @single_thread_mode
83
100
  unless Thread.current.object_id == @main_thread_id
84
101
  raise SingleThreadViolation,
@@ -99,7 +116,7 @@ module CrystalRuby
99
116
  op_name, *args, tvars[:thread_id],
100
117
  CALLBACKS_MAP[return_type]
101
118
  )
102
- yield!(time: 0)
119
+ yield!(lib: lib, time: 0)
103
120
  }
104
121
  when blocking
105
122
  lambda {
@@ -116,7 +133,7 @@ module CrystalRuby
116
133
  else
117
134
  lambda {
118
135
  outstanding_jobs = receiver.send(op_name, *args)
119
- yield!(time: 0.01) unless outstanding_jobs.zero?
136
+ yield!(lib: lib, time: 0) unless outstanding_jobs == 0
120
137
  }
121
138
  end
122
139
  )
@@ -124,61 +141,15 @@ module CrystalRuby
124
141
  end
125
142
  end
126
143
 
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
144
  def running?
157
145
  @main_loop&.alive?
158
146
  end
159
147
 
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
148
+ def init_single_thread_mode!
149
+ @single_thread_mode ||= begin
150
+ @main_thread_id = Thread.current.object_id
151
+ true
177
152
  end
178
153
  end
179
-
180
- at_exit do
181
- @stopped = true
182
- end
183
154
  end
184
155
  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