carray-jit 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +771 -3
  3. data/README.md +7 -6
  4. data/carray-jit.gemspec +1 -3
  5. data/docs/00_Introduction.md +4 -3
  6. data/docs/01_GettingStarted.md +1 -1
  7. data/docs/02_KernelShapes.md +93 -14
  8. data/docs/03_SupportedFeatures.md +582 -26
  9. data/docs/04_Compiling.md +33 -6
  10. data/docs/05_DesignNotes.md +3 -3
  11. data/docs/06_Cheatsheet.md +198 -5
  12. data/docs/07_StepByStep.ja.md +534 -0
  13. data/docs/07_StepByStep.md +535 -0
  14. data/examples/README.md +12 -0
  15. data/examples/applications/alarm.rb +121 -0
  16. data/examples/applications/collatz.rb +105 -0
  17. data/examples/applications/cubic_spline.rb +331 -0
  18. data/examples/applications/dithering.rb +144 -0
  19. data/examples/applications/group_stats.rb +115 -0
  20. data/examples/applications/lookup.rb +126 -0
  21. data/examples/applications/median_filter.rb +153 -0
  22. data/examples/applications/parcel_ascent.rb +220 -0
  23. data/examples/applications/point_in_polygon.rb +111 -0
  24. data/examples/applications/random_walk.rb +98 -0
  25. data/examples/applications/van_der_pol.rb +186 -0
  26. data/examples/applications/wet_bulb.rb +140 -0
  27. data/examples/features/10_complex.rb +14 -4
  28. data/examples/features/15_loops.rb +7 -1
  29. data/lib/carray/jit/access.rb +14 -0
  30. data/lib/carray/jit/analyzer.rb +2077 -136
  31. data/lib/carray/jit/block_reader.rb +37 -6
  32. data/lib/carray/jit/c_function.rb +613 -76
  33. data/lib/carray/jit/c_generator.rb +1595 -156
  34. data/lib/carray/jit/call.rb +68 -0
  35. data/lib/carray/jit/compiler.rb +75 -11
  36. data/lib/carray/jit/kernel.rb +369 -32
  37. data/lib/carray/jit/node.rb +359 -9
  38. data/lib/carray/jit/sorting_networks.rb +182 -0
  39. data/lib/carray/jit/type_assignment.rb +371 -34
  40. data/lib/carray/jit/version.rb +1 -1
  41. data/lib/carray/jit.rb +560 -64
  42. metadata +22 -8
  43. data/ext/carray_jit_access/carray_jit_access.c +0 -460
  44. data/ext/carray_jit_access/extconf.rb +0 -8
@@ -115,12 +115,16 @@ class CArray
115
115
  # every one of these answers false for it, so a guard written as "refuse
116
116
  # unless this is a number" refuses it rather than letting it through.
117
117
  def self.integer? (type) = KINDS[type] == :integer
118
+ # A Float, and not an Integer standing on the same line: `nan?` is
119
+ # asked of one and refused by the other, as Ruby refuses it.
120
+ def self.floating? (type) = KINDS[type] == :real
118
121
  def self.real? (type) = REAL_TYPES.include?(type)
119
122
  def self.complex? (type) = KINDS[type] == :complex
120
123
  def self.boolean? (type) = KINDS[type] == :boolean
121
124
  def self.numeric? (type) = NUMERIC_TYPES.include?(type)
122
125
 
123
126
  def integer? (type) = self.class.integer?(type)
127
+ def floating? (type) = self.class.floating?(type)
124
128
  def real? (type) = self.class.real?(type)
125
129
  def complex? (type) = self.class.complex?(type)
126
130
  def boolean? (type) = self.class.boolean?(type)
@@ -133,17 +137,24 @@ class CArray
133
137
  def self.scalar_type (value)
134
138
  case value
135
139
  when Float then :double
136
- when Integer then :int64
140
+ when Integer then integer_type(value)
137
141
  when Complex then :complex
