Class: CrystalRuby::Types::FixedWidth

Inherits:
Type
  • Object
show all
Defined in:
lib/crystalruby/types/fixed_width.rb

Overview

For a fixed width type, we allocate a single block block of memory of the form

ref_count (uint32), data(uint8*)

Direct Known Subclasses

VariableWidth

Constant Summary

Constants inherited from Type

Type::ARC_MUTEX

Constants included from CrystalRuby::Typemaps

CrystalRuby::Typemaps::CRYSTAL_TYPE_MAP, CrystalRuby::Typemaps::C_TYPE_CONVERSIONS, CrystalRuby::Typemaps::C_TYPE_MAP, CrystalRuby::Typemaps::ERROR_VALUE, CrystalRuby::Typemaps::FFI_TYPE_MAP

Instance Attribute Summary

Attributes inherited from Type

#ffi_primitive, #memory, #value

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Type

#==, [], anonymous?, base_crystal_class_name, bind_local_vars!, cast!, #coerce, crystal_class_name, crystal_type_to_pointer_type_conversion, #deep_dup, #dup, each_child_address, ffi_primitive_type, fixed_width?, from_ffi_array_repr, inner_type, #inner_value, #inspect, inspect, inspect_name, #item_size, #method_missing, named_type_expr, #native, native_type_expr, nested_types, #nil?, numeric?, pointer_to_crystal_type_conversion, primitive?, subclass?, template_name, type_defn, type_digest, type_expr, union_types, valid?, valid_cast?, validate!, variable_width?, |

Methods included from CrystalRuby::Typemaps

#build_type_map, #convert_crystal_to_lib_type, #convert_lib_to_crystal_type, #crystal_type, #error_value, #ffi_type, #lib_type

Methods included from Allocator

gc_bytes_seen, gc_hint!, gc_hint_reset!, included

Constructor Details

#initialize(rbval) ⇒ FixedWidth

We can instantiate it with a Value (for a new object) or a Pointer (for a copy of an existing object)



8
9
10
11
12
13
14
15
16
17
# File 'lib/crystalruby/types/fixed_width.rb', line 8

def initialize(rbval)
  super
  case rbval
  when FFI::Pointer then allocate_new_from_reference!(rbval)
  else allocate_new_from_value!(rbval)
  end
  self.class.increment_ref_count!(memory)
  ObjectSpace.define_finalizer(self, self.class.finalize(memory, self.class))
  Allocator.gc_hint!(total_memsize)
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class CrystalRuby::Types::Type

Class Method Details

.build(typename = nil, error: nil, inner_types: nil, inner_keys: nil, ffi_type: :pointer, memsize: FFI.type_size(ffi_type), refsize: 8, convert_if: [], superclass: FixedWidth, size_offset: 4, data_offset: 4, ffi_primitive: false, &block) ⇒ Object

Build a new FixedWith subtype Layout varies according to the sizes of internal types



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/crystalruby/types/fixed_width.rb', line 176

def self.build(
  typename = nil,
  error: nil,
  inner_types: nil,
  inner_keys: nil,
  ffi_type: :pointer,
  memsize: FFI.type_size(ffi_type),
  refsize: 8,
  convert_if: [],
  superclass: FixedWidth,
  size_offset: 4,
  data_offset: 4,
  ffi_primitive: false,
  &block
)
  inner_types&.each(&Type.method(:validate!))

  Class.new(superclass) do
    bind_local_vars!(
      %i[typename error inner_types inner_keys ffi_type memsize convert_if size_offset data_offset
         refsize ffi_primitive], binding
    )
    class_eval(&block) if block_given?

    def self.fixed_width?
      true
    end
  end
end

.crystal_supertypeObject



149
150
151
# File 'lib/crystalruby/types/fixed_width.rb', line 149

def self.crystal_supertype
  "CrystalRuby::Types::FixedWidth"
end

.crystal_typeObject



153
154
155
# File 'lib/crystalruby/types/fixed_width.rb', line 153

def self.crystal_type
  "Pointer(::UInt8)"
end

.decr_inner_ref_counts!(pointer) ⇒ Object



111
112
113
114
115
116
117
118
119
# File 'lib/crystalruby/types/fixed_width.rb', line 111

def self.decr_inner_ref_counts!(pointer)
  each_child_address(pointer) do |child_type, child_address|
    child_type.decrement_ref_count!(child_address.read_pointer) if child_type.fixed_width?
  end
  # Free data block, if we're a variable width type.
  return unless variable_width?

  free(pointer[data_offset].read_pointer)
end

.decrement_ref_count!(memory, by = 1) ⇒ Object



95
96
97
98
99
100
# File 'lib/crystalruby/types/fixed_width.rb', line 95

def self.decrement_ref_count!(memory, by = 1)
  synchronize { memory.write_int32(memory.read_int32 - by) }
  return unless memory.read_int32.zero?

  free!(memory)
end

.fetch_multi(pointer, size, native: false) ⇒ Object

Fetch an array of a given data type from a list pointer (Type can be a byte-array, pointer or numeric type)



87
88
89
# File 'lib/crystalruby/types/fixed_width.rb', line 87

def self.fetch_multi(pointer, size, native: false)
  size.times.map { |i| fetch_single(pointer[i * refsize], native: native) }
end

.fetch_single(pointer, native: false) ⇒ Object

