carray-jit 0.1.2 → 0.1.4

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +917 -3
  3. data/README.md +14 -12
  4. data/carray-jit.gemspec +1 -3
  5. data/docs/00_Introduction.md +5 -3
  6. data/docs/01_GettingStarted.md +1 -1
  7. data/docs/02_KernelShapes.md +94 -15
  8. data/docs/03_SupportedFeatures.md +665 -32
  9. data/docs/04_Compiling.md +34 -7
  10. data/docs/05_DesignNotes.md +3 -3
  11. data/docs/06_Cheatsheet.md +200 -7
  12. data/docs/07_StepByStep.ja.md +533 -0
  13. data/docs/07_StepByStep.md +534 -0
  14. data/docs/08_LanguageReference.md +427 -0
  15. data/examples/README.md +12 -0
  16. data/examples/applications/alarm.rb +121 -0
  17. data/examples/applications/collatz.rb +105 -0
  18. data/examples/applications/cubic_spline.rb +331 -0
  19. data/examples/applications/dithering.rb +144 -0
  20. data/examples/applications/group_stats.rb +115 -0
  21. data/examples/applications/lookup.rb +126 -0
  22. data/examples/applications/median_filter.rb +153 -0
  23. data/examples/applications/parcel_ascent.rb +220 -0
  24. data/examples/applications/point_in_polygon.rb +111 -0
  25. data/examples/applications/random_walk.rb +98 -0
  26. data/examples/applications/van_der_pol.rb +186 -0
  27. data/examples/applications/wet_bulb.rb +140 -0
  28. data/examples/features/10_complex.rb +14 -4
  29. data/examples/features/15_loops.rb +7 -1
  30. data/lib/carray/jit/access.rb +14 -0
  31. data/lib/carray/jit/analyzer.rb +2218 -140
  32. data/lib/carray/jit/block_reader.rb +37 -6
  33. data/lib/carray/jit/c_function.rb +961 -86
  34. data/lib/carray/jit/c_generator.rb +1970 -191
  35. data/lib/carray/jit/call.rb +68 -0
  36. data/lib/carray/jit/compiler.rb +102 -14
  37. data/lib/carray/jit/expression.rb +271 -33
  38. data/lib/carray/jit/kernel.rb +382 -32
  39. data/lib/carray/jit/node.rb +374 -9
  40. data/lib/carray/jit/sorting_networks.rb +182 -0
  41. data/lib/carray/jit/type_assignment.rb +424 -35
  42. data/lib/carray/jit/version.rb +1 -1
  43. data/lib/carray/jit.rb +547 -77
  44. metadata +23 -8
  45. data/ext/carray_jit_access/carray_jit_access.c +0 -460
  46. 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, :assigned_parameters
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, :unbounded_indices,
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
@@ -306,7 +503,9 @@ class CArray
306
503
  @inner_names_seen = []
307
504
  @array_ranks = {}
308
505
  @inner_ranges = {}
506
+ @unbounded_indices = []
309
507
  @subscripts = Hash.new { |hash, key| hash[key] = [] }
508
+ @write_subscripts = Hash.new { |hash, key| hash[key] = [] }
310
509
  @given_rank = rank
311
510
  @steps = steps
312
511
  @contract = contract
@@ -316,13 +515,32 @@ class CArray
316
515
  @free_names = []
317
516
  @index_names = []
318
517
  @local_names = []
518
+ # The arrays the body made, by name: [storage type, shape] for the
519
+ # declaration in sight. Kept beside @local_names rather than in it,
520
+ # because the two are different things under one syntax and a name
521
+ # holds one or the other -- which is what lets each say so.
522
+ @local_array_names = []
523
+ @local_arrays = {}
524
+ # One entry per C array the body declares, as
525
+ # [scope, name, storage, shape], so that the room they take together
526
+ # can be counted once the body has been read. Two sibling loops that
527
+ # each declare `w` are one entry: their blocks do not overlap, so
528
+ # neither do their frames.
529
+ @local_array_declarations = []
530
+ @clears_a_local_array = false
531
+ # The locals each scope holds, innermost last: the kernel's block --
532
+ # or a function's -- and one for every inner loop's block around the
533
+ # statement being built. `while` and `if` make none, as in Ruby.
534
+ @scopes = [[]]
319
535
  @assigned_names = []
320
536
  @scalar_names = []
321
537
  @c_function_names = []
322
538
  @parameter_names = []
539
+ @assigned_parameters = []
323
540
  @pointer_names = []
324
541
  @address_arrays = []
325
542
  @address_parameters = {}
543
+ @random_names = []
326
544
  @read_offsets = Hash.new { |hash, key| hash[key] = [] }
327
545
  @written_arrays = []
328
546
  analyze
@@ -377,18 +595,29 @@ class CArray
377
595
  end
378
596
 
379
597
  # 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.
598
+ # index the kernel is running -- an outer one or an inner one, at
599
+ # whatever offset -- or is pinned at a position known before the first
600
+ # cell.
382
601
  #
383
602
  # Two iterations may land on the same cell -- `box[0]` puts every one of
384
603
  # them there -- and that is not an ambiguity: the extent states the
385
604
  # order, so what the array holds afterwards is what the same Ruby loop
386
605
  # would leave in it. An inner loop already rests on exactly that, an
387
606
  # accumulator being one cell written once per pass.
607
+ #
608
+ # An inner index is in that position and not a weaker one: its loop
609
+ # states its extent the way the kernel's own extents do, so the cell it
610
+ # reaches is as knowable as `i`'s and is bounds-checked with it. What
611
+ # the write is for is a row of workspace per outer cell -- filling one
612
+ # and walking back down it is the shape a small dense solve has, and it
613
+ # was the reason to add an axis rather than split a body into kernels
614
+ # that each pay a call. It was refused until it was noticed that
615
+ # `kk = k + 0` on the way in compiled it anyway, as a scatter: the rule
616
+ # was stopping the spelling rather than the thing.
388
617
  def walking_subscripts? (subscripts)
389
618
  subscripts.all? { |index, offset|
390
619
  if index
391
- @outer_names.include?(index)
620
+ index_identifier_in_scope?(index)
392
621
  else
393
622
  !offset.is_a?(Node) || Analyzer.fixed_subscript?(offset)
394
623
  end
@@ -452,9 +681,89 @@ class CArray
452
681
  node.is_a?(Prism::CallNode) && node.receiver.nil? && node.name == :raise
453
682
  end
454
683
 
684
+ def scan_address_array_names (node, c_functions)
685
+ # Two passes rather than one, because the arguments of a call sit
686
+ # under an ArgumentsNode: pruning them out of a single walk would
687
+ # mean matching a grandchild, and the set is the clearer statement.
688
+ addressed = {}.compare_by_identity
689
+ handed = []
690
+ collect_address_arguments(node, c_functions, handed, addressed)
691
+ return [] if handed.empty?
692
+ as_cells = []
693
+ collect_cell_names(node, addressed, as_cells)
694
+ (handed.uniq - as_cells - scan_local_array_names(node))
695
+ end
696
+
697
+ # The names standing bare where a declaration wants a pointer at
698
+ # numbers, and the argument nodes they stand in.
699
+ def collect_address_arguments (node, c_functions, handed, addressed)
700
+ return unless node
701
+ return if raise_call?(node)
702
+ if node.is_a?(Prism::CallNode) &&
703
+ C_FUNCTION_CALL_NAMES.include?(node.name) &&
704
+ (function = captured_name(node.receiver)) &&
705
+ c_functions.key?(function)
706
+ parameters = c_functions.fetch(function).parameters
707
+ arguments = node.arguments ? node.arguments.arguments : []
708
+ arguments.each_with_index do |argument, position|
709
+ next unless parameters[position]&.indexable?
710
+ given = captured_name(argument)
711
+ next unless given
712
+ handed << given
713
+ addressed[argument] = true
714
+ end
715
+ end
716
+ node.compact_child_nodes.each { |child|
717
+ collect_address_arguments(child, c_functions, handed, addressed)
718
+ }
719
+ end
720
+
721
+ # Every other way a name is reached: subscripted, or bare where a bare
722
+ # name is a cell. One of these takes a name back out of the answer.
723
+ def collect_cell_names (node, addressed, as_cells)
724
+ return unless node
725
+ return if raise_call?(node)
726
+ return if addressed[node]
727
+ if node.is_a?(Prism::CallNode) && node.name == :[] &&
728
+ (subscripted = captured_name(node.receiver))
729
+ as_cells << subscripted
730
+ end
731
+ if (bare = captured_name(node)) &&
732
+ !(node.is_a?(Prism::CallNode) &&
733
+ C_FUNCTION_CALL_NAMES.include?(node.name))
734
+ as_cells << bare
735
+ end
736
+ node.compact_child_nodes.each { |child|
737
+ collect_cell_names(child, addressed, as_cells)
738
+ }
739
+ end
740
+
741
+ def scan_local_array_names (node)
742
+ return [] unless node
743
+ names = []
744
+ if node.is_a?(Prism::LocalVariableWriteNode) &&
745
+ self.class.local_array_constructor?(node.value)
746
+ names << node.name
747
+ end
748
+ node.compact_child_nodes.each { |child|
749
+ names.concat(scan_local_array_names(child))
750
+ }
751
+ names.uniq
752
+ end
753
+
455
754
  def names_constants (node)
456
755
  return [] unless node
457
756
  return [] if raise_call?(node)
757
+ # The receiver of a constructor is part of a spelling rather than a
758
+ # name the block reached outside for: `CArray.double(9)` says what to
759
+ # declare, and the class in front of it is not a value to look up.
760
+ # Left in, it is looked up, found to be a Class, and refused for
761
+ # being one -- which says nothing about the line that was written.
762
+ if self.class.local_array_constructor?(node)
763
+ arguments = node.arguments ? node.arguments.arguments : []
764
+ return arguments.flat_map { |argument| names_constants(argument) } +
765
+ names_constants(node.block)
766
+ end
458
767
  # `Foo::TABLE` names one thing, and `Foo` on its own names none of
459
768
  # it, so a path is read whole and not descended into.
460
769
  return [node.slice.to_sym] if node.is_a?(Prism::ConstantPathNode)
@@ -501,6 +810,15 @@ class CArray
501
810
  raise Unsupported.new("kernel body is empty")
502
811
  end
503
812
  @assigned_names = collect_assigned_names(block.body)
813
+ read_parameter_assignments(block.body) if @function
814
+ # A parameter the body assigns is a local from the top of the body
815
+ # on, starting at the value the call passed. Bound here, before any
816
+ # statement is built, so every read of it -- the first included --
817
+ # is a read of that local.
818
+ entry = @assigned_parameters.map { |name|
819
+ @scalar_names << name unless @scalar_names.include?(name)
820
+ assign_local(name, CaptureRead.new(name), nil)
821
+ }
504
822
 
505
823
  # A contraction may end in a bare expression rather than an
506
824
  # assignment; that is the form that allocates its result and returns.
@@ -509,19 +827,30 @@ class CArray
509
827
  # A block that ends in an assignment still has that assignment's
510
828
  # value, which is what Ruby says it has -- so jit_map keeps the
511
829
  # statement and reads the cell back for the value.
512
- map_assignment = @map && last.is_a?(Prism::LocalVariableWriteNode)
830
+ map_assignment = @map && (last.is_a?(Prism::LocalVariableWriteNode) ||
831
+ last.is_a?(Prism::LocalVariableOperatorWriteNode))
513
832
  returns_value = (@function && @returns) || (@map && !map_assignment) ||
514
833
  (@contract &&
515
834
  !(last.is_a?(Prism::CallNode) && last.name == :[]=))
516
835
  built = statements[0..-2].map { |node| build_statement(node) }
517
836
  if map_assignment
518
837
  built << build_statement(last)
838
+ # `w = CArray.double(4)` as the last line: the block's value in Ruby
839
+ # is the array, and a cell of the result has nowhere to put one.
840
+ if built.last.is_a?(LocalArrayDeclaration)
841
+ raise Unsupported.new(
842
+ "this block ends by making `#{built.last.name}`, so its value " \
843
+ "is an array and there is no cell for one to go in; end with " \
844
+ "the number you want -- a cell of it, or `sum(#{built.last.name})` " \
845
+ "and its kin",
846
+ last.location)
847
+ end
519
848
  @map_value = build_name_read(last.name, last.location)
520
849
  else
521
850
  built << (returns_value ? build(last) : build_statement(last))
522
851
  @map_value = built.last if @map
523
852
  end
524
- @body = KernelBody.new(built)
853
+ @body = KernelBody.new(entry + built)
525
854
 
526
855
  return if @contract == :probe || @map == :probe
527
856
  return if @function
@@ -544,22 +873,52 @@ class CArray
544
873
  end
545
874
  end
546
875
 
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.
876
+ # An array the kernel writes at `a[i]` is written once per outer cell,
877
+ # and reading it at `a[j]` for an inner `j` would reach cells other
878
+ # outer iterations own -- an order no extent states.
879
+ #
880
+ # What decides that is the axis the outer indices pick the cell by, not
881
+ # the array. `work[i, k] = ...` gives outer cell i a row of its own,
882
+ # and `work[i, k]`, `work[i, j]` or `work[i, position]` are that row
883
+ # being read back inside the same cell, in the order the body states.
884
+ # So a read carrying an inner index has to address every axis some
885
+ # write walks with an outer index with that same index -- at whatever
886
+ # offset, `work[i-1, k]` being the row before this one and a recurrence
887
+ # like any other displaced read. Where it does, the inner index is
888
+ # roaming inside the cell the outer ones already picked, and whatever
889
+ # the writes do along that axis they do there too.
550
890
  def verify_written_arrays_are_not_read_through_inner_indices
551
891
  @written_arrays.each do |array|
892
+ writes = @write_subscripts[array]
552
893
  @subscripts[array].each do |per_axis|
553
- per_axis.each do |index, _|
554
- next unless @inner_names_seen.include?(index)
894
+ inner = per_axis.map(&:first).find { |index|
895
+ @inner_names_seen.include?(index)
896
+ }
897
+ spelled = @index_sources.fetch(inner, inner) if inner
898
+ next unless inner
899
+ per_axis.each_with_index do |(index, _), axis|
900
+ name = outer_name_written(writes, axis)
901
+ next if name.nil? || name == index
555
902
  raise Unsupported.new(
556
- "`#{array}` is written by this kernel, so it cannot also be " \
557
- "read through the inner index `#{index}`")
903
+ "`#{array}` is written on axis #{axis} through `#{name}`, " \
904
+ "so it cannot also be read through the inner index " \
905
+ "`#{spelled}`: a read that carries one addresses axis " \
906
+ "#{axis} through `#{name}` too, or it reaches cells another " \
907
+ "outer iteration owns")
558
908
  end
559
909
  end
560
910
  end
561
911
  end
562
912
 
913
+ # The outer index the writes walk one axis with, where they agree on
914
+ # one. Writes that pin the axis or work its position out say nothing
915
+ # about which cell is whose, and so name nothing here.
916
+ def outer_name_written (writes, axis)
917
+ names = writes.map { |written| written[axis]&.first }
918
+ .select { |name| @outer_names.include?(name) }.uniq
919
+ names.size == 1 ? names.first : nil
920
+ end
921
+
563
922
  # Turns `c[i,j] = a[i,k] * b[k,j]` into the loops it stands for.
564
923
  #
565
924
  # Which indices are summed is not the assignment's business: a repeated
@@ -593,6 +952,23 @@ class CArray
593
952
  "array of your own")
594
953
  end
595
954
 
955
+ # And one expression is all of it: a local assigned before the summand
956
+ # is a run of statements, which a contraction has no room for -- the
957
+ # same reason it takes no local array. Said here, because what said
958
+ # it before was the type assignment, which walks the accumulator's
959
+ # zero -- taken from the summand, above the loop the local is
960
+ # assigned in -- and reported the local as read before it is assigned.
961
+ leading = statements[0..-2].grep(Assignment).map(&:name)
962
+ unless leading.empty?
963
+ names = leading.uniq.map { |name| "`#{name}`" }.join(" and ")
964
+ raise Unsupported.new(
965
+ "#{names} #{leading.uniq.size == 1 ? 'is' : 'are'} assigned " \
966
+ "before the summand, and a contraction is one expression: there " \
967
+ "is no run of statements to assign in. Write the value into the " \
968
+ "expression, or write the loop with jit_for",
969
+ statements.first.location)
970
+ end
971
+
596
972
  write = writes.first