138
142
  else
139
143
  # A generator captured by name is worth its own answer: it is not a
140
144
  # value the kernel could carry, and what to do instead is the same
141
145
  # thing `rand` inside the block is told.
142
- if value.is_a?(Random) || value.equal?(Random) || value.equal?(Kernel)
146
+ #
147
+ # `::Random` rather than `Random`, though this file is inside
148
+ # `class CArray` and nothing there shadows it. A generator of
149
+ # CArray's own is `CArray::Rng` precisely so that it does not, and
150
+ # writing the `::` keeps the question this asks legible from the
151
+ # line rather than from that fact.
152
+ if value.is_a?(::Random) || value.equal?(::Random) ||
153
+ value.equal?(Kernel)
143
154
  raise Unsupported,
144
155
  "a generator draws in an order a kernel does not fix; fill " \
145
- "an array with `CArray#random!` and read a cell of it, as " \
146
- "the kernel reads any other array"
156
+ "an array with `CArray#random!` and read a cell of it, or " \
157
+ "draw from `CArray::Rng.new`, which a kernel can reach"
147
158
  end
148
159
  raise Unsupported,
149
160
  "captured scalars must be Float, Integer or Complex, got " \
@@ -151,6 +162,32 @@ class CArray
151
162
  end
152
163
  end
153
164
 
165
+ # Which of the two integer widths a captured Integer travels in. Ruby's
166
+ # Integer has none, so the value decides: int64 while it fits one, and
167
+ # uint64 above that, which is the width CArray has for those values and
168
+ # the one a kernel already computes them in. A value that fits neither
169
+ # is refused here rather than packed, because packing it is what would
170
+ # not say so -- `pack("q")` takes 2**64 modulo the width and hands back
171
+ # a zero.
172
+ #
173
+ # This is the value deciding a type, which nothing else about a capture
174
+ # does, and it is sound only because the value is in the kernel's cache
175
+ # key: 5 and 2**63 + 5 are two kernels, compiled and cached apart.
176
+ INT64_MIN = -(2**63)
177
+ INT64_MAX = 2**63 - 1
178
+ UINT64_MAX = 2**64 - 1
179
+
180
+ def self.integer_type (value)
181
+ return :int64 if value >= INT64_MIN && value <= INT64_MAX
182
+ return :uint64 if value >= 0 && value <= UINT64_MAX
183
+ raise Unsupported,
184
+ "#{value} is outside the integers a kernel computes in -- an " \
185
+ "int64 down to -2**63, a uint64 up to 2**64-1 -- and Ruby's " \
186
+ "Integer, which has no width, is the only thing here that can " \
187
+ "hold it. Capture it as a Float where the value's precision " \
188
+ "allows one"
189
+ end
190
+
154
191
  def self.storage_type (name)
155
192
  return STORAGE_COMPUTATION_TYPES[name] if STORAGE_COMPUTATION_TYPES[name]
156
193
  raise Unsupported, "unsupported array data type `#{name}`"
@@ -172,6 +209,12 @@ class CArray
172
209
  }
173
210
  @bindings = {}
174
211
  @binding_counts = Hash.new(0)
212
+ # The scope nodes open around the statement being typed, outermost
213
+ # first, which is what an assignment's scope counts along.
214
+ @scope_nodes = []
215
+ # A name a `while` gave its first value, by that `while`: after the
216
+ # loop it may not have one, and the refusal says why.
217
+ @assigned_in_while = {}
175
218
  assign
176
219
  end
177
220
 
@@ -188,23 +231,126 @@ class CArray
188
231
  verify(@body)
189
232
  end
190
233
 
234
+ # A local's bindings are numbered per name, from 1: `x` at its first
235
+ # type is binding 1 and at its second binding 2. What it is spelled in
236
+ # C is the generator's business.
191
237
  def bind (name, type)
192
238
  current = @bindings[name]
193
239
  return current.last if current && current.first == type
