carray-jit 0.1.2 → 0.1.3

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +771 -3
  3. data/README.md +7 -6
  4. data/carray-jit.gemspec +1 -3
  5. data/docs/00_Introduction.md +4 -3
  6. data/docs/01_GettingStarted.md +1 -1
  7. data/docs/02_KernelShapes.md +93 -14
  8. data/docs/03_SupportedFeatures.md +582 -26
  9. data/docs/04_Compiling.md +33 -6
  10. data/docs/05_DesignNotes.md +3 -3
  11. data/docs/06_Cheatsheet.md +198 -5
  12. data/docs/07_StepByStep.ja.md +534 -0
  13. data/docs/07_StepByStep.md +535 -0
  14. data/examples/README.md +12 -0
  15. data/examples/applications/alarm.rb +121 -0
  16. data/examples/applications/collatz.rb +105 -0
  17. data/examples/applications/cubic_spline.rb +331 -0
  18. data/examples/applications/dithering.rb +144 -0
  19. data/examples/applications/group_stats.rb +115 -0
  20. data/examples/applications/lookup.rb +126 -0
  21. data/examples/applications/median_filter.rb +153 -0
  22. data/examples/applications/parcel_ascent.rb +220 -0
  23. data/examples/applications/point_in_polygon.rb +111 -0
  24. data/examples/applications/random_walk.rb +98 -0
  25. data/examples/applications/van_der_pol.rb +186 -0
  26. data/examples/applications/wet_bulb.rb +140 -0
  27. data/examples/features/10_complex.rb +14 -4
  28. data/examples/features/15_loops.rb +7 -1
  29. data/lib/carray/jit/access.rb +14 -0
  30. data/lib/carray/jit/analyzer.rb +2077 -136
  31. data/lib/carray/jit/block_reader.rb +37 -6
  32. data/lib/carray/jit/c_function.rb +613 -76
  33. data/lib/carray/jit/c_generator.rb +1595 -156
  34. data/lib/carray/jit/call.rb +68 -0
  35. data/lib/carray/jit/compiler.rb +75 -11
  36. data/lib/carray/jit/kernel.rb +369 -32
  37. data/lib/carray/jit/node.rb +359 -9
  38. data/lib/carray/jit/sorting_networks.rb +182 -0
  39. data/lib/carray/jit/type_assignment.rb +371 -34
  40. data/lib/carray/jit/version.rb +1 -1
  41. data/lib/carray/jit.rb +560 -64
  42. metadata +22 -8
  43. data/ext/carray_jit_access/carray_jit_access.c +0 -460
  44. data/ext/carray_jit_access/extconf.rb +0 -8
@@ -12,6 +12,96 @@ class CArray
12
12
  # so the caller passes them in -- it knows, because it has the values.
13
13
  class Analyzer
14
14
 
15
+ # ---- the vocabulary a local array is written in ----
16
+
17
+ # The types a body may make an array of: CArray's own constructor names,
18
+ # each naming the storage type CArray gives it. Taken from CArray
19
+ # rather than invented here, which is why the aliases land where they
20
+ # do -- `CArray.float` is a float32 and `CArray.complex` is a cmplx64,
21
+ # the opposite of what Ruby's own Float and Complex would suggest.
22
+ LOCAL_ARRAY_TYPES = {
23
+ :int8 => "int8", :int16 => "int16",
24
+ :int32 => "int32", :int64 => "int64",
25
+ :uint8 => "uint8", :uint16 => "uint16",
26
+ :uint32 => "uint32", :uint64 => "uint64",
27
+ :float32 => "float32", :float64 => "float64",
28
+ :cmplx64 => "cmplx64", :cmplx128 => "cmplx128",
29
+ :boolean => "boolean",
30
+ # The aliases, as CArray spells them.
31
+ :float => "float32", :double => "float64",
32
+ :complex => "cmplx64", :dcomplex => "cmplx128",
33
+ :byte => "uint8", :short => "int16",
34
+ :int => "int32",
35
+ }.freeze
36
+
37
+ # What one cell of each takes, which is what the limits below are
38
+ # counted in. Beside the table above so that a type added to one is
39
+ # visibly missing from the other.
40
+ LOCAL_ARRAY_STORAGE_BYTES = {
41
+ "int8" => 1, "int16" => 2, "int32" => 4, "int64" => 8,
42
+ "uint8" => 1, "uint16" => 2, "uint32" => 4, "uint64" => 8,
43
+ "float32" => 4, "float64" => 8,
44
+ "cmplx64" => 8, "cmplx128" => 16,
45
+ "boolean" => 1,
46
+ }.freeze
47
+
48
+ # How much of a local array a stack frame is asked to hold. Past
49
+ # either number the array is allocated at the kernel's entry instead --
50
+ # so these say where the array goes rather than whether it is allowed,
51
+ # and nothing is refused for its size.
52
+ #
53
+ # Both are provisional: what a Ruby thread's stack actually is on each
54
+ # platform has not been measured, and until it has, these are a guess
55
+ # with room under it. They are in one place so that measuring moves
56
+ # one number rather than several. What the total does not count is in
57
+ # docs: a pasted function's own arrays, recursion, and whatever a
58
+ # future thread pool gives its threads.
59
+ LOCAL_ARRAY_BYTE_LIMIT = 4 * 1024
60
+ LOCAL_ARRAY_TOTAL_BYTE_LIMIT = 16 * 1024
61
+
62
+ # The types CArray has that a kernel computes with neither of, and what
63
+ # each of them holds instead -- which is what the refusal says.
64
+ LOCAL_ARRAY_REFUSED_TYPES_HOLD = {
65
+ :object => "Ruby objects",
66
+ :fixlen => "fixed-length byte strings",
67
+ }.freeze
68
+ LOCAL_ARRAY_REFUSED_TYPES = LOCAL_ARRAY_REFUSED_TYPES_HOLD.keys.freeze
69
+
70
+ # CArray's compatibility layer, which `data_type_extension.rb` opens by
71
+ # calling itself "Numo / NumPy-style" and "not the 'main' carray API".
72
+ # The words inside a block are carray's own, so these are sent to the
73
+ # spelling they stand for rather than taken.
74
+ LOCAL_ARRAY_COMPATIBILITY_NAMES = [:zeros, :ones, :full].freeze
75
+
76
+ # Every name a constructor may be written under, so that the receiver of
77
+ # one is known for what it is before anything else has been read.
78
+ LOCAL_ARRAY_CONSTRUCTOR_NAMES =
79
+ (LOCAL_ARRAY_TYPES.keys + LOCAL_ARRAY_REFUSED_TYPES +
80
+ LOCAL_ARRAY_COMPATIBILITY_NAMES + [:new, :empty]).freeze
81
+
82
+ # `CArray`, `::CArray`, or one of the type classes under either. A
83
+ # constructor is written on one of these and on nothing else.
84
+ def self.carray_receiver? (node)
85
+ case node
86
+ when Prism::ConstantReadNode
87
+ node.name == :CArray
88
+ when Prism::ConstantPathNode
89
+ text = node.slice
90
+ text == "::CArray" ||
91
+ text.start_with?("CArray::") || text.start_with?("::CArray::")
92
+ else
93
+ false
94
+ end
95
+ end
96
+
97
+ # Whether this call is one a body would make an array with -- including
98
+ # the spellings that are refused, so that each is refused by the reader
99
+ # that knows why rather than by the capture scan.
100
+ def self.local_array_constructor? (node)
101
+ node.is_a?(Prism::CallNode) && carray_receiver?(node.receiver) &&
102
+ LOCAL_ARRAY_CONSTRUCTOR_NAMES.include?(node.name)
103
+ end
104
+
15
105
  # Ruby Math methods that correspond 1:1 to a math.h function.
16
106
  #
17
107
  # Deliberately excludes anything whose C counterpart disagrees on
@@ -35,6 +125,11 @@ class CArray
35
125
  :cosh => "cosh",
36
126
  :tanh => "tanh",
37
127
  :hypot => "hypot",
128
+ :erf => "erf",
129
+ :erfc => "erfc",
130
+ # Not lowered to `tgamma` but to a helper, `Math.gamma` being what
131
+ # Ruby computes and tgamma only most of it -- see CGenerator.
132
+ :gamma => "gamma",
38
133
  :asinh => "asinh",
39
134
  :acosh => "acosh",
40
135
  :atanh => "atanh",
@@ -101,12 +196,25 @@ class CArray
101
196
  :rad_pi => "no math.h counterpart",
102
197
  }.freeze
103
198
 
199
+ # Ruby's other ways of counting a loop. Each is a loop this could run,
200
+ # and each is refused by name rather than by the generic message, so
201
+ # that the answer is the spelling to use rather than a list to search.
202
+ INNER_LOOP_SPELLINGS = %i[downto upto reverse_each each_with_index
203
+ each_with_object].freeze
204
+
205
+ # The two questions about a number that answer true or false in Ruby,
206
+ # and that C answers with a macro of its own. What each accepts is
207
+ # Ruby's business and is checked with the types: `nan?` is Float's
208
+ # alone, while `finite?` answers for an Integer and a Complex too.
209
+ NUMERIC_PREDICATES = %i[nan? finite?].freeze
210
+
104
211
  ARITHMETIC_OPERATORS = [:+, :-, :*, :/, :%].freeze
105
212
  # 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.
213
+ # the edges is C's answer rather than Ruby's -- a shift wraps, and a
214
+ # count of 64 or more or below zero is C's undefined one. The width is
215
+ # the kernel's, int64, not the cell's: CArray's own `<<` shifts an int32
216
+ # in 32 bits (`ext/mkkernel.rb`, :bit_lshift), so past the cell's width
217
+ # the two part, and the kernel is the one that agrees with a Ruby loop.
110
218
  BIT_OPERATORS = [:&, :|, :^, :<<, :>>].freeze
111
219
 
112
220
  # Methods whose result type is a property of the method: Float#floor and
@@ -134,13 +242,28 @@ class CArray
134
242
  :phase => :arg,
135
243
  }.freeze
136
244
 
245
+ # The rest of Ruby's Math, and why each is not lowered. C has a
246
+ # function by every one of these names -- saying it does not would be
247
+ # untrue, and it was what this said until the list was read against
248
+ # `math.h` -- so the reason is the one that actually applies: what
249
+ # Ruby computes and what C computes are not the same thing.
250
+ REFUSED_MATH = {
251
+ :lgamma => "Ruby's answer is a pair -- the value and the sign -- " \
252
+ "and a cell holds one number",
253
+ :frexp => "Ruby's answer is a pair -- the fraction and the " \
254
+ "exponent -- and a cell holds one number",
255
+ :ldexp => "its second argument is an exponent rather than a " \
256
+ "number, and a math call here computes every argument " \
257
+ "in the type of its result",
258
+ }.freeze
259
+
137
260
  # Constants under Math, emitted as literals so that the C sees exactly
138
261
  # the double Ruby would have used.
139
262
  MATH_CONSTANTS = { :PI => Math::PI, :E => Math::E }.freeze
140
263
  COMPARISON_OPERATORS = [:<, :<=, :>, :>=, :==, :!=].freeze
141
264
 
142
265
  attr_reader :parameter_names, :pointer_names, :address_arrays,
143
- :address_parameters
266
+ :address_parameters, :random_names
144
267
  # An offset that is an integer here rather than when the kernel runs.
145
268
  # A literal is one; so is arithmetic over literals, which is the same
146
269
  # number written a way that says where it came from -- `w[-RADIUS-1]`
@@ -187,7 +310,18 @@ class CArray
187
310
  attr_reader :windows
188
311
  attr_reader :index_names, :array_names, :scalar_names, :c_function_names, :body,
189
312
  :written_arrays, :array_ranks, :subscripts, :inner_ranges,
313
+ :index_sources,
190
314
  :contracted_names
315
+ # The C arrays the body declares, as [scope, name, storage, shape].
316
+ # Nothing in the operand tables says a body has any, so this is how the
317
+ # generator learns it has a header to include and a mask to refuse.
318
+ attr_reader :local_array_declarations
319
+
320
+ # Whether any of them is one of the zeroed spellings, which is what
321
+ # `<string.h>` is included for.
322
+ def clears_a_local_array?
323
+ @clears_a_local_array
324
+ end
191
325
 
192
326
  # A kernel that mentions UNDEF is a masked kernel whatever its arrays
193
327
  # happen to carry: it asks about masks, or makes them.
@@ -217,6 +351,45 @@ class CArray
217
351
  allocate.send(:scan_free_names, source, node)
218
352
  end
219
353
 
354
+ # The names the block hands to a C function whole, and reads in no
355
+ # other way.
356
+ #
357
+ # Wanted before the analyzer runs, because what the caller does with
358
+ # the answer -- leaving those arrays out of the broadcast -- has to
359
+ # happen before it: the broadcast is what a shape that does not line up
360
+ # dies in, and an array handed over whole has no shape to line up.
361
+ #
362
+ # The parameters decide, not the spelling. A bare array name in an
363
+ # argument position looks like an address pass and need not be one: in
364
+ # the whole-array spelling a bare name is a cell, so `twice.call(a)`
365
+ # against `double twice(double x)` walks `a` cell by cell. Asking the
366
+ # declaration is not a second reading of the signature -- the CFunction
367
+ # parsed it once, and this asks the same object the same question the
368
+ # analyzer asks.
369
+ #
370
+ # A name used as a cell anywhere, subscripted or bare, is not here
371
+ # however else it is also used: an array that is both walked and handed
372
+ # over is walked, and lines up as it always did.
373
+ def self.address_array_names (source, node: nil, c_functions: {})
374
+ analyzer = allocate
375
+ block = node || analyzer.send(:parse_block, source)
376
+ analyzer.send(:scan_address_array_names, block.body, c_functions)
377
+ end
378
+
379
+ # The names the block makes an array of its own under.
380
+ #
381
+ # Read off the source rather than off a built analyzer, because the
382
+ # caller needs them before it has anything to build one with: in the
383
+ # whole-array spelling a name the block assigns may be an array outside,
384
+ # and whether to look this one up outside is exactly what this answers.
385
+ # The compatibility spellings count too, so that `CArray.zeros(4)` is
386
+ # refused by the reader that knows why rather than by a broadcast.
387
+ def self.local_array_names (source, node: nil)
388
+ analyzer = allocate
389
+ block = node || analyzer.send(:parse_block, source)
390
+ analyzer.send(:scan_local_array_names, block.body)
391
+ end
392
+
220
393
  # `rank` is given only for a block that takes no parameters and writes
221
394
  # `out[] = a[] + b[]`. There the rank is a property of the arrays
222
395
  # rather than of the block, so it arrives from the caller and the loop
@@ -245,11 +418,16 @@ class CArray
245
418
  # number, and is not the same statement.
246
419
  def initialize (source, node: nil, array_names: [], c_functions: {}, rank: nil,
247
420
  steps: nil, contract: false, result: nil, function: false,
248
- pointers: {}, map: false, cell_names: [],
421
+ pointers: {}, pointer_lengths: {}, map: false,
422
+ cell_names: [],
249
423
  recursion: nil, windows: [], returns: true,
250
- free_indices: nil)
424
+ free_indices: nil, randoms: {}, masked: false,
425
+ declared_parameters: nil)
251
426
  @source = source
252
427
  @node = node
428
+ # `jit_call`'s: the parameter names its declaration gave, standing
429
+ # where a block's own would.
430
+ @declared_parameters = declared_parameters
253
431
  @array_names = array_names
254
432
  # Arrays with one cell and no axis to walk -- a CScalar. There is no
255
433
  # index to write for one, which is the whole of what distinguishes it
@@ -264,6 +442,11 @@ class CArray
264
442
  # none, because a window has nowhere to write one.
265
443
  @windows = windows
266
444
  @c_functions = c_functions
445
+ # The generators the block closed over, by the name it reached each
446
+ # one by, and the made-up name of the state array that carries it.
447
+ # A draw is neither a captured function nor an array read, so it is
448
+ # kept apart from both.
449
+ @randoms = randoms
267
450
  @function = function
268
451
  # A function declared `void` ends in a statement like any other; one
269
452
  # that returns ends in the expression it returns. Which it is comes
@@ -279,6 +462,9 @@ class CArray
279
462
  # read and write, false to read only (= const), nil for one that
280
463
  # points at nothing in particular and so cannot be reached at all.
281
464
  @pointers = pointers
465
+ # And how many cells a pointer's declaration said it points at, for
466
+ # the ones whose declarator carried a length: `double v[3]`.
467
+ @pointer_lengths = pointer_lengths
282
468
  # What the function being compiled is called, what it takes and what
283
469
  # it returns -- so its own body can call it. C puts a declarator's
284
470
  # name in scope inside the body it heads, and this is that. Nil for
@@ -295,9 +481,20 @@ class CArray
295
481
  reaches[name] = Array.new(rank.to_i) { [0, 0] }
296
482
  }
297
483
  @uses_undef = false
484
+ # Whether the kernel this body belongs to carries masks, which the
485
+ # caller settled from the operands before anything was read here. A
486
+ # body reads the same either way; what it decides is what a local
487
+ # array is -- cells alone, or cells with a shadow byte beside each of
488
+ # them -- and so how much stack one takes and what may be done with
489
+ # it. Always false for a function body, which has no cell to take a
490
+ # mask from and nowhere to put one.
491
+ @masked = masked
298
492
  @calls_for_effect = false
299
493
  @outer_names = []
300
494
  @inner_names = []
