ikra 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|