194
240
  @binding_counts[name] += 1
195
- suffix = @binding_counts[name] == 1 ? "" : "__#{@binding_counts[name]}"
196
- @bindings[name] = [type, :"#{name}#{suffix}"]
241
+ @bindings[name] = [type, @binding_counts[name]]
197
242
  @bindings[name].last
198
243
  end
199
244
 
245
+ # A local is declared once per C variable, at the head of the block for
246
+ # its scope. A binding the scope already declared is not declared again.
247
+ def declare (assignment)
248
+ scope = @scope_nodes.fetch(assignment.scope)
249
+ entry = [assignment.name, assignment.binding, assignment.type]
250
+ scope.declarations << entry unless scope.declarations.include?(entry)
251
+ end
252
+
253
+ # The same for an array the body made. Writing the same constructor
254
+ # twice in one scope is one C array and one declaration; what it emits
255
+ # at each statement is the clearing, which is the generator's.
256
+ def declare_array (declaration)
257
+ scope = @scope_nodes.fetch(declaration.scope)
258
+ entry = [declaration.name, declaration.binding,
259
+ declaration.storage, declaration.shape, declaration.heap]
260
+ scope.array_declarations << entry unless scope.array_declarations.include?(entry)
261
+ end
262
+
263
+ # An array's binding is its name's, and what counts as the same one is
264
+ # the storage type together with the shape: `w` at two shapes is two C
265
+ # arrays, exactly as `x` at two types is two C variables.
266
+ def bind_array (declaration)
267
+ bind(declaration.name,
268
+ [:local_array, declaration.storage, declaration.shape])
269
+ end
270
+
271
+ # Which of the name's variables a read or a write of an array resolves
272
+ # to. The analyzer has already refused a name that holds a number here
273
+ # and an array there, so what is left to say is that the array reached
274
+ # for is still bound -- which is what a branch that made it in one arm
275
+ # only leaves untrue.
276
+ def array_binding (node)
277
+ current = @bindings[node.name]
278
+ wanted = [:local_array, node.storage, node.shape]
279
+ unless current && current.first == wanted
280
+ raise Unsupported.new(
281
+ "`#{node.name}` is not an array here: it is made on only one path " \
282
+ "to this line, or at another shape, so there is no one C array " \
283
+ "for it to be. Make it before the branch",
284
+ node.location)
285
+ end
286
+ current.last
287
+ end
288
+
200
289
  def walk (node)
201
290
  case node
202
291
  when KernelBody
292
+ node.declarations = []
293
+ node.array_declarations = []
294
+ @scope_nodes.push(node)
203
295
  node.statements.each { |statement| walk(statement) }
296
+ @scope_nodes.pop
204
297
  when Assignment
205
298
  walk(node.expression)
206
299
  node.type = node.expression.type