495
+ @inner_sources_seen = []
496
+ @inner_aliases = {}
497
+ @index_sources = {}
301
498
  # How many loops the statement being built stands inside. `break`
302
499
  # needs a loop to leave and does not care which kind: an inner loop
303
500
  # brings an index and a `while` brings none, but both are loops in C
@@ -307,6 +504,7 @@ class CArray
307
504
  @array_ranks = {}
308
505
  @inner_ranges = {}
309
506
  @subscripts = Hash.new { |hash, key| hash[key] = [] }
507
+ @write_subscripts = Hash.new { |hash, key| hash[key] = [] }
310
508
  @given_rank = rank
311
509
  @steps = steps
312
510
  @contract = contract
@@ -316,6 +514,23 @@ class CArray
316
514
  @free_names = []
317
515
  @index_names = []
318
516
  @local_names = []
517
+ # The arrays the body made, by name: [storage type, shape] for the
518
+ # declaration in sight. Kept beside @local_names rather than in it,
519
+ # because the two are different things under one syntax and a name
520
+ # holds one or the other -- which is what lets each say so.
521
+ @local_array_names = []
522
+ @local_arrays = {}
523
+ # One entry per C array the body declares, as
524
+ # [scope, name, storage, shape], so that the room they take together
525
+ # can be counted once the body has been read. Two sibling loops that
526
+ # each declare `w` are one entry: their blocks do not overlap, so
527
+ # neither do their frames.
528
+ @local_array_declarations = []
529
+ @clears_a_local_array = false
530
+ # The locals each scope holds, innermost last: the kernel's block --
531
+ # or a function's -- and one for every inner loop's block around the
532
+ # statement being built. `while` and `if` make none, as in Ruby.
533
+ @scopes = [[]]
319
534
  @assigned_names = []
320
535
  @scalar_names = []
321
536
  @c_function_names = []
@@ -323,6 +538,7 @@ class CArray
323
538
  @pointer_names = []
324
539
  @address_arrays = []
325
540
  @address_parameters = {}
541
+ @random_names = []
326
542
  @read_offsets = Hash.new { |hash, key| hash[key] = [] }
327
543
  @written_arrays = []
328
544
  analyze
@@ -377,18 +593,29 @@ class CArray
377
593
  end
378
594
 
379
595
  # A write is addressed the way a read is: every axis either walks with an
380
- # outer index, at whatever offset, or is pinned at a position known
381
- # before the first cell.
596
+ # index the kernel is running -- an outer one or an inner one, at
597
+ # whatever offset -- or is pinned at a position known before the first
598
+ # cell.
382
599
  #
383
600
  # Two iterations may land on the same cell -- `box[0]` puts every one of
384
601
  # them there -- and that is not an ambiguity: the extent states the
385
602
  # order, so what the array holds afterwards is what the same Ruby loop
386
603
  # would leave in it. An inner loop already rests on exactly that, an
387
604
  # accumulator being one cell written once per pass.
605
+ #
606
+ # An inner index is in that position and not a weaker one: its loop
607
+ # states its extent the way the kernel's own extents do, so the cell it
608
+ # reaches is as knowable as `i`'s and is bounds-checked with it. What
609
+ # the write is for is a row of workspace per outer cell -- filling one
610
+ # and walking back down it is the shape a small dense solve has, and it
611
+ # was the reason to add an axis rather than split a body into kernels
612
+ # that each pay a call. It was refused until it was noticed that
613
+ # `kk = k + 0` on the way in compiled it anyway, as a scatter: the rule
614
+ # was stopping the spelling rather than the thing.
388
615
  def walking_subscripts? (subscripts)
389
616
  subscripts.all? { |index, offset|
390
617
  if index
391
- @outer_names.include?(index)
618
+ index_identifier_in_scope?(index)
392
619
  else
393
620
  !offset.is_a?(Node) || Analyzer.fixed_subscript?(offset)
394
621
  end
@@ -452,9 +679,89 @@ class CArray
452
679
  node.is_a?(Prism::CallNode) && node.receiver.nil? && node.name == :raise
453
680
  end
454
681
 
682
+ def scan_address_array_names (node, c_functions)
683
+ # Two passes rather than one, because the arguments of a call sit
684
+ # under an ArgumentsNode: pruning them out of a single walk would
685
+ # mean matching a grandchild, and the set is the clearer statement.
686
+ addressed = {}.compare_by_identity
687
+ handed = []
688
+ collect_address_arguments(node, c_functions, handed, addressed)
689
+ return [] if handed.empty?
690
+ as_cells = []
691
+ collect_cell_names(node, addressed, as_cells)
692
+ (handed.uniq - as_cells - scan_local_array_names(node))
693
+ end
694
+
695
+ # The names standing bare where a declaration wants a pointer at
696
+ # numbers, and the argument nodes they stand in.
697
+ def collect_address_arguments (node, c_functions, handed, addressed)
698
+ return unless node
699
+ return if raise_call?(node)
700
+ if node.is_a?(Prism::CallNode) &&
701
+ C_FUNCTION_CALL_NAMES.include?(node.name) &&
702
+ (function = captured_name(node.receiver)) &&
703
+ c_functions.key?(function)
704
+ parameters = c_functions.fetch(function).parameters
705
+ arguments = node.arguments ? node.arguments.arguments : []
706
+ arguments.each_with_index do |argument, position|
707
+ next unless parameters[position]&.indexable?
708
+ given = captured_name(argument)
709
+ next unless given
710
+ handed << given
711
+ addressed[argument] = true
712
+ end
713
+ end
714
+ node.compact_child_nodes.each { |child|
715
+ collect_address_arguments(child, c_functions, handed, addressed)
716
+ }
717
+ end
718
+
719
+ # Every other way a name is reached: subscripted, or bare where a bare
720
+ # name is a cell. One of these takes a name back out of the answer.
721
+ def collect_cell_names (node, addressed, as_cells)
722
+ return unless node
723
+ return if raise_call?(node)
724
+ return if addressed[node]
725
+ if node.is_a?(Prism::CallNode) && node.name == :[] &&
726
+ (subscripted = captured_name(node.receiver))
727
+ as_cells << subscripted
728
+ end
729
+ if (bare = captured_name(node)) &&
730
+ !(node.is_a?(Prism::CallNode) &&
731
+ C_FUNCTION_CALL_NAMES.include?(node.name))
732
+ as_cells << bare
733
+ end
734
+ node.compact_child_nodes.each { |child|
735
+ collect_cell_names(child, addressed, as_cells)
736
+ }
737
+ end
738
+
739
+ def scan_local_array_names (node)
740
+ return [] unless node
741
+ names = []
742
+ if node.is_a?(Prism::LocalVariableWriteNode) &&
743
+ self.class.local_array_constructor?(node.value)
744
+ names << node.name
745
+ end
746
+ node.compact_child_nodes.each { |child|
747
+ names.concat(scan_local_array_names(child))
748
+ }
749
+ names.uniq
750
+ end
751
+
455
752
  def names_constants (node)
456
753
  return [] unless node
457
754
  return [] if raise_call?(node)
755
+ # The receiver of a constructor is part of a spelling rather than a
756
+ # name the block reached outside for: `CArray.double(9)` says what to
757
+ # declare, and the class in front of it is not a value to look up.
758
+ # Left in, it is looked up, found to be a Class, and refused for
759
+ # being one -- which says nothing about the line that was written.
760
+ if self.class.local_array_constructor?(node)
761
+ arguments = node.arguments ? node.arguments.arguments : []
762
+ return arguments.flat_map { |argument| names_constants(argument) } +
763
+ names_constants(node.block)
764
+ end
458
765
  # `Foo::TABLE` names one thing, and `Foo` on its own names none of
459
766
  # it, so a path is read whole and not descended into.
460
767
  return [node.slice.to_sym] if node.is_a?(Prism::ConstantPathNode)
@@ -509,13 +816,24 @@ class CArray
509
816
  # A block that ends in an assignment still has that assignment's
510
817
  # value, which is what Ruby says it has -- so jit_map keeps the
511
818
  # statement and reads the cell back for the value.
512
- map_assignment = @map && last.is_a?(Prism::LocalVariableWriteNode)
819
+ map_assignment = @map && (last.is_a?(Prism::LocalVariableWriteNode) ||
820
+ last.is_a?(Prism::LocalVariableOperatorWriteNode))
513
821
  returns_value = (@function && @returns) || (@map && !map_assignment) ||
514
822
  (@contract &&
515
823
  !(last.is_a?(Prism::CallNode) && last.name == :[]=))
516
824
  built = statements[0..-2].map { |node| build_statement(node) }
517
825
  if map_assignment
518
826
  built << build_statement(last)
827
+ # `w = CArray.double(4)` as the last line: the block's value in Ruby
828
+ # is the array, and a cell of the result has nowhere to put one.
829
+ if built.last.is_a?(LocalArrayDeclaration)
830
+ raise Unsupported.new(
831
+ "this block ends by making `#{built.last.name}`, so its value " \
832
+ "is an array and there is no cell for one to go in; end with " \
833
+ "the number you want -- a cell of it, or `sum(#{built.last.name})` " \
834
+ "and its kin",
835
+ last.location)
836
+ end
519
837
  @map_value = build_name_read(last.name, last.location)
520
838
  else
521
839
  built << (returns_value ? build(last) : build_statement(last))
@@ -544,22 +862,52 @@ class CArray
544
862
  end
545
863
  end
546
864
 
547
- # An array the kernel writes is written once per outer cell; reading it
548
- # through an inner index would reach cells other outer iterations own,
549
- # and no evaluation order settles that.
865
+ # An array the kernel writes at `a[i]` is written once per outer cell,
866
+ # and reading it at `a[j]` for an inner `j` would reach cells other
867
+ # outer iterations own -- an order no extent states.
868
+ #
869
+ # What decides that is the axis the outer indices pick the cell by, not
870
+ # the array. `work[i, k] = ...` gives outer cell i a row of its own,
871
+ # and `work[i, k]`, `work[i, j]` or `work[i, position]` are that row
872
+ # being read back inside the same cell, in the order the body states.
873
+ # So a read carrying an inner index has to address every axis some
874
+ # write walks with an outer index with that same index -- at whatever
875
+ # offset, `work[i-1, k]` being the row before this one and a recurrence
876
+ # like any other displaced read. Where it does, the inner index is
877
+ # roaming inside the cell the outer ones already picked, and whatever
878
+ # the writes do along that axis they do there too.
550
879
  def verify_written_arrays_are_not_read_through_inner_indices
551
880
  @written_arrays.each do |array|
881
+ writes = @write_subscripts[array]
552
882
  @subscripts[array].each do |per_axis|
553
- per_axis.each do |index, _|
554
- next unless @inner_names_seen.include?(index)
883
+ inner = per_axis.map(&:first).find { |index|
884
+ @inner_names_seen.include?(index)
885
+ }
886
+ spelled = @index_sources.fetch(inner, inner) if inner
887
+ next unless inner
888
+ per_axis.each_with_index do |(index, _), axis|
889
+ name = outer_name_written(writes, axis)
890
+ next if name.nil? || name == index
555
891
  raise Unsupported.new(
556
- "`#{array}` is written by this kernel, so it cannot also be " \
557
- "read through the inner index `#{index}`")
892
+ "`#{array}` is written on axis #{axis} through `#{name}`, " \
893
+ "so it cannot also be read through the inner index " \
894
+ "`#{spelled}`: a read that carries one addresses axis " \
895
+ "#{axis} through `#{name}` too, or it reaches cells another " \
896
+ "outer iteration owns")
558
897
  end
559
898
  end
560
899
  end
561
900
  end
562
901
 
902
+ # The outer index the writes walk one axis with, where they agree on
903
+ # one. Writes that pin the axis or work its position out say nothing
904
+ # about which cell is whose, and so name nothing here.
905
+ def outer_name_written (writes, axis)
906
+ names = writes.map { |written| written[axis]&.first }
907
+ .select { |name| @outer_names.include?(name) }.uniq
908
+ names.size == 1 ? names.first : nil
909
+ end
910
+
563
911
  # Turns `c[i,j] = a[i,k] * b[k,j]` into the loops it stands for.
564
912
  #
565
913
  # Which indices are summed is not the assignment's business: a repeated
@@ -593,6 +941,23 @@ class CArray
593
941
  "array of your own")
594
942
  end
595
943
 
944
+ # And one expression is all of it: a local assigned before the summand
945
+ # is a run of statements, which a contraction has no room for -- the
946
+ # same reason it takes no local array. Said here, because what said
947
+ # it before was the type assignment, which walks the accumulator's
948
+ # zero -- taken from the summand, above the loop the local is
949
+ # assigned in -- and reported the local as read before it is assigned.
950
+ leading = statements[0..-2].grep(Assignment).map(&:name)
951
+ unless leading.empty?
952
+ names = leading.uniq.map { |name| "`#{name}`" }.join(" and ")
953
+ raise Unsupported.new(
954
+ "#{names} #{leading.uniq.size == 1 ? 'is' : 'are'} assigned " \
955
+ "before the summand, and a contraction is one expression: there " \
956
+ "is no run of statements to assign in. Write the value into the " \
957
+ "expression, or write the loop with jit_for",
958
+ statements.first.location)
959
+ end
960
+
596
961
  write = writes.first
597
962
  unless write
598
963
  # The returned form: the free indices, in the order the block named
@@ -664,6 +1029,7 @@ class CArray
664
1029
  def classify_indices (statements, write)
665
1030
  counts = Hash.new(0)
666
1031
  subscripts_of(statements, write).each { |index, _| counts[index] += 1 }
1032
+ refuse_a_gathered_index(statements, write)
667
1033
 
668
1034
  missing = @index_names - counts.keys
669
1035
  unless missing.empty?
@@ -671,25 +1037,84 @@ class CArray
671
1037
  "#{missing.map { |name| "`#{name}`" }.join(', ')} " \
672
1038
  "#{missing.size == 1 ? 'names no axis' : 'name no axis'} here")
673
1039
  end
674
- # Naming the result's axes says which indices are free; it does not
675
- # say what a repetition means, and a repetition still means a sum. So
676
- # a named index is free however often it appears -- twice is what a
677
- # point number does, and `square[a,a]` is the diagonal rather than a
678
- # trace -- while a parameter is summed by repeating, here as under the
679
- # convention. One that appears once is free and was not named, which
680
- # is a sum along an axis and is not a contraction.
1040
+ # Naming the result's axes says which indices are free, and says it
1041
+ # of all of them: what is not named is summed, at however few
1042
+ # positions it sits. So a named index is free however often it
1043
+ # appears -- twice is what a point number does, and `square[a,a]` is
1044
+ # the diagonal rather than a trace -- and a parameter is summed
1045
+ # whether it repeats or not, which is what lets `"ik->i"` be written.
1046
+ #
1047
+ # Where nothing is named the convention decides instead, and there a
1048
+ # repetition is what makes a sum: an index at one position is free,
1049
+ # because a list that would have said otherwise was not given.
681
1050
  if @free_indices
682
- parameters = @index_names - @free_indices
683
- alone = parameters.select { |name| counts[name] == 1 }
684
- unless alone.empty?
685
- raise Unsupported.new(describe_a_lone_parameter(alone))
686
- end
687
- return [@free_indices, parameters]
1051
+ return [@free_indices, @index_names - @free_indices]
688
1052
  end
689
1053
  [@index_names.select { |name| counts[name] == 1 },
690
1054
  @index_names.select { |name| counts[name] > 1 }]
691
1055
  end
692
1056
 
1057
+ # An index inside a computed subscript -- `m[i, idx[k]]` -- is read at a
1058
+ # position, but not at a position of the array it stands in: it
1059
+ # addresses an axis of `idx`. A contraction is a count of positions,
1060
+ # and this one cannot be counted either way. Counting it makes
1061
+ # `m[i, idx[k]] * v[k]` a sum over k, which is what the notation says,
1062
+ # and makes `a[i, k] * w[idx[i]]` a sum over i, which is not what
1063
+ # anybody writing a gather means. Not counting it leaves the first one
1064
+ # a free index and the answer an outer product -- a whole matrix where
1065
+ # the reader asked for a vector, quietly.
1066
+ #
1067
+ # So it is refused, and the loop it means is written with jit_for,
1068
+ # where the gather is the ordinary computed subscript it already is.
1069
+ def refuse_a_gathered_index (statements, write)
1070
+ summand = write.is_a?(ElementWrite) ? write.expression : write
1071
+ found = []
1072
+ # Inside a gathering subscript every index is one of these, however
1073
+ # deep: `a[idx[jdx[k]]]` gathers twice and k is no more a position of
1074
+ # `a` for it.
1075
+ inside = lambda do |node|
1076
+ return unless node.is_a?(Node)
1077
+ subscript_indices(node).each do |index|
1078
+ found << index if @index_names.include?(index)
1079
+ end
1080
+ gathered_subscripts(node).each { |offset| inside.call(offset) }
1081
+ node.children.each { |child| inside.call(child) }
1082
+ end
1083
+ walk = lambda do |node|
1084
+ return unless node.is_a?(Node)
1085
+ gathered_subscripts(node).each { |offset| inside.call(offset) }
1086
+ node.children.each { |child| walk.call(child) }
1087
+ end
1088
+ (statements[0..-2] + [summand]).each { |statement| walk.call(statement) }
1089
+ return if found.empty?
1090
+ names = found.uniq.map { |name| "`#{name}`" }.join(" and ")
1091
+ raise Unsupported.new(
1092
+ "#{names} #{found.uniq.size == 1 ? 'stands' : 'stand'} inside a " \
1093
+ "subscript the kernel works out, where a contraction cannot count " \
1094
+ "it: an index there addresses an axis of the array doing the " \
1095
+ "gathering, not of the array being read, so whether it is summed " \
1096
+ "or free is not something the notation says. Write the loop with " \
1097
+ "jit_for",
1098
+ summand.location)
1099
+ end
1100
+
1101
+ # The indices a read names directly, as the symbols a subscript pair
1102
+ # carries rather than as nodes.
1103
+ def subscript_indices (node)
1104
+ return [] unless node.is_a?(ElementRead) || node.is_a?(MaskTest)
1105
+ node.subscripts.filter_map { |index, _| index }
1106
+ end
1107
+
1108
+ # The subscript expressions a read works out, which are the ones an
1109
+ # index can hide in. A plain `a[i]` or `a[i-1]` carries its index in
1110
+ # the pair itself and has none of these.
1111
+ def gathered_subscripts (node)
1112
+ return [] unless node.is_a?(ElementRead) || node.is_a?(MaskTest)
1113
+ node.subscripts.filter_map { |index, offset|
1114
+ offset if index.nil? && offset.is_a?(Node)
1115
+ }
1116
+ end
1117
+
693
1118
  # Every subscript on the right-hand side: the summand, and whatever the
