Skip to content

Commit 417e627

Browse files
committed
v 0.3.3
Added dynamic scheduled GC based on memory hints Pinned prism version Auto-convert to ffi-primitive types where possible for increased performance
1 parent 4567386 commit 417e627

27 files changed

+313
-165
lines changed

Gemfile.lock

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
PATH
22
remote: .
33
specs:
4-
crystalruby (0.3.2)
4+
crystalruby (0.3.3)
55
digest
66
ffi
7-
fileutils
8-
prism
7+
fileutils (~> 1.7)
8+
prism (~> 1.3.0)
99

1010
GEM
1111
remote: https://rubygems.org/
@@ -18,6 +18,7 @@ GEM
1818
reline (>= 0.3.8)
1919
digest (3.1.1)
2020
ffi (1.17.0)
21+
ffi (1.17.0-arm64-darwin)
2122
fileutils (1.7.2)
2223
io-console (0.7.2)
2324
irb (1.12.0)
@@ -35,7 +36,7 @@ GEM
3536
parser (3.3.0.5)
3637
ast (~> 2.4.1)
3738
racc
38-
prism (1.2.0)
39+
prism (1.3.0)
3940
psych (5.1.2)
4041
stringio
4142
racc (1.7.3)

crystalruby.gemspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ Gem::Specification.new do |spec|
3434
# spec.add_dependency "example-gem", "~> 1.0"
3535
spec.add_dependency "digest"
3636
spec.add_dependency "ffi"
37-
spec.add_dependency "fileutils"
38-
spec.add_dependency "prism"
37+
spec.add_dependency "fileutils", "~> 1.7"
38+
spec.add_dependency "prism", "~> 1.3.0"
3939
# For more information and examples about making a new gem, check out our
4040
# guide at: https://bundler.io/guides/creating_gem.html
4141
end