207
- node.binding_name = bind(node.name, node.type)
300
+ node.binding = bind(node.name, node.type)
301
+ @assigned_in_while.delete(node.name)
302
+ declare(node)
303
+ when ParallelAssignment
304
+ # The values were made statements of their own by the analyzer, and
305
+ # stand before the writes. Walking them in that order is what gives
306
+ # a name on the left the binding it had on the right: `a, b = b, a`
307
+ # reads `b` while `b` is still the old one, because the statement
308
+ # that reads it comes first.
309
+ node.statements.each { |statement| walk(statement) }
310
+ when LocalArrayDeclaration
311
+ # A declaration has no value, so it has no type: what it says is
312
+ # what to put at the head of the block. An extent the block wrote
313
+ # as an expression is an expression, and is typed as one.
314
+ node.children.each { |extent| walk(extent) }
315
+ node.binding = bind_array(node)
316
+ @assigned_in_while.delete(node.name)
317
+ declare_array(node)
318
+ when LocalArrayAddress
319
+ # The address of an array the body made. Its binding is what says
320
+ # which C name the callee is handed; there is no value here, so no
321
+ # computation type either -- the declaration settled what it is.
322
+ node.binding = array_binding(node)
323
+ node.type = :address
324
+ when IntrinsicCall
325
+ node.binding = array_binding(node)
326
+ node.type = self.class.storage_type(node.storage)
327
+ verify_intrinsic_element_type(node)
328
+ when IntrinsicStatement
329
+ node.binding = array_binding(node)
330
+ # What it did is in the array, so there is no value and no type --
331
+ # the same slot a call to a `void` function leaves empty.
332
+ node.type = :void
333
+ verify_intrinsic_element_type(node)
334
+ when LocalArrayRead
335
+ node.binding = array_binding(node)
336
+ walk_local_array_subscripts(node)
337
+ node.type = self.class.storage_type(node.storage)
338
+ when LocalArrayWrite
339
+ node.binding = array_binding(node)
340
+ walk_local_array_subscripts(node)
341
+ walk(node.expression)
342
+ node.type = self.class.storage_type(node.storage)
343
+ when LocalArrayMaskWrite
344
+ # A mark rather than a value: it says the cell is missing and
345
+ # leaves the bytes under it alone, so there is nothing to type
346
+ # beyond the cell's own storage.
347
+ node.binding = array_binding(node)
348
+ walk_local_array_subscripts(node)
349
+ node.type = self.class.storage_type(node.storage)
350
+ when LocalArrayMaskTest
351
+ node.binding = array_binding(node)
352
+ walk_local_array_subscripts(node)
353
+ node.type = :boolean
208
354
  when ElementWrite
209
355
  walk_subscripts(write_subscripts(node))
210
356
  walk(node.expression)
@@ -215,12 +361,29 @@ class CArray
215
361
  when MaskTest
216
362
  walk_subscripts(node.subscripts)
217
363
  node.type = :boolean
364
+ when NumericPredicate
365
+ walk(node.operand)
366
+ node.type = :boolean
367
+ when Clamp
368
+ node.children.each { |child| walk(child) }
369
+ # The receiver's, which is exact where the three agree -- and
370
+ # where they do not, verify refuses rather than choosing.
371
+ node.type = node.value.type
218
372
  when InnerLoop
219
373
  walk(node.from)
220
374
  walk(node.to)
221
375
  entering = @bindings.dup
376
+ node.entering = entering
377
+ node.declarations = []
378
+ node.array_declarations = []
379
+ @scope_nodes.push(node)
222
380
  node.statements.each { |statement| walk(statement) }
381
+ @scope_nodes.pop
223
382
  verify_loop_carries_one_type(entering, node)
383
+ # What the block made is the block's. Past it, the names outside
384
+ # are what they were on the way in, carrying whatever type the
385
+ # body left them -- which the check above says is the same one.
386
+ @bindings = @bindings.select { |name, _| entering.key?(name) }
224
387
  when While
225
388
  # The condition first, and that is not only an order: a name the
226
389
  # body introduces is not bound yet when the condition is walked, so
@@ -230,6 +393,12 @@ class CArray
230
393
  entering = @bindings.dup
231
394
  node.statements.each { |statement| walk(statement) }
232
395
  verify_loop_carries_one_type(entering, node)
396
+ # The body may run no passes, so what it gave a first value to may
397
+ # have none after it -- Ruby's nil, and a C variable nothing wrote.
398
+ (@bindings.keys - entering.keys).each do |name|
399
+ @assigned_in_while[name] = node
400
+ end
401
+ @bindings = merge_bindings(entering, @bindings)
233
402
  when Print
234
403
  node.arguments.each { |argument| walk(argument) }
235
404
  when CallStatement
@@ -286,11 +455,17 @@ class CArray
286
455
  node.type = type
287
456
  when LocalRead
288
457
  current = @bindings[node.name]
458
+ if !current && (loop = @assigned_in_while[node.name])
459
+ raise Unsupported.new(
460
+ "`#{node.name}` is first assigned inside this `while`, which may " \
461
+ "run no passes; give it a value before the loop",
462
+ loop.location)
463
+ end
289
464
  unless current