694
1119
  # locals before it read.
695
1120
  def subscripts_of (statements, write)
@@ -706,22 +1131,6 @@ class CArray
706
1131
  collected.reject { |index, _| index.nil? }
707
1132
  end
708
1133
 
709
- # A parameter at one position only. Nothing there stands in for a
710
- # sigma, so summing it would be the argument list quietly meaning more
711
- # than it says -- the same reason the convention refuses it.
712
- def describe_a_lone_parameter (alone)
713
- listed = alone.map { |name| "`#{name}`" }.join(", ")
714
- named = (@free_indices + alone).map { |name| ":#{name}" }.join(", ")
715
- "#{listed} #{alone.size == 1 ? 'appears' : 'appear'} once, so " \
716
- "#{alone.size == 1 ? 'it is' : 'they are'} free rather than summed. " \
717
- "A contraction sums the indices that repeat; name " \
718
- "#{alone.size == 1 ? 'it' : 'them'} as " \
719
- "#{alone.size == 1 ? 'an axis' : 'axes'} of the result " \
720
- "(`CArray.jit_contract(#{named})`) to keep " \
721
- "#{alone.size == 1 ? 'it' : 'them'}, or use sum(axis:) to sum along " \
722
- "the axis"
723
- end
724
-
725
1134
  def describe_index_mismatch (written, free, summed)
726
1135
  summed_on_left = written & summed
727
1136
  unless summed_on_left.empty?
@@ -729,6 +1138,16 @@ class CArray
729
1138
  # the result's axes, and the one that repeats is only the reason
730
1139
  # the convention could not see it.
731
1140
  named = written.map { |name| ":#{name}" }.join(", ")
1141
+ if @free_indices
1142
+ # A list was given, so what is summed is what the list left out.
1143
+ # An axis of the left-hand side that is not in it is the list
1144
+ # falling short of the result, not the index being a sum.
1145
+ listed = summed_on_left.map { |name| "`#{name}`" }.join(", ")
1146
+ return "#{listed} #{summed_on_left.size == 1 ? 'is an axis' : 'are axes'} " \
1147
+ "of the left-hand side and #{summed_on_left.size == 1 ? 'was' : 'were'} " \
1148
+ "not named. Naming the result's axes names all of them, and " \
1149
+ "what is left out is summed: `CArray.jit_contract(#{named})`"
1150
+ end
732
1151
  return "#{summed_on_left.map { |name| "`#{name}`" }.join(', ')} " \
733
1152
  "#{summed_on_left.size == 1 ? 'is repeated' : 'are repeated'} " \
734
1153
  "on the right, so #{summed_on_left.size == 1 ? 'it is' : 'they are'} " \
@@ -768,6 +1187,19 @@ class CArray
768
1187
  # block's locals, and the indices -- an index called `contraction` would
769
1188
  # have shared the identifier with the accumulator and the sum would have
770
1189
  # come out zero, with nothing said.
1190
+ # The identifier an inner index gets in the generated C: its own name
1191
+ # the first time it is written, and a numbered one after that. Every
1192
+ # name the block has said is avoided, so nothing collides with a local
1193
+ # or another index.
1194
+ def free_index_name (index)
1195
+ return index unless @inner_sources_seen.include?(index)
1196
+ taken = @local_names + @index_names + @outer_names + @inner_names +
1197
+ @inner_names_seen + @contracted_names
1198
+ suffix = 2
1199
+ suffix += 1 while taken.include?(:"#{index}__#{suffix}")
1200
+ :"#{index}__#{suffix}"
1201
+ end
1202
+
771
1203
  def free_local_name
772
1204
  taken = @local_names + @index_names + @outer_names + @inner_names +
773
1205
  @contracted_names
@@ -785,10 +1217,22 @@ class CArray
785
1217
  extract_block(result.value)
786
1218
  end
787
1219
 
1220
+ # Every spelling that binds a local: `x = e`, the operator forms, which
1221
+ # read the name and write it back, and a name on the left of a parallel
1222
+ # assignment. A name the block assigns is not a name it captures, and
1223
+ # one that is an array outside is the array the whole-array spelling
1224
+ # writes -- so an operator form left out here would have `out += a`
1225
+ # reaching for a capture that is not there.
1226
+ ASSIGNMENT_NODES = [Prism::LocalVariableWriteNode,
1227
+ Prism::LocalVariableOperatorWriteNode,
1228
+ Prism::LocalVariableOrWriteNode,
1229
+ Prism::LocalVariableAndWriteNode,
1230
+ Prism::LocalVariableTargetNode].freeze
1231
+
788
1232
  def collect_assigned_names (node)
789
1233
  return [] unless node
790
1234
  names = []
791
- names << node.name if node.is_a?(Prism::LocalVariableWriteNode)
1235
+ names << node.name if ASSIGNMENT_NODES.any? { |kind| node.is_a?(kind) }
792
1236
  node.compact_child_nodes.each do |child|
793
1237
  names.concat(collect_assigned_names(child))
794
1238
  end
@@ -827,7 +1271,23 @@ class CArray
827
1271
  end
828
1272
 
829
1273
  if @function
830
- @parameter_names = requireds.map(&:name)
1274
+ # A body written for `jit_call` takes no parameters: the names are
1275
+ # in the declaration, where they also name the locals the call
1276
+ # reads. Written in both places they could disagree, so the block
1277
+ # is refused for naming any.
1278
+ if @declared_parameters
1279
+ unless requireds.empty?
1280
+ raise Unsupported.new(
1281
+ "this body's parameters are the ones its declaration named " \
1282
+ "-- #{@declared_parameters.map { |n| "`#{n}`" }.join(', ')} " \
1283
+ "-- and the block names #{requireds.map { |p| "`#{p.name}`" }.join(', ')} " \
1284
+ "again. Written in two places they could disagree; leave " \
1285
+ "the block's off")
1286
+ end
1287
+ @parameter_names = @declared_parameters
1288
+ else
1289
+ @parameter_names = requireds.map(&:name)
1290
+ end
831
1291
  @index_names = []
832
1292
  @outer_names = []
833
1293
  return
@@ -955,31 +1415,34 @@ class CArray
955
1415
  @uses_undef = true
956
1416
  return MaskWrite.new(node.name, node.location)
957
1417
  end
958
- expression = build(node.value)
959
- # In the whole-array spelling every name in the block is a cell, so
960
- # an assignment to a name that is an array outside writes that
961
- # array's cell -- the one the loop is on, the same cell every read
962
- # in the block is at. A name that is not an array is a local, as
963
- # it is anywhere else.
964
- if @whole_array && @array_names.include?(node.name)
965
- return whole_array_write(node.name, expression, node.location)
966
- end
967
- # An index is the loop's, not the block's. Ruby reads `i = 2` as
968
- # rebinding the parameter and leaves the loop alone; the C would
969
- # assign to the counter, so the loop would walk somewhere else --
970
- # to cells outside the array, given a value outside its extent.
971
- # The two do not mean the same thing, so this is not compiled.
972
- if index_in_scope?(node.name)
973
- raise Unsupported.new(
974
- "`#{node.name}` is a loop index, and assigning to it here would " \
975
- "move the loop rather than the value: in Ruby the same line " \
976
- "rebinds the parameter and the loop runs on. Use a local of " \
977
- "another name",
978
- node.location)
1418
+ # A constructor is read before the right-hand side is built, the way
1419
+ # `out = UNDEF` above is: what it says is a declaration, not a value
1420
+ # to compute and store.
1421
+ if (made = read_local_array_constructor(node.value))
1422
+ storage, shape, zeroed = made
1423
+ return declare_local_array(node.name, storage, shape, zeroed,
1424
+ node.location, node.depth)
979
1425
  end
980
- @local_names << node.name unless @local_names.include?(node.name)
981
- Assignment.new(node.name, expression, node.location)
1426
+ expression = build(node.value)
1427
+ assign_local(node.name, expression, node.location, node.depth)
1428
+ when Prism::LocalVariableOperatorWriteNode
1429
+ build_operator_assignment(node)
1430
+ when Prism::MultiWriteNode
1431
+ build_parallel_assignment(node)
1432
+ when Prism::IndexOperatorWriteNode
1433
+ build_operator_element_write(node)
1434
+ when Prism::LocalVariableOrWriteNode, Prism::LocalVariableAndWriteNode,
1435
+ Prism::IndexOrWriteNode, Prism::IndexAndWriteNode
1436
+ raise Unsupported.new(
1437
+ "`#{node.is_a?(Prism::LocalVariableOrWriteNode) ||
1438
+ node.is_a?(Prism::IndexOrWriteNode) ? '||=' : '&&='}` asks " \
1439
+ "whether the value is already nil or false, which a number in a " \
1440
+ "kernel never is; write the assignment out",
1441
+ node.location)
982
1442
  when Prism::CallNode
1443
+ if (intrinsic = build_intrinsic_statement(node))
1444
+ return intrinsic
1445
+ end
983
1446
  if node.receiver.nil? && node.name == :printf
984
1447
  return build_print(node)
985
1448
  end
@@ -989,7 +1452,8 @@ class CArray
989
1452
  # pointed -- which is the whole reason C has statements that are
990
1453
  # calls. Everything else keeps the refusal below: a computation
991
1454
  # standing where a statement stands is a line that does nothing.
992
- if (call = recursive_call(node) || c_function_call(node))
1455
+ if (call = recursive_call(node) || c_function_call(node) ||
1456
+ random_call(node))
993
1457
  @calls_for_effect = true
994
1458
  return CallStatement.new(call, node.location)
995
1459
  end
@@ -1003,13 +1467,58 @@ class CArray
1003
1467
  when Prism::BreakNode
1004
1468
  build_loop_jump(node, LoopStop, "break")
1005
1469
  else
1006
- raise Unsupported.new(
1007
- "a kernel body holds assignments, `if`, `while`, `(a...b).each` " \
1008
- "and `n.times` only, got #{node_name(node)}",
1009
- node.location)
1470
+ refuse_as_a_statement(node, statement_description(node))
1010
1471
  end
1011
1472
  end
1012
1473
 
1474
+ # What a body may hold, said in one place: it was said in two, and they
1475
+ # had already come apart -- one of them listing `step` and the other
1476
+ # not.
1477
+ def refuse_as_a_statement (node, description)
1478
+ hint = STATEMENT_HINTS[node.class]
1479
+ raise Unsupported.new(
1480
+ "a kernel body holds assignments, `if`, `while`, `(a...b).each`, " \
1481
+ "`n.times` and `a.step(b, s)` only, got #{description}" +
1482
+ (hint ? " -- #{hint}" : ""),
1483
+ node.location)
1484
+ end
1485
+
1486
+ # A refusal names what was written rather than the node this compiler
1487
+ # read it as: `Prism::UnlessNode` is not a thing anyone typed.
1488
+ STATEMENT_DESCRIPTIONS = {
1489
+ Prism::UnlessNode => "`unless`",
1490
+ Prism::UntilNode => "`until`",
1491
+ Prism::CaseNode => "`case`",
1492
+ Prism::CaseMatchNode => "`case ... in`",
1493
+ Prism::ForNode => "`for`",
1494
+ Prism::ReturnNode => "`return`",
1495
+ Prism::BeginNode => "`begin`",
1496
+ Prism::RescueModifierNode => "`rescue`",
1497
+ Prism::MultiWriteNode => "a parallel assignment",
1498
+ Prism::InstanceVariableWriteNode => "an instance variable",
1499
+ Prism::GlobalVariableWriteNode => "a global variable",
1500
+ Prism::ConstantWriteNode => "an assignment to a constant",
1501
+ Prism::DefNode => "a method definition",
1502
+ }.freeze
1503
+
1504
+ STATEMENT_HINTS = {
1505
+ Prism::UnlessNode => "write it as `if` with the condition negated",
1506
+ Prism::UntilNode => "write it as `while` with the condition negated",
1507
+ Prism::CaseNode => "write the branches out with `if` and `elsif`",
1508
+ Prism::ReturnNode => "a body is one expression, and its value is " \
1509
+ "the last thing in it",
1510
+ # A statement, and a supported one; what it has not got is a value.
1511
+ # Ruby's is the array of what it wrote, and a cell holds a number.
1512
+ Prism::MultiWriteNode =>
1513
+ "its value in Ruby is the array of what it " \
1514
+ "wrote, and a cell holds a number; end with " \
1515
+ "the one you want",
1516
+ }.freeze
1517
+
1518
+ def statement_description (node)
1519
+ STATEMENT_DESCRIPTIONS.fetch(node.class) { node_name(node) }
1520
+ end
1521
+
1013
1522
  # `next` skips the rest of this iteration, `break` leaves the loop. Both
1014
1523
  # mean in the generated loop what they mean in the Ruby loop it replaces
1015
1524
  # -- with one exception: Ruby's `break` in the kernel block is not a
@@ -1116,7 +1625,12 @@ class CArray
1116
1625
  def build_inner_loop (node)
1117
1626
  counted = node.name == :times
1118
1627
  range = unwrap(node.receiver)
1119
- if !counted && !range.is_a?(Prism::RangeNode)
1628
+ # Two spellings carry a stride, and they are Ruby's two: a range
1629
+ # steps within the ends it already states, and an integer steps to
1630
+ # one it is given. Which is which is the receiver.
1631
+ stepped = node.name == :step && !range.is_a?(Prism::RangeNode)
1632
+ walked = node.name == :step && range.is_a?(Prism::RangeNode)
1633
+ if !counted && !stepped && !range.is_a?(Prism::RangeNode)
1120
1634
  raise Unsupported.new(
1121
1635
  "an inner loop runs over a range, as in `(0...n).each { |j| ... }`",
1122
1636
  node.location)
@@ -1132,7 +1646,7 @@ class CArray
1132
1646
  if index_in_scope?(index)
1133
1647
  raise Unsupported.new("`#{index}` is already an index here", node.location)
1134
1648
  end
1135
- unless counted || (range.left && range.right)
1649
+ unless counted || stepped || (range.left && range.right)
1136
1650
  raise Unsupported.new("an inner loop needs both ends of its range",
1137
1651
  node.location)
1138
1652
  end
@@ -1140,26 +1654,102 @@ class CArray
1140
1654
  # `n.times` is `(0...n).each`: the count is the exclusive end, and it
1141
1655
  # is built from the same vocabulary a range end is, so a literal and a
1142
1656
  # captured integer both serve.
1657
+ #
1658
+ # `from.step(to, s)` is the spelling an extent already says a stride
1659
+ # or a downward sweep with, and it means here what it means there --
1660
+ # `to` included, which is Ruby's own reading of it and the opposite of
1661
+ # a `...` range's. It is turned into an exclusive end at once, so
1662
+ # everything below this line counts the way `each` does.
1663
+ step = 1
1143
1664
  if counted
1144
1665
  from = IntegerLiteral.new(0, node.location)
1145
1666
  to = build(node.receiver)
1667
+ elsif stepped
1668
+ from = build(node.receiver)
1669
+ arguments = node.arguments ? node.arguments.arguments : []
1670
+ unless arguments.size == 2
1671
+ raise Unsupported.new(
1672
+ "an inner loop written with `step` gives it both the last " \
1673
+ "index and the stride, as in `(n-1).step(0, -1) { |k| ... }`",
1674
+ node.location)
1675
+ end
1676
+ step = inner_loop_step(arguments.last)
1677
+ to = build(arguments.first)
1678
+ to = BinaryOperation.new(step.positive? ? :+ : :-, to,
1679
+ IntegerLiteral.new(1, node.location),
1680
+ node.location)
1146
1681
  else
1147
1682
  from = build(range.left)
1148
1683
  to = build(range.right)
1149
1684
  to = BinaryOperation.new(:+, to, IntegerLiteral.new(1, node.location),
1150
1685
  node.location) unless range.exclude_end?
1686
+ if walked
1687
+ arguments = node.arguments ? node.arguments.arguments : []
1688
+ unless arguments.size == 1
1689
+ raise Unsupported.new(
1690
+ "a range steps by one stride, as in `(0...n).step(2) " \
1691
+ "{ |j| ... }`", node.location)
1692
+ end
1693
+ step = inner_loop_step(arguments.first)
1694
+ if step.negative?
1695
+ raise Unsupported.new(
1696
+ "a range cannot step backwards, in Ruby either -- count " \
1697
+ "down with `(n-1).step(0, -1) { |j| ... }`, which is the " \
1698
+ "spelling an extent takes too",
1699
+ node.location)
1700
+ end
1701
+ end
1151
1702
  end
