carray-jit 0.1.0

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 (60) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +10 -0
  3. data/CHANGELOG.md +84 -0
  4. data/LICENSE +21 -0
  5. data/README.md +88 -0
  6. data/bin/carray-jit +194 -0
  7. data/carray-jit.gemspec +41 -0
  8. data/docs/00_Introduction.md +40 -0
  9. data/docs/01_GettingStarted.md +80 -0
  10. data/docs/02_KernelShapes.md +397 -0
  11. data/docs/03_SupportedFeatures.md +595 -0
  12. data/docs/04_Compiling.md +234 -0
  13. data/docs/05_DesignNotes.md +136 -0
  14. data/docs/06_Cheatsheet.md +177 -0
  15. data/examples/README.md +56 -0
  16. data/examples/applications/game_of_life.rb +161 -0
  17. data/examples/applications/heat_equation.rb +117 -0
  18. data/examples/applications/kepler.rb +178 -0
  19. data/examples/applications/mandelbrot.rb +151 -0
  20. data/examples/applications/moving_average.rb +124 -0
  21. data/examples/applications/partial_sums.rb +141 -0
  22. data/examples/applications/point_cloud.rb +110 -0
  23. data/examples/applications/quicksort.rb +118 -0
  24. data/examples/applications/recursion.rb +121 -0
  25. data/examples/applications/relaxation.rb +115 -0
  26. data/examples/applications/sensor_gaps.rb +118 -0
  27. data/examples/applications/sieve.rb +95 -0
  28. data/examples/applications/sobel_edges.rb +80 -0
  29. data/examples/features/01_element_wise.rb +69 -0
  30. data/examples/features/02_stencil.rb +40 -0
  31. data/examples/features/03_recurrence.rb +50 -0
  32. data/examples/features/04_thomas.rb +81 -0
  33. data/examples/features/05_reduction.rb +90 -0
  34. data/examples/features/06_jit_contract.rb +58 -0
  35. data/examples/features/07_masks.rb +55 -0
  36. data/examples/features/08_views.rb +46 -0
  37. data/examples/features/09_inspecting.rb +55 -0
  38. data/examples/features/10_complex.rb +107 -0
  39. data/examples/features/11_c_functions.rb +260 -0
  40. data/examples/features/12_sweep.rb +139 -0
  41. data/examples/features/13_cscalar.rb +80 -0
  42. data/examples/features/14_stencil_window.rb +106 -0
  43. data/examples/features/15_loops.rb +148 -0
  44. data/examples/features/16_raising.rb +69 -0
  45. data/ext/carray_jit_access/carray_jit_access.c +460 -0
  46. data/ext/carray_jit_access/extconf.rb +8 -0
  47. data/lib/carray/jit/analyzer.rb +1847 -0
  48. data/lib/carray/jit/block_reader.rb +139 -0
  49. data/lib/carray/jit/c_function.rb +777 -0
  50. data/lib/carray/jit/c_generator.rb +2305 -0
  51. data/lib/carray/jit/compiler.rb +468 -0
  52. data/lib/carray/jit/errors.rb +37 -0
  53. data/lib/carray/jit/expression.rb +202 -0
  54. data/lib/carray/jit/kernel.rb +509 -0
  55. data/lib/carray/jit/node.rb +573 -0
  56. data/lib/carray/jit/sweep.rb +97 -0
  57. data/lib/carray/jit/type_assignment.rb +811 -0
  58. data/lib/carray/jit/version.rb +5 -0
  59. data/lib/carray/jit.rb +1210 -0
  60. metadata +139 -0