290
465
  raise Unsupported.new("`#{node.name}` is read before it is assigned",
291
466
  node.location)
292
467
  end
293
- node.type, node.binding_name = current
468
+ node.type, node.binding = current
294
469
  when UnaryMinus
295
470
  walk(node.operand)
296
471
  node.type = node.operand.type
@@ -359,6 +534,11 @@ class CArray
359
534
  node.location)
360
535
  end
361
536
  node.type = node.result_type
537
+ when RandomDraw
538
+ # What the generator's C returns for this draw. There is nothing
539
+ # to infer and no argument to convert: a draw reads the state and
540
+ # nothing the block wrote.
541
+ node.type = node.kind == :bits ? :uint64 : :double
362
542
  when CFunctionCall
363
543
  # Nothing is inferred here: the prototype said what the function
364
544
  # returns and what it takes, and the arguments are converted to
@@ -507,6 +687,48 @@ class CArray
507
687
  end
508
688
  end
509
689
 
690
+ # Which element types each intrinsic has an answer for.
691
+ #
692
+ # `min` / `max` / `sort` put the cells in an order, and neither a
693
+ # Complex nor a boolean has one -- Ruby refuses `1i < 2i` and
694
+ # `true < false` as well. `sum` adds, which a boolean does not do
695
+ # either: CArray reads the sum of a boolean array as a count, and
696
+ # counting is a meaning this compiler would be borrowing rather than
697
+ # deciding. Write the count into an integer array and sum that.
698
+ def verify_intrinsic_element_type (node)
699
+ type = self.class.storage_type(node.storage)
700
+ if boolean?(type)
701
+ if node.intrinsic == :sum
702
+ raise Unsupported.new(
703
+ "`sum` adds numbers, and `#{node.name}` is a boolean array; " \
704
+ "CArray reads the sum of one as a count, which is a meaning " \
705
+ "this would be borrowing rather than deciding. Count into an " \
706
+ "integer array and sum that",
707
+ node.location)
708
+ end
709
+ raise Unsupported.new(
710
+ "`#{node.name}` holds `true` and `false`, which do not order " \
711
+ "each other -- in Ruby either -- so `#{node.intrinsic}` has " \
712
+ "nothing to compare",
713
+ node.location)
714
+ end
715
+ return unless complex?(type)
716
+ return if node.intrinsic == :sum
717
+ raise Unsupported.new(
718
+ "`#{node.intrinsic}` orders the cells, and `#{node.name}` holds " \
719
+ "Complex numbers: Ruby does not order Complex numbers either. " \
720
+ "Take `.abs` or `.real` into a real array first",
721
+ node.location)
722
+ end
723
+
724
+ # A local array's subscripts are walked the way an operand's are: a
725
+ # position that is an expression has to be an integer. An axis the
726
+ # analyzer settled carries a literal offset rather than a node, and
727
+ # there is nothing there to type.
728
+ def walk_local_array_subscripts (node)
729
+ walk_subscripts(node.subscripts)
730
+ end
731
+
510
732
  def element_type (array, location)
511
733
  @element_types[array] or
512
734
  raise Unsupported.new("no array supplied for `#{array}`", location)
@@ -574,9 +796,11 @@ class CArray
574
796
  # raises too. The one crossing Ruby does allow is `flags[i] = 1`, which
575
797
  # CArray takes for true -- and only 0 and 1, which is why it has to be a
576
798
  # literal here.
577
- def verify_storable (node)
578
- wanted = @element_types.fetch(node.array, nil)
579
- given = node.expression.type
799
+ # What may be stored where, said about a storage type and a name rather
800
+ # than about an operand: an array the body made has a storage type of
801
+ # its own and no entry in the operand table.
802
+ def verify_storable (name, wanted, expression, location)
803
+ given = expression.type
580
804
  return if boolean?(wanted) && boolean?(given)