1152
1703
 
1704
+ # Two sibling loops may both be written `{ |k| ... }`, and each has
1705
+ # a range of its own -- a forward sweep from 1 and the sweep back
1706
+ # down from the far end are exactly that pair. They are one name in
1707
+ # the block and cannot be one variable here, because the reach of
1708
+ # `a[k-1]` is checked against the loop it sits in. So the second
1709
+ # gets an identifier of its own, and the name in the block goes on
1710
+ # meaning whichever loop it is inside.
1711
+ internal = free_index_name(index)
1153
1712
  @inner_names << index
1154
- @inner_names_seen << index
1155
- @inner_ranges[index] = [from, to]
1713
+ @inner_sources_seen << index
1714
+ @inner_names_seen << internal
1715
+ @inner_aliases[index] = internal
1716
+ @index_sources[internal] = index
1717
+ @inner_ranges[internal] = [from, to, step]
1156
1718
  @loop_depth += 1
1719
+ @scopes.push([])
1157
1720
  statements = statements_of(node.block.body).map { |inner|
1158
1721
  build_statement(inner)
1159
1722
  }
1723
+ @scopes.pop
1160
1724
  @loop_depth -= 1
1161
1725
  @inner_names.pop
1162
- InnerLoop.new(index, from, to, statements, node.location)
1726
+ @inner_aliases.delete(index)
1727
+ InnerLoop.new(internal, from, to, statements, node.location, step)
1728
+ end
1729
+
1730
+ # The stride is read off the page rather than computed, because it is
1731
+ # what says which way the loop runs and the C has to be written one way
1732
+ # or the other before anything is known. A captured integer would
1733
+ # leave that open.
1734
+ def inner_loop_step (node)
1735
+ value = case node
1736
+ when Prism::IntegerNode then node.value
1737
+ when Prism::CallNode
1738
+ if node.name == :-@ && node.receiver.is_a?(Prism::IntegerNode)
1739
+ -node.receiver.value
1740
+ end
1741
+ end
1742
+ if value.nil?
1743
+ raise Unsupported.new(
1744
+ "an inner loop's stride says which way it runs, so it is an " \
1745
+ "integer literal -- `step(0, -1)` for a downward sweep",
1746
+ node.location)
1747
+ end
1748
+ if value.zero?
1749
+ raise Unsupported.new("an inner loop's stride cannot be zero",
1750
+ node.location)
1751
+ end
1752
+ value
1163
1753
  end
1164
1754
 
1165
1755
  def unwrap (node)
@@ -1192,8 +1782,1014 @@ class CArray
1192
1782
  node ? node.body : []
1193
1783
  end
1194
1784
 