597
973
  unless write
598
974
  # The returned form: the free indices, in the order the block named
@@ -664,6 +1040,7 @@ class CArray
664
1040
  def classify_indices (statements, write)
665
1041
  counts = Hash.new(0)
666
1042
  subscripts_of(statements, write).each { |index, _| counts[index] += 1 }
1043
+ refuse_a_gathered_index(statements, write)
667
1044
 
668
1045
  missing = @index_names - counts.keys
669
1046
  unless missing.empty?
@@ -671,25 +1048,84 @@ class CArray
671
1048
  "#{missing.map { |name| "`#{name}`" }.join(', ')} " \
672
1049
  "#{missing.size == 1 ? 'names no axis' : 'name no axis'} here")
673
1050
  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.
1051
+ # Naming the result's axes says which indices are free, and says it
1052
+ # of all of them: what is not named is summed, at however few
1053
+ # positions it sits. So a named index is free however often it
1054
+ # appears -- twice is what a point number does, and `square[a,a]` is
1055
+ # the diagonal rather than a trace -- and a parameter is summed
1056
+ # whether it repeats or not, which is what lets `"ik->i"` be written.
1057
+ #
1058
+ # Where nothing is named the convention decides instead, and there a
1059
+ # repetition is what makes a sum: an index at one position is free,
1060
+ # because a list that would have said otherwise was not given.
681
1061
  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]
1062
+ return [@free_indices, @index_names - @free_indices]
688
1063
  end
689
1064
  [@index_names.select { |name| counts[name] == 1 },
690
1065
  @index_names.select { |name| counts[name] > 1 }]
691
1066
  end
692
1067
 
1068
+ # An index inside a computed subscript -- `m[i, idx[k]]` -- is read at a
1069
+ # position, but not at a position of the array it stands in: it
1070
+ # addresses an axis of `idx`. A contraction is a count of positions,
1071
+ # and this one cannot be counted either way. Counting it makes
1072
+ # `m[i, idx[k]] * v[k]` a sum over k, which is what the notation says,
1073
+ # and makes `a[i, k] * w[idx[i]]` a sum over i, which is not what
1074
+ # anybody writing a gather means. Not counting it leaves the first one
1075
+ # a free index and the answer an outer product -- a whole matrix where
1076
+ # the reader asked for a vector, quietly.
1077
+ #
1078
+ # So it is refused, and the loop it means is written with jit_for,
1079
+ # where the gather is the ordinary computed subscript it already is.
1080
+ def refuse_a_gathered_index (statements, write)
1081
+ summand = write.is_a?(ElementWrite) ? write.expression : write
1082
+ found = []
1083
+ # Inside a gathering subscript every index is one of these, however
1084
+ # deep: `a[idx[jdx[k]]]` gathers twice and k is no more a position of
1085
+ # `a` for it.
1086
+ inside = lambda do |node|
1087
+ return unless node.is_a?(Node)
1088
+ subscript_indices(node).each do |index|
1089
+ found << index if @index_names.include?(index)
1090
+ end
1091
+ gathered_subscripts(node).each { |offset| inside.call(offset) }
1092
+ node.children.each { |child| inside.call(child) }
1093
+ end
1094
+ walk = lambda do |node|
1095
+ return unless node.is_a?(Node)
1096
+ gathered_subscripts(node).each { |offset| inside.call(offset) }
1097
+ node.children.each { |child| walk.call(child) }
1098
+ end
1099
+ (statements[0..-2] + [summand]).each { |statement| walk.call(statement) }
1100
+ return if found.empty?
1101
+ names = found.uniq.map { |name| "`#{name}`" }.join(" and ")
1102
+ raise Unsupported.new(
1103
+ "#{names} #{found.uniq.size == 1 ? 'stands' : 'stand'} inside a " \
1104
+ "subscript the kernel works out, where a contraction cannot count " \
1105
+ "it: an index there addresses an axis of the array doing the " \
1106
+ "gathering, not of the array being read, so whether it is summed " \
1107
+ "or free is not something the notation says. Write the loop with " \
1108
+ "jit_for",
1109
+ summand.location)
1110
+ end
1111
+
1112
+ # The indices a read names directly, as the symbols a subscript pair
1113
+ # carries rather than as nodes.
1114
+ def subscript_indices (node)
1115
+ return [] unless node.is_a?(ElementRead) || node.is_a?(MaskTest)
1116
+ node.subscripts.filter_map { |index, _| index }
1117
+ end
1118
+
1119
+ # The subscript expressions a read works out, which are the ones an
1120
+ # index can hide in. A plain `a[i]` or `a[i-1]` carries its index in
1121
+ # the pair itself and has none of these.
1122
+ def gathered_subscripts (node)
1123
+ return [] unless node.is_a?(ElementRead) || node.is_a?(MaskTest)
1124
+ node.subscripts.filter_map { |index, offset|
1125
+ offset if index.nil? && offset.is_a?(Node)
1126
+ }
1127
+ end
1128
+
693
1129
  # Every subscript on the right-hand side: the summand, and whatever the
694
1130
  # locals before it read.
695
1131
  def subscripts_of (statements, write)
@@ -706,22 +1142,6 @@ class CArray
706
1142
  collected.reject { |index, _| index.nil? }
707
1143
  end
708
1144
 
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
1145
  def describe_index_mismatch (written, free, summed)
726
1146
  summed_on_left = written & summed
727
1147
  unless summed_on_left.empty?
@@ -729,6 +1149,16 @@ class CArray
729
1149
  # the result's axes, and the one that repeats is only the reason
730
1150
  # the convention could not see it.
731
1151
  named = written.map { |name| ":#{name}" }.join(", ")
1152
+ if @free_indices
1153
+ # A list was given, so what is summed is what the list left out.
1154
+ # An axis of the left-hand side that is not in it is the list
1155
+ # falling short of the result, not the index being a sum.
1156
+ listed = summed_on_left.map { |name| "`#{name}`" }.join(", ")
1157
+ return "#{listed} #{summed_on_left.size == 1 ? 'is an axis' : 'are axes'} " \
1158
+ "of the left-hand side and #{summed_on_left.size == 1 ? 'was' : 'were'} " \
1159
+ "not named. Naming the result's axes names all of them, and " \
1160
+ "what is left out is summed: `CArray.jit_contract(#{named})`"
1161
+ end
732
1162
  return "#{summed_on_left.map { |name| "`#{name}`" }.join(', ')} " \
733
1163
  "#{summed_on_left.size == 1 ? 'is repeated' : 'are repeated'} " \
734
1164
  "on the right, so #{summed_on_left.size == 1 ? 'it is' : 'they are'} " \
@@ -768,6 +1198,19 @@ class CArray
768
1198
  # block's locals, and the indices -- an index called `contraction` would
769
1199
  # have shared the identifier with the accumulator and the sum would have
770
1200
  # come out zero, with nothing said.
1201
+ # The identifier an inner index gets in the generated C: its own name
1202
+ # the first time it is written, and a numbered one after that. Every
1203
+ # name the block has said is avoided, so nothing collides with a local
1204
+ # or another index.
1205
+ def free_index_name (index)
1206
+ return index unless @inner_sources_seen.include?(index)
1207
+ taken = @local_names + @index_names + @outer_names + @inner_names +
1208
+ @inner_names_seen + @contracted_names
1209
+ suffix = 2
1210
+ suffix += 1 while taken.include?(:"#{index}__#{suffix}")
1211
+ :"#{index}__#{suffix}"
1212
+ end
1213
+
771
1214
  def free_local_name
772
1215
  taken = @local_names + @index_names + @outer_names + @inner_names +
773
1216
  @contracted_names
@@ -785,16 +1228,72 @@ class CArray
785
1228
  extract_block(result.value)
786
1229
  end
787
1230
 
1231
+ # Every spelling that binds a local: `x = e`, the operator forms, which
1232
+ # read the name and write it back, and a name on the left of a parallel
1233
+ # assignment. A name the block assigns is not a name it captures, and
1234
+ # one that is an array outside is the array the whole-array spelling
1235
+ # writes -- so an operator form left out here would have `out += a`
1236
+ # reaching for a capture that is not there.
1237
+ ASSIGNMENT_NODES = [Prism::LocalVariableWriteNode,
1238
+ Prism::LocalVariableOperatorWriteNode,
1239
+ Prism::LocalVariableOrWriteNode,
1240
+ Prism::LocalVariableAndWriteNode,
1241
+ Prism::LocalVariableTargetNode].freeze
1242
+
788
1243
  def collect_assigned_names (node)
789
1244
  return [] unless node
790
1245
  names = []
791
- names << node.name if node.is_a?(Prism::LocalVariableWriteNode)
1246
+ names << node.name if ASSIGNMENT_NODES.any? { |kind| node.is_a?(kind) }
792
1247
  node.compact_child_nodes.each do |child|
793
1248
  names.concat(collect_assigned_names(child))
794
1249
  end
795
1250
  names.uniq
796
1251
  end
797
1252
 
1253
+ # A parameter the body assigns. Left alone, the assignment was taken
1254
+ # for a new local of the same name -- declared a second time in the C,
1255
+ # which the C compiler refused, or read before it was assigned, which
1256
+ # is not what it is.
1257
+ #
1258
+ # In a `jit_function` body it is what C makes of it: the parameter is
1259
+ # the function's own copy of what it was called with, and a body may
1260
+ # count it down or replace it. So a number is kept, and bound as a
1261
+ # local at the top of the body (see #analyze). A pointer is not: the
1262
+ # name is the array the body indexes, and a second array under it is
1263
+ # nothing the body's arrays can be.
1264
+ #
1265
+ # For `jit_call` it is the leak in the block reading as a closure: the
1266
+ # name is a local of the method around the call, and assigning it
1267
+ # looks like writing that local, which a value passed by value cannot
1268
+ # do. An array parameter does come back, through its cells, so the
1269
+ # two look alike and only one of them works.
1270
+ def read_parameter_assignments (node)
1271
+ return unless node
1272
+ if ASSIGNMENT_NODES.any? { |kind| node.is_a?(kind) } &&
1273
+ @parameter_names.include?(node.name)
1274
+ name = node.name
1275
+ if @declared_parameters
1276
+ raise Unsupported.new(
1277
+ "`#{name}` is a parameter of this `jit_call`, so assigning it " \
1278
+ "looks like writing the local of that name -- and a parameter " \
1279
+ "is passed by value, so nothing written here would reach it. " \
1280
+ "An array's cells do come back, as in `#{name}[0] = ...`; for " \
1281
+ "a number, give the body a local of its own name, or pass a " \
1282
+ "one-cell array and write its cell",
1283
+ node.location)
1284
+ end
1285
+ if @pointers.key?(name)
1286
+ raise Unsupported.new(
1287
+ "`#{name}` is a pointer parameter, and the body's arrays are " \
1288
+ "the ones it was called with; write its cells, as in " \
1289
+ "`#{name}[0] = ...`, rather than the name",
1290
+ node.location)
1291
+ end
1292
+ @assigned_parameters << name unless @assigned_parameters.include?(name)
1293
+ end
1294
+ node.compact_child_nodes.each { |child| read_parameter_assignments(child) }
1295
+ end
1296
+
798
1297
  # Accepts `->(i) { ... }`, `proc { |i| ... }` and `lambda { |i| ... }`.
799
1298
  def extract_block (program)
800
1299
  statements = program.statements.body
@@ -827,7 +1326,23 @@ class CArray
827
1326
  end
828
1327
 
829
1328
  if @function
830
- @parameter_names = requireds.map(&:name)
1329
+ # A body written for `jit_call` takes no parameters: the names are
1330
+ # in the declaration, where they also name the locals the call
1331
+ # reads. Written in both places they could disagree, so the block
1332
+ # is refused for naming any.
1333
+ if @declared_parameters
1334
+ unless requireds.empty?
1335
+ raise Unsupported.new(
1336
+ "this body's parameters are the ones its declaration named " \
1337
+ "-- #{@declared_parameters.map { |n| "`#{n}`" }.join(', ')} " \
1338
+ "-- and the block names #{requireds.map { |p| "`#{p.name}`" }.join(', ')} " \
1339
+ "again. Written in two places they could disagree; leave " \
1340
+ "the block's off")
1341
+ end
1342
+ @parameter_names = @declared_parameters
1343
+ else
1344
+ @parameter_names = requireds.map(&:name)
1345
+ end
831
1346
  @index_names = []
832
1347
  @outer_names = []
833
1348
  return
@@ -955,31 +1470,34 @@ class CArray
955
1470
  @uses_undef = true
956
1471
  return MaskWrite.new(node.name, node.location)
957
1472
  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)
1473
+ # A constructor is read before the right-hand side is built, the way
1474
+ # `out = UNDEF` above is: what it says is a declaration, not a value
1475
+ # to compute and store.
1476
+ if (made = read_local_array_constructor(node.value))
1477
+ storage, shape, zeroed = made
1478
+ return declare_local_array(node.name, storage, shape, zeroed,
1479
+ node.location, node.depth)
966
1480
  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)