581
805
  # A Complex does not fit in a real cell, and Ruby says so: assigning
582
806
  # one into a float64 CArray raises rather than dropping the imaginary
@@ -584,26 +808,26 @@ class CArray
584
808
  # whose imaginary part is zero.
585
809
  if complex?(given) && real?(wanted)
586
810
  raise Unsupported.new(
587
- "`#{node.array}` holds real numbers, and the value stored into " \
811
+ "`#{name}` holds real numbers, and the value stored into " \
588
812
  "it is a Complex; storing one into a real CArray raises in Ruby " \
589
813
  "too. Store `.real`, `.imag` or `.abs`",
590
- node.location)
814
+ location)
591
815
  end
592
816
  return if !boolean?(wanted) && numeric?(given)
593
- if boolean?(wanted) && node.expression.is_a?(IntegerLiteral) &&
594
- [0, 1].include?(node.expression.value)
817
+ if boolean?(wanted) && expression.is_a?(IntegerLiteral) &&
818
+ [0, 1].include?(expression.value)
595
819
  return
596
820
  end
597
821
  if boolean?(wanted)
598
822
  raise Unsupported.new(
599
- "`#{node.array}` is a boolean array, so it holds `true` and " \
823
+ "`#{name}` is a boolean array, so it holds `true` and " \
600
824
  "`false`; storing #{given} into it is what Ruby refuses too",
601
- node.location)
825
+ location)
602
826
  end
603
827
  raise Unsupported.new(
604
- "the value stored into `#{node.array}` is " \
828
+ "the value stored into `#{name}` is " \
605
829
  "#{given || 'undetermined'}, not a number",
606
- node.location)
830
+ location)
607
831
  end
608
832
 
609
833
  # The nodes that arrive without a data type of their own: a literal and
@@ -648,6 +872,7 @@ class CArray
648
872
  # side's width rather than the other side widening to Ruby's.
649
873
  return scalar.type if KIND_RANK[scalar_kind] > KIND_RANK[typed_kind]
650
874
  if scalar_kind == typed_kind
875
+ refuse_wide_capture(scalar, typed)
651
876
  # The scalar becomes that type rather than being widened to meet it,
652
877
  # and says so: a literal emitted as a double would take the C
653
878
  # expression back to double however this node is typed.
@@ -656,6 +881,31 @@ class CArray
656
881
  typed.type
657
882
  end
658
883
 
884
+ # A captured Integer above int64 meeting an integer, which absorption
885
+ # has no answer for: the scalar is supposed to take the other side's
886
+ # width, and this value did not come from a width -- it came from a Ruby
887
+ # Integer, which has none.
888
+ #
889
+ # CArray refuses the same expression, `u + 2**63` raising `bignum too
890
+ # big to convert into 'long long'` whatever the array's own type is, and
891
+ # this follows it rather than inventing an answer beside it. Where the
892
+ # other side is a Float or a Complex there is nothing to refuse: the
893
+ # wider kind wins, CArray converts the same way, and the value reaches a
894
+ # double as it does in Ruby.
895
+ def refuse_wide_capture (scalar, typed)
896
+ return unless scalar.is_a?(CaptureRead) && scalar.type == :uint64
897
+ width = typed.type == :uint64 ? "a uint64" : "an int64"
898
+ raise Unsupported.new(
899
+ "`#{scalar.name}` is a captured Integer above 2**63-1, and it is " \
900
+ "meeting #{width}: a captured Integer brings no width of its own -- " \
901
+ "it takes the width of what it meets -- and this value came from " \
902
+ "none. CArray refuses the same expression, whatever the array's own " \
903
+ "type is. Give the value a width: `CScalar.uint64() " \
904
+ "{ #{scalar.name} }`, which a kernel reads as the one-cell array it " \
905
+ "is",
906
+ scalar.location)
907
+ end
908
+
659
909
  def join (left, right)
