carray-jit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +10 -0
  3. data/CHANGELOG.md +84 -0
  4. data/LICENSE +21 -0
  5. data/README.md +88 -0
  6. data/bin/carray-jit +194 -0
  7. data/carray-jit.gemspec +41 -0
  8. data/docs/00_Introduction.md +40 -0
  9. data/docs/01_GettingStarted.md +80 -0
  10. data/docs/02_KernelShapes.md +397 -0
  11. data/docs/03_SupportedFeatures.md +595 -0
  12. data/docs/04_Compiling.md +234 -0
  13. data/docs/05_DesignNotes.md +136 -0
  14. data/docs/06_Cheatsheet.md +177 -0
  15. data/examples/README.md +56 -0
  16. data/examples/applications/game_of_life.rb +161 -0
  17. data/examples/applications/heat_equation.rb +117 -0
  18. data/examples/applications/kepler.rb +178 -0
  19. data/examples/applications/mandelbrot.rb +151 -0
  20. data/examples/applications/moving_average.rb +124 -0
  21. data/examples/applications/partial_sums.rb +141 -0
  22. data/examples/applications/point_cloud.rb +110 -0
  23. data/examples/applications/quicksort.rb +118 -0
  24. data/examples/applications/recursion.rb +121 -0
  25. data/examples/applications/relaxation.rb +115 -0
  26. data/examples/applications/sensor_gaps.rb +118 -0
  27. data/examples/applications/sieve.rb +95 -0
  28. data/examples/applications/sobel_edges.rb +80 -0
  29. data/examples/features/01_element_wise.rb +69 -0
  30. data/examples/features/02_stencil.rb +40 -0
  31. data/examples/features/03_recurrence.rb +50 -0
  32. data/examples/features/04_thomas.rb +81 -0
  33. data/examples/features/05_reduction.rb +90 -0
  34. data/examples/features/06_jit_contract.rb +58 -0
  35. data/examples/features/07_masks.rb +55 -0
  36. data/examples/features/08_views.rb +46 -0
  37. data/examples/features/09_inspecting.rb +55 -0
  38. data/examples/features/10_complex.rb +107 -0
  39. data/examples/features/11_c_functions.rb +260 -0
  40. data/examples/features/12_sweep.rb +139 -0
  41. data/examples/features/13_cscalar.rb +80 -0
  42. data/examples/features/14_stencil_window.rb +106 -0
  43. data/examples/features/15_loops.rb +148 -0
  44. data/examples/features/16_raising.rb +69 -0
  45. data/ext/carray_jit_access/carray_jit_access.c +460 -0
  46. data/ext/carray_jit_access/extconf.rb +8 -0
  47. data/lib/carray/jit/analyzer.rb +1847 -0
  48. data/lib/carray/jit/block_reader.rb +139 -0
  49. data/lib/carray/jit/c_function.rb +777 -0
  50. data/lib/carray/jit/c_generator.rb +2305 -0
  51. data/lib/carray/jit/compiler.rb +468 -0
  52. data/lib/carray/jit/errors.rb +37 -0
  53. data/lib/carray/jit/expression.rb +202 -0
  54. data/lib/carray/jit/kernel.rb +509 -0
  55. data/lib/carray/jit/node.rb +573 -0
  56. data/lib/carray/jit/sweep.rb +97 -0
  57. data/lib/carray/jit/type_assignment.rb +811 -0
  58. data/lib/carray/jit/version.rb +5 -0
  59. data/lib/carray/jit.rb +1210 -0
  60. metadata +139 -0