Read a value of this type from the contained pointer at a given index



62
63
64
65
66
67
68
# File 'lib/crystalruby/types/fixed_width.rb', line 62

def self.fetch_single(pointer, native: false)
  # Nothing to fetch for Nils
  return if memsize.zero?

  value_pointer = pointer.read_pointer
  native ? new(value_pointer).native : new(value_pointer)
end

.finalize(memory, type) ⇒ Object



19
20
21
22
23
# File 'lib/crystalruby/types/fixed_width.rb', line 19

def self.finalize(memory, type)
  lambda do |_|
    decrement_ref_count!(memory)
  end
end

.free!(memory) ⇒ Object



102
103
104
105
106
107
108
109
# File 'lib/crystalruby/types/fixed_width.rb', line 102

def self.free!(memory)
  # Decrease ref counts for any data we are pointing to
  # Also responsible for freeing internal memory if ref count reaches zero
  decr_inner_ref_counts!(memory)

  # # Free slot memory
  free(memory)
end

.increment_ref_count!(memory, by = 1) ⇒ Object



91
92
93
# File 'lib/crystalruby/types/fixed_width.rb', line 91

def self.increment_ref_count!(memory, by = 1)
  synchronize { memory.write_int32(memory.read_int32 + by) }
end

.to_ffi_repr(value) ⇒ Object

Each type should be convertible to an FFI representation. (I.e. how is a value or reference to this value stored within e.g. an Array, Hash, Tuple or any other containing type). For both fixed and variable types these are simply stored within the containing type as a pointer to the memory block. We return the pointer to this memory here.



54
55
56
57
58
# File 'lib/crystalruby/types/fixed_width.rb', line 54

def self.to_ffi_repr(value)
  to_store = new(value)
  increment_ref_count!(to_store.memory)
  to_store.memory
end

.write_single(pointer, value) ⇒ Object

Write a data type into a pointer at a given index (Type can be a byte-array, pointer or numeric type

)



74
75
76
77
78
79
80
81
82
83
# File 'lib/crystalruby/types/fixed_width.rb', line 74

def self.write_single(pointer, value)
  # Dont need to write nils
  return if memsize.zero?

  decrement_ref_count!(pointer.read_pointer) unless pointer.read_pointer.null?
  memory = malloc(refsize + data_offset)
  copy_to!(cast!(value), memory: memory)
  increment_ref_count!(memory)
  pointer.write_pointer(memory)
end

Instance Method Details

#addressObject



145
146
147
# File 'lib/crystalruby/types/fixed_width.rb', line 145

def address
  @memory.address
end

#allocate_new_from_reference!(memory) ⇒ Object



42
43
44
45
46
47
# File 'lib/crystalruby/types/fixed_width.rb', line 42

def allocate_new_from_reference!(memory)
  # When we point to an existing block of memory, we don't need to allocate anything.
  # This memory should be to a single, separately allocated block of the above size.
  # When this type is contained within another type, it should be as a pointer to this block (not the contents of the block itself).
  self.memory = memory
end

#allocate_new_from_value!(rbval) ⇒ Object



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/crystalruby/types/fixed_width.rb', line 25

def allocate_new_from_value!(rbval)
  # New block of memory, to hold our object.
  # For variable with, this is 2x UInt32 for ref count and size, plus a data pointer (8 bytes)
  # Layout:
  # - ref_count (4 bytes)
  # - size (4 bytes)
  # - data (8 bytes)
  #
  # For fixed the data is inline
  # Layout:
  # - ref_count (4 bytes)
  # - size (0 bytes) (No size for fixed width types)
  # - data (memsize bytes)
  self.memory = malloc(refsize + data_offset)
  self.value = rbval
end

#data_pointerObject

Data pointer follows the ref count (and size for variable width types) In the case of variable width types the data pointer points to the start of a separate data block So this method is overridden inside variable_width.rb to resolve this pointer.



133
134
135
# File 'lib/crystalruby/types/fixed_width.rb', line 133

def data_pointer
  memory[data_offset].read_pointer
end

#ref_countObject

Ref count is always the first Int32 in the memory block



122
123
124
# File 'lib/crystalruby/types/fixed_width.rb', line 122

def ref_count
  memory.read_uint32
end

#ref_count=(val) ⇒ Object



126
127
128
# File 'lib/crystalruby/types/fixed_width.rb', line 126

def ref_count=(val)
  memory.write_int32(val)
end

#sizeObject



137
138
139
# File 'lib/crystalruby/types/fixed_width.rb', line 137

def size
  memory[size_offset].read_int32
end

#total_memsizeObject



141
142
143
# File 'lib/crystalruby/types/fixed_width.rb', line 141

def total_memsize
  memsize + refsize + size
end

#value=(value) ⇒ Object

If we are fixed with, The memory we allocate a single block of memory, if not already given. Within this block of memory, we copy our contents directly.

If we are variable width, we allocate a small block of memory for the pointer only and allocate a separate block of memory for the data. We store the pointer to the data in the memory block.



165
166
167
168
169
170
171
172
# File 'lib/crystalruby/types/fixed_width.rb', line 165

def value=(value)
  # If we're already pointing at something
  # Decrement the ref counts of anything we're pointing at
  value = cast!(value)

  self.class.decr_inner_ref_counts!(memory) if ref_count > 0
  self.class.copy_to!(value, memory: memory)
end