979
- end
980
- @local_names << node.name unless @local_names.include?(node.name)
981
- Assignment.new(node.name, expression, node.location)
1481
+ expression = build(node.value)
1482
+ assign_local(node.name, expression, node.location, node.depth)
1483
+ when Prism::LocalVariableOperatorWriteNode
1484
+ build_operator_assignment(node)
1485
+ when Prism::MultiWriteNode
1486
+ build_parallel_assignment(node)
1487
+ when Prism::IndexOperatorWriteNode
1488
+ build_operator_element_write(node)
1489
+ when Prism::LocalVariableOrWriteNode, Prism::LocalVariableAndWriteNode,
1490
+ Prism::IndexOrWriteNode, Prism::IndexAndWriteNode
1491
+ raise Unsupported.new(
1492
+ "`#{node.is_a?(Prism::LocalVariableOrWriteNode) ||
1493
+ node.is_a?(Prism::IndexOrWriteNode) ? '||=' : '&&='}` asks " \
1494
+ "whether the value is already nil or false, which a number in a " \
1495
+ "kernel never is; write the assignment out",
1496
+ node.location)
982
1497
  when Prism::CallNode
1498
+ if (intrinsic = build_intrinsic_statement(node))
1499
+ return intrinsic
1500
+ end
983
1501
  if node.receiver.nil? && node.name == :printf
984
1502
  return build_print(node)
985
1503
  end
@@ -989,7 +1507,8 @@ class CArray
989
1507
  # pointed -- which is the whole reason C has statements that are
990
1508
  # calls. Everything else keeps the refusal below: a computation
991
1509
  # standing where a statement stands is a line that does nothing.
992
- if (call = recursive_call(node) || c_function_call(node))
1510
+ if (call = recursive_call(node) || c_function_call(node) ||
1511
+ random_call(node))
993
1512
  @calls_for_effect = true
994
1513
  return CallStatement.new(call, node.location)
995
1514
  end
@@ -1003,13 +1522,58 @@ class CArray
1003
1522
  when Prism::BreakNode
1004
1523
  build_loop_jump(node, LoopStop, "break")
1005
1524
  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)
1525
+ refuse_as_a_statement(node, statement_description(node))
1010
1526
  end
1011
1527
  end
1012
1528
 
1529
+ # What a body may hold, said in one place: it was said in two, and they
1530
+ # had already come apart -- one of them listing `step` and the other
1531
+ # not.
1532
+ def refuse_as_a_statement (node, description)
1533
+ hint = STATEMENT_HINTS[node.class]
1534
+ raise Unsupported.new(
1535
+ "a kernel body holds assignments, `if`, `while`, `(a...b).each`, " \
1536
+ "`n.times` and `a.step(b, s)` only, got #{description}" +
1537
+ (hint ? " -- #{hint}" : ""),
1538
+ node.location)
1539
+ end
1540
+
1541
+ # A refusal names what was written rather than the node this compiler
1542
+ # read it as: `Prism::UnlessNode` is not a thing anyone typed.
1543
+ STATEMENT_DESCRIPTIONS = {
1544
+ Prism::UnlessNode => "`unless`",
1545
+ Prism::UntilNode => "`until`",
1546
+ Prism::CaseNode => "`case`",
1547
+ Prism::CaseMatchNode => "`case ... in`",
1548
+ Prism::ForNode => "`for`",
1549
+ Prism::ReturnNode => "`return`",
1550
+ Prism::BeginNode => "`begin`",
1551
+ Prism::RescueModifierNode => "`rescue`",
1552
+ Prism::MultiWriteNode => "a parallel assignment",
1553
+ Prism::InstanceVariableWriteNode => "an instance variable",
1554
+ Prism::GlobalVariableWriteNode => "a global variable",
1555
+ Prism::ConstantWriteNode => "an assignment to a constant",
1556
+ Prism::DefNode => "a method definition",
1557
+ }.freeze
1558
+
1559
+ STATEMENT_HINTS = {
1560
+ Prism::UnlessNode => "write it as `if` with the condition negated",
1561
+ Prism::UntilNode => "write it as `while` with the condition negated",
1562
+ Prism::CaseNode => "write the branches out with `if` and `elsif`",
1563
+ Prism::ReturnNode => "a body is one expression, and its value is " \
1564
+ "the last thing in it",
1565
+ # A statement, and a supported one; what it has not got is a value.
1566
+ # Ruby's is the array of what it wrote, and a cell holds a number.
1567
+ Prism::MultiWriteNode =>
1568
+ "its value in Ruby is the array of what it " \
1569
+ "wrote, and a cell holds a number; end with " \
1570
+ "the one you want",
1571
+ }.freeze
1572
+
1573
+ def statement_description (node)
1574
+ STATEMENT_DESCRIPTIONS.fetch(node.class) { node_name(node) }
1575
+ end
1576
+
1013
1577
  # `next` skips the rest of this iteration, `break` leaves the loop. Both
1014
1578
  # mean in the generated loop what they mean in the Ruby loop it replaces
1015
1579
  # -- with one exception: Ruby's `break` in the kernel block is not a
@@ -1061,7 +1625,7 @@ class CArray
1061
1625
  "Ruby; write the test at the top",
1062
1626
  node.location)
1063
1627
  end
1064
- condition = build(node.predicate)
1628
+ condition = build_condition(node.predicate)
1065
1629
  @loop_depth += 1
1066
1630
  statements = statements_of(node.statements).map { |inner|
1067
1631
  build_statement(inner)
@@ -1116,7 +1680,12 @@ class CArray
1116
1680
  def build_inner_loop (node)
1117
1681
  counted = node.name == :times
1118
1682
  range = unwrap(node.receiver)
1119
- if !counted && !range.is_a?(Prism::RangeNode)
1683
+ # Two spellings carry a stride, and they are Ruby's two: a range
1684
+ # steps within the ends it already states, and an integer steps to
1685
+ # one it is given. Which is which is the receiver.
1686
+ stepped = node.name == :step && !range.is_a?(Prism::RangeNode)
1687
+ walked = node.name == :step && range.is_a?(Prism::RangeNode)
1688
+ if !counted && !stepped && !range.is_a?(Prism::RangeNode)
1120
1689
  raise Unsupported.new(
1121
1690
  "an inner loop runs over a range, as in `(0...n).each { |j| ... }`",
1122
1691
  node.location)
@@ -1132,7 +1701,7 @@ class CArray
1132
1701
  if index_in_scope?(index)
1133
1702
  raise Unsupported.new("`#{index}` is already an index here", node.location)
1134
1703
  end
1135
- unless counted || (range.left && range.right)
1704
+ unless counted || stepped || (range.left && range.right)
1136
1705
  raise Unsupported.new("an inner loop needs both ends of its range",
1137
1706
  node.location)
1138
1707
  end
@@ -1140,26 +1709,106 @@ class CArray
1140
1709
  # `n.times` is `(0...n).each`: the count is the exclusive end, and it
1141
1710
  # is built from the same vocabulary a range end is, so a literal and a
1142
1711
  # captured integer both serve.
1712
+ #
1713
+ # `from.step(to, s)` is the spelling an extent already says a stride
1714
+ # or a downward sweep with, and it means here what it means there --
1715
+ # `to` included, which is Ruby's own reading of it and the opposite of
1716
+ # a `...` range's. It is turned into an exclusive end at once, so
1717
+ # everything below this line counts the way `each` does.
1718
+ step = 1
1143
1719
  if counted
1144
1720
  from = IntegerLiteral.new(0, node.location)
1145
1721
  to = build(node.receiver)
1722
+ elsif stepped
1723
+ from = build(node.receiver)
1724
+ arguments = node.arguments ? node.arguments.arguments : []
1725
+ unless arguments.size == 2
1726
+ raise Unsupported.new(
1727
+ "an inner loop written with `step` gives it both the last " \
1728
+ "index and the stride, as in `(n-1).step(0, -1) { |k| ... }`",
1729
+ node.location)
1730
+ end
1731
+ step = inner_loop_step(arguments.last)
1732
+ to = build(arguments.first)
1733
+ to = BinaryOperation.new(step.positive? ? :+ : :-, to,
1734
+ IntegerLiteral.new(1, node.location),
1735
+ node.location)
1146
1736
  else
1147
1737
  from = build(range.left)
1148
1738
  to = build(range.right)
1149
1739
  to = BinaryOperation.new(:+, to, IntegerLiteral.new(1, node.location),
1150
1740
  node.location) unless range.exclude_end?
1741
+ if walked
1742
+ arguments = node.arguments ? node.arguments.arguments : []
1743
+ unless arguments.size == 1
1744
+ raise Unsupported.new(
1745
+ "a range steps by one stride, as in `(0...n).step(2) " \
1746
+ "{ |j| ... }`", node.location)
1747
+ end
1748
+ step = inner_loop_step(arguments.first)
1749
+ if step.negative?
1750
+ raise Unsupported.new(
1751
+ "a range cannot step backwards, in Ruby either -- count " \
1752
+ "down with `(n-1).step(0, -1) { |j| ... }`, which is the " \
1753
+ "spelling an extent takes too",
1754
+ node.location)
1755
+ end
1756
+ end
1151
1757
  end
1152
1758
 
1759
+ # Two sibling loops may both be written `{ |k| ... }`, and each has
1760
+ # a range of its own -- a forward sweep from 1 and the sweep back
1761
+ # down from the far end are exactly that pair. They are one name in
1762
+ # the block and cannot be one variable here, because the reach of
1763
+ # `a[k-1]` is checked against the loop it sits in. So the second
1764
+ # gets an identifier of its own, and the name in the block goes on
1765
+ # meaning whichever loop it is inside.
1766
+ internal = free_index_name(index)
1153
1767
  @inner_names << index
1154
- @inner_names_seen << index
1155
- @inner_ranges[index] = [from, to]
1768
+ @inner_sources_seen << index
1769
+ @inner_names_seen << internal
1770
+ @inner_aliases[index] = internal
1771
+ @index_sources[internal] = index
1772
+ @inner_ranges[internal] = [from, to, step]
1773
+ unless Analyzer.boundable_range?(from, @unbounded_indices) &&
1774
+ Analyzer.boundable_range?(to, @unbounded_indices)
1775
+ @unbounded_indices << internal
1776
+ end
1156
1777
  @loop_depth += 1
1778
+ @scopes.push([])
1157
1779
  statements = statements_of(node.block.body).map { |inner|
1158
1780
  build_statement(inner)
1159
1781
  }
1782
+ @scopes.pop
1160
1783
  @loop_depth -= 1
1161
1784
  @inner_names.pop
1162
- InnerLoop.new(index, from, to, statements, node.location)
1785
+ @inner_aliases.delete(index)
1786
+ InnerLoop.new(internal, from, to, statements, node.location, step)
1787
+ end
1788
+
1789
+ # The stride is read off the page rather than computed, because it is
1790
+ # what says which way the loop runs and the C has to be written one way
1791
+ # or the other before anything is known. A captured integer would
1792
+ # leave that open.
1793
+ def inner_loop_step (node)
1794
+ value = case node
1795
+ when Prism::IntegerNode then node.value
1796
+ when Prism::CallNode
1797
+ if node.name == :-@ && node.receiver.is_a?(Prism::IntegerNode)
1798
+ -node.receiver.value
1799
+ end
1800
+ end
1801
+ if value.nil?
1802
+ raise Unsupported.new(
1803
+ "an inner loop's stride says which way it runs, so it is an " \
1804
+ "integer literal -- `step(0, -1)` for a downward sweep",
1805
+ node.location)
1806
+ end
1807
+ if value.zero?
1808
+ raise Unsupported.new("an inner loop's stride cannot be zero",
1809
+ node.location)
1810
+ end
1811
+ value
1163
1812
  end
1164
1813
 
1165
1814
  def unwrap (node)
@@ -1180,7 +1829,7 @@ class CArray
1180
1829
  else
1181
1830
  raise Unsupported.new("unsupported `if` continuation", node.location)
1182
1831
  end