1785
+ # Handing a local array to a C function, with the declaration matched
1786
+ # against what the block wrote.
1787
+ #
1788
+ # The length check is the one that matters, and it is not decoration: a
1789
+ # compiled function does not check a subscript on a pointer parameter
1790
+ # (unchecked by design -- the caller's business, as in C), and this
1791
+ # array is on the stack of the block that made it. A declaration that
1792
+ # reads more cells than the array holds would walk over the frame, and
1793
+ # the return address is in it.
1794
+ def build_local_array_address (name, argument, parameter, c_function)
1795
+ storage, shape = local_array_in_sight(name)
1796
+ # The callee is handed a pointer to the cells, and the shadow beside
1797
+ # them travels in no C declaration there is a spelling for here: a
1798
+ # signature says what it points at, and "these cells, and a byte each
1799
+ # saying whether they are there" is not one of the things it can say.
1800
+ if @masked
1801
+ raise Unsupported.new(
1802
+ "`#{name}` is a local array of this kernel, and the kernel " \
1803
+ "carries masks -- so `#{name}` carries one beside each of its " \
1804
+ "cells, and there is no way to hand that to `#{c_function}`: a " \
1805
+ "signature says what a pointer points at, and a mask travels in " \
1806
+ "no C declaration. Copy the cells the function is to see into a " \
1807
+ "captured array, or answer the mask in the kernel",
1808
+ argument.location)
1809
+ end
1810
+ unless parameter&.pointer
1811
+ raise Unsupported.new(
1812
+ "`#{c_function}` takes #{parameter ? "`#{parameter.text}`" : 'a number'} " \
1813
+ "there, and `#{name}` is an array; pass a cell of it, as in " \
1814
+ "`#{name}[0]`",
1815
+ argument.location)
1816
+ end
1817
+ unless parameter.indexable?
1818
+ raise Unsupported.new(
1819
+ "`#{c_function}` takes `#{parameter.text}` there, which points at " \
1820
+ "nothing in particular, so there is no element type to hold " \
1821
+ "`#{name}` to; declare what it points at",
1822
+ argument.location)
1823
+ end
1824
+ wanted = CDeclaration::DATA_TYPES.fetch(parameter.element.fiddle).to_s
1825
+ unless storage == wanted
1826
+ raise Unsupported.new(
1827
+ "`#{c_function}` takes `#{parameter.text}` there, which is a " \
1828
+ "#{wanted} array, and `#{name}` is #{storage}; make it with " \
1829
+ "`CArray.new(:#{wanted}, [#{shape.first}])` -- an element type is " \
1830
+ "matched exactly here, as it is for a captured array",
1831
+ argument.location)
1832
+ end
1833
+ # The cells over every axis, because that is what the callee is handed:
1834
+ # a local array is packed row-major, so `double[3][4]` is the twelve
1835
+ # cells `const double a[12]` asks for.
1836
+ #
1837
+ # A declaration that names a length can only be held to a shape that
1838
+ # is numbers: the two are matched as the block is read, and a length
1839
+ # the kernel works out is not there yet. A pointer with no length is
1840
+ # the C that says "however many you have", and takes one.
1841
+ unless literal_shape?(shape)
1842
+ if parameter.sized?
1843
+ raise Unsupported.new(
1844
+ "`#{c_function}` takes `#{parameter.text}` there, which reads " \
1845
+ "#{parameter.array} cells, and the shape of `#{name}` is " \
1846
+ "worked out when the kernel runs -- so the two cannot be " \
1847
+ "matched here, and the function checks no subscript of its " \
1848
+ "own. Declare the parameter without a length " \
1849
+ "(`#{parameter.element.text} *`), which promises nothing about " \
1850
+ "how many cells there are, or give `#{name}` a shape written " \
1851
+ "out",
1852
+ argument.location)
1853
+ end
1854
+ return LocalArrayAddress.new(name, storage, shape, argument.location)
1855
+ end
1856
+ cells = shape.inject(1, :*)
1857
+ if parameter.sized? && cells < parameter.array
1858
+ shaped = shape.size == 1 ? "" :
1859
+ " (#{shape.join(' x ')}, packed row-major)"
1860
+ raise Unsupported.new(
1861
+ "`#{c_function}` takes `#{parameter.text}` there, which reads " \
1862
+ "#{parameter.array} cells, and `#{name}` has #{cells}#{shaped}. A " \
1863
+ "local array is on this block's stack and the function does not " \
1864
+ "check a subscript of its own, so the missing cells would be the " \
1865
+ "frame; give `#{name}` at least #{parameter.array}",
1866
+ argument.location)
1867
+ end
1868
+ LocalArrayAddress.new(name, storage, shape, argument.location)
1869
+ end
1870
+
1871
+ # ---- the functions the compiler brings with it ----
1872
+
1873
+ # Written as bare calls, the way `random(rng: r)` is: names of this
1874
+ # compiler's own rather than methods on an array, so that what each one
1875
+ # means is settled here instead of promising CArray's method semantics
1876
+ # -- `axis:`, masks skipped, an identity for an empty array, a CScalar
1877
+ # or a number back, one type promoted to another.
1878
+ #
1879
+ # A bare name with no arguments is a capture (`sum = 0.0` beside
1880
+ # `sum(w)` is a local and reads as one), and a name with a receiver is a
1881
+ # call to whatever holds it (`sum.call(x)`), so neither collides.
1882
+ INTRINSIC_VALUES = [:sum, :min, :max].freeze
1883
+ INTRINSIC_STATEMENTS = [:sort].freeze
1884
+ INTRINSIC_NAMES = (INTRINSIC_VALUES + INTRINSIC_STATEMENTS).freeze
1885
+
1886
+ # `sum(w)` / `min(w)` / `max(w)` in an expression, or nil where this
1887
+ # call is not one of them.
1888
+ def build_intrinsic_call (node)
1889
+ return nil unless intrinsic_spelling?(node)
1890
+ if INTRINSIC_STATEMENTS.include?(node.name)
1891
+ raise Unsupported.new(
1892
+ "`#{node.name}` rearranges the array and has no value to put " \
1893
+ "anywhere; write it on a line of its own, then read the cells " \
1894
+ "you want",
1895
+ node.location)
1896
+ end
1897
+ return nil unless INTRINSIC_VALUES.include?(node.name)
1898
+ name, storage, shape = intrinsic_array(node)
1899
+ IntrinsicCall.new(node.name, name, storage, shape, node.location)
1900
+ end
1901
+
1902
+ # `sort(w)` standing as a statement.
1903
+ def build_intrinsic_statement (node)
1904
+ return nil unless intrinsic_spelling?(node)
1905
+ if INTRINSIC_VALUES.include?(node.name)
1906
+ raise Unsupported.new(
1907
+ "`#{node.name}` works out a value, and standing on a line of its " \
1908
+ "own it puts that value nowhere; assign it, as in " \
1909
+ "`total = #{node.name}(...)`",
1910
+ node.location)
1911
+ end
1912
+ return nil unless INTRINSIC_STATEMENTS.include?(node.name)
1913
+ name, storage, shape = intrinsic_array(node)
1914
+ # Deliberately not `@calls_for_effect`: rearranging an array the body
1915
+ # made leaves nothing outside the kernel, so a kernel whose only work
1916
+ # is a `sort` has still put its work nowhere and is told so.
1917
+ IntrinsicStatement.new(node.name, name, storage, shape, node.location)
1918
+ end
1919
+
1920
+ def intrinsic_spelling? (node)
1921
+ node.is_a?(Prism::CallNode) && node.receiver.nil? && node.block.nil? &&
1922
+ INTRINSIC_NAMES.include?(node.name) && !node.arguments.nil?
1923
+ end
1924
+
1925
+ # The one argument, which is a local array of one axis.
1926
+ def intrinsic_array (node)
1927
+ arguments = node.arguments ? node.arguments.arguments : []
1928
+ unless arguments.size == 1
1929
+ raise Unsupported.new(
1930
+ "`#{node.name}` takes one local array, and #{arguments.size} " \
1931
+ "#{arguments.size == 1 ? 'argument was' : 'arguments were'} " \
1932
+ "given" +
1933
+ (node.name == :min || node.name == :max ?
1934
+ ". For the #{node.name} of two numbers write " \
1935
+ "`x #{node.name == :min ? '<' : '>'} y ? x : y`, and to hold " \
1936
+ "a value between two bounds write `x.clamp(lo, hi)`" : ""),
1937
+ node.location)
1938
+ end
1939
+ argument = arguments.first
1940
+ name = local_array_name(argument)
1941
+ refuse_an_intrinsic_argument(node, argument) unless name
1942
+ # What the four should do with a missing cell is not decided, and
1943
+ # there is more than one defensible answer: skip it, gather it at the
1944
+ # end the way `sort` gathers NaN, count it where a median is counted.
1945
+ # So the array is refused rather than given a meaning here.
1946
+ if @masked
1947
+ raise Unsupported.new(
1948
+ "`#{node.name}(#{name})` is over a local array of this kernel, " \
1949
+ "and the kernel carries masks -- so `#{name}` carries one beside " \
1950
+ "each of its cells, and what `#{node.name}` should do with a " \
1951
+ "missing one is not decided: whether to pass over it, to gather " \
1952
+ "it at the end as `sort` gathers NaN, or to count it where a " \
1953
+ "median counts. Write the loop yourself, deciding it there",
1954
+ node.location)
1955
+ end
1956
+ storage, shape = local_array_in_sight(name)
1957
+ unless shape.size == 1
1958
+ raise Unsupported.new(
1959
+ "`#{name}` has #{shape.size} axes, and `#{node.name}` takes an " \
1960
+ "array of one axis. A sweep over every cell would read well " \
1961
+ "enough for `sum`, and `sort` over one has no obvious meaning at " \
1962
+ "all, so the four keep one rule; walk the axes yourself, or copy " \
1963
+ "the cells you want into an array of one axis",
1964
+ node.location)
1965
+ end
1966
+ [name, storage, shape]
1967
+ end
1968
+
1969
+ # What was passed instead, said in the terms it was written in.
1970
+ def refuse_an_intrinsic_argument (node, argument)
1971
+ passed = captured_name(argument)
1972
+ if passed && @array_names.include?(passed)
1973
+ raise Unsupported.new(
1974
+ "`#{node.name}` takes a local array -- one this block made with " \
1975
+ "`CArray.double(n)` or its kin -- and `#{passed}` is an array " \
1976
+ "the block closed over. Walking all of it at every cell is a " \
1977
+ "pass over the whole array per cell, which is not what the line " \
1978
+ "looks like it costs; `#{passed}.#{node.name}` outside the " \
1979
+ "kernel says what it costs, and a loop written out says it too",
1980
+ node.location)
1981
+ end
1982
+ raise Unsupported.new(
1983
+ "`#{node.name}` takes a local array -- one this block made with " \
1984
+ "`CArray.double(n)` or its kin -- named on its own, and " \
1985
+ "`#{argument.slice}` is not one",
1986
+ node.location)
1987
+ end
1988
+
1989
+ # ---- the arrays a body makes for itself ----
1990
+
1991
+ LOCAL_ARRAY_SUBSCRIPTS = [:[], :[]=].freeze
1992
+
1993
+ # A constructor stands on the right of an assignment and nowhere else.
1994
+ # Everywhere else it is an array standing where a number goes -- in an
1995
+ # argument, under a subscript, as an operand -- and there is no name for
1996
+ # the C array to be declared under.
1997
+ def refuse_a_constructor_in_an_expression (node)
1998
+ return unless node
1999
+ return unless self.class.local_array_constructor?(node)
2000
+ raise Unsupported.new(
2001
+ "`#{node.slice}` makes an array, and a constructor stands on the " \
2002
+ "right of an assignment and nowhere else: write " \
2003
+ "`w = #{node.slice}` on a line of its own, then reach `w` at a " \
2004
+ "subscript",
2005
+ node.location)
2006
+ end
2007
+
2008
+ # `[storage type, shape, zeroed]` for a constructor, or nil where this
2009
+ # call is not one. Nil is the answer for everything else a body may
2010
+ # write, so nothing outside this vocabulary is touched; once the answer
2011
+ # is not nil the call is a constructor, and a spelling that cannot be
2012
+ # taken is refused here rather than further along.
2013
+ def read_local_array_constructor (node)
2014
+ return nil unless self.class.local_array_constructor?(node)
2015
+ refuse_a_local_array_in_this_spelling(node)
2016
+ if node.block
2017
+ raise Unsupported.new(
2018
+ "`#{node.slice}` fills the array from a block, and a local " \
2019
+ "array is either zero-filled or left as it stands; write the " \
2020
+ "fill out as a loop over its cells",
2021
+ node.location)
2022
+ end
2023
+ arguments = node.arguments ? node.arguments.arguments : []
2024
+ if arguments.any? { |argument| argument.is_a?(Prism::KeywordHashNode) }
2025
+ raise Unsupported.new(
2026
+ "`#{node.slice}` passes a keyword, and a local array is the " \
2027
+ "type and the shape only", node.location)
2028
+ end
2029
+ if node.receiver.is_a?(Prism::ConstantPathNode) &&
2030
+ node.receiver.slice != "::CArray"
2031
+ return refuse_a_type_class_constructor(node, arguments)
2032
+ end
2033
+ case node.name
2034
+ when :new then read_carray_new(node, arguments, true)
2035
+ when :empty then read_carray_empty(node, arguments)
2036
+ else read_type_constructor(node, arguments)
2037
+ end
2038
+ end
2039
+
2040
+ # `CArray.double(9)` and the rest of the singleton methods CArray's own
2041
+ # `Init` defines: the type is the name and the shape is the arguments.
2042
+ def read_type_constructor (node, arguments)
2043
+ if LOCAL_ARRAY_REFUSED_TYPES.include?(node.name)
2044
+ raise Unsupported.new(
2045
+ "`#{node.name}` holds #{LOCAL_ARRAY_REFUSED_TYPES_HOLD.fetch(node.name)} " \
2046
+ "rather than numbers, and a kernel computes with numbers; a " \
2047
+ "local array is one of " \
2048
+ "#{LOCAL_ARRAY_TYPES.keys.take(13).map { |name| "`#{name}`" }.join(', ')}",
2049
+ node.location)
2050
+ end
2051
+ if LOCAL_ARRAY_COMPATIBILITY_NAMES.include?(node.name)
2052
+ return refuse_a_compatibility_constructor(node, arguments, nil)
2053
+ end
2054
+ storage = LOCAL_ARRAY_TYPES.fetch(node.name)
2055
+ if arguments.empty?
2056
+ raise Unsupported.new(
2057
+ "`#{node.slice}` says no shape; a local array is written " \
2058
+ "`CArray.#{node.name}(n)` with the length written out",
2059
+ node.location)
2060
+ end
2061
+ [storage, read_local_array_shape(node, arguments), true]
2062
+ end
2063
+
2064
+ # `CArray.new(:int64, [256])` -- `rb_ca_initialize`, whose
2065
+ # `carray_safe_setup` is what fills the cells with zero.
2066
+ def read_carray_new (node, arguments, zeroed)
2067
+ unless arguments.size == 2
2068
+ raise Unsupported.new(
2069
+ "`CArray.#{node.name}` takes the type and the shape, as in " \
2070
+ "`CArray.#{node.name}(:float64, [9])`", node.location)
2071
+ end
2072
+ [read_local_array_type(node, arguments.first),
2073
+ read_local_array_shape(node, read_shape_arguments(node, arguments.last)),
2074
+ zeroed]
2075
+ end
2076
+
2077
+ # Two spellings share the name. `CArray.empty(:float64, [9])` is
2078
+ # carray's, and stands beside `CArray.new` with the fill left out;
2079
+ # `CArray.empty(9)` is the compatibility layer's NumPy form, where the
2080
+ # type is float64 by convention rather than by anything written down.
2081
+ def read_carray_empty (node, arguments)
2082
+ carray_form = arguments.size == 2 &&
2083
+ arguments.first.is_a?(Prism::SymbolNode) &&
2084
+ arguments.last.is_a?(Prism::ArrayNode)
2085
+ return read_carray_new(node, arguments, false) if carray_form
2086
+ refuse_a_compatibility_constructor(node, arguments, nil)
2087
+ end
2088
+
2089
+ def read_shape_arguments (node, argument)
2090
+ unless argument.is_a?(Prism::ArrayNode)
2091
+ raise Unsupported.new(
2092
+ "the shape `CArray.#{node.name}` takes is an Array, as in " \
2093
+ "`CArray.#{node.name}(:float64, [9])`", node.location)
2094
+ end
2095
+ argument.elements
2096
+ end
2097
+
2098
+ # The type is a Symbol written out. One of CArray's constants would be
2099
+ # a name looked up outside the block, and one kernel serves every value
2100
+ # of a capture -- so a type that arrived that way would not be in the C
2101
+ # at all.
2102
+ def read_local_array_type (node, argument)
2103
+ unless argument.is_a?(Prism::SymbolNode)
2104
+ raise Unsupported.new(
2105
+ "the type `CArray.#{node.name}` takes is a Symbol written out, " \
2106
+ "as in `CArray.#{node.name}(:float64, [9])`; " \
2107
+ "`#{argument.slice}` is looked up where the block was written, " \
2108
+ "and one compiled kernel serves every value of that",
2109
+ node.location)
2110
+ end
2111
+ name = argument.unescaped.to_sym
2112
+ if LOCAL_ARRAY_REFUSED_TYPES.include?(name)
2113
+ raise Unsupported.new(
2114
+ "`:#{name}` holds #{LOCAL_ARRAY_REFUSED_TYPES_HOLD.fetch(name)} " \
2115
+ "rather than numbers, and a kernel computes with numbers",
2116
+ node.location)
2117
+ end
2118
+ LOCAL_ARRAY_TYPES.fetch(name) do
2119
+ raise Unsupported.new(
2120
+ "`#{argument.slice}` is not a type a local array holds; they are " \
2121
+ "#{LOCAL_ARRAY_TYPES.keys.take(13).map { |n| "`:#{n}`" }.join(', ')}",
2122
+ node.location)
2123
+ end
2124
+ end
2125
+
2126
+ # The shape, one entry an axis: an Integer where the block wrote a
2127
+ # number, and a LocalArrayExtent where it wrote an expression over the
2128
+ # integers it captured. A number is what the C declares and what a
2129
+ # subscript is checked against as the block is read; an expression is
2130
+ # worked out once at the kernel's entry, and every check against it
2131
+ # happens where the cell is reached.
2132
+ def read_local_array_shape (node, arguments)
2133
+ if arguments.empty?
2134
+ raise Unsupported.new(
2135
+ "`#{node.slice}` says no shape; a local array is written " \
2136
+ "`CArray.#{node.name}(n)` with the length written out",
2137
+ node.location)
2138
+ end
2139
+ arguments.map { |argument|
2140
+ extent = literal_integer(argument)
2141
+ next captured_extent(node, argument) unless extent
2142
+ unless extent.positive?
2143
+ raise Unsupported.new(
2144
+ "a local array holds at least one cell, and `#{argument.slice}` " \
2145
+ "asks for #{extent}", node.location)
2146
+ end
2147
+ extent
2148
+ }
2149
+ end
2150
+
2151
+ # A length the kernel works out at its entry, from the integers it
2152
+ # captured.
2153
+ #
2154
+ # It may read nothing that varies from cell to cell: the array is one
2155
+ # allocation for the whole pass, made before the first cell, so an
2156
+ # extent over a loop index or an array's cell is a length that would
2157
+ # have to change underneath it. That is what is refused here, and the
2158
+ # message says which of the two was written.
2159
+ def captured_extent (node, argument)
2160
+ built = build(argument)
2161
+ varies = extent_varies_by_cell(built)
2162
+ if varies
2163
+ raise Unsupported.new(
2164
+ "`#{argument.slice}` is the shape of a local array, and " \
2165
+ "#{varies} -- the array is one allocation made before the first " \
2166
+ "cell, so its length cannot change from cell to cell. Take the " \
2167
+ "widest length the pass needs, or pass a captured array of " \
2168
+ "workspace",
2169
+ node.location)
2170
+ end
2171
+ LocalArrayExtent.new(argument.slice, built)
2172
+ end
2173
+
2174
+ # What in an extent expression is not settled before the loop runs, in
2175
+ # the words it was written in, or nil where everything in it is.
2176
+ def extent_varies_by_cell (node)
2177
+ case node
2178
+ when IntegerLiteral, CaptureRead then nil
2179
+ when UnaryMinus then extent_varies_by_cell(node.operand)
2180
+ when BinaryOperation
2181
+ unless [:+, :-, :*].include?(node.operator)
2182
+ return "`#{node.operator}` is not one of `+`, `-` and `*`"
2183
+ end
2184
+ extent_varies_by_cell(node.left) || extent_varies_by_cell(node.right)
2185
+ when IndexVariable then "`#{node.name}` is a loop index"
2186
+ when LocalRead then "`#{node.name}` is worked out inside the block"
2187
+ when ElementRead then "`#{node.array}` is a cell of an array"
2188
+ when LocalArrayRead then "`#{node.name}` is a cell of an array"
2189
+ else "it is not an integer expression over the captured integers"
2190
+ end
2191
+ end
2192
+
2193
+ # `CArray.zeros(4)`, `CArray.empty(4)`, `CArray.ones(4)`: the
2194
+ # compatibility layer, which `data_type_extension.rb` opens by calling
2195
+ # itself "Numo / NumPy-style" and "not the 'main' carray API". The
2196
+ # words inside a block are carray's own.
2197
+ def refuse_a_compatibility_constructor (node, arguments, type)
2198
+ storage = type || "float64"
2199
+ length = arguments.empty? ? nil : literal_integer(arguments.first)
2200
+ shape = length ? "[#{length}]" : "[n]"
2201
+ raise Unsupported.new(
2202
+ "`#{node.slice}` is CArray's Numo/NumPy compatibility layer, and " \
2203
+ "a kernel body is written in carray's own words: " \
2204
+ "`CArray.new(:#{storage}, #{shape})` for cells that start at zero, " \
2205
+ "or `CArray.empty(:#{storage}, #{shape})` where every cell is " \
2206
+ "written before it is read",
2207
+ node.location)
2208
+ end
2209
+
2210
+ # `CArray::Int64.empty(4)` and `CArray::Int64.zeros(4)`: the same layer
2211
+ # reached through a type class, which is the Numo spelling of it.
2212
+ def refuse_a_type_class_constructor (node, arguments)
2213
+ tail = node.receiver.slice.split("::").last.to_s
2214
+ type = LOCAL_ARRAY_TYPES[tail.downcase.to_sym]
2215
+ refuse_a_compatibility_constructor(node, arguments, type)
2216
+ end
2217
+
2218
+ # Which entry points take one, and which release takes the rest. The
2219
+ # shape of the check is the mode this analyzer was made in, because that
2220
+ # is what says how the block is being read.
2221
+ LOCAL_ARRAY_ELSEWHERE = {
2222
+ :contract => ["a contraction's body", nil],
2223
+ }.freeze
2224
+
2225
+ def refuse_a_local_array_in_this_spelling (node)
2226
+ mode = if @contract then :contract
2227
+ end
2228
+ return unless mode
2229
+ where, when_ = LOCAL_ARRAY_ELSEWHERE.fetch(mode)
2230
+ if mode == :contract
2231
+ raise Unsupported.new(
2232
+ "`#{node.slice}` makes a local array, and #{where} is one " \
2233
+ "expression -- with, at most, an assignment into an array of its " \
2234
+ "own -- so there is no run of statements for a workspace to be " \
2235
+ "used by. Write the body as `CArray.jit_for`",
2236
+ node.location)
2237
+ end
2238
+ raise Unsupported.new(
2239
+ "`#{node.slice}` makes a local array, which #{where} takes in " \
2240
+ "#{when_}; `CArray.jit_for` takes one now, and a captured array of " \
2241
+ "workspace works here today",
2242
+ node.location)
2243
+ end
2244
+
2245
+ # The declaration itself.
2246
+ def declare_local_array (name, storage, shape, zeroed, location, depth)
2247
+ if index_in_scope?(name)
2248
+ raise Unsupported.new(
2249
+ "`#{name}` is a loop index, and the loop is what owns it; give " \
2250
+ "the array a name of its own",
2251
+ location)
2252
+ end
2253
+ if @local_names.include?(name) && local_in_sight?(name)
2254
+ raise Unsupported.new(
2255
+ "`#{name}` holds a number here, and a local array is not a " \
2256
+ "number; the two cannot be one C variable, so give the array a " \
2257
+ "name of its own",
2258
+ location)
2259
+ end
2260
+ heap = heap_array?(storage, shape)
2261
+ refuse_a_heap_array_in_a_body(name, storage, shape, location) if heap
2262
+ @local_array_names << name unless @local_array_names.include?(name)
2263
+ @local_arrays[name] = [storage, shape]
2264
+ scope = [@scopes.size - 1 - depth, 0].max
2265
+ @scopes[scope] << name unless @scopes[scope].include?(name)
2266
+ entry = [scope, name, storage, shape, heap]
2267
+ @local_array_declarations << entry unless @local_array_declarations.include?(entry)
2268
+ @clears_a_local_array ||= zeroed
2269
+ LocalArrayDeclaration.new(name, storage, shape, zeroed, location, scope,
2270
+ heap)
2271
+ end
2272
+
2273
+ # Where the array goes. The stack while it is small enough to be a
2274
+ # frame's business, and the heap otherwise -- which is a placement and
2275
+ # not a refusal: nothing is turned away for its size any more.
2276
+ #
2277
+ # Three ways onto the heap: a length the kernel works out, an array
2278
+ # larger than one frame should hold, and an array that fits on its own
2279
+ # but not beside the ones this kernel already has.
2280
+ def heap_array? (storage, shape)
2281
+ return true unless literal_shape?(shape)
2282
+ bytes = local_array_bytes(storage, shape)
2283
+ return true if bytes > LOCAL_ARRAY_BYTE_LIMIT
2284
+ bytes + stack_array_bytes > LOCAL_ARRAY_TOTAL_BYTE_LIMIT
2285
+ end
2286
+
2287
+ def literal_shape? (shape)
2288
+ shape.all? { |extent| extent.is_a?(Integer) }
2289
+ end
2290
+
2291
+ # What the arrays declared so far take on the stack. Two sibling
2292
+ # loops that each declare `w` are one entry: their blocks do not
2293
+ # overlap, so neither do their frames.
2294
+ def stack_array_bytes
2295
+ @local_array_declarations.sum { |_scope, _name, storage, shape, heap|
2296
+ heap ? 0 : local_array_bytes(storage, shape)
2297
+ }
2298
+ end
2299
+
2300
+ # A compiled function's body is called once per cell, so an allocation
2301
+ # in it would be one per cell -- and the body has no entry of its own
2302
+ # outside the kernel's loop to make it at instead.
2303
+ def refuse_a_heap_array_in_a_body (name, storage, shape, location)
2304
+ return unless @function
2305
+ reason =
2306
+ if literal_shape?(shape)
2307
+ "`#{name}` asks for " \
2308
+ "#{local_array_bytes_phrase(storage, shape)}, which is past the " \
2309
+ "#{LOCAL_ARRAY_BYTE_LIMIT} a frame here holds"
2310
+ else
2311
+ "the shape of `#{name}` is worked out when the kernel runs"
2312
+ end
2313
+ raise Unsupported.new(
2314
+ "#{reason}, so `#{name}` would have to be allocated -- and a " \
2315
+ "function's body is called once per cell, which would be one " \
2316
+ "allocation per cell. Make the array in the kernel that calls " \
2317
+ "this and pass it in, as a pointer parameter",
2318
+ location)
2319
+ end
2320
+
2321
+ # A shadow byte a cell where the kernel carries masks: it is on the
2322
+ # same stack as the cells, so it is counted with them.
2323
+ def local_array_bytes (storage, shape)
2324
+ cells = shape.inject(1) { |product, extent| product * extent }
2325
+ (LOCAL_ARRAY_STORAGE_BYTES.fetch(storage) + (@masked ? 1 : 0)) * cells
2326
+ end
2327
+
2328
+ # How the refusal accounts for a size the block did not write out.
2329
+ def local_array_bytes_phrase (storage, shape)
2330
+ cells = shape.inject(1) { |product, extent| product * extent }
2331
+ values = LOCAL_ARRAY_STORAGE_BYTES.fetch(storage) * cells
2332
+ return "#{values} bytes of #{storage}" unless @masked
2333
+ "#{values + cells} bytes -- #{values} of #{storage} and #{cells} of " \
2334
+ "the mask beside it, a byte a cell, since this kernel carries masks"
2335
+ end
2336
+
2337
+ # A name is in sight where some scope still standing holds it. The
2338
+ # scopes are pushed and popped with the inner loops' blocks, so a name
2339
+ # an inner loop introduced is out of sight after it -- which is what
2340
+ # Ruby says about it too.
2341
+ def local_in_sight? (name)
2342
+ @scopes.any? { |scope| scope.include?(name) }
2343
+ end
2344
+
2345
+ # The name of the local array this receiver is, or nil.
2346
+ #
2347
+ # A name the body did make an array under, but in a block that has since
2348
+ # closed, is neither: it says so rather than falling through to be
2349
+ # refused for not being a captured array, which is not what happened.
2350
+ def local_array_name (node)
2351
+ name = case node
2352
+ when Prism::LocalVariableReadNode
2353
+ node.name
2354
+ when Prism::CallNode
2355
+ node.name if node.receiver.nil? && node.arguments.nil? &&
2356
+ node.block.nil?
2357
+ end
2358
+ return nil unless name && @local_array_names.include?(name)
2359
+ unless local_in_sight?(name)
2360
+ raise Unsupported.new(
2361
+ "`#{name}` belongs to the loop block it was assigned in, and is " \
2362
+ "not there after it; make the array before the loop",
2363
+ node.location)
2364
+ end
2365
+ name
2366
+ end
2367
+
2368
+ # `[storage, shape]` for the declaration in sight under this name.
2369
+ def local_array_in_sight (name)
2370
+ @local_arrays.fetch(name)
2371
+ end
2372
+
2373
+ # A local array's subscripts.
2374
+ #
2375
+ # Not `read_subscripts`. That one answers for the operand tables: it
2376
+ # refuses an index where the block takes none, records the array's rank
2377
+ # in a table shared with every other array, and hands a window its
2378
+ # offsets instead. A local array is in none of that -- its rank is its
2379
+ # shape's, and its subscripts are written wherever the block stands.
2380
+ # What the two share is only the two ways a position is checked.
2381
+ def local_array_subscripts (name, shape, arguments, location)
2382
+ unless arguments.size == shape.size
2383
+ # A block may give one of its own arrays the name of the window it
2384
+ # was handed. Ruby reads that as rebinding the parameter, and so
2385
+ # does this -- but then the window's own spelling stops working, and
2386
+ # the axis count alone is a poor account of why.
2387
+ if @windows.include?(name)
2388
+ raise Unsupported.new(
2389
+ "`#{name}` is the window this block was given, and the block " \
2390
+ "also makes an array of its own under that name -- which " \
2391
+ "rebinds it, here as in Ruby, so from that line on `#{name}` " \
2392
+ "is the array and the window has no name left. " \
2393
+ "`#{name}[#{Array.new(rank, 0).join(', ')}]` is the window's " \
2394
+ "spelling and wants #{rank} #{rank == 1 ? 'offset' : 'offsets'}; " \
2395
+ "give the array a name of its own",
2396
+ location)
2397
+ end
2398
+ raise Unsupported.new(
2399
+ "`#{name}` has #{shape.size == 1 ? 'one axis' : "#{shape.size} axes"}, " \
2400
+ "so it takes #{shape.size == 1 ? 'one subscript' : "#{shape.size} subscripts"}, " \
2401
+ "and #{arguments.size} #{arguments.size == 1 ? 'was' : 'were'} written",
2402
+ location)
2403
+ end
2404
+ arguments.each_with_index.map { |argument, axis|
2405
+ local_array_subscript(name, shape, axis, argument, location)
2406
+ }
2407
+ end
2408
+
2409
+ # How a message names the thing that is out of range.
2410
+ #
2411
+ # With one axis the whole subscript is the thing, and quoting it back is
2412
+ # exactly right. With more than one it is not: the block wrote
2413
+ # `m[r, c]`, so quoting `m[c]` would be a spelling nobody typed, and an
2414
+ # extent on its own ("a local array of 4 cells") is a poor account of a
2415
+ # 3 x 4 one. So the axis is named instead, and the shape with it.
2416
+ def local_array_subject (name, shape, axis, argument)
2417
+ if shape.size == 1
2418
+ "`#{name}[#{argument.slice}]`"
2419
+ else
2420
+ "the subscript `#{argument.slice}` on axis #{axis} of `#{name}`, " \
2421
+ "which is #{shape.join(' x ')},"
2422
+ end
2423
+ end
2424
+
2425
+ # What the cells of the axis in question are, said whole rather than as
2426
+ # a fragment to glue on: the two ranks want different sentences and
2427
+ # composing them produced "is outside and that axis has 4 cells".
2428
+ def local_array_cells_phrase (shape, axis)
2429
+ extent = shape[axis]
2430
+ unless extent.is_a?(Integer)
2431
+ length = "a length of `#{extent}`, which the kernel works out"
2432
+ return shape.size == 1 ? "a local array of #{length}"
2433
+ : "that axis's #{length}"
2434
+ end
2435
+ cells = "#{extent} #{extent == 1 ? 'cell' : 'cells'}"
2436
+ run = extent == 1 ? "only cell is 0" : "cells are 0 to #{extent - 1}"
2437
+ shape.size == 1 ? "a local array of #{cells}, whose #{run}"
2438
+ : "that axis's #{cells}, whose #{run}"
2439
+ end
2440
+
2441
+ # (a) An index whose loop states its range in literals reaches a set of
2442
+ # positions that is known now, so it is checked now and the C carries no
2443
+ # test. (b) Anything else is checked where the cell is reached.
2444
+ #
2445
+ # A literal position is (a) as well, its range being itself.
2446
+ def local_array_subscript (name, shape, axis, argument, location)
2447
+ extent = shape[axis]
2448
+ # An axis whose length the kernel works out has nothing for (a) to
2449
+ # compare against: a literal position included, since 3 is inside an
2450
+ # array of 4 cells and outside one of 2. So every position on such
2451
+ # an axis is checked where the cell is reached.
2452
+ return [nil, build(argument)] unless extent.is_a?(Integer)
2453
+ if (index = index_name(argument)) && index_in_scope?(index)
2454
+ settled = checked_index_subscript(name, shape, axis, index, 0,
2455
+ argument, location)
2456
+ return settled if settled
2457
+ elsif argument.is_a?(Prism::CallNode) &&
2458
+ [:+, :-].include?(argument.name) &&
2459
+ (receiver = index_name(argument.receiver)) &&
2460
+ index_in_scope?(receiver)
2461
+ arguments = argument.arguments ? argument.arguments.arguments : []
2462
+ if arguments.size == 1 && (offset = literal_integer(arguments.first))
2463
+ offset = -offset if argument.name == :-
2464
+ settled = checked_index_subscript(name, shape, axis, receiver,
2465
+ offset, argument, location)
2466
+ return settled if settled
2467
+ end
2468
+ end
2469
+ if (position = literal_integer(argument))
2470
+ unless (0...extent).cover?(position)
2471
+ raise Unsupported.new(
2472
+ "#{local_array_subject(name, shape, axis, argument)} is " \
2473
+ "outside #{local_array_cells_phrase(shape, axis)}" +
2474
+ (position.negative? ?
2475
+ ". A subscript here counts from the start, so CArray's " \
2476
+ "`#{name}[-1]` for the last cell is written " \
2477
+ "`#{name}[#{extent - 1}]`" : ""),
2478
+ location)
2479
+ end
2480
+ return [nil, IntegerLiteral.new(position, location)]
2481
+ end
2482
+ [nil, build(argument)]
2483
+ end
2484
+
2485
+ # The subscript settled as the block is read, or nil where it cannot be.
2486
+ #
2487
+ # Nil is not "allow it": it sends the axis to the check at the access,
2488
+ # which is where every position this compiler cannot settle goes. The
2489
+ # two kinds it cannot settle are a loop whose bound is a captured
2490
+ # integer and one whose range is written over another index -- in both
2491
+ # the reach is not knowable until the kernel runs. Returning the index
2492
+ # unchecked would leave a write past the end of a stack array with
2493
+ # nothing at all looking at it.
2494
+ def checked_index_subscript (name, shape, axis, index, offset, argument,
2495
+ location)
2496
+ identifier = index_identifier(index)
2497
+ return nil unless index_reach(identifier)
2498
+ verify_index_reach(name, shape, axis, identifier, offset, argument,
2499
+ location)
2500
+ [identifier, offset]
2501
+ end
2502
+
2503
+ # The lowest and highest position an index takes, where its loop says so
2504
+ # in literals; nil where it does not, or where the loop runs no passes
2505
+ # at all and so reaches nothing.
2506
+ #
2507
+ # Computed rather than enumerated: `(0...10_000_000).each` states its
2508
+ # range in literals too.
2509
+ def index_reach (identifier)
2510
+ range = @inner_ranges[identifier]
2511
+ return nil unless range
2512
+ from, to, step = range
2513
+ return nil unless from.is_a?(IntegerLiteral) && to.is_a?(IntegerLiteral)
2514
+ first = from.value
2515
+ last_excluded = to.value
2516
+ if step.positive?
2517
+ return nil unless first < last_excluded
2518
+ [first, first + ((last_excluded - 1 - first) / step) * step]
2519
+ else
2520
+ return nil unless first > last_excluded
2521
+ stride = -step
2522
+ [first - ((first - (last_excluded + 1)) / stride) * stride, first]
2523
+ end
2524
+ end
2525
+
2526
+ # This looks at the loop's range and not at what stands around the
2527
+ # access, which is the character the captured arrays' own bounds check
2528
+ # already has: an `if` around the line does not make the reach smaller,
2529
+ # because the reach is the loop's. Narrowing the loop's range is what
2530
+ # makes it smaller.
2531
+ def verify_index_reach (name, shape, axis, identifier, offset, argument,
2532
+ location)
2533
+ extent = shape[axis]
2534
+ reach = index_reach(identifier)
2535
+ return unless reach
2536
+ low = reach.first + offset
2537
+ high = reach.last + offset
2538
+ return if low >= 0 && high < extent
2539
+ outside = low.negative? ? low : high
2540
+ spelled = @index_sources.fetch(identifier, identifier)
2541
+ raise Unsupported.new(
2542
+ "#{local_array_subject(name, shape, axis, argument)} reaches cell " \
2543
+ "#{outside} of #{local_array_cells_phrase(shape, axis)}: " \
2544
+ "`#{spelled}` runs #{reach.first} to #{reach.last} here. Narrow " \
2545
+ "the loop's range, or give `#{name}` more cells -- an `if` around " \
2546
+ "the line does not narrow the reach, which is the loop's",
2547
+ location)
2548
+ end
2549
+
2550
+ # In the whole-array spelling every name in the block is a cell, so an
2551
+ # assignment to a name that is an array outside writes that array's
2552
+ # cell -- the one the loop is on, the same cell every read in the block
2553
+ # is at. A name that is not an array is a local, as it is anywhere
2554
+ # else.
2555
+ #
2556
+ # `depth` is Prism's: how many scopes out the variable is. Counted from
2557
+ # the statement, it says which of the scopes here holds the local -- and
2558
+ # one past the kernel's block is a name of the method around it, which
2559
+ # the kernel holds as its own, as it always has.
2560
+ def assign_local (name, expression, location, depth = 0)
2561
+ if @whole_array && @array_names.include?(name)
2562
+ return whole_array_write(name, expression, location)
2563
+ end
2564
+ # An index is the loop's, not the block's. Ruby reads `i = 2` as
2565
+ # rebinding the parameter and leaves the loop alone; the C would
2566
+ # assign to the counter, so the loop would walk somewhere else -- to
2567
+ # cells outside the array, given a value outside its extent. The two
2568
+ # do not mean the same thing, so this is not compiled.
2569
+ if index_in_scope?(name)
2570
+ raise Unsupported.new(
2571
+ "`#{name}` is a loop index, and assigning to it here would " \
2572
+ "move the loop rather than the value: in Ruby the same line " \
2573
+ "rebinds the parameter and the loop runs on. Use a local of " \
2574
+ "another name",
2575
+ location)
2576
+ end
2577
+ if @local_array_names.include?(name)
2578
+ if local_in_sight?(name)
2579
+ raise Unsupported.new(
2580
+ "`#{name}` is a local array here, and cannot hold a number; the " \
2581
+ "two cannot be one C variable, so give the number a name of its " \
2582
+ "own",
2583
+ location)
2584
+ end
2585
+ # Out of sight, the array belonged to a block that has closed, and
2586
+ # this line introduces a variable that merely shares its name. The
2587
+ # record goes, so a read below is read as the number it now is.
2588
+ # The array itself stays counted: its C declaration is still in that
2589
+ # block, and still on that block's stack.
2590
+ @local_array_names.delete(name)
2591
+ @local_arrays.delete(name)
2592
+ end
2593
+ @local_names << name unless @local_names.include?(name)
2594
+ scope = [@scopes.size - 1 - depth, 0].max
2595
+ @scopes[scope] << name unless @scopes[scope].include?(name)
2596
+ Assignment.new(name, expression, location, scope)
2597
+ end
2598
+
2599
+ # What a name on the left of a parallel assignment may not be, said the
2600
+ # way it was written. A cell may: `a[i], a[j] = a[j], a[i]` is the swap
2601
+ # a sort is written with, and each half of it is the write `a[i] = ...`
2602
+ # already was.
2603
+ TARGET_DESCRIPTIONS = {
2604
+ Prism::MultiTargetNode => "a nested target",
2605
+ Prism::SplatNode => "a splat",
2606
+ Prism::ImplicitRestNode => "a bare comma",
2607
+ Prism::InstanceVariableTargetNode => "an instance variable",
2608
+ Prism::GlobalVariableTargetNode => "a global variable",
2609
+ Prism::ClassVariableTargetNode => "a class variable",
2610
+ Prism::ConstantTargetNode => "a constant",
2611
+ Prism::ConstantPathTargetNode => "a constant",
2612
+ Prism::CallTargetNode => "an attribute",
2613
+ }.freeze
2614
+
2615
+ TARGET_HINTS = {
2616
+ Prism::MultiTargetNode => "it unpacks one value into several, and " \
2617
+ "nothing in a kernel holds several; write " \
2618
+ "the names out flat",
2619
+ Prism::SplatNode => "it takes however many values are left " \
2620
+ "over, and how many there are is the one " \
2621
+ "thing a C variable cannot be",
2622
+ Prism::ImplicitRestNode => "it drops the rest of the values, and how " \
2623
+ "many that is has to be written out here",
2624
+ }.freeze
2625
+
2626
+ # `prev, cur = cur, prev + cur`, and the swap it is the general case of.
2627
+ #
2628
+ # Ruby settles every value on the right before it writes any of them,
2629
+ # and so does this: each value is assigned to a name of its own first,
2630
+ # and only then are the writes made. The other way round, the line
2631
+ # above would write `prev` and then read the new one back while working
2632
+ # out `cur` -- which is the one thing the spelling exists to prevent,
2633
+ # and why `jit_for` wanted it. A recurrence advances by a parallel
2634
+ # assignment in Ruby; until now it had to be unpicked by hand here, and
2635
+ # the temporary a reader had to introduce is where the bug lives.
2636
+ #
2637
+ # The names are written even where nothing on the right reads what the
2638
+ # left writes. A rule that dropped them sometimes would be one a reader
2639
+ # of the generated C had to know before they could tell what a line
2640
+ # meant, and a C compiler drops a name assigned once and read once
2641
+ # without being asked.
2642
+ def build_parallel_assignment (node)
2643
+ targets = parallel_targets(node)
2644
+ values = parallel_values(node, targets)
2645
+ temporaries = values.map { |value|
2646
+ assign_local(parallel_value_name, build(value), value.location)
2647
+ }
2648
+ writes = targets.each_with_index.map { |target, k|
2649
+ value = build_name_read(temporaries[k].name, target.location)
2650
+ if target.is_a?(Prism::LocalVariableTargetNode)
2651
+ assign_local(target.name, value, target.location, target.depth)
2652
+ else
2653
+ indices = target.arguments ? target.arguments.arguments : []
2654
+ element_write(target, indices, value, target.location)
2655
+ end
2656
+ }
2657
+ ParallelAssignment.new(temporaries + writes, node.location)
2658
+ end
2659
+
2660
+ def parallel_targets (node)
2661
+ targets = node.lefts + (node.rest ? [node.rest] : []) + node.rights
2662
+ targets.each do |target|
2663
+ next if target.is_a?(Prism::LocalVariableTargetNode) ||
2664
+ target.is_a?(Prism::IndexTargetNode)
2665
+ described = TARGET_DESCRIPTIONS[target.class]
2666
+ hint = TARGET_HINTS[target.class]
2667
+ raise Unsupported.new(
2668
+ "a parallel assignment writes names and cells here -- " \
2669
+ "`a, b = b, a`, `a[i], a[j] = a[j], a[i]` -- and this one writes " \
2670
+ "#{described || node_name(target)}#{hint ? " -- #{hint}" : ""}",
2671
+ target.location)
2672
+ end
2673
+ targets
2674
+ end
2675
+
2676
+ # The values, told apart from the one value Ruby would spread.
2677
+ #
2678
+ # `a, b = b, a` writes its values out, and Prism hands them over as an
2679
+ # array with no brackets around it. Brackets are Ruby's other spelling
2680
+ # -- `a, b = [b, a]` -- and it means something else: one value, taken
2681
+ # apart across the names. Nothing in a kernel is a value that can be
2682
+ # taken apart, so the two are told apart here rather than quietly read
2683
+ # as one.
2684
+ def parallel_values (node, targets)
2685
+ value = node.value
2686
+ unless value.is_a?(Prism::ArrayNode) && value.opening_loc.nil?
2687
+ raise Unsupported.new(
2688
+ "a parallel assignment takes one value per name, written out on " \
2689
+ "the right, as in `a, b = b, a`. Ruby takes a single value apart " \
2690
+ "across the names, and a kernel computes numbers -- there is " \
2691
+ "nothing here to take apart",
2692
+ value.location)
2693
+ end
2694
+ if (splat = value.elements.find { |element|
2695
+ element.is_a?(Prism::SplatNode) })
2696
+ raise Unsupported.new(
2697
+ "`*` spreads a value across the names, and how many it comes to " \
2698
+ "is the one thing a C variable cannot be; write the values out",
2699
+ splat.location)
2700
+ end
2701
+ unless value.elements.size == targets.size
2702
+ # Ruby has an answer for either way round, and neither is one a
2703
+ # kernel can keep: the names it runs out of values for are nil, and
2704
+ # a value it runs out of names for is dropped.
2705
+ raise Unsupported.new(
2706
+ "this writes #{count_of(targets.size, "name")} and has " \
2707
+ "#{count_of(value.elements.size, "value")} for them. Ruby " \
2708
+ "#{value.elements.size < targets.size ?
2709
+ "leaves the rest of the names nil, which is not a number a " \
2710
+ "local or a cell can hold" :
2711
+ "drops the values it has no name for, and a value computed " \
2712
+ "and dropped is a line that does nothing"}",
2713
+ node.location)
2714
+ end
2715
+ value.elements
2716
+ end
2717
+
2718
+ def count_of (n, noun)
2719
+ "#{n} #{n == 1 ? noun : noun + "s"}"
2720
+ end
2721
+
2722
+ # A name for a value on the right that no name in the block can be, and
2723
+ # that no other statement uses.
2724
+ #
2725
+ # The block's own text settles the first half: a local, a capture, an
2726
+ # index and an array all appear in it, so a name that appears nowhere
2727
+ # in it is none of them. An underscore is added until that holds,
2728
+ # which keeps the same block reading the same way every time -- it has
2729
+ # to, the generated C being part of the cache key.
2730
+ #
2731
+ # The second half is the counter. Two parallel assignments sharing a
2732
+ # name would be two statements sharing a C variable, and a variable
2733
+ # live at the head of a loop is one the loop carries: a body that
2734
+ # swapped Integers before the loop and Floats inside it was refused for
2735
+ # changing the type of `value1` on the way round -- a name its author
2736
+ # never wrote, about a value that is dead by the end of the line it is
2737
+ # on. A name of its own per statement cannot be carried anywhere.
2738
+ def parallel_value_name
2739
+ @parallel_value_prefix ||= begin
2740
+ text = (@source || @node&.slice).to_s
2741
+ prefix = "value"
2742
+ prefix = "_" + prefix while text.include?(prefix)
2743
+ prefix
2744
+ end
2745
+ @parallel_value_count = @parallel_value_count.to_i + 1
2746
+ :"#{@parallel_value_prefix}#{@parallel_value_count}"
2747
+ end
2748
+
2749
+ # `x += e` is `x = x + e`, and is read as exactly that: the name is
2750
+ # read where Ruby reads it -- before the right-hand side, and by the
2751
+ # same rule, so a name the block has not assigned yet says so rather
2752
+ # than starting from whatever C left in the variable.
2753
+ def build_operator_assignment (node)
2754
+ operator = assignment_operator(node)
2755
+ read = build_name_read(node.name, node.location)
2756
+ expression = combined(operator, read, build(node.value), node.location)
2757
+ assign_local(node.name, expression, node.location, node.depth)
2758
+ end
2759
+
2760
+ # `a[i] += e` is `a[i] = a[i] + e`, cell for cell -- including under a
2761
+ # mask, where the read is a read like any other and carries what it
2762
+ # finds into what is written.
2763
+ def build_operator_element_write (node)
2764
+ operator = assignment_operator(node)
2765
+ indices = node.arguments ? node.arguments.arguments : []
2766
+ read = build_element_read(node, indices)
2767
+ expression = combined(operator, read, build(node.value), node.location)
2768
+ element_write(node, indices, expression, node.location)
2769
+ end
2770
+
2771
+ # The operators an assignment may carry are the ones an expression may:
2772
+ # `||=` and `&&=` are refused where they are read, being about nil and
2773
+ # false rather than about arithmetic.
2774
+ def assignment_operator (node)
2775
+ operator = node.binary_operator
2776
+ unless (ARITHMETIC_OPERATORS + BIT_OPERATORS + [:**]).include?(operator)
2777
+ raise Unsupported.new(
2778
+ "`#{operator}=` is not one of the operators a kernel computes " \
2779
+ "with", node.location)
2780
+ end
2781
+ operator
2782
+ end
2783
+
2784
+ def combined (operator, left, right, location)
2785
+ return power_node(left, right, location) if operator == :**
2786
+ BinaryOperation.new(operator, left, right, location)
2787
+ end
2788
+
1195
2789
  def build_element_write (node)
