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.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/lib/ast/builder.rb +225 -77
  3. data/lib/ast/host_section_builder.rb +38 -0
  4. data/lib/ast/interpreter.rb +67 -0
  5. data/lib/ast/lexical_variables_enumerator.rb +3 -2
  6. data/lib/ast/nodes.rb +521 -31
  7. data/lib/ast/printer.rb +116 -18
  8. data/lib/ast/ssa_generator.rb +192 -0
  9. data/lib/ast/visitor.rb +235 -21
  10. data/lib/config/configuration.rb +28 -3
  11. data/lib/config/os_configuration.rb +62 -9
  12. data/lib/cpu/cpu_implementation.rb +39 -0
  13. data/lib/ikra.rb +13 -3
  14. data/lib/resources/cuda/allocate_device_memory.cpp +5 -0
  15. data/lib/resources/cuda/allocate_host_memory.cpp +1 -0
  16. data/lib/resources/cuda/allocate_memcpy_environment_to_device.cpp +11 -0
  17. data/lib/resources/cuda/ast/assignment.cpp +1 -0
  18. data/lib/resources/cuda/block_function_head.cpp +7 -1
  19. data/lib/resources/cuda/entry_point.cpp +47 -0
  20. data/lib/resources/cuda/env_builder_copy_array.cpp +8 -2
  21. data/lib/resources/cuda/free_device_memory.cpp +3 -0
  22. data/lib/resources/cuda/free_memory_for_command.cpp +24 -0
  23. data/lib/resources/cuda/header.cpp +23 -9
  24. data/lib/resources/cuda/header_structs.cpp +92 -0
  25. data/lib/resources/cuda/host_section_block_function_head.cpp +12 -0
  26. data/lib/resources/cuda/host_section_entry_point.cpp +55 -0
  27. data/lib/resources/cuda/host_section_free_device_memory.cpp +18 -0
  28. data/lib/resources/cuda/host_section_launch_parallel_section.cpp +14 -0
  29. data/lib/resources/cuda/host_section_malloc_memcpy_device_to_host.cpp +10 -0
  30. data/lib/resources/cuda/kernel.cpp +9 -2
  31. data/lib/resources/cuda/launch_kernel.cpp +5 -0
  32. data/lib/resources/cuda/memcpy_device_to_host.cpp +3 -0
  33. data/lib/resources/cuda/memcpy_device_to_host_expr.cpp +10 -0
  34. data/lib/resources/cuda/reduce_body.cpp +88 -0
  35. data/lib/resources/cuda/stencil_array_reconstruction.cpp +2 -0
  36. data/lib/resources/cuda/stencil_body.cpp +16 -0
  37. data/lib/resources/cuda/struct_definition.cpp +4 -0
  38. data/lib/ruby_core/array.rb +34 -0
  39. data/lib/ruby_core/array_command.rb +313 -0
  40. data/lib/ruby_core/core.rb +103 -0
  41. data/lib/ruby_core/interpreter.rb +16 -0
  42. data/lib/ruby_core/math.rb +32 -0
  43. data/lib/ruby_core/ruby_integration.rb +256 -0
  44. data/lib/symbolic/host_section.rb +115 -0
  45. data/lib/symbolic/input.rb +87 -0
  46. data/lib/symbolic/input_visitor.rb +68 -0
  47. data/lib/symbolic/symbolic.rb +793 -117
  48. data/lib/symbolic/visitor.rb +70 -8
  49. data/lib/translator/array_command_struct_builder.rb +163 -0
  50. data/lib/translator/ast_translator.rb +572 -0
  51. data/lib/translator/block_translator.rb +104 -48
  52. data/lib/translator/commands/array_combine_command.rb +41 -0
  53. data/lib/translator/commands/array_identity_command.rb +28 -0
  54. data/lib/translator/commands/array_index_command.rb +52 -0
  55. data/lib/translator/commands/array_reduce_command.rb +135 -0
  56. data/lib/translator/commands/array_stencil_command.rb +129 -0
  57. data/lib/translator/commands/array_zip_command.rb +30 -0
  58. data/lib/translator/commands/command_translator.rb +264 -0
  59. data/lib/translator/cuda_errors.rb +32 -0
  60. data/lib/translator/environment_builder.rb +263 -0
  61. data/lib/translator/host_section/array_host_section_command.rb +150 -0
  62. data/lib/translator/host_section/array_in_host_section_command.rb +41 -0
  63. data/lib/translator/host_section/ast_translator.rb +14 -0
  64. data/lib/translator/host_section/parallel_section_invocation_visitor.rb +20 -0
  65. data/lib/translator/host_section/program_builder.rb +89 -0
  66. data/lib/translator/input_translator.rb +226 -0
  67. data/lib/translator/kernel_builder.rb +137 -0
  68. data/lib/translator/kernel_launcher/for_loop_kernel_launcher.rb +40 -0
  69. data/lib/translator/kernel_launcher/kernel_launcher.rb +259 -0
  70. data/lib/translator/kernel_launcher/while_loop_kernel_launcher.rb +38 -0
  71. data/lib/translator/last_returns_visitor.rb +19 -10
  72. data/lib/translator/program_builder.rb +197 -0
  73. data/lib/translator/program_launcher.rb +273 -0
  74. data/lib/translator/struct_type.rb +55 -0
  75. data/lib/translator/translator.rb +34 -11
  76. data/lib/translator/variable_classifier_visitor.rb +56 -0
  77. data/lib/types/inference/ast_inference.rb +586 -0
  78. data/lib/types/inference/clear_types_visitor.rb +11 -0
  79. data/lib/types/inference/command_inference.rb +101 -0
  80. data/lib/types/inference/input_inference.rb +62 -0
  81. data/lib/types/{object_tracer.rb → inference/object_tracer.rb} +5 -6
  82. data/lib/types/inference/ruby_extension.rb +35 -0
  83. data/lib/types/inference/symbol_table.rb +131 -0
  84. data/lib/types/types.rb +14 -0
  85. data/lib/types/types/array_command_type.rb +123 -0
  86. data/lib/types/types/array_type.rb +137 -0
  87. data/lib/types/{class_type.rb → types/class_type.rb} +42 -18
  88. data/lib/types/{primitive_type.rb → types/primitive_type.rb} +20 -7
  89. data/lib/types/types/ruby_type.rb +88 -0
  90. data/lib/types/types/struct_type.rb +179 -0
  91. data/lib/types/types/union_type.rb +239 -0
  92. metadata +160 -18
  93. data/lib/ast/method_definition.rb +0 -37
  94. data/lib/ast/translator.rb +0 -264
  95. data/lib/resources/cuda/kernel_launcher.cpp +0 -28
  96. data/lib/scope.rb +0 -166
  97. data/lib/translator/command_translator.rb +0 -421
  98. data/lib/translator/local_variables_enumerator.rb +0 -35
  99. data/lib/translator/method_translator.rb +0 -24
  100. data/lib/types/array_type.rb +0 -51
  101. data/lib/types/ruby_extension.rb +0 -67
  102. data/lib/types/ruby_type.rb +0 -45
  103. data/lib/types/type_inference.rb +0 -382
  104. data/lib/types/union_type.rb +0 -155
