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

#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, fixed_width?, from_ffi_array_repr, inner_type, #inner_value, inspect, #inspect, inspect_name, #item_size, #method_missing, #native, native_type_expr, nested_types, #nil?, numeric?, pointer_to_crystal_type_conversion, primitive?, 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

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
# 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))
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, &block) ⇒ Object

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



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/crystalruby/types/fixed_width.rb', line 171

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,
  &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], binding
    )
    class_eval(&block) if block_given?

    def self.fixed_width?
      true
    end
  end
end

.crystal_supertypeObject



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

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

.crystal_typeObject



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

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

.decr_inner_ref_counts!(pointer) ⇒ Object



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

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



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

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)



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

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



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

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) ⇒ Object



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

def self.finalize(memory)
  lambda { |_|
    decrement_ref_count!(memory)
  }
end

.free!(memory) ⇒ Object



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

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



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

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.



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

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

)



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

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



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

def address
  @memory.address
end

#allocate_new_from_reference!(memory) ⇒ Object



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

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



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

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.



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

def data_pointer
  memory[data_offset].read_pointer
end

#ref_countObject

Ref count is always the first Int32 in the memory block



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

def ref_count
  memory.read_uint32
end

#ref_count=(val) ⇒ Object



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

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

#sizeObject



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

def size
  memory[size_offset].read_int32
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.



160
161
162
163
164
165
166
167
# File 'lib/crystalruby/types/fixed_width.rb', line 160

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