@@ -0,0 +1,1847 @@
1
+ require "prism"
2
+
3
+ class CArray
4
+ module JIT
5
+
6
+ # Turns the source of a kernel block into an untyped IR tree, rejecting
7
+ # anything outside the recognized subset.
8
+ #
9
+ # The block's parameters are the loop indices and nothing else; arrays and
10
+ # scalars alike are reached as variables the block closed over. Which of
11
+ # those names are arrays has to be settled before the tree can be built,
12
+ # so the caller passes them in -- it knows, because it has the values.
13
+ class Analyzer
14
+
15
+ # Ruby Math methods that correspond 1:1 to a math.h function.
16
+ #
17
+ # Deliberately excludes anything whose C counterpart disagrees on
18
+ # semantics. `%` is the cautionary case: CArray floors it to agree with
19
+ # Ruby, while C's fmod truncates, so `%` is NOT lowered to fmod.
20
+ MATH_FUNCTIONS = {
21
+ :sqrt => "sqrt",
22
+ :cbrt => "cbrt",
23
+ :exp => "exp",
24
+ :log => "log",
25
+ :log2 => "log2",
26
+ :log10 => "log10",
27
+ :sin => "sin",
28
+ :cos => "cos",
29
+ :tan => "tan",
30
+ :asin => "asin",
31
+ :acos => "acos",
32
+ :atan => "atan",
33
+ :atan2 => "atan2",
34
+ :sinh => "sinh",
35
+ :cosh => "cosh",
36
+ :tanh => "tanh",
37
+ :hypot => "hypot",
38
+ :asinh => "asinh",
39
+ :acosh => "acosh",
40
+ :atanh => "atanh",
41
+ }.freeze
42
+
43
+ # The C99 complex form of each math.h function that has one, by the
44
+ # real name this IR carries. Which functions appear here is not a
45
+ # choice made here: it is the set CArray itself computes on a complex
46
+ # array, so that a formula gives the same answer whether it is applied
47
+ # to the array or compiled cell by cell.
48
+ #
49
+ # Absent, because a complex CArray raises CArray::DataTypeError for
50
+ # them: log10 and log2 (C99 has no clog10 or clog2), cbrt (no ccbrt),
51
+ # and the two-argument atan2 and hypot, which are about the plane a
52
+ # complex number already lives in.
53
+ COMPLEX_MATH_FUNCTIONS = {
54
+ "sqrt" => "csqrt",
55
+ "exp" => "cexp",
56
+ "log" => "clog",
57
+ "sin" => "csin",
58
+ "cos" => "ccos",
59
+ "tan" => "ctan",
60
+ "asin" => "casin",
61
+ "acos" => "cacos",
62
+ "atan" => "catan",
63
+ "sinh" => "csinh",
64
+ "cosh" => "ccosh",
65
+ "tanh" => "ctanh",
66
+ "asinh" => "casinh",
67
+ "acosh" => "cacosh",
68
+ "atanh" => "catanh",
69
+ }.freeze
70
+
71
+ # The postfix spelling `x.sqrt`, which CArray::CoreExtensions puts on
72
+ # Float and Integer so that one formula reads the same whether it is
73
+ # applied to a scalar or to a whole array. A per-cell kernel works on
74
+ # scalars pulled out of arrays, so a formula already written that way
75
+ # should not have to be rewritten to be compiled.
76
+ #
77
+ # Only the names that are 1:1 with math.h; the rest of the refinement
78
+ # is refused by name below.
79
+ POSTFIX_NAMES = %i[sqrt exp log log10 sin cos tan sinh cosh tanh
80
+ asin acos atan asinh acosh atanh].freeze
81
+
82
+ # Names the same refinement provides that are NOT 1:1 with math.h.
83
+ # `expm1` and `log1p` are the ones worth naming: C has functions by
84
+ # those names, and they exist precisely because `exp(x) - 1` and
85
+ # `log(1 + x)` lose precision for small x -- which is what the Ruby
86
+ # side computes. Lowering them to the C functions would silently
87
+ # produce different numbers.
88
+ REFUSED_POSTFIX = {
89
+ :expm1 => "the refinement computes exp(x) - 1, which is not what " \
90
+ "C's expm1 computes",
91
+ :log1p => "the refinement computes log(1 + x), which is not what " \
92
+ "C's log1p computes",
93
+ :rad => "write the multiplication out",
94
+ :deg => "write the multiplication out",
95
+ :square => "write x * x",
96
+ :rsqrt => "write 1.0 / Math.sqrt(x)",
97
+ :signbit => "write x < 0",
98
+ :deg_360 => "no math.h counterpart",
99
+ :deg_180 => "no math.h counterpart",
100
+ :rad_2pi => "no math.h counterpart",
101
+ :rad_pi => "no math.h counterpart",
102
+ }.freeze
103
+
104
+ ARITHMETIC_OPERATORS = [:+, :-, :*, :/, :%].freeze
105
+ # Ruby's bit operators on Integers, which C has too. What they do at
106
+ # the edges is C's answer rather than Ruby's -- a shift wraps and takes
107
+ # its count modulo the width, because CArray's own `<<` compiles to the
108
+ # same C shift (`ext/mkkernel.rb`, :bit_lshift) and this has to agree
109
+ # with CArray.
110
+ BIT_OPERATORS = [:&, :|, :^, :<<, :>>].freeze
111
+
112
+ # Methods whose result type is a property of the method: Float#floor and
113
+ # friends hand back an Integer in Ruby, and so must here.
114
+ CONVERSIONS = {
115
+ :floor => [:int64, "floor"],
116
+ :ceil => [:int64, "ceil"],
117
+ :round => [:int64, "round"],
118
+ :truncate => [:int64, "trunc"],
119
+ :to_i => [:int64, "trunc"],
120
+ :to_int => [:int64, "trunc"],
121
+ :to_f => [:double, nil],
122
+ }.freeze
123
+
124
+ # The parts of a complex number, by every spelling Ruby gives them.
125
+ # `imaginary` and `imag` are one method in Ruby and one node here.
126
+ COMPLEX_PARTS = {
127
+ :real => :real,
128
+ :imaginary => :imaginary,
129
+ :imag => :imaginary,
130
+ :conjugate => :conjugate,
131
+ :conj => :conjugate,
132
+ :arg => :arg,
133
+ :angle => :arg,
134
+ :phase => :arg,
135
+ }.freeze
136
+
137
+ # Constants under Math, emitted as literals so that the C sees exactly
138
+ # the double Ruby would have used.
139
+ MATH_CONSTANTS = { :PI => Math::PI, :E => Math::E }.freeze
140
+ COMPARISON_OPERATORS = [:<, :<=, :>, :>=, :==, :!=].freeze
141
+
142
+ attr_reader :parameter_names, :pointer_names, :address_arrays,
143
+ :address_parameters
144
+ # An offset that is an integer here rather than when the kernel runs.
145
+ # A literal is one; so is arithmetic over literals, which is the same
146
+ # number written a way that says where it came from -- `w[-RADIUS-1]`
147
+ # cannot be written, but `w[-2-1]` can, and a stencil drawn from a
148
+ # formula is usually written the second way.
149
+ #
150
+ # Nothing that reads a value: a captured integer arrives with the call,
151
+ # and one kernel serves every value of it, so a window built from one
152
+ # would have a radius the compiled loop does not know. The radius is
153
+ # what lets the interior be walked without asking, cell by cell,
154
+ # whether it is still inside.
155
+ LITERAL_OPERATORS = { :+ => true, :- => true, :* => true }.freeze
156
+
157
+ def literal_integer (node)
158
+ node = unwrap(node)
159
+ case node
160
+ when Prism::IntegerNode
161
+ node.value
162
+ when Prism::CallNode
163
+ return nil unless node.receiver
164
+ value = literal_integer(node.receiver)
165
+ return nil unless value
166
+ arguments = node.arguments&.arguments || []
167
+ if arguments.empty?
168
+ return -value if node.name == :-@
169
+ return value if node.name == :+@
170
+ return nil
171
+ end
172
+ return nil unless arguments.size == 1 && LITERAL_OPERATORS[node.name]
173
+ right = literal_integer(arguments.first)
174
+ right && value.public_send(node.name, right)
175
+ end
176
+ end
177
+
178
+ # How far the windows reach on each axis, as [lowest, highest] -- the
179
+ # radius, kept per side because a window need not be symmetric. It is
180
+ # what the caller walks the interior by.
181
+ attr_reader :window_reach
182
+ # The names the block gave its windows, which are the arrays a border
183
+ # rule applies to: the ones the block reaches away from the cell in.
184
+ attr_reader :windows
185
+ attr_reader :index_names, :array_names, :scalar_names, :c_function_names, :body,
186
+ :written_arrays, :array_ranks, :subscripts, :inner_ranges,
187
+ :contracted_names
188
+
189
+ # A kernel that mentions UNDEF is a masked kernel whatever its arrays
190
+ # happen to carry: it asks about masks, or makes them.
191
+ def uses_undef?
192
+ @uses_undef
193
+ end
194
+
195
+ # Collects the names a block reaches for without assigning them, so the
196
+ # caller can look up their values and say which are arrays.
197
+ # Whether the source names UNDEF anywhere, which decides that the kernel
198
+ # is a masked one before anything else is known about it.
199
+ def self.mentions_undef? (source, node: nil)
200
+ analyzer = allocate
201
+ block = node || analyzer.send(:parse_block, source)
202
+ analyzer.send(:names_constants, block).include?(:UNDEF)
203
+ end
204
+
205
+ # The names the block reaches for, and the names it assigns. The
206
+ # second set is not a subset of the first -- a name the block assigns
207
+ # is not free in it -- but in the whole-array spelling an assignment
208
+ # may still land in an array outside, so the caller looks both up.
209
+ def self.free_names (source, node: nil)
210
+ allocate.send(:scan_free_names, source, node).first
211
+ end
212
+
213
+ def self.free_and_assigned_names (source, node: nil)
214
+ allocate.send(:scan_free_names, source, node)
215
+ end
216
+
217
+ # `rank` is given only for a block that takes no parameters and writes
218
+ # `out[] = a[] + b[]`. There the rank is a property of the arrays
219
+ # rather than of the block, so it arrives from the caller and the loop
220
+ # indices are named here.
221
+ # `steps` is the stride each outer index advances by, which decides
222
+ # whether an offset is a dependency at all: with a step of two, reading
223
+ # `a[i-1]` touches a cell this loop never writes.
224
+ # `contract` puts the block in the contraction convention: every parameter
225
+ # is an index, the ones that do not appear on the left are summed over, and
226
+ # every extent comes from the arrays' own shapes.
227
+ # `contract` is true for a contraction, or :probe to stop before the
228
+ # rewrite -- which is how the returned form learns the summand's type
229
+ # and the free indices' extents before it has an array to put them in.
230
+ # `result` names the array a returned contraction writes into.
231
+ # `function` puts the block in the third mode: its parameters are values
232
+ # rather than loop indices, its body is an expression whose value is
233
+ # returned, and it reaches no array. It is the smallest of the three --
234
+ # with no cell to address there is no extent, no direction and no mask,
235
+ # so most of what follows never runs.
236
+ def initialize (source, node: nil, array_names: [], c_functions: {}, rank: nil,
237
+ steps: nil, contract: false, result: nil, function: false,
238
+ pointers: {}, map: false, cell_names: [],
239
+ recursion: nil, windows: [], returns: true)
240
+ @source = source
241
+ @node = node
242
+ @array_names = array_names
243
+ # Arrays with one cell and no axis to walk -- a CScalar. There is no
244
+ # index to write for one, which is the whole of what distinguishes it
245
+ # from the one-cell CArray it otherwise is, so it is spelled `s[]` or
246
+ # named bare and the loop reads its cell at every iteration.
247
+ @cell_names = cell_names
248
+ # `jit_stencil`: the block's parameters are windows onto the arrays it
249
+ # was given, rather than the loop's indices. `a[-1, 1]` is then an
250
+ # offset from the cell the loop is on -- the same reach `a[i-1, j+1]`
251
+ # writes with the indices named, which is what it becomes here. The
252
+ # indices are this analyzer's, as they are for a block that names
253
+ # none, because a window has nowhere to write one.
254
+ @windows = windows
255
+ @c_functions = c_functions
256
+ @function = function
257
+ # A function declared `void` ends in a statement like any other; one
258
+ # that returns ends in the expression it returns. Which it is comes
259
+ # from the declaration, as everything else about a signature does.
260
+ @returns = returns
261
+ # `map` is jit_map rather than jit_each: the same block, read the
262
+ # same way, except that its last statement is a value and every cell
263
+ # of the result gets it. An assignment may be that statement, since
264
+ # in Ruby an assignment has the value it assigned.
265
+ @map = map
266
+ # In a function, a parameter declared `const double *` is reached the
267
+ # way C reaches it. The value says what may be done with it: true to
268
+ # read and write, false to read only (= const), nil for one that
269
+ # points at nothing in particular and so cannot be reached at all.
270
+ @pointers = pointers
271
+ # What the function being compiled is called, what it takes and what
272
+ # it returns -- so its own body can call it. C puts a declarator's
273
+ # name in scope inside the body it heads, and this is that. Nil for
274
+ # a kernel, and for a function declared without a name, which has
275
+ # nothing to call itself by.
276
+ @recursion = recursion
277
+ @whole_array = false
278
+ # How far the windows reach on each axis, filled in as they are read.
279
+ @window_reach = Array.new(rank.to_i) { [0, 0] }
280
+ @uses_undef = false
281
+ @calls_for_effect = false
282
+ @outer_names = []
283
+ @inner_names = []
284
+ # How many loops the statement being built stands inside. `break`
285
+ # needs a loop to leave and does not care which kind: an inner loop
286
+ # brings an index and a `while` brings none, but both are loops in C
287
+ # and in the Ruby they stand for.
288
+ @loop_depth = 0
289
+ @inner_names_seen = []
290
+ @array_ranks = {}
291
+ @inner_ranges = {}
292
+ @subscripts = Hash.new { |hash, key| hash[key] = [] }
293
+ @given_rank = rank
294
+ @steps = steps
295
+ @contract = contract
296
+ @result = result
297
+ @contracted_names = []
298
+ @free_names = []
299
+ @index_names = []
300
+ @local_names = []
301
+ @assigned_names = []
302
+ @scalar_names = []
303
+ @c_function_names = []
304
+ @parameter_names = []
305
+ @pointer_names = []
306
+ @address_arrays = []
307
+ @address_parameters = {}
308
+ @read_offsets = Hash.new { |hash, key| hash[key] = [] }
309
+ @written_arrays = []
310
+ analyze
311
+ end
312
+
313
+ def rank
314
+ @index_names.size
315
+ end
316
+
317
+ # For one axis of one array: which indices walk it and how far the kernel
318
+ # reaches along it with each, plus any fixed positions it is also read
319
+ # at. Returns [[[index, minimum, maximum], ...], constants].
320
+ #
321
+ # An axis may be walked by more than one index, which is how a covariance
322
+ # is written -- `c[p,a] * c[p,b]` reads the same axis at two independent
323
+ # positions. A fixed subscript sits alongside them, so a kernel may read
324
+ # `a[i, 0]` and `a[i, j]` on the same axis too.
325
+ #
326
+ # This holds for an array the kernel writes as well. In `v[a,b] =
327
+ # v[a,a]` the cell being read is one this loop also writes, at b == a,
328
+ # so cells reached before that read the old value and cells reached
329
+ # after it read the new one -- and the answer depends on the order. It
330
+ # is not an ambiguity, though: the extent states the order, so the
331
+ # kernel runs the order it was given and means what the same Ruby loop
332
+ # means. What is lost is only the check, since there is no fixed offset
333
+ # here to derive a direction from and compare the extent against.
334
+ def axis_use (array, axis)
335
+ uses = @subscripts[array].map { |per_axis| per_axis[axis] }.compact
336
+ walked = uses.reject { |index, _| index.nil? }
337
+ constants = uses.select { |index, _| index.nil? }.map(&:last)
338
+ .select { |node|
339
+ !node.is_a?(Node) || Analyzer.fixed_subscript?(node)
340
+ }
341
+ names = walked.map(&:first).uniq
342
+ walkers = names.map { |name|
343
+ [name, walked.select { |index, _| index == name }.map(&:last)]
344
+ }
345
+ [walkers, constants]
346
+ end
347
+
348
+ # Per array: the axes it is read at an index only the running kernel
349
+ # knows. Those axes are bounds-checked as they are reached.
350
+ def dynamic_axes (array)
351
+ axes = []
352
+ @subscripts[array].each do |per_axis|
353
+ per_axis.each_with_index do |(index, offset), axis|
354
+ next unless index.nil? && offset.is_a?(Node)
355
+ axes << axis unless Analyzer.fixed_subscript?(offset)
356
+ end
357
+ end
358
+ axes.uniq.sort
359
+ end
360
+
361
+ # A write is addressed the way a read is: every axis either walks with an
362
+ # outer index, at whatever offset, or is pinned at a position known
363
+ # before the first cell.
364
+ #
365
+ # Two iterations may land on the same cell -- `box[0]` puts every one of
366
+ # them there -- and that is not an ambiguity: the extent states the
367
+ # order, so what the array holds afterwards is what the same Ruby loop
368
+ # would leave in it. An inner loop already rests on exactly that, an
369
+ # accumulator being one cell written once per pass.
370
+ def walking_subscripts? (subscripts)
371
+ subscripts.all? { |index, offset|
372
+ if index
373
+ @outer_names.include?(index)
374
+ else
375
+ !offset.is_a?(Node) || Analyzer.fixed_subscript?(offset)
376
+ end
377
+ }
378
+ end
379
+
380
+ # A write is a scatter when some axis of it is addressed by a value only
381
+ # the running kernel knows. The axes that are not may still be the
382
+ # loop's own indices.
383
+ def computed_subscripts? (subscripts)
384
+ subscripts.any? { |index, offset|
385
+ index.nil? && offset.is_a?(Node) && !Analyzer.fixed_subscript?(offset)
386
+ } && subscripts.all? { |index, offset|
387
+ (index && offset.zero?) ||
388
+ (index.nil? && offset.is_a?(Node) && !Analyzer.fixed_subscript?(offset))
389
+ }
390
+ end
391
+
392
+ def arrays_used
393
+ (@subscripts.keys + @written_arrays).uniq
394
+ end
395
+
396
+ def inner_index? (name)
397
+ @inner_names.include?(name)
398
+ end
399
+
400
+ private
401
+
402
+ def scan_free_names (source, node)
403
+ block = node || parse_block(source)
404
+ parameters = block.parameters
405
+ list = parameters && parameters.parameters
406
+ indices = list ? list.requireds.map(&:name) : []
407
+ assigned = collect_assigned_names(block.body)
408
+ # An inner loop names an index too, and it is no more a captured
409
+ # variable than the outer ones are.
410
+ inner = collect_block_parameters(block.body)
411
+ constants = names_constants(block.body).reject { |name| resolved_here?(name) }
412
+ free = (collect_names(block.body) + constants).uniq -
413
+ indices - assigned - inner
414
+ [free, assigned.uniq - indices - inner]
415
+ end
416
+
417
+ # Names this analyzer answers for itself, so they are never captured:
418
+ # UNDEF is a mark rather than a value, and `Math` is written out in C
419
+ # (see build_math_call and build_constant_path).
420
+ NAMES_RESOLVED_HERE = [:UNDEF, :Math].freeze
421
+
422
+ # `Math::PI` and the rest are written out in C, so they are not among
423
+ # the names captured either.
424
+ def resolved_here? (name)
425
+ NAMES_RESOLVED_HERE.include?(name) || name.to_s.start_with?("Math::")
426
+ end
427
+
428
+ # `raise "..."` says what it says: the class is not named and the
429
+ # message is a literal, so nothing under it is a name the block reached
430
+ # for. Both scans stop here, or `raise ArgumentError, "x"` would be
431
+ # refused for capturing a Class and never reach the reason it is
432
+ # actually refused for.
433
+ def raise_call? (node)
434
+ node.is_a?(Prism::CallNode) && node.receiver.nil? && node.name == :raise
435
+ end
436
+
437
+ def names_constants (node)
438
+ return [] unless node
439
+ return [] if raise_call?(node)
440
+ # `Foo::TABLE` names one thing, and `Foo` on its own names none of
441
+ # it, so a path is read whole and not descended into.
442
+ return [node.slice.to_sym] if node.is_a?(Prism::ConstantPathNode)
443
+ names = []
444
+ names << node.name if node.is_a?(Prism::ConstantReadNode)
445
+ node.compact_child_nodes.each { |child| names.concat(names_constants(child)) }
446
+ names
447
+ end
448
+
449
+ def collect_block_parameters (node)
450
+ return [] unless node
451
+ names = []
452
+ if node.is_a?(Prism::BlockParametersNode) && node.parameters
453
+ names.concat(node.parameters.requireds.map(&:name))
454
+ end
455
+ node.compact_child_nodes.each do |child|
456
+ names.concat(collect_block_parameters(child))
457
+ end
458
+ names
459
+ end
460
+
461
+ def collect_names (node)
462
+ return [] unless node
463
+ return [] if raise_call?(node)
464
+ names = []
465
+ case node
466
+ when Prism::LocalVariableReadNode
467
+ names << node.name
468
+ when Prism::CallNode
469
+ if node.receiver.nil? && node.arguments.nil? && node.block.nil?
470
+ names << node.name
471
+ end
472
+ end
473
+ node.compact_child_nodes.each { |child| names.concat(collect_names(child)) }
474
+ names
475
+ end
476
+
477
+ def analyze
478
+ block = @node || parse_block(@source)
479
+ read_parameters(block)
480
+
481
+ statements = block.body ? block.body.body : []
482
+ if statements.empty?
483
+ raise Unsupported.new("kernel body is empty")
484
+ end
485
+ @assigned_names = collect_assigned_names(block.body)
486
+
487
+ # A contraction may end in a bare expression rather than an
488
+ # assignment; that is the form that allocates its result and returns.
489
+ # A function always ends in one -- that expression is what it returns.
490
+ last = statements.last
491
+ # A block that ends in an assignment still has that assignment's
492
+ # value, which is what Ruby says it has -- so jit_map keeps the
493
+ # statement and reads the cell back for the value.
494
+ map_assignment = @map && last.is_a?(Prism::LocalVariableWriteNode)
495
+ returns_value = (@function && @returns) || (@map && !map_assignment) ||
496
+ (@contract &&
497
+ !(last.is_a?(Prism::CallNode) && last.name == :[]=))
498
+ built = statements[0..-2].map { |node| build_statement(node) }
499
+ if map_assignment
500
+ built << build_statement(last)
501
+ @map_value = build_name_read(last.name, last.location)
502
+ else
503
+ built << (returns_value ? build(last) : build_statement(last))
504
+ @map_value = built.last if @map
505
+ end
506
+ @body = KernelBody.new(built)
507
+
508
+ return if @contract == :probe || @map == :probe
509
+ return if @function
510
+
511
+ map_body if @map
512
+ contract_body if @contract
513
+ verify_written_arrays_are_not_read_through_inner_indices
514
+
515
+ # A kernel that only calls a C function still does something: what
516
+ # it did is wherever the addresses it handed over pointed. Only a
517
+ # kernel that neither wrote nor called has put its work nowhere.
518
+ if @written_arrays.empty? && !@calls_for_effect
519
+ raise Unsupported.new(
520
+ @whole_array ?
521
+ "this block computes a value and puts it nowhere; assign it to " \
522
+ "an array, as in `out = ...`, or ask for the value back with " \
523
+ "`CArray.jit_map`" :
524
+ "the kernel writes to no array; assign to one, as in " \
525
+ "`out[i] = ...`")
526
+ end
527
+ end
528
+
529
+ # An array the kernel writes is written once per outer cell; reading it
530
+ # through an inner index would reach cells other outer iterations own,
531
+ # and no evaluation order settles that.
532
+ def verify_written_arrays_are_not_read_through_inner_indices
533
+ @written_arrays.each do |array|
534
+ @subscripts[array].each do |per_axis|
535
+ per_axis.each do |index, _|
536
+ next unless @inner_names_seen.include?(index)
537
+ raise Unsupported.new(
538
+ "`#{array}` is written by this kernel, so it cannot also be " \
539
+ "read through the inner index `#{index}`")
540
+ end
541
+ end
542
+ end
543
+ end
544
+
545
+ # Turns `c[i,j] = a[i,k] * b[k,j]` into the loops it stands for.
546
+ #
547
+ # Which indices are summed is not the assignment's business: an index
548
+ # that appears twice in the term is summed, and that repetition is the
549
+ # notation -- it is what stands in for the sigma. An index appearing
550
+ # once is free. The left-hand side says where the result goes and in
551
+ # what order its axes lie; it cannot make an index disappear.
552
+ # The block's value is what every cell of the result gets, so the last
553
+ # expression becomes a write into the result at the cell the loop is on.
554
+ # jit_map writes its value into a result of its own, at the cell the
555
+ # loop is on, and that array is what comes back. Where the block ended
556
+ # in an assignment the assignment stays, and the value is the cell it
557
+ # just wrote -- which is the value Ruby gives that statement.
558
+ def map_body
559
+ statements = @body.statements
560
+ statements = statements[0..-2] if statements.last.equal?(@map_value)
561
+ subscripts = @outer_names.map { |name| [name, 0] }
562
+ write = ElementWrite.new(@result, @map_value, @map_value.location,
563
+ subscripts)
564
+ @body = KernelBody.new(statements + [write])
565
+ record_array_rank(@result, subscripts.size)
566
+ record_subscripts(@result, subscripts)
567
+ @written_arrays << @result unless @written_arrays.include?(@result)
568
+ end
569
+
570
+ def contract_body
571
+ statements = @body.statements
572
+ writes = statements.grep(ElementWrite)
573
+ unless writes.size <= 1 && (writes.empty? || statements.last.equal?(writes.first))
574
+ raise Unsupported.new(
575
+ "a contraction is one expression, optionally assigned into an " \
576
+ "array of your own")
577
+ end
578
+
579
+ write = writes.first
580
+ unless write
581
+ # The returned form: the free indices, in the order the block named
582
+ # them, are the result's axes.
583
+ _, summed = classify_indices(statements, statements.last)
584
+ free = @index_names - summed
585
+ # With every index summed the result is a single number, which lives
586
+ # in a one-cell array at a fixed subscript.
587
+ subscripts = free.empty? ? [[nil, 0]] : free.map { |name| [name, 0] }
588
+ write = ElementWrite.new(@result, statements.last, statements.last.location,
589
+ subscripts)
590
+ statements = statements[0..-2] + [write]
591
+ @body = KernelBody.new(statements)
592
+ record_array_rank(@result, subscripts.size)
593
+ record_subscripts(@result, subscripts)
594
+ @written_arrays << @result
595
+ end
596
+
597
+ written = write.subscripts.reject { |index, _| index.nil? }.map(&:first)
598
+ if written.uniq.size != written.size
599
+ raise Unsupported.new(
600
+ "the left-hand side names #{written.join(', ')}; an index can walk " \
601
+ "one of its axes only")
602
+ end
603
+ unless write.subscripts.all? { |index, offset| index.nil? || offset.zero? }
604
+ raise Unsupported.new(
605
+ "a contraction writes the cell it is on, with no offset")
606
+ end
607
+ if @subscripts[write.array].size > 1
608
+ raise Unsupported.new(
609
+ "`#{write.array}` is both written and read here, which is a " \
610
+ "recurrence rather than a contraction; write it with jit_for")
611
+ end
612
+
613
+ free, summed = classify_indices(statements, write)
614
+ unless written.sort == free.sort
615
+ raise Unsupported.new(describe_index_mismatch(written, free, summed))
616
+ end
617
+
618
+ @free_names = free
619
+ @contracted_names = summed
620
+ @outer_names = written
621
+ @index_names = written
622
+
623
+ accumulator = free_local_name
624
+ summand = statements[0..-2] + [Assignment.new(accumulator,
625
+ BinaryOperation.new(:+,
626
+ LocalRead.new(accumulator),
627
+ write.expression))]
628
+ inner = @contracted_names.each_with_index.reverse_each.inject(summand) {
629
+ |body, (name, position)|
630
+ slot = @outer_names.size + position
631
+ from = BoundsValue.new(3 * slot)
632
+ to = BoundsValue.new(3 * slot + 1)
633
+ @inner_ranges[name] = [from, to]
634
+ [InnerLoop.new(name, from, to, body)]
635
+ }
636
+
637
+ @body = KernelBody.new(
638
+ [Assignment.new(accumulator, zero_for(write))] + inner +
639
+ [ElementWrite.new(write.array, LocalRead.new(accumulator), write.location,
640
+ write.subscripts)])
641
+ end
642
+
643
+ # Counts where each index sits on a tensor, on the right-hand side only.
644
+ # Once is free, twice is summed, and more than twice is not the
645
+ # convention -- it says nothing about which pair to sum.
646
+ def classify_indices (statements, write)
647
+ counts = Hash.new(0)
648
+ subscripts_of(statements, write).each { |index, _| counts[index] += 1 }
649
+
650
+ missing = @index_names - counts.keys
651
+ unless missing.empty?
652
+ raise Unsupported.new(
653
+ "#{missing.map { |name| "`#{name}`" }.join(', ')} " \
654
+ "#{missing.size == 1 ? 'names no axis' : 'name no axis'} here")
655
+ end
656
+ crowded = counts.select { |_, count| count > 2 }.keys
657
+ unless crowded.empty?
658
+ raise Unsupported.new(
659
+ "#{crowded.map { |name| "`#{name}`" }.join(', ')} appears more " \
660
+ "than twice; a contraction sums a pair, and there is no pair to " \
661
+ "choose")
662
+ end
663
+
664
+ [@index_names.select { |name| counts[name] == 1 },
665
+ @index_names.select { |name| counts[name] == 2 }]
666
+ end
667
+
668
+ # Every subscript on the right-hand side: the summand, and whatever the
669
+ # locals before it read.
670
+ def subscripts_of (statements, write)
671
+ summand = write.is_a?(ElementWrite) ? write.expression : write
672
+ collected = []
673
+ walk = lambda do |node|
674
+ return unless node.is_a?(Node)
675
+ collected.concat(node.subscripts) if node.is_a?(ElementRead)
676
+ collected.concat(node.subscripts) if node.is_a?(MaskTest)
677
+ node.children.each { |child| walk.call(child) }
678
+ end
679
+ statements[0..-2].each { |statement| walk.call(statement) }
680
+ walk.call(summand)
681
+ collected.reject { |index, _| index.nil? }
682
+ end
683
+
684
+ def describe_index_mismatch (written, free, summed)
685
+ summed_on_left = written & summed
686
+ unless summed_on_left.empty?
687
+ return "#{summed_on_left.map { |name| "`#{name}`" }.join(', ')} " \
688
+ "#{summed_on_left.size == 1 ? 'appears' : 'appear'} twice on " \
689
+ "the right, so #{summed_on_left.size == 1 ? 'it is' : 'they are'} " \
690
+ "summed over and cannot also be free"
691
+ end
692
+ dropped = free - written
693
+ "#{dropped.map { |name| "`#{name}`" }.join(', ')} " \
694
+ "#{dropped.size == 1 ? 'appears' : 'appear'} once, so " \
695
+ "#{dropped.size == 1 ? 'it is' : 'they are'} free and must be on the " \
696
+ "left. A contraction sums the indices that appear twice; to sum one " \
697
+ "that does not, write the loop with jit_for, or use sum(axis:)"
698
+ end
699
+
700
+ # The sum starts from zero of whatever the summand is; which zero that
701
+ # is falls out of the type assignment.
702
+ def zero_for (write)
703
+ ZeroLike.new(write.expression, write.location)
704
+ end
705
+
706
+ public
707
+
708
+ # For the returned form, before the rewrite: which indices stay free, so
709
+ # the caller can size the result.
710
+ def probe_free_names
711
+ _, summed = classify_indices(@body.statements, @body.statements.last)
712
+ @index_names - summed
713
+ end
714
+
715
+ private
716
+
717
+ def free_local_name
718
+ name = :contraction
719
+ name = :"#{name}_" while @local_names.include?(name)
720
+ name
721
+ end
722
+
723
+ def parse_block (source)
724
+ result = Prism.parse(source)
725
+ unless result.success?
726
+ message = result.errors.map { |error| error.message }.join("; ")
727
+ raise Unsupported.new("kernel source does not parse: #{message}")
728
+ end
729
+ extract_block(result.value)
730
+ end
731
+
732
+ def collect_assigned_names (node)
733
+ return [] unless node
734
+ names = []
735
+ names << node.name if node.is_a?(Prism::LocalVariableWriteNode)
736
+ node.compact_child_nodes.each do |child|
737
+ names.concat(collect_assigned_names(child))
738
+ end
739
+ names.uniq
740
+ end
741
+
742
+ # Accepts `->(i) { ... }`, `proc { |i| ... }` and `lambda { |i| ... }`.
743
+ def extract_block (program)
744
+ statements = program.statements.body
745
+ unless statements.size == 1
746
+ raise Unsupported.new("kernel source must be a single block literal, " \
747
+ "got #{statements.size} statements")
748
+ end
749
+ node = statements.first
750
+
751
+ case node
752
+ when Prism::LambdaNode
753
+ node
754
+ when Prism::CallNode
755
+ unless [:proc, :lambda].include?(node.name) && node.block
756
+ raise Unsupported.new("expected a block literal", node.location)
757
+ end
758
+ node.block
759
+ else
760
+ raise Unsupported.new("expected a block literal such as `{ |i| ... }`, " \
761
+ "got #{node_name(node)}", node.location)
762
+ end
763
+ end
764
+
765
+ def read_parameters (block)
766
+ parameters = block.parameters
767
+ list = parameters && parameters.parameters
768
+ requireds = list ? list.requireds : []
769
+ if list && (list.optionals.any? || list.rest || list.keywords.any? || list.block)
770
+ raise Unsupported.new("the kernel block takes only required parameters")
771
+ end
772
+
773
+ if @function
774
+ @parameter_names = requireds.map(&:name)
775
+ @index_names = []
776
+ @outer_names = []
777
+ return
778
+ end
779
+
780
+ if @windows.any?
781
+ # The parameters were read before this analyzer was built -- the
782
+ # caller had to, to know which array each window is onto -- so what
783
+ # is left here is to check that the block agrees with what it was
784
+ # given, and to name the indices it does not name.
785
+ unless requireds.map(&:name) == @windows
786
+ raise Unsupported.new(
787
+ "the block's parameters are the windows onto the arrays it was " \
788
+ "given, in that order")
789
+ end
790
+ @index_names = Array.new(@given_rank) { |axis| :"index#{axis}" }
791
+ elsif requireds.empty?
792
+ unless @given_rank
793
+ raise Unsupported.new("the kernel block takes the loop indices as " \
794
+ "its parameters, and was given none")
795
+ end
796
+ @whole_array = true
797
+ @index_names = Array.new(@given_rank) { |axis| :"index#{axis}" }
798
+ else
799
+ @index_names = requireds.map(&:name)
800
+ end
801
+ @outer_names = @index_names.dup
802
+ end
803
+
804
+ # `printf` writes to the terminal from inside the loop, which is what
805
+ # it is for: the kernel is otherwise silent until it finishes. The
806
+ # format has to be a literal, since C reads it at compile time.
807
+ def build_print (node)
808
+ arguments = node.arguments ? node.arguments.arguments : []
809
+ template = arguments.first
810
+ unless template.is_a?(Prism::StringNode)
811
+ raise Unsupported.new(
812
+ "printf's format has to be written out, as in " \
813
+ "`printf(\"x = %g\\n\", x)`",
814
+ node.location)
815
+ end
816
+ Print.new(template.unescaped,
817
+ arguments.drop(1).map { |argument| build(argument) },
818
+ node.location)
819
+ end
820
+
821
+ # `raise "the message"`, and nothing else in that shape: the class is
822
+ # not named -- what comes back is the RuntimeError `raise "..."` gives
823
+ # in Ruby -- and the message is a literal, because it is registered when
824
+ # the body is compiled and only its code travels out of a cell.
825
+ #
826
+ # A compiled function may raise as a kernel may. It reports into the
827
+ # flag it already reports a division by zero into -- its own when it
828
+ # stands alone, the caller's when it is pasted into a kernel -- and the
829
+ # message behind the code travels with the function, so whichever way
830
+ # the body is reached, the same line raises the same thing.
831
+ def build_raise (node)
832
+ arguments = node.arguments ? node.arguments.arguments : []
833
+ if arguments.size != 1
834
+ raise Unsupported.new(
835
+ arguments.empty? ?
836
+ "`raise` in a kernel takes the message, as in `raise \"x is 0\"`" :
837
+ "`raise` in a kernel takes the message alone; the class is not " \
838
+ "named, and what comes back is a RuntimeError",
839
+ node.location)
840
+ end
841
+ message = arguments.first
842
+ unless message.is_a?(Prism::StringNode)
843
+ raise Unsupported.new(
844
+ "a kernel's `raise` message is written out rather than built: it " \
845
+ "is registered when the kernel is compiled, and the cell reports " \
846
+ "which one it was",
847
+ node.location)
848
+ end
849
+ Raise.new(message.unescaped, node.location)
850
+ end
851
+
852
+ def build_statement (node)
853
+ case node
854
+ when Prism::LocalVariableWriteNode
855
+ # `out = UNDEF` marks the cell missing, the same as `out[i] = UNDEF`
856
+ # does where the indices are written out. It is read before the
857
+ # right-hand side is built, since UNDEF is a mark and not a value.
858
+ if @whole_array && @array_names.include?(node.name) &&
859
+ undef_constant?(node.value)
860
+ record_array_rank(node.name, rank, node.location)
861
+ record_subscripts(node.name,
862
+ Array.new(rank) { |axis| [@outer_names[axis], 0] })
863
+ @written_arrays << node.name unless @written_arrays.include?(node.name)
864
+ @uses_undef = true
865
+ return MaskWrite.new(node.name, node.location)
866
+ end
867
+ expression = build(node.value)
868
+ # In the whole-array spelling every name in the block is a cell, so
869
+ # an assignment to a name that is an array outside writes that
870
+ # array's cell -- the one the loop is on, the same cell every read
871
+ # in the block is at. A name that is not an array is a local, as
872
+ # it is anywhere else.
873
+ if @whole_array && @array_names.include?(node.name)
874
+ return whole_array_write(node.name, expression, node.location)
875
+ end
876
+ @local_names << node.name unless @local_names.include?(node.name)
877
+ Assignment.new(node.name, expression, node.location)
878
+ when Prism::CallNode
879
+ if node.receiver.nil? && node.name == :printf
880
+ return build_print(node)
881
+ end
882
+ return build_raise(node) if node.receiver.nil? && node.name == :raise
883
+ # A call to a C function may stand alone. Its value is dropped, as
884
+ # Ruby drops it, and what it did is wherever its pointer parameters
885
+ # pointed -- which is the whole reason C has statements that are
886
+ # calls. Everything else keeps the refusal below: a computation
887
+ # standing where a statement stands is a line that does nothing.
888
+ if (call = recursive_call(node) || c_function_call(node))
889
+ @calls_for_effect = true
890
+ return CallStatement.new(call, node.location)
891
+ end
892
+ build_element_write(node)
893
+ when Prism::IfNode
894
+ build_branch(node)
895
+ when Prism::WhileNode
896
+ build_while(node)
897
+ when Prism::NextNode
898
+ build_loop_jump(node, LoopSkip, "next")
899
+ when Prism::BreakNode
900
+ build_loop_jump(node, LoopStop, "break")
901
+ else
902
+ raise Unsupported.new(
903
+ "a kernel body holds assignments, `if`, `while`, `(a...b).each` " \
904
+ "and `n.times` only, got #{node_name(node)}",
905
+ node.location)
906
+ end
907
+ end
908
+
909
+ # `next` skips the rest of this iteration, `break` leaves the loop. Both
910
+ # mean in the generated loop what they mean in the Ruby loop it replaces
911
+ # -- with one exception: Ruby's `break` in the kernel block is not a
912
+ # loop exit at all but a return from `jit_for` with a value, and the
913
+ # value is one this cannot produce. So `break` belongs to an inner
914
+ # loop, which is a loop in both languages.
915
+ def build_loop_jump (node, kind, word)
916
+ if node.arguments
917
+ raise Unsupported.new(
918
+ "`#{word}` here takes no value: the loop's own value is never " \
919
+ "used, so a value would be dropped",
920
+ node.location)
921
+ end
922
+ if kind == LoopStop && @loop_depth.zero?
923
+ raise Unsupported.new(
924
+ "`break` in the kernel block returns from `jit_for` in Ruby, " \
925
+ "with a value this cannot produce; it works inside a loop -- an " \
926
+ "`(a...b).each`, an `n.times` or a `while` -- and `next` skips " \
927
+ "the cell here",
928
+ node.location)
929
+ end
930
+ kind.new(node.location)
931
+ end
932
+
933
+ # `while cond ... end`. The loop whose end is not written down.
934
+ #
935
+ # It is the one construct here that can keep a kernel from returning,
936
+ # and there is no honest way to stop it from that: a bound the compiler
937
+ # invented would be a number nobody could choose -- the loops whose
938
+ # bound is knowable are already the bounded loop's, spelled
939
+ # `(0...cap).each` with a `break`. So what is offered is C's own
940
+ # bargain, the same one a `jit_function` that recurses too deep already
941
+ # takes: the loop does what it is written to do, and a condition that
942
+ # never goes false does not return. Worth knowing before writing one:
943
+ # the C loop has no interrupt check in it, so `Ctrl-C` does not reach a
944
+ # kernel that is running -- whether or not it holds the GVL -- and a
945
+ # runaway pass ends with a signal from another terminal.
946
+ #
947
+ # `begin ... end while` is refused rather than compiled as a do-while.
948
+ # Ruby's is the one loop in the language that tests after the body, and
949
+ # a reader who missed the `begin` would read the body's first pass as
950
+ # conditional when it is not. `while` with the test written first says
951
+ # the same thing where it can be seen.
952
+ def build_while (node)
953
+ if node.begin_modifier?
954
+ raise Unsupported.new(
955
+ "`begin ... end while` runs its body before the condition is " \
956
+ "ever read, which is not what `while` says anywhere else in " \
957
+ "Ruby; write the test at the top",
958
+ node.location)
959
+ end
960
+ condition = build(node.predicate)
961
+ @loop_depth += 1
962
+ statements = statements_of(node.statements).map { |inner|
963
+ build_statement(inner)
964
+ }
965
+ @loop_depth -= 1
966
+ # Whether a loop returns is not a question that can be answered in
967
+ # general, and nothing here pretends to answer it. This is the one
968
+ # case where it does not have to be: a condition written `true` is
969
+ # never going to be false, so the only way out is a `break` or a
970
+ # `raise`, and a body with neither cannot end. That is not a guess
971
+ # about the data -- it is the loop read as written -- so refusing it
972
+ # costs no legitimate program. `while true` with a `break` in it is
973
+ # a normal thing to write, and is left alone.
974
+ if literal_true?(node.predicate) && !can_leave?(statements)
975
+ raise Unsupported.new(
976
+ "this `while true` holds no `break` and no `raise`, so it cannot " \
977
+ "end; a kernel that does not return cannot be interrupted either, " \
978
+ "since `Ctrl-C` does not reach a running kernel",
979
+ node.location)
980
+ end
981
+ While.new(condition, statements, node.location)
982
+ end
983
+
984
+ def literal_true? (node)
985
+ unwrap(node).is_a?(Prism::TrueNode)
986
+ end
987
+
988
+ # Whether any statement of this loop's own body could leave it. A
989
+ # `break` in a loop nested inside leaves that one, not this one, so the
990
+ # walk goes into branches and stops at loops.
991
+ def can_leave? (statements)
992
+ statements.any? do |statement|
993
+ case statement
994
+ when LoopStop, Raise then true
995
+ when Branch
996
+ can_leave?(statement.consequent) || can_leave?(statement.alternative)
997
+ else false
998
+ end
999
+ end
1000
+ end
1001
+
1002
+ # `if` in statement position, where the branches write cells rather than
1003
+ # produce a value. Unlike the expression form, this one does not need an
1004
+ # `else`: a cell the kernel does not write keeps what it had.
1005
+ # `(from...to).each { |j| ... }` -- the loop a reduction runs in -- and
1006
+ # `n.times { |j| ... }`, which is that loop with both ends implied.
1007
+ #
1008
+ # `each` rather than `for` because its scoping is the one being
1009
+ # compiled: `for` would assign an enclosing variable of the same name
1010
+ # and leave the index bound afterwards, neither of which the generated
1011
+ # loop does. `times` has the scoping of `each` and needs no exception.
1012
+ def build_inner_loop (node)
1013
+ counted = node.name == :times
1014
+ range = unwrap(node.receiver)
1015
+ if !counted && !range.is_a?(Prism::RangeNode)
1016
+ raise Unsupported.new(
1017
+ "an inner loop runs over a range, as in `(0...n).each { |j| ... }`",
1018
+ node.location)
1019
+ end
1020
+ unless node.block && node.block.parameters
1021
+ raise Unsupported.new("an inner loop names its index", node.location)
1022
+ end
1023
+ requireds = node.block.parameters.parameters&.requireds || []
1024
+ unless requireds.size == 1
1025
+ raise Unsupported.new("an inner loop names one index", node.location)
1026
+ end
1027
+ index = requireds.first.name
1028
+ if index_in_scope?(index)
1029
+ raise Unsupported.new("`#{index}` is already an index here", node.location)
1030
+ end
1031
+ unless counted || (range.left && range.right)
1032
+ raise Unsupported.new("an inner loop needs both ends of its range",
1033
+ node.location)
1034
+ end
1035
+
1036
+ # `n.times` is `(0...n).each`: the count is the exclusive end, and it
1037
+ # is built from the same vocabulary a range end is, so a literal and a
1038
+ # captured integer both serve.
1039
+ if counted
1040
+ from = IntegerLiteral.new(0, node.location)
1041
+ to = build(node.receiver)
1042
+ else
1043
+ from = build(range.left)
1044
+ to = build(range.right)
1045
+ to = BinaryOperation.new(:+, to, IntegerLiteral.new(1, node.location),
1046
+ node.location) unless range.exclude_end?
1047
+ end
1048
+
1049
+ @inner_names << index
1050
+ @inner_names_seen << index
1051
+ @inner_ranges[index] = [from, to]
1052
+ @loop_depth += 1
1053
+ statements = statements_of(node.block.body).map { |inner|
1054
+ build_statement(inner)
1055
+ }
1056
+ @loop_depth -= 1
1057
+ @inner_names.pop
1058
+ InnerLoop.new(index, from, to, statements, node.location)
1059
+ end
1060
+
1061
+ def unwrap (node)
1062
+ return node unless node.is_a?(Prism::ParenthesesNode)
1063
+ body = node.body ? node.body.body : []
1064
+ body.size == 1 ? unwrap(body.first) : node
1065
+ end
1066
+
1067
+ def build_branch (node)
1068
+ alternative =
1069
+ case node.subsequent
1070
+ when nil then []
1071
+ when Prism::ElseNode
1072
+ statements_of(node.subsequent.statements).map { |inner|
1073
+ build_statement(inner)
1074
+ }
1075
+ when Prism::IfNode then [build_branch(node.subsequent)]
1076
+ else
1077
+ raise Unsupported.new("unsupported `if` continuation", node.location)
1078
+ end
1079
+ Branch.new(build(node.predicate),
1080
+ statements_of(node.statements).map { |inner|
1081
+ build_statement(inner)
1082
+ },
1083
+ alternative,
1084
+ node.location)
1085
+ end
1086
+
1087
+ def statements_of (node)
1088
+ node ? node.body : []
1089
+ end
1090
+
1091
+ def build_element_write (node)
1092
+ return build_inner_loop(node) if node.name == :each || node.name == :times
1093
+ unless node.name == :[]=
1094
+ # An expression standing alone in this spelling is a computation
1095
+ # nobody can see: there is no index to have written it against, and
1096
+ # nothing took its value.
1097
+ if @whole_array && !@map
1098
+ raise Unsupported.new(
1099
+ "this computes a value and puts it nowhere; assign it to an " \
1100
+ "array, as in `out = ...`, or ask for the value back with " \
1101
+ "`CArray.jit_map`",
1102
+ node.location)
1103
+ end
1104
+ raise Unsupported.new(
1105
+ "a kernel body holds assignments, `if`, `while`, `(a...b).each` " \
1106
+ "and `n.times` only, got a call to `#{node.name}`",
1107
+ node.location)
1108
+ end
1109
+ if (pointer = pointer_subscript(node, write: true))
1110
+ name, index = pointer
1111
+ arguments = node.arguments.arguments
1112
+ return PointerWrite.new(name, index, build(arguments.last),
1113
+ node.location)
1114
+ end
1115
+ array = array_name(node.receiver, node.location)
1116
+ arguments = node.arguments ? node.arguments.arguments : []
1117
+ # `out[] = ...` was how a block that had to run as Ruby said "the
1118
+ # whole array", `[]=` being the only spelling Ruby has for it. The
1119
+ # block is read rather than run, and every name in it is a cell, so
1120
+ # the assignment is Ruby's own: `out = ...`.
1121
+ if @whole_array && arguments.size == 1
1122
+ raise Unsupported.new(
1123
+ "`#{array}[] = ...` writes the cell the loop is on, which is what " \
1124
+ "`#{array} = ...` says; the block is read rather than run, so the " \
1125
+ "assignment is an ordinary one",
1126
+ node.location)
1127
+ end
1128
+ subscripts = read_subscripts(array, arguments[0..-2], node.location)
1129
+ # A kernel writes a cell its own indices reach -- or one it works out,
1130
+ # which is a scatter and is checked as it is reached rather than in
1131
+ # advance. What it may not do is write through an index that is not
1132
+ # the loop's: an inner index addresses reads only.
1133
+ #
1134
+ # In a contraction the left-hand side names which indices are free and
1135
+ # which are summed over, so it is checked there instead.
1136
+ unless @contract || walking_subscripts?(subscripts) ||
1137
+ computed_subscripts?(subscripts)
1138
+ raise Unsupported.new(
1139
+ "a kernel writes through its own indices, so `#{array}[...]` on " \
1140
+ "the left of `=` is addressed by #{@outer_names.join(', ')}, by a " \
1141
+ "position fixed before the loop runs -- or by a value the kernel " \
1142
+ "works out, which is a scatter",
1143
+ node.location)
1144
+ end
1145
+ record_subscripts(array, subscripts)
1146
+ @written_arrays << array unless @written_arrays.include?(array)
1147
+ if undef_constant?(arguments.last)
1148
+ @uses_undef = true
1149
+ return MaskWrite.new(array, node.location)
1150
+ end
1151
+ ElementWrite.new(array, build(arguments.last), node.location, subscripts)
1152
+ end
1153
+
1154
+ def build (node)
1155
+ case node
1156
+ when Prism::IntegerNode
1157
+ IntegerLiteral.new(node.value, node.location)
1158
+ when Prism::FloatNode
1159
+ FloatLiteral.new(node.value, node.location)
1160
+ when Prism::ImaginaryNode
1161
+ # `2i` is Complex(0, 2); its imaginary part is what the literal
1162
+ # spells, and may be an Integer or a Float.
1163
+ ImaginaryLiteral.new(node.value.imaginary, node.location)
1164
+ when Prism::TrueNode
1165
+ BooleanLiteral.new(true, node.location)
1166
+ when Prism::FalseNode
1167
+ BooleanLiteral.new(false, node.location)
1168
+ when Prism::ParenthesesNode
1169
+ inner = node.body ? node.body.body : []
1170
+ unless inner.size == 1
1171
+ raise Unsupported.new("parentheses must hold one expression", node.location)
1172
+ end
1173
+ build(inner.first)
1174
+ when Prism::LocalVariableReadNode
1175
+ build_name_read(node.name, node.location)
1176
+ when Prism::IfNode
1177
+ build_conditional(node)
1178
+ when Prism::AndNode
1179
+ LogicalOperation.new(:"&&", build(node.left), build(node.right),
1180
+ node.location)
1181
+ when Prism::OrNode
1182
+ LogicalOperation.new(:"||", build(node.left), build(node.right),
1183
+ node.location)
1184
+ when Prism::ConstantPathNode
1185
+ build_constant_path(node)
1186
+ when Prism::CallNode
1187
+ build_call(node)
1188
+ when Prism::ConstantReadNode
1189
+ if node.name == :UNDEF
1190
+ raise Unsupported.new(
1191
+ "UNDEF is not a number; write it as `a[i] == UNDEF` to test a " \
1192
+ "cell, or `a[i] = UNDEF` to mark one",
1193
+ node.location)
1194
+ end
1195
+ # A constant holds an array, a number or a function like any other
1196
+ # name -- and is the only name a method body can reach, since `def`
1197
+ # closes over nothing.
1198
+ build_name_read(node.name, node.location)
1199
+ else
1200
+ raise Unsupported.new("unsupported expression #{node_name(node)}",
1201
+ node.location)
1202
+ end
1203
+ end
1204
+
1205
+ # A name is an index, a local once assigned, an error if the block
1206
+ # assigns it only later, an array, or a captured scalar. The same rule
1207
+ # serves both entry points: parsed on its own a captured name arrives as
1208
+ # a receiverless call, while in the file it was written in Prism
1209
+ # resolves it to the enclosing scope's local.
1210
+ def build_name_read (name, location)
1211
+ axis = @outer_names.index(name)
1212
+ return IndexVariable.new(name, axis, location) if axis
1213
+ return IndexVariable.new(name, nil, location) if @inner_names.include?(name)
1214
+ if @local_names.include?(name)
1215
+ return LocalRead.new(name, location)
1216
+ end
1217
+ if @cell_names.include?(name)
1218
+ return cell_read(name, location)
1219
+ end
1220
+ if @array_names.include?(name)
1221
+ # Every name in this spelling is a cell: the loop is the compiler's
1222
+ # and is not written in the block, so `a + b * c` reads three cells
1223
+ # and computes one. What the same expression means in CArray is
1224
+ # what it adds up to over the whole array -- in one pass here,
1225
+ # instead of three with two arrays in between.
1226
+ # And a stencil reads a captured array the same way: the windows
1227
+ # are what reach, so a name that is not one is the cell the loop is
1228
+ # on -- `a[0, 0]` spelled the way a block with no indices spells it.
1229
+ if @whole_array || @windows.any?
1230
+ subscripts = Array.new(rank) { |axis| [@outer_names[axis], 0] }
1231
+ record_array_rank(name, rank, location)
1232
+ record_subscripts(name, subscripts)
1233
+ return ElementRead.new(name, subscripts, location)
1234
+ end
1235
+ raise Unsupported.new(
1236
+ "`#{name}` is an array; index it, as in `#{name}[#{@index_names.first}]`",
1237
+ location)
1238
+ end
1239
+ if @assigned_names.include?(name)
1240
+ raise Unsupported.new("`#{name}` is read before it is assigned", location)
1241
+ end
1242
+ @scalar_names << name unless @scalar_names.include?(name)
1243
+ CaptureRead.new(name, location)
1244
+ end
1245
+
1246
+ def build_conditional (node)
1247
+ unless node.subsequent
1248
+ raise Unsupported.new("`if` without `else` has no value for every cell",
1249
+ node.location)
1250
+ end
1251
+ unless node.subsequent.is_a?(Prism::ElseNode)
1252
+ raise Unsupported.new("`elsif` is not supported", node.location)
1253
+ end
1254
+ Conditional.new(build(node.predicate),
1255
+ build_single(node.statements, node.location, "if"),
1256
+ build_single(node.subsequent.statements, node.location, "else"),
1257
+ node.location)
1258
+ end
1259
+
1260
+ def build_single (statements, location, what)
1261
+ body = statements ? statements.body : []
1262
+ unless body.size == 1
1263
+ raise Unsupported.new("the `#{what}` branch must be a single expression",
1264
+ location)
1265
+ end
1266
+ build(body.first)
1267
+ end
1268
+
1269
+ # `f.call(x)`, `f.(x)` -- which Prism also spells `call` -- and `f[x]`,
1270
+ # after Proc. All three are real Ruby that computes the same thing when
1271
+ # the block is run rather than compiled, which is the property the rest
1272
+ # of the kernel grammar keeps too.
1273
+ C_FUNCTION_CALL_NAMES = [:call, :[]].freeze
1274
+
1275
+ def build_call (node)
1276
+ if node.receiver.nil? && node.arguments.nil? && node.block.nil?
1277
+ return build_name_read(node.name, node.location)
1278
+ end
1279
+ if (recursive = recursive_call(node))
1280
+ return recursive
1281
+ end
1282
+ if (c_function = c_function_call(node))
1283
+ return c_function
1284
+ end
1285
+ return build_complex(node) if node.receiver.nil? && node.name == :Complex
1286
+ if node.block
1287
+ raise Unsupported.new("block arguments are not supported", node.location)
1288
+ end
1289
+ return build_element_read(node) if node.name == :[]
1290
+
1291
+ if node.receiver.is_a?(Prism::ConstantReadNode) && node.receiver.name == :Math
1292
+ return build_math_call(node)
1293
+ end
1294
+
1295
+ arguments = node.arguments ? node.arguments.arguments : []
1296
+
1297
+ if node.name == :-@ && arguments.empty?
1298
+ return UnaryMinus.new(build(node.receiver), node.location)
1299
+ end
1300
+ if node.name == :+@ && arguments.empty?
1301
+ return build(node.receiver)
1302
+ end
1303
+ if node.name == :! && arguments.empty?
1304
+ return LogicalNot.new(build(node.receiver), node.location)
1305
+ end
1306
+ if node.name == :abs && arguments.empty?
1307
+ return AbsoluteValue.new(build(node.receiver), node.location)
1308
+ end
1309
+ if arguments.empty? && (part = COMPLEX_PARTS[node.name])
1310
+ return ComplexPart.new(part, node.name, build(node.receiver),
1311
+ node.location)
1312
+ end
1313
+ if arguments.empty? && (conversion = CONVERSIONS[node.name])
1314
+ type, function = conversion
1315
+ return Conversion.new(function, build(node.receiver), type, node.location)
1316
+ end
1317
+ if node.name == :** && arguments.size == 1
1318
+ return build_power(node.receiver, arguments.first, node.location)
1319
+ end
1320
+ if arguments.empty?
1321
+ if (function = postfix_math(node.name))
1322
+ return MathCall.new(function, [build(node.receiver)], node.location)
1323
+ end
1324
+ if (reason = REFUSED_POSTFIX[node.name])
1325
+ raise Unsupported.new("`.#{node.name}` is not compiled: #{reason}",
1326
+ node.location)
1327
+ end
1328
+ end
1329
+ if [:==, :!=].include?(node.name) && arguments.size == 1
1330
+ # Either way round: `a[i] == UNDEF` and `UNDEF == a[i]` ask the same
1331
+ # question, and Ruby lets both be written.
1332
+ if undef_constant?(arguments.first)
1333
+ return build_mask_test(node.receiver, node.name == :!=, node.location)
1334
+ end
1335
+ if undef_constant?(node.receiver)
1336
+ return build_mask_test(arguments.first, node.name == :!=, node.location)
1337
+ end
1338
+ end
1339
+ if node.name == :~ && arguments.empty?
1340
+ return BitwiseNot.new(build(node.receiver), node.location)
1341
+ end
1342
+ if (ARITHMETIC_OPERATORS + COMPARISON_OPERATORS +
1343
+ BIT_OPERATORS).include?(node.name)
1344
+ unless arguments.size == 1
1345
+ raise Unsupported.new("`#{node.name}` takes one operand", node.location)
1346
+ end
1347
+ return BinaryOperation.new(node.name, build(node.receiver),
1348
+ build(arguments.first), node.location)
1349
+ end
1350
+
1351
+ if raise_call?(node)
1352
+ raise Unsupported.new(
1353
+ "`raise` is a statement here, not a value: write it on its own, " \
1354
+ "as in `raise \"x is 0\" if x == 0`",
1355
+ node.location)
1356
+ end
1357
+ raise Unsupported.new("unsupported method `#{node.name}`", node.location)
1358
+ end
1359
+
1360
+ def build_constant_path (node)
1361
+ # Math is answered here, whether or not the answer exists: a name it
1362
+ # does not have is not something to go looking for outside.
1363
+ if node.parent.is_a?(Prism::ConstantReadNode) && node.parent.name == :Math
1364
+ if MATH_CONSTANTS.key?(node.name)
1365
+ return FloatLiteral.new(MATH_CONSTANTS.fetch(node.name), node.location)
1366
+ end
1367
+ raise Unsupported.new("unsupported constant #{node.slice}", node.location)
1368
+ end
1369
+ # Any other path names one thing, the way a plain constant does.
1370
+ build_name_read(node.slice.to_sym, node.location)
1371
+ end
1372
+
1373
+ def subscripted? (node)
1374
+ node.is_a?(Prism::CallNode) && node.name == :[]
1375
+ end
1376
+
1377
+ def undef_constant? (node)
1378
+ node.is_a?(Prism::ConstantReadNode) && node.name == :UNDEF
1379
+ end
1380
+
1381
+ # `a[i] == UNDEF` asks whether the cell is missing, which is a question
1382
+ # about the mask. Only a cell can be asked.
1383
+ def build_mask_test (receiver, negated, location)
1384
+ # In an element kernel the name is already the cell -- the loop is
1385
+ # the compiler's and is not written in the block -- so `a == UNDEF`
1386
+ # asks about the same cell `a` reads. `a[] == UNDEF` says it the
1387
+ # longer way and is read below, with the indexed spellings.
1388
+ if @whole_array && !subscripted?(receiver)
1389
+ name = captured_name(receiver)
1390
+ unless name && @array_names.include?(name)
1391
+ raise Unsupported.new(
1392
+ "only a cell can be compared with UNDEF, as in `a == UNDEF`",
1393
+ location)
1394
+ end
1395
+ subscripts = Array.new(rank) { |axis| [@outer_names[axis], 0] }
1396
+ record_array_rank(name, rank, location)
1397
+ record_subscripts(name, subscripts)
1398
+ @uses_undef = true
1399
+ return MaskTest.new(name, subscripts, negated, location)
1400
+ end
1401
+ unless subscripted?(receiver)
1402
+ raise Unsupported.new(
1403
+ "only a cell can be compared with UNDEF, as in `a[i] == UNDEF`",
1404
+ location)
1405
+ end
1406
+ array = array_name(receiver.receiver, location)
1407
+ array_for_test = array
1408
+ subscripts = read_subscripts(array_for_test,
1409
+ receiver.arguments ? receiver.arguments.arguments : [],
1410
+ location)
1411
+ # Recorded like any other read, because the cell still has to exist
1412
+ # and still has to have been settled before it is asked about. What
1413
+ # it does not do is feed the value-mask propagation, which is decided
1414
+ # separately: a mask test never enters the value's mask.
1415
+ record_subscripts(array, subscripts)
1416
+ @uses_undef = true
1417
+ MaskTest.new(array, subscripts, negated, location)
1418
+ end
1419
+
1420
+ # `[name, index]` when this subscripts a pointer parameter, nil when it
1421
+ # does not. A pointer declared const is refused on the left rather than
1422
+ # silently written through: the declaration is a promise to the caller,
1423
+ # not decoration.
1424
+ def pointer_subscript (node, write: false)
1425
+ name = captured_name(node.receiver)
1426
+ return nil unless name && @pointers.key?(name)
1427
+ case @pointers.fetch(name)
1428
+ when nil
1429
+ raise Unsupported.new(
1430
+ "`#{name}` points at nothing in particular, so there is no cell " \
1431
+ "for `#{name}[...]` to reach; declare what it points at",
1432
+ node.location)
1433
+ when false
1434
+ if write
1435
+ raise Unsupported.new(
1436
+ "`#{name}` is declared const, so the function may read it but " \
1437
+ "not write through it", node.location)
1438
+ end
1439
+ end
1440
+ arguments = node.arguments ? node.arguments.arguments : []
1441
+ arguments = arguments[0..-2] if write
1442
+ unless arguments.size == 1
1443
+ raise Unsupported.new(
1444
+ "`#{name}` is a pointer, so it takes one index", node.location)
1445
+ end
1446
+ @pointer_names << name unless @pointer_names.include?(name)
1447
+ [name, build(arguments.first)]
1448
+ end
1449
+
1450
+ # `fact(n - 1)` inside the body of `double fact(double)`, and
1451
+ # `fact.call(n - 1)` for whoever prefers the spelling a captured
1452
+ # function takes. Returns nil when this is not that call.
1453
+ #
1454
+ # It is not a capture: nothing outside the function is reached, and the
1455
+ # compiled object still references no Ruby value. The name is in the
1456
+ # declaration the caller wrote, which is where C would have put it too.
1457
+ def recursive_call (node)
1458
+ return nil unless @recursion
1459
+ name, parameters, result_type = @recursion
1460
+ # `fact.call(n - 1)`, the spelling every other C function takes. A
1461
+ # bare `fact(n - 1)` would read better as C and is refused all the
1462
+ # same: it is not Ruby, and the block has to stay runnable, since
1463
+ # running it beside the compiled function is how the two are checked
1464
+ # against each other.
1465
+ if node.receiver.nil? && node.name == name && node.arguments
1466
+ raise Unsupported.new(
1467
+ "`#{name}` calls itself the way any C function is called here, " \
1468
+ "as `#{name}.call(...)` -- a bare `#{name}(...)` is not Ruby, and " \
1469
+ "the block has to stay runnable",
1470
+ node.location)
1471
+ end
1472
+ return nil unless C_FUNCTION_CALL_NAMES.include?(node.name) &&
1473
+ captured_name(node.receiver) == name
1474
+ return nil if node.block
1475
+ arguments = node.arguments ? node.arguments.arguments : []
1476
+ unless arguments.size == parameters.size
1477
+ raise Unsupported.new(
1478
+ "`#{name}` takes #{parameters.size} " \
1479
+ "#{parameters.size == 1 ? 'argument' : 'arguments'}; " \
1480
+ "#{arguments.size} #{arguments.size == 1 ? 'was' : 'were'} given",
1481
+ node.location)
1482
+ end
1483
+ built = arguments.each_with_index.map { |argument, position|
1484
+ build_c_function_argument(argument, parameters[position], name)
1485
+ }
1486
+ RecursiveCall.new(name, built, parameters, result_type, node.location)
1487
+ end
1488
+
1489
+ # Returns the node for a call on a captured C function, or nil when this
1490
+ # is not one.
1491
+ def c_function_call (node)
1492
+ return nil unless C_FUNCTION_CALL_NAMES.include?(node.name)
1493
+ name = captured_name(node.receiver)
1494
+ return nil unless name && @c_functions.key?(name)
1495
+ c_function = @c_functions.fetch(name)
1496
+ arguments = node.arguments ? node.arguments.arguments : []
1497
+ unless arguments.size == c_function.arity
1498
+ raise Unsupported.new(
1499
+ "`#{name}` is `#{c_function}`, so it takes #{c_function.arity} " \
1500
+ "#{c_function.arity == 1 ? 'argument' : 'arguments'}; " \
1501
+ "#{arguments.size} #{arguments.size == 1 ? 'was' : 'were'} given",
1502
+ node.location)
1503
+ end
1504
+ @c_function_names << name unless @c_function_names.include?(name)
1505
+ built = arguments.each_with_index.map { |argument, position|
1506
+ build_c_function_argument(argument, c_function.parameters[position], name)
1507
+ }
1508
+ CFunctionCall.new(name, built, node.location)
1509
+ end
1510
+
1511
+ # A parameter that points at numbers takes an array, not a cell: the
1512
+ # kernel hands over the address and the function reaches the cells
1513
+ # itself. Which it is comes from the declaration, so the same captured
1514
+ # name means a cell in one argument position and the whole array in
1515
+ # another -- `poly.call(x[i], coef)` says both.
1516
+ def build_c_function_argument (argument, parameter, c_function)
1517
+ # Inside a function, one of its own pointer parameters is already the
1518
+ # address the callee wants, and handing it on is what C does -- for a
1519
+ # `void *` slot as much as for a run of numbers, since neither is
1520
+ # read here. It is emitted as the bare name, which is what the C
1521
+ # parameter is called.
1522
+ if parameter&.pointer
1523
+ name = captured_name(argument)
1524
+ if name && @pointers.key?(name)
1525
+ @pointer_names << name unless @pointer_names.include?(name)
1526
+ return ArrayAddress.new(name, argument.location)
1527
+ end
1528
+ end
1529
+ return build(argument) unless parameter&.indexable?
1530
+ name = captured_name(argument)
1531
+ unless name && @array_names.include?(name)
1532
+ raise Unsupported.new(
1533
+ "`#{c_function}` takes `#{parameter.text}` there, which is an array; " \
1534
+ "pass one by name",
1535
+ argument.location)
1536
+ end
1537
+ @address_arrays << name unless @address_arrays.include?(name)
1538
+ # What the declaration promised about it, kept so the caller can hold
1539
+ # the array to it: which type it points at, whether it may be written
1540
+ # through, and how long it said it was.
1541
+ (@address_parameters[name] ||= []) << parameter
1542
+ ArrayAddress.new(name, argument.location)
1543
+ end
1544
+
1545
+ # A free name, however Prism spelled it: a block parsed on its own sees
1546
+ # a local it does not know as a method call with no receiver.
1547
+ def captured_name (receiver)
1548
+ case receiver
1549
+ when Prism::LocalVariableReadNode then receiver.name
1550
+ when Prism::ConstantReadNode then receiver.name
1551
+ when Prism::ConstantPathNode then receiver.slice.to_sym
1552
+ when Prism::CallNode
1553
+ receiver.name if receiver.receiver.nil? && receiver.arguments.nil? &&
1554
+ receiver.block.nil?
1555
+ end
1556
+ end
1557
+
1558
+ # `out = ...` in the whole-array spelling. The cell is the loop's own,
1559
+ # which is what makes this an assignment rather than a subscript: there
1560
+ # is no other cell it could mean.
1561
+ def whole_array_write (name, expression, location)
1562
+ subscripts = Array.new(rank) { |axis| [@outer_names[axis], 0] }
1563
+ record_array_rank(name, rank, location)
1564
+ record_subscripts(name, subscripts)
1565
+ @written_arrays << name unless @written_arrays.include?(name)
1566
+ ElementWrite.new(name, expression, location, subscripts)
1567
+ end
1568
+
1569
+ def build_element_read (node)
1570
+ if (pointer = pointer_subscript(node))
1571
+ return PointerRead.new(pointer.first, pointer.last, node.location)
1572
+ end
1573
+ array = array_name(node.receiver, node.location)
1574
+ arguments = node.arguments ? node.arguments.arguments : []
1575
+ subscripts = read_subscripts(array, arguments, node.location)
1576
+ record_subscripts(array, subscripts)
1577
+ ElementRead.new(array, subscripts, node.location)
1578
+ end
1579
+
1580
+ def record_subscripts (array, subscripts)
1581
+ @subscripts[array] << subscripts unless @subscripts[array].include?(subscripts)
1582
+ end
1583
+
1584
+ def array_name (receiver, location)
1585
+ name = captured_name(receiver)
1586
+ unless name && @array_names.include?(name)
1587
+ raise Unsupported.new("only a captured CArray may be indexed", location)
1588
+ end
1589
+ name
1590
+ end
1591
+
1592
+ # One [index name, offset] per axis of the array. Which index addresses
1593
+ # which axis is the caller's choice, so a reduction can run an inner
1594
+ # index down one axis while an outer one holds the others.
1595
+ def read_subscripts (array, arguments, location)
1596
+ return window_subscripts(array, arguments, location) if @windows.include?(array)
1597
+ # An array the block closed over rather than was given is read at the
1598
+ # cell the loop is on, which is what a bare name means everywhere a
1599
+ # loop is this compiler's.
1600
+ if @windows.any? && arguments.empty? && !@cell_names.include?(array)
1601
+ record_array_rank(array, rank, location)
1602
+ return Array.new(rank) { |axis| [@outer_names[axis], 0] }
1603
+ end
1604
+ if @whole_array
1605
+ unless arguments.empty?
1606
+ raise Unsupported.new(
1607
+ "this block takes no indices, so an array is spelled `a[]`",
1608
+ location)
1609
+ end
1610
+ return Array.new(rank) { |axis| [@outer_names[axis], 0] }
1611
+ end
1612
+ if arguments.empty?
1613
+ return cell_subscripts(array, location) if @cell_names.include?(array)
1614
+ raise Unsupported.new("`#{array}` needs an index for each of its axes",
1615
+ location)
1616
+ end
1617
+ record_array_rank(array, arguments.size, location)
1618
+ arguments.map { |argument| read_subscript(argument, location) }
1619
+ end
1620
+
1621
+ # `a[-1, 1]`: one offset per axis, written out. Written out because the
1622
+ # offsets are what the radius is read from, and the radius is what lets
1623
+ # the interior be walked without asking, at every cell, whether it is
1624
+ # still inside. A computed subscript is a different thing and has a
1625
+ # different spelling -- it is `jit_for`'s.
1626
+ def window_subscripts (array, arguments, location)
1627
+ if arguments.size != rank
1628
+ raise Unsupported.new(
1629
+ "`#{array}` is a window onto a rank-#{rank} array, so it takes " \
1630
+ "#{rank} #{rank == 1 ? 'offset' : 'offsets'}: `#{array}[0" \
1631
+ "#{', 0' * (rank - 1)}]` is the cell itself",
1632
+ location)
1633
+ end
1634
+ record_array_rank(array, arguments.size, location)
1635
+ arguments.each_with_index.map { |argument, axis|
1636
+ offset = literal_integer(argument)
1637
+ unless offset
1638
+ raise Unsupported.new(
1639
+ "a window's offsets are written out, as in `#{array}[-1, 1]`; " \
1640
+ "a subscript the kernel works out is `jit_for`'s",
1641
+ location)
1642
+ end
1643
+ @window_reach[axis] = [[@window_reach[axis][0], offset].min,
1644
+ [@window_reach[axis][1], offset].max]
1645
+ [@outer_names[axis], offset]
1646
+ }
1647
+ end
1648
+
1649
+ def record_array_rank (array, count, location = nil)
1650
+ known = @array_ranks[array]
1651
+ if known && known != count
1652
+ raise Unsupported.new(
1653
+ "`#{array}` is indexed with #{known} " \
1654
+ "#{known == 1 ? 'index' : 'indices'} in one place and #{count} in " \
1655
+ "another",
1656
+ location)
1657
+ end
1658
+ @array_ranks[array] = count
1659
+ end
1660
+
1661
+ # An index expression is `j`, `j + c` or `j - c`, where `j` is any index
1662
+ # in scope, so that the offset is a compile-time constant.
1663
+ def read_subscript (node, location)
1664
+ if node.is_a?(Prism::LocalVariableReadNode) && index_in_scope?(node.name)
1665
+ return [node.name, 0]
1666
+ end
1667
+ if node.is_a?(Prism::CallNode) && [:+, :-].include?(node.name) &&
1668
+ node.receiver.is_a?(Prism::LocalVariableReadNode) &&
1669
+ index_in_scope?(node.receiver.name)
1670
+ return walked_subscript(node)
1671
+ end
1672
+ # Anything else pins the axis at a position the loop does not walk:
1673
+ # `a[i, 0]`, or `a[row, k]` where `row` is an integer the block closed
1674
+ # over. It is an argument to the kernel like any other scalar, so one
1675
+ # compiled kernel serves every value of it.
1676
+ [nil, pinned_subscript(node)]
1677
+ end
1678
+
1679
+ def pinned_subscript (node)
1680
+ build(node)
1681
+ end
1682
+
1683
+ # The one cell a CScalar has: position zero on its one axis, which is a
1684
+ # pinned subscript like any other and is checked like one.
1685
+ def cell_subscripts (array, location)
1686
+ record_array_rank(array, 1, location)
1687
+ [[nil, IntegerLiteral.new(0, location)]]
1688
+ end
1689
+
1690
+ def cell_read (array, location)
1691
+ subscripts = cell_subscripts(array, location)
1692
+ record_subscripts(array, subscripts)
1693
+ ElementRead.new(array, subscripts, location)
1694
+ end
1695
+
1696
+ # A subscript that does not walk with the loop is *fixed* when its value
1697
+ # can be worked out before the kernel runs -- a literal, a captured
1698
+ # integer, arithmetic over those. Then it joins the checks that happen
1699
+ # in advance: it is part of the box a view has to transfer, and reaching
1700
+ # outside the array is a message rather than a read.
1701
+ #
1702
+ # Anything else is *dynamic*: `a[b[i]]`, or an index the body computed.
1703
+ # Its value is not knowable until the cell is reached, so the check
1704
+ # moves to the access itself.
1705
+ def self.fixed_subscript? (node)
1706
+ case node
1707
+ when IntegerLiteral, CaptureRead then true
1708
+ when UnaryMinus then fixed_subscript?(node.operand)
1709
+ when BinaryOperation
1710
+ [:+, :-, :*].include?(node.operator) &&
1711
+ fixed_subscript?(node.left) && fixed_subscript?(node.right)
1712
+ else false
1713
+ end
1714
+ end
1715
+
1716
+
1717
+ # `j + c` or `j - c`, where c is a literal or an integer built from
1718
+ # literals and captured integers -- `a[i - window]` for a window the
1719
+ # caller chooses. A captured offset reaches the kernel as an argument,
1720
+ # so one compiled kernel serves every value of it, and the judgements
1721
+ # that need the value (which way the axis runs, and whether the offset
1722
+ # is a dependency at all under this extent's step) are made when the
1723
+ # kernel is called, where the value is known, alongside the bounds check.
1724
+ def walked_subscript (node)
1725
+ receiver = node.receiver
1726
+ arguments = node.arguments ? node.arguments.arguments : []
1727
+ unless arguments.size == 1
1728
+ raise Unsupported.new("an index offset takes one argument",
1729
+ node.location)
1730
+ end
1731
+ argument = arguments.first
1732
+ if argument.is_a?(Prism::IntegerNode)
1733
+ constant = argument.value
1734
+ if constant < 0
1735
+ raise Unsupported.new("write the offset as `j - c` with c >= 0",
1736
+ node.location)
1737
+ end
1738
+ return [receiver.name, node.name == :+ ? constant : -constant]
1739
+ end
1740
+ offset = pinned_subscript(argument)
1741
+ [receiver.name, node.name == :+ ? offset : UnaryMinus.new(offset)]
1742
+ end
1743
+
1744
+ def index_in_scope? (name)
1745
+ @outer_names.include?(name) || @inner_names.include?(name)
1746
+ end
1747
+
1748
+ def available_indices
1749
+ (@outer_names + @inner_names).map { |name| "`#{name}`" }.join(", ")
1750
+ end
1751
+
1752
+ # Index expressions are limited to `i`, `i + c` and `i - c` so that the
1753
+ # dependency offset is a compile-time constant.
1754
+ def read_offset (node, axis)
1755
+ expected = @index_names[axis]
1756
+ if node.is_a?(Prism::LocalVariableReadNode) && node.name == expected
1757
+ return 0
1758
+ end
1759
+ unless node.is_a?(Prism::CallNode) && [:+, :-].include?(node.name)
1760
+ raise Unsupported.new(
1761
+ "index #{axis} must be `#{expected}`, `#{expected} + c` or " \
1762
+ "`#{expected} - c`", node.location)
1763
+ end
1764
+ receiver = node.receiver
1765
+ unless receiver.is_a?(Prism::LocalVariableReadNode) && receiver.name == expected
1766
+ raise Unsupported.new("index #{axis} must start from `#{expected}`",
1767
+ node.location)
1768
+ end
1769
+ arguments = node.arguments ? node.arguments.arguments : []
1770
+ unless arguments.size == 1 && arguments.first.is_a?(Prism::IntegerNode)
1771
+ raise Unsupported.new("an index offset must be an integer literal",
1772
+ node.location)
1773
+ end
1774
+ constant = arguments.first.value
1775
+ if constant < 0
1776
+ raise Unsupported.new("write the offset as `#{expected} - c` with c >= 0",
1777
+ node.location)
1778
+ end
1779
+ node.name == :+ ? constant : -constant
1780
+ end
1781
+
1782
+ # `Complex(x, y)`, whose arguments are the parts, and `Complex(x)`,
1783
+ # which is taken for `Complex(x, 0.0)`.
1784
+ #
1785
+ # In Ruby the shorter one is not quite the longer one: its imaginary
1786
+ # part is an exact Integer zero, and `f_add` returns the other operand
1787
+ # untouched rather than adding that zero to it, so
1788
+ # `Complex(1.0) + Complex(2.0, -0.0)` keeps the sign of a zero that
1789
+ # `Complex(1.0, 0.0) + Complex(2.0, -0.0)` loses. Nothing else tells
1790
+ # them apart -- multiplication and division agree, infinities and all --
1791
+ # and carrying an exactly-zero imaginary part through the type lattice
1792
+ # to reproduce that one case is not worth what it would cost to read.
1793
+ def build_complex (node)
1794
+ arguments = node.arguments ? node.arguments.arguments : []
1795
+ unless (1..2).cover?(arguments.size)
1796
+ raise Unsupported.new(
1797
+ "`Complex` takes one part or two, and got #{arguments.size}",
1798
+ node.location)
1799
+ end
1800
+ imaginary = arguments.size == 2 ? build(arguments.last)
1801
+ : FloatLiteral.new(0.0, node.location)
1802
+ ComplexBuild.new(build(arguments.first), imaginary, node.location)
1803
+ end
1804
+
1805
+ def postfix_math (name)
1806
+ return nil unless POSTFIX_NAMES.include?(name)
1807
+ MATH_FUNCTIONS[name]
1808
+ end
1809
+
1810
+ # Ruby's Integer ** Integer is exact and unbounded, which int64 is not.
1811
+ # A non-negative literal exponent can still be squared out; anything
1812
+ # else has to be asked for in floating point.
1813
+ def build_power (receiver, exponent, location)
1814
+ base = build(receiver)
1815
+ power = build(exponent)
1816
+ if power.is_a?(IntegerLiteral) && power.value < 0
1817
+ raise Unsupported.new(
1818
+ "a negative exponent gives a Rational in Ruby; write `1.0 / x ** n`",
1819
+ location)
1820
+ end
1821
+ Power.new(base, power, location)
1822
+ end
1823
+
1824
+ def build_math_call (node)
1825
+ function = MATH_FUNCTIONS[node.name]
1826
+ unless function
1827
+ raise Unsupported.new("Math.#{node.name} has no math.h counterpart",
1828
+ node.location)
1829
+ end
1830
+ arguments = node.arguments ? node.arguments.arguments : []
1831
+ expected = [:atan2, :hypot].include?(node.name) ? 2 : 1
1832
+ unless arguments.size == expected
1833
+ raise Unsupported.new("Math.#{node.name} takes #{expected} argument(s)",
1834
+ node.location)
1835
+ end
1836
+ MathCall.new(function, arguments.map { |argument| build(argument) },
1837
+ node.location)
1838
+ end
1839
+
1840
+ def node_name (node)
1841
+ node.class.name.split("::").last.sub(/Node\z/, "")
1842
+ end
1843
+
1844
+ end
1845
+
1846
+ end
1847
+ end