1196
- return build_inner_loop(node) if node.name == :each || node.name == :times
2790
+ if node.name == :each || node.name == :times || node.name == :step
2791
+ return build_inner_loop(node)
2792
+ end
1197
2793
  unless node.name == :[]=
1198
2794
  # An expression standing alone in this spelling is a computation
1199
2795
  # nobody can see: there is no index to have written it against, and
@@ -1205,54 +2801,98 @@ class CArray
1205
2801
  "`CArray.jit_map`",
1206
2802
  node.location)
1207
2803
  end
1208
- raise Unsupported.new(
1209
- "a kernel body holds assignments, `if`, `while`, `(a...b).each` " \
1210
- "and `n.times` only, got a call to `#{node.name}`",
1211
- node.location)
2804
+ if INNER_LOOP_SPELLINGS.include?(node.name)
2805
+ raise Unsupported.new(
2806
+ "an inner loop is written `(a...b).each`, `n.times` or " \
2807
+ "`a.step(b, s)`, which is the spelling an extent takes a " \
2808
+ "stride and a direction in -- `#{node.name}` is not one of " \
2809
+ "them",
2810
+ node.location)
2811
+ end
2812
+ refuse_as_a_statement(node, "a call to `#{node.name}`")
1212
2813
  end
1213
- if (pointer = pointer_subscript(node, write: true))
2814
+ arguments = node.arguments ? node.arguments.arguments : []
2815
+ element_write(node, arguments[0..-2], nil, node.location,
2816
+ value_node: arguments.last)
2817
+ end
2818
+
2819
+ # The write itself, with the indices and the value already told apart:
2820
+ # `a[i] = e` puts the value last among the arguments, while `a[i] += e`
2821
+ # keeps it somewhere else, and everything from here down is the same
2822
+ # for both. `value_node` is passed where the value is still a Prism
2823
+ # node, since `a[i] = UNDEF` is a mark rather than a value and is read
2824
+ # off the node.
2825
+ def element_write (node, indices, expression, location, value_node: nil)
2826
+ if (pointer = pointer_subscript(node, write: true, indices: indices))
1214
2827
  name, index = pointer