lib/crystalruby/adapter.rb

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ module Adapter
3636
# @option options [Boolean] :async (false) Mark the method as async (allows multiplexing).
3737
# @option options [String] :lib ("crystalruby") The name of the library to compile the Crystal code into.
3838
# @option options [Proc] :block An optional wrapper Ruby block that wraps around any invocations of the crystal code
39-
def crystallize( returns=:void, raw: false, async: false, lib: "crystalruby", &block)
39+
def crystallize(returns = :void, raw: false, async: false, lib: "crystalruby", &block)
4040
(self == TOPLEVEL_BINDING.receiver ? Object : self).instance_eval do
4141
@crystallize_next = {
4242
raw: raw,
@@ -49,7 +49,7 @@ def crystallize( returns=:void, raw: false, async: false, lib: "crystalruby", &b
4949
end
5050

5151
# Alias for `crystallize`
52-
alias :crystalize :crystallize
52+
alias crystalize crystallize
5353

5454
# Exposes a Ruby method to one or more Crystal libraries.
5555
# Type annotations follow the same rules as the `crystallize` method, but are
@@ -58,7 +58,7 @@ def crystallize( returns=:void, raw: false, async: false, lib: "crystalruby", &b
5858
# @param [Hash] options The options hash.
5959
# @option options [Boolean] :raw (false) Pass raw Crystal code to the compiler as a string.
6060
# @option options [String] :libs (["crystalruby"]) The name of the Crystal librarie(s) to expose the Ruby code to.
61-
def expose_to_crystal( returns=:void, libs: ["crystalruby"])
61+
def expose_to_crystal(returns = :void, libs: ["crystalruby"])
6262
(self == TOPLEVEL_BINDING.receiver ? Object : self).instance_eval do
6363
@expose_next_to_crystal = {
6464
returns: returns,
@@ -70,22 +70,26 @@ def expose_to_crystal( returns=:void, libs: ["crystalruby"])
7070
# Define a shard dependency
7171
# This dependency will be automatically injected into the shard.yml file for
7272
# the given library and installed upon compile if it is not already installed.
73-
def shard(shard_name, lib: 'crystalruby', **opts)
73+
def shard(shard_name, lib: "crystalruby", **opts)
7474
CrystalRuby::Library[lib].require_shard(shard_name, **opts)
7575
end
7676

7777
# Use this method to define inline Crystal code that does not need to be bound to a Ruby method.
7878
# This is useful for defining classes, modules, performing set-up tasks etc.
7979
# See: docs for .crystallize to understand the `raw` and `lib` parameters.
8080
def crystal(raw: false, lib: "crystalruby", &block)
81-
inline_crystal_body = respond_to?(:name) ? Template::InlineChunk.render(
82-
{
83-
module_name: name,
84-
body: SourceReader.extract_source_from_proc(block, raw: raw),
85-
mod_or_class: self.kind_of?(Class) && self < Types::Type ? "class" : "module",
86-
superclass: self.kind_of?(Class) && self < Types::Type ? "< #{self.crystal_supertype}" : ""
87-
}) :
88-
SourceReader.extract_source_from_proc(block, raw: raw)
81+
inline_crystal_body = if respond_to?(:name)
82+
Template::InlineChunk.render(
83+
{
84+
module_name: name,
85+
body: SourceReader.extract_source_from_proc(block, raw: raw),
86+
mod_or_class: is_a?(Class) && self < Types::Type ? "class" : "module",
87+
superclass: is_a?(Class) && self < Types::Type ? "< #{crystal_supertype}" : ""
88+
}
89+
)
90+
else
91+
SourceReader.extract_source_from_proc(block, raw: raw)
92+
end
8993

9094
CrystalRuby::Library[lib].crystallize_chunk(
9195
self,
@@ -94,7 +98,6 @@ def crystal(raw: false, lib: "crystalruby", &block)
9498
)
9599
end
96100

97-
98101
# This method provides a useful DSL for defining Crystal types in pure Ruby
99102
# MyType = CRType{ Int32 | Hash(String, Array(Bool) | Float65 | Nil) }
100103
# @param [Proc] block The block within which we build the type definition.
@@ -152,14 +155,16 @@ def #{method.name} (#{(args.keys - [:__yield_to]).join(", ")})
152155

153156
owner = method.owner.singleton_class? ? method.owner.attached_object : method.owner
154157
owner.class_eval(src)
155-
owner.instance_eval(src) unless method.kind_of?(UnboundMethod) && method.owner.ancestors.include?(CrystalRuby::Types::Type)
156-
method = owner.send(method.kind_of?(UnboundMethod) ? :instance_method : :method, method.name)
158+
unless method.is_a?(UnboundMethod) && method.owner.ancestors.include?(CrystalRuby::Types::Type)
159+
owner.instance_eval(src)
160+
end
161+
method = owner.send(method.is_a?(UnboundMethod) ? :instance_method : :method, method.name)
157162

158163
libs.each do |lib|
159164
CrystalRuby::Library[lib].expose_method(
160165
method,
161166
args,
162-
returns,
167+
returns
163168
)
164169
end
165170
end

lib/crystalruby/function.rb

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ def define_crystallized_methods!(lib)
6666
raise ArgumentError, "no block given but function expects block" if !blk && func.takes_block?
6767

6868
args << blk if blk
69+
6970
func.map_args!(args)
7071
args.unshift(memory) if func.instance_method
7172

@@ -86,27 +87,35 @@ def define_crystallized_methods!(lib)
8687
def register_callback!
8788
return unless ruby
8889

89-
@callback_func = FFI::Function.new(ffi_ret_type, ffi_types) do |*args|
90+
ret_type = ffi_ret_type == :string ? :pointer : ffi_ret_type
91+
@callback_func = FFI::Function.new(ret_type, ffi_types) do |*args|
9092
receiver = instance_method ? owner.new(args.shift) : owner
91-
ret_val = if takes_block?
92-
block_arg = arg_type_map[:__yield_to][:crystalruby_type].new(args.pop)
93-
receiver.send(name, *unmap_args(args)) do |*args|
94-
args = args.map.with_index do |arg, i|
95-
arg = block_arg.inner_types[i].new(arg) unless arg.is_a?(block_arg.inner_types[i])
96-
arg.memory
97-
end
98-
return_val = block_arg.invoke(*args)
99-
unless return_val.is_a?(block_arg.inner_types[-1])
100-
return_val = block_arg.inner_types[-1].new(return_val)
101-
end
102-
block_arg.inner_types[-1].anonymous? ? return_val.value : return_val
103-
end
104-
else
105-
receiver.send(name, *unmap_args(args))
106-
end
93+
ret_val = \
94+
if takes_block?
95+
block_arg = arg_type_map[:__yield_to][:crystalruby_type].new(args.pop)
96+
receiver.send(name, *unmap_args(args)) do |*args|
97+
args = args.map.with_index do |arg, i|
98+
arg = block_arg.inner_types[i].new(arg) unless arg.is_a?(block_arg.inner_types[i])
99+
arg.memory
100+
end
101+
return_val = block_arg.invoke(*args)
102+
return_val = block_arg.inner_types[-1].new(return_val) unless return_val.is_a?(block_arg.inner_types[-1])
103+
block_arg.inner_types[-1].anonymous? ? return_val.value : return_val
104+
end
105+
else
106+
receiver.send(name, *unmap_args(args))
107+
end
107108
unmap_retval(ret_val)
108109
end
109-
Reactor.schedule_work!(lib, :"register_#{name.to_s.gsub("?", "q").gsub("=", "eq").gsub("!", "bang")}_callback", @callback_func, :void, blocking: true, async: false)
110+
111+
Reactor.schedule_work!(
112+
lib,
113+
:"register_#{name.to_s.gsub("?", "q").gsub("=", "eq").gsub("!", "bang")}_callback",
114+
@callback_func,
115+
:void,
116+
blocking: true,
117+
async: false
118+
)
110119
end
111120

112121
# Attaches the crystallized FFI functions to their related Ruby modules and classes.
@@ -245,7 +254,7 @@ def custom_types
245254

246255
def register_custom_types!(lib)
247256
custom_types.each do |crystalruby_type|
248-
next unless crystalruby_type.is_a?(Class) && crystalruby_type < Types::Type
257+
next unless Types::Type.subclass?(crystalruby_type)
249258

250259
[*crystalruby_type.nested_types].uniq.each do |type|
251260
lib.register_type!(type)
@@ -263,7 +272,7 @@ def map_args!(args)
263272

264273
mapped = argmap[args[index]]
265274
case mapped
266-
when CrystalRuby::Types::Type then
275+
when CrystalRuby::Types::Type
267276
args[index] = mapped.memory
268277
(refs ||= []) << mapped
269278
else
@@ -291,10 +300,12 @@ def map_retval(retval)
291300
end
292301

293302
def unmap_retval(retval)
303+
return FFI::MemoryPointer.from_string(retval) if return_type_map[:ffi_ret_type] == :string
294304
return retval unless return_type_map[:arg_mapper]
295305

296306
retval = return_type_map[:arg_mapper][retval]
297-
retval = retval.memory if retval.kind_of?(CrystalRuby::Types::Type)
307+
308+
retval = retval.memory if retval.is_a?(CrystalRuby::Types::Type)
298309
retval
299310
end
300311

lib/crystalruby/library.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -214,9 +214,7 @@ def build!
214214
unless compiled?
215215
FileUtils.rm_f(lib_file)
216216

217-
if shard_dependencies.any? && shards.empty?
218-
rewrite_shards_file!
219-
end
217+
rewrite_shards_file! if shard_dependencies.any? && shards.empty?
220218

221219
CrystalRuby::Compilation.install_shards!(src_dir)
222220
CrystalRuby::Compilation.compile!(
@@ -239,14 +237,16 @@ def attach!
239237
singleton_class.class_eval do
240238
extend FFI::Library
241239
ffi_lib lib_file
242-
%i[yield init].each do |method_name|
240+
%i[yield init gc].each do |method_name|
243241
singleton_class.undef_method(method_name) if singleton_class.method_defined?(method_name)
244242
undef_method(method_name) if method_defined?(method_name)
245243
end
246244
attach_function :init, %i[string pointer pointer], :void
247245
attach_function :yield, %i[], :int
246+
attach_function :gc, %i[], :void
248247
lib_methods.each_value.select(&:ruby).each do |method|
249-
attach_function :"register_#{method.name.to_s.gsub("?", "q").gsub("=", "eq").gsub("!", "bang")}_callback", %i[pointer], :void
248+
attach_function :"register_#{method.name.to_s.gsub("?", "q").gsub("=", "eq").gsub("!", "bang")}_callback",
249+
%i[pointer], :void
250250
end
251251
end
252252

0 commit comments

Comments
 (0)