@@ -0,0 +1,87 @@
1
+ module Ikra
2
+ module Symbolic
3
+ # Specifies an input parameter for a parallel section. A parameter might be expanded to
4
+ # multiple parameters in the generated CUDA code to avoid passing arrays etc.
5
+ class Input
6
+ # Returns the access pattern of this input, e.g., `:tid` (single element, identified
7
+ # by thread ID) or `:entire` (access to entire array is necessary).
8
+ attr_reader :pattern
9
+
10
+ attr_reader :command
11
+
12
+ def initialize(pattern:)
13
+ # Currently supported: :tid, :entire
14
+ @pattern = pattern
15
+ end
16
+
17
+ def ==(other)
18
+ return self.class == other.class &&
19
+ self.pattern == other.pattern &&
20
+ self.command == other.command
21
+ end
22
+
23
+ def hash
24
+ return (pattern.hash + command.hash) % 7656781
25
+ end
26
+
27
+ def eql?(other)
28
+ return self == other
29
+ end
30
+ end
31
+
32
+ # A single input value produced by one command.
33
+ class SingleInput < Input
34
+ def initialize(command:, pattern:)
35
+ super(pattern: pattern)
36
+
37
+ @command = command
38
+ end
39
+ end
40
+
41
+ # An array containing values produced by one previous command.
42
+ class StencilArrayInput < Input
43
+ attr_reader :offsets
44
+ attr_reader :out_of_bounds_value
45
+
46
+ def initialize(command:, pattern:, offsets:, out_of_bounds_value:)
47
+ super(pattern: pattern)
48
+
49
+ @command = command
50
+ @offsets = offsets
51
+ @out_of_bounds_value = out_of_bounds_value
52
+ end
53
+
54
+ def ==(other)
55
+ return super(other) &&
56
+ offsets == other.offsets &&
57
+ out_of_bounds_value == other.out_of_bounds_value
58
+ end
59
+ end
60
+
61
+ class StencilSingleInput < Input
62
+ attr_reader :offsets
63
+ attr_reader :out_of_bounds_value
64
+
65
+ def initialize(command:, pattern:, offsets:, out_of_bounds_value:)
66
+ super(pattern: pattern)
67
+
68
+ @command = command
69
+ @offsets = offsets
70
+ @out_of_bounds_value = out_of_bounds_value
71
+ end
72
+
73
+ def ==(other)
74
+ return super(other) &&
75
+ offsets == other.offsets &&
76
+ out_of_bounds_value == other.out_of_bounds_value
77
+ end
78
+ end
79
+
80
+ # Similar to [SingleInput], but two values are passed to the block.
81
+ class ReduceInput < SingleInput
82
+
83
+ end
84
+ end
85
+ end
86
+
87
+ require_relative "input_visitor"
@@ -0,0 +1,68 @@
1
+ require_relative "input"
2
+ require_relative "visitor"
3
+
4
+ module Ikra
5
+ module Symbolic
6
+ class Input
7
+ def accept(visitor)
8
+ visitor.visit_input(self, pattern: pattern)
9
+ end
10
+ end
11
+
12
+ class SingleInput
13
+ def accept(visitor)
14
+ visitor.visit_single_input(self, pattern: pattern)
15
+ end
16
+ end
17
+
18
+ class StencilArrayInput
19
+ def accept(visitor)
20
+ visitor.visit_stecil_array_input(self, pattern: pattern)
21
+ end
22
+ end
23
+
24
+ class StencilSingleInput
25
+ def accept(visitor)
26
+ visitor.visit_stencil_single_input(self, pattern: pattern)
27
+ end
28
+ end
29
+
30
+ class ReduceInput
31
+ def accept(visitor)
32
+ visitor.visit_reduce_input(self, pattern: pattern)
33
+ end
34
+ end
35
+
36
+ class InputVisitor < Visitor
37
+ def visit_input(input, pattern:)
38
+
39
+ end
40
+
41
+ def visit_single_input(input, pattern:)
42
+ visit_input(input)
43
+ input.command.accept(self)
44
+ end
45
+
46
+ def visit_stencil_array_input(input, pattern:)
47
+ visit_input(input)
48
+ input.command.accept(self)
49
+ end
50
+
51
+ def visit_stencil_single_input(input, pattern:)
52
+ visit_input(input)
53
+ input.command.accept(self)
54
+ end
55
+
56
+ def visit_reduce_input(input, pattern:)
57
+ visit_input(input)
58
+ input.command.accept(self)
59
+ end
60
+
61
+ def visit_array_command(command)
62
+ for input in command.input
63
+ input.accept(self)
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -1,176 +1,793 @@
1
1
  require "set"