660
910
  return right if left.nil?
661
911
  return left if right.nil?
@@ -682,6 +932,58 @@ class CArray
682
932
  # Nothing to check: no value is computed.
683
933
  when MaskTest
684
934
  # Nothing to check: it reads a mask byte.
935
+ when Clamp
936
+ node.children.each { |child| verify(child) }
937
+ types = node.children.map(&:type)
938
+ unless types.all? { |type| numeric?(type) }
939
+ raise Unsupported.new("`clamp` orders numbers", node.location)
940
+ end
941
+ if complex?(node.value.type)
942
+ raise Unsupported.new(
943
+ "`clamp` has no meaning for a Complex, and raises " \
944
+ "NoMethodError in Ruby; take `.abs` or `.real` first",
945
+ node.location)
946
+ end
947
+ # Ruby hands back the receiver in one branch and a bound in the
948
+ # other, so where the two are different *classes* the answer's
949
+ # class is decided by the value: `1.clamp(0.0, 3.0)` is the
950
+ # Integer 1 and `5.clamp(0.0, 3.0)` the Float 3.0. No type
951
+ # assigned here is both, so that is refused rather than settled
952
+ # one way and wrong the other.
953
+ #
954
+ # Two widths of the same class are not that case. A float32 cell
955
+ # is a Ruby Float, as a double is, so `f[i].clamp(0.0, 1.0)` has
956
+ # one class throughout and the answer is the receiver's width --
957
+ # the rule every other expression over that cell already takes.
958
+ unless types.map { |type| integer?(type) }.uniq.size == 1
959
+ value, bound = if integer?(node.value.type)
960
+ ["an Integer", "a Float"]
961
+ else
962
+ ["a Float", "an Integer"]
963
+ end
964
+ raise Unsupported.new(
965
+ "`clamp` hands back the value or one of the bounds, so with " \
966
+ "#{value} value and #{bound} bound Ruby's answer is one class " \
967
+ "in one branch and the other in the other; write the bounds " \
968
+ "#{integer?(node.value.type) ? 'as Integers' : 'with a decimal point'}",
969
+ node.location)
970
+ end
971
+ when NumericPredicate
972
+ verify(node.operand)
973
+ unless numeric?(node.operand.type)
974
+ raise Unsupported.new("`#{node.name}` asks about a number",
975
+ node.location)
976
+ end
977
+ # `nan?` is Float's alone: an Integer has no NaN to be and a
978
+ # Complex has no such method, and both raise NoMethodError in Ruby
979
+ # rather than answering false. `finite?` is on all three.
980
+ if node.name == :nan? && !floating?(node.operand.type)
981
+ raise Unsupported.new(
982
+ "`nan?` is a Float's question -- " \
983
+ "#{complex?(node.operand.type) ? 'a Complex' : 'an Integer'} " \
984
+ "has no method by that name and raises NoMethodError in Ruby",
985
+ node.location)
986
+ end
685
987
  when While
686
988
  verify(node.condition)
687
989
  unless boolean?(node.condition.type)
@@ -689,6 +991,8 @@ class CArray
689
991
  node.condition.location)
690
992
  end
691
993
  node.statements.each { |statement| verify(statement) }
994
+ when ParallelAssignment
995
+ node.statements.each { |statement| verify(statement) }
692
996
  when InnerLoop
693
997
  [node.from, node.to].each do |bound|
694
998
  verify(bound)
@@ -706,7 +1010,46 @@ class CArray
706
1010
  node.children.each { |child| verify(child) }
707
1011
  when ElementWrite
708
1012
  verify(node.expression)
