crystalruby 0.1.13 → 0.2.0

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,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