2
- require_relative "../translator/command_translator"
3
- require_relative "../types/primitive_type"
4
- require_relative "../types/class_type"
5
- require_relative "../types/union_type"
6
- require_relative "../types/array_type"
2
+ require_relative "input"
3
+ require_relative "../translator/translator"
4
+ require_relative "../types/types"
7
5
  require_relative "../type_aware_array"
8
6
  require_relative "../parsing"
9
7
  require_relative "../ast/nodes"
10
8
  require_relative "../ast/lexical_variables_enumerator"
11
9
  require_relative "../config/os_configuration"
12
10
 
13
- Ikra::Configuration.check_software_configuration
14
-
15
11
  module Ikra
16
12
  module Symbolic
17
- class BlockParameter
18
- Normal = 0
19
- Index = 1
20
- PreviousFusion = 2
21
-
22
- attr_accessor :name
23
- attr_accessor :type
24
- attr_accessor :source_type
13
+ DEFAULT_BLOCK_SIZE = 256
14
+
15
+ def self.stencil(directions:, distance:)
16
+ return ["G", directions, distance]
17
+ end
18
+
19
+ module ParallelOperations
20
+ def preduce(symbol = nil, **options, &block)
21
+ if symbol == nil && (block != nil || options[:ast] != nil)
22
+ return ArrayReduceCommand.new(
23
+ to_command,
24
+ block,
25
+ **options)
26
+ elsif symbol != nil && block == nil
27
+ ast = AST::BlockDefNode.new(
28
+ ruby_block: nil,
29
+ parameters: [:a, :b],
30
+ body: AST::RootNode.new(single_child:
31
+ AST::SendNode.new(
32
+ receiver: AST::LVarReadNode.new(identifier: :a),
33
+ selector: symbol,
34
+ arguments: [AST::LVarReadNode.new(identifier: :b)])))
35
+
36
+ return ArrayReduceCommand.new(
37
+ to_command,
38
+ nil,
39
+ ast: ast,
40
+ **options)
41
+ else
42
+ raise ArgumentError.new("Either block or symbol expected")
43
+ end
44
+ end
45
+
46
+ def pstencil(offsets, out_of_range_value, **options, &block)
47
+ return ArrayStencilCommand.new(
48
+ to_command,
49
+ offsets,
50
+ out_of_range_value,
51
+ block,
52
+ **options)
53
+ end
54
+
55
+ def pmap(**options, &block)
56
+ return pcombine(
57
+ **options,
58
+ &block)
59
+ end
60
+
61
+ def pcombine(*others, **options, &block)
62
+ return ArrayCombineCommand.new(
63
+ to_command,
64
+ wrap_in_command(*others),
65
+ block,
66
+ **options)
67
+ end
68
+
69
+ def pzip(*others, **options)
70
+ return ArrayZipCommand.new(
71
+ to_command,
72
+ wrap_in_command(*others),
73
+ **options)
74
+ end
75
+
76
+ def +(other)
77
+ return pcombine(other) do |a, b|
78
+ a + b
79
+ end
80
+ end
81
+
82
+ def -(other)
83
+ return pcombine(other) do |a, b|
84
+ a - b
85
+ end
86
+ end
87
+
88
+ def *(other)
89
+ return pcombine(other) do |a, b|
90
+ a * b
91
+ end
92
+ end
93
+
94
+ def /(other)
95
+ return pcombine(other) do |a, b|
96
+ a / b
97
+ end
98
+ end
99
+
100
+ def |(other)
101
+ return pcombine(other) do |a, b|
102
+ a | b
103
+ end
104
+ end
105
+
106
+ def &(other)
107
+ return pcombine(other) do |a, b|
108
+ a & b
109
+ end
110
+ end
111
+
112
+ def ^(other)
113
+ return pcombine(other) do |a, b|
114
+ a ^ b
115
+ end
116
+ end
117
+
118
+ def <(other)
119
+ return pcombine(other) do |a, b|
120
+ a < b
121
+ end
122
+ end
123
+
124
+ def <=(other)
125
+ return pcombine(other) do |a, b|
126
+ a <= b
127
+ end
128
+ end
129
+
130
+ def >(other)
131
+ return pcombine(other) do |a, b|
132
+ a > b
133
+ end
134
+ end
135
+
136
+ def >=(other)
137
+ return pcombine(other) do |a, b|
138
+ a >= b
139
+ end
140
+ end
25
141
 
26
- def initialize(name:, type:, source_type: Normal)
27
- @name = name
28
- @type = type
29
- @source_type = source_type
142
+ # TODO(springerm): Should implement #== but this could cause trouble when using with
143
+ # hash maps etc.
144
+
145
+ private
146
+
147
+ def wrap_in_command(*others)
148
+ return others.map do |other|
149
+ other.to_command
150
+ end
30
151
  end
31
152
  end
32
153
 
33
154
  module ArrayCommand
