ikra 0.0.1 → 0.0.2
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/lib/ast/builder.rb +225 -77
- data/lib/ast/host_section_builder.rb +38 -0
- data/lib/ast/interpreter.rb +67 -0
- data/lib/ast/lexical_variables_enumerator.rb +3 -2
- data/lib/ast/nodes.rb +521 -31
- data/lib/ast/printer.rb +116 -18
- data/lib/ast/ssa_generator.rb +192 -0
- data/lib/ast/visitor.rb +235 -21
- data/lib/config/configuration.rb +28 -3
- data/lib/config/os_configuration.rb +62 -9
- data/lib/cpu/cpu_implementation.rb +39 -0
- data/lib/ikra.rb +13 -3
- data/lib/resources/cuda/allocate_device_memory.cpp +5 -0
- data/lib/resources/cuda/allocate_host_memory.cpp +1 -0
- data/lib/resources/cuda/allocate_memcpy_environment_to_device.cpp +11 -0
- data/lib/resources/cuda/ast/assignment.cpp +1 -0
- data/lib/resources/cuda/block_function_head.cpp +7 -1
- data/lib/resources/cuda/entry_point.cpp +47 -0
- data/lib/resources/cuda/env_builder_copy_array.cpp +8 -2
- data/lib/resources/cuda/free_device_memory.cpp +3 -0
- data/lib/resources/cuda/free_memory_for_command.cpp +24 -0
- data/lib/resources/cuda/header.cpp +23 -9
- data/lib/resources/cuda/header_structs.cpp +92 -0
- data/lib/resources/cuda/host_section_block_function_head.cpp +12 -0
- data/lib/resources/cuda/host_section_entry_point.cpp +55 -0
- data/lib/resources/cuda/host_section_free_device_memory.cpp +18 -0
- data/lib/resources/cuda/host_section_launch_parallel_section.cpp +14 -0
- data/lib/resources/cuda/host_section_malloc_memcpy_device_to_host.cpp +10 -0
- data/lib/resources/cuda/kernel.cpp +9 -2
- data/lib/resources/cuda/launch_kernel.cpp +5 -0
- data/lib/resources/cuda/memcpy_device_to_host.cpp +3 -0
- data/lib/resources/cuda/memcpy_device_to_host_expr.cpp +10 -0
- data/lib/resources/cuda/reduce_body.cpp +88 -0
- data/lib/resources/cuda/stencil_array_reconstruction.cpp +2 -0
- data/lib/resources/cuda/stencil_body.cpp +16 -0
- data/lib/resources/cuda/struct_definition.cpp +4 -0
- data/lib/ruby_core/array.rb +34 -0
- data/lib/ruby_core/array_command.rb +313 -0
- data/lib/ruby_core/core.rb +103 -0
- data/lib/ruby_core/interpreter.rb +16 -0
- data/lib/ruby_core/math.rb +32 -0
- data/lib/ruby_core/ruby_integration.rb +256 -0
- data/lib/symbolic/host_section.rb +115 -0
- data/lib/symbolic/input.rb +87 -0
- data/lib/symbolic/input_visitor.rb +68 -0
- data/lib/symbolic/symbolic.rb +793 -117
- data/lib/symbolic/visitor.rb +70 -8
- data/lib/translator/array_command_struct_builder.rb +163 -0
- data/lib/translator/ast_translator.rb +572 -0
- data/lib/translator/block_translator.rb +104 -48
- data/lib/translator/commands/array_combine_command.rb +41 -0
- data/lib/translator/commands/array_identity_command.rb +28 -0
- data/lib/translator/commands/array_index_command.rb +52 -0
- data/lib/translator/commands/array_reduce_command.rb +135 -0
- data/lib/translator/commands/array_stencil_command.rb +129 -0
- data/lib/translator/commands/array_zip_command.rb +30 -0
- data/lib/translator/commands/command_translator.rb +264 -0
- data/lib/translator/cuda_errors.rb +32 -0
- data/lib/translator/environment_builder.rb +263 -0
- data/lib/translator/host_section/array_host_section_command.rb +150 -0
- data/lib/translator/host_section/array_in_host_section_command.rb +41 -0
- data/lib/translator/host_section/ast_translator.rb +14 -0
- data/lib/translator/host_section/parallel_section_invocation_visitor.rb +20 -0
- data/lib/translator/host_section/program_builder.rb +89 -0
- data/lib/translator/input_translator.rb +226 -0
- data/lib/translator/kernel_builder.rb +137 -0
- data/lib/translator/kernel_launcher/for_loop_kernel_launcher.rb +40 -0
- data/lib/translator/kernel_launcher/kernel_launcher.rb +259 -0
- data/lib/translator/kernel_launcher/while_loop_kernel_launcher.rb +38 -0
- data/lib/translator/last_returns_visitor.rb +19 -10
- data/lib/translator/program_builder.rb +197 -0
- data/lib/translator/program_launcher.rb +273 -0
- data/lib/translator/struct_type.rb +55 -0
- data/lib/translator/translator.rb +34 -11
- data/lib/translator/variable_classifier_visitor.rb +56 -0
- data/lib/types/inference/ast_inference.rb +586 -0
- data/lib/types/inference/clear_types_visitor.rb +11 -0
- data/lib/types/inference/command_inference.rb +101 -0
- data/lib/types/inference/input_inference.rb +62 -0
- data/lib/types/{object_tracer.rb → inference/object_tracer.rb} +5 -6
- data/lib/types/inference/ruby_extension.rb +35 -0
- data/lib/types/inference/symbol_table.rb +131 -0
- data/lib/types/types.rb +14 -0
- data/lib/types/types/array_command_type.rb +123 -0
- data/lib/types/types/array_type.rb +137 -0
- data/lib/types/{class_type.rb → types/class_type.rb} +42 -18
- data/lib/types/{primitive_type.rb → types/primitive_type.rb} +20 -7
- data/lib/types/types/ruby_type.rb +88 -0
- data/lib/types/types/struct_type.rb +179 -0
- data/lib/types/types/union_type.rb +239 -0
- metadata +160 -18
- data/lib/ast/method_definition.rb +0 -37
- data/lib/ast/translator.rb +0 -264
- data/lib/resources/cuda/kernel_launcher.cpp +0 -28
- data/lib/scope.rb +0 -166
- data/lib/translator/command_translator.rb +0 -421
- data/lib/translator/local_variables_enumerator.rb +0 -35
- data/lib/translator/method_translator.rb +0 -24
- data/lib/types/array_type.rb +0 -51
- data/lib/types/ruby_extension.rb +0 -67
- data/lib/types/ruby_type.rb +0 -45
- data/lib/types/type_inference.rb +0 -382
- data/lib/types/union_type.rb +0 -155
@@ -0,0 +1,137 @@
|
|
1
|
+
# No explicit `require`s. This file should be includes via types.rb
|
2
|
+
|
3
|
+
module Ikra
|
4
|
+
module Types
|
5
|
+
class ArrayType
|
6
|
+
include RubyType
|
7
|
+
|
8
|
+
class << self
|
9
|
+
alias_method :new_original, :new
|
10
|
+
|
11
|
+
# Ensure singleton per class
|
12
|
+
def new(inner_type)
|
13
|
+
if @cache == nil
|
14
|
+
@cache = {}
|
15
|
+
@cache.default_proc = proc do |hash, key|
|
16
|
+
hash[key] = new_original(key)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
return @cache[inner_type]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_reader :inner_type
|
25
|
+
|
26
|
+
def ==(other)
|
27
|
+
return other.class == self.class && other.inner_type == self.inner_type
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(inner_type)
|
31
|
+
if not inner_type.is_union_type?
|
32
|
+
raise AssertionError.new("Union type expected")
|
33
|
+
end
|
34
|
+
|
35
|
+
@inner_type = inner_type
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_s
|
39
|
+
return "[#{self.class.to_s}, inner_type = #{inner_type}]"
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_c_type
|
43
|
+
return "#{@inner_type.to_c_type} *"
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_ffi_type
|
47
|
+
return :pointer
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_ruby_type
|
51
|
+
return ::Array
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class LocationAwareArrayType < ArrayType
|
56
|
+
# Determines if the array is allocated on the host or on the device
|
57
|
+
attr_reader :location
|
58
|
+
|
59
|
+
def ==(other)
|
60
|
+
return super && self.location == other.location
|
61
|
+
end
|
62
|
+
|
63
|
+
def initialize(inner_type, location)
|
64
|
+
@inner_type = inner_type
|
65
|
+
@location = location
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_c_type
|
69
|
+
return "variable_size_array_t"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class LocationAwareVariableSizeArrayType < LocationAwareArrayType
|
74
|
+
class << self
|
75
|
+
def new(inner_type, location: :device)
|
76
|
+
if @cache == nil
|
77
|
+
@cache = {}
|
78
|
+
@cache.default_proc = Proc.new do |hash, key|
|
79
|
+
hash[key] = new_original(*key)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
return @cache[[inner_type, location]]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def to_command
|
88
|
+
# No fusion possible here. The first parameter (target) is a reference to the
|
89
|
+
# array command struct representing the [ArrayInHostSectionCommand].
|
90
|
+
# TODO: The code depends on the template (variable name `cmd` and `input_0`).
|
91
|
+
return Symbolic::ArrayInHostSectionCommand.new("((#{@inner_type.to_c_type} *) cmd->input_0)", @inner_type)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
class LocationAwareFixedSizeArrayType < LocationAwareArrayType
|
96
|
+
class << self
|
97
|
+
def new(inner_type, dimensions, location: :device)
|
98
|
+
if @cache == nil
|
99
|
+
@cache = {}
|
100
|
+
@cache.default_proc = Proc.new do |hash, key|
|
101
|
+
hash[key] = new_original(*key)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
return @cache[[inner_type, location, dimensions]]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
attr_reader :dimensions
|
110
|
+
|
111
|
+
def initialize(inner_type, location, dimensions)
|
112
|
+
super(inner_type, location)
|
113
|
+
@dimensions = dimensions
|
114
|
+
end
|
115
|
+
|
116
|
+
def to_command
|
117
|
+
# No fusion possible here. The first parameter (target) is a reference to the
|
118
|
+
# array command struct representing the [ArrayInHostSectionCommand].
|
119
|
+
# TODO: The code depends on the template (variable name `cmd` and `input_0`).
|
120
|
+
return Symbolic::FixedSizeArrayInHostSectionCommand.new(
|
121
|
+
"((#{@inner_type.to_c_type} *) cmd->input_0)", @inner_type, @dimensions)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
class Array
|
128
|
+
def ikra_type
|
129
|
+
inner_type = Ikra::Types::UnionType.new
|
130
|
+
|
131
|
+
self.each do |element|
|
132
|
+
inner_type.add(element.ikra_type)
|
133
|
+
end
|
134
|
+
|
135
|
+
return Ikra::Types::ArrayType.new(inner_type)
|
136
|
+
end
|
137
|
+
end
|
@@ -1,9 +1,9 @@
|
|
1
|
+
# No explicit `require`s. This file should be includes via types.rb
|
2
|
+
|
1
3
|
require "set"
|
2
|
-
require_relative "
|
3
|
-
require_relative "
|
4
|
-
require_relative "
|
5
|
-
require_relative "../parsing"
|
6
|
-
require_relative "../ast/builder"
|
4
|
+
require_relative "../../sourcify/lib/sourcify"
|
5
|
+
require_relative "../../parsing"
|
6
|
+
require_relative "../../ast/builder"
|
7
7
|
|
8
8
|
module Ikra
|
9
9
|
module Types
|
@@ -27,6 +27,10 @@ module Ikra
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
+
def ==(other)
|
31
|
+
return other.class == self.class && other.cls == self.cls
|
32
|
+
end
|
33
|
+
|
30
34
|
def initialize(cls)
|
31
35
|
@cls = cls
|
32
36
|
@inst_vars_read = Set.new
|
@@ -71,23 +75,36 @@ module Ikra
|
|
71
75
|
"obj_id_t"
|
72
76
|
end
|
73
77
|
|
78
|
+
# Generates a class name for [@cls], which is a valid C++ identifier.
|
79
|
+
#
|
80
|
+
# For example:
|
81
|
+
# A --> A
|
82
|
+
# #<Class: A> --> singleton_A
|
83
|
+
def class_name
|
84
|
+
# Handle name generation for singleton classes
|
85
|
+
return ruby_name.gsub("\#<Class:", "singleton_").gsub(">", "")
|
86
|
+
end
|
87
|
+
|
74
88
|
def mangled_method_name(selector)
|
75
|
-
"_method_#{
|
89
|
+
"_method_#{class_name}_#{selector}_"
|
76
90
|
end
|
77
91
|
|
78
92
|
def inst_var_array_name(inst_var_name)
|
79
93
|
if inst_var_name.to_s[0] != "@"
|
80
|
-
raise "Expected instance variable identifier"
|
94
|
+
raise AssertionError.new("Expected instance variable identifier")
|
81
95
|
end
|
82
96
|
|
83
|
-
"_iv_#{
|
97
|
+
"_iv_#{class_name}_#{inst_var_name.to_s[1..-1]}_"
|
84
98
|
end
|
85
99
|
|
86
100
|
def method_ast(selector)
|
87
101
|
source = Parsing.parse_method(cls.instance_method(selector))
|
88
|
-
|
89
|
-
|
90
|
-
|
102
|
+
return AST::Builder.from_parser_ast(source)
|
103
|
+
end
|
104
|
+
|
105
|
+
def method_binding(selector)
|
106
|
+
# TODO: Fix binding
|
107
|
+
return cls.instance_method(selector).send(:binding)
|
91
108
|
end
|
92
109
|
|
93
110
|
def method_parameters(selector)
|
@@ -98,12 +115,13 @@ module Ikra
|
|
98
115
|
end
|
99
116
|
end
|
100
117
|
|
101
|
-
def
|
102
|
-
|
118
|
+
def should_generate_self_arg?
|
119
|
+
# Do not generate type for singleton classes
|
120
|
+
return !to_ruby_type.is_a?(Module.singleton_class)
|
103
121
|
end
|
104
122
|
|
105
123
|
def to_s
|
106
|
-
"<class: #{
|
124
|
+
"<class: #{class_name}>"
|
107
125
|
end
|
108
126
|
|
109
127
|
def c_size
|
@@ -117,12 +135,18 @@ end
|
|
117
135
|
class Object
|
118
136
|
def self.to_ikra_type
|
119
137
|
# TODO: should this method be defined on Class?
|
120
|
-
Ikra::Types::ClassType.new(self)
|
138
|
+
return Ikra::Types::ClassType.new(self)
|
121
139
|
end
|
122
140
|
|
123
|
-
# Returns the [Ikra::Types::RubyType] for this
|
124
|
-
|
125
|
-
|
141
|
+
# Returns the [Ikra::Types::RubyType] for this object. Instance of the same Ruby class can
|
142
|
+
# principally have different Ikra types. Thus, this method is defined as an instance method.
|
143
|
+
def ikra_type
|
144
|
+
if self.is_a?(Module)
|
145
|
+
return self.singleton_class.to_ikra_type
|
146
|
+
else
|
147
|
+
# TODO: Double check if we always want to have the singleton class?
|
148
|
+
return self.class.to_ikra_type
|
149
|
+
end
|
126
150
|
end
|
127
151
|
end
|
128
152
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
# No explicit `require`s. This file should be includes via types.rb
|
2
2
|
|
3
3
|
module Ikra
|
4
4
|
module Types
|
@@ -6,19 +6,26 @@ module Ikra
|
|
6
6
|
include RubyType
|
7
7
|
|
8
8
|
attr_reader :c_size
|
9
|
+
attr_reader :class_id
|
9
10
|
|
10
|
-
def initialize(c_type, ruby_type, c_size, ffi_type)
|
11
|
+
def initialize(c_type, ruby_type, c_size, ffi_type, class_id)
|
11
12
|
@c_type = c_type
|
12
13
|
@ruby_type = ruby_type
|
13
14
|
@c_size = c_size
|
14
15
|
@ffi_type = ffi_type
|
16
|
+
@class_id = class_id
|
15
17
|
end
|
16
18
|
|
17
|
-
Int = self.new("int", Fixnum, 4, :int)
|
18
|
-
Float = self.new("float", Float, 4, :float)
|
19
|
-
Bool = self.new("bool", TrueClass, 1, :bool)
|
20
|
-
Void = self.new("void", nil, 0, :void)
|
19
|
+
Int = self.new("int", Fixnum, 4, :int, 1)
|
20
|
+
Float = self.new("float", Float, 4, :float, 2)
|
21
|
+
Bool = self.new("bool", TrueClass, 1, :bool, 3)
|
22
|
+
Void = self.new("void", nil, 0, :void, 4)
|
23
|
+
Nil = self.new("int", NilClass, 4, :int, 5)
|
21
24
|
|
25
|
+
def ==(other)
|
26
|
+
return other.is_a?(PrimitiveType) && other.class_id == class_id
|
27
|
+
end
|
28
|
+
|
22
29
|
def to_ruby_type
|
23
30
|
@ruby_type
|
24
31
|
end
|
@@ -30,7 +37,7 @@ module Ikra
|
|
30
37
|
def to_ffi_type
|
31
38
|
@ffi_type
|
32
39
|
end
|
33
|
-
|
40
|
+
|
34
41
|
def is_primitive?
|
35
42
|
true
|
36
43
|
end
|
@@ -71,3 +78,9 @@ class FalseClass
|
|
71
78
|
Ikra::Types::PrimitiveType::Bool
|
72
79
|
end
|
73
80
|
end
|
81
|
+
|
82
|
+
class NilClass
|
83
|
+
def self.to_ikra_type
|
84
|
+
Ikra::Types::PrimitiveType::Nil
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require "set"
|
2
|
+
|
3
|
+
module Ikra
|
4
|
+
module Types
|
5
|
+
|
6
|
+
# Defines the minimal interface for Ikra types. Instances of {UnionType} are expected in most cases.
|
7
|
+
module RubyType
|
8
|
+
@@next_class_id = 10
|
9
|
+
|
10
|
+
def to_str
|
11
|
+
return to_s
|
12
|
+
end
|
13
|
+
|
14
|
+
def inspect
|
15
|
+
return to_s
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_ruby_type
|
19
|
+
raise NotImplementedError
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_c_type
|
23
|
+
raise NotImplementedError
|
24
|
+
end
|
25
|
+
|
26
|
+
def is_primitive?
|
27
|
+
false
|
28
|
+
end
|
29
|
+
|
30
|
+
def is_union_type?
|
31
|
+
false
|
32
|
+
end
|
33
|
+
|
34
|
+
def should_generate_self_arg?
|
35
|
+
return true
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_array_type
|
39
|
+
# TODO: This should probably not return a union type by default
|
40
|
+
return ArrayType.new(self).to_union_type
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_union_type
|
44
|
+
return UnionType.new(self)
|
45
|
+
end
|
46
|
+
|
47
|
+
def class_id
|
48
|
+
if @class_id == nil
|
49
|
+
@class_id = @@next_class_id
|
50
|
+
@@next_class_id += 1
|
51
|
+
end
|
52
|
+
|
53
|
+
@class_id
|
54
|
+
end
|
55
|
+
|
56
|
+
def eql?(other)
|
57
|
+
return self == other
|
58
|
+
end
|
59
|
+
|
60
|
+
def hash
|
61
|
+
# TODO: Implement
|
62
|
+
return 0
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# This type is marker and denotes that an expression should be executed only in the Ruby
|
67
|
+
# interpreter. No CUDA code should be generated for such expressions.
|
68
|
+
class InterpreterOnlyType
|
69
|
+
include RubyType
|
70
|
+
|
71
|
+
def self.new
|
72
|
+
if @singleton_instance == nil
|
73
|
+
@singleton_instance = super
|
74
|
+
end
|
75
|
+
|
76
|
+
return @singleton_instance
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class Array
|
83
|
+
def to_type_array_string
|
84
|
+
"[" + map do |set|
|
85
|
+
set.to_s
|
86
|
+
end.join(", ") + "]"
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
# No explicit `require`s. This file should be includes via types.rb
|
2
|
+
|
3
|
+
require "ffi"
|
4
|
+
|
5
|
+
module Ikra
|
6
|
+
module Types
|
7
|
+
# This type represents a C++ struct. It has fields, each of which has
|
8
|
+
# a type.
|
9
|
+
class StructType
|
10
|
+
include RubyType
|
11
|
+
|
12
|
+
attr_reader :fields
|
13
|
+
|
14
|
+
class << self
|
15
|
+
# Ensure singleton per class
|
16
|
+
def new(fields)
|
17
|
+
if @cache == nil
|
18
|
+
@cache = {}
|
19
|
+
end
|
20
|
+
|
21
|
+
if not @cache.include?(identifier_from_hash(fields))
|
22
|
+
@cache[identifier_from_hash(fields)] = super(fields)
|
23
|
+
end
|
24
|
+
|
25
|
+
@cache[identifier_from_hash(fields)]
|
26
|
+
end
|
27
|
+
|
28
|
+
# Generates a unique type identifier based on the types of the struct fields.
|
29
|
+
def identifier_from_hash(fields)
|
30
|
+
identifier = "indexed_struct_#{fields.size}_lt_"
|
31
|
+
|
32
|
+
type_parts = fields.map do |key, value|
|
33
|
+
value.to_c_type
|
34
|
+
end
|
35
|
+
|
36
|
+
identifier = identifier + type_parts.join("_")
|
37
|
+
|
38
|
+
return identifier + "_gt_t"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def ==(other)
|
43
|
+
return other.class == self.class && other.fields == self.fields
|
44
|
+
end
|
45
|
+
|
46
|
+
def initialize(fields)
|
47
|
+
fields.each do |key, value|
|
48
|
+
if not value.is_union_type?
|
49
|
+
raise AssertionError.new("Union type expected for field #{key}")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
@fields = fields
|
54
|
+
end
|
55
|
+
|
56
|
+
def to_c_type
|
57
|
+
return StructType.identifier_from_hash(@fields)
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_ffi_type
|
61
|
+
# TODO: Support transfering zipped data back from GPU
|
62
|
+
return to_ruby_type
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_ruby_type
|
66
|
+
# TODO: general structs
|
67
|
+
raise NotImplementedError.new
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# This type represents the type of an array that is the result of
|
72
|
+
# zipping two arrays. [ZipStructType] is similar to [StructType] but
|
73
|
+
# can be accessed via indices.
|
74
|
+
class ZipStructType < StructType
|
75
|
+
class << self
|
76
|
+
def new(*types)
|
77
|
+
identifiers = Array.new(types.size) do |index|
|
78
|
+
:"field_#{index}"
|
79
|
+
end
|
80
|
+
|
81
|
+
super(Hash[identifiers.zip(types)])
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Performs type inference for the result of accessing this Zip "Array" by index.
|
86
|
+
def get_return_type(selector, *arg_nodes)
|
87
|
+
# TODO: Can only handle single cases at the moment. This should eventually forward
|
88
|
+
# to Array integration code.
|
89
|
+
|
90
|
+
if selector != :"[]"
|
91
|
+
raise AssertionError.new(
|
92
|
+
"Selector not supported for ZipStructType: #{selector}")
|
93
|
+
end
|
94
|
+
|
95
|
+
if arg_nodes.size != 1
|
96
|
+
raise AssertionError.new("Expected exactly one argument")
|
97
|
+
end
|
98
|
+
|
99
|
+
if arg_nodes.first.class == AST::IntLiteralNode
|
100
|
+
if arg_nodes.first.value >= @fields.size
|
101
|
+
raise AssertionError.new(
|
102
|
+
"ZipStruct index out of bounds: #{arg_nodes.first.value}")
|
103
|
+
end
|
104
|
+
|
105
|
+
return self[arg_nodes.first.value]
|
106
|
+
else
|
107
|
+
return get_return_type_non_constant(selector)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Performs type inference for the result of accessing this Zip "Array" by index.
|
112
|
+
def get_return_type_non_constant(selector)
|
113
|
+
# TODO: Can only handle single cases at the moment. This should eventually forward
|
114
|
+
# to Array integration code.
|
115
|
+
|
116
|
+
# TODO: This code assumes that the all struct elements have the same type
|
117
|
+
return self[0]
|
118
|
+
end
|
119
|
+
|
120
|
+
# Returns the type of the element at [index].
|
121
|
+
def [](index)
|
122
|
+
return @fields[:"field_#{index}"]
|
123
|
+
end
|
124
|
+
|
125
|
+
# A module that provides array-like functionality for FFI Struct types.
|
126
|
+
module ZipStruct
|
127
|
+
def self.included(base)
|
128
|
+
base.include(Enumerable)
|
129
|
+
end
|
130
|
+
|
131
|
+
def [](index)
|
132
|
+
# Out of bounds: returns nil
|
133
|
+
return super(:"field_#{index}")
|
134
|
+
end
|
135
|
+
|
136
|
+
def []=(index, value)
|
137
|
+
# TODO: What should we do if the type of a field changes?
|
138
|
+
|
139
|
+
# Fill up missing slots with `nil`
|
140
|
+
for id in (@fields.size)..index
|
141
|
+
super(:"field_{id}", nil)
|
142
|
+
end
|
143
|
+
|
144
|
+
super(:"field_{index}", value)
|
145
|
+
return value
|
146
|
+
end
|
147
|
+
|
148
|
+
def each(&block)
|
149
|
+
for index in 0...(@fields.size)
|
150
|
+
yield(self[index])
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def to_ruby_type
|
156
|
+
# Cache struct types
|
157
|
+
if @struct_type == nil
|
158
|
+
# Create class
|
159
|
+
@struct_type = Class.new(FFI::Struct)
|
160
|
+
@struct_type.include(ZipStruct)
|
161
|
+
|
162
|
+
# Define layout of struct
|
163
|
+
var_names = Array.new(@fields.size) do |index|
|
164
|
+
:"field_#{index}"
|
165
|
+
end
|
166
|
+
|
167
|
+
var_types = var_names.map do |name|
|
168
|
+
@fields[name].to_ffi_type
|
169
|
+
end
|
170
|
+
|
171
|
+
layout = var_names.zip(var_types).flatten
|
172
|
+
@struct_type.layout(*layout)
|
173
|
+
end
|
174
|
+
|
175
|
+
return @struct_type
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|