709
- verify_storable(node)
1013
+ verify_storable(node.array, @element_types.fetch(node.array, nil),
1014
+ node.expression, node.location)
1015
+ when LocalArrayDeclaration
1016
+ # No value is computed here. What there can be is an extent the
1017
+ # block wrote as an expression, and a length is a whole number of
1018
+ # cells -- a Float or a Complex one is not a length at all.
1019
+ node.shape.each_with_index do |extent, axis|
1020
+ next if extent.is_a?(Integer)
1021
+ verify(extent.node)
1022
+ next if integer?(extent.node.type)
1023
+ where = node.shape.size == 1 ? "" : " on axis #{axis}"
1024
+ raise Unsupported.new(
1025
+ "`#{extent.text}` is the length of `#{node.name}`#{where}, and " \
1026
+ "a length is a whole number of cells; this is " \
1027
+ "#{extent.node.type == :boolean ? 'a true or false' : "a #{extent.node.type}"}",
1028
+ node.location)
1029
+ end
1030
+ when LocalArrayAddress
1031
+ # Nothing to check: the declaration was matched against the shape
1032
+ # and the storage type as the block was read.
1033
+ when IntrinsicCall, IntrinsicStatement
1034
+ # The element type was checked as the types were assigned, and the
1035
+ # argument is an array rather than an expression: there is nothing
1036
+ # under here to walk.
1037
+ when LocalArrayRead
1038
+ node.subscripts.each { |_index, offset|
1039
+ verify(offset) if offset.is_a?(Node)
1040
+ }
1041
+ when LocalArrayMaskWrite, LocalArrayMaskTest
1042
+ # Nothing to check: one writes a mask byte and the other reads one.
1043
+ node.subscripts.each { |_index, offset|
1044
+ verify(offset) if offset.is_a?(Node)
1045
+ }
1046
+ when LocalArrayWrite
1047
+ node.subscripts.each { |_index, offset|
1048
+ verify(offset) if offset.is_a?(Node)
1049
+ }
1050
+ verify(node.expression)
1051
+ verify_storable(node.name, self.class.storage_type(node.storage),
1052
+ node.expression, node.location)
710
1053
  when LocalRead
711
1054
  unless node.type
712
1055
  raise Unsupported.new("`#{node.name}` is read before it is assigned",
@@ -763,6 +1106,14 @@ class CArray
763
1106
  Analyzer::COMPARISON_OPERATORS.include?(node.operator)
764
1107
  verify_bitwise(node) if
765
1108
  Analyzer::BIT_OPERATORS.include?(node.operator)
1109
+ # `true * true` is a NoMethodError in Ruby. Said here, where the
1110
+ # operator is, rather than wherever the boolean it made ends up.
1111
+ if Analyzer::ARITHMETIC_OPERATORS.include?(node.operator) &&
1112
+ !numeric?(node.type)
1113
+ raise Unsupported.new(
1114
+ "`#{node.operator}` is arithmetic, and `true` and `false` have " \
1115
+ "none, in Ruby either", node.location)
1116
+ end
766
1117
  # `Complex(1,2) % 2` is a NoMethodError in Ruby: a floored
767
1118
  # remainder needs an order, and the plane has none.
768
1119
  if node.operator == :% && complex?(node.type)
@@ -788,20 +1139,6 @@ class CArray
788
1139
  "Ruby would not; make one of them a Float",
789
1140
  node.location)
790
1141
  end
791
- when BinaryOperation
792
- node.children.each { |child| verify(child) }
793
- if Analyzer::COMPARISON_OPERATORS.include?(node.operator)
794
- unless numeric?(node.left.type) &&
795
- numeric?(node.right.type)
796
- raise Unsupported.new("comparison operands must be numbers",
797
- node.location)
798
- end
799
- else
800
- unless numeric?(node.type)
801
- raise Unsupported.new("`#{node.operator}` operands must be numbers",
802
- node.location)
803
- end
804
- end
805
1142
  else
806
1143
  node.children.each { |child| verify(child) }
807
1144
  end
@@ -1,5 +1,5 @@
1
1
  class CArray
2
2
  module JIT
3
- VERSION = "0.1.2"
3
+ VERSION = "0.1.3"
4
4
  end
5
5
  end