155
+ include Enumerable
156
+ include ParallelOperations
157
+
158
+ attr_reader :block_size
159
+
160
+ # [Fixnum] Returns a unique ID for this command. It is used during name mangling in
161
+ # the code generator to determine the name of array identifiers (and do other stuff?).
162
+ attr_reader :unique_id
163
+
164
+ # An array of commands that serve as input to this command. The number of input
165
+ # commands depends on the type of the command.
166
+ attr_reader :input
167
+
168
+ # Indicates if result should be kept on the GPU for further processing.
169
+ attr_reader :keep
170
+
171
+ # This field can only be used if keep is true
172
+ attr_accessor :gpu_result_pointer
34
173
 
35
- attr_reader :unique_id # [Fixnum] Returns a unique ID for this command. It is used during name mangling in the code generator to determine the name of array identifiers (and do other stuff?).
174
+ # Returns the block of the parallel section or [nil] if none.
175
+ attr_reader :block
176
+
177
+ # A reference to the AST send node that generated this [ArrayCommand] (if inside a
178
+ # host section).
179
+ attr_reader :generator_node
36
180
 
37
181
  @@unique_id = 1
38
182
 
39
- def initialize
40
- super
183
+ def self.reset_unique_id
184
+ @@unique_id = 1
185
+ end
41
186
 
42
- # Generate unique ID
43
- @unique_id = @@unique_id
44
- @@unique_id += 1
187
+ def to_s
188
+ return "[#{self.class.to_s}, size = #{size.to_s}]"
45
189
  end
46
190
 
47
- def [](index)
48
- if @result == nil
49
- execute
191
+ def initialize(
192
+ block: nil,
193
+ block_ast: nil,
194
+ block_size: nil,
195
+ keep: nil,
196
+ generator_node: nil,
197
+ command_binding: nil)
198
+
199
+ super()
200
+
201
+ set_unique_id
202
+
203
+ # Set instance variables
204
+ @block_size = block_size
205
+ @keep = keep
206
+ @generator_node = generator_node
207
+ @command_binding = command_binding
208
+
209
+ if block != nil and block_ast == nil
210
+ @block = block
211
+ elsif block == nil and block_ast != nil
212
+ @ast = block_ast
213
+ elsif block != nil and block_ast != nil
214
+ raise ArgumentError.new("`block` and `block_ast` given. Expected at most one.")
50
215
  end
51
-
52
- @result[index]
53
216
  end
217
+
54
218
 
219
+ # ----- EQUALITY -----
220
+
221
+ # Methods for equality and hash. These methods are required for comparing array
222
+ # commands for equality. This is necessary because every array command can also
223
+ # act as a type. Types must be comparable for equality.
224
+
225
+ def ==(other)
226
+ # Important: ArrayCommands may be created over and over during type inference.
227
+ # It is important that we compare values and not identities!
228
+
229
+ return self.class == other.class &&
230
+ block_size == other.block_size &&
231
+ input == other.input &&
232
+ keep == other.keep &&
233
+ block_def_node == other.block_def_node
234
+ end
235
+
236
+ def hash
237
+ return (block_size.hash + input.hash + keep.hash + block_def_node.hash) % 7546777
238
+ end
239
+
240
+ def eql?(other)
241
+ return self == other
242
+ end
243
+
244
+ # ----- EQUALITY END -----
245
+
246
+
247
+ # ----- ARRAY METHODS -----
248
+
249
+ def [](index)
250
+ execute
251
+ return @result[index]
252
+ end
253
+
254
+ def each(&block)
255
+ next_index = 0
256
+
257
+ while next_index < size
258
+ yield(self[next_index])
259
+ next_index += 1
260
+ end
261
+ end
262
+
263
+ def pack(fmt)
264
+ execute
265
+ return @result.pack(fmt)
266
+ end
267
+
268
+ # ----- ARRAY END -----
269
+
270
+
271
+ # The class or subclass of [CommandTranslator] that should be used for translating
272
+ # this command. May be overridden.
273
+ def command_translator_class
274
+ return Translator::CommandTranslator
275
+ end
276
+
55
277
  def execute
56
- @result = Translator.translate_command(self).execute
278
+ if @result == nil
279
+ @result = command_translator_class.translate_command(self).execute
280
+ end
57
281
  end
58
282
 
59
283
  def to_command
60
- self
284
+ return self
61
285
  end
62
-
63
- def pmap(&block)
64
- ArrayMapCommand.new(self, block)
286
+
287
+ # This method is executed after execution of the parallel section has finish. The
288
+ # boolean return value indicates if a change has been registered or not.
289
+ def post_execute(environment)
290
+ if keep
291
+ # The (temporary) result of this command should be kept on the GPU. Store a
292
+ # pointer to the result in global memory in an instance variable.
293
+
294
+ begin
295
+ @gpu_result_pointer = environment[("prev_" + unique_id.to_s).to_sym].to_i
296
+ Log.info("Kept pointer for result of command #{unique_id.to_s}: #{@gpu_result_pointer}")
297
+ return true
298
+ rescue ArgumentError
299
+ # No pointer saved for this command. This can happen if the result of this
300
+ # command was already cached earlier and the cached result of a
301
+ # computation based on this command was used now.
302
+ Log.info("No pointer kept for result of command #{unique_id.to_s}.")
303
+ return false
304
+ end
305
+ end
306
+
307
+ return false
308
+ end
309
+
310
+ def has_previous_result?
311
+ return !gpu_result_pointer.nil?
65
312
  end
66
313
 
67
314
  # Returns a collection of the names of all block parameters.
68
315
  # @return [Array(Symbol)] list of block parameters