1183
- Branch.new(build(node.predicate),
1832
+ Branch.new(build_condition(node.predicate),
1184
1833
  statements_of(node.statements).map { |inner|
1185
1834
  build_statement(inner)
1186
1835
  },
@@ -1192,8 +1841,1014 @@ class CArray
1192
1841
  node ? node.body : []
1193
1842
  end
1194
1843
 
1844
+ # Handing a local array to a C function, with the declaration matched
1845
+ # against what the block wrote.
1846
+ #
1847
+ # The length check is the one that matters, and it is not decoration: a
1848
+ # compiled function does not check a subscript on a pointer parameter
1849
+ # (unchecked by design -- the caller's business, as in C), and this
1850
+ # array is on the stack of the block that made it. A declaration that
1851
+ # reads more cells than the array holds would walk over the frame, and
1852
+ # the return address is in it.
1853
+ def build_local_array_address (name, argument, parameter, c_function)
1854
+ storage, shape = local_array_in_sight(name)
1855
+ # The callee is handed a pointer to the cells, and the shadow beside
1856
+ # them travels in no C declaration there is a spelling for here: a
1857
+ # signature says what it points at, and "these cells, and a byte each
1858
+ # saying whether they are there" is not one of the things it can say.
1859
+ if @masked
1860
+ raise Unsupported.new(
1861
+ "`#{name}` is a local array of this kernel, and the kernel " \
1862
+ "carries masks -- so `#{name}` carries one beside each of its " \
1863
+ "cells, and there is no way to hand that to `#{c_function}`: a " \
1864
+ "signature says what a pointer points at, and a mask travels in " \
1865
+ "no C declaration. Copy the cells the function is to see into a " \
1866
+ "captured array, or answer the mask in the kernel",
1867
+ argument.location)
1868
+ end
1869
+ unless parameter&.pointer
1870
+ raise Unsupported.new(
1871
+ "`#{c_function}` takes #{parameter ? "`#{parameter.text}`" : 'a number'} " \
1872
+ "there, and `#{name}` is an array; pass a cell of it, as in " \
1873
+ "`#{name}[0]`",
1874
+ argument.location)
1875
+ end
1876
+ unless parameter.indexable?
1877
+ raise Unsupported.new(
1878
+ "`#{c_function}` takes `#{parameter.text}` there, which points at " \
1879
+ "nothing in particular, so there is no element type to hold " \
1880
+ "`#{name}` to; declare what it points at",
1881
+ argument.location)
1882
+ end
1883
+ wanted = CDeclaration::DATA_TYPES.fetch(parameter.element.fiddle).to_s
1884
+ unless storage == wanted
1885
+ raise Unsupported.new(
1886
+ "`#{c_function}` takes `#{parameter.text}` there, which is a " \
1887
+ "#{wanted} array, and `#{name}` is #{storage}; make it with " \
1888
+ "`CArray.new(:#{wanted}, [#{shape.first}])` -- an element type is " \
1889
+ "matched exactly here, as it is for a captured array",
1890
+ argument.location)
1891
+ end
1892
+ # The cells over every axis, because that is what the callee is handed:
1893
+ # a local array is packed row-major, so `double[3][4]` is the twelve
1894
+ # cells `const double a[12]` asks for.
1895
+ #
1896
+ # A declaration that names a length can only be held to a shape that
1897
+ # is numbers: the two are matched as the block is read, and a length
1898
+ # the kernel works out is not there yet. A pointer with no length is
1899
+ # the C that says "however many you have", and takes one.
1900
+ unless literal_shape?(shape)
1901
+ if parameter.sized?
1902
+ raise Unsupported.new(
1903
+ "`#{c_function}` takes `#{parameter.text}` there, which reads " \
1904
+ "#{parameter.array} cells, and the shape of `#{name}` is " \
1905
+ "worked out when the kernel runs -- so the two cannot be " \
1906
+ "matched here, and the function checks no subscript of its " \
1907
+ "own. Declare the parameter without a length " \
1908
+ "(`#{parameter.element.text} *`), which promises nothing about " \
1909
+ "how many cells there are, or give `#{name}` a shape written " \
1910
+ "out",
1911
+ argument.location)
1912
+ end
1913
+ return LocalArrayAddress.new(name, storage, shape, argument.location)
1914
+ end
1915
+ cells = shape.inject(1, :*)
1916
+ if parameter.sized? && cells < parameter.array
1917
+ shaped = shape.size == 1 ? "" :
1918
+ " (#{shape.join(' x ')}, packed row-major)"
1919
+ raise Unsupported.new(
1920
+ "`#{c_function}` takes `#{parameter.text}` there, which reads " \
1921
+ "#{parameter.array} cells, and `#{name}` has #{cells}#{shaped}. A " \
1922
+ "local array is on this block's stack and the function does not " \
1923
+ "check a subscript of its own, so the missing cells would be the " \
1924
+ "frame; give `#{name}` at least #{parameter.array}",
1925
+ argument.location)
1926
+ end
1927
+ LocalArrayAddress.new(name, storage, shape, argument.location)
1928
+ end
1929
+
1930
+ # ---- the functions the compiler brings with it ----
1931
+
1932
+ # Written as bare calls, the way `random(rng: r)` is: names of this
1933
+ # compiler's own rather than methods on an array, so that what each one
1934
+ # means is settled here instead of promising CArray's method semantics
1935
+ # -- `axis:`, masks skipped, an identity for an empty array, a CScalar
1936
+ # or a number back, one type promoted to another.
1937
+ #
1938
+ # A bare name with no arguments is a capture (`sum = 0.0` beside
1939
+ # `sum(w)` is a local and reads as one), and a name with a receiver is a
1940
+ # call to whatever holds it (`sum.call(x)`), so neither collides.
1941
+ INTRINSIC_VALUES = [:sum, :min, :max].freeze
1942
+ INTRINSIC_STATEMENTS = [:sort].freeze
1943
+ INTRINSIC_NAMES = (INTRINSIC_VALUES + INTRINSIC_STATEMENTS).freeze
1944
+
1945
+ # `sum(w)` / `min(w)` / `max(w)` in an expression, or nil where this
1946
+ # call is not one of them.
1947
+ def build_intrinsic_call (node)
1948
+ return nil unless intrinsic_spelling?(node)
1949
+ if INTRINSIC_STATEMENTS.include?(node.name)
1950
+ raise Unsupported.new(
1951
+ "`#{node.name}` rearranges the array and has no value to put " \
1952
+ "anywhere; write it on a line of its own, then read the cells " \
1953
+ "you want",
1954
+ node.location)
1955
+ end
1956
+ return nil unless INTRINSIC_VALUES.include?(node.name)
1957
+ name, storage, shape = intrinsic_array(node)
1958
+ IntrinsicCall.new(node.name, name, storage, shape, node.location)
1959
+ end
1960
+
1961
+ # `sort(w)` standing as a statement.
1962
+ def build_intrinsic_statement (node)
1963
+ return nil unless intrinsic_spelling?(node)
1964
+ if INTRINSIC_VALUES.include?(node.name)
1965
+ raise Unsupported.new(
1966
+ "`#{node.name}` works out a value, and standing on a line of its " \
1967
+ "own it puts that value nowhere; assign it, as in " \
1968
+ "`total = #{node.name}(...)`",
1969
+ node.location)
1970
+ end
1971
+ return nil unless INTRINSIC_STATEMENTS.include?(node.name)
1972
+ name, storage, shape = intrinsic_array(node)
1973
+ # Deliberately not `@calls_for_effect`: rearranging an array the body
1974
+ # made leaves nothing outside the kernel, so a kernel whose only work
1975
+ # is a `sort` has still put its work nowhere and is told so.
1976
+ IntrinsicStatement.new(node.name, name, storage, shape, node.location)
1977
+ end
1978
+
1979
+ def intrinsic_spelling? (node)
1980
+ node.is_a?(Prism::CallNode) && node.receiver.nil? && node.block.nil? &&
1981
+ INTRINSIC_NAMES.include?(node.name) && !node.arguments.nil?
1982
+ end
1983
+
1984
+ # The one argument, which is a local array of one axis.
1985
+ def intrinsic_array (node)
1986
+ arguments = node.arguments ? node.arguments.arguments : []
1987
+ unless arguments.size == 1
1988
+ raise Unsupported.new(
1989
+ "`#{node.name}` takes one local array, and #{arguments.size} " \
1990
+ "#{arguments.size == 1 ? 'argument was' : 'arguments were'} " \
1991
+ "given" +
1992
+ (node.name == :min || node.name == :max ?
1993
+ ". For the #{node.name} of two numbers write " \
1994
+ "`x #{node.name == :min ? '<' : '>'} y ? x : y`, and to hold " \
1995
+ "a value between two bounds write `x.clamp(lo, hi)`" : ""),
1996
+ node.location)
1997
+ end
1998
+ argument = arguments.first
1999
+ name = local_array_name(argument)
2000
+ refuse_an_intrinsic_argument(node, argument) unless name
2001
+ # What the four should do with a missing cell is not decided, and
2002
+ # there is more than one defensible answer: skip it, gather it at the
2003
+ # end the way `sort` gathers NaN, count it where a median is counted.
2004
+ # So the array is refused rather than given a meaning here.
2005
+ if @masked
2006
+ raise Unsupported.new(
2007
+ "`#{node.name}(#{name})` is over a local array of this kernel, " \
2008
+ "and the kernel carries masks -- so `#{name}` carries one beside " \
2009
+ "each of its cells, and what `#{node.name}` should do with a " \
2010
+ "missing one is not decided: whether to pass over it, to gather " \
2011
+ "it at the end as `sort` gathers NaN, or to count it where a " \
2012
+ "median counts. Write the loop yourself, deciding it there",
2013
+ node.location)
2014
+ end
2015
+ storage, shape = local_array_in_sight(name)
2016
+ unless shape.size == 1
2017
+ raise Unsupported.new(
2018
+ "`#{name}` has #{shape.size} axes, and `#{node.name}` takes an " \
2019
+ "array of one axis. A sweep over every cell would read well " \
2020
+ "enough for `sum`, and `sort` over one has no obvious meaning at " \
2021
+ "all, so the four keep one rule; walk the axes yourself, or copy " \
2022
+ "the cells you want into an array of one axis",
2023
+ node.location)
2024
+ end
2025
+ [name, storage, shape]
2026
+ end
2027
+
2028
+ # What was passed instead, said in the terms it was written in.
2029
+ def refuse_an_intrinsic_argument (node, argument)
2030
+ passed = captured_name(argument)
2031
+ if passed && @array_names.include?(passed)
2032
+ raise Unsupported.new(
2033
+ "`#{node.name}` takes a local array -- one this block made with " \
2034
+ "`CArray.double(n)` or its kin -- and `#{passed}` is an array " \
2035
+ "the block closed over. Walking all of it at every cell is a " \
2036
+ "pass over the whole array per cell, which is not what the line " \
2037
+ "looks like it costs; `#{passed}.#{node.name}` outside the " \
2038
+ "kernel says what it costs, and a loop written out says it too",
2039
+ node.location)
2040
+ end
2041
+ raise Unsupported.new(
2042
+ "`#{node.name}` takes a local array -- one this block made with " \
2043
+ "`CArray.double(n)` or its kin -- named on its own, and " \
2044
+ "`#{argument.slice}` is not one",
2045
+ node.location)
2046
+ end
2047
+
2048
+ # ---- the arrays a body makes for itself ----
2049
+
2050
+ LOCAL_ARRAY_SUBSCRIPTS = [:[], :[]=].freeze
2051
+
2052
+ # A constructor stands on the right of an assignment and nowhere else.
2053
+ # Everywhere else it is an array standing where a number goes -- in an
2054
+ # argument, under a subscript, as an operand -- and there is no name for
2055
+ # the C array to be declared under.
2056
+ def refuse_a_constructor_in_an_expression (node)
2057
+ return unless node
2058
+ return unless self.class.local_array_constructor?(node)
2059
+ raise Unsupported.new(
2060
+ "`#{node.slice}` makes an array, and a constructor stands on the " \
2061
+ "right of an assignment and nowhere else: write " \
2062
+ "`w = #{node.slice}` on a line of its own, then reach `w` at a " \
2063
+ "subscript",
2064
+ node.location)
2065
+ end
2066
+
2067
+ # `[storage type, shape, zeroed]` for a constructor, or nil where this
2068
+ # call is not one. Nil is the answer for everything else a body may
2069
+ # write, so nothing outside this vocabulary is touched; once the answer
2070
+ # is not nil the call is a constructor, and a spelling that cannot be
2071
+ # taken is refused here rather than further along.
2072
+ def read_local_array_constructor (node)
2073
+ return nil unless self.class.local_array_constructor?(node)
2074
+ refuse_a_local_array_in_this_spelling(node)
2075
+ if node.block
2076
+ raise Unsupported.new(
2077
+ "`#{node.slice}` fills the array from a block, and a local " \
2078
+ "array is either zero-filled or left as it stands; write the " \
2079
+ "fill out as a loop over its cells",
2080
+ node.location)
2081
+ end
2082
+ arguments = node.arguments ? node.arguments.arguments : []
2083
+ if arguments.any? { |argument| argument.is_a?(Prism::KeywordHashNode) }
2084
+ raise Unsupported.new(
2085
+ "`#{node.slice}` passes a keyword, and a local array is the " \
2086
+ "type and the shape only", node.location)
2087
+ end
2088
+ if node.receiver.is_a?(Prism::ConstantPathNode) &&
2089
+ node.receiver.slice != "::CArray"
2090
+ return refuse_a_type_class_constructor(node, arguments)
2091
+ end
2092
+ case node.name
2093
+ when :new then read_carray_new(node, arguments, true)
2094
+ when :empty then read_carray_empty(node, arguments)
2095
+ else read_type_constructor(node, arguments)
2096
+ end
2097
+ end
2098
+
2099
+ # `CArray.double(9)` and the rest of the singleton methods CArray's own
2100
+ # `Init` defines: the type is the name and the shape is the arguments.
2101
+ def read_type_constructor (node, arguments)
2102
+ if LOCAL_ARRAY_REFUSED_TYPES.include?(node.name)
2103
+ raise Unsupported.new(
2104
+ "`#{node.name}` holds #{LOCAL_ARRAY_REFUSED_TYPES_HOLD.fetch(node.name)} " \
2105
+ "rather than numbers, and a kernel computes with numbers; a " \
2106
+ "local array is one of " \
2107
+ "#{LOCAL_ARRAY_TYPES.keys.take(13).map { |name| "`#{name}`" }.join(', ')}",
2108
+ node.location)
2109
+ end
2110
+ if LOCAL_ARRAY_COMPATIBILITY_NAMES.include?(node.name)
2111
+ return refuse_a_compatibility_constructor(node, arguments, nil)
2112
+ end
2113
+ storage = LOCAL_ARRAY_TYPES.fetch(node.name)
2114
+ if arguments.empty?
2115
+ raise Unsupported.new(
2116
+ "`#{node.slice}` says no shape; a local array is written " \
2117
+ "`CArray.#{node.name}(n)` with the length written out",
2118
+ node.location)
2119
+ end
2120
+ [storage, read_local_array_shape(node, arguments), true]
2121
+ end
2122
+
2123
+ # `CArray.new(:int64, [256])` -- `rb_ca_initialize`, whose
2124
+ # `carray_safe_setup` is what fills the cells with zero.
2125
+ def read_carray_new (node, arguments, zeroed)
2126
+ unless arguments.size == 2
2127
+ raise Unsupported.new(
2128
+ "`CArray.#{node.name}` takes the type and the shape, as in " \
2129
+ "`CArray.#{node.name}(:float64, [9])`", node.location)
2130
+ end
2131
+ [read_local_array_type(node, arguments.first),
2132
+ read_local_array_shape(node, read_shape_arguments(node, arguments.last)),
2133
+ zeroed]
2134
+ end
2135
+
2136
+ # Two spellings share the name. `CArray.empty(:float64, [9])` is
2137
+ # carray's, and stands beside `CArray.new` with the fill left out;
2138
+ # `CArray.empty(9)` is the compatibility layer's NumPy form, where the
2139
+ # type is float64 by convention rather than by anything written down.
2140
+ def read_carray_empty (node, arguments)
2141
+ carray_form = arguments.size == 2 &&
2142
+ arguments.first.is_a?(Prism::SymbolNode) &&
2143
+ arguments.last.is_a?(Prism::ArrayNode)
2144
+ return read_carray_new(node, arguments, false) if carray_form
2145
+ refuse_a_compatibility_constructor(node, arguments, nil)
2146
+ end
2147
+
2148
+ def read_shape_arguments (node, argument)
2149
+ unless argument.is_a?(Prism::ArrayNode)
2150
+ raise Unsupported.new(
2151
+ "the shape `CArray.#{node.name}` takes is an Array, as in " \
2152
+ "`CArray.#{node.name}(:float64, [9])`", node.location)
2153
+ end
2154
+ argument.elements
2155
+ end
2156
+
2157
+ # The type is a Symbol written out. One of CArray's constants would be
2158
+ # a name looked up outside the block, and one kernel serves every value
2159
+ # of a capture -- so a type that arrived that way would not be in the C
2160
+ # at all.
2161
+ def read_local_array_type (node, argument)
2162
+ unless argument.is_a?(Prism::SymbolNode)
2163
+ raise Unsupported.new(
2164
+ "the type `CArray.#{node.name}` takes is a Symbol written out, " \
2165
+ "as in `CArray.#{node.name}(:float64, [9])`; " \
2166
+ "`#{argument.slice}` is looked up where the block was written, " \
2167
+ "and one compiled kernel serves every value of that",
2168
+ node.location)
2169
+ end
2170
+ name = argument.unescaped.to_sym
2171
+ if LOCAL_ARRAY_REFUSED_TYPES.include?(name)
2172
+ raise Unsupported.new(
2173
+ "`:#{name}` holds #{LOCAL_ARRAY_REFUSED_TYPES_HOLD.fetch(name)} " \
2174
+ "rather than numbers, and a kernel computes with numbers",
2175
+ node.location)
2176
+ end
2177
+ LOCAL_ARRAY_TYPES.fetch(name) do
2178
+ raise Unsupported.new(
2179
+ "`#{argument.slice}` is not a type a local array holds; they are " \
2180
+ "#{LOCAL_ARRAY_TYPES.keys.take(13).map { |n| "`:#{n}`" }.join(', ')}",
2181
+ node.location)
2182
+ end
2183
+ end
2184
+
2185
+ # The shape, one entry an axis: an Integer where the block wrote a
2186
+ # number, and a LocalArrayExtent where it wrote an expression over the
2187
+ # integers it captured. A number is what the C declares and what a
2188
+ # subscript is checked against as the block is read; an expression is
2189
+ # worked out once at the kernel's entry, and every check against it
2190
+ # happens where the cell is reached.
2191
+ def read_local_array_shape (node, arguments)
2192
+ if arguments.empty?
2193
+ raise Unsupported.new(
2194
+ "`#{node.slice}` says no shape; a local array is written " \
2195
+ "`CArray.#{node.name}(n)` with the length written out",
2196
+ node.location)
2197
+ end
2198
+ arguments.map { |argument|
2199
+ extent = literal_integer(argument)
2200
+ next captured_extent(node, argument) unless extent
2201
+ unless extent.positive?
2202
+ raise Unsupported.new(
2203
+ "a local array holds at least one cell, and `#{argument.slice}` " \
2204
+ "asks for #{extent}", node.location)
2205
+ end
2206
+ extent
2207
+ }
2208
+ end
2209
+
2210
+ # A length the kernel works out at its entry, from the integers it
2211
+ # captured.
2212
+ #
2213
+ # It may read nothing that varies from cell to cell: the array is one
2214
+ # allocation for the whole pass, made before the first cell, so an
2215
+ # extent over a loop index or an array's cell is a length that would
2216
+ # have to change underneath it. That is what is refused here, and the
2217
+ # message says which of the two was written.
2218
+ def captured_extent (node, argument)
2219
+ built = build(argument)
2220
+ varies = extent_varies_by_cell(built)
2221
+ if varies
2222
+ raise Unsupported.new(
2223
+ "`#{argument.slice}` is the shape of a local array, and " \
2224
+ "#{varies} -- the array is one allocation made before the first " \
2225
+ "cell, so its length cannot change from cell to cell. Take the " \
2226
+ "widest length the pass needs, or pass a captured array of " \
2227
+ "workspace",
2228
+ node.location)
2229
+ end
2230
+ LocalArrayExtent.new(argument.slice, built)
2231
+ end
2232
+
2233
+ # What in an extent expression is not settled before the loop runs, in
2234
+ # the words it was written in, or nil where everything in it is.
2235
+ def extent_varies_by_cell (node)
2236
+ case node
2237
+ when IntegerLiteral, CaptureRead then nil
2238
+ when UnaryMinus then extent_varies_by_cell(node.operand)
2239
+ when BinaryOperation
2240
+ unless [:+, :-, :*].include?(node.operator)
2241
+ return "`#{node.operator}` is not one of `+`, `-` and `*`"
2242
+ end
2243
+ extent_varies_by_cell(node.left) || extent_varies_by_cell(node.right)
2244
+ when IndexVariable then "`#{node.name}` is a loop index"
2245
+ when LocalRead then "`#{node.name}` is worked out inside the block"
2246
+ when ElementRead then "`#{node.array}` is a cell of an array"
2247
+ when LocalArrayRead then "`#{node.name}` is a cell of an array"
2248
+ else "it is not an integer expression over the captured integers"
2249
+ end
2250
+ end
2251
+
2252
+ # `CArray.zeros(4)`, `CArray.empty(4)`, `CArray.ones(4)`: the
2253
+ # compatibility layer, which `data_type_extension.rb` opens by calling
2254
+ # itself "Numo / NumPy-style" and "not the 'main' carray API". The
2255
+ # words inside a block are carray's own.
2256
+ def refuse_a_compatibility_constructor (node, arguments, type)
2257
+ storage = type || "float64"
2258
+ length = arguments.empty? ? nil : literal_integer(arguments.first)
2259
+ shape = length ? "[#{length}]" : "[n]"
2260
+ raise Unsupported.new(
2261
+ "`#{node.slice}` is CArray's Numo/NumPy compatibility layer, and " \
2262
+ "a kernel body is written in carray's own words: " \
2263
+ "`CArray.new(:#{storage}, #{shape})` for cells that start at zero, " \
2264
+ "or `CArray.empty(:#{storage}, #{shape})` where every cell is " \
2265
+ "written before it is read",
2266
+ node.location)
2267
+ end
2268
+
2269
+ # `CArray::Int64.empty(4)` and `CArray::Int64.zeros(4)`: the same layer
2270
+ # reached through a type class, which is the Numo spelling of it.
2271
+ def refuse_a_type_class_constructor (node, arguments)
2272
+ tail = node.receiver.slice.split("::").last.to_s
2273
+ type = LOCAL_ARRAY_TYPES[tail.downcase.to_sym]
2274
+ refuse_a_compatibility_constructor(node, arguments, type)
2275
+ end
2276
+
2277
+ # Which entry points take one, and which release takes the rest. The
2278
+ # shape of the check is the mode this analyzer was made in, because that
2279
+ # is what says how the block is being read.
2280
+ LOCAL_ARRAY_ELSEWHERE = {
2281
+ :contract => ["a contraction's body", nil],
2282
+ }.freeze
2283
+
2284
+ def refuse_a_local_array_in_this_spelling (node)
2285
+ mode = if @contract then :contract
2286
+ end
2287
+ return unless mode
2288
+ where, when_ = LOCAL_ARRAY_ELSEWHERE.fetch(mode)
2289
+ if mode == :contract
2290
+ raise Unsupported.new(
2291
+ "`#{node.slice}` makes a local array, and #{where} is one " \
2292
+ "expression -- with, at most, an assignment into an array of its " \
2293
+ "own -- so there is no run of statements for a workspace to be " \
2294
+ "used by. Write the body as `CArray.jit_for`",
2295
+ node.location)
2296
+ end
2297
+ raise Unsupported.new(
2298
+ "`#{node.slice}` makes a local array, which #{where} takes in " \
2299
+ "#{when_}; `CArray.jit_for` takes one now, and a captured array of " \
2300
+ "workspace works here today",
2301
+ node.location)
2302
+ end
2303
+
2304
+ # The declaration itself.
2305
+ def declare_local_array (name, storage, shape, zeroed, location, depth)
2306
+ if index_in_scope?(name)
2307
+ raise Unsupported.new(
2308
+ "`#{name}` is a loop index, and the loop is what owns it; give " \
2309
+ "the array a name of its own",
2310
+ location)
2311
+ end
2312
+ if @local_names.include?(name) && local_in_sight?(name)
2313
+ raise Unsupported.new(
2314
+ "`#{name}` holds a number here, and a local array is not a " \
2315
+ "number; the two cannot be one C variable, so give the array a " \
2316
+ "name of its own",
2317
+ location)
2318
+ end
2319
+ heap = heap_array?(storage, shape)
2320
+ refuse_a_heap_array_in_a_body(name, storage, shape, location) if heap
2321
+ @local_array_names << name unless @local_array_names.include?(name)
2322
+ @local_arrays[name] = [storage, shape]
2323
+ scope = [@scopes.size - 1 - depth, 0].max
2324
+ @scopes[scope] << name unless @scopes[scope].include?(name)
2325
+ entry = [scope, name, storage, shape, heap]
2326
+ @local_array_declarations << entry unless @local_array_declarations.include?(entry)
2327
+ @clears_a_local_array ||= zeroed
2328
+ LocalArrayDeclaration.new(name, storage, shape, zeroed, location, scope,
2329
+ heap)
2330
+ end
2331
+
2332
+ # Where the array goes. The stack while it is small enough to be a
2333
+ # frame's business, and the heap otherwise -- which is a placement and
2334
+ # not a refusal: nothing is turned away for its size any more.
2335
+ #
2336
+ # Three ways onto the heap: a length the kernel works out, an array
2337
+ # larger than one frame should hold, and an array that fits on its own
2338
+ # but not beside the ones this kernel already has.
2339
+ def heap_array? (storage, shape)
2340
+ return true unless literal_shape?(shape)
2341
+ bytes = local_array_bytes(storage, shape)
2342
+ return true if bytes > LOCAL_ARRAY_BYTE_LIMIT
2343
+ bytes + stack_array_bytes > LOCAL_ARRAY_TOTAL_BYTE_LIMIT
2344
+ end
2345
+
2346
+ def literal_shape? (shape)
2347
+ shape.all? { |extent| extent.is_a?(Integer) }
2348
+ end
2349
+
2350
+ # What the arrays declared so far take on the stack. Two sibling
2351
+ # loops that each declare `w` are one entry: their blocks do not
2352
+ # overlap, so neither do their frames.
2353
+ def stack_array_bytes
2354
+ @local_array_declarations.sum { |_scope, _name, storage, shape, heap|
2355
+ heap ? 0 : local_array_bytes(storage, shape)
2356
+ }
2357
+ end
2358
+
2359
+ # A compiled function's body is called once per cell, so an allocation
2360
+ # in it would be one per cell -- and the body has no entry of its own
2361
+ # outside the kernel's loop to make it at instead.
2362
+ def refuse_a_heap_array_in_a_body (name, storage, shape, location)
2363
+ return unless @function
2364
+ reason =
2365
+ if literal_shape?(shape)
2366
+ "`#{name}` asks for " \
2367
+ "#{local_array_bytes_phrase(storage, shape)}, which is past the " \
2368
+ "#{LOCAL_ARRAY_BYTE_LIMIT} a frame here holds"
2369
+ else
2370
+ "the shape of `#{name}` is worked out when the kernel runs"
2371
+ end
2372
+ raise Unsupported.new(
2373
+ "#{reason}, so `#{name}` would have to be allocated -- and a " \
2374
+ "function's body is called once per cell, which would be one " \
2375
+ "allocation per cell. Make the array in the kernel that calls " \
2376
+ "this and pass it in, as a pointer parameter",
2377
+ location)
2378
+ end
2379
+
2380
+ # A shadow byte a cell where the kernel carries masks: it is on the
2381
+ # same stack as the cells, so it is counted with them.
2382
+ def local_array_bytes (storage, shape)
2383
+ cells = shape.inject(1) { |product, extent| product * extent }
2384
+ (LOCAL_ARRAY_STORAGE_BYTES.fetch(storage) + (@masked ? 1 : 0)) * cells
2385
+ end
2386
+
2387
+ # How the refusal accounts for a size the block did not write out.
2388
+ def local_array_bytes_phrase (storage, shape)
2389
+ cells = shape.inject(1) { |product, extent| product * extent }
2390
+ values = LOCAL_ARRAY_STORAGE_BYTES.fetch(storage) * cells
2391
+ return "#{values} bytes of #{storage}" unless @masked
2392
+ "#{values + cells} bytes -- #{values} of #{storage} and #{cells} of " \
2393
+ "the mask beside it, a byte a cell, since this kernel carries masks"
2394
+ end
2395
+
2396
+ # A name is in sight where some scope still standing holds it. The
2397
+ # scopes are pushed and popped with the inner loops' blocks, so a name
2398
+ # an inner loop introduced is out of sight after it -- which is what
2399
+ # Ruby says about it too.
2400
+ def local_in_sight? (name)
2401
+ @scopes.any? { |scope| scope.include?(name) }
2402
+ end
2403
+
2404
+ # The name of the local array this receiver is, or nil.
2405
+ #
2406
+ # A name the body did make an array under, but in a block that has since
2407
+ # closed, is neither: it says so rather than falling through to be
2408
+ # refused for not being a captured array, which is not what happened.
2409
+ def local_array_name (node)
2410
+ name = case node
2411
+ when Prism::LocalVariableReadNode
2412
+ node.name
2413
+ when Prism::CallNode
2414
+ node.name if node.receiver.nil? && node.arguments.nil? &&
2415
+ node.block.nil?
2416
+ end
2417
+ return nil unless name && @local_array_names.include?(name)
2418
+ unless local_in_sight?(name)
2419
+ raise Unsupported.new(
2420
+ "`#{name}` belongs to the loop block it was assigned in, and is " \
2421
+ "not there after it; make the array before the loop",
2422
+ node.location)
2423
+ end
2424
+ name
2425
+ end
2426
+
2427
+ # `[storage, shape]` for the declaration in sight under this name.
2428
+ def local_array_in_sight (name)
2429
+ @local_arrays.fetch(name)
2430
+ end
2431
+
2432
+ # A local array's subscripts.
2433
+ #
2434
+ # Not `read_subscripts`. That one answers for the operand tables: it
2435
+ # refuses an index where the block takes none, records the array's rank
2436
+ # in a table shared with every other array, and hands a window its
2437
+ # offsets instead. A local array is in none of that -- its rank is its
2438
+ # shape's, and its subscripts are written wherever the block stands.
2439
+ # What the two share is only the two ways a position is checked.
2440
+ def local_array_subscripts (name, shape, arguments, location)
2441
+ unless arguments.size == shape.size
2442
+ # A block may give one of its own arrays the name of the window it
2443
+ # was handed. Ruby reads that as rebinding the parameter, and so
2444
+ # does this -- but then the window's own spelling stops working, and
2445
+ # the axis count alone is a poor account of why.
2446
+ if @windows.include?(name)
2447
+ raise Unsupported.new(
2448
+ "`#{name}` is the window this block was given, and the block " \
2449
+ "also makes an array of its own under that name -- which " \
2450
+ "rebinds it, here as in Ruby, so from that line on `#{name}` " \
2451
+ "is the array and the window has no name left. " \
2452
+ "`#{name}[#{Array.new(rank, 0).join(', ')}]` is the window's " \
2453
+ "spelling and wants #{rank} #{rank == 1 ? 'offset' : 'offsets'}; " \
2454
+ "give the array a name of its own",
2455
+ location)
2456
+ end
2457
+ raise Unsupported.new(
2458
+ "`#{name}` has #{shape.size == 1 ? 'one axis' : "#{shape.size} axes"}, " \
2459
+ "so it takes #{shape.size == 1 ? 'one subscript' : "#{shape.size} subscripts"}, " \
2460
+ "and #{arguments.size} #{arguments.size == 1 ? 'was' : 'were'} written",
2461
+ location)
2462
+ end
2463
+ arguments.each_with_index.map { |argument, axis|
2464
+ local_array_subscript(name, shape, axis, argument, location)
2465
+ }
2466
+ end
2467
+
2468
+ # How a message names the thing that is out of range.
2469
+ #
2470
+ # With one axis the whole subscript is the thing, and quoting it back is
2471
+ # exactly right. With more than one it is not: the block wrote
2472
+ # `m[r, c]`, so quoting `m[c]` would be a spelling nobody typed, and an
2473
+ # extent on its own ("a local array of 4 cells") is a poor account of a
2474
+ # 3 x 4 one. So the axis is named instead, and the shape with it.
2475
+ def local_array_subject (name, shape, axis, argument)
2476
+ if shape.size == 1
2477
+ "`#{name}[#{argument.slice}]`"
2478
+ else
2479
+ "the subscript `#{argument.slice}` on axis #{axis} of `#{name}`, " \
2480
+ "which is #{shape.join(' x ')},"
2481
+ end
2482
+ end
2483
+
2484
+ # What the cells of the axis in question are, said whole rather than as
2485
+ # a fragment to glue on: the two ranks want different sentences and
2486
+ # composing them produced "is outside and that axis has 4 cells".
2487
+ def local_array_cells_phrase (shape, axis)
2488
+ extent = shape[axis]
2489
+ unless extent.is_a?(Integer)
2490
+ length = "a length of `#{extent}`, which the kernel works out"
2491
+ return shape.size == 1 ? "a local array of #{length}"
2492
+ : "that axis's #{length}"
2493
+ end
2494
+ cells = "#{extent} #{extent == 1 ? 'cell' : 'cells'}"
2495
+ run = extent == 1 ? "only cell is 0" : "cells are 0 to #{extent - 1}"
2496
+ shape.size == 1 ? "a local array of #{cells}, whose #{run}"
2497
+ : "that axis's #{cells}, whose #{run}"
2498
+ end
2499
+
2500
+ # (a) An index whose loop states its range in literals reaches a set of
2501
+ # positions that is known now, so it is checked now and the C carries no
2502
+ # test. (b) Anything else is checked where the cell is reached.
2503
+ #
2504
+ # A literal position is (a) as well, its range being itself.
2505
+ def local_array_subscript (name, shape, axis, argument, location)
2506
+ extent = shape[axis]
2507
+ # An axis whose length the kernel works out has nothing for (a) to
2508
+ # compare against: a literal position included, since 3 is inside an
2509
+ # array of 4 cells and outside one of 2. So every position on such
2510
+ # an axis is checked where the cell is reached.
2511
+ return [nil, build(argument)] unless extent.is_a?(Integer)
2512
+ if (index = index_name(argument)) && index_in_scope?(index)
2513
+ settled = checked_index_subscript(name, shape, axis, index, 0,
2514
+ argument, location)
2515
+ return settled if settled
2516
+ elsif argument.is_a?(Prism::CallNode) &&
2517
+ [:+, :-].include?(argument.name) &&
2518
+ (receiver = index_name(argument.receiver)) &&
2519
+ index_in_scope?(receiver)
2520
+ arguments = argument.arguments ? argument.arguments.arguments : []
2521
+ if arguments.size == 1 && (offset = literal_integer(arguments.first))
2522
+ offset = -offset if argument.name == :-
2523
+ settled = checked_index_subscript(name, shape, axis, receiver,
2524
+ offset, argument, location)
2525
+ return settled if settled
2526
+ end
2527
+ end
2528
+ if (position = literal_integer(argument))
2529
+ unless (0...extent).cover?(position)
2530
+ raise Unsupported.new(
2531
+ "#{local_array_subject(name, shape, axis, argument)} is " \
2532
+ "outside #{local_array_cells_phrase(shape, axis)}" +
2533
+ (position.negative? ?
2534
+ ". A subscript here counts from the start, so CArray's " \
2535
+ "`#{name}[-1]` for the last cell is written " \
2536
+ "`#{name}[#{extent - 1}]`" : ""),
2537
+ location)
2538
+ end
2539
+ return [nil, IntegerLiteral.new(position, location)]
2540
+ end
2541
+ [nil, build(argument)]
2542
+ end
2543
+
2544
+ # The subscript settled as the block is read, or nil where it cannot be.
2545
+ #
2546
+ # Nil is not "allow it": it sends the axis to the check at the access,
2547
+ # which is where every position this compiler cannot settle goes. The
2548
+ # two kinds it cannot settle are a loop whose bound is a captured
2549
+ # integer and one whose range is written over another index -- in both
2550
+ # the reach is not knowable until the kernel runs. Returning the index
2551
+ # unchecked would leave a write past the end of a stack array with
2552
+ # nothing at all looking at it.
2553
+ def checked_index_subscript (name, shape, axis, index, offset, argument,
2554
+ location)
2555
+ identifier = index_identifier(index)
2556
+ return nil unless index_reach(identifier)
2557
+ verify_index_reach(name, shape, axis, identifier, offset, argument,
2558
+ location)
2559
+ [identifier, offset]
2560
+ end
2561
+
2562
+ # The lowest and highest position an index takes, where its loop says so
2563
+ # in literals; nil where it does not, or where the loop runs no passes
2564
+ # at all and so reaches nothing.
2565
+ #
2566
+ # Computed rather than enumerated: `(0...10_000_000).each` states its
2567
+ # range in literals too.
2568
+ def index_reach (identifier)
2569
+ range = @inner_ranges[identifier]
2570
+ return nil unless range
2571
+ from, to, step = range
2572
+ return nil unless from.is_a?(IntegerLiteral) && to.is_a?(IntegerLiteral)
2573
+ first = from.value
2574
+ last_excluded = to.value
2575
+ if step.positive?
2576
+ return nil unless first < last_excluded
2577
+ [first, first + ((last_excluded - 1 - first) / step) * step]
2578
+ else
2579
+ return nil unless first > last_excluded
2580
+ stride = -step
2581
+ [first - ((first - (last_excluded + 1)) / stride) * stride, first]
2582
+ end
2583
+ end
2584
+
2585
+ # This looks at the loop's range and not at what stands around the
2586
+ # access, which is the character the captured arrays' own bounds check
2587
+ # already has: an `if` around the line does not make the reach smaller,
2588
+ # because the reach is the loop's. Narrowing the loop's range is what
2589
+ # makes it smaller.
2590
+ def verify_index_reach (name, shape, axis, identifier, offset, argument,
2591
+ location)
2592
+ extent = shape[axis]
2593
+ reach = index_reach(identifier)
2594
+ return unless reach
2595
+ low = reach.first + offset
2596
+ high = reach.last + offset
2597
+ return if low >= 0 && high < extent
2598
+ outside = low.negative? ? low : high
2599
+ spelled = @index_sources.fetch(identifier, identifier)
2600
+ raise Unsupported.new(
2601
+ "#{local_array_subject(name, shape, axis, argument)} reaches cell " \
2602
+ "#{outside} of #{local_array_cells_phrase(shape, axis)}: " \
2603
+ "`#{spelled}` runs #{reach.first} to #{reach.last} here. Narrow " \
2604
+ "the loop's range, or give `#{name}` more cells -- an `if` around " \
2605
+ "the line does not narrow the reach, which is the loop's",
2606
+ location)
2607
+ end
2608
+
2609
+ # In the whole-array spelling every name in the block is a cell, so an
2610
+ # assignment to a name that is an array outside writes that array's
2611
+ # cell -- the one the loop is on, the same cell every read in the block
2612
+ # is at. A name that is not an array is a local, as it is anywhere
2613
+ # else.
2614
+ #
2615
+ # `depth` is Prism's: how many scopes out the variable is. Counted from
2616
+ # the statement, it says which of the scopes here holds the local -- and
2617
+ # one past the kernel's block is a name of the method around it, which
2618
+ # the kernel holds as its own, as it always has.
2619
+ def assign_local (name, expression, location, depth = 0)
2620
+ if @whole_array && @array_names.include?(name)
2621
+ return whole_array_write(name, expression, location)
2622
+ end
2623
+ # An index is the loop's, not the block's. Ruby reads `i = 2` as
2624
+ # rebinding the parameter and leaves the loop alone; the C would
2625
+ # assign to the counter, so the loop would walk somewhere else -- to
2626
+ # cells outside the array, given a value outside its extent. The two
2627
+ # do not mean the same thing, so this is not compiled.
2628
+ if index_in_scope?(name)
2629
+ raise Unsupported.new(
2630
+ "`#{name}` is a loop index, and assigning to it here would " \
2631
+ "move the loop rather than the value: in Ruby the same line " \
2632
+ "rebinds the parameter and the loop runs on. Use a local of " \
2633
+ "another name",
2634
+ location)
2635
+ end
2636
+ if @local_array_names.include?(name)
2637
+ if local_in_sight?(name)
2638
+ raise Unsupported.new(
2639
+ "`#{name}` is a local array here, and cannot hold a number; the " \
2640
+ "two cannot be one C variable, so give the number a name of its " \
2641
+ "own",
2642
+ location)
2643
+ end
2644
+ # Out of sight, the array belonged to a block that has closed, and
2645
+ # this line introduces a variable that merely shares its name. The
2646
+ # record goes, so a read below is read as the number it now is.
2647
+ # The array itself stays counted: its C declaration is still in that
2648
+ # block, and still on that block's stack.
2649
+ @local_array_names.delete(name)
2650
+ @local_arrays.delete(name)
2651
+ end
2652
+ @local_names << name unless @local_names.include?(name)
2653
+ scope = [@scopes.size - 1 - depth, 0].max
2654
+ @scopes[scope] << name unless @scopes[scope].include?(name)
2655
+ Assignment.new(name, expression, location, scope)
2656
+ end
2657
+
2658
+ # What a name on the left of a parallel assignment may not be, said the
2659
+ # way it was written. A cell may: `a[i], a[j] = a[j], a[i]` is the swap
2660
+ # a sort is written with, and each half of it is the write `a[i] = ...`
2661
+ # already was.
2662
+ TARGET_DESCRIPTIONS = {
2663
+ Prism::MultiTargetNode => "a nested target",
2664
+ Prism::SplatNode => "a splat",
2665
+ Prism::ImplicitRestNode => "a bare comma",
2666
+ Prism::InstanceVariableTargetNode => "an instance variable",
2667
+ Prism::GlobalVariableTargetNode => "a global variable",
2668
+ Prism::ClassVariableTargetNode => "a class variable",
2669
+ Prism::ConstantTargetNode => "a constant",
2670
+ Prism::ConstantPathTargetNode => "a constant",
2671
+ Prism::CallTargetNode => "an attribute",
2672
+ }.freeze
2673
+
2674
+ TARGET_HINTS = {
2675
+ Prism::MultiTargetNode => "it unpacks one value into several, and " \
2676
+ "nothing in a kernel holds several; write " \
2677
+ "the names out flat",
2678
+ Prism::SplatNode => "it takes however many values are left " \
2679
+ "over, and how many there are is the one " \
2680
+ "thing a C variable cannot be",
2681
+ Prism::ImplicitRestNode => "it drops the rest of the values, and how " \
2682
+ "many that is has to be written out here",
2683
+ }.freeze
2684
+
2685
+ # `prev, cur = cur, prev + cur`, and the swap it is the general case of.
2686
+ #
2687
+ # Ruby settles every value on the right before it writes any of them,
2688
+ # and so does this: each value is assigned to a name of its own first,
2689
+ # and only then are the writes made. The other way round, the line
2690
+ # above would write `prev` and then read the new one back while working
2691
+ # out `cur` -- which is the one thing the spelling exists to prevent,
2692
+ # and why `jit_for` wanted it. A recurrence advances by a parallel
2693
+ # assignment in Ruby; until now it had to be unpicked by hand here, and
2694
+ # the temporary a reader had to introduce is where the bug lives.
2695
+ #
2696
+ # The names are written even where nothing on the right reads what the
2697
+ # left writes. A rule that dropped them sometimes would be one a reader
2698
+ # of the generated C had to know before they could tell what a line
2699
+ # meant, and a C compiler drops a name assigned once and read once
2700
+ # without being asked.
2701
+ def build_parallel_assignment (node)
2702
+ targets = parallel_targets(node)
2703
+ values = parallel_values(node, targets)
2704
+ temporaries = values.map { |value|
2705
+ assign_local(parallel_value_name, build(value), value.location)
2706
+ }
2707
+ writes = targets.each_with_index.map { |target, k|
2708
+ value = build_name_read(temporaries[k].name, target.location)
2709
+ if target.is_a?(Prism::LocalVariableTargetNode)
2710
+ assign_local(target.name, value, target.location, target.depth)
2711
+ else
2712
+ indices = target.arguments ? target.arguments.arguments : []
2713
+ element_write(target, indices, value, target.location)
2714
+ end
2715
+ }
2716
+ ParallelAssignment.new(temporaries + writes, node.location)
2717
+ end
2718
+
2719
+ def parallel_targets (node)
2720
+ targets = node.lefts + (node.rest ? [node.rest] : []) + node.rights
2721
+ targets.each do |target|
2722
+ next if target.is_a?(Prism::LocalVariableTargetNode) ||
2723
+ target.is_a?(Prism::IndexTargetNode)
2724
+ described = TARGET_DESCRIPTIONS[target.class]
2725
+ hint = TARGET_HINTS[target.class]
2726
+ raise Unsupported.new(
2727
+ "a parallel assignment writes names and cells here -- " \
2728
+ "`a, b = b, a`, `a[i], a[j] = a[j], a[i]` -- and this one writes " \
2729
+ "#{described || node_name(target)}#{hint ? " -- #{hint}" : ""}",
2730
+ target.location)
2731
+ end
2732
+ targets
2733
+ end
2734
+
2735
+ # The values, told apart from the one value Ruby would spread.
2736
+ #
2737
+ # `a, b = b, a` writes its values out, and Prism hands them over as an
2738
+ # array with no brackets around it. Brackets are Ruby's other spelling
2739
+ # -- `a, b = [b, a]` -- and it means something else: one value, taken
2740
+ # apart across the names. Nothing in a kernel is a value that can be
2741
+ # taken apart, so the two are told apart here rather than quietly read
2742
+ # as one.
2743
+ def parallel_values (node, targets)
2744
+ value = node.value
2745
+ unless value.is_a?(Prism::ArrayNode) && value.opening_loc.nil?
2746
+ raise Unsupported.new(
2747
+ "a parallel assignment takes one value per name, written out on " \
2748
+ "the right, as in `a, b = b, a`. Ruby takes a single value apart " \
2749
+ "across the names, and a kernel computes numbers -- there is " \
2750
+ "nothing here to take apart",
2751
+ value.location)
2752
+ end
2753
+ if (splat = value.elements.find { |element|
2754
+ element.is_a?(Prism::SplatNode) })
2755
+ raise Unsupported.new(
2756
+ "`*` spreads a value across the names, and how many it comes to " \
2757
+ "is the one thing a C variable cannot be; write the values out",
2758
+ splat.location)
2759
+ end
2760
+ unless value.elements.size == targets.size
2761
+ # Ruby has an answer for either way round, and neither is one a
2762
+ # kernel can keep: the names it runs out of values for are nil, and
2763
+ # a value it runs out of names for is dropped.
2764
+ raise Unsupported.new(
2765
+ "this writes #{count_of(targets.size, "name")} and has " \
2766
+ "#{count_of(value.elements.size, "value")} for them. Ruby " \
2767
+ "#{value.elements.size < targets.size ?
2768
+ "leaves the rest of the names nil, which is not a number a " \
2769
+ "local or a cell can hold" :
2770
+ "drops the values it has no name for, and a value computed " \
2771
+ "and dropped is a line that does nothing"}",
2772
+ node.location)
2773
+ end
2774
+ value.elements
2775
+ end
2776
+
2777
+ def count_of (n, noun)
2778
+ "#{n} #{n == 1 ? noun : noun + "s"}"
2779
+ end
2780
+
2781
+ # A name for a value on the right that no name in the block can be, and
2782
+ # that no other statement uses.
2783
+ #
2784
+ # The block's own text settles the first half: a local, a capture, an
2785
+ # index and an array all appear in it, so a name that appears nowhere
2786
+ # in it is none of them. An underscore is added until that holds,
2787
+ # which keeps the same block reading the same way every time -- it has
2788
+ # to, the generated C being part of the cache key.
2789
+ #
2790
+ # The second half is the counter. Two parallel assignments sharing a
2791
+ # name would be two statements sharing a C variable, and a variable
2792
+ # live at the head of a loop is one the loop carries: a body that
2793
+ # swapped Integers before the loop and Floats inside it was refused for
2794
+ # changing the type of `value1` on the way round -- a name its author
2795
+ # never wrote, about a value that is dead by the end of the line it is
2796
+ # on. A name of its own per statement cannot be carried anywhere.
2797
+ def parallel_value_name
2798
+ @parallel_value_prefix ||= begin
2799
+ text = (@source || @node&.slice).to_s
2800
+ prefix = "value"
2801
+ prefix = "_" + prefix while text.include?(prefix)
2802
+ prefix
2803
+ end
2804
+ @parallel_value_count = @parallel_value_count.to_i + 1
2805
+ :"#{@parallel_value_prefix}#{@parallel_value_count}"
2806
+ end
2807
+
2808
+ # `x += e` is `x = x + e`, and is read as exactly that: the name is
2809
+ # read where Ruby reads it -- before the right-hand side, and by the
2810
+ # same rule, so a name the block has not assigned yet says so rather
2811
+ # than starting from whatever C left in the variable.
2812
+ def build_operator_assignment (node)
2813
+ operator = assignment_operator(node)
2814
+ read = build_name_read(node.name, node.location)
2815
+ expression = combined(operator, read, build(node.value), node.location)
2816
+ assign_local(node.name, expression, node.location, node.depth)
2817
+ end
2818
+
2819
+ # `a[i] += e` is `a[i] = a[i] + e`, cell for cell -- including under a
2820
+ # mask, where the read is a read like any other and carries what it
2821
+ # finds into what is written.
2822
+ def build_operator_element_write (node)
2823
+ operator = assignment_operator(node)
2824
+ indices = node.arguments ? node.arguments.arguments : []
2825
+ read = build_element_read(node, indices)
2826
+ expression = combined(operator, read, build(node.value), node.location)
2827
+ element_write(node, indices, expression, node.location)
2828
+ end
2829
+
2830
+ # The operators an assignment may carry are the ones an expression may:
2831
+ # `||=` and `&&=` are refused where they are read, being about nil and
2832
+ # false rather than about arithmetic.
2833
+ def assignment_operator (node)
2834
+ operator = node.binary_operator
2835
+ unless (ARITHMETIC_OPERATORS + BIT_OPERATORS + [:**]).include?(operator)
2836
+ raise Unsupported.new(
2837
+ "`#{operator}=` is not one of the operators a kernel computes " \
2838
+ "with", node.location)
2839
+ end
2840
+ operator
2841
+ end
2842
+
2843
+ def combined (operator, left, right, location)
2844
+ return power_node(left, right, location) if operator == :**
2845
+ BinaryOperation.new(operator, left, right, location)
2846
+ end
2847
+
1195
2848
  def build_element_write (node)
1196
- return build_inner_loop(node) if node.name == :each || node.name == :times
2849
+ if node.name == :each || node.name == :times || node.name == :step
2850
+ return build_inner_loop(node)
2851
+ end
1197
2852
  unless node.name == :[]=
1198
2853
  # An expression standing alone in this spelling is a computation
1199
2854
  # nobody can see: there is no index to have written it against, and
@@ -1205,54 +2860,98 @@ class CArray
1205
2860
  "`CArray.jit_map`",
1206
2861
  node.location)
