ikra 0.0.1 → 0.0.2

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