69
316
  def block_parameter_names
70
- block.parameters.map do |param|
71
- param[1]
72
- end
317
+ return block_def_node.parameters
73
318
  end
74
319
 
75
- # Returns the size (number of elements) of the result, after executing the parallel section.
320
+ # Returns the size (number of elements) of the result, after executing the parallel
321
+ # section.
76
322
  # @return [Fixnum] size
77
323
  def size
78
- raise NotImplementedError
324
+ raise NotImplementedError.new
79
325
  end
80
326
 
81
- def target
82
- raise NotImplementedError
327
+ def dimensions
328
+ # Dimensions are defined in a root command. First input currently determines the
329
+ # dimensions (even if there are multiple root commands).
330
+ return input.first.command.dimensions
83
331
  end
84
332
 
85
333
  # Returns the abstract syntax tree for a parallel section.
86
- def ast
87
- # TODO: add caching for AST here
88
- # TODO: maybe set dummy class_owner here?
89
- parser_local_vars = block.binding.local_variables + block_parameter_names
90
- source = Parsing.parse_block(block, parser_local_vars)
91
- AST::Builder.from_parser_ast(source)
92
- end
334
+ def block_def_node
335
+ if @ast == nil
336
+ if block == nil
337
+ return nil
338
+ end
339
+
340
+ # Get array of block parameter names
341
+ block_params = block.parameters.map do |param|
342
+ param[1]
343
+ end
344
+
345
+ parser_local_vars = command_binding.local_variables + block_params
346
+ source = Parsing.parse_block(block, parser_local_vars)
347
+ @ast = AST::BlockDefNode.new(
348
+ parameters: block_params,
349
+ ruby_block: block, # necessary to get binding
350
+ body: AST::Builder.from_parser_ast(source))
351
+ end
93
352
 
94
- # Returns a collection of lexical variables that are accessed within a parallel section.
95
- # @return [Hash{Symbol => Object}]
96
- def lexical_externals
97
- all_lexical_vars = block.binding.local_variables
98
- lexical_vars_enumerator = AST::LexicalVariablesEnumerator.new(all_lexical_vars)
99
- ast.accept(lexical_vars_enumerator)
100
- accessed_variables = lexical_vars_enumerator.lexical_variables
353
+ # Ensure `return` is there
354
+ @ast.accept(Translator::LastStatementReturnsVisitor.new)
355
+
356
+ return @ast
357
+ end
101
358
 
102
- result = Hash.new
103
- for var_name in accessed_variables
104
- result[var_name] = block.binding.local_variable_get(var_name)
359
+ # Returns the binding of this command. It is used to retrieve lexical variables that
360
+ # are used inside this parallel section.
361
+ def command_binding
362
+ if @command_binding != nil
363
+ return @command_binding
364
+ elsif block != nil
365
+ return block.binding
366
+ else
367
+ return nil
105
368
  end
369
+ end
106
370
 
107
- result
371
+ # Returns a collection of lexical variables that are accessed within a parallel
372
+ # section.
373
+ # @return [Hash{Symbol => Object}]
374
+ def lexical_externals
375
+ if block_def_node != nil && command_binding != nil
376
+ all_lexical_vars = command_binding.local_variables
377
+ lexical_vars_enumerator = AST::LexicalVariablesEnumerator.new(all_lexical_vars)
378
+ block_def_node.accept(lexical_vars_enumerator)
379
+ accessed_variables = lexical_vars_enumerator.lexical_variables
380
+
381
+ result = Hash.new
382
+ for var_name in accessed_variables
383
+ result[var_name] = command_binding.local_variable_get(var_name)
384
+ end
385
+
386
+ return result
387
+ else
388
+ return {}
389
+ end
108
390
  end
109
391
 
110
392
  # Returns a collection of external objects that are accessed within a parallel section.
111
393
  def externals
112
- lexical_externals.keys
394
+ return lexical_externals.keys
113
395
  end
114
396
 
115
- protected
397
+ def set_unique_id
398
+ # Generate unique ID
399
+ @unique_id = @@unique_id
400
+ @@unique_id += 1
401
+ end
116
402
 
117
- # Returns the block of the parallel section.
118
- # @return [Proc] block
119
- def block
120
- raise NotImplementedError
403
+ def with_index(&block)
404
+ self.block = block
405
+ @input.push(SingleInput.new(
406
+ command: ArrayIndexCommand.new(dimensions: dimensions),
407
+ pattern: :tid))
408
+ return self
121
409
  end
410
+
411
+ protected
412
+
413
+ attr_writer :block
122
414
  end
123
415
 
124
- class ArrayNewCommand
416
+ class ArrayIndexCommand
125
417
  include ArrayCommand
126
418
 
127
- def initialize(size, block)
128
- super()
419
+ attr_reader :dimensions
420
+ attr_reader :size
129
421
 
130
- @size = size
131
- @block = block
422
+ def initialize(block_size: DEFAULT_BLOCK_SIZE, keep: false, dimensions: nil)
423
+ super(block_size: block_size, keep: keep)
424
+
425
+ @dimensions = dimensions
426
+ @size = dimensions.reduce(:*)
427
+
428
+ # No input
429
+ @input = []
430
+ end
431
+
432
+ def ==(other)
433
+ return super(other) && dimensions == other.dimensions && size == other.size
434
+ end
435
+ end
436
+
437
+ class ArrayCombineCommand
438
+ include ArrayCommand
439
+
440
+ def initialize(
441
+ target,
442
+ others,
443
+ block,
444
+ ast: nil,
445
+ block_size: DEFAULT_BLOCK_SIZE,
446
+ keep: false,
447
+ generator_node: nil,
448
+ with_index: false,
449
+ command_binding: nil)
450
+
451
+ super(block: block, block_ast: ast, block_size: block_size, keep: keep, generator_node: generator_node, command_binding: command_binding)
452
+
453
+ # Read array at position `tid`
454
+ @input = [SingleInput.new(command: target.to_command, pattern: :tid)] + others.map do |other|
455
+ SingleInput.new(command: other.to_command, pattern: :tid)
456
+ end
457
+
458
+ if with_index
459
+ @input.push(SingleInput.new(
460
+ command: ArrayIndexCommand.new(dimensions: dimensions),
461
+ pattern: :tid))
462
+ end
132
463
  end