1215
- arguments = node.arguments.arguments
1216
- return PointerWrite.new(name, index, build(arguments.last),
1217
- node.location)
2828
+ return PointerWrite.new(name, index,
2829
+ expression || build(value_node), location)
2830
+ end
2831
+ refuse_a_constructor_in_an_expression(node.receiver)
2832
+ if (name = local_array_name(node.receiver))
2833
+ storage, shape = local_array_in_sight(name)
2834
+ if value_node && undef_constant?(value_node)
2835
+ # A kernel that writes UNDEF anywhere carries masks by that
2836
+ # alone, so this is a mark on the shadow beside the cell. In a
2837
+ # function body there is no shadow: a function is handed numbers
2838
+ # and pointers, and a mask reaches neither.
2839
+ unless @masked
2840
+ raise Unsupported.new(
2841
+ "`#{name}` is a local array in a compiled function's body, " \
2842
+ "and nothing there carries masks -- a function is handed " \
2843
+ "numbers and pointers, and a mask travels in neither. Keep " \
2844
+ "a value that stands for missing, or mark the cell in the " \
2845
+ "kernel that called this",
2846
+ location)
2847
+ end
2848
+ @uses_undef = true
2849
+ return LocalArrayMaskWrite.new(
2850
+ name, storage, shape,
2851
+ local_array_subscripts(name, shape, indices, location), location)
2852
+ end
2853
+ return LocalArrayWrite.new(
2854
+ name, storage, shape,
2855
+ local_array_subscripts(name, shape, indices, location),
2856
+ expression || build(value_node), location)
1218
2857
  end
1219
- array = array_name(node.receiver, node.location)
1220
- arguments = node.arguments ? node.arguments.arguments : []
2858
+ array = array_name(node.receiver, location)
1221
2859
  # `out[] = ...` was how a block that had to run as Ruby said "the
1222
2860
  # whole array", `[]=` being the only spelling Ruby has for it. The
1223
2861
  # block is read rather than run, and every name in it is a cell, so
1224
2862
  # the assignment is Ruby's own: `out = ...`.
1225
- if @whole_array && arguments.size == 1
2863
+ if @whole_array && indices.empty?
1226
2864
  raise Unsupported.new(
1227
2865
  "`#{array}[] = ...` writes the cell the loop is on, which is what " \
1228
2866
  "`#{array} = ...` says; the block is read rather than run, so the " \
1229
2867
  "assignment is an ordinary one",
1230
- node.location)
2868
+ location)
1231
2869
  end
1232
- subscripts = read_subscripts(array, arguments[0..-2], node.location)
1233
- # A kernel writes a cell its own indices reach -- or one it works out,
1234
- # which is a scatter and is checked as it is reached rather than in
1235
- # advance. What it may not do is write through an index that is not
1236
- # the loop's: an inner index addresses reads only.
2870
+ subscripts = read_subscripts(array, indices, location)
2871
+ # A kernel writes a cell one of its own indices reaches -- an outer
2872
+ # one or an inner one -- or one it works out, which is a scatter and
2873
+ # is checked as it is reached rather than in advance. What it may not
2874
+ # do is write through a name that addresses no cell at all.
1237
2875
  #
1238
2876
  # In a contraction the left-hand side names which indices are free and
1239
2877
  # which are summed over, so it is checked there instead.
1240
2878
  unless @contract || walking_subscripts?(subscripts) ||
1241
2879
  computed_subscripts?(subscripts)
1242
2880
  raise Unsupported.new(
1243
- "a kernel writes through its own indices, so `#{array}[...]` on " \
1244
- "the left of `=` is addressed by #{@outer_names.join(', ')}, by a " \
1245
- "position fixed before the loop runs -- or by a value the kernel " \
1246
- "works out, which is a scatter",
1247
- node.location)
2881
+ "a kernel writes through the indices it is running, so " \
2882
+ "`#{array}[...]` on the left of `=` is addressed by " \
2883
+ "#{available_indices}, by a position fixed before the loop runs " \
2884
+ "-- or by a value the kernel works out, which is a scatter",
2885
+ location)
1248
2886
  end
1249
2887
  record_subscripts(array, subscripts)
2888
+ record_write_subscripts(array, subscripts)
1250
2889
  @written_arrays << array unless @written_arrays.include?(array)
1251
- if undef_constant?(arguments.last)
2890
+ if value_node && undef_constant?(value_node)
1252
2891
  @uses_undef = true
1253
- return MaskWrite.new(array, node.location)
2892
+ return MaskWrite.new(array, location)
1254
2893
  end
1255
- ElementWrite.new(array, build(arguments.last), node.location, subscripts)
2894
+ ElementWrite.new(array, expression || build(value_node), location,
2895
+ subscripts)
1256
2896
  end
1257
2897
 
1258
2898
  def build (node)
@@ -1301,8 +2941,19 @@ class CArray
1301
2941
  # closes over nothing.
1302
2942
  build_name_read(node.name, node.location)
1303
2943
  else
1304
- raise Unsupported.new("unsupported expression #{node_name(node)}",
1305
- node.location)
2944
+ # Named as it was written where there is a name for it, and with
2945
+ # the same advice a statement in that position gets: `unless` in
2946
+ # value position was "unsupported expression Unless", which is
2947
+ # this compiler's reading of it rather than the reader's.
2948
+ described = STATEMENT_DESCRIPTIONS[node.class]
2949
+ hint = STATEMENT_HINTS[node.class]
2950
+ raise Unsupported.new(
2951
+ described ?
2952
+ "a kernel computes its value from arithmetic, comparisons, " \
2953
+ "cells and calls, and this one is #{described}" \
2954
+ "#{hint ? " -- #{hint}" : ""}" :
2955
+ "unsupported expression #{node_name(node)}",
2956
+ node.location)
1306
2957
  end
1307
2958
  end
1308
2959
 
@@ -1314,8 +2965,31 @@ class CArray
1314
2965
  def build_name_read (name, location)
1315
2966
  axis = @outer_names.index(name)
1316
2967
  return IndexVariable.new(name, axis, location) if axis
1317
- return IndexVariable.new(name, nil, location) if @inner_names.include?(name)
2968
+ if @inner_names.include?(name)
2969
+ return IndexVariable.new(@inner_aliases.fetch(name, name), nil, location)
2970
+ end
2971
+ if @local_array_names.include?(name)
2972
+ unless local_in_sight?(name)
2973
+ raise Unsupported.new(
2974
+ "`#{name}` belongs to the loop block it was assigned in, and is " \
2975
+ "not there after it; make the array before the loop",
2976
+ location)
2977
+ end
2978
+ raise Unsupported.new(
2979
+ "`#{name}` is a local array; index it, as in `#{name}[0]`. An " \
2980
+ "array has no value of its own in a kernel -- what the name " \
2981
+ "stands for is its cells",
2982
+ location)
2983
+ end
1318
2984
  if @local_names.include?(name)
2985
+ # A local of an inner loop's block that has closed is not in sight:
2986
+ # Ruby reads the same name after the block as a method call.
2987
+ unless @scopes.any? { |scope| scope.include?(name) }
2988
+ raise Unsupported.new(
2989
+ "`#{name}` belongs to the loop block it was assigned in, and is " \
2990
+ "not there after it; give it a value before the loop",
2991
+ location)
2992
+ end
1319
2993
  return LocalRead.new(name, location)
1320
2994
  end
1321
2995
  if @cell_names.include?(name)
@@ -1380,9 +3054,29 @@ class CArray
1380
3054
  if node.receiver.nil? && node.arguments.nil? && node.block.nil?
1381
3055
  return build_name_read(node.name, node.location)
1382
3056
  end
3057
+ if (intrinsic = build_intrinsic_call(node))
3058
+ return intrinsic
3059
+ end
3060
+ refuse_a_constructor_in_an_expression(node)
3061
+ # A method on a local array. The operators are left alone so that
3062
+ # `w + 1` is refused for reading the array bare, which is what it
3063
+ # does; a named method is refused here, where the name can be said.
3064
+ if (holder = local_array_name(node.receiver)) &&
3065
+ !LOCAL_ARRAY_SUBSCRIPTS.include?(node.name) &&
3066
+ !(ARITHMETIC_OPERATORS + COMPARISON_OPERATORS +
3067
+ BIT_OPERATORS + [:**]).include?(node.name)
3068
+ raise Unsupported.new(
3069
+ "`#{holder}` is a local array, and `#{node.name}` is a method " \
3070
+ "CArray answers outside a kernel; inside one an array is reached " \
3071
+ "at a subscript. Write the loop out over `#{holder}[k]`",
3072
+ node.location)
3073
+ end
1383
3074
  if (recursive = recursive_call(node))
1384
3075
  return recursive
1385
3076
  end
3077
+ if (draw = random_call(node))
3078
+ return draw
3079
+ end
1386
3080
  if (c_function = c_function_call(node))
1387
3081
  return c_function
1388
3082
  end
@@ -1421,6 +3115,28 @@ class CArray
1421
3115
  if node.name == :** && arguments.size == 1
1422
3116
  return build_power(node.receiver, arguments.first, node.location)
1423
3117
  end
3118
+ if node.name == :clamp
3119
+ unless arguments.size == 2
3120
+ raise Unsupported.new(
3121
+ "`clamp` takes the two bounds, as in `x.clamp(0.0, 1.0)`" +
3122
+ (arguments.size == 1 ? " -- the range form is not in the subset" : ""),
3123
+ node.location)
3124
+ end
3125
+ return Clamp.new(build(node.receiver), build(arguments.first),
3126
+ build(arguments.last), node.location)
3127
+ end
3128
+ if arguments.empty? && NUMERIC_PREDICATES.include?(node.name)
3129
+ return NumericPredicate.new(node.name, build(node.receiver),
3130
+ node.location)
3131
+ end
3132
+ if arguments.empty? && node.name == :infinite?
3133
+ raise Unsupported.new(
3134
+ "`infinite?` answers nil, 1 or -1 in Ruby rather than true or " \
3135
+ "false, and a kernel has no nil to answer with; ask " \
3136
+ "`x.abs == Float::INFINITY`, or `x == Float::INFINITY` where " \
3137
+ "the sign is the question",
3138
+ node.location)
3139
+ end
1424
3140
  if arguments.empty?
1425
3141
  if (function = postfix_math(node.name))
1426
3142
  return MathCall.new(function, [build(node.receiver)], node.location)
@@ -1507,11 +3223,26 @@ class CArray
1507
3223
  "only a cell can be compared with UNDEF, as in `a[i] == UNDEF`",
1508
3224
  location)
1509
3225
  end
3226
+ arguments = receiver.arguments ? receiver.arguments.arguments : []
3227
+ if (made = local_array_name(receiver.receiver))
3228
+ storage, shape = local_array_in_sight(made)
3229
+ unless @masked
3230
+ raise Unsupported.new(
3231
+ "`#{made}` is a local array in a compiled function's body, " \
3232
+ "and nothing there carries masks -- a function is handed " \
3233
+ "numbers and pointers, and a mask travels in neither, so " \
3234
+ "there is no shadow beside these cells to ask about",
3235
+ location)
3236
+ end
3237
+ @uses_undef = true
3238
+ return LocalArrayMaskTest.new(
3239
+ made, storage, shape,
3240
+ local_array_subscripts(made, shape, arguments, location),
3241
+ negated, location)
3242
+ end
1510
3243
  array = array_name(receiver.receiver, location)
1511
3244
  array_for_test = array
1512
- subscripts = read_subscripts(array_for_test,
1513
- receiver.arguments ? receiver.arguments.arguments : [],
1514
- location)
3245
+ subscripts = read_subscripts(array_for_test, arguments, location)
1515
3246
  # Recorded like any other read, because the cell still has to exist
1516
3247
  # and still has to have been settled before it is asked about. What
1517
3248
  # it does not do is feed the value-mask propagation, which is decided
@@ -1525,7 +3256,7 @@ class CArray
1525
3256
  # does not. A pointer declared const is refused on the left rather than
1526
3257
  # silently written through: the declaration is a promise to the caller,
1527
3258
  # not decoration.
1528
- def pointer_subscript (node, write: false)
3259
+ def pointer_subscript (node, write: false, indices: nil)
1529
3260
  name = captured_name(node.receiver)
1530
3261
  return nil unless name && @pointers.key?(name)
1531
3262
  case @pointers.fetch(name)
@@ -1541,14 +3272,36 @@ class CArray
1541
3272
  "not write through it", node.location)
1542
3273
  end
1543
3274
  end
1544
- arguments = node.arguments ? node.arguments.arguments : []
1545
- arguments = arguments[0..-2] if write
3275
+ arguments = indices
3276
+ unless arguments
3277
+ arguments = node.arguments ? node.arguments.arguments : []
3278
+ arguments = arguments[0..-2] if write
3279
+ end
1546
3280
  unless arguments.size == 1
1547
3281
  raise Unsupported.new(
1548
3282
  "`#{name}` is a pointer, so it takes one index", node.location)
1549
3283
  end
1550
3284
  @pointer_names << name unless @pointer_names.include?(name)