1207
2862
  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)
2863
+ if INNER_LOOP_SPELLINGS.include?(node.name)
2864
+ raise Unsupported.new(
2865
+ "an inner loop is written `(a...b).each`, `n.times` or " \
2866
+ "`a.step(b, s)`, which is the spelling an extent takes a " \
2867
+ "stride and a direction in -- `#{node.name}` is not one of " \
2868
+ "them",
2869
+ node.location)
2870
+ end
2871
+ refuse_as_a_statement(node, "a call to `#{node.name}`")
1212
2872
  end
1213
- if (pointer = pointer_subscript(node, write: true))
2873
+ arguments = node.arguments ? node.arguments.arguments : []
2874
+ element_write(node, arguments[0..-2], nil, node.location,
2875
+ value_node: arguments.last)
2876
+ end
2877
+
2878
+ # The write itself, with the indices and the value already told apart:
2879
+ # `a[i] = e` puts the value last among the arguments, while `a[i] += e`
2880
+ # keeps it somewhere else, and everything from here down is the same
2881
+ # for both. `value_node` is passed where the value is still a Prism
2882
+ # node, since `a[i] = UNDEF` is a mark rather than a value and is read
2883
+ # off the node.
2884
+ def element_write (node, indices, expression, location, value_node: nil)
2885
+ if (pointer = pointer_subscript(node, write: true, indices: indices))
1214
2886
  name, index = pointer