133
464
 
134
465
  def size
135
- @size
466
+ return input.first.command.size
136
467
  end
137
468
 
138
- protected
469
+ def ==(other)
470
+ return super(other) && size == other.size
471
+ end
472
+ end
139
473
 
140
- attr_reader :block
474
+ class ArrayZipCommand
475
+ include ArrayCommand
476
+
477
+ def initialize(target, others, **options)
478
+ super(**options)
479
+
480
+ @input = [SingleInput.new(command: target.to_command, pattern: :tid)] + others.map do |other|
481
+ SingleInput.new(command: other.to_command, pattern: :tid)
482
+ end
483
+ end
484
+
485
+ def size
486
+ return input.first.command.size
487
+ end
488
+
489
+ def block_parameter_names
490
+ # Have to set block parameter names but names are never used
491
+ return [:irrelevant] * @input.size
492
+ end
493
+
494
+ def ==(other)
495
+ return super(other) && size == other.size
496
+ end
141
497
  end
142
498
 
143
- class ArrayMapCommand
499
+ class ArrayReduceCommand
144
500
  include ArrayCommand
145
-
146
- attr_reader :target
147
501
 
148
- def initialize(target, block)
149
- super()
502
+ def initialize(
503
+ target,
504
+ block,
505
+ block_size: DEFAULT_BLOCK_SIZE,
506
+ ast: nil,
507
+ generator_node: nil,
508
+ command_binding: nil)
150
509
 
151
- @target = target
152
- @block = block
510
+ super(block: block, block_ast: ast, block_size: block_size, keep: keep, generator_node: generator_node, command_binding: command_binding)
511
+
512
+ @input = [ReduceInput.new(command: target.to_command, pattern: :entire)]
513
+ end
514
+
515
+ def execute
516
+ if input.first.command.size == 0
517
+ @result = [nil]
518
+ elsif @input.first.command.size == 1
519
+ @result = [input.first.command[0]]
520
+ else
521
+ @result = super
522
+ end
153
523
  end
154
524
 
155
525
  def size
156
- @target.size
526
+ return 1
157
527
  end
