crystalruby 0.2.3 → 0.3.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +2 -0
- data/README.md +389 -193
- data/Rakefile +4 -3
- data/crystalruby.gemspec +2 -2
- data/exe/crystalruby +1 -0
- data/lib/crystalruby/adapter.rb +131 -65
- data/lib/crystalruby/arc_mutex.rb +47 -0
- data/lib/crystalruby/compilation.rb +32 -3
- data/lib/crystalruby/config.rb +41 -37
- data/lib/crystalruby/function.rb +211 -68
- data/lib/crystalruby/library.rb +153 -48
- data/lib/crystalruby/reactor.rb +40 -23
- data/lib/crystalruby/source_reader.rb +86 -0
- data/lib/crystalruby/template.rb +16 -5
- data/lib/crystalruby/templates/function.cr +11 -10
- data/lib/crystalruby/templates/index.cr +53 -66
- data/lib/crystalruby/templates/inline_chunk.cr +1 -1
- data/lib/crystalruby/templates/ruby_interface.cr +34 -0
- data/lib/crystalruby/templates/top_level_function.cr +62 -0
- data/lib/crystalruby/templates/top_level_ruby_interface.cr +33 -0
- data/lib/crystalruby/typebuilder.rb +11 -55
- data/lib/crystalruby/typemaps.rb +92 -67
- data/lib/crystalruby/types/concerns/allocator.rb +80 -0
- data/lib/crystalruby/types/fixed_width/named_tuple.cr +80 -0
- data/lib/crystalruby/types/fixed_width/named_tuple.rb +86 -0
- data/lib/crystalruby/types/fixed_width/proc.cr +45 -0
- data/lib/crystalruby/types/fixed_width/proc.rb +79 -0
- data/lib/crystalruby/types/fixed_width/tagged_union.cr +53 -0
- data/lib/crystalruby/types/fixed_width/tagged_union.rb +109 -0
- data/lib/crystalruby/types/fixed_width/tuple.cr +82 -0
- data/lib/crystalruby/types/fixed_width/tuple.rb +92 -0
- data/lib/crystalruby/types/fixed_width.cr +138 -0
- data/lib/crystalruby/types/fixed_width.rb +205 -0
- data/lib/crystalruby/types/primitive.cr +21 -0
- data/lib/crystalruby/types/primitive.rb +117 -0
- data/lib/crystalruby/types/primitive_types/bool.cr +34 -0
- data/lib/crystalruby/types/primitive_types/bool.rb +11 -0
- data/lib/crystalruby/types/primitive_types/nil.cr +35 -0
- data/lib/crystalruby/types/primitive_types/nil.rb +16 -0
- data/lib/crystalruby/types/primitive_types/numbers.cr +37 -0
- data/lib/crystalruby/types/primitive_types/numbers.rb +28 -0
- data/lib/crystalruby/types/primitive_types/symbol.cr +55 -0
- data/lib/crystalruby/types/primitive_types/symbol.rb +35 -0
- data/lib/crystalruby/types/primitive_types/time.cr +35 -0
- data/lib/crystalruby/types/primitive_types/time.rb +25 -0
- data/lib/crystalruby/types/type.cr +64 -0
- data/lib/crystalruby/types/type.rb +239 -30
- data/lib/crystalruby/types/variable_width/array.cr +74 -0
- data/lib/crystalruby/types/variable_width/array.rb +88 -0
- data/lib/crystalruby/types/variable_width/hash.cr +146 -0
- data/lib/crystalruby/types/variable_width/hash.rb +117 -0
- data/lib/crystalruby/types/variable_width/string.cr +36 -0
- data/lib/crystalruby/types/variable_width/string.rb +18 -0
- data/lib/crystalruby/types/variable_width.cr +23 -0
- data/lib/crystalruby/types/variable_width.rb +46 -0
- data/lib/crystalruby/types.rb +32 -13
- data/lib/crystalruby/version.rb +2 -2
- data/lib/crystalruby.rb +13 -6
- metadata +41 -19
- data/lib/crystalruby/types/array.rb +0 -15
- data/lib/crystalruby/types/bool.rb +0 -3
- data/lib/crystalruby/types/hash.rb +0 -17
- data/lib/crystalruby/types/named_tuple.rb +0 -28
- data/lib/crystalruby/types/nil.rb +0 -3
- data/lib/crystalruby/types/numbers.rb +0 -5
- data/lib/crystalruby/types/string.rb +0 -3
- data/lib/crystalruby/types/symbol.rb +0 -3
- data/lib/crystalruby/types/time.rb +0 -8
- data/lib/crystalruby/types/tuple.rb +0 -17
- data/lib/crystalruby/types/type_serializer/json.rb +0 -41
- data/lib/crystalruby/types/type_serializer.rb +0 -37
- data/lib/crystalruby/types/typedef.rb +0 -57
- data/lib/crystalruby/types/union_type.rb +0 -43
- data/lib/module.rb +0 -3
@@ -0,0 +1,53 @@
|
|
1
|
+
class <%= base_crystal_class_name %> < CrystalRuby::Types::FixedWidth
|
2
|
+
|
3
|
+
def initialize(value : <%= native_type_expr %>)
|
4
|
+
@memory = malloc(data_offset + <%= memsize %>_u64)
|
5
|
+
self.value = value
|
6
|
+
increment_ref_count!
|
7
|
+
end
|
8
|
+
|
9
|
+
def value=(value : <%= native_type_expr %>)
|
10
|
+
self.class.copy_to!(value, @memory)
|
11
|
+
end
|
12
|
+
|
13
|
+
def ==(other : <%= native_type_expr %>)
|
14
|
+
native == other
|
15
|
+
end
|
16
|
+
|
17
|
+
def value
|
18
|
+
data_pointer = @memory + data_offset
|
19
|
+
case data_pointer.value
|
20
|
+
<% union_types.each_with_index do |type, index| %>
|
21
|
+
when <%= index %>
|
22
|
+
<%= type.crystal_class_name %>.fetch_single(data_pointer+1)
|
23
|
+
<% end %>
|
24
|
+
else raise "Invalid union type #{data_pointer.value}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def native
|
29
|
+
data_pointer = @memory + data_offset
|
30
|
+
case data_pointer.value
|
31
|
+
<% union_types.each_with_index do |type, index| %>
|
32
|
+
when <%= index %>
|
33
|
+
<%= type.crystal_class_name %>.fetch_single(data_pointer+1).native
|
34
|
+
<% end %>
|
35
|
+
else raise "Invalid union type #{data_pointer.value}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.copy_to!(value : <%= native_type_expr %>, ptr : Pointer(::UInt8))
|
40
|
+
data_pointer = ptr + data_offset
|
41
|
+
case value
|
42
|
+
<% union_types.each_with_index do |type, index| %>
|
43
|
+
when <%= type.native_type_expr %>
|
44
|
+
data_pointer[0] = <%= index %>
|
45
|
+
<%= type.crystal_class_name %>.write_single(data_pointer + 1, value)
|
46
|
+
<% end %>
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.memsize
|
51
|
+
<%= memsize %>
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CrystalRuby::Types
|
4
|
+
TaggedUnion = Class.new(Type) { @error = "Union type must be instantiated from one or more concrete types" }
|
5
|
+
|
6
|
+
def self.TaggedUnion(*union_types)
|
7
|
+
Class.new(FixedWidth) do
|
8
|
+
# We only accept List-like values, which have all of the required keys
|
9
|
+
# and values of the correct type
|
10
|
+
# can successfully be cast to our inner types
|
11
|
+
def self.cast!(value)
|
12
|
+
casteable_type_index = union_types.find_index do |type, _index|
|
13
|
+
next false unless type.valid_cast?(value)
|
14
|
+
|
15
|
+
type.cast!(value)
|
16
|
+
next true
|
17
|
+
rescue StandardError
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
unless casteable_type_index
|
21
|
+
raise CrystalRuby::InvalidCastError,
|
22
|
+
"Cannot cast #{value}:#{value.class} to #{inspect}"
|
23
|
+
end
|
24
|
+
|
25
|
+
[casteable_type_index, value]
|
26
|
+
end
|
27
|
+
|
28
|
+
def value(native: false)
|
29
|
+
type = self.class.union_types[data_pointer.read_uint8]
|
30
|
+
type.fetch_single(data_pointer[1], native: native)
|
31
|
+
end
|
32
|
+
|
33
|
+
def nil?
|
34
|
+
value.nil?
|
35
|
+
end
|
36
|
+
|
37
|
+
def ==(other)
|
38
|
+
value == other
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.copy_to!((type_index, value), memory:)
|
42
|
+
memory[data_offset].write_int8(type_index)
|
43
|
+
union_types[type_index].write_single(memory[data_offset + 1], value)
|
44
|
+
end
|
45
|
+
|
46
|
+
def data_pointer
|
47
|
+
memory[data_offset]
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.each_child_address(pointer)
|
51
|
+
pointer += data_offset
|
52
|
+
type = self.union_types[pointer.read_uint8]
|
53
|
+
yield type, pointer[1]
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.inner_types
|
57
|
+
union_types
|
58
|
+
end
|
59
|
+
|
60
|
+
define_singleton_method(:memsize) do
|
61
|
+
union_types.map(&:refsize).max + 1
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.refsize
|
65
|
+
8
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.typename
|
69
|
+
"TaggedUnion"
|
70
|
+
end
|
71
|
+
|
72
|
+
define_singleton_method(:union_types) do
|
73
|
+
union_types
|
74
|
+
end
|
75
|
+
|
76
|
+
define_singleton_method(:valid?) do
|
77
|
+
union_types.all?(&:valid?)
|
78
|
+
end
|
79
|
+
|
80
|
+
define_singleton_method(:error) do
|
81
|
+
union_types.map(&:error).join(", ") if union_types.any?(&:error)
|
82
|
+
end
|
83
|
+
|
84
|
+
define_singleton_method(:inspect) do
|
85
|
+
if anonymous?
|
86
|
+
union_types.map(&:inspect).join(" | ")
|
87
|
+
else
|
88
|
+
crystal_class_name
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
define_singleton_method(:native_type_expr) do
|
93
|
+
union_types.map(&:native_type_expr).join(" | ")
|
94
|
+
end
|
95
|
+
|
96
|
+
define_singleton_method(:type_expr) do
|
97
|
+
anonymous? ? native_type_expr : name
|
98
|
+
end
|
99
|
+
|
100
|
+
define_singleton_method(:data_offset) do
|
101
|
+
4
|
102
|
+
end
|
103
|
+
|
104
|
+
define_singleton_method(:valid_cast?) do |raw|
|
105
|
+
union_types.any? { |type| type.valid_cast?(raw) }
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
class <%= base_crystal_class_name %> < CrystalRuby::Types::FixedWidth
|
2
|
+
|
3
|
+
def initialize(tuple : ::Tuple(<%= inner_types.map(&:native_type_expr).join(',') %>))
|
4
|
+
@memory = malloc(data_offset + 8)
|
5
|
+
self.value = tuple
|
6
|
+
increment_ref_count!
|
7
|
+
end
|
8
|
+
|
9
|
+
def data_pointer
|
10
|
+
Pointer(::UInt8).new((@memory + data_offset).as(Pointer(::UInt64)).value)
|
11
|
+
end
|
12
|
+
|
13
|
+
def [](index : Int)
|
14
|
+
index += size if index < 0
|
15
|
+
<% offset = 0 %>
|
16
|
+
<% inner_types.each_with_index do |type, i| %>
|
17
|
+
<% offset += type.refsize %>
|
18
|
+
return <%= type.crystal_class_name %>.fetch_single(data_pointer + <%= offset - type.refsize %>) if <%= i %> == index
|
19
|
+
<% end %>
|
20
|
+
end
|
21
|
+
|
22
|
+
<% inner_types.each_with_index.group_by{|t,i| t.native_type_expr }.each do |(native_type_expr, types_width_index)| %>
|
23
|
+
def []=(index : Int, value : <%= native_type_expr %>)
|
24
|
+
index += size if index < 0
|
25
|
+
<% types_width_index.each do |type, i| %>
|
26
|
+
return <%= type.crystal_class_name %>.write_single(data_pointer + <%= offset_for(i) %>, value) if <%= i %> == index
|
27
|
+
<% end %>
|
28
|
+
raise ArgumentError.new("Index out of bounds")
|
29
|
+
end
|
30
|
+
<% end %>
|
31
|
+
|
32
|
+
def ==(other : ::Tuple(<%= inner_types.map(&:native_type_expr).join(',') %>))
|
33
|
+
value == other
|
34
|
+
end
|
35
|
+
|
36
|
+
def value=(tuple : ::Tuple(<%= inner_types.map(&:native_type_expr).join(',') %>))
|
37
|
+
self.class.copy_to!(tuple, @memory)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.copy_to!(tuple : ::Tuple(<%= inner_types.map(&:native_type_expr).join(',') %>), memory : Pointer(::UInt8))
|
41
|
+
|
42
|
+
data_pointer = malloc(self.memsize)
|
43
|
+
address = data_pointer.address
|
44
|
+
|
45
|
+
<% inner_types.each_with_index do |type, i| %>
|
46
|
+
<%= type.crystal_class_name %>.write_single(data_pointer, tuple[<%= i %>])
|
47
|
+
data_pointer += <%= type.refsize %>
|
48
|
+
<% end %>
|
49
|
+
|
50
|
+
(memory+data_offset).as(Pointer(::UInt64)).value = address
|
51
|
+
end
|
52
|
+
|
53
|
+
def ==(other : <%= native_type_expr %>)
|
54
|
+
native == other
|
55
|
+
end
|
56
|
+
|
57
|
+
def value : ::Tuple(<%= inner_types.map(&:native_type_expr).join(',') %>)
|
58
|
+
address = data_pointer
|
59
|
+
<% inner_types.each_with_index do |type, i| %>
|
60
|
+
v<%= i %> = <%= type.crystal_class_name %>.fetch_single(address)
|
61
|
+
address += <%= type.refsize %>
|
62
|
+
<% end %>
|
63
|
+
::Tuple.new(<%= inner_types.map.with_index { |_, i| "v#{i}" }.join(", ") %>)
|
64
|
+
end
|
65
|
+
|
66
|
+
def native : ::Tuple(<%= inner_types.map(&:native_type_expr).join(',') %>)
|
67
|
+
address = data_pointer
|
68
|
+
<% inner_types.each_with_index do |type, i| %>
|
69
|
+
v<%= i %> = <%= type.crystal_class_name %>.fetch_single(address).native
|
70
|
+
address += <%= type.refsize %>
|
71
|
+
<% end %>
|
72
|
+
::Tuple.new(<%= inner_types.map.with_index { |_, i| "v#{i}" }.join(", ") %>)
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.memsize
|
76
|
+
<%= memsize %>
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.refsize
|
80
|
+
<%= refsize %>
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CrystalRuby::Types
|
4
|
+
Tuple = FixedWidth.build(
|
5
|
+
:Tuple,
|
6
|
+
error: "Tuple type must contain one or more types E.g. Tuple(Int32, String)"
|
7
|
+
)
|
8
|
+
|
9
|
+
def self.Tuple(*types)
|
10
|
+
FixedWidth.build(:Tuple, inner_types: types, convert_if: [Root::Array], superclass: Tuple) do
|
11
|
+
@data_offset = 4
|
12
|
+
|
13
|
+
# We only accept List-like values, which have all of the required keys
|
14
|
+
# and values of the correct type
|
15
|
+
# can successfully be cast to our inner types
|
16
|
+
def self.cast!(value)
|
17
|
+
unless (value.is_a?(Array) || value.is_a?(Tuple) || value.is_a?(Root::Array)) && value.zip(inner_types).each do |v, t|
|
18
|
+
t && t.valid_cast?(v)
|
19
|
+
end && value.length == inner_types.length
|
20
|
+
raise CrystalRuby::InvalidCastError, "Cannot cast #{value} to #{inspect}"
|
21
|
+
end
|
22
|
+
|
23
|
+
value
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.copy_to!(values, memory:)
|
27
|
+
data_pointer = malloc(memsize)
|
28
|
+
|
29
|
+
memory[data_offset].write_pointer(data_pointer)
|
30
|
+
|
31
|
+
inner_types.each.reduce(0) do |offset, type|
|
32
|
+
type.write_single(data_pointer[offset], values.shift)
|
33
|
+
offset + type.refsize
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.each_child_address(pointer)
|
38
|
+
data_pointer = pointer[data_offset].read_pointer
|
39
|
+
inner_types.each do |type|
|
40
|
+
yield type, data_pointer
|
41
|
+
data_pointer += type.refsize
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.memsize
|
46
|
+
inner_types.map(&:refsize).sum
|
47
|
+
end
|
48
|
+
|
49
|
+
def size
|
50
|
+
inner_types.size
|
51
|
+
end
|
52
|
+
|
53
|
+
def checked_offset!(index, size)
|
54
|
+
raise "Index out of bounds: #{index} >= #{size}" if index >= size
|
55
|
+
|
56
|
+
if index < 0
|
57
|
+
raise "Index out of bounds: #{index} < -#{size}" if index < -size
|
58
|
+
|
59
|
+
index += size
|
60
|
+
end
|
61
|
+
self.class.offset_for(index)
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.offset_for(index)
|
65
|
+
inner_types[0...index].map(&:refsize).sum
|
66
|
+
end
|
67
|
+
|
68
|
+
# Return the element at the given index.
|
69
|
+
# This will automatically increment
|
70
|
+
# the reference count if not a primitive type.
|
71
|
+
def [](index)
|
72
|
+
inner_types[index].fetch_single(data_pointer[checked_offset!(index, size)])
|
73
|
+
end
|
74
|
+
|
75
|
+
# Overwrite the element at the given index
|
76
|
+
# The replaced element will have
|
77
|
+
# its reference count decremented.
|
78
|
+
def []=(index, value)
|
79
|
+
inner_types[index].write_single(data_pointer[checked_offset!(index, size)], value)
|
80
|
+
end
|
81
|
+
|
82
|
+
def value(native: false)
|
83
|
+
ptr = data_pointer
|
84
|
+
inner_types.map do |type|
|
85
|
+
result = type.fetch_single(ptr, native: native)
|
86
|
+
ptr += type.refsize
|
87
|
+
result
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
module CrystalRuby
|
2
|
+
module Types
|
3
|
+
# For a fixed width type, we allocate a single block block of memory of the form
|
4
|
+
# [ref_count (uint32), data(uint8*)]
|
5
|
+
class FixedWidth < Type
|
6
|
+
|
7
|
+
# We can instantiate it with a Value (for a new object)
|
8
|
+
# or a Pointer (for a copy of an existing object)
|
9
|
+
macro inherited
|
10
|
+
def initialize(@memory : Pointer(::UInt8))
|
11
|
+
increment_ref_count!
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def finalize
|
16
|
+
self.class.decrement_ref_count!(@memory)
|
17
|
+
end
|
18
|
+
|
19
|
+
def increment_ref_count!
|
20
|
+
self.class.increment_ref_count!(@memory)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.increment_ref_count!(memory, by=1)
|
24
|
+
as_int32_ptr = memory.as(Pointer(::UInt32))
|
25
|
+
synchronize{ as_int32_ptr[0] += by }
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.decrement_ref_count!(memory, by=1)
|
29
|
+
as_int32_ptr = memory.as(Pointer(::UInt32))
|
30
|
+
synchronize{ as_int32_ptr[0] -= by }
|
31
|
+
free!(memory) if as_int32_ptr[0] == 0
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.refsize
|
35
|
+
8
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
def self.free!(memory)
|
40
|
+
# Decrease ref counts for any data we are pointing to
|
41
|
+
# Also responsible for freeing internal memory if ref count reaches zero
|
42
|
+
decr_inner_ref_counts!(memory)
|
43
|
+
# # Free slot memory
|
44
|
+
free(memory)
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.decr_inner_ref_counts!(pointer)
|
48
|
+
self.each_child_address(pointer) do |child_type, child_address|
|
49
|
+
child_type.decrement_ref_count!(child_address.read_pointer)
|
50
|
+
end
|
51
|
+
# Free data block, if we're a variable with type.
|
52
|
+
as_pointer_ref = (pointer+data_offset).as(Pointer(Pointer(::UInt8)))
|
53
|
+
free(as_pointer_ref[0]) if variable_width?
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.variable_width?
|
57
|
+
false
|
58
|
+
end
|
59
|
+
|
60
|
+
# Ref count is always the first UInt32 in the memory block
|
61
|
+
def ref_count
|
62
|
+
memory.as(Pointer(::UInt32))[0]
|
63
|
+
end
|
64
|
+
|
65
|
+
def ref_count=(val)
|
66
|
+
memory.as(Pointer(::UInt32))[0] = value
|
67
|
+
end
|
68
|
+
|
69
|
+
# Data pointer follows the ref count (and size for variable width types)
|
70
|
+
# In the case of variable width types the data pointer points to the start of a separate data block
|
71
|
+
# So this method is overridden inside variable_width.rb to resolve this pointer.
|
72
|
+
def data_pointer : Pointer(UInt8)
|
73
|
+
(memory + data_offset)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Create a brand new copy of this object
|
77
|
+
def deep_dup
|
78
|
+
self.class.new(value)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Create a new reference to this object.
|
82
|
+
def dup
|
83
|
+
self.class.new(@memory)
|
84
|
+
end
|
85
|
+
|
86
|
+
def address
|
87
|
+
memory.address
|
88
|
+
end
|
89
|
+
|
90
|
+
def data_offset
|
91
|
+
self.class.data_offset
|
92
|
+
end
|
93
|
+
|
94
|
+
def memsize
|
95
|
+
self.class.memsize
|
96
|
+
end
|
97
|
+
|
98
|
+
def size_offset
|
99
|
+
self.class.size_offset
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.size_offset
|
103
|
+
4
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.data_offset
|
107
|
+
4
|
108
|
+
end
|
109
|
+
|
110
|
+
# Read a value of this type from the
|
111
|
+
# contained pointer at a given index
|
112
|
+
def self.fetch_single(pointer : Pointer(::UInt8))
|
113
|
+
value_pointer = pointer.as(Pointer(Pointer(::UInt8)))
|
114
|
+
new(value_pointer.value)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Write a data type into a pointer at a given index
|
118
|
+
# (Type can be a byte-array, pointer or numeric type
|
119
|
+
#
|
120
|
+
# )
|
121
|
+
def self.write_single(pointer, value)
|
122
|
+
value_pointer = pointer.as(Pointer(Pointer(::UInt8)))
|
123
|
+
if !value_pointer[0].null?
|
124
|
+
decrement_ref_count!(value_pointer[0])
|
125
|
+
end
|
126
|
+
memory = malloc(self.data_offset + self.memsize)
|
127
|
+
|
128
|
+
self.copy_to!(value, memory)
|
129
|
+
value_pointer.value = memory
|
130
|
+
increment_ref_count!(memory)
|
131
|
+
end
|
132
|
+
|
133
|
+
# Fetch an array of a given data type from a list pointer
|
134
|
+
# (Type can be a byte-array, pointer or numeric type)
|
135
|
+
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,205 @@
|
|
1
|
+
module CrystalRuby
|
2
|
+
module Types
|
3
|
+
# For a fixed width type, we allocate a single block block of memory of the form
|
4
|
+
# [ref_count (uint32), data(uint8*)]
|
5
|
+
class FixedWidth < Type
|
6
|
+
# We can instantiate it with a Value (for a new object)
|
7
|
+
# or a Pointer (for a copy of an existing object)
|
8
|
+
def initialize(rbval)
|
9
|
+
super
|
10
|
+
case rbval
|
11
|
+
when FFI::Pointer then allocate_new_from_reference!(rbval)
|
12
|
+
else allocate_new_from_value!(rbval)
|
13
|
+
end
|
14
|
+
self.class.increment_ref_count!(memory)
|
15
|
+
ObjectSpace.define_finalizer(self, self.class.finalize(memory))
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.finalize(memory)
|
19
|
+
lambda { |_|
|
20
|
+
decrement_ref_count!(memory)
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
def allocate_new_from_value!(rbval)
|
25
|
+
# New block of memory, to hold our object.
|
26
|
+
# For variable with, this is 2x UInt32 for ref count and size, plus a data pointer (8 bytes)
|
27
|
+
# Layout:
|
28
|
+
# - ref_count (4 bytes)
|
29
|
+
# - size (4 bytes)
|
30
|
+
# - data (8 bytes)
|
31
|
+
#
|
32
|
+
# For fixed the data is inline
|
33
|
+
# Layout:
|
34
|
+
# - ref_count (4 bytes)
|
35
|
+
# - size (0 bytes) (No size for fixed width types)
|
36
|
+
# - data (memsize bytes)
|
37
|
+
self.memory = malloc(refsize + data_offset)
|
38
|
+
self.value = rbval
|
39
|
+
end
|
40
|
+
|
41
|
+
def allocate_new_from_reference!(memory)
|
42
|
+
# When we point to an existing block of memory, we don't need to allocate anything.
|
43
|
+
# This memory should be to a single, separately allocated block of the above size.
|
44
|
+
# When this type is contained within another type, it should be as a pointer to this block (not the contents of the block itself).
|
45
|
+
self.memory = memory
|
46
|
+
end
|
47
|
+
|
48
|
+
# Each type should be convertible to an FFI representation. (I.e. how is a value or reference to this value stored
|
49
|
+
# within e.g. an Array, Hash, Tuple or any other containing type).
|
50
|
+
# For both fixed and variable types these are simply stored within
|
51
|
+
# the containing type as a pointer to the memory block.
|
52
|
+
# We return the pointer to this memory here.
|
53
|
+
def self.to_ffi_repr(value)
|
54
|
+
to_store = new(value)
|
55
|
+
increment_ref_count!(to_store.memory)
|
56
|
+
to_store.memory
|
57
|
+
end
|
58
|
+
|
59
|
+
# Read a value of this type from the
|
60
|
+
# contained pointer at a given index
|
61
|
+
def self.fetch_single(pointer, native: false)
|
62
|
+
# Nothing to fetch for Nils
|
63
|
+
return if memsize.zero?
|
64
|
+
|
65
|
+
value_pointer = pointer.read_pointer
|
66
|
+
native ? new(value_pointer).native : new(value_pointer)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Write a data type into a pointer at a given index
|
70
|
+
# (Type can be a byte-array, pointer or numeric type
|
71
|
+
#
|
72
|
+
# )
|
73
|
+
def self.write_single(pointer, value)
|
74
|
+
# Dont need to write nils
|
75
|
+
return if memsize.zero?
|
76
|
+
|
77
|
+
decrement_ref_count!(pointer.read_pointer) unless pointer.read_pointer.null?
|
78
|
+
memory = malloc(refsize + data_offset)
|
79
|
+
copy_to!(cast!(value), memory: memory)
|
80
|
+
increment_ref_count!(memory)
|
81
|
+
pointer.write_pointer(memory)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Fetch an array of a given data type from a list pointer
|
85
|
+
# (Type can be a byte-array, pointer or numeric type)
|
86
|
+
def self.fetch_multi(pointer, size, native: false)
|
87
|
+
size.times.map { |i| fetch_single(pointer[i * refsize], native: native) }
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.increment_ref_count!(memory, by = 1)
|
91
|
+
synchronize { memory.write_int32(memory.read_int32 + by) }
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.decrement_ref_count!(memory, by = 1)
|
95
|
+
synchronize { memory.write_int32(memory.read_int32 - by) }
|
96
|
+
return unless memory.read_int32.zero?
|
97
|
+
|
98
|
+
free!(memory)
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.free!(memory)
|
102
|
+
# Decrease ref counts for any data we are pointing to
|
103
|
+
# Also responsible for freeing internal memory if ref count reaches zero
|
104
|
+
decr_inner_ref_counts!(memory)
|
105
|
+
|
106
|
+
# # Free slot memory
|
107
|
+
free(memory)
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.decr_inner_ref_counts!(pointer)
|
111
|
+
each_child_address(pointer) do |child_type, child_address|
|
112
|
+
child_type.decrement_ref_count!(child_address.read_pointer) if child_type.fixed_width?
|
113
|
+
end
|
114
|
+
# Free data block, if we're a variable width type.
|
115
|
+
return unless variable_width?
|
116
|
+
|
117
|
+
free(pointer[data_offset].read_pointer)
|
118
|
+
end
|
119
|
+
|
120
|
+
# Ref count is always the first Int32 in the memory block
|
121
|
+
def ref_count
|
122
|
+
memory.read_uint32
|
123
|
+
end
|
124
|
+
|
125
|
+
def ref_count=(val)
|
126
|
+
memory.write_int32(val)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Data pointer follows the ref count (and size for variable width types)
|
130
|
+
# In the case of variable width types the data pointer points to the start of a separate data block
|
131
|
+
# So this method is overridden inside variable_width.rb to resolve this pointer.
|
132
|
+
def data_pointer
|
133
|
+
memory[data_offset].read_pointer
|
134
|
+
end
|
135
|
+
|
136
|
+
def size
|
137
|
+
memory[size_offset].read_int32
|
138
|
+
end
|
139
|
+
|
140
|
+
def address
|
141
|
+
@memory.address
|
142
|
+
end
|
143
|
+
|
144
|
+
def self.crystal_supertype
|
145
|
+
"CrystalRuby::Types::FixedWidth"
|
146
|
+
end
|
147
|
+
|
148
|
+
def self.crystal_type
|
149
|
+
"Pointer(::UInt8)"
|
150
|
+
end
|
151
|
+
|
152
|
+
# If we are fixed with,
|
153
|
+
# The memory we allocate a single block of memory, if not already given.
|
154
|
+
# Within this block of memory, we copy our contents directly.
|
155
|
+
#
|
156
|
+
# If we are variable width, we allocate a small block of memory for the pointer only
|
157
|
+
# and allocate a separate block of memory for the data.
|
158
|
+
# We store the pointer to the data in the memory block.
|
159
|
+
|
160
|
+
def value=(value)
|
161
|
+
# If we're already pointing at something
|
162
|
+
# Decrement the ref counts of anything we're pointing at
|
163
|
+
value = cast!(value)
|
164
|
+
|
165
|
+
self.class.decr_inner_ref_counts!(memory) if ref_count > 0
|
166
|
+
self.class.copy_to!(value, memory: memory)
|
167
|
+
end
|
168
|
+
|
169
|
+
# Build a new FixedWith subtype
|
170
|
+
# Layout varies according to the sizes of internal types
|
171
|
+
def self.build(
|
172
|
+
typename = nil,
|
173
|
+
error: nil,
|
174
|
+
inner_types: nil,
|
175
|
+
inner_keys: nil,
|
176
|
+
ffi_type: :pointer,
|
177
|
+
memsize: FFI.type_size(ffi_type),
|
178
|
+
refsize: 8,
|
179
|
+
convert_if: [],
|
180
|
+
superclass: FixedWidth,
|
181
|
+
size_offset: 4,
|
182
|
+
data_offset: 4,
|
183
|
+
&block
|
184
|
+
)
|
185
|
+
inner_types&.each(&Type.method(:validate!))
|
186
|
+
|
187
|
+
Class.new(superclass) do
|
188
|
+
bind_local_vars!(
|
189
|
+
%i[typename error inner_types inner_keys ffi_type memsize convert_if size_offset data_offset
|
190
|
+
refsize], binding
|
191
|
+
)
|
192
|
+
class_eval(&block) if block_given?
|
193
|
+
|
194
|
+
def self.fixed_width?
|
195
|
+
true
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
require_relative "fixed_width/proc"
|
203
|
+
require_relative "fixed_width/named_tuple"
|
204
|
+
require_relative "fixed_width/tuple"
|
205
|
+
require_relative "fixed_width/tagged_union"
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module CrystalRuby
|
2
|
+
module Types
|
3
|
+
class Primitive < Type
|
4
|
+
def return_value
|
5
|
+
@value
|
6
|
+
end
|
7
|
+
|
8
|
+
def native
|
9
|
+
value
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.fetch_single(pointer : Pointer(::UInt8))
|
13
|
+
new(pointer)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.refsize
|
17
|
+
self.memsize
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|