1215
- arguments = node.arguments.arguments
1216
- return PointerWrite.new(name, index, build(arguments.last),
1217
- node.location)
2887
+ return PointerWrite.new(name, index,
2888
+ expression || build(value_node), location)
2889
+ end
2890
+ refuse_a_constructor_in_an_expression(node.receiver)
2891
+ if (name = local_array_name(node.receiver))
2892
+ storage, shape = local_array_in_sight(name)
2893
+ if value_node && undef_constant?(value_node)
2894
+ # A kernel that writes UNDEF anywhere carries masks by that
2895
+ # alone, so this is a mark on the shadow beside the cell. In a
2896
+ # function body there is no shadow: a function is handed numbers
2897
+ # and pointers, and a mask reaches neither.
2898
+ unless @masked
2899
+ raise Unsupported.new(
2900
+ "`#{name}` is a local array in a compiled function's body, " \
2901
+ "and nothing there carries masks -- a function is handed " \
2902
+ "numbers and pointers, and a mask travels in neither. Keep " \
2903
+ "a value that stands for missing, or mark the cell in the " \
2904
+ "kernel that called this",
2905
+ location)
2906
+ end
2907
+ @uses_undef = true
2908
+ return LocalArrayMaskWrite.new(
2909
+ name, storage, shape,
2910
+ local_array_subscripts(name, shape, indices, location), location)
2911
+ end
2912
+ return LocalArrayWrite.new(
2913
+ name, storage, shape,
2914
+ local_array_subscripts(name, shape, indices, location),
2915
+ expression || build(value_node), location)
1218
2916
  end
1219
- array = array_name(node.receiver, node.location)
1220
- arguments = node.arguments ? node.arguments.arguments : []
2917
+ array = array_name(node.receiver, location)
1221
2918
  # `out[] = ...` was how a block that had to run as Ruby said "the
1222
2919
  # whole array", `[]=` being the only spelling Ruby has for it. The
1223
2920
  # block is read rather than run, and every name in it is a cell, so
1224
2921
  # the assignment is Ruby's own: `out = ...`.
1225
- if @whole_array && arguments.size == 1
2922
+ if @whole_array && indices.empty?
1226
2923
  raise Unsupported.new(
1227
2924
  "`#{array}[] = ...` writes the cell the loop is on, which is what " \
1228
2925
  "`#{array} = ...` says; the block is read rather than run, so the " \
1229
2926
  "assignment is an ordinary one",
1230
- node.location)
2927
+ location)
1231
2928
  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.