158
-
528
+
529
+ # Returns the number of elements in the input
530
+ def input_size
531
+ return input.first.command.size
532
+ end
533
+
534
+ def ==(other)
535
+ return super(other) && size == other.size
536
+ end
537
+ end
538
+
539
+ class ArrayStencilCommand
540
+
541
+ # This visitor executes the check_parents_index function on every local variable
542
+ # If the local variable is the "stencil array" then its indices will get modified/corrected for the 1D array access
543
+ class FlattenIndexNodeVisitor < AST::Visitor
544
+
545
+ attr_reader :offsets
546
+ attr_reader :name
547
+ attr_reader :command
548
+
549
+ def initialize(name, offsets, command)
550
+ @name = name
551
+ @offsets = offsets
552
+ @command = command
553
+ end
554
+
555
+ def visit_lvar_read_node(node)
556
+ super(node)
557
+ check_index(name, offsets, node)
558
+ end
559
+
560
+ def visit_lvar_write_node(node)
561
+ super(node)
562
+ check_index(name, offsets, node)
563
+ end
564
+
565
+
566
+ def check_index(name, offsets, node)
567
+ if node.identifier == name
568
+ # This is the array-variable used in the stencil
569
+
570
+ send_node = node
571
+ index_combination = []
572
+ is_literal = true
573
+
574
+ # Build the index off this access in index_combination
575
+ for i in 0..command.dimensions.size-1
576
+ send_node = send_node.parent
577
+ if not (send_node.is_a?(AST::SendNode) && send_node.selector == :[])
578
+ raise AssertionError.new(
579
+ "This has to be a SendNode and Array-selector")
580
+ end
581
+ index_combination[i] = send_node.arguments.first
582
+ if not index_combination[i].is_a?(AST::IntLiteralNode)
583
+ is_literal = false
584
+ end
585
+ end
586
+
587
+ if is_literal
588
+ # The index consists of only literals so we can translate it easily by mapping the index onto the offsets
589
+
590
+ index_combination = index_combination.map do |x|
591
+ x.value
592
+ end
593
+
594
+ replacement = AST::IntLiteralNode.new(
595
+ value: offsets[index_combination])
596
+ else
597
+ # This handles the case where non-literals have to be translated with the Ternary Node
598
+
599
+ offset_arr = offsets.to_a
600
+ replacement = AST::IntLiteralNode.new(value: offset_arr[0][1])
601
+ for i in 1..offset_arr.size-1
602
+ # Build combination of ternary nodes
603
+
604
+ ternary_build = AST::SendNode.new(receiver: AST::IntLiteralNode.new(value: offset_arr[i][0][0]), selector: :==, arguments: [index_combination[0]])
605
+ for j in 1..index_combination.size-1
606
+
607
+ next_eq = AST::SendNode.new(receiver: AST::IntLiteralNode.new(value: offset_arr[i][0][j]), selector: :==, arguments: [index_combination[j]])
608
+ ternary_build = AST::SendNode.new(receiver: next_eq, selector: :"&&", arguments: [ternary_build])
609
+ end
610
+ replacement = AST::TernaryNode.new(condition: ternary_build, true_val: AST::IntLiteralNode.new(value: offset_arr[i][1]), false_val: replacement)
611
+ end
612
+ end
613
+
614
+ #Replace outer array access with new 1D array access
615
+
616
+ send_node.replace(AST::SendNode.new(receiver: node, selector: :[], arguments: [replacement]))
617
+ end
618
+ end
619
+ end
620
+
621
+ include ArrayCommand
622
+
623
+ attr_reader :offsets
624
+ attr_reader :out_of_range_value
625
+ attr_reader :use_parameter_array
626
+
627
+ def initialize(
628
+ target,
629
+ offsets,
630
+ out_of_range_value,
631
+ block,
632
+ ast: nil,
633
+ block_size: DEFAULT_BLOCK_SIZE,
634
+ keep: false,
635
+ use_parameter_array: true,
636
+ generator_node: nil,
637
+ with_index: false,
638
+ command_binding: nil)
639
+
640
+ super(block: block, block_ast: ast, block_size: block_size, keep: keep, generator_node: generator_node, command_binding: command_binding)
641
+
642
+ if offsets.first == "G"
643
+ # A stencil will be generated
644
+ dims = target.to_command.dimensions.size
645
+
646
+ directions = offsets[1]
647
+ # Directions says how many of the dimensions can be used in the stencil. E.g.: in 2D directions = 1 would relate in a stencil that only has up, down, left, right offsets, but no diagonals
648
+ distance = offsets[2]
649
+ # Distance says how many steps can be made into the directions distance = 2 in the example before would mean 1 or 2 up/down/left/right
650
+
651
+ if directions > dims
652
+ raise ArgumentError.new(
653
+ "Directions should not be higher than the number of dimensions")
654
+ end
655
+
656
+ singles = [0]
657
+ # Building the numbers that can be part of an offset
658
+ for i in 1..distance
659
+ singles = singles + [i] + [-i]
660
+ end
661
+
662
+ offsets = []
663
+
664
+ # Iterate all possibilities
665
+ for i in 0..singles.size**dims-1
666
+ # Transform current permutation to according string / base representation
667
+ base = i.to_s(singles.size)
668
+
669
+ # Fill up zeroes
670
+ sizedif = (singles.size**dims-1).to_s(singles.size).size - base.size
671
+ base = "0" * sizedif + base
672
+
673
+ # Check whether offset is allowed (concerning directions)
674
+ if base.gsub(/[^0]/, "").size >= dims - directions
675
+ new_offset = []
676
+ for j in 0..dims-1
677
+ new_offset.push(singles[base[j].to_i])
678
+ end
679
+ offsets.push(new_offset)
680
+ end
681
+ end
682
+ end
683
+
684
+ if not offsets.first.is_a?(Array)
685
+ offsets = offsets.map do |offset|
686
+ [offset]
687
+ end
688
+ end
689
+
690
+ # Read more than just one element, fall back to `:entire` for now
691
+
692
+ @out_of_range_value = out_of_range_value
693
+ @use_parameter_array = use_parameter_array
694
+
695
+ if use_parameter_array
696
+ @input = [StencilArrayInput.new(
697
+ command: target.to_command,
698
+ pattern: :entire,
699
+ offsets: offsets,
700
+ out_of_bounds_value: out_of_range_value)]
701
+ else
702
+ @input = [StencilSingleInput.new(
703
+ command: target.to_command,
704
+ pattern: :entire,
705
+ offsets: offsets,
706
+ out_of_bounds_value: out_of_range_value)]
707
+ end
708
+
709
+ if with_index
710
+ @input.push(SingleInput.new(
711
+ command: ArrayIndexCommand.new(dimensions: dimensions),
712
+ pattern: :tid))
713
+ end
714
+
715
+ # Offsets should be arrays
716
+ for offset in offsets
717
+ if !offset.is_a?(Array)
718
+ raise ArgumentError.new("Array expected but #{offset.class} found")
719
+ end
720
+
721
+ if offset.size != dimensions.size
722
+ raise ArgumentError.new("#{dimensions.size} indices expected")
723
+ end
724
+ end
725
+
726
+ @offsets = offsets
727
+
728
+ # Translate relative indices to 1D-indicies starting by 0
729
+ if block_def_node != nil
730
+ if use_parameter_array
731
+ offsets_mapped = Hash.new
732
+ for i in 0..offsets.size-1
733
+ offsets_mapped[offsets[i]] = i
734
+ end
735
+
736
+ # Do not modify the original AST
737
+ @ast = block_def_node.clone
738
+ @ast.accept(FlattenIndexNodeVisitor.new(
739
+ block_parameter_names.first, offsets_mapped, self))
740
+ end
741
+ end
742
+ end
743
+
744
+ def ==(other)
745
+ return super(other) && offsets == other.offsets &&
746
+ out_of_range_value == other.out_of_range_value &&
747
+ use_parameter_array == other.use_parameter_array
748
+ end
749
+
750
+ def size
751
+ return input.first.command.size
752
+ end
753
+
754
+ def min_offset
755
+ return offsets.min
756
+ end
757
+
758
+ def max_offset
759
+ return offsets.max
760
+ end
761
+
159
762
  protected
160
763
 