@@ -0,0 +1,811 @@
1
+ class CArray
2
+ module JIT
3
+
4
+ # Assigns a computation type to every node.
5
+ #
6
+ # This is assignment, not inference: each array's dtype and the runtime
7
+ # classes of the captured scalars fix the leaves, and everything else
8
+ # propagates bottom-up.
9
+ #
10
+ # The distinction that matters is between *storage* type and *computation*
11
+ # type. A Ruby block reading a float32 array gets a Ruby Float -- a
12
+ # double -- computes in double, and rounds back to float32 only when the
13
+ # value is stored. Computing in float instead would diverge from the Ruby
14
+ # evaluator, so reads from any float array are typed :double here and the
15
+ # generator casts once, at the store.
16
+ #
17
+ # cmplx64 stands in the same relation to cmplx128: a cell of either is a
18
+ # Ruby Complex whose parts are Floats, so both are typed :complex and the
19
+ # narrowing happens at the store.
20
+ class TypeAssignment
21
+
22
+ STORAGE_COMPUTATION_TYPES = {
23
+ "float64" => :double,
24
+ "float32" => :float,
25
+ "int64" => :int64,
26
+ "int32" => :int64,
27
+ "int16" => :int64,
28
+ "int8" => :int64,
29
+ # The unsigned widths that int64 holds exactly. uint64 does not --
30
+ # it carries values above 2**63 that int64 cannot represent -- so it
31
+ # computes in a type of its own rather than being quietly wrapped.
32
+ "uint32" => :int64,
33
+ "uint16" => :int64,
34
+ "uint8" => :int64,
35
+ "uint64" => :uint64,
36
+ "boolean" => :boolean,
37
+ "cmplx64" => :float_complex,
38
+ "cmplx128" => :complex,
39
+ }.freeze
40
+
41
+ # What each computation type is, which is the one place a new one is
42
+ # added. Every question a guard asks -- is this an integer, can it be
43
+ # a subscript, will `~` take it -- is asked of the kind rather than of a
44
+ # list of names, so that a type added here is a type every guard already
45
+ # knows about. A list of names is the shape that goes stale silently:
46
+ # it keeps answering, and answers wrongly, for the type nobody added.
47
+ KINDS = {
48
+ :int64 => :integer,
49
+ :uint64 => :integer,
50
+ :float => :real,
51
+ :double => :real,
52
+ :float_complex => :complex,
53
+ :complex => :complex,
54
+ :boolean => :boolean,
55
+ }.freeze
56
+
57
+ # The array `jit_map` allocates for a value of each computation type:
58
+ # the storage that holds what was computed without narrowing it. Here
59
+ # beside KINDS, and not written out where the result is allocated, for
60
+ # the reason KINDS gives: a table of names kept somewhere else keeps
61
+ # answering after a computation type is added, and answers wrongly.
62
+ RESULT_STORAGE_TYPES = {
63
+ :int64 => :int64,
64
+ :uint64 => :uint64,
65
+ :float => :float32,
66
+ :double => :float64,
67
+ :complex => :cmplx128,
68
+ }.freeze
69
+
70
+ # Raises rather than falling back, because there is no type to fall back
71
+ # to: every candidate narrows something. A computation type that is not
72
+ # here is one nobody decided a result for, and saying so is the whole
73
+ # point of asking.
74
+ def self.result_storage_type (type)
75
+ RESULT_STORAGE_TYPES.fetch(type) do
76
+ raise Unsupported,
77
+ "a block whose value is #{type} has no array to be collected " \
78
+ "into"
79
+ end
80
+ end
81
+
82
+ # Narrowest first: the order is how they widen into one another, which
83
+ # is Ruby's own promotion -- `1 + 1.5` is a Float, `1.5 + 1i` a Complex.
84
+ #
85
+ # This one stays a list because it is an order rather than a set: the
86
+ # sets below are derived from KINDS, but which of two types is the wider
87
+ # is not something a kind can answer.
88
+ #
89
+ # :uint64 sits above :int64 rather than beside it, which is not
90
+ # containment -- neither type holds the other -- but is what CArray
91
+ # answers (`CArray.result_type(:uint64, :int64)` is `:uint64`) and what
92
+ # C's usual arithmetic conversions do with the same pair. Ruby has no
93
+ # opinion to follow here: its Integer has no width, so it never reaches
94
+ # the case. Both of the languages that do agree, so the order is
95
+ # theirs.
96
+ NUMERIC_TYPES =
97
+ [:int64, :uint64, :float, :double, :float_complex, :complex].freeze
98
+
99
+ # The types a real number can be, which is every place a complex one
100
+ # cannot go: a subscript, a loop bound, an argument to floor.
101
+ REAL_TYPES =
102
+ NUMERIC_TYPES.select { |type| [:integer, :real].include?(KINDS[type]) }
103
+ .freeze
104
+
105
+ # The types whose `/` floors rather than dividing, whose `**` stays
106
+ # exact, and which a bitwise operator will take. Asking this rather
107
+ # than asking whether a type is :int64 is what lets a second integer
108
+ # type exist at all.
109
+ INTEGER_TYPES =
110
+ NUMERIC_TYPES.select { |type| KINDS[type] == :integer }.freeze
111
+
112
+ # The questions the guards ask. A type KINDS does not know is nothing:
113
+ # every one of these answers false for it, so a guard written as "refuse
114
+ # unless this is a number" refuses it rather than letting it through.
115
+ def self.integer? (type) = KINDS[type] == :integer
116
+ def self.real? (type) = REAL_TYPES.include?(type)
117
+ def self.complex? (type) = KINDS[type] == :complex
118
+ def self.boolean? (type) = KINDS[type] == :boolean
119
+ def self.numeric? (type) = NUMERIC_TYPES.include?(type)
120
+
121
+ def integer? (type) = self.class.integer?(type)
122
+ def real? (type) = self.class.real?(type)
123
+ def complex? (type) = self.class.complex?(type)
124
+ def boolean? (type) = self.class.boolean?(type)
125
+ def numeric? (type) = self.class.numeric?(type)
126
+
127
+ attr_reader :scalar_types
128
+
129
+ # Exposed so that the hot path can build a cache key from the capture
130
+ # values without running a full analysis.
131
+ def self.scalar_type (value)
132
+ case value
133
+ when Float then :double
134
+ when Integer then :int64
135
+ when Complex then :complex
136
+ else
137
+ # A generator captured by name is worth its own answer: it is not a
138
+ # value the kernel could carry, and what to do instead is the same
139
+ # thing `rand` inside the block is told.
140
+ if value.is_a?(Random) || value.equal?(Random) || value.equal?(Kernel)
141
+ raise Unsupported,
142
+ "a generator draws in an order a kernel does not fix; fill " \
143
+ "an array with `CArray#random!` and read a cell of it, as " \
144
+ "the kernel reads any other array"
145
+ end
146
+ raise Unsupported,
147
+ "captured scalars must be Float, Integer or Complex, got " \
148
+ "#{value.class}"
149
+ end
150
+ end
151
+
152
+ def self.storage_type (name)
153
+ return STORAGE_COMPUTATION_TYPES[name] if STORAGE_COMPUTATION_TYPES[name]
154
+ raise Unsupported, "unsupported array data type `#{name}`"
155
+ end
156
+
157
+ # `scalar_types` is given rather than derived when the leaves are
158
+ # declared instead of captured -- a function's parameters are named in
159
+ # its signature, and there is no value to read a type off.
160
+ def initialize (body, storage_types, scalar_values, c_functions = {},
161
+ scalar_types: nil, pointer_types: {})
162
+ @body = body
163
+ @c_functions = c_functions
164
+ @pointer_types = pointer_types
165
+ @element_types = storage_types.transform_values { |name|
166
+ self.class.storage_type(name)
167
+ }
168
+ @scalar_types = scalar_types || scalar_values.transform_values { |value|
169
+ self.class.scalar_type(value)
170
+ }
171
+ @bindings = {}
172
+ @binding_counts = Hash.new(0)
173
+ assign
174
+ end
175
+
176
+ private
177
+
178
+ # Types are settled in one forward pass, because that is how Ruby reads
179
+ # the body: a local holds whatever it was last assigned.
180
+ #
181
+ # That matters when the type changes. `x = 5; y = x / 2; x = 1.5` is an
182
+ # integer division in Ruby and a float one after, and no single C
183
+ # variable is both, so each type gets a variable of its own.
184
+ def assign
185
+ walk(@body)
186
+ verify(@body)
187
+ end
188
+
189
+ def bind (name, type)
190
+ current = @bindings[name]
191
+ return current.last if current && current.first == type
192
+ @binding_counts[name] += 1
193
+ suffix = @binding_counts[name] == 1 ? "" : "__#{@binding_counts[name]}"
194
+ @bindings[name] = [type, :"#{name}#{suffix}"]
195
+ @bindings[name].last
196
+ end
197
+
198
+ def walk (node)
199
+ case node
200
+ when KernelBody
201
+ node.statements.each { |statement| walk(statement) }
202
+ when Assignment
203
+ walk(node.expression)
204
+ node.type = node.expression.type
205
+ node.binding_name = bind(node.name, node.type)
206
+ when ElementWrite
207
+ walk_subscripts(write_subscripts(node))
208
+ walk(node.expression)
209
+ node.type = element_type(node.array, node.location)
210
+ when MaskWrite
211
+ walk_subscripts(write_subscripts(node))
212
+ node.type = element_type(node.array, node.location)
213
+ when MaskTest
214
+ walk_subscripts(node.subscripts)
215
+ node.type = :boolean
216
+ when InnerLoop
217
+ walk(node.from)
218
+ walk(node.to)
219
+ entering = @bindings.dup
220
+ node.statements.each { |statement| walk(statement) }
221
+ verify_loop_carries_one_type(entering, node)
222
+ when While
223
+ # The condition first, and that is not only an order: a name the
224
+ # body introduces is not bound yet when the condition is walked, so
225
+ # `while x < 3` over an `x` the body assigns is refused there rather
226
+ # than reading whatever C left in the variable on the first pass.
227
+ walk(node.condition)
228
+ entering = @bindings.dup
229
+ node.statements.each { |statement| walk(statement) }
230
+ verify_loop_carries_one_type(entering, node)
231
+ when Print
232
+ node.arguments.each { |argument| walk(argument) }
233
+ when CallStatement
234
+ # The arguments are typed as they would be anywhere; the result is
235
+ # not, because nothing takes it. That is what lets a `void`
236
+ # function be called at all: its return type is a slot in the
237
+ # signature, and a slot nobody reads is no obstacle.
238
+ call = node.call
239
+ call.arguments.each { |argument| walk(argument) }
240
+ call.type =
241
+ case call
242
+ when RecursiveCall then call.result_type
243
+ else @c_functions.fetch(call.name).discarded_result_type
244
+ end
245
+ node.type = :void
246
+ when Raise
247
+ # A message, settled when the kernel was compiled.
248
+ when LoopSkip, LoopStop
249
+ # Control flow, carrying no value and so no type.
250
+ when Branch
251
+ # Each arm is typed from the state before the branch, and a local
252
+ # whose type ends up different on the two paths has no single C
253
+ # variable to be, so it does not survive the branch.
254
+ walk(node.condition)
255
+ before = @bindings.dup
256
+ node.consequent.each { |statement| walk(statement) }
257
+ from_consequent = @bindings
258
+ @bindings = before.dup
259
+ node.alternative.each { |statement| walk(statement) }
260
+ @bindings = merge_bindings(from_consequent, @bindings)
261
+ when IntegerLiteral
262
+ node.type = :int64
263
+ when FloatLiteral
264
+ node.type = :double
265
+ when ImaginaryLiteral
266
+ node.type = :complex
267
+ when BooleanLiteral
268
+ node.type = :boolean
269
+ when IndexVariable
270
+ node.type = :int64
271
+ when BoundsValue
272
+ node.type = :int64
273
+ when ZeroLike
274
+ walk(node.reference)
275
+ node.type = node.reference.type
276
+ when ElementRead
277
+ walk_subscripts(node.subscripts)
278
+ node.type = element_type(node.array, node.location)
279
+ when CaptureRead
280
+ type = @scalar_types[node.name]
281
+ unless type
282
+ raise Unsupported.new("no value supplied for `#{node.name}`", node.location)
283
+ end
284
+ node.type = type
285
+ when LocalRead
286
+ current = @bindings[node.name]
287
+ unless current
288
+ raise Unsupported.new("`#{node.name}` is read before it is assigned",
289
+ node.location)
290
+ end
291
+ node.type, node.binding_name = current
292
+ when UnaryMinus
293
+ walk(node.operand)
294
+ node.type = node.operand.type
295
+ when AbsoluteValue
296
+ walk(node.operand)
297
+ # `Complex#abs` is the magnitude, and a Float -- unlike the other
298
+ # unary operators, abs does not keep the type it was given.
299
+ node.type = complex?(node.operand.type) ? :double : node.operand.type
300
+ when Conversion
301
+ walk(node.operand)
302
+ # `floor`, `ceil`, `round` and `to_i` name int64 as what they
303
+ # produce, which is the right answer for a Float and the wrong one
304
+ # for a uint64: Ruby's Integer#floor is the number itself, so a
305
+ # width may not be lost on the way through. An integer operand
306
+ # keeps its own type and the conversion is nothing to emit.
307
+ node.type =
308
+ if node.result_type == :int64 && integer?(node.operand.type)
309
+ node.operand.type
310
+ else
311
+ node.result_type
312
+ end
313
+ when ComplexPart
314
+ walk(node.operand)
315
+ node.type = complex_part_type(node)
316
+ when ComplexBuild
317
+ node.children.each { |child| walk(child) }
318
+ node.type = :complex
319
+ when LogicalOperation
320
+ node.children.each { |child| walk(child) }
321
+ node.type = :boolean
322
+ when LogicalNot
323
+ walk(node.operand)
324
+ node.type = :boolean
325
+ when BitwiseNot
326
+ walk(node.operand)
327
+ node.type = node.operand.type
328
+ when Power
329
+ walk(node.base)
330
+ walk(node.exponent)
331
+ node.type = join_operands(node.base, node.exponent)
332
+ when MathCall
333
+ node.arguments.each { |argument| walk(argument) }
334
+ node.type = math_call_type(node)
335
+ when PointerRead
336
+ walk(node.index)
337
+ # Declared, not inferred: the prototype said what it points at.
338
+ node.type = @pointer_types.fetch(node.name)
339
+ when PointerWrite
340
+ walk(node.index)
341
+ walk(node.expression)
342
+ node.type = @pointer_types.fetch(node.name)
343
+ when ArrayAddress
344
+ # An address is not a value; nothing computes with it, and the only
345
+ # place it may stand is a C function's pointer parameter.
346
+ node.type = :address
347
+ when RecursiveCall
348
+ # The declaration already said, the same way it says for a captured
349
+ # function; there is nothing here to infer from a body that is
350
+ # still being walked.
351
+ node.arguments.each { |argument| walk(argument) }
352
+ if node.result_type.nil?
353
+ raise Unsupported.new(
354
+ "`#{node.name}` returns `void`, which is a slot in the " \
355
+ "signature rather than a value a kernel can compute with; a " \
356
+ "call to it may stand where a statement stands",
357
+ node.location)
358
+ end
359
+ node.type = node.result_type
360
+ when CFunctionCall
361
+ # Nothing is inferred here: the prototype said what the function
362
+ # returns and what it takes, and the arguments are converted to
363
+ # meet it. That is the same rule the rest of this file follows --
364
+ # types are assigned from the leaves, not solved for.
365
+ node.arguments.each { |argument| walk(argument) }
366
+ function = @c_functions.fetch(node.name)
367
+ # Named by the local the block reached it by rather than by the
368
+ # symbol it compiled to, which is the name a reader of the block
369
+ # can look for.
370
+ if function.discarded_result_type == :void
371
+ raise Unsupported.new(
372
+ "`#{node.name}` returns `void`, which is a slot in the " \
373
+ "signature rather than a value a kernel can compute with; a " \
374
+ "call to it may stand where a statement stands",
375
+ node.location)
376
+ end
377
+ node.type = function.result_type
378
+ when Conditional
379
+ node.children.each { |child| walk(child) }
380
+ node.type = join_operands(node.consequent, node.alternative)
381
+ when BinaryOperation
382
+ walk(node.left)
383
+ walk(node.right)
384
+ node.type =
385
+ if Analyzer::COMPARISON_OPERATORS.include?(node.operator)
386
+ :boolean
387
+ elsif [:<<, :>>].include?(node.operator)
388
+ # A shift takes its count as a number and keeps the type of the
389
+ # thing being shifted.
390
+ node.left.type
391
+ else
392
+ join_operands(node.left, node.right)
393
+ end
394
+ else
395
+ raise Error, "type assignment reached #{node.class}"
396
+ end
397
+ end
398
+
399
+ # A local that is carried across a loop's back edge has to be the same
400
+ # type on the way round as it was on the way in. The forward pass reads
401
+ # the body once, the way Ruby reads it -- but Ruby reads it again on the
402
+ # next pass, with whatever the body left behind, and `x = 2` outside a
403
+ # loop with `x = 1.5` inside it means an integer division on the first
404
+ # pass and a float one after. One C variable cannot be both, and this
405
+ # is not the branch case where the local simply does not survive: the
406
+ # value goes round the loop.
407
+ def verify_loop_carries_one_type (entering, node)
408
+ entering.each do |name, (type, _)|
409
+ now = @bindings[name]
410
+ next if now.nil? || now.first == type
411
+ raise Unsupported.new(
412
+ "`#{name}` enters this loop as #{article(type)} and comes back " \
413
+ "round as #{article(now.first)}; the value carried to the next " \
414
+ "pass would change type, and one C variable is one type -- give " \
415
+ "it one type before the loop",
416
+ node.location)
417
+ end
418
+ end
419
+
420
+ # What to call a type in a message. These are Ruby's names where Ruby
421
+ # has one; :uint64 keeps CArray's, because the thing that distinguishes
422
+ # it from :int64 is a width, and Ruby's Integer has none -- a message
423
+ # that called both of them "an Integer" would be saying the two types in
424
+ # a mismatch are the same type.
425
+ NAMES = { :int64 => "Integer", :uint64 => "uint64", :double => "Float",
426
+ :float => "float32", :complex => "Complex",
427
+ :float_complex => "cmplx64" }.freeze
428
+
429
+ def self.name_of (type)
430
+ NAMES.fetch(type, type.to_s)
431
+ end
432
+
433
+ def article (type)
434
+ type == :int64 ? "an Integer" : "a #{self.class.name_of(type)}"
435
+ end
436
+
437
+ # The parts of a real number, which Ruby answers rather than refusing:
438
+ # `1.5.real` is 1.5 and `1.5.imaginary` is an Integer zero.
439
+ #
440
+ # `arg` is the one that does not settle: Ruby hands back an Integer zero
441
+ # for a number that is not negative and Math::PI for one that is, so its
442
+ # class depends on the value. One C variable is one type, so it is a
443
+ # Float throughout -- the same number either way, since the zero is a
444
+ # zero whichever class carries it.
445
+ # The real width that goes with a complex one: the parts of a cmplx64
446
+ # are float32s, as CArray's own `real`, `imag`, `abs` and `arg` say.
447
+ REAL_PART_TYPES = { :complex => :double, :float_complex => :float }.freeze
448
+
449
+ def complex_part_type (node)
450
+ if complex?(node.operand.type)
451
+ return node.operand.type if node.name == :conjugate
452
+ return REAL_PART_TYPES.fetch(node.operand.type)
453
+ end
454
+ case node.name
455
+ when :real, :conjugate then node.operand.type
456
+ when :imaginary then :int64
457
+ else :double
458
+ end
459
+ end
460
+
461
+ # A math function of a complex argument is complex, and the ones with
462
+ # no complex form say so rather than silently taking the real part --
463
+ # which is what C would do if the call were emitted as it stands.
464
+ def math_call_type (node)
465
+ unless node.arguments.any? { |a| complex?(a.type) }
466
+ # The width the arguments agree on. An integer has none to offer --
467
+ # `Math.sqrt(2)` is a Float in Ruby -- so it widens to double, and a
468
+ # float32 argument keeps float32, which is the width CArray computes
469
+ # the same call at.
470
+ joined = node.arguments.map(&:type).reduce { |a, b| join(a, b) }
471
+ return integer?(joined) ? :double : joined
472
+ end
473
+ if node.arguments.size > 1
474
+ raise Unsupported.new(
475
+ "`#{node.name}` takes two real numbers; a complex number is " \
476
+ "already the plane it is asking about", node.location)
477
+ end
478
+ unless Analyzer::COMPLEX_MATH_FUNCTIONS.key?(node.name)
479
+ raise Unsupported.new(
480
+ "`#{node.name}` has no complex form -- C99 has none and a " \
481
+ "complex CArray refuses it too", node.location)
482
+ end
483
+ # As wide as the argument, and reached at that width: CArray computes
484
+ # a cmplx64 through the f-suffixed complex library rather than through
485
+ # double, so a kernel does too.
486
+ node.arguments.find { |a| complex?(a.type) }.type
487
+ end
488
+
489
+ # A write into the cell the loop is on has no subscripts of its own.
490
+ def write_subscripts (node)
491
+ node.respond_to?(:subscripts) && node.subscripts ? node.subscripts : []
492
+ end
493
+
494
+ # A subscript's expression sits beside the tree rather than in it: the
495
+ # position a pinned axis is held at, or the offset a walked one is read
496
+ # away from.
497
+ def walk_subscripts (subscripts)
498
+ subscripts.each do |_index, offset|
499
+ next unless offset.is_a?(Node)
500
+ walk(offset)
501
+ next if integer?(offset.type)
502
+ raise Unsupported.new(
503
+ "a subscript is an integer; this one is #{offset.type}",
504
+ offset.location)
505
+ end
506
+ end
507
+
508
+ def element_type (array, location)
509
+ @element_types[array] or
510
+ raise Unsupported.new("no array supplied for `#{array}`", location)
511
+ end
512
+
513
+ def merge_bindings (left, right)
514
+ (left.keys & right.keys).each_with_object({}) do |name, merged|
515
+ merged[name] = left[name] if left[name] == right[name]
516
+ end
517
+ end
518
+
519
+ # Integer and Float mix to Float, as in Ruby.
520
+ # `&`, `|` and `^` join two integers or two booleans, and a shift moves
521
+ # an integer -- which is what they do in Ruby, where `1.5 & 1` raises.
522
+ def verify_bitwise (node)
523
+ left, right = node.left.type, node.right.type
524
+ if [:&, :|, :^].include?(node.operator) &&
525
+ boolean?(left) && boolean?(right)
526
+ return
527
+ end
528
+ return if integer?(left) && integer?(right)
529
+ raise Unsupported.new(
530
+ "`#{node.operator}` joins two integers#{
531
+ [:&, :|, :^].include?(node.operator) ? ' or two booleans' : ''
532
+ }, as in Ruby, and got #{left} and #{right}",
533
+ node.location)
534
+ end
535
+
536
+ # `true` and `false` compare with each other and with nothing else.
537
+ #
538
+ # `flags[i] == 1` is the one worth a message of its own: Ruby answers it
539
+ # rather than raising, and the answer is always false, so compiling it
540
+ # would be compiling a bug into C. It is an easy one to write -- the
541
+ # reference implementation this project started from had it, and its
542
+ # results were all NaN because of it.
543
+ def verify_comparable (node)
544
+ left, right = node.left.type, node.right.type
545
+ if (complex?(left) || complex?(right)) &&
546
+ ![:==, :!=].include?(node.operator)
547
+ raise Unsupported.new(
548
+ "`#{node.operator}` does not order Complex numbers, in Ruby " \
549
+ "either; compare `.abs` or `.real`", node.location)
550
+ end
551
+ return unless boolean?(left) || boolean?(right)
552
+ if boolean?(left) && boolean?(right)
553
+ return if [:==, :!=].include?(node.operator)
554
+ raise Unsupported.new(
555
+ "`true` and `false` do not compare with `#{node.operator}`, in " \
556
+ "Ruby either", node.location)
557
+ end
558
+ if [:==, :!=].include?(node.operator)
559
+ raise Unsupported.new(
560
+ "a boolean cell compares with `true` and `false`, not with a " \
561
+ "number: in Ruby `flags[i] == 1` is false whatever the cell " \
562
+ "holds. Write `if flags[i]` or `flags[i] == true`",
563
+ node.location)
564
+ end
565
+ raise Unsupported.new(
566
+ "a boolean cell does not compare with a number, in Ruby either",
567
+ node.location)
568
+ end
569
+
570
+ # A boolean array holds true and false, and a numeric one holds numbers.
571
+ # Ruby says the same: `flags[i] = 1.0` raises, and `values[i] = true`
572
+ # raises too. The one crossing Ruby does allow is `flags[i] = 1`, which
573
+ # CArray takes for true -- and only 0 and 1, which is why it has to be a
574
+ # literal here.
575
+ def verify_storable (node)
576
+ wanted = @element_types.fetch(node.array, nil)
577
+ given = node.expression.type
578
+ return if boolean?(wanted) && boolean?(given)
579
+ # A Complex does not fit in a real cell, and Ruby says so: assigning
580
+ # one into a float64 CArray raises rather than dropping the imaginary
581
+ # part. The other direction is fine -- a real number is a Complex
582
+ # whose imaginary part is zero.
583
+ if complex?(given) && real?(wanted)
584
+ raise Unsupported.new(
585
+ "`#{node.array}` holds real numbers, and the value stored into " \
586
+ "it is a Complex; storing one into a real CArray raises in Ruby " \
587
+ "too. Store `.real`, `.imag` or `.abs`",
588
+ node.location)
589
+ end
590
+ return if !boolean?(wanted) && numeric?(given)
591
+ if boolean?(wanted) && node.expression.is_a?(IntegerLiteral) &&
592
+ [0, 1].include?(node.expression.value)
593
+ return
594
+ end
595
+ if boolean?(wanted)
596
+ raise Unsupported.new(
597
+ "`#{node.array}` is a boolean array, so it holds `true` and " \
598
+ "`false`; storing #{given} into it is what Ruby refuses too",
599
+ node.location)
600
+ end
601
+ raise Unsupported.new(
602
+ "the value stored into `#{node.array}` is " \
603
+ "#{given || 'undetermined'}, not a number",
604
+ node.location)
605
+ end
606
+
607
+ # The nodes that arrive without a data type of their own: a literal and
608
+ # a captured Ruby Numeric. A CScalar is not one of them -- it is a
609
+ # one-cell array, so it has a dtype and says what it is.
610
+ WEAK_NODES = [IntegerLiteral, FloatLiteral, ImaginaryLiteral,
611
+ CaptureRead].freeze
612
+
613
+ # Integer < Float < Complex, which is the order a Ruby Numeric's class
614
+ # sits in and the only thing about a bare one that is settled.
615
+ KIND_RANK = { :integer => 0, :real => 1, :complex => 2 }.freeze
616
+
617
+ def weak? (node)
618
+ WEAK_NODES.any? { |kind| node.is_a?(kind) }
619
+ end
620
+
621
+ # Two operands meeting, where one of them may be a bare Numeric.
622
+ #
623
+ # CArray has two rules here and they are not the same rule. Array
624
+ # against array is `CArray.result_type`, which is what #join spells out
625
+ # as an order. A bare Numeric against an array is absorption: the
626
+ # scalar takes the array's dtype rather than the array widening to meet
627
+ # it, so `f32 * 2.0` is float32 where the order alone would say double.
628
+ #
629
+ # What the array gives the scalar is a width, never a kind, so the two
630
+ # have to be the same kind for anything to be given. `i32 * 2.0` is a
631
+ # float64 and `f32 * 1i` a cmplx128 -- the scalar\'s own Ruby type, since
632
+ # the array has no narrower one of that kind to offer. And a real
633
+ # scalar against a complex array stays real, which is not a width
634
+ # question at all: Ruby\'s `z + x` adds to the real part and leaves the
635
+ # imaginary one alone, sign included, and that is a different operation
636
+ # from adding a complex number that happens to have a zero in it.
637
+ def join_operands (left, right)
638
+ return join(left.type, right.type) if weak?(left) == weak?(right)
639
+ scalar, typed = weak?(left) ? [left, right] : [right, left]
640
+ scalar_kind, typed_kind = KINDS[scalar.type], KINDS[typed.type]
641
+ unless KIND_RANK.key?(scalar_kind) && KIND_RANK.key?(typed_kind)
642
+ return join(left.type, right.type)
643
+ end
644
+ # The wider kind wins, as it does between two arrays. What absorption
645
+ # settles is the tie: at the same kind the scalar takes the other
646
+ # side's width rather than the other side widening to Ruby's.
647
+ return scalar.type if KIND_RANK[scalar_kind] > KIND_RANK[typed_kind]
648
+ if scalar_kind == typed_kind
649
+ # The scalar becomes that type rather than being widened to meet it,
650
+ # and says so: a literal emitted as a double would take the C
651
+ # expression back to double however this node is typed.
652
+ scalar.type = typed.type
653
+ end
654
+ typed.type
655
+ end
656
+
657
+ def join (left, right)
658
+ return right if left.nil?
659
+ return left if right.nil?
660
+ return left if left == right
661
+ if numeric?(left) && numeric?(right)
662
+ return NUMERIC_TYPES[
663
+ [NUMERIC_TYPES.index(left), NUMERIC_TYPES.index(right)].max]
664
+ end
665
+ # `true` is not a number in Ruby either: `flags[i] + 1` raises, even
666
+ # though the array-level `flags + 1` promotes. A kernel is the cell
667
+ # loop, so the cell's answer is the one it has to give.
668
+ raise Unsupported, "cannot combine types #{left} and #{right}"
669
+ end
670
+
671
+ def verify (node)
672
+ case node
673
+ when Print
674
+ node.arguments.each { |argument| verify(argument) }
675
+ when CallStatement
676
+ node.call.arguments.each { |argument| verify(argument) }
677
+ when Raise
678
+ # Nothing to check: no value is computed.
679
+ when MaskWrite
680
+ # Nothing to check: no value is computed.
681
+ when MaskTest
682
+ # Nothing to check: it reads a mask byte.
683
+ when While
684
+ verify(node.condition)
685
+ unless boolean?(node.condition.type)
686
+ raise Unsupported.new("a condition must be a comparison",
687
+ node.condition.location)
688
+ end
689
+ node.statements.each { |statement| verify(statement) }
690
+ when InnerLoop
691
+ [node.from, node.to].each do |bound|
692
+ verify(bound)
693
+ unless integer?(bound.type)
694
+ raise Unsupported.new("an inner loop's range must be integers",
695
+ bound.location)
696
+ end
697
+ end
698
+ node.statements.each { |statement| verify(statement) }
699
+ when Branch
700
+ unless boolean?(node.condition.type)
701
+ raise Unsupported.new("a condition must be a comparison",
702
+ node.condition.location)
703
+ end
704
+ node.children.each { |child| verify(child) }
705
+ when ElementWrite
706
+ verify(node.expression)
707
+ verify_storable(node)
708
+ when LocalRead
709
+ unless node.type
710
+ raise Unsupported.new("`#{node.name}` is read before it is assigned",
711
+ node.location)
712
+ end
713
+ when Conditional
714
+ unless boolean?(node.condition.type)
715
+ raise Unsupported.new("a condition must be a comparison",
716
+ node.condition.location)
717
+ end
718
+ node.children.each { |child| verify(child) }
719
+ when LogicalOperation, LogicalNot
720
+ node.children.each { |child| verify(child) }
721
+ unless node.children.all? { |child| boolean?(child.type) }
722
+ raise Unsupported.new(
723
+ "`#{node.is_a?(LogicalNot) ? '!' : node.operator}` combines " \
724
+ "comparisons, not numbers",
725
+ node.location)
726
+ end
727
+ when AbsoluteValue
728
+ verify(node.operand)
729
+ unless numeric?(node.operand.type)
730
+ raise Unsupported.new("`AbsoluteValue` needs a number",
731
+ node.location)
732
+ end
733
+ when Conversion
734
+ verify(node.operand)
735
+ unless numeric?(node.operand.type)
736
+ raise Unsupported.new("`Conversion` needs a number", node.location)
737
+ end
738
+ # `Complex(1,2).floor` and `.to_f` raise in Ruby -- a complex number
739
+ # has no place on the line these round to.
740
+ if complex?(node.operand.type)
741
+ raise Unsupported.new(
742
+ "`#{node.name || 'to_f'}` has no meaning for a Complex, and " \
743
+ "raises in Ruby; take `.real` or `.abs` first", node.location)
744
+ end
745
+ when ComplexBuild
746
+ node.children.each { |child| verify(child) }
747
+ unless node.children.all? { |child| real?(child.type) }
748
+ raise Unsupported.new(
749
+ "the parts of a Complex are real numbers", node.location)
750
+ end
751
+ when ComplexPart
752
+ verify(node.operand)
753
+ unless numeric?(node.operand.type)
754
+ raise Unsupported.new(
755
+ "`#{node.ruby_name}` is asked of a number; this one is " \
756
+ "#{article(node.operand.type)}", node.location)
757
+ end
758
+ when BinaryOperation
759
+ node.children.each { |child| verify(child) }
760
+ verify_comparable(node) if
761
+ Analyzer::COMPARISON_OPERATORS.include?(node.operator)
762
+ verify_bitwise(node) if
763
+ Analyzer::BIT_OPERATORS.include?(node.operator)
764
+ # `Complex(1,2) % 2` is a NoMethodError in Ruby: a floored
765
+ # remainder needs an order, and the plane has none.
766
+ if node.operator == :% && complex?(node.type)
767
+ raise Unsupported.new(
768
+ "`%` has no meaning for a Complex, and raises in Ruby",
769
+ node.location)
770
+ end
771
+ when BitwiseNot
772
+ verify(node.operand)
773
+ unless integer?(node.operand.type)
774
+ raise Unsupported.new("`~` needs an integer, as in Ruby",
775
+ node.location)
776
+ end
777
+ when Power
778
+ node.children.each { |child| verify(child) }
779
+ unless numeric?(node.type)
780
+ raise Unsupported.new("`**` operands must be numbers", node.location)
781
+ end
782
+ if integer?(node.type) &&
783
+ !node.exponent.is_a?(IntegerLiteral)
784
+ raise Unsupported.new(
785
+ "an integer raised to a variable power overflows int64 where " \
786
+ "Ruby would not; make one of them a Float",
787
+ node.location)
788
+ end
789
+ when BinaryOperation
790
+ node.children.each { |child| verify(child) }
791
+ if Analyzer::COMPARISON_OPERATORS.include?(node.operator)
792
+ unless numeric?(node.left.type) &&
793
+ numeric?(node.right.type)
794
+ raise Unsupported.new("comparison operands must be numbers",
795
+ node.location)
796
+ end
797
+ else
798
+ unless numeric?(node.type)
799
+ raise Unsupported.new("`#{node.operator}` operands must be numbers",
800
+ node.location)
801
+ end
802
+ end
803
+ else
804
+ node.children.each { |child| verify(child) }
805
+ end
806
+ end
807
+
808
+ end
809
+
810
+ end
811
+ end