2929
+ subscripts = read_subscripts(array, indices, location)
2930
+ # A kernel writes a cell one of its own indices reaches -- an outer
2931
+ # one or an inner one -- or one it works out, which is a scatter and
2932
+ # is checked as it is reached rather than in advance. What it may not
2933
+ # do is write through a name that addresses no cell at all.
1237
2934
  #
1238
2935
  # In a contraction the left-hand side names which indices are free and
1239
2936
  # which are summed over, so it is checked there instead.
1240
2937
  unless @contract || walking_subscripts?(subscripts) ||
1241
2938
  computed_subscripts?(subscripts)
1242
2939
  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)
2940
+ "a kernel writes through the indices it is running, so " \
2941
+ "`#{array}[...]` on the left of `=` is addressed by " \
2942
+ "#{available_indices}, by a position fixed before the loop runs " \
2943
+ "-- or by a value the kernel works out, which is a scatter",
2944
+ location)
1248
2945
  end
1249
2946
  record_subscripts(array, subscripts)
2947
+ record_write_subscripts(array, subscripts)
1250
2948
  @written_arrays << array unless @written_arrays.include?(array)
1251
- if undef_constant?(arguments.last)
2949
+ if value_node && undef_constant?(value_node)
1252
2950
  @uses_undef = true
1253
- return MaskWrite.new(array, node.location)
2951
+ return MaskWrite.new(array, location)
1254
2952
  end
1255
- ElementWrite.new(array, build(arguments.last), node.location, subscripts)
2953
+ ElementWrite.new(array, expression || build(value_node), location,
2954
+ subscripts)
1256
2955
  end
1257
2956
 
1258
2957
  def build (node)
@@ -1301,8 +3000,19 @@ class CArray
1301
3000
  # closes over nothing.
1302
3001
  build_name_read(node.name, node.location)
1303
3002
  else
1304
- raise Unsupported.new("unsupported expression #{node_name(node)}",
1305
- node.location)
3003
+ # Named as it was written where there is a name for it, and with
3004
+ # the same advice a statement in that position gets: `unless` in
3005
+ # value position was "unsupported expression Unless", which is
3006
+ # this compiler's reading of it rather than the reader's.
3007
+ described = STATEMENT_DESCRIPTIONS[node.class]
3008
+ hint = STATEMENT_HINTS[node.class]
3009
+ raise Unsupported.new(
3010
+ described ?
3011
+ "a kernel computes its value from arithmetic, comparisons, " \
3012
+ "cells and calls, and this one is #{described}" \
3013
+ "#{hint ? " -- #{hint}" : ""}" :
3014
+ "unsupported expression #{node_name(node)}",
3015
+ node.location)
1306
3016
  end
1307
3017
  end
1308
3018
 
@@ -1314,8 +3024,31 @@ class CArray
1314
3024
  def build_name_read (name, location)
1315
3025
  axis = @outer_names.index(name)
1316
3026
  return IndexVariable.new(name, axis, location) if axis
1317
- return IndexVariable.new(name, nil, location) if @inner_names.include?(name)
3027
+ if @inner_names.include?(name)
3028
+ return IndexVariable.new(@inner_aliases.fetch(name, name), nil, location)
3029
+ end
3030
+ if @local_array_names.include?(name)
3031
+ unless local_in_sight?(name)
3032
+ raise Unsupported.new(
3033
+ "`#{name}` belongs to the loop block it was assigned in, and is " \
3034
+ "not there after it; make the array before the loop",
3035
+ location)
3036
+ end
3037
+ raise Unsupported.new(
3038
+ "`#{name}` is a local array; index it, as in `#{name}[0]`. An " \
3039
+ "array has no value of its own in a kernel -- what the name " \
3040
+ "stands for is its cells",
3041
+ location)
3042
+ end
1318
3043
  if @local_names.include?(name)
3044
+ # A local of an inner loop's block that has closed is not in sight:
3045
+ # Ruby reads the same name after the block as a method call.
3046
+ unless @scopes.any? { |scope| scope.include?(name) }
3047
+ raise Unsupported.new(
3048
+ "`#{name}` belongs to the loop block it was assigned in, and is " \
3049
+ "not there after it; give it a value before the loop",
3050
+ location)
3051
+ end
1319
3052
  return LocalRead.new(name, location)
1320
3053
  end
1321
3054
  if @cell_names.include?(name)
@@ -1347,6 +3080,42 @@ class CArray
1347
3080
  CaptureRead.new(name, location)
1348
3081
  end
1349
3082
 
3083
+ # What `if`, `while` and `?:` read. A pointer parameter named bare
3084
+ # there is Ruby's truthiness -- nil is false and an array is true -- so
3085
+ # `if grad` asks whether an address came, which is what NLopt's
3086
+ # derivative-free methods leave out. Only where a condition is read:
3087
+ # anywhere else the bare name would be a value, and a pointer is not
3088
+ # one. `!`, `&&`, `||` and parentheses are looked through, since each
3089
+ # of those reads its operands as conditions too.
3090
+ def build_condition (node)
3091
+ case node
3092
+ when Prism::ParenthesesNode
3093
+ inner = node.body ? node.body.body : []
3094
+ return build_condition(inner.first) if inner.size == 1
3095
+ when Prism::AndNode
3096
+ return LogicalOperation.new(:"&&", build_condition(node.left),
3097
+ build_condition(node.right), node.location)
3098
+ when Prism::OrNode
3099
+ return LogicalOperation.new(:"||", build_condition(node.left),
3100
+ build_condition(node.right), node.location)
3101
+ when Prism::CallNode
3102
+ if node.name == :! && node.receiver && node.arguments.nil?
3103
+ return LogicalNot.new(build_condition(node.receiver), node.location)
3104
+ end
3105
+ end
3106
+ pointer_test(node, false, node.location) || build(node)
3107
+ end
3108
+
3109
+ # A PointerTest when `receiver` names a pointer parameter, nil when it
3110
+ # does not. Asking is not reaching through, so a `void *` may be asked
3111
+ # too: whether a slot was filled is a question about the slot.
3112
+ def pointer_test (receiver, null, location)
3113
+ name = captured_name(receiver)
3114
+ return nil unless name && @pointers.key?(name)
3115
+ return nil if @local_names.include?(name)
3116
+ PointerTest.new(name, null, location)
3117
+ end
3118
+
1350
3119
  def build_conditional (node)
1351
3120
  unless node.subsequent
1352
3121
  raise Unsupported.new("`if` without `else` has no value for every cell",
@@ -1355,7 +3124,7 @@ class CArray
1355
3124
  unless node.subsequent.is_a?(Prism::ElseNode)
1356
3125
  raise Unsupported.new("`elsif` is not supported", node.location)
1357
3126
  end
1358
- Conditional.new(build(node.predicate),
3127
+ Conditional.new(build_condition(node.predicate),
1359
3128
  build_single(node.statements, node.location, "if"),
1360
3129
  build_single(node.subsequent.statements, node.location, "else"),
1361
3130
  node.location)
@@ -1380,9 +3149,29 @@ class CArray
1380
3149
  if node.receiver.nil? && node.arguments.nil? && node.block.nil?
1381
3150
  return build_name_read(node.name, node.location)
1382
3151
  end
3152
+ if (intrinsic = build_intrinsic_call(node))
3153
+ return intrinsic
3154
+ end
3155
+ refuse_a_constructor_in_an_expression(node)
3156
+ # A method on a local array. The operators are left alone so that
3157
+ # `w + 1` is refused for reading the array bare, which is what it
3158
+ # does; a named method is refused here, where the name can be said.
3159
+ if (holder = local_array_name(node.receiver)) &&
3160
+ !LOCAL_ARRAY_SUBSCRIPTS.include?(node.name) &&
3161
+ !(ARITHMETIC_OPERATORS + COMPARISON_OPERATORS +
3162
+ BIT_OPERATORS + [:**]).include?(node.name)
3163
+ raise Unsupported.new(
3164
+ "`#{holder}` is a local array, and `#{node.name}` is a method " \
3165
+ "CArray answers outside a kernel; inside one an array is reached " \
3166
+ "at a subscript. Write the loop out over `#{holder}[k]`",
3167
+ node.location)
3168
+ end
1383
3169
  if (recursive = recursive_call(node))
1384
3170
  return recursive
1385
3171
  end
3172
+ if (draw = random_call(node))
3173
+ return draw
3174
+ end
1386
3175
  if (c_function = c_function_call(node))
1387
3176
  return c_function
1388
3177
  end
@@ -1421,6 +3210,42 @@ class CArray
1421
3210
  if node.name == :** && arguments.size == 1
1422
3211
  return build_power(node.receiver, arguments.first, node.location)
1423
3212
  end
3213
+ if node.name == :clamp
3214
+ unless arguments.size == 2
3215
+ raise Unsupported.new(
3216
+ "`clamp` takes the two bounds, as in `x.clamp(0.0, 1.0)`" +
3217
+ (arguments.size == 1 ? " -- the range form is not in the subset" : ""),
3218
+ node.location)
3219
+ end
3220
+ return Clamp.new(build(node.receiver), build(arguments.first),
3221
+ build(arguments.last), node.location)
3222
+ end
3223
+ if node.name == :nil? && arguments.empty? &&
3224
+ (test = pointer_test(node.receiver, true, node.location))
3225
+ return test
3226
+ end
3227
+ if [:==, :!=].include?(node.name) && arguments.size == 1
3228
+ # `grad == nil` and `nil == grad`, which Ruby answers the way
3229
+ # `grad.nil?` does and so may be written wherever that is.
3230
+ receiver, other = node.receiver, arguments.first
3231
+ receiver, other = other, receiver if receiver.is_a?(Prism::NilNode)
3232
+ if other.is_a?(Prism::NilNode) &&
3233
+ (test = pointer_test(receiver, node.name == :==, node.location))
3234
+ return test
3235
+ end
3236
+ end
3237
+ if arguments.empty? && NUMERIC_PREDICATES.include?(node.name)
3238
+ return NumericPredicate.new(node.name, build(node.receiver),
3239
+ node.location)
3240
+ end
3241
+ if arguments.empty? && node.name == :infinite?
3242
+ raise Unsupported.new(
3243
+ "`infinite?` answers nil, 1 or -1 in Ruby rather than true or " \
3244
+ "false, and a kernel has no nil to answer with; ask " \
3245
+ "`x.abs == Float::INFINITY`, or `x == Float::INFINITY` where " \
3246
+ "the sign is the question",
3247
+ node.location)
3248
+ end
1424
3249
  if arguments.empty?
1425
3250
  if (function = postfix_math(node.name))
1426
3251
  return MathCall.new(function, [build(node.receiver)], node.location)
@@ -1507,11 +3332,26 @@ class CArray
1507
3332
  "only a cell can be compared with UNDEF, as in `a[i] == UNDEF`",
1508
3333
  location)
1509
3334
  end
3335
+ arguments = receiver.arguments ? receiver.arguments.arguments : []
3336
+ if (made = local_array_name(receiver.receiver))
3337
+ storage, shape = local_array_in_sight(made)
3338
+ unless @masked
3339
+ raise Unsupported.new(
3340
+ "`#{made}` is a local array in a compiled function's body, " \
3341
+ "and nothing there carries masks -- a function is handed " \
3342
+ "numbers and pointers, and a mask travels in neither, so " \
3343
+ "there is no shadow beside these cells to ask about",
3344
+ location)
3345
+ end
3346
+ @uses_undef = true
3347
+ return LocalArrayMaskTest.new(
3348
+ made, storage, shape,
3349
+ local_array_subscripts(made, shape, arguments, location),
3350
+ negated, location)
3351
+ end
1510
3352
  array = array_name(receiver.receiver, location)
1511
3353
  array_for_test = array
1512
- subscripts = read_subscripts(array_for_test,
1513
- receiver.arguments ? receiver.arguments.arguments : [],
1514
- location)
3354
+ subscripts = read_subscripts(array_for_test, arguments, location)
1515
3355
  # Recorded like any other read, because the cell still has to exist
1516
3356
  # and still has to have been settled before it is asked about. What
1517
3357
  # it does not do is feed the value-mask propagation, which is decided
@@ -1525,7 +3365,7 @@ class CArray
1525
3365
  # does not. A pointer declared const is refused on the left rather than
1526
3366
  # silently written through: the declaration is a promise to the caller,
1527
3367
  # not decoration.
1528
- def pointer_subscript (node, write: false)
3368
+ def pointer_subscript (node, write: false, indices: nil)
1529
3369
  name = captured_name(node.receiver)
1530
3370
  return nil unless name && @pointers.key?(name)
1531
3371
  case @pointers.fetch(name)
@@ -1541,14 +3381,36 @@ class CArray
1541
3381
  "not write through it", node.location)
1542
3382
  end
1543
3383
  end
1544
- arguments = node.arguments ? node.arguments.arguments : []
1545
- arguments = arguments[0..-2] if write
3384
+ arguments = indices
3385
+ unless arguments
3386
+ arguments = node.arguments ? node.arguments.arguments : []
3387
+ arguments = arguments[0..-2] if write
3388
+ end
1546
3389
  unless arguments.size == 1
1547
3390
  raise Unsupported.new(
1548
3391
  "`#{name}` is a pointer, so it takes one index", node.location)
1549
3392
  end
1550
3393
  @pointer_names << name unless @pointer_names.include?(name)
1551
- [name, build(arguments.first)]
3394
+ index = build(arguments.first)
3395
+ refuse_undeclared_cell(name, index, node.location)
3396
+ [name, index]
3397
+ end
3398
+
3399
+ # A subscript on a pointer is not checked when the function runs: that
3400
+ # is C's bargain, and a computed one could only be checked there. A
3401
+ # literal needs no running to be read, though, and where the
3402
+ # declaration said how many cells there are, one outside them is a
3403
+ # write or a read past what the caller was held to -- the caller is
3404
+ # checked for at least that many and no more. C says nothing about
3405
+ # `v[7]` against `double v[2]`; this says it as the body is read.
3406
+ def refuse_undeclared_cell (name, index, location)
3407
+ length = @pointer_lengths[name]
3408
+ return unless length && index.is_a?(IntegerLiteral)
3409
+ return if index.value >= 0 && index.value < length
3410
+ raise Unsupported.new(
3411
+ "`#{name}[#{index.value}]` is outside the #{length} " \
3412
+ "#{length == 1 ? 'cell' : 'cells'} `#{name}` is declared with; " \
3413
+ "a caller is held to that many and no more", location)
1552
3414
  end
1553
3415
 
1554
3416
  # `fact(n - 1)` inside the body of `double fact(double)`, and
@@ -1561,16 +3423,19 @@ class CArray
1561
3423
  def recursive_call (node)
1562
3424
  return nil unless @recursion
1563
3425
  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.
3426
+ # `fact.call(n - 1)`, the spelling every other C function takes,
3427
+ # borrowed or written. A bare `fact(n - 1)` reads better as C and is
3428
+ # refused all the same, because a bare call is a spelling this
3429
+ # compiler has taken for itself: `sum(w)` and `sort(w)` are its own
3430
+ # functions, reached by no name the block wrote. A function of yours
3431
+ # is reached through the name that holds it -- which is also what
3432
+ # makes a recursion read like a call to anything else.
1569
3433
  if node.receiver.nil? && node.name == name && node.arguments
1570
3434
  raise Unsupported.new(
1571
3435
  "`#{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",
3436
+ "as `#{name}.call(...)` -- a bare `#{name}(...)` is how this " \
3437
+ "compiler spells its own functions, such as `sum(w)`, and a " \
3438
+ "function of yours is reached through the name that holds it",
1574
3439
  node.location)
1575
3440
  end
1576
3441
  return nil unless C_FUNCTION_CALL_NAMES.include?(node.name) &&
@@ -1590,6 +3455,111 @@ class CArray
1590
3455
  RecursiveCall.new(name, built, parameters, result_type, node.location)
1591
3456
  end
1592
3457
 