161
- attr_reader :block
764
+ def block=(block)
765
+ super
766
+
767
+ if use_parameter_array
768
+ offsets_mapped = Hash.new
769
+ for i in 0..offsets.size-1
770
+ offsets_mapped[offsets[i]] = i
771
+ end
772
+
773
+ # Do not modify the original AST
774
+ @ast = block_def_node.clone
775
+ @ast.accept(FlattenIndexNodeVisitor.new(
776
+ block_parameter_names.first, offsets_mapped, self))
777
+ end
778
+ end
162
779
  end
163
780
 
164
781
  class ArraySelectCommand
165
782
  include ArrayCommand
166
783
 
167
- attr_reader :target
168
-
169
784
  def initialize(target, block)
170
- super
785
+ super()
171
786
 
172
- @target = target
173
787
  @block = block
788
+
789
+ # One element per thread
790
+ @input = [SingleInput.new(command: target.to_command, pattern: :tid)]
174
791
  end
175
792
 
176
793
  # how to implement SELECT?
@@ -182,67 +799,126 @@ module Ikra
182
799
 
183
800
  attr_reader :target
184
801
 
185
- Block = Proc.new do |element|
186
- element
187
- end
188
-
189
802
  @@unique_id = 1
190
803
 
191
- def initialize(target)
192
- super()
193
-
194
- @target = target
804
+ def initialize(target, block_size: DEFAULT_BLOCK_SIZE, dimensions: nil)
805
+ super(block_size: block_size)
195
806
 
196
807
  # Ensure that base array cannot be modified
197
808
  target.freeze
809
+
810
+ # One thread per array element
811
+ @target = target
812
+ @input = [SingleInput.new(command: target, pattern: :tid)]
813
+
814
+ @dimensions = dimensions
198
815
  end
199
-
816
+
200
817
  def execute
201
- @target
818
+ return input.first.command
202
819
  end
203
820
 
204
821
  def size
205
- @target.size
822
+ return input.first.command.size
823
+ end
824
+
825
+ def dimensions
826
+ if @dimensions == nil
827
+ return [size]
828
+ else
829
+ return @dimensions
830
+ end
206
831
  end
207
832
 
208
- # Returns a collection of external objects that are accessed within a parallel section. This includes all elements of the base array.
833
+ # Returns a collection of external objects that are accessed within a parallel
834
+ # section. This includes all elements of the base array.
209
835
  def externals
210
- lexical_externals.keys + @target
836
+ lexical_externals.keys + input.first.command
211
837
  end
212
838
 
213
839
  def base_type
214
- # TODO: add caching (@target is frozen)
840
+ # TODO: add caching (`input` is frozen)
215
841
  type = Types::UnionType.new
216
842
 
217
- @target.each do |element|
218
- type.expand_with_singleton_type(element.class.to_ikra_type)
843
+ input.first.command.each do |element|
844
+ type.add(element.class.to_ikra_type)
219
845
  end
220
846
 
221
- type
222
- end
223
-
224
- protected
225
-
226
- def block
227
- Block
847
+ return type
228
848
  end
229
849
  end
230
850
  end
231
851
  end
232
852
 
233
853
  class Array
854
+ include Ikra::Symbolic::ParallelOperations
855
+
234
856
  class << self
235
- def pnew(size, &block)
236
- Ikra::Symbolic::ArrayNewCommand.new(size, block)
857
+ def pnew(size = nil, **options, &block)
858
+ if size != nil
859
+ dimensions = [size]
860
+ else
861
+ dimensions = options[:dimensions]
862
+ end
863
+
864
+ map_options = options.dup
865
+ map_options.delete(:dimensions)
866
+
867
+ return Ikra::Symbolic::ArrayIndexCommand.new(
868
+ dimensions: dimensions, block_size: options[:block_size]).pmap(**map_options, &block)
237
869
  end
238
870
  end
239
871
 
240
- def pmap(&block)
241
- Ikra::Symbolic::ArrayMapCommand.new(to_command, block)
872
+ # Have to keep the old methods around because sometimes we want to have the original code
873
+ alias_method :old_plus, :+
874
+ alias_method :old_minus, :-
875
+ alias_method :old_mul, :*
876
+ alias_method :old_or, :|
877
+ alias_method :old_and, :&
878
+
879
+ def +(other)
880
+ if other.is_a?(Ikra::Symbolic::ArrayCommand)
881
+ super(other)
882
+ else
883
+ return self.old_plus(other)
884
+ end
885
+ end
886
+
887
+ def -(other)
888
+ if other.is_a?(Ikra::Symbolic::ArrayCommand)
889
+ super(other)
890
+ else
891
+ return self.old_minus(other)
892
+ end
242
893
  end
243
894
 
244
- def to_command
245
- Ikra::Symbolic::ArrayIdentityCommand.new(self)
895
+ def *(other)
896
+ if other.is_a?(Ikra::Symbolic::ArrayCommand)
897
+ super(other)
898
+ else
899
+ return self.old_mul(other)
900
+ end
901
+ end
902
+
903
+ def |(other)
904
+ if other.is_a?(Ikra::Symbolic::ArrayCommand)
905
+ super(other)
906
+ else
907
+ return self.old_or(other)
908
+ end
909
+ end
910
+
911
+ def &(other)
912
+ if other.is_a?(Ikra::Symbolic::ArrayCommand)
913
+ super(other)
914
+ else
915
+ return self.old_and(other)
916
+ end
917
+ end
918
+
919
+ def to_command(dimensions: nil)
920
+ return Ikra::Symbolic::ArrayIdentityCommand.new(self, dimensions: dimensions)
246
921
  end
247
922
  end
248
923
 
924
+ require_relative "host_section"