1551
- [name, build(arguments.first)]
3285
+ index = build(arguments.first)
3286
+ refuse_undeclared_cell(name, index, node.location)
3287
+ [name, index]
3288
+ end
3289
+
3290
+ # A subscript on a pointer is not checked when the function runs: that
3291
+ # is C's bargain, and a computed one could only be checked there. A
3292
+ # literal needs no running to be read, though, and where the
3293
+ # declaration said how many cells there are, one outside them is a
3294
+ # write or a read past what the caller was held to -- the caller is
3295
+ # checked for at least that many and no more. C says nothing about
3296
+ # `v[7]` against `double v[2]`; this says it as the body is read.
3297
+ def refuse_undeclared_cell (name, index, location)
3298
+ length = @pointer_lengths[name]
3299
+ return unless length && index.is_a?(IntegerLiteral)
3300
+ return if index.value >= 0 && index.value < length
3301
+ raise Unsupported.new(
3302
+ "`#{name}[#{index.value}]` is outside the #{length} " \
3303
+ "#{length == 1 ? 'cell' : 'cells'} `#{name}` is declared with; " \
3304
+ "a caller is held to that many and no more", location)
1552
3305
  end
1553
3306
 
1554
3307
  # `fact(n - 1)` inside the body of `double fact(double)`, and
@@ -1561,16 +3314,19 @@ class CArray
1561
3314
  def recursive_call (node)
1562
3315
  return nil unless @recursion
1563
3316
  name, parameters, result_type = @recursion
1564
- # `fact.call(n - 1)`, the spelling every other C function takes. A
1565
- # bare `fact(n - 1)` would read better as C and is refused all the
1566
- # same: it is not Ruby, and the block has to stay runnable, since
1567
- # running it beside the compiled function is how the two are checked
1568
- # against each other.
3317
+ # `fact.call(n - 1)`, the spelling every other C function takes,
3318
+ # borrowed or written. A bare `fact(n - 1)` reads better as C and is
3319
+ # refused all the same, because a bare call is a spelling this
3320
+ # compiler has taken for itself: `sum(w)` and `sort(w)` are its own
3321
+ # functions, reached by no name the block wrote. A function of yours
3322
+ # is reached through the name that holds it -- which is also what
3323
+ # makes a recursion read like a call to anything else.
1569
3324
  if node.receiver.nil? && node.name == name && node.arguments
1570
3325
  raise Unsupported.new(
1571
3326
  "`#{name}` calls itself the way any C function is called here, " \
1572
- "as `#{name}.call(...)` -- a bare `#{name}(...)` is not Ruby, and " \
1573
- "the block has to stay runnable",
3327
+ "as `#{name}.call(...)` -- a bare `#{name}(...)` is how this " \
3328
+ "compiler spells its own functions, such as `sum(w)`, and a " \
3329
+ "function of yours is reached through the name that holds it",
1574
3330
  node.location)
1575
3331
  end
1576
3332
  return nil unless C_FUNCTION_CALL_NAMES.include?(node.name) &&
@@ -1590,6 +3346,111 @@ class CArray
1590
3346
  RecursiveCall.new(name, built, parameters, result_type, node.location)
1591
3347
  end
1592
3348
 
3349
+ # `int64_t s[4]`, the parameter the generator's own C takes. Held as a
3350
+ # parsed declaration rather than described here, so that what the state
3351
+ # array is checked against is the C the draw will actually be compiled
3352
+ # into -- the same check `poly.call(x, coef)` gets, from the same code.
3353
+ # Parsed on first use: the declaration parser is in c_function.rb,
3354
+ # which is required after this file.
3355
+ def self.random_state_parameter
3356
+ @random_state_parameter ||=
3357
+ CDeclaration.parse("double draw(int64_t state[4])").last.first
3358
+ end
3359
+
3360
+ # `r.rand` and `r.bits` -- a draw written as a method of the generator,
3361
+ # which is how Ruby writes one. `#rand` and `#bits` do these outside a
3362
+ # kernel, and the same names inside it mean the same two things.
3363
+ RANDOM_DRAW_METHODS =
3364
+ { :random => :random, :randomn => :randomn, :bits => :bits }.freeze
3365
+
3366
+ # `random(rng: r)` -- the same draw as `r.rand`, written as the array
3367
+ # language writes it. `a.random!(rng: r)` fills an array from a
3368
+ # generator, and this is that sentence about one cell, so a reader
3369
+ # moving between the two reads one word rather than matching two up.
3370
+ #
3371
+ # It is the only keyword argument in the subset and the only bare name
3372
+ # in it that Ruby does not have; both are paid for that.
3373
+ RANDOM_KEYWORD_NAMES =
3374
+ { :random => :random, :randomn => :randomn }.freeze
3375
+ RANDOM_DRAW_KEYWORD = "rng"
3376
+
3377
+ # Returns the node for a draw from a captured CArray::Rng, or nil when
3378
+ # this is not one.
3379
+ def random_call (node)
3380
+ if node.receiver.nil? && (kind = RANDOM_KEYWORD_NAMES[node.name])
3381
+ return build_random_draw(random_generator_name(node), kind, node)
3382
+ end
3383
+ name = captured_name(node.receiver)
3384
+ return nil unless name && @randoms.key?(name)
3385
+ kind = RANDOM_DRAW_METHODS[node.name]
3386
+ refuse_a_generator_call(name, node) unless kind
3387
+ unless (node.arguments ? node.arguments.arguments : []).empty?
3388
+ bounded = node.name == :random ?
3389
+ ". `CArray#random!` takes a range for a whole array; a kernel " \
3390
+ "has no bounded draw, so scale the one you get" : ""
3391
+ raise Unsupported.new(
3392
+ "`#{name}.#{node.name}` in a kernel is one draw and takes no " \
3393
+ "arguments#{bounded}",
3394
+ node.location)
3395
+ end
3396
+ build_random_draw(name, kind, node)
3397
+ end
3398
+
3399
+ def build_random_draw (name, kind, node)
3400
+ state = @randoms.fetch(name)
3401
+ @random_names << name unless @random_names.include?(name)
3402
+ # The state travels the way any array handed to a C function whole
3403
+ # does: by address, in the `data` buffer, copied back after the call.
3404
+ # That copy-back is what leaves the generator advanced.
3405
+ @address_arrays << state unless @address_arrays.include?(state)
3406
+ (@address_parameters[state] ||= []) << self.class.random_state_parameter
3407
+ RandomDraw.new(name, state, kind, node.location)
3408
+ end
3409
+
3410
+ # Everything else reached on a generator. There are three spellings
3411
+ # and no fourth, so the message lists them rather than saying only that
3412
+ # this one is not among them.
3413
+ def refuse_a_generator_call (name, node)
3414
+ raise Unsupported.new(
3415
+ "`#{name}` is a generator, and `#{name}.#{node.name}` is not a way " \
3416
+ "to draw from one. A kernel has `#{name}.random` for a double in " \
3417
+ "[0.0, 1.0), `#{name}.randomn` for a standard normal, " \
3418
+ "`#{name}.bits` for the raw word, and `random(rng: #{name})` or " \
3419
+ "`randomn(rng: #{name})` for the first two written as " \
3420
+ "`CArray#random!` writes them",
3421
+ node.location)
3422
+ end
3423
+
3424
+ # Which generator `random` was pointed at, or a refusal saying how to
3425
+ # point it at one. Nothing else is accepted in the parentheses: a
3426
+ # range, a bound, a data type are all things `CArray#random!` takes and
3427
+ # none of them has an answer for one cell.
3428
+ def random_generator_name (node)
3429
+ arguments = node.arguments ? node.arguments.arguments : []
3430
+ keywords = arguments.last if arguments.last.is_a?(Prism::KeywordHashNode)
3431
+ pairs = keywords ? keywords.elements : []
3432
+ unless arguments.size == 1 && keywords && pairs.size == 1 &&
3433
+ pairs.first.is_a?(Prism::AssocNode) &&
3434
+ pairs.first.key.is_a?(Prism::SymbolNode) &&
3435
+ pairs.first.key.unescaped == RANDOM_DRAW_KEYWORD
3436
+ raise Unsupported.new(
3437
+ "`#{node.name}` in a kernel draws one number from a generator " \
3438
+ "and takes `rng:` and nothing else -- `#{node.name}(rng: r)`, " \
3439
+ "where `r` is a `CArray::Rng` the block closed over",
3440
+ node.location)
3441
+ end
3442
+ name = captured_name(pairs.first.value)
3443
+ unless name && @randoms.key?(name)
3444
+ reached = name ? "`#{name}`" : "what was written there"
3445
+ raise Unsupported.new(
3446
+ "`rng:` takes a `CArray::Rng` the block closed over, and " \
3447
+ "#{reached} is not one. A Ruby `Random` cannot be reached from a " \
3448
+ "kernel at all; `CArray::Rng.new` makes one that can",
3449
+ pairs.first.value.location)
3450
+ end
3451
+ name
3452
+ end
3453
+
1593
3454
  # Returns the node for a call on a captured C function, or nil when this
1594
3455
  # is not one.
1595
3456
  def c_function_call (node)
@@ -1600,7 +3461,8 @@ class CArray
1600
3461
  arguments = node.arguments ? node.arguments.arguments : []
1601
3462
  unless arguments.size == c_function.arity
1602
3463
  raise Unsupported.new(
1603
- "`#{name}` is `#{c_function}`, so it takes #{c_function.arity} " \
3464
+ "`#{name}` is `#{c_function.declaration_as(name)}`, so it takes " \
3465
+ "#{c_function.arity} " \
1604
3466
  "#{c_function.arity == 1 ? 'argument' : 'arguments'}; " \
1605
3467
  "#{arguments.size} #{arguments.size == 1 ? 'was' : 'were'} given",
1606
3468
  node.location)
@@ -1618,6 +3480,16 @@ class CArray
1618
3480
  # name means a cell in one argument position and the whole array in
1619
3481
  # another -- `poly.call(x[i], coef)` says both.
1620
3482
  def build_c_function_argument (argument, parameter, c_function)
3483
+ # A local array decays to the pointer the callee wants, as it does in
3484
+ # C -- the name its declaration was written under is already the
3485
+ # address. What the declaration says about it is matched here, as the
3486
+ # block is read, because both halves are written in the block: the
3487
+ # element type is the constructor and the length is a literal. A
3488
+ # captured array is matched at the call instead (`address_buffer`),
3489
+ # neither being knowable until there is an array.
3490
+ if (name = local_array_name(argument))
3491
+ return build_local_array_address(name, argument, parameter, c_function)
3492
+ end
1621
3493
  # Inside a function, one of its own pointer parameters is already the
1622
3494
  # address the callee wants, and handing it on is what C does -- for a
1623
3495
  # `void *` slot as much as for a run of numbers, since neither is
@@ -1670,12 +3542,24 @@ class CArray
1670
3542
  ElementWrite.new(name, expression, location, subscripts)
1671
3543
  end
1672
3544
 
1673
- def build_element_read (node)
1674
- if (pointer = pointer_subscript(node))
3545
+ # `indices` is given where the node is not a plain `a[i]` -- `a[i] += e`
3546
+ # carries its indices and its value in different places, and the read
3547
+ # it stands for is the same read either way.
3548
+ def build_element_read (node, indices = nil)
3549
+ if (pointer = pointer_subscript(node, indices: indices))
1675
3550
  return PointerRead.new(pointer.first, pointer.last, node.location)
1676
3551
  end
3552
+ refuse_a_constructor_in_an_expression(node.receiver)
3553
+ if (name = local_array_name(node.receiver))
3554
+ storage, shape = local_array_in_sight(name)
3555
+ arguments = indices || (node.arguments ? node.arguments.arguments : [])
3556
+ return LocalArrayRead.new(
3557
+ name, storage, shape,
3558
+ local_array_subscripts(name, shape, arguments, node.location),
3559
+ node.location)
3560
+ end
1677
3561
  array = array_name(node.receiver, node.location)
1678
- arguments = node.arguments ? node.arguments.arguments : []
3562
+ arguments = indices || (node.arguments ? node.arguments.arguments : [])
1679
3563
  subscripts = read_subscripts(array, arguments, node.location)
1680
3564
  record_subscripts(array, subscripts)
1681
3565
  ElementRead.new(array, subscripts, node.location)
@@ -1685,6 +3569,15 @@ class CArray
1685
3569
  @subscripts[array] << subscripts unless @subscripts[array].include?(subscripts)
1686
3570
  end
1687
3571
 
3572
+ # Kept apart from the reads because the two are asked different
3573
+ # questions: every use of an array decides the box a view is
3574
+ # transferred in, while only the writes say which axes the kernel owns
3575
+ # a cell of.
3576
+ def record_write_subscripts (array, subscripts)
3577
+ list = @write_subscripts[array]
3578
+ list << subscripts unless list.include?(subscripts)
3579
+ end
3580
+
1688
3581
  def array_name (receiver, location)
1689
3582
  name = captured_name(receiver)
1690
3583
  unless name && @array_names.include?(name)
@@ -1769,7 +3662,7 @@ class CArray
1769
3662
  # in scope, so that the offset is a compile-time constant.
1770
3663
  def read_subscript (node, location)
1771
3664
  name = index_name(node)
1772
- return [name, 0] if name && index_in_scope?(name)
3665
+ return [index_identifier(name), 0] if name && index_in_scope?(name)
1773
3666
  if node.is_a?(Prism::CallNode) && [:+, :-].include?(node.name) &&
1774
3667
  (receiver = index_name(node.receiver)) && index_in_scope?(receiver)
1775
3668
  return walked_subscript(node)
@@ -1840,10 +3733,39 @@ class CArray
1840
3733
  raise Unsupported.new("write the offset as `j - c` with c >= 0",
1841
3734
  node.location)
1842
3735
  end
1843
- return [receiver.name, node.name == :+ ? constant : -constant]
3736
+ return [index_identifier(receiver.name),
3737
+ node.name == :+ ? constant : -constant]
1844
3738
  end
1845
3739
  offset = pinned_subscript(argument)
1846
- [receiver.name, node.name == :+ ? offset : UnaryMinus.new(offset)]
3740
+ refuse_an_index_in_the_offset(node, receiver.name, offset)
3741
+ [index_identifier(receiver.name),
3742
+ node.name == :+ ? offset : UnaryMinus.new(offset)]
3743
+ end
3744
+
3745
+ # `a[i + r]`, where `r` is an inner loop's index: the subscript walks
3746
+ # with `i`, and an offset is worked out once per call, before any cell,
3747
+ # which an index cannot be. Left to the call, it was refused there
3748
+ # for a reason about an inner loop's range -- true of the code that
3749
+ # happened to read it, and nothing to do with what was written. The
3750
+ # same sum put in a local first is a subscript the kernel works out
3751
+ # and checks at the cell, which is what the message points to.
3752
+ def refuse_an_index_in_the_offset (node, walker, offset)
3753
+ others = []
3754
+ visit = lambda { |item|
3755
+ next unless item.is_a?(Node)
3756
+ others << item.name if item.is_a?(IndexVariable)
3757
+ item.children.each(&visit)
3758
+ }
3759
+ visit.call(offset)
3760
+ return if others.empty?
3761
+ names = others.uniq.map { |name| "`#{name}`" }.join(" and ")
3762
+ raise Unsupported.new(
3763
+ "`#{node.slice}` walks with `#{walker}` and is offset by #{names}, " \
3764
+ "an index as well; an offset is worked out once per call, from " \
3765
+ "literals and captured integers -- put the sum in a local first " \
3766
+ "(`k = #{node.slice}`, then `[k]`) and it is worked out and " \
3767
+ "checked at each cell",
3768
+ node.location)
1847
3769
  end
1848
3770
 
1849
3771
  # An index the block names as a parameter reads as a local variable.
@@ -1860,6 +3782,19 @@ class CArray
1860
3782
  end
1861
3783
  end
1862
3784
 
3785
+ # An index as this compiler knows it: the name the block wrote, unless
3786
+ # that name belongs to a second loop of the same name, which has an
3787
+ # identifier of its own.
3788
+ def index_identifier (name)
3789
+ @inner_aliases.fetch(name, name)
3790
+ end
3791
+
3792
+ # The same question asked of an identifier a subscript carries, which
3793
+ # for a second loop of the same name is not the name the block wrote.
3794
+ def index_identifier_in_scope? (name)
3795
+ @outer_names.include?(name) || @inner_aliases.value?(name)
3796
+ end
3797
+
1863
3798
  def index_in_scope? (name)
1864
3799
  @outer_names.include?(name) || @inner_names.include?(name)
1865
3800
  end
@@ -1930,8 +3865,10 @@ class CArray
1930
3865
  # A non-negative literal exponent can still be squared out; anything
1931
3866
  # else has to be asked for in floating point.
1932
3867
  def build_power (receiver, exponent, location)
1933
- base = build(receiver)
1934
- power = build(exponent)
3868
+ power_node(build(receiver), build(exponent), location)
3869
+ end
3870
+
3871
+ def power_node (base, power, location)
1935
3872
  if power.is_a?(IntegerLiteral) && power.value < 0
1936
3873
  raise Unsupported.new(
1937
3874
  "a negative exponent gives a Rational in Ruby; write `1.0 / x ** n`",
@@ -1943,8 +3880,12 @@ class CArray
1943
3880
  def build_math_call (node)
1944
3881
  function = MATH_FUNCTIONS[node.name]
1945
3882
  unless function
1946
- raise Unsupported.new("Math.#{node.name} has no math.h counterpart",
1947
- node.location)
3883
+ if (reason = REFUSED_MATH[node.name])
3884
+ raise Unsupported.new("Math.#{node.name} is not compiled: " \
3885
+ "#{reason}", node.location)
3886
+ end
3887
+ raise Unsupported.new(
3888
+ "Math.#{node.name} is not a name this compiles", node.location)
1948
3889
  end
1949
3890
  arguments = node.arguments ? node.arguments.arguments : []
1950
3891
  expected = [:atan2, :hypot].include?(node.name) ? 2 : 1