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.
- checksums.yaml +7 -0
- data/.yardopts +10 -0
- data/CHANGELOG.md +84 -0
- data/LICENSE +21 -0
- data/README.md +88 -0
- data/bin/carray-jit +194 -0
- data/carray-jit.gemspec +41 -0
- data/docs/00_Introduction.md +40 -0
- data/docs/01_GettingStarted.md +80 -0
- data/docs/02_KernelShapes.md +397 -0
- data/docs/03_SupportedFeatures.md +595 -0
- data/docs/04_Compiling.md +234 -0
- data/docs/05_DesignNotes.md +136 -0
- data/docs/06_Cheatsheet.md +177 -0
- data/examples/README.md +56 -0
- data/examples/applications/game_of_life.rb +161 -0
- data/examples/applications/heat_equation.rb +117 -0
- data/examples/applications/kepler.rb +178 -0
- data/examples/applications/mandelbrot.rb +151 -0
- data/examples/applications/moving_average.rb +124 -0
- data/examples/applications/partial_sums.rb +141 -0
- data/examples/applications/point_cloud.rb +110 -0
- data/examples/applications/quicksort.rb +118 -0
- data/examples/applications/recursion.rb +121 -0
- data/examples/applications/relaxation.rb +115 -0
- data/examples/applications/sensor_gaps.rb +118 -0
- data/examples/applications/sieve.rb +95 -0
- data/examples/applications/sobel_edges.rb +80 -0
- data/examples/features/01_element_wise.rb +69 -0
- data/examples/features/02_stencil.rb +40 -0
- data/examples/features/03_recurrence.rb +50 -0
- data/examples/features/04_thomas.rb +81 -0
- data/examples/features/05_reduction.rb +90 -0
- data/examples/features/06_jit_contract.rb +58 -0
- data/examples/features/07_masks.rb +55 -0
- data/examples/features/08_views.rb +46 -0
- data/examples/features/09_inspecting.rb +55 -0
- data/examples/features/10_complex.rb +107 -0
- data/examples/features/11_c_functions.rb +260 -0
- data/examples/features/12_sweep.rb +139 -0
- data/examples/features/13_cscalar.rb +80 -0
- data/examples/features/14_stencil_window.rb +106 -0
- data/examples/features/15_loops.rb +148 -0
- data/examples/features/16_raising.rb +69 -0
- data/ext/carray_jit_access/carray_jit_access.c +460 -0
- data/ext/carray_jit_access/extconf.rb +8 -0
- data/lib/carray/jit/analyzer.rb +1847 -0
- data/lib/carray/jit/block_reader.rb +139 -0
- data/lib/carray/jit/c_function.rb +777 -0
- data/lib/carray/jit/c_generator.rb +2305 -0
- data/lib/carray/jit/compiler.rb +468 -0
- data/lib/carray/jit/errors.rb +37 -0
- data/lib/carray/jit/expression.rb +202 -0
- data/lib/carray/jit/kernel.rb +509 -0
- data/lib/carray/jit/node.rb +573 -0
- data/lib/carray/jit/sweep.rb +97 -0
- data/lib/carray/jit/type_assignment.rb +811 -0
- data/lib/carray/jit/version.rb +5 -0
- data/lib/carray/jit.rb +1210 -0
- 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
|