3458
+ # `int64_t s[4]`, the parameter the generator's own C takes. Held as a
3459
+ # parsed declaration rather than described here, so that what the state
3460
+ # array is checked against is the C the draw will actually be compiled
3461
+ # into -- the same check `poly.call(x, coef)` gets, from the same code.
3462
+ # Parsed on first use: the declaration parser is in c_function.rb,
3463
+ # which is required after this file.
3464
+ def self.random_state_parameter
3465
+ @random_state_parameter ||=
3466
+ CDeclaration.parse("double draw(int64_t state[4])").last.first
3467
+ end
3468
+
3469
+ # `r.rand` and `r.bits` -- a draw written as a method of the generator,
3470
+ # which is how Ruby writes one. `#rand` and `#bits` do these outside a
3471
+ # kernel, and the same names inside it mean the same two things.
3472
+ RANDOM_DRAW_METHODS =
3473
+ { :random => :random, :randomn => :randomn, :bits => :bits }.freeze
3474
+
3475
+ # `random(rng: r)` -- the same draw as `r.rand`, written as the array
3476
+ # language writes it. `a.random!(rng: r)` fills an array from a
3477
+ # generator, and this is that sentence about one cell, so a reader
3478
+ # moving between the two reads one word rather than matching two up.
3479
+ #
3480
+ # It is the only keyword argument in the subset and the only bare name
3481
+ # in it that Ruby does not have; both are paid for that.
3482
+ RANDOM_KEYWORD_NAMES =
3483
+ { :random => :random, :randomn => :randomn }.freeze
3484
+ RANDOM_DRAW_KEYWORD = "rng"
3485
+
3486
+ # Returns the node for a draw from a captured CArray::Rng, or nil when
3487
+ # this is not one.
3488
+ def random_call (node)
3489
+ if node.receiver.nil? && (kind = RANDOM_KEYWORD_NAMES[node.name])
3490
+ return build_random_draw(random_generator_name(node), kind, node)
3491
+ end
3492
+ name = captured_name(node.receiver)
3493
+ return nil unless name && @randoms.key?(name)
3494
+ kind = RANDOM_DRAW_METHODS[node.name]
3495
+ refuse_a_generator_call(name, node) unless kind
3496
+ unless (node.arguments ? node.arguments.arguments : []).empty?
3497
+ bounded = node.name == :random ?
3498
+ ". `CArray#random!` takes a range for a whole array; a kernel " \
3499
+ "has no bounded draw, so scale the one you get" : ""
3500
+ raise Unsupported.new(
3501
+ "`#{name}.#{node.name}` in a kernel is one draw and takes no " \
3502
+ "arguments#{bounded}",
3503
+ node.location)
3504
+ end
3505
+ build_random_draw(name, kind, node)
3506
+ end
3507
+
3508
+ def build_random_draw (name, kind, node)
3509
+ state = @randoms.fetch(name)
3510
+ @random_names << name unless @random_names.include?(name)
3511
+ # The state travels the way any array handed to a C function whole
3512
+ # does: by address, in the `data` buffer, copied back after the call.
3513
+ # That copy-back is what leaves the generator advanced.
3514
+ @address_arrays << state unless @address_arrays.include?(state)
3515
+ (@address_parameters[state] ||= []) << self.class.random_state_parameter
3516
+ RandomDraw.new(name, state, kind, node.location)
3517
+ end
3518
+
3519
+ # Everything else reached on a generator. There are three spellings
3520
+ # and no fourth, so the message lists them rather than saying only that
3521
+ # this one is not among them.
3522
+ def refuse_a_generator_call (name, node)
3523
+ raise Unsupported.new(
3524
+ "`#{name}` is a generator, and `#{name}.#{node.name}` is not a way " \
3525
+ "to draw from one. A kernel has `#{name}.random` for a double in " \
3526
+ "[0.0, 1.0), `#{name}.randomn` for a standard normal, " \
3527
+ "`#{name}.bits` for the raw word, and `random(rng: #{name})` or " \
3528
+ "`randomn(rng: #{name})` for the first two written as " \
3529
+ "`CArray#random!` writes them",
3530
+ node.location)
3531
+ end
3532
+
3533
+ # Which generator `random` was pointed at, or a refusal saying how to
3534
+ # point it at one. Nothing else is accepted in the parentheses: a
3535
+ # range, a bound, a data type are all things `CArray#random!` takes and
3536
+ # none of them has an answer for one cell.
3537
+ def random_generator_name (node)
3538
+ arguments = node.arguments ? node.arguments.arguments : []
3539
+ keywords = arguments.last if arguments.last.is_a?(Prism::KeywordHashNode)
3540
+ pairs = keywords ? keywords.elements : []
3541
+ unless arguments.size == 1 && keywords && pairs.size == 1 &&
3542
+ pairs.first.is_a?(Prism::AssocNode) &&
3543
+ pairs.first.key.is_a?(Prism::SymbolNode) &&
3544
+ pairs.first.key.unescaped == RANDOM_DRAW_KEYWORD
3545
+ raise Unsupported.new(
3546
+ "`#{node.name}` in a kernel draws one number from a generator " \
3547
+ "and takes `rng:` and nothing else -- `#{node.name}(rng: r)`, " \
3548
+ "where `r` is a `CArray::Rng` the block closed over",
3549
+ node.location)
3550
+ end
3551
+ name = captured_name(pairs.first.value)
3552
+ unless name && @randoms.key?(name)
3553
+ reached = name ? "`#{name}`" : "what was written there"
3554
+ raise Unsupported.new(
3555
+ "`rng:` takes a `CArray::Rng` the block closed over, and " \
3556
+ "#{reached} is not one. A Ruby `Random` cannot be reached from a " \
3557
+ "kernel at all; `CArray::Rng.new` makes one that can",
3558
+ pairs.first.value.location)
3559
+ end
3560
+ name
3561
+ end
3562
+
1593
3563
  # Returns the node for a call on a captured C function, or nil when this
1594
3564
  # is not one.
1595
3565
  def c_function_call (node)
@@ -1600,7 +3570,8 @@ class CArray
1600
3570
  arguments = node.arguments ? node.arguments.arguments : []
1601
3571
  unless arguments.size == c_function.arity
1602
3572
  raise Unsupported.new(
1603
- "`#{name}` is `#{c_function}`, so it takes #{c_function.arity} " \
3573
+ "`#{name}` is `#{c_function.declaration_as(name)}`, so it takes " \
3574
+ "#{c_function.arity} " \
1604
3575
  "#{c_function.arity == 1 ? 'argument' : 'arguments'}; " \
1605
3576
  "#{arguments.size} #{arguments.size == 1 ? 'was' : 'were'} given",
1606
3577
  node.location)
@@ -1618,6 +3589,16 @@ class CArray
1618
3589
  # name means a cell in one argument position and the whole array in
1619
3590
  # another -- `poly.call(x[i], coef)` says both.
1620
3591
  def build_c_function_argument (argument, parameter, c_function)
3592
+ # A local array decays to the pointer the callee wants, as it does in
3593
+ # C -- the name its declaration was written under is already the
3594
+ # address. What the declaration says about it is matched here, as the
3595
+ # block is read, because both halves are written in the block: the
3596
+ # element type is the constructor and the length is a literal. A
3597
+ # captured array is matched at the call instead (`address_buffer`),
3598
+ # neither being knowable until there is an array.
3599
+ if (name = local_array_name(argument))
3600
+ return build_local_array_address(name, argument, parameter, c_function)
3601
+ end
1621
3602
  # Inside a function, one of its own pointer parameters is already the
1622
3603
  # address the callee wants, and handing it on is what C does -- for a
1623
3604
  # `void *` slot as much as for a run of numbers, since neither is
@@ -1670,12 +3651,24 @@ class CArray
1670
3651
  ElementWrite.new(name, expression, location, subscripts)
1671
3652
  end
1672
3653
 
1673
- def build_element_read (node)
1674
- if (pointer = pointer_subscript(node))
3654
+ # `indices` is given where the node is not a plain `a[i]` -- `a[i] += e`
3655
+ # carries its indices and its value in different places, and the read
3656
+ # it stands for is the same read either way.
3657
+ def build_element_read (node, indices = nil)
3658
+ if (pointer = pointer_subscript(node, indices: indices))
1675
3659
  return PointerRead.new(pointer.first, pointer.last, node.location)
1676
3660
  end
3661
+ refuse_a_constructor_in_an_expression(node.receiver)
3662
+ if (name = local_array_name(node.receiver))
3663
+ storage, shape = local_array_in_sight(name)
3664
+ arguments = indices || (node.arguments ? node.arguments.arguments : [])
3665
+ return LocalArrayRead.new(
3666
+ name, storage, shape,
3667
+ local_array_subscripts(name, shape, arguments, node.location),
3668
+ node.location)
3669
+ end
1677
3670
  array = array_name(node.receiver, node.location)
1678
- arguments = node.arguments ? node.arguments.arguments : []
3671
+ arguments = indices || (node.arguments ? node.arguments.arguments : [])
1679
3672
  subscripts = read_subscripts(array, arguments, node.location)
1680
3673
  record_subscripts(array, subscripts)
1681
3674
  ElementRead.new(array, subscripts, node.location)
@@ -1685,6 +3678,15 @@ class CArray
1685
3678
  @subscripts[array] << subscripts unless @subscripts[array].include?(subscripts)
1686
3679
  end
1687
3680
 
3681
+ # Kept apart from the reads because the two are asked different
3682
+ # questions: every use of an array decides the box a view is
3683
+ # transferred in, while only the writes say which axes the kernel owns
3684
+ # a cell of.
3685
+ def record_write_subscripts (array, subscripts)
3686
+ list = @write_subscripts[array]
3687
+ list << subscripts unless list.include?(subscripts)
3688
+ end
3689
+
1688
3690
  def array_name (receiver, location)
1689
3691
  name = captured_name(receiver)
1690
3692
  unless name && @array_names.include?(name)
@@ -1769,9 +3771,13 @@ class CArray
1769
3771
  # in scope, so that the offset is a compile-time constant.
1770
3772
  def read_subscript (node, location)
1771
3773
  name = index_name(node)
1772
- return [name, 0] if name && index_in_scope?(name)
3774
+ if name && index_in_scope?(name)
3775
+ return [nil, pinned_subscript(node)] if unbounded_index?(name)
3776
+ return [index_identifier(name), 0]
3777
+ end
1773
3778
  if node.is_a?(Prism::CallNode) && [:+, :-].include?(node.name) &&
1774
3779
  (receiver = index_name(node.receiver)) && index_in_scope?(receiver)
3780
+ return [nil, pinned_subscript(node)] if unbounded_index?(receiver)
1775
3781
  return walked_subscript(node)
1776
3782
  end
1777
3783
  # Anything else pins the axis at a position the loop does not walk:
@@ -1785,6 +3791,30 @@ class CArray
1785
3791
  build(node)
1786
3792
  end
1787
3793
 
3794
+ # An inner index whose range reads a local or a cell. Its range is not
3795
+ # known until the loop is reached, so a subscript it walks cannot be
3796
+ # checked before the kernel runs; it is checked at each access instead,
3797
+ # as `a[b[i]]` is.
3798
+ def unbounded_index? (name)
3799
+ @unbounded_indices.include?(index_identifier(name))
3800
+ end
3801
+
3802
+ # Whether an inner loop's range can be worked out before the kernel
3803
+ # runs: literals, captured integers, the extents, and indices whose own
3804
+ # range can, under `+`, `-` and `*`.
3805
+ def self.boundable_range? (node, unbounded)
3806
+ case node
3807
+ when IntegerLiteral, CaptureRead, BoundsValue then true
3808
+ when IndexVariable then !unbounded.include?(node.name)
3809
+ when UnaryMinus then boundable_range?(node.operand, unbounded)
3810
+ when BinaryOperation
3811
+ [:+, :-, :*].include?(node.operator) &&
3812
+ boundable_range?(node.left, unbounded) &&
3813
+ boundable_range?(node.right, unbounded)
3814
+ else false
3815
+ end
3816
+ end
3817
+
1788
3818
  # The one cell a CScalar has: position zero on its one axis, which is a
1789
3819
  # pinned subscript like any other and is checked like one.
1790
3820
  def cell_subscripts (array, location)
@@ -1840,10 +3870,39 @@ class CArray
1840
3870
  raise Unsupported.new("write the offset as `j - c` with c >= 0",
1841
3871
  node.location)
1842
3872
  end
1843
- return [receiver.name, node.name == :+ ? constant : -constant]
3873
+ return [index_identifier(receiver.name),
3874
+ node.name == :+ ? constant : -constant]
1844
3875
  end
1845
3876
  offset = pinned_subscript(argument)
1846
- [receiver.name, node.name == :+ ? offset : UnaryMinus.new(offset)]
3877
+ refuse_an_index_in_the_offset(node, receiver.name, offset)
3878
+ [index_identifier(receiver.name),
3879
+ node.name == :+ ? offset : UnaryMinus.new(offset)]
3880
+ end
3881
+
3882
+ # `a[i + r]`, where `r` is an inner loop's index: the subscript walks
3883
+ # with `i`, and an offset is worked out once per call, before any cell,
3884
+ # which an index cannot be. Left to the call, it was refused there
3885
+ # for a reason about an inner loop's range -- true of the code that
3886
+ # happened to read it, and nothing to do with what was written. The
3887
+ # same sum put in a local first is a subscript the kernel works out
3888
+ # and checks at the cell, which is what the message points to.
3889
+ def refuse_an_index_in_the_offset (node, walker, offset)
3890
+ others = []
3891
+ visit = lambda { |item|
3892
+ next unless item.is_a?(Node)
3893
+ others << item.name if item.is_a?(IndexVariable)
3894
+ item.children.each(&visit)
3895
+ }
3896
+ visit.call(offset)
3897
+ return if others.empty?
3898
+ names = others.uniq.map { |name| "`#{name}`" }.join(" and ")
3899
+ raise Unsupported.new(
3900
+ "`#{node.slice}` walks with `#{walker}` and is offset by #{names}, " \
3901
+ "an index as well; an offset is worked out once per call, from " \
3902
+ "literals and captured integers -- put the sum in a local first " \
3903
+ "(`k = #{node.slice}`, then `[k]`) and it is worked out and " \
3904
+ "checked at each cell",
3905
+ node.location)
1847
3906
  end
1848
3907
 
1849
3908
  # An index the block names as a parameter reads as a local variable.
@@ -1860,6 +3919,19 @@ class CArray
1860
3919
  end
1861
3920
  end
1862
3921
 
3922
+ # An index as this compiler knows it: the name the block wrote, unless
3923
+ # that name belongs to a second loop of the same name, which has an
3924
+ # identifier of its own.
3925
+ def index_identifier (name)
3926
+ @inner_aliases.fetch(name, name)
3927
+ end
3928
+
3929
+ # The same question asked of an identifier a subscript carries, which
3930
+ # for a second loop of the same name is not the name the block wrote.
3931
+ def index_identifier_in_scope? (name)
3932
+ @outer_names.include?(name) || @inner_aliases.value?(name)
3933
+ end
3934
+
1863
3935
  def index_in_scope? (name)
1864
3936
  @outer_names.include?(name) || @inner_names.include?(name)
1865
3937
  end
@@ -1930,8 +4002,10 @@ class CArray
1930
4002
  # A non-negative literal exponent can still be squared out; anything
1931
4003
  # else has to be asked for in floating point.
1932
4004
  def build_power (receiver, exponent, location)
1933
- base = build(receiver)
1934
- power = build(exponent)
4005
+ power_node(build(receiver), build(exponent), location)
4006
+ end
4007
+
4008
+ def power_node (base, power, location)
1935
4009
  if power.is_a?(IntegerLiteral) && power.value < 0
1936
4010
  raise Unsupported.new(
1937
4011
  "a negative exponent gives a Rational in Ruby; write `1.0 / x ** n`",
@@ -1943,8 +4017,12 @@ class CArray
1943
4017
  def build_math_call (node)
1944
4018
  function = MATH_FUNCTIONS[node.name]
1945
4019
  unless function
1946
- raise Unsupported.new("Math.#{node.name} has no math.h counterpart",
1947
- node.location)
4020
+ if (reason = REFUSED_MATH[node.name])
4021
+ raise Unsupported.new("Math.#{node.name} is not compiled: " \
4022
+ "#{reason}", node.location)
4023
+ end
4024
+ raise Unsupported.new(
4025
+ "Math.#{node.name} is not a name this compiles", node.location)
1948
4026
  end
1949
4027
  arguments = node.arguments ? node.arguments.arguments : []
1950
4028
  expected = [:atan2, :hypot].include?(node.name) ? 2 : 1